Compare commits

..

17 commits

Author SHA1 Message Date
len0rd
07a1731419 better support for custom color themes in button and label 2025-08-12 12:54:44 -04:00
len0rd
45a4b65053 add 'scale' option support to drawing a button box 2025-08-12 12:54:19 -04:00
5b3dcdad9c add bounding box info to button... idk 2025-08-09 17:51:27 -04:00
len0rd
faa7b93bc3 theme: add support to drawBox to include outline. add scale parameter to button for text 2025-08-05 09:56:18 -04:00
len0rd
4aada674ad core: require that the user manually call suit:enterFrame during update/draw so that manually specifying mouse coordinates works 2025-08-05 09:55:28 -04:00
2634383ffd layout: add newRow function, which increments the layout y position by the current cell height, while resetting the x position and internal width 2025-08-04 21:34:36 -04:00
tyler miller
28abbc8bd1 core: add optional parameters to enterFrame for mouse coordinates. allows user to use a separate coordinate system than the screen for ui elements 2025-08-04 16:56:27 -04:00
Matt Wiens
1767782603 Fix a sentence in docs 2019-02-01 23:05:22 +01:00
Matthias Richter
1781d5eec6
Merge pull request #72 from SimonLarsen/imagebutton-fix
Fixed ImageButton checking mask bounds on mask instead of image
2019-02-01 23:04:51 +01:00
Simon Larsen
bec02e99c5 Fixed ImageButton checking mask bounds on mask instead of image 2018-08-12 20:58:48 +02:00
Matthias Richter
0a723777ed [docs] better words 2018-06-21 23:46:58 +02:00
Matthias Richter
2f479ba30a Fix #70: overlapping buttons both react to hover 2018-06-17 18:52:30 +02:00
Matthias Richter
eabad8c554 Fix #68: ImageButton error on LÖVE 11.1
Introduction of new option `mask` that should hold an ImageData for the
alpha test.
2018-06-17 18:10:18 +02:00
Matthias Richter
8ec0e638ce Fix rockspec 2018-04-08 14:15:21 +02:00
Matthias Richter
bd3811ec03 Fix #62: Add rockspec 2018-04-08 13:58:49 +02:00
Matthias Richter
7985b38a95
Merge pull request #64 from rodel77/master
Update theme colors to love 11.0
2018-04-08 13:42:43 +02:00
rodel77
2f3db27769 Updated to love 11.0 2018-04-05 18:07:37 -05:00
10 changed files with 198 additions and 66 deletions

View file

@ -6,6 +6,7 @@ return function(core, text, ...)
local opt, x,y,w,h = core.getOptionsAndSize(...) local opt, x,y,w,h = core.getOptionsAndSize(...)
opt.id = opt.id or text opt.id = opt.id or text
opt.font = opt.font or love.graphics.getFont() opt.font = opt.font or love.graphics.getFont()
opt.color = opt.color or core.theme.color
w = w or opt.font:getWidth(text) + 4 w = w or opt.font:getWidth(text) + 4
h = h or opt.font:getHeight() + 4 h = h or opt.font:getHeight() + 4
@ -18,6 +19,10 @@ return function(core, text, ...)
hit = core:mouseReleasedOn(opt.id), hit = core:mouseReleasedOn(opt.id),
hovered = core:isHovered(opt.id), hovered = core:isHovered(opt.id),
entered = core:isHovered(opt.id) and not core:wasHovered(opt.id), entered = core:isHovered(opt.id) and not core:wasHovered(opt.id),
left = not core:isHovered(opt.id) and core:wasHovered(opt.id) left = not core:isHovered(opt.id) and core:wasHovered(opt.id),
x = x,
y = y,
w = w,
h = h
} }
end end

View file

