Fix #17: Support multiple instances
This commit is contained in:
parent
f77ab8e5e8
commit
aca8a297bb
14 changed files with 385 additions and 244 deletions
|
@ -76,23 +76,23 @@ Clears GUI state when exiting a frame.
|
|||
GUI State
|
||||
^^^^^^^^^
|
||||
|
||||
.. function:: anyHot()
|
||||
.. function:: anyHovered()
|
||||
|
||||
:returns: ``true`` if any widget is in the ``hot`` state.
|
||||
:returns: ``true`` if any widget is hovered by the mouse.
|
||||
|
||||
Checks if any widget is in the hot state
|
||||
Checks if any widget is hovered by the mouse.
|
||||
|
||||
.. function:: isHot(id)
|
||||
.. function:: isHovered(id)
|
||||
|
||||
:param mixed id: Identifier of the widget.
|
||||
:returns: ``true`` if the widget is in the ``hot`` state.
|
||||
:returns: ``true`` if the widget is hovered by the mouse.
|
||||
|
||||
Checks if the widget identified by ``id`` is hovered by the mouse.
|
||||
|
||||
.. function:: wasHot(id)
|
||||
.. function:: wasHovered(id)
|
||||
|
||||
:param mixed id: Identifier of the widget.
|
||||
:returns: ``true`` if the widget was in the ``hot`` state in the last frame.
|
||||
:returns: ``true`` if the widget was in the hovered by the mouse in the last frame.
|
||||
|
||||
Checks if the widget identified by ``id`` was hovered by the mouse in the last frame.
|
||||
|
||||
|
@ -121,7 +121,7 @@ Checks whether the mouse cursor is in the rectangle defined by ``x,y,w,h``.
|
|||
:param function hit: Function to perform the hit test.
|
||||
|
||||
Registers a hit-test defined by the function ``hit`` for the widget identified
|
||||
by ``id``. Sets the widget to ``hot`` if th hit-test returns ``true``. Sets the
|
||||
by ``id``. Sets the widget to ``hovered`` if th hit-test returns ``true``. Sets the
|
||||
widget to ``active`` if the hit-test returns ``true`` and the mouse button is
|
||||
pressed.
|
||||
|
||||
|
@ -186,3 +186,36 @@ Checks whether the widget identified by ``id`` currently has keyboard focus.
|
|||
Checks whether the key ``key`` was pressed while the widget identified by
|
||||
``id`` has keyboard focus.
|
||||
|
||||
|
||||
Instancing
|
||||
----------
|
||||
|
||||
.. function:: new()
|
||||
|
||||
:returns: Separate UI state.
|
||||
|
||||
Create a separate UI and layout state. Everything that happens in the new
|
||||
state will not affect any other state. You can use the new state like the
|
||||
"global" state ``suit``, but call functions with the colon syntax instead of
|
||||
the dot syntax, e.g.::
|
||||
|
||||
function love.load()
|
||||
dress = suit.new()
|
||||
end
|
||||
|
||||
function love.update()
|
||||
dress.layout:reset()
|
||||
dress:Label("Hello, World!", dress.layout:row(200,30))
|
||||
dress:Input(input, dress.layout:row())
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
dress:draw()
|
||||
end
|
||||
|
||||
.. warning::
|
||||
|
||||
Unlike UI and layout state, the theme might be shared with other states.
|
||||
Changes in a shared theme will be shared across all themes.
|
||||
See the :ref:`Instance Theme <instance-theme>` subsection in the
|
||||
:doc:`gettingstarted` guide.
|
||||
|
|
|
@ -93,7 +93,7 @@ draw it in ``love.draw()``::
|
|||
end
|
||||
|
||||
function love.draw()
|
||||
suit.core.draw()
|
||||
suit.draw()
|
||||
end
|
||||
|
||||
This will produce this UI (after clicking the button):
|
||||
|
@ -159,11 +159,11 @@ and ``textinput`` events to SUIT::
|
|||
|
||||
-- forward keyboard events
|
||||
function love.textinput(t)
|
||||
suit.core.textinput(t)
|
||||
suit.textinput(t)
|
||||
end
|
||||
|
||||
function love.keypressed(key)
|
||||
suit.core.keypressed(key)
|
||||
suit.keypressed(key)
|
||||
end
|
||||
|
||||
.. image:: _static/keyboard.gif
|
||||
|
@ -193,33 +193,34 @@ The first example can be written as follows::
|
|||
function love.update(dt)
|
||||
-- put the layout origin at position (100,100)
|
||||
-- cells will grow down and to the right of the origin
|
||||
suit.layout.reset(100,100)
|
||||
-- note the colon syntax
|
||||
suit.layout:reset(100,100)
|
||||
|
||||
-- put 10 extra pixels between cells in each direction
|
||||
suit.layout.padding(10,10)
|
||||
suit.layout:padding(10,10)
|
||||
|
||||
-- construct a cell of size 300x30 px and put the button into it
|
||||
if suit.Button("Hello, World!", suit.layout.row(300,30)).hit then
|
||||
if suit.Button("Hello, World!", suit.layout:row(300,30)).hit then
|
||||
show_message = true
|
||||
end
|
||||
|
||||
-- add another cell below the first cell
|
||||
-- the size of the cell is the same as the first cell
|
||||
if show_message then
|
||||
suit.Label("How are you today?", suit.layout.row())
|
||||
suit.Label("How are you today?", suit.layout:row())
|
||||
end
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
suit.core.draw()
|
||||
suit.draw()
|
||||
end
|
||||
|
||||
.. image:: _static/layout.gif
|
||||
|
||||
At the beginning of each frame, the layout origin (and some internal layout
|
||||
state) has to be reset. You can also define optional padding between cells.
|
||||
Cells are added using ``layout.row(w,h)`` (which puts the new cell below the
|
||||
old cell) and ``layout.col(w,h)`` (which puts the new cell to the right of the
|
||||
Cells are added using ``layout:row(w,h)`` (which puts the new cell below the
|
||||
old cell) and ``layout:col(w,h)`` (which puts the new cell to the right of the
|
||||
old cell). If omitted, the width and height of the new cell are copied from
|
||||
the old cell. There are also special identifiers that calculate the size from
|
||||
the sizes of all cells that were created since the last ``reset()``: ``max``,
|
||||
|
@ -235,31 +236,90 @@ Themeing
|
|||
|
||||
SUIT lets you customize how any widget (except :func:`ImageButton`) is drawn.
|
||||
Each widget (except, :func:`you know <ImageButton>`) is drawn by a function in
|
||||
the table ``suit.core.theme``. Conveniently, the name of the function
|
||||
the table ``suit.theme``. Conveniently, the name of the function
|
||||
responsible for drawing a widget is named after it, so, a button is drawn by
|
||||
the function ``suit.core.theme.Button``. If you want to change how a button is
|
||||
the function ``suit.theme.Button``. If you want to change how a button is
|
||||
drawn, simply overwrite the function. If you want to redecorate completely, it
|
||||
might be easiest to start from scratch and swap the whole table.
|
||||
|
||||
However, if you just don't like the colors, the default theme is open to change.
|
||||
It requires you to change the background (``bg``) and foreground (``fg``) color
|
||||
of three possible widget states: ``normal``, when nothing out of
|
||||
the ordinary happened, ``hover``, when the mouse hovers above a widget, and
|
||||
the ordinary happened, ``hovered``, when the mouse hovers above a widget, and
|
||||
``active``, when the mouse hovers above, and the mouse button is pressed (but
|
||||
not yet released) on the widget. The colors are saved in the table
|
||||
``suit.core.theme.color``. The default color scheme is this::
|
||||
``suit.theme.color``. The default color scheme is this::
|
||||
|
||||
suit.core.theme.color = {
|
||||
normal = {bg = { 66, 66, 66}, fg = {188,188,188}},
|
||||
hover = {bg = { 50,153,187}, fg = {255,255,255}},
|
||||
active = {bg = {255,153, 0}, fg = {225,225,225}}
|
||||
suit.theme.color = {
|
||||
normal = {bg = { 66, 66, 66}, fg = {188,188,188}},
|
||||
hovered = {bg = { 50,153,187}, fg = {255,255,255}},
|
||||
active = {bg = {255,153, 0}, fg = {225,225,225}}
|
||||
}
|
||||
|
||||
You can also do minimally invasive surgery::
|
||||
|
||||
function love.load()
|
||||
suit.core.theme.color.normal.fg = {255,255,255}
|
||||
suit.core.theme.color.hover = {bg = {200,230,255}, fg = {0,0,0}}
|
||||
suit.theme.color.normal.fg = {255,255,255}
|
||||
suit.theme.color.hovered = {bg = {200,230,255}, fg = {0,0,0}}
|
||||
end
|
||||
|
||||
|
||||
GUI Instances
|
||||
-------------
|
||||
|
||||
Sometimes you might feel the need to separate parts of the GUI. Maybe the
|
||||
widgets should have a different theme, maybe certain should always be drawn
|
||||
before or after other UI elements, or maybe you don't want the UI state to
|
||||
"leak" (e.g., from a stacked pause gamestate to the main gamestate).
|
||||
|
||||
For this reason, SUIT allows you to create GUI instances::
|
||||
|
||||
local dress = suit.new()
|
||||
|
||||
The IO and layout state of ``dress`` is totally contained in the instance and
|
||||
does not affect any other instances (including the "global" instance ``suit``).
|
||||
In particular, ``suit.draw()`` will not draw anything from ``dress``. Luckily,
|
||||
you can do that yourself::
|
||||
|
||||
dress:draw()
|
||||
|
||||
Notice that instances require that you use the colon syntax. This is true for
|
||||
every `core <core>` function as well as the widgets. To create a button, for
|
||||
example, you have to write::
|
||||
|
||||
dress:Button("Click?", dress.layout:row())
|
||||
|
||||
.. _instance-theme:
|
||||
|
||||
Instance Theme
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Unlike UI and layout state, themes **are** shared among instances. The reason
|
||||
is that the ``suit.theme`` and ``dress.theme`` are **references**, and point to
|
||||
the same table (unless you make either of them point somewhere else). Usually
|
||||
this is a feature, but please still consider this
|
||||
|
||||
.. warning::
|
||||
|
||||
Changes in a shared theme will be shared across GUI instances.
|
||||
|
||||
If this is an issue---for example because you only want to change the color
|
||||
scheme of an instance---you can either `deep-copy
|
||||
<http://hump.readthedocs.org/en/latest/class.html#class:clone>`_ the theme
|
||||
table or use some metatable magic::
|
||||
|
||||
dress.theme = setmetatable({}, {__index = suit.theme})
|
||||
|
||||
-- NOTE: you have to replace the whole color table. E.g., replacing only
|
||||
-- dress.theme.color.normal will also change suit.theme.color.normal!
|
||||
dress.theme.color = {
|
||||
normal = {bg = {188,188,188}, fg = { 66, 66, 66}},
|
||||
hovered = {bg = {255,255,255}, fg = { 50,153,187}},
|
||||
active = {bg = {255,255,255}, fg = {225,153, 0}}
|
||||
}
|
||||
|
||||
function dress.theme.Label(text, opt, x,y,w,h)
|
||||
-- draw the label in a fancier way
|
||||
end
|
||||
|
||||
.. [1] But it thinks you can handle that.
|
||||
|
|
|
@ -49,7 +49,7 @@ The following code will create this UI:
|
|||
-- generate some assets (below)
|
||||
function love.load()
|
||||
snd = generateClickySound()
|
||||
normal, hover, active = generateImageButton()
|
||||
normal, hovered, active = generateImageButton()
|
||||
smallerFont = love.graphics.newFont(10)
|
||||
end
|
||||
|
||||
|
@ -63,11 +63,11 @@ The following code will create this UI:
|
|||
-- put the layout origin at position (100,100)
|
||||
-- cells will grown down and to the right from this point
|
||||
-- also set cell padding to 20 pixels to the right and to the bottom
|
||||
suit.layout.reset(100,100, 20,20)
|
||||
suit.layout:reset(100,100, 20,20)
|
||||
|
||||
-- put a button at the layout origin
|
||||
-- the cell of the button has a size of 200 by 30 pixels
|
||||
state = suit.Button("Click?", suit.layout.row(200,30))
|
||||
state = suit.Button("Click?", suit.layout:row(200,30))
|
||||
|
||||
-- if the button was entered, play a sound
|
||||
if state.entered then love.audio.play(snd) end
|
||||
|
@ -78,67 +78,67 @@ The following code will create this UI:
|
|||
-- put an input box below the button
|
||||
-- the cell of the input box has the same size as the cell above
|
||||
-- if the input cell is submitted, print the text
|
||||
if suit.Input(input, suit.layout.row()).submitted then
|
||||
if suit.Input(input, suit.layout:row()).submitted then
|
||||
print(input.text)
|
||||
end
|
||||
|
||||
-- put a button below the input box
|
||||
-- the width of the cell will be the same as above, the height will be 40 px
|
||||
if suit.Button("Hover?", suit.layout.row(nil,40)).hovered then
|
||||
if suit.Button("Hover?", suit.layout:row(nil,40)).hovered then
|
||||
-- if the button is hovered, show two other buttons
|
||||
-- this will shift all other ui elements down
|
||||
|
||||
-- put a button below the previous button
|
||||
-- the cell height will be 30 px
|
||||
-- the label of the button will be aligned top left
|
||||
suit.Button("You can see", {align='left', valign='top'}, suit.layout.row(nil,30))
|
||||
suit.Button("You can see", {align='left', valign='top'}, suit.layout:row(nil,30))
|
||||
|
||||
-- put a button below the previous button
|
||||
-- the cell size will be the same as the one above
|
||||
-- the label will be aligned bottom right
|
||||
suit.Button("...but you can't touch!", {align='right', valign='bottom'},
|
||||
suit.layout.row())
|
||||
suit.layout:row())
|
||||
end
|
||||
|
||||
-- put a checkbox below the button
|
||||
-- the size will be the same as above
|
||||
-- (NOTE: height depends on whether "Hover?" is hovered)
|
||||
-- the label "Check?" will be aligned right
|
||||
suit.Checkbox(chk, {align='right'}, suit.layout.row())
|
||||
suit.Checkbox(chk, {align='right'}, suit.layout:row())
|
||||
|
||||
-- put a nested layout
|
||||
-- the size of the cell will be as big as the cell above or as big as the
|
||||
-- nested content, whichever is bigger
|
||||
suit.layout.push(suit.layout.row())
|
||||
suit.layout:push(suit.layout:row())
|
||||
|
||||
-- put a slider in the cell
|
||||
-- the inner cell will be 160 px wide and 20 px high
|
||||
suit.Slider(slider, suit.layout.col(160, 20))
|
||||
suit.Slider(slider, suit.layout:col(160, 20))
|
||||
|
||||
-- put a label that shows the slider value to the right of the slider
|
||||
-- the width of the label will be 40 px
|
||||
suit.Label(("%.02f"):format(slider.value), suit.layout.col(40))
|
||||
suit.Label(("%.02f"):format(slider.value), suit.layout:col(40))
|
||||
|
||||
-- close the nested layout
|
||||
suit.layout.pop()
|
||||
suit.layout:pop()
|
||||
|
||||
-- put an image button below the nested cell
|
||||
-- the size of the cell will be 200 by 100 px,
|
||||
-- but the image may be bigger or smaller
|
||||
-- the button shows the image `normal' when the mouse is outside the image
|
||||
-- or above a transparent pixel
|
||||
-- the button shows the image `hover` if the mouse is above an opaque pixel
|
||||
-- the button shows the image `hovered` if the mouse is above an opaque pixel
|
||||
-- of the image `normal'
|
||||
-- the button shows the image `active` if the mouse is above an opaque pixel
|
||||
-- of the image `normal' and the mouse button is pressed
|
||||
suit.ImageButton(normal, {hover = hover, active = active}, suit.layout.row(200,100))
|
||||
suit.ImageButton(normal, {hovered = hovered, active = active}, suit.layout:row(200,100))
|
||||
|
||||
-- if the checkbox is checked, display a precomputed layout
|
||||
if chk.checked then
|
||||
-- the precomputed layout will be 3 rows below each other
|
||||
-- the origin of the layout will be at (400,100)
|
||||
-- the minimal height of the layout will be 300 px
|
||||
rows = suit.layout.rows{pos = {400,100}, min_height = 300,
|
||||
rows = suit.layout:rows{pos = {400,100}, min_height = 300,
|
||||
{200, 30}, -- the first cell will measure 200 by 30 px
|
||||
{30, 'fill'}, -- the second cell will be 30 px wide and fill the
|
||||
-- remaining vertical space between the other cells
|
||||
|
@ -165,17 +165,17 @@ The following code will create this UI:
|
|||
|
||||
function love.draw()
|
||||
-- draw the gui
|
||||
suit.core.draw()
|
||||
suit.draw()
|
||||
end
|
||||
|
||||
function love.textinput(t)
|
||||
-- forward text input to SUIT
|
||||
suit.core.textinput(t)
|
||||
suit.textinput(t)
|
||||
end
|
||||
|
||||
function love.keypressed(key)
|
||||
-- forward keypressed to SUIT
|
||||
suit.core.keypressed(key)
|
||||
suit.keypressed(key)
|
||||
end
|
||||
|
||||
-- generate assets (see love.load)
|
||||
|
@ -203,11 +203,11 @@ The following code will create this UI:
|
|||
end
|
||||
end
|
||||
|
||||
local normal, hover, active = love.image.newImageData(200,100), love.image.newImageData(200,100), love.image.newImageData(200,100)
|
||||
local normal, hovered, active = love.image.newImageData(200,100), love.image.newImageData(200,100), love.image.newImageData(200,100)
|
||||
normal:mapPixel(metaballs(.48, 188,188,188))
|
||||
hover:mapPixel(metaballs(.46, 50,153,187))
|
||||
hovered:mapPixel(metaballs(.46, 50,153,187))
|
||||
active:mapPixel(metaballs(.43, 255,153,0))
|
||||
return love.graphics.newImage(normal), love.graphics.newImage(hover), love.graphics.newImage(active)
|
||||
return love.graphics.newImage(normal), love.graphics.newImage(hovered), love.graphics.newImage(active)
|
||||
end
|
||||
|
||||
Indices and tables
|
||||
|
|
|
@ -93,9 +93,9 @@ The specification is a table of tables, where each inner table follows the
|
|||
convention of :func:`row` and :func:`col`.
|
||||
The result is a layout definition object that can be used to access the cells.
|
||||
|
||||
There is almost only one reason to do so: You know the area of your layout in
|
||||
advance (say, the screen size), and want certain cells to dynamically fill the
|
||||
available space.
|
||||
There are almost only two reasons to do so: (1) You know the area of your
|
||||
layout in advance (say, the screen size), and want certain cells to dynamically
|
||||
fill the available space; (2) You want to animate the cells.
|
||||
|
||||
.. note::
|
||||
Unlike immediate mode layouts, predefined layouts **can not be nested**.
|
||||
|
@ -139,8 +139,7 @@ define the position (upper left corner) of the layout using the ``pos`` keyword:
|
|||
Layout Definition Objects
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Once constructed, the layout can be executed using a layout definition object
|
||||
in two ways:
|
||||
Once constructed, the cells can be accessed in two ways:
|
||||
|
||||
- Using iterators::
|
||||
|
||||
|
@ -154,6 +153,27 @@ in two ways:
|
|||
suit.Button("Button 3", definition.cell(3))
|
||||
suit.Button("Button 2", definition.cell(2))
|
||||
|
||||
There is actually a third way: Because layout definitions are just tables, you
|
||||
can access the cells directly::
|
||||
|
||||
local cell = definition[1]
|
||||
suit.Button("Button 1", cell[1], cell[2], cell[3], cell[4])
|
||||
-- or suit.Button("Button 1", unpack(cell))
|
||||
|
||||
This is especially useful if you want to animate the cells, for example with a
|
||||
`tween <http://hump.readthedocs.org/en/latest/timer.html#Timer.tween>`_::
|
||||
|
||||
for i,cell in ipairs(definition)
|
||||
local destination = {[2] = cell[2]} -- save cell y position
|
||||
cell[2] = -cell[4] -- move cell just outside of the screen
|
||||
|
||||
-- let the cells fall into the screen one after another
|
||||
timer.after(i / 10, function()
|
||||
timer.tween(0.7, cell, destination, 'bounce')
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
Constructors
|
||||
^^^^^^^^^^^^
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ theme.
|
|||
The argument ``normal`` defines the image of the normal state as well as the
|
||||
area of the widget: The button activates when the mouse is over a pixel with
|
||||
non-zero alpha value.
|
||||
You can provide additional ``hover`` and ``active`` images, but the widget area
|
||||
You can provide additional ``hovered`` and ``active`` images, but the widget area
|
||||
is always computed from the ``normal`` image.
|
||||
|
||||
Note that ``ImageButton`` does not recieve width and height parameters. As
|
||||
|
@ -52,11 +52,11 @@ such, it does not necessarily honor the cell size of a :doc:`layout`.
|
|||
``normal``
|
||||
Image for the normal state of the widget. Defaults to widget payload.
|
||||
|
||||
``hover``
|
||||
Image for the hot state of the widget. Defaults to ``normal`` if omitted.
|
||||
``hovered``
|
||||
Image for the hovered state of the widget. Defaults to ``normal`` if omitted.
|
||||
|
||||
``active``
|
||||
Image for the active state of the widget. Defaults to ``hover`` if omitted.
|
||||
Image for the active state of the widget. Defaults to ``hovered`` if omitted.
|
||||
|
||||
Mutable Widgets
|
||||
---------------
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue