diff --git a/Applications/MultiScreenGUI.lua b/Applications/MultiScreenGUI.lua index 6fefc85f..4cd57589 100644 --- a/Applications/MultiScreenGUI.lua +++ b/Applications/MultiScreenGUI.lua @@ -3,153 +3,277 @@ local component = require("component") local color = require("color") local buffer = require("doubleBuffering") local GUI = require("GUI") +local bit32 = require("bit32") local event = require("event") -local scale = require("scale") local unicode = require("unicode") +local fs = require("filesystem") -------------------------------------------------------------------------------- -local elementWidth = 32 +local configPath = "/MultiScreen.cfg" +local elementWidth = 48 +local baseResolutionWidth = 146 +local baseResolutionHeight = 54 local GPUProxy = buffer.getGPUProxy() local mainScreenAddress = GPUProxy.getScreen() +local config = { + backgroundColor = 0x0, +} + -------------------------------------------------------------------------------- +if fs.exists(configPath) then + config = table.fromFile(configPath) +end + +local function saveConfig() + table.toFile(configPath, config, true) +end + local mainContainer = GUI.fullScreenContainer() mainContainer:addChild(GUI.panel(1, 1, mainContainer.width, mainContainer.height, 0x1E1E1E)) local layout = mainContainer:addChild(GUI.layout(1, 1, mainContainer.width, mainContainer.height, 1, 1)) +local function clearScreens() + for address in component.list("screen") do + if address ~= mainScreenAddress then + GPUProxy.bind(address, false) + GPUProxy.setResolution(baseResolutionWidth, baseResolutionHeight) + GPUProxy.setBackground(config.backgroundColor) + GPUProxy.fill(1, 1, baseResolutionWidth, baseResolutionHeight, " ") + end + end + + GPUProxy.bind(mainScreenAddress, false) +end + local function addButton(text) return layout:addChild(GUI.button(1, 1, elementWidth, 3, 0x3C3C3C, 0x969696, 0x969696, 0x3C3C3C, text)) end +local function addTextBox(lines) + local textBox = layout:addChild(GUI.textBox(1, 1, elementWidth, 16, nil, 0x969696, lines, 1, 0, 0, true, true)) + textBox.eventHandler = nil -local mainMenu - -local function calibrationMenu() - layout:removeChildren() - - local hSlider = layout:addChild(GUI.slider(1, 1, elementWidth, 0x66DB80, 0x0, 0xFFFFFF, 0xAAAAAA, 1, 10, 5, false, "Screens by horizontal: ", "")) - hSlider.roundValues = true - hSlider.height = 2 - - local vSlider = layout:addChild(GUI.slider(1, 1, elementWidth, 0x66DB80, 0x0, 0xFFFFFF, 0xAAAAAA, 1, 10, 4, false, "Screens by vertical: ", "")) - vSlider.roundValues = true - vSlider.height = 2 - - addButton("Next").onTouch = function() - local connectedCount = -1 - for address in component.list("screen") do - connectedCount = connectedCount + 1 - end - - hSlider.value, vSlider.value = math.floor(hSlider.value), math.floor(vSlider.value) - local specifiedCount = hSlider.value * vSlider.value - - if specifiedCount <= connectedCount then - layout:removeChildren() - - local SSX, SSY = 1, 1 - local function screenObjectDraw(object) - buffer.drawRectangle(object.x, object.y, object.width, object.height, (SSX == object.SX and SSY == object.SY) and 0x22FF22 or 0xE1E1E1, 0x0, " ") - end - - local function newScreen(SX, SY) - local object = GUI.object(1, 1, 8, 3) - object.draw = screenObjectDraw - object.SX = SX - object.SY = SY - - return object - end - - local function newScreenLine(SY) - local lineLayout = GUI.layout(1, 1, layout.width, 3, 1, 1) - lineLayout:setDirection(1, 1, GUI.DIRECTION_HORIZONTAL) - lineLayout:setSpacing(1, 1, 2) - - for SX = 1, hSlider.value do - lineLayout:addChild(newScreen(SX, SY)) - end - - return lineLayout - end - - for SY = 1, vSlider.value do - layout:addChild(newScreenLine(SY)) - end - - mainContainer:drawOnScreen() - - local map = {} - local hue, hueStep = 0, 360 / specifiedCount - while true do - local e1, e2 = event.pull("touch") - if e2 ~= mainScreenAddress then - GPUProxy.bind(e2, false) - local RW, RH = scale.getResolution(1) - - GPUProxy.setResolution(RW, RH) - GPUProxy.setBackground(color.HSBToInteger(hue, 1, 1)) - GPUProxy.setForeground(0x0) - GPUProxy.fill(1, 1, RW, RH, " ") - - local text = "Screen " .. SSX .. "x" .. SSY .. " has been calibrated" - GPUProxy.set(math.floor(RW / 2 - unicode.len(text) / 2), math.floor(RH / 2), text) - - GPUProxy.bind(mainScreenAddress, false) - - SSX, hue = SSX + 1, hue + hueStep - if SSX > hSlider.value then - SSX, SSY = 1, SSY + 1 - if SSY > vSlider.value then - table.toFile("/MultiScreen.cfg", map, true) - break - end - end - - map[SSY] = map[SSY] or {} - map[SSY][SSX] = { - address = e2, - resolution = { - width = RW, - height = RH - } - } - - mainContainer:drawOnScreen() - end - end - - GUI.alert("All screens has been successfully calibrated") - mainMenu() - else - GUI.alert("Invalid count of connected screens. You're specified " .. specifiedCount .. " of screens, but there's " .. connectedCount .. " connected screens") - end - end - - mainContainer:drawOnScreen() + return textBox end -mainMenu = function(force) +local function mainMenu(force) layout:removeChildren() + local lines = { + {color = 0xE1E1E1, text = "Welcome to MultiScreen software"}, + " ", + "Here you can combine multiple screens into a single cluster and draw huge images saved in OCIF5 format. There's some useful tips for best experience:", + "• Use maximum size constructions for each screen (8x6 blocks)", + "• Connect more power sources to screens if they're blinking", + " " + } + + if config.map then + table.insert(lines, {color = 0xE1E1E1, text = "Current cluster properties:"}) + table.insert(lines, " ") + local width, height = #config.map[1], #config.map + table.insert(lines, width .. "x" .. height .. " screen blocks") + table.insert(lines, width * baseResolutionWidth .. "x" .. height * baseResolutionHeight .. " OpenComputers pixels") + table.insert(lines, width * baseResolutionWidth * 2 .. "x" .. height * baseResolutionHeight * 4 .. " pixels using Braille font") + else + table.insert(lines, {color = 0xE1E1E1, text = "Calibrate your system before starting"}) + end + + addTextBox(lines) + local actionComboBox = layout:addChild(GUI.comboBox(1, 1, elementWidth, 3, 0xEEEEEE, 0x2D2D2D, 0xCCCCCC, 0x888888)) actionComboBox:addItem("Draw image") actionComboBox:addItem("Clear screens") actionComboBox:addItem("Calibrate") - addButton("Next").onTouch = function() - if actionComboBox.selectedItem == 1 then - - elseif actionComboBox.selectedItem == 2 then + local filesystemChooser = layout:addChild(GUI.filesystemChooser(1, 1, elementWidth, 3, 0xE1E1E1, 0x888888, 0x3C3C3C, 0x888888, nil, "Open", "Cancel", "Choose file", "/")) + filesystemChooser:setMode(GUI.IO_MODE_OPEN, GUI.IO_MODE_FILE) + filesystemChooser:addExtensionFilter(".pic") + local colorSelector = layout:addChild(GUI.colorSelector(2, 2, elementWidth, 3, 0xFF55FF, "Choose color")) + + local actionButton = addButton("Next") + + actionComboBox.onItemSelected = function() + filesystemChooser.hidden = actionComboBox.selectedItem ~= 1 + colorSelector.hidden = actionComboBox.selectedItem ~= 2 + actionButton.disabled = actionComboBox.selectedItem == 1 and (not filesystemChooser.path or not config.map) + end + + filesystemChooser.onSubmit = function() + actionComboBox.onItemSelected() + mainContainer:drawOnScreen() + end + + actionButton.onTouch = function() + if actionComboBox.selectedItem == 1 then + local file = io.open(filesystemChooser.path, "rb") + + local signature = file:read(4) + if signature == "OCIF" then + local encodingMethod = string.byte(file:read(1)) + if encodingMethod == 5 then + local width = bit32.byteArrayToNumber({string.byte(file:read(2), 1, 2)}) + local height = bit32.byteArrayToNumber({string.byte(file:read(2), 1, 2)}) + + clearScreens() + + local background, foreground, currentBackground, currentForeground, currentAddress + for y = 1, height do + for x = 1, width do + background = color.to24Bit(string.byte(file:read(1))) + foreground = color.to24Bit(string.byte(file:read(1))) + file:read(1) + + local xMonitor = math.ceil(x / baseResolutionWidth) + local yMonitor = math.ceil(y / baseResolutionHeight) + + if config.map[yMonitor] and config.map[yMonitor][xMonitor] then + if currentAddress ~= config.map[yMonitor][xMonitor] then + GPUProxy.bind(config.map[yMonitor][xMonitor], false) + GPUProxy.setBackground(background) + GPUProxy.setForeground(foreground) + + currentAddress, currentBackground, currentForeground = config.map[yMonitor][xMonitor], background, foreground + end + + if currentBackground ~= background then + GPUProxy.setBackground(background) + currentBackground = background + end + + if currentForeground ~= foreground then + GPUProxy.setForeground(foreground) + currentForeground = foreground + end + + GPUProxy.set(x - (xMonitor - 1) * baseResolutionWidth, y - (yMonitor - 1) * baseResolutionHeight, fs.readUnicodeChar(file)) + end + end + end + + file:close() + + GPUProxy.bind(mainScreenAddress, false) + GUI.alert("Done.") + else + file:close() + GUI.alert("Wrong encodingMethod: " .. tostring(encodingMethod)) + end + else + file:close() + GUI.alert("Wrong signature: " .. tostring(signature)) + end + elseif actionComboBox.selectedItem == 2 then + config.backgroundColor = colorSelector.color + saveConfig() + clearScreens() else - calibrationMenu() + layout:removeChildren() + + local hSlider = layout:addChild(GUI.slider(1, 1, elementWidth, 0x66DB80, 0x0, 0xFFFFFF, 0xAAAAAA, 1, 10, 5, false, "Screens by horizontal: ", "")) + hSlider.roundValues = true + hSlider.height = 2 + + local vSlider = layout:addChild(GUI.slider(1, 1, elementWidth, 0x66DB80, 0x0, 0xFFFFFF, 0xAAAAAA, 1, 10, 4, false, "Screens by vertical: ", "")) + vSlider.roundValues = true + vSlider.height = 2 + + addButton("Next").onTouch = function() + local connectedCount = -1 + for address in component.list("screen") do + connectedCount = connectedCount + 1 + end + + hSlider.value, vSlider.value = math.floor(hSlider.value), math.floor(vSlider.value) + local specifiedCount = hSlider.value * vSlider.value + + if specifiedCount <= connectedCount then + layout:removeChildren() + + local SSX, SSY = 1, 1 + local function screenObjectDraw(object) + buffer.drawRectangle(object.x, object.y, object.width, object.height, (SSX == object.SX and SSY == object.SY) and 0x22FF22 or 0xE1E1E1, 0x0, " ") + end + + local function newScreen(SX, SY) + local object = GUI.object(1, 1, 8, 3) + object.draw = screenObjectDraw + object.SX = SX + object.SY = SY + + return object + end + + local function newScreenLine(SY) + local lineLayout = GUI.layout(1, 1, layout.width, 3, 1, 1) + lineLayout:setDirection(1, 1, GUI.DIRECTION_HORIZONTAL) + lineLayout:setSpacing(1, 1, 2) + + for SX = 1, hSlider.value do + lineLayout:addChild(newScreen(SX, SY)) + end + + return lineLayout + end + + for SY = 1, vSlider.value do + layout:addChild(newScreenLine(SY)) + end + + mainContainer:drawOnScreen() + + clearScreens(0x0) + + config.map = {} + local hue, hueStep = 0, 360 / specifiedCount + while true do + local e1, e2 = event.pull("touch") + if e2 ~= mainScreenAddress then + GPUProxy.bind(e2, false) + + GPUProxy.setResolution(baseResolutionWidth, baseResolutionHeight) + GPUProxy.setBackground(color.HSBToInteger(hue, 1, 1)) + GPUProxy.setForeground(0x0) + GPUProxy.fill(1, 1, baseResolutionWidth, baseResolutionHeight, " ") + + local text = "Screen " .. SSX .. "x" .. SSY .. " has been calibrated" + GPUProxy.set(math.floor(baseResolutionWidth / 2 - unicode.len(text) / 2), math.floor(baseResolutionHeight / 2), text) + + GPUProxy.bind(mainScreenAddress, false) + + config.map[SSY] = config.map[SSY] or {} + config.map[SSY][SSX] = e2 + + SSX, hue = SSX + 1, hue + hueStep + if SSX > hSlider.value then + SSX, SSY = 1, SSY + 1 + if SSY > vSlider.value then + saveConfig() + break + end + end + + mainContainer:drawOnScreen() + end + end + + GUI.alert("All screens has been successfully calibrated") + mainMenu() + else + GUI.alert("Invalid count of connected screens. You're specified " .. specifiedCount .. " of screens, but there's " .. connectedCount .. " connected screens") + end + end + + mainContainer:drawOnScreen() end end + actionComboBox.onItemSelected() mainContainer:drawOnScreen(force) end