2
0
mirror of https://github.com/koreader/koreader synced 2024-11-16 06:12:56 +00:00
koreader/frontend/ui/widget/progresswidget.lua
NiLuJe fadee1f5dc
Clarify our OOP semantics across the codebase (#9586)
Basically:

* Use `extend` for class definitions
* Use `new` for object instantiations

That includes some minor code cleanups along the way:

* Updated `Widget`'s docs to make the semantics clearer.
* Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283)
* Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass).
* Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events.
* Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier.
* Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references.
  * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak).
* Terminal: Make sure the shell is killed on plugin teardown.
* InputText: Fix Home/End/Del physical keys to behave sensibly.
* InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...).
* OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of.
* ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed!
* Kobo: Minor code cleanups.
2022-10-06 02:14:48 +02:00

213 lines
7.0 KiB
Lua

--[[--
Widget for displaying progress bar.
Configurable attributes:
* width
* height
* margin_v -- vertical margin between border and fill bar
* margin_h -- horizontal margin between border and fill bar
* radius
* bordersize
* bordercolor
* bgcolor
* fillcolor -- color of the main fill bar
* altcolor -- color of the alt fill bar
* ticks (list) -- default to nil, use this if you want to insert markers
* tick_width
* last -- maximum tick, used with ticks
Example:
local foo_bar = ProgressWidget:new{
width = Screen:scaleBySize(400),
height = Screen:scaleBySize(10),
percentage = 50/100,
}
UIManager:show(foo_bar)
]]
local BD = require("ui/bidi")
local Blitbuffer = require("ffi/blitbuffer")
local Geom = require("ui/geometry")
local Widget = require("ui/widget/widget")
local Screen = require("device").screen
local ProgressWidget = Widget:extend{
width = nil,
height = nil,
margin_h = Screen:scaleBySize(3),
margin_v = Screen:scaleBySize(1),
radius = Screen:scaleBySize(2),
bordersize = Screen:scaleBySize(1),
bordercolor = Blitbuffer.COLOR_BLACK,
bgcolor = Blitbuffer.COLOR_WHITE,
fillcolor = Blitbuffer.COLOR_DARK_GRAY,
altcolor = Blitbuffer.COLOR_LIGHT_GRAY,
percentage = nil,
ticks = nil,
tick_width = Screen:scaleBySize(3),
last = nil,
fill_from_right = false,
allow_mirroring = true,
alt = nil, -- table with alternate pages to mark with different color (in the form {{ini1, len1}, {ini2, len2}, ...})
_orig_margin_v = nil,
_orig_bordersize = nil,
}
function ProgressWidget:getSize()
return { w = self.width, h = self.height }
end
function ProgressWidget:paintTo(bb, x, y)
local my_size = self:getSize()
self.dimen = Geom:new{
x = x, y = y,
w = my_size.w,
h = my_size.h
}
if self.dimen.w == 0 or self.dimen.h == 0 then return end
local _mirroredUI = BD.mirroredUILayout()
-- We'll draw every bar element in order, bottom to top.
local fill_width = my_size.w - 2*(self.margin_h + self.bordersize)
local fill_y = y + self.margin_v + self.bordersize
local fill_height = my_size.h - 2*(self.margin_v + self.bordersize)
if self.radius == 0 then
-- If we don't have rounded borders, we can start with a simple border colored rectangle.
bb:paintRect(x, y, my_size.w, my_size.h, self.bordercolor)
-- And a full background bar inside (i.e., on top) of that.
bb:paintRect(x + self.margin_h + self.bordersize,
fill_y,
math.ceil(fill_width),
math.ceil(fill_height),
self.bgcolor)
else
-- Otherwise, we have to start with the background.
bb:paintRoundedRect(x, y, my_size.w, my_size.h, self.bgcolor, self.radius)
-- Then the border around that.
bb:paintBorder(x, y,
my_size.w, my_size.h,
self.bordersize, self.bordercolor, self.radius)
end
-- Then we can just paint the fill rectangle(s) and tick(s) on top of that.
-- First the fill bar(s)...
-- Fill bar for alternate pages (e.g. non-linear flows).
if self.alt and self.alt[1] ~= nil then
for i=1, #self.alt do
local tick_x = fill_width * ((self.alt[i][1] - 1) / self.last)
local width = fill_width * (self.alt[i][2] / self.last)
if _mirroredUI then
tick_x = fill_width - tick_x - width
end
tick_x = math.floor(tick_x)
width = math.ceil(width)
bb:paintRect(x + self.margin_h + self.bordersize + tick_x,
fill_y,
width,
math.ceil(fill_height),
self.altcolor)
end
end
-- Main fill bar for the specified percentage.
if self.percentage >= 0 and self.percentage <= 1 then
local fill_x = x + self.margin_h + self.bordersize
if self.fill_from_right or (_mirroredUI and not self.fill_from_right) then
fill_x = fill_x + (fill_width * (1 - self.percentage))
fill_x = math.floor(fill_x)
end
bb:paintRect(fill_x,
fill_y,
math.ceil(fill_width * self.percentage),
math.ceil(fill_height),
self.fillcolor)
end
-- ...then the tick(s).
if self.ticks and self.last and self.last > 0 then
for i, tick in ipairs(self.ticks) do
local tick_x = fill_width * (tick / self.last)
if _mirroredUI then
tick_x = fill_width - tick_x
end
tick_x = math.floor(tick_x)
bb:paintRect(x + self.margin_h + self.bordersize + tick_x,
fill_y,
self.tick_width,
math.ceil(fill_height),
self.bordercolor)
end
end
end
function ProgressWidget:setPercentage(percentage)
self.percentage = percentage
end
function ProgressWidget:getPercentageFromPosition(pos)
if not pos or not pos.x then
return nil
end
local width = self.dimen.w - 2*self.margin_h
local x = pos.x - self.dimen.x - self.margin_h
if x < 0 or x > width then
return nil
end
if BD.mirroredUILayout() then
x = width - x
end
return x / width
end
function ProgressWidget:setHeight(height)
self.height = Screen:scaleBySize(height)
-- Adjust vertical margin and border size to ensure there's
-- at least 1 pixel left for the actual bar
self._orig_margin_v = self._orig_margin_v or self.margin_v
self._orig_bordersize = self._orig_bordersize or self.bordersize
local margin_v_min = self._orig_margin_v > 0 and 1 or 0
local bordersize_min = self._orig_bordersize > 0 and 1 or 0
self.margin_v = math.min(self._orig_margin_v, math.floor((self.height - 2*self._orig_bordersize - 1) / 2))
self.margin_v = math.max(self.margin_v, margin_v_min)
self.bordersize = math.min(self._orig_bordersize, math.floor((self.height - 2*self.margin_v - 1) / 2))
self.bordersize = math.max(self.bordersize, bordersize_min)
end
function ProgressWidget:updateStyle(thick, height)
if thick then
self.margin_h = Screen:scaleBySize(3)
self.margin_v = Screen:scaleBySize(1)
self.bordersize = Screen:scaleBySize(1)
self.radius = Screen:scaleBySize(2)
self.bgcolor = Blitbuffer.COLOR_WHITE
self.fillcolor = Blitbuffer.COLOR_DARK_GRAY
self._orig_margin_v = nil
self._orig_bordersize = nil
if height then
self:setHeight(height)
end
else
self.margin_h = 0
self.margin_v = 0
self.bordersize = 0
self.radius = 0
self.bgcolor = Blitbuffer.COLOR_GRAY
self.fillcolor = Blitbuffer.COLOR_DIM_GRAY
self.ticks = nil
self._orig_margin_v = nil
self._orig_bordersize = nil
if height then
self:setHeight(height)
end
end
end
return ProgressWidget