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
48 lines
1.4 KiB
TOML
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"
|