From 75990ff056938bae0611fc69b5e99abd69e85bde Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 12 Dec 2023 15:35:23 +0000 Subject: [PATCH] menu: Add stack tab specific menu --- resources/i18n/en/cosmic_comp.ftl | 3 +- src/shell/element/stack.rs | 36 +++++++++++++ src/shell/element/stack/tab.rs | 18 +++++++ src/shell/element/window.rs | 1 + src/shell/grabs/menu/default.rs | 73 +++++++++++++++++++++++++-- src/shell/layout/tiling/mod.rs | 28 ++++++++-- src/shell/mod.rs | 55 +++++++++++++------- src/shell/workspace.rs | 4 +- src/utils/screenshot.rs | 24 ++++----- src/wayland/handlers/xdg_shell/mod.rs | 2 +- 10 files changed, 197 insertions(+), 47 deletions(-) diff --git a/resources/i18n/en/cosmic_comp.ftl b/resources/i18n/en/cosmic_comp.ftl index 31906a9b..d0a02a91 100644 --- a/resources/i18n/en/cosmic_comp.ftl +++ b/resources/i18n/en/cosmic_comp.ftl @@ -12,7 +12,8 @@ window-menu-resize = Resize window-menu-move-prev-workspace = Move to previous workspace window-menu-move-next-workspace = Move to next workspace window-menu-stack = Create window stack -window-menu-unstack = Unstack windows +window-menu-unstack-all = Unstack windows +window-menu-unstack = Unstack window window-menu-always-on-top = Always on top window-menu-always-on-visible-ws = Always on visible workspace window-menu-close = Close diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index 4655e436..c67a352d 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -591,6 +591,7 @@ impl CosmicStack { pub enum Message { DragStart, Menu, + TabMenu(usize), PotentialTabDragStart(usize), Activate(usize), Close(usize), @@ -709,6 +710,40 @@ impl Program for CosmicStackInternal { &seat, serial, cursor - position.as_logical(), + true, + ); + } + } + }); + } + } + } + Message::TabMenu(idx) => { + if let Some((seat, serial)) = self.last_seat.lock().unwrap().clone() { + if let Some(surface) = self.windows.lock().unwrap()[idx].wl_surface() { + loop_handle.insert_idle(move |state| { + if let Some(mapped) = + state.common.shell.element_for_wl_surface(&surface).cloned() + { + if let Some(workspace) = state.common.shell.space_for_mut(&mapped) { + let position = workspace + .element_geometry(&mapped) + .unwrap() + .loc + .to_global(&workspace.output); + let mut cursor = seat + .get_pointer() + .unwrap() + .current_location() + .to_i32_round(); + cursor.y -= TAB_HEIGHT; + Shell::menu_request( + state, + &surface, + &seat, + serial, + cursor - position.as_logical(), + false, ); } } @@ -763,6 +798,7 @@ impl Program for CosmicStackInternal { user_data.get::().unwrap().clone(), ) .on_press(Message::PotentialTabDragStart(i)) + .on_right_click(Message::TabMenu(i)) .on_close(Message::Close(i)) }), active, diff --git a/src/shell/element/stack/tab.rs b/src/shell/element/stack/tab.rs index 48b9933e..1ac3b447 100644 --- a/src/shell/element/stack/tab.rs +++ b/src/shell/element/stack/tab.rs @@ -121,6 +121,7 @@ pub struct Tab { font: Font, close_message: Option, press_message: Option, + right_click_message: Option, rule_theme: TabRuleTheme, background_theme: TabBackgroundTheme, active: bool, @@ -135,6 +136,7 @@ impl Tab { font: cosmic::font::FONT, close_message: None, press_message: None, + right_click_message: None, rule_theme: TabRuleTheme::Default, background_theme: TabBackgroundTheme::Default, active: false, @@ -146,6 +148,11 @@ impl Tab { self } + pub fn on_right_click(mut self, message: Message) -> Self { + self.right_click_message = Some(message); + self + } + pub fn on_close(mut self, message: Message) -> Self { self.close_message = Some(message); self @@ -238,6 +245,7 @@ impl Tab { background: self.background_theme.into(), elements: items, press_message: self.press_message, + right_click_message: self.right_click_message, } } } @@ -256,6 +264,7 @@ pub(super) struct TabInternal<'a, Message: TabMessage, Renderer> { background: theme::Container, elements: Vec>, press_message: Option, + right_click_message: Option, } impl<'a, Message, Renderer> Widget for TabInternal<'a, Message, Renderer> @@ -382,6 +391,15 @@ where return event::Status::Captured; } } + if matches!( + event, + event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) + ) { + if let Some(message) = self.right_click_message.clone() { + shell.publish(message); + return event::Status::Captured; + } + } if matches!( event, event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 8e4639c9..b4919173 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -310,6 +310,7 @@ impl Program for CosmicWindowInternal { &seat, serial, cursor - position.as_logical(), + false, ); } } diff --git a/src/shell/grabs/menu/default.rs b/src/shell/grabs/menu/default.rs index fb6122e4..abf08fd7 100644 --- a/src/shell/grabs/menu/default.rs +++ b/src/shell/grabs/menu/default.rs @@ -3,9 +3,13 @@ use smithay::wayland::seat::WaylandFocus; use crate::{ config::{Action, StaticConfig}, fl, - shell::{element::CosmicMapped, grabs::ReleaseMode, Shell}, + shell::{ + element::{CosmicMapped, CosmicWindow}, + grabs::ReleaseMode, + CosmicSurface, Shell, + }, state::{Common, State}, - utils::screenshot::screenshot_window, + utils::{prelude::SeatExt, screenshot::screenshot_window}, }; use super::{Item, ResizeEdge}; @@ -99,6 +103,66 @@ fn move_next_workspace(state: &mut State, mapped: &CosmicMapped) { } } +pub fn tab_items( + stack: &CosmicMapped, + tab: &CosmicSurface, + is_tiled: bool, + config: &StaticConfig, +) -> impl Iterator { + let unstack_clone_stack = stack.clone(); + let unstack_clone_tab = tab.clone(); + let screenshot_clone = tab.clone(); + let close_clone = tab.clone(); + + vec![ + Item::new(fl!("window-menu-unstack"), move |handle| { + let mut mapped = unstack_clone_stack.clone(); + let surface = unstack_clone_tab.clone(); + let _ = handle.insert_idle(move |state| { + mapped.stack_ref_mut().unwrap().remove_window(&surface); + let mapped: CosmicMapped = CosmicWindow::new( + surface, + state.common.event_loop_handle.clone(), + state.common.theme.clone(), + ) + .into(); + + let seat = state.common.last_active_seat().clone(); + let output = seat.active_output(); + let workspace = state.common.shell.workspaces.active_mut(&output); + if is_tiled { + for mapped in workspace + .mapped() + .filter(|m| m.maximized_state.lock().unwrap().is_some()) + .cloned() + .collect::>() + .into_iter() + { + workspace.unmaximize_request(&mapped.active_window()); + } + let focus_stack = workspace.focus_stack.get(&seat); + workspace + .tiling_layer + .map(mapped, Some(focus_stack.iter()), None, false); + } else { + workspace.floating_layer.map(mapped, None) + } + }); + }), + Item::Separator, + Item::new(fl!("window-menu-screenshot"), move |handle| { + let tab = screenshot_clone.clone(); + let _ = handle.insert_idle(move |state| screenshot_window(state, &tab)); + }), + Item::Separator, + Item::new(fl!("window-menu-close"), move |_handle| { + close_clone.close(); + }) + .shortcut(config.get_shortcut_for_action(&Action::Close)), + ] + .into_iter() +} + pub fn window_items( window: &CosmicMapped, is_tiled: bool, @@ -126,7 +190,7 @@ pub fn window_items( vec![ is_stacked.then_some( - Item::new(fl!("window-menu-unstack"), move |handle| { + Item::new(fl!("window-menu-unstack-all"), move |handle| { let mapped = unstack_clone.clone(); let _ = handle.insert_idle(move |state| { unstack(state, &mapped); @@ -161,7 +225,8 @@ pub fn window_items( // TODO: Where to save? Some(Item::new(fl!("window-menu-screenshot"), move |handle| { let mapped = screenshot_clone.clone(); - let _ = handle.insert_idle(move |state| screenshot_window(state, &mapped)); + let _ = + handle.insert_idle(move |state| screenshot_window(state, &mapped.active_window())); })), Some(Item::Separator), Some(Item::new(fl!("window-menu-move"), move |handle| { diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index c717d61b..f68f9b74 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -367,10 +367,11 @@ impl TilingLayout { window: CosmicMapped, focus_stack: Option + 'a>, direction: Option, + add_to_stack: bool, ) { window.output_enter(&self.output, window.bbox()); window.set_bounds(self.output.geometry().size.as_logical()); - self.map_internal(window, focus_stack, direction); + self.map_internal(window, focus_stack, direction, add_to_stack); } pub fn map_internal<'a>( @@ -378,13 +379,21 @@ impl TilingLayout { window: impl Into, focus_stack: Option + 'a>, direction: Option, + add_to_stack: bool, ) { let gaps = self.gaps(); let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); let last_active = focus_stack .and_then(|focus_stack| TilingLayout::last_active_window(&mut tree, focus_stack)); - TilingLayout::map_to_tree(&mut tree, window, &self.output, last_active, direction); + TilingLayout::map_to_tree( + &mut tree, + window, + &self.output, + last_active, + direction, + add_to_stack, + ); let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps); self.queue.push_tree(tree, ANIMATION_DURATION, blocker); } @@ -395,6 +404,7 @@ impl TilingLayout { output: &Output, node: Option<(NodeId, CosmicMapped)>, direction: Option, + add_to_stack: bool, ) { let window = window.into(); let new_window = Node::new(Data::Mapped { @@ -425,7 +435,7 @@ impl TilingLayout { } } else { if let Some((ref node_id, mut last_active_window)) = node { - if window.is_window() && last_active_window.is_stack() { + if add_to_stack && window.is_window() && last_active_window.is_stack() { let surface = window.active_window(); last_active_window .stack_ref_mut() @@ -533,7 +543,7 @@ impl TilingLayout { } mapped.set_tiled(true); - other.map(mapped.clone(), Some(focus_stack), None); + other.map(mapped.clone(), Some(focus_stack), None, true); return Some(KeyboardFocusTarget::Element(mapped)); } None => { @@ -1964,6 +1974,7 @@ impl TilingLayout { &self.output, Some(current_node), None, + false, ); let node = window.tiling_node_id.lock().unwrap().clone().unwrap(); @@ -2494,7 +2505,14 @@ impl TilingLayout { } } _ => { - TilingLayout::map_to_tree(&mut tree, window.clone(), &self.output, None, None); + TilingLayout::map_to_tree( + &mut tree, + window.clone(), + &self.output, + None, + None, + false, + ); window } }; diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 6a046dbf..b67ed9b7 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -74,7 +74,7 @@ use self::{ CosmicWindow, }, focus::target::KeyboardFocusTarget, - grabs::{window_items, MenuGrab, ReleaseMode, ResizeEdge, ResizeGrab}, + grabs::{tab_items, window_items, Item, MenuGrab, ReleaseMode, ResizeEdge, ResizeGrab}, layout::{ floating::ResizeState, tiling::{NodeDesc, ResizeForkGrab, TilingLayout}, @@ -1508,11 +1508,12 @@ impl Shell { .unwrap(); match target_layer { ManagedLayer::Floating => new_workspace.floating_layer.map(mapped, None), - ManagedLayer::Tiling => { - new_workspace - .tiling_layer - .map(mapped, Option::>::None, None) - } + ManagedLayer::Tiling => new_workspace.tiling_layer.map( + mapped, + Option::>::None, + None, + false, + ), }; } @@ -1640,7 +1641,7 @@ impl Shell { let focus_stack = workspace.focus_stack.get(&seat); workspace .tiling_layer - .map(mapped.clone(), Some(focus_stack.iter()), None); + .map(mapped.clone(), Some(focus_stack.iter()), None, true); } if should_be_fullscreen { @@ -1779,9 +1780,12 @@ impl Shell { if window_state.layer == ManagedLayer::Floating { to_workspace.floating_layer.map(mapped.clone(), None); } else { - to_workspace - .tiling_layer - .map(mapped.clone(), Some(focus_stack.iter()), direction); + to_workspace.tiling_layer.map( + mapped.clone(), + Some(focus_stack.iter()), + direction, + true, + ); } let focus_target = if let Some(f) = window_state.was_fullscreen { @@ -1919,6 +1923,7 @@ impl Shell { seat: &Seat, serial: impl Into>, location: Point, + target_stack: bool, ) { let serial = serial.into(); if let Some(start_data) = @@ -1958,15 +1963,27 @@ impl Shell { let grab = MenuGrab::new( start_data, seat, - window_items( - &mapped, - is_tiled, - is_stacked, - tiling_enabled, - edge, - &state.common.config.static_conf, - ) - .into_iter(), + if target_stack || !is_stacked { + Box::new(window_items( + &mapped, + is_tiled, + is_stacked, + tiling_enabled, + edge, + &state.common.config.static_conf, + )) as Box> + } else { + let (tab, _) = mapped + .windows() + .find(|(s, _)| s.wl_surface().as_ref() == Some(surface)) + .unwrap(); + Box::new(tab_items( + &mapped, + &tab, + is_tiled, + &state.common.config.static_conf, + )) as Box> + }, global_position, state.common.event_loop_handle.clone(), state.common.theme.clone(), diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index b815d368..f830793c 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -758,7 +758,7 @@ impl Workspace { { self.floating_layer.unmap(&window); self.tiling_layer - .map(window, Some(focus_stack.iter()), None) + .map(window, Some(focus_stack.iter()), None, false) } self.tiling_enabled = true; } @@ -776,7 +776,7 @@ impl Workspace { let focus_stack = self.focus_stack.get(seat); self.floating_layer.unmap(&window); self.tiling_layer - .map(window.clone(), Some(focus_stack.iter()), None) + .map(window.clone(), Some(focus_stack.iter()), None, false) } } } diff --git a/src/utils/screenshot.rs b/src/utils/screenshot.rs index f91ad40e..9038bbe1 100644 --- a/src/utils/screenshot.rs +++ b/src/utils/screenshot.rs @@ -17,11 +17,11 @@ use tracing::warn; use crate::{ backend::kms::source_node_for_surface, - shell::element::{CosmicMapped, CosmicSurface}, + shell::element::CosmicSurface, state::{BackendData, State}, }; -pub fn screenshot_window(state: &mut State, mapped: &CosmicMapped) { +pub fn screenshot_window(state: &mut State, surface: &CosmicSurface) { fn render_window( renderer: &mut R, window: &CosmicSurface, @@ -96,32 +96,26 @@ pub fn screenshot_window(state: &mut State, mapped: &CosmicMapped) { Ok(()) } - if let Some(surface) = mapped.active_window().wl_surface() { + if let Some(wl_surface) = surface.wl_surface() { let res = match &mut state.backend { BackendData::Kms(kms) => { - let node = source_node_for_surface(&surface, &state.common.display_handle) + let node = source_node_for_surface(&wl_surface, &state.common.display_handle) .unwrap_or(kms.primary); kms.api .single_renderer(&node) .with_context(|| "Failed to get renderer for screenshot") .and_then(|mut multirenderer| { - render_window( - &mut multirenderer, - &mapped.active_window(), - &state.common.local_offset, - ) + render_window(&mut multirenderer, surface, &state.common.local_offset) }) } BackendData::Winit(winit) => render_window( winit.backend.renderer(), - &mapped.active_window(), - &state.common.local_offset, - ), - BackendData::X11(x11) => render_window( - &mut x11.renderer, - &mapped.active_window(), + surface, &state.common.local_offset, ), + BackendData::X11(x11) => { + render_window(&mut x11.renderer, surface, &state.common.local_offset) + } BackendData::Unset => unreachable!(), }; if let Err(err) = res { diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index e4b43c2b..edbb65a5 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -375,7 +375,7 @@ impl XdgShellHandler for State { }) .unwrap_or_default() .loc; - Shell::menu_request(self, surface.wl_surface(), &seat, serial, location) + Shell::menu_request(self, surface.wl_surface(), &seat, serial, location, false) } }