bobmarley12345

EChest Colour

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