Skip to content

Commit 16e340b

Browse files
authored
Add Event::WindowScale and Ctx::scale for easier scale handling. (#2335)
Also cleans up the SVG widget and makes it use scale information.
1 parent 47780f8 commit 16e340b

File tree

8 files changed

+91
-73
lines changed

8 files changed

+91
-73
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ You can find its changes [documented below](#070---2021-01-01).
7575
- Windows: Dark mode support for the title bar ([#2196] by [@dristic])
7676
- `ZStack` widget ([#2235] by [@xarvic])
7777
- `Lifecycle::ViewStateChanged`, `InternalLifecycle::RouteViewStateChanged`, `ChangeCtx`, and `RequestCtx` ([#2149] by [@xarvic])
78+
- `Event::WindowScale` to notify widgets of the window's scale changes. ([#2335] by [@xStrom])
79+
- `Ctx::scale` method to all contexts for widgets to easily access the window's scale. ([#2335] by [@xStrom])
7880
- Add a public constructor to `StringCursor` ([#2319] by [@benoitryder])
7981

8082
### Changed
@@ -884,6 +886,7 @@ Last release without a changelog :(
884886
[#2323]: https://github.com/linebender/druid/pull/2323
885887
[#2324]: https://github.com/linebender/druid/pull/2324
886888
[#2331]: https://github.com/linebender/druid/pull/2331
889+
[#2335]: https://github.com/linebender/druid/pull/2335
887890

888891
[Unreleased]: https://github.com/linebender/druid/compare/v0.7.0...master
889892
[0.7.0]: https://github.com/linebender/druid/compare/v0.6.0...v0.7.0

druid-shell/src/window.rs

+1
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ impl WindowHandle {
413413
/// The returned [`Scale`](crate::Scale) is a copy and thus its information will be stale after
414414
/// the platform DPI changes. This means you should not stash it and rely on it later; it is
415415
/// only guaranteed to be valid for the current pass of the runloop.
416+
// TODO: Can we get rid of the Result/Error for ergonomics?
416417
pub fn get_scale(&self) -> Result<Scale, Error> {
417418
self.0.get_scale().map_err(Into::into)
418419
}

druid/src/contexts.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ use crate::shell::Region;
3333
use crate::text::{ImeHandlerRef, TextFieldRegistration};
3434
use crate::{
3535
commands, sub_window::SubWindowDesc, widget::Widget, Affine, Command, Cursor, Data, Env,
36-
ExtEventSink, Insets, Menu, Notification, Point, Rect, SingleUse, Size, Target, TimerToken,
37-
Vec2, WidgetId, WindowConfig, WindowDesc, WindowHandle, WindowId,
36+
ExtEventSink, Insets, Menu, Notification, Point, Rect, Scale, SingleUse, Size, Target,
37+
TimerToken, Vec2, WidgetId, WindowConfig, WindowDesc, WindowHandle, WindowId,
3838
};
3939

4040
/// A macro for implementing methods on multiple contexts.
@@ -344,6 +344,18 @@ impl_context_method!(
344344
pub fn text(&mut self) -> &mut PietText {
345345
&mut self.state.text
346346
}
347+
348+
/// The current window's [`Scale`].
349+
///
350+
/// The returned [`Scale`] is a copy and thus its information will be stale after
351+
/// the platform changes the window's scale. This means you can only rely on it
352+
/// until the next [`Event::WindowScale`] event happens.
353+
///
354+
/// [`Scale`]: crate::Scale
355+
/// [`Event::WindowScale`]: crate::Event::WindowScale
356+
pub fn scale(&self) -> Scale {
357+
self.state.window.get_scale().unwrap_or_default()
358+
}
347359
}
348360
);
349361

druid/src/core.rs

+4
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,10 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
714714
}
715715
true
716716
}
717+
Event::WindowScale(_) => {
718+
self.state.needs_layout = true;
719+
true
720+
}
717721
Event::WindowSize(_) => {
718722
self.state.needs_layout = true;
719723
ctx.is_root

druid/src/event.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use druid_shell::{Clipboard, KeyEvent, TimerToken};
2020

2121
use crate::kurbo::{Rect, Size};
2222
use crate::mouse::MouseEvent;
23-
use crate::{Command, Notification, Point, WidgetId};
23+
use crate::{Command, Notification, Point, Scale, WidgetId};
2424

2525
/// An event, propagated downwards during event flow.
2626
///
@@ -75,6 +75,12 @@ pub enum Event {
7575
/// This event means the window *will* go away; it is safe to dispose of resources and
7676
/// do any other cleanup.
7777
WindowDisconnected,
78+
/// Called when the window's [`Scale`] changes.
79+
///
80+
/// This information can be used to switch between different resolution image assets.
81+
///
82+
/// [`Scale`]: crate::Scale
83+
WindowScale(Scale),
7884
/// Called on the root widget when the window size changes.
7985
///
8086
/// Discussion: it's not obvious this should be propagated to user
@@ -418,6 +424,7 @@ impl Event {
418424
Event::WindowConnected
419425
| Event::WindowCloseRequested
420426
| Event::WindowDisconnected
427+
| Event::WindowScale(_)
421428
| Event::WindowSize(_)
422429
| Event::Timer(_)
423430
| Event::AnimFrame(_)

druid/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ pub use shell::keyboard_types;
218218
pub use shell::{
219219
Application, Clipboard, ClipboardFormat, Code, Cursor, CursorDesc, Error as PlatformError,
220220
FileInfo, FileSpec, FormatId, HotKey, KbKey, KeyEvent, Location, Modifiers, Monitor,
221-
MouseButton, MouseButtons, RawMods, Region, Scalable, Scale, Screen, SysMods, TimerToken,
222-
WindowHandle, WindowLevel, WindowState,
221+
MouseButton, MouseButtons, RawMods, Region, Scalable, Scale, ScaledArea, Screen, SysMods,
222+
TimerToken, WindowHandle, WindowLevel, WindowState,
223223
};
224224

225225
#[cfg(feature = "raw-win-handle")]

druid/src/widget/svg.rs

+56-66
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,17 @@
1414

1515
//! An SVG widget.
1616
17-
use druid;
18-
use druid::RenderContext;
17+
use std::sync::Arc;
18+
1919
use resvg;
20+
use usvg::Tree;
21+
22+
use crate::piet::{ImageBuf, ImageFormat, InterpolationMode};
23+
use crate::widget::prelude::*;
24+
use crate::{Rect, ScaledArea};
2025

2126
#[allow(dead_code)]
22-
pub fn new(data: impl Into<std::sync::Arc<usvg::Tree>>) -> Svg {
27+
pub fn new(data: impl Into<Arc<Tree>>) -> Svg {
2328
Svg::new(data.into())
2429
}
2530

@@ -31,27 +36,28 @@ pub fn from_str(s: &str) -> Result<SvgData, <SvgData as std::str::FromStr>::Err>
3136

3237
/// A widget that renders a SVG
3338
pub struct Svg {
34-
tree: std::sync::Arc<usvg::Tree>,
35-
default_size: druid::Size,
36-
cached: Option<(druid::Size, druid::piet::ImageBuf)>,
39+
tree: Arc<Tree>,
40+
default_size: Size,
41+
cached: Option<ImageBuf>,
3742
}
3843

3944
impl Svg {
4045
/// Create an SVG-drawing widget from SvgData.
4146
///
4247
/// The SVG will scale to fit its box constraints.
43-
pub fn new(tree: impl Into<std::sync::Arc<usvg::Tree>>) -> Self {
48+
pub fn new(tree: impl Into<Arc<Tree>>) -> Self {
4449
let tree = tree.into();
4550
Svg {
46-
default_size: druid::Size::new(tree.size.width(), tree.size.height()),
47-
cached: None::<(druid::Size, druid::piet::ImageBuf)>,
51+
default_size: Size::new(tree.size.width(), tree.size.height()),
52+
cached: None,
4853
tree,
4954
}
5055
}
5156

52-
fn render(&self, size: druid::Size) -> Option<druid::piet::ImageBuf> {
53-
let fit = usvg::FitTo::Size(size.width as u32, size.height as u32);
54-
let mut pixmap = tiny_skia::Pixmap::new(size.width as u32, size.height as u32).unwrap();
57+
fn render(&self, size_px: Size) -> Option<ImageBuf> {
58+
let fit = usvg::FitTo::Size(size_px.width as u32, size_px.height as u32);
59+
let mut pixmap =
60+
tiny_skia::Pixmap::new(size_px.width as u32, size_px.height as u32).unwrap();
5561

5662
if resvg::render(
5763
&self.tree,
@@ -65,85 +71,69 @@ impl Svg {
6571
return None;
6672
}
6773

68-
Some(druid::piet::ImageBuf::from_raw(
74+
Some(ImageBuf::from_raw(
6975
pixmap.data(),
70-
druid::piet::ImageFormat::RgbaPremul,
71-
size.width as usize,
72-
size.height as usize,
76+
ImageFormat::RgbaPremul,
77+
size_px.width as usize,
78+
size_px.height as usize,
7379
))
7480
}
7581
}
7682

77-
impl<T: druid::Data> druid::Widget<T> for Svg {
78-
fn event(
79-
&mut self,
80-
_ctx: &mut druid::EventCtx,
81-
_event: &druid::Event,
82-
_data: &mut T,
83-
_env: &druid::Env,
84-
) {
85-
}
83+
impl<T: Data> Widget<T> for Svg {
84+
fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut T, _env: &Env) {}
8685

87-
fn lifecycle(
88-
&mut self,
89-
_ctx: &mut druid::LifeCycleCtx,
90-
_event: &druid::LifeCycle,
91-
_data: &T,
92-
_env: &druid::Env,
93-
) {
94-
}
86+
fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle, _data: &T, _env: &Env) {}
9587

96-
fn update(&mut self, _ctx: &mut druid::UpdateCtx, _old_data: &T, _data: &T, _env: &druid::Env) {
97-
}
88+
fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &T, _data: &T, _env: &Env) {}
9889

9990
fn layout(
10091
&mut self,
101-
_layout_ctx: &mut druid::LayoutCtx,
102-
bc: &druid::BoxConstraints,
92+
_layout_ctx: &mut LayoutCtx,
93+
bc: &BoxConstraints,
10394
_data: &T,
104-
_env: &druid::Env,
105-
) -> druid::Size {
95+
_env: &Env,
96+
) -> Size {
10697
// preferred size comes from the svg
10798
let size = self.default_size;
10899
bc.constrain_aspect_ratio(size.height / size.width, size.width)
109100
}
110101

111-
fn paint(&mut self, ctx: &mut druid::PaintCtx, _data: &T, _env: &druid::Env) {
102+
fn paint(&mut self, ctx: &mut PaintCtx, _data: &T, _env: &Env) {
112103
let size = ctx.size();
104+
let area = ScaledArea::from_dp(size, ctx.scale());
105+
let size_px = area.size_px();
113106

114-
let cached = self.cached.as_ref().filter(|(csize, _)| *csize == size);
115-
let cached = match cached {
116-
Some(current) => Some(current.clone()),
117-
None => self.render(size).map(|i| (size, i)),
118-
};
119-
let cached = match cached {
120-
Some(current) => current,
121-
None => {
122-
tracing::error!("unable to paint svg");
123-
return;
124-
}
125-
};
107+
let needs_render = self
108+
.cached
109+
.as_ref()
110+
.filter(|image_buf| image_buf.size() == size_px)
111+
.is_none();
112+
113+
if needs_render {
114+
self.cached = self.render(size_px);
115+
}
116+
117+
if self.cached.is_none() {
118+
tracing::error!("unable to paint SVG due to no rendered image");
119+
return;
120+
}
126121

127-
let clip_rect = druid::Rect::ZERO.with_size(cached.0);
128-
let img = cached.1.to_image(ctx.render_ctx);
122+
let clip_rect = Rect::ZERO.with_size(size);
123+
let img = self.cached.as_ref().unwrap().to_image(ctx.render_ctx);
129124
ctx.clip(clip_rect);
130-
ctx.draw_image(
131-
&img,
132-
clip_rect,
133-
druid::piet::InterpolationMode::NearestNeighbor,
134-
);
135-
self.cached = Some(cached);
125+
ctx.draw_image(&img, clip_rect, InterpolationMode::NearestNeighbor);
136126
}
137127
}
138128

139129
/// Stored parsed SVG tree.
140-
#[derive(Clone, druid::Data)]
130+
#[derive(Clone, Data)]
141131
pub struct SvgData {
142-
tree: std::sync::Arc<usvg::Tree>,
132+
tree: Arc<Tree>,
143133
}
144134

145135
impl SvgData {
146-
fn new(tree: std::sync::Arc<usvg::Tree>) -> Self {
136+
fn new(tree: Arc<Tree>) -> Self {
147137
Self { tree }
148138
}
149139

@@ -171,14 +161,14 @@ impl std::str::FromStr for SvgData {
171161
..usvg::Options::default()
172162
};
173163

174-
match usvg::Tree::from_str(svg_str, &re_opt) {
175-
Ok(tree) => Ok(SvgData::new(std::sync::Arc::new(tree))),
164+
match Tree::from_str(svg_str, &re_opt) {
165+
Ok(tree) => Ok(SvgData::new(Arc::new(tree))),
176166
Err(err) => Err(err.into()),
177167
}
178168
}
179169
}
180170

181-
impl From<SvgData> for std::sync::Arc<usvg::Tree> {
171+
impl From<SvgData> for Arc<Tree> {
182172
fn from(d: SvgData) -> Self {
183173
d.tree
184174
}

druid/src/win_handler.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -951,8 +951,9 @@ impl<T: Data> WinHandler for DruidHandler<T> {
951951
self.app_state.do_window_event(event, self.window_id);
952952
}
953953

954-
fn scale(&mut self, _scale: Scale) {
955-
// TODO: Do something with the scale
954+
fn scale(&mut self, scale: Scale) {
955+
let event = Event::WindowScale(scale);
956+
self.app_state.do_window_event(event, self.window_id);
956957
}
957958

958959
fn command(&mut self, id: u32) {

0 commit comments

Comments
 (0)