Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local helptext = [=[
- indicator_screen
- ================
- Module containing getScreen & isWideView functions, to be
- called with dfhack.script_environment(gui/indicator_screen)
- version 1.0
- getScreen arguments:
- 1: A table with 1 to N tables ([] values being optional) of
- {text[, color][,onclick ][, onrclick][, notEndOfLine]
- [, onhovertext][, onhoverfunction]},
- where text is text or function that returns text, color is
- number/pen or function that returns it, onclick and onrclick
- are functions to be called when the text is (right-)clicked,
- notEndOfLine being true continues next text on same line,
- onhovertext being a table with text and color used on hover,
- and onhoverfunction is function called on hover. Table can
- be changed after creation, and color/onclick/onrclick
- /onhoverfunction can also be applied to whole table.
- 2: Optional table that optionally contains x, y, width and
- height for text display. Places the text by upper left
- corner at x,y. Calculates height and width from 1 if not set.
- 3: Optional table like the above for frame boundary.
- If width or height are not set, uses DF's height and width.
- If left out, then frame boundary is not drawn.
- Returns a gui.FramedScreen element with additional functions:
- adjustDims(affectsText,x,y,w,h) - meant for resizing
- onHelp() - dismisses the screen
- removeFromView([dissmiss_when]) - [dismisses in n ticks]
- inheritBindings()
- clearBindings()
- onDestroy()
- onGetSelectedJob()
- onGetSelectedUnit()
- onInput(keys)
- onRenderBody()
- onRenderFrame()
- onResize()
- onShow()
- setRects(textrect, framerect)
- And public values affecting behaviour:
- signature: false will not paint, nil will use light gray
- signature_text: Text used as signature if there's no frame
- binding_inherit: valid values "hook", "keybinding", "none".
- dismiss_on_zoom: nil will dismiss parent and adopt grandpa
- dismiss_ordered: used to dismiss screen on viewscreen change.
- nonblinky_dismiss: default evaluates by fps/gfps>1+#screens.
- onStateChangeKey: dfhack.onStateChange function key for ^. ]=]
- local gui = require 'gui'
- function isWideView()
- -- Tells if one has tabbed the view mode to wide or not with true or false.
- -- In other cases, such as not even thin, returns nil.
- local map_width = df.global.ui_area_map_width
- local menu_width = df.global.ui_menu_width
- if map_width == 3 and menu_width == 1 then
- return true
- elseif map_width == 2 and
- (menu_width == 1 or
- (--[==[dfhack.screen.readTile((df.global.gps.dimx-56),2).ch == 219 and--]==]--
- -- To check the case where menu should be gone but active sidebar keeps it visible
- -- Deprecated: Introduces lag.
- (not dfhack.gui.getCurFocus():find("dwarfmode/Default")) and
- -- To prevent failing on Trade depots in NE corner, though a rogue screen could override this.
- menu_width == 2)
- ) --or (
- --(not dfhack.gui.getCurFocus():find("dwarfmode/Default"))
- --and map_width == 3 and menu_width == 3 )
- then
- return true
- elseif (map_width == 3 and menu_width == 3 and dfhack.gui.getCurFocus():find("dwarfmode/Default") ) or (
- map_width == 2 and menu_width == 2 ) then
- return nil
- else
- return false
- end
- end
- function getScreen(dynamic_text_table, simpleTextRect, simpleFrameRect)
- --[[ :dynamic_text_table: a {} with 1..n iterated integer keys
- each points to {text = valueA, color = valueB}
- values can be functions
- :simpleTextRect: an optional {} with optional x, y, width, height number values
- if not set, uses window dimensions
- :simpleFrameRect: like simpleTextRect
- if not set, only places signature on closest vanilla frame border.
- Returns not yet shown screen
- ]]--
- function getLongestLength (ltable, lkey)
- -- Single-Utility function, returns longest length of a given key or notEndOfLine keychain in a table.
- -- Runs functions once to get the length of their answers.
- local len,clen = 0,0
- for i=1, #ltable do
- if type(ltable[i][lkey]) == "function" then
- clen = clen + #((ltable[i][lkey])())
- else
- clen = clen + #(ltable[i][lkey])
- end
- if ltable[i].notEndOfLine and i ~= #ltable then
- --doNothing, unless it is the last element
- elseif clen > len then
- len = clen
- clen = 0
- else
- clen = 0
- end
- end
- return len
- end
- function getHeight(ltable)
- --single-use utility function to get height of text used.
- local height = #ltable
- for i=1, #ltable do
- if i~= #ltable and ltable[i].notEndOfLine then
- height = height - 1
- end
- end
- return height
- end
- simpleTextRect = simpleTextRect or {}
- --In case it is not specified, as all methods refer to a table.
- local screen = gui.FramedScreen{
- frame_style = gui.BOUNDARY_FRAME, -- not necessary, but indicatoror will default anyway
- --frame_title = '', -- Adds clutter and is not useful for most inline indicators. Can be set later
- frame_width = simpleTextRect.width or getLongestLength(dynamic_text_table, "text"),
- --function, as lenghts shorter clip it away, while longer ones create unnecesary black space
- frame_height = simpleTextRect.height or getHeight(dynamic_text_table),
- frame_inset = simpleFrameRect and 1 or -1
- }
- simpleTextRect.width = simpleTextRect.width or screen.frame_body.width
- simpleTextRect.height = simpleTextRect.height or screen.frame_body.height
- local unused, nr = dfhack.gui.getCurFocus():gsub("dfhack","hack")
- screen.nonblinky_dismiss = df.global.enabler.fps_per_gfps < (2+(nr or 0)) and true or false
- unused, nr = nil, nil
- --Issue nbA: If one creates multiple screens at once and then later shows them at once, may blink.
- --But if one sets this value on show, then user will be unable to modify it if desired,
- --as the relevant necessary function to make it work is also called then and only then.
- --Issue nbB: Inaccurate if fps isn't capped (possible once this thing can unpause)
- --Issue nbC: if set to false, .dismiss_ordered does nothing.
- screen.signature = true
- -- only affects things if inset is -1
- -- other options being nil (light gray on light gray) and false(not painted)
- screen.binding_inherit = "hook"
- --[[ Controls if and how the created screen will react to keybindings active in the context it is called
- Purpose is to provide better interaction with fourth party plugins and scripts
- Possible options are hook, hook_bottom, keybinding, none=nil=false
- none: doesn't look at the bindings set to viewscreen beneath, though focus path is set according to it.
- keybinding: Asks keybinding for all keybindings, and then adds binding in parent viewscreen to its context
- for as long as the screen is up. Doesn't always work even when it could, for scripts often check
- context they're called in before running.
- hook: Asks keybinding for all keybindings, and when it gets a combination applicable in parent screen removes
- itself as child from parent screen, calls the command with dfhack, waits a frame, and readds itself.
- After 1 frame, adds this screen back on top of topmost viewscreen.
- ]]--
- screen.dismiss_on_zoom = true
- -- Controls whether screen is kept or dismissed on zooming or ascending (with Escape) out of a menu.
- -- Default is true, just as new vanilla screens are placed on top of created screens.
- -- False would mimic how binding_inherit hook results in this screen being placed on top of keybinded screen.
- function createRect(inputRect)
- --Takes nil or partial or complete table of x, y, width, height values
- -- negative x,y will be wrapped
- --Returns table with all location values screen uses.
- local simpleRect = inputRect or {}
- --Either use passed simple rectangle or create new one as big as screen.
- local newRect = {}
- newRect.width = simpleRect.width or df.global.gps.dimx
- newRect.height = simpleRect.height or df.global.gps.dimy
- --if values are missing, set them to full screen width
- newRect.x1 = simpleRect.x and (simpleRect.x > -1 and simpleRect.x or (df.global.gps.dimx+simpleRect.x)) or 0
- newRect.y1 = simpleRect.y and (simpleRect.y > -1 and simpleRect.y or (df.global.gps.dimy+simpleRect.y)) or 0
- --wrapping around the screen, and zeroes if not initialized
- newRect.x2=newRect.x1+newRect.width - 1
- newRect.y2=newRect.y1+newRect.height - 1
- newRect.clip_x1=newRect.x1
- newRect.clip_x2=newRect.x2
- newRect.clip_y1=newRect.y1
- newRect.clip_y2=newRect.y2
- newRect.x=newRect.x1
- newRect.y=newRect.y1
- newRect.hgap=newRect.height -- if both hgap and wgap are 0, it'll set the screen to black.
- newRect.wgap=newRect.width -- that's a little undesirable loop if I'm creating, not dismissing
- if (newRect.hgap == 0) and (newRect.hgap == newRect.wgap) then newRect.hgap = 1 end
- -- preventing it allows zero-size frames without blanking the screen.
- return newRect
- end
- function set_rect(target, corrector)
- -- sets all corrector key values found in targetrect to corrector ones
- -- This softness is useful for avoiding table bloat or modifying native userdata
- for key, value in pairs(corrector) do
- if target[key] then target[key] = value end
- end
- end
- textrect = createRect(simpleTextRect)
- framerect = simpleFrameRect and createRect(simpleFrameRect) or false
- local zeroRect = {width = 0, height = 0, x = 0, y = 0} --Declared for setRects to prevent table proliferation
- function screen:setRects(textrect, framerect)
- --sets screen's dimension values to given input rects
- set_rect(self.frame_body, textrect)
- if framerect then
- set_rect(self.frame_rect, framerect)
- else
- set_rect(self.frame_rect, createRect(zeroRect))
- --Even without instructions for frame body, it is still set.
- --Also dfhack indicator depends on it existing somewhere, so can't just skip drawing frame.
- end
- end
- local inheritedbindings = {}
- local modifiertable = {}
- --(Static) table for all possible keybinding modifiers
- table.insert(modifiertable, "") --nothing too
- table.insert(modifiertable, "Ctrl-")
- table.insert(modifiertable, "Alt-")
- table.insert(modifiertable, "Shift-")
- table.insert(modifiertable, "Ctrl-Alt-")
- table.insert(modifiertable, "Ctrl-Shift-")
- table.insert(modifiertable, "Shift-Alt-")
- table.insert(modifiertable, "Ctrl-Alt-Shift")
- function screen:inheritBindings()
- --Asks keybinding for all keybindings in parent context, and binds them to itself.
- if self.binding_inherit and not (self.binding_inherit:find("none")) then
- local bindinglist = {}
- local context = dfhack.gui.getFocusString(self._native.parent)
- function insertAllKeymods(key, searched_context)
- --Takes a single key (e.g. K) and checks if it with any and all modifiers applies to searched context
- --If pplies, adds that to binding list
- for i = 1, #modifiertable do
- local keybinding_text = dfhack.run_command_silent("keybinding list " .. modifiertable[i] .. key)
- if not keybinding_text:find("No bindings") then --No bindings, ergo nothing to do
- for single_bind in keybinding_text:gmatch("%s%s[^%s].-\n") do --Split up the list and iterate it.
- local received_context = single_bind:match("@.-:") --Ask what context given command uses
- if not received_context or --general, DF-wide context
- searched_context:find(received_context:sub(2,-2)) then --context matches same or lower screen
- table.insert(bindinglist, modifiertable[i] .. key --modifier-key combination
- .. (received_context and single_bind:sub(3,-2)--binding has context (matters for @_:)
- or ("@_: " .. single_bind:sub(3,-2) )) .. '"')--binding doesn't have context+" at end.
- end
- end
- end
- end
- end
- for charCode = 65, 90 do --A to Z
- insertAllKeymods(string.char(charCode), context)
- end
- for i=0, 9 do --0 to 9
- insertAllKeymods(tostring(i), context)
- end
- for i=0, 12 do --F1 to F12
- insertAllKeymods("F" .. tostring(i), context)
- end
- insertAllKeymods("Enter", context) --Enter is only non-alpa(F)numeric key.
- for index = 1, #bindinglist do
- local inheritedbinding = bindinglist[index]
- :gsub("\n",'"')
- :gsub("@.-: ",
- ("@dfhack/lua/" .. self.focus_path .. (self.binding_inherit == "hook" and ' "gui/indicator_screen execute_hook ' or ' "')))
- table.insert(inheritedbindings, inheritedbinding)
- dfhack.run_command("keybinding add " .. inheritedbinding)
- end
- bindinglist = nil --cleanup
- end
- end
- function screen:clearBindings()
- --function to clear up previously added keybindings
- if self.binding_inherit and
- (self.binding_inherit == "keybinding" or self.binding_inherit == "hook") then
- for index = 1, #inheritedbindings do
- dfhack.run_command_silent("keybinding clear " .. inheritedbindings[index])
- end
- end
- inheritedbindings = {} --Bye old table, hello new table
- end
- function screen:onDestroy()
- --Screen is removed, so better clean up keybinding list.
- self:clearBindings()
- self._native = nil
- --Potentially dangerous technique that should reduce RAM usage due C not having any garbage collection.
- inheritedbindings = nil --no longer needed
- end
- -- Utility function
- -- Due dynamic tree structure, don't know where desired viewscreen is? Retrieve it
- function getBottomMostViewscreenWithFocus(text, targetscreen)
- --Finds and returns the screen closest to root screen whose path includes text
- if targetscreen and
- dfhack.gui.getFocusString(targetscreen):find(text) then
- return targetscreen
- elseif targetscreen and targetscreen.child then --Attempting to call nil.child will crash this
- return getBottomMostViewscreenWithFocus(text, targetscreen.child)
- end
- -- if there's no child, it didn't find a screen with text in focus and returns nil
- end
- -- Replacement for onGetSelectedUnit and onGetSelectedJob
- -- Necessary for View unit, unitlist, joblist
- local function onGetSelectedX(X)
- --View mode
- if getBottomMostViewscreenWithFocus("dwarfmode/ViewUnits/Some", df.global.gview.view.child) then
- if X == "units" then
- return df.global.world.units.active[df.global.ui_selected_unit]
- elseif X == "jobs" then
- return df.global.world.units.active[df.global.ui_selected_unit].job.current_job
- end
- end
- --Workshop mode
- local workshopscr = getBottomMostViewscreenWithFocus("QueryBuilding/Some/Workshop/Job", df.global.gview.view.child)
- if workshopscr then
- if X == "jobs" then
- return df.global.ui_sidebar_menus.workshop_job.choices_all[0].building --workshop
- .jobs[df.global.ui_workshop_job_cursor] --currently selected job in workshop
- elseif X == "units" then
- local jobrefs=
- df.global.ui_sidebar_menus.workshop_job.choices_all[0].building --workshop
- .jobs[df.global.ui_workshop_job_cursor] --selected job
- .general_refs
- for index, ref in pairs(jobrefs) do
- if ref._type == df.general_ref_unit_workerst then
- return df.unit.find(ref.unit_id)
- end
- end
- end
- end
- --joblist, unitlistmode
- local joblist = getBottomMostViewscreenWithFocus("joblist",df.global.gview.view.child)
- local unitlist = getBottomMostViewscreenWithFocus("unitlist",df.global.gview.view.child)
- if joblist then
- if X == "units" then
- if joblist.jobs
- [joblist.cursor_pos] then
- local jobrefs =
- joblist.jobs
- [joblist.cursor_pos]
- .general_refs
- for index, ref in pairs(jobrefs) do
- if ref._type == df.general_ref_unit_workerst then
- return df.unit.find(ref.unit_id)
- end
- end
- else
- return joblist[X]
- [joblist.cursor_pos]
- end
- end
- if X == "jobs" then
- return joblist[X]
- [joblist.cursor_pos]
- end
- elseif unitlist then
- local whichlist
- if unitlist.page == 0 then
- whichlist = "Citizens"
- elseif unitlist.page == 1 then
- whichlist = "Livestock"
- elseif unitlist.page == 2 then
- whichlist = "Others"
- else
- whichlist = "Dead" --dead taking our jobs. Say it ain't so!
- end
- return unitlist[X]
- [whichlist]
- [unitlist.cursor_pos[whichlist]]
- end
- end
- local function setOnSelectedUnitJob(self)
- if getBottomMostViewscreenWithFocus("joblist",df.global.gview.view.child) or
- getBottomMostViewscreenWithFocus("unitlist",df.global.gview.view.child) or
- getBottomMostViewscreenWithFocus("QueryBuilding/Some/Workshop/Job", df.global.gview.view.child) or
- getBottomMostViewscreenWithFocus("dwarfmode/ViewUnits/Some", df.global.gview.view.child) then
- self.onGetSelectedUnit = function() return onGetSelectedX("units") end
- self.onGetSelectedJob = function() return onGetSelectedX("jobs") end
- end
- end
- function screen:onShow()
- self.focus_path = "indicator_screen/" .. dfhack.gui.getFocusString(self._native.parent)
- --Doesn't have a parent before showing
- self:setRects(textrect, framerect)
- self.dismiss_ordered = false
- --Lets not dismiss the screen the first time we look away, mkay.
- if self.binding_inherit and
- (self.binding_inherit == "keybinding" or self.binding_inherit == "hook") then
- --If screen is set to inherit bindings, set the appropriate keybindings.
- self:inheritBindings()
- end
- -- Additional gui functions, but only in contexts where they matter.
- -- Also checked/added in onInput
- setOnSelectedUnitJob(self)
- -- low-FPS/GFPS ratio concerns.
- -- As it is, does ultimately nothing if screen.dismiss_ordered isn't modified later
- -- because all the pre-set dissmisses are already on viewscreen changes.
- if self.nonblinky_dismiss then
- self.onStateChangeKey = "indicator_screen_" .. tostring(self) .. "_nonblinky"
- --Unique, reproducible, maybe-useful key for onstateChange
- --Using self itself as key will prevent garbage collection and be not as understandable as "what's this"
- function orderDismissOnScreenChange(code)
- if not self or not self:isActive() then
- --shenagians like v-z-r-z can poof the screens
- dfhack.onStateChange[self.onStateChangeKey] = nil
- if self and not self:isDismissed() then self:dismiss() end
- -- does away with non-shown screen, if I have one.
- elseif code == SC_VIEWSCREEN_CHANGED --changed viewscreen
- and self.dismiss_ordered then --this generated screen has been ordered to disappear
- self:dismiss()
- dfhack.onStateChange[self.onStateChangeKey] = nil --clean up global namespace after use
- end
- end
- dfhack.onStateChange[self.onStateChangeKey] = orderDismissOnScreenChange
- end
- end
- function screen:onResize() self:setRects(textrect, framerect) end
- -- initialize display area
- -- onRenderBody: has support for either plaintext/number values or function values
- -- Note that due getLongestLength each text function will be called once even without showing the screen.
- -- since dynamic_text_table is a table, the values in it will change when something else modifies them.
- -- could reduce overhead by only checking which ones are functions at start- but then it is not as dynamic.
- local emptyKeyTable, onRenderi, cury,curx1,curx2,mousey,mousex,useGeneral = {}
- --Some variables used in onRenderBody left outside to reduce footprint.
- local function renderText(dc, dynamic_text, notnewline)
- dc:string(
- (type(dynamic_text.text) == "function") and dynamic_text.text() or dynamic_text.text,
- (type(dynamic_text.color) == "function") and dynamic_text.color() or dynamic_text.color
- )
- if not notnewline then dc:newline() end
- end
- function screen:onRenderBody(dc)
- --DF sometimes needs a poke to catch up to changes in viewscreen ordering
- if self.focus_path ~= ("indicator_screen/" .. dfhack.gui.getFocusString(self._native.parent)) or
- ("dfhack/lua/" .. self.focus_path ) ~= dfhack.gui.getFocusString(self._native) or --probaby unnecessary
- self.binding_inherit == "hook" and self._native.parent.breakdown_level == 2 then
- --While the logic is generally done in onInput for this infrequent problem, before rendering next frame
- gui.simulateInput(self._native, emptyKeyTable)
- -- the viewscreen doesn't update it's focus path properly without poking it.
- end
- --Rendering section
- if dynamic_text_table.color then dc:pen(dynamic_text_table.color) end
- cury,curx1,curx2, useGeneral = self.frame_body.y1, self.frame_body.x1, self.frame_body.x1, true
- mousey = df.global.gps.mouse_y
- mousex = df.global.gps.mouse_x
- for onRenderi=1, #dynamic_text_table do
- if (mousey >= self.frame_body.y1 and
- mousey <= self.frame_body.y2) and
- (mousex >= self.frame_body.x1 and
- mousex <= self.frame_body.x2) then
- --A mouse over the frame. Does it do something?
- --First, lets give priority to specific parts of frame.
- curx2 = curx1 -1 + #((type(dynamic_text_table[onRenderi].text) == "function") and
- dynamic_text_table[onRenderi].text()
- or dynamic_text_table[onRenderi].text)
- --Determining the dimensions of given dynamic text
- if mousey == cury and
- (mousex >= curx1 and
- mousex <= curx2) then
- --There's an overlap with text. Commencing hover function and text adding
- --Something of an issue is how it still uses non-rendered text dimensions
- --That can even result in multiple onhovers being called simultaneously.
- --Blinking and such alternatives aren't too good either, so it stays.
- if dynamic_text_table[onRenderi].onhoverfunction and
- type(dynamic_text_table[onRenderi].onhoverfunction) == "function" then
- useGeneral = false
- dynamic_text_table[onRenderi].onhoverfunction()
- end
- if dynamic_text_table[onRenderi].onhovertext and
- type(dynamic_text_table[onRenderi].onhovertext) == "table" then
- curx2 = curx2 +1 - #((type(dynamic_text_table[onRenderi].text) == "function") and
- dynamic_text_table[onRenderi].text()
- or dynamic_text_table[onRenderi].text)
- + #((type(dynamic_text_table[onRenderi].onhovertext.text) == "function") and
- dynamic_text_table[onRenderi].onhovertext.text()
- or dynamic_text_table[onRenderi].onhovertext.text)
- --Got to readjust x if I rendered hovertext
- renderText(dc, dynamic_text_table[onRenderi].onhovertext, dynamic_text_table[onRenderi].notEndOfLine)
- --Using old notEndOfLine for arguably greater convenience.
- --If onhovertext was fully independent text element, it should be different, but it isn't.
- else
- renderText(dc, dynamic_text_table[onRenderi], dynamic_text_table[onRenderi].notEndOfLine)
- end
- else
- renderText(dc, dynamic_text_table[onRenderi], dynamic_text_table[onRenderi].notEndOfLine)
- end
- if not dynamic_text_table[onRenderi].notEndOfLine then cury = cury + 1 end
- if dynamic_text_table[onRenderi].notEndOfLine then curx1 = curx2+1 else curx1 = self.frame_body.x1 end
- else --Mouse isn't over frame at all, proceed as normal
- renderText(dc, dynamic_text_table[onRenderi], dynamic_text_table[onRenderi].notEndOfLine)
- end
- --General hover comes in in case specific ones didn't apply.
- --Because it is far easier to apply general anyway after - or before - specific in relevant function,
- --than it is to block general from applying.
- if useGeneral and self.onhoverfunction and
- type(self.onhoverfunction) == "function" then
- self.onhoverfunction()
- end
- end
- end
- local initial_view_child = df.global.gview.view.child.child
- --Must check current subview before I plant a new subview to see whether it is nil
- local localpen = {ch, fg, bg, bold, tile, tile_color, tile_fg, tile_bg}
- for i, value in pairs(screen.frame_style) do
- localpen[i] = value
- end
- --Must not edit a global pen, and doing this right below would do new {} every frame.
- --disadvantage is that must later edit localpen, not self.frame_style
- screen.signature_text = "DFHack"
- function paint_signature(castx, casty, style, self)
- -- Places dfhack indicator on closest border, either verticall or horizontally
- -- Used when not rendering the rest of frame
- -- signature defines if it is called normally, called with gray on gray or not called for true, nil, false
- if(self.signature == true or self.signature == nil) then
- local MiddleFrame = (not initial_view_child) -- not nil (== true) if this is first screen.
- and (isWideView() -- In that case, lets check if view is wide,
- and (df.global.gps.dimx-56) -- and if it is, return it's bar's location,
- or (isWideView()==false -- and it isn't, if it is thin
- and (df.global.gps.dimx-32) -- and therefore thin bar's location
- or 0)) -- or perhaps left edge if it isn't thin too.
- or 0 -- Tho subordinate screens typically don't have divider.
- --if there's a child frame, there isn't a viewscreen present. Unless it's dfhack viewscreen
- -- Potential todo: Elegant handling several indicator screens in same location.
- -- Currently, it tends to stack the dfhacks on top of each other for multiple indicator screens.
- -- Might be preferable to having several of them, though, so keeping current behaviour
- localpen.fg = self.signature and 16 or 8
- localpen.bg = 8
- local Hcloseness = casty > --Is the drawn screen N side
- (df.global.gps.dimy - casty)-- closer to
- and df.global.gps.dimy -- Bottom window border
- or 0 -- or Top window border?
- local Vcloseness = (df.global.gps.dimx-castx) < -- Is the drawn screen W side
- (castx-MiddleFrame ) -- closest to
- and df.global.gps.dimx -- Right window border?
- or ((MiddleFrame - castx) < castx -- Else is it closest to
- and MiddleFrame -- Middle divider
- or 0 ) -- or Left window border?
- if math.abs(Vcloseness - castx) < math.abs(Hcloseness-casty) then
- x = Vcloseness
- y = casty
- for index = 1, #screen.signature_text do
- dfhack.screen.paintString(localpen,x,y+index-1,string.sub(screen.signature_text,index,index))
- end
- else
- x = castx
- y = Hcloseness
- dfhack.screen.paintString(localpen,x,y, screen.signature_text)
- end
- end
- end
- -- Overshadowing gui function, to paint signature for cases of no inset.
- -- Though worth noting that that function paints the frame even for inset -1, just behind the text screen usually.
- function screen:onRenderFrame(dc, rect)
- local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2
- if rect.wgap <= 0 and rect.hgap <= 0 then
- dc:clear()
- else
- self:renderParent()
- dc:fill(rect, self.frame_background)
- end
- if self.frame_inset > -1 then gui.paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title)
- else
- paint_signature(self.frame_body.x1, self.frame_body.y1,self.frame_style, self)
- end
- end
- -- A dynamic input rectangle could permit adjusting dims as well, but would need constantly rechecking values
- -- Alternatively, to change things only as necessay, a function to set them.
- -- Asks for if you alter body or frame, then takes upper left corner position and width
- -- values not set will be default
- -- to adjust only, say, width, you'd use yourscreen:adjustDims(true, nil, nil, newwidth)
- -- could create new indicatoror on adjusting those values.
- function screen:adjustDims(affectsText,x,y,w,h)
- local targetscreen = affectsText and "frame_body" or "frame_rect"
- local simpleRect = {
- x = x or self[targetscreen].x1,
- y = y or self[targetscreen].y1,
- width = w or self[targetscreen].width,
- height = h or self[targetscreen].height
- }
- set_rect(self[targetscreen],createRect(simpleRect))
- end
- --If I ever want to remove it from view without, yknow, flickering on dismiss.
- --Limited use, as what are you going to do, just leave them all there? Going to create feature-creep eventually
- --Maybe use with init setting less_flickering to, say, set timeout to dismiss on every thousandth frame/second
- --Alternative solution to flickering: whenever exiting a (vanilla) viewscreen, mask behind its flicker.
- --Possibly setting an auto-clean if there's, say, 100 indicator screens already displayed
- function screen:removeFromView(dissmiss_when)
- for i,k in pairs(self.frame_rect) do
- self.frame_rect[i] = -10
- end
- for i,k in pairs(self.frame_body) do
- self.frame_body[i] = -10
- end
- self.frame_rect.wgap = 1
- self.dismiss_ordered=true
- --Works with enabled nonblinky dismiss
- self.signature = nil
- --Still subtly visible, but the screen is hidden, so it shouldn't be obvious
- if dismiss_when then
- dfhack.timeout(dismiss_when,"ticks", function () self:dismiss() end)
- end
- end
- function screen:onHelp()
- --Placeholder function
- --Dismisses self as to not make it impossible to ask for native help while screen is up
- self:dismiss()
- end
- function screen:onInput(keys)
- -- Basic input reactor
- -- One may subsume this function on customizing, but it already does quite a lot.
- --More of a syntax simplifier function
- local function parentHas(text)
- return(dfhack.gui.getFocusString(self._native.parent):find(text))
- end
- --onclick & onrclick implementation
- if ((keys._MOUSE_L or keys._MOUSE_L_DOWN) or
- (keys._MOUSE_R or keys._MOUSE_R_DOWN)) and
- (df.global.gps.mouse_y >= self.frame_body.y1 and
- df.global.gps.mouse_y <= self.frame_body.y2) and
- (df.global.gps.mouse_x >= self.frame_body.x1 and
- df.global.gps.mouse_x <= self.frame_body.x2) then
- --A click over the frame. Does it do something?
- --First, lets give priority to specific parts of frame.
- cury,curx1,curx2, useGeneral = self.frame_body.y1, self.frame_body.x1, self.frame_body.x1, true
- for i=1, #dynamic_text_table do
- curx2 = curx1 -1 + #((type(dynamic_text_table[i].text) == "function") and
- dynamic_text_table[i].text()
- or dynamic_text_table[i].text)
- if df.global.gps.mouse_y == cury and
- (df.global.gps.mouse_x >= curx1 and
- df.global.gps.mouse_x <= curx2) then
- --Single character has y1 == y2, empty string y2<y1
- if (keys._MOUSE_L or keys._MOUSE_L_DOWN) and
- dynamic_text_table[i].onclick and
- type(dynamic_text_table[i].onclick) == "function" then
- --left click
- useGeneral = false
- dynamic_text_table[i].onclick()
- elseif (keys._MOUSE_R or keys._MOUSE_R_DOWN) and
- dynamic_text_table[i].onrclick and
- type(dynamic_text_table[i].onrclick) == "function" then
- --right click
- useGeneral = false
- dynamic_text_table[i].onrclick()
- end
- break; --onclick or no, no point in looking further when found the overlap.
- end
- if not dynamic_text_table[i].notEndOfLine then cury = cury + 1 end
- if dynamic_text_table[i].notEndOfLine then curx1 = curx2+1 else curx1 = self.frame_body.x1 end
- end
- --General clicks come in in case specific ones didn't apply.
- --Because it is far easier to apply general anyway after - or before - specific in relevant function,
- --than it is to block general from applying.
- if useGeneral and
- (keys._MOUSE_L or keys._MOUSE_L_DOWN) and
- self.onclick and
- type(self.onclick) == "function" then
- self.onclick()
- elseif useGeneral and
- (keys._MOUSE_R or keys._MOUSE_R_DOWN) and
- self.onrclick and
- type(self.onrclick) == "function" then
- self.onrclick()
- end
- end
- -- Menu scrolling fix
- --higher numbers mean multi-stacked indicator screens. I only need to fix cursor once.
- if parentHas("dwarfmode") == 1 and (
- keys.SECONDSCROLL_DOWN or --actually pressed a key I didn't want to change navigation
- keys.SECONDSCROLL_UP or
- keys.SECONDSCROLL_PAGEDOWN or
- keys.SECONDSCROLL_PAGEUP ) then
- if(keys.SECONDSCROLL_DOWN) then
- df.global.cursor.x = df.global.cursor.x+1
- df.global.cursor.y = df.global.cursor.y-1
- end
- if(keys.SECONDSCROLL_UP) then
- df.global.cursor.y = df.global.cursor.y+10
- end
- if(keys.SECONDSCROLL_PAGEDOWN) then
- df.global.cursor.x = df.global.cursor.x -1
- df.global.cursor.y = df.global.cursor.y +1
- end
- if(keys.SECONDSCROLL_PAGEUP) then
- df.global.cursor.x = df.global.cursor.x+10
- end
- --originally had a section here that reset cursor position to within map area...
- --But game does that by itself.
- --TODO: Iirc there were some screens without cursor where the above adjustment resulted
- -- in taking the hidden x -30000 and making it jump there after exiting and using v.
- --Some sections don't move cursor, but affect df.global.window_x,y instead
- local windowx = df.global.window_x
- local windowy = df.global.window_y
- self:sendInputToParent(keys)
- df.global.window_x = windowx
- df.global.window_y = windowy
- else
- self:sendInputToParent(keys)
- end
- --In some cases, screen must be removed for DF hotkey to function
- if parentHas("dwarfmode/Default") and (keys.LEAVESCREEN or --Can't Escape into savegame menu with screen
- keys.D_ONESTEP or D_PAUSE or --DF doesn't run unpaused when screen is up.
- keys.UNITVIEW_FOLLOW) then --Can't follow unit around either
- self:dismiss()
- --Any replacing of screens onto applicable areas has to be handled by planting a new screen probs.
- end
- --Navigation: Leaving entered menus:
- if keys.LEAVESCREEN and (not parentHas("dwarfmode")) or
- -- Navigation woes: You can't bind Escape nor navigate "down" while screen is up
- -- That mostly means zooming, I think
- -- With z:
- ( (not parentHas("dwarfmode")) and (--Don't want to cancel screen because z-ing an unit or kitchen
- --Since this is "not in dwarfmode", z will cancel the screen even where it'd normally do nothing.
- (--keys.D_HOTKEY_ZOOM or --Hotkeys to enable zooming with F1-8 to place
- keys.UNITVIEW_RELATIONSHIPS_ZOOM or--relationships zoom
- keys.UNITJOB_ZOOM_CRE or --unitlist, joblist
- keys.ANNOUNCE_ZOOM or --(a)nnouncement list, reports
- keys.STORES_ZOOM or --effect unknown
- keys.A_LOG_ZOOM_SELECTED ) --effect unknown
- or --With b, in unitlist and joblist only:
- (keys.UNITJOB_ZOOM_BUILD and
- (parentHas("unitlist") or
- parentHas("joblist")
- ))
- or --with q and t, in buildinglist only:
- (parentHas("buildinglist") and
- ((keys.BUILDINGLIST_ZOOM_Q)
- or
- (keys.BUILDINGLIST_ZOOM_T)))
- --or OPTION2-20+ --What are these for?
- ) ) then
- if self.dismiss_on_zoom then
- self:dismiss()
- elseif not dfhack.gui.getFocusString(self._native.parent):find("indicator_screen") then
- --only have to re-place the lowest indicator screen; otherwise others will become lost.
- self._native.parent.breakdown_level = 2
- end
- end
- -- With navigation or hooking, one will carry over bindings intended for grandparent indicator screen.
- -- That can be useful - i.e. going gui/dfstatus then stocks show - but is not intended and can be detrimental
- -- with starting from default and then using v-navigation for instance.
- if self.focus_path ~= ("indicator_screen/" .. dfhack.gui.getFocusString(self._native.parent)) then
- self.focus_path = "indicator_screen/" .. dfhack.gui.getFocusString(self._native.parent)
- self:clearBindings()
- self:inheritBindings()
- end
- if self._native.parent.breakdown_level == 2 and not self:isDismissed() then
- --In case of hooks and zooming, the dismissed screen can be left hanging and screw things up.
- self._native.parent.parent.child = self._native.parent.child
- self._native.parent = self._native.parent.parent
- --Gotta move the screen one upwards.
- end
- -- Script support checks that context of the functions is still apppropriate.
- -- If one dismisses on zoom, would matter only if there's a preexisting screen present
- -- as one enters relevant contexts.
- -- Does rely on the assumption that one uses keyboard or mouse (instead of console) to navigate.
- setOnSelectedUnitJob(self)
- end
- return screen
- end
- local args = {...}
- if args and args[1] == "help" then print(helptext)
- elseif args and args[1] == "execute_hook" then
- local execute_helptext = [[
- indicator_screen execute_hook
- =============================
- Utility command for indicator-screen module.
- Temporarily removes bottommost scren with indicator_screen focus path,
- launchs a command given, then adds the screen back on top after that
- Usage format:
- gui/indicator_screen execute_hook <valid command to keybind>
- Intended to be used with keybinding so that the command is executed
- in context below bottommost indicator screen.
- ]]
- if #args == 1 or --No command given,
- (tostring(args[2]):find("help") and (#(tostring(args[2]))<6)) or --asking for help,
- (tostring(args[2]):find("?") and (#(tostring(args[2]))<3)) then -- or being confused?
- dfhack.print(execute_helptext) -- Explain!
- else
- local gui = require 'gui'
- function getIndicatorScreenChainBase(targetscreen)
- --Finds and returns the screen farthest from parent screen whose focus path includes indicator_screen
- --Not closest to root screen, as that leads to weirdness with screen>unitlist>screen>manipulator
- if targetscreen and --Recursive function may run into nil
- dfhack.gui.getFocusString(targetscreen):find("indicator_screen") and --the topmost is what we want
- not dfhack.gui.getFocusString(targetscreen.parent):find("indicator_screen") --and it's parent is not
- then
- return targetscreen
- elseif targetscreen and targetscreen.parent then --Attempting to call nil.parent will crash this
- return getIndicatorScreenChainBase(targetscreen.parent)
- end
- -- if there's no parent, it didn't find an indicator screen and returns nil
- end
- function placeOnTop(domScreen)
- --Takes a domScreen, and places it on top of currently topmost screen.
- local subScreen = dfhack.gui.getCurViewscreen()
- --Maybe this local variable name might be a little mean...
- domScreen.parent=subScreen
- --It's not the worst possible name, considering.
- subScreen.child=domScreen
- end
- local lowestIndicatorScreen = getIndicatorScreenChainBase(dfhack.gui.getCurViewscreen())
- --only if we find any indicator screens do we execute the command
- if lowestIndicatorScreen then
- lowestIndicatorScreen.parent.child = nil
- -- Return execution environment to pre-screen context before execution
- dfhack.timeout(1, "frames", function() -- Wait a bit for dfhack to catch up
- dfhack.run_command(table.concat(args, " ",2)) --Execute command in base environment
- --Needs spaces in-between arguments
- placeOnTop(lowestIndicatorScreen) --Place the indicator screen back on top.
- end)
- end
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement