3 User Interface
dxtr edited this page 2026-10-19 21:25:47 +02:00

User Interface

The system pyssel/ui is the user interface toolkit, as Godot's controls are: a user interface is scene nodes, saved and laid out like any scene, drawn with the colours, fonts and style boxes of themes. What is here is the control, which fills a rectangle, the layout that places controls, the containers that lay them out, themes, the input that reaches controls, from the pointer and the keyboard, and the widgets of a menu screen or a settings form: panels, labels, buttons, check boxes, sliders, progress bars, scroll bars, line edits and text edits. This page says what every control has; Containers, Widgets, Themes, Control Input and Popups, Menus and Dialogs the rest. Panels draw backgrounds:

(asdf:load-system :pyssel/ui)

(defpackage #:panels
  (:use #:cl)
  (:local-nicknames (#:sc #:pyssel/scene) (#:s2d #:pyssel/scene-2d) (#:ui #:pyssel/ui)))
(in-package #:panels)

(defun card (color &key (radius 6))
  (make-instance 'ui:flat-style-box :background-color color :corner-radius radius))

(defun make-screen ()
  (let ((screen (make-instance 'ui:panel
                               :name "screen" :preset :full-rect
                               :theme (make-instance 'ui:theme
                                                     :style-boxes `((ui:panel :panel
                                                                     ,(card '(0.25 0.3 0.4)))))))
        (badge (make-instance 'ui:panel :preset :bottom-right :preset-margin 8
                                        :custom-minimum-width 60 :custom-minimum-height 24)))
    (setf (ui:control-style-box screen :panel) (card '(0.1 0.1 0.12) :radius 0)
          (ui:control-style-box badge :panel) (card '(0.3 0.6 0.3)))
    (sc:add-child screen (make-instance 'ui:panel :preset :top-wide :preset-margin 8
                                                  :custom-minimum-height 32))
    (sc:add-child screen (make-instance 'ui:panel :preset :center
                                                  :custom-minimum-width 200
                                                  :custom-minimum-height 120))
    (sc:add-child screen badge)
    screen))

(defvar *tree* (s2d:run (make-screen) :title "Panels" :width 480 :height 320))

A bar runs along the top, a box sits in the centre and a badge in the bottom right corner. Resize the window and each keeps its place. The bar and the box draw the theme's style box for panels; the screen and the badge have their own. The theme is the screen's, a resource it holds, and *tree* the REPL's way in. Change the theme from the REPL, on the tree's thread, and the bar and the box follow at the next frame:

(defun screen (tree)
  (sc:find-node (sc:tree-root tree) "screen"))

(sc:call-in-tree *tree*
                 (lambda ()
                   (setf (ui:style-box-background-color
                          (ui:theme-style-box (ui:control-theme (screen *tree*)) 'ui:panel :panel))
                         '(0.5 0.3 0.2))))
  • Controls: control-x, control-y, control-width and control-height are a control's rectangle in its parent's coordinates, as laid out; it and its children draw from its top left. Setting them moves or resizes it at once. offsets-for-position gives the offsets that would put its top left at a point of its parent, keeping its size, as Godot's set_position works them out, to set through an editor's history; anchoring-size is the size its anchors are fractions of, its parent's, as Godot's get_parent_anchorable_rect.
  • Anchors and offsets place a control in its parent control, or in the window if its parent is not a control: each edge is anchored at a fraction of the parent's width or height, control-anchor-left and the rest, and sits control-offset-left and the rest units from there. set-layout-preset, or :preset and :preset-margin when making one, sets them for a corner, the middle of a side, the centre, a whole side or the whole parent.
  • Rotation, scale and pivot: as Godot's, a control and its children are turned by control-rotation, clockwise in radians, and stretched by control-scale, a gm:vec2, about control-pivot-offset, a point from its top left in its own units: T(position) T(pivot) R S T(-pivot). They are saved with a scene, the pointer reaches a control through them, and a turned control's clip is the box around it. A scale of 0 is taken as 0.00001, as Godot's. A container sets its children's rotation back to 0 and their scale to 1 by 1 as it places them, as Godot's fit_child_in_rect does, so the inspector shows those three of a container's child but does not edit them; res:property-read-only-p says so of an object's property, as Godot's _validate_property marks one read only.
  • Minimum sizes: a control is never laid out smaller than control-minimum-size, the larger of its control-custom-minimum-width and -height and what its content needs, which its class's control-content-minimum-size method says. Where its anchors give it less room, it grows as control-grow-horizontal and -vertical say. A class whose content changes size calls update-minimum-size.
  • Layout runs before each frame is drawn, after the tree's processing, and only for what asked for it: a property that places or sizes a control, a minimum size, a container's children, a theme, or the window's size. It finds minimum sizes bottom-up and rectangles top-down, emits resized from a control whose size changed, and allocates nothing in steady state. It runs in every mode, so the editor shows a game's user interface laid out.
  • Drawing: a control draws in its s2d:draw method, as any canvas item does, from the pixel nearest its top left, so that its edges are sharp even where its rectangle falls between pixels. It is drawn again when its size, its theme items or a theme changes.
  • Text painters: a control draws its text with one of pyssel/2d's painters, as control-text-painter says: :inherit, the default, as the nearest control or viewport above it says, viewport-text-painter for a viewport, popups' too, or :raster where none does; :raster, glyphs hinted to the display's pixels, the crispest for small upright text; :sdf, from multi-channel distance fields; or :slug, from the glyphs' curves. The last two stay sharp as a transform scales or turns the text, but are softer than raster text at small sizes. So (setf (control-text-painter hud) :sdf) draws a whole screen's text from distance fields, and one label below it may still say :raster. Text whose fields are still being made is drawn rasterized meanwhile. control-effective-text-painter says which painter a control draws with, and draw-control-text draws a layout as a control draws its text, for controls of one's own: each keeps its text's layout in a text-block, which make-text-block makes and text-block-for lays out, again only as the text, its font or its width changes, and into the layout it keeps, so that a short line laid out again allocates nothing. control-text-font is the font a control draws its text in.
  • Errors in a control's control-content-minimum-size or place-children stop that control, as an error in a callback stops a node, until code is redefined. An error in its draw keeps what it drew before, as for any canvas item; an error there while it behaves, or in its gui-input, stops it too, so that it takes no input. A game's control draws while its tree is edited, as every canvas item does, but takes no input then: the pointer passes over it. A control's setters declare their values' types, as an alignment's (member ...) or p2d:color, so a wrong value is refused as a type-error; what make-instance is given is checked by the property metaclass, in words read from the property's type (see Scene Tree).