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, root_view_id, window_inner_screen_bounds, window_inner_screen_position,
window_outer_screen_bounds, window_outer_screen_position,
};
use floem_winit::{
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Pixel},
window::{UserAttentionType, Window, WindowId},
};
use peniko::kurbo::{Point, Rect, Size};
thread_local! {
pub(crate) static WINDOW_UPDATE_MESSAGES: RefCell<HashMap<WindowId, Vec<WindowUpdate>>> = Default::default();
}
#[allow(dead_code)] enum WindowUpdate {
Visibility(bool),
InnerBounds(Rect),
OuterBounds(Rect),
OuterLocation(Point),
InnerSize(Size),
RequestAttention(Option<UserAttentionType>),
Minimize(bool),
Maximize(bool),
#[allow(unused_variables)] DocumentEdited(bool),
}
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
pub enum Urgency {
Critical,
Informational,
#[default]
Default,
}
impl From<Urgency> for Option<UserAttentionType> {
fn from(urgency: Urgency) -> Self {
match urgency {
Urgency::Critical => Some(UserAttentionType::Critical),
Urgency::Informational => Some(UserAttentionType::Informational),
Urgency::Default => None,
}
}
}
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]);
}
});
}
}
#[allow(private_bounds)]
pub trait WindowIdExt: WindowIdExtSealed {
fn bounds_on_screen_including_frame(&self) -> Option<Rect>;
fn bounds_of_content_on_screen(&self) -> Option<Rect>;
fn position_on_screen_including_frame(&self) -> Option<Point>;
fn position_of_content_on_screen(&self) -> Option<Point>;
fn monitor_bounds(&self) -> Option<Rect>;
fn is_visible(&self) -> bool;
fn is_minimized(&self) -> bool;
fn is_maximized(&self) -> bool;
fn is_document_edited(&self) -> bool;
#[allow(unused_variables)] fn set_document_edited(&self, edited: bool) {
#[cfg(target_os = "macos")]
self.add_window_update(WindowUpdate::DocumentEdited(edited))
}
fn set_visible(&self, visible: bool) {
self.add_window_update(WindowUpdate::Visibility(visible))
}
fn set_window_inner_bounds(&self, bounds: Rect) {
self.add_window_update(WindowUpdate::InnerBounds(bounds))
}
fn set_window_outer_bounds(&self, bounds: Rect) {
self.add_window_update(WindowUpdate::OuterBounds(bounds))
}
fn maximized(&self, maximized: bool) {
self.add_window_update(WindowUpdate::Maximize(maximized))
}
fn minimized(&self, minimized: bool) {
self.add_window_update(WindowUpdate::Minimize(minimized))
}
fn set_outer_location(&self, location: Point) {
self.add_window_update(WindowUpdate::OuterLocation(location))
}
fn set_content_size(&self, size: Size) {
self.add_window_update(WindowUpdate::InnerSize(size))
}
fn request_attention(&self, urgency: Urgency) {
self.add_window_update(WindowUpdate::RequestAttention(urgency.into()))
}
fn force_repaint(&self) -> bool;
fn root_view(&self) -> Option<ViewId>;
fn screen_layout(&self) -> Option<ScreenLayout>;
fn scale(&self) -> f64;
}
impl WindowIdExt for WindowId {
fn bounds_on_screen_including_frame(&self) -> Option<Rect> {
window_outer_screen_bounds(self)
}
fn bounds_of_content_on_screen(&self) -> Option<Rect> {
window_inner_screen_bounds(self)
}
fn position_on_screen_including_frame(&self) -> Option<Point> {
window_outer_screen_position(self)
}
fn position_of_content_on_screen(&self) -> Option<Point> {
window_inner_screen_position(self)
}
fn monitor_bounds(&self) -> Option<Rect> {
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<ViewId> {
root_view_id(self)
}
fn screen_layout(&self) -> Option<ScreenLayout> {
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)
}
}
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)] 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);
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);
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 {
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
}
fn bounds_to_logical_outer_position_and_inner_size(
window: &Window,
target_bounds: Rect,
target_is_outer: bool,
) -> (LogicalPosition<f64>, LogicalSize<f64>) {
if !window.is_decorated() {
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 {
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 {
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)
{
delta
} else {
(
size_delta.0 / 2.0,
size_delta.1, )
};
(
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()),
)
}
}
#[allow(unused_variables)] fn maybe_yield_with_repaint(window: &Window) {
#[cfg(target_os = "macos")]
{
window.request_redraw();
let main = Some("main") != std::thread::current().name();
if !main {
std::thread::yield_now();
}
}
}
fn delta_size(inner: PhysicalSize<u32>, outer: PhysicalSize<u32>, 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::dpi::PhysicalPosition<i32>, 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
}
fn winit_position_to_point<I: Into<f64> + Pixel>(pos: LogicalPosition<I>) -> Point {
Point::new(pos.x.into(), pos.y.into())
}
fn winit_size_to_size<I: Into<f64> + Pixel>(size: LogicalSize<I>) -> Size {
Size::new(size.width.into(), size.height.into())
}
fn winit_phys_position_to_point<I: Into<f64> + Pixel>(
pos: PhysicalPosition<I>,
window_scale: f64,
) -> Point {
winit_position_to_point::<I>(pos.to_logical(window_scale))
}
fn winit_phys_size_to_size<I: Into<f64> + Pixel>(size: PhysicalSize<I>, window_scale: f64) -> Size {
winit_size_to_size::<I>(size.to_logical(window_scale))
}