diff --git a/frontend/ui/widget/notification.lua b/frontend/ui/widget/notification.lua index 88f4657cc..caf09ac3a 100644 --- a/frontend/ui/widget/notification.lua +++ b/frontend/ui/widget/notification.lua @@ -10,9 +10,11 @@ local FrameContainer = require("ui/widget/container/framecontainer") local Geom = require("ui/geometry") local GestureRange = require("ui/gesturerange") local InputContainer = require("ui/widget/container/inputcontainer") +local RectSpan = require("ui/widget/rectspan") local Size = require("ui/size") local TextWidget = require("ui/widget/textwidget") local UIManager = require("ui/uimanager") +local VerticalGroup = require("ui/widget/verticalgroup") local Input = Device.input local Screen = Device.screen @@ -23,6 +25,8 @@ local Notification = InputContainer:new{ padding = Size.padding.default, timeout = 2, -- default to 2 seconds toast = true, -- closed on any event, and let the event propagate to next top widget + + _nums_shown = {}, -- array of stacked notifications } function Notification:init() @@ -48,36 +52,69 @@ function Notification:init() end end - -- we construct the actual content here because self.text is only available now local text_widget = TextWidget:new{ text = self.text, face = self.face, } local widget_size = text_widget:getSize() - self[1] = CenterContainer:new{ - dimen = Geom:new{ - w = Screen:getWidth(), - h = math.floor(Screen:getHeight() / 10), - }, - FrameContainer:new{ - background = Blitbuffer.COLOR_WHITE, - radius = 0, - margin = self.margin, - padding = self.padding, - CenterContainer:new{ - dimen = Geom:new{ - w = widget_size.w, - h = widget_size.h - }, - text_widget, - } + self.frame = FrameContainer:new{ + background = Blitbuffer.COLOR_WHITE, + radius = 0, + margin = self.margin, + padding = self.padding, + CenterContainer:new{ + dimen = Geom:new{ + w = widget_size.w, + h = widget_size.h + }, + text_widget, } } + local notif_height = self.frame:getSize().h + + self:_cleanShownStack() + table.insert(Notification._nums_shown, os.time()) + self.num = #Notification._nums_shown + + self[1] = VerticalGroup:new{ + align = "center", + -- We use a span to properly position this notification: + RectSpan:new{ + -- have this VerticalGroup full width, to ensure centering + width = Screen:getWidth(), + -- push this frame at its y=self.num position + height = notif_height * (self.num - 1), + }, + self.frame, + } +end + +function Notification:_cleanShownStack(num) + -- Clean stack of shown notifications + if num then + -- This notification is no longer displayed + Notification._nums_shown[num] = false + end + -- We remove from the stack tail all slots no longer displayed. + -- Even if slots at top are available, we'll keep adding new + -- notifications only at the tail/bottom (easier for the eyes + -- to follow what is happening). + -- As a sanity check, we also forget those shown for + -- more than 30s in case no close event was received. + local expire_ts = os.time() - 30 + for i=#Notification._nums_shown, 1, -1 do + if Notification._nums_shown[i] and Notification._nums_shown[i] > expire_ts then + break -- still shown (or not yet expired) + end + table.remove(Notification._nums_shown, i) + end end function Notification:onCloseWidget() + self:_cleanShownStack(self.num) + self.num = nil -- avoid mess in case onCloseWidget is called multiple times UIManager:setDirty(nil, function() - return "ui", self[1][1].dimen + return "ui", self.frame.dimen end) return true end @@ -85,7 +122,7 @@ end function Notification:onShow() -- triggered by the UIManager after we got successfully shown (not yet painted) UIManager:setDirty(self, function() - return "ui", self[1][1].dimen + return "ui", self.frame.dimen end) if self.timeout then UIManager:scheduleIn(self.timeout, function() UIManager:close(self) end)