diff options
| author | jacqueline <me@jacqueline.id.au> | 2024-09-09 15:15:00 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2024-09-09 15:15:00 +1000 |
| commit | 2b1a01705d62d08cefd6816ba108c5cae48a50ac (patch) | |
| tree | 20ba16a6259ffc335dbcded84fa6bcbe327e9d84 /lua | |
| parent | 9475d10d1000c7e21a7ea311b0c8ee6a72ef46c4 (diff) | |
| parent | acdc9789c90ed6f083d054cd07930e020123457f (diff) | |
| download | tangara-fw-2b1a01705d62d08cefd6816ba108c5cae48a50ac.tar.gz | |
Merge branch 'main' into jqln/tts
Diffstat (limited to 'lua')
| -rw-r--r-- | lua/browser.lua | 5 | ||||
| -rw-r--r-- | lua/file_browser.lua | 123 | ||||
| -rw-r--r-- | lua/fonts/fusion10 | bin | 198308 -> 386220 bytes | |||
| -rw-r--r-- | lua/fonts/fusion12 | bin | 375744 -> 615260 bytes | |||
| -rw-r--r-- | lua/images.lua | 3 | ||||
| -rw-r--r-- | lua/img/back.png | bin | 0 -> 7254 bytes | |||
| -rw-r--r-- | lua/img/bat/0.png | bin | 627 -> 8430 bytes | |||
| -rw-r--r-- | lua/img/bat/0chg.png | bin | 627 -> 0 bytes | |||
| -rw-r--r-- | lua/img/bat/100.png | bin | 629 -> 8434 bytes | |||
| -rw-r--r-- | lua/img/bat/20.png | bin | 624 -> 8430 bytes | |||
| -rw-r--r-- | lua/img/bat/40.png | bin | 625 -> 8431 bytes | |||
| -rw-r--r-- | lua/img/bat/60.png | bin | 628 -> 8435 bytes | |||
| -rw-r--r-- | lua/img/bat/80.png | bin | 623 -> 8436 bytes | |||
| -rw-r--r-- | lua/img/bat/chg.png | bin | 667 -> 8326 bytes | |||
| -rw-r--r-- | lua/img/bt.png | bin | 4595 -> 10120 bytes | |||
| -rw-r--r-- | lua/img/bt_conn.png | bin | 4363 -> 11932 bytes | |||
| -rw-r--r-- | lua/img/ce.png | bin | 0 -> 1276 bytes | |||
| -rw-r--r-- | lua/img/next.png | bin | 4811 -> 7873 bytes | |||
| -rw-r--r-- | lua/img/pause.png | bin | 4771 -> 5963 bytes | |||
| -rw-r--r-- | lua/img/pausecirc.png | bin | 7054 -> 8247 bytes | |||
| -rw-r--r-- | lua/img/play.png | bin | 4813 -> 6828 bytes | |||
| -rw-r--r-- | lua/img/playcirc.png | bin | 7074 -> 8949 bytes | |||
| -rw-r--r-- | lua/img/prev.png | bin | 4810 -> 7870 bytes | |||
| -rw-r--r-- | lua/img/repeat.png | bin | 5023 -> 8578 bytes | |||
| -rw-r--r-- | lua/img/repeat_off.png | bin | 0 -> 9349 bytes | |||
| -rw-r--r-- | lua/img/settings.png | bin | 5898 -> 12292 bytes | |||
| -rw-r--r-- | lua/img/shuffle_off.png | bin | 0 -> 7791 bytes | |||
| -rw-r--r-- | lua/img/weee.png | bin | 0 -> 1395 bytes | |||
| -rw-r--r-- | lua/licenses.lua | 6 | ||||
| -rw-r--r-- | lua/main.lua | 14 | ||||
| -rw-r--r-- | lua/main_menu.lua | 11 | ||||
| -rw-r--r-- | lua/playing.lua | 48 | ||||
| -rw-r--r-- | lua/settings.lua | 402 | ||||
| -rw-r--r-- | lua/theme_dark.lua | 85 | ||||
| -rw-r--r-- | lua/theme_hicon.lua | 273 | ||||
| -rw-r--r-- | lua/theme_light.lua | 159 | ||||
| -rw-r--r-- | lua/widgets.lua | 68 |
37 files changed, 1007 insertions, 190 deletions
diff --git a/lua/browser.lua b/lua/browser.lua index ce0f7978..406c2e49 100644 --- a/lua/browser.lua +++ b/lua/browser.lua @@ -10,7 +10,7 @@ local theme = require("theme") local screen = require("screen") return screen:new{ - createUi = function(self) + create_ui = function(self) self.root = lvgl.Object(nil, { flex = { flex_direction = "column", @@ -58,7 +58,7 @@ return screen:new{ flex = { flex_direction = "row", flex_wrap = "wrap", - justify_content = "center", + justify_content = "space-between", align_items = "center", align_content = "center" }, @@ -84,6 +84,7 @@ return screen:new{ local play = widgets.IconBtn(buttons, "//lua/img/play_small.png", "Play") play:onClicked(function() queue.clear() + queue.random:set(false) queue.add(original_iterator) playback.playing:set(true) backstack.push(playing:new()) diff --git a/lua/file_browser.lua b/lua/file_browser.lua index 91b84c84..0ccd2c13 100644 --- a/lua/file_browser.lua +++ b/lua/file_browser.lua @@ -10,64 +10,75 @@ local theme = require("theme") local screen = require("screen") local filesystem = require("filesystem") -return screen:new{ - createUi = function(self) - self.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() - }) - self.root:center() +return screen:new { + create_ui = function(self) + self.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() + }) + self.root:center() - self.status_bar = widgets.StatusBar(self, { - back_cb = backstack.pop, - title = self.title - }) + self.status_bar = widgets.StatusBar(self, { + back_cb = backstack.pop, + title = self.title + }) - 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), - scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF - } - theme.set_style(header, "header") + 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), + scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF + } + theme.set_style(header, "header") - if self.breadcrumb then - header:Label{ - text = self.breadcrumb, - text_font = font.fusion_10 - } - end - - widgets.InfiniteList(self.root, self.iterator, { - callback = function(item) - return function() - local is_dir = item:is_directory() - if is_dir then - backstack.push(require("file_browser"):new{ - title = self.title, - iterator = filesystem.iterator(tostring(item)), - breadcrumb = tostring(item) - }) - end - end - end - }) + if self.breadcrumb then + header:Label { + text = self.breadcrumb, + text_font = font.fusion_10 + } end + + widgets.InfiniteList(self.root, self.iterator, { + callback = function(item) + return function() + local is_dir = item:is_directory() + if is_dir then + backstack.push(require("file_browser"):new { + title = self.title, + iterator = filesystem.iterator(item:filepath()), + breadcrumb = item:filepath() + }) + elseif + item:filepath():match("%.playlist$") or + item:filepath():match("%.m3u8?$") then + queue.open_playlist(item:filepath()) + playback.playing:set(true) + backstack.push(playing:new()) + elseif playback.is_playable(item:filepath()) then + queue.clear() + queue.add(item:filepath()) + playback.playing:set(true) + backstack.push(playing:new()) + end + end + end + }) + end } diff --git a/lua/fonts/fusion10 b/lua/fonts/fusion10 Binary files differindex 9fa4b78d..41e3916e 100644 --- a/lua/fonts/fusion10 +++ b/lua/fonts/fusion10 diff --git a/lua/fonts/fusion12 b/lua/fonts/fusion12 Binary files differindex 0fda8b03..3745c17d 100644 --- a/lua/fonts/fusion12 +++ b/lua/fonts/fusion12 diff --git a/lua/images.lua b/lua/images.lua index 1634bc44..5bb33d4d 100644 --- a/lua/images.lua +++ b/lua/images.lua @@ -1,6 +1,7 @@ local lvgl = require("lvgl") local img = { + back = lvgl.ImgData("//lua/img/back.png"), play = lvgl.ImgData("//lua/img/play.png"), play_small = lvgl.ImgData("//lua/img/playcirc.png"), pause = lvgl.ImgData("//lua/img/pause.png"), @@ -8,7 +9,9 @@ local img = { next = lvgl.ImgData("//lua/img/next.png"), prev = lvgl.ImgData("//lua/img/prev.png"), shuffle = lvgl.ImgData("//lua/img/shuffle.png"), + shuffle_off = lvgl.ImgData("//lua/img/shuffle_off.png"), repeat_src = lvgl.ImgData("//lua/img/repeat.png"), -- repeat is a reserved word + repeat_off = lvgl.ImgData("//lua/img/repeat_off.png"), queue = lvgl.ImgData("//lua/img/queue.png"), files = lvgl.ImgData("//lua/img/files.png"), settings = lvgl.ImgData("//lua/img/settings.png"), diff --git a/lua/img/back.png b/lua/img/back.png Binary files differnew file mode 100644 index 00000000..0f34453b --- /dev/null +++ b/lua/img/back.png diff --git a/lua/img/bat/0.png b/lua/img/bat/0.png Binary files differindex 15639ef5..d4c69fd4 100644 --- a/lua/img/bat/0.png +++ b/lua/img/bat/0.png diff --git a/lua/img/bat/0chg.png b/lua/img/bat/0chg.png Binary files differdeleted file mode 100644 index 7267ade1..00000000 --- a/lua/img/bat/0chg.png +++ /dev/null diff --git a/lua/img/bat/100.png b/lua/img/bat/100.png Binary files differindex f762ec2f..443ac6cc 100644 --- a/lua/img/bat/100.png +++ b/lua/img/bat/100.png diff --git a/lua/img/bat/20.png b/lua/img/bat/20.png Binary files differindex b1fcca1b..11fb049e 100644 --- a/lua/img/bat/20.png +++ b/lua/img/bat/20.png diff --git a/lua/img/bat/40.png b/lua/img/bat/40.png Binary files differindex fbba9c3b..3dcfe2e6 100644 --- a/lua/img/bat/40.png +++ b/lua/img/bat/40.png diff --git a/lua/img/bat/60.png b/lua/img/bat/60.png Binary files differindex 97a8b605..9e677571 100644 --- a/lua/img/bat/60.png +++ b/lua/img/bat/60.png diff --git a/lua/img/bat/80.png b/lua/img/bat/80.png Binary files differindex ab90987b..4c393f03 100644 --- a/lua/img/bat/80.png +++ b/lua/img/bat/80.png diff --git a/lua/img/bat/chg.png b/lua/img/bat/chg.png Binary files differindex d604bb76..629586e3 100644 --- a/lua/img/bat/chg.png +++ b/lua/img/bat/chg.png diff --git a/lua/img/bt.png b/lua/img/bt.png Binary files differindex 73f3179f..ae45dfbf 100644 --- a/lua/img/bt.png +++ b/lua/img/bt.png diff --git a/lua/img/bt_conn.png b/lua/img/bt_conn.png Binary files differindex 91f9964d..890e4160 100644 --- a/lua/img/bt_conn.png +++ b/lua/img/bt_conn.png diff --git a/lua/img/ce.png b/lua/img/ce.png Binary files differnew file mode 100644 index 00000000..dae12bc6 --- /dev/null +++ b/lua/img/ce.png diff --git a/lua/img/next.png b/lua/img/next.png Binary files differindex 1f6f044b..d245148e 100644 --- a/lua/img/next.png +++ b/lua/img/next.png diff --git a/lua/img/pause.png b/lua/img/pause.png Binary files differindex e7011821..32c5a2b4 100644 --- a/lua/img/pause.png +++ b/lua/img/pause.png diff --git a/lua/img/pausecirc.png b/lua/img/pausecirc.png Binary files differindex d7e944fa..4c6f4fd8 100644 --- a/lua/img/pausecirc.png +++ b/lua/img/pausecirc.png diff --git a/lua/img/play.png b/lua/img/play.png Binary files differindex a3b8a5af..833cb087 100644 --- a/lua/img/play.png +++ b/lua/img/play.png diff --git a/lua/img/playcirc.png b/lua/img/playcirc.png Binary files differindex f2e48da7..dc394f3e 100644 --- a/lua/img/playcirc.png +++ b/lua/img/playcirc.png diff --git a/lua/img/prev.png b/lua/img/prev.png Binary files differindex b445c75a..ee4273e7 100644 --- a/lua/img/prev.png +++ b/lua/img/prev.png diff --git a/lua/img/repeat.png b/lua/img/repeat.png Binary files differindex 40a7564e..c9941601 100644 --- a/lua/img/repeat.png +++ b/lua/img/repeat.png diff --git a/lua/img/repeat_off.png b/lua/img/repeat_off.png Binary files differnew file mode 100644 index 00000000..b8db6c4d --- /dev/null +++ b/lua/img/repeat_off.png diff --git a/lua/img/settings.png b/lua/img/settings.png Binary files differindex 4fc29e51..e6fbecb3 100644 --- a/lua/img/settings.png +++ b/lua/img/settings.png diff --git a/lua/img/shuffle_off.png b/lua/img/shuffle_off.png Binary files differnew file mode 100644 index 00000000..eb99ccf5 --- /dev/null +++ b/lua/img/shuffle_off.png diff --git a/lua/img/weee.png b/lua/img/weee.png Binary files differnew file mode 100644 index 00000000..ca0a5ddd --- /dev/null +++ b/lua/img/weee.png diff --git a/lua/licenses.lua b/lua/licenses.lua index b8c71f36..404719e3 100644 --- a/lua/licenses.lua +++ b/lua/licenses.lua @@ -8,8 +8,8 @@ local function show_license(text) backstack.push(widgets.MenuScreen:new { show_back = true, title = "Licenses", - createUi = function(self) - widgets.MenuScreen.createUi(self) + create_ui = function(self) + widgets.MenuScreen.create_ui(self) self.root:Label { w = lvgl.PCT(100), h = lvgl.SIZE_CONTENT, @@ -64,7 +64,7 @@ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR I end return function(self) - local container = self.root:Object { + local container = self.content:Object { flex = { flex_direction = "column", flex_wrap = "nowrap", diff --git a/lua/main.lua b/lua/main.lua index 4cd754b6..f79b0e87 100644 --- a/lua/main.lua +++ b/lua/main.lua @@ -14,9 +14,15 @@ local backstack = require("backstack") local main_menu = require("main_menu") local function init_ui() - -- Load the theme within init_ui because the theme needs fonts to be ready. - local theme_dark = require("theme_dark") - theme.set(theme_dark) + -- Theme is set within init_ui because the theme needs fonts to be ready. + -- Set the theme to the saved theme if available + local saved_theme = theme.theme_filename() + local res = theme.load_theme(saved_theme) + if not res then + -- Set a default theme (in case the saved theme does not load) + local default_theme = require("theme_light") + theme.set(default_theme) + end local lock_time = time.ticks() @@ -59,7 +65,7 @@ local function init_ui() elseif time.ticks() - lock_time > 8000 then local queue = require("queue") if queue.size:get() > 0 then - require("playing"):pushIfNotShown() + require("playing"):push_if_not_shown() end end end), diff --git a/lua/main_menu.lua b/lua/main_menu.lua index f3b7a042..a6b46a8a 100644 --- a/lua/main_menu.lua +++ b/lua/main_menu.lua @@ -13,8 +13,8 @@ local img = require("images") local playback = require("playback") return widgets.MenuScreen:new { - createUi = function(self) - widgets.MenuScreen.createUi(self) + create_ui = function(self) + widgets.MenuScreen.create_ui(self) -- At the top, a card showing details about the current track. Hidden if -- there is no track currently playing. @@ -33,9 +33,9 @@ return widgets.MenuScreen:new { margin_all = 2, pad_bottom = 2, pad_column = 4, - border_color = "#FFFFFF", border_width = 1, }) + theme.set_style(now_playing, "now_playing"); local play_pause = now_playing:Image { src = img.play_small } local title = now_playing:Label { @@ -140,6 +140,7 @@ return widgets.MenuScreen:new { w = lvgl.PCT(100), h = lvgl.SIZE_CONTENT, pad_top = 4, + pad_bottom = 2, }) -- local queue_btn = bottom_bar:Button {} @@ -154,13 +155,13 @@ return widgets.MenuScreen:new { }) end) files_btn:Image { src = img.files } - theme.set_style(files_btn, "icon_enabled") + theme.set_style(files_btn, "menu_icon") local settings_btn = bottom_bar:Button {} settings_btn:onClicked(function() backstack.push(require("settings"):new()) end) settings_btn:Image { src = img.settings } - theme.set_style(settings_btn, "icon_enabled") + theme.set_style(settings_btn, "menu_icon") end, } diff --git a/lua/playing.lua b/lua/playing.lua index 90e20f49..b3b4ec4d 100644 --- a/lua/playing.lua +++ b/lua/playing.lua @@ -20,7 +20,7 @@ local icon_enabled_class = "icon_enabled" local icon_disabled_class = "icon_disabled" return screen:new { - createUi = function(self) + create_ui = function(self) self.root = lvgl.Object(nil, { flex = { flex_direction = "column", @@ -74,7 +74,7 @@ return screen:new { align_items = "center", align_content = "center", }, - w = lvgl.PCT(100), + w = lvgl.PCT(95), h = lvgl.SIZE_CONTENT, } @@ -114,11 +114,12 @@ return screen:new { playlist:Object({ w = 3, h = 1 }) -- spacer local scrubber = self.root:Slider { - w = lvgl.PCT(100), + w = lvgl.PCT(90), h = 5, range = { min = 0, max = 100 }, value = 0, } + theme.set_style(scrubber, "scrubber"); local scrubber_desc = widgets.Description(scrubber, "Scrubber") scrubber:onevent(lvgl.EVENT.RELEASED, function() @@ -138,6 +139,8 @@ return screen:new { end end) + self.root:Object({ w = 1, h = 1 }) -- spacer + local controls = self.root:Object { flex = { flex_direction = "row", @@ -158,7 +161,7 @@ return screen:new { queue.repeat_track:set(not queue.repeat_track:get()) end) local repeat_img = repeat_btn:Image { src = img.repeat_src } - theme.set_style(repeat_img, icon_enabled_class) + theme.set_style(repeat_btn, icon_enabled_class) local repeat_desc = widgets.Description(repeat_btn) @@ -171,7 +174,7 @@ return screen:new { end end) local prev_img = prev_btn:Image { src = img.prev } - theme.set_style(prev_img, icon_enabled_class) + theme.set_style(prev_btn, icon_enabled_class) local prev_desc = widgets.Description(prev_btn, "Previous track") local play_pause_btn = controls:Button {} @@ -180,13 +183,13 @@ return screen:new { end) play_pause_btn:focus() local play_pause_img = play_pause_btn:Image { src = img.pause } - theme.set_style(play_pause_img, icon_enabled_class) + theme.set_style(play_pause_btn, icon_enabled_class) local play_pause_desc = widgets.Description(play_pause_btn, "Play") local next_btn = controls:Button {} next_btn:onClicked(queue.next) local next_img = next_btn:Image { src = img.next } - theme.set_style(next_img, icon_disabled_class) + theme.set_style(next_btn, icon_disabled_class) local next_desc = widgets.Description(next_btn, "Next track") local shuffle_btn = controls:Button {} @@ -194,7 +197,7 @@ return screen:new { queue.random:set(not queue.random:get()) end) local shuffle_img = shuffle_btn:Image { src = img.shuffle } - theme.set_style(shuffle_img, icon_enabled_class) + theme.set_style(shuffle_btn, icon_enabled_class) local shuffle_desc = widgets.Description(shuffle_btn) controls:Object({ flex_grow = 1, h = 1 }) -- spacer @@ -223,7 +226,18 @@ return screen:new { end end), playback.track:bind(function(track) - if not track then return end + if not track then + if queue.loading:get() then + title:set { text = "Loading..." } + else + title:set { text = "" } + end + artist:set { text = "" } + cur_time:set { text = format_time(0) } + end_time:set { text = format_time(0) } + scrubber:set { value = 0 } + return + end if track.duration then end_time:set { text = format_time(track.duration) } else @@ -238,22 +252,26 @@ return screen:new { local can_next = pos < queue.size:get() or queue.random:get() theme.set_style( - next_img, can_next and icon_enabled_class or icon_disabled_class + next_btn, can_next and icon_enabled_class or icon_disabled_class ) end), queue.random:bind(function(shuffling) - theme.set_style(shuffle_img, shuffling and icon_enabled_class or icon_disabled_class) + theme.set_style(shuffle_btn, shuffling and icon_enabled_class or icon_disabled_class) if shuffling then + shuffle_img:set_src(img.shuffle) shuffle_desc:set { text = "Disable shuffle" } else + shuffle_img:set_src(img.shuffle_off) shuffle_desc:set { text = "Enable shuffle" } end end), queue.repeat_track:bind(function(en) - theme.set_style(repeat_img, en and icon_enabled_class or icon_disabled_class) + theme.set_style(repeat_btn, en and icon_enabled_class or icon_disabled_class) if en then + repeat_img:set_src(img.repeat_src) repeat_desc:set { text = "Disable track repeat" } else + repeat_img:set_src(img.repeat_off) repeat_desc:set { text = "Enable track repeat" } end end), @@ -263,9 +281,9 @@ return screen:new { end), } end, - onShown = function() is_now_playing_shown = true end, - onHidden = function() is_now_playing_shown = false end, - pushIfNotShown = function(self) + on_show = function() is_now_playing_shown = true end, + on_hide = function() is_now_playing_shown = false end, + push_if_not_shown = function(self) if not is_now_playing_shown then backstack.push(self:new()) end diff --git a/lua/settings.lua b/lua/settings.lua index cb7f65e0..9b77274d 100644 --- a/lua/settings.lua +++ b/lua/settings.lua @@ -7,13 +7,16 @@ local display = require("display") local controls = require("controls") local bluetooth = require("bluetooth") local theme = require("theme") +local filesystem = require("filesystem") local database = require("database") local usb = require("usb") +local font = require("font") +local main_menu = require("main_menu") local SettingsScreen = widgets.MenuScreen:new { show_back = true, - createUi = function(self) - widgets.MenuScreen.createUi(self) + create_ui = function(self) + widgets.MenuScreen.create_ui(self) self.content = self.root:Object { flex = { flex_direction = "column", @@ -30,10 +33,38 @@ local SettingsScreen = widgets.MenuScreen:new { end } +local BluetoothPairing = SettingsScreen:new { + title = "Nearby Devices", + create_ui = function(self) + SettingsScreen.create_ui(self) + + local devices = self.content:List { + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + } + + self.bindings = self.bindings + { + bluetooth.discovered_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) + backstack.pop() + end) + end + end) + } + end, + on_show = function() bluetooth.discovering:set(true) end, + on_hide = function() bluetooth.discovering:set(false) end, +} + local BluetoothSettings = SettingsScreen:new { title = "Bluetooth", - createUi = function(self) - SettingsScreen.createUi(self) + create_ui = function(self) + SettingsScreen.create_ui(self) + + -- Enable/Disable switch local enable_container = self.content:Object { flex = { flex_direction = "row", @@ -52,11 +83,42 @@ local BluetoothSettings = SettingsScreen:new { bluetooth.enabled:set(enabled) end) - theme.set_style(self.content:Label { - text = "Paired Device", - pad_bottom = 1, - }, "settings_title") + self.bindings = 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), + } + + -- Connection status + -- This is presented as a label on the field showing the currently paired + -- device. + + local paired_label = + self.content:Label { + text = "", + pad_bottom = 1, + } + theme.set_style(paired_label, "settings_title") + + self.bindings = self.bindings + { + bluetooth.connecting:bind(function(conn) + if conn then + paired_label:set { text = "Connecting to:" } + else + if bluetooth.connected:get() then + paired_label:set { text = "Connected to:" } + else + paired_label:set { text = "Paired with:" } + end + end + end), + } + -- The name of the currently paired device. local paired_container = self.content:Object { flex = { flex_direction = "row", @@ -78,24 +140,7 @@ local BluetoothSettings = SettingsScreen:new { bluetooth.paired_device:set() end) - theme.set_style(self.content:Label { - text = "Nearby Devices", - pad_bottom = 1, - }, "settings_title") - - local devices = self.content:List { - w = lvgl.PCT(100), - h = lvgl.SIZE_CONTENT, - } - self.bindings = 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 } @@ -105,13 +150,52 @@ local BluetoothSettings = SettingsScreen:new { clear_paired:add_flag(lvgl.FLAG.HIDDEN) end end), - bluetooth.devices:bind(function(devs) + } + + theme.set_style(self.content:Label { + text = "Known Devices", + pad_bottom = 1, + }, "settings_title") + + local devices = self.content:List { + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + } + + -- 'Pair new device' button that goes to the discovery screen. + + local button_container = self.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, + } + button_container:add_style(styles.list_item) + + local pair_new = button_container:Button {} + pair_new:Label { text = "Pair new device" } + pair_new:onClicked(function() + backstack.push(BluetoothPairing:new()) + end) + + + self.bindings = self.bindings + { + bluetooth.known_devices:bind(function(devs) + local group = lvgl.group.get_default() + group.remove_obj(pair_new) devices:clean() for _, dev in pairs(devs) do devices:add_btn(nil, dev.name):onClicked(function() bluetooth.paired_device:set(dev) end) end + group:add_obj(pair_new) end) } end @@ -119,8 +203,8 @@ local BluetoothSettings = SettingsScreen:new { local HeadphonesSettings = SettingsScreen:new { title = "Headphones", - createUi = function(self) - SettingsScreen.createUi(self) + create_ui = function(self) + SettingsScreen.create_ui(self) theme.set_style(self.content:Label { text = "Maxiumum volume limit", @@ -143,7 +227,6 @@ local HeadphonesSettings = SettingsScreen:new { local balance = self.content:Slider { w = lvgl.PCT(100), - h = 5, range = { min = -100, max = 100 }, value = 0, } @@ -183,8 +266,8 @@ local HeadphonesSettings = SettingsScreen:new { local DisplaySettings = SettingsScreen:new { title = "Display", - createUi = function(self) - SettingsScreen.createUi(self) + create_ui = function(self) + SettingsScreen.create_ui(self) local brightness_title = self.content:Object { flex = { @@ -203,7 +286,6 @@ local DisplaySettings = SettingsScreen:new { local brightness = self.content:Slider { w = lvgl.PCT(100), - h = 5, range = { min = 0, max = 100 }, value = display.brightness:get(), } @@ -219,10 +301,69 @@ local DisplaySettings = SettingsScreen:new { end } +local ThemeSettings = SettingsScreen:new { + title = "Theme", + create_ui = function(self) + SettingsScreen.create_ui(self) + + theme.set_style(self.content:Label { + text = "Theme", + }, "settings_title") + + local themeOptions = {} + themeOptions["Dark"] = "/lua/theme_dark.lua" + themeOptions["Light"] = "/lua/theme_light.lua" + themeOptions["High Contrast"] = "/lua/theme_hicon.lua" + + -- Parse theme directory for more themes + local theme_dir_iter = filesystem.iterator("/.themes/") + for dir in theme_dir_iter do + local theme_name = tostring(dir):match("(.+).lua$") + themeOptions[theme_name] = "/sdcard/.themes/" .. theme_name .. ".lua" + end + + local saved_theme = theme.theme_filename(); + local saved_theme_name = saved_theme:match(".+/(.*).lua$") + + local options = "" + local idx = 0 + local selected_idx = -1 + for i, v in pairs(themeOptions) do + if (saved_theme == v) then + selected_idx = idx + end + if idx > 0 then + options = options .. "\n" + end + options = options .. i + idx = idx + 1 + end + + if (selected_idx == -1) then + options = options .. "\n" .. saved_theme_name + selected_idx = idx + end + + local theme_chooser = self.content:Dropdown { + options = options, + } + theme_chooser:set({selected = selected_idx}) + + theme_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function() + local option = theme_chooser:get('selected_str') + local selectedTheme = themeOptions[option] + if (selectedTheme) then + theme.load_theme(tostring(selectedTheme)) + backstack.reset(main_menu:new()) + end + end) + end +} + local InputSettings = SettingsScreen:new { title = "Input Method", - createUi = function(self) - SettingsScreen.createUi(self) + create_ui = function(self) + SettingsScreen.create_ui(self) theme.set_style(self.content:Label { text = "Control scheme", @@ -262,14 +403,13 @@ local InputSettings = SettingsScreen:new { controls.scheme:set(scheme) end) - theme.set_style(self.menu.content:Label { + theme.set_style(self.content:Label { text = "Scroll Sensitivity", }, "settings_title") local slider_scale = 4; -- Power steering local sensitivity = self.content:Slider { w = lvgl.PCT(90), - h = 5, range = { min = 0, max = 255 / slider_scale }, value = controls.scroll_sensitivity:get() / slider_scale, } @@ -281,8 +421,8 @@ local InputSettings = SettingsScreen:new { local MassStorageSettings = SettingsScreen:new { title = "USB Storage", - createUi = function(self) - SettingsScreen.createUi(self) + create_ui = function(self) + SettingsScreen.create_ui(self) local version = require("version").samd() if tonumber(version) < 3 then @@ -339,15 +479,15 @@ local MassStorageSettings = SettingsScreen:new { end) } end, - canPop = function() + can_pop = function() return not usb.msc_enabled:get() end } local DatabaseSettings = SettingsScreen:new { title = "Database", - createUi = function(self) - SettingsScreen.createUi(self) + create_ui = function(self) + SettingsScreen.create_ui(self) local db = require("database") widgets.Row(self.content, "Schema version", db.version()) @@ -357,11 +497,12 @@ local DatabaseSettings = SettingsScreen:new { flex = { flex_direction = "row", justify_content = "flex-start", - align_items = "flex-start", + align_items = "center", align_content = "flex-start", }, w = lvgl.PCT(100), h = lvgl.SIZE_CONTENT, + pad_bottom = 4, } auto_update_container:add_style(styles.list_item) auto_update_container:Label { text = "Auto update", flex_grow = 1 } @@ -403,10 +544,63 @@ local DatabaseSettings = SettingsScreen:new { end } +local PowerSettings = SettingsScreen:new { + title = "Power", + create_ui = function(self) + SettingsScreen.create_ui(self) + local power = require("power") + + local charge_pct = widgets.Row(self.content, "Charge").right + local charge_volts = widgets.Row(self.content, "Voltage").right + local charge_state = widgets.Row(self.content, "Status").right + + self.bindings = self.bindings + { + power.battery_pct:bind(function(pct) + charge_pct:set { text = string.format("%d%%", pct) } + end), + power.battery_millivolts:bind(function(mv) + charge_volts:set { text = string.format("%.2fV", mv / 1000) } + end), + power.charge_state:bind(function(state) + charge_state:set { text = state } + end), + } + + local fast_charge_container = self.content:Object { + flex = { + flex_direction = "row", + justify_content = "flex-start", + align_items = "center", + align_content = "flex-start", + }, + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + pad_bottom = 4, + } + fast_charge_container:add_style(styles.list_item) + fast_charge_container:Label { text = "Fast Charging", flex_grow = 1 } + local fast_charge_sw = fast_charge_container:Switch {} + + fast_charge_sw:onevent(lvgl.EVENT.VALUE_CHANGED, function() + power.fast_charge:set(fast_charge_sw:enabled()) + end) + + self.bindings = self.bindings + { + power.fast_charge:bind(function(en) + if en then + fast_charge_sw:add_state(lvgl.STATE.CHECKED) + else + fast_charge_sw:clear_state(lvgl.STATE.CHECKED) + end + end), + } + end +} + local SamdConfirmation = SettingsScreen:new { title = "Are you sure?", - createUi = function(self) - SettingsScreen.createUi(self) + create_ui = function(self) + SettingsScreen.create_ui(self) self.content:Label { w = lvgl.PCT(100), text = "After selecting 'flash', copy the new UF2 file onto the USB drive that appears. The screen will be blank until the update is finished.", @@ -437,8 +631,8 @@ local SamdConfirmation = SettingsScreen:new { local FirmwareSettings = SettingsScreen:new { title = "Firmware", - createUi = function(self) - SettingsScreen.createUi(self) + create_ui = function(self) + SettingsScreen.create_ui(self) local version = require("version") widgets.Row(self.content, "ESP32", version.esp()) widgets.Row(self.content, "SAMD21", version.samd()) @@ -468,17 +662,118 @@ local FirmwareSettings = SettingsScreen:new { local LicensesScreen = SettingsScreen:new { title = "Licenses", - createUi = function(self) - SettingsScreen.createUi(self) - self.root = require("licenses")(self) + create_ui = function(self) + SettingsScreen.create_ui(self) + require("licenses")(self) + end +} + +local FccStatementScreen = SettingsScreen:new { + title = "FCC Statement", + create_ui = function(self) + SettingsScreen.create_ui(self) + + local text_part = function(text) + self.content:Label { + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + text = text, + text_font = font.fusion_10, + long_mode = lvgl.LABEL.LONG_WRAP, + } + end + + text_part( + "This device complies with part 15 of the FCC Rules. Operation is subject to the following two conditions:") + text_part("(1) This device may not cause harmful interference, and") + text_part( + "(2) this device must accept any interference received, including interference that may cause undesired operation.") + + local scroller = self.content:Object { w = 1, h = 1 } + scroller:onevent(lvgl.EVENT.FOCUSED, function() + scroller:scroll_to_view(1); + end) + lvgl.group.get_default():add_obj(scroller) + end +} + +local RegulatoryScreen = SettingsScreen:new { + title = "Regulatory", + create_ui = function(self) + SettingsScreen.create_ui(self) + local version = require("version") + + local small_row = function(left, right) + local container = self.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 + } + container:add_style(styles.list_item) + container:Label { + text = left, + flex_grow = 1, + text_font = font.fusion_10, + } + container:Label { + text = right, + text_font = font.fusion_10, + } + end + small_row("Manufacturer", "cool tech zone") + small_row("Product model", "CTZ-1") + small_row("FCC ID", "2BG33-CTZ1") + + local button_container = self.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, + } + button_container:add_style(styles.list_item) + + local button = button_container:Button {} + button:Label { text = "FCC Statement" } + button:onClicked(function() + backstack.push(FccStatementScreen:new()) + end) + + local logo_container = self.content:Object { + w = lvgl.PCT(100), + h = lvgl.SIZE_CONTENT, + flex = { + flex_direction = "row", + justify_content = "center", + align_items = "center", + align_content = "center", + }, + pad_top = 4, + pad_column = 4, + } + theme.set_style(logo_container, "regulatory_icons") + button_container:add_style(styles.list_item) + + logo_container:Image { src = "//lua/img/ce.png" } + logo_container:Image { src = "//lua/img/weee.png" } end } return widgets.MenuScreen:new { show_back = true, title = "Settings", - createUi = function(self) - widgets.MenuScreen.createUi(self) + create_ui = function(self) + widgets.MenuScreen.create_ui(self) local list = self.root:List { w = lvgl.PCT(100), h = lvgl.PCT(100), @@ -507,6 +802,7 @@ return widgets.MenuScreen:new { section("Interface") submenu("Display", DisplaySettings) + submenu("Theme", ThemeSettings) submenu("Input Method", InputSettings) section("USB") @@ -514,7 +810,11 @@ return widgets.MenuScreen:new { section("System") submenu("Database", DatabaseSettings) + submenu("Power", PowerSettings) + + section("About") submenu("Firmware", FirmwareSettings) submenu("Licenses", LicensesScreen) + submenu("Regulatory", RegulatoryScreen) end } diff --git a/lua/theme_dark.lua b/lua/theme_dark.lua index 6508f642..41fddf81 100644 --- a/lua/theme_dark.lua +++ b/lua/theme_dark.lua @@ -79,8 +79,6 @@ local theme_dark = { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff pad_all = 2, bg_color = background_muted, - shadow_width = 5, - shadow_opa = lvgl.OPA(100) }}, {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { bg_color = background_muted, @@ -88,6 +86,37 @@ local theme_dark = { {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { bg_color = highlight_color, }}, + {lvgl.PART.KNOB | lvgl.STATE.EDITED, lvgl.Style { + pad_all = 2, + }}, + {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + bg_color = highlight_color, + }}, + }, + scrubber = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_muted, + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + }}, + {lvgl.PART.INDICATOR, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = highlight_color, + }}, + {lvgl.PART.KNOB, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = background_muted, + }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = background_muted, + }}, + {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = highlight_color, + pad_all = 1, + }}, + {lvgl.PART.KNOB | lvgl.STATE.EDITED, lvgl.Style { + pad_all = 2, + }}, {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { bg_color = highlight_color, }}, @@ -95,24 +124,27 @@ local theme_dark = { switch = { {lvgl.PART.MAIN, lvgl.Style { bg_opa = lvgl.OPA(100), - width = 28, - height = 8, + width = 18, + height = 10, radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff bg_color = background_muted, - border_color = highlight_color, + border_color = text_color, + border_width = 1, }}, {lvgl.PART.INDICATOR, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - bg_color = background_muted, + bg_color = background_color, }}, {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + bg_opa = lvgl.OPA(100), bg_color = highlight_color, }}, {lvgl.PART.KNOB, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - pad_all = 2, bg_opa = lvgl.OPA(100), bg_color = background_muted, + border_color = text_color, + border_width = 1, }}, {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { bg_color = highlight_color, @@ -170,7 +202,44 @@ local theme_dark = { image_recolor = icon_enabled_color, }}, }, - + now_playing = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + border_width = 1, + border_color = highlight_color, + border_side = 15, -- LV_BORDER_SIDE_FULL + }}, + }, + menu_icon = { + {lvgl.PART.MAIN, lvgl.Style { + pad_all = 4, + radius = 4 + }}, + }, + battery = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 0, + }}, + }, + battery_0 = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 180, + image_recolor = "#aa3333", + }}, + }, + battery_charging = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 180, + image_recolor = "#33aa33", + }}, + }, + battery_charge_icon = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 180, + image_recolor = "#fdd833", + }}, + }, } return theme_dark diff --git a/lua/theme_hicon.lua b/lua/theme_hicon.lua new file mode 100644 index 00000000..30947c18 --- /dev/null +++ b/lua/theme_hicon.lua @@ -0,0 +1,273 @@ +local lvgl = require("lvgl") +local font = require("font") + +-- local background_color = "#000000" +-- local text_color = "#33b5e5" + +local text_color = "#000000" +local background_color = "#FFFFFF" + +local theme_hicon = { + base = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(0), + text_font = font.fusion_12, + }}, + {lvgl.PART.SCROLLBAR, lvgl.Style { + bg_color = text_color, + }}, + }, + root = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_color, -- Root background color + text_color = text_color + }}, + }, + header = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_color, + }}, + }, + pop_up = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_color, + border_color = text_color, + border_width = 1, + }}, + }, + button = { + {lvgl.PART.MAIN, lvgl.Style { + pad_left = 2, + pad_right = 2, + pad_top = 1, + pad_bottom = 1, + bg_color = background_color, + image_recolor_opa = 255, + image_recolor = text_color, + radius = 5, + border_color = text_color, + border_width = 1, + }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = text_color, + image_recolor_opa = 255, + image_recolor = background_color, + text_color = background_color, + }}, + }, + listbutton = { + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = text_color, + text_color = background_color, + }}, + }, + bar = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_color, + border_color = text_color, + border_width = 1, + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + }}, + {lvgl.PART.INDICATOR, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = text_color, + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + }}, + }, + slider = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_color, + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + border_color = text_color, + border_width = 1, + height = 8, + }}, + {lvgl.PART.INDICATOR, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = text_color, + }}, + {lvgl.PART.KNOB, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = background_color, + border_color = text_color, + border_width = 1, + }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = background_color, + }}, + {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = text_color, + }}, + {lvgl.PART.KNOB | lvgl.STATE.EDITED, lvgl.Style { + pad_all = 2, + }}, + {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + bg_color = text_color, + }}, + }, + scrubber = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_color, + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + border_color = text_color, + border_width = 1, + }}, + {lvgl.PART.INDICATOR, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = text_color, + }}, + {lvgl.PART.KNOB, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = background_color, + border_color = text_color, + border_width = 1, + }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = background_color, + }}, + {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = text_color, + pad_all = 1, + }}, + {lvgl.PART.KNOB | lvgl.STATE.EDITED, lvgl.Style { + pad_all = 4, + }}, + {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + bg_color = text_color, + }}, + }, + switch = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + width = 18, + height = 10, + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = background_color, + border_color = text_color, + border_width = 1, + }}, + {lvgl.PART.INDICATOR, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = background_color, + }}, + {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = text_color, + }}, + {lvgl.PART.KNOB, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_opa = lvgl.OPA(100), + bg_color = background_color, + border_color = text_color, + border_width = 1, + }}, + {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = text_color, + }}, + }, + dropdown = { + {lvgl.PART.MAIN, lvgl.Style{ + radius = 2, + pad_all = 2, + border_width = 1, + border_color = text_color, + border_side = 15, -- LV_BORDER_SIDE_FULL + bg_color = background_color, + bg_opa = lvgl.OPA(100), + }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + border_color = text_color, + bg_color = text_color, + text_color = background_color, + }}, + }, + dropdownlist = { + {lvgl.PART.MAIN, lvgl.Style{ + radius = 2, + pad_all = 2, + border_width = 1, + border_color = text_color, + bg_opa = lvgl.OPA(100), + bg_color = background_color + }}, + {lvgl.PART.SELECTED | lvgl.STATE.CHECKED, lvgl.Style { + bg_color = text_color, + text_color = background_color, + }}, + }, + database_indicator = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 255, + image_recolor = text_color, + }}, + }, + settings_title = { + {lvgl.PART.MAIN, lvgl.Style { + pad_top = 2, + pad_bottom = 4, + text_font = font.fusion_10, + text_color = text_color, + }}, + }, + icon_disabled = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 255, + image_recolor = text_color, + }}, + }, + icon_enabled = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 255, + image_recolor = text_color, + }}, + }, + now_playing = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + border_width = 1, + border_color = text_color, + border_side = 15, -- LV_BORDER_SIDE_FULL + }}, + }, + menu_icon = { + {lvgl.PART.MAIN, lvgl.Style { + pad_all = 4, + radius = 4 + }}, + }, + battery = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 255, + image_recolor = text_color, + }}, + }, + battery_0 = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 255, + image_recolor = text_color, + }}, + }, + battery_charging = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 255, + image_recolor = text_color, + }}, + }, + battery_charge_icon = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 200, + image_recolor = text_color, + }}, + }, +} + +return theme_hicon diff --git a/lua/theme_light.lua b/lua/theme_light.lua index 05b7d291..275d06ca 100644 --- a/lua/theme_light.lua +++ b/lua/theme_light.lua @@ -2,11 +2,12 @@ local lvgl = require("lvgl") local font = require("font") local background_color = "#ffffff" -local background_muted = "#fafafa" -local text_color = "#000000" -local highlight_color = "#ce93d8" -local icon_enabled_color = "#2c2c2c" +local background_muted = "#f2f2f2" +local text_color = "#2c2c2c" +local highlight_color = "#ff82bc" +local icon_enabled_color = "#ff82bc" local icon_disabled_color = "#999999" +local border_color = "#888888" local theme_light = { base = { @@ -36,25 +37,35 @@ local theme_light = { }, button = { {lvgl.PART.MAIN, lvgl.Style { - pad_left = 2, - pad_right = 2, - pad_top = 1, - pad_bottom = 1, + pad_left = 1, + pad_right = 1, + margin_all = 1, bg_color = background_color, image_recolor_opa = 180, image_recolor = highlight_color, - radius = 5, + radius = 4, + border_color = border_color, + border_width = 1, + border_side = 9, -- Bottom right + outline_color = border_color, + outline_width = 1, }}, {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { bg_opa = lvgl.OPA(100), + text_color = "#ffffff", bg_color = highlight_color, image_recolor_opa = 0, }}, + {lvgl.PART.MAIN | lvgl.STATE.PRESSED, lvgl.Style { + margin_left = 2, + border_width = 0, + }}, }, listbutton = { {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { bg_opa = lvgl.OPA(100), bg_color = highlight_color, + text_color = "#ffffff" }}, }, bar = { @@ -68,23 +79,61 @@ local theme_light = { bg_opa = lvgl.OPA(100), bg_color = background_muted, radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + border_color = border_color, + border_width = 1, + height = 8, }}, {lvgl.PART.INDICATOR, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff bg_color = highlight_color, + border_color = border_color, + border_width = 1, }}, {lvgl.PART.KNOB, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = background_muted, + border_color = border_color, + border_width = 1, + border_side = 9, + outline_color = border_color, + outline_width = 1, + }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = background_muted, + }}, + {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { + bg_color = highlight_color, + }}, + {lvgl.PART.KNOB | lvgl.STATE.EDITED, lvgl.Style { pad_all = 2, + }}, + {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + bg_color = highlight_color, + }}, + }, + scrubber = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + bg_color = background_muted, + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + }}, + {lvgl.PART.INDICATOR, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + bg_color = highlight_color, + }}, + {lvgl.PART.KNOB, lvgl.Style { + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff bg_color = background_muted, - shadow_width = 5, - shadow_opa = lvgl.OPA(100) }}, {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { bg_color = background_muted, }}, {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { bg_color = highlight_color, + pad_all = 1, + }}, + {lvgl.PART.KNOB | lvgl.STATE.EDITED, lvgl.Style { + pad_all = 2, }}, {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { bg_color = highlight_color, @@ -93,24 +142,30 @@ local theme_light = { switch = { {lvgl.PART.MAIN, lvgl.Style { bg_opa = lvgl.OPA(100), - width = 28, - height = 8, + width = 18, + height = 10, radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff bg_color = background_muted, - border_color = highlight_color, + border_color = border_color, + border_width = 1, }}, {lvgl.PART.INDICATOR, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - bg_color = background_muted, + bg_color = background_color, }}, {lvgl.PART.INDICATOR | lvgl.STATE.CHECKED, lvgl.Style { + bg_opa = lvgl.OPA(100), bg_color = highlight_color, }}, {lvgl.PART.KNOB, lvgl.Style { radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff - pad_all = 2, bg_opa = lvgl.OPA(100), bg_color = background_muted, + border_color = border_color, + border_width = 1, + border_side = 9, + outline_color = border_color, + outline_width = 1, }}, {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { bg_color = highlight_color, @@ -120,10 +175,12 @@ local theme_light = { {lvgl.PART.MAIN, lvgl.Style{ radius = 2, pad_all = 2, - border_width = 1, - border_color = background_muted, - border_side = 15, -- LV_BORDER_SIDE_FULL bg_color = background_color, + border_color = border_color, + border_width = 1, + border_side = 9, + outline_color = border_color, + outline_width = 1, }}, {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { border_color = highlight_color, @@ -134,7 +191,7 @@ local theme_light = { radius = 2, pad_all = 2, border_width = 1, - border_color = highlight_color, + border_color = border_color, bg_opa = lvgl.OPA(100), bg_color = background_color }}, @@ -148,6 +205,17 @@ local theme_light = { image_recolor = highlight_color, }}, }, + back_button = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 180, + image_recolor = icon_enabled_color, + pad_all = 0, + }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + image_recolor_opa = 0, + image_recolor = "#ffffff", + }}, + }, settings_title = { {lvgl.PART.MAIN, lvgl.Style { pad_top = 2, @@ -161,14 +229,63 @@ local theme_light = { image_recolor_opa = 180, image_recolor = icon_disabled_color, }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + image_recolor_opa = 0, + image_recolor = "#ffffff", + }}, }, icon_enabled = { {lvgl.PART.MAIN, lvgl.Style { image_recolor_opa = 180, image_recolor = icon_enabled_color, }}, + {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { + image_recolor_opa = 0, + image_recolor = "#ffffff", + }}, + }, + now_playing = { + {lvgl.PART.MAIN, lvgl.Style { + bg_opa = lvgl.OPA(100), + radius = 32767, -- LV_RADIUS_CIRCLE = 0x7fff + }}, + }, + menu_icon = { + {lvgl.PART.MAIN, lvgl.Style { + pad_all = 4, + radius = 4 + }}, + }, + battery = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 180, + image_recolor = highlight_color, + }}, + }, + battery_0 = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 180, + image_recolor = "#aa3333", + }}, + }, + battery_charging = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 180, + image_recolor = "#33aa33", + }}, + }, + battery_charge_icon = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 180, + image_recolor = "#fdd833", + }}, + }, + regulatory_icons = { + {lvgl.PART.MAIN, lvgl.Style { + image_recolor_opa = 255, + image_recolor = text_color, + }}, }, - } return theme_light diff --git a/lua/widgets.lua b/lua/widgets.lua index c3573c0b..980f0bb2 100644 --- a/lua/widgets.lua +++ b/lua/widgets.lua @@ -8,6 +8,7 @@ local styles = require("styles") local database = require("database") local theme = require("theme") local screen = require("screen") +local images = require("images") local img = { db = lvgl.ImgData("//lua/img/db.png"), @@ -18,7 +19,6 @@ local img = { bat_40 = lvgl.ImgData("//lua/img/bat/40.png"), bat_20 = lvgl.ImgData("//lua/img/bat/20.png"), bat_0 = lvgl.ImgData("//lua/img/bat/0.png"), - bat_0chg = lvgl.ImgData("//lua/img/bat/0chg.png"), bt_conn = lvgl.ImgData("//lua/img/bt_conn.png"), bt = lvgl.ImgData("//lua/img/bt.png") } @@ -37,7 +37,7 @@ end widgets.MenuScreen = screen:new { show_back = false, title = "", - createUi = function(self) + create_ui = function(self) self.root = lvgl.Object(nil, { flex = { flex_direction = "column", @@ -58,7 +58,7 @@ widgets.MenuScreen = screen:new { end } -function widgets.Row(parent, left, right) +function widgets.Row(parent, left_text, right_text) local container = parent:Object { flex = { flex_direction = "row", @@ -70,20 +70,23 @@ function widgets.Row(parent, left, right) h = lvgl.SIZE_CONTENT } container:add_style(styles.list_item) - container:Label { - text = left, - flex_grow = 1 + local left = container:Label { + text = left_text, + flex_grow = 1, + } + local right = container:Label { + text = right_text or "", } - container:Label { - text = right + return { + left = left, + right = right, } end -local bindings_meta = { - __add = function(a, b) - return table.move(a, 1, #a, #b + 1, b) - end -} +local bindings_meta = {} +bindings_meta["__add"] = function(a, b) + return setmetatable(table.move(a, 1, #a, #b + 1, b), bindings_meta) +end function widgets.StatusBar(parent, opts) local root = parent.root:Object { @@ -98,6 +101,7 @@ function widgets.StatusBar(parent, opts) pad_top = 1, pad_bottom = 1, pad_left = 4, + pad_right = 4, pad_column = 1, scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF, } @@ -109,11 +113,22 @@ function widgets.StatusBar(parent, opts) if opts.back_cb then local back = root:Button { w = lvgl.SIZE_CONTENT, - h = 12, + h = lvgl.SIZE_CONTENT, } - local label = back:Label({ text = "<", align = lvgl.ALIGN.CENTER }) - widgets.Description(label, "Back") + back:Image{src=images.back} + theme.set_style(back, "back_button") + widgets.Description(back, "Back") back:onClicked(opts.back_cb) + back:onevent(lvgl.EVENT.FOCUSED, function() + local first_view = parent.content + if not first_view then return end + while first_view:get_child_cnt() > 0 do + first_view = first_view:get_child(0) + end + if first_view then + first_view:scroll_to_view_recursive(1) + end + end) end local title = root:Label { @@ -123,6 +138,7 @@ function widgets.StatusBar(parent, opts) text = "", align = lvgl.ALIGN.CENTER, flex_grow = 1, + pad_left = 2, } if opts.title then title:set { text = opts.title } @@ -141,24 +157,29 @@ function widgets.StatusBar(parent, opts) local function update_battery_icon() if is_charging == nil or percent == nil then return end local src + theme.set_style(battery_icon, "battery") if percent >= 95 then + theme.set_style(battery_icon, "battery_100") src = img.bat_100 elseif percent >= 75 then + theme.set_style(battery_icon, "battery_80") src = img.bat_80 elseif percent >= 55 then + theme.set_style(battery_icon, "battery_60") src = img.bat_60 elseif percent >= 35 then + theme.set_style(battery_icon, "battery_40") src = img.bat_40 elseif percent >= 15 then + theme.set_style(battery_icon, "battery_20") src = img.bat_20 else - if is_charging then - src = img.bat_0chg - else - src = img.bat_0 - end + theme.set_style(battery_icon, "battery_0") + src = img.bat_0 end if is_charging then + theme.set_style(battery_icon, "battery_charging") + theme.set_style(charge_icon, "battery_charge_icon") charge_icon:clear_flag(lvgl.FLAG.HIDDEN) else charge_icon:add_flag(lvgl.FLAG.HIDDEN) @@ -190,6 +211,7 @@ function widgets.StatusBar(parent, opts) end end), bluetooth.connected:bind(function(connected) + theme.set_style(bt_icon, "bluetooth_icon") if connected then bt_icon:set_src(img.bt_conn) else @@ -210,10 +232,6 @@ function widgets.IconBtn(parent, icon, text) }, w = lvgl.SIZE_CONTENT, h = lvgl.SIZE_CONTENT, - pad_top = 1, - pad_bottom = 1, - pad_left = 1, - pad_column = 1 } btn:Image { src = icon |
