bos-settings: round-2 GUI review fixes (stable column width, service UX, switch styling)

Root cause of two panels rendering full-bleed instead of the 760px column
(breadsearch, breadclip): hint()'s long unwrapped text reported an
unbounded natural width, and CONTENT_MAX_WIDTH is a floor (set_size_request),
not a cap — nothing was actually capping width. hint() and empty_state()
now bound their labels with set_max_width_chars, which stabilizes every
panel's column at a consistent width and left edge for the first time.

service_control(): added a second status line (enabled-at-boot vs
just-running), a confirm dialog on Stop for critical units (breadd — the
whole desktop's event backbone was one accidental click from stopping,
styled identically to optional helpers), and a consistent "Save only
writes the file, Restart applies it" hint on panels that have both a
service and a config file (previously only breadsearch said this, in its
own inconsistent wording).

Consistency sweep: Network's Scan button and empty-state treatment now
match the rest of the app (was full-width where every other secondary
action is auto-width; "not scanned" was a hint while "no results" was an
empty_state card — now both empty_state). breadclip gets an actual "Open
history" action instead of reading as an unfinished stub. breadbox's Save
button pulled out of a mixed row into its own row like every other panel.
Packages list rows now get the same card styling as every other row in
the app.

Panel title now shares view_scaffold's width+center treatment with the
content column, so it actually heads the column instead of sitting ~440px
to its left.

Also: switch widgets had a stray box-shadow/outline/border showing as a
faint ring around the knob, and bread-theme's switch slider color
(@on-surface) was being masked by Adwaita's default gloss background-image
— clearing it reveals the correctly-themed near-white knob against the
dark surface.
This commit is contained in:
Breadway 2026-07-04 10:09:59 +08:00
parent a9c73926c2
commit 23ea36dff4
9 changed files with 128 additions and 45 deletions

View file

