-
Notifications
You must be signed in to change notification settings - Fork 199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create gui/tooltips.lua: show info (f.e. job name) at units and/or mouse cursor #1365
Open
TymurGubayev
wants to merge
10
commits into
DFHack:master
Choose a base branch
from
TymurGubayev:gui/tooltips/1
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 7 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
1ed5e22
Create tooltips.lua
TymurGubayev 7abc355
Create tooltips.rst
TymurGubayev 66dce0d
Update docs/gui/tooltips.rst
TymurGubayev c27777a
Update gui/tooltips.lua
TymurGubayev 0ca5a9a
enter emoticons
TymurGubayev 0f6aeba
fix GetScreenCoordinates for ASCII mode
TymurGubayev be5bccd
tooltips.rst: add IMPORTANT NOTE as well as some clarifications
TymurGubayev 9484f8e
vieport.window_x -> .coord
TymurGubayev 33dbe85
use `getUnitsInBox(pos1, pos2)` overload
TymurGubayev 8d345a7
make this an overlay
TymurGubayev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
gui/tooltips | ||
============ | ||
|
||
.. dfhack-tool:: | ||
:summary: Show name and job tooltips near units on map. | ||
:tags: fort inspection | ||
|
||
**IMPORTANT NOTE**: the tooltips will show over any vanilla UI elements! | ||
|
||
|
||
This script shows "tooltips" in two optional modes: | ||
|
||
* following the mouse, when a unit is underneath the cursor; | ||
* following units on the map. | ||
|
||
Information shown includes happiness indicator, name, and current job. | ||
|
||
Usage | ||
----- | ||
|
||
:: | ||
|
||
gui/tooltips |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,300 @@ | ||
-- Show tooltips on units and/or mouse | ||
|
||
local RELOAD = false -- set to true when actively working on this script | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI,
|
||
|
||
local gui = require('gui') | ||
local widgets = require('gui.widgets') | ||
local ResizingPanel = require('gui.widgets.containers.resizing_panel') | ||
|
||
-------------------------------------------------------------------------------- | ||
|
||
local follow_units = true; | ||
local follow_mouse = true; | ||
local function change_follow_units(new, old) | ||
follow_units = new | ||
end | ||
local function change_follow_mouse(new, old) | ||
follow_mouse = new | ||
end | ||
|
||
local shortenings = { | ||
["Store item in stockpile"] = "Store item", | ||
} | ||
|
||
-------------------------------------------------------------------------------- | ||
|
||
local TITLE = "Tooltips" | ||
|
||
if RELOAD then TooltipControlWindow = nil end | ||
TooltipControlWindow = defclass(TooltipControlWindow, widgets.Window) | ||
TooltipControlWindow.ATTRS { | ||
frame_title=TITLE, | ||
frame_inset=0, | ||
resizable=false, | ||
frame = { | ||
w = 25, | ||
h = 4, | ||
-- just under the minimap: | ||
r = 2, | ||
t = 18, | ||
}, | ||
} | ||
|
||
function TooltipControlWindow:init() | ||
self:addviews{ | ||
widgets.ToggleHotkeyLabel{ | ||
view_id = 'btn_follow_units', | ||
frame={t=0, h=1}, | ||
label="Follow units", | ||
key='CUSTOM_ALT_U', | ||
on_change=change_follow_units, | ||
}, | ||
widgets.ToggleHotkeyLabel{ | ||
view_id = 'btn_follow_mouse', | ||
frame={t=1, h=1}, | ||
label="Follow mouse", | ||
key='CUSTOM_ALT_M', | ||
on_change=change_follow_mouse, | ||
}, | ||
} | ||
end | ||
|
||
local function GetUnitHappiness(unit) | ||
-- keep in mind, this will look differently with game's font | ||
local mapToEmoticon = {[0] = "=C", ":C", ":(", ":]", ":)", ":D", "=D" } | ||
-- same as in ASCII mode, but for then middle (3), which is GREY instead of WHITE | ||
local mapToColor = {[0] = COLOR_RED, COLOR_LIGHTRED, COLOR_YELLOW, COLOR_GREY, COLOR_GREEN, COLOR_LIGHTGREEN, COLOR_LIGHTCYAN} | ||
local stressCat = dfhack.units.getStressCategory(unit) | ||
if stressCat > 6 then stressCat = 6 end | ||
return mapToEmoticon[stressCat], mapToColor[stressCat] | ||
end | ||
|
||
local function GetUnitJob(unit) | ||
local job = unit.job.current_job | ||
return job and dfhack.job.getName(job) | ||
end | ||
|
||
local function GetUnitNameAndJob(unit) | ||
local sb = {} | ||
sb[#sb+1] = dfhack.units.getReadableName(unit) | ||
local jobName = GetUnitJob(unit) | ||
if jobName then | ||
sb[#sb+1] = ": " | ||
sb[#sb+1] = jobName | ||
end | ||
return table.concat(sb) | ||
end | ||
|
||
local function GetTooltipText(x,y,z) | ||
local txt = {} | ||
local units = dfhack.units.getUnitsInBox(x,y,z,x,y,z) or {} -- todo: maybe (optionally) use filter parameter here? | ||
TymurGubayev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
for _,unit in ipairs(units) do | ||
txt[#txt+1] = GetUnitNameAndJob(unit) | ||
txt[#txt+1] = NEWLINE | ||
end | ||
|
||
return txt | ||
end | ||
|
||
-------------------------------------------------------------------------------- | ||
-- MouseTooltip is an almost copy&paste of the DimensionsTooltip | ||
-- | ||
if RELOAD then MouseTooltip = nil end | ||
MouseTooltip = defclass(MouseTooltip, ResizingPanel) | ||
|
||
MouseTooltip.ATTRS{ | ||
frame_style=gui.FRAME_THIN, | ||
frame_background=gui.CLEAR_PEN, | ||
no_force_pause_badge=true, | ||
auto_width=true, | ||
display_offset={x=3, y=3}, | ||
} | ||
|
||
function MouseTooltip:init() | ||
ensure_key(self, 'frame').w = 17 | ||
self.frame.h = 4 | ||
|
||
self.label = widgets.Label{ | ||
frame={t=0}, | ||
auto_width=true, | ||
} | ||
|
||
self:addviews{ | ||
widgets.Panel{ | ||
-- set minimum size for tooltip frame so the DFHack frame badge fits | ||
frame={t=0, l=0, w=7, h=2}, | ||
}, | ||
self.label, | ||
} | ||
end | ||
|
||
function MouseTooltip:render(dc) | ||
if not follow_mouse then return end | ||
|
||
local x, y = dfhack.screen.getMousePos() | ||
if not x then return end | ||
|
||
local pos = dfhack.gui.getMousePos() | ||
local text = GetTooltipText(pos2xyz(pos)) | ||
if #text == 0 then return end | ||
self.label:setText(text) | ||
|
||
local sw, sh = dfhack.screen.getWindowSize() | ||
local frame_width = math.max(9, self.label:getTextWidth() + 2) | ||
self.frame.l = math.min(x + self.display_offset.x, sw - frame_width) | ||
self.frame.t = math.min(y + self.display_offset.y, sh - self.frame.h) | ||
self:updateLayout() | ||
MouseTooltip.super.render(self, dc) | ||
end | ||
|
||
-------------------------------------------------------------------------------- | ||
|
||
if RELOAD then TooltipsVizualizer = nil end | ||
TooltipsVizualizer = defclass(TooltipsVizualizer, gui.ZScreen) | ||
TooltipsVizualizer.ATTRS{ | ||
focus_path='TooltipsVizualizer', | ||
pass_movement_keys=true, | ||
} | ||
|
||
function TooltipsVizualizer:init() | ||
local controls = TooltipControlWindow{view_id = 'controls'} | ||
local tooltip = MouseTooltip{view_id = 'tooltip'} | ||
self:addviews{controls, tooltip} | ||
end | ||
|
||
-- map coordinates -> interface layer coordinates | ||
function GetScreenCoordinates(map_coord) | ||
if not map_coord then return end | ||
-- -> map viewport offset | ||
local vp = df.global.world.viewport | ||
local vp_Coord = vp.window_x -- is actually coord | ||
TymurGubayev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
local map_offset_by_vp = { | ||
x = map_coord.x - vp_Coord.x, | ||
y = map_coord.y - vp_Coord.y, | ||
z = map_coord.z - vp_Coord.z, | ||
} | ||
|
||
if not dfhack.screen.inGraphicsMode() then | ||
return map_offset_by_vp | ||
else | ||
-- -> pixel offset | ||
local gps = df.global.gps | ||
local map_tile_pixels = gps.viewport_zoom_factor // 4; | ||
local screen_coord_px = { | ||
x = map_tile_pixels * map_offset_by_vp.x, | ||
y = map_tile_pixels * map_offset_by_vp.y, | ||
} | ||
-- -> interface layer coordinates | ||
local screen_coord_text = { | ||
x = math.ceil( screen_coord_px.x / gps.tile_pixel_x ), | ||
y = math.ceil( screen_coord_px.y / gps.tile_pixel_y ), | ||
} | ||
|
||
return screen_coord_text | ||
end | ||
end | ||
|
||
function TooltipsVizualizer:onRenderFrame(dc, rect) | ||
TooltipsVizualizer.super.onRenderFrame(self, dc, rect) | ||
|
||
if not follow_units then return end | ||
|
||
if not dfhack.screen.inGraphicsMode() and not gui.blink_visible(500) then | ||
return | ||
end | ||
|
||
local vp = df.global.world.viewport | ||
local topleft = vp.window_x | ||
local width = vp.max_x | ||
local height = vp.max_y | ||
local bottomright = {x = topleft.x + width, y = topleft.y + height, z = topleft.z} | ||
|
||
local units = dfhack.units.getUnitsInBox(topleft.x,topleft.y,topleft.z,bottomright.x,bottomright.y,bottomright.z) or {} | ||
if #units == 0 then return end | ||
|
||
local oneTileOffset = GetScreenCoordinates({x = topleft.x + 1, y = topleft.y + 1, z = topleft.z + 0}) | ||
local pen = COLOR_WHITE | ||
|
||
local used_tiles = {} | ||
for i = #units, 1, -1 do | ||
local unit = units[i] | ||
|
||
local happiness, happyPen = GetUnitHappiness(unit) | ||
local job = GetUnitJob(unit) | ||
job = shortenings[job] or job | ||
if not job and not happiness then goto continue end | ||
|
||
local pos = xyz2pos(dfhack.units.getPosition(unit)) | ||
if not pos then goto continue end | ||
|
||
local txt = table.concat({happiness, job}, " ") | ||
|
||
local scrPos = GetScreenCoordinates(pos) | ||
local y = scrPos.y - 1 -- subtract 1 to move the text over the heads | ||
local x = scrPos.x + oneTileOffset.x - 1 -- subtract 1 to move the text inside the map tile | ||
|
||
-- to resolve overlaps, we'll mark every coordinate we write anything in, | ||
-- and then check if the new tooltip will overwrite any used coordinate. | ||
-- if it will, try the next row, to a maximum offset of 4. | ||
TymurGubayev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
local row | ||
local dy = 0 | ||
-- todo: search for the "best" offset instead, f.e. max `usedAt` value, with `-1` the best | ||
local usedAt = -1 | ||
for yOffset = 0, 4 do | ||
dy = yOffset | ||
|
||
row = used_tiles[y + dy] | ||
if not row then | ||
row = {} | ||
used_tiles[y + dy] = row | ||
end | ||
|
||
usedAt = -1 | ||
for j = 0, #txt - 1 do | ||
if row[x + j] then | ||
usedAt = j | ||
break | ||
end | ||
end | ||
|
||
if usedAt == -1 then break end | ||
end -- for dy | ||
|
||
-- in case there isn't enough space, cut the text off | ||
if usedAt > 0 then | ||
local s = happiness and #happiness + 1 or 0 | ||
job = job:sub(0, usedAt - s - 1) .. '_' | ||
txt = txt:sub(0, usedAt - 1) .. '_' -- for marking | ||
end | ||
|
||
dc:seek(x, y + dy) | ||
:pen(happyPen):string(happiness or "") | ||
:string((happiness and job) and " " or "") | ||
:pen(pen):string(job or "") | ||
|
||
-- mark coordinates as used | ||
for j = 0, #txt - 1 do | ||
row[x + j] = true | ||
end | ||
|
||
::continue:: | ||
end | ||
end | ||
|
||
function TooltipsVizualizer:onDismiss() | ||
view = nil | ||
end | ||
|
||
---------------------------------------------------------------- | ||
|
||
if not dfhack.isMapLoaded() then | ||
qerror('gui/tooltips requires a map to be loaded') | ||
end | ||
|
||
if RELOAD and view then | ||
view:dismiss() | ||
-- view is nil now | ||
end | ||
|
||
view = view and view:raise() or TooltipsVizualizer{}:show() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs disclaimer that tooltips will show over any vanilla UI elements:
The
dig
ascii overlays suffer from the same problem. I don't know of any good solution here. I'm not saying that any behavior needs to change -- just needs to be documented.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added an
**IMPORTANT NOTE**
at the beginning of the description text