Skip to content

Commit

Permalink
Implement a number of window management features
Browse files Browse the repository at this point in the history
 * 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
timboudreau committed May 30, 2024
1 parent 318a4ae commit d9c4ee3
Show file tree
Hide file tree
Showing 6 changed files with 568 additions and 50 deletions.
4 changes: 3 additions & 1 deletion src/app_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
view::View,
window::WindowConfig,
window_handle::WindowHandle,
window_id::process_window_updates,
};

pub(crate) struct ApplicationHandle {
Expand Down Expand Up @@ -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) {}
}
}

Expand Down
14 changes: 2 additions & 12 deletions src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -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<WindowId> {
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);
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
100 changes: 90 additions & 10 deletions src/screen_layout.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
//! 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::{
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
/// 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.
Expand Down Expand Up @@ -48,7 +49,7 @@ pub fn try_create_screen_layout(view: &ViewId) -> Option<ScreenLayout> {
monitor_bounds,
window_content_bounds,
window_bounds,
view_origin_in_window,
view_origin_in_window: Some(view_origin_in_window),
window_id: *window_id,
}
})
Expand All @@ -62,6 +63,52 @@ pub fn try_create_screen_layout(view: &ViewId) -> Option<ScreenLayout> {
.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
Expand All @@ -70,6 +117,7 @@ pub fn try_create_screen_layout(view: &ViewId) -> Option<ScreenLayout> {
/// 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,
Expand All @@ -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<Point>,
}

impl ScreenLayout {
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down
Loading

0 comments on commit d9c4ee3

Please sign in to comment.