MineOS/Applications/SmartHouse/SmartHouse.lua

495 lines
16 KiB
Lua
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local sides = require("sides")
local component = require("component")
local advancedLua = require("advancedLua")
local image = require("image")
local buffer = require("doubleBuffering")
local keyboard = require("keyboard")
local GUI = require("GUI")
local ecs = require("ECSAPI")
local MineOSCore = require("MineOSCore")
local computer = require("computer")
local fs = require("filesystem")
-----------------------------------------------------------------------------------------------------------------------------------
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 = 0x1b1b1b,
connectionLines = 0xFFFFFF,
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.modulePath = modulePath
module.icon = image.load(module.modulePath .. "/Icon.pic")
modules[fs.name(module.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].isHidden = 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(x, y, 16, 9)
container.name = name
container.module = modules[componentName]
container.componentProxy = componentProxy
container.componentName = componentName
container.detailsIsHidden = true
x, y = 1, 1
container.deviceImage = container:addImage(x, y, container.module.icon); y = y + 8
local stateButton = container:addButton(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(1, y, container.width, 1, colors.devicesBackground, colors.devicesBackgroundTransparency)
container:addLabel(2, y, container.width - 2, 1, 0xFFFFFF, container.name):setAlignment(GUI.alignment.horizontal.center, GUI.alignment.vertical.top); y = y + 1
container:addLabel(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)
container.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.sendSignal = 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 = GUI.fullScreenWindow()
-- Создаем главное и неебически важное устройство домашнего писюка
local homePC = createDevice(math.floor(window.width / 2 - 8), math.floor(window.height / 2 - 4), "homePC", component.proxy(computer.address()), "Сервак")
homePC.module.icon = image.load(homePC.module.modulePath .. "Server.pic")
homePC.deviceImage.image = homePC.module.icon
-- Перед отрисовкой окна чистим буфер фоном и перехуячиваем позиции объектов групп
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()
buffer.draw()
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()
buffer.draw()
window:handleEvents(1)