Skip to content

Commit f0720ae

Browse files
committed
- Added render throttling
- Fixed some small bugs
1 parent f479417 commit f0720ae

11 files changed

Lines changed: 144 additions & 84 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ ascii.lua
66
tests
77
testWorkflows
88
.vscode
9+
.idea
910
todo.txt
1011
Flexbox2.lua
1112
markdown.lua

src/elements/BaseElement.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ end
466466
--- @param stateName string The state to remove
467467
--- @return BaseElement self
468468
function BaseElement:unregisterState(stateName)
469-
self._stateRegistry[stateName] = nil
469+
self._registeredStates[stateName] = nil
470470
self:unsetState(stateName)
471471
return self
472472
end

src/elements/BaseFrame.lua

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,6 @@ end
201201
--- @protected
202202
function BaseFrame:mouse_up(button, x, y)
203203
Container.mouse_up(self, button, x, y)
204-
Container.mouse_release(self, button, x, y)
205204
end
206205

207206
--- @shortDescription Resizes the Frame

src/elements/Container.lua

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ function Container:getChild(path)
340340
return v
341341
else
342342
if(v:isType("Container"))then
343-
return v:find(table.concat(parts, "/", 2))
343+
return v:getChild(table.concat(parts, "/", 2))
344344
end
345345
end
346346
end
@@ -441,6 +441,7 @@ function Container:mouse_up(button, x, y)
441441
if(success)then
442442
return true
443443
end
444+
return true
444445
end
445446
return false
446447
end
@@ -453,7 +454,11 @@ end
453454
function Container:mouse_release(button, x, y)
454455
VisualElement.mouse_release(self, button, x, y)
455456
local args = convertMousePosition(self, "mouse_release", button, x, y)
456-
self:callChildrenEvent(false, "mouse_release", table.unpack(args))
457+
for _, child in ipairs(self._values.children) do
458+
if not child._destroyed then
459+
child:dispatchEvent("mouse_release", table.unpack(args))
460+
end
461+
end
457462
end
458463

459464
--- @shortDescription Handles mouse move events

src/elements/Dialog.lua

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,25 @@ function Dialog:close()
7777
return self
7878
end
7979

80+
--- Calculates the number of lines needed to display a message at a given width
81+
--- @private
82+
function Dialog:calcLines(message, width)
83+
local available = width - 3 -- left padding + right border
84+
local lines = 1
85+
local col = 0
86+
for word in message:gmatch("%S+") do
87+
if col > 0 and col + 1 + #word > available then
88+
lines = lines + 1
89+
col = #word
90+
else
91+
col = col == 0 and #word or col + 1 + #word
92+
end
93+
end
94+
-- Also count explicit newlines in message
95+
for _ in message:gmatch("\n") do lines = lines + 1 end
96+
return lines
97+
end
98+
8099
--- Creates a simple alert dialog
81100
--- @shortDescription Creates a simple alert dialog
82101
--- @param title string The alert title
@@ -86,23 +105,29 @@ end
86105
function Dialog:alert(title, message, callback)
87106
self:clear()
88107
self.set("title", title)
89-
self.set("height", 8)
108+
109+
local w = self.getResolved("width")
110+
local msgLines = self:calcLines(message, w)
111+
-- 1 border top + 1 title + 1 gap + msgLines + 1 gap + 1 button + 1 gap + 1 border bottom
112+
local h = 2 + 1 + 1 + msgLines + 1 + 1 + 1
113+
self.set("height", h)
90114

91115
self:addLabel({
92116
text = message,
93117
x = 2, y = 3,
94-
width = self.getResolved("width") - 3,
95-
height = 3,
118+
width = w - 3,
119+
height = msgLines,
120+
autoSize = false,
96121
foreground = colors.white
97122
})
98123

99124
local btnWidth = 10
100-
local btnX = math.floor((self.getResolved("width") - btnWidth) / 2) + 1
125+
local btnX = math.floor((w - btnWidth) / 2) + 1
101126

