diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5e7c422..2d340b3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,7 +5,6 @@ on: [push] jobs: docs: runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v3 @@ -32,4 +31,34 @@ jobs: api-key: ${{ secrets.DOSSIER_API_KEY }} project: cushy from: target/doc/ - to: /${{ github.ref_name }}/docs \ No newline at end of file + to: /${{ github.ref_name }}/docs + + guide: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: dtolnay/rust-toolchain@stable + + - name: Download mdbook + run: | + curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.36/mdbook-v0.4.36-x86_64-unknown-linux-gnu.tar.gz | tar -xz + + - name: Install mdbook-variables + run: | + cargo install mdbook-variables + + - name: Build Guide + run: | + ./mdbook build guide + + - name: Deploy + uses: khonsulabs/sync-to-dossier@main + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/release' || startsWith(github.ref, 'refs/tags/') + with: + url: ${{ secrets.DOSSIER_URL }} + api-key-id: ${{ secrets.DOSSIER_API_KEY_ID }} + api-key: ${{ secrets.DOSSIER_API_KEY }} + project: cushy + from: target/guide/ + to: /${{ github.ref_name }}/guide \ No newline at end of file diff --git a/guide/book.toml b/guide/book.toml index 0690ff1..5c28854 100644 --- a/guide/book.toml +++ b/guide/book.toml @@ -4,3 +4,10 @@ language = "en" multilingual = false src = "src" title = "Cushy User's Guide" + +[build] +build-dir = "../target/guide" +extra-watch-dirs = ["./guide-examples/"] + +[preprocessor.variables.variables] +docs = "https://cushy.rs/main/docs/cushy" diff --git a/guide/guide-examples/examples/align.rs b/guide/guide-examples/examples/align.rs index 2797060..09a42a3 100644 --- a/guide/guide-examples/examples/align.rs +++ b/guide/guide-examples/examples/align.rs @@ -1,133 +1,167 @@ +use std::array; + use cushy::figures::units::{Lp, Px}; use cushy::figures::{Point, Size}; -use cushy::styles::{Edges, ThemePair}; +use cushy::styles::ThemePair; use cushy::widget::MakeWidget; -use cushy::widgets::Space; -use guide_examples::BookExample; +use cushy::widgets::grid::{GridDimension, GridWidgets}; +use cushy::widgets::{Grid, Space}; +use guide_examples::book_example; +// ANCHOR: content fn content() -> impl MakeWidget { - Space::primary().size(Size::squared(Px::new(32))) + Space::primary().size(Size::squared(Px::new(32)..)) +} +// ANCHOR_END: content + +fn align_left() -> impl MakeWidget { + // ANCHOR: align-left + content().align_left() + // ANCHOR_END: align-left +} + +fn centered() -> impl MakeWidget { + // ANCHOR: horizontal-center + content().centered() + // ANCHOR_END: horizontal-center +} + +fn align_right() -> impl MakeWidget { + // ANCHOR: align-right + content().align_right() + // ANCHOR_END: align-right +} + +fn align_horizontal() -> impl MakeWidget { + Grid::from_rows( + GridWidgets::new() + .and(("Unaligned", content())) + .and(("align_left()", align_left())) + .and(("centered()", centered())) + .and(("align_right()", align_right())), + ) + .dimensions([ + GridDimension::FitContent, + GridDimension::Fractional { weight: 1 }, + ]) +} + +fn align_top() -> impl MakeWidget { + // ANCHOR: align-top + content().align_top() + // ANCHOR_END: align-top +} + +fn align_bottom() -> impl MakeWidget { + // ANCHOR: align-bottom + content().align_bottom() + // ANCHOR_END: align-bottom +} + +fn align_vertical() -> impl MakeWidget { + Grid::from_rows( + GridWidgets::new() + .and(("Unaligned", "align_top()", "centered()", "align_bottom()")) + .and(( + content().height(Lp::inches(1)).centered(), + align_top(), + centered(), + align_bottom(), + )), + ) + .dimensions(array::from_fn(|_| GridDimension::Fractional { weight: 1 })) +} + +fn align() -> impl MakeWidget { + "Horizontal Alignment" + .and(align_horizontal().contain()) + .and("Vertical Alignment") + .and(align_vertical().contain()) + .into_rows() } fn main() { - BookExample::new( - "align-horizontal", - "Default Behavior" - .and(content()) - .and("align_left()") - .and({ - // ANCHOR: align-left - content().align_left() - // ANCHOR_END: align-left - }) - .and("pad_by().align_left()") - .and({ - // ANCHOR: align-left-pad - content() - .pad_by(Edges::default().with_left(Lp::inches(1))) - .align_left() - // ANCHOR_END: align-left-pad - }) - .and("centered()") - .and({ - // ANCHOR: centered - content().centered() - // ANCHOR_END: centered - }) - .and("pad_by().align_right()") - .and({ - // ANCHOR: align-right-pad - content() - .pad_by(Edges::default().with_right(Lp::inches(1))) - .align_right() - // ANCHOR_END: align-right-pad - }) - .and("align_right()") - .and({ - // ANCHOR: align-right - content().align_right() - // ANCHOR_END: align-right - }) - .into_rows(), - ) - .still_frame(|recorder| { - const LEFT: u32 = 40; - const PADDING: u32 = 96; - const RIGHT: u32 = 710; - const CENTER: u32 = 375; + let theme = ThemePair::default(); + let container_color = theme.dark.surface.low_container; + let primary = theme.dark.primary.color; + book_example!(align).still_frame(|recorder| { + const LEFT: u32 = 145; + const RIGHT: u32 = 705; + const H_CENTER: u32 = (RIGHT + LEFT) / 2; + const TOP: u32 = 282; + const BOTTOM: u32 = 345; + const V_CENTER: u32 = (TOP + BOTTOM) / 2; - let container_color = ThemePair::default().dark.surface.lowest_container; - let primary = ThemePair::default().dark.primary.color; - - recorder.assert_pixel_color(Point::new(LEFT, 35), container_color, "surface"); + // Verify the inner container color + recorder.assert_pixel_color(Point::new(32, 62), container_color, "surface"); // Default fills the entire space - recorder.assert_pixel_color(Point::new(LEFT, 70), primary, "default spacer"); - recorder.assert_pixel_color(Point::new(CENTER, 70), primary, "default spacer"); - recorder.assert_pixel_color(Point::new(RIGHT, 70), primary, "default spacer"); + recorder.assert_pixel_color(Point::new(LEFT, 78), primary, "default spacer"); + recorder.assert_pixel_color(Point::new(H_CENTER, 78), primary, "default spacer"); + recorder.assert_pixel_color(Point::new(RIGHT, 78), primary, "default spacer"); // align-left - recorder.assert_pixel_color(Point::new(LEFT, 140), primary, "align-left spacer"); + recorder.assert_pixel_color(Point::new(LEFT, 110), primary, "align-left spacer"); recorder.assert_pixel_color( - Point::new(LEFT + PADDING, 140), + Point::new(H_CENTER, 110), container_color, "align-left empty", ); - // align-left-pad - recorder.assert_pixel_color( - Point::new(LEFT + PADDING, 215), - primary, - "align-left-pad spacer", - ); - recorder.assert_pixel_color( - Point::new(LEFT, 215), - container_color, - "align-left-pad empty before", - ); - recorder.assert_pixel_color( - Point::new(CENTER, 215), - container_color, - "align-left-pad empty after", - ); - // centered - recorder.assert_pixel_color(Point::new(CENTER, 295), primary, "centered spacer"); + recorder.assert_pixel_color(Point::new(H_CENTER, 142), primary, "centered spacer"); recorder.assert_pixel_color( - Point::new(LEFT + PADDING, 295), + Point::new(LEFT, 142), container_color, "centered empty before", ); recorder.assert_pixel_color( - Point::new(RIGHT - PADDING, 295), + Point::new(RIGHT, 142), container_color, "centered empty after", ); - // align-right-pad - recorder.assert_pixel_color( - Point::new(RIGHT - PADDING, 360), - primary, - "align-right-pad spacer", - ); - recorder.assert_pixel_color( - Point::new(CENTER, 360), - container_color, - "align-right-pad empty before", - ); - recorder.assert_pixel_color( - Point::new(RIGHT, 360), - container_color, - "align-right-pad empty after", - ); - // align-right - recorder.assert_pixel_color(Point::new(RIGHT, 435), primary, "align-right spacer"); + recorder.assert_pixel_color(Point::new(RIGHT, 175), primary, "align-right spacer"); recorder.assert_pixel_color( - Point::new(RIGHT - PADDING, 435), + Point::new(V_CENTER, 175), container_color, "align-right empty", ); + + // Default fills the entire space + recorder.assert_pixel_color(Point::new(115, TOP), primary, "default spacer"); + recorder.assert_pixel_color(Point::new(115, V_CENTER), primary, "default spacer"); + recorder.assert_pixel_color(Point::new(115, BOTTOM), primary, "default spacer"); + + // align-top + recorder.assert_pixel_color(Point::new(285, TOP), primary, "align-top spacer"); + recorder.assert_pixel_color( + Point::new(285, V_CENTER), + container_color, + "align-top empty", + ); + + // centered + recorder.assert_pixel_color(Point::new(460, V_CENTER), primary, "centered spacer"); + recorder.assert_pixel_color( + Point::new(460, TOP), + container_color, + "centered empty before", + ); + recorder.assert_pixel_color( + Point::new(460, BOTTOM), + container_color, + "centered empty after", + ); + + // align-bottom + recorder.assert_pixel_color(Point::new(635, BOTTOM), primary, "align-bottom spacer"); + recorder.assert_pixel_color( + Point::new(635, V_CENTER), + container_color, + "align-bottom empty", + ); }); } diff --git a/guide/guide-examples/src/lib.rs b/guide/guide-examples/src/lib.rs index 99930ff..40f2bcb 100644 --- a/guide/guide-examples/src/lib.rs +++ b/guide/guide-examples/src/lib.rs @@ -67,3 +67,10 @@ impl BookExample { // { // } } + +#[macro_export] +macro_rules! book_example { + ($name:ident) => { + guide_examples::BookExample::new(stringify!($name), $name()) + }; +} diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 7390c82..2660906 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -1,3 +1,42 @@ # Summary -- [Chapter 1](./chapter_1.md) + + +[Introduction](./intro.md) + +- [About Cushy](./about.md) + - [Cushy's Philosophies](./about/philosophies.md) +- [Widgets]() + - [Layout Widgets](./widgets/layout.md) + - [Align](./widgets/layout/align.md) + - [Collapse]() + - [Container]() + - [Expand]() + - [Grid]() + - [Layers]() + - [Resize]() + - [Stack]() + - [Wrap]() + - [Controls](./widgets/controls.md) + - [Button]() + - [Canvas]() + - [Checkbox]() + - [Color Pickers]() + - [Disclose]() + - [Input]() + - [Label]() + - [ProgressBar]() + - [Radio]() + - [Scroll]() + - [Select]() + - [Slider]() + - [Switcher]() + - [Image]() + - [TileMap]() + - [Utility Widgets](./widgets/utility.md) + - [Custom]() + - [Data]() + - [Style]() + - [Themed]() + - [ThemedMode]() + - [Space](./widgets/utility/space.md) diff --git a/guide/src/about.md b/guide/src/about.md new file mode 100644 index 0000000..4b51d64 --- /dev/null +++ b/guide/src/about.md @@ -0,0 +1 @@ +# About diff --git a/guide/src/about/philosophies.md b/guide/src/about/philosophies.md new file mode 100644 index 0000000..7db8f71 --- /dev/null +++ b/guide/src/about/philosophies.md @@ -0,0 +1,34 @@ +# 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: + +- 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. +- Composition is powerful and easy to reason about. The built-in widget library + is aimed at providing a suite of libraries each with an individual purpose to + aide in developers being able to compose more complex user interfaces from + more basic widgets. +- If a developer dislikes a built-in widget's behavior, they should be empowered + to create their own that behaves the way they desire. To ensure developers + have this flexibility, all provided widgets must only utilize functionality + that is publicly available. +- Widgets should be flexible in the types they support, prefering trait + implementations instead of hard-coded types. For example, the Label widget + supports any type that implements `Display`. +- Cushy needs both physical pixel and resolution independent measurement types. + UI designers want to use real-world measurements that scale based on the DPI + resolution of the device it is being rendered on. Widget authors and game + developers want to work with pixel-perfect measurements to ensure perfect + alignment. + +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. +- Cushy should be able to idle at close to 0% CPU. Cushy should not redraw + unless needed. diff --git a/guide/src/chapter_1.md b/guide/src/chapter_1.md deleted file mode 100644 index c94bb7e..0000000 --- a/guide/src/chapter_1.md +++ /dev/null @@ -1,33 +0,0 @@ -# Aligning Widgets - -![align.rs - horizontal-align](/examples/align-horizontal.png) - -## Align a widget to the left - -```rust,no_run,no_playground -{{#include ../guide-examples/examples/align.rs:align-left}} -``` - -## Align a widget to the left, with padding - -```rust,no_run,no_playground -{{#include ../guide-examples/examples/align.rs:align-left-pad}} -``` - -## Align a widget to the center - -```rust,no_run,no_playground -{{#include ../guide-examples/examples/align.rs:centered}} -``` - -## Align a widget to the right, with padding - -```rust,no_run,no_playground -{{#include ../guide-examples/examples/align.rs:align-right-pad}} -``` - -## Align a widget to the right - -```rust,no_run,no_playground -{{#include ../guide-examples/examples/align.rs:align-right}} -``` diff --git a/guide/src/examples/align-horizontal.png b/guide/src/examples/align-horizontal.png deleted file mode 100644 index c74c4d4..0000000 Binary files a/guide/src/examples/align-horizontal.png and /dev/null differ diff --git a/guide/src/examples/align.png b/guide/src/examples/align.png new file mode 100644 index 0000000..9a35005 Binary files /dev/null and b/guide/src/examples/align.png differ diff --git a/guide/src/intro.md b/guide/src/intro.md new file mode 100644 index 0000000..f82f58e --- /dev/null +++ b/guide/src/intro.md @@ -0,0 +1 @@ +# Welcome diff --git a/guide/src/widgets/controls.md b/guide/src/widgets/controls.md new file mode 100644 index 0000000..3dfb862 --- /dev/null +++ b/guide/src/widgets/controls.md @@ -0,0 +1,4 @@ +# Controls + +This section contains interactive widgets that read and/or write data through +the reactive data model. diff --git a/guide/src/widgets/layout.md b/guide/src/widgets/layout.md new file mode 100644 index 0000000..3c175a6 --- /dev/null +++ b/guide/src/widgets/layout.md @@ -0,0 +1,4 @@ +# Layout + +Widgets in this section have a primary purpose of performing some layout +functionality on one or more child widgets. diff --git a/guide/src/widgets/layout/align.md b/guide/src/widgets/layout/align.md new file mode 100644 index 0000000..685c9bd --- /dev/null +++ b/guide/src/widgets/layout/align.md @@ -0,0 +1,96 @@ +# Aligning Widgets + +{{#title Align - Layout Widgets - Cushy User's Guide}} + +The [`Align`][align] widget positions a child widget within its parent. It +supports both horizontal and vertical alignment. + +It accomplishes this by requesting the child measure itself using +[`SizeToFit`][sizetofit] for the child's width and/or height, and then positions +the child to align it. + +The align widget uses [`Edges`][edges][``][flexibledimension] +to specify the alignment of each edge. If an edge is +[`FlexibleDimension::Dimension`][flexexact], that edge of the child will be +placed the exact measurement from the parent's matching edge. If an edge is +[`FlexibleDimension::Auto`][flexauto], that edge will not be positioned relative +to the parent's matching edge. + +## Examples + +![Align widget example](/examples/align.png) + +The `content()` function in each of these snippets is a [`Space`][space] widget +occupying at least 32px squared: + +```rust,no_run,no_playground +{{#include ../../../guide-examples/examples/align.rs:content}} +``` + +### Align a widget to the left + +Any widget can be aligned to the left using +[`MakeWidget::align_left()`][align-left]: + +```rust,no_run,no_playground +{{#include ../../../guide-examples/examples/align.rs:align-left}} +``` + +### Align a widget to the center + +Any widget can be centered using +[`MakeWidget::centered()`][align-center]: + +```rust,no_run,no_playground +{{#include ../../../guide-examples/examples/align.rs:horizontal-center}} +``` + +`centered()` works in both axis. To center only in one direction, "fit" the +other direction: + +- To center vertically but occupy the parent's width, use + [`MakeWidget::fit_horizontally()`][fit-vert]. +- To center horizontally but occupy the parent's height, use + [`MakeWidget::fit_vertically()`][fit-horiz]. + +### Align a widget to the right + +Any widget can be aligned to the right using +[`MakeWidget::align_right()`][align-right]: + +```rust,no_run,no_playground +{{#include ../../../guide-examples/examples/align.rs:align-right}} +``` + +### Align a widget to the top + +Any widget can be aligned to the top using +[`MakeWidget::align_top()`][align-top]: + +```rust,no_run,no_playground +{{#include ../../../guide-examples/examples/align.rs:align-top}} +``` + +### Align a widget to the bottom + +Any widget can be aligned to the bottom using +[`MakeWidget::align_bottom()`][align-bottom]: + +```rust,no_run,no_playground +{{#include ../../../guide-examples/examples/align.rs:align-bottom}} +``` + +[align]: <{{ docs }}/widgets/struct.Align.html> +[align-left]: <{{ docs }}/widget/trait.MakeWidget.html#method.align_left> +[align-center]: <{{ docs }}/widget/trait.MakeWidget.html#method.centered> +[align-right]: <{{ docs }}/widget/trait.MakeWidget.html#method.align_right> +[align-top]: <{{ docs }}/widget/trait.MakeWidget.html#method.align_top> +[align-bottom]: <{{ docs }}/widget/trait.MakeWidget.html#method.align_bottom> +[sizetofit]: <{{ docs }}/enum.ConstraintLimit.html#variant.SizeToFit> +[edges]: <{{ docs }}/styles/struct.Edges.html> +[flexibledimension]: <{{ docs }}/styles/enum.FlexibleDimension.html> +[flexexact]: <{{ docs }}/styles/enum.FlexibleDimension.html#variant.Dimension> +[flexauto]: <{{ docs }}/styles/enum.FlexibleDimension.html#variant.Auto> +[fit-vert]: +[fit-horiz]: +[space]: ../utility/space.md diff --git a/guide/src/widgets/layout/layout.md b/guide/src/widgets/layout/layout.md new file mode 100644 index 0000000..40eefa2 --- /dev/null +++ b/guide/src/widgets/layout/layout.md @@ -0,0 +1 @@ +# Layout Widgets diff --git a/guide/src/widgets/utility.md b/guide/src/widgets/utility.md new file mode 100644 index 0000000..e02d2dd --- /dev/null +++ b/guide/src/widgets/utility.md @@ -0,0 +1,4 @@ +# Utility Widgets + +The widgets in this section are non-interactive widgets that do not directly +impact the layout of other widgets. diff --git a/guide/src/widgets/utility/space.md b/guide/src/widgets/utility/space.md new file mode 100644 index 0000000..01d908a --- /dev/null +++ b/guide/src/widgets/utility/space.md @@ -0,0 +1,5 @@ +# Space Widget + +The [`Space`][space] + +[space]: <{{ docs }}/widgets/struct.Space.html>