Merge pull request #2875 from robob27/tabs-as-widgets

Add Tab/TabBar to widgets
develop
Myk 2023-02-16 00:53:13 -08:00 committed by GitHub
commit 070e10a75a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 188 additions and 2 deletions

@ -53,7 +53,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## API ## API
- ``Gui::any_civzone_hotkey``, ``Gui::getAnyCivZone``, ``Gui::getSelectedCivZone``: new functions to operate on the new zone system - ``Gui::any_civzone_hotkey``, ``Gui::getAnyCivZone``, ``Gui::getSelectedCivZone``: new functions to operate on the new zone system
- Units module: added new predicates for: - Units module: added new predicates for:
- ``isGeldable()`` - ``isGeldable()``
- ``isMarkedForGelding()`` - ``isMarkedForGelding()``
@ -61,8 +60,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## Lua ## Lua
- ``dfhack.gui.getSelectedCivZone``: returns the Zone that the user has selected currently - ``dfhack.gui.getSelectedCivZone``: returns the Zone that the user has selected currently
- ``widgets.FilteredList``: Added ``edit_on_change`` optional parameter to allow a custom callback on filter edit change. - ``widgets.FilteredList``: Added ``edit_on_change`` optional parameter to allow a custom callback on filter edit change.
- Added ``widgets.TabBar`` and ``widgets.Tab`` (migrated from control-panel.lua)
## Removed ## Removed

@ -5028,6 +5028,41 @@ The widget implements:
Same as with an ordinary list. Same as with an ordinary list.
TabBar class
------------
This widget implements a set of one or more tabs to allow navigation between groups of content. Tabs automatically wrap on
the width of the window and will continue rendering on the next line(s) if all tabs cannot fit on a single line.
:key: Specifies a keybinding that can be used to switch to the next tab.
:key_back: Specifies a keybinding that can be used to switch to the previous tab.
:labels: A table of strings; entry representing the label text for a single tab. The order of the entries
determines the order the tabs will appear in.
:on_select: Callback executed when a tab is selected. It receives the selected tab index as an argument. The provided function
should update the value of whichever variable your script uses to keep track of the currently selected tab.
:get_cur_page: The function used by the TabBar to determine which Tab is currently selected. The function you provide should
return an integer that corresponds to the non-zero index of the currently selected Tab (i.e. whatever variable
you update in your ``on_select`` callback)
:active_tab_pens: A table of pens used to render active tabs. See the default implementation in widgets.lua for an example
of how to construct the table. Leave unspecified to use the default pens.
:inactive_tab_pens: A table of pens used to render inactive tabs. See the default implementation in widgets.lua for an example
of how to construct the table. Leave unspecified to use the default pens.
:get_pens: A function used to determine which pens should be used to render a tab. Receives the index of the tab as the first
argument and the TabBar widget itself as the second. The default implementation, which will handle most situations,
returns ``self.active_tab_pens``, if ``self.get_cur_page() == idx``, otherwise returns ``self.inactive_tab_pens``.
Tab class
---------
This widget implements a single clickable tab and is the main component of the TabBar widget. Usage of the ``TabBar``
widget does not require direct usage of ``Tab``.
:id: The id of the tab.
:label: The text displayed on the tab.
:on_select: Callback executed when the tab is selected.
:get_pens: A function that is used during ``Tab:onRenderBody`` to determine the pens that should be used for drawing. See the
usage of ``Tab`` in ``TabBar:init()`` for an example. See the default value of ``active_tab_pens`` or ``inactive_tab_pens``
in ``TabBar`` for an example of how to construct pens.
.. _lua-plugins: .. _lua-plugins:

