implement panel resizing

develop
Myk Taylor 2022-12-13 18:15:11 -08:00
parent 7505b6bd8e
commit c030704705
No known key found for this signature in database
GPG Key ID: 8A39CA0FA0C16E78
2 changed files with 172 additions and 23 deletions

@ -4182,9 +4182,28 @@ Has attributes:
``true`` if the drag was "successful" (i.e. not canceled) and ``false`` ``true`` if the drag was "successful" (i.e. not canceled) and ``false``
otherwise. Dragging can be canceled by right clicking while dragging with the otherwise. Dragging can be canceled by right clicking while dragging with the
mouse, hitting :kbd:`Esc` (while dragging with the mouse or keyboard), or by mouse, hitting :kbd:`Esc` (while dragging with the mouse or keyboard), or by
calling ``Panel:setCursorMoveEnabled(false)`` (while dragging with the calling ``Panel:setKeyboaredDragEnabled(false)`` (while dragging with the
keyboard). keyboard).
* ``resizable = bool`` (default: ``false``)
* ``resize_anchors = {}`` (default: ``{t=false, l=true, r=true, b=true}``
* ``resize_min = {}`` (default: w and h from the ``frame``, or ``{w=5, h=5}``)
* ``on_resize_begin = function()`` (default: ``nil``)
* ``on_resize_end = function(bool)`` (default: ``nil``)
If ``resizable`` is set to ``true``, then the player can click the mouse on
any edge specified in ``resize_anchors`` and drag the border to resize the
window. If two adjacent edges are enabled as anchors, then the tile where they
meet can be used to resize both edges at the same time. The minimum dimensions
specified in ``resize_min`` (or inherited from ``frame`` are respected when
resizing. The panel is also prevented from resizing beyond the boundaries of
its parent. When the player clicks on a valid anchor, ``on_resize_begin()`` is
called. The boolean passed to the ``on_resize_end`` callback will be ``true``
if the drag was "successful" (i.e. not canceled) and ``false`` otherwise.
Dragging can be canceled by right clicking while resizing with the mouse,
hitting :kbd:`Esc` (while resizing with the mouse or keyboard), or by calling
``Panel:setKeyboardResizeEnabled(false)`` (while resizing with the keyboard).
* ``autoarrange_subviews = bool`` (default: ``false``) * ``autoarrange_subviews = bool`` (default: ``false``)
* ``autoarrange_gap = int`` (default: ``0``) * ``autoarrange_gap = int`` (default: ``0``)
@ -4207,8 +4226,8 @@ Has functions:
* ``panel:setKeyboardDragEnabled(bool)`` * ``panel:setKeyboardDragEnabled(bool)``
If called with something truthy and the panel is not already in keyboard drag If called with ``true`` and the panel is not already in keyboard drag mode,
mode, then any current drag operations are halted where they are (not then any current drag or resize operations are halted where they are (not
canceled), the panel siezes input focus (see `View class`_ above for canceled), the panel siezes input focus (see `View class`_ above for
information on the DFHack focus subsystem), and further keyboard cursor keys information on the DFHack focus subsystem), and further keyboard cursor keys
move the window as if it were being dragged. Shift-cursor keys move by larger move the window as if it were being dragged. Shift-cursor keys move by larger
@ -4216,12 +4235,24 @@ Has functions:
cancel. If dragging is canceled, then the window is moved back to its original cancel. If dragging is canceled, then the window is moved back to its original
position. position.
* ``panel:setKeyboardResizeEnabled(bool)``
If called with ``true`` and the panel is not already in keyboard resize mode,
then any current drag or resize operations are halted where they are (not
canceled), the panel siezes input focus (see `View class`_ above for
information on the DFHack focus subsystem), and further keyboard cursor keys
resize the window as if it were being dragged from the lower right corner. If
neither the bottom or right edge is a valid anchor, an appropriate corner will
be chosen. Shift-cursor keys move by larger amounts. Hit :kbd:`Enter` to
commit the new window size or :kbd:`Esc` to cancel. If resizing is canceled,
then the window size from before the resize operation is restored.
ResizingPanel class ResizingPanel class
------------------- -------------------
Subclass of Panel; automatically adjusts its own frame height according to Subclass of Panel; automatically adjusts its own frame height and width to the
the size, position, and visibility of its subviews. Pairs nicely with a minimum required to show its subviews. Pairs nicely with a parent Panel that has
parent Panel that has ``autoarrange_subviews`` enabled. ``autoarrange_subviews`` enabled.
Pages class Pages class
----------- -----------

@ -76,24 +76,35 @@ Panel.ATTRS {
drag_bound = 'frame', -- or 'body' drag_bound = 'frame', -- or 'body'
on_drag_begin = DEFAULT_NIL, on_drag_begin = DEFAULT_NIL,
on_drag_end = DEFAULT_NIL, on_drag_end = DEFAULT_NIL,
resizable = false,
resize_anchors = {t=false, l=true, r=true, b=true},
resize_min = DEFAULT_NIL,
on_resize_begin = DEFAULT_NIL,
on_resize_end = DEFAULT_NIL,
autoarrange_subviews = false, -- whether to automatically lay out subviews autoarrange_subviews = false, -- whether to automatically lay out subviews
autoarrange_gap = 0, -- how many blank lines to insert between widgets autoarrange_gap = 0, -- how many blank lines to insert between widgets
} }
function Panel:init(args) function Panel:init(args)
self.keyboard_drag = nil -- true when we are in keyboard dragging mode 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
self.saved_frame = nil -- copy of frame when dragging started self.saved_frame = nil -- copy of frame when dragging started
self.saved_frame_rect = nil -- copy of frame_rect 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 self.drag_offset = nil -- relative pos of held panel tile
self.resize_edge = nil -- which dimension is being resized?
self:addviews(args.subviews) self:addviews(args.subviews)
end end
local function Panel_update_frame(self, frame, clear_state) local function Panel_update_frame(self, frame, clear_state)
if clear_state then if clear_state then
self.keyboard_drag = nil self.kbd_get_pos = nil
self.saved_frame = nil self.saved_frame = nil
self.saved_frame_rect = nil self.saved_frame_rect = nil
self.drag_offset = nil self.drag_offset = nil
self.resize_edge = nil
end end
if not frame then return end if not frame then return end
if self.frame.l == frame.l and self.frame.r == frame.r if self.frame.l == frame.l and self.frame.r == frame.r
@ -105,6 +116,57 @@ local function Panel_update_frame(self, frame, clear_state)
self:updateLayout() self:updateLayout()
end end
-- 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
local function Panel_drag_frame(self, mouse_pos) local function Panel_drag_frame(self, mouse_pos)
local frame = copyall(self.frame) local frame = copyall(self.frame)
local parent_rect, frame_rect = self.frame_parent_rect, self.frame_rect local parent_rect, frame_rect = self.frame_parent_rect, self.frame_rect
@ -140,31 +202,42 @@ end
local function Panel_make_frame(self, mouse_pos) local function Panel_make_frame(self, mouse_pos)
mouse_pos = mouse_pos or xy2pos(dfhack.screen.getMousePos()) mouse_pos = mouse_pos or xy2pos(dfhack.screen.getMousePos())
return Panel_drag_frame(self, mouse_pos) return self.resize_edge and Panel_resize_frame(self, mouse_pos)
or Panel_drag_frame(self, mouse_pos)
end end
local function Panel_begin_drag(self, drag_offset) local function Panel_begin_drag(self, drag_offset, resize_edge)
Panel_update_frame(self, nil, true) Panel_update_frame(self, nil, true)
self.drag_offset = drag_offset or {x=0, y=0} self.drag_offset = drag_offset or {x=0, y=0}
self.resize_edge = resize_edge
self.saved_frame = copyall(self.frame) self.saved_frame = copyall(self.frame)
self.saved_frame_rect = copyall(self.frame_rect) self.saved_frame_rect = copyall(self.frame_rect)
self.prev_focus_owner = self.focus_group.cur self.prev_focus_owner = self.focus_group.cur
self:setFocus(true) self:setFocus(true)
if self.on_drag_begin then self.on_drag_begin() end if self.resize_edge then
if self.on_resize_begin then self.on_resize_begin(success) end
else
if self.on_drag_begin then self.on_drag_begin(success) end
end
end end
local function Panel_end_drag(self, frame, success) local function Panel_end_drag(self, frame, success)
success = not not success
if self.prev_focus_owner then if self.prev_focus_owner then
self.prev_focus_owner:setFocus(true) self.prev_focus_owner:setFocus(true)
else else
self:setFocus(false) self:setFocus(false)
end end
Panel_update_frame(self, frame, true) Panel_update_frame(self, frame, true)
if self.resize_edge then
if self.on_resize_end then self.on_resize_end(success) end
else
if self.on_drag_end then self.on_drag_end(success) end if self.on_drag_end then self.on_drag_end(success) end
end
end end
function Panel:onInput(keys) function Panel:onInput(keys)
if self.keyboard_drag then if self.kbd_get_pos then
if keys.SELECT or keys.LEAVESCREEN then if keys.SELECT or keys.LEAVESCREEN then
Panel_end_drag(self, keys.LEAVESCREEN and self.saved_frame or nil, Panel_end_drag(self, keys.LEAVESCREEN and self.saved_frame or nil,
not not keys.SELECT) not not keys.SELECT)
@ -173,8 +246,10 @@ function Panel:onInput(keys)
for code in pairs(keys) do for code in pairs(keys) do
local dx, dy = guidm.get_movement_delta(code, 1, 10) local dx, dy = guidm.get_movement_delta(code, 1, 10)
if dx then if dx then
local kbd_pos = {x=self.frame_rect.x1+dx, local frame_rect = self.frame_rect
y=self.frame_rect.y1+dy} local kbd_pos = self.kbd_get_pos()
kbd_pos.x = kbd_pos.x + dx
kbd_pos.y = kbd_pos.y + dy
Panel_update_frame(self, Panel_make_frame(self, kbd_pos)) Panel_update_frame(self, Panel_make_frame(self, kbd_pos))
return true return true
end end
@ -197,31 +272,74 @@ function Panel:onInput(keys)
local x,y = self:getMousePos(gui.ViewRect{rect=rect}) local x,y = self:getMousePos(gui.ViewRect{rect=rect})
if not x then return end if not x then return end
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
self.resize_edge = 'rb'
elseif self.resize_anchors.l and self.resize_anchors.b
and x == 0 and y == rect.y2 - rect.y1 then
self.resize_edge = 'lb'
elseif self.resize_anchors.r and self.resize_anchors.t
and x == rect.x2 - rect.x1 and y == 0 then
self.resize_edge = 'rt'
elseif self.resize_anchors.r and self.resize_anchors.t
and x == 0 and y == 0 then
self.resize_edge = 'lt'
elseif self.resize_anchors.r and x == rect.x2 - rect.x1 then
self.resize_edge = 'r'
elseif self.resize_anchors.l and x == 0 then
self.resize_edge = 'l'
elseif self.resize_anchors.b and y == rect.y2 - rect.y1 then
self.resize_edge = 'b'
elseif self.resize_anchors.t and y == 0 then
self.resize_edge = 't'
end
end
local is_dragging = false local is_dragging = false
if self.draggable then if not self.resize_edge and self.draggable then
local on_body = self:getMousePos() local on_body = self:getMousePos()
is_dragging = (self.drag_anchors.title and self.frame_style and y == 0) 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.frame and not on_body) -- includes inset
or (self.drag_anchors.body and on_body) or (self.drag_anchors.body and on_body)
end end
if is_dragging then if self.resize_edge or is_dragging then
Panel_begin_drag(self, {x=x, y=y}) Panel_begin_drag(self, {x=x, y=y}, self.resize_edge)
return true return true
end end
end end
function Panel:setKeyboardDragEnabled(enabled) function Panel:setKeyboardDragEnabled(enabled)
if (enabled and self.keyboard_drag) if (enabled and self.kbd_get_pos)
or (not enabled and not self.keyboard_drag) then or (not enabled and not self.kbd_get_pos) then
return return
end end
if enabled then if enabled then
Panel_begin_drag(self) local kbd_get_pos = function() return {x=0, y=0} end
Panel_begin_drag(self, kbd_get_pos())
self.kbd_get_pos = kbd_get_pos
else
Panel_end_drag(self)
end
end
function Panel:setKeyboardResizeEnabled(enabled)
if (enabled and self.kbd_get_pos)
or (not enabled and not self.kbd_get_pos) then
return
end
if enabled then
local resize_edge = 'rb'
local kbd_get_pos = function()
return {x=self.frame_rect.x2, y=self.frame_rect.y2}
end
Panel_begin_drag(self, kbd_get_pos(), resize_edge)
self.kbd_get_pos = kbd_get_pos
else else
Panel_end_drag(self) Panel_end_drag(self)
end end
self.keyboard_drag = enabled
end end
function Panel:onRenderBody(dc) function Panel:onRenderBody(dc)
@ -264,7 +382,7 @@ function Panel:onRenderFrame(dc, rect)
if not self.frame_style then return end if not self.frame_style then return end
local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2 local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2
gui.paint_frame(x1, y1, x2, y2, self.frame_style, self.frame_title) gui.paint_frame(x1, y1, x2, y2, self.frame_style, self.frame_title)
if self.drag_offset and not self.keyboard_drag if self.drag_offset and not self.kbd_get_pos
and df.global.enabler.mouse_lbut == 0 then and df.global.enabler.mouse_lbut == 0 then
Panel_end_drag(self, nil, true) Panel_end_drag(self, nil, true)
end end