· 4 years ago · Jul 21, 2021, 05:40 PM
1--Displays text overlays.
2--[[TODO list:
3 Problems with constant/unpaused running:
4 relations-indicator view unit mode, as well as constructmultiz leaving, relied on esc key in dwarfmode cancelling itself.
5 Making the screen on dwarfmode and then trying to toggle priorities also attempts (and fails) to trigger copystock from inherited bindings.
6 Hm, it seems it is usually plain not called on Alt-P in designations menu, must be internal to the script
7 Exiting on esc while in search menu
8
9 [, key][,rkey][,hkey] keycodes to call their respective functions in onInput. [onmouseexit] for cleanup. Refactoring to return element mouse is currently over
10
11 [, hide] function/boolean tag to not render texttable element for a bit
12
13 Allowing "hello" instead of {{"Hello"}}, as well as {"hello", NEWLINE} in case it is toggled above
14
15 Scrolling implementation/clipping control
16
17 Flattening out indicator-screens by passing text,body and frame tables to render for the below element after previous ones, and dismissal removing them from the stack.
18
19 Extending screens other than gui.Framedscreen or gui.Screen, in which case it disregards dynamic_text_table handling in rendering and input (inheriting those instead)
20 ; potentially using class arguments. Basically, warmist's gui tutorial should be followable with just 1 line change.
21]]
22local helptext = [===[
23indicator_screen
24================
25Module containing getScreen & isWideView & setCommandContext &
26getCommandContext functions, to be called with
27dfhack.script_environment(gui/indicator_screen)
28 version 1.64
29 getScreen arguments:
301: A table with 1 to N tables ([] values being optional) of
31 {text[, color][, indent][,onclick ][, notEndOfLine]
32 [, onrclick][, onhovertext][, onhoverfunction][, onrelease]
33 [, onrrelease]},
34 where text is text or function that returns text, color is
35 number/pen or function that returns it, onclick and onrclick
36 are functions to be called when the text is (right-)clicked,
37 on(r)release is functions to be called after finishing click,
38 notEndOfLine being true continues next text on same line,
39 onhovertext being a table with text and color used on hover,
40 onhoverfunction is function called on hover and indent is nr
41 of indented spaces. Table can be changed after creation,
42 and color/onclick+/onhoverfunction can also be applied to
43 whole table or screen.
44
45 In 1..N tables, key can be omitted for index in that order.
46
472: Optional table that optionally contains x, y, width and
48 height for text display. Places the text by upper left
49 corner at x,y. Calculates height and width from 1 if not set.
50
513: Optional table like the above for frame boundary.
52 If width or height are not set, uses DF's height and width.
53 If left out, then frame boundary is not drawn.
54
55Returns a gui.FramedScreen element with additional functions:
56 adjustDims(affectsText,x,y,w,h) - meant for resizing, 2.{} ok
57 onHelp() - dismisses the screen
58 removeFromView([dissmiss_when]) - [dismisses in n ticks]
59 hide()
60 setDesiredContext(context_string or function)
61 getDesiredContext() - returns what was set above
62 onHide() does nothing by default, called on context exit.
63 inheritBindings()
64 clearBindings()
65 onDestroy()
66 onGetSelectedJob()
67 onGetSelectedUnit()
68 preInput(keys) empty, called before onInput
69 onInput(keys) handles focus changes, key limiter, cursor
70 postInput(keys) empty, called after onInput
71 onRenderBody()
72 onRenderFrame()
73 onResize()
74 onShow() handles inheritance and stealth
75 setRects(textrect, framerect)
76 placeOnTop([screen])
77
78And public values affecting behaviour:
79 preInput: Optional onInput function called before onInput.
80 postInput: Optional onInput function called after onInput.
81 binding_inherit: valid values "hook", "keybinding", "none".
82 key_timeout: wait between accepting keypresses(default 0)
83 signature: false will not paint, nil will use light gray
84 signature_text: Text used as signature if there's no frame
85 dismiss_on_zoom: nil will dismiss parent and adopt grandpa
86 dismiss_ordered: used to dismiss screen on viewscreen change.
87 nonblinky_dismiss: default evaluates by fps/gfps>1+#screens.
88 onStateChangeKey: dfhack.onStateChange function key for ^.
89
90 setCommandContext arguments:
911. command: valid dfhack command that doesn't give an error
92 (ones that do are removed from the list)
932. context: string or function that is passed current focus to
94 The command is run when focus changes from not matching to
95 matching, or if function(context) becomes true
96 ]===]
97
98indicator_screen_version = 1.64
99
100local gui = require 'gui'
101
102function placeOnTop(domScreen)
103 --Takes a domScreen, and places it on top of currently topmost screen.
104 if domScreen then
105 --don't accept nothing
106 local subScreen = dfhack.gui.getCurViewscreen()
107 if domscreen ~= subScreen then --can't top self
108 domScreen.parent=subScreen
109 subScreen.child=domScreen
110 end
111 end
112end
113
114local args = {...}
115
116if args and args[1] == "help" then print(helptext) return
117elseif args and args[1] == "execute_hook" then --Don't need to initialize other variables on hooking
118local execute_helptext = [[
119indicator_screen execute_hook
120=============================
121
122Utility command for indicator-screen module.
123Temporarily removes bottommost scren with indicator_screen focus path,
124launchs a command given, then adds the screen back on top after that
125
126Usage format:
127 gui/indicator_screen execute_hook <valid command to keybind>
128
129 Intended to be used with keybinding so that the command is executed
130 in context below bottommost indicator screen.
131]]
132if #args == 1 or --No command given,
133 (tostring(args[2]):find("help") and (#(tostring(args[2]))<6)) or --asking for help,
134 (tostring(args[2]):find("?") and (#(tostring(args[2]))<3)) then -- or being confused?
135 dfhack.print(execute_helptext) -- Explain!
136 return
137else
138 local gui = require 'gui'
139
140 function getIndicatorScreenChainBase(targetscreen)
141 --Finds and returns the screen farthest from parent screen whose focus path includes indicator_screen
142 --Not closest to root screen, as that leads to weirdness with screen>unitlist>screen>manipulator
143 if targetscreen and --Recursive function may run into nil
144 dfhack.gui.getFocusString(targetscreen):find("indicator_screen") and --the topmost is what we want
145 not dfhack.gui.getFocusString(targetscreen.parent):find("indicator_screen") --and it's parent is not
146 then
147 return targetscreen
148 elseif targetscreen and targetscreen.parent then --Attempting to call nil.parent will crash this
149 return getIndicatorScreenChainBase(targetscreen.parent)
150 end
151 -- if there's no parent, it didn't find an indicator screen and returns nil
152 end
153
154 local lowestIndicatorScreen = getIndicatorScreenChainBase(dfhack.gui.getCurViewscreen())
155 --only if we find any indicator screens do we execute the command
156 if lowestIndicatorScreen then
157 executing_hook = true --to prevent race conditions with context-bound calls and screens
158 lowestIndicatorScreen.parent.child = nil
159 -- Return execution environment to pre-screen context before execution
160 dfhack.timeout(1, "frames", function() -- Wait a bit for dfhack to catch up
161 dfhack.run_command(table.concat(args, " ",2)) --Execute command in base environment
162 --Needs spaces in-between arguments
163 placeOnTop(lowestIndicatorScreen) --Place the indicator screen back on top.
164 executing_hook = false
165 end)
166 return
167 end
168end
169
170end
171
172function isWideView()
173 -- Tells if one has tabbed the view mode to wide or not with true or false.
174 -- In other cases, such as not even thin, returns nil.
175 local map_width, menu_width
176 if("number"==type(df.global.ui_menu_width)) then
177 map_width = df.global.ui_area_map_width
178 menu_width = df.global.ui_menu_width
179 elseif(#df.global.ui_menu_width == 2) then
180 map_width = df.global.ui_menu_width[1]
181 menu_width = df.global.ui_menu_width[0]
182 else
183 print("gui/indicator-screen can't handle this menu width configuration")
184 return true
185 end
186 if map_width == 3 and menu_width == 1 then
187 return true
188 elseif map_width == 2 and
189 (menu_width == 1 or
190 (--[==[dfhack.screen.readTile((df.global.gps.dimx-56),2).ch == 219 and--]==]--
191 -- To check the case where menu should be gone but active sidebar keeps it visible
192 -- Deprecated: Introduces lag.
193 (not dfhack.gui.getCurFocus():find("dwarfmode/Default")) and
194 -- To prevent failing on Trade depots in NE corner, though a rogue screen could override this.
195 menu_width == 2)
196 ) --or (
197 --(not dfhack.gui.getCurFocus():find("dwarfmode/Default"))
198 --and map_width == 3 and menu_width == 3 )
199 then
200 return true
201 elseif (map_width == 3 and menu_width == 3 and dfhack.gui.getCurFocus():find("dwarfmode/Default") ) or (
202 map_width == 2 and menu_width == 2 ) then
203 return nil
204 else
205 return false
206 end
207end
208
209-- Utility function
210-- Due dynamic tree structure, don't know where desired viewscreen is? Retrieve it
211function getBottomMostViewscreenWithFocus(text, targetscreeni)
212 --Finds and returns the screen closest to root screen whose path includes text
213 local targetscreen = targetscreeni or df.global.gview.view.child
214 --if the target screen wasn't included, starts looking from the bottom
215 if targetscreen and
216 dfhack.gui.getFocusString(targetscreen):find(text) then
217 return targetscreen
218 elseif targetscreen and targetscreen.child then --Attempting to call nil.child will crash this
219 return getBottomMostViewscreenWithFocus(text, targetscreen.child)
220 end
221 -- if there's no child, it didn't find a screen with text in focus and returns nil
222end
223
224timeoutid = timeoutid or -1
225executing_hook = executing_hook or false
226context_bound_indicator_screens = context_bound_indicator_screens or {true}
227context_called_commands = context_called_commands or {true}
228
229local function getLen(ptable) local l=0 for _,__ in pairs(ptable) do l=l+1 end return l end
230
231local function repeatfunction(rfunc)
232 --repeats a function every 10 frames, until the function returns false or nil
233 local myrfunc
234
235 myrfunc = function ()
236 timeoutid = dfhack.timeout(10, "frames", function(options)
237 if rfunc() then myrfunc() end
238 end)
239 end
240
241 myrfunc()
242end
243
244local function printerr(text)
245 dfhack.color(COLOR_RED)
246 print(text)
247 dfhack.color(COLOR_RESET)
248end
249
250local function contextFits(focus, context)
251 if type(context) == "string" then return focus:find(context)
252 elseif type(context) == "function" then return context(focus) end
253end
254
255
256 focus,viewscreen = dfhack.gui.getCurFocus(), dfhack.gui.getCurViewscreen()
257 oldFocus,oldViewscreen = oldFocus or "", oldViewscreen or df.global.gview.view.child --otherwise each function calls generates their own
258 local errorfree, value
259local function timeoutfunc()
260 if executing_hook then return true --Skip checks during hooks
261 else
262 viewscreen = dfhack.gui.getCurViewscreen()
263 focus = dfhack.gui.getCurFocus()
264 end
265 --with dismiss_on_zoom set to false, the grandparented screen keeps its focus
266 --this results in it's focus matching desired focus, and thus it is not removed
267 --the viewscreen will also be constant since the viewscreen is the screen I just grandparented
268 if (context_bound_indicator_screens[1] ~= nil and context_called_commands[1] ~= nil) then
269 dfhack.timeout_active(timeoutid,nil) --cancel the timer if there's nothing to time
270 timeoutid = -1 --set timeout callback to default
271 return false --stop repeating
272 elseif oldFocus ~= focus then
273 for screen, screenfocus in pairs(context_bound_indicator_screens) do --must store individual screens as keys due focus overlaps
274 --TODO remove pcall in favour of adding focus/command back in after it completes?
275 if type(screen) == "table" then --for cases where there are no screen but are context-called commands
276 errorfree,value = pcall(function() --it is possible that one particular screen is faulty, which could crash all screens being hold
277 if not screen.hidden and --only hiding unhidden screens
278 screen._native ~= nil and --that are active
279 contextFits(oldFocus, screenfocus) and --and were being displayed (Unnecessary? If they weren't hidden, I want to hide them anyway)
280 not (contextFits(focus,screenfocus) and viewscreen == oldViewscreen) then
281 --and either shouldn't be anymore or should be redisplayed on new viewscreen
282 --possible issue with keybinds and prioritizelast: does the inheritance change when focus changes?
283
284 --hide screens that went out of focus, if they've been shown
285 -- print("am hiding a screen...", focus, viewscreen, viewscreen.breakdown_level)
286 screen:hide()
287 --hiding a screen changes focus/screen
288 focus = dfhack.gui.getCurFocus()
289 viewscreen = dfhack.gui.getCurViewscreen()
290 end
291 --display new ones
292 if screen.hidden and
293 contextFits(focus,screenfocus) and --show a hidden screen fitting in context
294 viewscreen.breakdown_level == 0 then --but not on top of something that's leaving
295 -- print("am showing a screen...", focus, viewscreen, viewscreen.breakdown_level)
296 if screen:isShown() then
297 screen:placeOnTop()
298 else
299 screen:show()
300 end
301 --showing a screen changes focus/screen too
302 focus = dfhack.gui.getCurFocus()
303 viewscreen = dfhack.gui.getCurViewscreen()
304 end
305 if screen.hidden and
306 contextFits(focus,screenfocus) and --show a hidden screen fitting in context
307 viewscreen.breakdown_level ~= 0 then --but not on top of something that's leaving
308 --printerr("Would have placed inappropriate screen here")--never seen so far
309 end
310
311
312 end)
313 if not errorfree then
314 context_bound_indicator_screens[screen] = nil --don't keep iterating over erronous screen
315 printerr("couldn't handle desired context of " .. tostring(screen.focus_path))
316 printerr(value)
317 end
318 end
319 end
320 if viewscreen.breakdown_level == 0 then -- no running commands on leaving viewscreens
321 for command, context in pairs(context_called_commands) do
322 if type(command) == "string" then --for cases where are no commands but are screens
323 errorfree,value =pcall(function()
324 if contextFits(focus,context) and not contextFits(oldFocus,context) then
325 -- print("running", command, focus, oldFocus)
326 dfhack.run_command(command)
327 end
328 --often used to place screens so focus/viewscreen can change afterwards
329 focus = dfhack.gui.getCurFocus()
330 viewscreen = dfhack.gui.getCurViewscreen()
331 end)
332 if not errorfree then
333 context_called_commands[command] = nil --don't keep iterating over erronous command
334 printerr("couldn't handle desired command " .. tostring(command))
335 printerr(value)
336 end
337 end
338 end
339 end
340 oldFocus = focus
341 oldViewscreen = viewscreen
342 end
343 return true --continue repeating
344end
345
346function setCommandContext(command, context)
347 if (not command) then print("Inappropriate setCommandContext values") return end
348 if context ~= nil and type(context) ~= "string" and type(context) ~= "function" then print("Inappropriate setCommandContext context") return end
349 context_called_commands[command] = context
350 if context==nil then
351 if getLen(context_called_commands) == 0 then
352 context_called_commands[1]=true
353 end
354 else
355 context_called_commands[1]=nil
356 if timeoutid == -1 then
357 repeatfunction(timeoutfunc)
358 end
359 end
360end
361
362function getCommandContext(command)
363 return context_called_commands[command]
364end
365
366
367if timeoutid == -1 and #context_bound_indicator_screens == 0 and #context_called_commands == 0 then
368 --Should never be called as when the second is 0 the first should never be -1 and vice-versa
369 printerr("Set up repeat on timeoutid -1; how come this was possible to happen???")
370 repeatfunction(timeoutfunc)
371end
372
373local function containsTable(a,b)
374 for key, value in pairs(b) do
375 if type(a[key]) == "table" and type(value) == "table" then
376 if not containsTable(a[key], value) then return false end
377 elseif type(a[key]) == "function" and type(value) == "function" then
378 --I must not call these willnilly, that's how I ended up 9 onclick calls...
379 --if a[key]() ~= value() then return false end
380 if string.dump(a[key]) ~= string.dump(value) then return false end
381 elseif a[key] ~= value then
382 return false
383 end
384 end
385 return true
386end
387
388local function equalTables(a,b)
389 --print("running two containstable checks!")
390 return containsTable(a,b) and containsTable(b,a)
391end
392--[[
393function redirectTable(access, destination)
394 for i,v in pairs(access) do
395 access[i] = nil
396 destination[i] = v
397 end
398
399 setmetatable(access,{
400 __index = destination,
401 __newindex = destination
402 })
403end--Failed to work as desired]]
404
405--Adding dwarfmode widgets section
406widget_screens = widget_screens or {}
407
408local function patchdwarmonitor(widget_table, widget_str)
409 --injects a widget table and widget str into dwarfmonitor.json and dwarfmonitor.lua
410 --returns true if injection successful or done before
411 if not (widget_table or widget_str or widget_table.type) then qerror ("Inappropriate params to pathdwarfmode") end
412 local dmPath = dfhack.getDFPath() .. "/hack/lua/plugins/dwarfmonitor.lua"
413 local json = require('json')
414 local dmJson = json.decode_file('dfhack-config/dwarfmonitor.json')
415 for i, widget in pairs(dmJson.widgets) do if widget.type == widget_table.type then return true end end
416 table.insert(dmJson.widgets,widget_table)
417 json.encode_file(dmJson,'dfhack-config/dwarfmonitor.json')
418 local dmFile, err = io.open(dmPath, "r")
419 if err then return false end
420 local lines = dmFile:read("*a")
421 local insertpoint = "\nreturn _ENV"
422 if lines:find(widget_str) then return true end
423 lines = lines:gsub(insertpoint, widget_str .. insertpoint)
424 dmFile:close()
425 dmFile, err = io.open(dmPath, "w")
426 if err then return false end
427 dmFile:write(lines)
428 dmFile:flush()
429 dmFile:close()
430 return true
431end
432
433local indiWidgetTable = {--need to have a way to add/update with individual indis
434 ['type'] = "indicator_screen",
435 ['x'] = 0,
436 ['y'] = 0
437}
438
439indiWidget = defclass(indiWidget)
440
441function indiWidget:init(opts)
442 self.opts = opts
443 for index, screenEl in ipairs(self.opts) do
444 if type(screenEl.onRenderBody) == "string" then
445 screenEl.onRenderBody = load(screenEl.onRenderBody)
446 end
447 end
448end
449
450function indiWidget:render()
451 --Hold on, this will not work due dwarfmonitor being on a different lua state; there is no screenEl.frame_body
452 for index, screenEl in ipairs(self.opts) do
453 local passSelf = {['frame_body'] = screenEl.frame_body, widget = true}
454 screenEl.onRenderBody(passSelf, gui.Painter{view_rect = screenEl.frame_body})
455 end
456end
457
458local widget_str = "pcall(function() Widget_indicator = dfhack.script_environment('gui/indicator_screen').indiWidget end)"
459
460twbt_is_enabled = twbt_is_enabled
461if twbt_is_enabled == nil and dfhack.timeout then twbt_is_enabled = dfhack.run_command_silent('plug twbt'):match('enabled') and true or false end
462
463 local dims = dfhack.gui.getDwarfmodeViewDims()
464if twbt_is_enabled and not xratioNr and dfhack.timeout then
465 dfhack.timeout(20, "frames", function()
466 dims = dfhack.gui.getDwarfmodeViewDims() --dims isn't properly set until slightly after startup with twbt
467 local function round(number) return (number%1)>=0.5 and math.ceil(number) or math.floor(number) end
468 local text_x = df.global.init.display.windowed == 0 and df.global.init.font.small_font_dispx or df.global.init.font.large_font_dispx
469 local text_y = df.global.init.display.windowed == 0 and df.global.init.font.small_font_dispy or df.global.init.font.large_font_dispy
470 local graphics_x = round(text_x*((dims.menu_x1 > 0 and dims.menu_x1 or df.global.gps.dimx)-2)/dims.map_x2)
471 local graphics_y = round(text_y*dims.y2/dims.map_y2)
472
473 xratioNr = graphics_x/text_x --only one that matters usually
474 yratioNr = graphics_y/text_y
475 end)
476end
477
478local function getMousePos()
479 local mouse_x, mouse_y = df.global.gps.mouse_x, df.global.gps.mouse_y
480 if mouse_x < 0 then return mouse_x, mouse_y end
481 if mouse_x > dims.map_x2 then return mouse_x, mouse_y end
482 if twbt_is_enabled and amRenderingDwarfmode() and xratioNr then
483 return mouse_x*xratioNr, mouse_y*yratioNr
484 else
485 return mouse_x, mouse_y
486 end
487end
488
489is_poking = 0
490
491 local enabler, gps = df.global.enabler, df.global.gps
492local function disposablescreenpoke()
493 --for twbt compatibility, only for inputs
494 --cant be triggered from dwarfmode rendering if there's no lua screen present
495 if (enabler.mouse_lbut==1 or enabler.mouse_lbut_down==1) then is_poking = 1 else is_poking = 2 end
496 local dscr = gui.Screen{frame_width=1, frame_height = 1}
497 executing_hook = true
498 dscr:show()
499 dfhack.timeout(1,'frames', function() dscr:dismiss() executing_hook = false dfhack.timeout(1+math.ceil(enabler.fps_per_gfps), "frames", function() if (enabler.mouse_lbut==0 and enabler.mouse_rbut==0 and enabler.mouse_lbut_down==0 and enabler.mouse_rbut_down==0) then is_poking = -1*is_poking end end) end)
500end
501
502has_poked = has_poked
503
504local function repeatpoke()
505 local pokeRfunc
506 pokeRfunc = function ()
507 if gps.mouse_x < 0 and (enabler.mouse_lbut==1 or enabler.mouse_rbut==1 or enabler.mouse_lbut_down==1 or enabler.mouse_rbut_down==1) then --mouse button pressed down on sidebar
508 disposablescreenpoke()
509 timeoutid = dfhack.timeout(20+math.ceil(enabler.fps_per_gfps), "frames", function()
510 is_poking = 0
511 pokeRfunc()
512 end)
513 else
514 dfhack.timeout(1, "frames", function()
515 pokeRfunc()
516 end)
517 end
518 end
519 pokeRfunc()
520end
521
522if twbt_is_enabled and not has_poked and dfhack.timeout then
523 has_poked = true
524 dfhack.timeout(100, "frames", function() repeatpoke() end)
525end
526
527 local function getBottomMostVanillaViewscreen(targetscreeni)
528 local targetscreen = targetscreeni or df.global.gview.view.child.child
529 if targetscreen and
530 not dfhack.gui.getFocusString(targetscreen):find("dfhack") then
531 return targetscreen
532 elseif targetscreen and targetscreen.child then --Attempting to call nil.child will crash this
533 return getBottomMostVanillaViewscreen(targetscreen.child)
534 end
535 end
536function amRenderingDwarfmode()
537 --[==[
538 possible cases:
539
540 dwarfmode (-> lua screen) -> another vanilla viewscreen (always 100% coverage) -> indicator screen
541 Covered it it can find non-dfhack, non-dwarfmode viewscreen
542
543
544 dwarfmode -> indi (-> some other screen)
545 there isn't a non-dfhack-nondwarfmode viewscreen
546 if indi is rendered, dwarfmode is rendered
547 if some other screen is fullscreen, indi isn't rendered
548
549 dwarfmode -> some other dfhack fullscreen -> indi
550 gives erronous true result
551 --]==]
552 if getBottomMostVanillaViewscreen() then
553 return false
554 else
555 return true
556 end
557end
558
559function getScreen(dynamic_text_table, simpleTextRect, simpleFrameRect)
560--[[ :dynamic_text_table: a {} with 1..n iterated integer keys
561 each points to {text = valueA, color = valueB}
562 values can be functions
563 :simpleTextRect: an optional {} with optional x, y, width, height number values
564 if not set, uses window dimensions
565 :simpleFrameRect: like simpleTextRect
566 if not set, only places signature on closest vanilla frame border.
567
568 Returns not yet shown screen
569]]--
570if not dynamic_text_table or type(dynamic_text_table) ~= "table" then qerror('gui/indicator_screen needs a dynamic text table') end
571--metatableing to support 1-8 alternatives
572function setMetatableForDynamic_text_table(dynamic_text_table)
573 --TODO: check for old metatable and adopt it to new if it isn't indice_mt
574 indicetable = indicetable or {
575 "text",
576 "color",
577 "indent",
578 "onclick",
579 "notEndOfLine",
580 "onrclick",
581 "onhovertext",
582 "onhoverfunction",
583 "onrelease",
584 "onrrelease"
585 }
586
587 indicetableInv = indicetableInv or (require 'utils').invert(indicetable)
588 indice_mt = indice_mt or {
589 __index=function(t,k)
590 if indicetableInv[k] then return rawget(t,indicetableInv[k]) --text to 1
591 elseif indicetable[k] then return rawget(t,indicetable[k]) end
592 end, --1 to text
593 __newindex = function(t,k,v) --if alternative is already set, modify that instead of setting new value
594 if indicetable[k] and t[indicetable[k]] then --assigning nr to text table
595 t[indicetable[k]] = v
596 elseif indicetableInv[k] and t[indicetableInv[k]] then --assigning text to numerical table
597 t[indicetableInv[k]] = v
598 else
599 rawset(t,k,v)
600 end
601 end
602 }
603 for indice, texttable in pairs(dynamic_text_table) do
604 if type(texttable) == "table" then
605 setmetatable(texttable, indice_mt)
606 if texttable.onhovertext then
607 setmetatable(texttable.onhovertext, indice_mt)
608 end
609 end
610 end
611
612end
613 --For allowing replacing of old dtts in old, memoized screens returned by getScreen.
614 --Would prefer to set both to same pointers though
615 --so that both old and new one would be functional instances.
616 --Tried some attempts with metatables but it can't succeed due pairs not working with metatable
617memoizedscreens = memoizedscreens or setmetatable({},{__mode="kv"}) --a table with weak keys and values
618
619setMetatableForDynamic_text_table(dynamic_text_table)
620
621for mscreen, dtt in pairs(memoizedscreens) do
622 if dtt == dynamic_text_table then --literally same table
623 return mscreen, true
624 elseif equalTables(dynamic_text_table, dtt) then
625 mscreen:replace_dynamic_text_table(dynamic_text_table)
626 memoizedscreens[mscreen] = dynamic_text_table
627 return mscreen, true
628 end
629end
630
631
632function getLongestLength (ltable, lkey)
633 -- Single-Utility function, returns longest length of a given key or notEndOfLine keychain in a table.
634 -- Runs functions once to get the length of their answers.
635 local len,clen = 0,0
636 local twostring=tostring --function outside iterator
637 for i=1, #ltable do
638 if type(ltable[i][lkey]) == "function" then
639 clen = clen + #((ltable[i][lkey])())
640
641 else
642 clen = clen + #(twostring(ltable[i][lkey])) --convert to string, in case it is a number
643 end
644 if i ~= #ltable and (ltable[i].notEndOfLine or (ltable.notEndOfLine and ltable[i].notEndOfLine~=false) )then
645 --doNothing, unless it is the last element
646 elseif clen > len then
647 len = clen
648 clen = 0
649 else
650 clen = 0
651 end
652 end
653 return len
654end
655
656function getHeight(ltable)
657 --single-use utility function to get height of text used.
658 local height = #ltable
659 for i=1, #ltable do
660 if i~= #ltable and (ltable[i].notEndOfLine or ltable.notEndOfLine and ltable[i].notEndOfLine ~= false) then
661 height = height - 1
662 end
663 end
664 return height
665end
666
667indicatorScreen = defclass(indicatorScreen, gui.FramedScreen)
668
669local simpleTextRect = simpleTextRect or {}
670
671local screen = indicatorScreen{
672 frame_style = gui.BOUNDARY_FRAME, -- not necessary, but indicatoror will default anyway
673 --frame_title = '', -- Adds clutter and is not useful for most inline indicators. Can be set later
674 frame_width = simpleTextRect.width or getLongestLength(dynamic_text_table, "text"),
675 --function, as lenghts shorter clip it away, while longer ones create unnecesary black space
676 frame_height = simpleTextRect.height or getHeight(dynamic_text_table),
677 frame_inset = simpleFrameRect and 1 or -1
678}
679
680memoizedscreens[screen] = dynamic_text_table
681
682simpleTextRect.width = simpleTextRect.width or screen.frame_body.width
683simpleTextRect.height = simpleTextRect.height or screen.frame_body.height
684 --all globals are shared between screens and remembered between calls, thus must associate values with sreen or use local
685
686local unused, nr = dfhack.gui.getCurFocus():gsub("dfhack","hack")
687screen.nonblinky_dismiss = df.global.enabler.fps_per_gfps < (2+(nr or 0)) and true or false
688unused, nr = nil, nil
689 --Issue nbA: If one creates multiple screens at once and then later shows them at once, may blink.
690 --But if one sets this value on show, then user will be unable to modify it if desired,
691 --as the relevant necessary function to make it work is also called then and only then.
692 --Issue nbB: Inaccurate if fps isn't capped (possible once this thing can unpause)
693 --Issue nbC: if set to false, .dismiss_ordered does nothing.
694
695screen.signature = true
696-- only affects things if inset is -1
697-- other options being nil (light gray on light gray) and false(not painted)
698
699screen.binding_inherit = "hook"
700--[[ Controls if and how the created screen will react to keybindings active in the context it is called
701 Purpose is to provide better interaction with fourth party plugins and scripts
702
703 Possible options are hook, hook_bottom, keybinding, none=nil=false
704
705 none: doesn't look at the bindings set to viewscreen beneath, though focus path is set according to it.
706
707 keybinding: Asks keybinding for all keybindings, and then adds binding in parent viewscreen to its context
708 for as long as the screen is up. Doesn't always work even when it could, for scripts often check
709 context they're called in before running.
710
711 hook: Asks keybinding for all keybindings, and when it gets a combination applicable in parent screen removes
712 itself as child from parent screen, calls the command with dfhack, waits a frame, and readds itself.
713 After 1 frame, adds this screen back on top of topmost viewscreen.
714 ]]--
715
716screen.dismiss_on_zoom = true
717 -- Controls whether screen is kept or dismissed on zooming or ascending (with Escape) out of a menu.
718 -- Default is true, just as new vanilla screens are placed on top of created screens.
719 -- False would mimic how binding_inherit hook results in this screen being placed on top of keybinded screen.
720function createRect(inputRect)
721--Takes nil or partial or complete table of x, y, width, height values
722-- negative x,y will be wrapped
723--Returns table with all location values screen uses.
724 local simpleRect = inputRect or {}
725 --Either use passed simple rectangle or create new one as big as screen.
726 local newRect = {}
727 newRect.width = simpleRect.width or df.global.gps.dimx
728 newRect.height = simpleRect.height or df.global.gps.dimy
729 --if values are missing, set them to full screen width
730
731 newRect.x1 = simpleRect.x and (simpleRect.x > -1 and simpleRect.x or (df.global.gps.dimx+simpleRect.x)) or 0
732 newRect.y1 = simpleRect.y and (simpleRect.y > -1 and simpleRect.y or (df.global.gps.dimy+simpleRect.y)) or 0
733 --wrapping around the screen, and zeroes if not initialized
734 newRect.x2=newRect.x1+newRect.width - 1
735 newRect.y2=newRect.y1+newRect.height - 1
736 newRect.clip_x1=newRect.x1
737 newRect.clip_x2=newRect.x2
738 newRect.clip_y1=newRect.y1
739 newRect.clip_y2=newRect.y2
740 newRect.x=newRect.x1
741 newRect.y=newRect.y1
742 newRect.hgap=newRect.height -- if both hgap and wgap are 0, it'll set the screen to black.
743 newRect.wgap=newRect.width -- that's a little undesirable loop if I'm creating, not dismissing
744 if (newRect.hgap == 0) and (newRect.hgap == newRect.wgap) then newRect.hgap = 1 end
745 -- preventing it allows zero-size frames without blanking the screen.
746
747 return newRect
748end
749
750
751function set_rect(target, corrector)
752 -- sets all corrector key values found in targetrect to corrector ones
753 -- This softness is useful for avoiding table bloat or modifying native userdata
754 for key, value in pairs(corrector) do
755 if target[key] then target[key] = value end
756 end
757end
758
759local textrect = createRect(simpleTextRect)
760local framerect = simpleFrameRect and createRect(simpleFrameRect) or false
761
762 local zeroRect = {width = 0, height = 0, x = 0, y = 0} --Declared for setRects to prevent table proliferation
763function screen:setRects(textrect, framerect)
764 --sets screen's dimension values to given input rects
765set_rect(self.frame_body, textrect)
766if framerect then
767 set_rect(self.frame_rect, framerect)
768else
769 set_rect(self.frame_rect, createRect(zeroRect))
770 --Even without instructions for frame body, it is still set.
771 --Also dfhack indicator depends on it existing somewhere, so can't just skip drawing frame.
772end
773end
774
775
776 local inheritedbindings = {}
777 local modifiertable = {}
778 --(Static) table for all possible keybinding modifiers
779 table.insert(modifiertable, "") --nothing too
780 table.insert(modifiertable, "Ctrl-")
781 table.insert(modifiertable, "Alt-")
782 table.insert(modifiertable, "Shift-")
783 table.insert(modifiertable, "Ctrl-Alt-")
784 table.insert(modifiertable, "Ctrl-Shift-")
785 table.insert(modifiertable, "Shift-Alt-")
786 table.insert(modifiertable, "Ctrl-Alt-Shift")
787function screen:inheritBindings()
788 --Asks keybinding for all keybindings in parent context, and binds them to itself.
789 if self.binding_inherit and not (self.binding_inherit:find("none")) then
790local bindinglist = {}
791local context = dfhack.gui.getFocusString(self._native.parent)
792
793function insertAllKeymods(key, searched_context)
794 --Takes a single key (e.g. K) and checks if it with any and all modifiers applies to searched context
795 --If applies, adds that to binding list
796 for i = 1, #modifiertable do
797 local keybinding_text = dfhack.run_command_silent("keybinding list " .. modifiertable[i] .. key)
798 if not keybinding_text:find("No bindings") then --No bindings, ergo nothing to do
799 for single_bind in keybinding_text:gmatch("%s%s[^%s].-\n") do --Split up the list and iterate it.
800 local received_context = single_bind:match("@.-:") --Ask what context given command uses
801 if not received_context or --general, DF-wide context...But I need to still insert it to get execute_hook on it
802 searched_context:find(received_context:sub(2,-2)) then --context matches same or lower screen
803
804 table.insert(bindinglist, modifiertable[i] .. key --modifier-key combination
805 .. (received_context and single_bind:sub(3,-2)--binding has context (matters for @_:)
806 :gsub("gui/indicator_screen execute_hook ","")--prevents chaining hooks, which can make the binding nonfunctional
807 or ("@_: " .. single_bind:sub(3,-2) )) .. '"')--binding doesn't have context+" at end.
808 end
809 end
810 end
811 end
812end
813
814for charCode = 65, 90 do --A to Z
815 insertAllKeymods(string.char(charCode), context)
816end
817for i=0, 9 do --0 to 9
818 insertAllKeymods(tostring(i), context)
819end
820for i=0, 12 do --F1 to F12
821 insertAllKeymods("F" .. tostring(i), context)
822end
823insertAllKeymods("Enter", context) --Enter is only non-alpa(F)numeric key.
824
825for index = 1, #bindinglist do
826 local inheritedbinding = bindinglist[index]
827 :gsub("\n",'"')
828 :gsub("@.-: ",
829 ("@dfhack/lua/" .. self.focus_path .. (self.binding_inherit == "hook" and ' "gui/indicator_screen execute_hook ' or ' "')))
830 table.insert(inheritedbindings, inheritedbinding)
831 dfhack.run_command("keybinding add " .. inheritedbinding)
832end
833bindinglist = nil --cleanup
834
835 end
836end
837
838function screen:clearBindings()
839 --function to clear up previously added keybindings
840 if self.binding_inherit and
841 (self.binding_inherit == "keybinding" or self.binding_inherit == "hook") then
842 for index = 1, #inheritedbindings do
843 dfhack.run_command_silent("keybinding clear " .. inheritedbindings[index])
844 end
845 end
846 inheritedbindings = {} --Bye old table, hello new table
847end
848
849
850function screen:getDesiredContext()
851 return context_bound_indicator_screens[self]
852end
853
854function screen:setDesiredContext(context)
855 if self.widget then qerror("indicator_screen cannot set context for widget.") end
856 context_bound_indicator_screens[self] = context
857 context_bound_indicator_screens[1] = nil
858
859 if timeoutid == -1 and context then
860 repeatfunction(timeoutfunc)
861 end
862
863 if context==nil then
864 if getLen(context_bound_indicator_screens)==0 then context_bound_indicator_screens[1] = true end
865 end
866
867 if (not context) and self and self._native and
868 (not self._native.parent) then self:dismiss()
869 else
870 return self
871 end
872end
873
874function screen:dismiss()
875 --typically, hiding makes more sense than dismissing if there's a desired context
876 if not self.widget then
877 if not context_bound_indicator_screens[self] then
878 gui.Screen.dismiss(self)
879 else
880 self:hide()
881 end
882 end
883end
884
885
886
887function screen:onDestroy()
888 --Is still called when screen is orphaned due v-z-r-z
889 context_bound_indicator_screens[self] = nil --don't need to show screen anymore
890 memoizedscreens[self] = nil --There's no screen to return early
891 --Screen is removed, so better clean up keybinding list.
892 self:clearBindings()
893 self._native = nil
894 --Potentially dangerous technique that should reduce RAM usage due C not having any garbage collection.
895 inheritedbindings = nil --no longer needed
896 if getLen(context_bound_indicator_screens)==0 then context_bound_indicator_screens[1] = true end
897 --if last context-bound screen was removed, remove that trigger from repeat
898end
899
900
901
902-- Replacement for onGetSelectedUnit and onGetSelectedJob
903-- Necessary for View unit, unitlist, joblist
904local function onGetSelectedX(X)
905
906 --View mode
907 if getBottomMostViewscreenWithFocus("dwarfmode/ViewUnits/Some", df.global.gview.view.child) then
908 if X == "units" then
909 return df.global.world.units.active[df.global.ui_selected_unit]
910 elseif X == "jobs" then
911 return df.global.world.units.active[df.global.ui_selected_unit].job.current_job
912 end
913 end
914
915 --Workshop mode
916 if getBottomMostViewscreenWithFocus("QueryBuilding/Some/Workshop/Job", df.global.gview.view.child) then
917 if X == "jobs" then
918 return df.global.ui_sidebar_menus.workshop_job.choices_all[0].building --workshop
919 .jobs[df.global.ui_workshop_job_cursor] --currently selected job in workshop
920 elseif X == "units" then
921 local jobrefs=
922 df.global.ui_sidebar_menus.workshop_job.choices_all[0].building --workshop
923 .jobs[df.global.ui_workshop_job_cursor] --selected job
924 .general_refs
925 for index, ref in pairs(jobrefs) do
926 if ref._type == df.general_ref_unit_workerst then
927 return df.unit.find(ref.unit_id)
928 end
929 end
930 end
931 end
932
933 --joblist, unitlistmode
934 local joblist = getBottomMostViewscreenWithFocus("joblist",df.global.gview.view.child)
935 local unitlist = getBottomMostViewscreenWithFocus("unitlist",df.global.gview.view.child)
936 if joblist then
937 if X == "units" then
938 if joblist.jobs
939 [joblist.cursor_pos] then
940 local jobrefs =
941 joblist.jobs
942 [joblist.cursor_pos]
943 .general_refs
944 for index, ref in pairs(jobrefs) do
945 if ref._type == df.general_ref_unit_workerst then
946 return df.unit.find(ref.unit_id)
947 end
948 end
949 else
950 return joblist[X]
951 [joblist.cursor_pos]
952 end
953 end
954 if X == "jobs" then
955 return joblist[X]
956 [joblist.cursor_pos]
957 end
958 elseif unitlist then
959 return #unitlist[X][unitlist.page]>0 and unitlist[X]
960 [unitlist.page]
961 [unitlist.cursor_pos[unitlist.page]] or nil
962 end
963
964end
965
966local function setOnSelectedUnitJob(self)
967 if getBottomMostViewscreenWithFocus("joblist",df.global.gview.view.child) or
968 getBottomMostViewscreenWithFocus("unitlist",df.global.gview.view.child) or
969 getBottomMostViewscreenWithFocus("QueryBuilding/Some/Workshop/Job", df.global.gview.view.child) or
970 getBottomMostViewscreenWithFocus("dwarfmode/ViewUnits/Some", df.global.gview.view.child) then
971 self.onGetSelectedUnit = function() return onGetSelectedX("units") end
972 self.onGetSelectedJob = function() return onGetSelectedX("jobs") end
973 end
974end
975
976 --Less complicated handling (kicking upstairs) for buildings/plants/items
977function screen:onGetSelectedBuilding()
978 return dfhack.gui.getAnyBuilding(self._native.parent)
979end
980
981function screen:onGetSelectedPlant()
982 return dfhack.gui.getAnyPlant(self._native.parent)
983end
984
985function screen:onGetSelectedItem()
986 return dfhack.gui.getAnyItem(self._native.parent)
987end
988
989local function setOptions(screen)--Necessary for unpaused
990 if(screen._native and screen._native.parent and dfhack.gui.getFocusString(screen._native.parent):find("dwarfmode/Default")) then
991 --find instead of match allows it to function with stacked indicator screens in dwarfmode
992 screen.allow_options = true --allows escaping into dwarfmode options screen from main menu, even if there isn't input handling.
993 else
994 screen.allow_options = false
995 end
996end
997
998function screen:show()
999 --Might need placeOnTop? Anyway, silent failure is to allow for simpler usage with command context
1000 if self.widget then
1001 dfhack.run_command("reload dwarfmonitor")
1002 dfhack.run_command("enable dwarfmonitor")
1003 elseif not self._native then
1004 indicatorScreen.super.show(self)
1005 end
1006 return self
1007end
1008
1009 screen.hidden = true --utility value for context-specific screen
1010 local kcursor --for some reason, screens on lookaround tend to reset place to first on any movement
1011function screen:onShow()
1012 self.focus_path = "indicator_screen/" .. dfhack.gui.getFocusString(self._native.parent)
1013 self.hidden = false
1014 --Doesn't have a parent before showing
1015 self:setRects(textrect, framerect)
1016 self.dismiss_ordered = false
1017 --Lets not dismiss the screen the first time we look away, mkay.
1018 if self.binding_inherit and
1019 (self.binding_inherit == "keybinding" or self.binding_inherit == "hook") then
1020 --If screen is set to inherit bindings, set the appropriate keybindings.
1021 self:clearBindings()
1022 self:inheritBindings()
1023 end
1024 -- Additional gui functions, but only in contexts where they matter.
1025 -- Also checked/added in onInput
1026 setOnSelectedUnitJob(self)
1027 setOptions(self)
1028 kcursor = df.global.ui_look_cursor
1029 -- low-FPS/GFPS ratio concerns.
1030 -- As it is, does ultimately nothing if screen.dismiss_ordered isn't modified later
1031 -- because all the pre-set dissmisses are already on viewscreen changes.
1032 if self.nonblinky_dismiss then
1033 self.onStateChangeKey = "indicator_screen_" .. tostring(self) .. "_nonblinky"
1034 --Unique, reproducible, maybe-useful key for onstateChange
1035 --Using self itself as key will prevent garbage collection and be not as understandable as "what's this"
1036 local function orderDismissOnScreenChange(code)
1037 if not self or not self:isActive() then
1038 --shenagians like v-z-r-z can poof the screens
1039 if self then
1040 dfhack.onStateChange[self.onStateChangeKey] = nil
1041 self:dismiss()
1042 end
1043 -- does away with non-shown screen, if I have one.
1044 elseif code == SC_VIEWSCREEN_CHANGED --changed viewscreen
1045 and self.dismiss_ordered then --this generated screen has been ordered to disappear
1046 self:dismiss()
1047 dfhack.onStateChange[self.onStateChangeKey] = nil --clean up global namespace after use
1048 end
1049 end
1050 dfhack.onStateChange[self.onStateChangeKey] = orderDismissOnScreenChange
1051 end
1052
1053end
1054
1055function screen:onResize()
1056 --Since negative coordinate translations may change...
1057 textrect = createRect(simpleTextRect)
1058 framerect = simpleFrameRect and createRect(simpleFrameRect) or false
1059
1060 self:setRects(textrect, framerect)
1061end
1062
1063
1064-- initialize display area
1065
1066-- onRenderBody: has support for either plaintext/number values or function values
1067-- Note that due getLongestLength each text function will be called once even without showing the screen.
1068-- since dynamic_text_table is a table, the values in it will change when something else modifies them.
1069-- could reduce overhead by only checking which ones are functions at start- but then it is not as dynamic.
1070local emptyKeyTable, onRenderi, cury,curx1,curx2,mousey,mousex,useGeneral, useGeneralHover, useGeneralClick = {}
1071local indent, hindent, hmissing = 0,0,false
1072--Some variables used in onRenderBody left outside to reduce footprint.
1073local function renderText(dc, dynamic_text, notnewline)
1074 --indent = type(dynamic_text.indent) == "function" and tonumber(dynamic_text.indent()) or tonumber(dynamic_text.indent)
1075 --if(indent and indent>0) then
1076 dc.x = dc.x+indent
1077 --end
1078 dc:string(
1079 (type(dynamic_text.text) == "function") and dynamic_text.text() or dynamic_text.text,
1080 (type(dynamic_text.color) == "function") and dynamic_text.color() or dynamic_text.color
1081 )
1082 --if(indent and indent>0) then
1083 dc.x = dc.x-indent
1084 --end
1085 if not notnewline then dc:newline() end
1086end
1087
1088--Support for unpaused
1089function screen:onIdle()
1090 if not self.widget then self._native.parent:logic() end
1091end
1092 --syntax simplification
1093 local gps = df.global.gps
1094 local lastmx, lastmy = -1, -1
1095function screen:mouseInFrame()
1096 local mx,my
1097 if is_poking == 0 then
1098 mx, my = getMousePos()
1099 else
1100 mx, my = lastmx, lastmy
1101 end
1102return (my >= self.frame_body.y1 and
1103 my <= self.frame_body.y2) and
1104 (mx >= self.frame_body.x1 and
1105 mx <= self.frame_body.x2) or false
1106end
1107
1108 local enabler = df.global.enabler
1109
1110 local function getIndent(dtti)
1111 local gi = (type(dtti.indent) == "function" and tonumber(dtti.indent()) or tonumber(dtti.indent))
1112 if gi then return gi else return 0, true end
1113 end
1114 local cur_timeout,notEndOfLinei, ldir, rdir, clickcall = 0
1115 visiblescreens = visiblescreens or {}
1116 local poked, nclick, releaseclick = 0, 0 --pokes is for momentary screen flashes to make twbt not reset coords momentarily, nclick otherwise normal clicks
1117function screen:onRenderBody(dc)
1118 if cur_timeout > 0 then cur_timeout=cur_timeout -1 end --keyhandling rate limit
1119 --DF sometimes needs a poke to catch up to changes in viewscreen ordering
1120 if not self.widget and --don't apply if it is a dwarfmode widget
1121 (self.focus_path ~= ("indicator_screen/" .. dfhack.gui.getFocusString(self._native.parent)) or
1122 self.binding_inherit == "hook" and self._native.parent.breakdown_level ~= 0) then
1123 --While the logic is generally done in onInput for this infrequent problem, before rendering next frame
1124 gui.simulateInput(self._native, emptyKeyTable)
1125 -- the viewscreen doesn't update it's focus path properly without poking it.
1126 end
1127 --a visible screen isn't counted in garbage collection for memoized screens, so must specially note it.
1128 visiblescreens[self] = enabler.last_tick
1129 for scr, tick in pairs(visiblescreens) do if tick ~= enabler.last_tick then visiblescreens[scr] = nil end end
1130 --Rendering section
1131 if dynamic_text_table.color then dc:pen((type(dynamic_text_table.color) == "function") and dynamic_text_table.color() or dynamic_text_table.color) end
1132 mousex,mousey = getMousePos()
1133 lastmx, lastmy = mousex<0 and lastmx or mousex, mousey<0 and lastmy or mousey
1134 cury,curx1,curx2, useGeneralHover = self.frame_body.y1, self.frame_body.x1, self.frame_body.x1, self:mouseInFrame()
1135 --Due dwarfmonitor/twbt, clicks are handled here rather than in onInput
1136 useGeneralClick = useGeneralHover
1137 indent = 0
1138 clickcall = nil
1139 if is_poking ~= 0 then
1140 mousex, mousey = lastmx, lastmy
1141 else
1142 if mousex > -1 then
1143 if (enabler.mouse_lbut==1 or enabler.mouse_lbut_down==1) then
1144 nclick = 1
1145 elseif (enabler.mouse_rbut==1 or enabler.mouse_rbut_down==1) then
1146 nclick = 2
1147 else
1148 nclick = 0
1149 end
1150 end
1151 if (poked+nclick)~= 0 then
1152 clickcall = releaseclick
1153 releaseclick = nil
1154 end
1155 if nclick == 0 then poked = 0 end
1156 end
1157 for onRenderi=1, #dynamic_text_table do
1158 notEndOfLinei = dynamic_text_table.notEndOfLine and dynamic_text_table[onRenderi].notEndOfLine~=false or dynamic_text_table[onRenderi].notEndOfLine
1159 indent = getIndent(dynamic_text_table[onRenderi])+indent
1160 curx1 = curx1 + getIndent(dynamic_text_table[onRenderi])
1161 if useGeneralHover then
1162 --On mouse over, priority goes to specific parts of frame.
1163 curx2 = curx1 -1 + #((type(dynamic_text_table[onRenderi].text) == "function") and
1164 dynamic_text_table[onRenderi].text()
1165 or dynamic_text_table[onRenderi].text)
1166 --Determining the dimensions of given dynamic text
1167 if mousey == cury and
1168 (mousex >= curx1 and
1169 mousex <= curx2) then
1170 --There's an overlap with text. Commencing hover function and text adding
1171 --Something of an issue is how it still uses non-rendered text dimensions
1172 --That can even result in multiple onhovers being called simultaneously.
1173 --Blinking and such alternatives aren't too good either, so it stays.
1174 if dynamic_text_table[onRenderi].onhoverfunction and
1175 type(dynamic_text_table[onRenderi].onhoverfunction) == "function" then
1176 useGeneralHover = false
1177 dynamic_text_table[onRenderi].onhoverfunction()
1178 end
1179
1180 if dynamic_text_table[onRenderi].onhovertext and
1181 type(dynamic_text_table[onRenderi].onhovertext) == "table" then
1182 hmissing = false
1183 hindent, hmissing = getIndent(dynamic_text_table[onRenderi].onhovertext)
1184 if not hmissing then
1185 hindent = hindent - getIndent(dynamic_text_table[onRenderi])
1186 indent = indent + hindent
1187 end
1188
1189 curx2 = curx2 +1- #((type(dynamic_text_table[onRenderi].text) == "function") and
1190 dynamic_text_table[onRenderi].text()
1191 or dynamic_text_table[onRenderi].text)
1192 + #((type(dynamic_text_table[onRenderi].onhovertext.text) == "function") and
1193 dynamic_text_table[onRenderi].onhovertext.text()
1194 or dynamic_text_table[onRenderi].onhovertext.text)
1195 --Got to readjust x if I rendered hovertext
1196
1197 renderText(dc, dynamic_text_table[onRenderi].onhovertext, notEndOfLinei)
1198 --Using old notEndOfLine for arguably greater convenience.
1199 --If onhovertext was fully independent text element, it should be different, but it isn't.
1200 else
1201 renderText(dc, dynamic_text_table[onRenderi], notEndOfLinei)
1202 end
1203
1204 if (is_poking+nclick) == 1 then
1205 if dynamic_text_table[onRenderi].onclick and type(dynamic_text_table[onRenderi].onclick) == "function" then
1206 clickcall = dynamic_text_table[onRenderi].onclick -- need to avoid calling until done rendering, because click might alter dtt
1207 useGeneralClick = false
1208 end
1209 if dynamic_text_table[onRenderi].onrelease and type(dynamic_text_table[onRenderi].onrelease) == "function" then
1210 releaseclick = dynamic_text_table[onRenderi].onrelease -- need to avoid calling until done rendering, because click might alter dtt
1211 end
1212 elseif (is_poking+nclick) == 2 then
1213 if dynamic_text_table[onRenderi].onrclick and type(dynamic_text_table[onRenderi].onrclick) == "function" then
1214 clickcall = dynamic_text_table[onRenderi].onrclick
1215 useGeneralClick = false
1216 end
1217 if dynamic_text_table[onRenderi].onrrelease and type(dynamic_text_table[onRenderi].onrrelease) == "function" then
1218 releaseclick = dynamic_text_table[onRenderi].onrrelease
1219 end
1220 end
1221
1222 else
1223 renderText(dc, dynamic_text_table[onRenderi], notEndOfLinei)
1224 end
1225 if not notEndOfLinei then
1226 cury = cury + 1
1227 curx1 = self.frame_body.x1
1228 indent = 0
1229 end
1230 if notEndOfLinei then curx1 = curx2+1 end
1231 else --Mouse isn't over frame at all, proceed as normal
1232 renderText(dc, dynamic_text_table[onRenderi], notEndOfLinei)
1233 if not notEndOfLinei then indent = 0 end
1234 end
1235 end
1236--General hover comes in in case specific ones didn't apply.
1237--Because it is far easier to apply general anyway after - or before - specific in relevant function,
1238--than it is to block general from applying.
1239
1240 if useGeneralHover then
1241 if dynamic_text_table.onhoverfunction and
1242 type(dynamic_text_table.onhoverfunction) == "function" then
1243 dynamic_text_table.onhoverfunction()
1244 end
1245 end
1246
1247 if poked == 0 then --because clicks can alter the table, got to run them after rendering the whole table
1248 if not releaseclick then
1249 if (is_poking+nclick) == 1 then
1250 if self.onrelease and type(self.onrelease) == "function" then releaseclick = self.onrelease
1251 elseif dynamic_text_table.onrelease and type(dynamic_text_table.onrelease) == "function" then releaseclick = dynamic_text_table.onrelease end
1252 elseif (is_poking+nclick) == 2 then
1253 if self.onrrelease and type(self.onrrelease) == "function" then releaseclick = self.onrrelease
1254 elseif dynamic_text_table.onrrelease and type(dynamic_text_table.onrrelease) == "function" then releaseclick = dynamic_text_table.onrrelease end
1255 end
1256 end
1257 if useGeneralClick then
1258 if (is_poking+nclick) == 1 then
1259 poked = (is_poking+nclick)
1260 if self.onclick and type(self.onclick) == "function" then self.onclick()
1261 elseif dynamic_text_table.onclick and type(dynamic_text_table.onclick) == "function" then dynamic_text_table.onclick()
1262 end
1263 elseif (is_poking+nclick) == 2 then
1264 poked = (is_poking+nclick)
1265 if self.onrclick and type(self.onrclick) == "function" then self.onrclick()
1266 elseif dynamic_text_table.onrclick and type(dynamic_text_table.onrclick) == "function" then dynamic_text_table.onrclick()
1267 end
1268 end
1269 elseif clickcall then
1270 poked = (is_poking+nclick)
1271 clickcall()
1272 end
1273
1274 end
1275
1276end
1277
1278 local initial_view_child = df.global.gview.view.child.child
1279 --Must check current subview before I plant a new subview to see whether it is nil
1280 local localpen = {ch, fg, bg, bold, tile, tile_color, tile_fg, tile_bg}
1281 for i, value in pairs(screen.frame_style) do
1282 localpen[i] = value
1283 end
1284 --Must not edit a global pen, and doing this right below would do new {} every frame.
1285 --disadvantage is that must later edit localpen, not self.frame_style
1286 screen.signature_text = "DFHack"
1287function paint_signature(castx, casty, style, self)
1288 -- Places dfhack indicator on closest border, either verticall or horizontally
1289 -- Used when not rendering the rest of frame
1290 -- signature defines if it is called normally, called with gray on gray or not called for true, nil, false
1291 if(self.signature == true or self.signature == nil) then
1292 local MiddleFrame = (not initial_view_child) -- not nil (== true) if this is first screen.
1293 and (isWideView() -- In that case, lets check if view is wide,
1294 and (df.global.gps.dimx-56) -- and if it is, return it's bar's location,
1295 or (isWideView()==false -- and it isn't, if it is thin
1296 and (df.global.gps.dimx-32) -- and therefore thin bar's location
1297 or 0)) -- or perhaps left edge if it isn't thin too.
1298 or 0 -- Tho subordinate screens typically don't have divider.
1299 --if there's a child frame, there isn't a viewscreen present. Unless it's dfhack viewscreen
1300 -- Potential todo: Elegant handling several indicator screens in same location.
1301 -- Currently, it tends to stack the dfhacks on top of each other for multiple indicator screens.
1302 -- Might be preferable to having several of them, though, so keeping current behaviour
1303 localpen.fg = self.signature and 16 or 8
1304 localpen.bg = 8
1305 local Hcloseness = casty > --Is the drawn screen N side
1306 (df.global.gps.dimy - casty)-- closer to
1307 and df.global.gps.dimy -- Bottom window border
1308 or 0 -- or Top window border?
1309
1310 local Vcloseness = (df.global.gps.dimx-castx) < -- Is the drawn screen W side
1311 (castx-MiddleFrame ) -- closest to
1312 and df.global.gps.dimx -- Right window border?
1313 or ((MiddleFrame - castx) < castx -- Else is it closest to
1314 and MiddleFrame -- Middle divider
1315 or 0 ) -- or Left window border?
1316 local x,y
1317 if math.abs(Vcloseness - castx) < math.abs(Hcloseness-casty) then
1318 x = Vcloseness
1319 y = casty
1320 for index = 1, #screen.signature_text do
1321 dfhack.screen.paintString(localpen,x,y+index-1,string.sub(screen.signature_text,index,index))
1322 end
1323 else
1324 x = castx
1325 y = Hcloseness
1326 dfhack.screen.paintString(localpen,x,y, screen.signature_text)
1327 end
1328 end
1329end
1330
1331-- Overshadowing gui function, to paint signature for cases of no inset.
1332-- Though worth noting that that function paints the frame even for inset -1, just behind the text screen usually.
1333function screen:onRenderFrame(dc, rect)
1334 local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2
1335
1336 if rect.wgap <= 0 and rect.hgap <= 0 then
1337 dc:clear()
1338 else
1339 --self:renderParent()
1340 self._native.parent:render()
1341 dc:fill(rect, self.frame_background)
1342 end
1343 if self.frame_inset > -1 then gui.paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title)
1344 else
1345 paint_signature(self.frame_body.x1, self.frame_body.y1,self.frame_style, self)
1346 end
1347end
1348
1349-- A dynamic input rectangle could permit adjusting dims as well, but would need constantly rechecking values
1350-- Alternatively, to change things only as necessay, a function to set them.
1351-- Asks for if you alter body or frame, then takes upper left corner position and width
1352-- values not set will be default
1353-- to adjust only, say, width, you'd use yourscreen:adjustDims(true, nil, nil, newwidth)
1354-- could create new indicatoror on adjusting those values.
1355function screen:adjustDims(affectsText,x,y,w,h)
1356 if type(x) == "table" then x,y,w,h=x.x,x.y,x.width,x.height end
1357 local targetscreen = affectsText and "frame_body" or "frame_rect"
1358 local simpleRect = {
1359 x = x or self[targetscreen].x1,
1360 y = y or self[targetscreen].y1,
1361 width = w or self[targetscreen].width,
1362 height = h or self[targetscreen].height
1363 }
1364 if affectsText then textrect = createRect(simpleRect) else framerect = createRect(simpleRect) end
1365 --have to update the textrect and framerect, just like in onResize, otherwise it reverts on showing again.
1366 set_rect(self[targetscreen], (affectsText and textrect or framerect))
1367end
1368 --for updating to the latest call of getScreen
1369function screen:replace_dynamic_text_table(new_table)
1370 dynamic_text_table=new_table
1371end
1372
1373--If I ever want to remove it from view without, yknow, flickering on dismiss.
1374--Limited use, as what are you going to do, just leave them all there? Going to create feature-creep eventually
1375--Maybe use with init setting less_flickering to, say, set timeout to dismiss on every thousandth frame/second
1376--Alternative solution to flickering: whenever exiting a (vanilla) viewscreen, mask behind its flicker.
1377--Possibly setting an auto-clean if there's, say, 100 indicator screens already displayed
1378function screen:removeFromView(dissmiss_when)
1379 for i,k in pairs(self.frame_rect) do
1380 self.frame_rect[i] = -10
1381 end
1382 for i,k in pairs(self.frame_body) do
1383 self.frame_body[i] = -10
1384 end
1385 self.frame_rect.wgap = 1
1386 self.dismiss_ordered=true
1387 --Works with enabled nonblinky dismiss
1388 self.signature = nil
1389 --Still subtly visible, but the screen is hidden, so it shouldn't be obvious
1390 if dismiss_when then
1391 dfhack.timeout(dismiss_when,"ticks", function () self:dismiss() end)
1392 end
1393end
1394
1395function screen:onHelp()
1396 --Placeholder function
1397 --Dismisses self as to not make it impossible to ask for native help while screen is up
1398 if not self.widget then gui.Screen.dismiss(self) end
1399end
1400
1401 --Two placehold functions so one doesn't have to store onInput itself
1402function screen:preInput(keys)
1403end
1404function screen:postInput(keys)
1405end
1406
1407 screen.key_timeout = 0
1408 local mousedrag,notEndOfLineIn = false
1409
1410local function pressMouse(keys)
1411 if keys._MOUSE_L then
1412 df.global.enabler.mouse_lbut=1
1413 end
1414 if keys._MOUSE_L_DOWN then
1415 df.global.enabler.mouse_lbut_down=1
1416 end
1417 if keys._MOUSE_R then
1418 df.global.enabler.mouse_rbut=1
1419 end
1420 if keys._MOUSE_R_DOWN then
1421 df.global.enabler.mouse_rbut_down=1
1422 end
1423end
1424--[[
1425local function applyMouse(keys)
1426 if df.global.enabler.mouse_lbut==1 then
1427 keys._MOUSE_L = true
1428 end
1429 if df.global.enabler.mouse_lbut_down==1 then
1430 keys._MOUSE_L_DOWN = true
1431 end
1432 if df.global.enabler.mouse_rbut==1 then
1433 keys._MOUSE_R = true
1434 end
1435 if df.global.enabler.mouse_rbut_down==1 then
1436 keys._MOUSE_R_DOWN = true
1437 end
1438end
1439--]]
1440 local windowx,windowy,parentFocus, first, the_screen
1441function screen:onInput(keys)
1442 -- Basic input reactor
1443 -- One may subsume this function on customizing, but it already does quite a lot.
1444 parentFocus = dfhack.gui.getFocusString(self._native.parent)
1445 if cur_timeout == 0 or mousedrag then --rate limiting, expect for drawing
1446 self:preInput(keys)
1447 --readjusting vanilla clicks, which can be outside the bonds (but not inside)
1448 if ((keys._MOUSE_L or keys._MOUSE_L_DOWN) or
1449 (keys._MOUSE_R or keys._MOUSE_R_DOWN))
1450 then
1451 if not self:mouseInFrame() then
1452 mousedrag = true
1453 pressMouse(keys)
1454 end
1455 else
1456 mousedrag = false
1457 end
1458 -- Menu scrolling fix
1459 --higher numbers mean multi-stacked indicator screens. I only need to fix cursor once.
1460
1461 if not (parentFocus:find("dwarfmode") == 1 and
1462 not parentFocus:find("dwarfmode/Build/Material/Groups")
1463 and (
1464 keys.SECONDSCROLL_DOWN or --+-/*
1465 keys.SECONDSCROLL_UP or
1466 keys.SECONDSCROLL_PAGEDOWN or
1467 keys.SECONDSCROLL_PAGEUP )) then
1468 self:sendInputToParent(keys)--optimization
1469 else
1470 if(keys.SECONDSCROLL_DOWN) then -- +
1471 df.global.cursor.x = df.global.cursor.x+1
1472 df.global.cursor.y = df.global.cursor.y-1
1473 if parentFocus:find("dwarfmode/LookAround") == 1 then --TODO: also burrows
1474 kcursor = (1 + kcursor) % #df.global.ui_look_list.items
1475 end
1476 end
1477
1478 if(keys.SECONDSCROLL_UP) then -- -
1479 df.global.cursor.y = df.global.cursor.y+10
1480 if parentFocus:find("dwarfmode/LookAround") == 1 then
1481 kcursor = (kcursor -1) % #df.global.ui_look_list.items
1482 end
1483 end
1484
1485 if(keys.SECONDSCROLL_PAGEDOWN) then -- *
1486 df.global.cursor.x = df.global.cursor.x -1
1487 df.global.cursor.y = df.global.cursor.y +1
1488 if parentFocus:find("dwarfmode/LookAround") == 1 then
1489 kcursor = kcursor < #df.global.ui_look_list.items and
1490 math.min(kcursor + (df.global.gps.dimy-9),#df.global.ui_look_list.items-1) or
1491 0
1492 end
1493 end
1494 if(keys.SECONDSCROLL_PAGEUP) then -- /
1495 df.global.cursor.x = df.global.cursor.x+10
1496 if parentFocus:find("dwarfmode/LookAround") == 1 then
1497 kcursor = kcursor > 0 and math.max(0, kcursor - (df.global.gps.dimy-9)) or
1498 (#df.global.ui_look_list.items-1)
1499 end
1500 end
1501 --originally had a section here that reset cursor position to within map area...
1502 --But game does that by itself.
1503 --TODO: Iirc there were some screens without cursor where the above adjustment resulted
1504 -- in taking the hidden x -30000 and making it jump there after exiting and using v.
1505
1506
1507 --Some sections don't move cursor, but affect df.global.window_x,y instead
1508 windowx = df.global.window_x
1509 windowy = df.global.window_y
1510 self:sendInputToParent(keys)
1511 df.global.window_x = windowx
1512 df.global.window_y = windowy
1513 if parentFocus:find("dwarfmode/LookAround") == 1 then
1514 dfhack.timeout(1, "frames", function()
1515 df.global.ui_look_cursor = kcursor --lookaround item list movement to zero happens both before and after input, thus must use a timeout.
1516 end)
1517 end
1518 end
1519
1520
1521 --focus may have changed after sending input
1522 parentFocus = dfhack.gui.getFocusString(self._native.parent)
1523 if keys.LEAVESCREEN and parentFocus:find("dwarfmode/Default") then self:dismiss() end --unnecessary, but many of my scripts rely on this
1524
1525 --Disable dragging, but going by Putnam leaving them out may cause mouse becoming stuck.
1526 if false and not mousedrag then
1527 df.global.enabler.mouse_lbut=0
1528 df.global.enabler.mouse_lbut_down=0
1529 df.global.enabler.mouse_rbut=0
1530 df.global.enabler.mouse_rbut_down=0
1531 end
1532
1533 --if (getLen(keys)==0 and parentFocus:find("dwarfmode")) then applyMouse(keys) end
1534
1535 --Fromer onclick & onrclick implementation (moved to rendering to support dwarfmonitor/twbt)
1536 --[=[
1537 if false and ((keys._MOUSE_L or keys._MOUSE_L_DOWN) or
1538 (keys._MOUSE_R or keys._MOUSE_R_DOWN)) and
1539 self:mouseInFrame() then
1540 --A click over the frame. Does it do something?
1541 --First, lets give priority to specific parts of frame.
1542 cury,curx1,curx2, useGeneral = self.frame_body.y1, self.frame_body.x1, self.frame_body.x1, true
1543 local mousex, mousey = getMousePos()
1544 for i=1, #dynamic_text_table do
1545 curx1 = curx1 + getIndent(dynamic_text_table[i])
1546 curx2 = curx1 -1 + #((type(dynamic_text_table[i].text) == "function") and
1547 dynamic_text_table[i].text()
1548 or dynamic_text_table[i].text)
1549 --print(i,curx1, curx2)
1550 if mousey == cury and
1551 (mousex >= curx1 and
1552 mousex <= curx2) then
1553 --Single character has y1 == y2, empty string y2<y1
1554 if (keys._MOUSE_L or keys._MOUSE_L_DOWN) and
1555 dynamic_text_table[i].onclick and
1556 type(dynamic_text_table[i].onclick) == "function" then
1557 --left click
1558 useGeneral = false
1559 dynamic_text_table[i].onclick()
1560 elseif (keys._MOUSE_R or keys._MOUSE_R_DOWN) and
1561 dynamic_text_table[i].onrclick and
1562 type(dynamic_text_table[i].onrclick) == "function" then
1563 --right click
1564 useGeneral = false
1565 dynamic_text_table[i].onrclick()
1566 end
1567 break; --onclick or no, no point in looking further when found the overlap.
1568 end
1569 notEndOfLinei = dynamic_text_table.notEndOfLine and dynamic_text_table[i].notEndOfLine~=false or dynamic_text_table[i].notEndOfLine
1570 if not notEndOfLinei then
1571 cury = cury + 1
1572 curx1 = self.frame_body.x1
1573 end
1574 if notEndOfLinei then curx1 = curx2+1 end
1575 end
1576 --General clicks come in in case specific ones didn't apply.
1577 --Because it is far easier to apply general anyway after - or before - specific in relevant function,
1578 --than it is to block general from applying.
1579 if useGeneral then
1580 if (keys._MOUSE_L or keys._MOUSE_L_DOWN) then
1581 if self.onclick and type(self.onclick) == "function" then self.onclick()
1582 elseif dynamic_text_table.onclick and type(dynamic_text_table.onclick) == "function" then dynamic_text_table.onclick()
1583 end
1584 elseif (keys._MOUSE_R or keys._MOUSE_R_DOWN) then
1585 if self.onrclick and type(self.onrclick) == "function" then self.onrclick()
1586 elseif dynamic_text_table.onrclick and type(dynamic_text_table.onrclick) == "function" then dynamic_text_table.onrclick()
1587 end
1588 end
1589 end
1590
1591 end--]=]
1592
1593 --Navigation: Leaving entered menus:
1594 if self._native.parent and self._native.parent.breakdown_level ~=0 then
1595 --seems to cover all cases below more succintly (due being done after input was passed to parent)
1596
1597
1598 --[===[
1599 if keys.LEAVESCREEN and (not parentFocus:find( "dwarfmode")) or
1600 -- Navigation woes: You can't bind Escape nor navigate "down" while screen is up
1601 -- That mostly means zooming, I think
1602
1603 -- With z:
1604 ( (not parentFocus:find("dwarfmode")) and (--Don't want to cancel screen because z-ing an unit or kitchen
1605 --Since this is "not in dwarfmode", z will cancel the screen even where it'd normally do nothing.
1606
1607 (--keys.D_HOTKEY_ZOOM or --Hotkeys to enable zooming with F1-8 to place
1608 keys.UNITVIEW_RELATIONSHIPS_ZOOM or--relationships zoom
1609 keys.UNITJOB_ZOOM_CRE or --unitlist, joblist
1610 keys.ANNOUNCE_ZOOM or --(a)nnouncement list, reports
1611 keys.STORES_ZOOM or --effect unknown
1612 keys.A_LOG_ZOOM_SELECTED ) --effect unknown
1613 or --With b, in unitlist and joblist only:
1614 (keys.UNITJOB_ZOOM_BUILD and
1615 (parentFocus:find("unitlist") or
1616 parentFocus:find("joblist")
1617 ))
1618 or --with q and t, in buildinglist only:
1619 (parentFocus:find("buildinglist") and
1620 ((keys.BUILDINGLIST_ZOOM_Q)
1621 or
1622 (keys.BUILDINGLIST_ZOOM_T)))
1623 --or OPTION2-20+ --What are these for?
1624 ) ) then --]===]
1625
1626 --It seems all zooms can summed up by parent's breakdown being 2 or 3
1627 if self.dismiss_on_zoom or self:getDesiredContext() then
1628 --default option/it's nonsense to not recheck context on zoom
1629 self:dismiss()
1630 elseif not parentFocus:find("indicator_screen") then
1631 --only have to re-place the lowest indicator screen; otherwise others will become lost.
1632 self.support_zoom = true
1633 --this avoids modifying the parent screen
1634 end
1635 end
1636
1637 --support for not losing screen on zoom
1638 if self.support_zoom and --only zooming cases
1639 self._native.parent --indirectly prevents hidden screens from being replaced lower (as their parent is set to nil), shouldn't happen
1640 and not self:isDismissed() then --in case of dismissal, doesn't replace screen
1641 --In case of hooks and zooming, the dismissed screen can be left hanging and screw things up.
1642 self.support_zoom = nil
1643 --only support zoom once.
1644 if self._native.parent.breakdown_level ==2 then
1645
1646 self._native.parent,
1647 self._native.parent.parent.child,
1648 self._native.parent.parent,
1649 self._native.parent.child
1650 =
1651 self._native.parent.parent,
1652 self._native,
1653 nil,
1654 nil
1655 --orphans screen, but already set to break down.
1656 elseif self._native.parent.breakdown_level ==3 then
1657 --first, prevents losing MY screen and everything on top of it by making it break down only screens I don't want to keep
1658 first = df.global.gview.view.child
1659 the_screen = first.child
1660 while(the_screen ~= self._native) do
1661 the_screen.breakdown_level = 2
1662 the_screen = the_screen.child
1663 end
1664 --Then, places my screen on top of first sreen and discards the inbetween
1665 self._native.parent,
1666 first.child,
1667 first.child.parent,
1668 self._native.parent.child
1669 =
1670 first,
1671 self._native,
1672 nil,
1673 nil
1674
1675 end
1676 --Gotta move the screen one or more screens upwards.
1677 end
1678
1679 --it's possible the above changed parentfocus or removed parent
1680 if self._native and self._native.parent then
1681 parentFocus = dfhack.gui.getFocusString(self._native.parent)
1682 else
1683 parentFocus = "" --just dfhack/lua/
1684 end
1685 setOptions(self)
1686 -- With navigation, zooming or hooking, one will carry over bindings intended for grandparent indicator screen.
1687 -- That can be useful - i.e. going gui/dfstatus then stocks show - but is not intended and can be detrimental
1688 -- with starting from default and then using v-navigation for instance.
1689 if self.focus_path ~= ("indicator_screen/" .. parentFocus) and
1690 self._native and not (self:isDismissed() or self.hidden) then
1691 self.focus_path = "indicator_screen/" .. parentFocus
1692 self:clearBindings()
1693 self:inheritBindings()
1694 end
1695 -- Script support checks that context of the functions is still apppropriate.
1696 -- If one dismisses on zoom, would matter only if there's a preexisting screen present
1697 -- as one enters relevant contexts.
1698 -- Does rely on the assumption that one uses keyboard or mouse (instead of console) to navigate.
1699 setOnSelectedUnitJob(self)
1700 cur_timeout=self.key_timeout
1701 self:postInput(keys)
1702 else
1703 cur_timeout = cur_timeout > 0 and (cur_timeout -1) or 0
1704 end
1705end
1706
1707function screen:placeOnTop()
1708 --There's freezing issues if this is done when breakdown level is unsuitable
1709 if not self.widget and self._native and dfhack.gui.getCurViewscreen().breakdown_level == 0 then
1710 if self.hidden and self.onAboutToShow then
1711 self:onAboutToShow()
1712 end
1713 placeOnTop(self._native)
1714 if self.hidden then
1715 self:onShow()
1716 end
1717 end
1718end
1719
1720function screen:onHide()
1721 --dummy function
1722end
1723
1724function screen:hide()
1725 --print("hiding " .. self.focus_path)
1726 --removes parent and child links from screen and connects its parent to parent's grandchild, if one exists.
1727 if not self.widget and self._native.parent then
1728 if self._native.child then
1729 self._native.parent.child, self._native.child.parent = self._native.child, self._native.parent
1730 self._native.child, self._native.parent = nil,nil
1731 else
1732 self._native.parent.child, self._native.parent= nil,nil
1733 end
1734 self.hidden = true
1735 screen:clearBindings()
1736 self:onHide()
1737 end
1738end
1739
1740return screen
1741end
1742