@ -2091,4 +2091,156 @@ function FilteredList:onFilterChar(char, text)
return true return true
end end
local DEFAULT_ACTIVE_TAB_PENS = {
text_mode_tab_pen=to_pen{fg=COLOR_YELLOW},
text_mode_label_pen=to_pen{fg=COLOR_WHITE},
lt=to_pen{tile=1005, write_to_lower=true},
lt2=to_pen{tile=1006, write_to_lower=true},
t=to_pen{tile=1007, fg=COLOR_BLACK, write_to_lower=true, top_of_text=true},
rt2=to_pen{tile=1008, write_to_lower=true},
rt=to_pen{tile=1009, write_to_lower=true},
lb=to_pen{tile=1015, write_to_lower=true},
lb2=to_pen{tile=1016, write_to_lower=true},
b=to_pen{tile=1017, fg=COLOR_BLACK, write_to_lower=true, bottom_of_text=true},
rb2=to_pen{tile=1018, write_to_lower=true},
rb=to_pen{tile=1019, write_to_lower=true},
}
local DEFAULT_INACTIVE_TAB_PENS = {
text_mode_tab_pen=to_pen{fg=COLOR_BROWN},
text_mode_label_pen=to_pen{fg=COLOR_DARKGREY},
lt=to_pen{tile=1000, write_to_lower=true},
lt2=to_pen{tile=1001, write_to_lower=true},
t=to_pen{tile=1002, fg=COLOR_WHITE, write_to_lower=true, top_of_text=true},
rt2=to_pen{tile=1003, write_to_lower=true},
rt=to_pen{tile=1004, write_to_lower=true},
lb=to_pen{tile=1010, write_to_lower=true},
lb2=to_pen{tile=1011, write_to_lower=true},
b=to_pen{tile=1012, fg=COLOR_WHITE, write_to_lower=true, bottom_of_text=true},
rb2=to_pen{tile=1013, write_to_lower=true},
rb=to_pen{tile=1014, write_to_lower=true},
}
---------
-- Tab --
---------
Tab = defclass(Tabs, Widget)
Tab.ATTRS{
id=DEFAULT_NIL,
label=DEFAULT_NIL,
on_select=DEFAULT_NIL,
get_pens=DEFAULT_NIL,
}
function Tab:preinit(init_table)
init_table.frame = init_table.frame or {}
init_table.frame.w = #init_table.label + 4
init_table.frame.h = 2
end
function Tab:onRenderBody(dc)
local pens = self.get_pens()
dc:seek(0, 0)
if dfhack.screen.inGraphicsMode() then
dc:char(nil, pens.lt):char(nil, pens.lt2)
for i=1,#self.label do
dc:char(self.label:sub(i,i), pens.t)
end
dc:char(nil, pens.rt2):char(nil, pens.rt)
dc:seek(0, 1)
dc:char(nil, pens.lb):char(nil, pens.lb2)
for i=1,#self.label do
dc:char(self.label:sub(i,i), pens.b)
end
dc:char(nil, pens.rb2):char(nil, pens.rb)
else
local tp = pens.text_mode_tab_pen
dc:char(' ', tp):char('/', tp)
for i=1,#self.label do
dc:char('-', tp)
end
dc:char('\\', tp):char(' ', tp)
dc:seek(0, 1)
dc:char('/', tp):char('-', tp)
dc:string(self.label, pens.text_mode_label_pen)
dc:char('-', tp):char('\\', tp)
end
end
function Tab:onInput(keys)
if Tab.super.onInput(self, keys) then return true end
if keys._MOUSE_L_DOWN and self:getMousePos() then
self.on_select(self.id)
return true
end
end
-------------
-- Tab Bar --
-------------
TabBar = defclass(TabBar, ResizingPanel)
TabBar.ATTRS{
labels=DEFAULT_NIL,
on_select=DEFAULT_NIL,
get_cur_page=DEFAULT_NIL,
active_tab_pens=DEFAULT_ACTIVE_TAB_PENS,
inactive_tab_pens=DEFAULT_INACTIVE_TAB_PENS,
get_pens=DEFAULT_NIL,
key=DEFAULT_NIL,
key_back=DEFAULT_NIL,
}
function TabBar:init()
for idx,label in ipairs(self.labels) do
self:addviews{
Tab{
frame={t=0, l=0},
id=idx,
label=label,
on_select=self.on_select,
get_pens=self.get_pens and function()
return self.get_pens(idx, self)
end or function()
if self.get_cur_page() == idx then
return self.active_tab_pens
end
return self.inactive_tab_pens
end,
}
}
end
end
function TabBar:postComputeFrame(body)
local t, l, width = 0, 0, body.width
for _,tab in ipairs(self.subviews) do
if l > 0 and l + tab.frame.w > width then
t = t + 2
l = 0
end
tab.frame.t = t
tab.frame.l = l
l = l + tab.frame.w
end
end
function TabBar:onInput(keys)
if TabBar.super.onInput(self, keys) then return true end
if self.key and keys[self.key] then
local zero_idx = self.get_cur_page() - 1
local next_zero_idx = (zero_idx + 1) % #self.labels
self.on_select(next_zero_idx + 1)
return true
end
if self.key_back and keys[self.key_back] then
local zero_idx = self.get_cur_page() - 1
local prev_zero_idx = (zero_idx - 1) % #self.labels
self.on_select(prev_zero_idx + 1)
return true
end
end
return _ENV return _ENV