diff --git a/frontend/ui/uimanager.lua b/frontend/ui/uimanager.lua index ca4b3d6d9..da8e229a5 100644 --- a/frontend/ui/uimanager.lua +++ b/frontend/ui/uimanager.lua @@ -1541,6 +1541,16 @@ function UIManager:widgetRepaint(widget, x, y) if not widget then return end logger.dbg("Explicit widgetRepaint:", widget.name or widget.id or tostring(widget), "@ (", x, ",", y, ")") + if widget.show_parent and widget.show_parent.cropping_widget then + -- The main widget parent of this subwidget has a cropping container: see if + -- this widget is a child of this cropping container + local cropping_widget = widget.show_parent.cropping_widget + if util.arrayReferences(cropping_widget, widget) then + -- Delegate the painting of this subwidget to its cropping widget container + cropping_widget:paintTo(Screen.bb, cropping_widget.dimen.x, cropping_widget.dimen.y) + return + end + end widget:paintTo(Screen.bb, x, y) end @@ -1558,6 +1568,19 @@ function UIManager:widgetInvert(widget, x, y, w, h) if not widget then return end logger.dbg("Explicit widgetInvert:", widget.name or widget.id or tostring(widget), "@ (", x, ",", y, ")") + if widget.show_parent and widget.show_parent.cropping_widget then + -- The main widget parent of this subwidget has a cropping container: see if + -- this widget is a child of this cropping container + local cropping_widget = widget.show_parent.cropping_widget + if util.arrayReferences(cropping_widget, widget) then + -- Invert only what intersects with the cropping container + local widget_region = Geom:new{x=x, y=y, w=w or widget.dimen.w, h=h or widget.dimen.h} + local crop_region = cropping_widget:getCropRegion() + local invert_region = crop_region:intersect(widget_region) + Screen.bb:invertRect(invert_region.x, invert_region.y, invert_region.w, invert_region.h) + return + end + end Screen.bb:invertRect(x, y, w or widget.dimen.w, h or widget.dimen.h) end diff --git a/frontend/ui/widget/button.lua b/frontend/ui/widget/button.lua index 2a875a203..20711235a 100644 --- a/frontend/ui/widget/button.lua +++ b/frontend/ui/widget/button.lua @@ -144,6 +144,7 @@ function Button:init() -- set FrameContainer content self.frame = FrameContainer:new{ margin = self.margin, + show_parent = self.show_parent, bordersize = self.bordersize, background = self.background, radius = self.radius, diff --git a/frontend/ui/widget/container/scrollablecontainer.lua b/frontend/ui/widget/container/scrollablecontainer.lua new file mode 100644 index 000000000..d900cedf6 --- /dev/null +++ b/frontend/ui/widget/container/scrollablecontainer.lua @@ -0,0 +1,449 @@ +--[[-- +ScrollableContainer allows scrolling its content (1 widget) within its own dimensions + +This scrollable container needs to be known as widget.cropping_widget in +the widget using it that is passed to UIManager:show() for UIManager to +ensure proper interception of inner widget self-repainting/invert (mostly +used when flashing for UI feedback that we want to limit to the cropped +area). +If we notice some inner element flashing leaking outside the scrollable +area, it's probably some 'show_parent' forwarding missing from the main +widget to some of the inner widgets: chase the missing ones and add them. +--]] + +local BD = require("ui/bidi") +local Blitbuffer = require("ffi/blitbuffer") +local Device = require("device") +local Geom = require("ui/geometry") +local GestureRange = require("ui/gesturerange") +local HorizontalScrollBar = require("ui/widget/horizontalscrollbar") +local InputContainer = require("ui/widget/container/inputcontainer") +local Math = require("optmath") +local UIManager = require("ui/uimanager") +local VerticalScrollBar = require("ui/widget/verticalscrollbar") +local Screen = Device.screen +local logger = require("logger") + +local ScrollableContainer = InputContainer:new{ + -- Events to ignore (ie: ignore_events={"hold", "hold_release"}) + ignore_events = nil, + scroll_bar_width = Screen:scaleBySize(6), + + -- Set to true if child widget is larger, false otherwise + _is_scrollable = nil, + -- Current scroll offset (use getScrolledOffset()/setScrolledOffset() to access them) + _scroll_offset_x = 0, + _scroll_offset_y = 0, + _max_scroll_offset_x = 0, + _max_scroll_offset_y = 0, + -- Internal state between events + _touch_pre_pan_was_inside = false, + _scrolling = false, + _scroll_relative_x = nil, + _scroll_relative_y = nil, + -- Scrollbar widgets, created as needed + _v_scroll_bar = nil, + _h_scroll_bar = nil, + -- Scratch buffer + _bb = nil, + _crop_w = nil, + _crop_h = nil, + _crop_dx = 0, + _mirroredUI = BD.mirroredUILayout(), +} + +function ScrollableContainer:getScrollbarWidth(scroll_bar_width) + -- Return the width taken by the (default) scroll bar and its paddings + if not scroll_bar_width then + scroll_bar_width = self.scroll_bar_width + end + return 3 * scroll_bar_width +end + +function ScrollableContainer:init() + if Device:isTouchDevice() then + local range = Geom:new{ + x = 0, y = 0, + w = Screen:getWidth(), + h = Screen:getHeight(), + } + -- Unflatten self.ignore_events to table keys for cleaner code below + local ignore = {} + if self.ignore_events then + for _, evname in pairs(self.ignore_events) do + ignore[evname] = true + end + end + -- The following gestures need to be supported, depending on the + -- ways a user can move/scroll things: + -- Hold happens if he holds at start + -- Pan happens if he doesn't hold at start, but holds at end + -- Swipe happens if he doesn't hold at any moment + -- (Touch is needed for accurate pan) + self.ges_events = {} + self.ges_events.ScrollableTouch = not ignore.touch and { GestureRange:new{ ges = "touch", range = range } } or nil + self.ges_events.ScrollableSwipe = not ignore.swipe and { GestureRange:new{ ges = "swipe", range = range } } or nil + self.ges_events.ScrollableHold = not ignore.hold and { GestureRange:new{ ges = "hold", range = range } } or nil + self.ges_events.ScrollableHoldPan = not ignore.hold_pan and { GestureRange:new{ ges = "hold_pan", range = range } } or nil + self.ges_events.ScrollableHoldRelease = not ignore.hold_release and { GestureRange:new{ ges = "hold_release", range = range } } or nil + self.ges_events.ScrollablePan = not ignore.pan and { GestureRange:new{ ges = "pan", range = range } } or nil + self.ges_events.ScrollablePanRelease = not ignore.pan_release and { GestureRange:new{ ges = "pan_release", range = range } } or nil + end +end + +function ScrollableContainer:initState() + local content_size = self[1]:getSize() + self._max_scroll_offset_x = math.max(0, content_size.w - self.dimen.w) + self._max_scroll_offset_y = math.max(0, content_size.h - self.dimen.h) + if self._max_scroll_offset_x == 0 and self._max_scroll_offset_y == 0 then + -- Inner widget fits entirely: no need for anything scrollable + self._is_scrollable = false + else + self._is_scrollable = true + self._crop_w = self.dimen.w + self._crop_h = self.dimen.h + if self._max_scroll_offset_y > 0 then + -- Adding a vertical scrollbar reduces the available width: recompute + self._max_scroll_offset_x = math.max(0, content_size.w - (self.dimen.w - 3*self.scroll_bar_width)) + end + if self._max_scroll_offset_x > 0 then + -- Adding a horizontal scrollbar reduces the available height: recompute + self._max_scroll_offset_y = math.max(0, content_size.h - (self.dimen.h - 3*self.scroll_bar_width)) + if self._max_scroll_offset_y > 0 then + -- And re-compute again if we have to now add a vertical scrollbar + self._max_scroll_offset_x = math.max(0, content_size.w - (self.dimen.w - 3*self.scroll_bar_width)) + end + end + -- Scrollbars won't be classic sub-widgets, we'll handle their painting ourselves + if self._max_scroll_offset_y > 0 then + self._v_scroll_bar = VerticalScrollBar:new{ + width = self.scroll_bar_width, + height = self.dimen.h, + scroll_callback = function(ratio) + self:scrollToRatio(nil, ratio) + end + } + self._crop_w = self.dimen.w - 3*self.scroll_bar_width + end + if self._max_scroll_offset_x > 0 then + self._h_scroll_bar_shift = 0 + if self._v_scroll_bar then + -- Reduce its width so to not overlap with the vertical scroll bar + self._h_scroll_bar_shift = 3*self.scroll_bar_width + end + self._h_scroll_bar = HorizontalScrollBar:new{ + height = self.scroll_bar_width, + width = self.dimen.w - self._h_scroll_bar_shift, + scroll_callback = function(ratio) + self:scrollToRatio(ratio, nil) + end + } + self._crop_h = self.dimen.h - 3*self.scroll_bar_width + end + if self._mirroredUI then + if self._v_scroll_bar then + self._crop_dx = self.dimen.w - self._crop_w + end + end + self:_updateScrollBars() + end +end + +function ScrollableContainer:getCropRegion() + return Geom:new{ + x = self.dimen.x + self._crop_dx, + y = self.dimen.y, + w = self._crop_w, + h = self._crop_h, + } +end + +function ScrollableContainer:_updateScrollBars() + if self._v_scroll_bar then + local dheight = self._crop_h / (self._max_scroll_offset_y + self._crop_h) + local low = self._scroll_offset_y / (self._max_scroll_offset_y + self._crop_h) + local high = low + dheight + self._v_scroll_bar:set(low, high) + end + if self._h_scroll_bar then + local dwidth = self._crop_w / (self._max_scroll_offset_x + self._crop_w) + local low = self._scroll_offset_x / (self._max_scroll_offset_x + self._crop_w) + local high = low + dwidth + self._h_scroll_bar:set(low, high) + end +end + +function ScrollableContainer:scrollToRatio(ratio_x, ratio_y) + if ratio_y then + local dy = ratio_y * (self._max_scroll_offset_y + self._crop_h) + self._scroll_offset_y = dy - Math.round(self._crop_h/2) + if self._scroll_offset_y < 0 then + self._scroll_offset_y = 0 + end + if self._scroll_offset_y > self._max_scroll_offset_y then + self._scroll_offset_y = self._max_scroll_offset_y + end + end + if ratio_x then + local dx = ratio_x * (self._max_scroll_offset_x + self._crop_w) + self._scroll_offset_x = dx - Math.round(self._crop_w/2) + if self._scroll_offset_x < 0 then + self._scroll_offset_x = 0 + end + if self._scroll_offset_x > self._max_scroll_offset_x then + self._scroll_offset_x = self._max_scroll_offset_x + end + end + self:_scrollBy(0, 0) -- get the additional work done +end + +function ScrollableContainer:_scrollBy(dx, dy) + if self._mirroredUI then + dx = -dx + end + self._scroll_offset_x = self._scroll_offset_x + Math.round(dx) + self._scroll_offset_y = self._scroll_offset_y + Math.round(dy) + if self._scroll_offset_x < 0 then + self._scroll_offset_x = 0 + end + if self._scroll_offset_y < 0 then + self._scroll_offset_y = 0 + end + if self._scroll_offset_x > self._max_scroll_offset_x then + self._scroll_offset_x = self._max_scroll_offset_x + end + if self._scroll_offset_y > self._max_scroll_offset_y then + self._scroll_offset_y = self._max_scroll_offset_y + end + self:_updateScrollBars() + UIManager:setDirty(self.show_parent, function() + return "ui", self.dimen + end) +end + +function ScrollableContainer:getScrolledOffset() + return Geom:new{ + x = self._scroll_offset_x, + y = self._scroll_offset_y, + } +end + +function ScrollableContainer:setScrolledOffset(offset_point) + if offset_point and offset_point.x and offset_point.y then + self._scroll_offset_x = offset_point.x + self._scroll_offset_y = offset_point.y + end +end + +function ScrollableContainer:onCloseWidget() + if self._bb then + self._bb:free() + self._bb = nil + end +end + +function ScrollableContainer:paintTo(bb, x, y) + if self[1] == nil then + return + end + self.dimen.x = x + self.dimen.y = y + + if self._is_scrollable == nil then -- not checked yet + self:initState() + end + + if not self._is_scrollable then + -- nothing to scroll: pass-through + self[1]:paintTo(bb, x, y) + return + end + + local screen_size = Screen:getSize() + -- Create/Recreate the compose cache if we changed screen geometry + if not self._bb or self._bb:getWidth() ~= screen_size.w or self._bb:getHeight() ~= screen_size.h then + if self._bb then + self._bb:free() + end + -- create a canvas for our child widget to paint to + self._bb = Blitbuffer.new(screen_size.w, screen_size.h, bb:getType()) + end + + -- We need to fill it with our usual background color on each drawing, + -- to erase bits that may not be overwritten after a scroll + self._bb:fill(Blitbuffer.COLOR_WHITE) + local dx + if self._mirroredUI then + dx = self._max_scroll_offset_x - self._scroll_offset_x - self._crop_dx + else + dx = self._scroll_offset_x + end + self[1]:paintTo(self._bb, x - dx, y - self._scroll_offset_y) + bb:blitFrom(self._bb, x + self._crop_dx, y, x + self._crop_dx, y, self._crop_w, self._crop_h) + + -- Draw our scrollbars over + if self._h_scroll_bar then + if self._mirroredUI then + self._h_scroll_bar:paintTo(bb, x + self._h_scroll_bar_shift, y + self.dimen.h - 2*self.scroll_bar_width) + else + self._h_scroll_bar:paintTo(bb, x, y + self.dimen.h - 2*self.scroll_bar_width) + end + end + if self._v_scroll_bar then + if self._mirroredUI then + self._v_scroll_bar:paintTo(bb, x + self.scroll_bar_width, y) + else + self._v_scroll_bar:paintTo(bb, x + self.dimen.w - 2*self.scroll_bar_width, y) + end + end +end + +function ScrollableContainer:propagateEvent(event) + -- Override WidgetContainer:propagateEvent() (which propagates an event + -- to children before having it handled by the widget itself) + if not self._is_scrollable then + -- pass-through + return InputContainer.propagateEvent(self, event) + end + if event.handler == "onGesture" and event.argc == 1 then + local ges = event.args[1] + -- Don't propagate events that happen out of view (in the hidden + -- scrolled-out area) to child + if ges.pos and not ges.pos:intersectWith(self.dimen) then + return false -- we may handle it here + end + end + -- Give any event first to our scrollbars + if self._v_scroll_bar and self._v_scroll_bar:handleEvent(event) then + return true + end + if self._h_scroll_bar and self._h_scroll_bar:handleEvent(event) then + return true + end + -- Pass non-gestures events, and gestures event in the view, to our child + return InputContainer.propagateEvent(self, event) +end + +function ScrollableContainer:onScrollableSwipe(_, ges) + if not self._is_scrollable then + return false + end + logger.dbg("ScrollableContainer:onScrollableSwipe", ges) + if not ges.pos:intersectWith(self.dimen) then + -- with swipe, ges.pos is swipe's start position, which should + -- be on us to consider it + return false + end + self._scrolling = false -- could have been set by "pan" event received before "swipe" + local direction = ges.direction + local distance = ges.distance + local sq_distance = math.floor(math.sqrt(distance*distance/2)) + if direction == "north" then self:_scrollBy(0, distance) + elseif direction == "south" then self:_scrollBy(0, -distance) + elseif direction == "east" then self:_scrollBy(-distance, 0) + elseif direction == "west" then self:_scrollBy(distance, 0) + elseif direction == "northeast" then self:_scrollBy(-sq_distance, sq_distance) + elseif direction == "northwest" then self:_scrollBy(sq_distance, sq_distance) + elseif direction == "southeast" then self:_scrollBy(-sq_distance, -sq_distance) + elseif direction == "southwest" then self:_scrollBy(sq_distance, -sq_distance) + end + return true +end + +function ScrollableContainer:onScrollableTouch(_, ges) + if not self._is_scrollable then + return false + end + -- First "pan" event may already be outside of us, we need to + -- remember any "touch" event on us prior to "pan" + logger.dbg("ScrollableContainer:onScrollableTouch", ges) + if ges.pos:intersectWith(self.dimen) then + self._touch_pre_pan_was_inside = true + self._scroll_relative_x = ges.pos.x + self._scroll_relative_y = ges.pos.y + else + self._touch_pre_pan_was_inside = false + end + return false +end + +function ScrollableContainer:onScrollableHold(_, ges) + if not self._is_scrollable then + return false + end + logger.dbg("ScrollableContainer:onScrollableHold", ges) + if ges.pos:intersectWith(self.dimen) then + self._scrolling = true -- start of pan + self._scroll_relative_x = ges.pos.x + self._scroll_relative_y = ges.pos.y + return true + end + return false +end + +function ScrollableContainer:onScrollableHoldPan(_, ges) + if not self._is_scrollable then + return false + end + logger.dbg("ScrollableContainer:onScrollableHoldPan", ges) + -- we may sometimes not see the "hold" event + if ges.pos:intersectWith(self.dimen) or self._scrolling or self._touch_pre_pan_was_inside then + self._touch_pre_pan_was_inside = false -- reset it + self._scrolling = true + return true + end + return false +end + +function ScrollableContainer:onScrollableHoldRelease(_, ges) + if not self._is_scrollable then + return false + end + logger.dbg("ScrollableContainer:onScrollableHoldRelease", ges) + if self._scrolling or self._touch_pre_pan_was_inside then + self._scrolling = false + if not self._scroll_relative_x or not self._scroll_relative_y then + -- no previous event gave us accurate scroll info, ignore it + return false + end + self._scroll_relative_x = ges.pos.x - self._scroll_relative_x + self._scroll_relative_y = ges.pos.y - self._scroll_relative_y + self:_scrollBy(-self._scroll_relative_x, -self._scroll_relative_y) + self._scroll_relative_x = nil + self._scroll_relative_y = nil + return true + end + return false +end + +function ScrollableContainer:onScrollablePan(_, ges) + if not self._is_scrollable then + return false + end + logger.dbg("ScrollableContainer:onScrollablePan", ges) + if ges.pos:intersectWith(self.dimen) or self._scrolling or self._touch_pre_pan_was_inside then + self._touch_pre_pan_was_inside = false -- reset it + self._scrolling = true + self._scroll_relative_x = ges.relative.x + self._scroll_relative_y = ges.relative.y + return true + end + return false +end + +function ScrollableContainer:onScrollablePanRelease(_, ges) + if not self._is_scrollable then + return false + end + logger.dbg("ScrollableContainer:onScrollablePanRelease", ges) + if self._scrolling then + self:_scrollBy(-self._scroll_relative_x, -self._scroll_relative_y) + self._scrolling = false + self._scroll_relative_x = nil + self._scroll_relative_y = nil + return true + end + return false +end + +return ScrollableContainer diff --git a/frontend/ui/widget/horizontalscrollbar.lua b/frontend/ui/widget/horizontalscrollbar.lua new file mode 100644 index 000000000..e33e7eae9 --- /dev/null +++ b/frontend/ui/widget/horizontalscrollbar.lua @@ -0,0 +1,128 @@ +local BD = require("ui/bidi") +local Blitbuffer = require("ffi/blitbuffer") +local Device = require("device") +local Geom = require("ui/geometry") +local GestureRange = require("ui/gesturerange") +local InputContainer = require("ui/widget/container/inputcontainer") +local Size = require("ui/size") +local Screen = require("device").screen + +local HorizontalScrollBar = InputContainer:new{ + enable = true, + low = 0, + high = 1, + height = Size.padding.default, + width = Size.item.height_large, -- as in VerticalScrollBar + bordersize = Size.border.thin, + bordercolor = Blitbuffer.COLOR_BLACK, + radius = 0, + rectcolor = Blitbuffer.COLOR_BLACK, + -- minimal width of the thumb/knob/grip (usually showing the current + -- view size and position relative to the whole scrollable width): + min_thumb_size = Size.line.thick, + scroll_callback = nil, + -- extra touchable height (for scrolling with pan) can be larger than + -- the provided height (this is added on each side) + extra_touch_on_side_heightratio = 1, -- make it 3 x height + _mirroredUI = BD.mirroredUILayout(), +} + +function HorizontalScrollBar:init() + self.extra_touch_on_side = math.ceil( self.extra_touch_on_side_heightratio * self.height) + if Device:isTouchDevice() then + local pan_rate = Screen.low_pan_rate and 2.0 or 5.0 + self.ges_events = { + TapScroll = { + GestureRange:new{ + ges = "tap", + range = function() return self.touch_dimen end, + }, + }, + HoldScroll = { + GestureRange:new{ + ges = "hold", + range = function() return self.touch_dimen end, + }, + }, + HoldPanScroll = { + GestureRange:new{ + ges = "hold_pan", + rate = pan_rate, + range = function() return self.touch_dimen end, + }, + }, + HoldReleaseScroll = { + GestureRange:new{ + ges = "hold_release", + range = function() return self.touch_dimen end, + }, + }, + PanScroll = { + GestureRange:new{ + ges = "pan", + rate = pan_rate, + range = function() return self.touch_dimen end, + }, + }, + PanScrollRelease = { + GestureRange:new{ + ges = "pan_release", + range = function() return self.touch_dimen end, + }, + } + } + end +end + +function HorizontalScrollBar:onTapScroll(arg, ges) + if self.scroll_callback then + local ratio = (ges.pos.x - self.touch_dimen.x) / self.width + if self._mirroredUI then + ratio = 1 - ratio + end + self.scroll_callback(ratio) + return true + end +end +HorizontalScrollBar.onHoldScroll = HorizontalScrollBar.onTapScroll +HorizontalScrollBar.onHoldPanScroll = HorizontalScrollBar.onTapScroll +HorizontalScrollBar.onHoldReleaseScroll = HorizontalScrollBar.onTapScroll +HorizontalScrollBar.onPanScroll = HorizontalScrollBar.onTapScroll +HorizontalScrollBar.onPanScrollRelease = HorizontalScrollBar.onTapScroll + +function HorizontalScrollBar:getSize() + return Geom:new{ + w = self.width, + h = self.height + } +end + +function HorizontalScrollBar:set(low, high) + self.low = low > 0 and low or 0 + self.high = high < 1 and high or 1 +end + +function HorizontalScrollBar:paintTo(bb, x, y) + if not self.enable then return end + self.touch_dimen = Geom:new{ + x = x, + y = y - self.extra_touch_on_side, + w = self.width, + h = self.height + 2 * self.extra_touch_on_side, + } + bb:paintBorder(x, y, self.width, self.height, + self.bordersize, self.bordercolor, self.radius) + if self._mirroredUI then + bb:paintRect(x + self.bordersize + (1-self.high) * self.width, y + self.bordersize, + math.max((self.width - 2 * self.bordersize) * (self.high - self.low), self.min_thumb_size), + self.height - 2 * self.bordersize, + self.rectcolor) + else + bb:paintRect(x + self.bordersize + self.low * self.width, y + self.bordersize, + math.max((self.width - 2 * self.bordersize) * (self.high - self.low), self.min_thumb_size), + self.height - 2 * self.bordersize, + self.rectcolor) + end +end + +return HorizontalScrollBar diff --git a/frontend/ui/widget/keyboardlayoutdialog.lua b/frontend/ui/widget/keyboardlayoutdialog.lua index b1398159c..a6a384c41 100644 --- a/frontend/ui/widget/keyboardlayoutdialog.lua +++ b/frontend/ui/widget/keyboardlayoutdialog.lua @@ -14,6 +14,7 @@ local Language = require("ui/language") local LineWidget = require("ui/widget/linewidget") local MovableContainer = require("ui/widget/container/movablecontainer") local RadioButtonTable = require("ui/widget/radiobuttontable") +local ScrollableContainer = require("ui/widget/container/scrollablecontainer") local Size = require("ui/size") local TextWidget = require("ui/widget/textwidget") local UIManager = require("ui/uimanager") @@ -100,12 +101,15 @@ function KeyboardLayoutDialog:init() }, }) + -- (RadioButtonTable's width and padding setup is a bit fishy: we get + -- this to look ok by using a CenterContainer to ensure some padding) + local scroll_container_inner_width = self.title_bar:getSize().w - ScrollableContainer:getScrollbarWidth() self.radio_button_table = RadioButtonTable:new{ radio_buttons = radio_buttons, - width = math.floor(self.width * 0.9), + width = scroll_container_inner_width - 2*Size.padding.large, focused = true, - scroll = false, parent = self, + show_parent = self, face = self.face, } @@ -119,6 +123,29 @@ function KeyboardLayoutDialog:init() show_parent = self, } + local max_radio_button_container_height = math.floor(Screen:getHeight()*0.9 + - self.title_widget:getSize().h - self.title_bar:getSize().h + - Size.span.vertical_large*4 - self.button_table:getSize().h) + local radio_button_container_height = math.min(self.radio_button_table:getSize().h, max_radio_button_container_height) + + -- Our scrollable container needs to be known as widget.cropping_widget in + -- the widget that is passed to UIManager:show() for UIManager to ensure + -- proper interception of inner widget self repainting/invert (mostly used + -- when flashing for UI feedback that we want to limit to the cropped area). + self.cropping_widget = ScrollableContainer:new{ + dimen = Geom:new{ + w = self.title_bar:getSize().w, + h = radio_button_container_height, + }, + show_parent = self, + CenterContainer:new{ + dimen = Geom:new{ + w = scroll_container_inner_width, + h = self.radio_button_table:getSize().h, + }, + self.radio_button_table, + }, + } self.dialog_frame = FrameContainer:new{ radius = Size.radius.window, bordersize = Size.border.window, @@ -132,13 +159,7 @@ function KeyboardLayoutDialog:init() VerticalSpan:new{ width = Size.span.vertical_large*2, }, - CenterContainer:new{ - dimen = Geom:new{ - w = self.title_bar:getSize().w, - h = self.radio_button_table:getSize().h, - }, - self.radio_button_table, - }, + self.cropping_widget, -- our ScrollableContainer VerticalSpan:new{ width = Size.span.vertical_large*2, }, diff --git a/frontend/ui/widget/radiobutton.lua b/frontend/ui/widget/radiobutton.lua index 631fea93c..f6b9f4393 100644 --- a/frontend/ui/widget/radiobutton.lua +++ b/frontend/ui/widget/radiobutton.lua @@ -87,6 +87,7 @@ function RadioButton:update() background = self.background, radius = self.radius, padding = self.padding, + show_parent = self.show_parent, LeftContainer:new{ dimen = Geom:new{ w = self.width,