diff --git a/CHANGELOG.md b/CHANGELOG.md index d8cd79c..f9f60a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 properly when used in a `WidgetInstance` shared between multiple windows. - `WidgetRef::unmount_in` should be called when the widget is being unmounted to clean up individual window state. +- `Dynamic` and `DynamicReader` have had most of their functions moved + into the traits `Source` and `Destination`. This unifies the APIs + between the two types, and offers a path for other specialized reactive data + types to all share a unified API. +- `map_mut` now takes a `Mutable<'_, T>` parameter instead of an `&mut T` + parameter. This type tracks whether the reference is accessed using + `DerefMut`, allowing `map_mut` to skip invoking change callbacks if only + `Deref` is used. ### Fixed @@ -73,6 +81,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `RunningWindow::kludgine_id()` returns a unique id for that window. - `WindowLocal` is a `HashMap`-based type that stores data on a per-window basis using `RunningWindow::kludgine_id()` as the key. +- `Source` and `Destination` are new traits that contain the reactive data + model's API interface. `Dynamic` implements both traits, and + `DynamicReader` implements only `Source`. [99]: https://github.com/khonsulabs/cushy/issues/99 [120]: https://github.com/khonsulabs/cushy/issues/120 diff --git a/examples/animation.rs b/examples/animation.rs index dd0d944..8e7c867 100644 --- a/examples/animation.rs +++ b/examples/animation.rs @@ -1,7 +1,7 @@ use std::time::Duration; use cushy::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn}; -use cushy::value::Dynamic; +use cushy::value::{Destination, Dynamic}; use cushy::widget::MakeWidget; use cushy::widgets::progress::Progressable; use cushy::{Run, WithClone}; diff --git a/examples/background-tasks.rs b/examples/background-tasks.rs index a07c57a..885eb2b 100644 --- a/examples/background-tasks.rs +++ b/examples/background-tasks.rs @@ -4,7 +4,7 @@ use std::time::Duration; use cushy::animation::ZeroToOne; -use cushy::value::{Dynamic, Switchable}; +use cushy::value::{Destination, Dynamic, Switchable}; use cushy::widget::MakeWidget; use cushy::widgets::progress::{Progress, Progressable}; use cushy::Run; diff --git a/examples/basic-button.rs b/examples/basic-button.rs index 356be50..1043c59 100644 --- a/examples/basic-button.rs +++ b/examples/basic-button.rs @@ -1,4 +1,4 @@ -use cushy::value::Dynamic; +use cushy::value::{Destination, Dynamic, Source}; use cushy::widget::MakeWidget; use cushy::Run; diff --git a/examples/buttonception.rs b/examples/buttonception.rs index bd61bec..c5f8a8a 100644 --- a/examples/buttonception.rs +++ b/examples/buttonception.rs @@ -16,7 +16,7 @@ //! ┃ ┃ ┣ clicked_button Label //! ``` -use cushy::value::Dynamic; +use cushy::value::{Destination, Dynamic}; use cushy::widget::MakeWidget; use cushy::widgets::button::{ButtonHoverBackground, ButtonHoverForeground}; use cushy::Run; diff --git a/examples/buttons.rs b/examples/buttons.rs index 281602a..76fe271 100644 --- a/examples/buttons.rs +++ b/examples/buttons.rs @@ -1,4 +1,4 @@ -use cushy::value::Dynamic; +use cushy::value::{Destination, Dynamic, Source}; use cushy::widget::MakeWidget; use cushy::widgets::button::ButtonKind; use cushy::Run; diff --git a/examples/checkbox.rs b/examples/checkbox.rs index cce6cec..eb09aea 100644 --- a/examples/checkbox.rs +++ b/examples/checkbox.rs @@ -1,4 +1,4 @@ -use cushy::value::Dynamic; +use cushy::value::{Destination, Dynamic, Source}; use cushy::widget::MakeWidget; use cushy::widgets::checkbox::{Checkable, CheckboxState}; use cushy::Run; diff --git a/examples/contacts.rs b/examples/contacts.rs index c5b53ab..340d64b 100644 --- a/examples/contacts.rs +++ b/examples/contacts.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use cushy::value::{Dynamic, MapEach}; +use cushy::value::{Dynamic, MapEach, Source}; use cushy::widget::{Children, MakeWidget}; use cushy::widgets::input::InputValue; use cushy::Run; diff --git a/examples/containers.rs b/examples/containers.rs index f937069..52d06d3 100644 --- a/examples/containers.rs +++ b/examples/containers.rs @@ -1,4 +1,4 @@ -use cushy::value::Dynamic; +use cushy::value::{Destination, Dynamic}; use cushy::widget::{MakeWidget, WidgetInstance}; use cushy::widgets::container::ContainerShadow; use cushy::window::ThemeMode; @@ -22,7 +22,7 @@ fn set_of_containers(repeat: usize, theme_mode: Dynamic) -> WidgetIns "Toggle Theme Mode" .into_button() .on_click(move |_| { - theme_mode.map_mut(|mode| mode.toggle()); + theme_mode.map_mut(|mut mode| mode.toggle()); }) .make_widget() }; diff --git a/examples/counter.rs b/examples/counter.rs index 7131a0b..6a1df68 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -1,6 +1,6 @@ use std::string::ToString; -use cushy::value::Dynamic; +use cushy::value::{Dynamic, Source}; use cushy::widget::MakeWidget; use cushy::Run; use figures::units::Lp; diff --git a/examples/custom-widgets.rs b/examples/custom-widgets.rs index 603c623..1e68df8 100644 --- a/examples/custom-widgets.rs +++ b/examples/custom-widgets.rs @@ -1,7 +1,7 @@ //! This example shows two approaches to writing custom widgets: implementing //! traits or using the [`Custom`] widget with callbacks. -use cushy::value::Dynamic; +use cushy::value::{Destination, Dynamic, Source}; use cushy::widget::{MakeWidget, MakeWidgetWithTag, Widget, WidgetInstance, WidgetTag, HANDLED}; use cushy::widgets::Custom; use cushy::Run; diff --git a/examples/debug-window.rs b/examples/debug-window.rs index 338fe4a..2a45288 100644 --- a/examples/debug-window.rs +++ b/examples/debug-window.rs @@ -1,5 +1,5 @@ use cushy::debug::DebugContext; -use cushy::value::Dynamic; +use cushy::value::{Destination, Dynamic, Source}; use cushy::widget::MakeWidget; use cushy::widgets::slider::Slidable; use cushy::{Application, Open, PendingApp}; @@ -46,7 +46,7 @@ fn open_a_window( app: &dyn Application, ) { *window_count.lock() += 1; - let window_number = total_windows.map_mut(|total| { + let window_number = total_windows.map_mut(|mut total| { *total += 1; *total }); diff --git a/examples/focus-order.rs b/examples/focus-order.rs index eb82c56..1d31113 100644 --- a/examples/focus-order.rs +++ b/examples/focus-order.rs @@ -1,6 +1,6 @@ use std::process::exit; -use cushy::value::{Dynamic, MapEach}; +use cushy::value::{Dynamic, MapEach, Source}; use cushy::widget::{MakeWidget, MakeWidgetWithTag, WidgetTag}; use cushy::widgets::grid::{Grid, GridDimension, GridWidgets}; use cushy::widgets::input::{InputValue, MaskedString}; diff --git a/examples/focus.rs b/examples/focus.rs index 0b32b95..d7fe8b6 100644 --- a/examples/focus.rs +++ b/examples/focus.rs @@ -1,4 +1,4 @@ -use cushy::value::Dynamic; +use cushy::value::{Dynamic, Source}; use cushy::widget::MakeWidget; use cushy::widgets::input::InputValue; use cushy::widgets::slider::Slidable; diff --git a/examples/gameui.rs b/examples/gameui.rs index 814bd86..68053c1 100644 --- a/examples/gameui.rs +++ b/examples/gameui.rs @@ -1,4 +1,4 @@ -use cushy::value::Dynamic; +use cushy::value::{Destination, Dynamic}; use cushy::widget::{MakeWidget, HANDLED, IGNORED}; use cushy::widgets::input::InputValue; use cushy::Run; @@ -20,8 +20,8 @@ fn main() -> cushy::Result { .and(chat_message.clone().into_input().on_key(move |input| { match (input.state, input.logical_key) { (ElementState::Pressed, Key::Named(NamedKey::Enter)) => { - let new_message = chat_message.map_mut(std::mem::take); - chat_log.map_mut(|chat_log| { + let new_message = chat_message.take(); + chat_log.map_mut(|mut chat_log| { chat_log.push_str(&new_message); chat_log.push('\n'); }); diff --git a/examples/image.rs b/examples/image.rs index c43f667..575167d 100644 --- a/examples/image.rs +++ b/examples/image.rs @@ -1,5 +1,5 @@ use cushy::animation::ZeroToOne; -use cushy::value::{Dynamic, MapEachCloned}; +use cushy::value::{Dynamic, MapEachCloned, Source}; use cushy::widget::MakeWidget; use cushy::widgets::image::{Aspect, ImageScaling}; use cushy::widgets::slider::Slidable; diff --git a/examples/login.rs b/examples/login.rs index efcdfc9..7e61eff 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -1,6 +1,6 @@ use std::process::exit; -use cushy::value::{Dynamic, Validations}; +use cushy::value::{Dynamic, Source, Validations}; use cushy::widget::MakeWidget; use cushy::widgets::input::{InputValue, MaskedString}; use cushy::widgets::layers::OverlayLayer; diff --git a/examples/multi-window.rs b/examples/multi-window.rs index add53fb..88bf6f9 100644 --- a/examples/multi-window.rs +++ b/examples/multi-window.rs @@ -1,5 +1,5 @@ use cushy::kludgine::include_texture; -use cushy::value::{Dynamic, MapEach}; +use cushy::value::{Destination, Dynamic, MapEach}; use cushy::widget::MakeWidget; use cushy::widgets::Image; use cushy::window::PendingWindow; @@ -63,13 +63,13 @@ fn open_another_window( counter: &Dynamic, texture: &LazyTexture, ) { - let my_number = counter.map_mut(|count| { + let my_number = counter.map_mut(|mut count| { *count += 1; *count }); let open_windows = open_windows.clone(); - open_windows.map_mut(|open_windows| *open_windows += 1); + open_windows.map_mut(|mut open_windows| *open_windows += 1); let window = PendingWindow::default(); let handle = window.handle(); @@ -87,7 +87,7 @@ fn open_another_window( .into_rows() .centered(), ) - .on_close(move || open_windows.map_mut(|open_windows| *open_windows -= 1)) + .on_close(move || open_windows.map_mut(|mut open_windows| *open_windows -= 1)) .open(app) .expect("error opening another window"); } diff --git a/examples/slider.rs b/examples/slider.rs index 7803c8c..6b41960 100644 --- a/examples/slider.rs +++ b/examples/slider.rs @@ -1,5 +1,5 @@ use cushy::animation::{LinearInterpolate, PercentBetween}; -use cushy::value::{Dynamic, ForEach}; +use cushy::value::{Destination, Dynamic, ForEach, Source}; use cushy::widget::MakeWidget; use cushy::widgets::checkbox::Checkable; use cushy::widgets::input::InputValue; diff --git a/examples/switcher.rs b/examples/switcher.rs index e0fe1e4..8662888 100644 --- a/examples/switcher.rs +++ b/examples/switcher.rs @@ -1,4 +1,4 @@ -use cushy::value::{Dynamic, Switchable}; +use cushy::value::{Destination, Dynamic, Switchable}; use cushy::widget::{MakeWidget, WidgetInstance}; use cushy::Run; diff --git a/examples/theme.rs b/examples/theme.rs index 2aaa71c..36f0615 100644 --- a/examples/theme.rs +++ b/examples/theme.rs @@ -5,7 +5,7 @@ use cushy::styles::{ ColorScheme, ColorSchemeBuilder, ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme, ThemePair, }; -use cushy::value::{Dynamic, MapEachCloned}; +use cushy::value::{Destination, Dynamic, MapEachCloned, Source}; use cushy::widget::MakeWidget; use cushy::widgets::checkbox::Checkable; use cushy::widgets::color::ColorSourcePicker; @@ -218,9 +218,10 @@ fn color_editor(color: &Dynamic) -> impl MakeWidget { hue.for_each_cloned({ let color = color.clone(); move |hue| { - let mut source = color.get(); - source.hue = OklabHue::new(hue); - color.set(source); + if let Ok(mut source) = color.try_get() { + source.hue = OklabHue::new(hue); + color.set(source); + } } }) .persist(); @@ -231,9 +232,10 @@ fn color_editor(color: &Dynamic) -> impl MakeWidget { .for_each_cloned({ let color = color.clone(); move |saturation| { - let mut source = color.get(); - source.saturation = saturation; - color.set(source); + if let Ok(mut source) = color.try_get() { + source.saturation = saturation; + color.set(source); + } } }) .persist(); diff --git a/examples/tic-tac-toe.rs b/examples/tic-tac-toe.rs index 2319e49..ba09414 100644 --- a/examples/tic-tac-toe.rs +++ b/examples/tic-tac-toe.rs @@ -3,7 +3,7 @@ use std::iter; use std::ops::Not; use std::time::SystemTime; -use cushy::value::Dynamic; +use cushy::value::{Destination, Dynamic, Source}; use cushy::widget::MakeWidget; use cushy::widgets::button::ButtonKind; use cushy::{Run, WithClone}; diff --git a/examples/tilemap.rs b/examples/tilemap.rs index 77cbe42..b01f620 100644 --- a/examples/tilemap.rs +++ b/examples/tilemap.rs @@ -11,7 +11,7 @@ use cushy::kludgine::tilemap::{ DebugGrid, Object, ObjectLayer, TileArray, TileKind, TileMapFocus, TILE_SIZE, }; use cushy::kludgine::Color; -use cushy::value::Dynamic; +use cushy::value::{Destination, Dynamic}; use cushy::widgets::TileMap; use cushy::{Run, Tick}; use figures::FloatConversion; @@ -71,7 +71,7 @@ fn main() -> cushy::Result { let cursor_pos = input.mouse.as_ref().map(|mouse| mouse.position); - layers.map_mut(|layers| { + layers.map_mut(|mut layers| { let player = &mut layers.1[myself]; let animation_tag = match direction.x.total_cmp(&0.) { diff --git a/examples/validation.rs b/examples/validation.rs index 85f917f..d501c88 100644 --- a/examples/validation.rs +++ b/examples/validation.rs @@ -1,4 +1,4 @@ -use cushy::value::{Dynamic, Validations}; +use cushy::value::{Destination, Dynamic, Validations}; use cushy::widget::MakeWidget; use cushy::widgets::input::InputValue; use cushy::Run; diff --git a/examples/window-properties.rs b/examples/window-properties.rs index d07d1d0..20c070d 100644 --- a/examples/window-properties.rs +++ b/examples/window-properties.rs @@ -1,4 +1,4 @@ -use cushy::value::Dynamic; +use cushy::value::{Dynamic, Source}; use cushy::widget::{MakeWidget, WidgetInstance}; use cushy::Run; use figures::Size; diff --git a/src/animation.rs b/src/animation.rs index 6f8ae60..be7e974 100644 --- a/src/animation.rs +++ b/src/animation.rs @@ -57,7 +57,7 @@ use kludgine::Color; use crate::animation::easings::Linear; use crate::styles::{Component, RequireInvalidation}; use crate::utils::{run_in_bg, IgnorePoison}; -use crate::value::Dynamic; +use crate::value::{Destination, Dynamic, Source}; static ANIMATIONS: Mutex = Mutex::new(Animating::new()); static NEW_ANIMATIONS: Condvar = Condvar::new(); diff --git a/src/context.rs b/src/context.rs index 9d96ee2..0667601 100644 --- a/src/context.rs +++ b/src/context.rs @@ -19,7 +19,7 @@ use crate::styles::components::{ }; use crate::styles::{ComponentDefinition, Styles, Theme, ThemePair}; use crate::tree::Tree; -use crate::value::{IntoValue, Value}; +use crate::value::{IntoValue, Source, Value}; use crate::widget::{EventHandling, MountedWidget, RootBehavior, WidgetId, WidgetInstance}; use crate::window::{CursorState, RunningWindow, ThemeMode}; use crate::ConstraintLimit; diff --git a/src/debug.rs b/src/debug.rs index c01a063..bcf98fa 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -4,7 +4,7 @@ use std::fmt::Debug; use alot::OrderedLots; -use crate::value::{Dynamic, DynamicReader, ForEach, WeakDynamic}; +use crate::value::{Dynamic, DynamicReader, ForEach, Source, WeakDynamic}; use crate::widget::{Children, MakeWidget, WidgetInstance}; use crate::widgets::grid::{Grid, GridWidgets}; use crate::window::Window; diff --git a/src/styles.rs b/src/styles.rs index c4adb6b..f2d9a59 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -23,7 +23,7 @@ use crate::animation::{EasingFunction, ZeroToOne}; use crate::context::WidgetContext; use crate::names::Name; use crate::utils::Lazy; -use crate::value::{Dynamic, IntoValue, Value}; +use crate::value::{Dynamic, IntoValue, Source, Value}; #[macro_use] pub mod components; diff --git a/src/tick.rs b/src/tick.rs index fe757ee..daad98e 100644 --- a/src/tick.rs +++ b/src/tick.rs @@ -11,7 +11,7 @@ use kludgine::app::winit::keyboard::Key; use crate::context::WidgetContext; use crate::utils::IgnorePoison; -use crate::value::Dynamic; +use crate::value::{Destination, Dynamic}; use crate::widget::{EventHandling, HANDLED, IGNORED}; /// A fixed-rate callback that provides access to tracked input on its @@ -211,7 +211,7 @@ where // Signal that we have a new frame, which will cause the widget to // redraw. - data.tick_number.map_mut(|tick| *tick += 1); + data.tick_number.map_mut(|mut tick| *tick += 1); // Wait for a frame to be rendered. while state.keep_running { diff --git a/src/value.rs b/src/value.rs index 1cbc55c..1eaf32b 100644 --- a/src/value.rs +++ b/src/value.rs @@ -17,7 +17,7 @@ use intentional::Assert; use kempt::{Map, Sort}; use crate::animation::{AnimationHandle, DynamicTransition, IntoAnimate, LinearInterpolate, Spawn}; -use crate::context::{self, WidgetContext}; +use crate::context::{self, Trackable, WidgetContext}; use crate::utils::{run_in_bg, IgnorePoison, WithClone}; use crate::widget::{ Children, MakeWidget, MakeWidgetWithTag, OnceCallback, WidgetId, WidgetInstance, @@ -25,6 +25,638 @@ use crate::widget::{ use crate::widgets::{Radio, Select, Space, Switcher}; use crate::window::WindowHandle; +/// A source of one or more `T` values. +pub trait Source { + /// Maps the contents with read-only access, providing access to the value's + /// [`Generation`]. + fn try_map_generational( + &self, + map: impl FnOnce(&GenerationalValue) -> R, + ) -> Result; + + /// Maps the contents with read-only access, providing access to the value's + /// [`Generation`]. + /// + /// # Panics + /// + /// This function panics if this value is already locked by the current + /// thread. + fn map_generational(&self, map: impl FnOnce(&GenerationalValue) -> R) -> R { + self.try_map_generational(map).expect("deadlocked") + } + + /// Returns the current generation of the value. + /// + /// # Panics + /// + /// This function panics if this value is already locked by the current + /// thread. + #[must_use] + fn generation(&self) -> Generation { + self.map_generational(GenerationalValue::generation) + } + + /// Maps the contents with read-only access. + /// + /// # Panics + /// + /// This function panics if this value is already locked by the current + /// thread. + fn map_ref(&self, map: impl FnOnce(&T) -> R) -> R { + self.map_generational(|gen| map(&gen.value)) + } + + /// Returns a clone of the currently contained value. + /// + /// # Panics + /// + /// This function panics if this value is already locked by the current + /// thread. + #[must_use] + fn get(&self) -> T + where + T: Clone, + { + self.map_ref(T::clone) + } + + /// Maps the contents with read-only access. + fn try_map_ref(&self, map: impl FnOnce(&T) -> R) -> Result { + self.try_map_generational(|gen| map(&gen.value)) + } + + /// Returns a clone of the currently contained value. + fn try_get(&self) -> Result + where + T: Clone, + { + self.try_map_generational(|gen| gen.value.clone()) + } + + /// Returns a clone of the currently contained value. + /// + /// `context` will be invalidated when the value is updated. + /// + /// # Panics + /// + /// This function panics if this value is already locked by the current + /// thread. + #[must_use] + fn get_tracking_redraw(&self, context: &WidgetContext<'_, '_>) -> T + where + T: Clone, + Self: Trackable + Sized, + { + context.redraw_when_changed(self); + self.get() + } + + /// Returns a clone of the currently contained value. + /// + /// `context` will be invalidated when the value is updated. + /// + /// # Panics + /// + /// This function panics if this value is already locked by the current + /// thread. + #[must_use] + fn get_tracking_invalidate(&self, context: &WidgetContext<'_, '_>) -> T + where + T: Clone, + Self: Trackable + Sized, + { + context.invalidate_when_changed(self); + self.get() + } + + /// Attaches `for_each` to this value so that it is invoked each time the + /// value's contents are updated. + /// + /// Returning `Err(CallbackDisconnected)` will prevent the callback from + /// being invoked again. + fn for_each_generational_try(&self, for_each: F) -> CallbackHandle + where + T: Send + 'static, + F: for<'a> FnMut(&'a GenerationalValue) -> Result<(), CallbackDisconnected> + + Send + + 'static; + + /// Attaches `for_each` to this value and its [`Generation`] so that it is + /// invoked each time the value's contents are updated. + fn for_each_generational(&self, mut for_each: F) -> CallbackHandle + where + T: Send + 'static, + F: for<'a> FnMut(&'a GenerationalValue) + Send + 'static, + { + self.for_each_generational_try(move |value| { + for_each(value); + Ok(()) + }) + } + + /// Attaches `for_each` to this value so that it is invoked each time the + /// value's contents are updated. + /// + /// Returning `Err(CallbackDisconnected)` will prevent the callback from + /// being invoked again. + fn for_each_try(&self, mut for_each: F) -> CallbackHandle + where + T: Send + 'static, + F: for<'a> FnMut(&'a T) -> Result<(), CallbackDisconnected> + Send + 'static, + { + self.for_each_generational_try(move |gen| for_each(&gen.value)) + } + + /// Attaches `for_each` to this value so that it is invoked each time the + /// value's contents are updated. + fn for_each(&self, mut for_each: F) -> CallbackHandle + where + T: Send + 'static, + F: for<'a> FnMut(&'a T) + Send + 'static, + { + self.for_each_try(move |value| { + for_each(value); + Ok(()) + }) + } + + /// Attaches `for_each` to this value so that it is invoked each time the + /// value's contents are updated. + /// + /// Returning `Err(CallbackDisconnected)` will prevent the callback from + /// being invoked again. + fn for_each_generational_cloned_try(&self, for_each: F) -> CallbackHandle + where + T: Clone + Send + 'static, + F: FnMut(GenerationalValue) -> Result<(), CallbackDisconnected> + Send + 'static; + + /// Attaches `for_each` to this value so that it is invoked each time the + /// value's contents are updated. + fn for_each_cloned_try(&self, mut for_each: F) -> CallbackHandle + where + T: Clone + Send + 'static, + F: FnMut(T) -> Result<(), CallbackDisconnected> + Send + 'static, + { + self.for_each_generational_cloned_try(move |gen| for_each(gen.value)) + } + + /// Attaches `for_each` to this value so that it is invoked each time the + /// value's contents are updated. + fn for_each_cloned(&self, mut for_each: F) -> CallbackHandle + where + T: Clone + Send + 'static, + F: FnMut(T) + Send + 'static, + { + self.for_each_cloned_try(move |value| { + for_each(value); + Ok(()) + }) + } + + /// Returns a new dynamic that contains the updated contents of this dynamic + /// at most once every `period`. + #[must_use] + fn debounced_every(&self, period: Duration) -> Dynamic + where + T: PartialEq + Clone + Send + Sync + 'static, + { + let debounced = Dynamic::new(self.get()); + let mut debounce = Debounce::new(debounced.clone(), period); + let callback = self.for_each_cloned(move |value| debounce.update(value)); + debounced.set_source(callback); + debounced + } + + /// Returns a new dynamic that contains the updated contents of this dynamic + /// delayed by `period`. Each time this value is updated, the delay is + /// reset. + #[must_use] + fn debounced_with_delay(&self, period: Duration) -> Dynamic + where + T: PartialEq + Clone + Send + Sync + 'static, + { + let debounced = Dynamic::new(self.get()); + let mut debounce = Debounce::new(debounced.clone(), period).extending(); + let callback = self.for_each_cloned(move |value| debounce.update(value)); + debounced.set_source(callback); + debounced + } + + /// Creates a new dynamic value that contains the result of invoking `map` + /// each time this value is changed. + fn map_each_generational(&self, mut map: F) -> Dynamic + where + T: Send + 'static, + F: for<'a> FnMut(&'a GenerationalValue) -> R + Send + 'static, + R: PartialEq + Send + 'static, + { + let mapped = Dynamic::new(self.map_generational(&mut map)); + let mapped_weak = mapped.downgrade(); + mapped.set_source(self.for_each_generational_try(move |value| { + let mapped = mapped_weak.upgrade().ok_or(CallbackDisconnected)?; + mapped.set(map(value)); + Ok(()) + })); + mapped + } + + /// Creates a new dynamic value that contains the result of invoking `map` + /// each time this value is changed. + fn map_each(&self, mut map: F) -> Dynamic + where + T: Send + 'static, + F: for<'a> FnMut(&'a T) -> R + Send + 'static, + R: PartialEq + Send + 'static, + { + self.map_each_generational(move |gen| map(&gen.value)) + } + + /// Creates a new dynamic value that contains the result of invoking `map` + /// each time this value is changed. + fn map_each_cloned(&self, mut map: F) -> Dynamic + where + T: Clone + Send + 'static, + F: FnMut(T) -> R + Send + 'static, + R: PartialEq + Send + 'static, + { + let mapped = Dynamic::new(map(self.get())); + let mapped_weak = mapped.downgrade(); + mapped.set_source(self.for_each_cloned_try(move |value| { + let mapped = mapped_weak.upgrade().ok_or(CallbackDisconnected)?; + mapped.set(map(value)); + Ok(()) + })); + mapped + } + + /// Returns a new dynamic that is updated using `U::from(T.clone())` each + /// time `self` is updated. + #[must_use] + fn map_each_into(&self) -> Dynamic + where + U: PartialEq + From + Send + 'static, + T: Clone + Send + 'static, + { + self.map_each(|value| U::from(value.clone())) + } + + /// Returns a new dynamic that is updated using `U::from(&T)` each + /// time `self` is updated. + #[must_use] + fn map_each_to(&self) -> Dynamic + where + U: PartialEq + for<'a> From<&'a T> + Send + 'static, + T: Clone + Send + 'static, + { + self.map_each(|value| U::from(value)) + } +} + +/// A destination for values of type `T`. +pub trait Destination { + /// Maps the contents with exclusive access. Before returning from this + /// function, all observers will be notified that the contents have been + /// updated. + fn try_map_mut(&self, map: impl FnOnce(Mutable<'_, T>) -> R) -> Result; + + /// Maps the contents with exclusive access. Before returning from this + /// function, all observers will be notified that the contents have been + /// updated. + /// + /// # Panics + /// + /// This function panics if this value is already locked by the current + /// thread. + fn map_mut(&self, map: impl FnOnce(Mutable<'_, T>) -> R) -> R { + self.try_map_mut(map).expect("deadlocked") + } + + /// Replaces the contents with `new_value` if `new_value` is different than + /// the currently stored value. If the value is updated, the previous + /// contents are returned. + /// + /// + /// Before returning from this function, all observers will be notified that + /// the contents have been updated. + /// + /// # Errors + /// + /// - [`ReplaceError::NoChange`]: Returned when `new_value` is equal to the + /// currently stored value. + /// - [`ReplaceError::Deadlock`]: Returned when the current thread already + /// has exclusive access to the contents of this dynamic. + fn try_replace(&self, new_value: T) -> Result> + where + T: PartialEq, + { + match self.try_map_mut(|mut value| { + if *value == new_value { + Err(ReplaceError::NoChange(new_value)) + } else { + Ok(std::mem::replace(&mut *value, new_value)) + } + }) { + Ok(old) => old, + Err(DeadlockError) => Err(ReplaceError::Deadlock), + } + } + + /// Replaces the contents with `new_value`, returning the previous contents. + /// Before returning from this function, all observers will be notified that + /// the contents have been updated. + /// + /// If the calling thread has exclusive access to the contents of this + /// dynamic, this call will return None and the value will not be updated. + /// If detecting this is important, use [`Self::try_replace()`]. + fn replace(&self, new_value: T) -> Option + where + T: PartialEq, + { + self.try_replace(new_value).ok() + } + + /// Stores `new_value` in this dynamic. Before returning from this function, + /// all observers will be notified that the contents have been updated. + /// + /// If the calling thread has exclusive access to the contents of this + /// dynamic, this call will return None and the value will not be updated. + /// If detecting this is important, use [`Self::try_replace()`]. + fn set(&self, new_value: T) + where + T: PartialEq, + { + let _old = self.replace(new_value); + } + + /// Replaces the current value with `new_value` if the current value is + /// equal to `expected_current`. + /// + /// Returns `Ok` with the overwritten value upon success. + /// + /// # Errors + /// + /// - [`TryCompareSwapError::Deadlock`]: This operation would result in a + /// thread deadlock. + /// - [`TryCompareSwapError::CurrentValueMismatch`]: The current value did + /// not match `expected_current`. The `T` returned is a clone of the + /// currently stored value. + fn try_compare_swap( + &self, + expected_current: &T, + new_value: T, + ) -> Result> + where + T: Clone + PartialEq, + { + match self.try_map_mut(|mut value| { + if &*value == expected_current { + Ok(std::mem::replace(&mut *value, new_value)) + } else { + Err(TryCompareSwapError::CurrentValueMismatch(value.clone())) + } + }) { + Ok(old) => old, + Err(_) => Err(TryCompareSwapError::Deadlock), + } + } + + /// Replaces the current value with `new_value` if the current value is + /// equal to `expected_current`. + /// + /// Returns `Ok` with the overwritten value upon success. + /// + /// # Errors + /// + /// Returns `Err` with the currently stored value when `expected_current` + /// does not match the currently stored value. + fn compare_swap(&self, expected_current: &T, new_value: T) -> Result + where + T: Clone + PartialEq, + { + match self.try_compare_swap(expected_current, new_value) { + Ok(old) => Ok(old), + Err(TryCompareSwapError::Deadlock) => unreachable!("deadlocked"), + Err(TryCompareSwapError::CurrentValueMismatch(value)) => Err(value), + } + } + + /// Updates the value to the result of invoking [`Not`] on the current + /// value. This function returns the new value. + #[allow(clippy::must_use_candidate)] + fn toggle(&self) -> T + where + T: Not + Clone, + { + self.map_mut(|mut value| { + *value = !value.clone(); + value.clone() + }) + } + + /// Returns the currently stored value, replacing the current contents with + /// `T::default()`. + /// + /// # Panics + /// + /// This function panics if this value is already locked by the current + /// thread. + #[must_use] + fn take(&self) -> T + where + Self: Source, + T: Default, + { + self.map_mut(|mut value| std::mem::take(&mut *value)) + } + + /// Checks if the currently stored value is different than `T::default()`, + /// and if so, returns `Some(self.take())`. + /// + /// # Panics + /// + /// This function panics if this value is already locked by the current + /// thread. + #[must_use] + fn take_if_not_default(&self) -> Option + where + T: Default + PartialEq, + { + let default = T::default(); + self.map_mut(|mut value| { + if *value == default { + None + } else { + Some(std::mem::replace(&mut *value, default)) + } + }) + } +} + +impl Source for Arc> { + fn try_map_generational( + &self, + map: impl FnOnce(&GenerationalValue) -> R, + ) -> Result { + let state = self.state()?; + Ok(map(&state.wrapped)) + } + + fn for_each_generational_try(&self, mut for_each: F) -> CallbackHandle + where + T: Send + 'static, + F: for<'a> FnMut(&'a GenerationalValue) -> Result<(), CallbackDisconnected> + + Send + + 'static, + { + let this = WeakDynamic(Arc::downgrade(self)); + DynamicData::for_each(self, move || { + let this = this.upgrade().ok_or(CallbackDisconnected)?; + this.map_generational(&mut for_each)?; + Ok(()) + }) + } + + fn for_each_generational_cloned_try(&self, mut for_each: F) -> CallbackHandle + where + T: Clone + Send + 'static, + F: FnMut(GenerationalValue) -> Result<(), CallbackDisconnected> + Send + 'static, + { + let this = WeakDynamic(Arc::downgrade(self)); + DynamicData::for_each(self, move || { + let this = this.upgrade().ok_or(CallbackDisconnected)?; + + if let Ok(value) = this.try_map_generational(GenerationalValue::clone) { + for_each(value)?; + } + + Ok(()) + }) + } +} + +impl Source for Dynamic { + fn try_map_generational( + &self, + map: impl FnOnce(&GenerationalValue) -> R, + ) -> Result { + self.0.try_map_generational(map) + } + + fn for_each_generational_try(&self, for_each: F) -> CallbackHandle + where + T: Send + 'static, + F: for<'a> FnMut(&'a GenerationalValue) -> Result<(), CallbackDisconnected> + + Send + + 'static, + { + self.0.for_each_generational_try(for_each) + } + + fn for_each_generational_cloned_try(&self, for_each: F) -> CallbackHandle + where + T: Clone + Send + 'static, + F: FnMut(GenerationalValue) -> Result<(), CallbackDisconnected> + Send + 'static, + { + self.0.for_each_generational_cloned_try(for_each) + } +} + +impl Source for DynamicReader { + fn try_map_generational( + &self, + map: impl FnOnce(&GenerationalValue) -> R, + ) -> Result { + self.source.try_map_generational(|generational| { + *self.read_generation.lock().ignore_poison() = generational.generation; + map(generational) + }) + } + + fn for_each_generational_try(&self, for_each: F) -> CallbackHandle + where + T: Send + 'static, + F: for<'a> FnMut(&'a GenerationalValue) -> Result<(), CallbackDisconnected> + + Send + + 'static, + { + self.source.for_each_generational_try(for_each) + } + + fn for_each_generational_cloned_try(&self, for_each: F) -> CallbackHandle + where + T: Clone + Send + 'static, + F: FnMut(GenerationalValue) -> Result<(), CallbackDisconnected> + Send + 'static, + { + self.source.for_each_generational_cloned_try(for_each) + } +} + +impl Destination for Dynamic { + fn try_map_mut(&self, map: impl FnOnce(Mutable<'_, T>) -> R) -> Result { + self.0.map_mut(map) + } +} + +/// A `mut` reference to `T` that tracks whether the contents have been accessed +/// through `DerefMut`. +#[derive(Debug)] +pub struct Mutable<'a, T> { + value: &'a mut T, + mutated: Mutated<'a>, +} + +#[derive(Debug)] +enum Mutated<'a> { + Tracked(&'a mut bool), + Ignored, +} + +impl Mutated<'_> { + fn set(&mut self, mutated: bool) { + match self { + Self::Tracked(value) => **value = mutated, + Self::Ignored => {} + } + } +} + +impl Deref for Mutable<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value + } +} + +impl DerefMut for Mutable<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.mutated.set(true); + self.value + } +} + +impl<'a, T> Mutable<'a, T> { + /// Creates a new wrapper that sets `mutated` to true when `DerefMut` is + /// used to access `value`. + #[must_use] + pub fn new(value: &'a mut T, mutated: &'a mut bool) -> Self { + *mutated = false; + Self { + value, + mutated: Mutated::Tracked(mutated), + } + } +} + +impl<'a, T> From<&'a mut T> for Mutable<'a, T> { + fn from(value: &'a mut T) -> Self { + Self { + value, + mutated: Mutated::Ignored, + } + } +} + /// An instance of a value that provides APIs to observe and react to its /// contents. pub struct Dynamic(Arc>); @@ -114,27 +746,30 @@ impl Dynamic { TIntoR: FnMut(&T) -> TIntoRResult + Send + 'static, RIntoT: FnMut(&R) -> RIntoTResult + Send + 'static, { - let initial_r = self - .map_ref(|v| t_into_r(v)) - .into() - .expect("t_into_r must succeed with the current value"); - let r = Dynamic::new(initial_r); - r.with_clone(move |r| { - self.for_each(move |t| { - if let Some(update) = t_into_r(t).into() { - let _result = r.replace(update); - } - }) - .persist(); - }); + let r = Dynamic::new( + self.map_ref(|v| t_into_r(v)) + .into() + .expect("t_into_r must succeed with the current value"), + ); + let r_weak = r.downgrade(); + r.set_source(self.for_each_try(move |t| { + let r = r_weak.upgrade().ok_or(CallbackDisconnected)?; + if let Some(update) = t_into_r(t).into() { + r.set(update); + } + Ok(()) + })); - self.with_clone(|t| { - r.with_for_each(move |r| { - if let Some(update) = r_into_t(r).into() { - let _result = t.replace(update); - } - }) - }) + let t_weak = self.downgrade(); + self.set_source(r.for_each_try(move |r| { + let t = t_weak.upgrade().ok_or(CallbackDisconnected)?; + if let Some(update) = r_into_t(r).into() { + let _result = t.replace(update); + } + Ok(()) + })); + + r } /// Creates a [linked](Self::linked) dynamic containing a `String`. @@ -153,53 +788,6 @@ impl Dynamic { self.linked(ToString::to_string, |s: &String| s.parse().ok()) } - /// Maps the contents with read-only access. - /// - /// # Panics - /// - /// This function panics if this value is already locked by the current - /// thread. - pub fn map_ref(&self, map: impl FnOnce(&T) -> R) -> R { - self.map_generational(|gen| map(&gen.value)) - } - - /// Maps the contents with read-only access, providing access to the value's - /// [`Generation`]. - /// - /// # Panics - /// - /// This function panics if this value is already locked by the current - /// thread. - pub fn map_generational(&self, map: impl FnOnce(&GenerationalValue) -> R) -> R { - let state = self.state().expect("deadlocked"); - map(&state.wrapped) - } - - /// Maps the contents with exclusive access. Before returning from this - /// function, all observers will be notified that the contents have been - /// updated. - /// - /// # Panics - /// - /// This function panics if this value is already locked by the current - /// thread. - pub fn map_mut(&self, map: impl FnOnce(&mut T) -> R) -> R { - self.0.map_mut(|value, _| map(value)).expect("deadlocked") - } - - /// Updates the value to the result of invoking [`Not`] on the current - /// value. This function returns the new value. - #[allow(clippy::must_use_candidate)] - pub fn toggle(&self) -> T - where - T: Not + Clone, - { - self.map_mut(|value| { - *value = !value.clone(); - value.clone() - }) - } - /// Sets the current `source` for this dynamic with `source`. /// /// A dynamic can have multiple source callbacks. @@ -210,102 +798,6 @@ impl Dynamic { self.state().assert("deadlocked").source_callback += source; } - /// Returns a new dynamic that contains the updated contents of this dynamic - /// at most once every `period`. - #[must_use] - pub fn debounced_every(&self, period: Duration) -> Self - where - T: PartialEq + Clone + Send + Sync + 'static, - { - let debounced = Dynamic::new(self.get()); - let mut debounce = Debounce::new(debounced.clone(), period); - let callback = self.for_each_cloned(move |value| debounce.update(value)); - debounced.set_source(callback); - debounced - } - - /// Returns a new dynamic that contains the updated contents of this dynamic - /// delayed by `period`. Each time this value is updated, the delay is - /// reset. - #[must_use] - pub fn debounced_with_delay(&self, period: Duration) -> Self - where - T: PartialEq + Clone + Send + Sync + 'static, - { - let debounced = Dynamic::new(self.get()); - let mut debounce = Debounce::new(debounced.clone(), period).extending(); - let callback = self.for_each_cloned(move |value| debounce.update(value)); - debounced.set_source(callback); - debounced - } - - /// Returns a new dynamic that is updated using `U::from(T.clone())` each - /// time `self` is updated. - #[must_use] - pub fn map_each_into(&self) -> Dynamic - where - U: PartialEq + From + Send + 'static, - T: Clone + Send + 'static, - { - self.map_each(|value| U::from(value.clone())) - } - - /// Returns a new dynamic that is updated using `U::from(&T)` each - /// time `self` is updated. - #[must_use] - pub fn map_each_to(&self) -> Dynamic - where - U: PartialEq + for<'a> From<&'a T> + Send + 'static, - T: Clone + Send + 'static, - { - self.map_each(|value| U::from(value)) - } - - /// Attaches `for_each` to this value so that it is invoked each time the - /// value's contents are updated. - pub fn for_each(&self, mut for_each: F) -> CallbackHandle - where - T: Send + 'static, - F: for<'a> FnMut(&'a T) + Send + 'static, - { - let this = self.downgrade(); - self.0.for_each(move || { - let this = this.upgrade().ok_or(CallbackDisconnected)?; - this.map_ref(&mut for_each); - Ok(()) - }) - } - - /// Attaches `for_each` to this value and its [`Generation`] so that it is - /// invoked each time the value's contents are updated. - pub fn for_each_generational(&self, mut for_each: F) -> CallbackHandle - where - T: Send + 'static, - F: for<'a> FnMut(&'a GenerationalValue) + Send + 'static, - { - let this = self.downgrade(); - self.0.for_each(move || { - let this = this.upgrade().ok_or(CallbackDisconnected)?; - this.map_generational(&mut for_each); - Ok(()) - }) - } - - /// Attaches `for_each` to this value so that it is invoked each time the - /// value's contents are updated. - pub fn for_each_cloned(&self, mut for_each: F) -> CallbackHandle - where - T: Clone + Send + 'static, - F: FnMut(T) + Send + 'static, - { - let this = self.downgrade(); - self.0.for_each(move || { - let this = this.upgrade().ok_or(CallbackDisconnected)?; - for_each(this.get()); - Ok(()) - }) - } - /// Attaches `for_each` to this value so that it is invoked each time the /// value's contents are updated. This function returns `self`. #[must_use] @@ -318,51 +810,6 @@ impl Dynamic { self } - /// Creates a new dynamic value that contains the result of invoking `map` - /// each time this value is changed. - pub fn map_each(&self, mut map: F) -> Dynamic - where - T: Send + 'static, - F: for<'a> FnMut(&'a T) -> R + Send + 'static, - R: PartialEq + Send + 'static, - { - let this = self.downgrade(); - self.0.map_each(move || { - let this = this.upgrade().ok_or(CallbackDisconnected)?; - Ok(this.map_ref(&mut map)) - }) - } - - /// Creates a new dynamic value that contains the result of invoking `map` - /// each time this value is changed. - pub fn map_each_generational(&self, mut map: F) -> Dynamic - where - T: Send + 'static, - F: for<'a> FnMut(&'a GenerationalValue) -> R + Send + 'static, - R: PartialEq + Send + 'static, - { - let this = self.downgrade(); - self.0.map_each(move || { - let this = this.upgrade().ok_or(CallbackDisconnected)?; - Ok(this.map_generational(&mut map)) - }) - } - - /// Creates a new dynamic value that contains the result of invoking `map` - /// each time this value is changed. - pub fn map_each_cloned(&self, mut map: F) -> Dynamic - where - T: Clone + Send + 'static, - F: FnMut(T) -> R + Send + 'static, - R: PartialEq + Send + 'static, - { - let this = self.downgrade(); - self.0.map_each(move || { - let this = this.upgrade().ok_or(CallbackDisconnected)?; - Ok(map(this.get())) - }) - } - /// A helper function that invokes `with_clone` with a clone of self. This /// code may produce slightly more readable code. /// @@ -398,201 +845,6 @@ impl Dynamic { self.0.invalidate_when_changed(window, widget); } - /// Returns a clone of the currently contained value. - /// - /// # Panics - /// - /// This function panics if this value is already locked by the current - /// thread. - #[must_use] - pub fn get(&self) -> T - where - T: Clone, - { - self.0.get().expect("deadlocked").value - } - - /// Returns a clone of the currently contained value. - /// - /// `context` will be invalidated when the value is updated. - /// - /// # Panics - /// - /// This function panics if this value is already locked by the current - /// thread. - #[must_use] - pub fn get_tracking_redraw(&self, context: &WidgetContext<'_, '_>) -> T - where - T: Clone, - { - context.redraw_when_changed(self); - self.get() - } - - /// Returns a clone of the currently contained value. - /// - /// `context` will be invalidated when the value is updated. - /// - /// # Panics - /// - /// This function panics if this value is already locked by the current - /// thread. - #[must_use] - pub fn get_tracking_invalidate(&self, context: &WidgetContext<'_, '_>) -> T - where - T: Clone, - { - context.invalidate_when_changed(self); - self.get() - } - - /// Returns the currently stored value, replacing the current contents with - /// `T::default()`. - /// - /// # Panics - /// - /// This function panics if this value is already locked by the current - /// thread. - #[must_use] - pub fn take(&self) -> T - where - T: Default, - { - std::mem::take(&mut self.lock()) - } - - /// Checks if the currently stored value is different than `T::default()`, - /// and if so, returns `Some(self.take())`. - /// - /// # Panics - /// - /// This function panics if this value is already locked by the current - /// thread. - #[must_use] - pub fn take_if_not_default(&self) -> Option - where - T: Default + PartialEq, - { - let default = T::default(); - let mut guard = self.lock(); - if *guard == default { - None - } else { - Some(std::mem::replace(&mut guard, default)) - } - } - - /// Replaces the contents with `new_value`, returning the previous contents. - /// Before returning from this function, all observers will be notified that - /// the contents have been updated. - /// - /// If the calling thread has exclusive access to the contents of this - /// dynamic, this call will return None and the value will not be updated. - /// If detecting this is important, use [`Self::try_replace()`]. - pub fn replace(&self, new_value: T) -> Option - where - T: PartialEq, - { - self.try_replace(new_value).ok() - } - - /// Replaces the contents with `new_value` if `new_value` is different than - /// the currently stored value. If the value is updated, the previous - /// contents are returned. - /// - /// - /// Before returning from this function, all observers will be notified that - /// the contents have been updated. - /// - /// # Errors - /// - /// - [`ReplaceError::NoChange`]: Returned when `new_value` is equal to the - /// currently stored value. - /// - [`ReplaceError::Deadlock`]: Returned when the current thread already - /// has exclusive access to the contents of this dynamic. - pub fn try_replace(&self, new_value: T) -> Result> - where - T: PartialEq, - { - match self.0.map_mut(|value, changed| { - if *value == new_value { - *changed = false; - Err(ReplaceError::NoChange(new_value)) - } else { - Ok(std::mem::replace(value, new_value)) - } - }) { - Ok(old) => old, - Err(_) => Err(ReplaceError::Deadlock), - } - } - - /// Stores `new_value` in this dynamic. Before returning from this function, - /// all observers will be notified that the contents have been updated. - /// - /// If the calling thread has exclusive access to the contents of this - /// dynamic, this call will return None and the value will not be updated. - /// If detecting this is important, use [`Self::try_replace()`]. - pub fn set(&self, new_value: T) - where - T: PartialEq, - { - let _old = self.replace(new_value); - } - - /// Replaces the current value with `new_value` if the current value is - /// equal to `expected_current`. - /// - /// Returns `Ok` with the overwritten value upon success. - /// - /// # Errors - /// - /// - [`TryCompareSwapError::Deadlock`]: This operation would result in a - /// thread deadlock. - /// - [`TryCompareSwapError::CurrentValueMismatch`]: The current value did - /// not match `expected_current`. The `T` returned is a clone of the - /// currently stored value. - pub fn try_compare_swap( - &self, - expected_current: &T, - new_value: T, - ) -> Result> - where - T: Clone + PartialEq, - { - match self.0.map_mut(|value, changed| { - if value == expected_current { - Ok(std::mem::replace(value, new_value)) - } else { - *changed = false; - Err(TryCompareSwapError::CurrentValueMismatch(value.clone())) - } - }) { - Ok(old) => old, - Err(_) => Err(TryCompareSwapError::Deadlock), - } - } - - /// Replaces the current value with `new_value` if the current value is - /// equal to `expected_current`. - /// - /// Returns `Ok` with the overwritten value upon success. - /// - /// # Errors - /// - /// Returns `Err` with the currently stored value when `expected_current` - /// does not match the currently stored value. - pub fn compare_swap(&self, expected_current: &T, new_value: T) -> Result - where - T: Clone + PartialEq, - { - match self.try_compare_swap(expected_current, new_value) { - Ok(old) => Ok(old), - Err(TryCompareSwapError::Deadlock) => unreachable!("deadlocked"), - Err(TryCompareSwapError::CurrentValueMismatch(value)) => Err(value), - } - } - /// Returns a new reference-based reader for this dynamic value. /// /// # Panics @@ -604,7 +856,7 @@ impl Dynamic { self.state().expect("deadlocked").readers += 1; DynamicReader { source: self.0.clone(), - read_generation: self.0.state().expect("deadlocked").wrapped.generation, + read_generation: Mutex::new(self.0.state().expect("deadlocked").wrapped.generation), } } @@ -641,17 +893,6 @@ impl Dynamic { self.0.state() } - /// Returns the current generation of the value. - /// - /// # Panics - /// - /// This function panics if this value is already locked by the current - /// thread. - #[must_use] - pub fn generation(&self) -> Generation { - self.state().expect("deadlocked").wrapped.generation - } - /// Returns a pending transition for this value to `new_value`. pub fn transition_to(&self, new_value: T) -> DynamicTransition where @@ -925,19 +1166,12 @@ impl DynamicData { state.widgets.insert((window, widget)); } - pub fn get(&self) -> Result, DeadlockError> - where - T: Clone, - { - self.state().map(|state| state.wrapped.clone()) - } - - pub fn map_mut(&self, map: impl FnOnce(&mut T, &mut bool) -> R) -> Result { + pub fn map_mut(&self, map: impl FnOnce(Mutable) -> R) -> Result { let mut state = self.state()?; let (old, callbacks) = { let state = &mut *state; - let mut changed = true; - let result = map(&mut state.wrapped.value, &mut changed); + let mut changed = false; + let result = map(Mutable::new(&mut state.wrapped.value, &mut changed)); let callbacks = changed.then(|| state.note_changed()); (result, callbacks) @@ -961,25 +1195,6 @@ impl DynamicData { callbacks: state.callbacks.clone(), })) } - - pub fn map_each(&self, mut map: F) -> Dynamic - where - F: for<'a> FnMut() -> Result + Send + 'static, - R: PartialEq + Send + 'static, - { - let initial_value = map().expect("initial map returned Disconnected"); - let mapped_value = Dynamic::new(initial_value); - let returned = mapped_value.clone(); - - let callback = self.for_each(move || { - mapped_value.set(map()?); - Ok(()) - }); - - returned.set_source(callback); - - returned - } } /// A callback function is no longer connected to its source. @@ -1013,7 +1228,7 @@ pub enum ReplaceError { /// Currently Cushy is only able to detect deadlocks where a single thread tries /// to lock the same [`Dynamic`] multiple times. #[derive(Debug)] -struct DeadlockError; +pub struct DeadlockError; impl std::error::Error for DeadlockError {} @@ -1410,13 +1625,13 @@ impl DerefMut for GenerationalValue { /// If the contents are accessed through [`DerefMut`], all obververs will be /// notified of a change when this guard is dropped. #[derive(Debug)] -pub struct DynamicGuard<'a, T> { +pub struct DynamicGuard<'a, T, const READONLY: bool = false> { guard: DynamicMutexGuard<'a, T>, accessed_mut: bool, prevent_notifications: bool, } -impl DynamicGuard<'_, T> { +impl DynamicGuard<'_, T, READONLY> { /// Returns the generation of the value at the time of locking the dynamic. /// /// Even if this guard accesses the data through [`DerefMut`], this value @@ -1433,7 +1648,7 @@ impl DynamicGuard<'_, T> { } } -impl<'a, T> Deref for DynamicGuard<'a, T> { +impl<'a, T, const READONLY: bool> Deref for DynamicGuard<'a, T, READONLY> { type Target = T; fn deref(&self) -> &Self::Target { @@ -1441,14 +1656,14 @@ impl<'a, T> Deref for DynamicGuard<'a, T> { } } -impl<'a, T> DerefMut for DynamicGuard<'a, T> { +impl<'a, T> DerefMut for DynamicGuard<'a, T, false> { fn deref_mut(&mut self) -> &mut Self::Target { self.accessed_mut = true; &mut self.guard.wrapped.value } } -impl Drop for DynamicGuard<'_, T> { +impl Drop for DynamicGuard<'_, T, READONLY> { fn drop(&mut self) { if self.accessed_mut && !self.prevent_notifications { let mut callbacks = Some(self.guard.note_changed()); @@ -1527,22 +1742,34 @@ impl PartialEq> for Dynamic { /// A reader that tracks the last generation accessed through this reader. pub struct DynamicReader { source: Arc>, - read_generation: Generation, + read_generation: Mutex, } impl DynamicReader { - /// Maps the contents of the dynamic value and returns the result. + /// Returns an read-only, exclusive reference to the contents of this + /// dynamic. /// - /// This function marks the currently stored value as being read. + /// This call will block until all other guards for this dynamic have been + /// dropped. /// /// # Panics /// /// This function panics if this value is already locked by the current /// thread. - pub fn map_ref(&mut self, map: impl FnOnce(&T) -> R) -> R { - let state = self.source.state().expect("deadlocked"); - self.read_generation = state.wrapped.generation; - map(&state.wrapped.value) + #[must_use] + pub fn lock(&self) -> DynamicGuard<'_, T, true> { + DynamicGuard { + guard: self.source.state().expect("deadlocked"), + accessed_mut: false, + prevent_notifications: false, + } + } + + /// Returns the current generation that has been accessed through this + /// reader. + #[must_use] + pub fn read_generation(&self) -> Generation { + *self.read_generation.lock().ignore_poison() } /// Returns true if the dynamic has been modified since the last time the @@ -1554,64 +1781,7 @@ impl DynamicReader { /// thread. #[must_use] pub fn has_updated(&self) -> bool { - self.source.state().expect("deadlocked").wrapped.generation != self.read_generation - } - - /// Returns a clone of the currently contained value. - /// - /// This function marks the currently stored value as being read. - /// - /// # Panics - /// - /// This function panics if this value is already locked by the current - /// thread. - #[must_use] - pub fn get(&mut self) -> T - where - T: Clone, - { - let GenerationalValue { value, generation } = self.source.get().expect("deadlocked"); - self.read_generation = generation; - value - } - - /// Returns a clone of the currently contained value. - /// - /// This function marks the currently stored value as being read. - /// - /// `context` will be invalidated when the value is updated. - /// - /// # Panics - /// - /// This function panics if this value is already locked by the current - /// thread. - #[must_use] - pub fn get_tracking_redraw(&mut self, context: &WidgetContext<'_, '_>) -> T - where - T: Clone, - { - self.source.redraw_when_changed(context.handle()); - self.get() - } - - /// Returns a clone of the currently contained value. - /// - /// This function marks the currently stored value as being read. - /// - /// `context` will be invalidated when the value is updated. - /// - /// # Panics - /// - /// This function panics if this value is already locked by the current - /// thread. - #[must_use] - pub fn get_tracking_invalidate(&mut self, context: &WidgetContext<'_, '_>) -> T - where - T: Clone, - { - self.source - .invalidate_when_changed(context.handle(), context.widget().id()); - self.get() + self.source.state().expect("deadlocked").wrapped.generation != self.read_generation() } /// Blocks the current thread until the contained value has been updated or @@ -1623,7 +1793,7 @@ impl DynamicReader { /// /// This function panics if this value is already locked by the current /// thread. - pub fn block_until_updated(&mut self) -> bool { + pub fn block_until_updated(&self) -> bool { assert!( self.source .during_callback_state @@ -1636,7 +1806,7 @@ impl DynamicReader { ); let mut state = self.source.state.lock().ignore_poison(); loop { - if state.wrapped.generation != self.read_generation { + if state.wrapped.generation != self.read_generation() { return true; } else if state.readers == Arc::strong_count(&self.source) || state.on_disconnect.is_none() @@ -1660,7 +1830,7 @@ impl DynamicReader { /// updated or there are no remaining writers for the value. /// /// Returns true if a newly updated value was discovered. - pub fn wait_until_updated(&mut self) -> BlockUntilUpdatedFuture<'_, T> { + pub fn wait_until_updated(&self) -> BlockUntilUpdatedFuture<'_, T> { BlockUntilUpdatedFuture(self) } @@ -1702,7 +1872,7 @@ where fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DynamicReader") .field("source", &DebugDynamicData(&self.source)) - .field("read_generation", &self.read_generation.0) + .field("read_generation", &self.read_generation().0) .finish() } } @@ -1712,7 +1882,7 @@ impl Clone for DynamicReader { self.source.state().expect("deadlocked").readers += 1; Self { source: self.source.clone(), - read_generation: self.read_generation, + read_generation: Mutex::new(self.read_generation()), } } } @@ -1730,14 +1900,14 @@ impl Drop for DynamicReader { /// Yeilds true if a newly updated value was discovered. #[derive(Debug)] #[must_use = "futures must be .await'ed to be executed"] -pub struct BlockUntilUpdatedFuture<'a, T>(&'a mut DynamicReader); +pub struct BlockUntilUpdatedFuture<'a, T>(&'a DynamicReader); impl<'a, T> Future for BlockUntilUpdatedFuture<'a, T> { type Output = bool; fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { let mut state = self.0.source.state().expect("deadlocked"); - if state.wrapped.generation != self.0.read_generation { + if state.wrapped.generation != self.0.read_generation() { return Poll::Ready(true); } else if state.readers == Arc::strong_count(&self.0.source) || state.on_disconnect.is_none() @@ -1753,7 +1923,7 @@ impl<'a, T> Future for BlockUntilUpdatedFuture<'a, T> { #[test] fn disconnecting_reader_from_dynamic() { let value = Dynamic::new(1); - let mut ref_reader = value.create_reader(); + let ref_reader = value.create_reader(); drop(value); assert!(!ref_reader.block_until_updated()); } @@ -1761,9 +1931,9 @@ fn disconnecting_reader_from_dynamic() { #[test] fn disconnecting_reader_threaded() { let a = Dynamic::new(1); - let mut a_reader = a.create_reader(); + let a_reader = a.create_reader(); let b = Dynamic::new(1); - let mut b_reader = b.create_reader(); + let b_reader = b.create_reader(); let thread = std::thread::spawn(move || { b.set(2); @@ -1787,9 +1957,9 @@ fn disconnecting_reader_threaded() { #[test] fn disconnecting_reader_async() { let a = Dynamic::new(1); - let mut a_reader = a.create_reader(); + let a_reader = a.create_reader(); let b = Dynamic::new(1); - let mut b_reader = b.create_reader(); + let b_reader = b.create_reader(); let async_thread = std::thread::spawn(move || { pollster::block_on(async move { @@ -1973,9 +2143,9 @@ impl Value { } /// Maps the current contents with exclusive access and returns the result. - pub fn map_mut(&mut self, map: impl FnOnce(&mut T) -> R) -> R { + pub fn map_mut(&mut self, map: impl FnOnce(Mutable<'_, T>) -> R) -> R { match self { - Value::Constant(value) => map(value), + Value::Constant(value) => map(Mutable::from(value)), Value::Dynamic(dynamic) => dynamic.map_mut(map), } } @@ -2596,7 +2766,7 @@ impl Validations { where T: Send + 'static, { - self.invalid.map_mut(|invalid| *invalid += 1); + self.invalid.map_mut(|mut invalid| *invalid += 1); let invalid_count = self.invalid.clone(); let dynamic = dynamic.clone(); @@ -2610,9 +2780,9 @@ impl Validations { }; if invalid != new_invalid { if new_invalid { - invalid_count.map_mut(|invalid| *invalid += 1); + invalid_count.map_mut(|mut invalid| *invalid += 1); } else { - invalid_count.map_mut(|invalid| *invalid -= 1); + invalid_count.map_mut(|mut invalid| *invalid -= 1); } invalid = new_invalid; } @@ -2892,3 +3062,34 @@ fn ref_counts() { assert!(invoked.get()); assert!(!reader.connected()); } + +#[test] +fn linked_short_circuit() { + let usize = Dynamic::new(0_usize); + let string = usize.linked_string(); + + string.map_ref(|s| assert_eq!(s, "0")); + string.set(String::from("1")); + assert_eq!(usize.get(), 1); + usize.set(2); + string.map_ref(|s| assert_eq!(s, "2")); +} + +#[test] +fn graph_shortcircuit() { + let a = Dynamic::new(0_usize); + let doubled = a.map_each_cloned(|a| a * 2); + let quadrupled = doubled.map_each_cloned(|a| a * 2); + a.set_source(quadrupled.for_each_cloned({ + let a = a.clone(); + move |quad| a.set(quad / 4) + })); + + assert_eq!(a.get(), 0); + assert_eq!(quadrupled.get(), 0); + a.set(1); + assert_eq!(quadrupled.get(), 4); + quadrupled.set(16); + assert_eq!(a.get(), 4); + assert_eq!(doubled.get(), 8); +} diff --git a/src/widgets/align.rs b/src/widgets/align.rs index b3402f4..9f2219c 100644 --- a/src/widgets/align.rs +++ b/src/widgets/align.rs @@ -36,7 +36,7 @@ impl Align { #[must_use] pub fn align_left(mut self) -> Self { self.edges - .map_mut(|edges| edges.left = FlexibleDimension::ZERO); + .map_mut(|mut edges| edges.left = FlexibleDimension::ZERO); self } @@ -44,7 +44,7 @@ impl Align { #[must_use] pub fn align_top(mut self) -> Self { self.edges - .map_mut(|edges| edges.top = FlexibleDimension::ZERO); + .map_mut(|mut edges| edges.top = FlexibleDimension::ZERO); self } @@ -52,7 +52,7 @@ impl Align { #[must_use] pub fn align_bottom(mut self) -> Self { self.edges - .map_mut(|edges| edges.bottom = FlexibleDimension::ZERO); + .map_mut(|mut edges| edges.bottom = FlexibleDimension::ZERO); self } @@ -60,14 +60,14 @@ impl Align { #[must_use] pub fn align_right(mut self) -> Self { self.edges - .map_mut(|edges| edges.right = FlexibleDimension::ZERO); + .map_mut(|mut edges| edges.right = FlexibleDimension::ZERO); self } /// Sets the left and right edges of alignment to 0 and returns self. #[must_use] pub fn fit_horizontally(mut self) -> Self { - self.edges.map_mut(|edges| { + self.edges.map_mut(|mut edges| { edges.left = FlexibleDimension::ZERO; edges.right = FlexibleDimension::ZERO; }); @@ -77,7 +77,7 @@ impl Align { /// Sets the top and bottom edges of alignment to 0 and returns self. #[must_use] pub fn fit_vertically(mut self) -> Self { - self.edges.map_mut(|edges| { + self.edges.map_mut(|mut edges| { edges.top = FlexibleDimension::ZERO; edges.bottom = FlexibleDimension::ZERO; }); diff --git a/src/widgets/button.rs b/src/widgets/button.rs index aaa1506..016d0d4 100644 --- a/src/widgets/button.rs +++ b/src/widgets/button.rs @@ -19,7 +19,7 @@ use crate::styles::components::{ HighlightColor, IntrinsicPadding, OpaqueWidgetColor, OutlineColor, SurfaceColor, TextColor, }; use crate::styles::{ColorExt, Styles}; -use crate::value::{Dynamic, IntoValue, Value}; +use crate::value::{Destination, Dynamic, IntoValue, Source, Value}; use crate::widget::{Callback, EventHandling, MakeWidget, Widget, WidgetRef, HANDLED}; use crate::window::WindowLocal; use crate::FitMeasuredSize; diff --git a/src/widgets/checkbox.rs b/src/widgets/checkbox.rs index 8a26f9f..11f949e 100644 --- a/src/widgets/checkbox.rs +++ b/src/widgets/checkbox.rs @@ -10,7 +10,7 @@ use kludgine::shapes::{PathBuilder, Shape, StrokeOptions}; use crate::context::{GraphicsContext, LayoutContext}; use crate::styles::components::{LineHeight, OutlineColor, TextColor, WidgetAccentColor}; use crate::styles::Dimension; -use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoValue, Value}; +use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoValue, Source, Value}; use crate::widget::{MakeWidget, MakeWidgetWithTag, Widget, WidgetInstance}; use crate::widgets::button::ButtonKind; use crate::ConstraintLimit; diff --git a/src/widgets/collapse.rs b/src/widgets/collapse.rs index 3027d00..d89caac 100644 --- a/src/widgets/collapse.rs +++ b/src/widgets/collapse.rs @@ -6,7 +6,7 @@ use figures::Size; use crate::animation::easings::{EaseInQuadradic, EaseOutQuadradic}; use crate::animation::{AnimationHandle, AnimationTarget, EasingFunction, Spawn}; use crate::context::LayoutContext; -use crate::value::{Dynamic, IntoDynamic}; +use crate::value::{Dynamic, IntoDynamic, Source}; use crate::widget::{MakeWidget, WidgetRef, WrappedLayout, WrapperWidget}; use crate::ConstraintLimit; diff --git a/src/widgets/color.rs b/src/widgets/color.rs index 5db525b..624b5d6 100644 --- a/src/widgets/color.rs +++ b/src/widgets/color.rs @@ -12,7 +12,7 @@ use crate::animation::ZeroToOne; use crate::context::{EventContext, GraphicsContext}; use crate::styles::components::{HighlightColor, OutlineColor, TextColor}; use crate::styles::{ColorExt, ColorSource}; -use crate::value::{Dynamic, IntoValue, Value}; +use crate::value::{Destination, Dynamic, IntoValue, Source, Value}; use crate::widget::{EventHandling, Widget, HANDLED}; /// A widget that selects a [`ColorSource`]. diff --git a/src/widgets/container.rs b/src/widgets/container.rs index 2e7555f..254503e 100644 --- a/src/widgets/container.rs +++ b/src/widgets/container.rs @@ -10,7 +10,7 @@ use kludgine::Color; use crate::context::{EventContext, GraphicsContext, LayoutContext, WidgetContext}; use crate::styles::components::{CornerRadius, IntrinsicPadding, Opacity, SurfaceColor}; use crate::styles::{Component, ContainerLevel, Dimension, Edges, RequireInvalidation, Styles}; -use crate::value::{Dynamic, IntoValue, Value}; +use crate::value::{Dynamic, IntoValue, Source, Value}; use crate::widget::{MakeWidget, RootBehavior, Widget, WidgetInstance, WidgetRef}; use crate::ConstraintLimit; diff --git a/src/widgets/disclose.rs b/src/widgets/disclose.rs index 0e8204a..321ca26 100644 --- a/src/widgets/disclose.rs +++ b/src/widgets/disclose.rs @@ -13,7 +13,7 @@ use crate::animation::{AnimationHandle, AnimationTarget, Spawn}; use crate::context::{EventContext, LayoutContext}; use crate::styles::components::{HighlightColor, IntrinsicPadding, LineHeight, OutlineColor}; use crate::styles::Dimension; -use crate::value::{Dynamic, IntoDynamic, IntoValue, Value}; +use crate::value::{Destination, Dynamic, IntoDynamic, IntoValue, Source, Value}; use crate::widget::{ EventHandling, MakeWidget, MakeWidgetWithTag, Widget, WidgetInstance, WidgetRef, WidgetTag, HANDLED, IGNORED, diff --git a/src/widgets/image.rs b/src/widgets/image.rs index 76174a5..eeaaa9d 100644 --- a/src/widgets/image.rs +++ b/src/widgets/image.rs @@ -6,7 +6,7 @@ use kludgine::{AnyTexture, CollectedTexture, LazyTexture, SharedTexture, Texture use crate::animation::ZeroToOne; use crate::context::LayoutContext; -use crate::value::{IntoValue, Value}; +use crate::value::{IntoValue, Source, Value}; use crate::widget::Widget; use crate::ConstraintLimit; diff --git a/src/widgets/input.rs b/src/widgets/input.rs index 67f66af..bcfbc93 100644 --- a/src/widgets/input.rs +++ b/src/widgets/input.rs @@ -25,7 +25,7 @@ use zeroize::Zeroizing; use crate::context::{EventContext, GraphicsContext, LayoutContext}; use crate::styles::components::{HighlightColor, IntrinsicPadding, OutlineColor, TextColor}; use crate::utils::ModifiersExt; -use crate::value::{Dynamic, Generation, IntoDynamic, IntoValue, Value}; +use crate::value::{Destination, Dynamic, Generation, IntoDynamic, IntoValue, Source, Value}; use crate::widget::{Callback, EventHandling, Widget, HANDLED, IGNORED}; use crate::{ConstraintLimit, Lazy}; @@ -189,7 +189,7 @@ where } fn replace_range(&mut self, start: Cursor, end: Cursor, new_text: &str) { - self.value.map_mut(|value| { + self.value.map_mut(|mut value| { let value = value.as_string_mut(); let start = start.offset.min(value.len().saturating_sub(1)); let end = end.offset.min(value.len()); diff --git a/src/widgets/layers.rs b/src/widgets/layers.rs index 03bfb0d..ae5ab61 100644 --- a/src/widgets/layers.rs +++ b/src/widgets/layers.rs @@ -14,7 +14,7 @@ use crate::animation::easings::EaseOutQuadradic; use crate::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn, ZeroToOne}; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext}; use crate::utils::IgnorePoison; -use crate::value::{Dynamic, DynamicGuard, IntoValue, Value}; +use crate::value::{Destination, Dynamic, DynamicGuard, IntoValue, Source, Value}; use crate::widget::{ Callback, Children, MakeWidget, MountedChildren, MountedWidget, Widget, WidgetId, WidgetRef, WrapperWidget, @@ -593,7 +593,7 @@ impl OverlayBuilder<'_> { #[must_use] pub fn show(self) -> OverlayHandle { self.fade_in(); - self.overlay.state.map_mut(|state| { + self.overlay.state.map_mut(|mut state| { state.new_overlays += 1; OverlayHandle { state: self.overlay.state.clone(), diff --git a/src/widgets/progress.rs b/src/widgets/progress.rs index 85225eb..f761a4e 100644 --- a/src/widgets/progress.rs +++ b/src/widgets/progress.rs @@ -12,7 +12,7 @@ use crate::animation::easings::{EaseInQuadradic, EaseOutQuadradic}; use crate::animation::{ AnimationHandle, AnimationTarget, IntoAnimate, PercentBetween, Spawn, ZeroToOne, }; -use crate::value::{Dynamic, IntoDynamic, IntoValue, MapEach, Value}; +use crate::value::{Destination, Dynamic, IntoDynamic, IntoValue, MapEach, Source, Value}; use crate::widget::{MakeWidget, MakeWidgetWithTag, Widget, WidgetInstance}; use crate::widgets::slider::{InactiveTrackColor, Slidable, TrackColor, TrackSize}; use crate::widgets::Data; diff --git a/src/widgets/radio.rs b/src/widgets/radio.rs index eae447f..48b64b5 100644 --- a/src/widgets/radio.rs +++ b/src/widgets/radio.rs @@ -9,7 +9,7 @@ use kludgine::DrawableExt; use crate::context::{GraphicsContext, LayoutContext}; use crate::styles::components::{LineHeight, OutlineColor, WidgetAccentColor}; use crate::styles::Dimension; -use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoValue, Value}; +use crate::value::{Destination, Dynamic, DynamicReader, IntoDynamic, IntoValue, Source, Value}; use crate::widget::{MakeWidget, MakeWidgetWithTag, Widget, WidgetInstance}; use crate::widgets::button::ButtonKind; use crate::ConstraintLimit; diff --git a/src/widgets/scroll.rs b/src/widgets/scroll.rs index 9c316fa..153cad8 100644 --- a/src/widgets/scroll.rs +++ b/src/widgets/scroll.rs @@ -13,7 +13,7 @@ use crate::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn, Zer use crate::context::{AsEventContext, EventContext, LayoutContext}; use crate::styles::components::{EasingIn, EasingOut, LineHeight}; use crate::styles::Dimension; -use crate::value::Dynamic; +use crate::value::{Destination, Dynamic, Source}; use crate::widget::{EventHandling, MakeWidget, Widget, WidgetRef, HANDLED, IGNORED}; use crate::ConstraintLimit; diff --git a/src/widgets/select.rs b/src/widgets/select.rs index 7a53daa..4b608d9 100644 --- a/src/widgets/select.rs +++ b/src/widgets/select.rs @@ -5,7 +5,7 @@ use kludgine::Color; use crate::styles::components::OutlineColor; use crate::styles::{Component, DynamicComponent}; -use crate::value::{Dynamic, IntoDynamic, IntoValue, MapEach, Value}; +use crate::value::{Destination, Dynamic, IntoDynamic, IntoValue, MapEach, Source, Value}; use crate::widget::{MakeWidget, MakeWidgetWithTag, WidgetInstance}; use crate::widgets::button::{ButtonBackground, ButtonHoverBackground, ButtonKind}; diff --git a/src/widgets/slider.rs b/src/widgets/slider.rs index 2c3de1d..b720620 100644 --- a/src/widgets/slider.rs +++ b/src/widgets/slider.rs @@ -19,7 +19,7 @@ use crate::styles::components::{ WidgetAccentColor, }; use crate::styles::{Dimension, HorizontalOrder, VerticalOrder, VisualOrder}; -use crate::value::{Dynamic, IntoDynamic, IntoValue, Value}; +use crate::value::{Destination, Dynamic, IntoDynamic, IntoValue, Source, Value}; use crate::widget::{EventHandling, Widget, HANDLED, IGNORED}; use crate::ConstraintLimit; @@ -445,7 +445,7 @@ where let mut max = self.maximum.get_tracking_redraw(context); if max < min { - self.maximum.map_mut(|max| *max = min.clone()); + self.maximum.map_mut(|mut max| *max = min.clone()); max = min.clone(); } let mut value_clamped = false; @@ -472,7 +472,7 @@ where if value_clamped { self.value - .map_mut(|v| *v = T::from_parts(start_value.clone(), end_value.clone())); + .map_mut(|mut v| *v = T::from_parts(start_value.clone(), end_value.clone())); } let start_percent = start_value.percent_between(&min, &max); diff --git a/src/widgets/style.rs b/src/widgets/style.rs index a40867a..e423d0a 100644 --- a/src/widgets/style.rs +++ b/src/widgets/style.rs @@ -11,7 +11,7 @@ use crate::styles::components::{ use crate::styles::{ ComponentDefinition, IntoComponentValue, IntoDynamicComponentValue, StoredComponent, Styles, }; -use crate::value::{IntoValue, Value}; +use crate::value::{Destination, IntoValue, Mutable, Value}; use crate::widget::{MakeWidget, WidgetRef, WrapperWidget}; /// A widget that applies a set of [`Styles`] to all contained widgets. @@ -31,9 +31,9 @@ impl Style { } } - fn map_styles_mut(&mut self, map: impl FnOnce(&mut Styles) -> R) -> R { + fn map_styles_mut(&mut self, map: impl FnOnce(Mutable<'_, Styles>) -> R) -> R { match &mut self.styles { - Value::Constant(styles) => map(styles), + Value::Constant(styles) => map(Mutable::from(styles)), Value::Dynamic(dynamic) => dynamic.map_mut(map), } } @@ -48,7 +48,7 @@ impl Style { where Value: IntoComponentValue, { - self.map_styles_mut(|styles| { + self.map_styles_mut(|mut styles| { styles.insert(name, component.into_value()); }); self @@ -64,7 +64,7 @@ impl Style { where Value: IntoComponentValue, { - self.map_styles_mut(|styles| { + self.map_styles_mut(|mut styles| { styles.insert_named( name.name().into_owned(), StoredComponent::local(component.into_value()), @@ -84,7 +84,7 @@ impl Style { where Value: IntoComponentValue, { - self.map_styles_mut(|styles| { + self.map_styles_mut(|mut styles| { styles.insert_dynamic(name, dynamic); }); self diff --git a/src/widgets/switcher.rs b/src/widgets/switcher.rs index 422e1b6..a412123 100644 --- a/src/widgets/switcher.rs +++ b/src/widgets/switcher.rs @@ -3,7 +3,7 @@ use std::fmt::Debug; use figures::Size; use crate::context::LayoutContext; -use crate::value::{Dynamic, DynamicReader, IntoDynamic}; +use crate::value::{Dynamic, DynamicReader, IntoDynamic, Source}; use crate::widget::{WidgetInstance, WidgetRef, WrapperWidget}; use crate::ConstraintLimit; @@ -37,7 +37,7 @@ impl Switcher { /// `widget_factory` each time `value` changes. #[must_use] pub fn new(source: impl IntoDynamic) -> Self { - let mut source = source.into_dynamic().into_reader(); + let source = source.into_dynamic().into_reader(); let child = WidgetRef::new(source.get()); Self { source, child } } diff --git a/src/widgets/validated.rs b/src/widgets/validated.rs index a7df260..e589a74 100644 --- a/src/widgets/validated.rs +++ b/src/widgets/validated.rs @@ -8,7 +8,9 @@ use crate::styles::components::{ ErrorColor, LineHeight, LineHeight2, OutlineColor, TextColor, TextSize, TextSize2, }; use crate::styles::Dimension; -use crate::value::{Dynamic, IntoDynamic, IntoValue, MapEach, Validation, Value}; +use crate::value::{ + Destination, Dynamic, IntoDynamic, IntoValue, MapEach, Source, Validation, Value, +}; use crate::widget::{MakeWidget, MakeWidgetWithTag, WidgetInstance, WidgetRef, WrapperWidget}; /// A widget that displays validation information around another widget. diff --git a/src/window.rs b/src/window.rs index 29290bd..503ecf9 100644 --- a/src/window.rs +++ b/src/window.rs @@ -37,7 +37,9 @@ use crate::graphics::{FontState, Graphics}; use crate::styles::{Edges, FontFamilyList, ThemePair}; use crate::tree::Tree; use crate::utils::ModifiersExt; -use crate::value::{Dynamic, DynamicReader, Generation, IntoDynamic, IntoValue, Value}; +use crate::value::{ + Destination, Dynamic, DynamicReader, Generation, IntoDynamic, IntoValue, Source, Value, +}; use crate::widget::{ EventHandling, MakeWidget, MountedWidget, OnceCallback, RootBehavior, Widget, WidgetId, WidgetInstance, HANDLED, IGNORED,