diff --git a/.DS_Store b/.DS_Store index 2a184565..7a4ebe36 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Applications.txt b/Applications.txt index f0e71d31..c2de0cfa 100644 --- a/Applications.txt +++ b/Applications.txt @@ -531,6 +531,69 @@ version=1.0, }, ----------------------------------------------------- Приложения -------------------------------------------------------------------------- + { + name="MineOS/Applications/SmartHouse", + url="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/SmartHouse.lua", + type="Application", + icon="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/Icon.pic", + createShortcut="desktop", + version=1.21, + resources={ + { + name="Modules/redstone/Icon.pic", + url="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/Modules/redstone/Icon.pic", + }, + { + name="Modules/redstone/Main.lua", + url="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/Modules/redstone/Main.lua", + }, + -- + { + name="Modules/mfsu/Icon.pic", + url="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/Modules/mfsu/Icon.pic", + }, + { + name="Modules/mfsu/Main.lua", + url="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/Modules/mfsu/Main.lua", + }, + -- + { + name="Modules/screen/Icon.pic", + url="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/Modules/screen/Icon.pic", + }, + { + name="Modules/screen/Main.lua", + url="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/Modules/screen/Main.lua", + }, + -- + { + name="Modules/homePC/Icon.pic", + url="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/Modules/homePC/Icon.pic", + }, + { + name="Modules/homePC/Main.lua", + url="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/Modules/homePC/Main.lua", + }, + -- + { + name="Modules/motion_sensor/Icon.pic", + url="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/Modules/motion_sensor/Icon.pic", + }, + { + name="Modules/motion_sensor/Main.lua", + url="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/Modules/motion_sensor/Main.lua", + }, + -- + { + name="Modules/reactor/Icon.pic", + url="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/Modules/reactor/Icon.pic", + }, + { + name="Modules/reactor/Main.lua", + url="IgorTimofeev/OpenComputers/master/Applications/SmartHouse/Modules/reactor/Main.lua", + }, + }, + }, { name="MineOS/Applications/VK", url="IgorTimofeev/OpenComputers/master/Applications/VK/VK.lua", diff --git a/Applications/.DS_Store b/Applications/.DS_Store index d0da1984..5ef69973 100644 Binary files a/Applications/.DS_Store and b/Applications/.DS_Store differ diff --git a/Applications/SmartHouse/Icon.pic b/Applications/SmartHouse/Icon.pic new file mode 100644 index 00000000..cef5c6d7 Binary files /dev/null and b/Applications/SmartHouse/Icon.pic differ diff --git a/Applications/SmartHouse/Modules/computer/Icon.pic b/Applications/SmartHouse/Modules/computer/Icon.pic new file mode 100644 index 00000000..3d64612a Binary files /dev/null and b/Applications/SmartHouse/Modules/computer/Icon.pic differ diff --git a/Applications/SmartHouse/Modules/computer/Main.lua b/Applications/SmartHouse/Modules/computer/Main.lua new file mode 100755 index 00000000..a09a8d93 --- /dev/null +++ b/Applications/SmartHouse/Modules/computer/Main.lua @@ -0,0 +1,35 @@ + +local module = { + allowSignalConnections = false, + updateWhenModuleDetailsIsHidden = false, +} + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- This method is called once during module initialization +function module.start(moduleContainer) + +end + +-- This method is called on each frame update (every second by default), but only if module details is not hidden or updateWhenModuleDetailsIsHidden == true +function module.update(moduleContainer, eventData) + +end + +-- This method is called when a this module receives virtual signal from the another module, but only if field allowSignalConnections == true +function module.onSignalReceived(moduleContainer, ...) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +return module + + + + + + + + + diff --git a/Applications/SmartHouse/Modules/homePC/Icon.pic b/Applications/SmartHouse/Modules/homePC/Icon.pic new file mode 100644 index 00000000..47635948 Binary files /dev/null and b/Applications/SmartHouse/Modules/homePC/Icon.pic differ diff --git a/Applications/SmartHouse/Modules/homePC/Main.lua b/Applications/SmartHouse/Modules/homePC/Main.lua new file mode 100755 index 00000000..a09a8d93 --- /dev/null +++ b/Applications/SmartHouse/Modules/homePC/Main.lua @@ -0,0 +1,35 @@ + +local module = { + allowSignalConnections = false, + updateWhenModuleDetailsIsHidden = false, +} + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- This method is called once during module initialization +function module.start(moduleContainer) + +end + +-- This method is called on each frame update (every second by default), but only if module details is not hidden or updateWhenModuleDetailsIsHidden == true +function module.update(moduleContainer, eventData) + +end + +-- This method is called when a this module receives virtual signal from the another module, but only if field allowSignalConnections == true +function module.onSignalReceived(moduleContainer, ...) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +return module + + + + + + + + + diff --git a/Applications/SmartHouse/Modules/mfsu/Icon.pic b/Applications/SmartHouse/Modules/mfsu/Icon.pic new file mode 100644 index 00000000..f8db004f Binary files /dev/null and b/Applications/SmartHouse/Modules/mfsu/Icon.pic differ diff --git a/Applications/SmartHouse/Modules/mfsu/Main.lua b/Applications/SmartHouse/Modules/mfsu/Main.lua new file mode 100755 index 00000000..d235cf2b --- /dev/null +++ b/Applications/SmartHouse/Modules/mfsu/Main.lua @@ -0,0 +1,41 @@ + +local module = { + allowSignalConnections = false, + updateWhenModuleDetailsIsHidden = false, +} + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- This method is called once during module initialization +function module.start(moduleContainer) + local x, y = 2, moduleContainer.children[#moduleContainer.children].localPosition.y + 2 + + moduleContainer.capaticyLabel = moduleContainer:addLabel("capaticyLabel", x, y, moduleContainer.width - 2, 1, 0xDDDDDD, "Capacity"):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); y = y + 1 + moduleContainer.chart = moduleContainer:addChart("chart", x, y, moduleContainer.width - 2, math.floor(moduleContainer.width - 2) / 2, 0xFFFFFF, 0x999999, 0xFFDB40, "t", "%", 0, 100, {}) +end + +-- This method is called on each frame update (every second by default), but only if module details is not hidden or updateWhenModuleDetailsIsHidden == true +function module.update(moduleContainer, eventData) + table.insert(moduleContainer.chart.values, math.ceil(moduleContainer.componentProxy.getStored() / moduleContainer.componentProxy.getCapacity() * 100)) + if #moduleContainer.chart.values > moduleContainer.chart.width - 1 then + table.remove(moduleContainer.chart.values, 1) + end +end + +-- This method is called when a this module receives virtual signal from the another module, but only if field allowSignalConnections == true +function module.onSignalReceived(moduleContainer, ...) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +return module + + + + + + + + + diff --git a/Applications/SmartHouse/Modules/motion_sensor/Icon.pic b/Applications/SmartHouse/Modules/motion_sensor/Icon.pic new file mode 100644 index 00000000..51314a2f Binary files /dev/null and b/Applications/SmartHouse/Modules/motion_sensor/Icon.pic differ diff --git a/Applications/SmartHouse/Modules/motion_sensor/Main.lua b/Applications/SmartHouse/Modules/motion_sensor/Main.lua new file mode 100755 index 00000000..285dd496 --- /dev/null +++ b/Applications/SmartHouse/Modules/motion_sensor/Main.lua @@ -0,0 +1,53 @@ + +local module = { + allowSignalConnections = false, + updateWhenModuleDetailsIsHidden = true, +} + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +local sleepValue = 1 + +-- This method is called once during module initialization +function module.start(moduleContainer) + local x, y = 1, moduleContainer.children[#moduleContainer.children].localPosition.y + 2 + + local lines = {limit = 5} + moduleContainer.editWhitelistButton = moduleContainer:addButton("editWhitelistButton", 2, y, moduleContainer.width - 2, 1, 0xDDDDDD, 0x262626, 0xAAAAAA, 0x262626, "Whitelist") + y = y + 2 + moduleContainer.sleepSlider = moduleContainer:addHorizontalSlider("sleepSlider", x, y, moduleContainer.width, 0xFFDB80, 0x000000, 0xFFDB40, 0xDDDDDD, 0.5, 2, sleepValue, false, "Sleep: ") + moduleContainer.sleepSlider.onValueChanged = function() + sleepValue = moduleContainer.sleepSlider.value + end +end + +-- This method is called on each frame update (every second by default), but only if module details is not hidden or updateWhenModuleDetailsIsHidden == true +function module.update(moduleContainer, eventData) + if eventData[1] == "motion" then + if moduleContainer.componentProxy.address == eventData[2] then + if eventData[6] == "ECS" then + moduleContainer:pushSignal("redstone", "setValue", 15) + os.sleep(sleepValue) + moduleContainer:pushSignal("redstone", "setValue", 0) + end + end + end +end + +-- This method is called when a this module receives virtual signal from the another module, but only if field allowSignalConnections == true +function module.onSignalReceived(moduleContainer, ...) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +return module + + + + + + + + + diff --git a/Applications/SmartHouse/Modules/reactor/Icon.pic b/Applications/SmartHouse/Modules/reactor/Icon.pic new file mode 100644 index 00000000..c463dc0f Binary files /dev/null and b/Applications/SmartHouse/Modules/reactor/Icon.pic differ diff --git a/Applications/SmartHouse/Modules/reactor/Main.lua b/Applications/SmartHouse/Modules/reactor/Main.lua new file mode 100755 index 00000000..f7cb205a --- /dev/null +++ b/Applications/SmartHouse/Modules/reactor/Main.lua @@ -0,0 +1,38 @@ + +local module = { + allowSignalConnections = false, + updateWhenModuleDetailsIsHidden = false, +} + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- This method is called once during module initialization +function module.start(moduleContainer) + local x, y = 2, moduleContainer.children[#moduleContainer.children].localPosition.y + 2 + + moduleContainer.heatLabel = moduleContainer:addLabel("heatLabel", x, y, moduleContainer.width - 2, 1, 0xDDDDDD, ""); y = y + 1 + moduleContainer.outputLabel = moduleContainer:addLabel("outputLabel", x, y, moduleContainer.width - 2, 1, 0xDDDDDD, "") +end + +-- This method is called on each frame update (every second by default), but only if module details is not hidden or updateWhenModuleDetailsIsHidden == true +function module.update(moduleContainer, eventData) + moduleContainer.heatLabel.text = "Heat: " .. math.ceil(moduleContainer.componentProxy.getHeat() / moduleContainer.componentProxy.getMaxHeat() * 100) .. "%" + moduleContainer.outputLabel.text = "Output: " .. moduleContainer.componentProxy.getReactorEnergyOutput() +end + +-- This method is called when a this module receives virtual signal from the another module, but only if field allowSignalConnections == true +function module.onSignalReceived(moduleContainer, ...) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +return module + + + + + + + + diff --git a/Applications/SmartHouse/Modules/redstone/Icon.pic b/Applications/SmartHouse/Modules/redstone/Icon.pic new file mode 100644 index 00000000..90841a49 Binary files /dev/null and b/Applications/SmartHouse/Modules/redstone/Icon.pic differ diff --git a/Applications/SmartHouse/Modules/redstone/Main.lua b/Applications/SmartHouse/Modules/redstone/Main.lua new file mode 100755 index 00000000..f7d626c9 --- /dev/null +++ b/Applications/SmartHouse/Modules/redstone/Main.lua @@ -0,0 +1,90 @@ +local sides = require("sides") + +local module = { + allowSignalConnections = true, + updateWhenModuleDetailsIsHidden = false, +} + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +local function changeRedstoneState(moduleContainer, state) + local sidesThatWillBeChanged = {} + local comboBoxText = moduleContainer.sidesComboBox.items[moduleContainer.sidesComboBox.currentItem].text + if comboBoxText == "All" then + sidesThatWillBeChanged = {0,1,2,3,4,5} + -- ecs.error("ALLL SIDES YOPTA") + else + sidesThatWillBeChanged = {sides[string.lower(comboBoxText)]} + -- ecs.error("HERE HERE: " .. sides[string.lower(comboBoxText)]) + end + + for i = 1, #sidesThatWillBeChanged do + moduleContainer.redstoneStates[sidesThatWillBeChanged[i]] = state + moduleContainer.componentProxy.setOutput(sidesThatWillBeChanged[i], state and 15 or 0) + end +end + +-- This method is called once during module initialization +function module.start(moduleContainer) + local x, y = 2, moduleContainer.children[#moduleContainer.children].localPosition.y + 2 + + moduleContainer.redstoneStates = {} + for i = 0, 5 do + local signalStrength = moduleContainer.componentProxy.getOutput(i) + moduleContainer.redstoneStates[i] = signalStrength > 0 and true or false + end + + moduleContainer:addLabel("toggleLabel", x, y, moduleContainer.width - 2, 1, 0xDDDDDD, "Signal:") + moduleContainer.signalSwitch = moduleContainer:addSwitch("redstoneSwitch", moduleContainer.width - 6, y, 6, 0xFFDB40, 0xBBBBBB, 0xFFFFFF, false) + moduleContainer.signalSwitch.onStateChanged = function() + changeRedstoneState(moduleContainer, moduleContainer.signalSwitch.state) + end + y = y + 2 + moduleContainer.emitOnceButton = moduleContainer:addButton("emitOnceButton", 2, y, moduleContainer.width - 2, 1, 0xDDDDDD, 0x262626, 0xAAAAAA, 0x262626, "Emit once") + moduleContainer.emitOnceButton.onTouch = function() + changeRedstoneState(moduleContainer, true) + os.sleep(0.1) + changeRedstoneState(moduleContainer, false) + moduleContainer.signalSwitch.state = false + end + y = y + 2 + moduleContainer:addLabel("sideLabel", x, y, moduleContainer.width - 2, 1, 0xFFFFFF, "Side"):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top) + y = y + 2 + moduleContainer.sidesComboBox = moduleContainer:addComboBox("comboBox", x, y, moduleContainer.width - 2, 1, 0xDDDDDD, 0x262626, 0xCCCCCC, 0x262626, {"All", "Up", "Down", "North", "South", "West", "East"}) + moduleContainer.sidesComboBox.onItemSelected = function() + local comboBoxText = moduleContainer.sidesComboBox.items[moduleContainer.sidesComboBox.currentItem].text + if comboBoxText == "All" then + moduleContainer.signalSwitch.state = false + else + local side = sides[string.lower(comboBoxText)] + moduleContainer.signalSwitch.state = moduleContainer.redstoneStates[side] + end + end + y = y + 2 +end + +-- This method is called on each frame update (every second by default), but only if module details is not hidden or updateWhenModuleDetailsIsHidden == true +function module.update(moduleContainer, eventData) + +end + +-- This method is called when a this module receives virtual signal from the another module, but only if field allowSignalConnections == true +function module.onSignalReceived(moduleContainer, ...) + local data = {...} + if data[1] == "redstone" and data[2] == "setState" and type(data[3]) == "boolean" then + changeRedstoneState(moduleContainer, data[3]) + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +return module + + + + + + + + + diff --git a/Applications/SmartHouse/Modules/screen/Icon.pic b/Applications/SmartHouse/Modules/screen/Icon.pic new file mode 100644 index 00000000..fc9b172c Binary files /dev/null and b/Applications/SmartHouse/Modules/screen/Icon.pic differ diff --git a/Applications/SmartHouse/Modules/screen/Main.lua b/Applications/SmartHouse/Modules/screen/Main.lua new file mode 100755 index 00000000..a09a8d93 --- /dev/null +++ b/Applications/SmartHouse/Modules/screen/Main.lua @@ -0,0 +1,35 @@ + +local module = { + allowSignalConnections = false, + updateWhenModuleDetailsIsHidden = false, +} + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- This method is called once during module initialization +function module.start(moduleContainer) + +end + +-- This method is called on each frame update (every second by default), but only if module details is not hidden or updateWhenModuleDetailsIsHidden == true +function module.update(moduleContainer, eventData) + +end + +-- This method is called when a this module receives virtual signal from the another module, but only if field allowSignalConnections == true +function module.onSignalReceived(moduleContainer, ...) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------- + +return module + + + + + + + + + diff --git a/Applications/SmartHouse/SmartHouse.lua b/Applications/SmartHouse/SmartHouse.lua new file mode 100755 index 00000000..1c96d95e --- /dev/null +++ b/Applications/SmartHouse/SmartHouse.lua @@ -0,0 +1,503 @@ + +local libraries = { + sides = "sides", + component = "component", + advancedLua = "advancedLua", + image = "image", + buffer = "doubleBuffering", + keyboard = "keyboard", + GUI = "GUI", + ecs = "ECSAPI", + windows = "windows", + MineOSCore = "MineOSCore", + computer = "computer", + fs = "filesystem", +} + +for library in pairs(libraries) do if not _G[library] then _G[library] = require(libraries[library]) end end; libraries = nil + +----------------------------------------------------------------------------------------------------------------------------------- + +local window + +local paths = {} +paths.resources = MineOSCore.getCurrentApplicationResourcesDirectory(), +-- paths.resources = "/SmartHouse/" +paths.modules = paths.resources .. "Modules/" + +local colors = { + -- background = image.load("/MineOS/Pictures/Ciri.pic"), + background = 0xDDDDDD, + connectionLines = 0x262626, + devicesBackgroundTransparency = 40, + devicesBackground = 0x0, + devicesButtonBackground = 0xFFFFFF, + devicesButtonText = 0x262626, + devicesInfoText = 0xDDDDDD, + groupsTransparency = nil, +} + +local signals = {} +local groups = {} +local modules = {} +local offset = {x = 0, y = 0} + +----------------------------------------------------------------------------------------------------------------------------------- + +local function loadModule(modulePath) + local success, module = pcall(loadfile(modulePath .. "/Main.lua")) + if success then + module.icon = image.load(modulePath .. "/Icon.pic") + modules[fs.name(modulePath)] = module + else + error("Module loading failed: " .. module) + end +end + +local function loadModules() + modules = {} + for file in fs.list(paths.modules) do + local modulePath = paths.modules .. file + if fs.isDirectory(modulePath) then + loadModule(modulePath) + end + end +end + +local function highlightDiviceAsSignal(deviceContainer, color) + buffer.square(deviceContainer.x - 1, deviceContainer.y, deviceContainer.width + 2, deviceContainer.height, color) + buffer.text(deviceContainer.x - 1, deviceContainer.y - 1, color, string.rep("▄", deviceContainer.width + 2)) + buffer.text(deviceContainer.x - 1, deviceContainer.y + deviceContainer.height, color, string.rep("▀", deviceContainer.width + 2)) +end + +local function createNewGroup(name, color) + table.insert(groups, { + color = color, + name = name, + devices = {} + }) +end + +local function removeDeviceFromGroup(address) + for groupIndex = 1, #groups do + local deviceIndex = 1 + while deviceIndex <= #groups[groupIndex].devices do + if groups[groupIndex].devices[deviceIndex].componentProxy.address == address then + table.remove(groups[groupIndex].devices, deviceIndex) + deviceIndex = deviceIndex - 1 + if #groups[groupIndex].devices == 0 then + groups[groupIndex] = nil + return + end + end + deviceIndex = deviceIndex + 1 + end + end +end + +local function addDeviceToGroup(deviceContainer, name, color) + local groupIndex + + for i = 1, #groups do + if groups[i].name == name then groupIndex = i; break end + end + + if not groupIndex then + createNewGroup(name, color) + groupIndex = #groups + end + + table.insert(groups[groupIndex].devices, deviceContainer) + groups[groupIndex].color = color +end + +local function containerPushSignal(container, ...) + for signalIndex = 1, #signals do + if signals[signalIndex].fromDevice == container then + for toDeviceIndex = 1, #signals[signalIndex].toDevices do + if signals[signalIndex].toDevices[toDeviceIndex].module.onSignalReceived then + signals[signalIndex].toDevices[toDeviceIndex].module.onSignalReceived(signals[signalIndex].toDevices[toDeviceIndex], ...) + end + end + end + end +end + +local function changeChildrenState(container, state) + for i = 3, #container.children do + container.children[i].hidden = state + end +end + +local function createDevice(x, y, componentName, componentProxy, name) + if not modules[componentName] then error("No such module: " .. componentName) end + local container = window:addContainer(name, x, y, 16, 9) + + container.name = name + container.module = modules[componentName] + container.componentProxy = componentProxy + container.componentName = componentName + container.detailsIsHidden = true + + x, y = 1, 1 + local deviceImage = container:addImage("deviceImage", x, y, container.module.icon); y = y + 8 + local stateButton = container:addButton("stateButton", 1, y, container.width, 1, colors.devicesButtonBackground, colors.devicesButtonText, colors.devicesButtonText, colors.devicesButtonBackground, "*") + stateButton.onTouch = function() + container.detailsIsHidden = not container.detailsIsHidden + changeChildrenState(container, container.detailsIsHidden) + if container.detailsIsHidden then + stateButton.localPosition.y = container.backgroundPanel.localPosition.y + else + stateButton.localPosition.y = container.backgroundPanel.localPosition.y + container.backgroundPanel.height + end + end + + container.backgroundPanel = container:addPanel("backgroundPanel", 1, y, container.width, 1, colors.devicesBackground, colors.devicesBackgroundTransparency) + + + container:addLabel("nameLabel", 2, y, container.width - 2, 1, 0xFFFFFF, container.name):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); y = y + 1 + container:addLabel("addressLabel", 2, y, container.width - 2, 1, 0x999999, container.componentProxy.address):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); y = y + 2 + + container.module.start(container) + container.module.update(container, {}) + + y = container.children[#container.children].localPosition.y + (container.children[#container.children].height or 0) + 1 + container.backgroundPanel.height = container.backgroundPanel.height + (y - container.backgroundPanel.y - 1) + + deviceImage.onTouch = function(eventData) + if eventData[5] == 0 then + window.deviceToDrag = container + container:moveToFront() + if keyboard.isShiftDown() then + local x, y = container.x + 8, container.y + 4 + signals.fromDevice = container + signals.toPoint = {x = x, y = y} + end + else + local action = GUI.contextMenu(eventData[3], eventData[4], {"Add to group"}, {"Remove from group"}):show() + if action == "Add to group" then + local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, + {"EmptyLine"}, + {"CenterText", ecs.colors.orange, "Add to group"}, + {"EmptyLine"}, + {"Input", 0xFFFFFF, ecs.colors.orange, "Group #1"}, + {"Color", "Group color", 0xAAAAAA}, + {"EmptyLine"}, + {"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Cancel"}} + ) + + if data[3] == "OK" then + addDeviceToGroup(container, data[1], data[2]) + end + elseif action == "Remove from group" then + removeDeviceFromGroup(container.componentProxy.address) + end + end + end + + local oldDraw = container.draw + container.draw = function() + if container.highlightColor then + highlightDiviceAsSignal(container, container.highlightColor) + end + oldDraw(container) + end + container.pushSignal = containerPushSignal + + changeChildrenState(container, container.detailsIsHidden) + return container +end + +local function drawConnectionLine(x1, y1, x2, y2, thin, color, cutAfterPixels) + local symbol = "" + if x1 < x2 then + if y1 < y2 then + symbol = thin and "┐" or "█" + else + symbol = thin and "┘" or "▀" + end + else + if y1 < y2 then + symbol = thin and "┌" or "█" + else + symbol = thin and "└" or "▀" + end + end + + local x, y, counter, xIncrement, yIncrement = x1, y1, 1, (x1 <= x2 and 1 or -1), (y1 <= y2 and 1 or -1) + + while true do + local bg = buffer.get(x, y) + buffer.set(x, y, bg, color, thin and "─" or "▀") + x = x + xIncrement + + if counter < cutAfterPixels then + counter = counter + 1 + else + x = x + xIncrement + end + + if x1 <= x2 then + if x >= x2 - 1 then break end + else + if x < x2 + 1 then break end + end + end + + buffer.text(x, y, color, symbol) + y = y + yIncrement + + while true do + local bg = buffer.get(x, y) + buffer.set(x, y, bg, color, thin and "│" or "█") + y = y + yIncrement + + if counter < cutAfterPixels then + counter = counter + 1 + else + y = y + yIncrement + end + + if y1 <= y2 then + if y >= y2 then break end + else + if y < y2 then break end + end + end +end + +local function moveDevice(container, x, y) + container.localPosition.x, container.localPosition.y = container.localPosition.x + x, container.localPosition.y + y +end + +local function moveDevices(x, y) + for i = 1, #window.children do + moveDevice(window.children[i], x, y) + end +end + +local function getGroupGeometry(devices) + local geometry = {} + + for deviceIndex = 1, #devices do + geometry.x = math.min(geometry.x or devices[deviceIndex].localPosition.x, devices[deviceIndex].localPosition.x) + geometry.y = math.min(geometry.y or devices[deviceIndex].localPosition.y, devices[deviceIndex].localPosition.y) + + geometry.xMax = math.max(geometry.xMax or devices[deviceIndex].localPosition.x, devices[deviceIndex].localPosition.x) + geometry.yMax = math.max(geometry.yMax or devices[deviceIndex].localPosition.y, devices[deviceIndex].localPosition.y) + end + + local xOffset, yOffset = 2, 2 + geometry.width, geometry.height = geometry.xMax - geometry.x + 16 + xOffset * 2, geometry.yMax - geometry.y + 9 + yOffset * 2 + geometry.x, geometry.y = geometry.x - xOffset, geometry.y - yOffset - 1 + + return geometry +end + +local function drawGroups() + for groupIndex = 1, #groups do + local groupGeometry = getGroupGeometry(groups[groupIndex].devices) + buffer.square(groupGeometry.x, groupGeometry.y, groupGeometry.width, groupGeometry.height, groups[groupIndex].color) + GUI.label(groupGeometry.x, groupGeometry.y + 1, groupGeometry.width, 1, 0x000000, groups[groupIndex].name):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top):draw() + end +end + +local function drawSignals() + local colorPrimary = 0xFF4940 + local step = 16 + + if signals.fromDevice then + drawConnectionLine( + signals.fromDevice.x + 8, + signals.fromDevice.y + 4, + signals.toPoint.x, + signals.toPoint.y, + false, colorPrimary, step + ) + signals.fromDevice.highlightColor = colorPrimary + end + + for signalIndex = 1, #signals do + --Подсветка подключенных устройств + for toDeviceIndex = 1, #signals[signalIndex].toDevices do + signals[signalIndex].toDevices[toDeviceIndex].highlightColor = colorPrimary + drawConnectionLine( + signals[signalIndex].fromDevice.x + 8, + signals[signalIndex].fromDevice.y + 4, + signals[signalIndex].toDevices[toDeviceIndex].x + 8, + signals[signalIndex].toDevices[toDeviceIndex].y + 4, + false, colorPrimary, step + ) + end + -- Подсветка стартового устройства + signals[signalIndex].fromDevice.highlightColor = colorPrimary + end +end + +local function createWindow() + window = windows.fullScreen() + + -- Создаем главное и неебически важное устройство домашнего писюка + local homePC = createDevice(math.floor(window.width / 2 - 8), math.floor(window.height / 2 - 4), "homePC", component.proxy(computer.address()), "Сервак") + + -- Перед отрисовкой окна чистим буфер фоном и перехуячиваем позиции объектов групп + window.onDrawStarted = function() + buffer.clear(colors.background) + drawGroups() + + local xPC, yPC = homePC.x + 8, homePC.y + 4 + for i = 1, #window.children do + drawConnectionLine(xPC, yPC, window.children[i].x + 8, window.children[i].y + 4, true, colors.connectionLines, math.huge) + end + + drawSignals() + end + + window.onAnyEvent = function(eventData) + if eventData[1] == "key_down" then + if eventData[4] == 19 then + colors.background = math.random(0x0, 0xFFFFFF) + elseif eventData[4] == 200 then + moveDevices(0, -2) + elseif eventData[4] == 208 then + moveDevices(0, 2) + elseif eventData[4] == 203 then + moveDevices(-4, 0) + elseif eventData[4] == 205 then + moveDevices(4, 0) + end + elseif eventData[1] == "touch" then + window.dragOffset = {x = eventData[3], y = eventData[4]} + elseif eventData[1] == "drag" then + if keyboard.isShiftDown() then + if signals.fromDevice then + signals.toPoint.x, signals.toPoint.y = eventData[3], eventData[4] + end + else + if eventData[5] == 0 and window.deviceToDrag then + window.deviceToDrag.localPosition.x, window.deviceToDrag.localPosition.y = window.deviceToDrag.localPosition.x + (eventData[3] - window.dragOffset.x), window.deviceToDrag.y + (eventData[4] - window.dragOffset.y) + end + end + window.dragOffset = {x = eventData[3], y = eventData[4]} + elseif eventData[1] == "drop" then + if keyboard.isShiftDown() and signals.fromDevice then + --Создаем новый сигнал, если такового еще не существовало + local signalIndex + for i = 1, #signals do + if signals[i].fromDevice == signals.fromDevice then signalIndex = i; break end + end + if not signalIndex then + table.insert(signals, {fromDevice = signals.fromDevice, toDevices = {}}) + signalIndex = #signals + end + + --Ищем контейнер, на который дропнулось + local container + for i = 1, #window.children do + if window.children[i]:isClicked(eventData[3], eventData[4]) then + container = window.children[i] + break + end + end + + -- Если контейнер найден, то + if container then + --Чекаем, принимает ли модуль этого контейнера сигналы, и если принимает, то + if container.module.allowSignalConnections then + --Проверяем, нет ли часом этого устройства в УЖЕ подключенных + local deviceExists = false + for i = 1, #signals[signalIndex].toDevices do + if signals[signalIndex].toDevices[i] == container then deviceExists = true end + end + + --И если нет, а также если это устройство не является устройством, от которого ВЕЛОСЬ подключение, то + if not deviceExists and container ~= signals[signalIndex].fromDevice then + table.insert(signals[signalIndex].toDevices, container) + end + else + GUI.error("This device doesn't support virtual signal receiving", {title = {text = "Warning", color = 0xFF5555}}) + if #signals[signalIndex].toDevices <= 0 then + signals[signalIndex].fromDevice.highlightColor = nil + signals[signalIndex] = nil + end + end + else + -- А если мы дропнули на пустую точку, то оффаем выделение + if #signals[signalIndex].toDevices <= 0 then + signals[signalIndex].fromDevice.highlightColor = nil + signals[signalIndex] = nil + end + end + + end + + + -- Удаляем временные сигнальные переменные + signals.fromDevice, signals.toPoint = nil, nil + -- Сбрасываем также общее смещение драга + window.dragOffset = nil + window.deviceToDrag = nil + end + + for i = 1, #window.children do + if not window.children[i].detailsIsHidden or window.children[i].module.updateWhenModuleDetailsIsHidden then + window.children[i].module.update(window.children[i], eventData) + end + end + + window:draw() + end +end + +local function getComputerInfo() + local currentComputerAddress = computer.address() + for address, information in pairs(computer.getDeviceInfo()) do + if currentComputerAddress == address then + return information.description + end + end +end + +local function refreshComponents() + local devices = {} + for componentAddress, componentName in pairs(component.list()) do + if modules[componentName] then + table.insert(devices, {componentAddress = componentAddress, componentName = componentName}) + end + end + local x, y = math.floor(buffer.screen.width / 2 - #devices * 18 / 2 + 1), 2 + for i = 1, #devices do + createDevice(x, y, devices[i].componentName, component.proxy(devices[i].componentAddress), devices[i].componentName) + x = x + 18 + end +end + +----------------------------------------------------------------------------------------------------------------------------------- + +buffer.start() + +loadModules() +createWindow() +refreshComponents() +window:draw() +window:handleEvents(1) + + + + + + + + + + + + + + + + + + + + diff --git a/lib/GUI.lua b/lib/GUI.lua index e597cf21..1d4d59e9 100755 --- a/lib/GUI.lua +++ b/lib/GUI.lua @@ -38,9 +38,9 @@ GUI.colors = { text = 0xAAAAAA }, contextMenu = { - background = 0xFFFFFF, separator = 0xAAAAAA, default = { + background = 0xFFFFFF, text = 0x2D2D2D }, disabled = { @@ -57,7 +57,7 @@ GUI.colors = { } } -GUI.contextMenuElementTypes = enum( +GUI.dropDownMenuElementTypes = enum( "default", "separator" ) @@ -80,14 +80,16 @@ GUI.objectTypes = enum( "textBox", "horizontalSlider", "switch", - "progressBar" + "progressBar", + "chart", + "comboBox" ) ----------------------------------------- Primitive objects ----------------------------------------- -- Universal method to check if object was clicked by following coordinates local function isObjectClicked(object, x, y) - if x >= object.x and y >= object.y and x <= object.x + object.width - 1 and y <= object.y + object.height - 1 and not object.disabled and not object.invisible ~= false then return true end + if x >= object.x and y >= object.y and x <= object.x + object.width - 1 and y <= object.y + object.height - 1 and not object.disabled and not object.hidden then return true end return false end @@ -163,26 +165,78 @@ end function GUI.getClickedObject(container, xEvent, yEvent) local clickedObject, clickedIndex for objectIndex = #container.children, 1, -1 do - container.children[objectIndex].x, container.children[objectIndex].y = container.children[objectIndex].localPosition.x + container.x - 1, container.children[objectIndex].localPosition.y + container.y - 1 - if container.children[objectIndex].children and #container.children[objectIndex].children > 0 then - clickedObject, clickedIndex = GUI.getClickedObject(container.children[objectIndex], xEvent, yEvent) - if clickedObject then break end - elseif not container.children[objectIndex].disableClicking and container.children[objectIndex]:isClicked(xEvent, yEvent) then - clickedObject, clickedIndex = container.children[objectIndex], objectIndex - break + if not container.children[objectIndex].hidden then + container.children[objectIndex].x, container.children[objectIndex].y = container.children[objectIndex].localPosition.x + container.x - 1, container.children[objectIndex].localPosition.y + container.y - 1 + if container.children[objectIndex].children and #container.children[objectIndex].children > 0 then + clickedObject, clickedIndex = GUI.getClickedObject(container.children[objectIndex], xEvent, yEvent) + if clickedObject then break end + elseif not container.children[objectIndex].disableClicking and container.children[objectIndex]:isClicked(xEvent, yEvent) then + clickedObject, clickedIndex = container.children[objectIndex], objectIndex + break + end end end return clickedObject, clickedIndex end +local function checkObjectParentExists(object) + if not object.parent then error("Object doesn't have a parent container") end +end + +local function containerObjectIndexOf(object) + checkObjectParentExists(object) + for objectIndex = 1, #object.parent.children do + if object.parent.children[objectIndex] == object then + return objectIndex + end + end +end + +-- Move container's object "closer" to our eyes +local function containerObjectMoveForward(object) + local objectIndex = object:indexOf() + if objectIndex < #object.parent.children then + object.parent.children[index], object.parent.children[index + 1] = swap(object.parent.children[index], object.parent.children[index + 1]) + end +end + +-- Move container's object "more far out" of our eyes +local function containerObjectMoveBackward(object) + local objectIndex = object:indexOf() + if objectIndex > 1 then + object.parent.children[index], object.parent.children[index - 1] = swap(object.parent.children[index], object.parent.children[index - 1]) + end +end + +-- Move container's object to front of all objects +local function containerObjectMoveToFront(object) + local objectIndex = object:indexOf() + table.insert(object.parent.children, object) + table.remove(object.parent.children, objectIndex) +end + +-- Move container's object to back of all objects +local function containerObjectMoveToBack(object) + local objectIndex = object:indexOf() + table.insert(object.parent.children, 1, object) + table.remove(object.parent.children, objectIndex + 1) +end + -- Add any object as children to parent container with specified objectName local function addObjectToContainer(container, objectType, objectName, object) object.name = objectName object.type = objectType object.parent = container + object.indexOf = containerObjectIndexOf + object.moveToFront = containerObjectMoveToFront + object.moveToBack = containerObjectMoveToBack + object.moveForward = containerObjectMoveForward + object.moveBackward = containerObjectMoveBackward object.localPosition = {x = object.x, y = object.y} + table.insert(container.children, object) + return object end @@ -266,18 +320,31 @@ local function addSwitchObjectToContainer(container, objectName, ...) return addObjectToContainer(container, GUI.objectTypes.switch, objectName, GUI.switch(...)) end +-- Add Chart object to container +local function addChartObjectToContainer(container, objectName, ...) + return addObjectToContainer(container, GUI.objectTypes.chart, objectName, GUI.chart(...)) +end + +-- Add ComboBox object to container +local function addComboBoxObjectToContainer(container, objectName, ...) + return addObjectToContainer(container, GUI.objectTypes.comboBox, objectName, GUI.comboBox(...)) +end + -- Recursively draw container's content including all children container's content local function drawContainerContent(container) for objectIndex = 1, #container.children do - container.children[objectIndex].x, container.children[objectIndex].y = container.children[objectIndex].localPosition.x + container.x - 1, container.children[objectIndex].localPosition.y + container.y - 1 - if container.children[objectIndex].children then - -- drawContainerContent(container.children[objectIndex]) - container.children[objectIndex]:draw() - else - if container.children[objectIndex].draw then + if not container.children[objectIndex].hidden then + container.children[objectIndex].x, container.children[objectIndex].y = container.children[objectIndex].localPosition.x + container.x - 1, container.children[objectIndex].localPosition.y + container.y - 1 + if container.children[objectIndex].children then + -- drawContainerContent(container.children[objectIndex]) + -- We use :draw() method against of recursive call. The reason is possible user-defined :draw() reimplementations container.children[objectIndex]:draw() else - error("Container object with index " .. objectIndex .. " and name \"" .. tostring(container.children[objectIndex].name) .. "\" doesn't have :draw() method") + if container.children[objectIndex].draw then + container.children[objectIndex]:draw() + else + error("Container object with index " .. objectIndex .. " and name \"" .. tostring(container.children[objectIndex].name) .. "\" doesn't have :draw() method") + end end end end @@ -315,6 +382,8 @@ function GUI.container(x, y, width, height) container.addHorizontalSlider = addHorizontalSliderObjectToContainer container.addSwitch = addSwitchObjectToContainer container.addProgressBar = addProgressBarObjectToContainer + container.addChart = addChartObjectToContainer + container.addComboBox = addComboBoxObjectToContainer return container end @@ -513,72 +582,70 @@ function GUI.windowActionButtons(x, y, fatSymbol) return container end ------------------------------------------ Context Menu ----------------------------------------- +----------------------------------------- Dropdown Menu ----------------------------------------- -local function drawContextMenuElement(contextMenuObject, elementIndex, isPressed) - if contextMenuObject.elements[elementIndex].type == GUI.contextMenuElementTypes.default then - local textColor = contextMenuObject.elements[elementIndex].disabled and GUI.colors.contextMenu.disabled.text or (contextMenuObject.elements[elementIndex].color or GUI.colors.contextMenu.default.text) - +local function drawDropDownMenuElement(object, itemIndex, isPressed) + local y = object.y + itemIndex * (object.spaceBetweenElements + 1) - 1 + local yText = math.floor(y) + + if object.items[itemIndex].type == GUI.dropDownMenuElementTypes.default then + local textColor = object.items[itemIndex].disabled and object.colors.disabled.text or (object.items[itemIndex].color or object.colors.default.text) + + -- Нажатие if isPressed then - buffer.square(contextMenuObject.x, contextMenuObject.y + elementIndex - 1, contextMenuObject.width, 1, GUI.colors.contextMenu.pressed.background, GUI.colors.contextMenu.pressed.text, " ") - textColor = GUI.colors.contextMenu.pressed.text + buffer.square(object.x, y - object.spaceBetweenElements, object.width, object.spaceBetweenElements * 2 + 1, object.colors.pressed.background, object.colors.pressed.text, " ") + textColor = object.colors.pressed.text end - buffer.text(contextMenuObject.x + 2, contextMenuObject.y + elementIndex - 1, textColor, contextMenuObject.elements[elementIndex].text) - - if contextMenuObject.elements[elementIndex].shortcut then - buffer.text(contextMenuObject.x + contextMenuObject.width - unicode.len(contextMenuObject.elements[elementIndex].shortcut) - 2, contextMenuObject.y + elementIndex - 1, textColor, contextMenuObject.elements[elementIndex].shortcut) + -- Основной текст + buffer.text(object.x + object.sidesOffset, yText, textColor, string.limit(object.items[itemIndex].text, object.width - object.sidesOffset * 2, false)) + -- Шурткатикус + if object.items[itemIndex].shortcut then + buffer.text(object.x + object.width - unicode.len(object.items[itemIndex].shortcut) - object.sidesOffset, yText, textColor, object.items[itemIndex].shortcut) end else - buffer.text(contextMenuObject.x, contextMenuObject.y + elementIndex - 1, GUI.colors.contextMenu.separator, string.rep("─", contextMenuObject.width)) + -- Сепаратор + buffer.text(object.x, yText, object.colors.separator, string.rep("─", object.width)) end end -local function drawContextMenu(contextMenuObject) - buffer.square(contextMenuObject.x, contextMenuObject.y, contextMenuObject.width, contextMenuObject.height, GUI.colors.contextMenu.background, GUI.colors.contextMenu.default.text, " ", GUI.colors.contextMenu.transparency.background) - GUI.windowShadow(contextMenuObject.x, contextMenuObject.y, contextMenuObject.width, contextMenuObject.height, GUI.colors.contextMenu.transparency.shadow, true) - for elementIndex = 1, #contextMenuObject.elements do drawContextMenuElement(contextMenuObject, elementIndex, false) end +local function drawDropDownMenu(object) + buffer.square(object.x, object.y, object.width, object.height, object.colors.default.background, object.colors.default.text, " ", object.colors.transparency) + if object.drawShadow then GUI.windowShadow(object.x, object.y, object.width, object.height, GUI.colors.contextMenu.transparency.shadow, true) end + for itemIndex = 1, #object.items do drawDropDownMenuElement(object, itemIndex, false) end end -local function showContextMenu(contextMenuObject) +local function showDropDownMenu(object) local oldDrawLimit = buffer.getDrawLimit(); buffer.resetDrawLimit() - -- Расчет ширины окна меню - local longestElement, longestShortcut = 0, 0 - for elementIndex = 1, #contextMenuObject.elements do - if contextMenuObject.elements[elementIndex].type == GUI.contextMenuElementTypes.default then - longestElement = math.max(longestElement, unicode.len(contextMenuObject.elements[elementIndex].text)) - if contextMenuObject.elements[elementIndex].shortcut then longestShortcut = math.max(longestShortcut, unicode.len(contextMenuObject.elements[elementIndex].shortcut)) end - end - end - contextMenuObject.width, contextMenuObject.height = longestElement + 4 + (longestShortcut > 0 and longestShortcut + 3 or 0), #contextMenuObject.elements + object.height = #object.items * (object.spaceBetweenElements + 1) + object.spaceBetweenElements - -- А это чтоб за края экрана не лезло - if contextMenuObject.y + contextMenuObject.height >= buffer.screen.height then contextMenuObject.y = buffer.screen.height - contextMenuObject.height end - if contextMenuObject.x + contextMenuObject.width + 1 >= buffer.screen.width then contextMenuObject.x = buffer.screen.width - contextMenuObject.width - 1 end - - local oldPixels = buffer.copy(contextMenuObject.x, contextMenuObject.y, contextMenuObject.width + 1, contextMenuObject.height + 1) + local oldPixels = buffer.copy(object.x, object.y, object.width + 1, object.height + 1) local function quit() - buffer.paste(contextMenuObject.x, contextMenuObject.y, oldPixels) + buffer.paste(object.x, object.y, oldPixels) buffer.draw() buffer.setDrawLimit(oldDrawLimit) end - drawContextMenu(contextMenuObject) + drawDropDownMenu(object) buffer.draw() while true do local e = {event.pull()} if e[1] == "touch" then local objectFound = false - for elementIndex = 1, #contextMenuObject.elements do - if e[3] >= contextMenuObject.x and e[3] <= contextMenuObject.x + contextMenuObject.width - 1 and e[4] == contextMenuObject.y + elementIndex - 1 then + for itemIndex = 1, #object.items do + if + e[3] >= object.x and + e[3] <= object.x + object.width - 1 and + e[4] == object.y + itemIndex * (object.spaceBetweenElements + 1) - 1 + then objectFound = true - if not contextMenuObject.elements[elementIndex].disabled and contextMenuObject.elements[elementIndex].type == GUI.contextMenuElementTypes.default then - drawContextMenuElement(contextMenuObject, elementIndex, true) + if not object.items[itemIndex].disabled and object.items[itemIndex].type == GUI.dropDownMenuElementTypes.default then + drawDropDownMenuElement(object, itemIndex, true) buffer.draw() os.sleep(0.2) quit() - return contextMenuObject.elements[elementIndex].text + return object.items[itemIndex].text, itemIndex end break end @@ -589,43 +656,97 @@ local function showContextMenu(contextMenuObject) end end -local function addContextMenuElement(contextMenuObject, text, disabled, shortcut, color) - local element = {} - element.type = GUI.contextMenuElementTypes.default - element.text = text - element.disabled = disabled - element.shortcut = shortcut - element.color = color or GUI.colors.contextMenu.default.text --OPTIMIZATION +local function addDropDownMenuItem(object, text, disabled, shortcut, color) + local item = {} + item.type = GUI.dropDownMenuElementTypes.default + item.text = text + item.disabled = disabled + item.shortcut = shortcut + item.color = color - table.insert(contextMenuObject.elements, element) - return element + table.insert(object.items, item) + return item end -local function addContextMenuSeparator(contextMenuObject) - local element = {type = GUI.contextMenuElementTypes.separator} - table.insert(contextMenuObject.elements, element) - return element +local function addDropDownMenuSeparator(object) + local item = {type = GUI.dropDownMenuElementTypes.separator} + table.insert(object.items, item) + return item +end + +function GUI.dropDownMenu(x, y, width, spaceBetweenElements, backgroundColor, textColor, backgroundPressedColor, textPressedColor, disabledColor, separatorColor, transparency, items) + local object = GUI.object(x, y, width, 1) + object.colors = { + default = { + background = backgroundColor, + text = textColor + }, + pressed = { + background = backgroundPressedColor, + text = textPressedColor + }, + disabled = { + text = disabledColor + }, + separator = separatorColor, + transparency = transparency + } + object.sidesOffset = 2 + object.spaceBetweenElements = spaceBetweenElements + object.addSeparator = addDropDownMenuSeparator + object.addItem = addDropDownMenuItem + object.items = {} + if items then + for i = 1, #items do + object:addItem(items[i]) + end + end + object.drawShadow = true + object.draw = drawDropDownMenu + object.show = showDropDownMenu + return object +end + +----------------------------------------- Context Menu ----------------------------------------- + +local function showContextMenu(object) + -- Расчет ширины окна меню + local longestItem, longestShortcut = 0, 0 + for itemIndex = 1, #object.items do + if object.items[itemIndex].type == GUI.dropDownMenuElementTypes.default then + longestItem = math.max(longestItem, unicode.len(object.items[itemIndex].text)) + if object.items[itemIndex].shortcut then longestShortcut = math.max(longestShortcut, unicode.len(object.items[itemIndex].shortcut)) end + end + end + object.width = object.sidesOffset + longestItem + (longestShortcut > 0 and 3 + longestShortcut or 0) + object.sidesOffset + object.height = #object.items * (object.spaceBetweenElements + 1) + object.spaceBetweenElements + + -- А это чтоб за края экрана не лезло + if object.y + object.height >= buffer.screen.height then object.y = buffer.screen.height - object.height end + if object.x + object.width + 1 >= buffer.screen.width then object.x = buffer.screen.width - object.width - 1 end + + object:reimplementedShow() end function GUI.contextMenu(x, y, ...) - local argumentElements = {...} + local argumentItems = {...} + local object = GUI.dropDownMenu(x, y, 1, 0, GUI.colors.contextMenu.default.background, GUI.colors.contextMenu.default.text, GUI.colors.contextMenu.pressed.background, GUI.colors.contextMenu.pressed.text, GUI.colors.contextMenu.disabled.text, GUI.colors.contextMenu.separator, GUI.colors.contextMenu.transparency.background) - local contextMenuObject = GUI.object(x, y, 1, 1) - contextMenuObject.elements = {} - contextMenuObject.addElement = addContextMenuElement - contextMenuObject.addSeparator = addContextMenuSeparator - contextMenuObject.show = showContextMenu - contextMenuObject.selectedElement = nil - - for elementIndex = 1, #argumentElements do - if argumentElements[elementIndex] == "-" then - contextMenuObject:addSeparator() + -- Заполняем менюшку парашей + for itemIndex = 1, #argumentItems do + if argumentItems[itemIndex] == "-" then + object:addSeparator() else - contextMenuObject:addElement(argumentElements[elementIndex][1], argumentElements[elementIndex][2], argumentElements[elementIndex][3], argumentElements[elementIndex][4]) + object:addItem(argumentItems[itemIndex][1], argumentItems[itemIndex][2], argumentItems[itemIndex][3], argumentItems[itemIndex][4]) end end - return contextMenuObject + object.reimplementedShow = object.show + object.show = showContextMenu + object.selectedElement = nil + object.spaceBetweenElements = 0 + + return object end ----------------------------------------- Menu ----------------------------------------- @@ -1155,15 +1276,144 @@ function GUI.switch(x, y, width, activeColor, passiveColor, pipeColor, state) return object end +----------------------------------------- Chart object ----------------------------------------- + +local function drawChart(object) + -- Ебошем пездатые оси + for i = object.y, object.y + object.height - 2 do buffer.text(object.x, i, object.colors.axis, "│") end + buffer.text(object.x + 1, object.y + object.height - 1, object.colors.axis, string.rep("─", object.width - 1)) + buffer.text(object.x, object.y + object.height - 1, object.colors.axis, "└") + + if #object.values > 1 then + local oldDrawLimit = buffer.getDrawLimit() + buffer.setDrawLimit(object.x, object.y, object.width, object.height) + + local delta, fieldWidth, fieldHeight = object.maximumValue - object.minimumValue, object.width - 2, object.height - 1 + + -- Рисуем линии значений + local roundValues = object.maximumValue > 10 + local step = 0.2 * fieldHeight + for i = step, fieldHeight, step do + local value = object.minimumValue + delta * (i / fieldHeight) + local stringValue = roundValues and tostring(math.floor(value)) or math.doubleToString(value, 1) + buffer.text(object.x + 1, math.floor(object.y + fieldHeight - i), object.colors.value, string.rep("─", object.width - unicode.len(stringValue) - 2) .. " " .. stringValue) + end + + -- Рисуем графек, йопта + local function getDotPosition(valueIndex) + return + object.x + math.round((fieldWidth * (valueIndex - 1) / (#object.values - 1))) + 1, + object.y + math.round(((fieldHeight - 1) * (object.maximumValue - object.values[valueIndex]) / delta)) + end + + local x, y = getDotPosition(1) + for valueIndex = 2, #object.values do + local xNew, yNew = getDotPosition(valueIndex) + buffer.semiPixelLine(x, y * 2, xNew, yNew * 2, object.colors.chart) + x, y = xNew, yNew + end + + buffer.setDrawLimit(oldDrawLimit) + end + + -- Дорисовываем названия осей + if object.axisNames.y then buffer.text(object.x + 1, object.y, object.colors.axis, object.axisNames.y) end + if object.axisNames.x then buffer.text(object.x + object.width - unicode.len(object.axisNames.x), object.y + object.height - 2, object.colors.axis, object.axisNames.x) end +end + +function GUI.chart(x, y, width, height, axisColor, axisValueColor, chartColor, xAxisName, yAxisName, minimumValue, maximumValue, values) + if minimumValue >= maximumValue then error("Chart's minimum value can't be >= maximum value!") end + local object = GUI.object(x, y, width, height) + object.colors = {axis = axisColor, chart = chartColor, value = axisValueColor} + object.draw = drawChart + object.values = values + object.minimumValue = minimumValue + object.maximumValue = maximumValue + object.axisNames = {x = xAxisName, y = yAxisName} + return object +end + +----------------------------------------- Combo Box Object ----------------------------------------- + +local function drawComboBox(object) + buffer.square(object.x, object.y, object.width, object.height, object.colors.default.background) + local x, y, limit, arrowSize = object.x + 1, math.floor(object.y + object.height / 2), object.width - 5, object.height + buffer.text(x, y, object.colors.default.text, string.limit(object.items[object.currentItem].text, limit, false)) + GUI.button(object.x + object.width - arrowSize * 2 + 1, object.y, arrowSize * 2 - 1, arrowSize, object.colors.arrow.background, object.colors.arrow.text, 0x0, 0x0, object.state and "▲" or "▼"):draw() +end + +local function selectComboBoxItem(object) + object.state = true + object:draw() + + local dropDownMenu = GUI.dropDownMenu(object.x, object.y + object.height, object.width, object.height == 1 and 0 or 1, object.colors.default.background, object.colors.default.text, object.colors.pressed.background, object.colors.pressed.text, GUI.colors.contextMenu.disabled.text, GUI.colors.contextMenu.separator, GUI.colors.contextMenu.transparency.background, object.items) + dropDownMenu.items = object.items + dropDownMenu.sidesOffset = 1 + local _, itemIndex = dropDownMenu:show() + + object.currentItem = itemIndex or object.currentItem + object.state = false + object:draw() + buffer.draw() +end + +function GUI.comboBox(x, y, width, height, backgroundColor, textColor, arrowBackgroundColor, arrowTextColor, items) + local object = GUI.object(x, y, width, height) + object.colors = { + default = { + background = backgroundColor, + text = textColor + }, + pressed = { + background = GUI.colors.contextMenu.pressed.background, + text = GUI.colors.contextMenu.pressed.text + }, + arrow = { + background = arrowBackgroundColor, + text = arrowTextColor + } + } + object.items = {} + object.currentItem = 1 + object.addItem = addDropDownMenuItem + object.addSeparator = addDropDownMenuSeparator + if items then + for i = 1, #items do + object:addItem(items[i]) + end + end + object.draw = drawComboBox + object.selectItem = selectComboBoxItem + object.state = false + return object +end + -------------------------------------------------------------------------------------------------------------------------------- +-- buffer.start() -- buffer.clear(0x1b1b1b) --- buffer.draw(true) --- GUI.switch(2, 2, 8, 0x77FF77, 0x999999, 0xFFFFFF, true):draw() +-- local comboBox = GUI.comboBox(2, 2, 30, 1, 0xFFFFFF, 0x262626, 0xDDDDDD, 0x262626, {"PIC", "RAW", "PNG", "JPG"}) +-- comboBox:selectItem() -- buffer.draw() +-- GUI.chart(2, 10, 40, 20, 0xFFFFFF, 0xBBBBBB, 0xFFDB40, "t", "EU", 0, 2, { +-- 0.5, +-- 0.12 +-- }):draw() + +-- local menu = GUI.dropDownMenu(2, 2, 40, 1, 0xFFFFFF, 0x000000, 0xFFDB40, 0xFFFFFF, 0x999999, 0x777777, 50) +-- menu:addItem("New") +-- menu:addItem("Open") +-- menu:addSeparator() +-- menu:addItem("Save") +-- menu:addItem("Save as") +-- menu:show() + +-- GUI.contextMenu(2, 2, {"Hello"}, {"World"}, "-", {"You are the"}, {"Best of best", false, "^S"}, {"And bestest yopta"}):show() + + -------------------------------------------------------------------------------------------------------------------------------- return GUI diff --git a/lib/doubleBuffering.lua b/lib/doubleBuffering.lua index 1b7e85e4..7e111cd1 100755 --- a/lib/doubleBuffering.lua +++ b/lib/doubleBuffering.lua @@ -8,6 +8,7 @@ local libraries = { for library in pairs(libraries) do if not _G[library] then _G[library] = require(libraries[library]) end end; libraries = nil +local gpu = component.gpu local buffer = {} ------------------------------------------------- Вспомогательные методы ----------------------------------------------------------------- @@ -71,12 +72,12 @@ end -- Инициализация буфера со всеми необходимыми параметрами, вызывается автоматически function buffer.start() - buffer.flush(component.gpu.getResolution()) + buffer.flush(gpu.getResolution()) end -- Изменение разрешения экрана и пересоздание массивов буфера function buffer.changeResolution(width, height) - component.gpu.setResolution(width, height) + gpu.setResolution(width, height) buffer.flush(width, height) end @@ -495,6 +496,42 @@ function buffer.semiPixelSquare(x, y, width, height, color) end end +function buffer.semiPixelLine(x0, y0, x1, y1, color) + local steep = false; + + if math.abs(x0 - x1) < math.abs(y0 - y1 ) then + x0, y0 = swap(x0, y0) + x1, y1 = swap(x1, y1) + steep = true; + end + + if (x0 > x1) then + x0, x1 = swap(x0, x1) + y0, y1 = swap(y0, y1) + end + + local dx = x1 - x0; + local dy = y1 - y0; + local derror2 = math.abs(dy) * 2 + local error2 = 0; + local y = y0; + + for x = x0, x1, 1 do + if steep then + buffer.semiPixelSet(y, x, color); + else + buffer.semiPixelSet(x, y, color) + end + + error2 = error2 + derror2; + + if error2 > dx then + y = y + (y1 > y0 and 1 or -1); + error2 = error2 - dx * 2; + end + end +end + ------------------------------------------- Просчет изменений и отрисовка ------------------------------------------------------------------------ --Функция рассчитывает изменения и применяет их, возвращая то, что было изменено @@ -595,11 +632,11 @@ function buffer.draw(force) --Перебираем все цвета текста и фона, выполняя гпу-операции for foreground in pairs(buffer.screen.changes) do - if currentForeground ~= foreground then component.gpu.setForeground(foreground); currentForeground = foreground end + if currentForeground ~= foreground then gpu.setForeground(foreground); currentForeground = foreground end for background in pairs(buffer.screen.changes[foreground]) do - if currentBackground ~= background then component.gpu.setBackground(background); currentBackground = background end + if currentBackground ~= background then gpu.setBackground(background); currentBackground = background end for i = 1, #buffer.screen.changes[foreground][background], 3 do - component.gpu.set(buffer.screen.changes[foreground][background][i], buffer.screen.changes[foreground][background][i + 1], buffer.screen.changes[foreground][background][i + 2]) + gpu.set(buffer.screen.changes[foreground][background][i], buffer.screen.changes[foreground][background][i + 1], buffer.screen.changes[foreground][background][i + 2]) end end end diff --git a/lib/windows.lua b/lib/windows.lua index 101d3bd7..b59161fa 100755 --- a/lib/windows.lua +++ b/lib/windows.lua @@ -44,19 +44,19 @@ local function buttonHandler(window, object, objectIndex, eventData) object.pressed = true; window:draw(); buffer.draw() os.sleep(0.2) object.pressed = false; window:draw(); buffer.draw() - executeObjectMethod(object.onTouch, object, eventData) + executeObjectMethod(object.onTouch, eventData) end local function tabBarTabHandler(window, object, objectIndex, eventData) object.parent.parent.selectedTab = objectIndex window:draw(); buffer:draw() - executeObjectMethod(object.parent.parent.onTabSwitched, object, eventData) + executeObjectMethod(object.parent.parent.onTabSwitched, eventData) end local function inputTextBoxHandler(window, object, objectIndex, eventData) object:input() window:draw(); buffer:draw() - executeObjectMethod(object.onInputFinished, object, eventData) + executeObjectMethod(object.onInputFinished, eventData) end local function textBoxScrollHandler(window, object, objectIndex, eventData) @@ -67,13 +67,18 @@ local function horizontalSliderHandler(window, object, objectIndex, eventData) local clickPosition = eventData[3] - object.x + 1 object.value = object.minimumValue + (clickPosition * (object.maximumValue - object.minimumValue) / object.width) window:draw(); buffer:draw() - executeObjectMethod(object.onValueChanged, object, eventData) + executeObjectMethod(object.onValueChanged, eventData) end local function switchHandler(window, object, objectIndex, eventData) object.state = not object.state window:draw(); buffer:draw() - executeObjectMethod(object.onStateChanged, object, eventData) + executeObjectMethod(object.onStateChanged, eventData) +end + +local function comboBoxHandler(window, object, objectIndex, eventData) + object:selectItem() + executeObjectMethod(object.onItemSelected, eventData) end function windows.handleEventData(window, eventData) @@ -90,6 +95,8 @@ function windows.handleEventData(window, eventData) horizontalSliderHandler(window, object, objectIndex, eventData) elseif object.type == GUI.objectTypes.switch then switchHandler(window, object, objectIndex, eventData) + elseif object.type == GUI.objectTypes.comboBox then + comboBoxHandler(window, object, objectIndex, eventData) elseif object.onTouch then executeObjectMethod(object.onTouch, eventData) end @@ -118,6 +125,15 @@ function windows.handleEventData(window, eventData) else executeObjectMethod(window.onDrag, eventData) end + elseif eventData[1] == "drop" then + local object, objectIndex = window:getClickedObject(eventData[3], eventData[4]) + if object then + if object.onDrag then + executeObjectMethod(object.onDrop, eventData) + end + else + executeObjectMethod(window.onDrop, eventData) + end elseif eventData[1] == "key_down" then executeObjectMethod(window.onKeyDown, eventData) elseif eventData[1] == "key_up" then @@ -158,7 +174,7 @@ end local function drawWindow(window) if window.onDrawStarted then window.onDrawStarted() end - window:reimplementedDraw() + window:update() if window.drawShadow then GUI.windowShadow(window.x, window.y, window.width, window.height, 50) end if window.onDrawFinished then window.onDrawFinished() end buffer.draw() @@ -171,7 +187,7 @@ local function newWindow(x, y, width, height, minimumWidth, minimumHeight) window.minimumWidth = minimumWidth window.minimumHeight = minimumHeight window.drawShadow = true - window.reimplementedDraw = window.draw + window.update = window.draw window.draw = drawWindow window.handleEventData = windows.handleEventData window.handleEvents = windows.handleEvents