diff --git a/examples/basicmenu.lua b/examples/basicmenu.lua new file mode 100644 index 0000000..f8370eb --- /dev/null +++ b/examples/basicmenu.lua @@ -0,0 +1,124 @@ +--- Basic menu example +-- +-- Trivial menu made of buttons. +-- Illustrates onHit() and onEnter()/onLeave() +-- events. +-- +-- Relevant code to build UI inside: makeMainMenu(). +-- +-- Layout: Rows +-- Widget: Button + +local yui = require 'lib.yui' + +local Button = yui.Button +local Rows = yui.Rows +local Ui = yui.Ui + +local GUI_WIDTH = 500 +local GUI_HEIGHT = 300 +local FONT_SIZE = 32 + +local function centerRectOnScreen(w, h) + local x = math.floor((love.graphics.getWidth() - w) / 2) + local y = math.floor((love.graphics.getHeight() - h) / 2) + + return x, y +end + +-- Creates main menu. +local function makeMainMenu() + -- Position the UI + local x,y = centerRectOnScreen(GUI_WIDTH, GUI_HEIGHT) + local rh = GUI_HEIGHT / 5 -- 5 elements along height + + -- Keep track what's being focused... + widgetEnter = function (w) + guiStatus.current = "You're hovering \""..w.text.."\", feel like pressing it?" + end + widgetLeave = function (w) + guiStatus.previous = "So you left \""..w.text.."\"..." + end + + return Ui.new { + x = x, y = y, -- Place UI at the calculated spot + + -- Place the elements in rows from top to bottom + Rows { + Button { + -- Provide first button's size... + w = GUI_WIDTH, h = rh, + + text = "Start game", + + onHit = function () + guiStatus = { current = "Game started!" } + end, + onEnter = widgetEnter, + onLeave = widgetLeave + }, + Button { + -- ...subsequent widgets _in the same layout_ + -- take last widget's size by default. + text = "Continue", + + onHit = function() + guiStatus = { current = "Loading game..." } + end, + onEnter = widgetEnter, + onLeave = widgetLeave + }, + Button { + text = "Options", + + onHit = function() + guiStatus = { current = "Options pressed." } + end, + onEnter = widgetEnter, + onLeave = widgetLeave + }, + Button { + text = "Credits", + + onHit = function() + guiStatus = { current = "Showcasing credits 8)" } + end, + onEnter = widgetEnter, + onLeave = widgetLeave + }, + Button { + text = "Quit", + + onHit = function () love.event.quit() end, + onEnter = widgetEnter, + onLeave = widgetLeave + } + } + } +end + +function love.load() + guiStatus = { current = "Menu is open." } + guiFont = love.graphics.newFont('fonts/PixelDroidMenu.ttf', FONT_SIZE) + gui = makeMainMenu() +end + +function love.update(dt) + -- Let the UI update its status + gui:update(dt) +end + +function love.draw() + love.graphics.setFont(guiFont) + + -- Print UI status + local y = 0 + if guiStatus.previous ~= nil then + love.graphics.print(guiStatus.previous, 0, y) + y = y + guiFont:getHeight() + end + love.graphics.print(guiStatus.current, 0, y) + + -- Draw UI + gui:draw() +end diff --git a/examples/helloworld.lua b/examples/helloworld.lua new file mode 100644 index 0000000..a7e5ffe --- /dev/null +++ b/examples/helloworld.lua @@ -0,0 +1,51 @@ +-- the ubiquitous "Hello, World!" demo. +-- 'Nuff said. +-- +-- Layout: Rows +-- Widgets: Label, Button +-- Relevant UI construction code in: love.load() + +local yui = require 'lib.yui' + +-- Some convenience aliases +local Ui = yui.Ui +local Rows = yui.Rows +local Button, Label = yui.Button, yui.Label + +local function centerRectOnScreen(w, h) + local x = math.floor((love.graphics.getWidth() - w) / 2) + local y = math.floor((love.graphics.getHeight() - h) / 2) + + return x, y +end + +function love.load() + local W, H = 400, 80 -- pick arbitrary UI size + local x, y = centerRectOnScreen(W, H) + + gui = Ui.new { + x = x, y = y, + + Rows { + Label { + w = W, h = H, + + text = "Hello, World!" + }, + Button { + text = "OBEY", + onHit = function () love.event.quit() end + } + } + } +end + +function love.update(dt) + gui:update(dt) +end + +function love.draw() + -- Pretty black out there, isn't it? + -- See more complete examples for shinier stuff :) + gui:draw() +end diff --git a/examples/init.lua b/examples/init.lua new file mode 100644 index 0000000..83a4580 --- /dev/null +++ b/examples/init.lua @@ -0,0 +1,79 @@ +local BASE = (...)..'.' + +local endswith = require('lib.gear.strings').endswith +local yui = require 'lib.yui' + +local Button = yui.Button +local Rows = yui.Rows +local Ui = yui.Ui + +local Examples = {} +Examples.__index = Examples + + +local function isexample(file) + return file ~= 'init.lua' and endswith(file, '.lua') +end + +local function loadexample(file) + love.event.clear() + for i in ipairs(love.handlers) do + love.handlers[i] = nil + end + + -- Restart to next example. + require(BASE..file) + if love.load then love.load() end +end + +local W = 400 +local RH = 32 + +local function makeSelectionMenu() + local menu = Rows {} + + local files = love.filesystem.getDirectoryItems('examples') + table.sort(files) + + for _,file in ipairs(files) do + if isexample(file) then + local name = file:sub(1, -5) + + menu[#menu+1] = Button { + w = W, h = RH, + + text = name, + notranslate = true, + onHit = function() loadexample(name) end + } + end + end + menu[#menu+1] = Button { + w = W, h = RH, + + text = "Quit", + onHit = function() love.event.quit() end + } + + local x = math.floor(love.graphics.getWidth() - W) / 2 + local y = math.floor(love.graphics.getHeight() - RH * #menu) / 2 + + return Ui.new { + x = x, y = y, + menu + } +end + +function love.load() + gui = makeSelectionMenu() +end + +function love.update(dt) + gui:update(dt) +end + +function love.draw() + gui:draw() +end + +return Examples diff --git a/examples/optionsmenu.lua b/examples/optionsmenu.lua new file mode 100644 index 0000000..d1a39da --- /dev/null +++ b/examples/optionsmenu.lua @@ -0,0 +1,264 @@ +--- Options screen example +-- +-- A fairly complete configuration menu to demonstrate +-- several types of widgets coexisting. +-- Many widgets' behavior is tuned by their attributes. +-- +-- Relevant code to build UI inside: makeOptionsMenu(). +-- +-- Layout: Rows, Columns +-- Widget: Button, Checkbox, Choice, Input, Label, Slider, Spacer + +local yui = require 'lib.yui' + +local Button = yui.Button +local Checkbox = yui.Checkbox +local Choice = yui.Choice +local Columns = yui.Columns +local Input = yui.Input +local Label = yui.Label +local Rows = yui.Rows +local Slider = yui.Slider +local Spacer = yui.Spacer +local Ui = yui.Ui + +local GUI_WIDTH = 500 +local GUI_HEIGHT = 300 +local FONT_SIZE = 32 + +-- A dummy save file, for demo purposes. +local dummySave = {} + +-- Dummy config load mock. +function dummySave.load() + local config = {} + + -- The default configuration + local defconfig = { + name = "Player 1", + graphics = 'medium', + lang = 'en', + fullscreen = false, + musicVolume = 80, + sfxVolume = 90 + } + + local saved = dummySave.latest or defconfig + + print("Loading configuration: {") + for k,v in pairs(saved) do + config[k] = v + + print(("\t%s = %s,"):format(k, v)) + end + print("}") + + return config +end +-- Dummy config save mock +function dummySave.save(config) + dummySave.latest = {} + + print("Saving configuration: {") + for k,v in pairs(config) do + dummySave.latest[k] = v + + print(("\t%s = %s,"):format(k, v)) + end + print("}") +end + +-- Center rectangle on screen. +local function centerRectOnScreen(w, h) + local x = math.floor((love.graphics.getWidth() - w) / 2) + local y = math.floor((love.graphics.getHeight() - h) / 2) + + return x, y +end + +-- Actual menu creation. +local function makeOptionsMenu() + -- Position the UI + local x,y = centerRectOnScreen(GUI_WIDTH, GUI_HEIGHT) + local vpad = 8 + local hpad = 4 + + local w = (GUI_WIDTH - hpad) / 2 -- cut away padding from widget width + local h = (GUI_HEIGHT - vpad) / 9 -- 9 rows (Spacer counts as 2) + + -- Load configuration "from disk" + local config = dummySave.load() + + -- Make configuration editable via UI + return Ui.new { + x = x, y = y, + + Rows { + padding = vpad, + + Columns { + padding = hpad, + + Label { + w = w, h = h, + text = "Name" + }, + Input { + text = config.name, -- populate initial value + + onChange = function(_, text) + config.name = text -- write changed value back + end + } + }, + Columns { + padding = hpad, + + Label { + w = w, h = h, + text = "Language" + }, + Choice { + -- We don't want languages to be translated. + notranslate = true, + + choices = { + { text = "English", value = 'en' }, -- just kidding, only English in demo :) + -- { text = "Français", value = 'fr' }, + -- { text = "Italiano", value = 'it' }, + -- { text = "日本語", value = 'ja' } + }, + default = config.lang, + nowrap = true, + + onChange = function(_, choice) config.lang = choice.value end + } + }, + Columns { + padding = hpad, + + Label { + w = w, h = h, + text = "Graphics" + }, + Choice { + choices = { + -- Text may be localized, + -- so map entries to well known enum values. + { text = "Fastest", value = 'low' }, + { text = "Normal", value = 'medium' }, + { text = "Best", value = 'high' } + }, + default = config.graphics, + nowrap = true, + + onChange = function(_, choice) config.graphics = choice.value end + } + }, + Columns { + padding = hpad, + + Label { + w = w, h = h, + text = "Fullscreen" + }, + Checkbox { + checked = config.fullscreen, + + onChange = function(_, checked) config.fullscreen = checked end + } + }, + Columns { + padding = hpad, + + Label { + w = w, h = h, + text = "Music" + }, + Slider { + min = 0, max = 100, + value = config.musicVolume, + + onChange = function(_, value) config.musicVolume = value end + } + }, + Columns { + padding = hpad, + + Label { + w = w, h = h, + text = "Effects" + }, + Slider { + min = 0, max = 100, + value = config.sfxVolume, + + onChange = function(_, value) config.sfxVolume = value end + } + }, + + Spacer { h = 2*h }, -- leave 2 rows out. + + Columns { + padding = hpad, + + Button { + w = w, h = h, + text = "Back", + + cancelfocus = true, -- gets focused on ESC. + + onHit = function() love.event.quit() end + }, + Button { + text = "Apply", + + onHit = function() + -- Save configuration "on disk" + dummySave.save(config) + end + } + } + } + } +end + +function love.load() + guiFont = love.graphics.newFont('fonts/PixelDroidMenu.ttf', FONT_SIZE) + gui = makeOptionsMenu() +end + +function love.update(dt) + -- Let the UI update its status + gui:update(dt) +end + +-- Options menu contains a text input field, +-- this requires propagating LÖVE input events to UI. +function love.keypressed(key, scan, isrepeat) + gui:keypressed(key, scan, isrepeat) +end +function love.keyreleased(key, scan) + gui:keyreleased(key, scan) +end +function love.textinput(text) + gui:textinput(text) +end +function love.textedited(text, start, len) + gui:textedited(text, start, len) +end + +function love.draw() + love.graphics.setFont(guiFont) + + -- Draw menu title + local title = "Options" + local tw = guiFont:getWidth(title) + local th = guiFont:getHeight() + local tx = (love.graphics.getWidth() - tw) / 2 + local ty = th*2 + love.graphics.print(title, tx,ty) + + -- Draw UI + gui:draw() +end diff --git a/fonts/PixelDroidMenu.ttf b/fonts/PixelDroidMenu.ttf new file mode 100644 index 0000000..1465139 Binary files /dev/null and b/fonts/PixelDroidMenu.ttf differ diff --git a/fonts/README.ACKNOWLEDGEMENT b/fonts/README.ACKNOWLEDGEMENT new file mode 100644 index 0000000..dc4642b --- /dev/null +++ b/fonts/README.ACKNOWLEDGEMENT @@ -0,0 +1,8 @@ +pixeldroid fonts + +Available at: https://github.com/pixeldroid/fonts + + .otf, .ttf, and .fnt files can be downloaded from the releases page. + pixeldroid fonts are licensed under the Open Font License (OFL). + +Full SIL Open Font License: https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..b6ae31a --- /dev/null +++ b/main.lua @@ -0,0 +1 @@ +require 'examples' diff --git a/pics/hello_world.png b/pics/hello_world.png new file mode 100644 index 0000000..2a8f408 Binary files /dev/null and b/pics/hello_world.png differ