Justering/expand/marginaler, hover-popup med peek, appikon
All checks were successful
check / check (push) Successful in 1m37s
All checks were successful
check / check (push) Successful in 1m37s
- Ny layoutmodell: alignment (center/start/end längs kanten), expand (ö eller täck hela kanten) och tre marginaler — A från kanten, B/C i början/slutet av kanten. Funkar för alla fyra positionerna. - Settings → Layout har en ritad illustration (cairo) som visar vad A/B/C betyder för vald position och uppdateras live. - Fönsterlistan för grupper kan öppnas vid hover (group_popup) och stängs när pekaren lämnar den; hover på en rad kan peeka (alla andra fönster tonas ner via Wayfires alpha-plugin) eller fokusera fönstret (list_hover: peek/focus/none). - Appikon (assets/waydock.svg) + .desktop-fil; installeras av install.sh och används av settings-fönstret. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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(),
|
||||
|
||||
102
src/dock.rs
102
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<Ctx>, monitor: &gdk::Monitor, connector: String) -> Rc<Do
|
||||
let orient = orientation(&cfg.position);
|
||||
let panel = gtk::Box::new(orient, 0);
|
||||
panel.add_css_class("dock-panel");
|
||||
panel.set_halign(gtk::Align::Center);
|
||||
panel.set_valign(gtk::Align::Center);
|
||||
let items_align = match cfg.alignment {
|
||||
Alignment::Center => 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<Ctx>, monitor: &gdk::Monitor, connector: String) -> Rc<Do
|
||||
|
||||
let dock = Rc::new(Dock {
|
||||
window: win.clone(),
|
||||
panel: panel.clone(),
|
||||
items_box: items_box.clone(),
|
||||
revealer,
|
||||
connector,
|
||||
hover: Cell::new(false),
|
||||
@@ -481,13 +519,13 @@ pub fn refresh_items(ctx: &Rc<Ctx>) {
|
||||
.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<Ctx>, dock: &Rc<Dock>) {
|
||||
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<Ctx>, dock: &Rc<Dock>) {
|
||||
service.toggle_show_desktop();
|
||||
}
|
||||
});
|
||||
dock.panel.append(&btn);
|
||||
dock.items_box.append(&btn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,6 +648,33 @@ fn build_item_widget(ctx: &Rc<Ctx>, connector: &str, item: Rc<Item>) -> 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<Ctx>, item: &Rc<Item>, 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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
108
src/menu.rs
108
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<Ctx>, item: &Rc<Item>, 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<i64> {
|
||||
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<Ctx>, item: &Rc<Item>, 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<std::cell::RefCell<Vec<i64>>> = 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<Ctx>, item: &Rc<Item>, 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();
|
||||
}
|
||||
|
||||
179
src/settings.rs
179
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<Option<glib::WeakRef<gtk::Window>>> =
|
||||
const { std::cell::RefCell::new(None) };
|
||||
static ILLUSTRATION: std::cell::RefCell<Option<glib::WeakRef<gtk::DrawingArea>>> =
|
||||
const { std::cell::RefCell::new(None) };
|
||||
}
|
||||
|
||||
/// Uppdatera config + applicera + spara (debouncat 300 ms).
|
||||
@@ -30,6 +32,11 @@ fn commit(ctx: &Rc<Ctx>, 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<Ctx>) -> gtk::ScrolledWindow {
|
||||
scroll
|
||||
}
|
||||
|
||||
fn margin_illustration(ctx: &Rc<Ctx>) -> 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<Ctx>) -> gtk::ScrolledWindow {
|
||||
let (scroll, list) = page();
|
||||
let cfg = ctx.cfg.borrow().clone();
|
||||
@@ -272,8 +398,19 @@ fn build_layout_page(ctx: &Rc<Ctx>) -> 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<Ctx>) -> 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<Ctx>) -> 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<Ctx>) {
|
||||
|
||||
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();
|
||||
|
||||
@@ -178,6 +178,14 @@ pub fn subscribe(on_event: std::rc::Rc<dyn Fn(&Value)>) -> 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 {
|
||||
|
||||
Reference in New Issue
Block a user