Skip to content

Instantly share code, notes, and snippets.

@sebdelsol
Last active December 20, 2025 19:39
Show Gist options
  • Select an option

  • Save sebdelsol/16ab741b44b75a46cbceb133163bbab5 to your computer and use it in GitHub Desktop.

Select an option

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 Font = require("ui/font")
local Geom = require("ui/geometry")
local ProgressWidget = require("ui/widget/progresswidget")
local SpinWidget = require("ui/widget/spinwidget")
local TextWidget = require("ui/widget/textwidget")
local UIManager = require("ui/uimanager")
local Math = require("optmath")
local ReaderFooter = require("apps/reader/modules/readerfooter")
local T = require("ffi/util").template
local _ = require("gettext")
local Screen = Device.screen
-- Somewhat empirically chosen threshold to switch between the two designs ;o)
local INITIAL_MARKER_HEIGHT_THRESHOLD = Screen:scaleBySize(12)
ProgressWidget.paintTo = function(self, bb, x, y)
-- TODO Overlay the initial position marker on top of that
local my_size = self:getSize()
-- same bar height if special, need extra space for the taller markers
local dy = self.special_tick and math.floor(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
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 not self.special_tick then -- special doesn't need a border
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(math.floor(x), math.floor(y),
my_size.w, my_size.h,
self.bordersize, self.bordercolor, self.radius)
end
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
elseif self.special_tick then -- paint in case missing
bb:paintRect(x + self.margin_h + self.bordersize,
fill_y + dy,
fill_width,
math.ceil(fill_height) - dy * 2,
self.altcolor)
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.special_tick 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
ReaderFooter.setTocMarkers = function(self, reset)
local save_thin_setting = self.settings.progress_style_thin
local save_getTocTicksFlattened = self.ui.toc.getTocTicksFlattened
self.settings.progress_style_thin = false -- prevent premature exit
self.ui.toc.getTocTicksFlattened = function(self, for_chapter_navigation) return self.ui.toc.getTocTicks(self, 1) end -- TOC only at level 1
orig_ReaderFooter_setTocMarkers(self, reset)
self.settings.progress_style_thin = save_thin_setting
self.ui.toc.getTocTicksFlattened = save_getTocTicksFlattened
self.progress_bar.special_tick = self.settings.progress_style_thin and self.settings.toc_markers -- check ProgressWidget.paintTo
end
local orig_ReaderFooter_addToMainMenu = ReaderFooter.addToMainMenu
local replace_item = function(attrib_name, replacement, menu, ...)
local logger = require("logger")
local find_sub_item = function(sub_items, text_to_find)
local is_text
if type(text_to_find) == "table" then
local set = {} for _, text in ipairs(text_to_find) do set[text] = true end
is_text = function(text) return set[text] end
else
is_text = function(text) return text == text_to_find 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 is_text(item_text) then
-- logger.info("Found item", item_text)
return item
end
end
end
local find_deep_item = function(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_deep_item(menu, {...})
if item 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
ReaderFooter.addToMainMenu = function(self, menu_items)
orig_ReaderFooter_addToMainMenu(self, menu_items)
replace_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")
)
replace_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")
)
replace_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
@sebdelsol
Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment