zng

Module layout

source
Expand description

Layout service, units and other types.

A widget final size and position is influenced by the widget and all ancestor widgets, the properties and nodes that influence the size and position can be grouped into widget intrinsics, widget properties, layout properties and transform properties.

Internally this is split into two passes UiNode::layout and UiNode::render, transform properties are only applied during render and only influence the size and position of the widget and descendants, the other properties are true layout and influence the size and position of the parent widget and siblings too.

§Widget Intrinsics

Each widget defines a size preference, the default widget has no minimum nor maximum size, it fills available space and collapses to zero when aligned, most widgets override this and have a minimum size preference. The Text! prefers a size that fits the entire text without introducing wrap line breaks, the Stack! widget prefers a size that fits all its children positioned in a given direction.

§Widget Properties

Widget size can be influenced by properties widget specific properties, the Text! widget is affected by the font properties for example, as different fonts have different sizes. The Stack! widget is affected by the direction property that changes position of children widgets and so changes the size that best fits the children.

§Layout Properties

Widget size and position can be more directly configured using the standalone layout properties defined in this module, as an example the min_size property influences the widget size and the align property influences the widget position, the margin property potentially influences both size and position.

use zng::prelude::*;

Window! {
    child = Wgt! {
        layout::min_size = 40;
        layout::align = layout::Align::CENTER;
        widget::background_color = colors::AZURE;
    };
}

§Transform Properties

Widget size and position can be affected during render only, the standalone transform property and derived properties like scale change the final size and position of the widget by transforming the final layout size and position, this affects only the widget and descendants, widget interactions like clicks will see the widget at its final transformed bounds, but the parent widget will size itself and position other children using the layout size and position.

use zng::prelude::*;

Stack! {
    layout::align = layout::Align::CENTER;
    direction = StackDirection::left_to_right();
    children = ui_vec![
        Wgt! {
            layout::size = (100, 200);
            widget::background_color = colors::RED;
        },
        Wgt! {
            layout::scale = 120.pct();
            layout::size = (100, 200);
            widget::z_index = widget::ZIndex::FRONT;
            widget::background_color = colors::GREEN;
        },
        Wgt! {
            layout::size = (100, 200);
            widget::background_color = colors::BLUE;
        },
    ];
}

The example above declares a horizontal stack with 3 rectangles, the green rectangle is rendered slightly over the other rectangles because it is scale to 120% of the size, scale is a render transform only so the stack widget still positions the other rectangles as if the middle one was not scaled. Also note the widget::z_index usage, the stack widget render each children in declaration order by default, this is overridden for the green rectangle so it is rendered last, over the blue rectangle too.

§Layout Units

Most layout properties receive inputs in Length or length composite types like Size. These types are layout in the widget context to compute their actual length, the example below demonstrates every LengthUnits, FactorUnits and length expressions.

use zng::prelude::*;

