From eeabf3995b2cc0d411ae3f170a5d82111a659ce5 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Wed, 13 Jan 2021 18:48:25 +0000 Subject: [PATCH 1/8] Expose WindowLevel, add get_content_insets, track window coordinates --- CHANGELOG.md | 3 + druid-shell/src/platform/gtk/window.rs | 7 +- druid-shell/src/platform/mac/window.rs | 156 ++++++++++++++------- druid-shell/src/platform/web/window.rs | 7 +- druid-shell/src/platform/windows/window.rs | 7 +- druid-shell/src/platform/x11/window.rs | 7 +- druid-shell/src/window.rs | 10 +- druid/examples/hello.rs | 1 + druid/examples/sub_window.rs | 4 +- druid/src/app.rs | 42 +++++- druid/src/contexts.rs | 21 ++- druid/src/core.rs | 11 ++ druid/src/event.rs | 5 +- druid/src/lib.rs | 4 +- druid/src/win_handler.rs | 1 + druid/src/window.rs | 35 ++++- 16 files changed, 247 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e56bded61b..45e6bceef0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ You can find its changes [documented below](#070---2021-01-01). - RichTextBuilder ([#1520] by [@Maan2003]) - `get_external_handle` on `DelegateCtx` ([#1526] by [@Maan2003]) - `AppLauncher::localization_resources` to use custom l10n resources. ([#1528] by [@edwin0cheng]) +- Shell: get_content_insets and mac implementation ([#XXXX] by [@rjwittams]) +- Contexts: to_window and to_screen (useful for relatively positioning sub windows) ([#XXXX] by [@rjwittams]) +- WindowSizePolicy: allow windows to be sized by their content ([#XXXX] by [@rjwittams]) ### Changed diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index ea3b00c6d5..fb01e09bf6 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -32,7 +32,7 @@ use gio::ApplicationExt; use gtk::prelude::*; use gtk::{AccelGroup, ApplicationWindow, DrawingArea, SettingsExt}; -use crate::kurbo::{Point, Rect, Size, Vec2}; +use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; use crate::piet::{Piet, PietText, RenderContext}; use crate::common_util::{ClickCounter, IdleCallback}; @@ -818,6 +818,11 @@ impl WindowHandle { } } + pub fn get_content_insets(&self) -> Insets { + log::warn!("WindowHandle::get_content_insets unimplemented for GTK platforms."); + Insets::ZERO + } + pub fn set_level(&self, level: WindowLevel) { if let Some(state) = self.state.upgrade() { let hint = match level { diff --git a/druid-shell/src/platform/mac/window.rs b/druid-shell/src/platform/mac/window.rs index fd5e64096a..8aaf330a5c 100644 --- a/druid-shell/src/platform/mac/window.rs +++ b/druid-shell/src/platform/mac/window.rs @@ -40,7 +40,7 @@ use objc::rc::WeakPtr; use objc::runtime::{Class, Object, Sel}; use objc::{class, msg_send, sel, sel_impl}; -use crate::kurbo::{Point, Rect, Size, Vec2}; +use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; use crate::piet::{Piet, PietText, RenderContext}; use super::appkit::{ @@ -127,10 +127,17 @@ pub(crate) struct IdleHandle { idle_queue: Weak>>, } +#[derive(Debug)] +enum DeferredOp { + SetSize(Size), + SetPosition(Point), +} + /// This represents different Idle Callback Mechanism enum IdleKind { Callback(Box), Token(IdleToken), + DeferredOp(DeferredOp), } /// This is the state associated with our custom NSView. @@ -156,7 +163,7 @@ impl WindowBuilder { handler: None, title: String::new(), menu: None, - size: Size::new(500.0, 400.0), + size: Size::new(0., 0.), min_size: None, position: None, level: None, @@ -220,10 +227,11 @@ impl WindowBuilder { style_mask |= NSWindowStyleMask::NSResizableWindowMask; } - let rect = NSRect::new( - NSPoint::new(0., 0.), - NSSize::new(self.size.width, self.size.height), - ); + let screen_height = crate::Screen::get_display_rect().height(); + let position = self.position.unwrap_or_else(|| Point::new(20., 20.)); + let origin = NSPoint::new(position.x, screen_height - position.y - self.size.height); // Flip back + + let rect = NSRect::new(origin, NSSize::new(self.size.width, self.size.height)); let window: id = msg_send![WINDOW_CLASS.0, alloc]; let window = window.initWithContentRect_styleMask_backing_defer_( @@ -238,7 +246,6 @@ impl WindowBuilder { window.setContentMinSize_(size); } - window.cascadeTopLeftFromPoint_(NSPoint::new(20.0, 20.0)); window.setTitle_(make_nsstring(&self.title)); let (view, idle_queue) = make_view(self.handler.expect("view")); @@ -260,9 +267,6 @@ impl WindowBuilder { idle_queue, }; - if let Some(pos) = self.position { - handle.set_position(pos); - } if let Some(window_state) = self.window_state { handle.set_window_state(window_state); } @@ -758,6 +762,43 @@ extern "C" fn draw_rect(this: &mut Object, _: Sel, dirtyRect: NSRect) { } } +fn run_deferred(this: &mut Object, view_state: &mut ViewState, op: DeferredOp) { + match op { + DeferredOp::SetSize(size) => set_size_deferred(this, view_state, size), + DeferredOp::SetPosition(pos) => set_position_deferred(this, view_state, pos), + } +} + +fn set_size_deferred(this: &mut Object, _view_state: &mut ViewState, size: Size) { + unsafe { + let window: id = msg_send![this, window]; + let current_frame: NSRect = msg_send![window, frame]; + let mut new_frame = current_frame; + + // TODO: maintain druid origin (as mac origin is bottom left) + new_frame.origin.y -= size.height - current_frame.size.height; + new_frame.size.width = size.width; + new_frame.size.height = size.height; + let () = msg_send![window, setFrame: new_frame display: YES]; + } +} + +fn set_position_deferred(this: &mut Object, _view_state: &mut ViewState, position: Point) { + unsafe { + // TODO this should be the max y in orig mac coords + + let window: id = msg_send![this, window]; + let frame: NSRect = msg_send![window, frame]; + + let mut new_frame = frame; + new_frame.origin.x = position.x; + let screen_height = crate::Screen::get_display_rect().height(); + new_frame.origin.y = screen_height - position.y - frame.size.height; // Flip back + let () = msg_send![window, setFrame: new_frame display: YES]; + log::debug!("set_position_deferred {:?}", position); + } +} + extern "C" fn run_idle(this: &mut Object, _: Sel) { let view_state = unsafe { let view_state: *mut c_void = *this.get_ivar("viewState"); @@ -773,6 +814,7 @@ extern "C" fn run_idle(this: &mut Object, _: Sel) { IdleKind::Token(it) => { view_state.handler.as_mut().idle(it); } + IdleKind::DeferredOp(op) => run_deferred(this, view_state, op), } } } @@ -1005,17 +1047,7 @@ impl WindowHandle { // Need to translate mac y coords, as they start from bottom left pub fn set_position(&self, position: Point) { - unsafe { - // TODO this should be the max y in orig mac coords - let screen_height = crate::Screen::get_display_rect().height(); - let window: id = msg_send![*self.nsview.load(), window]; - let frame: NSRect = msg_send![window, frame]; - - let mut new_frame = frame; - new_frame.origin.x = position.x; - new_frame.origin.y = screen_height - position.y - frame.size.height; // Flip back - let () = msg_send![window, setFrame: new_frame display: YES]; - } + self.defer(DeferredOp::SetPosition(position)) } pub fn get_position(&self) -> Point { @@ -1033,6 +1065,34 @@ impl WindowHandle { } } + pub fn get_content_insets(&self) -> Insets { + unsafe { + let screen_height = crate::Screen::get_display_rect().height(); + + let window: id = msg_send![*self.nsview.load(), window]; + let clr: NSRect = msg_send![window, contentLayoutRect]; + + let window_frame_r: NSRect = NSWindow::frame(window); + let content_frame_r: NSRect = NSWindow::convertRectToScreen_(window, clr); + + let window_frame_rk = Rect::from_origin_size( + ( + window_frame_r.origin.x, + screen_height - window_frame_r.origin.y - window_frame_r.size.height, + ), + (window_frame_r.size.width, window_frame_r.size.height), + ); + let content_frame_rk = Rect::from_origin_size( + ( + content_frame_r.origin.x, + screen_height - content_frame_r.origin.y - content_frame_r.size.height, + ), + (content_frame_r.size.width, content_frame_r.size.height), + ); + window_frame_rk - content_frame_rk + } + } + pub fn set_level(&self, level: WindowLevel) { unsafe { let level = levels::as_raw_window_level(level); @@ -1042,14 +1102,7 @@ impl WindowHandle { } pub fn set_size(&self, size: Size) { - unsafe { - let window: id = msg_send![*self.nsview.load(), window]; - let current_frame: NSRect = msg_send![window, frame]; - let mut new_frame = current_frame; - new_frame.size.width = size.width; - new_frame.size.height = size.height; - let () = msg_send![window, setFrame: new_frame display: YES]; - } + self.defer(DeferredOp::SetSize(size)); } pub fn get_size(&self) -> Size { @@ -1132,6 +1185,12 @@ impl WindowHandle { } } + fn defer(&self, op: DeferredOp) { + if let Some(i) = self.get_idle_handle() { + i.add_idle(IdleKind::DeferredOp(op)) + } + } + /// Get a handle that can be used to schedule an idle task. pub fn get_idle_handle(&self) -> Option { if self.nsview.load().is_null() { @@ -1154,16 +1213,7 @@ impl WindowHandle { unsafe impl Send for IdleHandle {} impl IdleHandle { - /// Add an idle handler, which is called (once) when the message loop - /// is empty. The idle handler will be run from the main UI thread, and - /// won't be scheduled if the associated view has been dropped. - /// - /// Note: the name "idle" suggests that it will be scheduled with a lower - /// priority than other UI events, but that's not necessarily the case. - pub fn add_idle_callback(&self, callback: F) - where - F: FnOnce(&dyn Any) + Send + 'static, - { + fn add_idle(&self, idle: IdleKind) { if let Some(queue) = self.idle_queue.upgrade() { let mut queue = queue.lock().expect("queue lock"); if queue.is_empty() { @@ -1174,23 +1224,25 @@ impl IdleHandle { withObject: nil waitUntilDone: NO); } } - queue.push(IdleKind::Callback(Box::new(callback))); + queue.push(idle); } } + /// Add an idle handler, which is called (once) when the message loop + /// is empty. The idle handler will be run from the main UI thread, and + /// won't be scheduled if the associated view has been dropped. + /// + /// Note: the name "idle" suggests that it will be scheduled with a lower + /// priority than other UI events, but that's not necessarily the case. + pub fn add_idle_callback(&self, callback: F) + where + F: FnOnce(&dyn Any) + Send + 'static, + { + self.add_idle(IdleKind::Callback(Box::new(callback))); + } + pub fn add_idle_token(&self, token: IdleToken) { - if let Some(queue) = self.idle_queue.upgrade() { - let mut queue = queue.lock().expect("queue lock"); - if queue.is_empty() { - unsafe { - let nsview = self.nsview.load(); - // Note: the nsview might be nil here if the window has been dropped, but that's ok. - let () = msg_send!(*nsview, performSelectorOnMainThread: sel!(runIdle) - withObject: nil waitUntilDone: NO); - } - } - queue.push(IdleKind::Token(token)); - } + self.add_idle(IdleKind::Token(token)); } } diff --git a/druid-shell/src/platform/web/window.rs b/druid-shell/src/platform/web/window.rs index 3a4c1a4610..7eb422ae5f 100644 --- a/druid-shell/src/platform/web/window.rs +++ b/druid-shell/src/platform/web/window.rs @@ -25,7 +25,7 @@ use instant::Instant; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -use crate::kurbo::{Point, Rect, Size, Vec2}; +use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; use crate::piet::{PietText, RenderContext}; @@ -477,6 +477,11 @@ impl WindowHandle { Size::new(0.0, 0.0) } + pub fn get_content_insets(&self) -> Insets { + log::warn!("WindowHandle::get_content_insets unimplemented for web."); + Insets::ZERO + } + pub fn set_window_state(&self, _state: window::WindowState) { log::warn!("WindowHandle::set_window_state unimplemented for web."); } diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index ea7692eac2..b0ab9c95e2 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -47,7 +47,7 @@ use winapi::um::winuser::*; use piet_common::d2d::{D2DFactory, DeviceContext}; use piet_common::dwrite::DwriteFactory; -use crate::kurbo::{Point, Rect, Size, Vec2}; +use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; use crate::piet::{Piet, PietText, RenderContext}; use super::accels::register_accel; @@ -1657,6 +1657,11 @@ impl WindowHandle { Point::new(0.0, 0.0) } + pub fn get_content_insets(&self) -> Insets { + log::warn!("WindowHandle::get_content_insets unimplemented for windows."); + Insets::ZERO + } + // Sets the size of the window in DP pub fn set_size(&self, size: Size) { self.defer(DeferredOp::SetSize(size)); diff --git a/druid-shell/src/platform/x11/window.rs b/druid-shell/src/platform/x11/window.rs index 2a34f0eaf8..8829f8bfef 100644 --- a/druid-shell/src/platform/x11/window.rs +++ b/druid-shell/src/platform/x11/window.rs @@ -41,7 +41,7 @@ use crate::common_util::IdleCallback; use crate::dialog::FileDialogOptions; use crate::error::Error as ShellError; use crate::keyboard::{KeyEvent, KeyState, Modifiers}; -use crate::kurbo::{Point, Rect, Size, Vec2}; +use crate::kurbo::{Insets, Point, Rect, Size, Vec2}; use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent}; use crate::piet::{Piet, PietText, RenderContext}; use crate::region::Region; @@ -1404,6 +1404,11 @@ impl WindowHandle { Point::new(0.0, 0.0) } + pub fn get_content_insets(&self) -> Insets { + log::warn!("WindowHandle::get_content_insets unimplemented for X11 platforms."); + Insets::ZERO + } + pub fn set_level(&self, _level: WindowLevel) { log::warn!("WindowHandle::set_level is currently unimplemented for X11 platforms."); } diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index 9420efcd2a..6bdffdbd6d 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -22,7 +22,7 @@ use crate::common_util::Counter; use crate::dialog::{FileDialogOptions, FileInfo}; use crate::error::Error; use crate::keyboard::KeyEvent; -use crate::kurbo::{Point, Rect, Size}; +use crate::kurbo::{Insets, Point, Rect, Size}; use crate::menu::Menu; use crate::mouse::{Cursor, CursorDesc, MouseEvent}; use crate::platform::window as platform; @@ -194,12 +194,18 @@ impl WindowHandle { self.0.set_position(position.into()) } - /// Returns the position of the window in [pixels](crate::Scale), relative to the origin of the + /// Returns the position of the top left corner of the window in [pixels](crate::Scale), relative to the origin of the /// virtual screen. pub fn get_position(&self) -> Point { self.0.get_position() } + /// Returns the insets of the window content from its position and size in [pixels](crate::Scale). + /// This is to account for any window system provided chrome, eg. title bars. + pub fn get_content_insets(&self) -> Insets { + self.0.get_content_insets() + } + /// Set the window's size in [display points](crate::Scale). /// /// The actual window size in pixels will depend on the platform DPI settings. diff --git a/druid/examples/hello.rs b/druid/examples/hello.rs index 78e73ede33..819859242d 100644 --- a/druid/examples/hello.rs +++ b/druid/examples/hello.rs @@ -40,6 +40,7 @@ pub fn main() { // start the application AppLauncher::with_window(main_window) + .use_simple_logger() .launch(initial_state) .expect("Failed to launch application"); } diff --git a/druid/examples/sub_window.rs b/druid/examples/sub_window.rs index 19afbf9a14..1320e08b4d 100644 --- a/druid/examples/sub_window.rs +++ b/druid/examples/sub_window.rs @@ -20,7 +20,7 @@ use druid::widget::{ use druid::{ theme, Affine, AppLauncher, BoxConstraints, Color, Data, Env, Event, EventCtx, LayoutCtx, Lens, LensExt, LifeCycle, LifeCycleCtx, LocalizedString, PaintCtx, Point, Rect, RenderContext, Size, - TimerToken, UpdateCtx, Widget, WidgetExt, WindowConfig, WindowDesc, WindowId, + TimerToken, UpdateCtx, Widget, WidgetExt, WindowConfig, WindowDesc, WindowId, WindowSizePolicy, }; use druid_shell::piet::Text; use druid_shell::{Screen, WindowLevel}; @@ -141,7 +141,7 @@ impl> Controller for TooltipController { let win_id = ctx.new_sub_window( WindowConfig::default() .show_titlebar(false) - .window_size(Size::new(100.0, 23.0)) + .window_size_policy(WindowSizePolicy::Content) .set_level(WindowLevel::Tooltip) .set_position( ctx.window().get_position() diff --git a/druid/src/app.rs b/druid/src/app.rs index abb813e07b..bfe226fbb2 100644 --- a/druid/src/app.rs +++ b/druid/src/app.rs @@ -24,8 +24,6 @@ use crate::{AppDelegate, Data, Env, LocalizedString, MenuDesc, Widget}; use druid_shell::WindowState; -const WINDOW_MIN_SIZE: Size = Size::new(400., 400.); - /// A function that modifies the initial environment. type EnvSetupFn = dyn FnOnce(&mut Env, &T); @@ -38,11 +36,22 @@ pub struct AppLauncher { ext_event_host: ExtEventHost, } +/// Defines how a windows size should be determined +#[derive(Copy, Clone, Debug)] +pub enum WindowSizePolicy { + /// Use the content of the window to determine the size + Content, + /// Use the provided window size + User, +} + /// Window configuration that can be applied to a WindowBuilder, or to an existing WindowHandle. /// It does not include anything related to app data. +#[derive(Debug)] pub struct WindowConfig { + pub(crate) size_policy: WindowSizePolicy, pub(crate) size: Option, - pub(crate) min_size: Size, + pub(crate) min_size: Option, pub(crate) position: Option, pub(crate) resizable: Option, pub(crate) show_titlebar: Option, @@ -61,12 +70,15 @@ pub struct WindowDesc { pub id: WindowId, } -/// The parts of a window, pending construction, that are dependent on top level app state. +/// The parts of a window, pending construction, that are dependent on top level app state +/// or are not part of the druid shells windowing abstraction. /// This includes the boxed root widget, as well as other window properties such as the title. pub struct PendingWindow { pub(crate) root: Box>, pub(crate) title: LabelText, pub(crate) menu: Option>, + pub(crate) size_policy: WindowSizePolicy, // This is copied over from the WindowConfig + // when the native window is constructed. } impl PendingWindow { @@ -81,6 +93,7 @@ impl PendingWindow { root: Box::new(root()), title: LocalizedString::new("app-name").into(), menu: MenuDesc::platform_default(), + size_policy: WindowSizePolicy::User, } } @@ -207,8 +220,9 @@ impl AppLauncher { impl Default for WindowConfig { fn default() -> Self { WindowConfig { + size_policy: WindowSizePolicy::User, size: None, - min_size: WINDOW_MIN_SIZE, + min_size: None, position: None, resizable: None, show_titlebar: None, @@ -219,6 +233,12 @@ impl Default for WindowConfig { } impl WindowConfig { + /// Set the window size policy. + pub fn window_size_policy(mut self, size_policy: WindowSizePolicy) -> Self { + self.size_policy = size_policy; + self + } + /// Set the window's initial drawing area size in [display points]. /// /// You can pass in a tuple `(width, height)` or a [`Size`], @@ -252,7 +272,7 @@ impl WindowConfig { /// [`window_size`]: #method.window_size /// [display points]: struct.Scale.html pub fn with_min_size(mut self, size: impl Into) -> Self { - self.min_size = size.into(); + self.min_size = Some(size.into()); self } @@ -319,7 +339,9 @@ impl WindowConfig { builder.set_window_state(state); } - builder.set_min_size(self.min_size); + if let Some(min_size) = self.min_size { + builder.set_min_size(min_size); + } } /// Apply this window configuration to the passed in WindowHandle @@ -389,6 +411,12 @@ impl WindowDesc { self } + /// Set the window size policy + pub fn window_size_policy(mut self, size_policy: WindowSizePolicy) -> Self { + self.config.size_policy = size_policy; + self + } + /// Set the window's initial drawing area size in [display points]. /// /// You can pass in a tuple `(width, height)` or a [`Size`], diff --git a/druid/src/contexts.rs b/druid/src/contexts.rs index 8690703fc9..71e1ec3d5a 100644 --- a/druid/src/contexts.rs +++ b/druid/src/contexts.rs @@ -27,7 +27,7 @@ use crate::piet::{Piet, PietText, RenderContext}; use crate::shell::Region; use crate::{ commands, sub_window::SubWindowDesc, widget::Widget, Affine, Command, ContextMenu, Cursor, - Data, Env, ExtEventSink, Insets, MenuDesc, Notification, Point, Rect, SingleUse, Size, Target, + Data, Env, ExtEventSink, Insets, MenuDesc, Notification, Point, Rect, Vec2, SingleUse, Size, Target, TimerToken, WidgetId, WindowConfig, WindowDesc, WindowHandle, WindowId, }; @@ -186,6 +186,25 @@ impl_context_method!( self.widget_state.size() } + /// The origin of the widget in window coordinates. + pub fn window_origin(&self) -> Point { + self.widget_state.window_origin + } + + /// Takes a point in widget coordinates and transforms it to + /// window coordinates (relative to the content area, excluding chrome) + pub fn to_window(&self, widget_point: Point) -> Point { + self.window_origin() + widget_point.to_vec2() + } + + /// Takes a point in widget coordinates and transforms it to + /// screen coordinates + pub fn to_screen(&self, widget_point: Point) -> Point { + let insets = self.window().get_content_insets(); + let content_origin = self.window().get_position() + Vec2::new(insets.x0, insets.y0); + content_origin + self.to_window(widget_point).to_vec2() + } + /// The "hot" (aka hover) status of a widget. /// /// A widget is "hot" when the mouse is hovered over it. Widgets will diff --git a/druid/src/core.rs b/druid/src/core.rs index 9df7bc1c71..92ad7b92dc 100644 --- a/druid/src/core.rs +++ b/druid/src/core.rs @@ -79,6 +79,8 @@ pub(crate) struct WidgetState { /// The origin of the child in the parent's coordinate space; together with /// `size` these constitute the child's layout rect. origin: Point, + /// The origin of the child in the window coordinate space; + pub(crate) window_origin: Point, /// A flag used to track and debug missing calls to set_origin. is_expecting_set_origin_call: bool, /// The insets applied to the layout rect to generate the paint rect. @@ -910,6 +912,14 @@ impl> WidgetPod { _ => false, } } + InternalLifeCycle::ParentWindowOrigin(parent_window_origin) => { + self.state.window_origin = *parent_window_origin + self.state.origin.to_vec2() + - self.state.viewport_offset; + extra_event = Some(LifeCycle::Internal(InternalLifeCycle::ParentWindowOrigin( + self.state.window_origin, + ))); + false + } #[cfg(test)] InternalLifeCycle::DebugRequestState { widget, state_cell } => { if *widget == self.id() { @@ -1073,6 +1083,7 @@ impl WidgetState { WidgetState { id, origin: Point::ORIGIN, + window_origin: Point::ORIGIN, size: size.unwrap_or_default(), is_expecting_set_origin_call: true, paint_insets: Insets::ZERO, diff --git a/druid/src/event.rs b/druid/src/event.rs index e044cd5f86..3654feb0f8 100644 --- a/druid/src/event.rs +++ b/druid/src/event.rs @@ -14,7 +14,7 @@ //! Events. -use crate::kurbo::{Rect, Shape, Size, Vec2}; +use crate::kurbo::{Point, Rect, Shape, Size, Vec2}; use druid_shell::{Clipboard, KeyEvent, TimerToken}; @@ -299,6 +299,9 @@ pub enum InternalLifeCycle { /// the widget that is gaining focus, if any new: Option, }, + /// The widget parents origin in window coordinate space. + /// This occurs when a whole window has been laid out. + ParentWindowOrigin(Point), /// Testing only: request the `WidgetState` of a specific widget. /// /// During testing, you may wish to verify that the state of a widget diff --git a/druid/src/lib.rs b/druid/src/lib.rs index 328b8308e6..3e9d5af9b6 100644 --- a/druid/src/lib.rs +++ b/druid/src/lib.rs @@ -188,11 +188,11 @@ pub use shell::{ Application, Clipboard, ClipboardFormat, Code, Cursor, CursorDesc, Error as PlatformError, FileInfo, FileSpec, FormatId, HotKey, KbKey, KeyEvent, Location, Modifiers, Monitor, MouseButton, MouseButtons, RawMods, Region, Scalable, Scale, Screen, SysMods, TimerToken, - WindowHandle, WindowState, + WindowHandle, WindowLevel, WindowState, }; pub use crate::core::WidgetPod; -pub use app::{AppLauncher, WindowConfig, WindowDesc}; +pub use app::{AppLauncher, WindowConfig, WindowDesc, WindowSizePolicy}; pub use app_delegate::{AppDelegate, DelegateCtx}; pub use box_constraints::BoxConstraints; pub use command::{sys as commands, Command, Notification, Selector, SingleUse, Target}; diff --git a/druid/src/win_handler.rs b/druid/src/win_handler.rs index 69baaa9201..03af420d53 100644 --- a/druid/src/win_handler.rs +++ b/druid/src/win_handler.rs @@ -796,6 +796,7 @@ impl AppState { let data = self.data(); let env = self.env(); + pending.size_policy = config.size_policy; pending.title.resolve(&data, &env); builder.set_title(pending.title.display_text().to_string()); diff --git a/druid/src/window.rs b/druid/src/window.rs index 1cac3016d2..d97b76f1e2 100644 --- a/druid/src/window.rs +++ b/druid/src/window.rs @@ -23,7 +23,7 @@ use instant::Instant; use crate::piet::{Piet, RenderContext}; use crate::shell::{Counter, Cursor, Region, WindowHandle}; -use crate::app::PendingWindow; +use crate::app::{PendingWindow, WindowSizePolicy}; use crate::contexts::ContextState; use crate::core::{CommandQueue, FocusChange, WidgetState}; use crate::util::ExtendDrain; @@ -44,6 +44,7 @@ pub struct Window { pub(crate) id: WindowId, pub(crate) root: WidgetPod>>, pub(crate) title: LabelText, + size_policy: WindowSizePolicy, size: Size, invalid: Region, pub(crate) menu: Option>, @@ -68,6 +69,7 @@ impl Window { Window { id, root: WidgetPod::new(pending.root), + size_policy: pending.size_policy, size: Size::ZERO, invalid: Region::EMPTY, title: pending.title, @@ -255,6 +257,15 @@ impl Window { self.handle.set_cursor(&Cursor::Arrow); } + if matches!( + (event, self.size_policy), + (Event::WindowSize(_), WindowSizePolicy::Content) + ) { + // Because our initial size can be zero, the window system won't ask us to paint. + // So layout ourselves and hopefully we resize + self.layout(queue, data, env); + } + self.post_event_processing(&mut widget_state, queue, data, env, false); is_handled @@ -367,10 +378,28 @@ impl Window { widget_state: &mut widget_state, mouse_pos: self.last_mouse_pos, }; - let bc = BoxConstraints::tight(self.size); - self.root.layout(&mut layout_ctx, &bc, data, env); + let bc = match self.size_policy { + WindowSizePolicy::User => BoxConstraints::tight(self.size), + WindowSizePolicy::Content => BoxConstraints::UNBOUNDED, + }; + let ret = self.root.layout(&mut layout_ctx, &bc, data, env); + if let WindowSizePolicy::Content = self.size_policy { + let insets = self.handle.get_content_insets(); + let full_size = (ret.to_rect() + insets).size(); + if self.size != full_size { + self.size = full_size; + self.handle.set_size(full_size) + } + } self.root .set_origin(&mut layout_ctx, data, env, Point::ORIGIN); + self.lifecycle( + queue, + &LifeCycle::Internal(InternalLifeCycle::ParentWindowOrigin(Point::ORIGIN)), + data, + env, + false, + ); self.post_event_processing(&mut widget_state, queue, data, env, true); } From c9ca6548684ef41074eec88f563fee5994c0efc7 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Wed, 13 Jan 2021 21:59:37 +0000 Subject: [PATCH 2/8] Make scroll contained things re layout if the scroll offset changes, so the window origin is correct for descendents. --- druid/src/contexts.rs | 6 +++--- druid/src/core.rs | 26 ++++++++++++++++---------- druid/src/event.rs | 7 +++---- druid/src/window.rs | 6 +++--- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/druid/src/contexts.rs b/druid/src/contexts.rs index 71e1ec3d5a..445c142571 100644 --- a/druid/src/contexts.rs +++ b/druid/src/contexts.rs @@ -27,8 +27,8 @@ use crate::piet::{Piet, PietText, RenderContext}; use crate::shell::Region; use crate::{ commands, sub_window::SubWindowDesc, widget::Widget, Affine, Command, ContextMenu, Cursor, - Data, Env, ExtEventSink, Insets, MenuDesc, Notification, Point, Rect, Vec2, SingleUse, Size, Target, - TimerToken, WidgetId, WindowConfig, WindowDesc, WindowHandle, WindowId, + Data, Env, ExtEventSink, Insets, MenuDesc, Notification, Point, Rect, SingleUse, Size, Target, + TimerToken, Vec2, WidgetId, WindowConfig, WindowDesc, WindowHandle, WindowId, }; /// A macro for implementing methods on multiple contexts. @@ -188,7 +188,7 @@ impl_context_method!( /// The origin of the widget in window coordinates. pub fn window_origin(&self) -> Point { - self.widget_state.window_origin + self.widget_state.window_origin() } /// Takes a point in widget coordinates and transforms it to diff --git a/druid/src/core.rs b/druid/src/core.rs index 92ad7b92dc..0af0a10479 100644 --- a/druid/src/core.rs +++ b/druid/src/core.rs @@ -79,8 +79,8 @@ pub(crate) struct WidgetState { /// The origin of the child in the parent's coordinate space; together with /// `size` these constitute the child's layout rect. origin: Point, - /// The origin of the child in the window coordinate space; - pub(crate) window_origin: Point, + /// The origin of the parent in the window coordinate space; + pub(crate) parent_window_origin: Point, /// A flag used to track and debug missing calls to set_origin. is_expecting_set_origin_call: bool, /// The insets applied to the layout rect to generate the paint rect. @@ -286,6 +286,12 @@ impl> WidgetPod { /// /// [`Scroll`]: widget/struct.Scroll.html pub fn set_viewport_offset(&mut self, offset: Vec2) { + if offset != self.state.viewport_offset { + // We need the parent_window_origin recalculated. + // It should be possible to just trigger the InternalLifeCycle::ParentWindowOrigin here, + // instead of full layout. Would need more management in WidgetState. + self.state.needs_layout = true; + } self.state.viewport_offset = offset; } @@ -912,13 +918,9 @@ impl> WidgetPod { _ => false, } } - InternalLifeCycle::ParentWindowOrigin(parent_window_origin) => { - self.state.window_origin = *parent_window_origin + self.state.origin.to_vec2() - - self.state.viewport_offset; - extra_event = Some(LifeCycle::Internal(InternalLifeCycle::ParentWindowOrigin( - self.state.window_origin, - ))); - false + InternalLifeCycle::ParentWindowOrigin => { + self.state.parent_window_origin = ctx.widget_state.window_origin(); + true } #[cfg(test)] InternalLifeCycle::DebugRequestState { widget, state_cell } => { @@ -1083,7 +1085,7 @@ impl WidgetState { WidgetState { id, origin: Point::ORIGIN, - window_origin: Point::ORIGIN, + parent_window_origin: Point::ORIGIN, size: size.unwrap_or_default(), is_expecting_set_origin_call: true, paint_insets: Insets::ZERO, @@ -1188,6 +1190,10 @@ impl WidgetState { pub(crate) fn add_sub_window_host(&mut self, window_id: WindowId, host_id: WidgetId) { self.sub_window_hosts.push((window_id, host_id)) } + + pub(crate) fn window_origin(&self) -> Point { + self.parent_window_origin + self.origin.to_vec2() - self.viewport_offset + } } impl CursorChange { diff --git a/druid/src/event.rs b/druid/src/event.rs index 3654feb0f8..d12cda5ebf 100644 --- a/druid/src/event.rs +++ b/druid/src/event.rs @@ -14,7 +14,7 @@ //! Events. -use crate::kurbo::{Point, Rect, Shape, Size, Vec2}; +use crate::kurbo::{Rect, Shape, Size, Vec2}; use druid_shell::{Clipboard, KeyEvent, TimerToken}; @@ -299,9 +299,8 @@ pub enum InternalLifeCycle { /// the widget that is gaining focus, if any new: Option, }, - /// The widget parents origin in window coordinate space. - /// This occurs when a whole window has been laid out. - ParentWindowOrigin(Point), + /// The parents widget origin in window coordinate space has changed. + ParentWindowOrigin, /// Testing only: request the `WidgetState` of a specific widget. /// /// During testing, you may wish to verify that the state of a widget diff --git a/druid/src/window.rs b/druid/src/window.rs index d97b76f1e2..e863a218ef 100644 --- a/druid/src/window.rs +++ b/druid/src/window.rs @@ -382,10 +382,10 @@ impl Window { WindowSizePolicy::User => BoxConstraints::tight(self.size), WindowSizePolicy::Content => BoxConstraints::UNBOUNDED, }; - let ret = self.root.layout(&mut layout_ctx, &bc, data, env); + let content_size = self.root.layout(&mut layout_ctx, &bc, data, env); if let WindowSizePolicy::Content = self.size_policy { let insets = self.handle.get_content_insets(); - let full_size = (ret.to_rect() + insets).size(); + let full_size = (content_size.to_rect() + insets).size(); if self.size != full_size { self.size = full_size; self.handle.set_size(full_size) @@ -395,7 +395,7 @@ impl Window { .set_origin(&mut layout_ctx, data, env, Point::ORIGIN); self.lifecycle( queue, - &LifeCycle::Internal(InternalLifeCycle::ParentWindowOrigin(Point::ORIGIN)), + &LifeCycle::Internal(InternalLifeCycle::ParentWindowOrigin), data, env, false, From 2140fbea858f9c31e764a6420880600b197ace6b Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Thu, 14 Jan 2021 14:02:30 +0000 Subject: [PATCH 3/8] Update druid-shell/src/window.rs doc fix Co-authored-by: Colin Rofls --- druid-shell/src/window.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index 6bdffdbd6d..60d781b407 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -201,6 +201,7 @@ impl WindowHandle { } /// Returns the insets of the window content from its position and size in [pixels](crate::Scale). + /// /// This is to account for any window system provided chrome, eg. title bars. pub fn get_content_insets(&self) -> Insets { self.0.get_content_insets() From 8e2a732bed8c4f0cc7b320c07314d0eef8058a47 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Thu, 14 Jan 2021 14:04:31 +0000 Subject: [PATCH 4/8] Update druid/src/contexts.rs doc fix Co-authored-by: Colin Rofls --- druid/src/contexts.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/druid/src/contexts.rs b/druid/src/contexts.rs index 445c142571..2ef0f70159 100644 --- a/druid/src/contexts.rs +++ b/druid/src/contexts.rs @@ -191,8 +191,9 @@ impl_context_method!( self.widget_state.window_origin() } - /// Takes a point in widget coordinates and transforms it to - /// window coordinates (relative to the content area, excluding chrome) + /// Convert a point from the widget's coordinate space to the window's. + /// + /// The returned point is relative to the content area; it excludes window chrome. pub fn to_window(&self, widget_point: Point) -> Point { self.window_origin() + widget_point.to_vec2() } From fe410e61e073ac6bed94f795e08bf2fb18bccb4e Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Thu, 14 Jan 2021 14:04:43 +0000 Subject: [PATCH 5/8] Update druid/src/app.rs doc fix Co-authored-by: Colin Rofls --- druid/src/app.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/druid/src/app.rs b/druid/src/app.rs index bfe226fbb2..a4ea96e07e 100644 --- a/druid/src/app.rs +++ b/druid/src/app.rs @@ -39,7 +39,10 @@ pub struct AppLauncher { /// Defines how a windows size should be determined #[derive(Copy, Clone, Debug)] pub enum WindowSizePolicy { - /// Use the content of the window to determine the size + /// Use the content of the window to determine the size. + /// + /// If you use this option, your root widget will be passed infinite constraints; + /// you are responsible for ensuring that your content picks an appropriate size. Content, /// Use the provided window size User, From cd8a0691a5284a7ae795f52eef1d6719738c78c7 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Thu, 14 Jan 2021 14:05:50 +0000 Subject: [PATCH 6/8] Update druid/src/contexts.rs doc fix Co-authored-by: Colin Rofls --- druid/src/contexts.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/druid/src/contexts.rs b/druid/src/contexts.rs index 2ef0f70159..9c076e318e 100644 --- a/druid/src/contexts.rs +++ b/druid/src/contexts.rs @@ -198,8 +198,7 @@ impl_context_method!( self.window_origin() + widget_point.to_vec2() } - /// Takes a point in widget coordinates and transforms it to - /// screen coordinates + /// Convert a point from the widget's coordinate space to the screen's. pub fn to_screen(&self, widget_point: Point) -> Point { let insets = self.window().get_content_insets(); let content_origin = self.window().get_position() + Vec2::new(insets.x0, insets.y0); From 1ac4e09fc2540dc28b7f3c3f2ddcabd83b5f6817 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Thu, 14 Jan 2021 14:12:09 +0000 Subject: [PATCH 7/8] Comment fixes --- druid-shell/src/platform/mac/window.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/druid-shell/src/platform/mac/window.rs b/druid-shell/src/platform/mac/window.rs index 8aaf330a5c..8153904670 100644 --- a/druid-shell/src/platform/mac/window.rs +++ b/druid-shell/src/platform/mac/window.rs @@ -775,7 +775,7 @@ fn set_size_deferred(this: &mut Object, _view_state: &mut ViewState, size: Size) let current_frame: NSRect = msg_send![window, frame]; let mut new_frame = current_frame; - // TODO: maintain druid origin (as mac origin is bottom left) + // maintain druid origin (as mac origin is bottom left) new_frame.origin.y -= size.height - current_frame.size.height; new_frame.size.width = size.width; new_frame.size.height = size.height; @@ -785,13 +785,19 @@ fn set_size_deferred(this: &mut Object, _view_state: &mut ViewState, size: Size) fn set_position_deferred(this: &mut Object, _view_state: &mut ViewState, position: Point) { unsafe { - // TODO this should be the max y in orig mac coords + let window: id = msg_send![this, window]; let frame: NSRect = msg_send![window, frame]; let mut new_frame = frame; new_frame.origin.x = position.x; + // TODO Everywhere we use the height for flipping around y it should be the max y in orig mac coords. + // Need to set up a 3 screen config to test in this arrangement. + // 3 + // 1 + // 2 + let screen_height = crate::Screen::get_display_rect().height(); new_frame.origin.y = screen_height - position.y - frame.size.height; // Flip back let () = msg_send![window, setFrame: new_frame display: YES]; From 6ceefdc669adadf42513e4ee07d8792f6a1e3cf0 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Thu, 14 Jan 2021 14:25:54 +0000 Subject: [PATCH 8/8] Rename content_insets --- druid-shell/src/platform/gtk/window.rs | 4 ++-- druid-shell/src/platform/mac/window.rs | 4 +--- druid-shell/src/platform/web/window.rs | 4 ++-- druid-shell/src/platform/windows/window.rs | 4 ++-- druid-shell/src/platform/x11/window.rs | 4 ++-- druid-shell/src/window.rs | 4 ++-- druid/src/contexts.rs | 8 ++++++-- druid/src/window.rs | 2 +- 8 files changed, 18 insertions(+), 16 deletions(-) diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index fb01e09bf6..0a235ca2f9 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -818,8 +818,8 @@ impl WindowHandle { } } - pub fn get_content_insets(&self) -> Insets { - log::warn!("WindowHandle::get_content_insets unimplemented for GTK platforms."); + pub fn content_insets(&self) -> Insets { + log::warn!("WindowHandle::content_insets unimplemented for GTK platforms."); Insets::ZERO } diff --git a/druid-shell/src/platform/mac/window.rs b/druid-shell/src/platform/mac/window.rs index 8153904670..c51a496874 100644 --- a/druid-shell/src/platform/mac/window.rs +++ b/druid-shell/src/platform/mac/window.rs @@ -785,8 +785,6 @@ fn set_size_deferred(this: &mut Object, _view_state: &mut ViewState, size: Size) fn set_position_deferred(this: &mut Object, _view_state: &mut ViewState, position: Point) { unsafe { - - let window: id = msg_send![this, window]; let frame: NSRect = msg_send![window, frame]; @@ -1071,7 +1069,7 @@ impl WindowHandle { } } - pub fn get_content_insets(&self) -> Insets { + pub fn content_insets(&self) -> Insets { unsafe { let screen_height = crate::Screen::get_display_rect().height(); diff --git a/druid-shell/src/platform/web/window.rs b/druid-shell/src/platform/web/window.rs index 7eb422ae5f..3e16852106 100644 --- a/druid-shell/src/platform/web/window.rs +++ b/druid-shell/src/platform/web/window.rs @@ -477,8 +477,8 @@ impl WindowHandle { Size::new(0.0, 0.0) } - pub fn get_content_insets(&self) -> Insets { - log::warn!("WindowHandle::get_content_insets unimplemented for web."); + pub fn content_insets(&self) -> Insets { + log::warn!("WindowHandle::content_insets unimplemented for web."); Insets::ZERO } diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index b0ab9c95e2..c9ecf640cb 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -1657,8 +1657,8 @@ impl WindowHandle { Point::new(0.0, 0.0) } - pub fn get_content_insets(&self) -> Insets { - log::warn!("WindowHandle::get_content_insets unimplemented for windows."); + pub fn content_insets(&self) -> Insets { + log::warn!("WindowHandle::content_insets unimplemented for windows."); Insets::ZERO } diff --git a/druid-shell/src/platform/x11/window.rs b/druid-shell/src/platform/x11/window.rs index 8829f8bfef..a44154b9a4 100644 --- a/druid-shell/src/platform/x11/window.rs +++ b/druid-shell/src/platform/x11/window.rs @@ -1404,8 +1404,8 @@ impl WindowHandle { Point::new(0.0, 0.0) } - pub fn get_content_insets(&self) -> Insets { - log::warn!("WindowHandle::get_content_insets unimplemented for X11 platforms."); + pub fn content_insets(&self) -> Insets { + log::warn!("WindowHandle::content_insets unimplemented for X11 platforms."); Insets::ZERO } diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index 60d781b407..eb779d6e2a 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -203,8 +203,8 @@ impl WindowHandle { /// Returns the insets of the window content from its position and size in [pixels](crate::Scale). /// /// This is to account for any window system provided chrome, eg. title bars. - pub fn get_content_insets(&self) -> Insets { - self.0.get_content_insets() + pub fn content_insets(&self) -> Insets { + self.0.content_insets() } /// Set the window's size in [display points](crate::Scale). diff --git a/druid/src/contexts.rs b/druid/src/contexts.rs index 9c076e318e..7db5ddef22 100644 --- a/druid/src/contexts.rs +++ b/druid/src/contexts.rs @@ -186,7 +186,8 @@ impl_context_method!( self.widget_state.size() } - /// The origin of the widget in window coordinates. + /// The origin of the widget in window coordinates, relative to the top left corner of the + /// content area. pub fn window_origin(&self) -> Point { self.widget_state.window_origin() } @@ -199,8 +200,11 @@ impl_context_method!( } /// Convert a point from the widget's coordinate space to the screen's. + /// See the [`Screen`] module + /// + /// [`Screen`]: crate::shell::Screen pub fn to_screen(&self, widget_point: Point) -> Point { - let insets = self.window().get_content_insets(); + let insets = self.window().content_insets(); let content_origin = self.window().get_position() + Vec2::new(insets.x0, insets.y0); content_origin + self.to_window(widget_point).to_vec2() } diff --git a/druid/src/window.rs b/druid/src/window.rs index e863a218ef..64a808e3fc 100644 --- a/druid/src/window.rs +++ b/druid/src/window.rs @@ -384,7 +384,7 @@ impl Window { }; let content_size = self.root.layout(&mut layout_ctx, &bc, data, env); if let WindowSizePolicy::Content = self.size_policy { - let insets = self.handle.get_content_insets(); + let insets = self.handle.content_insets(); let full_size = (content_size.to_rect() + insets).size(); if self.size != full_size { self.size = full_size;