From 4546d826ee1cec9a40880e606f34c5c66df15504 Mon Sep 17 00:00:00 2001 From: poire-z Date: Sat, 8 Jan 2022 16:58:30 +0100 Subject: [PATCH] TitleBar: new options, dedicated close button - Fix hold callback name - Add options to handle long titles (truncated by default): "title_multines" and "title_shrink_font_to_fit" (allowing setTitle() with them makes the code a bit more complex). - Add options to set bottom line color and horizontal padding. - Use an added close.svg (based on exit.svg, tweaked to have similar size and baseline as other icons) for close button. IconButton: - handle hold/hold_release similar to Button. - new option allow_flash to allow disabling flashing. --- frontend/ui/widget/bookmapwidget.lua | 4 +- frontend/ui/widget/iconbutton.lua | 23 ++- frontend/ui/widget/pagebrowserwidget.lua | 6 +- frontend/ui/widget/titlebar.lua | 205 +++++++++++++++++++---- resources/icons/mdlight/close.svg | 53 ++++++ 5 files changed, 258 insertions(+), 33 deletions(-) create mode 100644 resources/icons/mdlight/close.svg diff --git a/frontend/ui/widget/bookmapwidget.lua b/frontend/ui/widget/bookmapwidget.lua index 10c761a59..09e82da9d 100644 --- a/frontend/ui/widget/bookmapwidget.lua +++ b/frontend/ui/widget/bookmapwidget.lua @@ -600,7 +600,7 @@ function BookMapWidget:init() left_icon = "notice-info", left_icon_tap_callback = function() self:showHelp() end, close_callback = function() self:onClose() end, - hold_close_callback = function() self:onClose(true) end, + close_hold_callback = function() self:onClose(true) end, show_parent = self, } self.title_bar_h = self.title_bar:getHeight() @@ -1036,6 +1036,8 @@ function BookMapWidget:onClose(close_all_parents) -- The last one of these (which has no launcher attribute) -- will do the cleanup below. self.launcher:onClose(true) + else + UIManager:setDirty(self.launcher, "ui") end else -- Remove all thumbnails generated for a different target size than diff --git a/frontend/ui/widget/iconbutton.lua b/frontend/ui/widget/iconbutton.lua index f4b1367e8..8eec1a5c3 100644 --- a/frontend/ui/widget/iconbutton.lua +++ b/frontend/ui/widget/iconbutton.lua @@ -27,6 +27,7 @@ local IconButton = InputContainer:new{ padding_left = nil, enabled = true, callback = nil, + allow_flash = true, -- set to false for any IconButton that may close its container } function IconButton:init() @@ -85,6 +86,13 @@ function IconButton:initGesListener() range = self.dimen, }, doc = "Hold IconButton", + }, + HoldReleaseIconButton = { + GestureRange:new{ + ges = "hold_release", + range = self.dimen, + }, + doc = "Hold Release IconButton", } } end @@ -92,7 +100,7 @@ end function IconButton:onTapIconButton() if not self.callback then return end - if G_reader_settings:isFalse("flash_ui") then + if G_reader_settings:isFalse("flash_ui") or not self.allow_flash then self.callback() else -- c.f., ui/widget/button for more gnarly details about the implementation, but the flow of the flash_ui codepath essentially goes like this: @@ -133,6 +141,10 @@ function IconButton:onTapIconButton() end function IconButton:onHoldIconButton() + -- If we're going to process this hold, we must make + -- sure to also handle its hold_release below, so it's + -- not propagated up to a MovableContainer + self._hold_handled = nil if self.enabled and self.hold_callback then self.hold_callback() elseif self.hold_input then @@ -140,9 +152,18 @@ function IconButton:onHoldIconButton() elseif type(self.hold_input_func) == "function" then self:onInput(self.hold_input_func()) elseif self.hold_callback == nil then return end + self._hold_handled = true return true end +function IconButton:onHoldReleaseIconButton() + if self._hold_handled then + self._hold_handled = nil + return true + end + return false +end + function IconButton:onFocus() --quick and dirty, need better way to show focus self.image.invert = true diff --git a/frontend/ui/widget/pagebrowserwidget.lua b/frontend/ui/widget/pagebrowserwidget.lua index 82827e2db..9ffd5c5fc 100644 --- a/frontend/ui/widget/pagebrowserwidget.lua +++ b/frontend/ui/widget/pagebrowserwidget.lua @@ -105,7 +105,7 @@ function PageBrowserWidget:init() left_icon = "notice-info", left_icon_tap_callback = function() self:showHelp() end, close_callback = function() self:onClose() end, - hold_close_callback = function() self:onClose(true) end, + close_hold_callback = function() self:onClose(true) end, show_parent = self, } self.title_bar_h = self.title_bar:getHeight() @@ -618,8 +618,8 @@ function PageBrowserWidget:onClose(close_all_parents) if self.requests_batch_id then self.ui.thumbnail:cancelPageThumbnailRequests(self.requests_batch_id) end - logger.dbg("closing PageBrowserWidget") -- Close this widget + logger.dbg("closing PageBrowserWidget") UIManager:close(self) if self.launcher then -- We were launched by a BookMapWidget, don't do any cleanup. @@ -627,6 +627,8 @@ function PageBrowserWidget:onClose(close_all_parents) -- The last one of these (which has no launcher attribute) -- will do the cleanup below. self.launcher:onClose(true) + else + UIManager:setDirty(self.launcher, "ui") end else -- Remove all thumbnails generated for a different target size than diff --git a/frontend/ui/widget/titlebar.lua b/frontend/ui/widget/titlebar.lua index 1f2bfc1a9..cb16e2576 100644 --- a/frontend/ui/widget/titlebar.lua +++ b/frontend/ui/widget/titlebar.lua @@ -8,6 +8,7 @@ local LineWidget = require("ui/widget/linewidget") local Math = require("optmath") local OverlapGroup = require("ui/widget/overlapgroup") local Size = require("ui/size") +local TextBoxWidget = require("ui/widget/textboxwidget") local TextWidget = require("ui/widget/textwidget") local UIManager = require("ui/uimanager") local VerticalGroup = require("ui/widget/verticalgroup") @@ -17,14 +18,21 @@ local Screen = Device.screen local TitleBar = OverlapGroup:extend{ width = nil, -- default to screen width fullscreen = false, -- larger font and small adjustments if fullscreen - align = "center", + align = "center", -- title & subtitle alignment inside TitleBar + with_bottom_line = false, + bottom_line_color = nil, -- default to black + bottom_line_h_padding = nil, -- default to 0: full width title = "", - subtitle = nil, title_face = nil, -- if not provided, one of these will be used: title_face_fullscreen = Font:getFace("smalltfont"), title_face_not_fullscreen = Font:getFace("x_smalltfont"), + -- by default: single line, truncated if overflow -- the default could be made dependant on self.fullscreen + title_multilines = false, -- multilines if overflow + title_shrink_font_to_fit = false, -- reduce font size so that single line text fits + + subtitle = nil, subtitle_face = Font:getFace("xx_smallinfofont"), title_top_padding = nil, -- computed if none provided @@ -46,12 +54,23 @@ local TitleBar = OverlapGroup:extend{ -- If provided, use right_icon="exit" and use this as right_icon_tap_callback close_callback = nil, - hold_close_callback = nil, + close_hold_callback = nil, + + show_parent = nil, + + -- Internal: remember first sizes computed when title_shrink_font_to_fit=true, + -- and keep using them after :setTitle() in case a smaller font size is needed, + -- to keep the TitleBar geometry stable. + _initial_title_top_padding = nil, + _initial_title_text_baseline = nil, + _initial_titlebar_height = nil, + _initial_filler_height = nil, + _initial_re_init_needed = nil, } function TitleBar:init() if self.close_callback then - self.right_icon = "exit" + self.right_icon = "close" self.right_icon_tap_callback = self.close_callback self.right_icon_allow_flash = false if self.close_hold_callback then @@ -82,25 +101,86 @@ function TitleBar:init() end -- Title, subtitle, and their alignment - if not self.title_face then - self.title_face = self.fullscreen and self.title_face_fullscreen or self.title_face_not_fullscreen + local title_face = self.title_face + if not title_face then + title_face = self.fullscreen and self.title_face_fullscreen or self.title_face_not_fullscreen end - if not self.title_top_padding then + if self.title_multilines then + self.title_widget = TextBoxWidget:new{ + text = self.title, + alignment = self.align, + width = title_max_width, + face = title_face, + } + else + while true do + self.title_widget = TextWidget:new{ + text = self.title, + face = title_face, + padding = 0, + max_width = not self.title_shrink_font_to_fit and title_max_width, + -- truncate if not self.title_shrink_font_to_fit + } + if not self.title_shrink_font_to_fit then + break -- truncation allowed, no loop needed + end + if self.title_widget:getWidth() <= title_max_width then + break -- text with normal font fits, no loop needed + end + -- Text doesn't fit + if not self._initial_titlebar_height then + -- We're with title_shrink_font_to_fit and in the first :init(): + -- we don't want to go on measuring with this too long text. + -- We want metrics proper for when text fits, so if later :setTitle() + -- is called with a text that fits, this text will look allright. + -- Longer title with a smaller font size should be laid out on the + -- baseline of a fitted text. + -- So, go on computing sizes with an empty title. When all is + -- gathered, we'll re :init() ourselves with the original title, + -- using the metrics we're computing now (self._initial*). + self._initial_re_init_needed = true + self.title_widget:free() + self.title_widget = TextWidget:new{ + text = "", + face = title_face, + padding = 0, + } + break + end + -- otherwise, loop and do the same with a smaller font size + self.title_widget:free() + title_face = Font:getFace(title_face.orig_font, title_face.orig_size - 1) + end + end + local title_top_padding = self.title_top_padding + if not title_top_padding then -- Compute it so baselines of the text and of the icons align. -- Our icons' baselines looks like they could be at 83% to 90% of their height. - local face_height, face_baseline = self.title_face.ftface:getHeightAndAscender() -- luacheck: no unused + local text_baseline = self.title_widget:getBaseline() local icon_height = math.max(left_icon_size, right_icon_size) local icon_baseline = icon_height * 0.85 + self.button_padding - self.title_top_padding = Math.round(math.max(0, icon_baseline - face_baseline)) + title_top_padding = Math.round(math.max(0, icon_baseline - text_baseline)) + if self.title_shrink_font_to_fit then + -- Use, or store, the first top padding and baseline we have computed, + -- so the text stays vertically stable + if self._initial_title_top_padding then + -- Use this to have baselines aligned: + -- title_top_padding = Math.round(self._initial_title_top_padding + self._initial_title_text_baseline - text_baseline) + -- But then, smaller text is not vertically centered in the title bar. + -- So, go with just half the baseline difference: + title_top_padding = Math.round(self._initial_title_top_padding + (self._initial_title_text_baseline - text_baseline)/2) + else + self._initial_title_top_padding = title_top_padding + self._initial_title_text_baseline = text_baseline + end + end end - self.title_widget = TextWidget:new{ - text = self.title, - face = self.title_face, - max_width = title_max_width, - padding = 0, - } + self.subtitle_widget = nil if self.subtitle then + -- No specific options for subtitles currently: truncate if too long. + -- We might want to add truncate_left if this gets used for a filepath + -- as in FileManager. self.subtitle_widget = TextWidget:new{ text = self.subtitle, face = self.subtitle_face, @@ -115,7 +195,7 @@ function TitleBar:init() self.title_group = VerticalGroup:new{ align = self.align, - VerticalSpan:new{width = self.title_top_padding}, + VerticalSpan:new{width = title_top_padding}, self.title_widget, self.subtitle_widget and VerticalSpan:new{width = self.title_subtitle_v_padding}, self.subtitle_widget, @@ -139,15 +219,45 @@ function TitleBar:init() -- the one we set as self.dimen.h. self.titlebar_height = self.title_group:getSize().h + if self.title_shrink_font_to_fit then + -- Use, or store, the first title_group height we have computed, + -- so the TitleBar geometry and the bottom line position stay stable + -- (face height may have changed, even after we kept the baseline + -- stable, as we did above). + if self._initial_titlebar_height then + self.titlebar_height = self._initial_titlebar_height + else + self._initial_titlebar_height = self.titlebar_height + end + end + if self.with_bottom_line then -- Be sure we add between the text and the line at least as much padding -- as above the text, to keep it vertically centered. - local title_bottom_padding = math.max(self.title_top_padding, Size.padding.default) + local title_bottom_padding = math.max(title_top_padding, Size.padding.default) + local filler_height = self.titlebar_height + title_bottom_padding + if self.title_shrink_font_to_fit then + -- Use, or store, the first filler height we have computed, + if self._initial_filler_height then + filler_height = self._initial_filler_height + else + self._initial_filler_height = filler_height + end + end + local line_widget = LineWidget:new{ + dimen = Geom:new{ w = self.width, h = Size.line.thick }, + background = self.bottom_line_color; + } + if self.bottom_line_h_padding then + line_widget.dimen.w = line_widget.dimen.w - 2 * self.bottom_line_h_padding + line_widget = HorizontalGroup:new{ + HorizontalSpan:new{ width = self.bottom_line_h_padding }, + line_widget, + } + end local filler_and_bottom_line = VerticalGroup:new{ - VerticalSpan:new{ width = self.titlebar_height + title_bottom_padding }, - LineWidget:new{ - dimen = Geom:new{ w = self.width, h = Size.line.thick }, - }, + VerticalSpan:new{ width = filler_height }, + line_widget, } table.insert(self, filler_and_bottom_line) self.titlebar_height = filler_and_bottom_line:getSize().h @@ -161,6 +271,14 @@ function TitleBar:init() end self.titlebar_height = self.titlebar_height + self.bottom_v_padding + if self._initial_re_init_needed then + -- We have computed all the self._initial_ metrics needed. + self._initial_re_init_needed = nil + self:clear() + self:init() + return + end + self.dimen = Geom:new{ w = self.width, h = self.titlebar_height, -- buttons can overflow this @@ -177,7 +295,8 @@ function TitleBar:init() overlap_align = "left", callback = self.left_icon_tap_callback, hold_callback = self.left_icon_hold_callback, - allow_flash = self.left_icon_allow_flash + allow_flash = self.left_icon_allow_flash, + show_parent = self.show_parent, } table.insert(self, self.left_button) end @@ -192,7 +311,8 @@ function TitleBar:init() overlap_align = "right", callback = self.right_icon_tap_callback, hold_callback = self.right_icon_hold_callback, - allow_flash = self.right_icon_allow_flash + allow_flash = self.right_icon_allow_flash, + show_parent = self.show_parent, } table.insert(self, self.right_button) end @@ -215,13 +335,40 @@ function TitleBar:getHeight() return self.titlebar_height end -function TitleBar:setTitle(title) - self.title_widget:setText(title) - if self.inner_title_group then - self.inner_title_group:resetLayout() +function TitleBar:setTitle(title, no_refresh) + if self.title_multilines or self.title_shrink_font_to_fit then + -- We need to re-init the whole widget as its height or + -- padding may change. + local previous_height = self.titlebar_height + -- Call WidgetContainer:clear() that will call :free() and + -- will remove subwidgets from the OverlapGroup we are. + self:clear() + self.title = title + self:init() + if no_refresh then + -- If caller is sure to handle refresh correctly, it can provides this + return + end + if self.title_multilines and self.titlebar_height ~= previous_height then + -- Title height have changed, and the upper widget may not have + -- hooks to refresh a combination of its previous size and new + -- size: be sure everything is repainted + UIManager:setDirty("all", "ui") + else + UIManager:setDirty(self.show_parent, "ui", self.dimen) + end + else + -- TextWidget with max-width: we can just update its text + self.title_widget:setText(title) + if self.inner_title_group then + self.inner_title_group:resetLayout() + end + self.title_group:resetLayout() + if no_refresh then + return + end + UIManager:setDirty(self.show_parent, "ui", self.dimen) end - self.title_group:resetLayout() - UIManager:setDirty(self.show_parent, "ui", self.dimen) end function TitleBar:setSubTitle(subtitle) diff --git a/resources/icons/mdlight/close.svg b/resources/icons/mdlight/close.svg new file mode 100644 index 000000000..a5cc21a5b --- /dev/null +++ b/resources/icons/mdlight/close.svg @@ -0,0 +1,53 @@ + +image/svg+xml + +