mirror of
				https://github.com/Rockbox/rockbox.git
				synced 2025-10-29 00:36:22 -04:00 
			
		
		
		
	allows menus + submenus + context menus all with simple tables menu_t which is a table of strings func_t which are the corresponding functions to go with those strings see lua_scripts/submenu_demo.lua Change-Id: I907b74b4abef0ecbe49f181d0ced6e6d20e94de5
		
			
				
	
	
		
			464 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			464 lines
		
	
	
	
		
			15 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| --[[ Lua Print functions
 | |
| /***************************************************************************
 | |
|  *             __________               __   ___.
 | |
|  *   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.
 | |
|  *
 | |
|  ****************************************************************************/
 | |
| ]]
 | |
| 
 | |
| --[[ Exposed Functions
 | |
| 
 | |
|     _print.clear
 | |
|     _print.f
 | |
|     _print.opt
 | |
|     _print.opt.area
 | |
|     _print.opt.autoupdate
 | |
|     _print.opt.color
 | |
|     _print.opt.column
 | |
|     _print.opt.defaults
 | |
|     _print.opt.get
 | |
|     _print.opt.justify
 | |
|     _print.opt.line
 | |
|     _print.opt.overflow
 | |
|     _print.opt.sel_line
 | |
|     _print.opt.set
 | |
| 
 | |
| ]]
 | |
| 
 | |
| if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
 | |
| 
 | |
| local _print = {} do
 | |
| 
 | |
|     -- internal constants
 | |
|     local _clr = require("color") -- _clr functions required
 | |
| 
 | |
|     local _NIL = nil -- _NIL placeholder
 | |
|     local _LCD = rb.lcd_framebuffer()
 | |
|     local WHITE = _clr.set(-1, 255, 255, 255)
 | |
|     local BLACK = _clr.set(0, 0, 0, 0)
 | |
|     local DRMODE_SOLID = 3
 | |
|     local col_buf, s_lines = {}, {}
 | |
|     local _p_opts = _NIL
 | |
|     local tabstring = string.rep(" ", 2)
 | |
| -- print internal helper functions
 | |
| --------------------------------------------------------------------------------
 | |
|    -- clamps value to >= min and <= max
 | |
|     local function clamp(val, min, max)
 | |
|         -- Warning doesn't check if min < max
 | |
|         if val < min then
 | |
|             return min
 | |
|         elseif val < max then
 | |
|             return val
 | |
|         end
 | |
|         return max
 | |
|     end
 | |
| 
 | |
|     -- Gets size of text
 | |
|     local function text_extent(msg, font)
 | |
|         -- res, w, h
 | |
|         return rb.font_getstringsize(msg, font or rb.FONT_UI)
 | |
|     end
 | |
| 
 | |
|     -- Updates a single line on the screen
 | |
|     local function update_line(enabled, opts, line, h)
 | |
|         if enabled ~= true then return end
 | |
|         local o = opts or _p_opts
 | |
|     -- updates screen in specified rectangle
 | |
|         rb.lcd_update_rect(o.x - 1, o.y + line * h,
 | |
|              clamp(o.x + o.width, 1, rb.LCD_WIDTH) - 1,
 | |
|              clamp(o.y + line * h + 1 + h, 1, rb.LCD_HEIGHT) - 1)
 | |
|     end
 | |
| 
 | |
|     -- Clears a single line on the screen
 | |
|     local function clear_line(opts, line, h)
 | |
|         local o = opts or _p_opts
 | |
|         _LCD:clear(o.bg_pattern, o.x, o.y + line * h + 1,
 | |
|                                  o.x + o.width, line * h + h + o.y)
 | |
|     end
 | |
| 
 | |
|     -- Sets the maximum number of lines on the screen
 | |
|     local function max_lines(opts)
 | |
|         local h  = opts.height
 | |
|         local _, _, th = text_extent("W", opts.font)
 | |
|         return h / th
 | |
|     end
 | |
| 
 | |
|     --saves the items displayed for side to side scroll
 | |
|     local function col_buf_insert(msg, line, _p_opts)
 | |
|         --if _p_opts.line <= 1 then col_buf = {} end
 | |
|         if not col_buf[line] then
 | |
|             table.insert(col_buf, line, msg)
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     --replaces / strips tab characters
 | |