102127
self:addButton({
103128
text = "OK",
104129
x = btnX,
105-
y = self.getResolved("height") - 2,
130+
y = h - 2,
106131
width = btnWidth,
107132
height = 1,
108133
background = self.getResolved("primaryColor"),
@@ -124,25 +149,30 @@ end
124149
function Dialog:confirm(title, message, callback)
125150
self:clear()
126151
self.set("title", title)
127-
self.set("height", 8)
152+
153+
local w = self.getResolved("width")
154+
local msgLines = self:calcLines(message, w)
155+
local h = 2 + 1 + 1 + msgLines + 1 + 1 + 1
156+
self.set("height", h)
128157

129158
self:addLabel({
130159
text = message,
131160
x = 2, y = 3,
132-
width = self.getResolved("width") - 3,
133-
height = 3,
161+
width = w - 3,
162+
height = msgLines,
163+
autoSize = false,
134164
foreground = colors.white
135165
})
136166

137167
local btnWidth = 10
138168
local spacing = 2
139169
local totalWidth = btnWidth * 2 + spacing
140-
local startX = math.floor((self.getResolved("width") - totalWidth) / 2) + 1
170+
local startX = math.floor((w - totalWidth) / 2) + 1
141171

142172
self:addButton({
143173
text = "Cancel",
144174
x = startX,
145-
y = self.getResolved("height") - 2,
175+
y = h - 2,
146176
width = btnWidth,
147177
height = 1,
148178
background = self.getResolved("secondaryColor"),
@@ -155,7 +185,7 @@ function Dialog:confirm(title, message, callback)
155185
self:addButton({
156186
text = "OK",
157187
x = startX + btnWidth + spacing,
158-
y = self.getResolved("height") - 2,
188+
y = h - 2,
159189
width = btnWidth,
160190
height = 1,
161191
background = self.getResolved("primaryColor"),

src/elements/Input.lua

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,7 @@ end
217217
--- @shortDescription Handles a blur event
218218
--- @protected
219219
function Input:blur()
220-
VisualElement.blur(self)
221-
self:setCursor(1, 1, false, self.getResolved("cursorColor") or self.getResolved("foreground"))
220+
VisualElement.blur(self)
222221
self:updateRender()
223222
end
224223

src/elements/TextBox.lua

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,10 +1143,28 @@ function TextBox:render()
11431143
local relativeY = self.getResolved("cursorY") - scrollY
11441144
if relativeX >= 1 and relativeX <= width and relativeY >= 1 and relativeY <= height then
11451145
self:setCursor(relativeX, relativeY, true, self.getResolved("cursorColor") or foreground)
1146+
else
1147+
self:setCursor(relativeX, relativeY, false, self.getResolved("cursorColor") or foreground)
11461148
end
11471149
end
11481150
end
11491151

1152+
--- @shortDescription Handles a focus event
1153+
--- @protected
1154+
function TextBox:focus()
1155+
VisualElement.focus(self)
1156+
local scrollX = self.getResolved("scrollX")
1157+
local scrollY = self.getResolved("scrollY")
1158+
local relativeX = self.getResolved("cursorX") - scrollX
1159+
local relativeY = self.getResolved("cursorY") - scrollY
1160+
local width = self.getResolved("width")
1161+
local height = self.getResolved("height")
1162+
if relativeX >= 1 and relativeX <= width and relativeY >= 1 and relativeY <= height then
1163+
self:setCursor(relativeX, relativeY, true, self.getResolved("cursorColor") or self.getResolved("foreground"))
1164+
end
1165+
self:updateRender()
1166+
end
1167+
11501168
function TextBox:destroy()
11511169
if self._autoCompleteFrame and not self._autoCompleteFrame._destroyed then
11521170
self._autoCompleteFrame:destroy()

src/elements/VisualElement.lua

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,21 @@ local VisualElement = setmetatable({}, BaseElement)
1111
VisualElement.__index = VisualElement
1212

1313
---@property x number 1 The horizontal position relative to parent
14-
VisualElement.defineProperty(VisualElement, "x", {default = 1, type = "number", canTriggerRender = true})
14+
VisualElement.defineProperty(VisualElement, "x", {default = 1, type = "number", canTriggerRender = true, setter = function(self, value)
15+
if self.parent then
16+
self.parent.set("childrenSorted", false)
17+
self.parent.set("childrenEventsSorted", false)
18+
end
19+
return value
20+
end})
1521
---@property y number 1 The vertical position relative to parent
16-
VisualElement.defineProperty(VisualElement, "y", {default = 1, type = "number", canTriggerRender = true})
22+
VisualElement.defineProperty(VisualElement, "y", {default = 1, type = "number", canTriggerRender = true, setter = function(self, value)
23+
if self.parent then
24+
self.parent.set("childrenSorted", false)
25+
self.parent.set("childrenEventsSorted", false)
26+
end
27+
return value
28+
end})
1729
---@property z number 1 The z-index for layering elements
1830
VisualElement.defineProperty(VisualElement, "z", {default = 1, type = "number", canTriggerRender = true, setter = function(self, value)
1931
if self.parent then
@@ -29,9 +41,21 @@ VisualElement.defineProperty(VisualElement, "constraints", {
2941
})
3042

3143
---@property width number 1 The width of the element
32-
VisualElement.defineProperty(VisualElement, "width", {default = 1, type = "number", canTriggerRender = true})
44+
VisualElement.defineProperty(VisualElement, "width", {default = 1, type = "number", canTriggerRender = true, setter = function(self, value)
45+
if self.parent then
46+
self.parent.set("childrenSorted", false)
47+
self.parent.set("childrenEventsSorted", false)
48+
end
49+
return value
50+
end})
3351
---@property height number 1 The height of the element
34-
VisualElement.defineProperty(VisualElement, "height", {default = 1, type = "number", canTriggerRender = true})
52+
VisualElement.defineProperty(VisualElement, "height", {default = 1, type = "number", canTriggerRender = true, setter = function(self, value)
53+
if self.parent then
54+
self.parent.set("childrenSorted", false)
55+
self.parent.set("childrenEventsSorted", false)
56+
end
57+
return value
58+
end})
3559
---@property background color black The background color
3660
VisualElement.defineProperty(VisualElement, "background", {default = colors.black, type = "color", canTriggerRender = true})
3761
---@property foreground color white The text/foreground color
@@ -75,6 +99,7 @@ VisualElement.combineProperties(VisualElement, "size", "width", "height")
7599
VisualElement.combineProperties(VisualElement, "color", "foreground", "background")
76100

77101
---@event onClick {button string, x number, y number} Fired on mouse click
102+
---@event onDoubleClick {button, x, y} Fired on double click (two clicks within 400ms)
78103
---@event onClickUp {button, x, y} Fired on mouse button release
79104
---@event onRelease {button, x, y} Fired when mouse leaves while clicked
80105
---@event onDrag {button, x, y} Fired when mouse moves while clicked
@@ -91,11 +116,12 @@ VisualElement.defineEvent(VisualElement, "focus")
91116
VisualElement.defineEvent(VisualElement, "blur")
92117

93118
VisualElement.registerEventCallback(VisualElement, "Click", "mouse_click", "mouse_up")
119+
VisualElement.registerEventCallback(VisualElement, "DoubleClick", "mouse_double_click", "mouse_click")
94120
VisualElement.registerEventCallback(VisualElement, "ClickUp", "mouse_up", "mouse_click")
95121
VisualElement.registerEventCallback(VisualElement, "Drag", "mouse_drag", "mouse_click", "mouse_up")
96122
VisualElement.registerEventCallback(VisualElement, "Scroll", "mouse_scroll")
97123
VisualElement.registerEventCallback(VisualElement, "Enter", "mouse_enter", "mouse_move")
98-
VisualElement.registerEventCallback(VisualElement, "LeEave", "mouse_leave", "mouse_move")
124+
VisualElement.registerEventCallback(VisualElement, "Leave", "mouse_leave", "mouse_move")
99125
VisualElement.registerEventCallback(VisualElement, "Focus", "focus", "blur")
100126
VisualElement.registerEventCallback(VisualElement, "Blur", "blur", "focus")
101127
VisualElement.registerEventCallback(VisualElement, "Key", "key", "key_up")
@@ -780,10 +806,22 @@ end
780806
--- @param y number The y position of the click
781807
--- @return boolean clicked Whether the element was clicked
782808
--- @protected
809+
local DOUBLE_CLICK_THRESHOLD = 0.4 -- seconds
810+
783811
function VisualElement:mouse_click(button, x, y)
784812
if self:isInBounds(x, y) then
785813
self:setState("clicked")
786-
self:fireEvent("mouse_click", button, self:getRelativePosition(x, y))
814+
local rx, ry = self:getRelativePosition(x, y)
815+
local now = os.clock()
816+
local last = self._lastClick
817+
if last and (now - last.time) <= DOUBLE_CLICK_THRESHOLD
818+
and last.button == button then
819+
self._lastClick = nil
820+
self:fireEvent("mouse_double_click", button, rx, ry)
821+
else
822+
self._lastClick = { time = now, button = button }
823+
self:fireEvent("mouse_click", button, rx, ry)
824+
end
787825
return true
788826
end
789827
return false
@@ -824,16 +862,16 @@ end
824862
--- @protected
825863
function VisualElement:mouse_move(_, x, y)
826864
if(x==nil)or(y==nil)then return false end
827-
local hover = self.getResolved("hover")
865+
local hover = self:hasState("hover")
828866
if(self:isInBounds(x, y))then
829867
if(not hover)then
830-
self.set("hover", true)
868+
self:setState("hover")
831869
self:fireEvent("mouse_enter", self:getRelativePosition(x, y))
832870
end
833871
return true
834872
else
835873
if(hover)then
836-
self.set("hover", false)
874+
self:unsetState("hover")
837875
self:fireEvent("mouse_leave", self:getRelativePosition(x, y))
838876
end
839877
end
@@ -899,13 +937,6 @@ function VisualElement:setFocused(focused, internal)
899937
return self
900938
end
901939

902-
--- Gets whether this element is focused
903-
--- @shortDescription Checks if element is focused
904-
--- @return boolean isFocused
905-
function VisualElement:isFocused()
906-
return self:hasState("focused")
907-
end
908-
909940
--- @shortDescription Handles a focus event
910941
--- @protected
911942
function VisualElement:focus()
@@ -916,8 +947,7 @@ end
916947
--- @protected
917948
function VisualElement:blur()
918949
self:fireEvent("blur")
919-
-- Attempt to clear cursor; signature may expect (x,y,blink,fg,bg)
920-
pcall(function() self:setCursor(1,1,false, self.get and self.getResolved("foreground")) end)
950+
self:setCursor(1, 1, false, self.getResolved("foreground"))
921951
end
922952

923953
--- Gets whether this element is focused

src/main.lua

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ end
3434
local main = nil
3535
local focusedFrame = nil
3636
local activeFrames = {}
37+
local THROTTLE_TIME = 0
3738
local _type = type
3839

3940
local lazyElements = {}
@@ -298,10 +299,15 @@ local function updateEvent(event, ...)
298299
end
299300
end
300301

302+
local lastRenderTime = 0
301303
local function renderFrames()
302-
for _, frame in pairs(activeFrames)do
303-
frame:render()
304-
frame:postRender()
304+
local currentTime = os.clock()
305+
if currentTime - lastRenderTime >= THROTTLE_TIME then
306+
for _, frame in pairs(activeFrames)do
307+
frame:render()
308+
frame:postRender()
309+
end
310+
lastRenderTime = currentTime
305311
end
306312
end
307313

@@ -498,6 +504,15 @@ function basalt.requireElements(elements, autoLoad)
498504
return true
499505
end
500506

507+
--- Sets the render throttle time in seconds (default: 0, no throttling)
508+
--- @shortDescription Sets the render throttle time
509+
--- @param time number The minimum time in seconds between renders
510+
--- @usage basalt.setRenderThrottleTime(0.1) -- Throttle rendering to 0.1 seconds
511+
function basalt.setRenderThrottleTime(time)
512+
expect(1, time, "number")
513+
THROTTLE_TIME = time
514+
end
515+
501516
--- Loads a manifest file that describes element requirements and configuration
502517
--- @shortDescription Loads an application manifest
503518
--- @param path string The path to the manifest file

src/plugins/animation.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ Animation.registerAnimation("entries", {
549549
index = #list
550550
end
551551
anim.element.set(anim.args[1], list[index])
552-
552+
return progress >= 1
553553
end,
554554

555555
complete = function(anim)

0 commit comments

Comments
 (0)