@ -100,7 +100,7 @@ function suit:mouseInRect(x,y,w,h)
end end
function suit:registerMouseHit(id, ul_x, ul_y, hit) function suit:registerMouseHit(id, ul_x, ul_y, hit)
if hit(self.mouse_x - ul_x, self.mouse_y - ul_y) then if not self.hovered and hit(self.mouse_x - ul_x, self.mouse_y - ul_y) then
self.hovered = id self.hovered = id
if self.active == nil and self.mouse_button_down then if self.active == nil and self.mouse_button_down then
self.active = id self.active = id
@ -176,15 +176,24 @@ function suit:keyPressedOn(id, key)
end end
-- state update -- state update
function suit:enterFrame() function suit:enterFrame(mouseX, mouseY)
if not self.mouse_button_down then if not self.mouse_button_down then
self.active = nil self.active = nil
elseif self.active == nil then elseif self.active == nil then
self.active = NONE self.active = NONE
end end
local mx = mouseX
local my = mouseY
if mx == nil then
mx = love.mouse.getX()
end
if my == nil then
my = love.mouse.getY()
end
self.hovered_last, self.hovered = self.hovered, nil self.hovered_last, self.hovered = self.hovered, nil
self:updateMouse(love.mouse.getX(), love.mouse.getY(), love.mouse.isDown(1)) self:updateMouse(mx, my, love.mouse.isDown(1))
self.key_down, self.textchar = nil, "" self.key_down, self.textchar = nil, ""
self:grabKeyboardFocus(NONE) self:grabKeyboardFocus(NONE)
self.hit = nil self.hit = nil
@ -204,14 +213,14 @@ function suit:registerDraw(f, ...)
end end
function suit:draw() function suit:draw()
self:exitFrame() -- self:exitFrame() -- CALL these manually in your update method
love.graphics.push('all') love.graphics.push('all')
for i = self.draw_queue.n,1,-1 do for i = self.draw_queue.n,1,-1 do
self.draw_queue[i]() self.draw_queue[i]()
end end
love.graphics.pop() love.graphics.pop()
self.draw_queue.n = 0 self.draw_queue.n = 0
self:enterFrame() -- self:enterFrame() -- CALL these manually in your update method
end end
return suit return suit

View file

