diff --git a/graphics/elements/controls/app.lua b/graphics/elements/controls/app.lua index f610393..4ac936d 100644 --- a/graphics/elements/controls/app.lua +++ b/graphics/elements/controls/app.lua @@ -30,7 +30,7 @@ local function app_button(args) element.assert(type(args.app_fg_bg) == "table", "app_fg_bg is a required field") args.height = 4 - args.width = 5 + args.width = 7 -- create new graphics element base object local e = element.new(args) @@ -46,7 +46,7 @@ local function app_button(args) end -- draw icon - e.w_set_cur(1, 1) + e.w_set_cur(2, 1) e.w_set_fgd(fgd) e.w_set_bkg(bkg) e.w_write("\x9f\x83\x83\x83") @@ -55,16 +55,16 @@ local function app_button(args) e.w_write("\x90") e.w_set_fgd(fgd) e.w_set_bkg(bkg) - e.w_set_cur(1, 2) + e.w_set_cur(2, 2) e.w_write("\x95 ") e.w_set_fgd(bkg) e.w_set_bkg(fgd) e.w_write("\x95") - e.w_set_cur(1, 3) + e.w_set_cur(2, 3) e.w_write("\x82\x8f\x8f\x8f\x81") -- write the icon text - e.w_set_cur(3, 2) + e.w_set_cur(4, 2) e.w_set_fgd(fgd) e.w_set_bkg(bkg) e.w_write(args.text) diff --git a/graphics/elements/controls/sidebar.lua b/graphics/elements/controls/sidebar.lua index fef8a8a..56fc89c 100644 --- a/graphics/elements/controls/sidebar.lua +++ b/graphics/elements/controls/sidebar.lua @@ -8,13 +8,7 @@ local element = require("graphics.element") local MOUSE_CLICK = core.events.MOUSE_CLICK ----@class sidebar_tab ----@field char string character identifier ----@field color cpair tab colors (fg/bg) - ---@class sidebar_args ----@field tabs table sidebar tab options ----@field callback function function to call on tab change ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted @@ -27,21 +21,16 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK ---@param args sidebar_args ---@return graphics_element element, element_id id local function sidebar(args) - element.assert(type(args.tabs) == "table", "tabs is a required field") - element.assert(#args.tabs > 0, "at least one tab is required") - element.assert(type(args.callback) == "function", "callback is a required field") - args.width = 3 -- create new graphics element base object local e = element.new(args) - element.assert(e.frame.h >= (#args.tabs * 3), "height insufficent to display all tabs") - -- default to 1st tab e.value = 1 local was_pressed = false + local tabs = {} -- show the button state ---@param pressed? boolean if the currently selected tab should appear as actively pressed @@ -51,10 +40,18 @@ local function sidebar(args) was_pressed = pressed pressed_idx = pressed_idx or e.value - for i = 1, #args.tabs do - local tab = args.tabs[i] ---@type sidebar_tab + -- clear + e.w_set_fgd(e.fg_bg.fgd) + e.w_set_bkg(e.fg_bg.bkg) + for y = 1, e.frame.h do + e.w_set_cur(1, y) + e.w_write(" ") + end - local y = ((i - 1) * 3) + 1 + -- draw tabs + for i = 1, #tabs do + local tab = tabs[i] ---@type sidebar_tab + local y = tab.y_start e.w_set_cur(1, y) @@ -66,13 +63,29 @@ local function sidebar(args) e.w_set_bkg(tab.color.bkg) end - e.w_write(" ") - e.w_set_cur(1, y + 1) - if e.value == i then - e.w_write(" " .. tab.char .. "\x10") - else e.w_write(" " .. tab.char .. " ") end - e.w_set_cur(1, y + 2) - e.w_write(" ") + if tab.tall then + e.w_write(" ") + e.w_set_cur(1, y + 1) + end + + e.w_write(tab.label) + + if tab.tall then + e.w_set_cur(1, y + 2) + e.w_write(" ") + end + end + end + + -- determine which tab was pressed + ---@param y integer y coordinate + local function find_tab(y) + for i = 1, #tabs do + local tab = tabs[i] ---@type sidebar_tab + + if y >= tab.y_start and y <= tab.y_end then + return i + end end end @@ -81,23 +94,23 @@ local function sidebar(args) function e.handle_mouse(event) -- determine what was pressed if e.enabled then - local cur_idx = math.ceil(event.current.y / 3) - local ini_idx = math.ceil(event.initial.y / 3) + local cur_idx = find_tab(event.current.y) + local ini_idx = find_tab(event.initial.y) - if args.tabs[cur_idx] ~= nil then + if tabs[cur_idx] ~= nil then if event.type == MOUSE_CLICK.TAP then e.value = cur_idx draw(true) -- show as unpressed in 0.25 seconds tcd.dispatch(0.25, function () draw(false) end) - args.callback(e.value) + tabs[cur_idx].callback() elseif event.type == MOUSE_CLICK.DOWN then draw(true, cur_idx) elseif event.type == MOUSE_CLICK.UP then if cur_idx == ini_idx and e.in_frame_bounds(event.current.x, event.current.y) then e.value = cur_idx draw(false) - args.callback(e.value) + tabs[cur_idx].callback() else draw(false) end end elseif event.type == MOUSE_CLICK.UP then @@ -113,6 +126,35 @@ local function sidebar(args) draw(false) end + -- update the sidebar nav options + ---@param items table sidebar entries + function e.on_update(items) + local next_y = 1 + + tabs = {} + + for i = 1, #items do + local item = items[i] + local height = util.trinary(item.tall, 3, 1) + + ---@class sidebar_tab + local entry = { + y_start = next_y, + y_end = next_y + height - 1, + tall = item.tall, + label = item.label, + color = item.color, + callback = item.callback + } + + next_y = next_y + height + + tabs[i] = entry + end + + draw() + end + -- element redraw e.redraw = draw diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 30f4879..cc50df6 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -5,8 +5,10 @@ local log = require("scada-common.log") local psil = require("scada-common.psil") local types = require("scada-common.types") +local util = require("scada-common.util") local ALARM = types.ALARM +local ALARM_STATE = types.ALARM_STATE ---@todo nominal trip time is ping (0ms to 10ms usually) local WARN_TT = 40 @@ -72,6 +74,10 @@ function iocontrol.alloc_nav() self.pane = root_pane end + function io.nav.set_sidebar(sidebar) + self.sidebar = sidebar + end + -- register an app ---@param app_id POCKET_APP_ID app ID ---@param container graphics_element element that contains this app (usually a Div) @@ -79,18 +85,37 @@ function iocontrol.alloc_nav() function io.nav.register_app(app_id, container, pane) ---@class pocket_app local app = { + loaded = false, + load = nil, root = { _p = nil, _c = {}, nav_to = function () end, tasks = {} }, ---@type nav_tree_page cur_page = nil, ---@type nav_tree_page pane = pane, - paned_pages = {} + paned_pages = {}, + sidebar_items = {} } + app.load = function () app.loaded = true end + -- delayed set of the pane if it wasn't ready at the start ---@param root_pane graphics_element multipane function app.set_root_pane(root_pane) app.pane = root_pane end + function app.set_sidebar(items) + app.sidebar_items = items + if self.sidebar then self.sidebar.update(items) end + end + + -- function to run on initial load into memory + ---@param on_load function callback + function app.set_on_load(on_load) + app.load = function () + on_load() + app.loaded = true + end + end + -- if a pane was provided, this will switch between numbered pages ---@param idx integer page index function app.switcher(idx) @@ -160,9 +185,16 @@ function iocontrol.alloc_nav() -- open a given app ---@param app_id POCKET_APP_ID function io.nav.open_app(app_id) - if self.apps[app_id] then + local app = self.apps[app_id] ---@type pocket_app + if app then + if not app.loaded then app.load() end + self.cur_app = app_id self.pane.set_value(app_id) + + if #app.sidebar_items > 0 then + self.sidebar.update(app.sidebar_items) + end else log.debug("tried to open unknown app") end @@ -262,6 +294,16 @@ function iocontrol.init_fac(conf, temp_scale) auto_ramping = false, auto_saturated = false, + auto_scram = false, + ---@type ascram_status + ascram_status = { + matrix_dc = false, + matrix_fill = false, + crit_alarm = false, + radiation = false, + gen_fault = false + }, + ---@type WASTE_PRODUCT auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM, auto_pu_fallback_active = false, @@ -282,6 +324,179 @@ function iocontrol.init_fac(conf, temp_scale) env_d_ps = psil.create(), env_d_data = {} } + + -- create induction and SPS tables (currently only 1 of each is supported) + table.insert(io.facility.induction_ps_tbl, psil.create()) + table.insert(io.facility.induction_data_tbl, {}) + table.insert(io.facility.sps_ps_tbl, psil.create()) + table.insert(io.facility.sps_data_tbl, {}) + + -- determine tank information + if io.facility.tank_mode == 0 then + io.facility.tank_defs = {} + -- on facility tank mode 0, setup tank defs to match unit tank option + for i = 1, conf.num_units do + io.facility.tank_defs[i] = util.trinary(conf.cooling.r_cool[i].TankConnection, 1, 0) + end + + io.facility.tank_list = { table.unpack(io.facility.tank_defs) } + else + -- decode the layout of tanks from the connections definitions + local tank_mode = io.facility.tank_mode + local tank_defs = io.facility.tank_defs + local tank_list = { table.unpack(tank_defs) } + + local function calc_fdef(start_idx, end_idx) + local first = 4 + for i = start_idx, end_idx do + if io.facility.tank_defs[i] == 2 then + if i < first then first = i end + end + end + return first + end + + if tank_mode == 1 then + -- (1) 1 total facility tank (A A A A) + local first_fdef = calc_fdef(1, #tank_defs) + for i = 1, #tank_defs do + if i > first_fdef and tank_defs[i] == 2 then + tank_list[i] = 0 + end + end + elseif tank_mode == 2 then + -- (2) 2 total facility tanks (A A A B) + local first_fdef = calc_fdef(1, math.min(3, #tank_defs)) + for i = 1, #tank_defs do + if (i ~= 4) and (i > first_fdef) and (tank_defs[i] == 2) then + tank_list[i] = 0 + end + end + elseif tank_mode == 3 then + -- (3) 2 total facility tanks (A A B B) + for _, a in pairs({ 1, 3 }) do + local b = a + 1 + if (tank_defs[a] == 2) and (tank_defs[b] == 2) then + tank_list[b] = 0 + end + end + elseif tank_mode == 4 then + -- (4) 2 total facility tanks (A B B B) + local first_fdef = calc_fdef(2, #tank_defs) + for i = 1, #tank_defs do + if (i ~= 1) and (i > first_fdef) and (tank_defs[i] == 2) then + tank_list[i] = 0 + end + end + elseif tank_mode == 5 then + -- (5) 3 total facility tanks (A A B C) + local first_fdef = calc_fdef(1, math.min(2, #tank_defs)) + for i = 1, #tank_defs do + if (not (i == 3 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then + tank_list[i] = 0 + end + end + elseif tank_mode == 6 then + -- (6) 3 total facility tanks (A B B C) + local first_fdef = calc_fdef(2, math.min(3, #tank_defs)) + for i = 1, #tank_defs do + if (not (i == 1 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then + tank_list[i] = 0 + end + end + elseif tank_mode == 7 then + -- (7) 3 total facility tanks (A B C C) + local first_fdef = calc_fdef(3, #tank_defs) + for i = 1, #tank_defs do + if (not (i == 1 or i == 2)) and (i > first_fdef) and (tank_defs[i] == 2) then + tank_list[i] = 0 + end + end + end + + io.facility.tank_list = tank_list + end + + -- create facility tank tables + for i = 1, #io.facility.tank_list do + if io.facility.tank_list[i] == 2 then + table.insert(io.facility.tank_ps_tbl, psil.create()) + table.insert(io.facility.tank_data_tbl, {}) + end + end + + -- create unit data structures + io.units = {} + for i = 1, conf.num_units do + ---@class pioctl_unit + local entry = { + unit_id = i, + + num_boilers = 0, + num_turbines = 0, + num_snas = 0, + has_tank = conf.cooling.r_cool[i].TankConnection, + + control_state = false, + burn_rate_cmd = 0.0, + radiation = types.new_zero_radiation_reading(), + + sna_peak_rate = 0.0, + sna_max_rate = 0.0, + sna_out_rate = 0.0, + + waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM, + waste_product = types.WASTE_PRODUCT.PLUTONIUM, + + -- auto control group + a_group = 0, + + ---@type alarms + alarms = { ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE }, + + annunciator = {}, ---@type annunciator + + unit_ps = psil.create(), + reactor_data = {}, ---@type reactor_db + + boiler_ps_tbl = {}, + boiler_data_tbl = {}, + + turbine_ps_tbl = {}, + turbine_data_tbl = {}, + + tank_ps_tbl = {}, + tank_data_tbl = {} + } + + -- on other facility modes, overwrite unit TANK option with facility tank defs + if io.facility.tank_mode ~= 0 then + entry.has_tank = conf.cooling.fac_tank_defs[i] > 0 + end + + -- create boiler tables + for _ = 1, conf.cooling.r_cool[i].BoilerCount do + table.insert(entry.boiler_ps_tbl, psil.create()) + table.insert(entry.boiler_data_tbl, {}) + end + + -- create turbine tables + for _ = 1, conf.cooling.r_cool[i].TurbineCount do + table.insert(entry.turbine_ps_tbl, psil.create()) + table.insert(entry.turbine_data_tbl, {}) + end + + -- create tank tables + if io.facility.tank_defs[i] == 1 then + table.insert(entry.tank_ps_tbl, psil.create()) + table.insert(entry.tank_data_tbl, {}) + end + + entry.num_boilers = #entry.boiler_data_tbl + entry.num_turbines = #entry.turbine_data_tbl + + table.insert(io.units, entry) + end end -- set network link state diff --git a/pocket/ui/apps/dummy_app.lua b/pocket/ui/apps/dummy_app.lua index fe21db3..d7845a7 100644 --- a/pocket/ui/apps/dummy_app.lua +++ b/pocket/ui/apps/dummy_app.lua @@ -19,6 +19,8 @@ local function create_pages(root) db.nav.register_app(iocontrol.APP_ID.DUMMY, main).new_page(nil, function () end) TextBox{parent=main,text="This app is not implemented yet.",x=1,y=2,alignment=core.ALIGN.CENTER} + + TextBox{parent=main,text=" pretend something cool is here \x03",x=1,y=10,alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.gray,colors.black)} end return create_pages diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 02e33fd..cbbeaa3 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -71,10 +71,6 @@ local function init(main) local page_div = Div{parent=main_pane,x=4,y=1} - local sidebar_tabs = { - { char = "#", color = cpair(colors.black, colors.green) } - } - home_page(page_div) unit_page(page_div) @@ -84,13 +80,13 @@ local function init(main) assert(#db.nav.get_containers() == iocontrol.APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered") - local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=db.nav.get_containers()} - db.nav.set_pane(page_pane) - - Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=db.nav.open_app} + db.nav.set_pane(MultiPane{parent=page_div,x=1,y=1,panes=db.nav.get_containers()}) + db.nav.set_sidebar(Sidebar{parent=main_pane,x=1,y=1,height=18,fg_bg=cpair(colors.white,colors.gray)}) PushButton{parent=main_pane,x=1,y=19,text="\x1b",min_width=3,fg_bg=cpair(colors.white,colors.gray),active_fg_bg=cpair(colors.gray,colors.black),callback=db.nav.nav_up} + db.nav.open_app(iocontrol.APP_ID.ROOT) + --#endregion end diff --git a/pocket/ui/pages/home_page.lua b/pocket/ui/pages/home_page.lua index 0ea7c3f..483a881 100644 --- a/pocket/ui/pages/home_page.lua +++ b/pocket/ui/pages/home_page.lua @@ -39,21 +39,26 @@ local function new_view(root) local function open(id) db.nav.open_app(id) end + app.set_sidebar({ + { label = " #\x10", tall = true, color = core.cpair(colors.black, colors.green), callback = function () open(APP_ID.ROOT) end } + }) + local active_fg_bg = cpair(colors.white,colors.gray) - App{parent=apps_1,x=3,y=2,text="U",title="Units",callback=function()open(APP_ID.UNITS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg} - App{parent=apps_1,x=10,y=2,text="\x17",title="PRC",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg} - App{parent=apps_1,x=17,y=2,text="\x15",title="CTL",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg} - App{parent=apps_1,x=3,y=7,text="\x08",title="DEV",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg} - App{parent=apps_1,x=10,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg} - App{parent=apps_1,x=17,y=7,text="\xb6",title="Guide",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg} - App{parent=apps_1,x=3,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=2,y=2,text="U",title="Units",callback=function()open(APP_ID.UNITS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=9,y=2,text="F",title="Facil",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=2,y=7,text="\x17",title="Process",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=9,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=16,y=7,text="\x08",title="Devices",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=2,y=12,text="\xb6",title="Guide",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=9,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),active_fg_bg=active_fg_bg} TextBox{parent=apps_2,text="Diagnostic Apps",x=1,y=2,height=1,alignment=ALIGN.CENTER} - App{parent=apps_2,x=3,y=4,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg} - App{parent=apps_2,x=10,y=4,text="\x1e",title="LoopT",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg} - App{parent=apps_2,x=17,y=4,text="@",title="Comps",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg} + App{parent=apps_2,x=2,y=4,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg} + App{parent=apps_2,x=9,y=4,text="\x1e",title="LoopT",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg} + App{parent=apps_2,x=16,y=4,text="@",title="Comps",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg} return main end diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index 273ad27..53dcce8 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -3,13 +3,31 @@ -- local iocontrol = require("pocket.iocontrol") +local util = require("scada-common.util") local core = require("graphics.core") local Div = require("graphics.elements.div") +local MultiPane = require("graphics.elements.multipane") local TextBox = require("graphics.elements.textbox") +local AlarmLight = require("graphics.elements.indicators.alight") +local CoreMap = require("graphics.elements.indicators.coremap") +local DataIndicator = require("graphics.elements.indicators.data") +local IconIndicator = require("graphics.elements.indicators.icon") +local IndicatorLight = require("graphics.elements.indicators.light") +local RadIndicator = require("graphics.elements.indicators.rad") +local TriIndicatorLight = require("graphics.elements.indicators.trilight") +local VerticalBar = require("graphics.elements.indicators.vbar") + +local HazardButton = require("graphics.elements.controls.hazard_button") +local MultiButton = require("graphics.elements.controls.multi_button") +local PushButton = require("graphics.elements.controls.push_button") +local RadioButton = require("graphics.elements.controls.radio_button") +local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") + local ALIGN = core.ALIGN +local cpair = core.cpair -- new unit page view ---@param root graphics_element parent @@ -21,9 +39,112 @@ local function new_view(root) local app = db.nav.register_app(iocontrol.APP_ID.UNITS, main) app.new_page(nil, function () end) - TextBox{parent=main,y=2,text="UNITS",height=1,alignment=ALIGN.CENTER} + TextBox{parent=main,y=2,text="Units App",height=1,alignment=ALIGN.CENTER} - TextBox{parent=main,y=4,text="work in progress",height=1,alignment=ALIGN.CENTER} + TextBox{parent=main,y=4,text="Loading...",height=1,alignment=ALIGN.CENTER} + + local page_div = Div{parent=main,x=2,y=2,width=main.get_width()-2} + + local btn_fg_bg = cpair(colors.yellow, colors.black) + local btn_active = cpair(colors.white, colors.black) + local label = cpair(colors.lightGray, colors.black) + + local function set_sidebar(unit) + app.set_sidebar({ + { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end }, + { label = "U#" .. unit, color = core.cpair(colors.black, colors.yellow), callback = function () end }, + { label = "RPS", color = core.cpair(colors.black, colors.red), callback = function () end }, + { label = "RCS", color = core.cpair(colors.black, colors.blue), callback = function () end }, + { label = " R ", tall = true, color = core.cpair(colors.black, colors.orange), callback = function () end }, + }) + end + + local function load() + local u_pages = {} + + local active_unit = 1 + set_sidebar(active_unit) + + for _ = 1, db.facility.num_units do + local div = Div{parent=page_div} + table.insert(u_pages, div) + end + + local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=u_pages} + + local function prev(x) + active_unit = util.trinary(x == 1, db.facility.num_units, x - 1) + u_pane.set_value(active_unit) + set_sidebar(active_unit) + end + + local function next(x) + active_unit = util.trinary(x == db.facility.num_units, 1, x + 1) + u_pane.set_value(active_unit) + set_sidebar(active_unit) + end + + for i = 1, db.facility.num_units do + local u_div = u_pages[i] ---@type graphics_element + local unit = db.units[i] ---@type pioctl_unit + + TextBox{parent=u_div,y=1,text="Reactor Unit #"..i,height=1,alignment=ALIGN.CENTER} + PushButton{parent=u_div,x=1,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()prev(i)end} + PushButton{parent=u_div,x=21,y=1,text=">",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()next(i)end} + + local type = util.trinary(unit.num_boilers > 0, "Sodium Cooled Reactor", "Boiling Water Reactor") + TextBox{parent=u_div,y=3,text=type,height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.black)} + + local lu_col = cpair(colors.lightGray, colors.lightGray) + local text_fg = cpair(colors.white, colors._INHERIT) + + local rate = DataIndicator{parent=u_div,y=5,lu_colors=lu_col,label="Rate",unit="mB/t",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} + local temp = DataIndicator{parent=u_div,lu_colors=lu_col,label="Temp",unit="K",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} + + local basic_states = { + { color = cpair(colors.black,colors.lightGray), symbol = "\x07" }, + { color = cpair(colors.black,colors.red), symbol = "-" }, + { color = cpair(colors.black,colors.yellow), symbol = "\x1e" }, + { color = cpair(colors.black,colors.green), symbol = "+" } + } + + local mode_states = { + { color = cpair(colors.black,colors.lightGray), symbol = "\x07" }, + { color = cpair(colors.black,colors.red), symbol = "-" }, + { color = cpair(colors.black,colors.green), symbol = "+" }, + { color = cpair(colors.black,colors.purple), symbol = "A" } + } + + local ctrl = IconIndicator{parent=u_div,x=1,y=8,label="Control State",states=mode_states} + + ctrl.update(i+1) + + u_div.line_break() + + local rct = IconIndicator{parent=u_div,x=1,label="Fission Reactor",states=basic_states} + local rps = IconIndicator{parent=u_div,x=1,label="Protection System",states=basic_states} + + u_div.line_break() + + local rcs = IconIndicator{parent=u_div,x=1,label="Coolant System",states=basic_states} + + for b = 1, unit.num_boilers do + local blr = IconIndicator{parent=u_div,x=1,label="Boiler "..b,states=basic_states} + blr.update(b+2) + end + + for t = 1, unit.num_turbines do + local trb = IconIndicator{parent=u_div,x=1,label="Turbine "..t,states=basic_states} + trb.update(t) + end + + rct.update(4) + rps.update(3) + rcs.update(3) + end + end + + app.set_on_load(load) return main end