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-08-24 03:28:34 -06:00
|
|
|
USE_GRAPHICS = dscreen.inGraphicsMode()
|
|
|
|
|
2012-11-03 10:06:33 -06:00
|
|
|
local to_pen = dfhack.pen.parse
|
|
|
|
|
|
|
|
CLEAR_PEN = to_pen{ch=32,fg=0,bg=0}
|
2012-08-20 13:04:01 -06:00
|
|
|
|
2012-11-29 02:37:16 -07:00
|
|
|
local FAKE_INPUT_KEYS = {
|
|
|
|
_MOUSE_L = true,
|
|
|
|
_MOUSE_R = true,
|
2012-12-02 03:43:23 -07:00
|
|
|
_MOUSE_L_DOWN = true,
|
|
|
|
_MOUSE_R_DOWN = true,
|
2012-11-29 02:37:16 -07:00
|
|
|
_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()
|
|
|
|
if rect and rect:inClipGlobalXY(x,y) then
|
|
|
|
return rect:localXY(x,y)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
|
2012-08-21 09:40:37 -06:00
|
|
|
------------------------
|
|
|
|
-- Framed screen object --
|
|
|
|
------------------------
|
|
|
|
|
2012-08-22 02:28:01 -06:00
|
|
|
-- Plain grey-colored frame.
|
2012-08-21 09:40:37 -06:00
|
|
|
GREY_FRAME = {
|
2012-11-03 10:06:33 -06:00
|
|
|
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 },
|
2012-08-22 02:28:01 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
-- The usual boundary used by the DF screens. Often has fancy pattern in tilesets.
|
|
|
|
BOUNDARY_FRAME = {
|
2012-11-03 10:06:33 -06:00
|
|
|
frame_pen = to_pen{ ch = 0xDB, fg = COLOR_DARKGREY, bg = COLOR_BLACK },
|
|
|
|
title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
|
|
|
|
signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_DARKGREY },
|
2012-08-21 09:40:37 -06:00
|
|
|
}
|
|
|
|
|
2012-08-22 03:06:06 -06:00
|
|
|
GREY_LINE_FRAME = {
|
2012-11-03 10:06:33 -06:00
|
|
|
frame_pen = to_pen{ ch = 206, fg = COLOR_GREY, bg = COLOR_BLACK },
|
|
|
|
h_frame_pen = to_pen{ ch = 205, fg = COLOR_GREY, bg = COLOR_BLACK },
|
|
|
|
v_frame_pen = to_pen{ ch = 186, fg = COLOR_GREY, bg = COLOR_BLACK },
|
|
|
|
lt_frame_pen = to_pen{ ch = 201, fg = COLOR_GREY, bg = COLOR_BLACK },
|
|
|
|
lb_frame_pen = to_pen{ ch = 200, fg = COLOR_GREY, bg = COLOR_BLACK },
|
|
|
|
rt_frame_pen = to_pen{ ch = 187, fg = COLOR_GREY, bg = COLOR_BLACK },
|
|
|
|
rb_frame_pen = to_pen{ ch = 188, fg = COLOR_GREY, bg = COLOR_BLACK },
|
|
|
|
title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
|
|
|
|
signature_pen = to_pen{ fg = COLOR_DARKGREY, bg = COLOR_BLACK },
|
2012-08-22 03:06:06 -06:00
|
|
|
}
|
|
|
|
|
2022-12-14 13:04:29 -07:00
|
|
|
function paint_frame(dc,rect,style,title)
|
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)
|
|
|
|
dscreen.paintTile(style.rb_frame_pen or pen, x2, y2)
|
|
|
|
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
|
|
|
|
dscreen.paintString(style.title_pen or pen, x, y1, tstr)
|
2012-08-19 07:53:25 -06:00
|
|
|
end
|
|
|
|
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
|
|
|
|
|
2012-08-19 07:53:25 -06:00
|
|
|
return _ENV
|