-- demo.lua -- Демо-панель ("Dashboard") на pixelgfx + gui.lua. -- Кладите этот файл вместе с pixelgfx.lua, font.lua и gui.lua в одну папку -- (например /home) и запускайте: demo -- -- Показывает: заголовок с часами, боковое меню с РЕАЛЬНЫМ переключением -- раздела, прокручиваемую (!) область контента — колесо мыши, протягивание -- пальцем/курсором, скроллбар справа — с прогресс-барами, чекбоксами и -- слайдером, статус-строку внизу. -- -- Раскладка процентная от реального размера канваса (см. предыдущую -- версию файла) — одинаково хорошо ложится и на Tier 2, и на Tier 3 -- экран. Контент теперь живёт в scrollPanel, поэтому если он не влезает -- по высоте — он просто прокручивается, а не обрезается/расползается. local component = require("component") local gpu = component.gpu local gui = require("gui") -- ---------- разрешение ---------- if gpu.maxResolution then local maxW, maxH = gpu.maxResolution() if maxW and maxH then local curW, curH = gpu.getResolution() if curW ~= maxW or curH ~= maxH then pcall(gpu.setResolution, maxW, maxH) end end end local screenW, screenH = gpu.getResolution() local canvasW, canvasH = screenW, screenH * 2 -- Цветовая палитра (тёмная тема) local COL_BG = 0x14161c local COL_PANEL = 0x1b2030 local COL_PANEL2 = 0x1f2536 local COL_ACCENT = 0x4ea1ff local COL_GOOD = 0x3ddc84 local COL_WARN = 0xffb454 local COL_TEXT = 0xe6e6e6 local COL_TEXT_DIM = 0x8fa3c8 local COL_ITEM = 0x232838 local COL_ITEM_ON = 0x2f3b52 if canvasW < 40 or canvasH < 24 then gpu.setBackground(COL_BG) gpu.setForeground(0xffffff) gpu.fill(1, 1, screenW, screenH, " ") gpu.set(1, 1, "Экран слишком маленький для GUI.") gpu.set(1, 2, "Нужен минимум Tier 2 GPU + экран (80x25).") return end local screen = gui.newScreen(gpu, 1, 1, canvasW, canvasH, {bg = COL_BG}) -- ---------- шапка ---------- local headerH = math.max(9, math.floor(canvasH * 0.13)) screen:add(gui.panel(0, 0, canvasW, headerH, COL_PANEL)) local padX = math.max(3, math.floor(canvasW * 0.012)) screen:add(gui.label(padX, math.floor((headerH - gui.textHeight(1)) / 2), "DASHBOARD", COL_TEXT, 1)) local titleEndX = padX + gui.textWidth("DASHBOARD", 1) + math.max(6, math.floor(canvasW * 0.03)) local clockW = gui.textWidth("00:00:00", 1) local clockX = canvasW - clockW - padX local clockLabel if clockX > titleEndX then clockLabel = screen:add(gui.label(clockX, math.floor((headerH - gui.textHeight(1)) / 2), "00:00:00", COL_TEXT_DIM, 1)) end -- ---------- футер (статус-строка) ---------- local footerH = math.max(8, math.floor(canvasH * 0.10)) screen:add(gui.panel(0, canvasH - footerH, canvasW, footerH, COL_PANEL)) local statusLabel = screen:add(gui.label(padX, canvasH - footerH + math.floor((footerH - gui.textHeight(1)) / 2), "STATUS: OK", COL_GOOD, 1)) -- ---------- тело: сайдбар + прокручиваемый контент ---------- local bodyY = headerH local bodyH = canvasH - headerH - footerH local bodyPad = math.max(2, math.floor(bodyH * 0.03)) local sidebarW = math.max(30, math.floor(canvasW * 0.22)) screen:add(gui.panel(0, bodyY, sidebarW, bodyH, COL_PANEL)) local sections = {"OVERVIEW", "ENERGY", "NETWORK", "STORAGE", "SETTINGS"} local sectionButtons = {} local contentX = sidebarW + math.max(4, math.floor(canvasW * 0.02)) local contentW = canvasW - contentX - math.max(4, math.floor(canvasW * 0.02)) local contentTop = bodyY + bodyPad local contentH = bodyH - 2 * bodyPad local innerPadX = math.max(4, math.floor(contentW * 0.04)) local rowW = contentW - 2 * innerPadX - 4 -- небольшой запас под скроллбар local content = screen:add(gui.scrollPanel(contentX, contentTop, contentW, contentH, {bg = COL_PANEL})) -- ---------- строители содержимого разделов ---------- -- Каждый builder заполняет content:add(...) виджетами в ЛОКАЛЬНЫХ -- координатах (0,0 — верх прокручиваемой области). Если контента больше, -- чем помещается по высоте панели — он не обрезается, а прокручивается. local cpuBar, cpuLabel -- нужны из onTick, поэтому объявлены снаружи local function rowLabel(y, text, color) return content:add(gui.label(innerPadX, y, text, color or COL_TEXT_DIM, 1)) end local function buildOverview() local y = 6 rowLabel(y, "CPU LOAD"); y = y + gui.textHeight(1) + 3 cpuBar = content:add(gui.progressBar(innerPadX, y, rowW, 8, 42, 100, COL_ACCENT)); y = y + 8 + 3 cpuLabel = content:add(gui.label(innerPadX, y, "42%", COL_TEXT, 1)); y = y + gui.textHeight(1) + 12 rowLabel(y, "ENERGY"); y = y + gui.textHeight(1) + 3 content:add(gui.progressBar(innerPadX, y, rowW, 8, 78, 100, COL_GOOD)); y = y + 8 + 14 rowLabel(y, "NETWORK"); y = y + gui.textHeight(1) + 3 content:add(gui.progressBar(innerPadX, y, rowW, 8, 61, 100, COL_WARN)); y = y + 8 + 14 rowLabel(y, "STORAGE"); y = y + gui.textHeight(1) + 3 content:add(gui.progressBar(innerPadX, y, rowW, 8, 35, 100, COL_ACCENT)); y = y + 8 + 6 content:setContentHeight(y) end -- Простой список "имя показателя — значение", демонстрирует прокрутку. local function buildStatList(rows) local y = 6 local rowStep = gui.textHeight(1) + 10 for _, r in ipairs(rows) do content:add(gui.label(innerPadX, y, r[1], COL_TEXT_DIM, 1)) content:add(gui.label(innerPadX + rowW - gui.textWidth(r[2], 1), y, r[2], COL_TEXT, 1)) y = y + rowStep end content:setContentHeight(y) end local function buildEnergy() buildStatList({ {"GRID LOAD", "42%"}, {"SOLAR INPUT", "88%"}, {"BATTERY", "91%"}, {"WIND INPUT", "17%"}, {"REACTOR TEMP", "340C"}, {"OUTPUT", "1200 RF/T"}, {"PEAK TODAY", "1800 RF/T"}, {"UPTIME", "14D 6H"}, }) end local function buildNetwork() buildStatList({ {"PING", "12MS"}, {"UPLOAD", "44 MB/S"}, {"DOWNLOAD", "210 MB/S"}, {"PACKET LOSS", "0%"}, {"NODES ONLINE", "23/24"}, {"FIREWALL", "ON"}, }) end local function buildStorage() buildStatList({ {"USED", "35%"}, {"FREE", "65%"}, {"ITEMS", "184302"}, {"DRIVES", "6"}, {"LAST BACKUP", "2H AGO"}, }) end local brightnessLabel local function buildSettings() local y = 6 local rowStep = 18 rowLabel(y, "AUTO MODE") content:add(gui.checkbox(innerPadX + rowW - 12, y - 2, 12, true, function() end)) y = y + rowStep rowLabel(y, "SOUND") content:add(gui.checkbox(innerPadX + rowW - 12, y - 2, 12, false, function() end)) y = y + rowStep rowLabel(y, "NOTIFICATIONS") content:add(gui.checkbox(innerPadX + rowW - 12, y - 2, 12, true, function() end)) y = y + rowStep + 6 rowLabel(y, "BRIGHTNESS") brightnessLabel = content:add(gui.label(innerPadX + rowW - gui.textWidth("100%", 1), y, "70%", COL_TEXT, 1)) y = y + gui.textHeight(1) + 4 content:add(gui.slider(innerPadX, y, rowW, 0, 100, 70, function(v) brightnessLabel:setText(math.floor(v) .. "%") end)) y = y + 6 + 16 rowLabel(y, "VOLUME") local volumeLabel = content:add(gui.label(innerPadX + rowW - gui.textWidth("100%", 1), y, "50%", COL_TEXT, 1)) y = y + gui.textHeight(1) + 4 content:add(gui.slider(innerPadX, y, rowW, 0, 100, 50, function(v) volumeLabel:setText(math.floor(v) .. "%") end)) y = y + 6 + 10 content:setContentHeight(y) end local builders = {buildOverview, buildEnergy, buildNetwork, buildStorage, buildSettings} local function selectSection(i) for j, b in ipairs(sectionButtons) do b.bg = (j == i) and COL_ITEM_ON or COL_ITEM end content:clear() cpuBar, cpuLabel = nil, nil builders[i]() end -- ---------- боковое меню ---------- do local sideTop = bodyY + bodyPad local sideAvail = bodyH - 2 * bodyPad local n = #sections local btnGap = math.max(1, math.floor(sideAvail * 0.02)) local btnH = math.floor((sideAvail - (n - 1) * btnGap) / n) if btnH < 5 then btnH = 5 n = math.max(1, math.floor((sideAvail + btnGap) / (btnH + btnGap))) end local btnW = sidebarW - 2 * math.max(2, math.floor(sidebarW * 0.08)) local btnX = math.max(2, math.floor(sidebarW * 0.08)) for i = 1, n do local by = sideTop + (i - 1) * (btnH + btnGap) local btn = gui.button(btnX, by, btnW, btnH, sections[i], function() selectSection(i) end, { bg = (i == 1) and COL_ITEM_ON or COL_ITEM, bgActive = COL_ACCENT, }) sectionButtons[i] = btn screen:add(btn) end end selectSection(1) -- ---------- цикл: часы + имитация "живой" нагрузки ---------- screen:run({ tick = 1, onTick = function(s) if clockLabel then clockLabel:setText(os.date("%H:%M:%S")) end if cpuBar and cpuLabel then local v = cpuBar.value + math.random(-4, 4) if v < 5 then v = 5 elseif v > 95 then v = 95 end cpuBar:setValue(v) cpuLabel:setText(math.floor(v) .. "%") if v > 85 then statusLabel:setColor(COL_WARN) statusLabel:setText("STATUS: HIGH LOAD") else statusLabel:setColor(COL_GOOD) statusLabel:setText("STATUS: OK") end end end, })