mirror of
https://github.com/danbulant/cushy
synced 2026-06-23 16:42:28 +00:00
Children renamed to WidgetList
Plus more work on the user's guide, which inspired the rename.
This commit is contained in:
parent
b5bd69136a
commit
6ad6cca32d
35 changed files with 705 additions and 73 deletions
|
|
@ -1,3 +1,6 @@
|
|||
<!-- This file is generated by `rustme`. Ensure you're editing the source in the .rustme/ directory --!>
|
||||
<!-- markdownlint-disable first-line-h1 -->
|
||||
|
||||

|
||||
[](https://crates.io/crates/cushy)
|
||||
[](https://cushy.rs/main/docs/cushy/)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
<!-- This file is generated by `rustme`. Ensure you're editing the source in the .rustme/ directory --!>
|
||||
<!-- markdownlint-disable first-line-h1 -->
|
||||
|
||||

|
||||
[](https://crates.io/crates/cushy)
|
||||
[]($docs$)
|
||||
|
|
|
|||
|
|
@ -42,12 +42,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- `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>`.
|
||||
- `Dynamic<Children>::wrap` has been renamed to `into_wrap` for consistency.
|
||||
- `Dynamic<WidgetList>::wrap` and `WidgetList::wrap` have been renamed to
|
||||
`into_wrap` for consistency.
|
||||
- Cushy now has its own `KeyEvent` type, as winit's has private fields. This
|
||||
prevented simulating input in a `VirtualWindow`.
|
||||
- `FlexibleDimension::ZERO` has been removed, and now `FlexibleDimension`
|
||||
implements `Zero` which defines an associated constant of the same name and
|
||||
purpose.
|
||||
- `Children` has been renamed to `WidgetList`.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
# Cushy
|
||||
|
||||
<!-- This file is generated by `rustme`. Ensure you're editing the source in the .rustme/ directory --!>
|
||||
<!-- markdownlint-disable first-line-h1 -->
|
||||
|
||||

|
||||
[](https://crates.io/crates/cushy)
|
||||
[](https://cushy.rs/main/docs/cushy/)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use cushy::value::{Dynamic, MapEach, Source};
|
||||
use cushy::widget::{Children, MakeWidget};
|
||||
use cushy::widget::{MakeWidget, WidgetList};
|
||||
use cushy::widgets::input::InputValue;
|
||||
use cushy::Run;
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ fn main() -> cushy::Result {
|
|||
.new_select(Some(id), format!("{first} {last}").align_left())
|
||||
.make_widget()
|
||||
})
|
||||
.collect::<Children>()
|
||||
.collect::<WidgetList>()
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use cushy::styles::components::{LineHeight, TextSize};
|
||||
use cushy::value::Dynamic;
|
||||
use cushy::widget::{Children, MakeWidget};
|
||||
use cushy::widget::{MakeWidget, WidgetList};
|
||||
use cushy::widgets::wrap::{VerticalAlign, WrapAlign};
|
||||
use cushy::Run;
|
||||
use figures::units::Lp;
|
||||
|
|
@ -16,7 +16,7 @@ fn main() -> cushy::Result {
|
|||
let text_size = Lp::points(rng.gen_range(14..48));
|
||||
word.with(&TextSize, text_size).with(&LineHeight, text_size)
|
||||
})
|
||||
.collect::<Children>();
|
||||
.collect::<WidgetList>();
|
||||
|
||||
let align = Dynamic::<WrapAlign>::default();
|
||||
let vertical_align = Dynamic::<VerticalAlign>::default();
|
||||
|
|
@ -50,7 +50,7 @@ fn main() -> cushy::Result {
|
|||
.h3()
|
||||
.and(
|
||||
words
|
||||
.wrap()
|
||||
.into_wrap()
|
||||
.align(align)
|
||||
.vertical_align(vertical_align)
|
||||
.expand_horizontally()
|
||||
|
|
|
|||
|
|
@ -11,3 +11,4 @@ extra-watch-dirs = ["./guide-examples/"]
|
|||
|
||||
[preprocessor.variables.variables]
|
||||
docs = "https://cushy.rs/main/docs/cushy"
|
||||
src = "https://github.com/khonsulabs/cushy/tree/main"
|
||||
|
|
|
|||
50
guide/guide-examples/examples/composition-makewidget.rs
Normal file
50
guide/guide-examples/examples/composition-makewidget.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
use cushy::value::{Dynamic, IntoValue, Value};
|
||||
use cushy::widgets::input::InputValue;
|
||||
|
||||
fn composition_makewidget() -> impl cushy::widget::MakeWidget {
|
||||
// ANCHOR: definition
|
||||
use cushy::widget::{MakeWidget, WidgetInstance};
|
||||
|
||||
struct FormField {
|
||||
label: Value<String>,
|
||||
field: WidgetInstance,
|
||||
}
|
||||
|
||||
impl FormField {
|
||||
pub fn new(label: impl IntoValue<String>, field: impl MakeWidget) -> Self {
|
||||
Self {
|
||||
label: label.into_value(),
|
||||
field: field.make_widget(),
|
||||
}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: definition
|
||||
|
||||
// ANCHOR: makewidget
|
||||
impl MakeWidget for FormField {
|
||||
fn make_widget(self) -> WidgetInstance {
|
||||
self.label
|
||||
.align_left()
|
||||
.and(self.field)
|
||||
.into_rows()
|
||||
.make_widget()
|
||||
}
|
||||
}
|
||||
|
||||
FormField::new(
|
||||
"Label",
|
||||
Dynamic::<String>::default()
|
||||
.into_input()
|
||||
.placeholder("Field"),
|
||||
)
|
||||
// ANCHOR_END: makewidget
|
||||
}
|
||||
|
||||
fn main() {
|
||||
guide_examples::book_example!(composition_makewidget).untested_still_frame();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runs() {
|
||||
main();
|
||||
}
|
||||
119
guide/guide-examples/examples/composition-widget.rs
Normal file
119
guide/guide-examples/examples/composition-widget.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
use cushy::context::{GraphicsContext, LayoutContext, Trackable};
|
||||
use cushy::figures::units::{Px, UPx};
|
||||
use cushy::figures::{IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, Size};
|
||||
use cushy::kludgine::text::{MeasuredText, TextOrigin};
|
||||
use cushy::styles::components::IntrinsicPadding;
|
||||
use cushy::value::{Dynamic, IntoValue, Value};
|
||||
use cushy::widget::Widget;
|
||||
use cushy::widgets::input::InputValue;
|
||||
use cushy::ConstraintLimit;
|
||||
|
||||
fn composition_widget() -> impl cushy::widget::MakeWidget {
|
||||
use cushy::widget::{MakeWidget, WidgetRef};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FormField {
|
||||
label: Value<String>,
|
||||
field: WidgetRef,
|
||||
}
|
||||
|
||||
impl FormField {
|
||||
pub fn new(label: impl IntoValue<String>, field: impl MakeWidget) -> Self {
|
||||
Self {
|
||||
label: label.into_value(),
|
||||
field: WidgetRef::new(field),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FormField {
|
||||
fn measured_label(
|
||||
&self,
|
||||
context: &mut GraphicsContext<'_, '_, '_, '_>,
|
||||
) -> MeasuredText<Px> {
|
||||
self.label.invalidate_when_changed(context);
|
||||
self.label.map(|label| context.gfx.measure_text(label))
|
||||
}
|
||||
|
||||
fn label_and_padding_size(
|
||||
&self,
|
||||
context: &mut GraphicsContext<'_, '_, '_, '_>,
|
||||
) -> Size<UPx> {
|
||||
let label_size = self.measured_label(context).size.into_unsigned();
|
||||
let padding = context.get(&IntrinsicPadding).into_upx(context.gfx.scale());
|
||||
Size::new(label_size.width, label_size.height + padding)
|
||||
}
|
||||
}
|
||||
|
||||
// ANCHOR: widget-a
|
||||
impl Widget for FormField {
|
||||
fn layout(
|
||||
&mut self,
|
||||
available_space: Size<ConstraintLimit>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_>,
|
||||
) -> Size<UPx> {
|
||||
let label_and_padding = self.label_and_padding_size(context);
|
||||
let field_available_space = Size::new(
|
||||
available_space.width,
|
||||
available_space.height - label_and_padding.height,
|
||||
);
|
||||
let field = self.field.mounted(context);
|
||||
let field_size = context.for_other(&field).layout(field_available_space);
|
||||
|
||||
let full_size = Size::new(
|
||||
available_space
|
||||
.width
|
||||
.min()
|
||||
.max(field_size.width)
|
||||
.max(label_and_padding.width),
|
||||
field_size.height + label_and_padding.height,
|
||||
);
|
||||
|
||||
context.set_child_layout(
|
||||
&field,
|
||||
Rect::new(
|
||||
Point::new(UPx::ZERO, label_and_padding.height),
|
||||
Size::new(full_size.width, field_size.height),
|
||||
)
|
||||
.into_signed(),
|
||||
);
|
||||
|
||||
full_size
|
||||
}
|
||||
|
||||
// ANCHOR_END: widget-a
|
||||
|
||||
// ANCHOR: widget-b
|
||||
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
|
||||
let label = self.measured_label(context);
|
||||
context.gfx.draw_measured_text(&label, TextOrigin::TopLeft);
|
||||
|
||||
let field = self.field.mounted(context);
|
||||
context.for_other(&field).redraw();
|
||||
}
|
||||
|
||||
// ANCHOR_END: widget-b
|
||||
|
||||
// ANCHOR: widget-c
|
||||
fn unmounted(&mut self, context: &mut cushy::context::EventContext<'_>) {
|
||||
self.field.unmount_in(context);
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: widget-c
|
||||
|
||||
FormField::new(
|
||||
"Label",
|
||||
Dynamic::<String>::default()
|
||||
.into_input()
|
||||
.placeholder("Field"),
|
||||
)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
guide_examples::book_example!(composition_widget).untested_still_frame();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runs() {
|
||||
main();
|
||||
}
|
||||
126
guide/guide-examples/examples/composition-wrapperwidget.rs
Normal file
126
guide/guide-examples/examples/composition-wrapperwidget.rs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
use cushy::context::{GraphicsContext, LayoutContext, Trackable};
|
||||
use cushy::figures::units::{Px, UPx};
|
||||
use cushy::figures::{IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, Size};
|
||||
use cushy::kludgine::text::{MeasuredText, TextOrigin};
|
||||
use cushy::styles::components::IntrinsicPadding;
|
||||
use cushy::value::{Dynamic, IntoValue, Value};
|
||||
use cushy::widget::{WrappedLayout, WrapperWidget};
|
||||
use cushy::widgets::input::InputValue;
|
||||
use cushy::ConstraintLimit;
|
||||
|
||||
fn composition_wrapperwidget() -> impl cushy::widget::MakeWidget {
|
||||
// ANCHOR: definition
|
||||
use cushy::widget::{MakeWidget, WidgetRef};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FormField {
|
||||
label: Value<String>,
|
||||
field: WidgetRef,
|
||||
}
|
||||
|
||||
impl FormField {
|
||||
pub fn new(label: impl IntoValue<String>, field: impl MakeWidget) -> Self {
|
||||
Self {
|
||||
label: label.into_value(),
|
||||
field: WidgetRef::new(field),
|
||||
}
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: definition
|
||||
|
||||
// ANCHOR: helpers
|
||||
impl FormField {
|
||||
fn measured_label(
|
||||
&self,
|
||||
context: &mut GraphicsContext<'_, '_, '_, '_>,
|
||||
) -> MeasuredText<Px> {
|
||||
self.label.invalidate_when_changed(context);
|
||||
self.label.map(|label| context.gfx.measure_text(label))
|
||||
}
|
||||
|
||||
fn label_and_padding_size(
|
||||
&self,
|
||||
context: &mut GraphicsContext<'_, '_, '_, '_>,
|
||||
) -> Size<UPx> {
|
||||
let label_size = self.measured_label(context).size.into_unsigned();
|
||||
let padding = context.get(&IntrinsicPadding).into_upx(context.gfx.scale());
|
||||
Size::new(label_size.width, label_size.height + padding)
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: helpers
|
||||
|
||||
// ANCHOR: wrapperwidget-a
|
||||
impl WrapperWidget for FormField {
|
||||
fn child_mut(&mut self) -> &mut WidgetRef {
|
||||
&mut self.field
|
||||
}
|
||||
|
||||
// ANCHOR_END: wrapperwidget-a
|
||||
|
||||
// ANCHOR: wrapperwidget-b
|
||||
fn adjust_child_constraints(
|
||||
&mut self,
|
||||
available_space: Size<ConstraintLimit>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_>,
|
||||
) -> Size<ConstraintLimit> {
|
||||
let label_and_padding = self.label_and_padding_size(context);
|
||||
Size::new(
|
||||
available_space.width,
|
||||
available_space.height - label_and_padding.height,
|
||||
)
|
||||
}
|
||||
|
||||
// ANCHOR_END: wrapperwidget-b
|
||||
|
||||
// ANCHOR: wrapperwidget-c
|
||||
fn position_child(
|
||||
&mut self,
|
||||
child_size: Size<Px>,
|
||||
available_space: Size<ConstraintLimit>,
|
||||
context: &mut LayoutContext<'_, '_, '_, '_>,
|
||||
) -> WrappedLayout {
|
||||
let label_and_padding = self.label_and_padding_size(context).into_signed();
|
||||
let full_size = Size::new(
|
||||
available_space
|
||||
.width
|
||||
.min()
|
||||
.into_signed()
|
||||
.max(child_size.width)
|
||||
.max(label_and_padding.width),
|
||||
child_size.height + label_and_padding.height,
|
||||
);
|
||||
WrappedLayout {
|
||||
child: Rect::new(
|
||||
Point::new(Px::ZERO, label_and_padding.height),
|
||||
Size::new(full_size.width, child_size.height),
|
||||
),
|
||||
size: full_size.into_unsigned(),
|
||||
}
|
||||
}
|
||||
|
||||
// ANCHOR_END: wrapperwidget-c
|
||||
|
||||
// ANCHOR: wrapperwidget-d
|
||||
fn redraw_foreground(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
|
||||
let label = self.measured_label(context);
|
||||
context.gfx.draw_measured_text(&label, TextOrigin::TopLeft);
|
||||
}
|
||||
}
|
||||
// ANCHOR_END: wrapperwidget-d
|
||||
|
||||
FormField::new(
|
||||
"Label",
|
||||
Dynamic::<String>::default()
|
||||
.into_input()
|
||||
.placeholder("Field"),
|
||||
)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
guide_examples::book_example!(composition_wrapperwidget).untested_still_frame();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runs() {
|
||||
main();
|
||||
}
|
||||
|
|
@ -14,11 +14,9 @@ pub struct BookExampleBuilder {
|
|||
|
||||
impl BookExampleBuilder {
|
||||
pub fn finish(self) -> BookExample {
|
||||
let mut recorder = self.recorder.finish().expect("error creating recorder");
|
||||
recorder.window.set_focused(true);
|
||||
BookExample {
|
||||
name: self.name,
|
||||
recorder,
|
||||
recorder: self.recorder.finish().expect("error creating recorder"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,17 +8,19 @@
|
|||
- [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)
|
||||
- [Composing User Interfaces](./about/composition.md)
|
||||
- [Widgets](./widgets.md)
|
||||
- [Multi-Widget Layout](./widgets/multi-layout.md)
|
||||
- [Grid](./widgets/layout/grid.md)
|
||||
- [Layers](./widgets/layout/layers.md)
|
||||
- [Stack](./widgets/layout/stack.md)
|
||||
- [Wrap](./widgets/layout/wrap.md)
|
||||
- [Single-widget Layout](./widgets/single-layout.md)
|
||||
- [Align](./widgets/layout/align.md)
|
||||
- [Collapse](./widgets/layout/collapse.md)
|
||||
- [Container](./widgets/layout/container.md)
|
||||
- [Expand](./widgets/layout/expand.md)
|
||||
- [Grid](./widgets/layout/grid.md)
|
||||
- [Layers](./widgets/layout/layers.md)
|
||||
- [Resize](./widgets/layout/resize.md)
|
||||
- [Stack](./widgets/layout/stack.md)
|
||||
- [Wrap](./widgets/layout/wrap.md)
|
||||
- [Controls](./widgets/controls.md)
|
||||
- [Button]()
|
||||
- [Canvas]()
|
||||
|
|
@ -32,6 +34,7 @@
|
|||
- [Scroll]()
|
||||
- [Select]()
|
||||
- [Slider]()
|
||||
- [Space](./widgets/controls/space.md)
|
||||
- [Switcher]()
|
||||
- [Image]()
|
||||
- [TileMap]()
|
||||
|
|
@ -41,4 +44,3 @@
|
|||
- [Style]()
|
||||
- [Themed]()
|
||||
- [ThemedMode]()
|
||||
- [Space](./widgets/utility/space.md)
|
||||
|
|
|
|||
260
guide/src/about/composition.md
Normal file
260
guide/src/about/composition.md
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
# Composing User Interfaces
|
||||
|
||||
Designing user interfaces in Cushy can feel different than in other frameworks.
|
||||
Part of what makes Cushy unique is its [reactive data model](./reactive.md). The
|
||||
other significant architectural design was to focus on composition.
|
||||
|
||||
The content area of each [window][window] in Cushy is a single
|
||||
[widget](./widgets.md). A window cannot have more than one widget at its root.
|
||||
So, how is a user interface with multiple widgets built? Through composition.
|
||||
|
||||
Cushy has a category of widgets dedicated to composing multiple widgets into a
|
||||
single widget: [multi-widget layout widgets](../widgets/multi-layout.md). For
|
||||
example, the [`Stack`](../widgets/layout/stack.md) widget positions its children
|
||||
horizontally as a set of columns or vertically as a set of rows, while the
|
||||
[`Layers`](../widgets/layout/layers.md) widget positions its children on top of
|
||||
each other in the Z direction.
|
||||
|
||||
The power to this approach is that adding new layout strategies is as simple as
|
||||
implementing a new [`Widget`][widget] that implements
|
||||
[`Widget::layout`][widget-layout], and that new widget can be used anywhere in
|
||||
Cushy that any other widget can be used.
|
||||
|
||||
## Creating Composable Widgets
|
||||
|
||||
At the core of widget composition are the [`MakeWidget`][makewidget] and
|
||||
[`MakeWidgetWithTag`][makewidgettag] traits. The goal of both of these traits is
|
||||
simple: transform `self` into a [`WidgetInstance`][widgetinstance].
|
||||
|
||||
A `WidgetInstance` is a type-erased, reference-counted instance of a
|
||||
[`Widget`][widget] trait implementation. This type enables widgets to refer to
|
||||
other widgets without knowing their underlying types. For example, the label of
|
||||
a [`Button`][button] can be any widget, not just a string, because it accepts a
|
||||
`MakeWidget` implementor in its constructor.
|
||||
|
||||
Let's see the various ways that Cushy offers to create the same component: a
|
||||
labeled form field.
|
||||
|
||||
### Example: A `FormField` widget
|
||||
|
||||
Let's design a way to present a widget resembling this reusable structure:
|
||||
|
||||
```text
|
||||
Label
|
||||
Field
|
||||
```
|
||||
|
||||
The structure definition for this widget might look like this:
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../guide-examples/examples/composition-makewidget.rs:definition}}
|
||||
```
|
||||
|
||||
While it would arguably be better to accept `label` as another `WidgetInstance`,
|
||||
by focusing on composing a single widget, this example can also include a
|
||||
utility trait: [`WrapperWidget`][wrapperwidget].
|
||||
|
||||
#### Approach A: Using the `MakeWidget` trait
|
||||
|
||||
The simplest approach to making new widgets is to avoid implementing them at
|
||||
all! In this case, we can reuse the existing [`Stack`][stack] and
|
||||
[`Align`][align] widgets to position the label and field. So, instead of
|
||||
creating a `Widget` implementation, if we implement `MakeWidget`, we can compose
|
||||
our interface using existing widgets:
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../guide-examples/examples/composition-makewidget.rs:makewidget}}
|
||||
```
|
||||
|
||||
The example `FormField` when rendered looks like this:
|
||||
|
||||

|
||||
|
||||
#### Approach B: Using the `WrapperWidget` trait
|
||||
|
||||
The [`WrapperWidget`][wrapperwidget] trait is an alternate trait from `Widget`
|
||||
that makes it less error-prone to implement a widget that wraps a single other
|
||||
child widget. The only required function is
|
||||
[`WrapperWidget::child_mut`][child-mut], which returns a `&mut WidgetRef`.
|
||||
Previously, we were using [`WidgetInstance`][widgetinstance] to store our field.
|
||||
|
||||
When a `WidgetInstance` is mounted inside of another widget in a window, a
|
||||
[`MountedWidget`][mountedwidget] is returned. A [`WidgetRef`][widgetref] is a
|
||||
type that manages mounting and unmounting a widget automatically through its API
|
||||
usage. It also keeps track of each window's `MountedWidget`.
|
||||
|
||||
Updating the type to use `WidgetRef` is fairly straightforward, and does not
|
||||
impact the type's public API:
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../guide-examples/examples/composition-wrapperwidget.rs:definition}}
|
||||
```
|
||||
|
||||
Instead of calling `field.make_widget()`, we now use `WidgetRef::new(field)`.
|
||||
Now, let's look at the `WrapperWidget` implementation:
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../guide-examples/examples/composition-wrapperwidget.rs:wrapperwidget-a}}
|
||||
```
|
||||
|
||||
As mentioned before, this `child_mut` is the only required function. All other
|
||||
functions provide default behaviors that ensure that the child is mounted,
|
||||
positioned, and rendered for us. Running this example without the rest of the
|
||||
implementation would show only the field without our label.
|
||||
|
||||
Before we can start drawing and positioning the field based on the label's size,
|
||||
let's define a couple of helper functions that we can use to implement the
|
||||
`WrapperWidget` functions needed:
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../guide-examples/examples/composition-wrapperwidget.rs:helpers}}
|
||||
```
|
||||
|
||||
The first function uses our graphics context to return Kludgine's
|
||||
[`MeasuredText<Px>`][measuredtext] type. The second function looks up the
|
||||
current size to use for padding, and adds it to the height of the measured text.
|
||||
|
||||
> **Advanced Tip**: `MeasuredText` not only contains the dimensions of the text
|
||||
> we asked it to measure, it also has all the information necessary to draw all
|
||||
> the glyphs necessary in the future. It would be more efficient to cache this
|
||||
> data structure in a [`WindowLocal`][windowlocal] and only re-cache the text
|
||||
> layout when needed.
|
||||
|
||||
The [`Widget::layout()`][layout] implementation for `WrapperWidget`s splits the
|
||||
layout process into multiple steps. The goal is to allow wrapper widget authors
|
||||
to be able to customize as little or as much of the process as needed. The first
|
||||
function we are going to use is
|
||||
[`adjust_child_constraints()`][adjust-constraints]:
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../guide-examples/examples/composition-wrapperwidget.rs:wrapperwidget-b}}
|
||||
```
|
||||
|
||||
`adjust_child_constraints()` is responsible for adjusting the incoming
|
||||
`available_space` constraints by whatever space we need to surround the child.
|
||||
In our case, we need to subtract the label and padding height from the available
|
||||
height. By reducing the available space, we ensure that even if a field is
|
||||
wrapped in an [`Expand`][expand] widget, we will have already allocated space
|
||||
for the label to be visible with an appropriate amount of padding.
|
||||
|
||||
The next step in the layout process is to position the child:
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../guide-examples/examples/composition-wrapperwidget.rs:wrapperwidget-c}}
|
||||
```
|
||||
|
||||
The above implementation calculates the full size's width by taking the maximum
|
||||
of the [minimum available][constraintlimit-min] width, the child's width, and
|
||||
the label's width. It calcaultes the full size's height by adding the child's
|
||||
height and the label and padding height.
|
||||
|
||||
The result of this function is a structure that contains the child's rectangle
|
||||
and the total size this widget is requesting. The child's rectangle is placed
|
||||
below the label and padding, and its width is set to `full_size.width`. This is
|
||||
to mimic the behavior our original choice of placing the widgets in a stack. In
|
||||
our example, this is what stretches the text input field to be the full width of
|
||||
the field.
|
||||
|
||||
The final step is to draw the label:
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../guide-examples/examples/composition-wrapperwidget.rs:wrapperwidget-d}}
|
||||
```
|
||||
|
||||
Because the [`Widget::redraw`][redraw] implementation takes care of drawing the
|
||||
field for us, all this function needed to do was draw the label.
|
||||
|
||||
This is the result of using our new implementation:
|
||||
|
||||

|
||||
|
||||
It looks the exact same as the previous image. That was our goal! Rest assured
|
||||
that this image [was generated][book-example-wrapper] using the `WrapperWidget`
|
||||
implementation shown.
|
||||
|
||||
#### Approach C: Using the `Widget` trait
|
||||
|
||||
Implementing [`Widget`][widget] is very similar to implementing
|
||||
[`WrapperWidget`][wrapperwidget], except that we are in full control of layout
|
||||
and rendering. Since our `WrapperWidget` implementation needed to fully adjust
|
||||
the layout, our `layout()` function is basically just the combination of the two
|
||||
layout functions from before:
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../guide-examples/examples/composition-widget.rs:widget-a}}
|
||||
```
|
||||
|
||||
The major difference in this function is that we are manually calling `layout()`
|
||||
on our field widget. This is done by creating a context for our field
|
||||
(`context.for_other()`), and calling the layout function on that context. After
|
||||
we receive the field's size, we must call
|
||||
[`set_child_layout()`][set-child-layout] to finish laying out the field.
|
||||
|
||||
Finally, the result of the `layout()` function is the full size that the field
|
||||
needs. With layout done, rendering is the next step:
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../guide-examples/examples/composition-widget.rs:widget-b}}
|
||||
```
|
||||
|
||||
This function isn't much more complicated than the `WrapperWidget`'s
|
||||
implementation. The extra two lines again use `context.for_other()` to create a
|
||||
context for the `field` widget, but this time we call the field's `redraw()`
|
||||
function instead.
|
||||
|
||||
Finally, we have one last bit of housekeeping that `WrapperWidget` did
|
||||
automatically: unmounting the field when the form field itself is unmounted.
|
||||
This is important to minimize memory usage by ensuring that if the widget is
|
||||
shared between multiple windows, each window's state is cleaned up indepdent of
|
||||
the widget itself:
|
||||
|
||||
```rust,no_run,no_playground
|
||||
{{#include ../../guide-examples/examples/composition-widget.rs:widget-c}}
|
||||
```
|
||||
|
||||
And with this, we can look at an identical picture that shows [this
|
||||
implementation][book-example-widget] works the same as the previous
|
||||
implementations:
|
||||
|
||||

|
||||
|
||||
## Conclusion
|
||||
|
||||
Cushy tries to provide the tools needed to avoid implementing your own widgets
|
||||
by utilizing composition. It also has many tools for creating reusable,
|
||||
composible widgets. If you find yourself uncertain of what path to use when
|
||||
creating a reusable component, try using a [`MakeWidget`][makewidget]
|
||||
implementation initially.
|
||||
|
||||
If using `MakeWidget` is cumbersome or doesn't expose as much functionality as
|
||||
needed, you may still find that you can utilize `MakeWidget` along with a
|
||||
simpler custom widget than implementing a more complex, multi-part widget. This
|
||||
approach is how the built-in [`Checkbox`][checkbox], [`Radio`][radio] and
|
||||
[`Select`][select] widgets are all built using a [`Button`][button].
|
||||
|
||||
[window]: <{{ docs }}/window/struct.Window.html>
|
||||
[widget]: <{{ docs }}/widget/trait.Widget.html>
|
||||
[layout]: <{{ docs }}/widget/trait.Widget.html#method.layout>
|
||||
[redraw]: <{{ docs }}/widget/trait.Widget.html#method.redraw>
|
||||
[mountedwidget]: <{{ docs }}/widget/struct.MountedWidget.html>
|
||||
[widgetref]: <{{ docs }}/widget/struct.WidgetRef.html>
|
||||
[wrapperwidget]: <{{ docs }}/widget/trait.WrapperWidget.html>
|
||||
[child-mut]: <{{ docs }}/widget/trait.WrapperWidget.html#tymethod.child_mut>
|
||||
[adjust-constraints]: <{{ docs }}/widget/trait.WrapperWidget.html#method.adjust_child_constraints>
|
||||
[makewidget]: <{{ docs }}/widget/trait.MakeWidget.html>
|
||||
[makewidgettag]: <{{ docs }}/widget/trait.MakeWidgetWithTag.html>
|
||||
[widget-layout]: <{{ docs }}/widget/trait.Widget.html#method.layout>
|
||||
[widgetinstance]: <{{ docs }}/widget/struct.WidgetInstance.html>
|
||||
[button]: ../widgets/controls/button.md
|
||||
[radio]: ../widgets/controls/radio.md
|
||||
[select]: ../widgets/controls/select.md
|
||||
[checkbox]: ../widgets/controls/checkbox.md
|
||||
[stack]: ../widgets/layout/stack.md
|
||||
[align]: ../widgets/layout/align.md
|
||||
[expand]: ../widgets/layout/expand.md
|
||||
[book-example-wrapper]: <{{ src }}/guide/guide-examples/examples/composition-wrapperwidget.rs>
|
||||
[book-example-widget]: <{{ src }}/guide/guide-examples/examples/composition-wrapperwidget.rs>
|
||||
[measuredtext]: <https://docs.rs/kludgine/latest/kludgine/text/struct.MeasuredText.html>
|
||||
[windowlocal]: <{{ docs }}/window/struct.WindowLocal.html>
|
||||
[constraintlimit-min]: <{{ docs }}/enum.ConstraintLimit.html#method.min>
|
||||
[set-child-layout]: <{{ docs }}/context/struct.LayoutContext.html#method.set_child_layout>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# Cushy's Philosophies
|
||||
|
||||
There are a lot of GUI libraries with wildly varying approaches to how UIs are
|
||||
displayed. Here's the philosophies that drive Cushy's design:
|
||||
displayed. Here are the philosophies that drive Cushy's design:
|
||||
|
||||
- Cushy retains information between redraws so that many events can be handled
|
||||
without redrawing the user interface.
|
||||
|
|
@ -25,9 +25,12 @@ displayed. Here's the philosophies that drive Cushy's design:
|
|||
|
||||
From an implementation standpoint, Cushy has these goals:
|
||||
|
||||
- For graphics, provide a wgpu-centric library that exposes a rendering API
|
||||
inspired by wgpu's Encapsulating Graphics Work article.
|
||||
- For windowing, embrace winit and route input events to the correct widgets.
|
||||
This allows widgets to support any features that winit can support.
|
||||
- For graphics, provide a [wgpu][wgpu]-centric library that exposes a rendering
|
||||
API inspired by wgpu's Encapsulating Graphics Work article.
|
||||
- For windowing, embrace [winit][winit] and route input events to the correct
|
||||
widgets. This allows widgets to support any features that winit can support.
|
||||
- Cushy should be able to idle at close to 0% CPU. Cushy should not redraw
|
||||
unless needed.
|
||||
|
||||
[wgpu]: <https://github.com/gfx-rs/wgpu>
|
||||
[winit]: <https://github.com/rust-windowing/winit>
|
||||
|
|
|
|||
BIN
guide/src/examples/composition_makewidget.png
Normal file
BIN
guide/src/examples/composition_makewidget.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.9 KiB |
BIN
guide/src/examples/composition_widget.png
Normal file
BIN
guide/src/examples/composition_widget.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.9 KiB |
BIN
guide/src/examples/composition_wrapperwidget.png
Normal file
BIN
guide/src/examples/composition_wrapperwidget.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.9 KiB |
|
|
@ -1,4 +1,4 @@
|
|||
# Introduction
|
||||
# Welcome to the Cushy User's Guide
|
||||
|
||||
This is a user's guide for [Cushy][cushy], a [Rust][rust] GUI crate. The
|
||||
[documentation][docs] is a great resource for finding information about specific
|
||||
|
|
|
|||
21
guide/src/widgets.md
Normal file
21
guide/src/widgets.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Widgets
|
||||
|
||||
Types that implement [`Widget`][widget] are the [building
|
||||
blocks](./about/widgets.md) of Cushy user interfaces. The built-in widgets each
|
||||
aim to serve a single purpose or solve a single problem. [Through
|
||||
composition](./about/composition.md), complex user interfaces can be built by
|
||||
combining these single-purpose widgets.
|
||||
|
||||
This section is organized into four categories of widgets:
|
||||
|
||||
- [Multi-widget Layout Widgets](./multi-layout.md): Widgets that are designed to
|
||||
layout multiple widgets as a single widget.
|
||||
- [Single-widget Layout Widgets](./single-layout.md): Widgets that are designed
|
||||
to influence a single widget's layout.
|
||||
- [Controls](./controls.md): Widgets that are visible to the user to present
|
||||
and/or interact with data.
|
||||
- [Utility Widgets](./utility.md): Widgets that have no direct layout or visual
|
||||
presentation. This type of widget usually associates extra data that may
|
||||
impact how child widgets are presented.
|
||||
|
||||
[widget]: <{{ docs }}/widget/trait.Widget.html>
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
# Layout
|
||||
|
||||
Widgets in this section have a primary purpose of performing some layout
|
||||
functionality on one or more child widgets.
|
||||
|
|
@ -91,4 +91,4 @@ Any widget can be aligned to the bottom using
|
|||
[flexauto]: <{{ docs }}/styles/enum.FlexibleDimension.html#variant.Auto>
|
||||
[fit-vert]: <https://cushy.rs/main/docs/cushy/widget/trait.MakeWidget.html#method.fit_vertically>
|
||||
[fit-horiz]: <https://cushy.rs/main/docs/cushy/widget/trait.MakeWidget.html#method.fit_horizontally>
|
||||
[space]: ../utility/space.md
|
||||
[space]: ../controls/space.md
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# Layers
|
||||
|
||||
The [`Layers`][layers] widget lays its [`Children`][children] widgets on top of
|
||||
The [`Layers`][layers] widget lays its [`WidgetList`][children] widgets on top of
|
||||
each other in the Z orientation.
|
||||
|
||||
When computing its size, it uses the largest width and height from all of its
|
||||
children.
|
||||
|
||||
[Layers]: <{{ docs }}/widgets/layers/struct.Layers.html>
|
||||
[children]: <{{ docs }}/widget/struct.Children.html>
|
||||
[children]: <{{ docs }}/widget/struct.WidgetList.html>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
# Stack
|
||||
|
||||
The [`Stack`][stack] widget lays a set of [`Children`][children] as either a set
|
||||
The [`Stack`][stack] widget lays a set of [`WidgetList`][children] as either a set
|
||||
of columns or rows. It is a convenient way to construct a 1D
|
||||
[`Grid`](./grid.md). It can be constructed using either:
|
||||
|
||||
- [`Stack::rows`][rows]/[`Children::into_rows()`][into_rows]
|
||||
- [`Stack::columns`][columns]/[`Children::into_columns()`][into_columns]
|
||||
- [`Stack::rows`][rows]/[`WidgetList::into_rows()`][into_rows]
|
||||
- [`Stack::columns`][columns]/[`WidgetList::into_columns()`][into_columns]
|
||||
|
||||
The stack widget places spacing between each element called a [gutter][gutter].
|
||||
|
||||
[stack]: <{{ docs }}/widgets/stack/struct.Stack.html>
|
||||
[children]: <{{ docs }}/widget/struct.Children.html>
|
||||
[children]: <{{ docs }}/widget/struct.WidgetList.html>
|
||||
[rows]: <{{ docs }}/widgets/stack/struct.Stack.html#method.rows>
|
||||
[columns]: <{{ docs }}/widgets/stack/struct.Stack.html#method.columns>
|
||||
[into_columns]: <{{ docs }}/widget/struct.Children.html#method.into_columns>
|
||||
[into_rows]: <{{ docs }}/widget/struct.Children.html#method.into_rows>
|
||||
[into_columns]: <{{ docs }}/widget/struct.WidgetList.html#method.into_columns>
|
||||
[into_rows]: <{{ docs }}/widget/struct.WidgetList.html#method.into_rows>
|
||||
[gutter]: <{{ docs }}/widgets/stack/struct.Stack.html#method.gutter>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Wrap
|
||||
|
||||
The [`Wrap`][wrap] widget lays its [`Children`][children] widgets out in a
|
||||
The [`Wrap`][wrap] widget lays its [`WidgetList`][children] widgets out in a
|
||||
fashion that mimics text layout.
|
||||
|
||||
It works by measuring each child with [`SizeToFit`][size-to-fit] and laying out
|
||||
|
|
@ -26,7 +26,7 @@ Once the widgets have been grouped into rows, the [alignment][align] and
|
|||
|
||||
[wrap]: <{{ docs }}/widgets/wrap/struct.Wrap.html>
|
||||
[wrapalign]: <{{ docs }}/widgets/wrap/enum.WrapAlign.html>
|
||||
[children]: <{{ docs }}/widget/struct.Children.html>
|
||||
[children]: <{{ docs }}/widget/struct.WidgetList.html>
|
||||
[size-to-fit]: <{{ docs }}/enum.ConstraintLimit.html#variant.SizeToFit>
|
||||
[spacing]: <{{ docs }}/widgets/wrap/struct.Wrap.html#method.spacing>
|
||||
[align]: <{{ docs }}/widgets/wrap/struct.Wrap.html#method.align>
|
||||
|
|
|
|||
10
guide/src/widgets/multi-layout.md
Normal file
10
guide/src/widgets/multi-layout.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Multi-Widget Layout
|
||||
|
||||
Widgets in this section have a primary purpose of performing some layout
|
||||
functionality on a collection of widgets. These currently are:
|
||||
|
||||
- [Grid](./layout/grid.md): A 2D grid where each cell is a widget.
|
||||
- [Layers](./layout/layers.md): A Z-direction stack of widgets.
|
||||
- [Stack](./layout/stack.md): A 1D grid of either rows or columns.
|
||||
- [Wrap](./layout/wrap.md): Widgets are laid out horizontally, wrapping into
|
||||
multiple rows if needed.
|
||||
17
guide/src/widgets/single-layout.md
Normal file
17
guide/src/widgets/single-layout.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Single-widget Layout
|
||||
|
||||
Widgets in this section have a primary purpose of performing some layout
|
||||
functionality on a single child widget. To use one of these layout widgets with
|
||||
multiple widgets, first lay out the widgets using one of the [multi-widget
|
||||
layout widgets](./multi-layout.md).
|
||||
|
||||
The widgets in this category are:
|
||||
|
||||
- [Align](./layout/align.md): Aligns its child relative to its parent's edges.
|
||||
- [Collapse](./layout/collapse.md): Shows/hides its child.
|
||||
- [Container](./layout/container.md): Visually contains a child. Useful for
|
||||
grouping related content visually.
|
||||
- [Expand](./layout/expand.md): Resizes its child to occupy as much space as
|
||||
available.
|
||||
- [Resize](./layout/resize.md): Restricts its child's size to be within a range
|
||||
of widths and/or heights.
|
||||
|
|
@ -15,6 +15,7 @@ pub struct PendingApp {
|
|||
|
||||
impl PendingApp {
|
||||
/// The shared resources this application utilizes.
|
||||
#[must_use]
|
||||
pub const fn cushy(&self) -> &Cushy {
|
||||
&self.cushy
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use std::fmt::Debug;
|
|||
use alot::OrderedLots;
|
||||
|
||||
use crate::value::{Dynamic, DynamicReader, ForEach, Source, WeakDynamic};
|
||||
use crate::widget::{Children, MakeWidget, WidgetInstance};
|
||||
use crate::widget::{MakeWidget, WidgetInstance, WidgetList};
|
||||
use crate::widgets::grid::{Grid, GridWidgets};
|
||||
use crate::window::Window;
|
||||
use crate::{Open, PendingApp};
|
||||
|
|
@ -203,7 +203,7 @@ impl DebugSection {
|
|||
children
|
||||
.iter()
|
||||
.map(|section| section.map_ref(|section| section.widget.clone()))
|
||||
.collect::<Children>()
|
||||
.collect::<WidgetList>()
|
||||
});
|
||||
|
||||
let parent = parent.map(Dynamic::downgrade);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ use crate::animation::{AnimationHandle, DynamicTransition, IntoAnimate, LinearIn
|
|||
use crate::context::{self, Trackable, WidgetContext};
|
||||
use crate::utils::{run_in_bg, IgnorePoison, WithClone};
|
||||
use crate::widget::{
|
||||
Children, MakeWidget, MakeWidgetWithTag, OnceCallback, WidgetId, WidgetInstance,
|
||||
MakeWidget, MakeWidgetWithTag, OnceCallback, WidgetId, WidgetInstance, WidgetList,
|
||||
};
|
||||
use crate::widgets::{Label, Radio, Select, Space, Switcher};
|
||||
use crate::window::WindowHandle;
|
||||
|
|
@ -2442,7 +2442,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl GetWidget<usize> for Children {
|
||||
impl GetWidget<usize> for WidgetList {
|
||||
fn get<'a>(&'a self, key: &usize) -> Option<&'a WidgetInstance> {
|
||||
(**self).get(*key)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1117,8 +1117,8 @@ pub trait MakeWidget: Sized {
|
|||
}
|
||||
|
||||
/// Returns a collection of widgets using `self` and `other`.
|
||||
fn and(self, other: impl MakeWidget) -> Children {
|
||||
let mut children = Children::new();
|
||||
fn and(self, other: impl MakeWidget) -> WidgetList {
|
||||
let mut children = WidgetList::new();
|
||||
children.push(self);
|
||||
children.push(other);
|
||||
children
|
||||
|
|
@ -1922,14 +1922,25 @@ impl WidgetGuard<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A list of [`Widget`]s.
|
||||
/// A list of [`Widget`]s without a layout strategy.
|
||||
///
|
||||
/// To use a `WidgetList` in a user interface, a choice must be made for how
|
||||
/// each child should be positioned. The built-in widgets that can layout a
|
||||
/// `WidgetList` are:
|
||||
///
|
||||
/// - As rows: [`Stack::rows`] / [`Self::into_rows`]
|
||||
/// - As columns: [`Stack::columns`] / [`Self::into_columns`]
|
||||
/// - Positioned on top of each other in the Z orientation: [`Layers::new`] /
|
||||
/// [`Self::into_layers`]
|
||||
/// - Layout horizontally, wrapping into multiple rows as needed: [`Wrap::new`]
|
||||
/// / [`Self::into_wrap`].
|
||||
#[derive(Default, Eq, PartialEq)]
|
||||
#[must_use]
|
||||
pub struct Children {
|
||||
pub struct WidgetList {
|
||||
ordered: Vec<WidgetInstance>,
|
||||
}
|
||||
|
||||
impl Children {
|
||||
impl WidgetList {
|
||||
/// Returns an empty list.
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
|
|
@ -2021,7 +2032,7 @@ impl Children {
|
|||
/// Returns a [`Wrap`] that lays the children out horizontally, wrapping
|
||||
/// into additional rows as needed.
|
||||
#[must_use]
|
||||
pub fn wrap(self) -> Wrap {
|
||||
pub fn into_wrap(self) -> Wrap {
|
||||
Wrap::new(self)
|
||||
}
|
||||
|
||||
|
|
@ -2066,13 +2077,13 @@ impl Children {
|
|||
}
|
||||
}
|
||||
|
||||
impl Debug for Children {
|
||||
impl Debug for WidgetList {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Debug::fmt(&self.ordered, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Dynamic<Children> {
|
||||
impl Dynamic<WidgetList> {
|
||||
/// Returns `self` as a vertical [`Stack`] of rows.
|
||||
#[must_use]
|
||||
pub fn into_rows(self) -> Stack {
|
||||
|
|
@ -2126,7 +2137,7 @@ impl Dynamic<Children> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<W> FromIterator<W> for Children
|
||||
impl<W> FromIterator<W> for WidgetList
|
||||
where
|
||||
W: MakeWidget,
|
||||
{
|
||||
|
|
@ -2137,7 +2148,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl Deref for Children {
|
||||
impl Deref for WidgetList {
|
||||
type Target = [WidgetInstance];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
|
@ -2145,13 +2156,13 @@ impl Deref for Children {
|
|||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Children {
|
||||
impl DerefMut for WidgetList {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.ordered
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Children {
|
||||
impl<'a> IntoIterator for &'a WidgetList {
|
||||
type IntoIter = slice::Iter<'a, WidgetInstance>;
|
||||
type Item = &'a WidgetInstance;
|
||||
|
||||
|
|
@ -2160,7 +2171,7 @@ impl<'a> IntoIterator for &'a Children {
|
|||
}
|
||||
}
|
||||
|
||||
/// A change to perform during [`Children::synchronize_with`].
|
||||
/// A change to perform during [`WidgetList::synchronize_with`].
|
||||
pub enum ChildrenSyncChange {
|
||||
/// Insert a new widget at the given index.
|
||||
Insert(usize, WidgetInstance),
|
||||
|
|
@ -2174,7 +2185,7 @@ pub enum ChildrenSyncChange {
|
|||
///
|
||||
/// This collection is a helper aimed at making it easier to build widgets that
|
||||
/// contain multiple children widgets. It is used in conjunction with a
|
||||
/// `Value<Children>`.
|
||||
/// `Value<WidgetList>`.
|
||||
#[derive(Debug)]
|
||||
pub struct MountedChildren<T = MountedWidget> {
|
||||
generation: Option<Generation>,
|
||||
|
|
@ -2186,10 +2197,14 @@ where
|
|||
T: MountableChild,
|
||||
{
|
||||
/// Mounts and unmounts all children needed to be in sync with `children`.
|
||||
pub fn synchronize_with(&mut self, children: &Value<Children>, context: &mut EventContext<'_>) {
|
||||
pub fn synchronize_with(
|
||||
&mut self,
|
||||
children: &Value<WidgetList>,
|
||||
context: &mut EventContext<'_>,
|
||||
) {
|
||||
let current_generation = children.generation();
|
||||
if current_generation.map_or_else(
|
||||
|| children.map(Children::len) != self.children.len(),
|
||||
|| children.map(WidgetList::len) != self.children.len(),
|
||||
|gen| Some(gen) != self.generation,
|
||||
) {
|
||||
self.generation = current_generation;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContex
|
|||
use crate::utils::IgnorePoison;
|
||||
use crate::value::{Destination, Dynamic, DynamicGuard, IntoValue, Source, Value};
|
||||
use crate::widget::{
|
||||
Callback, Children, MakeWidget, MountedChildren, MountedWidget, Widget, WidgetId, WidgetRef,
|
||||
Callback, MakeWidget, MountedChildren, MountedWidget, Widget, WidgetId, WidgetList, WidgetRef,
|
||||
WrapperWidget,
|
||||
};
|
||||
use crate::widgets::container::ContainerShadow;
|
||||
|
|
@ -26,13 +26,13 @@ use crate::ConstraintLimit;
|
|||
#[derive(Debug)]
|
||||
pub struct Layers {
|
||||
/// The children that are laid out as layers with index 0 being the lowest (bottom).
|
||||
pub children: Value<Children>,
|
||||
pub children: Value<WidgetList>,
|
||||
mounted: MountedChildren,
|
||||
}
|
||||
|
||||
impl Layers {
|
||||
/// Returns a new instance that lays out `children` as layers.
|
||||
pub fn new(children: impl IntoValue<Children>) -> Self {
|
||||
pub fn new(children: impl IntoValue<WidgetList>) -> Self {
|
||||
Self {
|
||||
children: children.into_value(),
|
||||
mounted: MountedChildren::default(),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//! A widget that combines a collection of [`Children`] widgets into one.
|
||||
//! A widget that combines a collection of [`WidgetList`] widgets into one.
|
||||
|
||||
use figures::units::UPx;
|
||||
use figures::{IntoSigned, Rect, Round, ScreenScale, Size};
|
||||
|
|
@ -7,18 +7,18 @@ use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContex
|
|||
use crate::styles::components::IntrinsicPadding;
|
||||
use crate::styles::FlexibleDimension;
|
||||
use crate::value::{Generation, IntoValue, Value};
|
||||
use crate::widget::{Children, ChildrenSyncChange, MountedWidget, Widget, WidgetRef};
|
||||
use crate::widget::{ChildrenSyncChange, MountedWidget, Widget, WidgetList, WidgetRef};
|
||||
use crate::widgets::grid::{GridDimension, GridLayout, Orientation};
|
||||
use crate::widgets::{Expand, Resize};
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
/// A widget that displays a collection of [`Children`] widgets in a
|
||||
/// A widget that displays a collection of [`WidgetList`] widgets in a
|
||||
/// [orientation](Orientation).
|
||||
#[derive(Debug)]
|
||||
pub struct Stack {
|
||||
orientation: Orientation,
|
||||
/// The children widgets that belong to this array.
|
||||
pub children: Value<Children>,
|
||||
pub children: Value<WidgetList>,
|
||||
/// The amount of space to place between each widget.
|
||||
pub gutter: Value<FlexibleDimension>,
|
||||
layout: GridLayout,
|
||||
|
|
@ -28,7 +28,7 @@ pub struct Stack {
|
|||
|
||||
impl Stack {
|
||||
/// Returns a new widget with the given orientation and widgets.
|
||||
pub fn new(orientation: Orientation, widgets: impl IntoValue<Children>) -> Self {
|
||||
pub fn new(orientation: Orientation, widgets: impl IntoValue<WidgetList>) -> Self {
|
||||
Self {
|
||||
orientation,
|
||||
children: widgets.into_value(),
|
||||
|
|
@ -40,12 +40,12 @@ impl Stack {
|
|||
}
|
||||
|
||||
/// Returns a new instance that displays `widgets` in a series of columns.
|
||||
pub fn columns(widgets: impl IntoValue<Children>) -> Self {
|
||||
pub fn columns(widgets: impl IntoValue<WidgetList>) -> Self {
|
||||
Self::new(Orientation::Column, widgets)
|
||||
}
|
||||
|
||||
/// Returns a new instance that displays `widgets` in a series of rows.
|
||||
pub fn rows(widgets: impl IntoValue<Children>) -> Self {
|
||||
pub fn rows(widgets: impl IntoValue<WidgetList>) -> Self {
|
||||
Self::new(Orientation::Row, widgets)
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ impl Stack {
|
|||
let current_generation = self.children.generation();
|
||||
self.children.invalidate_when_changed(context);
|
||||
if current_generation.map_or_else(
|
||||
|| self.children.map(Children::len) != self.layout.len(),
|
||||
|| self.children.map(WidgetList::len) != self.layout.len(),
|
||||
|gen| Some(gen) != self.layout_generation,
|
||||
) {
|
||||
self.layout_generation = self.children.generation();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::context::{AsEventContext, GraphicsContext, LayoutContext, Trackable};
|
|||
use crate::styles::components::{IntrinsicPadding, LayoutOrder};
|
||||
use crate::styles::{FlexibleDimension, HorizontalOrder};
|
||||
use crate::value::{IntoValue, Value};
|
||||
use crate::widget::{Children, MountedChildren, Widget};
|
||||
use crate::widget::{MountedChildren, Widget, WidgetList};
|
||||
use crate::ConstraintLimit;
|
||||
|
||||
/// A widget that lays its children out horizontally, wrapping into multiple
|
||||
|
|
@ -20,7 +20,7 @@ use crate::ConstraintLimit;
|
|||
#[derive(Debug)]
|
||||
pub struct Wrap {
|
||||
/// The children to wrap.
|
||||
pub children: Value<Children>,
|
||||
pub children: Value<WidgetList>,
|
||||
/// The horizontal alignment for widgets on the same row.
|
||||
pub align: Value<WrapAlign>,
|
||||
/// The vertical alignment for widgets on the same row.
|
||||
|
|
@ -34,7 +34,7 @@ pub struct Wrap {
|
|||
impl Wrap {
|
||||
/// Returns a new widget that wraps `children`.
|
||||
#[must_use]
|
||||
pub fn new(children: impl IntoValue<Children>) -> Self {
|
||||
pub fn new(children: impl IntoValue<WidgetList>) -> Self {
|
||||
Self {
|
||||
children: children.into_value(),
|
||||
align: Value::default(),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
//! Types for displaying a [`Widget`] inside of a desktop window.
|
||||
//! Types for displaying a [`Widget`](crate::widget::Widget) inside of a desktop
|
||||
//! window.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::hash_map;
|
||||
|
|
@ -2499,7 +2500,8 @@ impl CushyWindowBuilder {
|
|||
#[must_use]
|
||||
pub fn finish_virtual(self, device: &wgpu::Device, queue: &wgpu::Queue) -> VirtualWindow {
|
||||
let mut state = VirtualState::new();
|
||||
let cushy = self.finish(&mut state, device, queue);
|
||||
let mut cushy = self.finish(&mut state, device, queue);
|
||||
cushy.set_focused(true);
|
||||
|
||||
VirtualWindow {
|
||||
cushy,
|
||||
|
|
|
|||
Loading…
Reference in a new issue