2012-08-19 07:53:25 -06:00
|
|
|
-- Viewscreen implementation utility collection.
|
|
|
|
|
|
|
|
local _ENV = mkmodule('gui')
|
|
|
|
|
2022-12-02 16:36:45 -07:00
|
|
|
local utils = require('utils')
|
|
|
|
|
2012-08-19 07:53:25 -06:00
|
|
|
local dscreen = dfhack.screen
|
2022-12-02 16:36:45 -07:00
|
|
|
local getval = utils.getval
|
2012-08-19 07:53:25 -06:00
|
|
|
|
2012-11-03 10:06:33 -06:00
|
|
|
local to_pen = dfhack.pen.parse
|
|
|
|
|
2023-07-31 22:05:02 -06:00
|
|
|
CLEAR_PEN = to_pen{tile=dfhack.internal.getAddress('init') and df.global.init.texpos_border_interior, ch=32, fg=0, bg=0, write_to_lower=true}
|
2023-01-12 13:44:50 -07:00
|
|
|
TRANSPARENT_PEN = to_pen{tile=0, ch=0}
|
2023-01-11 19:23:16 -07:00
|
|
|
KEEP_LOWER_PEN = to_pen{ch=32, fg=0, bg=0, keep_lower=true}
|
2012-08-20 13:04:01 -06:00
|
|
|
|
2023-01-23 18:40:16 -07:00
|
|
|
local MOUSE_KEYS = {
|
2012-11-29 02:37:16 -07:00
|
|
|
_MOUSE_L = true,
|
|
|
|
_MOUSE_R = true,
|
2023-01-03 15:52:49 -07:00
|
|
|
_MOUSE_M = true,
|
2012-12-02 03:43:23 -07:00
|
|
|
_MOUSE_L_DOWN = true,
|
|
|
|
_MOUSE_R_DOWN = true,
|
2023-01-03 15:52:49 -07:00
|
|
|
_MOUSE_M_DOWN = true,
|
2012-11-29 02:37:16 -07:00
|
|
|
}
|
|
|
|
|
2023-01-23 18:40:16 -07:00
|
|
|
local FAKE_INPUT_KEYS = copyall(MOUSE_KEYS)
|
|
|
|
FAKE_INPUT_KEYS._STRING = true
|
|
|
|
|
2012-08-20 13:04:01 -06:00
|
|
|
function simulateInput(screen,...)
|
|
|
|
local keys = {}
|
|
|
|
local function push_key(arg)
|
|
|
|
local kv = arg
|
|
|
|
if type(arg) == 'string' then
|
|
|
|
kv = df.interface_key[arg]
|
2012-11-29 02:37:16 -07:00
|
|
|
if kv == nil and not FAKE_INPUT_KEYS[arg] then
|
2012-08-20 13:04:01 -06:00
|
|
|
error('Invalid keycode: '..arg)
|
|
|
|
end
|
|
|
|
end
|
2012-08-25 00:37:03 -06:00
|
|
|
if type(kv) == 'number' then
|
2012-08-20 13:04:01 -06:00
|
|
|
keys[#keys+1] = kv
|
|
|
|
end
|
|
|
|
end
|
|
|
|
for i = 1,select('#',...) do
|
|
|
|
local arg = select(i,...)
|
|
|
|
if arg ~= nil then
|
|
|
|
local t = type(arg)
|
|
|
|
if type(arg) == 'table' then
|
|
|
|
for k,v in pairs(arg) do
|
|
|
|
if v == true then
|
|
|
|
push_key(k)
|
|
|
|
else
|
|
|
|
push_key(v)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
push_key(arg)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
dscreen._doSimulateInput(screen, keys)
|
|
|
|
end
|
|
|
|
|
2012-08-21 01:35:39 -06:00
|
|
|
function mkdims_xy(x1,y1,x2,y2)
|
|
|
|
return { x1=x1, y1=y1, x2=x2, y2=y2, width=x2-x1+1, height=y2-y1+1 }
|
|
|
|
end
|
|
|
|
function mkdims_wh(x1,y1,w,h)
|
|
|
|
return { x1=x1, y1=y1, x2=x1+w-1, y2=y1+h-1, width=w, height=h }
|
|
|
|
end
|
2012-08-24 03:28:34 -06:00
|
|
|
function is_in_rect(rect,x,y)
|
|
|
|
return x and y and x >= rect.x1 and x <= rect.x2 and y >= rect.y1 and y <= rect.y2
|
|
|
|
end
|
2012-08-21 01:35:39 -06:00
|
|
|
|
2012-10-15 10:03:18 -06:00
|
|
|
local function align_coord(gap,align,lv,rv)
|
|
|
|
if gap <= 0 then
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
if not align then
|
|
|
|
if rv and not lv then
|
|
|
|
align = 1.0
|
|
|
|
elseif lv and not rv then
|
|
|
|
align = 0.0
|
|
|
|
else
|
|
|
|
align = 0.5
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return math.floor(gap*align)
|
|
|
|
end
|
|
|
|
|
|
|
|
function compute_frame_rect(wavail,havail,spec,xgap,ygap)
|
|
|
|
if not spec then
|
|
|
|
return mkdims_wh(0,0,wavail,havail)
|
|
|
|
end
|
|
|
|
|
|
|
|
local sw = wavail - (spec.l or 0) - (spec.r or 0)
|
|
|
|
local sh = havail - (spec.t or 0) - (spec.b or 0)
|
|
|
|
local rqw = math.min(sw, (spec.w or sw)+xgap)
|
|
|
|
local rqh = math.min(sh, (spec.h or sh)+ygap)
|
|
|
|
local ax = align_coord(sw - rqw, spec.xalign, spec.l, spec.r)
|
|
|
|
local ay = align_coord(sh - rqh, spec.yalign, spec.t, spec.b)
|
|
|
|
|
|
|
|
local rect = mkdims_wh((spec.l or 0) + ax, (spec.t or 0) + ay, rqw, rqh)
|
|
|
|
rect.wgap = sw - rqw
|
|
|
|
rect.hgap = sh - rqh
|
|
|
|
return rect
|
|
|
|
end
|
|
|
|
|
add scroll icons to Label widget (#2101)
* WIP: add scroll icons to Label widget
It's an opt-out. The icons are rendered in the right-most column of the 1st and last row. They are only rendered when text can actually be scrolled in the corresponding direction.
WIP: Currently, the icons might overlay text characters, there is no mechanism preventing it
* gui.lua: expose the `parse_inset()` function
* refactor Label's scroll icon code
* since `render_scroll_icons` only works with a label, it's now a class function
* `update_scroll_inset` ensures `frame_inset.r` or `.l` is at least 1, according to `show_scroll_icons`
* `show_scroll_icons` has 4 possible values: `false` for no icons, `left` for icons on the first column on the left (also ensuring `frame_inset.l >= 1`), `right` - last column on the right, `DEFAULT_NIL` - same as `right` if text height greater than `frame_body.height`, else same as `false`.
* make `render_scroll_icons` always draw icons
The check now happens in `onRenderFrame`
* draw frame's background
calling `Label.super.onRenderFrame(self, dc, rect)` makes frame's background invisible for some reason
* remove trailing spaces
* fix scroll icons placed far above/below text
With `Label.frame_inset = 1` the text could be vertically centered with plenty of space below and above,
but not all rendered. Before this change, the scroll icons would be at the very top and bottom of the frame
instead of near the first and last rendered text line.
* always `update_scroll_inset` to react to resized window
* draw scroll icons next to text
* update `Lua API.rst` with new `Label` parameters
* move comment separator up
This way every scroll related parameter is in one group
* list default values for new parameters in docs
* add missing description of `Label.scroll_keys`
2022-04-29 07:55:08 -06:00
|
|
|
function parse_inset(inset)
|
2012-10-15 10:03:18 -06:00
|
|
|
local l,r,t,b
|
|
|
|
if type(inset) == 'table' then
|
2022-04-16 09:17:35 -06:00
|
|
|
l,r = inset.l or inset.x or 0, inset.r or inset.x or 0
|
|
|
|
t,b = inset.t or inset.y or 0, inset.b or inset.y or 0
|
2012-10-15 10:03:18 -06:00
|
|
|
else
|
|
|
|
l = inset or 0
|
|
|
|
t,r,b = l,l,l
|
|
|
|
end
|
2018-03-11 04:40:30 -06:00
|
|
|
return l,t,r,b
|
2012-10-15 10:03:18 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function inset_frame(rect, inset, gap)
|
2018-04-04 15:12:06 -06:00
|
|
|
if not rect then return mkdims_wh(0, 0, 0, 0) end
|
2012-10-15 10:03:18 -06:00
|
|
|
gap = gap or 0
|
|
|
|
local l,t,r,b = parse_inset(inset)
|
|
|
|
return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)
|
|
|
|
end
|
|
|
|
|
2012-11-30 08:10:17 -07:00
|
|
|
function compute_frame_body(wavail, havail, spec, inset, gap, inner_frame)
|
2012-10-15 10:03:18 -06:00
|
|
|
gap = gap or 0
|
|
|
|
local l,t,r,b = parse_inset(inset)
|
2012-11-30 08:10:17 -07:00
|
|
|
local xgap,ygap = 0,0
|
|
|
|
if inner_frame then
|
|
|
|
xgap,ygap = gap*2+l+r, gap*2+t+b
|
|
|
|
end
|
|
|
|
local rect = compute_frame_rect(wavail, havail, spec, xgap, ygap)
|
2012-10-15 10:03:18 -06:00
|
|
|
local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)
|
|
|
|
return rect, body
|
|
|
|
end
|
|
|
|
|
|
|
|
function blink_visible(delay)
|
|
|
|
return math.floor(dfhack.getTickCount()/delay) % 2 == 0
|
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function getKeyDisplay(code)
|
|
|
|
if type(code) == 'string' then
|
|
|
|
code = df.interface_key[code]
|
|
|
|
end
|
|
|
|
return dscreen.getKeyDisplay(code)
|
|
|
|
end
|
2012-08-21 01:35:39 -06:00
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
-----------------------------------
|
|
|
|
-- Clipped view rectangle object --
|
|
|
|
-----------------------------------
|
2012-08-21 01:35:39 -06:00
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
ViewRect = defclass(ViewRect, nil)
|
|
|
|
|
|
|
|
function ViewRect:init(args)
|
|
|
|
if args.view_rect then
|
|
|
|
self:assign(args.view_rect)
|
|
|
|
else
|
|
|
|
local rect = args.rect or mkdims_wh(0,0,dscreen.getWindowSize())
|
|
|
|
local crect = args.clip_rect or rect
|
|
|
|
self:assign{
|
|
|
|
x1 = rect.x1, clip_x1 = crect.x1,
|
|
|
|
y1 = rect.y1, clip_y1 = crect.y1,
|
|
|
|
x2 = rect.x2, clip_x2 = crect.x2,
|
|
|
|
y2 = rect.y2, clip_y2 = crect.y2,
|
|
|
|
width = rect.x2-rect.x1+1,
|
|
|
|
height = rect.y2-rect.y1+1,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
if args.clip_view then
|
|
|
|
local cr = args.clip_view
|
|
|
|
self:assign{
|
|
|
|
clip_x1 = math.max(self.clip_x1, cr.clip_x1),
|
|
|
|
clip_y1 = math.max(self.clip_y1, cr.clip_y1),
|
|
|
|
clip_x2 = math.min(self.clip_x2, cr.clip_x2),
|
|
|
|
clip_y2 = math.min(self.clip_y2, cr.clip_y2),
|
|
|
|
}
|
|
|
|
end
|
2012-09-18 10:30:25 -06:00
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function ViewRect:isDefunct()
|
|
|
|
return (self.clip_x1 > self.clip_x2 or self.clip_y1 > self.clip_y2)
|
2012-08-21 01:35:39 -06:00
|
|
|
end
|
2012-08-19 07:53:25 -06:00
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function ViewRect:inClipGlobalXY(x,y)
|
|
|
|
return x >= self.clip_x1 and x <= self.clip_x2
|
|
|
|
and y >= self.clip_y1 and y <= self.clip_y2
|
2012-10-02 05:25:59 -06:00
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function ViewRect:inClipLocalXY(x,y)
|
|
|
|
return (x+self.x1) >= self.clip_x1 and (x+self.x1) <= self.clip_x2
|
|
|
|
and (y+self.y1) >= self.clip_y1 and (y+self.y1) <= self.clip_y2
|
2012-10-02 05:25:59 -06:00
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function ViewRect:localXY(x,y)
|
|
|
|
return x-self.x1, y-self.y1
|
2012-08-19 07:53:25 -06:00
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function ViewRect:globalXY(x,y)
|
|
|
|
return x+self.x1, y+self.y1
|
|
|
|
end
|
|
|
|
|
|
|
|
function ViewRect:viewport(x,y,w,h)
|
2012-09-05 09:45:45 -06:00
|
|
|
if type(x) == 'table' then
|
|
|
|
x,y,w,h = x.x1, x.y1, x.width, x.height
|
|
|
|
end
|
2012-08-21 09:40:37 -06:00
|
|
|
local x1,y1 = self.x1+x, self.y1+y
|
|
|
|
local x2,y2 = x1+w-1, y1+h-1
|
|
|
|
local vp = {
|
|
|
|
-- Logical viewport
|
|
|
|
x1 = x1, y1 = y1, x2 = x2, y2 = y2,
|
|
|
|
width = w, height = h,
|
|
|
|
-- Actual clipping rect
|
|
|
|
clip_x1 = math.max(self.clip_x1, x1),
|
|
|
|
clip_y1 = math.max(self.clip_y1, y1),
|
|
|
|
clip_x2 = math.min(self.clip_x2, x2),
|
|
|
|
clip_y2 = math.min(self.clip_y2, y2),
|
|
|
|
}
|
2012-10-15 05:30:00 -06:00
|
|
|
return mkinstance(ViewRect, vp)
|
|
|
|
end
|
|
|
|
|
|
|
|
----------------------------
|
|
|
|
-- Clipped painter object --
|
|
|
|
----------------------------
|
|
|
|
|
|
|
|
Painter = defclass(Painter, ViewRect)
|
|
|
|
|
|
|
|
function Painter:init(args)
|
|
|
|
self.x = self.x1
|
|
|
|
self.y = self.y1
|
2012-11-03 10:06:33 -06:00
|
|
|
self.cur_pen = to_pen(args.pen or COLOR_GREY)
|
|
|
|
self.cur_key_pen = to_pen(args.key_pen or COLOR_LIGHTGREEN)
|
2017-06-02 10:42:51 -06:00
|
|
|
self.to_map = false
|
2012-10-15 05:30:00 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function Painter.new(rect, pen)
|
|
|
|
return Painter{ rect = rect, pen = pen }
|
|
|
|
end
|
|
|
|
|
|
|
|
function Painter.new_view(view_rect, pen)
|
|
|
|
return Painter{ view_rect = view_rect, pen = pen }
|
|
|
|
end
|
|
|
|
|
|
|
|
function Painter.new_xy(x1,y1,x2,y2,pen)
|
|
|
|
return Painter{ rect = mkdims_xy(x1,y1,x2,y2), pen = pen }
|
|
|
|
end
|
|
|
|
|
|
|
|
function Painter.new_wh(x,y,w,h,pen)
|
|
|
|
return Painter{ rect = mkdims_wh(x,y,w,h), pen = pen }
|
|
|
|
end
|
|
|
|
|
|
|
|
function Painter:isValidPos()
|
|
|
|
return self:inClipGlobalXY(self.x, self.y)
|
|
|
|
end
|
|
|
|
|
|
|
|
function Painter:viewport(x,y,w,h)
|
2021-03-27 14:55:17 -06:00
|
|
|
local vp = ViewRect.viewport(self,x,y,w,h)
|
2012-10-15 05:30:00 -06:00
|
|
|
vp.cur_pen = self.cur_pen
|
2012-11-03 10:06:33 -06:00
|
|
|
vp.cur_key_pen = self.cur_key_pen
|
2012-08-21 09:40:37 -06:00
|
|
|
return mkinstance(Painter, vp):seek(0,0)
|
2012-08-21 01:35:39 -06:00
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function Painter:cursor()
|
|
|
|
return self.x - self.x1, self.y - self.y1
|
|
|
|
end
|
|
|
|
|
|
|
|
function Painter:cursorX()
|
2012-08-21 09:40:37 -06:00
|
|
|
return self.x - self.x1
|
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function Painter:cursorY()
|
2012-08-21 09:40:37 -06:00
|
|
|
return self.y - self.y1
|
2012-08-21 01:35:39 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function Painter:seek(x,y)
|
|
|
|
if x then self.x = self.x1 + x end
|
|
|
|
if y then self.y = self.y1 + y end
|
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
|
|
|
function Painter:advance(dx,dy)
|
|
|
|
if dx then self.x = self.x + dx end
|
|
|
|
if dy then self.y = self.y + dy end
|
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
|
|
|
function Painter:newline(dx)
|
|
|
|
self.y = self.y + 1
|
|
|
|
self.x = self.x1 + (dx or 0)
|
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
2012-08-21 09:40:37 -06:00
|
|
|
function Painter:pen(pen,...)
|
|
|
|
self.cur_pen = to_pen(self.cur_pen, pen, ...)
|
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
|
|
|
function Painter:color(fg,bold,bg)
|
2012-11-03 10:06:33 -06:00
|
|
|
self.cur_pen = to_pen(self.cur_pen, fg, bg, bold)
|
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
|
|
|
function Painter:key_pen(pen,...)
|
|
|
|
self.cur_key_pen = to_pen(self.cur_key_pen, pen, ...)
|
2012-08-21 01:35:39 -06:00
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
2017-06-02 10:42:51 -06:00
|
|
|
function Painter:map(to_map)
|
|
|
|
self.to_map = to_map
|
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
2012-08-21 01:35:39 -06:00
|
|
|
function Painter:clear()
|
2012-08-21 09:40:37 -06:00
|
|
|
dscreen.fillRect(CLEAR_PEN, self.clip_x1, self.clip_y1, self.clip_x2, self.clip_y2)
|
2012-08-21 01:35:39 -06:00
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
2012-08-21 09:40:37 -06:00
|
|
|
function Painter:fill(x1,y1,x2,y2,pen,bg,bold)
|
2012-08-21 01:35:39 -06:00
|
|
|
if type(x1) == 'table' then
|
2012-08-21 09:40:37 -06:00
|
|
|
x1, y1, x2, y2, pen, bg, bold = x1.x1, x1.y1, x1.x2, x1.y2, y1, x2, y2
|
2012-08-21 01:35:39 -06:00
|
|
|
end
|
2012-09-06 02:37:29 -06:00
|
|
|
x1 = math.max(x1+self.x1,self.clip_x1)
|
|
|
|
y1 = math.max(y1+self.y1,self.clip_y1)
|
|
|
|
x2 = math.min(x2+self.x1,self.clip_x2)
|
|
|
|
y2 = math.min(y2+self.y1,self.clip_y2)
|
2017-06-02 10:42:51 -06:00
|
|
|
dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2,self.to_map)
|
2012-08-21 01:35:39 -06:00
|
|
|
return self
|
|
|
|
end
|
|
|
|
|
2012-08-21 09:40:37 -06:00
|
|
|
function Painter:char(char,pen,...)
|
2012-08-21 01:35:39 -06:00
|
|
|
if self:isValidPos() then
|
2017-06-02 10:42:51 -06:00
|
|
|
dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char, nil, self.to_map)
|
2012-08-21 01:35:39 -06:00
|
|
|
end
|
|
|
|
return self:advance(1, nil)
|
|
|
|
end
|
|
|
|
|
2012-08-21 09:40:37 -06:00
|
|
|
function Painter:tile(char,tile,pen,...)
|
2012-08-21 01:35:39 -06:00
|
|
|
if self:isValidPos() then
|
2017-06-02 10:42:51 -06:00
|
|
|
dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char, tile, self.to_map)
|
2012-08-21 01:35:39 -06:00
|
|
|
end
|
|
|
|
return self:advance(1, nil)
|
|
|
|
end
|
|
|
|
|
2012-08-21 09:40:37 -06:00
|
|
|
function Painter:string(text,pen,...)
|
|
|
|
if self.y >= self.clip_y1 and self.y <= self.clip_y2 then
|
2012-08-21 01:35:39 -06:00
|
|
|
local dx = 0
|
2012-08-21 09:40:37 -06:00
|
|
|
if self.x < self.clip_x1 then
|
|
|
|
dx = self.clip_x1 - self.x
|
2012-08-21 01:35:39 -06:00
|
|
|
end
|
|
|
|
local len = #text
|
2012-08-21 09:40:37 -06:00
|
|
|
if self.x + len - 1 > self.clip_x2 then
|
|
|
|
len = self.clip_x2 - self.x + 1
|
2012-08-21 01:35:39 -06:00
|
|
|
end
|
|
|
|
if len > dx then
|
|
|
|
dscreen.paintString(
|
2012-08-21 09:40:37 -06:00
|
|
|
to_pen(self.cur_pen, pen, ...),
|
2012-08-21 01:35:39 -06:00
|
|
|
self.x+dx, self.y,
|
2017-06-02 10:42:51 -06:00
|
|
|
string.sub(text,dx+1,len),
|
|
|
|
self.to_map
|
2012-08-21 01:35:39 -06:00
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return self:advance(#text, nil)
|
|
|
|
end
|
|
|
|
|
2020-09-23 20:00:19 -06:00
|
|
|
function Painter:key(keycode,pen,...)
|
2012-10-02 05:25:59 -06:00
|
|
|
return self:string(
|
2020-09-23 20:00:19 -06:00
|
|
|
getKeyDisplay(keycode),
|
2012-11-03 10:06:33 -06:00
|
|
|
to_pen(self.cur_key_pen, pen, ...)
|
2012-10-02 05:25:59 -06:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2020-09-23 20:00:19 -06:00
|
|
|
function Painter:key_string(keycode, text, ...)
|
|
|
|
return self:key(keycode):string(': '):string(text, ...)
|
2017-03-01 13:56:50 -07:00
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
--------------------------
|
|
|
|
-- Abstract view object --
|
|
|
|
--------------------------
|
|
|
|
|
|
|
|
View = defclass(View)
|
|
|
|
|
|
|
|
View.ATTRS {
|
|
|
|
active = true,
|
2012-10-15 10:03:18 -06:00
|
|
|
visible = true,
|
|
|
|
view_id = DEFAULT_NIL,
|
2022-06-01 18:42:13 -06:00
|
|
|
on_focus = DEFAULT_NIL,
|
|
|
|
on_unfocus = DEFAULT_NIL,
|
2012-10-15 05:30:00 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
function View:init(args)
|
|
|
|
self.subviews = {}
|
2022-06-01 18:42:13 -06:00
|
|
|
self.focus_group = {self}
|
|
|
|
self.focus = false
|
|
|
|
end
|
|
|
|
|
|
|
|
local function inherit_focus_group(view, focus_group)
|
|
|
|
for _,child in ipairs(view.subviews) do
|
|
|
|
inherit_focus_group(child, focus_group)
|
|
|
|
end
|
|
|
|
view.focus_group = focus_group
|
2012-10-15 05:30:00 -06:00
|
|
|
end
|
|
|
|
|
2012-10-15 10:03:18 -06:00
|
|
|
function View:addviews(list)
|
2012-10-16 04:18:35 -06:00
|
|
|
if not list then return end
|
|
|
|
|
2012-10-15 10:03:18 -06:00
|
|
|
local sv = self.subviews
|
|
|
|
|
|
|
|
for _,obj in ipairs(list) do
|
2022-06-01 18:42:13 -06:00
|
|
|
-- absorb the focus groups of new children
|
|
|
|
for _,focus_obj in ipairs(obj.focus_group) do
|
|
|
|
table.insert(self.focus_group, focus_obj)
|
|
|
|
end
|
|
|
|
-- if the child's focus group has a focus owner, absorb it if we don't
|
|
|
|
-- already have one. otherwise keep the focus owner we have and clear
|
|
|
|
-- the focus of the child.
|
|
|
|
if obj.focus_group.cur then
|
|
|
|
if not self.focus_group.cur then
|
|
|
|
self.focus_group.cur = obj.focus_group.cur
|
|
|
|
else
|
|
|
|
obj.focus_group.cur:setFocus(false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- overwrite the child's focus_group hierarchy with ours
|
|
|
|
inherit_focus_group(obj, self.focus_group)
|
|
|
|
-- if we don't have a focus owner, give it to the new child if they want
|
|
|
|
if not self.focus_group.cur and obj:getPreferredFocusState() then
|
|
|
|
obj:setFocus(true)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- set ourselves as the parent view of the new child
|
|
|
|
obj.parent_view = self
|
|
|
|
|
|
|
|
-- add the subview to our child list
|
2012-10-15 10:03:18 -06:00
|
|
|
table.insert(sv, obj)
|
|
|
|
|
|
|
|
local id = obj.view_id
|
|
|
|
if id and type(id) ~= 'number' and sv[id] == nil then
|
|
|
|
sv[id] = obj
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for _,dir in ipairs(list) do
|
2012-10-16 08:33:00 -06:00
|
|
|
for id,obj in pairs(dir.subviews) do
|
2012-10-15 10:03:18 -06:00
|
|
|
if id and type(id) ~= 'number' and sv[id] == nil then
|
|
|
|
sv[id] = obj
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-06-01 18:42:13 -06:00
|
|
|
-- should be overridden by widgets that care about capturing keyboard focus
|
|
|
|
-- (e.g. widgets.EditField)
|
|
|
|
function View:getPreferredFocusState()
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
function View:setFocus(focus)
|
|
|
|
if focus then
|
|
|
|
if self.focus then return end -- nothing to do if we already have focus
|
|
|
|
if self.focus_group.cur then
|
|
|
|
-- steal focus from current owner
|
|
|
|
self.focus_group.cur:setFocus(false)
|
|
|
|
end
|
|
|
|
self.focus_group.cur = self
|
|
|
|
self.focus = true
|
|
|
|
if self.on_focus then
|
|
|
|
self.on_focus()
|
|
|
|
end
|
|
|
|
elseif self.focus then
|
|
|
|
self.focus = false
|
|
|
|
self.focus_group.cur = nil
|
|
|
|
if self.on_unfocus then
|
|
|
|
self.on_unfocus()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function View:getWindowSize()
|
|
|
|
local rect = self.frame_body
|
|
|
|
return rect.width, rect.height
|
|
|
|
end
|
|
|
|
|
2022-11-07 14:58:12 -07:00
|
|
|
function View:getMousePos(view_rect)
|
|
|
|
local rect = view_rect or self.frame_body
|
2012-10-15 05:30:00 -06:00
|
|
|
local x,y = dscreen.getMousePos()
|
2023-01-18 16:44:25 -07:00
|
|
|
if rect and x and rect:inClipGlobalXY(x,y) then
|
2012-10-15 05:30:00 -06:00
|
|
|
return rect:localXY(x,y)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-01-04 14:36:46 -07:00
|
|
|
function View:getMouseFramePos()
|
|
|
|
return self:getMousePos(ViewRect{rect=self.frame_rect})
|
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function View:computeFrame(parent_rect)
|
|
|
|
return mkdims_wh(0,0,parent_rect.width,parent_rect.height)
|
|
|
|
end
|
|
|
|
|
2012-10-15 10:03:18 -06:00
|
|
|
function View:updateSubviewLayout(frame_body)
|
|
|
|
for _,child in ipairs(self.subviews) do
|
|
|
|
child:updateLayout(frame_body)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function View:updateLayout(parent_rect)
|
|
|
|
if not parent_rect then
|
|
|
|
parent_rect = self.frame_parent_rect
|
|
|
|
else
|
|
|
|
self.frame_parent_rect = parent_rect
|
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
self:invoke_before('preUpdateLayout', parent_rect)
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
local frame_rect,body_rect = self:computeFrame(parent_rect)
|
|
|
|
|
|
|
|
self.frame_rect = frame_rect
|
|
|
|
self.frame_body = parent_rect:viewport(body_rect or frame_rect)
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
self:invoke_after('postComputeFrame', self.frame_body)
|
|
|
|
|
2012-10-15 10:03:18 -06:00
|
|
|
self:updateSubviewLayout(self.frame_body)
|
2012-10-15 05:30:00 -06:00
|
|
|
|
2012-10-15 10:03:18 -06:00
|
|
|
self:invoke_after('postUpdateLayout', self.frame_body)
|
2012-10-15 05:30:00 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function View:renderSubviews(dc)
|
|
|
|
for _,child in ipairs(self.subviews) do
|
2022-12-02 16:36:45 -07:00
|
|
|
if getval(child.visible) then
|
2012-10-15 05:30:00 -06:00
|
|
|
child:render(dc)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function View:render(dc)
|
2012-10-16 04:18:35 -06:00
|
|
|
self:onRenderFrame(dc, self.frame_rect)
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
local sub_dc = Painter{
|
|
|
|
view_rect = self.frame_body,
|
|
|
|
clip_view = dc
|
|
|
|
}
|
|
|
|
|
|
|
|
self:onRenderBody(sub_dc)
|
|
|
|
|
|
|
|
self:renderSubviews(sub_dc)
|
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
function View:onRenderFrame(dc,rect)
|
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function View:onRenderBody(dc)
|
|
|
|
end
|
|
|
|
|
2022-06-01 18:42:13 -06:00
|
|
|
-- Returns whether we should invoke the focus owner's onInput() function from
|
|
|
|
-- the given view's inputToSubviews() function. That is, returns true if:
|
|
|
|
-- - the view is not itself the focus owner since that would be an infinite loop
|
|
|
|
-- - the view is not a descendent of the focus owner (same as above)
|
|
|
|
-- - the focus owner and all of its ancestors are visible and active, since if
|
|
|
|
-- the focus owner is not (directly or transitively) visible or active, then
|
|
|
|
-- it shouldn't be getting input.
|
|
|
|
local function should_send_input_to_focus_owner(view, focus_owner)
|
|
|
|
local iter = view
|
|
|
|
while iter do
|
|
|
|
if iter == focus_owner then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
iter = iter.parent_view
|
|
|
|
end
|
|
|
|
iter = focus_owner
|
|
|
|
while iter do
|
2022-12-02 16:36:45 -07:00
|
|
|
if not getval(iter.visible) or not getval(iter.active) then
|
2022-06-01 18:42:13 -06:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
iter = iter.parent_view
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function View:inputToSubviews(keys)
|
2012-10-17 01:49:11 -06:00
|
|
|
local children = self.subviews
|
|
|
|
|
2022-06-01 18:42:13 -06:00
|
|
|
-- give focus owner first dibs on the input
|
|
|
|
local focus_owner = self.focus_group.cur
|
|
|
|
if focus_owner and should_send_input_to_focus_owner(self, focus_owner) and
|
|
|
|
focus_owner:onInput(keys) then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2012-10-17 01:49:11 -06:00
|
|
|
for i=#children,1,-1 do
|
|
|
|
local child = children[i]
|
2022-12-02 16:36:45 -07:00
|
|
|
if getval(child.visible) and getval(child.active)
|
|
|
|
and child ~= focus_owner and child:onInput(keys) then
|
2012-10-15 05:30:00 -06:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
function View:onInput(keys)
|
|
|
|
return self:inputToSubviews(keys)
|
|
|
|
end
|
|
|
|
|
2012-08-21 01:35:39 -06:00
|
|
|
------------------------
|
|
|
|
-- Base screen object --
|
|
|
|
------------------------
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
Screen = defclass(Screen, View)
|
2012-08-21 01:35:39 -06:00
|
|
|
|
2012-08-22 08:18:19 -06:00
|
|
|
Screen.text_input_mode = false
|
2022-12-30 23:13:53 -07:00
|
|
|
Screen.request_full_screen_refresh = false
|
2012-08-22 08:18:19 -06:00
|
|
|
|
2012-09-18 10:30:25 -06:00
|
|
|
function Screen:postinit()
|
2012-10-15 05:30:00 -06:00
|
|
|
self:onResize(dscreen.getWindowSize())
|
2012-08-24 03:28:34 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
Screen.isDismissed = dscreen.isDismissed
|
|
|
|
|
2012-08-19 07:53:25 -06:00
|
|
|
function Screen:isShown()
|
|
|
|
return self._native ~= nil
|
|
|
|
end
|
|
|
|
|
|
|
|
function Screen:isActive()
|
|
|
|
return self:isShown() and not self:isDismissed()
|
|
|
|
end
|
|
|
|
|
2012-08-24 03:28:34 -06:00
|
|
|
function Screen:invalidate()
|
|
|
|
dscreen.invalidate()
|
|
|
|
end
|
|
|
|
|
2012-08-19 07:53:25 -06:00
|
|
|
function Screen:renderParent()
|
|
|
|
if self._native and self._native.parent then
|
|
|
|
self._native.parent:render()
|
|
|
|
else
|
|
|
|
dscreen.clear()
|
|
|
|
end
|
2022-12-30 23:13:53 -07:00
|
|
|
if Screen.request_full_screen_refresh then
|
|
|
|
df.global.gps.force_full_display_count = 1
|
|
|
|
Screen.request_full_screen_refresh = false
|
|
|
|
end
|
2012-08-19 07:53:25 -06:00
|
|
|
end
|
|
|
|
|
2012-08-22 08:18:19 -06:00
|
|
|
function Screen:sendInputToParent(...)
|
2012-08-20 13:04:01 -06:00
|
|
|
if self._native and self._native.parent then
|
|
|
|
simulateInput(self._native.parent, ...)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-25 02:09:39 -06:00
|
|
|
function Screen:show(parent)
|
2012-08-21 01:35:39 -06:00
|
|
|
if self._native then
|
|
|
|
error("This screen is already on display")
|
|
|
|
end
|
2022-04-11 21:58:54 -06:00
|
|
|
self:onAboutToShow(parent or dfhack.gui.getCurViewscreen(true))
|
|
|
|
|
|
|
|
-- if we're autodetecting the parent, refresh the parent handle in case the
|
|
|
|
-- original parent has been dismissed by onAboutToShow()'s actions
|
2012-10-25 02:09:39 -06:00
|
|
|
parent = parent or dfhack.gui.getCurViewscreen(true)
|
|
|
|
if not dscreen.show(self, parent.child) then
|
2012-08-22 08:18:19 -06:00
|
|
|
error('Could not show screen')
|
2012-08-21 01:35:39 -06:00
|
|
|
end
|
2022-11-04 18:41:15 -06:00
|
|
|
return self
|
2012-08-21 01:35:39 -06:00
|
|
|
end
|
|
|
|
|
2012-10-25 02:09:39 -06:00
|
|
|
function Screen:onAboutToShow(parent)
|
2012-08-21 01:35:39 -06:00
|
|
|
end
|
|
|
|
|
2012-08-24 03:20:08 -06:00
|
|
|
function Screen:onShow()
|
2012-10-15 05:30:00 -06:00
|
|
|
self:onResize(dscreen.getWindowSize())
|
2012-08-21 01:35:39 -06:00
|
|
|
end
|
|
|
|
|
2012-08-22 12:28:52 -06:00
|
|
|
function Screen:dismiss()
|
2012-08-24 03:20:08 -06:00
|
|
|
if self._native then
|
2012-08-22 12:28:52 -06:00
|
|
|
dscreen.dismiss(self)
|
|
|
|
end
|
2022-12-30 23:20:50 -07:00
|
|
|
-- don't leave artifacts behind on the parent screen when we disappear
|
|
|
|
Screen.request_full_screen_refresh = true
|
2012-08-22 12:28:52 -06:00
|
|
|
end
|
|
|
|
|
2012-08-24 03:20:08 -06:00
|
|
|
function Screen:onDismiss()
|
2012-08-22 12:28:52 -06:00
|
|
|
end
|
|
|
|
|
2012-08-25 00:37:03 -06:00
|
|
|
function Screen:onDestroy()
|
|
|
|
end
|
|
|
|
|
2012-08-24 03:28:34 -06:00
|
|
|
function Screen:onResize(w,h)
|
2012-10-15 05:30:00 -06:00
|
|
|
self:updateLayout(ViewRect{ rect = mkdims_wh(0,0,w,h) })
|
2012-08-24 03:28:34 -06:00
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function Screen:onRender()
|
|
|
|
self:render(Painter.new())
|
2012-08-24 03:28:34 -06:00
|
|
|
end
|
|
|
|
|
2023-01-04 14:36:46 -07:00
|
|
|
-----------------------------
|
|
|
|
-- Z-order swapping screen --
|
|
|
|
-----------------------------
|
|
|
|
|
2023-01-27 15:57:45 -07:00
|
|
|
DEFAULT_INITIAL_PAUSE = true
|
|
|
|
|
2023-01-27 14:36:01 -07:00
|
|
|
local zscreen_inhibit_mouse_l = false
|
|
|
|
|
2023-02-13 18:35:02 -07:00
|
|
|
-- ensure underlying DF screens don't also react to handled clicks
|
|
|
|
function markMouseClicksHandled(keys)
|
|
|
|
if keys._MOUSE_L_DOWN then
|
|
|
|
-- note we can't clear mouse_lbut here. otherwise we break dragging,
|
|
|
|
df.global.enabler.mouse_lbut_down = 0
|
|
|
|
zscreen_inhibit_mouse_l = true
|
|
|
|
end
|
|
|
|
if keys._MOUSE_R_DOWN then
|
|
|
|
df.global.enabler.mouse_rbut_down = 0
|
|
|
|
df.global.enabler.mouse_rbut = 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-01-04 14:36:46 -07:00
|
|
|
ZScreen = defclass(ZScreen, Screen)
|
2023-01-23 18:40:16 -07:00
|
|
|
ZScreen.ATTRS{
|
2023-02-01 09:46:32 -07:00
|
|
|
defocusable=true,
|
2023-01-27 15:57:45 -07:00
|
|
|
initial_pause=DEFAULT_NIL,
|
2023-01-23 18:40:16 -07:00
|
|
|
force_pause=false,
|
|
|
|
pass_pause=true,
|
|
|
|
pass_movement_keys=false,
|
|
|
|
pass_mouse_clicks=true,
|
|
|
|
}
|
|
|
|
|
2023-01-27 15:57:45 -07:00
|
|
|
function ZScreen:preinit(args)
|
2023-02-13 10:26:13 -07:00
|
|
|
if self.ATTRS.initial_pause == nil then
|
2023-02-13 10:09:39 -07:00
|
|
|
args.initial_pause = DEFAULT_INITIAL_PAUSE or
|
2023-02-13 10:26:13 -07:00
|
|
|
(self.ATTRS.pass_mouse_clicks == false)
|
2023-01-27 15:57:45 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-01-23 18:40:16 -07:00
|
|
|
function ZScreen:init()
|
|
|
|
self.saved_pause_state = df.global.pause_state
|
2023-01-28 09:05:37 -07:00
|
|
|
if self.initial_pause and dfhack.isMapLoaded() then
|
2023-01-23 18:40:16 -07:00
|
|
|
df.global.pause_state = true
|
|
|
|
end
|
|
|
|
self.defocused = false
|
|
|
|
end
|
|
|
|
|
2023-01-23 18:46:03 -07:00
|
|
|
function ZScreen:dismiss()
|
|
|
|
ZScreen.super.dismiss(self)
|
2023-01-28 09:05:37 -07:00
|
|
|
if (self.force_pause or self.initial_pause) and dfhack.isMapLoaded() then
|
2023-01-23 18:40:16 -07:00
|
|
|
-- never go from unpaused to paused, just from paused to unpaused
|
2023-01-23 18:46:03 -07:00
|
|
|
df.global.pause_state = df.global.pause_state and self.saved_pause_state
|
2023-01-23 18:40:16 -07:00
|
|
|
end
|
|
|
|
end
|
2023-01-04 14:36:46 -07:00
|
|
|
|
2023-01-29 00:34:23 -07:00
|
|
|
local NO_LOGIC_SCREENS = {
|
2023-01-28 09:05:37 -07:00
|
|
|
'viewscreen_loadgamest',
|
2023-04-18 09:28:00 -06:00
|
|
|
'viewscreen_adopt_regionst',
|
2023-01-28 09:05:37 -07:00
|
|
|
'viewscreen_export_regionst',
|
|
|
|
'viewscreen_choose_game_typest',
|
2023-01-28 09:57:24 -07:00
|
|
|
'viewscreen_worldst',
|
2023-01-28 09:05:37 -07:00
|
|
|
}
|
2023-01-29 00:34:23 -07:00
|
|
|
for _,v in ipairs(NO_LOGIC_SCREENS) do
|
|
|
|
if not df[v] then
|
|
|
|
error('invalid class name: ' .. v)
|
|
|
|
end
|
|
|
|
NO_LOGIC_SCREENS[df[v]] = true
|
|
|
|
end
|
2023-01-28 09:05:37 -07:00
|
|
|
|
2023-01-23 18:40:16 -07:00
|
|
|
-- this is necessary for middle-click map scrolling to function
|
2023-01-04 14:36:46 -07:00
|
|
|
function ZScreen:onIdle()
|
2023-01-28 09:05:37 -07:00
|
|
|
if self.force_pause and dfhack.isMapLoaded() then
|
2023-01-23 18:40:16 -07:00
|
|
|
df.global.pause_state = true
|
|
|
|
end
|
2023-01-04 14:36:46 -07:00
|
|
|
if self._native and self._native.parent then
|
2023-01-29 00:34:23 -07:00
|
|
|
local vs_type = dfhack.gui.getDFViewscreen(true)._type
|
|
|
|
if NO_LOGIC_SCREENS[vs_type] then
|
2023-01-28 09:05:37 -07:00
|
|
|
self.force_pause = true
|
2023-01-28 09:57:24 -07:00
|
|
|
self.pass_movement_keys = false
|
|
|
|
self.pass_mouse_clicks = false
|
2023-01-28 09:05:37 -07:00
|
|
|
else
|
|
|
|
self._native.parent:logic()
|
|
|
|
end
|
2023-01-04 14:36:46 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function ZScreen:render(dc)
|
|
|
|
self:renderParent()
|
|
|
|
ZScreen.super.render(self, dc)
|
|
|
|
end
|
|
|
|
|
2023-01-23 18:40:16 -07:00
|
|
|
function ZScreen:hasFocus()
|
|
|
|
return not self.defocused
|
|
|
|
and dfhack.gui.getCurViewscreen(true) == self._native
|
2023-01-06 19:48:53 -07:00
|
|
|
end
|
|
|
|
|
2023-01-04 14:36:46 -07:00
|
|
|
function ZScreen:onInput(keys)
|
2023-01-23 18:40:16 -07:00
|
|
|
local has_mouse = self:isMouseOver()
|
|
|
|
if not self:hasFocus() then
|
2023-05-23 13:26:44 -06:00
|
|
|
if has_mouse and
|
|
|
|
(keys._MOUSE_L_DOWN or keys._MOUSE_R_DOWN or
|
2023-05-23 16:20:09 -06:00
|
|
|
keys.CONTEXT_SCROLL_UP or keys.CONTEXT_SCROLL_DOWN or
|
|
|
|
keys.CONTEXT_SCROLL_PAGEUP or keys.CONTEXT_SCROLL_PAGEDOWN) then
|
2023-01-04 14:36:46 -07:00
|
|
|
self:raise()
|
|
|
|
else
|
|
|
|
self:sendInputToParent(keys)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if ZScreen.super.onInput(self, keys) then
|
2023-02-13 18:35:02 -07:00
|
|
|
markMouseClicksHandled(keys)
|
2023-01-04 14:36:46 -07:00
|
|
|
return
|
|
|
|
end
|
2023-01-06 19:48:53 -07:00
|
|
|
|
2023-01-23 19:05:03 -07:00
|
|
|
if self.pass_mouse_clicks and keys._MOUSE_L_DOWN and not has_mouse then
|
2023-02-01 09:46:32 -07:00
|
|
|
self.defocused = self.defocusable
|
2023-01-23 18:40:16 -07:00
|
|
|
self:sendInputToParent(keys)
|
2023-01-06 19:48:53 -07:00
|
|
|
return
|
2023-01-23 18:40:16 -07:00
|
|
|
elseif keys.LEAVESCREEN or keys._MOUSE_R_DOWN then
|
2023-01-04 14:36:46 -07:00
|
|
|
self:dismiss()
|
2023-02-13 18:35:02 -07:00
|
|
|
markMouseClicksHandled(keys)
|
2023-01-04 14:36:46 -07:00
|
|
|
return
|
2023-01-23 18:40:16 -07:00
|
|
|
else
|
2023-01-27 14:36:01 -07:00
|
|
|
if zscreen_inhibit_mouse_l then
|
2023-01-24 22:28:25 -07:00
|
|
|
if keys._MOUSE_L then
|
|
|
|
return
|
|
|
|
else
|
2023-01-27 14:36:01 -07:00
|
|
|
zscreen_inhibit_mouse_l = false
|
2023-01-24 22:28:25 -07:00
|
|
|
end
|
|
|
|
end
|
2023-01-23 18:40:16 -07:00
|
|
|
local passit = self.pass_pause and keys.D_PAUSE
|
|
|
|
if not passit and self.pass_mouse_clicks then
|
2023-05-23 16:20:09 -06:00
|
|
|
if keys.CONTEXT_SCROLL_UP or keys.CONTEXT_SCROLL_DOWN or
|
|
|
|
keys.CONTEXT_SCROLL_PAGEUP or keys.CONTEXT_SCROLL_PAGEDOWN then
|
2023-05-23 13:26:44 -06:00
|
|
|
passit = true
|
|
|
|
else
|
|
|
|
for key in pairs(MOUSE_KEYS) do
|
|
|
|
if keys[key] then
|
|
|
|
passit = true
|
|
|
|
break
|
|
|
|
end
|
2023-01-23 18:40:16 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not passit and self.pass_movement_keys then
|
|
|
|
passit = require('gui.dwarfmode').getMapKey(keys)
|
|
|
|
end
|
|
|
|
if passit then
|
|
|
|
self:sendInputToParent(keys)
|
|
|
|
end
|
|
|
|
return
|
2023-01-04 14:36:46 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function ZScreen:raise()
|
2023-01-23 18:40:16 -07:00
|
|
|
if self:isDismissed() or self:hasFocus() then
|
2023-01-06 12:11:11 -07:00
|
|
|
return self
|
2023-01-04 14:36:46 -07:00
|
|
|
end
|
|
|
|
dscreen.raise(self)
|
2023-01-23 18:40:16 -07:00
|
|
|
self.defocused = false
|
2023-01-06 12:11:11 -07:00
|
|
|
return self
|
2023-01-04 14:36:46 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
function ZScreen:isMouseOver()
|
2023-01-06 19:48:53 -07:00
|
|
|
for _,sv in ipairs(self.subviews) do
|
|
|
|
if sv:getMouseFramePos() then return true end
|
|
|
|
end
|
2023-01-04 14:36:46 -07:00
|
|
|
end
|
|
|
|
|
2023-01-09 10:35:55 -07:00
|
|
|
local function zscreen_get_any(scr, thing)
|
|
|
|
if not scr._native or not scr._native.parent then return nil end
|
|
|
|
return dfhack.gui['getAny'..thing](scr._native.parent)
|
|
|
|
end
|
|
|
|
function ZScreen:onGetSelectedUnit()
|
|
|
|
return zscreen_get_any(self, 'Unit')
|
|
|
|
end
|
|
|
|
function ZScreen:onGetSelectedItem()
|
|
|
|
return zscreen_get_any(self, 'Item')
|
|
|
|
end
|
|
|
|
function ZScreen:onGetSelectedBuilding()
|
|
|
|
return zscreen_get_any(self, 'Building')
|
|
|
|
end
|
|
|
|
function ZScreen:onGetSelectedPlant()
|
|
|
|
return zscreen_get_any(self, 'Plant')
|
|
|
|
end
|
|
|
|
|
2023-04-24 15:32:33 -06:00
|
|
|
-- convenience subclass for modal dialogs
|
|
|
|
ZScreenModal = defclass(ZScreenModal, ZScreen)
|
|
|
|
ZScreenModal.ATTRS{
|
|
|
|
defocusable = false,
|
|
|
|
force_pause = true,
|
|
|
|
pass_pause = false,
|
|
|
|
pass_movement_keys = false,
|
|
|
|
pass_mouse_clicks = false,
|
|
|
|
}
|
|
|
|
|
2023-08-14 03:07:27 -06:00
|
|
|
-- DFHack textures
|
|
|
|
--------------------------
|
|
|
|
|
2023-08-14 04:04:38 -06:00
|
|
|
-- Preloaded DFHack Assets
|
2023-08-14 03:07:27 -06:00
|
|
|
-- Use this handles if you need to get dfhack standard textures
|
2023-08-13 23:17:57 -06:00
|
|
|
local texpos_handles = {
|
|
|
|
green_pin = dfhack.textures.loadTileset('hack/data/art/green-pin.png', 8, 12),
|
|
|
|
red_pin = dfhack.textures.loadTileset('hack/data/art/red-pin.png', 8, 12),
|
|
|
|
icons = dfhack.textures.loadTileset('hack/data/art/icons.png', 8, 12),
|
|
|
|
on_off = dfhack.textures.loadTileset('hack/data/art/on-off.png', 8, 12),
|
|
|
|
control_panel = dfhack.textures.loadTileset('hack/data/art/control-panel.png', 8, 12),
|
|
|
|
border_thin = dfhack.textures.loadTileset('hack/data/art/border-thin.png', 8, 12),
|
|
|
|
border_medium = dfhack.textures.loadTileset('hack/data/art/border-medium.png', 8, 12),
|
|
|
|
border_bold = dfhack.textures.loadTileset('hack/data/art/border-bold.png', 8, 12),
|
|
|
|
border_panel = dfhack.textures.loadTileset('hack/data/art/border-panel.png', 8, 12),
|
|
|
|
border_window = dfhack.textures.loadTileset('hack/data/art/border-window.png', 8, 12),
|
|
|
|
}
|
2023-01-23 03:25:16 -07:00
|
|
|
|
2023-08-14 04:04:38 -06:00
|
|
|
-- Methods to obtain valid texposes by handles
|
2023-08-14 03:07:27 -06:00
|
|
|
function tp_green_pin(offset)
|
|
|
|
return dfhack.textures.getTexposByHandle(texpos_handles.green_pin[offset])
|
|
|
|
end
|
|
|
|
function tp_red_pin(offset)
|
|
|
|
return dfhack.textures.getTexposByHandle(texpos_handles.red_pin[offset])
|
|
|
|
end
|
|
|
|
function tp_icons(offset)
|
|
|
|
return dfhack.textures.getTexposByHandle(texpos_handles.icons[offset])
|
|
|
|
end
|
|
|
|
function tp_on_off(offset)
|
|
|
|
return dfhack.textures.getTexposByHandle(texpos_handles.on_off[offset])
|
|
|
|
end
|
|
|
|
function tp_control_panel(offset)
|
|
|
|
return dfhack.textures.getTexposByHandle(texpos_handles.control_panel[offset])
|
|
|
|
end
|
2023-08-13 23:17:57 -06:00
|
|
|
function tp_border_thin(offset)
|
|
|
|
return dfhack.textures.getTexposByHandle(texpos_handles.border_thin[offset])
|
|
|
|
end
|
|
|
|
function tp_border_medium(offset)
|
|
|
|
return dfhack.textures.getTexposByHandle(texpos_handles.border_medium[offset])
|
|
|
|
end
|
|
|
|
function tp_border_bold(offset)
|
|
|
|
return dfhack.textures.getTexposByHandle(texpos_handles.border_bold[offset])
|
|
|
|
end
|
|
|
|
function tp_border_panel(offset)
|
|
|
|
return dfhack.textures.getTexposByHandle(texpos_handles.border_panel[offset])
|
|
|
|
end
|
|
|
|
function tp_border_window(offset)
|
|
|
|
return dfhack.textures.getTexposByHandle(texpos_handles.border_window[offset])
|
|
|
|
end
|
2023-08-14 03:07:27 -06:00
|
|
|
|
2023-08-14 04:04:38 -06:00
|
|
|
-- Framed screen object
|
|
|
|
--------------------------
|
|
|
|
|
|
|
|
-- Plain grey-colored frame.
|
|
|
|
-- deprecated
|
|
|
|
GREY_FRAME = {
|
|
|
|
frame_pen = to_pen{ ch = ' ', fg = COLOR_BLACK, bg = COLOR_GREY },
|
|
|
|
title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_WHITE },
|
|
|
|
signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
|
|
|
|
}
|
|
|
|
|
|
|
|
-- The boundary used by the pre-steam DF screens.
|
|
|
|
-- deprecated
|
|
|
|
BOUNDARY_FRAME = {
|
|
|
|
frame_pen = to_pen{ ch = 0xDB, fg = COLOR_GREY, bg = COLOR_BLACK },
|
|
|
|
title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
|
|
|
|
signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
|
|
|
|
}
|
|
|
|
|
|
|
|
local BASE_FRAME = {
|
|
|
|
frame_pen = to_pen{ ch=206, fg=COLOR_GREY, bg=COLOR_BLACK },
|
|
|
|
title_pen = to_pen{ fg=COLOR_BLACK, bg=COLOR_GREY },
|
|
|
|
inactive_title_pen = to_pen{ fg=COLOR_GREY, bg=COLOR_BLACK },
|
|
|
|
signature_pen = to_pen{ fg=COLOR_GREY, bg=COLOR_BLACK },
|
|
|
|
paused_pen = to_pen{fg=COLOR_RED, bg=COLOR_BLACK},
|
|
|
|
}
|
|
|
|
|
2023-08-13 23:17:57 -06:00
|
|
|
|
|
|
|
local function make_frame(tp, double_line)
|
2023-01-23 03:25:16 -07:00
|
|
|
local frame = copyall(BASE_FRAME)
|
2023-08-14 03:07:27 -06:00
|
|
|
frame.t_frame_pen = to_pen{ tile=curry(tp, 2), ch=double_line and 205 or 196, fg=COLOR_GREY, bg=COLOR_BLACK }
|
|
|
|
frame.l_frame_pen = to_pen{ tile=curry(tp, 8), ch=double_line and 186 or 179, fg=COLOR_GREY, bg=COLOR_BLACK }
|
|
|
|
frame.b_frame_pen = to_pen{ tile=curry(tp, 16), ch=double_line and 205 or 196, fg=COLOR_GREY, bg=COLOR_BLACK }
|
|
|
|
frame.r_frame_pen = to_pen{ tile=curry(tp, 10), ch=double_line and 186 or 179, fg=COLOR_GREY, bg=COLOR_BLACK }
|
|
|
|
frame.lt_frame_pen = to_pen{ tile=curry(tp, 1), ch=double_line and 201 or 218, fg=COLOR_GREY, bg=COLOR_BLACK }
|
|
|
|
frame.lb_frame_pen = to_pen{ tile=curry(tp, 15), ch=double_line and 200 or 192, fg=COLOR_GREY, bg=COLOR_BLACK }
|
|
|
|
frame.rt_frame_pen = to_pen{ tile=curry(tp, 3), ch=double_line and 187 or 191, fg=COLOR_GREY, bg=COLOR_BLACK }
|
|
|
|
frame.rb_frame_pen = to_pen{ tile=curry(tp, 17), ch=double_line and 188 or 217, fg=COLOR_GREY, bg=COLOR_BLACK }
|
2023-01-23 03:25:16 -07:00
|
|
|
return frame
|
|
|
|
end
|
|
|
|
|
2023-08-13 23:17:57 -06:00
|
|
|
function FRAME_WINDOW(resizable)
|
|
|
|
local frame = make_frame(tp_border_window, true)
|
|
|
|
if not resizable then
|
2023-08-14 03:07:27 -06:00
|
|
|
frame.rb_frame_pen = to_pen{ tile=curry(tp_border_panel, 17), ch=double_line and 188 or 217, fg=COLOR_GREY, bg=COLOR_BLACK }
|
2023-08-13 23:17:57 -06:00
|
|
|
end
|
|
|
|
return frame
|
|
|
|
end
|
|
|
|
function FRAME_PANEL(resizable)
|
|
|
|
return make_frame(tp_border_panel, false)
|
|
|
|
end
|
|
|
|
function FRAME_MEDIUM(resizable)
|
|
|
|
return make_frame(tp_border_medium, false)
|
|
|
|
end
|
|
|
|
function FRAME_BOLD(resizable)
|
|
|
|
return make_frame(tp_border_bold, true)
|
|
|
|
end
|
|
|
|
function FRAME_INTERIOR(resizable)
|
|
|
|
local frame = make_frame(tp_border_thin, false)
|
|
|
|
frame.signature_pen = false
|
|
|
|
return frame
|
|
|
|
end
|
|
|
|
function FRAME_INTERIOR_MEDIUM(resizable)
|
|
|
|
local frame = make_frame(tp_border_medium, false)
|
|
|
|
frame.signature_pen = false
|
|
|
|
return frame
|
|
|
|
end
|
2023-01-23 03:25:16 -07:00
|
|
|
|
2023-01-23 05:03:31 -07:00
|
|
|
-- for compatibility with pre-steam code
|
2023-05-18 03:22:58 -06:00
|
|
|
GREY_LINE_FRAME = FRAME_PANEL
|
|
|
|
|
|
|
|
-- for compatibility with deprecated frame naming scheme
|
|
|
|
WINDOW_FRAME = FRAME_WINDOW
|
|
|
|
PANEL_FRAME = FRAME_PANEL
|
|
|
|
MEDIUM_FRAME = FRAME_MEDIUM
|
|
|
|
BOLD_FRAME = FRAME_BOLD
|
|
|
|
INTERIOR_FRAME = FRAME_INTERIOR
|
|
|
|
INTERIOR_MEDIUM_FRAME = FRAME_INTERIOR_MEDIUM
|
|
|
|
|
2023-08-12 07:11:42 -06:00
|
|
|
function paint_frame(dc, rect, style, title, inactive, pause_forced, resizable)
|
2023-08-13 23:17:57 -06:00
|
|
|
if type(style) == 'function' then
|
|
|
|
style = style(resizable)
|
|
|
|
end
|
2012-08-21 09:40:37 -06:00
|
|
|
local pen = style.frame_pen
|
2022-12-14 13:04:29 -07:00
|
|
|
local x1,y1,x2,y2 = dc.x1+rect.x1, dc.y1+rect.y1, dc.x1+rect.x2, dc.y1+rect.y2
|
2012-08-21 09:40:37 -06:00
|
|
|
dscreen.paintTile(style.lt_frame_pen or pen, x1, y1)
|
|
|
|
dscreen.paintTile(style.rt_frame_pen or pen, x2, y1)
|
|
|
|
dscreen.paintTile(style.lb_frame_pen or pen, x1, y2)
|
2023-08-13 23:17:57 -06:00
|
|
|
dscreen.paintTile(style.rb_frame_pen or pen, x2, y2)
|
2012-08-21 09:40:37 -06:00
|
|
|
dscreen.fillRect(style.t_frame_pen or style.h_frame_pen or pen,x1+1,y1,x2-1,y1)
|
|
|
|
dscreen.fillRect(style.b_frame_pen or style.h_frame_pen or pen,x1+1,y2,x2-1,y2)
|
|
|
|
dscreen.fillRect(style.l_frame_pen or style.v_frame_pen or pen,x1,y1+1,x1,y2-1)
|
|
|
|
dscreen.fillRect(style.r_frame_pen or style.v_frame_pen or pen,x2,y1+1,x2,y2-1)
|
2022-11-28 16:04:28 -07:00
|
|
|
if style.signature_pen ~= false then
|
|
|
|
dscreen.paintString(style.signature_pen or style.title_pen or pen,x2-7,y2,"DFHack")
|
|
|
|
end
|
2012-08-19 07:53:25 -06:00
|
|
|
|
|
|
|
if title then
|
2012-08-21 09:40:37 -06:00
|
|
|
local x = math.max(0,math.floor((x2-x1-3-#title)/2)) + x1
|
|
|
|
local tstr = ' '..title..' '
|
|
|
|
if #tstr > x2-x1-1 then
|
|
|
|
tstr = string.sub(tstr,1,x2-x1-1)
|
|
|
|
end
|
2023-01-06 19:58:08 -07:00
|
|
|
dscreen.paintString(inactive and style.inactive_title_pen or style.title_pen or pen,
|
|
|
|
x, y1, tstr)
|
2012-08-19 07:53:25 -06:00
|
|
|
end
|
2023-01-06 19:48:53 -07:00
|
|
|
|
2023-01-23 18:40:16 -07:00
|
|
|
if pause_forced then
|
2023-02-01 15:01:29 -07:00
|
|
|
dscreen.paintString(style.paused_pen or style.title_pen or pen,
|
|
|
|
x1+2, y2, ' PAUSE FORCED ')
|
2023-01-06 19:48:53 -07:00
|
|
|
end
|
2012-08-19 07:53:25 -06:00
|
|
|
end
|
|
|
|
|
2012-08-21 09:40:37 -06:00
|
|
|
FramedScreen = defclass(FramedScreen, Screen)
|
|
|
|
|
2012-09-18 10:30:25 -06:00
|
|
|
FramedScreen.ATTRS{
|
|
|
|
frame_style = BOUNDARY_FRAME,
|
|
|
|
frame_title = DEFAULT_NIL,
|
|
|
|
frame_width = DEFAULT_NIL,
|
|
|
|
frame_height = DEFAULT_NIL,
|
2012-10-15 05:30:00 -06:00
|
|
|
frame_inset = 0,
|
2012-10-15 10:03:18 -06:00
|
|
|
frame_background = CLEAR_PEN,
|
2012-09-18 10:30:25 -06:00
|
|
|
}
|
2012-08-21 09:40:37 -06:00
|
|
|
|
2012-09-05 09:45:45 -06:00
|
|
|
function FramedScreen:getWantedFrameSize()
|
|
|
|
return self.frame_width, self.frame_height
|
|
|
|
end
|
|
|
|
|
2012-10-15 05:30:00 -06:00
|
|
|
function FramedScreen:computeFrame(parent_rect)
|
|
|
|
local sw, sh = parent_rect.width, parent_rect.height
|
2012-10-16 08:33:00 -06:00
|
|
|
local fw, fh = self:getWantedFrameSize(parent_rect)
|
2012-11-30 08:10:17 -07:00
|
|
|
return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1, true)
|
2012-08-21 09:40:37 -06:00
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
function FramedScreen:onRenderFrame(dc, rect)
|
2012-10-15 10:03:18 -06:00
|
|
|
if rect.wgap <= 0 and rect.hgap <= 0 then
|
2012-10-15 05:30:00 -06:00
|
|
|
dc:clear()
|
2012-08-19 07:53:25 -06:00
|
|
|
else
|
|
|
|
self:renderParent()
|
2012-10-15 10:03:18 -06:00
|
|
|
dc:fill(rect, self.frame_background)
|
2012-08-19 07:53:25 -06:00
|
|
|
end
|
2022-12-14 13:04:29 -07:00
|
|
|
paint_frame(dc,rect,self.frame_style,self.frame_title)
|
2012-08-24 03:28:34 -06:00
|
|
|
end
|
|
|
|
|
2023-02-16 22:43:03 -07:00
|
|
|
-- Inverts the brightness of the color, optionally taking a "bold" parameter,
|
|
|
|
-- which you should include if you're reading the fg color of a pen.
|
|
|
|
function invert_color(color, bold)
|
|
|
|
color = bold and (color + 8) or color
|
|
|
|
return (color + 8) % 16
|
|
|
|
end
|
2012-08-19 07:53:25 -06:00
|
|
|
return _ENV
|