dev #1
9 changed files with 128 additions and 45 deletions
|
|
@ -34,6 +34,12 @@ scale slider { background-color: @on-surface; border-radius: 999px; }\n\
|
||||||
primary action instead of a dangerous one. Fixed regardless of palette. */\n\
|
primary action instead of a dangerous one. Fixed regardless of palette. */\n\
|
||||||
button.destructive-action { background-color: #c0392b; color: #ffffff; }\n\
|
button.destructive-action { background-color: #c0392b; color: #ffffff; }\n\
|
||||||
button.destructive-action:hover { background-color: #d64535; }\n\
|
button.destructive-action:hover { background-color: #d64535; }\n\
|
||||||
|
/* Adwaita's default switch slider (the knob) carries a box-shadow used for \
|
||||||
|
its 3D bevel look — bread-theme's override only sets background-color, \
|
||||||
|
so that shadow still renders as a pale ring around the knob on top of \
|
||||||
|
our flat colour. */\n\
|
||||||
|
switch slider { box-shadow: none; outline: none; border: none; background-image: none; }\n\
|
||||||
|
switch { box-shadow: none; outline: none; border: none; background-image: none; }\n\
|
||||||
";
|
";
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ pub fn build() -> GBox {
|
||||||
|
|
||||||
let (outer, c) = w::view_scaffold("Daemon");
|
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::section("Daemon"));
|
||||||
c.append(&w::dropdown_row(
|
c.append(&w::dropdown_row(
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ pub fn build() -> GBox {
|
||||||
let model = Rc::new(RefCell::new(read_contexts(&doc.borrow())));
|
let model = Rc::new(RefCell::new(read_contexts(&doc.borrow())));
|
||||||
|
|
||||||
let (outer, content) = w::view_scaffold("Launcher");
|
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::section("Contexts"));
|
||||||
content.append(&w::hint(
|
content.append(&w::hint(
|
||||||
|
|
@ -157,10 +157,12 @@ pub fn build() -> GBox {
|
||||||
scroll.set_child(Some(&list));
|
scroll.set_child(Some(&list));
|
||||||
content.append(&scroll);
|
content.append(&scroll);
|
||||||
|
|
||||||
let btn_row = GBox::new(Orientation::Horizontal, 8);
|
// Add-row button lives with the list it adds to, halign Start like
|
||||||
btn_row.set_margin_top(8);
|
// 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");
|
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 model = model.clone();
|
||||||
let list = list.clone();
|
let list = list.clone();
|
||||||
|
|
@ -172,12 +174,14 @@ pub fn build() -> GBox {
|
||||||
rebuild_list(&list, &model);
|
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");
|
let save_btn = Button::with_label("Save");
|
||||||
save_btn.add_css_class("suggested-action");
|
save_btn.add_css_class("suggested-action");
|
||||||
let status_lbl = Label::new(None);
|
let status_lbl = Label::new(None);
|
||||||
status_lbl.add_css_class("dim-label");
|
status_lbl.add_css_class("dim-label");
|
||||||
|
|
||||||
{
|
{
|
||||||
let doc = doc.clone();
|
let doc = doc.clone();
|
||||||
let model = model.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(&save_btn);
|
||||||
btn_row.append(&status_lbl);
|
btn_row.append(&status_lbl);
|
||||||
outer.append(&btn_row);
|
outer.append(&btn_row);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
//! had no presence in Settings at all despite running its own service.
|
//! had no presence in Settings at all despite running its own service.
|
||||||
|
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4::Box as GBox;
|
use gtk4::{Box as GBox, Button};
|
||||||
|
|
||||||
use crate::ui::widgets as w;
|
use crate::ui::widgets as w;
|
||||||
|
|
||||||
|
|
@ -13,12 +13,21 @@ pub fn build() -> GBox {
|
||||||
let (outer, c) = w::view_scaffold("Clipboard");
|
let (outer, c) = w::view_scaffold("Clipboard");
|
||||||
|
|
||||||
c.append(&w::hint(
|
c.append(&w::hint(
|
||||||
"breadclip keeps a history of copied text/images and shows it as a \
|
"Keeps a history of copied text/images and shows it as a popup. \
|
||||||
popup (SUPER+V or SUPER+SHIFT+V). breadclipd is the background \
|
breadclipd (below) is the background daemon that watches the \
|
||||||
daemon that actually watches the clipboard — breadclip itself is \
|
clipboard — breadclip itself is just the popup UI, launched on \
|
||||||
just the popup UI, launched on demand.",
|
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
|
outer
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -335,7 +335,7 @@ pub fn build() -> GBox {
|
||||||
let profiles = Rc::new(RefCell::new(read_profiles(&doc.borrow())));
|
let profiles = Rc::new(RefCell::new(read_profiles(&doc.borrow())));
|
||||||
|
|
||||||
let (outer, content) = w::view_scaffold("Wi-Fi Profiles");
|
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
|
// [settings] — edited in place on the shared doc
|
||||||
content.append(&w::section("Settings"));
|
content.append(&w::section("Settings"));
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ pub fn build() -> GBox {
|
||||||
let doc = Rc::new(RefCell::new(config::load_doc(&path)));
|
let doc = Rc::new(RefCell::new(config::load_doc(&path)));
|
||||||
|
|
||||||
let (outer, c) = w::view_scaffold("File Search");
|
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::section("Power"));
|
||||||
c.append(&w::hint(
|
c.append(&w::hint(
|
||||||
|
|
@ -110,10 +110,6 @@ pub fn build() -> GBox {
|
||||||
200,
|
200,
|
||||||
));
|
));
|
||||||
|
|
||||||
c.append(&w::hint(
|
|
||||||
"Changes take effect after a restart — use the Restart button above.",
|
|
||||||
));
|
|
||||||
|
|
||||||
outer.append(&w::save_button(&doc, path));
|
outer.append(&w::save_button(&doc, path));
|
||||||
outer
|
outer
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,18 +104,25 @@ pub fn build() -> GBox {
|
||||||
|
|
||||||
let list = ListBox::new();
|
let list = ListBox::new();
|
||||||
list.set_selection_mode(gtk4::SelectionMode::None);
|
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();
|
let scroll = ScrolledWindow::new();
|
||||||
scroll.set_min_content_height(220);
|
scroll.set_min_content_height(220);
|
||||||
scroll.set_child(Some(&list));
|
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);
|
content.append(&scroll);
|
||||||
|
|
||||||
let not_scanned = w::hint("Not scanned yet — press Scan to see nearby networks.");
|
|
||||||
content.append(¬_scanned);
|
|
||||||
|
|
||||||
// Password entry, shown only while connecting to a secured, unknown
|
// Password entry, shown only while connecting to a secured, unknown
|
||||||
// network — set visible/hidden rather than a modal dialog, to keep this
|
// 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.
|
// 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 populate_list = {
|
||||||
let list = list.clone();
|
let list = list.clone();
|
||||||
let scroll = scroll.clone();
|
|
||||||
let not_scanned = not_scanned.clone();
|
|
||||||
let pw_row = pw_row.clone();
|
let pw_row = pw_row.clone();
|
||||||
let pending_ssid = pending_ssid.clone();
|
let pending_ssid = pending_ssid.clone();
|
||||||
let connect_open_or_known = connect_open_or_known.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() {
|
while let Some(child) = list.first_child() {
|
||||||
list.remove(&child);
|
list.remove(&child);
|
||||||
}
|
}
|
||||||
not_scanned.set_visible(false);
|
|
||||||
scroll.set_visible(true);
|
|
||||||
if networks.is_empty() {
|
if networks.is_empty() {
|
||||||
let row = ListBoxRow::new();
|
let row = ListBoxRow::new();
|
||||||
row.set_selectable(false);
|
row.set_selectable(false);
|
||||||
|
|
@ -280,6 +283,7 @@ pub fn build() -> GBox {
|
||||||
};
|
};
|
||||||
|
|
||||||
let scan_btn = Button::with_label("Scan");
|
let scan_btn = Button::with_label("Scan");
|
||||||
|
scan_btn.set_halign(gtk4::Align::Start);
|
||||||
{
|
{
|
||||||
let status = status.clone();
|
let status = status.clone();
|
||||||
let populate_list = populate_list.clone();
|
let populate_list = populate_list.clone();
|
||||||
|
|
|
||||||
|
|
@ -66,10 +66,9 @@ fn populate_packages(list: &ListBox, log_buf: >k4::TextBuffer, log_view: &Text
|
||||||
let row = ListBoxRow::new();
|
let row = ListBoxRow::new();
|
||||||
row.set_selectable(false);
|
row.set_selectable(false);
|
||||||
let hbox = GBox::new(Orientation::Horizontal, 16);
|
let hbox = GBox::new(Orientation::Horizontal, 16);
|
||||||
hbox.set_margin_top(6);
|
hbox.add_css_class("card");
|
||||||
hbox.set_margin_bottom(6);
|
hbox.set_margin_top(3);
|
||||||
hbox.set_margin_start(8);
|
hbox.set_margin_bottom(3);
|
||||||
hbox.set_margin_end(8);
|
|
||||||
|
|
||||||
let name_lbl = Label::new(Some(name));
|
let name_lbl = Label::new(Some(name));
|
||||||
name_lbl.set_hexpand(true);
|
name_lbl.set_hexpand(true);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use gtk4::prelude::*;
|
use gtk4::prelude::*;
|
||||||
use gtk4::{
|
use gtk4::{
|
||||||
Adjustment, Box as GBox, Button, DropDown, Entry, Expression, Label, Orientation,
|
Adjustment, AlertDialog, Box as GBox, Button, DropDown, Entry, Expression, Label, Orientation,
|
||||||
SpinButton, StringList, Switch,
|
SpinButton, StringList, Switch,
|
||||||
};
|
};
|
||||||
use toml_edit::DocumentMut;
|
use toml_edit::DocumentMut;
|
||||||
|
|
@ -77,6 +77,13 @@ pub fn hint(text: &str) -> Label {
|
||||||
lbl.add_css_class("dim-label");
|
lbl.add_css_class("dim-label");
|
||||||
lbl.set_xalign(0.0);
|
lbl.set_xalign(0.0);
|
||||||
lbl.set_wrap(true);
|
lbl.set_wrap(true);
|
||||||
|
// set_wrap alone doesn't cap a label's *natural* width — a long enough
|
||||||
|
// hint reports a natural width of its whole unwrapped string, which
|
||||||
|
// `view_scaffold`'s content column (sized to its widest child, floored
|
||||||
|
// not capped at CONTENT_MAX_WIDTH) then inherits, blowing the entire
|
||||||
|
// panel full-width. This is what makes breadsearch/breadclip render
|
||||||
|
// edge-to-edge while every other panel sits in a stable ~760px column.
|
||||||
|
lbl.set_max_width_chars(64);
|
||||||
lbl.set_margin_bottom(4);
|
lbl.set_margin_bottom(4);
|
||||||
lbl
|
lbl
|
||||||
}
|
}
|
||||||
|
|
@ -111,6 +118,7 @@ pub fn empty_state(icon_name: &str, title: &str, hint_text: &str) -> GBox {
|
||||||
hint_lbl.add_css_class("dim-label");
|
hint_lbl.add_css_class("dim-label");
|
||||||
hint_lbl.set_justify(gtk4::Justification::Center);
|
hint_lbl.set_justify(gtk4::Justification::Center);
|
||||||
hint_lbl.set_wrap(true);
|
hint_lbl.set_wrap(true);
|
||||||
|
hint_lbl.set_max_width_chars(48);
|
||||||
wrapper.append(&hint_lbl);
|
wrapper.append(&hint_lbl);
|
||||||
|
|
||||||
wrapper
|
wrapper
|
||||||
|
|
@ -126,18 +134,19 @@ pub fn view_scaffold(title: &str) -> (GBox, GBox) {
|
||||||
let title_lbl = Label::new(Some(title));
|
let title_lbl = Label::new(Some(title));
|
||||||
title_lbl.add_css_class("title");
|
title_lbl.add_css_class("title");
|
||||||
title_lbl.set_xalign(0.0);
|
title_lbl.set_xalign(0.0);
|
||||||
|
// Same width+center treatment as `content` below, so the title's left
|
||||||
|
// edge lines up with the column it's heading instead of sitting flush
|
||||||
|
// against the panel edge while the column centers ~440px to its right.
|
||||||
|
title_lbl.set_halign(gtk4::Align::Center);
|
||||||
|
title_lbl.set_size_request(CONTENT_MAX_WIDTH, -1);
|
||||||
outer.append(&title_lbl);
|
outer.append(&title_lbl);
|
||||||
|
|
||||||
let content = GBox::new(Orientation::Vertical, 4);
|
let content = GBox::new(Orientation::Vertical, 4);
|
||||||
// Explicit hexpand(false) overrides the auto-computed value GTK would
|
// Explicit hexpand(false) overrides the auto-computed value GTK would
|
||||||
// otherwise derive from the hexpand-ing labels inside every row, and
|
// otherwise derive from the hexpand-ing labels inside every row, and
|
||||||
// Start keeps content pinned under the left-aligned title instead of
|
// Center distributes the dead space evenly either side on a wide window
|
||||||
// stretching (Fill) or floating in the middle of a wide pane (Center).
|
// instead of stranding it all on the right of a left-hugging column.
|
||||||
content.set_hexpand(false);
|
content.set_hexpand(false);
|
||||||
// Center rather than left-align: on a wide/maximized window, a
|
|
||||||
// left-hugging column just moves the dead space from "right of every
|
|
||||||
// row" to "right of the whole column" — centering distributes it evenly
|
|
||||||
// either side instead, which reads as deliberate rather than stranded.
|
|
||||||
content.set_halign(gtk4::Align::Center);
|
content.set_halign(gtk4::Align::Center);
|
||||||
content.set_size_request(CONTENT_MAX_WIDTH, -1);
|
content.set_size_request(CONTENT_MAX_WIDTH, -1);
|
||||||
|
|
||||||
|
|
@ -341,11 +350,29 @@ fn systemctl_active(unit: &str) -> bool {
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn systemctl_enabled(unit: &str) -> bool {
|
||||||
|
std::process::Command::new("systemctl")
|
||||||
|
.args(["--user", "is-enabled", "--quiet", unit])
|
||||||
|
.status()
|
||||||
|
.map(|s| s.success())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
/// Live systemd `--user` unit status plus Start/Stop/Restart/Logs controls —
|
/// Live systemd `--user` unit status plus Start/Stop/Restart/Logs controls —
|
||||||
/// every bread-ecosystem panel whose app is actually a daemon (not just a
|
/// every bread-ecosystem panel whose app is actually a daemon (not just a
|
||||||
/// config file) gets this, instead of only ever being able to edit the TOML
|
/// config file) gets this, instead of only ever being able to edit the TOML
|
||||||
/// and hope the running process picks it up.
|
/// and hope the running process picks it up.
|
||||||
pub fn service_control(unit: &'static str) -> GBox {
|
///
|
||||||
|
/// `critical` guards Stop behind a confirm dialog — for a unit like `breadd`
|
||||||
|
/// (the whole desktop's event backbone), a bare click currently stops it as
|
||||||
|
/// casually as an optional sync helper; everything else can stay one-click.
|
||||||
|
///
|
||||||
|
/// `has_config` adds a one-line note that Restart (not Save) is what applies
|
||||||
|
/// config changes below — previously only breadsearch's panel said this,
|
||||||
|
/// inconsistently, in its own hint text; bread/breadbox/breadcrumbs edit a
|
||||||
|
/// running daemon's TOML and never mentioned the running process won't
|
||||||
|
/// notice until restarted.
|
||||||
|
pub fn service_control(unit: &'static str, critical: bool, has_config: bool) -> GBox {
|
||||||
let wrapper = GBox::new(Orientation::Vertical, 4);
|
let wrapper = GBox::new(Orientation::Vertical, 4);
|
||||||
wrapper.append(§ion("Service"));
|
wrapper.append(§ion("Service"));
|
||||||
|
|
||||||
|
|
@ -354,6 +381,10 @@ pub fn service_control(unit: &'static str) -> GBox {
|
||||||
status_lbl.set_selectable(true);
|
status_lbl.set_selectable(true);
|
||||||
wrapper.append(&row(unit, &status_lbl));
|
wrapper.append(&row(unit, &status_lbl));
|
||||||
|
|
||||||
|
let enabled_lbl = Label::new(None);
|
||||||
|
enabled_lbl.add_css_class("dim-label");
|
||||||
|
wrapper.append(&row("Starts at login", &enabled_lbl));
|
||||||
|
|
||||||
let btn_row = GBox::new(Orientation::Horizontal, 8);
|
let btn_row = GBox::new(Orientation::Horizontal, 8);
|
||||||
btn_row.set_margin_top(4);
|
btn_row.set_margin_top(4);
|
||||||
let toggle_btn = Button::new();
|
let toggle_btn = Button::new();
|
||||||
|
|
@ -365,24 +396,53 @@ pub fn service_control(unit: &'static str) -> GBox {
|
||||||
// plain closures aren't Clone.
|
// plain closures aren't Clone.
|
||||||
let refresh: Rc<dyn Fn()> = {
|
let refresh: Rc<dyn Fn()> = {
|
||||||
let status_lbl = status_lbl.clone();
|
let status_lbl = status_lbl.clone();
|
||||||
|
let enabled_lbl = enabled_lbl.clone();
|
||||||
let toggle_btn = toggle_btn.clone();
|
let toggle_btn = toggle_btn.clone();
|
||||||
Rc::new(move || {
|
Rc::new(move || {
|
||||||
let active = systemctl_active(unit);
|
let active = systemctl_active(unit);
|
||||||
status_lbl.set_text(if active { "Running" } else { "Stopped" });
|
status_lbl.set_text(if active { "Running" } else { "Stopped" });
|
||||||
toggle_btn.set_label(if active { "Stop" } else { "Start" });
|
toggle_btn.set_label(if active { "Stop" } else { "Start" });
|
||||||
|
enabled_lbl.set_text(if systemctl_enabled(unit) { "Yes" } else { "No" });
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
{
|
let do_toggle: Rc<dyn Fn()> = {
|
||||||
let refresh = refresh.clone();
|
let refresh = refresh.clone();
|
||||||
toggle_btn.connect_clicked(move |_| {
|
Rc::new(move || {
|
||||||
let verb = if systemctl_active(unit) { "stop" } else { "start" };
|
let verb = if systemctl_active(unit) { "stop" } else { "start" };
|
||||||
let log_buf = gtk4::TextBuffer::new(None);
|
let log_buf = gtk4::TextBuffer::new(None);
|
||||||
let refresh = refresh.clone();
|
let refresh = refresh.clone();
|
||||||
stream_command_then(&["systemctl", "--user", verb, unit], log_buf, move || {
|
stream_command_then(&["systemctl", "--user", verb, unit], log_buf, move || {
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
};
|
||||||
|
{
|
||||||
|
let do_toggle = do_toggle.clone();
|
||||||
|
toggle_btn.connect_clicked(move |btn| {
|
||||||
|
if critical && systemctl_active(unit) {
|
||||||
|
let window = btn.root().and_then(|r| r.downcast::<gtk4::Window>().ok());
|
||||||
|
let dialog = AlertDialog::builder()
|
||||||
|
.message(format!("Stop {unit}?"))
|
||||||
|
.detail(
|
||||||
|
"This is a core part of the desktop's event handling — \
|
||||||
|
stopping it may affect other bread apps until it's \
|
||||||
|
restarted.",
|
||||||
|
)
|
||||||
|
.buttons(["Cancel", "Stop"])
|
||||||
|
.cancel_button(0)
|
||||||
|
.default_button(0)
|
||||||
|
.build();
|
||||||
|
let do_toggle = do_toggle.clone();
|
||||||
|
dialog.choose(window.as_ref(), gtk4::gio::Cancellable::NONE, move |result| {
|
||||||
|
if result == Ok(1) {
|
||||||
|
do_toggle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
do_toggle();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|
@ -406,6 +466,13 @@ pub fn service_control(unit: &'static str) -> GBox {
|
||||||
btn_row.append(&logs_btn);
|
btn_row.append(&logs_btn);
|
||||||
wrapper.append(&btn_row);
|
wrapper.append(&btn_row);
|
||||||
|
|
||||||
|
if has_config {
|
||||||
|
wrapper.append(&hint(
|
||||||
|
"Save (below) only writes the config file — click Restart above \
|
||||||
|
for the running service to pick up the change.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
wrapper
|
wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue