Skroll-stegning i fönsterlistan + klick/launch-debugloggar
All checks were successful
check / check (push) Successful in 1m34s
All checks were successful
check / check (push) Successful in 1m34s
Med en grupps lista öppen stegar skroll (på gruppikonen eller över listan) markeringen mellan raderna — markerad rad får accentbakgrund och triggar samma förhandsvisning som hover (peek/fokus enligt list_hover). Funkar i både hover- och klickläge; utan öppen lista beter sig skroll som förut (cykla fönster). Watchdogen räknar stegning som aktivitet så listan inte stängs mitt i. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
19
src/dock.rs
19
src/dock.rs
@@ -47,6 +47,8 @@ pub struct Ctx {
|
|||||||
pub refresh_pending: Cell<bool>,
|
pub refresh_pending: Cell<bool>,
|
||||||
/// den enda popover som får vara öppen åt gången
|
/// den enda popover som får vara öppen åt gången
|
||||||
pub current_popover: RefCell<Option<gtk::Popover>>,
|
pub current_popover: RefCell<Option<gtk::Popover>>,
|
||||||
|
/// öppen fönsterlista (för skroll-stegning från gruppikonen)
|
||||||
|
pub group_list: RefCell<Option<menu::GroupList>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Dock {
|
pub struct Dock {
|
||||||
@@ -730,6 +732,17 @@ fn build_item_widget(ctx: &Rc<Ctx>, connector: &str, item: Rc<Item>) -> gtk::But
|
|||||||
let item = item.clone();
|
let item = item.clone();
|
||||||
let conn2 = connector.to_string();
|
let conn2 = connector.to_string();
|
||||||
scroll.connect_scroll(move |_, _, dy| {
|
scroll.connect_scroll(move |_, _, dy| {
|
||||||
|
// om den här gruppens lista är öppen: stega markeringen i den
|
||||||
|
let step = ctx
|
||||||
|
.group_list
|
||||||
|
.borrow()
|
||||||
|
.as_ref()
|
||||||
|
.filter(|h| h.key == item.meta.key && h.connector == conn2)
|
||||||
|
.map(|h| h.step.clone());
|
||||||
|
if let Some(step) = step {
|
||||||
|
step(dy);
|
||||||
|
return glib::Propagation::Stop;
|
||||||
|
}
|
||||||
if ctx.cfg.borrow().for_output(&conn2).behavior.scroll == ScrollAction::CycleWindows {
|
if ctx.cfg.borrow().for_output(&conn2).behavior.scroll == ScrollAction::CycleWindows {
|
||||||
let live = live_item(&ctx, &conn2, &item);
|
let live = live_item(&ctx, &conn2, &item);
|
||||||
cycle_windows(&ctx, &live, dy > 0.0);
|
cycle_windows(&ctx, &live, dy > 0.0);
|
||||||
@@ -745,6 +758,12 @@ fn build_item_widget(ctx: &Rc<Ctx>, connector: &str, item: Rc<Item>) -> gtk::But
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn primary_action(ctx: &Rc<Ctx>, connector: &str, item: &Rc<Item>, anchor: >k::Button) {
|
fn primary_action(ctx: &Rc<Ctx>, connector: &str, item: &Rc<Item>, anchor: >k::Button) {
|
||||||
|
debug!(
|
||||||
|
"vänsterklick: {} ({} fönster, pinnad={})",
|
||||||
|
item.meta.display_name,
|
||||||
|
item.windows.len(),
|
||||||
|
item.pinned
|
||||||
|
);
|
||||||
let service = ctx.service.borrow();
|
let service = ctx.service.borrow();
|
||||||
let Some(service) = service.as_ref() else {
|
let Some(service) = service.as_ref() else {
|
||||||
icons::launch(&item.meta);
|
icons::launch(&item.meta);
|
||||||
|
|||||||
@@ -88,8 +88,9 @@ pub fn lookup_pin(desktop_id: &str) -> Option<AppMeta> {
|
|||||||
/// Starta en app (ny instans).
|
/// Starta en app (ny instans).
|
||||||
pub fn launch(meta: &AppMeta) {
|
pub fn launch(meta: &AppMeta) {
|
||||||
if let Some(info) = &meta.app_info {
|
if let Some(info) = &meta.app_info {
|
||||||
if let Err(e) = info.launch(&[], None::<&gio::AppLaunchContext>) {
|
match info.launch(&[], None::<&gio::AppLaunchContext>) {
|
||||||
log::warn!("kunde inte starta {}: {e}", meta.display_name);
|
Ok(_) => log::info!("startade {}", meta.display_name),
|
||||||
|
Err(e) => log::warn!("kunde inte starta {}: {e}", meta.display_name),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::warn!("ingen .desktop för {} — kan inte starta", meta.display_name);
|
log::warn!("ingen .desktop för {} — kan inte starta", meta.display_name);
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ fn activate(app: >k::Application, cfg_path: PathBuf) {
|
|||||||
open_popovers: Cell::new(0),
|
open_popovers: Cell::new(0),
|
||||||
refresh_pending: Cell::new(false),
|
refresh_pending: Cell::new(false),
|
||||||
current_popover: RefCell::new(None),
|
current_popover: RefCell::new(None),
|
||||||
|
group_list: RefCell::new(None),
|
||||||
});
|
});
|
||||||
|
|
||||||
// CSS + rebuild/refresh-logik
|
// CSS + rebuild/refresh-logik
|
||||||
|
|||||||
166
src/menu.rs
166
src/menu.rs
@@ -18,6 +18,13 @@ use crate::settings;
|
|||||||
use crate::toplevel::ToplevelInfo;
|
use crate::toplevel::ToplevelInfo;
|
||||||
use crate::wayfire_ipc;
|
use crate::wayfire_ipc;
|
||||||
|
|
||||||
|
/// Handle till en öppen fönsterlista: gruppikonen skroll-stegar via denna.
|
||||||
|
pub struct GroupList {
|
||||||
|
pub key: String,
|
||||||
|
pub connector: String,
|
||||||
|
pub step: Rc<dyn Fn(f64)>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Målet för den senast öppnade ikon-menyn (actions läser härifrån).
|
/// Målet för den senast öppnade ikon-menyn (actions läser härifrån).
|
||||||
pub struct MenuTarget {
|
pub struct MenuTarget {
|
||||||
pub key: String,
|
pub key: String,
|
||||||
@@ -343,7 +350,10 @@ pub fn popup_group_list(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: >k::Button) {
|
|||||||
// fönster som tonats ner av peek — återställs vid radbyte/stängning
|
// 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()));
|
let dimmed: Rc<std::cell::RefCell<Vec<i64>>> = Rc::new(std::cell::RefCell::new(Vec::new()));
|
||||||
|
|
||||||
for w in &item.windows {
|
// bygg raderna först, koppla handlers efteråt (select behöver alla)
|
||||||
|
let windows: Rc<Vec<crate::toplevel::ToplevelInfo>> = Rc::new(item.windows.clone());
|
||||||
|
let mut rows_vec = Vec::new();
|
||||||
|
for w in windows.iter() {
|
||||||
let row = gtk::Button::new();
|
let row = gtk::Button::new();
|
||||||
row.add_css_class("flat");
|
row.add_css_class("flat");
|
||||||
let title = if w.title.is_empty() { item.meta.display_name.clone() } else { w.title.clone() };
|
let title = if w.title.is_empty() { item.meta.display_name.clone() } else { w.title.clone() };
|
||||||
@@ -352,6 +362,68 @@ pub fn popup_group_list(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: >k::Button) {
|
|||||||
label.set_max_width_chars(40);
|
label.set_max_width_chars(40);
|
||||||
label.set_halign(gtk::Align::Start);
|
label.set_halign(gtk::Align::Start);
|
||||||
row.set_child(Some(&label));
|
row.set_child(Some(&label));
|
||||||
|
list.append(&row);
|
||||||
|
rows_vec.push(row);
|
||||||
|
}
|
||||||
|
let rows: Rc<Vec<gtk::Button>> = Rc::new(rows_vec);
|
||||||
|
|
||||||
|
// markera rad idx: visuell markering + förhandsvisning (peek/fokus)
|
||||||
|
let selected = Rc::new(std::cell::Cell::new(None::<usize>));
|
||||||
|
let select: Rc<dyn Fn(usize)> = Rc::new({
|
||||||
|
let rows = rows.clone();
|
||||||
|
let windows = windows.clone();
|
||||||
|
let dimmed = dimmed.clone();
|
||||||
|
let selected = selected.clone();
|
||||||
|
let list_hover = list_hover.clone();
|
||||||
|
let ctx2 = ctx.clone();
|
||||||
|
move |idx: usize| {
|
||||||
|
for (i, r) in rows.iter().enumerate() {
|
||||||
|
if i == idx {
|
||||||
|
r.add_css_class("selected");
|
||||||
|
} else {
|
||||||
|
r.remove_css_class("selected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selected.set(Some(idx));
|
||||||
|
let w = &windows[idx];
|
||||||
|
match list_hover {
|
||||||
|
ListHover::Peek if peek_possible => {
|
||||||
|
peek_restore(&dimmed.borrow());
|
||||||
|
*dimmed.borrow_mut() = peek_apply(w);
|
||||||
|
}
|
||||||
|
ListHover::Focus => {
|
||||||
|
if let Some(s) = ctx2.service.borrow().as_ref() {
|
||||||
|
s.activate(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// skroll-stegning (från gruppikonen eller över listan)
|
||||||
|
let step: Rc<dyn Fn(f64)> = Rc::new({
|
||||||
|
let select = select.clone();
|
||||||
|
let selected = selected.clone();
|
||||||
|
let n = windows.len();
|
||||||
|
move |dy: f64| {
|
||||||
|
if n == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let next = match selected.get() {
|
||||||
|
None => {
|
||||||
|
if dy > 0.0 { 0 } else { n - 1 }
|
||||||
|
}
|
||||||
|
Some(i) => {
|
||||||
|
if dy > 0.0 { (i + 1) % n } else { (i + n - 1) % n }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
select(next);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// per rad: klick aktiverar; hover markerar (efter armering)
|
||||||
|
for (i, (row, w)) in rows.iter().zip(windows.iter()).enumerate() {
|
||||||
let ctx2 = ctx.clone();
|
let ctx2 = ctx.clone();
|
||||||
let w2 = w.clone();
|
let w2 = w.clone();
|
||||||
let pop2 = popover.clone();
|
let pop2 = popover.clone();
|
||||||
@@ -364,47 +436,47 @@ pub fn popup_group_list(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: >k::Button) {
|
|||||||
}
|
}
|
||||||
pop2.popdown();
|
pop2.popdown();
|
||||||
});
|
});
|
||||||
|
if list_hover != ListHover::None {
|
||||||
// hover-effekt per rad: peek eller fokus
|
let mc = gtk::EventControllerMotion::new();
|
||||||
match list_hover {
|
let select2 = select.clone();
|
||||||
ListHover::Peek if peek_possible => {
|
let armed2 = armed.clone();
|
||||||
let mc = gtk::EventControllerMotion::new();
|
mc.connect_enter(move |_, _, _| {
|
||||||
let w3 = w.clone();
|
if armed2.get() {
|
||||||
let dimmed3 = dimmed.clone();
|
select2(i);
|
||||||
let armed3 = armed.clone();
|
}
|
||||||
mc.connect_enter(move |_, _, _| {
|
});
|
||||||
if !armed3.get() {
|
row.add_controller(mc);
|
||||||
return;
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
let armed3 = armed.clone();
|
|
||||||
mc.connect_enter(move |_, _, _| {
|
|
||||||
if !armed3.get() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Some(s) = ctx3.service.borrow().as_ref() {
|
|
||||||
s.activate(&w3);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
row.add_controller(mc);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
list.append(&row);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skroll över själva listan stegar också
|
||||||
|
{
|
||||||
|
let sc = gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::VERTICAL);
|
||||||
|
let step2 = step.clone();
|
||||||
|
sc.connect_scroll(move |_, _, dy| {
|
||||||
|
step2(dy);
|
||||||
|
glib::Propagation::Stop
|
||||||
|
});
|
||||||
|
popover.add_controller(sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
// registrera handlet så gruppikonens skroll når stegningen
|
||||||
|
*ctx.group_list.borrow_mut() = Some(GroupList {
|
||||||
|
key: item.meta.key.clone(),
|
||||||
|
connector: connector.clone(),
|
||||||
|
step: step.clone(),
|
||||||
|
});
|
||||||
|
{
|
||||||
|
let ctx2 = ctx.clone();
|
||||||
|
let key = item.meta.key.clone();
|
||||||
|
popover.connect_closed(move |_| {
|
||||||
|
let mut gl = ctx2.group_list.borrow_mut();
|
||||||
|
if gl.as_ref().map(|h| h.key == key).unwrap_or(false) {
|
||||||
|
*gl = None;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
popover.set_child(Some(&list));
|
popover.set_child(Some(&list));
|
||||||
popover.set_parent(anchor);
|
popover.set_parent(anchor);
|
||||||
|
|
||||||
@@ -437,13 +509,19 @@ pub fn popup_group_list(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: >k::Button) {
|
|||||||
}
|
}
|
||||||
popover.add_controller(mc.clone());
|
popover.add_controller(mc.clone());
|
||||||
|
|
||||||
// watchdog: om pekaren aldrig ens når listan, stäng efter en stund
|
// watchdog: om pekaren aldrig når listan OCH ingen skroll-stegning
|
||||||
|
// sker, stäng efter en stund (upprepas; aktivitet = markerad rad)
|
||||||
{
|
{
|
||||||
let pop3 = popover.clone();
|
let pop3 = popover.clone();
|
||||||
glib::timeout_add_local_once(std::time::Duration::from_millis(1500), move || {
|
let selected2 = selected.clone();
|
||||||
if !mc.contains_pointer() {
|
let last_seen = std::cell::Cell::new(selected.get());
|
||||||
pop3.popdown();
|
glib::timeout_add_local(std::time::Duration::from_millis(1500), move || {
|
||||||
|
if mc.contains_pointer() || selected2.get() != last_seen.get() {
|
||||||
|
last_seen.set(selected2.get());
|
||||||
|
return glib::ControlFlow::Continue;
|
||||||
}
|
}
|
||||||
|
pop3.popdown();
|
||||||
|
glib::ControlFlow::Break
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,8 @@ fn css_body(cfg: &Config, p: &str) -> String {
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
{p}popover.waydock-menu modelbutton:hover,
|
{p}popover.waydock-menu modelbutton:hover,
|
||||||
{p}popover.waydock-menu button.flat:hover {{
|
{p}popover.waydock-menu button.flat:hover,
|
||||||
|
{p}popover.waydock-menu button.flat.selected {{
|
||||||
background-color: {accent_soft};
|
background-color: {accent_soft};
|
||||||
}}
|
}}
|
||||||
"#,
|
"#,
|
||||||
|
|||||||
Reference in New Issue
Block a user