widgets.rs raw

//! The widget demo: a retained widget tree with nested flex layout,
//! wrapping text, and an image, shared by the runnable example and the
//! golden tests.

use guiduck_core::{Container, Image, Text, WidgetId, WidgetTree, taffy};
use guiduck_scene::paint::color::palette::css;
use taffy::prelude::{auto, length, percent};

use crate::{SAMPLE_FONT_FAMILY, checkerboard, sample_text_context};

/// Build the demo tree. Returns the tree and the id of the paragraph widget
/// (used by tests to check wrapping behavior).
pub fn demo_tree() -> (WidgetTree, WidgetId) {
    let mut tree = WidgetTree::with_text_context(sample_text_context());

    let root = tree.insert(
        Container::new().background(css::WHITE_SMOKE),
        taffy::Style {
            size: taffy::Size {
                width: percent(1.0_f32),
                height: percent(1.0_f32),
            },
            flex_direction: taffy::FlexDirection::Column,
            padding: taffy::Rect::length(16.0_f32),
            gap: taffy::Size {
                width: length(12.0_f32),
                height: length(12.0_f32),
            },
            ..Default::default()
        },
        None,
    );

    tree.insert(
        Text::new("guiduck — widgets, flexbox, wrapping text")
            .font_size(20.0)
            .family(SAMPLE_FONT_FAMILY)
            .brush(css::DARK_SLATE_GRAY),
        taffy::Style::default(),
        Some(root),
    );

    // A row of three growing cards.
    let cards = tree.insert(
        Container::new(),
        taffy::Style {
            flex_direction: taffy::FlexDirection::Row,
            gap: taffy::Size {
                width: length(12.0_f32),
                height: length(12.0_f32),
            },
            ..Default::default()
        },
        Some(root),
    );
    for (label, color) in [
        ("First", css::LIGHT_STEEL_BLUE),
        ("Second", css::NAVAJO_WHITE),
        ("Third", css::THISTLE),
    ] {
        let card = tree.insert(
            Container::new()
                .background(color)
                .corner_radius(10.0)
                .border(css::SLATE_GRAY, 1.5),
            taffy::Style {
                flex_grow: 1.0,
                flex_basis: length(0.0_f32),
                padding: taffy::Rect::length(12.0_f32),
                ..Default::default()
            },
            Some(cards),
        );
        tree.insert(
            Text::new(label)
                .font_size(15.0)
                .family(SAMPLE_FONT_FAMILY)
                .brush(css::BLACK),
            taffy::Style::default(),
            Some(card),
        );
    }

    // A paragraph that wraps beside a fixed-width image.
    let body = tree.insert(
        Container::new(),
        taffy::Style {
            flex_direction: taffy::FlexDirection::Row,
            gap: taffy::Size {
                width: length(12.0_f32),
                height: length(12.0_f32),
            },
            align_items: Some(taffy::AlignItems::FLEX_START),
            ..Default::default()
        },
        Some(root),
    );
    let paragraph = tree.insert(
        Text::new(
            "This paragraph lives in a flex row next to an image. When the window \
             gets narrower the text wraps to more lines and the row grows taller, \
             which is exactly the layout behavior taffy owes us. The quick brown \
             fox jumps over the lazy dog.",
        )
        .font_size(14.0)
        .family(SAMPLE_FONT_FAMILY)
        .brush(css::BLACK),
        taffy::Style {
            flex_grow: 1.0,
            flex_basis: length(0.0_f32),
            ..Default::default()
        },
        Some(body),
    );
    tree.insert(
        Image::new(guiduck_core::graphic::Graphic::Image(checkerboard())),
        taffy::Style {
            size: taffy::Size {
                width: length(96.0_f32),
                height: auto(),
            },
            flex_shrink: 0.0,
            ..Default::default()
        },
        Some(body),
    );

    (tree, paragraph)
}

#[cfg(test)]
mod tests {
    use guiduck_scene::FragmentStore;
    use guiduck_scene::geom::Size;

    use super::*;

    #[test]
    fn demo_lays_out_and_paints() {
        let (mut tree, paragraph) = demo_tree();
        tree.compute_layout(Size::new(640.0, 420.0));
        let wide_height = tree.layout(paragraph).height();

        let mut store = FragmentStore::new();
        let root = tree.paint_fresh(&mut store).expect("tree has a root");
        assert!(store.get(root).unwrap().is_balanced());

        let (mut narrow, paragraph2) = demo_tree();
        narrow.compute_layout(Size::new(360.0, 420.0));
        assert!(
            narrow.layout(paragraph2).height() > wide_height,
            "narrower viewport wraps the paragraph to more lines"
        );
    }
}