summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrdsh <rdsh@noreply.codeberg.org>2025-02-05 21:57:45 +0000
committerrdsh <rdsh@noreply.codeberg.org>2025-02-05 21:57:45 +0000
commit2ec84a1393331f57f8402bce66d337c0dd255f64 (patch)
tree30f271bf10988a9717abdf266d135e3ec0ae2d31
parentddcd06dbca61fc55a7c2cd68f82f8cfe7b4c5cbf (diff)
parent9ecb79a264daa7896ce7d5a65592c05631213d5a (diff)
downloadtangara-fw-2ec84a1393331f57f8402bce66d337c0dd255f64.tar.gz
Merge pull request 'main' (#1) from cool-tech-zone/tangara-fw:main into main
Reviewed-on: https://codeberg.org/rdsh/tangara-fw/pulls/1
-rw-r--r--.gitignore1
-rw-r--r--lib/libtags/wav.c2
-rw-r--r--lua/browser.lua1
-rw-r--r--lua/file_browser.lua6
-rw-r--r--lua/fonts/fusion12bin615260 -> 655932 bytes
-rw-r--r--lua/images.lua2
-rw-r--r--lua/img/file_icons/directory.pngbin0 -> 7848 bytes
-rw-r--r--lua/img/file_icons/playlist.pngbin0 -> 6832 bytes
-rw-r--r--lua/main_menu.lua31
-rw-r--r--lua/playing.lua2
-rw-r--r--lua/playlist_browser.lua94
-rw-r--r--lua/playlist_iterator.lua35
-rw-r--r--lua/settings.lua92
-rw-r--r--lua/theme_hicon.lua2
-rw-r--r--lua/theme_light.lua5
-rw-r--r--lua/widgets.lua10
-rw-r--r--src/drivers/include/drivers/nvs.hpp9
-rw-r--r--src/drivers/nvs.cpp21
-rw-r--r--src/tangara/audio/track_queue.cpp1
-rw-r--r--src/tangara/input/input_device.hpp3
-rw-r--r--src/tangara/input/input_nav_buttons.cpp2
-rw-r--r--src/tangara/input/input_nav_buttons.hpp2
-rw-r--r--src/tangara/input/input_touch_dpad.cpp2
-rw-r--r--src/tangara/input/input_touch_dpad.hpp2
-rw-r--r--src/tangara/input/input_touch_wheel.cpp18
-rw-r--r--src/tangara/input/input_touch_wheel.hpp5
-rw-r--r--src/tangara/input/input_volume_buttons.cpp12
-rw-r--r--src/tangara/input/input_volume_buttons.hpp5
-rw-r--r--src/tangara/input/lvgl_input_driver.cpp31
-rw-r--r--src/tangara/input/lvgl_input_driver.hpp2
-rw-r--r--src/tangara/lua/lua_controls.cpp17
-rw-r--r--src/tangara/ui/ui_fsm.cpp1
-rw-r--r--tools/cmake/common.cmake2
-rw-r--r--tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/ark-pixel.txt94
-rw-r--r--tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/cubic-11.txt108
-rw-r--r--tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/galmuri.txt93
-rw-r--r--tools/fonts/fusion/fusion-pixel-12px-proportional/OFL.txt94
-rw-r--r--tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ja.ttfbin0 -> 5965792 bytes
-rw-r--r--tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ko.ttfbin0 -> 5971452 bytes
-rw-r--r--tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-latin.ttfbin0 -> 5950920 bytes
-rw-r--r--tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-zh_hans.ttf (renamed from tools/fonts/fusion/fusion-pixel-12px-proportional.ttf)bin5674732 -> 5958408 bytes
-rw-r--r--tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-zh_hant.ttfbin0 -> 5980564 bytes
-rw-r--r--tools/fonts/fusion12bin0 -> 655932 bytes
-rwxr-xr-xtools/fonts/mkfonts.sh7
44 files changed, 751 insertions, 63 deletions
diff --git a/.gitignore b/.gitignore
index 7776977d..1ff0f7b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ sdkconfig.old
.vscode
compile_commands.json
warnings.txt
+.DS_Store
doc/
diff --git a/lib/libtags/wav.c b/lib/libtags/wav.c
index 69b1946a..21faa46d 100644
--- a/lib/libtags/wav.c
+++ b/lib/libtags/wav.c
@@ -49,6 +49,8 @@ tagwav(Tagctx *ctx)
break;
sz -= 4+4;
csz = leuint(d+4);
+ if(csz % 2 == 1)
+ csz += 1;
if(sz < csz)
break;
sz -= csz;
diff --git a/lua/browser.lua b/lua/browser.lua
index 2a024fc5..264db0c5 100644
--- a/lua/browser.lua
+++ b/lua/browser.lua
@@ -118,6 +118,7 @@ return screen:new {
end
widgets.InfiniteList(self.root, self.iterator, {
+ focus_first_item = true,
get_icon = get_icon_func,
callback = function(item)
return function()
diff --git a/lua/file_browser.lua b/lua/file_browser.lua
index a85c2ba2..6289828f 100644
--- a/lua/file_browser.lua
+++ b/lua/file_browser.lua
@@ -8,11 +8,11 @@ local backstack = require("backstack")
local font = require("font")
local queue = require("queue")
local playing = require("playing")
-local styles = require("styles")
local playback = require("playback")
local theme = require("theme")
local screen = require("screen")
local filesystem = require("filesystem")
+local playlist_iterator = require("playlist_iterator")
return screen:new {
create_ui = function(self)
@@ -59,6 +59,7 @@ return screen:new {
end
widgets.InfiniteList(self.root, self.iterator, {
+ focus_first_item = true,
callback = function(item)
return function()
local is_dir = item:is_directory()
@@ -69,8 +70,7 @@ return screen:new {
breadcrumb = item:filepath()
})
elseif
- item:filepath():match("%.playlist$") or
- item:filepath():match("%.m3u8?$") then
+ playlist_iterator:is_playlist(item) then
queue.open_playlist(item:filepath())
playback.playing:set(true)
backstack.push(playing:new())
diff --git a/lua/fonts/fusion12 b/lua/fonts/fusion12
index 3745c17d..c5380a47 100644
--- a/lua/fonts/fusion12
+++ b/lua/fonts/fusion12
Binary files differ
diff --git a/lua/images.lua b/lua/images.lua
index b10d0f74..69bd8e4b 100644
--- a/lua/images.lua
+++ b/lua/images.lua
@@ -29,6 +29,8 @@ local img = {
unlistened = lvgl.ImgData("//lua/img/unlistened.png"),
info = lvgl.ImgData("//lua/img/info.png"),
menu = lvgl.ImgData("//lua/img/menu.png"),
+ file_directory = lvgl.ImgData("//lua/img/file_icons/directory.png"),
+ file_playlist = lvgl.ImgData("//lua/img/file_icons/playlist.png"),
}
return img
diff --git a/lua/img/file_icons/directory.png b/lua/img/file_icons/directory.png
new file mode 100644
index 00000000..32624415
--- /dev/null
+++ b/lua/img/file_icons/directory.png
Binary files differ
diff --git a/lua/img/file_icons/playlist.png b/lua/img/file_icons/playlist.png
new file mode 100644
index 00000000..4787d59c
--- /dev/null
+++ b/lua/img/file_icons/playlist.png
Binary files differ
diff --git a/lua/main_menu.lua b/lua/main_menu.lua
index cc7874d7..8754df85 100644
--- a/lua/main_menu.lua
+++ b/lua/main_menu.lua
@@ -11,7 +11,6 @@ local browser = require("browser")
local playing = require("playing")
local styles = require("styles")
local filesystem = require("filesystem")
-local screen = require("screen")
local font = require("font")
local theme = require("theme")
local img = require("images")
@@ -56,7 +55,6 @@ return widgets.MenuScreen:new {
now_playing:onClicked(function() backstack.push(playing:new()) end)
- local has_focus = false
local track_duration = nil
self.bindings = self.bindings + {
@@ -72,7 +70,6 @@ return widgets.MenuScreen:new {
now_playing:add_flag(lvgl.FLAG.HIDDEN)
return
else
- has_focus = true
now_playing:clear_flag(lvgl.FLAG.HIDDEN)
end
title:set { text = track.title }
@@ -131,6 +128,15 @@ return widgets.MenuScreen:new {
})
end
+ local playlist_btn = indexes_list:add_btn(nil, "Playlists")
+ playlist_btn:onClicked(function()
+ backstack.push(require("playlist_browser"):new {
+ title = "Playlists",
+ iterator = filesystem.iterator("/Playlists")
+ })
+ end)
+ playlist_btn:add_style(styles.list_item)
+
local function show_no_indexes(msg)
indexes_list:add_flag(lvgl.FLAG.HIDDEN)
no_indexes_container:clear_flag(lvgl.FLAG.HIDDEN)
@@ -140,6 +146,18 @@ return widgets.MenuScreen:new {
local function hide_no_indexes()
no_indexes_container:add_flag(lvgl.FLAG.HIDDEN)
indexes_list:clear_flag(lvgl.FLAG.HIDDEN)
+
+ if indexes[1] then
+ indexes[1].object:focus()
+ end
+ end
+
+ local function hide_playlist_listing()
+ playlist_btn:add_flag(lvgl.FLAG.HIDDEN)
+ end
+
+ local function show_playlist_listing()
+ playlist_btn:clear_flag(lvgl.FLAG.HIDDEN)
end
local function update_visible_indexes()
@@ -155,6 +173,13 @@ return widgets.MenuScreen:new {
end
if has_valid_index then
hide_no_indexes()
+
+ -- If we have valid indexes, then also check for playlists
+ if filesystem.iterator("/Playlists/"):next() == nil then
+ hide_playlist_listing()
+ else
+ show_playlist_listing()
+ end
else
if require("database").updating:get() then
show_no_indexes("The database is updating for the first time. Please wait.")
diff --git a/lua/playing.lua b/lua/playing.lua
index 08cdaaa2..9391a85c 100644
--- a/lua/playing.lua
+++ b/lua/playing.lua
@@ -276,7 +276,7 @@ return screen:new {
if queue.loading:get() then
title:set { text = "Loading..." }
else
- title:set{text=""}
+ title:set{ text = "Not Playing" }
end
album:set{text=""}
artist:set{text=""}
diff --git a/lua/playlist_browser.lua b/lua/playlist_browser.lua
new file mode 100644
index 00000000..8854a19f
--- /dev/null
+++ b/lua/playlist_browser.lua
@@ -0,0 +1,94 @@
+-- SPDX-FileCopyrightText: 2025 Sam Lord <code@samlord.co.uk>
+--
+-- SPDX-License-Identifier: GPL-3.0-only
+
+local lvgl = require("lvgl")
+local widgets = require("widgets")
+local backstack = require("backstack")
+local playing = require("playing")
+local filesystem = require("filesystem")
+local screen = require("screen")
+local font = require("font")
+local theme = require("theme")
+local playback = require("playback")
+local queue = require("queue")
+local playlist_iterator = require("playlist_iterator")
+local img = require("images")
+
+
+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
+ })
+
+ 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,
+ scrollbar_mode = lvgl.SCROLLBAR_MODE.OFF
+ }
+ theme.set_subject(header, "header")
+
+ if self.breadcrumb then
+ header:Label {
+ text = self.breadcrumb,
+ text_font = font.fusion_10
+ }
+ end
+
+ local get_icon_func = function(item)
+ if item:is_directory() then
+ return img.file_directory
+ else
+ return img.file_playlist
+ end
+ end
+
+ widgets.InfiniteList(self.root, playlist_iterator:create(self.iterator), {
+ focus_first_item = true,
+ get_icon = get_icon_func,
+ callback = function(item)
+ return function()
+ if item:is_directory() then
+ backstack.push(
+ require("playlist_browser"):new {
+ title = self.title,
+ iterator = filesystem.iterator(item:filepath()),
+ breadcrumb = item:filepath()
+ })
+ elseif
+ playlist_iterator:is_playlist(item) then
+ -- TODO: playlist viewer
+ queue.open_playlist(item:filepath())
+ playback.playing:set(true)
+ backstack.push(playing:new())
+ end
+ end
+ end
+ })
+ end
+}
diff --git a/lua/playlist_iterator.lua b/lua/playlist_iterator.lua
new file mode 100644
index 00000000..06e80ad2
--- /dev/null
+++ b/lua/playlist_iterator.lua
@@ -0,0 +1,35 @@
+local PlaylistIterator = {}
+
+function PlaylistIterator:is_playlist(item)
+ return item:filepath():match("%.playlist$")
+ or item:filepath():match("%.m3u8?$")
+end
+
+function PlaylistIterator:create(fs_iterator)
+ local iterator = fs_iterator:clone()
+ local obj = {};
+
+ local find_matching = function(iterate_fn)
+ local next = iterate_fn(iterator);
+ while next and (not PlaylistIterator:is_playlist(next) and not next:is_directory()) do
+ next = iterate_fn();
+ end
+ return next;
+ end
+
+ function obj:clone()
+ return PlaylistIterator:create(iterator)
+ end
+
+ function obj:next()
+ return find_matching(iterator.next)
+ end
+
+ function obj:prev()
+ return find_matching(iterator.prev)
+ end
+
+ return obj
+end
+
+return PlaylistIterator
diff --git a/lua/settings.lua b/lua/settings.lua
index c0e7c23e..aae6db99 100644
--- a/lua/settings.lua
+++ b/lua/settings.lua
@@ -93,6 +93,7 @@ settings.BluetoothSettings = SettingsScreen:new {
local enabled = enable_sw:enabled()
bluetooth.enabled:set(enabled)
end)
+ enable_sw:focus()
self.bindings = self.bindings + {
bluetooth.enabled:bind(function(en)
@@ -232,6 +233,7 @@ settings.HeadphonesSettings = SettingsScreen:new {
local selection = volume_chooser:get('selected') + 1
volume.limit_db:set(limits[selection])
end)
+ volume_chooser:focus()
theme.set_subject(self.content:Label {
text = "Left/Right balance",
@@ -304,6 +306,7 @@ settings.DisplaySettings = SettingsScreen:new {
brightness:onevent(lvgl.EVENT.VALUE_CHANGED, function()
display.brightness:set(brightness:value())
end)
+ brightness:focus()
self.bindings = self.bindings + {
display.brightness:bind(function(b)
@@ -372,6 +375,8 @@ settings.ThemeSettings = SettingsScreen:new {
backstack.reset(main_menu:new())
end
end)
+
+ theme_chooser:focus()
end
}
@@ -380,44 +385,62 @@ settings.InputSettings = SettingsScreen:new {
create_ui = function(self)
SettingsScreen.create_ui(self)
- theme.set_subject(self.content:Label {
- text = "Control scheme",
- }, "settings_title")
+ -- Use the control scheme enum lists to generate the relevant dropdowns
+ local make_scheme_control = function(self, scheme_list, control_scheme)
+ local option_to_scheme = {}
+ local scheme_to_option = {}
+ local option_idx = 0
+ local options = ""
+
+ -- Sort the keys to order the dropdowns the same as the enums
+ keys = {}
+ for i in pairs(scheme_list) do table.insert(keys, i) end
+ table.sort(keys)
+
+ for i, k in pairs(keys) do
+ v = scheme_list[k]
+
+ option_to_scheme[option_idx] = k
+ scheme_to_option[k] = option_idx
+ if option_idx > 0 then
+ options = options .. "\n"
+ end
+ options = options .. v
+ option_idx = option_idx + 1
+ end
- local schemes = controls.schemes()
- local option_to_scheme = {}
- local scheme_to_option = {}
+ local controls_chooser = self.content:Dropdown {
+ options = options,
+ symbol = img.chevron,
+ }
- local option_idx = 0
- local options = ""
+ self.bindings = self.bindings + {
+ control_scheme:bind(function(s)
+ local option = scheme_to_option[s]
+ controls_chooser:set({ selected = option })
+ end)
+ }
- for i, v in pairs(schemes) do
- option_to_scheme[option_idx] = i
- scheme_to_option[i] = option_idx
- if option_idx > 0 then
- options = options .. "\n"
- end
- options = options .. v
- option_idx = option_idx + 1
+ controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function()
+ local option = controls_chooser:get('selected')
+ local scheme = option_to_scheme[option]
+ control_scheme:set(scheme)
+ end)
+
+ return controls_chooser
end
- local controls_chooser = self.content:Dropdown {
- options = options,
- symbol = img.chevron,
- }
+ theme.set_subject(self.content:Label {
+ text = "Control scheme",
+ }, "settings_title")
+ local controls_chooser = make_scheme_control(self, controls.schemes(), controls.scheme)
- self.bindings = self.bindings + {
- controls.scheme:bind(function(s)
- local option = scheme_to_option[s]
- controls_chooser:set({ selected = option })
- end)
- }
+ theme.set_subject(self.content:Label {
+ text = "Control scheme when locked",
+ }, "settings_title")
+ make_scheme_control(self, controls.locked_schemes(), controls.locked_scheme)
- controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function()
- local option = controls_chooser:get('selected')
- local scheme = option_to_scheme[option]
- controls.scheme:set(scheme)
- end)
+ controls_chooser:focus()
theme.set_subject(self.content:Label {
text = "Scroll Sensitivity",
@@ -483,6 +506,7 @@ settings.MassStorageSettings = SettingsScreen:new {
end
bind_switch()
end)
+ enable_sw:focus()
self.bindings = self.bindings + {
usb.msc_enabled:bind(bind_switch),
@@ -560,6 +584,7 @@ settings.DatabaseSettings = SettingsScreen:new {
update:onClicked(function()
database.update()
end)
+ update:focus()
self.bindings = self.bindings + {
database.auto_update:bind(function(en)
@@ -841,10 +866,11 @@ settings.Root = widgets.MenuScreen:new {
end)
end
item:add_style(styles.list_item)
+ return item
end
local audio_section = section("Audio")
- submenu("Bluetooth", settings.BluetoothSettings, audio_section)
+ local first_item = submenu("Bluetooth", settings.BluetoothSettings, audio_section)
submenu("Headphones", settings.HeadphonesSettings)
section("Interface")
@@ -863,6 +889,8 @@ settings.Root = widgets.MenuScreen:new {
submenu("Firmware", settings.FirmwareSettings)
submenu("Licenses", settings.LicensesScreen)
submenu("Regulatory", settings.RegulatoryScreen)
+
+ first_item:focus()
end
}
diff --git a/lua/theme_hicon.lua b/lua/theme_hicon.lua
index 80f78a10..f712e0a6 100644
--- a/lua/theme_hicon.lua
+++ b/lua/theme_hicon.lua
@@ -179,6 +179,8 @@ local theme_hicon = {
bg_color = background_color,
border_color = text_color,
border_width = 1,
+ outline_color = background_color,
+ outline_width = 1,
}},
{lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style {
bg_color = text_color,
diff --git a/lua/theme_light.lua b/lua/theme_light.lua
index f4039766..2addf546 100644
--- a/lua/theme_light.lua
+++ b/lua/theme_light.lua
@@ -186,6 +186,7 @@ local theme_light = {
},
dropdown = {
{lvgl.PART.MAIN, lvgl.Style{
+ bg_opa = lvgl.OPA(100),
radius = 2,
pad_all = 2,
bg_color = background_color,
@@ -196,7 +197,8 @@ local theme_light = {
outline_width = 1,
}},
{lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style {
- border_color = highlight_color,
+ text_color = "#ffffff",
+ bg_color = highlight_color,
}},
{lvgl.PART.INDICATOR, lvgl.Style {
image_recolor_opa = 255,
@@ -213,6 +215,7 @@ local theme_light = {
bg_color = background_color
}},
{lvgl.PART.SELECTED | lvgl.STATE.CHECKED, lvgl.Style {
+ text_color = "#ffffff",
bg_color = highlight_color,
}},
},
diff --git a/lua/widgets.lua b/lua/widgets.lua
index 5e18809b..20f0cd2a 100644
--- a/lua/widgets.lua
+++ b/lua/widgets.lua
@@ -306,10 +306,11 @@ function widgets.InfiniteList(parent, iterator, opts)
fwd_iterator:prev()
end
- local function add_item(item, index)
+ local function add_item(item, index, item_opts)
if not item then
return
end
+ item_opts = item_opts or {}
local this_item = index
local add_to_top = false
if this_item < first_index then
@@ -325,6 +326,9 @@ function widgets.InfiniteList(parent, iterator, opts)
if add_to_top then
btn:move_to_index(0)
end
+ if item_opts.focus then
+ btn:focus()
+ end
-- opts.callback should take an item and return a function matching the arg of onClicked
if opts.callback then
btn:onClicked(opts.callback(item))
@@ -357,11 +361,11 @@ function widgets.InfiniteList(parent, iterator, opts)
end
for idx = 0, 8 do
- local val = fwd_iterator()
+ local val = fwd_iterator:next()
if not val then
break
end
- add_item(val, idx)
+ add_item(val, idx, { focus = (opts.focus_first_item and idx == 0) })
end
return infinite_list
diff --git a/src/drivers/include/drivers/nvs.hpp b/src/drivers/include/drivers/nvs.hpp
index 9725bb0f..18bc5de6 100644
--- a/src/drivers/include/drivers/nvs.hpp
+++ b/src/drivers/include/drivers/nvs.hpp
@@ -138,6 +138,14 @@ class NvsStorage {
auto PrimaryInput() -> InputModes;
auto PrimaryInput(InputModes) -> void;
+ enum class LockedInputModes : uint8_t {
+ kDisabled = 0,
+ kVolumeOnly = 1,
+ };
+
+ auto LockedInput() -> LockedInputModes;
+ auto LockedInput(LockedInputModes) -> void;
+
auto QueueRepeatMode() -> uint8_t;
auto QueueRepeatMode(uint8_t) -> void;
@@ -167,6 +175,7 @@ class NvsStorage {
Setting<uint16_t> amp_cur_vol_;
Setting<int8_t> amp_left_bias_;
Setting<uint8_t> input_mode_;
+ Setting<uint8_t> locked_input_mode_;
Setting<uint8_t> output_mode_;
Setting<std::string> theme_;
diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp
index 6f0d874e..02a0058b 100644
--- a/src/drivers/nvs.cpp
+++ b/src/drivers/nvs.cpp
@@ -34,6 +34,7 @@ static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max";
static constexpr char kKeyAmpCurrentVolume[] = "hp_vol";
static constexpr char kKeyAmpLeftBias[] = "hp_bias";
static constexpr char kKeyPrimaryInput[] = "in_pri";
+static constexpr char kKeyLockedInput[] = "in_locked";
static constexpr char kKeyScrollSensitivity[] = "scroll";
static constexpr char kKeyLockPolarity[] = "lockpol";
static constexpr char kKeyDisplayCols[] = "dispcols";
@@ -272,6 +273,7 @@ NvsStorage::NvsStorage(nvs_handle_t handle)
amp_cur_vol_(kKeyAmpCurrentVolume),
amp_left_bias_(kKeyAmpLeftBias),
input_mode_(kKeyPrimaryInput),
+ locked_input_mode_(kKeyLockedInput),
output_mode_(kKeyOutput),
theme_{kKeyInterfaceTheme},
bt_preferred_(kKeyBluetoothPreferred),
@@ -300,6 +302,7 @@ auto NvsStorage::Read() -> void {
amp_cur_vol_.read(handle_);
amp_left_bias_.read(handle_);
input_mode_.read(handle_);
+ locked_input_mode_.read(handle_);
output_mode_.read(handle_);
theme_.read(handle_);
bt_preferred_.read(handle_);
@@ -323,6 +326,7 @@ auto NvsStorage::Write() -> bool {
amp_cur_vol_.write(handle_);
amp_left_bias_.write(handle_);
input_mode_.write(handle_);
+ locked_input_mode_.write(handle_);
output_mode_.write(handle_);
theme_.write(handle_);
bt_preferred_.write(handle_);
@@ -570,6 +574,23 @@ auto NvsStorage::PrimaryInput(InputModes mode) -> void {
input_mode_.set(static_cast<uint8_t>(mode));
}
+auto NvsStorage::LockedInput() -> LockedInputModes {
+ std::lock_guard<std::mutex> lock{mutex_};
+ switch (locked_input_mode_.get().value_or(static_cast<uint8_t>(LockedInputModes::kDisabled))) {
+ case static_cast<uint8_t>(LockedInputModes::kDisabled):
+ return LockedInputModes::kDisabled;
+ case static_cast<uint8_t>(LockedInputModes::kVolumeOnly):
+ return LockedInputModes::kVolumeOnly;
+ default:
+ return LockedInputModes::kDisabled;
+ }
+}
+
+auto NvsStorage::LockedInput(LockedInputModes mode) -> void {
+ std::lock_guard<std::mutex> lock{mutex_};
+ locked_input_mode_.set(static_cast<uint8_t>(mode));
+}
+
auto NvsStorage::QueueRepeatMode() -> uint8_t {
std::lock_guard<std::mutex> lock{mutex_};
return queue_repeat_mode_.get().value_or(0);
diff --git a/src/tangara/audio/track_queue.cpp b/src/tangara/audio/track_queue.cpp
index c54be11f..90d01992 100644
--- a/src/tangara/audio/track_queue.cpp
+++ b/src/tangara/audio/track_queue.cpp
@@ -181,6 +181,7 @@ auto TrackQueue::openPlaylist(const std::string& playlist_file, bool notify)
if (!res) {
return false;
}
+ ready_ = true;
updateShuffler(true);
if (notify) {
notifyChanged(true, Reason::kExplicitUpdate);
diff --git a/src/tangara/input/input_device.hpp b/src/tangara/input/input_device.hpp
index 7edded3e..424c0da3 100644
--- a/src/tangara/input/input_device.hpp
+++ b/src/tangara/input/input_device.hpp
@@ -10,6 +10,7 @@
#include <string>
#include <vector>
+#include "drivers/nvs.hpp"
#include "indev/lv_indev.h"
#include "input/input_hook.hpp"
#include "lua/property.hpp"
@@ -34,7 +35,7 @@ class IInputDevice {
}
/* Called by the LVGL driver when controls are being locked. */
- virtual auto onLock() -> void {}
+ virtual auto onLock(drivers::NvsStorage::LockedInputModes) -> void {}
/* Called by the LVGL driver when controls are being unlocked. */
virtual auto onUnlock() -> void {}
};
diff --git a/src/tangara/input/input_nav_buttons.cpp b/src/tangara/input/input_nav_buttons.cpp
index 54bef7a6..a5e10013 100644
--- a/src/tangara/input/input_nav_buttons.cpp
+++ b/src/tangara/input/input_nav_buttons.cpp
@@ -42,7 +42,7 @@ auto NavButtons::triggers()
return {up_, down_};
}
-auto NavButtons::onLock() -> void {
+auto NavButtons::onLock(drivers::NvsStorage::LockedInputModes mode) -> void {
locked_ = true;
}
diff --git a/src/tangara/input/input_nav_buttons.hpp b/src/tangara/input/input_nav_buttons.hpp
index 95d56d54..c9575fe0 100644
--- a/src/tangara/input/input_nav_buttons.hpp
+++ b/src/tangara/input/input_nav_buttons.hpp
@@ -28,7 +28,7 @@ class NavButtons : public IInputDevice {
auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> override;
- auto onLock() -> void override;
+ auto onLock(drivers::NvsStorage::LockedInputModes) -> void override;
auto onUnlock() -> void override;
private:
diff --git a/src/tangara/input/input_touch_dpad.cpp b/src/tangara/input/input_touch_dpad.cpp
index 8ed2bdd7..25c2315b 100644
--- a/src/tangara/input/input_touch_dpad.cpp
+++ b/src/tangara/input/input_touch_dpad.cpp
@@ -65,7 +65,7 @@ auto TouchDPad::triggers()
return {centre_, up_, right_, down_, left_};
}
-auto TouchDPad::onLock() -> void {
+auto TouchDPad::onLock(drivers::NvsStorage::LockedInputModes mode) -> void {
wheel_.LowPowerMode(true);
locked_ = true;
}
diff --git a/src/tangara/input/input_touch_dpad.hpp b/src/tangara/input/input_touch_dpad.hpp
index d787bace..086f556e 100644
--- a/src/tangara/input/input_touch_dpad.hpp
+++ b/src/tangara/input/input_touch_dpad.hpp
@@ -27,7 +27,7 @@ class TouchDPad : public IInputDevice {
auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> override;
- auto onLock() -> void override;
+ auto onLock(drivers::NvsStorage::LockedInputModes) -> void override;
auto onUnlock() -> void override;
private:
diff --git a/src/tangara/input/input_touch_wheel.cpp b/src/tangara/input/input_touch_wheel.cpp
index 19ac5211..1aee6fae 100644
--- a/src/tangara/input/input_touch_wheel.cpp
+++ b/src/tangara/input/input_touch_wheel.cpp
@@ -21,6 +21,8 @@
#include "lua/property.hpp"
#include "ui/ui_events.hpp"
+#include "esp_timer.h"
+
namespace input {
TouchWheel::TouchWheel(drivers::NvsStorage& nvs, drivers::TouchWheel& wheel)
@@ -52,7 +54,8 @@ TouchWheel::TouchWheel(drivers::NvsStorage& nvs, drivers::TouchWheel& wheel)
is_scrolling_(false),
threshold_(calculateThreshold(nvs.ScrollSensitivity())),
is_first_read_(true),
- last_angle_(0) {}
+ last_angle_(0),
+ last_wheel_touch_time_(0) {}
auto TouchWheel::read(lv_indev_data_t* data) -> void {
if (locked_) {
@@ -77,7 +80,16 @@ auto TouchWheel::read(lv_indev_data_t* data) -> void {
data->enc_diff = 0;
}
- centre_.update(wheel_data.is_button_touched && !wheel_data.is_wheel_touched,
+ // Prevent accidental center button touches while scrolling
+ if (wheel_data.is_wheel_touched) {
+ last_wheel_touch_time_ = esp_timer_get_time();
+ }
+
+ bool wheel_touch_timed_out =
+ esp_timer_get_time() - last_wheel_touch_time_ > SCROLL_TIMEOUT_US;
+
+ centre_.update(wheel_touch_timed_out && wheel_data.is_button_touched &&
+ !wheel_data.is_wheel_touched,
data);
// If the user is touching the wheel but not scrolling, then they may be
@@ -113,7 +125,7 @@ auto TouchWheel::triggers()
return {centre_, up_, right_, down_, left_};
}
-auto TouchWheel::onLock() -> void {
+auto TouchWheel::onLock(drivers::NvsStorage::LockedInputModes mode) -> void {
wheel_.LowPowerMode(true);
locked_ = true;
}
diff --git a/src/tangara/input/input_touch_wheel.hpp b/src/tangara/input/input_touch_wheel.hpp
index 982f89f4..420454b8 100644
--- a/src/tangara/input/input_touch_wheel.hpp
+++ b/src/tangara/input/input_touch_wheel.hpp
@@ -30,12 +30,14 @@ class TouchWheel : public IInputDevice {
auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> override;
- auto onLock() -> void override;
+ auto onLock(drivers::NvsStorage::LockedInputModes) -> void override;
auto onUnlock() -> void override;
auto sensitivity() -> lua::Property&;
private:
+ const int64_t SCROLL_TIMEOUT_US = 250000; // 250ms
+
auto calculateTicks(const drivers::TouchWheelData& data) -> int8_t;
auto calculateThreshold(uint8_t sensitivity) -> uint8_t;
@@ -55,6 +57,7 @@ class TouchWheel : public IInputDevice {
uint8_t threshold_;
bool is_first_read_;
uint8_t last_angle_;
+ int64_t last_wheel_touch_time_;
};
} // namespace input
diff --git a/src/tangara/input/input_volume_buttons.cpp b/src/tangara/input/input_volume_buttons.cpp
index 7ffdfcdc..5c814ffa 100644
--- a/src/tangara/input/input_volume_buttons.cpp
+++ b/src/tangara/input/input_volume_buttons.cpp
@@ -15,13 +15,15 @@ VolumeButtons::VolumeButtons(drivers::IGpios& gpios)
: gpios_(gpios),
up_("upper", actions::volumeUp()),
down_("lower", actions::volumeDown()),
- locked_(false) {}
+ locked_() {}
auto VolumeButtons::read(lv_indev_data_t* data) -> void {
bool up = !gpios_.Get(drivers::IGpios::Pin::kKeyUp);
bool down = !gpios_.Get(drivers::IGpios::Pin::kKeyDown);
- if ((up && down) || locked_) {
+ bool input_disabled = locked_.has_value() && (locked_ != drivers::NvsStorage::LockedInputModes::kVolumeOnly);
+
+ if ((up && down) || input_disabled) {
up = false;
down = false;
}
@@ -39,12 +41,12 @@ auto VolumeButtons::triggers()
return {up_, down_};
}
-auto VolumeButtons::onLock() -> void {
- locked_ = true;
+auto VolumeButtons::onLock(drivers::NvsStorage::LockedInputModes mode) -> void {
+ locked_ = mode;
}
auto VolumeButtons::onUnlock() -> void {
- locked_ = false;
+ locked_ = {};
}
} // namespace input
diff --git a/src/tangara/input/input_volume_buttons.hpp b/src/tangara/input/input_volume_buttons.hpp
index ffb3156b..35a44390 100644
--- a/src/tangara/input/input_volume_buttons.hpp
+++ b/src/tangara/input/input_volume_buttons.hpp
@@ -27,7 +27,7 @@ class VolumeButtons : public IInputDevice {
auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> override;
- auto onLock() -> void override;
+ auto onLock(drivers::NvsStorage::LockedInputModes) -> void override;
auto onUnlock() -> void override;
private:
@@ -36,7 +36,8 @@ class VolumeButtons : public IInputDevice {
TriggerHooks up_;
TriggerHooks down_;
- bool locked_;
+ // When locked, this contains the active mode
+ std::optional<drivers::NvsStorage::LockedInputModes> locked_;
};
} // namespace input
diff --git a/src/tangara/input/lvgl_input_driver.cpp b/src/tangara/input/lvgl_input_driver.cpp
index c008b007..2859c6a8 100644
--- a/src/tangara/input/lvgl_input_driver.cpp
+++ b/src/tangara/input/lvgl_input_driver.cpp
@@ -53,6 +53,8 @@ static void focus_cb(lv_group_t* group) {
instance->feedback(LV_EVENT_FOCUSED);
}
+namespace {
+
auto intToMode(int raw) -> std::optional<drivers::NvsStorage::InputModes> {
switch (raw) {
case 0:
@@ -68,6 +70,19 @@ auto intToMode(int raw) -> std::optional<drivers::NvsStorage::InputModes> {
}
}
+auto intToLockedMode(int raw) -> std::optional<drivers::NvsStorage::LockedInputModes> {
+ switch (raw) {
+ case 0:
+ return drivers::NvsStorage::LockedInputModes::kDisabled;
+ case 1:
+ return drivers::NvsStorage::LockedInputModes::kVolumeOnly;
+ default:
+ return {};
+ }
+}
+
+} // namespace {}
+
LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs,
DeviceFactory& factory)
: nvs_(nvs),
@@ -85,6 +100,18 @@ LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs,
inputs_ = factory.createInputs(*mode);
return true;
}),
+ locked_mode_(static_cast<int>(nvs.LockedInput()),
+ [&](const lua::LuaValue& val) {
+ if (!std::holds_alternative<int>(val)) {
+ return false;
+ }
+ auto mode = intToLockedMode(std::get<int>(val));
+ if (!mode) {
+ return false;
+ }
+ nvs.LockedInput(*mode);
+ return true;
+ }),
inputs_(factory.createInputs(nvs.PrimaryInput())),
feedbacks_(factory.createFeedbacks()),
is_locked_(false) {
@@ -130,9 +157,11 @@ auto LvglInputDriver::feedback(uint8_t event) -> void {
auto LvglInputDriver::lock(bool l) -> void {
is_locked_ = l;
+ auto locked_input_mode = nvs_.LockedInput();
+
for (auto&& device : inputs_) {
if (l) {
- device->onLock();
+ device->onLock(locked_input_mode);
} else {
device->onUnlock();
}
diff --git a/src/tangara/input/lvgl_input_driver.hpp b/src/tangara/input/lvgl_input_driver.hpp
index 9b62c24d..ce950621 100644
--- a/src/tangara/input/lvgl_input_driver.hpp
+++ b/src/tangara/input/lvgl_input_driver.hpp
@@ -36,6 +36,7 @@ class LvglInputDriver {
LvglInputDriver(drivers::NvsStorage& nvs, DeviceFactory&);
auto mode() -> lua::Property& { return mode_; }
+ auto lockedMode() -> lua::Property& { return locked_mode_; }
auto setGroup(lv_group_t*) -> void;
auto read(lv_indev_data_t* data) -> void;
@@ -49,6 +50,7 @@ class LvglInputDriver {
DeviceFactory& factory_;
lua::Property mode_;
+ lua::Property locked_mode_;
lv_indev_t* device_;
std::vector<std::shared_ptr<IInputDevice>> inputs_;
diff --git a/src/tangara/lua/lua_controls.cpp b/src/tangara/lua/lua_controls.cpp
index baf40891..87b7ca16 100644
--- a/src/tangara/lua/lua_controls.cpp
+++ b/src/tangara/lua/lua_controls.cpp
@@ -42,7 +42,24 @@ static auto controls_schemes(lua_State* L) -> int {
return 1;
}
+static auto locked_controls_schemes(lua_State* L) -> int {
+ lua_newtable(L);
+
+ lua_pushliteral(L, "Disabled");
+ lua_rawseti(
+ L, -2,
+ static_cast<int>(drivers::NvsStorage::LockedInputModes::kDisabled));
+
+ lua_pushliteral(L, "Volume Only");
+ lua_rawseti(
+ L, -2,
+ static_cast<int>(drivers::NvsStorage::LockedInputModes::kVolumeOnly));
+
+ return 1;
+}
+
static const struct luaL_Reg kControlsFuncs[] = {{"schemes", controls_schemes},
+ {"locked_schemes", locked_controls_schemes},
{NULL, NULL}};
static auto lua_controls(lua_State* state) -> int {
diff --git a/src/tangara/ui/ui_fsm.cpp b/src/tangara/ui/ui_fsm.cpp
index 4a54d974..1823f780 100644
--- a/src/tangara/ui/ui_fsm.cpp
+++ b/src/tangara/ui/ui_fsm.cpp
@@ -667,6 +667,7 @@ void Lua::entry() {
"controls",
{
{"scheme", &sInput->mode()},
+ {"locked_scheme", &sInput->lockedMode()},
{"lock_switch", &sLockSwitch},
{"hooks", [&](lua_State* L) { return sInput->pushHooks(L); }},
});
diff --git a/tools/cmake/common.cmake b/tools/cmake/common.cmake
index 2dc74108..3d3a9e66 100644
--- a/tools/cmake/common.cmake
+++ b/tools/cmake/common.cmake
@@ -5,7 +5,7 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
-set(PROJECT_VER "1.1.2")
+set(PROJECT_VER "1.2.0")
# esp-idf sets the C++ standard weird. Set cmake vars to match.
set(CMAKE_CXX_STANDARD 23)
diff --git a/tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/ark-pixel.txt b/tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/ark-pixel.txt
new file mode 100644
index 00000000..be1dc1f3
--- /dev/null
+++ b/tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/ark-pixel.txt
@@ -0,0 +1,94 @@
+Copyright (c) 2021, TakWolf (https://takwolf.com),
+with Reserved Font Name 'Ark Pixel'.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/cubic-11.txt b/tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/cubic-11.txt
new file mode 100644
index 00000000..4e863313
--- /dev/null
+++ b/tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/cubic-11.txt
@@ -0,0 +1,108 @@
+[Cubic 11]
+These fonts are free software.
+Unlimited permission is granted to use, copy, and distribute them, with or without modification, either commercially or noncommercially.
+THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
+此字型是免費的。
+無論您是否進行對本字型進行商業或非商業性修改,均可無限制地使用,複製和分發它們。
+本字型的衍生品之授權必須與此字型相同,且不作任何擔保。
+[JF Dot M+H 12]
+Copyright(c) 2005 M+ FONTS PROJECT
+[M+ BITMAP FONTS]
+Copyright (C) 2002-2004 COZ
+These fonts are free software.
+Unlimited permission is granted to use, copy, and distribute it, with or without modification, either commercially and noncommercially.
+THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
+これらのフォントはフリー(自由な)ソフトウエアです。
+あらゆる改変の有無に関わらず、また商業的な利用であっても、自由にご利用、複製、再配布することができますが、全て無保証とさせていただきます。
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/galmuri.txt b/tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/galmuri.txt
new file mode 100644
index 00000000..c6c485da
--- /dev/null
+++ b/tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/galmuri.txt
@@ -0,0 +1,93 @@
+Copyright (c) 2019–2024 Lee Minseo (quiple@quiple.dev)
+
+This font software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/tools/fonts/fusion/fusion-pixel-12px-proportional/OFL.txt b/tools/fonts/fusion/fusion-pixel-12px-proportional/OFL.txt
new file mode 100644
index 00000000..45986b79
--- /dev/null
+++ b/tools/fonts/fusion/fusion-pixel-12px-proportional/OFL.txt
@@ -0,0 +1,94 @@
+Copyright (c) 2022, TakWolf (https://takwolf.com),
+with Reserved Font Name 'Fusion Pixel'.
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ja.ttf b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ja.ttf
new file mode 100644
index 00000000..08bf3f01
--- /dev/null
+++ b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ja.ttf
Binary files differ
diff --git a/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ko.ttf b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ko.ttf
new file mode 100644
index 00000000..b11ee7a3
--- /dev/null
+++ b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ko.ttf
Binary files differ
diff --git a/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-latin.ttf b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-latin.ttf
new file mode 100644
index 00000000..4684dda5
--- /dev/null
+++ b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-latin.ttf
Binary files differ
diff --git a/tools/fonts/fusion/fusion-pixel-12px-proportional.ttf b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-zh_hans.ttf
index 2f5ae548..9052c501 100644
--- a/tools/fonts/fusion/fusion-pixel-12px-proportional.ttf
+++ b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-zh_hans.ttf
Binary files differ
diff --git a/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-zh_hant.ttf b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-zh_hant.ttf
new file mode 100644
index 00000000..dfa93ea9
--- /dev/null
+++ b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-zh_hant.ttf
Binary files differ
diff --git a/tools/fonts/fusion12 b/tools/fonts/fusion12
new file mode 100644
index 00000000..c5380a47
--- /dev/null
+++ b/tools/fonts/fusion12
Binary files differ
diff --git a/tools/fonts/mkfonts.sh b/tools/fonts/mkfonts.sh
index a402536d..cdf9591a 100755
--- a/tools/fonts/mkfonts.sh
+++ b/tools/fonts/mkfonts.sh
@@ -8,10 +8,13 @@
# npm i lv_font_conv -g
fusion_12() {
lv_font_conv \
- --font fusion/fusion-pixel-12px-proportional.ttf \
+ --font fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-latin.ttf \
-r 0x2000-0x206F \
-r 0x20-0x7F,0xA0-0xFF \
- -r 0x3000-0x303f,0x3040-0x309F,0x30A0-0x30FF,0xFF00-0xFFEF,0x4E00-0x9FAF \
+ --font fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ja.ttf \
+ -r 0x3000-0x303f,0x3040-0x309F,0x30A0-0x30FF \
+ -r 0xFF00-0xFFEF,0x4E00-0x9FAF \
+ --font fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ko.ttf \
-r 0xAC00-0xD7AF \
--size 12 \
--bpp 1 --format bin -o fusion12