Merge pull request 'main' (#1) from cool-tech-zone/tangara-fw:main into main

Reviewed-on: https://codeberg.org/rdsh/tangara-fw/pulls/1
custom
rdsh 2 months ago
commit 2ec84a1393
  1. 1
      .gitignore
  2. 2
      lib/libtags/wav.c
  3. 1
      lua/browser.lua
  4. 6
      lua/file_browser.lua
  5. BIN
      lua/fonts/fusion12
  6. 2
      lua/images.lua
  7. BIN
      lua/img/file_icons/directory.png
  8. BIN
      lua/img/file_icons/playlist.png
  9. 31
      lua/main_menu.lua
  10. 2
      lua/playing.lua
  11. 94
      lua/playlist_browser.lua
  12. 35
      lua/playlist_iterator.lua
  13. 52
      lua/settings.lua
  14. 2
      lua/theme_hicon.lua
  15. 5
      lua/theme_light.lua
  16. 10
      lua/widgets.lua
  17. 9
      src/drivers/include/drivers/nvs.hpp
  18. 21
      src/drivers/nvs.cpp
  19. 1
      src/tangara/audio/track_queue.cpp
  20. 3
      src/tangara/input/input_device.hpp
  21. 2
      src/tangara/input/input_nav_buttons.cpp
  22. 2
      src/tangara/input/input_nav_buttons.hpp
  23. 2
      src/tangara/input/input_touch_dpad.cpp
  24. 2
      src/tangara/input/input_touch_dpad.hpp
  25. 18
      src/tangara/input/input_touch_wheel.cpp
  26. 5
      src/tangara/input/input_touch_wheel.hpp
  27. 12
      src/tangara/input/input_volume_buttons.cpp
  28. 5
      src/tangara/input/input_volume_buttons.hpp
  29. 31
      src/tangara/input/lvgl_input_driver.cpp
  30. 2
      src/tangara/input/lvgl_input_driver.hpp
  31. 17
      src/tangara/lua/lua_controls.cpp
  32. 1
      src/tangara/ui/ui_fsm.cpp
  33. 2
      tools/cmake/common.cmake
  34. 94
      tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/ark-pixel.txt
  35. 108
      tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/cubic-11.txt
  36. 93
      tools/fonts/fusion/fusion-pixel-12px-proportional/LICENSE/galmuri.txt
  37. 94
      tools/fonts/fusion/fusion-pixel-12px-proportional/OFL.txt
  38. BIN
      tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ja.ttf
  39. BIN
      tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ko.ttf
  40. BIN
      tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-latin.ttf
  41. BIN
      tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-zh_hans.ttf
  42. BIN
      tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-zh_hant.ttf
  43. BIN
      tools/fonts/fusion12
  44. 7
      tools/fonts/mkfonts.sh

1
.gitignore vendored

@ -12,6 +12,7 @@ sdkconfig.old
.vscode .vscode
compile_commands.json compile_commands.json
warnings.txt warnings.txt
.DS_Store
doc/ doc/

@ -49,6 +49,8 @@ tagwav(Tagctx *ctx)
break; break;
sz -= 4+4; sz -= 4+4;
csz = leuint(d+4); csz = leuint(d+4);
if(csz % 2 == 1)
csz += 1;
if(sz < csz) if(sz < csz)
break; break;
sz -= csz; sz -= csz;

@ -118,6 +118,7 @@ return screen:new {
end end
widgets.InfiniteList(self.root, self.iterator, { widgets.InfiniteList(self.root, self.iterator, {
focus_first_item = true,
get_icon = get_icon_func, get_icon = get_icon_func,
callback = function(item) callback = function(item)
return function() return function()

@ -8,11 +8,11 @@ local backstack = require("backstack")
local font = require("font") local font = require("font")
local queue = require("queue") local queue = require("queue")
local playing = require("playing") local playing = require("playing")
local styles = require("styles")
local playback = require("playback") local playback = require("playback")
local theme = require("theme") local theme = require("theme")
local screen = require("screen") local screen = require("screen")
local filesystem = require("filesystem") local filesystem = require("filesystem")
local playlist_iterator = require("playlist_iterator")
return screen:new { return screen:new {
create_ui = function(self) create_ui = function(self)
@ -59,6 +59,7 @@ return screen:new {
end end
widgets.InfiniteList(self.root, self.iterator, { widgets.InfiniteList(self.root, self.iterator, {
focus_first_item = true,
callback = function(item) callback = function(item)
return function() return function()
local is_dir = item:is_directory() local is_dir = item:is_directory()
@ -69,8 +70,7 @@ return screen:new {
breadcrumb = item:filepath() breadcrumb = item:filepath()
}) })
elseif elseif
item:filepath():match("%.playlist$") or playlist_iterator:is_playlist(item) then
item:filepath():match("%.m3u8?$") then
queue.open_playlist(item:filepath()) queue.open_playlist(item:filepath())
playback.playing:set(true) playback.playing:set(true)
backstack.push(playing:new()) backstack.push(playing:new())

Binary file not shown.

@ -29,6 +29,8 @@ local img = {
unlistened = lvgl.ImgData("//lua/img/unlistened.png"), unlistened = lvgl.ImgData("//lua/img/unlistened.png"),
info = lvgl.ImgData("//lua/img/info.png"), info = lvgl.ImgData("//lua/img/info.png"),
menu = lvgl.ImgData("//lua/img/menu.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 return img

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

@ -11,7 +11,6 @@ local browser = require("browser")
local playing = require("playing") local playing = require("playing")
local styles = require("styles") local styles = require("styles")
local filesystem = require("filesystem") local filesystem = require("filesystem")
local screen = require("screen")
local font = require("font") local font = require("font")
local theme = require("theme") local theme = require("theme")
local img = require("images") local img = require("images")
@ -56,7 +55,6 @@ return widgets.MenuScreen:new {
now_playing:onClicked(function() backstack.push(playing:new()) end) now_playing:onClicked(function() backstack.push(playing:new()) end)
local has_focus = false
local track_duration = nil local track_duration = nil
self.bindings = self.bindings + { self.bindings = self.bindings + {
@ -72,7 +70,6 @@ return widgets.MenuScreen:new {
now_playing:add_flag(lvgl.FLAG.HIDDEN) now_playing:add_flag(lvgl.FLAG.HIDDEN)
return return
else else
has_focus = true
now_playing:clear_flag(lvgl.FLAG.HIDDEN) now_playing:clear_flag(lvgl.FLAG.HIDDEN)
end end
title:set { text = track.title } title:set { text = track.title }
@ -131,6 +128,15 @@ return widgets.MenuScreen:new {
}) })
end 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) local function show_no_indexes(msg)
indexes_list:add_flag(lvgl.FLAG.HIDDEN) indexes_list:add_flag(lvgl.FLAG.HIDDEN)
no_indexes_container:clear_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() local function hide_no_indexes()
no_indexes_container:add_flag(lvgl.FLAG.HIDDEN) no_indexes_container:add_flag(lvgl.FLAG.HIDDEN)
indexes_list:clear_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 end
local function update_visible_indexes() local function update_visible_indexes()
@ -155,6 +173,13 @@ return widgets.MenuScreen:new {
end end
if has_valid_index then if has_valid_index then
hide_no_indexes() 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 else
if require("database").updating:get() then if require("database").updating:get() then
show_no_indexes("The database is updating for the first time. Please wait.") show_no_indexes("The database is updating for the first time. Please wait.")

@ -276,7 +276,7 @@ return screen:new {
if queue.loading:get() then if queue.loading:get() then
title:set { text = "Loading..." } title:set { text = "Loading..." }
else else
title:set{text=""} title:set{ text = "Not Playing" }
end end
album:set{text=""} album:set{text=""}
artist:set{text=""} artist:set{text=""}

@ -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
}

@ -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

@ -93,6 +93,7 @@ settings.BluetoothSettings = SettingsScreen:new {
local enabled = enable_sw:enabled() local enabled = enable_sw:enabled()
bluetooth.enabled:set(enabled) bluetooth.enabled:set(enabled)
end) end)
enable_sw:focus()
self.bindings = self.bindings + { self.bindings = self.bindings + {
bluetooth.enabled:bind(function(en) bluetooth.enabled:bind(function(en)
@ -232,6 +233,7 @@ settings.HeadphonesSettings = SettingsScreen:new {
local selection = volume_chooser:get('selected') + 1 local selection = volume_chooser:get('selected') + 1
volume.limit_db:set(limits[selection]) volume.limit_db:set(limits[selection])
end) end)
volume_chooser:focus()
theme.set_subject(self.content:Label { theme.set_subject(self.content:Label {
text = "Left/Right balance", text = "Left/Right balance",
@ -304,6 +306,7 @@ settings.DisplaySettings = SettingsScreen:new {
brightness:onevent(lvgl.EVENT.VALUE_CHANGED, function() brightness:onevent(lvgl.EVENT.VALUE_CHANGED, function()
display.brightness:set(brightness:value()) display.brightness:set(brightness:value())
end) end)
brightness:focus()
self.bindings = self.bindings + { self.bindings = self.bindings + {
display.brightness:bind(function(b) display.brightness:bind(function(b)
@ -372,6 +375,8 @@ settings.ThemeSettings = SettingsScreen:new {
backstack.reset(main_menu:new()) backstack.reset(main_menu:new())
end end
end) end)
theme_chooser:focus()
end end
} }
@ -380,20 +385,23 @@ settings.InputSettings = SettingsScreen:new {
create_ui = function(self) create_ui = function(self)
SettingsScreen.create_ui(self) SettingsScreen.create_ui(self)
theme.set_subject(self.content:Label { -- Use the control scheme enum lists to generate the relevant dropdowns
text = "Control scheme", local make_scheme_control = function(self, scheme_list, control_scheme)
}, "settings_title")
local schemes = controls.schemes()
local option_to_scheme = {} local option_to_scheme = {}
local scheme_to_option = {} local scheme_to_option = {}
local option_idx = 0 local option_idx = 0
local options = "" local options = ""
for i, v in pairs(schemes) do -- Sort the keys to order the dropdowns the same as the enums
option_to_scheme[option_idx] = i keys = {}
scheme_to_option[i] = option_idx 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 if option_idx > 0 then
options = options .. "\n" options = options .. "\n"
end end
@ -407,7 +415,7 @@ settings.InputSettings = SettingsScreen:new {
} }
self.bindings = self.bindings + { self.bindings = self.bindings + {
controls.scheme:bind(function(s) control_scheme:bind(function(s)
local option = scheme_to_option[s] local option = scheme_to_option[s]
controls_chooser:set({ selected = option }) controls_chooser:set({ selected = option })
end) end)
@ -416,9 +424,24 @@ settings.InputSettings = SettingsScreen:new {
controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function() controls_chooser:onevent(lvgl.EVENT.VALUE_CHANGED, function()
local option = controls_chooser:get('selected') local option = controls_chooser:get('selected')
local scheme = option_to_scheme[option] local scheme = option_to_scheme[option]
controls.scheme:set(scheme) control_scheme:set(scheme)
end) end)
return controls_chooser
end
theme.set_subject(self.content:Label {
text = "Control scheme",
}, "settings_title")
local controls_chooser = make_scheme_control(self, controls.schemes(), controls.scheme)
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:focus()
theme.set_subject(self.content:Label { theme.set_subject(self.content:Label {
text = "Scroll Sensitivity", text = "Scroll Sensitivity",
}, "settings_title") }, "settings_title")
@ -483,6 +506,7 @@ settings.MassStorageSettings = SettingsScreen:new {
end end
bind_switch() bind_switch()
end) end)
enable_sw:focus()
self.bindings = self.bindings + { self.bindings = self.bindings + {
usb.msc_enabled:bind(bind_switch), usb.msc_enabled:bind(bind_switch),
@ -560,6 +584,7 @@ settings.DatabaseSettings = SettingsScreen:new {
update:onClicked(function() update:onClicked(function()
database.update() database.update()
end) end)
update:focus()
self.bindings = self.bindings + { self.bindings = self.bindings + {
database.auto_update:bind(function(en) database.auto_update:bind(function(en)
@ -841,10 +866,11 @@ settings.Root = widgets.MenuScreen:new {
end) end)
end end
item:add_style(styles.list_item) item:add_style(styles.list_item)
return item
end end
local audio_section = section("Audio") local audio_section = section("Audio")
submenu("Bluetooth", settings.BluetoothSettings, audio_section) local first_item = submenu("Bluetooth", settings.BluetoothSettings, audio_section)
submenu("Headphones", settings.HeadphonesSettings) submenu("Headphones", settings.HeadphonesSettings)
section("Interface") section("Interface")
@ -863,6 +889,8 @@ settings.Root = widgets.MenuScreen:new {
submenu("Firmware", settings.FirmwareSettings) submenu("Firmware", settings.FirmwareSettings)
submenu("Licenses", settings.LicensesScreen) submenu("Licenses", settings.LicensesScreen)
submenu("Regulatory", settings.RegulatoryScreen) submenu("Regulatory", settings.RegulatoryScreen)
first_item:focus()
end end
} }

@ -179,6 +179,8 @@ local theme_hicon = {
bg_color = background_color, bg_color = background_color,
border_color = text_color, border_color = text_color,
border_width = 1, border_width = 1,
outline_color = background_color,
outline_width = 1,
}}, }},
{lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style { {lvgl.PART.KNOB | lvgl.STATE.FOCUSED, lvgl.Style {
bg_color = text_color, bg_color = text_color,

@ -186,6 +186,7 @@ local theme_light = {
}, },
dropdown = { dropdown = {
{lvgl.PART.MAIN, lvgl.Style{ {lvgl.PART.MAIN, lvgl.Style{
bg_opa = lvgl.OPA(100),
radius = 2, radius = 2,
pad_all = 2, pad_all = 2,
bg_color = background_color, bg_color = background_color,
@ -196,7 +197,8 @@ local theme_light = {
outline_width = 1, outline_width = 1,
}}, }},
{lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style { {lvgl.PART.MAIN | lvgl.STATE.FOCUSED, lvgl.Style {
border_color = highlight_color, text_color = "#ffffff",
bg_color = highlight_color,
}}, }},
{lvgl.PART.INDICATOR, lvgl.Style { {lvgl.PART.INDICATOR, lvgl.Style {
image_recolor_opa = 255, image_recolor_opa = 255,
@ -213,6 +215,7 @@ local theme_light = {
bg_color = background_color bg_color = background_color
}}, }},
{lvgl.PART.SELECTED | lvgl.STATE.CHECKED, lvgl.Style { {lvgl.PART.SELECTED | lvgl.STATE.CHECKED, lvgl.Style {
text_color = "#ffffff",
bg_color = highlight_color, bg_color = highlight_color,
}}, }},
}, },

@ -306,10 +306,11 @@ function widgets.InfiniteList(parent, iterator, opts)
fwd_iterator:prev() fwd_iterator:prev()
end end
local function add_item(item, index) local function add_item(item, index, item_opts)
if not item then if not item then
return return
end end
item_opts = item_opts or {}
local this_item = index local this_item = index
local add_to_top = false local add_to_top = false
if this_item < first_index then if this_item < first_index then
@ -325,6 +326,9 @@ function widgets.InfiniteList(parent, iterator, opts)
if add_to_top then if add_to_top then
btn:move_to_index(0) btn:move_to_index(0)
end end
if item_opts.focus then
btn:focus()
end
-- opts.callback should take an item and return a function matching the arg of onClicked -- opts.callback should take an item and return a function matching the arg of onClicked
if opts.callback then if opts.callback then
btn:onClicked(opts.callback(item)) btn:onClicked(opts.callback(item))
@ -357,11 +361,11 @@ function widgets.InfiniteList(parent, iterator, opts)
end end
for idx = 0, 8 do for idx = 0, 8 do
local val = fwd_iterator() local val = fwd_iterator:next()
if not val then if not val then
break break
end end
add_item(val, idx) add_item(val, idx, { focus = (opts.focus_first_item and idx == 0) })
end end
return infinite_list return infinite_list

@ -138,6 +138,14 @@ class NvsStorage {
auto PrimaryInput() -> InputModes; auto PrimaryInput() -> InputModes;
auto PrimaryInput(InputModes) -> void; 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;
auto QueueRepeatMode(uint8_t) -> void; auto QueueRepeatMode(uint8_t) -> void;
@ -167,6 +175,7 @@ class NvsStorage {
Setting<uint16_t> amp_cur_vol_; Setting<uint16_t> amp_cur_vol_;
Setting<int8_t> amp_left_bias_; Setting<int8_t> amp_left_bias_;
Setting<uint8_t> input_mode_; Setting<uint8_t> input_mode_;
Setting<uint8_t> locked_input_mode_;
Setting<uint8_t> output_mode_; Setting<uint8_t> output_mode_;
Setting<std::string> theme_; Setting<std::string> theme_;

@ -34,6 +34,7 @@ static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max";
static constexpr char kKeyAmpCurrentVolume[] = "hp_vol"; static constexpr char kKeyAmpCurrentVolume[] = "hp_vol";
static constexpr char kKeyAmpLeftBias[] = "hp_bias"; static constexpr char kKeyAmpLeftBias[] = "hp_bias";
static constexpr char kKeyPrimaryInput[] = "in_pri"; static constexpr char kKeyPrimaryInput[] = "in_pri";
static constexpr char kKeyLockedInput[] = "in_locked";
static constexpr char kKeyScrollSensitivity[] = "scroll"; static constexpr char kKeyScrollSensitivity[] = "scroll";
static constexpr char kKeyLockPolarity[] = "lockpol"; static constexpr char kKeyLockPolarity[] = "lockpol";
static constexpr char kKeyDisplayCols[] = "dispcols"; static constexpr char kKeyDisplayCols[] = "dispcols";
@ -272,6 +273,7 @@ NvsStorage::NvsStorage(nvs_handle_t handle)
amp_cur_vol_(kKeyAmpCurrentVolume), amp_cur_vol_(kKeyAmpCurrentVolume),
amp_left_bias_(kKeyAmpLeftBias), amp_left_bias_(kKeyAmpLeftBias),
input_mode_(kKeyPrimaryInput), input_mode_(kKeyPrimaryInput),
locked_input_mode_(kKeyLockedInput),
output_mode_(kKeyOutput), output_mode_(kKeyOutput),
theme_{kKeyInterfaceTheme}, theme_{kKeyInterfaceTheme},
bt_preferred_(kKeyBluetoothPreferred), bt_preferred_(kKeyBluetoothPreferred),
@ -300,6 +302,7 @@ auto NvsStorage::Read() -> void {
amp_cur_vol_.read(handle_); amp_cur_vol_.read(handle_);
amp_left_bias_.read(handle_); amp_left_bias_.read(handle_);
input_mode_.read(handle_); input_mode_.read(handle_);
locked_input_mode_.read(handle_);
output_mode_.read(handle_); output_mode_.read(handle_);
theme_.read(handle_); theme_.read(handle_);
bt_preferred_.read(handle_); bt_preferred_.read(handle_);
@ -323,6 +326,7 @@ auto NvsStorage::Write() -> bool {
amp_cur_vol_.write(handle_); amp_cur_vol_.write(handle_);
amp_left_bias_.write(handle_); amp_left_bias_.write(handle_);
input_mode_.write(handle_); input_mode_.write(handle_);
locked_input_mode_.write(handle_);
output_mode_.write(handle_); output_mode_.write(handle_);
theme_.write(handle_); theme_.write(handle_);
bt_preferred_.write(handle_); bt_preferred_.write(handle_);
@ -570,6 +574,23 @@ auto NvsStorage::PrimaryInput(InputModes mode) -> void {
input_mode_.set(static_cast<uint8_t>(mode)); 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 { auto NvsStorage::QueueRepeatMode() -> uint8_t {
std::lock_guard<std::mutex> lock{mutex_}; std::lock_guard<std::mutex> lock{mutex_};
return queue_repeat_mode_.get().value_or(0); return queue_repeat_mode_.get().value_or(0);

@ -181,6 +181,7 @@ auto TrackQueue::openPlaylist(const std::string& playlist_file, bool notify)
if (!res) { if (!res) {
return false; return false;
} }
ready_ = true;
updateShuffler(true); updateShuffler(true);
if (notify) { if (notify) {
notifyChanged(true, Reason::kExplicitUpdate); notifyChanged(true, Reason::kExplicitUpdate);

@ -10,6 +10,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "drivers/nvs.hpp"
#include "indev/lv_indev.h" #include "indev/lv_indev.h"
#include "input/input_hook.hpp" #include "input/input_hook.hpp"
#include "lua/property.hpp" #include "lua/property.hpp"
@ -34,7 +35,7 @@ class IInputDevice {
} }
/* Called by the LVGL driver when controls are being locked. */ /* 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. */ /* Called by the LVGL driver when controls are being unlocked. */
virtual auto onUnlock() -> void {} virtual auto onUnlock() -> void {}
}; };

@ -42,7 +42,7 @@ auto NavButtons::triggers()
return {up_, down_}; return {up_, down_};
} }
auto NavButtons::onLock() -> void { auto NavButtons::onLock(drivers::NvsStorage::LockedInputModes mode) -> void {
locked_ = true; locked_ = true;
} }

@ -28,7 +28,7 @@ class NavButtons : public IInputDevice {
auto name() -> std::string override; auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> 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 onUnlock() -> void override;
private: private:

@ -65,7 +65,7 @@ auto TouchDPad::triggers()
return {centre_, up_, right_, down_, left_}; return {centre_, up_, right_, down_, left_};
} }
auto TouchDPad::onLock() -> void { auto TouchDPad::onLock(drivers::NvsStorage::LockedInputModes mode) -> void {
wheel_.LowPowerMode(true); wheel_.LowPowerMode(true);
locked_ = true; locked_ = true;
} }

@ -27,7 +27,7 @@ class TouchDPad : public IInputDevice {
auto name() -> std::string override; auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> 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 onUnlock() -> void override;
private: private:

@ -21,6 +21,8 @@
#include "lua/property.hpp" #include "lua/property.hpp"
#include "ui/ui_events.hpp" #include "ui/ui_events.hpp"
#include "esp_timer.h"
namespace input { namespace input {
TouchWheel::TouchWheel(drivers::NvsStorage& nvs, drivers::TouchWheel& wheel) TouchWheel::TouchWheel(drivers::NvsStorage& nvs, drivers::TouchWheel& wheel)
@ -52,7 +54,8 @@ TouchWheel::TouchWheel(drivers::NvsStorage& nvs, drivers::TouchWheel& wheel)
is_scrolling_(false), is_scrolling_(false),
threshold_(calculateThreshold(nvs.ScrollSensitivity())), threshold_(calculateThreshold(nvs.ScrollSensitivity())),
is_first_read_(true), is_first_read_(true),
last_angle_(0) {} last_angle_(0),
last_wheel_touch_time_(0) {}
auto TouchWheel::read(lv_indev_data_t* data) -> void { auto TouchWheel::read(lv_indev_data_t* data) -> void {
if (locked_) { if (locked_) {
@ -77,7 +80,16 @@ auto TouchWheel::read(lv_indev_data_t* data) -> void {
data->enc_diff = 0; 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); data);
// If the user is touching the wheel but not scrolling, then they may be // 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_}; return {centre_, up_, right_, down_, left_};
} }
auto TouchWheel::onLock() -> void { auto TouchWheel::onLock(drivers::NvsStorage::LockedInputModes mode) -> void {
wheel_.LowPowerMode(true); wheel_.LowPowerMode(true);
locked_ = true; locked_ = true;
} }

@ -30,12 +30,14 @@ class TouchWheel : public IInputDevice {
auto name() -> std::string override; auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> 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 onUnlock() -> void override;
auto sensitivity() -> lua::Property&; auto sensitivity() -> lua::Property&;
private: private:
const int64_t SCROLL_TIMEOUT_US = 250000; // 250ms
auto calculateTicks(const drivers::TouchWheelData& data) -> int8_t; auto calculateTicks(const drivers::TouchWheelData& data) -> int8_t;
auto calculateThreshold(uint8_t sensitivity) -> uint8_t; auto calculateThreshold(uint8_t sensitivity) -> uint8_t;
@ -55,6 +57,7 @@ class TouchWheel : public IInputDevice {
uint8_t threshold_; uint8_t threshold_;
bool is_first_read_; bool is_first_read_;
uint8_t last_angle_; uint8_t last_angle_;
int64_t last_wheel_touch_time_;
}; };
} // namespace input } // namespace input

@ -15,13 +15,15 @@ VolumeButtons::VolumeButtons(drivers::IGpios& gpios)
: gpios_(gpios), : gpios_(gpios),
up_("upper", actions::volumeUp()), up_("upper", actions::volumeUp()),
down_("lower", actions::volumeDown()), down_("lower", actions::volumeDown()),
locked_(false) {} locked_() {}
auto VolumeButtons::read(lv_indev_data_t* data) -> void { auto VolumeButtons::read(lv_indev_data_t* data) -> void {
bool up = !gpios_.Get(drivers::IGpios::Pin::kKeyUp); bool up = !gpios_.Get(drivers::IGpios::Pin::kKeyUp);
bool down = !gpios_.Get(drivers::IGpios::Pin::kKeyDown); 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; up = false;
down = false; down = false;
} }
@ -39,12 +41,12 @@ auto VolumeButtons::triggers()
return {up_, down_}; return {up_, down_};
} }
auto VolumeButtons::onLock() -> void { auto VolumeButtons::onLock(drivers::NvsStorage::LockedInputModes mode) -> void {
locked_ = true; locked_ = mode;
} }
auto VolumeButtons::onUnlock() -> void { auto VolumeButtons::onUnlock() -> void {
locked_ = false; locked_ = {};
} }
} // namespace input } // namespace input

@ -27,7 +27,7 @@ class VolumeButtons : public IInputDevice {
auto name() -> std::string override; auto name() -> std::string override;
auto triggers() -> std::vector<std::reference_wrapper<TriggerHooks>> 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 onUnlock() -> void override;
private: private:
@ -36,7 +36,8 @@ class VolumeButtons : public IInputDevice {
TriggerHooks up_; TriggerHooks up_;
TriggerHooks down_; TriggerHooks down_;
bool locked_; // When locked, this contains the active mode
std::optional<drivers::NvsStorage::LockedInputModes> locked_;
}; };
} // namespace input } // namespace input

@ -53,6 +53,8 @@ static void focus_cb(lv_group_t* group) {
instance->feedback(LV_EVENT_FOCUSED); instance->feedback(LV_EVENT_FOCUSED);
} }
namespace {
auto intToMode(int raw) -> std::optional<drivers::NvsStorage::InputModes> { auto intToMode(int raw) -> std::optional<drivers::NvsStorage::InputModes> {
switch (raw) { switch (raw) {
case 0: 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, LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs,
DeviceFactory& factory) DeviceFactory& factory)
: nvs_(nvs), : nvs_(nvs),
@ -85,6 +100,18 @@ LvglInputDriver::LvglInputDriver(drivers::NvsStorage& nvs,
inputs_ = factory.createInputs(*mode); inputs_ = factory.createInputs(*mode);
return true; 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())), inputs_(factory.createInputs(nvs.PrimaryInput())),
feedbacks_(factory.createFeedbacks()), feedbacks_(factory.createFeedbacks()),
is_locked_(false) { is_locked_(false) {
@ -130,9 +157,11 @@ auto LvglInputDriver::feedback(uint8_t event) -> void {
auto LvglInputDriver::lock(bool l) -> void { auto LvglInputDriver::lock(bool l) -> void {
is_locked_ = l; is_locked_ = l;
auto locked_input_mode = nvs_.LockedInput();
for (auto&& device : inputs_) { for (auto&& device : inputs_) {
if (l) { if (l) {
device->onLock(); device->onLock(locked_input_mode);
} else { } else {
device->onUnlock(); device->onUnlock();
} }

@ -36,6 +36,7 @@ class LvglInputDriver {
LvglInputDriver(drivers::NvsStorage& nvs, DeviceFactory&); LvglInputDriver(drivers::NvsStorage& nvs, DeviceFactory&);
auto mode() -> lua::Property& { return mode_; } auto mode() -> lua::Property& { return mode_; }
auto lockedMode() -> lua::Property& { return locked_mode_; }
auto setGroup(lv_group_t*) -> void; auto setGroup(lv_group_t*) -> void;
auto read(lv_indev_data_t* data) -> void; auto read(lv_indev_data_t* data) -> void;
@ -49,6 +50,7 @@ class LvglInputDriver {
DeviceFactory& factory_; DeviceFactory& factory_;
lua::Property mode_; lua::Property mode_;
lua::Property locked_mode_;
lv_indev_t* device_; lv_indev_t* device_;
std::vector<std::shared_ptr<IInputDevice>> inputs_; std::vector<std::shared_ptr<IInputDevice>> inputs_;

@ -42,7 +42,24 @@ static auto controls_schemes(lua_State* L) -> int {
return 1; 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}, static const struct luaL_Reg kControlsFuncs[] = {{"schemes", controls_schemes},
{"locked_schemes", locked_controls_schemes},
{NULL, NULL}}; {NULL, NULL}};
static auto lua_controls(lua_State* state) -> int { static auto lua_controls(lua_State* state) -> int {

@ -667,6 +667,7 @@ void Lua::entry() {
"controls", "controls",
{ {
{"scheme", &sInput->mode()}, {"scheme", &sInput->mode()},
{"locked_scheme", &sInput->lockedMode()},
{"lock_switch", &sLockSwitch}, {"lock_switch", &sLockSwitch},
{"hooks", [&](lua_State* L) { return sInput->pushHooks(L); }}, {"hooks", [&](lua_State* L) { return sInput->pushHooks(L); }},
}); });

@ -5,7 +5,7 @@
# For more information about build system see # For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html # 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. # esp-idf sets the C++ standard weird. Set cmake vars to match.
set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD 23)

@ -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.

@ -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.

@ -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.

@ -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.

Binary file not shown.

@ -8,10 +8,13 @@
# npm i lv_font_conv -g # npm i lv_font_conv -g
fusion_12() { fusion_12() {
lv_font_conv \ 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 0x2000-0x206F \
-r 0x20-0x7F,0xA0-0xFF \ -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 \ -r 0xAC00-0xD7AF \
--size 12 \ --size 12 \
--bpp 1 --format bin -o fusion12 --bpp 1 --format bin -o fusion12

Loading…
Cancel
Save