v0.1.2: reservera plats som faktiskt reserverar + kantvänlig reveal

- Exclusive zone sätts explicit (uppmätt storlek + marginal) — gtk4-
  layer-shells auto-zon räknar bara för ytor som spänner kanten, och
  zonen ignoreras per spec för hörn-förankrade ytor. Därför spänner
  ytan nu alltid kanten när avoid_windows är på (transparenta flanker);
  ön ritas via panel-alignment och ser ut precis som förut.
- Zonen fryses och ligger kvar när docken dodge-göms (fönstret hålls
  mappat) — maximerade fönster stannar alltid vid docklinjen.
- Dodge-djupet använder senaste synliga storlek (ingen flapp).
- Reveal via kant-sensorn: docken STANNAR medan pekaren är kvar vid
  kanten/i dockområdet; göm-fördröjningen börjar först när man lämnar
  området (sensor_hover räknas som hover).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 14:37:55 +02:00
parent bf279c5f2f
commit ad37495e60
4 changed files with 131 additions and 45 deletions

2
Cargo.lock generated
View File

@@ -1042,7 +1042,7 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "waydock"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"async-channel",
"gtk4",

View File

@@ -1,6 +1,6 @@
[package]
name = "waydock"
version = "0.1.1"
version = "0.1.2"
edition = "2021"
description = "Modern, resurssnål dock för Wayfire — transparent, JSON-konfigurerad, per-skärm"
license = "MIT"

View File

@@ -13,9 +13,11 @@ command -v gtk4-launch >/dev/null 2>&1 || \
mkdir -p "$DEST"
echo "Hämtar waydock (latest)…"
curl -fSL -o "$DEST/waydock" \
# temp + mv: atomiskt byte som funkar även när waydock kör (ETXTBSY)
curl -fSL -o "$DEST/waydock.new" \
"$BASE/releases/download/latest/waydock-latest-linux-x64"
chmod +x "$DEST/waydock"
chmod +x "$DEST/waydock.new"
mv -f "$DEST/waydock.new" "$DEST/waydock"
# ikon + .desktop-fil
mkdir -p "$HOME/.local/share/icons/hicolor/scalable/apps" "$HOME/.local/share/applications"

View File

@@ -58,10 +58,15 @@ pub struct Dock {
pub connector: String,
/// pekaren är över dockan just nu
pub hover: Cell<bool>,
/// pekaren är på väcknings-sensorn vid kanten
pub sensor_hover: Cell<bool>,
/// dockan är undan-gömd av autohide
pub hidden: Cell<bool>,
/// något fönster överlappar dockans kantzon (dodge)
pub dodge_overlap: Cell<bool>,
/// senaste synliga storlek längs huvudaxeln (px) — används för
/// exclusive zone och dodge-djup även när docken är gömd
pub main_size: Cell<i32>,
/// generationsräknare för fördröjd göm-timer
hide_generation: Cell<u64>,
/// sensorremsan som väcker dockan när den är gömd
@@ -113,14 +118,17 @@ fn configure_layer(win: &gtk::ApplicationWindow, cfg: &Config, monitor: &gdk::Mo
let (side_start, side_end) = side_edges(&cfg.position);
win.set_anchor(edge, true);
win.set_margin(edge, cfg.margin);
if cfg.expand {
// täck hela kanten; start/slut-marginalerna drar in baren
// exclusive zone honoreras bara för ytor som är förankrade i EN kant
// eller spänner kanten (spec + Wayfires tolkning) — så med reservera
// plats spänner ytan alltid kanten; ön ritas ändå som en ö via
// panelens alignment (flankerna är transparenta)
if cfg.expand || cfg.avoid_windows {
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);
win.set_margin(side_start, cfg.margin_start.max(0));
win.set_margin(side_end, cfg.margin_end.max(0));
} else {
// ö: förankra bara i vald ände (ingen sidoförankring = centrerad)
// ö utan reservation: förankra bara i vald ände
match cfg.alignment {
Alignment::Center => {}
Alignment::Start => {
@@ -133,13 +141,35 @@ fn configure_layer(win: &gtk::ApplicationWindow, cfg: &Config, monitor: &gdk::Mo
}
}
}
// reservera plats fungerar ihop med autohide: maximerade fönster
// stannar vid dockan (och triggar då aldrig dodge) — bara fönster
// man själv drar in i zonen gömmer den
if cfg.avoid_windows {
win.auto_exclusive_zone_enable();
// exclusive zone sätts explicit efter map (auto-zonen i
// gtk4-layer-shell fungerar bara för ytor som spänner hela kanten)
win.set_exclusive_zone(0);
}
/// Reservera plats: zon = dockans uppmätta storlek + marginal. Zonen
/// ligger kvar även när docken är dodge-gömd (fönstret hålls mappat),
/// så maximerade fönster alltid stannar vid docklinjen.
pub fn update_exclusive_zone(ctx: &Rc<Ctx>, dock: &Rc<Dock>) {
let cfg = ctx.cfg.borrow().for_output(&dock.connector);
if !cfg.avoid_windows {
if !dock.hidden.get() {
dock.window.set_exclusive_zone(0);
}
return;
}
let horizontal = matches!(cfg.position, Position::Bottom | Position::Top);
let size = if horizontal {
dock.window.height()
} else {
win.set_exclusive_zone(0);
dock.window.width()
};
if size > 1 && !dock.hidden.get() {
dock.main_size.set(size);
}
if dock.main_size.get() > 1 {
let zone = dock.main_size.get() + cfg.margin.max(0);
debug!("{}: exclusive zone {}px", dock.connector, zone);
dock.window.set_exclusive_zone(zone);
}
}
@@ -204,9 +234,17 @@ fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Rc<Do
items_box.set_vexpand(true);
items_box.set_valign(items_align);
}
} else {
panel.set_halign(gtk::Align::Center);
} else if orient == gtk::Orientation::Horizontal {
// ö: när ytan spänner kanten (reservera plats) placerar
// panel-alignmenten ön; expand krävs för att alignment ska
// verka i hela ytans bredd (annars packas panelen först)
panel.set_hexpand(true);
panel.set_halign(items_align);
panel.set_valign(gtk::Align::Center);
} else {
panel.set_vexpand(true);
panel.set_halign(gtk::Align::Center);
panel.set_valign(items_align);
}
panel.append(&items_box);
@@ -279,8 +317,10 @@ fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Rc<Do
revealer,
connector,
hover: Cell::new(false),
sensor_hover: Cell::new(false),
hidden: Cell::new(false),
dodge_overlap: Cell::new(false),
main_size: Cell::new(0),
hide_generation: Cell::new(0),
sensor: RefCell::new(None),
});
@@ -290,13 +330,13 @@ fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Rc<Do
empty_click.set_button(3);
{
let ctx = ctx.clone();
let panel2 = panel.clone();
let outer2 = outer.clone();
empty_click.connect_pressed(move |gesture, _, x, y| {
gesture.set_state(gtk::EventSequenceState::Claimed);
menu::popup_panel_menu(&ctx, &panel2, x, y);
menu::popup_panel_menu(&ctx, &outer2, x, y);
});
}
panel.add_controller(empty_click);
outer.add_controller(empty_click);
// hover-spårning för autohide
let motion = gtk::EventControllerMotion::new();
@@ -358,39 +398,61 @@ fn build_sensor(ctx: &Rc<Ctx>, dock: &Rc<Dock>, monitor: &gdk::Monitor) {
let ctx = ctx.clone();
let dock2 = dock.clone();
motion.connect_enter(move |_, _, _| {
dock2.sensor_hover.set(true);
dock2.hide_generation.set(dock2.hide_generation.get() + 1);
if dock2.hidden.get() {
set_dock_hidden(&dock2, false);
// om pekaren aldrig når dockan: göm igen efter en stund
let ctx2 = ctx.clone();
let dock3 = dock2.clone();
glib::timeout_add_local_once(Duration::from_millis(1500), move || {
if !dock3.hover.get() {
update_autohide(&ctx2);
}
});
set_dock_hidden(&ctx, &dock2, false);
}
});
}
{
let ctx = ctx.clone();
let dock2 = dock.clone();
motion.connect_leave(move |_| {
dock2.sensor_hover.set(false);
// göm-fördröjningen börjar först NÄR man lämnar kantområdet
schedule_hide(&ctx, &dock2);
});
}
strip.add_controller(motion);
sensor.present();
*dock.sensor.borrow_mut() = Some(sensor);
}
fn set_dock_hidden(dock: &Rc<Dock>, hidden: bool) {
fn set_dock_hidden(ctx: &Ctx, dock: &Rc<Dock>, hidden: bool) {
if dock.hidden.get() == hidden {
return;
}
let cfg = ctx.cfg.borrow().for_output(&dock.connector);
let horizontal = matches!(cfg.position, Position::Bottom | Position::Top);
// kom ihåg synlig storlek innan vi gömmer
if hidden {
let size = if horizontal { dock.window.height() } else { dock.window.width() };
if size > 1 {
dock.main_size.set(size);
}
}
dock.hidden.set(hidden);
if hidden {
let duration = dock.revealer.transition_duration();
dock.revealer.set_reveal_child(false);
let dock2 = dock.clone();
glib::timeout_add_local_once(Duration::from_millis(duration as u64 + 30), move || {
if dock2.hidden.get() {
dock2.window.set_visible(false);
}
});
if cfg.avoid_windows {
// reservera plats även gömd: håll fönstret mappat (kollapsat)
// med frusen zon — maximerade fönster stannar vid docklinjen
dock.window
.set_exclusive_zone(dock.main_size.get().max(1) + cfg.margin.max(0));
} else {
let duration = dock.revealer.transition_duration();
let dock2 = dock.clone();
glib::timeout_add_local_once(
Duration::from_millis(duration as u64 + 30),
move || {
if dock2.hidden.get() {
dock2.window.set_visible(false);
}
},
);
}
} else {
dock.window.set_visible(true);
dock.revealer.set_reveal_child(true);
@@ -399,7 +461,7 @@ fn set_dock_hidden(dock: &Rc<Dock>, hidden: bool) {
fn should_hide(ctx: &Ctx, dock: &Dock) -> bool {
let cfg = ctx.cfg.borrow().for_output(&dock.connector);
if !cfg.autohide.enabled || dock.hover.get() {
if !cfg.autohide.enabled || dock.hover.get() || dock.sensor_hover.get() {
return false;
}
match cfg.autohide.mode {
@@ -417,7 +479,7 @@ fn should_hide(ctx: &Ctx, dock: &Dock) -> bool {
/// Applicera göm/visa-läget på alla dockor.
pub fn update_autohide(ctx: &Rc<Ctx>) {
for dock in ctx.docks.borrow().iter() {
set_dock_hidden(dock, should_hide(ctx, dock));
set_dock_hidden(ctx, dock, should_hide(ctx, dock));
}
}
@@ -429,7 +491,7 @@ fn schedule_hide(ctx: &Rc<Ctx>, dock: &Rc<Dock>) {
let dock = dock.clone();
glib::timeout_add_local_once(Duration::from_millis(delay as u64), move || {
if dock.hide_generation.get() == generation && should_hide(&ctx, &dock) {
set_dock_hidden(&dock, true);
set_dock_hidden(&ctx, &dock, true);
}
});
}
@@ -462,11 +524,18 @@ pub fn recompute_dodge(ctx: &Rc<Ctx>) {
};
let ow = output["geometry"]["width"].as_f64().unwrap_or(0.0);
let oh = output["geometry"]["height"].as_f64().unwrap_or(0.0);
// zonen dockan upptar vid sin kant (fönstrets storlek + marginal)
let depth = match position {
Position::Bottom | Position::Top => dock.window.height().max(40) + margin,
_ => dock.window.width().max(40) + margin,
} as f64;
// zonen dockan upptar vid sin kant: senaste SYNLIGA storlek —
// den kollapsade storleken skulle få hidden/synlig att flappa
if !dock.hidden.get() {
let current = match position {
Position::Bottom | Position::Top => dock.window.height(),
_ => dock.window.width(),
};
if current > 1 {
dock.main_size.set(current);
}
}
let depth = (dock.main_size.get().max(40) + margin) as f64;
let overlap = views.iter().any(|v| {
if v["role"].as_str() != Some("toplevel")
@@ -579,6 +648,21 @@ pub fn refresh_items(ctx: &Rc<Ctx>) {
let widget = build_item_widget(ctx, &dock.connector, Rc::new(item));
dock.items_box.append(&widget);
}
let ctx2 = ctx.clone();
let dock2 = dock.clone();
// första configure-rundan är asynkron — mät tills storleken finns
let mut attempts = 0;
glib::timeout_add_local(Duration::from_millis(120), move |
| {
attempts += 1;
update_exclusive_zone(&ctx2, &dock2);
let done = dock2.main_size.get() > 1 || attempts > 20;
if done {
glib::ControlFlow::Break
} else {
glib::ControlFlow::Continue
}
});
}
}