bobmarley12345

CC BigReactor GUI source

Nov 25th, 2023 (edited)
49
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 102.85 KB | None | 0 0
  1. -- By TheRareCarrot/REghZy >:)
  2. -- This is an implementation of the WPF layout system in lua
  3. -- The measure phase takes a constraint and measures the component (and child components too) to calculate the desired size
  4. -- The arrange phase positions the component (and child components) on screen, relative to the parent component
  5. -- The layout is a recursive operation, so it can affect performance if a layout is requested for a fairly top level component
  6. -- Rendering is pretty bad, as implementing a pixel buffer in computercraft would be expensive and so the entire component tree is re-drawn (but only when required (InvalidateVisual))
  7.  
  8. local USE_DEBUG_TRACE = true
  9. local TRACE = {}
  10.  
  11. local AppTimerId = 0
  12. local AppTimerTickRate = 10
  13. local AppTimerInterval = 1 / AppTimerTickRate
  14. local IsAppRunning = true
  15. local AppDispatcherId = 0
  16.  
  17. ---@class AppRootComponent : UIComponent
  18. local AppRootComponent
  19.  
  20. -- display sleep utils, to help with server performance
  21. local CanDisplayAutoSleep = true
  22. local IsDisplayHidden = false
  23. local LastDisplayWakeTime = 0
  24.  
  25. -- ONLY USE IN EXCEPTIONAL CASES!!! THIS WILL EAT UP A MEGATON OF DISK
  26. local ALLOW_PRINT_DEBUG = false
  27.  
  28. local ENABLE_RENDERING = true
  29. local IsRootRenderDirty = false
  30. local DebugFileHandle = nil
  31.  
  32. local IsProcessingDispatcherQueue = false
  33. local TheLayoutUpdateTask = nil
  34.  
  35. local ui = {}
  36. local maths = {}
  37. local toolkit = {}
  38.  
  39. local CompositionTarget = term
  40. local CompositionOffsetX = 0
  41. local CompositionOffsetY = 0
  42. local CompositionWidth = 0
  43. local CompositionHeight = 0
  44. local CompositionComponentSource = nil
  45.  
  46. local FocusedComponent = nil
  47. local MouseCapturedComponent = nil
  48.  
  49. local NextDispatcherOperationId = 1
  50. local PriorityHighest = 1
  51. local PriorityLayout = 2
  52. local PriorityRender = 3
  53. local PriorityBackground = 4
  54.  
  55. local Event_PreviewMouseDown = 1
  56. local Event_MouseDown = 2
  57. local Event_MouseDrag = 3
  58. local Event_Button_OnClick = 20
  59. local Event_TabItem_OnNowActive = 40
  60. local Event_PropertyChanged = 100
  61.  
  62. -- stores the application root component objects
  63. local ForceArrangeOrMeasure = false
  64. local layout_arrange_queue = {}
  65. local layout_measure_queue = {}
  66. local dispatcher_queue = {
  67.     [PriorityHighest] = {},
  68.     [PriorityLayout] = {},
  69.     [PriorityRender] = {},
  70.     [PriorityBackground] = {},
  71. }
  72.  
  73. local delayedTaskQueue = {}
  74.  
  75. local ConfigTable = {}
  76.  
  77. ---Defines a new type, using the given base type class
  78. ---@param baseType table? The base class
  79. ---@param readableTypeName string? A readable type name (usually the name of the table returned)
  80. ---@return table table The actual type
  81. function DefineType(baseType, readableTypeName)
  82.     local theTable = {}
  83.     theTable.__index = theTable
  84.     if (baseType ~= nil) then
  85.         setmetatable(theTable, baseType)
  86.         theTable.__TheBaseType__ = baseType
  87.     end
  88.     theTable.ActualTypeString = readableTypeName or "<no typename>"
  89.     if (baseType ~= nil and baseType.SetupDerivedType ~= nil) then
  90.         baseType.SetupDerivedType(baseType, theTable)
  91.     end
  92.     return theTable
  93. end
  94.  
  95. ---Creates a new instance of the given type
  96. ---@param theType table The type
  97. ---@param readableTypeName string? The readable type name (key as TypeName). Defaults to the type's ActualTypeString
  98. ---@return table table The new instance
  99. function NewTypeInstance(theType, readableTypeName)
  100.     local baseType = theType.__TheBaseType__
  101.     local obj
  102.     if (baseType == nil or baseType.new == nil) then
  103.         obj = {}
  104.     else
  105.         obj = baseType.new()
  106.     end
  107.  
  108.     setmetatable(obj, theType)
  109.     obj.TypeName = readableTypeName or theType.ActualTypeString
  110.     obj.__TheType__ = theType
  111.     obj.__TheBaseType__ = baseType
  112.     return obj
  113. end
  114.  
  115. --region Base Object
  116.  
  117. ---@class BaseRZObject
  118. ---@field TypeName string?
  119. ---@field __TheType__ table
  120. ---@field __TheBaseType__ table
  121. local BaseRZObject = DefineType(nil, "BaseRZObject")
  122.  
  123. ---@class DependencyObject : BaseRZObject
  124. ---@field PropValueTable table
  125. DependencyObject = DefineType(BaseRZObject, "DependencyObject")
  126. DependencyObject.PropTable = {}
  127. DependencyObject.PropTable.TotalEntries = 0
  128.  
  129. function DependencyObject:SetupDerivedType(derivedType)
  130.     derivedType.PropTable = {}
  131.     derivedType.PropTable.TotalEntries = 0
  132.     -- error("New derived: " .. self.ActualTypeString .. "->" .. derivedType.ActualTypeString .. ". " .. tostring(self.PropTable) .. " -> " .. tostring(derivedType.PropTable))
  133. end
  134.  
  135. local PropertyNullWrapper = {}
  136.  
  137. function DependencyObject.new()
  138.     local obj = NewTypeInstance(DependencyObject, "DependencyObject")
  139.     obj.PropValueTable = {}
  140.     return obj
  141. end
  142.  
  143. ---@alias PropertyChangeFlags
  144. ---| "AffectsMeasure"
  145. ---| "AffectsArrange"
  146. ---| "AffectsRender"
  147.  
  148. ---@alias HorizontalAlignment
  149. ---| "left"
  150. ---| "center"
  151. ---| "right"
  152. ---| "stretch"
  153.  
  154. ---@alias VerticalAlignment
  155. ---| "top"
  156. ---| "center"
  157. ---| "bottom"
  158. ---| "stretch"
  159.  
  160. ---@alias Visibility
  161. ---| "visible"
  162. ---| "hidden"
  163. ---| "collapsed"
  164.  
  165. ---@alias DockSide
  166. ---| "left"
  167. ---| "top"
  168. ---| "right"
  169. ---| "bottom"
  170.  
  171. ---@alias EventHandler fun(self: any, time: number, args: table):boolean?
  172.  
  173. ---Defines a new property for this type. DO NOT REGISTER FOR EACH INSTANCE OF OBJECT!
  174. ---@param name string The property name
  175. ---@param theType type The type of value stored
  176. ---@param defValue any The default value
  177. ---@param ... PropertyChangeFlags AffectsRender, AffectsMeasure, AffectsArrange
  178. function DependencyObject:RegProp(name, theType, defValue, ...)
  179.     if (self.PropTable == nil) then
  180.         error("Not a DependencyObject: " .. (self.TypeName or "<unknown type>"))
  181.     end
  182.     if (self.PropTable[name] ~= nil) then
  183.         error("Property already exists: " .. name .. ". Self = " .. self.ActualTypeString)
  184.     end
  185.     if (self.__TheType__ ~= nil) then
  186.         error("Properties must be registered on the object type table, not the instance")
  187.     end
  188.  
  189.     local property = {
  190.         Name = name,
  191.         ValueType = theType,
  192.         DefaultValue = defValue,
  193.         Flags = {},
  194.         ChangeHandlers = {}
  195.     }
  196.  
  197.     if (arg ~= nil) then
  198.         for i = 1, arg.n do
  199.             property.Flags[arg[i]] = true
  200.         end
  201.     end
  202.  
  203.     self.PropTable[name] = property
  204.     self.PropTable.TotalEntries = self.PropTable.TotalEntries + 1
  205.     return property
  206. end
  207.  
  208. ---Finds a property with a string name, or returns the property passed as a parameter
  209. ---@param self DependencyObject Ourself
  210. ---@param thePropertyOrName table|string
  211. ---@return table
  212. function DependencyObject:FindProperty(thePropertyOrName)
  213.     if (type(thePropertyOrName) == "string") then
  214.         local obj = self
  215.         local property
  216.         while (true) do
  217.             property = obj.PropTable[thePropertyOrName]
  218.             if (property ~= nil) then
  219.                 return property
  220.             end
  221.  
  222.             obj = obj.__TheBaseType__
  223.             if (obj == nil) then
  224.                 break
  225.             end
  226.         end
  227.         error("No such property: " .. thePropertyOrName)
  228.     elseif (thePropertyOrName == nil) then
  229.         error("Property (or name) cannot be null")
  230.     elseif (thePropertyOrName.Name == nil) then
  231.         error("Invalid property object")
  232.     end
  233.  
  234.     return thePropertyOrName
  235. end
  236.  
  237. ---Adds a property changed handler. This has the same method signature as a
  238. ---regular event. These are fired before Event_PropertyChanged
  239. ---@param propName string Property Name
  240. ---@param onPropertyChangedHandler EventHandler The event handler function
  241. function DependencyObject:AddPropertyHandler(propName, onPropertyChangedHandler)
  242.     local definition = self.PropTable[propName]
  243.     if (definition == nil) then
  244.         error("No such property: " .. propName)
  245.     end
  246.     table.insert(definition.ChangeHandlers, onPropertyChangedHandler)
  247. end
  248.  
  249. function DependencyObject:GetPropValue(propertyOrName)
  250.     local property = self:FindProperty(propertyOrName);
  251.     local value = self.PropValueTable[property]
  252.     if (value == nil) then
  253.         return property.DefaultValue
  254.     elseif (value == PropertyNullWrapper) then
  255.         return nil
  256.     else
  257.         return value
  258.     end
  259. end
  260.  
  261. function DependencyObject:SetPropValue(propertyOrName, newValue)
  262.     local property = self:FindProperty(propertyOrName);
  263.     local oldValue = self.PropValueTable[property]
  264.     if (oldValue == nil) then
  265.         oldValue = property.DefaultValue
  266.     end
  267.  
  268.     if (oldValue == newValue) then
  269.         return
  270.     end
  271.  
  272.     self.PropValueTable[property] = newValue or PropertyNullWrapper
  273.     self:InternalOnPropValueChanged(property, oldValue, newValue, false)
  274. end
  275.  
  276. function DependencyObject:ClearPropValue(propertyOrName)
  277.     local property = self:FindProperty(propertyOrName);
  278.     local oldValue = self.PropValueTable[property]
  279.     if (oldValue == nil) then
  280.         return
  281.     end
  282.  
  283.     if (oldValue == PropertyNullWrapper) then
  284.         oldValue = nil
  285.     end
  286.  
  287.     self.PropValueTable[property] = nil
  288.     self:InternalOnPropValueChanged(property, oldValue, nil, true)
  289. end
  290.  
  291. function DependencyObject:InternalOnPropValueChanged(property, oldValue, newValue, isClearing)
  292.  
  293. end
  294.  
  295. --endregion
  296.  
  297. --region UI Methods
  298.  
  299. local function IndexForPixel(x, y)
  300.     return (y - 1) * CompositionWidth + x
  301. end
  302.  
  303. ---Sets the background colour
  304. ---@param the_bg integer? The colour
  305. function ui.setBackgroundColour(the_bg)
  306.     if (the_bg ~= nil) then
  307.         CompositionTarget.setBackgroundColour(the_bg)
  308.     end
  309. end
  310.  
  311. ---Sets the text colour
  312. ---@param the_tc integer? The colour
  313. function ui.setTextColour(the_tc)
  314.     if (the_tc ~= nil) then
  315.         CompositionTarget.setTextColour(the_tc)
  316.     end
  317. end
  318.  
  319. ---Clears the target
  320. function ui.clear()
  321.     CompositionTarget.setBackgroundColour(colours.black)
  322.     CompositionTarget.setTextColour(colours.white)
  323.     CompositionTarget.clear()
  324.     CompositionTarget.setCursorPos(1, 1)
  325. end
  326.  
  327. ---Raw function for writing text at a position (just calls setCursorPos and write)
  328. ---@param x integer The X pos to write text at
  329. ---@param y integer The Y pos to write text at
  330. ---@param text string The text to write
  331. function ui.writeTextAt(x, y, text)
  332.     CompositionTarget.setCursorPos(x + CompositionOffsetX, y + CompositionOffsetY)
  333.     CompositionTarget.write(text)
  334. end
  335.  
  336. ---Fills a region with a colour
  337. ---@param x integer The X pos to begin drawing at
  338. ---@param y integer The Y pos to begin drawing at
  339. ---@param width integer The width of the square
  340. ---@param height integer The height of the square
  341. ---@param bg_colour integer? The colour to draw
  342. function ui.fill(x, y, width, height, bg_colour)
  343.     ui.setBackgroundColour(bg_colour)
  344.     local text = string.rep(" ", width)
  345.     for i = 0, height - 1 do
  346.         ui.writeTextAt(x, y + i, text)
  347.     end
  348. end
  349.  
  350. ---Writes text at the given position with a given text and background colour
  351. ---@param x integer The X pos to write text at
  352. ---@param y integer The Y pos to write text at
  353. ---@param text string The text to write
  354. ---@param text_colour integer? The text colour
  355. ---@param bg_colour integer? The background colour
  356. function ui.drawText(x, y, text, text_colour, bg_colour)
  357.     ui.setTextColour(text_colour)
  358.     ui.setBackgroundColour(bg_colour)
  359.     ui.writeTextAt(x, y, text)
  360. end
  361.  
  362. ---Writes text at the given position with a given text and background colour
  363. ---@param x integer The X pos to write text at
  364. ---@param y integer The Y pos to write text at
  365. ---@param text string The text to write
  366. ---@param text_colour integer? The text colour
  367. ---@param bg_colour integer? The background colour
  368. function ui.drawTextCentered(x, y, width, height, text, text_colour, bg_colour)
  369.     local absX = math.floor((width / 2) - (#text / 2))
  370.     local absY = math.floor(height / 2)
  371.     ui.drawText(x + absX, y + absY, text, text_colour, bg_colour)
  372. end
  373.  
  374. ---Draws a horizontal line
  375. ---@param x integer The X pos to start the line at
  376. ---@param y integer The Y pos to start the line at
  377. ---@param length integer The length of the line
  378. ---@param colour integer? The colour of the line
  379. function ui.drawLineH(x, y, length, colour)
  380.     ui.setBackgroundColour(colour)
  381.     ui.writeTextAt(x, y, string.rep(" ", length))
  382. end
  383.  
  384. ---Draws a vertical line
  385. ---@param x integer The X pos to start the line at
  386. ---@param y integer The Y pos to start the line at
  387. ---@param height integer The height of the line
  388. ---@param colour integer? The colour of the line
  389. function ui.drawLineV(x, y, height, colour)
  390.     x = x + CompositionOffsetX
  391.     y = y + CompositionOffsetY
  392.     ui.setBackgroundColour(colour)
  393.     for i = 1, height, 1 do
  394.         CompositionTarget.setCursorPos(x, y + i)
  395.         CompositionTarget.write(" ")
  396.     end
  397. end
  398.  
  399. local function internal_calc_progress_bar(length, min, max, value)
  400.     return math.floor((maths.clamp(value, min, max) / (max - min)) * length)
  401. end
  402.  
  403. ---Draws a progress bar
  404. ---@param x integer The X pos to start drawing
  405. ---@param y integer The Y pos to start drawing
  406. ---@param length integer The length of the progress bar
  407. ---@param min number The minimum value
  408. ---@param max number The maximum value
  409. ---@param value number The value
  410. ---@param bg_colour integer? The background colour (non-value)
  411. ---@param val_colour integer? The foreground aka value colour
  412. ---@param rightToLeft boolean? True to draw the progress right to left instead of left to right
  413. function ui.drawProgressBarH(x, y, length, min, max, value, bg_colour, val_colour, rightToLeft)
  414.     local draw_len = internal_calc_progress_bar(length, min, max, value)
  415.     ui.drawLineH(draw_len + 1, y, length - draw_len, bg_colour)
  416.     if (rightToLeft == true) then
  417.         ui.drawLineH(x + length - draw_len, y, draw_len, val_colour)
  418.     else
  419.         ui.drawLineH(x, y, draw_len, val_colour)
  420.     end
  421. end
  422.  
  423. ---Draws a progress bar
  424. ---@param x integer The X pos to start drawing
  425. ---@param y integer The Y pos to start drawing
  426. ---@param height integer The height of the progress bar
  427. ---@param min number The minimum value
  428. ---@param max number The maximum value
  429. ---@param value number The value
  430. ---@param bg_colour integer? The background colour (non-value)
  431. ---@param val_colour integer? The foreground aka value colour
  432. ---@param bottomToTop boolean? True to draw the progress bottom to top instead of top to bottom
  433. function ui.drawProgressBarV(x, y, height, min, max, value, bg_colour, val_colour, bottomToTop)
  434.     ui.drawLineV(x, y, height, bg_colour)
  435.     local draw_len = internal_calc_progress_bar(height, min, max, value)
  436.     if (bottomToTop ~= nil and bottomToTop == true) then
  437.         ui.drawLineV(x, y + height - draw_len, draw_len, val_colour)
  438.     else
  439.         ui.drawLineV(x, y, draw_len, val_colour)
  440.     end
  441. end
  442.  
  443. --endregion
  444.  
  445. --region Utils
  446.  
  447. function PushFunction(funcName, ...)
  448.     if (USE_DEBUG_TRACE) then
  449.         table.insert(TRACE, {FunctionName = funcName, Args = {...}})
  450.     end
  451. end
  452.  
  453. function PushFunctionAndInvoke(funcName, func, ...)
  454.     PushFunction(funcName)
  455.     return PopFunction(func(...))
  456. end
  457.  
  458. function PopFunction(...)
  459.     if (USE_DEBUG_TRACE and #TRACE > 0) then
  460.         table.remove(TRACE, #TRACE)
  461.     end
  462.     if (... ~= nil) then
  463.         return ...
  464.     end
  465. end
  466.  
  467. function PrintStackTrace()
  468.     for i = 1, #TRACE, 1 do
  469.         local info = TRACE[i]
  470.         local text = info.FunctionName
  471.         if (#info.Args > 0) then
  472.             local strtab = {}
  473.             for key, value in pairs(info.Args) do
  474.                 if (type(key) == "number") then
  475.                     table.insert(strtab, tostring(value))
  476.                 else
  477.                     table.insert(strtab, tostring(key) .. "=" .. tostring(value))
  478.                 end
  479.             end
  480.  
  481.             text = text .. ": " .. table.concat(strtab, ", ")
  482.         end
  483.         print(text)
  484.         PrintDebug(text)
  485.     end
  486. end
  487.  
  488. function PrintDebug(msg, ...)
  489.     if (ALLOW_PRINT_DEBUG and DebugFileHandle ~= nil) then
  490.         local args = {...}
  491.         if (args ~= nil and #args > 0) then
  492.             for i = 1, #args do
  493.                 local theArg = args[i]
  494.                 if (theArg == nil) then
  495.                     args[i] = "nil"
  496.                 else
  497.                     local argType = type(theArg)
  498.                     if (argType == "boolean" or argType == "function" or argType == "table") then
  499.                         args[i] = tostring(theArg)
  500.                     end
  501.                 end
  502.             end
  503.  
  504.             msg = string.format(msg, unpack(args))
  505.         end
  506.         DebugFileHandle.writeLine(msg)
  507.     end
  508. end
  509.  
  510. function maths.min(...)
  511.     local min = 0
  512.     for i = 1, arg.n, 1 do
  513.         min = math.min(#arg[i], min)
  514.     end
  515.     return min
  516. end
  517.  
  518. function maths.max(...)
  519.     local max = 0
  520.     for i = 1, arg.n, 1 do
  521.         max = math.max(#arg[i], max)
  522.     end
  523.     return max
  524. end
  525.  
  526. function maths.clamp(value, min, max)
  527.     if (value < min) then return min end
  528.     if (value > max) then return max end
  529.     return value
  530. end
  531.  
  532. function maths.isNaN(val)
  533.     return val ~= val
  534. end
  535.  
  536. function UpdateCachedIndices(children)
  537.     for k, obj in pairs(children) do
  538.         obj.IndexInParent = k
  539.     end
  540. end
  541.  
  542. function ClearTable(theTable)
  543.     for i = #theTable, 1, -1 do
  544.         table.remove(theTable, i)
  545.     end
  546. end
  547.  
  548. --endregion
  549.  
  550. --region Dispatcher utils
  551.  
  552. local DispatcherOperation = DefineType(BaseRZObject, "DispatcherOperation")
  553.  
  554. function DispatcherOperation.new(theMethod, ...)
  555.     local id = NextDispatcherOperationId
  556.     NextDispatcherOperationId = id + 1
  557.     local operation = NewTypeInstance(DispatcherOperation)
  558.     operation.Id = id
  559.     operation.IsCompleted = false
  560.     operation.Method = theMethod
  561.     operation.Args = {...}
  562.     return operation
  563. end
  564.  
  565. function InvokeAsync(theMethod, priority, ...)
  566.     local operation = DispatcherOperation.new(theMethod, ...)
  567.     if (priority == nil) then
  568.         priority = PriorityBackground
  569.     end
  570.  
  571.     table.insert(dispatcher_queue[priority], operation)
  572.     if (AppDispatcherId == 0) then
  573.         AppDispatcherId = os.startTimer(0)
  574.     end
  575.  
  576.     return operation
  577. end
  578.  
  579. function InvokeAsyncWithDelay(theMethod, delay_seconds, ...)
  580.     local operation = DispatcherOperation.new(theMethod, ...)
  581.     operation.TimeUntilExecution = os.clock() + delay_seconds
  582.     table.insert(delayedTaskQueue, operation)
  583. end
  584.  
  585. --endregion
  586.  
  587. --region UI Toolkit
  588.  
  589. ---@class UIComponent : DependencyObject
  590. ---@field DesiredWidth number
  591. ---@field DesiredHeight number
  592. ---@field RenderPosX number
  593. ---@field RenderPosY number
  594. ---@field RenderWidth number
  595. ---@field RenderHeight number
  596. ---@field _internal_Visibility Visibility
  597. ---@field _internal_FinalVisibility Visibility
  598. ---@field IsVisualDirty boolean
  599. ---@field IsMeasureDirty boolean
  600. ---@field IsArrangeDirty boolean
  601. ---@field NeverMeasured boolean
  602. ---@field NeverArranged boolean
  603. ---@field MeasureInProgress boolean
  604. ---@field ArrangeInProgress boolean
  605. ---@field MeasureDuringArrange boolean
  606. ---@field LastMeasureAvailableWidth number
  607. ---@field LastMeasureAvailableHeight number
  608. ---@field LastArrangeFinalX number
  609. ---@field LastArrangeFinalY number
  610. ---@field LastArrangeFinalW number
  611. ---@field LastArrangeFinalH number
  612. ---@field IsVisualPositionValid boolean
  613. ---@field Children UIComponent[]
  614. ---@field IndexInParent integer
  615. ---@field Parent UIComponent
  616. ---@field IdToChildTable table<string,UIComponent>
  617. ---@field EventHandlers table<EventHandler>
  618. UIComponent = DefineType(DependencyObject, "UIComponent")
  619. UIComponent.MarginLProperty = DependencyObject.RegProp(UIComponent, "MarginL", "number", 0, "AffectsArrange")
  620. UIComponent.MarginTProperty = DependencyObject.RegProp(UIComponent, "MarginT", "number", 0, "AffectsArrange")
  621. UIComponent.MarginRProperty = DependencyObject.RegProp(UIComponent, "MarginR", "number", 0, "AffectsArrange")
  622. UIComponent.MarginBProperty = DependencyObject.RegProp(UIComponent, "MarginB", "number", 0, "AffectsArrange")
  623. UIComponent.HorizontalAlignmentProperty = DependencyObject.RegProp(UIComponent, "HorizontalAlignment", "string", "left", "AffectsArrange")
  624. UIComponent.VerticalAlignmentProperty = DependencyObject.RegProp(UIComponent, "VerticalAlignment", "string", "top", "AffectsArrange")
  625. UIComponent.WidthProperty = DependencyObject.RegProp(UIComponent, "Width", "number", nil, "AffectsMeasure")
  626. UIComponent.HeightProperty = DependencyObject.RegProp(UIComponent, "Height", "number", nil, "AffectsMeasure")
  627. UIComponent.MinWidthProperty = DependencyObject.RegProp(UIComponent, "MinWidth", "number", 0, "AffectsMeasure")
  628. UIComponent.MinHeightProperty = DependencyObject.RegProp(UIComponent, "MinHeight", "number", 0, "AffectsMeasure")
  629. UIComponent.MaxWidthProperty = DependencyObject.RegProp(UIComponent, "MaxWidth", "number", math.huge, "AffectsMeasure")
  630. UIComponent.MaxHeightProperty = DependencyObject.RegProp(UIComponent, "MaxHeight", "number", math.huge, "AffectsMeasure")
  631. UIComponent.IsFocusableProperty = DependencyObject.RegProp(UIComponent, "IsFocusable", "boolean", false)
  632. UIComponent.IsFocusedProperty = DependencyObject.RegProp(UIComponent, "IsFocused", "boolean", false)
  633. UIComponent.BackgroundProperty = DependencyObject.RegProp(UIComponent, "Background", "number", nil, "AffectsRender")
  634.  
  635. ---@class Button : UIComponent
  636. ---@field IsPressed boolean
  637. ---@field IsToggleButton boolean
  638. ---@field Text string
  639. ---@field DoNotProcessButtonClickLogic boolean
  640. Button = DefineType(UIComponent, "Button")
  641. Button.TextProperty = DependencyObject.RegProp(Button, "Text", "string", "Text", "AffectsMeasure", "AffectsRender")
  642. Button.PressedBackgroundProperty = DependencyObject.RegProp(Button, "PressedBackground", "number", colours.lightBlue, "AffectsRender")
  643.  
  644. ---@class TextBlock : UIComponent
  645. TextBlock = DefineType(UIComponent, "TextBlock")
  646. TextBlock.TextProperty = DependencyObject.RegProp(TextBlock, "Text", "string", "Text", "AffectsMeasure", "AffectsRender")
  647. TextBlock.TextColourProperty = DependencyObject.RegProp(TextBlock, "TextColour", "string", colours.white, "AffectsRender")
  648.  
  649. ---@class UniformPanel : UIComponent
  650. UniformPanel = DefineType(UIComponent, "UniformPanel")
  651. UniformPanel.OrientationProperty = DependencyObject.RegProp(UniformPanel, "Orientation", "string", "horizontal")
  652. UniformPanel.SpacingProperty = DependencyObject.RegProp(UniformPanel, "Spacing", "number", 0)
  653.  
  654. ---@class HorizontalStackPanel : UIComponent
  655. HorizontalStackPanel = DefineType(UIComponent, "HorizontalStackPanel")
  656.  
  657. ---@class VerticalStackPanel : UIComponent
  658. VerticalStackPanel = DefineType(UIComponent, "VerticalStackPanel")
  659.  
  660. ---@class DockPanel : UIComponent
  661. DockPanel = DefineType(UIComponent, "DockPanel")
  662.  
  663. ---@class ContentPresenter : UIComponent
  664. ContentPresenter = DefineType(UIComponent, "ContentPresenter")
  665.  
  666. ---@class ContentPresenter : UIComponent
  667. ---@field MyContentPresenter ContentPresenter
  668. local TabControl = DefineType(UIComponent, "TabControl")
  669.  
  670. ---@class ProgressBar : UIComponent
  671. ProgressBar = DefineType(UIComponent, "ProgressBar")
  672. ProgressBar.ValueColourProperty = DependencyObject.RegProp(ProgressBar, "ValueColour", "number", colours.lightBlue, "AffectsRender")
  673. ProgressBar.ValueProperty = DependencyObject.RegProp(ProgressBar, "Value", "number", 0, "AffectsRender")
  674. ProgressBar.MinimumProperty = DependencyObject.RegProp(ProgressBar, "Minimum", "number", 0, "AffectsRender")
  675. ProgressBar.MaximumProperty = DependencyObject.RegProp(ProgressBar, "Maximum", "number", 1, "AffectsRender")
  676.  
  677. ---@class DualColourTextBlock : UIComponent
  678. ---@field LeftText string
  679. ---@field RightText string
  680. ---@field LeftColour number
  681. ---@field RightColour number
  682. local DualColourTextBlock = DefineType(UIComponent, "DualColourTextBlock")
  683.  
  684. ---@class BasePageComponent : UIComponent
  685. local BasePageComponent = DefineType(UIComponent, "BasePageComponent")
  686.  
  687. ---@class HomePageComponent : BasePageComponent
  688. ---@field PowerLabel DualColourTextBlock
  689. ---@field FuelLevelLabel DualColourTextBlock
  690. ---@field FuelLevelProgBar ProgressBar
  691. ---@field FuelTempLabel DualColourTextBlock
  692. ---@field FuelTempProgBar ProgressBar
  693. ---@field CasingTempLabel DualColourTextBlock
  694. ---@field CasingTempProgBar ProgressBar
  695. ---@field EnergyLabel DualColourTextBlock
  696. ---@field StoredLabel DualColourTextBlock
  697. ---@field StoredProgBar ProgressBar
  698. ---@field ControlRodsLabel DualColourTextBlock
  699. ---@field ControlBtn_Auto Button
  700. ---@field ControlBtn_On Button
  701. ---@field ControlBtn_Off Button
  702. local HomePageComponent = DefineType(BasePageComponent, "HomePageComponent")
  703.  
  704. ---@class RodsPageComponent : BasePageComponent
  705. local RodsPageComponent = DefineType(BasePageComponent, "RodsPageComponent")
  706.  
  707. ---@class AutoPageComponent : BasePageComponent
  708. local AutoPageComponent = DefineType(BasePageComponent, "AutoPageComponent")
  709.  
  710. ---@class InfoPageComponent : BasePageComponent
  711. local InfoPageComponent = DefineType(BasePageComponent, "InfoPageComponent")
  712.  
  713. ---Adds an event handler (of the given event type) to this component
  714. ---@param type number The event type
  715. ---@param handler EventHandler The handler. Parameters are self,time,args
  716. function UIComponent:AddEventHandler(type, handler)
  717.     PushFunction("AddEventHandler", self.TypeName)
  718.     local myHandlerTable = self.EventHandlers[type]
  719.     if (myHandlerTable == nil) then
  720.         myHandlerTable = {}
  721.         self.EventHandlers[type] = myHandlerTable
  722.     end
  723.     table.insert(myHandlerTable, handler)
  724.     PopFunction()
  725. end
  726.  
  727. ---Raises an event of the given type, using the given time and args as parameters (and ourself obviously)
  728. ---@param type number The event type
  729. ---@param time number The os clock time
  730. ---@param args table The event args
  731. ---@return boolean boolean The event args handled state
  732. function UIComponent:RaiseEvent(type, time, args)
  733.     PushFunction("RaiseEvent", self.TypeName)
  734.     local tab = self.EventHandlers[type]
  735.     if (tab ~= nil) then
  736.         if (time == nil) then time = os.clock() end
  737.         for i, handler in ipairs(tab) do
  738.             if (handler(self, time, args) == true) then
  739.                 args.handled = true
  740.             end
  741.         end
  742.     end
  743.     return PopFunction(args.handled)
  744. end
  745.  
  746. function UIComponent:InternalOnPropValueChanged(property, oldValue, newValue, isClearing)
  747.     if (property.Flags["AffectsMeasure"] == true) then
  748.         self:InvalidateMeasure()
  749.     end
  750.  
  751.     if (property.Flags["AffectsArrange"] == true) then
  752.         self:InvalidateArrange()
  753.     end
  754.  
  755.     if (property.Flags["AffectsRender"] == true) then
  756.         self:InvalidateVisual()
  757.     end
  758.  
  759.     DependencyObject.InternalOnPropValueChanged(self, property, oldValue, newValue, isClearing)
  760.  
  761.     local time = os.clock()
  762.     local args = {OldValue = oldValue, NewValue = newValue, Property = property}
  763.     for i, handler in ipairs(property.ChangeHandlers) do
  764.         handler(self, time, args)
  765.     end
  766.     self:RaiseEvent(Event_PropertyChanged, time, args)
  767. end
  768.  
  769. ---new UIComponent
  770. function UIComponent.new()
  771.     local obj = NewTypeInstance(UIComponent, "UIComponent")
  772.     obj.DesiredWidth = 0      -- measured width based on Width and children
  773.     obj.DesiredHeight = 0     -- measured height based on Height and children
  774.     obj.RenderPosX = 0        -- arranged render X (relative to parent duh)
  775.     obj.RenderPosY = 0        -- arranged render Y (relative to parent duh)
  776.     obj.RenderWidth = 0       -- arranged render width
  777.     obj.RenderHeight = 0      -- arranged render height
  778.     obj._internal_Visibility = "visible"      -- our target visibility
  779.     obj._internal_FinalVisibility = "visible" -- out final visibility, based on self and parent tree
  780.  
  781.     obj.IsVisualDirty = false -- true when this component and its hierarchy requires drawing
  782.     obj.IsMeasureDirty = false
  783.     obj.IsArrangeDirty = false
  784.     obj.NeverMeasured = true
  785.     obj.NeverArranged = true
  786.     obj.MeasureInProgress = false
  787.     obj.ArrangeInProgress = false
  788.     obj.MeasureDuringArrange = false
  789.     obj.LastMeasureAvailableWidth = 0
  790.     obj.LastMeasureAvailableHeight = 0
  791.     obj.LastArrangeFinalX = 0
  792.     obj.LastArrangeFinalY = 0
  793.     obj.LastArrangeFinalW = 0
  794.     obj.LastArrangeFinalH = 0
  795.  
  796.     obj.IsVisualPositionValid = false
  797.     obj.Children = {}         -- a table of child components
  798.     obj.IndexInParent = -1    -- the index of this component in its parent
  799.     obj.Parent = nil          -- our parent object
  800.     obj.IdToChildTable = {}
  801.  
  802.     obj.EventHandlers = {}
  803.     return obj
  804. end
  805.  
  806. function UIComponent:SetVisibility(visible)
  807.     self._internal_Visibility = visible
  808.     self:UpdateVisibility()
  809.     self:InvalidateLayoutAndVisual()
  810. end
  811.  
  812. function UIComponent:GetVisibility(useNonInherited)
  813.     if (useNonInherited == true) then
  814.         return self._internal_Visibility
  815.     else
  816.         return self._internal_FinalVisibility
  817.     end
  818. end
  819.  
  820. function UIComponent:UpdateVisibility()
  821.     if (self.Parent == nil) then
  822.         self._internal_FinalVisibility = self._internal_Visibility
  823.     else
  824.         local parent_vis = self.Parent._internal_FinalVisibility
  825.         if (parent_vis == "visible") then
  826.             self._internal_FinalVisibility = self._internal_Visibility
  827.         else
  828.             self._internal_FinalVisibility = parent_vis
  829.         end
  830.     end
  831.  
  832.     for i, child in ipairs(self.Children) do
  833.         child:UpdateVisibility()
  834.     end
  835. end
  836.  
  837. ---Sets the component's margins
  838. ---@generic T: UIComponent
  839. ---@param self T
  840. ---@param left number? The left margin
  841. ---@param top number? The top margin
  842. ---@param right number? The right margin
  843. ---@param bottom number? The bottom margin
  844. ---@return T
  845. function UIComponent:SetMargin(left, top, right, bottom)
  846.     if (left ~= nil)   then DependencyObject.SetPropValue(self, UIComponent.MarginLProperty, left) end
  847.     if (top ~= nil)    then DependencyObject.SetPropValue(self, UIComponent.MarginTProperty, top) end
  848.     if (right ~= nil)  then DependencyObject.SetPropValue(self, UIComponent.MarginRProperty, right) end
  849.     if (bottom ~= nil) then DependencyObject.SetPropValue(self, UIComponent.MarginBProperty, bottom) end
  850.     return self
  851. end
  852.  
  853. ---Sets the alignments for this component
  854. ---@generic T: UIComponent
  855. ---@param self T
  856. ---@param horizontal HorizontalAlignment? The horizontal alignment
  857. ---@param vertical VerticalAlignment? The horizontal alignment
  858. ---@return T
  859. function UIComponent:SetAlignment(horizontal, vertical)
  860.     if (horizontal ~= nil) then DependencyObject.SetPropValue(self, UIComponent.HorizontalAlignmentProperty, horizontal) end
  861.     if (vertical ~= nil) then DependencyObject.SetPropValue(self, UIComponent.VerticalAlignmentProperty, vertical) end
  862.     return self
  863. end
  864.  
  865. ---Sets the width and height of this component
  866. ---@generic T: UIComponent
  867. ---@param self T
  868. ---@param width number? The new width
  869. ---@param height number? The new height
  870. ---@return T
  871. function UIComponent:SetSize(width, height)
  872.     if (width ~= nil) then DependencyObject.SetPropValue(self, UIComponent.WidthProperty, width) end
  873.     if (height ~= nil) then DependencyObject.SetPropValue(self, UIComponent.HeightProperty, height) end
  874.     return self
  875. end
  876.  
  877. ---Creates a new uniform panel
  878. ---@return UniformPanel
  879. function UniformPanel.new()
  880.     local obj = NewTypeInstance(UniformPanel, "UniformPanel")
  881.     return obj
  882. end
  883.  
  884. function HorizontalStackPanel.new()
  885.     return NewTypeInstance(HorizontalStackPanel, "HorizontalStackPanel")
  886. end
  887.  
  888. ---Creates a new vertical stack panel
  889. ---@return VerticalStackPanel panel The stack panel returned
  890. function VerticalStackPanel.new()
  891.     return NewTypeInstance(VerticalStackPanel, "VerticalStackPanel")
  892. end
  893.  
  894. function ProgressBar.new(min, max, val, bg_colour, val_colour)
  895.     local progBar = NewTypeInstance(ProgressBar, "ProgressBar")
  896.     progBar:SetPropValue(ProgressBar.MinimumProperty, min or 0)
  897.     progBar:SetPropValue(ProgressBar.MaximumProperty, max or 1)
  898.     progBar:SetPropValue(ProgressBar.ValueProperty, val or 0)
  899.     DependencyObject.SetPropValue(progBar, UIComponent.BackgroundProperty, bg_colour or colours.grey)
  900.     DependencyObject.SetPropValue(progBar, ProgressBar.ValueColourProperty, val_colour or colours.lightBlue)
  901.     progBar:SetSize(nil, 1)
  902.     return progBar
  903. end
  904.  
  905. function ProgressBar:SetValue(new_value, val_colour)
  906.     if (val_colour ~= nil) then
  907.         self:SetPropValue(ProgressBar.ValueColourProperty, val_colour)
  908.     end
  909.  
  910.     self:SetPropValue(ProgressBar.ValueProperty, new_value)
  911. end
  912.  
  913. function ProgressBar:OnRender()
  914.     local min = self:GetPropValue(ProgressBar.MinimumProperty) or 0
  915.     local max = self:GetPropValue(ProgressBar.MaximumProperty) or 1
  916.     local value = self:GetPropValue(ProgressBar.ValueProperty) or 0
  917.     ui.drawProgressBarH(1, 1, self.RenderWidth, min, max, value, self:GetPropValue(UIComponent.BackgroundProperty), self:GetPropValue(ProgressBar.ValueColourProperty))
  918. end
  919.  
  920. function DockPanel.new()
  921.     local obj = NewTypeInstance(DockPanel, "DockPanel")
  922.     obj.LastChildFill = true
  923.     return obj
  924. end
  925.  
  926. function ContentPresenter.new()
  927.     local obj = NewTypeInstance(ContentPresenter, "ContentPresenter")
  928.     return obj
  929. end
  930.  
  931. function TabControl:SetActiveTabItem(tabItem)
  932.     if (tabItem ~= nil and tabItem.IsActive == true) then
  933.         return
  934.     end
  935.  
  936.     local presenter = self.MyContentPresenter
  937.     local lastItem = TabControl.ActiveTabItem
  938.     if (lastItem ~= nil) then
  939.         lastItem:SetPropValue(UIComponent.BackgroundProperty, lastItem.BackgroundBeforeNotActive)
  940.         lastItem.BackgroundBeforeNotActive = nil
  941.         lastItem.IsActive = false
  942.         if (lastItem.PageContent ~= nil) then
  943.             lastItem.PageContent:SetVisibility("collapsed")
  944.         end
  945.  
  946.         lastItem:InvalidateLayoutAndVisual()
  947.         TabControl.ActiveTabItem = nil
  948.     end
  949.  
  950.     if (tabItem ~= nil) then
  951.         tabItem.BackgroundBeforeNotActive = tabItem:GetPropValue(UIComponent.BackgroundProperty)
  952.         tabItem:SetPropValue(UIComponent.BackgroundProperty, colours.lightBlue)
  953.         tabItem.IsActive = true
  954.         tabItem:InvalidateLayoutAndVisual()
  955.         TabControl.ActiveTabItem = tabItem
  956.         if (tabItem.PageContent ~= nil) then
  957.             presenter:ClearChildren()
  958.             presenter:InsertChild(tabItem.PageContent)
  959.             tabItem.PageContent:SetVisibility("visible")
  960.         end
  961.  
  962.         tabItem:RaiseEvent(Event_TabItem_OnNowActive, os.clock(), {})
  963.     end
  964.     self:InvalidateLayoutAndVisual()
  965. end
  966.  
  967. function OnTabItemClicked(self, time, args)
  968.     local tabPanel = self.Parent
  969.     if (tabPanel == nil) then
  970.         return
  971.     end
  972.  
  973.     local tabControl = tabPanel.Parent
  974.     if (tabControl == nil) then
  975.         return
  976.     end
  977.  
  978.     tabControl:SetActiveTabItem(self)
  979. end
  980.  
  981. ---Creates a new button
  982. ---@param readableTypeName string? Readable type name
  983. ---@param buttonText string? Button text
  984. ---@return Button
  985. function Button.new(readableTypeName, buttonText)
  986.     local obj = NewTypeInstance(Button, readableTypeName or "Button")
  987.     obj.IsPressed = false
  988.     obj.IsToggleButton = false
  989.     obj:SetPropValue(Button.TextProperty, buttonText)
  990.     obj:SetPropValue(UIComponent.IsFocusableProperty, true)
  991.     obj:SetPropValue(UIComponent.BackgroundProperty, colours.grey)
  992.     obj:SetPropValue(Button.PressedBackgroundProperty, colours.green)
  993.     obj.DoNotProcessButtonClickLogic = false
  994.     UIComponent.AddEventHandler(obj, Event_MouseDown,Button.OnMouseDown)
  995.     return obj
  996. end
  997.  
  998. function NewTabItem(text, pageContent)
  999.     local btn = Button.new("TabItem:" .. text, text)
  1000.     btn:SetPropValue(UIComponent.HorizontalAlignmentProperty, "stretch")
  1001.     btn:AddEventHandler(Event_Button_OnClick, OnTabItemClicked)
  1002.     btn:SetPropValue(UIComponent.BackgroundProperty, colours.blue)
  1003.     btn.PageContent = pageContent
  1004.     btn.DoNotProcessButtonClickLogic = true
  1005.     return btn
  1006. end
  1007.  
  1008. function Button:SetPressed(isPressed)
  1009.     if (self.IsPressed == isPressed) then
  1010.         return
  1011.     end
  1012.  
  1013.     self.IsPressed = isPressed
  1014.     self:InvalidateVisual()
  1015. end
  1016.  
  1017. function TextBlock.new(text, text_colour, bg_colour)
  1018.     local obj = NewTypeInstance(TextBlock, "TextBlock")
  1019.     obj:SetPropValue(TextBlock.TextProperty, text)
  1020.     if (text_colour ~= nil) then
  1021.         obj:SetPropValue(TextBlock.TextColourProperty, text_colour)
  1022.     end
  1023.     if (bg_colour ~= nil) then
  1024.         obj:SetPropValue(UIComponent.BackgroundProperty, bg_colour)
  1025.     end
  1026.     return obj
  1027. end
  1028.  
  1029. function TextBlock:MeasureOverride(available_width, available_height)
  1030.     local text = self:GetPropValue(TextBlock.TextProperty)
  1031.     if (text ~= nil) then
  1032.         local charCount = #text
  1033.         if (charCount <= available_width) then
  1034.             return charCount,1
  1035.         else
  1036.             return available_width,math.ceil(charCount / available_width)
  1037.         end
  1038.     else
  1039.         return 0, 1
  1040.     end
  1041. end
  1042.  
  1043. function TextBlock:ArrangeOverride(width, height)
  1044.     local text = self:GetPropValue(TextBlock.TextProperty)
  1045.     if (text == nil) then
  1046.         self.InternalLines = nil
  1047.         return width,height
  1048.     end
  1049.  
  1050.     local lines = {}
  1051.     local j = 1
  1052.     local k = width
  1053.     local line = 0
  1054.     while true do
  1055.         line = line + 1
  1056.         if (line > height) then
  1057.             break
  1058.         end
  1059.  
  1060.         table.insert(lines, string.sub(text, j, k))
  1061.         j = k + 1
  1062.         k = k + width
  1063.     end
  1064.  
  1065.     self.InternalLines = lines
  1066.     return width, height
  1067. end
  1068.  
  1069. function TextBlock:OnRender()
  1070.     UIComponent.OnRender(self)
  1071.     if (self.InternalLines ~= nil) then
  1072.         local txt_col = self:GetPropValue(TextBlock.TextColourProperty) or colours.white
  1073.         -- local bg_col = self:GetPropValue(UIComponent.BackgroundProperty) or colours.black
  1074.         for i, str in ipairs(self.InternalLines) do
  1075.             ui.drawText(1, i, str, txt_col, nil)
  1076.         end
  1077.     end
  1078. end
  1079.  
  1080. ---Gets the final minimum and maximum size bounds, taking into account the width and height values
  1081. ---@return number number min width
  1082. ---@return number number max width
  1083. ---@return number number min height
  1084. ---@return number number max height
  1085. function UIComponent:GetMinMax()
  1086.     local min_w,max_w = self:GetPropValue(UIComponent.MinWidthProperty),self:GetPropValue(UIComponent.MaxWidthProperty)
  1087.     local min_h,max_h = self:GetPropValue(UIComponent.MinHeightProperty),self:GetPropValue(UIComponent.MaxHeightProperty)
  1088.     local w,h = self:GetPropValue(UIComponent.WidthProperty), self:GetPropValue(UIComponent.HeightProperty)
  1089.     max_w = math.max(math.min(w or math.huge, max_w), min_w)
  1090.     min_w = math.max(math.min(max_w, w or 0), min_w)
  1091.     max_h = math.max(math.min(h or math.huge, max_h), min_h)
  1092.     min_h = math.max(math.min(max_h, h or 0), min_h)
  1093.     return min_w, max_w, min_h, max_h
  1094. end
  1095.  
  1096. function UIComponent:GetRenderPositon()
  1097.     PushFunction("GetRenderPositon", self.TypeName)
  1098.     if (self.IsVisualPositionValid) then
  1099.         return PopFunction(self.AbsVisualPosX or 1,self.AbsVisualPosY or 1)
  1100.     end
  1101.  
  1102.     if (self.IsArrangeDirty) then
  1103.         error("Arrangement is dirty; cannot get absolute position")
  1104.     end
  1105.  
  1106.     local chain = {}
  1107.     local next_component = self
  1108.     while (next_component ~= nil) do
  1109.         table.insert(chain, 1, next_component)
  1110.         next_component = next_component.Parent
  1111.     end
  1112.  
  1113.     local x,y = 0,0
  1114.     for i, component in ipairs(chain) do
  1115.         x = x + component.RenderPosX
  1116.         y = y + component.RenderPosY
  1117.     end
  1118.    
  1119.     self.AbsVisualPosX = x
  1120.     self.AbsVisualPosY = y
  1121.     self.IsVisualPositionValid = true
  1122.     return PopFunction(x,y)
  1123. end
  1124.  
  1125. function UIComponent:OnRender()
  1126.     local bg = self:GetPropValue(UIComponent.BackgroundProperty)
  1127.     if (bg ~= nil) then
  1128.         ui.fill(1, 1, self.RenderWidth, self.RenderHeight, bg)
  1129.     end
  1130. end
  1131.  
  1132. ---Measures this component (and any child components), and updates (and also returns) the desired size of this component
  1133. ---@param available_width number The constrated width
  1134. ---@param available_height number The constrated height
  1135. ---@return number Desired Width
  1136. ---@return number Desired Height
  1137. function UIComponent:Measure(available_width, available_height)
  1138.     if (available_width == nil) then error("Cannot measure with nil width") end
  1139.     if (available_height == nil) then error("Cannot measure with nil height") end
  1140.  
  1141.     if (self:GetVisibility() == "collapsed") then
  1142.         self.LastMeasureAvailableWidth = available_width
  1143.         self.LastMeasureAvailableHeight = available_height
  1144.         if (ALLOW_PRINT_DEBUG) then
  1145.             PrintDebug("Skipping measure for %s as it is collapsed", self.TypeName)
  1146.         end
  1147.     else
  1148.         -- no need to measure again if it's already the same
  1149.         local isEqualToLastSize = available_width == self.LastMeasureAvailableWidth and available_height == self.LastMeasureAvailableHeight
  1150.         if (self.IsMeasureDirty or self.NeverMeasured or not isEqualToLastSize) then
  1151.             if (ALLOW_PRINT_DEBUG) then
  1152.                 PrintDebug("Begin Measure '%s' (forced = %s, dirty = %s, NeverMeasured = %s, similar = %s)", self.TypeName, ForceArrangeOrMeasure, self.IsMeasureDirty, self.NeverMeasured, isEqualToLastSize)
  1153.             end
  1154.             self.NeverMeasured = false
  1155.             self.LastMeasureAvailableWidth = available_width
  1156.             self.LastMeasureAvailableHeight = available_height
  1157.             self.MeasureInProgress = true
  1158.             local w, h = self:MeasureCore(available_width, available_height)
  1159.             self.MeasureInProgress = false
  1160.             if (w == nil) then error("MeasureCore returned null width") end
  1161.             if (h == nil) then error("MeasureCore returned null height") end
  1162.            
  1163.             self.DesiredWidth = w
  1164.             self.DesiredHeight = h
  1165.             self.IsMeasureDirty = false
  1166.             if (ALLOW_PRINT_DEBUG) then
  1167.                 PrintDebug("End Measure '%s' (%f x %f)", self.TypeName, w, h)
  1168.             end
  1169.         else
  1170.             if (ALLOW_PRINT_DEBUG) then
  1171.                 PrintDebug("Skipping measure for %s", self.TypeName)
  1172.             end
  1173.         end
  1174.     end
  1175.  
  1176.     return self.DesiredWidth,self.DesiredHeight
  1177. end
  1178.  
  1179. function UIComponent:MeasureCore(available_width, available_height)
  1180.     local margin_width = self:GetPropValue(UIComponent.MarginLProperty) + self:GetPropValue(UIComponent.MarginRProperty)
  1181.     local margin_height = self:GetPropValue(UIComponent.MarginTProperty) + self:GetPropValue(UIComponent.MarginBProperty)
  1182.     local min_width, max_width, min_height, max_height = self:GetMinMax()
  1183.  
  1184.     local desired_width = maths.clamp(math.max(available_width - margin_width, 0), min_width, max_width)
  1185.     local desired_height = maths.clamp(math.max(available_height - margin_height, 0), min_height, max_height)
  1186.     desired_width, desired_height = self:MeasureOverride(desired_width, desired_height)
  1187.     desired_width, desired_height = math.max(desired_width, min_width), math.max(desired_height, min_height)
  1188.  
  1189.     local real_measured_width,real_measured_height = desired_width,desired_height
  1190.  
  1191.     local isClipRequired = false
  1192.     if (desired_width > max_width) then
  1193.         desired_width = max_width
  1194.         isClipRequired = true
  1195.     end
  1196.  
  1197.     if (desired_height > max_height) then
  1198.         desired_height = max_height
  1199.         isClipRequired = true
  1200.     end
  1201.  
  1202.     local final_width, final_height = desired_width + margin_width, desired_height + margin_height
  1203.     if (final_width > available_width) then
  1204.         final_width = available_width
  1205.         isClipRequired = true
  1206.     end
  1207.  
  1208.     if (final_height > available_height) then
  1209.         final_height = available_height
  1210.         isClipRequired = true
  1211.     end
  1212.  
  1213.     local unclipped_desired_size = self.UnclippedDesiredSize
  1214.     if (isClipRequired or final_width < 0 or final_height < 0) then
  1215.         if (unclipped_desired_size == nil) then
  1216.             unclipped_desired_size = {}
  1217.             self.UnclippedDesiredSize = unclipped_desired_size
  1218.         end
  1219.  
  1220.         unclipped_desired_size.w = real_measured_width
  1221.         unclipped_desired_size.h = real_measured_height
  1222.     elseif (unclipped_desired_size ~= nil) then
  1223.         self.UnclippedDesiredSize = nil
  1224.     end
  1225.  
  1226.     final_width = math.max(final_width, 0)
  1227.     final_height = math.max(final_height, 0)
  1228.     return final_width,final_height
  1229. end
  1230.  
  1231. ---The overridable method for measuring this component. Returns 0,0 for an empty component
  1232. ---@param available_width number The maximum width
  1233. ---@param available_height number The maximum height
  1234. ---@return number number Desired width
  1235. ---@return number number Desired height
  1236. function UIComponent:MeasureOverride(available_width, available_height)
  1237.     local w, h = 0,0
  1238.     for i, child in ipairs(self.Children) do
  1239.         local cW, cH = child:Measure(available_width, available_height)
  1240.         if (cW > w) then w = cW end
  1241.         if (cH > h) then h = cH end
  1242.     end
  1243.  
  1244.     return w, h
  1245. end
  1246.  
  1247. function UIComponent:SetLastArrangeRect(final_x, final_y, final_w, final_h)
  1248.     self.LastArrangeFinalX = final_x
  1249.     self.LastArrangeFinalY = final_y
  1250.     self.LastArrangeFinalW = final_w
  1251.     self.LastArrangeFinalH = final_h
  1252. end
  1253.  
  1254. ---Arranges the position and calculates the render size for this component and any child components
  1255. ---@param posX number The rendering X offset (0 by default)
  1256. ---@param posY number The rendering Y offset (0 by default)
  1257. ---@param finalWidth number
  1258. ---@param finalHeight number
  1259. function UIComponent:Arrange(posX, posY, finalWidth, finalHeight)
  1260.     if (self:GetVisibility() == "collapsed") then
  1261.         if (ALLOW_PRINT_DEBUG) then
  1262.             PrintDebug("Skipping arrange for %s as it is collapsed", self.TypeName)
  1263.         end
  1264.         self:SetLastArrangeRect(posX, posY, finalWidth, finalHeight)
  1265.         return
  1266.     end
  1267.  
  1268.     if (self.IsMeasureDirty or self.NeverMeasured) then
  1269.         if (ALLOW_PRINT_DEBUG) then
  1270.             PrintDebug("Measuring %s during arrange (Dirty = %s, NeverMeasured = %s)", self.TypeName, self.IsMeasureDirty, self.NeverMeasured)
  1271.         end
  1272.         self.MeasureDuringArrange = true
  1273.         if (self.NeverMeasured) then
  1274.             self:Measure(finalWidth, finalHeight)
  1275.         else
  1276.             self:Measure(self.LastMeasureAvailableWidth, self.LastMeasureAvailableHeight)
  1277.         end
  1278.         self.MeasureDuringArrange = false
  1279.     end
  1280.  
  1281.     local isSimilarToOldArrange = self.LastArrangeFinalX == posX and self.LastArrangeFinalY == posY and
  1282.                                   self.LastArrangeFinalW == finalWidth and self.LastArrangeFinalH == finalHeight
  1283.     if (ForceArrangeOrMeasure or self.IsArrangeDirty or self.NeverArranged or not isSimilarToOldArrange) then
  1284.         if (ALLOW_PRINT_DEBUG) then
  1285.             PrintDebug("Begin Arrange '%s' (forced = %s, dirty = %s, NeverArranged = %s, similar = %s)", self.TypeName, ForceArrangeOrMeasure, self.IsArrangeDirty, self.NeverArranged, isSimilarToOldArrange)
  1286.         end
  1287.         self.IsVisualPositionValid = false
  1288.         self.NeverArranged = false
  1289.         self.ArrangeInProgress = true
  1290.         self:ArrangeCore(posX, posY, finalWidth, finalHeight)
  1291.         self.ArrangeInProgress = false
  1292.         self:SetLastArrangeRect(posX, posY, finalWidth, finalHeight)
  1293.         self.IsArrangeDirty = false
  1294.         if (ALLOW_PRINT_DEBUG) then
  1295.             PrintDebug("End Arrange '%s' at %f,%f (%f x %f)", self.TypeName, self.RenderPosX, self.RenderPosY, self.RenderWidth, self.RenderHeight)
  1296.         end
  1297.     else
  1298.         if (ALLOW_PRINT_DEBUG) then
  1299.             PrintDebug("Skipping arrange for %s", self.TypeName)
  1300.         end
  1301.     end
  1302. end
  1303.  
  1304. function UIComponent:ArrangeCore(posX, posY, availableWidth, availableHeight)
  1305.     local mL,mT,mR,mB = self:GetPropValue(UIComponent.MarginLProperty),self:GetPropValue(UIComponent.MarginTProperty),self:GetPropValue(UIComponent.MarginRProperty),self:GetPropValue(UIComponent.MarginBProperty)
  1306.     availableWidth = math.max(availableWidth - (mL + mR), 0)
  1307.     availableHeight = math.max(availableHeight - (mT + mB), 0)
  1308.     local cpyAvailableW, cpyAvailableH = availableWidth, availableHeight
  1309.  
  1310.     local desiredWidth, desiredHeight
  1311.     if (self.UnclippedDesiredSize == nil) then
  1312.         desiredWidth = math.max(self.DesiredWidth - (mL + mR), 0)
  1313.         desiredHeight = math.max(self.DesiredHeight - (mT + mB), 0)
  1314.     else
  1315.         desiredWidth = self.UnclippedDesiredSize.w
  1316.         desiredHeight = self.UnclippedDesiredSize.h
  1317.     end
  1318.  
  1319.     -- Check if there isn't enough space available, and if so, require clipping
  1320.     self.NeedClipBounds = false
  1321.     if (desiredWidth > availableWidth) then
  1322.         availableWidth = desiredWidth
  1323.         self.NeedClipBounds = true
  1324.     end
  1325.     if (desiredHeight > availableHeight) then
  1326.         availableHeight = desiredHeight
  1327.         self.NeedClipBounds = true
  1328.     end
  1329.  
  1330.     -- If alignment is stretch, then arrange using all of the available size
  1331.     -- Otherwise, only use our desired size, leaving extra space for other components
  1332.     local alignHorz = DependencyObject.GetPropValue(self, UIComponent.HorizontalAlignmentProperty)
  1333.     local alignVert = DependencyObject.GetPropValue(self, UIComponent.VerticalAlignmentProperty)
  1334.    
  1335.     if (alignHorz ~= "stretch") then availableWidth = desiredWidth end
  1336.     if (alignVert ~= "stretch") then availableHeight = desiredHeight end
  1337.  
  1338.     local _, max_width, _, max_height = self:GetMinMax()
  1339.  
  1340.     local maxOrDesiredWidth = math.max(desiredWidth, max_width)
  1341.     if (availableWidth > maxOrDesiredWidth) then
  1342.         availableWidth = maxOrDesiredWidth
  1343.         self.NeedClipBounds = true
  1344.     end
  1345.  
  1346.     local maxOrDesiredHeight = math.max(desiredHeight, max_height)
  1347.     if (availableHeight > maxOrDesiredHeight) then
  1348.         availableHeight = maxOrDesiredHeight
  1349.         self.NeedClipBounds = true
  1350.     end
  1351.  
  1352.     local arrangeWidth, arrangeHeight = self:ArrangeOverride(availableWidth, availableHeight)
  1353.     if (arrangeWidth == nil) then error("ArrangeOverride Width cannot be null") end
  1354.     if (arrangeHeight == nil) then error("ArrangeOverride Height cannot be null") end
  1355.     self.RenderWidth = arrangeWidth
  1356.     self.RenderHeight = arrangeHeight
  1357.  
  1358.     -- The actual arranged width/height exceeds our max width/height, so clip
  1359.     local finalArrangeWidth,finalArrangeHeight = math.min(arrangeWidth, max_width),math.min(arrangeHeight, max_height)
  1360.     if (not self.NeedClipBounds and ((finalArrangeWidth < arrangeWidth) or (finalArrangeHeight < arrangeHeight))) then
  1361.         self.NeedClipBounds = true
  1362.     end
  1363.     if (not self.NeedClipBounds and ((cpyAvailableW < finalArrangeWidth) or (cpyAvailableH < finalArrangeHeight))) then
  1364.         self.NeedClipBounds = true
  1365.     end
  1366.  
  1367.     if (finalArrangeWidth > cpyAvailableW) then alignHorz = "left" end
  1368.     if (finalArrangeHeight > cpyAvailableH) then alignVert = "top" end
  1369.  
  1370.     local vecX, vecY = 0,0
  1371.     if (alignHorz == "center" or alignHorz == "stretch") then
  1372.         vecX = (cpyAvailableW - finalArrangeWidth) / 2
  1373.     elseif (alignHorz == "right") then
  1374.         vecX = cpyAvailableW - finalArrangeWidth
  1375.     end
  1376.  
  1377.     if (alignVert == "center" or alignVert == "stretch") then
  1378.         vecY = (cpyAvailableH - finalArrangeHeight) / 2
  1379.     elseif (alignVert == "bottom") then
  1380.         vecY = cpyAvailableH - finalArrangeHeight
  1381.     end
  1382.  
  1383.     self.RenderPosX = vecX + posX + mL
  1384.     self.RenderPosY = vecY + posY + mT
  1385. end
  1386.  
  1387. ---The overridable method for arranging this component
  1388. ---@param width number The final width of this component
  1389. ---@param height number The final height of this component
  1390. ---@return number The render width, typically the final width parameter
  1391. ---@return number The render height, typically the final height parameter
  1392. function UIComponent:ArrangeOverride(width, height)
  1393.     for i, child in ipairs(self.Children) do
  1394.         child:Arrange(0, 0, width, height)
  1395.         if (child.IsArrangeDirty) then
  1396.             error("Child arrange was dirty after Arrange() call")
  1397.         end
  1398.     end
  1399.     return width, height
  1400. end
  1401.  
  1402. function UIComponent:IsMousePointOver(x, y)
  1403.     if (x < 0 or x >= self.RenderWidth) then
  1404.         return false
  1405.     end
  1406.     if (y < 0 or y >= self.RenderHeight) then
  1407.         return false
  1408.     end
  1409.     return true
  1410. end
  1411.  
  1412. function toolkit.RenderComponent(component)
  1413.     local absX,absY = component:GetRenderPositon()
  1414.     CompositionOffsetX = absX
  1415.     CompositionOffsetY = absY
  1416.     CompositionComponentSource = component
  1417.     component:OnRender()
  1418.     component.IsVisualDirty = false
  1419. end
  1420.  
  1421. function toolkit.RenderComponentTreeInternal(component)
  1422.     if (component:GetVisibility() ~= "visible") then
  1423.         return
  1424.     end
  1425.  
  1426.     if (component.IsArrangeDirty) then
  1427.         error("Component's arrangement is dirty; cannot render")
  1428.     end
  1429.  
  1430.     toolkit.RenderComponent(component)
  1431.     for i, child in ipairs(component.Children) do
  1432.         toolkit.RenderComponentTreeInternal(child)
  1433.     end
  1434. end
  1435.  
  1436. function toolkit.RenderComponentTree(component)
  1437.     PushFunction("DoRenderComponent", component.TypeName)
  1438.     if (ENABLE_RENDERING) then
  1439.         toolkit.RenderComponentTreeInternal(component)
  1440.         CompositionComponentSource = nil
  1441.         CompositionOffsetX = 0
  1442.         CompositionOffsetY = 0
  1443.     end
  1444.  
  1445.     PopFunction()
  1446. end
  1447.  
  1448. function toolkit.GetComponentDepth(component, depth)
  1449.     if (depth == nil) then depth = 0 end
  1450.     while component ~= nil do
  1451.         depth = depth + 1
  1452.         component = component.Parent
  1453.     end
  1454.     return depth
  1455. end
  1456.  
  1457. function toolkit.FindHighestComponentForRender(renderList)
  1458.     if (#renderList < 1) then
  1459.         return nil
  1460.     end
  1461.    
  1462.     local hIndex, hObj = toolkit.GetComponentDepth(renderList[1]), renderList[1]
  1463.     for i = 2, #renderList, 1 do
  1464.         local obj = renderList[i]
  1465.         local depth = toolkit.GetComponentDepth(obj)
  1466.         if (depth < hIndex) then
  1467.             hIndex = depth
  1468.             hObj = obj
  1469.         end
  1470.     end
  1471.  
  1472.     return hObj
  1473. end
  1474.  
  1475. function ThrowForDirtyArrangeWhenUnexpected(component)
  1476.     PushFunction("ThrowForDirtyArrangeWhenUnexpected: " .. component.TypeName)
  1477.     if (component:GetVisibility() ~= "collapsed" and component.IsArrangeDirty) then
  1478.         error("Child arrange was dirty after Arrange() call")
  1479.     end
  1480.     for i, child in ipairs(component.Children) do
  1481.         ThrowForDirtyArrangeWhenUnexpected(child)
  1482.     end
  1483.     PopFunction()
  1484. end
  1485.  
  1486. function toolkit.InternalCanProcessItem(component, isMeasure)
  1487.     if (isMeasure) then
  1488.         return component.IsMeasureDirty
  1489.     else
  1490.         return component.IsArrangeDirty
  1491.     end
  1492. end
  1493.  
  1494. function toolkit.DoLayoutUpdate()
  1495.     PushFunction("DoLayoutUpdate")
  1496.     if (ALLOW_PRINT_DEBUG) then
  1497.         PrintDebug("Doing layout update: %d measures and %d arranges", #layout_measure_queue, #layout_arrange_queue)
  1498.     end
  1499.  
  1500.     -- workaround for measuring/arranging scanning top-level components. If a child deep in the hierarchy
  1501.     -- is invalidated but a few parents above but before target_component are not invalid,
  1502.     -- then the child is never arranged. This is mostly a LUA performance limitation, since WPF runs on .NET
  1503.     -- which can afford to do the tree scan while invalidating each parent stage
  1504.     ForceArrangeOrMeasure = true
  1505.  
  1506.     if (#layout_measure_queue > 0) then
  1507.         for i, component in ipairs(layout_measure_queue) do
  1508.             local last_dirty = nil
  1509.             local scan_parent = component.Parent
  1510.             while (scan_parent ~= nil) do
  1511.                 if (scan_parent.IsMeasureDirty) then
  1512.                     last_dirty = scan_parent
  1513.                 end
  1514.                 scan_parent = scan_parent.Parent
  1515.             end
  1516.  
  1517.             local target_component
  1518.             if (last_dirty ~= nil) then
  1519.                 target_component = last_dirty
  1520.             else
  1521.                 target_component = component
  1522.             end
  1523.  
  1524.             local w,h
  1525.             if (not target_component.NeverMeasured) then
  1526.                 w = target_component.LastMeasureAvailableWidth
  1527.                 h = target_component.LastMeasureAvailableHeight
  1528.             elseif (target_component.Parent == nil) then
  1529.                 w,h = toolkit.GetCompositionRenderSize()
  1530.             else
  1531.                 local p = target_component.Parent
  1532.                 w = p.LastMeasureAvailableWidth
  1533.                 h = p.LastMeasureAvailableHeight
  1534.             end
  1535.  
  1536.             local lastDw,lastDh = component.DesiredWidth,component.DesiredHeight
  1537.             component:Measure(w, h)
  1538.             if (lastDw ~= component.DesiredWidth or lastDh ~= component.DesiredHeight) then
  1539.                 component:InvalidateArrange()
  1540.             end
  1541.         end
  1542.  
  1543.         ClearTable(layout_measure_queue)
  1544.     end
  1545.  
  1546.     if (#layout_arrange_queue > 0) then
  1547.         for i, component in ipairs(layout_arrange_queue) do
  1548.             local last_dirty = nil
  1549.             local scan_parent = component.Parent
  1550.             while (scan_parent ~= nil) do
  1551.                 if (scan_parent.IsArrangeDirty) then
  1552.                     last_dirty = scan_parent
  1553.                 end
  1554.                 scan_parent = scan_parent.Parent
  1555.             end
  1556.  
  1557.             local c -- target component
  1558.             if (last_dirty ~= nil) then
  1559.                 c = last_dirty
  1560.             else
  1561.                 c = component
  1562.             end
  1563.  
  1564.             local x,y,w,h
  1565.             if (not c.NeverArranged) then
  1566.                 x,y,w,h = c.LastArrangeFinalX, c.LastArrangeFinalY, c.LastArrangeFinalW, c.LastArrangeFinalH
  1567.             elseif (c.Parent == nil) then
  1568.                 x,y,w,h = 0, 0, toolkit.GetCompositionRenderSize()
  1569.             else
  1570.                 -- as long as arrangement happens in the correct order, the parent will have
  1571.                 -- always been arranged at least once due to the code in AppMain()
  1572.                 local p = c.Parent
  1573.                 x,y,w,h = p.LastArrangeFinalX, p.LastArrangeFinalY, p.LastArrangeFinalW, p.LastArrangeFinalH
  1574.             end
  1575.  
  1576.             local lastX, lastY = c.LastArrangeFinalX,c.LastArrangeFinalY
  1577.             local lastW, lastH = c.LastArrangeFinalW,c.LastArrangeFinalH
  1578.             c:Arrange(x, y, w, h)
  1579.  
  1580.             -- use when arrangement isn't working correctly
  1581.             -- ThrowForDirtyArrangeWhenUnexpected(target_component)
  1582.  
  1583.             component.IsVisualPositionValid = false
  1584.             local hasPosChanged = c.LastArrangeFinalX ~= lastX or c.LastArrangeFinalY ~= lastY
  1585.             local hasSizeChanged = c.LastArrangeFinalW ~= lastW or c.LastArrangeFinalH ~= lastH
  1586.             if (hasPosChanged or hasSizeChanged) then
  1587.                 UIComponent.InvalidateVisual(c)
  1588.             end
  1589.         end
  1590.  
  1591.         ClearTable(layout_arrange_queue)
  1592.     end
  1593.  
  1594.     ForceArrangeOrMeasure = false
  1595.     if (IsRootRenderDirty) then
  1596.         IsRootRenderDirty = false
  1597.         if (not IsDisplayHidden) then
  1598.             toolkit.RenderComponentTree(AppRootComponent)
  1599.         end
  1600.     end
  1601.  
  1602.     PopFunction()
  1603. end
  1604.  
  1605. function toolkit.TryScheduleLayoutUpdate()
  1606.     if (TheLayoutUpdateTask == nil or TheLayoutUpdateTask.IsCompleted) then
  1607.         TheLayoutUpdateTask = InvokeAsync(toolkit.DoLayoutUpdate, PriorityLayout)
  1608.     end
  1609. end
  1610.  
  1611. function UIComponent:InvalidateArrange()
  1612.     if (self.IsArrangeDirty or self.ArrangeInProgress) then
  1613.         return
  1614.     end
  1615.  
  1616.     if (ALLOW_PRINT_DEBUG) then
  1617.         PrintDebug("Arrange invalidated for '%s'", self.TypeName)
  1618.     end
  1619.  
  1620.     if (not self.NeverArranged) then
  1621.         table.insert(layout_arrange_queue, self)
  1622.     end
  1623.  
  1624.     self.IsArrangeDirty = true
  1625.     toolkit.TryScheduleLayoutUpdate()
  1626. end
  1627.  
  1628. function UIComponent:InvalidateMeasure()
  1629.     if (self.IsMeasureDirty or self.MeasureInProgress) then
  1630.         return
  1631.     end
  1632.  
  1633.     if (ALLOW_PRINT_DEBUG) then
  1634.         PrintDebug("Measure invalidated for '%s'", self.TypeName)
  1635.     end
  1636.  
  1637.     if (not self.NeverMeasured) then
  1638.         table.insert(layout_measure_queue, self)
  1639.     end
  1640.  
  1641.     self.IsMeasureDirty = true
  1642.     toolkit.TryScheduleLayoutUpdate()
  1643. end
  1644.  
  1645. function UIComponent:InvalidateVisual()
  1646.     toolkit.TryScheduleLayoutUpdate()
  1647.     if (not self.IsVisualDirty) then
  1648.         self.IsVisualDirty = true
  1649.         IsRootRenderDirty = true
  1650.         if (ALLOW_PRINT_DEBUG) then
  1651.             PrintDebug("Visual invalidated for '%s'", self.TypeName)
  1652.         end
  1653.     end
  1654. end
  1655.  
  1656. function UIComponent:InvalidateLayoutAndVisual()
  1657.     self:InvalidateMeasure()
  1658.     self:InvalidateArrange()
  1659.     self:InvalidateVisual()
  1660. end
  1661.  
  1662. -- ---@class ClassA
  1663. -- ---@field Name string
  1664. -- ---@field Type string
  1665. -- local ClassA = { Name = "joe", Type = "joemama" }
  1666. --
  1667. -- ---Adds an item
  1668. -- ---@generic T: ClassA
  1669. -- ---@param item T the
  1670. -- function ClassA:AddChild(item)
  1671. --     item.Name = "jo"
  1672. -- end
  1673.  
  1674. ---Inserts a child component
  1675. ---@generic T : UIComponent
  1676. ---@param child T The child to add
  1677. ---@param id string? A unique identifier for this component (relative to the component in which a child is being added to)
  1678. ---@return T
  1679. function UIComponent:InsertChild(child, id)
  1680.     PushFunction("InsertChild")
  1681.     assert(child ~= nil, "Child cannot be null")
  1682.     if (child.IndexInParent ~= -1) then
  1683.         error("Child component already added to another component: " .. child.IndexInParent)
  1684.     end
  1685.  
  1686.     if (self == child) then
  1687.         error("Cannot add self as a child")
  1688.     end
  1689.  
  1690.     child.Parent = self
  1691.     table.insert(self.Children, child)
  1692.     if (id ~= nil) then
  1693.         child.IdInParent = id
  1694.         self.IdToChildTable[id] = child
  1695.     end
  1696.  
  1697.     UpdateCachedIndices(self.Children)
  1698.     child:UpdateVisibility()
  1699.     self:InvalidateLayoutAndVisual()
  1700.     return PopFunction(child)
  1701. end
  1702.  
  1703. function UIComponent:RemoveChild(child, useDeferredCacheUpdate)
  1704.     if (child.IndexInParent == -1 or child.Parent ~= self) then
  1705.         return
  1706.     end
  1707.     self:RemoveChildAt(child.IndexInParent, useDeferredCacheUpdate)
  1708. end
  1709.  
  1710. function UIComponent:GetChildById(id)
  1711.     return self.IdToChildTable[id]
  1712. end
  1713.  
  1714. function UIComponent:RemoveChildAt(index, useDeferredCacheUpdate)
  1715.     PushFunction("RemoveChildAt", index, useDeferredCacheUpdate)
  1716.     local child = self.Children[index]
  1717.     child.Parent = nil
  1718.     child.IndexInParent = -1
  1719.     table.remove(self.Children, index)
  1720.     if (child.IdInParent ~= nil) then
  1721.         self.IdToChildTable[child.IdInParent] = nil
  1722.     end
  1723.     if (useDeferredCacheUpdate ~= true) then
  1724.         UpdateCachedIndices(self.Children)
  1725.     end
  1726.     child:UpdateVisibility()
  1727.     PopFunction()
  1728. end
  1729.  
  1730. function UIComponent:RemoveFromParent()
  1731.     if (self.IndexInParent == -1 or self.Parent == nil) then
  1732.         return
  1733.     end
  1734.  
  1735.     self.Parent:RemoveChildAt(self.IndexInParent)
  1736. end
  1737.  
  1738. function UIComponent:ClearChildren()
  1739.     PushFunction("ClearChildren")
  1740.     for i = #self.Children, 1, -1 do
  1741.         self:RemoveChildAt(i, true)
  1742.     end
  1743.     self.IdToChildTable = {}
  1744.     UpdateCachedIndices(self.Children)
  1745.     PopFunction()
  1746. end
  1747.  
  1748. function UIComponent:OnGotFocus() end
  1749. function UIComponent:OnLostFocus() end
  1750.  
  1751. function UniformPanel:CountVisibleChildren()
  1752.     local count = 0
  1753.     for i,child in ipairs(self.Children) do
  1754.         if (child:GetVisibility() ~= "collapsed") then
  1755.             count = count + 1
  1756.         end
  1757.     end
  1758.     return count
  1759. end
  1760.  
  1761. function UniformPanel:GetTotalGap(numElements, spacing)
  1762.     if (numElements > 1) then
  1763.         return (numElements - 1) * spacing
  1764.     end
  1765.     return 0
  1766. end
  1767.  
  1768. function UniformPanel:GetSlotPerElement(width, height, count)
  1769.     local totalGap = self:GetTotalGap(count, self:GetPropValue(UniformPanel.SpacingProperty))
  1770.     if (self:GetPropValue(UniformPanel.OrientationProperty) == "vertical") then
  1771.         return width, math.ceil((height - totalGap) / count)
  1772.     else
  1773.         return math.ceil((width - totalGap) / count), height
  1774.     end
  1775. end
  1776.  
  1777. function UniformPanel:MeasureOverride(max_w, max_h)
  1778.     local count = self:CountVisibleChildren()
  1779.     local slotW,slotH = self:GetSlotPerElement(max_w, max_h, count)
  1780.     local finalW, finalH = 0,0
  1781.     for i, child in ipairs(self.Children) do
  1782.         child:Measure(slotW, slotH)
  1783.         if (finalW < child.DesiredWidth) then finalW = child.DesiredWidth end
  1784.         if (finalH < child.DesiredHeight) then finalH = child.DesiredHeight end
  1785.     end
  1786.  
  1787.     return finalW,finalH
  1788.     -- if (self.Orientation == "horizontal") then
  1789.     --     return finalW * count, finalH
  1790.     -- else
  1791.     --     return finalW, finalH * count
  1792.     -- end
  1793. end
  1794.  
  1795. function UniformPanel:ArrangeOverride(arrangeW, arrangeH)
  1796.     local finalX,finalY = 0,0
  1797.     local count = self:CountVisibleChildren()
  1798.     local orientation = self:GetPropValue(UniformPanel.OrientationProperty)
  1799.     local spacing = self:GetPropValue(UniformPanel.SpacingProperty)
  1800.     local finalW,finalH = self:GetSlotPerElement(arrangeW, arrangeH, count)
  1801.     for i, child in ipairs(self.Children) do
  1802.         child:Arrange(finalX, finalY, finalW, finalH)
  1803.         if (child:GetVisibility() ~= "collapsed") then
  1804.             if (orientation == "vertical") then
  1805.                 finalY = finalY + finalH + spacing
  1806.             else
  1807.                 finalX = finalX + finalW + spacing
  1808.             end
  1809.         end
  1810.     end
  1811.  
  1812.     return arrangeW, arrangeH
  1813. end
  1814.  
  1815. -- function UniformPanel:MeasureOverride(max_w, max_h)
  1816. --     local limitW = math.ceil(max_w / #self.Children)
  1817. --     local wid, hei = 0, 0
  1818. --     for i, child in ipairs(self.Children) do
  1819. --         local cW, cH = child:Measure(limitW, max_h)
  1820. --         if (wid < cW) then wid = cW end
  1821. --         if (hei < cH) then hei = cH end
  1822. --     end
  1823. --     return wid * #self.Children, hei
  1824. -- end
  1825. --
  1826. -- function UniformPanel:ArrangeOverride(arrangeW, arrangeH)
  1827. --     local frX, frY, frW, frH = 0, 0, math.ceil(arrangeW / #self.Children), arrangeH
  1828. --     local width = frW
  1829. --     local numEx = arrangeW - 1
  1830. --     for i, child in ipairs(self.Children) do
  1831. --         child:Arrange(frX, frY, frW, frH)
  1832. --         frX = frX + width
  1833. --         if (frX >= numEx) then
  1834. --             frY = frY + frH
  1835. --             frX = 0
  1836. --         end
  1837. --     end
  1838. --     return arrangeW,arrangeH
  1839. -- end
  1840.  
  1841. function HorizontalStackPanel:MeasureOverride(max_w, max_h)
  1842.     local new_max_w, new_max_h = 0, 0
  1843.     for i, child in ipairs(self.Children) do
  1844.         local desired_w, desired_h = child:Measure(max_w, max_h)
  1845.         new_max_w = new_max_w + desired_w
  1846.         if (new_max_h < desired_h) then new_max_h = desired_h end
  1847.     end
  1848.  
  1849.     return new_max_w, new_max_h
  1850. end
  1851.  
  1852. function HorizontalStackPanel:ArrangeOverride(arrangeW, arrangeH)
  1853.     local finalX = 0
  1854.     for i, child in ipairs(self.Children) do
  1855.         child:Arrange(finalX, 0, child.DesiredWidth, arrangeH)
  1856.         finalX = finalX + child.DesiredWidth
  1857.     end
  1858.     return arrangeW,arrangeH
  1859. end
  1860.  
  1861. function VerticalStackPanel:MeasureOverride(max_w, max_h)
  1862.     local new_max_w, new_max_h = 0, 0
  1863.     for i, child in ipairs(self.Children) do
  1864.         local desired_w, desired_h = child:Measure(max_w, max_h)
  1865.         new_max_h = new_max_h + desired_h
  1866.         if (new_max_w < desired_w) then new_max_w = desired_w end
  1867.     end
  1868.  
  1869.     return new_max_w, new_max_h
  1870. end
  1871.  
  1872. function VerticalStackPanel:ArrangeOverride(arrangeW, arrangeH)
  1873.     local finalY = 0
  1874.     for i, child in ipairs(self.Children) do
  1875.         child:Arrange(0, finalY, arrangeW, child.DesiredHeight)
  1876.         finalY = finalY + child.DesiredHeight
  1877.     end
  1878.     return arrangeW,arrangeH
  1879. end
  1880.  
  1881. function DockPanel:MeasureOverride(max_w, max_h)
  1882.     local actual_max_w,actual_max_h,total_w,total_h = 0,0,0,0
  1883.     for i, child in ipairs(self.Children) do
  1884.         child:Measure(math.max(0, max_w - total_w), math.max(0, max_h - total_h))
  1885.         local dock = child.DockValue or "left"
  1886.         if (dock == "left" or dock == "right") then
  1887.             actual_max_h = math.max(actual_max_h, total_h + child.DesiredHeight)
  1888.             total_w = total_w + child.DesiredWidth
  1889.         elseif (dock == "top" or dock == "bottom") then
  1890.             actual_max_w = math.max(actual_max_w, total_w + child.DesiredWidth)
  1891.             total_h = total_h + child.DesiredHeight
  1892.         end
  1893.     end
  1894.     return math.max(actual_max_w, total_w),math.max(actual_max_h, total_h)
  1895. end
  1896.  
  1897. function DockPanel:ArrangeOverride(arr_w, arr_h)
  1898.     local lastIndex = #self.Children
  1899.     if (self.LastChildFill) then
  1900.         lastIndex = lastIndex - 1
  1901.     end
  1902.  
  1903.     local x,y,total_w,total_h = 0,0,0,0
  1904.    
  1905.     for i, child in ipairs(self.Children) do
  1906.         local fx,fy = x,y
  1907.         local fw,fh = math.max(0, arr_w - (x + total_w)), math.max(0, arr_h - (y + total_h))
  1908.         local dock = child.DockValue or "left"
  1909.         if ((i - 1) < lastIndex) then
  1910.             if (dock == "left") then
  1911.                 x = x + child.DesiredWidth
  1912.                 fw = child.DesiredWidth
  1913.             elseif (dock == "top") then
  1914.                 y = y + child.DesiredHeight
  1915.                 fh = child.DesiredHeight
  1916.             elseif (dock == "right") then
  1917.                 total_w = total_w + child.DesiredWidth
  1918.                 fx = math.max(0, arr_w - total_w)
  1919.                 fw = child.DesiredWidth
  1920.             elseif (dock == "bottom") then
  1921.                 total_h = total_h + child.DesiredHeight
  1922.                 fy = math.max(0, arr_h - total_h)
  1923.                 fh = child.DesiredHeight
  1924.             end
  1925.         end
  1926.  
  1927.         child:Arrange(fx, fy, fw, fh)
  1928.     end
  1929.     return arr_w,arr_h
  1930. end
  1931.  
  1932. function Button.OnMouseDown(self, time, args)
  1933.     args.handled = true
  1934.     if (not self.DoNotProcessButtonClickLogic) then
  1935.         if (self.IsToggleButton) then
  1936.             self.IsPressed = not self.IsPressed
  1937.             self:InvalidateVisual()
  1938.         elseif (not self.IsPressed) then
  1939.             self.IsPressed = true
  1940.             self:InvalidateVisual()
  1941.             InvokeAsyncWithDelay(function ()
  1942.                 self.IsPressed = false
  1943.                 self:InvalidateVisual()
  1944.             end, 0.5)
  1945.         end
  1946.     end
  1947.  
  1948.     self:RaiseEvent(Event_Button_OnClick, time, args)
  1949. end
  1950.  
  1951. function Button:MeasureOverride(max_w, max_h)
  1952.     local w, h = UIComponent.MeasureOverride(self, max_w, max_h)
  1953.     local text = self:GetPropValue(Button.TextProperty)
  1954.     if (text ~= nil and #text > w) then
  1955.         w = #text
  1956.     end
  1957.     if (h < 1) then h = 1 end
  1958.  
  1959.     return w, h
  1960. end
  1961.  
  1962. function Button:OnRender()
  1963.     local bg_colour
  1964.     if (self.IsPressed) then
  1965.         bg_colour = self:GetPropValue(Button.PressedBackgroundProperty)
  1966.     else
  1967.         bg_colour = self:GetPropValue(UIComponent.BackgroundProperty)
  1968.     end
  1969.     ui.fill(1, 1, self.RenderWidth, self.RenderHeight, bg_colour)
  1970.     ui.drawTextCentered(1, 1, self.RenderWidth, self.RenderHeight, self:GetPropValue(Button.TextProperty) or "", colours.white, nil)
  1971. end
  1972.  
  1973. function toolkit.FocusComponent(component)
  1974.     PushFunction("FocusComponent", component)
  1975.     if (component ~= nil and not component:GetPropValue(UIComponent.IsFocusableProperty)) then
  1976.         PopFunction()
  1977.         return
  1978.     end
  1979.  
  1980.     if (FocusedComponent ~= nil and FocusedComponent:GetPropValue(UIComponent.IsFocusedProperty)) then
  1981.         FocusedComponent:SetPropValue(UIComponent.IsFocusedProperty, false)
  1982.         FocusedComponent:OnLostFocus()
  1983.     end
  1984.  
  1985.     FocusedComponent = component
  1986.     if (component ~= nil) then
  1987.         component:SetPropValue(UIComponent.IsFocusedProperty, true)
  1988.         component:OnGotFocus()
  1989.     end
  1990.     PopFunction()
  1991. end
  1992.  
  1993. ---Sets the target peripheral object that will be used for arrangement and rendering
  1994. ---@param target table The new target. May only be null while app functions are not being used
  1995. function toolkit.SetComponsitionTarget(target)
  1996.     CompositionTarget = target
  1997.     if (target == nil) then
  1998.         CompositionWidth = 0
  1999.         CompositionHeight = 0
  2000.     else
  2001.         CompositionWidth, CompositionHeight = target.getSize()
  2002.     end
  2003. end
  2004.  
  2005. function toolkit.GetCompositionRenderSize()
  2006.     return CompositionWidth,CompositionHeight
  2007. end
  2008.  
  2009. --endregion
  2010.  
  2011. --region Application
  2012.  
  2013. function UIComponent:DoMouseClick_Tunnel(time, button, x, y)
  2014.     PushFunction("DoMouseClick_Tunnel", self.TypeName, button, x, y)
  2015.     if (self:IsMousePointOver(x, y)) then
  2016.         local args = {btn = button, x = x, y = y, handled = false}
  2017.         self:RaiseEvent(Event_PreviewMouseDown, time, args)
  2018.         if (not args.handled) then
  2019.             for i = #self.Children, 1, -1 do
  2020.                 local child = self.Children[i]
  2021.                 local posX, posY = x - child.RenderPosX, y - child.RenderPosY
  2022.                 local hit = child:DoMouseClick_Tunnel(time, button, posX, posY)
  2023.                 if (hit ~= nil) then
  2024.                     return PopFunction(hit,posX,posY)
  2025.                 end
  2026.             end
  2027.         end
  2028.  
  2029.         if (args.handled or self:GetPropValue(UIComponent.IsFocusableProperty) or self:GetPropValue(UIComponent.BackgroundProperty) ~= nil) then
  2030.             return PopFunction(self,x,y)
  2031.         end
  2032.     end
  2033.  
  2034.     return PopFunction(nil,nil,nil)
  2035. end
  2036.  
  2037. function UIComponent:DoMouseClick_Bubble(time, button, x, y)
  2038.     PushFunction("DoMouseClick_Bubble", self.TypeName, button, x, y)
  2039.     local eventArgs = {btn = button, x = x, y = y, handled = false}
  2040.     if (self:RaiseEvent(Event_MouseDown, time, eventArgs)) then
  2041.         return PopFunction(self)
  2042.     end
  2043.  
  2044.     if (self.Parent ~= nil) then
  2045.         x = x + self.RenderPosX
  2046.         y = y + self.RenderPosY
  2047.         return PopFunction(self.Parent:DoMouseClick_Bubble(time, button, x, y))
  2048.     end
  2049.  
  2050.     return PopFunction(nil)
  2051. end
  2052.  
  2053. function toolkit.OnWakeDisplay(time)
  2054.     LastDisplayWakeTime = time
  2055.     if (IsDisplayHidden) then
  2056.         IsDisplayHidden = false
  2057.         if (AppRootComponent ~= nil) then
  2058.             AppRootComponent:InvalidateVisual()
  2059.         end
  2060.  
  2061.         return true
  2062.     else
  2063.         return false
  2064.     end
  2065. end
  2066.  
  2067. function toolkit.OnTickDisplaySleep(time)
  2068.     if (CanDisplayAutoSleep and (not IsDisplayHidden) and (time - LastDisplayWakeTime) > 300) then -- 5 minutes
  2069.         IsDisplayHidden = true
  2070.         ui.clear()
  2071.  
  2072.         local w,h = toolkit.GetCompositionRenderSize()
  2073.         ui.drawTextCentered(1, 1, w, h, "Display is asleep. Click to wake", colours.grey, colours.black)
  2074.     end
  2075. end
  2076.  
  2077. ---Invoked when the user clicks a specific point on the screen
  2078. ---@param time number The OS clock time
  2079. ---@param btn integer The mouse button (1=LMB,2=RMB,3=MWB)
  2080. ---@param absX integer The mouse pos X
  2081. ---@param absY integer The mouse pos Y
  2082. function toolkit.OnMouseClick(time, btn, absX, absY)
  2083.     if (AppRootComponent == nil) then
  2084.         return
  2085.     end
  2086.  
  2087.     PushFunction("OnMouseClick", btn, absX, absY)
  2088.     local hit,x,y = AppRootComponent:DoMouseClick_Tunnel(time, btn, absX, absY)
  2089.     if (hit == nil) then
  2090.         toolkit.FocusComponent(nil)
  2091.     else
  2092.         local bubbleHit = hit:DoMouseClick_Bubble(time, btn, x, y)
  2093.         if (bubbleHit ~= nil) then
  2094.             toolkit.FocusComponent(bubbleHit)
  2095.         else
  2096.             toolkit.FocusComponent(hit)
  2097.         end
  2098.     end
  2099.     PopFunction()
  2100. end
  2101.  
  2102. function toolkit.OnMouseDrag(time, btn, absX, absY)
  2103.     PushFunction("OnMouseDrag", btn, absX, absY)
  2104.     if (FocusedComponent ~= nil and FocusedComponent.CanHandleDrag and not FocusedComponent.IsArrangeDirty) then
  2105.         local rpX,rpY = FocusedComponent:GetRenderPositon()
  2106.         local eventArgs = {btn = btn, x = absX - rpX, y = absY - rpY, absX = absX, absY = absY, handled = false}
  2107.         FocusedComponent:RaiseEvent(Event_MouseDrag, time, eventArgs)
  2108.     end
  2109.     return PopFunction()
  2110. end
  2111.  
  2112. -- Sets up a timer for the next application tick
  2113. function SetupTimer(delay)
  2114.     AppTimerId = os.startTimer(delay)
  2115. end
  2116.  
  2117. local function OnApplicationEvent(time, eventType, p1, p2, p3, p4, p5)
  2118.     PushFunction("OnApplicationEvent", eventType, p1, p2, p3, p4, p5)
  2119.     if (eventType == "timer") then
  2120.         if (p1 == AppTimerId) then
  2121.             SetupTimer(AppTimerInterval)
  2122.             if (#delayedTaskQueue > 0) then
  2123.                 local operationList = {}
  2124.                 local removalList = {}
  2125.                 for i, operation in ipairs(delayedTaskQueue) do
  2126.                     if (ALLOW_PRINT_DEBUG) then
  2127.                         PrintDebug(tostring(time) .. " >= " .. operation.TimeUntilExecution .. ": " .. tostring(time >= operation.TimeUntilExecution))
  2128.                     end
  2129.  
  2130.                     if (time >= operation.TimeUntilExecution) then
  2131.                         table.insert(operationList, operation)
  2132.                         table.insert(removalList, i)
  2133.                     end
  2134.                 end
  2135.  
  2136.                 -- Remove back to front. Always faster than front to back
  2137.                 for i = #removalList, 1, -1 do
  2138.                     table.remove(delayedTaskQueue, removalList[i])
  2139.                 end
  2140.  
  2141.                 -- Invoke operations
  2142.                 for i, operation in ipairs(operationList) do
  2143.                     operation.Method(unpack(operation.Args))
  2144.                 end
  2145.             end
  2146.  
  2147.             toolkit.OnTickDisplaySleep(time)
  2148.         elseif (p1 == AppDispatcherId) then
  2149.             AppDispatcherId = 0
  2150.  
  2151.             -- accumulate operations to invoke
  2152.             local invocationList = {}
  2153.             for i, list in ipairs(dispatcher_queue) do
  2154.                 for j = 1, #list do
  2155.                     table.insert(invocationList, list[j])
  2156.                 end
  2157.                 ClearTable(list)
  2158.             end
  2159.  
  2160.             -- execute dispatcher queue
  2161.             if (#invocationList > 0) then
  2162.                 IsProcessingDispatcherQueue = true
  2163.                 for i = 1, #invocationList do
  2164.                     local operation = invocationList[i]
  2165.                     operation.Method(unpack(operation.Args))
  2166.                     operation.IsCompleted = true
  2167.                 end
  2168.                 IsProcessingDispatcherQueue = false
  2169.             end
  2170.         end
  2171.     else
  2172.         if (eventType == "mouse_click") then
  2173.             if (toolkit.OnWakeDisplay(time)) then return end
  2174.             toolkit.OnMouseClick(time, p1, p2 - 1, p3 - 1)
  2175.         elseif (eventType == "monitor_touch") then
  2176.             if (toolkit.OnWakeDisplay(time)) then return end
  2177.             toolkit.OnMouseClick(time, 1, p2 - 1, p3 - 1)
  2178.         elseif (eventType == "mouse_drag") then
  2179.             if (toolkit.OnWakeDisplay(time)) then return end
  2180.             toolkit.OnMouseDrag(time, p1, p2 - 1, p3 - 1)
  2181.         elseif (eventType == "key") then
  2182.             -- app.OnKeyPress(time, p1)
  2183.         elseif (eventType == "char") then
  2184.             -- app.OnCharPress(time, p1)
  2185.         else
  2186.             return
  2187.         end
  2188.     end
  2189.     PopFunction()
  2190. end
  2191.  
  2192. function DualColourTextBlock.new(leftText, rightText, leftColour, rightColour, bg_colour)
  2193.     local obj = NewTypeInstance(DualColourTextBlock, "DualColourTextBlock")
  2194.     obj.LeftText = leftText or ""
  2195.     obj.RightText = rightText or ""
  2196.     obj.LeftColour = leftColour or colours.white
  2197.     obj.RightColour = rightColour or colours.white
  2198.     DependencyObject.SetPropValue(obj, UIComponent.BackgroundProperty, bg_colour or colours.black)
  2199.     return obj
  2200. end
  2201.  
  2202. function DualColourTextBlock:MeasureOverride(max_w, max_h)
  2203.     local w, h = UIComponent.MeasureOverride(self, max_w, max_h)
  2204.     local textLen = 0
  2205.     if (self.LeftText ~= nil) then textLen = textLen + #self.LeftText end
  2206.     if (self.RightText ~= nil) then textLen = textLen + #self.RightText end
  2207.     if (w < textLen) then
  2208.         w = textLen
  2209.     end
  2210.     if (h < 1) then h = 1 end
  2211.     return w, h
  2212. end
  2213.  
  2214. function DualColourTextBlock:OnRender()
  2215.     UIComponent.OnRender(self)
  2216.     ui.drawText(1, 1, self.LeftText, self.LeftColour, nil)
  2217.     ui.drawText(1 + #self.LeftText, 1, self.RightText, self.RightColour, nil)
  2218. end
  2219.  
  2220. function DualColourTextBlock:SetRightText(theText, theColour)
  2221.     if (theText == nil) then
  2222.         theText = ""
  2223.     elseif (type(theText) ~= "string") then
  2224.         theText = tostring(theText)
  2225.     end
  2226.  
  2227.     self.RightText = theText
  2228.     self.RightColour = theColour
  2229.     self:InvalidateMeasure()
  2230.     self:InvalidateVisual()
  2231. end
  2232.  
  2233. local TabItem_Home
  2234. local TabItem_Rods
  2235. local TabItem_Auto
  2236.  
  2237. local ReactorInfo = {
  2238.     Handle = nil,
  2239.     IsOnline = false,
  2240.     PowerLevel = 0,
  2241.     MaxFuelAmount = 0,
  2242.     CurFuelAmount = 0,
  2243.     FuelTemp = 0,
  2244.     CaseTemp = 0,
  2245.     EnergyProducedLastTick = 0,
  2246.     EnergyStored = 0,
  2247.     AvgRodInsertion = 0,
  2248.     TotalControlRodCount = 0,
  2249.     Automation = {
  2250.         Minimum = 40,
  2251.         Maximum = 60,
  2252.         IsEnabled = false
  2253.     },
  2254.     UI = {
  2255.         HomePage = {},
  2256.         RodsPage = {},
  2257.         AutoPage = {}
  2258.     }
  2259. }
  2260.  
  2261. function toolkit.LoadConfig()
  2262.     local hFile = fs.open("config", "r")
  2263.     if (hFile == nil) then
  2264.         return
  2265.     end
  2266.  
  2267.     while true do
  2268.         local nextLine = hFile.readLine()
  2269.         if (nextLine == nil) then
  2270.             break
  2271.         end
  2272.  
  2273.         for key, value in string.gmatch(nextLine, "([^=]+)=([^=]+)") do
  2274.             ConfigTable[key] = value
  2275.         end
  2276.     end
  2277.  
  2278.     hFile.close()
  2279.  
  2280.     ReactorInfo.Automation.Minimum = tonumber(ConfigTable["Automation_Minimum"]) or 30
  2281.     ReactorInfo.Automation.Maximum = tonumber(ConfigTable["Automation_Maximum"]) or 70
  2282.     local isAutoEnabled = (ConfigTable["Automation_IsEnabled"] == "true") or false
  2283.     CanDisplayAutoSleep = (ConfigTable["CanDisplayAutoSleep"] == "true") or false
  2284.  
  2285.     SetAutomationState(isAutoEnabled)
  2286. end
  2287.  
  2288. function toolkit.SaveConfig()
  2289.     local hFile = fs.open("config", "w")
  2290.     if (hFile == nil) then
  2291.         return
  2292.     end
  2293.  
  2294.     ConfigTable["Automation_Minimum"] = tostring(ReactorInfo.Automation.Minimum)
  2295.     ConfigTable["Automation_Maximum"] = tostring(ReactorInfo.Automation.Maximum)
  2296.     ConfigTable["Automation_IsEnabled"] = tostring(ReactorInfo.Automation.IsEnabled)
  2297.     ConfigTable["CanDisplayAutoSleep"] = tostring(CanDisplayAutoSleep)
  2298.  
  2299.     for key, value in pairs(ConfigTable) do
  2300.         hFile.writeLine(key .. "=" .. value)
  2301.     end
  2302.  
  2303.     hFile.close()
  2304. end
  2305.  
  2306. function ReactorInfo.SetRodInsertion(percent)
  2307.     if (ReactorInfo.Handle ~= nil) then
  2308.         ReactorInfo.Handle.setAllControlRodLevels(percent)
  2309.     end
  2310.     ReactorInfo.AvgRodInsertion = percent
  2311. end
  2312.  
  2313. function GetColourForReactorTemp(fuel_temp)
  2314.     if (fuel_temp < 500) then
  2315.         return colors.lime
  2316.     elseif (fuel_temp < 1000) then
  2317.         return colors.yellow
  2318.     elseif (fuel_temp < 1500) then
  2319.         return colors.orange
  2320.     else
  2321.         return colors.red
  2322.     end
  2323. end
  2324.  
  2325. function SetAutomationState(isAutomationEnabled)
  2326.     if (ReactorInfo.Automation.IsEnabled == isAutomationEnabled) then
  2327.         return false
  2328.     end
  2329.  
  2330.     ReactorInfo.Automation.IsEnabled = isAutomationEnabled
  2331.     toolkit.SaveConfig()
  2332.  
  2333.     local info = ReactorInfo.Automation
  2334.     local page = ReactorInfo.UI.HomePage
  2335.     if (info.IsEnabled) then
  2336.         page.ControlBtn_On:SetPropValue(Button.PressedBackgroundProperty, colours.cyan)
  2337.         page.ControlBtn_Off:SetPropValue(Button.PressedBackgroundProperty, colours.cyan)
  2338.         page.ControlBtn_Auto.IsPressed = true
  2339.     else
  2340.         page.ControlBtn_On:SetPropValue(Button.PressedBackgroundProperty, colours.green)
  2341.         page.ControlBtn_Off:SetPropValue(Button.PressedBackgroundProperty, colours.green)
  2342.         page.ControlBtn_Auto.IsPressed = false
  2343.     end
  2344.  
  2345.     DoReactorTick(false)
  2346.     return true
  2347. end
  2348.  
  2349. local app_ticks = 1
  2350.  
  2351. function DoReactorTick(canScheduleNextTick)
  2352.     PushFunction("DoReactorTick::" .. tostring(canScheduleNextTick))
  2353.     local reactor = ReactorInfo.Handle
  2354.     if (reactor ~= nil) then
  2355.         ReactorInfo.IsOnline = reactor.getActive()
  2356.         ReactorInfo.PowerLevel = reactor.getEnergyStored()
  2357.         ReactorInfo.MaxFuelAmount = reactor.getFuelAmountMax()
  2358.         ReactorInfo.CurFuelAmount = reactor.getFuelAmount()
  2359.         ReactorInfo.FuelTemp = reactor.getFuelTemperature()
  2360.         ReactorInfo.CaseTemp = reactor.getCasingTemperature()
  2361.         ReactorInfo.EnergyProducedLastTick = reactor.getEnergyProducedLastTick()
  2362.         ReactorInfo.EnergyStored = reactor.getEnergyStored()
  2363.         ReactorInfo.AvgRodInsertion = reactor.getControlRodLevel(0)
  2364.         ReactorInfo.TotalControlRodCount = reactor.getNumberOfControlRods()
  2365.         local auto = ReactorInfo.Automation
  2366.         if (auto.IsEnabled) then
  2367.             local power_percent = math.floor((ReactorInfo.EnergyStored / 10000000) * 100)
  2368.             if (auto.Minimum == auto.Maximum) then
  2369.                 if (power_percent < auto.Minimum) then
  2370.                     reactor.setActive(true)
  2371.                 elseif (power_percent > auto.Minimum) then
  2372.                     reactor.setActive(false)
  2373.                 end
  2374.             else
  2375.                 if (power_percent <= auto.Minimum) then
  2376.                     reactor.setActive(true)
  2377.                 elseif (power_percent >= auto.Maximum) then
  2378.                     reactor.setActive(false)
  2379.                 end
  2380.             end
  2381.         end
  2382.     end
  2383.  
  2384.     if (TabItem_Home.IsActive) then
  2385.         local page = ReactorInfo.UI.HomePage
  2386.         if (ReactorInfo.IsOnline) then
  2387.             page.PowerLabel:SetRightText("ONLINE", colours.green)
  2388.         else
  2389.             page.PowerLabel:SetRightText("OFFLINE", colours.red)
  2390.         end
  2391.  
  2392.         local fuelLevelPercent = math.floor((ReactorInfo.CurFuelAmount/ReactorInfo.MaxFuelAmount)*100)
  2393.         page.FuelLevelLabel:SetRightText(tostring(fuelLevelPercent) .. "%", colours.white)
  2394.         page.FuelLevelProgBar:SetValue(fuelLevelPercent, colours.lime)
  2395.  
  2396.         local fuel_temp = ReactorInfo.FuelTemp
  2397.         page.FuelTempLabel:SetRightText(tostring(math.floor(fuel_temp)) .. "/4000", colours.white)
  2398.         page.FuelTempProgBar:SetValue(fuel_temp, GetColourForReactorTemp(fuel_temp))
  2399.  
  2400.         local case_temp = ReactorInfo.CaseTemp
  2401.         page.CasingTempLabel:SetRightText(tostring(math.floor(case_temp)) .. "/4000", colours.white)
  2402.         page.CasingTempProgBar:SetValue(case_temp, GetColourForReactorTemp(case_temp))
  2403.  
  2404.         page.EnergyLabel:SetRightText(tostring(math.floor(ReactorInfo.EnergyProducedLastTick)) .. " RF/t", colours.white)
  2405.         page.StoredLabel:SetRightText(tostring(math.floor((ReactorInfo.EnergyStored/10000000)*100)) .. "% (" .. tostring(math.floor(ReactorInfo.EnergyStored)) .. " RF)", colours.white)
  2406.         page.StoredProgBar:SetValue(ReactorInfo.EnergyStored)
  2407.         page.ControlRodsLabel:SetRightText(ReactorInfo.AvgRodInsertion .. "%", colours.white)    
  2408.  
  2409.         page.ControlBtn_On:SetPressed(ReactorInfo.IsOnline)
  2410.         page.ControlBtn_Off:SetPressed(not ReactorInfo.IsOnline)
  2411.         page.ControlBtn_Auto:SetPressed(ReactorInfo.Automation.IsEnabled)
  2412.     elseif (TabItem_Rods.IsActive) then
  2413.         local page = ReactorInfo.UI.RodsPage
  2414.         page.InsertionTextBlock:SetRightText(tostring(ReactorInfo.AvgRodInsertion) .. "%", colours.lightBlue)
  2415.         page.InsertionProgBar:SetValue(ReactorInfo.AvgRodInsertion)
  2416.  
  2417.         page.TotalControlRodLabel:SetRightText(tostring(ReactorInfo.TotalControlRodCount))
  2418.     elseif (TabItem_Auto.IsActive) then
  2419.         local page = ReactorInfo.UI.AutoPage
  2420.         UpdateReactorAutomationLabels()
  2421.     end
  2422.  
  2423.     if (canScheduleNextTick == nil or canScheduleNextTick == true) then
  2424.         InvokeAsyncWithDelay(DoReactorTick, 0.4, true)
  2425.     end
  2426.  
  2427.     PopFunction()
  2428. end
  2429.  
  2430. ---Creates a basic page
  2431. ---@generic T : BasePageComponent
  2432. ---@param theComponentType T
  2433. ---@param footerMessage string
  2434. ---@return T
  2435. function CreateBasicPage(theComponentType, footerMessage)
  2436.     local page = NewTypeInstance(theComponentType, "BasicPage")
  2437.     DependencyObject.SetPropValue(page, UIComponent.BackgroundProperty, colours.black)
  2438.     page:SetPropValue(UIComponent.HorizontalAlignmentProperty, "stretch")
  2439.     page:SetPropValue(UIComponent.VerticalAlignmentProperty, "stretch")
  2440.  
  2441.     local label = TextBlock.new(footerMessage, colours.white, colours.blue)
  2442.     label:SetPropValue(UIComponent.HorizontalAlignmentProperty, "center")
  2443.  
  2444.     local bottom_strip = UIComponent.new()
  2445.     DependencyObject.SetPropValue(bottom_strip, UIComponent.BackgroundProperty, colours.blue)
  2446.     bottom_strip:SetPropValue(UIComponent.HorizontalAlignmentProperty, "stretch")
  2447.     bottom_strip:SetPropValue(UIComponent.VerticalAlignmentProperty, "bottom")
  2448.     bottom_strip:InsertChild(label)
  2449.     page:InsertChild(bottom_strip)
  2450.     return page
  2451. end
  2452.  
  2453. function CreateHomePage()
  2454.     local page_Home = CreateBasicPage(HomePageComponent, "Info about your reactor")
  2455.  
  2456.     local vert_stack = page_Home:InsertChild(VerticalStackPanel.new():SetMargin(1, 1, 1, 2))
  2457.     vert_stack:SetPropValue(UIComponent.HorizontalAlignmentProperty, "stretch")
  2458.  
  2459.     -- define page layout
  2460.     page_Home.PowerLabel = vert_stack:InsertChild(DualColourTextBlock.new("Power:        ", "OFFLINE", colours.yellow, colours.red))
  2461.     vert_stack:InsertChild(UIComponent.new():SetSize(0, 1))
  2462.  
  2463.     page_Home.FuelLevelLabel = vert_stack:InsertChild(DualColourTextBlock.new("Fuel Level:   ", "0", colours.yellow, colours.white))
  2464.     page_Home.FuelLevelProgBar = vert_stack:InsertChild(ProgressBar.new(0, 100, 0):SetAlignment("stretch", "top"))
  2465.  
  2466.     vert_stack:InsertChild(UIComponent.new():SetSize(0, 1))
  2467.  
  2468.     page_Home.FuelTempLabel = vert_stack:InsertChild(DualColourTextBlock.new("Fuel Temp:    ", "0", colours.yellow, colours.white))
  2469.     page_Home.FuelTempProgBar = vert_stack:InsertChild(ProgressBar.new(0, 4000, 0):SetAlignment("stretch", "top"))
  2470.  
  2471.     vert_stack:InsertChild(UIComponent.new():SetSize(0, 1))
  2472.  
  2473.     page_Home.CasingTempLabel = vert_stack:InsertChild(DualColourTextBlock.new("Casing Temp:  ", "0", colours.yellow, colours.white))
  2474.     page_Home.CasingTempProgBar = vert_stack:InsertChild(ProgressBar.new(0, 4000, 0):SetAlignment("stretch", "top"))
  2475.  
  2476.     vert_stack:InsertChild(UIComponent.new():SetSize(0, 1))
  2477.  
  2478.     local dock_panel = vert_stack:InsertChild(DockPanel.new():SetAlignment("stretch"))
  2479.     local btn_controls = dock_panel:InsertChild(HorizontalStackPanel.new())
  2480.     btn_controls.DockValue = "right"
  2481.  
  2482.     local otherInfoStack = dock_panel:InsertChild(VerticalStackPanel.new():SetMargin(0, 0, 1, 0))
  2483.     otherInfoStack:SetPropValue(UIComponent.HorizontalAlignmentProperty, "stretch")
  2484.  
  2485.     page_Home.EnergyLabel = otherInfoStack:InsertChild(DualColourTextBlock.new("Energy: ", "0 RF/t", colours.yellow, colours.white))
  2486.     page_Home.StoredLabel = otherInfoStack:InsertChild(DualColourTextBlock.new("Stored: ", "0 RF", colours.yellow, colours.white))
  2487.     page_Home.StoredProgBar = otherInfoStack:InsertChild(ProgressBar.new(0, 10000000, 0, colours.grey, colours.red):SetAlignment("stretch"))
  2488.     page_Home.ControlRodsLabel = otherInfoStack:InsertChild(DualColourTextBlock.new("Control Rods: ", "0%", colours.yellow, colours.white))
  2489.  
  2490.     local btn_auto = Button.new("Button:Automatic")
  2491.     page_Home.ControlBtn_Auto = btn_auto
  2492.     btn_auto.IsToggleButton = true
  2493.     btn_auto:SetPropValue(Button.TextProperty, "Automatic")
  2494.     btn_auto:SetSize(11, 3)
  2495.     btn_auto:AddEventHandler(Event_Button_OnClick, function (self, time, e)
  2496.         if (not SetAutomationState(self.IsPressed)) then
  2497.             return
  2498.         end
  2499.         self:InvalidateVisual()
  2500.     end)
  2501.  
  2502.     local btn_start = Button.new("Button:ON")
  2503.     page_Home.ControlBtn_On = btn_start
  2504.     btn_start.DoNotProcessButtonClickLogic = true
  2505.     btn_start:SetPropValue(Button.TextProperty, "ON")
  2506.     btn_start:SetSize(6, 3)
  2507.     btn_start:AddEventHandler(Event_Button_OnClick, function (self, time, e)
  2508.         if (ReactorInfo.Handle ~= nil) then ReactorInfo.Handle.setActive(true) end
  2509.         SetAutomationState(false)
  2510.         self:InvalidateVisual()
  2511.     end)
  2512.  
  2513.     local btn_stop = Button.new("Button:OFF")
  2514.     page_Home.ControlBtn_Off = btn_stop
  2515.     btn_stop.DoNotProcessButtonClickLogic = true
  2516.     btn_stop:SetPropValue(Button.TextProperty, "OFF")
  2517.     btn_stop:SetSize(7, 3)
  2518.     btn_stop:AddEventHandler(Event_Button_OnClick, function (self, time, e)
  2519.         if (ReactorInfo.Handle ~= nil) then ReactorInfo.Handle.setActive(false) end
  2520.         SetAutomationState(false)
  2521.         self:InvalidateVisual()
  2522.     end)
  2523.  
  2524.     btn_controls:InsertChild(btn_auto)
  2525.     btn_controls:InsertChild(btn_start)
  2526.     btn_controls:InsertChild(btn_stop)
  2527.     return page_Home
  2528. end
  2529.  
  2530. function AddToRodInsertion(increment)
  2531.     ReactorInfo.AvgRodInsertion = ReactorInfo.AvgRodInsertion + increment
  2532.     ReactorInfo.SetRodInsertion(maths.clamp(ReactorInfo.AvgRodInsertion, 0, 100))
  2533.  
  2534.     local page = ReactorInfo.UI.RodsPage
  2535.     page.InsertionTextBlock:SetRightText(tostring(ReactorInfo.AvgRodInsertion) .. "%", colours.lightBlue)
  2536.     page.InsertionProgBar:SetValue(ReactorInfo.AvgRodInsertion)
  2537. end
  2538.  
  2539. function CreateRodsPage()
  2540.     ---@class RodsPageComponent : BasePageComponent
  2541.     local page = CreateBasicPage(RodsPageComponent, "Modify control rods here")
  2542.     local vert_stack = page:InsertChild(VerticalStackPanel.new():SetMargin(1, 1, 1, 2):SetAlignment("stretch"))
  2543.  
  2544.     DependencyObject.SetPropValue(vert_stack, UIComponent.BackgroundProperty, colours.black)
  2545.     page.InsertionTextBlock = vert_stack:InsertChild(DualColourTextBlock.new("Inserted: ", "0%", colours.white, colours.lightBlue, colours.black):SetAlignment("center"))
  2546.     vert_stack:InsertChild(UIComponent.SetSize(UIComponent.new(), 0, 1))
  2547.  
  2548.     page.InsertionProgBar = vert_stack:InsertChild(ProgressBar.new(0, 100, 0, colours.grey, colours.lightBlue):SetAlignment("stretch"))
  2549.     vert_stack:InsertChild(UIComponent.SetSize(UIComponent.new(), 0, 1))
  2550.  
  2551.     local button_grid = vert_stack:InsertChild(UIComponent.new():SetAlignment("stretch"))
  2552.     local left_side = button_grid:InsertChild(HorizontalStackPanel.new():SetAlignment("left"))
  2553.     local decr_b = left_side:InsertChild(Button.new("DecrBigButton", "<<"):SetSize(4, 1))
  2554.     local decr_s = left_side:InsertChild(Button.new("DecrSmallButton", "<"):SetSize(3, 1):SetMargin(1))
  2555.  
  2556.     local right_side = button_grid:InsertChild(HorizontalStackPanel.new():SetAlignment("right"))
  2557.     local incr_s = right_side:InsertChild(Button.new("IncrSmallButton", ">"):SetSize(3, 1))
  2558.     local incr_b = right_side:InsertChild(Button.new("IncrBigButton", ">>"):SetSize(4, 1):SetMargin(1))
  2559.  
  2560.     decr_b:AddEventHandler(Event_Button_OnClick, function (self, time, e) AddToRodInsertion(-10) end)
  2561.     decr_s:AddEventHandler(Event_Button_OnClick, function (self, time, e) AddToRodInsertion(-1) end)
  2562.     incr_b:AddEventHandler(Event_Button_OnClick, function (self, time, e) AddToRodInsertion(10) end)
  2563.     incr_s:AddEventHandler(Event_Button_OnClick, function (self, time, e) AddToRodInsertion(1) end)
  2564.  
  2565.     page.TotalControlRodLabel = page:InsertChild(DualColourTextBlock.new("Total control rods: ", "0", colours.white, colours.green))
  2566.     page.TotalControlRodLabel:SetMargin(1, 0, 1, 2):SetAlignment("left", "bottom")
  2567.     vert_stack:InsertChild(UIComponent.SetSize(UIComponent.new(), 0, 1))
  2568.     return page
  2569. end
  2570.  
  2571. function UpdateReactorAutomationLabels()
  2572.     local info = ReactorInfo.Automation
  2573.     local page = ReactorInfo.UI.AutoPage
  2574.     page.StorageOnTextBlock:SetPropValue(TextBlock.TextProperty, tostring(info.Minimum) .. "% RF")
  2575.     page.StorageOffTextBlock:SetPropValue(TextBlock.TextProperty, tostring(info.Maximum) .. "% RF")
  2576. end
  2577.  
  2578. function AddToReactorAutomation(increment, isMinimum)
  2579.     local info = ReactorInfo.Automation
  2580.     if (isMinimum) then
  2581.         info.Minimum = maths.clamp(info.Minimum + increment, 0, info.Maximum)
  2582.     else
  2583.         info.Maximum = maths.clamp(info.Maximum + increment, info.Minimum, 100)
  2584.     end
  2585.     UpdateReactorAutomationLabels()
  2586.     toolkit.SaveConfig()
  2587. end
  2588.  
  2589. function CreateAutoPage()
  2590.     local page = CreateBasicPage(AutoPageComponent, "Modify automation parameters")
  2591.     local vert_stack = page:InsertChild(VerticalStackPanel.new():SetMargin(1, 1, 1, 2))
  2592.     vert_stack:SetPropValue(UIComponent.HorizontalAlignmentProperty, "stretch")
  2593.     DependencyObject.SetPropValue(vert_stack, UIComponent.BackgroundProperty, colours.black)
  2594.  
  2595.     vert_stack:InsertChild(TextBlock.new("Configure automatic ON/OFF data"))
  2596.     vert_stack:InsertChild(TextBlock.new("You can enable this by clicking 'Automatic' button in the home screen"))
  2597.  
  2598.     vert_stack:InsertChild(UIComponent.SetSize(UIComponent.new(), 0, 1))
  2599.  
  2600.     vert_stack:InsertChild(TextBlock.new("Turn ON when storage drops below:", colours.white, colours.black))
  2601.     local stack_ON = vert_stack:InsertChild(HorizontalStackPanel.new())
  2602.     local btn_min_sub_b = stack_ON:InsertChild(Button.new("ON_Decr_Button_b", "--"):SetSize(4):SetMargin(0, 0, 0, 0))
  2603.     local btn_min_sub_s = stack_ON:InsertChild(Button.new("ON_Decr_Button_s", "-"):SetSize(3):SetMargin(1, 0, 0, 0))
  2604.     page.StorageOnTextBlock = stack_ON:InsertChild(TextBlock.new("0% RF", colours.white, colours.black):SetSize(7):SetMargin(1, 0, 1))
  2605.     local btn_min_add_s = stack_ON:InsertChild(Button.new("ON_Incr_Button_s", "+"):SetSize(3):SetMargin(0, 0, 0, 0))
  2606.     local btn_min_add_b = stack_ON:InsertChild(Button.new("ON_Incr_Button_b", "++"):SetSize(4):SetMargin(1, 0, 0, 0))
  2607.  
  2608.     vert_stack:InsertChild(UIComponent.SetSize(UIComponent.new(), 0, 1))
  2609.  
  2610.     vert_stack:InsertChild(TextBlock.new("Turn OFF when storage exceeds:", colours.white, colours.black))
  2611.     local stack_OFF = vert_stack:InsertChild(HorizontalStackPanel.new())
  2612.     local btn_max_sub_b = stack_OFF:InsertChild(Button.new("OFF_Decr_Button_b", "--"):SetSize(4):SetMargin(0, 0, 0, 0))
  2613.     local btn_max_sub_s = stack_OFF:InsertChild(Button.new("OFF_Decr_Button_s", "-"):SetSize(3):SetMargin(1, 0, 0, 0))
  2614.     page.StorageOffTextBlock = stack_OFF:InsertChild(TextBlock.new("100% RF", colours.white, colours.black):SetSize(7):SetMargin(1, 0, 1))
  2615.     local btn_max_add_s = stack_OFF:InsertChild(Button.new("OFF_Incr_Button_s", "+"):SetSize(3):SetMargin(0, 0, 0, 0))
  2616.     local btn_max_add_b = stack_OFF:InsertChild(Button.new("OFF_Incr_Button_b", "++"):SetSize(4):SetMargin(1, 0, 0, 0))
  2617.  
  2618.     btn_min_sub_s:AddEventHandler(Event_Button_OnClick, function (self, time, e) AddToReactorAutomation(-1, true) end)
  2619.     btn_min_sub_b:AddEventHandler(Event_Button_OnClick, function (self, time, e) AddToReactorAutomation(-10, true) end)
  2620.     btn_min_add_s:AddEventHandler(Event_Button_OnClick, function (self, time, e) AddToReactorAutomation(1, true) end)
  2621.     btn_min_add_b:AddEventHandler(Event_Button_OnClick, function (self, time, e) AddToReactorAutomation(10, true) end)
  2622.     btn_max_sub_s:AddEventHandler(Event_Button_OnClick, function (self, time, e) AddToReactorAutomation(-1, false) end)
  2623.     btn_max_sub_b:AddEventHandler(Event_Button_OnClick, function (self, time, e) AddToReactorAutomation(-10, false) end)
  2624.     btn_max_add_s:AddEventHandler(Event_Button_OnClick, function (self, time, e) AddToReactorAutomation(1, false) end)
  2625.     btn_max_add_b:AddEventHandler(Event_Button_OnClick, function (self, time, e) AddToReactorAutomation(10, false) end)
  2626.  
  2627.     return page
  2628. end
  2629.  
  2630. function CreateInfoPage()
  2631.     local page = CreateBasicPage(InfoPageComponent, "About the program")
  2632.     local vert_stack = page:InsertChild(VerticalStackPanel.new():SetMargin(1, 1, 1, 2))
  2633.     DependencyObject.SetPropValue(vert_stack, UIComponent.BackgroundProperty, colours.black)
  2634.     vert_stack:InsertChild(TextBlock.new("Made by TheRareCarrot :D", colours.white))
  2635.     vert_stack:InsertChild(TextBlock.new("BigReactor controller program v1.0"))
  2636.     vert_stack:InsertChild(TextBlock.new("using Lua Presentation Framework (LPF) v1.1"))
  2637.     vert_stack:InsertChild(UIComponent.new():SetSize(0, 1))
  2638.     vert_stack:InsertChild(TextBlock.new("The 'Z' button (bottom right) toggles auto-sleep after 5 minutes"))
  2639.     vert_stack:InsertChild(TextBlock.new("The 'Automatic' button toggles reactor activation automation (configurable in the Auto tab)"))
  2640.     vert_stack:InsertChild(UIComponent.new():SetSize(0, 1))
  2641.     vert_stack:InsertChild(TextBlock.new("This works best in the computer itself, or a 5x3 monitor, though the monitor can be bigger if you wish", colours.yellow))
  2642.     return page
  2643. end
  2644.  
  2645. function CreateMainContent(parent)
  2646.     PushFunction("CreateMainContent", parent.TypeName)
  2647.  
  2648.     local theTabControl = NewTypeInstance(TabControl, "TabControl")
  2649.     theTabControl:SetPropValue(UIComponent.HorizontalAlignmentProperty, "stretch")
  2650.     theTabControl:SetPropValue(UIComponent.VerticalAlignmentProperty, "stretch")
  2651.  
  2652.     local tabPanel = theTabControl:InsertChild(UniformPanel.new())
  2653.     tabPanel:SetPropValue(UIComponent.HorizontalAlignmentProperty, "stretch")
  2654.     tabPanel:SetPropValue(UIComponent.VerticalAlignmentProperty, "stretch")
  2655.  
  2656.     local page_Home = CreateHomePage()
  2657.     local page_Rods = CreateRodsPage()
  2658.     local page_Auto = CreateAutoPage()
  2659.     local page_Info = CreateInfoPage()
  2660.  
  2661.     ReactorInfo.UI.HomePage = page_Home
  2662.     ReactorInfo.UI.RodsPage = page_Rods
  2663.     ReactorInfo.UI.AutoPage = page_Auto
  2664.  
  2665.     TabItem_Home = tabPanel:InsertChild(NewTabItem("Home", page_Home))
  2666.     TabItem_Rods = tabPanel:InsertChild(NewTabItem("Rods", page_Rods))
  2667.     TabItem_Auto = tabPanel:InsertChild(NewTabItem("Auto", page_Auto))
  2668.     TabItem_Info = tabPanel:InsertChild(NewTabItem("Info", page_Info))
  2669.  
  2670.     local contentPresenter = ContentPresenter.new()
  2671.     contentPresenter:SetPropValue(UIComponent.HorizontalAlignmentProperty, "stretch")
  2672.     contentPresenter:SetPropValue(UIComponent.VerticalAlignmentProperty, "stretch")
  2673.     contentPresenter:SetPropValue(UIComponent.MarginTProperty, 1)
  2674.     theTabControl:InsertChild(contentPresenter)
  2675.     theTabControl.MyContentPresenter = contentPresenter
  2676.  
  2677.     theTabControl:SetActiveTabItem(TabItem_Home)
  2678.     parent:InsertChild(theTabControl)
  2679.  
  2680.     local tabChangedHandler = function (self, time, e)
  2681.         PushFunctionAndInvoke("DoReactorTick::tabChangedHandler", DoReactorTick, false)
  2682.     end
  2683.  
  2684.     TabItem_Home:AddEventHandler(Event_TabItem_OnNowActive, tabChangedHandler)
  2685.     TabItem_Rods:AddEventHandler(Event_TabItem_OnNowActive, tabChangedHandler)
  2686.     TabItem_Auto:AddEventHandler(Event_TabItem_OnNowActive, tabChangedHandler)
  2687.  
  2688.     PopFunction()
  2689. end
  2690.  
  2691. function FindPeripheralByType(theType)
  2692.     local names = peripheral.getNames()
  2693.     for i, name in ipairs(names) do
  2694.        if (peripheral.getType(name) == theType) then
  2695.           return peripheral.wrap(name)
  2696.        end
  2697.     end
  2698.     return nil
  2699. end
  2700.  
  2701. function FindReactor()
  2702.     return FindPeripheralByType("BigReactors-Reactor")
  2703. end
  2704.  
  2705. local AutoSleepButton
  2706.  
  2707. function LoadReactorAndDoShit()
  2708.     ReactorInfo.Handle = FindReactor()
  2709.     toolkit.LoadConfig()
  2710.     AutoSleepButton.IsPressed = CanDisplayAutoSleep
  2711.     AutoSleepButton:InvalidateVisual()
  2712.     PushFunctionAndInvoke("DoReactorTick::LoadReactorAndDoShit", DoReactorTick, true)
  2713. end
  2714.  
  2715. function OnAppStartup()
  2716.     if (AppRootComponent == nil) then
  2717.         error("No root component")
  2718.     end
  2719.  
  2720.     CreateMainContent(AppRootComponent)
  2721.  
  2722.     AutoSleepButton = AppRootComponent:InsertChild(Button.new("AutoSleepButton", "Z"):SetAlignment("right", "bottom"))
  2723.     AutoSleepButton.IsToggleButton = true
  2724.     AutoSleepButton:AddEventHandler(Event_Button_OnClick, function (self, time, e)
  2725.         CanDisplayAutoSleep = self.IsPressed
  2726.         toolkit.SaveConfig()
  2727.     end)
  2728.  
  2729.     AppRootComponent:InvalidateLayoutAndVisual()
  2730.     InvokeAsync(LoadReactorAndDoShit, PriorityBackground)
  2731. end
  2732.  
  2733. local function SetupRootComponent()
  2734.     AppRootComponent:SetAlignment("stretch", "stretch")
  2735.     AppRootComponent.TypeName = "RootPanel"
  2736.  
  2737.     local w,h = toolkit.GetCompositionRenderSize()
  2738.     AppRootComponent:SetSize(w, h)
  2739.  
  2740.     AppRootComponent:UpdateVisibility()
  2741.     AppRootComponent:Arrange(0, 0, w, h)
  2742.     AppRootComponent:InvalidateLayoutAndVisual()
  2743.     toolkit.DoLayoutUpdate()
  2744. end
  2745.  
  2746. local function AppMain()
  2747.     PushFunction("AppMain")
  2748.     LastDisplayWakeTime = os.clock()
  2749.  
  2750.     toolkit.SetComponsitionTarget(FindPeripheralByType("monitor") or term) -- term size = 51 x 19
  2751.     ui.clear()
  2752.  
  2753.     AppRootComponent = UIComponent.new()
  2754.     SetupRootComponent();
  2755.  
  2756.     InvokeAsync(OnAppStartup, PriorityBackground)
  2757.  
  2758.     SetupTimer(AppTimerInterval)
  2759.     while IsAppRunning do
  2760.         local event, p1, p2, p3, p4, p5 = os.pullEventRaw()
  2761.         if (event == "terminate") then
  2762.             break
  2763.         end
  2764.  
  2765.         OnApplicationEvent(os.clock(), event, p1, p2, p3, p4, p5)
  2766.     end
  2767.     PopFunction()
  2768. end
  2769.  
  2770. --endregion
  2771.  
  2772. local function Main()
  2773.     DebugFileHandle = fs.open("debug.log", "w")
  2774.     local is_main_success, errMsg = pcall(AppMain)
  2775.     toolkit.SetComponsitionTarget(term)
  2776.     ui.setBackgroundColour(colours.black)
  2777.     ui.setTextColour(colours.white)
  2778.     if (not is_main_success) then
  2779.         PrintStackTrace()
  2780.         print("The app has crashed! " .. errMsg)
  2781.         PrintDebug(tostring(errMsg))
  2782.     end
  2783.  
  2784.     DebugFileHandle.close()
  2785.     if (is_main_success) then
  2786.         print("Application has exited without error")
  2787.     end
  2788. end
  2789.  
  2790. Main()
Add Comment
Please, Sign In to add comment