feat(slint): network sidebar toggle in artist header

Adds the Network button to the artist action cluster (Heart / Build
collection / Radio / Network), wired to NetworkSidebarState.open.
While the sidebar is open the button stays "active" — accent-tinted
icon over the same surface a hover would produce — mirroring Tauri's
`.network-btn.active`. The CircleAction primitive grew an `active`
prop to express this without bespoke per-button styling; existing
buttons default it to false and are unaffected.

The click toggles NetworkSidebarState.open and emits
NetworkSidebarActions.toggle(). Rust persists the new flag through
network_sidebar_prefs::set_open() so the sidebar reopens in the same
state the user left it.

Asset: element-connect.svg copied from the Tauri `static/` folder
verbatim — same icon Tauri renders for the button.

Hide (blacklist) button stays out of the cluster until the
artist_blacklist module migrates out of src-tauri. The sidebar
itself, its content, and the controllers that fill the state are
landed in the following phases.

cc
This commit is contained in:
vicrodh committed 2026-05-20 09:11:10 -06:00
1 parent a768cce5e5
commit a8c8947658
4 files changed
+62 -9

No files matched your search

+16
View File
@@ -1087,6 +1087,22 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
});
}
// Artist network sidebar — persist the open/closed flag on every
// toggle so the sidebar reopens in the same state next launch. The
// Slint side already flipped NetworkSidebarState.open before
// emitting the callback.
{
let weak = window.as_weak();
window
.global::<NetworkSidebarActions>()
.on_toggle(move || {
if let Some(w) = weak.upgrade() {
let open = w.global::<NetworkSidebarState>().get_open();
network_sidebar_prefs::set_open(open);
}
});
}
window.on_close_app(|| {
log::info!("[qbz-slint] closing");
let _ = slint::quit_event_loop();
+1 -1
View File
@@ -17,7 +17,7 @@ import { LoginScreen } from "login/LoginScreen.slint";
import { AppShell } from "shell/AppShell.slint";
// Re-export the state globals so the Rust layer can populate them.
export { HomeState, AlbumState, ArtistState, NavState, SessionState, SettingsState, AlbumActions, NowPlayingState, QueueState, SearchState, SearchActions } from "state.slint";
export { HomeState, AlbumState, ArtistState, NavState, SessionState, SettingsState, AlbumActions, NowPlayingState, QueueState, SearchState, SearchActions, NetworkSidebarState, NetworkSidebarActions } from "state.slint";
// Which top-level screen is shown. The app starts on `splash` while it
// restores a saved session, then resolves to `shell` or `login`.
@@ -8,7 +8,14 @@
import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint";
import { ArtistState, AlbumTrackItem, NavState, ContentView } from "../state.slint";
import {
ArtistState,
AlbumTrackItem,
NavState,
ContentView,
NetworkSidebarState,
NetworkSidebarActions,
} from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
import { ContextMenu } from "../primitives/ContextMenu.slint";
import { ContextMenuItem } from "../primitives/ContextMenuItem.slint";
@@ -21,6 +28,9 @@ import { NavButtons } from "../shell/NavButtons.slint";
component CircleAction inherits Rectangle {
in property <image> icon;
in property <bool> primary: false;
// Stays "pressed" — same surface as a hover, icon tinted accent.
// Used by the Network button while the sidebar is open.
in property <bool> active: false;
in property <length> diameter: 40px;
callback clicked();
@@ -29,9 +39,9 @@ component CircleAction inherits Rectangle {
border-radius: root.diameter / 2;
// Ghost circle — translucent fill with a faint ring, never a solid
// accent fill (mirrors the Tauri transport controls).
background: ta.has-hover ? Theme.surface-hover : transparent;
background: (ta.has-hover || root.active) ? Theme.surface-hover : transparent;
border-width: 1px;
border-color: ta.has-hover ? Theme.text-disabled : Theme.border-subtle;
border-color: (ta.has-hover || root.active) ? Theme.text-disabled : Theme.border-subtle;
QbzIcon {
source: root.icon;
@@ -39,7 +49,9 @@ component CircleAction inherits Rectangle {
height: root.primary ? 19px : 17px;
x: (parent.width - self.width) / 2;
y: (parent.height - self.height) / 2;
tint: ta.has-hover ? Theme.text-primary : Theme.text-secondary;
tint: root.active
? Theme.accent
: (ta.has-hover ? Theme.text-primary : Theme.text-secondary);
}
ta := TouchArea {
@@ -335,10 +347,11 @@ export component ArtistPageView inherits Rectangle {
Rectangle { height: 18px; }
// Action buttons — Heart, Build collection, Radio
// (with QBZ / Qobuz dropdown). Mirrors Tauri's
// artist-actions: NO Play here (top tracks has its
// own play). Network sidebar toggle and Hide are
// deferred to Tanda 2 (sidebar work).
// (with QBZ / Qobuz dropdown), Network. Mirrors
// Tauri's artist-actions: NO Play here (top tracks
// has its own play). Hide (blacklist) is still
// deferred until the blacklist module migrates out
// of src-tauri.
HorizontalLayout {
alignment: start;
spacing: 12px;
@@ -389,6 +402,17 @@ export component ArtistPageView inherits Rectangle {
}
}
}
// Network — toggles the artist network sidebar.
// Stays active (accent-tinted) while the sidebar
// is open, matching Tauri's `.network-btn.active`.
CircleAction {
icon: @image-url("../assets/icons/element-connect.svg");
active: NetworkSidebarState.open;
clicked => {
NetworkSidebarState.open = !NetworkSidebarState.open;
NetworkSidebarActions.toggle();
}
}
}
}
}
@@ -0,0 +1,13 @@
<?xml version="1.0" ?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 512 512" id="svg2793" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:serif="http://www.serif.com/" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs2797"/>
<g id="_24-Connect" style="display:inline" transform="translate(-3072,-4096)">
<g id="g2211" transform="translate(3339.32,4267.43)">
<path d="m 0,169.14 c -44.818,0 -81.28,-36.462 -81.28,-81.28 0,-44.818 36.462,-81.28 81.28,-81.28 44.817,0 81.279,36.462 81.279,81.28 0,44.818 -36.462,81.28 -81.279,81.28 m -149.2,141.43 c -24.587,0 -44.59,-20.003 -44.59,-44.59 0,-24.587 20.003,-44.59 44.59,-44.59 24.587,0 44.59,20.003 44.59,44.59 0,24.587 -20.003,44.59 -44.59,44.59 m -83.91,-398.5 c 0,-29.5 24,-53.5 53.5,-53.5 29.494,0 53.49,24 53.49,53.5 0,29.494 -23.996,53.49 -53.49,53.49 -29.5,0 -53.5,-23.996 -53.5,-53.49 m 412.099,37 c 17.364,0 31.49,14.126 31.49,31.49 0,17.369 -14.126,31.5 -31.49,31.5 -17.369,0 -31.5,-14.131 -31.5,-31.5 0,-17.364 14.131,-31.49 31.5,-31.49 m -44.46,73.915 c 11.207,11.739 26.989,19.075 44.46,19.075 33.907,0 61.49,-27.589 61.49,-61.5 0,-33.906 -27.583,-61.49 -61.49,-61.49 -33.911,0 -61.5,27.584 -61.5,61.49 0,5.628 0.776,11.076 2.198,16.259 L 87.091,18.672 C 66.687,-6.959 35.231,-23.42 0,-23.42 c -26.243,0 -50.388,9.137 -69.436,24.388 l -44.024,-38.046 c 10.859,-14.093 17.34,-31.725 17.34,-50.852 0,-46.042 -37.453,-83.5 -83.49,-83.5 -46.042,0 -83.5,37.458 -83.5,83.5 0,46.036 37.458,83.49 83.5,83.49 16.012,0 30.98,-4.54 43.702,-12.388 l 45.751,39.538 c -13.281,18.326 -21.123,40.838 -21.123,65.15 0,27.971 10.377,53.562 27.478,73.133 l -30.6,39.04 c -10.397,-5.508 -22.236,-8.643 -34.798,-8.643 -41.129,0 -74.59,33.461 -74.59,74.59 0,41.129 33.461,74.59 74.59,74.59 41.129,0 74.59,-33.461 74.59,-74.59 0,-17.745 -6.24,-34.055 -16.625,-46.873 l 30.054,-38.343 c 17.567,11.607 38.596,18.376 61.181,18.376 61.359,0 111.279,-49.92 111.279,-111.28 0,-15.382 -3.138,-30.045 -8.806,-43.383 z" id="path2209" style="fill-rule:nonzero"/>
</g>
<g id="g2215" transform="translate(3521.08,4502.59)">
<path d="M 0,57.45 C -15.842,57.45 -28.73,44.562 -28.73,28.72 -28.73,12.884 -15.842,0 0,0 15.836,0 28.72,12.884 28.72,28.72 28.72,44.562 15.836,57.45 0,57.45 M 0,-30 c -32.384,0 -58.73,26.342 -58.73,58.72 0,32.384 26.346,58.73 58.73,58.73 32.378,0 58.72,-26.346 58.72,-58.73 C 58.72,-3.658 32.378,-30 0,-30" id="path2213" style="fill-rule:nonzero"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB