Justering/expand/marginaler, hover-popup med peek, appikon
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:
2026-08-30 00:03:15 +02:00
parent c9cdf2edc3
commit 04d999642c
10 changed files with 490 additions and 32 deletions

View File

@@ -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: &gtk::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: &gtk::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: &gtk::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();
}