uimovabletabbar.lua

main 506 lines · 11.9 KB Raw
Alex Alex Commit Client Upload Part 2 19/10/2026 00:06
1-- @docclass
2UIMoveableTabBar = extends(UIWidget, "UIMoveableTabBar")
3
4-- private functions
5local function onTabClick(tab)
6 tab.tabBar:selectTab(tab)
7end
8
9local function updateMargins(tabBar)
10 if #tabBar.tabs == 0 then return end
11
12 local currentMargin = 0
13 for i = 1, #tabBar.tabs do
14 tabBar.tabs[i]:setMarginLeft(currentMargin)
15 currentMargin = currentMargin + tabBar.tabSpacing + tabBar.tabs[i]:getWidth()
16 end
17end
18
19local function updateNavigation(tabBar)
20 if tabBar.prevNavigation then
21 if #tabBar.preTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= 1 then
22 tabBar.prevNavigation:enable()
23 else
24 tabBar.prevNavigation:disable()
25 end
26 end
27
28 if tabBar.nextNavigation then
29 if #tabBar.postTabs > 0 or table.find(tabBar.tabs, tabBar.currentTab) ~= #tabBar.tabs then
30 tabBar.nextNavigation:enable()
31 else
32 tabBar.nextNavigation:disable()
33 end
34 end
35end
36
37local function updateIndexes(tabBar, tab, xoff)
38 local tabs = tabBar.tabs
39 local currentMargin = 0
40 local prevIndex = table.find(tabs, tab)
41 local newIndex = prevIndex
42 local xmid = xoff + tab:getWidth()/2
43 for i = 1, #tabs do
44 local nextTab = tabs[i]
45 if xmid >= currentMargin + nextTab:getWidth()/2 then
46 newIndex = table.find(tabs, nextTab)
47 end
48 currentMargin = currentMargin + tabBar.tabSpacing * (i - 1) + tabBar.tabs[i]:getWidth()
49 end
50 if newIndex ~= prevIndex then
51 table.remove(tabs, table.find(tabs, tab))
52 table.insert(tabs, newIndex, tab)
53 end
54 updateNavigation(tabBar)
55end
56
57local function getMaxMargin(tabBar, tab)
58 if #tabBar.tabs == 0 then return 0 end
59
60 local maxMargin = 0
61 for i = 1, #tabBar.tabs do
62 if tabBar.tabs[i] ~= tab then
63 maxMargin = maxMargin + tabBar.tabs[i]:getWidth()
64 end
65 end
66 return maxMargin + tabBar.tabSpacing * (#tabBar.tabs - 1)
67end
68
69local function updateTabs(tabBar)
70 if #tabBar.postTabs > 0 then
71 local i = 1
72 while i <= #tabBar.postTabs do
73 local tab = tabBar.postTabs[i]
74 if getMaxMargin(tabBar) + tab:getWidth() > tabBar:getWidth() then
75 break
76 end
77
78 table.remove(tabBar.postTabs, i)
79 table.insert(tabBar.tabs, tab)
80 tab:setVisible(true)
81 end
82 end
83 if #tabBar.preTabs > 0 then
84 for i = #tabBar.preTabs, 1, -1 do
85 local tab = tabBar.preTabs[i]
86 if getMaxMargin(tabBar) + tab:getWidth() > tabBar:getWidth() then
87 break
88 end
89
90 table.remove(tabBar.preTabs, i)
91 table.insert(tabBar.tabs, 1, tab)
92 tab:setVisible(true)
93 end
94 end
95 updateNavigation(tabBar)
96 updateMargins(tabBar)
97 if not tabBar.currentTab and #tabBar.tabs > 0 then
98 tabBar:selectTab(tabBar.tabs[1])
99 end
100end
101
102local function hideTabs(tabBar, fromBack, toArray, width)
103 while #tabBar.tabs > 0 and getMaxMargin(tabBar) + width > tabBar:getWidth() do
104 local index = fromBack and #tabBar.tabs or 1
105 local tab = tabBar.tabs[index]
106 table.remove(tabBar.tabs, index)
107 if fromBack then
108 table.insert(toArray, 1, tab)
109 else
110 table.insert(toArray, tab)
111 end
112 if tabBar.currentTab == tab then
113 if #tabBar.tabs > 0 then
114 tabBar:selectTab(tabBar.tabs[#tabBar.tabs])
115 else
116 tabBar.currentTab:setChecked(false)
117 tabBar.currentTab = nil
118 end
119 end
120 tab:setVisible(false)
121 end
122end
123
124local function showPreTab(tabBar)
125 if #tabBar.preTabs == 0 then
126 return nil
127 end
128
129 local tmpTab = tabBar.preTabs[#tabBar.preTabs]
130 hideTabs(tabBar, true, tabBar.postTabs, tmpTab:getWidth())
131
132 table.remove(tabBar.preTabs, #tabBar.preTabs)
133 table.insert(tabBar.tabs, 1, tmpTab)
134 tmpTab:setVisible(true)
135 return tmpTab
136end
137
138local function showPostTab(tabBar)
139 if #tabBar.postTabs == 0 then
140 return nil
141 end
142
143 local tmpTab = tabBar.postTabs[1]
144 hideTabs(tabBar, false, tabBar.preTabs, tmpTab:getWidth())
145
146 table.remove(tabBar.postTabs, 1)
147 table.insert(tabBar.tabs, tmpTab)
148 tmpTab:setVisible(true)
149 return tmpTab
150end
151
152local function onTabMousePress(tab, mousePos, mouseButton)
153 if mouseButton == MouseRightButton then
154 if tab.menuCallback then tab.menuCallback(tab, mousePos, mouseButton) end
155 return true
156 end
157end
158
159local function onTabDragEnter(tab, mousePos)
160 tab:raise()
161 tab.hotSpot = mousePos.x - tab:getMarginLeft()
162 tab.tabBar.selected = tab
163 return true
164end
165
166local function onTabDragLeave(tab)
167 updateMargins(tab.tabBar)
168 tab.tabBar.selected = nil
169 return true
170end
171
172local function onTabDragMove(tab, mousePos, mouseMoved)
173 if tab == tab.tabBar.selected then
174 local xoff = mousePos.x - tab.hotSpot
175
176 -- update indexes
177 updateIndexes(tab.tabBar, tab, xoff)
178 updateIndexes(tab.tabBar, tab, xoff)
179
180 -- update margins
181 updateMargins(tab.tabBar)
182 xoff = math.max(xoff, 0)
183 xoff = math.min(xoff, getMaxMargin(tab.tabBar, tab))
184 tab:setMarginLeft(xoff)
185 end
186end
187
188local function tabBlink(tab, step)
189 local step = step or 0
190 tab:setOn(not tab:isOn())
191
192 removeEvent(tab.blinkEvent)
193 if step < 4 then
194 tab.blinkEvent = scheduleEvent(function() tabBlink(tab, step+1) end, 500)
195 else
196 tab:setOn(true)
197 tab.blinkEvent = nil
198 end
199end
200
201-- public functions
202function UIMoveableTabBar.create()
203 local tabbar = UIMoveableTabBar.internalCreate()
204 tabbar:setFocusable(false)
205 tabbar.tabs = {}
206 tabbar.selected = nil -- dragged tab
207 tabbar.tabSpacing = 0
208 tabbar.tabsMoveable = false
209 tabbar.preTabs = {}
210 tabbar.postTabs = {}
211 tabbar.prevNavigation = nil
212 tabbar.nextNavigation = nil
213 tabbar.onGeometryChange = function()
214 hideTabs(tabbar, true, tabbar.postTabs, 0)
215 updateTabs(tabbar)
216 end
217 return tabbar
218end
219
220function UIMoveableTabBar:onDestroy()
221 if self.prevNavigation then
222 self.prevNavigation:disable()
223 end
224
225 if self.nextNavigation then
226 self.nextNavigation:disable()
227 end
228
229 self.nextNavigation = nil
230 self.prevNavigation = nil
231end
232
233function UIMoveableTabBar:setContentWidget(widget)
234 self.contentWidget = widget
235 if #self.tabs > 0 then
236 self.contentWidget:addChild(self.tabs[1].tabPanel)
237 end
238end
239
240function UIMoveableTabBar:setTabSpacing(tabSpacing)
241 self.tabSpacing = tabSpacing
242 updateMargins(self)
243end
244
245function UIMoveableTabBar:addTab(text, panel, menuCallback)
246 if panel == nil then
247 panel = g_ui.createWidget(self:getStyleName() .. 'Panel')
248 panel:setId('tabPanel')
249 end
250
251 local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self)
252 panel.isTab = true
253 tab.tabPanel = panel
254 tab.tabBar = self
255 tab:setId('tab')
256 tab:setDraggable(self.tabsMoveable)
257 tab:setText(text)
258 tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight())
259 tab.menuCallback = menuCallback or nil
260 tab.onClick = onTabClick
261 tab.onMousePress = onTabMousePress
262 tab.onDragEnter = onTabDragEnter
263 tab.onDragLeave = onTabDragLeave
264 tab.onDragMove = onTabDragMove
265 tab.onDestroy = function() tab.tabPanel:destroy() end
266
267 if #self.tabs == 0 then
268 self:selectTab(tab)
269 tab:setMarginLeft(0)
270 table.insert(self.tabs, tab)
271 else
272 local newMargin = self.tabSpacing * #self.tabs
273 for i = 1, #self.tabs do
274 newMargin = newMargin + self.tabs[i]:getWidth()
275 end
276 tab:setMarginLeft(newMargin)
277
278 hideTabs(self, true, self.postTabs, tab:getWidth())
279 table.insert(self.tabs, tab)
280 if #self.tabs == 1 then
281 self:selectTab(tab)
282 end
283 updateMargins(self)
284 end
285
286 updateNavigation(self)
287 return tab
288end
289
290-- Additional function to move the tab by lua
291function UIMoveableTabBar:moveTab(tab, units)
292 local index = table.find(self.tabs, tab)
293 if index == nil then return end
294
295 local focus = false
296 if self.currentTab == tab then
297 self:selectPrevTab()
298 focus = true
299 end
300
301 table.remove(self.tabs, index)
302
303 local newIndex = math.min(#self.tabs+1, math.max(index + units, 1))
304 table.insert(self.tabs, newIndex, tab)
305 if focus then self:selectTab(tab) end
306 updateMargins(self)
307 return newIndex
308end
309
310function UIMoveableTabBar:onStyleApply(styleName, styleNode)
311 if styleNode['movable'] then
312 self.tabsMoveable = styleNode['movable']
313 end
314 if styleNode['tab-spacing'] then
315 self:setTabSpacing(styleNode['tab-spacing'])
316 end
317end
318
319function UIMoveableTabBar:clearTabs()
320 while #self.tabs > 0 do
321 self:removeTab(self.tabs[#self.tabs])
322 end
323end
324
325function UIMoveableTabBar:removeTab(tab)
326 local tabTables = {self.tabs, self.preTabs, self.postTabs}
327 local index = nil
328 local tabTable = nil
329 for i = 1, #tabTables do
330 index = table.find(tabTables[i], tab)
331 if index ~= nil then
332 tabTable = tabTables[i]
333 break
334 end
335 end
336
337 if tabTable == nil then
338 return
339 end
340 table.remove(tabTable, index)
341 if self.currentTab == tab then
342 self:selectPrevTab()
343 if #self.tabs == 1 then
344 self.currentTab = nil
345 end
346 end
347 if tab.blinkEvent then
348 removeEvent(tab.blinkEvent)
349 end
350 updateTabs(self)
351 tab:destroy()
352end
353
354function UIMoveableTabBar:getTab(text)
355 for k,tab in pairs(self.tabs) do
356 if tab:getText():lower() == text:lower() then
357 return tab
358 end
359 end
360 for k,tab in pairs(self.preTabs) do
361 if tab:getText():lower() == text:lower() then
362 return tab
363 end
364 end
365 for k,tab in pairs(self.postTabs) do
366 if tab:getText():lower() == text:lower() then
367 return tab
368 end
369 end
370end
371
372function UIMoveableTabBar:selectTab(tab)
373 if self.currentTab == tab then return end
374 if self.contentWidget then
375 local selectedWidget = self.contentWidget:getLastChild()
376 if selectedWidget and selectedWidget.isTab then
377 self.contentWidget:removeChild(selectedWidget)
378 end
379 self.contentWidget:addChild(tab.tabPanel)
380 tab.tabPanel:fill('parent')
381 end
382
383 if self.currentTab then
384 self.currentTab:setChecked(false)
385 end
386 signalcall(self.onTabChange, self, tab)
387 self.currentTab = tab
388 tab:setChecked(true)
389 tab:setOn(false)
390 tab.blinking = false
391
392 if tab.blinkEvent then
393 removeEvent(tab.blinkEvent)
394 tab.blinkEvent = nil
395 end
396
397 local parent = tab:getParent()
398 parent:focusChild(tab, MouseFocusReason)
399 updateNavigation(self)
400end
401
402function UIMoveableTabBar:selectNextTab()
403 if self.currentTab == nil then
404 return
405 end
406
407 local index = table.find(self.tabs, self.currentTab)
408 if index == nil then
409 return
410 end
411
412 local newIndex = index + 1
413 if newIndex > #self.tabs then
414 if #self.postTabs > 0 then
415 local widget = showPostTab(self)
416 self:selectTab(widget)
417 else
418 if #self.preTabs > 0 then
419 for i = 1, #self.preTabs do
420 showPreTab(self)
421 end
422 end
423
424 self:selectTab(self.tabs[1])
425 end
426 updateTabs(self)
427 return
428 end
429
430 local nextTab = self.tabs[newIndex]
431 if not nextTab then
432 return
433 end
434
435 self:selectTab(nextTab)
436end
437
438function UIMoveableTabBar:selectPrevTab()
439 if self.currentTab == nil then
440 return
441 end
442
443 local index = table.find(self.tabs, self.currentTab)
444 if index == nil then
445 return
446 end
447
448 local newIndex = index - 1
449 if newIndex <= 0 then
450 if #self.preTabs > 0 then
451 local widget = showPreTab(self)
452 self:selectTab(widget)
453 else
454 if #self.postTabs > 0 then
455 for i = 1, #self.postTabs do
456 showPostTab(self)
457 end
458 end
459
460 self:selectTab(self.tabs[#self.tabs])
461 end
462 updateTabs(self)
463 return
464 end
465
466 local prevTab = self.tabs[newIndex]
467 if not prevTab then
468 return
469 end
470
471 self:selectTab(prevTab)
472end
473
474function UIMoveableTabBar:blinkTab(tab)
475 if tab:isChecked() then return end
476 tab.blinking = true
477 tabBlink(tab)
478end
479
480function UIMoveableTabBar:getTabPanel(tab)
481 return tab.tabPanel
482end
483
484function UIMoveableTabBar:getCurrentTabPanel()
485 if self.currentTab then
486 return self.currentTab.tabPanel
487 end
488end
489
490function UIMoveableTabBar:getCurrentTab()
491 return self.currentTab
492end
493
494function UIMoveableTabBar:setNavigation(prevButton, nextButton)
495 self.prevNavigation = prevButton
496 self.nextNavigation = nextButton
497
498 if self.prevNavigation then
499 self.prevNavigation.onClick = function() self:selectPrevTab() end
500 end
501 if self.nextNavigation then
502 self.nextNavigation.onClick = function() self:selectNextTab() end
503 end
504 updateNavigation(self)
505end
506
Top