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-example.png b/Mellow/mui-example.png new file mode 100644 index 0000000..48e9ab4 Binary files /dev/null and b/Mellow/mui-example.png differ diff --git a/Mellow/mui.lua b/Mellow/mui.lua new file mode 100644 index 0000000..9567553 --- /dev/null +++ b/Mellow/mui.lua @@ -0,0 +1,438 @@ +--[[ +======================================= + + 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', 4, 4, '0123456789abcdefghijklmnopqrstuvwxyz') + 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) + running = true + pending = false + elements = {} + + 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