Skip to content

Commit

Permalink
Add Event::WindowScale and Ctx::scale for easier scale handling. (#…
Browse files Browse the repository at this point in the history
…2335)

Also cleans up the SVG widget and makes it use scale information.
  • Loading branch information
xStrom authored Jan 16, 2023
1 parent 47780f8 commit 16e340b
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 73 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ You can find its changes [documented below](#070---2021-01-01).
- Windows: Dark mode support for the title bar ([#2196] by [@dristic])
- `ZStack` widget ([#2235] by [@xarvic])
- `Lifecycle::ViewStateChanged`, `InternalLifecycle::RouteViewStateChanged`, `ChangeCtx`, and `RequestCtx` ([#2149] by [@xarvic])
- `Event::WindowScale` to notify widgets of the window's scale changes. ([#2335] by [@xStrom])
- `Ctx::scale` method to all contexts for widgets to easily access the window's scale. ([#2335] by [@xStrom])
- Add a public constructor to `StringCursor` ([#2319] by [@benoitryder])

### Changed
Expand Down Expand Up @@ -884,6 +886,7 @@ Last release without a changelog :(
[#2323]: https://github.com/linebender/druid/pull/2323
[#2324]: https://github.com/linebender/druid/pull/2324
[#2331]: https://github.com/linebender/druid/pull/2331
[#2335]: https://github.com/linebender/druid/pull/2335

[Unreleased]: https://github.com/linebender/druid/compare/v0.7.0...master
[0.7.0]: https://github.com/linebender/druid/compare/v0.6.0...v0.7.0
Expand Down
1 change: 1 addition & 0 deletions druid-shell/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ impl WindowHandle {
/// The returned [`Scale`](crate::Scale) is a copy and thus its information will be stale after
/// the platform DPI changes. This means you should not stash it and rely on it later; it is
/// only guaranteed to be valid for the current pass of the runloop.
// TODO: Can we get rid of the Result/Error for ergonomics?
pub fn get_scale(&self) -> Result<Scale, Error> {
self.0.get_scale().map_err(Into::into)
}
Expand Down
16 changes: 14 additions & 2 deletions druid/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ use crate::shell::Region;
use crate::text::{ImeHandlerRef, TextFieldRegistration};
use crate::{
commands, sub_window::SubWindowDesc, widget::Widget, Affine, Command, Cursor, Data, Env,
ExtEventSink, Insets, Menu, Notification, Point, Rect, SingleUse, Size, Target, TimerToken,
Vec2, WidgetId, WindowConfig, WindowDesc, WindowHandle, WindowId,
ExtEventSink, Insets, Menu, Notification, Point, Rect, Scale, SingleUse, Size, Target,
TimerToken, Vec2, WidgetId, WindowConfig, WindowDesc, WindowHandle, WindowId,
};

/// A macro for implementing methods on multiple contexts.
Expand Down Expand Up @@ -344,6 +344,18 @@ impl_context_method!(
pub fn text(&mut self) -> &mut PietText {
&mut self.state.text
}

/// The current window's [`Scale`].
///
/// The returned [`Scale`] is a copy and thus its information will be stale after
/// the platform changes the window's scale. This means you can only rely on it
/// until the next [`Event::WindowScale`] event happens.
///
/// [`Scale`]: crate::Scale
/// [`Event::WindowScale`]: crate::Event::WindowScale
pub fn scale(&self) -> Scale {
self.state.window.get_scale().unwrap_or_default()
}
}
);

Expand Down
4 changes: 4 additions & 0 deletions druid/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,10 @@ impl<T: Data, W: Widget<T>> WidgetPod<T, W> {
}
true
}
Event::WindowScale(_) => {
self.state.needs_layout = true;
true
}
Event::WindowSize(_) => {
self.state.needs_layout = true;
ctx.is_root
Expand Down
9 changes: 8 additions & 1 deletion druid/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use druid_shell::{Clipboard, KeyEvent, TimerToken};

use crate::kurbo::{Rect, Size};
use crate::mouse::MouseEvent;
use crate::{Command, Notification, Point, WidgetId};
use crate::{Command, Notification, Point, Scale, WidgetId};

/// An event, propagated downwards during event flow.
///
Expand Down Expand Up @@ -75,6 +75,12 @@ pub enum Event {
/// This event means the window *will* go away; it is safe to dispose of resources and
/// do any other cleanup.
WindowDisconnected,
/// Called when the window's [`Scale`] changes.
///
/// This information can be used to switch between different resolution image assets.
///
/// [`Scale`]: crate::Scale
WindowScale(Scale),
/// Called on the root widget when the window size changes.
///
/// Discussion: it's not obvious this should be propagated to user
Expand Down Expand Up @@ -418,6 +424,7 @@ impl Event {
Event::WindowConnected
| Event::WindowCloseRequested
| Event::WindowDisconnected
| Event::WindowScale(_)
| Event::WindowSize(_)
| Event::Timer(_)
| Event::AnimFrame(_)
Expand Down
4 changes: 2 additions & 2 deletions druid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ pub use shell::keyboard_types;
pub use shell::{
Application, Clipboard, ClipboardFormat, Code, Cursor, CursorDesc, Error as PlatformError,
FileInfo, FileSpec, FormatId, HotKey, KbKey, KeyEvent, Location, Modifiers, Monitor,
MouseButton, MouseButtons, RawMods, Region, Scalable, Scale, Screen, SysMods, TimerToken,
WindowHandle, WindowLevel, WindowState,
MouseButton, MouseButtons, RawMods, Region, Scalable, Scale, ScaledArea, Screen, SysMods,
TimerToken, WindowHandle, WindowLevel, WindowState,
};

#[cfg(feature = "raw-win-handle")]
Expand Down
122 changes: 56 additions & 66 deletions druid/src/widget/svg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@

//! An SVG widget.
use druid;
use druid::RenderContext;
use std::sync::Arc;

use resvg;
use usvg::Tree;

use crate::piet::{ImageBuf, ImageFormat, InterpolationMode};
use crate::widget::prelude::*;
use crate::{Rect, ScaledArea};

#[allow(dead_code)]
pub fn new(data: impl Into<std::sync::Arc<usvg::Tree>>) -> Svg {
pub fn new(data: impl Into<Arc<Tree>>) -> Svg {
Svg::new(data.into())
}

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

/// A widget that renders a SVG
pub struct Svg {
tree: std::sync::Arc<usvg::Tree>,
default_size: druid::Size,
cached: Option<(druid::Size, druid::piet::ImageBuf)>,
tree: Arc<Tree>,
default_size: Size,
cached: Option<ImageBuf>,
}

impl Svg {
/// Create an SVG-drawing widget from SvgData.
///
/// The SVG will scale to fit its box constraints.
pub fn new(tree: impl Into<std::sync::Arc<usvg::Tree>>) -> Self {
pub fn new(tree: impl Into<Arc<Tree>>) -> Self {
let tree = tree.into();
Svg {
default_size: druid::Size::new(tree.size.width(), tree.size.height()),
cached: None::<(druid::Size, druid::piet::ImageBuf)>,
default_size: Size::new(tree.size.width(), tree.size.height()),
cached: None,
tree,
}
}

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

if resvg::render(
&self.tree,
Expand All @@ -65,85 +71,69 @@ impl Svg {
return None;
}

Some(druid::piet::ImageBuf::from_raw(
Some(ImageBuf::from_raw(
pixmap.data(),
druid::piet::ImageFormat::RgbaPremul,
size.width as usize,
size.height as usize,
ImageFormat::RgbaPremul,
size_px.width as usize,
size_px.height as usize,
))
}
}

impl<T: druid::Data> druid::Widget<T> for Svg {
fn event(
&mut self,
_ctx: &mut druid::EventCtx,
_event: &druid::Event,
_data: &mut T,
_env: &druid::Env,
) {
}
impl<T: Data> Widget<T> for Svg {
fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut T, _env: &Env) {}

fn lifecycle(
&mut self,
_ctx: &mut druid::LifeCycleCtx,
_event: &druid::LifeCycle,
_data: &T,
_env: &druid::Env,
) {
}
fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, _event: &LifeCycle, _data: &T, _env: &Env) {}

fn update(&mut self, _ctx: &mut druid::UpdateCtx, _old_data: &T, _data: &T, _env: &druid::Env) {
}
fn update(&mut self, _ctx: &mut UpdateCtx, _old_data: &T, _data: &T, _env: &Env) {}

fn layout(
&mut self,
_layout_ctx: &mut druid::LayoutCtx,
bc: &druid::BoxConstraints,
_layout_ctx: &mut LayoutCtx,
bc: &BoxConstraints,
_data: &T,
_env: &druid::Env,
) -> druid::Size {
_env: &Env,
) -> Size {
// preferred size comes from the svg
let size = self.default_size;
bc.constrain_aspect_ratio(size.height / size.width, size.width)
}

fn paint(&mut self, ctx: &mut druid::PaintCtx, _data: &T, _env: &druid::Env) {
fn paint(&mut self, ctx: &mut PaintCtx, _data: &T, _env: &Env) {
let size = ctx.size();
let area = ScaledArea::from_dp(size, ctx.scale());
let size_px = area.size_px();

let cached = self.cached.as_ref().filter(|(csize, _)| *csize == size);
let cached = match cached {
Some(current) => Some(current.clone()),
None => self.render(size).map(|i| (size, i)),
};
let cached = match cached {
Some(current) => current,
None => {
tracing::error!("unable to paint svg");
return;
}
};
let needs_render = self
.cached
.as_ref()
.filter(|image_buf| image_buf.size() == size_px)
.is_none();

if needs_render {
self.cached = self.render(size_px);
}

if self.cached.is_none() {
tracing::error!("unable to paint SVG due to no rendered image");
return;
}

let clip_rect = druid::Rect::ZERO.with_size(cached.0);
let img = cached.1.to_image(ctx.render_ctx);
let clip_rect = Rect::ZERO.with_size(size);
let img = self.cached.as_ref().unwrap().to_image(ctx.render_ctx);
ctx.clip(clip_rect);
ctx.draw_image(
&img,
clip_rect,
druid::piet::InterpolationMode::NearestNeighbor,
);
self.cached = Some(cached);
ctx.draw_image(&img, clip_rect, InterpolationMode::NearestNeighbor);
}
}

/// Stored parsed SVG tree.
#[derive(Clone, druid::Data)]
#[derive(Clone, Data)]
pub struct SvgData {
tree: std::sync::Arc<usvg::Tree>,
tree: Arc<Tree>,
}

impl SvgData {
fn new(tree: std::sync::Arc<usvg::Tree>) -> Self {
fn new(tree: Arc<Tree>) -> Self {
Self { tree }
}

Expand Down Expand Up @@ -171,14 +161,14 @@ impl std::str::FromStr for SvgData {
..usvg::Options::default()
};

match usvg::Tree::from_str(svg_str, &re_opt) {
Ok(tree) => Ok(SvgData::new(std::sync::Arc::new(tree))),
match Tree::from_str(svg_str, &re_opt) {
Ok(tree) => Ok(SvgData::new(Arc::new(tree))),
Err(err) => Err(err.into()),
}
}
}

impl From<SvgData> for std::sync::Arc<usvg::Tree> {
impl From<SvgData> for Arc<Tree> {
fn from(d: SvgData) -> Self {
d.tree
}
Expand Down
5 changes: 3 additions & 2 deletions druid/src/win_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -951,8 +951,9 @@ impl<T: Data> WinHandler for DruidHandler<T> {
self.app_state.do_window_event(event, self.window_id);
}

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

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

0 comments on commit 16e340b

Please sign in to comment.