mirror of
https://github.com/danbulant/cushy
synced 2026-06-18 22:11:34 +00:00
ReadOnly<T>, Owned<T>, IntoSource<T>, more
Closes #98 This finishes my initial refactoring of the dynamic system to add support for several dataflows including: - Pure data sources that can be implemented using an `Owned<T>` at the root of a graph of `Dynamic<U>`/`DynamicReader<U>`s. - Read-only data sinks. I thought this would be more useful across other widgets, but in general, Progress and Label seem like the only types that this applies to currently. - The ability to mix/match Dynamic/DynamicReader in tuple-based for_each/map_each.
This commit is contained in:
parent
8b19c4c304
commit
83e44912ee
18 changed files with 641 additions and 155 deletions
26
CHANGELOG.md
26
CHANGELOG.md
|
|
@ -22,6 +22,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
parameter. This type tracks whether the reference is accessed using
|
||||
`DerefMut`, allowing `map_mut` to skip invoking change callbacks if only
|
||||
`Deref` is used.
|
||||
- `redraw_when_changed()`/`invalidate_when_changed()` from some types have been
|
||||
moved to the `Trackable` trait. This was to ensure all trackable types provide
|
||||
the same API.
|
||||
- `Label` has been refactored to accept any `Display` type. As a result of this,
|
||||
`Label::text` is now named `display` and `Label::new()` now accepts an
|
||||
`IntoReadOnly<T>` instead of `IntoValue<String>`.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
@ -44,6 +50,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
are waiting for value when the last `Dynamic` is dropped.
|
||||
- Compatibility with Rust v1.70.0 has been restored, and continuous integration
|
||||
testing the MSRV has been added.
|
||||
- `Progress` now utilizes `IntoSource<Progress>` instead of
|
||||
`IntoDynamic<Progress>`. In general, this should not cause any code breakages
|
||||
unless the traits were being used in generics.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
@ -52,6 +61,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
the cache is invalidated.
|
||||
- All `Dynamic` mapping functions now utilize weak references, and clean up as
|
||||
necessary if a value is not able to be upgraded.
|
||||
- `ForEach`/`MapEach`'s implementations for tuples are now defined using
|
||||
`Source<T>` and `DynamicRead<T>`. This allows combinations of `Dynamic<T>`s
|
||||
and `DynamicReader<T>`s to be used in for_each/map_each expressions.
|
||||
|
||||
### Added
|
||||
|
||||
|
|
@ -84,6 +96,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- `Source<T>` and `Destination<T>` are new traits that contain the reactive data
|
||||
model's API interface. `Dynamic<T>` implements both traits, and
|
||||
`DynamicReader<T>` implements only `Source<T>`.
|
||||
- `DynamicRead<T>` is a new trait that provides read-only access to a dynamic's
|
||||
contents.
|
||||
- `IntoReadOnly<T>` is a new trait that types can implement to convert into a
|
||||
`ReadOnly<T>`.
|
||||
- `IntoReader<T>` is a new trait that types can implement to convert into a
|
||||
`DynamicReader<T>`.
|
||||
- `ReadOnly<T>` is a type similar to `Value<T>` but instead of possibly being a
|
||||
`Dynamic<T>`, `ReadOnly::Reader` contains a `DynamicReader<T>`. This type can
|
||||
be used where widgets that receive a value but never mutate it.
|
||||
- `Owned<T>` is a new type that can be used where no shared ownership is
|
||||
necessary. This type uses a `RefCell` internally instead of an `Arc` +
|
||||
`Mutex`. `Owned<T>` implements `Source<T>` and `Destination<T>`.
|
||||
- `GenerationalValue<T>` now implements `Default` when `T` does.
|
||||
- `Value<T>` now implements `From<Dynamic<T>>`.
|
||||
|
||||
[99]: https://github.com/khonsulabs/cushy/issues/99
|
||||
[120]: https://github.com/khonsulabs/cushy/issues/120
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use cushy::value::{Destination, Dynamic, Source};
|
||||
use cushy::value::{Destination, Dynamic, IntoReader, Source};
|
||||
use cushy::widget::MakeWidget;
|
||||
use cushy::Run;
|
||||
|
||||
|
|
@ -6,11 +6,12 @@ use cushy::Run;
|
|||
fn main() -> cushy::Result {
|
||||
// Create a dynamic usize.
|
||||
let count = Dynamic::new(0_isize);
|
||||
// Create a dynamic that contains `count.to_string()`
|
||||
let count_label = count.map_each(ToString::to_string);
|
||||
|
||||
// Create a new button whose text is our dynamic string.
|
||||
count_label
|
||||
// Create a new label displaying `count`
|
||||
count
|
||||
.clone()
|
||||
.into_label()
|
||||
// Use the label as the contents of a button
|
||||
.into_button()
|
||||
// Set the `on_click` callback to a closure that increments the counter.
|
||||
.on_click(move |_| count.set(count.get() + 1))
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
use std::string::ToString;
|
||||
|
||||
use cushy::value::{Dynamic, Source};
|
||||
use cushy::value::{Dynamic, IntoReader};
|
||||
use cushy::widget::MakeWidget;
|
||||
use cushy::Run;
|
||||
use figures::units::Lp;
|
||||
|
||||
fn main() -> cushy::Result {
|
||||
let counter = Dynamic::new(0i32);
|
||||
let label = counter.map_each(ToString::to_string);
|
||||
|
||||
label
|
||||
counter
|
||||
.clone()
|
||||
.into_label()
|
||||
.width(Lp::points(100))
|
||||
.and("+".into_button().on_click(counter.with_clone(|counter| {
|
||||
move |_| {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use cushy::debug::DebugContext;
|
||||
use cushy::value::{Destination, Dynamic, Source};
|
||||
use cushy::value::{Destination, Dynamic, IntoReader};
|
||||
use cushy::widget::MakeWidget;
|
||||
use cushy::widgets::slider::Slidable;
|
||||
use cushy::{Application, Open, PendingApp};
|
||||
|
|
@ -25,7 +25,7 @@ fn main() -> cushy::Result {
|
|||
|
||||
info.observe("Open Windows", &window_count, |window_count| {
|
||||
window_count
|
||||
.map_each(ToString::to_string)
|
||||
.into_label()
|
||||
.and(open_window_button.clone())
|
||||
.into_columns()
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use cushy::animation::{LinearInterpolate, PercentBetween};
|
||||
use cushy::value::{Destination, Dynamic, ForEach, Source};
|
||||
use cushy::value::{Destination, Dynamic, ForEach, IntoReader, Source};
|
||||
use cushy::widget::MakeWidget;
|
||||
use cushy::widgets::checkbox::Checkable;
|
||||
use cushy::widgets::input::InputValue;
|
||||
|
|
@ -31,7 +31,6 @@ fn u8_slider() -> impl MakeWidget {
|
|||
let max = Dynamic::new(u8::MAX);
|
||||
let max_text = max.linked_string();
|
||||
let value = Dynamic::new(128_u8);
|
||||
let value_text = value.map_each(ToString::to_string);
|
||||
|
||||
"Min"
|
||||
.and(min_text.into_input())
|
||||
|
|
@ -39,8 +38,8 @@ fn u8_slider() -> impl MakeWidget {
|
|||
.and(max_text.into_input())
|
||||
.into_columns()
|
||||
.centered()
|
||||
.and(value.slider_between(min, max))
|
||||
.and(value_text.centered())
|
||||
.and(value.clone().slider_between(min, max))
|
||||
.and(value.into_label().centered())
|
||||
.into_rows()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -969,12 +969,12 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
|
|||
|
||||
/// Ensures that this widget will be redrawn when `value` has been updated.
|
||||
pub fn redraw_when_changed(&self, value: &impl Trackable) {
|
||||
value.redraw_when_changed(self.handle());
|
||||
value.inner_redraw_when_changed(self.handle());
|
||||
}
|
||||
|
||||
/// Ensures that this widget will be redrawn when `value` has been updated.
|
||||
pub fn invalidate_when_changed(&self, value: &impl Trackable) {
|
||||
value.invalidate_when_changed(self.handle(), self.current_node.id());
|
||||
value.inner_invalidate_when_changed(self.handle(), self.current_node.id());
|
||||
}
|
||||
|
||||
/// Returns the last layout of this widget.
|
||||
|
|
@ -1344,7 +1344,27 @@ impl Default for WidgetCacheKey {
|
|||
}
|
||||
|
||||
/// A type that can be tracked to refresh or invalidate widgets.
|
||||
pub trait Trackable: sealed::Trackable {}
|
||||
pub trait Trackable: sealed::Trackable {
|
||||
/// Marks the widget for redraw when this value is updated.
|
||||
///
|
||||
/// This function has no effect if the value is constant.
|
||||
fn redraw_when_changed(&self, context: &WidgetContext<'_, '_>)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
context.redraw_when_changed(self);
|
||||
}
|
||||
|
||||
/// Marks the widget for redraw when this value is updated.
|
||||
///
|
||||
/// This function has no effect if the value is constant.
|
||||
fn invalidate_when_changed(&self, context: &WidgetContext<'_, '_>)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
context.invalidate_when_changed(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Trackable for T where T: sealed::Trackable {}
|
||||
|
||||
|
|
@ -1359,8 +1379,8 @@ pub(crate) mod sealed {
|
|||
use crate::window::WindowHandle;
|
||||
|
||||
pub trait Trackable {
|
||||
fn redraw_when_changed(&self, handle: WindowHandle);
|
||||
fn invalidate_when_changed(&self, handle: WindowHandle, id: WidgetId);
|
||||
fn inner_redraw_when_changed(&self, handle: WindowHandle);
|
||||
fn inner_invalidate_when_changed(&self, handle: WindowHandle, id: WidgetId);
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ pub use palette::OklabHue;
|
|||
use palette::{IntoColor, Okhsl, Srgb};
|
||||
|
||||
use crate::animation::{EasingFunction, ZeroToOne};
|
||||
use crate::context::WidgetContext;
|
||||
use crate::context::{Trackable, WidgetContext};
|
||||
use crate::names::Name;
|
||||
use crate::utils::Lazy;
|
||||
use crate::value::{Dynamic, IntoValue, Source, Value};
|
||||
|
|
|
|||
22
src/utils.rs
22
src/utils.rs
|
|
@ -7,18 +7,32 @@ use kludgine::app::winit::event::Modifiers;
|
|||
use kludgine::app::winit::keyboard::ModifiersState;
|
||||
|
||||
/// Invokes the provided macro with a pattern that can be matched using this
|
||||
/// `macro_rules!` expression: `$($type:ident $field:tt $var:ident),+`, where `$type` is an
|
||||
/// identifier to use for the generic parameter and `$field` is the field index
|
||||
/// inside of the tuple.
|
||||
/// `macro_rules!` expression: `$($type:ident $field:tt $var:ident),+`, where
|
||||
/// `$type` is an identifier to use for the generic parameter and `$field` is
|
||||
/// the field index inside of the tuple.
|
||||
///
|
||||
/// If `impl_all_tuples!(macro_name, 2)` is provided, an additional identifier
|
||||
/// will be provided before `$field`.
|
||||
macro_rules! impl_all_tuples {
|
||||
($macro_name:ident) => {
|
||||
impl_all_tuples!($macro_name, 1);
|
||||
};
|
||||
($macro_name:ident, 1) => {
|
||||
$macro_name!(T0 0 t0);
|
||||
$macro_name!(T0 0 t0, T1 1 t1);
|
||||
$macro_name!(T0 0 t0, T1 1 t1, T2 2 t2);
|
||||
$macro_name!(T0 0 t0, T1 1 t1, T2 2 t2, T3 3 t3);
|
||||
$macro_name!(T0 0 t0, T1 1 t1, T2 2 t2, T3 3 t3, T4 4 t4);
|
||||
$macro_name!(T0 0 t0, T1 1 t1, T2 2 t2, T3 3 t3, T4 4 t4, T5 5 t5);
|
||||
}
|
||||
};
|
||||
($macro_name:ident, 2) => {
|
||||
$macro_name!(T0 Y0 0 t0);
|
||||
$macro_name!(T0 Y0 0 t0, T1 Y1 1 t1);
|
||||
$macro_name!(T0 Y0 0 t0, T1 Y1 1 t1, T2 Y2 2 t2);
|
||||
$macro_name!(T0 Y0 0 t0, T1 Y1 1 t1, T2 Y2 2 t2, T3 Y3 3 t3);
|
||||
$macro_name!(T0 Y0 0 t0, T1 Y1 1 t1, T2 Y2 2 t2, T3 Y3 3 t3, T4 Y4 4 t4);
|
||||
$macro_name!(T0 Y0 0 t0, T1 Y1 1 t1, T2 Y2 2 t2, T3 Y3 3 t3, T4 Y4 4 t4, T5 Y5 5 t5);
|
||||
};
|
||||
}
|
||||
|
||||
/// Invokes a function with a clone of `self`.
|
||||
|
|
|
|||
545
src/value.rs
545
src/value.rs
|
|
@ -1,5 +1,6 @@
|
|||
//! Types for storing and interacting with values in Widgets.
|
||||
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Debug, Display};
|
||||
use std::future::Future;
|
||||
|
|
@ -22,7 +23,7 @@ use crate::utils::{run_in_bg, IgnorePoison, WithClone};
|
|||
use crate::widget::{
|
||||
Children, MakeWidget, MakeWidgetWithTag, OnceCallback, WidgetId, WidgetInstance,
|
||||
};
|
||||
use crate::widgets::{Radio, Select, Space, Switcher};
|
||||
use crate::widgets::{Label, Radio, Select, Space, Switcher};
|
||||
use crate::window::WindowHandle;
|
||||
|
||||
/// A source of one or more `T` values.
|
||||
|
|
@ -607,14 +608,14 @@ pub struct Mutable<'a, T> {
|
|||
|
||||
#[derive(Debug)]
|
||||
enum Mutated<'a> {
|
||||
Tracked(&'a mut bool),
|
||||
External(&'a mut bool),
|
||||
Ignored,
|
||||
}
|
||||
|
||||
impl Mutated<'_> {
|
||||
fn set(&mut self, mutated: bool) {
|
||||
match self {
|
||||
Self::Tracked(value) => **value = mutated,
|
||||
Self::External(value) => **value = mutated,
|
||||
Self::Ignored => {}
|
||||
}
|
||||
}
|
||||
|
|
@ -643,7 +644,7 @@ impl<'a, T> Mutable<'a, T> {
|
|||
*mutated = false;
|
||||
Self {
|
||||
value,
|
||||
mutated: Mutated::Tracked(mutated),
|
||||
mutated: Mutated::External(mutated),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -657,6 +658,201 @@ impl<'a, T> From<&'a mut T> for Mutable<'a, T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A unique, reactive value.
|
||||
///
|
||||
/// This type is useful for situations where a value is owned by exactly one
|
||||
/// type but needs to have reactivity through [`Source`]/[`Destination`].
|
||||
///
|
||||
/// A [`Dynamic`] utilizes a [`Arc`] + [`Mutex`] to support updating its values
|
||||
/// from multiple threads. This type utilizes a [`RefCell`], preventing it from
|
||||
/// being shared between multiple threads.
|
||||
#[derive(Default)]
|
||||
pub struct Owned<T> {
|
||||
wrapped: RefCell<GenerationalValue<T>>,
|
||||
callbacks: Arc<OwnedCallbacks<T>>,
|
||||
}
|
||||
|
||||
impl<T> Owned<T> {
|
||||
/// Returns a new reactive value.
|
||||
pub fn new(value: T) -> Self {
|
||||
Self {
|
||||
wrapped: RefCell::new(GenerationalValue {
|
||||
value,
|
||||
generation: Generation::default(),
|
||||
}),
|
||||
callbacks: Arc::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Borrows the contents of this value with read-only access.
|
||||
pub fn borrow(&self) -> OwnedRef<'_, T> {
|
||||
OwnedRef(self.wrapped.borrow())
|
||||
}
|
||||
|
||||
/// Borrows the contents of this value with exclusive access.
|
||||
///
|
||||
/// When the returned type is accessed through [`DerefMut`], all associated
|
||||
/// reactive callbacks will be invoked upon dropping the returned
|
||||
/// [`OwnedMut`].
|
||||
pub fn borrow_mut(&mut self) -> OwnedMut<'_, T> {
|
||||
OwnedMut {
|
||||
borrowed: self.wrapped.borrow_mut(),
|
||||
accessed_mut: false,
|
||||
owned: self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the contained value.
|
||||
pub fn into_inner(self) -> T {
|
||||
self.wrapped.into_inner().value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Source<T> for Owned<T> {
|
||||
fn try_map_generational<R>(
|
||||
&self,
|
||||
map: impl FnOnce(&GenerationalValue<T>) -> R,
|
||||
) -> Result<R, DeadlockError> {
|
||||
Ok(map(&self.wrapped.borrow()))
|
||||
}
|
||||
|
||||
fn for_each_generational_try<F>(&self, for_each: F) -> CallbackHandle
|
||||
where
|
||||
T: Send + 'static,
|
||||
F: for<'a> FnMut(&'a GenerationalValue<T>) -> Result<(), CallbackDisconnected>
|
||||
+ Send
|
||||
+ 'static,
|
||||
{
|
||||
let mut callbacks = self.callbacks.active.lock().ignore_poison();
|
||||
CallbackHandle(CallbackHandleInner::Single(CallbackHandleData {
|
||||
id: Some(callbacks.push(Box::new(for_each))),
|
||||
callbacks: self.callbacks.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn for_each_generational_cloned_try<F>(&self, mut for_each: F) -> CallbackHandle
|
||||
where
|
||||
T: Clone + Send + 'static,
|
||||
F: FnMut(GenerationalValue<T>) -> Result<(), CallbackDisconnected> + Send + 'static,
|
||||
{
|
||||
self.for_each_generational_try(move |value| for_each(value.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Destination<T> for Owned<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn try_map_mut<R>(&self, map: impl FnOnce(Mutable<'_, T>) -> R) -> Result<R, DeadlockError> {
|
||||
let mut updated = false;
|
||||
let result = map(Mutable::new(
|
||||
&mut self.wrapped.borrow_mut().value,
|
||||
&mut updated,
|
||||
));
|
||||
if updated {
|
||||
self.callbacks.invoke(&self.wrapped.borrow());
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// A read-only reference to the value in an [`Owned`].
|
||||
pub struct OwnedRef<'a, T>(Ref<'a, GenerationalValue<T>>)
|
||||
where
|
||||
T: 'static;
|
||||
|
||||
impl<T> Deref for OwnedRef<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// An exclusive reference to the value contained in an [`Owned`].
|
||||
///
|
||||
/// This type tracks if the referenced value is accessed through [`DerefMut`].
|
||||
/// If it is, reactive callbacks associated with the [`Owned`] value will be
|
||||
/// invoked.
|
||||
pub struct OwnedMut<'a, T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
owned: &'a Owned<T>,
|
||||
borrowed: RefMut<'a, GenerationalValue<T>>,
|
||||
accessed_mut: bool,
|
||||
}
|
||||
|
||||
impl<T> Deref for OwnedMut<'_, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.borrowed.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for OwnedMut<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.accessed_mut = true;
|
||||
&mut self.borrowed.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for OwnedMut<'_, T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
if self.accessed_mut {
|
||||
self.owned.callbacks.invoke(&self.borrowed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OwnedCallbacks<T> {
|
||||
active: Mutex<Lots<Box<dyn OwnedCallbackFn<T>>>>,
|
||||
}
|
||||
|
||||
impl<T> Default for OwnedCallbacks<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active: Mutex::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> OwnedCallbacks<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
pub fn invoke(&self, value: &GenerationalValue<T>) {
|
||||
let mut callbacks = self.active.lock().ignore_poison();
|
||||
callbacks.drain_filter(|callback| callback.updated(value).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> CallbackCollection for OwnedCallbacks<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn remove(&self, id: LotId) {
|
||||
self.active.lock().ignore_poison().remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
trait OwnedCallbackFn<T>: Send + 'static {
|
||||
fn updated(&mut self, value: &GenerationalValue<T>) -> Result<(), CallbackDisconnected>;
|
||||
}
|
||||
|
||||
impl<F, T> OwnedCallbackFn<T> for F
|
||||
where
|
||||
F: for<'a> FnMut(&'a GenerationalValue<T>) -> Result<(), CallbackDisconnected> + Send + 'static,
|
||||
{
|
||||
fn updated(&mut self, value: &GenerationalValue<T>) -> Result<(), CallbackDisconnected> {
|
||||
self(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// An instance of a value that provides APIs to observe and react to its
|
||||
/// contents.
|
||||
pub struct Dynamic<T>(Arc<DynamicData<T>>);
|
||||
|
|
@ -665,19 +861,7 @@ impl<T> Dynamic<T> {
|
|||
/// Creates a new instance wrapping `value`.
|
||||
pub fn new(value: T) -> Self {
|
||||
Self(Arc::new(DynamicData {
|
||||
state: Mutex::new(State {
|
||||
wrapped: GenerationalValue {
|
||||
value,
|
||||
generation: Generation::default(),
|
||||
},
|
||||
callbacks: Arc::default(),
|
||||
windows: AHashSet::new(),
|
||||
readers: 0,
|
||||
wakers: Vec::new(),
|
||||
widgets: AHashSet::new(),
|
||||
on_disconnect: Some(Vec::new()),
|
||||
source_callback: CallbackHandle::default(),
|
||||
}),
|
||||
state: Mutex::new(State::new(value)),
|
||||
during_callback_state: Mutex::default(),
|
||||
sync: Condvar::default(),
|
||||
}))
|
||||
|
|
@ -839,14 +1023,6 @@ impl<T> Dynamic<T> {
|
|||
with_clone(self.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn redraw_when_changed(&self, window: WindowHandle) {
|
||||
self.0.redraw_when_changed(window);
|
||||
}
|
||||
|
||||
pub(crate) fn invalidate_when_changed(&self, window: WindowHandle, widget: WidgetId) {
|
||||
self.0.invalidate_when_changed(window, widget);
|
||||
}
|
||||
|
||||
/// Returns a new reference-based reader for this dynamic value.
|
||||
///
|
||||
/// # Panics
|
||||
|
|
@ -884,6 +1060,10 @@ impl<T> Dynamic<T> {
|
|||
/// thread.
|
||||
#[must_use]
|
||||
pub fn lock(&self) -> DynamicGuard<'_, T> {
|
||||
self.lock_inner()
|
||||
}
|
||||
|
||||
fn lock_inner<const READONLY: bool>(&self) -> DynamicGuard<'_, T, READONLY> {
|
||||
DynamicGuard {
|
||||
guard: self.0.state().expect("deadlocked"),
|
||||
accessed_mut: false,
|
||||
|
|
@ -1007,12 +1187,12 @@ impl MakeWidgetWithTag for Dynamic<Option<WidgetInstance>> {
|
|||
}
|
||||
|
||||
impl<T> context::sealed::Trackable for Dynamic<T> {
|
||||
fn redraw_when_changed(&self, handle: WindowHandle) {
|
||||
self.redraw_when_changed(handle);
|
||||
fn inner_redraw_when_changed(&self, handle: WindowHandle) {
|
||||
self.0.redraw_when_changed(handle);
|
||||
}
|
||||
|
||||
fn invalidate_when_changed(&self, handle: WindowHandle, id: WidgetId) {
|
||||
self.invalidate_when_changed(handle, id);
|
||||
fn inner_invalidate_when_changed(&self, handle: WindowHandle, id: WidgetId) {
|
||||
self.0.invalidate_when_changed(handle, id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1240,6 +1420,10 @@ impl Display for DeadlockError {
|
|||
}
|
||||
}
|
||||
|
||||
trait CallbackCollection: Send + Sync + 'static {
|
||||
fn remove(&self, id: LotId);
|
||||
}
|
||||
|
||||
/// A handle to a callback installed on a [`Dynamic`]. When dropped, the
|
||||
/// callback will be uninstalled.
|
||||
///
|
||||
|
|
@ -1262,7 +1446,7 @@ enum CallbackHandleInner {
|
|||
|
||||
struct CallbackHandleData {
|
||||
id: Option<LotId>,
|
||||
callbacks: Arc<ChangeCallbacksData>,
|
||||
callbacks: Arc<dyn CallbackCollection>,
|
||||
}
|
||||
|
||||
impl Debug for CallbackHandle {
|
||||
|
|
@ -1328,8 +1512,7 @@ impl CallbackHandleData {
|
|||
impl Drop for CallbackHandleData {
|
||||
fn drop(&mut self) {
|
||||
if let Some(id) = self.id {
|
||||
let mut data = self.callbacks.callbacks.lock().ignore_poison();
|
||||
data.callbacks.remove(id);
|
||||
self.callbacks.remove(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1399,6 +1582,22 @@ struct State<T> {
|
|||
}
|
||||
|
||||
impl<T> State<T> {
|
||||
fn new(value: T) -> Self {
|
||||
Self {
|
||||
wrapped: GenerationalValue {
|
||||
value,
|
||||
generation: Generation::default(),
|
||||
},
|
||||
callbacks: Arc::default(),
|
||||
windows: AHashSet::new(),
|
||||
readers: 0,
|
||||
wakers: Vec::new(),
|
||||
widgets: AHashSet::new(),
|
||||
on_disconnect: Some(Vec::new()),
|
||||
source_callback: CallbackHandle::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn note_changed(&mut self) -> ChangeCallbacks {
|
||||
self.wrapped.generation = self.wrapped.generation.next();
|
||||
|
||||
|
|
@ -1481,6 +1680,13 @@ struct ChangeCallbacksData {
|
|||
sync: Condvar,
|
||||
}
|
||||
|
||||
impl CallbackCollection for ChangeCallbacksData {
|
||||
fn remove(&self, id: LotId) {
|
||||
let mut data = self.callbacks.lock().ignore_poison();
|
||||
data.callbacks.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
struct CallbacksList {
|
||||
callbacks: Lots<Box<dyn ValueCallback>>,
|
||||
invoked_at: Instant,
|
||||
|
|
@ -1569,7 +1775,7 @@ where
|
|||
}
|
||||
|
||||
/// A value stored in a [`Dynamic`] with its [`Generation`].
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Default, Clone, Debug, Eq, PartialEq)]
|
||||
pub struct GenerationalValue<T> {
|
||||
/// The stored value.
|
||||
pub value: T,
|
||||
|
|
@ -1858,11 +2064,11 @@ impl<T> DynamicReader<T> {
|
|||
}
|
||||
|
||||
impl<T> context::sealed::Trackable for DynamicReader<T> {
|
||||
fn redraw_when_changed(&self, handle: WindowHandle) {
|
||||
fn inner_redraw_when_changed(&self, handle: WindowHandle) {
|
||||
self.source.redraw_when_changed(handle);
|
||||
}
|
||||
|
||||
fn invalidate_when_changed(&self, handle: WindowHandle, id: WidgetId) {
|
||||
fn inner_invalidate_when_changed(&self, handle: WindowHandle, id: WidgetId) {
|
||||
self.source.invalidate_when_changed(handle, id);
|
||||
}
|
||||
}
|
||||
|
|
@ -1997,6 +2203,78 @@ impl Generation {
|
|||
}
|
||||
}
|
||||
|
||||
/// A type that can convert into a `ReadOnly<T>`.
|
||||
pub trait IntoReadOnly<T> {
|
||||
/// Returns `self` as a `ReadOnly`.
|
||||
fn into_read_only(self) -> ReadOnly<T>;
|
||||
}
|
||||
|
||||
impl<T> IntoReadOnly<T> for T {
|
||||
fn into_read_only(self) -> ReadOnly<T> {
|
||||
ReadOnly::Constant(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoReadOnly<T> for ReadOnly<T> {
|
||||
fn into_read_only(self) -> ReadOnly<T> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoReadOnly<T> for Value<T> {
|
||||
fn into_read_only(self) -> ReadOnly<T> {
|
||||
match self {
|
||||
Value::Constant(value) => ReadOnly::Constant(value),
|
||||
Value::Dynamic(dynamic) => ReadOnly::Reader(dynamic.into_reader()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoReadOnly<T> for Dynamic<T> {
|
||||
fn into_read_only(self) -> ReadOnly<T> {
|
||||
self.create_reader().into_read_only()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoReadOnly<T> for DynamicReader<T> {
|
||||
fn into_read_only(self) -> ReadOnly<T> {
|
||||
ReadOnly::Reader(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoReadOnly<T> for Owned<T> {
|
||||
fn into_read_only(self) -> ReadOnly<T> {
|
||||
ReadOnly::Constant(self.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that can be converted into a [`DynamicReader<T>`].
|
||||
pub trait IntoReader<T> {
|
||||
/// Returns this value as a reader.
|
||||
fn into_reader(self) -> DynamicReader<T>;
|
||||
|
||||
/// Returns `self` being `Display`ed in a [`Label`] widget.
|
||||
fn into_label(self) -> Label<T>
|
||||
where
|
||||
Self: Sized,
|
||||
T: Debug + Display + Send + 'static,
|
||||
{
|
||||
Label::new(self.into_reader())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoReader<T> for Dynamic<T> {
|
||||
fn into_reader(self) -> DynamicReader<T> {
|
||||
self.into_reader()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoReader<T> for DynamicReader<T> {
|
||||
fn into_reader(self) -> DynamicReader<T> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that can convert into a `Dynamic<T>`.
|
||||
pub trait IntoDynamic<T> {
|
||||
/// Returns `self` as a dynamic.
|
||||
|
|
@ -2086,6 +2364,90 @@ impl GetWidget<usize> for Vec<WidgetInstance> {
|
|||
|
||||
impl<T, W> Switchable<T> for W where W: IntoDynamic<T> {}
|
||||
|
||||
/// A value that can only be read from.
|
||||
pub enum ReadOnly<T> {
|
||||
/// A value that will not ever change externally.
|
||||
Constant(T),
|
||||
/// A value that is read from a dynamic.
|
||||
Reader(DynamicReader<T>),
|
||||
}
|
||||
|
||||
impl<T> ReadOnly<T> {
|
||||
/// Returns a clone of the currently stored value.
|
||||
#[must_use]
|
||||
pub fn get(&self) -> T
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
match self {
|
||||
Self::Constant(value) => value.clone(),
|
||||
Self::Reader(value) => value.get(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current generation of the data stored, if the contained
|
||||
/// value is [`Dynamic`].
|
||||
pub fn generation(&self) -> Option<Generation> {
|
||||
match self {
|
||||
Self::Constant(_) => None,
|
||||
Self::Reader(value) => Some(value.generation()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps the current contents to `map` and returns the result.
|
||||
pub fn map<R>(&self, map: impl FnOnce(&T) -> R) -> R {
|
||||
match self {
|
||||
Self::Constant(value) => map(value),
|
||||
Self::Reader(dynamic) => dynamic.map_ref(map),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new value that is updated using `U::from(T.clone())` each time
|
||||
/// `self` is updated.
|
||||
#[must_use]
|
||||
pub fn map_each<R, F>(&self, mut map: F) -> ReadOnly<R>
|
||||
where
|
||||
T: Send + 'static,
|
||||
F: for<'a> FnMut(&'a T) -> R + Send + 'static,
|
||||
R: PartialEq + Send + 'static,
|
||||
{
|
||||
match self {
|
||||
Self::Constant(value) => ReadOnly::Constant(map(value)),
|
||||
Self::Reader(dynamic) => ReadOnly::Reader(dynamic.map_each(map).into_reader()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<DynamicReader<T>> for ReadOnly<T> {
|
||||
fn from(value: DynamicReader<T>) -> Self {
|
||||
Self::Reader(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Dynamic<T>> for ReadOnly<T> {
|
||||
fn from(value: Dynamic<T>) -> Self {
|
||||
Self::from(value.into_reader())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Owned<T>> for ReadOnly<T> {
|
||||
fn from(value: Owned<T>) -> Self {
|
||||
Self::Constant(value.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for ReadOnly<T>
|
||||
where
|
||||
T: Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Constant(arg0) => Debug::fmt(arg0, f),
|
||||
Self::Reader(arg0) => Debug::fmt(arg0, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that may be either constant or dynamic.
|
||||
pub enum Value<T> {
|
||||
/// A value that will not ever change externally.
|
||||
|
|
@ -2205,26 +2567,42 @@ impl<T> Value<T> {
|
|||
Value::Dynamic(value) => Some(value.generation()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks the widget for redraw when this value is updated.
|
||||
///
|
||||
/// This function has no effect if the value is constant.
|
||||
pub fn redraw_when_changed(&self, context: &WidgetContext<'_, '_>) {
|
||||
if let Value::Dynamic(dynamic) = self {
|
||||
context.redraw_when_changed(dynamic);
|
||||
impl<T> crate::context::sealed::Trackable for ReadOnly<T> {
|
||||
fn inner_invalidate_when_changed(&self, handle: WindowHandle, id: WidgetId) {
|
||||
if let ReadOnly::Reader(dynamic) = self {
|
||||
dynamic.inner_invalidate_when_changed(handle, id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks the widget for redraw when this value is updated.
|
||||
///
|
||||
/// This function has no effect if the value is constant.
|
||||
pub fn invalidate_when_changed(&self, context: &WidgetContext<'_, '_>) {
|
||||
if let Value::Dynamic(dynamic) = self {
|
||||
context.invalidate_when_changed(dynamic);
|
||||
fn inner_redraw_when_changed(&self, handle: WindowHandle) {
|
||||
if let ReadOnly::Reader(dynamic) = self {
|
||||
dynamic.inner_redraw_when_changed(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> crate::context::sealed::Trackable for Value<T> {
|
||||
fn inner_invalidate_when_changed(&self, handle: WindowHandle, id: WidgetId) {
|
||||
if let Value::Dynamic(dynamic) = self {
|
||||
dynamic.inner_invalidate_when_changed(handle, id);
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_redraw_when_changed(&self, handle: WindowHandle) {
|
||||
if let Value::Dynamic(dynamic) = self {
|
||||
dynamic.inner_redraw_when_changed(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Dynamic<T>> for Value<T> {
|
||||
fn from(value: Dynamic<T>) -> Self {
|
||||
Self::Dynamic(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoDynamic<T> for Value<T> {
|
||||
fn into_dynamic(self) -> Dynamic<T> {
|
||||
match self {
|
||||
|
|
@ -2285,9 +2663,9 @@ impl<'a> IntoValue<String> for &'a str {
|
|||
}
|
||||
}
|
||||
|
||||
impl IntoValue<String> for Dynamic<&'static str> {
|
||||
fn into_value(self) -> Value<String> {
|
||||
self.map_each(ToString::to_string).into_value()
|
||||
impl<'a> IntoReadOnly<String> for &'a str {
|
||||
fn into_read_only(self) -> ReadOnly<String> {
|
||||
ReadOnly::Constant(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2327,10 +2705,13 @@ pub trait ForEach<T> {
|
|||
}
|
||||
|
||||
macro_rules! impl_tuple_for_each {
|
||||
($($type:ident $field:tt $var:ident),+) => {
|
||||
impl<$($type,)+> ForEach<($($type,)+)> for ($(&Dynamic<$type>,)+)
|
||||
($($type:ident $source:ident $field:tt $var:ident),+) => {
|
||||
impl<$($type,$source,)+> ForEach<($($type,)+)> for ($(&$source,)+)
|
||||
where
|
||||
$($type: Send + 'static,)+
|
||||
$(
|
||||
$source: DynamicRead<$type> + Source<$type> + Clone + Send + 'static,
|
||||
$type: Send + 'static,
|
||||
)+
|
||||
{
|
||||
type Ref<'a> = ($(&'a $type,)+);
|
||||
|
||||
|
|
@ -2408,7 +2789,7 @@ macro_rules! impl_tuple_for_each {
|
|||
) => {
|
||||
$handles += $var.for_each((&$for_each, $(&$rvar,)+).with_clone(|(for_each, $($rvar,)+)| {
|
||||
move |$var: &$type| {
|
||||
$(let $rvar = $rvar.lock();)+
|
||||
$(let $rvar = $rvar.read();)+
|
||||
let mut for_each =
|
||||
for_each.lock().ignore_poison();
|
||||
(for_each)(($(&$avar,)+));
|
||||
|
|
@ -2417,7 +2798,26 @@ macro_rules! impl_tuple_for_each {
|
|||
};
|
||||
}
|
||||
|
||||
impl_all_tuples!(impl_tuple_for_each);
|
||||
/// Read access to a value stored in a [`Dynamic`].
|
||||
pub trait DynamicRead<T> {
|
||||
/// Returns a guard that provides exclusive, read-only access to the value
|
||||
/// contained wihtin this dynamic.
|
||||
fn read(&self) -> DynamicGuard<'_, T, true>;
|
||||
}
|
||||
|
||||
impl<T> DynamicRead<T> for Dynamic<T> {
|
||||
fn read(&self) -> DynamicGuard<'_, T, true> {
|
||||
self.lock_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DynamicRead<T> for DynamicReader<T> {
|
||||
fn read(&self) -> DynamicGuard<'_, T, true> {
|
||||
self.lock()
|
||||
}
|
||||
}
|
||||
|
||||
impl_all_tuples!(impl_tuple_for_each, 2);
|
||||
|
||||
/// A type that can create a `Dynamic<U>` from a `T` passed into a mapping
|
||||
/// function.
|
||||
|
|
@ -2435,11 +2835,14 @@ pub trait MapEach<T, U> {
|
|||
}
|
||||
|
||||
macro_rules! impl_tuple_map_each {
|
||||
($($type:ident $field:tt $var:ident),+) => {
|
||||
impl<U, $($type),+> MapEach<($($type,)+), U> for ($(&Dynamic<$type>,)+)
|
||||
($($type:ident $source:ident $field:tt $var:ident),+) => {
|
||||
impl<U, $($type,$source),+> MapEach<($($type,)+), U> for ($(&$source,)+)
|
||||
where
|
||||
U: PartialEq + Send + 'static,
|
||||
$($type: Send + 'static),+
|
||||
$(
|
||||
$type: Send + 'static,
|
||||
$source: DynamicRead<$type> + Source<$type> + Clone + Send + 'static,
|
||||
)+
|
||||
{
|
||||
type Ref<'a> = ($(&'a $type,)+);
|
||||
|
||||
|
|
@ -2448,7 +2851,7 @@ macro_rules! impl_tuple_map_each {
|
|||
F: for<'a> FnMut(Self::Ref<'a>) -> U + Send + 'static,
|
||||
{
|
||||
let dynamic = {
|
||||
$(let $var = self.$field.lock();)+
|
||||
$(let $var = self.$field.read();)+
|
||||
|
||||
Dynamic::new(map_each(($(&$var,)+)))
|
||||
};
|
||||
|
|
@ -2465,7 +2868,7 @@ macro_rules! impl_tuple_map_each {
|
|||
};
|
||||
}
|
||||
|
||||
impl_all_tuples!(impl_tuple_map_each);
|
||||
impl_all_tuples!(impl_tuple_map_each, 2);
|
||||
|
||||
/// A type that can have a `for_each` operation applied to it.
|
||||
pub trait ForEachCloned<T> {
|
||||
|
|
@ -2476,10 +2879,13 @@ pub trait ForEachCloned<T> {
|
|||
}
|
||||
|
||||
macro_rules! impl_tuple_for_each_cloned {
|
||||
($($type:ident $field:tt $var:ident),+) => {
|
||||
impl<$($type,)+> ForEachCloned<($($type,)+)> for ($(&Dynamic<$type>,)+)
|
||||
($($type:ident $source:ident $field:tt $var:ident),+) => {
|
||||
impl<$($type,$source,)+> ForEachCloned<($($type,)+)> for ($(&$source,)+)
|
||||
where
|
||||
$($type: Clone + Send + 'static,)+
|
||||
$(
|
||||
$type: Clone + Send + 'static,
|
||||
$source: Source<$type> + Clone + Send + 'static,
|
||||
)+
|
||||
{
|
||||
|
||||
#[allow(unused_mut)]
|
||||
|
|
@ -2566,7 +2972,7 @@ macro_rules! impl_tuple_for_each_cloned {
|
|||
};
|
||||
}
|
||||
|
||||
impl_all_tuples!(impl_tuple_for_each_cloned);
|
||||
impl_all_tuples!(impl_tuple_for_each_cloned, 2);
|
||||
|
||||
/// A type that can create a `Dynamic<U>` from a `T` passed into a mapping
|
||||
/// function.
|
||||
|
|
@ -2579,11 +2985,14 @@ pub trait MapEachCloned<T, U> {
|
|||
}
|
||||
|
||||
macro_rules! impl_tuple_map_each_cloned {
|
||||
($($type:ident $field:tt $var:ident),+) => {
|
||||
impl<U, $($type),+> MapEachCloned<($($type,)+), U> for ($(&Dynamic<$type>,)+)
|
||||
($($type:ident $source:ident $field:tt $var:ident),+) => {
|
||||
impl<U, $($type,$source),+> MapEachCloned<($($type,)+), U> for ($(&$source,)+)
|
||||
where
|
||||
U: PartialEq + Send + 'static,
|
||||
$($type: Clone + Send + 'static),+
|
||||
$(
|
||||
$type: Clone + Send + 'static,
|
||||
$source: Source<$type> + Clone + Send + 'static,
|
||||
)+
|
||||
{
|
||||
|
||||
fn map_each_cloned<F>(&self, mut map_each: F) -> Dynamic<U>
|
||||
|
|
@ -2608,7 +3017,7 @@ macro_rules! impl_tuple_map_each_cloned {
|
|||
};
|
||||
}
|
||||
|
||||
impl_all_tuples!(impl_tuple_map_each_cloned);
|
||||
impl_all_tuples!(impl_tuple_map_each_cloned, 2);
|
||||
|
||||
/// The status of validating data.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use kludgine::app::winit::window::CursorIcon;
|
|||
use kludgine::Color;
|
||||
|
||||
use crate::app::{Application, Open, PendingApp, Run};
|
||||
use crate::context::sealed::Trackable as _;
|
||||
use crate::context::{
|
||||
AsEventContext, EventContext, GraphicsContext, LayoutContext, ManageWidget, WidgetContext,
|
||||
};
|
||||
|
|
@ -149,8 +150,9 @@ use crate::ConstraintLimit;
|
|||
/// layout functions, it needs to ensure that all depended upon [`Dynamic`]s are
|
||||
/// tracked using one of the various
|
||||
/// `*_tracking_redraw()`/`*_tracking_invalidate()` functions. For example,
|
||||
/// [`Dynamic::get_tracking_redraw()`] and
|
||||
/// [`Dynamic::get_tracking_invalidate()`].
|
||||
/// [`Source::get_tracking_redraw()`](crate::value::Source::get_tracking_redraw)
|
||||
/// and
|
||||
/// [`Source::get_tracking_invalidate()`](crate::value::Source::get_tracking_invalidate).
|
||||
///
|
||||
/// # Hover State: Hit Testing
|
||||
///
|
||||
|
|
@ -1564,7 +1566,7 @@ impl WidgetInstance {
|
|||
|
||||
pub(crate) fn enabled(&self, context: &WindowHandle) -> bool {
|
||||
if let Value::Dynamic(dynamic) = &self.data.enabled {
|
||||
dynamic.redraw_when_changed(context.clone());
|
||||
dynamic.inner_redraw_when_changed(context.clone());
|
||||
}
|
||||
self.data.enabled.get()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use figures::units::{Lp, UPx};
|
|||
use figures::{Fraction, IntoSigned, IntoUnsigned, Point, Rect, Round, ScreenScale, Size};
|
||||
use intentional::{Assert, Cast};
|
||||
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, Trackable};
|
||||
use crate::styles::components::IntrinsicPadding;
|
||||
use crate::styles::Dimension;
|
||||
use crate::value::{Generation, IntoValue, Value};
|
||||
|
|
|
|||
|
|
@ -1,30 +1,37 @@
|
|||
//! A read-only text widget.
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
use figures::units::{Px, UPx};
|
||||
use figures::{Point, Round, Size};
|
||||
use kludgine::text::{MeasuredText, Text, TextOrigin};
|
||||
use kludgine::{CanRenderTo, Color, DrawableExt};
|
||||
|
||||
use crate::context::{GraphicsContext, LayoutContext};
|
||||
use crate::context::{GraphicsContext, LayoutContext, Trackable};
|
||||
use crate::styles::components::TextColor;
|
||||
use crate::value::{Dynamic, Generation, IntoValue, Value};
|
||||
use crate::value::{Dynamic, Generation, IntoReadOnly, ReadOnly, Value};
|
||||
use crate::widget::{Widget, WidgetInstance};
|
||||
use crate::window::WindowLocal;
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
/// A read-only text widget.
|
||||
#[derive(Debug)]
|
||||
pub struct Label {
|
||||
pub struct Label<T> {
|
||||
/// The contents of the label.
|
||||
pub text: Value<String>,
|
||||
pub display: ReadOnly<T>,
|
||||
displayed: String,
|
||||
prepared_text: WindowLocal<(MeasuredText<Px>, Option<Generation>, Px, Color)>,
|
||||
}
|
||||
|
||||
impl Label {
|
||||
impl<T> Label<T>
|
||||
where
|
||||
T: std::fmt::Debug + std::fmt::Display + Send + 'static,
|
||||
{
|
||||
/// Returns a new label that displays `text`.
|
||||
pub fn new(text: impl IntoValue<String>) -> Self {
|
||||
pub fn new(text: impl IntoReadOnly<T>) -> Self {
|
||||
Self {
|
||||
text: text.into_value(),
|
||||
display: text.into_read_only(),
|
||||
displayed: String::new(),
|
||||
prepared_text: WindowLocal::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +42,7 @@ impl Label {
|
|||
color: Color,
|
||||
width: Px,
|
||||
) -> &MeasuredText<Px> {
|
||||
let check_generation = self.text.generation();
|
||||
let check_generation = self.display.generation();
|
||||
match self.prepared_text.get(context) {
|
||||
Some((prepared, prepared_generation, prepared_width, prepared_color))
|
||||
if prepared.can_render_to(&context.gfx)
|
||||
|
|
@ -44,10 +51,14 @@ impl Label {
|
|||
&& *prepared_width == width => {}
|
||||
_ => {
|
||||
context.apply_current_font_settings();
|
||||
let measured = self.text.map(|text| {
|
||||
let measured = self.display.map(|text| {
|
||||
self.displayed.clear();
|
||||
if let Err(err) = write!(&mut self.displayed, "{text}") {
|
||||
tracing::error!("Error invoking Display: {err}");
|
||||
}
|
||||
context
|
||||
.gfx
|
||||
.measure_text(Text::new(text, color).wrap_at(width))
|
||||
.measure_text(Text::new(&self.displayed, color).wrap_at(width))
|
||||
});
|
||||
self.prepared_text
|
||||
.set(context, (measured, check_generation, width, color));
|
||||
|
|
@ -61,9 +72,12 @@ impl Label {
|
|||
}
|
||||
}
|
||||
|
||||
impl Widget for Label {
|
||||
impl<T> Widget for Label<T>
|
||||
where
|
||||
T: std::fmt::Debug + std::fmt::Display + Send + 'static,
|
||||
{
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
self.text.invalidate_when_changed(context);
|
||||
self.display.invalidate_when_changed(context);
|
||||
|
||||
let size = context.gfx.region().size;
|
||||
let center = Point::from(size) / 2;
|
||||
|
|
@ -90,7 +104,7 @@ impl Widget for Label {
|
|||
}
|
||||
|
||||
fn summarize(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fmt.debug_tuple("Label").field(&self.text).finish()
|
||||
fmt.debug_tuple("Label").field(&self.display).finish()
|
||||
}
|
||||
|
||||
fn unmounted(&mut self, context: &mut crate::context::EventContext<'_, '_>) {
|
||||
|
|
@ -99,19 +113,20 @@ impl Widget for Label {
|
|||
}
|
||||
|
||||
macro_rules! impl_make_widget {
|
||||
($($type:ty),*) => {
|
||||
($($type:ty => $kind:ty),*) => {
|
||||
$(impl crate::widget::MakeWidgetWithTag for $type {
|
||||
fn make_with_tag(self, id: crate::widget::WidgetTag) -> WidgetInstance {
|
||||
Label::new(self).make_with_tag(id)
|
||||
Label::<$kind>::new(self).make_with_tag(id)
|
||||
}
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
impl_make_widget!(
|
||||
&'_ str,
|
||||
String,
|
||||
Value<String>,
|
||||
Dynamic<String>,
|
||||
Dynamic<&'static str>
|
||||
&'_ str => String,
|
||||
String => String,
|
||||
Dynamic<String> => String,
|
||||
Dynamic<&'static str> => &'static str,
|
||||
Value<String> => String,
|
||||
ReadOnly<String> => String
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use intentional::Assert;
|
|||
|
||||
use crate::animation::easings::EaseOutQuadradic;
|
||||
use crate::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn, ZeroToOne};
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, Trackable};
|
||||
use crate::utils::IgnorePoison;
|
||||
use crate::value::{Destination, Dynamic, DynamicGuard, IntoValue, Source, Value};
|
||||
use crate::widget::{
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use crate::animation::easings::{EaseInQuadradic, EaseOutQuadradic};
|
|||
use crate::animation::{
|
||||
AnimationHandle, AnimationTarget, IntoAnimate, PercentBetween, Spawn, ZeroToOne,
|
||||
};
|
||||
use crate::value::{Destination, Dynamic, IntoDynamic, IntoValue, MapEach, Source, Value};
|
||||
use crate::value::{Destination, Dynamic, IntoReadOnly, IntoReader, MapEach, ReadOnly, Source};
|
||||
use crate::widget::{MakeWidget, MakeWidgetWithTag, Widget, WidgetInstance};
|
||||
use crate::widgets::slider::{InactiveTrackColor, Slidable, TrackColor, TrackSize};
|
||||
use crate::widgets::Data;
|
||||
|
|
@ -20,7 +20,7 @@ use crate::widgets::Data;
|
|||
/// A bar-shaped progress indicator.
|
||||
#[derive(Debug)]
|
||||
pub struct ProgressBar {
|
||||
progress: Value<Progress>,
|
||||
progress: ReadOnly<Progress>,
|
||||
spinner: bool,
|
||||
}
|
||||
|
||||
|
|
@ -29,16 +29,16 @@ impl ProgressBar {
|
|||
#[must_use]
|
||||
pub const fn indeterminant() -> Self {
|
||||
Self {
|
||||
progress: Value::Constant(Progress::Indeterminant),
|
||||
progress: ReadOnly::Constant(Progress::Indeterminant),
|
||||
spinner: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new progress bar that displays `progress`.
|
||||
#[must_use]
|
||||
pub fn new(progress: impl IntoDynamic<Progress>) -> Self {
|
||||
pub fn new(progress: impl IntoReadOnly<Progress>) -> Self {
|
||||
Self {
|
||||
progress: Value::Dynamic(progress.into_dynamic()),
|
||||
progress: progress.into_read_only(),
|
||||
spinner: false,
|
||||
}
|
||||
}
|
||||
|
|
@ -100,7 +100,7 @@ impl MakeWidgetWithTag for ProgressBar {
|
|||
);
|
||||
|
||||
match self.progress {
|
||||
Value::Dynamic(progress) => {
|
||||
ReadOnly::Reader(progress) => {
|
||||
let callback = progress.for_each(move |progress| {
|
||||
update_progress_bar(
|
||||
*progress,
|
||||
|
|
@ -112,7 +112,9 @@ impl MakeWidgetWithTag for ProgressBar {
|
|||
});
|
||||
Data::new_wrapping((callback, progress), slider).make_widget()
|
||||
}
|
||||
Value::Constant(_) => Data::new_wrapping(indeterminant_animation, slider).make_widget(),
|
||||
ReadOnly::Constant(_) => {
|
||||
Data::new_wrapping(indeterminant_animation, slider).make_widget()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -193,32 +195,28 @@ fn update_progress_bar(
|
|||
}
|
||||
|
||||
/// A value that can be used in a progress indicator.
|
||||
pub trait Progressable<T>: IntoDynamic<T> + Sized
|
||||
pub trait Progressable<T>: IntoReader<T> + Sized
|
||||
where
|
||||
T: ProgressValue + Send,
|
||||
{
|
||||
/// Returns a new progress bar that displays progress from `T::MIN` to
|
||||
/// `T::MAX`.
|
||||
fn progress_bar(self) -> ProgressBar {
|
||||
ProgressBar::new(
|
||||
self.into_dynamic()
|
||||
.map_each(|value| value.to_progress(None)),
|
||||
)
|
||||
ProgressBar::new(self.into_reader().map_each(|value| value.to_progress(None)))
|
||||
}
|
||||
|
||||
/// Returns a new progress bar that displays progress from `T::MIN` to
|
||||
/// `max`. The maximum value can be either a `T` or an `Option<T>`. If
|
||||
/// `None` is the maximum value, an indeterminant progress bar will be
|
||||
/// displayed.
|
||||
fn progress_bar_to(self, max: impl IntoValue<T::Value>) -> ProgressBar
|
||||
fn progress_bar_to(self, max: impl IntoReadOnly<T::Value>) -> ProgressBar
|
||||
where
|
||||
T: Send,
|
||||
T::Value: PartialEq + Ranged + Send + Clone,
|
||||
{
|
||||
let max = max.into_value();
|
||||
let max = max.into_read_only();
|
||||
match max {
|
||||
Value::Constant(max) => self.progress_bar_between(<T::Value>::MIN..=max),
|
||||
Value::Dynamic(max) => {
|
||||
ReadOnly::Constant(max) => self.progress_bar_between(<T::Value>::MIN..=max),
|
||||
ReadOnly::Reader(max) => {
|
||||
self.progress_bar_between(max.map_each(|max| <T::Value>::MIN..=max.clone()))
|
||||
}
|
||||
}
|
||||
|
|
@ -230,26 +228,28 @@ where
|
|||
/// displayed.
|
||||
fn progress_bar_between<Range>(self, range: Range) -> ProgressBar
|
||||
where
|
||||
T: Send,
|
||||
T::Value: Send,
|
||||
Range: IntoValue<RangeInclusive<T::Value>>,
|
||||
Range: IntoReadOnly<RangeInclusive<T::Value>>,
|
||||
{
|
||||
let value = self.into_dynamic();
|
||||
let range = range.into_value();
|
||||
match range {
|
||||
Value::Constant(range) => ProgressBar::new(
|
||||
value.map_each(move |value| value.to_progress(Some(range.start()..=range.end()))),
|
||||
),
|
||||
Value::Dynamic(range) => {
|
||||
ProgressBar::new((&range, &value).map_each(|(range, value)| {
|
||||
value.to_progress(Some(range.start()..=range.end()))
|
||||
}))
|
||||
}
|
||||
}
|
||||
let value = self.into_reader();
|
||||
let range = range.into_read_only();
|
||||
ProgressBar::new(match range {
|
||||
ReadOnly::Constant(range) => value
|
||||
.map_each(move |value| value.to_progress(Some(range.start()..=range.end())))
|
||||
.into_reader(),
|
||||
ReadOnly::Reader(range) => (&range, &value)
|
||||
.map_each(|(range, value)| value.to_progress(Some(range.start()..=range.end())))
|
||||
.into_reader(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<U> Progressable<U> for Dynamic<U> where U: ProgressValue + Send {}
|
||||
impl<T, U> Progressable<U> for T
|
||||
where
|
||||
T: IntoReader<U> + Send,
|
||||
U: ProgressValue + Send,
|
||||
{
|
||||
}
|
||||
|
||||
/// A value that can be used in a progress indicator.
|
||||
pub trait ProgressValue: 'static {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use figures::units::UPx;
|
||||
use figures::{IntoSigned, Rect, Round, ScreenScale, Size};
|
||||
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
|
||||
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, Trackable};
|
||||
use crate::styles::components::IntrinsicPadding;
|
||||
use crate::styles::FlexibleDimension;
|
||||
use crate::value::{Generation, IntoValue, Value};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use kludgine::app::winit::window::CursorIcon;
|
|||
use kludgine::tilemap;
|
||||
use kludgine::tilemap::TileMapFocus;
|
||||
|
||||
use crate::context::{EventContext, GraphicsContext, LayoutContext};
|
||||
use crate::context::{EventContext, GraphicsContext, LayoutContext, Trackable};
|
||||
use crate::tick::Tick;
|
||||
use crate::value::{Dynamic, IntoValue, Value};
|
||||
use crate::widget::{EventHandling, Widget, HANDLED, IGNORED};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use figures::units::{Px, UPx};
|
|||
use figures::{IntoSigned, IntoUnsigned, Point, Rect, Round, ScreenScale, Size, Zero};
|
||||
use intentional::Cast;
|
||||
|
||||
use crate::context::{AsEventContext, GraphicsContext, LayoutContext};
|
||||
use crate::context::{AsEventContext, GraphicsContext, LayoutContext, Trackable};
|
||||
use crate::styles::components::{IntrinsicPadding, LayoutOrder};
|
||||
use crate::styles::{FlexibleDimension, HorizontalOrder};
|
||||
use crate::value::{IntoValue, Value};
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ use crate::animation::{LinearInterpolate, PercentBetween, ZeroToOne};
|
|||
use crate::app::{Application, Cushy, Open, PendingApp, Run};
|
||||
use crate::context::sealed::InvalidationStatus;
|
||||
use crate::context::{
|
||||
AsEventContext, EventContext, Exclusive, GraphicsContext, LayoutContext, WidgetContext,
|
||||
AsEventContext, EventContext, Exclusive, GraphicsContext, LayoutContext, Trackable,
|
||||
WidgetContext,
|
||||
};
|
||||
use crate::graphics::{FontState, Graphics};
|
||||
use crate::styles::{Edges, FontFamilyList, ThemePair};
|
||||
|
|
|
|||
Loading…
Reference in a new issue