@ -5,7 +5,7 @@ Before actually getting started, it is important to understand the motivation
and mechanics behind SUIT: and mechanics behind SUIT:
- **Immediate mode is better than retained mode** - **Immediate mode is better than retained mode**
- **Layout does not care about widgets** - **Layout should not care about content**
- **Less is more** - **Less is more**
Immediate mode? Immediate mode?
@ -68,11 +68,11 @@ to design a user interface.
SUIT is not a complete GUI library: It does not take control of the runtime. SUIT is not a complete GUI library: It does not take control of the runtime.
You have to do everything yourself [1]_. You have to do everything yourself [1]_.
**SUIT is not good at word processors!** **SUIT is not good at processing words!**
Hello, World Hello, World!
------------ -------------
SUITing up is is straightforward: Define your GUI in ``love.update()``, and SUITing up is is straightforward: Define your GUI in ``love.update()``, and
draw it in ``love.draw()``:: draw it in ``love.draw()``::
@ -96,15 +96,19 @@ draw it in ``love.draw()``::
suit.draw() suit.draw()
end end
This will produce this UI (after clicking the button): This will produce this UI:
.. image:: _static/hello-world.gif .. image:: _static/hello-world.gif
As written above, the two widgets are each created by a function call The two widgets (the button and the label) are each created by a function call
(:func:`suit.Button <Button>` and :func:`suit.Label <Label>`). The first (:func:`suit.Button <Button>` and :func:`suit.Label <Label>`). The first
argument to a widget function is always the "payload" of the widget, and the argument to a widget function always defines the *payload* of the widget.
last four arguments define the position and dimension of the widget. The Different widgets expect different payloads.
function returns a table that indicates the UI state of the widget. Here, both :func:`suit.Button <Button>` and :func:`suit.Label <Label>` expect a
string.
The last four arguments of a widget function define the position and dimension
of the widget.
The function returns a table that indicates the UI state of the widget.
Here, the state ``hit`` is used to figure out if the mouse was clicked and Here, the state ``hit`` is used to figure out if the mouse was clicked and
released on the button. See :doc:`Widgets <widgets>` for more info on widget released on the button. See :doc:`Widgets <widgets>` for more info on widget
states. states.
@ -112,8 +116,8 @@ states.
Mutable state Mutable state
------------- -------------
Widgets that mutate some state - input boxes, checkboxes and sliders - receive Widgets that mutate some state - input boxes, checkboxes and sliders - expect
a table argument as payload, e.g.:: a table as their payload, e.g.::
local slider = {value = 1, min = 0, max = 2} local slider = {value = 1, min = 0, max = 2}
function love.update(dt) function love.update(dt)
@ -131,8 +135,7 @@ Options
------- -------
You can define optional, well, options after the payload. Most options affect You can define optional, well, options after the payload. Most options affect
how the widget is drawn. For example, to align the label text to the left in how the widget is drawn. For example, to align the label text to the left::
the above example, you would write::
local slider = {value = 1, max = 2} local slider = {value = 1, max = 2}
function love.update(dt) function love.update(dt)
@ -148,8 +151,8 @@ the theme. See :doc:`Widgets <widgets>` for more info on widget options.
Keyboard input Keyboard input
-------------- --------------
The :func:`input widget <Input>` requires that you forward the ``keypressed`` The :func:`Input` widget requires that you forward the ``keypressed`` and
and ``textinput`` events to SUIT:: ``textinput`` events to SUIT::
local input = {text = ""} local input = {text = ""}
function love.update(dt) function love.update(dt)
@ -168,7 +171,7 @@ and ``textinput`` events to SUIT::
.. image:: _static/keyboard.gif .. image:: _static/keyboard.gif
The slider widget can also react to keyboard input. The mouse state is The :func:`Slider` widget can also react to keyboard input. The mouse state is
automatically updated, but you can provide your own version of reality if you automatically updated, but you can provide your own version of reality if you
need to. See the :doc:`Core functions <core>` for more details. need to. See the :doc:`Core functions <core>` for more details.
@ -185,7 +188,7 @@ you can directly pass as the final four arguments to the widget functions.
If you have ever dabbled with `Qt's <http://qt.io>`_ ``QBoxLayout``, you If you have ever dabbled with `Qt's <http://qt.io>`_ ``QBoxLayout``, you
already know 89% [2]_ of what you need to know. already know 89% [2]_ of what you need to know.
The first example can be written as follows:: Hello, World! can be rewritten as follows::
suit = require 'suit' suit = require 'suit'
@ -234,10 +237,10 @@ Refer to the :doc:`Layout <layout>` documentation for more information.
Widget ids Widget ids
---------- ----------
Each widget is identified by an ``id`` [4]_. Internally, this ``id`` is used t Each widget is identified by an ``id`` [4]_. Internally, this ``id`` is used to
figure out which widget should handle user input like mouse clicks and keyboard figure out which widget should handle user input like mouse clicks and keyboard
presses. presses.
Unless specified otherwise, the ``id`` is the same as the first argument, i.e., Unless specified otherwise, the ``id`` is the same as the payload, i.e.,
the ``id`` of ``Button("Hello, World!", ...)`` will be the string the ``id`` of ``Button("Hello, World!", ...)`` will be the string
``"Hello, World!"``. ``"Hello, World!"``.
In almost all of the cases, this will work fine and you don't have to worry about In almost all of the cases, this will work fine and you don't have to worry about
@ -332,10 +335,10 @@ You can also do minimally invasive surgery::
GUI Instances GUI Instances
------------- -------------
Sometimes you might feel the need to separate parts of the GUI. Maybe the Sometimes you might feel the need to separate parts of the GUI. Maybe certain
widgets should have a different theme, maybe certain should always be drawn UI elements should always be drawn before or after other UI elements, or maybe
before or after other UI elements, or maybe you don't want the UI state to you don't want the UI state to "leak" (e.g., from a stacked pause gamestate to
"leak" (e.g., from a stacked pause gamestate to the main gamestate). the main gamestate).
For this reason, SUIT allows you to create GUI instances:: For this reason, SUIT allows you to create GUI instances::

View file

