Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add and use Divider widget #4149

Merged
merged 8 commits into from
Jan 7, 2024
1 change: 1 addition & 0 deletions docs/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Template for new versions:

## Lua
- ``dfhack.capitalizeStringWords``: new function, returns string with all words capitalized
- ``widgets.Divider``: linear divider to split an existing frame; configurable T-junction edges and frame style matching

## Removed

Expand Down
55 changes: 52 additions & 3 deletions docs/dev/Lua API.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4728,9 +4728,9 @@ Base of all the widgets. Inherits from View and has the following attributes:
Panel class
-----------

Inherits from Widget, and intended for framing and/or grouping subviews. It is
a good class to choose for your "main screen" since it supports window dragging
and frames.
Inherits from Widget, and intended for framing and/or grouping subviews. Though
this can be used for your "main window", see the `Window class`_ below for a
more conveniently configured ``Panel`` subclass.

Has attributes:

Expand Down Expand Up @@ -4894,6 +4894,55 @@ Subclass of Panel; keeps exactly one child visible.
Selects the specified child, hiding the previous selected one.
It is permitted to use the subview object, or its ``view_id`` as index.

Divider class
-------------

Subclass of Widget; implements a divider line that can optionally connect to
existing frames via T-junction edges. A ``Divider`` instance is required to
have a ``frame`` that is either 1 unit tall or 1 unit wide.

``Divider`` widgets should be a sibling with the framed ``Panel`` that they
are dividing, and they should be added to the common parent widget **after**
the ``Panel`` so that the ``Divider`` can overwrite the ``Panel`` frame with
the appropriate T-junction graphic. If the ``Divider`` will not have
T-junction edges, then it could potentially be a child of the ``Panel`` since
the ``Divider`` won't need to overwrite the ``Panel``'s frame.

If two ``Divider`` widgets are set to cross, then you must have a third 1x1
``Divider`` widget for the crossing tile so the other two ``Divider``\s can
be seamlessly connected.

Attributes:

* ``frame_style``

The ``gui`` ``FRAME`` instance to use for the graphical tiles. Defaults to
``gui.FRAME_THIN``.

* ``interior``

Whether the edge T-junction tiles should connect to interior lines (e.g. the
vertical or horizontal segment of another ``Divider`` instance) or the
exterior border of a ``Panel`` frame. Defaults to ``false``, meaning
exterior T-junctions will be chosen.

* ``frame_style_t``
* ``frame_style_b``
* ``frame_style_l``
* ``frame_style_r``

Overrides for the frame style for specific T-junctions. Note that there are
not currently any frame styles that allow borders of different weights to be
seamlessly connected. If set to ``false``, then the indicated edge will end
in a straight segment instead of a T-junction.

* ``interior_t``
* ``interior_b``
* ``interior_l``
* ``interior_r``

Overrides for the interior/exterior specification for specific T-junctions.

EditField class
---------------

