From 4ea0136a9bcfb8a39070521a759e3dbf0429eaea Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Wed, 17 May 2023 19:46:21 +0200 Subject: [PATCH] tiling: Add code to render group hints --- rust-toolchain.toml | 2 +- src/backend/render/mod.rs | 133 +++- src/shell/element/mod.rs | 70 +- src/shell/focus/target.rs | 6 +- src/shell/layout/floating/grabs/moving.rs | 4 +- src/shell/layout/floating/mod.rs | 4 +- src/shell/layout/tiling/mod.rs | 786 ++++++++++++++++------ src/shell/workspace.rs | 10 +- 8 files changed, 739 insertions(+), 276 deletions(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 70772a38..5937e385 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.65" \ No newline at end of file +channel = "1.66" \ No newline at end of file diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index f820a113..dab84e90 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -3,6 +3,8 @@ use std::{ borrow::{Borrow, BorrowMut}, cell::RefCell, + collections::HashMap, + sync::Weak, }; #[cfg(feature = "debug")] @@ -12,8 +14,8 @@ use crate::{ }; use crate::{ shell::{ - element::window::CosmicWindowRenderElement, layout::floating::SeatMoveGrabState, - CosmicMappedRenderElement, + element::window::CosmicWindowRenderElement, focus::target::WindowGroup, + layout::floating::SeatMoveGrabState, CosmicMapped, CosmicMappedRenderElement, }, state::{Common, Fps}, utils::prelude::SeatExt, @@ -47,7 +49,7 @@ use smithay::{ }, }, output::Output, - utils::{Logical, Physical, Point, Rectangle, Size}, + utils::{IsAlive, Logical, Physical, Point, Rectangle, Size}, wayland::{ dmabuf::get_dmabuf, shm::{shm_format_to_fourcc, with_buffer_contents}, @@ -66,11 +68,54 @@ pub type GlMultiFrame<'a, 'b, 'frame> = MultiFrame<'a, 'a, 'b, 'frame, GbmGlesBackend, GbmGlesBackend>; pub static CLEAR_COLOR: [f32; 4] = [0.153, 0.161, 0.165, 1.0]; +pub static ACTIVE_GROUP_COLOR: [f32; 3] = [0.678, 0.635, 0.619]; +pub static GROUP_COLOR: [f32; 3] = [0.431, 0.404, 0.396]; pub static FOCUS_INDICATOR_COLOR: [f32; 3] = [0.580, 0.921, 0.921]; pub static FOCUS_INDICATOR_SHADER: &str = include_str!("./shaders/focus_indicator.frag"); pub struct IndicatorShader(pub GlesPixelProgram); -struct IndicatorElement(pub RefCell); + +#[derive(Clone)] +pub enum Key { + Group(Weak<()>), + Window(CosmicMapped), +} +impl std::hash::Hash for Key { + fn hash(&self, state: &mut H) { + match self { + Key::Group(arc) => (arc.as_ptr() as usize).hash(state), + Key::Window(window) => window.hash(state), + } + } +} +impl PartialEq for Key { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Key::Group(g1), Key::Group(g2)) => Weak::ptr_eq(g1, g2), + (Key::Window(w1), Key::Window(w2)) => w1 == w2, + _ => false, + } + } +} +impl Eq for Key {} +impl From for Key { + fn from(window: CosmicMapped) -> Self { + Key::Window(window) + } +} +impl From for Key { + fn from(group: WindowGroup) -> Self { + Key::Group(group.alive.clone()) + } +} + +#[derive(PartialEq)] +struct IndicatorSettings { + thickness: u8, + alpha: f32, + color: [f32; 3], +} +type IndicatorCache = RefCell>; impl IndicatorShader { pub fn get(renderer: &R) -> GlesPixelProgram { @@ -85,50 +130,63 @@ impl IndicatorShader { pub fn element( renderer: &R, + key: impl Into, geo: Rectangle, thickness: u8, alpha: f32, + color: [f32; 3], ) -> PixelShaderElement { - let thickness: f32 = thickness as f32; - let thickness_loc = (thickness as i32, thickness as i32); - let thickness_size = ((thickness * 2.0) as i32, (thickness * 2.0) as i32); - let geo = Rectangle::from_loc_and_size( - geo.loc - Point::from(thickness_loc), - geo.size + Size::from(thickness_size), - ); + let settings = IndicatorSettings { + thickness, + alpha, + color, + }; let user_data = Borrow::::borrow(renderer.glow_renderer()) .egl_context() .user_data(); - match user_data.get::() { - Some(elem) => { - let mut elem = elem.0.borrow_mut(); - if elem.geometry(1.0.into()).to_logical(1) != geo { - elem.resize(geo, None); - } - elem.clone() - } - None => { - let shader = Self::get(renderer); + user_data.insert_if_missing(|| IndicatorCache::new(HashMap::new())); + let mut cache = user_data.get::().unwrap().borrow_mut(); + cache.retain(|k, _| match k { + Key::Group(w) => w.upgrade().is_some(), + Key::Window(w) => w.alive(), + }); - let elem = PixelShaderElement::new( - shader, - geo, - None, //TODO - alpha, - vec![ - Uniform::new("color", FOCUS_INDICATOR_COLOR), - Uniform::new("thickness", thickness), - Uniform::new("radius", thickness * 2.0), - ], - ); - if !user_data.insert_if_missing(|| IndicatorElement(RefCell::new(elem.clone()))) { - *user_data.get::().unwrap().0.borrow_mut() = elem.clone(); - } - elem - } + let key = key.into(); + if cache + .get(&key) + .filter(|(old_settings, _)| &settings == old_settings) + .is_none() + { + let thickness: f32 = thickness as f32; + let thickness_loc = (thickness as i32, thickness as i32); + let thickness_size = ((thickness * 2.0) as i32, (thickness * 2.0) as i32); + let geo = Rectangle::from_loc_and_size( + geo.loc - Point::from(thickness_loc), + geo.size + Size::from(thickness_size), + ); + let shader = Self::get(renderer); + + let elem = PixelShaderElement::new( + shader, + geo, + None, //TODO + alpha, + vec![ + Uniform::new("color", color), + Uniform::new("thickness", thickness), + Uniform::new("radius", thickness * 2.0), + ], + ); + cache.insert(key.clone(), (settings, elem)); } + + let elem = &mut cache.get_mut(&key).unwrap().1; + if elem.geometry(1.0.into()).to_logical(1) != geo { + elem.resize(geo, None); + } + elem.clone() } } @@ -307,6 +365,7 @@ where &state.shell.override_redirect_windows, state.xwayland_state.as_mut(), (!move_active && is_active_space).then_some(&last_active_seat), + true, state.config.static_conf.active_hint, exclude_workspace_overview, ) diff --git a/src/shell/element/mod.rs b/src/shell/element/mod.rs index 9aa7fc29..818d5a2a 100644 --- a/src/shell/element/mod.rs +++ b/src/shell/element/mod.rs @@ -12,8 +12,8 @@ use smithay::{ input::KeyState, renderer::{ element::{ - utils::CropRenderElement, AsRenderElements, Element, RenderElement, - UnderlyingStorage, + utils::{CropRenderElement, RelocateRenderElement, RescaleRenderElement}, + AsRenderElements, Element, RenderElement, UnderlyingStorage, }, gles::element::PixelShaderElement, glow::GlowRenderer, @@ -259,7 +259,7 @@ impl CosmicMapped { } pub fn set_tiled(&self, tiled: bool) { - for window in match &self.element { + if let Some(window) = match &self.element { // we use the tiled state of stack windows anyway to get rid of decorations CosmicMappedInternal::Stack(_) => None, CosmicMappedInternal::Window(w) => Some(w.surface()), @@ -681,8 +681,16 @@ where { Stack(self::stack::CosmicStackRenderElement), Window(self::window::CosmicWindowRenderElement), - CroppedStack(CropRenderElement>), - CroppedWindow(CropRenderElement>), + TiledStack( + RelocateRenderElement< + RescaleRenderElement>>, + >, + ), + TiledWindow( + RelocateRenderElement< + RescaleRenderElement>>, + >, + ), Indicator(PixelShaderElement), #[cfg(feature = "debug")] Egui(TextureRenderElement), @@ -697,8 +705,8 @@ where match self { CosmicMappedRenderElement::Stack(elem) => elem.id(), CosmicMappedRenderElement::Window(elem) => elem.id(), - CosmicMappedRenderElement::CroppedStack(elem) => elem.id(), - CosmicMappedRenderElement::CroppedWindow(elem) => elem.id(), + CosmicMappedRenderElement::TiledStack(elem) => elem.id(), + CosmicMappedRenderElement::TiledWindow(elem) => elem.id(), CosmicMappedRenderElement::Indicator(elem) => elem.id(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.id(), @@ -709,8 +717,8 @@ where match self { CosmicMappedRenderElement::Stack(elem) => elem.current_commit(), CosmicMappedRenderElement::Window(elem) => elem.current_commit(), - CosmicMappedRenderElement::CroppedStack(elem) => elem.current_commit(), - CosmicMappedRenderElement::CroppedWindow(elem) => elem.current_commit(), + CosmicMappedRenderElement::TiledStack(elem) => elem.current_commit(), + CosmicMappedRenderElement::TiledWindow(elem) => elem.current_commit(), CosmicMappedRenderElement::Indicator(elem) => elem.current_commit(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.current_commit(), @@ -721,8 +729,8 @@ where match self { CosmicMappedRenderElement::Stack(elem) => elem.src(), CosmicMappedRenderElement::Window(elem) => elem.src(), - CosmicMappedRenderElement::CroppedStack(elem) => elem.src(), - CosmicMappedRenderElement::CroppedWindow(elem) => elem.src(), + CosmicMappedRenderElement::TiledStack(elem) => elem.src(), + CosmicMappedRenderElement::TiledWindow(elem) => elem.src(), CosmicMappedRenderElement::Indicator(elem) => elem.src(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.src(), @@ -733,8 +741,8 @@ where match self { CosmicMappedRenderElement::Stack(elem) => elem.geometry(scale), CosmicMappedRenderElement::Window(elem) => elem.geometry(scale), - CosmicMappedRenderElement::CroppedStack(elem) => elem.geometry(scale), - CosmicMappedRenderElement::CroppedWindow(elem) => elem.geometry(scale), + CosmicMappedRenderElement::TiledStack(elem) => elem.geometry(scale), + CosmicMappedRenderElement::TiledWindow(elem) => elem.geometry(scale), CosmicMappedRenderElement::Indicator(elem) => elem.geometry(scale), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.geometry(scale), @@ -745,8 +753,8 @@ where match self { CosmicMappedRenderElement::Stack(elem) => elem.location(scale), CosmicMappedRenderElement::Window(elem) => elem.location(scale), - CosmicMappedRenderElement::CroppedStack(elem) => elem.location(scale), - CosmicMappedRenderElement::CroppedWindow(elem) => elem.location(scale), + CosmicMappedRenderElement::TiledStack(elem) => elem.location(scale), + CosmicMappedRenderElement::TiledWindow(elem) => elem.location(scale), CosmicMappedRenderElement::Indicator(elem) => elem.location(scale), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.location(scale), @@ -757,8 +765,8 @@ where match self { CosmicMappedRenderElement::Stack(elem) => elem.transform(), CosmicMappedRenderElement::Window(elem) => elem.transform(), - CosmicMappedRenderElement::CroppedStack(elem) => elem.transform(), - CosmicMappedRenderElement::CroppedWindow(elem) => elem.transform(), + CosmicMappedRenderElement::TiledStack(elem) => elem.transform(), + CosmicMappedRenderElement::TiledWindow(elem) => elem.transform(), CosmicMappedRenderElement::Indicator(elem) => elem.transform(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.transform(), @@ -773,8 +781,8 @@ where match self { CosmicMappedRenderElement::Stack(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::Window(elem) => elem.damage_since(scale, commit), - CosmicMappedRenderElement::CroppedStack(elem) => elem.damage_since(scale, commit), - CosmicMappedRenderElement::CroppedWindow(elem) => elem.damage_since(scale, commit), + CosmicMappedRenderElement::TiledStack(elem) => elem.damage_since(scale, commit), + CosmicMappedRenderElement::TiledWindow(elem) => elem.damage_since(scale, commit), CosmicMappedRenderElement::Indicator(elem) => elem.damage_since(scale, commit), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.damage_since(scale, commit), @@ -785,8 +793,8 @@ where match self { CosmicMappedRenderElement::Stack(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::Window(elem) => elem.opaque_regions(scale), - CosmicMappedRenderElement::CroppedStack(elem) => elem.opaque_regions(scale), - CosmicMappedRenderElement::CroppedWindow(elem) => elem.opaque_regions(scale), + CosmicMappedRenderElement::TiledStack(elem) => elem.opaque_regions(scale), + CosmicMappedRenderElement::TiledWindow(elem) => elem.opaque_regions(scale), CosmicMappedRenderElement::Indicator(elem) => elem.opaque_regions(scale), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.opaque_regions(scale), @@ -797,8 +805,8 @@ where match self { CosmicMappedRenderElement::Stack(elem) => elem.alpha(), CosmicMappedRenderElement::Window(elem) => elem.alpha(), - CosmicMappedRenderElement::CroppedStack(elem) => elem.alpha(), - CosmicMappedRenderElement::CroppedWindow(elem) => elem.alpha(), + CosmicMappedRenderElement::TiledStack(elem) => elem.alpha(), + CosmicMappedRenderElement::TiledWindow(elem) => elem.alpha(), CosmicMappedRenderElement::Indicator(elem) => elem.alpha(), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.alpha(), @@ -817,8 +825,8 @@ impl RenderElement for CosmicMappedRenderElement { match self { CosmicMappedRenderElement::Stack(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::Window(elem) => elem.draw(frame, src, dst, damage), - CosmicMappedRenderElement::CroppedStack(elem) => elem.draw(frame, src, dst, damage), - CosmicMappedRenderElement::CroppedWindow(elem) => elem.draw(frame, src, dst, damage), + CosmicMappedRenderElement::TiledStack(elem) => elem.draw(frame, src, dst, damage), + CosmicMappedRenderElement::TiledWindow(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::Indicator(elem) => { RenderElement::::draw(elem, frame, src, dst, damage) } @@ -833,8 +841,8 @@ impl RenderElement for CosmicMappedRenderElement { match self { CosmicMappedRenderElement::Stack(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::Window(elem) => elem.underlying_storage(renderer), - CosmicMappedRenderElement::CroppedStack(elem) => elem.underlying_storage(renderer), - CosmicMappedRenderElement::CroppedWindow(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::TiledStack(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::TiledWindow(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::Indicator(elem) => elem.underlying_storage(renderer), #[cfg(feature = "debug")] CosmicMappedRenderElement::Egui(elem) => elem.underlying_storage(renderer), @@ -855,8 +863,8 @@ impl<'a, 'b> RenderElement> match self { CosmicMappedRenderElement::Stack(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::Window(elem) => elem.draw(frame, src, dst, damage), - CosmicMappedRenderElement::CroppedStack(elem) => elem.draw(frame, src, dst, damage), - CosmicMappedRenderElement::CroppedWindow(elem) => elem.draw(frame, src, dst, damage), + CosmicMappedRenderElement::TiledStack(elem) => elem.draw(frame, src, dst, damage), + CosmicMappedRenderElement::TiledWindow(elem) => elem.draw(frame, src, dst, damage), CosmicMappedRenderElement::Indicator(elem) => { RenderElement::::draw(elem, frame.glow_frame_mut(), src, dst, damage) .map_err(|err| MultiError::Render(err)) @@ -877,8 +885,8 @@ impl<'a, 'b> RenderElement> match self { CosmicMappedRenderElement::Stack(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::Window(elem) => elem.underlying_storage(renderer), - CosmicMappedRenderElement::CroppedStack(elem) => elem.underlying_storage(renderer), - CosmicMappedRenderElement::CroppedWindow(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::TiledStack(elem) => elem.underlying_storage(renderer), + CosmicMappedRenderElement::TiledWindow(elem) => elem.underlying_storage(renderer), CosmicMappedRenderElement::Indicator(elem) => { elem.underlying_storage(renderer.glow_renderer_mut()) } diff --git a/src/shell/focus/target.rs b/src/shell/focus/target.rs index 39d6481b..673e9366 100644 --- a/src/shell/focus/target.rs +++ b/src/shell/focus/target.rs @@ -52,9 +52,9 @@ impl From for PointerFocusTarget { #[derive(Debug, Clone)] pub struct WindowGroup { - pub(in crate::shell) node: NodeId, - pub(in crate::shell) output: WeakOutput, - pub(in crate::shell) alive: Weak<()>, + pub node: NodeId, + pub output: WeakOutput, + pub alive: Weak<()>, } impl PartialEq for WindowGroup { diff --git a/src/shell/layout/floating/grabs/moving.rs b/src/shell/layout/floating/grabs/moving.rs index 24b9ea59..3f5587cf 100644 --- a/src/shell/layout/floating/grabs/moving.rs +++ b/src/shell/layout/floating/grabs/moving.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::{ - backend::render::{element::AsGlowRenderer, IndicatorShader}, + backend::render::{element::AsGlowRenderer, IndicatorShader, FOCUS_INDICATOR_COLOR}, shell::{ element::{window::CosmicWindowRenderElement, CosmicMapped, CosmicMappedRenderElement}, focus::target::{KeyboardFocusTarget, PointerFocusTarget}, @@ -66,9 +66,11 @@ impl MoveGrabState { elements.push( CosmicMappedRenderElement::from(IndicatorShader::element( renderer, + self.window.clone(), Rectangle::from_loc_and_size(render_location, self.window.geometry().size), self.indicator_thickness, 1.0, + FOCUS_INDICATOR_COLOR, )) .into(), ); diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index b5a0ce04..a82c2041 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -13,7 +13,7 @@ use smithay::{ use std::collections::HashMap; use crate::{ - backend::render::{element::AsGlowRenderer, IndicatorShader}, + backend::render::{element::AsGlowRenderer, IndicatorShader, FOCUS_INDICATOR_COLOR}, shell::{ element::{window::CosmicWindowRenderElement, CosmicMapped, CosmicMappedRenderElement}, grabs::ResizeEdge, @@ -380,12 +380,14 @@ impl FloatingLayout { if indicator_thickness > 0 { let element = IndicatorShader::element( renderer, + elem.clone(), Rectangle::from_loc_and_size( self.space.element_location(elem).unwrap() - output_loc, elem.geometry().size, ), indicator_thickness, 1.0, + FOCUS_INDICATOR_COLOR, ); elements.insert(0, element.into()); } diff --git a/src/shell/layout/tiling/mod.rs b/src/shell/layout/tiling/mod.rs index a1e3d34d..d0d2d805 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, IndicatorShader}, + backend::render::{ + element::AsGlowRenderer, IndicatorShader, Key, ACTIVE_GROUP_COLOR, FOCUS_INDICATOR_COLOR, + GROUP_COLOR, + }, shell::{ element::{window::CosmicWindowRenderElement, CosmicMapped, CosmicMappedRenderElement}, focus::{ @@ -23,7 +26,10 @@ use cosmic_time::{Cubic, Ease, Tween}; use id_tree::{InsertBehavior, MoveBehavior, Node, NodeId, NodeIdError, RemoveBehavior, Tree}; use smithay::{ backend::renderer::{ - element::{utils::CropRenderElement, AsRenderElements, RenderElement}, + element::{ + utils::{CropRenderElement, Relocate, RelocateRenderElement, RescaleRenderElement}, + AsRenderElements, RenderElement, + }, ImportAll, ImportMem, Renderer, }, desktop::{layer_map_for_output, space::SpaceElement, PopupKind}, @@ -1432,6 +1438,8 @@ impl TilingLayout { renderer: &mut R, output: &Output, focused: Option<&CosmicMapped>, + non_exclusive_zone: Rectangle, + draw_groups: bool, indicator_thickness: u8, ) -> Result>, OutputNotMapped> where @@ -1473,213 +1481,589 @@ impl TilingLayout { let mut elements = Vec::new(); - // all old windows and fade them out - if let Some(reference_tree) = reference_tree.as_ref() { - if let Some(root) = reference_tree.root_node_id() { - elements.extend( - reference_tree - .traverse_pre_order(root) - .unwrap() - .filter(|node| node.data().is_mapped(None)) - .map(|node| match node.data() { - Data::Mapped { - mapped, - last_geometry, - .. - } => (mapped, last_geometry), - _ => unreachable!(), - }) - .filter(|(mapped, _)| { - if let Some(root) = target_tree.root_node_id() { - !target_tree - .traverse_pre_order(root) - .unwrap() - .any(|node| node.data().is_mapped(Some(mapped))) - } else { - true - } - }) - .flat_map(|(mapped, geo)| { - let crop_rect = geo.clone(); - AsRenderElements::::render_elements::>( - mapped, - renderer, - geo.loc.to_physical_precise_round(output_scale) - - mapped - .geometry() - .loc - .to_physical_precise_round(output_scale), - Scale::from(output_scale), - 1.0 - percentage, - ) - .into_iter() - .flat_map(|element| match element { - CosmicMappedRenderElement::Stack(elem) => { - CropRenderElement::from_element( - elem, - output_scale, - crop_rect.to_physical_precise_round(output_scale), - ) - .map(CosmicMappedRenderElement::CroppedStack) - } - CosmicMappedRenderElement::Window(elem) => { - CropRenderElement::from_element( - elem, - output_scale, - crop_rect.to_physical_precise_round(output_scale), - ) - .map(CosmicMappedRenderElement::CroppedWindow) - } - x => Some(x), - }) - .collect::>() - }), + // all gone windows and fade them out + let old_geometries = if let Some(reference_tree) = reference_tree.as_ref() { + let (geometries, _) = if draw_groups { + geometries_for_groupview( + reference_tree, + renderer, + non_exclusive_zone, + focused, // TODO: Would be better to be an old focus, + // but for that we have to associate focus with a tree (and animate focus changes properly) + 1.0 - percentage, ) + } else { + None } - } + .unzip(); - if let Some(root) = target_tree.root_node_id() { - elements.extend( - target_tree - .traverse_pre_order(root) - .unwrap() - .filter(|node| node.data().is_mapped(None)) - .filter(|node| match node.data() { - Data::Mapped { mapped, .. } => mapped.is_activated(), - _ => unreachable!(), - }) - .map(|node| match node.data() { - Data::Mapped { - mapped, - last_geometry, - .. - } => (mapped, last_geometry), - _ => unreachable!(), - }) - .chain( - target_tree - .traverse_pre_order(root) - .unwrap() - .filter(|node| node.data().is_mapped(None)) - .filter(|node| match node.data() { - Data::Mapped { mapped, .. } => !mapped.is_activated(), - _ => unreachable!(), - }) - .map(|node| match node.data() { - Data::Mapped { - mapped, - last_geometry, - .. - } => (mapped, last_geometry), - _ => unreachable!(), - }), - ) - .flat_map(|(mapped, new_geo)| { - let old_geo = if let Some(reference_tree) = reference_tree.as_ref() { - if let Some(root) = reference_tree.root_node_id() { - reference_tree - .traverse_pre_order(root) - .unwrap() - .find(|node| node.data().is_mapped(Some(mapped))) - .map(|node| match node.data() { - Data::Mapped { last_geometry, .. } => last_geometry, - _ => unreachable!(), - }) - } else { - None - } - } else { - None - }; + // all old windows we want to fade out + elements.extend(render_old_tree( + reference_tree, + target_tree, + renderer, + geometries.clone(), + output_scale, + percentage, + )); - let (geo, alpha) = if let Some(old_geo) = old_geo { - ( - Rectangle::from_loc_and_size( - ( - old_geo.loc.x - + ((new_geo.loc.x - old_geo.loc.x) as f32 * percentage) - .round() - as i32, - old_geo.loc.y - + ((new_geo.loc.y - old_geo.loc.y) as f32 * percentage) - .round() - as i32, - ), - ( - old_geo.size.w - + ((new_geo.size.w - old_geo.size.w) as f32 - * percentage) - .round() - as i32, - old_geo.size.h - + ((new_geo.size.h - old_geo.size.h) as f32 - * percentage) - .round() - as i32, - ), - ), - 1.0, - ) - } else { - // TODO: If old_geo.is_none() animate alpha - fade in - (*new_geo, percentage) - }; + geometries + } else { + None + }; - if alpha < 1.0 { - dbg!(alpha); - } - - let crop_rect = geo.clone(); - let mut elements = - AsRenderElements::::render_elements::>( - mapped, - renderer, - geo.loc.to_physical_precise_round(output_scale) - - mapped - .geometry() - .loc - .to_physical_precise_round(output_scale), - Scale::from(output_scale), - alpha, - ) - .into_iter() - .flat_map(|element| match element { - CosmicMappedRenderElement::Stack(elem) => { - CropRenderElement::from_element( - elem, - output_scale, - crop_rect.to_physical_precise_round(output_scale), - ) - .map(CosmicMappedRenderElement::CroppedStack) - } - CosmicMappedRenderElement::Window(elem) => { - CropRenderElement::from_element( - elem, - output_scale, - crop_rect.to_physical_precise_round(output_scale), - ) - .map(CosmicMappedRenderElement::CroppedWindow) - } - x => Some(x), - }) - .collect::>(); - - if focused == Some(mapped) { - if indicator_thickness > 0 { - let element = IndicatorShader::element( - renderer, - geo, - indicator_thickness, - 1.0, - ); - elements.insert(0, element.into()); - } - } - elements - }), + let (geometries, group_elements) = if draw_groups { + geometries_for_groupview( + target_tree, + renderer, + non_exclusive_zone, + focused, + percentage, ) + } else { + None } + .unzip(); + + // tiling hints + if let Some(group_elements) = group_elements { + elements.extend(group_elements); + } + + // all alive windows + elements.extend(render_new_tree( + target_tree, + reference_tree, + renderer, + geometries, + old_geometries, + focused, + output_scale, + percentage, + if draw_groups { 3 } else { indicator_thickness }, + )); Ok(elements) } } + +fn geometries_for_groupview( + tree: &Tree, + renderer: &mut R, + non_exclusive_zone: Rectangle, + focused: Option<&CosmicMapped>, + alpha: f32, +) -> Option<( + HashMap>, + Vec>, +)> +where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: 'static, + CosmicMappedRenderElement: RenderElement, + CosmicWindowRenderElement: RenderElement, +{ + // we need to recalculate geometry for all elements, if we are drawing groups + if let Some(root) = tree.root_node_id() { + let mut stack = vec![non_exclusive_zone]; + let mut elements = Vec::new(); + let mut geometries = HashMap::new(); + + const GAP: i32 = 16; + for node_id in tree.traverse_pre_order_ids(root).unwrap() { + if let Some(mut geo) = stack.pop() { + // zoom in windows + geo.loc += (GAP, GAP).into(); + geo.size -= (GAP * 2, GAP * 2).into(); + + let node: &Node = tree.get(&node_id).unwrap(); + let data = node.data(); + + let is_potential_group = if let Some(focused) = focused { + // 1. focused can move into us directly + if let Some(parent) = node.parent() { + let parent_data = tree.get(parent).unwrap().data(); + + let idx = tree + .children_ids(parent) + .unwrap() + .position(|id| id == &node_id) + .unwrap(); + if let Some((focused_idx, _focused_id)) = tree + .children_ids(parent) + .unwrap() + .enumerate() + .find(|(_, child_id)| { + tree.get(child_id).unwrap().data().is_mapped(Some(focused)) + }) + { + // only direct neighbors + focused_idx.abs_diff(idx) == 1 + // skip neighbors, if this is a group of two windows + && !(parent_data.len() == 2 && data.is_mapped(None)) + // skip groups of two in opposite orientation to indicate move between + && !(parent_data.len() == 2 && if data.is_group() { parent_data.orientation() != data.orientation() } else { false } ) + } else { + false + } + } + // 2. focused can move out into us + else { + tree.children_ids(&node_id) + .unwrap() + .find(|child_id| { + tree.children(child_id) + .unwrap() + .any(|child| child.data().is_mapped(Some(focused))) + }) + .is_some() + } + } else { + false + }; + + match data { + Data::Group { + orientation, + last_geometry, + sizes, + alive, + } => { + let has_active_child = if let Some(focused) = focused { + tree.children(&node_id) + .unwrap() + .any(|child| child.data().is_mapped(Some(focused))) + } else { + false + }; + + if (is_potential_group || has_active_child) && &node_id != root { + elements.push( + IndicatorShader::element( + renderer, + Key::Group(Arc::downgrade(&alive)), + geo, + 3, + alpha, + if has_active_child { + ACTIVE_GROUP_COLOR + } else { + GROUP_COLOR + }, + ) + .into(), + ) + } + + geometries.insert(node_id.clone(), geo); + + let previous_length = match orientation { + Orientation::Horizontal => last_geometry.size.h, + Orientation::Vertical => last_geometry.size.w, + }; + let new_length = match orientation { + Orientation::Horizontal => geo.size.h, + Orientation::Vertical => geo.size.w, + }; + + let mut sizes = sizes + .iter() + .map(|len| { + (((*len as f64) / (previous_length as f64)) * (new_length as f64)) + .round() as i32 + }) + .collect::>(); + let sum: i32 = sizes.iter().sum(); + if sum < new_length { + *sizes.last_mut().unwrap() += new_length - sum; + } + + match orientation { + Orientation::Horizontal => { + let mut previous: i32 = sizes.iter().sum(); + for size in sizes.iter().rev() { + previous -= *size; + stack.push(Rectangle::from_loc_and_size( + (geo.loc.x, geo.loc.y + previous), + (geo.size.w, *size), + )); + } + } + Orientation::Vertical => { + let mut previous: i32 = sizes.iter().sum(); + for size in sizes.iter().rev() { + previous -= *size; + stack.push(Rectangle::from_loc_and_size( + (geo.loc.x + previous, geo.loc.y), + (*size, geo.size.h), + )); + } + } + } + } + Data::Mapped { mapped, .. } => { + if is_potential_group { + elements.push( + IndicatorShader::element( + renderer, + mapped.clone(), + geo, + 3, + alpha, + GROUP_COLOR, + ) + .into(), + ); + + geo.loc += (GAP, GAP).into(); + geo.size -= (GAP * 2, GAP * 2).into(); + } + + geometries.insert(node_id.clone(), geo); + } + } + } + } + + Some((geometries, elements)) + } else { + None + } +} + +fn render_old_tree( + reference_tree: &Tree, + target_tree: &Tree, + renderer: &mut R, + geometries: Option>>, + output_scale: f64, + percentage: f32, +) -> Vec> +where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: 'static, + CosmicMappedRenderElement: RenderElement, + CosmicWindowRenderElement: RenderElement, +{ + if let Some(root) = reference_tree.root_node_id() { + let geometries = geometries.unwrap_or_default(); + reference_tree + .traverse_pre_order_ids(root) + .unwrap() + .filter(|node_id| reference_tree.get(node_id).unwrap().data().is_mapped(None)) + .map( + |node_id| match reference_tree.get(&node_id).unwrap().data() { + Data::Mapped { + mapped, + last_geometry, + .. + } => (mapped, last_geometry, geometries.get(&node_id)), + _ => unreachable!(), + }, + ) + .filter(|(mapped, _, _)| { + if let Some(root) = target_tree.root_node_id() { + !target_tree + .traverse_pre_order(root) + .unwrap() + .any(|node| node.data().is_mapped(Some(mapped))) + } else { + true + } + }) + .flat_map(|(mapped, original_geo, scaled_geo)| { + let (scale, offset) = scaled_geo + .map(|adapted_geo| scale_to_center(&original_geo, adapted_geo)) + .unwrap_or_else(|| (1.0.into(), (0, 0).into())); + let geo = scaled_geo + .map(|adapted_geo| { + Rectangle::from_loc_and_size( + adapted_geo.loc + offset, + ( + (original_geo.size.w as f64 * scale).round() as i32, + (original_geo.size.h as f64 * scale).round() as i32, + ), + ) + }) + .unwrap_or(*original_geo); + + let crop_rect = geo.clone(); + let original_location = original_geo.loc.to_physical_precise_round(output_scale) + - mapped + .geometry() + .loc + .to_physical_precise_round(output_scale); + AsRenderElements::::render_elements::>( + mapped, + renderer, + original_location, + Scale::from(output_scale), + 1.0 - percentage, + ) + .into_iter() + .flat_map(|element| match element { + CosmicMappedRenderElement::Stack(elem) => Some( + CosmicMappedRenderElement::TiledStack(RelocateRenderElement::from_element( + RescaleRenderElement::from_element( + CropRenderElement::from_element( + elem, + output_scale, + crop_rect.to_physical_precise_round(output_scale), + )?, + original_location, + scale, + ), + geo.loc.to_physical_precise_round(output_scale), + Relocate::Absolute, + )), + ), + CosmicMappedRenderElement::Window(elem) => { + Some(CosmicMappedRenderElement::TiledWindow( + RelocateRenderElement::from_element( + RescaleRenderElement::from_element( + CropRenderElement::from_element( + elem, + output_scale, + crop_rect.to_physical_precise_round(output_scale), + )?, + (0, 0).into(), + scale, + ), + geo.loc.to_physical_precise_round(output_scale), + Relocate::Absolute, + ), + )) + } + x => Some(x), + }) + .collect::>() + }) + .collect() + } else { + Vec::new() + } +} + +fn render_new_tree( + target_tree: &Tree, + reference_tree: Option<&Tree>, + renderer: &mut R, + geometries: Option>>, + old_geometries: Option>>, + focused: Option<&CosmicMapped>, + output_scale: f64, + percentage: f32, + indicator_thickness: u8, +) -> Vec> +where + R: Renderer + ImportAll + ImportMem + AsGlowRenderer, + ::TextureId: 'static, + CosmicMappedRenderElement: RenderElement, + CosmicWindowRenderElement: RenderElement, +{ + if let Some(root) = target_tree.root_node_id() { + let old_geometries = old_geometries.unwrap_or_default(); + let geometries = geometries.unwrap_or_default(); + target_tree + .traverse_pre_order_ids(root) + .unwrap() + .filter(|node_id| target_tree.get(node_id).unwrap().data().is_mapped(None)) + .map(|node_id| match target_tree.get(&node_id).unwrap().data() { + Data::Mapped { + mapped, + last_geometry, + .. + } => (mapped, last_geometry, geometries.get(&node_id)), + _ => unreachable!(), + }) + .flat_map(|(mapped, original_geo, scaled_geo)| { + let (old_original_geo, old_scaled_geo) = + if let Some(reference_tree) = reference_tree.as_ref() { + if let Some(root) = reference_tree.root_node_id() { + reference_tree + .traverse_pre_order_ids(root) + .unwrap() + .find(|node_id| { + reference_tree + .get(node_id) + .unwrap() + .data() + .is_mapped(Some(mapped)) + }) + .map( + |node_id| match reference_tree.get(&node_id).unwrap().data() { + Data::Mapped { last_geometry, .. } => { + (last_geometry, old_geometries.get(&node_id)) + } + _ => unreachable!(), + }, + ) + } else { + None + } + } else { + None + } + .unzip(); + let old_geo = old_original_geo.map(|original_geo| { + let (scale, offset) = old_scaled_geo + .unwrap() + .map(|adapted_geo| scale_to_center(original_geo, adapted_geo)) + .unwrap_or_else(|| (1.0.into(), (0, 0).into())); + old_scaled_geo + .unwrap() + .map(|adapted_geo| { + Rectangle::from_loc_and_size( + adapted_geo.loc + offset, + ( + (original_geo.size.w as f64 * scale).round() as i32, + (original_geo.size.h as f64 * scale).round() as i32, + ), + ) + }) + .unwrap_or(*original_geo) + }); + + let crop_rect = original_geo; + let (scale, offset) = scaled_geo + .map(|adapted_geo| scale_to_center(original_geo, adapted_geo)) + .unwrap_or_else(|| (1.0.into(), (0, 0).into())); + let new_geo = scaled_geo + .map(|adapted_geo| { + Rectangle::from_loc_and_size( + adapted_geo.loc + offset, + ( + (original_geo.size.w as f64 * scale).round() as i32, + (original_geo.size.h as f64 * scale).round() as i32, + ), + ) + }) + .unwrap_or(*original_geo); + + let (geo, alpha) = if let Some(old_geo) = old_geo { + ( + Rectangle::from_loc_and_size( + ( + old_geo.loc.x + + ((new_geo.loc.x - old_geo.loc.x) as f32 * percentage).round() + as i32, + old_geo.loc.y + + ((new_geo.loc.y - old_geo.loc.y) as f32 * percentage).round() + as i32, + ), + ( + old_geo.size.w + + ((new_geo.size.w - old_geo.size.w) as f32 * percentage) + .round() as i32, + old_geo.size.h + + ((new_geo.size.h - old_geo.size.h) as f32 * percentage) + .round() as i32, + ), + ), + 1.0, + ) + } else { + (new_geo, percentage) + }; + + let original_location = original_geo.loc.to_physical_precise_round(output_scale) + - mapped + .geometry() + .loc + .to_physical_precise_round(output_scale); + let mut elements = AsRenderElements::::render_elements::< + CosmicMappedRenderElement, + >( + mapped, + renderer, + original_location, + Scale::from(output_scale), + alpha, + ) + .into_iter() + .flat_map(|element| match element { + CosmicMappedRenderElement::Stack(elem) => Some( + CosmicMappedRenderElement::TiledStack(RelocateRenderElement::from_element( + RescaleRenderElement::from_element( + CropRenderElement::from_element( + elem, + output_scale, + crop_rect.to_physical_precise_round(output_scale), + )?, + original_location, + scale, + ), + geo.loc.to_physical_precise_round(output_scale), + Relocate::Absolute, + )), + ), + CosmicMappedRenderElement::Window(elem) => { + Some(CosmicMappedRenderElement::TiledWindow( + RelocateRenderElement::from_element( + RescaleRenderElement::from_element( + CropRenderElement::from_element( + elem, + output_scale, + crop_rect.to_physical_precise_round(output_scale), + )?, + (0, 0).into(), + scale, + ), + geo.loc.to_physical_precise_round(output_scale), + Relocate::Absolute, + ), + )) + } + x => Some(x), + }) + .collect::>(); + + if focused == Some(mapped) { + if indicator_thickness > 0 { + let element = IndicatorShader::element( + renderer, + mapped.clone(), + geo, + indicator_thickness, + 1.0, + FOCUS_INDICATOR_COLOR, + ); + elements.insert(0, element.into()); + } + } + + elements + }) + .collect() + } else { + Vec::new() + } +} + +fn scale_to_center( + old_geo: &Rectangle, + new_geo: &Rectangle, +) -> (f64, Point) { + let scale_w = new_geo.size.w as f64 / old_geo.size.w as f64; + let scale_h = new_geo.size.h as f64 / old_geo.size.h as f64; + + if scale_w > scale_h { + ( + scale_h, + ( + ((new_geo.size.w as f64 - old_geo.size.w as f64 * scale_h) / 2.0).round() as i32, + 0, + ) + .into(), + ) + } else { + ( + scale_w, + ( + 0, + ((new_geo.size.h as f64 - old_geo.size.h as f64 * scale_w) / 2.0).round() as i32, + ) + .into(), + ) + } +} diff --git a/src/shell/workspace.rs b/src/shell/workspace.rs index 1ae1239f..b5383f5b 100644 --- a/src/shell/workspace.rs +++ b/src/shell/workspace.rs @@ -454,6 +454,7 @@ impl Workspace { override_redirect_windows: &[X11Surface], xwm_state: Option<&'a mut XWaylandState>, draw_focus_indicator: Option<&Seat>, + draw_groups: bool, indicator_thickness: u8, exclude_workspace_overview: bool, ) -> Result>, OutputNotMapped> @@ -604,7 +605,14 @@ impl Workspace { //tiling surfaces render_elements.extend( self.tiling_layer - .render_output::(renderer, output, focused.as_ref(), indicator_thickness)? + .render_output::( + renderer, + output, + focused.as_ref(), + layer_map.non_exclusive_zone(), + draw_groups, + indicator_thickness, + )? .into_iter() .map(WorkspaceRenderElement::from), );