macro_rules! width {
    ($width:expr) => {
        Text! {
            layout::force_width = $width;
            txt = stringify!($width);
            widget::background_color = colors::BLUE.desaturate(50.pct());
        }
    };
}
Window! {
    child_align = layout::Align::START;
    child = Scroll! {
        mode = zng::scroll::ScrollMode::VERTICAL;
        padding = 10;
        child = Stack! {
            direction = StackDirection::top_to_bottom();
            spacing = 2;
            children = ui_vec![
                width!(100), // 100 device independent pixels
                width!(100.dip()), // 100 device independent pixels
                width!(100.px()), // 100 device pixels
                width!(100.pct()), // 100% of the available width
                width!(100.pct_l()), // 100% of the available width
                width!(50.pct()), // 50% of the available width
                width!(1.fct()), // 1 times the available width
                width!(1.fct_l()), // 1 times the available width
                width!(0.5.fct()), // 0.5 times the available width
                width!(100.pt()), // 100 font points
                width!(8.em()), // 8 times the font size
                width!(800.em_pct()), // 800% of the font size
                width!(8.rem()), // 8 times the root font size
                width!(800.rem_pct()), // 800% of the root font size
                width!(1.vw()), // 1 times the viewport width
                width!(100.vw_pct()), // 100% of the viewport width
                width!(0.5.vw()), // 0.5 times the viewport width
                width!(1.vh()), // 1 times the viewport height
                width!(100.vh_pct()), // 100% of the viewport height
                width!(0.5.vh()), // 0.5 times the viewport height
                width!(0.5.vmin()), // 0.5 times the viewport min(width, height)
                width!(50.vmin_pct()), // 50% of the viewport min(width, height)
                width!(0.5.vmax()), // 0.5 times the viewport max(width, height)
                width!(50.vmax_pct()), // 50% of the viewport max(width, height)
                width!(100.dip() + 50.pct()), // expression, 100dip + 50%.
                width!(1.lft()), //1 parcel of the leftover space.
                width!(Length::Default), // default value
            ];
            widget::border = 1, colors::RED.desaturate(50.pct());
        };
    };
}

§Length & Factor Units

Length units are defined by LengthUnits that provides extension methods for f32 and i32 values.

The most common unit is the device independent pixel, or DIP, this is a value that is multiplied by the system scale factor to compute the an exact pixel length, widgets sized in DIPs have a similar apparent size indented of the screen pixel density. This is the default unit, f32 and i32 convert to it so width = 100; is the same as width = 100.dip();.

Length can be relative to the available space provided by the parent widget, 100.pct() and 1.fct() declare FactorPercent and Factor values that convert to Length::Factor. The FactorUnits provide the extension methods and is implemented for f32 and i32. You can also use 100.pct_l() and 1.fct_l() to get a Length value directly in places that don’t convert the factor types to length.

There are multiple units related to font size, 24.pt() defines a size in font points, one font point is 96/72 * scale_factor device pixels. Size can be relative to the contextual font size, 2.em() and 200.em_pct() declare a length twice the computed contextual font size, 2.rem() and 2.rem_pct() declare a length twice the computed root font size (the Window! font size).

Lengths can also be relative to the viewport. The viewport is the window content area size, or the parent Scroll! visible area size. Lengths 0.2.vw() and 20.vw_pct() are 20% of the viewport width, 0.2.vh() and 20.vh_pct() are 20% of the viewport height, 1.vmin() is the minimum viewport length (min(w, h)), 1.vmax() is the maximum viewport length.

§Length Expressions

Different length units can be mixed into a length expression, 1.em() + 5.dip() will create a Length::Expr value that on layout will compute the pixel length of both terms and then sum. Length expressions support the four basic arithmetic operations, negation, maximum and minimum and absolute.

Some basic length expressions are pre-computed on the spot, 10.dip() + 10.dip() declares a Length::Dip(20) value, but most expression declare an object that dynamically executes the expression after all terms are layout.

§Leftover Length

The leftover length is a special value that represents the space leftover after non-leftover sibling widgets are layout. This must be implemented by a parent widget to fully work, the Grid! widget implements it, in widgets that don’t implement it the unit behaves like a factor.

use zng::prelude::*;

Window! {
    child = Grid! {
        columns = ui_vec![
            grid::Column!(1.lft()),
            grid::Column!(100.dip()),
            grid::Column!(2.lft()),
        ];
        rows = ui_vec![grid::Row!(100.pct())];
        cells = ui_vec![
            Wgt! {
                grid::cell::column = 0;
                widget::background_color = colors::RED;
            },
            Wgt! {
                grid::cell::column = 1;
                widget::background_color = colors::GREEN;
            },
            Wgt! {
                grid::cell::column = 2;
                widget::background_color = colors::BLUE;
            },
        ];
    }
}

