diff --git a/assets/waydock.desktop b/assets/waydock.desktop new file mode 100644 index 0000000..1ce67bf --- /dev/null +++ b/assets/waydock.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Name=waydock +Comment=Dock för Wayfire — transparent, JSON-konfigurerad, per skärm +Exec=waydock +Icon=waydock +Terminal=false +Categories=Utility; +Keywords=dock;panel;wayfire;taskbar; +X-GNOME-Autostart-enabled=true diff --git a/assets/waydock.svg b/assets/waydock.svg new file mode 100644 index 0000000..380c8f5 --- /dev/null +++ b/assets/waydock.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/usage.md b/doc/usage.md index 26d88ae..427069d 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -23,7 +23,12 @@ Alla ändringar i filen slår igenom **live** — ingen omstart behövs. - **Högerklick på tom yta**: Inställningar…, Ladda om config, Avsluta - **Visa skrivbord-knappen** minimerar allt; klick igen återställer - **Översikt-knappen** togglar Wayfires scale-vy (kräver ipc-pluginen) -- Grupp-ikoner får en siffra och accentprickar; hovra för fönsterlistan +- Grupp-ikoner får en siffra och accentprickar. Fönsterlistan öppnas + vid klick eller hover (`behavior.group_popup`); hovra en rad i listan + för peek (bara det fönstret syns, andra tonas ner — kräver Wayfires + alpha-plugin) eller fokus, enligt `behavior.list_hover` +- Marginalillustrationen i Inställningar → Layout visar vad A/B/C-måtten + styr för aktuell position ## Settings-GUI @@ -35,8 +40,11 @@ JSON-filen och appliceras live. GUI:t och handredigering kan blandas fritt. ```jsonc { "position": "bottom", // bottom | top | left | right - "centered": true, // ö i mitten i stället för hela kanten - "margin": 8, // px från skärmkant (0 = kant i kant) + "alignment": "center", // center | start | end — var längs kanten ön ligger + "expand": false, // true = täck hela kanten (panelen fyller bredden/höjden) + "margin": 8, // A: px från kanten baren ligger mot + "margin_start": 0, // B: marginal i början av kanten (vänster/topp) + "margin_end": 0, // C: marginal i slutet av kanten (höger/botten) "layer": "top", // top | overlay | bottom (always-on-top = overlay) "avoid_windows": false, // true = reservera plats (exclusive zone) @@ -61,7 +69,10 @@ JSON-filen och appliceras live. GUI:t och handredigering kan blandas fritt. "scroll": "cycle_windows", // cycle_windows | none "group_apps": true, "tooltips": true, - "urgent_pulse": true + "urgent_pulse": true, + "group_popup": "click", // click | hover — hur fönsterlistan öppnas + "list_hover": "peek" // peek (visa bara det fönstret, tona ner andra) + // | focus (fokusera vid hover) | none }, "context_menu": ["minimize", "maximize", "move_to_output", diff --git a/install.sh b/install.sh index 0056674..8dbd2df 100644 --- a/install.sh +++ b/install.sh @@ -17,6 +17,13 @@ curl -fSL -o "$DEST/waydock" \ "$BASE/releases/download/latest/waydock-latest-linux-x64" chmod +x "$DEST/waydock" +# ikon + .desktop-fil +mkdir -p "$HOME/.local/share/icons/hicolor/scalable/apps" "$HOME/.local/share/applications" +curl -fsSL -o "$HOME/.local/share/icons/hicolor/scalable/apps/waydock.svg" \ + "$BASE/raw/branch/master/assets/waydock.svg" || true +curl -fsSL -o "$HOME/.local/share/applications/waydock.desktop" \ + "$BASE/raw/branch/master/assets/waydock.desktop" || true + echo "Installerad: $DEST/waydock" case ":$PATH:" in *":$DEST:"*) ;; diff --git a/src/config.rs b/src/config.rs index 90474ea..0797d81 100644 --- a/src/config.rs +++ b/src/config.rs @@ -28,6 +28,34 @@ pub enum Layer { Bottom, } +/// Var längs kanten ön ligger (start = vänster/topp, end = höger/botten). +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum Alignment { + Center, + Start, + End, +} + +/// Hur fönsterlistan för en grupperad app öppnas. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum GroupPopup { + Click, + Hover, +} + +/// Vad som händer när man hovrar en rad i fönsterlistan. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum ListHover { + /// tona ner alla andra fönster så bara det hovrade syns tydligt + Peek, + /// fokusera/lyft fönstret direkt vid hover + Focus, + None, +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "snake_case")] pub enum ClickAction { @@ -151,6 +179,8 @@ pub struct Behavior { pub group_apps: bool, pub tooltips: bool, pub urgent_pulse: bool, + pub group_popup: GroupPopup, + pub list_hover: ListHover, } impl Default for Behavior { fn default() -> Self { @@ -161,6 +191,8 @@ impl Default for Behavior { group_apps: true, tooltips: true, urgent_pulse: true, + group_popup: GroupPopup::Click, + list_hover: ListHover::Peek, } } } @@ -268,8 +300,15 @@ impl Default for LogConfig { #[serde(default)] pub struct Config { pub position: Position, - pub centered: bool, + pub alignment: Alignment, + /// false = ö som är så stor som innehållet; true = täck hela kanten + pub expand: bool, + /// avstånd från kanten baren ligger mot (px) pub margin: i32, + /// marginal i början av kanten: vänster (topp/botten) resp. topp (vänster/höger) + pub margin_start: i32, + /// marginal i slutet av kanten: höger resp. botten + pub margin_end: i32, pub layer: Layer, pub avoid_windows: bool, pub appearance: Appearance, @@ -287,8 +326,11 @@ impl Default for Config { fn default() -> Self { Self { position: Position::Bottom, - centered: true, + alignment: Alignment::Center, + expand: false, margin: 8, + margin_start: 0, + margin_end: 0, layer: Layer::Top, avoid_windows: false, appearance: Appearance::default(), diff --git a/src/dock.rs b/src/dock.rs index e8aa25d..d5d218a 100644 --- a/src/dock.rs +++ b/src/dock.rs @@ -16,8 +16,8 @@ use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell}; use log::{debug, info}; use crate::config::{ - AutohideMode, ClickAction, Layer as CfgLayer, MiddleClick, Position, ScrollAction, - SharedConfig, + Alignment, AutohideMode, ClickAction, GroupPopup, Layer as CfgLayer, MiddleClick, + Position, ScrollAction, SharedConfig, }; use crate::icons::{self, AppMeta}; use crate::menu; @@ -45,7 +45,7 @@ pub struct Ctx { pub struct Dock { pub window: gtk::ApplicationWindow, - pub panel: gtk::Box, + pub items_box: gtk::Box, pub revealer: gtk::Revealer, pub connector: String, /// pekaren är över dockan just nu @@ -103,12 +103,28 @@ fn configure_layer(win: >k::ApplicationWindow, ctx: &Ctx, monitor: &gdk::Monit win.set_keyboard_mode(KeyboardMode::None); let edge = main_edge(&cfg.position); - let (side_a, side_b) = side_edges(&cfg.position); + let (side_start, side_end) = side_edges(&cfg.position); win.set_anchor(edge, true); win.set_margin(edge, cfg.margin); - if !cfg.centered { - win.set_anchor(side_a, true); - win.set_anchor(side_b, true); + if cfg.expand { + // täck hela kanten; start/slut-marginalerna drar in baren + win.set_anchor(side_start, true); + win.set_anchor(side_end, true); + win.set_margin(side_start, cfg.margin_start); + win.set_margin(side_end, cfg.margin_end); + } else { + // ö: förankra bara i vald ände (ingen sidoförankring = centrerad) + match cfg.alignment { + Alignment::Center => {} + Alignment::Start => { + win.set_anchor(side_start, true); + win.set_margin(side_start, cfg.margin_start); + } + Alignment::End => { + win.set_anchor(side_end, true); + win.set_margin(side_end, cfg.margin_end); + } + } } if cfg.avoid_windows && !cfg.autohide.enabled { win.auto_exclusive_zone_enable(); @@ -158,8 +174,30 @@ fn build_dock(ctx: &Rc, monitor: &gdk::Monitor, connector: String) -> Rc gtk::Align::Center, + Alignment::Start => gtk::Align::Start, + Alignment::End => gtk::Align::End, + }; + let items_box = gtk::Box::new(orient, 0); + if cfg.expand { + // panelbakgrunden täcker kanten; ikonerna läggs enligt alignment + panel.set_halign(gtk::Align::Fill); + panel.set_valign(gtk::Align::Fill); + if orient == gtk::Orientation::Horizontal { + panel.set_hexpand(true); + items_box.set_hexpand(true); + items_box.set_halign(items_align); + } else { + panel.set_vexpand(true); + items_box.set_vexpand(true); + items_box.set_valign(items_align); + } + } else { + panel.set_halign(gtk::Align::Center); + panel.set_valign(gtk::Align::Center); + } + panel.append(&items_box); // yttre marginal = plats för skuggan att rita let outer = gtk::Box::new(orient, 0); @@ -198,7 +236,7 @@ fn build_dock(ctx: &Rc, monitor: &gdk::Monitor, connector: String) -> Rc) { .unwrap_or_default(); for dock in ctx.docks.borrow().iter() { - while let Some(child) = dock.panel.first_child() { - dock.panel.remove(&child); + while let Some(child) = dock.items_box.first_child() { + dock.items_box.remove(&child); } add_special_buttons(ctx, dock); for item in build_items(ctx, &dock.connector, &snapshot) { let widget = build_item_widget(ctx, &dock.connector, Rc::new(item)); - dock.panel.append(&widget); + dock.items_box.append(&widget); } } } @@ -509,7 +547,7 @@ fn add_special_buttons(ctx: &Rc, dock: &Rc) { if specials.overview && wayfire_ipc::available() { let btn = special_button("view-grid-symbolic", "Översikt (scale)"); btn.connect_clicked(|_| wayfire_ipc::toggle_scale()); - dock.panel.append(&btn); + dock.items_box.append(&btn); } if specials.show_desktop { let btn = special_button("user-desktop-symbolic", "Visa skrivbordet"); @@ -519,7 +557,7 @@ fn add_special_buttons(ctx: &Rc, dock: &Rc) { service.toggle_show_desktop(); } }); - dock.panel.append(&btn); + dock.items_box.append(&btn); } } @@ -610,6 +648,33 @@ fn build_item_widget(ctx: &Rc, connector: &str, item: Rc) -> gtk::But } btn.add_controller(click); + // hover-popup för grupperade appar (om valt i configen) + if item.windows.len() > 1 + && ctx.cfg.borrow().behavior.group_popup == GroupPopup::Hover + { + let hover_ctl = gtk::EventControllerMotion::new(); + let ctx2 = ctx.clone(); + let item2 = item.clone(); + let btn2 = btn.clone(); + let conn2 = connector.to_string(); + hover_ctl.connect_enter(move |ctl, _, _| { + let ctx3 = ctx2.clone(); + let item3 = item2.clone(); + let btn3 = btn2.clone(); + let conn3 = conn2.clone(); + let ctl2 = ctl.clone(); + glib::timeout_add_local_once(Duration::from_millis(350), move || { + if ctl2.contains_pointer() { + let live = live_item(&ctx3, &conn3, &item3); + if live.windows.len() > 1 { + menu::popup_group_list(&ctx3, &live, &btn3); + } + } + }); + }); + btn.add_controller(hover_ctl); + } + // skroll: växla mellan gruppens fönster let scroll = gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::VERTICAL); { @@ -649,8 +714,11 @@ fn primary_action(ctx: &Rc, item: &Rc, anchor: >k::Button) { } } _ => { - let click = ctx.cfg.borrow().behavior.click.clone(); - if click == ClickAction::Cycle { + let (click, popup) = { + let b = &ctx.cfg.borrow().behavior; + (b.click.clone(), b.group_popup.clone()) + }; + if click == ClickAction::Cycle || popup == GroupPopup::Hover { cycle_windows(ctx, item, true); } else { menu::popup_group_list(ctx, item, anchor); diff --git a/src/main.rs b/src/main.rs index 453a585..c9421d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -102,10 +102,13 @@ fn init_logging(cli: &Cli, cfg: &config::Config) { /// Delar av configen som kräver att fönstren byggs om (inte bara CSS). fn layout_signature(cfg: &config::Config) -> String { format!( - "{:?}|{}|{}|{:?}|{}|{}|{}|{:?}", + "{:?}|{:?}|{}|{}|{}|{}|{:?}|{}|{}|{}|{:?}", cfg.position, - cfg.centered, + cfg.alignment, + cfg.expand, cfg.margin, + cfg.margin_start, + cfg.margin_end, cfg.layer, cfg.avoid_windows, cfg.appearance.shadow.enabled, diff --git a/src/menu.rs b/src/menu.rs index 3ff8a93..bcbaf87 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -11,7 +11,7 @@ use gtk4::glib; use gtk4::prelude::*; use log::warn; -use crate::config::{self, MenuItem}; +use crate::config::{self, GroupPopup, ListHover, MenuItem}; use crate::dock::{Ctx, Item}; use crate::icons; use crate::settings; @@ -232,12 +232,56 @@ pub fn popup_item_menu(ctx: &Rc, item: &Rc, anchor: >k::Button) { popover.popup(); } -/// Vänsterklick på grupp med flera fönster: lista fönstren. +/// Tona ner alla andra fönster än det hovrade (peek). Returnerar id:n +/// som dimmats så de kan återställas. +fn peek_apply(hovered: &crate::toplevel::ToplevelInfo) -> Vec { + let Some(views) = wayfire_ipc::list_views() else { + return Vec::new(); + }; + let hovered_id = views + .iter() + .find(|v| { + v["app-id"].as_str() == Some(hovered.app_id.as_str()) + && v["title"].as_str() == Some(hovered.title.as_str()) + }) + .and_then(|v| v["id"].as_i64()); + let mut dimmed = Vec::new(); + for v in &views { + if v["role"].as_str() != Some("toplevel") + || !v["mapped"].as_bool().unwrap_or(false) + || v["minimized"].as_bool().unwrap_or(false) + { + continue; + } + let Some(id) = v["id"].as_i64() else { continue }; + if Some(id) != hovered_id { + wayfire_ipc::set_view_alpha(id, 0.15); + dimmed.push(id); + } + } + dimmed +} + +fn peek_restore(dimmed: &[i64]) { + for id in dimmed { + wayfire_ipc::set_view_alpha(*id, 1.0); + } +} + +/// Vänsterklick (eller hover) på grupp med flera fönster: lista fönstren. pub fn popup_group_list(ctx: &Rc, item: &Rc, anchor: >k::Button) { let popover = gtk::Popover::new(); popover.add_css_class("waydock-menu"); let list = gtk::Box::new(gtk::Orientation::Vertical, 2); + let (list_hover, hover_mode) = { + let b = &ctx.cfg.borrow().behavior; + (b.list_hover.clone(), b.group_popup == GroupPopup::Hover) + }; + let peek_possible = list_hover == ListHover::Peek && wayfire_ipc::available(); + // fönster som tonats ner av peek — återställs vid radbyte/stängning + let dimmed: Rc>> = Rc::new(std::cell::RefCell::new(Vec::new())); + for w in &item.windows { let row = gtk::Button::new(); row.add_css_class("flat"); @@ -250,16 +294,76 @@ pub fn popup_group_list(ctx: &Rc, item: &Rc, anchor: >k::Button) { let ctx2 = ctx.clone(); let w2 = w.clone(); let pop2 = popover.clone(); + let dimmed2 = dimmed.clone(); row.connect_clicked(move |_| { + peek_restore(&dimmed2.borrow()); + dimmed2.borrow_mut().clear(); if let Some(s) = ctx2.service.borrow().as_ref() { s.activate(&w2); } pop2.popdown(); }); + + // hover-effekt per rad: peek eller fokus + match list_hover { + ListHover::Peek if peek_possible => { + let mc = gtk::EventControllerMotion::new(); + let w3 = w.clone(); + let dimmed3 = dimmed.clone(); + mc.connect_enter(move |_, _, _| { + peek_restore(&dimmed3.borrow()); + *dimmed3.borrow_mut() = peek_apply(&w3); + }); + let dimmed4 = dimmed.clone(); + mc.connect_leave(move |_| { + peek_restore(&dimmed4.borrow()); + dimmed4.borrow_mut().clear(); + }); + row.add_controller(mc); + } + ListHover::Focus => { + let mc = gtk::EventControllerMotion::new(); + let ctx3 = ctx.clone(); + let w3 = w.clone(); + mc.connect_enter(move |_, _, _| { + if let Some(s) = ctx3.service.borrow().as_ref() { + s.activate(&w3); + } + }); + row.add_controller(mc); + } + _ => {} + } list.append(&row); } popover.set_child(Some(&list)); popover.set_parent(anchor); + + // säkerhetsnät: återställ peek när popovern stängs + { + let dimmed5 = dimmed.clone(); + popover.connect_closed(move |_| { + peek_restore(&dimmed5.borrow()); + dimmed5.borrow_mut().clear(); + }); + } + + // i hover-läge: stäng listan när pekaren lämnar den + if hover_mode { + let mc = gtk::EventControllerMotion::new(); + let pop3 = popover.clone(); + mc.connect_leave(move |ctl| { + let pop4 = pop3.clone(); + let ctl2 = ctl.clone(); + glib::timeout_add_local_once(std::time::Duration::from_millis(400), move || { + if !ctl2.contains_pointer() { + pop4.popdown(); + } + }); + }); + popover.add_controller(mc); + } + autodispose(&popover); popover.popup(); } diff --git a/src/settings.rs b/src/settings.rs index 50aae62..dacb35b 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -14,14 +14,16 @@ use gtk4::prelude::*; use log::warn; use crate::config::{ - self, AutohideMode, ClickAction, HotspotAction, IndicatorStyle, Layer, MenuItem, - MiddleClick, Position, ScrollAction, + self, Alignment, AutohideMode, ClickAction, GroupPopup, HotspotAction, IndicatorStyle, + Layer, ListHover, MenuItem, MiddleClick, Position, ScrollAction, }; use crate::dock::Ctx; thread_local! { static OPEN_WINDOW: std::cell::RefCell>> = const { std::cell::RefCell::new(None) }; + static ILLUSTRATION: std::cell::RefCell>> = + const { std::cell::RefCell::new(None) }; } /// Uppdatera config + applicera + spara (debouncat 300 ms). @@ -30,6 +32,11 @@ fn commit(ctx: &Rc, mutate: impl FnOnce(&mut config::Config)) { if let Some(cb) = ctx.on_config_changed.borrow().as_ref() { cb(); } + ILLUSTRATION.with(|i| { + if let Some(da) = i.borrow().as_ref().and_then(|w| w.upgrade()) { + da.queue_draw(); + } + }); let generation = ctx.save_generation.get() + 1; ctx.save_generation.set(generation); let ctx = ctx.clone(); @@ -253,6 +260,125 @@ fn build_appearance_page(ctx: &Rc) -> gtk::ScrolledWindow { scroll } +fn margin_illustration(ctx: &Rc) -> gtk::DrawingArea { + let da = gtk::DrawingArea::new(); + da.set_content_width(280); + da.set_content_height(190); + da.set_halign(gtk::Align::Center); + let ctx2 = ctx.clone(); + da.set_draw_func(move |_, cr, w, h| { + let cfg = ctx2.cfg.borrow(); + let (w, h) = (w as f64, h as f64); + + // "skärmen" + let (sx, sy, sw, sh) = (30.0, 22.0, w - 60.0, h - 60.0); + cr.set_source_rgba(0.16, 0.16, 0.18, 1.0); + cr.rectangle(sx, sy, sw, sh); + let _ = cr.fill(); + cr.set_source_rgba(0.5, 0.5, 0.55, 1.0); + cr.set_line_width(1.5); + cr.rectangle(sx, sy, sw, sh); + let _ = cr.stroke(); + + // förhållandena skalade: marginalerna visas symboliskt (max ~18 px) + let clamp = |v: i32| 6.0 + (v.max(0) as f64).min(60.0) * 0.2; + let a = clamp(cfg.margin); + let b = clamp(cfg.margin_start); + let c = clamp(cfg.margin_end); + let thick = 12.0; + let horizontal = matches!(cfg.position, Position::Top | Position::Bottom); + + // barens längd/placering längs kanten + let full = if horizontal { sw } else { sh }; + let len = if cfg.expand { + full - b - c + } else { + (full * 0.45).max(40.0) + }; + let along = if cfg.expand { + b + } else { + match cfg.alignment { + Alignment::Start => b, + Alignment::End => full - len - c, + Alignment::Center => (full - len) / 2.0, + } + }; + + // accentfärgad bar + let acc = &cfg.appearance.accent; + let hex = acc.trim_start_matches('#'); + let (r, g, bl) = if hex.len() == 6 { + ( + u8::from_str_radix(&hex[0..2], 16).unwrap_or(200) as f64 / 255.0, + u8::from_str_radix(&hex[2..4], 16).unwrap_or(85) as f64 / 255.0, + u8::from_str_radix(&hex[4..6], 16).unwrap_or(95) as f64 / 255.0, + ) + } else { + (0.79, 0.33, 0.36) + }; + cr.set_source_rgba(r, g, bl, 0.95); + let (bx, by, bw, bh) = match cfg.position { + Position::Bottom => (sx + along, sy + sh - a - thick, len, thick), + Position::Top => (sx + along, sy + a, len, thick), + Position::Left => (sx + a, sy + along, thick, len), + Position::Right => (sx + sw - a - thick, sy + along, thick, len), + }; + cr.rectangle(bx, by, bw, bh); + let _ = cr.fill(); + + // måttpilar med etiketter + cr.set_source_rgba(0.85, 0.85, 0.88, 1.0); + cr.set_line_width(1.0); + cr.set_font_size(11.0); + let label = |x: f64, y: f64, text: &str| { + cr.move_to(x, y); + let _ = cr.show_text(text); + }; + let line = |x1: f64, y1: f64, x2: f64, y2: f64| { + cr.move_to(x1, y1); + cr.line_to(x2, y2); + let _ = cr.stroke(); + }; + match cfg.position { + Position::Bottom => { + line(bx + bw / 2.0, by + thick, bx + bw / 2.0, sy + sh); + label(bx + bw / 2.0 + 3.0, sy + sh - 2.0, "A"); + line(sx, by + thick / 2.0, bx, by + thick / 2.0); + label(sx + (bx - sx) / 2.0 - 3.0, by - 3.0, "B"); + line(bx + bw, by + thick / 2.0, sx + sw, by + thick / 2.0); + label(bx + bw + (sx + sw - bx - bw) / 2.0 - 3.0, by - 3.0, "C"); + } + Position::Top => { + line(bx + bw / 2.0, sy, bx + bw / 2.0, by); + label(bx + bw / 2.0 + 3.0, sy + 12.0, "A"); + line(sx, by + thick / 2.0, bx, by + thick / 2.0); + label(sx + (bx - sx) / 2.0 - 3.0, by + thick + 12.0, "B"); + line(bx + bw, by + thick / 2.0, sx + sw, by + thick / 2.0); + label(bx + bw + (sx + sw - bx - bw) / 2.0 - 3.0, by + thick + 12.0, "C"); + } + Position::Left => { + line(sx, by + bh / 2.0, bx, by + bh / 2.0); + label(sx + 2.0, by + bh / 2.0 - 4.0, "A"); + line(bx + thick / 2.0, sy, bx + thick / 2.0, by); + label(bx + thick + 3.0, sy + (by - sy) / 2.0, "B"); + line(bx + thick / 2.0, by + bh, bx + thick / 2.0, sy + sh); + label(bx + thick + 3.0, by + bh + (sy + sh - by - bh) / 2.0, "C"); + } + Position::Right => { + line(bx + thick, by + bh / 2.0, sx + sw, by + bh / 2.0); + label(sx + sw - 12.0, by + bh / 2.0 - 4.0, "A"); + line(bx + thick / 2.0, sy, bx + thick / 2.0, by); + label(bx - 12.0, sy + (by - sy) / 2.0, "B"); + line(bx + thick / 2.0, by + bh, bx + thick / 2.0, sy + sh); + label(bx - 12.0, by + bh + (sy + sh - by - bh) / 2.0, "C"); + } + } + }); + ILLUSTRATION.with(|i| *i.borrow_mut() = Some(da.downgrade())); + da +} + fn build_layout_page(ctx: &Rc) -> gtk::ScrolledWindow { let (scroll, list) = page(); let cfg = ctx.cfg.borrow().clone(); @@ -272,8 +398,19 @@ fn build_layout_page(ctx: &Rc) -> gtk::ScrolledWindow { _ => 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 align_idx = match cfg.alignment { + Alignment::Center => 0, + Alignment::Start => 1, + Alignment::End => 2, + }; + row(&list, "Justering längs kanten", &dropdown(ctx, &["Centrerad", "Start (vänster/topp)", "Slut (höger/botten)"], align_idx, |c, i| { + c.alignment = match i { + 1 => Alignment::Start, + 2 => Alignment::End, + _ => Alignment::Center, + }; + })); + row(&list, "Täck hela kanten", &switch(ctx, cfg.expand, |c, v| c.expand = v)); let layer_idx = match cfg.layer { Layer::Top => 0, Layer::Overlay => 1, @@ -288,6 +425,18 @@ fn build_layout_page(ctx: &Rc) -> gtk::ScrolledWindow { })); row(&list, "Reservera plats (undvik fönster)", &switch(ctx, cfg.avoid_windows, |c, v| c.avoid_windows = v)); + heading(&list, "Marginaler"); + list.append(&margin_illustration(ctx)); + row(&list, "A — Avstånd från kanten", &spin(ctx, cfg.margin as f64, (0.0, 128.0), 1.0, |c, v| c.margin = v as i32)); + row(&list, "B — Marginal start", &spin(ctx, cfg.margin_start as f64, (0.0, 512.0), 1.0, |c, v| c.margin_start = v as i32)); + row(&list, "C — Marginal slut", &spin(ctx, cfg.margin_end as f64, (0.0, 512.0), 1.0, |c, v| c.margin_end = v as i32)); + let note = gtk::Label::new(Some( + "A = avstånd från kanten baren ligger mot.\nB/C = marginal i början/slutet av kanten — indrag när baren\ntäcker kanten, annars avstånd från vald sida (vid Start/Slut).", + )); + note.add_css_class("dim-label"); + note.set_halign(gtk::Align::Start); + list.append(¬e); + heading(&list, "Autohide"); row(&list, "Aktiverad", &switch(ctx, cfg.autohide.enabled, |c, v| c.autohide.enabled = v)); let mode_idx = match cfg.autohide.mode { @@ -348,8 +497,27 @@ fn build_behavior_page(ctx: &Rc) -> gtk::ScrolledWindow { c.behavior.scroll = if i == 1 { ScrollAction::None } else { ScrollAction::CycleWindows }; })); - heading(&list, "Övrigt"); + heading(&list, "Gruppering & popup"); row(&list, "Gruppera appar", &switch(ctx, b.group_apps, |c, v| c.behavior.group_apps = v)); + let popup_idx = match b.group_popup { + GroupPopup::Click => 0, + GroupPopup::Hover => 1, + }; + row(&list, "Fönsterlistan öppnas", &dropdown(ctx, &["Vid klick", "Vid hover"], popup_idx, |c, i| { + c.behavior.group_popup = if i == 1 { GroupPopup::Hover } else { GroupPopup::Click }; + })); + let lh_idx = match b.list_hover { + ListHover::Peek => 0, + ListHover::Focus => 1, + ListHover::None => 2, + }; + row(&list, "Hover i fönsterlistan", &dropdown(ctx, &["Peek — visa bara det fönstret", "Fokusera fönstret", "Ingen effekt"], lh_idx, |c, i| { + c.behavior.list_hover = match i { + 1 => ListHover::Focus, + 2 => ListHover::None, + _ => ListHover::Peek, + }; + })); 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)); @@ -510,6 +678,7 @@ pub fn open(ctx: &Rc) { let win = gtk::Window::new(); win.set_title(Some("waydock — inställningar")); + win.set_icon_name(Some("waydock")); win.set_default_size(520, 620); let notebook = gtk::Notebook::new(); diff --git a/src/wayfire_ipc.rs b/src/wayfire_ipc.rs index 3781ffc..64351d4 100644 --- a/src/wayfire_ipc.rs +++ b/src/wayfire_ipc.rs @@ -178,6 +178,14 @@ pub fn subscribe(on_event: std::rc::Rc) -> Result<(), String> { Ok(()) } +/// Sätt en vys genomskinlighet (kräver alpha-pluginen). +pub fn set_view_alpha(view_id: i64, alpha: f64) { + call_logged( + "wf/alpha/set-view-alpha", + json!({ "view-id": view_id, "alpha": alpha }), + ); +} + /// Flytta en vy till en annan output (namn t.ex. "DP-1"). pub fn move_view_to_output(view_id: i64, output_name: &str) { let Some(outputs) = list_outputs() else {