@ -21,7 +21,7 @@ pub fn build() -> GBox {
let (outer, c) = w::view_scaffold("Daemon");
c.append(&w::service_control("breadd.service"));
c.append(&w::service_control("breadd.service", true, true));
c.append(&w::section("Daemon"));
c.append(&w::dropdown_row(

View file

@ -141,7 +141,7 @@ pub fn build() -> GBox {
let model = Rc::new(RefCell::new(read_contexts(&doc.borrow())));
let (outer, content) = w::view_scaffold("Launcher");
content.append(&w::service_control("breadbox-sync.service"));
content.append(&w::service_control("breadbox-sync.service", false, true));
content.append(&w::section("Contexts"));
content.append(&w::hint(
@ -157,10 +157,12 @@ pub fn build() -> GBox {
scroll.set_child(Some(&list));
content.append(&scroll);
let btn_row = GBox::new(Orientation::Horizontal, 8);
btn_row.set_margin_top(8);
// Add-row button lives with the list it adds to, halign Start like
// breadcrumbs' "Add network"/"Add profile" — Save stays alone in its
// own row at the bottom, consistent with every other panel.
let add_btn = Button::with_label("Add context");
add_btn.set_halign(gtk4::Align::Start);
add_btn.set_margin_top(8);
{
let model = model.clone();
let list = list.clone();
@ -172,12 +174,14 @@ pub fn build() -> GBox {
rebuild_list(&list, &model);
});
}
content.append(&add_btn);
let btn_row = GBox::new(Orientation::Horizontal, 12);
btn_row.set_margin_top(16);
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 doc = doc.clone();
let model = model.clone();
@ -198,8 +202,6 @@ pub fn build() -> GBox {
}
});
}
btn_row.append(&add_btn);
btn_row.append(&save_btn);
btn_row.append(&status_lbl);
outer.append(&btn_row);

View file

@ -5,7 +5,7 @@
//! had no presence in Settings at all despite running its own service.
use gtk4::prelude::*;
use gtk4::Box as GBox;
use gtk4::{Box as GBox, Button};
use crate::ui::widgets as w;
@ -13,12 +13,21 @@ pub fn build() -> GBox {
let (outer, c) = w::view_scaffold("Clipboard");
c.append(&w::hint(
"breadclip keeps a history of copied text/images and shows it as a \
popup (SUPER+V or SUPER+SHIFT+V). breadclipd is the background \
daemon that actually watches the clipboard breadclip itself is \
just the popup UI, launched on demand.",
"Keeps a history of copied text/images and shows it as a popup. \
breadclipd (below) is the background daemon that watches the \
clipboard breadclip itself is just the popup UI, launched on \
demand by the keybind or the button below.",
));
c.append(&w::service_control("breadclipd.service"));
let open_btn = Button::with_label("Open history (SUPER+V)");
open_btn.add_css_class("suggested-action");
open_btn.set_halign(gtk4::Align::Start);
open_btn.connect_clicked(|_| {
let _ = std::process::Command::new("breadclip").spawn();
});
c.append(&open_btn);
c.append(&w::service_control("breadclipd.service", false, false));
outer
}

View file

@ -335,7 +335,7 @@ pub fn build() -> GBox {
let profiles = Rc::new(RefCell::new(read_profiles(&doc.borrow())));
let (outer, content) = w::view_scaffold("Wi-Fi Profiles");
content.append(&w::service_control("breadcrumbs.service"));
content.append(&w::service_control("breadcrumbs.service", false, true));
// [settings] — edited in place on the shared doc
content.append(&w::section("Settings"));

View file

@ -19,7 +19,7 @@ pub fn build() -> GBox {
let doc = Rc::new(RefCell::new(config::load_doc(&path)));
let (outer, c) = w::view_scaffold("File Search");
c.append(&w::service_control("breadmill.service"));
c.append(&w::service_control("breadmill.service", false, true));
c.append(&w::section("Power"));
c.append(&w::hint(
@ -110,10 +110,6 @@ pub fn build() -> GBox {
200,
));
c.append(&w::hint(
"Changes take effect after a restart — use the Restart button above.",
));
outer.append(&w::save_button(&doc, path));
outer
}

View file

@ -104,18 +104,25 @@ pub fn build() -> GBox {
let list = ListBox::new();
list.set_selection_mode(gtk4::SelectionMode::None);
// Starts with an empty_state row ("Not scanned yet") rather than being
// hidden — the "no results yet" and "scanned, found nothing" states are
// the same idea and should look the same, not one a hint label and the
// other an empty_state card.
{
let row = ListBoxRow::new();
row.set_selectable(false);
row.set_child(Some(&w::empty_state(
"network-wireless-symbolic",
"Not scanned yet",
"Press Scan to see nearby networks.",
)));
list.append(&row);
}
let scroll = ScrolledWindow::new();
scroll.set_min_content_height(220);
scroll.set_child(Some(&list));
// Hidden until a scan actually produces results — a 220px empty scroll
// area between the radio row and the Scan button was dead space on
// every fresh open of this panel.
scroll.set_visible(false);
content.append(&scroll);
let not_scanned = w::hint("Not scanned yet — press Scan to see nearby networks.");
content.append(&not_scanned);
// Password entry, shown only while connecting to a secured, unknown
// network — set visible/hidden rather than a modal dialog, to keep this
// panel's async flow in one place instead of a nested dialog callback.
@ -189,8 +196,6 @@ pub fn build() -> GBox {
let populate_list = {
let list = list.clone();
let scroll = scroll.clone();
let not_scanned = not_scanned.clone();
let pw_row = pw_row.clone();
let pending_ssid = pending_ssid.clone();
let connect_open_or_known = connect_open_or_known.clone();
@ -198,8 +203,6 @@ pub fn build() -> GBox {
while let Some(child) = list.first_child() {
list.remove(&child);
}
not_scanned.set_visible(false);
scroll.set_visible(true);
if networks.is_empty() {
let row = ListBoxRow::new();
row.set_selectable(false);
@ -280,6 +283,7 @@ pub fn build() -> GBox {
};
let scan_btn = Button::with_label("Scan");
scan_btn.set_halign(gtk4::Align::Start);
{
let status = status.clone();
let populate_list = populate_list.clone();

View file

@ -66,10 +66,9 @@ fn populate_packages(list: &ListBox, log_buf: &gtk4::TextBuffer, log_view: &Text
let row = ListBoxRow::new();
row.set_selectable(false);
let hbox = GBox::new(Orientation::Horizontal, 16);
hbox.set_margin_top(6);
hbox.set_margin_bottom(6);
hbox.set_margin_start(8);
hbox.set_margin_end(8);
hbox.add_css_class("card");
hbox.set_margin_top(3);
hbox.set_margin_bottom(3);
let name_lbl = Label::new(Some(name));
name_lbl.set_hexpand(true);