Files
qbz/crates/qbz-slint/Cargo.toml
T
vicrodh c4b6acb760 fix(slint): smooth sticky scroll + artwork right-click menu
Two unrelated things landing together.

Sticky scroll fix:
  - The natural-top binding on both jump-nav and the sidebar reads
    from `body-row.absolute-position.y` (a live, scroll-aware
    lookup). The y formula was `max(natural-top + viewport-y, ...)`
    — adding viewport-y on top of an already scroll-aware value
    double-counted the scroll. During mid-scroll the bar drifted
    ahead of the body row, opening a visible gap and briefly
    overlapping the header.
  - Drops `+ viewport-y` from both formulas. natural-top alone IS
    the viewport-relative y the elements want to occupy; the
    max() clamp just keeps them from going above the top.
  - Initial position and sticky position are unchanged; only the
    transition is fixed.

Artwork right-click menu (artist portrait + album cover):
  - Both headers get a TouchArea on the artwork that opens a
    PopupWindow + ContextMenu on right-click, positioned at the
    cursor. Mirrors Tauri's `oncontextmenu` flow on the artist
    portrait and album cover.
  - Menu items match Tauri 1:1: Add image / cover, Change + Remove
    (when a custom image is registered), divider, Open in
    browser, Save as.
  - New `crate::custom_artwork` JSON-backed store at
    `<data-dir>/qbz/custom_artwork.json` keyed by artist name +
    album id (mirror of the Tauri library's
    `v2_library_set/remove_custom_*` persistence).
  - ArtistState / AlbumState expose `artwork-url` (network URL for
    Open / Save) + `has-custom-image` / `has-custom-cover` (drives
    the Add vs Change/Remove menu rendering), both populated from
    Rust at apply_artist / apply_album time.
  - ArtworkActions global + main.rs handlers:
      * open-in-browser → `open::that(url)`.
      * save-as → `rfd::AsyncFileDialog::save_file()` + reqwest
        download + tokio::fs::write.
      * add-custom → `rfd::AsyncFileDialog::pick_file()` then
        `custom_artwork::set_*` + flip the has-custom flag.
      * remove-custom → `custom_artwork::remove_*` + reset flag.
  - rfd added with xdg-portal + tokio features. external-link and
    image-plus SVGs ported from Lucide.

Custom-image RENDERING (using the picked file in place of the
network artwork) is deferred — the store persists the choice and
the menu reflects it, but the displayed image still comes from
Qobuz. Next pass.

cc
2026-05-20 18:24:39 -06:00

48 lines
1.4 KiB
TOML

[package]
name = "qbz-slint"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
description = "Slint functional MVP shell for QBZ (private branch only)"
[[bin]]
name = "qbz-slint"
path = "src/main.rs"
[dependencies]
# femtovg renderer, pinned >=1.16 for the femtovg text-rendering fixes
# (slint-ui/slint #5177 pixel-grid alignment + #11335 0.5px tearing /
# inconsistent glyph spacing). Skia never received that treatment.
slint = { version = "1.16", default-features = false, features = [
"std",
"backend-winit",
"renderer-femtovg",
"renderer-software",
"accessibility",
"compat-1-2",
] }
log = { workspace = true }
env_logger = "0.11"
tokio = { workspace = true }
async-trait = { workspace = true }
reqwest = { workspace = true }
image = { version = "0.25", default-features = false, features = ["jpeg", "png"] }
urlencoding = "2"
open = "5"
rfd = { version = "0.15", default-features = false, features = ["xdg-portal", "tokio"] }
serde = { workspace = true }
serde_json = { workspace = true }
dirs = "5"
qbz-app = { path = "../qbz-app" }
qbz-audio = { path = "../qbz-audio" }
qbz-core = { path = "../qbz-core" }
qbz-models = { path = "../qbz-models" }
qbz-integrations = { path = "../qbz-integrations" }
rusqlite = { version = "0.31", features = ["bundled"] }
qbz-cache = { path = "../qbz-cache" }
qbz-credentials = { path = "../qbz-credentials" }
[build-dependencies]
slint-build = "1.16"