A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1--[[ Lua Print functions
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]]
23
24--[[ Exposed Functions
25
26 _print.clear
27 _print.f
28 _print.opt
29 _print.opt.area
30 _print.opt.autoupdate
31 _print.opt.color
32 _print.opt.column
33 _print.opt.defaults
34 _print.opt.get
35 _print.opt.justify
36 _print.opt.line
37 _print.opt.overflow
38 _print.opt.sel_line
39 _print.opt.set
40
41]]
42
43if not rb.lcd_framebuffer then rb.splash(rb.HZ, "No Support!") return nil end
44
45local _print = {} do
46
47 -- internal constants
48 local _clr = require("color") -- _clr functions required
49
50 local _NIL = nil -- _NIL placeholder
51 local _LCD = rb.lcd_framebuffer()
52 local WHITE = _clr.set(-1, 255, 255, 255)
53 local BLACK = _clr.set(0, 0, 0, 0)
54 local DRMODE_SOLID = 3
55 local col_buf, s_lines = {}, {}
56 local _p_opts = _NIL
57 local tabstring = string.rep(" ", 2)
58-- print internal helper functions
59--------------------------------------------------------------------------------
60 -- clamps value to >= min and <= max
61 local function clamp(val, min, max)
62 -- Warning doesn't check if min < max
63 if val < min then
64 return min
65 elseif val < max then
66 return val
67 end
68 return max
69 end
70
71 -- Gets size of text
72 local function text_extent(msg, font)
73 -- res, w, h
74 return rb.font_getstringsize(msg, font or rb.FONT_UI)
75 end
76
77 -- Updates a single line on the screen
78 local function update_line(enabled, opts, line, h)
79 if enabled ~= true then return end
80 local o = opts or _p_opts
81 -- updates screen in specified rectangle
82 rb.lcd_update_rect(o.x - 1, o.y + line * h,
83 clamp(o.x + o.width, 1, rb.LCD_WIDTH) - 1,
84 clamp(o.y + line * h + 1 + h, 1, rb.LCD_HEIGHT) - 1)
85 end
86
87 -- Clears a single line on the screen
88 local function clear_line(opts, line, h)
89 local o = opts or _p_opts
90 _LCD:clear(o.bg_pattern, o.x, o.y + line * h + 1,
91 o.x + o.width, line * h + h + o.y)
92 end
93
94 -- Sets the maximum number of lines on the screen
95 local function max_lines(opts)
96 local h = opts.height
97 local _, _, th = text_extent("W", opts.font)
98 return h / th
99 end
100
101 --saves the items displayed for side to side scroll
102 local function col_buf_insert(msg, line, _p_opts)
103 --if _p_opts.line <= 1 then col_buf = {} end
104 if not col_buf[line] then
105 table.insert(col_buf, line, msg)
106 end
107 end
108
109 --replaces / strips tab characters
110 local function check_escapes(o, msg)
111--[[ --for replacing a variety of escapes
112 local tabsz = 2
113 local tabstr = string.rep(" ", tabsz)
114 local function repl(esc)
115 local ret = ""
116 if esc:sub(1,1) == "\t" then ret = string.rep(tabstr, esc:len()) end
117 return ret
118 end
119 msg = msg:gsub("(%c+)", repl)
120]]
121 msg = msg:gsub("\t", tabstring)
122 local res, w, h = text_extent(msg, o.font)
123 return w, h, msg
124 end
125--------------------------------------------------------------------------------
126 local function set_linedesc(t_linedesc, opts)
127 local o = opts or _print.opt.get(true)
128 --local out = function() local t = {} for k, v in pairs(o) do t[#t + 1] = tostring(k) t[#t + 1] = tostring(v) end return table.concat(t, "\n") end
129 --rb.splash_scroller(1000, out())
130 local linedesc ={
131 --These are the defaults - changes will be made below if you supplied t_linedesc
132 indent = 0, -- internal indent text
133 line = 0, -- line index within group
134 nlines = 1, -- selection grouping
135 offset = 0, -- internal item offset
136 scroll = true,
137 selected = false, --internal
138 separator_height = 0,
139 line_separator = false,
140 show_cursor = false,
141 show_icons = false,
142 icon = -1,
143 icon_fn = function() return -1 end,
144 style = rb.STYLE_COLORBAR,
145 text_color = o.fg_pattern or WHITE,
146 line_color = o.bg_pattern or BLACK,
147 line_end_color= o.bg_pattern or BLACK,
148 }
149 if type(t_linedesc) == "table" then
150
151 if not o.linedesc then
152 o.linedesc = {}
153 for k, v in pairs(linedesc) do
154 o.linedesc[k] = v
155 end
156 end
157
158 for k, v in pairs(t_linedesc) do
159 o.linedesc[k] = v
160 end
161 if o.linedesc.separator_height > 0 then
162 o.linedesc.line_separator = true
163 end
164 return
165 end
166 o.linedesc = linedesc
167 return o.linedesc
168 end
169
170 -- set defaults for print view
171 local function set_defaults()
172 _p_opts = { x = 1,
173 y = 1,
174 width = rb.LCD_WIDTH - 1,
175 height = rb.LCD_HEIGHT - 1,
176 font = rb.FONT_UI,
177 drawmode = DRMODE_SOLID,
178 fg_pattern = WHITE,
179 bg_pattern = BLACK,
180 sel_pattern = WHITE,
181 line = 1,
182 max_line = _NIL,
183 col = 0,
184 header = false, --Internal use - treats first entry as header
185 ovfl = "auto", -- auto, manual, none
186 justify = "left", --left, center, right
187 autoupdate = true, --updates screen as items are added
188 drawsep = false, -- separator between items
189 }
190 set_linedesc(nil, _p_opts) -- default line display
191 _p_opts.max_line = max_lines(_p_opts)
192
193 s_lines, col_buf = {}, {}
194 return _p_opts
195 end
196
197 -- returns table with settings for print
198 -- if bByRef is _NIL or false then a copy is returned
199 local function get_settings(bByRef)
200 _p_opts = _p_opts or set_defaults()
201 if not bByRef then
202 -- shallow copy of table
203 local copy = {}
204 for k, v in pairs(_p_opts) do
205 copy[k] = v
206 end
207 return copy
208 end
209
210 return _p_opts
211 end
212
213 -- sets the settings for print with your passed table
214 local function set_settings(t_opts)
215 _p_opts = t_opts or set_defaults()
216 if t_opts then
217 _p_opts.max_line = max_lines(_p_opts)
218 col_buf = {}
219 end
220 end
221
222 -- sets colors for print
223 local function set_color(fgclr, bgclr, selclr)
224 local o = get_settings(true)
225
226 if fgclr ~= _NIL then
227 o.fg_pattern, o.sel_pattern = fgclr, fgclr
228 end
229 o.sel_pattern = selclr or o.sel_pattern
230 o.bg_pattern = bgclr or o.bg_pattern
231 end
232
233 -- helper function sets up colors/marker for selected items
234 local function show_selected(iLine, msg)
235 if rb.LCD_DEPTH == 2 then -- invert 2-bit screens
236 local o = get_settings() -- using a copy of opts so changes revert
237 if not o then rb.set_viewport() return end
238 o.fg_pattern = 3 - o.fg_pattern
239 o.bg_pattern = 3 - o.bg_pattern
240 rb.set_viewport(o)
241 o = _NIL
242 else
243 show_selected = function() end -- no need to check again
244 end
245 end
246
247 -- sets line explicitly or increments line if line is _NIL
248 local function set_line(iLine)
249 local o = get_settings(true)
250
251 o.line = iLine or o.line + 1
252
253 if(o.line < 1 or o.line > o.max_line) then
254 o.line = 1
255 end
256 end
257
258 -- clears the set print area
259 local function clear()
260 local o = get_settings(true)
261 _LCD:clear(o.bg_pattern, o.x, o.y, o.x + o.width, o.y + o.height)
262 if o.autoupdate == true then rb.lcd_update() end
263 rb.lcd_scroll_stop()
264 set_line(1)
265 for i=1, #col_buf do col_buf[i] = _NIL end
266 s_lines = {}
267 collectgarbage("collect")
268 end
269
270 -- screen update after each call to print.f
271 local function set_update(bAutoUpdate)
272 local o = get_settings(true)
273 o.autoupdate = bAutoUpdate or false
274 end
275
276 -- sets print area
277 local function set_area(x, y, w, h)
278 local o = get_settings(true)
279 o.x, o.y = clamp(x, 1, rb.LCD_WIDTH), clamp(y, 1, rb.LCD_HEIGHT)
280 o.width, o.height = clamp(w, 1, rb.LCD_WIDTH - o.x), clamp(h, 1, rb.LCD_HEIGHT - o.y)
281 o.max_line = max_lines(_p_opts)
282
283 clear()
284 return o.line, o.max_line
285 end
286
287 -- when string is longer than print width scroll -- "auto", "manual", "none"
288 local function set_overflow(str_mode)
289 -- "auto", "manual", "none"
290 local str_mode = str_mode or "auto"
291 local o = get_settings(true)
292 o.ovfl = str_mode:lower()
293 col_buf = {}
294 end
295
296 -- aligns text to: "left", "center", "right"
297 local function set_justify(str_mode)
298 -- "left", "center", "right"
299 local str_mode = str_mode or "left"
300 local o = get_settings(true)
301 o.justify = str_mode:lower()
302 end
303
304 -- selects line
305 local function select_line(iLine)
306 s_lines[iLine] = true
307 end
308
309 -- Internal print function
310 local function print_internal(t_opts, x, w, h, msg)
311
312 local linedesc
313 local line_separator = false
314 local o = t_opts
315 local ld = o.linedesc or set_linedesc()
316 local show_cursor = ld.show_cursor or 0
317 local line_indent = 0
318
319 local linestyle = ld.style or rb.STYLE_COLORBAR
320
321 if o.justify ~= "left" then
322 line_indent = (o.width - w) --"right"
323 if o.justify == "center" then
324 line_indent = line_indent / 2
325 end
326 end
327
328 local line = o.line - 1 -- rb is 0-based lua is 1-based
329
330 if o.ovfl == "manual" then --save msg for later side scroll
331 col_buf_insert(msg, o.line, o)
332 end
333
334 -- bit of a pain to set the fields this way but its much more efficient than iterating a table to set them
335 local function set_desc(tld, scroll, separator_height, selected, style, indent, text_color, line_color, line_end_color)
336 tld.scroll = scroll
337 tld.separator_height = separator_height
338 tld.selected = selected
339 tld.style = style
340 tld.indent = indent
341 tld.text_color = text_color
342 tld.line_color = line_color
343 tld.line_end_color = line_end_color
344 end
345
346 line_separator = ld.line_separator or o.drawsep
347 local indent = line_indent < 0 and 0 or line_indent --rb scroller doesn't like negative offset!
348 if o.line == 1 and o.header then
349 set_desc(ld, true, 1, false, rb.STYLE_DEFAULT,
350 indent, o.fg_pattern, o.bg_pattern, o.bg_pattern)
351 ld.show_cursor = false
352 elseif s_lines[o.line] then
353 --/* Display line selector */
354 local style = show_cursor == true and rb.STYLE_DEFAULT or linestyle
355 local ovfl = (o.ovfl == "auto" and w >= o.width and x == 0)
356 set_desc(ld, ovfl, 0, true, style, indent,
357 o.bg_pattern, o.sel_pattern, o.sel_pattern)
358 else
359 set_desc(ld, false, 0, false, rb.STYLE_DEFAULT,line_indent,
360 o.fg_pattern, o.bg_pattern, o.bg_pattern)
361 end
362
363 if ld.show_icons then
364 ld.icon = ld.icon_fn(line, ld.icon or -1)
365 end
366
367 rb.lcd_put_line(x, line *h, msg, ld)
368
369 ld.show_cursor = show_cursor
370 ld.style = linestyle
371 if line_separator then
372 if ld.selected == true then
373 rb.set_viewport(o) -- revert drawmode if selected
374 end
375 if not o.header then
376 rb.lcd_drawline(0, line * h, o.width, line * h)
377 end
378 rb.lcd_drawline(0, line * h + h, o.width, line * h + h) --only to add the last line
379 -- but we don't have an idea which line is the last line here so every line is the last line!
380 end
381
382 --only update the line we changed
383 update_line(o.autoupdate, o, line, h)
384
385 set_line(_NIL) -- increments line counter
386 end
387
388 -- Helper function that acts mostly like a normal printf() would
389 local function printf(fmt, v1, ...)
390 local o = get_settings(true)
391 local w, h, msg, rep
392 local line = o.line - 1 -- rb is 0-based lua is 1-based
393
394 if not (fmt) or (fmt) == "\n" then -- handles blank line / single '\n'
395 local res, w, h = text_extent(" ", o.font)
396
397 clear_line(o, line, h)
398 update_line(o.autoupdate, o, line, h)
399
400 if (fmt) then set_line(_NIL) end
401
402 return o.line, o.max_line, o.width, h
403 end
404
405 fmt, rep = fmt.gsub(fmt or "", "%%h", "%%s")
406 o.header = (rep == 1)
407
408 msg = string.format(fmt, v1, ...)
409
410 show_selected(o.line, msg)
411
412 w, h, msg = check_escapes(o, msg)
413
414 print_internal(o, o.col, w, h, msg)
415
416 return o.line, o.max_line, w, h
417 end
418
419 -- x > 0 scrolls right x < 0 scrolls left
420 local function set_column(x)
421 local o = get_settings()
422 if o.ovfl ~= "manual" then return end -- no buffer stored to scroll
423 rb.lcd_scroll_stop()
424
425 local res, w, h, str, line
426
427 for key, value in pairs(col_buf) do
428 line = key - 1 -- rb is 0-based lua is 1-based
429 o.line = key
430
431 if value then
432 show_selected(key, value)
433 res, w, h = text_extent(value, o.font)
434 clear_line(o, line, h)
435
436 print_internal(o, x + o.col, w, h, value)
437 update_line(o.autoupdate, o, line, h)
438 end
439 end
440 o = _NIL
441 end
442
443 --expose functions to the outside through _print table
444 _print.opt = {}
445 _print.opt.column = set_column
446 _print.opt.color = set_color
447 _print.opt.area = set_area
448 _print.opt.set = set_settings
449 _print.opt.get = get_settings
450 _print.opt.defaults = set_defaults
451 _print.opt.overflow = set_overflow
452 _print.opt.justify = set_justify
453 _print.opt.sel_line = select_line
454 _print.opt.line = set_line
455 _print.opt.linedesc = set_linedesc
456 _print.opt.autoupdate = set_update
457 _print.selected = function() return s_lines end
458 _print.clear = clear
459 _print.f = printf
460
461end --_print functions
462
463return _print
464