lua print table put_line a do_menu alternative

add stylized lines to lua

the exported do_menu has a severe limitation of 64 items
it also requires double the memory

put_line is the way rockbox builds menus

update printtable

user config from core -- done
code cleanup
fixed for 1-bit screens
changed button behavior
fixed for 2-bit screens

Change-Id: I4de55e42685aa1d2f53a33bc8e980827864e810b
This commit is contained in:
William Wilgus 2021-04-09 23:11:31 -04:00 committed by William Wilgus
parent c71a47f649
commit acda37edd1
13 changed files with 582 additions and 164 deletions

View file

@ -123,6 +123,49 @@ local _print = {} do
return w, h, msg
end
--------------------------------------------------------------------------------
local function set_linedesc(t_linedesc, opts)
local o = opts or _print.opt.get(true)
--local out = function() local t = {} for k, v in pairs(o) do t[#t + 1] = tostring(k) t[#t + 1] = tostring(v) end return table.concat(t, "\n") end
--rb.splash_scroller(1000, out())
local linedesc ={
--These are the defaults - changes will be made below if you supplied t_linedesc
indent = 0, -- internal indent text
line = 0, -- line index within group
nlines = 1, -- selection grouping
offset = 0, -- internal item offset
scroll = true,
selected = false, --internal
separator_height = 0,
line_separator = false,
show_cursor = false,
show_icons = false,
icon = -1,
icon_fn = function() return -1 end,
style = rb.STYLE_COLORBAR,
text_color = o.fg_pattern or WHITE,
line_color = o.bg_pattern or BLACK,
line_end_color= o.bg_pattern or BLACK,
}
if type(t_linedesc) == "table" then
if not o.linedesc then
o.linedesc = {}
for k, v in pairs(linedesc) do
o.linedesc[k] = v
end
end
for k, v in pairs(t_linedesc) do
o.linedesc[k] = v
end
if o.linedesc.separator_height > 0 then
o.linedesc.line_separator = true
end
return
end
o.linedesc = linedesc
return o.linedesc
end
-- set defaults for print view
local function set_defaults()
@ -138,11 +181,13 @@ local _print = {} do
line = 1,
max_line = _NIL,
col = 0,
ovfl = "auto",
justify = "left",
autoupdate = true,
drawsep = false,
header = false, --Internal use - treats first entry as header
ovfl = "auto", -- auto, manual, none
justify = "left", --left, center, right
autoupdate = true, --updates screen as items are added
drawsep = false, -- separator between items
}
set_linedesc(nil, _p_opts) -- default line display
_p_opts.max_line = max_lines(_p_opts)
s_lines, col_buf = {}, {}
@ -187,29 +232,16 @@ local _print = {} do
-- helper function sets up colors/marker for selected items
local function show_selected(iLine, msg)
local o = get_settings() -- using a copy of opts so changes revert
if s_lines[iLine] == true then
if not rb.lcd_set_background then
o.drawmode = bit.bxor(o.drawmode, 4)
else
o.fg_pattern = o.bg_pattern
o.bg_pattern = o.sel_pattern
end
-- alternative selection method
--msg = "> " .. msg
end
if not o then rb.set_viewport() return end
if rb.LCD_DEPTH == 2 then -- invert 2-bit screens
local o = get_settings() -- using a copy of opts so changes revert
if not o then rb.set_viewport() return end
o.fg_pattern = 3 - o.fg_pattern
o.bg_pattern = 3 - o.bg_pattern
rb.set_viewport(o)
o = _NIL
else
show_selected = function() end -- no need to check again
end
rb.set_viewport(o)
o = _NIL
end
-- sets line explicitly or increments line if line is _NIL
@ -274,31 +306,82 @@ local _print = {} do
s_lines[iLine] = true
end
-- Internal print function
-- Internal print function
local function print_internal(t_opts, x, w, h, msg)
local linedesc
local line_separator = false
local o = t_opts
if o.justify == "center" then
x = x + (o.width - w) / 2
elseif o.justify == "right" then
x = x + (o.width - w)
local ld = o.linedesc or set_linedesc()
local show_cursor = ld.show_cursor or 0
local line_indent = 0
local linestyle = ld.style or rb.STYLE_COLORBAR
if o.justify ~= "left" then
line_indent = (o.width - w) --"right"
if o.justify == "center" then
line_indent = line_indent / 2
end
end
local line = o.line - 1 -- rb is 0-based lua is 1-based
if(o.ovfl == "auto" and w >= o.width) then -- -o.x
rb.lcd_puts_scroll(0, line, msg)
else
rb.lcd_putsxy(x, line * h, msg)
if o.ovfl == "manual" then --save msg for later side scroll
col_buf_insert(msg, o.line, o)
end
if o.ovfl == "manual" then --save msg for later side scroll
col_buf_insert(msg, o.line, o)
end
if o.drawsep == true then
if s_lines[o.line] == true then
rb.set_viewport(o) --nned to revert drawmode if selected
-- bit of a pain to set the fields this way but its much more efficient than iterating a table to set them
local function set_desc(tld, scroll, separator_height, selected, style, indent, text_color, line_color, line_end_color)
tld.scroll = scroll
tld.separator_height = separator_height
tld.selected = selected
tld.style = style
tld.indent = indent
tld.text_color = text_color
tld.line_color = line_color
tld.line_end_color = line_end_color
end
line_separator = ld.line_separator
if o.line == 1 and o.header then
--rb scroller doesn't like negative offset!
local indent = line_indent < 0 and 0 or line_indent
set_desc(ld, true, 1, false, rb.STYLE_DEFAULT,
indent, o.fg_pattern, o.bg_pattern, o.bg_pattern)
ld.show_cursor = false
elseif s_lines[o.line] then
--/* Display line selector */
local style = show_cursor == true and rb.STYLE_DEFAULT or linestyle
local indent = line_indent < 0 and 0 or line_indent
--rb scroller doesn't like negative offset!
local ovfl = (o.ovfl == "auto" and w >= o.width and x == 0)
set_desc(ld, ovfl, 0, true, style, indent,
o.bg_pattern, o.sel_pattern, o.sel_pattern)
else
set_desc(ld, false, 0, false, rb.STYLE_DEFAULT,line_indent,
o.fg_pattern, o.bg_pattern, o.bg_pattern)
end
if ld.show_icons then
ld.icon = ld.icon_fn(line, ld.icon or -1)
end
rb.lcd_put_line(x, line *h, msg, ld)
ld.show_cursor = show_cursor
ld.style = linestyle
if line_separator then
if ld.selected == true then
rb.set_viewport(o) -- revert drawmode if selected
end
rb.lcd_drawline(0, line * h, o.width, line * h)
rb.lcd_drawline(0, line * h + h, o.width, line * h + h) --only to add the last line
-- but we don't have an idea which line is the last line here so every line is the last line!
end
--only update the line we changed
update_line(o.autoupdate, o, line, h)
@ -308,7 +391,7 @@ local _print = {} do
-- Helper function that acts mostly like a normal printf() would
local function printf(fmt, v1, ...)
local o = get_settings(true)
local w, h, msg
local w, h, msg, rep
local line = o.line - 1 -- rb is 0-based lua is 1-based
if not (fmt) or (fmt) == "\n" then -- handles blank line / single '\n'
@ -322,6 +405,9 @@ local _print = {} do
return o.line, o.max_line, o.width, h
end
fmt, rep = fmt.gsub(fmt or "", "%%h", "%%s")
o.header = (rep == 1)
msg = string.format(fmt, v1, ...)
show_selected(o.line, msg)
@ -337,6 +423,8 @@ local _print = {} do
local function set_column(x)
local o = get_settings()
if o.ovfl ~= "manual" then return end -- no buffer stored to scroll
rb.lcd_scroll_stop()
local res, w, h, str, line
for key, value in pairs(col_buf) do
@ -367,6 +455,7 @@ local _print = {} do
_print.opt.justify = set_justify
_print.opt.sel_line = select_line
_print.opt.line = set_line
_print.opt.linedesc = set_linedesc
_print.opt.autoupdate = set_update
_print.clear = clear
_print.f = printf

View file

@ -0,0 +1,249 @@
--[[
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2017 William Wilgus
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
]]
if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
require("printtable")
local _clr = require("color")
local _LCD = rb.lcd_framebuffer()
--[[ -- dpad requires:
require("actions") -- Contains rb.actions & rb.contexts
local _timer = require("timer")
-- Button definitions --
local CANCEL_BUTTON = rb.actions.PLA_CANCEL
local DOWN_BUTTON = rb.actions.PLA_DOWN
local DOWNR_BUTTON = rb.actions.PLA_DOWN_REPEAT
local EXIT_BUTTON = rb.actions.PLA_EXIT
local LEFT_BUTTON = rb.actions.PLA_LEFT
local LEFTR_BUTTON = rb.actions.PLA_LEFT_REPEAT
local RIGHT_BUTTON = rb.actions.PLA_RIGHT
local RIGHTR_BUTTON = rb.actions.PLA_RIGHT_REPEAT
local SEL_BUTTON = rb.actions.PLA_SELECT
local SELREL_BUTTON = rb.actions.PLA_SELECT_REL
local SELR_BUTTON = rb.actions.PLA_SELECT_REPEAT
local UP_BUTTON = rb.actions.PLA_UP
local UPR_BUTTON = rb.actions.PLA_UP_REPEAT
]]
--------------------------------------------------------------------------------
local function get_core_settings()
if rb.core_color_table ~= nil and rb.core_talk_table ~= nil and
rb.core_list_settings_table ~= nil then return end
local rbs_is_loaded = (package.loaded.rbsettings ~= nil)
local s_is_loaded = (package.loaded.settings ~= nil)
require("rbsettings")
require("settings")
rb.metadata = nil -- remove track metadata settings
local rb_settings = rb.settings.dump('global_settings', "system")
local color_table = {}
local talk_table = {}
local list_settings_table = {}
local list_settings = "cursor_style|show_icons|statusbar|scrollbar|scrollbar_width|list_separator_height|backdrop_file|"
for key, value in pairs(rb_settings) do
key = key or ""
if (key:find("color")) then
color_table[key]=value
elseif (key:find("talk")) then
talk_table[key]=value
elseif (list_settings:find(key)) then
list_settings_table[key]=value
end
end
if not s_is_loaded then
rb.settings = nil
package.loaded.settings = nil
end
if not rbs_is_loaded then
rb.system = nil
rb.metadata = nil
package.loaded.rbsettings = nil
end
rb.core_color_table = color_table
rb.core_talk_table = talk_table
rb.core_list_settings_table = list_settings_table
collectgarbage("collect")
end
--[[ cursor style button routine
-- left / right are x, xi is increment xir is increment when repeat
-- up / down are y, yi is increment yir is increment when repeat
-- cancel is returned as 0,1
-- select as 0, 1, 2, 3 (none, pressed, repeat, relesed)
-- x_chg and y_chg are the amount x or y changed
-- timeout == nil or -1 loop waits indefinitely till button is pressed
-- time since last button press is returned in ticks..
-- make xi, xir, yi, yir negative to flip direction...
]]
--[[
local function dpad(x, xi, xir, y, yi, yir, timeout, overflow)
local scroll_is_fixed = overflow ~= "manual"
_timer("dpad") -- start a persistant timer; keeps time between button events
if timeout == nil then timeout = -1 end
local cancel, select = 0, 0
local x_chg, y_chg = 0, 0
local button
while true do
button = rb.get_plugin_action(timeout)
if button == CANCEL_BUTTON then
cancel = 1
break;
elseif button == EXIT_BUTTON then
cancel = 1
break;
elseif button == SEL_BUTTON then
select = 1
timeout = timeout + 1
elseif button == SELR_BUTTON then
select = 2
timeout = timeout + 1
elseif button == SELREL_BUTTON then
select = -1
timeout = timeout + 1
elseif button == LEFT_BUTTON then
x_chg = x_chg - xi
if scroll_is_fixed then
cancel = 1
break;
end
elseif button == LEFTR_BUTTON then
x_chg = x_chg - xir
elseif button == RIGHT_BUTTON then
x_chg = x_chg + xi
if scroll_is_fixed then
select = 1
timeout = timeout + 1
end
elseif button == RIGHTR_BUTTON then
x_chg = x_chg + xir
elseif button == UP_BUTTON then
y_chg = y_chg + yi
elseif button == UPR_BUTTON then
y_chg = y_chg + yir
elseif button == DOWN_BUTTON then
y_chg = y_chg - yi
elseif button == DOWNR_BUTTON then
y_chg = y_chg - yir
elseif timeout >= 0 then--and rb.button_queue_count() < 1 then
break;
end
if x_chg ~= 0 or y_chg ~= 0 then
timeout = timeout + 1
end
end
x = x + x_chg
y = y + y_chg
return cancel, select, x_chg, x, y_chg, y, _timer.check("dpad", true)
end -- dpad
]]
--------------------------------------------------------------------------------
-- displays text in menu_t calls function in same indice of func_t when selected
function print_menu(menu_t, func_t, selected, settings, copy_screen)
local i, start, vcur, screen_img
if selected then vcur = selected + 1 end
if vcur and vcur <= 1 then vcur = 2 end
get_core_settings()
local c_table = rb.core_color_table or {}
if not settings then
settings = {}
settings.default = true
end
settings.justify = settings.justify or "center"
settings.wrap = settings.wrap or true
settings.hfgc = settings.hfgc or c_table.lst_color or _clr.set( 0, 000, 000, 000)
settings.hbgc = settings.hbgc or c_table.bg_color or _clr.set(-1, 255, 255, 255)
settings.ifgc = settings.ifgc or c_table.fg_color or _clr.set(-1, 000, 255, 060)
settings.ibgc = settings.ibgc or c_table.bg_color or _clr.set( 0, 000, 000, 000)
settings.iselc = settings.iselc or c_table.lss_color or _clr.set( 1, 000, 200, 100)
if not settings.linedesc or rb.core_list_settings_table then
settings.linedesc = settings.linedesc or {}
local t_l = rb.core_list_settings_table
local linedesc = {
separator_height = t_l.list_separator_height or 0,
show_cursor = (t_l.cursor_style or 0) == 0,
style = t_l.cursor_style or 0xFFFF, --just a random non used index
show_icons = t_l.show_icons or false,
text_color = c_table.fg_color or _clr.set(-1, 000, 255, 060),
line_color = c_table.bg_color or _clr.set( 0, 000, 000, 000),
line_end_color= c_table.bg_color or _clr.set( 0, 000, 000, 000),
}
local styles = {rb.STYLE_NONE, rb.STYLE_INVERT, rb.STYLE_GRADIENT, rb.STYLE_COLORBAR, rb.STYLE_DEFAULT}
linedesc.style = styles[linedesc.style + 1] or rb.STYLE_COLORBAR
for k, v in pairs(linedesc) do
--dont overwrite supplied settings
settings.linedesc[k] = settings.linedesc[k] or v
end
end
settings.hasheader = true
settings.co_routine = nil
settings.msel = false
settings.start = start
settings.curpos = vcur
--settings.dpad_fn = dpad
while not i or i > 0 do
if copy_screen == true then
--make a copy of screen for restoration
screen_img = screen_img or rb.new_image()
screen_img:copy(_LCD)
else
screen_img = nil
end
_LCD:clear(settings.ibgc)
settings.start = start
settings.curpos = vcur
i, start, vcur = print_table(menu_t, #menu_t, settings)
--vcur = vcur + 1
collectgarbage("collect")
if copy_screen == true then _LCD:copy(screen_img) end
if func_t and func_t[i] then
if func_t[i](i, menu_t) == true then break end
else
break
end
end
if settings.default == true then settings = nil end
return screen_img, i
end

View file

@ -27,9 +27,9 @@ require("actions") -- Contains rb.actions & rb.contexts
local _clr = require("color")
local _print = require("print")
local _timer = require("timer")
local sb_width = 5
-- Button definitions --
local EXIT_BUTTON = rb.PLA_EXIT
local CANCEL_BUTTON = rb.actions.PLA_CANCEL
local DOWN_BUTTON = rb.actions.PLA_DOWN
local DOWNR_BUTTON = rb.actions.PLA_DOWN_REPEAT
@ -71,8 +71,8 @@ end
-- time since last button press is returned in ticks..
-- make xi, xir, yi, yir negative to flip direction...
]]
local function dpad(x, xi, xir, y, yi, yir, timeout)
local function dpad(x, xi, xir, y, yi, yir, timeout, overflow)
local scroll_is_fixed = overflow ~= "manual"
_timer("dpad") -- start a persistant timer; keeps time between button events
if timeout == nil then timeout = -1 end
local cancel, select = 0, 0
@ -98,10 +98,18 @@ local function dpad(x, xi, xir, y, yi, yir, timeout)
timeout = timeout + 1
elseif button == LEFT_BUTTON then
x_chg = x_chg - xi
if scroll_is_fixed then
cancel = 1
break;
end
elseif button == LEFTR_BUTTON then
x_chg = x_chg - xir
elseif button == RIGHT_BUTTON then
x_chg = x_chg + xi
if scroll_is_fixed then
select = 1
timeout = timeout + 1
end
elseif button == RIGHTR_BUTTON then
x_chg = x_chg + xir
elseif button == UP_BUTTON then
@ -152,19 +160,26 @@ function print_table(t, t_count, settings)
end
local wrap, justify, start, curpos, co_routine, hasheader, m_sel
local header_fgc, header_bgc, item_fgc, item_bgc, item_selc, drawsep
local header_fgc, header_bgc, item_fgc, item_bgc, item_selc
local table_linedesc, drawsep, overflow, dpad_fn
do
local s = settings or _print.get_settings()
wrap, justify = s.wrap, s.justify
start, curpos = s.start, s.curpos
co_routine = s.co_routine
hasheader = s.hasheader
drawsep = s.drawsep
m_sel = false
wrap, justify = s.wrap, s.justify
start, curpos = s.start, s.curpos
co_routine = s.co_routine
hasheader = s.hasheader
drawsep = s.drawsep
sb_width = s.sb_width or sb_width
m_sel = false
table_linedesc = s.linedesc
dpad_fn = s.dpad_fn
if type(dpad_fn) ~= "function" then dpad_fn = dpad end
if co_routine == nil then
--no multi select in incremental mode
m_sel = s.msel
end
overflow = s.ovfl or "auto"
header_fgc = s.hfgc or _clr.set( 0, 000, 000, 000)
header_bgc = s.hbgc or _clr.set(-1, 255, 255, 255)
item_fgc = s.ifgc or _clr.set(-1, 000, 255, 060)
@ -210,16 +225,19 @@ function print_table(t, t_count, settings)
-- displays header text at top
local function disp_header(hstr)
local header = header or hstr
local opts = _print.opt.get()
_print.opt.overflow("none") -- don't scroll header; colors change
local opts = _print.opt.get() -- save to restore settings
_print.opt.overflow("auto") -- don't scroll header; colors change
_print.opt.color(header_fgc, header_bgc)
_print.opt.line(1)
rb.set_viewport(_print.opt.get(true))
_print.f()
local line = _print.f(header)
_print.f("%h", tostring(header)) --hack to signal header
_print.opt.set(opts)
_print.opt.set(opts) -- restore settings
_print.opt.line(2)
rb.set_viewport(opts)
opts = nil
return 2
end
@ -229,7 +247,7 @@ function print_table(t, t_count, settings)
rb.lcd_update()
local quit, select, x_chg, xi, y_chg, yi, timeb =
dpad(t_p.col, -1, -t_p.col_scrl, t_p.row, -1, -t_p.row_scrl)
dpad_fn(t_p.col, -1, -t_p.col_scrl, t_p.row, -1, -t_p.row_scrl, nil, overflow)
t_p.vcursor = t_p.vcursor + y_chg
@ -322,6 +340,7 @@ function print_table(t, t_count, settings)
rb.button_clear_queue() -- keep the button queue from overflowing
end
rb.lcd_scroll_stop()
return sel
end -- display_table
--============================================================================--
@ -331,7 +350,7 @@ function print_table(t, t_count, settings)
_print.opt.color(item_fgc, item_bgc, item_selc)
table_p = init_position(15, 5)
line, maxline = _print.opt.area(5, 1, rb.LCD_WIDTH - 10, rb.LCD_HEIGHT - 2)
line, maxline = _print.opt.area(5, 1, rb.LCD_WIDTH - 10 - sb_width, rb.LCD_HEIGHT - 2)
maxline = math.min(maxline, t_count)
-- allow user to start at a position other than the beginning
@ -349,9 +368,11 @@ function print_table(t, t_count, settings)
end
_print.opt.sel_line(table_p.vcursor)
_print.opt.overflow("manual")
_print.opt.overflow(overflow)
_print.opt.justify(justify)
_print.opt.linedesc(table_linedesc)
local opts = _print.opt.get()
opts.drawsep = drawsep
_print.opt.set(opts)
@ -360,17 +381,19 @@ function print_table(t, t_count, settings)
-- initialize vertical scrollbar
set_vsb(); do
local vsb =_print.opt.get()
vsb.width = vsb.width + sb_width
if rb.LCD_DEPTH == 2 then -- invert 2-bit screens
vsb.fg_pattern = 3 - vsb.fg_pattern
vsb.bg_pattern = 3 - vsb.bg_pattern
end
set_vsb = function (item)
if t_count > (maxline or t_count) then
if sb_width > 0 and t_count > (maxline or t_count) then
rb.set_viewport(vsb)
item = item or 0
local m = maxline / 2 + 1
rb.gui_scrollbar_draw(vsb.width - 5, vsb.y, 5, vsb.height,
rb.gui_scrollbar_draw(vsb.width - sb_width, vsb.y, sb_width, vsb.height,
t_count, math.max(0, item - m),
math.min(item + m, t_count), 0)
end
@ -378,7 +401,7 @@ function print_table(t, t_count, settings)
end -- set_vsb
local selected = display_table(table_p, 0, 1, 0)
_print.opt.defaults()
_print.opt.defaults() --restore settings
if m_sel == true then -- walk the table to get selected items
selected = {}