Skip to content

Layout

Box Model

Counterweight's layout system is roughly based on the CSS box model. When you build your application, you represent it as a hierarchy of nested elements. The layout system constructs a mirrored nested hierarchy of layout boxes, each of which has a size and position (calculated by the layout system). Each layout box is composed of four nested rectangles:

  • Content: The area where the element's "content" is laid out. What the content actually is depends on the element's type. For example, a Div's content is its children, while a Text's content is its text.
  • Padding: The area between the content and the border.
  • Border: The area between the padding and the margin, with a border drawn from box-drawing characters.
  • Margin: The area between the border and the next element.

The size, background color, and other display properties of each area are controlled via dedicated styles. The example below shows how the four areas are laid out for a simple Div element.

from counterweight.app import app
from counterweight.components import component
from counterweight.controls import Quit, Screenshot
from counterweight.elements import Div
from counterweight.styles.utilities import *


@component
def root() -> Div:
    return Div(
        style=content_green_500
        | padding_orange_500
        | pad_x_2
        | pad_y_1
        | border_lightrounded
        | border_bg_blue_500
        | margin_red_500
        | margin_x_2
        | margin_y_1
    )

Box Model

Terminal cells are not square!

Unlike in a web browser, where pixel coordinates in the x and y directions represent the same physical distance, terminal cell coordinates (which is what Counterweight uses) are not square: they are twice as tall as they are wide.

Be careful with vertical padding and margin in particular, as they will appear to be twice as large as horizontal padding and margin, which can throw off your layout. Adding only horizontal padding/margin is often sufficient. Note how the example above uses twice as much horizontal padding/margin as vertical padding/margin in order to achieve a more equal aspect ratio.

Positioning

Relative Positioning

counterweight.styles.Relative

Relative positioning is relative to the parent element's content box. Elements occupy space and are laid out next to their siblings according to the parent's layout direction.

type class-attribute instance-attribute

type: Literal['relative'] = 'relative'

x class-attribute instance-attribute

x: int = 0

y class-attribute instance-attribute

y: int = 0
from counterweight.app import app
from counterweight.components import component
from counterweight.controls import Quit, Screenshot
from counterweight.elements import Div, Text
from counterweight.styles.utilities import *

extra_style = pad_1 | margin_1


@component
def root() -> Div:
    return Div(
        style=col | justify_children_space_between,
        children=[
            Div(
                style=row,
                children=[
                    Text(
                        style=relative(x=x, y=y) | extra_style | border_lightrounded | margin_red_600,
                        content=f"relative(x={x}, y={y})",
                    )
                    for x, y in (
                        (0, 0),
                        (0, 5),
                        (0, -3),
                    )
                ],
            ),
            Div(
                style=row,
                children=[
                    Text(
                        style=relative(x=x, y=y) | extra_style | border_heavy | margin_amber_600,
                        content=f"relative(x={x}, y={y})",
                    )
                    for x, y in (
                        (0, 0),
                        (3, 3),
                        (0, 0),
                    )
                ],
            ),
            Div(
                style=row,
                children=[
                    Text(
                        style=relative(x=x, y=y) | extra_style | border_light | margin_violet_700,
                        content=f"relative(x={x}, y={y})",
                    )
                    for x, y in (
                        (0, 0),
                        (5, 0),
                        (0, -5),
                    )
                ],
            ),
        ],
    )

Relative Positioning

Absolute Positioning

counterweight.styles.Absolute

Absolute positioning is relative to the parent element's content box, but the element does not occupy space in the layout.

The inset property determines which corner of the parent element's content box this element is positioned relative to.

type class-attribute instance-attribute

type: Literal['absolute'] = 'absolute'

x class-attribute instance-attribute

x: int = 0

y class-attribute instance-attribute

y: int = 0

inset class-attribute instance-attribute

inset: Inset = Field(default=Inset())
from counterweight.app import app
from counterweight.components import component
from counterweight.controls import Quit, Screenshot
from counterweight.elements import Div, Text
from counterweight.styles.utilities import *

extra_style = border_light | pad_1 | margin_1


