mirror of
https://github.com/danbulant/cushy
synced 2026-06-18 22:11:34 +00:00
Dynamic now requires PartialEq
This reduces the complexity of operations capable with Dynamic, and also makes it easier to shortcut deadlocking operations.
This commit is contained in:
parent
b63e4d66d2
commit
b2fdf06e60
17 changed files with 543 additions and 234 deletions
|
|
@ -3,9 +3,16 @@
|
|||
[]($docs$)
|
||||
|
||||
Gooey is an experimental Graphical User Interface (GUI) crate for the Rust
|
||||
programming language. It is built using [`Kludgine`][kludgine], which is powered
|
||||
by [`winit`][winit] and [`wgpu`][wgpu]. It is incredibly early in development,
|
||||
and is being developed for a game that will hopefully be developed shortly.
|
||||
programming language. It is powered by:
|
||||
|
||||
- [`Kludgine`][kludgine], a 2d graphics library powered by:
|
||||
- [`winit`][winit] for windowing/input
|
||||
- [`wgpu`][wgpu] for graphics
|
||||
- [`cosmic_text`][cosmic_text]
|
||||
- [`palette`][palette]
|
||||
- [`arboard`][arboard]
|
||||
|
||||
## Getting Started with Gooey
|
||||
|
||||
The [`Widget`][widget] trait is the building block of Gooey: Every user
|
||||
interface element implements `Widget`. A full list of built-in widgets can be
|
||||
|
|
@ -19,9 +26,25 @@ increments its own label:
|
|||
$../examples/basic-button.rs:readme$
|
||||
```
|
||||
|
||||
A great way to learn more about Gooey is to explore the [examples
|
||||
directory][examples]. Nearly every feature in Gooey was initially tested by
|
||||
creating an example.
|
||||
|
||||
## Project Status
|
||||
|
||||
This project is early in development, but is quickly becoming a decent
|
||||
framework. It is considered experimental and unspported at this time, and the
|
||||
primary focus for [@ecton][ecton] is to use this for his own projects. Feature
|
||||
requests and bug fixes will be prioritized based on @ecton's own needs.
|
||||
|
||||
[widget]: $widget$
|
||||
[kludgine]: https://github.com/khonsulabs/kludgine
|
||||
[wgpu]: https://github.com/gfx-rs/wgpu
|
||||
[winit]: https://github.com/rust-windowing/winit
|
||||
[widgets]: $widgets$
|
||||
[button-example]: https://github.com/khonsulabs/gooey/tree/$ref-name$/examples/basic-button.rs
|
||||
[examples]: https://github.com/khonsulabs/gooey/tree/$ref-name$/examples/
|
||||
[cosmic_text]: https://github.com/pop-os/cosmic-text
|
||||
[palette]: https://github.com/Ogeon/palette
|
||||
[arboard]: https://github.com/1Password/arboard
|
||||
[ecton]: https://github.com/khonsulabs/ecton
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ fn main() -> gooey::Result {
|
|||
.clone()
|
||||
.into_checkbox(label)
|
||||
.and("Maybe".into_button().on_click(move |()| {
|
||||
checkbox_state.update(CheckboxState::Indeterminant);
|
||||
checkbox_state.set(CheckboxState::Indeterminant);
|
||||
}))
|
||||
.into_columns()
|
||||
.centered()
|
||||
|
|
|
|||
|
|
@ -46,12 +46,12 @@ fn u8_slider() -> impl MakeWidget {
|
|||
|
||||
fn u8_range_slider() -> impl MakeWidget {
|
||||
let range = Dynamic::new(42..=127);
|
||||
let start = range.map_each_unique(|range| *range.start());
|
||||
let end = range.map_each_unique(|range| *range.end());
|
||||
let start = range.map_each(|range| *range.start());
|
||||
let end = range.map_each(|range| *range.end());
|
||||
(&start, &end).for_each({
|
||||
let range = range.clone();
|
||||
move |(start, end)| {
|
||||
let _result = range.try_update(*start..=*end);
|
||||
range.set(*start..=*end);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use gooey::value::{Dynamic, Switchable};
|
|||
use gooey::widget::{MakeWidget, WidgetInstance};
|
||||
use gooey::Run;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
enum ActiveContent {
|
||||
Intro,
|
||||
Success,
|
||||
|
|
|
|||
|
|
@ -1,73 +1,153 @@
|
|||
use gooey::styles::components::{TextColor, WidgetBackground};
|
||||
use gooey::styles::{
|
||||
ColorScheme, ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme, ThemePair,
|
||||
ColorScheme, ColorSchemeBuilder, ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme,
|
||||
ThemePair,
|
||||
};
|
||||
use gooey::value::{Dynamic, MapEach};
|
||||
use gooey::value::{Dynamic, MapEachCloned};
|
||||
use gooey::widget::MakeWidget;
|
||||
use gooey::widgets::checkbox::Checkable;
|
||||
use gooey::widgets::input::InputValue;
|
||||
use gooey::widgets::slider::Slidable;
|
||||
use gooey::widgets::Space;
|
||||
use gooey::window::ThemeMode;
|
||||
use gooey::Run;
|
||||
use kludgine::figures::units::Lp;
|
||||
use kludgine::Color;
|
||||
use palette::OklabHue;
|
||||
|
||||
struct Scheme<Primary, Other = Primary> {
|
||||
primary: Primary,
|
||||
secondary: Other,
|
||||
tertiary: Other,
|
||||
error: Other,
|
||||
neutral: Other,
|
||||
neutral_variant: Other,
|
||||
}
|
||||
|
||||
impl From<ColorScheme> for Scheme<ColorSource> {
|
||||
fn from(scheme: ColorScheme) -> Self {
|
||||
Self {
|
||||
primary: scheme.primary,
|
||||
secondary: scheme.secondary,
|
||||
tertiary: scheme.tertiary,
|
||||
error: scheme.error,
|
||||
neutral: scheme.neutral,
|
||||
neutral_variant: scheme.neutral_variant,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Scheme<T> {
|
||||
pub fn map<R>(&self, mut map: impl FnMut(T) -> R) -> Scheme<R>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
Scheme {
|
||||
primary: map(self.primary.clone()),
|
||||
secondary: map(self.secondary.clone()),
|
||||
tertiary: map(self.tertiary.clone()),
|
||||
error: map(self.error.clone()),
|
||||
neutral: map(self.neutral.clone()),
|
||||
neutral_variant: map(self.neutral_variant.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Primary, Other> Scheme<Primary, Other> {
|
||||
pub fn map_labeled<NewPrimary, NewOther>(
|
||||
&self,
|
||||
primary: impl FnOnce(Primary) -> NewPrimary,
|
||||
mut map: impl FnMut(&str, Other) -> NewOther,
|
||||
) -> Scheme<NewPrimary, NewOther>
|
||||
where
|
||||
Primary: Clone,
|
||||
Other: Clone,
|
||||
{
|
||||
Scheme {
|
||||
primary: primary(self.primary.clone()),
|
||||
secondary: map("Secondary", self.secondary.clone()),
|
||||
tertiary: map("Tertiary", self.tertiary.clone()),
|
||||
error: map("Error", self.error.clone()),
|
||||
neutral: map("Netural", self.neutral.clone()),
|
||||
neutral_variant: map("Neutral Variant", self.neutral_variant.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> gooey::Result {
|
||||
let scheme = ColorScheme::default();
|
||||
let (primary, primary_editor) = color_editor(scheme.primary, "Primary");
|
||||
let (secondary, secondary_editor) = color_editor(scheme.secondary, "Secondary");
|
||||
let (tertiary, tertiary_editor) = color_editor(scheme.tertiary, "Tertiary");
|
||||
let (error, error_editor) = color_editor(scheme.error, "Error");
|
||||
let (neutral, neutral_editor) = color_editor(scheme.neutral, "Neutral");
|
||||
let (neutral_variant, neutral_variant_editor) =
|
||||
color_editor(scheme.neutral_variant, "Neutral Variant");
|
||||
let (theme_mode, theme_switcher) = dark_mode_slider();
|
||||
let (theme_mode, theme_switcher) = dark_mode_picker();
|
||||
|
||||
let default_theme = (
|
||||
&primary,
|
||||
&secondary,
|
||||
&tertiary,
|
||||
&error,
|
||||
&neutral,
|
||||
&neutral_variant,
|
||||
let scheme = Scheme::from(ColorScheme::default());
|
||||
let sources = scheme.map(Dynamic::new);
|
||||
let editors = sources.map_labeled(
|
||||
|primary| {
|
||||
swatch_label("Primary", &primary)
|
||||
.and(color_editor(&primary))
|
||||
.into_rows()
|
||||
.make_widget()
|
||||
},
|
||||
|label, source| {
|
||||
let (enabled, editor) = optional_editor(label, &source);
|
||||
let opt_color =
|
||||
(&enabled, &source).map_each_cloned(|(enabled, source)| enabled.then_some(source));
|
||||
(opt_color, editor)
|
||||
},
|
||||
);
|
||||
let color_scheme = (
|
||||
&sources.primary,
|
||||
&editors.secondary.0,
|
||||
&editors.tertiary.0,
|
||||
&editors.error.0,
|
||||
&editors.neutral.0,
|
||||
&editors.neutral_variant.0,
|
||||
)
|
||||
.map_each(
|
||||
|(primary, secondary, tertiary, error, neutral, neutral_variant)| {
|
||||
ThemePair::from(ColorScheme {
|
||||
primary: *primary,
|
||||
secondary: *secondary,
|
||||
tertiary: *tertiary,
|
||||
error: *error,
|
||||
neutral: *neutral,
|
||||
neutral_variant: *neutral_variant,
|
||||
})
|
||||
.map_each_cloned(
|
||||
move |(primary, secondary, tertiary, error, neutral, neutral_variant)| {
|
||||
let mut scheme = ColorSchemeBuilder::new(primary);
|
||||
scheme.secondary = secondary;
|
||||
scheme.tertiary = tertiary;
|
||||
scheme.error = error;
|
||||
scheme.neutral = neutral;
|
||||
scheme.neutral_variant = neutral_variant;
|
||||
scheme.build()
|
||||
},
|
||||
);
|
||||
color_scheme.for_each_cloned(move |scheme| {
|
||||
sources.primary.set(scheme.primary);
|
||||
sources.secondary.set(scheme.secondary);
|
||||
sources.tertiary.set(scheme.tertiary);
|
||||
sources.error.set(scheme.error);
|
||||
sources.neutral.set(scheme.neutral);
|
||||
sources.neutral_variant.set(scheme.neutral_variant);
|
||||
});
|
||||
let theme = color_scheme.map_each_cloned(ThemePair::from);
|
||||
|
||||
let editors = theme_switcher
|
||||
.and(primary_editor)
|
||||
.and(secondary_editor)
|
||||
.and(tertiary_editor)
|
||||
.and(error_editor)
|
||||
.and(neutral_editor)
|
||||
.and(neutral_variant_editor)
|
||||
.and(editors.primary)
|
||||
.and(editors.secondary.1)
|
||||
.and(editors.tertiary.1)
|
||||
.and(editors.error.1)
|
||||
.and(editors.neutral.1)
|
||||
.and(editors.neutral_variant.1)
|
||||
.into_rows()
|
||||
.vertical_scroll();
|
||||
|
||||
editors
|
||||
.and(fixed_themes(
|
||||
default_theme.map_each(|theme| theme.primary_fixed),
|
||||
default_theme.map_each(|theme| theme.secondary_fixed),
|
||||
default_theme.map_each(|theme| theme.tertiary_fixed),
|
||||
theme.map_each(|theme| theme.primary_fixed),
|
||||
theme.map_each(|theme| theme.secondary_fixed),
|
||||
theme.map_each(|theme| theme.tertiary_fixed),
|
||||
))
|
||||
.and(theme(
|
||||
default_theme.map_each(|theme| theme.dark),
|
||||
.and(theme_preview(
|
||||
theme.map_each(|theme| theme.dark),
|
||||
ThemeMode::Dark,
|
||||
))
|
||||
.and(theme(
|
||||
default_theme.map_each(|theme| theme.light),
|
||||
.and(theme_preview(
|
||||
theme.map_each(|theme| theme.light),
|
||||
ThemeMode::Light,
|
||||
))
|
||||
.into_columns()
|
||||
.themed(default_theme)
|
||||
.themed(theme)
|
||||
.pad()
|
||||
.expand()
|
||||
.into_window()
|
||||
|
|
@ -75,36 +155,68 @@ fn main() -> gooey::Result {
|
|||
.run()
|
||||
}
|
||||
|
||||
fn dark_mode_slider() -> (Dynamic<ThemeMode>, impl MakeWidget) {
|
||||
let theme_mode = Dynamic::default();
|
||||
fn dark_mode_picker() -> (Dynamic<ThemeMode>, impl MakeWidget) {
|
||||
let dark = Dynamic::new(true);
|
||||
let theme_mode = dark.map_each(|dark| {
|
||||
if *dark {
|
||||
ThemeMode::Dark
|
||||
} else {
|
||||
ThemeMode::Light
|
||||
}
|
||||
});
|
||||
|
||||
(theme_mode.clone(), dark.into_checkbox("Dark Mode"))
|
||||
}
|
||||
|
||||
fn swatch_label(label: &str, color: &Dynamic<ColorSource>) -> impl MakeWidget {
|
||||
Space::colored(color.map_each(|source| source.color(0.5)))
|
||||
.width(Lp::mm(1))
|
||||
.and(label)
|
||||
.into_columns()
|
||||
}
|
||||
|
||||
fn optional_editor(label: &str, color: &Dynamic<ColorSource>) -> (Dynamic<bool>, impl MakeWidget) {
|
||||
let enabled = Dynamic::new(false);
|
||||
let hide_editor = enabled.map_each(|enabled| !enabled);
|
||||
|
||||
(
|
||||
theme_mode.clone(),
|
||||
"Theme Mode".and(theme_mode.slider()).into_rows(),
|
||||
enabled.clone(),
|
||||
enabled
|
||||
.clone()
|
||||
.into_checkbox(swatch_label(label, color))
|
||||
.and(color_editor(color).collapse_vertically(hide_editor))
|
||||
.into_rows(),
|
||||
)
|
||||
}
|
||||
|
||||
fn color_editor(
|
||||
initial_color: ColorSource,
|
||||
label: &str,
|
||||
) -> (Dynamic<ColorSource>, impl MakeWidget) {
|
||||
let hue = Dynamic::new(initial_color.hue.into_degrees());
|
||||
fn color_editor(color: &Dynamic<ColorSource>) -> impl MakeWidget {
|
||||
let hue = color.map_each(|color| color.hue.into_positive_degrees());
|
||||
hue.for_each_cloned({
|
||||
let color = color.clone();
|
||||
move |hue| {
|
||||
let mut source = color.get();
|
||||
source.hue = OklabHue::new(hue);
|
||||
color.set(source);
|
||||
}
|
||||
});
|
||||
|
||||
let hue_text = hue.linked_string();
|
||||
let saturation = Dynamic::new(initial_color.saturation);
|
||||
let saturation = color.map_each(|color| color.saturation);
|
||||
saturation.for_each_cloned({
|
||||
let color = color.clone();
|
||||
move |saturation| {
|
||||
let mut source = color.get();
|
||||
source.saturation = saturation;
|
||||
color.set(source);
|
||||
}
|
||||
});
|
||||
let saturation_text = saturation.linked_string();
|
||||
|
||||
let color =
|
||||
(&hue, &saturation).map_each(|(hue, saturation)| ColorSource::new(*hue, *saturation));
|
||||
|
||||
(
|
||||
color,
|
||||
label
|
||||
.and(hue.slider_between(0., 360.))
|
||||
.and(hue_text.into_input())
|
||||
.and(saturation.slider())
|
||||
.and(saturation_text.into_input())
|
||||
.into_rows(),
|
||||
)
|
||||
hue.slider_between(0., 359.99)
|
||||
.and(hue_text.into_input())
|
||||
.and(saturation.slider())
|
||||
.and(saturation_text.into_input())
|
||||
.into_rows()
|
||||
}
|
||||
|
||||
fn fixed_themes(
|
||||
|
|
@ -146,7 +258,7 @@ fn fixed_theme(theme: Dynamic<FixedTheme>, label: &str) -> impl MakeWidget {
|
|||
.expand()
|
||||
}
|
||||
|
||||
fn theme(theme: Dynamic<Theme>, mode: ThemeMode) -> impl MakeWidget {
|
||||
fn theme_preview(theme: Dynamic<Theme>, mode: ThemeMode) -> impl MakeWidget {
|
||||
match mode {
|
||||
ThemeMode::Light => "Light",
|
||||
ThemeMode::Dark => "Dark",
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ fn main() -> gooey::Result {
|
|||
.run()
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, Eq, PartialEq)]
|
||||
enum AppState {
|
||||
#[default]
|
||||
Playing,
|
||||
|
|
@ -187,8 +187,8 @@ fn square(row: usize, column: usize, game: &Dynamic<GameState>) -> impl MakeWidg
|
|||
return;
|
||||
};
|
||||
|
||||
if enabled.update(false) {
|
||||
label.update(player.to_string());
|
||||
if enabled.replace(false).is_some() {
|
||||
label.set(player.to_string());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -215,11 +215,11 @@ where
|
|||
fn update(&self, percent: f32) {
|
||||
self.change
|
||||
.dynamic
|
||||
.update(self.start.lerp(&self.change.new_value, percent));
|
||||
.set(self.start.lerp(&self.change.new_value, percent));
|
||||
}
|
||||
|
||||
fn finish(&self) {
|
||||
self.change.dynamic.update(self.change.new_value.clone());
|
||||
self.change.dynamic.set(self.change.new_value.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -458,7 +458,7 @@ pub struct RunningAnimation<T, Easing> {
|
|||
|
||||
/// A handle to a spawned animation. When dropped, the associated animation will
|
||||
/// be stopped.
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
#[must_use]
|
||||
pub struct AnimationHandle(Option<LotId>);
|
||||
|
||||
|
|
@ -1236,6 +1236,16 @@ impl RequireInvalidation for EasingFunction {
|
|||
}
|
||||
}
|
||||
|
||||
impl PartialEq for EasingFunction {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Fn(l0), Self::Fn(r0)) => l0 == r0,
|
||||
(Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs easing for value interpolation.
|
||||
pub trait Easing: Debug + Send + Sync + RefUnwindSafe + UnwindSafe + 'static {
|
||||
/// Eases a value ranging between zero and one. The resulting value does not
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ where
|
|||
|
||||
impl<T> IntoComponentValue for Value<T>
|
||||
where
|
||||
T: Clone,
|
||||
T: Clone + Send + 'static,
|
||||
Component: From<T>,
|
||||
{
|
||||
fn into_component_value(self) -> Value<Component> {
|
||||
|
|
@ -147,7 +147,7 @@ where
|
|||
|
||||
impl<T> IntoComponentValue for Dynamic<T>
|
||||
where
|
||||
T: Clone,
|
||||
T: Clone + Send + 'static,
|
||||
Component: From<T>,
|
||||
{
|
||||
fn into_component_value(self) -> Value<Component> {
|
||||
|
|
@ -201,7 +201,7 @@ impl IntoIterator for Styles {
|
|||
// }
|
||||
|
||||
/// A value of a style component.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Component {
|
||||
/// A color.
|
||||
Color(Color),
|
||||
|
|
@ -781,6 +781,12 @@ impl CustomComponent {
|
|||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CustomComponent {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl RequireInvalidation for CustomComponent {
|
||||
fn requires_invalidation(&self) -> bool {
|
||||
self.0.requires_invalidation()
|
||||
|
|
@ -1112,7 +1118,7 @@ impl IntoValue<Dimension> for Lp {
|
|||
}
|
||||
|
||||
/// A set of light and dark [`Theme`]s.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ThemePair {
|
||||
/// The theme to use when the user interface is in light mode.
|
||||
pub light: Theme,
|
||||
|
|
@ -1987,6 +1993,16 @@ pub trait ProtoColor: Sized {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> ProtoColor for &'a ColorSource {
|
||||
fn hue(&self) -> OklabHue {
|
||||
self.hue
|
||||
}
|
||||
|
||||
fn saturation(&self) -> Option<ZeroToOne> {
|
||||
Some(self.saturation)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtoColor for f32 {
|
||||
fn hue(&self) -> OklabHue {
|
||||
(*self).into()
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ define_components! {
|
|||
/// The [`Dimension`] to use as the size to render text.
|
||||
TextSize(Dimension, "text_size", Dimension::Lp(Lp::points(12)))
|
||||
/// The [`Dimension`] to use to space multiple lines of text.
|
||||
LineHeight(Dimension,"line_height",Dimension::Lp(Lp::points(14)))
|
||||
LineHeight(Dimension,"line_height",Dimension::Lp(Lp::points(16)))
|
||||
/// The [`Color`] of the surface for the user interface to draw upon.
|
||||
SurfaceColor(Color, "surface_color", .surface.color)
|
||||
/// The [`Color`] to use when rendering text.
|
||||
|
|
|
|||
413
src/value.rs
413
src/value.rs
|
|
@ -8,11 +8,12 @@ use std::str::FromStr;
|
|||
use std::sync::{Arc, Mutex, MutexGuard, TryLockError};
|
||||
use std::task::{Poll, Waker};
|
||||
use std::thread::ThreadId;
|
||||
use std::time::Duration;
|
||||
|
||||
use ahash::AHashSet;
|
||||
use intentional::Assert;
|
||||
|
||||
use crate::animation::{DynamicTransition, LinearInterpolate};
|
||||
use crate::animation::{DynamicTransition, IntoAnimate, LinearInterpolate, Spawn};
|
||||
use crate::context::sealed::WindowHandle;
|
||||
use crate::context::{self, WidgetContext};
|
||||
use crate::utils::{IgnorePoison, UnwindsafeCondvar, WithClone};
|
||||
|
|
@ -33,7 +34,7 @@ impl<T> Dynamic<T> {
|
|||
value,
|
||||
generation: Generation::default(),
|
||||
},
|
||||
callbacks: Vec::new(),
|
||||
callbacks: Arc::default(),
|
||||
windows: AHashSet::new(),
|
||||
readers: 0,
|
||||
wakers: Vec::new(),
|
||||
|
|
@ -82,7 +83,7 @@ impl<T> Dynamic<T> {
|
|||
r.with_clone(move |r| {
|
||||
self.for_each(move |t| {
|
||||
if let Some(update) = t_into_r(t).into() {
|
||||
let _result = r.try_update(update);
|
||||
let _result = r.replace(update);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -90,7 +91,7 @@ impl<T> Dynamic<T> {
|
|||
self.with_clone(|t| {
|
||||
r.with_for_each(move |r| {
|
||||
if let Some(update) = r_into_t(r).into() {
|
||||
let _result = t.try_update(update);
|
||||
let _result = t.replace(update);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -153,8 +154,8 @@ impl<T> Dynamic<T> {
|
|||
#[must_use]
|
||||
pub fn map_each_into<U>(&self) -> Dynamic<U>
|
||||
where
|
||||
U: From<T> + Send + 'static,
|
||||
T: Clone,
|
||||
U: PartialEq + From<T> + Send + 'static,
|
||||
T: Clone + Send + 'static,
|
||||
{
|
||||
self.map_each(|value| U::from(value.clone()))
|
||||
}
|
||||
|
|
@ -164,8 +165,8 @@ impl<T> Dynamic<T> {
|
|||
#[must_use]
|
||||
pub fn map_each_to<U>(&self) -> Dynamic<U>
|
||||
where
|
||||
U: for<'a> From<&'a T> + Send + 'static,
|
||||
T: Clone,
|
||||
U: PartialEq + for<'a> From<&'a T> + Send + 'static,
|
||||
T: Clone + Send + 'static,
|
||||
{
|
||||
self.map_each(|value| U::from(value))
|
||||
}
|
||||
|
|
@ -174,19 +175,37 @@ impl<T> Dynamic<T> {
|
|||
/// value's contents are updated.
|
||||
pub fn for_each<F>(&self, mut for_each: F)
|
||||
where
|
||||
T: Send + 'static,
|
||||
F: for<'a> FnMut(&'a T) + Send + 'static,
|
||||
{
|
||||
self.0.for_each(move |gen| for_each(&gen.value));
|
||||
let this = self.clone();
|
||||
self.0.for_each(move || {
|
||||
this.map_ref(&mut for_each);
|
||||
});
|
||||
}
|
||||
|
||||
/// Attaches `for_each` to this value so that it is invoked each time the
|
||||
/// value's contents are updated.
|
||||
pub fn for_each_cloned<F>(&self, mut for_each: F)
|
||||
where
|
||||
T: Clone + Send + 'static,
|
||||
F: FnMut(T) + Send + 'static,
|
||||
{
|
||||
let this = self.clone();
|
||||
self.0.for_each(move || {
|
||||
for_each(this.get());
|
||||
});
|
||||
}
|
||||
|
||||
/// 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]
|
||||
pub fn with_for_each<F>(self, mut for_each: F) -> Self
|
||||
pub fn with_for_each<F>(self, for_each: F) -> Self
|
||||
where
|
||||
T: Send + 'static,
|
||||
F: for<'a> FnMut(&'a T) + Send + 'static,
|
||||
{
|
||||
self.0.for_each(move |gen| for_each(&gen.value));
|
||||
self.for_each(for_each);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -194,23 +213,24 @@ impl<T> Dynamic<T> {
|
|||
/// each time this value is changed.
|
||||
pub fn map_each<R, F>(&self, mut map: F) -> Dynamic<R>
|
||||
where
|
||||
T: Send + 'static,
|
||||
F: for<'a> FnMut(&'a T) -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
R: PartialEq + Send + 'static,
|
||||
{
|
||||
self.0.map_each(move |gen| map(&gen.value))
|
||||
let this = self.clone();
|
||||
self.0.map_each(move || this.map_ref(&mut map))
|
||||
}
|
||||
|
||||
/// Creates a new dynamic value that contains the result of invoking `map`
|
||||
/// each time this value is changed.
|
||||
///
|
||||
/// This version of `map_each` uses [`Dynamic::try_update`] to prevent
|
||||
/// deadlocks and debounce dependent values.
|
||||
pub fn map_each_unique<R, F>(&self, mut map: F) -> Dynamic<R>
|
||||
pub fn map_each_cloned<R, F>(&self, mut map: F) -> Dynamic<R>
|
||||
where
|
||||
F: for<'a> FnMut(&'a T) -> R + Send + 'static,
|
||||
R: Send + PartialEq + 'static,
|
||||
T: Clone + Send + 'static,
|
||||
F: FnMut(T) -> R + Send + 'static,
|
||||
R: PartialEq + Send + 'static,
|
||||
{
|
||||
self.0.map_each_unique(move |gen| map(&gen.value))
|
||||
let this = self.clone();
|
||||
self.0.map_each(move || map(this.get()))
|
||||
}
|
||||
|
||||
/// A helper function that invokes `with_clone` with a clone of self. This
|
||||
|
|
@ -336,77 +356,62 @@ impl<T> Dynamic<T> {
|
|||
/// Before returning from this function, all observers will be notified that
|
||||
/// the contents have been updated.
|
||||
///
|
||||
/// # Panics
|
||||
/// 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<T>
|
||||
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.
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
#[must_use]
|
||||
pub fn replace(&self, new_value: T) -> T {
|
||||
self.0
|
||||
.map_mut(|value, _| std::mem::replace(value, new_value))
|
||||
.expect("deadlocked")
|
||||
///
|
||||
/// 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<T, ReplaceError<T>>
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
let cell = Cell::new(Some(new_value));
|
||||
match self.0.map_mut(|value, changed| {
|
||||
let new_value = cell.take().assert("only one callback will be invoked");
|
||||
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.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
pub fn set(&self, new_value: T) {
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// Updates this dynamic with `new_value`, but only if `new_value` is not
|
||||
/// equal to the currently stored value.
|
||||
///
|
||||
/// Returns true if the value was updated.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if this value is already locked by the current
|
||||
/// thread.
|
||||
pub fn update(&self, new_value: T) -> bool
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
self.0
|
||||
.map_mut(|value, changed| {
|
||||
if *value == new_value {
|
||||
*changed = false;
|
||||
false
|
||||
} else {
|
||||
*value = new_value;
|
||||
true
|
||||
}
|
||||
})
|
||||
.expect("deadlocked")
|
||||
}
|
||||
|
||||
/// Attempt to store `new_value` in `self`. If the value cannot be stored
|
||||
/// due to a deadlock, it is returned as an error.
|
||||
///
|
||||
/// Returns true if the value was updated.
|
||||
pub fn try_update(&self, new_value: T) -> Result<bool, T>
|
||||
where
|
||||
T: PartialEq,
|
||||
{
|
||||
let cell = Cell::new(Some(new_value));
|
||||
self.0
|
||||
.map_mut(|value, changed| {
|
||||
let new_value = cell.take().assert("only one callback will be invoked");
|
||||
if *value == new_value {
|
||||
*changed = false;
|
||||
false
|
||||
} else {
|
||||
*value = new_value;
|
||||
true
|
||||
}
|
||||
})
|
||||
.map_err(|_| cell.take().assert("only one callback will be invoked"))
|
||||
}
|
||||
|
||||
/// Returns a new reference-based reader for this dynamic value.
|
||||
///
|
||||
/// # Panics
|
||||
|
|
@ -644,17 +649,16 @@ impl<T> DynamicData<T> {
|
|||
|
||||
pub fn map_mut<R>(&self, map: impl FnOnce(&mut T, &mut bool) -> R) -> Result<R, DeadlockError> {
|
||||
let mut state = self.state()?;
|
||||
let old = {
|
||||
let (old, callbacks) = {
|
||||
let state = &mut *state;
|
||||
let mut changed = true;
|
||||
let result = map(&mut state.wrapped.value, &mut changed);
|
||||
if changed {
|
||||
state.note_changed();
|
||||
}
|
||||
let callbacks = changed.then(|| state.note_changed());
|
||||
|
||||
result
|
||||
(result, callbacks)
|
||||
};
|
||||
drop(state);
|
||||
drop(callbacks);
|
||||
|
||||
self.sync.notify_all();
|
||||
|
||||
|
|
@ -663,55 +667,44 @@ impl<T> DynamicData<T> {
|
|||
|
||||
pub fn for_each<F>(&self, map: F)
|
||||
where
|
||||
F: for<'a> FnMut(&'a GenerationalValue<T>) + Send + 'static,
|
||||
F: for<'a> FnMut() + Send + 'static,
|
||||
{
|
||||
let mut state = self.state().expect("deadlocked");
|
||||
state.callbacks.push(Box::new(map));
|
||||
let state = self.state().expect("deadlocked");
|
||||
let mut callbacks = state.callbacks.lock().ignore_poison();
|
||||
callbacks.push(Box::new(map));
|
||||
}
|
||||
|
||||
pub fn map_each<R, F>(&self, mut map: F) -> Dynamic<R>
|
||||
where
|
||||
F: for<'a> FnMut(&'a GenerationalValue<T>) -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let mut state = self.state().expect("deadlocked");
|
||||
let initial_value = map(&state.wrapped);
|
||||
let mapped_value = Dynamic::new(initial_value);
|
||||
let returned = mapped_value.clone();
|
||||
state
|
||||
.callbacks
|
||||
.push(Box::new(move |updated: &GenerationalValue<T>| {
|
||||
mapped_value.set(map(updated));
|
||||
}));
|
||||
|
||||
returned
|
||||
}
|
||||
|
||||
pub fn map_each_unique<R, F>(&self, mut map: F) -> Dynamic<R>
|
||||
where
|
||||
F: for<'a> FnMut(&'a GenerationalValue<T>) -> R + Send + 'static,
|
||||
F: for<'a> FnMut() -> R + Send + 'static,
|
||||
R: PartialEq + Send + 'static,
|
||||
{
|
||||
let mut state = self.state().expect("deadlocked");
|
||||
let initial_value = map(&state.wrapped);
|
||||
let initial_value = map();
|
||||
let mapped_value = Dynamic::new(initial_value);
|
||||
let returned = mapped_value.clone();
|
||||
state
|
||||
.callbacks
|
||||
.push(Box::new(move |updated: &GenerationalValue<T>| {
|
||||
let _deadlock = mapped_value.try_update(map(updated));
|
||||
}));
|
||||
|
||||
self.for_each(move || {
|
||||
mapped_value.set(map());
|
||||
});
|
||||
|
||||
returned
|
||||
}
|
||||
}
|
||||
|
||||
/// An error occurred while updating a value in a [`Dynamic`].
|
||||
pub enum ReplaceError<T> {
|
||||
/// The value was already equal to the one set.
|
||||
NoChange(T),
|
||||
/// The current thread already has exclusive access to this dynamic.
|
||||
Deadlock,
|
||||
}
|
||||
|
||||
/// A deadlock occurred accessing a [`Dynamic`].
|
||||
///
|
||||
/// Currently Gooey is only able to detect deadlocks where a single thread tries
|
||||
/// to lock the same [`Dynamic`] multiple times.
|
||||
#[derive(Debug)]
|
||||
pub struct DeadlockError;
|
||||
struct DeadlockError;
|
||||
|
||||
impl std::error::Error for DeadlockError {}
|
||||
|
||||
|
|
@ -723,7 +716,7 @@ impl Display for DeadlockError {
|
|||
|
||||
struct State<T> {
|
||||
wrapped: GenerationalValue<T>,
|
||||
callbacks: Vec<Box<dyn ValueCallback<T>>>,
|
||||
callbacks: Arc<Mutex<Vec<Box<dyn ValueCallback>>>>,
|
||||
windows: AHashSet<WindowHandle>,
|
||||
widgets: AHashSet<(WindowHandle, WidgetId)>,
|
||||
wakers: Vec<Waker>,
|
||||
|
|
@ -731,12 +724,9 @@ struct State<T> {
|
|||
}
|
||||
|
||||
impl<T> State<T> {
|
||||
fn note_changed(&mut self) {
|
||||
fn note_changed(&mut self) -> ChangeCallbacks {
|
||||
self.wrapped.generation = self.wrapped.generation.next();
|
||||
|
||||
for callback in &mut self.callbacks {
|
||||
callback.update(&self.wrapped);
|
||||
}
|
||||
for (window, widget) in self.widgets.drain() {
|
||||
window.invalidate(widget);
|
||||
}
|
||||
|
|
@ -746,6 +736,8 @@ impl<T> State<T> {
|
|||
for waker in self.wakers.drain(..) {
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
ChangeCallbacks(self.callbacks.clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -761,16 +753,28 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
trait ValueCallback<T>: Send {
|
||||
fn update(&mut self, value: &GenerationalValue<T>);
|
||||
struct ChangeCallbacks(Arc<Mutex<Vec<Box<dyn ValueCallback>>>>);
|
||||
|
||||
impl Drop for ChangeCallbacks {
|
||||
fn drop(&mut self) {
|
||||
if let Ok(mut callbacks) = self.0.lock() {
|
||||
for callback in &mut *callbacks {
|
||||
callback.changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F> ValueCallback<T> for F
|
||||
trait ValueCallback: Send {
|
||||
fn changed(&mut self);
|
||||
}
|
||||
|
||||
impl<F> ValueCallback for F
|
||||
where
|
||||
F: for<'a> FnMut(&'a GenerationalValue<T>) + Send + 'static,
|
||||
F: for<'a> FnMut() + Send + 'static,
|
||||
{
|
||||
fn update(&mut self, value: &GenerationalValue<T>) {
|
||||
self(value);
|
||||
fn changed(&mut self) {
|
||||
self();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -808,7 +812,10 @@ impl<'a, T> DerefMut for DynamicGuard<'a, T> {
|
|||
impl<T> Drop for DynamicGuard<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
if self.accessed_mut {
|
||||
self.guard.note_changed();
|
||||
let mut callbacks = Some(self.guard.note_changed());
|
||||
Duration::ZERO
|
||||
.on_complete(move || drop(callbacks.take()))
|
||||
.launch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1086,7 +1093,7 @@ impl<T> IntoDynamic<T> for Dynamic<T> {
|
|||
impl<T, F> IntoDynamic<T> for F
|
||||
where
|
||||
F: FnMut(&T) + Send + 'static,
|
||||
T: Default,
|
||||
T: Default + Send + 'static,
|
||||
{
|
||||
/// Returns [`Dynamic::default()`] with `self` installed as a for-each
|
||||
/// callback.
|
||||
|
|
@ -1182,8 +1189,9 @@ impl<T> Value<T> {
|
|||
#[must_use]
|
||||
pub fn map_each<R, F>(&self, mut map: F) -> Value<R>
|
||||
where
|
||||
T: Send + 'static,
|
||||
F: for<'a> FnMut(&'a T) -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
R: PartialEq + Send + 'static,
|
||||
{
|
||||
match self {
|
||||
Value::Constant(value) => Value::Constant(map(value)),
|
||||
|
|
@ -1423,7 +1431,7 @@ macro_rules! impl_tuple_map_each {
|
|||
($($type:ident $field:tt $var:ident),+) => {
|
||||
impl<U, $($type),+> MapEach<($($type,)+), U> for ($(&Dynamic<$type>,)+)
|
||||
where
|
||||
U: Send + 'static,
|
||||
U: PartialEq + Send + 'static,
|
||||
$($type: Send + 'static),+
|
||||
{
|
||||
type Ref<'a> = ($(&'a $type,)+);
|
||||
|
|
@ -1451,3 +1459,144 @@ macro_rules! impl_tuple_map_each {
|
|||
}
|
||||
|
||||
impl_all_tuples!(impl_tuple_map_each);
|
||||
|
||||
/// A type that can have a `for_each` operation applied to it.
|
||||
pub trait ForEachCloned<T> {
|
||||
/// Apply `for_each` to each value contained within `self`.
|
||||
fn for_each_cloned<F>(&self, for_each: F)
|
||||
where
|
||||
F: for<'a> FnMut(T) + Send + 'static;
|
||||
}
|
||||
|
||||
macro_rules! impl_tuple_for_each_cloned {
|
||||
($($type:ident $field:tt $var:ident),+) => {
|
||||
impl<$($type,)+> ForEachCloned<($($type,)+)> for ($(&Dynamic<$type>,)+)
|
||||
where
|
||||
$($type: Clone + Send + 'static,)+
|
||||
{
|
||||
|
||||
#[allow(unused_mut)]
|
||||
fn for_each_cloned<F>(&self, mut for_each: F)
|
||||
where
|
||||
F: for<'a> FnMut(($($type,)+)) + Send + 'static,
|
||||
{
|
||||
impl_tuple_for_each_cloned!(self for_each [] [$($type $field $var),+]);
|
||||
}
|
||||
}
|
||||
};
|
||||
($self:ident $for_each:ident [] [$type:ident $field:tt $var:ident]) => {
|
||||
$self.$field.for_each(move |field: &$type| $for_each((field.clone(),)));
|
||||
};
|
||||
($self:ident $for_each:ident [] [$($type:ident $field:tt $var:ident),+]) => {
|
||||
let $for_each = Arc::new(Mutex::new($for_each));
|
||||
$(let $var = $self.$field.clone();)*
|
||||
|
||||
|
||||
impl_tuple_for_each_cloned!(invoke $self $for_each [] [$($type $field $var),+]);
|
||||
};
|
||||
(
|
||||
invoke
|
||||
// Identifiers used from the outer method
|
||||
$self:ident $for_each:ident
|
||||
// List of all tuple fields that have already been positioned as the focused call
|
||||
[$($ltype:ident $lfield:tt $lvar:ident),*]
|
||||
//
|
||||
[$type:ident $field:tt $var:ident, $($rtype:ident $rfield:tt $rvar:ident),+]
|
||||
) => {
|
||||
impl_tuple_for_each_cloned!(
|
||||
invoke
|
||||
$self $for_each
|
||||
$type $field $var
|
||||
[$($ltype $lfield $lvar,)* $type $field $var, $($rtype $rfield $rvar),+]
|
||||
[$($ltype $lfield $lvar,)* $($rtype $rfield $rvar),+]
|
||||
);
|
||||
impl_tuple_for_each_cloned!(
|
||||
invoke
|
||||
$self $for_each
|
||||
[$($ltype $lfield $lvar,)* $type $field $var]
|
||||
[$($rtype $rfield $rvar),+]
|
||||
);
|
||||
};
|
||||
(
|
||||
invoke
|
||||
// Identifiers used from the outer method
|
||||
$self:ident $for_each:ident
|
||||
// List of all tuple fields that have already been positioned as the focused call
|
||||
[$($ltype:ident $lfield:tt $lvar:ident),+]
|
||||
//
|
||||
[$type:ident $field:tt $var:ident]
|
||||
) => {
|
||||
impl_tuple_for_each_cloned!(
|
||||
invoke
|
||||
$self $for_each
|
||||
$type $field $var
|
||||
[$($ltype $lfield $lvar,)+ $type $field $var]
|
||||
[$($ltype $lfield $lvar),+]
|
||||
);
|
||||
};
|
||||
(
|
||||
invoke
|
||||
// Identifiers used from the outer method
|
||||
$self:ident $for_each:ident
|
||||
// Tuple field that for_each is being invoked on
|
||||
$type:ident $field:tt $var:ident
|
||||
// The list of all tuple fields in this invocation, in the correct order.
|
||||
[$($atype:ident $afield:tt $avar:ident),+]
|
||||
// The list of tuple fields excluding the one being invoked.
|
||||
[$($rtype:ident $rfield:tt $rvar:ident),+]
|
||||
) => {
|
||||
$var.for_each_cloned((&$for_each, $(&$rvar,)+).with_clone(|(for_each, $($rvar,)+)| {
|
||||
move |$var: $type| {
|
||||
$(let $rvar = $rvar.get();)+
|
||||
if let Ok(mut for_each) =
|
||||
for_each.try_lock() {
|
||||
(for_each)(($($avar,)+));
|
||||
}
|
||||
}
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
impl_all_tuples!(impl_tuple_for_each_cloned);
|
||||
|
||||
/// A type that can create a `Dynamic<U>` from a `T` passed into a mapping
|
||||
/// function.
|
||||
pub trait MapEachCloned<T, U> {
|
||||
/// Apply `map_each` to each value in `self`, storing the result in the
|
||||
/// returned dynamic.
|
||||
fn map_each_cloned<F>(&self, map_each: F) -> Dynamic<U>
|
||||
where
|
||||
F: for<'a> FnMut(T) -> U + Send + 'static;
|
||||
}
|
||||
|
||||
macro_rules! impl_tuple_map_each_cloned {
|
||||
($($type:ident $field:tt $var:ident),+) => {
|
||||
impl<U, $($type),+> MapEachCloned<($($type,)+), U> for ($(&Dynamic<$type>,)+)
|
||||
where
|
||||
U: PartialEq + Send + 'static,
|
||||
$($type: Clone + Send + 'static),+
|
||||
{
|
||||
|
||||
fn map_each_cloned<F>(&self, mut map_each: F) -> Dynamic<U>
|
||||
where
|
||||
F: for<'a> FnMut(($($type,)+)) -> U + Send + 'static,
|
||||
{
|
||||
let dynamic = {
|
||||
$(let $var = self.$field.get();)+
|
||||
|
||||
Dynamic::new(map_each(($($var,)+)))
|
||||
};
|
||||
self.for_each_cloned({
|
||||
let dynamic = dynamic.clone();
|
||||
|
||||
move |tuple| {
|
||||
dynamic.set(map_each(tuple));
|
||||
}
|
||||
});
|
||||
dynamic
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_all_tuples!(impl_tuple_map_each_cloned);
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ impl Button {
|
|||
.spawn();
|
||||
}
|
||||
(true, Some(style)) => {
|
||||
style.update(new_style);
|
||||
style.set(new_style);
|
||||
self.color_animation.clear();
|
||||
}
|
||||
_ => {
|
||||
|
|
|
|||
|
|
@ -118,8 +118,8 @@ fn update_progress_bar(
|
|||
}
|
||||
Progress::Percent(value) => {
|
||||
let _stopped_animation = indeterminant_animation.take();
|
||||
start.update(ZeroToOne::ZERO);
|
||||
end.update(value);
|
||||
start.set(ZeroToOne::ZERO);
|
||||
end.set(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -127,7 +127,7 @@ fn update_progress_bar(
|
|||
/// A value that can be used in a progress indicator.
|
||||
pub trait Progressable<T>: IntoDynamic<T> + Sized
|
||||
where
|
||||
T: ProgressValue,
|
||||
T: ProgressValue + Send,
|
||||
{
|
||||
/// Returns a new progress bar that displays progress from `T::MIN` to
|
||||
/// `T::MAX`.
|
||||
|
|
@ -145,7 +145,7 @@ where
|
|||
fn progress_bar_to(self, max: impl IntoValue<T::Value>) -> ProgressBar
|
||||
where
|
||||
T: Send,
|
||||
T::Value: Ranged + Send + Clone,
|
||||
T::Value: PartialEq + Ranged + Send + Clone,
|
||||
{
|
||||
let max = max.into_value();
|
||||
match max {
|
||||
|
|
@ -181,7 +181,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<U> Progressable<U> for Dynamic<U> where U: ProgressValue {}
|
||||
impl<U> Progressable<U> for Dynamic<U> where U: ProgressValue + Send {}
|
||||
|
||||
/// A value that can be used in a progress indicator.
|
||||
pub trait ProgressValue: 'static {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ where
|
|||
.into_columns()
|
||||
.into_button()
|
||||
.on_click(move |()| {
|
||||
self.state.update(self.value.clone());
|
||||
self.state.set(self.value.clone());
|
||||
})
|
||||
.kind(self.kind)
|
||||
.make_widget()
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ impl Widget for Scroll {
|
|||
let scroll_pct = scroll.y.into_float() / current_max_scroll.y.into_float();
|
||||
scroll.y = max_scroll_y * scroll_pct;
|
||||
}
|
||||
self.scroll.update(scroll);
|
||||
self.scroll.set(scroll);
|
||||
self.control_size = control_size;
|
||||
self.content_size = new_content_size;
|
||||
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ where
|
|||
start = value;
|
||||
self.focused_knob = Some(Knob::Start);
|
||||
}
|
||||
self.value.update(T::from_parts(start, opt_end));
|
||||
self.value.set(T::from_parts(start, opt_end));
|
||||
}
|
||||
|
||||
fn step(&mut self, forwards: bool, factor: f32) {
|
||||
|
|
@ -391,7 +391,7 @@ where
|
|||
(Knob::Start, Some(end)) => (new_value, Some(end)),
|
||||
(Knob::End, Some(start)) => (start, Some(new_value)),
|
||||
};
|
||||
self.value.update(T::from_parts(start, end));
|
||||
self.value.set(T::from_parts(start, end));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,8 +39,7 @@ impl Space {
|
|||
|
||||
impl Widget for Space {
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) {
|
||||
self.color.redraw_when_changed(context);
|
||||
let color = self.color.get();
|
||||
let color = self.color.get_tracked(context);
|
||||
context.fill(color);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ impl Window<WidgetInstance> {
|
|||
/// of `false`.
|
||||
pub fn focused(mut self, focused: impl IntoDynamic<bool>) -> Self {
|
||||
let focused = focused.into_dynamic();
|
||||
focused.update(false);
|
||||
focused.set(false);
|
||||
self.focused = Some(focused);
|
||||
self
|
||||
}
|
||||
|
|
@ -187,7 +187,7 @@ impl Window<WidgetInstance> {
|
|||
/// `occluded` will be initialized with an initial state of `false`.
|
||||
pub fn occluded(mut self, occluded: impl IntoDynamic<bool>) -> Self {
|
||||
let occluded = occluded.into_dynamic();
|
||||
occluded.update(false);
|
||||
occluded.set(false);
|
||||
self.occluded = Some(occluded);
|
||||
self
|
||||
}
|
||||
|
|
@ -528,7 +528,7 @@ where
|
|||
|
||||
let theme_mode = match settings.theme_mode.take() {
|
||||
Some(Value::Dynamic(dynamic)) => {
|
||||
dynamic.update(window.theme().into());
|
||||
dynamic.set(window.theme().into());
|
||||
Value::Dynamic(dynamic)
|
||||
}
|
||||
Some(Value::Constant(mode)) => Value::Constant(mode),
|
||||
|
|
@ -660,7 +660,7 @@ where
|
|||
window: kludgine::app::Window<'_, WindowCommand>,
|
||||
_kludgine: &mut Kludgine,
|
||||
) {
|
||||
self.focused.update(window.focused());
|
||||
self.focused.set(window.focused());
|
||||
}
|
||||
|
||||
fn occlusion_changed(
|
||||
|
|
@ -668,7 +668,7 @@ where
|
|||
window: kludgine::app::Window<'_, WindowCommand>,
|
||||
_kludgine: &mut Kludgine,
|
||||
) {
|
||||
self.occluded.update(window.ocluded());
|
||||
self.occluded.set(window.ocluded());
|
||||
}
|
||||
|
||||
fn render<'pass>(
|
||||
|
|
@ -1104,7 +1104,7 @@ where
|
|||
_kludgine: &mut Kludgine,
|
||||
) {
|
||||
if let Value::Dynamic(theme_mode) = &self.theme_mode {
|
||||
theme_mode.update(window.theme().into());
|
||||
theme_mode.set(window.theme().into());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue