forked from lapce/floem
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add methods to show and hide a window * Fix lapce#463 - broken inter-window update messages This addresses a couple of issues: * Messages (including repaints) not getting processed when delivered a `View` in a window which is inactive * Ability to retrieve the window id of a view without the application having to pass that information down to whatever code needs it and addresses a related issue - obtaining a `WindowId` for a realized `View` and its bounds on screen, in order to convert view-relative locations into screen locations for purposes such as positioning windows relative to a `View`. This patch adds a tracking data structure, and a convenience data structure which contains sufficient information to convert view-relative positions to screen-positions and back. * A static mapping of `root-id:WindowId`:`Arc<Window>` which is populated on window creation and cleared on destruction, which makes it possible to access `Window.request_redraw()` to force a redraw. It is guarded by a read-write lock (but written to only during window creation and destruction). * `ScreenLayout` contains a snapshot of the window id, the window's content and framed bounds, the view's origin within the window and the monitor scaling. So, a ViewId that wants its window-id looks up its root view's id, and looks up the WindowId associated with that root. We extend `WindowId` with methods to get the window bounds on screen with and without decorations and similar - these methods could be added to `ViewId` instead, but it seems more intuitive that you ask a window id for the window's bounds the same way you ask a view id for the view's bounds - that seems like what users will expect. Given the usage patterns (the mapping will only be locked for writing when a window is being created or destroyed, and will not be read-locked except in the case of forcing a repaint or looking up window coordinates), it is not touched in the critical path of rendering - unless the application and destroying many windows at the same time on different threads, I would not expect the read-write lock to be contended at all in practice (famous last words, I know). A thread-local for the mapping, as is done in `update.rs` might work (or look like it works), but we would be assuming no platform floem will ever support uses different event threads for different windows' event loops. I know of two that do - BeOS and Java AWT (with its stack of event threads to allow modal dialogs that work when their parent is disabled or blocked, which was a source of "interesting" deadlocks). If there was some path that I missed that would let a `ViewId` reach down to the `WindowHandle` that owns it without doing this sort of tracking, I would love to know about it - `PaintCx` has this information, but it is the only thing that does, and the `ViewId` has no access to it. * Move WindowIdExt to its own file; eliminate force-paint hack - better fix available * 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.
- Loading branch information
1 parent
959e292
commit dde5a1b
Showing
8 changed files
with
1,071 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,280 @@ | ||
//! Tools for computing screen locations from locations within a View and | ||
//! vice-versa. | ||
use crate::ViewId; | ||
use floem_winit::window::{Window, WindowId}; | ||
use peniko::kurbo::{Point, Rect, Size}; | ||
|
||
use crate::window_tracking::{ | ||
monitor_bounds_for_monitor, rect_from_physical_bounds_for_window, with_window_id_and_window, | ||
}; | ||
|
||
/// 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, 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. | ||
pub fn try_create_screen_layout(view: &ViewId) -> Option<ScreenLayout> { | ||
with_window_id_and_window(view, |window_id, window| { | ||
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 = find_window_origin(view); | ||
let monitor_scale = window.scale_factor(); | ||
|
||
ScreenLayout { | ||
monitor_scale, | ||
monitor_bounds, | ||
window_content_bounds, | ||
window_bounds, | ||
view_origin_in_window: Some(view_origin_in_window), | ||
window_id: *window_id, | ||
} | ||
}) | ||
.ok() | ||
}) | ||
.ok() | ||
}) | ||
.unwrap_or(None) | ||
.unwrap_or(None) | ||
}) | ||
.unwrap_or(None) | ||
} | ||
|
||
pub fn screen_layout_for_window(window_id: WindowId, window: &Window) -> Option<ScreenLayout> { | ||
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 | ||
/// DPI displays, the scaling is already applied). | ||
/// | ||
/// 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, | ||
/// The scaling of the monitor, if any | ||
pub monitor_scale: f64, | ||
/// The logical bounds of the monitor | ||
pub monitor_bounds: Rect, | ||
/// The bounds of the view content within the monitor's bounds | ||
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, 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<Point>, | ||
} | ||
|
||
impl ScreenLayout { | ||
/// Unscales this Screen to physical device coordinates, less any DPI | ||
/// scaling done by hardware. | ||
pub fn to_physical_scale(&self) -> Self { | ||
// The bounds we have are pre-scaled by 1.0/window.scale(), so | ||
// inverting them is multiplication, not division. | ||
Self { | ||
monitor_scale: 1.0, | ||
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: 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 | ||
} | ||
|
||
/// Convert a screen position to a position within the view that created | ||
/// this one. | ||
pub fn view_location_from_screen(&self, screen_point: Point) -> Point { | ||
let mut result = screen_point; | ||
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 | ||
/// entirely on-screen. | ||
pub fn screen_location_from_view( | ||
&self, | ||
relative_position: Option<Point>, | ||
target_size: Option<Size>, | ||
) -> Point { | ||
let mut result = Point::new(self.window_content_bounds.x0, self.window_content_bounds.y0); | ||
if let Some(offset) = relative_position { | ||
result.x += offset.x; | ||
result.y += offset.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) | ||
if let Some(size) = target_size { | ||
let mut target_bounds = Rect::new( | ||
result.x, | ||
result.y, | ||
result.x + size.width, | ||
result.y + size.height, | ||
); | ||
if target_bounds.x1 > self.monitor_bounds.x1 { | ||
let offset = target_bounds.x1 - self.monitor_bounds.x1; | ||
target_bounds.x0 -= offset; | ||
target_bounds.x1 -= offset; | ||
} | ||
if target_bounds.y1 > self.monitor_bounds.y1 { | ||
let offset = target_bounds.y1 - self.monitor_bounds.y1; | ||
target_bounds.y0 -= offset; | ||
target_bounds.y1 -= offset; | ||
} | ||
if target_bounds.x0 < self.monitor_bounds.x0 { | ||
let offset = self.monitor_bounds.x0 - target_bounds.x0; | ||
target_bounds.x0 += offset; | ||
target_bounds.x1 += offset; | ||
} | ||
if target_bounds.y0 < self.monitor_bounds.y0 { | ||
let offset = self.monitor_bounds.y0 - target_bounds.y0; | ||
target_bounds.y0 += offset; | ||
target_bounds.y1 += offset | ||
} | ||
result.x = target_bounds.x0; | ||
result.y = target_bounds.y0; | ||
} | ||
result | ||
} | ||
} | ||
|
||
fn find_window_origin(view: &ViewId) -> Point { | ||
let mut pt = Point::ZERO; | ||
recursively_find_window_origin(*view, &mut pt); | ||
pt | ||
} | ||
|
||
fn recursively_find_window_origin(view: ViewId, point: &mut Point) { | ||
if let Some(layout) = view.get_layout() { | ||
point.x += layout.location.x as f64; | ||
point.y += layout.location.y as f64; | ||
if let Some(parent) = view.parent() { | ||
recursively_find_window_origin(parent, point); | ||
} | ||
} | ||
} | ||
|
||
fn scale_point(by: f64, mut pt: Point) -> Point { | ||
pt.x *= by; | ||
pt.y *= by; | ||
pt | ||
} | ||
|
||
fn scale_rect(by: f64, mut bds: Rect) -> Rect { | ||
bds.x0 *= by; | ||
bds.y0 *= by; | ||
bds.x1 *= by; | ||
bds.y1 *= by; | ||
bds | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,4 +94,5 @@ pub(crate) enum UpdateMessage { | |
position: Point, | ||
size: Size, | ||
}, | ||
WindowVisible(bool), | ||
} |
Oops, something went wrong.