Skip to content

Instantly share code, notes, and snippets.

@sebdelsol
Last active May 4, 2025 22:38
Show Gist options
  • Save sebdelsol/16ab741b44b75a46cbceb133163bbab5 to your computer and use it in GitHub Desktop.
Save sebdelsol/16ab741b44b75a46cbceb133163bbab5 to your computer and use it in GitHub Desktop.
KOReader user patch: Thin status bar with chapter markers
local BD = require("ui/bidi")
local Blitbuffer = require("ffi/blitbuffer")
local Device = require("device")
local Geom = require("ui/geometry")
local ProgressWidget = require("ui/widget/progresswidget")
local Math = require("optmath")
local ReaderFooter = require("apps/reader/modules/readerfooter")
local ReaderToc = require("apps/reader/modules/readertoc")
local T = require("ffi/util").template
local _ = require("gettext")
local Screen = Device.screen
local userpatch = require("userpatch")
local initial_marker_height_threshold = userpatch.getUpValue(ProgressWidget.paintTo, "INITIAL_MARKER_HEIGHT_THRESHOLD")
local logger = require("logger")
ProgressWidget.paintTo = function(self, bb, x, y)
local my_size = self:getSize()
-- same bar height if thin_ticks, need extra space for the taller markers
local dy = self.thin_ticks and (Screen:scaleBySize(1) / 2) or 0
my_size.h = my_size.h + dy * 2
if not self.dimen then
self.dimen = Geom:new{
x = x, y = y,
w = my_size.w,
h = my_size.h
}
else
self.dimen.x = x
self.dimen.y = y
self.dimen.w = my_size.w
self.dimen.h = my_size.h
end
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 + dy, my_size.w, my_size.h - dy * 2, self.bordercolor)
-- And a full background bar inside (i.e., on top) of that.
bb:paintRect(x + self.margin_h + self.bordersize,
fill_y + dy,
math.ceil(fill_width),
math.ceil(fill_height) - dy * 2,
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(math.floor(x), math.floor(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 + dy,
width,
math.ceil(fill_height) - dy * 2,
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 + dy,
math.ceil(fill_width * self.percentage),
math.ceil(fill_height) - dy * 2,
self.fillcolor)
-- Overlay the initial position marker on top of that
if self.initial_pos_marker and self.initial_percentage >= 0 then
if self.height <= initial_marker_height_threshold then
self.initial_pos_icon:paintTo(bb, Math.round(fill_x + math.ceil(fill_width * self.initial_percentage) - self.height / 4), y - Math.round(self.height / 6))
else
self.initial_pos_icon:paintTo(bb, Math.round(fill_x + math.ceil(fill_width * self.initial_percentage) - self.height / 2), y)
end
end
end
-- ...then the tick(s).
if self.ticks and self.last and self.last > 0 then
local filled = math.floor(fill_width * self.percentage)
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)
-- color depend on the tick placment: white if it's read, black if after
local color = (self.thin_ticks and (tick_x < filled)) and Blitbuffer.COLOR_WHITE or self.bordercolor
bb:paintRect(x + self.margin_h + self.bordersize + tick_x,
fill_y,
self.tick_width,
math.ceil(fill_height),
color)
end
end
end
local orig_ReaderFooter_setTocMarkers = ReaderFooter.setTocMarkers
local orig_ReaderToc_getTocTicks = ReaderToc.getTocTicks
local was_thin_ticks
ReaderFooter.setTocMarkers = function(self, reset)
self.progress_bar.thin_ticks = self.settings.progress_style_thin and self.settings.toc_markers -- check ProgressWidget.paintTo
local force_reset = false
if self.progress_bar.thin_ticks ~= was_thin_ticks then
self.ui.toc.ticks_flattened = nil
force_reset = true
end
was_thin_ticks = self.progress_bar.thin_ticks
if self.progress_bar.thin_ticks then -- force TOC to level 1 to avoid cluttering the status bar
ReaderToc.getTocTicks = function(self, level) return { orig_ReaderToc_getTocTicks(self, 1) } end
end
local save_thin_setting = self.settings.progress_style_thin
self.settings.progress_style_thin = false -- prevent premature exit
orig_ReaderFooter_setTocMarkers(self, reset or force_reset)
self.settings.progress_style_thin = save_thin_setting
ReaderToc.getTocTicks = orig_ReaderToc_getTocTicks
end
local function patch_menu_item(attrib_name, replacement, menu, ...)
local function find_sub_item(sub_items, text)
local find_text
if type(text) == "table" then
local set = {} for _, t in ipairs(text) do set[t] = true end
find_text = function(a_text) return set[a_text] end
else
find_text = function(a_text) return a_text == text end
end
for _, item in ipairs(sub_items) do
local item_text = item.text or (item.text_func and item.text_func())
if item_text and find_text(item_text) then
-- logger.info("Found item", item_text)
return item
end
end
end
local function find_item_from_path(menu, path)
local sub_items, item
for _, text in ipairs(path) do
sub_items = (item or menu).sub_item_table
if not sub_items then return end
item = find_sub_item(sub_items, text)
if not item then return end
end
return item
end
local item = find_item_from_path(menu, {...})
if item and item[attrib_name] then
item[attrib_name] = replacement
local path = {...} for i, t in ipairs(path) do if type(t)=="table" then path[i] = table.concat(t, " | ") end end
logger.info("Patch", attrib_name, "in '", table.concat(path," > "),"'")
end
end
local orig_ReaderFooter_addToMainMenu = ReaderFooter.addToMainMenu
ReaderFooter.addToMainMenu = function(self, menu_items)
orig_ReaderFooter_addToMainMenu(self, menu_items)
patch_menu_item(
"callback",
function()
self.settings.progress_style_thin = true
local bar_height = self.settings.progress_style_thin_height
self.progress_bar:updateStyle(false, bar_height)
self:setTocMarkers()
self:refreshFooter(true, true)
end,
menu_items.status_bar,
_("Progress bar"),
{_("Thickness and height: thin"), _("Thickness and height: thick")},
_("Thin")
)
patch_menu_item(
"enabled_func",
function()
return not self.settings.chapter_progress_bar and not self.settings.disable_progress_bar
end,
menu_items.status_bar,
_("Progress bar"),
_("Show chapter markers")
)
patch_menu_item(
"enabled_func",
function()
return not self.settings.chapter_progress_bar and self.settings.toc_markers and not self.settings.disable_progress_bar
end,
menu_items.status_bar,
_("Progress bar"),
T(_("Chapter marker width: %1"), self:genProgressBarChapterMarkerWidthMenuItems())
)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment