mirror of
https://github.com/IgorTimofeev/MineOS.git
synced 2025-12-20 11:09:21 +01:00
505 lines
16 KiB
Lua
Executable File
505 lines
16 KiB
Lua
Executable File
|
||
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 windows = require("windows")
|
||
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 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 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 = windows.fullScreen()
|
||
|
||
-- Создаем главное и неебически важное устройство домашнего писюка
|
||
local homePC = createDevice(math.floor(window.width / 2 - 8), math.floor(window.height / 2 - 4), "homePC", component.proxy(computer.address()), "Сервак")
|
||
local computerDescription = getComputerInfo()
|
||
if computerDescription == "Server" then homePC.module.icon = image.load(homePC.module.modulePath .. "Server.pic"); homePC.deviceImage.image = homePC.module.icon end
|
||
|
||
-- Перед отрисовкой окна чистим буфер фоном и перехуячиваем позиции объектов групп
|
||
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)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|