Glove is a widget library for LÖVE (love2d) which is a framework for building 2D games in Lua. It defines many commonly used widgets and a layout system.
To run the demo, enter love src
.
To include Glove in your project,
copy the glove
directory found in the src
directory into it.
For example code, see the main*.lua
files in the src
directory.
Glove defines the following colors that can be accessed with
Glove.color.{color-name}
:
black
, blue
, brown
, gray
, green
, orange
,
purple
, red
, white
, and yellow
.
Glove defines the following fonts that can be accessed with
Glove.font.{font-name}
:
default12
default18
default30
Each of the currently supported graphical widgets are described below. Many of them display a green outline when the mouse cursor hovers over them.
This widget is a clickable button.
The parameters are:
- text to display on the button
- table of options
The supported options are:
buttonColor
: background color of the button; defaults to whitefont
: font used for the button labellabelColor
: color of the label; defaults to blackonClick
: function called when the button is clicked
For example:
local button = Glove.Button("Press Me", {
buttonColor = Glove.colors.red,
font = Glove.fonts.default18,
labelColor = Glove.colors.yellow,
onClick = function()
print("got click")
end
})
This widget ties a checkbox state to a boolean value in a table.
The parameters are:
- text to display after the checkbox
- table that holds its state
- key within the table that holds its state
- table of options
The supported options are:
font
: used for the button labelcolor
: of the label and checkbox; defaults to whiteonChange
: optional function called when the checkbox is clicked
For example:
local state = { hungry = false }
Glove.Checkbox("Hungry?", state, "hungry", {
onChange = function(t, key, value)
print(key .. " is now " .. tostring(value))
end
})
This widget displays the frames per second currently being achieved
The parameters are:
- table of options
The supported options are:
font
: used for the text
For example:
Glove.FPS({ font = Glove.fonts.default12 })
Image
This widget displays an image.
The parameters are:
- filePath: path to the image file
- table of options
The supported options are:
height
: of the image (aspect ratio is preserved)
For example:
Glove.Image("images/love2d-heart.png", { height = 100 })
This widget allows the user to enter text. The text automatically scrolls horizontally when it exceeds the specified width. The cursor can be positioned using the left and right arrow keys. The character to the left of the cursor can be deleted by pressing the delete key.
The text is tied to value of a given key in a given table.
Currently the cursor cannot be positioned by clicking, entered text cannot be selected, and the tab key cannot be used to move focus to another widget.
The parameters are:
- table that holds its state
- key within the table that holds its state
- table of options
The supported options are:
color
: of the border and text; defaults to whitefont
: used for the textwidth
: of the widget
For example:
local state = { firstName = "" }
Glove.Input(state, "firstName", { width = 100 })
This widget allows the user to select one radiobutton from a set.
The selected value is tied to value of a given key in a given table.
The parameters are:
- choices described by an array-like table containing
tables with
label
andvalue
keys - table that holds its state
- key within the table that holds its state
- table of options
The supported options are:
color
: of the radiobuttons and their labels; defaults to whitefont
: used for the labelsonChange
: optional function to be called when a choice is selectedvertical
: boolean indicating whether the radiobuttons should be arranged vertically; defaults to false
For example:
local state = { color = "r" }
Glove.RadioButtons(
{
{ label = "Red", value = "r" },
{ label = "Green", value = "g" },
{ label = "Blue", value = "b" }
},
state,
"color",
{
onChange = function(t, key, value)
print(key .. " is now " .. value)
end,
vertical = true
}
)
This widget displays a dropdown list of options and allows the user to select one.
The selected value is tied to value of a given key in a given table.
The parameters are:
- choices described by an array-like table containing
tables with
label
andvalue
keys - table that holds its state
- key within the table that holds its state
- table of options
The supported options are:
color
: of the labels; defaults to whitefont
: used for the labelsonChange
: optional function to be called when a choice is selected
For example:
Glove.Select(
{
{ label = "Red", value = "r" },
{ label = "Green", value = "g" },
{ label = "Blue", value = "b" }
},
state,
"color",
{
onChange = function(t, key, value)
print(key .. " is now " .. value)
end
}
)
This widget displays a row of tabs where only one can be selected at a time.
Each tab is associated with a single widget which is
typically an HStack
, VStack
, or ZStack
.
The widget associated with the selected tab is displayed below the tabs.
The parameters are:
- tabs described by an array-like table containing
tables with
label
andwidget
keys - table of options
The supported options are:
color
: of the labels; defaults to whitefont
: used for the labelsonChange
: optional function to be called when a tab is selected; passed the tab index and the table describing the tab
For example:
Glove.Tabs(
{
{
label = "Baseball",
widget = Glove.VStack(
{ spacing = 10 },
Glove.Text("There's no crying in baseball!", { font = tabFont }),
Glove.HStack(
{ align = "center", spacing = 10 },
Glove.Text("Like baseball?"),
Glove.Toggle(state, "likeBaseball")
)
)
},
{
label = "Basketball",
widget = Glove.VStack(
{ spacing = 10 },
Glove.Text("Nuggets Rule!"),
Glove.HStack(
{ align = "center", spacing = 10 },
Glove.Text("Like basketball?"),
Glove.Toggle(state, "likeBasketball")
)
)
},
{
label = "Football",
widget = Glove.Text("Football detail goes here!")
},
{
label = "Hockey",
widget = Glove.Text("Hockey detail goes here!")
}
},
{
font = Glove.fonts.default18,
onChange = function(index, tab)
print("selected tab " .. tab.label .. " at index " .. index)
end,
}
)
This widget displays static text, computed text, or the value of a given key in a given table.
The parameters are:
- text to display or a function that returns it
- table of options
The supported options are:
color
: of the text; defaults to whitefont
: used for the texttable
: a table that holds the text to displaykey
: a key within the table that holds the text to displaywidth
: used when key and table are specified
For example:
Glove.Text("Hello, World!", {
color Glove.colors.red,
font = Glove.fonts.default18
})
local state = { firstName = "Mark", lastName = "Volkmann" }
Glove.Text(
function()
return "Hello, " .. state.firstName .. " " .. state.lastName .. "!"
end
)
Glove.Text("", { table = state, key = "firstName" }),
This widget ties a toggle state to a boolean value in a table.
The parameters are:
- table that holds its state
- key within the table that holds its state
- table of options
The supported options are:
color
: of the toggle; defaults to whiteonChange
: optional function called when the checkbox is clicked
For example:
Glove.Toggle(state, "hungry", {
onChange = function(t, key, value)
print(key .. " is now " .. tostring(value))
end
})
Layout is heavily based on ideas from SwiftUI and
borrows the names HStack
, VStack
, ZStack
, and Spacer
.
The "stack" widgets each hold child widgets.
Complex layouts can be defined by nesting stack widgets
inside other stack widgets.
This arranges widgets horizontally.
By default there is no space between the widgets.
To add space, specify the spacing
option.
To vertically align the widgets, specify the align
option
with a value of "top"
(default), "center"
, or "bottom"
.
The parameters are:
- table of options
- child widgets as individual arguments
The supported options are:
align
: "top" (default), "center", or "bottom"spacing
: positive integer to add space between non-spacer children
For example:
Glove.HStack(
{ align = "center", spacing = 20 },
{
Glove.Text("Left"),
Glove.Text("Center"),
Glove.Text("Right")
}
)
By default HStack
widgets assume they can use the entire width of the window.
This works well for top-level instances.
Nested instances should be given a specific width.
For example:
-- in main.lua
local love = require "love"
require "glove"
local container
local function createUI()
local spacing = 20
local half = (Glove.getAvailableWidth() - spacing) / 2
container = Glove.HStack(
{ spacing = spacing },
Glove.HStack(
{ width = half },
Glove.Text("One"),
Glove.Spacer(),
Glove.Text("Two")
),
Glove.HStack(
{ width = half },
Glove.Text("Three"),
Glove.Spacer(),
Glove.Text("Four")
)
)
end
function love.load()
createUI()
end
function love.update(dt)
-- Currently nothing is needed here.
end
function love.draw()
container:draw()
end
function love.resize()
createUI()
end
This arranges widgets vertically.
By default there is no space between the widgets.
To add space, specify the spacing
option.
To horizontally align the widgets, specify the align
option
with a value of "start"
(default), "center"
, or "end"
.
The parameters are:
- table of options
- child widgets as individual arguments
The supported options are:
align
: "start" (default), "center", or "end"spacing
: positive integer to add space between non-spacer children
For example:
Glove.VStack(
{ align = "center", spacing = 20 },
{
Glove.Text("Top"),
Glove.Text("Center"),
Glove.Text("Bottom")
}
)
By default VStack
widgets assume they can use the entire height of the window.
This works well for top-level instances.
Nested instances should be given a specific height.
For example:
-- in main.lua
local love = require "love"
require "glove"
local container
local function createUI()
local spacing = 20
local half = (Glove.getAvailableWidth() - spacing) / 2
container = Glove.HStack(
{ spacing = spacing },
Glove.HStack(
{ width = half },
Glove.Text("One"),
Glove.Spacer(),
Glove.Text("Two")
),
Glove.HStack(
{ width = half },
Glove.Text("Three"),
Glove.Spacer(),
Glove.Text("Four")
)
)
end
function love.load()
createUI()
end
function love.update(dt)
-- Currently nothing is needed here.
end
function love.draw()
container:draw()
end
function love.resize()
createUI()
end
This stacks widgets on top of each other.
To control the position of each widget in the stack,
specify the align
option with a compass direction or "center"
.
The parameters are:
- table of options
The supported options are:
- align: "center" or one of the following compass directions: "north", "south", "east", "west", "northeast", "southeast", "southwest", or "northwest" (default)
For example:
Glove.ZStack(
{ align = "center" },
Glove.Image("images/love2d-heart.png", { height = 200 }),
Glove.Text("LÖVE", {
color = Glove.colors.black,
font = Glove.fonts.default30
})
)
This widget adds space inside an HStack
or VStack
.
Adding a Spacer
at the end of a table of child widgets
pushes them to the left.
Adding a Spacer
at the beginning of a table of child widgets
pushes them to the right.
Adding a Spacer
between widgets in a table of child widgets
pushes the ones preceding it to the left and
pushes the ones following it to the right.
Any number of Spacer
widgets can be added to a table of widgets.
The amount of space consumed by each is computed by
dividing the unused space by the number of Spacer
widgets.
For example, the following code creates a row of Text
widgets
that are vertically centered.
The space between "One" and "Two" is 20.
The space between "Two" and "Three" is 20.
The space between "Three" and "Four" is all the remaining space in the row.
The text "Four" is pushed to the right.
Glove.HStack(
{ align = "center", spacing = 20 },
{
Glove.Text("One"),
Glove.Text("Two", { font = Glove.fonts.default18 }),
Glove.Text("Three")
Glove.Spacer(),
Glove.Text("Four")
}
)
Functions that implement custom widgets have the following requirements:
- They must return a table.
- The table must define a
draw
method that takes a reference to an instance of the widget, an x coordinate, and a y coordinate. The coordinates indicate the location where its upper-left corner should be drawn. - The table must define a
getHeight
method that takes a reference to an instance of the widget and returns its height. - The table must define a
getWidth
method that takes a reference to an instance of the widget and returns its width.
For example, the following code defines a custom widget that draws a rectangle that is filled with a given color and has a given size.
local function CustomWidget(color, width, height)
color = color or colors.white
width = width or 50
height = height or 50
return {
draw = function(self, parentX, parentY)
local x = parentX + self.x
local y = parentY + self.y
local g = love.graphics
g.setColor(color)
g.rectangle(
"fill",
x, y,
self:getWidth(), self:getHeight()
)
g.setColor(colors.white)
g.rectangle(
"line",
x, y,
self:getWidth(), self:getHeight()
)
end,
getHeight = function(self) return height end,
getWidth = function(self) return width end
}
end
Glove defines the many functional programming functions. The predefined widgets use them and your code can too. To use them, add the following near the top of a source file:
local fun = require "glove/fun"
The functions provided are:
-
fun.count(t, predicate)
Returns the number of items in a given table that match a predicate.
-
fun.every(t, predicate)
Returns a boolean indicating whether every item in a given table matches a predicate.
-
fun.filter(t, predicate)
Returns a new table containing all items in a given table that match a predicate.
-
fun.find(t, predicate)
Returns the first item in a given table that matches a predicate.
-
fun.map(t, fn)
Returns a new table containing the results of passing each item in a given table to a given function.
-
fun.max(t, fn)
Returns the maximum value returned by a function that is passed each item in a given table.
-
fun.min(t, fn)
Returns the minimum value returned by a function that is passed each item in a given table.
-
fun.reduce(t, fn, initial)
Returns a single value computed by accumulating the results of passing each item in a given table to a given function.
-
fun.some(t, predicate)
Returns a boolean indicating whether some item in a given table matches a predicate.
-
fun.sum(t)
Returns the sum of the numbers in a given table. While this can be implemented using "reduce", this is a bit more efficient.
-
fun.sumFn(t, fn)
Returns the sum of the values returned by a function that is passed each item in a given table. While this can be implemented using "reduce", this is a bit more efficient.
Most of the widgets take an options object. This becomes the object that represents an instance of the widget. For this reason you should NOT pass the same options object to multiple widget creation functions.
The options object can include keys not listed as supported options.
For example, it can be useful include an id
key
that acts as a unique identifier for widget instances.
This can be output during debugging.