menu: Add stack tab specific menu

This commit is contained in:
Victoria Brekenfeld 2023-12-12 15:35:23 +00:00 committed by Victoria Brekenfeld
parent 58a024ba67
commit 75990ff056
10 changed files with 197 additions and 47 deletions

View file

@ -12,7 +12,8 @@ window-menu-resize = Resize
window-menu-move-prev-workspace = Move to previous workspace window-menu-move-prev-workspace = Move to previous workspace
window-menu-move-next-workspace = Move to next workspace window-menu-move-next-workspace = Move to next workspace
window-menu-stack = Create window stack 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-top = Always on top
window-menu-always-on-visible-ws = Always on visible workspace window-menu-always-on-visible-ws = Always on visible workspace
window-menu-close = Close window-menu-close = Close

View file

@ -591,6 +591,7 @@ impl CosmicStack {
pub enum Message { pub enum Message {
DragStart, DragStart,
Menu, Menu,
TabMenu(usize),
PotentialTabDragStart(usize), PotentialTabDragStart(usize),
Activate(usize), Activate(usize),
Close(usize), Close(usize),
@ -709,6 +710,40 @@ impl Program for CosmicStackInternal {
&seat, &seat,
serial, serial,
cursor - position.as_logical(), 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::<Id>().unwrap().clone(), user_data.get::<Id>().unwrap().clone(),
) )
.on_press(Message::PotentialTabDragStart(i)) .on_press(Message::PotentialTabDragStart(i))
.on_right_click(Message::TabMenu(i))
.on_close(Message::Close(i)) .on_close(Message::Close(i))
}), }),
active, active,

View file

@ -121,6 +121,7 @@ pub struct Tab<Message: TabMessage> {
font: Font, font: Font,
close_message: Option<Message>, close_message: Option<Message>,
press_message: Option<Message>, press_message: Option<Message>,
right_click_message: Option<Message>,
rule_theme: TabRuleTheme, rule_theme: TabRuleTheme,
background_theme: TabBackgroundTheme, background_theme: TabBackgroundTheme,
active: bool, active: bool,
@ -135,6 +136,7 @@ impl<Message: TabMessage> Tab<Message> {
font: cosmic::font::FONT, font: cosmic::font::FONT,
close_message: None, close_message: None,
press_message: None, press_message: None,
right_click_message: None,
rule_theme: TabRuleTheme::Default, rule_theme: TabRuleTheme::Default,
background_theme: TabBackgroundTheme::Default, background_theme: TabBackgroundTheme::Default,
active: false, active: false,
@ -146,6 +148,11 @@ impl<Message: TabMessage> Tab<Message> {
self 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 { pub fn on_close(mut self, message: Message) -> Self {
self.close_message = Some(message); self.close_message = Some(message);
self self
@ -238,6 +245,7 @@ impl<Message: TabMessage> Tab<Message> {
background: self.background_theme.into(), background: self.background_theme.into(),
elements: items, elements: items,
press_message: self.press_message, 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, background: theme::Container,
elements: Vec<Element<'a, Message, Renderer>>, elements: Vec<Element<'a, Message, Renderer>>,
press_message: Option<Message>, press_message: Option<Message>,
right_click_message: Option<Message>,
} }
impl<'a, Message, Renderer> Widget<Message, Renderer> for TabInternal<'a, Message, Renderer> impl<'a, Message, Renderer> Widget<Message, Renderer> for TabInternal<'a, Message, Renderer>
@ -382,6 +391,15 @@ where
return event::Status::Captured; 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!( if matches!(
event, event,
event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))

View file

@ -310,6 +310,7 @@ impl Program for CosmicWindowInternal {
&seat, &seat,
serial, serial,
cursor - position.as_logical(), cursor - position.as_logical(),
false,
); );
} }
} }

View file

