DabDaddy6223

basil_ui

Aug 4th, 2026 (edited)
72
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 11.89 KB | None | 0 0
  1. local ui = {}
  2. ui.text = {}
  3. ui.draw = {}
  4. ui.widgets = {}
  5. ui.layout = {}
  6.  
  7. ui.dirty = true
  8. ui.target = term
  9.  
  10. --------------------------------------------------
  11. -- Theme
  12. --------------------------------------------------
  13.  
  14. ui.theme = {}
  15. ui.theme.colors = {
  16.     background = colors.black,
  17.     foreground = colors.white
  18. }
  19. ui.theme.colors.defaults = {
  20.     background = colors.black,
  21.     foreground = colors.white,
  22.  
  23.     buttonBackground = colors.gray,
  24.     buttonForeground = colors.white,
  25.     buttonSelectedBackground = colors.white,
  26.     buttonSelectedForeground = colors.black
  27. }
  28. ui.theme.colors.button = {
  29.     background = colors.gray,
  30.     foreground = colors.white,
  31.  
  32.     selectedBackground = colors.white,
  33.     selectedForeground = colors.black
  34. }
  35. ui.theme.characters = {
  36.     horizontal = "-",
  37.     vertical = "|",
  38.  
  39.     topLeft = "+",
  40.     topRight = "+",
  41.     bottomLeft = "+",
  42.     bottomRight = "+",
  43.  
  44.     leftJunction = "+",
  45.     rightJunction = "+",
  46.  
  47.     progressEmpty = "-",
  48.     progressFill = "#"
  49. }
  50.  
  51. --------------------------------------------------
  52. -- Terminal
  53. --------------------------------------------------
  54.  
  55. function ui.invalidate()
  56.     ui.dirty = true
  57. end
  58.  
  59. function ui.validate()
  60.     ui.dirty = false
  61. end
  62.  
  63. function ui.isDirty()
  64.     return ui.dirty
  65. end
  66.  
  67. function ui.clear()
  68.     ui.target.clear()
  69.     ui.target.setCursorPos(1, 1)
  70. end
  71.  
  72. function ui.setStyle(style)
  73.     for k, v in pairs(style) do
  74.         if k == "background" then
  75.             ui.theme.colors.background = v
  76.         elseif k == "foreground" then
  77.             ui.theme.colors.foreground = v
  78.         end
  79.     end
  80. end
  81.  
  82. function ui.resetStyle()
  83.     ui.theme.colors.background = ui.theme.colors.defaults.background
  84.     ui.theme.colors.foreground = ui.theme.colors.defaults.foreground
  85.  
  86.     ui.theme.colors.button.background = ui.theme.colors.defaults.buttonBackground
  87.     ui.theme.colors.button.foreground = ui.theme.colors.defaults.buttonForeground
  88.     ui.theme.colors.button.selectedBackground = ui.theme.colors.defaults.buttonSelectedBackground
  89.     ui.theme.colors.button.selectedForeground = ui.theme.colors.defaults.buttonSelectedForeground
  90.  
  91.     ui.target.setBackgroundColor(ui.theme.colors.background)
  92.     ui.target.setTextColor(ui.theme.colors.foreground)
  93. end
  94.  
  95. function ui.setTarget(target)
  96.     ui.target = target
  97. end
  98.  
  99. function ui.setTextScale(scale)
  100.     if ui.target.setTextScale then
  101.         ui.target.setTextScale(scale)
  102.     end
  103. end
  104.  
  105. --------------------------------------------------
  106. -- Text
  107. --------------------------------------------------
  108.  
  109. function ui.text.center(y, text)
  110.     local w, _ = ui.target.getSize()
  111.  
  112.     local x = math.floor((w - #text) / 2) + 1
  113.  
  114.     ui.draw.text(x, y, text)
  115. end
  116.  
  117. --------------------------------------------------
  118. -- Draw
  119. --------------------------------------------------
  120.  
  121. function ui.draw.point(x, y, char)
  122.     ui.target.setCursorPos(x, y)
  123.     ui.target.write(char)
  124. end
  125.  
  126. function ui.draw.hLine(x, y, length)
  127.     ui.target.setCursorPos(x, y)
  128.     ui.target.write(string.rep(ui.theme.characters.horizontal, length))
  129. end
  130.  
  131. function ui.draw.vLine(x, y, length)
  132.     for i = 1, length - 1 do
  133.         ui.target.setCursorPos(x, y + i)
  134.         ui.target.write(ui.theme.characters.vertical)
  135.     end
  136. end
  137.  
  138. function ui.draw.box(x, y, w, h)
  139.     ui.draw.hLine(x + 1, y, w - 2)
  140.     ui.draw.hLine(x + 1, y + h - 1, w - 2)
  141.  
  142.     ui.draw.vLine(x, y, h - 1)
  143.     ui.draw.vLine(x + w - 1, y, h - 1)
  144.  
  145.     ui.draw.point(x, y, ui.theme.characters.topLeft)
  146.     ui.draw.point(x + w - 1, y, ui.theme.characters.topRight)
  147.     ui.draw.point(x, y + h - 1, ui.theme.characters.bottomLeft)
  148.     ui.draw.point(x + w - 1, y + h - 1, ui.theme.characters.bottomRight)
  149. end
  150.  
  151. function ui.draw.fill(x, y, w, h)
  152.     local blank = string.rep(" ", w)
  153.  
  154.     for i = 0, h - 1 do
  155.         ui.target.setCursorPos(x, y + i)
  156.         ui.target.write(blank)
  157.     end
  158. end
  159.  
  160. function ui.draw.text(x, y, text)
  161.     ui.target.setCursorPos(x, y)
  162.     ui.target.write(text)
  163. end
  164.  
  165. --------------------------------------------------
  166. -- Widgets
  167. --------------------------------------------------
  168.  
  169. function ui.widgets.text(rect, text, hAlign, vAlign)
  170.     hAlign = hAlign or "left"
  171.     vAlign = vAlign or "top"
  172.  
  173.     local x, y = rect.x, rect.y
  174.  
  175.     if hAlign == "center" then
  176.         x = rect.x + math.floor((rect.w - #text) / 2)
  177.     elseif hAlign == "right" then
  178.         x = rect.x + (rect.w - #text)
  179.     end
  180.  
  181.     if vAlign == "middle" then
  182.         y = rect.y + math.floor(rect.h / 2)
  183.     elseif vAlign == "bottom" then
  184.         y = rect.y + rect.h - 1
  185.     end
  186.  
  187.     local available = rect.w - (x - rect.x)
  188.     if #text > available then
  189.         text = text:sub(1, available)
  190.     end
  191.  
  192.     ui.draw.text(x, y, text)
  193. end
  194.  
  195. function ui.widgets.list(rect, lst, drawItem, spacing)
  196.     spacing = spacing or 0
  197.     drawItem = drawItem or function(row, item)
  198.         ui.widgets.text(row, tostring(item))
  199.     end
  200.  
  201.     local remaining = ui.layout.copy(rect)
  202.  
  203.     for _, item in ipairs(lst) do
  204.         if remaining.h <= 0 then
  205.             break
  206.         end
  207.  
  208.         local row = ui.layout.row(remaining, 1)
  209.         drawItem(row, item)
  210.  
  211.         remaining = ui.layout.consumeTop(remaining, 1)
  212.     end
  213.  
  214.     return remaining
  215. end
  216.  
  217. function ui.widgets.panel(rect, title, hAlign)
  218.     ui.draw.box(rect.x, rect.y, rect.w, rect.h)
  219.     ui.draw.fill(rect.x + 1, rect.y + 1, rect.w - 2, rect.h - 2)
  220.  
  221.     if title == nil then
  222.         return rect
  223.     end
  224.  
  225.     local titleRect = ui.layout.margin(rect, 1, 1, 1, rect.h - 2)
  226.     ui.widgets.text(titleRect, title, hAlign)
  227.  
  228.     ui.draw.hLine(rect.x + 1, rect.y + 2, rect.w - 2)
  229.     ui.draw.point(rect.x, rect.y + 2, ui.theme.characters.leftJunction)
  230.     ui.draw.point(rect.x + rect.w - 1, rect.y + 2, ui.theme.characters.rightJunction)
  231.  
  232.     return ui.layout.margin(rect, 1, 3, 1, 1)
  233. end
  234.  
  235. function ui.widgets.progress(rect, amount)
  236.     -- TODO: Allow amount to be any value and have a min & max variable rather than forcing 0.0-1.0
  237.    
  238.     amount = math.max(0, math.min(1, amount))
  239.     local percentage = amount
  240.  
  241.     local filled = math.floor(rect.w * percentage)
  242.     local empty = rect.w - filled
  243.  
  244.     ui.draw.text(rect.x, rect.y, string.rep(ui.theme.characters.progressFill, filled) .. string.rep(ui.theme.characters.progressEmpty, empty))
  245. end
  246.  
  247. function ui.widgets.button(rect, text, selected, bordered)
  248.     local bg = ui.theme.colors.button.background
  249.     local fg = ui.theme.colors.button.foreground
  250.    
  251.     if selected then
  252.         bg = ui.theme.colors.button.selectedBackground
  253.         fg = ui.theme.colors.button.selectedForeground
  254.     end
  255.  
  256.     ui.target.setBackgroundColor(bg)
  257.     ui.target.setTextColor(fg)
  258.  
  259.     if bordered then
  260.         ui.draw.box(rect.x, rect.y, rect.w, rect.h)
  261.         ui.draw.fill(rect.x + 1, rect.y + 1, rect.w - 2, rect.h - 2)
  262.     else
  263.         ui.draw.fill(rect.x, rect.y, rect.w, rect.h)
  264.     end
  265.     ui.widgets.text(rect, text, "center", "middle")
  266.  
  267.     ui.target.setBackgroundColor(ui.theme.colors.background)
  268.     ui.target.setTextColor(ui.theme.colors.foreground)
  269. end
  270.  
  271. --------------------------------------------------
  272. -- Layout
  273. --------------------------------------------------
  274.  
  275. function ui.layout.copy(rect)
  276.     return {
  277.         x = rect.x,
  278.         y = rect.y,
  279.         w = rect.w,
  280.         h = rect.h
  281.     }
  282. end
  283.  
  284. function ui.layout.row(rect, index)
  285.     return {
  286.         x = rect.x,
  287.         y = rect.y + index - 1,
  288.         w = rect.w,
  289.         h = 1
  290.     }
  291. end
  292.  
  293. function ui.layout.takeLeft(rect, width)
  294.     return {
  295.         x = rect.x,
  296.         y = rect.y,
  297.         w = width,
  298.         h = rect.h
  299.     }
  300. end
  301.  
  302. function ui.layout.takeRight(rect, width)
  303.     return {
  304.         x = rect.x + rect.w - width,
  305.         y = rect.y,
  306.         w = width,
  307.         h = rect.h
  308.     }
  309. end
  310.  
  311. function ui.layout.takeTop(rect, height)
  312.     return {
  313.         x = rect.x,
  314.         y = rect.y,
  315.         w = rect.w,
  316.         h = height
  317.     }
  318. end
  319.  
  320. function ui.layout.takeBottom(rect, height)
  321.     return {
  322.         x = rect.x,
  323.         y = rect.y + rect.h - height,
  324.         w = rect.w,
  325.         h = rect.h
  326.     }
  327. end
  328.  
  329. function ui.layout.consumeLeft(rect, width)
  330.     return {
  331.         x = rect.x + width,
  332.         y = rect.y,
  333.         w = rect.w - width,
  334.         h = rect.h
  335.     }
  336. end
  337.  
  338. function ui.layout.consumeRight(rect, width)
  339.     return {
  340.         x = rect.x,
  341.         y = rect.y,
  342.         w = rect.w - width,
  343.         h = rect.h
  344.     }
  345. end
  346.  
  347. function ui.layout.consumeTop(rect, height)
  348.     return {
  349.         x = rect.x,
  350.         y = rect.y + height,
  351.         w = rect.w,
  352.         h = rect.h - height
  353.     }
  354. end
  355.  
  356. function ui.layout.consumeBottom(rect, height)
  357.     return {
  358.         x = rect.x,
  359.         y = rect.y,
  360.         w = rect.w,
  361.         h = rect.h - height
  362.     }
  363. end
  364.  
  365. function ui.layout.center(rect, w, h)
  366.     return {
  367.         x = rect.x + math.floor((rect.w - w) / 2),
  368.         y = rect.y + math.floor((rect.h - h) / 2),
  369.         w = w,
  370.         h = h
  371.     }
  372. end
  373.  
  374. function ui.layout.centerX(rect, w)
  375.     return {
  376.         x = rect.x + math.floor((rect.w - w) / 2),
  377.         y = rect.y,
  378.         w = w,
  379.         h = rect.h
  380.     }
  381. end
  382.  
  383. function ui.layout.centerY(rect, h)
  384.     return {
  385.         x = rect.x,
  386.         y = rect.y + math.floor((rect.h - h) / 2),
  387.         w = rect.w,
  388.         h = h
  389.     }
  390. end
  391.  
  392. function ui.layout.full()
  393.     local terminalWidth, terminalHeight = ui.target.getSize()
  394.     return {
  395.         x = 1,
  396.         y = 1,
  397.         w = terminalWidth,
  398.         h = terminalHeight
  399.     }
  400. end
  401.  
  402. function ui.layout.splitVertical(rect, split_percentage)
  403.     local left = {
  404.         x = rect.x,
  405.         y = rect.y,
  406.         w = math.floor(rect.w * split_percentage),
  407.         h = rect.h
  408.     }
  409.     local right = {
  410.         x = rect.x + left.w,
  411.         y = rect.y,
  412.         w = rect.w - left.w,
  413.         h = rect.h
  414.     }
  415.  
  416.     return left, right
  417. end
  418.  
  419. function ui.layout.columns(rect, split_ratios)
  420.     local columns = {}
  421.  
  422.     local currentX = rect.x
  423.     local remainingWidth = rect.w
  424.  
  425.     for i, ratio in ipairs(split_ratios) do
  426.         local width
  427.  
  428.         if i == #split_ratios then
  429.             width = remainingWidth
  430.         else
  431.             width = math.floor(rect.w * ratio)
  432.         end
  433.  
  434.         table.insert(columns, {
  435.             x = currentX,
  436.             y = rect.y,
  437.             w = width,
  438.             h = rect.h
  439.         })
  440.  
  441.         currentX = currentX + width
  442.         remainingWidth = remainingWidth - width
  443.     end
  444.  
  445.     return columns
  446. end
  447.  
  448. function ui.layout.splitHorizontal(rect, split_percentage)
  449.     local top = {
  450.         x = rect.x,
  451.         y = rect.y,
  452.         w = rect.w,
  453.         h = math.floor(rect.h * split_percentage)
  454.     }
  455.     local bottom = {
  456.         x = rect.x,
  457.         y = rect.y + top.h,
  458.         w = rect.w,
  459.         h = rect.h - top.h
  460.     }
  461.  
  462.     return top, bottom
  463. end
  464.  
  465. function ui.layout.inset(rect, amount)
  466.     return {
  467.         x = rect.x + amount,
  468.         y = rect.y + amount,
  469.         w = rect.w - amount * 2,
  470.         h = rect.h - amount * 2
  471.     }
  472. end
  473.  
  474. function ui.layout.margin(rect, left, top, right, bottom)
  475.     left = left or 0
  476.     top = top or left
  477.     right = right or left
  478.     bottom = bottom or top
  479.  
  480.     return {
  481.         x = rect.x + left,
  482.         y = rect.y + top,
  483.         w = rect.w - left - right,
  484.         h = rect.h - top - bottom
  485.     }
  486. end
  487.  
  488. function ui.layout.verticalStack(rect, w, elementHeight, elementCount, spacing)
  489.     spacing = spacing or 0
  490.  
  491.     local rects = {}
  492.  
  493.     local totalHeight =
  494.         elementCount * elementHeight +
  495.         (elementCount - 1) * spacing
  496.  
  497.     local startY = rect.y + math.floor((rect.h - totalHeight) / 2)
  498.  
  499.     for i = 1, elementCount do
  500.         table.insert(rects, {
  501.             x = rect.x + math.floor((rect.w - w) / 2),
  502.             y = startY + (i - 1) * (elementHeight + spacing),
  503.             w = w,
  504.             h = elementHeight
  505.         })
  506.     end
  507.  
  508.     return rects
  509. end
  510.  
  511. return ui
Advertisement
Add Comment
Please, Sign In to add comment