2
0
mirror of https://github.com/koreader/koreader synced 2024-11-11 19:11:14 +00:00
koreader/frontend/ui/widget/titlebar.lua
poire-z 2530e954a2 Add TitleBar widget
Existing widgets building their own title bar with help
of CloseButton should progressively be updated to use
this, for clarity, consistency, and less code duplication.
2022-01-04 21:59:37 +01:00

252 lines
9.5 KiB
Lua

local Device = require("device")
local Font = require("ui/font")
local Geom = require("ui/geometry")
local HorizontalGroup = require("ui/widget/horizontalgroup")
local HorizontalSpan = require("ui/widget/horizontalspan")
local IconButton = require("ui/widget/iconbutton")
local LineWidget = require("ui/widget/linewidget")
local Math = require("optmath")
local OverlapGroup = require("ui/widget/overlapgroup")
local Size = require("ui/size")
local TextWidget = require("ui/widget/textwidget")
local UIManager = require("ui/uimanager")
local VerticalGroup = require("ui/widget/verticalgroup")
local VerticalSpan = require("ui/widget/verticalspan")
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",
with_bottom_line = false,
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"),
subtitle_face = Font:getFace("xx_smallinfofont"),
title_top_padding = nil, -- computed if none provided
title_h_padding = Size.padding.large, -- horizontal padding (this replaces button_padding on the inner/title side)
title_subtitle_v_padding = Screen:scaleBySize(3),
bottom_v_padding = nil, -- hardcoded default values, different whether with_bottom_line true or false
button_padding = Screen:scaleBySize(11), -- fine to keep exit/cross icon diagonally aligned with screen corners
left_icon = nil,
left_icon_size_ratio = 0.6,
left_icon_tap_callback = function() end,
left_icon_hold_callback = function() end,
left_icon_allow_flash = true,
right_icon = nil,
right_icon_size_ratio = 0.6,
right_icon_tap_callback = function() end,
right_icon_hold_callback = function() end,
right_icon_allow_flash = true,
-- If provided, use right_icon="exit" and use this as right_icon_tap_callback
close_callback = nil,
hold_close_callback = nil,
}
function TitleBar:init()
if self.close_callback then
self.right_icon = "exit"
self.right_icon_tap_callback = self.close_callback
self.right_icon_allow_flash = false
if self.close_hold_callback then
self.right_icon_hold_callback = function() self.close_hold_callback() end
end
end
if not self.width then
self.width = Screen:getWidth()
end
local title_max_width = self.width - 2 * self.title_h_padding
local left_icon_size = Screen:scaleBySize(DGENERIC_ICON_SIZE * self.left_icon_size_ratio)
local right_icon_size = Screen:scaleBySize(DGENERIC_ICON_SIZE * self.right_icon_size_ratio)
self.has_left_icon = false
self.has_right_icon = false
-- No button on non-touch device
if Device:isTouchDevice() then
if self.left_icon then
title_max_width = title_max_width - left_icon_size - self.button_padding
self.has_left_icon = true
end
if self.right_icon then
title_max_width = title_max_width - right_icon_size - self.button_padding
self.has_right_icon = true
end
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
end
if not self.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 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))
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
self.subtitle_widget = TextWidget:new{
text = self.subtitle,
face = self.subtitle_face,
max_width = title_max_width,
padding = 0,
}
end
-- To debug vertical positionning:
-- local FrameContainer = require("ui/widget/container/framecontainer")
-- self.title_widget = FrameContainer:new{ padding=0, margin=0, bordersize=1, self.title_widget}
-- self.subtitle_widget = FrameContainer:new{ padding=0, margin=0, bordersize=1, self.subtitle_widget}
self.title_group = VerticalGroup:new{
align = self.align,
VerticalSpan:new{width = self.title_top_padding},
self.title_widget,
self.subtitle_widget and VerticalSpan:new{width = self.title_subtitle_v_padding},
self.subtitle_widget,
}
if self.align == "left" then
local padding = self.title_h_padding
if self.has_left_icon then
padding = padding + self.button_padding + left_icon_size
end
self.inner_title_group = self.title_group -- we need to :resetLayout() both in :setTitle()
self.title_group = HorizontalGroup:new{
HorizontalSpan:new{ width = padding },
self.title_group,
}
end
self.title_group.overlap_align = self.align
table.insert(self, self.title_group)
-- This TitleBar widget is an OverlapGroup: all sub elements overlap,
-- and can overflow or underflow. Its height for its containers is
-- the one we set as self.dimen.h.
self.titlebar_height = self.title_group:getSize().h
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 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 },
},
}
table.insert(self, filler_and_bottom_line)
self.titlebar_height = filler_and_bottom_line:getSize().h
end
if not self.bottom_v_padding then
if self.with_bottom_line then
self.bottom_v_padding = Size.padding.default
else
self.bottom_v_padding = Size.padding.large
end
end
self.titlebar_height = self.titlebar_height + self.bottom_v_padding
self.dimen = Geom:new{
w = self.width,
h = self.titlebar_height, -- buttons can overflow this
}
if self.has_left_icon then
self.left_button = IconButton:new{
icon = self.left_icon,
width = left_icon_size,
height = left_icon_size,
padding = self.button_padding,
padding_right = 2 * left_icon_size, -- extend button tap zone
padding_bottom = left_icon_size,
overlap_align = "left",
callback = self.left_icon_tap_callback,
hold_callback = self.left_icon_hold_callback,
allow_flash = self.left_icon_allow_flash
}
table.insert(self, self.left_button)
end
if self.has_right_icon then
self.right_button = IconButton:new{
icon = self.right_icon,
width = right_icon_size,
height = right_icon_size,
padding = self.button_padding,
padding_left = 2 * right_icon_size, -- extend button tap zone
padding_bottom = right_icon_size,
overlap_align = "right",
callback = self.right_icon_tap_callback,
hold_callback = self.right_icon_hold_callback,
allow_flash = self.right_icon_allow_flash
}
table.insert(self, self.right_button)
end
-- We :extend() OverlapGroup and did not :new() it, so we can
-- :init() it now, after we have added all the subelements.
OverlapGroup.init(self)
end
function TitleBar:paintTo(bb, x, y)
-- We need to update self.dimen's x and y for any ges.pos:intersectWith(title_bar)
-- to work. (This is done by FrameContainer, but not by most other widgets... It
-- should probably be done in all of them, but not sure of side effects...)
self.dimen.x = x
self.dimen.y = y
OverlapGroup.paintTo(self, bb, x, y)
end
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()
end
self.title_group:resetLayout()
UIManager:setDirty(self.show_parent, "ui", self.dimen)
end
function TitleBar:setSubTitle(subtitle)
if self.subtitle_widget then
self.subtitle_widget:setText(subtitle)
if self.inner_title_group then
self.inner_title_group:resetLayout()
end
self.title_group:resetLayout()
UIManager:setDirty(self.show_parent, "ui", self.dimen)
end
end
function TitleBar:setLeftIcon(icon)
if self.has_left_icon then
self.left_button:setIcon(icon)
UIManager:setDirty(self.show_parent, "ui", self.dimen)
end
end
function TitleBar:setRightIcon(icon)
if self.has_right_icon then
self.right_button:setIcon(icon)
UIManager:setDirty(self.show_parent, "ui", self.dimen)
end
end
return TitleBar