From d9c4ee3dba4dd6ae71668ec595f4f0c8bd5e1ac5 Mon Sep 17 00:00:00 2001 From: Tim Boudreau Date: Thu, 30 May 2024 03:19:42 -0400 Subject: [PATCH] Implement a number of window management features * Window show/hide (particularly needed for repeatedly shown popups where you don't want to allocate a native window on every call, but just reposition and reveal) * Window location / size / bounds management * Fetching the bounds of the monitor a window is on (needed for relative positioning of windows so they don't go offscreen) * Minimized / maximized management * Edited status management (MacOS only) Tested on Mac OS Ventura 13.6.6 / Intel and Gentoo Linux 6.7.6 w/ Openbox WM. Attempted to test on Wayland, but ran into wgpu crashes with the OpenGL back-end and I don't have the DRM back-end built. --- src/app_handle.rs | 4 +- src/id.rs | 14 +- src/lib.rs | 2 +- src/screen_layout.rs | 100 ++++++++- src/window_id.rs | 462 ++++++++++++++++++++++++++++++++++++++++- src/window_tracking.rs | 36 ++-- 6 files changed, 568 insertions(+), 50 deletions(-) diff --git a/src/app_handle.rs b/src/app_handle.rs index 888c6471..56c7c2e2 100644 --- a/src/app_handle.rs +++ b/src/app_handle.rs @@ -17,6 +17,7 @@ use crate::{ view::View, window::WindowConfig, window_handle::WindowHandle, + window_id::process_window_updates, }; pub(crate) struct ApplicationHandle { @@ -391,8 +392,9 @@ impl ApplicationHandle { } fn handle_updates_for_all_windows(&mut self) { - for (_, handle) in self.window_handles.iter_mut() { + for (window_id, handle) in self.window_handles.iter_mut() { handle.process_update(); + while process_window_updates(window_id) {} } } diff --git a/src/id.rs b/src/id.rs index f0e63e70..becc3c7c 100644 --- a/src/id.rs +++ b/src/id.rs @@ -22,10 +22,7 @@ use crate::{ view::{IntoView, View}, view_state::{ChangeFlags, StackOffset, ViewState}, view_storage::VIEW_STORAGE, - window_tracking::{ - monitor_bounds, window_id_for_root, window_inner_screen_bounds, - window_inner_screen_position, window_outer_screen_bounds, window_outer_screen_position, - }, + window_tracking::window_id_for_root, ScreenLayout, }; @@ -258,19 +255,12 @@ impl ViewId { self.request_changes(ChangeFlags::LAYOUT) } - pub fn is_in_active_window(&self) -> bool { - if let Some(root) = self.root() { - return crate::window_handle::get_current_view() == root; - } - false - } - + /// Get the window id of the window containing this view, if there is one. pub fn window_id(&self) -> Option { self.root().and_then(window_id_for_root) } pub fn request_paint(&self) { - let active = self.is_in_active_window(); self.add_update_message(UpdateMessage::RequestPaint); } diff --git a/src/lib.rs b/src/lib.rs index e9d50b05..4f66f132 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -197,4 +197,4 @@ pub use screen_layout::ScreenLayout; pub use taffy; pub use view::{recursively_layout_view, AnyView, IntoView, View}; pub use window::{close_window, new_window}; -pub use window_id::WindowIdExt; +pub use window_id::{Urgency, WindowIdExt}; diff --git a/src/screen_layout.rs b/src/screen_layout.rs index dfa282e2..2451d8c5 100644 --- a/src/screen_layout.rs +++ b/src/screen_layout.rs @@ -1,7 +1,7 @@ //! Tools for computing screen locations from locations within a View and //! vice-versa. use crate::ViewId; -use floem_winit::window::WindowId; +use floem_winit::window::{Window, WindowId}; use peniko::kurbo::{Point, Rect, Size}; use crate::window_tracking::{ @@ -9,7 +9,8 @@ use crate::window_tracking::{ }; /// Create a ScreenLayout for a view. This can fail if the view or an -/// ancestor of it has no parent and is not realized on-screen. ScreenLayout +/// ancestor of it has no parent and is not realized on-screen, or if the +/// platform does not support reading window inner or outer bounds. ScreenLayout /// is useful when needing to convert locations within a view into absolute /// positions on-screen, such as for creating a window at position relative /// to that view. @@ -48,7 +49,7 @@ pub fn try_create_screen_layout(view: &ViewId) -> Option { monitor_bounds, window_content_bounds, window_bounds, - view_origin_in_window, + view_origin_in_window: Some(view_origin_in_window), window_id: *window_id, } }) @@ -62,6 +63,52 @@ pub fn try_create_screen_layout(view: &ViewId) -> Option { .unwrap_or(None) } +pub fn screen_layout_for_window(window_id: WindowId, window: &Window) -> Option { + window + .current_monitor() + .map(|monitor| { + window + .inner_position() + .map(|inner_position| { + window + .outer_position() + .map(|outer_position| { + let monitor_bounds = monitor_bounds_for_monitor(window, &monitor); + let inner_size = window.inner_size(); + let outer_size = window.outer_size(); + + let window_bounds = rect_from_physical_bounds_for_window( + window, + outer_position, + outer_size, + ); + + let window_content_bounds = rect_from_physical_bounds_for_window( + window, + inner_position, + inner_size, + ); + + let view_origin_in_window = None; + let monitor_scale = window.scale_factor(); + + ScreenLayout { + monitor_scale, + monitor_bounds, + window_content_bounds, + window_bounds, + view_origin_in_window, + window_id, + } + }) + .ok() + }) + .ok() + }) + .unwrap_or(None) + .unwrap_or(None) +} + /// Relates a realized `View` to the bounds of the window that contains it, /// and the window to the bounds of the monitor that contains it. All fields /// are in logical coordinates (if the OS scales physical coordinates for high @@ -70,6 +117,7 @@ pub fn try_create_screen_layout(view: &ViewId) -> Option { /// Instances are a snapshot in time of the location of the view and window /// at the time of creation, and are not updated if view or window or monitor /// in use is. +#[derive(Copy, Clone, Debug, PartialEq)] pub struct ScreenLayout { /// The window id pub window_id: WindowId, @@ -81,8 +129,11 @@ pub struct ScreenLayout { pub window_content_bounds: Rect, /// The bounds of the window within the monitor's bounds pub window_bounds: Rect, - /// The origin of the view within the window - pub view_origin_in_window: Point, + /// The origin of the view within the window, if this ScreenLayout was + /// created from a `View` rather than a `WindowId` - needed for computing + /// relative offsets from, e.g., the location of a mouse click within + /// a `View`. + pub view_origin_in_window: Option, } impl ScreenLayout { @@ -96,11 +147,28 @@ impl ScreenLayout { monitor_bounds: scale_rect(self.monitor_scale, self.monitor_bounds), window_bounds: scale_rect(self.monitor_scale, self.window_bounds), window_content_bounds: scale_rect(self.monitor_scale, self.window_content_bounds), - view_origin_in_window: scale_point(self.monitor_scale, self.view_origin_in_window), + view_origin_in_window: self + .view_origin_in_window + .map(|origin| scale_point(self.monitor_scale, origin)), window_id: self.window_id, } } + /// Get the insets required to transform the outer rectangle into + /// the inner one in the form `(left, top, right, bottom)` + pub fn window_frame_insets(&self) -> (f64, f64, f64, f64) { + // Kurbo contains an Insets type obtainable from + // self.window_bounds - self.window_content_bounds + // but uses a definition of "insets" that is has nothing + // to do with what any UI toolkit has ever meant by the word. + ( + self.window_content_bounds.x0 - self.window_bounds.x0, + self.window_content_bounds.y0 - self.window_bounds.y0, + self.window_bounds.x1 - self.window_content_bounds.x1, + self.window_bounds.y1 - self.window_content_bounds.y1, + ) + } + /// If true, this instance has scaling applied. pub fn is_scaled(&self) -> bool { self.monitor_scale != 0_f64 @@ -110,11 +178,21 @@ impl ScreenLayout { /// this one. pub fn view_location_from_screen(&self, screen_point: Point) -> Point { let mut result = screen_point; - result.x -= self.view_origin_in_window.x + self.window_content_bounds.x0; - result.y -= self.view_origin_in_window.y + self.window_content_bounds.y0; + if let Some(origin) = self.view_origin_in_window { + result.x -= origin.x + self.window_content_bounds.x0; + result.y -= origin.y + self.window_content_bounds.y0; + } result } + /// Determine if this `ScreenBounds` has a different bounding rectangle for + /// the content and frame bounds. Some X11 window managers (Openbox, for one) + /// appear to support getting frame position separately from content position, + /// but in fact report the same bounds for both. + pub fn contains_frame_decoration_insets(&self) -> bool { + self.window_content_bounds != self.window_bounds + } + /// Compute a position, in screen coordinates, relative to the view this layout /// was created from. If a target size is passed, the implementation will attempt /// to adjust the resulting point so that a rectangle of the required size fits @@ -130,8 +208,10 @@ impl ScreenLayout { result.y += offset.y; } - result.x += self.view_origin_in_window.x; - result.y += self.view_origin_in_window.y; + if let Some(origin) = self.view_origin_in_window { + result.x += origin.x; + result.y += origin.y; + } // If we have a size, adjust the resulting point to ensure the resulting // bounds will fit on screen (if it is possible) diff --git a/src/window_id.rs b/src/window_id.rs index 6051aac7..d7c6a626 100644 --- a/src/window_id.rs +++ b/src/window_id.rs @@ -1,18 +1,107 @@ +use crate::{ + screen_layout::screen_layout_for_window, + window_tracking::{force_window_repaint, with_window}, + ScreenLayout, ViewId, +}; +use std::{cell::RefCell, collections::HashMap}; + use super::window_tracking::{ - monitor_bounds, window_inner_screen_bounds, window_inner_screen_position, + monitor_bounds, root_view_id, window_inner_screen_bounds, window_inner_screen_position, window_outer_screen_bounds, window_outer_screen_position, }; -use floem_winit::window::WindowId; -use peniko::kurbo::{Point, Rect}; +use floem_winit::{ + dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel}, + window::{UserAttentionType, Window, WindowId}, +}; +use peniko::kurbo::{Point, Rect, Size}; + +// Using thread_local for consistency with static vars in updates.rs, but I suspect these +// are thread_local not because thread-locality is desired, but only because static mutability is +// desired - but that's a patch for another day. +thread_local! { + /// Holding pen for window state changes, processed as part of the event loop cycle + pub(crate) static WINDOW_UPDATE_MESSAGES: RefCell>> = Default::default(); +} + +/// Enum of state updates that can be requested on a window which are processed +/// asynchronously after event processing. +#[allow(dead_code)] // DocumentEdited is seen as unused on non-mac builds +enum WindowUpdate { + Visibility(bool), + InnerBounds(Rect), + OuterBounds(Rect), + // Since both inner bounds and outer bounds require some fudgery because winit + // only supports setting outer location and *inner* bounds, it is a good idea + // also to support setting the two things winit supports directly: + OuterLocation(Point), + InnerSize(Size), + RequestAttention(Option), + Minimize(bool), + Maximize(bool), + // Mac OS only + #[allow(unused_variables)] // seen as unused on linux, etc. + DocumentEdited(bool), +} + +/// Delegate enum for `winit`'s +/// [`UserAttentionType`](https://docs.rs/winit/latest/winit/window/enum.UserAttentionType.html), +/// used for making the window's icon bounce in the Mac OS dock or the equivalent of that on +/// other platforms. +#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] +pub enum Urgency { + Critical, + Informational, + + /// The default attention type (equivalent of passing `None` to `winit::Window::request_user_attention())`). + /// On some platforms (X11), it is necessary to call `WindowId.request_attention(Urgency::Default)` to stop + /// the attention-seeking behavior of the window. + #[default] + Default, +} + +impl From for Option { + fn from(urgency: Urgency) -> Self { + match urgency { + Urgency::Critical => Some(UserAttentionType::Critical), + Urgency::Informational => Some(UserAttentionType::Informational), + Urgency::Default => None, + } + } +} /// Ensures `WindowIdExt` cannot be implemented on arbitrary types. -trait WindowIdExtSealed {} -impl WindowIdExtSealed for WindowId {} +trait WindowIdExtSealed: Sized + Copy { + fn add_window_update(&self, msg: WindowUpdate); +} + +impl WindowIdExtSealed for WindowId { + fn add_window_update(&self, msg: WindowUpdate) { + WINDOW_UPDATE_MESSAGES.with_borrow_mut(|map| match map.entry(*self) { + std::collections::hash_map::Entry::Occupied(updates) => { + updates.into_mut().push(msg); + } + std::collections::hash_map::Entry::Vacant(v) => { + v.insert(vec![msg]); + } + }); + } +} -/// Extends WindowId to give instances methods to retrieve properties of the associated window, -/// much as ViewId does. Methods may return None if the view is not realized on-screen, or +/// Extends `WindowId` to give instances methods to retrieve properties of the associated window, +/// much as `ViewId` does. Methods may return None if the view is not realized on-screen, or /// if information needed to compute the result is not available on the current platform or /// available on the current platform but not from the calling thread. +/// +/// **Platform support notes:** +/// * Mac OS: Many of the methods here, if called from a thread other than `main`, are +/// blocking because accessing most window properties may only be done from the main +/// thread on that OS. +/// * Android & Wayland: Getting the outer position of a window is not supported by `winit` and +/// methods whose return value have that as a prerequisite will return `None` or return a +/// reasonable default. +/// * X11: Some window managers (Openbox was one such which was tested) *appear* to support +/// retreiving separate window-with-frame and window-content positions and sizes, but in +/// fact report the same values for both. #[allow(private_bounds)] pub trait WindowIdExt: WindowIdExtSealed { /// Get the bounds of the content of this window, including @@ -23,10 +112,111 @@ pub trait WindowIdExt: WindowIdExtSealed { fn bounds_of_content_on_screen(&self) -> Option; /// Get the location of the window including any OS titlebar. fn position_on_screen_including_frame(&self) -> Option; - /// Get the location of the window **excluding** any OS titlebar. + /// Get the location of the window's content on the monitor where + /// it currently resides, **excluding** any OS titlebar. fn position_of_content_on_screen(&self) -> Option; - /// Get the logical bounds of the monitor this window is on + /// Get the logical bounds of the monitor this window is on. fn monitor_bounds(&self) -> Option; + /// Determine if this window is currently visible. Note that if a + /// call to set a window visible which is invisible has happened within + /// the current event loop cycle, the state returned will not reflect that. + fn is_visible(&self) -> bool; + /// Determine if this window is currently minimized. Note that if a + /// call to minimize or unminimize this window, and it is currently in the + /// opposite state, has happened the current event loop cycle, the state + /// returned will not reflect that. + fn is_minimized(&self) -> bool; + + /// Determine if this window is currently maximize. Note that if a + /// call to maximize or unmaximize this window, and it is currently in the + /// opposite state, has happened the current event loop cycle, the state + /// returned will not reflect that. + fn is_maximized(&self) -> bool; + + /// Determine if the window decorations should indicate an edited, unsaved + /// document. Platform-dependent: Will only ever return `true` on Mac OS. + fn is_document_edited(&self) -> bool; + + /// Instruct the window manager to indicate in the window's decorations + /// the the window contains an unsaved, edited document. Only has an + /// effect on Mac OS. + #[allow(unused_variables)] // edited unused on non-mac builds + fn set_document_edited(&self, edited: bool) { + #[cfg(target_os = "macos")] + self.add_window_update(WindowUpdate::DocumentEdited(edited)) + } + + /// Set this window's visible state, hiding or showing it if it has been + /// hidden + fn set_visible(&self, visible: bool) { + self.add_window_update(WindowUpdate::Visibility(visible)) + } + + /// Update the bounds of this window. + fn set_window_inner_bounds(&self, bounds: Rect) { + self.add_window_update(WindowUpdate::InnerBounds(bounds)) + } + + /// Update the bounds of this window. + fn set_window_outer_bounds(&self, bounds: Rect) { + self.add_window_update(WindowUpdate::OuterBounds(bounds)) + } + + /// Change this window's maximized state. + fn maximized(&self, maximized: bool) { + self.add_window_update(WindowUpdate::Maximize(maximized)) + } + + /// Change this window's minimized state. + fn minimized(&self, minimized: bool) { + self.add_window_update(WindowUpdate::Minimize(minimized)) + } + + /// Change this window's minimized state. + fn set_outer_location(&self, location: Point) { + self.add_window_update(WindowUpdate::OuterLocation(location)) + } + + /// Ask the OS's windowing framework to update the size of the window + /// based on the passed size for its *content* (excluding titlebar, frame + /// or other decorations). + fn set_content_size(&self, size: Size) { + self.add_window_update(WindowUpdate::InnerSize(size)) + } + + /// Cause the desktop to perform some attention-drawing behavior that draws + /// the user's attention specifically to this window - e.g. bouncing in + /// the dock on Mac OS. On X11, after calling this method with some urgency + /// other than `None`, it is necessary to *clear* the attention-seeking state + /// by calling this method again with `Urgency::None`. + fn request_attention(&self, urgency: Urgency) { + self.add_window_update(WindowUpdate::RequestAttention(urgency.into())) + } + + /// Force a repaint of this window through the native window's repaint mechanism, + /// bypassing floem's normal repaint mechanism. + /// + /// This method may be removed or deprecated in the future, but has been needed + /// in [some situations](https://github.com/lapce/floem/issues/463), and to + /// address a few ongoing issues in `winit` (window unmaximize is delayed until + /// an external event triggers a repaint of the requesting window), and may + /// be needed as a workaround if other such issues are discovered until they + /// can be addressed. + /// + /// Returns true if the repaint request was issued successfully (i.e. there is + /// an actual system-level window corresponding to this `WindowId`). + fn force_repaint(&self) -> bool; + + /// Get the root view of this window. + fn root_view(&self) -> Option; + + /// Get a layout of this window in relation to the monitor on which it currently + /// resides, if any. + fn screen_layout(&self) -> Option; + + /// Get the dots-per-inch scaling of this window or 1.0 if the platform does not + /// support it (Android). + fn scale(&self) -> f64; } impl WindowIdExt for WindowId { @@ -49,4 +239,258 @@ impl WindowIdExt for WindowId { fn monitor_bounds(&self) -> Option { monitor_bounds(self) } + + fn is_visible(&self) -> bool { + with_window(self, |window| window.is_visible().unwrap_or(false)).unwrap_or(false) + } + + fn is_minimized(&self) -> bool { + with_window(self, |window| window.is_minimized().unwrap_or(false)).unwrap_or(false) + } + + fn is_maximized(&self) -> bool { + with_window(self, Window::is_maximized).unwrap_or(false) + } + + #[cfg(target_os = "macos")] + #[allow(dead_code)] + fn is_document_edited(&self) -> bool { + with_window( + self, + floem_winit::platform::macos::WindowExtMacOS::is_document_edited, + ) + .unwrap_or(false) + } + + #[cfg(not(target_os = "macos"))] + #[allow(dead_code)] + fn is_document_edited(&self) -> bool { + false + } + + fn force_repaint(&self) -> bool { + force_window_repaint(self) + } + + fn root_view(&self) -> Option { + root_view_id(self) + } + + fn screen_layout(&self) -> Option { + with_window(self, move |window| screen_layout_for_window(*self, window)).unwrap_or(None) + } + + fn scale(&self) -> f64 { + with_window(self, Window::scale_factor).unwrap_or(1.0) + } +} + +/// Called by `ApplicationHandle` at the end of the event loop callback. +pub(crate) fn process_window_updates(id: &WindowId) -> bool { + let mut result = false; + if let Some(items) = WINDOW_UPDATE_MESSAGES.with_borrow_mut(|map| map.remove(id)) { + result = !items.is_empty(); + for update in items { + match update { + WindowUpdate::Visibility(visible) => { + with_window(id, |window| { + window.set_visible(visible); + }); + } + #[allow(unused_variables)] // non mac - edited is unused + WindowUpdate::DocumentEdited(edited) => { + #[cfg(target_os = "macos")] + with_window(id, |window| { + use floem_winit::platform::macos::WindowExtMacOS; + window.set_document_edited(edited); + }); + } + WindowUpdate::OuterBounds(bds) => { + with_window(id, |window| { + let params = + bounds_to_logical_outer_position_and_inner_size(window, bds, true); + window.set_outer_position(params.0); + // XXX log any returned error? + let _ = window.request_inner_size(params.1); + }); + } + WindowUpdate::InnerBounds(bds) => { + with_window(id, |window| { + let params = + bounds_to_logical_outer_position_and_inner_size(window, bds, false); + window.set_outer_position(params.0); + // XXX log any returned error? + let _ = window.request_inner_size(params.1); + }); + } + WindowUpdate::RequestAttention(att) => { + with_window(id, |window| { + window.request_user_attention(att); + }); + } + WindowUpdate::Minimize(minimize) => { + with_window(id, |window| { + window.set_minimized(minimize); + if !minimize { + // If we don't trigger a repaint on Mac OS, + // unminimize doesn't happen until an input + // event arrives. Unrelated to + // https://github.com/lapce/floem/issues/463 - + // this is in winit or below. + maybe_yield_with_repaint(window); + } + }); + } + WindowUpdate::Maximize(maximize) => { + with_window(id, |window| window.set_maximized(maximize)); + } + WindowUpdate::OuterLocation(outer) => { + with_window(id, |window| { + window.set_outer_position(LogicalPosition::new(outer.x, outer.y)); + }); + } + WindowUpdate::InnerSize(size) => { + with_window(id, |window| { + window.request_inner_size(LogicalSize::new(size.width, size.height)) + }); + } + } + } + } + result +} + +/// Compute a new logical position and size, given a window, a rectangle and whether the +/// rectangle represents the desired inner or outer bounds of the window. +/// +/// This is complex because winit offers us two somewhat contradictory ways of setting +/// the bounds: +/// +/// * You can set the **outer** position with `window.set_outer_position(position)` +/// * You can set the **inner** size with `window.request_inner_size(size)` +/// * You can obtain inner and outer sizes and positions, but you can only set outer +/// position and *inner* size +/// +/// So we must take the delta of the inner and outer size and/or positions (position +/// availability is more limited by platform), and from that, create an appropriate +/// inner size and outer position based on a `Rect` that represents either inner or +/// outer. +fn bounds_to_logical_outer_position_and_inner_size( + window: &Window, + target_bounds: Rect, + target_is_outer: bool, +) -> (LogicalPosition, LogicalSize) { + if !window.is_decorated() { + // For undecorated windows, the inner and outer location and size are always identical + // so no further work is needed + return ( + LogicalPosition::new(target_bounds.x0, target_bounds.y0), + LogicalSize::new(target_bounds.width(), target_bounds.height()), + ); + } + + let scale = window.scale_factor(); + if target_is_outer { + // We need to reduce the size we are requesting by the width and height of the + // OS-added decorations to get the right target INNER size: + let inner_to_outer_size_delta = delta_size(window.inner_size(), window.outer_size(), scale); + + ( + LogicalPosition::new(target_bounds.x0, target_bounds.y0), + LogicalSize::new( + (target_bounds.width() + inner_to_outer_size_delta.0).max(0.), + (target_bounds.height() + inner_to_outer_size_delta.1).max(0.), + ), + ) + } else { + // We need to shift the x/y position we are requesting up and left (negatively) + // to come up with an *outer* location that makes sense with the passed rectangle's + // size as an *inner* size + let size_delta = delta_size(window.inner_size(), window.outer_size(), scale); + let inner_to_outer_delta: (f64, f64) = if let Some(delta) = + delta_position(window.inner_position(), window.outer_position(), scale) + { + // This is the more accurate way, but may be unavailable on some platforms + delta + } else { + // We have to make a few assumptions here, one of which is that window + // decorations are horizontally symmetric - the delta-x / 2 equals a position + // on the perimeter of the window's frame. A few ancient XWindows window + // managers (Enlightenment) might violate that assumption, but it is a rarity. + ( + size_delta.0 / 2.0, + size_delta.1, // assume vertical is titlebar and give it full weight + ) + }; + ( + LogicalPosition::new( + target_bounds.x0 - inner_to_outer_delta.0, + target_bounds.y0 - inner_to_outer_delta.1, + ), + LogicalSize::new(target_bounds.width(), target_bounds.height()), + ) + } +} + +/// Some operations - notably minimize and restoring visibility - don't take +/// effect on Mac OS until something triggers a repaint in the target window - the +/// issue is below the level of floem's event loops and seems to be in winit or +/// deeper. Workaround is to force the window to repaint. +#[allow(unused_variables)] // non mac builds see `window` as unused +fn maybe_yield_with_repaint(window: &Window) { + #[cfg(target_os = "macos")] + { + window.request_redraw(); + let main = Some("main") != std::thread::current().name(); + if !main { + // attempt to get out of the way of the main thread + std::thread::yield_now(); + } + } +} + +fn delta_size(inner: PhysicalSize, outer: PhysicalSize, window_scale: f64) -> (f64, f64) { + let inner = winit_phys_size_to_size(inner, window_scale); + let outer = winit_phys_size_to_size(outer, window_scale); + (outer.width - inner.width, outer.height - inner.height) +} + +type PositionResult = + Result, floem_winit::error::NotSupportedError>; + +fn delta_position( + inner: PositionResult, + outer: PositionResult, + window_scale: f64, +) -> Option<(f64, f64)> { + if let Ok(inner) = inner { + if let Ok(outer) = outer { + let outer = winit_phys_position_to_point(outer, window_scale); + let inner = winit_phys_position_to_point(inner, window_scale); + + return Some((inner.x - outer.x, inner.y - outer.y)); + } + } + None +} + +// Conversion functions for winit's size and point types: + +fn winit_position_to_point + Pixel>(pos: LogicalPosition) -> Point { + Point::new(pos.x.into(), pos.y.into()) +} + +fn winit_size_to_size + Pixel>(size: LogicalSize) -> Size { + Size::new(size.width.into(), size.height.into()) +} + +fn winit_phys_position_to_point + Pixel>( + pos: PhysicalPosition, + window_scale: f64, +) -> Point { + winit_position_to_point::(pos.to_logical(window_scale)) +} + +fn winit_phys_size_to_size + Pixel>(size: PhysicalSize, window_scale: f64) -> Size { + winit_size_to_size::(size.to_logical(window_scale)) } diff --git a/src/window_tracking.rs b/src/window_tracking.rs index b8c53e1f..6ef01607 100644 --- a/src/window_tracking.rs +++ b/src/window_tracking.rs @@ -17,7 +17,7 @@ use std::{ static WINDOW_FOR_WINDOW_AND_ROOT_IDS: OnceLock> = OnceLock::new(); -/// Add a mapping from root_id -> window_id -> window for the given triple. +/// Add a mapping from `root_id` -> `window_id` -> `window` for the given triple. pub fn store_window_id_mapping( root_id: ViewId, window_id: WindowId, @@ -26,7 +26,7 @@ pub fn store_window_id_mapping( with_window_map_mut(move |m| m.add(root_id, window_id, window.clone())); } -/// Remove the mapping from root_id -> window_id -> window for the given triple. +/// Remove the mapping from `root_id` -> `window_id` -> `window` for the given triple. pub fn remove_window_id_mapping(root_id: &ViewId, window_id: &WindowId) { with_window_map_mut(move |m| m.remove(root_id, window_id)); } @@ -71,15 +71,18 @@ impl WindowMapping { self.window_for_window_id.get(window).map(f) } - fn each_window)>(&self, f: F) { - for (_, window) in self.window_for_window_id.iter() { - f(window) - } - } - fn window_id_for_root(&self, id: &ViewId) -> Option { self.window_id_for_root_view_id.get(id).copied() } + + fn root_view_id_for(&self, window_id: &WindowId) -> Option { + for (k, v) in self.window_id_for_root_view_id.iter() { + if v == window_id { + return Some(*k); + } + } + None + } } pub fn with_window_id_and_window T, T>( @@ -110,6 +113,14 @@ fn with_window_map T, T>(f: F) -> Option { } } +pub fn with_window T, T>(window: &WindowId, f: F) -> Option { + with_window_map(|m| m.with_window(window, |w| f(w.as_ref()))).unwrap_or(None) +} + +pub fn root_view_id(window: &WindowId) -> Option { + with_window_map(|m| m.root_view_id_for(window)).unwrap_or(None) +} + /// Force a single window to repaint - this is necessary in cases where the /// window is not the active window and otherwise would not process update /// messages sent to it. @@ -125,15 +136,6 @@ pub fn window_id_for_root(root_id: ViewId) -> Option { with_window_map(|map| map.window_id_for_root(&root_id)).unwrap_or(None) } -pub fn force_all_repaint() -> bool { - with_window_map(|m| { - println!("Force repaint of {} windows", m.window_for_window_id.len()); - m.each_window(|window| window.request_redraw()); - !m.window_for_window_id.is_empty() - }) - .unwrap_or(false) -} - pub fn monitor_bounds(id: &WindowId) -> Option { with_window_map(|m| { m.with_window(id, |window| {