Class Block
START HERE.
Parent of every element in the system.
Introduction
(words/letters in bold in this section refer to the ones in the Fig. 1 below)
- A block is used to denote an element of a graphical user interface in this library (BLOCK), another famous name for this is widget but I will be using element / block interchangeable here.
- The coordinate system as it is in love2d.
- A rectangle we define by the coordinate of its upper-left corner and
its width
and height. I.e. it is a tuple (x, y, w, h).
In Lua a rectangle would be a table with 4 keys
x, y, w, h:local rectangle = { x = 10, y = 20, w = 100, h = 50, }
- The enclosing cell of a block is a rectangle which the block is placed in, (X, Y, W, H) . In order to place the block use its place method. NOTE: you are not supposed to use this method on every block in your GUI, you usually call it once on the root block. See Layout. So placing an element on the screen requires not only the coordinate but the whole rectangle. NOTE: place itself doesn't draw anything. Use the block's draw method to actually draw it.
likeliHUD was created with the following objectives:
- Declarative construction (similar to QML)
- Everything is a rectangle
- Automatic layout (but see the
offsetparameter below)
A typical user workflow consists of at least 3 steps when using this library
- Create
- Place
- Draw
Create
Declarative construction means you create a block tree which represents
your UI.
Blocks can have properties and embedded (children) blocks.
Properties go in the hash part of the block (in the end every block is a
Lua table)
i.e. key-value pairs; children elements is the array part of the block.
However, see the
inside property in the description of the new method. A simple example of
an object tree which has the following diagram (properties and their values
are written in the
parentheses)
A1 (a: 1) ├───B1 (b: 2) │ ├───B2 (b: 3) │ ├───B3 (b: 4) │ ├───B4 (b: 5) │ ├───C1 (c: 1) ├───C2 (c: 2)
can be created with the following code:
local root = A1 { a = 1, B1 { b = 2, B2 { b = 3, }, B3 { b = 4, }, B4 { b = 5, }, }, C1 { c = 1, C2 { c = 2, } } }
Place
This library treats every block (BLOCK) as a rectangle of some width a and height b placed inside its enclosing cell (X, Y, W, H). It may also have some pads pad and/or offset (Xo, Yo, Wo, Ho). Below you can see a figure which we'll be referring to in this documentation a lot:
Fig.1
Enclosing cell
┆
┆ offset
┆ ┆
(X, Y) ▼ ┆
■───────────────────────────────────┆────────────────────╮
│ ▲ ┆ │
│ Yo ┆ │
│ ▼ ▼ │
│ ■──────────────────────────────────┬──────╮ │
│ │ ▲ │ │ │
│◀︎ Xo ▶︎│ Pad │ │ │
│ │ (Xs, Ys) ▼ │ │ │
│ │ ■──────────────────╮ │ │ │
│ │ │ │ │ │ │
│ │◀︎ Pad ▶︎│ BLOCK b B │ │
│ │ │ │ │ Ho H
│ │ ╰────────a─────────╯ │ │ │
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
│ ├─────────────────A────────────────╯ │ │
│ │ │ │
│ ╰───────────────────Wo────────────────────╯ │
│ │
│ │
│ │
╰───────────────────────────W────────────────────────────╯
This is the code that corresponds to the figure:
local BLOCK = ui.Rectangle { w = a, h = b, offset = { x = Xo, y = Yo, w = Wo, h = Ho, }, pad = Pad, } BLOCK:place(X, Y, W, H)
One more time: to place an element you call its place method with the enclosing cell as an argument (actually it is 4 arguments, but you get the idea). Don't be afraid - you are not going to call this method on each element in your UI - usually only the root element is placed explicitly.
Draw
In order to draw an element call its draw method. Similarly to place you usually call it once (per frame) for the root element.
See below for details.
Class Block
| block:new () | Constructor. |
| block:size (includePad) | Returns the size of the block. |
| block:cell () | Enclosing cell. |
| block:place (x, y, w, h) | Places the block within specified enclosing cell. |
| block:draw () | Draws the block. |
| block:traverse () | Block iterator. |
| block:mousemoved (x, y, dx, dy, istouch) | Mouse movement handler. |
| block:mousepressed (x, y, button, istouch, presses) | Mouse press handler. |
| block:mousereleased (x, y, button, istouch, presses) | Mouse release handler. |
Events
| block:push (event) | Pushes an event to the block and to all of its child down the tree. |
| block:filter (event) | It says whether the event should propagate further down the tree or not. |
User defined blocks
| block:doDraw () | A block must override this function to be drawn. |
| block:doPlace (x, y, w, h) | Placement. |
| block:doMousemoved (x, y, _, _, _) | Mouse moved. |
| block:doMousepressed (_, _, _, _, _) | Mouse pressed. |
| block:doMousereleased (_, _, _, _, _) | Mouse released. |
Class Block
- block:new ()
-
Constructor.
The user is not supposed to explicitly call this method. It's called
automatically when an
instance of Block is created. See
here for details.
When created a block can be given properties. The list of the properties
below is common for
all blocks in this library. NOTE: though can be given some properties
might be ignored by
a block either because of the type of the block and/or different
circumstances. F.e. the
fillproperty only makes sense when the block is placed inside a Layout.visible: boolean.true(default) means the element is visible (drawn). Otherwise it is not visiblecolor: a color like in love2d (the table variant). For different type of a block it has different meaning. See concrete type. Default is nil.pad: a non-negative number. The minimum gap between the block's border and the enclosing cell (see Fig. 1). It can be greater than the pad because of theoffsetparameter. NOTE: the pad along the x-axis is eqaul to the pad along the y-axis.align: Alignment of the block inside its enclosing cell. A string containing one or two words'center'(default),'top', 'bottom', 'left', 'right'delimited by the+sign. For example,'top+right'(equivalent to'right+top'). NOTE: this string must be consistent: it doesn't make sense to have something like'top+bottom'('top'will be set), or'left+right'('right'will be set). In other words there are only 9 options of alignment (* means default):Fig.2 ╭────────────────┬────────────┬────────────────╮ │ top + left │ top │ top + right │ │ │ │ │ │ │ │ │ ├────────────────┼────────────┼────────────────┤ │ │ │ │ │ left │ center* │ right │ │ │ │ │ ├────────────────┼────────────┼────────────────┤ │ │ │ │ │ │ │ │ │ bottom + left │ bottom │ bottom + right │ ╰────────────────┴────────────┴────────────────╯fill: a table of the form{ x = boolean, y = boolean }. Tells whether the block should occupy free space (along the corresponding axis) if placed inside a Layout. See Layout:new.offset: a rectangle (Xo, Yo, Wo, Ho). Modifies the enclosing cell (X, Y, W, H) of the element (which was given by calling the place method) by adding (Xo, Yo) to (X, Y) and overriding (W, H) by (Wo, Ho). In other words, placing the element with theoffsetfield (Xo, Yo, Wo, Ho) inside the cell (X, Y, W, H) is the same as placing this element inside the cell (X + Xo, Y + Yo, Wo, Ho). See Fig. 1. Usually used together with theinsideelement (see below).-- Rectangle is a Block local element = ui.Rectangle { w = 100, h = 100, offset = { x = 10, y = 20, w = 200, h = 200, } } element:place(10, 10, 300, 300) -- actually placed in (20, 30, 200, 200)inside: a block. Special property which value must be a block. Let us explain the meaning of theinsideproperty looking at the following example:local UI = ui.Rectangle { id = 'A' w = 100, h = 50, inside = { ui.Rectangle { -- the enclosing cell of this element is A itself id = 'B', w = 60, h = 30, } } } UI:place(20, 20, 200, 200)which produces the following diagram:
Fig.3 Enclosing cell of A ┆ ┆ ┆ Enclosing cell of B (0, 0) ┆ ┆ ■──────────────────────────┆───────┆─────────────────╮ │ ▲ ┆ ┆ │ │ 20 ┆ ┆ │ │ ▼ ▼ ┆ │ │ ■──────────────────────────┆──────────╮ │ │ │ ┆ │ │ │◀︎ 20 ▶︎│ ┆ │ │ │ │ ▼ │ │ │ │ ╭────────────────────╮ │ │ │ │ │A │ │ │ │ │ │ ╭────────────╮ │ │ │ │ │ │ │B │ 50 │ │ │ │ │ │ 30 │ 200 H (screen) │ │ │ ╰─────60─────╯ │ │ │ │ │ │ │ │ │ │ │ ╰────────100─────────╯ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────200───────────────╯ │ │ │ │ │ │ │ ╰───────────────────────────W (screen)───────────────╯as you can see now the enclosing cell of B is A itself, namely, (A.x, A.y, A.w, A.h). In other words B is automatically placed inside A. This property together with the
offsetone makes it possible to integrate elements (usually Label) into images. F.e., suppose you drew a health bar image:╭─────────────────╮ │■───────────────■│◀︎- cool health bar image ││ area for text ││ │■───────────────■│ ╰─────────────────╯and you want the text showing the health is always centered inside this image wherever the bar is placed. All you need is to place a Label inside this image with the right offset:
local hp = ui.Image { path = 'path/to/image.png', inside = { ui.Label { text = '100', offset = { -- area for text } } } }Now wherever
hpis placed the text inside it is placed correctly automatically.filter : a function. It is supposed to filter the events if pushed to the element. See the Events section for details.
mouse: a table or boolean. Used to make a block react to mouse events (can be used to make buttons). A boolean used by some basic blocks in this library. In most cases this is a table containing mouse callbacks. There are 4 mouse callbacks available:onExit: called each time the mouse (cursor) leaves the blockonEnter: called each time the mouse enters the blockonPress: called each time the mouse is pressed inside the blockonClicked: called each time the mouse is pressed and released inside the block
The mouse actually is an FSM (wiki) which diagram you can see below:
Fig.4 Mouse FSM outside/onExit * ┌───────────────────────────────┐ ┌─────┐ │ │ │ ┌────▶State├───┐ │ │ │ │ └─────┘ │ │ ┌───▼─────┐ inside/onEnter ┌──┴──────┐ │ │ │ │ Idle │──────────────────────▶ Hover │ │ event/action │ └─▶(initial)│ │ │ └──────────────┘ └─▲───────┘ └───┬───▲─┘ │ press/onPressed │ │ │ ┌─────────────┘ │ │ │ │ │ │ │ │ ┌─▼─────────┐ │release/onClick │ outside/onExit │ Pressed │ │ └──────────────────│ ├───────┘ └───────────┘So there are 4 events managing the mouse FSM:
pressinsideoutsiderelease
Each mouse callback accepts up to 1 (one) argument: if passed it is supposed to be the block itself (
self). This is how it can be used:ui.Rectangle { w = 200, h = 200, color = { 1, 0, 0 }, mouse = { onExit = function () print('Bye, cursor !') end, onEnter = function () print('Hello, cursor !') end, onPress = function (self) self.color = { 0, 0, 1 } end, onClick = function (self) self.color = { 0, 1, 0 } end, } }Note how in the example above the
onExitandonEntercallbacks don't have parameters. But each time the mouse is pressed or clicked inside the rectangleonPressandonClickare used to change thecolorproperty of the rectangle. - block:size (includePad)
-
Returns the size of the block.
The size of the block is the minimum rectangle which the block can be
placed in,
in the form of a table
{ x = width, y = height }.Parameters:
- includePad
If
truethen the pads of the block are included (AxB in Fig. 1). Ifnil(default) orfalsethe pads are not included (axb in the figure above)
Returns:
-
A table of the form
{ x = width, y = height } - includePad
If
- block:cell ()
-
Enclosing cell.
Returns the enclosing cell of the block.
Returns:
-
The enclosing cell rectangle i.e. a table of the form
{ x = cell.x, y = cell.y, w = cell.w, h = cell.h }, ornil. NOTE: the enclosing cell is initialized only after the block was placed by calling its place method. Otherwise it staysnil.Usage:
local r = ui.Rectangle { w = 100, h = 200, } local c = r:cell() --> c == nil r:place(10, 10, 300, 300) local c = r:cell() --> c = { x = 10, y = 10, w = 300, h = 300 }
- block:place (x, y, w, h)
-
Places the block within specified enclosing cell.
Parameters:
- x x coordinate of the cell (X in the Fig. 1)
- y y coordinate of the cell (Y in the Fig. 1)
- w width of the cell (W in the Fig. 1)
- h height of the cell (H in the Fig. 1)
- block:draw ()
- Draws the block. Used to draw the block. Usually you don't want to call it on every block in your GUI - only once for each root element.
- block:traverse ()
-
Block iterator.
Used to iterate over blocks in the hierarchy.
Usage:
local UI = ui.Layout { rows = 1, columns = 2, ui.Label { text = 'foo' }, ui.Label { text = 'bar' } } for i, b in UI:traverse() do b.border = true end
- block:mousemoved (x, y, dx, dy, istouch)
-
Mouse movement handler.
. Parameters are the same as in
love.mousemoved
Parameters:
- x
- y
- dx
- dy
- istouch
- block:mousepressed (x, y, button, istouch, presses)
-
Mouse press handler.
Mouse press handler. Parameters are the same as in
love.mousepressed
Parameters:
- x
- y
- button
- istouch
- presses
- block:mousereleased (x, y, button, istouch, presses)
-
Mouse release handler.
Mouse release handler. Parameters are the same as in
love.mousereleased
Parameters:
- x
- y
- button
- istouch
- presses
Events
Events. An Event is any table with the mandatory id key and optional
data. For example, this table can be an Event:
local event = {
id = 'button.pressed',
key = 'space',
}
The block which is supposed to react on events must have the
corresponding on property of the following structure:
local l = ui.Label {
text = 'hello',
on = {
['text.changed'] = function (self, event)
self.text = event.message
end
}
}
i.e. it is a table where keys are events' IDs and values are
callbacks to those events.
A callback accepts two arguments: the block itself (self), and the event.
To send an event to a block you call the push method on the block with the event as the only argument:
l:push { id = 'text.changed', message = 'world' }
This call will propagate the event to
the label and call the corresponding function with 2 arguments:
self is the label object, and the event itself.
NOTE: this means you cannot easily push an event to a non-root block. Also it means that triggering an event propagation from one block to another is not straightforward: you probably want to emit a signal from one block which in turn will push the corresponding event to the root element. See the buttons.lua for an example.
See the events.lua file for an example.
- block:push (event)
-
Pushes an event to the block and to all of its child down the tree.
Parameters:
- event
A table. NOTE: it MUST have the
idkey which is often just a string.
- event
A table. NOTE: it MUST have the
- block:filter (event)
-
It says whether the event should propagate further down the tree or not.
And also can be used to modify the event: so the children blocks will get
a modified event.
Parameters:
- event Event to be passed. NOTE: DON'T modify this event inside this function (if overriden). If you want to modify the event create a new one inside this function and return it (see the 2nd return value).
Returns:
-
A boolean.
truemeans thateventpropagates down the tree (i.e. to the children).falsemeans the event is filtered out and the propagation breaks. -
[optional] A table. If returned it MUST be a new event table.
This new event will replace the original one (but only
for the subtree starting from
self). NOTE: you can either pass this function as a property in the constructor or override the method. See events.lua for an example.
User defined blocks
- block:doDraw ()
- A block must override this function to be drawn. Does nothing by default.
- block:doPlace (x, y, w, h)
-
Placement.
A block should override this function to be placed.
Used to place the block within specified enclosing cell.
Parameters:
- x x coordinate of the window
- y y coordinate of the window
- w width of the window
- h height of the window
- block:doMousemoved (x, y, _, _, _)
-
Mouse moved.
A block can override this function.
⚠️ Care must be taken when overriding this function. As stated in Block:new this function manages the internal mouse FSM - if overridden incorrectly it can cause inconsistent behaviour. If you need to override this function you probably want to doMousepressed and doMousereleased also.
See Block:mousemoved for parameter description.
Parameters:
- x
- y
- _
- _
- _
- block:doMousepressed (_, _, _, _, _)
-
Mouse pressed.
A block can override this function.
⚠️ Care must be taken when overriding this function. As stated in Block:new this function manages the internal mouse FSM - if overridden incorrectly it can cause inconsistent behaviour. If you need to override this function you probably want to doMousemoved and doMousereleased also.
See Block:mousepressed for parameter description.
Parameters:
- _
- _
- _
- _
- _
- block:doMousereleased (_, _, _, _, _)
-
Mouse released.
A block can override this function.
⚠️ Care must be taken when overriding this function. As stated in Block:new this function manages the internal mouse FSM - if overridden incorrectly it can cause inconsistent behaviour. If you need to override this function you probably want to doMousemoved and doMousepressed also.
See Block:mousereleased for parameter description.
Parameters:
- _
- _
- _
- _
- _