The example above declares a grid with 3 columns, on layout the grid computes the width of the middle column first (100.dip()), the leftover available width is divided between the other 2 columns proportional to the leftover value. Note that value range of leftover is normalized across all leftover siblings, in the example above changing the values to 2.lft() and 4.lft() will produce the column sizes.

§Default Length

The Length::Default value represents the length that is used when no other length is set. It is a placeholder value that is filled in by the widget or property that is resolving the layout. The grid::Column!() has Default width, in grids this means auto-size, the column is sized to fit all cells. In the standalone width property the default width means the fill width.

§Measure & Layout

Nodes that implement custom layout must handle UiNode::measure and UiNode::layout. Measure and layout provide a desired size and final size respectively, given the same context both methods return the same size, the different is that the measure call must not actually affect the widget, it exists to allow a parent widget to query what the layout result would be for a given context.

Consider a Stack! that is aligned CENTER and has children aligned FILL, to fulfill these constraints the stack does the layout in two passes, first it measures each child to find the width, then it layouts each child constrained to this width. If this same stack is given an exact size it will skip the measure pass and just do the layout directly.

The coordination between layout properties on a widget and between widgets is centered on the LAYOUT, WidgetMeasure, WidgetLayout and the return PxSize. Parent nodes set context metrics and constraints using the LAYOUT service, child nodes returns the size and optionally set more return metadata in the WidgetMeasure and WidgetLayout args. The parent node then sets the child position using WidgetLayout or by manually transforming the child during render.

Other contextual services and variables may complement the layout computation, the WIDGET_SIZE is used to implement Length::Leftover layouts, the widget::BORDER is used to implement the alignment between borders and the background. Widgets can use context vars to define layout preferences that only apply to their special layout, the Text! and Image! widgets are examples of this.

UI components are very modular, during layout is when they are the closest coupled, implementers must careful consider the full LAYOUT, WidgetMeasure WidgetLayout APIs, understand what properties placed in the NestGroup::LAYOUT can do and what the widget outer and inner bounds are. Implementers also must consider if their layout will support inlining or if it will only be a block. After reading the APIs a good way to learn is by studying the source code of properties in this module, followed by the Image!, Stack!, Grid! and Wrap! implementations.

§Outer & Inner Bounds

Each laidout widget has two computed rectangles, the inner bounds define the rendered area, the outer bounds define the extra space taken by the widget layout, properties like align and margin are still a part of the widget, the blank space they add around the widget is inside the widget outer bounds.

use zng::prelude::*;

Window! {
    padding = 20;
    child = Wgt! {
        layout::size = 80;
        layout::align = layout::Align::CENTER;
        window::inspector::show_bounds = true;
    };
}

The example above uses the window::inspector::show_bounds property to inspect the bounds of a widget, it shows the outer bounds of the widget extend to almost cover the entire window, that happens because the window default child_align is FILL and it only reserved 20 of padding space, leaving the rest of the space for the child widget to handle. The widget wants to have an exact size of 80 centered on the available space, so it ends up with the outer bounds taking the available space and the inner bounds taking the exact size.

§Inline

Layout has two modes, blocks and inline, in block layout the shape of the laidout widgets is not changed, they are always rectangular, inline layout expands layout to alter the shape of laidout widgets to potentially split into multiple rectangles that define the first line, the middle block of lines and the last line.

The example below declares a Wrap! with 3 Text! children, both the wrap and text widgets support inline layout so the end-result is that the green text will be reshaped as two rectangles, one after the red text and one before the blue text.

use zng::prelude::*;

Wrap! {
    children = ui_vec![
        Text! {
            widget::background_color = colors::RED.with_alpha(40.pct());
            txt = "RED";
        },
        Text! {
            widget::background_color = colors::GREEN.with_alpha(40.pct());
            txt = "GREEN\nGREEN";
        },
        Text! {
            widget::background_color = colors::BLUE.with_alpha(40.pct());
            txt = "BLUE";
        },
    ]
}

