mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-13 12:23:38 +01:00
feat(animation): pending workspace transitions
Let a workspace that is being switched say which of its windows are going to slide out of the monitor and which are going to slide in, so that hiding and showing them is left to the animations that do that. A window with a pending exit is not hidden, and one that is to slide in is not shown, by the usual paths. It is the animation that takes it over, and it stays held from then on until the animation shows it, however many times the layout of its workspace is done meanwhile: taking what is pending only takes what was asked for. Whatever is still pending when the switch is over, and has no animation, is hidden or shown at once, so nothing can be left behind, and what is pending expires after a couple of seconds as well. The border of a window that is sliding in cannot be cut off at the edge of the monitor as its picture is, so it can be held: hidden, and not moved or shown by anything that happens to the window, until it is let go of.
This commit is contained in:
1 parent
38e4f04900
commit
794a0bcd82
5 files changed
+540
-8
No files matched your search
@@ -8,6 +8,11 @@
|
||||
|
||||
use crate::core::OperationDirection;
|
||||
use crate::core::Rect;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
/// Which side of the monitor the workspace that is entered comes in from. The
|
||||
/// workspace that is left goes out on the other side.
|
||||
@@ -123,6 +128,222 @@ pub fn clip_thumbnail(dest: Rect, source: (i32, i32), clip: Rect) -> Option<(Rec
|
||||
))
|
||||
}
|
||||
|
||||
/// What a window is about to be made to do by a workspace that is being
|
||||
/// switched, which it is not to be made to do any other way meanwhile
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Pending {
|
||||
/// Slide out of the monitor, and be hidden when its ghost is up
|
||||
Exit,
|
||||
/// Slide into the monitor, and be shown when it has got there
|
||||
Enter {
|
||||
direction: SlideDirection,
|
||||
clip: Rect,
|
||||
},
|
||||
/// Sliding into the monitor, which has been taken over by its animation:
|
||||
/// the window is still not to be shown by anything else, as a layout of the
|
||||
/// workspace that is done meanwhile would, until the animation shows it
|
||||
Entering,
|
||||
}
|
||||
|
||||
/// How long something that is pending can be: a transition that does not get
|
||||
/// round to what it is waiting to do must not leave a window hidden, or
|
||||
/// shown, because of it, so what is pending for longer than this is not.
|
||||
const PENDING_LIFETIME: Duration = Duration::from_secs(2);
|
||||
|
||||
struct Entry {
|
||||
pending: Pending,
|
||||
since: Instant,
|
||||
/// Whether an animation that is to do it has been made, which is not
|
||||
/// finished by `finish_pending`: the animation does it when it starts
|
||||
claimed: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Registry {
|
||||
entries: HashMap<isize, Entry>,
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
fn expect(&mut self, hwnd: isize, pending: Pending, now: Instant) {
|
||||
self.entries.insert(
|
||||
hwnd,
|
||||
Entry {
|
||||
pending,
|
||||
since: now,
|
||||
claimed: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn claim(&mut self, hwnd: isize) {
|
||||
if let Some(entry) = self.entries.get_mut(&hwnd) {
|
||||
entry.claimed = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn current(&self, hwnd: isize, now: Instant) -> Option<Pending> {
|
||||
self.entries
|
||||
.get(&hwnd)
|
||||
.filter(|entry| now.saturating_duration_since(entry.since) < PENDING_LIFETIME)
|
||||
.map(|entry| entry.pending)
|
||||
}
|
||||
|
||||
/// Take what is pending for the window if it is something `wanted`, and
|
||||
/// nothing otherwise: what is pending for it is anyone else's to take, and
|
||||
/// is left as it is. What has been pending for too long is not pending, and
|
||||
/// is got rid of whoever asks.
|
||||
fn take_if(
|
||||
&mut self,
|
||||
hwnd: isize,
|
||||
now: Instant,
|
||||
wanted: impl FnOnce(&Pending) -> bool,
|
||||
) -> Option<Pending> {
|
||||
let Some(pending) = self.current(hwnd, now) else {
|
||||
self.entries.remove(&hwnd);
|
||||
return None;
|
||||
};
|
||||
|
||||
if !wanted(&pending) {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.entries.remove(&hwnd);
|
||||
Some(pending)
|
||||
}
|
||||
|
||||
/// Everything that is pending and has no animation to do it, which is no
|
||||
/// longer pending. What is claimed stays until the animation takes it, or
|
||||
/// it has been pending for too long.
|
||||
fn drain(&mut self, now: Instant) -> Vec<(isize, Pending)> {
|
||||
let mut unclaimed = vec![];
|
||||
|
||||
self.entries.retain(|hwnd, entry| {
|
||||
if entry.claimed {
|
||||
return now.saturating_duration_since(entry.since) < PENDING_LIFETIME;
|
||||
}
|
||||
|
||||
unclaimed.push((*hwnd, entry.pending));
|
||||
false
|
||||
});
|
||||
|
||||
unclaimed
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref PENDING: Mutex<Registry> = Mutex::new(Registry::default());
|
||||
}
|
||||
|
||||
/// The window is to slide out of the monitor: hiding it is left to the
|
||||
/// animation, which hides it when its ghost is up
|
||||
pub fn expect_exit(hwnd: isize) {
|
||||
PENDING.lock().expect(hwnd, Pending::Exit, Instant::now());
|
||||
}
|
||||
|
||||
/// The window is to slide into the monitor: showing it is left to the
|
||||
/// animation, which shows it when it has got there
|
||||
pub fn expect_enter(hwnd: isize, direction: SlideDirection, clip: Rect) {
|
||||
PENDING
|
||||
.lock()
|
||||
.expect(hwnd, Pending::Enter { direction, clip }, Instant::now());
|
||||
}
|
||||
|
||||
/// An animation has been made that is to do what is pending for the window
|
||||
pub fn claim(hwnd: isize) {
|
||||
PENDING.lock().claim(hwnd);
|
||||
}
|
||||
|
||||
/// Whether hiding the window is left to the animation that is to slide it out
|
||||
pub fn is_exiting(hwnd: isize) -> bool {
|
||||
matches!(
|
||||
PENDING.lock().current(hwnd, Instant::now()),
|
||||
Some(Pending::Exit)
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether showing the window is left to the animation that is to slide it in
|
||||
pub fn is_entering(hwnd: isize) -> bool {
|
||||
matches!(
|
||||
PENDING.lock().current(hwnd, Instant::now()),
|
||||
Some(Pending::Enter { .. } | Pending::Entering)
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether the window is on its way into the monitor, with its animation
|
||||
pub fn is_sliding_in(hwnd: isize) -> bool {
|
||||
matches!(
|
||||
PENDING.lock().current(hwnd, Instant::now()),
|
||||
Some(Pending::Entering)
|
||||
)
|
||||
}
|
||||
|
||||
/// The animation takes over hiding the window: say whether it was to
|
||||
pub fn take_exit(hwnd: isize) -> bool {
|
||||
PENDING
|
||||
.lock()
|
||||
.take_if(hwnd, Instant::now(), |pending| {
|
||||
matches!(pending, Pending::Exit)
|
||||
})
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// The animation takes over showing the window: which way it is to slide in
|
||||
/// from, and what it is not to be seen outside of
|
||||
///
|
||||
/// Showing the window is left to the animation from here on, until it calls
|
||||
/// `finish_enter`: a window is not shown by the layout of its workspace that
|
||||
/// is done while it slides in, which is done for every switch of workspaces
|
||||
/// when the focus change that follows it is handled.
|
||||
pub fn take_enter(hwnd: isize) -> Option<(SlideDirection, Rect)> {
|
||||
let now = Instant::now();
|
||||
let mut pending = PENDING.lock();
|
||||
|
||||
// only a window that is yet to slide in is taken: one that is already doing
|
||||
// so is left sliding in, however many times the layout that follows the
|
||||
// switch asks
|
||||
match pending.take_if(hwnd, now, |pending| {
|
||||
matches!(pending, Pending::Enter { .. })
|
||||
}) {
|
||||
Some(Pending::Enter { direction, clip }) => {
|
||||
pending.expect(hwnd, Pending::Entering, now);
|
||||
pending.claim(hwnd);
|
||||
Some((direction, clip))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The animation of a window that slides in is about to show it
|
||||
pub fn finish_enter(hwnd: isize) {
|
||||
PENDING.lock().take_if(hwnd, Instant::now(), |pending| {
|
||||
matches!(pending, Pending::Enter { .. } | Pending::Entering)
|
||||
});
|
||||
}
|
||||
|
||||
/// Do now what was left to no animation: hide the windows that were to slide
|
||||
/// out and show the ones that were to slide in
|
||||
pub fn finish_pending() {
|
||||
// out of the registry first, or what is done would be left to the animation
|
||||
let pending = PENDING.lock().drain(Instant::now());
|
||||
|
||||
for (hwnd, pending) in pending {
|
||||
let window = crate::window::Window { hwnd };
|
||||
match pending {
|
||||
Pending::Exit => window.hide(),
|
||||
Pending::Enter { .. } | Pending::Entering => window.restore(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Finishes what is pending when it goes out of scope, however that is
|
||||
pub struct PendingGuard;
|
||||
|
||||
impl Drop for PendingGuard {
|
||||
fn drop(&mut self) {
|
||||
finish_pending();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -318,4 +539,227 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn something_pending_is_pending_until_it_is_taken() {
|
||||
let mut registry = Registry::default();
|
||||
let now = Instant::now();
|
||||
let enter = Pending::Enter {
|
||||
direction: SlideDirection::FromRight,
|
||||
clip: rect(0, 0, 1920, 1080),
|
||||
};
|
||||
|
||||
registry.expect(1, Pending::Exit, now);
|
||||
registry.expect(2, enter, now);
|
||||
|
||||
assert_eq!(registry.current(1, now), Some(Pending::Exit));
|
||||
assert_eq!(registry.current(2, now), Some(enter));
|
||||
assert_eq!(registry.current(3, now), None);
|
||||
|
||||
// looking does not take it
|
||||
assert_eq!(registry.current(2, now), Some(enter));
|
||||
|
||||
assert_eq!(registry.take_if(2, now, |_| true), Some(enter));
|
||||
assert_eq!(registry.take_if(2, now, |_| true), None);
|
||||
assert_eq!(registry.current(2, now), None);
|
||||
|
||||
// the other is where it was
|
||||
assert_eq!(registry.current(1, now), Some(Pending::Exit));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn something_pending_for_too_long_is_not_pending() {
|
||||
let mut registry = Registry::default();
|
||||
let then = Instant::now();
|
||||
|
||||
registry.expect(1, Pending::Exit, then);
|
||||
|
||||
let almost = then + PENDING_LIFETIME - Duration::from_millis(1);
|
||||
let late = then + PENDING_LIFETIME;
|
||||
|
||||
assert_eq!(registry.current(1, almost), Some(Pending::Exit));
|
||||
assert_eq!(registry.current(1, late), None);
|
||||
|
||||
// and taking it gets nothing, and gets rid of it
|
||||
assert_eq!(registry.take_if(1, late, |_| true), None);
|
||||
assert!(registry.drain(late).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expecting_again_starts_the_wait_again() {
|
||||
let mut registry = Registry::default();
|
||||
let then = Instant::now();
|
||||
let later = then + Duration::from_secs(1);
|
||||
|
||||
registry.expect(1, Pending::Exit, then);
|
||||
registry.expect(1, Pending::Exit, later);
|
||||
|
||||
assert_eq!(
|
||||
registry.current(1, later + Duration::from_millis(1500)),
|
||||
Some(Pending::Exit)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn draining_gives_everything_that_is_pending_once() {
|
||||
let mut registry = Registry::default();
|
||||
let now = Instant::now();
|
||||
|
||||
registry.expect(1, Pending::Exit, now);
|
||||
registry.expect(2, Pending::Exit, now);
|
||||
|
||||
let mut drained = registry.drain(now);
|
||||
drained.sort_by_key(|(hwnd, _)| *hwnd);
|
||||
|
||||
assert_eq!(drained, vec![(1, Pending::Exit), (2, Pending::Exit)]);
|
||||
assert!(registry.drain(now).is_empty());
|
||||
assert_eq!(registry.current(1, now), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_has_an_animation_to_do_it_is_left_pending_when_the_rest_is_finished() {
|
||||
let mut registry = Registry::default();
|
||||
let then = Instant::now();
|
||||
|
||||
registry.expect(1, Pending::Exit, then);
|
||||
registry.expect(2, Pending::Exit, then);
|
||||
registry.claim(2);
|
||||
// nothing to claim is fine
|
||||
registry.claim(3);
|
||||
|
||||
assert_eq!(registry.drain(then), vec![(1, Pending::Exit)]);
|
||||
assert_eq!(registry.current(2, then), Some(Pending::Exit));
|
||||
|
||||
// until the animation takes it
|
||||
assert_eq!(registry.take_if(2, then, |_| true), Some(Pending::Exit));
|
||||
assert_eq!(registry.current(2, then), None);
|
||||
|
||||
// or it was not taken for too long, which is the end of it
|
||||
registry.expect(4, Pending::Exit, then);
|
||||
registry.claim(4);
|
||||
assert!(registry.drain(then + PENDING_LIFETIME).is_empty());
|
||||
assert!(registry.entries.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_is_pending_for_a_window_is_taken_by_whoever_it_is_for() {
|
||||
// windows nothing else in the tests uses
|
||||
let (leaving, entering) = (0x7e57_1001, 0x7e57_1002);
|
||||
let clip = rect(-1920, 0, 1920, 1080);
|
||||
|
||||
expect_exit(leaving);
|
||||
expect_enter(entering, SlideDirection::FromLeft, clip);
|
||||
|
||||
assert!(is_exiting(leaving));
|
||||
assert!(!is_entering(leaving));
|
||||
assert!(is_entering(entering));
|
||||
assert!(!is_exiting(entering));
|
||||
|
||||
assert!(take_exit(leaving));
|
||||
assert!(!is_exiting(leaving));
|
||||
assert_eq!(take_enter(entering), Some((SlideDirection::FromLeft, clip)));
|
||||
finish_enter(entering);
|
||||
assert!(!is_entering(entering));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_window_that_slides_in_is_not_shown_by_anything_else_until_its_animation_does() {
|
||||
// a window nothing else in the tests uses
|
||||
let hwnd = 0x7e57_1003;
|
||||
let clip = rect(0, 0, 1920, 1080);
|
||||
|
||||
expect_enter(hwnd, SlideDirection::FromRight, clip);
|
||||
assert!(is_entering(hwnd));
|
||||
assert!(!is_sliding_in(hwnd));
|
||||
|
||||
assert_eq!(take_enter(hwnd), Some((SlideDirection::FromRight, clip)));
|
||||
|
||||
// the layout that is done after the switch finds it still hidden
|
||||
assert!(is_entering(hwnd));
|
||||
assert!(is_sliding_in(hwnd));
|
||||
// and it is not slid in twice
|
||||
assert_eq!(take_enter(hwnd), None);
|
||||
|
||||
finish_enter(hwnd);
|
||||
assert!(!is_entering(hwnd));
|
||||
assert!(!is_sliding_in(hwnd));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_window_that_is_sliding_in_has_its_animation_to_show_it_not_finish_pending() {
|
||||
let mut registry = Registry::default();
|
||||
let now = Instant::now();
|
||||
|
||||
registry.expect(1, Pending::Entering, now);
|
||||
registry.claim(1);
|
||||
|
||||
assert!(registry.drain(now).is_empty());
|
||||
assert_eq!(registry.current(1, now), Some(Pending::Entering));
|
||||
|
||||
// it is not for ever, if its animation never gets to it
|
||||
assert_eq!(registry.current(1, now + PENDING_LIFETIME), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn taking_what_is_not_wanted_leaves_it_pending() {
|
||||
let mut registry = Registry::default();
|
||||
let now = Instant::now();
|
||||
|
||||
registry.expect(1, Pending::Entering, now);
|
||||
registry.claim(1);
|
||||
|
||||
// asked for by something that is after another state
|
||||
assert_eq!(
|
||||
registry.take_if(1, now, |pending| matches!(pending, Pending::Exit)),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
registry.take_if(1, now, |pending| matches!(pending, Pending::Enter { .. })),
|
||||
None
|
||||
);
|
||||
assert_eq!(registry.current(1, now), Some(Pending::Entering));
|
||||
|
||||
// and by what is after it
|
||||
assert_eq!(
|
||||
registry.take_if(1, now, |pending| matches!(pending, Pending::Entering)),
|
||||
Some(Pending::Entering)
|
||||
);
|
||||
assert_eq!(registry.current(1, now), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_is_pending_for_too_long_is_got_rid_of_by_anyone_asking() {
|
||||
let mut registry = Registry::default();
|
||||
let then = Instant::now();
|
||||
|
||||
registry.expect(1, Pending::Entering, then);
|
||||
|
||||
let late = then + PENDING_LIFETIME;
|
||||
assert_eq!(registry.take_if(1, late, |_| false), None);
|
||||
assert!(registry.entries.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_window_stays_swallowed_however_many_layouts_look_at_it_while_it_slides_in() {
|
||||
// a window nothing else in the tests uses
|
||||
let hwnd = 0x7e57_1004;
|
||||
let clip = rect(0, 0, 1920, 1080);
|
||||
|
||||
expect_enter(hwnd, SlideDirection::FromRight, clip);
|
||||
|
||||
// the layout of the switch takes it, and the layouts of the focus changes
|
||||
// that follow it find it on its way in
|
||||
assert!(take_enter(hwnd).is_some());
|
||||
for _ in 0..3 {
|
||||
assert_eq!(take_enter(hwnd), None);
|
||||
assert!(!take_exit(hwnd));
|
||||
assert!(is_entering(hwnd));
|
||||
assert!(is_sliding_in(hwnd));
|
||||
}
|
||||
|
||||
// until its animation shows it
|
||||
finish_enter(hwnd);
|
||||
assert!(!is_entering(hwnd));
|
||||
assert!(!is_sliding_in(hwnd));
|
||||
}
|
||||
}
|
||||
@@ -452,7 +452,10 @@ impl Border {
|
||||
rect.add_margin(self.width);
|
||||
rect.add_padding(-self.offset);
|
||||
|
||||
WindowsApi::set_border_pos(self.hwnd, &rect, reference_hwnd)?;
|
||||
// a border that is held is put where it is to be and left hidden: it is
|
||||
// shown along with its window, not when it is created
|
||||
let show = !crate::border_manager::is_held(reference_hwnd);
|
||||
WindowsApi::set_border_pos(self.hwnd, &rect, reference_hwnd, show)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -521,7 +524,7 @@ impl Border {
|
||||
// The window is hidden while a ghost stands in for it, and
|
||||
// is not where the ghost is, which is where the border
|
||||
// belongs: the animation moves it.
|
||||
if crate::border_manager::is_ghosted(reference_hwnd) {
|
||||
if crate::border_manager::leaves_border_alone(reference_hwnd) {
|
||||
tracing::trace!(
|
||||
"border: left the border of hwnd {reference_hwnd} to its ghost's animation"
|
||||
);
|
||||
|
||||
@@ -23,6 +23,7 @@ use parking_lot::Mutex;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
@@ -135,6 +136,36 @@ pub(crate) fn is_ghosted(hwnd: isize) -> bool {
|
||||
GHOSTED.lock().contains_key(&hwnd)
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// The windows whose borders are not to be seen until they are let go of:
|
||||
/// the border of a window that is sliding into a monitor cannot be cut off
|
||||
/// at the edge of the monitor, as the picture of the window is, so it is
|
||||
/// not shown until the window is.
|
||||
static ref HELD: Mutex<HashSet<isize>> = Mutex::new(HashSet::new());
|
||||
}
|
||||
|
||||
/// Hold, or let go of, the border of a window: while it is held it is hidden
|
||||
/// and nothing that happens to the window shows it or moves it. Letting go of
|
||||
/// it does not show it: showing the window does.
|
||||
pub fn set_held(hwnd: isize, held: bool) {
|
||||
if held {
|
||||
HELD.lock().insert(hwnd);
|
||||
hide_border(hwnd);
|
||||
} else {
|
||||
HELD.lock().remove(&hwnd);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_held(hwnd: isize) -> bool {
|
||||
HELD.lock().contains(&hwnd)
|
||||
}
|
||||
|
||||
/// Whether the border of a window is not to be moved to where the window is,
|
||||
/// because something else is putting it where it is to be seen
|
||||
pub(crate) fn leaves_border_alone(hwnd: isize) -> bool {
|
||||
is_ghosted(hwnd) || is_held(hwnd)
|
||||
}
|
||||
|
||||
pub fn window_border(hwnd: isize) -> Option<BorderInfo> {
|
||||
let id = WINDOWS_BORDERS.lock().get(&hwnd)?.clone();
|
||||
BORDER_STATE.lock().get(&id).map(|b| BorderInfo {
|
||||
@@ -522,7 +553,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
// Update the border's monitor idx in case it changed
|
||||
border.monitor_idx = Some(monitor_idx);
|
||||
|
||||
let rect = if !new_border && is_ghosted(focused_window_hwnd) {
|
||||
let rect = if !new_border && leaves_border_alone(focused_window_hwnd) {
|
||||
tracing::trace!(
|
||||
"border: left the border of hwnd {focused_window_hwnd} to its ghost's animation"
|
||||
);
|
||||
@@ -687,7 +718,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
// avoid getting into a thread restart loop if we try to look up
|
||||
// rect info for a window that has been destroyed by the time
|
||||
// we get here
|
||||
let rect = if !new_border && is_ghosted(focused_window_hwnd) {
|
||||
let rect = if !new_border && leaves_border_alone(focused_window_hwnd) {
|
||||
tracing::trace!(
|
||||
"border: left the border of hwnd {focused_window_hwnd} to its ghost's animation"
|
||||
);
|
||||
@@ -788,7 +819,7 @@ fn handle_floating_borders(
|
||||
// Update the border's monitor idx in case it changed
|
||||
border.monitor_idx = Some(monitor_idx);
|
||||
|
||||
let rect = if !new_border && is_ghosted(window.hwnd) {
|
||||
let rect = if !new_border && leaves_border_alone(window.hwnd) {
|
||||
tracing::trace!(
|
||||
"border: left the border of hwnd {} to its ghost's animation",
|
||||
window.hwnd
|
||||
@@ -859,6 +890,16 @@ fn remove_border(
|
||||
borders: &mut HashMap<String, Box<Border>>,
|
||||
windows_borders: &mut HashMap<isize, String>,
|
||||
) -> color_eyre::Result<()> {
|
||||
// the border of a window that is about to slide out is the only one there is
|
||||
// until its ghost, which has one drawn in it, is up: the window is hidden,
|
||||
// and its border with it, when that is
|
||||
if borders
|
||||
.get(id)
|
||||
.is_some_and(|border| crate::animation::transition::is_exiting(border.tracking_hwnd))
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(removed_border) = borders.remove(id) {
|
||||
windows_borders.remove(&removed_border.tracking_hwnd);
|
||||
destroy_border(removed_border)?;
|
||||
@@ -1010,4 +1051,27 @@ mod tests {
|
||||
fn nothing_is_waiting_for_a_border_that_was_not_ghosted() {
|
||||
assert!(take_animated_rect(0x7e57_0003).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_window_whose_border_is_held_has_it_left_alone_until_it_is_let_go_of() {
|
||||
let hwnd = 0x7e57_0004;
|
||||
|
||||
assert!(!leaves_border_alone(hwnd));
|
||||
|
||||
set_held(hwnd, true);
|
||||
assert!(is_held(hwnd));
|
||||
assert!(leaves_border_alone(hwnd));
|
||||
assert!(!leaves_border_alone(hwnd + 1));
|
||||
|
||||
set_held(hwnd, false);
|
||||
assert!(!is_held(hwnd));
|
||||
assert!(!leaves_border_alone(hwnd));
|
||||
|
||||
// a ghost leaves it alone too, which is not holding it
|
||||
set_ghosted(hwnd, true);
|
||||
assert!(leaves_border_alone(hwnd));
|
||||
assert!(!is_held(hwnd));
|
||||
set_ghosted(hwnd, false);
|
||||
assert!(!leaves_border_alone(hwnd));
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ use crate::animation::ghost_dispatcher::GhostDispatcher;
|
||||
use crate::animation::lerp::Lerp;
|
||||
use crate::animation::prefix::AnimationPrefix;
|
||||
use crate::animation::prefix::new_animation_key;
|
||||
use crate::animation::transition;
|
||||
use crate::border_manager;
|
||||
use crate::cloaked;
|
||||
use crate::com::SetCloak;
|
||||
@@ -415,6 +416,12 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn hide_with_border(self, hide_border: bool) {
|
||||
// a workspace that is sliding out hides its windows itself, when their
|
||||
// ghosts are up
|
||||
if transition::is_exiting(self.hwnd) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
||||
if !programmatically_hidden_hwnds.contains(&self.hwnd) {
|
||||
programmatically_hidden_hwnds.push(self.hwnd);
|
||||
@@ -441,6 +448,12 @@ impl Window {
|
||||
}
|
||||
|
||||
pub fn restore_with_border(self, restore_border: bool) {
|
||||
// a workspace that is sliding in shows its windows itself, when they have
|
||||
// got there
|
||||
if transition::is_entering(self.hwnd) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock();
|
||||
if let Some(idx) = programmatically_hidden_hwnds
|
||||
.iter()
|
||||
|
||||
@@ -606,11 +606,19 @@ impl WindowsApi {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_border_pos(hwnd: isize, layout: &Rect, position: isize) -> eyre::Result<()> {
|
||||
pub fn set_border_pos(
|
||||
hwnd: isize,
|
||||
layout: &Rect,
|
||||
position: isize,
|
||||
show: bool,
|
||||
) -> eyre::Result<()> {
|
||||
let mut flags = SetWindowPosition::NO_SEND_CHANGING
|
||||
| SetWindowPosition::NO_ACTIVATE
|
||||
| SetWindowPosition::NO_REDRAW
|
||||
| SetWindowPosition::SHOW_WINDOW;
|
||||
| SetWindowPosition::NO_REDRAW;
|
||||
|
||||
if show {
|
||||
flags |= SetWindowPosition::SHOW_WINDOW;
|
||||
}
|
||||
|
||||
if matches!(
|
||||
WINDOW_HANDLING_BEHAVIOUR.load(),
|
||||
|
||||
Reference in new issue
Block a user