Expand Down
20 changes: 18 additions & 2 deletions library/lua/gui.lua
Original file line number Diff line number Diff line change
Expand Up @@ -908,13 +908,13 @@ GREY_FRAME = {
-- The boundary used by the pre-steam DF screens.
-- deprecated
BOUNDARY_FRAME = {
frame_pen = to_pen{ ch = 0xDB, fg = COLOR_GREY, bg = COLOR_BLACK },
frame_pen = to_pen{ ch = 0xDB, fg = COLOR_GREY, bg = COLOR_BLACK }, -- ch=0xDB is "full block" (█)
title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
}

local BASE_FRAME = {
frame_pen = to_pen{ ch=206, fg=COLOR_GREY, bg=COLOR_BLACK },
frame_pen = to_pen{ ch=206, fg=COLOR_GREY, bg=COLOR_BLACK }, -- ch=206 is "box drawings double vertical and horizontal" (╬)
title_pen = to_pen{ fg=COLOR_BLACK, bg=COLOR_GREY },
inactive_title_pen = to_pen{ fg=COLOR_GREY, bg=COLOR_BLACK },
signature_pen = to_pen{ fg=COLOR_GREY, bg=COLOR_BLACK },
Expand All @@ -924,14 +924,30 @@ local BASE_FRAME = {

local function make_frame(tp, double_line)
local frame = copyall(BASE_FRAME)
-- external horizontal/vertical bars
frame.t_frame_pen = to_pen{ tile=curry(tp, 2), ch=double_line and 205 or 196, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.l_frame_pen = to_pen{ tile=curry(tp, 8), ch=double_line and 186 or 179, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.b_frame_pen = to_pen{ tile=curry(tp, 16), ch=double_line and 205 or 196, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.r_frame_pen = to_pen{ tile=curry(tp, 10), ch=double_line and 186 or 179, fg=COLOR_GREY, bg=COLOR_BLACK }
-- external corners
frame.lt_frame_pen = to_pen{ tile=curry(tp, 1), ch=double_line and 201 or 218, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.lb_frame_pen = to_pen{ tile=curry(tp, 15), ch=double_line and 200 or 192, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.rt_frame_pen = to_pen{ tile=curry(tp, 3), ch=double_line and 187 or 191, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.rb_frame_pen = to_pen{ tile=curry(tp, 17), ch=double_line and 188 or 217, fg=COLOR_GREY, bg=COLOR_BLACK }
-- internal T-junctions
frame.tTi_frame_pen = to_pen{ tile=curry(tp, 21), ch=double_line and 203 or 194, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.bTi_frame_pen = to_pen{ tile=curry(tp, 20), ch=double_line and 202 or 193, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.lTi_frame_pen = to_pen{ tile=curry(tp, 19), ch=double_line and 204 or 195, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.rTi_frame_pen = to_pen{ tile=curry(tp, 18), ch=double_line and 185 or 180, fg=COLOR_GREY, bg=COLOR_BLACK }
-- external T-junctions
frame.tTe_frame_pen = to_pen{ tile=curry(tp, 11), ch=double_line and 203 or 194, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.bTe_frame_pen = to_pen{ tile=curry(tp, 12), ch=double_line and 202 or 193, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.lTe_frame_pen = to_pen{ tile=curry(tp, 13), ch=double_line and 204 or 195, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.rTe_frame_pen = to_pen{ tile=curry(tp, 14), ch=double_line and 185 or 180, fg=COLOR_GREY, bg=COLOR_BLACK }
-- internal horizontal/vertical bars (and cross junction)
frame.v_frame_pen = to_pen{ tile=curry(tp, 5), ch=179, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.h_frame_pen = to_pen{ tile=curry(tp, 6), ch=196, fg=COLOR_GREY, bg=COLOR_BLACK }
frame.x_frame_pen = to_pen{ tile=curry(tp, 4), ch=197, fg=COLOR_GREY, bg=COLOR_BLACK }
return frame
end

Expand Down
67 changes: 67 additions & 0 deletions library/lua/gui/widgets.lua
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,73 @@ function Widget:onRenderFrame(dc, rect)
end
end

-------------
-- Divider --
-------------

Divider = defclass(Divider, Widget)
Divider.ATTRS{
frame_style=gui.FRAME_THIN,
interior=false,
frame_style_t=DEFAULT_NIL,
interior_t=DEFAULT_NIL,
frame_style_b=DEFAULT_NIL,
interior_b=DEFAULT_NIL,
frame_style_l=DEFAULT_NIL,
interior_l=DEFAULT_NIL,
frame_style_r=DEFAULT_NIL,
interior_r=DEFAULT_NIL,
}

local function divider_get_val(self, base_name, edge_name)
local val = self[base_name..'_'..edge_name]
if val ~= nil then return val end
return self[base_name]
end

local function divider_get_junction_pen(self, edge_name)
local interior = divider_get_val(self, 'interior', edge_name)
local pen_name = ('%sT%s_frame_pen'):format(edge_name, interior and 'i' or 'e')
local frame_style = divider_get_val(self, 'frame_style', edge_name)
if type(frame_style) == 'function' then
frame_style = frame_style()
end
return frame_style[pen_name]
end

function Divider:onRenderBody(dc)
local rect, style = self.frame_rect, self.frame_style
if type(style) == 'function' then
style = style()
end

if rect.height == 1 and rect.width == 1 then
dc:seek(0, 0):char(nil, style.x_frame_pen)
elseif rect.width == 1 then
local fill_start, fill_end = 0, rect.height-1
if self.frame_style_t ~= false then
fill_start = 1
dc:seek(0, 0):char(nil, divider_get_junction_pen(self, 't'))
end
if self.frame_style_b ~= false then
fill_end = rect.height-2
dc:seek(0, rect.height-1):char(nil, divider_get_junction_pen(self, 'b'))
end
dc:fill(0, fill_start, 0, fill_end, style.v_frame_pen)
else
local fill_start, fill_end = 0, rect.width-1
if self.frame_style_l ~= false then
fill_start = 1
dc:seek(0, 0):char(nil, divider_get_junction_pen(self, 'l'))
end
if self.frame_style_r ~= false then
fill_end = rect.width-2
dc:seek(rect.width-1, 0):char(nil, divider_get_junction_pen(self, 'r'))
end
dc:fill(fill_start, 0, fill_end, 0, style.h_frame_pen)
end
end

-----------
-- Panel --
-----------
Expand Down
19 changes: 2 additions & 17 deletions plugins/lua/buildingplan/filterselection.lua
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,9 @@ function QualityAndMaterialsPage:init()
},
},
},
widgets.Panel{
view_id='divider',
widgets.Divider{
frame={l=TYPE_COL_WIDTH-1, t=HEADER_HEIGHT, b=FOOTER_HEIGHT+QUALITY_HEIGHT, w=1},
on_render=self:callback('draw_divider'),
frame_style=gui.INTERIOR_FRAME,
},
widgets.Panel{
view_id='quality_panel',
Expand Down Expand Up @@ -437,20 +436,6 @@ function QualityAndMaterialsPage:set_max_quality(idx)
self.dirty = true
end

function QualityAndMaterialsPage:draw_divider(dc)
local y2 = dc.height - 1
for y=0,y2 do
dc:seek(0, y)
if y == 0 then
dc:char(nil, pens.VERT_TOP_PEN)
elseif y == y2 then
dc:char(nil, pens.VERT_BOT_PEN)
else
dc:char(nil, pens.VERT_MID_PEN)
end
end
end

function QualityAndMaterialsPage:onRenderFrame(dc, rect)
QualityAndMaterialsPage.super.onRenderFrame(self, dc, rect)
if self.dirty then
Expand Down
2 changes: 1 addition & 1 deletion plugins/lua/buildingplan/inspectoroverlay.lua
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ InspectorOverlay.ATTRS{
default_enabled=true,
viewscreens='dwarfmode/ViewSheets/BUILDING',
frame={w=30, h=15},
frame_style=gui.MEDIUM_FRAME,
frame_style=gui.FRAME_MEDIUM,
frame_background=gui.CLEAR_PEN,
}

Expand Down
2 changes: 1 addition & 1 deletion plugins/lua/buildingplan/itemselection.lua
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ function ItemSelection:init()
},
widgets.Panel{
frame={l=0, t=3, r=0, b=0},
frame_style=gui.INTERIOR_FRAME,
frame_style=gui.FRAME_INTERIOR,
subviews={
widgets.FilteredList{
view_id='flist',
Expand Down
3 changes: 0 additions & 3 deletions plugins/lua/buildingplan/pens.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ local gui = require('gui')
local textures = require('gui.textures')

GOOD_TILE_PEN, BAD_TILE_PEN = nil, nil
VERT_TOP_PEN, VERT_MID_PEN, VERT_BOT_PEN = nil, nil, nil
HORI_LEFT_PEN, HORI_MID_PEN, HORI_RIGHT_PEN = nil, nil, nil
BUTTON_START_PEN, BUTTON_END_PEN = nil, nil
SELECTED_ITEM_PEN = nil
MINI_TEXT_PEN, MINI_TEXT_HPEN, MINI_BUTT_PEN, MINI_BUTT_HPEN = nil, nil, nil, nil

Expand Down
21 changes: 3 additions & 18 deletions plugins/lua/buildingplan/planneroverlay.lua
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ function PlannerOverlay:init()
local main_panel = widgets.Panel{
view_id='main',
frame={t=1, l=0, r=0, h=14},
frame_style=gui.INTERIOR_MEDIUM_FRAME,
frame_style=gui.FRAME_INTERIOR_MEDIUM,
frame_background=gui.CLEAR_PEN,
visible=self:callback('is_not_minimized'),
}
Expand Down Expand Up @@ -599,10 +599,9 @@ function PlannerOverlay:init()
},
}

local divider_widget = widgets.Panel{
view_id='divider',
local divider_widget = widgets.Divider{
frame={t=10, l=0, r=0, h=1},
on_render=self:callback('draw_divider_h'),
frame_style=gui.FRAME_INTERIOR_MEDIUM,
visible=self:callback('is_not_minimized'),
}

Expand Down Expand Up @@ -687,20 +686,6 @@ function PlannerOverlay:toggle_minimized()
self:reset()
end

function PlannerOverlay:draw_divider_h(dc)
local x2 = dc.width -1
for x=0,x2 do
dc:seek(x, 0)
if x == 0 then
dc:char(nil, pens.HORI_LEFT_PEN)
elseif x == x2 then
dc:char(nil, pens.HORI_RIGHT_PEN)
else
dc:char(nil, pens.HORI_MID_PEN)
end
end
end

function PlannerOverlay:reset()
self.subviews.item1:reset()
self.subviews.item2:reset()
Expand Down