summaryrefslogtreecommitdiffstats
path: root/apps/plugins/lua/include_lua/printsubmenu.lua
blob: f4b93db98de8f60a9b057d2eb0efb9c4d62e2660 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
--[[
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2021 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
--GLOBALS
menu_ctx = {}
submenu_insert = table.insert
submenu_remove = table.remove

local last_ctx = false
local p_settings
local function empty_fn() end

--[[root menu tables
expanded menus get inserted / removed
and context menus replace them but unless you
want a new root menu they are never overwritten
func_t functions get 3 variables passed by the menu_system
func_t[i] =function sample(i, menu_t, func_t]
this function gets run on user selection
for every function in func_t:
'i' is the selected item
'menu_t' is the current strings table
'func_t' is the current function table

menu_t[i] will returnm the text of the item user selected and
func_t[i] will return the function we are currently in
]]

menu_t = {}
func_t = {}

require("printmenus")

local BUTTON = require("menubuttons")
local last_sel = 0

local function display_context_menu() end -- forward declaration

local function dpad(x, xi, xir, y, yi, yir, timeout, overflow, selected)
    local scroll_is_fixed = overflow ~= "manual"
    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
            last_sel = 1
            timeout = timeout + 1
        elseif button == BUTTON.SELR then
            last_sel = 2
            if display_context_menu(selected or -1) == true then
                select = 1
                break;
            end
            timeout = timeout + 1
        elseif button == BUTTON.SELREL then
            if last_sel == 1 then
                select = 1
            end
            last_sel = 0
            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, 0xffff
end -- dpad

local function ctx_loop()
    local loopfn = ctx_loop
    ctx_loop = empty_fn() --prevent another execution
    local mt, ft = get_menu()
    local i
    repeat
        if menu_ctx.update then mt, ft = get_menu(); menu_ctx.update = false end
        _, i = print_menu(mt, ft, menu_ctx.start, p_settings)
    until menu_ctx.quit

    ctx_loop = loopfn --restore for another run
end

--[[ push_ctx() save surrent menu and load another ]]
local function push_ctx(new_getmenu)
    last_ctx = last_ctx or {}
    submenu_insert(last_ctx, menu_ctx)
    menu_ctx.getmenu = get_menu
    menu_ctx.settings = p_settings
    --menu_ctx is a new variable after this point
    submenu_set_defaults()
    menu_ctx.update = true
    if type(new_getmenu) == 'function' then
        get_menu = new_getmenu
    end
end

--[[ pop_ctx() restore last menu ]]
local function pop_ctx()
    menu_ctx = submenu_remove(last_ctx)
    if menu_ctx then
        get_menu = menu_ctx.getmenu
        p_settings = menu_ctx.settings
        if menu_ctx.restorefn then
            menu_ctx.restorefn(menu_t, func_t)
            menu_ctx.restorefn = nil
        end
        menu_ctx.getmenu = nil
        menu_ctx.settings = nil
        menu_ctx.update = true
        return true
    end
end

--[[ display_context_menu_internal() supplies a new get_menu function that returns
     the context menu 'user_context_fn' supplied by set_menu()
     this menu is immediately displayed and when finished will
     automatically restore the last menu
]]
local function display_context_menu_internal(sel)
        if sel <= 0 or not menu_ctx.user_context_fn then return false end
        local parent = submenu_get_parent() or 0
        local user_context_fn = menu_ctx.user_context_fn

        local function display_context_menu(i, menu_t, func_t)
            local function new_getmenu()
                local mt, ft = user_context_fn(parent, i, menu_t, func_t)
                ft[0] = pop_ctx --set back fn
                return mt, ft
            end
            push_ctx(new_getmenu)
            return true
        end

        --save the current function in closure restore_fn for later
        local funct = func_t[sel]
        local function restore_fn(mt, ft)
            ft[sel] = funct
            menu_ctx.start = sel - 1
        end

        menu_ctx.restorefn = restore_fn
        -- insert into the current fn table so it gets execd by the menu
        func_t[sel] = display_context_menu

        return true
end

--[[ submenu_get_parent() gets the parent of the top most level
     if lv is supplied it instead gets the parent of that level ]]
function submenu_get_parent(lv)
    lv = lv or #menu_ctx.collapse_fn or 1
    collectgarbage("step")
    local t = menu_ctx.collapse_fn[lv] or {}
    return t[2] or -1, lv
end

--[[ submenu_collapse() collapses submenu till level or ROOT is reached ]]
function submenu_collapse(parent, lv)
    local lv_out, menu_sz = 0, 0
    local item_out = -1
    local items_removed = 0
    if lv <= #menu_ctx.collapse_fn then
        repeat
            local collapse_fn = submenu_remove(menu_ctx.collapse_fn)
            if collapse_fn then
                lv_out, item_out, menu_sz = collapse_fn[1](parent, menu_t, func_t)
                items_removed = items_removed + menu_sz
            end

        until not collapse_fn or lv >= lv_out
    end
    return lv_out, item_out, items_removed
end

--[[ submenu_create() supply level of submenu > 0, ROOT is lv 0
    supply menu strings table and function table
    closure returned run this function to expand the menu
]]
function submenu_create(lv, mt, ft)
    if lv < 1 then error("Level < 1") end
    if type(mt) ~= 'table' or type(ft) ~= 'table' then
        error("mt and ft must be tables")
    end

    -- everything in lua is 1 based menu level is no exception
    local lv_tab = string.rep ("\t", lv)
    local function submenu_closure(i, m, f)
        menu_ctx.lv = lv
        local lv_out, menusz_out, start_item
        local item_in, item_out = i, i
        if lv <= #menu_ctx.collapse_fn then --something else expanded??
            repeat
                local collapse_fn = submenu_remove(menu_ctx.collapse_fn)
                if collapse_fn then
                    lv_out, item_out, menusz_out = collapse_fn[1](i, m, f)
                    -- if the item i is below this menu, it needs to shift too
                    if item_in > item_out then i = i - (menusz_out) end
                end
            until not collapse_fn or lv >= lv_out
            menu_ctx.start = i
            if item_out == item_in then return end
        end

        local menu_sz = #mt
        menu_ctx.start = i
        start_item = i
        menu_ctx.update = true
        for item, _ in ipairs(mt) do
            i = i + 1
            submenu_insert(m, i, lv_tab .. mt[item])
            submenu_insert(f, i, ft[item])
        end

        local function collapse_closure(i, m, f)
            --creates a closure around lv, start_item and menu_sz
            for j = 1, menu_sz, 1 do
                submenu_remove(m, start_item + 1)
                submenu_remove(f, start_item + 1)
            end
            return lv, start_item, menu_sz
        end

        submenu_insert(menu_ctx.collapse_fn, lv, {collapse_closure, start_item})
        return true
    end

    return submenu_closure
end

--
function submenu_set_defaults(settings, ctx)
    p_settings = settings or {wrap = true, hasheader = true, justify = "left", dpad_fn = dpad}
    menu_ctx = ctx or {collapse_fn = {}, lv = 0, update = false, start = 1}
end

--[[ get_menu() returns the ROOT string and fn tables]]
function get_menu()
    return menu_t, func_t
end

--[[ set_menu() set your menu and the menu has now been entered ]]
function set_menu(mt, ft, user_context_fn, settings)

    submenu_set_defaults(settings)
    if type(user_context_fn) == 'function' then
        display_context_menu = display_context_menu_internal
        menu_ctx.user_context_fn = user_context_fn
    else
        display_context_menu = empty_fn
        menu_ctx.user_context_fn = false
    end
    p_settings = settings or p_settings
    menu_t, func_t = mt, ft
    ctx_loop()
end