@ -12,14 +12,35 @@ You may also download the sourcecode as a `zip
<http://github.com/vrld/SUIT/zipball/master>`_ or `tar <http://github.com/vrld/SUIT/zipball/master>`_ or `tar
<http://github.com/vrld/SUIT/tarball/master>`_ file. <http://github.com/vrld/SUIT/tarball/master>`_ file.
Using `Git <http://git-scm.com>`_, you can clone the project by running:: Otherwise, use `Git <http://git-scm.com>`_::
git clone git://github.com/vrld/SUIT git clone git://github.com/vrld/SUIT
Once done, you can check for updates by running:: Update::
git pull git pull
Hello, Suit::
suit = require 'suit'
local show_message = false
function love.update(dt)
-- Put a button on the screen. If hit, show a message.
if suit.Button("Hello, World!", 100,100, 300,30).hit then
show_message = true
end
-- if the button was pressed at least one time, but a label below
if show_message then
suit.Label("How are you today?", 100,150, 300,30)
end
end
function love.draw()
suit.draw()
end
Read on Read on
------- -------
@ -49,7 +70,7 @@ The following code will create this UI:
-- generate some assets (below) -- generate some assets (below)
function love.load() function love.load()
snd = generateClickySound() snd = generateClickySound()
normal, hovered, active = generateImageButton() normal, hovered, active, mask = generateImageButton()
smallerFont = love.graphics.newFont(10) smallerFont = love.graphics.newFont(10)
end end
@ -128,13 +149,14 @@ The following code will create this UI:
-- put an image button below the nested cell -- put an image button below the nested cell
-- the size of the cell will be 200 by 100 px, -- the size of the cell will be 200 by 100 px,
-- but the image may be bigger or smaller -- but the image may be bigger or smaller
-- the button shows the image `normal' when the mouse is outside the image -- the button shows the image `normal' when the button is inactive
-- or above a transparent pixel -- the button shows the image `hovered` if the mouse is over an opaque pixel
-- the button shows the image `hovered` if the mouse is above an opaque pixel -- of the ImageData `mask`
-- of the image `normal'
-- the button shows the image `active` if the mouse is above an opaque pixel -- the button shows the image `active` if the mouse is above an opaque pixel
-- of the image `normal' and the mouse button is pressed -- of the ImageData `mask` and the mouse button is pressed
suit.ImageButton(normal, {hovered = hovered, active = active}, suit.layout:row(200,100)) -- if `mask` is omitted, the alpha-test will be swapped for a test whether
-- the mouse is in the area occupied by the widget
suit.ImageButton(normal, {mask = mask, hovered = hovered, active = active}, suit.layout:row(200,50))
-- if the checkbox is checked, display a precomputed layout -- if the checkbox is checked, display a precomputed layout
if chk.checked then if chk.checked then
@ -200,17 +222,17 @@ The following code will create this UI:
local d2 = math.exp(-((px+.7)^2 + (py+.1)^2) * 2) local d2 = math.exp(-((px+.7)^2 + (py+.1)^2) * 2)
local d = (d1 + d2)/2 local d = (d1 + d2)/2
if d > t then if d > t then
return r,g,b, 255 * ((d-t) / (1-t))^.2 return r,g,b, ((d-t) / (1-t))^.2
end end
return 0,0,0,0 return 0,0,0,0
end end
end end
local normal, hovered, 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)) normal:mapPixel(metaballs(.48, .74,.74,.74))
hovered:mapPixel(metaballs(.46, 50,153,187)) hovered:mapPixel(metaballs(.46, .2,.6,.6))
active:mapPixel(metaballs(.43, 255,153,0)) active:mapPixel(metaballs(.43, 1,.6,0))
return love.graphics.newImage(normal), love.graphics.newImage(hovered), love.graphics.newImage(active) return love.graphics.newImage(normal), love.graphics.newImage(hovered), love.graphics.newImage(active), normal
end end
Indices and tables Indices and tables

View file

@ -30,7 +30,7 @@ Creates a label at position ``(x,y)`` with width ``w`` and height ``h``.
.. function:: ImageButton(normal, options, x,y) .. function:: ImageButton(normal, options, x,y)
:param Image normal: Image of the button in normal state. :param mixed normal: Image of the button in normal state.
:param table options: Widget options. :param table options: Widget options.
:param numbers x,y: Upper left corner of the widget. :param numbers x,y: Upper left corner of the widget.
:returns: Return state (see below). :returns: Return state (see below).
@ -39,13 +39,21 @@ Creates an image button widget at position ``(x,y)``.
Unlike all other widgets, an ``ImageButton`` is not affected by the current Unlike all other widgets, an ``ImageButton`` is not affected by the current
theme. theme.
The argument ``normal`` defines the image of the normal state as well as the 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 area of the widget.
non-zero alpha value. The button activates when the mouse enters the area occupied by the widget.
If the option ``mask`` defined, the button activates only if the mouse is over
a pixel with non-zero alpha.
You can provide additional ``hovered`` and ``active`` images, but the widget area
is always computed from the ``normal`` image.
You can provide additional ``hovered`` 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. is always computed from the ``normal`` image.
**Additional Options:** **Additional Options:**
``mask``
Alpha-mask of the button, i.e. an ``ImageData`` of the same size as the
``normal`` image that has non-zero alpha where the button should activate.
``normal`` ``normal``
Image for the normal state of the widget. Defaults to widget payload. Image for the normal state of the widget. Defaults to widget payload.

