Helpers galore

This commit is contained in:
Jonathan Johnson 2023-11-14 09:31:56 -08:00
parent 494fa680cb
commit 4c7c3be5ba
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
23 changed files with 336 additions and 174 deletions

View file

@ -16,16 +16,22 @@ reactive data models work, consider this example that displays a button that
increments its own label:
```rust,ignore
// Create a dynamic usize.
let count = Dynamic::new(0_usize);
fn main() -> gooey::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 with a label that is produced by mapping the contents
// of `count`.
Button::new(count.map_each(ToString::to_string))
// Set the `on_click` callback to a closure that increments the counter.
.on_click(count.with_clone(|count| move |_| count.set(count.get() + 1)))
// Run the button as an an application.
.run()
// Create a new button whose text is our dynamic string.
count_label
.into_button()
// Set the `on_click` callback to a closure that increments the counter.
.on_click(count.with_clone(|count| move |_| count.set(count.get() + 1)))
// Position the button in the center
.centered()
// Run the application
.run()
}
```
[widget]: crate::widget::Widget
@ -33,7 +39,7 @@ Button::new(count.map_each(ToString::to_string))
[wgpu]: https://github.com/gfx-rs/wgpu
[winit]: https://github.com/rust-windowing/winit
[widgets]: mod@crate::widgets
[button-example]: https://github.com/khonsulabs/gooey/tree/main/examples/button.rs
[button-example]: https://github.com/khonsulabs/gooey/tree/main/examples/basic-button.rs
## Open-source Licenses

View file