@ -3,9 +3,13 @@ use smithay::wayland::seat::WaylandFocus;
use crate::{ use crate::{
config::{Action, StaticConfig}, config::{Action, StaticConfig},
fl, fl,
shell::{element::CosmicMapped, grabs::ReleaseMode, Shell}, shell::{
element::{CosmicMapped, CosmicWindow},
grabs::ReleaseMode,
CosmicSurface, Shell,
},
state::{Common, State}, state::{Common, State},
utils::screenshot::screenshot_window, utils::{prelude::SeatExt, screenshot::screenshot_window},
}; };
use super::{Item, ResizeEdge}; 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<Item = Item> {
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::<Vec<_>>()
.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( pub fn window_items(
window: &CosmicMapped, window: &CosmicMapped,
is_tiled: bool, is_tiled: bool,
@ -126,7 +190,7 @@ pub fn window_items(
vec![ vec![
is_stacked.then_some( 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 mapped = unstack_clone.clone();
let _ = handle.insert_idle(move |state| { let _ = handle.insert_idle(move |state| {
unstack(state, &mapped); unstack(state, &mapped);
@ -161,7 +225,8 @@ pub fn window_items(
// TODO: Where to save? // TODO: Where to save?
Some(Item::new(fl!("window-menu-screenshot"), move |handle| { Some(Item::new(fl!("window-menu-screenshot"), move |handle| {
let mapped = screenshot_clone.clone(); 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::Separator),
Some(Item::new(fl!("window-menu-move"), move |handle| { Some(Item::new(fl!("window-menu-move"), move |handle| {

View file

@ -367,10 +367,11 @@ impl TilingLayout {
window: CosmicMapped, window: CosmicMapped,
focus_stack: Option<impl Iterator<Item = &'a CosmicMapped> + 'a>, focus_stack: Option<impl Iterator<Item = &'a CosmicMapped> + 'a>,
direction: Option<Direction>, direction: Option<Direction>,
add_to_stack: bool,
) { ) {
window.output_enter(&self.output, window.bbox()); window.output_enter(&self.output, window.bbox());
window.set_bounds(self.output.geometry().size.as_logical()); 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>( pub fn map_internal<'a>(
@ -378,13 +379,21 @@ impl TilingLayout {
window: impl Into<CosmicMapped>, window: impl Into<CosmicMapped>,
focus_stack: Option<impl Iterator<Item = &'a CosmicMapped> + 'a>, focus_stack: Option<impl Iterator<Item = &'a CosmicMapped> + 'a>,
direction: Option<Direction>, direction: Option<Direction>,
add_to_stack: bool,
) { ) {
let gaps = self.gaps(); let gaps = self.gaps();
let mut tree = self.queue.trees.back().unwrap().0.copy_clone(); let mut tree = self.queue.trees.back().unwrap().0.copy_clone();
let last_active = focus_stack let last_active = focus_stack
.and_then(|focus_stack| TilingLayout::last_active_window(&mut tree, 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); let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps);
self.queue.push_tree(tree, ANIMATION_DURATION, blocker); self.queue.push_tree(tree, ANIMATION_DURATION, blocker);
} }
@ -395,6 +404,7 @@ impl TilingLayout {
output: &Output, output: &Output,
node: Option<(NodeId, CosmicMapped)>, node: Option<(NodeId, CosmicMapped)>,
direction: Option<Direction>, direction: Option<Direction>,
add_to_stack: bool,
) { ) {
let window = window.into(); let window = window.into();
let new_window = Node::new(Data::Mapped { let new_window = Node::new(Data::Mapped {
@ -425,7 +435,7 @@ impl TilingLayout {
} }
} else { } else {
if let Some((ref node_id, mut last_active_window)) = node { 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(); let surface = window.active_window();
last_active_window last_active_window
.stack_ref_mut() .stack_ref_mut()
@ -533,7 +543,7 @@ impl TilingLayout {
} }
mapped.set_tiled(true); 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)); return Some(KeyboardFocusTarget::Element(mapped));
} }
None => { None => {
@ -1964,6 +1974,7 @@ impl TilingLayout {
&self.output, &self.output,
Some(current_node), Some(current_node),
None, None,
false,
); );
let node = window.tiling_node_id.lock().unwrap().clone().unwrap(); 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 window
} }
}; };

View file

@ -74,7 +74,7 @@ use self::{
CosmicWindow, CosmicWindow,
}, },
focus::target::KeyboardFocusTarget, focus::target::KeyboardFocusTarget,
grabs::{window_items, MenuGrab, ReleaseMode, ResizeEdge, ResizeGrab}, grabs::{tab_items, window_items, Item, MenuGrab, ReleaseMode, ResizeEdge, ResizeGrab},
layout::{ layout::{
floating::ResizeState, floating::ResizeState,
tiling::{NodeDesc, ResizeForkGrab, TilingLayout}, tiling::{NodeDesc, ResizeForkGrab, TilingLayout},
@ -1508,11 +1508,12 @@ impl Shell {
.unwrap(); .unwrap();
match target_layer { match target_layer {
ManagedLayer::Floating => new_workspace.floating_layer.map(mapped, None), ManagedLayer::Floating => new_workspace.floating_layer.map(mapped, None),
ManagedLayer::Tiling => { ManagedLayer::Tiling => new_workspace.tiling_layer.map(
new_workspace mapped,
.tiling_layer Option::<std::iter::Empty<_>>::None,
.map(mapped, Option::<std::iter::Empty<_>>::None, None) None,
} false,
),
}; };
} }
@ -1640,7 +1641,7 @@ impl Shell {
let focus_stack = workspace.focus_stack.get(&seat); let focus_stack = workspace.focus_stack.get(&seat);
workspace workspace
.tiling_layer .tiling_layer
.map(mapped.clone(), Some(focus_stack.iter()), None); .map(mapped.clone(), Some(focus_stack.iter()), None, true);
} }
if should_be_fullscreen { if should_be_fullscreen {
@ -1779,9 +1780,12 @@ impl Shell {
if window_state.layer == ManagedLayer::Floating { if window_state.layer == ManagedLayer::Floating {
to_workspace.floating_layer.map(mapped.clone(), None); to_workspace.floating_layer.map(mapped.clone(), None);
} else { } else {
to_workspace to_workspace.tiling_layer.map(
.tiling_layer mapped.clone(),
.map(mapped.clone(), Some(focus_stack.iter()), direction); Some(focus_stack.iter()),
direction,
true,
);
} }
let focus_target = if let Some(f) = window_state.was_fullscreen { let focus_target = if let Some(f) = window_state.was_fullscreen {
@ -1919,6 +1923,7 @@ impl Shell {
seat: &Seat<State>, seat: &Seat<State>,
serial: impl Into<Option<Serial>>, serial: impl Into<Option<Serial>>,
location: Point<i32, Logical>, location: Point<i32, Logical>,
target_stack: bool,
) { ) {
let serial = serial.into(); let serial = serial.into();
if let Some(start_data) = if let Some(start_data) =
@ -1958,15 +1963,27 @@ impl Shell {
let grab = MenuGrab::new( let grab = MenuGrab::new(
start_data, start_data,
seat, seat,
window_items( if target_stack || !is_stacked {
&mapped, Box::new(window_items(
is_tiled, &mapped,
is_stacked, is_tiled,
tiling_enabled, is_stacked,
edge, tiling_enabled,
&state.common.config.static_conf, edge,
) &state.common.config.static_conf,
.into_iter(), )) as Box<dyn Iterator<Item = Item>>
} 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<dyn Iterator<Item = Item>>
},
global_position, global_position,
state.common.event_loop_handle.clone(), state.common.event_loop_handle.clone(),
state.common.theme.clone(), state.common.theme.clone(),

View file

@ -758,7 +758,7 @@ impl Workspace {
{ {
self.floating_layer.unmap(&window); self.floating_layer.unmap(&window);
self.tiling_layer self.tiling_layer
.map(window, Some(focus_stack.iter()), None) .map(window, Some(focus_stack.iter()), None, false)
} }
self.tiling_enabled = true; self.tiling_enabled = true;
} }
@ -776,7 +776,7 @@ impl Workspace {
let focus_stack = self.focus_stack.get(seat); let focus_stack = self.focus_stack.get(seat);
self.floating_layer.unmap(&window); self.floating_layer.unmap(&window);
self.tiling_layer self.tiling_layer
.map(window.clone(), Some(focus_stack.iter()), None) .map(window.clone(), Some(focus_stack.iter()), None, false)
} }
} }
} }

View file

@ -17,11 +17,11 @@ use tracing::warn;
use crate::{ use crate::{
backend::kms::source_node_for_surface, backend::kms::source_node_for_surface,
shell::element::{CosmicMapped, CosmicSurface}, shell::element::CosmicSurface,
state::{BackendData, State}, state::{BackendData, State},
}; };
pub fn screenshot_window(state: &mut State, mapped: &CosmicMapped) { pub fn screenshot_window(state: &mut State, surface: &CosmicSurface) {
fn render_window<R>( fn render_window<R>(
renderer: &mut R, renderer: &mut R,
window: &CosmicSurface, window: &CosmicSurface,
@ -96,32 +96,26 @@ pub fn screenshot_window(state: &mut State, mapped: &CosmicMapped) {
Ok(()) Ok(())
} }
if let Some(surface) = mapped.active_window().wl_surface() { if let Some(wl_surface) = surface.wl_surface() {
let res = match &mut state.backend { let res = match &mut state.backend {
BackendData::Kms(kms) => { 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); .unwrap_or(kms.primary);
kms.api kms.api
.single_renderer(&node) .single_renderer(&node)
.with_context(|| "Failed to get renderer for screenshot") .with_context(|| "Failed to get renderer for screenshot")
.and_then(|mut multirenderer| { .and_then(|mut multirenderer| {
render_window( render_window(&mut multirenderer, surface, &state.common.local_offset)
&mut multirenderer,
&mapped.active_window(),
&state.common.local_offset,
)
}) })
} }
BackendData::Winit(winit) => render_window( BackendData::Winit(winit) => render_window(
winit.backend.renderer(), winit.backend.renderer(),
&mapped.active_window(), surface,
&state.common.local_offset,
),
BackendData::X11(x11) => render_window(
&mut x11.renderer,
&mapped.active_window(),
&state.common.local_offset, &state.common.local_offset,
), ),
BackendData::X11(x11) => {
render_window(&mut x11.renderer, surface, &state.common.local_offset)
}
BackendData::Unset => unreachable!(), BackendData::Unset => unreachable!(),
}; };
if let Err(err) = res { if let Err(err) = res {

View file

@ -375,7 +375,7 @@ impl XdgShellHandler for State {
}) })
.unwrap_or_default() .unwrap_or_default()
.loc; .loc;
Shell::menu_request(self, surface.wl_surface(), &seat, serial, location) Shell::menu_request(self, surface.wl_surface(), &seat, serial, location, false)
} }
} }