shell: implement minimize

This commit is contained in:
Victoria Brekenfeld 2024-02-23 17:25:40 +01:00 committed by Victoria Brekenfeld
parent fffae1491d
commit 3eb7e5f82e
20 changed files with 1185 additions and 307 deletions

View file

@ -379,6 +379,16 @@ impl CosmicMapped {
window.is_activated(pending)
}
pub fn is_minimized(&self) -> bool {
self.active_window().is_minimized()
}
pub fn set_minimized(&self, minimized: bool) {
for (w, _) in self.windows() {
w.set_minimized(minimized);
}
}
pub fn pending_size(&self) -> Option<Size<i32, Logical>> {
match &self.element {
CosmicMappedInternal::Stack(s) => s.pending_size(),

View file

@ -725,11 +725,10 @@ impl Program for CosmicStackInternal {
} else if let Some(workspace) =
state.common.shell.space_for_mut(&mapped)
{
workspace
.element_geometry(&mapped)
.unwrap()
.loc
.to_global(&workspace.output)
let Some(elem_geo) = workspace.element_geometry(&mapped) else {
return;
};
elem_geo.loc.to_global(&workspace.output)
} else {
return;
};
@ -761,11 +760,10 @@ impl Program for CosmicStackInternal {
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 Some(elem_geo) = workspace.element_geometry(&mapped) else {
return;
};
let position = elem_geo.loc.to_global(&workspace.output);
let mut cursor = seat
.get_pointer()
.unwrap()

View file

@ -1,4 +1,7 @@
use std::time::Duration;
use std::{
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
use smithay::{
backend::renderer::{
@ -70,6 +73,9 @@ impl From<X11Surface> for CosmicSurface {
}
}
#[derive(Default)]
struct Minimized(AtomicBool);
pub const SSD_HEIGHT: i32 = 48;
pub const RESIZE_BORDER: i32 = 10;
@ -351,6 +357,45 @@ impl CosmicSurface {
}
}
pub fn is_minimized(&self) -> bool {
match self.0.underlying_surface() {
WindowSurface::Wayland(_) => self
.0
.user_data()
.get_or_insert_threadsafe(Minimized::default)
.0
.load(Ordering::SeqCst),
WindowSurface::X11(surface) => surface.is_minimized(),
}
}
pub fn set_minimized(&self, minimized: bool) {
match self.0.underlying_surface() {
WindowSurface::Wayland(_) => self
.0
.user_data()
.get_or_insert_threadsafe(Minimized::default)
.0
.store(minimized, Ordering::SeqCst),
WindowSurface::X11(surface) => {
let _ = surface.set_minimized(minimized);
}
}
}
pub fn set_suspended(&self, suspended: bool) {
match self.0.underlying_surface() {
WindowSurface::Wayland(window) => window.with_pending_state(|state| {
if suspended {
state.states.set(ToplevelState::Suspended);
} else {
state.states.unset(ToplevelState::Suspended);
}
}),
_ => {}
}
}
pub fn min_size(&self) -> Option<Size<i32, Logical>> {
match self.0.underlying_surface() {
WindowSurface::Wayland(toplevel) => {

View file

@ -290,7 +290,8 @@ impl Program for CosmicWindowInternal {
if let Some(mapped) =
state.common.shell.element_for_wl_surface(&surface).cloned()
{
state.common.shell.maximize_toggle(&mapped)
let seat = state.common.last_active_seat().clone();
state.common.shell.maximize_toggle(&mapped, &seat)
}
});
}
@ -315,11 +316,10 @@ impl Program for CosmicWindowInternal {
} else if let Some(workspace) =
state.common.shell.space_for_mut(&mapped)
{
workspace
.element_geometry(&mapped)
.unwrap()
.loc
.to_global(&workspace.output)
let Some(elem_geo) = workspace.element_geometry(&mapped) else {
return;
};
elem_geo.loc.to_global(&workspace.output)
} else {
return;
};

View file

@ -41,13 +41,13 @@ impl<'a> FocusStack<'a> {
pub fn last(&self) -> Option<&CosmicMapped> {
self.0
.as_ref()
.and_then(|set| set.iter().rev().find(|w| w.alive()))
.and_then(|set| set.iter().rev().find(|w| w.alive() && !w.is_minimized()))
}
pub fn iter(&self) -> impl Iterator<Item = &'_ CosmicMapped> {
self.0
.iter()
.flat_map(|set| set.iter().rev().filter(|w| w.alive()))
.flat_map(|set| set.iter().rev().filter(|w| w.alive() && !w.is_minimized()))
}
}
@ -63,11 +63,14 @@ impl<'a> FocusStackMut<'a> {
}
pub fn last(&self) -> Option<&CosmicMapped> {
self.0.iter().rev().find(|w| w.alive())
self.0.iter().rev().find(|w| w.alive() && !w.is_minimized())
}
pub fn iter(&self) -> impl Iterator<Item = &'_ CosmicMapped> {
self.0.iter().rev().filter(|w| w.alive())
self.0
.iter()
.rev()
.filter(|w| w.alive() && !w.is_minimized())
}
}
@ -96,44 +99,34 @@ impl ActiveFocus {
}
impl Shell {
pub fn append_focus_stack(
state: &mut State,
target: Option<&KeyboardFocusTarget>,
active_seat: &Seat<State>,
) {
pub fn append_focus_stack(state: &mut State, mapped: &CosmicMapped, active_seat: &Seat<State>) {
if mapped.is_minimized() {
return;
}
// update FocusStack and notify layouts about new focus (if any window)
let element = match target {
Some(KeyboardFocusTarget::Element(mapped)) => Some(mapped.clone()),
Some(KeyboardFocusTarget::Fullscreen(window)) => {
state.common.shell.element_for_surface(window).cloned()
}
_ => None,
let workspace = state.common.shell.space_for_mut(&mapped);
let workspace = if workspace.is_none() {
state
.common
.shell
.active_space_mut(&active_seat.active_output())
} else {
workspace.unwrap()
};
if let Some(mapped) = element {
let workspace = state.common.shell.space_for_mut(&mapped);
let workspace = if workspace.is_none() {
state
.common
.shell
.active_space_mut(&active_seat.active_output())
} else {
workspace.unwrap()
};
let mut focus_stack = workspace.focus_stack.get_mut(active_seat);
if Some(&mapped) != focus_stack.last() {
trace!(?mapped, "Focusing window.");
focus_stack.append(&mapped);
// also remove popup grabs, if we are switching focus
if let Some(mut popup_grab) = active_seat
.user_data()
.get::<PopupGrabData>()
.and_then(|x| x.take())
{
if !popup_grab.has_ended() {
popup_grab.ungrab(PopupUngrabStrategy::All);
}
let mut focus_stack = workspace.focus_stack.get_mut(active_seat);
if Some(mapped) != focus_stack.last() {
trace!(?mapped, "Focusing window.");
focus_stack.append(&mapped);
// also remove popup grabs, if we are switching focus
if let Some(mut popup_grab) = active_seat
.user_data()
.get::<PopupGrabData>()
.and_then(|x| x.take())
{
if !popup_grab.has_ended() {
popup_grab.ungrab(PopupUngrabStrategy::All);
}
}
}
@ -145,7 +138,20 @@ impl Shell {
active_seat: &Seat<State>,
serial: Option<Serial>,
) {
Self::append_focus_stack(state, target, active_seat);
let element = match target {
Some(KeyboardFocusTarget::Element(mapped)) => Some(mapped.clone()),
Some(KeyboardFocusTarget::Fullscreen(window)) => {
state.common.shell.element_for_surface(window).cloned()
}
_ => None,
};
if let Some(mapped) = element {
if mapped.is_minimized() {
return;
}
Self::append_focus_stack(state, &mapped, active_seat);
}
// update keyboard focus
if let Some(keyboard) = active_seat.get_keyboard() {
@ -360,7 +366,7 @@ fn focus_target_is_valid(
let has_fullscreen = workspace.get_fullscreen().is_some();
if is_sticky && !is_in_focus_stack {
Shell::append_focus_stack(state, Some(&KeyboardFocusTarget::Element(mapped)), seat);
Shell::append_focus_stack(state, &mapped, seat);
}
(is_sticky || is_in_focus_stack) && !has_fullscreen

View file

@ -194,8 +194,10 @@ pub fn window_items(
Some(
Item::new(fl!("window-menu-maximize"), move |handle| {
let mapped = maximize_clone.clone();
let _ =
handle.insert_idle(move |state| state.common.shell.maximize_toggle(&mapped));
let _ = handle.insert_idle(move |state| {
let seat = state.common.last_active_seat().clone();
state.common.shell.maximize_toggle(&mapped, &seat);
});
})
.shortcut(config.get_shortcut_for_action(&Action::Maximize))
.toggled(window.is_maximized(false)),

View file

@ -52,12 +52,116 @@ pub const ANIMATION_DURATION: Duration = Duration::from_millis(200);
pub struct FloatingLayout {
pub(crate) space: Space<CosmicMapped>,
spawn_order: Vec<CosmicMapped>,
tiling_animations: HashMap<CosmicMapped, (Instant, Rectangle<i32, Local>)>,
animations: HashMap<CosmicMapped, Animation>,
hovered_stack: Option<(CosmicMapped, Rectangle<i32, Local>)>,
dirty: AtomicBool,
pub theme: cosmic::Theme,
}
#[derive(Debug)]
enum Animation {
Tiled {
start: Instant,
previous_geometry: Rectangle<i32, Local>,
},
Minimize {
start: Instant,
previous_geometry: Rectangle<i32, Local>,
target_geometry: Rectangle<i32, Local>,
},
Unminimize {
start: Instant,
previous_geometry: Rectangle<i32, Local>,
target_geometry: Rectangle<i32, Local>,
},
}
impl Animation {
fn start(&self) -> &Instant {
match self {
Animation::Tiled { start, .. } => start,
Animation::Minimize { start, .. } => start,
Animation::Unminimize { start, .. } => start,
}
}
fn alpha(&self) -> f32 {
match self {
Animation::Tiled { .. } => 1.0,
Animation::Minimize { start, .. } => {
1.0 - (Instant::now()
.duration_since(*start)
.min(ANIMATION_DURATION)
.as_secs_f32()
/ ANIMATION_DURATION.as_secs_f32())
}
Animation::Unminimize { start, .. } => {
Instant::now()
.duration_since(*start)
.min(ANIMATION_DURATION)
.as_secs_f32()
/ ANIMATION_DURATION.as_secs_f32()
}
}
}
fn previous_geometry(&self) -> &Rectangle<i32, Local> {
match self {
Animation::Tiled {
previous_geometry, ..
} => previous_geometry,
Animation::Minimize {
previous_geometry, ..
} => previous_geometry,
Animation::Unminimize {
previous_geometry, ..
} => previous_geometry,
}
}
fn geometry(
&self,
output_geometry: Rectangle<i32, Logical>,
tiled_state: Option<&TiledCorners>,
) -> Rectangle<i32, Local> {
let target_rect = match self {
Animation::Minimize {
target_geometry, ..
}
| Animation::Unminimize {
target_geometry, ..
} => target_geometry.clone(),
Animation::Tiled {
previous_geometry, ..
} => {
if let Some(target_rect) =
tiled_state.map(|state| state.relative_geometry(output_geometry))
{
target_rect
} else {
previous_geometry.clone()
}
}
};
let previous_rect = self.previous_geometry().clone();
let start = *self.start();
let now = Instant::now();
let progress = now
.duration_since(start)
.min(ANIMATION_DURATION)
.as_secs_f64()
/ ANIMATION_DURATION.as_secs_f64();
ease(
EaseInOutCubic,
EaseRectangle(previous_rect),
EaseRectangle(target_rect),
progress,
)
.unwrap()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TiledCorners {
Top,
@ -220,8 +324,13 @@ impl FloatingLayout {
mapped.moved_since_mapped.store(true, Ordering::SeqCst);
self.tiling_animations
.insert(mapped.clone(), (Instant::now(), previous_geometry));
self.animations.insert(
mapped.clone(),
Animation::Tiled {
start: Instant::now(),
previous_geometry,
},
);
if mapped.floating_tiled.lock().unwrap().take().is_some() {
if let Some(state) = mapped.maximized_state.lock().unwrap().as_mut() {
if let Some(real_old_geo) = mapped.last_geometry.lock().unwrap().clone() {
@ -433,13 +542,55 @@ impl FloatingLayout {
.set_geometry(Rectangle::from_loc_and_size(position, win_geo.size).to_global(&output));
mapped.configure();
if let Some(previous_geo) = prev.or(already_mapped) {
self.tiling_animations
.insert(mapped.clone(), (Instant::now(), previous_geo));
if let Some(previous_geometry) = prev.or(already_mapped) {
self.animations.insert(
mapped.clone(),
Animation::Tiled {
start: Instant::now(),
previous_geometry,
},
);
}
self.space.map_element(mapped, position.as_logical(), false);
}
pub fn remap_minimized(
&mut self,
mapped: CosmicMapped,
from: Rectangle<i32, Local>,
position: Point<i32, Local>,
) {
let output = self.space.outputs().next().unwrap().clone();
let layers = layer_map_for_output(&output);
let geometry = layers.non_exclusive_zone().as_local();
mapped.set_bounds(geometry.size.as_logical());
let window_size = mapped.geometry().size;
if mapped.is_maximized(false) {
mapped.set_geometry(geometry.to_global(&output));
mapped.configure();
} else {
mapped.set_geometry(Rectangle::from_loc_and_size(
position.to_global(&output),
window_size.as_global(),
));
}
mapped.set_minimized(false);
self.space
.map_element(mapped.clone(), position.as_logical(), true);
let target_geometry = self.space.element_geometry(&mapped).unwrap().as_local();
self.animations.insert(
mapped,
Animation::Unminimize {
start: Instant::now(),
previous_geometry: from,
target_geometry,
},
);
}
pub fn unmap(&mut self, window: &CosmicMapped) -> bool {
if let Some(_) = window.floating_tiled.lock().unwrap().take() {
if let Some(last_size) = window.last_geometry.lock().unwrap().map(|geo| geo.size) {
@ -467,7 +618,7 @@ impl FloatingLayout {
}
}
let _ = self.tiling_animations.remove(window);
let _ = self.animations.remove(window);
let was_unmaped = self.space.elements().any(|e| e == window);
self.space.unmap_elem(&window);
@ -480,6 +631,34 @@ impl FloatingLayout {
was_unmaped
}
pub fn unmap_minimize(
&mut self,
window: &CosmicMapped,
to: Rectangle<i32, Local>,
) -> Option<(CosmicMapped, Point<i32, Local>)> {
let previous_geometry = self.space.element_geometry(window);
self.space.unmap_elem(&window);
if let Some(previous_geometry) = previous_geometry {
if let Some(pos) = self.spawn_order.iter().position(|w| w == window) {
self.spawn_order.truncate(pos);
}
window.moved_since_mapped.store(true, Ordering::SeqCst);
self.animations.insert(
window.clone(),
Animation::Minimize {
start: Instant::now(),
previous_geometry: previous_geometry.as_local(),
target_geometry: to,
},
);
window.set_minimized(true);
Some((window.clone(), previous_geometry.loc.as_local()))
} else {
None
}
}
pub fn drop_window(
&mut self,
window: CosmicMapped,
@ -772,30 +951,8 @@ impl FloatingLayout {
let output_geometry = layers.non_exclusive_zone();
std::mem::drop(layers);
let start_rectangle = if let Some((previous_start, previous_rect)) =
self.tiling_animations.remove(focused)
{
if let Some(target_rect) = tiled_state
.as_ref()
.map(|state| state.relative_geometry(output_geometry))
{
ease(
EaseInOutCubic,
EaseRectangle(previous_rect),
EaseRectangle(target_rect),
Instant::now()
.duration_since(previous_start)
.max(ANIMATION_DURATION)
.as_secs_f64()
/ ANIMATION_DURATION.as_secs_f64(),
)
.unwrap()
} else {
self.space
.element_geometry(focused)
.map(RectExt::as_local)
.unwrap()
}
let start_rectangle = if let Some(anim) = self.animations.remove(focused) {
anim.geometry(output_geometry, tiled_state.as_ref())
} else {
self.space
.element_geometry(focused)
@ -944,13 +1101,14 @@ impl FloatingLayout {
}
pub fn animations_going(&self) -> bool {
self.dirty.swap(false, Ordering::SeqCst) || !self.tiling_animations.is_empty()
self.dirty.swap(false, Ordering::SeqCst) || !self.animations.is_empty()
}
pub fn update_animation_state(&mut self) {
self.tiling_animations
.retain(|_, (start, _)| Instant::now().duration_since(*start) < ANIMATION_DURATION);
if self.tiling_animations.is_empty() {
let was_empty = self.animations.is_empty();
self.animations
.retain(|_, anim| Instant::now().duration_since(*anim.start()) < ANIMATION_DURATION);
if self.animations.is_empty() != was_empty {
self.dirty.store(true, Ordering::SeqCst);
}
}
@ -1000,12 +1158,18 @@ impl FloatingLayout {
let mut window_elements = Vec::new();
let mut popup_elements = Vec::new();
self.space.elements().rev().for_each(|elem| {
let mut geometry = self
.tiling_animations
for elem in self
.animations
.iter()
.filter(|(_, anim)| matches!(anim, Animation::Minimize { .. }))
.map(|(elem, _)| elem)
.chain(self.space.elements().rev())
{
let (mut geometry, alpha) = self
.animations
.get(elem)
.map(|(_, rect)| *rect)
.unwrap_or_else(|| self.space.element_geometry(elem).unwrap().as_local());
.map(|anim| (*anim.previous_geometry(), alpha * anim.alpha()))
.unwrap_or_else(|| (self.space.element_geometry(elem).unwrap().as_local(), alpha));
let render_location = geometry.loc - elem.geometry().loc.as_local();
let (mut w_elements, p_elements) = elem.split_render_elements(
@ -1017,30 +1181,12 @@ impl FloatingLayout {
alpha,
);
if let Some((start, original_geo)) = self.tiling_animations.get(elem) {
let target_rect = elem
.floating_tiled
.lock()
.unwrap()
.as_ref()
.map(|state| state.relative_geometry(output_geometry))
.or_else(|| {
elem.is_maximized(true)
.then_some(output_geometry.as_local())
})
.unwrap_or_else(|| self.space.element_geometry(elem).unwrap().as_local());
geometry = ease(
EaseInOutCubic,
EaseRectangle(original_geo.clone()),
EaseRectangle(target_rect),
Instant::now()
.duration_since(*start)
.min(ANIMATION_DURATION)
.as_millis() as f32
/ ANIMATION_DURATION.as_millis() as f32,
)
.unwrap();
if let Some(anim) = self.animations.get(elem) {
let original_geo = anim.previous_geometry();
geometry = anim.geometry(
output_geometry,
elem.floating_tiled.lock().unwrap().as_ref(),
);
let buffer_size = elem.geometry().size;
let scale = Scale {
@ -1142,7 +1288,7 @@ impl FloatingLayout {
window_elements.extend(w_elements);
popup_elements.extend(p_elements);
});
}
(window_elements, popup_elements)
}

View file

@ -141,6 +141,7 @@ pub enum Data {
Mapped {
mapped: CosmicMapped,
last_geometry: Rectangle<i32, Local>,
minimize_to: Option<Rectangle<i32, Local>>,
},
Placeholder {
last_geometry: Rectangle<i32, Local>,
@ -317,6 +318,14 @@ enum FocusedNodeData {
Window(CosmicMapped),
}
#[derive(Debug)]
pub struct MinimizedTilingState {
pub parent: id_tree::NodeId,
pub orientation: Orientation,
pub idx: usize,
pub sizes: Vec<i32>,
}
impl TilingLayout {
pub fn new(theme: cosmic::Theme, output: &Output) -> TilingLayout {
TilingLayout {
@ -347,11 +356,7 @@ impl TilingLayout {
.into_iter()
.flatten()
{
if let Data::Mapped {
mapped,
last_geometry: _,
} = node.data()
{
if let Data::Mapped { mapped, .. } = node.data() {
mapped.output_leave(&self.output);
mapped.output_enter(output, mapped.bbox());
}
@ -390,6 +395,75 @@ impl TilingLayout {
self.queue.push_tree(tree, ANIMATION_DURATION, blocker);
}
pub fn remap_minimized<'a>(
&mut self,
window: CosmicMapped,
from: Rectangle<i32, Local>,
tiling_state: Option<MinimizedTilingState>,
focus_stack: Option<impl Iterator<Item = &'a CosmicMapped> + 'a>,
) {
let gaps = self.gaps();
let mut tree = self.queue.trees.back().unwrap().0.copy_clone();
if let Some(MinimizedTilingState {
parent,
orientation,
idx,
mut sizes,
}) = tiling_state
{
if let Ok(node) = tree.get_mut(&parent) {
if let Data::Group {
orientation: current_orientation,
sizes: current_sizes,
..
} = node.data_mut()
{
if *current_orientation == orientation && sizes.len() == current_sizes.len() + 1
{
let previous_length: i32 = sizes.iter().copied().sum();
let new_length: i32 = current_sizes.iter().copied().sum();
if previous_length != new_length {
// rescale sizes
sizes.iter_mut().for_each(|len| {
*len = (((*len as f64) / (previous_length as f64))
* (new_length as f64))
.round() as i32;
});
let sum: i32 = sizes.iter().sum();
// fix rounding issues
if sum != new_length {
let diff = new_length - sum;
*sizes.last_mut().unwrap() += diff;
}
}
}
*current_sizes = sizes;
let new_node = Node::new(Data::Mapped {
mapped: window.clone(),
last_geometry: Rectangle::from_loc_and_size((0, 0), (100, 100)),
minimize_to: Some(from),
});
let new_id = tree
.insert(new_node, InsertBehavior::UnderNode(&parent))
.unwrap();
tree.make_nth_sibling(&new_id, idx).unwrap();
*window.tiling_node_id.lock().unwrap() = Some(new_id);
window.set_minimized(false);
let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps);
self.queue.push_tree(tree, ANIMATION_DURATION, blocker);
return;
}
}
}
// else add as new_window
self.map_internal(window, focus_stack, None);
}
fn map_to_tree<'a>(
mut tree: &mut Tree<Data>,
window: impl Into<CosmicMapped>,
@ -401,6 +475,7 @@ impl TilingLayout {
let new_window = Node::new(Data::Mapped {
mapped: window.clone(),
last_geometry: Rectangle::from_loc_and_size((0, 0), (100, 100)),
minimize_to: None,
});
let window_id = if let Some(direction) = direction {
@ -934,6 +1009,7 @@ impl TilingLayout {
.replace_data(Data::Mapped {
mapped,
last_geometry: geometry,
minimize_to: None,
});
}
(None, Some(other_surface)) => {
@ -1021,6 +1097,7 @@ impl TilingLayout {
.replace_data(Data::Mapped {
mapped,
last_geometry: geometry,
minimize_to: None,
});
}
(Some(this_surface), Some(other_surface)) => {
@ -1173,6 +1250,53 @@ impl TilingLayout {
Some(node_id)
}
pub fn unmap_minimize(
&mut self,
window: &CosmicMapped,
to: Rectangle<i32, Local>,
) -> Option<MinimizedTilingState> {
let node_id = window.tiling_node_id.lock().unwrap().clone()?;
let state = {
let tree = &self.queue.trees.back().unwrap().0;
tree.get(&node_id).unwrap().parent().and_then(|parent_id| {
let parent = tree.get(&parent_id).unwrap();
let idx = parent
.children()
.iter()
.position(|id| id == &node_id)
.unwrap();
if let Data::Group {
orientation, sizes, ..
} = parent.data()
{
Some(MinimizedTilingState {
parent: parent_id.clone(),
orientation: *orientation,
idx,
sizes: sizes.clone(),
})
} else {
None
}
})
};
if self.unmap_window_internal(window) {
let tree = &mut self
.queue
.trees
.get_mut(self.queue.trees.len() - 2)
.unwrap()
.0;
if let Data::Mapped { minimize_to, .. } = tree.get_mut(&node_id).unwrap().data_mut() {
*minimize_to = Some(to);
}
window.set_minimized(true);
}
state
}
fn unmap_window_internal(&mut self, mapped: &CosmicMapped) -> bool {
let tiling_node_id = mapped.tiling_node_id.lock().unwrap().as_ref().cloned();
let gaps = self.gaps();
@ -1297,6 +1421,7 @@ impl TilingLayout {
let new_node = Node::new(Data::Mapped {
mapped: mapped.clone(),
last_geometry: Rectangle::from_loc_and_size((0, 0), (100, 100)),
minimize_to: None,
});
let new_id = tree.insert(new_node, InsertBehavior::AsRoot).unwrap();
TilingLayout::new_group(&mut tree, &node_id, &new_id, orientation).unwrap();
@ -2070,6 +2195,7 @@ impl TilingLayout {
*data = Data::Mapped {
mapped: mapped.clone(),
last_geometry: geo,
minimize_to: None,
};
let blocker = TilingLayout::update_positions(&self.output, &mut tree, gaps);
@ -2406,6 +2532,7 @@ impl TilingLayout {
Node::new(Data::Mapped {
mapped: window.clone(),
last_geometry: Rectangle::from_loc_and_size((0, 0), (100, 100)),
minimize_to: None,
}),
InsertBehavior::UnderNode(group_id),
)
@ -2441,6 +2568,7 @@ impl TilingLayout {
Node::new(Data::Mapped {
mapped: window.clone(),
last_geometry: Rectangle::from_loc_and_size((0, 0), (100, 100)),
minimize_to: None,
}),
InsertBehavior::UnderNode(group_id),
)
@ -2462,6 +2590,7 @@ impl TilingLayout {
*data = Data::Mapped {
mapped: window.clone(),
last_geometry: geo,
minimize_to: None,
};
*window.tiling_node_id.lock().unwrap() = Some(node_id.clone());
window
@ -2472,6 +2601,7 @@ impl TilingLayout {
Node::new(Data::Mapped {
mapped: window.clone(),
last_geometry: Rectangle::from_loc_and_size((0, 0), (100, 100)),
minimize_to: None,
}),
InsertBehavior::UnderNode(&window_id),
)
@ -2916,6 +3046,7 @@ impl TilingLayout {
Data::Mapped {
mapped,
last_geometry,
..
},
)) => {
let test_point = (location.to_f64() - last_geometry.loc.to_f64()

View file

@ -50,7 +50,10 @@ use crate::{
state::client_should_see_privileged_protocols,
utils::prelude::*,
wayland::{
handlers::{xdg_activation::ActivationContext, xdg_shell::popup::get_popup_toplevel},
handlers::{
toplevel_management::ToplevelManagementExt, xdg_activation::ActivationContext,
xdg_shell::popup::get_popup_toplevel,
},
protocols::{
toplevel_info::ToplevelInfoState,
toplevel_management::{ManagementCapabilities, ToplevelManagementState},
@ -289,6 +292,12 @@ fn move_workspace_to_group(
toplevel_info_state.toplevel_enter_workspace(&surface, &workspace.handle);
}
}
for window in workspace.minimized_windows.iter() {
for (surface, _) in window.window.windows() {
toplevel_info_state.toplevel_leave_workspace(&surface, &old_workspace_handle);
toplevel_info_state.toplevel_enter_workspace(&surface, &workspace.handle);
}
}
workspace_state.remove_workspace(old_workspace_handle);
}
@ -311,6 +320,7 @@ fn merge_workspaces(
toplevel_info_state.toplevel_enter_workspace(&toplevel, &into.handle);
}
}
// TODO: merge minimized windows
into.tiling_layer.merge(workspace.tiling_layer);
into.floating_layer.merge(workspace.floating_layer);
workspace_state.remove_workspace(workspace.handle);
@ -433,7 +443,7 @@ impl WorkspaceSet {
if self
.workspaces
.last()
.map(|last| !last.pending_tokens.is_empty() || last.windows().next().is_some())
.map(|last| !last.is_empty())
.unwrap_or(true)
{
self.add_empty_workspace(state);
@ -443,10 +453,7 @@ impl WorkspaceSet {
let len = self.workspaces.len();
let mut keep = vec![true; len];
for (i, workspace) in self.workspaces.iter().enumerate() {
let has_windows =
!workspace.pending_tokens.is_empty() || workspace.windows().next().is_some();
if !has_windows && i != self.active && i != len - 1 {
if workspace.is_empty() && i != self.active && i != len - 1 {
state.remove_workspace(workspace.handle);
keep[i] = false;
}
@ -609,6 +616,22 @@ impl Workspaces {
workspace.refresh(xdg_activation_state);
new_set.workspaces.push(workspace);
}
for window in set.sticky_layer.mapped() {
for (surface, _) in window.windows() {
toplevel_info_state.toplevel_leave_output(&surface, output);
toplevel_info_state.toplevel_enter_output(&surface, &new_output);
}
}
new_set.sticky_layer.merge(set.sticky_layer);
for window in set.minimized_windows.iter() {
for (surface, _) in window.window.windows() {
toplevel_info_state.toplevel_leave_output(&surface, output);
toplevel_info_state.toplevel_enter_output(&surface, &new_output);
}
}
new_set.minimized_windows.extend(set.minimized_windows);
if self.mode == WorkspaceMode::OutputBound {
workspace_state.remove_workspace_group(set.group);
} else {
@ -776,10 +799,7 @@ impl Workspaces {
let mut active = self.sets[0].active;
let mut keep = vec![true; len];
for i in 0..len {
let has_windows = self.sets.values().any(|s| {
!s.workspaces[i].pending_tokens.is_empty()
|| s.workspaces[i].windows().next().is_some()
});
let has_windows = self.sets.values().any(|s| !s.workspaces[i].is_empty());
if !has_windows && i != active && i != len - 1 {
for workspace in self.sets.values().map(|s| &s.workspaces[i]) {
@ -1231,6 +1251,9 @@ impl Shell {
.find(|w| {
w.mapped()
.any(|e| e.has_surface(surface, WindowSurfaceType::ALL))
|| w.minimized_windows
.iter()
.any(|m| m.window.has_surface(surface, WindowSurfaceType::ALL))
})
.map(|w| (w.handle.clone(), w.output().clone())),
}
@ -1238,8 +1261,10 @@ impl Shell {
pub fn element_for_surface(&self, surface: &CosmicSurface) -> Option<&CosmicMapped> {
self.workspaces.sets.values().find_map(|set| {
set.sticky_layer
.mapped()
set.minimized_windows
.iter()
.map(|w| &w.window)
.chain(set.sticky_layer.mapped())
.find(|w| w.windows().any(|(s, _)| &s == surface))
.or_else(|| {
set.workspaces
@ -1251,8 +1276,10 @@ impl Shell {
pub fn element_for_wl_surface(&self, surface: &WlSurface) -> Option<&CosmicMapped> {
self.workspaces.sets.values().find_map(|set| {
set.sticky_layer
.mapped()
set.minimized_windows
.iter()
.map(|w| &w.window)
.chain(set.sticky_layer.mapped())
.find(|w| {
w.windows()
.any(|(s, _)| s.wl_surface().as_ref() == Some(surface))
@ -1267,8 +1294,10 @@ impl Shell {
pub fn element_for_x11_surface(&self, surface: &X11Surface) -> Option<&CosmicMapped> {
self.workspaces.sets.values().find_map(|set| {
set.sticky_layer
.mapped()
set.minimized_windows
.iter()
.map(|w| &w.window)
.chain(set.sticky_layer.mapped())
.find(|w| w.windows().any(|(s, _)| s.x11_surface() == Some(surface)))
.or_else(|| {
set.workspaces
@ -1279,15 +1308,23 @@ impl Shell {
}
pub fn space_for(&self, mapped: &CosmicMapped) -> Option<&Workspace> {
self.workspaces
.spaces()
.find(|workspace| workspace.mapped().any(|m| m == mapped))
self.workspaces.spaces().find(|workspace| {
workspace.mapped().any(|m| m == mapped)
|| workspace
.minimized_windows
.iter()
.any(|m| &m.window == mapped)
})
}
pub fn space_for_mut(&mut self, mapped: &CosmicMapped) -> Option<&mut Workspace> {
self.workspaces
.spaces_mut()
.find(|workspace| workspace.mapped().any(|m| m == mapped))
self.workspaces.spaces_mut().find(|workspace| {
workspace.mapped().any(|m| m == mapped)
|| workspace
.minimized_windows
.iter()
.any(|m| &m.window == mapped)
})
}
pub fn outputs(&self) -> impl DoubleEndedIterator<Item = &Output> {
@ -1731,7 +1768,13 @@ impl Shell {
}
if !parent_is_sticky && should_be_fullscreen {
workspace.fullscreen_request(&mapped.active_window(), None);
let from = state
.common
.shell
.toplevel_management_state
.minimize_rectangle(&output, &mapped.active_window());
workspace.fullscreen_request(&mapped.active_window(), None, from, &seat);
}
if parent_is_sticky {
@ -1753,11 +1796,7 @@ impl Shell {
None,
);
} else if workspace_empty || was_activated || should_be_fullscreen {
Shell::append_focus_stack(
state,
Some(&KeyboardFocusTarget::from(mapped.clone())),
&seat,
);
Shell::append_focus_stack(state, &mapped, &seat);
state.common.shell.set_urgent(&workspace_handle);
}
@ -1891,6 +1930,7 @@ impl Shell {
None
};
let any_seat = seat.unwrap_or(state.common.last_active_seat()).clone();
let mut to_workspace = state
.common
.shell
@ -1938,7 +1978,13 @@ impl Shell {
}
}
to_workspace.fullscreen_request(&mapped.active_window(), f.previously);
let from = state
.common
.shell
.toplevel_management_state
.minimize_rectangle(&to_output, &mapped.active_window());
to_workspace.fullscreen_request(&mapped.active_window(), f.previously, from, &any_seat);
to_workspace
.fullscreen
.as_ref()
@ -2027,19 +2073,19 @@ impl Shell {
pub fn update_reactive_popups(&self, mapped: &CosmicMapped) {
if let Some(workspace) = self.space_for(mapped) {
let element_loc = workspace
if let Some(element_loc) = workspace
.element_geometry(mapped)
.unwrap()
.loc
.to_global(&workspace.output);
for (window, offset) in mapped.windows() {
if let Some(toplevel) = window.0.toplevel() {
let window_geo_offset = window.geometry().loc.as_global();
update_reactive_popups(
toplevel,
element_loc + offset.as_global() + window_geo_offset,
self.outputs(),
);
.map(|geo| geo.loc.to_global(&workspace.output))
{
for (window, offset) in mapped.windows() {
if let Some(toplevel) = window.0.toplevel() {
let window_geo_offset = window.geometry().loc.as_global();
update_reactive_popups(
toplevel,
element_loc + offset.as_global() + window_geo_offset,
self.outputs(),
);
}
}
}
}
@ -2089,10 +2135,12 @@ impl Shell {
} else if let Some(workspace) = state.common.shell.space_for_mut(&mapped) {
let output = seat.active_output();
let global_position = (workspace.element_geometry(&mapped).unwrap().loc
+ relative_loc.as_local()
+ location.as_local())
.to_global(&output);
let Some(elem_geo) = workspace.element_geometry(&mapped) else {
return;
};
let global_position =
(elem_geo.loc + relative_loc.as_local() + location.as_local())
.to_global(&output);
let is_tiled = workspace.is_tiled(&mapped);
let edge = if is_tiled {
mapped
@ -2177,6 +2225,10 @@ impl Shell {
if let Some(mut old_mapped) =
state.common.shell.element_for_wl_surface(surface).cloned()
{
if old_mapped.is_minimized() {
return;
}
let seats = state.common.seats().cloned().collect::<Vec<_>>();
for workspace in state.common.shell.workspaces.spaces_mut() {
for seat in seats.iter() {
@ -2222,11 +2274,10 @@ impl Shell {
let _ = workspace.remove_fullscreen(); // We are moving this window, we don't need to send it back to it's original workspace
}
let mut initial_window_location = workspace
.element_geometry(&old_mapped)
.unwrap()
.loc
.to_global(&output);
let Some(elem_geo) = workspace.element_geometry(&old_mapped) else {
return;
};
let mut initial_window_location = elem_geo.loc.to_global(&output);
if mapped.maximized_state.lock().unwrap().is_some() {
// If surface is maximized then unmaximize it
@ -2616,19 +2667,91 @@ impl Shell {
}
}
pub fn maximize_toggle(&mut self, window: &CosmicMapped) {
pub fn maximize_toggle(&mut self, window: &CosmicMapped, seat: &Seat<State>) {
if window.is_maximized(true) {
self.unmaximize_request(window);
} else {
self.maximize_request(window);
self.maximize_request(window, seat);
}
}
pub fn minimize_request(&mut self, mapped: &CosmicMapped) {}
pub fn minimize_request(&mut self, mapped: &CosmicMapped) {
if let Some(set) = self
.workspaces
.sets
.values_mut()
.find(|set| set.sticky_layer.mapped().any(|m| m == mapped))
{
let to = self
.toplevel_management_state
.minimize_rectangle(&set.output, &mapped.active_window());
let (window, position) = set.sticky_layer.unmap_minimize(mapped, to).unwrap();
set.minimized_windows.push(MinimizedWindow {
window,
previous_state: MinimizedState::Sticky { position },
output_geo: set.output.geometry(),
fullscreen: None,
});
} else if let Some(workspace) = self.workspaces.sets.values_mut().find_map(|set| {
set.workspaces
.iter_mut()
.find(|workspace| workspace.mapped().any(|m| m == mapped))
}) {
let to = self
.toplevel_management_state
.minimize_rectangle(workspace.output(), &mapped.active_window());
if let Some(minimized) = workspace.minimize(&mapped, to) {
workspace.minimized_windows.push(minimized);
}
}
}
pub fn unminimize_request(&mut self, mapped: &CosmicMapped) {}
pub fn unminimize_request(&mut self, mapped: &CosmicMapped, seat: &Seat<State>) {
if let Some((set, window)) = self.workspaces.sets.values_mut().find_map(|set| {
set.minimized_windows
.iter()
.position(|m| &m.window == mapped)
.map(|i| set.minimized_windows.swap_remove(i))
.map(|window| (set, window))
}) {
let from = self
.toplevel_management_state
.minimize_rectangle(&set.output, &mapped.active_window());
pub fn maximize_request(&mut self, mapped: &CosmicMapped) {
if let MinimizedState::Sticky { mut position } = window.previous_state {
let current_output_size = set.output.geometry().size.as_logical();
if current_output_size != window.output_geo.size.as_logical() {
position = Point::from((
(position.x as f64 / window.output_geo.size.w as f64
* current_output_size.w as f64)
.floor() as i32,
(position.y as f64 / window.output_geo.size.h as f64
* current_output_size.h as f64)
.floor() as i32,
))
};
set.sticky_layer
.remap_minimized(window.window, from, position);
} else {
unreachable!("None sticky window in WorkspaceSet minimized_windows");
}
} else if let Some((workspace, window)) = self.workspaces.spaces_mut().find_map(|w| {
w.minimized_windows
.iter()
.position(|m| &m.window == mapped)
.map(|i| w.minimized_windows.swap_remove(i))
.map(|window| (w, window))
}) {
let from = self
.toplevel_management_state
.minimize_rectangle(workspace.output(), &mapped.active_window());
workspace.unminimize(window, from, seat);
}
}
pub fn maximize_request(&mut self, mapped: &CosmicMapped, seat: &Seat<State>) {
self.unminimize_request(mapped, seat);
let (original_layer, floating_layer, original_geometry) = if let Some(set) = self
.workspaces
.sets
@ -2661,22 +2784,29 @@ impl Shell {
}
pub fn unmaximize_request(&mut self, mapped: &CosmicMapped) -> Option<Size<i32, Logical>> {
if let Some(set) = self
.workspaces
.sets
.values_mut()
.find(|set| set.sticky_layer.mapped().any(|m| m == mapped))
{
if let Some(set) = self.workspaces.sets.values_mut().find(|set| {
set.sticky_layer.mapped().any(|m| m == mapped)
|| set.minimized_windows.iter().any(|m| &m.window == mapped)
}) {
let mut state = mapped.maximized_state.lock().unwrap();
if let Some(state) = state.take() {
assert_eq!(state.original_layer, ManagedLayer::Sticky);
mapped.set_maximized(false);
set.sticky_layer.map_internal(
mapped.clone(),
Some(state.original_geometry.loc),
Some(state.original_geometry.size.as_logical()),
None,
);
if let Some(minimized) = set
.minimized_windows
.iter_mut()
.find(|m| &m.window == mapped)
{
minimized.unmaximize(state.original_geometry);
} else {
mapped.set_maximized(false);
set.sticky_layer.map_internal(
mapped.clone(),
Some(state.original_geometry.loc),
Some(state.original_geometry.size.as_logical()),
None,
);
}
Some(state.original_geometry.size.as_logical())
} else {
None
@ -2875,7 +3005,9 @@ impl Shell {
} else {
ManagedLayer::Floating
};
let geometry = workspace.element_geometry(mapped).unwrap();
let Some(geometry) = workspace.element_geometry(mapped) else {
return;
};
workspace.unmap(mapped);
*mapped.previous_layer.lock().unwrap() = Some(previous_layer);

View file

@ -34,7 +34,7 @@ use smithay::{
glow::{GlowFrame, GlowRenderer},
ImportAll, ImportMem, Renderer,
},
desktop::{layer_map_for_output, space::SpaceElement},
desktop::{layer_map_for_output, space::SpaceElement, WindowSurfaceType},
input::Seat,
output::Output,
reexports::wayland_server::{protocol::wl_surface::WlSurface, Client, Resource},
@ -66,7 +66,7 @@ use super::{
FocusStack, FocusStackMut,
},
grabs::ResizeEdge,
layout::tiling::{Data, NodeDesc},
layout::tiling::{Data, MinimizedTilingState, NodeDesc},
CosmicMappedRenderElement, CosmicSurface, ResizeDirection, ResizeMode,
};
@ -94,10 +94,46 @@ pub struct Workspace {
#[derive(Debug)]
pub struct MinimizedWindow {
pub window: CosmicMapped,
pub previous_layer: ManagedLayer,
pub was_fullscreen: Option<FullscreenSurface>,
pub was_maximized: bool,
pub tiling_state: Option<(id_tree::NodeId, Rectangle<i32, Logical>)>,
pub previous_state: MinimizedState,
pub fullscreen: Option<FullscreenSurface>,
pub output_geo: Rectangle<i32, Global>,
}
#[derive(Debug)]
pub enum MinimizedState {
Sticky {
position: Point<i32, Local>,
},
Floating {
position: Point<i32, Local>,
},
Tiling {
tiling_state: Option<MinimizedTilingState>,
was_maximized: bool,
},
}
impl MinimizedWindow {
pub(super) fn unmaximize(&mut self, original_geometry: Rectangle<i32, Local>) {
self.window.set_maximized(false);
self.window.configure();
match &mut self.previous_state {
MinimizedState::Sticky { position } | MinimizedState::Floating { position } => {
*position = original_geometry.loc;
}
MinimizedState::Tiling { was_maximized, .. } => {
*was_maximized = false;
}
}
}
fn unfullscreen(&mut self) -> Option<(ManagedLayer, WorkspaceHandle)> {
let fullscreen = self.fullscreen.take()?;
self.window.set_fullscreen(false);
self.window.set_geometry(fullscreen.original_geometry);
fullscreen.previously
}
}
#[derive(Debug, Clone)]
@ -338,6 +374,18 @@ impl Workspace {
.0
.on_commit();
}
if let Some(mapped) = self.minimized_windows.iter().find_map(|m| {
m.window
.has_surface(surface, WindowSurfaceType::TOPLEVEL)
.then_some(&m.window)
}) {
mapped
.windows()
.find(|(w, _)| w.wl_surface().as_ref() == Some(surface))
.unwrap()
.0
.on_commit();
}
}
pub fn output(&self) -> &Output {
@ -357,6 +405,12 @@ impl Workspace {
toplevel_info.toplevel_enter_output(&surface, output);
}
}
for window in self.minimized_windows.iter() {
for (surface, _) in window.window.windows() {
toplevel_info.toplevel_leave_output(&surface, &self.output);
toplevel_info.toplevel_enter_output(&surface, output);
}
}
let output_name = output.name();
if let Some(pos) = self
.output_stack
@ -375,7 +429,7 @@ impl Workspace {
}
pub fn unmap(&mut self, mapped: &CosmicMapped) -> Option<ManagedState> {
let was_fullscreen = self
let mut was_fullscreen = self
.fullscreen
.as_ref()
.filter(|f| f.ended_at.is_none())
@ -388,11 +442,27 @@ impl Workspace {
let _ = self.unmaximize_request(mapped);
}
let was_floating = self.floating_layer.unmap(&mapped);
let was_tiling = self.tiling_layer.unmap(&mapped);
let mut was_floating = self.floating_layer.unmap(&mapped);
let mut was_tiling = self.tiling_layer.unmap(&mapped);
if was_floating || was_tiling {
assert!(was_floating != was_tiling);
}
if let Some(pos) = self
.minimized_windows
.iter()
.position(|m| &m.window == mapped)
{
let state = self.minimized_windows.remove(pos);
match state.previous_state {
MinimizedState::Sticky { .. } | MinimizedState::Floating { .. } => {
was_floating = true;
}
MinimizedState::Tiling { .. } => {
was_tiling = true;
}
}
was_fullscreen = state.fullscreen;
}
self.focus_stack
.0
@ -417,6 +487,7 @@ impl Workspace {
self.floating_layer
.mapped()
.chain(self.tiling_layer.mapped().map(|(w, _)| w))
.chain(self.minimized_windows.iter().map(|w| &w.window))
.find(|e| e.windows().any(|(w, _)| &w == surface))
}
@ -424,6 +495,7 @@ impl Workspace {
self.floating_layer
.mapped()
.chain(self.tiling_layer.mapped().map(|(w, _)| w))
.chain(self.minimized_windows.iter().map(|w| &w.window))
.find(|e| {
e.windows()
.any(|(w, _)| w.wl_surface().as_ref() == Some(surface))
@ -434,6 +506,7 @@ impl Workspace {
self.floating_layer
.mapped()
.chain(self.tiling_layer.mapped().map(|(w, _)| w))
.chain(self.minimized_windows.iter().map(|w| &w.window))
.find(|e| e.windows().any(|(w, _)| w.x11_surface() == Some(surface)))
}
@ -463,29 +536,38 @@ impl Workspace {
pub fn unmaximize_request(&mut self, elem: &CosmicMapped) -> Option<Size<i32, Logical>> {
let mut state = elem.maximized_state.lock().unwrap();
if let Some(state) = state.take() {
match state.original_layer {
ManagedLayer::Tiling if self.tiling_enabled => {
// should still be mapped in tiling
self.floating_layer.unmap(&elem);
elem.output_enter(&self.output, elem.bbox());
elem.set_maximized(false);
elem.set_geometry(state.original_geometry.to_global(&self.output));
elem.configure();
self.tiling_layer.recalculate();
self.tiling_layer
.element_geometry(&elem)
.map(|geo| geo.size.as_logical())
}
ManagedLayer::Sticky => unreachable!(),
_ => {
elem.set_maximized(false);
self.floating_layer.map_internal(
elem.clone(),
Some(state.original_geometry.loc),
Some(state.original_geometry.size.as_logical()),
None,
);
Some(state.original_geometry.size.as_logical())
if let Some(minimized) = self
.minimized_windows
.iter_mut()
.find(|m| &m.window == elem)
{
minimized.unmaximize(state.original_geometry);
Some(state.original_geometry.size.as_logical())
} else {
match state.original_layer {
ManagedLayer::Tiling if self.tiling_enabled => {
// should still be mapped in tiling
self.floating_layer.unmap(&elem);
elem.output_enter(&self.output, elem.bbox());
elem.set_maximized(false);
elem.set_geometry(state.original_geometry.to_global(&self.output));
elem.configure();
self.tiling_layer.recalculate();
self.tiling_layer
.element_geometry(&elem)
.map(|geo| geo.size.as_logical())
}
ManagedLayer::Sticky => unreachable!(),
_ => {
elem.set_maximized(false);
self.floating_layer.map_internal(
elem.clone(),
Some(state.original_geometry.loc),
Some(state.original_geometry.size.as_logical()),
None,
);
Some(state.original_geometry.size.as_logical())
}
}
}
} else {
@ -493,10 +575,156 @@ impl Workspace {
}
}
pub fn minimize(
&mut self,
elem: &CosmicMapped,
to: Rectangle<i32, Local>,
) -> Option<MinimizedWindow> {
let fullscreen = if self
.get_fullscreen()
.is_some_and(|s| elem.windows().any(|(w, _)| *s == w))
{
let fullscreen_state = self.fullscreen.clone().unwrap();
{
let f = self.fullscreen.as_mut().unwrap();
f.ended_at = Some(
Instant::now()
- (FULLSCREEN_ANIMATION_DURATION
- f.start_at
.take()
.map(|earlier| {
Instant::now()
.duration_since(earlier)
.min(FULLSCREEN_ANIMATION_DURATION)
})
.unwrap_or(FULLSCREEN_ANIMATION_DURATION)),
);
}
Some(fullscreen_state)
} else {
None
};
if self.is_tiled(elem) {
let was_maximized = self.floating_layer.unmap(&elem);
let tiling_state = self.tiling_layer.unmap_minimize(elem, to);
Some(MinimizedWindow {
window: elem.clone(),
previous_state: MinimizedState::Tiling {
tiling_state,
was_maximized,
},
output_geo: self.output.geometry(),
fullscreen,
})
} else {
self.floating_layer
.unmap_minimize(elem, to)
.map(|(window, position)| MinimizedWindow {
window,
previous_state: MinimizedState::Floating { position },
output_geo: self.output.geometry(),
fullscreen,
})
}
}
pub fn unminimize(
&mut self,
window: MinimizedWindow,
from: Rectangle<i32, Local>,
seat: &Seat<State>,
) -> Option<(CosmicMapped, ManagedLayer, WorkspaceHandle)> {
match window.previous_state {
MinimizedState::Floating { mut position } => {
let current_output_size = self.output.geometry().size.as_logical();
if current_output_size != window.output_geo.size.as_logical() {
position = Point::from((
(position.x as f64 / window.output_geo.size.w as f64
* current_output_size.w as f64)
.floor() as i32,
(position.y as f64 / window.output_geo.size.h as f64
* current_output_size.h as f64)
.floor() as i32,
))
};
self.floating_layer
.remap_minimized(window.window, from, position);
}
MinimizedState::Sticky { .. } => unreachable!(),
MinimizedState::Tiling {
tiling_state,
was_maximized,
} => {
if self.tiling_enabled {
let focus_stack = self.focus_stack.get(seat);
self.tiling_layer.remap_minimized(
window.window.clone(),
from,
tiling_state,
Some(focus_stack.iter()),
);
if was_maximized {
let previous_geometry =
self.tiling_layer.element_geometry(&window.window).unwrap();
self.floating_layer
.map_maximized(window.window, previous_geometry);
}
} else {
if was_maximized {
self.floating_layer.map_maximized(window.window, from);
} else {
self.floating_layer.map(window.window.clone(), None);
// get the right animation
let geometry = self
.floating_layer
.element_geometry(&window.window)
.unwrap();
self.floating_layer.remap_minimized(
window.window.clone(),
from,
geometry.loc,
);
}
}
}
}
if let Some(mut fullscreen) = window.fullscreen {
let old_fullscreen = self.remove_fullscreen();
fullscreen.start_at = Some(Instant::now());
let geo = self.output.geometry();
if geo != window.output_geo {
fullscreen.animation_signal = if let Some(surface) = fullscreen.surface.wl_surface()
{
let signal = Arc::new(AtomicBool::new(false));
add_blocker(
&surface,
FullscreenBlocker {
signal: signal.clone(),
},
);
Some(signal)
} else {
None
};
fullscreen.surface.set_geometry(geo);
fullscreen.surface.send_configure();
}
self.fullscreen = Some(fullscreen);
old_fullscreen
} else {
None
}
}
pub fn fullscreen_request(
&mut self,
window: &CosmicSurface,
previously: Option<(ManagedLayer, WorkspaceHandle)>,
from: Rectangle<i32, Local>,
seat: &Seat<State>,
) {
if self
.fullscreen
@ -507,6 +735,15 @@ impl Workspace {
return;
}
if let Some(pos) = self
.minimized_windows
.iter()
.position(|m| m.window.windows().any(|(w, _)| &w == window))
{
let minimized = self.minimized_windows.remove(pos);
let _ = self.unminimize(minimized, from, seat);
}
window.set_fullscreen(true);
let geo = self.output.geometry();
let original_geometry = window.geometry().as_global();
@ -540,7 +777,13 @@ impl Workspace {
&mut self,
window: &CosmicSurface,
) -> Option<(ManagedLayer, WorkspaceHandle)> {
if let Some(f) = self
if let Some(minimized) = self
.minimized_windows
.iter_mut()
.find(|m| m.fullscreen.as_ref().is_some_and(|f| &f.surface == window))
{
minimized.unfullscreen()
} else if let Some(f) = self
.fullscreen
.as_mut()
.filter(|f| &f.surface == window && f.ended_at.is_none())
@ -711,24 +954,41 @@ impl Workspace {
self.floating_layer.space.outputs()
}
pub fn windows(&self) -> impl Iterator<Item = CosmicSurface> + '_ {
self.floating_layer
.windows()
.chain(self.tiling_layer.windows().map(|(w, _)| w))
pub fn is_empty(&self) -> bool {
self.floating_layer.mapped().next().is_none()
&& self.tiling_layer.mapped().next().is_none()
&& self.minimized_windows.is_empty()
&& self.pending_tokens.is_empty()
}
pub fn is_fullscreen(&self, mapped: &CosmicMapped) -> bool {
self.fullscreen
.as_ref()
.is_some_and(|f| f.surface == mapped.active_window())
|| self
.minimized_windows
.iter()
.any(|m| &m.window == mapped && m.fullscreen.is_some())
}
pub fn is_floating(&self, mapped: &CosmicMapped) -> bool {
!self.is_fullscreen(mapped) && self.floating_layer.mapped().any(|m| m == mapped)
!self.is_fullscreen(mapped)
&& (self.floating_layer.mapped().any(|m| m == mapped)
|| self.minimized_windows.iter().any(|m| {
&m.window == mapped
&& matches!(
m.previous_state,
MinimizedState::Floating { .. } | MinimizedState::Sticky { .. }
)
}))
}
pub fn is_tiled(&self, mapped: &CosmicMapped) -> bool {
!self.is_fullscreen(mapped) && self.tiling_layer.mapped().any(|(m, _)| m == mapped)
!self.is_fullscreen(mapped)
&& (self.tiling_layer.mapped().any(|(m, _)| m == mapped)
|| self.minimized_windows.iter().any(|m| {
&m.window == mapped && matches!(m.previous_state, MinimizedState::Tiling { .. })
}))
}
pub fn node_desc(&self, focus: KeyboardFocusTarget) -> Option<NodeDesc> {