@component
def root() -> Div:
    return Div(
        style=col | justify_children_space_around,
        children=[
            Div(
                style=border_heavy,
                children=[
                    Text(
                        style=text_green_600,
                        content="Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
                    )
                ]
                + [
                    Text(
                        style=absolute(x=x, y=y) | extra_style | margin_red_600,
                        content=f"absolute(x={x}, y={y})",
                    )
                    for x, y in (
                        (0, 0),
                        (10, -7),
                        (33, 3),
                    )
                ],
            ),
            Div(
                style=border_heavy,
                children=[
                    Text(
                        style=text_cyan_600,
                        content="Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
                    )
                ]
                + [
                    Text(
                        style=absolute(x=x, y=y) | extra_style | margin_amber_600,
                        content=f"absolute(x={x}, y={y})",
                    )
                    for x, y in (
                        (0, 0),
                        (10, -7),
                        (33, 3),
                    )
                ],
            ),
        ],
    )

Absolute Positioning

Controlling Overlapping with z

from counterweight.app import app
from counterweight.components import component
from counterweight.controls import Quit, Screenshot
from counterweight.elements import Div, Text
from counterweight.styles.utilities import *


@component
def root() -> Div:
    return Div(
        style=col,
        children=[
            Text(
                style=z(1) | absolute(6, 6) | border_lightrounded | margin_1 | margin_purple_600,
                content="z = +1",
            ),
            Text(
                style=z(0) | absolute(4, 3) | border_lightrounded | margin_1 | margin_teal_600,
                content="z =  0",
            ),
            Text(
                style=z(-1) | absolute(0, 0) | border_lightrounded | margin_1 | margin_red_600,
                content="z = -1",
            ),
            Text(
                style=z(2) | absolute(13, 3) | border_lightrounded | margin_1 | margin_amber_600,
                content="z = +2",
            ),
        ],
    )

Z Layers

from counterweight.app import app
from counterweight.components import component
from counterweight.controls import Quit, Screenshot
from counterweight.elements import Div, Text
from counterweight.styles.utilities import *


@component
def root() -> Div:
    return Div(
        style=col | align_self_stretch,
        children=[
            Div(
                style=row | align_self_stretch | border_heavy,
                children=[
                    Text(
                        style=inset_top_left,
                        content="inset_top_left",
                    ),
                    Text(
                        style=inset_top_left | absolute(x=3, y=3),
                        content="inset_top_left | absolute(x=3, y=3)",
                    ),
                    Text(
                        style=inset_top_center,
                        content="inset_top_center",
                    ),
                    Text(
                        style=inset_top_right,
                        content="inset_top_right",
                    ),
                    Text(
                        style=inset_center_left,
                        content="inset_center_left",
                    ),
                    Text(
                        style=inset_center_center,
                        content="inset_center_center",
                    ),
                    Text(
                        style=inset_center_center | absolute(x=-2, y=-4),
                        content="inset_center_center | absolute(x=-2, y=-4)",
                    ),
                    Text(
                        style=inset_center_right,
                        content="inset_center_right",
                    ),
                    Text(
                        style=inset_bottom_left,
                        content="inset_bottom_left",
                    ),
                    Text(
                        style=inset_bottom_center,
                        content="inset_bottom_center",
                    ),
                    Text(
                        style=inset_bottom_right,
                        content="inset_bottom_right",
                    ),
                    Text(
                        style=inset_bottom_right | absolute(y=-4),
                        content="inset_bottom_right | absolute(y=-4)",
                    ),
                ],
            )
        ],
    )

Absolute Positioning Insets

Fixed Positioning

counterweight.styles.Fixed

Fixed positioning is relative to the screen's top-left corner (0, 0).

type class-attribute instance-attribute

type: Literal['fixed'] = 'fixed'

x class-attribute instance-attribute

x: int = 0

y class-attribute instance-attribute

y: int = 0
from counterweight.app import app
from counterweight.components import component
from counterweight.controls import Quit, Screenshot
from counterweight.elements import Div, Text
from counterweight.styles.utilities import *

extra_style = border_heavy | pad_1 | margin_1 | margin_red_600


@component
def root() -> Div:
    return Div(
        style=row,
        children=[
            Text(
                style=fixed(x=x, y=y) | extra_style,
                content=f"fixed(x={x}, y={y})",
            )
            for x, y in (
                (0, 0),
                (10, 10),
                (30, 20),
                (15, 25),
                (33, 3),
            )
        ],
    )

Fixed Positioning