Skip to content

Commit d4c4421

Browse files
committed
devel/dflayout: gui.dflayout fort toolbars demo
1 parent 1f7663b commit d4c4421

File tree

3 files changed

+336
-0
lines changed

3 files changed

+336
-0
lines changed

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Template for new versions:
2727
# Future
2828

2929
## New Tools
30+
- `devel/dflayout`: demo and visually verify gui.dflayout module (fort toolbars)
3031

3132
## New Features
3233

devel/dflayout.lua

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
local gui = require('gui')
2+
local layout = require('gui.dflayout')
3+
local Panel = require('gui.widgets.containers.panel')
4+
local Window = require('gui.widgets.containers.window')
5+
local Label = require('gui.widgets.labels.label')
6+
local Toggle = require('gui.widgets.labels.toggle_hotkey_label')
7+
local List = require('gui.widgets.list')
8+
local utils = require('utils')
9+
10+
--- Demo Control Window and Screen ---
11+
12+
local screen
13+
local DemoScreen
14+
local visible
15+
16+
do -- limit env pollution
17+
local function demo_available(demo)
18+
if not demo.available then return true end
19+
return demo.available()
20+
end
21+
22+
local visible_when_not_focused = true
23+
function visible()
24+
if visible_when_not_focused then return true end
25+
if not screen then return false end
26+
return screen:isActive() and not screen.defocused
27+
end
28+
29+
local DemoWindow = defclass(nil, Window)
30+
DemoWindow.ATTRS{
31+
frame_title = 'dflayout demos',
32+
frame = { w = 39, h = 9 },
33+
resizable = true,
34+
autoarrange_subviews = true,
35+
autoarrange_gap = 1,
36+
}
37+
38+
function DemoWindow:init(args)
39+
self.demos = args.demos
40+
self:addviews{
41+
Toggle{
42+
label = 'Demos visible when not focused?',
43+
initial_option = visible_when_not_focused,
44+
on_change = function(new, old)
45+
visible_when_not_focused = new
46+
end
47+
},
48+
List{
49+
view_id = 'list',
50+
frame = { h = 10, },
51+
icon_pen = COLOR_GREY,
52+
icon_width = 3,
53+
on_submit = function(index, item)
54+
local demo = self.demos[index]
55+
demo.active = demo_available(demo) and not demo.active
56+
self:refresh()
57+
end
58+
},
59+
}
60+
end
61+
62+
local CHECK = string.char(251) -- U+221A SQUARE ROOT
63+
64+
function DemoWindow:refresh()
65+
local choices = {}
66+
for _, demo in ipairs(self.demos) do
67+
local icon
68+
if not demo_available(demo) then
69+
icon = '-'
70+
elseif demo.active then
71+
icon = CHECK
72+
end
73+
table.insert(choices, {
74+
text = demo.text,
75+
icon = icon,
76+
})
77+
end
78+
self.subviews.list:setChoices(choices)
79+
return self
80+
end
81+
82+
DemoScreen = defclass(nil, gui.ZScreen)
83+
function DemoScreen:init(args)
84+
self.demos = args.demos
85+
local function demo_views()
86+
local views = {}
87+
for _, demo in ipairs(self.demos) do
88+
if demo.views then
89+
table.move(demo.views, 1, #demo.views, #views + 1, views)
90+
end
91+
end
92+
return views
93+
end
94+
self:addviews{
95+
DemoWindow{ demos = self.demos }:refresh(),
96+
table.unpack(demo_views())
97+
}
98+
end
99+
100+
local if_percentage
101+
function DemoScreen:render(...)
102+
if visible_when_not_focused then
103+
local new_if_percentage = df.global.init.display.max_interface_percentage
104+
if new_if_percentage ~= if_percentage then
105+
self:updateLayout()
106+
end
107+
end
108+
return DemoScreen.super.render(self, ...)
109+
end
110+
111+
function DemoScreen:postComputeFrame(frame_body)
112+
for _, demo in ipairs(self.demos) do
113+
if demo.active and demo.update then
114+
demo.update()
115+
end
116+
end
117+
end
118+
end
119+
120+
--- Fort Toolbar Demo ---
121+
122+
local fort_toolbars_demo = {
123+
text = 'fort toolbars',
124+
available = dfhack.world.isFortressMode,
125+
}
126+
127+
do
128+
local function fort_toolbars_visible()
129+
return visible() and fort_toolbars_demo.active
130+
end
131+
132+
FortToolbarDemoPanel = defclass(FortToolbarDemoPanel, Panel)
133+
FortToolbarDemoPanel.ATTRS{
134+
frame_style = function(...)
135+
local style = gui.FRAME_THIN(...)
136+
style.signature_pen = false
137+
return style
138+
end,
139+
visible_override = true,
140+
visible = fort_toolbars_visible,
141+
frame_background = { ch = 32, bg = COLOR_BLACK },
142+
}
143+
144+
local left_toolbar_demo = FortToolbarDemoPanel{
145+
frame_title = 'left toolbar',
146+
subviews = { Label{ view_id = 'buttons', frame = { l = 0, r = 0 } } },
147+
}
148+
local center_toolbar_demo = FortToolbarDemoPanel{
149+
frame_title = 'center toolbar',
150+
subviews = { Label{ view_id = 'buttons', frame = { l = 0, r = 0 } } },
151+
}
152+
local right_toolbar_demo = FortToolbarDemoPanel{
153+
frame_title = 'right toolbar',
154+
subviews = { Label{ view_id = 'buttons', frame = { l = 0, r = 0 } } },
155+
}
156+
local secondary_visible = false
157+
local secondary_toolbar_demo = FortToolbarDemoPanel{
158+
frame_title = 'secondary toolbar',
159+
subviews = { Label{ view_id = 'buttons', frame = { l = 0, r = 0 } } },
160+
visible = function() return fort_toolbars_visible() and secondary_visible end,
161+
}
162+
163+
fort_toolbars_demo.views = {
164+
left_toolbar_demo,
165+
center_toolbar_demo,
166+
right_toolbar_demo,
167+
secondary_toolbar_demo,
168+
}
169+
170+
---@param secondary? DFLayout.Fort.SecondaryToolbar.Names
171+
local function update_fort_toolbars(secondary)
172+
-- by default, draw primary toolbar demonstrations right above the primary toolbars:
173+
-- {l demo} {c demo} {r demo}
174+
-- [l tool] [c tool] [r tool] (bottom of UI)
175+
local toolbar_demo_dy = -layout.TOOLBAR_HEIGHT
176+
local ir = gui.get_interface_rect()
177+
---@param v widgets.Panel
178+
---@param frame widgets.Widget.frame
179+
---@param buttons DFLayout.Toolbar.NamedButtons
180+
local function update(v, frame, buttons)
181+
v.frame = {
182+
w = frame.w,
183+
h = frame.h,
184+
l = frame.l + ir.x1,
185+
t = frame.t + ir.y1 + toolbar_demo_dy,
186+
}
187+
local sorted = {}
188+
for _, button in pairs(buttons) do
189+
utils.insert_sorted(sorted, button, 'offset')
190+
end
191+
local buttons = ''
192+
for i, o in ipairs(sorted) do
193+
if o.offset > #buttons then
194+
buttons = buttons .. (' '):rep(o.offset - #buttons)
195+
end
196+
if o.width == 1 then
197+
buttons = buttons .. '|'
198+
elseif o.width > 1 then
199+
buttons = buttons .. '/' .. ('-'):rep(o.width - 2) .. '\\'
200+
end
201+
end
202+
v.subviews.buttons:setText(
203+
buttons:sub(2) -- the demo panel border is at offset 0, so trim first character to start at offset 1
204+
)
205+
end
206+
if secondary then
207+
-- a secondary toolbar is active, move the primary demonstration up to
208+
-- let the secondary be demonstrated right above the actual secondary:
209+
-- {l demo} {c demo} {r demo}
210+
-- {s demo}
211+
-- [s tool]
212+
-- [l tool] [c tool] [r tool] (bottom of UI)
213+
update(secondary_toolbar_demo, layout.fort.secondary_toolbars[secondary].frame(ir),
214+
layout.fort.secondary_toolbars[secondary].buttons)
215+
secondary_visible = true
216+
toolbar_demo_dy = toolbar_demo_dy - 2 * layout.SECONDARY_TOOLBAR_HEIGHT
217+
else
218+
secondary_visible = false
219+
end
220+
221+
update(left_toolbar_demo, layout.fort.toolbars.left.frame(ir), layout.fort.toolbars.left.buttons)
222+
update(right_toolbar_demo, layout.fort.toolbars.right.frame(ir), layout.fort.toolbars.right.buttons)
223+
update(center_toolbar_demo, layout.fort.toolbars.center.frame(ir), layout.fort.toolbars.center.buttons)
224+
end
225+
226+
local tool_from_designation = {
227+
-- df.main_designation_type.NONE -- not a tool
228+
[df.main_designation_type.DIG_DIG] = 'dig',
229+
[df.main_designation_type.DIG_REMOVE_STAIRS_RAMPS] = 'dig',
230+
[df.main_designation_type.DIG_STAIR_UP] = 'dig',
231+
[df.main_designation_type.DIG_STAIR_UPDOWN] = 'dig',
232+
[df.main_designation_type.DIG_STAIR_DOWN] = 'dig',
233+
[df.main_designation_type.DIG_RAMP] = 'dig',
234+
[df.main_designation_type.DIG_CHANNEL] = 'dig',
235+
[df.main_designation_type.CHOP] = 'chop',
236+
[df.main_designation_type.GATHER] = 'gather',
237+
[df.main_designation_type.SMOOTH] = 'smooth',
238+
[df.main_designation_type.TRACK] = 'smooth',
239+
[df.main_designation_type.ENGRAVE] = 'smooth',
240+
[df.main_designation_type.FORTIFY] = 'smooth',
241+
-- df.main_designation_type.REMOVE_CONSTRUCTION -- not used?
242+
[df.main_designation_type.CLAIM] = 'mass_designation',
243+
[df.main_designation_type.UNCLAIM] = 'mass_designation',
244+
[df.main_designation_type.MELT] = 'mass_designation',
245+
[df.main_designation_type.NO_MELT] = 'mass_designation',
246+
[df.main_designation_type.DUMP] = 'mass_designation',
247+
[df.main_designation_type.NO_DUMP] = 'mass_designation',
248+
[df.main_designation_type.HIDE] = 'mass_designation',
249+
[df.main_designation_type.NO_HIDE] = 'mass_designation',
250+
-- df.main_designation_type.TOGGLE_ENGRAVING -- not used?
251+
[df.main_designation_type.DIG_FROM_MARKER] = 'dig',
252+
[df.main_designation_type.DIG_TO_MARKER] = 'dig',
253+
[df.main_designation_type.CHOP_FROM_MARKER] = 'chop',
254+
[df.main_designation_type.CHOP_TO_MARKER] = 'chop',
255+
[df.main_designation_type.GATHER_FROM_MARKER] = 'gather',
256+
[df.main_designation_type.GATHER_TO_MARKER] = 'gather',
257+
[df.main_designation_type.SMOOTH_FROM_MARKER] = 'smooth',
258+
[df.main_designation_type.SMOOTH_TO_MARKER] = 'smooth',
259+
[df.main_designation_type.DESIGNATE_TRAFFIC_HIGH] = 'traffic',
260+
[df.main_designation_type.DESIGNATE_TRAFFIC_NORMAL] = 'traffic',
261+
[df.main_designation_type.DESIGNATE_TRAFFIC_LOW] = 'traffic',
262+
[df.main_designation_type.DESIGNATE_TRAFFIC_RESTRICTED] = 'traffic',
263+
[df.main_designation_type.ERASE] = 'erase',
264+
}
265+
local tool_from_bottom = {
266+
-- df.main_bottom_mode_type.NONE
267+
-- df.main_bottom_mode_type.BUILDING
268+
-- df.main_bottom_mode_type.BUILDING_PLACEMENT
269+
-- df.main_bottom_mode_type.BUILDING_PICK_MATERIALS
270+
-- df.main_bottom_mode_type.ZONE
271+
-- df.main_bottom_mode_type.ZONE_PAINT
272+
[df.main_bottom_mode_type.STOCKPILE] = 'stockpile',
273+
[df.main_bottom_mode_type.STOCKPILE_PAINT] = 'stockpile_paint',
274+
-- df.main_bottom_mode_type.BURROW
275+
[df.main_bottom_mode_type.BURROW_PAINT] = 'burrow_paint'
276+
-- df.main_bottom_mode_type.HAULING
277+
-- df.main_bottom_mode_type.ARENA_UNIT
278+
-- df.main_bottom_mode_type.ARENA_TREE
279+
-- df.main_bottom_mode_type.ARENA_WATER_PAINT
280+
-- df.main_bottom_mode_type.ARENA_MAGMA_PAINT
281+
-- df.main_bottom_mode_type.ARENA_SNOW_PAINT
282+
-- df.main_bottom_mode_type.ARENA_MUD_PAINT
283+
-- df.main_bottom_mode_type.ARENA_REMOVE_PAINT
284+
}
285+
---@return DFLayout.Fort.SecondaryToolbar.Names?
286+
local function active_secondary()
287+
local designation = df.global.game.main_interface.main_designation_selected
288+
if designation ~= df.main_designation_type.NONE then
289+
return tool_from_designation[designation]
290+
end
291+
local bottom = df.global.game.main_interface.bottom_mode_selected
292+
if bottom ~= df.main_bottom_mode_type.NONE then
293+
return tool_from_bottom[bottom]
294+
end
295+
end
296+
297+
fort_toolbars_demo.update = function()
298+
update_fort_toolbars(active_secondary())
299+
end
300+
301+
local secondary
302+
function center_toolbar_demo:render(...)
303+
local new_secondary = active_secondary()
304+
if new_secondary ~= secondary then
305+
secondary = new_secondary
306+
update_fort_toolbars(secondary)
307+
end
308+
return FortToolbarDemoPanel.render(self, ...)
309+
end
310+
end
311+
312+
--- start demo control window ---
313+
314+
screen = DemoScreen{
315+
demos = {
316+
fort_toolbars_demo,
317+
},
318+
}:show()

docs/devel/dflayout.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
devel/dflayout
2+
==============
3+
4+
.. dfhack-tool::
5+
:summary: Demonstrate the DF UI element position calculations available in the gui.dflayout module.
6+
:tags: dev
7+
8+
The demonstrations are GUI-based notations that show the calculated positions of
9+
the supported DF UI elements. The main window includes a list of toggleable
10+
demonstrations.
11+
12+
Usage
13+
-----
14+
15+
::
16+
17+
devel/dflayout

0 commit comments

Comments
 (0)