mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 08:21:39 -06:00
e28f45452d
Layout breakpoints use the screen width at 1x, so Interface size scales text, icons and spacing without dropping Home columns or detail panes. Every page, sheet and widget picks a layout that fits instead of cutting text or leaving gaps; search becomes balanced list-row sections, Edit Home keeps hidden widgets, stock KOReader messages become BookOrbit toasts, and the plugin is 2.1.0.
213 lines
7.1 KiB
Lua
213 lines
7.1 KiB
Lua
--[[--
|
|
Shared geometry for the widget dashboard.
|
|
|
|
Every size on the board is given in density-independent units (dp) and
|
|
converted here, so the board scales like the rest of KOReader
|
|
(Screen:scaleBySize, short side / 600) plus one density step: a 10" panel
|
|
shows more content instead of the 6" page blown up, and a phone gets
|
|
finger-sized type. The reader's interface-size preference is applied after
|
|
that device adjustment. Font sizes go through the same scale, so text and
|
|
boxes stay in proportion.
|
|
|
|
Line heights mirror the widgets that draw them: a single-line TextWidget is
|
|
ceil(face height) tall, where Noto's face height is 1.362 em, and a
|
|
TextBoxWidget line is round(1.3 em).
|
|
]]
|
|
|
|
local Blitbuffer = require("ffi/blitbuffer")
|
|
local Device = require("device")
|
|
local Font = require("ui/font")
|
|
|
|
local Screen = Device.screen
|
|
|
|
local BoardMetrics = {}
|
|
|
|
BoardMetrics.UI_SCALES = { 1, 1.05, 1.1, 1.15, 1.2 }
|
|
BoardMetrics.DEFAULT_UI_SCALE = 1.05
|
|
|
|
local FACE_HEIGHT_EM = 1.362
|
|
local FACES = {
|
|
sans = "NotoSans-Regular.ttf",
|
|
sans_italic = "NotoSans-Italic.ttf",
|
|
serif = "NotoSerif-Regular.ttf",
|
|
serif_italic = "NotoSerif-Italic.ttf",
|
|
}
|
|
|
|
-- The sixteen gray levels e-ink panels can show, by name.
|
|
BoardMetrics.INK = {
|
|
black = Blitbuffer.COLOR_BLACK,
|
|
g1 = Blitbuffer.COLOR_GRAY_1,
|
|
g2 = Blitbuffer.COLOR_GRAY_2,
|
|
g3 = Blitbuffer.COLOR_GRAY_3,
|
|
g4 = Blitbuffer.COLOR_GRAY_4,
|
|
g5 = Blitbuffer.COLOR_GRAY_5,
|
|
g6 = Blitbuffer.COLOR_GRAY_6,
|
|
g7 = Blitbuffer.COLOR_GRAY_7,
|
|
g8 = Blitbuffer.COLOR_DARK_GRAY,
|
|
g9 = Blitbuffer.COLOR_GRAY_9,
|
|
ga = Blitbuffer.COLOR_GRAY,
|
|
gb = Blitbuffer.COLOR_GRAY_B,
|
|
gc = Blitbuffer.COLOR_LIGHT_GRAY,
|
|
gd = Blitbuffer.COLOR_GRAY_D,
|
|
ge = Blitbuffer.COLOR_GRAY_E,
|
|
white = Blitbuffer.COLOR_WHITE,
|
|
}
|
|
|
|
-- Hex values of the same grays, for the SVG icons drawn in them.
|
|
BoardMetrics.INK_HEX = {
|
|
black = "#000000", g1 = "#111111", g2 = "#222222", g3 = "#333333", g4 = "#444444",
|
|
g5 = "#555555", g6 = "#666666", g7 = "#777777", g8 = "#888888", g9 = "#999999",
|
|
ga = "#aaaaaa", gb = "#bbbbbb", gc = "#cccccc", gd = "#dddddd", ge = "#eeeeee", white = "#ffffff",
|
|
}
|
|
|
|
local cache = nil
|
|
local user_scale = BoardMetrics.DEFAULT_UI_SCALE
|
|
|
|
local function clamp(v, lo, hi)
|
|
if v < lo then return lo end
|
|
if v > hi then return hi end
|
|
return v
|
|
end
|
|
|
|
function BoardMetrics.normalizeUserScale(value)
|
|
local numeric = tonumber(value)
|
|
if not numeric then return BoardMetrics.DEFAULT_UI_SCALE end
|
|
local rounded = math.floor(numeric * 100 + 0.5) / 100
|
|
if rounded == 1.25 then return 1.2 end
|
|
for _, scale in ipairs(BoardMetrics.UI_SCALES) do
|
|
if rounded == scale then return scale end
|
|
end
|
|
return BoardMetrics.DEFAULT_UI_SCALE
|
|
end
|
|
|
|
function BoardMetrics.setUserScale(value)
|
|
user_scale = BoardMetrics.normalizeUserScale(value)
|
|
cache = nil
|
|
return user_scale
|
|
end
|
|
|
|
-- The density step: sqrt of how much shorter or longer the panel's short side
|
|
-- is than a 6" one (3.6"), kept gentle and within [0.78, 1.12].
|
|
function BoardMetrics.densityAdjust(short_px, dpi)
|
|
if not short_px or not dpi or dpi <= 0 then return 1 end
|
|
local inches = short_px / dpi
|
|
local adj = clamp(math.sqrt(3.6 / inches), 0.78, 1.12)
|
|
return math.floor(adj * 100 + 0.5) / 100
|
|
end
|
|
|
|
-- Recomputed when the screen geometry changes (rotation, DPI override).
|
|
function BoardMetrics.refresh()
|
|
local w, h = Screen:getWidth(), Screen:getHeight()
|
|
local short = math.min(w, h)
|
|
local adj = BoardMetrics.densityAdjust(short, Screen:getDPI())
|
|
-- Screen:scaleBySize's own scale: the short side over 600, averaged with
|
|
-- DPI / 160 under a user DPI override. Computed here rather than read
|
|
-- back from scaleBySize(1e6), which rounds up at the sixth decimal and
|
|
-- turns an exact 600 dp screen into 599.9994.
|
|
local size_scale = short / 600
|
|
local dpi_scale = Screen.dpi_override and (Screen:getDPI() / 160) or size_scale
|
|
local base = (size_scale + dpi_scale) / 2
|
|
cache = {
|
|
w = w,
|
|
h = h,
|
|
adj = adj,
|
|
scale = user_scale,
|
|
dp = base * adj * user_scale,
|
|
layout_dp = base * adj,
|
|
}
|
|
cache.W = w / cache.dp
|
|
cache.H = h / cache.dp
|
|
-- The screen at interface size 1. Layout choices (columns, margins, one
|
|
-- or two column pages) are made on these, so a larger interface size
|
|
-- draws the device's own layout bigger instead of a smaller device's.
|
|
cache.LW = w / cache.layout_dp
|
|
cache.LH = h / cache.layout_dp
|
|
return cache
|
|
end
|
|
|
|
-- The screen width layout choices are made on, rounded so an exact 600 dp
|
|
-- screen is not 599.9994.
|
|
function BoardMetrics.layoutW()
|
|
return math.floor(BoardMetrics.get().LW * 1000 + 0.5) / 1000
|
|
end
|
|
|
|
-- A pixel width in layout dp, for comparing a box against a layout
|
|
-- breakpoint.
|
|
function BoardMetrics.layoutDp(w_px)
|
|
return w_px / BoardMetrics.get().layout_dp
|
|
end
|
|
|
|
-- dp to pixels at interface size 1, for layout limits that must not move
|
|
-- with the interface size.
|
|
function BoardMetrics.lpx(n)
|
|
if not n or n <= 0 then return 0 end
|
|
return math.ceil(n * BoardMetrics.get().layout_dp - 1e-7)
|
|
end
|
|
|
|
function BoardMetrics.get()
|
|
if not cache or cache.w ~= Screen:getWidth() or cache.h ~= Screen:getHeight() then
|
|
BoardMetrics.refresh()
|
|
end
|
|
return cache
|
|
end
|
|
|
|
-- dp to device pixels. Rounded up so a positive size never collapses to 0.
|
|
function BoardMetrics.px(n)
|
|
if not n or n <= 0 then return 0 end
|
|
return math.ceil(n * BoardMetrics.get().dp - 1e-7)
|
|
end
|
|
|
|
-- The pixel size of a font of nominal size n.
|
|
function BoardMetrics.fs(n)
|
|
return BoardMetrics.px(n)
|
|
end
|
|
|
|
-- KOReader applies Screen:scaleBySize inside Font:getFace, so named and file
|
|
-- faces receive only BookOrbit's density and interface-size multipliers.
|
|
function BoardMetrics.fontSize(n)
|
|
local metrics = BoardMetrics.get()
|
|
return n * metrics.adj * metrics.scale
|
|
end
|
|
|
|
function BoardMetrics.namedFace(name, n)
|
|
return Font:getFace(name, BoardMetrics.fontSize(n))
|
|
end
|
|
|
|
function BoardMetrics.fileFace(file, n)
|
|
return Font:getFace(file, BoardMetrics.fontSize(n))
|
|
end
|
|
|
|
-- Single-line text height (TextWidget) for nominal size n.
|
|
function BoardMetrics.lh1(n)
|
|
return math.ceil(FACE_HEIGHT_EM * BoardMetrics.fs(n) - 1e-7)
|
|
end
|
|
|
|
-- Line height of a TextBoxWidget for nominal size n.
|
|
function BoardMetrics.lh(n)
|
|
return math.floor(1.3 * BoardMetrics.fs(n) + 0.5)
|
|
end
|
|
|
|
-- Height of an n-line TextBoxWidget of nominal size: lines of round(1.3 em)
|
|
-- plus the amount Noto's 1.362 em glyph box overflows one line, which
|
|
-- TextBoxWidget adds below the last line. One line is a TextWidget instead.
|
|
function BoardMetrics.textBoxHeight(lines, n)
|
|
if lines <= 1 then return BoardMetrics.lh1(n) end
|
|
local line = BoardMetrics.lh(n)
|
|
local face = FACE_HEIGHT_EM * BoardMetrics.fs(n)
|
|
return lines * line + math.max(0, -math.floor(line - face))
|
|
end
|
|
|
|
-- A face at nominal size n. Font:getFace applies scaleBySize itself, so it is
|
|
-- given the size with only the density step applied.
|
|
function BoardMetrics.face(n, kind)
|
|
local file = FACES[kind or "sans"] or FACES.sans
|
|
return BoardMetrics.fileFace(file, n)
|
|
end
|
|
|
|
-- Rebuilds without a screen, for specs.
|
|
function BoardMetrics._setForTest(values)
|
|
cache = values
|
|
end
|
|
|
|
return BoardMetrics
|