A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 314 lines 10 kB view raw
1--[[ 2/*************************************************************************** 3 * __________ __ ___. 4 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 5 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 6 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 7 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 8 * \/ \/ \/ \/ \/ 9 * $Id$ 10 * 11 * Copyright (C) 2021 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--GLOBALS 25menu_ctx = {} 26submenu_insert = table.insert 27submenu_remove = table.remove 28 29local last_ctx = false 30local p_settings 31local function empty_fn() end 32 33--[[root menu tables 34expanded menus get inserted / removed 35and context menus replace them but unless you 36want a new root menu they are never overwritten 37func_t functions get 3 variables passed by the menu_system 38func_t[i] =function sample(i, menu_t, func_t] 39this function gets run on user selection 40for every function in func_t: 41'i' is the selected item 42'menu_t' is the current strings table 43'func_t' is the current function table 44 45menu_t[i] will returnm the text of the item user selected and 46func_t[i] will return the function we are currently in 47]] 48 49menu_t = {} 50func_t = {} 51 52require("printmenus") 53 54local BUTTON = require("menubuttons") 55local last_sel = 0 56 57local function display_context_menu() end -- forward declaration 58 59local function dpad(x, xi, xir, y, yi, yir, timeout, overflow, selected) 60 local scroll_is_fixed = overflow ~= "manual" 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 last_sel = 1 76 timeout = timeout + 1 77 elseif button == BUTTON.SELR then 78 last_sel = 2 79 if display_context_menu(selected or -1) == true then 80 select = 1 81 break; 82 end 83 timeout = timeout + 1 84 elseif button == BUTTON.SELREL then 85 if last_sel == 1 then 86 select = 1 87 end 88 last_sel = 0 89 timeout = timeout + 1 90 elseif button == BUTTON.LEFT then 91 x_chg = x_chg - xi 92 if scroll_is_fixed then 93 cancel = 1 94 break; 95 end 96 elseif button == BUTTON.LEFTR then 97 x_chg = x_chg - xir 98 elseif button == BUTTON.RIGHT then 99 x_chg = x_chg + xi 100 if scroll_is_fixed then 101 select = 1 102 timeout = timeout + 1 103 end 104 elseif button == BUTTON.RIGHTR then 105 x_chg = x_chg + xir 106 elseif button == BUTTON.UP then 107 y_chg = y_chg + yi 108 elseif button == BUTTON.UPR then 109 y_chg = y_chg + yir 110 elseif button == BUTTON.DOWN then 111 y_chg = y_chg - yi 112 elseif button == BUTTON.DOWNR then 113 y_chg = y_chg - yir 114 elseif timeout >= 0 then--and rb.button_queue_count() < 1 then 115 break; 116 end 117 118 if x_chg ~= 0 or y_chg ~= 0 then 119 timeout = timeout + 1 120 end 121 end 122 123 x = x + x_chg 124 y = y + y_chg 125 126 return cancel, select, x_chg, x, y_chg, y, 0xffff 127end -- dpad 128 129local function ctx_loop() 130 local loopfn = ctx_loop 131 ctx_loop = empty_fn() --prevent another execution 132 local mt, ft = get_menu() 133 local i 134 repeat 135 if menu_ctx.update then mt, ft = get_menu(); menu_ctx.update = false end 136 _, i = print_menu(mt, ft, menu_ctx.start, p_settings) 137 until menu_ctx.quit 138 139 ctx_loop = loopfn --restore for another run 140end 141 142--[[ push_ctx() save surrent menu and load another ]] 143local function push_ctx(new_getmenu) 144 last_ctx = last_ctx or {} 145 submenu_insert(last_ctx, menu_ctx) 146 menu_ctx.getmenu = get_menu 147 menu_ctx.settings = p_settings 148 --menu_ctx is a new variable after this point 149 submenu_set_defaults() 150 menu_ctx.update = true 151 if type(new_getmenu) == 'function' then 152 get_menu = new_getmenu 153 end 154end 155 156--[[ pop_ctx() restore last menu ]] 157local function pop_ctx() 158 menu_ctx = submenu_remove(last_ctx) 159 if menu_ctx then 160 get_menu = menu_ctx.getmenu 161 p_settings = menu_ctx.settings 162 if menu_ctx.restorefn then 163 menu_ctx.restorefn(menu_t, func_t) 164 menu_ctx.restorefn = nil 165 end 166 menu_ctx.getmenu = nil 167 menu_ctx.settings = nil 168 menu_ctx.update = true 169 return true 170 end 171end 172 173--[[ display_context_menu_internal() supplies a new get_menu function that returns 174 the context menu 'user_context_fn' supplied by set_menu() 175 this menu is immediately displayed and when finished will 176 automatically restore the last menu 177]] 178local function display_context_menu_internal(sel) 179 if sel <= 0 or not menu_ctx.user_context_fn then return false end 180 local parent = submenu_get_parent() or 0 181 local user_context_fn = menu_ctx.user_context_fn 182 183 local function display_context_menu(i, menu_t, func_t) 184 local function new_getmenu() 185 local mt, ft = user_context_fn(parent, i, menu_t, func_t) 186 ft[0] = pop_ctx --set back fn 187 return mt, ft 188 end 189 push_ctx(new_getmenu) 190 return true 191 end 192 193 --save the current function in closure restore_fn for later 194 local funct = func_t[sel] 195 local function restore_fn(mt, ft) 196 ft[sel] = funct 197 menu_ctx.start = sel - 1 198 end 199 200 menu_ctx.restorefn = restore_fn 201 -- insert into the current fn table so it gets execd by the menu 202 func_t[sel] = display_context_menu 203 204 return true 205end 206 207--[[ submenu_get_parent() gets the parent of the top most level 208 if lv is supplied it instead gets the parent of that level ]] 209function submenu_get_parent(lv) 210 lv = lv or #menu_ctx.collapse_fn or 1 211 collectgarbage("step") 212 local t = menu_ctx.collapse_fn[lv] or {} 213 return t[2] or -1, lv 214end 215 216--[[ submenu_collapse() collapses submenu till level or ROOT is reached ]] 217function submenu_collapse(parent, lv) 218 local lv_out, menu_sz = 0, 0 219 local item_out = -1 220 local items_removed = 0 221 if lv <= #menu_ctx.collapse_fn then 222 repeat 223 local collapse_fn = submenu_remove(menu_ctx.collapse_fn) 224 if collapse_fn then 225 lv_out, item_out, menu_sz = collapse_fn[1](parent, menu_t, func_t) 226 items_removed = items_removed + menu_sz 227 end 228 229 until not collapse_fn or lv >= lv_out 230 end 231 return lv_out, item_out, items_removed 232end 233 234--[[ submenu_create() supply level of submenu > 0, ROOT is lv 0 235 supply menu strings table and function table 236 closure returned run this function to expand the menu 237]] 238function submenu_create(lv, mt, ft) 239 if lv < 1 then error("Level < 1") end 240 if type(mt) ~= 'table' or type(ft) ~= 'table' then 241 error("mt and ft must be tables") 242 end 243 244 -- everything in lua is 1 based menu level is no exception 245 local lv_tab = string.rep ("\t", lv) 246 local function submenu_closure(i, m, f) 247 menu_ctx.lv = lv 248 local lv_out, menusz_out, start_item 249 local item_in, item_out = i, i 250 if lv <= #menu_ctx.collapse_fn then --something else expanded?? 251 repeat 252 local collapse_fn = submenu_remove(menu_ctx.collapse_fn) 253 if collapse_fn then 254 lv_out, item_out, menusz_out = collapse_fn[1](i, m, f) 255 -- if the item i is below this menu, it needs to shift too 256 if item_in > item_out then i = i - (menusz_out) end 257 end 258 until not collapse_fn or lv >= lv_out 259 menu_ctx.start = i 260 if item_out == item_in then return end 261 end 262 263 local menu_sz = #mt 264 menu_ctx.start = i 265 start_item = i 266 menu_ctx.update = true 267 for item, _ in ipairs(mt) do 268 i = i + 1 269 submenu_insert(m, i, lv_tab .. mt[item]) 270 submenu_insert(f, i, ft[item]) 271 end 272 273 local function collapse_closure(i, m, f) 274 --creates a closure around lv, start_item and menu_sz 275 for j = 1, menu_sz, 1 do 276 submenu_remove(m, start_item + 1) 277 submenu_remove(f, start_item + 1) 278 end 279 return lv, start_item, menu_sz 280 end 281 282 submenu_insert(menu_ctx.collapse_fn, lv, {collapse_closure, start_item}) 283 return true 284 end 285 286 return submenu_closure 287end 288 289-- 290function submenu_set_defaults(settings, ctx) 291 p_settings = settings or {wrap = true, hasheader = true, justify = "left", dpad_fn = dpad} 292 menu_ctx = ctx or {collapse_fn = {}, lv = 0, update = false, start = 1} 293end 294 295--[[ get_menu() returns the ROOT string and fn tables]] 296function get_menu() 297 return menu_t, func_t 298end 299 300--[[ set_menu() set your menu and the menu has now been entered ]] 301function set_menu(mt, ft, user_context_fn, settings) 302 303 submenu_set_defaults(settings) 304 if type(user_context_fn) == 'function' then 305 display_context_menu = display_context_menu_internal 306 menu_ctx.user_context_fn = user_context_fn 307 else 308 display_context_menu = empty_fn 309 menu_ctx.user_context_fn = false 310 end 311 p_settings = settings or p_settings 312 menu_t, func_t = mt, ft 313 ctx_loop() 314end