local GUI = require("GUI") local screen = require("Screen") local color = require("Color") local image = require("Image") local system = require("System") local paths = require("Paths") local system = require("System") local text = require("Text") local filesystem = require("Filesystem") -------------------------------------------------------------------------------- local stack = {} local port = 451 local maxInputs = 18 local scrollSpeed = 2 local from = 3 local maxActiveInputs = 4 local elementWidth = 40 local syncDelay = 4 local historyLimit = 30 local modem local currentEffects = "{}" local localization = system.getCurrentScriptLocalization() local config = { favourites = {}, } local configPath = paths.user.applicationData .. "Nanomachines/Config.cfg" if filesystem.exists(configPath) then config = filesystem.readTable(configPath) end local function saveConfig() filesystem.writeTable(configPath, config) end -------------------------------------------------------------------------------- local workspace, window = system.addWindow(GUI.filledWindow(1, 1, 80, 22, 0xF0F0F0)) local inputsPanel = window:addChild(GUI.panel(1, 1, 19, window.height, 0x2D2D2D)) window.backgroundPanel.localX = inputsPanel.width + 1 window.backgroundPanel.width = window.width - inputsPanel.width local inputsContainer = window:addChild(GUI.container(1, 4, inputsPanel.width, inputsPanel.height - 3)) local layout = window:addChild(GUI.layout(window.backgroundPanel.localX, 1, window.backgroundPanel.width, window.height, 1, 1)) layout:setMargin(1, 1, 0, from) layout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP) local textBox = window:addChild(GUI.textBox(window.backgroundPanel.localX, 1, window.backgroundPanel.width, 5, 0xFFFFFF, 0xB4B4B4, {}, 1, 1)) textBox.eventHandler = nil textBox.localY = window.height - textBox.height + 1 local textBoxButton = window:addChild(GUI.adaptiveButton(1, 1, 2, 0, 0xFFFFFF, 0xD2D2D2, 0xFFFFFF, 0x2D2D2D, " ")) textBoxButton.localX = math.floor(textBox.localX + textBox.width / 2 - textBoxButton.width / 2) local syncContainer = window:addChild(GUI.container(1, 1, window.width, window.height)) local syncPanel = syncContainer:addChild(GUI.panel(1, 1, syncContainer.width, syncContainer.height, 0xF0F0F0)) local syncLayout = syncContainer:addChild(GUI.layout(1, 1, syncContainer.width, syncContainer.height, 1, 1)) syncLayout:addChild(GUI.text(1, 1, 0x2D2D2D, localization.syncWelcome)) local syncTextBox = syncLayout:addChild(GUI.textBox(1, 1, 44, 1, nil, 0xA5A5A5, {}, 1, 0, 0, true, true)) syncTextBox.eventHandler = nil textBoxButton.onTouch = function() textBox.hidden = not textBox.hidden textBoxButton.localY = textBox.hidden and window.height or textBox.localY - 1 textBoxButton.text = textBox.hidden and "▲" or "▼" end local function addMessage(prefix, ...) local message = {...} for i = 1, #message do message[i] = tostring(message[i]) end message = text.wrap(prefix .. table.concat(message, ", "), textBox.width - 2) for i = 1, #message do table.insert(textBox.lines, message[i]) end for i = 1, #textBox.lines - historyLimit do table.remove(textBox.lines, 1) end textBox:scrollToEnd() end local function broadcast(...) addMessage(localization.sent .. ": ", ...) workspace:draw() modem.broadcast(port, "nanomachines", ...) end local function broadcastPut(...) table.insert(stack, {...}) end local function broadcastNext() if #stack > 0 then broadcast(table.unpack(stack[1])) table.remove(stack, 1) end end local function getActiveSwitchCount() local count = 0 for i = 1, maxInputs do if inputsContainer.children[i].state then count = count + 1 end end return count end local function checkSwitches() local count = getActiveSwitchCount() for i = 1, maxInputs do inputsContainer.children[i].colors.active = count > 2 and 0xFF4940 or 0x66DB80 inputsContainer.children[i].disabled = not inputsContainer.children[i].state and count > 3 end end local spacing, width = 3, 6 local startX = math.floor(inputsContainer.width / 2 - width / 2 - spacing) local x, y = startX, 1 for i = 1, maxInputs do local input = inputsContainer:addChild(GUI.switch(x, y, width, 0x66DB80, 0x1E1E1E, 0xE1E1E1, false)) input.onStateChanged = function() checkSwitches() workspace:draw() broadcastPut("setInput", i, input.state) broadcastPut("getActiveEffects") broadcastNext() end x = x + width + spacing if x > inputsContainer.width - 1 then x, y = startX, y + 2 end end for i = 1, maxInputs do local child = inputsContainer.children[i] inputsContainer:addChild(GUI.text(math.floor(child.localX + width / 2 - 1), child.localY + 1, 0x4B4B4B, tostring(i))) end layout:addChild(GUI.text(1, 1, 0x2D2D2D, localization.favourites)) local favouritesLayout = layout:addChild(GUI.layout(1, 1, elementWidth, 3, 1, 1)) favouritesLayout:setDirection(1, 1, GUI.DIRECTION_HORIZONTAL) favouritesLayout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP) local favouritesComboBox = favouritesLayout:addChild(GUI.comboBox(1, 1, favouritesLayout.width - 12, 3, 0xE1E1E1, 0x696969, 0xD2D2D2, 0xA5A5A5)) local favouritesAddButton = favouritesLayout:addChild(GUI.button(1, 1, 5, 3, 0xE1E1E1, 0x787878, 0x2D2D2D, 0xE1E1E1, "+")) local favouritesRemoveButton = favouritesLayout:addChild(GUI.button(1, 1, 5, 3, 0xE1E1E1, 0x787878, 0x2D2D2D, 0xE1E1E1, "-")) layout:addChild(GUI.text(1, 1, 0x2D2D2D, localization.activeEffects)) local effectsLayout = layout:addChild(GUI.layout(1, 1, elementWidth, 1, 1, 1)) effectsLayout:setSpacing(1, 1, 0) effectsLayout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP) layout:addChild(GUI.button(1, 1, elementWidth, 3, 0xE1E1E1, 0x787878, 0x2D2D2D, 0xE1E1E1, localization.saveConfiguration)).onTouch = function() broadcastPut("saveConfiguration") broadcastNext() end layout:addChild(GUI.text(1, 1, 0x2D2D2D, localization.instructionTitle)) local infoTextBox = layout:addChild(GUI.textBox(1, 1, elementWidth, 1, nil, 0xA5A5A5, {localization.instructionData}, 1, 0, 0, true, true)) infoTextBox.eventHandler = nil local function parseEffects() local variants = {} for node in currentEffects:sub(2, -2):gmatch("[^%,]+") do local type, name = node:match("([^%.]+)%.(.+)") table.insert(variants, { type = type or "effect", name = name or node }) end table.sort(variants, function(a, b) return a.type < b.type end) return variants end local function updateEffects(variants) effectsLayout:removeChildren() local function effectDraw(object) screen.drawRectangle(object.x, object.y, object.width, object.height, object.backgroundColor, object.textColor, " ") screen.drawText(object.x + 1, object.y + 1, object.textColor, object.text) end local step = false local function add(text) local object = effectsLayout:addChild(GUI.object(1, 1, elementWidth, 3)) object.draw = effectDraw object.backgroundColor = step and 0xD2D2D2 or 0xE1E1E1 object.textColor = 0x787878 object.text = text step = not step end if #variants > 0 then local width = 14 for i = 1, #variants do add(variants[i].type .. string.rep(" ", width - unicode.len(variants[i].type)) .. variants[i].name) end else add(localization.nothingFound) end effectsLayout.height = #effectsLayout.children * 3 end local function runtimeEventHandler(workspace, object, e1, e2, e3, e4, e5, e6, e7, e8, ...) if e1 == "modem_message" and e6 == "nanomachines" then if e7 == "input" then local child = inputsContainer.children[e8] if not child then GUI.alert(localization.notSynced) end elseif e7 == "effects" then currentEffects = e8 updateEffects(parseEffects()) elseif e7 == "saved" and e8 == false then GUI.alert(localization.saveFailed) end addMessage(localization.received .. ": ", e7, e8, ...) workspace:draw() broadcastNext() elseif e1 == "scroll" then local cell = layout.cells[1][1] local to = -math.floor(cell.childrenHeight / 2) cell.verticalMargin = cell.verticalMargin + (e5 > 0 and scrollSpeed or -scrollSpeed) if cell.verticalMargin > from then cell.verticalMargin = from elseif cell.verticalMargin < to then cell.verticalMargin = to end workspace:draw() end end local syncResult, syncDeadline, syncStarted local function syncUpdate() syncDeadline = computer.uptime() + syncDelay end local function setLines(text) syncTextBox.lines = {text} syncTextBox:update() end local function syncReset() setLines(localization.syncInfo) syncStarted = false syncResult = {} syncUpdate() end favouritesAddButton.onTouch = function() local text, variants = "", parseEffects() for i = 1, #variants do text = text .. variants[i].name .. (i < #variants and ", " or "") end for i = 1, favouritesComboBox:count() do local item = favouritesComboBox:getItem(i) if item.text == text then return end end local inputs = {} for i = 1, maxInputs do inputs[i] = inputsContainer.children[i].state end favouritesComboBox:addItem(text) table.insert(config.favourites, { effects = currentEffects, inputs = inputs, text = text, }) favouritesComboBox.selectedItem = favouritesComboBox:count() saveConfig() end favouritesRemoveButton.onTouch = function() if favouritesComboBox:count() > 0 then table.remove(config.favourites, favouritesComboBox.selectedItem) favouritesComboBox:removeItem(favouritesComboBox.selectedItem) saveConfig() end end favouritesComboBox.onItemSelected = function() -- Оффаем текущие эффекты for i = 1, maxInputs do local child = inputsContainer.children[i] if child.state then child:setState(false) broadcastPut("setInput", i, false) end end -- Парисим эффекты конфига currentEffects = config.favourites[favouritesComboBox.selectedItem].effects updateEffects(parseEffects()) -- Врубаем новые эффекты for i = 1, maxInputs do if config.favourites[favouritesComboBox.selectedItem].inputs[i] then inputsContainer.children[i]:setState(true) broadcastPut("setInput", i, true) end end checkSwitches() workspace:draw() broadcastNext() end for i = 1, #config.favourites do favouritesComboBox:addItem(config.favourites[i].text) end -------------------------------------------------------------------------------- -- syncContainer.hidden = true window.actionButtons:moveToFront() if component.isAvailable("modem") then modem = component.get("modem") if modem.isWireless() then modem.open(port) textBoxButton.onTouch() updateEffects(parseEffects()) syncReset() layout.eventHandler = function(workspace, object, e1, e2, e3, e4, e5, e6, e7, e8, e9) if not e1 then if computer.uptime() >= syncDeadline then syncReset() setLines(localization.syncInfo) workspace:draw() end if not syncStarted then broadcast("setResponsePort", port) end elseif e1 == "modem_message" and e6 == "nanomachines" then if e7 == "port" and not syncStarted then syncStarted = true for i = 1, maxInputs do broadcastPut("getInput", i) end broadcastPut("getActiveEffects") elseif e7 == "input" then syncResult[e8] = e9 elseif e7 == "effects" then currentEffects = e8 updateEffects(parseEffects()) syncContainer.hidden = true for i = 1, #syncResult do inputsContainer.children[i]:setState(syncResult[i]) end checkSwitches() workspace:draw() layout.eventHandler = runtimeEventHandler return end setLines(string.format(localization.syncProgress .. localization.syncContacts, #syncResult, maxInputs)) workspace:draw() syncUpdate() broadcastNext() end end else setLines(localization.notWireless) end else setLines(localization.noModem) end workspace:draw()