More user guide work

This commit is contained in:
Jonathan Johnson 2024-01-07 15:57:23 -08:00
parent 01d6da3d35
commit adb51dba7e
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
11 changed files with 182 additions and 10 deletions

View file

@ -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!"
}

View 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();
}

View file

@ -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)

View file

@ -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.

View 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:
![Threaded Progress Bar Update](../examples/thread_progress.png)
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}}
```
![Hello Ferris Example](../examples/intro.png)
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>

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -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>

View file

@ -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>,

View file

@ -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

View file

@ -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