summaryrefslogtreecommitdiff
path: root/lua
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-09-09 15:15:00 +1000
committerjacqueline <me@jacqueline.id.au>2024-09-09 15:15:00 +1000
commit2b1a01705d62d08cefd6816ba108c5cae48a50ac (patch)
tree20ba16a6259ffc335dbcded84fa6bcbe327e9d84 /lua
parent9475d10d1000c7e21a7ea311b0c8ee6a72ef46c4 (diff)
parentacdc9789c90ed6f083d054cd07930e020123457f (diff)
downloadtangara-fw-2b1a01705d62d08cefd6816ba108c5cae48a50ac.tar.gz
Merge branch 'main' into jqln/tts
Diffstat (limited to 'lua')
-rw-r--r--lua/browser.lua5
-rw-r--r--lua/file_browser.lua123
-rw-r--r--lua/fonts/fusion10bin198308 -> 386220 bytes
-rw-r--r--lua/fonts/fusion12bin375744 -> 615260 bytes
-rw-r--r--lua/images.lua3
-rw-r--r--lua/img/back.pngbin0 -> 7254 bytes
-rw-r--r--lua/img/bat/0.pngbin627 -> 8430 bytes
-rw-r--r--lua/img/bat/0chg.pngbin627 -> 0 bytes
-rw-r--r--lua/img/bat/100.pngbin629 -> 8434 bytes
-rw-r--r--lua/img/bat/20.pngbin624 -> 8430 bytes
-rw-r--r--lua/img/bat/40.pngbin625 -> 8431 bytes
-rw-r--r--lua/img/bat/60.pngbin628 -> 8435 bytes
-rw-r--r--lua/img/bat/80.pngbin623 -> 8436 bytes
-rw-r--r--lua/img/bat/chg.pngbin667 -> 8326 bytes
-rw-r--r--lua/img/bt.pngbin4595 -> 10120 bytes
-rw-r--r--lua/img/bt_conn.pngbin4363 -> 11932 bytes
-rw-r--r--lua/img/ce.pngbin0 -> 1276 bytes
-rw-r--r--lua/img/next.pngbin4811 -> 7873 bytes
-rw-r--r--lua/img/pause.pngbin4771 -> 5963 bytes
-rw-r--r--lua/img/pausecirc.pngbin7054 -> 8247 bytes
-rw-r--r--lua/img/play.pngbin4813 -> 6828 bytes
-rw-r--r--lua/img/playcirc.pngbin7074 -> 8949 bytes
-rw-r--r--lua/img/prev.pngbin4810 -> 7870 bytes
-rw-r--r--lua/img/repeat.pngbin5023 -> 8578 bytes
-rw-r--r--lua/img/repeat_off.pngbin0 -> 9349 bytes
-rw-r--r--lua/img/settings.pngbin5898 -> 12292 bytes
-rw-r--r--lua/img/shuffle_off.pngbin0 -> 7791 bytes
-rw-r--r--lua/img/weee.pngbin0 -> 1395 bytes
-rw-r--r--lua/licenses.lua6
-rw-r--r--lua/main.lua14
-rw-r--r--lua/main_menu.lua11
-rw-r--r--lua/playing.lua48
-rw-r--r--lua/settings.lua402
-rw-r--r--lua/theme_dark.lua85
-rw-r--r--lua/theme_hicon.lua273
-rw-r--r--lua/theme_light.lua159
-rw-r--r--lua/widgets.lua68
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
index 9fa4b78d..41e3916e 100644
--- a/lua/fonts/fusion10
+++ b/lua/fonts/fusion10
Binary files differ
diff --git a/lua/fonts/fusion12 b/lua/fonts/fusion12
index 0fda8b03..3745c17d 100644
--- a/lua/fonts/fusion12
+++ b/lua/fonts/fusion12
Binary files differ
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
new file mode 100644
index 00000000..0f34453b
--- /dev/null
+++ b/lua/img/back.png
Binary files differ
diff --git a/lua/img/bat/0.png b/lua/img/bat/0.png
index 15639ef5..d4c69fd4 100644
--- a/lua/img/bat/0.png
+++ b/lua/img/bat/0.png
Binary files differ
diff --git a/lua/img/bat/0chg.png b/lua/img/bat/0chg.png
deleted file mode 100644
index 7267ade1..00000000
--- a/lua/img/bat/0chg.png
+++ /dev/null
Binary files differ
diff --git a/lua/img/bat/100.png b/lua/img/bat/100.png
index f762ec2f..443ac6cc 100644
--- a/lua/img/bat/100.png
+++ b/lua/img/bat/100.png
Binary files differ
diff --git a/lua/img/bat/20.png b/lua/img/bat/20.png
index b1fcca1b..11fb049e 100644
--- a/lua/img/bat/20.png
+++ b/lua/img/bat/20.png
Binary files differ
diff --git a/lua/img/bat/40.png b/lua/img/bat/40.png
index fbba9c3b..3dcfe2e6 100644
--- a/lua/img/bat/40.png
+++ b/lua/img/bat/40.png
Binary files differ
diff --git a/lua/img/bat/60.png b/lua/img/bat/60.png
index 97a8b605..9e677571 100644
--- a/lua/img/bat/60.png
+++ b/lua/img/bat/60.png
Binary files differ
diff --git a/lua/img/bat/80.png b/lua/img/bat/80.png
index ab90987b..4c393f03 100644
--- a/lua/img/bat/80.png
+++ b/lua/img/bat/80.png
Binary files differ
diff --git a/lua/img/bat/chg.png b/lua/img/bat/chg.png
index d604bb76..629586e3 100644
--- a/lua/img/bat/chg.png
+++ b/lua/img/bat/chg.png
Binary files differ
diff --git a/lua/img/bt.png b/lua/img/bt.png
index 73f3179f..ae45dfbf 100644
--- a/lua/img/bt.png
+++ b/lua/img/bt.png
Binary files differ
diff --git a/lua/img/bt_conn.png b/lua/img/bt_conn.png
index 91f9964d..890e4160 100644
--- a/lua/img/bt_conn.png
+++ b/lua/img/bt_conn.png
Binary files differ
diff --git a/lua/img/ce.png b/lua/img/ce.png
new file mode 100644
index 00000000..dae12bc6
--- /dev/null
+++ b/lua/img/ce.png
Binary files differ
diff --git a/lua/img/next.png b/lua/img/next.png
index 1f6f044b..d245148e 100644
--- a/lua/img/next.png
+++ b/lua/img/next.png
Binary files differ
diff --git a/lua/img/pause.png b/lua/img/pause.png
index e7011821..32c5a2b4 100644
--- a/lua/img/pause.png
+++ b/lua/img/pause.png
Binary files differ
diff --git a/lua/img/pausecirc.png b/lua/img/pausecirc.png
index d7e944fa..4c6f4fd8 100644
--- a/lua/img/pausecirc.png
+++ b/lua/img/pausecirc.png
Binary files differ
diff --git a/lua/img/play.png b/lua/img/play.png
index a3b8a5af..833cb087 100644
--- a/lua/img/play.png
+++ b/lua/img/play.png
Binary files differ
diff --git a/lua/img/playcirc.png b/lua/img/playcirc.png
index f2e48da7..dc394f3e 100644
--- a/lua/img/playcirc.png
+++ b/lua/img/playcirc.png
Binary files differ
diff --git a/lua/img/prev.png b/lua/img/prev.png
index b445c75a..ee4273e7 100644
--- a/lua/img/prev.png
+++ b/lua/img/prev.png
Binary files differ
diff --git a/lua/img/repeat.png b/lua/img/repeat.png
index 40a7564e..c9941601 100644
--- a/lua/img/repeat.png
+++ b/lua/img/repeat.png
Binary files differ
diff --git a/lua/img/repeat_off.png b/lua/img/repeat_off.png
new file mode 100644
index 00000000..b8db6c4d
--- /dev/null
+++ b/lua/img/repeat_off.png
Binary files differ
diff --git a/lua/img/settings.png b/lua/img/settings.png
index 4fc29e51..e6fbecb3 100644
--- a/lua/img/settings.png
+++ b/lua/img/settings.png
Binary files differ
diff --git a/lua/img/shuffle_off.png b/lua/img/shuffle_off.png
new file mode 100644
index 00000000..eb99ccf5
--- /dev/null
+++ b/lua/img/shuffle_off.png
Binary files differ
diff --git a/lua/img/weee.png b/lua/img/weee.png
new file mode 100644
index 00000000..ca0a5ddd
--- /dev/null
+++ b/lua/img/weee.png
Binary files differ
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