|     local function check_escapes(o, msg)
 | |
| --[[    --for replacing a variety of escapes
 | |
|         local tabsz  = 2
 | |
|         local tabstr = string.rep(" ", tabsz)
 | |
|         local function repl(esc)
 | |
|             local ret = ""
 | |
|             if esc:sub(1,1) == "\t" then ret = string.rep(tabstr, esc:len()) end
 | |
|             return ret
 | |
|         end
 | |
|         msg = msg:gsub("(%c+)", repl)
 | |
| ]]
 | |
|         msg = msg:gsub("\t", tabstring)
 | |
|         local res, w, h = text_extent(msg, o.font)
 | |
|         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()
 | |
|         _p_opts = { x = 1,
 | |
|                     y = 1,
 | |
|                     width = rb.LCD_WIDTH - 1,
 | |
|                     height = rb.LCD_HEIGHT - 1,
 | |
|                     font = rb.FONT_UI,
 | |
|                     drawmode = DRMODE_SOLID,
 | |
|                     fg_pattern = WHITE,
 | |
|                     bg_pattern = BLACK,
 | |
|                     sel_pattern = WHITE,
 | |
|                     line = 1,
 | |
|                     max_line = _NIL,
 | |
|                     col = 0,
 | |
|                     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 = {}, {}
 | |
|         return _p_opts
 | |
|     end
 | |
| 
 | |
|     -- returns table with settings for print
 | |
|     -- if bByRef is _NIL or false then a copy is returned
 | |
|     local function get_settings(bByRef)
 | |
|         _p_opts = _p_opts or set_defaults()
 | |
|         if not bByRef then
 | |
|             -- shallow copy of table
 | |
|             local copy = {}
 | |
|             for k, v in pairs(_p_opts) do
 | |
|                 copy[k] = v
 | |
|             end
 | |
|             return copy
 | |
|         end
 | |
| 
 | |
|         return _p_opts
 | |
|     end
 | |
| 
 | |
|     -- sets the settings for print with your passed table
 | |
|     local function set_settings(t_opts)
 | |
|         _p_opts = t_opts or set_defaults()
 | |
|         if t_opts then
 | |
|             _p_opts.max_line = max_lines(_p_opts)
 | |
|             col_buf = {}
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     -- sets colors for print
 | |
|     local function set_color(fgclr, bgclr, selclr)
 | |
|         local o = get_settings(true)
 | |
| 
 | |
|         if fgclr ~= _NIL then
 | |
|             o.fg_pattern, o.sel_pattern = fgclr, fgclr
 | |
|         end
 | |
|         o.sel_pattern = selclr or o.sel_pattern
 | |
|         o.bg_pattern = bgclr or o.bg_pattern
 | |
|     end
 | |
| 
 | |
|     -- helper function sets up colors/marker for selected items
 | |
|     local function show_selected(iLine, msg)
 | |
|         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
 | |
|     end
 | |
| 
 | |
|     -- sets line explicitly or increments line if line is _NIL
 | |
|     local function set_line(iLine)
 | |
|         local o = get_settings(true)
 | |
| 
 | |
|         o.line = iLine or o.line + 1
 | |
| 
 | |
|         if(o.line < 1 or o.line > o.max_line) then
 | |
|             o.line = 1
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     -- clears the set print area
 | |
|     local function clear()
 | |
|         local o = get_settings(true)
 | |
|         _LCD:clear(o.bg_pattern, o.x, o.y, o.x + o.width, o.y + o.height)
 | |
|         if o.autoupdate == true then rb.lcd_update() end
 | |
|         rb.lcd_scroll_stop()
 | |
|         set_line(1)
 | |
|         for i=1, #col_buf do col_buf[i] = _NIL end
 | |
|         s_lines = {}
 | |
|         collectgarbage("collect")
 | |
|     end
 | |
| 
 | |
|     -- screen update after each call to print.f
 | |
|     local function set_update(bAutoUpdate)
 | |
|         local o = get_settings(true)
 | |
|         o.autoupdate = bAutoUpdate or false
 | |
|     end
 | |
| 
 | |
|     -- sets print area
 | |
|     local function set_area(x, y, w, h)
 | |
|         local o = get_settings(true)
 | |
|         o.x, o.y = clamp(x, 1, rb.LCD_WIDTH), clamp(y, 1, rb.LCD_HEIGHT)
 | |
|         o.width, o.height = clamp(w, 1, rb.LCD_WIDTH - o.x), clamp(h, 1, rb.LCD_HEIGHT - o.y)
 | |
|         o.max_line = max_lines(_p_opts)
 | |
| 
 | |
|         clear()
 | |
|         return o.line, o.max_line
 | |
|     end
 | |
| 
 | |
|     -- when string is longer than print width scroll -- "auto", "manual", "none"
 | |
|     local function set_overflow(str_mode)
 | |
|         -- "auto", "manual", "none"
 | |
|         local str_mode = str_mode or "auto"
 | |
|         local o = get_settings(true)
 | |
|         o.ovfl = str_mode:lower()
 | |
|         col_buf = {}
 | |
|     end
 | |
| 
 | |
|     -- aligns text to: "left", "center", "right"
 | |
|     local function set_justify(str_mode)
 | |
|         -- "left", "center", "right"
 | |
|         local str_mode = str_mode or "left"
 | |
|         local o = get_settings(true)
 | |
|         o.justify = str_mode:lower()
 | |
|     end
 | |
| 
 | |
|     -- selects line
 | |
|     local function select_line(iLine)
 | |
|         s_lines[iLine] = true
 | |
|     end
 | |
| 
 | |
|    -- Internal print function
 | |
|     local function print_internal(t_opts, x, w, h, msg)
 | |
| 
 | |
|         local linedesc
 | |
|         local line_separator = false
 | |
|         local o = t_opts
 | |
|         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 == "manual" then --save msg for later side scroll
 | |
|             col_buf_insert(msg, o.line, o)
 | |
|         end
 | |
| 
 | |
|         -- 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 or o.drawsep
 | |
|         local indent = line_indent < 0 and 0 or line_indent --rb scroller doesn't like negative offset!
 | |
|         if o.line == 1 and o.header then
 | |
|             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 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
 | |
|             if not o.header then
 | |
|                 rb.lcd_drawline(0, line * h, o.width, line * h)
 | |
|             end
 | |
|             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)
 | |
