Advertisement
massacring

Cherry

Jul 14th, 2025 (edited)
32
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 18.66 KB | None | 0 0
  1. --[[
  2. ───────────────────────────────────────────────────────────────────────────────────────────────────────
  3. ─██████████████─██████──██████─██████████████─████████████████───████████████████───████████──████████─
  4. ─██░░░░░░░░░░██─██░░██──██░░██─██░░░░░░░░░░██─██░░░░░░░░░░░░██───██░░░░░░░░░░░░██───██░░░░██──██░░░░██─
  5. ─██░░██████████─██░░██──██░░██─██░░██████████─██░░████████░░██───██░░████████░░██───████░░██──██░░████─
  6. ─██░░██─────────██░░██──██░░██─██░░██─────────██░░██────██░░██───██░░██────██░░██─────██░░░░██░░░░██───
  7. ─██░░██─────────██░░██████░░██─██░░██████████─██░░████████░░██───██░░████████░░██─────████░░░░░░████───
  8. ─██░░██─────────██░░░░░░░░░░██─██░░░░░░░░░░██─██░░░░░░░░░░░░██───██░░░░░░░░░░░░██───────████░░████─────
  9. ─██░░██─────────██░░██████░░██─██░░██████████─██░░██████░░████───██░░██████░░████─────────██░░██───────
  10. ─██░░██─────────██░░██──██░░██─██░░██─────────██░░██──██░░██─────██░░██──██░░██───────────██░░██───────
  11. ─██░░██████████─██░░██──██░░██─██░░██████████─██░░██──██░░██████─██░░██──██░░██████───────██░░██───────
  12. ─██░░░░░░░░░░██─██░░██──██░░██─██░░░░░░░░░░██─██░░██──██░░░░░░██─██░░██──██░░░░░░██───────██░░██───────
  13. ─██████████████─██████──██████─██████████████─██████──██████████─██████──██████████───────██████───────
  14. ───────────────────────────────────────────────────────────────────────────────────────────────────────
  15.  
  16.     Cherry is a UI Layout Library for CC:Tweaked* using Lua**.
  17.  
  18.     *  https://tweaked.cc
  19.     ** https://www.lua.org/about.html
  20.  
  21.     Usage example: https://pastebin.com/ghC6YS70
  22. --]]
  23.  
  24. ---Main Object Class
  25. local Cherry = {}
  26. Cherry.__index = Cherry
  27.  
  28. ---Creates a new Object
  29. function Cherry:new()
  30. end
  31.  
  32. ---Returns an object that extends this one.
  33. ---@return table
  34. function Cherry:extend()
  35.     local cherry = {}
  36.     for key, value in pairs(self) do
  37.         if key:find("__") == true then
  38.             cherry[key] = value
  39.         end
  40.     end
  41.     cherry.__index = cherry
  42.     cherry.super = self
  43.     setmetatable(cherry, self)
  44.     return cherry
  45. end
  46.  
  47. ---Implements the functions of objects.
  48. ---@param ... table
  49. function Cherry:implement(...)
  50.   for _, cherry in pairs({...}) do
  51.     for key, value in pairs(cherry) do
  52.       if self[key] == nil and type(value) == "function" then
  53.         self[key] = value
  54.       end
  55.     end
  56.   end
  57. end
  58.  
  59. ---Overrides the call functionality of the Object to return a new instance of the Object.
  60. ---@param ... unknown
  61. ---@return table
  62. function Cherry:__call(...)
  63.     local cherry = setmetatable({}, self)
  64. ---@diagnostic disable-next-line: redundant-parameter
  65.     cherry:new(...)
  66.     return cherry
  67. end
  68.  
  69. local UIElement = Cherry:extend()
  70. local Vector2 = Cherry:extend()
  71.  
  72. ---Returns a function that determines the element width and size to FIT.
  73. ---@return function
  74. local function fit()
  75.     return function (element, type)
  76.         if type == "width" then
  77.             element.widthSizing = "FIT"
  78.             element.width = 0
  79.         else
  80.             element.heightSizing = "FIT"
  81.             element.height = 0
  82.         end
  83.     end
  84. end
  85.  
  86.  
  87. ---Returns a function that determines the element width and size to FIXED.
  88. ---@param num number
  89. ---@return function
  90. local function fixed(num)
  91.     return function (element, type)
  92.         if type == "width" then
  93.             element.widthSizing = "FIXED"
  94.             element.width = num
  95.             element.widthMinimum = num
  96.         else
  97.             element.heightSizing = "FIXED"
  98.             element.height = num
  99.             element.heightMinimum = num
  100.         end
  101.     end
  102. end
  103.  
  104. ---Returns a function that determines the element width and size to GROW.
  105. ---@return function
  106. local function grow()
  107.     return function (element, type)
  108.         if type == "width" then
  109.             element.widthSizing = "GROW"
  110.             element.width = 0
  111.         else
  112.             element.heightSizing = "GROW"
  113.             element.height = 0
  114.         end
  115.         return 0
  116.     end
  117. end
  118.  
  119. ---Draws a rectangle.
  120. ---@param properties table
  121. local function drawRectangle(properties)
  122.     setmetatable(properties, {__index={
  123.         position = Vector2(0, 0),
  124.         size = Vector2(0, 0),
  125.         color = 1,
  126.     }})
  127.     if properties.window == nil then
  128.         error("You must specify a window to draw on.", 2)
  129.     end
  130.     properties.window.setCursorPos(properties.position.x, properties.position.y)
  131.     properties.window.setBackgroundColor(properties.color)
  132.     for row = 1, properties.size.y, 1 do
  133.         properties.window.setCursorPos(properties.position.x, properties.position.y+row-1)
  134.         properties.window.write(string.rep(" ", properties.size.x))
  135.     end
  136. end
  137.  
  138. ---Creates a new UIElement Object
  139. ---@param properties table
  140. function UIElement:new(properties)
  141.     setmetatable(properties, {__index={
  142.         direction = "LEFT_TO_RIGHT",
  143.         position = { 0, 0 },
  144.         size = { fit(), fit() },
  145.         padding = { left = 0, top = 0, right = 0, bottom = 0 },
  146.         childGap = 0,
  147.         children = {},
  148.         backgroundColor = 1
  149.     }})
  150.  
  151.     self.parent = properties.parent
  152.  
  153.     self.direction = properties.direction
  154.     local x, y =
  155.         properties.position.x or properties.position[1],
  156.         properties.position.y or properties.position[2]
  157.     self.position = Vector2(x, y)
  158.     self.x = x
  159.     self.y = y
  160.     properties.size[1](self, "width")
  161.     properties.size[2](self, "height")
  162.     self.size = Vector2(self.width, self.height)
  163.     self.padding = properties.padding
  164.     self.childGap = properties.childGap
  165.     self.backgroundColor = properties.backgroundColor
  166.     self.children = {}
  167.  
  168.     if properties.text ~= nil then
  169.         self.text = properties.text
  170.         self.isText = true
  171.         self.preferredWidth = #properties.text
  172.  
  173.         for word in string.gmatch(self.text, " ") do
  174.             local length = #word
  175.             if length > self.widthMinimum then
  176.                 self.widthMinimum = length
  177.                 self.width = length
  178.             end
  179.         end
  180.     end
  181.  
  182.     for _, child in ipairs(properties.children) do
  183.         -- Do something
  184.         child.parent = self
  185.         table.insert(self.children, UIElement(child))
  186.     end
  187.  
  188.     self:fitSizing()
  189.     self:growAndShrinkChildElements()
  190.     self:calculateChildPositions()
  191. end
  192.  
  193. ---Calculates the child positions of the UIElement.
  194. function UIElement:calculateChildPositions()
  195.     local offset = self:getPadding("along").x
  196.     if self.children then
  197.         for _, child in ipairs(self.children) do
  198.             local childPos = Vector2(child.x, child.y)
  199.             childPos = childPos + Vector2(self.x, self.y)
  200.  
  201.             if self.direction == "LEFT_TO_RIGHT" then
  202.                 child.x = childPos.x + offset
  203.                 child.y = childPos.y + self.padding.top
  204.                 offset = offset + child.width + self.childGap
  205.             elseif self.direction == "TOP_TO_BOTTOM" then
  206.                 child.y = childPos.y + offset
  207.                 child.x = childPos.x + self.padding.left
  208.                 offset = offset + child.height + self.childGap
  209.             else
  210.                 error("Invalid direction.", 2)
  211.             end
  212.         end
  213.     end
  214. end
  215.  
  216. ---Grows and Shrinks the child elements of the UIelement.
  217. function UIElement:growAndShrinkChildElements()
  218.     local remainingAlong = self:getSize("along")
  219.     remainingAlong = remainingAlong - self:getPadding("along"):sum()
  220.  
  221.     for _, child in ipairs(self.children) do
  222.         remainingAlong = remainingAlong - child:getSize("along", self.direction)
  223.     end
  224.     remainingAlong = remainingAlong - (#self.children - 1) * self.childGap
  225.  
  226.     local growable = {}
  227.  
  228.     for _, child in ipairs(self.children) do
  229.         if child:getSizing("along", self.direction) == "GROW" then table.insert(growable, child) end
  230.         if child:getSizing("across", self.direction) == "GROW" then
  231.             child:setSize(self:getSize("across") - self:getPadding("across"):sum(), "across", self.direction)
  232.         end
  233.     end
  234.  
  235.     if #growable == 0 then return end
  236.  
  237.     while remainingAlong > 0 do
  238.         local smallest = growable[1]:getSize("along")
  239.         local secondSmallest = 2147483647 -- max integer
  240.         local sizeToAdd = remainingAlong
  241.  
  242.         for _, child in ipairs(growable) do
  243.             if child:getSize("along") < smallest then
  244.                 secondSmallest = smallest
  245.                 smallest = child:getSize("along")
  246.             end
  247.             if child:getSize("along") > smallest then
  248.                 secondSmallest = math.min(secondSmallest, child:getSize("along"))
  249.                 sizeToAdd = secondSmallest - smallest
  250.             end
  251.         end
  252.  
  253.         sizeToAdd = math.min(sizeToAdd, math.floor(remainingAlong / #growable))
  254.         if sizeToAdd == 0 then
  255.             sizeToAdd = 1
  256.         end
  257.  
  258.         for _, child in ipairs(growable) do
  259.             if child:getSize("along") == smallest then
  260.                 child:addSize(sizeToAdd, "along", self.direction)
  261.                 remainingAlong = remainingAlong - sizeToAdd
  262.                 if remainingAlong <= 0 then
  263.                     break
  264.                 end
  265.             end
  266.         end
  267.     end
  268. end
  269.  
  270. ---Fits the size of the UIElement to its children.
  271. function UIElement:fitSizing()
  272.     local parent = self.parent
  273.     if self:getSizing("along") == "FIT" then
  274.         self:addSize(self:getPadding("along"):sum(), "along")
  275.     end
  276.     if self:getSizing("across") == "FIT" then
  277.         self:addSize(self:getPadding("across"):sum(), "across")
  278.     end
  279.  
  280.     if not self.parent then return end
  281.  
  282.     local childGap = (math.max(0, #self.children - 1) * self.childGap)
  283.     if parent:getSizing("along") == "FIT" then
  284.         parent:addSize(self:getSize("along", parent.direction) + childGap, "along")
  285.     end
  286.     if parent:getSizing("across") == "FIT" then
  287.         parent:setSize(math.max(self:getSize("across", parent.direction), parent:getSize("across")), "across")
  288.     end
  289. end
  290.  
  291. ---Gets the sizing type of the UIElement based on side and direction.
  292. ---@param side string
  293. ---@param direction string
  294. ---@return string
  295. function UIElement:getSizing(side, direction)
  296.     if direction == nil then
  297.         direction = self.direction
  298.     end
  299.  
  300.     if direction == "LEFT_TO_RIGHT" then
  301.         if side == "along" then
  302.             return self.widthSizing
  303.         elseif side == "across" then
  304.             return self.heightSizing
  305.         else
  306.             error("Invalid side.", 2)
  307.         end
  308.     elseif direction == "TOP_TO_BOTTOM" then
  309.         if side == "along" then
  310.             return self.heightSizing
  311.         elseif side == "across" then
  312.             return self.widthSizing
  313.         else
  314.             error("Invalid side.", 2)
  315.         end
  316.     else
  317.         error("Invalid direction.", 3)
  318.     end
  319. end
  320.  
  321. ---Sets the size of the UIElement based on side and direction.
  322. ---@param size number
  323. ---@param side string
  324. ---@param direction string
  325. function UIElement:setSize(size, side, direction)
  326.     if direction == nil then
  327.         direction = self.direction
  328.     end
  329.  
  330.     if direction == "LEFT_TO_RIGHT" then
  331.         if side == "along" then
  332.             self.width = size
  333.         elseif side == "across" then
  334.             self.height = size
  335.         else
  336.             error("Invalid side.", 2)
  337.         end
  338.     elseif direction == "TOP_TO_BOTTOM" then
  339.         if side == "along" then
  340.             self.height = size
  341.         elseif side == "across" then
  342.             self.width = size
  343.         else
  344.             error("Invalid side.", 2)
  345.         end
  346.     else
  347.         error("Invalid direction.", 3)
  348.     end
  349. end
  350.  
  351. ---Adds to the size of the UIElement based on side and direction.
  352. ---@param size number
  353. ---@param side string
  354. ---@param direction string
  355. function UIElement:addSize(size, side, direction)
  356.     if direction == nil then
  357.         direction = self.direction
  358.     end
  359.  
  360.     if direction == "LEFT_TO_RIGHT" then
  361.         if side == "along" then
  362.             self.width = self.width + size
  363.         elseif side == "across" then
  364.             self.height = self.height + size
  365.         else
  366.             error("Invalid side.", 2)
  367.         end
  368.     elseif direction == "TOP_TO_BOTTOM" then
  369.         if side == "along" then
  370.             self.height = self.height + size
  371.         elseif side == "across" then
  372.             self.width = self.width + size
  373.         else
  374.             error("Invalid side.", 2)
  375.         end
  376.     else
  377.         error("Invalid direction.", 3)
  378.     end
  379. end
  380.  
  381. ---Removes from the size of the UIElement based on side and direction.
  382. ---@param size number
  383. ---@param side string
  384. ---@param direction string
  385. function UIElement:removeSize(size, side, direction)
  386.     if direction == nil then
  387.         direction = self.direction
  388.     end
  389.  
  390.     if direction == "LEFT_TO_RIGHT" then
  391.         if side == "along" then
  392.             self.width = self.width - size
  393.         elseif side == "across" then
  394.             self.height = self.height - size
  395.         else
  396.             error("Invalid side.", 2)
  397.         end
  398.     elseif direction == "TOP_TO_BOTTOM" then
  399.         if side == "along" then
  400.             self.height = self.height - size
  401.         elseif side == "across" then
  402.             self.width = self.width - size
  403.         else
  404.             error("Invalid side.", 2)
  405.         end
  406.     else
  407.         error("Invalid direction.", 3)
  408.     end
  409. end
  410.  
  411. ---Gets the size of the UIElement based on side and direction.
  412. ---@param side string
  413. ---@param direction string
  414. ---@return number
  415. function UIElement:getSize(side, direction)
  416.     if direction == nil then
  417.         direction = self.direction
  418.     end
  419.  
  420.     if direction == "LEFT_TO_RIGHT" then
  421.         if side == "along" then
  422.             return self.width
  423.         elseif side == "across" then
  424.             return self.height
  425.         else
  426.             error("Invalid side.", 2)
  427.         end
  428.     elseif direction == "TOP_TO_BOTTOM" then
  429.         if side == "along" then
  430.             return self.height
  431.         elseif side == "across" then
  432.             return self.width
  433.         else
  434.             error("Invalid side.", 2)
  435.         end
  436.     else
  437.         error("Invalid direction.", 3)
  438.     end
  439. end
  440.  
  441. ---Gets the padding of the UIElement based on side and direction.
  442. ---@param side string
  443. ---@param direction string
  444. ---@return table
  445. function UIElement:getPadding(side, direction)
  446.     if direction == nil then
  447.         direction = self.direction
  448.     end
  449.  
  450.     if direction == "LEFT_TO_RIGHT" then
  451.         if side == "along" then
  452.             return Vector2(self.padding.left, self.padding.right)
  453.         elseif side == "across" then
  454.             return Vector2(self.padding.top, self.padding.bottom)
  455.         else
  456.             error("Invalid side.", 2)
  457.         end
  458.     elseif direction == "TOP_TO_BOTTOM" then
  459.         if side == "along" then
  460.             return Vector2(self.padding.top, self.padding.bottom)
  461.         elseif side == "across" then
  462.             return Vector2(self.padding.left, self.padding.right)
  463.         else
  464.             error("Invalid side.", 2)
  465.         end
  466.     else
  467.         error("Invalid direction.", 3)
  468.     end
  469. end
  470.  
  471. ---Draws the UIElement and its children on the window.
  472. ---@param window table
  473. function UIElement:draw(window)
  474.     drawRectangle{window = window, position = Vector2(self.x, self.y), size = Vector2(self.width, self.height), color = self.backgroundColor}
  475.     if self.children then
  476.         for _, child in ipairs(self.children) do
  477.             drawRectangle{
  478.                 window = window,
  479.                 position = Vector2(child.x, child.y),
  480.                 size = Vector2(child.width, child.height),
  481.                 color = child.backgroundColor
  482.             }
  483.         end
  484.     end
  485. end
  486.  
  487. ---Adds two Vector2s together.
  488. ---@param vec1 table
  489. ---@param vec2 table
  490. ---@return table
  491. Vector2.__add = function (vec1, vec2)
  492.     local x = vec1.x + vec2.x or vec1[1] + vec2[1]
  493.     local y = vec1.y + vec2.y or vec1[2] + vec2[2]
  494.     return Vector2(x, y)
  495. end
  496.  
  497. ---Instanciates a new Vector2.
  498. ---@param x number
  499. ---@param y number
  500. function Vector2:new(x, y)
  501.     self.x = x or 0
  502.     self.y = y or 0
  503. end
  504.  
  505. ---Gets the sum of the Vector2.
  506. ---@return number
  507. function Vector2:sum()
  508.     return self.x + self.y
  509. end
  510.  
  511. return { CHERRY = UIElement, FIXED = fixed, GROW = grow, FIT = fit }
  512.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement