Added Zoom factor

Closes #146
This commit is contained in:
Jonathan Johnson 2024-07-25 11:31:38 -07:00
parent 83fe4b05ef
commit ba3a4b9b02
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
6 changed files with 144 additions and 59 deletions

View file

@ -43,6 +43,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
true, the window is allowed to be closed. If false is returned, the window true, the window is allowed to be closed. If false is returned, the window
will remain open. This feature is most commonly used to prevent losing unsaved will remain open. This feature is most commonly used to prevent losing unsaved
changes. changes.
- `Fraction` now has `LinearInterpolation` and `PercentBetween` implementations.
- `Window::zoom` allows setting a `Dynamic<Fraction>` that scales all DPI-scaled
operations by an additional scaling factor.
## v0.3.0 (2024-05-12) ## v0.3.0 (2024-05-12)

68
Cargo.lock generated
View file

@ -1101,12 +1101,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.4.0" version = "0.4.0"
@ -1242,9 +1236,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.31" version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -1296,8 +1290,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]] [[package]]
name = "kludgine" name = "kludgine"
version = "0.9.0" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/khonsulabs/kludgine#eca1ac5d3a9dead20c9c94fb849a4e2bf7719654"
checksum = "3dac414f26b39a8b25572244a3cbb4a4a566c86ab9a0d878b88a2e46d657d59c"
dependencies = [ dependencies = [
"ahash", "ahash",
"alot", "alot",
@ -1522,7 +1515,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"rayon",
] ]
[[package]] [[package]]
@ -1649,9 +1641,9 @@ dependencies = [
[[package]] [[package]]
name = "nominals" name = "nominals"
version = "0.3.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd4b6e50a0a7f2214e99ecf7f4a2c9cb9572e5817d96e37a6d31387961c23994" checksum = "83d3716836932a56eadedb47bd27ee2174dbdd3ac97c585002e2994e162c5b37"
[[package]] [[package]]
name = "noop_proc_macro" name = "noop_proc_macro"
@ -1720,16 +1712,6 @@ dependencies = [
"libm", "libm",
] ]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi 0.3.9",
"libc",
]
[[package]] [[package]]
name = "num_enum" name = "num_enum"
version = "0.7.2" version = "0.7.2"
@ -1965,9 +1947,9 @@ dependencies = [
[[package]] [[package]]
name = "object" name = "object"
version = "0.36.1" version = "0.36.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -2174,7 +2156,7 @@ checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"concurrent-queue", "concurrent-queue",
"hermit-abi 0.4.0", "hermit-abi",
"pin-project-lite", "pin-project-lite",
"rustix", "rustix",
"tracing", "tracing",
@ -2392,16 +2374,15 @@ dependencies = [
[[package]] [[package]]
name = "ravif" name = "ravif"
version = "0.11.8" version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6ba61c28ba24c0cf8406e025cb29a742637e3f70776e61c27a8a8b72a042d12" checksum = "5797d09f9bd33604689e87e8380df4951d4912f01b63f71205e2abd4ae25e6b6"
dependencies = [ dependencies = [
"avif-serialize", "avif-serialize",
"imgref", "imgref",
"loop9", "loop9",
"quick-error", "quick-error",
"rav1e", "rav1e",
"rayon",
"rgb", "rgb",
] ]
@ -2628,9 +2609,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.6" version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -2928,32 +2909,31 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.38.1" version = "1.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"num_cpus",
"pin-project-lite", "pin-project-lite",
] ]
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.15" version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"toml_edit 0.22.16", "toml_edit 0.22.17",
] ]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.6" version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -2971,15 +2951,15 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.16" version = "0.22.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
"winnow 0.6.14", "winnow 0.6.16",
] ]
[[package]] [[package]]
@ -3797,9 +3777,9 @@ dependencies = [
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.14" version = "0.6.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374ec40a2d767a3c1b4972d9475ecd557356637be906f2cb3f7fe17a6eb5e22f" checksum = "b480ae9340fc261e6be3e95a1ba86d54ae3f9171132a73ce8d4bbaf68339507c"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View file

@ -22,7 +22,9 @@ tokio = ["dep:tokio"]
tokio-multi-thread = ["tokio", "tokio/rt-multi-thread"] tokio-multi-thread = ["tokio", "tokio/rt-multi-thread"]
[dependencies] [dependencies]
kludgine = { version = "0.9.0", features = ["app"] } kludgine = { git = "https://github.com/khonsulabs/kludgine", features = [
"app",
] }
figures = { version = "0.4.0" } figures = { version = "0.4.0" }
alot = "0.3" alot = "0.3"
interner = "0.2.1" interner = "0.2.1"

21
examples/window-zoom.rs Normal file
View file

@ -0,0 +1,21 @@
use cushy::value::{Dynamic, Source};
use cushy::widget::MakeWidget;
use cushy::widgets::slider::Slidable;
use cushy::Run;
use figures::Fraction;
fn main() -> cushy::Result<()> {
let zoom = Dynamic::new(Fraction::ONE);
zoom.map_each(|z| z.to_string())
.and(
zoom.clone()
.slider_between(Fraction::new(1, 4), Fraction::new(4, 1)),
)
.into_rows()
.fit_horizontally()
.pad()
.expand()
.into_window()
.zoom(zoom)
.run()
}

View file

@ -49,7 +49,7 @@ use std::time::{Duration, Instant};
use alot::{LotId, Lots}; use alot::{LotId, Lots};
use figures::units::{Lp, Px, UPx}; use figures::units::{Lp, Px, UPx};
use figures::{Angle, Point, Ranged, Rect, Size, UnscaledUnit, Zero}; use figures::{Angle, Fraction, Point, Ranged, Rect, Size, UnscaledUnit, Zero};
use intentional::Cast; use intentional::Cast;
use kempt::Set; use kempt::Set;
use kludgine::Color; use kludgine::Color;
@ -883,6 +883,11 @@ impl LinearInterpolate for f64 {
*self + delta * f64::from(percent) *self + delta * f64::from(percent)
} }
} }
impl LinearInterpolate for Fraction {
fn lerp(&self, target: &Self, percent: f32) -> Self {
Fraction::from(self.into_f32().lerp(&target.into_f32(), percent))
}
}
impl LinearInterpolate for Angle { impl LinearInterpolate for Angle {
fn lerp(&self, target: &Self, percent: f32) -> Self { fn lerp(&self, target: &Self, percent: f32) -> Self {
@ -1130,6 +1135,13 @@ impl PercentBetween for Color {
} }
} }
impl PercentBetween for Fraction {
fn percent_between(&self, min: &Self, max: &Self) -> ZeroToOne {
self.into_f32()
.percent_between(&min.into_f32(), &max.into_f32())
}
}
#[test] #[test]
fn int_percent_between() { fn int_percent_between() {
assert_eq!(1_u8.percent_between(&1_u8, &2_u8), ZeroToOne::ZERO); assert_eq!(1_u8.percent_between(&1_u8, &2_u8), ZeroToOne::ZERO);

View file

@ -56,7 +56,8 @@ use crate::styles::{Edges, FontFamilyList, ThemePair};
use crate::tree::Tree; use crate::tree::Tree;
use crate::utils::ModifiersExt; use crate::utils::ModifiersExt;
use crate::value::{ use crate::value::{
Destination, Dynamic, DynamicReader, Generation, IntoDynamic, IntoValue, Source, Value, Destination, Dynamic, DynamicRead, DynamicReader, Generation, IntoDynamic, IntoValue, Source,
Value,
}; };
use crate::widget::{ use crate::widget::{
Callback, EventHandling, MakeWidget, MountedWidget, OnceCallback, RootBehavior, WidgetId, Callback, EventHandling, MakeWidget, MountedWidget, OnceCallback, RootBehavior, WidgetId,
@ -499,6 +500,7 @@ where
on_closed: Option<OnceCallback>, on_closed: Option<OnceCallback>,
inner_size: Option<Dynamic<Size<UPx>>>, inner_size: Option<Dynamic<Size<UPx>>>,
zoom: Option<Dynamic<Fraction>>,
occluded: Option<Dynamic<bool>>, occluded: Option<Dynamic<bool>>,
focused: Option<Dynamic<bool>>, focused: Option<Dynamic<bool>>,
theme_mode: Option<Value<ThemeMode>>, theme_mode: Option<Value<ThemeMode>>,
@ -577,6 +579,7 @@ where
multisample_count: NonZeroU32::new(4).assert("not 0"), multisample_count: NonZeroU32::new(4).assert("not 0"),
vsync: true, vsync: true,
close_requested: None, close_requested: None,
zoom: None,
} }
} }
@ -610,7 +613,7 @@ where
self self
} }
/// Sets `inner_size` to be the dynamic syncrhonized with this window's /// Sets `inner_size` to be the dynamic synchronized with this window's
/// inner size. /// inner size.
/// ///
/// When the window is resized, the dynamic will contain its new size. When /// When the window is resized, the dynamic will contain its new size. When
@ -622,6 +625,15 @@ where
self self
} }
/// Sets this window's `zoom` factor.
///
/// The zoom factor is multiplied with the DPI scaling from the window
/// server to allow an additional scaling factor to be applied.
pub fn zoom(mut self, zoom: impl IntoDynamic<Fraction>) -> Self {
self.zoom = Some(zoom.into_dynamic().map_each_into());
self
}
/// Sets the [`ThemeMode`] for this window. /// Sets the [`ThemeMode`] for this window.
/// ///
/// If a [`ThemeMode`] is provided, the window will be set to this theme /// If a [`ThemeMode`] is provided, the window will be set to this theme
@ -729,6 +741,7 @@ where
vsync: self.vsync, vsync: self.vsync,
multisample_count: self.multisample_count, multisample_count: self.multisample_count,
close_requested: self.close_requested.map(|cb| Arc::new(Mutex::new(cb))), close_requested: self.close_requested.map(|cb| Arc::new(Mutex::new(cb))),
zoom: self.zoom.unwrap_or_else(|| Dynamic::new(Fraction::ONE)),
}), }),
}, },
)?; )?;
@ -808,6 +821,9 @@ struct OpenWindow<T> {
cushy: Cushy, cushy: Cushy,
on_closed: Option<OnceCallback>, on_closed: Option<OnceCallback>,
vsync: bool, vsync: bool,
dpi_scale: Dynamic<Fraction>,
zoom: Dynamic<Fraction>,
zoom_generation: Generation,
close_requested: Option<Arc<Mutex<Callback<(), bool>>>>, close_requested: Option<Arc<Mutex<Callback<(), bool>>>>,
} }
@ -1172,6 +1188,8 @@ where
let on_closed = settings.on_closed.take(); let on_closed = settings.on_closed.take();
let close_requested = settings.close_requested.take(); let close_requested = settings.close_requested.take();
let vsync = settings.vsync; let vsync = settings.vsync;
let zoom = settings.zoom.clone();
let dpi_scale = Dynamic::new(graphics.dpi_scale());
inner_size.set(window.inner_size()); inner_size.set(window.inner_size());
@ -1229,6 +1247,9 @@ where
on_closed, on_closed,
vsync, vsync,
close_requested, close_requested,
dpi_scale,
zoom_generation: zoom.generation(),
zoom,
} }
} }
@ -1247,9 +1268,17 @@ where
self.redraw_status.refresh_received(); self.redraw_status.refresh_received();
graphics.reset_text_attributes(); graphics.reset_text_attributes();
let zoom = self.zoom.read();
if zoom.generation() != self.zoom_generation {
graphics.set_zoom(*zoom);
self.redraw_status.invalidate(self.root.id());
}
self.tree self.tree
.new_frame(self.redraw_status.invalidations().drain()); .new_frame(self.redraw_status.invalidations().drain());
drop(zoom);
let resizable = window.is_resizable() || self.resize_to_fit; let resizable = window.is_resizable() || self.resize_to_fit;
let mut window = RunningWindow::new( let mut window = RunningWindow::new(
window, window,
@ -1892,7 +1921,14 @@ where
// fn occlusion_changed(&mut self, window: kludgine::app::Window<'_, ()>) {} // fn occlusion_changed(&mut self, window: kludgine::app::Window<'_, ()>) {}
// fn scale_factor_changed(&mut self, window: kludgine::app::Window<'_, ()>) {} fn scale_factor_changed(
&mut self,
mut window: kludgine::app::Window<'_, WindowCommand>,
kludgine: &mut Kludgine,
) {
self.dpi_scale.set(kludgine.dpi_scale());
window.set_needs_redraw();
}
fn resized( fn resized(
&mut self, &mut self,
@ -2055,7 +2091,7 @@ pub(crate) mod sealed {
use std::sync::Arc; use std::sync::Arc;
use figures::units::UPx; use figures::units::UPx;
use figures::{Point, Size}; use figures::{Fraction, Point, Size};
use image::DynamicImage; use image::DynamicImage;
use kludgine::Color; use kludgine::Color;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -2081,6 +2117,7 @@ pub(crate) mod sealed {
pub occluded: Dynamic<bool>, pub occluded: Dynamic<bool>,
pub focused: Dynamic<bool>, pub focused: Dynamic<bool>,
pub inner_size: Dynamic<Size<UPx>>, pub inner_size: Dynamic<Size<UPx>>,
pub zoom: Dynamic<Fraction>,
pub theme: Option<Value<ThemePair>>, pub theme: Option<Value<ThemePair>>,
pub theme_mode: Option<Value<ThemeMode>>, pub theme_mode: Option<Value<ThemeMode>>,
pub transparent: bool, pub transparent: bool,
@ -2441,7 +2478,7 @@ impl VirtualState {
/// Window state that is able to be updated outside of event handling, /// Window state that is able to be updated outside of event handling,
/// potentially via other threads depending on the application. /// potentially via other threads depending on the application.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug)]
pub struct WindowDynamicState { pub struct WindowDynamicState {
/// The target of the next frame to draw. /// The target of the next frame to draw.
pub redraw_target: Dynamic<RedrawTarget>, pub redraw_target: Dynamic<RedrawTarget>,
@ -2453,6 +2490,16 @@ pub struct WindowDynamicState {
pub title: Dynamic<String>, pub title: Dynamic<String>,
} }
impl Default for WindowDynamicState {
fn default() -> Self {
Self {
redraw_target: Default::default(),
close_requested: Default::default(),
title: Default::default(),
}
}
}
/// A target for the next redraw of a window. /// A target for the next redraw of a window.
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)] #[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
pub enum RedrawTarget { pub enum RedrawTarget {
@ -2530,6 +2577,7 @@ pub struct CushyWindowBuilder {
initial_size: Size<UPx>, initial_size: Size<UPx>,
scale: f32, scale: f32,
transparent: bool, transparent: bool,
zoom: Dynamic<Fraction>,
} }
impl CushyWindowBuilder { impl CushyWindowBuilder {
@ -2541,6 +2589,7 @@ impl CushyWindowBuilder {
multisample_count: NonZeroU32::new(4).assert("not 0"), multisample_count: NonZeroU32::new(4).assert("not 0"),
initial_size: Size::new(UPx::new(800), UPx::new(600)), initial_size: Size::new(UPx::new(800), UPx::new(600)),
scale: 1., scale: 1.,
zoom: Dynamic::new(Fraction::ONE),
transparent: false, transparent: false,
} }
} }
@ -2621,6 +2670,7 @@ impl CushyWindowBuilder {
vsync: false, vsync: false,
multisample_count: self.multisample_count, multisample_count: self.multisample_count,
close_requested: None, close_requested: None,
zoom: self.zoom,
}, },
); );
@ -2757,13 +2807,24 @@ impl CushyWindow {
} }
/// Returns the current DPI scale of the window. /// Returns the current DPI scale of the window.
pub const fn scale(&self) -> Fraction { pub const fn dpi_scale(&self) -> Fraction {
self.kludgine.dpi_scale()
}
/// Returns the effective scale of the window.
pub fn effective_scale(&self) -> Fraction {
self.kludgine.scale() self.kludgine.scale()
} }
/// Updates the dimensions and DPI scaling of the window. /// Updates the dimensions and DPI scaling of the window.
pub fn resize(&mut self, new_size: Size<UPx>, new_scale: impl Into<f32>, queue: &wgpu::Queue) { pub fn resize(
self.kludgine.resize(new_size, new_scale.into(), queue); &mut self,
new_size: Size<UPx>,
new_scale: impl Into<Fraction>,
new_zoom: impl Into<Fraction>,
queue: &wgpu::Queue,
) {
self.kludgine.resize(new_size, new_scale, new_zoom, queue);
self.window.resized(new_size); self.window.resized(new_size);
} }
@ -2971,13 +3032,19 @@ impl VirtualWindow {
} }
/// Returns the current DPI scale of the window. /// Returns the current DPI scale of the window.
pub const fn scale(&self) -> Fraction { pub const fn dpi_scale(&self) -> Fraction {
self.cushy.scale() self.cushy.dpi_scale()
} }
/// Updates the dimensions and DPI scaling of the window. /// Updates the dimensions and DPI scaling of the window.
pub fn resize(&mut self, new_size: Size<UPx>, new_scale: impl Into<f32>, queue: &wgpu::Queue) { pub fn resize(
self.cushy.resize(new_size, new_scale.into(), queue); &mut self,
new_size: Size<UPx>,
new_scale: impl Into<Fraction>,
queue: &wgpu::Queue,
) {
self.cushy
.resize(new_size, new_scale, self.cushy.kludgine.zoom(), queue);
} }
/// Provide keyboard input to this virtual window. /// Provide keyboard input to this virtual window.
@ -3431,7 +3498,7 @@ where
fn redraw(&mut self) { fn redraw(&mut self) {
let mut render_size = self.window.size().ceil(); let mut render_size = self.window.size().ceil();
if self.window.state.size != render_size { if self.window.state.size != render_size {
let current_scale = self.window.scale(); let current_scale = self.window.dpi_scale();
self.window self.window
.resize(self.window.state.size, current_scale, &self.queue); .resize(self.window.state.size, current_scale, &self.queue);
render_size = self.window.state.size; render_size = self.window.state.size;