From 7c7b122c7669bf78ae8fa00c665ec27fbe6dae29 Mon Sep 17 00:00:00 2001 From: George Paton Date: Thu, 30 Oct 2025 13:20:02 +1100 Subject: [PATCH 1/4] Add MUI rapid gui building library, with bonus .bet font file! --- Mellow/4.bet | 189 ++++++++++++++++++ Mellow/mui-example.lua | 216 ++++++++++++++++++++ Mellow/mui.lua | 434 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 839 insertions(+) create mode 100644 Mellow/4.bet create mode 100644 Mellow/mui-example.lua create mode 100644 Mellow/mui.lua diff --git a/Mellow/4.bet b/Mellow/4.bet new file mode 100644 index 0000000..f40e14b --- /dev/null +++ b/Mellow/4.bet @@ -0,0 +1,189 @@ + ██ +█ █ +█ █ + ██ + + ██ +█ █ + █ +████ + + ██ +█ █ + █ +██ █ + +████ + ██ + █ +████ + +█ █ +████ + █ + █ + +████ +█ + ███ +████ + + ██ +█ +████ +████ + +████ + █ + █ + █ + +███ +█ ██ +██ █ + ███ + +████ +█ █ +████ + █ + +████ +█ █ +████ +█ █ + +████ +█ ██ +██ █ +████ + +████ +█ +█ +████ + +███ +█ █ +█ █ +███ + +████ +██ +█ +████ + +████ +█ +███ +█ + +███ +█ +█ █ +████ + +█ █ +████ +█ █ +█ █ + +████ + ██ + ██ +████ + +████ + █ +█ █ + ██ + +█ █ +███ +███ +█ ██ + +█ +█ +█ +████ + +█ ██ +████ +██ █ +█ █ + +█ █ +██ █ +█ ██ +█ █ + +████ +█ █ +█ █ +████ + +████ +█ █ +████ +█ + +████ +█ █ +█ ██ +████ + +████ +█ █ +███ +█ ██ + +████ +██ + ██ +████ + +████ + ██ + ██ + ██ + +█ █ +█ █ +█ █ +████ + +█ █ +█ █ +█ █ + ██ + +█ █ +█ ██ +████ +██ █ + +█ █ + ██ + ██ +█ █ + +█ █ +████ + ██ + ██ + +████ + ██ +██ +████ + + █ + █ + █ +█ + +█ + █ + █ +█ \ No newline at end of file diff --git a/Mellow/mui-example.lua b/Mellow/mui-example.lua new file mode 100644 index 0000000..efca038 --- /dev/null +++ b/Mellow/mui-example.lua @@ -0,0 +1,216 @@ +local mui = require 'mui' +local computer = require 'computer' +local component = require 'component' +local note = require 'note' +local gpu = component.gpu + + + +-- Fonts +local fourBet = mui.loadBet('/home/4.bet', 4, 4, '0123456789abcdefghijklmnopqrstuvwxyz/:') + + + +-- Images +local imagePaLoop = [[ +┌────┐ ┌────┐ +├─┬──│──────────────────────────│──┬─┤ +│ │ │ ► ► │ │ │ +└────┘ └────┘ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ + │ │ +┌────┐ ┌────┐ +│ │ │ ◄ ◄ │ │ │ +├─┴──│──────────────────────────│──┴─┤ +└────┘ └────┘ +]] + +local imageEmitters = [[ +┌─────────┐ +│ % >───│ +│ % │ +└───────┬─┘ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ + │ +┌───────┴─┐ +│ / │ +│ { ~ │ +│ >┼<───│ +│ ~ } │ +│ / │ +└─────────┘ +]] + + + +-- PA bits +-- This used to be a set of component.proxy() initialisers, +-- but have been stubbed out so the program can function as an example +local sendGold +local returnGold + +local sendNbti +local returnNbti + +local returnBosco + +local limit = { + gold = { + lower = 0, + upper = 2200, + }, + nbti = { + lower = 1500, + upper = 8400, + }, + bosco = { + lower = 7500, + upper = 15000, + }, +} + +local targets = { + { name = 'antimatter', momentum = 300 }, + { name = 'antischrab', momentum = 400 }, + { name = 'muon', momentum = 2500 }, + { name = 'tachyon', momentum = 5000 }, + { name = 'higgs', momentum = 6500 }, + { name = 'dark', momentum = 10000 }, + { name = 'strange', momentum = 12500 }, + { name = 'spark', momentum = 12500 }, +} + +function setTarget(momentum) + momentum = tonumber(momentum) + if momentum == nil or type(momentum) ~= "number" or momentum <= 0 then + print('please enter a valid momentum') + return + end + + if momentum > limit.bosco.upper then + print('momentum too high for accelerator') + return + end + + -- stubbed out thresholds + sendGold = math.min(momentum + 100, limit.gold.upper) + returnGold = math.min(momentum, limit.gold.upper) + + sendNbti = math.min(momentum + 100, limit.nbti.upper) + returnNbti = math.min(momentum, limit.nbti.upper) + + returnBosco = math.min(momentum, limit.bosco.upper) +end + + + +local images = {} +local lastBtn + + +-- MUI start! +local events = { + + -- build out our UI elements + init = function () + local imageX = 18 + local imageY = 6 + + images.emitters = mui.addImage(imageX, imageY, imageEmitters) + images.loopGold = mui.addImage(imageX + 11, imageY, imagePaLoop) + images.loopNbti = mui.addImage(imageX + 11 + 38, imageY, imagePaLoop) + images.loopBosco = mui.addImage(imageX + 11 + 38 + 38, imageY, imagePaLoop) + + mui.addImage(130, 1, fourBet:from('pac/ui')) + + images.momentum = mui.addImage(3, 33, fourBet:from('momentum: n/a')) + images.particle = mui.addImage(110, 33, '', 0x00FFFF) + + mui.addHBar(37) + + local btnWidth = 16 + local btnHeight = 10 + local btnY = 40 + + for i, target in ipairs(targets) do + local x = i * (btnWidth + 2) - btnWidth + 1 + local textX = (btnWidth - #target.name) // 2 + + mui.addLabel(x + textX, btnY - 1, target.name) + mui.addBtn(x, btnY, btnWidth, btnHeight, 0x00FF00, 0x00AA00, function (btn) + setTarget(target.momentum) + + images.particle.lines = fourBet:from(target.name) + images.particle.x = 160 - #target.name * 5 + + if lastBtn then + lastBtn.color.default = 0x00FF00 + end + lastBtn = btn + + btn.color.default = 0x00FFFF + + note.play('A5', 0) + end) + end + + -- quit + mui.addLabel(153, btnY - 1, 'QUIT') + mui.addBtn(151, btnY, 8, btnHeight, 0xFF0000, 0xFF0000, mui.exit) + end, + + + + -- On all presses do some global updates + touch = function (x, y) + images.loopNbti.color = 0xFFFFFF + images.loopBosco.color = 0xFFFFFF + + if sendGold ~= returnGold then + images.loopNbti.color = 0xFF0000 + end + + if sendNbti ~= returnNbti then + images.loopBosco.color = 0xFF0000 + end + + images.momentum.lines = fourBet:from('momentum: ' .. returnBosco) + end, + + + -- on shutdown, + exit = function () + note.play('A4') + + -- Kiosk mode, immediately restart into the application + if not component.isAvailable('keyboard') then + computer.shutdown(true) + end + end + +} + +mui.runLoop(events) \ No newline at end of file diff --git a/Mellow/mui.lua b/Mellow/mui.lua new file mode 100644 index 0000000..a5cc449 --- /dev/null +++ b/Mellow/mui.lua @@ -0,0 +1,434 @@ +--[[ +======================================= + + MUI BIEN! + + A lib for generating pretty OpenOS GUIs rapidly. + + + API REFERENCE: + + api.runLoop(events) + Starts the main event loop, automatically handles interrupts and touch events for most objects + * events [object] - a combination of `event.pull` events + our own custom events for drawing and init + added events include: + * draw - called after every action is performed, used for drawing + * init - called just before drawing for the first time and entering the main loop + * exit - called while the program is closing, whether by interrupt or `api.exit()` + + + + api.exit() + Quits the program gracefully + + + + + + api.addLabel(x, y, text, color) + Add a label to the screen + * x [integer] - the X position of the label + * y [integer] - the Y position of the label + * text [string] - the text that the label should show + * color [color] - the color of the text + ! RETURNS [label] - the object created by this function, allowing for modification + + + + + + api.addBtn(x, y, w, h, defaultColor, pressColor, callback) + Add a button to the screen + * x [integer] - the X position of the button + * y [integer] - the Y position of the button + * w [integer] - the width of the button + * h [integer] - the height of the button + * defaultolor [color] - the color of the button + * pressColor [color] - the color of the button when pressed + * callback [function] - the function to call when the button is pressed + ! RETURNS [button] - the object created by this function, allowing for modification + + + + + + api.addImage(x, y, content, color) + Add an image to the screen + * x [integer] - the X position of the image + * y [integer] - the Y position of the image + * content [string|array] - either an array of strings, or a newline separated single string + * color [color] - the color of the image + ! RETURNS [image] - the object created by this function, allowing for modification + + EXAMPLE: + local image = [[ + ┌─────────┐ + │ % >───│ + │ % hi! │ + └───────┬─┘ + ]] --[[ ignore me I'm reopening the comment + + mui.addImage(4, 4, image) + + -- OR -- + + local bet = api.loadBet('/home/big.bet') + mui.addImage(4, 4, bet:from('hi')) + + + + image:setContent(content) + Sets the image's content to a new value + * content [string|array] - either an array of strings, or a newline separated single string + + + + + + api.addHBar(y, color) + Adds a horizontal bar to the screen + * y [integer] - the Y position of the bar + * color [color] - the color of the bar + + + + + + api.loadBet(filename, width, height, letterMap) + Load an alphabet from a file, make sure each character is separated by an empty newline, + characters must be fixed width and height! + * filename [string] - location of the character map on disk + * width [integer] - width of the characters + * height [integer] - height of the characters + * letterMap [string] - the letters in the file, in order! + ! RETURNS [bet] - a table of all the letters, each letter being an array of strings, + can be indexed like `object.a` to get the lines for letter "a" + + + + bet:from(inputText) + Turns an input string into an array of lines, ready for use in an `Image` + * inputText [string] - The string you'd like to turn into a big font + ! RETURNS [array] - an array of strings + + + MUCHA SUERTE! + +-- ======================================= +]] + + +local term = require 'term' +local event = require 'event' +local text = require 'text' +local component = require 'component' +local gpu = component.gpu + +local api = {} + +local running = true +local pending = false +local elements = {} + + + +-- ALPHABET +local function betFrom(bet, inputText) + local lines = {} + + for c in inputText:gmatch('.') do + for i, v in ipairs(bet[c]) do + if not lines[i] then + lines[i] = '' + else + lines[i] = lines[i] .. ' ' + end + + lines[i] = lines[i] .. v + end + end + + return lines +end + +function api.loadBet(filename, width, height, letterMap) + local bet = {} + + local file = io.open(filename) + + local index = 1 + local letterLines + + for line in file:lines() do + if line:match('^%s*$') then + if letterLines then + bet[letterMap:sub(index, index)] = letterLines + index = index + 1 + end + + letterLines = {} + else + if not letterLines then letterLines = {} end + table.insert(letterLines, text.padRight(line, width)) + end + end + + if #letterLines > 0 then + bet[letterMap:sub(index, index)] = letterLines + end + + local space = {} + for y = 1, height do + space[y] = string.rep(' ', width) + end + + bet[' '] = space + + + bet.width = width + bet.height = height + bet.from = betFrom + + return bet +end +-- /ALPHABET + + + +-- BUTTON +local function btnPress(btn, x, y) + if x < btn.x or x > btn.x + btn.w or y < btn.y or y > btn.y + btn.h then return end + + btn.callback(btn) + btn.state = true + + pending = true +end + +local function btnDraw(btn) + if btn.last then + gpu.setBackground(0x000000) + gpu.fill(btn.last.x, btn.last.y, btn.last.w, btn.last.h, ' ') + end + + gpu.setBackground(btn.state and btn.color.pressed or btn.color.default) + gpu.fill(btn.x, btn.y, btn.w, btn.h, ' ') + gpu.setBackground(0x000000) + + btn.last = { + x = btn.x, + y = btn.y, + w = btn.w, + h = btn.h, + } +end + +function api.addBtn(x, y, w, h, defaultColor, pressColor, callback) + local btn = { + x = x, + y = y, + w = w, + h = h, + color = { + default = defaultColor, + pressed = pressColor, + }, + state = false, + callback = callback, + press = btnPress, + draw = btnDraw, + last = nil, + } + + table.insert(elements, btn) + return btn +end +-- /BUTTON + + + +-- LABEL +local function labelDraw(label) + if label.last then + gpu.fill(label.last.x, label.last.y, label.last.w, 1, ' ') + end + + gpu.setForeground(label.color) + gpu.set(label.x, label.y, label.text) + + label.last = { + x = label.x, + y = label.y, + w = #label.text, + } +end + +function api.addLabel(x, y, text, color) + local label = { + x = x, + y = y, + text = text, + color = color or 0xFFFFFF, + draw = labelDraw, + last = nil, + } + + table.insert(elements, label) + return label +end +-- /LABEL + + + +-- IMAGE +function imageDraw(image) + gpu.setForeground(image.color) + + if image.lastX and image.lastY and image.lastWidth and image.lastHeight then + gpu.fill(image.lastX, image.lastY, image.lastWidth, image.lastHeight, ' ') + end + + local width = 0 + for i, line in ipairs(image.lines) do + gpu.set(image.x, image.y + i - 1, line) + width = math.max(width, #line) + end + + image.lastX = image.x + image.lastY = image.y + image.lastWidth = width + image.lastHeight = #image.lines +end + +function imageSetContent(image, content) + local lines = {} + if type(content) == 'string' then + for line in content:gmatch("[^\n]+") do + table.insert(lines, line) + end + else + lines = content + end + + image.lines = lines +end + +function api.addImage(x, y, content, color) + local image = { + x = x, + y = y, + color = color or 0xFFFFFF, + setContent = imageSetContent, + draw = imageDraw, + lastWidth = 0, + } + + image:setContent(content) + + table.insert(elements, image) + return image +end +-- /IMAGE + + + +-- HBAR +local function hbarDraw(hbar) + if hbar.last then + gpu.fill(3, hbar.last.y, 156, 1, ' ') + end + + gpu.setForeground(hbar.color) + gpu.fill(3, hbar.y, 156, 1, '─') + + hbar.last = { + y = hbar.y, + } +end + +function api.addHBar(y, color) + local hbar = { + y = y, + color = color or 0xFFFFFF, + draw = hbarDraw, + } + + table.insert(elements, hbar) + return hbar +end +-- /HBAR + + + +-- LOOP +function api.runLoop(events) + local inInterrupt = events.interrupted + local inTouch = events.touch + local inDraw = events.draw + + events.interrupted = function () + running = false + + if inInterrupt then inInterrupt() end + end + + events.touch = function (x, y) + for _, e in ipairs(elements) do + if e.press then e:press(x, y) end + end + + if inTouch then inTouch(x, y) end + end + + events.draw = function () + for i, e in ipairs(elements) do + e:draw() + end + + if inDraw then inDraw() end + end + + if events.init then + events.init() + end + + term.clear() + events.draw() + + while running do + -- if pending a redraw, update after a small wait + local id, _, x, y = event.pullFiltered(pending and 0.1 or nil, function (id) return events[id] ~= nil end) + + if pending then + for i, e in ipairs(elements) do + if e.state then e.state = false end + end + + pending = false + end + + local performEvent = events[id] + if performEvent then performEvent(x, y) end + + events.draw() + end + + if events.exit then + events.exit() + end + + term.clear() +end +-- /LOOP + + + +-- DIE +function api.exit() + running = false +end +-- /~ATH + + + +return api \ No newline at end of file From 20065d8b71c06b825e6174e0d6a22a38047bfbad Mon Sep 17 00:00:00 2001 From: George Paton Date: Thu, 30 Oct 2025 13:27:26 +1100 Subject: [PATCH 2/4] add screenshot of example program --- Mellow/mui-example.png | Bin 0 -> 16947 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Mellow/mui-example.png diff --git a/Mellow/mui-example.png b/Mellow/mui-example.png new file mode 100644 index 0000000000000000000000000000000000000000..48e9ab4488e8e8ec03736b438ed3bb752a307015 GIT binary patch literal 16947 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 JTrqq4{{YLK+Cl&T literal 0 HcmV?d00001 From e56f5e7c8b8bb073243e905c3a547eca39f1395d Mon Sep 17 00:00:00 2001 From: George Paton Date: Thu, 30 Oct 2025 13:33:29 +1100 Subject: [PATCH 3/4] touch up documentation --- Mellow/mui.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Mellow/mui.lua b/Mellow/mui.lua index a5cc449..c39a0fd 100644 --- a/Mellow/mui.lua +++ b/Mellow/mui.lua @@ -72,7 +72,7 @@ -- OR -- - local bet = api.loadBet('/home/big.bet') + local bet = api.loadBet('/home/big.bet', 4, 4, '0123456789abcdefghijklmnopqrstuvwxyz') mui.addImage(4, 4, bet:from('hi')) @@ -230,8 +230,8 @@ function api.addBtn(x, y, w, h, defaultColor, pressColor, callback) w = w, h = h, color = { - default = defaultColor, - pressed = pressColor, + default = defaultColor, + pressed = pressColor, }, state = false, callback = callback, From 9c5cfafe6dc2cb6363af5f3fd82416721e9fde4c Mon Sep 17 00:00:00 2001 From: George Paton Date: Thu, 30 Oct 2025 18:07:34 +1100 Subject: [PATCH 4/4] the pr curse continues --- Mellow/mui.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Mellow/mui.lua b/Mellow/mui.lua index c39a0fd..9567553 100644 --- a/Mellow/mui.lua +++ b/Mellow/mui.lua @@ -362,6 +362,10 @@ end -- LOOP function api.runLoop(events) + running = true + pending = false + elements = {} + local inInterrupt = events.interrupted local inTouch = events.touch local inDraw = events.draw