| 
 | |
|         set_line(_NIL) -- increments line counter
 | |
|     end
 | |
| 
 | |
|     -- Helper function that acts mostly like a normal printf() would
 | |
|     local function printf(fmt, v1, ...)
 | |
|         local o = get_settings(true)
 | |
|         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'
 | |
|              local res, w, h = text_extent(" ", o.font)
 | |
| 
 | |
|             clear_line(o, line, h)
 | |
|             update_line(o.autoupdate, o, line, h)
 | |
| 
 | |
|             if (fmt) then set_line(_NIL) end
 | |
| 
 | |
|             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)
 | |
| 
 | |
|         w, h, msg = check_escapes(o, msg)
 | |
| 
 | |
|         print_internal(o, o.col, w, h, msg)
 | |
| 
 | |
|         return o.line, o.max_line, w, h
 | |
|     end
 | |
| 
 | |
|     -- x > 0 scrolls right x < 0 scrolls left
 | |
|     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
 | |
|             line = key - 1 -- rb is 0-based lua is 1-based
 | |
|             o.line = key
 | |
| 
 | |
|             if value then
 | |
|                 show_selected(key, value)
 | |
|                 res, w, h = text_extent(value, o.font)
 | |
|                 clear_line(o, line, h)
 | |
| 
 | |
|                 print_internal(o, x + o.col, w, h, value)
 | |
|                 update_line(o.autoupdate, o, line, h)
 | |
|             end
 | |
|         end
 | |
|         o = _NIL
 | |
|     end
 | |
| 
 | |
|     --expose functions to the outside through _print table
 | |
|     _print.opt            = {}
 | |
|     _print.opt.column     = set_column
 | |
|     _print.opt.color      = set_color
 | |
|     _print.opt.area       = set_area
 | |
|     _print.opt.set        = set_settings
 | |
|     _print.opt.get        = get_settings
 | |
|     _print.opt.defaults   = set_defaults
 | |
|     _print.opt.overflow   = set_overflow
 | |
|     _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.selected       = function() return s_lines end
 | |
|     _print.clear = clear
 | |
|     _print.f     = printf
 | |
| 
 | |
| end --_print functions
 | |
| 
 | |
| return _print
 | |
| 
 |