Table of Contents
Containers
Containers are the controls that place their children themselves, as Godot's do: rows and columns, grids, margins, centring, panels, scrolling, splits, flows and tabs. The controls they place are those of User Interface.
-
Containers place their children themselves, ignoring their anchors, and need what their children need. Each child fills the space a container gives it, or takes its minimum size at the start, centre or end of it, as its sizing says:
control-size-horizontaland-vertical,:fillor:shrink-start,:shrink-centeror:shrink-end. The containers, Godot's:hbox-containerandvbox-containerlay their children out in a row or a column, the theme's:separationapart. Children that expand,control-expand-horizontalin a row andcontrol-expand-verticalin a column, share the space the others leave by theircontrol-stretch-ratio; when none does,box-container-alignmentputs them at the:start,:centeror:end.grid-containerlays its children out in rows ofgrid-container-columns, each column as wide as its widest child and each row as tall as its tallest,:h-separationand:v-separationapart. Columns and rows with an expanding child share the space left over.margin-containerkeeps its children inside the margins its constants:margin-left,:margin-top,:margin-rightand:margin-bottomsay;center-containercentres them at their minimum size;panel-containerdraws its style box:panelbehind them and keeps them inside its content margins.scroll-containershows part of larger content, below.hsplit-containerandvsplit-containerplace their two children side by side or one over the other, with a bar between that the pointer drags:split-container-offsetmoves it from where the children's sizing puts it, never past a child's minimum size, and it emitsdrag-started,draggedanddrag-ended.hflow-containerandvflow-containerlay their children out in lines that wrap where the next would not fit, as words in a paragraph, aligned byflow-container-alignment.tab-containershows one of its children, its pages, under atab-barwith a tab for each, titled by the page's name;tab-container-current-tabchooses the page, as does a click on a tab, and it emitstab-changed.
Boxes and grids share space out in whole units, rounding minimum sizes up, so that their children's edges fall on whole units, sharp at a whole scale. A container class of your own defines
place-children, which places each child withfit-child, which sets the child's rotation back to 0 and its scale to 1 by 1, as Godot'sfit_child_in_rectdoes, andcontrol-content-minimum-size, which adds up its children's, going through them withdo-child-controls. -
Scrolling: a
scroll-containergives its content at least its minimum size and at least its view, and shows it scrolled byscroll-container-scroll-xand-scroll-y, which layout keeps within the content, cut off inside its style box's content margins. Its scroll bars show where the view is, each whenscroll-container-horizontal-modeor-vertical-modesays::auto, when the content overflows;:show-always;:show-never, though it scrolls;:reserve, as:autobut keeping the bar's room; or:disabled, not scrolling that way at all, which makes the content's size there its minimum. The wheel scrolls it, an eighth of the view a step, across with Shift; one at its end leaves the wheel to the scroll container around it. Its bars drag, and a press on a track pages.ensure-control-visiblescrolls to a control inside, and withscroll-container-follow-focusit scrolls to one that gains the focus.
Containers lay controls out for you. Here a list scrolls down the left of the window and a grid of tiles fills the rest, inside a margin:
(defpackage #:layout
(:use #:cl)
(:local-nicknames (#:sc #:pyssel/scene) (#:s2d #:pyssel/scene-2d) (#:ui #:pyssel/ui)))
(in-package #:layout)
(defun rounded (color)
(make-instance 'ui:flat-style-box :background-color color :corner-radius 4))
(defun tile (color &rest initargs)
(let ((tile (apply #'make-instance 'ui:panel initargs)))
(setf (ui:control-style-box tile :panel) (rounded color))
tile))
(defun add (parent &rest children)
(dolist (child children parent)
(sc:add-child parent child)))
(defun make-layout ()
(let ((list (make-instance 'ui:vbox-container :name "list"))
(grid (make-instance 'ui:grid-container :name "grid" :columns 3 :expand-horizontal t)))
(dotimes (index 30)
(add list (tile '(0.3 0.4 0.6) :custom-minimum-height 28)))
(dotimes (index 9)
(add grid (tile '(0.6 0.45 0.3) :custom-minimum-height 60 :expand-horizontal t)))
(add (make-instance 'ui:margin-container
:name "layout" :preset :full-rect
:constant-overrides '(:margin-left 12 :margin-top 12
:margin-right 12 :margin-bottom 12))
(add (make-instance 'ui:hbox-container :name "columns")
(add (make-instance 'ui:scroll-container :name "scroll" :horizontal-mode :disabled
:custom-minimum-width 160)
list)
grid))))
(defvar *tree* (s2d:run (make-layout) :title "Layout" :width 640 :height 400))
The list's rows are more than the window holds, so its scroll bar shows.
Resize the window: the list keeps its width and the grid's columns share
the rest. The parts are nodes with names, found by their paths, and
*tree* the REPL's way in. Scroll the list from the REPL, by an offset or
to a row:
(defun part (tree path)
(sc:find-node (sc:tree-root tree) path))
(sc:call-in-tree *tree*
(lambda () (setf (ui:scroll-container-scroll-y (part *tree* "layout/columns/scroll"))
300)))
(sc:call-in-tree *tree*
(lambda ()
(ui:ensure-control-visible (part *tree* "layout/columns/scroll")
(sc:node-child (part *tree* "layout/columns/scroll/list")
25))))
An editor's docks are made of these. Here a tab container, its first
page a tree of a scene's nodes, and a flow of tiles sit either side of a
bar to drag, in a window of their own, which *docks* reaches:
(defun make-docks ()
(let ((flow (make-instance 'ui:hflow-container :name "flow" :expand-horizontal t))
(nodes (make-instance 'ui:tree :name "Scene" :hide-root t)))
(dotimes (index 12)
(add flow (tile '(0.45 0.6 0.3) :custom-minimum-width 80 :custom-minimum-height 60)))
(let ((root (ui:create-tree-item nodes)))
(dolist (name '("Player" "Camera" "Light"))
(let ((item (ui:create-tree-item nodes root)))
(setf (ui:tree-item-text item 0) name
(ui:tree-item-text (ui:create-tree-item nodes item) 0) "Sprite"))))
(sc:connect nodes 'ui:item-activated nil
(lambda (item column) (format t "Open ~A~%" (ui:tree-item-text item column))))
(add (make-instance 'ui:hsplit-container :name "docks" :preset :full-rect)
(add (make-instance 'ui:tab-container :custom-minimum-width 180
:drag-to-rearrange-enabled t)
nodes
(make-instance 'ui:label :name "Import" :text "How to import"))
flow)))
(defvar *docks* (s2d:run (make-docks) :title "Docks" :width 640 :height 400))
Drag the bar between them: the tiles reflow into the room left. Click
the tabs to switch pages, and drag one to move it. In the tree, the
arrows fold the nodes' children away, a click selects a node, and a
double click, or Return, opens it, as the function connected to
item-activated says. A
tab-bar on its own shows tabs without pages, from :tabs, a list of
tab objects, which make-tab makes, of a title, a tooltip, and
whether it is disabled or hidden, with close buttons if
tab-bar-close-button-policy asks for them; tab-bar-tab gives the one
at an index.
Getting started
The core
Drawing
Scenes
User interface
Games and the editor
- Playing a Game
- Projects
- The Editor
- The File System Dock
- The Script Screen
- The REPL
- The Debugger
- Lisp Text
- Code Services
- Project Manager
- Undo and Redo
- Exporting
The project