From 8036ca0d56da2784ba3682258a353ecc3e5df2b9 Mon Sep 17 00:00:00 2001 From: poire-z Date: Thu, 23 Mar 2023 18:37:42 +0100 Subject: [PATCH] MovableContainer: add support for anchoring initial position When passing as 'anchor' a Geom object (ie. a widget dimen or a ges.pos), or a function returning such an object, a MovableContainer will be initially positionned near this point/widget, instead of being centered on the screen. Allow that for ButtonDialog and ButtonDialogTitle, so we can make them behave as context menus (ie. for a titlebar top left/right icons). --- frontend/ui/widget/buttondialog.lua | 1 + frontend/ui/widget/buttondialogtitle.lua | 1 + .../ui/widget/container/movablecontainer.lua | 66 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/frontend/ui/widget/buttondialog.lua b/frontend/ui/widget/buttondialog.lua index 524e3cb78..d8925136f 100644 --- a/frontend/ui/widget/buttondialog.lua +++ b/frontend/ui/widget/buttondialog.lua @@ -88,6 +88,7 @@ function ButtonDialog:init() end self.movable = MovableContainer:new{ alpha = self.alpha, + anchor = self.anchor, FrameContainer:new{ ButtonTable:new{ buttons = self.buttons, diff --git a/frontend/ui/widget/buttondialogtitle.lua b/frontend/ui/widget/buttondialogtitle.lua index 6746ae11f..3ddbe2800 100644 --- a/frontend/ui/widget/buttondialogtitle.lua +++ b/frontend/ui/widget/buttondialogtitle.lua @@ -88,6 +88,7 @@ function ButtonDialogTitle:init() dimen = Screen:getSize(), ignore_if_over = "height", MovableContainer:new{ + anchor = self.anchor, FrameContainer:new{ VerticalGroup:new{ align = "center", diff --git a/frontend/ui/widget/container/movablecontainer.lua b/frontend/ui/widget/container/movablecontainer.lua index c90f47f55..f570217f7 100644 --- a/frontend/ui/widget/container/movablecontainer.lua +++ b/frontend/ui/widget/container/movablecontainer.lua @@ -13,6 +13,7 @@ position, Hold will toggle between full opacity and 0.7 transparency. This container's content is expected to not change its width and height. ]] +local BD = require("ui/bidi") local Blitbuffer = require("ffi/blitbuffer") local Device = require("device") local Geom = require("ui/geometry") @@ -35,6 +36,12 @@ local MovableContainer = InputContainer:extend{ -- Events to ignore (ie: ignore_events={"hold", "hold_release"}) ignore_events = nil, + -- Initial position can be set related to an existing widget + -- 'anchor' should be a Geom object (a widget's 'dimen', or a point), and + -- can be a function returning that object + anchor = nil, + _anchor_ensured = nil, + -- Current move offset (use getMovedOffset()/setMovedOffset() to access them) _moved_offset_x = 0, _moved_offset_y = 0, @@ -100,6 +107,59 @@ function MovableContainer:setMovedOffset(offset_point) end end +function MovableContainer:ensureAnchor(x, y) + local anchor_dimen = self.anchor + local prefers_pop_down + if type(self.anchor) == "function" then + anchor_dimen, prefers_pop_down = self.anchor() + end + if not anchor_dimen then + return + end + -- We try to find the best way to draw our content, depending on + -- the size of the content and the space available on the screen. + local content_w, content_h = self.dimen.w, self.dimen.h + local screen_w, screen_h = Screen:getWidth(), Screen:getHeight() + local left, top + if BD.mirroredUILayout() then + left = anchor_dimen.x + anchor_dimen.w - content_w + else + left = anchor_dimen.x + end + if left < 0 then + left = 0 + elseif left + content_w > screen_w then + left = screen_w - content_w + end + -- We prefer displaying above the anchor if there is room (so it looks like popping up) + -- except if anchor() returned prefers_pop_down + local h_remaining_if_above = anchor_dimen.y - content_h + local h_remaining_if_below = screen_h - (anchor_dimen.y + anchor_dimen.h + content_h) + if h_remaining_if_above >= 0 and not prefers_pop_down then + -- Enough room above the anchor + top = anchor_dimen.y - content_h + elseif h_remaining_if_below >= 0 then + -- Enough room below the anchor + top = anchor_dimen.y + anchor_dimen.h + elseif h_remaining_if_above >= 0 then + -- Enough room above the anchor + top = anchor_dimen.y - content_h + else -- both negative + if h_remaining_if_above >= h_remaining_if_below then + top = 0 + else + top = screen_h - content_h + end + end + -- Ensure we show the top if we would overflow + if top < 0 then + top = 0 + end + -- Make the initial offsets so that we display at left/top + self._moved_offset_x = left - x + self._moved_offset_y = top - y +end + function MovableContainer:paintTo(bb, x, y) if self[1] == nil then return @@ -112,6 +172,12 @@ function MovableContainer:paintTo(bb, x, y) self._orig_x = x self._orig_y = y + -- If there is a widget passed as anchor, we need to set our initial position + -- related to it. After that, we allow it to be moved like any other movable. + if self.anchor and not self._anchor_ensured then + self:ensureAnchor(x, y) + self._anchor_ensured = true + end -- We just need to shift painting by our _moved_offset_x/y self.dimen.x = x + self._moved_offset_x self.dimen.y = y + self._moved_offset_y