Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --------------------------------------------------
- -- GUI.lua
- -- Базовая библиотека GUI для OpenComputers
- -- (упрощённая версия в одном файле)
- --------------------------------------------------
- local component = require("component")
- local computer = require("computer")
- local event = require("event")
- local unicode = require("unicode")
- local gpu = component.gpu
- --------------------------------------------------
- -- Объект GUI
- --------------------------------------------------
- local GUI = {}
- GUI.__index = GUI
- -- Это "корневой" объект, хранящий сцены / окна.
- -- Но можно хранить в нём всё, что угодно.
- function GUI.new()
- local self = setmetatable({}, GUI)
- return self
- end
- --------------------------------------------------
- -- Scene: для управления окнами, общим циклом событий
- --------------------------------------------------
- GUI.Scene = {}
- GUI.Scene.__index = GUI.Scene
- function GUI.Scene:new()
- local scene = {
- windows = {},
- running = false,
- backgroundColor = 0x000000, -- фоновый цвет сцены
- }
- setmetatable(scene, GUI.Scene)
- return scene
- end
- function GUI.Scene:addWindow(window)
- table.insert(self.windows, window)
- window.scene = self
- return window
- end
- function GUI.Scene:draw()
- -- Заливка всей сцены (опционально)
- gpu.setBackground(self.backgroundColor)
- local w, h = gpu.getResolution()
- gpu.fill(1, 1, w, h, " ")
- -- Рисуем все окна
- for _, win in ipairs(self.windows) do
- if win.draw then
- win:draw()
- end
- end
- end
- -- Запуск основного цикла событий
- function GUI.Scene:run()
- self.running = true
- while self.running do
- local ev = { event.pull(0.05) }
- for _, win in ipairs(self.windows) do
- if win.handleEvent then
- win:handleEvent(ev)
- end
- end
- -- (!) Перерисовываем всю сцену каждый раз
- self:draw()
- end
- end
- function GUI.Scene:stop()
- self.running = false
- end
- --------------------------------------------------
- -- Window: Окно, содержащее элементы (виджеты)
- --------------------------------------------------
- GUI.Window = {}
- GUI.Window.__index = GUI.Window
- function GUI.Window:new(x, y, width, height, title)
- local wnd = {
- x = x, y = y,
- width = width,
- height = height,
- title = title or "Window",
- background = 0x2D2D2D,
- foreground = 0xFFFFFF,
- elements = {}, -- список элементов (Button, Label и т.п.)
- scene = nil, -- ссылается на Scene (устанавливается при addWindow)
- }
- setmetatable(wnd, GUI.Window)
- return wnd
- end
- function GUI.Window:addElement(element)
- table.insert(self.elements, element)
- element.parent = self
- return element
- end
- function GUI.Window:draw()
- -- Фон окна
- gpu.setBackground(self.background)
- gpu.setForeground(self.foreground)
- gpu.fill(self.x, self.y, self.width, self.height, " ")
- -- Опционально: нарисовать заголовок
- local titlePosX = self.x + 1
- gpu.set(titlePosX, self.y, "[ " .. self.title .. " ]")
- -- Рисуем элементы
- for _, elem in ipairs(self.elements) do
- if elem.draw then
- elem:draw()
- end
- end
- end
- function GUI.Window:handleEvent(ev)
- -- Рассылаем событие элементам
- for _, elem in ipairs(self.elements) do
- if elem.handleEvent then
- elem:handleEvent(ev)
- end
- end
- end
- --------------------------------------------------
- -- Button: кнопка
- --------------------------------------------------
- GUI.Button = {}
- GUI.Button.__index = GUI.Button
- function GUI.Button:new(x, y, w, h, text, callback)
- local btn = {
- x = x, y = y,
- width = w, height = h,
- text = text or "Button",
- callback = callback, -- вызывается при клике
- background = 0x444444,
- foreground = 0xFFFFFF,
- disabled = false,
- }
- setmetatable(btn, GUI.Button)
- return btn
- end
- function GUI.Button:draw()
- local px = self.parent.x + self.x - 1
- local py = self.parent.y + self.y - 1
- gpu.setBackground(self.background)
- gpu.setForeground(self.foreground)
- -- заливаем область кнопки
- gpu.fill(px, py, self.width, self.height, " ")
- -- выводим текст (по центру строки)
- local textPosX = px + math.floor((self.width - unicode.len(self.text)) / 2)
- local textPosY = py + math.floor(self.height / 2)
- gpu.set(textPosX, textPosY, self.text)
- end
- function GUI.Button:handleEvent(ev)
- if self.disabled then return end
- if ev[1] == "touch" then
- local _, _, tx, ty = table.unpack(ev)
- local px = self.parent.x + self.x - 1
- local py = self.parent.y + self.y - 1
- if tx >= px and tx < px + self.width
- and ty >= py and ty < py + self.height then
- -- клик по кнопке
- if type(self.callback) == "function" then
- self.callback(self)
- end
- end
- end
- end
- --------------------------------------------------
- -- RadioButton: радиокнопка
- -- Обычно нужно хранить "groupId", чтобы только одна
- -- кнопка в группе могла быть "включена".
- --------------------------------------------------
- GUI.RadioButton = {}
- GUI.RadioButton.__index = GUI.RadioButton
- function GUI.RadioButton:new(x, y, label, groupId, checked, onChange)
- local rb = {
- x = x, y = y,
- label = label or "Radio",
- groupId = groupId or "defaultGroup",
- checked = (checked == true),
- onChange = onChange, -- callback, вызывается при переключении
- }
- setmetatable(rb, GUI.RadioButton)
- return rb
- end
- function GUI.RadioButton:draw()
- local px = self.parent.x + self.x - 1
- local py = self.parent.y + self.y - 1
- gpu.setBackground(self.parent.background)
- gpu.setForeground(self.parent.foreground)
- local marker = self.checked and "(*) " or "( ) "
- gpu.set(px, py, marker .. self.label)
- end
- function GUI.RadioButton:handleEvent(ev)
- if ev[1] == "touch" then
- local _, _, tx, ty = table.unpack(ev)
- local px = self.parent.x + self.x - 1
- local py = self.parent.y + self.y - 1
- local strLen = unicode.len(self.label) + 4 -- "( ) " + label
- if tx >= px and tx < px + strLen and ty == py then
- -- щёлкнули по радиокнопке => активируем
- self.checked = true
- -- Нужно отключить (checked=false) у других RadioButton с той же группой
- -- (в рамках окна). Примерно так:
- for _, elem in ipairs(self.parent.elements) do
- if elem ~= self
- and elem.__index == GUI.RadioButton
- and elem.groupId == self.groupId then
- elem.checked = false
- end
- end
- if type(self.onChange) == "function" then
- self.onChange(self)
- end
- end
- end
- end
- --------------------------------------------------
- -- CheckBox: галочка
- --------------------------------------------------
- GUI.CheckBox = {}
- GUI.CheckBox.__index = GUI.CheckBox
- function GUI.CheckBox:new(x, y, label, checked, onChange)
- local cb = {
- x = x, y = y,
- label = label or "Check me",
- checked = (checked == true),
- onChange = onChange,
- }
- setmetatable(cb, GUI.CheckBox)
- return cb
- end
- function GUI.CheckBox:draw()
- local px = self.parent.x + self.x - 1
- local py = self.parent.y + self.y - 1
- gpu.setBackground(self.parent.background)
- gpu.setForeground(self.parent.foreground)
- local marker = self.checked and "[X] " or "[ ] "
- gpu.set(px, py, marker .. self.label)
- end
- function GUI.CheckBox:handleEvent(ev)
- if ev[1] == "touch" then
- local _, _, tx, ty = table.unpack(ev)
- local px = self.parent.x + self.x - 1
- local py = self.parent.y + self.y - 1
- local strLen = unicode.len(self.label) + 4 -- "[ ] " + label
- if tx >= px and tx < px + strLen and ty == py then
- self.checked = not self.checked
- if type(self.onChange) == "function" then
- self.onChange(self, self.checked)
- end
- end
- end
- end
- --------------------------------------------------
- -- Switch: переключатель (On/Off)
- --------------------------------------------------
- GUI.Switch = {}
- GUI.Switch.__index = GUI.Switch
- function GUI.Switch:new(x, y, isOn, onToggle)
- local sw = {
- x = x, y = y,
- isOn = (isOn == true),
- onToggle = onToggle,
- }
- setmetatable(sw, GUI.Switch)
- return sw
- end
- function GUI.Switch:draw()
- local px = self.parent.x + self.x - 1
- local py = self.parent.y + self.y - 1
- gpu.setBackground(self.parent.background)
- gpu.setForeground(self.parent.foreground)
- local text = self.isOn and "[ON ]" or "[OFF]"
- gpu.set(px, py, text)
- end
- function GUI.Switch:handleEvent(ev)
- if ev[1] == "touch" then
- local _, _, tx, ty = table.unpack(ev)
- local px = self.parent.x + self.x - 1
- local py = self.parent.y + self.y - 1
- local strLen = 5 -- "[ON ]" или "[OFF]" (5 символов)
- if tx >= px and tx < px + strLen and ty == py then
- self.isOn = not self.isOn
- if type(self.onToggle) == "function" then
- self.onToggle(self, self.isOn)
- end
- end
- end
- end
- --------------------------------------------------
- -- Label: простой текст
- --------------------------------------------------
- GUI.Label = {}
- GUI.Label.__index = GUI.Label
- function GUI.Label:new(x, y, text)
- local lbl = {
- x = x, y = y,
- text = text or "Label",
- }
- setmetatable(lbl, GUI.Label)
- return lbl
- end
- function GUI.Label:draw()
- local px = self.parent.x + self.x - 1
- local py = self.parent.y + self.y - 1
- gpu.setBackground(self.parent.background)
- gpu.setForeground(self.parent.foreground)
- gpu.set(px, py, self.text)
- end
- function GUI.Label:handleEvent(ev)
- -- По умолчанию Label только отображает текст,
- -- можно добавить логику, если нужно реагировать на клик.
- end
- --------------------------------------------------
- -- TextEdit: поле ввода текста
- --------------------------------------------------
- GUI.TextEdit = {}
- GUI.TextEdit.__index = GUI.TextEdit
- function GUI.TextEdit:new(x, y, width, text, onEnter)
- local te = {
- x = x, y = y,
- width = width,
- text = text or "",
- cursorPos = #text + 1,
- onEnter = onEnter, -- callback при нажатии Enter
- focus = false,
- }
- setmetatable(te, GUI.TextEdit)
- return te
- end
- function GUI.TextEdit:draw()
- local px = self.parent.x + self.x - 1
- local py = self.parent.y + self.y - 1
- gpu.setBackground(self.parent.background)
- gpu.setForeground(self.parent.foreground)
- local shownText = self.text
- if unicode.len(shownText) > self.width - 1 then
- -- если текст длиннее, чем ширина поля, обрежем
- shownText = unicode.sub(shownText, - (self.width - 1))
- end
- -- заливаем фон
- gpu.fill(px, py, self.width, 1, " ")
- -- выводим текст
- gpu.set(px, py, shownText)
- -- если фокус есть, покажем курсор
- if self.focus then
- local cursorX = px + unicode.len(shownText)
- if cursorX < px + self.width then
- gpu.set(cursorX, py, "_")
- end
- end
- end
- function GUI.TextEdit:handleEvent(ev)
- if ev[1] == "touch" then
- local _, _, tx, ty = table.unpack(ev)
- local px = self.parent.x + self.x - 1
- local py = self.parent.y + self.y - 1
- if ty == py and tx >= px and tx < px + self.width then
- self.focus = true
- else
- self.focus = false
- end
- elseif ev[1] == "key_down" and self.focus then
- local _, char, code = table.unpack(ev)
- -- если это символ
- if char > 31 and char < 127 then
- -- добавляем символ
- self.text = self.text .. string.char(char)
- elseif code == 8 then
- -- backspace
- self.text = unicode.sub(self.text, 1, -2)
- elseif code == 13 then
- -- enter
- if self.onEnter then
- self.onEnter(self, self.text)
- end
- end
- end
- end
- --------------------------------------------------
- -- Возвращаем объект GUI
- --------------------------------------------------
- return GUI
Advertisement
Add Comment
Please, Sign In to add comment