element
FreeBodyEngine.ui.element
#
ElementStates
#
Bases: Enum
The interaction states a UIElement can be in, matching the state-style
override keys documented above (note FOCUSED's value is "selected", not "focused").
GenericElement(tag=None)
#
Base for anything that can parent UIElements - shared by RootElement
and UIElement itself so elements nest arbitrarily deep.
Args: tag: Optional identifier for this element, not used by the layout/draw code itself.
Layout(x, y, width, height)
dataclass
#
RootElement(width, height, styles={})
#
Bases: GenericElement
Root UI element.
Supported styles:
padding
padding_left
padding_right
padding_top
padding_bottom
layout:
"vertical"
"horizontal"
gap
Args: width: Width of the root layout area (typically the window/framebuffer width), in pixels. height: Height of the root layout area (typically the window/framebuffer height), in pixels. styles: Root-level styles - see the style documentation above.
height
property
writable
#
See width - the same live-vs-stale fix, for height.
layout = Layout(0, 0, width, height)
instance-attribute
#
styles = styles
instance-attribute
#
width
property
writable
#
Current root width, in pixels. A property reading straight from
self.layout (the one place UIManager.resize() actually updates)
rather than a separate stored value - width/height used to be
their own plain attributes, set once at construction and never
touched again, while resize() only ever updated self.layout's
copy. Every window resize after the first frame left this
permanently stuck at whatever size the window happened to be at
launch: any code reading root.width/root.height directly (a
content area sizing itself to "the window height minus my
header/footer," say) silently kept computing against a stale
snapshot forever after, however many times the real window
resized - exactly the shape of "layout looks right on launch, then
the proportions are wrong forever after," which a tiling window
manager (retiling on every window open/close) triggers constantly.
A property means every existing read of root.width/root.height
just starts seeing the live value with no call-site changes.
calculate_layout()
#
Calculate the root content area and recursively calculate the layout of all children.
set_styles(styles)
#
Replaces the root's styles wholesale.
UIAnimation(element, style, end_value, duration, curve=Linear())
#
An in-progress animation of one style on one UIElement, created by
UIElement.set_style(..., duration=...) and driven forward by
UIElement._update() each frame until it reaches duration and removes itself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
element
|
UIElement
|
The element whose style is being animated. |
required |
style
|
str
|
Name of the style to animate. |
required |
end_value
|
any
|
The value |
required |
duration
|
float
|
How long the animation takes, in seconds. |
required |
curve
|
Curve
|
Easing curve applied to the 0-1 progress before interpolating. |
Linear()
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
curve = curve
instance-attribute
#
duration = duration
instance-attribute
#
elapsed = 0.0
instance-attribute
#
element = element
instance-attribute
#
end_shape = self._get_shape(self.end_value)
instance-attribute
#
end_value = end_value
instance-attribute
#
finished = False
instance-attribute
#
start_shape = self._get_shape(self.start_value)
instance-attribute
#
start_value = self._capture_start_value()
instance-attribute
#
style = style
instance-attribute
#
remove()
#
Detaches this animation from its element, stopping it from being updated further.
update()
#
Advances the animation by one frame's delta time, writing the
interpolated value straight into the element's style, and removes
itself from the element once duration has elapsed.
UIElement(tag=None, styles={})
#
Bases: GenericElement
Generic UI element.
Supported styles:
Sizing
width height
Padding
padding padding_left padding_right padding_top padding_bottom
Child layout
layout gap
Positioning
parent_anchor anchor x y
Text
text String to draw over the element via the engine's MSDF text pipeline. Empty (the default) draws nothing.
font
Path to a raw font file (e.g. "test.ttf"), resolved the same
way any other asset path is - relative to the project's asset
directory. Its MSDF atlas is generated and cached the first
time it's used (see core/files/loaders/font.py's
resolve_font()); no separate build step or .fbfont asset is
required.
font_size Text size in pixels.
font_weight
Selects a weight variant of font - either a name
("thin", "extralight", "light", "regular", "medium",
"semibold", "bold", "extrabold", "black", plus the aliases
"normal"/"book"/"heavy"/"demibold") or a CSS-style number
100-900. Default is "regular"/400 (no change from font
as given).
Resolved as a sibling file next to `font` following the
"{family}-{Weight}.ttf" naming convention (e.g. "bold" with
font="JetBrainsMono-Regular.ttf" looks for
"JetBrainsMono-Bold.ttf" beside it) - see
core/files/loaders/font.py's resolve_font(). This selects a
real, separately-authored font file; it doesn't synthesize
bold/faux-bold from a single weight. If no matching file
exists, warns once and falls back to `font` unchanged.
text_color (r, g, b, a) text color, 0-1 per channel.
State overrides
normal clicked hover selected
Args: tag: Optional identifier for this element, not used by the layout/draw code itself. styles: This element's styles - see the style documentation above.
DEFAULT_STYLES = {'width': 0, 'height': 0, 'padding': 0, 'layout': 'vertical', 'gap': 0, 'x': 0, 'y': 0, 'parent_anchor': 'center', 'anchor': 'bottom_left', 'base_color': (0.0, 0.0, 0.0, 0.0), 'border_radius': 0, 'border_width': 0, 'border_color': (0.0, 0.0, 0.0, 1.0), 'image': None, 'text': '', 'font': None, 'font_size': 24, 'font_weight': 'regular', 'text_color': (1.0, 1.0, 1.0, 1.0), 'editable': False, 'secret': False, 'scroll': False, 'overflow': 'visible', 'scrollbar_width': 8, 'scrollbar_margin': 2, 'scrollbar_min_thumb': 24, 'scrollbar_track': {}, 'scrollbar_thumb': {}}
class-attribute
instance-attribute
#
VALID_ANCHORS = {'top_left', 'top_center', 'top_right', 'center_left', 'center', 'center_right', 'bottom_left', 'bottom_center', 'bottom_right'}
class-attribute
instance-attribute
#
VALID_OVERFLOWS = {'visible', 'hidden', 'scroll', 'auto'}
class-attribute
instance-attribute
#
animations = []
instance-attribute
#
id = uuid.uuid4()
instance-attribute
#
parent
instance-attribute
#
state = ElementStates.NORMAL
instance-attribute
#
styles = styles
instance-attribute
#
calculate_layout(root, parent_layout=None)
#
Calculate this element's size and position, then recursively calculate the layout of its children.
clear(event)
#
Removes every callback registered for event via on() - for
re-rendering a element whose behavior changes with some state (a
follow button toggling between "Follow"/"Unfollow", say) without
accumulating a duplicate callback on it every time.
get_current_styles()
#
Merge base styles with state-specific styles.
Example:
{
"width": 100,
"hover": {
"width": 120
}
}
When hovered, width becomes 120.
get_overflow(styles=None)
#
Resolves the effective "overflow" mode - "visible", "hidden", "scroll", or "auto" - taking the "scroll": True legacy alias into account (see the OVERFLOW docs above). Checked via _has_own_style rather than the merged dict for the same reason anchor detection is: DEFAULT_STYLES always contains "overflow": "visible", so a plain presence check could never tell "explicitly set" apart from "just the default" - and an explicit "overflow" always wins over the older "scroll" flag if an element somehow has both.
get_style(name)
#
Gets the raw, un-merged value of style name set directly on this
element (not through get_current_styles() - so no DEFAULT_STYLES
fallback and no state-override merging), warning if it isn't set.
off(event, callback)
#
Unregisters callback from event, if it was registered.
on(event, callback)
#
Registers callback to run when event fires on this element -
"hover_enter", "hover_exit", "press", "release", "click", or
"submit" (editable elements only) - see the INTERACTION style docs
above. Multiple callbacks may be registered for the same event.
scroll_by(delta_px)
#
Adjusts this element's scroll offset by delta_px (only has any
visible effect if this element's "scroll" style is on). Clamped to
the valid range on the next layout pass, so over-scrolling here just
settles back to the nearest edge next frame rather than needing to
be clamped here against content it hasn't measured yet.
set_state(state)
#
Transition this element to a new interaction state (normal, hover, clicked, selected). This is the only place self.state should be assigned from outside the class - input handling (mouse-over/click detection against self._layout) should call this rather than setting element.state directly, so behavior stays consistent if this method grows validation/callbacks later.
set_style(name, val, duration=0, curve=Linear)
#
Set or animate a style.
Immediate:
element.set_style(
"width",
200
)
Animated:
element.set_style(
"width",
200,
duration=0.5
)