mirror of
https://github.com/danbulant/cushy
synced 2026-06-19 14:31:04 +00:00
More user guide work
This commit is contained in:
parent
01d6da3d35
commit
adb51dba7e
11 changed files with 182 additions and 10 deletions
|
|
@ -8,7 +8,7 @@ fn main() -> cushy::Result {
|
|||
|
||||
#[test]
|
||||
fn book() {
|
||||
fn hello_world() -> impl MakeWidget {
|
||||
fn hello_world() -> impl cushy::widget::MakeWidget {
|
||||
"Hello, World!"
|
||||
}
|
||||
|
||||
|
|
|
|||
34
guide/guide-examples/examples/thread-progress.rs
Normal file
34
guide/guide-examples/examples/thread-progress.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use cushy::value::{Destination, Dynamic, Source};
|
||||
use cushy::widget::MakeWidget;
|
||||
use cushy::widgets::progress::Progressable;
|
||||
use guide_examples::book_example;
|
||||
|
||||
fn thread_progress() -> impl MakeWidget {
|
||||
// ANCHOR: example
|
||||
let progress = Dynamic::new(0_u8);
|
||||
std::thread::spawn({
|
||||
let progress = progress.clone();
|
||||
move || {
|
||||
while progress.get() < 10 {
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
progress.set(progress.get() + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
progress.progress_bar_to(10)
|
||||
// ANCHOR_END: example
|
||||
}
|
||||
|
||||
fn main() {
|
||||
book_example!(thread_progress).animated(|recorder| {
|
||||
recorder.wait_for(Duration::from_secs(2)).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runs() {
|
||||
main();
|
||||
}
|
||||
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
- [About Cushy](./about.md)
|
||||
- [Cushy's Philosophies](./about/philosophies.md)
|
||||
- [Everything is a `Widget`](./about/widgets.md)
|
||||
- [Reactive Data Model](./about/reactive.md)
|
||||
- [Widgets]()
|
||||
- [Layout Widgets](./widgets/layout.md)
|
||||
- [Align](./widgets/layout/align.md)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ displayed. Here's the philosophies that drive Cushy's design:
|
|||
|
||||
- Cushy retains information between redraws so that many events can be handled
|
||||
without redrawing the user interface.
|
||||
- Everything is a widget. The "root" of a user interface/window is a widget, and
|
||||
widgets can contain other widgets.
|
||||
- [Everything is a widget](./widgets.md). The "root" of a user interface/window
|
||||
is a widget, and widgets can contain other widgets.
|
||||
- Composition is powerful and easy to reason about. The built-in widget library
|
||||
is aimed at providing a suite of single-purpose widgets that can be composed
|
||||
to create more complex user interfaces.
|
||||
|
|
|
|||
93
guide/src/about/reactive.md
Normal file
93
guide/src/about/reactive.md
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# Reactive Data Model
|
||||
|
||||
Cushy is designed around a reactive data model. In many UI frameworks, setting
|
||||
the text of a [`Label`][label] is done by telling the label what it's new text
|
||||
is. The Cushy way is to create a `Dynamic<T>` containing the value you want to
|
||||
display, and give a copy to the label and keep a copy for your application to
|
||||
update as needed.
|
||||
|
||||
When a widget uses a `Dynamic<T>`, it informs Cushy that it needs to be either
|
||||
redrawn or invalidated when its contents change. In turn, when Cushy detects a
|
||||
change, it invalidates what is necessary.
|
||||
|
||||
This means that updating a progress bar from a different thread is as simple as
|
||||
passing a clone of a `Dynamic<T>` that has been provided to a progress bar:
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../guide-examples/examples/thread-progress.rs:example}}
|
||||
```
|
||||
|
||||
The above snippet produces this user interface:
|
||||
|
||||

|
||||
|
||||
This example just shows one simple way that Cushy's reactive model simplifies
|
||||
development. To learn more, let's dive into the data types and traits that power
|
||||
everything.
|
||||
|
||||
## How widgets interact with data
|
||||
|
||||
In Cushy, it's conventional for widgets to accept data using one of these
|
||||
traits:
|
||||
|
||||
| Trait | Target Type | Purpose |
|
||||
|-------|-------------|---------|
|
||||
| [`IntoValue<T>`][intovalue] | [`Value<T>`][value] | For possibly constant or dynamic values. Can be either a `T` or a `Dynamic<T>`. |
|
||||
| [`IntoDynamic<T>`][intodynamic] | [`Dynamic<T>`][dynamic] | For values that are read from and written to. |
|
||||
| [`IntoReadOnly<T>`][intoreadonly] | [`ReadOnly<T>`][readonly] | For values that are read-only. Can be either a `T` or a `DynamicReader<T>`. |
|
||||
| [`IntoDynamicReader<T>`][intodynamicreader] | [`DynamicReader<T>`][dynamicreader] | For values that are read-only, but are unexpected to be constant. In general, `IntoValue<T>` should be preferred if a single value makes sense to accept. |
|
||||
|
||||
Let's look at an example of how these traits are utilizes.
|
||||
[`Label::new()`][label-new] accepts the value it is to display as a
|
||||
`ReadOnly<T>` where `T: Display + ...`.
|
||||
|
||||
This showcases Cushy's philosophy of embracing the Rust type system. Rather than
|
||||
forcing `Label` to receive a `String`, it accepts any type that implements
|
||||
`Display`, This allows it to accept a wide variety of types.
|
||||
|
||||
Beyond basic values, it can also be given a special type that the `Label` can
|
||||
react to when updated: a `Dynamic<T>` or a `DynamicReader<T>`.
|
||||
|
||||
## What is a `Dynamic<T>`?
|
||||
|
||||
A [`Dynamic<T>`][dynamic] is a reference-counted, threadsafe, async-friendly
|
||||
location in memory that can invoke a series of callbacks when its contents
|
||||
change. Let's revisit the example from the [intro](../intro.md):
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../guide-examples/examples/intro.rs:example}}
|
||||
```
|
||||
|
||||

|
||||
|
||||
Both the [`Input`][input] and the [`Label`][label] widgets have been given
|
||||
instances of `Dynamic<String>`s, but they are two different dynamics. The text
|
||||
input field was given the dynamic we want to be edited. We react to the changes
|
||||
through the `name.map_each(...)` callback.
|
||||
|
||||
## What is a `DynamicReader<T>`?
|
||||
|
||||
A [`DynamicReader<T>`][dynamicreader] provides read-only access to a
|
||||
`Dynamic<T>`, and also can:
|
||||
|
||||
- [block][block] the current thread until the underlying `Dynamic<T>` is changed.
|
||||
- [wait][wait] for a change in an async task.
|
||||
- Detect when the underlying `Dynamic<T>` has had all of its instances dropped.
|
||||
|
||||
`DynamicReader<T>`s can be created using [`Dynamic::into_reader`][into-reader]/[`Dynamic::create_reader`][create-reader].
|
||||
|
||||
[value]: <{{ docs }}/value/enum.Value.html>
|
||||
[readonly]: <{{ docs }}/value/enum.ReadOnly.html>
|
||||
[dynamic]: <{{ docs }}/value/struct.Dynamic.html>
|
||||
[into-reader]: <{{ docs }}/value/struct.Dynamic.html#method.into_reader>
|
||||
[create-reader]: <{{ docs }}/value/struct.Dynamic.html#method.create_reader>
|
||||
[dynamicreader]: <{{ docs }}/value/struct.DynamicReader.html>
|
||||
[block]: <{{ docs }}/value/struct.DynamicReader.html#method.block_until_updated>
|
||||
[wait]: <{{ docs }}/value/struct.DynamicReader.html#method.wait_until_updated>
|
||||
[intovalue]: <{{ docs }}/value/trait.IntoValue.html>
|
||||
[intodynamic]: <{{ docs }}/value/trait.IntoDynamic.html>
|
||||
[intoreadonly]: <{{ docs }}/value/trait.IntoReadOnly.html>
|
||||
[intodynamicreader]: <{{ docs }}/value/trait.IntoDynamicReader.html>
|
||||
[label-new]: <{{ docs }}/widgets/label/struct.Label.html#method.new>
|
||||
[label]: <{{ docs }}/widgets/label/struct.Label.html>
|
||||
[input]: <{{ docs }}/widgets/input/struct.Input.html>
|
||||
38
guide/src/about/widgets.md
Normal file
38
guide/src/about/widgets.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Everything is a `Widget`
|
||||
|
||||
A widget is a rectangular area of a screen that implements the
|
||||
[`Widget`][widget] trait. Widgets are the fundamental building block of Cushy.
|
||||
|
||||
The `Widget` trait can look daunting, as it defines every possible function a
|
||||
`Widget` might need in a graphical user interface. Thankfully, the details of
|
||||
how this trait works can be ignored until you're ready to create custom widgets.
|
||||
|
||||
Developing a user interface in Cushy is a two-step process: gather the
|
||||
information for the interface and present the information in one or more
|
||||
widgets.
|
||||
|
||||
Cushy makes the process of creating widgets easy through the
|
||||
[`MakeWidget`][makewidget] trait. Every `Widget` implementor automatically
|
||||
implements `MakeWidget`, but it can also be implemented by any type to make it
|
||||
easy to utilize within Cushy. For example, `String` implements `MakeWidget` by
|
||||
returning a [`Label`][label]. This approach can also be used to convert complex
|
||||
structures into multi-widget components without needing to create any new
|
||||
`Widget` implementations.
|
||||
|
||||
`MakeWidget` is also responsible for why `"Hello, World".run()` works. The
|
||||
[`Run`][run] trait is automatically implemented for all `MakeWidget`
|
||||
implementations. The implementation simply creates a [`Window`][window] from the
|
||||
widget and runs it:
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../../src/widget.rs:run}}
|
||||
```
|
||||
|
||||
So now that we know our goal is to create one or more widgets to represent our
|
||||
data, how do we transform our data and application state into widgets?
|
||||
|
||||
[widget]: <{{ docs }}/widget/trait.Widget.html>
|
||||
[makewidget]: <{{ docs }}/widget/trait.MakeWidget.html>
|
||||
[label]: <{{docs}}/widgets/label/struct.Label.html>
|
||||
[run]: <{{ docs }}/trait.Run.html>
|
||||
[window]: <{{ docs }}/window/struct.Window.html>
|
||||
BIN
guide/src/examples/thread_progress.png
Normal file
BIN
guide/src/examples/thread_progress.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -1,5 +1,8 @@
|
|||
# Space Widget
|
||||
|
||||
The [`Space`][space]
|
||||
The [`Space`][space] widget is used to fill space. It is not interactive, and
|
||||
will always occupy the smallest size it can.
|
||||
|
||||
It can render a color or be clear.
|
||||
|
||||
[space]: <{{ docs }}/widgets/struct.Space.html>
|
||||
|
|
|
|||
|
|
@ -2030,7 +2030,7 @@ impl<T> PartialEq<WeakDynamic<T>> for Dynamic<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A reader that tracks the last generation accessed through this reader.
|
||||
/// A reader of a [`Dynamic<T>`] that tracks the last generation accessed.
|
||||
pub struct DynamicReader<T> {
|
||||
source: Arc<DynamicData<T>>,
|
||||
read_generation: Mutex<Generation>,
|
||||
|
|
|
|||
|
|
@ -455,14 +455,16 @@ pub trait Widget: Send + Debug + 'static {
|
|||
}
|
||||
}
|
||||
|
||||
// ANCHOR: run
|
||||
impl<T> Run for T
|
||||
where
|
||||
T: MakeWidget,
|
||||
{
|
||||
fn run(self) -> crate::Result {
|
||||
Window::<WidgetInstance>::new(self.make_widget()).run()
|
||||
Window::for_widget(self).run()
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: run
|
||||
|
||||
impl<T> Open for T
|
||||
where
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ use crate::value::{
|
|||
Destination, Dynamic, DynamicReader, Generation, IntoDynamic, IntoValue, Source, Value,
|
||||
};
|
||||
use crate::widget::{
|
||||
EventHandling, MakeWidget, MountedWidget, OnceCallback, RootBehavior, Widget, WidgetId,
|
||||
WidgetInstance, HANDLED, IGNORED,
|
||||
EventHandling, MakeWidget, MountedWidget, OnceCallback, RootBehavior, WidgetId, WidgetInstance,
|
||||
HANDLED, IGNORED,
|
||||
};
|
||||
use crate::window::sealed::WindowCommand;
|
||||
use crate::{initialize_tracing, ConstraintLimit};
|
||||
|
|
@ -496,9 +496,9 @@ impl Window<WidgetInstance> {
|
|||
/// Returns a new instance using `widget` as its contents.
|
||||
pub fn for_widget<W>(widget: W) -> Self
|
||||
where
|
||||
W: Widget,
|
||||
W: MakeWidget,
|
||||
{
|
||||
Self::new(WidgetInstance::new(widget))
|
||||
Self::new(widget.make_widget())
|
||||
}
|
||||
|
||||
/// Sets `focused` to be the dynamic updated when this window's focus status
|
||||
|
|
|
|||
Loading…
Reference in a new issue