Module layout

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, block 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 three 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 children 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§

Align
x and y alignment.
AngleDegree
Angle in degrees.
AngleGradian
Angle in gradians.
AngleRadian
Angle in radians.
AngleTurn
Angle in turns (complete rotations).
BoolVector2D
A 2d vector of booleans, useful for component-wise logic operations.
ByteLength
A length in bytes.
CornerRadius2D
Ellipses that define the radius of the four corners of a 2D box.
Dip
Device independent pixel.
DistanceKey
Comparable key that represents the absolute distance between two pixel points.
Factor
Normalized multiplication factor.
Factor2d
Scale factor applied to x and y dimensions.
FactorPercent
Multiplication factor in percentage (0%-100%).
FactorSideOffsets
Scale factor applied to margins.
GridSpacing
Spacing in-between grid cells in Length units.
InlineConstraintsLayout
Constraints for inline layout.
InlineConstraintsMeasure
Constraints for inline measure.
InlineSegment
Represents a segment in an inlined widget first or last row.
InlineSegmentPos
Position of an inline segment set by the inlining parent.
LAYOUT
Current layout context.
LayoutMask
Mask of values that can affect the layout operation of a value.
LayoutMetrics
Layout metrics in a LAYOUT context.
LayoutMetricsSnapshot
Layout metrics snapshot.
LayoutPassId
Identifies the layout pass of a window.
Line
2D line in Length units.
Point
2D point in Length units.
Px
Device pixel.
PxConstraints
Pixel length constraints.
PxConstraints2d
Pixel size constraints.
PxDensity
Measurement of pixels in a screen or points in print.
PxGridSpacing
Computed GridSpacing.
PxLine
Computed Line.
Rect
2D rect in Length units.
SideOffsets
2D size offsets in Length units.
SideOffsets2D
A group of 2D side offsets, which correspond to top/right/bottom/left for borders, padding, and margins in CSS, optionally tagged with a unit.
Size
2D size in Length units.
Transform
A transform builder type.
Vector
2D vector in Length units.
WIDGET_SIZE
Exact size property info.
WidgetLayout
Represents the in-progress layout pass for a widget tree.
WidgetMeasure
Represents the in-progress measure pass for a widget tree.

Enums§

InlineConstraints
Constraints for inline measure or layout.
InlineMode
Inline mode explicitly selected for a widget.
LayoutAxis
Represents a layout dimension.
LayoutDirection
Defines the layout flow direction.
Length
1D length units.
LengthExpr
Represents an unresolved Length expression.
Orientation2D
Orientation of two 2D items.
PxTransform
A transform in device pixels.
TextSegmentKind
The type of an inline/text segment.
TransformStyle
Defines if a widget is part of the same 3D space as the parent.
WidgetLength
Represents the width or height property value set on a widget.

Statics§

DIRECTION_VAR
Wrap direction of text in a widget context.

Traits§

AngleUnits
Extension methods for initializing angle units.
ByteUnits
Extension methods for initializing ByteLength values.
DipToPx
Conversion from Dip to Px units.
FactorUnits
Extension methods for initializing factor units.
Layout1d
Represents a one-dimensional length value that can be converted to a pixel length in a LAYOUT context.
Layout2d
Represents a two-dimensional value that can be converted to a pixel value in a LAYOUT context.
LengthUnits
Extension methods for initializing Length units.
LineFromTuplesBuilder
Build a Line using the syntax (x1, y1).to(x2, y2).
PxDensityUnits
Extension methods for initializing pixel density units.
PxToDip
Conversion from Px to Dip units.
RectFromTuplesBuilder
Build a Rect using the syntax (width, height).at(x, y).
TimeUnits
Extension methods for initializing Duration values.

Functions§

actual_bounds
P Getter property, gets the latest rendered widget inner bounds in the window space.
actual_height
P Getter property, gets the latest layout widget inner height.
actual_height_px
P Getter property, gets the latest layout widget inner height, in device pixels.
actual_size
P Getter property, gets the latest layout widget inner size.
actual_size_px
P Getter property, gets the latest layout widget inner size, in device pixels.
actual_transform
P Getter property, gets the latest rendered widget inner transform.
actual_width
P Getter property, gets the latest layout widget inner width.
actual_width_px
P Getter property, gets the latest layout widget inner width, in device pixels.
align
P Aligns the widget within the available space.
backface_visibility
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.
baseline
P Set or overwrite the baseline of the widget.
child_align
P Aligns the widget content within the available space.
force_height
P Exact height of the widget ignoring the contextual max.
force_size
P Exact size of the widget ignoring the contextual max.
force_width
P Exact width of the widget ignoring the contextual max.
height
P Exact height of the widget.
inline
P Enforce an inline mode on the widget.
is_ltr
P If the layout direction is left-to-right.
is_rtl
P If the layout direction is right-to-left.
margin
P Margin space around the widget.
max_height
P Maximum height of the widget.
max_size
P Maximum size of the widget.
max_width
P Maximum width of the widget.
min_height
P Minimum height of the widget.
min_size
P Minimum size of the widget.
min_width
P Minimum width of the widget.
offset
P Widget layout offset.
padding
P Margin space around the content of a widget.
perspective
P Distance from the Z plane (0) the viewer is, affects 3D transform on the widget’s children.
perspective_origin
P Vanishing point used by 3D transforms in the widget’s children.
rotate
P Rotate transform.
rotate_x
P Rotate transform.
rotate_y
P Rotate transform.
rotate_z
P Same as rotate.
scale
P Scale transform.
scale_x
P Scale X transform.
scale_xy
P Scale X and Y transform.
scale_y
P Scale Y transform.
size
P Exact size of the widget.
skew
P Skew transform.
skew_x
P Skew X transform.
skew_y
P Skew Y transform.
slerp_enabled
Calls f with is_slerp_enabled set to enabled.
slerp_sampler
Spherical linear interpolation sampler.
sticky_height
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.
sticky_size
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.
sticky_width
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.
transform
P Custom transform.
transform_origin
P Point relative to the widget inner bounds around which the transform is applied.
transform_style
P Defines how the widget and children are positioned in 3D space.
translate
P Translate transform.
translate_x
P Translate X transform.
translate_y
P Translate Y transform.
translate_z
P Translate Z transform.
width
P Exact width of the widget.
x
P Offset on the x axis.
y
P Offset on the y axis.

Type Aliases§

DipBox
A rectangle box in device independent pixels.
DipCornerRadius
Corner-radius in device independent pixels.
DipPoint
A point in device independent pixels.
DipRect
A rectangle in device independent pixels.
DipSideOffsets
Side-offsets in device independent pixels.
DipSize
A size in device pixels.
DipVector
A vector in device independent pixels.
PxBox
A rectangle box in device pixels.
PxCornerRadius
Corner-radius in device pixels.
PxDensity2d
Pixel density value that can differ between dimensions.
PxPoint
A point in device pixels.
PxRect
A rectangle in device pixels.
PxSideOffsets
Side-offsets in device pixels.
PxSize
A size in device pixels.
PxVector
A vector in device pixels.