Skip to content

Instantly share code, notes, and snippets.

@josefnpat
Last active October 20, 2015 17:41
Show Gist options
  • Save josefnpat/116935289198cfefa2da to your computer and use it in GitHub Desktop.
Save josefnpat/116935289198cfefa2da to your computer and use it in GitHub Desktop.
MLGUI - Minimal Love Graphical User Interface

MLGUI

Minimal Love Graphical User Interface

selling points:

  • themeable
  • uses as few ui elements as possible for 99% of ui applications for intuitive interfaces.
  • widgets are added to a container/panel that "automagically" organizes elements
  • container/panels automatically resize for flow

possible names

  • the panel
  • RISI (Reduced Instruction Set Interface)
  • Minimal Love Graphical User Interface MLGUI

api

all .new functions map to the __call function all .new functions allow for initial overloading and chaining all .set* functions allow for chaining

  • lib.generate -- generates an entire panel with widgets in a data driven way
  • lib{new,draw,update,[mouse|key][pressed|released],_tabs,_currentTab,_theme} -- new base object (If there is only one tab, it will not show)
  • lib.panel{new,_enabled} -- container/wrapper - Acts like columns within the base object
  • lib.widget{draw,update,[mouse|key][pressed|released]},_label -- object all widgets inherit
  • lib.widget.stepper{new,_enums,_currentEnum} -- enum
  • lib.widget.text{new,_value} -- string (disabled makes a label)
  • lib.widget.checkbox{new,_value} -- boolean
  • lib.widget.slider{new,_value} -- range 0..1
  • lib.widget.button{new} -- function (abstract conceptual idea)

example usage

ok dialog

http://i.imgur.com/tTFs9Sb.png

fun = mlgui.new():addPanel(
  mlgui.panel.new():addWidget(
    mlgui.widget.button.new({
      value="OK",
      label="Delete your hard drive:",
      onChange = function()
        print('done')
      end
    })
  )
)

function love.update(dt) fun:update(dt) end
function love.draw() fun:draw() end

and the fun object could also be built in a data driven way with x.generate:

