2012-10-15 10:03:18 -06:00
|
|
|
-- Simple widgets for screens
|
|
|
|
|
|
|
|
local _ENV = mkmodule('gui.widgets')
|
|
|
|
|
|
|
|
local gui = require('gui')
|
2022-12-12 19:07:05 -07:00
|
|
|
local guidm = require('gui.dwarfmode')
|
2012-10-15 10:03:18 -06:00
|
|
|
local utils = require('utils')
|
|
|
|
|
2022-12-02 16:36:45 -07:00
|
|
|
local getval = utils.getval
|
2022-12-30 04:28:59 -07:00
|
|
|
local to_pen = dfhack.pen.parse
|
2012-10-15 10:03:18 -06:00
|
|
|
|
2012-10-17 01:49:11 -06:00
|
|
|
local function show_view(view,vis)
|
2012-10-16 04:18:35 -06:00
|
|
|
if view then
|
|
|
|
view.visible = vis
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-17 00:41:50 -06:00
|
|
|
local function map_opttab(tab,idx)
|
|
|
|
if tab then
|
|
|
|
return tab[idx]
|
|
|
|
else
|
|
|
|
return idx
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-01-30 17:40:15 -07:00
|
|
|
STANDARDSCROLL = {
|
|
|
|
STANDARDSCROLL_UP = -1,
|
2022-12-30 02:13:13 -07:00
|
|
|
KEYBOARD_CURSOR_UP = -1,
|
2021-01-30 17:40:15 -07:00
|
|
|
STANDARDSCROLL_DOWN = 1,
|
2022-12-30 02:13:13 -07:00
|
|
|
KEYBOARD_CURSOR_DOWN = 1,
|
2021-01-30 17:40:15 -07:00
|
|
|
STANDARDSCROLL_PAGEUP = '-page',
|
2022-12-30 02:13:13 -07:00
|
|
|
KEYBOARD_CURSOR_UP_FAST = '-page',
|
2021-01-30 17:40:15 -07:00
|
|
|
STANDARDSCROLL_PAGEDOWN = '+page',
|
2022-12-30 02:13:13 -07:00
|
|
|
KEYBOARD_CURSOR_DOWN_FAST = '+page',
|
2021-01-30 17:40:15 -07:00
|
|
|
}
|
|
|
|
|
2012-10-15 10:03:18 -06:00
|
|
|
------------
|
|
|
|
-- Widget --
|
|
|
|
------------
|
|
|
|
|
|
|
|
Widget = defclass(Widget, gui.View)
|
|
|
|
|
|
|
|
Widget.ATTRS {
|
|
|
|
frame = DEFAULT_NIL,
|
|
|
|
frame_inset = DEFAULT_NIL,
|
|
|
|
frame_background = DEFAULT_NIL,
|
|
|
|
}
|
|
|
|
|
|
|
|
function Widget:computeFrame(parent_rect)
|
|
|
|
local sw, sh = parent_rect.width, parent_rect.height
|
|
|
|
return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset)
|
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
function Widget:onRenderFrame(dc, rect)
|
2012-10-15 10:03:18 -06:00
|
|
|
if self.frame_background then
|
2012-10-16 04:18:35 -06:00
|
|
|
dc:fill(rect, self.frame_background)
|
2012-10-15 10:03:18 -06:00
|
|
|
end
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
-----------
|
|
|
|
-- Panel --
|
|
|
|
-----------
|
|
|
|
|
2022-12-14 20:03:38 -07:00
|
|
|
DOUBLE_CLICK_MS = 500
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
Panel = defclass(Panel, Widget)
|
2012-10-15 10:03:18 -06:00
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
Panel.ATTRS {
|
2022-11-06 17:42:01 -07:00
|
|
|
frame_style = DEFAULT_NIL, -- as in gui.FramedScreen
|
|
|
|
frame_title = DEFAULT_NIL, -- as in gui.FramedScreen
|
2012-10-16 04:18:35 -06:00
|
|
|
on_render = DEFAULT_NIL,
|
2012-11-30 08:10:17 -07:00
|
|
|
on_layout = DEFAULT_NIL,
|
2022-12-12 19:07:05 -07:00
|
|
|
draggable = false,
|
2022-12-13 19:42:21 -07:00
|
|
|
drag_anchors = DEFAULT_NIL,
|
2022-12-12 19:07:05 -07:00
|
|
|
drag_bound = 'frame', -- or 'body'
|
|
|
|
on_drag_begin = DEFAULT_NIL,
|
|
|
|
on_drag_end = DEFAULT_NIL,
|
2022-12-13 19:15:11 -07:00
|
|
|
resizable = false,
|
2022-12-13 19:42:21 -07:00
|
|
|
resize_anchors = DEFAULT_NIL,
|
2022-12-13 19:15:11 -07:00
|
|
|
resize_min = DEFAULT_NIL,
|
|
|
|
on_resize_begin = DEFAULT_NIL,
|
|
|
|
on_resize_end = DEFAULT_NIL,
|
2022-04-11 19:25:00 -06:00
|
|
|
autoarrange_subviews = false, -- whether to automatically lay out subviews
|
|
|
|
autoarrange_gap = 0, -- how many blank lines to insert between widgets
|
2012-10-16 04:18:35 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
function Panel:init(args)
|
2022-12-13 19:42:21 -07:00
|
|
|
if not self.drag_anchors then
|
2023-05-23 16:17:01 -06:00
|
|
|
self.drag_anchors = {title=true, frame=not self.resizable, body=true}
|
2022-12-13 19:42:21 -07:00
|
|
|
end
|
|
|
|
if not self.resize_anchors then
|
|
|
|
self.resize_anchors = {t=false, l=true, r=true, b=true}
|
|
|
|
end
|
2022-12-13 19:15:11 -07:00
|
|
|
self.resize_min = self.resize_min or {}
|
|
|
|
self.resize_min.w = self.resize_min.w or (self.frame or {}).w or 5
|
|
|
|
self.resize_min.h = self.resize_min.h or (self.frame or {}).h or 5
|
|
|
|
|
|
|
|
self.kbd_get_pos = nil -- fn when we are in keyboard dragging mode
|
2022-12-12 19:07:05 -07:00
|
|
|
self.saved_frame = nil -- copy of frame when dragging started
|
|
|
|
self.saved_frame_rect = nil -- copy of frame_rect when dragging started
|
|
|
|
self.drag_offset = nil -- relative pos of held panel tile
|
2022-12-13 19:15:11 -07:00
|
|
|
self.resize_edge = nil -- which dimension is being resized?
|
2022-12-14 20:03:38 -07:00
|
|
|
|
|
|
|
self.last_title_click_ms = 0 -- used to track double-clicking on the title
|
2012-10-16 04:18:35 -06:00
|
|
|
self:addviews(args.subviews)
|
|
|
|
end
|
|
|
|
|
2022-12-12 19:07:05 -07:00
|
|
|
local function Panel_update_frame(self, frame, clear_state)
|
|
|
|
if clear_state then
|
2022-12-13 19:15:11 -07:00
|
|
|
self.kbd_get_pos = nil
|
2022-12-12 19:07:05 -07:00
|
|
|
self.saved_frame = nil
|
|
|
|
self.saved_frame_rect = nil
|
|
|
|
self.drag_offset = nil
|
2022-12-13 19:15:11 -07:00
|
|
|
self.resize_edge = nil
|
2022-12-12 19:07:05 -07:00
|
|
|
end
|
|
|
|
if not frame then return end
|
|
|
|
if self.frame.l == frame.l and self.frame.r == frame.r
|
|
|
|
and self.frame.t == frame.t and self.frame.b == frame.b
|
|
|
|
and self.frame.w == frame.w and self.frame.h == frame.h then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
self.frame = frame
|
|
|
|
self:updateLayout()
|
|
|
|
end
|
|
|
|
|
2022-12-13 19:15:11 -07:00
|
|
|
-- dim: the name of the dimension var (i.e. 'h' or 'w')
|
|
|
|
-- anchor: the name of the anchor var (i.e. 't', 'b', 'l', or 'r')
|
|
|
|
-- opposite_anchor: the name of the anchor var for the opposite edge
|
|
|
|
-- max_dim: how big this panel can get from its current pos and fit in parent
|
|
|
|
-- wanted_dim: how big the player is trying to make the panel
|
|
|
|
-- max_anchor: max value of the frame anchor for the edge that is being resized
|
|
|
|
-- wanted_anchor: how small the player is trying to make the anchor value
|
|
|
|
local function Panel_resize_edge_base(frame, resize_min, dim, anchor,
|
|
|
|
opposite_anchor, max_dim, wanted_dim,
|
|
|
|
max_anchor, wanted_anchor)
|
|
|
|
frame[dim] = math.max(resize_min[dim], math.min(max_dim, wanted_dim))
|
|
|
|
if frame[anchor] or not frame[opposite_anchor] then
|
|
|
|
frame[anchor] = math.max(0, math.min(max_anchor, wanted_anchor))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function Panel_resize_edge(frame, resize_min, dim, anchor,
|
|
|
|
opposite_anchor, dim_base, dim_ref, anchor_ref,
|
|
|
|
dim_far, mouse_ref)
|
|
|
|
local dim_sign = (anchor == 't' or anchor == 'l') and 1 or -1
|
|
|
|
local max_dim = dim_base - dim_ref + 1
|
|
|
|
local wanted_dim = dim_sign * (dim_far - mouse_ref) + 1
|
|
|
|
local max_anchor = dim_base - resize_min[dim] - dim_ref + 1
|
|
|
|
local wanted_anchor = dim_sign * (mouse_ref - anchor_ref)
|
|
|
|
Panel_resize_edge_base(frame, resize_min, dim, anchor, opposite_anchor,
|
|
|
|
max_dim, wanted_dim, max_anchor, wanted_anchor)
|
|
|
|
end
|
|
|
|
|
|
|
|
local function Panel_resize_frame(self, mouse_pos)
|
|
|
|
local frame, resize_min = copyall(self.frame), self.resize_min
|
|
|
|
local parent_rect = self.frame_parent_rect
|
|
|
|
local ref_rect = self.saved_frame_rect
|
|
|
|
if self.resize_edge:find('t') then
|
|
|
|
Panel_resize_edge(frame, resize_min, 'h', 't', 'b', ref_rect.y2,
|
|
|
|
parent_rect.y1, parent_rect.y1, ref_rect.y2, mouse_pos.y)
|
|
|
|
end
|
|
|
|
if self.resize_edge:find('b') then
|
|
|
|
Panel_resize_edge(frame, resize_min, 'h', 'b', 't', parent_rect.y2,
|
|
|
|
ref_rect.y1, parent_rect.y2, ref_rect.y1, mouse_pos.y)
|
|
|
|
end
|
|
|
|
if self.resize_edge:find('l') then
|
|
|
|
Panel_resize_edge(frame, resize_min, 'w', 'l', 'r', ref_rect.x2,
|
|
|
|
parent_rect.x1, parent_rect.x1, ref_rect.x2, mouse_pos.x)
|
|
|
|
end
|
|
|
|
if self.resize_edge:find('r') then
|
|
|
|
Panel_resize_edge(frame, resize_min, 'w', 'r', 'l', parent_rect.x2,
|
|
|
|
ref_rect.x1, parent_rect.x2, ref_rect.x1, mouse_pos.x)
|
|
|
|
end
|
|
|
|
return frame
|
|
|
|
end
|
|
|
|
|
2022-12-12 19:07:05 -07:00
|
|
|
local function Panel_drag_frame(self, mouse_pos)
|
|
|
|
local frame = copyall(self.frame)
|
|
|
|
local parent_rect, frame_rect = self.frame_parent_rect, self.frame_rect
|
|
|
|
local bound_rect = self.drag_bound == 'body' and self.frame_body
|
|
|
|
or frame_rect
|
|
|
|
local offset = self.drag_offset
|
|
|
|
local max_width = parent_rect.width - (bound_rect.x2-frame_rect.x1+1)
|
|
|
|
local max_height = parent_rect.height - (bound_rect.y2-frame_rect.y1+1)
|
|
|
|
if frame.t or not frame.b then
|
|
|
|
local min_pos = frame_rect.y1 - bound_rect.y1
|
2022-12-13 13:39:17 -07:00
|
|
|
local requested_pos = mouse_pos.y - parent_rect.y1 - offset.y
|
|
|
|
frame.t = math.max(min_pos, math.min(max_height, requested_pos))
|
2022-12-12 19:07:05 -07:00
|
|
|
end
|
|
|
|
if frame.b or not frame.t then
|
|
|
|
local min_pos = bound_rect.y2 - frame_rect.y2
|
2022-12-13 13:39:17 -07:00
|
|
|
local requested_pos = parent_rect.y2 - mouse_pos.y + offset.y -
|
|
|
|
(frame_rect.y2 - frame_rect.y1)
|
|
|
|
frame.b = math.max(min_pos, math.min(max_height, requested_pos))
|
2022-12-12 19:07:05 -07:00
|
|
|
end
|
|
|
|
if frame.l or not frame.r then
|
|
|
|
local min_pos = frame_rect.x1 - bound_rect.x1
|
2022-12-13 13:39:17 -07:00
|
|
|
local requested_pos = mouse_pos.x - parent_rect.x1 - offset.x
|
|
|
|
frame.l = math.max(min_pos, math.min(max_width, requested_pos))
|
2022-12-12 19:07:05 -07:00
|
|
|
end
|
|
|
|
if frame.r or not frame.l then
|
|
|
|
local min_pos = bound_rect.x2 - frame_rect.x2
|
2022-12-13 13:39:17 -07:00
|
|
|
local requested_pos = parent_rect.x2 - mouse_pos.x + offset.x -
|
|
|
|
(frame_rect.x2 - frame_rect.x1)
|
|
|
|
frame.r = math.max(min_pos, math.min(max_width, requested_pos))
|
2022-12-12 19:07:05 -07:00
|
|
|
end
|
|
|
|
return frame
|
|
|
|
end
|
|
|
|
|
|
|
|
local function Panel_make_frame(self, mouse_pos)
|
|
|
|
mouse_pos = mouse_pos or xy2pos(dfhack.screen.getMousePos())
|
2022-12-13 19:15:11 -07:00
|
|
|
return self.resize_edge and Panel_resize_frame(self, mouse_pos)
|
|
|
|
or Panel_drag_frame(self, mouse_pos)
|
2022-12-12 19:07:05 -07:00
|
|
|
end
|
|
|
|
|
2022-12-13 19:15:11 -07:00
|
|
|
local function Panel_begin_drag(self, drag_offset, resize_edge)
|
2022-12-12 19:07:05 -07:00
|
|
|
Panel_update_frame(self, nil, true)
|
|
|
|
self.drag_offset = drag_offset or {x=0, y=0}
|
2022-12-13 19:15:11 -07:00
|
|
|
self.resize_edge = resize_edge
|
2022-12-12 19:07:05 -07:00
|
|
|
self.saved_frame = copyall(self.frame)
|
|
|
|
self.saved_frame_rect = copyall(self.frame_rect)
|
|
|
|
self.prev_focus_owner = self.focus_group.cur
|
|
|
|
self:setFocus(true)
|
2022-12-13 19:15:11 -07:00
|
|
|
if self.resize_edge then
|
2023-08-07 18:02:55 -06:00
|
|
|
self:onResizeBegin()
|
2022-12-13 19:15:11 -07:00
|
|
|
else
|
2023-08-07 18:02:55 -06:00
|
|
|
self:onDragBegin()
|
2022-12-13 19:15:11 -07:00
|
|
|
end
|
2022-12-12 19:07:05 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
local function Panel_end_drag(self, frame, success)
|
2022-12-13 19:15:11 -07:00
|
|
|
success = not not success
|
2022-12-12 19:07:05 -07:00
|
|
|
if self.prev_focus_owner then
|
|
|
|
self.prev_focus_owner:setFocus(true)
|
|
|
|
else
|
|
|
|
self:setFocus(false)
|
|
|
|
end
|
2023-08-07 17:01:56 -06:00
|
|
|
local resize_edge = self.resize_edge
|
2022-12-12 19:07:05 -07:00
|
|
|
Panel_update_frame(self, frame, true)
|
2023-08-07 17:01:56 -06:00
|
|
|
if resize_edge then
|
2023-08-07 18:02:55 -06:00
|
|
|
self:onResizeEnd(success, self.frame)
|
2022-12-13 19:15:11 -07:00
|
|
|
else
|
2023-08-07 18:02:55 -06:00
|
|
|
self:onDragEnd(success, self.frame)
|
2022-12-13 19:15:11 -07:00
|
|
|
end
|
2022-12-12 19:07:05 -07:00
|
|
|
end
|
|
|
|
|
2022-12-14 20:03:38 -07:00
|
|
|
local function Panel_on_double_click(self)
|
|
|
|
local a = self.resize_anchors
|
|
|
|
local can_vert, can_horiz = a.t or a.b, a.l or a.r
|
|
|
|
if not can_vert and not can_horiz then return false end
|
|
|
|
local f, rmin = self.frame, self.resize_min
|
|
|
|
local maximized = f.t == 0 and f.b == 0 and f.l == 0 and f.r == 0
|
|
|
|
local frame
|
|
|
|
if maximized then
|
|
|
|
frame = {
|
|
|
|
t=not can_vert and f.t or nil,
|
|
|
|
l=not can_horiz and f.l or nil,
|
|
|
|
b=not can_vert and f.b or nil,
|
|
|
|
r=not can_horiz and f.r or nil,
|
|
|
|
w=can_vert and rmin.w or f.w,
|
|
|
|
h=can_horiz and rmin.h or f.h,
|
|
|
|
}
|
|
|
|
else
|
|
|
|
frame = {
|
|
|
|
t=can_vert and 0 or f.t,
|
|
|
|
l=can_horiz and 0 or f.l,
|
|
|
|
b=can_vert and 0 or f.b,
|
|
|
|
r=can_horiz and 0 or f.r
|
|
|
|
}
|
|
|
|
end
|
|
|
|
Panel_update_frame(self, frame, true)
|
2022-12-12 19:07:05 -07:00
|
|
|
end
|
|
|
|
|
|
|
|
function Panel:onInput(keys)
|
2022-12-13 19:15:11 -07:00
|
|
|
if self.kbd_get_pos then
|
2023-01-09 13:52:26 -07:00
|
|
|
if keys.SELECT or keys.LEAVESCREEN or keys._MOUSE_R_DOWN then
|
|
|
|
Panel_end_drag(self, not keys.SELECT and self.saved_frame or nil,
|
2022-12-13 13:39:17 -07:00
|
|
|
not not keys.SELECT)
|
2022-12-12 19:07:05 -07:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
for code in pairs(keys) do
|
|
|
|
local dx, dy = guidm.get_movement_delta(code, 1, 10)
|
|
|
|
if dx then
|
2022-12-13 19:15:11 -07:00
|
|
|
local frame_rect = self.frame_rect
|
|
|
|
local kbd_pos = self.kbd_get_pos()
|
|
|
|
kbd_pos.x = kbd_pos.x + dx
|
|
|
|
kbd_pos.y = kbd_pos.y + dy
|
2022-12-12 19:07:05 -07:00
|
|
|
Panel_update_frame(self, Panel_make_frame(self, kbd_pos))
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return
|
|
|
|
end
|
|
|
|
if self.drag_offset then
|
|
|
|
if keys._MOUSE_R_DOWN then
|
|
|
|
Panel_end_drag(self, self.saved_frame)
|
|
|
|
elseif keys._MOUSE_L then
|
|
|
|
Panel_update_frame(self, Panel_make_frame(self))
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
2023-01-09 13:52:26 -07:00
|
|
|
if Panel.super.onInput(self, keys) then
|
2022-12-12 19:07:05 -07:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
if not keys._MOUSE_L_DOWN then return end
|
2023-01-04 14:36:46 -07:00
|
|
|
local x,y = self:getMouseFramePos()
|
2022-12-12 19:07:05 -07:00
|
|
|
if not x then return end
|
|
|
|
|
2022-12-14 20:03:38 -07:00
|
|
|
if self.resizable and y == 0 then
|
|
|
|
local now_ms = dfhack.getTickCount()
|
|
|
|
if now_ms - self.last_title_click_ms <= DOUBLE_CLICK_MS then
|
|
|
|
self.last_title_click_ms = 0
|
|
|
|
if Panel_on_double_click(self) then return true end
|
|
|
|
else
|
|
|
|
self.last_title_click_ms = now_ms
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local resize_edge = nil
|
2022-12-13 19:15:11 -07:00
|
|
|
if self.resizable then
|
|
|
|
local rect = self.frame_rect
|
|
|
|
if self.resize_anchors.r and self.resize_anchors.b
|
|
|
|
and x == rect.x2 - rect.x1 and y == rect.y2 - rect.y1 then
|
2022-12-14 20:03:38 -07:00
|
|
|
resize_edge = 'rb'
|
2022-12-13 19:15:11 -07:00
|
|
|
elseif self.resize_anchors.l and self.resize_anchors.b
|
|
|
|
and x == 0 and y == rect.y2 - rect.y1 then
|
2022-12-14 20:03:38 -07:00
|
|
|
resize_edge = 'lb'
|
2022-12-13 19:15:11 -07:00
|
|
|
elseif self.resize_anchors.r and self.resize_anchors.t
|
|
|
|
and x == rect.x2 - rect.x1 and y == 0 then
|
2022-12-14 20:03:38 -07:00
|
|
|
resize_edge = 'rt'
|
2022-12-13 19:15:11 -07:00
|
|
|
elseif self.resize_anchors.r and self.resize_anchors.t
|
|
|
|
and x == 0 and y == 0 then
|
2022-12-14 20:03:38 -07:00
|
|
|
resize_edge = 'lt'
|
2022-12-13 19:15:11 -07:00
|
|
|
elseif self.resize_anchors.r and x == rect.x2 - rect.x1 then
|
2022-12-14 20:03:38 -07:00
|
|
|
resize_edge = 'r'
|
2022-12-13 19:15:11 -07:00
|
|
|
elseif self.resize_anchors.l and x == 0 then
|
2022-12-14 20:03:38 -07:00
|
|
|
resize_edge = 'l'
|
2022-12-13 19:15:11 -07:00
|
|
|
elseif self.resize_anchors.b and y == rect.y2 - rect.y1 then
|
2022-12-14 20:03:38 -07:00
|
|
|
resize_edge = 'b'
|
2022-12-13 19:15:11 -07:00
|
|
|
elseif self.resize_anchors.t and y == 0 then
|
2022-12-14 20:03:38 -07:00
|
|
|
resize_edge = 't'
|
2022-12-13 19:15:11 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-12-12 19:07:05 -07:00
|
|
|
local is_dragging = false
|
2022-12-14 20:03:38 -07:00
|
|
|
if not resize_edge and self.draggable then
|
2022-12-12 19:07:05 -07:00
|
|
|
local on_body = self:getMousePos()
|
|
|
|
is_dragging = (self.drag_anchors.title and self.frame_style and y == 0)
|
|
|
|
or (self.drag_anchors.frame and not on_body) -- includes inset
|
|
|
|
or (self.drag_anchors.body and on_body)
|
|
|
|
end
|
|
|
|
|
2022-12-14 20:03:38 -07:00
|
|
|
if resize_edge or is_dragging then
|
|
|
|
Panel_begin_drag(self, {x=x, y=y}, resize_edge)
|
2022-12-12 19:07:05 -07:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-12-13 14:01:49 -07:00
|
|
|
function Panel:setKeyboardDragEnabled(enabled)
|
2022-12-13 19:15:11 -07:00
|
|
|
if (enabled and self.kbd_get_pos)
|
|
|
|
or (not enabled and not self.kbd_get_pos) then
|
2022-12-12 19:07:05 -07:00
|
|
|
return
|
|
|
|
end
|
|
|
|
if enabled then
|
2022-12-30 05:40:50 -07:00
|
|
|
local kbd_get_pos = function()
|
|
|
|
return {x=self.frame_rect.x1, y=self.frame_rect.y1}
|
|
|
|
end
|
|
|
|
Panel_begin_drag(self)
|
2022-12-13 19:15:11 -07:00
|
|
|
self.kbd_get_pos = kbd_get_pos
|
|
|
|
else
|
|
|
|
Panel_end_drag(self)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-12-13 19:42:21 -07:00
|
|
|
local function Panel_get_resize_data(self)
|
|
|
|
local resize_anchors = self.resize_anchors
|
|
|
|
if resize_anchors.r and resize_anchors.b then
|
|
|
|
return 'rb', function()
|
|
|
|
return {x=self.frame_rect.x2, y=self.frame_rect.y2} end
|
|
|
|
elseif resize_anchors.l and resize_anchors.b then
|
|
|
|
return 'lb', function()
|
|
|
|
return {x=self.frame_rect.x1, y=self.frame_rect.y2} end
|
|
|
|
elseif resize_anchors.r and resize_anchors.t then
|
|
|
|
return 'rt', function()
|
|
|
|
return {x=self.frame_rect.x2, y=self.frame_rect.y1} end
|
|
|
|
elseif resize_anchors.l and resize_anchors.t then
|
|
|
|
return 'lt', function()
|
|
|
|
return {x=self.frame_rect.x1, y=self.frame_rect.y1} end
|
|
|
|
elseif resize_anchors.b then
|
|
|
|
return 'b', function()
|
|
|
|
return {x=(self.frame_rect.x1+self.frame_rect.x2)/2,
|
|
|
|
y=self.frame_rect.y2} end
|
|
|
|
elseif resize_anchors.r then
|
|
|
|
return 'r', function()
|
|
|
|
return {x=self.frame_rect.x2,
|
|
|
|
y=(self.frame_rect.y1+self.frame_rect.y2)/2} end
|
|
|
|
elseif resize_anchors.l then
|
|
|
|
return 'l', function()
|
|
|
|
return {x=self.frame_rect.x1,
|
|
|
|
y=(self.frame_rect.y1+self.frame_rect.y2)/2} end
|
|
|
|
elseif resize_anchors.t then
|
|
|
|
return 't', function()
|
|
|
|
return {x=(self.frame_rect.x1+self.frame_rect.x2)/2,
|
|
|
|
y=self.frame_rect.y1} end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-12-13 19:15:11 -07:00
|
|
|
function Panel:setKeyboardResizeEnabled(enabled)
|
|
|
|
if (enabled and self.kbd_get_pos)
|
|
|
|
or (not enabled and not self.kbd_get_pos) then
|
2022-12-12 19:07:05 -07:00
|
|
|
return
|
|
|
|
end
|
|
|
|
if enabled then
|
2022-12-13 19:42:21 -07:00
|
|
|
local resize_edge, kbd_get_pos = Panel_get_resize_data(self)
|
|
|
|
if not resize_edge then
|
|
|
|
dfhack.printerr('cannot resize window: no anchors are enabled')
|
|
|
|
else
|
|
|
|
Panel_begin_drag(self, kbd_get_pos(), resize_edge)
|
|
|
|
self.kbd_get_pos = kbd_get_pos
|
2022-12-13 19:15:11 -07:00
|
|
|
end
|
2022-12-12 19:07:05 -07:00
|
|
|
else
|
|
|
|
Panel_end_drag(self)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
function Panel:onRenderBody(dc)
|
|
|
|
if self.on_render then self.on_render(dc) end
|
|
|
|
end
|
|
|
|
|
2022-11-06 17:42:01 -07:00
|
|
|
function Panel:computeFrame(parent_rect)
|
|
|
|
local sw, sh = parent_rect.width, parent_rect.height
|
2023-01-15 21:41:48 -07:00
|
|
|
if self.frame then
|
|
|
|
if self.frame.t and self.frame.h and self.frame.t + self.frame.h > sh then
|
|
|
|
self.frame.t = math.max(0, sh - self.frame.h)
|
|
|
|
end
|
|
|
|
if self.frame.b and self.frame.h and self.frame.b + self.frame.h > sh then
|
|
|
|
self.frame.b = math.max(0, sh - self.frame.h)
|
|
|
|
end
|
|
|
|
if self.frame.l and self.frame.w and self.frame.l + self.frame.w > sw then
|
|
|
|
self.frame.l = math.max(0, sw - self.frame.w)
|
|
|
|
end
|
|
|
|
if self.frame.r and self.frame.w and self.frame.r + self.frame.w > sw then
|
|
|
|
self.frame.r = math.max(0, sw - self.frame.w)
|
|
|
|
end
|
|
|
|
end
|
2022-11-06 17:42:01 -07:00
|
|
|
return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset,
|
|
|
|
self.frame_style and 1 or 0)
|
|
|
|
end
|
|
|
|
|
2012-11-30 08:10:17 -07:00
|
|
|
function Panel:postComputeFrame(body)
|
|
|
|
if self.on_layout then self.on_layout(body) end
|
|
|
|
end
|
|
|
|
|
2022-04-11 19:25:00 -06:00
|
|
|
-- if self.autoarrange_subviews is true, lay out visible subviews vertically,
|
|
|
|
-- adding gaps between widgets according to self.autoarrange_gap.
|
|
|
|
function Panel:postUpdateLayout()
|
2022-12-30 21:01:09 -07:00
|
|
|
-- don't leave artifacts behind on the parent screen when we move
|
2022-12-30 23:13:53 -07:00
|
|
|
gui.Screen.request_full_screen_refresh = true
|
2022-12-30 21:01:09 -07:00
|
|
|
|
2022-04-11 19:25:00 -06:00
|
|
|
if not self.autoarrange_subviews then return end
|
|
|
|
|
|
|
|
local gap = self.autoarrange_gap
|
|
|
|
local y = 0
|
|
|
|
for _,subview in ipairs(self.subviews) do
|
2022-04-17 21:07:47 -06:00
|
|
|
if not subview.frame then goto continue end
|
2022-04-11 19:25:00 -06:00
|
|
|
subview.frame.t = y
|
2022-12-02 16:36:45 -07:00
|
|
|
if getval(subview.visible) then
|
2022-04-17 21:07:47 -06:00
|
|
|
y = y + (subview.frame.h or 0) + gap
|
2022-04-11 19:25:00 -06:00
|
|
|
end
|
2022-04-17 21:07:47 -06:00
|
|
|
::continue::
|
2022-04-11 19:25:00 -06:00
|
|
|
end
|
|
|
|
self.frame_rect.height = y
|
|
|
|
|
|
|
|
-- let widgets adjust to their new positions
|
|
|
|
self:updateSubviewLayout()
|
|
|
|
end
|
|
|
|
|
2022-11-06 17:42:01 -07:00
|
|
|
function Panel:onRenderFrame(dc, rect)
|
|
|
|
Panel.super.onRenderFrame(self, dc, rect)
|
|
|
|
if not self.frame_style then return end
|
2023-01-23 18:40:16 -07:00
|
|
|
local inactive = self.parent_view and self.parent_view.hasFocus
|
|
|
|
and not self.parent_view:hasFocus()
|
|
|
|
local pause_forced = self.parent_view and self.parent_view.force_pause
|
|
|
|
gui.paint_frame(dc, rect, self.frame_style, self.frame_title, inactive,
|
2023-01-27 15:20:21 -07:00
|
|
|
pause_forced, self.resizable)
|
2022-12-13 19:42:21 -07:00
|
|
|
if self.kbd_get_pos then
|
|
|
|
local pos = self.kbd_get_pos()
|
2022-12-30 04:28:59 -07:00
|
|
|
local pen = to_pen{fg=COLOR_GREEN, bg=COLOR_BLACK}
|
2022-12-13 19:42:21 -07:00
|
|
|
dc:seek(pos.x, pos.y):pen(pen):char(string.char(0xDB))
|
|
|
|
end
|
2022-12-13 19:15:11 -07:00
|
|
|
if self.drag_offset and not self.kbd_get_pos
|
2022-12-12 19:07:05 -07:00
|
|
|
and df.global.enabler.mouse_lbut == 0 then
|
|
|
|
Panel_end_drag(self, nil, true)
|
|
|
|
end
|
2022-11-06 17:42:01 -07:00
|
|
|
end
|
|
|
|
|
2023-08-07 18:02:55 -06:00
|
|
|
function Panel:onDragBegin()
|
|
|
|
if self.on_drag_begin then self.on_drag_begin() end
|
|
|
|
end
|
|
|
|
|
|
|
|
function Panel:onDragEnd(success, new_frame)
|
|
|
|
if self.on_drag_end then self.on_drag_end(success, new_frame) end
|
|
|
|
end
|
|
|
|
|
|
|
|
function Panel:onResizeBegin()
|
|
|
|
if self.on_resize_begin then self.on_resize_begin() end
|
|
|
|
end
|
|
|
|
|
|
|
|
function Panel:onResizeEnd(success, new_frame)
|
|
|
|
if self.on_resize_end then self.on_resize_end(success, new_frame) end
|
|
|
|
end
|
|
|
|
|
2022-12-14 13:04:29 -07:00
|
|
|
------------
|
|
|
|
-- Window --
|
|
|
|
------------
|
|
|
|
|
|
|
|
Window = defclass(Window, Panel)
|
|
|
|
|
|
|
|
Window.ATTRS {
|
2023-01-23 05:16:38 -07:00
|
|
|
frame_style = gui.WINDOW_FRAME,
|
2022-12-14 13:04:29 -07:00
|
|
|
frame_background = gui.CLEAR_PEN,
|
|
|
|
frame_inset = 1,
|
|
|
|
draggable = true,
|
|
|
|
}
|
|
|
|
|
2022-04-11 19:25:00 -06:00
|
|
|
-------------------
|
|
|
|
-- ResizingPanel --
|
|
|
|
-------------------
|
|
|
|
|
|
|
|
ResizingPanel = defclass(ResizingPanel, Panel)
|
|
|
|
|
2023-01-14 02:00:38 -07:00
|
|
|
ResizingPanel.ATTRS{
|
|
|
|
auto_height = true,
|
|
|
|
auto_width = false,
|
|
|
|
}
|
|
|
|
|
2022-04-11 19:25:00 -06:00
|
|
|
-- adjust our frame dimensions according to positions and sizes of our subviews
|
|
|
|
function ResizingPanel:postUpdateLayout(frame_body)
|
|
|
|
local w, h = 0, 0
|
2022-07-20 16:36:17 -06:00
|
|
|
for _,s in ipairs(self.subviews) do
|
2022-12-02 16:36:45 -07:00
|
|
|
if getval(s.visible) then
|
2022-07-20 16:36:17 -06:00
|
|
|
w = math.max(w, (s.frame and s.frame.l or 0) +
|
|
|
|
(s.frame and s.frame.w or frame_body.width))
|
|
|
|
h = math.max(h, (s.frame and s.frame.t or 0) +
|
|
|
|
(s.frame and s.frame.h or frame_body.height))
|
2022-04-11 19:25:00 -06:00
|
|
|
end
|
|
|
|
end
|
2022-11-28 16:12:22 -07:00
|
|
|
local l,t,r,b = gui.parse_inset(self.frame_inset)
|
|
|
|
w = w + l + r
|
|
|
|
h = h + t + b
|
2022-11-06 17:42:01 -07:00
|
|
|
if self.frame_style then
|
|
|
|
w = w + 2
|
|
|
|
h = h + 2
|
|
|
|
end
|
2022-04-17 21:07:47 -06:00
|
|
|
if not self.frame then self.frame = {} end
|
2022-11-06 17:42:01 -07:00
|
|
|
local oldw, oldh = self.frame.w, self.frame.h
|
2023-01-14 02:00:38 -07:00
|
|
|
if not self.auto_height then h = oldh end
|
|
|
|
if not self.auto_width then w = oldw end
|
2022-04-11 19:25:00 -06:00
|
|
|
self.frame.w, self.frame.h = w, h
|
2022-11-06 17:42:01 -07:00
|
|
|
if not self._updateLayoutGuard and (oldw ~= w or oldh ~= h) then
|
|
|
|
self._updateLayoutGuard = true -- protect against infinite loops
|
|
|
|
self:updateLayout() -- our frame has changed, we need to fully refresh
|
|
|
|
end
|
|
|
|
self._updateLayoutGuard = nil
|
2022-04-11 19:25:00 -06:00
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
-----------
|
|
|
|
-- Pages --
|
|
|
|
-----------
|
|
|
|
|
|
|
|
Pages = defclass(Pages, Panel)
|
|
|
|
|
|
|
|
function Pages:init(args)
|
|
|
|
for _,v in ipairs(self.subviews) do
|
2012-10-17 01:49:11 -06:00
|
|
|
v.visible = false
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
self:setSelected(args.selected or 1)
|
|
|
|
end
|
|
|
|
|
|
|
|
function Pages:setSelected(idx)
|
|
|
|
if type(idx) ~= 'number' then
|
|
|
|
local key = idx
|
|
|
|
if type(idx) == 'string' then
|
|
|
|
key = self.subviews[key]
|
|
|
|
end
|
|
|
|
idx = utils.linear_index(self.subviews, key)
|
|
|
|
if not idx then
|
2022-12-14 18:51:07 -07:00
|
|
|
error('Unknown page: '..tostring(key))
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-17 01:49:11 -06:00
|
|
|
show_view(self.subviews[self.selected], false)
|
2012-10-16 04:18:35 -06:00
|
|
|
self.selected = math.min(math.max(1, idx), #self.subviews)
|
2012-10-17 01:49:11 -06:00
|
|
|
show_view(self.subviews[self.selected], true)
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function Pages:getSelected()
|
|
|
|
return self.selected, self.subviews[self.selected]
|
2012-10-15 10:03:18 -06:00
|
|
|
end
|
|
|
|
|
2017-06-23 10:46:46 -06:00
|
|
|
function Pages:getSelectedPage()
|
|
|
|
return self.subviews[self.selected]
|
|
|
|
end
|
|
|
|
|
2012-10-15 10:03:18 -06:00
|
|
|
----------------
|
|
|
|
-- Edit field --
|
|
|
|
----------------
|
|
|
|
|
|
|
|
EditField = defclass(EditField, Widget)
|
|
|
|
|
|
|
|
EditField.ATTRS{
|
2022-05-18 17:35:06 -06:00
|
|
|
label_text = DEFAULT_NIL,
|
2012-10-15 10:03:18 -06:00
|
|
|
text = '',
|
|
|
|
text_pen = DEFAULT_NIL,
|
|
|
|
on_char = DEFAULT_NIL,
|
|
|
|
on_change = DEFAULT_NIL,
|
2012-10-16 04:18:35 -06:00
|
|
|
on_submit = DEFAULT_NIL,
|
2022-07-15 14:46:01 -06:00
|
|
|
on_submit2 = DEFAULT_NIL,
|
2017-06-27 19:10:14 -06:00
|
|
|
key = DEFAULT_NIL,
|
2022-05-18 17:35:06 -06:00
|
|
|
key_sep = DEFAULT_NIL,
|
2022-06-01 22:48:21 -06:00
|
|
|
modal = false,
|
2022-08-29 12:40:56 -06:00
|
|
|
ignore_keys = DEFAULT_NIL,
|
2012-10-15 10:03:18 -06:00
|
|
|
}
|
|
|
|
|
2022-07-17 17:04:36 -06:00
|
|
|
function EditField:preinit(init_table)
|
2022-07-20 16:36:17 -06:00
|
|
|
init_table.frame = init_table.frame or {}
|
|
|
|
init_table.frame.h = init_table.frame.h or 1
|
2022-07-17 17:04:36 -06:00
|
|
|
end
|
|
|
|
|
2022-05-18 17:35:06 -06:00
|
|
|
function EditField:init()
|
2022-06-01 22:48:21 -06:00
|
|
|
local function on_activate()
|
|
|
|
self.saved_text = self.text
|
|
|
|
self:setFocus(true)
|
|
|
|
end
|
|
|
|
|
2022-07-17 16:43:58 -06:00
|
|
|
self.start_pos = 1
|
2022-07-20 16:36:17 -06:00
|
|
|
self.cursor = #self.text + 1
|
2022-07-15 23:22:51 -06:00
|
|
|
|
2022-05-18 17:35:06 -06:00
|
|
|
self:addviews{HotkeyLabel{frame={t=0,l=0},
|
|
|
|
key=self.key,
|
|
|
|
key_sep=self.key_sep,
|
2022-06-01 22:48:21 -06:00
|
|
|
label=self.label_text,
|
|
|
|
on_activate=self.key and on_activate or nil}}
|
|
|
|
end
|
|
|
|
|
|
|
|
function EditField:getPreferredFocusState()
|
|
|
|
return not self.key
|
2022-05-18 17:35:06 -06:00
|
|
|
end
|
|
|
|
|
2022-07-15 23:22:51 -06:00
|
|
|
function EditField:setCursor(cursor)
|
|
|
|
if not cursor or cursor > #self.text then
|
|
|
|
self.cursor = #self.text + 1
|
|
|
|
return
|
|
|
|
end
|
|
|
|
self.cursor = math.max(1, cursor)
|
|
|
|
end
|
|
|
|
|
2022-07-20 16:40:49 -06:00
|
|
|
function EditField:setText(text, cursor)
|
2023-07-02 20:27:07 -06:00
|
|
|
local old = self.text
|
2022-07-20 16:40:49 -06:00
|
|
|
self.text = text
|
2022-07-15 23:22:51 -06:00
|
|
|
self:setCursor(cursor)
|
2023-07-02 20:27:07 -06:00
|
|
|
if self.on_change and text ~= old then
|
|
|
|
self.on_change(self.text, old)
|
|
|
|
end
|
2022-07-20 16:40:49 -06:00
|
|
|
end
|
|
|
|
|
2022-05-18 17:35:06 -06:00
|
|
|
function EditField:postUpdateLayout()
|
|
|
|
self.text_offset = self.subviews[1]:getTextWidth()
|
|
|
|
end
|
|
|
|
|
2012-10-15 10:03:18 -06:00
|
|
|
function EditField:onRenderBody(dc)
|
2023-01-12 13:47:43 -07:00
|
|
|
dc:pen(self.text_pen or COLOR_LIGHTCYAN)
|
2012-10-15 10:03:18 -06:00
|
|
|
|
2022-07-15 23:22:51 -06:00
|
|
|
local cursor_char = '_'
|
2022-12-02 16:36:45 -07:00
|
|
|
if not getval(self.active) or not self.focus or gui.blink_visible(300) then
|
2022-07-15 23:22:51 -06:00
|
|
|
cursor_char = (self.cursor > #self.text) and ' ' or
|
|
|
|
self.text:sub(self.cursor, self.cursor)
|
2012-10-15 10:03:18 -06:00
|
|
|
end
|
2022-07-15 23:22:51 -06:00
|
|
|
local txt = self.text:sub(1, self.cursor - 1) .. cursor_char ..
|
|
|
|
self.text:sub(self.cursor + 1)
|
2022-05-18 17:35:06 -06:00
|
|
|
local max_width = dc.width - self.text_offset
|
2022-07-17 16:43:58 -06:00
|
|
|
self.start_pos = 1
|
2017-06-27 19:10:14 -06:00
|
|
|
if #txt > max_width then
|
2022-07-15 23:22:51 -06:00
|
|
|
-- get the substring in the vicinity of the cursor
|
|
|
|
max_width = max_width - 2
|
|
|
|
local half_width = math.floor(max_width/2)
|
|
|
|
local start_pos = math.max(1, self.cursor-half_width)
|
|
|
|
local end_pos = math.min(#txt, self.cursor+half_width-1)
|
|
|
|
if self.cursor + half_width > #txt then
|
2022-07-17 16:43:58 -06:00
|
|
|
start_pos = #txt - (max_width - 1)
|
2022-07-15 23:22:51 -06:00
|
|
|
end
|
|
|
|
if self.cursor - half_width <= 1 then
|
|
|
|
end_pos = max_width + 1
|
|
|
|
end
|
2022-07-17 16:43:58 -06:00
|
|
|
self.start_pos = start_pos > 1 and start_pos - 1 or start_pos
|
2022-07-15 23:22:51 -06:00
|
|
|
txt = ('%s%s%s'):format(start_pos == 1 and '' or string.char(27),
|
|
|
|
txt:sub(start_pos, end_pos),
|
|
|
|
end_pos == #txt and '' or string.char(26))
|
2012-10-15 10:03:18 -06:00
|
|
|
end
|
2022-05-18 17:35:06 -06:00
|
|
|
dc:advance(self.text_offset):string(txt)
|
2012-10-15 10:03:18 -06:00
|
|
|
end
|
|
|
|
|
2023-01-03 13:57:02 -07:00
|
|
|
function EditField:insert(text)
|
|
|
|
local old = self.text
|
|
|
|
self:setText(old:sub(1,self.cursor-1)..text..old:sub(self.cursor),
|
|
|
|
self.cursor + #text)
|
|
|
|
end
|
|
|
|
|
2012-10-15 10:03:18 -06:00
|
|
|
function EditField:onInput(keys)
|
2022-06-01 22:48:21 -06:00
|
|
|
if not self.focus then
|
|
|
|
-- only react to our hotkey
|
|
|
|
return self:inputToSubviews(keys)
|
|
|
|
end
|
|
|
|
|
2022-08-29 12:40:56 -06:00
|
|
|
if self.ignore_keys then
|
|
|
|
for _,ignore_key in ipairs(self.ignore_keys) do
|
|
|
|
if keys[ignore_key] then return false end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-01-13 18:05:08 -07:00
|
|
|
if self.key and (keys.LEAVESCREEN or keys._MOUSE_R_DOWN) then
|
2022-07-16 23:03:39 -06:00
|
|
|
self:setText(self.saved_text)
|
2022-06-01 22:48:21 -06:00
|
|
|
self:setFocus(false)
|
2012-10-16 04:18:35 -06:00
|
|
|
return true
|
2022-06-01 22:48:21 -06:00
|
|
|
end
|
|
|
|
|
2022-12-30 02:13:13 -07:00
|
|
|
if keys.SELECT or keys.CUSTOM_SHIFT_ENTER then
|
2022-07-15 14:46:01 -06:00
|
|
|
if self.key then
|
|
|
|
self:setFocus(false)
|
|
|
|
end
|
2022-12-30 02:13:13 -07:00
|
|
|
if keys.CUSTOM_SHIFT_ENTER then
|
|
|
|
if self.on_submit2 then
|
|
|
|
self.on_submit2(self.text)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if self.on_submit then
|
|
|
|
self.on_submit(self.text)
|
|
|
|
return true
|
|
|
|
end
|
2022-07-15 14:46:01 -06:00
|
|
|
end
|
|
|
|
return not not self.key
|
2022-07-16 23:03:39 -06:00
|
|
|
elseif keys._MOUSE_L then
|
|
|
|
local mouse_x, mouse_y = self:getMousePos()
|
|
|
|
if mouse_x then
|
2022-11-21 18:51:04 -07:00
|
|
|
self:setCursor(self.start_pos + mouse_x - (self.text_offset or 0))
|
2022-07-16 23:03:39 -06:00
|
|
|
return true
|
|
|
|
end
|
2022-08-19 23:40:53 -06:00
|
|
|
elseif keys._STRING then
|
|
|
|
local old = self.text
|
|
|
|
if keys._STRING == 0 then
|
|
|
|
-- handle backspace
|
|
|
|
local del_pos = self.cursor - 1
|
|
|
|
if del_pos > 0 then
|
|
|
|
self:setText(old:sub(1, del_pos-1) .. old:sub(del_pos+1),
|
|
|
|
del_pos)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
local cv = string.char(keys._STRING)
|
|
|
|
if not self.on_char or self.on_char(cv, old) then
|
2023-01-03 13:57:02 -07:00
|
|
|
self:insert(cv)
|
2022-10-21 13:43:53 -06:00
|
|
|
elseif self.on_char then
|
|
|
|
return self.modal
|
2022-08-19 23:40:53 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
return true
|
2022-12-30 02:13:13 -07:00
|
|
|
elseif keys.KEYBOARD_CURSOR_LEFT then
|
2022-07-16 23:03:39 -06:00
|
|
|
self:setCursor(self.cursor - 1)
|
2022-07-15 23:22:51 -06:00
|
|
|
return true
|
2022-12-30 02:13:13 -07:00
|
|
|
elseif keys.CUSTOM_CTRL_B then -- back one word
|
2022-08-08 12:44:33 -06:00
|
|
|
local _, prev_word_end = self.text:sub(1, self.cursor-1):
|
2022-12-30 02:13:13 -07:00
|
|
|
find('.*[%w_%-][^%w_%-]')
|
2022-08-08 12:44:33 -06:00
|
|
|
self:setCursor(prev_word_end or 1)
|
2022-07-15 23:22:51 -06:00
|
|
|
return true
|
2023-07-07 15:49:10 -06:00
|
|
|
-- commented out until we get HOME key support from DF
|
|
|
|
-- elseif keys.CUSTOM_CTRL_A then -- home
|
|
|
|
-- self:setCursor(1)
|
|
|
|
-- return true
|
2022-12-30 02:13:13 -07:00
|
|
|
elseif keys.KEYBOARD_CURSOR_RIGHT then
|
2022-07-16 23:03:39 -06:00
|
|
|
self:setCursor(self.cursor + 1)
|
2022-07-15 23:22:51 -06:00
|
|
|
return true
|
2022-12-30 02:13:13 -07:00
|
|
|
elseif keys.CUSTOM_CTRL_F then -- forward one word
|
2022-08-08 12:44:33 -06:00
|
|
|
local _,next_word_start = self.text:find('[^%w_%-][%w_%-]', self.cursor)
|
|
|
|
self:setCursor(next_word_start)
|
2022-07-15 23:22:51 -06:00
|
|
|
return true
|
2022-12-30 02:13:13 -07:00
|
|
|
elseif keys.CUSTOM_CTRL_E then -- end
|
2022-07-16 23:03:39 -06:00
|
|
|
self:setCursor()
|
2022-07-15 23:22:51 -06:00
|
|
|
return true
|
2023-07-02 19:04:06 -06:00
|
|
|
elseif keys.CUSTOM_CTRL_C then
|
2023-07-03 12:05:58 -06:00
|
|
|
dfhack.internal.setClipboardTextCp437(self.text)
|
2023-07-02 19:04:06 -06:00
|
|
|
return true
|
|
|
|
elseif keys.CUSTOM_CTRL_X then
|
2023-07-03 12:05:58 -06:00
|
|
|
dfhack.internal.setClipboardTextCp437(self.text)
|
2023-07-02 19:04:06 -06:00
|
|
|
self:setText('')
|
|
|
|
return true
|
|
|
|
elseif keys.CUSTOM_CTRL_V then
|
2023-07-03 12:05:58 -06:00
|
|
|
self:insert(dfhack.internal.getClipboardTextCp437())
|
2023-07-02 19:04:06 -06:00
|
|
|
return true
|
2012-10-15 10:03:18 -06:00
|
|
|
end
|
2022-06-01 22:48:21 -06:00
|
|
|
|
|
|
|
-- if we're modal, then unconditionally eat all the input
|
|
|
|
return self.modal
|
2012-10-15 10:03:18 -06:00
|
|
|
end
|
|
|
|
|
2022-10-06 12:13:16 -06:00
|
|
|
---------------
|
|
|
|
-- Scrollbar --
|
|
|
|
---------------
|
|
|
|
|
2022-10-07 16:40:05 -06:00
|
|
|
SCROLL_INITIAL_DELAY_MS = 300
|
|
|
|
SCROLL_DELAY_MS = 20
|
|
|
|
|
2022-10-06 12:13:16 -06:00
|
|
|
Scrollbar = defclass(Scrollbar, Widget)
|
|
|
|
|
|
|
|
Scrollbar.ATTRS{
|
|
|
|
on_scroll = DEFAULT_NIL,
|
|
|
|
}
|
|
|
|
|
|
|
|
function Scrollbar:preinit(init_table)
|
|
|
|
init_table.frame = init_table.frame or {}
|
2022-12-30 04:28:59 -07:00
|
|
|
init_table.frame.w = init_table.frame.w or 2
|
2022-10-06 12:13:16 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function Scrollbar:init()
|
2022-10-07 16:40:05 -06:00
|
|
|
self.last_scroll_ms = 0
|
|
|
|
self.is_first_click = false
|
|
|
|
self.scroll_spec = nil
|
2022-10-07 17:27:19 -06:00
|
|
|
self.is_dragging = false -- index of the scrollbar tile that we're dragging
|
2022-10-06 12:13:16 -06:00
|
|
|
self:update(1, 1, 1)
|
|
|
|
end
|
|
|
|
|
2022-10-21 13:17:59 -06:00
|
|
|
local function scrollbar_get_max_pos_and_height(scrollbar)
|
|
|
|
local frame_body = scrollbar.frame_body
|
|
|
|
local scrollbar_body_height = (frame_body and frame_body.height or 3) - 2
|
|
|
|
|
2023-02-05 11:00:38 -07:00
|
|
|
local height = math.max(2, math.floor(
|
2022-10-21 13:17:59 -06:00
|
|
|
(math.min(scrollbar.elems_per_page, scrollbar.num_elems) * scrollbar_body_height) /
|
|
|
|
scrollbar.num_elems))
|
|
|
|
|
|
|
|
return scrollbar_body_height - height, height
|
|
|
|
end
|
|
|
|
|
2022-10-06 12:13:16 -06:00
|
|
|
-- calculate and cache the number of tiles of empty space above the top of the
|
|
|
|
-- scrollbar and the number of tiles the scrollbar should occupy to represent
|
|
|
|
-- the percentage of text that is on the screen.
|
|
|
|
-- if elems_per_page or num_elems are not specified, the last values passed to
|
|
|
|
-- Scrollbar:update() are used.
|
|
|
|
function Scrollbar:update(top_elem, elems_per_page, num_elems)
|
|
|
|
if not top_elem then error('must specify index of new top element') end
|
|
|
|
elems_per_page = elems_per_page or self.elems_per_page
|
|
|
|
num_elems = num_elems or self.num_elems
|
2022-10-21 13:17:59 -06:00
|
|
|
self.top_elem = top_elem
|
|
|
|
self.elems_per_page, self.num_elems = elems_per_page, num_elems
|
2022-10-06 12:13:16 -06:00
|
|
|
|
2022-10-21 13:17:59 -06:00
|
|
|
local max_pos, height = scrollbar_get_max_pos_and_height(self)
|
2022-10-07 14:14:52 -06:00
|
|
|
local pos = (num_elems == elems_per_page) and 0 or
|
|
|
|
math.ceil(((top_elem-1) * max_pos) /
|
|
|
|
(num_elems - elems_per_page))
|
2022-10-06 12:13:16 -06:00
|
|
|
|
|
|
|
self.bar_offset, self.bar_height = pos, height
|
|
|
|
end
|
|
|
|
|
2022-10-07 17:27:19 -06:00
|
|
|
local function scrollbar_do_drag(scrollbar)
|
2023-04-30 18:12:43 -06:00
|
|
|
local x,y = dfhack.screen.getMousePos()
|
|
|
|
if not y then return end
|
|
|
|
x,y = scrollbar.frame_body:localXY(x, y)
|
2022-10-21 13:17:59 -06:00
|
|
|
local cur_pos = y - scrollbar.is_dragging
|
|
|
|
local max_top = scrollbar.num_elems - scrollbar.elems_per_page + 1
|
|
|
|
local max_pos = scrollbar_get_max_pos_and_height(scrollbar)
|
|
|
|
local new_top_elem = math.floor(cur_pos * max_top / max_pos) + 1
|
|
|
|
new_top_elem = math.max(1, math.min(new_top_elem, max_top))
|
|
|
|
if new_top_elem ~= scrollbar.top_elem then
|
|
|
|
scrollbar.on_scroll(new_top_elem)
|
2022-10-07 17:27:19 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-10-21 13:17:59 -06:00
|
|
|
local function scrollbar_is_visible(scrollbar)
|
|
|
|
return scrollbar.elems_per_page < scrollbar.num_elems
|
|
|
|
end
|
|
|
|
|
2023-05-16 18:12:44 -06:00
|
|
|
local SBSO = df.global.init.scrollbar_texpos[0] --Scroll Bar Spritesheet Offset / change this to point to a different spritesheet (ui themes, anyone? :p)
|
2023-02-05 10:56:51 -07:00
|
|
|
local SCROLLBAR_UP_LEFT_PEN = to_pen{tile=SBSO+0, ch=47, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_UP_RIGHT_PEN = to_pen{tile=SBSO+1, ch=92, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_DOWN_LEFT_PEN = to_pen{tile=SBSO+24, ch=92, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_DOWN_RIGHT_PEN = to_pen{tile=SBSO+25, ch=47, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_UP_LEFT_PEN = to_pen{tile=SBSO+6, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_UP_RIGHT_PEN = to_pen{tile=SBSO+7, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_LEFT_PEN = to_pen{tile=SBSO+30, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_RIGHT_PEN = to_pen{tile=SBSO+31, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_CENTER_UP_LEFT_PEN = to_pen{tile=SBSO+10, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_CENTER_UP_RIGHT_PEN = to_pen{tile=SBSO+11, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_CENTER_DOWN_LEFT_PEN = to_pen{tile=SBSO+22, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_CENTER_DOWN_RIGHT_PEN = to_pen{tile=SBSO+23, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_CENTER_LEFT_PEN = to_pen{tile=SBSO+18, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_CENTER_RIGHT_PEN = to_pen{tile=SBSO+19, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_DOWN_LEFT_PEN = to_pen{tile=SBSO+42, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_DOWN_RIGHT_PEN = to_pen{tile=SBSO+43, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_2TALL_UP_LEFT_PEN = to_pen{tile=SBSO+26, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_2TALL_UP_RIGHT_PEN = to_pen{tile=SBSO+27, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_2TALL_DOWN_LEFT_PEN = to_pen{tile=SBSO+38, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_2TALL_DOWN_RIGHT_PEN = to_pen{tile=SBSO+39, ch=219, fg=COLOR_CYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_UP_LEFT_HOVER_PEN = to_pen{tile=SBSO+2, ch=47, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_UP_RIGHT_HOVER_PEN = to_pen{tile=SBSO+3, ch=92, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_DOWN_LEFT_HOVER_PEN = to_pen{tile=SBSO+14, ch=92, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_DOWN_RIGHT_HOVER_PEN = to_pen{tile=SBSO+15, ch=47, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_UP_LEFT_HOVER_PEN = to_pen{tile=SBSO+8, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_UP_RIGHT_HOVER_PEN = to_pen{tile=SBSO+9, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_LEFT_HOVER_PEN = to_pen{tile=SBSO+32, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_RIGHT_HOVER_PEN = to_pen{tile=SBSO+33, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_CENTER_UP_LEFT_HOVER_PEN = to_pen{tile=SBSO+34, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_CENTER_UP_RIGHT_HOVER_PEN = to_pen{tile=SBSO+35, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_CENTER_DOWN_LEFT_HOVER_PEN = to_pen{tile=SBSO+46, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_CENTER_DOWN_RIGHT_HOVER_PEN = to_pen{tile=SBSO+47, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_CENTER_LEFT_HOVER_PEN = to_pen{tile=SBSO+20, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_CENTER_RIGHT_HOVER_PEN = to_pen{tile=SBSO+21, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_DOWN_LEFT_HOVER_PEN = to_pen{tile=SBSO+44, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_DOWN_RIGHT_HOVER_PEN = to_pen{tile=SBSO+45, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_2TALL_UP_LEFT_HOVER_PEN = to_pen{tile=SBSO+28, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_2TALL_UP_RIGHT_HOVER_PEN = to_pen{tile=SBSO+29, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_2TALL_DOWN_LEFT_HOVER_PEN = to_pen{tile=SBSO+40, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_2TALL_DOWN_RIGHT_HOVER_PEN = to_pen{tile=SBSO+41, ch=219, fg=COLOR_LIGHTCYAN, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_BG_LEFT_PEN = to_pen{tile=SBSO+12, ch=176, fg=COLOR_DARKGREY, bg=COLOR_BLACK}
|
|
|
|
local SCROLLBAR_BAR_BG_RIGHT_PEN = to_pen{tile=SBSO+13, ch=176, fg=COLOR_DARKGREY, bg=COLOR_BLACK}
|
2022-10-06 12:13:16 -06:00
|
|
|
|
|
|
|
function Scrollbar:onRenderBody(dc)
|
|
|
|
-- don't draw if all elements are visible
|
2022-11-07 17:13:45 -07:00
|
|
|
if not scrollbar_is_visible(self) then
|
|
|
|
return
|
|
|
|
end
|
2022-12-30 04:28:59 -07:00
|
|
|
-- determine which elements should be highlighted
|
|
|
|
local _,hover_y = self:getMousePos()
|
|
|
|
local hover_up, hover_down, hover_bar = false, false, false
|
|
|
|
if hover_y == 0 then
|
|
|
|
hover_up = true
|
|
|
|
elseif hover_y == dc.height-1 then
|
|
|
|
hover_down = true
|
|
|
|
elseif hover_y then
|
|
|
|
hover_bar = true
|
|
|
|
end
|
|
|
|
-- render up arrow
|
|
|
|
dc:seek(0, 0)
|
|
|
|
dc:char(nil, hover_up and SCROLLBAR_UP_LEFT_HOVER_PEN or SCROLLBAR_UP_LEFT_PEN)
|
|
|
|
dc:char(nil, hover_up and SCROLLBAR_UP_RIGHT_HOVER_PEN or SCROLLBAR_UP_RIGHT_PEN)
|
2022-10-06 12:13:16 -06:00
|
|
|
-- render scrollbar body
|
|
|
|
local starty = self.bar_offset + 1
|
|
|
|
local endy = self.bar_offset + self.bar_height
|
2022-12-30 04:28:59 -07:00
|
|
|
local midy = (starty + endy)/2
|
2022-10-06 12:13:16 -06:00
|
|
|
for y=1,dc.height-2 do
|
|
|
|
dc:seek(0, y)
|
|
|
|
if y >= starty and y <= endy then
|
2023-01-01 18:55:06 -07:00
|
|
|
if y == starty and y <= midy - 1 then
|
2022-12-30 04:28:59 -07:00
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_UP_LEFT_HOVER_PEN or SCROLLBAR_BAR_UP_LEFT_PEN)
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_UP_RIGHT_HOVER_PEN or SCROLLBAR_BAR_UP_RIGHT_PEN)
|
2023-02-04 07:18:26 -07:00
|
|
|
elseif y == midy - 0.5 and self.bar_height == 2 then
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_2TALL_UP_LEFT_HOVER_PEN or SCROLLBAR_BAR_2TALL_UP_LEFT_PEN)
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_2TALL_UP_RIGHT_HOVER_PEN or SCROLLBAR_BAR_2TALL_UP_RIGHT_PEN)
|
|
|
|
elseif y == midy + 0.5 and self.bar_height == 2 then
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_2TALL_DOWN_LEFT_HOVER_PEN or SCROLLBAR_BAR_2TALL_DOWN_LEFT_PEN)
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_2TALL_DOWN_RIGHT_HOVER_PEN or SCROLLBAR_BAR_2TALL_DOWN_RIGHT_PEN)
|
2022-12-30 04:28:59 -07:00
|
|
|
elseif y == midy - 0.5 then
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_CENTER_UP_LEFT_HOVER_PEN or SCROLLBAR_BAR_CENTER_UP_LEFT_PEN)
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_CENTER_UP_RIGHT_HOVER_PEN or SCROLLBAR_BAR_CENTER_UP_RIGHT_PEN)
|
|
|
|
elseif y == midy + 0.5 then
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_CENTER_DOWN_LEFT_HOVER_PEN or SCROLLBAR_BAR_CENTER_DOWN_LEFT_PEN)
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_CENTER_DOWN_RIGHT_HOVER_PEN or SCROLLBAR_BAR_CENTER_DOWN_RIGHT_PEN)
|
|
|
|
elseif y == midy then
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_CENTER_LEFT_HOVER_PEN or SCROLLBAR_BAR_CENTER_LEFT_PEN)
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_CENTER_RIGHT_HOVER_PEN or SCROLLBAR_BAR_CENTER_RIGHT_PEN)
|
2023-01-01 18:55:06 -07:00
|
|
|
elseif y == endy and y >= midy + 1 then
|
2022-12-30 04:28:59 -07:00
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_DOWN_LEFT_HOVER_PEN or SCROLLBAR_BAR_DOWN_LEFT_PEN)
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_DOWN_RIGHT_HOVER_PEN or SCROLLBAR_BAR_DOWN_RIGHT_PEN)
|
|
|
|
else
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_LEFT_HOVER_PEN or SCROLLBAR_BAR_LEFT_PEN)
|
|
|
|
dc:char(nil, hover_bar and SCROLLBAR_BAR_RIGHT_HOVER_PEN or SCROLLBAR_BAR_RIGHT_PEN)
|
|
|
|
end
|
2022-10-06 12:13:16 -06:00
|
|
|
else
|
2022-12-30 04:28:59 -07:00
|
|
|
dc:char(nil, SCROLLBAR_BAR_BG_LEFT_PEN)
|
|
|
|
dc:char(nil, SCROLLBAR_BAR_BG_RIGHT_PEN)
|
2022-10-06 12:13:16 -06:00
|
|
|
end
|
|
|
|
end
|
2022-12-30 04:28:59 -07:00
|
|
|
-- render down arrow
|
|
|
|
dc:seek(0, dc.height-1)
|
|
|
|
dc:char(nil, hover_down and SCROLLBAR_DOWN_LEFT_HOVER_PEN or SCROLLBAR_DOWN_LEFT_PEN)
|
|
|
|
dc:char(nil, hover_down and SCROLLBAR_DOWN_RIGHT_HOVER_PEN or SCROLLBAR_DOWN_RIGHT_PEN)
|
2022-10-07 17:27:19 -06:00
|
|
|
if not self.on_scroll then return end
|
|
|
|
-- manage state for dragging and continuous scrolling
|
|
|
|
if self.is_dragging then
|
|
|
|
scrollbar_do_drag(self)
|
|
|
|
end
|
2022-11-21 18:36:46 -07:00
|
|
|
if df.global.enabler.mouse_lbut == 0 then
|
2022-10-07 16:40:05 -06:00
|
|
|
self.last_scroll_ms = 0
|
2022-10-07 17:27:19 -06:00
|
|
|
self.is_dragging = false
|
2022-10-07 16:40:05 -06:00
|
|
|
self.scroll_spec = nil
|
|
|
|
return
|
|
|
|
end
|
2022-10-07 17:27:19 -06:00
|
|
|
if self.last_scroll_ms == 0 then return end
|
2022-10-07 16:40:05 -06:00
|
|
|
local now = dfhack.getTickCount()
|
|
|
|
local delay = self.is_first_click and
|
|
|
|
SCROLL_INITIAL_DELAY_MS or SCROLL_DELAY_MS
|
|
|
|
if now - self.last_scroll_ms >= delay then
|
|
|
|
self.is_first_click = false
|
|
|
|
self.on_scroll(self.scroll_spec)
|
|
|
|
self.last_scroll_ms = now
|
|
|
|
end
|
2022-10-06 12:13:16 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function Scrollbar:onInput(keys)
|
2022-12-30 02:37:22 -07:00
|
|
|
if not self.on_scroll or not scrollbar_is_visible(self) then
|
2022-10-21 13:17:59 -06:00
|
|
|
return false
|
|
|
|
end
|
2022-12-30 06:40:57 -07:00
|
|
|
|
2023-08-04 02:35:51 -06:00
|
|
|
if self.parent_view and self.parent_view:getMousePos() then
|
2022-12-30 02:37:22 -07:00
|
|
|
if keys.CONTEXT_SCROLL_UP then
|
|
|
|
self.on_scroll('up_small')
|
|
|
|
return true
|
|
|
|
elseif keys.CONTEXT_SCROLL_DOWN then
|
|
|
|
self.on_scroll('down_small')
|
|
|
|
return true
|
|
|
|
elseif keys.CONTEXT_SCROLL_PAGEUP then
|
|
|
|
self.on_scroll('up_large')
|
|
|
|
return true
|
|
|
|
elseif keys.CONTEXT_SCROLL_PAGEDOWN then
|
|
|
|
self.on_scroll('down_large')
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not keys._MOUSE_L_DOWN then return false end
|
2022-10-06 12:13:16 -06:00
|
|
|
local _,y = self:getMousePos()
|
|
|
|
if not y then return false end
|
2022-10-07 16:40:05 -06:00
|
|
|
local scroll_spec = nil
|
|
|
|
if y == 0 then scroll_spec = 'up_small'
|
|
|
|
elseif y == self.frame_body.height - 1 then scroll_spec = 'down_small'
|
|
|
|
elseif y <= self.bar_offset then scroll_spec = 'up_large'
|
|
|
|
elseif y > self.bar_offset + self.bar_height then scroll_spec = 'down_large'
|
2022-10-07 17:27:19 -06:00
|
|
|
else
|
|
|
|
self.is_dragging = y - self.bar_offset
|
|
|
|
return true
|
2022-10-06 12:13:16 -06:00
|
|
|
end
|
2022-10-07 16:40:05 -06:00
|
|
|
self.scroll_spec = scroll_spec
|
2022-10-07 17:27:19 -06:00
|
|
|
self.on_scroll(scroll_spec)
|
2022-10-07 16:40:05 -06:00
|
|
|
-- reset continuous scroll state
|
|
|
|
self.is_first_click = true
|
|
|
|
self.last_scroll_ms = dfhack.getTickCount()
|
2022-10-06 12:13:16 -06:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
-----------
|
|
|
|
-- Label --
|
|
|
|
-----------
|
|
|
|
|
|
|
|
function parse_label_text(obj)
|
|
|
|
local text = obj.text or {}
|
|
|
|
if type(text) ~= 'table' then
|
|
|
|
text = { text }
|
|
|
|
end
|
|
|
|
local curline = nil
|
|
|
|
local out = { }
|
|
|
|
local active = nil
|
|
|
|
local idtab = nil
|
|
|
|
for _,v in ipairs(text) do
|
|
|
|
local vv
|
|
|
|
if type(v) == 'string' then
|
2021-07-03 00:30:59 -06:00
|
|
|
vv = v:split(NEWLINE)
|
2012-10-16 04:18:35 -06:00
|
|
|
else
|
|
|
|
vv = { v }
|
|
|
|
end
|
|
|
|
|
|
|
|
for i = 1,#vv do
|
|
|
|
local cv = vv[i]
|
|
|
|
if i > 1 then
|
|
|
|
if not curline then
|
|
|
|
table.insert(out, {})
|
|
|
|
end
|
|
|
|
curline = nil
|
|
|
|
end
|
|
|
|
if cv ~= '' then
|
|
|
|
if not curline then
|
|
|
|
curline = {}
|
|
|
|
table.insert(out, curline)
|
|
|
|
end
|
|
|
|
|
|
|
|
if type(cv) == 'string' then
|
|
|
|
table.insert(curline, { text = cv })
|
|
|
|
else
|
|
|
|
table.insert(curline, cv)
|
|
|
|
|
|
|
|
if cv.on_activate then
|
|
|
|
active = active or {}
|
|
|
|
table.insert(active, cv)
|
|
|
|
end
|
|
|
|
|
|
|
|
if cv.id then
|
|
|
|
idtab = idtab or {}
|
|
|
|
idtab[cv.id] = cv
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
obj.text_lines = out
|
|
|
|
obj.text_active = active
|
|
|
|
obj.text_ids = idtab
|
|
|
|
end
|
|
|
|
|
2012-10-20 11:57:36 -06:00
|
|
|
local function is_disabled(token)
|
|
|
|
return (token.disabled ~= nil and getval(token.disabled)) or
|
|
|
|
(token.enabled ~= nil and not getval(token.enabled))
|
|
|
|
end
|
|
|
|
|
2023-02-16 22:38:27 -07:00
|
|
|
-- Make the hover pen -- that is a pen that should render elements that has the
|
|
|
|
-- mouse hovering over it. if hpen is specified, it just checks the fields and
|
|
|
|
-- returns it (in parsed pen form)
|
|
|
|
local function make_hpen(pen, hpen)
|
|
|
|
if not hpen then
|
|
|
|
pen = dfhack.pen.parse(pen)
|
|
|
|
|
|
|
|
-- Swap the foreground and background
|
|
|
|
hpen = dfhack.pen.make(pen.bg, nil, pen.fg + (pen.bold and 8 or 0))
|
|
|
|
end
|
|
|
|
|
|
|
|
-- text_hpen needs a character in order to paint the background using
|
|
|
|
-- Painter:fill(), so let's make it paint a space to show the background
|
|
|
|
-- color
|
|
|
|
local hpen_parsed = dfhack.pen.parse(hpen)
|
|
|
|
hpen_parsed.ch = string.byte(' ')
|
|
|
|
return hpen_parsed
|
|
|
|
end
|
|
|
|
|
|
|
|
function render_text(obj,dc,x0,y0,pen,dpen,disabled,hpen,hovered)
|
2012-10-16 04:18:35 -06:00
|
|
|
local width = 0
|
2021-01-13 00:27:14 -07:00
|
|
|
for iline = dc and obj.start_line_num or 1, #obj.text_lines do
|
|
|
|
local x, line = 0, obj.text_lines[iline]
|
2012-10-16 04:18:35 -06:00
|
|
|
if dc then
|
2020-11-04 19:06:50 -07:00
|
|
|
local offset = (obj.start_line_num or 1) - 1
|
2021-01-13 23:02:22 -07:00
|
|
|
local y = y0 + iline - offset - 1
|
|
|
|
-- skip text outside of the containing frame
|
|
|
|
if y > dc.height - 1 then break end
|
|
|
|
dc:seek(x+x0, y)
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
for _,token in ipairs(line) do
|
|
|
|
token.line = iline
|
|
|
|
token.x1 = x
|
|
|
|
|
|
|
|
if token.gap then
|
|
|
|
x = x + token.gap
|
|
|
|
if dc then
|
|
|
|
dc:advance(token.gap)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-25 16:19:24 -06:00
|
|
|
if token.tile or (hovered and token.htile) then
|
2012-10-16 23:41:48 -06:00
|
|
|
x = x + 1
|
|
|
|
if dc then
|
2023-03-25 16:19:24 -06:00
|
|
|
local tile = hovered and getval(token.htile or token.tile) or getval(token.tile)
|
2023-02-24 18:16:30 -07:00
|
|
|
local tile_pen = tonumber(tile) and to_pen{tile=tile} or tile
|
2023-02-01 15:51:46 -07:00
|
|
|
dc:char(nil, tile_pen)
|
2023-01-11 19:23:16 -07:00
|
|
|
if token.width then
|
|
|
|
dc:advance(token.width-1)
|
|
|
|
end
|
2012-10-16 23:41:48 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
if token.text or token.key then
|
2012-11-30 08:10:17 -07:00
|
|
|
local text = ''..(getval(token.text) or '')
|
2022-12-30 04:28:59 -07:00
|
|
|
local keypen = to_pen(token.key_pen or COLOR_LIGHTGREEN)
|
2012-10-16 04:18:35 -06:00
|
|
|
|
|
|
|
if dc then
|
2012-10-25 03:20:41 -06:00
|
|
|
local tpen = getval(token.pen)
|
2023-02-16 22:54:44 -07:00
|
|
|
local dcpen = to_pen(tpen or pen)
|
2023-02-16 22:38:27 -07:00
|
|
|
|
|
|
|
-- If disabled, figure out which dpen to use
|
2012-10-24 09:25:06 -06:00
|
|
|
if disabled or is_disabled(token) then
|
2023-02-16 22:54:44 -07:00
|
|
|
dcpen = to_pen(getval(token.dpen) or tpen or dpen)
|
2017-05-28 21:11:37 -06:00
|
|
|
if keypen.fg ~= COLOR_BLACK then
|
|
|
|
keypen.bold = false
|
|
|
|
end
|
2023-02-16 22:38:27 -07:00
|
|
|
|
|
|
|
-- if hovered *and* disabled, combine both effects
|
|
|
|
if hovered then
|
|
|
|
dcpen = make_hpen(dcpen)
|
|
|
|
end
|
|
|
|
elseif hovered then
|
|
|
|
dcpen = make_hpen(dcpen, getval(token.hpen) or hpen)
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
|
2023-02-16 22:38:27 -07:00
|
|
|
dc:pen(dcpen)
|
|
|
|
end
|
2012-11-30 08:10:17 -07:00
|
|
|
local width = getval(token.width)
|
|
|
|
local padstr
|
|
|
|
if width then
|
|
|
|
x = x + width
|
|
|
|
if #text > width then
|
|
|
|
text = string.sub(text,1,width)
|
|
|
|
else
|
|
|
|
if token.pad_char then
|
|
|
|
padstr = string.rep(token.pad_char,width-#text)
|
|
|
|
end
|
|
|
|
if dc and token.rjustify then
|
|
|
|
if padstr then dc:string(padstr) else dc:advance(width-#text) end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
x = x + #text
|
|
|
|
end
|
2012-10-16 04:18:35 -06:00
|
|
|
|
|
|
|
if token.key then
|
2022-12-23 11:17:26 -07:00
|
|
|
if type(token.key) == 'string' and not df.interface_key[token.key] then
|
|
|
|
error('Invalid interface_key: ' .. token.key)
|
|
|
|
end
|
2012-10-16 04:18:35 -06:00
|
|
|
local keystr = gui.getKeyDisplay(token.key)
|
|
|
|
local sep = token.key_sep or ''
|
|
|
|
|
2012-10-16 08:33:00 -06:00
|
|
|
x = x + #keystr
|
|
|
|
|
2022-05-18 17:35:06 -06:00
|
|
|
if sep:startswith('()') then
|
2012-10-16 04:18:35 -06:00
|
|
|
if dc then
|
|
|
|
dc:string(text)
|
2022-05-18 17:35:06 -06:00
|
|
|
dc:string(' ('):string(keystr,keypen)
|
|
|
|
dc:string(sep:sub(2))
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
2022-05-18 17:35:06 -06:00
|
|
|
x = x + 1 + #sep
|
2012-10-16 04:18:35 -06:00
|
|
|
else
|
|
|
|
if dc then
|
|
|
|
dc:string(keystr,keypen):string(sep):string(text)
|
|
|
|
end
|
|
|
|
x = x + #sep
|
|
|
|
end
|
|
|
|
else
|
|
|
|
if dc then
|
|
|
|
dc:string(text)
|
|
|
|
end
|
|
|
|
end
|
2012-11-30 08:10:17 -07:00
|
|
|
|
|
|
|
if width and dc and not token.rjustify then
|
|
|
|
if padstr then dc:string(padstr) else dc:advance(width-#text) end
|
|
|
|
end
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
token.x2 = x
|
|
|
|
end
|
|
|
|
width = math.max(width, x)
|
|
|
|
end
|
|
|
|
obj.text_width = width
|
|
|
|
end
|
|
|
|
|
|
|
|
function check_text_keys(self, keys)
|
|
|
|
if self.text_active then
|
|
|
|
for _,item in ipairs(self.text_active) do
|
2012-10-20 11:57:36 -06:00
|
|
|
if item.key and keys[item.key] and not is_disabled(item) then
|
2012-10-16 04:18:35 -06:00
|
|
|
item.on_activate()
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
Label = defclass(Label, Widget)
|
|
|
|
|
|
|
|
Label.ATTRS{
|
|
|
|
text_pen = COLOR_WHITE,
|
2016-05-09 19:29:04 -06:00
|
|
|
text_dpen = COLOR_DARKGREY, -- disabled
|
2023-02-18 17:06:03 -07:00
|
|
|
text_hpen = DEFAULT_NIL, -- hover - default is to invert the fg/bg colors
|
2012-10-24 09:25:06 -06:00
|
|
|
disabled = DEFAULT_NIL,
|
|
|
|
enabled = DEFAULT_NIL,
|
2012-10-16 04:18:35 -06:00
|
|
|
auto_height = true,
|
2012-10-16 08:33:00 -06:00
|
|
|
auto_width = false,
|
2016-05-09 19:29:04 -06:00
|
|
|
on_click = DEFAULT_NIL,
|
|
|
|
on_rclick = DEFAULT_NIL,
|
2020-11-04 19:06:50 -07:00
|
|
|
scroll_keys = STANDARDSCROLL,
|
2012-10-16 04:18:35 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
function Label:init(args)
|
2022-10-07 13:45:43 -06:00
|
|
|
self.scrollbar = Scrollbar{
|
|
|
|
frame={r=0},
|
|
|
|
on_scroll=self:callback('on_scrollbar')}
|
|
|
|
|
|
|
|
self:addviews{self.scrollbar}
|
|
|
|
|
2022-04-17 21:07:47 -06:00
|
|
|
self:setText(args.text or self.text)
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
|
2022-10-07 13:45:43 -06:00
|
|
|
local function update_label_scrollbar(label)
|
|
|
|
local body_height = label.frame_body and label.frame_body.height or 1
|
|
|
|
label.scrollbar:update(label.start_line_num, body_height,
|
|
|
|
label:getTextHeight())
|
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
function Label:setText(text)
|
2022-07-11 18:23:23 -06:00
|
|
|
self.start_line_num = 1
|
2012-10-16 04:18:35 -06:00
|
|
|
self.text = text
|
|
|
|
parse_label_text(self)
|
|
|
|
|
|
|
|
if self.auto_height then
|
|
|
|
self.frame = self.frame or {}
|
|
|
|
self.frame.h = self:getTextHeight()
|
|
|
|
end
|
|
|
|
|
2022-10-07 13:45:43 -06:00
|
|
|
update_label_scrollbar(self)
|
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
|
|
|
end
|
|
|
|
|
2012-10-16 08:33:00 -06:00
|
|
|
function Label:preUpdateLayout()
|
|
|
|
if self.auto_width then
|
|
|
|
self.frame = self.frame or {}
|
|
|
|
self.frame.w = self:getTextWidth()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-10-07 13:45:43 -06:00
|
|
|
function Label:postUpdateLayout()
|
|
|
|
update_label_scrollbar(self)
|
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
function Label:itemById(id)
|
|
|
|
if self.text_ids then
|
|
|
|
return self.text_ids[id]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function Label:getTextHeight()
|
|
|
|
return #self.text_lines
|
|
|
|
end
|
|
|
|
|
|
|
|
function Label:getTextWidth()
|
|
|
|
render_text(self)
|
|
|
|
return self.text_width
|
|
|
|
end
|
|
|
|
|
2023-02-14 22:20:18 -07:00
|
|
|
-- Overridden by subclasses that also want to add new mouse handlers, see
|
|
|
|
-- HotkeyLabel.
|
|
|
|
function Label:shouldHover()
|
|
|
|
return self.on_click or self.on_rclick
|
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
function Label:onRenderBody(dc)
|
2016-05-09 19:29:04 -06:00
|
|
|
local text_pen = self.text_pen
|
2023-02-16 22:38:27 -07:00
|
|
|
local hovered = self:getMousePos() and self:shouldHover()
|
|
|
|
render_text(self,dc,0,0,text_pen,self.text_dpen,is_disabled(self), self.text_hpen, hovered)
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
|
2022-10-07 13:45:43 -06:00
|
|
|
function Label:on_scrollbar(scroll_spec)
|
|
|
|
local v = 0
|
2022-10-21 13:17:59 -06:00
|
|
|
if tonumber(scroll_spec) then
|
|
|
|
v = scroll_spec - self.start_line_num
|
|
|
|
elseif scroll_spec == 'down_large' then
|
2022-10-07 13:45:43 -06:00
|
|
|
v = '+halfpage'
|
|
|
|
elseif scroll_spec == 'up_large' then
|
|
|
|
v = '-halfpage'
|
|
|
|
elseif scroll_spec == 'down_small' then
|
|
|
|
v = 1
|
|
|
|
elseif scroll_spec == 'up_small' then
|
|
|
|
v = -1
|
2022-09-11 20:33:01 -06:00
|
|
|
end
|
|
|
|
|
2022-10-07 13:45:43 -06:00
|
|
|
self:scroll(v)
|
2022-09-11 20:33:01 -06:00
|
|
|
end
|
|
|
|
|
2020-11-04 19:06:50 -07:00
|
|
|
function Label:scroll(nlines)
|
2022-09-11 20:33:01 -06:00
|
|
|
if not nlines then return end
|
2023-01-23 03:15:45 -07:00
|
|
|
local text_height = math.max(1, self:getTextHeight())
|
2022-08-07 00:48:25 -06:00
|
|
|
if type(nlines) == 'string' then
|
|
|
|
if nlines == '+page' then
|
|
|
|
nlines = self.frame_body.height
|
|
|
|
elseif nlines == '-page' then
|
|
|
|
nlines = -self.frame_body.height
|
|
|
|
elseif nlines == '+halfpage' then
|
|
|
|
nlines = math.ceil(self.frame_body.height/2)
|
|
|
|
elseif nlines == '-halfpage' then
|
|
|
|
nlines = -math.ceil(self.frame_body.height/2)
|
2023-01-23 03:15:45 -07:00
|
|
|
elseif nlines == 'home' then
|
|
|
|
nlines = 1 - self.start_line_num
|
|
|
|
elseif nlines == 'end' then
|
|
|
|
nlines = text_height
|
2022-08-07 00:48:25 -06:00
|
|
|
else
|
|
|
|
error(('unhandled scroll keyword: "%s"'):format(nlines))
|
|
|
|
end
|
|
|
|
end
|
2020-11-04 19:06:50 -07:00
|
|
|
local n = self.start_line_num + nlines
|
2022-11-28 16:12:22 -07:00
|
|
|
n = math.min(n, text_height - self.frame_body.height + 1)
|
2020-11-04 19:06:50 -07:00
|
|
|
n = math.max(n, 1)
|
2022-10-07 13:45:43 -06:00
|
|
|
nlines = n - self.start_line_num
|
2020-11-04 19:06:50 -07:00
|
|
|
self.start_line_num = n
|
2022-10-07 13:45:43 -06:00
|
|
|
update_label_scrollbar(self)
|
2022-09-11 20:33:01 -06:00
|
|
|
return nlines
|
2020-11-04 19:06:50 -07:00
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
function Label:onInput(keys)
|
2020-11-04 19:06:50 -07:00
|
|
|
if is_disabled(self) then return false end
|
2022-10-07 13:45:43 -06:00
|
|
|
if self:inputToSubviews(keys) then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
if keys._MOUSE_L_DOWN and self:getMousePos() and self.on_click then
|
2023-03-29 00:36:49 -06:00
|
|
|
self.on_click()
|
2022-10-07 13:45:43 -06:00
|
|
|
return true
|
2020-11-04 19:06:50 -07:00
|
|
|
end
|
|
|
|
if keys._MOUSE_R_DOWN and self:getMousePos() and self.on_rclick then
|
2023-03-29 00:36:49 -06:00
|
|
|
self.on_rclick()
|
2022-10-07 13:45:43 -06:00
|
|
|
return true
|
2020-11-04 19:06:50 -07:00
|
|
|
end
|
|
|
|
for k,v in pairs(self.scroll_keys) do
|
2022-10-07 13:45:43 -06:00
|
|
|
if keys[k] and 0 ~= self:scroll(v) then
|
|
|
|
return true
|
2016-05-09 19:29:04 -06:00
|
|
|
end
|
2012-10-24 09:25:06 -06:00
|
|
|
end
|
2020-11-04 19:06:50 -07:00
|
|
|
return check_text_keys(self, keys)
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
|
2022-04-17 21:07:47 -06:00
|
|
|
------------------
|
2022-04-22 10:53:10 -06:00
|
|
|
-- WrappedLabel --
|
2022-04-17 21:07:47 -06:00
|
|
|
------------------
|
|
|
|
|
2022-04-22 10:53:10 -06:00
|
|
|
WrappedLabel = defclass(WrappedLabel, Label)
|
2022-04-17 21:07:47 -06:00
|
|
|
|
2022-04-22 10:53:10 -06:00
|
|
|
WrappedLabel.ATTRS{
|
|
|
|
text_to_wrap=DEFAULT_NIL,
|
|
|
|
indent=0,
|
2022-04-17 21:07:47 -06:00
|
|
|
}
|
|
|
|
|
2022-04-22 10:53:10 -06:00
|
|
|
function WrappedLabel:getWrappedText(width)
|
2022-04-29 12:29:00 -06:00
|
|
|
-- 0 width can happen if the parent has 0 width
|
|
|
|
if not self.text_to_wrap or width <= 0 then return nil end
|
2022-04-22 10:53:10 -06:00
|
|
|
local text_to_wrap = getval(self.text_to_wrap)
|
|
|
|
if type(text_to_wrap) == 'table' then
|
|
|
|
text_to_wrap = table.concat(text_to_wrap, NEWLINE)
|
2022-04-17 21:07:47 -06:00
|
|
|
end
|
2022-04-22 10:53:10 -06:00
|
|
|
return text_to_wrap:wrap(width - self.indent)
|
2022-04-17 21:07:47 -06:00
|
|
|
end
|
|
|
|
|
2023-01-15 16:59:09 -07:00
|
|
|
function WrappedLabel:preUpdateLayout()
|
|
|
|
self.saved_start_line_num = self.start_line_num
|
|
|
|
end
|
|
|
|
|
2022-04-17 21:07:47 -06:00
|
|
|
-- we can't set the text in init() since we may not yet have a frame that we
|
|
|
|
-- can get wrapping bounds from.
|
2022-04-22 10:53:10 -06:00
|
|
|
function WrappedLabel:postComputeFrame()
|
2023-01-03 01:56:07 -07:00
|
|
|
local wrapped_text = self:getWrappedText(self.frame_body.width-3)
|
2022-04-22 10:53:10 -06:00
|
|
|
if not wrapped_text then return end
|
2022-04-17 21:07:47 -06:00
|
|
|
local text = {}
|
2022-04-22 10:53:10 -06:00
|
|
|
for _,line in ipairs(wrapped_text:split(NEWLINE)) do
|
2022-04-17 21:07:47 -06:00
|
|
|
table.insert(text, {gap=self.indent, text=line})
|
2022-04-22 10:53:10 -06:00
|
|
|
-- a trailing newline will get ignored so we don't have to manually trim
|
2022-04-17 21:07:47 -06:00
|
|
|
table.insert(text, NEWLINE)
|
|
|
|
end
|
|
|
|
self:setText(text)
|
2023-01-15 16:59:09 -07:00
|
|
|
self:scroll(self.saved_start_line_num - 1)
|
2022-04-17 21:07:47 -06:00
|
|
|
end
|
|
|
|
|
2022-04-22 10:53:10 -06:00
|
|
|
------------------
|
|
|
|
-- TooltipLabel --
|
|
|
|
------------------
|
|
|
|
|
|
|
|
TooltipLabel = defclass(TooltipLabel, WrappedLabel)
|
|
|
|
|
|
|
|
TooltipLabel.ATTRS{
|
|
|
|
show_tooltip=DEFAULT_NIL,
|
|
|
|
indent=2,
|
|
|
|
text_pen=COLOR_GREY,
|
|
|
|
}
|
|
|
|
|
2022-12-02 16:36:45 -07:00
|
|
|
function TooltipLabel:init()
|
|
|
|
self.visible = self.show_tooltip
|
2022-04-22 10:53:10 -06:00
|
|
|
end
|
|
|
|
|
2022-04-17 21:07:47 -06:00
|
|
|
-----------------
|
|
|
|
-- HotkeyLabel --
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
HotkeyLabel = defclass(HotkeyLabel, Label)
|
|
|
|
|
|
|
|
HotkeyLabel.ATTRS{
|
|
|
|
key=DEFAULT_NIL,
|
2022-05-18 17:35:06 -06:00
|
|
|
key_sep=': ',
|
2022-04-17 21:07:47 -06:00
|
|
|
label=DEFAULT_NIL,
|
|
|
|
on_activate=DEFAULT_NIL,
|
|
|
|
}
|
|
|
|
|
|
|
|
function HotkeyLabel:init()
|
2023-02-08 11:00:13 -07:00
|
|
|
self:initializeLabel()
|
|
|
|
end
|
|
|
|
|
|
|
|
function HotkeyLabel:setOnActivate(on_activate)
|
|
|
|
self.on_activate = on_activate
|
|
|
|
self:initializeLabel()
|
|
|
|
end
|
|
|
|
|
|
|
|
function HotkeyLabel:setLabel(label)
|
|
|
|
self.label = label
|
|
|
|
self:initializeLabel()
|
|
|
|
end
|
|
|
|
|
2023-02-14 22:20:18 -07:00
|
|
|
function HotkeyLabel:shouldHover()
|
|
|
|
-- When on_activate is set, text should also hover on mouseover
|
2023-02-22 18:22:04 -07:00
|
|
|
return self.on_activate or HotkeyLabel.super.shouldHover(self)
|
2023-02-14 22:20:18 -07:00
|
|
|
end
|
|
|
|
|
2023-02-08 11:00:13 -07:00
|
|
|
function HotkeyLabel:initializeLabel()
|
2022-05-18 17:35:06 -06:00
|
|
|
self:setText{{key=self.key, key_sep=self.key_sep, text=self.label,
|
|
|
|
on_activate=self.on_activate}}
|
2022-04-17 21:07:47 -06:00
|
|
|
end
|
|
|
|
|
2022-07-17 17:04:36 -06:00
|
|
|
function HotkeyLabel:onInput(keys)
|
|
|
|
if HotkeyLabel.super.onInput(self, keys) then
|
|
|
|
return true
|
2023-02-17 15:23:07 -07:00
|
|
|
elseif keys._MOUSE_L_DOWN and self:getMousePos() and self.on_activate
|
|
|
|
and not is_disabled(self) then
|
2022-07-17 17:04:36 -06:00
|
|
|
self.on_activate()
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-08-07 13:40:48 -06:00
|
|
|
-----------------
|
|
|
|
-- BannerPanel --
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
BannerPanel = defclass(BannerPanel, Panel)
|
|
|
|
|
|
|
|
function BannerPanel:onRenderBody(dc)
|
2023-08-07 18:20:58 -06:00
|
|
|
dc:pen(COLOR_RED)
|
2023-08-07 13:40:48 -06:00
|
|
|
for y=0,self.frame_rect.height-1 do
|
2023-08-07 18:20:58 -06:00
|
|
|
dc:seek(0, y):char('[')
|
|
|
|
dc:seek(self.frame_rect.width-1):char(']')
|
2023-08-07 13:40:48 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-07-16 21:24:39 -06:00
|
|
|
----------------
|
|
|
|
-- TextButton --
|
|
|
|
----------------
|
|
|
|
|
2023-08-07 13:40:48 -06:00
|
|
|
TextButton = defclass(TextButton, BannerPanel)
|
2023-07-16 21:24:39 -06:00
|
|
|
|
|
|
|
function TextButton:init(info)
|
|
|
|
self.label = HotkeyLabel{
|
|
|
|
frame={t=0, l=1, r=1},
|
|
|
|
key=info.key,
|
|
|
|
key_sep=info.key_sep,
|
|
|
|
label=info.label,
|
|
|
|
on_activate=info.on_activate,
|
|
|
|
text_pen=info.text_pen,
|
|
|
|
text_dpen=info.text_dpen,
|
|
|
|
text_hpen=info.text_hpen,
|
|
|
|
disabled=info.disabled,
|
|
|
|
enabled=info.enabled,
|
|
|
|
auto_height=info.auto_height,
|
|
|
|
auto_width=info.auto_width,
|
|
|
|
on_click=info.on_click,
|
|
|
|
on_rclick=info.on_rclick,
|
|
|
|
scroll_keys=info.scroll_keys,
|
|
|
|
}
|
|
|
|
|
2023-08-07 13:40:48 -06:00
|
|
|
self:addviews{self.label}
|
2023-07-16 21:24:39 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function TextButton:setLabel(label)
|
|
|
|
self.label:setLabel(label)
|
|
|
|
end
|
|
|
|
|
2022-04-17 21:07:47 -06:00
|
|
|
----------------------
|
|
|
|
-- CycleHotkeyLabel --
|
|
|
|
----------------------
|
|
|
|
|
|
|
|
CycleHotkeyLabel = defclass(CycleHotkeyLabel, Label)
|
|
|
|
|
|
|
|
CycleHotkeyLabel.ATTRS{
|
|
|
|
key=DEFAULT_NIL,
|
2023-02-06 06:55:46 -07:00
|
|
|
key_back=DEFAULT_NIL,
|
2023-04-25 17:02:38 -06:00
|
|
|
key_sep=': ',
|
2023-05-02 08:46:46 -06:00
|
|
|
option_gap=1,
|
2022-04-17 21:07:47 -06:00
|
|
|
label=DEFAULT_NIL,
|
|
|
|
label_width=DEFAULT_NIL,
|
2023-02-27 05:13:05 -07:00
|
|
|
label_below=false,
|
2022-04-17 21:07:47 -06:00
|
|
|
options=DEFAULT_NIL,
|
|
|
|
initial_option=1,
|
|
|
|
on_change=DEFAULT_NIL,
|
|
|
|
}
|
|
|
|
|
|
|
|
function CycleHotkeyLabel:init()
|
2023-01-07 02:00:40 -07:00
|
|
|
self:setOption(self.initial_option)
|
2022-04-17 21:07:47 -06:00
|
|
|
|
2023-02-27 05:13:05 -07:00
|
|
|
if self.label_below then
|
2023-05-02 08:46:46 -06:00
|
|
|
self.option_gap = self.option_gap + (self.key_back and 1 or 0) + (self.key and 2 or 0)
|
2023-02-27 05:13:05 -07:00
|
|
|
end
|
|
|
|
|
2022-04-17 21:07:47 -06:00
|
|
|
self:setText{
|
2023-02-06 13:12:47 -07:00
|
|
|
self.key_back ~= nil and {key=self.key_back, key_sep='', width=0, on_activate=self:callback('cycle', true)} or {},
|
2023-04-25 17:02:38 -06:00
|
|
|
{key=self.key, key_sep=self.key_sep, text=self.label, width=self.label_width,
|
2022-04-17 21:07:47 -06:00
|
|
|
on_activate=self:callback('cycle')},
|
2023-02-27 05:13:05 -07:00
|
|
|
self.label_below and NEWLINE or '',
|
2023-05-02 08:46:46 -06:00
|
|
|
{gap=self.option_gap, text=self:callback('getOptionLabel'),
|
2023-01-06 12:34:15 -07:00
|
|
|
pen=self:callback('getOptionPen')},
|
2022-04-17 21:07:47 -06:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2023-02-14 22:20:18 -07:00
|
|
|
-- CycleHotkeyLabels are always clickable and therefore should always change
|
|
|
|
-- color when hovered.
|
|
|
|
function CycleHotkeyLabel:shouldHover()
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2023-02-06 06:55:46 -07:00
|
|
|
function CycleHotkeyLabel:cycle(backwards)
|
2022-04-17 21:07:47 -06:00
|
|
|
local old_option_idx = self.option_idx
|
2023-02-06 06:55:46 -07:00
|
|
|
if self.option_idx == #self.options and not backwards then
|
2022-04-17 21:07:47 -06:00
|
|
|
self.option_idx = 1
|
2023-02-06 06:55:46 -07:00
|
|
|
elseif self.option_idx == 1 and backwards then
|
|
|
|
self.option_idx = #self.options
|
2022-04-17 21:07:47 -06:00
|
|
|
else
|
2023-02-06 06:55:46 -07:00
|
|
|
self.option_idx = self.option_idx + (not backwards and 1 or -1)
|
2022-04-17 21:07:47 -06:00
|
|
|
end
|
|
|
|
if self.on_change then
|
|
|
|
self.on_change(self:getOptionValue(),
|
|
|
|
self:getOptionValue(old_option_idx))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-01-07 02:00:40 -07:00
|
|
|
function CycleHotkeyLabel:setOption(value_or_index, call_on_change)
|
|
|
|
local option_idx = nil
|
|
|
|
for i in ipairs(self.options) do
|
|
|
|
if value_or_index == self:getOptionValue(i) then
|
|
|
|
option_idx = i
|
|
|
|
break
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not option_idx then
|
|
|
|
if self.options[value_or_index] then
|
|
|
|
option_idx = value_or_index
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not option_idx then
|
2023-06-29 19:13:16 -06:00
|
|
|
option_idx = 1
|
2023-01-07 02:00:40 -07:00
|
|
|
end
|
|
|
|
local old_option_idx = self.option_idx
|
|
|
|
self.option_idx = option_idx
|
|
|
|
if call_on_change and self.on_change then
|
|
|
|
self.on_change(self:getOptionValue(),
|
|
|
|
self:getOptionValue(old_option_idx))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-29 00:36:49 -06:00
|
|
|
local function cyclehotkeylabel_getOptionElem(self, option_idx, key, require_key)
|
2022-04-17 21:07:47 -06:00
|
|
|
option_idx = option_idx or self.option_idx
|
|
|
|
local option = self.options[option_idx]
|
|
|
|
if type(option) == 'table' then
|
2023-01-06 12:34:15 -07:00
|
|
|
return option[key]
|
2022-04-17 21:07:47 -06:00
|
|
|
end
|
2023-03-29 00:36:49 -06:00
|
|
|
return not require_key and option or nil
|
2022-04-17 21:07:47 -06:00
|
|
|
end
|
|
|
|
|
2023-01-06 12:34:15 -07:00
|
|
|
function CycleHotkeyLabel:getOptionLabel(option_idx)
|
2023-03-29 00:36:49 -06:00
|
|
|
return getval(cyclehotkeylabel_getOptionElem(self, option_idx, 'label'))
|
2023-01-06 12:34:15 -07:00
|
|
|
end
|
|
|
|
|
2022-04-17 21:07:47 -06:00
|
|
|
function CycleHotkeyLabel:getOptionValue(option_idx)
|
2023-01-06 12:34:15 -07:00
|
|
|
return cyclehotkeylabel_getOptionElem(self, option_idx, 'value')
|
|
|
|
end
|
|
|
|
|
|
|
|
function CycleHotkeyLabel:getOptionPen(option_idx)
|
2023-03-29 00:36:49 -06:00
|
|
|
local pen = getval(cyclehotkeylabel_getOptionElem(self, option_idx, 'pen', true))
|
2023-01-06 13:15:38 -07:00
|
|
|
if type(pen) == 'string' then return nil end
|
|
|
|
return pen
|
2022-04-17 21:07:47 -06:00
|
|
|
end
|
|
|
|
|
2022-08-08 11:56:17 -06:00
|
|
|
function CycleHotkeyLabel:onInput(keys)
|
|
|
|
if CycleHotkeyLabel.super.onInput(self, keys) then
|
|
|
|
return true
|
2023-02-27 05:13:05 -07:00
|
|
|
elseif keys._MOUSE_L_DOWN and self:getMousePos() and not is_disabled(self) then
|
2022-08-08 11:56:17 -06:00
|
|
|
self:cycle()
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-04-17 21:07:47 -06:00
|
|
|
-----------------------
|
|
|
|
-- ToggleHotkeyLabel --
|
|
|
|
-----------------------
|
|
|
|
|
|
|
|
ToggleHotkeyLabel = defclass(ToggleHotkeyLabel, CycleHotkeyLabel)
|
|
|
|
ToggleHotkeyLabel.ATTRS{
|
2023-01-06 12:34:15 -07:00
|
|
|
options={{label='On', value=true, pen=COLOR_GREEN},
|
2022-04-17 21:07:47 -06:00
|
|
|
{label='Off', value=false}},
|
|
|
|
}
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
----------
|
|
|
|
-- List --
|
|
|
|
----------
|
|
|
|
|
|
|
|
List = defclass(List, Widget)
|
|
|
|
|
|
|
|
List.ATTRS{
|
|
|
|
text_pen = COLOR_CYAN,
|
2023-02-18 17:06:03 -07:00
|
|
|
text_hpen = DEFAULT_NIL, -- hover color, defaults to inverting the FG/BG pens for each text object
|
2012-10-16 04:18:35 -06:00
|
|
|
cursor_pen = COLOR_LIGHTCYAN,
|
|
|
|
inactive_pen = DEFAULT_NIL,
|
|
|
|
on_select = DEFAULT_NIL,
|
|
|
|
on_submit = DEFAULT_NIL,
|
2012-11-29 05:27:51 -07:00
|
|
|
on_submit2 = DEFAULT_NIL,
|
2023-02-02 21:24:40 -07:00
|
|
|
on_double_click = DEFAULT_NIL,
|
|
|
|
on_double_click2 = DEFAULT_NIL,
|
2012-10-16 04:18:35 -06:00
|
|
|
row_height = 1,
|
|
|
|
scroll_keys = STANDARDSCROLL,
|
2012-10-16 23:41:48 -06:00
|
|
|
icon_width = DEFAULT_NIL,
|
2012-10-16 04:18:35 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
function List:init(info)
|
|
|
|
self.page_top = 1
|
|
|
|
self.page_size = 1
|
2022-10-06 12:13:16 -06:00
|
|
|
self.scrollbar = Scrollbar{
|
|
|
|
frame={r=0},
|
|
|
|
on_scroll=self:callback('on_scrollbar')}
|
|
|
|
|
|
|
|
self:addviews{self.scrollbar}
|
2012-12-01 05:50:03 -07:00
|
|
|
|
|
|
|
if info.choices then
|
|
|
|
self:setChoices(info.choices, info.selected)
|
|
|
|
else
|
|
|
|
self.choices = {}
|
|
|
|
self.selected = 1
|
|
|
|
end
|
2023-02-02 21:24:40 -07:00
|
|
|
|
|
|
|
self.last_select_click_ms = 0 -- used to track double-clicking on an item
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function List:setChoices(choices, selected)
|
2018-06-20 08:23:57 -06:00
|
|
|
self.choices = {}
|
2012-10-16 04:18:35 -06:00
|
|
|
|
2018-06-20 08:46:24 -06:00
|
|
|
for i,v in ipairs(choices or {}) do
|
2018-06-20 08:23:57 -06:00
|
|
|
local l = utils.clone(v);
|
2012-10-16 04:18:35 -06:00
|
|
|
if type(v) ~= 'table' then
|
2018-06-20 08:23:57 -06:00
|
|
|
l = { text = v }
|
|
|
|
else
|
|
|
|
l.text = v.text or v.caption or v[1]
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
2018-06-20 08:23:57 -06:00
|
|
|
parse_label_text(l)
|
|
|
|
self.choices[i] = l
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
self:setSelected(selected)
|
2023-02-14 18:33:33 -07:00
|
|
|
|
|
|
|
-- Check if page_top needs to be adjusted
|
|
|
|
if #self.choices - self.page_size < 0 then
|
|
|
|
self.page_top = 1
|
2023-05-20 05:38:03 -06:00
|
|
|
elseif self.page_top > #self.choices - self.page_size + 1 then
|
2023-02-14 18:33:33 -07:00
|
|
|
self.page_top = #self.choices - self.page_size + 1
|
|
|
|
end
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function List:setSelected(selected)
|
|
|
|
self.selected = selected or self.selected or 1
|
|
|
|
self:moveCursor(0, true)
|
|
|
|
return self.selected
|
|
|
|
end
|
|
|
|
|
2012-10-17 00:41:50 -06:00
|
|
|
function List:getChoices()
|
|
|
|
return self.choices
|
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
function List:getSelected()
|
2012-10-24 09:25:06 -06:00
|
|
|
if #self.choices > 0 then
|
|
|
|
return self.selected, self.choices[self.selected]
|
|
|
|
end
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function List:getContentWidth()
|
|
|
|
local width = 0
|
|
|
|
for i,v in ipairs(self.choices) do
|
|
|
|
render_text(v)
|
|
|
|
local roww = v.text_width
|
|
|
|
if v.key then
|
|
|
|
roww = roww + 3 + #gui.getKeyDisplay(v.key)
|
|
|
|
end
|
|
|
|
width = math.max(width, roww)
|
|
|
|
end
|
2012-10-16 23:41:48 -06:00
|
|
|
return width + (self.icon_width or 0)
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function List:getContentHeight()
|
|
|
|
return #self.choices * self.row_height
|
|
|
|
end
|
|
|
|
|
2022-10-06 12:13:16 -06:00
|
|
|
local function update_list_scrollbar(list)
|
2022-10-07 13:45:43 -06:00
|
|
|
list.scrollbar:update(list.page_top, list.page_size, #list.choices)
|
|
|
|
end
|
|
|
|
|
2023-02-02 20:42:41 -07:00
|
|
|
function List:postComputeFrame(body)
|
2023-05-19 13:12:52 -06:00
|
|
|
local row_count = body.height // self.row_height
|
2023-02-14 18:33:33 -07:00
|
|
|
self.page_size = math.max(1, row_count)
|
|
|
|
|
|
|
|
local num_choices = #self.choices
|
|
|
|
if num_choices == 0 then
|
2023-02-07 15:12:52 -07:00
|
|
|
self.page_top = 1
|
2023-02-14 18:33:33 -07:00
|
|
|
update_list_scrollbar(self)
|
|
|
|
return
|
2023-02-07 15:12:52 -07:00
|
|
|
end
|
2023-02-14 18:33:33 -07:00
|
|
|
|
2023-05-20 05:38:03 -06:00
|
|
|
if self.page_top > num_choices - self.page_size + 1 then
|
|
|
|
self.page_top = math.max(1, num_choices - self.page_size + 1)
|
2023-02-14 18:33:33 -07:00
|
|
|
end
|
|
|
|
|
2023-02-02 20:42:41 -07:00
|
|
|
update_list_scrollbar(self)
|
|
|
|
end
|
|
|
|
|
2022-10-07 13:45:43 -06:00
|
|
|
function List:postUpdateLayout()
|
|
|
|
update_list_scrollbar(self)
|
2022-10-06 12:13:16 -06:00
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
function List:moveCursor(delta, force_cb)
|
|
|
|
local cnt = #self.choices
|
2012-10-16 08:33:00 -06:00
|
|
|
|
|
|
|
if cnt < 1 then
|
|
|
|
self.page_top = 1
|
|
|
|
self.selected = 1
|
2022-10-06 12:13:16 -06:00
|
|
|
update_list_scrollbar(self)
|
2012-12-01 05:50:03 -07:00
|
|
|
if force_cb and self.on_select then
|
|
|
|
self.on_select(nil,nil)
|
|
|
|
end
|
2012-10-16 08:33:00 -06:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
local off = self.selected+delta-1
|
|
|
|
local ds = math.abs(delta)
|
|
|
|
|
|
|
|
if ds > 1 then
|
|
|
|
if off >= cnt+ds-1 then
|
|
|
|
off = 0
|
|
|
|
else
|
|
|
|
off = math.min(cnt-1, off)
|
|
|
|
end
|
|
|
|
if off <= -ds then
|
|
|
|
off = cnt-1
|
|
|
|
else
|
|
|
|
off = math.max(0, off)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-10-06 12:13:16 -06:00
|
|
|
local buffer = 1 + math.min(4, math.floor(self.page_size/10))
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
self.selected = 1 + off % cnt
|
2022-10-06 12:13:16 -06:00
|
|
|
if (self.selected - buffer) < self.page_top then
|
|
|
|
self.page_top = math.max(1, self.selected - buffer)
|
|
|
|
elseif (self.selected + buffer + 1) > (self.page_top + self.page_size) then
|
|
|
|
local max_page_top = cnt - self.page_size + 1
|
|
|
|
self.page_top = math.max(1,
|
|
|
|
math.min(max_page_top, self.selected - self.page_size + buffer + 1))
|
|
|
|
end
|
|
|
|
update_list_scrollbar(self)
|
2012-10-16 04:18:35 -06:00
|
|
|
|
|
|
|
if (force_cb or delta ~= 0) and self.on_select then
|
|
|
|
self.on_select(self:getSelected())
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-10-06 12:13:16 -06:00
|
|
|
function List:on_scrollbar(scroll_spec)
|
|
|
|
local v = 0
|
2022-10-21 13:17:59 -06:00
|
|
|
if tonumber(scroll_spec) then
|
|
|
|
v = scroll_spec - self.page_top
|
|
|
|
elseif scroll_spec == 'down_large' then
|
2022-10-07 13:45:43 -06:00
|
|
|
v = math.ceil(self.page_size / 2)
|
2022-10-06 12:13:16 -06:00
|
|
|
elseif scroll_spec == 'up_large' then
|
2022-10-07 13:45:43 -06:00
|
|
|
v = -math.ceil(self.page_size / 2)
|
2022-10-06 12:13:16 -06:00
|
|
|
elseif scroll_spec == 'down_small' then
|
|
|
|
v = 1
|
|
|
|
elseif scroll_spec == 'up_small' then
|
|
|
|
v = -1
|
|
|
|
end
|
|
|
|
|
|
|
|
local max_page_top = math.max(1, #self.choices - self.page_size + 1)
|
|
|
|
self.page_top = math.max(1, math.min(max_page_top, self.page_top + v))
|
|
|
|
update_list_scrollbar(self)
|
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
function List:onRenderBody(dc)
|
|
|
|
local choices = self.choices
|
|
|
|
local top = self.page_top
|
|
|
|
local iend = math.min(#choices, top+self.page_size-1)
|
2012-10-16 23:41:48 -06:00
|
|
|
local iw = self.icon_width
|
2012-10-16 04:18:35 -06:00
|
|
|
|
2012-11-17 09:32:39 -07:00
|
|
|
local function paint_icon(icon, obj)
|
|
|
|
if type(icon) ~= 'string' then
|
2023-01-30 09:48:45 -07:00
|
|
|
dc:char(nil,icon)
|
2012-11-17 09:32:39 -07:00
|
|
|
else
|
|
|
|
if current then
|
|
|
|
dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen)
|
|
|
|
else
|
|
|
|
dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-15 23:33:31 -07:00
|
|
|
local hoveridx = self:getIdxUnderMouse()
|
2012-10-16 04:18:35 -06:00
|
|
|
for i = top,iend do
|
|
|
|
local obj = choices[i]
|
|
|
|
local current = (i == self.selected)
|
2023-02-15 23:33:31 -07:00
|
|
|
local hovered = (i == hoveridx)
|
2023-02-16 22:38:27 -07:00
|
|
|
-- cur_pen and cur_dpen can't be integers or background colors get
|
|
|
|
-- messed up in render_text for subsequent renders
|
2023-02-15 23:33:31 -07:00
|
|
|
local cur_pen = to_pen(self.cursor_pen)
|
|
|
|
local cur_dpen = to_pen(self.text_pen)
|
|
|
|
local active_pen = (current and cur_pen or cur_dpen)
|
2012-10-16 04:18:35 -06:00
|
|
|
|
2022-12-02 16:36:45 -07:00
|
|
|
if not getval(self.active) then
|
2012-10-16 04:18:35 -06:00
|
|
|
cur_pen = self.inactive_pen or self.cursor_pen
|
|
|
|
end
|
|
|
|
|
|
|
|
local y = (i - top)*self.row_height
|
2012-11-17 09:32:39 -07:00
|
|
|
local icon = getval(obj.icon)
|
2012-10-16 23:41:48 -06:00
|
|
|
|
2012-11-17 09:32:39 -07:00
|
|
|
if iw and icon then
|
2015-10-30 17:16:29 -06:00
|
|
|
dc:seek(0, y):pen(active_pen)
|
2012-11-17 09:32:39 -07:00
|
|
|
paint_icon(icon, obj)
|
2012-10-16 23:41:48 -06:00
|
|
|
end
|
|
|
|
|
2023-02-16 22:38:27 -07:00
|
|
|
render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current, self.text_hpen, hovered)
|
2012-10-16 04:18:35 -06:00
|
|
|
|
2012-11-17 09:32:39 -07:00
|
|
|
local ip = dc.width
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
if obj.key then
|
|
|
|
local keystr = gui.getKeyDisplay(obj.key)
|
2022-10-19 18:30:51 -06:00
|
|
|
ip = ip-3-#keystr
|
2012-11-17 09:32:39 -07:00
|
|
|
dc:seek(ip,y):pen(self.text_pen)
|
2012-10-16 04:18:35 -06:00
|
|
|
dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')')
|
|
|
|
end
|
2012-11-17 09:32:39 -07:00
|
|
|
|
|
|
|
if icon and not iw then
|
2015-10-30 17:16:29 -06:00
|
|
|
dc:seek(ip-1,y):pen(active_pen)
|
2012-11-17 09:32:39 -07:00
|
|
|
paint_icon(icon, obj)
|
|
|
|
end
|
2012-10-16 04:18:35 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-11-07 17:13:45 -07:00
|
|
|
function List:getIdxUnderMouse()
|
2022-11-11 11:40:10 -07:00
|
|
|
if self.scrollbar:getMousePos() then return end
|
2022-11-07 17:13:45 -07:00
|
|
|
local _,mouse_y = self:getMousePos()
|
|
|
|
if mouse_y and #self.choices > 0 and
|
|
|
|
mouse_y < (#self.choices-self.page_top+1) * self.row_height then
|
|
|
|
return self.page_top + math.floor(mouse_y/self.row_height)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-16 08:33:00 -06:00
|
|
|
function List:submit()
|
|
|
|
if self.on_submit and #self.choices > 0 then
|
|
|
|
self.on_submit(self:getSelected())
|
2022-12-30 02:13:13 -07:00
|
|
|
return true
|
2012-10-16 08:33:00 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-11-29 05:27:51 -07:00
|
|
|
function List:submit2()
|
|
|
|
if self.on_submit2 and #self.choices > 0 then
|
|
|
|
self.on_submit2(self:getSelected())
|
2022-12-30 02:13:13 -07:00
|
|
|
return true
|
2012-11-29 05:27:51 -07:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-02 21:24:40 -07:00
|
|
|
function List:double_click()
|
|
|
|
if #self.choices == 0 then return end
|
|
|
|
local cb = dfhack.internal.getModifiers().shift and
|
|
|
|
self.on_double_click2 or self.on_double_click
|
|
|
|
if cb then
|
|
|
|
cb(self:getSelected())
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-16 04:18:35 -06:00
|
|
|
function List:onInput(keys)
|
2022-10-06 12:13:16 -06:00
|
|
|
if self:inputToSubviews(keys) then
|
|
|
|
return true
|
|
|
|
end
|
2022-12-30 02:13:13 -07:00
|
|
|
if keys.SELECT then
|
|
|
|
return self:submit()
|
|
|
|
elseif keys.CUSTOM_SHIFT_ENTER then
|
|
|
|
return self:submit2()
|
2022-11-21 18:36:46 -07:00
|
|
|
elseif keys._MOUSE_L_DOWN then
|
2022-11-07 17:13:45 -07:00
|
|
|
local idx = self:getIdxUnderMouse()
|
|
|
|
if idx then
|
2023-02-02 21:24:40 -07:00
|
|
|
local now_ms = dfhack.getTickCount()
|
|
|
|
if idx ~= self:getSelected() then
|
|
|
|
self.last_select_click_ms = now_ms
|
|
|
|
else
|
|
|
|
if now_ms - self.last_select_click_ms <= DOUBLE_CLICK_MS then
|
|
|
|
self.last_select_click_ms = 0
|
|
|
|
if self:double_click() then return true end
|
|
|
|
else
|
|
|
|
self.last_select_click_ms = now_ms
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-07-16 23:18:38 -06:00
|
|
|
self:setSelected(idx)
|
2022-11-07 17:14:16 -07:00
|
|
|
if dfhack.internal.getModifiers().shift then
|
|
|
|
self:submit2()
|
|
|
|
else
|
|
|
|
self:submit()
|
|
|
|
end
|
2022-07-16 23:18:38 -06:00
|
|
|
return true
|
|
|
|
end
|
2012-10-16 04:18:35 -06:00
|
|
|
else
|
|
|
|
for k,v in pairs(self.scroll_keys) do
|
|
|
|
if keys[k] then
|
|
|
|
if v == '+page' then
|
|
|
|
v = self.page_size
|
|
|
|
elseif v == '-page' then
|
|
|
|
v = -self.page_size
|
|
|
|
end
|
|
|
|
|
|
|
|
self:moveCursor(v)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for i,v in ipairs(self.choices) do
|
|
|
|
if v.key and keys[v.key] then
|
|
|
|
self:setSelected(i)
|
2012-10-16 08:33:00 -06:00
|
|
|
self:submit()
|
2012-10-16 04:18:35 -06:00
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local current = self.choices[self.selected]
|
|
|
|
if current then
|
|
|
|
return check_text_keys(current, keys)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-17 00:41:50 -06:00
|
|
|
-------------------
|
|
|
|
-- Filtered List --
|
|
|
|
-------------------
|
|
|
|
|
2023-05-16 13:36:58 -06:00
|
|
|
FILTER_FULL_TEXT = false
|
|
|
|
|
2012-10-17 00:41:50 -06:00
|
|
|
FilteredList = defclass(FilteredList, Widget)
|
|
|
|
|
2012-11-30 08:10:17 -07:00
|
|
|
FilteredList.ATTRS {
|
2023-02-26 10:47:15 -07:00
|
|
|
case_sensitive = false,
|
2012-11-30 08:10:17 -07:00
|
|
|
edit_below = false,
|
2017-06-27 19:10:14 -06:00
|
|
|
edit_key = DEFAULT_NIL,
|
2022-08-29 12:40:56 -06:00
|
|
|
edit_ignore_keys = DEFAULT_NIL,
|
2022-10-21 13:43:53 -06:00
|
|
|
edit_on_char = DEFAULT_NIL,
|
2023-02-12 15:44:19 -07:00
|
|
|
edit_on_change = DEFAULT_NIL,
|
2012-11-30 08:10:17 -07:00
|
|
|
}
|
|
|
|
|
2012-10-17 00:41:50 -06:00
|
|
|
function FilteredList:init(info)
|
2022-10-21 13:43:53 -06:00
|
|
|
local on_char = self:callback('onFilterChar')
|
|
|
|
if self.edit_on_char then
|
|
|
|
on_char = function(c, text)
|
|
|
|
return self.edit_on_char(c, text) and self:onFilterChar(c, text)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-12 15:44:19 -07:00
|
|
|
local on_change = self:callback('onFilterChange')
|
|
|
|
if self.edit_on_change then
|
|
|
|
on_change = function(text)
|
|
|
|
self.edit_on_change(text)
|
|
|
|
self:onFilterChange(text)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-17 00:41:50 -06:00
|
|
|
self.edit = EditField{
|
2012-11-04 06:06:32 -07:00
|
|
|
text_pen = info.edit_pen or info.cursor_pen,
|
2012-11-30 08:10:17 -07:00
|
|
|
frame = { l = info.icon_width, t = 0, h = 1 },
|
2023-02-12 15:44:19 -07:00
|
|
|
on_change = on_change,
|
2022-10-21 13:43:53 -06:00
|
|
|
on_char = on_char,
|
2017-06-27 19:10:14 -06:00
|
|
|
key = self.edit_key,
|
2022-08-29 12:40:56 -06:00
|
|
|
ignore_keys = self.edit_ignore_keys,
|
2012-10-17 00:41:50 -06:00
|
|
|
}
|
|
|
|
self.list = List{
|
|
|
|
frame = { t = 2 },
|
|
|
|
text_pen = info.text_pen,
|
|
|
|
cursor_pen = info.cursor_pen,
|
|
|
|
inactive_pen = info.inactive_pen,
|
2012-11-04 06:06:32 -07:00
|
|
|
icon_pen = info.icon_pen,
|
2012-10-17 00:41:50 -06:00
|
|
|
row_height = info.row_height,
|
|
|
|
scroll_keys = info.scroll_keys,
|
|
|
|
icon_width = info.icon_width,
|
|
|
|
}
|
2012-11-30 08:10:17 -07:00
|
|
|
if self.edit_below then
|
|
|
|
self.edit.frame = { l = info.icon_width, b = 0, h = 1 }
|
|
|
|
self.list.frame = { t = 0, b = 2 }
|
|
|
|
end
|
2012-10-17 00:41:50 -06:00
|
|
|
if info.on_select then
|
|
|
|
self.list.on_select = function()
|
|
|
|
return info.on_select(self:getSelected())
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if info.on_submit then
|
|
|
|
self.list.on_submit = function()
|
|
|
|
return info.on_submit(self:getSelected())
|
|
|
|
end
|
|
|
|
end
|
2012-11-29 05:27:51 -07:00
|
|
|
if info.on_submit2 then
|
|
|
|
self.list.on_submit2 = function()
|
|
|
|
return info.on_submit2(self:getSelected())
|
|
|
|
end
|
|
|
|
end
|
2023-02-02 21:42:23 -07:00
|
|
|
if info.on_double_click then
|
|
|
|
self.list.on_double_click = function()
|
|
|
|
return info.on_double_click(self:getSelected())
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if info.on_double_click2 then
|
|
|
|
self.list.on_double_click2 = function()
|
|
|
|
return info.on_double_click2(self:getSelected())
|
|
|
|
end
|
|
|
|
end
|
2012-10-17 00:41:50 -06:00
|
|
|
self.not_found = Label{
|
2012-12-01 05:50:03 -07:00
|
|
|
visible = true,
|
2012-10-17 00:41:50 -06:00
|
|
|
text = info.not_found_label or 'No matches',
|
|
|
|
text_pen = COLOR_LIGHTRED,
|
2012-11-30 08:10:17 -07:00
|
|
|
frame = { l = info.icon_width, t = self.list.frame.t },
|
2012-10-17 00:41:50 -06:00
|
|
|
}
|
2012-10-17 01:49:11 -06:00
|
|
|
self:addviews{ self.edit, self.list, self.not_found }
|
2012-12-01 05:50:03 -07:00
|
|
|
if info.choices then
|
|
|
|
self:setChoices(info.choices, info.selected)
|
|
|
|
else
|
|
|
|
self.choices = {}
|
|
|
|
end
|
2012-10-17 00:41:50 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function FilteredList:getChoices()
|
|
|
|
return self.choices
|
|
|
|
end
|
|
|
|
|
2017-06-27 19:10:14 -06:00
|
|
|
function FilteredList:getVisibleChoices()
|
|
|
|
return self.list.choices
|
|
|
|
end
|
|
|
|
|
2012-10-17 00:41:50 -06:00
|
|
|
function FilteredList:setChoices(choices, pos)
|
|
|
|
choices = choices or {}
|
2022-08-15 17:22:56 -06:00
|
|
|
self.edit:setText('')
|
2012-10-17 00:41:50 -06:00
|
|
|
self.list:setChoices(choices, pos)
|
2020-02-26 23:56:30 -07:00
|
|
|
self.choices = self.list.choices
|
2012-10-17 00:41:50 -06:00
|
|
|
self.not_found.visible = (#choices == 0)
|
|
|
|
end
|
|
|
|
|
|
|
|
function FilteredList:submit()
|
|
|
|
return self.list:submit()
|
|
|
|
end
|
|
|
|
|
2012-11-29 05:27:51 -07:00
|
|
|
function FilteredList:submit2()
|
|
|
|
return self.list:submit2()
|
|
|
|
end
|
|
|
|
|
2012-10-17 00:41:50 -06:00
|
|
|
function FilteredList:canSubmit()
|
|
|
|
return not self.not_found.visible
|
|
|
|
end
|
|
|
|
|
|
|
|
function FilteredList:getSelected()
|
|
|
|
local i,v = self.list:getSelected()
|
2012-10-24 09:25:06 -06:00
|
|
|
if i then
|
|
|
|
return map_opttab(self.choice_index, i), v
|
|
|
|
end
|
2012-10-17 00:41:50 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
function FilteredList:getContentWidth()
|
|
|
|
return self.list:getContentWidth()
|
|
|
|
end
|
|
|
|
|
|
|
|
function FilteredList:getContentHeight()
|
|
|
|
return self.list:getContentHeight() + 2
|
|
|
|
end
|
|
|
|
|
|
|
|
function FilteredList:getFilter()
|
|
|
|
return self.edit.text, self.list.choices
|
|
|
|
end
|
|
|
|
|
|
|
|
function FilteredList:setFilter(filter, pos)
|
|
|
|
local choices = self.choices
|
|
|
|
local cidx = nil
|
|
|
|
|
|
|
|
filter = filter or ''
|
2022-08-29 16:17:17 -06:00
|
|
|
if filter ~= self.edit.text then
|
|
|
|
self.edit:setText(filter)
|
|
|
|
end
|
2012-10-17 00:41:50 -06:00
|
|
|
|
|
|
|
if filter ~= '' then
|
2021-07-03 00:30:59 -06:00
|
|
|
local tokens = filter:split()
|
2012-10-17 00:41:50 -06:00
|
|
|
local ipos = pos
|
|
|
|
|
|
|
|
choices = {}
|
|
|
|
cidx = {}
|
|
|
|
pos = nil
|
|
|
|
|
|
|
|
for i,v in ipairs(self.choices) do
|
|
|
|
local ok = true
|
2022-11-28 16:49:01 -07:00
|
|
|
local search_key = v.search_key
|
|
|
|
if not search_key then
|
|
|
|
if type(v.text) ~= 'table' then
|
|
|
|
search_key = v.text
|
|
|
|
else
|
|
|
|
local texts = {}
|
|
|
|
for _,token in ipairs(v.text) do
|
|
|
|
table.insert(texts,
|
|
|
|
type(token) == 'string' and token
|
|
|
|
or getval(token.text) or '')
|
|
|
|
end
|
|
|
|
search_key = table.concat(texts, ' ')
|
|
|
|
end
|
|
|
|
end
|
2012-10-17 00:41:50 -06:00
|
|
|
for _,key in ipairs(tokens) do
|
2022-04-11 19:22:31 -06:00
|
|
|
key = key:escape_pattern()
|
2023-02-06 20:40:59 -07:00
|
|
|
if key ~= '' then
|
2023-02-08 00:02:50 -07:00
|
|
|
if not self.case_sensitive then
|
|
|
|
search_key = string.lower(search_key)
|
|
|
|
key = string.lower(key)
|
|
|
|
end
|
|
|
|
|
2023-05-16 13:36:58 -06:00
|
|
|
-- the separate checks for non-space or non-punctuation allows
|
|
|
|
-- punctuation itself to be matched if that is useful (e.g.
|
|
|
|
-- filenames or parameter names)
|
|
|
|
if not FILTER_FULL_TEXT and not search_key:match('%f[^%p\x00]'..key)
|
|
|
|
and not search_key:match('%f[^%s\x00]'..key) then
|
|
|
|
ok = false
|
|
|
|
break
|
|
|
|
elseif FILTER_FULL_TEXT and not search_key:find(key) then
|
|
|
|
ok = false
|
|
|
|
break
|
2023-02-06 20:40:59 -07:00
|
|
|
end
|
2012-10-17 00:41:50 -06:00
|
|
|
end
|
|
|
|
end
|
|
|
|
if ok then
|
|
|
|
table.insert(choices, v)
|
|
|
|
cidx[#choices] = i
|
|
|
|
if ipos == i then
|
|
|
|
pos = #choices
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
self.choice_index = cidx
|
|
|
|
self.list:setChoices(choices, pos)
|
|
|
|
self.not_found.visible = (#choices == 0)
|
|
|
|
end
|
|
|
|
|
|
|
|
function FilteredList:onFilterChange(text)
|
|
|
|
self:setFilter(text)
|
|
|
|
end
|
|
|
|
|
|
|
|
function FilteredList:onFilterChar(char, text)
|
|
|
|
if char == ' ' then
|
|
|
|
return string.match(text, '%S$')
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2023-05-16 18:12:44 -06:00
|
|
|
local TSO = df.global.init.tabs_texpos[0] -- tab spritesheet offset
|
2023-02-11 06:44:34 -07:00
|
|
|
local DEFAULT_ACTIVE_TAB_PENS = {
|
|
|
|
text_mode_tab_pen=to_pen{fg=COLOR_YELLOW},
|
|
|
|
text_mode_label_pen=to_pen{fg=COLOR_WHITE},
|
2023-05-16 18:12:44 -06:00
|
|
|
lt=to_pen{tile=TSO+5, write_to_lower=true},
|
|
|
|
lt2=to_pen{tile=TSO+6, write_to_lower=true},
|
|
|
|
t=to_pen{tile=TSO+7, fg=COLOR_BLACK, write_to_lower=true, top_of_text=true},
|
|
|
|
rt2=to_pen{tile=TSO+8, write_to_lower=true},
|
|
|
|
rt=to_pen{tile=TSO+9, write_to_lower=true},
|
|
|
|
lb=to_pen{tile=TSO+15, write_to_lower=true},
|
|
|
|
lb2=to_pen{tile=TSO+16, write_to_lower=true},
|
|
|
|
b=to_pen{tile=TSO+17, fg=COLOR_BLACK, write_to_lower=true, bottom_of_text=true},
|
|
|
|
rb2=to_pen{tile=TSO+18, write_to_lower=true},
|
|
|
|
rb=to_pen{tile=TSO+19, write_to_lower=true},
|
2023-02-11 06:44:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
local DEFAULT_INACTIVE_TAB_PENS = {
|
|
|
|
text_mode_tab_pen=to_pen{fg=COLOR_BROWN},
|
|
|
|
text_mode_label_pen=to_pen{fg=COLOR_DARKGREY},
|
2023-05-16 18:12:44 -06:00
|
|
|
lt=to_pen{tile=TSO+0, write_to_lower=true},
|
|
|
|
lt2=to_pen{tile=TSO+1, write_to_lower=true},
|
|
|
|
t=to_pen{tile=TSO+2, fg=COLOR_WHITE, write_to_lower=true, top_of_text=true},
|
|
|
|
rt2=to_pen{tile=TSO+3, write_to_lower=true},
|
|
|
|
rt=to_pen{tile=TSO+4, write_to_lower=true},
|
|
|
|
lb=to_pen{tile=TSO+10, write_to_lower=true},
|
|
|
|
lb2=to_pen{tile=TSO+11, write_to_lower=true},
|
|
|
|
b=to_pen{tile=TSO+12, fg=COLOR_WHITE, write_to_lower=true, bottom_of_text=true},
|
|
|
|
rb2=to_pen{tile=TSO+13, write_to_lower=true},
|
|
|
|
rb=to_pen{tile=TSO+14, write_to_lower=true},
|
2023-02-11 06:44:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
---------
|
|
|
|
-- Tab --
|
|
|
|
---------
|
|
|
|
|
|
|
|
Tab = defclass(Tabs, Widget)
|
|
|
|
Tab.ATTRS{
|
|
|
|
id=DEFAULT_NIL,
|
|
|
|
label=DEFAULT_NIL,
|
|
|
|
on_select=DEFAULT_NIL,
|
|
|
|
get_pens=DEFAULT_NIL,
|
|
|
|
}
|
|
|
|
|
|
|
|
function Tab:preinit(init_table)
|
|
|
|
init_table.frame = init_table.frame or {}
|
|
|
|
init_table.frame.w = #init_table.label + 4
|
|
|
|
init_table.frame.h = 2
|
|
|
|
end
|
|
|
|
|
|
|
|
function Tab:onRenderBody(dc)
|
|
|
|
local pens = self.get_pens()
|
|
|
|
dc:seek(0, 0)
|
|
|
|
if dfhack.screen.inGraphicsMode() then
|
|
|
|
dc:char(nil, pens.lt):char(nil, pens.lt2)
|
|
|
|
for i=1,#self.label do
|
|
|
|
dc:char(self.label:sub(i,i), pens.t)
|
|
|
|
end
|
|
|
|
dc:char(nil, pens.rt2):char(nil, pens.rt)
|
|
|
|
dc:seek(0, 1)
|
|
|
|
dc:char(nil, pens.lb):char(nil, pens.lb2)
|
|
|
|
for i=1,#self.label do
|
|
|
|
dc:char(self.label:sub(i,i), pens.b)
|
|
|
|
end
|
|
|
|
dc:char(nil, pens.rb2):char(nil, pens.rb)
|
|
|
|
else
|
|
|
|
local tp = pens.text_mode_tab_pen
|
|
|
|
dc:char(' ', tp):char('/', tp)
|
|
|
|
for i=1,#self.label do
|
|
|
|
dc:char('-', tp)
|
|
|
|
end
|
|
|
|
dc:char('\\', tp):char(' ', tp)
|
|
|
|
dc:seek(0, 1)
|
|
|
|
dc:char('/', tp):char('-', tp)
|
|
|
|
dc:string(self.label, pens.text_mode_label_pen)
|
|
|
|
dc:char('-', tp):char('\\', tp)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function Tab:onInput(keys)
|
|
|
|
if Tab.super.onInput(self, keys) then return true end
|
|
|
|
if keys._MOUSE_L_DOWN and self:getMousePos() then
|
|
|
|
self.on_select(self.id)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-------------
|
|
|
|
-- Tab Bar --
|
|
|
|
-------------
|
|
|
|
|
|
|
|
TabBar = defclass(TabBar, ResizingPanel)
|
|
|
|
TabBar.ATTRS{
|
|
|
|
labels=DEFAULT_NIL,
|
|
|
|
on_select=DEFAULT_NIL,
|
|
|
|
get_cur_page=DEFAULT_NIL,
|
|
|
|
active_tab_pens=DEFAULT_ACTIVE_TAB_PENS,
|
|
|
|
inactive_tab_pens=DEFAULT_INACTIVE_TAB_PENS,
|
|
|
|
get_pens=DEFAULT_NIL,
|
2023-05-16 18:26:16 -06:00
|
|
|
key='CUSTOM_CTRL_T',
|
|
|
|
key_back='CUSTOM_CTRL_Y',
|
2023-02-11 06:44:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
function TabBar:init()
|
|
|
|
for idx,label in ipairs(self.labels) do
|
|
|
|
self:addviews{
|
|
|
|
Tab{
|
|
|
|
frame={t=0, l=0},
|
|
|
|
id=idx,
|
|
|
|
label=label,
|
|
|
|
on_select=self.on_select,
|
|
|
|
get_pens=self.get_pens and function()
|
|
|
|
return self.get_pens(idx, self)
|
|
|
|
end or function()
|
|
|
|
if self.get_cur_page() == idx then
|
|
|
|
return self.active_tab_pens
|
|
|
|
end
|
|
|
|
|
|
|
|
return self.inactive_tab_pens
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function TabBar:postComputeFrame(body)
|
|
|
|
local t, l, width = 0, 0, body.width
|
|
|
|
for _,tab in ipairs(self.subviews) do
|
|
|
|
if l > 0 and l + tab.frame.w > width then
|
|
|
|
t = t + 2
|
|
|
|
l = 0
|
|
|
|
end
|
|
|
|
tab.frame.t = t
|
|
|
|
tab.frame.l = l
|
|
|
|
l = l + tab.frame.w
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function TabBar:onInput(keys)
|
|
|
|
if TabBar.super.onInput(self, keys) then return true end
|
2023-02-16 00:56:52 -07:00
|
|
|
if self.key and keys[self.key] then
|
2023-02-11 06:44:34 -07:00
|
|
|
local zero_idx = self.get_cur_page() - 1
|
|
|
|
local next_zero_idx = (zero_idx + 1) % #self.labels
|
|
|
|
self.on_select(next_zero_idx + 1)
|
|
|
|
return true
|
|
|
|
end
|
2023-02-16 00:56:52 -07:00
|
|
|
if self.key_back and keys[self.key_back] then
|
|
|
|
local zero_idx = self.get_cur_page() - 1
|
|
|
|
local prev_zero_idx = (zero_idx - 1) % #self.labels
|
|
|
|
self.on_select(prev_zero_idx + 1)
|
|
|
|
return true
|
|
|
|
end
|
2023-02-11 06:44:34 -07:00
|
|
|
end
|
|
|
|
|
2023-04-19 03:28:57 -06:00
|
|
|
--------------------------------
|
2023-04-20 05:24:47 -06:00
|
|
|
-- RangeSlider
|
2023-04-19 03:28:57 -06:00
|
|
|
--
|
|
|
|
|
2023-04-20 05:24:47 -06:00
|
|
|
RangeSlider = defclass(RangeSlider, Widget)
|
|
|
|
RangeSlider.ATTRS{
|
2023-04-19 03:28:57 -06:00
|
|
|
num_stops=DEFAULT_NIL,
|
|
|
|
get_left_idx_fn=DEFAULT_NIL,
|
|
|
|
get_right_idx_fn=DEFAULT_NIL,
|
|
|
|
on_left_change=DEFAULT_NIL,
|
|
|
|
on_right_change=DEFAULT_NIL,
|
|
|
|
}
|
|
|
|
|
2023-04-20 05:24:47 -06:00
|
|
|
function RangeSlider:preinit(init_table)
|
2023-04-19 03:28:57 -06:00
|
|
|
init_table.frame = init_table.frame or {}
|
|
|
|
init_table.frame.h = init_table.frame.h or 1
|
|
|
|
end
|
|
|
|
|
2023-04-20 05:24:47 -06:00
|
|
|
function RangeSlider:init()
|
|
|
|
if self.num_stops < 2 then error('too few RangeSlider stops') end
|
2023-04-19 03:28:57 -06:00
|
|
|
self.is_dragging_target = nil -- 'left', 'right', or 'both'
|
|
|
|
self.is_dragging_idx = nil -- offset from leftmost dragged tile
|
|
|
|
end
|
|
|
|
|
2023-04-20 05:24:47 -06:00
|
|
|
local function rangeslider_get_width_per_idx(self)
|
2023-04-19 03:28:57 -06:00
|
|
|
return math.max(5, (self.frame_body.width-7) // (self.num_stops-1))
|
|
|
|
end
|
|
|
|
|
2023-04-20 05:24:47 -06:00
|
|
|
function RangeSlider:onInput(keys)
|
2023-04-19 03:28:57 -06:00
|
|
|
if not keys._MOUSE_L_DOWN then return false end
|
|
|
|
local x = self:getMousePos()
|
|
|
|
if not x then return false end
|
|
|
|
local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn()
|
2023-04-20 05:24:47 -06:00
|
|
|
local width_per_idx = rangeslider_get_width_per_idx(self)
|
2023-04-19 03:28:57 -06:00
|
|
|
local left_pos = width_per_idx*(left_idx-1)
|
|
|
|
local right_pos = width_per_idx*(right_idx-1) + 4
|
|
|
|
if x < left_pos then
|
|
|
|
self.on_left_change(self.get_left_idx_fn() - 1)
|
|
|
|
elseif x < left_pos+3 then
|
|
|
|
self.is_dragging_target = 'left'
|
|
|
|
self.is_dragging_idx = x - left_pos
|
|
|
|
elseif x < right_pos then
|
|
|
|
self.is_dragging_target = 'both'
|
|
|
|
self.is_dragging_idx = x - left_pos
|
|
|
|
elseif x < right_pos+3 then
|
|
|
|
self.is_dragging_target = 'right'
|
|
|
|
self.is_dragging_idx = x - right_pos
|
|
|
|
else
|
|
|
|
self.on_right_change(self.get_right_idx_fn() + 1)
|
|
|
|
end
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
2023-04-20 05:24:47 -06:00
|
|
|
local function rangeslider_do_drag(self, width_per_idx)
|
2023-04-19 03:28:57 -06:00
|
|
|
local x = self.frame_body:localXY(dfhack.screen.getMousePos())
|
|
|
|
local cur_pos = x - self.is_dragging_idx
|
|
|
|
cur_pos = math.max(0, cur_pos)
|
|
|
|
cur_pos = math.min(width_per_idx*(self.num_stops-1)+7, cur_pos)
|
|
|
|
local offset = self.is_dragging_target == 'right' and -2 or 1
|
|
|
|
local new_idx = math.max(0, cur_pos+offset)//width_per_idx + 1
|
|
|
|
local new_left_idx, new_right_idx
|
|
|
|
if self.is_dragging_target == 'right' then
|
|
|
|
new_right_idx = new_idx
|
|
|
|
else
|
|
|
|
new_left_idx = new_idx
|
|
|
|
if self.is_dragging_target == 'both' then
|
|
|
|
new_right_idx = new_left_idx + self.get_right_idx_fn() - self.get_left_idx_fn()
|
|
|
|
if new_right_idx > self.num_stops then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if new_left_idx and new_left_idx ~= self.get_left_idx_fn() then
|
2023-06-29 19:13:16 -06:00
|
|
|
if not new_right_idx and new_left_idx > self.get_right_idx_fn() then
|
|
|
|
self.on_right_change(new_left_idx)
|
|
|
|
end
|
2023-04-19 03:28:57 -06:00
|
|
|
self.on_left_change(new_left_idx)
|
|
|
|
end
|
|
|
|
if new_right_idx and new_right_idx ~= self.get_right_idx_fn() then
|
2023-06-29 19:13:16 -06:00
|
|
|
if new_right_idx < self.get_left_idx_fn() then
|
|
|
|
self.on_left_change(new_right_idx)
|
|
|
|
end
|
2023-04-19 03:28:57 -06:00
|
|
|
self.on_right_change(new_right_idx)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local SLIDER_LEFT_END = to_pen{ch=198, fg=COLOR_GREY, bg=COLOR_BLACK}
|
|
|
|
local SLIDER_TRACK = to_pen{ch=205, fg=COLOR_GREY, bg=COLOR_BLACK}
|
|
|
|
local SLIDER_TRACK_SELECTED = to_pen{ch=205, fg=COLOR_LIGHTGREEN, bg=COLOR_BLACK}
|
|
|
|
local SLIDER_TRACK_STOP = to_pen{ch=216, fg=COLOR_GREY, bg=COLOR_BLACK}
|
|
|
|
local SLIDER_TRACK_STOP_SELECTED = to_pen{ch=216, fg=COLOR_LIGHTGREEN, bg=COLOR_BLACK}
|
|
|
|
local SLIDER_RIGHT_END = to_pen{ch=181, fg=COLOR_GREY, bg=COLOR_BLACK}
|
|
|
|
local SLIDER_TAB_LEFT = to_pen{ch=60, fg=COLOR_BLACK, bg=COLOR_YELLOW}
|
|
|
|
local SLIDER_TAB_CENTER = to_pen{ch=9, fg=COLOR_BLACK, bg=COLOR_YELLOW}
|
|
|
|
local SLIDER_TAB_RIGHT = to_pen{ch=62, fg=COLOR_BLACK, bg=COLOR_YELLOW}
|
|
|
|
|
2023-04-20 05:24:47 -06:00
|
|
|
function RangeSlider:onRenderBody(dc, rect)
|
2023-04-19 03:28:57 -06:00
|
|
|
local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn()
|
2023-04-20 05:24:47 -06:00
|
|
|
local width_per_idx = rangeslider_get_width_per_idx(self)
|
2023-04-19 03:28:57 -06:00
|
|
|
-- draw track
|
|
|
|
dc:seek(1,0)
|
|
|
|
dc:char(nil, SLIDER_LEFT_END)
|
|
|
|
dc:char(nil, SLIDER_TRACK)
|
|
|
|
for stop_idx=1,self.num_stops-1 do
|
|
|
|
local track_stop_pen = SLIDER_TRACK_STOP_SELECTED
|
|
|
|
local track_pen = SLIDER_TRACK_SELECTED
|
|
|
|
if left_idx > stop_idx or right_idx < stop_idx then
|
|
|
|
track_stop_pen = SLIDER_TRACK_STOP
|
|
|
|
track_pen = SLIDER_TRACK
|
|
|
|
elseif right_idx == stop_idx then
|
|
|
|
track_pen = SLIDER_TRACK
|
|
|
|
end
|
|
|
|
dc:char(nil, track_stop_pen)
|
|
|
|
for i=2,width_per_idx do
|
|
|
|
dc:char(nil, track_pen)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if right_idx >= self.num_stops then
|
|
|
|
dc:char(nil, SLIDER_TRACK_STOP_SELECTED)
|
|
|
|
else
|
|
|
|
dc:char(nil, SLIDER_TRACK_STOP)
|
|
|
|
end
|
|
|
|
dc:char(nil, SLIDER_TRACK)
|
|
|
|
dc:char(nil, SLIDER_RIGHT_END)
|
|
|
|
-- draw tabs
|
|
|
|
dc:seek(width_per_idx*(left_idx-1))
|
|
|
|
dc:char(nil, SLIDER_TAB_LEFT)
|
|
|
|
dc:char(nil, SLIDER_TAB_CENTER)
|
|
|
|
dc:char(nil, SLIDER_TAB_RIGHT)
|
|
|
|
dc:seek(width_per_idx*(right_idx-1)+4)
|
|
|
|
dc:char(nil, SLIDER_TAB_LEFT)
|
|
|
|
dc:char(nil, SLIDER_TAB_CENTER)
|
|
|
|
dc:char(nil, SLIDER_TAB_RIGHT)
|
|
|
|
-- manage dragging
|
|
|
|
if self.is_dragging_target then
|
2023-04-20 05:24:47 -06:00
|
|
|
rangeslider_do_drag(self, width_per_idx)
|
2023-04-19 03:28:57 -06:00
|
|
|
end
|
|
|
|
if df.global.enabler.mouse_lbut == 0 then
|
|
|
|
self.is_dragging_target = nil
|
|
|
|
self.is_dragging_idx = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-10-15 10:03:18 -06:00
|
|
|
return _ENV
|