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 Binary files a/lua/fonts/fusion12 and b/lua/fonts/fusion12 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 Binary files /dev/null and b/lua/img/file_icons/directory.png differ diff --git a/lua/img/file_icons/playlist.png b/lua/img/file_icons/playlist.png new file mode 100644 index 00000000..4787d59c Binary files /dev/null and b/lua/img/file_icons/playlist.png 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 +-- +-- 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 amp_cur_vol_; Setting amp_left_bias_; Setting input_mode_; + Setting locked_input_mode_; Setting output_mode_; Setting 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(mode)); } +auto NvsStorage::LockedInput() -> LockedInputModes { + std::lock_guard lock{mutex_}; + switch (locked_input_mode_.get().value_or(static_cast(LockedInputModes::kDisabled))) { + case static_cast(LockedInputModes::kDisabled): + return LockedInputModes::kDisabled; + case static_cast(LockedInputModes::kVolumeOnly): + return LockedInputModes::kVolumeOnly; + default: + return LockedInputModes::kDisabled; + } +} + +auto NvsStorage::LockedInput(LockedInputModes mode) -> void { + std::lock_guard lock{mutex_}; + locked_input_mode_.set(static_cast(mode)); +} + auto NvsStorage::QueueRepeatMode() -> uint8_t { std::lock_guard 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 #include +#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> 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> 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> 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> 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 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 { switch (raw) { case 0: @@ -68,6 +70,19 @@ auto intToMode(int raw) -> std::optional { } } +auto intToLockedMode(int raw) -> std::optional { + 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(nvs.LockedInput()), + [&](const lua::LuaValue& val) { + if (!std::holds_alternative(val)) { + return false; + } + auto mode = intToLockedMode(std::get(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> 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(drivers::NvsStorage::LockedInputModes::kDisabled)); + + lua_pushliteral(L, "Volume Only"); + lua_rawseti( + L, -2, + static_cast(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 Binary files /dev/null and b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ja.ttf 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 Binary files /dev/null and b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-ko.ttf 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 Binary files /dev/null and b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-latin.ttf 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 similarity index 54% rename from tools/fonts/fusion/fusion-pixel-12px-proportional.ttf rename to tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-zh_hans.ttf index 2f5ae548..9052c501 100644 Binary files a/tools/fonts/fusion/fusion-pixel-12px-proportional.ttf and b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-zh_hans.ttf 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 Binary files /dev/null and b/tools/fonts/fusion/fusion-pixel-12px-proportional/fusion-pixel-12px-proportional-zh_hant.ttf differ diff --git a/tools/fonts/fusion12 b/tools/fonts/fusion12 new file mode 100644 index 00000000..c5380a47 Binary files /dev/null and b/tools/fonts/fusion12 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