Smoke rounds 3-4 (owner feedback). Checkpoint during iteration. Ambient (the big one): rewrote ambient.wgsl from translating gaussian blobs to a METABALL field (r²/d² summed + smoothstep iso-surface) with 2-octave domain warp — lobes fuse/stretch/split like an amoeba / lava lamp (the Cider motion that was missing), faster clock, more contrast. Surfaces made background-aware (translucent when the dynamic background is on): - Search results view (root + toolbar), artist Network panel + its tab-row header (transparent to avoid double-translucent compounding), JUMP TO bar (JumpNavBar bar-bg passed in — can't import ShellState without a cycle). - Discover toolbars + tab pill + BrowseHeaderTools; SearchResultsView toolbar. - CircleAction (shared: playlist/album/artist/etc. action buttons) on-surface secondary fill goes glass; playlist Sort dropdown; QbzSelect closed control. - New app-background-bar-alpha knob (0.3, env QBZ_BG_BAR_ALPHA): thin bars go MORE transparent than panels (surface-alpha 0.5). - Content well: frosted tint (0.22) + 1px hairline border → distinct panel. - Album/Artist header gradient auto-suppressed under the dynamic background (they clashed); header text stays light on the dynamic backdrop. - Discover carousel edge-fade "shadows" hidden when active (Carousel / PlaylistCarousel / ArtistCarousel / PinnedCarousel). Base:76ea5a82. Prior checkpoint:4e405338. cc
133 lines
5.8 KiB
Plaintext
133 lines
5.8 KiB
Plaintext
// 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, ShellState, AppearanceState } from "../state.slint";
|
|
|
|
export component CircleAction inherits Rectangle {
|
|
in property <image> icon;
|
|
in property <bool> primary: false;
|
|
// Stays "pressed" — same surface as hover, icon tinted accent.
|
|
in property <bool> active: false;
|
|
// While the album's first track is fetching, the icon swaps for a spinner.
|
|
in property <bool> 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 <length> 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 <bool> enabled: true;
|
|
// Optional hover tooltip (empty = none), using the global TooltipOverlay.
|
|
in property <string> 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 <bool> 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
|
|
: (ShellState.app-background-active
|
|
? Theme.surface-elevated.with-alpha(AppearanceState.app-background-surface-alpha)
|
|
: 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;
|
|
}
|
|
}
|
|
}
|
|
}
|