T51: round 1 fix: on X11 the room above runs to the top of the RandR monitor under her, or of _NET_WORKAREA where that lies lower, since the work area spans every monitor; on Wayland she checks twice a second where the window's size and state put her anchor, counted from the pane as gpui counts it, and opens there again when it moved, since gpui hears a change of state alone as no change of bounds and gives an unseen popup no frames; tiling counts where gpui reports it, for a window it draws itself; her tests move to pet/tests.rs, keeping pet.rs under 500 lines
This commit is contained in:
6 files changed
+234
-153
No files matched your search
@@ -0,0 +1,94 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use super::{look::Mark, *};
|
||||
|
||||
/// Her, `mood` since `since`, come long before.
|
||||
fn ely(mood: Mood, since: Instant) -> Pet {
|
||||
Pet {
|
||||
rig: Rc::new(Rig::new()),
|
||||
mood,
|
||||
since,
|
||||
shown: since - Duration::from_secs(60),
|
||||
petted: None,
|
||||
act: Act::Calm,
|
||||
last: None,
|
||||
eased: None,
|
||||
edge: Edge::Top,
|
||||
anchor: None,
|
||||
glue: None,
|
||||
_watch: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_change_of_mood_or_a_press_eases_her_from_where_she_is_though_the_act_stays() {
|
||||
let start = Instant::now();
|
||||
let at = |ms: u64| start + Duration::from_millis(ms);
|
||||
// A wait hops her, a press winks, and a program starts working inside the wink.
|
||||
let mut pet = ely(Mood::Waiting, start);
|
||||
pet.press(at(2250));
|
||||
let before = pet.pose(at(2750));
|
||||
pet.feel(Mood::Working, at(2750));
|
||||
let after = pet.pose(at(2751));
|
||||
assert_eq!((before.act, after.act), (Act::Wink, Act::Wink));
|
||||
assert!(
|
||||
(after.lift - before.lift).abs() < 1.0,
|
||||
"no drop: {} then {}",
|
||||
before.lift,
|
||||
after.lift
|
||||
);
|
||||
let marks: Vec<Mark> = after.marks.iter().map(|(mark, _)| *mark).collect();
|
||||
assert!(
|
||||
marks.contains(&Mark::Bang),
|
||||
"the wait's mark fades: {marks:?}"
|
||||
);
|
||||
// An old finish clears mid-glance: calm before and after, her eyes turn back at her pace.
|
||||
let mut pet = ely(Mood::Done, start);
|
||||
let before = pet.pose(at(13_600));
|
||||
pet.feel(Mood::Calm, at(13_600));
|
||||
let after = pet.pose(at(13_601));
|
||||
assert_eq!((before.act, after.act), (Act::Calm, Act::Calm));
|
||||
assert!(
|
||||
before.look.0 < -5.0 && (after.look.0 - before.look.0).abs() < 0.5,
|
||||
"no snap: {:?} then {:?}",
|
||||
before.look,
|
||||
after.look
|
||||
);
|
||||
let later = pet.pose(at(14_000));
|
||||
assert!(
|
||||
pet.eased.is_none() && later.look.0.abs() < 0.5,
|
||||
"eased in by then"
|
||||
);
|
||||
// A second press mid-hop restarts the wink without a drop.
|
||||
let mut pet = ely(Mood::Calm, start);
|
||||
pet.press(at(1000));
|
||||
let before = pet.pose(at(1300));
|
||||
pet.press(at(1300));
|
||||
let after = pet.pose(at(1301));
|
||||
assert!(
|
||||
before.lift > 10.0 && (after.lift - before.lift).abs() < 1.0,
|
||||
"no drop: {} then {}",
|
||||
before.lift,
|
||||
after.lift
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn she_stands_on_the_top_edge_while_her_box_fits_above_it() {
|
||||
assert_eq!(edge(HEAD + ROOM), Edge::Top, "just room for her");
|
||||
assert_eq!(edge(400.0), Edge::Top);
|
||||
assert_eq!(edge(HEAD + ROOM - 0.5), Edge::Bottom, "half a point short");
|
||||
assert_eq!(edge(0.0), Edge::Bottom, "a window at the work area's top");
|
||||
assert_eq!(edge(-30.0), Edge::Bottom, "a window above it");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn she_holds_the_edge_of_any_pane() {
|
||||
assert_eq!(left(px(1200.)), LEFT);
|
||||
assert_eq!(left(px(200.)), 200.0 - WIDTH);
|
||||
assert_eq!(
|
||||
left(px(100.)),
|
||||
0.0,
|
||||
"narrower than she is, she stands at its left"
|
||||
);
|
||||
}
|
||||
+63
-9
@@ -4,7 +4,10 @@ use anyhow::{Context as _, bail};
|
||||
use gpui::{Bounds, Pixels, Window};
|
||||
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
|
||||
use x11rb::{
|
||||
protocol::xproto::{Atom, AtomEnum, ConfigureWindowAux, ConnectionExt as _, StackMode},
|
||||
protocol::{
|
||||
randr::ConnectionExt as _,
|
||||
xproto::{Atom, AtomEnum, ConfigureWindowAux, ConnectionExt as _, StackMode},
|
||||
},
|
||||
rust_connection::RustConnection,
|
||||
};
|
||||
|
||||
@@ -33,16 +36,20 @@ impl Glue {
|
||||
main,
|
||||
workarea,
|
||||
};
|
||||
match glue.work_top(glue.frame()?.0)? {
|
||||
Some(top) => log::info!("pet: the work area starts {top} pixels down the screen"),
|
||||
let (root, _) = glue.frame()?;
|
||||
let monitors = glue.monitors(root)?.len();
|
||||
match glue.work_top(root)? {
|
||||
Some(top) => {
|
||||
log::info!("pet: {monitors} monitors; the work area starts {top} pixels down")
|
||||
}
|
||||
None => log::info!(
|
||||
"pet: the window manager names no work area; the screen's top bounds her"
|
||||
"pet: {monitors} monitors; the window manager names no work area, so each monitor's top bounds her"
|
||||
),
|
||||
}
|
||||
Ok(glue)
|
||||
}
|
||||
|
||||
/// Stands her on the main window's pane, on its top edge while her box fits under the top of the work area, else on its bottom edge, as far in as its width allows, stacked just above the main window's frame, and gives the edge.
|
||||
/// Stands her on the main window's pane, on its top edge while her box fits under the top of the monitor under her, or of the work area where that lies lower, else on its bottom edge, as far in as its width allows, stacked just above the main window's frame, and gives the edge.
|
||||
pub fn place(&self, main: &Window) -> anyhow::Result<Edge> {
|
||||
self.put(main.scale_factor(), pane(main))
|
||||
}
|
||||
@@ -55,15 +62,17 @@ impl Glue {
|
||||
.reply()
|
||||
.context("pet: the main window's place")?;
|
||||
let down = |y: Pixels| i32::from(at.dst_y) + (f32::from(y) * scale).round() as i32;
|
||||
let room = down(pane.origin.y) - self.work_top(root)?.unwrap_or(0);
|
||||
let edge = edge(room as f32 / scale);
|
||||
let x = f32::from(pane.origin.x) + left(pane.size.width);
|
||||
let x = i32::from(at.dst_x) + (x * scale).round() as i32;
|
||||
let top = down(pane.origin.y);
|
||||
let usable = usable(&self.monitors(root)?, self.work_top(root)?, (x, top));
|
||||
let edge = edge(usable.map_or(0.0, |usable| (top - usable) as f32 / scale));
|
||||
let bottom = down(match edge {
|
||||
Edge::Top => pane.origin.y,
|
||||
Edge::Bottom => pane.bottom(),
|
||||
});
|
||||
let x = f32::from(pane.origin.x) + left(pane.size.width);
|
||||
let aux = ConfigureWindowAux::new()
|
||||
.x(i32::from(at.dst_x) + (x * scale).round() as i32)
|
||||
.x(x)
|
||||
.y(bottom - ((HEAD + ROOM) * scale).round() as i32)
|
||||
.sibling(frame)
|
||||
.stack_mode(StackMode::ABOVE);
|
||||
@@ -74,6 +83,18 @@ impl Glue {
|
||||
Ok(edge)
|
||||
}
|
||||
|
||||
/// Each monitor's x, y, width and height on the root, as RandR names them.
|
||||
fn monitors(&self, root: u32) -> anyhow::Result<Vec<[i32; 4]>> {
|
||||
let reply = (self.connection)
|
||||
.randr_get_monitors(root, true)?
|
||||
.reply()
|
||||
.context("pet: the monitors")?;
|
||||
let monitors = reply.monitors.iter();
|
||||
Ok(monitors
|
||||
.map(|m| [m.x.into(), m.y.into(), m.width.into(), m.height.into()])
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// The top of the work area the window manager names in `_NET_WORKAREA`, its first desktop's, which managers keep alike; none where it names none.
|
||||
fn work_top(&self, root: u32) -> anyhow::Result<Option<i32>> {
|
||||
let reply = (self.connection)
|
||||
@@ -111,6 +132,14 @@ impl Glue {
|
||||
}
|
||||
}
|
||||
|
||||
/// The top of the room over `(x, y)`: the top of the monitor it lies on, or the work area's top `area` where that lies lower; none off every monitor.
|
||||
fn usable(monitors: &[[i32; 4]], area: Option<i32>, (x, y): (i32, i32)) -> Option<i32> {
|
||||
let [_, top, _, _] = monitors.iter().find(|[left, top, width, height]| {
|
||||
(*left..left + width).contains(&x) && (*top..top + height).contains(&y)
|
||||
})?;
|
||||
Some(area.map_or(*top, |area| area.max(*top)))
|
||||
}
|
||||
|
||||
/// The X window gpui made for `window`.
|
||||
fn native(window: &Window) -> anyhow::Result<u32> {
|
||||
let handle = HasWindowHandle::window_handle(window)
|
||||
@@ -135,6 +164,31 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::testx11::Xvfb;
|
||||
|
||||
#[test]
|
||||
fn the_room_above_runs_to_the_top_of_the_monitor_under_her_or_of_the_work_area() {
|
||||
let stacked = [[0, 0, 1280, 800], [0, 800, 1280, 800]];
|
||||
assert_eq!(
|
||||
usable(&stacked, None, (240, 800)),
|
||||
Some(800),
|
||||
"the lower monitor's top"
|
||||
);
|
||||
assert_eq!(
|
||||
usable(&stacked, Some(0), (240, 800)),
|
||||
Some(800),
|
||||
"a work area over both monitors leaves the lower one's top"
|
||||
);
|
||||
assert_eq!(
|
||||
usable(&stacked, Some(32), (240, 300)),
|
||||
Some(32),
|
||||
"a panel atop the upper one"
|
||||
);
|
||||
assert_eq!(
|
||||
usable(&stacked, None, (2000, 300)),
|
||||
None,
|
||||
"off every monitor"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn she_stands_on_an_edge_of_the_pane_just_above_the_frame_and_hides_with_it() {
|
||||
let xvfb = Xvfb::start();
|
||||
|
||||
Reference in new issue
Block a user