A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1--[[
2/***************************************************************************
3 * __________ __ ___.
4 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
5 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
6 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
7 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
8 * \/ \/ \/ \/ \/
9 * $Id$
10 *
11 * Copyright (C) 2017 William Wilgus
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22]]
23if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
24
25local _clr = require("color")
26local _print = require("print")
27local _timer = require("timer")
28local BUTTON = require("menubuttons")
29local sb_width = 5
30
31-- clamps value to >= min and <= max
32local function clamp(iVal, iMin, iMax)
33 if iMin > iMax then
34 local swap = iMin
35 iMin, iMax = iMax, swap
36 end
37
38 if iVal < iMin then
39 return iMin
40 elseif iVal < iMax then
41 return iVal
42 end
43
44 return iMax
45end
46
47--------------------------------------------------------------------------------
48--[[ cursor style button routine
49-- left / right are x, xi is increment xir is increment when repeat
50-- up / down are y, yi is increment yir is increment when repeat
51-- cancel is returned as 0,1
52-- select as 0, 1, 2, 3 (none, pressed, repeat, relesed)
53-- x_chg and y_chg are the amount x or y changed
54-- timeout == nil or -1 loop waits indefinitely till button is pressed
55-- time since last button press is returned in ticks..
56-- make xi, xir, yi, yir negative to flip direction...
57]]
58local function dpad(x, xi, xir, y, yi, yir, timeout, overflow, selected)
59 local scroll_is_fixed = overflow ~= "manual"
60 _timer("dpad") -- start a persistant timer; keeps time between button events
61 if timeout == nil then timeout = -1 end
62 local cancel, select = 0, 0
63 local x_chg, y_chg = 0, 0
64 local button
65 while true do
66 button = rb.get_plugin_action(timeout)
67
68 if button == BUTTON.CANCEL then
69 cancel = 1
70 break;
71 elseif button == BUTTON.EXIT then
72 cancel = 1
73 break;
74 elseif button == BUTTON.SEL then
75 select = 1
76 timeout = timeout + 1
77 elseif button == BUTTON.SELR then
78 select = 2
79 timeout = timeout + 1
80 elseif button == BUTTON.SELREL then
81 select = -1
82 timeout = timeout + 1
83 elseif button == BUTTON.LEFT then
84 x_chg = x_chg - xi
85 if scroll_is_fixed then
86 cancel = 1
87 break;
88 end
89 elseif button == BUTTON.LEFTR then
90 x_chg = x_chg - xir
91 elseif button == BUTTON.RIGHT then
92 x_chg = x_chg + xi
93 if scroll_is_fixed then
94 select = 1
95 timeout = timeout + 1
96 end
97 elseif button == BUTTON.RIGHTR then
98 x_chg = x_chg + xir
99 elseif button == BUTTON.UP then
100 y_chg = y_chg + yi
101 elseif button == BUTTON.UPR then
102 y_chg = y_chg + yir
103 elseif button == BUTTON.DOWN then
104 y_chg = y_chg - yi
105 elseif button == BUTTON.DOWNR then
106 y_chg = y_chg - yir
107 elseif timeout >= 0 then--and rb.button_queue_count() < 1 then
108 break;
109 end
110
111 if x_chg ~= 0 or y_chg ~= 0 then
112 timeout = timeout + 1
113 end
114 end
115
116 x = x + x_chg
117 y = y + y_chg
118
119 return cancel, select, x_chg, x, y_chg, y, _timer.check("dpad", true)
120end -- dpad
121
122
123
124--------------------------------------------------------------------------------
125--[[ prints a scrollable table to the screen;
126-- requires a contiguous table with only strings;
127-- 1st item in table is the title if hasheader == true
128-- returns select item indice if NOT m_sel..
129-- if m_sel == true a table of selected indices are returned ]]
130--------------------------------------------------------------------------------
131-- SECOND MODE OF OPERATION -- if co_routine is defined...
132-- prints values returned from a resumable factory in a coroutine this allows
133-- very large files etc to be displayed.. the downside is it takes time
134-- to load data when scrolling also NO multiple selection is allowed
135-- table is passed along with the final count t_count
136--------------------------------------------------------------------------------
137
138function print_table(t, t_count, settings)
139-- (table, t_count, {hasheader, wrap, m_sel, start, curpos, justify, co_routine})
140
141 if type(t) ~= "table" then
142 rb.splash(rb.HZ * 5, "table expected got ".. type(t))
143 return
144 end
145
146 local wrap, justify, start, curpos, co_routine, hasheader, m_sel
147 local header_fgc, header_bgc, item_fgc, item_bgc, item_selc
148 local table_linedesc, drawsep, overflow, dpad_fn, pagescroll
149 do
150 local s = settings or _print.get_settings()
151 wrap, justify = s.wrap, s.justify
152 start, curpos = s.start, s.curpos
153 co_routine = s.co_routine
154 hasheader = s.hasheader
155 pagescroll = s.pagescroll
156 drawsep = s.drawsep
157 sb_width = s.sb_width or sb_width
158 m_sel = false
159 table_linedesc = s.linedesc
160 dpad_fn = s.dpad_fn
161 if type(dpad_fn) ~= "function" then dpad_fn = dpad end
162
163 if co_routine == nil then
164 --no multi select in incremental mode
165 m_sel = s.msel
166 end
167 overflow = s.ovfl or "auto"
168 header_fgc = s.hfgc or _clr.set( 0, 000, 000, 000)
169 header_bgc = s.hbgc or _clr.set(-1, 255, 255, 255)
170 item_fgc = s.ifgc or _clr.set(-1, 000, 255, 060)
171 item_bgc = s.ibgc or _clr.set( 0, 000, 000, 000)
172 item_selc = s.iselc or _clr.set( 1, 000, 200, 100)
173 end
174
175 local table_p, line, maxline
176
177 local function set_vsb() end -- forward declaration; initialized below
178
179 local function init_position(acc_ticks, acc_steps)
180 if not acc_ticks then acc_ticks = 15 end-- accelerate scroll every this many ticks
181 if not acc_steps then acc_steps = 5 end -- default steps for an accelerated scroll
182
183 return {row = 1, row_scrl= acc_steps,
184 col = 0, col_scrl = acc_steps,
185 vcursor = 1, vcursor_min = 1,
186 acc_ticks = acc_ticks,
187 acc_steps = acc_steps}
188 end
189
190 local function set_accel(time, scrl, t_p)
191 if time < t_p.acc_ticks then -- accelerate scroll
192 scrl = scrl + 1
193 else
194 scrl = t_p.acc_steps
195 end
196 return scrl
197 end
198
199 --adds or removes \0 from end of table entry to mark selected items
200 local function select_item(item)
201 if item < 1 then item = 1 end
202 if not t[item] then return end
203 if t[item]:sub(-1) == "\0" then
204 t[item] = t[item]:sub(1, -2) -- de-select
205 else
206 t[item] = t[item] .. "\0" -- select
207 end
208 end
209
210 -- displays header text at top
211 local function disp_header(hstr)
212 local header = header or hstr
213 local opts = _print.opt.get() -- save to restore settings
214 _print.opt.overflow("auto") -- don't scroll header; colors change
215 _print.opt.color(header_fgc, header_bgc)
216 _print.opt.line(1)
217
218 rb.set_viewport(_print.opt.get(true))
219 _print.f()
220 _print.f("%h", tostring(header)) --hack to signal header
221
222 _print.opt.set(opts) -- restore settings
223 _print.opt.line(2)
224 rb.set_viewport(opts)
225 opts = nil
226 return 2
227 end
228
229 -- gets user input to select items, quit, scroll
230 local function get_input(t_p)
231 set_vsb(t_p.row + t_p.vcursor - 1)--t_p.row)
232 rb.lcd_update()
233
234 local quit, select, x_chg, xi, y_chg, yi, timeb =
235 dpad_fn(t_p.col, -1, -t_p.col_scrl, t_p.row, -1, -t_p.row_scrl,
236 nil, overflow, (t_p.row + t_p.vcursor - 1))
237
238
239 if pagescroll == true then
240 t_p.row = t_p.row + y_chg * maxline - 1
241 end
242 t_p.vcursor = t_p.vcursor + y_chg
243
244
245 if t_p.vcursor > maxline or t_p.vcursor < t_p.vcursor_min then
246 t_p.row = yi
247 end
248
249 if wrap == true and (y_chg == 1 or y_chg == -1) then
250
251 -- wraps list, stops at end if accelerated
252 if t_p.row < t_p.vcursor_min - 1 then
253 t_p.row = t_count - maxline + 1
254 t_p.vcursor = maxline
255 elseif t_p.row + maxline - 1 > t_count then
256 t_p.row, t_p.vcursor = t_p.vcursor_min - 1, t_p.vcursor_min - 1
257 end
258 end
259
260 t_p.row = clamp(t_p.row, 1, math.max(t_count - maxline + 1, 1))
261 t_p.vcursor = clamp(t_p.vcursor, t_p.vcursor_min, maxline)
262
263 if x_chg ~= 0 then
264
265 if x_chg ~= 1 and x_chg ~= -1 then --stop at the center if accelerated
266 if (t_p.col <= 0 and xi > 0) or (t_p.col >= 0 and xi < 0) then
267 xi = 0
268 end
269 end
270 t_p.col = xi
271
272 t_p.col_scrl = set_accel(timeb, t_p.col_scrl, t_p)
273
274 elseif y_chg ~= 0 then
275 --t_p.col = 0 -- reset column to the beginning
276 _print.clear()
277
278 _print.opt.sel_line(t_p.vcursor)
279
280 t_p.row_scrl = set_accel(timeb, t_p.row_scrl, t_p)
281
282 end
283
284 if select > 0 and timeb > 15 then --select may be sent multiple times
285 if m_sel == true then
286 select_item(t_p.row + t_p.vcursor - 1)
287 else
288 return -1, 0, 0, (t_p.row + t_p.vcursor - 1)
289 end
290 end
291 if quit > 0 then return -2, 0, 0, 0 end
292 return t_p.row, x_chg, y_chg, 0
293 end
294
295 -- displays the actual table
296 local function display_table(table_p, col_c, row_c, sel)
297 local i = table_p.row
298 while i >= 1 and i <= t_count do
299
300 -- only print if beginning or user scrolled up/down
301 if row_c ~= 0 then
302
303 if t[i] == nil and co_routine then
304 --value has been garbage collected or not created yet
305 coroutine.resume(co_routine, i)
306 end
307
308 if t[i] == nil then
309 rb.splash(1, string.format("ERROR %d is nil", i))
310 t[i] = "???"
311 if rb.get_plugin_action(10) == BUTTON.CANCEL then return 0 end
312 end
313
314 if m_sel == true and t[i]:sub(-1) == "\0" then
315 _print.opt.sel_line(line)
316 end
317
318 if i == 1 and hasheader == true then
319 line = disp_header(t[1])
320 else
321 line = _print.f("%s", tostring(t[i]))
322 end
323
324 end
325
326 i = i + 1 -- important!
327
328 if line == 1 or i > t_count or col_c ~= 0 then
329 _print.opt.column(table_p.col)
330 i, col_c, row_c, sel = get_input(table_p)
331 end
332
333 rb.button_clear_queue() -- keep the button queue from overflowing
334 end
335 rb.lcd_scroll_stop()
336 return sel
337 end -- display_table
338--============================================================================--
339
340 _print.opt.defaults()
341 _print.opt.autoupdate(false)
342 _print.opt.color(item_fgc, item_bgc, item_selc)
343
344 table_p = init_position(15, 5)
345 line, maxline = _print.opt.area(5, 1, rb.LCD_WIDTH - 10 - sb_width, rb.LCD_HEIGHT - 2)
346
347 if (curpos or 0) > maxline then
348 local c = maxline / 2
349 start = (start or 1) + curpos - maxline
350 curpos = maxline
351 while start + maxline <= t_count and curpos > c do
352 curpos = curpos - 1
353 start = start + 1
354 end
355 end
356 maxline = math.min(maxline, t_count)
357
358 -- allow user to start at a position other than the beginning
359 if start ~= nil then table_p.row = clamp(start, 1, t_count + 1) end
360
361 if hasheader == true then
362 table_p.vcursor_min = 2 -- lowest selectable item
363 table_p.vcursor = 2
364 end
365
366 table_p.vcursor = curpos or table_p.vcursor_min
367
368 if table_p.vcursor < 1 or table_p.vcursor > maxline then
369 table_p.vcursor = table_p.vcursor_min
370 end
371
372 _print.opt.sel_line(table_p.vcursor)
373 _print.opt.overflow(overflow)
374 _print.opt.justify(justify)
375
376 _print.opt.linedesc(table_linedesc)
377
378 local opts = _print.opt.get()
379 opts.drawsep = drawsep
380 _print.opt.set(opts)
381
382
383 -- initialize vertical scrollbar
384 set_vsb(); do
385 local vsb =_print.opt.get()
386 vsb.width = vsb.width + sb_width
387
388 if rb.LCD_DEPTH == 2 then -- invert 2-bit screens
389 vsb.fg_pattern = 3 - vsb.fg_pattern
390 vsb.bg_pattern = 3 - vsb.bg_pattern
391 end
392
393 set_vsb = function (item)
394 if sb_width > 0 and t_count > (maxline or t_count) then
395 rb.set_viewport(vsb)
396 item = item or 0
397 local m = maxline / 2 + 1
398 rb.gui_scrollbar_draw(vsb.width - sb_width, vsb.y, sb_width, vsb.height,
399 t_count, math.max(0, item - m),
400 math.min(item + m, t_count), 0)
401 end
402 end
403 end -- set_vsb
404 local selected = display_table(table_p, 0, 1, 0)
405
406 _print.opt.defaults() --restore settings
407
408 if m_sel == true then -- walk the table to get selected items
409 selected = {}
410 for i = 1, t_count do
411 if t[i]:sub(-1) == "\0" then table.insert(selected, i) end
412 end
413 end
414 --rb.splash(100, string.format("#1 %d, %d, %d", row, vcursor_pos, sel))
415 return selected, table_p.row, table_p.vcursor
416end --print_table