A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 416 lines 14 kB view raw
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