Inline layout is modeled to support complex text layout interactions, like bidirectional text reordering, inlined widgets don’t need to be text however, the Wrap! widget itself can be nested.

If a widget does not support inline it calls WidgetMeasure::disable_inline, in an inline context these widgets are inline-blocks. If a panel widget does not support inline and it needs to measure children it calls WidgetMeasure::measure_block.

If a widget or property supports inline it can detect it is in an inline context by WidgetMeasure::inline where the preferred segments of the widget can be set for the parent inline panel to analyze, if inline is set during measure it will also be inline during layout and LAYOUT will have inline constraints. During layout the WidgetLayout::inline value can be set to the final inline info.

After inline layout the are positioned so that the last line of the previous sibling connects with the first line of the next, all of the widget visual properties must support this however, the WIDGET.bounds().inline() is available during render with cached negative space clips that can quickly be used. If a visual property is not aware of inline it can potentially render over the previous sibling, inline should be disabled for the widget if the property cannot support inline.

§Full API

See zng_layout, zng_wgt_transform and zng_wgt_size_offset for the full API.

Structs§

Enums§

Statics§

Traits§

Functions§

  • P Getter property, gets the latest rendered widget inner bounds in the window space.
  • P Getter property, gets the latest rendered widget inner height.
  • P Getter property, gets the latest rendered widget inner height, in device pixels.
  • P Getter property, gets the latest rendered widget inner size.
  • P Getter property, gets the latest rendered widget inner size, in device pixels.
  • P Getter property, gets the latest rendered widget inner transform.
  • P Getter property, gets the latest rendered widget inner width.
  • P Getter property, gets the latest rendered widget inner width, in device pixels.
  • P Aligns the widget within the available space.
  • P Sets if the widget is still visible when it is turned back towards the viewport due to rotations in X or Y axis in the widget or in parent widgets.
  • P Set or overwrite the baseline of the widget.
  • P Aligns the widget content within the available space.
  • P Exact height of the widget ignoring the contextual max.
  • P Exact size of the widget ignoring the contextual max.
  • P Exact width of the widget ignoring the contextual max.
  • P Exact height of the widget.
  • P Enforce an inline mode on the widget.
  • P If the layout direction is left-to-right.
  • P If the layout direction is right-to-left.
  • P Margin space around the widget.
  • P Maximum height of the widget.
  • P Maximum size of the widget.
  • P Maximum width of the widget.
  • P Minimum height of the widget.
  • P Minimum size of the widget.
  • P Minimum width of the widget.
  • P Widget layout offset.
  • P Margin space around the content of a widget.
  • P Distance from the Z plane (0) the viewer is, affects 3D transform on the widget’s children.
  • P Vanishing point used by 3D transforms in the widget’s children.
  • P Rotate transform.
  • P Rotate transform.
  • P Rotate transform.
  • P Same as rotate.
  • P Scale transform.
  • P Scale X transform.
  • P Scale X and Y transform.
  • P Scale Y transform.
  • P Exact size of the widget.
  • P Skew transform.
  • P Skew X transform.
  • P Skew Y transform.
  • Calls f with is_slerp_enabled set to enabled.
  • Spherical linear interpolation sampler.
  • P Retain the widget’s previous height if the new layout height is smaller. The widget is layout using its previous height as the minimum height constrain.
  • P Retain the widget’s previous size if the new layout size is smaller. The widget is layout using its previous size as the minimum size constrain.
  • P Retain the widget’s previous width if the new layout width is smaller. The widget is layout using its previous width as the minimum width constrain.
  • P Custom transform.
  • P Point relative to the widget inner bounds around which the transform is applied.
  • P Defines how the widget and children are positioned in 3D space.
  • P Translate transform.
  • P Translate X transform.
  • P Translate Y transform.
  • P Translate Z transform.
  • P Exact width of the widget.
  • P Offset on the x axis.
  • P Offset on the y axis.

Type Aliases§