Layout and Styling
OSUI provides a robust layout and styling system to control the appearance and positioning of your UI elements. This system is built around several key concepts: Transform
, Position
, Dimension
, Style
, and Background
.
1. Transform
: Positioning and Sizing Rules
The Transform
component is used to define how a widget should be positioned and sized relative to its parent. It specifies declarative rules rather than absolute pixel values, allowing for flexible and responsive layouts.
You typically create and attach a Transform
using the @
component syntax in rsx!
or by calling widget.component(Transform::new()...)
.
use osui::prelude::*;
// Default transform: (0,0) position, content-sized
let default_transform = Transform::new();
// Centered horizontally and vertically, content-sized
let centered_transform = Transform::center();
rsx! {
@Transform::new().padding(2, 1).dimensions(30, 5);
Div { "A fixed-size div with padding." }
@Transform::new().right().margin(5, 0);
Div { "Aligned to the right with a 5-cell horizontal margin." }
@Transform::new().bottom().margin(0, 2);
Div { "Aligned to the bottom with a 2-cell vertical margin." }
}
Transform
Fields:
x: Position
: Horizontal position relative to the parent.y: Position
: Vertical position relative to the parent.mx: i32
: Horizontal margin (offset) from the calculatedx
position. Can be negative for overlap.my: i32
: Vertical margin (offset) from the calculatedy
position. Can be negative for overlap.px: u16
: Horizontal padding (internal spacing) around the content.py: u16
: Vertical padding (internal spacing) around the content.width: Dimension
: Rule for the widget's width.height: Dimension
: Rule for the widget's height.
Chainable Methods for Transform
Transform
provides several convenient chainable methods for common layout patterns:
Transform::new()
: Creates a default transform at(0,0)
withContent
dimensions and no padding/margin.Transform::center()
: Creates a transform centered both horizontally and vertically.Transform::bottom(self)
: Setsy
toPosition::End
.Transform::right(self)
: Setsx
toPosition::End
.Transform::margin(self, x: i32, y: i32)
: Setsmx
andmy
.Transform::padding(self, x: u16, y: u16)
: Setspx
andpy
.Transform::dimensions(self, width: u16, height: u16)
: Setswidth
andheight
toDimension::Const
.
2. Position
: Horizontal and Vertical Alignment
Position
defines how an element is placed along an axis relative to its parent's boundaries.
pub enum Position {
/// Fixed position in cells from the origin (top-left).
Const(u16),
/// Centered in the parent.
Center,
/// Aligned to the end (right for x, bottom for y) of the parent.
End,
}
Position::Const(value)
: Sets an exact coordinate from the top-left (0,0). You can also useu16
directly, thanks toimpl From<u16> for Position
.@Transform { x: 5, y: 10 }; // Same as x: Position::Const(5), y: Position::Const(10)
Position::Center
: Centers the element within the available space of its parent on that axis.@Transform { x: Position::Center, y: Position::Center };
Position::End
: Aligns the element to the right (forx
) or bottom (fory
) edge of its parent.@Transform { x: Position::End, y: Position::Const(0) }; // Top-right aligned
3. Dimension
: Sizing Rules
Dimension
defines how an element's width or height is determined.
pub enum Dimension {
/// Fills the available space from the parent.
Full,
/// Automatically sized to fit content.
Content,
/// Fixed size in cells.
Const(u16),
}
Dimension::Full
: The element will take up 100% of the available space on that axis within its parent.Dimension::Content
: The element's size will be determined by its content. For container elements (likeDiv
,FlexRow
,FlexCol
), this means their size will expand to encompass their children. For text elements, it will be the size of the text. This is the default.Dimension::Const(value)
: The element will have a fixed size in cells. You can also useu16
directly, thanks toimpl From<u16> for Dimension
.@Transform { width: 20, height: 5 }; // Same as width: Dimension::Const(20), height: Dimension::Const(5)
4. Style
: Visual Appearance
The Style
component defines the background and foreground colors of a widget.
pub struct Style {
pub background: Background,
pub foreground: Option<u32>,
}
background: Background
: Specifies the widget's background appearance.foreground: Option<u32>
: Sets the text color.None
means the default terminal foreground color.
You attach Style
using the @
component syntax:
use osui::prelude::*;
rsx! {
@Style { background: Background::Solid(0x222222), foreground: Some(0xFFFFFF) };
Div {
"This text is white on a dark gray background."
}
@Style { background: Background::Outline(0x00FF00), foreground: Some(0x00FF00) };
Div {
"This div has a green outline and green text."
}
}
Background
: Background Appearance Options
Background
defines various ways a widget's background can be drawn. Colors are specified as u32
hex values (e.g., 0xFF0000
for red).
pub enum Background {
/// Transparent / no background.
NoBackground,
/// Draws a basic outline using the given color.
Outline(u32),
/// Draws a rounded outline using the given color.
RoundedOutline(u32),
/// Fills the background with the specified color.
Solid(u32),
}
The transform!
Macro
For even more concise Transform
definitions, you can use the transform!
macro:
use osui::prelude::*;
rsx! {
@transform!{ x: 10, y: Center, width: Full, padding: (1, 1) };
Div {
"This div is at x=10, centered vertically, full width, with 1 unit of padding."
}
}
This macro automatically converts u16
values to Position::Const
or Dimension::Const
where appropriate.
How Layout and Styling are Applied
When the Screen
renders a widget, it performs the following steps (simplified):
- Retrieve Components: It fetches the
Transform
andStyle
components attached to the widget. - Resolve Transform: The
Transform
'suse_dimensions
anduse_position
methods are called. These methods take the parent's resolved size (from theRenderScope
) and convert the declarativePosition
andDimension
rules into concreteu16
values (absolutex
,y
,width
,height
) within aRawTransform
structure. - Apply Style: The
Style
component is passed to theRenderScope
. - Element Rendering: The widget's
Element::render
method is called. This method uses the now-resolvedRawTransform
andStyle
from theRenderScope
to queue its drawing instructions (text, rectangles). It might also updateRenderScope
's size based on content. - Parent Rendering (
after_render
): For container elements (likeDiv
,FlexRow
,FlexCol
), theirElement::after_render
method then takes over. They iterate through their children, set theRenderScope
's "parent size" to their own resolved size, and recursively trigger the rendering process for each child. - Drawing to Terminal: Finally,
RenderScope::draw()
is called, which takes all accumulated drawing instructions and applies them to the terminal usingcrossterm
and ANSI escape codes. Backgrounds and outlines are drawn first, then text and other content.
This multi-stage process ensures that layout rules are correctly interpreted from parent to child, allowing for adaptive and well-positioned UI elements.