bos/bos-settings/src/ui/views/breadbar.rs
Breadway eb740a3724 bos-settings 0.5.0: GNOME-Settings-style redesign + live app control
New panels: About, Network (Wi-Fi/Ethernet via nmcli), Sound (PipeWire
volume/device via pactl), Date & Time (timedatectl), and Clipboard
(breadclip previously had no bos-settings presence at all despite running
its own daemon).

Layout: every panel now shares one scaffold (6 views were hand-rolling their
own, inconsistently) with a centered, width-capped content column instead of
rows stretching edge-to-edge on a wide window with the control stranded far
from its label. Rows render as individual cards. Sidebar got human labels +
icons + task-based grouping (System/Personalization/Maintenance/About)
instead of raw binary names under "Apps"/"System".

New capability: a shared service_control() widget gives every bread app
backed by a systemd --user daemon (breadd, breadbox-sync, breadcrumbs,
breadmill, breadclipd) live status plus Start/Stop/Restart/View logs —
previously these panels only ever edited a TOML file and hoped the running
process picked it up. breadbar (no systemd unit) gets an equivalent: Save
now actually sends the SIGHUP its own reload mechanism needs, instead of
just claiming to in the hint text.

Fixed two real bugs surfaced while building About: CPU model string kept a
stray leading tab+colon (trim_start_matches was missing '\t'), and GPU
showed "unknown" on hardware whose lspci entry says "Display controller"
instead of "VGA compatible controller".

Local CSS patches (bos-settings/src/theme.rs) for two upstream bread-theme
gaps: suggested/destructive buttons and scale widgets don't clear Adwaita's
default background-image, so flat colour overrides were invisible; and
destructive-action red must not be pywal-derived (it can land on gold
depending on the wallpaper, indistinguishable from a primary action).
2026-07-03 23:50:26 +08:00

93 lines
3.4 KiB
Rust

use gtk4::prelude::*;
use gtk4::{Box as GBox, Button, Label, Orientation, ScrolledWindow, TextView};
use std::path::PathBuf;
use crate::ui::widgets as w;
fn css_path() -> PathBuf {
crate::config::config_dir().join("breadbar/style.css")
}
pub fn build() -> GBox {
let path = css_path();
let existing_css = std::fs::read_to_string(&path).unwrap_or_default();
let (outer, content) = w::view_scaffold("Bar");
content.append(&w::hint(
"CSS overrides for breadbar. Leave empty to use the default bread theme. \
Reloads live on save — no need to restart the bar.",
));
let buf = gtk4::TextBuffer::new(None);
buf.set_text(&existing_css);
let text_view = TextView::with_buffer(&buf);
text_view.set_monospace(true);
text_view.set_top_margin(12);
text_view.set_bottom_margin(12);
text_view.set_left_margin(12);
text_view.set_right_margin(12);
// A borderless full-bleed textview reads as a rendering bug, not an
// editor — give it the same card framing every other panel's content
// gets, and cap its height so it doesn't compete with the button row
// for the one screen's worth of space.
let editor_card = GBox::new(Orientation::Vertical, 0);
editor_card.add_css_class("card");
editor_card.set_vexpand(true);
let scroll = ScrolledWindow::new();
scroll.set_vexpand(true);
scroll.set_min_content_height(360);
scroll.set_child(Some(&text_view));
editor_card.append(&scroll);
content.append(&editor_card);
let btn_row = GBox::new(Orientation::Horizontal, 12);
btn_row.set_margin_top(12);
let save_btn = Button::with_label("Save");
save_btn.add_css_class("suggested-action");
let status_lbl = Label::new(None);
status_lbl.add_css_class("dim-label");
{
let path = path.clone();
let status_lbl = status_lbl.clone();
save_btn.connect_clicked(move |_| {
let (start, end) = buf.bounds();
let text = buf.text(&start, &end, false);
if let Some(parent) = path.parent() {
let _ = std::fs::create_dir_all(parent);
}
match std::fs::write(&path, text.as_str()) {
Ok(()) => {
// breadbar has no systemd unit (it's launched directly by
// hyprland.lua's exec-once) — SIGHUP is its own documented
// live-reload mechanism (see this file's own header
// comment: "reload live: kill -HUP $(pidof breadbar)").
// -x pkill exits non-zero if breadbar isn't running,
// which is fine — the file's still saved either way.
let reloaded = std::process::Command::new("pkill")
.args(["-HUP", "-x", "breadbar"])
.status()
.map(|s| s.success())
.unwrap_or(false);
status_lbl.set_text(if reloaded { "Saved & reloaded" } else { "Saved" });
let lbl = status_lbl.clone();
glib::timeout_add_seconds_local(3, move || {
lbl.set_text("");
glib::ControlFlow::Break
});
}
Err(e) => status_lbl.set_text(&format!("Error: {e}")),
}
});
}
btn_row.append(&save_btn);
btn_row.append(&status_lbl);
outer.append(&btn_row);
outer
}