From 37c530c69185636e8d71228133867397bc67b9aa Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Mon, 24 Jul 2023 19:31:31 +0200 Subject: [PATCH] moving: Add stacking indicator --- resources/i18n/en/cosmic_comp.ftl | 3 +- src/backend/render/mod.rs | 1 + src/shell/element/mod.rs | 67 +++++++++++++++++++++------- src/shell/element/stack_hover.rs | 74 +++++++++++++++++++++++++++++++ src/shell/grabs/moving.rs | 56 ++++++++++++++++++++--- src/shell/layout/tiling/mod.rs | 48 ++++++++++++++------ 6 files changed, 213 insertions(+), 36 deletions(-) create mode 100644 src/shell/element/stack_hover.rs diff --git a/resources/i18n/en/cosmic_comp.ftl b/resources/i18n/en/cosmic_comp.ftl index 219db51a..9a2ef9e8 100644 --- a/resources/i18n/en/cosmic_comp.ftl +++ b/resources/i18n/en/cosmic_comp.ftl @@ -1,3 +1,4 @@ grow-window = Grow shrink-window = Shrink -unknown-keybinding = \ No newline at end of file +unknown-keybinding = +stack-windows = Stack Windows \ No newline at end of file diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index b333c75e..da4c8718 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -83,6 +83,7 @@ pub type GlMultiError = MultiError, GbmGlesBackend< pub static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0]; pub static GROUP_COLOR: [f32; 3] = [0.788, 0.788, 0.788]; +pub static ACTIVE_GROUP_COLOR: [f32; 3] = [0.58, 0.922, 0.922]; pub static FOCUS_INDICATOR_COLOR: [f32; 3] = [0.580, 0.921, 0.921]; pub static OUTLINE_SHADER: &str = include_str!("./shaders/rounded_outline.frag"); diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index a84ececc..d5fc0fcf 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -13,6 +13,7 @@ use smithay::{ input::KeyState, renderer::{ element::{ + memory::MemoryRenderBufferRenderElement, utils::{CropRenderElement, RelocateRenderElement, RescaleRenderElement}, Element, RenderElement, UnderlyingStorage, }, @@ -55,6 +56,7 @@ pub use self::stack::CosmicStack; pub mod window; pub use self::window::CosmicWindow; pub mod resize_indicator; +pub mod stack_hover; #[cfg(feature = "debug")] use egui::plot::{Corner, Legend, Plot, PlotPoints, Polygon}; @@ -972,7 +974,8 @@ where ), GrabbedStack(RescaleRenderElement>), GrabbedWindow(RescaleRenderElement>), - Indicator(PixelShaderElement), + FocusIndicator(PixelShaderElement), + StackHoverIndicator(MemoryRenderBufferRenderElement), #[cfg(feature = "debug")] Egui(TextureRenderElement), } @@ -990,7 +993,8 @@ where CosmicMappedRenderElement::TiledWindow(elem) => elem.id(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.id(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.id(), - CosmicMappedRenderElement::Indicator(elem) => elem.id(), + CosmicMappedRenderElement::FocusIndicator(elem) => elem.id(), + CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.id(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.id(), } @@ -1004,7 +1008,8 @@ where CosmicMappedRenderElement::TiledWindow(elem) => elem.current_commit(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.current_commit(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.current_commit(), - CosmicMappedRenderElement::Indicator(elem) => elem.current_commit(), + CosmicMappedRenderElement::FocusIndicator(elem) => elem.current_commit(), + CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.current_commit(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.current_commit(), } @@ -1018,7 +1023,8 @@ where CosmicMappedRenderElement::TiledWindow(elem) => elem.src(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.src(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.src(), - CosmicMappedRenderElement::Indicator(elem) => elem.src(), + CosmicMappedRenderElement::FocusIndicator(elem) => elem.src(), + CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.src(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.src(), } @@ -1032,7 +1038,8 @@ where CosmicMappedRenderElement::TiledWindow(elem) => elem.geometry(scale), CosmicMappedRenderElement::GrabbedStack(elem) => elem.geometry(scale), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.geometry(scale), - CosmicMappedRenderElement::Indicator(elem) => elem.geometry(scale), + CosmicMappedRenderElement::FocusIndicator(elem) => elem.geometry(scale), + CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.geometry(scale), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.geometry(scale), } @@ -1046,7 +1053,8 @@ where CosmicMappedRenderElement::TiledWindow(elem) => elem.location(scale), CosmicMappedRenderElement::GrabbedStack(elem) => elem.location(scale), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.location(scale), - CosmicMappedRenderElement::Indicator(elem) => elem.location(scale), + CosmicMappedRenderElement::FocusIndicator(elem) => elem.location(scale), + CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.location(scale), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.location(scale), } @@ -1060,7 +1068,8 @@ where CosmicMappedRenderElement::TiledWindow(elem) => elem.transform(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.transform(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.transform(), - CosmicMappedRenderElement::Indicator(elem) => elem.transform(), + CosmicMappedRenderElement::FocusIndicator(elem) => elem.transform(), + CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.transform(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.transform(), } @@ -1078,7 +1087,10 @@ where CosmicMappedRenderElement::TiledWindow(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::GrabbedStack(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.damage_since(scale, commit), - CosmicMappedRenderElement::Indicator(elem) => elem.damage_since(scale, commit), + CosmicMappedRenderElement::FocusIndicator(elem) => elem.damage_since(scale, commit), + CosmicMappedRenderElement::StackHoverIndicator(elem) => { + elem.damage_since(scale, commit) + } #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.damage_since(scale, commit), } @@ -1092,7 +1104,8 @@ where CosmicMappedRenderElement::TiledWindow(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::GrabbedStack(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.opaque_regions(scale), - CosmicMappedRenderElement::Indicator(elem) => elem.opaque_regions(scale), + CosmicMappedRenderElement::FocusIndicator(elem) => elem.opaque_regions(scale), + CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.opaque_regions(scale), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.opaque_regions(scale), } @@ -1106,7 +1119,8 @@ where CosmicMappedRenderElement::TiledWindow(elem) => elem.alpha(), CosmicMappedRenderElement::GrabbedStack(elem) => elem.alpha(), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.alpha(), - CosmicMappedRenderElement::Indicator(elem) => elem.alpha(), + CosmicMappedRenderElement::FocusIndicator(elem) => elem.alpha(), + CosmicMappedRenderElement::StackHoverIndicator(elem) => elem.alpha(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.alpha(), } @@ -1128,7 +1142,10 @@ impl RenderElement for CosmicMappedRenderElement { CosmicMappedRenderElement::TiledWindow(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::GrabbedStack(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.draw(frame, src, dst, damage), - CosmicMappedRenderElement::Indicator(elem) => { + CosmicMappedRenderElement::FocusIndicator(elem) => { + RenderElement::::draw(elem, frame, src, dst, damage) + } + CosmicMappedRenderElement::StackHoverIndicator(elem) => { RenderElement::::draw(elem, frame, src, dst, damage) } #[cfg(feature = "debug")] @@ -1146,7 +1163,10 @@ impl RenderElement for CosmicMappedRenderElement { CosmicMappedRenderElement::TiledWindow(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::GrabbedStack(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.underlying_storage(renderer), - CosmicMappedRenderElement::Indicator(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::FocusIndicator(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::StackHoverIndicator(elem) => { + elem.underlying_storage(renderer) + } #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.underlying_storage(renderer), } @@ -1170,10 +1190,13 @@ impl<'a, 'b> RenderElement> CosmicMappedRenderElement::TiledWindow(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::GrabbedStack(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.draw(frame, src, dst, damage), - CosmicMappedRenderElement::Indicator(elem) => { + CosmicMappedRenderElement::FocusIndicator(elem) => { RenderElement::::draw(elem, frame.glow_frame_mut(), src, dst, damage) .map_err(|err| GlMultiError::Render(err)) } + CosmicMappedRenderElement::StackHoverIndicator(elem) => { + elem.draw(frame, src, dst, damage) + } #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => { let glow_frame = frame.glow_frame_mut(); @@ -1194,9 +1217,12 @@ impl<'a, 'b> RenderElement> CosmicMappedRenderElement::TiledWindow(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::GrabbedStack(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::GrabbedWindow(elem) => elem.underlying_storage(renderer), - CosmicMappedRenderElement::Indicator(elem) => { + CosmicMappedRenderElement::FocusIndicator(elem) => { elem.underlying_storage(renderer.glow_renderer_mut()) } + CosmicMappedRenderElement::StackHoverIndicator(elem) => { + elem.underlying_storage(renderer) + } #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => { let glow_renderer = renderer.glow_renderer_mut(); @@ -1239,7 +1265,18 @@ where CosmicMappedRenderElement: RenderElement, { fn from(elem: PixelShaderElement) -> Self { - CosmicMappedRenderElement::Indicator(elem) + CosmicMappedRenderElement::FocusIndicator(elem) + } +} + +impl From> for CosmicMappedRenderElement +where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: 'static, + CosmicMappedRenderElement: RenderElement, +{ + fn from(elem: MemoryRenderBufferRenderElement) -> Self { + CosmicMappedRenderElement::StackHoverIndicator(elem) } } diff --git a/src/shell/element/stack_hover.rs b/src/shell/element/stack_hover.rs new file mode 100644 index 00000000..fc4d3bea --- /dev/null +++ b/src/shell/element/stack_hover.rs @@ -0,0 +1,74 @@ +use crate::{ + fl, + utils::iced::{IcedElement, Program}, +}; + +use apply::Apply; +use calloop::LoopHandle; +use cosmic::{ + iced::widget::{container, row}, + iced_core::{Background, Color, Length}, + theme, + widget::{icon, text}, +}; +use smithay::utils::{Logical, Size}; + +pub type StackHover = IcedElement; + +pub fn stack_hover( + evlh: LoopHandle<'static, crate::state::Data>, + size: Size, +) -> StackHover { + StackHover::new(StackHoverInternal, size, evlh) +} + +pub struct StackHoverInternal; + +impl Program for StackHoverInternal { + type Message = (); + + fn view(&self) -> crate::utils::iced::Element<'_, Self::Message> { + row(vec![ + icon("window-stack-symbolic", 24) + .force_svg(true) + .style(theme::Svg::Symbolic) + .apply(container) + .padding(8) + .width(Length::Shrink) + .apply(container) + .center_y() + .height(Length::Fill) + .into(), + text(fl!("stack-windows")) + .font(cosmic::font::FONT) + .size(24) + .line_height(24.) + .apply(container) + .padding(8) + .width(Length::Shrink) + .apply(container) + .center_y() + .height(Length::Fill) + .into(), + ]) + .width(Length::Shrink) + .height(Length::Shrink) + .apply(container) + .padding(4) + .style(theme::Container::custom(|theme| container::Appearance { + text_color: Some(Color::from(theme.cosmic().palette.neutral_9)), + background: Some(Background::Color(theme.cosmic().palette.neutral_3.into())), + border_radius: 24.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + })) + .width(Length::Shrink) + .height(Length::Fixed(48.)) + .apply(container) + .center_x() + .center_y() + .width(Length::Fill) + .height(Length::Fill) + .into() + } +} diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index b615524c..39adade4 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -7,7 +7,10 @@ use crate::{ IndicatorShader, }, shell::{ - element::CosmicMappedRenderElement, + element::{ + stack_hover::{stack_hover, StackHover}, + CosmicMappedRenderElement, + }, focus::target::{KeyboardFocusTarget, PointerFocusTarget}, CosmicMapped, CosmicSurface, }, @@ -16,7 +19,7 @@ use crate::{ use smithay::{ backend::renderer::{ - element::{utils::RescaleRenderElement, RenderElement}, + element::{utils::RescaleRenderElement, AsRenderElements, RenderElement}, ImportAll, ImportMem, Renderer, }, desktop::space::SpaceElement, @@ -47,6 +50,7 @@ pub struct MoveGrabState { window_offset: Point, indicator_thickness: u8, start: Instant, + stacking_indicator: Option<(StackHover, Point)>, } impl MoveGrabState { @@ -114,8 +118,17 @@ impl MoveGrabState { 1.0, ); - popup_elements - .into_iter() + self.stacking_indicator + .iter() + .flat_map(|(indicator, location)| { + indicator.render_elements( + renderer, + location.to_physical_precise_round(output_scale), + output_scale, + 1.0, + ) + }) + .chain(popup_elements) .chain(focus_element) .chain(window_elements.into_iter().map(|elem| match elem { CosmicMappedRenderElement::Stack(stack) => { @@ -179,21 +192,49 @@ impl PointerGrab for MoveGrab { _focus: Option<(PointerFocusTarget, Point)>, event: &MotionEvent, ) { - let borrow = self + let mut borrow = self .seat .user_data() .get::() - .map(|s| s.borrow()); - if let Some(grab_state) = borrow.as_ref().and_then(|s| s.as_ref()) { + .map(|s| s.borrow_mut()); + if let Some(grab_state) = borrow.as_mut().and_then(|s| s.as_mut()) { let mut window_geo = self.window.geometry(); window_geo.loc += event.location.to_i32_round() + grab_state.window_offset; for output in state.common.shell.outputs() { if let Some(overlap) = output.geometry().intersection(window_geo) { if self.outputs.insert(output.clone()) { self.window.output_enter(output, overlap); + if let Some(indicator) = + grab_state.stacking_indicator.as_ref().map(|x| &x.0) + { + indicator.output_enter(output, overlap); + } } } else if self.outputs.remove(&output) { self.window.output_leave(output); + if let Some(indicator) = grab_state.stacking_indicator.as_ref().map(|x| &x.0) { + indicator.output_leave(output); + } + } + } + + let output = state.common.last_active_seat().active_output(); + if self.tiling { + let indicator_location = state + .common + .shell + .active_space(&output) + .tiling_layer + .stacking_indicator(); + + if indicator_location.is_some() != grab_state.stacking_indicator.is_some() { + grab_state.stacking_indicator = indicator_location.map(|geo| { + let element = stack_hover(state.common.event_loop_handle.clone(), geo.size); + for output in &self.outputs { + element.output_enter(output, output.geometry()); + } + (element, geo.loc) + }); } } } @@ -263,6 +304,7 @@ impl MoveGrab { window_offset: initial_window_location - initial_cursor_location.to_i32_round(), indicator_thickness, start: Instant::now(), + stacking_indicator: None, }; *seat diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index 2f80c0e6..9825c2cd 100644 --- a/src/shell/layout/tiling/mod.rs +++ b/src/shell/layout/tiling/mod.rs @@ -1,7 +1,10 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - backend::render::{element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, GROUP_COLOR}, + backend::render::{ + element::AsGlowRenderer, BackdropShader, IndicatorShader, Key, ACTIVE_GROUP_COLOR, + GROUP_COLOR, + }, shell::{ element::{ resize_indicator::ResizeIndicator, @@ -127,7 +130,7 @@ pub enum MoveResult { #[derive(Debug, Clone, PartialEq)] enum TargetZone { InitialPlaceholder(NodeId), - WindowStack(NodeId), + WindowStack(NodeId, Rectangle), WindowSplit(NodeId, Direction), GroupEdge(NodeId, Direction), GroupInterior(NodeId, usize), @@ -137,7 +140,7 @@ impl TargetZone { fn is_window_zone(&self) -> bool { matches!( self, - TargetZone::WindowStack(_) | TargetZone::WindowSplit(_, _) + TargetZone::WindowStack(..) | TargetZone::WindowSplit(_, _) ) } } @@ -1625,6 +1628,16 @@ impl TilingLayout { true } + pub fn stacking_indicator(&self) -> Option> { + if let Some(TargetZone::WindowStack(_, geo)) = + self.last_overview_hover.as_ref().map(|(_, zone)| zone) + { + Some(*geo) + } else { + None + } + } + pub fn drop_window( &mut self, window: CosmicMapped, @@ -2082,7 +2095,7 @@ impl TilingLayout { 1.0, overview.alpha().unwrap(), placeholder_id, - true, + Some(None), ) .map(|x| x.0) .unwrap_or_default(); @@ -2182,7 +2195,7 @@ impl TilingLayout { ); if stack_region.contains(location) { - TargetZone::WindowStack(res_id) + TargetZone::WindowStack(res_id, last_geometry) } else { let left_right = { let relative_loc = (location.x - last_geometry.loc.x) as f64; @@ -2703,7 +2716,8 @@ impl TilingLayout { let mut window_elements = Vec::new(); let mut popup_elements = Vec::new(); - let is_mouse_tiling = matches!(overview, OverviewMode::Started(Trigger::Pointer(_), _)); + let is_mouse_tiling = (matches!(overview, OverviewMode::Started(Trigger::Pointer(_), _))) + .then(|| self.last_overview_hover.as_ref().map(|x| &x.1)); // all gone windows and fade them out let old_geometries = if let Some(reference_tree) = reference_tree.as_ref() { @@ -2805,7 +2819,7 @@ fn geometries_for_groupview<'a, R>( alpha: f32, transition: f32, placeholder_id: &Id, - mouse_tiling: bool, + mouse_tiling: Option>, ) -> Option<( HashMap>, Vec>, @@ -2818,14 +2832,14 @@ where { // we need to recalculate geometry for all elements, if we are drawing groups if let Some(root) = tree.root_node_id() { - let outer_gap: i32 = (if mouse_tiling { + let outer_gap: i32 = (if mouse_tiling.is_some() { OUTER_GAP_MOUSE } else { OUTER_GAP_KEYBOARD } as f32 * transition) .round() as i32; - let inner_gap: i32 = (if mouse_tiling { + let inner_gap: i32 = (if mouse_tiling.is_some() { INNER_GAP_MOUSE } else { INNER_GAP_KEYBOARD @@ -2967,7 +2981,7 @@ where ) .into(), ); - } else if mouse_tiling && node.parent() == Some(&root) { + } else if mouse_tiling.is_some() && node.parent() == Some(&root) { elements.push( IndicatorShader::element( *renderer, @@ -3185,8 +3199,16 @@ where .unwrap() .any(|id| id == focused_id) }) - .unwrap_or(mouse_tiling) + .unwrap_or(mouse_tiling.is_some()) { + let color = match mouse_tiling { + Some(Some(TargetZone::WindowStack(stack_id, _))) + if *stack_id == node_id => + { + ACTIVE_GROUP_COLOR + } + _ => GROUP_COLOR, + }; elements.push( BackdropShader::element( *renderer, @@ -3197,13 +3219,13 @@ where * if focused .as_ref() .map(|focused_id| focused_id == &node_id) - .unwrap_or(false) + .unwrap_or(color == ACTIVE_GROUP_COLOR) { 0.4 } else { 0.15 }, - GROUP_COLOR, + color, ) .into(), );