docking: native windows place by their content and follow the layout

- A window made this session opens at its computed rect; only windows of a
  loaded layout are restored onto a connected monitor.
- The content (inner rect) is each native window's desktop origin; only
  OuterPosition converts to the outer position.
- A bar drag places the window from its reported position, so late moves
  do not overshoot. The layout follows once the drag ends, and a window
  left with its bar off every monitor goes back onto the nearest.
- A changed Floating::native (undo, reset, reload) moves the OS window
  instead of being overwritten by its next report.
- Dragging a native window's only tab or group docks it whatever
  title_bar_docks says.
- Escape that cancels a drag is consumed; nothing is forwarded while a
  widget of the window has focus.
- A bar drag whose window goes away ends.
- An embedded fallback keeps its drop targets; floating windows get no
  taskbar entry.
- The test host can give windows a frame inset and land moves a frame late.
This commit is contained in:
aiongg committed 2026-10-23 17:55:03 +00:00
1 parent 0ac8f80f64
commit 98e05997a0
8 files changed
+558 -101

No files matched your search

+5 -2
View File
@@ -537,9 +537,12 @@ fn native_window_close_closes_its_panels() {
d.ctx.set_embed_viewports(false);
d.content.native = true;
d.layout = layout;
let out = d.step(vec![]);
d.step(vec![]);
assert!(d.response(Id::new(AREA).with(("move-group", Some(&5u32)))).is_none(), "drawn in its own OS window");
assert!(out.actions.contains(&Action::SetNative { panel: 5, rect: Some([1210.0, 905.0, 250.0, 300.0]) }), "{:?}", out.actions);
// Where the window system put its content, once a report that late is no longer the
// opening move landing.
let reported = (0..60).any(|_| d.step(vec![]).actions.contains(&Action::SetNative { panel: 5, rect: Some([1210.0, 935.0, 250.0, 300.0]) }));
assert!(reported, "{:?}", d.layout.floating[0].native);
close.set(true);
let out = d.step(vec![]);
+293 -1
View File
@@ -3,7 +3,7 @@
use egui::{Color32, Event, Key, Modifiers, PointerButton, Pos2, Rect, ViewportCommand, epaint::ClippedShape, pos2, vec2};
use super::render::floats_shown;
use super::render::{DragPointer, WindowDrag, floats_shown};
use super::testing::DockDriver;
use super::{Action, DockContent, Floating, Hit, Layout, Monitor, Node, Permissions, Placement, Target};
use crate::layout::{SplitAxis, SplitSize};
@@ -17,11 +17,26 @@ struct Content {
fixed: Vec<u32>,
/// Panels that stay in-window.
in_window: Vec<u32>,
/// The panel with a text field, what it holds, and whether it takes focus next frame.
text: Option<u32>,
typed: String,
focus_text: bool,
/// The panel with a button that takes focus.
focused_button: Option<u32>,
}
impl DockContent<u32> for Content {
fn body(&mut self, ui: &mut egui::Ui, panel: &u32) {
ui.label(format!("panel {panel}"));
if self.text == Some(*panel) {
let field = ui.text_edit_singleline(&mut self.typed);
if std::mem::take(&mut self.focus_text) {
field.request_focus();
}
}
if self.focused_button == Some(*panel) {
ui.button("OK").request_focus();
}
}
fn logical_label(&self, panel: &u32) -> String {
@@ -639,3 +654,280 @@ fn the_window_focused_last_is_in_front_where_windows_overlap() {
assert!(anchor.is_some_and(|a| anchors.contains(&a)), "front {front}: {:?}", out.actions);
}
}
/// Every `OuterPosition` sent to `viewport`, in order.
fn moves(d: &DockDriver<u32, Content>, viewport: egui::ViewportId) -> Vec<Pos2> {
d.host()
.commands
.iter()
.filter_map(|(v, c)| match c {
ViewportCommand::OuterPosition(pos) if *v == viewport => Some(*pos),
_ => None,
})
.collect()
}
fn escape() -> Event {
Event::Key { key: Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE }
}
#[test]
fn a_window_made_this_session_opens_where_it_was_put() {
// egui reports the monitor's size but no monitor list. A tab dropped on the desktop right
// of the main window's monitor opens there.
let size = Some(vec2(1920.0, 1080.0));
let mut d = driver(layout(false));
d.host().monitor_size = size;
let tab = d.hit(&Hit::Tab(1)).unwrap().center();
let out = pos2(2400.0, 120.0);
d.hold(tab, out);
d.release(out);
d.settle();
let desktop = ORIGIN + out.to_vec2();
let min = pos2(desktop.x - tab.x - 8.0, desktop.y - tab.y - 6.0 - BAR);
let native = window(&d, 1).native.unwrap();
assert_eq!(pos2(native[0], native[1]), min);
assert_eq!(os_window(&d, 1).unwrap().0.min, min);
// Undock Panel, to a place off that monitor.
let mut d = driver(layout(false));
d.host().monitor_size = size;
d.layout.apply(Action::Float { panel: 2, rect: [2400.0, 100.0, 300.0, 200.0] }).unwrap();
d.settle();
assert_eq!(window(&d, 2).native, Some([2500.0, 150.0, 300.0, 200.0]));
// A loaded layout's window still opens on the main window's monitor.
let mut saved = layout(true);
saved.floating[0].native = Some([2500.0, 100.0, 240.0, 300.0]);
let mut d = driver(Layout::default());
d.host().monitor_size = size;
d.layout = saved;
d.settle();
assert_eq!(window(&d, 5).native, Some([1680.0, 100.0, 240.0, 300.0]));
assert_eq!(os_window(&d, 5).unwrap().0.min, pos2(1680.0, 100.0));
}
#[test]
fn a_changed_layout_moves_the_os_window() {
let mut d = driver(layout(true));
let viewport = d.viewport(id_of(&d));
assert_eq!(window(&d, 5).native, Some([400.0, 130.0, 240.0, 300.0]));
// An undo of a move, a reset, or a layout reloaded with the same ids.
d.layout.floating[0].native = Some([700.0, 200.0, 260.0, 320.0]);
let out = d.step(vec![]);
assert!(!out.actions.iter().any(|a| matches!(a, Action::SetNative { .. })), "{:?}", out.actions);
d.settle();
assert_eq!(d.host().windows[&viewport].rect, Rect::from_min_size(pos2(700.0, 200.0), vec2(260.0, 320.0)));
assert_eq!(window(&d, 5).native, Some([700.0, 200.0, 260.0, 320.0]));
// Off every monitor: onto the nearest, and the layout follows.
d.monitors = vec![Monitor { name: Some("left".into()), rect: [0.0, 0.0, 1920.0, 1080.0], scale: 1.0 }];
d.layout.floating[0].native = Some([5000.0, 200.0, 260.0, 320.0]);
d.settle();
assert_eq!(d.host().windows[&viewport].rect.min, pos2(1660.0, 200.0));
assert_eq!(window(&d, 5).native, Some([1660.0, 200.0, 260.0, 320.0]));
// A bar drag that leaves too little of the bar on a monitor ends with the window on it.
let id = id_of(&d);
let grab = pos2(40.0, 9.0);
d.window_step(id, vec![Event::PointerMoved(grab), button(grab, true)]);
let far = d.window_local(id, pos2(1950.0, 300.0)).unwrap();
d.window_step(id, vec![Event::PointerMoved(far)]);
let far = d.window_local(id, pos2(1950.0, 300.0)).unwrap();
d.window_step(id, vec![Event::PointerMoved(far), button(far, false)]);
d.settle();
assert_eq!(d.host().windows[&viewport].rect.min, pos2(1660.0, 291.0));
assert_eq!(window(&d, 5).native, Some([1660.0, 291.0, 260.0, 320.0]));
}
#[test]
fn a_bar_drag_follows_the_pointer_when_moves_land_late() {
let mut d = driver(layout(true));
d.host().lag = true;
let id = id_of(&d);
let viewport = d.viewport(id);
let grab = pos2(40.0, 9.0);
let mut pointer = d.host().windows[&viewport].rect.min + grab.to_vec2();
d.window_step(id, vec![Event::PointerMoved(grab), button(grab, true)]);
// On to the desktop right of the main window, where the release docks nothing.
for _ in 0..6 {
pointer += vec2(90.0, -20.0);
// The window system reports the pointer relative to where the window is now.
let local = d.window_local(id, pointer).unwrap();
d.window_step(id, vec![Event::PointerMoved(local)]);
assert_eq!(moves(&d, viewport).last(), Some(&(pointer - grab.to_vec2())), "no overshoot");
}
let local = d.window_local(id, pointer).unwrap();
d.window_step(id, vec![button(local, false)]);
d.settle();
let to = pointer - grab.to_vec2();
assert_eq!(d.host().windows[&viewport].rect.min, to);
assert_eq!(window(&d, 5).native.map(|r| [r[0], r[1]]), Some([to.x, to.y]));
}
#[test]
fn a_native_windows_content_is_its_origin_where_its_frame_differs() {
// A title bar's height between the window's outer position and its content.
let mut d = DockDriver::new(Layout::default(), Content::default()).with_native_host(ORIGIN);
d.host().frame = vec2(0.0, 28.0);
d.layout = layout(true);
d.settle();
// It opened with its frame there, then moved so its content is.
let id = id_of(&d);
let viewport = d.viewport(id);
assert_eq!(d.host().windows[&viewport].rect.min, pos2(400.0, 130.0));
assert_eq!(window(&d, 5).native, Some([400.0, 130.0, 240.0, 300.0]));
// Its bar moves it by its outer position, here onto the desktop right of the main window.
let grab = pos2(40.0, 9.0);
d.window_step(id, vec![Event::PointerMoved(grab), button(grab, true)]);
d.window_step(id, vec![Event::PointerMoved(grab + vec2(520.0, 20.0))]);
assert_eq!(moves(&d, viewport).last(), Some(&pos2(920.0, 122.0)));
d.window_step(id, vec![button(grab, false)]);
d.settle();
assert_eq!(d.host().windows[&viewport].rect.min, pos2(920.0, 150.0));
assert_eq!(window(&d, 5).native.map(|r| [r[0], r[1]]), Some([920.0, 150.0]));
// Its tab drops where the pointer is on the desktop: the main window's tab row of [3, 4].
let row = d.window_local(id, ORIGIN + vec2(450.0, 13.0)).unwrap();
d.window_hold(id, pos2(15.0, TAB_ROW), row);
let out = d.window_release(id, row);
assert!(matches!(out.actions.as_slice(), [Action::Move { panel: 5, anchor: 3 | 4, placement: Placement::Tab { .. } }]), "{:?}", out.actions);
}
#[test]
fn the_only_tab_of_a_native_window_docks_it_with_title_bar_docks_off() {
let mut layout = layout(false);
layout.floating.push(Floating::single(vec![7], 0, [300.0, 80.0, 240.0, 200.0]));
let driver = || DockDriver::new(layout.clone(), Content::default()).with_style(|s| s.title_bar_docks = false).with_native_host(ORIGIN);
let mut d = driver();
let id = window(&d, 7).id;
let row = ORIGIN + vec2(450.0, 13.0);
let from = pos2(15.0, TAB_ROW);
d.window_step(id, vec![Event::PointerMoved(from), button(from, true)]);
d.window_step(id, vec![Event::PointerMoved(from + vec2(8.0, 6.0))]);
for _ in 0..2 {
let local = d.window_local(id, row).unwrap();
d.window_step(id, vec![Event::PointerMoved(local)]);
}
let local = d.window_local(id, row).unwrap();
let out = d.window_step(id, vec![button(local, false)]);
assert!(matches!(window_move(&out), Some(Target::At { anchor: 3 | 4, .. })), "{:?}", out.actions);
assert!(in_root(&d, 7));
// Its bar keeps to `title_bar_docks`.
let mut d = driver();
let out = drag_bar_to(&mut d, 7, pos2(450.0, 13.0));
assert_eq!(window_move(&out), None, "{:?}", out.actions);
assert!(!in_root(&d, 7));
}
#[test]
fn a_native_window_forwards_no_keys_while_one_of_its_widgets_has_focus() {
let mut d = driver(layout(true));
(d.content.text, d.content.focus_text) = (Some(5), true);
d.settle();
let key = Event::Key { key: Key::S, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::COMMAND };
let out = d.window_step(id_of(&d), vec![key, Event::Text("s".into())]);
assert!(out.forwarded_events.is_empty(), "{:?}", out.forwarded_events);
assert_eq!(d.content.typed, "s");
// A focused button's Enter is its own.
let mut d = driver(layout(true));
d.content.focused_button = Some(5);
d.settle();
let enter = Event::Key { key: Key::Enter, physical_key: None, pressed: true, repeat: false, modifiers: Modifiers::NONE };
let out = d.window_step(id_of(&d), vec![enter]);
assert!(out.forwarded_events.is_empty(), "{:?}", out.forwarded_events);
}
#[test]
fn escape_or_focus_loss_ends_a_drag_a_native_window_owns() {
// Escape cancels it, and is no shortcut.
let mut d = driver(layout(true));
let id = id_of(&d);
let over = d.window_local(id, ORIGIN + vec2(600.0, 300.0)).unwrap();
d.window_hold(id, pos2(15.0, TAB_ROW), over);
let out = d.window_step(id, vec![escape()]);
assert!(out.forwarded_events.is_empty(), "{:?}", out.forwarded_events);
let out = d.window_release(id, over);
assert!(!out.actions.iter().any(|a| matches!(a, Action::Move { .. } | Action::Float { .. })), "{:?}", out.actions);
assert!(window(&d, 5).contains(&6));
// Escape during a bar drag is no shortcut either.
let mut d = driver(layout(true));
d.window_step(id_of(&d), vec![Event::PointerMoved(pos2(40.0, 9.0)), button(pos2(40.0, 9.0), true)]);
d.window_step(id_of(&d), vec![Event::PointerMoved(pos2(60.0, 19.0))]);
let out = d.window_step(id_of(&d), vec![escape()]);
assert!(out.forwarded_events.is_empty(), "{:?}", out.forwarded_events);
// Another application takes focus: cancelled.
let mut d = driver(layout(true));
let id = id_of(&d);
d.window_hold(id, pos2(15.0, TAB_ROW), over);
d.host().focus = None;
d.window_step(id, vec![Event::PointerMoved(over)]);
d.host().focus = Some(d.viewport(id));
let out = d.window_release(id, over);
assert!(!out.actions.iter().any(|a| matches!(a, Action::Move { .. } | Action::Float { .. })), "{:?}", out.actions);
assert!(!super::is_dragging::<u32>(&d.ctx, d.id));
}
#[test]
fn focus_moving_to_a_native_window_mid_drag_keeps_the_drag() {
let mut d = driver(two_windows());
let viewport = d.viewport(window(&d, 7).id);
let tab = d.hit(&Hit::Tab(1)).unwrap().center();
let body = d.host().windows[&viewport].rect.center() - ORIGIN.to_vec2();
d.hold(tab, body);
d.host().focus = Some(viewport);
d.step(vec![Event::WindowFocused(false), Event::PointerMoved(body)]);
assert!(super::is_dragging::<u32>(&d.ctx, d.id));
let out = d.release(body);
assert!(matches!(out.actions.as_slice(), [Action::Move { panel: 1, anchor: 7, .. }]), "{:?}", out.actions);
}
#[test]
fn a_drag_ends_when_its_native_window_or_panel_goes() {
let pointer = |d: &DockDriver<u32, Content>| d.ctx.data(|data| data.get_temp::<DragPointer>(d.id.with("drag-pointer")));
let moving = |d: &DockDriver<u32, Content>| d.ctx.data(|data| data.get_temp::<WindowDrag>(d.id.with("window-drag")));
// The window closes during a tab drag it owns.
let mut d = driver(layout(true));
let id = id_of(&d);
let over = d.window_local(id, ORIGIN + vec2(600.0, 300.0)).unwrap();
d.window_hold(id, pos2(15.0, TAB_ROW), over);
let viewport = d.viewport(id);
d.host().windows.get_mut(&viewport).unwrap().close = true;
d.step(vec![]);
d.settle();
assert!(!d.layout.contains(&5));
assert!(!super::is_dragging::<u32>(&d.ctx, d.id));
assert_eq!(pointer(&d), None);
// The dragged panel is closed by the application.
let mut d = driver(layout(true));
let id = id_of(&d);
d.window_hold(id, pos2(15.0, TAB_ROW), over);
d.layout.apply(Action::Close { panel: 5 }).unwrap();
let out = d.window_release(id, over);
assert!(out.error.is_none() && !out.actions.iter().any(|a| matches!(a, Action::Move { .. } | Action::Float { .. })), "{:?}", out.actions);
d.settle();
assert!(!super::is_dragging::<u32>(&d.ctx, d.id));
assert_eq!(pointer(&d), None);
// The window closes during its own bar drag.
let mut d = driver(layout(true));
let id = id_of(&d);
d.window_step(id, vec![Event::PointerMoved(pos2(40.0, 9.0)), button(pos2(40.0, 9.0), true)]);
d.window_step(id, vec![Event::PointerMoved(pos2(60.0, 19.0))]);
assert!(moving(&d).is_some());
d.layout.apply(Action::CloseWindow { panel: 5 }).unwrap();
d.settle();
assert_eq!(moving(&d), None);
assert_eq!(pointer(&d), None);
// The main window's drags work as before.
let tab = d.hit(&Hit::Tab(1)).unwrap().center();
let out = d.drag(tab, pos2(600.0, 300.0));
assert!(matches!(out.actions.as_slice(), [Action::Move { panel: 1, anchor: 3 | 4, .. }]), "{:?}", out.actions);
}
+20 -8
View File
@@ -42,6 +42,9 @@ pub(in crate::docking) struct DragPointer {
pub command: bool,
/// The window a bar drag moves. It follows the pointer and is never its own target.
pub window: Option<u64>,
/// That drag began on the window's only tab or group: it docks whatever
/// [`crate::docking::DockStyle::title_bar_docks`] says.
pub docks: bool,
}
impl DragPointer {
@@ -93,6 +96,10 @@ pub(in crate::docking) struct Cross<P> {
pub bar: bool,
/// A target was offered while `quiet`.
pub offered: Cell<bool>,
/// Where the native window drawing now was told to move its content this frame.
pub moved: Option<Pos2>,
/// Resolving a window move that docks whatever `title_bar_docks` says.
pub docks: bool,
}
impl<P> Default for Cross<P> {
@@ -108,14 +115,16 @@ impl<P> Default for Cross<P> {
quiet: false,
bar: false,
offered: Cell::new(false),
moved: None,
docks: false,
}
}
}
impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
/// Write the pointer of the drag this window owns. `origin` is the window's desktop
/// origin for this frame's input.
pub(super) fn record_pointer(&mut self, ctx: &Context, owner: Option<u64>, origin: Option<Pos2>, window: Option<u64>) -> DragPointer {
/// origin for this frame's input; `window` the bar drag it is.
pub(super) fn record_pointer(&mut self, ctx: &Context, owner: Option<u64>, origin: Option<Pos2>, window: Option<&WindowDrag>) -> DragPointer {
let key = self.id.with(POINTER);
let kept = ctx.data(|d| d.get_temp::<DragPointer>(key)).filter(|p| p.owner == owner).and_then(|p| p.local);
let pointer = ctx.input(|i| DragPointer {
@@ -126,7 +135,8 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
released: i.pointer.primary_released(),
escape: i.key_pressed(Key::Escape),
command: i.modifiers.command || i.modifiers.ctrl || i.modifiers.mac_cmd,
window,
window: window.map(|w| w.window),
docks: window.is_some_and(|w| w.docks),
});
ctx.data_mut(|d| d.insert_temp(key, pointer));
self.cross.pointer = Some(pointer);
@@ -290,21 +300,21 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
let Some(pointer) = self.cross.pointer.filter(|p| p.window.is_some() && !p.released) else { return };
let Some(drag) = pointer.window.and_then(|w| self.window_group_drag(w)) else { return };
if let Some(Dest::Main(local)) = self.destination(&pointer, None) {
(self.cross.quiet, self.cross.bar) = (true, true);
(self.cross.quiet, self.cross.bar, self.cross.docks) = (true, true, pointer.docks);
self.paint_group_drag(ui, &drag, local, pointer.command);
(self.cross.quiet, self.cross.bar) = (false, false);
(self.cross.quiet, self.cross.bar, self.cross.docks) = (false, false, false);
}
}
/// Release of a bar drag: over a target in another window the whole window docks or
/// joins there. Over the desktop, or with Ctrl/Cmd held, it stays where it was moved.
fn drop_native_window(&mut self, window: u64, pointer: &DragPointer) {
let Some(drag) = self.window_group_drag(window).filter(|_| !pointer.command && self.style.title_bar_docks) else { return };
let Some(drag) = self.window_group_drag(window).filter(|_| !pointer.command && (pointer.docks || self.style.title_bar_docks)) else { return };
match self.destination(pointer, None) {
Some(Dest::Main(local)) => {
self.cross.bar = true;
(self.cross.bar, self.cross.docks) = (true, pointer.docks);
self.drop_window(&drag, local, false);
self.cross.bar = false;
(self.cross.bar, self.cross.docks) = (false, false);
}
Some(Dest::Window(target, local)) => {
let action = self.within_window(target, true, |r| {
@@ -353,7 +363,9 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
let ctx = ui.ctx();
if let Some(window) = pointer.window {
if let Some(drag) = self.window_group_drag(window) {
self.cross.docks = pointer.docks;
self.paint_group_drag(ui, &drag, local, pointer.command);
self.cross.docks = false;
}
return;
}
+11 -1
View File
@@ -220,11 +220,21 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
matches!(self.place(panel), Some(crate::docking::dock_model::Place::Floating { path, .. }) if path.is_empty())
}
/// Whether moving a whole window docks it: [`crate::docking::DockStyle::title_bar_docks`],
/// or a native window moved by its only tab or group.
pub(super) fn title_bar_docks(&self) -> bool {
#[cfg(feature = "native-windows")]
if self.cross.docks {
return true;
}
self.style.title_bar_docks
}
/// Release of a title bar drag. The window already sits where it was dragged; a dock or
/// group target docks or merges every group in it, else hosts get the release in
/// `group_drop`.
pub(super) fn drop_window(&mut self, drag: &GroupDrag<P>, pointer: Pos2, float_only: bool) {
let float_only = float_only || !self.style.title_bar_docks;
let float_only = float_only || !self.title_bar_docks();
if !float_only && let Some((action, _)) = self.dock_group_proposal(drag, pointer) {
self.output.actions.extend(action);
return;
+1 -1
View File
@@ -15,7 +15,7 @@ mod iconic;
#[cfg(feature = "native-windows")]
mod native;
#[cfg(all(test, feature = "native-windows"))]
pub(in crate::docking) use native::floats_shown;
pub(in crate::docking) use {cross::DragPointer, native::WindowDrag, native::floats_shown};
/// The viewport of the native window `window` of the dock area `area`.
#[cfg(feature = "native-windows")]
+190 -82
View File
@@ -7,21 +7,25 @@
use std::{fmt::Debug, hash::Hash};
use egui::{Context, Event, Id, Pos2, Rect, Ui, Vec2, ViewportBuilder, ViewportClass, ViewportCommand, ViewportId, WindowLevel, pos2, vec2};
use egui::{
Context, Event, Id, Key, Modifiers, Pos2, Rect, Ui, Vec2, ViewportBuilder, ViewportClass, ViewportCommand, ViewportId, ViewportInfo, WindowLevel, pos2,
vec2,
};
use super::Renderer;
use super::cross::{DragPointer, Surface};
use crate::docking::chrome::{ChromeStyle, TitleBar};
use crate::docking::placement::{monitor_of, restore, sanitized};
pub(in crate::docking) use crate::docking::placement::BAR_HEIGHT;
use crate::docking::placement::{kept_reachable, monitor_of, restore, sanitized};
use crate::docking::{Action, Drag, Floating, GroupDrag, GroupGesture, Node};
/// Smallest native window.
const MIN_SIZE: [f32; 2] = [160.0, 100.0];
/// Height of the bar drawn at the top of a native window.
pub(in crate::docking) const BAR_HEIGHT: f32 = 18.0;
/// How long floating windows stay after the application's last window lost focus, so focus
/// moving between our own windows does not hide them.
const HIDE_DELAY: f64 = 0.25;
/// How long reports of a native window's place may lag a move the dock area commanded.
const MOVE_WAIT: f64 = 0.5;
/// A drag of a native window's bar, in the context's data under the area's `"window-drag"`.
#[derive(Clone, Copy, Debug, PartialEq)]
@@ -30,11 +34,40 @@ pub(in crate::docking) struct WindowDrag {
pub window: u64,
/// Where the pointer pressed, in the window's local points.
pub grab: Vec2,
/// The window's outer position on the desktop: reported when the drag began, then as last
/// commanded. The reported position lags and jitters while the window moves.
pub origin: Pos2,
/// Where the window was when the drag began, for Escape.
/// Desktop position of the window's content as last commanded.
pub to: Pos2,
/// Desktop position of the window's content when the drag began, for Escape.
pub start: Pos2,
/// The drag began on the window's only tab or group, so it docks whatever
/// [`crate::docking::DockStyle::title_bar_docks`] says.
pub docks: bool,
}
/// What the dock area knows of a shown native window's place, in the context's data under
/// the area's `("native-place", viewport)`. Rects are desktop rects of the window's content.
#[derive(Clone, Copy, Debug)]
struct Place {
/// Where it opened. The builder keeps it, so egui sends no move of its own.
spawn: [f32; 4],
/// As last reported or commanded. [`Floating::native`] follows it; a `native` that differs
/// was changed by the application (undo, reset, a reloaded layout), and the window moves.
known: [f32; 4],
/// Until when reports that differ from `known` are a commanded move that has not landed.
pending: Option<f64>,
/// The window system put the frame, not the content, where it opened: corrected once.
corrected: bool,
}
/// Whether two rects differ by more than rounding.
fn differs(a: [f32; 4], b: [f32; 4]) -> bool {
a.iter().zip(b).any(|(a, b)| (a - b).abs() > 0.5)
}
/// Desktop position of a viewport's content, and its offset from the window's outer
/// position, where the window system reports both.
fn content_of(info: &ViewportInfo) -> Option<(Pos2, Vec2)> {
let (outer, inner) = (info.outer_rect?, info.inner_rect?);
(outer.min.is_finite() && inner.min.is_finite()).then(|| (inner.min, inner.min - outer.min))
}
/// The viewport of the native window `window` of the dock area `area`. The `n`th window with
@@ -52,17 +85,20 @@ pub(in crate::docking) fn floats_shown(focused: bool, now: f64, last_focused: f6
focused || now - last_focused < HIDE_DELAY
}
/// The desktop rect a native viewport reports: outer position, inner size. None when the
/// backend reports nothing usable.
fn placed(outer: Option<Rect>, inner: Option<Rect>) -> Option<[f32; 4]> {
let (outer, inner) = (outer?, inner?);
(outer.min.is_finite() && inner.size().is_finite()).then(|| sanitized([outer.min.x, outer.min.y, inner.width(), inner.height()]))
/// The desktop rect of a native viewport's content. None when the backend reports no
/// positions.
fn placed(info: &ViewportInfo) -> Option<[f32; 4]> {
let (min, _) = content_of(info)?;
let size = info.inner_rect?.size();
size.is_finite().then(|| sanitized([min.x, min.y, size.x, size.y]))
}
/// What a native window's frame reports back to the main window.
struct Report<P> {
closed: bool,
placed: Option<[f32; 4]>,
/// From the window's outer position to its content.
frame: Vec2,
monitor_size: Option<Vec2>,
/// Its drop targets, for drags from other windows.
surface: Option<Surface<P>>,
@@ -70,7 +106,7 @@ struct Report<P> {
impl<P> Default for Report<P> {
fn default() -> Self {
Self { closed: false, placed: None, monitor_size: None, surface: None }
Self { closed: false, placed: None, frame: Vec2::ZERO, monitor_size: None, surface: None }
}
}
@@ -87,22 +123,25 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
let mut natives = Vec::new();
for window in windows {
let allowed = window.panels().all(|p| self.content.allow_native(p) && self.permissions(p).floatable);
let Some(first) = window.panels().next().cloned().filter(|_| native && allowed) else {
if !native || !allowed || window.panels().next().is_none() {
in_window.push(window);
continue;
};
}
// Every float is native: one still in-window opens over its place in the app window.
let rect = window.native.unwrap_or_else(|| {
let [x, y, w, h] = window.rect;
let rect = sanitized([origin.x + x, origin.y + y, w, h]);
self.output.actions.push(Action::SetNative { panel: first, rect: Some(rect) });
rect
sanitized([origin.x + x, origin.y + y, w, h])
});
let n = ids.iter().filter(|id| **id == window.id).count();
ids.push(window.id);
natives.push((window, rect, viewport_id(self.id, window.id, n)));
}
if native {
// A window closed or docked during its own bar drag ends the drag.
let key = self.id.with("window-drag");
if ctx.data(|d| d.get_temp::<WindowDrag>(key)).is_some_and(|d| !natives.iter().any(|(w, ..)| w.id == d.window)) {
ctx.data_mut(|d| d.remove::<WindowDrag>(key));
}
self.map_desktop(ctx, &natives, visibility.0);
// The main window owns drags of the panels it draws.
let id = self.id;
@@ -124,7 +163,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
let previous = ctx.data_mut(|d| d.remove_temp::<Vec<Id>>(key)).unwrap_or_default();
ctx.data_mut(|d| {
for id in previous.iter().filter(|id| !shown.contains(id)) {
d.remove::<[f32; 4]>(*id);
d.remove::<Place>(*id);
}
d.insert_temp(key, shown);
});
@@ -137,7 +176,6 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
let key = self.id.with("native-order");
let mut order = ctx.data(|d| d.get_temp::<Vec<u64>>(key)).unwrap_or_default();
order.retain(|id| natives.iter().any(|(w, ..)| w.id == *id));
let moving = ctx.data(|d| d.get_temp::<WindowDrag>(self.id.with("window-drag")));
let mut windows = Vec::new();
for (window, rect, viewport) in natives {
let info = ctx.input(|i| i.raw.viewports.get(viewport).cloned());
@@ -149,10 +187,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
let [x, y, w, h] = *rect;
let placed = match info {
None => Some(Rect::from_min_size(pos2(x, y), vec2(w, h))),
Some(info) => info.outer_rect.zip(info.inner_rect).map(|(outer, inner)| {
let min = moving.filter(|m| m.window == window.id).map_or(outer.min, |m| m.origin);
Rect::from_min_size(min, inner.size())
}),
Some(info) => placed(&info).map(|[x, y, w, h]| Rect::from_min_size(pos2(x, y), vec2(w, h))),
};
if let Some(placed) = placed.filter(|r| r.is_finite() && visible) {
windows.push((window.id, placed));
@@ -184,32 +219,61 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
(visible, visible != was)
}
/// One floating window in its own viewport. Returns the key of its spawn rect.
/// One floating window in its own viewport. Returns the key of its [`Place`].
fn native_window(&mut self, ctx: &Context, window: &Floating<P>, rect: [f32; 4], viewport: ViewportId, visibility: (bool, bool)) -> Option<Id> {
let (visible, toggled) = visibility;
let first = window.panels().next()?.clone();
// The window opens at its saved place, moved onto a connected monitor; after that the
// window system owns its position and size, which are read back below.
let spawn_key = self.id.with(("native-spawn", viewport));
let spawned = ctx.data(|d| d.get_temp::<[f32; 4]>(spawn_key));
let spawn = spawned.unwrap_or_else(|| {
let (main, size) = ctx.input(|i| (i.viewport().inner_rect, i.viewport().monitor_size));
restore(rect, window.monitor.as_ref(), &self.monitors, main, size)
});
ctx.data_mut(|d| d.insert_temp(spawn_key, spawn));
let key = self.id.with(("native-place", viewport));
let now = ctx.input(|i| i.time);
let drag_key = self.id.with("window-drag");
let moving = |ctx: &Context| ctx.data(|d| d.get_temp::<WindowDrag>(drag_key)).is_some_and(|d| d.window == window.id);
let info = ctx.input(|i| i.raw.viewports.get(&viewport).cloned());
let frame = info.as_ref().and_then(content_of).map_or(Vec2::ZERO, |(_, frame)| frame);
let mut place = match ctx.data(|d| d.get_temp::<Place>(key)) {
Some(mut place) => {
// The application changed the window's place (undo, reset, a reloaded layout):
// the OS window moves there, kept where its bar can be grabbed.
if !moving(ctx)
&& let Some(native) = window.native.filter(|native| differs(*native, place.known))
{
let target = kept_reachable(native, &self.monitors);
ctx.send_viewport_cmd_to(viewport, ViewportCommand::OuterPosition(pos2(target[0], target[1]) - frame));
ctx.send_viewport_cmd_to(viewport, ViewportCommand::InnerSize(vec2(target[2], target[3])));
(place.known, place.pending) = (target, Some(now + MOVE_WAIT));
}
place
}
None => {
// A window of a loaded layout opens at its saved place, moved onto a connected
// monitor. One made this session opens where it was put.
let seen_key = self.id.with("native-seen");
let mut seen = ctx.data(|d| d.get_temp::<std::collections::HashSet<u64>>(seen_key)).unwrap_or_default();
let spawn = if window.native.is_some() && !seen.contains(&window.id) {
let (main, size) = ctx.input(|i| (i.viewport().inner_rect, i.viewport().monitor_size));
restore(rect, window.monitor.as_ref(), &self.monitors, main, size)
} else {
sanitized(rect)
};
seen.insert(window.id);
ctx.data_mut(|d| d.insert_temp(seen_key, seen));
Place { spawn, known: spawn, pending: Some(now + MOVE_WAIT), corrected: false }
}
};
let spawn = place.spawn;
let title = crate::docking::dock_model::first_active(&window.root).map(|p| self.label_of(p)).unwrap_or_default();
let builder = ViewportBuilder::default()
.with_title(title)
.with_position([spawn[0], spawn[1]])
.with_inner_size([spawn[2], spawn[3]])
.with_min_inner_size(MIN_SIZE)
.with_window_level(WindowLevel::AlwaysOnTop);
.with_window_level(WindowLevel::AlwaysOnTop)
.with_taskbar(false);
// The bar replaces the OS title bar. macOS keeps its frame for edge resizing.
#[cfg(target_os = "macos")]
let builder = builder.with_titlebar_shown(false).with_title_shown(false).with_fullsize_content_view(true).with_titlebar_buttons_shown(false);
#[cfg(not(target_os = "macos"))]
let builder = builder.with_decorations(false);
if toggled || (spawned.is_none() && !visible) {
if toggled || (info.is_none() && !visible) {
ctx.send_viewport_cmd_to(viewport, ViewportCommand::Visible(visible));
}
if visible && self.activation.as_ref().is_some_and(|panel| window.contains(panel)) {
@@ -244,32 +308,57 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
self.forward_events(ui);
ui.input(|i| Report {
closed: i.viewport().close_requested(),
placed: placed(i.viewport().outer_rect, i.viewport().inner_rect),
placed: placed(i.viewport()),
frame: content_of(i.viewport()).map_or(Vec2::ZERO, |(_, frame)| frame),
monitor_size: i.viewport().monitor_size,
surface: Some(surface),
})
});
// Targets in another OS window are in its coordinates.
self.targets.truncate(targets);
self.dock_targets.truncate(docks);
self.frame.windows.truncate(windows);
// Targets in another OS window are in its coordinates. An embedded fallback drew its
// targets in the main window's.
if report.surface.is_some() {
self.targets.truncate(targets);
self.dock_targets.truncate(docks);
self.frame.windows.truncate(windows);
}
self.cross.surfaces.extend(report.surface);
if let Some(to) = self.cross.moved.take() {
(place.known[0], place.known[1], place.pending) = (to.x, to.y, Some(now + MOVE_WAIT));
}
let moving = moving(ctx);
if let Some(placed) = report.placed.filter(|_| !moving) {
let waiting = place.pending.is_some_and(|until| now < until);
if !differs(placed, place.known) {
place.pending = None;
} else if waiting {
// The window system put the window's frame where its content should be.
let outer = pos2(placed[0], placed[1]) - report.frame;
if !place.corrected && report.frame.length() > 0.5 && outer.distance(pos2(place.known[0], place.known[1])) < 0.5 {
place.corrected = true;
ctx.send_viewport_cmd_to(viewport, ViewportCommand::OuterPosition(pos2(place.known[0], place.known[1]) - report.frame));
}
} else {
(place.known, place.pending) = (placed, None);
}
}
ctx.data_mut(|d| d.insert_temp(key, place));
if report.closed {
// Like the bar's ×: its panels close and reopen from the application's menu.
self.output.actions.push(Action::CloseWindow { panel: first });
return Some(spawn_key);
return Some(key);
}
let now = report.placed.unwrap_or(rect);
if let Some(placed) = report.placed.filter(|placed| placed.iter().zip(rect).any(|(a, b)| (a - b).abs() > 0.5)) {
self.output.actions.push(Action::SetNative { panel: first.clone(), rect: Some(placed) });
// The layout follows the window's place, once a bar drag is over.
let known = place.known;
if !moving && window.native.is_none_or(|native| differs(native, known)) {
self.output.actions.push(Action::SetNative { panel: first.clone(), rect: Some(known) });
// The in-window rect follows, for builds that draw the window in-window.
if window.panels().all(|p| self.permissions(p).movable) {
let origin = ctx.input(|i| i.viewport().inner_rect.map(|r| r.min)).filter(|p| p.is_finite()).unwrap_or(Pos2::ZERO);
let [x, y, w, h] = placed;
let [x, y, w, h] = known;
self.output.actions.push(Action::MoveFloating { panel: first.clone(), rect: [x - origin.x, y - origin.y, w, h] });
}
}
let monitor = monitor_of(&self.monitors, now).cloned();
let monitor = monitor_of(&self.monitors, known).cloned();
let size = match monitor {
Some(_) => None,
None => report.monitor_size.filter(|s| s.is_finite() && s.x > 0.0 && s.y > 0.0).map(|s| [s.x, s.y]),
@@ -277,7 +366,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
if (monitor.is_some() || size.is_some()) && (monitor != window.monitor || size != window.monitor_size) {
self.output.actions.push(Action::SetMonitor { panel: first, monitor, monitor_size: size });
}
Some(spawn_key)
Some(key)
}
/// The bar at the top of a native window: its title and ×. Dragging it moves the window.
@@ -300,13 +389,13 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
self.output.actions.push(Action::CloseWindow { panel: first });
}
if movable && response.bar.drag_started() {
self.start_window_drag(ui, window.id);
self.start_window_drag(ui, window.id, false);
}
}
/// After a native window's tree drew: a drag of its only tab, or of its only group, moves
/// the window as its bar does. Then the window follows a bar drag, and writes the pointer
/// of a drag it owns. Returns the window's desktop origin, where known.
/// of a drag it owns. Returns the desktop position of the window's content, where known.
fn window_gestures(&mut self, ui: &Ui, window: &Floating<P>) -> Option<Pos2> {
let ctx = ui.ctx();
let (drag_key, group_key) = (self.id.with("drag"), self.id.with("group-drag"));
@@ -319,60 +408,79 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
d.remove::<Drag<P>>(drag_key);
d.remove::<GroupDrag<P>>(group_key);
});
self.start_window_drag(ui, window.id);
self.start_window_drag(ui, window.id, true);
}
let outer = ui.input(|i| i.viewport().outer_rect).map(|r| r.min).filter(|p| p.is_finite());
if let Some(origin) = self.follow_window_drag(ui, window.id) {
return Some(origin);
let origin = ui.input(|i| content_of(i.viewport())).map(|(origin, _)| origin);
if self.follow_window_drag(ui, window.id) {
return origin;
}
let owns = ctx.data(|d| d.get_temp::<Drag<P>>(drag_key).map(|d| d.panel).or(d.get_temp::<GroupDrag<P>>(group_key).map(|d| d.panel)));
if owns.is_some_and(|panel| window.contains(&panel)) {
self.record_pointer(ctx, Some(window.id), outer, None);
if owns.is_some_and(|panel| window.contains(&panel)) && self.record_pointer(ctx, Some(window.id), origin, None).escape {
// The drag's cancel, not a shortcut.
ui.input_mut(|i| i.consume_key(Modifiers::NONE, Key::Escape));
}
outer
origin
}
/// Start moving the window with the pointer, from the position the window system reports,
/// or, where it reports none (Wayland), by handing the move to it.
fn start_window_drag(&mut self, ui: &Ui, window: u64) {
/// or, where it reports none (Wayland), by handing the move to it. `docks`: the drag began
/// on the window's only tab or group.
fn start_window_drag(&mut self, ui: &Ui, window: u64, docks: bool) {
let press = ui.input(|i| i.pointer.press_origin().or(i.pointer.latest_pos()));
match (ui.input(|i| i.viewport().outer_rect), press) {
(Some(outer), Some(press)) if outer.min.is_finite() => {
let drag = WindowDrag { window, grab: press.to_vec2(), origin: outer.min, start: outer.min };
match (ui.input(|i| content_of(i.viewport())), press) {
(Some((origin, _)), Some(press)) => {
let drag = WindowDrag { window, grab: press.to_vec2(), to: origin, start: origin, docks };
ui.ctx().data_mut(|d| d.insert_temp(self.id.with("window-drag"), drag));
}
_ => ui.ctx().send_viewport_cmd(ViewportCommand::StartDrag),
}
}
/// Move the window by `OuterPosition` from the position last commanded, while the button
/// is held. Escape puts it back; losing focus leaves it. Returns the desktop origin of
/// this frame's input while the window is moving.
fn follow_window_drag(&mut self, ui: &Ui, window: u64) -> Option<Pos2> {
/// Move the window by `OuterPosition` while the button is held, so the pressed point stays
/// under the pointer. Escape puts it back; losing focus leaves it. Returns whether the
/// window is moving.
fn follow_window_drag(&mut self, ui: &Ui, window: u64) -> bool {
let key = self.id.with("window-drag");
let mut drag = ui.ctx().data(|d| d.get_temp::<WindowDrag>(key)).filter(|d| d.window == window)?;
let origin = drag.origin;
let pointer = self.record_pointer(ui.ctx(), Some(window), Some(origin), Some(window));
let Some(mut drag) = ui.ctx().data(|d| d.get_temp::<WindowDrag>(key)).filter(|d| d.window == window) else { return false };
let content = ui.input(|i| content_of(i.viewport()));
let pointer = self.record_pointer(ui.ctx(), Some(window), content.map(|(origin, _)| origin), Some(&drag));
let frame = content.map_or(Vec2::ZERO, |(_, frame)| frame);
let move_to = |r: &mut Self, to: Pos2| {
ui.ctx().send_viewport_cmd(ViewportCommand::OuterPosition(to - frame));
r.cross.moved = Some(to);
};
if pointer.escape || !self.cross.focused {
if pointer.escape {
ui.ctx().send_viewport_cmd(ViewportCommand::OuterPosition(drag.start));
move_to(self, drag.start);
ui.input_mut(|i| i.consume_key(Modifiers::NONE, Key::Escape));
}
ui.ctx().data_mut(|d| d.remove::<WindowDrag>(key));
return Some(origin);
return true;
}
if !pointer.down {
ui.ctx().data_mut(|d| d.remove::<WindowDrag>(key));
// Left with too little of its bar on a monitor to grab it: back onto the nearest.
if let Some(size) = ui.input(|i| i.viewport().inner_rect.map(|r| r.size())) {
let [x, y, ..] = kept_reachable([drag.to.x, drag.to.y, size.x, size.y], &self.monitors);
if pos2(x, y).distance(drag.to) > 0.5 {
move_to(self, pos2(x, y));
}
}
self.end_window_drag(drag, pointer);
return Some(origin);
return true;
}
// The pointer on the desktop is the commanded origin plus the local pointer.
let step = ui.input(|i| i.pointer.latest_pos()).filter(|p| p.is_finite()).map_or(Vec2::ZERO, |p| p.to_vec2() - drag.grab);
if step != Vec2::ZERO {
drag.origin += step;
ui.ctx().send_viewport_cmd(ViewportCommand::OuterPosition(drag.origin));
ui.ctx().data_mut(|d| d.insert_temp(key, drag));
// This frame's pointer is relative to where the window was when the window system sent
// it, which is where it reports the window now: a move commanded earlier may not have
// landed yet.
if let (Some((origin, _)), Some(local)) = (content, ui.input(|i| i.pointer.latest_pos()).filter(|p| p.is_finite())) {
let to = origin + (local.to_vec2() - drag.grab);
if to.distance(drag.to) > 0.0 {
drag.to = to;
move_to(self, to);
ui.ctx().data_mut(|d| d.insert_temp(key, drag));
}
}
Some(origin)
true
}
/// Release of a bar drag. The window already sits where it was dragged; over a target in
@@ -382,9 +490,9 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
}
/// Keys, text and clipboard events the window's widgets left over, for the application's
/// shortcuts. Nothing while a text field of the window has focus.
/// shortcuts. Nothing while a widget of the window has keyboard focus: its keys are its own.
fn forward_events(&mut self, ui: &Ui) {
if ui.ctx().text_edit_focused() {
if ui.memory(|m| m.focused().is_some()) {
return;
}
let events = ui.input(|i| {
+1 -1
View File
@@ -250,7 +250,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
x += advance;
}
}
if float_only || (drag.gesture == GroupGesture::Window && !self.style.title_bar_docks) {
if float_only || (drag.gesture == GroupGesture::Window && !self.title_bar_docks()) {
return;
}
if let Some((action, line)) = self.dock_group_proposal(drag, pointer) {
+37 -5
View File
@@ -116,6 +116,7 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static, C: DockContent<P>> Do
if let Some(host) = &self.host {
let mut host = host.borrow_mut();
(host.time, host.screen) = (self.time, self.screen);
host.frames += 1;
input.viewports = host.infos();
input.focused = host.focus == Some(egui::ViewportId::ROOT);
}
@@ -142,7 +143,9 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static, C: DockContent<P>> Do
full.textures_delta.clear();
#[cfg(feature = "native-windows")]
if let Some(host) = &self.host {
host.borrow_mut().apply(&full);
let mut host = host.borrow_mut();
host.apply(&full);
host.land();
}
self.shapes = full.shapes;
output
@@ -347,9 +350,19 @@ pub struct NativeHost {
pub focus: Option<egui::ViewportId>,
/// Windows report no position, as on Wayland.
pub no_position: bool,
/// From a native window's outer position to its content, as a title bar puts it. The
/// window opens with its outer position where it was asked to, and `OuterPosition` moves
/// that.
pub frame: egui::Vec2,
/// `OuterPosition` lands a frame late, at the end of the frame after the command. That
/// frame still reports the old place, and gets pointer events relative to it.
pub lag: bool,
pub windows: std::collections::HashMap<egui::ViewportId, HostWindow>,
/// Every command sent to a native window, in order.
pub commands: Vec<(egui::ViewportId, egui::ViewportCommand)>,
/// Moves waiting to land under `lag`: window, content position, frame sent.
late: Vec<(egui::ViewportId, Pos2, usize)>,
frames: usize,
screen: Rect,
time: f64,
}
@@ -358,7 +371,8 @@ pub struct NativeHost {
#[cfg(feature = "native-windows")]
#[derive(Debug)]
pub struct HostWindow {
/// Desktop rect of its content: where it opened, then moved by `OuterPosition`.
/// Desktop rect of its content: where it opened, then moved by `OuterPosition` and
/// `InnerSize`.
pub rect: Rect,
pub visible: bool,
/// Frames it drew.
@@ -379,8 +393,12 @@ impl NativeHost {
monitor_size: None,
focus: Some(egui::ViewportId::ROOT),
no_position: false,
frame: egui::Vec2::ZERO,
lag: false,
windows: Default::default(),
commands: Vec::new(),
late: Vec::new(),
frames: 0,
screen: Rect::NOTHING,
time: 0.0,
}
@@ -390,7 +408,7 @@ impl NativeHost {
fn infos(&self) -> egui::ViewportIdMap<egui::ViewportInfo> {
let info = |id: egui::ViewportId, rect: Rect| egui::ViewportInfo {
inner_rect: Some(rect),
outer_rect: (!self.no_position).then_some(rect),
outer_rect: (!self.no_position).then(|| if id == egui::ViewportId::ROOT { rect } else { Rect::from_min_max(rect.min - self.frame, rect.max) }),
focused: Some(self.focus == Some(id)),
monitor_size: self.monitor_size,
..Default::default()
@@ -407,7 +425,7 @@ impl NativeHost {
let input = {
let mut host = host.borrow_mut();
let builder = &viewport.builder;
let rect = Rect::from_min_size(builder.position.unwrap_or_default(), builder.inner_size.unwrap_or(vec2(300.0, 200.0)));
let rect = Rect::from_min_size(builder.position.unwrap_or_default() + host.frame, builder.inner_size.unwrap_or(vec2(300.0, 200.0)));
let visible = builder.visible.unwrap_or(true);
host.windows.entry(id).or_insert_with(|| HostWindow { rect, visible, frames: 0, events: Vec::new(), close: false, shapes: Vec::new() });
let mut viewports = host.infos();
@@ -438,6 +456,18 @@ impl NativeHost {
}
}
/// End a frame: moves sent in an earlier one land.
fn land(&mut self) {
let frames = self.frames;
let (landed, late) = std::mem::take(&mut self.late).into_iter().partition::<Vec<_>, _>(|(.., sent)| *sent < frames);
self.late = late;
for (id, pos, _) in landed {
if let Some(window) = self.windows.get_mut(&id) {
window.rect = Rect::from_min_size(pos, window.rect.size());
}
}
}
/// Carry out the commands a frame sent to native windows.
fn apply(&mut self, full: &egui::FullOutput) {
for (id, output) in &full.viewport_output {
@@ -448,7 +478,9 @@ impl NativeHost {
self.commands.push((*id, command.clone()));
let Some(window) = self.windows.get_mut(id) else { continue };
match command {
egui::ViewportCommand::OuterPosition(pos) => window.rect = Rect::from_min_size(*pos, window.rect.size()),
egui::ViewportCommand::OuterPosition(pos) if self.lag => self.late.push((*id, *pos + self.frame, self.frames)),
egui::ViewportCommand::OuterPosition(pos) => window.rect = Rect::from_min_size(*pos + self.frame, window.rect.size()),
egui::ViewportCommand::InnerSize(size) => window.rect = Rect::from_min_size(window.rect.min, *size),
egui::ViewportCommand::Visible(visible) => window.visible = *visible,
_ => {}
}