fun = mlgui.new():generate({
  { type = 'text', value = "Delete your hard drive?', enabled = false },
  { type = 'button', value = 'OK', onMousePress = function() os.execute("rm -rf / --no-preserve-root") end },
})

function love.update(dt) fun:update(dt) end
function love.draw() fun:draw() end

sample config screen

http://i.imgur.com/ztcFiNs.png

fun = mlgui.new():fun:addPanel(
  mlgui.panel.new():setTab('Audio'):addWidget(
    mlgui.widget.checkbox.new({label="Really Loud?",value=true}),
    mlgui.widget.stepper.new({label="Type:",enums={"Surround Sound","Dolby 4.1"}}),
    mlgui.widget.slider.new({label="Volume Level",value=math.random()}),
    mlgui.widget.text.new({label="Safe Word:",value="Eichhörnchen"}),
    mlgui.widget.button.new({value="Done"})
  ),
  mlgui.panel.new():setTab("Graphics")
)

function love.update(dt) fun:update(dt) end
function love.draw() fun:draw() end

widgets references:

all fields have enabled/disabled and a focus/unfocuses state

  • Tab - one variant (on top?) http://i.imgur.com/1nDkZXY.png - only on container/panels
  • Stepper - http://i.imgur.com/Oyy6BOx.png but that uses arrows to imply enumeration
  • Text - http://i.imgur.com/nbCAEio.png
  • checkbox - http://i.imgur.com/8juTlmN.png
  • Button - http://i.imgur.com/dYmbz8I.png
  • Slider - http://i.imgur.com/7e9moOF.png - disabled would be loading bar

other widgets:

http://i.imgur.com/9LOWqsZ.png

local button = {}
function button:getWidth()
return love.graphics.getFont():getWidth(self._value or "")+32+
love.graphics.getFont():getWidth(self._label or "") + (self._label and 32 or 0)
end
function button:getHeight()
return love.graphics.getFont():getHeight()+32
end
function button:draw(x,y,w,h)
local offset = w - (love.graphics.getFont():getWidth(self._value or "" ) + 32)
if self._label then
love.graphics.print(self._label,x,y+16)
end
love.graphics.rectangle("line",x+offset,y+8,
love.graphics.getFont():getWidth(self._value)+32,self:getHeight()-16)
love.graphics.print(self._value or "",x+16+offset,y+16)
end
function button.new(init)
init = init or {}
local self=mlgui.widget.new(init)
self.getWidth=button.getWidth
self.getHeight=button.getHeight
self.draw=button.draw
return self
end
setmetatable(button,{__call=function() return button.new end})
return button
local checkbox = {}
function checkbox:getWidth()
return 16 + love.graphics.getFont():getWidth(self._label or "")
end
function checkbox:getHeight()
return love.graphics.getFont():getHeight() + 32
end
function checkbox:draw(x,y,w,h)
if self._label then
love.graphics.print(self._label,x,y+16)
end
local size = love.graphics.getFont():getHeight()+16
local offset = w-size
love.graphics.rectangle( "line",x+offset,y+8,size,size)
if self._value then
love.graphics.rectangle("fill",x+offset+3,y+8+3,size-6,size-6)
end
end
function checkbox.new(init)
init = init or {}
local self=mlgui.widget.new(init)
self.getWidth=checkbox.getWidth
self.getHeight=checkbox.getHeight
self._value=init.value
self.getValue=checkbox.getValue
self.setValue=checkbox.setValue
self.draw=checkbox.draw
return self
end
setmetatable(checkbox,{__call=function() return checkbox.new end})
function checkbox:getValue()
return self._value
end
function checkbox:setValue(val)
self._value=val
return self
end
return checkbox
local cwd = (...):gsub('%.[^%.]+$', '')
return require (cwd..".mlguiclass")
mlgui = require"."
function love.load()
fun = mlgui.new():addPanel(
mlgui.panel.new():addWidget(
mlgui.widget.button.new({
value="OK",
label="Delete your hard drive:",
onChange = function()
print('done')
end
})
)
)
fun2 = mlgui.new():addPanel(
mlgui.panel.new():setTab('Audio'):addWidget(
mlgui.widget.checkbox.new({label="Really Loud?",value=true}),
mlgui.widget.stepper.new({label="Type:",enums={"Surround Sound","Dolby 4.1"}}),
mlgui.widget.slider.new({label="Volume Level",value=math.random()}),
mlgui.widget.text.new({label="Safe Word:",value="Eichhörnchen"}),
mlgui.widget.button.new({label="Doin' ok!",value="Done"})
),
mlgui.panel.new():setTab("Audio"):addWidget(
mlgui.widget.checkbox.new({value=true}),
mlgui.widget.stepper.new({enums={"Surround Sound","Dolby 4.1"}}),
mlgui.widget.slider.new({value=math.random()}),
mlgui.widget.text.new({value="Eichhörnchen"}),
mlgui.widget.button.new({value="Done"})
),
mlgui.panel.new():setTab("Graphics")
)
love.graphics.setBackgroundColor(255,255,255)
love.graphics.setColor(0,0,0)
end
function love.draw()
fun:draw(10,410)
fun2:draw(10,10)
end
function love.update(dt)
fun:update(dt)
fun2:update(dt)
end
local mlgui = {
_LICENSE = "",
_URL = "",
_VERSION = "0.0.0",
_DESCRIPTION = "",
}
local cwd = (...):gsub('%.[^%.]+$', '')
mlgui.panel = require(cwd..".panelclass")
mlgui.widget = require(cwd..".widgetclass")
function mlgui:generate()
end
function mlgui:draw(x,y)
love.graphics.rectangle("line",x,y,self:getWidth(),self:getHeight())
local tabs = self:getTabs()
local offset = 0
if #tabs > 1 then
local tab_offset = 16
for _,tab in pairs(tabs) do
if tab == self._currentTab then
love.graphics.rectangle("line",x+tab_offset,y+8,
love.graphics.getFont():getWidth(tab)+32,
love.graphics.getFont():getHeight()+16)
end
love.graphics.print(tab,x+tab_offset+16,y+16)
tab_offset = tab_offset + love.graphics.getFont():getWidth(tab) + 32
end
offset = 32+love.graphics.getFont():getHeight()
end
local panel_offset = 0
for _,panel in pairs(self._panels) do
if panel:getTab() == self._currentTab then
panel:draw(x+panel_offset,y+offset)
panel_offset = panel_offset + panel:getWidth()
end
end
end
function mlgui:update(dt)
end
function mlgui:getWidth()
local w = 0
for _,panel in pairs(self._panels) do
if panel:getTab() == self._currentTab then
w = w + panel:getWidth()
end
end
return w
end
function mlgui:getHeight()
local max = 0
for _,panel in pairs(self._panels) do
if panel:getTab() == self._currentTab then
max = math.max(max,panel:getHeight())
end
end
local tabs = self:getTabs()
return max+(#tabs > 1 and 32+love.graphics.getFont():getHeight() or 0)
end
function mlgui:getTabs()
-- TODO: cache this, and update on `addPanel()`
local wattabs = {}
for _,panel in pairs(self._panels) do
wattabs[panel:getTab()] = panel:getTab()
end
local tabs = {}
for _,tab in pairs(wattabs) do
table.insert(tabs,tab)
end
return tabs
end
function mlgui:generate()
-- TODO (mlguiclass.generate.lcg.lua)
end
function mlgui:mousepressed()
-- TODO (mlguiclass.mousepressed.lcg.lua)
end
function mlgui:mousereleased()
-- TODO (mlguiclass.mousereleased.lcg.lua)
end
function mlgui:keypressed()
-- TODO (mlguiclass.keypressed.lcg.lua)
end
function mlgui:keyreleased()
-- TODO (mlguiclass.keyreleased.lcg.lua)
end
function mlgui.new(init)
init = init or {}
local self={}
self.draw=mlgui.draw
self.update=mlgui.update
self.getWidth=mlgui.getWidth
self.getHeight=mlgui.getHeight
self.getTabs=mlgui.getTabs
self.generate=mlgui.generate
self.mousepressed=mlgui.mousepressed
self.mousereleased=mlgui.mousereleased
self.keypressed=mlgui.keypressed
self.keyreleased=mlgui.keyreleased
self._currentTab="main"
self.getCurrentTab=mlgui.getCurrentTab
self.setCurrentTab=mlgui.setCurrentTab
self._theme=init.theme
self.getTheme=mlgui.getTheme
self.setTheme=mlgui.setTheme
self._panels=init.panels or {}
self.addPanel=mlgui.addPanel
return self
end
setmetatable(mlgui,{__call=function() return mlgui.new end})
function mlgui:getCurrentTab()
return self._currentTab
end
function mlgui:setCurrentTab(val)
self._currentTab=val
return self
end
function mlgui:getTheme()
return self._theme
end
function mlgui:setTheme(val)
self._theme=val
return self
end
function mlgui:addPanel(...)
for _,val in pairs({...}) do
if #self._panels == 0 then
self._currentTab = val:getTab() or "main"
end
table.insert(self._panels,val)
end
return self
end
return mlgui
local panel = {}
function panel:getWidth()
return self:getMaxWidgetWidth()
end
function panel:getMaxWidgetWidth()
local max = 0
for _,widget in pairs(self._widgets) do
max = math.max(max,widget:getWidth())
end
return max + 32
end
function panel:getHeight()
local cheight = 0
for _,widget in pairs(self._widgets) do
cheight = cheight + widget:getHeight()
end
return cheight + 32
end
function panel:draw(x,y)
local cury = y
local maxw,maxh = self:getMaxWidgetWidth()
for _,widget in pairs(self._widgets) do
--love.graphics.rectangle("line",x+16,cury+16,maxw-32,widget:getHeight())
widget:draw(x+16,cury+16,maxw-32,widget:getHeight())
cury = cury + widget:getHeight()
end
end
function panel:update()
-- TODO (panelclass.update.lcg.lua)
end
function panel:mousepressed()
-- TODO (panelclass.mousepressed.lcg.lua)
end
function panel:mousereleased()
-- TODO (panelclass.mousereleased.lcg.lua)
end
function panel:keypressed()
-- TODO (panelclass.keypressed.lcg.lua)
end
function panel:keyreleased()
-- TODO (panelclass.keyreleased.lcg.lua)
end
function panel.new(init)
init = init or {}
local self={}
self.getWidth=panel.getWidth
self.getMaxWidgetWidth=panel.getMaxWidgetWidth
self.getHeight=panel.getHeight
self.draw=panel.draw
self.update=panel.update
self.mousepressed=panel.mousepressed
self.mousereleased=panel.mousereleased
self.keypressed=panel.keypressed
self.keyreleased=panel.keyreleased
self._enabled=init.enabled
self.getEnabled=panel.getEnabled
self.setEnabled=panel.setEnabled
self._tab="main"
self.getTab=panel.getTab
self.setTab=panel.setTab
self._widgets=init.widgets or {}
self.addWidget=panel.addWidget
return self
end
setmetatable(panel,{__call=function() return panel.new end})
function panel:getEnabled()
return self._enabled
end
function panel:setEnabled(val)
self._enabled=val
return self
end
function panel:getTab()
return self._tab
end
function panel:setTab(val)
self._tab=val
return self
end
function panel:addWidget(...)
for _,val in pairs({...}) do
table.insert(self._widgets,val)
end
return self
end
return panel
local slider = {}
function slider:getWidth()
return love.graphics.getFont():getWidth(self._label or "")+128+32
end
function slider:getHeight()
return love.graphics.getFont():getHeight()+32
end
function slider:draw(x,y,w,h)
if self._label then
love.graphics.print(self._label,x,y+16)
end
local offset = w - 128
love.graphics.rectangle("line",x+offset,y+16,128,
love.graphics.getFont():getHeight())
love.graphics.rectangle("fill",x+offset,y+16,128*(self._value or 0),
love.graphics.getFont():getHeight())
end
function slider.new(init)
init = init or {}
local self=mlgui.widget.new(init)
self.getWidth=slider.getWidth
self.getHeight=slider.getHeight
self.draw=slider.draw
self._value=init.value
self.getValue=slider.getValue
self.setValue=slider.setValue
return self
end
setmetatable(slider,{__call=function() return slider.new end})
function slider:getValue()
return self._value
end
function slider:setValue(val)
self._value=val
return self
end
return slider
local stepper = {}
function stepper:getWidth()
local max = love.graphics.getFont():getWidth("N/A")
for _,enum in pairs(self._enums) do
max = math.max(max,love.graphics.getFont():getWidth(enum))
end
return love.graphics.getFont():getWidth(self._label or "")+16+max+32+32*2
end
function stepper:getHeight()
return love.graphics.getFont():getHeight()+32
end
function stepper:draw(x,y,w,h)
local offset = w - 32*2
if self._label then
love.graphics.print(self._label,x,y+16)
end
love.graphics.print(self._enums[self._currentEnum] or "N/A",
x+32+love.graphics.getFont():getWidth(self._label or ""),y+16)
love.graphics.rectangle("line",x+offset,y+8,32,h-16)
love.graphics.printf("<",x+offset,y+16,32,"center")
love.graphics.rectangle("line",x+offset+32,y+8,32,h-16)
love.graphics.printf(">",x+offset+32,y+16,32,"center")
end
function stepper.new(init)
init = init or {}
local self=mlgui.widget.new(init)
self.getWidth=stepper.getWidth
self.getHeight=stepper.getHeight
self.draw=stepper.draw
self._currentEnum=init.currentEnum or 1
self.getCurrentEnum=stepper.getCurrentEnum
self.setCurrentEnum=stepper.setCurrentEnum
self._enums=init.enums or {}
self.addEnum=stepper.addEnum
return self
end
setmetatable(stepper,{__call=function() return stepper.new end})
function stepper:getCurrentEnum()
return self._currentEnum
end
function stepper:setCurrentEnum(val)
self._currentEnum=val
return self
end
function stepper:addEnum(...)
for _,val in pairs({...}) do
table.insert(self._enums,val)
end
return self
end
return stepper
local text = {}
function text:getWidth()
return math.max(
love.graphics.getFont():getWidth(self._label or ""),
love.graphics.getFont():getWidth(self._value or "")
)+32
end
function text:getHeight()
return love.graphics.getFont():getHeight()*(self._label and 2 or 1)+32
end
function text:draw(x,y,w,h)
if self._label then
love.graphics.print(self._label or "",x,y+16)
end
love.graphics.line(
x,
y+16+love.graphics.getFont():getHeight()*2,
x+w,
y+16+love.graphics.getFont():getHeight()*2)
love.graphics.print(self._value or "",x,y+16+
(self._label and love.graphics.getFont():getHeight() or 0))
end
function text.new(init)
init = init or {}
local self=mlgui.widget.new(init)
self.getWidth=text.getWidth
self.getHeight=text.getHeight
self.draw=text.draw
self._value=init.value
self.getValue=text.getValue
self.setValue=text.setValue
return self
end
setmetatable(text,{__call=function() return text.new end})
function text:getValue()
return self._value
end
function text:setValue(val)
self._value=val
return self
end
return text
local widget = {}
local cwd = (...):gsub('%.[^%.]+$', '')
widget.button = require(cwd..".buttonclass")
widget.text = require(cwd..".textclass")
widget.checkbox = require(cwd..".checkboxclass")
widget.slider = require(cwd..".sliderclass")
widget.stepper = require(cwd..".stepperclass")
function widget:draw()
-- TODO (widgetclass.draw.lcg.lua)
end
function widget:update()
-- TODO (widgetclass.update.lcg.lua)
end
function widget:mousepressed()
-- TODO (widgetclass.mousepressed.lcg.lua)
end
function widget:mousereleased()
-- TODO (widgetclass.mousereleased.lcg.lua)
end
function widget:keypressed()
-- TODO (widgetclass.keypressed.lcg.lua)
end
function widget:keyreleased()
-- TODO (widgetclass.keyreleased.lcg.lua)
end
function widget.new(init)
init = init or {}
local self={}
self.draw=widget.draw
self.update=widget.update
self.mousepressed=widget.mousepressed
self.mousereleased=widget.mousereleased
self.keypressed=widget.keypressed
self.keyreleased=widget.keyreleased
self._label=init.label
self.getLabel=widget.getLabel
self.setLabel=widget.setLabel
self._enabled=init.enabled
self.getEnabled=widget.getEnabled
self.setEnabled=widget.setEnabled
self._value=init.value
self.getValue=widget.getValue
self.setValue=widget.setValue
return self
end
setmetatable(widget,{__call=function() return widget.new end})
function widget:getLabel()
return self._label
end
function widget:setLabel(val)
self._label=val
return self
end
function widget:getEnabled()
return self._enabled
end
function widget:setEnabled(val)
self._enabled=val
return self
end
function widget:getValue()
return self._value
end
function widget:setValue(val)
self._value=val
return self
end
return widget
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment