Merge feature/adwaita-components: libadwaita components + slider/chip theming fixes
This commit is contained in:
commit
4e76bc7077
5 changed files with 155 additions and 0 deletions
31
Cargo.lock
generated
31
Cargo.lock
generated
|
|
@ -199,6 +199,7 @@ version = "0.3.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs",
|
"dirs",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
|
"libadwaita",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
@ -1279,6 +1280,36 @@ version = "3.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libadwaita"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85b9900e67182a4b5b1f157b448d94f0715c8b9770cce21cf000801917f53bfa"
|
||||||
|
dependencies = [
|
||||||
|
"gdk4",
|
||||||
|
"gio",
|
||||||
|
"glib",
|
||||||
|
"gtk4",
|
||||||
|
"libadwaita-sys",
|
||||||
|
"pango",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libadwaita-sys"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28d3c27642b389852aa99341bd4a4c19ec6f8a2b63ebdd7f5ba1952198079ccd"
|
||||||
|
dependencies = [
|
||||||
|
"gdk4-sys",
|
||||||
|
"gio-sys",
|
||||||
|
"glib-sys",
|
||||||
|
"gobject-sys",
|
||||||
|
"gtk4-sys",
|
||||||
|
"libc",
|
||||||
|
"pango-sys",
|
||||||
|
"system-deps",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.186"
|
version = "0.2.186"
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,24 @@ serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
dirs = { workspace = true }
|
dirs = { workspace = true }
|
||||||
gtk4 = { version = "0.11", features = ["v4_12"], optional = true }
|
gtk4 = { version = "0.11", features = ["v4_12"], optional = true }
|
||||||
|
# Rust bindings for libadwaita (GNOME's widget library on top of GTK4) — the
|
||||||
|
# actual source of the modern GNOME look (grouped preference rows, real
|
||||||
|
# toggle/spin rows, view switchers), not just a CSS reskin of plain GTK4
|
||||||
|
# widgets. `v1_7` for ToggleGroup (used for tab-row-style pickers); the
|
||||||
|
# system library only needs to be >= that (this machine has 1.9.2).
|
||||||
|
libadwaita = { version = "0.9", features = ["v1_7"], optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# Enable GTK4 CSS provider helpers (breadbar, breadbox, breadpad use this).
|
# Enable GTK4 CSS provider helpers (breadbar, breadbox, breadpad use this).
|
||||||
# bread (daemon) and breadcrumbs (CLI) depend on this crate without the feature.
|
# bread (daemon) and breadcrumbs (CLI) depend on this crate without the feature.
|
||||||
gtk = ["dep:gtk4"]
|
gtk = ["dep:gtk4"]
|
||||||
|
# Composite libadwaita-based widgets (bread_theme::adw) — separate from `gtk`
|
||||||
|
# because libadwaita's own top-level window chrome (AdwApplicationWindow)
|
||||||
|
# isn't compatible with gtk4-layer-shell surfaces, so the five layer-shell
|
||||||
|
# apps (breadbar, breadbox, breadclip, breadsearch, breadpad) only want
|
||||||
|
# plain CSS, not this. Apps with an ordinary top-level window (breadman,
|
||||||
|
# breadhelp) want both.
|
||||||
|
adw = ["gtk", "dep:libadwaita"]
|
||||||
|
|
||||||
# The generator CLI. It only touches the gtk-free lib API (render + write), so
|
# The generator CLI. It only touches the gtk-free lib API (render + write), so
|
||||||
# it builds without the gtk feature and stays light.
|
# it builds without the gtk feature and stays light.
|
||||||
|
|
|
||||||
78
bread-theme/src/adw.rs
Normal file
78
bread-theme/src/adw.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
//! Composite libadwaita widgets for the bread ecosystem's design system —
|
||||||
|
//! the actual mechanism (real GNOME-style widgets, not more hand-rolled CSS)
|
||||||
|
//! behind why bos-settings' sidebar/section/toggle rows read as more polished
|
||||||
|
//! than the plain-GTK4 apps'. An app calls these instead of assembling boxes
|
||||||
|
//! and labels and raw widgets from scratch each time, so spacing/sizing/
|
||||||
|
//! grouping decisions get made once, correctly, here — not re-derived per
|
||||||
|
//! screen.
|
||||||
|
//!
|
||||||
|
//! Not usable from the five `gtk4-layer-shell` apps (breadbar, breadbox,
|
||||||
|
//! breadclip, breadsearch, breadpad): `AdwApplicationWindow`'s own chrome
|
||||||
|
//! isn't compatible with a layer-shell surface, and these helpers assume an
|
||||||
|
//! ordinary top-level window. Apps with a plain top-level window (breadman,
|
||||||
|
//! breadhelp) can use the full set.
|
||||||
|
|
||||||
|
use libadwaita as adw;
|
||||||
|
use adw::prelude::*;
|
||||||
|
|
||||||
|
/// Call once at startup, before building any widgets from this module —
|
||||||
|
/// initializes libadwaita's style manager and forces dark mode regardless of
|
||||||
|
/// the system GTK theme preference. bread-theme's whole design is a *fixed*
|
||||||
|
/// dark base (only the accent tracks pywal — see `palette::FIXED_BACKGROUND`
|
||||||
|
/// etc.) so an app respecting a light system preference here would silently
|
||||||
|
/// break that contract the moment someone's GNOME settings say "light".
|
||||||
|
pub fn init() {
|
||||||
|
adw::init().expect("failed to initialize libadwaita");
|
||||||
|
adw::StyleManager::default().set_color_scheme(adw::ColorScheme::ForceDark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A titled, optionally-described group of setting rows — the
|
||||||
|
/// title-then-description-then-rows rhythm bos-settings already uses per
|
||||||
|
/// section, now available to native GTK4/relm4 apps instead of a hand-rolled
|
||||||
|
/// vbox with a bold label glued to the top.
|
||||||
|
pub fn preferences_group(title: &str, description: Option<&str>) -> adw::PreferencesGroup {
|
||||||
|
let group = adw::PreferencesGroup::builder().title(title).build();
|
||||||
|
if let Some(desc) = description {
|
||||||
|
group.set_description(Some(desc));
|
||||||
|
}
|
||||||
|
group
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single on/off setting row with a correctly-sized, correctly-positioned
|
||||||
|
/// switch — the direct fix for the ~1400px-wide stretched-switch bug
|
||||||
|
/// (breadman/settings had no intrinsic width on its hand-rolled switch, so
|
||||||
|
/// it filled the row like a progress bar).
|
||||||
|
pub fn toggle_row(title: &str, subtitle: Option<&str>, active: bool) -> adw::SwitchRow {
|
||||||
|
let row = adw::SwitchRow::builder().title(title).active(active).build();
|
||||||
|
if let Some(sub) = subtitle {
|
||||||
|
row.set_subtitle(sub);
|
||||||
|
}
|
||||||
|
row
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single numeric setting row (spin button docked to its own label,
|
||||||
|
/// instead of stranded ~1300px away at the window's far edge).
|
||||||
|
pub fn spin_row(title: &str, subtitle: Option<&str>, adjustment: >k4::Adjustment) -> adw::SpinRow {
|
||||||
|
let row = adw::SpinRow::builder().title(title).adjustment(adjustment).build();
|
||||||
|
if let Some(sub) = subtitle {
|
||||||
|
row.set_subtitle(sub);
|
||||||
|
}
|
||||||
|
row
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A general label(+subtitle) row with room for a trailing widget
|
||||||
|
/// (`row.add_suffix(&widget)`) — for settings that don't fit switch/spin
|
||||||
|
/// (text entries, buttons, dropdowns, a raw value display).
|
||||||
|
pub fn action_row(title: &str, subtitle: Option<&str>) -> adw::ActionRow {
|
||||||
|
let row = adw::ActionRow::builder().title(title).build();
|
||||||
|
if let Some(sub) = subtitle {
|
||||||
|
row.set_subtitle(sub);
|
||||||
|
}
|
||||||
|
row
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A page of one or more `preferences_group`s, with correct margins and
|
||||||
|
/// scroll handling — the top-level content container for a settings screen.
|
||||||
|
pub fn preferences_page() -> adw::PreferencesPage {
|
||||||
|
adw::PreferencesPage::new()
|
||||||
|
}
|
||||||
|
|
@ -114,6 +114,29 @@ pub fn apply_css(css: &str, provider: &RefCell<Option<CssProvider>>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A filter/tag chip using the shared `.chip` stylesheet rule (an
|
||||||
|
/// `@overlay`-filled pill, `@accent`-filled when the `active` CSS class is
|
||||||
|
/// set) instead of a fresh literal color — this is the fix for the same
|
||||||
|
/// component drifting to three different fills across breadclip (grey),
|
||||||
|
/// breadpad, and breadman (both cream), none of which agreed with each
|
||||||
|
/// other or with the shared token.
|
||||||
|
pub fn chip(label: &str) -> gtk4::Button {
|
||||||
|
gtk4::Button::builder().label(label).css_classes(["chip"]).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggles a chip's (or any widget's) `active` CSS class — the `.chip.active`
|
||||||
|
/// stylesheet rule fills it with the accent instead of the neutral overlay.
|
||||||
|
/// Wiring *when* a chip becomes active (single-select filter, multi-select
|
||||||
|
/// tags, etc.) is genuinely per-app, so that stays the caller's job; this is
|
||||||
|
/// just the one-line visual toggle every case needs.
|
||||||
|
pub fn set_chip_active(chip: &impl IsA<gtk4::Widget>, active: bool) {
|
||||||
|
if active {
|
||||||
|
chip.add_css_class("active");
|
||||||
|
} else {
|
||||||
|
chip.remove_css_class("active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Apply a user CSS override file at USER priority. Clears the provider if the
|
/// Apply a user CSS override file at USER priority. Clears the provider if the
|
||||||
/// file is absent so stale overrides don't persist across SIGHUP reloads.
|
/// file is absent so stale overrides don't persist across SIGHUP reloads.
|
||||||
pub fn apply_user_css(path: &Path, provider: &RefCell<Option<CssProvider>>) {
|
pub fn apply_user_css(path: &Path, provider: &RefCell<Option<CssProvider>>) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
pub mod palette;
|
pub mod palette;
|
||||||
#[cfg(feature = "gtk")]
|
#[cfg(feature = "gtk")]
|
||||||
pub mod gtk;
|
pub mod gtk;
|
||||||
|
#[cfg(feature = "adw")]
|
||||||
|
pub mod adw;
|
||||||
|
|
||||||
pub use palette::{load_palette, Palette};
|
pub use palette::{load_palette, Palette};
|
||||||
|
|
||||||
|
|
@ -187,6 +189,14 @@ pub fn stylesheet(p: &Palette) -> String {
|
||||||
switch {{ background-color: @overlay; border-radius: {pill}px; }}\n\
|
switch {{ background-color: @overlay; border-radius: {pill}px; }}\n\
|
||||||
switch:checked {{ background-color: @accent; }}\n\
|
switch:checked {{ background-color: @accent; }}\n\
|
||||||
switch slider {{ background-color: @on-surface; border-radius: {pill}px; }}\n\
|
switch slider {{ background-color: @on-surface; border-radius: {pill}px; }}\n\
|
||||||
|
/* GtkScale (sliders) render with GTK's own default accent (a fixed\
|
||||||
|
blue, independent of the app's theme) unless styled explicitly —\
|
||||||
|
every app with a volume/brightness slider was silently showing\
|
||||||
|
that default instead of the palette's accent until this rule\
|
||||||
|
existed. */\n\
|
||||||
|
scale trough {{ background-color: @overlay; border-radius: {pill}px; min-height: 6px; }}\n\
|
||||||
|
scale trough highlight {{ background-color: @accent; border-radius: {pill}px; min-height: 6px; }}\n\
|
||||||
|
scale slider {{ background-color: @on-bg; border-radius: {pill}px; }}\n\
|
||||||
list, listbox {{ background-color: transparent; }}\n\
|
list, listbox {{ background-color: transparent; }}\n\
|
||||||
row {{ border-radius: {r2}px; }}\n\
|
row {{ border-radius: {r2}px; }}\n\
|
||||||
row:selected, list row:selected {{ background-color: @accent; color: @on-accent; }}\n\
|
row:selected, list row:selected {{ background-color: @accent; color: @on-accent; }}\n\
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue