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``
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
calling ``Panel:setCursorMoveEnabled(false)`` (while dragging with the
calling ``Panel:setKeyboaredDragEnabled(false)`` (while dragging with the
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_gap = int`` (default: ``0``)
@ -4207,8 +4226,8 @@ Has functions:
* ``panel:setKeyboardDragEnabled(bool)``
If called with something truthy and the panel is not already in keyboard drag
mode, then any current drag operations are halted where they are (not
If called with ``true`` and the panel is not already in keyboard drag 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
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
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
-------------------
Subclass of Panel; automatically adjusts its own frame height according to
the size, position, and visibility of its subviews. Pairs nicely with a
parent Panel that has ``autoarrange_subviews`` enabled.
Subclass of Panel; automatically adjusts its own frame height and width to the
minimum required to show its subviews. Pairs nicely with a parent Panel that has
``autoarrange_subviews`` enabled.
Pages class
-----------

@ -76,24 +76,35 @@ Panel.ATTRS {
drag_bound = 'frame', -- or 'body'
on_drag_begin = 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_gap = 0, -- how many blank lines to insert between widgets
}
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_rect = nil -- copy of frame_rect when dragging started
self.drag_offset = nil -- relative pos of held panel tile
self.resize_edge = nil -- which dimension is being resized?
self:addviews(args.subviews)
end
local function Panel_update_frame(self, frame, clear_state)
if clear_state then
self.keyboard_drag = nil
self.kbd_get_pos = nil
self.saved_frame = nil
self.saved_frame_rect = nil
self.drag_offset = nil
self.resize_edge = nil
end
if not frame then return end
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()
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 frame = copyall(self.frame)
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)
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
local function Panel_begin_drag(self, drag_offset)
local function Panel_begin_drag(self, drag_offset, resize_edge)
Panel_update_frame(self, nil, true)
self.drag_offset = drag_offset or {x=0, y=0}
self.resize_edge = resize_edge
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)
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
local function Panel_end_drag(self, frame, success)
success = not not success
if self.prev_focus_owner then
self.prev_focus_owner:setFocus(true)
else
self:setFocus(false)
end
Panel_update_frame(self, frame, true)
if self.on_drag_end then self.on_drag_end(success) end
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
end
end
function Panel:onInput(keys)
if self.keyboard_drag then
if self.kbd_get_pos then
if keys.SELECT or keys.LEAVESCREEN then
Panel_end_drag(self, keys.LEAVESCREEN and self.saved_frame or nil,
not not keys.SELECT)
@ -173,8 +246,10 @@ function Panel:onInput(keys)
for code in pairs(keys) do
local dx, dy = guidm.get_movement_delta(code, 1, 10)
if dx then
local kbd_pos = {x=self.frame_rect.x1+dx,
y=self.frame_rect.y1+dy}
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
Panel_update_frame(self, Panel_make_frame(self, kbd_pos))
return true
end
@ -197,31 +272,74 @@ function Panel:onInput(keys)
local x,y = self:getMousePos(gui.ViewRect{rect=rect})
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
if self.draggable then
if not self.resize_edge and self.draggable then
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
if is_dragging then
Panel_begin_drag(self, {x=x, y=y})
if self.resize_edge or is_dragging then
Panel_begin_drag(self, {x=x, y=y}, self.resize_edge)
return true
end
end
function Panel:setKeyboardDragEnabled(enabled)
if (enabled and self.keyboard_drag)
or (not enabled and not self.keyboard_drag) then
if (enabled and self.kbd_get_pos)
or (not enabled and not self.kbd_get_pos) then
return
end
if enabled then
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
Panel_begin_drag(self)
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
Panel_end_drag(self)
end
self.keyboard_drag = enabled
end
function Panel:onRenderBody(dc)
@ -264,7 +382,7 @@ function Panel:onRenderFrame(dc, rect)
if not self.frame_style then return end
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)
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
Panel_end_drag(self, nil, true)
end