mirror of
				https://github.com/Rockbox/rockbox.git
				synced 2025-10-29 00:36:22 -04:00 
			
		
		
		
	fix a few bugs, improve memory usage Change-Id: I6ceefd033000a6178eab0dd513679b5d72cec81c
		
			
				
	
	
		
			409 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			409 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| --[[
 | |
| /***************************************************************************
 | |
|  *             __________               __   ___.
 | |
|  *   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
 | |
| 
 | |
| local _clr   = require("color")
 | |
| local _print = require("print")
 | |
| local _timer = require("timer")
 | |
| local BUTTON = require("menubuttons")
 | |
| local sb_width = 5
 | |
| 
 | |
| -- clamps value to >= min and <= max
 | |
| local function clamp(iVal, iMin, iMax)
 | |
|     if iMin > iMax then
 | |
|         local swap = iMin
 | |
|         iMin, iMax = iMax, swap
 | |
|     end
 | |
| 
 | |
|     if iVal < iMin then
 | |
|         return iMin
 | |
|     elseif iVal < iMax then
 | |
|         return iVal
 | |
|     end
 | |
| 
 | |
|     return iMax
 | |
| 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, selected)
 | |
|     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 == BUTTON.CANCEL then
 | |
|             cancel = 1
 | |
|             break;
 | |
|         elseif button == BUTTON.EXIT then
 | |
|             cancel = 1
 | |
|             break;
 | |
|         elseif button == BUTTON.SEL then
 | |
|             select = 1
 | |
|             timeout = timeout + 1
 | |
|         elseif button == BUTTON.SELR then
 | |
|             select = 2
 | |
|             timeout = timeout + 1
 | |
|         elseif button == BUTTON.SELREL then
 | |
|             select = -1
 | |
|             timeout = timeout + 1
 | |
|         elseif button == BUTTON.LEFT then
 | |
|             x_chg = x_chg - xi
 | |
|             if scroll_is_fixed then
 | |
|                 cancel = 1
 | |
|                 break;
 | |
|             end
 | |
|         elseif button == BUTTON.LEFTR then
 | |
|             x_chg = x_chg - xir
 | |
|         elseif button == BUTTON.RIGHT then
 | |
|             x_chg = x_chg + xi
 | |
|             if scroll_is_fixed then
 | |
|                 select = 1
 | |
|                 timeout = timeout + 1
 | |
|             end
 | |
|         elseif button == BUTTON.RIGHTR then
 | |
|             x_chg = x_chg + xir
 | |
|         elseif button == BUTTON.UP then
 | |
|             y_chg = y_chg + yi
 | |
|         elseif button == BUTTON.UPR then
 | |
|             y_chg = y_chg + yir
 | |
|         elseif button == BUTTON.DOWN then
 | |
|             y_chg = y_chg - yi
 | |
|         elseif button == BUTTON.DOWNR 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
 | |
| 
 | |
| 
 | |
| 
 | |
| --------------------------------------------------------------------------------
 | |
| --[[ prints a scrollable table to the screen;
 | |
| -- requires a contiguous table with only strings;
 | |
| -- 1st item in table is the title if hasheader == true
 | |
| -- returns select item indice if NOT m_sel..
 | |
| -- if m_sel == true a table of selected indices are returned ]]
 | |
| --------------------------------------------------------------------------------
 | |
| -- SECOND MODE OF OPERATION -- if co_routine is defined...
 | |
| -- prints values returned from a resumable factory in a coroutine this allows
 | |
| -- very large files etc to be displayed.. the downside is it takes time
 | |
| -- to load data when scrolling also NO multiple selection is allowed
 | |
| -- table is passed along with the final count t_count
 | |
| --------------------------------------------------------------------------------
 | |
| 
 | |
| function print_table(t, t_count, settings)
 | |
| -- (table, t_count, {hasheader, wrap, m_sel, start, curpos, justify, co_routine})
 | |
| 
 | |
|     if type(t) ~= "table" then
 | |
|         rb.splash(rb.HZ * 5, "table expected got ".. type(t))
 | |
|         return
 | |
|     end
 | |
| 
 | |
|     local wrap, justify, start, curpos, co_routine, hasheader, m_sel
 | |
|     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
 | |
|         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)
 | |
|         item_bgc   = s.ibgc  or _clr.set( 0, 000, 000, 000)
 | |
|         item_selc  = s.iselc or _clr.set( 1, 000, 200, 100)
 | |
|     end
 | |
| 
 | |
|     local table_p, line, maxline
 | |
| 
 | |
|     local function set_vsb() end -- forward declaration; initialized below
 | |
| 
 | |
|     local function init_position(acc_ticks, acc_steps)
 | |
|         if not acc_ticks then acc_ticks = 15 end-- accelerate scroll every this many ticks
 | |
|         if not acc_steps then acc_steps = 5 end -- default steps for an accelerated scroll
 | |
| 
 | |
|         return {row = 1, row_scrl= acc_steps,
 | |
|                 col = 0, col_scrl = acc_steps,
 | |
|                 vcursor = 1, vcursor_min = 1,
 | |
|                 acc_ticks = acc_ticks, 
 | |
|                 acc_steps = acc_steps}
 | |
|     end
 | |
| 
 | |
|     local function set_accel(time, scrl, t_p)
 | |
|         if time < t_p.acc_ticks then -- accelerate scroll
 | |
|             scrl = scrl + 1
 | |
|         else
 | |
|             scrl = t_p.acc_steps
 | |
|         end
 | |
|         return scrl
 | |
|     end
 | |
| 
 | |
|     --adds or removes \0 from end of table entry to mark selected items
 | |
|     local function select_item(item)
 | |
|         if item < 1 then item = 1 end
 | |
|         if not t[item] then return end
 | |
|         if t[item]:sub(-1) == "\0" then
 | |
|             t[item] = t[item]:sub(1, -2) -- de-select
 | |
|         else
 | |
|             t[item] = t[item] .. "\0" -- select
 | |
|         end
 | |
|     end
 | |
| 
 | |
|     -- displays header text at top
 | |
|     local function disp_header(hstr)
 | |
|         local header = header or hstr
 | |
|         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()
 | |
|         _print.f("%h", tostring(header)) --hack to signal header
 | |
| 
 | |
|         _print.opt.set(opts) -- restore settings
 | |
|         _print.opt.line(2)
 | |
|         rb.set_viewport(opts)
 | |
|         opts = nil
 | |
|         return 2
 | |
|     end
 | |
| 
 | |
|     -- gets user input to select items, quit, scroll
 | |
|     local function get_input(t_p)
 | |
|         set_vsb(t_p.row + t_p.vcursor - 1)--t_p.row)
 | |
|         rb.lcd_update()
 | |
| 
 | |
|         local quit, select, x_chg, xi, y_chg, yi, timeb =
 | |
|                           dpad_fn(t_p.col, -1, -t_p.col_scrl, t_p.row, -1, -t_p.row_scrl,
 | |
|                                   nil, overflow, (t_p.row + t_p.vcursor - 1))
 | |
| 
 | |
|         t_p.vcursor = t_p.vcursor + y_chg
 | |
| 
 | |
|         if t_p.vcursor > maxline or t_p.vcursor < t_p.vcursor_min then
 | |
|             t_p.row = yi
 | |
|         end
 | |
| 
 | |
|         if wrap == true and (y_chg == 1 or y_chg == -1) then
 | |
| 
 | |
|             -- wraps list, stops at end if accelerated
 | |
|             if t_p.row < t_p.vcursor_min - 1 then
 | |
|                 t_p.row  = t_count - maxline + 1
 | |
|                 t_p.vcursor = maxline
 | |
|             elseif t_p.row + maxline - 1 > t_count then
 | |
|                 t_p.row, t_p.vcursor = t_p.vcursor_min - 1, t_p.vcursor_min - 1
 | |
|             end
 | |
|         end
 | |
| 
 | |
|         t_p.row  = clamp(t_p.row, 1, math.max(t_count - maxline + 1, 1))
 | |
|         t_p.vcursor = clamp(t_p.vcursor, t_p.vcursor_min, maxline)
 | |
| 
 | |
|         if x_chg ~= 0 then
 | |
| 
 | |
|             if x_chg ~= 1 and x_chg ~= -1 then --stop at the center if accelerated
 | |
|                 if (t_p.col <= 0 and xi > 0) or (t_p.col >= 0 and xi < 0) then
 | |
|                     xi = 0
 | |
|                 end
 | |
|             end
 | |
|             t_p.col = xi
 | |
| 
 | |
|             t_p.col_scrl = set_accel(timeb, t_p.col_scrl, t_p)
 | |
| 
 | |
|         elseif y_chg ~= 0 then
 | |
|             --t_p.col = 0 -- reset column to the beginning
 | |
|             _print.clear()
 | |
|             _print.opt.sel_line(t_p.vcursor)
 | |
| 
 | |
|             t_p.row_scrl = set_accel(timeb, t_p.row_scrl, t_p)
 | |
| 
 | |
|         end
 | |
| 
 | |
|         if select > 0 and timeb > 15 then --select may be sent multiple times
 | |
|             if m_sel == true then
 | |
|                 select_item(t_p.row + t_p.vcursor - 1)
 | |
|             else
 | |
|                 return -1, 0, 0, (t_p.row + t_p.vcursor - 1)
 | |
|             end
 | |
|         end
 | |
|         if quit > 0 then return -2, 0, 0, 0 end
 | |
|         return t_p.row, x_chg, y_chg, 0
 | |
|     end
 | |
| 
 | |
|     -- displays the actual table
 | |
|     local function display_table(table_p, col_c, row_c, sel)
 | |
|         local i = table_p.row
 | |
|         while i >= 1 and i <= t_count do
 | |
| 
 | |
|             -- only print if beginning or user scrolled up/down
 | |
|             if row_c ~= 0 then
 | |
| 
 | |
|                 if t[i] == nil and co_routine then
 | |
|                     --value has been garbage collected or not created yet
 | |
|                     coroutine.resume(co_routine, i)
 | |
|                 end
 | |
| 
 | |
|                 if t[i] == nil then
 | |
|                     rb.splash(1, string.format("ERROR %d is nil", i))
 | |
|                     t[i] = "???"
 | |
|                     if rb.get_plugin_action(10) == BUTTON.CANCEL then return 0 end
 | |
|                 end
 | |
| 
 | |
|                 if m_sel == true and t[i]:sub(-1) == "\0" then
 | |
|                     _print.opt.sel_line(line)
 | |
|                 end
 | |
| 
 | |
|                 if i == 1 and hasheader == true then
 | |
|                     line = disp_header(t[1])
 | |
|                 else
 | |
|                     line = _print.f("%s", tostring(t[i]))
 | |
|                 end
 | |
| 
 | |
|             end
 | |
| 
 | |
|             i = i + 1 -- important!
 | |
| 
 | |
|             if line == 1 or i > t_count or col_c ~= 0 then
 | |
|                 _print.opt.column(table_p.col)
 | |
|                 i, col_c, row_c, sel = get_input(table_p)
 | |
|             end
 | |
| 
 | |
|             rb.button_clear_queue() -- keep the button queue from overflowing
 | |
|         end
 | |
|         rb.lcd_scroll_stop()
 | |
|         return sel
 | |
|     end -- display_table
 | |
| --============================================================================--
 | |
| 
 | |
|     _print.opt.defaults()
 | |
|     _print.opt.autoupdate(false)
 | |
|     _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 - sb_width, rb.LCD_HEIGHT - 2)
 | |
| 
 | |
|     if (curpos or 0) > maxline then
 | |
|         local c = maxline / 2
 | |
|         start = (start or 1) + curpos - maxline
 | |
|         curpos = maxline
 | |
|         while start + maxline <= t_count and curpos > c do
 | |
|             curpos = curpos - 1
 | |
|             start = start + 1
 | |
|         end
 | |
|     end
 | |
|     maxline = math.min(maxline, t_count)
 | |
| 
 | |
|     -- allow user to start at a position other than the beginning
 | |
|     if start ~= nil then table_p.row = clamp(start, 1, t_count + 1) end
 | |
| 
 | |
|     if hasheader == true then
 | |
|         table_p.vcursor_min = 2  -- lowest selectable item
 | |
|         table_p.vcursor     = 2
 | |
|     end
 | |
| 
 | |
|     table_p.vcursor = curpos or table_p.vcursor_min
 | |
| 
 | |
|     if table_p.vcursor < 1 or table_p.vcursor > maxline then
 | |
|         table_p.vcursor = table_p.vcursor_min
 | |
|     end
 | |
| 
 | |
|     _print.opt.sel_line(table_p.vcursor)
 | |
|     _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)
 | |
| 
 | |
| 
 | |
|     -- 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 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 - sb_width, vsb.y, sb_width, vsb.height,
 | |
|                                       t_count, math.max(0, item - m), 
 | |
|                                       math.min(item + m, t_count), 0)
 | |
|             end
 | |
|         end
 | |
|     end -- set_vsb
 | |
|     local selected = display_table(table_p, 0, 1, 0)
 | |
| 
 | |
|     _print.opt.defaults() --restore settings
 | |
| 
 | |
|     if m_sel == true then -- walk the table to get selected items
 | |
|         selected = {}
 | |
|         for i = 1, t_count do
 | |
|             if t[i]:sub(-1) == "\0" then table.insert(selected, i) end
 | |
|         end
 | |
|     end
 | |
|     --rb.splash(100, string.format("#1 %d, %d, %d", row, vcursor_pos, sel))
 | |
|    return selected, table_p.row, table_p.vcursor
 | |
| end --print_table
 |