Ctrl K
ComunidadGuías…

koreader-plugin/bookorbit.koplugin/bookorbit_board_layout.lua

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
--[[--
Pure layout for the widget dashboard: the grid a device gets, the dense
packing of a page's widgets, and how a page fits the screen.

A board is a grid of 2, 4 or 6 columns (by width in dp). Its row height
stretches between MIN_ROW and MAX_ROW so the page fills the screen exactly;
a page with too many rows drops widgets from its end, and a page with room to
spare takes optional extras while rows stay comfortable. Nothing here touches
widgets, so the whole fit can be unit tested.
]]

local BoardLayout = {}

BoardLayout.MIN_ROW = 84
BoardLayout.COMFY_ROW = 104
BoardLayout.MAX_ROW = 150
BoardLayout.GAP = 10
BoardLayout.TOP = 6
BoardLayout.HEADER = 64
BoardLayout.HEADER_GAP = 8
BoardLayout.FOOTER = 40
BoardLayout.FOOTER_GAP = 8
BoardLayout.BOTTOM = 8

-- Size codes: S 1x1, M 2x1, L 2x2, W full width x1, XL full width x2. On a
-- two-column board W is M and XL is L.
function BoardLayout.sizeDims(code, cols)
    if code == "S" then return 1, 1 end
    if code == "M" then return math.min(2, cols), 1 end
    if code == "L" then return math.min(2, cols), 2 end
    if code == "W" then return cols, 1 end
    return cols, 2
end

function BoardLayout.editBarTop(tile_y, tile_h, bar_h, gap, available_h)
    local below = tile_y + tile_h + gap
    if below + bar_h <= available_h then return below end
    return math.max(0, tile_y - bar_h - gap)
end

function BoardLayout.columnsFor(width_dp)
    if width_dp >= 900 then return 6 end
    if width_dp >= 560 then return 4 end
    return 2
end

function BoardLayout.marginFor(width_dp)
    if width_dp >= 900 then return 28 end
    if width_dp >= 700 then return 24 end
    if width_dp >= 560 then return 20 end
    return 16
end

-- px: the dp-to-pixel function. Returns everything in pixels. width_dp is
-- the screen's width at interface size 1, so a page keeps its columns at
-- every interface size.
function BoardLayout.geometry(px, screen_w, screen_h, width_dp)
    local g = {}
    g.mx = px(BoardLayout.marginFor(width_dp))
    g.w = screen_w - 2 * g.mx
    g.cols = BoardLayout.columnsFor(width_dp)
    g.gap = px(BoardLayout.GAP)
    g.col_w = math.floor((g.w - (g.cols - 1) * g.gap) / g.cols)
    g.top = px(BoardLayout.TOP)
    g.header_h = px(BoardLayout.HEADER)
    g.header_gap = px(BoardLayout.HEADER_GAP)
    g.footer_h = px(BoardLayout.FOOTER)
    g.footer_gap = px(BoardLayout.FOOTER_GAP)
    g.bottom = px(BoardLayout.BOTTOM)
    g.avail = screen_h - g.top - g.header_h - g.header_gap - g.footer_gap - g.footer_h - g.bottom
    g.min_row = px(BoardLayout.MIN_ROW)
    g.comfy_row = px(BoardLayout.COMFY_ROW)
    g.max_row = px(BoardLayout.MAX_ROW)
    return g
end

-- Width of a tile spanning cw columns starting at column x (0-based). The
-- last column absorbs the rounding remainder so the grid ends on the margin.
function BoardLayout.spanWidth(g, x, cw)
    if x + cw == g.cols then
        return g.w - x * (g.col_w + g.gap)
    end
    return cw * g.col_w + (cw - 1) * g.gap
end

function BoardLayout.cellX(g, x)
    return x * (g.col_w + g.gap)
end

-- First-fit dense packing, row-major. Returns placed copies (with x, y), the
-- number of rows used and the number of empty cells within those rows.
function BoardLayout.pack(items, cols)
    local occ = {}
    local function free(r, c, w, h)
        for y = r, r + h - 1 do
            local line = occ[y]
            if line then
                for x = c, c + w - 1 do
                    if line[x] then return false end
                end
            end
        end
        return true
    end
    local rows = 0
    local placed = {}
    for _i, it in ipairs(items) do
        local cw = math.min(it.cw, cols)
        local done = false
        local r = 0
        while not done do
            for x = 0, cols - cw do
                if free(r, x, cw, it.rh) then
                    for y = r, r + it.rh - 1 do
                        occ[y] = occ[y] or {}
                        for xx = x, x + cw - 1 do occ[y][xx] = true end
                    end
                    rows = math.max(rows, r + it.rh)
                    local p = {}
                    for k, v in pairs(it) do p[k] = v end
                    p.cw, p.x, p.y = cw, x, r
                    table.insert(placed, p)
                    done = true
                    break
                end
            end
            r = r + 1
        end
    end
    local holes = 0
    for y = 0, rows - 1 do
        for x = 0, cols - 1 do
            if not (occ[y] and occ[y][x]) then holes = holes + 1 end
        end
    end
    return { placed = placed, rows = rows, holes = holes }
end

function BoardLayout.rowHeight(g, rows)
    if rows <= 0 then return g.max_row end
    return math.floor((g.avail - (rows - 1) * g.gap) / rows)
end

-- Fits one page: drops trailing widgets until rows are at least MIN_ROW, then
-- adds extras that cost no row (or keep rows comfortable) and leave no new
-- holes. Returns the packing plus the row height to draw it at, and the
-- widgets dropped for room. The first widget always stays: two rows at
-- MIN_ROW fit every screen.
function BoardLayout.fitPage(g, base, extras)
    local items = {}
    for _i, it in ipairs(base or {}) do table.insert(items, it) end
    local dropped = {}
    while #items > 1 and BoardLayout.rowHeight(g, BoardLayout.pack(items, g.cols).rows) < g.min_row do
        table.insert(dropped, 1, table.remove(items))
    end
    for _i, ex in ipairs(extras or {}) do
        local before = BoardLayout.pack(items, g.cols)
        local trial = {}
        for _i, it in ipairs(items) do table.insert(trial, it) end
        table.insert(trial, ex)
        local after = BoardLayout.pack(trial, g.cols)
        if (after.rows == before.rows or BoardLayout.rowHeight(g, after.rows) >= g.comfy_row)
                and after.holes <= before.holes then
            items = trial
        end
    end
    local packed = BoardLayout.pack(items, g.cols)
    packed.row_h = math.min(g.max_row, BoardLayout.rowHeight(g, packed.rows))
    packed.grid_h = packed.rows * packed.row_h + math.max(0, packed.rows - 1) * g.gap
    packed.dropped = dropped
    return packed
end

return BoardLayout