Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Window management #480

Merged
merged 4 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
19 changes: 19 additions & 0 deletions src/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use std::{any::Any, cell::RefCell, rc::Rc};

use floem_winit::window::WindowId;
use peniko::kurbo::{Insets, Point, Rect, Size};
use slotmap::new_key_type;
use taffy::{Display, Layout, NodeId, TaffyTree};
Expand All @@ -21,6 +22,8 @@ use crate::{
view::{IntoView, View},
view_state::{ChangeFlags, StackOffset, ViewState},
view_storage::VIEW_STORAGE,
window_tracking::window_id_for_root,
ScreenLayout,
};

new_key_type! {
Expand Down Expand Up @@ -252,6 +255,11 @@ impl ViewId {
self.request_changes(ChangeFlags::LAYOUT)
}

/// 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) {
self.add_update_message(UpdateMessage::RequestPaint);
}
Expand Down Expand Up @@ -417,6 +425,12 @@ impl ViewId {
self.add_update_message(UpdateMessage::Draggable { id: *self });
}

/// Alter the visibility of the current window the view represented by this ID
/// is in.
pub fn window_visible(&self, visible: bool) {
self.add_update_message(UpdateMessage::WindowVisible(visible));
}

fn add_update_message(&self, msg: UpdateMessage) {
CENTRAL_UPDATE_MESSAGES.with_borrow_mut(|msgs| {
msgs.push((*self, msg));
Expand All @@ -428,4 +442,9 @@ impl ViewId {
msgs.push((*self, Box::new(state)));
});
}

/// Get a layout in screen-coordinates for this view, if possible.
pub fn screen_layout(&self) -> Option<ScreenLayout> {
crate::screen_layout::try_create_screen_layout(self)
}
}
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ pub mod pointer;
mod profiler;
pub mod renderer;
pub mod responsive;
mod screen_layout;
pub mod style;
pub(crate) mod theme;
pub mod unit;
Expand All @@ -180,6 +181,8 @@ pub mod view_tuple;
pub mod views;
pub mod window;
mod window_handle;
mod window_id;
mod window_tracking;

pub use app::{launch, quit_app, AppEvent, Application};
pub use app_state::AppState;
Expand All @@ -190,6 +193,8 @@ pub use floem_renderer::Renderer;
pub use id::ViewId;
pub use peniko;
pub use peniko::kurbo;
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::{Urgency, WindowIdExt};
280 changes: 280 additions & 0 deletions src/screen_layout.rs
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
}
1 change: 1 addition & 0 deletions src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,5 @@ pub(crate) enum UpdateMessage {
position: Point,
size: Size,
},
WindowVisible(bool),
}
Loading