Files
qbz/crates/qbz-ui/ui/settings/BlacklistManagerView.slint
T
vicrodh 4e40533870 feat(shell): dynamic background shows through content area + livelier ambient
Smoke round 2 (owner feedback: content area was an opaque black slab, motion
imperceptible, ambient≈blurred).

- Content-area sweep: 27 content-view roots go transparent when the app
  background is active (was opaque Theme.surface-main covering the background) —
  Home/Discover/Album/Artist/Search-adjacent/Favorites/Playlist/LocalLibrary/
  MyQBZ/managers/etc. Content well (AppShell) transparent too.
- SongCard (NPB classic), search input box, cortinilla panel go translucent so
  they stop reading as opaque slabs over the background.
- ambient.wgsl: much faster + bigger blob travel + stronger domain warp +
  brighter/more saturated so the flow is obviously moving and clearly distinct
  from the blurred-art mode.
- Defaults: dim 0.35, surface-alpha 0.5.

Checkpoint during smoke iteration (owner-requested). Base: 76ea5a82.

cc
2026-07-17 02:42:59 -06:00

903 lines
37 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Artist Blacklist Manager — a faithful Slint port of Tauri's
// BlacklistManagerView.svelte (consolidated review §7). The standalone
// manage-blacklist surface reached from the Settings content-filtering row:
// a header (Back + ban icon + title), a description line, a controls row
// (enable/disable toggle / search-as-you-type with × clear / Clear-All /
// total-count badge), an amber disabled-warning banner, and a 4-branch body
// (loading / empty / no-search-results / list), plus a Clear-All confirm
// modal overlaid above everything (ADR-009 — declared last so it stacks on
// top; the manager itself has no other overlay).
//
// State + actions live in BlacklistState / BlacklistActions; the controller
// (crate::blacklist_manager) precomputes the date string + has-notes flag and
// pushes ONLY the search-filtered rows into `items`, while `count` stays the
// FULL list count — so "empty" = count==0 and "no results" = count>0 with an
// empty `items`. Rows show a generic blind-eye glyph (no artist image exists),
// the name, "Added {date}", optional italic notes, and a per-row remove X
// (optimistic, no confirm). The blacklist visual across the Slint port is
// `blind-eye.svg` (TrackRow + ArtistPageView, T5/T9), used here too instead of
// Tauri's lucide `Ban`.
import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint";
import { BlacklistState, BlacklistActions, UiFocusState, ShellState } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
import { ListScrollbar } from "../primitives/ListScrollbar.slint";
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
import { NavButtons } from "../shell/NavButtons.slint";
// One blacklisted-artist row.
component ArtistRow inherits Rectangle {
in property <string> artist-name;
in property <string> added-display;
in property <string> notes;
in property <bool> has-notes;
property <bool> hovered: ta.has-hover || rm-ta.has-hover;
callback select();
callback remove();
callback hover-changed(bool);
height: 64px;
border-radius: Radius.md;
background: root.hovered ? Theme.surface-elevated : Theme.surface-card;
changed hovered => {
root.hover-changed(self.hovered);
}
HorizontalLayout {
padding-left: 16px;
padding-right: 8px;
spacing: 12px;
// Clickable artist-info region — opens the artist page.
Rectangle {
horizontal-stretch: 1;
ta := TouchArea {
mouse-cursor: pointer;
clicked => {
root.select();
}
}
HorizontalLayout {
spacing: 12px;
// Generic blind-eye avatar (no artist image data exists).
VerticalLayout {
alignment: center;
Rectangle {
width: 44px;
height: 44px;
border-radius: 22px;
background: Theme.surface-elevated;
QbzIcon {
source: @image-url("../assets/icons/blind-eye.svg");
width: 20px;
height: 20px;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
tint: Theme.text-muted;
}
}
}
// Name + meta (+ optional notes).
VerticalLayout {
alignment: center;
spacing: 2px;
Text {
text: root.artist-name;
color: Theme.text-primary;
font-size: Typography.body;
font-weight: Typography.medium;
overflow: elide;
}
Text {
text: @tr("Added {}", root.added-display);
color: Theme.text-muted;
font-size: Typography.legal;
overflow: elide;
}
if root.has-notes: Text {
text: root.notes;
color: Theme.text-muted;
font-size: Typography.legal;
font-italic: true;
overflow: elide;
}
}
}
}
// Per-row remove (optimistic, no confirm).
VerticalLayout {
alignment: center;
Rectangle {
width: 36px;
height: 36px;
border-radius: Radius.sm;
background: rm-ta.has-hover ? Theme.danger-bg : transparent;
QbzIcon {
source: @image-url("../assets/icons/x.svg");
width: 18px;
height: 18px;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
tint: rm-ta.has-hover ? Theme.danger : Theme.text-muted;
}
rm-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
root.remove();
}
}
}
}
}
}
// One blocked-album row (parallel to ArtistRow, with a cover thumbnail).
component AlbumRow inherits Rectangle {
in property <string> album-title;
in property <string> artist-name;
in property <string> added-display;
in property <image> cover-image;
property <bool> hovered: ta.has-hover || rm-ta.has-hover;
callback select();
callback remove();
callback hover-changed(bool);
height: 64px;
border-radius: Radius.md;
background: root.hovered ? Theme.surface-elevated : Theme.surface-card;
changed hovered => {
root.hover-changed(self.hovered);
}
HorizontalLayout {
padding-left: 16px;
padding-right: 8px;
spacing: 12px;
Rectangle {
horizontal-stretch: 1;
ta := TouchArea {
mouse-cursor: pointer;
clicked => {
root.select();
}
}
HorizontalLayout {
spacing: 12px;
// 48x48 cover with a blind-eye fallback (shows through until the
// network cover resolves, or when none exists).
VerticalLayout {
alignment: center;
Rectangle {
width: 48px;
height: 48px;
border-radius: Radius.sm;
clip: true;
background: Theme.surface-elevated;
QbzIcon {
source: @image-url("../assets/icons/blind-eye.svg");
width: 20px;
height: 20px;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
tint: Theme.text-muted;
}
Image {
width: 100%;
height: 100%;
source: root.cover-image;
image-fit: cover;
}
}
}
VerticalLayout {
alignment: center;
spacing: 2px;
Text {
text: root.album-title;
color: Theme.text-primary;
font-size: Typography.body;
font-weight: Typography.medium;
overflow: elide;
}
Text {
text: root.artist-name;
color: Theme.text-secondary;
font-size: Typography.legal;
overflow: elide;
}
Text {
text: @tr("Added {}", root.added-display);
color: Theme.text-muted;
font-size: Typography.legal;
overflow: elide;
}
}
}
}
VerticalLayout {
alignment: center;
Rectangle {
width: 36px;
height: 36px;
border-radius: Radius.sm;
background: rm-ta.has-hover ? Theme.danger-bg : transparent;
QbzIcon {
source: @image-url("../assets/icons/x.svg");
width: 18px;
height: 18px;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
tint: rm-ta.has-hover ? Theme.danger : Theme.text-muted;
}
rm-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
root.remove();
}
}
}
}
}
}
export component BlacklistManagerView inherits Rectangle {
background: ShellState.app-background-active ? transparent : Theme.surface-main;
// Local modal flag (Clear-All confirmation). Lives in the view; the
// confirm only fires BlacklistActions.clear-all on the danger button.
property <bool> confirm-clear-open: false;
property <int> list-hover-rows: 0;
VerticalLayout {
padding: 24px;
spacing: 14px;
// Top bar — Back (ADR-004 via NavButtons; routes through nav history).
HorizontalLayout {
vertical-stretch: 0;
NavButtons { }
}
// Header — blind-eye icon + title.
HorizontalLayout {
vertical-stretch: 0;
spacing: 12px;
VerticalLayout {
alignment: center;
QbzIcon {
source: @image-url("../assets/icons/blind-eye.svg");
width: 24px;
height: 24px;
tint: Theme.text-muted;
}
}
VerticalLayout {
alignment: center;
Text {
text: @tr("Blacklist");
color: Theme.text-primary;
font-size: Typography.section;
font-weight: Typography.bold;
}
}
}
// Tab bar — Artists | Albums (segmented, no pills per ADR-008).
HorizontalLayout {
vertical-stretch: 0;
spacing: 0;
alignment: start;
Rectangle {
width: tabs-row.preferred-width + 8px;
height: 36px;
border-radius: Radius.sm;
background: Theme.surface-elevated;
tabs-row := HorizontalLayout {
x: 4px;
width: self.preferred-width;
height: 100%;
padding: 4px;
spacing: 4px;
// Artists tab.
Rectangle {
width: artists-lbl.preferred-width + 28px;
border-radius: Radius.sm;
background: BlacklistState.active-tab == 0
? Theme.surface-card
: (artists-ta.has-hover ? Theme.surface-hover : transparent);
artists-lbl := Text {
text: @tr("Artists");
color: BlacklistState.active-tab == 0 ? Theme.text-primary : Theme.text-muted;
font-size: Typography.legal;
font-weight: Typography.medium;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
}
artists-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
BlacklistActions.set-tab(0);
}
}
}
// Albums tab.
Rectangle {
width: albums-lbl.preferred-width + 28px;
border-radius: Radius.sm;
background: BlacklistState.active-tab == 1
? Theme.surface-card
: (albums-ta.has-hover ? Theme.surface-hover : transparent);
albums-lbl := Text {
text: @tr("Albums");
color: BlacklistState.active-tab == 1 ? Theme.text-primary : Theme.text-muted;
font-size: Typography.legal;
font-weight: Typography.medium;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
}
albums-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
BlacklistActions.set-tab(1);
}
}
}
}
}
}
// Description (per tab).
Text {
vertical-stretch: 0;
text: BlacklistState.active-tab == 0
? @tr("Blacklisted artists are hidden from search results, radio, suggestions, and similar artists.")
: @tr("Blocked albums are hidden from search, discovery, and listings — even when their artist is allowed.");
color: Theme.text-muted;
font-size: Typography.body;
wrap: word-wrap;
}
// Controls row.
HorizontalLayout {
vertical-stretch: 0;
spacing: 12px;
// Enable/Disable toggle.
Rectangle {
width: toggle-row.preferred-width + 28px;
height: 34px;
border-radius: Radius.sm;
background: toggle-ta.has-hover ? Theme.surface-hover : Theme.surface-elevated;
toggle-row := HorizontalLayout {
x: 14px;
width: self.preferred-width;
height: 100%;
spacing: 8px;
VerticalLayout {
alignment: center;
QbzIcon {
source: BlacklistState.enabled
? @image-url("../assets/icons/eye.svg")
: @image-url("../assets/icons/eye-off.svg");
width: 18px;
height: 18px;
tint: BlacklistState.enabled ? Theme.accent : Theme.text-muted;
}
}
VerticalLayout {
alignment: center;
Text {
text: BlacklistState.enabled ? @tr("Enabled") : @tr("Disabled");
color: BlacklistState.enabled ? Theme.accent : Theme.text-muted;
font-size: Typography.legal;
vertical-alignment: center;
}
}
}
toggle-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
BlacklistActions.toggle-enabled();
}
}
}
// Search input (search-as-you-type) + clear (×).
Rectangle {
width: 280px;
height: 34px;
border-radius: Radius.sm;
background: Theme.surface-elevated;
border-width: 1px;
border-color: search-in.has-focus ? Theme.accent : Theme.border-subtle;
HorizontalLayout {
padding-left: 10px;
padding-right: 6px;
spacing: 6px;
VerticalLayout {
alignment: center;
QbzIcon {
source: @image-url("../assets/icons/search.svg");
width: 15px;
height: 15px;
tint: Theme.text-muted;
}
}
Rectangle {
horizontal-stretch: 1;
search-in := TextInput {
width: 100%;
height: 100%;
vertical-alignment: center;
color: Theme.text-primary;
font-size: Typography.legal;
text: BlacklistState.search-query;
changed has-focus => { UiFocusState.text-input-focused = self.has-focus; }
// Search-as-you-type: every edit re-pushes the
// controller-filtered rows. No debounce (Tauri parity).
edited => {
BlacklistActions.search-changed(self.text);
}
}
}
if BlacklistState.search-query != "": VerticalLayout {
alignment: center;
Rectangle {
width: 24px;
height: 24px;
border-radius: Radius.sm;
background: clear-ta.has-hover ? Theme.surface-hover : transparent;
QbzIcon {
source: @image-url("../assets/icons/x.svg");
width: 13px;
height: 13px;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
tint: clear-ta.has-hover ? Theme.text-primary : Theme.text-muted;
}
clear-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
search-in.text = "";
BlacklistActions.search-changed("");
}
}
}
}
}
}
// Clear-All — only when the active tab has something to clear.
if (BlacklistState.active-tab == 0 ? BlacklistState.count : BlacklistState.album-count) > 0: Rectangle {
width: clear-all-row.preferred-width + 28px;
height: 34px;
border-radius: Radius.sm;
background: clear-all-ta.has-hover ? Theme.danger-bg : Theme.surface-elevated;
clear-all-row := HorizontalLayout {
x: 14px;
width: self.preferred-width;
height: 100%;
spacing: 8px;
VerticalLayout {
alignment: center;
QbzIcon {
source: @image-url("../assets/icons/trash-2.svg");
width: 16px;
height: 16px;
tint: Theme.danger;
}
}
VerticalLayout {
alignment: center;
Text {
text: @tr("Clear All");
color: Theme.danger;
font-size: Typography.legal;
vertical-alignment: center;
}
}
}
clear-all-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
root.confirm-clear-open = true;
}
}
}
// Spacer + count badge (FULL count, right-aligned).
Rectangle {
horizontal-stretch: 1;
}
VerticalLayout {
alignment: center;
Text {
text: BlacklistState.active-tab == 0
? @tr("{} artists", BlacklistState.count)
: @tr("{} albums", BlacklistState.album-count);
color: Theme.text-muted;
font-size: Typography.legal;
horizontal-alignment: right;
}
}
}
// Disabled warning banner.
if !BlacklistState.enabled: Rectangle {
vertical-stretch: 0;
height: warn-row.preferred-height + 24px;
border-radius: Radius.md;
background: Theme.warning-bg;
border-width: 1px;
border-color: Theme.warning-border;
warn-row := HorizontalLayout {
padding: 12px;
spacing: 10px;
VerticalLayout {
alignment: center;
QbzIcon {
source: @image-url("../assets/icons/circle-alert.svg");
width: 16px;
height: 16px;
tint: Theme.warning;
}
}
VerticalLayout {
alignment: center;
Text {
text: @tr("Blacklist filtering is disabled. Blacklisted artists are shown everywhere until you re-enable it.");
color: Theme.warning;
font-size: Typography.legal;
wrap: word-wrap;
}
}
}
}
// Body — 4 mutually-exclusive branches (order is load-bearing).
Rectangle {
vertical-stretch: 1;
// 1) Loading.
if BlacklistState.loading: VerticalLayout {
alignment: center;
HorizontalLayout {
alignment: center;
LoadingSpinner { }
}
HorizontalLayout {
alignment: center;
Text {
text: @tr("Loading blacklist…");
color: Theme.text-muted;
font-size: Typography.body;
}
}
}
// 2) Empty blacklist (Artists tab).
if BlacklistState.active-tab == 0 && !BlacklistState.loading && BlacklistState.count == 0: VerticalLayout {
alignment: center;
spacing: 12px;
HorizontalLayout {
alignment: center;
QbzIcon {
source: @image-url("../assets/icons/blind-eye.svg");
width: 48px;
height: 48px;
tint: Theme.text-muted;
opacity: 0.3;
}
}
HorizontalLayout {
alignment: center;
Text {
text: @tr("No blacklisted artists");
color: Theme.text-muted;
font-size: Typography.heading;
}
}
HorizontalLayout {
alignment: center;
Text {
text: @tr("To hide an artist, go to their page and use the menu.");
color: Theme.text-muted;
font-size: Typography.legal;
horizontal-alignment: center;
}
}
}
// 3) No search results (Artists tab; full list non-empty, filtered empty).
if BlacklistState.active-tab == 0 && !BlacklistState.loading && BlacklistState.count > 0 && BlacklistState.items.length == 0: VerticalLayout {
alignment: center;
spacing: 12px;
HorizontalLayout {
alignment: center;
QbzIcon {
source: @image-url("../assets/icons/search.svg");
width: 48px;
height: 48px;
tint: Theme.text-muted;
opacity: 0.3;
}
}
HorizontalLayout {
alignment: center;
Text {
text: @tr("No results for \"{}\"", BlacklistState.search-query);
color: Theme.text-muted;
font-size: Typography.heading;
}
}
}
// 4) The list. Use Flickable + the shared custom scrollbar so the
// scrollbar can reveal on list/gutter hover; std ScrollView does
// not expose hover-controlled scrollbar visibility.
if BlacklistState.active-tab == 0 && !BlacklistState.loading && BlacklistState.items.length > 0: Rectangle {
list-flick := Flickable {
width: 100%;
height: 100%;
viewport-width: self.width;
viewport-height: list-content.preferred-height;
list-content := VerticalLayout {
width: parent.width - 18px;
spacing: 4px;
for item in BlacklistState.items: ArtistRow {
artist-name: item.artist-name;
added-display: item.added-display;
notes: item.notes;
has-notes: item.has-notes;
select => {
BlacklistActions.artist-select(item.artist-id);
}
remove => {
BlacklistActions.remove(item.artist-id);
}
hover-changed(h) => {
root.list-hover-rows = h ? root.list-hover-rows + 1 : Math.max(0, root.list-hover-rows - 1);
}
}
}
}
ListScrollbar {
x: parent.width - self.width - 4px;
width: 14px;
height: 100%;
viewport-height: list-flick.viewport-height;
visible-height: list-flick.height;
viewport-y <=> list-flick.viewport-y;
auto-hide: true;
hover-active: root.list-hover-rows > 0;
}
}
// --- Albums tab branches -------------------------------------
// 2b) Empty (no blocked albums).
if BlacklistState.active-tab == 1 && !BlacklistState.loading && BlacklistState.album-count == 0: VerticalLayout {
alignment: center;
spacing: 12px;
HorizontalLayout {
alignment: center;
QbzIcon {
source: @image-url("../assets/icons/blind-eye.svg");
width: 48px;
height: 48px;
tint: Theme.text-muted;
opacity: 0.3;
}
}
HorizontalLayout {
alignment: center;
Text {
text: @tr("No blocked albums");
color: Theme.text-muted;
font-size: Typography.heading;
}
}
HorizontalLayout {
alignment: center;
Text {
text: @tr("To block an album, open its menu and choose Block this album.");
color: Theme.text-muted;
font-size: Typography.legal;
horizontal-alignment: center;
}
}
}
// 3b) No search results (albums).
if BlacklistState.active-tab == 1 && !BlacklistState.loading && BlacklistState.album-count > 0 && BlacklistState.album-items.length == 0: VerticalLayout {
alignment: center;
spacing: 12px;
HorizontalLayout {
alignment: center;
QbzIcon {
source: @image-url("../assets/icons/search.svg");
width: 48px;
height: 48px;
tint: Theme.text-muted;
opacity: 0.3;
}
}
HorizontalLayout {
alignment: center;
Text {
text: @tr("No results for \"{}\"", BlacklistState.search-query);
color: Theme.text-muted;
font-size: Typography.heading;
}
}
}
// 4b) The blocked-albums list.
if BlacklistState.active-tab == 1 && !BlacklistState.loading && BlacklistState.album-items.length > 0: Rectangle {
album-flick := Flickable {
width: 100%;
height: 100%;
viewport-width: self.width;
viewport-height: album-content.preferred-height;
album-content := VerticalLayout {
width: parent.width - 18px;
spacing: 4px;
for item in BlacklistState.album-items: AlbumRow {
album-title: item.album-title;
artist-name: item.artist-name;
added-display: item.added-display;
cover-image: item.cover;
select => {
BlacklistActions.album-select(item.album-id);
}
remove => {
BlacklistActions.remove-album(item.album-id);
}
hover-changed(h) => {
root.list-hover-rows = h ? root.list-hover-rows + 1 : Math.max(0, root.list-hover-rows - 1);
}
}
}
}
ListScrollbar {
x: parent.width - self.width - 4px;
width: 14px;
height: 100%;
viewport-height: album-flick.viewport-height;
visible-height: album-flick.height;
viewport-y <=> album-flick.viewport-y;
auto-hide: true;
hover-active: root.list-hover-rows > 0;
}
}
}
}
// Clear-All confirm modal — declared LAST so it stacks above the view
// (ADR-009 ≥ 3000 satisfied by declaration order; mirrors
// PlaylistDuplicateConfirmModal's scrim + centered card). Backdrop click
// dismisses; Esc dismisses via the focus-scope key handler.
if root.confirm-clear-open: Rectangle {
background: #000000bf;
// Scrim — click outside to dismiss.
TouchArea {
mouse-cursor: default;
clicked => {
root.confirm-clear-open = false;
}
}
// Esc dismiss. Grab focus on mount so the key handler is live.
esc-scope := FocusScope {
init => {
self.focus();
}
key-pressed(event) => {
if (event.text == Key.Escape) {
root.confirm-clear-open = false;
return accept;
}
return reject;
}
}
Rectangle {
width: Math.min(root.width - 80px, 400px);
height: panel.preferred-height;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
border-radius: Radius.md;
background: Theme.surface-main;
border-width: 1px;
border-color: Theme.border-subtle;
drop-shadow-blur: 32px;
drop-shadow-color: #00000080;
// Swallow clicks so they don't reach the scrim.
TouchArea { }
panel := VerticalLayout {
padding: 24px;
spacing: 16px;
Text {
text: @tr("Clear Blacklist?");
color: Theme.text-primary;
font-size: Typography.heading;
font-weight: Typography.semibold;
}
Text {
text: BlacklistState.active-tab == 0
? @tr("This will remove all {} blacklisted artists. This cannot be undone.", BlacklistState.count)
: @tr("This will remove all {} blocked albums. This cannot be undone.", BlacklistState.album-count);
color: Theme.text-secondary;
font-size: Typography.body;
wrap: word-wrap;
}
HorizontalLayout {
alignment: end;
spacing: 10px;
// Cancel.
Rectangle {
width: cancel-lbl.preferred-width + 32px;
height: 36px;
border-radius: Radius.sm;
background: cancel-ta.has-hover ? Theme.surface-hover : Theme.surface-elevated;
cancel-lbl := Text {
text: @tr("Cancel");
color: Theme.text-primary;
font-size: Typography.body;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
}
cancel-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
root.confirm-clear-open = false;
}
}
}
// Danger Clear-All.
Rectangle {
width: danger-lbl.preferred-width + 32px;
height: 36px;
border-radius: Radius.sm;
// Solid-filled danger button: base is the theme's
// danger color, hover a derived darker solid shade.
// NOT Theme.danger-hover — that token is a translucent
// hover-tint for ghost/borderless danger surfaces (the
// trash buttons above); using it on a filled button
// would wash the fill out (and is LIGHTER in some a11y
// themes per the migration palette), regressing the
// white label's contrast. .darker() mirrors how
// accent-pressed is derived in theme.slint.
background: danger-ta.has-hover ? Theme.danger.darker(0.12) : Theme.danger;
danger-lbl := Text {
text: @tr("Clear All");
color: #ffffff;
font-size: Typography.body;
font-weight: Typography.medium;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
}
danger-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
if (BlacklistState.active-tab == 0) {
BlacklistActions.clear-all();
} else {
BlacklistActions.clear-all-albums();
}
root.confirm-clear-open = false;
}
}
}
}
}
}
}
}