T46: round 1 fixes: Ely's window never takes the keys, a panel key only if needed on macOS and on Windows a window that answers a press's activation with none; she opens with the sessions' mood; she stands 140 points in or less on a narrow pane, so she holds its edge at any width, and on Wayland, whose popup keeps the place it opened at, she opens again when a new width moves her
CI / platforms (ubuntu-24.04) (push) Successful in 1m53s
CI / platforms (windows-latest) (push) Successful in 2m2s
CI / macos (push) Canceled after 9m52s

This commit is contained in:
ZacharyZhang-NY committed 2026-10-18 05:26:13 -04:00
1 parent 9aeb3f3552
commit 6df7534e7f
7 files changed
+165 -70

No files matched your search

+6 -5
View File
@@ -1,6 +1,4 @@
#[cfg(target_os = "linux")]
use gpui::{Bounds, Decorations, Pixels, Size, point};
use gpui::{Window, WindowBackgroundAppearance};
use gpui::{Bounds, Decorations, Pixels, Size, Window, WindowBackgroundAppearance, point};
use crate::settings::Settings;
@@ -91,8 +89,11 @@ impl Blur {
}
/// The window's pane in its surface: all of it, less the inset on each free edge of a client-drawn window, as Ely's `ResizeBorder` pads it.
#[cfg(target_os = "linux")]
fn pane(size: Size<Pixels>, inset: Option<Pixels>, decorations: Decorations) -> Bounds<Pixels> {
pub(crate) fn pane(
size: Size<Pixels>,
inset: Option<Pixels>,
decorations: Decorations,
) -> Bounds<Pixels> {
let mut pane = Bounds::new(point(Pixels::ZERO, Pixels::ZERO), size);
let (Decorations::Client { tiling }, Some(inset)) = (decorations, inset) else {
return pane;
+103 -38
View File
@@ -18,10 +18,10 @@ use std::{
};
use gpui::{
Animation, AnimationExt as _, App, AppContext as _, Bounds, Context, Decorations, Image,
InteractiveElement, IntoElement, MouseButton, ParentElement, Pixels, Render, Styled,
Subscription, Task, Window, WindowBackgroundAppearance, WindowBounds, WindowHandle, WindowKind,
WindowOptions, div, img, point, popup, px, size,
Animation, AnimationExt as _, App, AppContext as _, Bounds, Context, Image, InteractiveElement,
IntoElement, MouseButton, ParentElement, Pixels, Render, Styled, Subscription, Task, Window,
WindowBackgroundAppearance, WindowBounds, WindowHandle, WindowKind, WindowOptions, div, img,
point, popup, px, size,
};
#[cfg(target_os = "macos")]
@@ -52,6 +52,8 @@ pub struct Pet {
petted: Option<Instant>,
/// Counts her moves, so each plays from its start.
moves: usize,
/// How far in from the pane's left edge she opened.
left: f32,
/// What keeps her window on the main window's edge, where the platform does not.
glue: Option<platform::Glue>,
_wake: Option<Task<()>>,
@@ -61,40 +63,80 @@ pub struct Pet {
/// Shows Ely over `main` while the settings want her.
pub fn follow(main: WindowHandle<Workspace>, cx: &mut App) {
let shown: Rc<RefCell<Option<WindowHandle<Pet>>>> = Rc::default();
let sync = move |cx: &mut App| {
let open = *shown.borrow();
match (SettingsStore::get(cx).ely, open) {
(true, None) => match show(main, cx) {
Ok(pet) => *shown.borrow_mut() = Some(pet),
Err(error) => log::error!("pet: Ely does not show: {error:#}"),
},
(false, Some(pet)) => {
*shown.borrow_mut() = None;
log::info!("pet: Ely goes, as the settings say");
if pet
.update(cx, |_, window, _| window.remove_window())
.is_err()
{
log::debug!("pet: her window had gone");
let sync = {
let shown = shown.clone();
move |cx: &mut App| {
let open = *shown.borrow();
match (SettingsStore::get(cx).ely, open) {
(true, None) => *shown.borrow_mut() = open_pet(main, cx),
(false, Some(pet)) => {
*shown.borrow_mut() = None;
close(pet, "as the settings say", cx);
}
_ => {}
}
_ => {}
}
};
sync(cx);
cx.observe_global::<SettingsStore>(sync).detach();
// A Wayland popup keeps the place it opened at, so she opens again where a new width puts her.
let moved = main.update(cx, |_, window, cx| {
cx.observe_window_bounds(window, move |_, window, cx| {
let Some(pet) = *shown.borrow() else {
return;
};
let at = left(pane(window).size.width);
if wayland(window) && pet.read(cx).is_ok_and(|pet| pet.left != at) {
let shown = shown.clone();
cx.defer(move |cx| {
close(pet, "to stand where the new width puts her", cx);
*shown.borrow_mut() = open_pet(main, cx);
});
}
})
});
match moved {
Ok(moved) => moved.detach(),
Err(error) => log::error!("pet: the main window's width goes unwatched: {error:#}"),
}
}
/// Opens her window, logging why when it does not open.
fn open_pet(main: WindowHandle<Workspace>, cx: &mut App) -> Option<WindowHandle<Pet>> {
show(main, cx)
.inspect_err(|error| log::error!("pet: Ely does not show: {error:#}"))
.ok()
}
fn close(pet: WindowHandle<Pet>, why: &str, cx: &mut App) {
log::info!("pet: Ely goes, {why}");
if pet
.update(cx, |_, window, _| window.remove_window())
.is_err()
{
log::debug!("pet: her window had gone");
}
}
/// How far in from the pane's left edge she stands on a pane `width` points wide: `LEFT`, or less on a narrow pane, so she holds its edge.
fn left(width: Pixels) -> f32 {
LEFT.min(f32::from(width) - WIDTH).max(0.0)
}
/// Opens her window on `main`'s top edge: a popup Wayland's compositor places, else a window kept there.
fn show(main: WindowHandle<Workspace>, cx: &mut App) -> anyhow::Result<WindowHandle<Pet>> {
let (bounds, inset, wayland) = main.update(cx, |_, window, _| {
(window.bounds(), top_inset(window), wayland(window))
let (bounds, pane, wayland) = main.update(cx, |_, window, _| {
(window.bounds(), pane(window), wayland(window))
})?;
let at = left(pane.size.width);
let room = size(px(WIDTH), px(HEAD + ROOM));
let kind = if wayland {
WindowKind::AnchoredPopup(popup::PopupOptions {
parent: main.into(),
anchor_rect: Bounds::new(point(inset + px(LEFT), inset), size(px(1.), px(1.))),
anchor_rect: Bounds::new(
point(pane.origin.x + px(at), pane.origin.y),
size(px(1.), px(1.)),
),
anchor: popup::PopupAnchor::TopLeft,
gravity: popup::PopupGravity::TopRight,
constraint_adjustment: popup::PopupConstraintAdjustment::empty(),
@@ -104,10 +146,7 @@ fn show(main: WindowHandle<Workspace>, cx: &mut App) -> anyhow::Result<WindowHan
} else {
WindowKind::PopUp
};
let origin = point(
bounds.origin.x + inset + px(LEFT),
bounds.origin.y + inset - room.height,
);
let origin = bounds.origin + pane.origin + point(px(at), -room.height);
let options = WindowOptions {
window_bounds: Some(WindowBounds::Windowed(Bounds::new(origin, room))),
titlebar: None,
@@ -121,21 +160,30 @@ fn show(main: WindowHandle<Workspace>, cx: &mut App) -> anyhow::Result<WindowHan
..Default::default()
};
let workspace = main.entity(cx)?;
let feeling = |workspace: &Workspace, cx: &App| {
mood(
workspace
.sessions()
.iter()
.filter_map(|session| session.looks(cx)),
)
};
let start = feeling(workspace.read(cx), cx);
let pet = cx.open_window(options, |_, cx| {
cx.new(|cx| {
let feel = cx.observe(&workspace, |pet: &mut Pet, workspace, cx| {
let sessions = workspace.read(cx).sessions();
let now = mood(sessions.iter().filter_map(|session| session.looks(cx)));
let feel = cx.observe(&workspace, move |pet: &mut Pet, workspace, cx| {
let now = feeling(workspace.read(cx), cx);
if now != pet.mood {
pet.feel(now, cx);
}
});
let mut pet = Pet {
faces: faces::images(),
mood: Mood::Calm,
mood: start,
since: Instant::now(),
petted: None,
moves: 0,
left: at,
glue: None,
_wake: None,
_watch: vec![feel],
@@ -152,7 +200,7 @@ fn show(main: WindowHandle<Workspace>, cx: &mut App) -> anyhow::Result<WindowHan
} else {
"a window kept there"
};
log::info!("pet: Ely peeks over the main window, {way}");
log::info!("pet: Ely peeks over the main window, {way}, feeling {start:?}");
Ok(pet)
}
@@ -178,12 +226,13 @@ fn glue(main: WindowHandle<Workspace>, pet: WindowHandle<Pet>, cx: &mut App) ->
})?
}
/// The pane's distance from its window's top: the resize margin of a window that draws its own frame and is free at the top.
fn top_inset(window: &Window) -> Pixels {
match (window.window_decorations(), window.client_inset()) {
(Decorations::Client { tiling }, Some(inset)) if !tiling.top => inset,
_ => px(0.),
}
/// The main window's pane, in its own points.
fn pane(window: &Window) -> Bounds<Pixels> {
crate::blur::pane(
window.viewport_size(),
window.client_inset(),
window.window_decorations(),
)
}
/// Whether `window` is a Wayland surface, where the compositor places windows.
@@ -299,3 +348,19 @@ impl Render for Pet {
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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"
);
}
}
+6 -5
View File
@@ -4,12 +4,12 @@ use anyhow::bail;
use gpui::Window;
use objc::{
msg_send,
runtime::{NO, Object},
runtime::{NO, Object, YES},
sel, sel_impl,
};
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use super::LEFT;
use super::left;
/// AppKit's `NSWindowAbove`.
const ABOVE: isize = 1;
@@ -36,7 +36,7 @@ pub struct Glue {
}
impl Glue {
/// Makes her window the main window's child, at its level and in its Spaces, with no shadow.
/// Makes her window the main window's child, at its level and in its Spaces, with no shadow and never key, so a press leaves the keys with the terminal.
pub fn new(pet: &Window, main: &Window) -> anyhow::Result<Self> {
let (pet, main) = (native(pet)?, native(main)?);
unsafe {
@@ -45,17 +45,18 @@ impl Glue {
let spaces: usize = msg_send![main, collectionBehavior];
let _: () = msg_send![pet, setCollectionBehavior: spaces];
let _: () = msg_send![pet, setHasShadow: NO];
let _: () = msg_send![pet, setBecomesKeyOnlyIfNeeded: YES];
let _: () = msg_send![main, addChildWindow: pet ordered: ABOVE];
}
Ok(Self { pet, main })
}
/// Stands her on the main window's top edge, `LEFT` points in from its left.
/// Stands her on the main window's top edge, as far in as its width allows.
pub fn place(&self, _: &Window) -> anyhow::Result<()> {
unsafe {
let frame: Rect = msg_send![self.main, frame];
let at = Point {
x: frame.origin.x + f64::from(LEFT),
x: frame.origin.x + f64::from(left(gpui::px(frame.width as f32))),
y: frame.origin.y + frame.height,
};
let _: () = msg_send![self.pet, setFrameOrigin: at];
+32 -8
View File
@@ -1,17 +1,23 @@
//! Ely's window on Windows: owned by the main window, so it stays above it and hides with it, and moved as it moves.
use std::sync::atomic::{AtomicIsize, Ordering};
use anyhow::bail;
use gpui::Window;
use gpui::{Window, px};
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use windows_sys::Win32::{
Foundation::{HWND, RECT},
Foundation::{HWND, LPARAM, LRESULT, RECT, WPARAM},
UI::WindowsAndMessaging::{
GWLP_HWNDPARENT, GetWindowRect, HWND_NOTOPMOST, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE,
SWP_NOZORDER, SetWindowLongPtrW, SetWindowPos,
CallWindowProcW, GWLP_HWNDPARENT, GWLP_WNDPROC, GetWindowRect, HWND_NOTOPMOST,
MA_NOACTIVATE, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, SetWindowLongPtrW,
SetWindowPos, WM_MOUSEACTIVATE, WNDPROC,
},
};
use super::{HEAD, LEFT, ROOM};
use super::{HEAD, ROOM, left};
/// gpui's window procedure, which hers passes every message on to but a press's activation.
static GPUI: AtomicIsize = AtomicIsize::new(0);
/// Her window and the main one.
pub struct Glue {
@@ -20,11 +26,14 @@ pub struct Glue {
}
impl Glue {
/// Gives her window the main window as owner, and takes away the topmost place gpui gives a popup.
/// Gives her window the main window as owner, takes away the topmost place gpui gives a popup, and keeps a press from activating it, so the keys stay with the terminal.
pub fn new(pet: &Window, main: &Window) -> anyhow::Result<Self> {
let (pet, main) = (native(pet)?, native(main)?);
unsafe {
SetWindowLongPtrW(pet, GWLP_HWNDPARENT, main as isize);
let gpui = SetWindowLongPtrW(pet, GWLP_WNDPROC, inactive as *const () as isize);
anyhow::ensure!(gpui != 0, "pet: her window's procedure does not read");
GPUI.store(gpui, Ordering::Relaxed);
let flags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE;
anyhow::ensure!(
SetWindowPos(pet, HWND_NOTOPMOST, 0, 0, 0, 0, flags) != 0,
@@ -34,7 +43,7 @@ impl Glue {
Ok(Self { pet, main })
}
/// Stands her on the main window's top edge, `LEFT` points in from its left.
/// Stands her on the main window's top edge, as far in as its width allows.
pub fn place(&self, main: &Window) -> anyhow::Result<()> {
let scale = main.scale_factor();
let mut frame = RECT::default();
@@ -43,7 +52,8 @@ impl Glue {
GetWindowRect(self.main, &mut frame) != 0,
"pet: the main window's place does not read"
);
let x = frame.left + (LEFT * scale).round() as i32;
let width = px((frame.right - frame.left) as f32 / scale);
let x = frame.left + (left(width) * scale).round() as i32;
let y = frame.top - ((HEAD + ROOM) * scale).round() as i32;
let flags = SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE;
anyhow::ensure!(
@@ -55,6 +65,20 @@ impl Glue {
}
}
/// Her window's procedure: a press does not activate her window; all else is gpui's.
unsafe extern "system" fn inactive(
window: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
if message == WM_MOUSEACTIVATE {
return MA_NOACTIVATE as LRESULT;
}
let gpui: WNDPROC = unsafe { std::mem::transmute(GPUI.load(Ordering::Relaxed)) };
unsafe { CallWindowProcW(gpui, window, message, wparam, lparam) }
}
/// The HWND gpui made for `window`.
fn native(window: &Window) -> anyhow::Result<HWND> {
let handle = HasWindowHandle::window_handle(window)
+16 -12
View File
@@ -1,14 +1,14 @@
//! Ely's window on X11: gpui's popup, which no window manager places, put on the main window's top edge and just above it.
use anyhow::{Context as _, bail};
use gpui::Window;
use gpui::{Bounds, Pixels, Window};
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use x11rb::{
protocol::xproto::{ConfigureWindowAux, ConnectionExt as _, StackMode},
rust_connection::RustConnection,
};
use super::{HEAD, LEFT, ROOM, top_inset};
use super::{HEAD, ROOM, left, pane};
/// Her X window, the main one, and a connection of her own to their server.
pub struct Glue {
@@ -29,21 +29,22 @@ impl Glue {
})
}
/// Stands her on the main window's pane, `LEFT` points in, stacked just above the main window's frame.
/// Stands her on the main window's pane, as far in as its width allows, stacked just above the main window's frame.
pub fn place(&self, main: &Window) -> anyhow::Result<()> {
self.put(main.scale_factor(), f32::from(top_inset(main)))
self.put(main.scale_factor(), pane(main))
}
/// Places her at `scale` over a pane `inset` points into the main window.
fn put(&self, scale: f32, inset: f32) -> anyhow::Result<()> {
/// Places her at `scale` over `pane`, in the main window's points.
fn put(&self, scale: f32, pane: Bounds<Pixels>) -> anyhow::Result<()> {
let (x, y) = (f32::from(pane.origin.x), f32::from(pane.origin.y));
let (root, frame) = self.frame()?;
let at = (self.connection)
.translate_coordinates(self.main, root, 0, 0)?
.reply()
.context("pet: the main window's place")?;
let aux = ConfigureWindowAux::new()
.x(i32::from(at.dst_x) + ((LEFT + inset) * scale).round() as i32)
.y(i32::from(at.dst_y) + ((inset - HEAD - ROOM) * scale).round() as i32)
.x(i32::from(at.dst_x) + ((x + left(pane.size.width)) * scale).round() as i32)
.y(i32::from(at.dst_y) + ((y - HEAD - ROOM) * scale).round() as i32)
.sibling(frame)
.stack_mode(StackMode::ABOVE);
(self.connection)
@@ -95,6 +96,8 @@ mod tests {
protocol::xproto::{CreateWindowAux, MapState, WindowClass},
};
use gpui::{point, px, size};
use super::*;
use crate::testx11::Xvfb;
@@ -146,7 +149,8 @@ mod tests {
pet,
main,
};
glue.put(2.0, 10.0).expect("she is placed");
let pane = Bounds::new(point(px(10.), px(10.)), size(px(380.), px(280.)));
glue.put(2.0, pane).expect("she is placed");
let check = x11rb::connect(Some(&xvfb.display))
.expect("the check connects")
.0;
@@ -155,11 +159,11 @@ mod tests {
.expect("the request")
.reply()
.expect("her place");
let left = (
100 + ((LEFT + 10.0) * 2.0).round() as i32,
let stands = (
100 + ((10.0 + left(px(380.))) * 2.0).round() as i32,
100 + ((10.0 - HEAD - ROOM) * 2.0).round() as i32,
);
assert_eq!((i32::from(at.x), i32::from(at.y)), left);
assert_eq!((i32::from(at.x), i32::from(at.y)), stands);
let order = check
.query_tree(root)
.expect("the request")