From 08aa4119294caad59ec528f18bb4453d3d36d356 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Mon, 4 May 2020 21:16:34 +0300 Subject: [PATCH 01/29] Fix DPI support in GTK. --- CHANGELOG.md | 3 + druid-shell/Cargo.toml | 1 + druid-shell/examples/invalidate.rs | 22 +- druid-shell/examples/perftest.rs | 23 +- druid-shell/examples/shello.rs | 15 +- druid-shell/src/lib.rs | 2 + druid-shell/src/platform/gtk/window.rs | 310 ++++++++++++------------- druid-shell/src/scale.rs | 234 +++++++++++++++++++ druid-shell/src/window.rs | 17 +- druid/src/win_handler.rs | 4 +- druid/src/window.rs | 7 +- 11 files changed, 431 insertions(+), 207 deletions(-) create mode 100644 druid-shell/src/scale.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f069f8add5..324d71af06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,8 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i - Global `Application` associated functions are instance methods instead, e.g. `Application::global().quit()` instead of the old `Application::quit()`. ([#763] by [@xStrom]) - Timer events will only be delivered to the widgets that requested them. ([#831] by [@sjoshid]) - `Event::Wheel` now contains a `MouseEvent` structure. ([#895] by [@teddemunnik]) +- The `WindowHandle::get_dpi` method got replaced by `WindowHandle::get_scale`. ([#904] by [@xStrom]) +- The `WinHandler::size` method now gets a `Size` in points. ([#904] by [@xStrom]) - `AppDelegate::command` now receives a `Target` instead of a `&Target`. ([#909] by [@xStrom]) - `SHOW_WINDOW` and `CLOSE_WINDOW` commands now only use `Target` to determine the affected window. ([#928] by [@finnerale]) - Replaced `NEW_WINDOW`, `SET_MENU` and `SHOW_CONTEXT_MENU` commands with methods on `EventCtx` and `DelegateCtx`. ([#931] by [@finnerale]) @@ -94,6 +96,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i - X11: Support individual window closing. ([#900] by [@xStrom]) - X11: Support `Application::quit`. ([#900] by [@xStrom]) - GTK: Support file filters in open/save dialogs. ([#903] by [@jneem]) +- GTK: Support DPI values other than 96. ([#904] by [@xStrom]) - X11: Support key and mouse button state. ([#920] by [@jneem]) - Routing `LifeCycle::FocusChanged` to descendant widgets. ([#925] by [@yrns]) - Built-in open and save menu items now show the correct label and submit the right commands. ([#930] by [@finnerale]) diff --git a/druid-shell/Cargo.toml b/druid-shell/Cargo.toml index 5807bcb3e2..239f79a188 100644 --- a/druid-shell/Cargo.toml +++ b/druid-shell/Cargo.toml @@ -24,6 +24,7 @@ kurbo = "0.6.0" log = "0.4.8" lazy_static = "1.0" time = "0.2.7" +float-cmp = { version = "0.6.0", default-features = false } cfg-if = "0.1.10" instant = { version = "0.1", features = ["wasm-bindgen"] } diff --git a/druid-shell/examples/invalidate.rs b/druid-shell/examples/invalidate.rs index 573cc65629..7c6b319b14 100644 --- a/druid-shell/examples/invalidate.rs +++ b/druid-shell/examples/invalidate.rs @@ -16,14 +16,14 @@ use std::any::Any; use std::time::{Duration, Instant}; -use druid_shell::kurbo::{Point, Rect}; +use druid_shell::kurbo::{Point, Rect, Size}; use druid_shell::piet::{Color, Piet, RenderContext}; use druid_shell::{Application, TimerToken, WinHandler, WindowBuilder, WindowHandle}; struct InvalidateTest { handle: WindowHandle, - size: (f64, f64), + size: Size, start_time: Instant, color: Color, rect: Rect, @@ -39,10 +39,10 @@ impl InvalidateTest { (_, _) => Color::rgb8(r, g, b.wrapping_add(10)), }; - self.rect.x0 = (self.rect.x0 + 5.0) % self.size.0; - self.rect.x1 = (self.rect.x1 + 5.5) % self.size.0; - self.rect.y0 = (self.rect.y0 + 3.0) % self.size.1; - self.rect.y1 = (self.rect.y1 + 3.5) % self.size.1; + self.rect.x0 = (self.rect.x0 + 5.0) % self.size.width; + self.rect.x1 = (self.rect.x1 + 5.5) % self.size.width; + self.rect.y0 = (self.rect.y0 + 3.0) % self.size.height; + self.rect.y1 = (self.rect.y1 + 3.5) % self.size.height; } } @@ -63,12 +63,8 @@ impl WinHandler for InvalidateTest { false } - fn size(&mut self, width: u32, height: u32) { - let dpi = self.handle.get_dpi(); - let dpi_scale = dpi as f64 / 96.0; - let width_f = (width as f64) / dpi_scale; - let height_f = (height as f64) / dpi_scale; - self.size = (width_f, height_f); + fn size(&mut self, size: Size) { + self.size = size; } fn command(&mut self, id: u32) { @@ -91,7 +87,7 @@ fn main() { let app = Application::new().unwrap(); let mut builder = WindowBuilder::new(app.clone()); let inv_test = InvalidateTest { - size: Default::default(), + size: Size::ZERO, handle: Default::default(), start_time: Instant::now(), rect: Rect::from_origin_size(Point::ZERO, (10.0, 20.0)), diff --git a/druid-shell/examples/perftest.rs b/druid-shell/examples/perftest.rs index 6c3914fcdf..0c62965100 100644 --- a/druid-shell/examples/perftest.rs +++ b/druid-shell/examples/perftest.rs @@ -16,7 +16,7 @@ use std::any::Any; use time::Instant; -use piet_common::kurbo::{Line, Rect}; +use piet_common::kurbo::{Line, Rect, Size}; use piet_common::{Color, FontBuilder, Piet, RenderContext, Text, TextLayoutBuilder}; use druid_shell::{Application, KeyEvent, WinHandler, WindowBuilder, WindowHandle}; @@ -26,7 +26,7 @@ const FG_COLOR: Color = Color::rgb8(0xf0, 0xf0, 0xea); struct PerfTest { handle: WindowHandle, - size: (f64, f64), + size: Size, start_time: Instant, last_time: Instant, } @@ -37,11 +37,14 @@ impl WinHandler for PerfTest { } fn paint(&mut self, piet: &mut Piet, _: Rect) -> bool { - let (width, height) = self.size; - let rect = Rect::new(0.0, 0.0, width, height); + let rect = self.size.to_rect(); piet.fill(rect, &BG_COLOR); - piet.stroke(Line::new((0.0, height), (width, 0.0)), &FG_COLOR, 1.0); + piet.stroke( + Line::new((0.0, self.size.height), (self.size.width, 0.0)), + &FG_COLOR, + 1.0, + ); let current_ns = (Instant::now() - self.start_time).whole_nanoseconds(); let th = ::std::f64::consts::PI * (current_ns as f64) * 2e-9; @@ -98,12 +101,8 @@ impl WinHandler for PerfTest { false } - fn size(&mut self, width: u32, height: u32) { - let dpi = self.handle.get_dpi(); - let dpi_scale = dpi as f64 / 96.0; - let width_f = (width as f64) / dpi_scale; - let height_f = (height as f64) / dpi_scale; - self.size = (width_f, height_f); + fn size(&mut self, size: Size) { + self.size = size; } fn destroy(&mut self) { @@ -119,7 +118,7 @@ fn main() { let app = Application::new().unwrap(); let mut builder = WindowBuilder::new(app.clone()); let perf_test = PerfTest { - size: Default::default(), + size: Size::ZERO, handle: Default::default(), start_time: time::Instant::now(), last_time: time::Instant::now(), diff --git a/druid-shell/examples/shello.rs b/druid-shell/examples/shello.rs index 29f6d4c2bb..36b9b1be73 100644 --- a/druid-shell/examples/shello.rs +++ b/druid-shell/examples/shello.rs @@ -14,7 +14,7 @@ use std::any::Any; -use druid_shell::kurbo::{Line, Rect}; +use druid_shell::kurbo::{Line, Rect, Size}; use druid_shell::piet::{Color, RenderContext}; use druid_shell::{ @@ -27,7 +27,7 @@ const FG_COLOR: Color = Color::rgb8(0xf0, 0xf0, 0xea); #[derive(Default)] struct HelloState { - size: (f64, f64), + size: Size, handle: WindowHandle, } @@ -37,8 +37,7 @@ impl WinHandler for HelloState { } fn paint(&mut self, piet: &mut piet_common::Piet, _: Rect) -> bool { - let (width, height) = self.size; - let rect = Rect::new(0.0, 0.0, width, height); + let rect = self.size.to_rect(); piet.fill(rect, &BG_COLOR); piet.stroke(Line::new((10.0, 50.0), (90.0, 90.0)), &FG_COLOR, 1.0); false @@ -92,12 +91,8 @@ impl WinHandler for HelloState { println!("timer fired: {:?}", id); } - fn size(&mut self, width: u32, height: u32) { - let dpi = self.handle.get_dpi(); - let dpi_scale = dpi as f64 / 96.0; - let width_f = (width as f64) / dpi_scale; - let height_f = (height as f64) / dpi_scale; - self.size = (width_f, height_f); + fn size(&mut self, size: Size) { + self.size = size; } fn destroy(&mut self) { diff --git a/druid-shell/src/lib.rs b/druid-shell/src/lib.rs index 2c03750e58..dcc1503c8c 100644 --- a/druid-shell/src/lib.rs +++ b/druid-shell/src/lib.rs @@ -35,6 +35,7 @@ mod keycodes; mod menu; mod mouse; mod platform; +mod scale; mod util; mod window; @@ -48,6 +49,7 @@ pub use keyboard::{KeyEvent, KeyModifiers}; pub use keycodes::KeyCode; pub use menu::Menu; pub use mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; +pub use scale::Scale; pub use window::{ IdleHandle, IdleToken, Text, TimerToken, WinHandler, WindowBuilder, WindowHandle, }; diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 91d63df8c2..30f39de4df 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -15,7 +15,7 @@ //! GTK window creation and management. use std::any::Any; -use std::cell::{Cell, RefCell}; +use std::cell::RefCell; use std::convert::TryFrom; use std::ffi::c_void; use std::ffi::OsString; @@ -37,6 +37,7 @@ use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; use crate::keyboard; use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; +use crate::scale::Scale; use crate::window::{IdleToken, Text, TimerToken, WinHandler}; use crate::Error; @@ -106,6 +107,7 @@ enum IdleKind { pub(crate) struct WindowState { window: ApplicationWindow, + scale: RefCell, drawing_area: DrawingArea, pub(crate) handler: RefCell>, idle_queue: Arc>>, @@ -165,16 +167,15 @@ impl WindowBuilder { window.set_resizable(self.resizable); window.set_decorated(self.show_titlebar); - let dpi_scale = window + // Get the GTK reported DPI + let dpi = window .get_display() .map(|c| c.get_default_screen().get_resolution() as f64) - .unwrap_or(96.0) - / 96.0; + .unwrap_or(96.0); + let mut scale = Scale::new(dpi); + let size_px = scale.set_size_pt(self.size); - window.set_default_size( - (self.size.width * dpi_scale) as i32, - (self.size.height * dpi_scale) as i32, - ); + window.set_default_size(size_px.width as i32, size_px.height as i32); let accel_group = AccelGroup::new(); window.add_accel_group(&accel_group); @@ -185,6 +186,7 @@ impl WindowBuilder { let win_state = Arc::new(WindowState { window, + scale: RefCell::new(scale), drawing_area, handler: RefCell::new(handler), idle_queue: Arc::new(Mutex::new(vec![])), @@ -233,53 +235,62 @@ impl WindowBuilder { Inhibit(true) }); - if let Some(min_size) = self.min_size { - win_state.drawing_area.set_size_request( - (min_size.width * dpi_scale) as i32, - (min_size.height * dpi_scale) as i32, - ); + // Set the minimum size + if let Some(size_pt) = self.min_size { + let mut scale = Scale::new(dpi); + let size_px = scale.set_size_pt(size_pt); + win_state + .drawing_area + .set_size_request(size_px.width as i32, size_px.height as i32); } - let last_size = Cell::new((0, 0)); - win_state.drawing_area.connect_draw(clone!(handle => move |widget, context| { if let Some(state) = handle.state.upgrade() { - - let extents = widget.get_allocation(); - let dpi_scale = state.window.get_window() - .map(|w| w.get_display().get_default_screen().get_resolution()) - .unwrap_or(96.0) / 96.0; - let size = ((extents.width as f64 * dpi_scale) as u32, (extents.height as f64 * dpi_scale) as u32); - - if last_size.get() != size { - if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() { - last_size.set(size); - handler_borrow.size(size.0, size.1); - } else { - log::warn!("Resizing was skipped because the handler was already borrowed"); + if let Ok(mut scale) = state.scale.try_borrow_mut() { + // Check if the GTK reported DPI has changed, + // so that we can change our scale factor without restarting the application. + if let Some(dpi) = state.window.get_window() + .map(|w| w.get_display().get_default_screen().get_resolution()) { + if !scale.dpi_approx_eq(dpi) { + *scale = Scale::new(dpi); + } } - } - // For some reason piet needs a mutable context, so give it one I guess. - let mut context = context.clone(); - let (x0, y0, x1, y1) = context.clip_extents(); - let mut piet_context = Piet::new(&mut context); - - if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() { - let invalid_rect = Rect::new(x0 * dpi_scale, y0 * dpi_scale, x1 * dpi_scale, y1 * dpi_scale); - let anim = handler_borrow - .paint(&mut piet_context, invalid_rect); - if let Err(e) = piet_context.finish() { - eprintln!("piet error on render: {:?}", e); + // Check if the size of the window has changed + let extents = widget.get_allocation(); + let size_px = Size::new(extents.width as f64, extents.height as f64); + if scale.size_px() != size_px { + if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() { + let size_pt = scale.set_size_px(size_px); + handler_borrow.size(size_pt); + } else { + log::warn!("Resizing was skipped because the handler was already borrowed"); + } } - if anim { - widget.queue_draw(); + if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() { + // For some reason piet needs a mutable context, so give it one I guess. + let mut context = context.clone(); + context.scale(scale.scale_x(), scale.scale_y()); + let (x0, y0, x1, y1) = context.clip_extents(); + let invalid_rect = Rect::new(x0, y0, x1, y1); + + let mut piet_context = Piet::new(&mut context); + let anim = handler_borrow + .paint(&mut piet_context, invalid_rect); + if let Err(e) = piet_context.finish() { + eprintln!("piet error on render: {:?}", e); + } + + if anim { + widget.queue_draw(); + } + } else { + log::warn!("Drawing was skipped because the handler was already borrowed"); } } else { - log::warn!("Drawing was skipped because the handler was already borrowed"); + log::warn!("Drawing was skipped because the scale was already borrowed"); } - } Inhibit(false) @@ -287,23 +298,27 @@ impl WindowBuilder { win_state.drawing_area.connect_button_press_event(clone!(handle => move |_widget, event| { if let Some(state) = handle.state.upgrade() { - if let Ok(mut handler) = state.handler.try_borrow_mut() { - if let Some(button) = get_mouse_button(event.get_button()) { - let button_state = event.get_state(); - handler.mouse_down( - &MouseEvent { - pos: Point::from(event.get_position()), - buttons: get_mouse_buttons_from_modifiers(button_state).with(button), - mods: get_modifiers(button_state), - count: get_mouse_click_count(event.get_event_type()), - focus: false, - button, - wheel_delta: Vec2::ZERO - }, - ); + if let Ok(scale) = state.scale.try_borrow() { + if let Ok(mut handler) = state.handler.try_borrow_mut() { + if let Some(button) = get_mouse_button(event.get_button()) { + let button_state = event.get_state(); + handler.mouse_down( + &MouseEvent { + pos: scale.px_to_pt_point(Point::from(event.get_position())), + buttons: get_mouse_buttons_from_modifiers(button_state).with(button), + mods: get_modifiers(button_state), + count: get_mouse_click_count(event.get_event_type()), + focus: false, + button, + wheel_delta: Vec2::ZERO + }, + ); + } + } else { + log::warn!("GTK event was dropped because the handler was already borrowed"); } } else { - log::info!("GTK event was dropped because the handler was already borrowed"); + log::warn!("GTK event was dropped because the scale was already borrowed"); } } @@ -312,23 +327,27 @@ impl WindowBuilder { win_state.drawing_area.connect_button_release_event(clone!(handle => move |_widget, event| { if let Some(state) = handle.state.upgrade() { - if let Ok(mut handler) = state.handler.try_borrow_mut() { - if let Some(button) = get_mouse_button(event.get_button()) { - let button_state = event.get_state(); - handler.mouse_up( - &MouseEvent { - pos: Point::from(event.get_position()), - buttons: get_mouse_buttons_from_modifiers(button_state).without(button), - mods: get_modifiers(button_state), - count: 0, - focus: false, - button, - wheel_delta: Vec2::ZERO - }, - ); + if let Ok(scale) = state.scale.try_borrow() { + if let Ok(mut handler) = state.handler.try_borrow_mut() { + if let Some(button) = get_mouse_button(event.get_button()) { + let button_state = event.get_state(); + handler.mouse_up( + &MouseEvent { + pos: scale.px_to_pt_point(Point::from(event.get_position())), + buttons: get_mouse_buttons_from_modifiers(button_state).without(button), + mods: get_modifiers(button_state), + count: 0, + focus: false, + button, + wheel_delta: Vec2::ZERO + }, + ); + } + } else { + log::warn!("GTK event was dropped because the handler was already borrowed"); } } else { - log::info!("GTK event was dropped because the handler was already borrowed"); + log::warn!("GTK event was dropped because the scale was already borrowed"); } } @@ -337,21 +356,25 @@ impl WindowBuilder { win_state.drawing_area.connect_motion_notify_event(clone!(handle => move |_widget, motion| { if let Some(state) = handle.state.upgrade() { - let motion_state = motion.get_state(); - let mouse_event = MouseEvent { - pos: Point::from(motion.get_position()), - buttons: get_mouse_buttons_from_modifiers(motion_state), - mods: get_modifiers(motion_state), - count: 0, - focus: false, - button: MouseButton::None, - wheel_delta: Vec2::ZERO - }; + if let Ok(scale) = state.scale.try_borrow() { + let motion_state = motion.get_state(); + let mouse_event = MouseEvent { + pos: scale.px_to_pt_point(Point::from(motion.get_position())), + buttons: get_mouse_buttons_from_modifiers(motion_state), + mods: get_modifiers(motion_state), + count: 0, + focus: false, + button: MouseButton::None, + wheel_delta: Vec2::ZERO + }; - if let Ok(mut handler) = state.handler.try_borrow_mut() { - handler.mouse_move(&mouse_event); + if let Ok(mut handler) = state.handler.try_borrow_mut() { + handler.mouse_move(&mouse_event); + } else { + log::warn!("GTK event was dropped because the handler was already borrowed"); + } } else { - log::info!("GTK event was dropped because the handler was already borrowed"); + log::warn!("GTK event was dropped because the scale was already borrowed"); } } @@ -360,21 +383,25 @@ impl WindowBuilder { win_state.drawing_area.connect_leave_notify_event(clone!(handle => move |_widget, crossing| { if let Some(state) = handle.state.upgrade() { - let crossing_state = crossing.get_state(); - let mouse_event = MouseEvent { - pos: Point::from(crossing.get_position()), - buttons: get_mouse_buttons_from_modifiers(crossing_state), - mods: get_modifiers(crossing_state), - count: 0, - focus: false, - button: MouseButton::None, - wheel_delta: Vec2::ZERO - }; + if let Ok(scale) = state.scale.try_borrow() { + let crossing_state = crossing.get_state(); + let mouse_event = MouseEvent { + pos: scale.px_to_pt_point(Point::from(crossing.get_position())), + buttons: get_mouse_buttons_from_modifiers(crossing_state), + mods: get_modifiers(crossing_state), + count: 0, + focus: false, + button: MouseButton::None, + wheel_delta: Vec2::ZERO + }; - if let Ok(mut handler) = state.handler.try_borrow_mut() { - handler.mouse_move(&mouse_event); + if let Ok(mut handler) = state.handler.try_borrow_mut() { + handler.mouse_move(&mouse_event); + } else { + log::warn!("GTK event was dropped because the handler was already borrowed"); + } } else { - log::info!("GTK event was dropped because the handler was already borrowed"); + log::warn!("GTK event was dropped because the scale was already borrowed"); } } @@ -486,10 +513,9 @@ impl WindowBuilder { .expect("realize didn't create window") .set_event_compression(false); - win_state - .handler - .borrow_mut() - .connect(&handle.clone().into()); + let mut handler = win_state.handler.borrow_mut(); + handler.connect(&handle.clone().into()); + handler.size(self.size); Ok(handle) } @@ -536,23 +562,21 @@ impl WindowHandle { /// Request invalidation of one rectangle, which is given relative to the drawing area. pub fn invalidate_rect(&self, rect: Rect) { - let dpi_scale = self.get_dpi() as f64 / 96.0; - let rect = Rect::from_origin_size( - (rect.x0 * dpi_scale, rect.y0 * dpi_scale), - rect.size() * dpi_scale, - ); - - // GTK+ takes rects with integer coordinates, and non-negative width/height. - let r = rect.abs().expand(); - if let Some(state) = self.state.upgrade() { - let origin = state.drawing_area.get_allocation(); - state.window.queue_draw_area( - r.x0 as i32 + origin.x, - r.y0 as i32 + origin.y, - r.width() as i32, - r.height() as i32, - ); + if let Ok(scale) = state.scale.try_borrow() { + // GTK+ takes rects with non-negative integer width/height. + let r = scale.pt_to_px_rect(rect.abs()); + // Calculate the smallest integer rect that is a superset of the requested rect. + let r = Rect::new(r.x0.floor(), r.y0.floor(), r.x1.ceil(), r.y1.ceil()); + + let origin = state.drawing_area.get_allocation(); + state.window.queue_draw_area( + r.x0 as i32 + origin.x, + r.y0 as i32 + origin.y, + r.width() as i32, + r.height() as i32, + ); + } } } @@ -615,42 +639,16 @@ impl WindowHandle { }) } - /// Get the dpi of the window. - /// - /// TODO: we want to migrate this from dpi (with 96 as nominal) to a scale - /// factor (with 1 as nominal). - pub fn get_dpi(&self) -> f32 { - self.state - .upgrade() - .and_then(|s| s.window.get_window()) - .map(|w| w.get_display().get_default_screen().get_resolution() as f32) - .unwrap_or(96.0) - } - - // TODO: the following methods are cut'n'paste code. A good way to DRY - // would be to have a platform-independent trait with these as methods with - // default implementations. - - /// Convert a dimension in px units to physical pixels (rounding). - pub fn px_to_pixels(&self, x: f32) -> i32 { - (x * self.get_dpi() * (1.0 / 96.0)).round() as i32 - } - - /// Convert a point in px units to physical pixels (rounding). - pub fn px_to_pixels_xy(&self, x: f32, y: f32) -> (i32, i32) { - let scale = self.get_dpi() * (1.0 / 96.0); - ((x * scale).round() as i32, (y * scale).round() as i32) - } - - /// Convert a dimension in physical pixels to px units. - pub fn pixels_to_px>(&self, x: T) -> f32 { - (x.into() as f32) * 96.0 / self.get_dpi() - } - - /// Convert a point in physical pixels to px units. - pub fn pixels_to_px_xy>(&self, x: T, y: T) -> (f32, f32) { - let scale = 96.0 / self.get_dpi(); - ((x.into() as f32) * scale, (y.into() as f32) * scale) + /// Get the `Scale` of the window. + pub fn get_scale(&self) -> Result { + if let Some(state) = self.state.upgrade() { + match state.scale.try_borrow() { + Ok(scale) => Ok(scale.clone()), + Err(_) => Err(Error::Other("Failed to borrow state")), + } + } else { + Err(Error::Other("WindowState already dropped")) + } } pub fn set_menu(&self, menu: Menu) { diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs new file mode 100644 index 0000000000..c00efbc4a6 --- /dev/null +++ b/druid-shell/src/scale.rs @@ -0,0 +1,234 @@ +// Copyright 2020 The xi-editor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Resolution scale related helpers. + +use float_cmp::ApproxEq; + +use crate::kurbo::{Point, Rect, Size}; + +const SCALE_TARGET_DPI: f64 = 96.0; + +/// Resolution scaling state. +/// +/// This holds the platform DPI, the platform area size in pixels, +/// the logical area size in points, and the scale factors between them. +#[derive(Clone)] +pub struct Scale { + /// The platform reported DPI. + dpi: f64, + /// The ideal scaling factor based on the DPI. + scale: f64, + /// The actual current scale factor on the x axis. + scale_x: f64, + /// The actual current scale factor on the y axis. + scale_y: f64, + /// The size of the scaled area in points. + size_pt: Size, + /// The size of the scaled area in pixels. + size_px: Size, +} + +impl Scale { + /// Create a new `Scale` state based on the specified `dpi`. + pub fn new(dpi: f64) -> Scale { + let scale = dpi / SCALE_TARGET_DPI; + Scale { + dpi, + scale, + scale_x: scale, + scale_y: scale, + size_pt: Size::ZERO, + size_px: Size::ZERO, + } + } + + /// Set the size of the scaled area in points. + /// + /// This updates the internal scaling state and returns the same size in pixels. + /// + /// The calculated size in pixels is rounded to integers. + pub fn set_size_pt(&mut self, size: Size) -> Size { + self.size_px = (size * self.scale).round(); + self.size_pt = size; + self.scale_x = self.size_px.width / self.size_pt.width; + self.scale_y = self.size_px.height / self.size_pt.height; + self.size_px + } + + /// Set the size of the scaled area in pixels. + /// + /// This updates the internal scaling state and returns the same size in points. + /// + /// The calculated size in points is rounded to integers. + pub fn set_size_px(&mut self, size: Size) -> Size { + self.size_pt = div_size(size, self.scale).round(); + self.size_px = size; + self.scale_x = self.size_px.width / self.size_pt.width; + self.scale_y = self.size_px.height / self.size_pt.height; + self.size_pt + } + + /// Returns the platform DPI associated with this `Scale`. + #[inline] + pub fn dpi(&self) -> f64 { + self.dpi + } + + /// Returns `true` if the specified `dpi` is approximately equal to the `Scale` dpi. + #[inline] + pub fn dpi_approx_eq(&self, dpi: f64) -> bool { + self.dpi.approx_eq(dpi, (f64::EPSILON, 2)) + } + + /// Returns the x axis scale factor. + #[inline] + pub fn scale_x(&self) -> f64 { + self.scale_x + } + + /// Returns the y axis scale factor. + #[inline] + pub fn scale_y(&self) -> f64 { + self.scale_y + } + + /// Returns the scaled area size in points. + #[inline] + pub fn size_pt(&self) -> Size { + self.size_pt + } + + /// Returns the scaled area size in pixels. + #[inline] + pub fn size_px(&self) -> Size { + self.size_px + } + + /// Converts from points into pixels, using the x axis scale factor. + #[inline] + pub fn pt_to_px_x>(&self, x: T) -> f64 { + x.into() * self.scale_x + } + + /// Converts from points into pixels, using the y axis scale factor. + #[inline] + pub fn pt_to_px_y>(&self, y: T) -> f64 { + y.into() * self.scale_y + } + + /// Converts from points into pixels, + /// using the x axis scale factor for `x` and the y axis scale factor for `y`. + #[inline] + pub fn pt_to_px_xy>(&self, x: T, y: T) -> (f64, f64) { + (x.into() * self.scale_x, y.into() * self.scale_y) + } + + /// Converts a `Point` from points into pixels, + /// using the x axis scale factor for `Point::x` and the y axis scale factor for `Point::y`. + #[inline] + pub fn pt_to_px_point>(&self, point: T) -> Point { + let point = point.into(); + Point::new(point.x * self.scale_x, point.y * self.scale_y) + } + + /// Converts a `Rect` from points into pixels, + /// using the x axis scale factor for `Rect::x0` and `Rect::x1` + /// and the y axis scale factor for `Rect::y0` and `Rect::y1`. + #[inline] + pub fn pt_to_px_rect>(&self, rect: T) -> Rect { + let rect = rect.into(); + Rect::new( + rect.x0 * self.scale_x, + rect.y0 * self.scale_y, + rect.x1 * self.scale_x, + rect.y1 * self.scale_y, + ) + } + + /// Converts a `Size` from points into pixels, + /// using the x axis scale factor for `Size::width` + /// and the y axis scale factor for `Size::height`. + #[inline] + pub fn pt_to_px_size>(&self, size: T) -> Size { + let size = size.into(); + Size::new(size.width * self.scale_x, size.height * self.scale_y) + } + + /// Converts from pixels into points, using the x axis scale factor. + #[inline] + pub fn px_to_pt_x>(&self, x: T) -> f64 { + x.into() / self.scale_x + } + + /// Converts from pixels into points, using the y axis scale factor. + #[inline] + pub fn px_to_pt_y>(&self, y: T) -> f64 { + y.into() / self.scale_y + } + + /// Converts from pixels into points, + /// using the x axis scale factor for `x` and the y axis scale factor for `y`. + #[inline] + pub fn px_to_pt_xy>(&self, x: T, y: T) -> (f64, f64) { + (x.into() / self.scale_x, y.into() / self.scale_y) + } + + /// Converts a `Point` from pixels into points, + /// using the x axis scale factor for `Point::x` and the y axis scale factor for `Point::y`. + #[inline] + pub fn px_to_pt_point>(&self, point: T) -> Point { + let point = point.into(); + Point::new(point.x / self.scale_x, point.y / self.scale_y) + } + + /// Converts a `Rect` from pixels into points, + /// using the x axis scale factor for `Rect::x0` and `Rect::x1` + /// and the y axis scale factor for `Rect::y0` and `Rect::y1`. + #[inline] + pub fn px_to_pt_rect>(&self, rect: T) -> Rect { + let rect = rect.into(); + Rect::new( + rect.x0 / self.scale_x, + rect.y0 / self.scale_y, + rect.x1 / self.scale_x, + rect.y1 / self.scale_y, + ) + } + + /// Converts a `Size` from pixels into points, + /// using the x axis scale factor for `Size::width` + /// and the y axis scale factor for `Size::height`. + #[inline] + pub fn px_to_pt_size>(&self, size: T) -> Size { + let size = size.into(); + Size::new(size.width / self.scale_x, size.height / self.scale_y) + } +} + +impl std::fmt::Debug for Scale { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "DPI {} => {} => ({}, {}) | {:?} => {:?}", + self.dpi, self.scale, self.scale_x, self.scale_y, self.size_pt, self.size_px + ) + } +} + +// TODO: Remove this if kurbo::Size gets division support +#[inline] +fn div_size(size: Size, rhs: f64) -> Size { + Size::new(size.width / rhs, size.height / rhs) +} diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index 3e566d3938..dc57d6aedb 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -26,6 +26,7 @@ use crate::kurbo::{Point, Rect, Size}; use crate::menu::Menu; use crate::mouse::{Cursor, MouseEvent}; use crate::platform::window as platform; +use crate::scale::Scale; // It's possible we'll want to make this type alias at a lower level, // see https://github.com/linebender/piet/pull/37 for more discussion. @@ -199,12 +200,13 @@ impl WindowHandle { self.0.get_idle_handle().map(IdleHandle) } - /// Get the dpi of the window. + /// Get the [`Scale`] information of the window. /// - /// TODO: we want to migrate this from dpi (with 96 as nominal) to a scale - /// factor (with 1 as nominal). - pub fn get_dpi(&self) -> f32 { - self.0.get_dpi() + /// This information will be stale after the window is resized or the platform DPI changes. + /// + /// [`Scale`]: struct.Scale.html + pub fn get_scale(&self) -> Result { + self.0.get_scale() } } @@ -281,10 +283,9 @@ pub trait WinHandler { /// wish to stash it. fn connect(&mut self, handle: &WindowHandle); - /// Called when the size of the window is changed. Note that size - /// is in physical pixels. + /// Called when the size of the window is changed. #[allow(unused_variables)] - fn size(&mut self, width: u32, height: u32) {} + fn size(&mut self, size: Size) {} /// Request the handler to paint the window contents. Return value /// indicates whether window is animating, i.e. whether another paint diff --git a/druid/src/win_handler.rs b/druid/src/win_handler.rs index c3248ba967..d9cbe8a81b 100644 --- a/druid/src/win_handler.rs +++ b/druid/src/win_handler.rs @@ -648,8 +648,8 @@ impl WinHandler for DruidHandler { self.app_state.paint_window(self.window_id, piet, rect) } - fn size(&mut self, width: u32, height: u32) { - let event = Event::WindowSize(Size::new(f64::from(width), f64::from(height))); + fn size(&mut self, size: Size) { + let event = Event::WindowSize(size); self.app_state.do_window_event(event, self.window_id); } diff --git a/druid/src/window.rs b/druid/src/window.rs index 4db972a544..70fc639c2c 100644 --- a/druid/src/window.rs +++ b/druid/src/window.rs @@ -156,6 +156,7 @@ impl Window { env: &Env, ) -> bool { match &event { + Event::WindowSize(size) => self.size = *size, Event::MouseDown(e) | Event::MouseUp(e) | Event::MouseMove(e) | Event::Wheel(e) => { self.last_mouse_pos = Some(e.pos) } @@ -169,12 +170,6 @@ impl Window { }; let event = match event { - Event::WindowSize(size) => { - let dpi = f64::from(self.handle.get_dpi()); - let scale = 96.0 / dpi; - self.size = Size::new(size.width * scale, size.height * scale); - Event::WindowSize(self.size) - } Event::Timer(token) => { if let Some(widget_id) = self.timers.get(&token) { Event::Internal(InternalEvent::RouteTimer(token, *widget_id)) From bc19b79b143366c3e9313325a4d3488d57f51027 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Mon, 4 May 2020 21:28:41 +0300 Subject: [PATCH 02/29] Make X11 compile with Scale. --- druid-shell/src/platform/x11/window.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/druid-shell/src/platform/x11/window.rs b/druid-shell/src/platform/x11/window.rs index af53817339..672c726afe 100644 --- a/druid-shell/src/platform/x11/window.rs +++ b/druid-shell/src/platform/x11/window.rs @@ -29,11 +29,13 @@ use xcb::{ }; use crate::dialog::{FileDialogOptions, FileInfo}; +use crate::error::Error as ShellError; use crate::keyboard::{KeyEvent, KeyModifiers}; use crate::keycodes::KeyCode; use crate::kurbo::{Point, Rect, Size, Vec2}; use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; use crate::piet::{Piet, RenderContext}; +use crate::scale::Scale; use crate::window::{IdleToken, Text, TimerToken, WinHandler}; use super::application::Application; @@ -292,7 +294,7 @@ impl Window { Ok(mut handler) => { let size = self.size()?; handler.connect(&handle.into()); - handler.size(size.width as u32, size.height as u32); + handler.size(size); Ok(()) } Err(err) => Err(Error::BorrowError(format!( @@ -361,7 +363,7 @@ impl Window { ))); } match self.handler.try_borrow_mut() { - Ok(mut handler) => handler.size(size.width as u32, size.height as u32), + Ok(mut handler) => handler.size(size), Err(err) => { return Err(Error::BorrowError(format!( "Window::set_size handler: {}", @@ -507,9 +509,9 @@ impl Window { // TODO(x11/menus): implement Window::set_menu (currently a no-op) } - fn get_dpi(&self) -> f32 { + fn get_scale(&self) -> Result { // TODO(x11/dpi_scaling): figure out DPI scaling - 96.0 + Ok(Scale::new(96.0)) } pub fn handle_expose(&self, expose: &xcb::ExposeEvent) -> Result<(), Error> { @@ -860,12 +862,12 @@ impl WindowHandle { Some(IdleHandle) } - pub fn get_dpi(&self) -> f32 { + pub fn get_scale(&self) -> Result { if let Some(w) = self.window.upgrade() { - w.get_dpi() + w.get_scale().map_err(ShellError::Platform) } else { log::error!("Window {} has already been dropped", self.id); - 96.0 + Ok(Scale::new(96.0)) } } } From 11014f3fb4b3cd28af81a934cd9f50511de487b7 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Tue, 5 May 2020 01:52:18 +0300 Subject: [PATCH 03/29] Update float-cmp to 0.8.0. --- druid-shell/Cargo.toml | 2 +- druid/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/druid-shell/Cargo.toml b/druid-shell/Cargo.toml index 239f79a188..9c93744597 100644 --- a/druid-shell/Cargo.toml +++ b/druid-shell/Cargo.toml @@ -24,7 +24,7 @@ kurbo = "0.6.0" log = "0.4.8" lazy_static = "1.0" time = "0.2.7" -float-cmp = { version = "0.6.0", default-features = false } +float-cmp = { version = "0.8.0", features = ["std"], default-features = false } cfg-if = "0.1.10" instant = { version = "0.1", features = ["wasm-bindgen"] } diff --git a/druid/Cargo.toml b/druid/Cargo.toml index 1e419547ae..094267fc4e 100644 --- a/druid/Cargo.toml +++ b/druid/Cargo.toml @@ -46,6 +46,6 @@ image = { version = "0.23.2", optional = true } console_log = "0.1.2" [dev-dependencies] -float-cmp = { version = "0.6.0", default-features = false } +float-cmp = { version = "0.8.0", features = ["std"], default-features = false } tempfile = "3.1.0" piet-common = { version = "0.1.0", features = ["png"] } From 0a204e4a5a00f8aa431e740345f22af964185340 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Tue, 5 May 2020 17:16:53 +0300 Subject: [PATCH 04/29] Refactor Windows shell code to use new Scale system. --- druid-shell/src/platform/gtk/window.rs | 7 +- druid-shell/src/platform/windows/paint.rs | 11 +- druid-shell/src/platform/windows/util.rs | 19 ++- druid-shell/src/platform/windows/window.rs | 164 ++++++++++----------- druid-shell/src/scale.rs | 23 ++- 5 files changed, 126 insertions(+), 98 deletions(-) diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 30f39de4df..1edb2ef65f 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -37,7 +37,7 @@ use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; use crate::keyboard; use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; -use crate::scale::Scale; +use crate::scale::{self, Scale}; use crate::window::{IdleToken, Text, TimerToken, WinHandler}; use crate::Error; @@ -565,10 +565,7 @@ impl WindowHandle { if let Some(state) = self.state.upgrade() { if let Ok(scale) = state.scale.try_borrow() { // GTK+ takes rects with non-negative integer width/height. - let r = scale.pt_to_px_rect(rect.abs()); - // Calculate the smallest integer rect that is a superset of the requested rect. - let r = Rect::new(r.x0.floor(), r.y0.floor(), r.x1.ceil(), r.y1.ceil()); - + let r = scale.pt_to_px_rect(scale::expand_rect(rect.abs())); let origin = state.drawing_area.get_allocation(); state.window.queue_draw_area( r.x0 as i32 + origin.x, diff --git a/druid-shell/src/platform/windows/paint.rs b/druid-shell/src/platform/windows/paint.rs index fdfb2b969f..ce57fb9c69 100644 --- a/druid-shell/src/platform/windows/paint.rs +++ b/druid-shell/src/platform/windows/paint.rs @@ -38,6 +38,7 @@ use winapi::Interface; use piet_common::d2d::D2DFactory; use crate::platform::windows::{DeviceContext, DxgiSurfaceRenderTarget, HwndRenderTarget}; +use crate::scale::Scale; use super::error::Error; use super::util::as_result; @@ -69,7 +70,7 @@ pub(crate) unsafe fn create_render_target( pub(crate) unsafe fn create_render_target_dxgi( d2d_factory: &D2DFactory, swap_chain: *mut IDXGISwapChain1, - dpi: f32, + scale: &Scale, ) -> Result { let mut buffer: *mut IDXGISurface = null_mut(); as_result((*swap_chain).GetBuffer( @@ -77,18 +78,22 @@ pub(crate) unsafe fn create_render_target_dxgi( &IDXGISurface::uuidof(), &mut buffer as *mut _ as *mut *mut c_void, ))?; + let dpi_x = (96.0 * scale.scale_x()) as f32; + let dpi_y = (96.0 * scale.scale_y()) as f32; let props = D2D1_RENDER_TARGET_PROPERTIES { _type: D2D1_RENDER_TARGET_TYPE_DEFAULT, pixelFormat: D2D1_PIXEL_FORMAT { format: DXGI_FORMAT_B8G8R8A8_UNORM, alphaMode: D2D1_ALPHA_MODE_IGNORE, }, - dpiX: dpi, - dpiY: dpi, + dpiX: dpi_x, + dpiY: dpi_y, usage: D2D1_RENDER_TARGET_USAGE_NONE, minLevel: D2D1_FEATURE_LEVEL_DEFAULT, }; + println!("Setting D2D dpi x/y to: {} {}", dpi_x, dpi_y); + let mut render_target: *mut ID2D1RenderTarget = null_mut(); let res = (*d2d_factory.get_raw()).CreateDxgiSurfaceRenderTarget(buffer, &props, &mut render_target); diff --git a/druid-shell/src/platform/windows/util.rs b/druid-shell/src/platform/windows/util.rs index b231a8d390..8c642a966d 100644 --- a/druid-shell/src/platform/windows/util.rs +++ b/druid-shell/src/platform/windows/util.rs @@ -28,7 +28,7 @@ use winapi::ctypes::c_void; use winapi::shared::guiddef::REFIID; use winapi::shared::minwindef::{HMODULE, UINT}; use winapi::shared::ntdef::{HRESULT, LPWSTR}; -use winapi::shared::windef::HMONITOR; +use winapi::shared::windef::{RECT, HMONITOR}; use winapi::shared::winerror::SUCCEEDED; use winapi::um::fileapi::{CreateFileA, GetFileType, OPEN_EXISTING}; use winapi::um::handleapi::INVALID_HANDLE_VALUE; @@ -40,6 +40,8 @@ use winapi::um::winbase::{FILE_TYPE_UNKNOWN, STD_ERROR_HANDLE, STD_OUTPUT_HANDLE use winapi::um::wincon::{AttachConsole, ATTACH_PARENT_PROCESS}; use winapi::um::winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE}; +use crate::kurbo::Rect; + use super::error::Error; pub fn as_result(hr: HRESULT) -> Result<(), Error> { @@ -103,6 +105,21 @@ impl FromWide for [u16] { } } +#[inline] +pub fn rect_to_recti(rect: Rect) -> RECT { + RECT { + left: rect.x0 as i32, + top: rect.y0 as i32, + right: rect.x1 as i32, + bottom: rect.y1 as i32, + } +} + +#[inline] +pub fn recti_to_rect(rect: RECT) -> Rect { + Rect::new(rect.left as f64, rect.top as f64, rect.right as f64, rect.bottom as f64) +} + // Types for functions we want to load, which are only supported on newer windows versions // from shcore.dll type GetDpiForSystem = unsafe extern "system" fn() -> UINT; diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index a100b2d7b5..c7deaeee81 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -54,8 +54,10 @@ use super::error::Error; use super::menu::Menu; use super::paint; use super::timers::TimerSlots; -use super::util::{as_result, FromWide, ToWide, OPTIONAL_FUNCTIONS}; +use super::util::{self, as_result, FromWide, ToWide, OPTIONAL_FUNCTIONS}; +use crate::scale::{self, Scale}; +use crate::error::Error as ShellError; use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; use crate::keyboard::{KeyEvent, KeyModifiers}; @@ -134,7 +136,7 @@ enum IdleKind { /// by interior mutability, so we can handle reentrant calls. struct WindowState { hwnd: Cell, - dpi: Cell, + scale: RefCell, wndproc: Box, idle_queue: Arc>>, timers: Arc>, @@ -166,7 +168,6 @@ struct WndState { handler: Box, render_target: Option, dcomp_state: Option, - dpi: f32, min_size: Option, /// The `KeyCode` of the last `WM_KEYDOWN` event. We stash this so we can /// include it when handling `WM_CHAR` events. @@ -286,10 +287,10 @@ fn is_point_in_client_rect(hwnd: HWND, x: i32, y: i32) -> bool { } impl WndState { - fn rebuild_render_target(&mut self, d2d: &D2DFactory) { + fn rebuild_render_target(&mut self, d2d: &D2DFactory, scale: &Scale) { unsafe { let swap_chain = self.dcomp_state.as_ref().unwrap().swap_chain; - let rt = paint::create_render_target_dxgi(d2d, swap_chain, self.dpi) + let rt = paint::create_render_target_dxgi(d2d, swap_chain, scale) .map(|rt| rt.as_device_context().expect("TODO remove this expect")); self.render_target = rt.ok(); } @@ -416,11 +417,15 @@ impl WndProc for MyWndProc { s.render_target = rt.ok(); } s.handler.rebuild_resources(); + let rect_pt = { + self.handle.borrow().state.upgrade().unwrap().scale.borrow() + .px_to_pt_rect(util::recti_to_rect(rect)) + }; s.render( &self.d2d_factory, &self.dwrite_factory, &self.handle, - self.handle.borrow().rect_to_px(rect), + rect_pt, ); if let Some(ref mut ds) = s.dcomp_state { let params = DXGI_PRESENT_PARAMETERS { @@ -455,12 +460,23 @@ impl WndProc for MyWndProc { let rt = paint::create_render_target(&self.d2d_factory, hwnd); s.render_target = rt.ok(); { + let rect_pt = { + let scale = self.handle.borrow().state.upgrade().unwrap().scale.borrow().clone(); + { + let size_px = scale.size_px(); + let rect = util::recti_to_rect(rect); + if rect.x0 != 0. || rect.y0 != 0. || rect.x1 != size_px.width || rect.y1 != size_px.height { + log::error!("Expected scale size {:?} but Windows gave {:?}", size_px.to_rect(), rect); + } + } + scale.size_px().to_rect() + }; s.handler.rebuild_resources(); s.render( &self.d2d_factory, &self.dwrite_factory, &self.handle, - self.handle.borrow().rect_to_px(rect), + rect_pt, ); } @@ -479,6 +495,7 @@ impl WndProc for MyWndProc { if let Ok(mut s) = self.state.try_borrow_mut() { let s = s.as_mut().unwrap(); if s.dcomp_state.is_some() { + let scale = self.handle.borrow().state.upgrade().unwrap().scale.borrow().clone(); let mut rect: RECT = mem::zeroed(); if GetClientRect(hwnd, &mut rect) == FALSE { log::warn!( @@ -489,6 +506,9 @@ impl WndProc for MyWndProc { } let width = (rect.right - rect.left) as u32; let height = (rect.bottom - rect.top) as u32; + if scale.size_px() != Size::new(width as f64, height as f64) { + log::error!("Expected scale size {:?} but Windows gave {:?}", scale.size_px(), Size::new(width as f64, height as f64)); + } let res = (*s.dcomp_state.as_mut().unwrap().swap_chain).ResizeBuffers( 2, width, @@ -498,12 +518,12 @@ impl WndProc for MyWndProc { ); if SUCCEEDED(res) { s.handler.rebuild_resources(); - s.rebuild_render_target(&self.d2d_factory); + s.rebuild_render_target(&self.d2d_factory, &scale); s.render( &self.d2d_factory, &self.dwrite_factory, &self.handle, - self.handle.borrow().rect_to_px(rect), + scale.size_pt().to_rect(), ); (*s.dcomp_state.as_ref().unwrap().swap_chain).Present(0, 0); } else { @@ -530,7 +550,13 @@ impl WndProc for MyWndProc { let s = s.as_mut().unwrap(); let width = LOWORD(lparam as u32) as u32; let height = HIWORD(lparam as u32) as u32; - s.handler.size(width, height); + let (scale, size_pt) = { + let size_pt = self.handle.borrow().state.upgrade().unwrap().scale.borrow_mut() + .set_size_px((width as f64, height as f64)); + let scale = self.handle.borrow().state.upgrade().unwrap().scale.borrow().clone(); + (scale, size_pt) + }; + s.handler.size(size_pt); let use_hwnd = if let Some(ref dcomp_state) = s.dcomp_state { dcomp_state.sizing } else { @@ -557,9 +583,8 @@ impl WndProc for MyWndProc { ); } if SUCCEEDED(res) { - let (w, h) = self.handle.borrow().pixels_to_px_xy(width, height); - let rect = Rect::from_origin_size(Point::ORIGIN, (w as f64, h as f64)); - s.rebuild_render_target(&self.d2d_factory); + let rect = size_pt.to_rect(); + s.rebuild_render_target(&self.d2d_factory, &scale); s.render(&self.d2d_factory, &self.dwrite_factory, &self.handle, rect); if let Some(ref mut dcomp_state) = s.dcomp_state { (*dcomp_state.swap_chain).Present(0, 0); @@ -736,8 +761,10 @@ impl WndProc for MyWndProc { } } - let (px, py) = self.handle.borrow().pixels_to_px_xy(x, y); - let pos = Point::new(px as f64, py as f64); + let pos = { + let scale = self.handle.borrow().state.upgrade().unwrap().scale.borrow().clone(); + scale.px_to_pt_point((x as f64, y as f64)) + }; let mods = KeyModifiers { shift: wparam & MK_SHIFT != 0, alt: get_mod_state_alt(), @@ -804,8 +831,10 @@ impl WndProc for MyWndProc { }; let x = LOWORD(lparam as u32) as i16 as i32; let y = HIWORD(lparam as u32) as i16 as i32; - let (px, py) = self.handle.borrow().pixels_to_px_xy(x, y); - let pos = Point::new(px as f64, py as f64); + let pos = { + let scale = self.handle.borrow().state.upgrade().unwrap().scale.borrow().clone(); + scale.px_to_pt_point((x as f64, y as f64)) + }; let mods = KeyModifiers { shift: wparam & MK_SHIFT != 0, alt: get_mod_state_alt(), @@ -892,10 +921,11 @@ impl WndProc for MyWndProc { if let Ok(s) = self.state.try_borrow() { let s = s.as_ref().unwrap(); if let Some(min_size) = s.min_size { - min_max_info.ptMinTrackSize.x = - (min_size.width * (f64::from(s.dpi) / 96.0)) as i32; - min_max_info.ptMinTrackSize.y = - (min_size.height * (f64::from(s.dpi) / 96.0)) as i32; + let scale = self.handle.borrow().state.upgrade().unwrap().scale.borrow().clone(); + let mut scale = Scale::new(scale.dpi()); + let size_px = scale.set_size_pt(min_size); + min_max_info.ptMinTrackSize.x = size_px.width as i32; + min_max_info.ptMinTrackSize.y = size_px.height as i32; } } else { self.log_dropped_msg(hwnd, msg, wparam, lparam); @@ -981,9 +1011,21 @@ impl WindowBuilder { present_strategy: self.present_strategy, }; + // Simple scaling based on System Dpi (96 is equivalent to 100%) + let dpi = if let Some(func) = OPTIONAL_FUNCTIONS.GetDpiForSystem { + // Only supported on windows 10 + func() as f64 + } else { + // TODO GetDpiForMonitor is supported on windows 8.1, try falling back to that here + // Probably GetDeviceCaps(..., LOGPIXELSX) is the best to do pre-10 + 96.0 + }; + let mut scale = Scale::new(dpi); + let size_px = scale.set_size_pt(self.size); + let window = WindowState { hwnd: Cell::new(0 as HWND), - dpi: Cell::new(0.0), + scale: RefCell::new(scale), wndproc: Box::new(wndproc), idle_queue: Default::default(), timers: Arc::new(Mutex::new(TimerSlots::new(1))), @@ -994,22 +1036,10 @@ impl WindowBuilder { state: Rc::downgrade(&win), }; - // Simple scaling based on System Dpi (96 is equivalent to 100%) - let dpi = if let Some(func) = OPTIONAL_FUNCTIONS.GetDpiForSystem { - // Only supported on windows 10 - func() as f32 - } else { - // TODO GetDpiForMonitor is supported on windows 8.1, try falling back to that here - // Probably GetDeviceCaps(..., LOGPIXELSX) is the best to do pre-10 - 96.0 - }; - win.dpi.set(dpi); - let state = WndState { handler: self.handler.unwrap(), render_target: None, dcomp_state: None, - dpi, min_size: self.min_size, stashed_key_code: KeyCode::Unknown(0), stashed_char: None, @@ -1018,9 +1048,6 @@ impl WindowBuilder { }; win.wndproc.connect(&handle, state); - let width = (self.size.width * (f64::from(dpi) / 96.0)) as i32; - let height = (self.size.height * (f64::from(dpi) / 96.0)) as i32; - let (hmenu, accels) = match self.menu { Some(menu) => { let accels = menu.accels(); @@ -1045,8 +1072,8 @@ impl WindowBuilder { dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, - width, - height, + size_px.width as i32, + size_px.height as i32, 0 as HWND, hmenu, 0 as HINSTANCE, @@ -1290,9 +1317,10 @@ impl WindowHandle { } pub fn invalidate_rect(&self, rect: Rect) { - let rect = self.px_to_rect(rect); if let Some(w) = self.state.upgrade() { let hwnd = w.hwnd.get(); + let rect = scale::expand_rect(w.scale.borrow().pt_to_px_rect(rect)); + let rect = util::rect_to_recti(rect); unsafe { if InvalidateRect(hwnd, &rect, FALSE) == FALSE { log::warn!( @@ -1371,9 +1399,9 @@ impl WindowHandle { let hmenu = menu.into_hmenu(); if let Some(w) = self.state.upgrade() { let hwnd = w.hwnd.get(); - let (x, y) = self.px_to_pixels_xy(pos.x as f32, pos.y as f32); + let pos = w.scale.borrow().pt_to_px_point(pos).round(); unsafe { - let mut point = POINT { x, y }; + let mut point = POINT { x: pos.x as i32, y: pos.y as i32 }; ClientToScreen(hwnd, &mut point); if TrackPopupMenu(hmenu, TPM_LEFTALIGN, point.x, point.y, 0, hwnd, null()) == FALSE { @@ -1458,51 +1486,15 @@ impl WindowHandle { } } - /// Get the dpi of the window. - pub fn get_dpi(&self) -> f32 { - if let Some(w) = self.state.upgrade() { - w.dpi.get() + /// Get the `Scale` of the window. + pub fn get_scale(&self) -> Result { + if let Some(state) = self.state.upgrade() { + match state.scale.try_borrow() { + Ok(scale) => Ok(scale.clone()), + Err(_) => Err(ShellError::Other("Failed to borrow state")), + } } else { - 96.0 - } - } - - /// Convert a dimension in px units to physical pixels (rounding). - pub fn px_to_pixels(&self, x: f32) -> i32 { - (x * self.get_dpi() * (1.0 / 96.0)).round() as i32 - } - - /// Convert a point in px units to physical pixels (rounding). - pub fn px_to_pixels_xy(&self, x: f32, y: f32) -> (i32, i32) { - let scale = self.get_dpi() * (1.0 / 96.0); - ((x * scale).round() as i32, (y * scale).round() as i32) - } - - /// Convert a dimension in physical pixels to px units. - pub fn pixels_to_px>(&self, x: T) -> f32 { - (x.into() as f32) * 96.0 / self.get_dpi() - } - - /// Convert a point in physical pixels to px units. - pub fn pixels_to_px_xy>(&self, x: T, y: T) -> (f32, f32) { - let scale = 96.0 / self.get_dpi(); - ((x.into() as f32) * scale, (y.into() as f32) * scale) - } - - /// Convert a rectangle from physical pixels to px units. - pub fn rect_to_px(&self, rect: RECT) -> Rect { - let (x0, y0) = self.pixels_to_px_xy(rect.left, rect.top); - let (x1, y1) = self.pixels_to_px_xy(rect.right, rect.bottom); - Rect::new(x0 as f64, y0 as f64, x1 as f64, y1 as f64) - } - - pub fn px_to_rect(&self, rect: Rect) -> RECT { - let scale = self.get_dpi() as f64 / 96.0; - RECT { - left: (rect.x0 * scale).floor() as i32, - top: (rect.y0 * scale).floor() as i32, - right: (rect.x1 * scale).ceil() as i32, - bottom: (rect.y1 * scale).ceil() as i32, + Err(ShellError::Other("WindowState already dropped")) } } diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index c00efbc4a6..16300399f4 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -59,7 +59,8 @@ impl Scale { /// This updates the internal scaling state and returns the same size in pixels. /// /// The calculated size in pixels is rounded to integers. - pub fn set_size_pt(&mut self, size: Size) -> Size { + pub fn set_size_pt>(&mut self, size: T) -> Size { + let size = size.into(); self.size_px = (size * self.scale).round(); self.size_pt = size; self.scale_x = self.size_px.width / self.size_pt.width; @@ -72,7 +73,8 @@ impl Scale { /// This updates the internal scaling state and returns the same size in points. /// /// The calculated size in points is rounded to integers. - pub fn set_size_px(&mut self, size: Size) -> Size { + pub fn set_size_px>(&mut self, size: T) -> Size { + let size = size.into(); self.size_pt = div_size(size, self.scale).round(); self.size_px = size; self.scale_x = self.size_px.width / self.size_pt.width; @@ -227,8 +229,23 @@ impl std::fmt::Debug for Scale { } } -// TODO: Remove this if kurbo::Size gets division support +// TODO: Remove this if kurbo::Size gets division support via kurbo#108. #[inline] fn div_size(size: Size, rhs: f64) -> Size { Size::new(size.width / rhs, size.height / rhs) } + +// TODO: Replace usages of this with rect.expand() after kurbo#107 has landed. +pub fn expand_rect(rect: Rect) -> Rect { + let (x0, x1) = if rect.x0 < rect.x1 { + (rect.x0.floor(), rect.x1.ceil()) + } else { + (rect.x0.ceil(), rect.x1.floor()) + }; + let (y0, y1) = if rect.y0 < rect.y1 { + (rect.y0.floor(), rect.y1.ceil()) + } else { + (rect.y0.ceil(), rect.y1.floor()) + }; + Rect::new(x0, y0, x1, y1) +} \ No newline at end of file From 78700b2fe6a05039d4184c6e32fc746e9f8e64dc Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Tue, 5 May 2020 20:24:53 +0300 Subject: [PATCH 05/29] Add more Windows changes. --- druid-shell/src/platform/windows/paint.rs | 8 +- druid-shell/src/platform/windows/util.rs | 11 +- druid-shell/src/platform/windows/window.rs | 149 +++++++++++---------- druid-shell/src/scale.rs | 2 +- 4 files changed, 93 insertions(+), 77 deletions(-) diff --git a/druid-shell/src/platform/windows/paint.rs b/druid-shell/src/platform/windows/paint.rs index ce57fb9c69..350f2c62ba 100644 --- a/druid-shell/src/platform/windows/paint.rs +++ b/druid-shell/src/platform/windows/paint.rs @@ -22,8 +22,6 @@ use std::mem; use std::ptr::null_mut; -use log::{error, warn}; - use winapi::ctypes::c_void; use winapi::shared::dxgi::*; use winapi::shared::dxgi1_2::*; @@ -49,7 +47,7 @@ pub(crate) unsafe fn create_render_target( ) -> Result { let mut rect: RECT = mem::zeroed(); if GetClientRect(hwnd, &mut rect) == 0 { - warn!("GetClientRect failed."); + log::warn!("GetClientRect failed."); Err(Error::D2Error) } else { let width = (rect.right - rect.left) as u32; @@ -57,7 +55,7 @@ pub(crate) unsafe fn create_render_target( let res = HwndRenderTarget::create(d2d_factory, hwnd, width, height); if let Err(ref e) = res { - error!("Creating hwnd render target failed: {:?}", e); + log::error!("Creating hwnd render target failed: {:?}", e); } res.map(|hrt| cast_to_device_context(&hrt).expect("removethis")) .map_err(|_| Error::D2Error) @@ -92,8 +90,6 @@ pub(crate) unsafe fn create_render_target_dxgi( minLevel: D2D1_FEATURE_LEVEL_DEFAULT, }; - println!("Setting D2D dpi x/y to: {} {}", dpi_x, dpi_y); - let mut render_target: *mut ID2D1RenderTarget = null_mut(); let res = (*d2d_factory.get_raw()).CreateDxgiSurfaceRenderTarget(buffer, &props, &mut render_target); diff --git a/druid-shell/src/platform/windows/util.rs b/druid-shell/src/platform/windows/util.rs index 8c642a966d..dd77c43777 100644 --- a/druid-shell/src/platform/windows/util.rs +++ b/druid-shell/src/platform/windows/util.rs @@ -28,7 +28,7 @@ use winapi::ctypes::c_void; use winapi::shared::guiddef::REFIID; use winapi::shared::minwindef::{HMODULE, UINT}; use winapi::shared::ntdef::{HRESULT, LPWSTR}; -use winapi::shared::windef::{RECT, HMONITOR}; +use winapi::shared::windef::{HMONITOR, RECT}; use winapi::shared::winerror::SUCCEEDED; use winapi::um::fileapi::{CreateFileA, GetFileType, OPEN_EXISTING}; use winapi::um::handleapi::INVALID_HANDLE_VALUE; @@ -105,6 +105,7 @@ impl FromWide for [u16] { } } +/// Converts a `Rect` to a winapi `RECT`. #[inline] pub fn rect_to_recti(rect: Rect) -> RECT { RECT { @@ -115,9 +116,15 @@ pub fn rect_to_recti(rect: Rect) -> RECT { } } +/// Converts a winapi `RECT` to a `Rect`. #[inline] pub fn recti_to_rect(rect: RECT) -> Rect { - Rect::new(rect.left as f64, rect.top as f64, rect.right as f64, rect.bottom as f64) + Rect::new( + rect.left as f64, + rect.top as f64, + rect.right as f64, + rect.bottom as f64, + ) } // Types for functions we want to load, which are only supported on newer windows versions diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index c7deaeee81..32f6d18211 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -56,13 +56,13 @@ use super::paint; use super::timers::TimerSlots; use super::util::{self, as_result, FromWide, ToWide, OPTIONAL_FUNCTIONS}; -use crate::scale::{self, Scale}; -use crate::error::Error as ShellError; use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; +use crate::error::Error as ShellError; use crate::keyboard::{KeyEvent, KeyModifiers}; use crate::keycodes::KeyCode; use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; +use crate::scale::{self, Scale}; use crate::window::{IdleToken, Text, TimerToken, WinHandler}; extern "system" { @@ -356,6 +356,42 @@ impl MyWndProc { msg, hwnd, wparam, lparam ); } + + fn with_scale(&self, func: F) -> T + where + F: FnOnce(&Scale) -> T, + { + func( + &self + .handle + // Right now there aren't any mutable borrows to this. + // TODO: Attempt to guarantee this by making mutable handle borrows useless. + .borrow() + .state + .upgrade() + .unwrap() // WindowState drops after WM_NCDESTROY, so it's always here. + .scale + .borrow(), // Fine as long as scale is accessed through this or briefly elsewhere. + ) + } + + fn with_scale_mut(&self, func: F) -> T + where + F: FnOnce(&mut Scale) -> T, + { + func( + &mut self + .handle + // Right now there aren't any mutable borrows to this. + // TODO: Attempt to guarantee this by making mutable handle borrows useless. + .borrow() + .state + .upgrade() + .unwrap() // WindowState drops after WM_NCDESTROY, so it's always here. + .scale + .borrow_mut(), // Fine as long as scale is accessed through this or briefly elsewhere. + ) + } } impl WndProc for MyWndProc { @@ -417,10 +453,8 @@ impl WndProc for MyWndProc { s.render_target = rt.ok(); } s.handler.rebuild_resources(); - let rect_pt = { - self.handle.borrow().state.upgrade().unwrap().scale.borrow() - .px_to_pt_rect(util::recti_to_rect(rect)) - }; + let rect_pt = + self.with_scale(|scale| scale.px_to_pt_rect(util::recti_to_rect(rect))); s.render( &self.d2d_factory, &self.dwrite_factory, @@ -449,28 +483,10 @@ impl WndProc for MyWndProc { if let Ok(mut s) = self.state.try_borrow_mut() { let s = s.as_mut().unwrap(); if s.dcomp_state.is_some() { - let mut rect: RECT = mem::zeroed(); - if GetClientRect(hwnd, &mut rect) == FALSE { - log::warn!( - "GetClientRect failed: {}", - Error::Hr(HRESULT_FROM_WIN32(GetLastError())) - ); - return None; - } let rt = paint::create_render_target(&self.d2d_factory, hwnd); s.render_target = rt.ok(); { - let rect_pt = { - let scale = self.handle.borrow().state.upgrade().unwrap().scale.borrow().clone(); - { - let size_px = scale.size_px(); - let rect = util::recti_to_rect(rect); - if rect.x0 != 0. || rect.y0 != 0. || rect.x1 != size_px.width || rect.y1 != size_px.height { - log::error!("Expected scale size {:?} but Windows gave {:?}", size_px.to_rect(), rect); - } - } - scale.size_px().to_rect() - }; + let rect_pt = self.with_scale(|scale| scale.size_px().to_rect()); s.handler.rebuild_resources(); s.render( &self.d2d_factory, @@ -495,24 +511,12 @@ impl WndProc for MyWndProc { if let Ok(mut s) = self.state.try_borrow_mut() { let s = s.as_mut().unwrap(); if s.dcomp_state.is_some() { - let scale = self.handle.borrow().state.upgrade().unwrap().scale.borrow().clone(); - let mut rect: RECT = mem::zeroed(); - if GetClientRect(hwnd, &mut rect) == FALSE { - log::warn!( - "GetClientRect failed: {}", - Error::Hr(HRESULT_FROM_WIN32(GetLastError())) - ); - return None; - } - let width = (rect.right - rect.left) as u32; - let height = (rect.bottom - rect.top) as u32; - if scale.size_px() != Size::new(width as f64, height as f64) { - log::error!("Expected scale size {:?} but Windows gave {:?}", scale.size_px(), Size::new(width as f64, height as f64)); - } + let scale = self.with_scale(|scale| scale.clone()); + let size_px = scale.size_px(); let res = (*s.dcomp_state.as_mut().unwrap().swap_chain).ResizeBuffers( 2, - width, - height, + size_px.width as u32, + size_px.height as u32, DXGI_FORMAT_UNKNOWN, 0, ); @@ -550,12 +554,8 @@ impl WndProc for MyWndProc { let s = s.as_mut().unwrap(); let width = LOWORD(lparam as u32) as u32; let height = HIWORD(lparam as u32) as u32; - let (scale, size_pt) = { - let size_pt = self.handle.borrow().state.upgrade().unwrap().scale.borrow_mut() - .set_size_px((width as f64, height as f64)); - let scale = self.handle.borrow().state.upgrade().unwrap().scale.borrow().clone(); - (scale, size_pt) - }; + let size_pt = self + .with_scale_mut(|scale| scale.set_size_px((width as f64, height as f64))); s.handler.size(size_pt); let use_hwnd = if let Some(ref dcomp_state) = s.dcomp_state { dcomp_state.sizing @@ -569,7 +569,7 @@ impl WndProc for MyWndProc { let _ = hrt.ptr.Resize(&size); } } - InvalidateRect(hwnd, null_mut(), FALSE); + InvalidateRect(hwnd, null(), FALSE); } else { let res; { @@ -584,7 +584,9 @@ impl WndProc for MyWndProc { } if SUCCEEDED(res) { let rect = size_pt.to_rect(); - s.rebuild_render_target(&self.d2d_factory, &scale); + self.with_scale(|scale| { + s.rebuild_render_target(&self.d2d_factory, &scale) + }); s.render(&self.d2d_factory, &self.dwrite_factory, &self.handle, rect); if let Some(ref mut dcomp_state) = s.dcomp_state { (*dcomp_state.swap_chain).Present(0, 0); @@ -761,10 +763,7 @@ impl WndProc for MyWndProc { } } - let pos = { - let scale = self.handle.borrow().state.upgrade().unwrap().scale.borrow().clone(); - scale.px_to_pt_point((x as f64, y as f64)) - }; + let pos = self.with_scale(|scale| scale.px_to_pt_point((x as f64, y as f64))); let mods = KeyModifiers { shift: wparam & MK_SHIFT != 0, alt: get_mod_state_alt(), @@ -831,10 +830,8 @@ impl WndProc for MyWndProc { }; let x = LOWORD(lparam as u32) as i16 as i32; let y = HIWORD(lparam as u32) as i16 as i32; - let pos = { - let scale = self.handle.borrow().state.upgrade().unwrap().scale.borrow().clone(); - scale.px_to_pt_point((x as f64, y as f64)) - }; + let pos = + self.with_scale(|scale| scale.px_to_pt_point((x as f64, y as f64))); let mods = KeyModifiers { shift: wparam & MK_SHIFT != 0, alt: get_mod_state_alt(), @@ -920,12 +917,11 @@ impl WndProc for MyWndProc { let min_max_info = unsafe { &mut *(lparam as *mut MINMAXINFO) }; if let Ok(s) = self.state.try_borrow() { let s = s.as_ref().unwrap(); - if let Some(min_size) = s.min_size { - let scale = self.handle.borrow().state.upgrade().unwrap().scale.borrow().clone(); - let mut scale = Scale::new(scale.dpi()); - let size_px = scale.set_size_pt(min_size); - min_max_info.ptMinTrackSize.x = size_px.width as i32; - min_max_info.ptMinTrackSize.y = size_px.height as i32; + if let Some(min_size_pt) = s.min_size { + let min_size_px = self + .with_scale(|scale| Scale::new(scale.dpi()).set_size_pt(min_size_pt)); + min_max_info.ptMinTrackSize.x = min_size_px.width as i32; + min_max_info.ptMinTrackSize.y = min_size_px.height as i32; } } else { self.log_dropped_msg(hwnd, msg, wparam, lparam); @@ -1311,18 +1307,32 @@ impl WindowHandle { if let Some(w) = self.state.upgrade() { let hwnd = w.hwnd.get(); unsafe { - InvalidateRect(hwnd, null(), FALSE); + if InvalidateRect(hwnd, null(), FALSE) == FALSE { + log::warn!( + "InvalidateRect failed: {}", + Error::Hr(HRESULT_FROM_WIN32(GetLastError())) + ); + } } } } pub fn invalidate_rect(&self, rect: Rect) { if let Some(w) = self.state.upgrade() { + let rect = + w.scale.try_borrow().ok().map(|scale| { + util::rect_to_recti(scale::expand_rect(scale.pt_to_px_rect(rect))) + }); let hwnd = w.hwnd.get(); - let rect = scale::expand_rect(w.scale.borrow().pt_to_px_rect(rect)); - let rect = util::rect_to_recti(rect); unsafe { - if InvalidateRect(hwnd, &rect, FALSE) == FALSE { + let result = match rect { + Some(rect) => InvalidateRect(hwnd, &rect, FALSE), + None => { + log::warn!("Failed to get scale"); + InvalidateRect(hwnd, null(), FALSE) + } + }; + if result == FALSE { log::warn!( "InvalidateRect failed: {}", Error::Hr(HRESULT_FROM_WIN32(GetLastError())) @@ -1401,7 +1411,10 @@ impl WindowHandle { let hwnd = w.hwnd.get(); let pos = w.scale.borrow().pt_to_px_point(pos).round(); unsafe { - let mut point = POINT { x: pos.x as i32, y: pos.y as i32 }; + let mut point = POINT { + x: pos.x as i32, + y: pos.y as i32, + }; ClientToScreen(hwnd, &mut point); if TrackPopupMenu(hmenu, TPM_LEFTALIGN, point.x, point.y, 0, hwnd, null()) == FALSE { diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index 16300399f4..b362036e2b 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -248,4 +248,4 @@ pub fn expand_rect(rect: Rect) -> Rect { (rect.y0.ceil(), rect.y1.floor()) }; Rect::new(x0, y0, x1, y1) -} \ No newline at end of file +} From 0f1529fc15b451899dfb0a53f80b7c5fee7635a5 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Wed, 6 May 2020 15:10:45 +0300 Subject: [PATCH 06/29] Improve Windows DPI code. --- druid-shell/src/platform/windows/window.rs | 26 +++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 32f6d18211..617ac95193 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -486,7 +486,7 @@ impl WndProc for MyWndProc { let rt = paint::create_render_target(&self.d2d_factory, hwnd); s.render_target = rt.ok(); { - let rect_pt = self.with_scale(|scale| scale.size_px().to_rect()); + let rect_pt = self.with_scale(|scale| scale.size_pt().to_rect()); s.handler.rebuild_resources(); s.render( &self.d2d_factory, @@ -569,7 +569,12 @@ impl WndProc for MyWndProc { let _ = hrt.ptr.Resize(&size); } } - InvalidateRect(hwnd, null(), FALSE); + if InvalidateRect(hwnd, null(), FALSE) == FALSE { + log::warn!( + "InvalidateRect failed: {}", + Error::Hr(HRESULT_FROM_WIN32(GetLastError())) + ); + } } else { let res; { @@ -585,7 +590,7 @@ impl WndProc for MyWndProc { if SUCCEEDED(res) { let rect = size_pt.to_rect(); self.with_scale(|scale| { - s.rebuild_render_target(&self.d2d_factory, &scale) + s.rebuild_render_target(&self.d2d_factory, scale) }); s.render(&self.d2d_factory, &self.dwrite_factory, &self.handle, rect); if let Some(ref mut dcomp_state) = s.dcomp_state { @@ -1007,12 +1012,12 @@ impl WindowBuilder { present_strategy: self.present_strategy, }; - // Simple scaling based on System Dpi (96 is equivalent to 100%) + // Simple scaling based on System DPI (96 is equivalent to 100%) let dpi = if let Some(func) = OPTIONAL_FUNCTIONS.GetDpiForSystem { - // Only supported on windows 10 + // Only supported on Windows 10 func() as f64 } else { - // TODO GetDpiForMonitor is supported on windows 8.1, try falling back to that here + // TODO GetDpiForMonitor is supported on Windows 8.1, try falling back to that here // Probably GetDeviceCaps(..., LOGPIXELSX) is the best to do pre-10 96.0 }; @@ -1504,7 +1509,7 @@ impl WindowHandle { if let Some(state) = self.state.upgrade() { match state.scale.try_borrow() { Ok(scale) => Ok(scale.clone()), - Err(_) => Err(ShellError::Other("Failed to borrow state")), + Err(_) => Err(ShellError::Other("Failed to borrow scale")), } } else { Err(ShellError::Other("WindowState already dropped")) @@ -1564,7 +1569,12 @@ impl IdleHandle { fn invalidate(&self) { unsafe { - InvalidateRect(self.hwnd, null(), FALSE); + if InvalidateRect(self.hwnd, null(), FALSE) == FALSE { + log::warn!( + "InvalidateRect failed: {}", + Error::Hr(HRESULT_FROM_WIN32(GetLastError())) + ); + } } } } From 8b9a8b0c3eee2391fb7f73288136c9e9a8582bd4 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Wed, 6 May 2020 16:17:37 +0300 Subject: [PATCH 07/29] Improve DPI docs. --- druid-shell/src/platform/gtk/window.rs | 14 ++++++++------ druid-shell/src/scale.rs | 12 +++++++++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 1edb2ef65f..3738957444 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -252,6 +252,8 @@ impl WindowBuilder { if let Some(dpi) = state.window.get_window() .map(|w| w.get_display().get_default_screen().get_resolution()) { if !scale.dpi_approx_eq(dpi) { + // This new Scale will also ensure a window size event for the handler, + // because it defaults to zero size. *scale = Scale::new(dpi); } } @@ -260,11 +262,11 @@ impl WindowBuilder { let extents = widget.get_allocation(); let size_px = Size::new(extents.width as f64, extents.height as f64); if scale.size_px() != size_px { + let size_pt = scale.set_size_px(size_px); if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() { - let size_pt = scale.set_size_px(size_px); - handler_borrow.size(size_pt); + handler_borrow.size(size_pt); } else { - log::warn!("Resizing was skipped because the handler was already borrowed"); + log::warn!("Failed to inform the handler of a resize because it was already borrowed"); } } @@ -564,8 +566,8 @@ impl WindowHandle { pub fn invalidate_rect(&self, rect: Rect) { if let Some(state) = self.state.upgrade() { if let Ok(scale) = state.scale.try_borrow() { - // GTK+ takes rects with non-negative integer width/height. - let r = scale.pt_to_px_rect(scale::expand_rect(rect.abs())); + // GTK takes rects with non-negative integer width/height. + let r = scale::expand_rect(scale.pt_to_px_rect(rect.abs())); let origin = state.drawing_area.get_allocation(); state.window.queue_draw_area( r.x0 as i32 + origin.x, @@ -641,7 +643,7 @@ impl WindowHandle { if let Some(state) = self.state.upgrade() { match state.scale.try_borrow() { Ok(scale) => Ok(scale.clone()), - Err(_) => Err(Error::Other("Failed to borrow state")), + Err(_) => Err(Error::Other("Failed to borrow scale")), } } else { Err(Error::Other("WindowState already dropped")) diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index b362036e2b..6040eb031d 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -24,6 +24,16 @@ const SCALE_TARGET_DPI: f64 = 96.0; /// /// This holds the platform DPI, the platform area size in pixels, /// the logical area size in points, and the scale factors between them. +/// +/// To translate coordinates between pixels and points you should use one of the +/// helper conversion methods of `Scale` or for manual conversion [`scale_x`] / [`scale_y`]. +/// +/// The platform DPI describes the ideal target however that is not always possible to achieve. +/// The physical area size in pixels has to be in integers by definition. +// TODO: Maybe re-architect the scale_x/scale_y stuff for better pixel alignment? +/// +/// [`scale_x`]: #method.scale_x +/// [`scale_y`]: #method.scale_y #[derive(Clone)] pub struct Scale { /// The platform reported DPI. @@ -229,7 +239,7 @@ impl std::fmt::Debug for Scale { } } -// TODO: Remove this if kurbo::Size gets division support via kurbo#108. +// TODO: Remove this after kurbo::Size gets division support via kurbo#108. #[inline] fn div_size(size: Size, rhs: f64) -> Size { Size::new(size.width / rhs, size.height / rhs) From 6205c93431585996f39f3b019ae00964cfe759d9 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Wed, 6 May 2020 19:30:02 +0300 Subject: [PATCH 08/29] Move to a more pixel alignment friendly scaling strategy. --- docs/src/widget.md | 2 +- druid-shell/src/mouse.rs | 4 +- druid-shell/src/platform/windows/paint.rs | 6 +- druid-shell/src/platform/windows/window.rs | 7 +- druid-shell/src/scale.rs | 115 +++++++++++---------- druid-shell/src/window.rs | 27 ++++- druid/src/app.rs | 25 +++-- druid/src/widget/flex.rs | 12 +-- 8 files changed, 118 insertions(+), 80 deletions(-) diff --git a/docs/src/widget.md b/docs/src/widget.md index 547f14b73c..d2a9fe66c2 100644 --- a/docs/src/widget.md +++ b/docs/src/widget.md @@ -48,7 +48,7 @@ widgets]. Widgets are intended to be modular and composable, not monolithic. For instance, widgets generally do not control their own alignment or padding; if you have -a label, and you would like it to have 8px of horizontal padding and 4px of +a label, and you would like it to have 8pt of horizontal padding and 4pt of vertical padding, you can just do, ```rust,noplaypen diff --git a/druid-shell/src/mouse.rs b/druid-shell/src/mouse.rs index 44c1c00557..5bbe4dc92f 100644 --- a/druid-shell/src/mouse.rs +++ b/druid-shell/src/mouse.rs @@ -24,9 +24,7 @@ use crate::keyboard::KeyModifiers; /// receiving a move event before another mouse event. #[derive(Debug, Clone, PartialEq)] pub struct MouseEvent { - /// The location of the mouse in the current window. - /// - /// This is in px units not device pixels, that is, adjusted for hi-dpi. + /// The location of the mouse in points in relation to the current window. pub pos: Point, /// Mouse buttons being held down during a move or after a click event. /// Thus it will contain the `button` that triggered a mouse-down event, diff --git a/druid-shell/src/platform/windows/paint.rs b/druid-shell/src/platform/windows/paint.rs index 350f2c62ba..b8a3bad171 100644 --- a/druid-shell/src/platform/windows/paint.rs +++ b/druid-shell/src/platform/windows/paint.rs @@ -76,16 +76,14 @@ pub(crate) unsafe fn create_render_target_dxgi( &IDXGISurface::uuidof(), &mut buffer as *mut _ as *mut *mut c_void, ))?; - let dpi_x = (96.0 * scale.scale_x()) as f32; - let dpi_y = (96.0 * scale.scale_y()) as f32; let props = D2D1_RENDER_TARGET_PROPERTIES { _type: D2D1_RENDER_TARGET_TYPE_DEFAULT, pixelFormat: D2D1_PIXEL_FORMAT { format: DXGI_FORMAT_B8G8R8A8_UNORM, alphaMode: D2D1_ALPHA_MODE_IGNORE, }, - dpiX: dpi_x, - dpiY: dpi_y, + dpiX: scale.dpi_x() as f32, + dpiY: scale.dpi_y() as f32, usage: D2D1_RENDER_TARGET_USAGE_NONE, minLevel: D2D1_FEATURE_LEVEL_DEFAULT, }; diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 617ac95193..24c4fe48db 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -923,8 +923,9 @@ impl WndProc for MyWndProc { if let Ok(s) = self.state.try_borrow() { let s = s.as_ref().unwrap(); if let Some(min_size_pt) = s.min_size { - let min_size_px = self - .with_scale(|scale| Scale::new(scale.dpi()).set_size_pt(min_size_pt)); + let min_size_px = self.with_scale(|scale| { + Scale::new(scale.dpi_x(), scale.dpi_y()).set_size_pt(min_size_pt) + }); min_max_info.ptMinTrackSize.x = min_size_px.width as i32; min_max_info.ptMinTrackSize.y = min_size_px.height as i32; } @@ -1021,7 +1022,7 @@ impl WindowBuilder { // Probably GetDeviceCaps(..., LOGPIXELSX) is the best to do pre-10 96.0 }; - let mut scale = Scale::new(dpi); + let mut scale = Scale::new(dpi, dpi); let size_px = scale.set_size_pt(self.size); let window = WindowState { diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index 6040eb031d..3d1004f5ca 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -24,25 +24,38 @@ const SCALE_TARGET_DPI: f64 = 96.0; /// /// This holds the platform DPI, the platform area size in pixels, /// the logical area size in points, and the scale factors between them. -/// +/// /// To translate coordinates between pixels and points you should use one of the /// helper conversion methods of `Scale` or for manual conversion [`scale_x`] / [`scale_y`]. -/// -/// The platform DPI describes the ideal target however that is not always possible to achieve. -/// The physical area size in pixels has to be in integers by definition. -// TODO: Maybe re-architect the scale_x/scale_y stuff for better pixel alignment? -/// +/// +/// The platform area size in pixels tends to be limited to integers and `Scale` works +/// under that assumption. +/// +/// The logical area size in points is an unrounded conversion, which means that it is +/// often not limited to integers. This allows for accurate calculations of +/// the platform area pixel boundaries from the logcal area using the scale factors. +/// +/// Even though the logcal area size can be fractional, the integer boundaries of that logical area +/// will still match up with the platform area pixel boundaries as often as the scale factor allows. +/// +/// `Scale` is designed for responsive applications, including responding to platform DPI changes. +/// The platform DPI can change quickly, e.g. when moving a window from one monitor to another. +/// +/// A clone of `Scale` will be stale as soon as the platform area size or the DPI changes. +/// /// [`scale_x`]: #method.scale_x /// [`scale_y`]: #method.scale_y +// NOTE: Scale does not derive Copy because the data it contains can be short-lived. +// Forcing an explicit clone can help remind people of this, and avoids accidental copies. #[derive(Clone)] pub struct Scale { - /// The platform reported DPI. - dpi: f64, - /// The ideal scaling factor based on the DPI. - scale: f64, - /// The actual current scale factor on the x axis. + /// The platform reported DPI on the x axis. + dpi_x: f64, + /// The platform reported DPI on the y axis. + dpi_y: f64, + /// The scale factor on the x axis. scale_x: f64, - /// The actual current scale factor on the y axis. + /// The scale factor on the y axis. scale_y: f64, /// The size of the scaled area in points. size_pt: Size, @@ -51,57 +64,61 @@ pub struct Scale { } impl Scale { - /// Create a new `Scale` state based on the specified `dpi`. - pub fn new(dpi: f64) -> Scale { - let scale = dpi / SCALE_TARGET_DPI; + /// Create a new `Scale` state based on the specified DPI. + pub fn new(dpi_x: f64, dpi_y: f64) -> Scale { Scale { - dpi, - scale, - scale_x: scale, - scale_y: scale, + dpi_x, + dpi_y, + scale_x: dpi_x / SCALE_TARGET_DPI, + scale_y: dpi_y / SCALE_TARGET_DPI, size_pt: Size::ZERO, size_px: Size::ZERO, } } - /// Set the size of the scaled area in points. - /// - /// This updates the internal scaling state and returns the same size in pixels. + /// Set the size of the scaled area in pixels. /// - /// The calculated size in pixels is rounded to integers. - pub fn set_size_pt>(&mut self, size: T) -> Size { + /// This updates the internal state and returns the same size in points. + pub fn set_size_px>(&mut self, size: T) -> Size { let size = size.into(); - self.size_px = (size * self.scale).round(); - self.size_pt = size; - self.scale_x = self.size_px.width / self.size_pt.width; - self.scale_y = self.size_px.height / self.size_pt.height; - self.size_px + self.size_px = size; + self.size_pt = self.px_to_pt_size(size); + self.size_pt } - /// Set the size of the scaled area in pixels. + /// Set the size of the scaled area in pixels via conversion from points. /// - /// This updates the internal scaling state and returns the same size in points. + /// This updates the internal state and returns the same size in pixels. /// - /// The calculated size in points is rounded to integers. - pub fn set_size_px>(&mut self, size: T) -> Size { + /// The calculated size in pixels is rounded away from zero to integers. + /// That means that the scaled area size in points isn't always the same + /// as the `size` given to this method. To find out the new size in points use [`size_pt`]. + /// + /// [`size_pt`]: #method.size_pt + pub fn set_size_pt>(&mut self, size: T) -> Size { let size = size.into(); - self.size_pt = div_size(size, self.scale).round(); - self.size_px = size; - self.scale_x = self.size_px.width / self.size_pt.width; - self.scale_y = self.size_px.height / self.size_pt.height; - self.size_pt + self.size_px = self.pt_to_px_size(size).expand(); + self.size_pt = self.px_to_pt_size(self.size_px); + self.size_px + } + + /// Returns the x axis platform DPI associated with this `Scale`. + #[inline] + pub fn dpi_x(&self) -> f64 { + self.dpi_x } - /// Returns the platform DPI associated with this `Scale`. + /// Returns the y axis platform DPI associated with this `Scale`. #[inline] - pub fn dpi(&self) -> f64 { - self.dpi + pub fn dpi_y(&self) -> f64 { + self.dpi_y } - /// Returns `true` if the specified `dpi` is approximately equal to the `Scale` dpi. + /// Returns `true` if the specified DPI is approximately equal to the `Scale` DPI. #[inline] - pub fn dpi_approx_eq(&self, dpi: f64) -> bool { - self.dpi.approx_eq(dpi, (f64::EPSILON, 2)) + pub fn dpi_approx_eq(&self, dpi_x: f64, dpi_y: f64) -> bool { + self.dpi_x.approx_eq(dpi_x, (f64::EPSILON, 2)) + && self.dpi_y.approx_eq(dpi_y, (f64::EPSILON, 2)) } /// Returns the x axis scale factor. @@ -233,18 +250,12 @@ impl std::fmt::Debug for Scale { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, - "DPI {} => {} => ({}, {}) | {:?} => {:?}", - self.dpi, self.scale, self.scale_x, self.scale_y, self.size_pt, self.size_px + "DPI ({}, {}) => ({}, {}) | {:?} => {:?}", + self.dpi_x, self.dpi_y, self.scale_x, self.scale_y, self.size_px, self.size_pt, ) } } -// TODO: Remove this after kurbo::Size gets division support via kurbo#108. -#[inline] -fn div_size(size: Size, rhs: f64) -> Size { - Size::new(size.width / rhs, size.height / rhs) -} - // TODO: Replace usages of this with rect.expand() after kurbo#107 has landed. pub fn expand_rect(rect: Rect) -> Rect { let (x0, x1) = if rect.x0 < rect.x1 { diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index dc57d6aedb..1e1a58904a 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -202,7 +202,9 @@ impl WindowHandle { /// Get the [`Scale`] information of the window. /// - /// This information will be stale after the window is resized or the platform DPI changes. + /// The returned [`Scale`] is a clone and thus its information will be stale after the window + /// is resized or the platform DPI changes. A correctly behaving application should consider + /// the lifetime of this [`Scale`] brief, limited to approximately a single event cycle. /// /// [`Scale`]: struct.Scale.html pub fn get_scale(&self) -> Result { @@ -231,12 +233,25 @@ impl WindowBuilder { self.0.set_handler(handler) } - /// Set the window's initial size. + /// Set the window's initial drawing area size in points. + /// + /// The actual window size in pixels will depend on the platform DPI settings. + /// + /// This should be considered a request to the platform to set the size of the window. + /// The platform might increase the size a tiny bit due to DPI. + /// To know the actual size of the window you should handle the [`WinHandler::size`] method. + /// + /// [`WinHandler::size`]: trait.WinHandler.html#method.size pub fn set_size(&mut self, size: Size) { self.0.set_size(size) } - /// Set the window's initial size. + /// Set the window's minimum drawing area size in points. + /// + /// The actual minimum window size in pixels will depend on the platform DPI settings. + /// + /// This should be considered a request to the platform to set the minimum size of the window. + /// The platform might increase the size a tiny bit due to DPI. pub fn set_min_size(&mut self, size: Size) { self.0.set_min_size(size) } @@ -283,14 +298,16 @@ pub trait WinHandler { /// wish to stash it. fn connect(&mut self, handle: &WindowHandle); - /// Called when the size of the window is changed. + /// Called when the size of the window has changed. + /// + /// The `size` parameter is the new size in points. #[allow(unused_variables)] fn size(&mut self, size: Size) {} /// Request the handler to paint the window contents. Return value /// indicates whether window is animating, i.e. whether another paint /// should be scheduled for the next animation frame. `invalid_rect` is the - /// rectangle that needs to be repainted. + /// rectangle in points that needs to be repainted. fn paint(&mut self, piet: &mut piet_common::Piet, invalid_rect: Rect) -> bool; /// Called when the resources need to be rebuilt. diff --git a/druid/src/app.rs b/druid/src/app.rs index 7867c7c819..d4c88dd14f 100644 --- a/druid/src/app.rs +++ b/druid/src/app.rs @@ -187,23 +187,36 @@ impl WindowDesc { self } - /// Set the initial window size. + /// Set the window's initial drawing area size in points. + /// + /// You can pass in a tuple `(width, height)` or a [`Size`], + /// e.g. to create a window with a drawing area 1000pt wide and 500pt high: /// - /// You can pass in a tuple `(width, height)` or `kurbo::Size` e.g. - /// to create a window 1000px wide and 500px high /// ```ignore /// window.window_size((1000.0, 500.0)); /// ``` + /// + /// The actual window size in pixels will depend on the platform DPI settings. + /// + /// This should be considered a request to the platform to set the size of the window. + /// The platform might increase the size a tiny bit due to DPI. + /// + /// [`Size`]: struct.Size.html pub fn window_size(mut self, size: impl Into) -> Self { self.size = Some(size.into()); self } - /// Set the minimum window size. + /// Set the window's minimum drawing area size in points. + /// + /// The actual minimum window size in pixels will depend on the platform DPI settings. + /// + /// This should be considered a request to the platform to set the minimum size of the window. + /// The platform might increase the size a tiny bit due to DPI. /// - /// To set the initial window size, see [`window_size`]. + /// To set the window's initial drawing area size use [`window_size`]. /// - /// [`window_size`]: struct.WindowDesc.html#method.window_size + /// [`window_size`]: #method.window_size pub fn with_min_size(mut self, size: impl Into) -> Self { self.min_size = Some(size.into()); self diff --git a/druid/src/widget/flex.rs b/druid/src/widget/flex.rs index 44382fc24e..7d069470ce 100644 --- a/druid/src/widget/flex.rs +++ b/druid/src/widget/flex.rs @@ -47,15 +47,15 @@ use crate::{ /// /// When should your children be flexible? With other things being equal, /// a flexible child has lower layout priority than a non-flexible child. -/// Imagine, for instance, we have a row that is 30px wide, and we have -/// two children, both of which want to be 20px wide. If child #1 is non-flex -/// and child #2 is flex, the first widget will take up its 20px, and the second -/// widget will be constrained to 10px. +/// Imagine, for instance, we have a row that is 30pt wide, and we have +/// two children, both of which want to be 20pt wide. If child #1 is non-flex +/// and child #2 is flex, the first widget will take up its 20pt, and the second +/// widget will be constrained to 10pt. /// /// If, instead, both widgets are flex, they will each be given equal space, -/// and both will end up taking up 15px. +/// and both will end up taking up 15pt. /// -/// If both are non-flex they will both take up 20px, and will overflow the +/// If both are non-flex they will both take up 20pt, and will overflow the /// container. /// /// ```no_compile From b96c74770075a94ffe6fd29685808e3151be31df Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Wed, 6 May 2020 20:18:19 +0300 Subject: [PATCH 09/29] Add new Scale support for X11/GTK. --- druid-shell/src/platform/gtk/window.rs | 8 ++++---- druid-shell/src/platform/x11/window.rs | 4 ++-- druid-shell/src/scale.rs | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 3738957444..0add202897 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -172,7 +172,7 @@ impl WindowBuilder { .get_display() .map(|c| c.get_default_screen().get_resolution() as f64) .unwrap_or(96.0); - let mut scale = Scale::new(dpi); + let mut scale = Scale::new(dpi, dpi); let size_px = scale.set_size_pt(self.size); window.set_default_size(size_px.width as i32, size_px.height as i32); @@ -237,7 +237,7 @@ impl WindowBuilder { // Set the minimum size if let Some(size_pt) = self.min_size { - let mut scale = Scale::new(dpi); + let mut scale = Scale::new(dpi, dpi); let size_px = scale.set_size_pt(size_pt); win_state .drawing_area @@ -251,10 +251,10 @@ impl WindowBuilder { // so that we can change our scale factor without restarting the application. if let Some(dpi) = state.window.get_window() .map(|w| w.get_display().get_default_screen().get_resolution()) { - if !scale.dpi_approx_eq(dpi) { + if !scale.dpi_approx_eq(dpi, dpi) { // This new Scale will also ensure a window size event for the handler, // because it defaults to zero size. - *scale = Scale::new(dpi); + *scale = Scale::new(dpi, dpi); } } diff --git a/druid-shell/src/platform/x11/window.rs b/druid-shell/src/platform/x11/window.rs index 672c726afe..6f04a1ecec 100644 --- a/druid-shell/src/platform/x11/window.rs +++ b/druid-shell/src/platform/x11/window.rs @@ -511,7 +511,7 @@ impl Window { fn get_scale(&self) -> Result { // TODO(x11/dpi_scaling): figure out DPI scaling - Ok(Scale::new(96.0)) + Ok(Scale::new(96.0, 96.0)) } pub fn handle_expose(&self, expose: &xcb::ExposeEvent) -> Result<(), Error> { @@ -867,7 +867,7 @@ impl WindowHandle { w.get_scale().map_err(ShellError::Platform) } else { log::error!("Window {} has already been dropped", self.id); - Ok(Scale::new(96.0)) + Ok(Scale::new(96.0, 96.0)) } } } diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index 3d1004f5ca..95dc977482 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -257,6 +257,7 @@ impl std::fmt::Debug for Scale { } // TODO: Replace usages of this with rect.expand() after kurbo#107 has landed. +#[allow(dead_code)] pub fn expand_rect(rect: Rect) -> Rect { let (x0, x1) = if rect.x0 < rect.x1 { (rect.x0.floor(), rect.x1.ceil()) From 05dd453fa516bf97435a1a9aaa2553687b788064 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Wed, 6 May 2020 20:40:21 +0300 Subject: [PATCH 10/29] Fix macOS compiling with new Scale. --- druid-shell/src/platform/mac/window.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/druid-shell/src/platform/mac/window.rs b/druid-shell/src/platform/mac/window.rs index 7bc774a55e..defb5abb4b 100644 --- a/druid-shell/src/platform/mac/window.rs +++ b/druid-shell/src/platform/mac/window.rs @@ -52,6 +52,7 @@ use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; use crate::keyboard::{KeyEvent, KeyModifiers}; use crate::keycodes::KeyCode; use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; +use crate::scale::Scale; use crate::window::{IdleToken, Text, TimerToken, WinHandler}; use crate::Error; @@ -204,7 +205,7 @@ impl WindowBuilder { (*view_state).handler.connect(&handle.clone().into()); (*view_state) .handler - .size(frame.size.width as u32, frame.size.height as u32); + .size(Size::new(frame.size.width, frame.size.height)); Ok(handle) } @@ -370,7 +371,7 @@ extern "C" fn set_frame_size(this: &mut Object, _: Sel, size: NSSize) { let view_state = &mut *(view_state as *mut ViewState); (*view_state) .handler - .size(size.width as u32, size.height as u32); + .size(Size::new(size.width, size.height)); let superclass = msg_send![this, superclass]; let () = msg_send![super(this, superclass), setFrameSize: size]; } @@ -825,13 +826,10 @@ impl WindowHandle { } } - /// Get the dpi of the window. - /// - /// TODO: we want to migrate this from dpi (with 96 as nominal) to a scale - /// factor (with 1 as nominal). - pub fn get_dpi(&self) -> f32 { - // TODO: get actual dpi - 96.0 + /// Get the `Scale` of the window. + pub fn get_scale(&self) -> Result { + // TODO: Get actual Scale + Ok(Scale::new(96.0, 96.0)) } } From e1a7625e55ead69ea592580a2a75a5db6dbedfcb Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Wed, 6 May 2020 21:56:22 +0300 Subject: [PATCH 11/29] Add support for Scale in web. --- druid-shell/src/platform/gtk/window.rs | 6 +- druid-shell/src/platform/mac/window.rs | 2 +- druid-shell/src/platform/web/error.rs | 6 ++ druid-shell/src/platform/web/window.rs | 113 +++++++++------------ druid-shell/src/platform/windows/error.rs | 14 ++- druid-shell/src/platform/windows/window.rs | 11 +- druid-shell/src/platform/x11/window.rs | 4 +- druid-shell/src/scale.rs | 20 +++- druid-shell/src/window.rs | 2 +- 9 files changed, 92 insertions(+), 86 deletions(-) diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 0add202897..a65fb3196f 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -172,7 +172,7 @@ impl WindowBuilder { .get_display() .map(|c| c.get_default_screen().get_resolution() as f64) .unwrap_or(96.0); - let mut scale = Scale::new(dpi, dpi); + let mut scale = Scale::from_dpi(dpi, dpi); let size_px = scale.set_size_pt(self.size); window.set_default_size(size_px.width as i32, size_px.height as i32); @@ -237,7 +237,7 @@ impl WindowBuilder { // Set the minimum size if let Some(size_pt) = self.min_size { - let mut scale = Scale::new(dpi, dpi); + let mut scale = Scale::from_dpi(dpi, dpi); let size_px = scale.set_size_pt(size_pt); win_state .drawing_area @@ -254,7 +254,7 @@ impl WindowBuilder { if !scale.dpi_approx_eq(dpi, dpi) { // This new Scale will also ensure a window size event for the handler, // because it defaults to zero size. - *scale = Scale::new(dpi, dpi); + *scale = Scale::from_dpi(dpi, dpi); } } diff --git a/druid-shell/src/platform/mac/window.rs b/druid-shell/src/platform/mac/window.rs index defb5abb4b..a036ff2a28 100644 --- a/druid-shell/src/platform/mac/window.rs +++ b/druid-shell/src/platform/mac/window.rs @@ -829,7 +829,7 @@ impl WindowHandle { /// Get the `Scale` of the window. pub fn get_scale(&self) -> Result { // TODO: Get actual Scale - Ok(Scale::new(96.0, 96.0)) + Ok(Scale::from_dpi(96.0, 96.0)) } } diff --git a/druid-shell/src/platform/web/error.rs b/druid-shell/src/platform/web/error.rs index 5adef2a490..62d021c978 100644 --- a/druid-shell/src/platform/web/error.rs +++ b/druid-shell/src/platform/web/error.rs @@ -18,6 +18,10 @@ use wasm_bindgen::JsValue; #[derive(Debug, Clone)] pub enum Error { + /// Generic error + Generic(String), + /// Runtime borrow failure + BorrowError(String), NoWindow, NoDocument, Js(JsValue), @@ -30,6 +34,8 @@ pub enum Error { impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { + Error::Generic(msg) => write!(f, "Error: {}", msg), + Error::BorrowError(msg) => write!(f, "Failed to borrow: {}", msg), Error::NoWindow => write!(f, "No global window found"), Error::NoDocument => write!(f, "No global document found"), Error::Js(err) => write!(f, "JavaScript error: {:?}", err.as_string()), diff --git a/druid-shell/src/platform/web/window.rs b/druid-shell/src/platform/web/window.rs index c6613e794b..cf4bad4ef0 100644 --- a/druid-shell/src/platform/web/window.rs +++ b/druid-shell/src/platform/web/window.rs @@ -35,6 +35,7 @@ use super::keycodes::key_to_text; use super::menu::Menu; use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; +use crate::scale::Scale; use crate::keyboard; use crate::keycodes::KeyCode; @@ -57,8 +58,6 @@ macro_rules! get_modifiers { type Result = std::result::Result; -const NOMINAL_DPI: f32 = 96.0; - /// Builder abstraction for creating new windows. pub(crate) struct WindowBuilder { handler: Option>, @@ -84,7 +83,7 @@ enum IdleKind { } struct WindowState { - dpr: Cell, + scale: RefCell, idle_queue: Arc>>, handler: RefCell>, window: web_sys::Window, @@ -120,14 +119,6 @@ impl WindowState { } } - fn get_width(&self) -> u32 { - self.canvas.offset_width() as u32 - } - - fn get_height(&self) -> u32 { - self.canvas.offset_height() as u32 - } - fn request_animation_frame(&self, f: impl FnOnce() + 'static) -> Result { Ok(self .window @@ -237,16 +228,19 @@ fn setup_resize_callback(ws: &Rc) { let state = ws.clone(); register_window_event_listener(ws, "resize", move |_: web_sys::UiEvent| { let (css_width, css_height, dpr) = state.get_window_size_and_dpr(); - let physical_width = (dpr * css_width) as u32; - let physical_height = (dpr * css_height) as u32; - state.dpr.replace(dpr); - state.canvas.set_width(physical_width); - state.canvas.set_height(physical_height); - let _ = state.context.scale(dpr, dpr); - state - .handler - .borrow_mut() - .size(physical_width, physical_height); + let size_pt = state.scale.try_borrow_mut().map_or(None, |mut scale| { + *scale = Scale::from_scale(dpr, dpr); + let size_px = scale.set_size_pt(Size::new(css_width, css_height)); + state.canvas.set_width(size_px.width as u32); + state.canvas.set_height(size_px.height as u32); + let _ = state.context.scale(scale.scale_x(), scale.scale_y()); + Some(scale.size_pt()) + }); + if let Some(size_pt) = size_pt { + state.handler.borrow_mut().size(size_pt); + } else { + log::error!("Skipped resize event because couldn't borrow scale"); + } }); } @@ -371,23 +365,27 @@ impl WindowBuilder { .ok_or(Error::NoContext)? .dyn_into::() .map_err(|_| Error::JsCast)?; - - let dpr = window.device_pixel_ratio(); - let old_w = canvas.offset_width(); - let old_h = canvas.offset_height(); - let new_w = (old_w as f64 * dpr) as u32; - let new_h = (old_h as f64 * dpr) as u32; - - canvas.set_width(new_w as u32); - canvas.set_height(new_h as u32); - let _ = context.scale(dpr, dpr); + // Create the Scale for resolution scaling + let mut scale = { + let dpr = window.device_pixel_ratio(); + Scale::from_scale(dpr, dpr) + }; + let size_px = { + // The initial size in points isn't necessarily the final size in points + let size_pt = Size::new(canvas.offset_width() as f64, canvas.offset_height() as f64); + scale.set_size_pt(size_pt) + }; + canvas.set_width(size_px.width as u32); + canvas.set_height(size_px.height as u32); + let _ = context.scale(scale.scale_x(), scale.scale_y()); + let size_pt = scale.size_pt(); set_cursor(&canvas, &self.cursor); let handler = self.handler.unwrap(); let window = Rc::new(WindowState { - dpr: Cell::new(dpr), + scale: RefCell::new(scale), idle_queue: Default::default(), handler: RefCell::new(handler), window, @@ -402,7 +400,7 @@ impl WindowBuilder { let wh = window.clone(); window .request_animation_frame(move || { - wh.handler.borrow_mut().size(new_w, new_h); + wh.handler.borrow_mut().size(size_pt); }) .expect("Failed to request animation frame"); @@ -449,12 +447,11 @@ impl WindowHandle { pub fn invalidate(&self) { if let Some(s) = self.0.upgrade() { - let rect = Rect::from_origin_size( - Point::ORIGIN, - // FIXME: does this need scaling? Not sure exactly where dpr enters... - (s.get_width() as f64, s.get_height() as f64), - ); - s.invalid_rect.set(rect); + if let Ok(scale) = s.scale.try_borrow() { + s.invalid_rect.set(scale.size_pt().to_rect()); + } else { + log::error!("Failed to get scale"); + } } self.render_soon(); } @@ -551,34 +548,16 @@ impl WindowHandle { }) } - /// Get the dpi of the window. - pub fn get_dpi(&self) -> f32 { - self.0 - .upgrade() - .map(|w| NOMINAL_DPI * w.dpr.get() as f32) - .unwrap_or(NOMINAL_DPI) - } - - /// Convert a dimension in px units to physical pixels (rounding). - pub fn px_to_pixels(&self, x: f32) -> i32 { - (x * self.get_dpi() / NOMINAL_DPI).round() as i32 - } - - /// Convert a point in px units to physical pixels (rounding). - pub fn px_to_pixels_xy(&self, x: f32, y: f32) -> (i32, i32) { - let scale = self.get_dpi() / NOMINAL_DPI; - ((x * scale).round() as i32, (y * scale).round() as i32) - } - - /// Convert a dimension in physical pixels to px units. - pub fn pixels_to_px>(&self, x: T) -> f32 { - (x.into() as f32) * NOMINAL_DPI / self.get_dpi() - } - - /// Convert a point in physical pixels to px units. - pub fn pixels_to_px_xy>(&self, x: T, y: T) -> (f32, f32) { - let scale = NOMINAL_DPI / self.get_dpi(); - ((x.into() as f32) * scale, (y.into() as f32) * scale) + /// Get the `Scale` of the window. + pub fn get_scale(&self) -> Result { + if let Some(state) = self.0.upgrade() { + match state.scale.try_borrow() { + Ok(scale) => Ok(scale.clone()), + Err(err) => Err(Error::BorrowError(format!("WindowHandle scale: {}", err))), + } + } else { + Err(Error::Generic("WindowState already dropped".into())) + } } pub fn set_menu(&self, _menu: Menu) { diff --git a/druid-shell/src/platform/windows/error.rs b/druid-shell/src/platform/windows/error.rs index 7668a00752..b58e0dc685 100644 --- a/druid-shell/src/platform/windows/error.rs +++ b/druid-shell/src/platform/windows/error.rs @@ -27,10 +27,14 @@ use winapi::um::winbase::{ use super::util::FromWide; -/// Error codes. At the moment, this is little more than HRESULT, but that -/// might change. +/// Windows platform errors. #[derive(Debug, Clone)] pub enum Error { + /// Generic error + Generic(String), + /// Runtime borrow failure + BorrowError(String), + /// Windows error code. Hr(HRESULT), // Maybe include the full error from the direct2d crate. D2Error, @@ -67,10 +71,12 @@ fn hresult_description(hr: HRESULT) -> Option { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - match *self { + match self { + Error::Generic(msg) => write!(f, "Error: {}", msg), + Error::BorrowError(msg) => write!(f, "Failed to borrow: {}", msg), Error::Hr(hr) => { write!(f, "HRESULT 0x{:x}", hr)?; - if let Some(description) = hresult_description(hr) { + if let Some(description) = hresult_description(*hr) { write!(f, ": {}", description)?; } Ok(()) diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 24c4fe48db..46704cd269 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -58,7 +58,6 @@ use super::util::{self, as_result, FromWide, ToWide, OPTIONAL_FUNCTIONS}; use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; -use crate::error::Error as ShellError; use crate::keyboard::{KeyEvent, KeyModifiers}; use crate::keycodes::KeyCode; use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; @@ -924,7 +923,7 @@ impl WndProc for MyWndProc { let s = s.as_ref().unwrap(); if let Some(min_size_pt) = s.min_size { let min_size_px = self.with_scale(|scale| { - Scale::new(scale.dpi_x(), scale.dpi_y()).set_size_pt(min_size_pt) + Scale::from_dpi(scale.dpi_x(), scale.dpi_y()).set_size_pt(min_size_pt) }); min_max_info.ptMinTrackSize.x = min_size_px.width as i32; min_max_info.ptMinTrackSize.y = min_size_px.height as i32; @@ -1022,7 +1021,7 @@ impl WindowBuilder { // Probably GetDeviceCaps(..., LOGPIXELSX) is the best to do pre-10 96.0 }; - let mut scale = Scale::new(dpi, dpi); + let mut scale = Scale::from_dpi(dpi, dpi); let size_px = scale.set_size_pt(self.size); let window = WindowState { @@ -1506,14 +1505,14 @@ impl WindowHandle { } /// Get the `Scale` of the window. - pub fn get_scale(&self) -> Result { + pub fn get_scale(&self) -> Result { if let Some(state) = self.state.upgrade() { match state.scale.try_borrow() { Ok(scale) => Ok(scale.clone()), - Err(_) => Err(ShellError::Other("Failed to borrow scale")), + Err(err) => Err(Error::BorrowError(format!("WindowHandle scale: {}", err))), } } else { - Err(ShellError::Other("WindowState already dropped")) + Err(Error::Generic("WindowState already dropped".into())) } } diff --git a/druid-shell/src/platform/x11/window.rs b/druid-shell/src/platform/x11/window.rs index 6f04a1ecec..cecac22a3a 100644 --- a/druid-shell/src/platform/x11/window.rs +++ b/druid-shell/src/platform/x11/window.rs @@ -511,7 +511,7 @@ impl Window { fn get_scale(&self) -> Result { // TODO(x11/dpi_scaling): figure out DPI scaling - Ok(Scale::new(96.0, 96.0)) + Ok(Scale::from_dpi(96.0, 96.0)) } pub fn handle_expose(&self, expose: &xcb::ExposeEvent) -> Result<(), Error> { @@ -867,7 +867,7 @@ impl WindowHandle { w.get_scale().map_err(ShellError::Platform) } else { log::error!("Window {} has already been dropped", self.id); - Ok(Scale::new(96.0, 96.0)) + Ok(Scale::from_dpi(96.0, 96.0)) } } } diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index 95dc977482..6773721fff 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -64,8 +64,10 @@ pub struct Scale { } impl Scale { - /// Create a new `Scale` state based on the specified DPI. - pub fn new(dpi_x: f64, dpi_y: f64) -> Scale { + /// Create a new `Scale` state based on the specified DPIs. + /// + /// Use this constructor if the platform provided DPI is the most accurate number. + pub fn from_dpi(dpi_x: f64, dpi_y: f64) -> Scale { Scale { dpi_x, dpi_y, @@ -76,6 +78,20 @@ impl Scale { } } + /// Create a new `Scale` state based on the specified scale factors. + /// + /// Use this constructor if the platform provided scale factor is the most accurate number. + pub fn from_scale(scale_x: f64, scale_y: f64) -> Scale { + Scale { + dpi_x: SCALE_TARGET_DPI * scale_x, + dpi_y: SCALE_TARGET_DPI * scale_y, + scale_x, + scale_y, + size_pt: Size::ZERO, + size_px: Size::ZERO, + } + } + /// Set the size of the scaled area in pixels. /// /// This updates the internal state and returns the same size in points. diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index 1e1a58904a..6656b7e72b 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -208,7 +208,7 @@ impl WindowHandle { /// /// [`Scale`]: struct.Scale.html pub fn get_scale(&self) -> Result { - self.0.get_scale() + self.0.get_scale().map_err(Into::into) } } From bf4069d2205015c2208849d1fb80d0bb7328c756 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Thu, 7 May 2020 01:44:21 +0300 Subject: [PATCH 12/29] Refactor shell errors. --- druid-shell/src/error.rs | 62 ++++++++++++++++++++-- druid-shell/src/platform/gtk/window.rs | 14 ++--- druid-shell/src/platform/web/error.rs | 6 --- druid-shell/src/platform/web/window.rs | 17 +++--- druid-shell/src/platform/windows/error.rs | 8 +-- druid-shell/src/platform/windows/window.rs | 7 +-- 6 files changed, 78 insertions(+), 36 deletions(-) diff --git a/druid-shell/src/error.rs b/druid-shell/src/error.rs index 04cdb01eed..2c0c03b020 100644 --- a/druid-shell/src/error.rs +++ b/druid-shell/src/error.rs @@ -18,13 +18,19 @@ use std::fmt; use crate::platform::error as platform; -/// Error codes. At the moment, this is little more than HRESULT, but that -/// might change. +/// Shell errors. #[derive(Debug, Clone)] pub enum Error { + /// The Application instance has already been created. ApplicationAlreadyExists, - Other(&'static str), + /// The window has already been destroyed. + WindowDropped, + /// Runtime borrow failure. + BorrowError(BorrowError), + /// Platform specific error. Platform(platform::Error), + /// Other miscellaneous error. + Other(&'static str), } impl fmt::Display for Error { @@ -33,8 +39,10 @@ impl fmt::Display for Error { Error::ApplicationAlreadyExists => { write!(f, "An application instance has already been created.") } + Error::WindowDropped => write!(f, "The window has already been destroyed."), + Error::BorrowError(err) => fmt::Display::fmt(err, f), + Error::Platform(err) => fmt::Display::fmt(err, f), Error::Other(s) => write!(f, "{}", s), - Error::Platform(p) => fmt::Display::fmt(&p, f), } } } @@ -46,3 +54,49 @@ impl From for Error { Error::Platform(src) } } + +/// Runtime borrow failure. +#[derive(Debug, Clone)] +pub struct BorrowError { + location: &'static str, + target: &'static str, + mutable: bool, +} + +impl BorrowError { + pub fn new(location: &'static str, target: &'static str, mutable: bool) -> BorrowError { + BorrowError { + location, + target, + mutable, + } + } +} + +impl fmt::Display for BorrowError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + if self.mutable { + // Mutable borrow fails when any borrow exists + write!( + f, + "{} was already borrowed in {}", + self.target, self.location + ) + } else { + // Regular borrow fails when a mutable borrow exists + write!( + f, + "{} was already mutably borrowed in {}", + self.target, self.location + ) + } + } +} + +impl std::error::Error for BorrowError {} + +impl From for Error { + fn from(src: BorrowError) -> Error { + Error::BorrowError(src) + } +} diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index a65fb3196f..0c8e1ffd26 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -35,11 +35,11 @@ use crate::piet::{Piet, RenderContext}; use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; +use crate::error::{BorrowError, Error as ShellError}; use crate::keyboard; use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; use crate::scale::{self, Scale}; use crate::window::{IdleToken, Text, TimerToken, WinHandler}; -use crate::Error; use super::application::Application; use super::dialog; @@ -156,7 +156,7 @@ impl WindowBuilder { self.menu = Some(menu); } - pub fn build(self) -> Result { + pub fn build(self) -> Result { let handler = self .handler .expect("Tried to build a window without setting the handler"); @@ -639,14 +639,14 @@ impl WindowHandle { } /// Get the `Scale` of the window. - pub fn get_scale(&self) -> Result { + pub fn get_scale(&self) -> Result { if let Some(state) = self.state.upgrade() { match state.scale.try_borrow() { Ok(scale) => Ok(scale.clone()), - Err(_) => Err(Error::Other("Failed to borrow scale")), + Err(_) => Err(BorrowError::new("WindowHandle::get_scale", "scale", false).into()), } } else { - Err(Error::Other("WindowState already dropped")) + Err(ShellError::WindowDropped) } } @@ -696,11 +696,11 @@ impl WindowHandle { &self, ty: FileDialogType, options: FileDialogOptions, - ) -> Result { + ) -> Result { if let Some(state) = self.state.upgrade() { dialog::get_file_dialog_path(state.window.upcast_ref(), ty, options) } else { - Err(Error::Other( + Err(ShellError::Other( "Cannot upgrade state from weak pointer to arc", )) } diff --git a/druid-shell/src/platform/web/error.rs b/druid-shell/src/platform/web/error.rs index 62d021c978..5adef2a490 100644 --- a/druid-shell/src/platform/web/error.rs +++ b/druid-shell/src/platform/web/error.rs @@ -18,10 +18,6 @@ use wasm_bindgen::JsValue; #[derive(Debug, Clone)] pub enum Error { - /// Generic error - Generic(String), - /// Runtime borrow failure - BorrowError(String), NoWindow, NoDocument, Js(JsValue), @@ -34,8 +30,6 @@ pub enum Error { impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - Error::Generic(msg) => write!(f, "Error: {}", msg), - Error::BorrowError(msg) => write!(f, "Failed to borrow: {}", msg), Error::NoWindow => write!(f, "No global window found"), Error::NoDocument => write!(f, "No global document found"), Error::Js(err) => write!(f, "JavaScript error: {:?}", err.as_string()), diff --git a/druid-shell/src/platform/web/window.rs b/druid-shell/src/platform/web/window.rs index cf4bad4ef0..c39b481414 100644 --- a/druid-shell/src/platform/web/window.rs +++ b/druid-shell/src/platform/web/window.rs @@ -35,6 +35,7 @@ use super::keycodes::key_to_text; use super::menu::Menu; use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; +use crate::error::{BorrowError, Error as ShellError}; use crate::scale::Scale; use crate::keyboard; @@ -56,8 +57,6 @@ macro_rules! get_modifiers { }; } -type Result = std::result::Result; - /// Builder abstraction for creating new windows. pub(crate) struct WindowBuilder { handler: Option>, @@ -119,7 +118,7 @@ impl WindowState { } } - fn request_animation_frame(&self, f: impl FnOnce() + 'static) -> Result { + fn request_animation_frame(&self, f: impl FnOnce() + 'static) -> Result { Ok(self .window .request_animation_frame(Closure::once_into_js(f).as_ref().unchecked_ref())?) @@ -351,7 +350,7 @@ impl WindowBuilder { self.menu = Some(menu); } - pub fn build(self) -> Result { + pub fn build(self) -> Result { let window = web_sys::window().ok_or_else(|| Error::NoWindow)?; let canvas = window .document() @@ -536,8 +535,8 @@ impl WindowHandle { &self, _ty: FileDialogType, _options: FileDialogOptions, - ) -> std::result::Result { - Err(crate::Error::Platform(Error::Unimplemented)) + ) -> Result { + Err(ShellError::Platform(Error::Unimplemented)) } /// Get a handle that can be used to schedule an idle task. @@ -549,14 +548,14 @@ impl WindowHandle { } /// Get the `Scale` of the window. - pub fn get_scale(&self) -> Result { + pub fn get_scale(&self) -> Result { if let Some(state) = self.0.upgrade() { match state.scale.try_borrow() { Ok(scale) => Ok(scale.clone()), - Err(err) => Err(Error::BorrowError(format!("WindowHandle scale: {}", err))), + Err(_) => Err(BorrowError::new("WindowHandle::get_scale", "scale", false).into()), } } else { - Err(Error::Generic("WindowState already dropped".into())) + Err(ShellError::WindowDropped) } } diff --git a/druid-shell/src/platform/windows/error.rs b/druid-shell/src/platform/windows/error.rs index b58e0dc685..0b400bb5af 100644 --- a/druid-shell/src/platform/windows/error.rs +++ b/druid-shell/src/platform/windows/error.rs @@ -27,13 +27,9 @@ use winapi::um::winbase::{ use super::util::FromWide; -/// Windows platform errors. +/// Windows platform error. #[derive(Debug, Clone)] pub enum Error { - /// Generic error - Generic(String), - /// Runtime borrow failure - BorrowError(String), /// Windows error code. Hr(HRESULT), // Maybe include the full error from the direct2d crate. @@ -72,8 +68,6 @@ fn hresult_description(hr: HRESULT) -> Option { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self { - Error::Generic(msg) => write!(f, "Error: {}", msg), - Error::BorrowError(msg) => write!(f, "Failed to borrow: {}", msg), Error::Hr(hr) => { write!(f, "HRESULT 0x{:x}", hr)?; if let Some(description) = hresult_description(*hr) { diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 46704cd269..7cfa88b02c 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -58,6 +58,7 @@ use super::util::{self, as_result, FromWide, ToWide, OPTIONAL_FUNCTIONS}; use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; +use crate::error::{BorrowError, Error as ShellError}; use crate::keyboard::{KeyEvent, KeyModifiers}; use crate::keycodes::KeyCode; use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; @@ -1505,14 +1506,14 @@ impl WindowHandle { } /// Get the `Scale` of the window. - pub fn get_scale(&self) -> Result { + pub fn get_scale(&self) -> Result { if let Some(state) = self.state.upgrade() { match state.scale.try_borrow() { Ok(scale) => Ok(scale.clone()), - Err(err) => Err(Error::BorrowError(format!("WindowHandle scale: {}", err))), + Err(_) => Err(BorrowError::new("WindowHandle::get_scale", "scale", false).into()), } } else { - Err(Error::Generic("WindowState already dropped".into())) + Err(ShellError::WindowDropped) } } From 1b6d7b80acdaac5f80c4eab32492bd176795d7dc Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Thu, 7 May 2020 01:48:02 +0300 Subject: [PATCH 13/29] Update changelog. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 324d71af06..6a65140a1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -128,6 +128,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i - GTK: Refactored `Application` to use the new structure. ([#892] by [@xStrom]) - X11: Refactored `Application` to use the new structure. ([#894] by [@xStrom]) - X11: Refactored `Window` to support some reentrancy and invalidation. ([#894] by [@xStrom]) +- Refactored DPI scaling. ([#904] by [@xStrom]) - Added docs generation testing for all features. ([#942] by [@xStrom]) ### Outside News @@ -186,6 +187,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i [#898]: https://github.com/xi-editor/druid/pull/898 [#900]: https://github.com/xi-editor/druid/pull/900 [#903]: https://github.com/xi-editor/druid/pull/903 +[#904]: https://github.com/xi-editor/druid/pull/904 [#905]: https://github.com/xi-editor/druid/pull/905 [#909]: https://github.com/xi-editor/druid/pull/909 [#917]: https://github.com/xi-editor/druid/pull/917 From 6a7655607c20e5a50feea316587fdb3a398665e0 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 9 May 2020 17:05:21 +0300 Subject: [PATCH 14/29] Add scaling to new wheel events. --- druid-shell/src/platform/gtk/window.rs | 2 +- druid-shell/src/platform/windows/window.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 0c8e1ffd26..9ccd5c803c 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -447,7 +447,7 @@ impl WindowBuilder { if let Some(wheel_delta) = wheel_delta { let mouse_event = MouseEvent { - pos: Point::from(scroll.get_position()), + pos: scale.px_to_pt_point(Point::from(scroll.get_position())), buttons: get_mouse_buttons_from_modifiers(scroll.get_state()), mods, count: 0, diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 7cfa88b02c..3da1c155e1 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -721,8 +721,8 @@ impl WndProc for MyWndProc { } } - let (px, py) = self.handle.borrow().pixels_to_px_xy(p.x, p.y); - let pos = Point::new(px as f64, py as f64); + let pos = + self.with_scale(|scale| scale.px_to_pt_point((p.x as f64, p.y as f64))); let buttons = get_buttons(down_state); let event = MouseEvent { pos, From 79012f9970db0e617f4b3da2861bdf1ea5feab34 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 9 May 2020 18:41:02 +0300 Subject: [PATCH 15/29] Change points/pt to display points/dp. --- CHANGELOG.md | 2 +- docs/src/widget.md | 2 +- druid-shell/src/mouse.rs | 2 +- druid-shell/src/platform/gtk/window.rs | 22 +-- druid-shell/src/platform/web/window.rs | 22 +-- druid-shell/src/platform/windows/window.rs | 47 ++--- druid-shell/src/scale.rs | 201 ++++++++++++--------- druid-shell/src/window.rs | 8 +- druid/src/app.rs | 6 +- druid/src/widget/flex.rs | 12 +- 10 files changed, 181 insertions(+), 143 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a65140a1c..ef07952907 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,7 +62,7 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i - Timer events will only be delivered to the widgets that requested them. ([#831] by [@sjoshid]) - `Event::Wheel` now contains a `MouseEvent` structure. ([#895] by [@teddemunnik]) - The `WindowHandle::get_dpi` method got replaced by `WindowHandle::get_scale`. ([#904] by [@xStrom]) -- The `WinHandler::size` method now gets a `Size` in points. ([#904] by [@xStrom]) +- The `WinHandler::size` method now gets a `Size` in display points. ([#904] by [@xStrom]) - `AppDelegate::command` now receives a `Target` instead of a `&Target`. ([#909] by [@xStrom]) - `SHOW_WINDOW` and `CLOSE_WINDOW` commands now only use `Target` to determine the affected window. ([#928] by [@finnerale]) - Replaced `NEW_WINDOW`, `SET_MENU` and `SHOW_CONTEXT_MENU` commands with methods on `EventCtx` and `DelegateCtx`. ([#931] by [@finnerale]) diff --git a/docs/src/widget.md b/docs/src/widget.md index d2a9fe66c2..1dc9a0da22 100644 --- a/docs/src/widget.md +++ b/docs/src/widget.md @@ -48,7 +48,7 @@ widgets]. Widgets are intended to be modular and composable, not monolithic. For instance, widgets generally do not control their own alignment or padding; if you have -a label, and you would like it to have 8pt of horizontal padding and 4pt of +a label, and you would like it to have 8dp of horizontal padding and 4dp of vertical padding, you can just do, ```rust,noplaypen diff --git a/druid-shell/src/mouse.rs b/druid-shell/src/mouse.rs index 5bbe4dc92f..674c3cf3a5 100644 --- a/druid-shell/src/mouse.rs +++ b/druid-shell/src/mouse.rs @@ -24,7 +24,7 @@ use crate::keyboard::KeyModifiers; /// receiving a move event before another mouse event. #[derive(Debug, Clone, PartialEq)] pub struct MouseEvent { - /// The location of the mouse in points in relation to the current window. + /// The location of the mouse in display points in relation to the current window. pub pos: Point, /// Mouse buttons being held down during a move or after a click event. /// Thus it will contain the `button` that triggered a mouse-down event, diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 9ccd5c803c..ce4c7b58fa 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -173,7 +173,7 @@ impl WindowBuilder { .map(|c| c.get_default_screen().get_resolution() as f64) .unwrap_or(96.0); let mut scale = Scale::from_dpi(dpi, dpi); - let size_px = scale.set_size_pt(self.size); + let size_px = scale.set_size_dp(self.size); window.set_default_size(size_px.width as i32, size_px.height as i32); @@ -236,9 +236,9 @@ impl WindowBuilder { }); // Set the minimum size - if let Some(size_pt) = self.min_size { + if let Some(size_dp) = self.min_size { let mut scale = Scale::from_dpi(dpi, dpi); - let size_px = scale.set_size_pt(size_pt); + let size_px = scale.set_size_dp(size_dp); win_state .drawing_area .set_size_request(size_px.width as i32, size_px.height as i32); @@ -262,9 +262,9 @@ impl WindowBuilder { let extents = widget.get_allocation(); let size_px = Size::new(extents.width as f64, extents.height as f64); if scale.size_px() != size_px { - let size_pt = scale.set_size_px(size_px); + let size_dp = scale.set_size_px(size_px); if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() { - handler_borrow.size(size_pt); + handler_borrow.size(size_dp); } else { log::warn!("Failed to inform the handler of a resize because it was already borrowed"); } @@ -306,7 +306,7 @@ impl WindowBuilder { let button_state = event.get_state(); handler.mouse_down( &MouseEvent { - pos: scale.px_to_pt_point(Point::from(event.get_position())), + pos: scale.to_dp(&Point::from(event.get_position())), buttons: get_mouse_buttons_from_modifiers(button_state).with(button), mods: get_modifiers(button_state), count: get_mouse_click_count(event.get_event_type()), @@ -335,7 +335,7 @@ impl WindowBuilder { let button_state = event.get_state(); handler.mouse_up( &MouseEvent { - pos: scale.px_to_pt_point(Point::from(event.get_position())), + pos: scale.to_dp(&Point::from(event.get_position())), buttons: get_mouse_buttons_from_modifiers(button_state).without(button), mods: get_modifiers(button_state), count: 0, @@ -361,7 +361,7 @@ impl WindowBuilder { if let Ok(scale) = state.scale.try_borrow() { let motion_state = motion.get_state(); let mouse_event = MouseEvent { - pos: scale.px_to_pt_point(Point::from(motion.get_position())), + pos: scale.to_dp(&Point::from(motion.get_position())), buttons: get_mouse_buttons_from_modifiers(motion_state), mods: get_modifiers(motion_state), count: 0, @@ -388,7 +388,7 @@ impl WindowBuilder { if let Ok(scale) = state.scale.try_borrow() { let crossing_state = crossing.get_state(); let mouse_event = MouseEvent { - pos: scale.px_to_pt_point(Point::from(crossing.get_position())), + pos: scale.to_dp(&Point::from(crossing.get_position())), buttons: get_mouse_buttons_from_modifiers(crossing_state), mods: get_modifiers(crossing_state), count: 0, @@ -447,7 +447,7 @@ impl WindowBuilder { if let Some(wheel_delta) = wheel_delta { let mouse_event = MouseEvent { - pos: scale.px_to_pt_point(Point::from(scroll.get_position())), + pos: scale.to_dp(&Point::from(scroll.get_position())), buttons: get_mouse_buttons_from_modifiers(scroll.get_state()), mods, count: 0, @@ -567,7 +567,7 @@ impl WindowHandle { if let Some(state) = self.state.upgrade() { if let Ok(scale) = state.scale.try_borrow() { // GTK takes rects with non-negative integer width/height. - let r = scale::expand_rect(scale.pt_to_px_rect(rect.abs())); + let r = scale::expand_rect(scale.to_px(&rect.abs())); let origin = state.drawing_area.get_allocation(); state.window.queue_draw_area( r.x0 as i32 + origin.x, diff --git a/druid-shell/src/platform/web/window.rs b/druid-shell/src/platform/web/window.rs index c39b481414..a921d2e673 100644 --- a/druid-shell/src/platform/web/window.rs +++ b/druid-shell/src/platform/web/window.rs @@ -227,16 +227,16 @@ fn setup_resize_callback(ws: &Rc) { let state = ws.clone(); register_window_event_listener(ws, "resize", move |_: web_sys::UiEvent| { let (css_width, css_height, dpr) = state.get_window_size_and_dpr(); - let size_pt = state.scale.try_borrow_mut().map_or(None, |mut scale| { + let size_dp = state.scale.try_borrow_mut().map_or(None, |mut scale| { *scale = Scale::from_scale(dpr, dpr); - let size_px = scale.set_size_pt(Size::new(css_width, css_height)); + let size_px = scale.set_size_dp(Size::new(css_width, css_height)); state.canvas.set_width(size_px.width as u32); state.canvas.set_height(size_px.height as u32); let _ = state.context.scale(scale.scale_x(), scale.scale_y()); - Some(scale.size_pt()) + Some(scale.size_dp()) }); - if let Some(size_pt) = size_pt { - state.handler.borrow_mut().size(size_pt); + if let Some(size_dp) = size_dp { + state.handler.borrow_mut().size(size_dp); } else { log::error!("Skipped resize event because couldn't borrow scale"); } @@ -370,14 +370,14 @@ impl WindowBuilder { Scale::from_scale(dpr, dpr) }; let size_px = { - // The initial size in points isn't necessarily the final size in points - let size_pt = Size::new(canvas.offset_width() as f64, canvas.offset_height() as f64); - scale.set_size_pt(size_pt) + // The initial size in display points isn't necessarily the final size in display points + let size_dp = Size::new(canvas.offset_width() as f64, canvas.offset_height() as f64); + scale.set_size_dp(size_dp) }; canvas.set_width(size_px.width as u32); canvas.set_height(size_px.height as u32); let _ = context.scale(scale.scale_x(), scale.scale_y()); - let size_pt = scale.size_pt(); + let size_dp = scale.size_dp(); set_cursor(&canvas, &self.cursor); @@ -399,7 +399,7 @@ impl WindowBuilder { let wh = window.clone(); window .request_animation_frame(move || { - wh.handler.borrow_mut().size(size_pt); + wh.handler.borrow_mut().size(size_dp); }) .expect("Failed to request animation frame"); @@ -447,7 +447,7 @@ impl WindowHandle { pub fn invalidate(&self) { if let Some(s) = self.0.upgrade() { if let Ok(scale) = s.scale.try_borrow() { - s.invalid_rect.set(scale.size_pt().to_rect()); + s.invalid_rect.set(scale.size_dp().to_rect()); } else { log::error!("Failed to get scale"); } diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 3da1c155e1..69b305e95b 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -453,13 +453,12 @@ impl WndProc for MyWndProc { s.render_target = rt.ok(); } s.handler.rebuild_resources(); - let rect_pt = - self.with_scale(|scale| scale.px_to_pt_rect(util::recti_to_rect(rect))); + let rect_dp = self.with_scale(|scale| scale.to_dp(&util::recti_to_rect(rect))); s.render( &self.d2d_factory, &self.dwrite_factory, &self.handle, - rect_pt, + rect_dp, ); if let Some(ref mut ds) = s.dcomp_state { let params = DXGI_PRESENT_PARAMETERS { @@ -486,13 +485,13 @@ impl WndProc for MyWndProc { let rt = paint::create_render_target(&self.d2d_factory, hwnd); s.render_target = rt.ok(); { - let rect_pt = self.with_scale(|scale| scale.size_pt().to_rect()); + let rect_dp = self.with_scale(|scale| scale.size_dp().to_rect()); s.handler.rebuild_resources(); s.render( &self.d2d_factory, &self.dwrite_factory, &self.handle, - rect_pt, + rect_dp, ); } @@ -527,7 +526,7 @@ impl WndProc for MyWndProc { &self.d2d_factory, &self.dwrite_factory, &self.handle, - scale.size_pt().to_rect(), + scale.size_dp().to_rect(), ); (*s.dcomp_state.as_ref().unwrap().swap_chain).Present(0, 0); } else { @@ -554,9 +553,9 @@ impl WndProc for MyWndProc { let s = s.as_mut().unwrap(); let width = LOWORD(lparam as u32) as u32; let height = HIWORD(lparam as u32) as u32; - let size_pt = self + let size_dp = self .with_scale_mut(|scale| scale.set_size_px((width as f64, height as f64))); - s.handler.size(size_pt); + s.handler.size(size_dp); let use_hwnd = if let Some(ref dcomp_state) = s.dcomp_state { dcomp_state.sizing } else { @@ -588,11 +587,16 @@ impl WndProc for MyWndProc { ); } if SUCCEEDED(res) { - let rect = size_pt.to_rect(); + let rect_dp = size_dp.to_rect(); self.with_scale(|scale| { s.rebuild_render_target(&self.d2d_factory, scale) }); - s.render(&self.d2d_factory, &self.dwrite_factory, &self.handle, rect); + s.render( + &self.d2d_factory, + &self.dwrite_factory, + &self.handle, + rect_dp, + ); if let Some(ref mut dcomp_state) = s.dcomp_state { (*dcomp_state.swap_chain).Present(0, 0); let _ = dcomp_state.dcomp_device.commit(); @@ -722,7 +726,7 @@ impl WndProc for MyWndProc { } let pos = - self.with_scale(|scale| scale.px_to_pt_point((p.x as f64, p.y as f64))); + self.with_scale(|scale| scale.to_dp(&(p.x as f64, p.y as f64).into())); let buttons = get_buttons(down_state); let event = MouseEvent { pos, @@ -768,7 +772,7 @@ impl WndProc for MyWndProc { } } - let pos = self.with_scale(|scale| scale.px_to_pt_point((x as f64, y as f64))); + let pos = self.with_scale(|scale| scale.to_dp(&(x as f64, y as f64).into())); let mods = KeyModifiers { shift: wparam & MK_SHIFT != 0, alt: get_mod_state_alt(), @@ -836,7 +840,7 @@ impl WndProc for MyWndProc { let x = LOWORD(lparam as u32) as i16 as i32; let y = HIWORD(lparam as u32) as i16 as i32; let pos = - self.with_scale(|scale| scale.px_to_pt_point((x as f64, y as f64))); + self.with_scale(|scale| scale.to_dp(&(x as f64, y as f64).into())); let mods = KeyModifiers { shift: wparam & MK_SHIFT != 0, alt: get_mod_state_alt(), @@ -922,9 +926,9 @@ impl WndProc for MyWndProc { let min_max_info = unsafe { &mut *(lparam as *mut MINMAXINFO) }; if let Ok(s) = self.state.try_borrow() { let s = s.as_ref().unwrap(); - if let Some(min_size_pt) = s.min_size { + if let Some(min_size_dp) = s.min_size { let min_size_px = self.with_scale(|scale| { - Scale::from_dpi(scale.dpi_x(), scale.dpi_y()).set_size_pt(min_size_pt) + Scale::from_dpi(scale.dpi_x(), scale.dpi_y()).set_size_dp(min_size_dp) }); min_max_info.ptMinTrackSize.x = min_size_px.width as i32; min_max_info.ptMinTrackSize.y = min_size_px.height as i32; @@ -1023,7 +1027,7 @@ impl WindowBuilder { 96.0 }; let mut scale = Scale::from_dpi(dpi, dpi); - let size_px = scale.set_size_pt(self.size); + let size_px = scale.set_size_dp(self.size); let window = WindowState { hwnd: Cell::new(0 as HWND), @@ -1325,10 +1329,11 @@ impl WindowHandle { pub fn invalidate_rect(&self, rect: Rect) { if let Some(w) = self.state.upgrade() { - let rect = - w.scale.try_borrow().ok().map(|scale| { - util::rect_to_recti(scale::expand_rect(scale.pt_to_px_rect(rect))) - }); + let rect = w + .scale + .try_borrow() + .ok() + .map(|scale| util::rect_to_recti(scale::expand_rect(scale.to_px(&rect)))); let hwnd = w.hwnd.get(); unsafe { let result = match rect { @@ -1415,7 +1420,7 @@ impl WindowHandle { let hmenu = menu.into_hmenu(); if let Some(w) = self.state.upgrade() { let hwnd = w.hwnd.get(); - let pos = w.scale.borrow().pt_to_px_point(pos).round(); + let pos = w.scale.borrow().to_px(&pos).round(); unsafe { let mut point = POINT { x: pos.x as i32, diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index 6773721fff..c18f81e125 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -23,15 +23,15 @@ const SCALE_TARGET_DPI: f64 = 96.0; /// Resolution scaling state. /// /// This holds the platform DPI, the platform area size in pixels, -/// the logical area size in points, and the scale factors between them. +/// the logical area size in display points, and the scale factors between them. /// -/// To translate coordinates between pixels and points you should use one of the +/// To translate coordinates between pixels and display points you should use one of the /// helper conversion methods of `Scale` or for manual conversion [`scale_x`] / [`scale_y`]. /// /// The platform area size in pixels tends to be limited to integers and `Scale` works /// under that assumption. /// -/// The logical area size in points is an unrounded conversion, which means that it is +/// The logical area size in display points is an unrounded conversion, which means that it is /// often not limited to integers. This allows for accurate calculations of /// the platform area pixel boundaries from the logcal area using the scale factors. /// @@ -57,12 +57,28 @@ pub struct Scale { scale_x: f64, /// The scale factor on the y axis. scale_y: f64, - /// The size of the scaled area in points. - size_pt: Size, + /// The size of the scaled area in display points. + size_dp: Size, /// The size of the scaled area in pixels. size_px: Size, } +/// The `Scalable` trait describes how coordinates should be translated +/// from display points into pixels and vice versa using a [`Scale`]. +/// +/// [`Scale`]: struct.Scale.html +pub trait Scalable { + /// Converts the scalable item from display points into pixels, + /// using the x axis scale factor for coordinates on the x axis + /// and the y axis scale factor for coordinates on the y axis. + fn to_px(&self, scale: &Scale) -> Self; + + /// Converts the scalable item from pixels into display points, + /// using the x axis scale factor for coordinates on the x axis + /// and the y axis scale factor for coordinates on the y axis. + fn to_dp(&self, scale: &Scale) -> Self; +} + impl Scale { /// Create a new `Scale` state based on the specified DPIs. /// @@ -73,7 +89,7 @@ impl Scale { dpi_y, scale_x: dpi_x / SCALE_TARGET_DPI, scale_y: dpi_y / SCALE_TARGET_DPI, - size_pt: Size::ZERO, + size_dp: Size::ZERO, size_px: Size::ZERO, } } @@ -87,34 +103,34 @@ impl Scale { dpi_y: SCALE_TARGET_DPI * scale_y, scale_x, scale_y, - size_pt: Size::ZERO, + size_dp: Size::ZERO, size_px: Size::ZERO, } } /// Set the size of the scaled area in pixels. /// - /// This updates the internal state and returns the same size in points. + /// This updates the internal state and returns the same size in display points. pub fn set_size_px>(&mut self, size: T) -> Size { let size = size.into(); self.size_px = size; - self.size_pt = self.px_to_pt_size(size); - self.size_pt + self.size_dp = self.to_dp(&size); + self.size_dp } - /// Set the size of the scaled area in pixels via conversion from points. + /// Set the size of the scaled area in pixels via conversion from display points. /// /// This updates the internal state and returns the same size in pixels. /// /// The calculated size in pixels is rounded away from zero to integers. - /// That means that the scaled area size in points isn't always the same - /// as the `size` given to this method. To find out the new size in points use [`size_pt`]. + /// That means that the scaled area size in display points isn't always the same + /// as the `size` given to this method. To find out the new size in points use [`size_dp`]. /// - /// [`size_pt`]: #method.size_pt - pub fn set_size_pt>(&mut self, size: T) -> Size { + /// [`size_dp`]: #method.size_dp + pub fn set_size_dp>(&mut self, size: T) -> Size { let size = size.into(); - self.size_px = self.pt_to_px_size(size).expand(); - self.size_pt = self.px_to_pt_size(self.size_px); + self.size_px = self.to_px(&size).expand(); + self.size_dp = self.to_dp(&self.size_px); self.size_px } @@ -149,10 +165,10 @@ impl Scale { self.scale_y } - /// Returns the scaled area size in points. + /// Returns the scaled area size in display points. #[inline] - pub fn size_pt(&self) -> Size { - self.size_pt + pub fn size_dp(&self) -> Size { + self.size_dp } /// Returns the scaled area size in pixels. @@ -161,119 +177,136 @@ impl Scale { self.size_px } - /// Converts from points into pixels, using the x axis scale factor. + /// Converts from display points into pixels, using the x axis scale factor. #[inline] - pub fn pt_to_px_x>(&self, x: T) -> f64 { + pub fn dp_to_px_x>(&self, x: T) -> f64 { x.into() * self.scale_x } - /// Converts from points into pixels, using the y axis scale factor. + /// Converts from display points into pixels, using the y axis scale factor. #[inline] - pub fn pt_to_px_y>(&self, y: T) -> f64 { + pub fn dp_to_px_y>(&self, y: T) -> f64 { y.into() * self.scale_y } - /// Converts from points into pixels, + /// Converts from display points into pixels, /// using the x axis scale factor for `x` and the y axis scale factor for `y`. #[inline] - pub fn pt_to_px_xy>(&self, x: T, y: T) -> (f64, f64) { + pub fn dp_to_px_xy>(&self, x: T, y: T) -> (f64, f64) { (x.into() * self.scale_x, y.into() * self.scale_y) } - /// Converts a `Point` from points into pixels, - /// using the x axis scale factor for `Point::x` and the y axis scale factor for `Point::y`. + /// Converts the `item` from display points into pixels, + /// using the x axis scale factor for coordinates on the x axis + /// and the y axis scale factor for coordinates on the y axis. #[inline] - pub fn pt_to_px_point>(&self, point: T) -> Point { - let point = point.into(); - Point::new(point.x * self.scale_x, point.y * self.scale_y) + pub fn to_px(&self, item: &T) -> T { + item.to_px(self) } - /// Converts a `Rect` from points into pixels, - /// using the x axis scale factor for `Rect::x0` and `Rect::x1` - /// and the y axis scale factor for `Rect::y0` and `Rect::y1`. + /// Converts from pixels into display points, using the x axis scale factor. #[inline] - pub fn pt_to_px_rect>(&self, rect: T) -> Rect { - let rect = rect.into(); - Rect::new( - rect.x0 * self.scale_x, - rect.y0 * self.scale_y, - rect.x1 * self.scale_x, - rect.y1 * self.scale_y, - ) + pub fn px_to_dp_x>(&self, x: T) -> f64 { + x.into() / self.scale_x } - /// Converts a `Size` from points into pixels, - /// using the x axis scale factor for `Size::width` - /// and the y axis scale factor for `Size::height`. + /// Converts from pixels into display points, using the y axis scale factor. #[inline] - pub fn pt_to_px_size>(&self, size: T) -> Size { - let size = size.into(); - Size::new(size.width * self.scale_x, size.height * self.scale_y) + pub fn px_to_dp_y>(&self, y: T) -> f64 { + y.into() / self.scale_y } - /// Converts from pixels into points, using the x axis scale factor. + /// Converts from pixels into display points, + /// using the x axis scale factor for `x` and the y axis scale factor for `y`. #[inline] - pub fn px_to_pt_x>(&self, x: T) -> f64 { - x.into() / self.scale_x + pub fn px_to_dp_xy>(&self, x: T, y: T) -> (f64, f64) { + (x.into() / self.scale_x, y.into() / self.scale_y) } - /// Converts from pixels into points, using the y axis scale factor. + /// Converts the `item` from pixels into display points, + /// using the x axis scale factor for coordinates on the x axis + /// and the y axis scale factor for coordinates on the y axis. #[inline] - pub fn px_to_pt_y>(&self, y: T) -> f64 { - y.into() / self.scale_y + pub fn to_dp(&self, item: &T) -> T { + item.to_dp(self) } +} - /// Converts from pixels into points, +impl std::fmt::Debug for Scale { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "DPI ({}, {}) => ({}, {}) | {:?} => {:?}", + self.dpi_x, self.dpi_y, self.scale_x, self.scale_y, self.size_px, self.size_dp, + ) + } +} + +impl Scalable for Point { + /// Converts a `Point` from display points into pixels, /// using the x axis scale factor for `x` and the y axis scale factor for `y`. #[inline] - pub fn px_to_pt_xy>(&self, x: T, y: T) -> (f64, f64) { - (x.into() / self.scale_x, y.into() / self.scale_y) + fn to_px(&self, scale: &Scale) -> Point { + Point::new(self.x * scale.scale_x, self.y * scale.scale_y) } - /// Converts a `Point` from pixels into points, - /// using the x axis scale factor for `Point::x` and the y axis scale factor for `Point::y`. + /// Converts a `Point` from pixels into display points, + /// using the x axis scale factor for `x` and the y axis scale factor for `y`. #[inline] - pub fn px_to_pt_point>(&self, point: T) -> Point { - let point = point.into(); - Point::new(point.x / self.scale_x, point.y / self.scale_y) + fn to_dp(&self, scale: &Scale) -> Point { + Point::new(self.x / scale.scale_x, self.y / scale.scale_y) } +} - /// Converts a `Rect` from pixels into points, - /// using the x axis scale factor for `Rect::x0` and `Rect::x1` - /// and the y axis scale factor for `Rect::y0` and `Rect::y1`. +impl Scalable for Size { + /// Converts a `Size` from display points into pixels, + /// using the x axis scale factor for `width` + /// and the y axis scale factor for `height`. #[inline] - pub fn px_to_pt_rect>(&self, rect: T) -> Rect { - let rect = rect.into(); - Rect::new( - rect.x0 / self.scale_x, - rect.y0 / self.scale_y, - rect.x1 / self.scale_x, - rect.y1 / self.scale_y, - ) + fn to_px(&self, scale: &Scale) -> Size { + Size::new(self.width * scale.scale_x, self.height * scale.scale_y) } /// Converts a `Size` from pixels into points, - /// using the x axis scale factor for `Size::width` - /// and the y axis scale factor for `Size::height`. + /// using the x axis scale factor for `width` + /// and the y axis scale factor for `height`. #[inline] - pub fn px_to_pt_size>(&self, size: T) -> Size { - let size = size.into(); - Size::new(size.width / self.scale_x, size.height / self.scale_y) + fn to_dp(&self, scale: &Scale) -> Size { + Size::new(self.width / scale.scale_x, self.height / scale.scale_y) } } -impl std::fmt::Debug for Scale { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "DPI ({}, {}) => ({}, {}) | {:?} => {:?}", - self.dpi_x, self.dpi_y, self.scale_x, self.scale_y, self.size_px, self.size_pt, +impl Scalable for Rect { + /// Converts a `Rect` from display points into pixels, + /// using the x axis scale factor for `x0` and `x1` + /// and the y axis scale factor for `y0` and `y1`. + #[inline] + fn to_px(&self, scale: &Scale) -> Rect { + Rect::new( + self.x0 * scale.scale_x, + self.y0 * scale.scale_y, + self.x1 * scale.scale_x, + self.y1 * scale.scale_y, + ) + } + + /// Converts a `Rect` from pixels into display points, + /// using the x axis scale factor for `x0` and `x1` + /// and the y axis scale factor for `y0` and `y1`. + #[inline] + fn to_dp(&self, scale: &Scale) -> Rect { + Rect::new( + self.x0 / scale.scale_x, + self.y0 / scale.scale_y, + self.x1 / scale.scale_x, + self.y1 / scale.scale_y, ) } } // TODO: Replace usages of this with rect.expand() after kurbo#107 has landed. #[allow(dead_code)] +#[doc(hidden)] pub fn expand_rect(rect: Rect) -> Rect { let (x0, x1) = if rect.x0 < rect.x1 { (rect.x0.floor(), rect.x1.ceil()) diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index 6656b7e72b..9f8087b87c 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -233,7 +233,7 @@ impl WindowBuilder { self.0.set_handler(handler) } - /// Set the window's initial drawing area size in points. + /// Set the window's initial drawing area size in display points. /// /// The actual window size in pixels will depend on the platform DPI settings. /// @@ -246,7 +246,7 @@ impl WindowBuilder { self.0.set_size(size) } - /// Set the window's minimum drawing area size in points. + /// Set the window's minimum drawing area size in display points. /// /// The actual minimum window size in pixels will depend on the platform DPI settings. /// @@ -300,14 +300,14 @@ pub trait WinHandler { /// Called when the size of the window has changed. /// - /// The `size` parameter is the new size in points. + /// The `size` parameter is the new size in display points. #[allow(unused_variables)] fn size(&mut self, size: Size) {} /// Request the handler to paint the window contents. Return value /// indicates whether window is animating, i.e. whether another paint /// should be scheduled for the next animation frame. `invalid_rect` is the - /// rectangle in points that needs to be repainted. + /// rectangle in display points that needs to be repainted. fn paint(&mut self, piet: &mut piet_common::Piet, invalid_rect: Rect) -> bool; /// Called when the resources need to be rebuilt. diff --git a/druid/src/app.rs b/druid/src/app.rs index d4c88dd14f..4038e01fac 100644 --- a/druid/src/app.rs +++ b/druid/src/app.rs @@ -187,10 +187,10 @@ impl WindowDesc { self } - /// Set the window's initial drawing area size in points. + /// Set the window's initial drawing area size in display points. /// /// You can pass in a tuple `(width, height)` or a [`Size`], - /// e.g. to create a window with a drawing area 1000pt wide and 500pt high: + /// e.g. to create a window with a drawing area 1000dp wide and 500dp high: /// /// ```ignore /// window.window_size((1000.0, 500.0)); @@ -207,7 +207,7 @@ impl WindowDesc { self } - /// Set the window's minimum drawing area size in points. + /// Set the window's minimum drawing area size in display points. /// /// The actual minimum window size in pixels will depend on the platform DPI settings. /// diff --git a/druid/src/widget/flex.rs b/druid/src/widget/flex.rs index 7d069470ce..50c02753b3 100644 --- a/druid/src/widget/flex.rs +++ b/druid/src/widget/flex.rs @@ -47,15 +47,15 @@ use crate::{ /// /// When should your children be flexible? With other things being equal, /// a flexible child has lower layout priority than a non-flexible child. -/// Imagine, for instance, we have a row that is 30pt wide, and we have -/// two children, both of which want to be 20pt wide. If child #1 is non-flex -/// and child #2 is flex, the first widget will take up its 20pt, and the second -/// widget will be constrained to 10pt. +/// Imagine, for instance, we have a row that is 30dp wide, and we have +/// two children, both of which want to be 20dp wide. If child #1 is non-flex +/// and child #2 is flex, the first widget will take up its 20dp, and the second +/// widget will be constrained to 10dp. /// /// If, instead, both widgets are flex, they will each be given equal space, -/// and both will end up taking up 15pt. +/// and both will end up taking up 15dp. /// -/// If both are non-flex they will both take up 20pt, and will overflow the +/// If both are non-flex they will both take up 20dp, and will overflow the /// container. /// /// ```no_compile From 38980c4382e362088d261721e41c0329f74e50b5 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 9 May 2020 18:50:13 +0300 Subject: [PATCH 16/29] Fix scale in GTK wheel event handler. --- druid-shell/src/platform/gtk/window.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index ce4c7b58fa..3bd3dc1a35 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -412,8 +412,7 @@ impl WindowBuilder { win_state.drawing_area.connect_scroll_event(clone!(handle => move |_widget, scroll| { if let Some(state) = handle.state.upgrade() { - if let Ok(mut handler) = state.handler.try_borrow_mut() { - + if let Ok(scale) = state.scale.try_borrow() { let mods = get_modifiers(scroll.get_state()); // The magic "120"s are from Microsoft's documentation for WM_MOUSEWHEEL. @@ -456,10 +455,14 @@ impl WindowBuilder { wheel_delta }; - handler.wheel(&mouse_event); + if let Ok(mut handler) = state.handler.try_borrow_mut() { + handler.wheel(&mouse_event); + } else { + log::info!("GTK event was dropped because the handler was already borrowed"); + } } } else { - log::info!("GTK event was dropped because the handler was already borrowed"); + log::warn!("GTK event was dropped because the scale was already borrowed"); } } From 28966e819c83ed4a9d53d65277a38d3c1b275c38 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 9 May 2020 19:30:49 +0300 Subject: [PATCH 17/29] Implement Scalable for Vec2/Line/Insets. --- druid-shell/src/scale.rs | 62 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index c18f81e125..a5ee0d24d4 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -16,7 +16,7 @@ use float_cmp::ApproxEq; -use crate::kurbo::{Point, Rect, Size}; +use crate::kurbo::{Insets, Line, Point, Rect, Size, Vec2}; const SCALE_TARGET_DPI: f64 = 96.0; @@ -242,6 +242,22 @@ impl std::fmt::Debug for Scale { } } +impl Scalable for Vec2 { + /// Converts a `Vec2` from display points into pixels, + /// using the x axis scale factor for `x` and the y axis scale factor for `y`. + #[inline] + fn to_px(&self, scale: &Scale) -> Vec2 { + Vec2::new(self.x * scale.scale_x, self.y * scale.scale_y) + } + + /// Converts a `Vec2` from pixels into display points, + /// using the x axis scale factor for `x` and the y axis scale factor for `y`. + #[inline] + fn to_dp(&self, scale: &Scale) -> Vec2 { + Vec2::new(self.x / scale.scale_x, self.y / scale.scale_y) + } +} + impl Scalable for Point { /// Converts a `Point` from display points into pixels, /// using the x axis scale factor for `x` and the y axis scale factor for `y`. @@ -258,6 +274,22 @@ impl Scalable for Point { } } +impl Scalable for Line { + /// Converts a `Line` from display points into pixels, + /// using the x axis scale factor for `x` and the y axis scale factor for `y`. + #[inline] + fn to_px(&self, scale: &Scale) -> Line { + Line::new(self.p0.to_px(scale), self.p1.to_px(scale)) + } + + /// Converts a `Line` from pixels into display points, + /// using the x axis scale factor for `x` and the y axis scale factor for `y`. + #[inline] + fn to_dp(&self, scale: &Scale) -> Line { + Line::new(self.p0.to_dp(scale), self.p1.to_dp(scale)) + } +} + impl Scalable for Size { /// Converts a `Size` from display points into pixels, /// using the x axis scale factor for `width` @@ -304,6 +336,34 @@ impl Scalable for Rect { } } +impl Scalable for Insets { + /// Converts `Insets` from display points into pixels, + /// using the x axis scale factor for `x0` and `x1` + /// and the y axis scale factor for `y0` and `y1`. + #[inline] + fn to_px(&self, scale: &Scale) -> Insets { + Insets::new( + self.x0 * scale.scale_x, + self.y0 * scale.scale_y, + self.x1 * scale.scale_x, + self.y1 * scale.scale_y, + ) + } + + /// Converts `Insets` from pixels into display points, + /// using the x axis scale factor for `x0` and `x1` + /// and the y axis scale factor for `y0` and `y1`. + #[inline] + fn to_dp(&self, scale: &Scale) -> Insets { + Insets::new( + self.x0 / scale.scale_x, + self.y0 / scale.scale_y, + self.x1 / scale.scale_x, + self.y1 / scale.scale_y, + ) + } +} + // TODO: Replace usages of this with rect.expand() after kurbo#107 has landed. #[allow(dead_code)] #[doc(hidden)] From 59992ca4c18f99880e842d61515a78ca872af30d Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Mon, 11 May 2020 18:43:33 +0300 Subject: [PATCH 18/29] Fix web wheel event DPI handling. --- druid-shell/src/platform/web/window.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/druid-shell/src/platform/web/window.rs b/druid-shell/src/platform/web/window.rs index a921d2e673..65656aae45 100644 --- a/druid-shell/src/platform/web/window.rs +++ b/druid-shell/src/platform/web/window.rs @@ -196,14 +196,15 @@ fn setup_scroll_callback(ws: &Rc) { let dx = event.delta_x(); let dy = event.delta_y(); - let height = state.canvas.height() as f64; - let width = state.canvas.width() as f64; // The value 35.0 was manually picked to produce similar behavior to mac/linux. let wheel_delta = match delta_mode { web_sys::WheelEvent::DOM_DELTA_PIXEL => Vec2::new(dx, dy), web_sys::WheelEvent::DOM_DELTA_LINE => Vec2::new(35.0 * dx, 35.0 * dy), - web_sys::WheelEvent::DOM_DELTA_PAGE => Vec2::new(width * dx, height * dy), + web_sys::WheelEvent::DOM_DELTA_PAGE => { + let size_dp = state.scale.borrow().size_dp(); + Vec2::new(size_dp.width * dx, size_dp.height * dy) + } _ => { log::warn!("Invalid deltaMode in WheelEvent: {}", delta_mode); return; From a1536278e6210e5e57df751346c6060f435ed685 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 16 May 2020 14:06:57 +0300 Subject: [PATCH 19/29] Merge Cargo.lock. --- Cargo.lock | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index d78f875c98..89b7451dc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -365,7 +365,7 @@ dependencies = [ "console_log 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "druid-derive 0.4.0", "druid-shell 0.6.0", - "float-cmp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "float-cmp 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "fluent-bundle 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "fluent-langneg 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "fluent-syntax 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -403,6 +403,7 @@ dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "cocoa 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", "core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "float-cmp 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "gdk 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "gdk-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -475,6 +476,11 @@ name = "float-cmp" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "fluent-bundle" version = "0.11.0" @@ -1912,6 +1918,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42" "checksum float-cmp 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e" "checksum float-cmp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "da62c4f1b81918835a8c6a484a397775fff5953fe83529afd51b05f5c6a6617d" +"checksum float-cmp 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" "checksum fluent-bundle 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "27ade33328521266c81cc0924523988f43ccd7359f64689a1b6e818afca3a646" "checksum fluent-langneg 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fe5815efd5542e40841cd34ef9003822352b04c67a70c595c6758597c72e1f56" "checksum fluent-syntax 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ac0f7e83d14cccbf26e165d8881dcac5891af0d85a88543c09dd72ebd31d91ba" From b04b003d0729168d213c1dd65bb15da2a2e2f1b1 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 16 May 2020 14:18:23 +0300 Subject: [PATCH 20/29] Make minor changes based on feedback. --- druid-shell/src/platform/web/window.rs | 19 +++++++++---------- druid-shell/src/platform/windows/util.rs | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/druid-shell/src/platform/web/window.rs b/druid-shell/src/platform/web/window.rs index 65656aae45..6406db1aea 100644 --- a/druid-shell/src/platform/web/window.rs +++ b/druid-shell/src/platform/web/window.rs @@ -228,13 +228,13 @@ fn setup_resize_callback(ws: &Rc) { let state = ws.clone(); register_window_event_listener(ws, "resize", move |_: web_sys::UiEvent| { let (css_width, css_height, dpr) = state.get_window_size_and_dpr(); - let size_dp = state.scale.try_borrow_mut().map_or(None, |mut scale| { + let size_dp = state.scale.try_borrow_mut().ok().map(|mut scale| { *scale = Scale::from_scale(dpr, dpr); let size_px = scale.set_size_dp(Size::new(css_width, css_height)); state.canvas.set_width(size_px.width as u32); state.canvas.set_height(size_px.height as u32); let _ = state.context.scale(scale.scale_x(), scale.scale_y()); - Some(scale.size_dp()) + scale.size_dp() }); if let Some(size_dp) = size_dp { state.handler.borrow_mut().size(size_dp); @@ -550,14 +550,13 @@ impl WindowHandle { /// Get the `Scale` of the window. pub fn get_scale(&self) -> Result { - if let Some(state) = self.0.upgrade() { - match state.scale.try_borrow() { - Ok(scale) => Ok(scale.clone()), - Err(_) => Err(BorrowError::new("WindowHandle::get_scale", "scale", false).into()), - } - } else { - Err(ShellError::WindowDropped) - } + self.0 + .upgrade() + .ok_or(ShellError::WindowDropped)? + .scale + .try_borrow() + .map_err(|_| BorrowError::new("WindowHandle::get_scale", "scale", false).into()) + .map(|scale| scale.clone()) } pub fn set_menu(&self, _menu: Menu) { diff --git a/druid-shell/src/platform/windows/util.rs b/druid-shell/src/platform/windows/util.rs index dd77c43777..8ae673a32c 100644 --- a/druid-shell/src/platform/windows/util.rs +++ b/druid-shell/src/platform/windows/util.rs @@ -107,7 +107,7 @@ impl FromWide for [u16] { /// Converts a `Rect` to a winapi `RECT`. #[inline] -pub fn rect_to_recti(rect: Rect) -> RECT { +pub(crate) fn rect_to_recti(rect: Rect) -> RECT { RECT { left: rect.x0 as i32, top: rect.y0 as i32, @@ -118,7 +118,7 @@ pub fn rect_to_recti(rect: Rect) -> RECT { /// Converts a winapi `RECT` to a `Rect`. #[inline] -pub fn recti_to_rect(rect: RECT) -> Rect { +pub(crate) fn recti_to_rect(rect: RECT) -> Rect { Rect::new( rect.left as f64, rect.top as f64, From d17bb2d7d3094c830f9857a813b6c689122be431 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 16 May 2020 15:46:46 +0300 Subject: [PATCH 21/29] Split out ScaledArea from Scale. --- druid-shell/src/platform/gtk/window.rs | 4 +- druid-shell/src/platform/windows/window.rs | 148 ++++++++++----------- druid-shell/src/scale.rs | 141 ++++++++------------ druid-shell/src/window.rs | 4 +- 4 files changed, 128 insertions(+), 169 deletions(-) diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 3bd3dc1a35..3db3cf00e6 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -38,7 +38,7 @@ use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; use crate::error::{BorrowError, Error as ShellError}; use crate::keyboard; use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; -use crate::scale::{self, Scale}; +use crate::scale::Scale; use crate::window::{IdleToken, Text, TimerToken, WinHandler}; use super::application::Application; @@ -570,7 +570,7 @@ impl WindowHandle { if let Some(state) = self.state.upgrade() { if let Ok(scale) = state.scale.try_borrow() { // GTK takes rects with non-negative integer width/height. - let r = scale::expand_rect(scale.to_px(&rect.abs())); + let r = scale.to_px(&rect.abs()).expand(); let origin = state.drawing_area.get_allocation(); state.window.queue_draw_area( r.x0 as i32 + origin.x, diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index 69b305e95b..e0aa4c89be 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -58,11 +58,11 @@ use super::util::{self, as_result, FromWide, ToWide, OPTIONAL_FUNCTIONS}; use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; -use crate::error::{BorrowError, Error as ShellError}; +use crate::error::Error as ShellError; use crate::keyboard::{KeyEvent, KeyModifiers}; use crate::keycodes::KeyCode; use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; -use crate::scale::{self, Scale}; +use crate::scale::{Scale, ScaledArea}; use crate::window::{IdleToken, Text, TimerToken, WinHandler}; extern "system" { @@ -136,7 +136,8 @@ enum IdleKind { /// by interior mutability, so we can handle reentrant calls. struct WindowState { hwnd: Cell, - scale: RefCell, + scale: Cell, + area: Cell, wndproc: Box, idle_queue: Arc>>, timers: Arc>, @@ -357,40 +358,40 @@ impl MyWndProc { ); } - fn with_scale(&self, func: F) -> T - where - F: FnOnce(&Scale) -> T, - { - func( - &self - .handle - // Right now there aren't any mutable borrows to this. - // TODO: Attempt to guarantee this by making mutable handle borrows useless. - .borrow() - .state - .upgrade() - .unwrap() // WindowState drops after WM_NCDESTROY, so it's always here. - .scale - .borrow(), // Fine as long as scale is accessed through this or briefly elsewhere. - ) + fn scale(&self) -> Scale { + self.handle + // Right now there aren't any mutable borrows to this. + // TODO: Attempt to guarantee this by making mutable handle borrows useless. + .borrow() + .state + .upgrade() + .unwrap() // WindowState drops after WM_NCDESTROY, so it's always here. + .scale + .get() } - fn with_scale_mut(&self, func: F) -> T - where - F: FnOnce(&mut Scale) -> T, - { - func( - &mut self - .handle - // Right now there aren't any mutable borrows to this. - // TODO: Attempt to guarantee this by making mutable handle borrows useless. - .borrow() - .state - .upgrade() - .unwrap() // WindowState drops after WM_NCDESTROY, so it's always here. - .scale - .borrow_mut(), // Fine as long as scale is accessed through this or briefly elsewhere. - ) + fn area(&self) -> ScaledArea { + self.handle + // Right now there aren't any mutable borrows to this. + // TODO: Attempt to guarantee this by making mutable handle borrows useless. + .borrow() + .state + .upgrade() + .unwrap() // WindowState drops after WM_NCDESTROY, so it's always here. + .area + .get() + } + + fn set_area(&self, area: ScaledArea) { + self.handle + // Right now there aren't any mutable borrows to this. + // TODO: Attempt to guarantee this by making mutable handle borrows useless. + .borrow() + .state + .upgrade() + .unwrap() // WindowState drops after WM_NCDESTROY, so it's always here. + .area + .set(area) } } @@ -453,7 +454,7 @@ impl WndProc for MyWndProc { s.render_target = rt.ok(); } s.handler.rebuild_resources(); - let rect_dp = self.with_scale(|scale| scale.to_dp(&util::recti_to_rect(rect))); + let rect_dp = self.scale().to_dp(&util::recti_to_rect(rect)); s.render( &self.d2d_factory, &self.dwrite_factory, @@ -485,7 +486,7 @@ impl WndProc for MyWndProc { let rt = paint::create_render_target(&self.d2d_factory, hwnd); s.render_target = rt.ok(); { - let rect_dp = self.with_scale(|scale| scale.size_dp().to_rect()); + let rect_dp = self.area().size_dp().to_rect(); s.handler.rebuild_resources(); s.render( &self.d2d_factory, @@ -510,8 +511,8 @@ impl WndProc for MyWndProc { if let Ok(mut s) = self.state.try_borrow_mut() { let s = s.as_mut().unwrap(); if s.dcomp_state.is_some() { - let scale = self.with_scale(|scale| scale.clone()); - let size_px = scale.size_px(); + let area = self.area(); + let size_px = area.size_px(); let res = (*s.dcomp_state.as_mut().unwrap().swap_chain).ResizeBuffers( 2, size_px.width as u32, @@ -521,12 +522,12 @@ impl WndProc for MyWndProc { ); if SUCCEEDED(res) { s.handler.rebuild_resources(); - s.rebuild_render_target(&self.d2d_factory, &scale); + s.rebuild_render_target(&self.d2d_factory, &self.scale()); s.render( &self.d2d_factory, &self.dwrite_factory, &self.handle, - scale.size_dp().to_rect(), + area.size_dp().to_rect(), ); (*s.dcomp_state.as_ref().unwrap().swap_chain).Present(0, 0); } else { @@ -553,8 +554,10 @@ impl WndProc for MyWndProc { let s = s.as_mut().unwrap(); let width = LOWORD(lparam as u32) as u32; let height = HIWORD(lparam as u32) as u32; - let size_dp = self - .with_scale_mut(|scale| scale.set_size_px((width as f64, height as f64))); + let scale = self.scale(); + let area = ScaledArea::from_px((width as f64, height as f64), &scale); + let size_dp = area.size_dp(); + self.set_area(area); s.handler.size(size_dp); let use_hwnd = if let Some(ref dcomp_state) = s.dcomp_state { dcomp_state.sizing @@ -587,15 +590,12 @@ impl WndProc for MyWndProc { ); } if SUCCEEDED(res) { - let rect_dp = size_dp.to_rect(); - self.with_scale(|scale| { - s.rebuild_render_target(&self.d2d_factory, scale) - }); + s.rebuild_render_target(&self.d2d_factory, &scale); s.render( &self.d2d_factory, &self.dwrite_factory, &self.handle, - rect_dp, + size_dp.to_rect(), ); if let Some(ref mut dcomp_state) = s.dcomp_state { (*dcomp_state.swap_chain).Present(0, 0); @@ -725,8 +725,7 @@ impl WndProc for MyWndProc { } } - let pos = - self.with_scale(|scale| scale.to_dp(&(p.x as f64, p.y as f64).into())); + let pos = self.scale().to_dp(&(p.x as f64, p.y as f64).into()); let buttons = get_buttons(down_state); let event = MouseEvent { pos, @@ -772,7 +771,7 @@ impl WndProc for MyWndProc { } } - let pos = self.with_scale(|scale| scale.to_dp(&(x as f64, y as f64).into())); + let pos = self.scale().to_dp(&(x as f64, y as f64).into()); let mods = KeyModifiers { shift: wparam & MK_SHIFT != 0, alt: get_mod_state_alt(), @@ -839,8 +838,7 @@ impl WndProc for MyWndProc { }; let x = LOWORD(lparam as u32) as i16 as i32; let y = HIWORD(lparam as u32) as i16 as i32; - let pos = - self.with_scale(|scale| scale.to_dp(&(x as f64, y as f64).into())); + let pos = self.scale().to_dp(&(x as f64, y as f64).into()); let mods = KeyModifiers { shift: wparam & MK_SHIFT != 0, alt: get_mod_state_alt(), @@ -927,9 +925,8 @@ impl WndProc for MyWndProc { if let Ok(s) = self.state.try_borrow() { let s = s.as_ref().unwrap(); if let Some(min_size_dp) = s.min_size { - let min_size_px = self.with_scale(|scale| { - Scale::from_dpi(scale.dpi_x(), scale.dpi_y()).set_size_dp(min_size_dp) - }); + let min_area = ScaledArea::from_dp(min_size_dp, &self.scale()); + let min_size_px = min_area.size_px(); min_max_info.ptMinTrackSize.x = min_size_px.width as i32; min_max_info.ptMinTrackSize.y = min_size_px.height as i32; } @@ -1026,12 +1023,14 @@ impl WindowBuilder { // Probably GetDeviceCaps(..., LOGPIXELSX) is the best to do pre-10 96.0 }; - let mut scale = Scale::from_dpi(dpi, dpi); - let size_px = scale.set_size_dp(self.size); + let scale = Scale::from_dpi(dpi, dpi); + let area = ScaledArea::from_dp(self.size, &scale); + let size_px = area.size_px(); let window = WindowState { hwnd: Cell::new(0 as HWND), - scale: RefCell::new(scale), + scale: Cell::new(scale), + area: Cell::new(area), wndproc: Box::new(wndproc), idle_queue: Default::default(), timers: Arc::new(Mutex::new(TimerSlots::new(1))), @@ -1329,21 +1328,10 @@ impl WindowHandle { pub fn invalidate_rect(&self, rect: Rect) { if let Some(w) = self.state.upgrade() { - let rect = w - .scale - .try_borrow() - .ok() - .map(|scale| util::rect_to_recti(scale::expand_rect(scale.to_px(&rect)))); + let rect = util::rect_to_recti(w.scale.get().to_px(&rect).expand()); let hwnd = w.hwnd.get(); unsafe { - let result = match rect { - Some(rect) => InvalidateRect(hwnd, &rect, FALSE), - None => { - log::warn!("Failed to get scale"); - InvalidateRect(hwnd, null(), FALSE) - } - }; - if result == FALSE { + if InvalidateRect(hwnd, &rect, FALSE) == FALSE { log::warn!( "InvalidateRect failed: {}", Error::Hr(HRESULT_FROM_WIN32(GetLastError())) @@ -1420,7 +1408,7 @@ impl WindowHandle { let hmenu = menu.into_hmenu(); if let Some(w) = self.state.upgrade() { let hwnd = w.hwnd.get(); - let pos = w.scale.borrow().to_px(&pos).round(); + let pos = w.scale.get().to_px(&pos).round(); unsafe { let mut point = POINT { x: pos.x as i32, @@ -1512,14 +1500,12 @@ impl WindowHandle { /// Get the `Scale` of the window. pub fn get_scale(&self) -> Result { - if let Some(state) = self.state.upgrade() { - match state.scale.try_borrow() { - Ok(scale) => Ok(scale.clone()), - Err(_) => Err(BorrowError::new("WindowHandle::get_scale", "scale", false).into()), - } - } else { - Err(ShellError::WindowDropped) - } + Ok(self + .state + .upgrade() + .ok_or(ShellError::WindowDropped)? + .scale + .get()) } /// Allocate a timer slot. diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index a5ee0d24d4..862a12a730 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -20,34 +20,21 @@ use crate::kurbo::{Insets, Line, Point, Rect, Size, Vec2}; const SCALE_TARGET_DPI: f64 = 96.0; -/// Resolution scaling state. +/// Coordinate scaling between pixels and display points. /// -/// This holds the platform DPI, the platform area size in pixels, -/// the logical area size in display points, and the scale factors between them. +/// This holds the platform DPI and the equivalent scale factors. /// /// To translate coordinates between pixels and display points you should use one of the /// helper conversion methods of `Scale` or for manual conversion [`scale_x`] / [`scale_y`]. /// -/// The platform area size in pixels tends to be limited to integers and `Scale` works -/// under that assumption. -/// -/// The logical area size in display points is an unrounded conversion, which means that it is -/// often not limited to integers. This allows for accurate calculations of -/// the platform area pixel boundaries from the logcal area using the scale factors. -/// -/// Even though the logcal area size can be fractional, the integer boundaries of that logical area -/// will still match up with the platform area pixel boundaries as often as the scale factor allows. -/// /// `Scale` is designed for responsive applications, including responding to platform DPI changes. /// The platform DPI can change quickly, e.g. when moving a window from one monitor to another. /// -/// A clone of `Scale` will be stale as soon as the platform area size or the DPI changes. +/// A copy of `Scale` will be stale as soon as the platform DPI changes. /// /// [`scale_x`]: #method.scale_x /// [`scale_y`]: #method.scale_y -// NOTE: Scale does not derive Copy because the data it contains can be short-lived. -// Forcing an explicit clone can help remind people of this, and avoids accidental copies. -#[derive(Clone)] +#[derive(Copy, Clone, Debug)] pub struct Scale { /// The platform reported DPI on the x axis. dpi_x: f64, @@ -57,6 +44,27 @@ pub struct Scale { scale_x: f64, /// The scale factor on the y axis. scale_y: f64, +} + +/// A specific area scaling state. +/// +/// This holds the platform area size in pixels and the logical area size in display points. +/// +/// The platform area size in pixels tends to be limited to integers and `ScaledArea` works +/// under that assumption. +/// +/// The logical area size in display points is an unrounded conversion, which means that it is +/// often not limited to integers. This allows for accurate calculations of +/// the platform area pixel boundaries from the logcal area using a [`Scale`]. +/// +/// Even though the logcal area size can be fractional, the integer boundaries of that logical area +/// will still match up with the platform area pixel boundaries as often as the scale factor allows. +/// +/// A copy of `ScaledArea` will be stale as soon as the platform area size changes. +/// +/// [`Scale`]: struct.Scale.html +#[derive(Copy, Clone, Debug)] +pub struct ScaledArea { /// The size of the scaled area in display points. size_dp: Size, /// The size of the scaled area in pixels. @@ -89,8 +97,6 @@ impl Scale { dpi_y, scale_x: dpi_x / SCALE_TARGET_DPI, scale_y: dpi_y / SCALE_TARGET_DPI, - size_dp: Size::ZERO, - size_px: Size::ZERO, } } @@ -103,37 +109,9 @@ impl Scale { dpi_y: SCALE_TARGET_DPI * scale_y, scale_x, scale_y, - size_dp: Size::ZERO, - size_px: Size::ZERO, } } - /// Set the size of the scaled area in pixels. - /// - /// This updates the internal state and returns the same size in display points. - pub fn set_size_px>(&mut self, size: T) -> Size { - let size = size.into(); - self.size_px = size; - self.size_dp = self.to_dp(&size); - self.size_dp - } - - /// Set the size of the scaled area in pixels via conversion from display points. - /// - /// This updates the internal state and returns the same size in pixels. - /// - /// The calculated size in pixels is rounded away from zero to integers. - /// That means that the scaled area size in display points isn't always the same - /// as the `size` given to this method. To find out the new size in points use [`size_dp`]. - /// - /// [`size_dp`]: #method.size_dp - pub fn set_size_dp>(&mut self, size: T) -> Size { - let size = size.into(); - self.size_px = self.to_px(&size).expand(); - self.size_dp = self.to_dp(&self.size_px); - self.size_px - } - /// Returns the x axis platform DPI associated with this `Scale`. #[inline] pub fn dpi_x(&self) -> f64 { @@ -165,18 +143,6 @@ impl Scale { self.scale_y } - /// Returns the scaled area size in display points. - #[inline] - pub fn size_dp(&self) -> Size { - self.size_dp - } - - /// Returns the scaled area size in pixels. - #[inline] - pub fn size_px(&self) -> Size { - self.size_px - } - /// Converts from display points into pixels, using the x axis scale factor. #[inline] pub fn dp_to_px_x>(&self, x: T) -> f64 { @@ -232,16 +198,6 @@ impl Scale { } } -impl std::fmt::Debug for Scale { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "DPI ({}, {}) => ({}, {}) | {:?} => {:?}", - self.dpi_x, self.dpi_y, self.scale_x, self.scale_y, self.size_px, self.size_dp, - ) - } -} - impl Scalable for Vec2 { /// Converts a `Vec2` from display points into pixels, /// using the x axis scale factor for `x` and the y axis scale factor for `y`. @@ -364,19 +320,36 @@ impl Scalable for Insets { } } -// TODO: Replace usages of this with rect.expand() after kurbo#107 has landed. -#[allow(dead_code)] -#[doc(hidden)] -pub fn expand_rect(rect: Rect) -> Rect { - let (x0, x1) = if rect.x0 < rect.x1 { - (rect.x0.floor(), rect.x1.ceil()) - } else { - (rect.x0.ceil(), rect.x1.floor()) - }; - let (y0, y1) = if rect.y0 < rect.y1 { - (rect.y0.floor(), rect.y1.ceil()) - } else { - (rect.y0.ceil(), rect.y1.floor()) - }; - Rect::new(x0, y0, x1, y1) +impl ScaledArea { + /// Create a new scaled area from pixels. + pub fn from_px>(size: T, scale: &Scale) -> ScaledArea { + let size_px = size.into(); + let size_dp = size_px.to_dp(scale); + ScaledArea { size_dp, size_px } + } + + /// Create a new scaled area from display points. + /// + /// The calculated size in pixels is rounded away from zero to integers. + /// That means that the scaled area size in display points isn't always the same + /// as the `size` given to this function. To find out the new size in points use [`size_dp`]. + /// + /// [`size_dp`]: #method.size_dp + pub fn from_dp>(size: T, scale: &Scale) -> ScaledArea { + let size_px = size.into().to_px(scale).expand(); + let size_dp = size_px.to_dp(scale); + ScaledArea { size_dp, size_px } + } + + /// Returns the scaled area size in display points. + #[inline] + pub fn size_dp(&self) -> Size { + self.size_dp + } + + /// Returns the scaled area size in pixels. + #[inline] + pub fn size_px(&self) -> Size { + self.size_px + } } diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index 9f8087b87c..92a26aca2b 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -202,8 +202,8 @@ impl WindowHandle { /// Get the [`Scale`] information of the window. /// - /// The returned [`Scale`] is a clone and thus its information will be stale after the window - /// is resized or the platform DPI changes. A correctly behaving application should consider + /// The returned [`Scale`] is a copy and thus its information will be stale after + /// the platform DPI changes. A correctly behaving application should consider /// the lifetime of this [`Scale`] brief, limited to approximately a single event cycle. /// /// [`Scale`]: struct.Scale.html From dfe28d85e8905c55f772301e9f4d717f8229e2d5 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 16 May 2020 16:25:38 +0300 Subject: [PATCH 22/29] Add ScaledArea support for web. --- druid-shell/src/platform/web/window.rs | 64 +++++++++++++------------- druid-shell/src/scale.rs | 1 + 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/druid-shell/src/platform/web/window.rs b/druid-shell/src/platform/web/window.rs index 6406db1aea..c91cf39df4 100644 --- a/druid-shell/src/platform/web/window.rs +++ b/druid-shell/src/platform/web/window.rs @@ -35,8 +35,8 @@ use super::keycodes::key_to_text; use super::menu::Menu; use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; -use crate::error::{BorrowError, Error as ShellError}; -use crate::scale::Scale; +use crate::error::Error as ShellError; +use crate::scale::{Scale, ScaledArea}; use crate::keyboard; use crate::keycodes::KeyCode; @@ -82,7 +82,8 @@ enum IdleKind { } struct WindowState { - scale: RefCell, + scale: Cell, + area: Cell, idle_queue: Arc>>, handler: RefCell>, window: web_sys::Window, @@ -132,6 +133,20 @@ impl WindowState { let dpr = w.device_pixel_ratio(); (width, height, dpr) } + + /// Updates the canvas size and scale factor and returns `Scale` and `ScaledArea`. + fn update_scale_and_area(&self) -> (Scale, ScaledArea) { + let (css_width, css_height, dpr) = self.get_window_size_and_dpr(); + let scale = Scale::from_scale(dpr, dpr); + let area = ScaledArea::from_dp(Size::new(css_width, css_height), &scale); + let size_px = area.size_px(); + self.canvas.set_width(size_px.width as u32); + self.canvas.set_height(size_px.height as u32); + let _ = self.context.scale(scale.scale_x(), scale.scale_y()); + self.scale.set(scale); + self.area.set(area); + (scale, area) + } } fn setup_mouse_down_callback(ws: &Rc) { @@ -202,7 +217,7 @@ fn setup_scroll_callback(ws: &Rc) { web_sys::WheelEvent::DOM_DELTA_PIXEL => Vec2::new(dx, dy), web_sys::WheelEvent::DOM_DELTA_LINE => Vec2::new(35.0 * dx, 35.0 * dy), web_sys::WheelEvent::DOM_DELTA_PAGE => { - let size_dp = state.scale.borrow().size_dp(); + let size_dp = state.area.get().size_dp(); Vec2::new(size_dp.width * dx, size_dp.height * dy) } _ => { @@ -227,20 +242,8 @@ fn setup_scroll_callback(ws: &Rc) { fn setup_resize_callback(ws: &Rc) { let state = ws.clone(); register_window_event_listener(ws, "resize", move |_: web_sys::UiEvent| { - let (css_width, css_height, dpr) = state.get_window_size_and_dpr(); - let size_dp = state.scale.try_borrow_mut().ok().map(|mut scale| { - *scale = Scale::from_scale(dpr, dpr); - let size_px = scale.set_size_dp(Size::new(css_width, css_height)); - state.canvas.set_width(size_px.width as u32); - state.canvas.set_height(size_px.height as u32); - let _ = state.context.scale(scale.scale_x(), scale.scale_y()); - scale.size_dp() - }); - if let Some(size_dp) = size_dp { - state.handler.borrow_mut().size(size_dp); - } else { - log::error!("Skipped resize event because couldn't borrow scale"); - } + let (_, area) = state.update_scale_and_area(); + state.handler.borrow_mut().size(area.size_dp()); }); } @@ -366,26 +369,28 @@ impl WindowBuilder { .dyn_into::() .map_err(|_| Error::JsCast)?; // Create the Scale for resolution scaling - let mut scale = { + let scale = { let dpr = window.device_pixel_ratio(); Scale::from_scale(dpr, dpr) }; - let size_px = { + let area = { // The initial size in display points isn't necessarily the final size in display points let size_dp = Size::new(canvas.offset_width() as f64, canvas.offset_height() as f64); - scale.set_size_dp(size_dp) + ScaledArea::from_dp(size_dp, &scale) }; + let size_px = area.size_px(); canvas.set_width(size_px.width as u32); canvas.set_height(size_px.height as u32); let _ = context.scale(scale.scale_x(), scale.scale_y()); - let size_dp = scale.size_dp(); + let size_dp = area.size_dp(); set_cursor(&canvas, &self.cursor); let handler = self.handler.unwrap(); let window = Rc::new(WindowState { - scale: RefCell::new(scale), + scale: Cell::new(scale), + area: Cell::new(area), idle_queue: Default::default(), handler: RefCell::new(handler), window, @@ -447,11 +452,7 @@ impl WindowHandle { pub fn invalidate(&self) { if let Some(s) = self.0.upgrade() { - if let Ok(scale) = s.scale.try_borrow() { - s.invalid_rect.set(scale.size_dp().to_rect()); - } else { - log::error!("Failed to get scale"); - } + s.invalid_rect.set(s.area.get().size_dp().to_rect()); } self.render_soon(); } @@ -550,13 +551,12 @@ impl WindowHandle { /// Get the `Scale` of the window. pub fn get_scale(&self) -> Result { - self.0 + Ok(self + .0 .upgrade() .ok_or(ShellError::WindowDropped)? .scale - .try_borrow() - .map_err(|_| BorrowError::new("WindowHandle::get_scale", "scale", false).into()) - .map(|scale| scale.clone()) + .get()) } pub fn set_menu(&self, _menu: Menu) { diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index 862a12a730..21400999a7 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -322,6 +322,7 @@ impl Scalable for Insets { impl ScaledArea { /// Create a new scaled area from pixels. + #[allow(dead_code)] pub fn from_px>(size: T, scale: &Scale) -> ScaledArea { let size_px = size.into(); let size_dp = size_px.to_dp(scale); From 048ac50503a9ccb9adadee8432fdb4771e2e8c41 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 16 May 2020 16:56:26 +0300 Subject: [PATCH 23/29] Add GTK support for ScaledArea. --- druid-shell/src/platform/gtk/window.rs | 360 ++++++++++++------------- 1 file changed, 172 insertions(+), 188 deletions(-) diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 3db3cf00e6..80a4f2b5bc 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -15,7 +15,7 @@ //! GTK window creation and management. use std::any::Any; -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::convert::TryFrom; use std::ffi::c_void; use std::ffi::OsString; @@ -35,10 +35,10 @@ use crate::piet::{Piet, RenderContext}; use crate::common_util::IdleCallback; use crate::dialog::{FileDialogOptions, FileDialogType, FileInfo}; -use crate::error::{BorrowError, Error as ShellError}; +use crate::error::Error as ShellError; use crate::keyboard; use crate::mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; -use crate::scale::Scale; +use crate::scale::{Scale, ScaledArea}; use crate::window::{IdleToken, Text, TimerToken, WinHandler}; use super::application::Application; @@ -107,7 +107,8 @@ enum IdleKind { pub(crate) struct WindowState { window: ApplicationWindow, - scale: RefCell, + scale: Cell, + area: Cell, drawing_area: DrawingArea, pub(crate) handler: RefCell>, idle_queue: Arc>>, @@ -172,8 +173,9 @@ impl WindowBuilder { .get_display() .map(|c| c.get_default_screen().get_resolution() as f64) .unwrap_or(96.0); - let mut scale = Scale::from_dpi(dpi, dpi); - let size_px = scale.set_size_dp(self.size); + let scale = Scale::from_dpi(dpi, dpi); + let area = ScaledArea::from_dp(self.size, &scale); + let size_px = area.size_px(); window.set_default_size(size_px.width as i32, size_px.height as i32); @@ -186,7 +188,8 @@ impl WindowBuilder { let win_state = Arc::new(WindowState { window, - scale: RefCell::new(scale), + scale: Cell::new(scale), + area: Cell::new(area), drawing_area, handler: RefCell::new(handler), idle_queue: Arc::new(Mutex::new(vec![])), @@ -236,62 +239,62 @@ impl WindowBuilder { }); // Set the minimum size - if let Some(size_dp) = self.min_size { - let mut scale = Scale::from_dpi(dpi, dpi); - let size_px = scale.set_size_dp(size_dp); + if let Some(min_size_dp) = self.min_size { + let min_area = ScaledArea::from_dp(min_size_dp, &scale); + let min_size_px = min_area.size_px(); win_state .drawing_area - .set_size_request(size_px.width as i32, size_px.height as i32); + .set_size_request(min_size_px.width as i32, min_size_px.height as i32); } win_state.drawing_area.connect_draw(clone!(handle => move |widget, context| { if let Some(state) = handle.state.upgrade() { - if let Ok(mut scale) = state.scale.try_borrow_mut() { - // Check if the GTK reported DPI has changed, - // so that we can change our scale factor without restarting the application. - if let Some(dpi) = state.window.get_window() - .map(|w| w.get_display().get_default_screen().get_resolution()) { - if !scale.dpi_approx_eq(dpi, dpi) { - // This new Scale will also ensure a window size event for the handler, - // because it defaults to zero size. - *scale = Scale::from_dpi(dpi, dpi); - } + let mut scale = state.scale.get(); + let mut scale_changed = false; + // Check if the GTK reported DPI has changed, + // so that we can change our scale factor without restarting the application. + if let Some(dpi) = state.window.get_window() + .map(|w| w.get_display().get_default_screen().get_resolution()) { + if !scale.dpi_approx_eq(dpi, dpi) { + scale = Scale::from_dpi(dpi, dpi); + state.scale.set(scale); + scale_changed = true; } + } - // Check if the size of the window has changed - let extents = widget.get_allocation(); - let size_px = Size::new(extents.width as f64, extents.height as f64); - if scale.size_px() != size_px { - let size_dp = scale.set_size_px(size_px); - if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() { - handler_borrow.size(size_dp); - } else { - log::warn!("Failed to inform the handler of a resize because it was already borrowed"); - } + // Check if the size of the window has changed + let extents = widget.get_allocation(); + let size_px = Size::new(extents.width as f64, extents.height as f64); + if scale_changed || state.area.get().size_px() != size_px { + let area = ScaledArea::from_px(size_px, &scale); + let size_dp = area.size_dp(); + state.area.set(area); + if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() { + handler_borrow.size(size_dp); + } else { + log::warn!("Failed to inform the handler of a resize because it was already borrowed"); } + } - if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() { - // For some reason piet needs a mutable context, so give it one I guess. - let mut context = context.clone(); - context.scale(scale.scale_x(), scale.scale_y()); - let (x0, y0, x1, y1) = context.clip_extents(); - let invalid_rect = Rect::new(x0, y0, x1, y1); - - let mut piet_context = Piet::new(&mut context); - let anim = handler_borrow - .paint(&mut piet_context, invalid_rect); - if let Err(e) = piet_context.finish() { - eprintln!("piet error on render: {:?}", e); - } + if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() { + // For some reason piet needs a mutable context, so give it one I guess. + let mut context = context.clone(); + context.scale(scale.scale_x(), scale.scale_y()); + let (x0, y0, x1, y1) = context.clip_extents(); + let invalid_rect = Rect::new(x0, y0, x1, y1); + + let mut piet_context = Piet::new(&mut context); + let anim = handler_borrow + .paint(&mut piet_context, invalid_rect); + if let Err(e) = piet_context.finish() { + eprintln!("piet error on render: {:?}", e); + } - if anim { - widget.queue_draw(); - } - } else { - log::warn!("Drawing was skipped because the handler was already borrowed"); + if anim { + widget.queue_draw(); } } else { - log::warn!("Drawing was skipped because the scale was already borrowed"); + log::warn!("Drawing was skipped because the handler was already borrowed"); } } @@ -300,27 +303,24 @@ impl WindowBuilder { win_state.drawing_area.connect_button_press_event(clone!(handle => move |_widget, event| { if let Some(state) = handle.state.upgrade() { - if let Ok(scale) = state.scale.try_borrow() { - if let Ok(mut handler) = state.handler.try_borrow_mut() { - if let Some(button) = get_mouse_button(event.get_button()) { - let button_state = event.get_state(); - handler.mouse_down( - &MouseEvent { - pos: scale.to_dp(&Point::from(event.get_position())), - buttons: get_mouse_buttons_from_modifiers(button_state).with(button), - mods: get_modifiers(button_state), - count: get_mouse_click_count(event.get_event_type()), - focus: false, - button, - wheel_delta: Vec2::ZERO - }, - ); - } - } else { - log::warn!("GTK event was dropped because the handler was already borrowed"); + if let Ok(mut handler) = state.handler.try_borrow_mut() { + if let Some(button) = get_mouse_button(event.get_button()) { + let scale = state.scale.get(); + let button_state = event.get_state(); + handler.mouse_down( + &MouseEvent { + pos: scale.to_dp(&Point::from(event.get_position())), + buttons: get_mouse_buttons_from_modifiers(button_state).with(button), + mods: get_modifiers(button_state), + count: get_mouse_click_count(event.get_event_type()), + focus: false, + button, + wheel_delta: Vec2::ZERO + }, + ); } } else { - log::warn!("GTK event was dropped because the scale was already borrowed"); + log::warn!("GTK event was dropped because the handler was already borrowed"); } } @@ -329,27 +329,24 @@ impl WindowBuilder { win_state.drawing_area.connect_button_release_event(clone!(handle => move |_widget, event| { if let Some(state) = handle.state.upgrade() { - if let Ok(scale) = state.scale.try_borrow() { - if let Ok(mut handler) = state.handler.try_borrow_mut() { - if let Some(button) = get_mouse_button(event.get_button()) { - let button_state = event.get_state(); - handler.mouse_up( - &MouseEvent { - pos: scale.to_dp(&Point::from(event.get_position())), - buttons: get_mouse_buttons_from_modifiers(button_state).without(button), - mods: get_modifiers(button_state), - count: 0, - focus: false, - button, - wheel_delta: Vec2::ZERO - }, - ); - } - } else { - log::warn!("GTK event was dropped because the handler was already borrowed"); + if let Ok(mut handler) = state.handler.try_borrow_mut() { + if let Some(button) = get_mouse_button(event.get_button()) { + let scale = state.scale.get(); + let button_state = event.get_state(); + handler.mouse_up( + &MouseEvent { + pos: scale.to_dp(&Point::from(event.get_position())), + buttons: get_mouse_buttons_from_modifiers(button_state).without(button), + mods: get_modifiers(button_state), + count: 0, + focus: false, + button, + wheel_delta: Vec2::ZERO + }, + ); } } else { - log::warn!("GTK event was dropped because the scale was already borrowed"); + log::warn!("GTK event was dropped because the handler was already borrowed"); } } @@ -358,25 +355,22 @@ impl WindowBuilder { win_state.drawing_area.connect_motion_notify_event(clone!(handle => move |_widget, motion| { if let Some(state) = handle.state.upgrade() { - if let Ok(scale) = state.scale.try_borrow() { - let motion_state = motion.get_state(); - let mouse_event = MouseEvent { - pos: scale.to_dp(&Point::from(motion.get_position())), - buttons: get_mouse_buttons_from_modifiers(motion_state), - mods: get_modifiers(motion_state), - count: 0, - focus: false, - button: MouseButton::None, - wheel_delta: Vec2::ZERO - }; + let scale = state.scale.get(); + let motion_state = motion.get_state(); + let mouse_event = MouseEvent { + pos: scale.to_dp(&Point::from(motion.get_position())), + buttons: get_mouse_buttons_from_modifiers(motion_state), + mods: get_modifiers(motion_state), + count: 0, + focus: false, + button: MouseButton::None, + wheel_delta: Vec2::ZERO + }; - if let Ok(mut handler) = state.handler.try_borrow_mut() { - handler.mouse_move(&mouse_event); - } else { - log::warn!("GTK event was dropped because the handler was already borrowed"); - } + if let Ok(mut handler) = state.handler.try_borrow_mut() { + handler.mouse_move(&mouse_event); } else { - log::warn!("GTK event was dropped because the scale was already borrowed"); + log::warn!("GTK event was dropped because the handler was already borrowed"); } } @@ -385,25 +379,22 @@ impl WindowBuilder { win_state.drawing_area.connect_leave_notify_event(clone!(handle => move |_widget, crossing| { if let Some(state) = handle.state.upgrade() { - if let Ok(scale) = state.scale.try_borrow() { - let crossing_state = crossing.get_state(); - let mouse_event = MouseEvent { - pos: scale.to_dp(&Point::from(crossing.get_position())), - buttons: get_mouse_buttons_from_modifiers(crossing_state), - mods: get_modifiers(crossing_state), - count: 0, - focus: false, - button: MouseButton::None, - wheel_delta: Vec2::ZERO - }; + let scale = state.scale.get(); + let crossing_state = crossing.get_state(); + let mouse_event = MouseEvent { + pos: scale.to_dp(&Point::from(crossing.get_position())), + buttons: get_mouse_buttons_from_modifiers(crossing_state), + mods: get_modifiers(crossing_state), + count: 0, + focus: false, + button: MouseButton::None, + wheel_delta: Vec2::ZERO + }; - if let Ok(mut handler) = state.handler.try_borrow_mut() { - handler.mouse_move(&mouse_event); - } else { - log::warn!("GTK event was dropped because the handler was already borrowed"); - } + if let Ok(mut handler) = state.handler.try_borrow_mut() { + handler.mouse_move(&mouse_event); } else { - log::warn!("GTK event was dropped because the scale was already borrowed"); + log::warn!("GTK event was dropped because the handler was already borrowed"); } } @@ -412,57 +403,54 @@ impl WindowBuilder { win_state.drawing_area.connect_scroll_event(clone!(handle => move |_widget, scroll| { if let Some(state) = handle.state.upgrade() { - if let Ok(scale) = state.scale.try_borrow() { - let mods = get_modifiers(scroll.get_state()); - - // The magic "120"s are from Microsoft's documentation for WM_MOUSEWHEEL. - // They claim that one "tick" on a scroll wheel should be 120 units. - let wheel_delta = match scroll.get_direction() { - ScrollDirection::Up if mods.shift => Some(Vec2::new(-120.0, 0.0)), - ScrollDirection::Up => Some(Vec2::new(0.0, -120.0)), - ScrollDirection::Down if mods.shift => Some(Vec2::new(120.0, 0.0)), - ScrollDirection::Down => Some(Vec2::new(0.0, 120.0)), - ScrollDirection::Left => Some(Vec2::new(-120.0, 0.0)), - ScrollDirection::Right => Some(Vec2::new(120.0, 0.0)), - ScrollDirection::Smooth => { - //TODO: Look at how gtk's scroll containers implements it - let (mut delta_x, mut delta_y) = scroll.get_delta(); - delta_x *= 120.; - delta_y *= 120.; - if mods.shift { - delta_x += delta_y; - delta_y = 0.; - } - Some(Vec2::new(delta_x, delta_y)) - } - e => { - eprintln!( - "Warning: the Druid widget got some whacky scroll direction {:?}", - e - ); - None + let scale = state.scale.get(); + let mods = get_modifiers(scroll.get_state()); + + // The magic "120"s are from Microsoft's documentation for WM_MOUSEWHEEL. + // They claim that one "tick" on a scroll wheel should be 120 units. + let wheel_delta = match scroll.get_direction() { + ScrollDirection::Up if mods.shift => Some(Vec2::new(-120.0, 0.0)), + ScrollDirection::Up => Some(Vec2::new(0.0, -120.0)), + ScrollDirection::Down if mods.shift => Some(Vec2::new(120.0, 0.0)), + ScrollDirection::Down => Some(Vec2::new(0.0, 120.0)), + ScrollDirection::Left => Some(Vec2::new(-120.0, 0.0)), + ScrollDirection::Right => Some(Vec2::new(120.0, 0.0)), + ScrollDirection::Smooth => { + //TODO: Look at how gtk's scroll containers implements it + let (mut delta_x, mut delta_y) = scroll.get_delta(); + delta_x *= 120.; + delta_y *= 120.; + if mods.shift { + delta_x += delta_y; + delta_y = 0.; } + Some(Vec2::new(delta_x, delta_y)) + } + e => { + eprintln!( + "Warning: the Druid widget got some whacky scroll direction {:?}", + e + ); + None + } + }; + + if let Some(wheel_delta) = wheel_delta { + let mouse_event = MouseEvent { + pos: scale.to_dp(&Point::from(scroll.get_position())), + buttons: get_mouse_buttons_from_modifiers(scroll.get_state()), + mods, + count: 0, + focus: false, + button: MouseButton::None, + wheel_delta }; - if let Some(wheel_delta) = wheel_delta { - let mouse_event = MouseEvent { - pos: scale.to_dp(&Point::from(scroll.get_position())), - buttons: get_mouse_buttons_from_modifiers(scroll.get_state()), - mods, - count: 0, - focus: false, - button: MouseButton::None, - wheel_delta - }; - - if let Ok(mut handler) = state.handler.try_borrow_mut() { - handler.wheel(&mouse_event); - } else { - log::info!("GTK event was dropped because the handler was already borrowed"); - } + if let Ok(mut handler) = state.handler.try_borrow_mut() { + handler.wheel(&mouse_event); + } else { + log::info!("GTK event was dropped because the handler was already borrowed"); } - } else { - log::warn!("GTK event was dropped because the scale was already borrowed"); } } @@ -568,17 +556,15 @@ impl WindowHandle { /// Request invalidation of one rectangle, which is given relative to the drawing area. pub fn invalidate_rect(&self, rect: Rect) { if let Some(state) = self.state.upgrade() { - if let Ok(scale) = state.scale.try_borrow() { - // GTK takes rects with non-negative integer width/height. - let r = scale.to_px(&rect.abs()).expand(); - let origin = state.drawing_area.get_allocation(); - state.window.queue_draw_area( - r.x0 as i32 + origin.x, - r.y0 as i32 + origin.y, - r.width() as i32, - r.height() as i32, - ); - } + // GTK takes rects with non-negative integer width/height. + let r = state.scale.get().to_px(&rect.abs()).expand(); + let origin = state.drawing_area.get_allocation(); + state.window.queue_draw_area( + r.x0 as i32 + origin.x, + r.y0 as i32 + origin.y, + r.width() as i32, + r.height() as i32, + ); } } @@ -643,14 +629,12 @@ impl WindowHandle { /// Get the `Scale` of the window. pub fn get_scale(&self) -> Result { - if let Some(state) = self.state.upgrade() { - match state.scale.try_borrow() { - Ok(scale) => Ok(scale.clone()), - Err(_) => Err(BorrowError::new("WindowHandle::get_scale", "scale", false).into()), - } - } else { - Err(ShellError::WindowDropped) - } + Ok(self + .state + .upgrade() + .ok_or(ShellError::WindowDropped)? + .scale + .get()) } pub fn set_menu(&self, menu: Menu) { From 04f2f5f960f71760beb5caa4ef36bd5b0f63a648 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 16 May 2020 17:04:55 +0300 Subject: [PATCH 24/29] Add default implementations to Scale and ScaledArea. --- druid-shell/src/lib.rs | 2 +- druid-shell/src/scale.rs | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/druid-shell/src/lib.rs b/druid-shell/src/lib.rs index dcc1503c8c..c7ac1e8bbd 100644 --- a/druid-shell/src/lib.rs +++ b/druid-shell/src/lib.rs @@ -49,7 +49,7 @@ pub use keyboard::{KeyEvent, KeyModifiers}; pub use keycodes::KeyCode; pub use menu::Menu; pub use mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; -pub use scale::Scale; +pub use scale::{Scale, ScaledArea}; pub use window::{ IdleHandle, IdleToken, Text, TimerToken, WinHandler, WindowBuilder, WindowHandle, }; diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index 21400999a7..592b1f5e77 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -87,6 +87,17 @@ pub trait Scalable { fn to_dp(&self, scale: &Scale) -> Self; } +impl Default for Scale { + fn default() -> Scale { + Scale { + dpi_x: SCALE_TARGET_DPI, + dpi_y: SCALE_TARGET_DPI, + scale_x: 1.0, + scale_y: 1.0, + } + } +} + impl Scale { /// Create a new `Scale` state based on the specified DPIs. /// @@ -320,9 +331,17 @@ impl Scalable for Insets { } } +impl Default for ScaledArea { + fn default() -> ScaledArea { + ScaledArea { + size_dp: Size::ZERO, + size_px: Size::ZERO, + } + } +} + impl ScaledArea { /// Create a new scaled area from pixels. - #[allow(dead_code)] pub fn from_px>(size: T, scale: &Scale) -> ScaledArea { let size_px = size.into(); let size_dp = size_px.to_dp(scale); From bb7fe5c90744aa2acba2177794b652ec2708d3c3 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 16 May 2020 18:12:30 +0300 Subject: [PATCH 25/29] Add a scale method to WinHandler. --- druid-shell/src/platform/gtk/window.rs | 6 ++++++ druid-shell/src/platform/mac/window.rs | 1 + druid-shell/src/platform/web/window.rs | 7 +++++-- druid-shell/src/platform/windows/window.rs | 6 ++++++ druid-shell/src/platform/x11/window.rs | 1 + druid-shell/src/scale.rs | 4 ++-- druid-shell/src/window.rs | 8 ++++++++ druid/src/data.rs | 7 +++++++ druid/src/env.rs | 2 +- druid/src/win_handler.rs | 6 +++++- 10 files changed, 42 insertions(+), 6 deletions(-) diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 80a4f2b5bc..4b2a798d72 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -259,6 +259,11 @@ impl WindowBuilder { scale = Scale::from_dpi(dpi, dpi); state.scale.set(scale); scale_changed = true; + if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() { + handler_borrow.scale(scale); + } else { + log::warn!("Failed to inform the handler of scale change because it was already borrowed"); + } } } @@ -508,6 +513,7 @@ impl WindowBuilder { let mut handler = win_state.handler.borrow_mut(); handler.connect(&handle.clone().into()); + handler.scale(scale); handler.size(self.size); Ok(handle) diff --git a/druid-shell/src/platform/mac/window.rs b/druid-shell/src/platform/mac/window.rs index a036ff2a28..3a6e8f00c6 100644 --- a/druid-shell/src/platform/mac/window.rs +++ b/druid-shell/src/platform/mac/window.rs @@ -203,6 +203,7 @@ impl WindowBuilder { idle_queue, }; (*view_state).handler.connect(&handle.clone().into()); + (*view_state).handler.scale(Scale::default()); (*view_state) .handler .size(Size::new(frame.size.width, frame.size.height)); diff --git a/druid-shell/src/platform/web/window.rs b/druid-shell/src/platform/web/window.rs index c91cf39df4..6b02375695 100644 --- a/druid-shell/src/platform/web/window.rs +++ b/druid-shell/src/platform/web/window.rs @@ -242,7 +242,9 @@ fn setup_scroll_callback(ws: &Rc) { fn setup_resize_callback(ws: &Rc) { let state = ws.clone(); register_window_event_listener(ws, "resize", move |_: web_sys::UiEvent| { - let (_, area) = state.update_scale_and_area(); + let (scale, area) = state.update_scale_and_area(); + // TODO: For performance, only call the handler when these values actually changed. + state.handler.borrow_mut().scale(scale); state.handler.borrow_mut().size(area.size_dp()); }); } @@ -401,10 +403,11 @@ impl WindowBuilder { setup_web_callbacks(&window); - // Register the size with the window handler. + // Register the scale & size with the window handler. let wh = window.clone(); window .request_animation_frame(move || { + wh.handler.borrow_mut().scale(scale); wh.handler.borrow_mut().size(size_dp); }) .expect("Failed to request animation frame"); diff --git a/druid-shell/src/platform/windows/window.rs b/druid-shell/src/platform/windows/window.rs index e0aa4c89be..23854ef19c 100644 --- a/druid-shell/src/platform/windows/window.rs +++ b/druid-shell/src/platform/windows/window.rs @@ -399,6 +399,12 @@ impl WndProc for MyWndProc { fn connect(&self, handle: &WindowHandle, state: WndState) { *self.handle.borrow_mut() = handle.clone(); *self.state.borrow_mut() = Some(state); + self.state + .borrow_mut() + .as_mut() + .unwrap() + .handler + .scale(self.scale()); } fn cleanup(&self, hwnd: HWND) { diff --git a/druid-shell/src/platform/x11/window.rs b/druid-shell/src/platform/x11/window.rs index cecac22a3a..f77b6e25d1 100644 --- a/druid-shell/src/platform/x11/window.rs +++ b/druid-shell/src/platform/x11/window.rs @@ -294,6 +294,7 @@ impl Window { Ok(mut handler) => { let size = self.size()?; handler.connect(&handle.into()); + handler.scale(Scale::default()); handler.size(size); Ok(()) } diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index 592b1f5e77..2984567c96 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -34,7 +34,7 @@ const SCALE_TARGET_DPI: f64 = 96.0; /// /// [`scale_x`]: #method.scale_x /// [`scale_y`]: #method.scale_y -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, PartialEq, Debug)] pub struct Scale { /// The platform reported DPI on the x axis. dpi_x: f64, @@ -63,7 +63,7 @@ pub struct Scale { /// A copy of `ScaledArea` will be stale as soon as the platform area size changes. /// /// [`Scale`]: struct.Scale.html -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, PartialEq, Debug)] pub struct ScaledArea { /// The size of the scaled area in display points. size_dp: Size, diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index 92a26aca2b..3b250313d9 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -304,6 +304,14 @@ pub trait WinHandler { #[allow(unused_variables)] fn size(&mut self, size: Size) {} + /// Called when the scale of the window has changed. + /// + /// This is always called before the accompanying [`size`]. + /// + /// [`size`]: #method.size + #[allow(unused_variables)] + fn scale(&mut self, scale: Scale) {} + /// Request the handler to paint the window contents. Return value /// indicates whether window is animating, i.e. whether another paint /// should be scheduled for the next animation frame. `invalid_rect` is the diff --git a/druid/src/data.rs b/druid/src/data.rs index b4d17d77d2..a4d0ca9a4e 100644 --- a/druid/src/data.rs +++ b/druid/src/data.rs @@ -19,6 +19,7 @@ use std::sync::Arc; use crate::kurbo::{self, ParamCurve}; use crate::piet; +use crate::shell::Scale; pub use druid_derive::Data; @@ -244,6 +245,12 @@ impl Data for (T0, T } } +impl Data for Scale { + fn same(&self, other: &Self) -> bool { + self == other + } +} + impl Data for kurbo::Point { fn same(&self, other: &Self) -> bool { self.x.same(&other.x) && self.y.same(&other.y) diff --git a/druid/src/env.rs b/druid/src/env.rs index 9e265f27ba..1838da06a6 100644 --- a/druid/src/env.rs +++ b/druid/src/env.rs @@ -88,7 +88,7 @@ struct EnvImpl { /// [`Env`]: struct.Env.html pub struct Key { key: &'static str, - value_type: PhantomData, + value_type: PhantomData<*const T>, } // we could do some serious deriving here: the set of types that can be stored diff --git a/druid/src/win_handler.rs b/druid/src/win_handler.rs index d9cbe8a81b..6205d79fb5 100644 --- a/druid/src/win_handler.rs +++ b/druid/src/win_handler.rs @@ -22,7 +22,7 @@ use std::rc::Rc; use crate::kurbo::{Rect, Size}; use crate::piet::Piet; use crate::shell::{ - Application, FileDialogOptions, IdleToken, MouseEvent, WinHandler, WindowHandle, + Application, FileDialogOptions, IdleToken, MouseEvent, Scale, WinHandler, WindowHandle, }; use crate::app_delegate::{AppDelegate, DelegateCtx}; @@ -653,6 +653,10 @@ impl WinHandler for DruidHandler { self.app_state.do_window_event(event, self.window_id); } + fn scale(&mut self, _scale: Scale) { + // TODO: Do something with the scale + } + fn command(&mut self, id: u32) { self.app_state.handle_system_cmd(id, Some(self.window_id)); } From 516df21cc1f97309468f86bc3ad1cc75e7834110 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 16 May 2020 18:13:50 +0300 Subject: [PATCH 26/29] Add changelog entry. --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef07952907..fa6e5f6972 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,9 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i - `MouseButtons` to `MouseEvent` to track which buttons are being held down during an event. ([#843] by [@xStrom]) - `Env` and `Key` gained methods for inspecting an `Env` at runtime ([#880] by [@Zarenor]) - `UpdateCtx::request_timer` and `UpdateCtx::request_anim_frame`. ([#898] by [@finnerale]) + - `LifeCycleCtx::request_timer`. ([#954] by [@xStrom]) +- `scale` method to `WinHandler`. ([#904] by [@xStrom]) - `UpdateCtx::size` and `LifeCycleCtx::size`. ([#917] by [@jneem]) - `WidgetExt::debug_widget_id`, for displaying widget ids on hover. ([#876] by [@cmyr]) - `im` feature, with `Data` support for the [`im` crate](https://docs.rs/im/) collections. ([#924] by [@cmyr]) From 37a2cc57d8582549b2a78c5d8eea8f6d6ef635f5 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 16 May 2020 18:45:08 +0300 Subject: [PATCH 27/29] Add more documentation about display points. --- druid-shell/src/mouse.rs | 4 +++- druid-shell/src/scale.rs | 13 +++++++++++++ druid-shell/src/window.rs | 18 +++++++++++++----- druid/src/app.rs | 6 ++++-- druid/src/lib.rs | 2 +- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/druid-shell/src/mouse.rs b/druid-shell/src/mouse.rs index 674c3cf3a5..9727e8fd3c 100644 --- a/druid-shell/src/mouse.rs +++ b/druid-shell/src/mouse.rs @@ -24,7 +24,9 @@ use crate::keyboard::KeyModifiers; /// receiving a move event before another mouse event. #[derive(Debug, Clone, PartialEq)] pub struct MouseEvent { - /// The location of the mouse in display points in relation to the current window. + /// The location of the mouse in [display points] in relation to the current window. + /// + /// [display points]: struct.Scale.html pub pos: Point, /// Mouse buttons being held down during a move or after a click event. /// Thus it will contain the `button` that triggered a mouse-down event, diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index 2984567c96..028cf36d63 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -24,6 +24,18 @@ const SCALE_TARGET_DPI: f64 = 96.0; /// /// This holds the platform DPI and the equivalent scale factors. /// +/// ## Pixels and Display Points +/// +/// A pixel (**px**) represents the smallest controllable area of color on the platform. +/// A display point (**dp**) is a resolution independent logical unit. +/// When developing your application you should primarily be thinking in display points. +/// These display points will be automatically converted into pixels under the hood. +/// One pixel is equal to one display point when the platform scale factor is `1.0`. +/// +/// Read more about pixels and display points [in the druid book]. +/// +/// ## Converting with `Scale` +/// /// To translate coordinates between pixels and display points you should use one of the /// helper conversion methods of `Scale` or for manual conversion [`scale_x`] / [`scale_y`]. /// @@ -34,6 +46,7 @@ const SCALE_TARGET_DPI: f64 = 96.0; /// /// [`scale_x`]: #method.scale_x /// [`scale_y`]: #method.scale_y +/// [in the druid book]: https://xi-editor.io/druid/resolution_independence.html #[derive(Copy, Clone, PartialEq, Debug)] pub struct Scale { /// The platform reported DPI on the x axis. diff --git a/druid-shell/src/window.rs b/druid-shell/src/window.rs index 3b250313d9..b8a33a9397 100644 --- a/druid-shell/src/window.rs +++ b/druid-shell/src/window.rs @@ -233,7 +233,7 @@ impl WindowBuilder { self.0.set_handler(handler) } - /// Set the window's initial drawing area size in display points. + /// Set the window's initial drawing area size in [display points]. /// /// The actual window size in pixels will depend on the platform DPI settings. /// @@ -242,16 +242,19 @@ impl WindowBuilder { /// To know the actual size of the window you should handle the [`WinHandler::size`] method. /// /// [`WinHandler::size`]: trait.WinHandler.html#method.size + /// [display points]: struct.Scale.html pub fn set_size(&mut self, size: Size) { self.0.set_size(size) } - /// Set the window's minimum drawing area size in display points. + /// Set the window's minimum drawing area size in [display points]. /// /// The actual minimum window size in pixels will depend on the platform DPI settings. /// /// This should be considered a request to the platform to set the minimum size of the window. /// The platform might increase the size a tiny bit due to DPI. + /// + /// [display points]: struct.Scale.html pub fn set_min_size(&mut self, size: Size) { self.0.set_min_size(size) } @@ -300,14 +303,17 @@ pub trait WinHandler { /// Called when the size of the window has changed. /// - /// The `size` parameter is the new size in display points. + /// The `size` parameter is the new size in [display points]. + /// + /// [display points]: struct.Scale.html #[allow(unused_variables)] fn size(&mut self, size: Size) {} - /// Called when the scale of the window has changed. + /// Called when the [`Scale`] of the window has changed. /// /// This is always called before the accompanying [`size`]. /// + /// [`Scale`]: struct.Scale.html /// [`size`]: #method.size #[allow(unused_variables)] fn scale(&mut self, scale: Scale) {} @@ -315,7 +321,9 @@ pub trait WinHandler { /// Request the handler to paint the window contents. Return value /// indicates whether window is animating, i.e. whether another paint /// should be scheduled for the next animation frame. `invalid_rect` is the - /// rectangle in display points that needs to be repainted. + /// rectangle in [display points] that needs to be repainted. + /// + /// [display points]: struct.Scale.html fn paint(&mut self, piet: &mut piet_common::Piet, invalid_rect: Rect) -> bool; /// Called when the resources need to be rebuilt. diff --git a/druid/src/app.rs b/druid/src/app.rs index 4038e01fac..8750e325ca 100644 --- a/druid/src/app.rs +++ b/druid/src/app.rs @@ -187,7 +187,7 @@ impl WindowDesc { self } - /// Set the window's initial drawing area size in display points. + /// Set the window's initial drawing area size in [display points]. /// /// You can pass in a tuple `(width, height)` or a [`Size`], /// e.g. to create a window with a drawing area 1000dp wide and 500dp high: @@ -202,12 +202,13 @@ impl WindowDesc { /// The platform might increase the size a tiny bit due to DPI. /// /// [`Size`]: struct.Size.html + /// [display points]: struct.Scale.html pub fn window_size(mut self, size: impl Into) -> Self { self.size = Some(size.into()); self } - /// Set the window's minimum drawing area size in display points. + /// Set the window's minimum drawing area size in [display points]. /// /// The actual minimum window size in pixels will depend on the platform DPI settings. /// @@ -217,6 +218,7 @@ impl WindowDesc { /// To set the window's initial drawing area size use [`window_size`]. /// /// [`window_size`]: #method.window_size + /// [display points]: struct.Scale.html pub fn with_min_size(mut self, size: impl Into) -> Self { self.min_size = Some(size.into()); self diff --git a/druid/src/lib.rs b/druid/src/lib.rs index 864d7dc263..25498fb33e 100644 --- a/druid/src/lib.rs +++ b/druid/src/lib.rs @@ -146,7 +146,7 @@ pub use piet::{Color, LinearGradient, RadialGradient, RenderContext, UnitPoint}; pub use shell::{ Application, Clipboard, ClipboardFormat, Cursor, Error as PlatformError, FileDialogOptions, FileInfo, FileSpec, FormatId, HotKey, KeyCode, KeyEvent, KeyModifiers, MouseButton, - MouseButtons, RawMods, SysMods, Text, TimerToken, WindowHandle, + MouseButtons, RawMods, Scale, SysMods, Text, TimerToken, WindowHandle, }; pub use crate::core::WidgetPod; From 16ec5af83e20d6df3b98844ba13db6ea55f9a95d Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Sat, 16 May 2020 19:07:55 +0300 Subject: [PATCH 28/29] Do some minor cleanup. --- CHANGELOG.md | 2 +- druid-shell/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa6e5f6972..4b7fc09acf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,9 +42,9 @@ This means that druid no longer requires cairo on macOS and uses Core Graphics i - `MouseButtons` to `MouseEvent` to track which buttons are being held down during an event. ([#843] by [@xStrom]) - `Env` and `Key` gained methods for inspecting an `Env` at runtime ([#880] by [@Zarenor]) - `UpdateCtx::request_timer` and `UpdateCtx::request_anim_frame`. ([#898] by [@finnerale]) - - `LifeCycleCtx::request_timer`. ([#954] by [@xStrom]) - `scale` method to `WinHandler`. ([#904] by [@xStrom]) +- `WinHandler::scale` method to inform of scale changes. ([#904] by [@xStrom]) - `UpdateCtx::size` and `LifeCycleCtx::size`. ([#917] by [@jneem]) - `WidgetExt::debug_widget_id`, for displaying widget ids on hover. ([#876] by [@cmyr]) - `im` feature, with `Data` support for the [`im` crate](https://docs.rs/im/) collections. ([#924] by [@cmyr]) diff --git a/druid-shell/src/lib.rs b/druid-shell/src/lib.rs index c7ac1e8bbd..e205f69304 100644 --- a/druid-shell/src/lib.rs +++ b/druid-shell/src/lib.rs @@ -49,7 +49,7 @@ pub use keyboard::{KeyEvent, KeyModifiers}; pub use keycodes::KeyCode; pub use menu::Menu; pub use mouse::{Cursor, MouseButton, MouseButtons, MouseEvent}; -pub use scale::{Scale, ScaledArea}; +pub use scale::{Scalable, Scale, ScaledArea}; pub use window::{ IdleHandle, IdleToken, Text, TimerToken, WinHandler, WindowBuilder, WindowHandle, }; From 060dc4fb7c4826ea95c633ba96b9df0399f16bf8 Mon Sep 17 00:00:00 2001 From: Kaur Kuut Date: Mon, 18 May 2020 21:54:37 +0300 Subject: [PATCH 29/29] Delete some Scale code. --- Cargo.lock | 1 - druid-shell/Cargo.toml | 1 - druid-shell/src/platform/gtk/window.rs | 5 ++-- druid-shell/src/scale.rs | 32 ++------------------------ 4 files changed, 5 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89b7451dc4..1e1f95d1f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,7 +403,6 @@ dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "cocoa 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", "core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", - "float-cmp 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "gdk 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "gdk-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/druid-shell/Cargo.toml b/druid-shell/Cargo.toml index 9c93744597..5807bcb3e2 100644 --- a/druid-shell/Cargo.toml +++ b/druid-shell/Cargo.toml @@ -24,7 +24,6 @@ kurbo = "0.6.0" log = "0.4.8" lazy_static = "1.0" time = "0.2.7" -float-cmp = { version = "0.8.0", features = ["std"], default-features = false } cfg-if = "0.1.10" instant = { version = "0.1", features = ["wasm-bindgen"] } diff --git a/druid-shell/src/platform/gtk/window.rs b/druid-shell/src/platform/gtk/window.rs index 4b2a798d72..95902a2837 100644 --- a/druid-shell/src/platform/gtk/window.rs +++ b/druid-shell/src/platform/gtk/window.rs @@ -255,8 +255,9 @@ impl WindowBuilder { // so that we can change our scale factor without restarting the application. if let Some(dpi) = state.window.get_window() .map(|w| w.get_display().get_default_screen().get_resolution()) { - if !scale.dpi_approx_eq(dpi, dpi) { - scale = Scale::from_dpi(dpi, dpi); + let reported_scale = Scale::from_dpi(dpi, dpi); + if scale != reported_scale { + scale = reported_scale; state.scale.set(scale); scale_changed = true; if let Ok(mut handler_borrow) = state.handler.try_borrow_mut() { diff --git a/druid-shell/src/scale.rs b/druid-shell/src/scale.rs index 028cf36d63..7c7987044b 100644 --- a/druid-shell/src/scale.rs +++ b/druid-shell/src/scale.rs @@ -14,8 +14,6 @@ //! Resolution scale related helpers. -use float_cmp::ApproxEq; - use crate::kurbo::{Insets, Line, Point, Rect, Size, Vec2}; const SCALE_TARGET_DPI: f64 = 96.0; @@ -68,9 +66,9 @@ pub struct Scale { /// /// The logical area size in display points is an unrounded conversion, which means that it is /// often not limited to integers. This allows for accurate calculations of -/// the platform area pixel boundaries from the logcal area using a [`Scale`]. +/// the platform area pixel boundaries from the logical area using a [`Scale`]. /// -/// Even though the logcal area size can be fractional, the integer boundaries of that logical area +/// Even though the logical area size can be fractional, the integer boundaries of that logical area /// will still match up with the platform area pixel boundaries as often as the scale factor allows. /// /// A copy of `ScaledArea` will be stale as soon as the platform area size changes. @@ -148,13 +146,6 @@ impl Scale { self.dpi_y } - /// Returns `true` if the specified DPI is approximately equal to the `Scale` DPI. - #[inline] - pub fn dpi_approx_eq(&self, dpi_x: f64, dpi_y: f64) -> bool { - self.dpi_x.approx_eq(dpi_x, (f64::EPSILON, 2)) - && self.dpi_y.approx_eq(dpi_y, (f64::EPSILON, 2)) - } - /// Returns the x axis scale factor. #[inline] pub fn scale_x(&self) -> f64 { @@ -167,25 +158,6 @@ impl Scale { self.scale_y } - /// Converts from display points into pixels, using the x axis scale factor. - #[inline] - pub fn dp_to_px_x>(&self, x: T) -> f64 { - x.into() * self.scale_x - } - - /// Converts from display points into pixels, using the y axis scale factor. - #[inline] - pub fn dp_to_px_y>(&self, y: T) -> f64 { - y.into() * self.scale_y - } - - /// Converts from display points into pixels, - /// using the x axis scale factor for `x` and the y axis scale factor for `y`. - #[inline] - pub fn dp_to_px_xy>(&self, x: T, y: T) -> (f64, f64) { - (x.into() * self.scale_x, y.into() * self.scale_y) - } - /// Converts the `item` from display points into pixels, /// using the x axis scale factor for coordinates on the x axis /// and the y axis scale factor for coordinates on the y axis.