From 5154eb181af73bc46c4f1e67a6093363bd8797d5 Mon Sep 17 00:00:00 2001 From: Robob27 Date: Sat, 11 Feb 2023 08:44:34 -0500 Subject: [PATCH] Add Tab/TabBar to widgets --- docs/changelog.txt | 3 +- docs/dev/Lua API.rst | 35 +++++++++ library/lua/gui/widgets.lua | 145 ++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index f3a2c0d25..a222a6ee7 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,7 +52,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - ``Gui::any_civzone_hotkey``, ``Gui::getAnyCivZone``, ``Gui::getSelectedCivZone``: new functions to operate on the new zone system - - Units module: added new predicates for: - ``isGeldable()`` - ``isMarkedForGelding()`` @@ -60,8 +59,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Lua - ``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. +- Added ``widgets.TabBar`` and ``widgets.Tab`` (migrated from control-panel.lua) ## Removed diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 1a46cbff1..7ed2d153b 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -5028,6 +5028,41 @@ The widget implements: 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. +:get_cur_page: Returns the current "page". This function does not have a default implementation; you must provide + an implementation that returns the current value of whichever variable your script uses to keep track of the + current "page" (this does not need to relate to an actual Pages widget). +:on_select: Callback executed when a tab is selected. It receives the selected tab index as an argument. Your implementation + should likely update the value of whichever variable your script uses to keep track of the current page. +: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: diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index ffe26936d..7276efefe 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -2091,4 +2091,149 @@ function FilteredList:onFilterChar(char, text) return true 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, + switch_tab_key=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.switch_tab_key and keys[self.switch_tab_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 +end + return _ENV