local ui = {} ui.theme = { bg = {0.13, 0.13, 0.14}, panel = {0.17, 0.17, 0.19}, border = {0.28, 0.28, 0.30}, borderwidth = 3, text = {0.90, 0.90, 0.90}, text_dim = {0.50, 0.50, 0.55}, accent = {0.27, 0.52, 0.90}, hover = {0.22, 0.25, 0.35}, active = {0.20, 0.40, 0.75}, } ui.theme2 = { bg = {0.5, 0, 0}, panel = {0.25, 0, 0}, border = {1, 0, 0}, borderwidth = 3, text = {0, 0, 1}, text_dim = {1, 0, 0}, accent = {0.7, 0, 0}, hover = {1, 0, 0}, active = {1, 0, 0}, } ui.activeTheme = ui.theme -- per-frame state (most/all gets reset if i remember) ui._wasDown = false ui._focused = nil ui._inputs = {} ui._claimedFocus = false ui._clickConsumed = false ui._elements = {} -- queue of UI elements, processed at end of frame ui._hoverConsumed = false -- so it sets hovered to the correct thing -- draws a basic bordered panel function ui._draw_panel(e) love.graphics.setColor(ui.activeTheme.panel) love.graphics.rectangle("fill", e.x, e.y, e.w, e.h) love.graphics.setColor(ui.activeTheme.border) love.graphics.setLineWidth(ui.activeTheme.borderwidth) love.graphics.rectangle("line", e.x, e.y, e.w, e.h) end -- draws centered, wrapped, clipped text inside a box function ui._draw_text(e) local font = love.graphics.getFont() local _, lines = font:getWrap(e.text, e.w) local totalHeight = #lines * font:getHeight() local startY = e.y + (e.h - totalHeight) / 2 love.graphics.setScissor(e.x, e.y, e.w, e.h) -- clip to box bounds love.graphics.setColor(ui.activeTheme.text) love.graphics.printf(e.text, e.x, startY, e.w, "center") love.graphics.setScissor() -- reset clip end -- draws a button, highlighted on hover function ui._draw_button(e) local mx, my = love.mouse.getPosition() local hovered = not ui._hoverConsumed and mx > e.x and mx < e.x + e.w and my > e.y and my < e.y + e.h love.graphics.setColor(e._hovered and ui.activeTheme.hover or ui.activeTheme.panel) love.graphics.rectangle("fill", e.x, e.y, e.w, e.h) love.graphics.setColor(ui.activeTheme.border) love.graphics.setLineWidth(ui.activeTheme.borderwidth) love.graphics.rectangle("line", e.x, e.y, e.w, e.h) ui._draw_text(e) end -- draws a text input, changes appearance based on focus state function ui._draw_textInput(e) local focused = ui._focused == e.placeholder local text = ui._inputs[e.placeholder] or "" if focused then love.graphics.setColor(ui.activeTheme.panel) love.graphics.rectangle("fill", e.x, e.y, e.w, e.h) love.graphics.setColor(ui.activeTheme.accent) love.graphics.setLineWidth(ui.activeTheme.borderwidth) love.graphics.rectangle("line", e.x, e.y, e.w, e.h) if text == "" then -- show dimmed placeholder text when empty local font = love.graphics.getFont() local startY = e.y + (e.h - font:getHeight()) / 2 love.graphics.setColor(ui.activeTheme.text_dim) love.graphics.printf(e.placeholder, e.x + 4, startY, e.w - 8, "left") else ui._draw_text({ x=e.x, y=e.y, w=e.w, h=e.h, text=text }) end else -- unfocused local display = text == "" and e.placeholder or text ui._draw_button({ x=e.x, y=e.y, w=e.w, h=e.h, text=display }) end end -- bordered panel, no interaction function ui.panel(x, y, w, h) table.insert(ui._elements, { type = "panel", x=x, y=y, w=w, h=h }) end -- centered text inside a box, no background function ui.text(x, y, w, h, text) table.insert(ui._elements, { type = "text", x=x, y=y, w=w, h=h, text=text }) end -- panel with centered text inside (justs draws a panel and puts text ontop, dependant on ui.text) function ui.textPanel(x, y, w, h, text) table.insert(ui._elements, { type = "textPanel", x=x, y=y, w=w, h=h, text=text }) end -- clickable button. callback is called when clicked (optional) -- example: ui.button(x, y, w, h, "Click me", function() print("hi") end) function ui.button(x, y, w, h, text, callback) table.insert(ui._elements, { type = "button", x=x, y=y, w=w, h=h, text=text, callback = callback }) end -- text input field. placeholder is also used as its unique ID -- read typed text with: ui._inputs["placeholder"] function ui.textInput(x, y, w, h, placeholder) table.insert(ui._elements, { type = "textInput", x=x, y=y, w=w, h=h, placeholder = placeholder }) end function ui.process() local mx, my = love.mouse.getPosition() local clicked = love.mouse.isDown(1) and not ui._wasDown for i = #ui._elements, 1, -1 do local e = ui._elements[i] local inside = mx > e.x and mx < e.x + e.w and my > e.y and my < e.y + e.h -- consume hover for top thing if inside and not ui._hoverConsumed then e._hovered = true ui._hoverConsumed = true else e._hovered = false end end -- reverse hit test so topmost element gets the click, similar to above but just for clicking for i = #ui._elements, 1, -1 do local e = ui._elements[i] if clicked and not ui._clickConsumed then local inside = mx > e.x and mx < e.x + e.w and my > e.y and my < e.y + e.h if inside then if e.type == "button" then if e.callback then e.callback() end -- fire callback if provided ui._clickConsumed = true elseif e.type == "textInput" then ui._focused = e.placeholder ui._claimedFocus = true ui._clickConsumed = true end end end end -- clicked outside everything, clear focus if clicked and not ui._claimedFocus then ui._focused = nil end -- draw in order for i = 1, #ui._elements do local e = ui._elements[i] if e.type == "panel" then ui._draw_panel(e) elseif e.type == "text" then ui._draw_text(e) elseif e.type == "textPanel" then ui._draw_panel(e) ui._draw_text(e) elseif e.type == "button" then ui._draw_button(e) elseif e.type == "textInput" then ui._draw_textInput(e) end end -- clear queue/list for next frame (leave at end) ui._elements = {} end function ui.flush() ui._wasDown = love.mouse.isDown(1) ui._claimedFocus = false ui._clickConsumed = false ui._hoverConsumed = false end -- text things, both used in the process of rendering text function ui.textinput_char(t) if ui._focused then ui._inputs[ui._focused] = (ui._inputs[ui._focused] or "") .. t end end function ui.textinput_backspace() if ui._focused then local str = ui._inputs[ui._focused] or "" ui._inputs[ui._focused] = str:sub(1, -2) end end return ui