@ -16,7 +16,7 @@ reactive data models work, consider this example that displays a button that
increments its own label:
```rust,ignore
$../examples/button.rs:readme$
$../examples/basic-button.rs:readme$
```
[widget]: $widget$
@ -24,4 +24,4 @@ $../examples/button.rs:readme$
[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/button.rs
[button-example]: https://github.com/khonsulabs/gooey/tree/$ref-name$/examples/basic-button.rs

View file

@ -18,16 +18,22 @@ reactive data models work, consider this example that displays a button that
increments its own label:
```rust,ignore
// Create a dynamic usize.
let count = Dynamic::new(0_usize);
fn main() -> gooey::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 with a label that is produced by mapping the contents
// of `count`.
Button::new(count.map_each(ToString::to_string))
// Set the `on_click` callback to a closure that increments the counter.
.on_click(count.with_clone(|count| move |_| count.set(count.get() + 1)))
// Run the button as an an application.
.run()
// Create a new button whose text is our dynamic string.
count_label
.into_button()
// Set the `on_click` callback to a closure that increments the counter.
.on_click(count.with_clone(|count| move |_| count.set(count.get() + 1)))
// Position the button in the center
.centered()
// Run the application
.run()
}
```
[widget]: https://gooey.rs/main/gooey/widget/trait.Widget.html
@ -35,7 +41,7 @@ Button::new(count.map_each(ToString::to_string))
[wgpu]: https://github.com/gfx-rs/wgpu
[winit]: https://github.com/rust-windowing/winit
[widgets]: https://gooey.rs/main/gooey/widgets/index.html
[button-example]: https://github.com/khonsulabs/gooey/tree/main/examples/button.rs
[button-example]: https://github.com/khonsulabs/gooey/tree/main/examples/basic-button.rs
## Open-source Licenses

View file

@ -1,9 +1,8 @@
use std::time::Duration;
use gooey::animation::{AnimationHandle, AnimationTarget, IntoAnimate, Spawn};
use gooey::value::Dynamic;
use gooey::value::{Dynamic, StringValue};
use gooey::widget::MakeWidget;
use gooey::widgets::{Button, Label, Stack};
use gooey::{Run, WithClone};
fn main() -> gooey::Result {
@ -18,13 +17,17 @@ fn main() -> gooey::Result {
.on_complete(|| println!("Gooey animations are neat!"))
.launch();
Stack::columns(
Button::new("To 0")
.on_click(animate_to(&animation, &value, 0))
.and(Label::new(label))
.and(Button::new("To 100").on_click(animate_to(&animation, &value, 100))),
)
.run()
"To 0"
.into_button()
.on_click(animate_to(&animation, &value, 0))
.and(label)
.and(
"To 100"
.into_button()
.on_click(animate_to(&animation, &value, 100)),
)
.into_columns()
.run()
}
fn animate_to(

22
examples/basic-button.rs Normal file
View file

@ -0,0 +1,22 @@
use gooey::value::{Dynamic, StringValue};
use gooey::widget::MakeWidget;
use gooey::Run;
// begin rustme snippet: readme
fn main() -> gooey::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
.into_button()
// Set the `on_click` callback to a closure that increments the counter.
.on_click(count.with_clone(|count| move |_| count.set(count.get() + 1)))
// Position the button in the center
.centered()
// Run the application
.run()
}
// end rustme snippet

View file

@ -1,6 +1,5 @@
use gooey::value::Dynamic;
use gooey::value::{Dynamic, StringValue};
use gooey::widget::{MakeWidget, WidgetInstance};
use gooey::widgets::{Button, Label};
use gooey::window::ThemeMode;
use gooey::Run;
@ -10,7 +9,7 @@ fn main() -> gooey::Result {
.centered()
.expand()
.into_window()
.with_theme_mode(theme_mode)
.themed_mode(theme_mode)
.run()
}
@ -18,20 +17,21 @@ fn set_of_containers(repeat: usize, theme_mode: Dynamic<ThemeMode>) -> WidgetIns
let inner = if let Some(remaining_iters) = repeat.checked_sub(1) {
set_of_containers(remaining_iters, theme_mode)
} else {
Button::new("Toggle Theme Mode")
"Toggle Theme Mode"
.into_button()
.on_click(move |_| {
theme_mode.map_mut(|mode| mode.toggle());
})
.make_widget()
};
Label::new("Lowest")
"Lowest"
.and(
Label::new("Low")
"Low"
.and(
Label::new("Mid")
"Mid"
.and(
Label::new("High")
.and(Label::new("Highest").and(inner).into_rows().contain())
"High"
.and("Highest".and(inner).into_rows().contain())
.into_rows()
.contain(),
)

View file

@ -1,8 +1,7 @@
use std::string::ToString;
use gooey::value::Dynamic;
use gooey::value::{Dynamic, StringValue};
use gooey::widget::MakeWidget;
use gooey::widgets::{Button, Label};
use gooey::Run;
use kludgine::figures::units::Lp;
@ -10,14 +9,14 @@ fn main() -> gooey::Result {
let counter = Dynamic::new(0i32);
let label = counter.map_each(ToString::to_string);
Label::new(label)
label
.width(Lp::points(100))
.and(Button::new("+").on_click(counter.with_clone(|counter| {
.and("+".into_button().on_click(counter.with_clone(|counter| {
move |_| {
*counter.lock() += 1;
}
})))
.and(Button::new("-").on_click(counter.with_clone(|counter| {
.and("-".into_button().on_click(counter.with_clone(|counter| {
move |_| {
*counter.lock() -= 1;
}

View file

@ -1,6 +1,6 @@
use gooey::value::Dynamic;
use gooey::value::{Dynamic, StringValue};
use gooey::widget::{MakeWidget, HANDLED, IGNORED};
use gooey::widgets::{Input, Label, Space};
use gooey::widgets::Space;
use gooey::Run;
use kludgine::app::winit::event::ElementState;
use kludgine::app::winit::keyboard::Key;
@ -10,13 +10,14 @@ fn main() -> gooey::Result {
let chat_log = Dynamic::new("Chat log goes here.\n".repeat(100));
let chat_message = Dynamic::new(String::new());
Label::new(chat_log.clone())
chat_log
.clone()
.vertical_scroll()
.expand()
.and(Space::colored(Color::RED).expand_weighted(2))
.into_columns()
.expand()
.and(Input::new(chat_message.clone()).on_key(move |input| {
.and(chat_message.clone().into_input().on_key(move |input| {
match (input.state, input.logical_key) {
(ElementState::Pressed, Key::Enter) => {
let new_message = chat_message.map_mut(std::mem::take);

View file

@ -1,7 +1,7 @@
use gooey::value::StringValue;
use gooey::widget::MakeWidget;
use gooey::widgets::Input;
use gooey::Run;
fn main() -> gooey::Result {
Input::new("Hello").expand().run()
"Hello".into_input().expand().run()
}

View file

@ -1,8 +1,8 @@
use std::process::exit;
use gooey::value::{Dynamic, MapEach};
use gooey::value::{Dynamic, MapEach, StringValue};
use gooey::widget::MakeWidget;
use gooey::widgets::{Button, Expand, Input, Label};
use gooey::widgets::Expand;
use gooey::Run;
use kludgine::figures::units::Lp;
@ -14,18 +14,19 @@ fn main() -> gooey::Result {
(&username, &password).map_each(|(username, password)| validate(username, password));
// TODO this should be a grid layout to ensure proper visual alignment.
let username_row = Label::new("Username")
.and(Input::new(username.clone()).expand())
let username_row = "Username"
.and(username.clone().into_input().expand())
.into_columns();
let password_row = Label::new("Password")
let password_row = "Password"
.and(
// TODO secure input
Input::new(password.clone()).expand(),
password.clone().into_input().expand(),
)
.into_columns();
let buttons = Button::new("Cancel")
let buttons = "Cancel"
.into_button()
.on_click(|_| {
eprintln!("Login cancelled");
exit(0)
@ -33,7 +34,8 @@ fn main() -> gooey::Result {
.into_escape()
.and(Expand::empty())
.and(
Button::new("Log In")
"Log In"
.into_button()
.enabled(valid)
.on_click(move |_| {
println!("Welcome, {}", username.get());

View file

@ -1,9 +1,8 @@
use gooey::widget::MakeWidget;
use gooey::widgets::Label;
use gooey::Run;
fn main() -> gooey::Result {
Label::new(include_str!("../src/widgets/scroll.rs"))
include_str!("../src/widgets/scroll.rs")
.scroll()
.expand()
.run()

View file

@ -0,0 +1,53 @@
use gooey::value::StringValue;
use gooey::widget::MakeWidget;
use gooey::Run;
/// This example shows a tricky layout problem. The hierarchy of widgets is
/// this:
///
/// ```text
/// Expand (.expand())
/// | Align (.centered())
/// | | Stack (.into_rows())
/// | | | Label
/// | | | Align (.centered())
/// | | | | Button
/// ```
///
/// When the Stack widget attempted to implmement a single-pass layout, this
/// caused the Button to be aligned to the left inside of the stack. The Stack
/// widget now utilizes two `layout()` operations for layouts like this. Here's
/// the reasoning:
///
/// At the window root, we have an Align wrapped by an Expand. The Align widget
/// during layout asks its children to size-to-fit. This means the Stack is
/// asking its children to size-to-fit as well.
///
/// The Stack's orientation is Rows, and since the children are Resizes or
/// Expands, the widgets are size-to-fit. This means that the Stack will measure
/// these widgets asking them to size to fit.
///
/// After running this pass of measurement, we can assign the heights of each of
/// the rows to the measurements we received. The width of the stack becomes the
/// maximum width of all children measured.
///
/// In a single-pass layout, this means the Align widget inside of the Stack
/// never receives an opportunity to lay its children out with the final width.
/// The Button does end up centered because of this. Fixing it also becomes
/// tricky, because if surround the button in an Expand, it now instructs the
/// Stack to expand to fill its parent.
///
/// After some careful deliberation, @ecton reasoned that in the situation where
/// a Stack is asked to layout with the Stack's non-primary being a size-to-fit
/// measurement, a second layout call for all children is required with Known
/// measurements to allow layouts like this example to work correctly.
fn main() -> gooey::Result {
// TODO once we have offscreen rendering, turn this into a test case
"Really Long Label"
.and("Short".into_button().centered())
.into_rows()
.contain()
.centered()
.expand()
.run()
}

View file

@ -1,12 +1,12 @@
use gooey::styles::components::TextColor;
use gooey::widget::MakeWidget;
use gooey::widgets::stack::Stack;
use gooey::widgets::{Button, Style};
use gooey::widgets::Style;
use gooey::Run;
use kludgine::Color;
fn main() -> gooey::Result {
Stack::rows(Button::new("Green").and(red_text(Button::new("Red"))))
Stack::rows("Green".and(red_text("Red")))
.with(&TextColor, Color::GREEN)
.run()
}

View file

@ -1,6 +1,6 @@
use gooey::value::Dynamic;
use gooey::value::{Dynamic, StringValue};
use gooey::widget::{MakeWidget, WidgetInstance};
use gooey::widgets::{Button, Label, Switcher};
use gooey::widgets::Switcher;
use gooey::Run;
#[derive(Debug)]
@ -24,9 +24,10 @@ fn main() -> gooey::Result {
fn intro(active: Dynamic<ActiveContent>) -> WidgetInstance {
const INTRO: &str = "This example demonstrates the Switcher<T> widget, which uses a mapping function to convert from a generic type to the widget it uses for its contents.";
Label::new(INTRO)
INTRO
.and(
Button::new("Switch!")
"Switch!"
.into_button()
.on_click(move |_| active.set(ActiveContent::Success))
.centered(),
)
@ -35,11 +36,12 @@ fn intro(active: Dynamic<ActiveContent>) -> WidgetInstance {
}
fn success(active: Dynamic<ActiveContent>) -> WidgetInstance {
Label::new("The value changed to `ActiveContent::Success`!")
"The value changed to `ActiveContent::Success`!"
.and(
Button::new("Start Over")
"Start Over"
.into_button()
.on_click(move |_| active.set(ActiveContent::Intro))
// .centered(),
.centered(),
)
.into_rows()
.make_widget()

View file

@ -5,9 +5,10 @@ use gooey::styles::components::{TextColor, WidgetBackground};
use gooey::styles::{
ColorScheme, ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme, ThemePair,
};
use gooey::value::{Dynamic, MapEach};
use gooey::value::{Dynamic, MapEach, StringValue};
use gooey::widget::MakeWidget;
use gooey::widgets::{Input, Label, ModeSwitch, Scroll, Slider, Stack, Themed};
use gooey::widgets::slider::Slidable;
use gooey::widgets::{Slider, Stack};
use gooey::window::ThemeMode;
use gooey::Run;
use kludgine::Color;
@ -44,38 +45,37 @@ fn main() -> gooey::Result {
},
);
Themed::new(
default_theme.clone(),
Stack::columns(
Scroll::vertical(Stack::rows(
theme_switcher
.and(primary_editor)
.and(secondary_editor)
.and(tertiary_editor)
.and(error_editor)
.and(neutral_editor)
.and(neutral_variant_editor),
))
.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),
))
.and(theme(
default_theme.map_each(|theme| theme.dark),
ThemeMode::Dark,
))
.and(theme(
default_theme.map_each(|theme| theme.light),
ThemeMode::Light,
)),
),
)
.pad()
.expand()
.into_window()
.with_theme_mode(theme_mode)
.run()
let editors = theme_switcher
.and(primary_editor)
.and(secondary_editor)
.and(tertiary_editor)
.and(error_editor)
.and(neutral_editor)
.and(neutral_variant_editor)
.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),
))
.and(theme(
default_theme.map_each(|theme| theme.dark),
ThemeMode::Dark,
))
.and(theme(
default_theme.map_each(|theme| theme.light),
ThemeMode::Light,
))
.into_columns()
.themed(default_theme)
.pad()
.expand()
.into_window()
.themed_mode(theme_mode)
.run()
}
fn dark_mode_slider() -> (Dynamic<ThemeMode>, impl MakeWidget) {
@ -83,7 +83,7 @@ fn dark_mode_slider() -> (Dynamic<ThemeMode>, impl MakeWidget) {
(
theme_mode.clone(),
Stack::rows(Label::new("Theme Mode").and(Slider::<ThemeMode>::from_value(theme_mode))),
"Theme Mode".and(theme_mode.slider()).into_rows(),
)
}
@ -114,11 +114,11 @@ fn color_editor(
(
color,
Stack::rows(
Label::new(label)
.and(Slider::<f32>::new(hue, 0., 360.))
.and(Input::new(hue_text))
label
.and(hue.slider_between(0., 360.))
.and(hue_text.into_input())
.and(Slider::<ZeroToOne>::from_value(saturation))
.and(Input::new(saturation_text)),
.and(saturation_text.into_input()),
),
)
}
@ -128,69 +128,64 @@ fn fixed_themes(
secondary: Dynamic<FixedTheme>,
tertiary: Dynamic<FixedTheme>,
) -> impl MakeWidget {
Stack::rows(
Label::new("Fixed")
.and(fixed_theme(primary, "Primary"))
.and(fixed_theme(secondary, "Secondary"))
.and(fixed_theme(tertiary, "Tertiary")),
)
.contain()
.expand()
"Fixed"
.and(fixed_theme(primary, "Primary"))
.and(fixed_theme(secondary, "Secondary"))
.and(fixed_theme(tertiary, "Tertiary"))
.into_rows()
.contain()
.expand()
}
fn fixed_theme(theme: Dynamic<FixedTheme>, label: &str) -> impl MakeWidget {
let color = theme.map_each(|theme| theme.color);
let on_color = theme.map_each(|theme| theme.on_color);
Stack::columns(
swatch(color.clone(), &format!("{label} Fixed"), on_color.clone())
.and(swatch(
theme.map_each(|theme| theme.dim_color),
&format!("Dim {label}"),
on_color.clone(),
))
.and(swatch(
on_color.clone(),
&format!("On {label} Fixed"),
color.clone(),
))
.and(swatch(
theme.map_each(|theme| theme.on_color_variant),
&format!("Variant On {label} Fixed"),
color,
)),
)
.contain()
.expand()
swatch(color.clone(), &format!("{label} Fixed"), on_color.clone())
.and(swatch(
theme.map_each(|theme| theme.dim_color),
&format!("Dim {label}"),
on_color.clone(),
))
.and(swatch(
on_color.clone(),
&format!("On {label} Fixed"),
color.clone(),
))
.and(swatch(
theme.map_each(|theme| theme.on_color_variant),
&format!("Variant On {label} Fixed"),
color,
))
.into_columns()
.contain()
.expand()
}
fn theme(theme: Dynamic<Theme>, mode: ThemeMode) -> impl MakeWidget {
ModeSwitch::new(
mode,
Stack::rows(
Label::new(match mode {
ThemeMode::Light => "Light",
ThemeMode::Dark => "Dark",
})
.and(
Stack::columns(
color_theme(theme.map_each(|theme| theme.primary), "Primary")
.and(color_theme(
theme.map_each(|theme| theme.secondary),
"Secondary",
))
.and(color_theme(
theme.map_each(|theme| theme.tertiary),
"Tertiary",
))
.and(color_theme(theme.map_each(|theme| theme.error), "Error")),
)
.contain()
.expand(),
)
.and(surface_theme(theme.map_each(|theme| theme.surface))),
)
.contain(),
match mode {
ThemeMode::Light => "Light",
ThemeMode::Dark => "Dark",
}
.and(
color_theme(theme.map_each(|theme| theme.primary), "Primary")
.and(color_theme(
theme.map_each(|theme| theme.secondary),
"Secondary",
))
.and(color_theme(
theme.map_each(|theme| theme.tertiary),
"Tertiary",
))
.and(color_theme(theme.map_each(|theme| theme.error), "Error"))
.into_columns()
.contain()
.expand(),
)
.and(surface_theme(theme.map_each(|theme| theme.surface)))
.into_rows()
.contain()
.themed_mode(mode)
.expand()
}
@ -310,7 +305,7 @@ fn color_theme(theme: Dynamic<ColorTheme>, label: &str) -> impl MakeWidget {
}
fn swatch(background: Dynamic<Color>, label: &str, text: Dynamic<Color>) -> impl MakeWidget {
Label::new(label)
label
.with(&TextColor, text)
.with(&WidgetBackground, background)
.fit_horizontally()

View file

@ -1,6 +1,6 @@
use manyhow::{manyhow, Result};
use quote_use::quote_use as quote;
use proc_macro2::TokenStream;
use quote_use::quote_use as quote;
mod animation;
#[manyhow(proc_macro_derive(LinearInterpolate))]

View file

@ -16,6 +16,7 @@ use crate::animation::{DynamicTransition, LinearInterpolate};
use crate::context::{WidgetContext, WindowHandle};
use crate::utils::{IgnorePoison, WithClone};
use crate::widget::WidgetId;
use crate::widgets::{Button, Input};
/// An instance of a value that provides APIs to observe and react to its
/// contents.
@ -1251,3 +1252,18 @@ macro_rules! impl_tuple_map_each {
}
impl_all_tuples!(impl_tuple_map_each);
/// A type that can be converted into a [`Value<String>`].
pub trait StringValue: IntoValue<String> + Sized {
/// Returns this string as a text input widget.
fn into_input(self) -> Input {
Input::new(self.into_value())
}
/// Returns this string as a clickable button.
fn into_button(self) -> Button {
Button::new(self.into_value())
}
}
impl<T> StringValue for T where T: IntoValue<String> {}

View file

@ -24,7 +24,7 @@ use crate::styles::{
use crate::tree::Tree;
use crate::utils::IgnorePoison;
use crate::value::{IntoValue, Value};
use crate::widgets::{Align, Container, Expand, Resize, Scroll, Stack, Style};
use crate::widgets::{Align, Container, Expand, Resize, Scroll, Stack, Style, Themed, ThemedMode};
use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior};
use crate::{ConstraintLimit, Run};
@ -727,6 +727,16 @@ pub trait MakeWidget: Sized {
fn pad_by(self, padding: impl IntoValue<Edges<Dimension>>) -> Container {
self.contain().transparent().pad_by(padding)
}
/// Applies `theme` to `self` and its children.
fn themed(self, theme: impl IntoValue<ThemePair>) -> Themed {
Themed::new(theme, self)
}
/// Applies `mode` to `self` and its children.
fn themed_mode(self, mode: impl IntoValue<ThemeMode>) -> ThemedMode {
ThemedMode::new(mode, self)
}
}
/// A type that can create a [`WidgetInstance`] with a preallocated

View file

@ -10,7 +10,7 @@ pub mod label;
mod mode_switch;
mod resize;
pub mod scroll;
mod slider;
pub mod slider;
mod space;
pub mod stack;
mod style;
@ -25,7 +25,7 @@ pub use container::Container;
pub use expand::Expand;
pub use input::Input;
pub use label::Label;
pub use mode_switch::ModeSwitch;
pub use mode_switch::ThemedMode;
pub use resize::Resize;
pub use scroll::Scroll;
pub use slider::Slider;

View file

@ -5,12 +5,12 @@ use crate::window::ThemeMode;
/// A widget that applies a set of [`ThemeMode`] to all contained widgets.
#[derive(Debug)]
pub struct ModeSwitch {
pub struct ThemedMode {
mode: Value<ThemeMode>,
child: WidgetRef,
}
impl ModeSwitch {
impl ThemedMode {
/// Returns a new widget that applies `mode` to all of its children.
pub fn new(mode: impl IntoValue<ThemeMode>, child: impl MakeWidget) -> Self {
Self {
@ -20,7 +20,7 @@ impl ModeSwitch {
}
}
impl WrapperWidget for ModeSwitch {
impl WrapperWidget for ThemedMode {
fn child_mut(&mut self) -> &mut WidgetRef {
&mut self.child
}

View file

@ -1,3 +1,4 @@
//! A widget that allows a user to "slide" between values.
use std::fmt::Debug;
use std::panic::UnwindSafe;
@ -327,3 +328,44 @@ define_components! {
InactiveTrackColor(Color, "inactive_track_color", |context| context.get(&OpaqueWidgetColor))
}
}
/// A value that can be used in a [`Slider`] widget.
pub trait Slidable<T>: IntoDynamic<T> + Sized
where
T: Clone
+ Debug
+ PartialOrd
+ LinearInterpolate
+ PercentBetween
+ UnwindSafe
+ Send
+ 'static,
{
/// Returns a new slider over the full [range](Ranged) of the type.
fn slider(self) -> Slider<T>
where
T: Ranged,
{
Slider::from_value(self.into_dynamic())
}
/// Returns a new slider using the value of `self`. The slider will be
/// limited to values between `min` and `max`.
fn slider_between(self, min: impl IntoValue<T>, max: impl IntoValue<T>) -> Slider<T> {
Slider::new(self.into_dynamic(), min, max)
}
}
impl<U, T> Slidable<U> for T
where
T: IntoDynamic<U>,
U: Clone
+ Debug
+ PartialOrd
+ LinearInterpolate
+ PercentBetween
+ UnwindSafe
+ Send
+ 'static,
{
}

View file

@ -139,7 +139,7 @@ impl Window<WidgetInstance> {
///
/// `focused` will be initialized with an initial state
/// of `false`.
pub fn with_focused(mut self, focused: impl IntoDynamic<bool>) -> Self {
pub fn focused(mut self, focused: impl IntoDynamic<bool>) -> Self {
let focused = focused.into_dynamic();
focused.update(false);
self.focused = Some(focused);
@ -154,7 +154,7 @@ impl Window<WidgetInstance> {
/// visible, this value will contain `true`.
///
/// `occluded` will be initialized with an initial state of `false`.
pub fn with_occluded(mut self, occluded: impl IntoDynamic<bool>) -> Self {
pub fn occluded(mut self, occluded: impl IntoDynamic<bool>) -> Self {
let occluded = occluded.into_dynamic();
occluded.update(false);
self.occluded = Some(occluded);
@ -174,10 +174,16 @@ impl Window<WidgetInstance> {
/// Setting the [`Dynamic`]'s value will also update the window with the new
/// mode until a mode change is detected, upon which the new mode will be
/// stored.
pub fn with_theme_mode(mut self, theme_mode: impl IntoValue<ThemeMode>) -> Self {
pub fn themed_mode(mut self, theme_mode: impl IntoValue<ThemeMode>) -> Self {
self.theme_mode = Some(theme_mode.into_value());
self
}
/// Applies `theme` to the widgets in this window.
pub fn themed(mut self, theme: impl IntoValue<ThemePair>) -> Self {
self.theme = theme.into_value();
self
}
}
impl<Behavior> Window<Behavior>