// Circular header/action button — matches the album-card hover OVERLAY // buttons: a semi-transparent white fill (stable hit surface) with a bright // 1.5px ring, white icon; `primary` is a solid white circle with a dark icon // (the play affordance). Shared by the album/artist/label page headers, their // Popular Tracks clusters, and the ephemeral pane. import { Theme } from "../foundation/semantic-colors.slint"; import { QbzIcon } from "QbzIcon.slint"; import { LoadingSpinner } from "LoadingSpinner.slint"; import { TooltipState } from "../state.slint"; export component CircleAction inherits Rectangle { in property icon; in property primary: false; // Stays "pressed" — same surface as hover, icon tinted accent. in property active: false; // While the album's first track is fetching, the icon swaps for a spinner. in property loading: false; // Homologated header sizing — the size hierarchy lives HERE, not in each // caller: the primary (Play) button is prominent (44px), every secondary // action is 80% of the old 40px default (32px). Callers no longer set // `diameter` for header buttons; an explicit override is only for special // contexts (e.g. a small card-overlay play). in property diameter: root.primary ? 44px : 32px; // When false the button dims and the click is gated (e.g. an empty // collection's hero CTAs). Additive — existing callers default to enabled. in property enabled: true; // Optional hover tooltip (empty = none), using the global TooltipOverlay. in property tooltip; // Theme-aware variant for header rows rendered WITHOUT the dark // atmosphere (album-header-gradient OFF): the default light-on-dark // palette (white fills/rings/icons) vanishes on light themes' plain // surface. `on-surface: true` swaps to the app's standard on-page // circular look (MixView pattern): accent play disc with a white glyph, // elevated-surface ghost buttons with a themed ring and text-primary // icon. Additive — every existing caller keeps the overlay palette. in property on-surface: false; callback clicked(); width: root.diameter; height: root.diameter; border-radius: root.diameter / 2; opacity: root.enabled ? 1.0 : 0.4; // Overlay-matched contrast: solid white for the primary play, semi- // transparent white fill + bright 1.5px ring otherwise. The on-surface // variant mirrors the theme-aware circular buttons used on plain page // backgrounds instead. background: root.on-surface ? (root.primary ? (ta.has-hover ? Theme.accent-hover : Theme.accent) : ((ta.has-hover || root.active) ? Theme.surface-hover : Theme.surface-elevated)) : (root.primary ? (ta.has-hover ? #ffffffd6 : #ffffff) : ((ta.has-hover || root.active) ? #ffffff3d : #ffffff24)); border-width: root.primary ? 0px : 1.5px; border-color: root.on-surface ? Theme.border-strong : #ffffffcc; if !root.loading: QbzIcon { source: root.icon; // Glyph scales with the tier so the smaller secondary buttons don't // look icon-heavy (primary keeps the larger 19px glyph). width: root.primary ? 19px : 15px; height: root.primary ? 19px : 15px; x: Math.round((parent.width - self.width) / 2 / 1px) * 1px; y: Math.round((parent.height - self.height) / 2 / 1px) * 1px; tint: root.active ? Theme.accent : root.on-surface ? (root.primary ? #ffffff : Theme.text-primary) : (root.primary ? #000000 : #ffffff); } if root.loading: LoadingSpinner { size: root.diameter * 0.45; // Dark on the solid white primary disc, white on the ghost variant; // themed tints for the on-surface variant. tint: root.primary ? (root.on-surface ? #ffffff : #000000) : (root.on-surface ? Theme.text-primary : #ffffff); x: Math.round((parent.width - self.width) / 2 / 1px) * 1px; y: Math.round((parent.height - self.height) / 2 / 1px) * 1px; } // Keyboard activation: Enter/Space fire the same click. The TouchArea below // stays the pointer source; this only adds key activation + focusability. fs := FocusScope { enabled: root.enabled; key-pressed(event) => { if (event.text == Key.Return || event.text == " ") { root.clicked(); return accept; } return reject; } } // Non-layout focus ring — transparent-bordered overlay shown only on // keyboard focus, circular to match the button so it never reflows. Rectangle { x: 0; y: 0; width: parent.width; height: parent.height; background: transparent; border-width: 2px; border-color: fs.has-focus ? Theme.focus-ring : transparent; border-radius: root.diameter / 2; } ta := TouchArea { mouse-cursor: root.enabled ? pointer : default; clicked => { if (root.enabled) { root.clicked(); } } changed has-hover => { if (self.has-hover && root.tooltip != "") { TooltipState.text = root.tooltip; TooltipState.anchor-x = root.absolute-position.x + root.width / 2; TooltipState.anchor-y = root.absolute-position.y; TooltipState.show = true; } else if (!self.has-hover) { TooltipState.show = false; } } } }