Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1ed5e22
Create tooltips.lua
TymurGubayev Jan 4, 2025
7abc355
Create tooltips.rst
TymurGubayev Jan 4, 2025
66dce0d
Update docs/gui/tooltips.rst
TymurGubayev Jan 6, 2025
c27777a
Update gui/tooltips.lua
TymurGubayev Jan 6, 2025
0ca5a9a
enter emoticons
TymurGubayev Jan 7, 2025
0f6aeba
fix GetScreenCoordinates for ASCII mode
TymurGubayev Jan 7, 2025
be5bccd
tooltips.rst: add IMPORTANT NOTE as well as some clarifications
TymurGubayev Jan 7, 2025
9484f8e
vieport.window_x -> .coord
TymurGubayev Jan 12, 2025
33dbe85
use `getUnitsInBox(pos1, pos2)` overload
TymurGubayev Jan 12, 2025
8d345a7
make this an overlay
TymurGubayev Jan 12, 2025
66d8d39
make possible to show specific stress/happiness levels; add config UI
TymurGubayev Jan 18, 2025
e97201c
trim trailing whitespaces
TymurGubayev Jan 18, 2025
2e1cf87
fix an ASCII mode exception
TymurGubayev Jan 18, 2025
7e53def
render mouse tooltips over unit banners
TymurGubayev Feb 1, 2025
ec27b39
use list instead of labels in config UI
TymurGubayev Feb 1, 2025
5ec1e54
persist config (globally)
TymurGubayev Feb 1, 2025
6c98c34
Merge branch 'master' into gui/tooltips/1
TymurGubayev Feb 21, 2025
7abd0f9
Delete docs/gui/tooltips.rst
TymurGubayev Feb 21, 2025
bb4972f
Delete gui/tooltips.lua
TymurGubayev Feb 21, 2025
561eeab
implement gui/spectate.lua
TymurGubayev Feb 21, 2025
3c3fa05
adjust starting position
TymurGubayev Feb 21, 2025
40f3913
Merge branch 'master' into HEAD
myk002 Feb 22, 2025
46689c2
refactor UI and use upstreamed functionality
myk002 Feb 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/gui/tooltips.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
gui/tooltips
============

.. dfhack-tool::
:summary: Show tooltips with useful info.
:tags: fort inspection

This script shows "tooltips" following units and/or mouse with job names.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs more description of the two options and their effects.

Copy link
Contributor Author

@TymurGubayev TymurGubayev Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a bit of clarification...
It's kind of hard to describe though


Copy link
Member

@myk002 myk002 Jan 6, 2025

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:
image

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.

Copy link
Contributor Author

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

Usage
-----

::

gui/tooltips
281 changes: 281 additions & 0 deletions gui/tooltips.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
-- Show tooltips on units and/or mouse

local RELOAD = false -- set to true when actively working on this script
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, devel/clear-script-env has the same effect:

multicmd devel/clear-script-env gui/tooltips; gui/tooltips


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 GetUnitJob(unit)
local job = unit.job
if job and job.current_job then
return dfhack.job.getName(job.current_job)
end
return nil
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?

for _,unit in pairs(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
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,
}
-- -> 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

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 txt = GetUnitJob(unit)
if not txt then goto continue end

txt = shortenings[txt] or txt

local pos = xyz2pos(dfhack.units.getPosition(unit))
if not pos then goto continue end

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.
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
txt = txt:sub(0, usedAt - 1) .. '_'
end

dc:seek(x, y + dy):pen(pen):string(txt)

-- 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()
Loading