View file

@ -2,36 +2,49 @@
local BASE = (...):match('(.-)[^%.]+$') local BASE = (...):match('(.-)[^%.]+$')
local function isType(val, typ)
return type(val) == "userdata" and val.typeOf and val:typeOf(typ)
end
return function(core, normal, ...) return function(core, normal, ...)
local opt, x,y = core.getOptionsAndSize(...) local opt, x,y = core.getOptionsAndSize(...)
opt.normal = normal or opt.normal or opt[1] opt.normal = normal or opt.normal or opt[1]
opt.hovered = opt.hovered or opt[2] or opt.normal opt.hovered = opt.hovered or opt[2] or opt.normal
opt.active = opt.active or opt[3] or opt.hovered opt.active = opt.active or opt[3] or opt.hovered
assert(opt.normal, "Need at least `normal' state image")
opt.id = opt.id or opt.normal opt.id = opt.id or opt.normal
opt.state = core:registerMouseHit(opt.id, x,y, function(u,v) local image = assert(opt.normal, "No image for state `normal'")
local id = opt.normal:getData()
assert(id:typeOf("ImageData"), "Can only use uncompressed images") core:registerMouseHit(opt.id, x, y, function(u,v)
-- mouse in image?
u, v = math.floor(u+.5), math.floor(v+.5) u, v = math.floor(u+.5), math.floor(v+.5)
if u < 0 or u >= opt.normal:getWidth() or v < 0 or v >= opt.normal:getHeight() then if u < 0 or u >= image:getWidth() or v < 0 or v >= image:getHeight() then
return false return false
end end
local _,_,_,a = id:getPixel(u,v)
return a > 0 if opt.mask then
-- alpha test
assert(isType(opt.mask, "ImageData"), "Option `mask` is not a love.image.ImageData")
assert(u < mask:getWidth() and v < mask:getHeight(), "Mask may not be smaller than image.")
local _,_,_,a = mask:getPixel(u,v)
return a > 0
end
return true
end) end)
local img = opt.normal
if core:isActive(opt.id) then if core:isActive(opt.id) then
img = opt.active image = opt.active
elseif core:isHovered(opt.id) then elseif core:isHovered(opt.id) then
img = opt.hovered image = opt.hovered
end end
core:registerDraw(opt.draw or function(img,x,y, r,g,b,a) assert(isType(image, "Image"), "state image is not a love.graphics.image")
core:registerDraw(opt.draw or function(image,x,y, r,g,b,a)
love.graphics.setColor(r,g,b,a) love.graphics.setColor(r,g,b,a)
love.graphics.draw(img,x,y) love.graphics.draw(image,x,y)
end, img, x,y, love.graphics.getColor()) end, image, x,y, love.graphics.getColor())
return { return {
id = opt.id, id = opt.id,

View file

@ -6,6 +6,7 @@ return function(core, text, ...)
local opt, x,y,w,h = core.getOptionsAndSize(...) local opt, x,y,w,h = core.getOptionsAndSize(...)
opt.id = opt.id or text opt.id = opt.id or text
opt.font = opt.font or love.graphics.getFont() opt.font = opt.font or love.graphics.getFont()
opt.color = opt.color or core.theme.color
w = w or opt.font:getWidth(text) + 4 w = w or opt.font:getWidth(text) + 4
h = h or opt.font:getHeight() + 4 h = h or opt.font:getHeight() + 4

View file

@ -15,6 +15,7 @@ function Layout:reset(x,y, padx,pady)
self._widths = {} self._widths = {}
self._heights = {} self._heights = {}
self._isFirstCell = true self._isFirstCell = true
self._start_x = self._x
return self return self
end end
@ -35,6 +36,12 @@ function Layout:nextRow()
return self._x, self._y + self._h + self._pady return self._x, self._y + self._h + self._pady
end end
function Layout:newRow()
self._x = self._start_x
self._y = self._y + self._h + self._pady
self._w = nil
end
Layout.nextDown = Layout.nextRow Layout.nextDown = Layout.nextRow
function Layout:nextCol() function Layout:nextCol()

28
suit-0.1-1.rockspec Normal file
View file

@ -0,0 +1,28 @@
package = "suit"
version = "0.1-1"
source = {
url = "git://github.com/vrld/suit.git"
}
description = {
summary="Immediate mode GUI library in pure Lua.",
homepage = "https://suit.readthedocs.io",
license = "MIT",
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
["suit"] = "init.lua",
["suit.button"] = "button.lua",
["suit.checkbox"] = "checkbox.lua",
["suit.core"] = "core.lua",
["suit.imagebutton"] = "imagebutton.lua",
["suit.input"] = "input.lua",
["suit.label"] = "label.lua",
["suit.layout"] = "layout.lua",
["suit.slider"] = "slider.lua",
["suit.theme"] = "theme.lua",
}
}

View file

@ -6,9 +6,9 @@ local theme = {}
theme.cornerRadius = 4 theme.cornerRadius = 4
theme.color = { theme.color = {
normal = {bg = { 66, 66, 66}, fg = {188,188,188}}, normal = {bg = { 0.25, 0.25, 0.25}, fg = {0.73,0.73,0.73}},
hovered = {bg = { 50,153,187}, fg = {255,255,255}}, hovered = {bg = { 0.19,0.6,0.73}, fg = {1,1,1}},
active = {bg = {255,153, 0}, fg = {225,225,225}} active = {bg = {1,0.6, 0}, fg = {1,1,1}}
} }
@ -18,16 +18,32 @@ function theme.getColorForState(opt)
return (opt.color and opt.color[opt.state]) or theme.color[s] return (opt.color and opt.color[opt.state]) or theme.color[s]
end end
function theme.drawBox(x,y,w,h, colors, cornerRadius) function theme.drawBox(x,y,w,h, colors, cornerRadius, outline, scale)
colors = colors or theme.getColorForState(opt) colors = colors or theme.getColorForState(opt)
cornerRadius = cornerRadius or theme.cornerRadius cornerRadius = (cornerRadius or theme.cornerRadius)
w = math.max(cornerRadius/2, w) w = math.max(cornerRadius/2, w)
if h < cornerRadius/2 then if h < cornerRadius/2 then
y,h = y - (cornerRadius - h), cornerRadius/2 y,h = y - (cornerRadius - h), cornerRadius/2
end end
-- by default, scale will operate about the center of the box. meaning it grows
-- out/in in all directions
if scale and scale ~= 1.0 then
local baseW, baseH = w, h
w = w * scale
h = h * scale
x = x - (w - baseW) / 2
y = y - (h - baseH) / 2
cornerRadius = cornerRadius * scale
end
love.graphics.setColor(colors.bg) love.graphics.setColor(colors.bg)
love.graphics.rectangle('fill', x,y, w,h, cornerRadius) love.graphics.rectangle('fill', x,y, w,h, cornerRadius)
if outline ~= nil then
love.graphics.setColor(colors.fg)
love.graphics.setLineWidth(outline)
love.graphics.rectangle('line', x,y, w,h, cornerRadius)
end
end end
function theme.getVerticalOffsetForAlign(valign, font, h) function theme.getVerticalOffsetForAlign(valign, font, h)
@ -51,13 +67,33 @@ end
function theme.Button(text, opt, x,y,w,h) function theme.Button(text, opt, x,y,w,h)
local c = theme.getColorForState(opt) local c = theme.getColorForState(opt)
local scale = opt.scale or 1.0
theme.drawBox(x,y,w,h, c, opt.cornerRadius) theme.drawBox(x,y,w,h, c, opt.cornerRadius, opt.outline, scale)
love.graphics.setColor(c.fg) love.graphics.setColor(c.fg)
love.graphics.setFont(opt.font) love.graphics.setFont(opt.font)
-- ensure text remains aligned regardless of scale
local align = opt.align or "center"
local textX = x
local textY = y
local textW = w
if scale ~= 1.0 then
-- For center alignment, shift x so the scaled text stays centered
if align == "center" then
textX = x + (w - w * scale) / 2
elseif align == "right" then
textX = x + (w - w * scale)
end
end
y = y + theme.getVerticalOffsetForAlign(opt.valign, opt.font, h) y = y + theme.getVerticalOffsetForAlign(opt.valign, opt.font, h)
love.graphics.printf(text, x+2, y, w-4, opt.align or "center") love.graphics.printf(text, textX+2, textY, textW-4,
align,
0,
scale,
scale
)
end end
function theme.Checkbox(chk, opt, x,y,w,h) function theme.Checkbox(chk, opt, x,y,w,h)