waydock v0.1: panel, pins, fönstergrupper, menyer, settings-GUI, live-config
Some checks failed
check / check (push) Failing after 52s
Some checks failed
check / check (push) Failing after 52s
M0–M5 ur doc/plan.md: layer-shell-dock per skärm med transparent #222222-ö, pins + körande appar (gruppering, badges, indikatorer), klick/skroll/mittklick, konfigurerbar högerklicksmeny, tomyta-meny, settings-GUI som redigerar JSON-configen live, filewatcher, Wayfire-IPC-stubbar (scale, flytta-till-skärm), CI + release-flöde. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
529
src/settings.rs
Normal file
529
src/settings.rs
Normal file
@@ -0,0 +1,529 @@
|
||||
// Settings-GUI:t: ett GTK4-fönster som redigerar hela JSON-configen.
|
||||
// Varje widgetändring uppdaterar delade configen, applicerar direkt
|
||||
// (snabb feedback) och sparar filen debouncat — samma fil som kan
|
||||
// handredigeras, filewatchern håller allt i synk.
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use gtk4 as gtk;
|
||||
use gtk4::gdk;
|
||||
use gtk4::glib;
|
||||
use gtk4::prelude::*;
|
||||
use log::warn;
|
||||
|
||||
use crate::config::{
|
||||
self, AutohideMode, ClickAction, HotspotAction, IndicatorStyle, Layer, MenuItem,
|
||||
MiddleClick, Position, ScrollAction,
|
||||
};
|
||||
use crate::dock::Ctx;
|
||||
|
||||
thread_local! {
|
||||
static OPEN_WINDOW: std::cell::RefCell<Option<glib::WeakRef<gtk::Window>>> =
|
||||
const { std::cell::RefCell::new(None) };
|
||||
}
|
||||
|
||||
/// Uppdatera config + applicera + spara (debouncat 300 ms).
|
||||
fn commit(ctx: &Rc<Ctx>, mutate: impl FnOnce(&mut config::Config)) {
|
||||
mutate(&mut ctx.cfg.borrow_mut());
|
||||
if let Some(cb) = ctx.on_config_changed.borrow().as_ref() {
|
||||
cb();
|
||||
}
|
||||
let generation = ctx.save_generation.get() + 1;
|
||||
ctx.save_generation.set(generation);
|
||||
let ctx = ctx.clone();
|
||||
glib::timeout_add_local_once(Duration::from_millis(300), move || {
|
||||
if ctx.save_generation.get() == generation {
|
||||
if let Err(e) = config::save(&ctx.cfg_path, &ctx.cfg.borrow()) {
|
||||
warn!("kunde inte spara config: {e}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn hex_of(rgba: &gdk::RGBA) -> String {
|
||||
format!(
|
||||
"#{:02x}{:02x}{:02x}",
|
||||
(rgba.red() * 255.0) as u8,
|
||||
(rgba.green() * 255.0) as u8,
|
||||
(rgba.blue() * 255.0) as u8
|
||||
)
|
||||
}
|
||||
|
||||
// -- widgethjälpare ------------------------------------------------------
|
||||
|
||||
fn page() -> (gtk::ScrolledWindow, gtk::Box) {
|
||||
let list = gtk::Box::new(gtk::Orientation::Vertical, 6);
|
||||
list.set_margin_top(12);
|
||||
list.set_margin_bottom(12);
|
||||
list.set_margin_start(14);
|
||||
list.set_margin_end(14);
|
||||
let scroll = gtk::ScrolledWindow::new();
|
||||
scroll.set_policy(gtk::PolicyType::Never, gtk::PolicyType::Automatic);
|
||||
scroll.set_child(Some(&list));
|
||||
(scroll, list)
|
||||
}
|
||||
|
||||
fn row(list: >k::Box, label: &str, widget: &impl IsA<gtk::Widget>) {
|
||||
let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 10);
|
||||
let lbl = gtk::Label::new(Some(label));
|
||||
lbl.set_halign(gtk::Align::Start);
|
||||
lbl.set_hexpand(true);
|
||||
hbox.append(&lbl);
|
||||
hbox.append(widget);
|
||||
list.append(&hbox);
|
||||
}
|
||||
|
||||
fn heading(list: >k::Box, text: &str) {
|
||||
let lbl = gtk::Label::new(None);
|
||||
lbl.set_markup(&format!("<b>{text}</b>"));
|
||||
lbl.set_halign(gtk::Align::Start);
|
||||
lbl.set_margin_top(10);
|
||||
list.append(&lbl);
|
||||
}
|
||||
|
||||
fn switch(ctx: &Rc<Ctx>, active: bool, set: impl Fn(&mut config::Config, bool) + 'static) -> gtk::Switch {
|
||||
let sw = gtk::Switch::new();
|
||||
sw.set_active(active);
|
||||
sw.set_valign(gtk::Align::Center);
|
||||
let ctx = ctx.clone();
|
||||
let set = Rc::new(set);
|
||||
sw.connect_active_notify(move |sw| {
|
||||
let v = sw.is_active();
|
||||
let set = set.clone();
|
||||
commit(&ctx, move |c| set(c, v));
|
||||
});
|
||||
sw
|
||||
}
|
||||
|
||||
fn spin(
|
||||
ctx: &Rc<Ctx>,
|
||||
value: f64,
|
||||
range: (f64, f64),
|
||||
step: f64,
|
||||
set: impl Fn(&mut config::Config, f64) + 'static,
|
||||
) -> gtk::SpinButton {
|
||||
let sb = gtk::SpinButton::with_range(range.0, range.1, step);
|
||||
sb.set_value(value);
|
||||
let ctx = ctx.clone();
|
||||
let set = Rc::new(set);
|
||||
sb.connect_value_changed(move |sb| {
|
||||
let v = sb.value();
|
||||
let set = set.clone();
|
||||
commit(&ctx, move |c| set(c, v));
|
||||
});
|
||||
sb
|
||||
}
|
||||
|
||||
fn scale01(ctx: &Rc<Ctx>, value: f64, set: impl Fn(&mut config::Config, f64) + 'static) -> gtk::Scale {
|
||||
let sc = gtk::Scale::with_range(gtk::Orientation::Horizontal, 0.0, 1.0, 0.01);
|
||||
sc.set_value(value);
|
||||
sc.set_size_request(160, -1);
|
||||
sc.set_draw_value(false);
|
||||
let ctx = ctx.clone();
|
||||
let set = Rc::new(set);
|
||||
sc.connect_value_changed(move |sc| {
|
||||
let v = sc.value();
|
||||
let set = set.clone();
|
||||
commit(&ctx, move |c| set(c, v));
|
||||
});
|
||||
sc
|
||||
}
|
||||
|
||||
fn color_button(
|
||||
ctx: &Rc<Ctx>,
|
||||
hex: &str,
|
||||
set: impl Fn(&mut config::Config, String) + 'static,
|
||||
) -> gtk::ColorDialogButton {
|
||||
let btn = gtk::ColorDialogButton::new(Some(gtk::ColorDialog::new()));
|
||||
if let Ok(rgba) = gdk::RGBA::parse(hex) {
|
||||
btn.set_rgba(&rgba);
|
||||
}
|
||||
let ctx = ctx.clone();
|
||||
let set = Rc::new(set);
|
||||
btn.connect_rgba_notify(move |btn| {
|
||||
let hex = hex_of(&btn.rgba());
|
||||
let set = set.clone();
|
||||
commit(&ctx, move |c| set(c, hex));
|
||||
});
|
||||
btn
|
||||
}
|
||||
|
||||
fn dropdown(
|
||||
ctx: &Rc<Ctx>,
|
||||
options: &[&str],
|
||||
selected: usize,
|
||||
set: impl Fn(&mut config::Config, usize) + 'static,
|
||||
) -> gtk::DropDown {
|
||||
let dd = gtk::DropDown::from_strings(options);
|
||||
dd.set_selected(selected as u32);
|
||||
let ctx = ctx.clone();
|
||||
let set = Rc::new(set);
|
||||
dd.connect_selected_notify(move |dd| {
|
||||
let i = dd.selected() as usize;
|
||||
let set = set.clone();
|
||||
commit(&ctx, move |c| set(c, i));
|
||||
});
|
||||
dd
|
||||
}
|
||||
|
||||
// -- hotspot-hjälpare ----------------------------------------------------
|
||||
|
||||
fn hotspot_to_text(a: &Option<HotspotAction>) -> String {
|
||||
match a {
|
||||
Some(HotspotAction::Exec(cmd)) => format!("exec:{cmd}"),
|
||||
Some(HotspotAction::Ipc(m)) => format!("ipc:{m}"),
|
||||
None => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn text_to_hotspot(text: &str) -> Option<HotspotAction> {
|
||||
let t = text.trim();
|
||||
if let Some(cmd) = t.strip_prefix("exec:") {
|
||||
Some(HotspotAction::Exec(cmd.trim().to_string()))
|
||||
} else if let Some(m) = t.strip_prefix("ipc:") {
|
||||
Some(HotspotAction::Ipc(m.trim().to_string()))
|
||||
} else if t.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(HotspotAction::Exec(t.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
fn hotspot_entry(
|
||||
ctx: &Rc<Ctx>,
|
||||
current: &Option<HotspotAction>,
|
||||
set: impl Fn(&mut config::Config, Option<HotspotAction>) + 'static,
|
||||
) -> gtk::Entry {
|
||||
let entry = gtk::Entry::new();
|
||||
entry.set_text(&hotspot_to_text(current));
|
||||
entry.set_placeholder_text(Some("exec:kommando eller ipc:scale/toggle"));
|
||||
entry.set_width_chars(24);
|
||||
let ctx = ctx.clone();
|
||||
let set = Rc::new(set);
|
||||
entry.connect_changed(move |e| {
|
||||
let action = text_to_hotspot(&e.text());
|
||||
let set = set.clone();
|
||||
commit(&ctx, move |c| set(c, action));
|
||||
});
|
||||
entry
|
||||
}
|
||||
|
||||
// -- sidorna -------------------------------------------------------------
|
||||
|
||||
fn build_appearance_page(ctx: &Rc<Ctx>) -> gtk::ScrolledWindow {
|
||||
let (scroll, list) = page();
|
||||
let a = ctx.cfg.borrow().appearance.clone();
|
||||
|
||||
heading(&list, "Panel");
|
||||
row(&list, "Panelfärg", &color_button(ctx, &a.background, |c, v| c.appearance.background = v));
|
||||
row(&list, "Transparens", &scale01(ctx, a.opacity, |c, v| c.appearance.opacity = v));
|
||||
row(&list, "Hörnradie", &spin(ctx, a.corner_radius as f64, (0.0, 40.0), 1.0, |c, v| c.appearance.corner_radius = v as u32));
|
||||
row(&list, "Accentfärg", &color_button(ctx, &a.accent, |c, v| c.appearance.accent = v));
|
||||
|
||||
heading(&list, "Kant & skugga");
|
||||
row(&list, "Kantfärg", &color_button(ctx, &a.border.color, |c, v| c.appearance.border.color = v));
|
||||
row(&list, "Kantopacitet", &scale01(ctx, a.border.opacity, |c, v| c.appearance.border.opacity = v));
|
||||
row(&list, "Kantbredd", &spin(ctx, a.border.width as f64, (0.0, 8.0), 1.0, |c, v| c.appearance.border.width = v as u32));
|
||||
row(&list, "Skugga", &switch(ctx, a.shadow.enabled, |c, v| c.appearance.shadow.enabled = v));
|
||||
row(&list, "Skuggstorlek", &spin(ctx, a.shadow.blur as f64, (0.0, 60.0), 1.0, |c, v| c.appearance.shadow.blur = v as u32));
|
||||
row(&list, "Skuggopacitet", &scale01(ctx, a.shadow.opacity, |c, v| c.appearance.shadow.opacity = v));
|
||||
|
||||
heading(&list, "Ikoner");
|
||||
row(&list, "Ikonstorlek", &spin(ctx, a.icon_size as f64, (16.0, 96.0), 2.0, |c, v| c.appearance.icon_size = v as i32));
|
||||
row(&list, "Ikonavstånd", &spin(ctx, a.icon_spacing as f64, (0.0, 32.0), 1.0, |c, v| c.appearance.icon_spacing = v as i32));
|
||||
row(&list, "Panel-padding", &spin(ctx, a.padding as f64, (0.0, 32.0), 1.0, |c, v| c.appearance.padding = v as i32));
|
||||
row(&list, "Hover-zoom", &switch(ctx, a.hover_zoom, |c, v| c.appearance.hover_zoom = v));
|
||||
row(&list, "Animationer", &switch(ctx, a.animations, |c, v| c.appearance.animations = v));
|
||||
let ind_idx = match a.indicator.style {
|
||||
IndicatorStyle::Dot => 0,
|
||||
IndicatorStyle::Line => 1,
|
||||
IndicatorStyle::None => 2,
|
||||
};
|
||||
row(&list, "Indikatorstil", &dropdown(ctx, &["Prickar", "Linje", "Ingen"], ind_idx, |c, i| {
|
||||
c.appearance.indicator.style = match i {
|
||||
1 => IndicatorStyle::Line,
|
||||
2 => IndicatorStyle::None,
|
||||
_ => IndicatorStyle::Dot,
|
||||
};
|
||||
}));
|
||||
row(&list, "Max indikatorer", &spin(ctx, a.indicator.max as f64, (1.0, 5.0), 1.0, |c, v| c.appearance.indicator.max = v as u32));
|
||||
|
||||
scroll
|
||||
}
|
||||
|
||||
fn build_layout_page(ctx: &Rc<Ctx>) -> gtk::ScrolledWindow {
|
||||
let (scroll, list) = page();
|
||||
let cfg = ctx.cfg.borrow().clone();
|
||||
|
||||
heading(&list, "Placering");
|
||||
let pos_idx = match cfg.position {
|
||||
Position::Bottom => 0,
|
||||
Position::Top => 1,
|
||||
Position::Left => 2,
|
||||
Position::Right => 3,
|
||||
};
|
||||
row(&list, "Kant", &dropdown(ctx, &["Nere", "Uppe", "Vänster", "Höger"], pos_idx, |c, i| {
|
||||
c.position = match i {
|
||||
1 => Position::Top,
|
||||
2 => Position::Left,
|
||||
3 => Position::Right,
|
||||
_ => Position::Bottom,
|
||||
};
|
||||
}));
|
||||
row(&list, "Centrerad ö", &switch(ctx, cfg.centered, |c, v| c.centered = v));
|
||||
row(&list, "Marginal från kant", &spin(ctx, cfg.margin as f64, (0.0, 64.0), 1.0, |c, v| c.margin = v as i32));
|
||||
let layer_idx = match cfg.layer {
|
||||
Layer::Top => 0,
|
||||
Layer::Overlay => 1,
|
||||
Layer::Bottom => 2,
|
||||
};
|
||||
row(&list, "Lager", &dropdown(ctx, &["Ovanpå fönster", "Alltid överst (overlay)", "Under fönster"], layer_idx, |c, i| {
|
||||
c.layer = match i {
|
||||
1 => Layer::Overlay,
|
||||
2 => Layer::Bottom,
|
||||
_ => Layer::Top,
|
||||
};
|
||||
}));
|
||||
row(&list, "Reservera plats (undvik fönster)", &switch(ctx, cfg.avoid_windows, |c, v| c.avoid_windows = v));
|
||||
|
||||
heading(&list, "Autohide");
|
||||
row(&list, "Aktiverad", &switch(ctx, cfg.autohide.enabled, |c, v| c.autohide.enabled = v));
|
||||
let mode_idx = match cfg.autohide.mode {
|
||||
AutohideMode::Dodge => 0,
|
||||
AutohideMode::Always => 1,
|
||||
};
|
||||
row(&list, "Läge", &dropdown(ctx, &["Dodge (göm vid överlapp)", "Göm alltid"], mode_idx, |c, i| {
|
||||
c.autohide.mode = if i == 1 { AutohideMode::Always } else { AutohideMode::Dodge };
|
||||
}));
|
||||
row(&list, "Fördröjning (ms)", &spin(ctx, cfg.autohide.hide_delay_ms as f64, (0.0, 3000.0), 50.0, |c, v| c.autohide.hide_delay_ms = v as u32));
|
||||
row(&list, "Sensorbredd (px)", &spin(ctx, cfg.autohide.reveal_px as f64, (1.0, 16.0), 1.0, |c, v| c.autohide.reveal_px = v as i32));
|
||||
row(&list, "Animationstid (ms)", &spin(ctx, cfg.autohide.animation_ms as f64, (0.0, 1000.0), 25.0, |c, v| c.autohide.animation_ms = v as u32));
|
||||
|
||||
let note = gtk::Label::new(Some("Per-skärm-inställningar (unika pins, only_this_output)\nredigeras i config.json under \"outputs\"."));
|
||||
note.add_css_class("dim-label");
|
||||
note.set_halign(gtk::Align::Start);
|
||||
note.set_margin_top(12);
|
||||
list.append(¬e);
|
||||
|
||||
scroll
|
||||
}
|
||||
|
||||
fn build_behavior_page(ctx: &Rc<Ctx>) -> gtk::ScrolledWindow {
|
||||
let (scroll, list) = page();
|
||||
let b = ctx.cfg.borrow().behavior.clone();
|
||||
let sp = ctx.cfg.borrow().special_buttons.clone();
|
||||
|
||||
heading(&list, "Klick");
|
||||
let click_idx = match b.click {
|
||||
ClickAction::FocusOrMinimize => 0,
|
||||
ClickAction::Focus => 1,
|
||||
ClickAction::Cycle => 2,
|
||||
};
|
||||
row(&list, "Vänsterklick", &dropdown(ctx, &["Fokusera/minimera", "Bara fokusera", "Växla fönster"], click_idx, |c, i| {
|
||||
c.behavior.click = match i {
|
||||
1 => ClickAction::Focus,
|
||||
2 => ClickAction::Cycle,
|
||||
_ => ClickAction::FocusOrMinimize,
|
||||
};
|
||||
}));
|
||||
let mid_idx = match b.middle_click {
|
||||
MiddleClick::LaunchNew => 0,
|
||||
MiddleClick::Close => 1,
|
||||
MiddleClick::None => 2,
|
||||
};
|
||||
row(&list, "Mittklick", &dropdown(ctx, &["Starta ny instans", "Stäng", "Inget"], mid_idx, |c, i| {
|
||||
c.behavior.middle_click = match i {
|
||||
1 => MiddleClick::Close,
|
||||
2 => MiddleClick::None,
|
||||
_ => MiddleClick::LaunchNew,
|
||||
};
|
||||
}));
|
||||
let scroll_idx = match b.scroll {
|
||||
ScrollAction::CycleWindows => 0,
|
||||
ScrollAction::None => 1,
|
||||
};
|
||||
row(&list, "Skroll", &dropdown(ctx, &["Växla fönster", "Inget"], scroll_idx, |c, i| {
|
||||
c.behavior.scroll = if i == 1 { ScrollAction::None } else { ScrollAction::CycleWindows };
|
||||
}));
|
||||
|
||||
heading(&list, "Övrigt");
|
||||
row(&list, "Gruppera appar", &switch(ctx, b.group_apps, |c, v| c.behavior.group_apps = v));
|
||||
row(&list, "Tooltips", &switch(ctx, b.tooltips, |c, v| c.behavior.tooltips = v));
|
||||
row(&list, "Urgency-puls", &switch(ctx, b.urgent_pulse, |c, v| c.behavior.urgent_pulse = v));
|
||||
|
||||
heading(&list, "Specialknappar");
|
||||
row(&list, "Visa skrivbordet", &switch(ctx, sp.show_desktop, |c, v| c.special_buttons.show_desktop = v));
|
||||
row(&list, "Översikt (scale)", &switch(ctx, sp.overview, |c, v| c.special_buttons.overview = v));
|
||||
|
||||
heading(&list, "Högerklicksmenyn");
|
||||
let entries: [(&str, MenuItem); 5] = [
|
||||
("Minimera", MenuItem::Minimize),
|
||||
("Maximera", MenuItem::Maximize),
|
||||
("Flytta till skärm", MenuItem::MoveToOutput),
|
||||
("Pinna/släpp", MenuItem::Pin),
|
||||
("Stäng", MenuItem::Close),
|
||||
];
|
||||
for (label, mi) in entries {
|
||||
let present = ctx.cfg.borrow().context_menu.contains(&mi);
|
||||
let mi2 = mi.clone();
|
||||
row(&list, label, &switch(ctx, present, move |c, v| {
|
||||
if v {
|
||||
if !c.context_menu.contains(&mi2) {
|
||||
c.context_menu.push(mi2.clone());
|
||||
}
|
||||
} else {
|
||||
c.context_menu.retain(|m| *m != mi2);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
scroll
|
||||
}
|
||||
|
||||
fn build_pins_page(ctx: &Rc<Ctx>) -> gtk::ScrolledWindow {
|
||||
let (scroll, list) = page();
|
||||
|
||||
heading(&list, "Globala pins (.desktop-id, i ordning)");
|
||||
let pins_box = gtk::Box::new(gtk::Orientation::Vertical, 4);
|
||||
list.append(&pins_box);
|
||||
|
||||
fn rebuild_rows(ctx: &Rc<Ctx>, pins_box: >k::Box) {
|
||||
while let Some(c) = pins_box.first_child() {
|
||||
pins_box.remove(&c);
|
||||
}
|
||||
let pins = ctx.cfg.borrow().pins.clone();
|
||||
for (i, pin) in pins.iter().enumerate() {
|
||||
let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
||||
let name = crate::icons::lookup_pin(pin)
|
||||
.map(|m| m.display_name)
|
||||
.unwrap_or_else(|| "⚠ hittas inte".into());
|
||||
let lbl = gtk::Label::new(Some(&format!("{pin} ({name})")));
|
||||
lbl.set_halign(gtk::Align::Start);
|
||||
lbl.set_hexpand(true);
|
||||
hbox.append(&lbl);
|
||||
|
||||
let up = gtk::Button::from_icon_name("go-up-symbolic");
|
||||
up.add_css_class("flat");
|
||||
let remove = gtk::Button::from_icon_name("edit-delete-symbolic");
|
||||
remove.add_css_class("flat");
|
||||
hbox.append(&up);
|
||||
hbox.append(&remove);
|
||||
pins_box.append(&hbox);
|
||||
|
||||
let ctx2 = ctx.clone();
|
||||
let pb2 = pins_box.clone();
|
||||
up.connect_clicked(move |_| {
|
||||
if i > 0 {
|
||||
commit(&ctx2, |c| c.pins.swap(i, i - 1));
|
||||
rebuild_rows(&ctx2, &pb2);
|
||||
}
|
||||
});
|
||||
let ctx3 = ctx.clone();
|
||||
let pb3 = pins_box.clone();
|
||||
remove.connect_clicked(move |_| {
|
||||
commit(&ctx3, |c| {
|
||||
c.pins.remove(i);
|
||||
});
|
||||
rebuild_rows(&ctx3, &pb3);
|
||||
});
|
||||
}
|
||||
}
|
||||
rebuild_rows(ctx, &pins_box);
|
||||
|
||||
let add_box = gtk::Box::new(gtk::Orientation::Horizontal, 6);
|
||||
let entry = gtk::Entry::new();
|
||||
entry.set_placeholder_text(Some("t.ex. org.kde.konsole"));
|
||||
entry.set_hexpand(true);
|
||||
let add = gtk::Button::with_label("Lägg till");
|
||||
add_box.append(&entry);
|
||||
add_box.append(&add);
|
||||
list.append(&add_box);
|
||||
{
|
||||
let ctx = ctx.clone();
|
||||
let pins_box = pins_box.clone();
|
||||
add.connect_clicked(move |_| {
|
||||
let id = entry.text().trim().to_string();
|
||||
if id.is_empty() {
|
||||
return;
|
||||
}
|
||||
if crate::icons::lookup_pin(&id).is_none() {
|
||||
entry.add_css_class("error");
|
||||
return;
|
||||
}
|
||||
entry.remove_css_class("error");
|
||||
entry.set_text("");
|
||||
let id2 = id.clone();
|
||||
commit(&ctx, move |c| {
|
||||
if !c.pins.contains(&id2) {
|
||||
c.pins.push(id2.clone());
|
||||
}
|
||||
});
|
||||
rebuild_rows(&ctx, &pins_box);
|
||||
});
|
||||
}
|
||||
|
||||
let note = gtk::Label::new(Some("Tips: hitta id med ls /usr/share/applications | grep -i <app>\nPer-skärm-pins redigeras i config.json under \"outputs\"."));
|
||||
note.add_css_class("dim-label");
|
||||
note.set_halign(gtk::Align::Start);
|
||||
note.set_margin_top(12);
|
||||
list.append(¬e);
|
||||
|
||||
scroll
|
||||
}
|
||||
|
||||
fn build_hotspots_page(ctx: &Rc<Ctx>) -> gtk::ScrolledWindow {
|
||||
let (scroll, list) = page();
|
||||
let h = ctx.cfg.borrow().hotspots.clone();
|
||||
|
||||
heading(&list, "Hot corners");
|
||||
row(&list, "Uppe vänster", &hotspot_entry(ctx, &h.corners.top_left, |c, v| c.hotspots.corners.top_left = v));
|
||||
row(&list, "Uppe höger", &hotspot_entry(ctx, &h.corners.top_right, |c, v| c.hotspots.corners.top_right = v));
|
||||
row(&list, "Nere vänster", &hotspot_entry(ctx, &h.corners.bottom_left, |c, v| c.hotspots.corners.bottom_left = v));
|
||||
row(&list, "Nere höger", &hotspot_entry(ctx, &h.corners.bottom_right, |c, v| c.hotspots.corners.bottom_right = v));
|
||||
|
||||
heading(&list, "Hot edges");
|
||||
row(&list, "Uppe", &hotspot_entry(ctx, &h.edges.top, |c, v| c.hotspots.edges.top = v));
|
||||
row(&list, "Nere", &hotspot_entry(ctx, &h.edges.bottom, |c, v| c.hotspots.edges.bottom = v));
|
||||
row(&list, "Vänster", &hotspot_entry(ctx, &h.edges.left, |c, v| c.hotspots.edges.left = v));
|
||||
row(&list, "Höger", &hotspot_entry(ctx, &h.edges.right, |c, v| c.hotspots.edges.right = v));
|
||||
|
||||
row(&list, "Dwell-tid (ms)", &spin(ctx, h.dwell_ms as f64, (0.0, 2000.0), 50.0, |c, v| c.hotspots.dwell_ms = v as u32));
|
||||
|
||||
let note = gtk::Label::new(Some("Format: exec:<kommando> eller ipc:<metod> (t.ex. ipc:scale/toggle).\nTomt fält = av. IPC kräver Wayfires ipc-plugin."));
|
||||
note.add_css_class("dim-label");
|
||||
note.set_halign(gtk::Align::Start);
|
||||
note.set_margin_top(12);
|
||||
list.append(¬e);
|
||||
|
||||
scroll
|
||||
}
|
||||
|
||||
/// Öppna settings-fönstret (återanvänder ett redan öppet).
|
||||
pub fn open(ctx: &Rc<Ctx>) {
|
||||
let existing = OPEN_WINDOW.with(|w| w.borrow().as_ref().and_then(|w| w.upgrade()));
|
||||
if let Some(win) = existing {
|
||||
win.present();
|
||||
return;
|
||||
}
|
||||
|
||||
let win = gtk::Window::new();
|
||||
win.set_title(Some("waydock — inställningar"));
|
||||
win.set_default_size(520, 620);
|
||||
|
||||
let notebook = gtk::Notebook::new();
|
||||
notebook.append_page(&build_appearance_page(ctx), Some(>k::Label::new(Some("Utseende"))));
|
||||
notebook.append_page(&build_layout_page(ctx), Some(>k::Label::new(Some("Layout"))));
|
||||
notebook.append_page(&build_behavior_page(ctx), Some(>k::Label::new(Some("Beteende"))));
|
||||
notebook.append_page(&build_pins_page(ctx), Some(>k::Label::new(Some("Pins"))));
|
||||
notebook.append_page(&build_hotspots_page(ctx), Some(>k::Label::new(Some("Hotspots"))));
|
||||
win.set_child(Some(¬ebook));
|
||||
|
||||
OPEN_WINDOW.with(|w| *w.borrow_mut() = Some(win.downgrade()));
|
||||
win.present();
|
||||
}
|
||||
|
||||
// hindra att Cell-importen städas bort av misstag när sidor ändras
|
||||
#[allow(unused)]
|
||||
fn _keep(_: Cell<u8>) {}
|
||||
Reference in New Issue
Block a user