-
Notifications
You must be signed in to change notification settings - Fork 122
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix #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.
- Loading branch information
1 parent
e580fde
commit 6ab4d1f
Showing
5 changed files
with
564 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
//! Tools for computing screen locations from locations within a View and | ||
//! vice-versa. | ||
use crate::ViewId; | ||
use floem_winit::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. 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, | ||
window_id: *window_id, | ||
} | ||
}) | ||
.ok() | ||
}) | ||
.ok() | ||
}) | ||
.unwrap_or(None) | ||
.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. | ||
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 | ||
pub view_origin_in_window: 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: scale_point(self.monitor_scale, self.view_origin_in_window), | ||
window_id: self.window_id, | ||
} | ||
} | ||
|
||
/// 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; | ||
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; | ||
result | ||
} | ||
|
||
/// 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; | ||
} | ||
|
||
result.x += self.view_origin_in_window.x; | ||
result.y += self.view_origin_in_window.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
Oops, something went wrong.