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 aText
'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
)
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.
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),
)
],
),
],
)
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.
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),
)
],
),
],
)
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",
),
],
)
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)",
),
],
)
],
)
Fixed Positioning
counterweight.styles.Fixed
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),
)
],
)