From ef72b25660912ff247997089abfb93e9f0b52809 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 7 Mar 2024 10:59:03 +1100 Subject: use prototype inheritance for lua screens, rather than functions this gives us a way to give each screen nice little hooks, like 'onShown' and 'onHidden'. later we can use these hooks to disable bindings for screens that aren't in-use. --- lua/browser.lua | 210 ++++++++++--------- lua/licenses.lua | 29 +-- lua/main_menu.lua | 61 +++--- lua/playing.lua | 439 +++++++++++++++++++-------------------- lua/settings.lua | 597 +++++++++++++++++++++++++++--------------------------- 5 files changed, 676 insertions(+), 660 deletions(-) (limited to 'lua') diff --git a/lua/browser.lua b/lua/browser.lua index a7f0c336..5577d4df 100644 --- a/lua/browser.lua +++ b/lua/browser.lua @@ -6,30 +6,11 @@ local queue = require("queue") local playing = require("playing") local theme = require("theme") local playback = require("playback") +local screen = require("screen") -local browser = {} - -function browser.create(opts) - local screen = {} - screen.root = lvgl.Object(nil, { - flex = { - flex_direction = "column", - flex_wrap = "wrap", - justify_content = "flex-start", - align_items = "flex-start", - align_content = "flex-start", - }, - w = lvgl.HOR_RES(), - h = lvgl.VER_RES(), - }) - screen.root:center() - - screen.status_bar = widgets.StatusBar(screen.root, { - title = opts.title, - }) - - if opts.breadcrumb then - local header = screen.root:Object { +return screen:new { + createUi = function(self) + self.root = lvgl.Object(nil, { flex = { flex_direction = "column", flex_wrap = "wrap", @@ -38,100 +19,113 @@ function browser.create(opts) align_content = "flex-start", }, w = lvgl.HOR_RES(), - h = lvgl.SIZE_CONTENT, - pad_left = 4, - pad_right = 4, - pad_bottom = 2, - bg_opa = lvgl.OPA(100), - bg_color = "#fafafa", - scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF, - } - - header:Label { - text = opts.breadcrumb, - text_font = font.fusion_10, - } + h = lvgl.VER_RES(), + }) + self.root:center() - local buttons = header:Object({ - flex = { - flex_direction = "row", - flex_wrap = "wrap", - justify_content = "flex-end", - align_items = "center", - align_content = "center", - }, - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - pad_column = 4, + self.status_bar = widgets.StatusBar(self.root, { + title = self.title, }) - local original_iterator = opts.iterator:clone() - local enqueue = widgets.IconBtn(buttons, "//lua/img/enqueue.png", "Enqueue") - enqueue:onClicked(function() - queue.add(original_iterator) - playback.playing:set(true) - end) - -- enqueue:add_flag(lvgl.FLAG.HIDDEN) - local play = widgets.IconBtn(buttons, "//lua/img/play_small.png", "Play") - play:onClicked(function() - queue.clear() - queue.add(original_iterator) - playback.playing:set(true) - backstack.push(playing) - end - ) - end - screen.list = lvgl.List(screen.root, { - w = lvgl.PCT(100), - h = lvgl.PCT(100), - flex_grow = 1, - scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF, - }) + if self.breadcrumb then + local header = self.root:Object { + flex = { + flex_direction = "column", + flex_wrap = "wrap", + justify_content = "flex-start", + align_items = "flex-start", + align_content = "flex-start", + }, + w = lvgl.HOR_RES(), + h = lvgl.SIZE_CONTENT, + pad_left = 4, + pad_right = 4, + pad_bottom = 2, + bg_opa = lvgl.OPA(100), + bg_color = "#fafafa", + scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF, + } - local back = screen.list:add_btn(nil, "< Back") - back:onClicked(backstack.pop) - back:add_style(theme.list_item) + header:Label { + text = self.breadcrumb, + text_font = font.fusion_10, + } - screen.focused_item = 0 - screen.last_item = 0 - screen.add_item = function(item) - if not item then return end - screen.last_item = screen.last_item + 1 - local this_item = screen.last_item - local btn = screen.list:add_btn(nil, tostring(item)) - btn:onClicked(function() - local contents = item:contents() - if type(contents) == "userdata" then - backstack.push(function() - return browser.create({ - title = opts.title, - iterator = contents, - breadcrumb = tostring(item), - }) - end) - else + local buttons = header:Object({ + flex = { + flex_direction = "row", + flex_wrap = "wrap", + justify_content = "flex-end", + align_items = "center", + align_content = "center", + }, + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + pad_column = 4, + }) + local original_iterator = self.iterator:clone() + local enqueue = widgets.IconBtn(buttons, "//lua/img/enqueue.png", "Enqueue") + enqueue:onClicked(function() + queue.add(original_iterator) + playback.playing:set(true) + end) + -- enqueue:add_flag(lvgl.FLAG.HIDDEN) + local play = widgets.IconBtn(buttons, "//lua/img/play_small.png", "Play") + play:onClicked(function() queue.clear() - queue.add(contents) + queue.add(original_iterator) playback.playing:set(true) backstack.push(playing) end - end) - btn:onevent(lvgl.EVENT.FOCUSED, function() - screen.focused_item = this_item - if screen.last_item - 5 < this_item then - screen.add_item(opts.iterator()) - end - end) - btn:add_style(theme.list_item) - end + ) + end - for _ = 1, 8 do - local val = opts.iterator() - if not val then break end - screen.add_item(val) - end + self.list = lvgl.List(self.root, { + w = lvgl.PCT(100), + h = lvgl.PCT(100), + flex_grow = 1, + scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF, + }) - return screen -end + local back = self.list:add_btn(nil, "< Back") + back:onClicked(backstack.pop) + back:add_style(theme.list_item) -return browser.create + self.focused_item = 0 + self.last_item = 0 + self.add_item = function(item) + if not item then return end + self.last_item = self.last_item + 1 + local this_item = self.last_item + local btn = self.list:add_btn(nil, tostring(item)) + btn:onClicked(function() + local contents = item:contents() + if type(contents) == "userdata" then + backstack.push(require("browser"):new { + title = self.title, + iterator = contents, + breadcrumb = tostring(item), + }) + else + queue.clear() + queue.add(contents) + playback.playing:set(true) + backstack.push(playing:new()) + end + end) + btn:onevent(lvgl.EVENT.FOCUSED, function() + self.focused_item = this_item + if self.last_item - 5 < this_item then + self.add_item(self.iterator()) + end + end) + btn:add_style(theme.list_item) + end + + for _ = 1, 8 do + local val = self.iterator() + if not val then break end + self.add_item(val) + end + end +} diff --git a/lua/licenses.lua b/lua/licenses.lua index 83437454..fb0e5702 100644 --- a/lua/licenses.lua +++ b/lua/licenses.lua @@ -2,20 +2,23 @@ local backstack = require("backstack") local widgets = require("widgets") local font = require("font") local theme = require("theme") +local screen = require("screen") local function show_license(text) - backstack.push(function() - local screen = widgets.MenuScreen { - show_back = true, - title = "Licenses", - } - screen.root:Label { - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - text_font = font.fusion_10, - text = text, - } - end) + backstack.push(screen:new { + createUi = function(self) + self.menu = widgets.MenuScreen { + show_back = true, + title = "Licenses", + } + self.menu.root:Label { + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + text_font = font.fusion_10, + text = text, + } + end + }) end local function gpl(copyright) @@ -175,4 +178,6 @@ return function() library("tremor", "bsd", function() xiphbsd("Copyright (c) 2002, Xiph.org Foundation") end) + + return menu end diff --git a/lua/main_menu.lua b/lua/main_menu.lua index 1311f8ea..7d47b785 100644 --- a/lua/main_menu.lua +++ b/lua/main_menu.lua @@ -5,41 +5,42 @@ local backstack = require("backstack") local browser = require("browser") local playing = require("playing") local theme = require("theme") +local screen = require("screen") -return function() - local menu = widgets.MenuScreen({}) +return screen:new { + createUi = function() + local menu = widgets.MenuScreen({}) - menu.list = lvgl.List(menu.root, { - w = lvgl.PCT(100), - h = lvgl.PCT(100), - flex_grow = 1, - }) + menu.list = lvgl.List(menu.root, { + w = lvgl.PCT(100), + h = lvgl.PCT(100), + flex_grow = 1, + }) - local now_playing = menu.list:add_btn(nil, "Now Playing") - now_playing:onClicked(function() - backstack.push(playing) - end) - now_playing:add_style(theme.list_item) + local now_playing = menu.list:add_btn(nil, "Now Playing") + now_playing:onClicked(function() + backstack.push(playing:new()) + end) + now_playing:add_style(theme.list_item) - local indexes = database.indexes() - for _, idx in ipairs(indexes) do - local btn = menu.list:add_btn(nil, tostring(idx)) - btn:onClicked(function() - backstack.push(function() - return browser { + local indexes = database.indexes() + for _, idx in ipairs(indexes) do + local btn = menu.list:add_btn(nil, tostring(idx)) + btn:onClicked(function() + backstack.push(browser:new { title = tostring(idx), - iterator = idx:iter() - } + iterator = idx:iter(), + }) end) - end) - btn:add_style(theme.list_item) - end + btn:add_style(theme.list_item) + end - local settings = menu.list:add_btn(nil, "Settings") - settings:onClicked(function() - backstack.push(require("settings").root) - end) - settings:add_style(theme.list_item) + local settings = menu.list:add_btn(nil, "Settings") + settings:onClicked(function() + backstack.push(require("settings"):new()) + end) + settings:add_style(theme.list_item) - return menu -end + return menu + end, +} diff --git a/lua/playing.lua b/lua/playing.lua index 4767e42f..947bdec9 100644 --- a/lua/playing.lua +++ b/lua/playing.lua @@ -4,6 +4,7 @@ local backstack = require("backstack") local font = require("font") local playback = require("playback") local queue = require("queue") +local screen = require("screen") local img = { play = "//lua/img/play.png", @@ -18,219 +19,227 @@ local img = { repeat_disabled = "//lua/img/repeat_disabled.png", } -return function(opts) - local screen = {} - screen.root = lvgl.Object(nil, { - flex = { - flex_direction = "column", - flex_wrap = "wrap", - justify_content = "center", - align_items = "center", - align_content = "center", - }, - w = lvgl.HOR_RES(), - h = lvgl.VER_RES(), - }) - screen.root:center() - - screen.status_bar = widgets.StatusBar(screen.root, { - back_cb = backstack.pop, - transparent_bg = true, - }) - - local info = screen.root:Object { - flex = { - flex_direction = "column", - flex_wrap = "wrap", - justify_content = "center", - align_items = "center", - align_content = "center", - }, - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - flex_grow = 1, - } - - local artist = info:Label { - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - text = "", - text_font = font.fusion_10, - text_align = 2, - } - - local title = info:Label { - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - text = "", - text_align = 2, - } - - local playlist = screen.root:Object { - flex = { - flex_direction = "row", - justify_content = "center", - align_items = "center", - align_content = "center", - }, - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - } - - playlist:Object({ w = 3, h = 1 }) -- spacer - - local cur_time = playlist:Label { - w = lvgl.SIZE_CONTENT, - h = lvgl.SIZE_CONTENT, - text = "", - text_font = font.fusion_10, - } - - playlist:Object({ flex_grow = 1, h = 1 }) -- spacer - - local playlist_pos = playlist:Label { - text = "", - text_font = font.fusion_10, - } - playlist:Label { - text = "/", - text_font = font.fusion_10, - } - local playlist_total = playlist:Label { - text = "", - text_font = font.fusion_10, - } - - playlist:Object({ flex_grow = 1, h = 1 }) -- spacer - - local end_time = playlist:Label { - w = lvgl.SIZE_CONTENT, - h = lvgl.SIZE_CONTENT, - align = lvgl.ALIGN.RIGHT_MID, - text = "", - text_font = font.fusion_10, - } - playlist:Object({ w = 3, h = 1 }) -- spacer - - local scrubber = screen.root:Slider { - w = lvgl.PCT(100), - h = 5, - range = { min = 0, max = 100 }, - value = 0, - } - - scrubber:onevent(lvgl.EVENT.RELEASED, function() - playback.position:set(scrubber:value()) - end) - - local controls = screen.root:Object { - flex = { - flex_direction = "row", - justify_content = "center", - align_items = "center", - align_content = "center", - }, - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - pad_column = 8, - pad_all = 2, - } - - - controls:Object({ flex_grow = 1, h = 1 }) -- spacer - - local repeat_btn = controls:Button {} - repeat_btn:onClicked(function() - queue.repeat_track:set(not queue.repeat_track:get()) - end) - local repeat_img = repeat_btn:Image { src = img.repeat_enabled } - - local prev_btn = controls:Button {} - prev_btn:onClicked(queue.previous) - local prev_img = prev_btn:Image { src = img.prev_disabled } - - local play_pause_btn = controls:Button {} - play_pause_btn:onClicked(function() - playback.playing:set(not playback.playing:get()) - end) - play_pause_btn:focus() - local play_pause_img = play_pause_btn:Image { src = img.pause } - - local next_btn = controls:Button {} - next_btn:onClicked(queue.next) - local next_img = next_btn:Image { src = img.next_disabled } - - local shuffle_btn = controls:Button {} - shuffle_btn:onClicked(function() - queue.random:set(not queue.random:get()) - end) - local shuffle_img = shuffle_btn:Image { src = img.shuffle } - - controls:Object({ flex_grow = 1, h = 1 }) -- spacer - - - local format_time = function(time) - return string.format("%d:%02d", time // 60, time % 60) +local is_now_playing_shown = false + +return screen:new { + createUi = function(self) + self.root = lvgl.Object(nil, { + flex = { + flex_direction = "column", + flex_wrap = "wrap", + justify_content = "center", + align_items = "center", + align_content = "center", + }, + w = lvgl.HOR_RES(), + h = lvgl.VER_RES(), + }) + self.root:center() + + self.status_bar = widgets.StatusBar(self.root, { + back_cb = backstack.pop, + transparent_bg = true, + }) + + local info = self.root:Object { + flex = { + flex_direction = "column", + flex_wrap = "wrap", + justify_content = "center", + align_items = "center", + align_content = "center", + }, + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + flex_grow = 1, + } + + local artist = info:Label { + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + text = "", + text_font = font.fusion_10, + text_align = 2, + } + + local title = info:Label { + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + text = "", + text_align = 2, + } + + local playlist = self.root:Object { + flex = { + flex_direction = "row", + justify_content = "center", + align_items = "center", + align_content = "center", + }, + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + } + + playlist:Object({ w = 3, h = 1 }) -- spacer + + local cur_time = playlist:Label { + w = lvgl.SIZE_CONTENT, + h = lvgl.SIZE_CONTENT, + text = "", + text_font = font.fusion_10, + } + + playlist:Object({ flex_grow = 1, h = 1 }) -- spacer + + local playlist_pos = playlist:Label { + text = "", + text_font = font.fusion_10, + } + playlist:Label { + text = "/", + text_font = font.fusion_10, + } + local playlist_total = playlist:Label { + text = "", + text_font = font.fusion_10, + } + + playlist:Object({ flex_grow = 1, h = 1 }) -- spacer + + local end_time = playlist:Label { + w = lvgl.SIZE_CONTENT, + h = lvgl.SIZE_CONTENT, + align = lvgl.ALIGN.RIGHT_MID, + text = "", + text_font = font.fusion_10, + } + playlist:Object({ w = 3, h = 1 }) -- spacer + + local scrubber = self.root:Slider { + w = lvgl.PCT(100), + h = 5, + range = { min = 0, max = 100 }, + value = 0, + } + + scrubber:onevent(lvgl.EVENT.RELEASED, function() + playback.position:set(scrubber:value()) + end) + + local controls = self.root:Object { + flex = { + flex_direction = "row", + justify_content = "center", + align_items = "center", + align_content = "center", + }, + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + pad_column = 8, + pad_all = 2, + } + + + controls:Object({ flex_grow = 1, h = 1 }) -- spacer + + local repeat_btn = controls:Button {} + repeat_btn:onClicked(function() + queue.repeat_track:set(not queue.repeat_track:get()) + end) + local repeat_img = repeat_btn:Image { src = img.repeat_enabled } + + local prev_btn = controls:Button {} + prev_btn:onClicked(queue.previous) + local prev_img = prev_btn:Image { src = img.prev_disabled } + + local play_pause_btn = controls:Button {} + play_pause_btn:onClicked(function() + playback.playing:set(not playback.playing:get()) + end) + play_pause_btn:focus() + local play_pause_img = play_pause_btn:Image { src = img.pause } + + local next_btn = controls:Button {} + next_btn:onClicked(queue.next) + local next_img = next_btn:Image { src = img.next_disabled } + + local shuffle_btn = controls:Button {} + shuffle_btn:onClicked(function() + queue.random:set(not queue.random:get()) + end) + local shuffle_img = shuffle_btn:Image { src = img.shuffle } + + controls:Object({ flex_grow = 1, h = 1 }) -- spacer + + + local format_time = function(time) + return string.format("%d:%02d", time // 60, time % 60) + end + + self.bindings = { + playback.playing:bind(function(playing) + if playing then + play_pause_img:set_src(img.pause) + else + play_pause_img:set_src(img.play) + end + end), + playback.position:bind(function(pos) + if not pos then return end + cur_time:set { + text = format_time(pos) + } + if not scrubber:is_dragged() then + scrubber:set { value = pos } + end + end), + playback.track:bind(function(track) + if not track then return end + end_time:set { + text = format_time(track.duration) + } + title:set { text = track.title } + artist:set { text = track.artist } + scrubber:set { + range = { min = 0, max = track.duration } + } + end), + queue.position:bind(function(pos) + if not pos then return end + playlist_pos:set { text = tostring(pos) } + + next_img:set_src( + pos < queue.size:get() and img.next or img.next_disabled + ) + prev_img:set_src( + pos > 1 and img.prev or img.prev_disabled + ) + end), + queue.random:bind(function(shuffling) + if shuffling then + shuffle_img:set_src(img.shuffle) + else + shuffle_img:set_src(img.shuffle_disabled) + end + end), + queue.repeat_track:bind(function(en) + if en then + repeat_img:set_src(img.repeat_enabled) + else + repeat_img:set_src(img.repeat_disabled) + end + end), + queue.size:bind(function(num) + if not num then return end + playlist_total:set { text = tostring(num) } + end), + } + end, + onShown = function() is_now_playing_shown = true end, + onHidden = function() is_now_playing_shown = false end, + pushIfNotShown = function(self) + if not is_now_playing_shown then + backstack.push(self:new()) + end end - - screen.bindings = { - playback.playing:bind(function(playing) - if playing then - play_pause_img:set_src(img.pause) - else - play_pause_img:set_src(img.play) - end - end), - playback.position:bind(function(pos) - if not pos then return end - cur_time:set { - text = format_time(pos) - } - if not scrubber:is_dragged() then - scrubber:set { value = pos } - end - end), - playback.track:bind(function(track) - if not track then return end - end_time:set { - text = format_time(track.duration) - } - title:set { text = track.title } - artist:set { text = track.artist } - scrubber:set { - range = { min = 0, max = track.duration } - } - end), - queue.position:bind(function(pos) - if not pos then return end - playlist_pos:set { text = tostring(pos) } - - next_img:set_src( - pos < queue.size:get() and img.next or img.next_disabled - ) - prev_img:set_src( - pos > 1 and img.prev or img.prev_disabled - ) - end), - queue.random:bind(function(shuffling) - if shuffling then - shuffle_img:set_src(img.shuffle) - else - shuffle_img:set_src(img.shuffle_disabled) - end - end), - queue.repeat_track:bind(function(en) - if en then - repeat_img:set_src(img.repeat_enabled) - else - repeat_img:set_src(img.repeat_disabled) - end - end), - queue.size:bind(function(num) - if not num then return end - playlist_total:set { text = tostring(num) } - end), - } - - return screen -end +} diff --git a/lua/settings.lua b/lua/settings.lua index 952292e4..9d9ccf2d 100644 --- a/lua/settings.lua +++ b/lua/settings.lua @@ -7,8 +7,7 @@ local display = require("display") local controls = require("controls") local bluetooth = require("bluetooth") local database = require("database") - -local settings = {} +local screen = require("screen") local function SettingsScreen(title) local menu = widgets.MenuScreen { @@ -31,323 +30,331 @@ local function SettingsScreen(title) return menu end -function settings.bluetooth() - local menu = SettingsScreen("Bluetooth") - - local enable_container = menu.content:Object { - flex = { - flex_direction = "row", - justify_content = "flex-start", - align_items = "content", - align_content = "flex-start", - }, - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - pad_bottom = 1, - } - enable_container:Label { text = "Enable", flex_grow = 1 } - local enable_sw = enable_container:Switch {} - enable_sw:onevent(lvgl.EVENT.VALUE_CHANGED, function() - local enabled = enable_sw:enabled() - bluetooth.enabled:set(enabled) - end) - - menu.content:Label { - text = "Paired Device", - pad_bottom = 1, - }:add_style(theme.settings_title) - - local paired_container = menu.content:Object { - flex = { - flex_direction = "row", - justify_content = "flex-start", - align_items = "flex-start", - align_content = "flex-start", - }, - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - pad_bottom = 2, - } - - local paired_device = paired_container:Label { - flex_grow = 1, - } - local clear_paired = paired_container:Button {} - clear_paired:Label { text = "x" } - clear_paired:onClicked(function() - bluetooth.paired_device:set() - end) - - menu.content:Label { - text = "Nearby Devices", - pad_bottom = 1, - }:add_style(theme.settings_title) - - local devices = menu.content:List { - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - } - - menu.bindings = { - bluetooth.enabled:bind(function(en) - if en then - enable_sw:add_state(lvgl.STATE.CHECKED) - else - enable_sw:clear_state(lvgl.STATE.CHECKED) - end - end), - bluetooth.paired_device:bind(function(device) - if device then - paired_device:set { text = device.name } - clear_paired:clear_flag(lvgl.FLAG.HIDDEN) - else - paired_device:set { text = "None" } - clear_paired:add_flag(lvgl.FLAG.HIDDEN) - end - end), - bluetooth.devices:bind(function(devs) - devices:clean() - for _, dev in pairs(devs) do - devices:add_btn(nil, dev.name):onClicked(function() - bluetooth.paired_device:set(dev) - end) - end +local BluetoothSettings = screen:new { + createUi = function(self) + self.menu = SettingsScreen("Bluetooth") + + local enable_container = self.menu.content:Object { + flex = { + flex_direction = "row", + justify_content = "flex-start", + align_items = "content", + align_content = "flex-start", + }, + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + pad_bottom = 1, + } + enable_container:Label { text = "Enable", flex_grow = 1 } + local enable_sw = enable_container:Switch {} + enable_sw:onevent(lvgl.EVENT.VALUE_CHANGED, function() + local enabled = enable_sw:enabled() + bluetooth.enabled:set(enabled) end) - } -end -function settings.headphones() - local menu = SettingsScreen("Headphones") + self.menu.content:Label { + text = "Paired Device", + pad_bottom = 1, + }:add_style(theme.settings_title) + + local paired_container = self.menu.content:Object { + flex = { + flex_direction = "row", + justify_content = "flex-start", + align_items = "flex-start", + align_content = "flex-start", + }, + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + pad_bottom = 2, + } + + local paired_device = paired_container:Label { + flex_grow = 1, + } + local clear_paired = paired_container:Button {} + clear_paired:Label { text = "x" } + clear_paired:onClicked(function() + bluetooth.paired_device:set() + end) - menu.content:Label { - text = "Maximum volume limit", - }:add_style(theme.settings_title) + self.menu.content:Label { + text = "Nearby Devices", + pad_bottom = 1, + }:add_style(theme.settings_title) + + local devices = self.menu.content:List { + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + } + + self.bindings = { + bluetooth.enabled:bind(function(en) + if en then + enable_sw:add_state(lvgl.STATE.CHECKED) + else + enable_sw:clear_state(lvgl.STATE.CHECKED) + end + end), + bluetooth.paired_device:bind(function(device) + if device then + paired_device:set { text = device.name } + clear_paired:clear_flag(lvgl.FLAG.HIDDEN) + else + paired_device:set { text = "None" } + clear_paired:add_flag(lvgl.FLAG.HIDDEN) + end + end), + bluetooth.devices:bind(function(devs) + devices:clean() + for _, dev in pairs(devs) do + devices:add_btn(nil, dev.name):onClicked(function() + bluetooth.paired_device:set(dev) + end) + end + end) + } + end +} + +local HeadphonesSettings = screen:new { + createUi = function(self) + self.menu = SettingsScreen("Headphones") + + self.menu.content:Label { + text = "Maximum volume limit", + }:add_style(theme.settings_title) + + local volume_chooser = self.menu.content:Dropdown { + options = "Line Level (-10 dB)\nCD Level (+6 dB)\nMaximum (+10dB)", + selected = 1, + } + local limits = { -10, 6, 10 } + volume_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function() + -- luavgl dropdown binding uses 0-based indexing :( + local selection = volume_chooser:get('selected') + 1 + volume.limit_db:set(limits[selection]) + end) - local volume_chooser = menu.content:Dropdown { - options = "Line Level (-10 dB)\nCD Level (+6 dB)\nMaximum (+10dB)", - selected = 1, - } - local limits = { -10, 6, 10 } - volume_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function() - -- luavgl dropdown binding uses 0-based indexing :( - local selection = volume_chooser:get('selected') + 1 - volume.limit_db:set(limits[selection]) - end) - - menu.content:Label { - text = "Left/Right balance", - }:add_style(theme.settings_title) - - local balance = menu.content:Slider { - w = lvgl.PCT(100), - h = 5, - range = { min = -100, max = 100 }, - value = 0, - } - balance:onevent(lvgl.EVENT.VALUE_CHANGED, function() - volume.left_bias:set(balance:value()) - end) + self.menu.content:Label { + text = "Left/Right balance", + }:add_style(theme.settings_title) + + local balance = self.menu.content:Slider { + w = lvgl.PCT(100), + h = 5, + range = { min = -100, max = 100 }, + value = 0, + } + balance:onevent(lvgl.EVENT.VALUE_CHANGED, function() + volume.left_bias:set(balance:value()) + end) - local balance_label = menu.content:Label {} + local balance_label = self.menu.content:Label {} - menu.bindings = { - volume.limit_db:bind(function(limit) - for i = 1, #limits do - if limits[i] == limit then - volume_chooser:set { selected = i - 1 } + self.bindings = { + volume.limit_db:bind(function(limit) + for i = 1, #limits do + if limits[i] == limit then + volume_chooser:set { selected = i - 1 } + end end - end - end), - volume.left_bias:bind(function(bias) - balance:set { - value = bias - } - if bias < 0 then - balance_label:set { - text = string.format("Left %.2fdB", bias / 4) + end), + volume.left_bias:bind(function(bias) + balance:set { + value = bias } - elseif bias > 0 then - balance_label:set { - text = string.format("Right %.2fdB", -bias / 4) - } - else - balance_label:set { text = "Balanced" } - end - end), - } - - return menu -end - -function settings.display() - local menu = SettingsScreen("Display") - - local brightness_title = menu.content:Object { - flex = { - flex_direction = "row", - justify_content = "flex-start", - align_items = "flex-start", - align_content = "flex-start", - }, - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - } - brightness_title:Label { text = "Brightness", flex_grow = 1 } - local brightness_pct = brightness_title:Label {} - brightness_pct:add_style(theme.settings_title) - - local brightness = menu.content:Slider { - w = lvgl.PCT(100), - h = 5, - range = { min = 0, max = 100 }, - value = display.brightness:get(), - } - brightness:onevent(lvgl.EVENT.VALUE_CHANGED, function() - display.brightness:set(brightness:value()) - end) - - menu.bindings = { - display.brightness:bind(function(b) - brightness_pct:set { text = tostring(b) .. "%" } + if bias < 0 then + balance_label:set { + text = string.format("Left %.2fdB", bias / 4) + } + elseif bias > 0 then + balance_label:set { + text = string.format("Right %.2fdB", -bias / 4) + } + else + balance_label:set { text = "Balanced" } + end + end), + } + end +} + +local DisplaySettings = screen:new { + createUi = function(self) + self.menu = SettingsScreen("Display") + + local brightness_title = self.menu.content:Object { + flex = { + flex_direction = "row", + justify_content = "flex-start", + align_items = "flex-start", + align_content = "flex-start", + }, + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + } + brightness_title:Label { text = "Brightness", flex_grow = 1 } + local brightness_pct = brightness_title:Label {} + brightness_pct:add_style(theme.settings_title) + + local brightness = self.menu.content:Slider { + w = lvgl.PCT(100), + h = 5, + range = { min = 0, max = 100 }, + value = display.brightness:get(), + } + brightness:onevent(lvgl.EVENT.VALUE_CHANGED, function() + display.brightness:set(brightness:value()) end) - } - return menu -end + self.bindings = { + display.brightness:bind(function(b) + brightness_pct:set { text = tostring(b) .. "%" } + end) + } + end +} -function settings.input() - local menu = SettingsScreen("Input Method") +local InputSettings = screen:new { + createUi = function(self) + self.menu = SettingsScreen("Input Method") - menu.content:Label { - text = "Control scheme", - }:add_style(theme.settings_title) + self.menu.content:Label { + text = "Control scheme", + }:add_style(theme.settings_title) - local schemes = controls.schemes() - local option_to_scheme = {} - local scheme_to_option = {} + local schemes = controls.schemes() + local option_to_scheme = {} + local scheme_to_option = {} - local option_idx = 0 - local options = "" + local option_idx = 0 + local options = "" - for i, v in pairs(schemes) do - option_to_scheme[option_idx] = i - scheme_to_option[i] = option_idx - if option_idx > 0 then - options = options .. "\n" + for i, v in pairs(schemes) do + option_to_scheme[option_idx] = i + scheme_to_option[i] = option_idx + if option_idx > 0 then + options = options .. "\n" + end + options = options .. v + option_idx = option_idx + 1 end - options = options .. v - option_idx = option_idx + 1 - end - - local controls_chooser = menu.content:Dropdown { - options = options, - } - menu.bindings = { - controls.scheme:bind(function(s) - local option = scheme_to_option[s] - controls_chooser:set({ selected = option }) + local controls_chooser = self.menu.content:Dropdown { + options = options, + } + + self.bindings = { + controls.scheme:bind(function(s) + local option = scheme_to_option[s] + controls_chooser:set({ selected = option }) + end) + } + + controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function() + local option = controls_chooser:get('selected') + local scheme = option_to_scheme[option] + controls.scheme:set(scheme) end) - } - - controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function() - local option = controls_chooser:get('selected') - local scheme = option_to_scheme[option] - controls.scheme:set(scheme) - end) - - menu.content:Label { - text = "Scroll Sensitivity", - }:add_style(theme.settings_title) - - local slider_scale = 4; -- Power steering - local sensitivity = menu.content:Slider { - w = lvgl.PCT(90), - h = 5, - range = { min = 0, max = 255/slider_scale }, - value = controls.scroll_sensitivity:get()/slider_scale, - } - sensitivity:onevent(lvgl.EVENT.VALUE_CHANGED, function() - controls.scroll_sensitivity:set(sensitivity:value()*slider_scale) - end) - - return menu -end - -function settings.database() - local menu = SettingsScreen("Database") - local db = require("database") - widgets.Row(menu.content, "Schema version", db.version()) - widgets.Row(menu.content, "Size on disk", string.format("%.1f KiB", db.size() / 1024)) - local actions_container = menu.content:Object { - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - flex = { - flex_direction = "row", - justify_content = "center", - align_items = "space-evenly", - align_content = "center", - }, - pad_top = 4, - pad_column = 4, - } - actions_container:add_style(theme.list_item) - - local update = actions_container:Button {} - update:Label { text = "Update" } - update:onClicked(function() - database.update() - end) -end - -function settings.firmware() - local menu = SettingsScreen("Firmware") - local version = require("version") - widgets.Row(menu.content, "ESP32", version.esp()) - widgets.Row(menu.content, "SAMD21", version.samd()) - widgets.Row(menu.content, "Collator", version.collator()) -end - -function settings.root() - local menu = widgets.MenuScreen { - show_back = true, - title = "Settings", - } - menu.list = menu.root:List { - w = lvgl.PCT(100), - h = lvgl.PCT(100), - flex_grow = 1, - } - - local function section(name) - menu.list:add_text(name):add_style(theme.list_heading) + self.menu.content:Label { + text = "Scroll Sensitivity", + }:add_style(theme.settings_title) + + local slider_scale = 4; -- Power steering + local sensitivity = self.menu.content:Slider { + w = lvgl.PCT(90), + h = 5, + range = { min = 0, max = 255 / slider_scale }, + value = controls.scroll_sensitivity:get() / slider_scale, + } + sensitivity:onevent(lvgl.EVENT.VALUE_CHANGED, function() + controls.scroll_sensitivity:set(sensitivity:value() * slider_scale) + end) end - - local function submenu(name, fn) - local item = menu.list:add_btn(nil, name) - item:onClicked(function() - backstack.push(fn) +} + +local DatabaseSettings = screen:new { + createUi = function(self) + self.menu = SettingsScreen("Database") + local db = require("database") + widgets.Row(self.menu.content, "Schema version", db.version()) + widgets.Row(self.menu.content, "Size on disk", string.format("%.1f KiB", db.size() / 1024)) + + local actions_container = self.menu.content:Object { + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + flex = { + flex_direction = "row", + justify_content = "center", + align_items = "space-evenly", + align_content = "center", + }, + pad_top = 4, + pad_column = 4, + } + actions_container:add_style(theme.list_item) + + local update = actions_container:Button {} + update:Label { text = "Update" } + update:onClicked(function() + database.update() end) - item:add_style(theme.list_item) end +} + +local FirmwareSettings = screen:new { + createUi = function(self) + self.menu = SettingsScreen("Firmware") + local version = require("version") + widgets.Row(self.menu.content, "ESP32", version.esp()) + widgets.Row(self.menu.content, "SAMD21", version.samd()) + widgets.Row(self.menu.content, "Collator", version.collator()) + end +} - section("Audio") - submenu("Bluetooth", settings.bluetooth) - submenu("Headphones", settings.headphones) +local LicensesScreen = screen:new { + createUi = function(self) + self.root = require("licenses")() + end +} + +return screen:new { + createUi = function(self) + self.menu = widgets.MenuScreen { + show_back = true, + title = "Settings", + } + self.list = self.menu.root:List { + w = lvgl.PCT(100), + h = lvgl.PCT(100), + flex_grow = 1, + } + + local function section(name) + self.list:add_text(name):add_style(theme.list_heading) + end - section("Interface") - submenu("Display", settings.display) - submenu("Input Method", settings.input) + local function submenu(name, class) + local item = self.list:add_btn(nil, name) + item:onClicked(function() + backstack.push(class:new()) + end) + item:add_style(theme.list_item) + end - section("System") - submenu("Database", settings.database) - submenu("Firmware", settings.firmware) - submenu("Licenses", function() - return require("licenses")() - end) + section("Audio") + submenu("Bluetooth", BluetoothSettings) + submenu("Headphones", HeadphonesSettings) - return menu -end + section("Interface") + submenu("Display", DisplaySettings) + submenu("Input Method", InputSettings) -return settings + section("System") + submenu("Database", DatabaseSettings) + submenu("Firmware", FirmwareSettings) + submenu("Licenses", LicensesScreen) + end +} -- cgit v1.2.3 From eba5adeb8cc606b4d685132248c6481c0aca53f6 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 7 Mar 2024 11:16:56 +1100 Subject: Show the now playing screen after being locked for a while --- lua/main.lua | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'lua') diff --git a/lua/main.lua b/lua/main.lua index 5cbbf0a6..4291c3da 100644 --- a/lua/main.lua +++ b/lua/main.lua @@ -1,8 +1,13 @@ local font = require("font") local vol = require("volume") +local controls = require("controls") +local time = require("time") + +local lock_time = time.ticks() -- Set up property bindings that are used across every screen. GLOBAL_BINDINGS = { + -- Show an alert with the current volume whenver the volume changes. vol.current_pct:bind(function(pct) require("alerts").show(function() local container = lvgl.Object(nil, { @@ -32,6 +37,18 @@ GLOBAL_BINDINGS = { container:center() end) end), + -- When the device has been locked for a while, default to showing the now + -- playing screen after unlocking. + controls.lock_switch:bind(function(locked) + if locked then + lock_time = time.ticks() + elseif time.ticks() - lock_time > 8000 then + local queue = require("queue") + if queue.size:get() > 0 then + require("playing"):pushIfNotShown() + end + end + end), } local backstack = require("backstack") -- cgit v1.2.3