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

@ -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()