mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-10-23 13:33:03 +00:00
Add optional emoji extension pack
Fixes #33 Adds an installable emoji content/input extension, a generic candidate-picker IME backend, and registry support for small content supplement fonts.
This commit is contained in:
1 parent
fc4a3c6261
commit
b8352bc5e6
21 files changed
+830
-45
No files matched your search
@@ -54,6 +54,7 @@ constexpr const char* kDisabledImeSentinel = "__none__";
|
||||
constexpr std::size_t kFontLoadOverlayThresholdBytes = 64U * 1024U;
|
||||
constexpr unsigned kMaxMissingFontDiagnostics = 20;
|
||||
constexpr uint32_t kFontLoadFailureBackoffMs = 5U * 60U * 1000U;
|
||||
constexpr std::size_t kSmallContentSupplementMaxBytes = 16U * 1024U;
|
||||
|
||||
#if defined(LV_USE_FS_POSIX) && LV_USE_FS_POSIX
|
||||
constexpr bool kLvFsPosixEnabled = true;
|
||||
@@ -108,6 +109,7 @@ struct ImePackRecord
|
||||
std::string display_name;
|
||||
std::string backend;
|
||||
std::string layout;
|
||||
std::vector<std::string> candidates;
|
||||
bool builtin = false;
|
||||
};
|
||||
|
||||
@@ -574,11 +576,54 @@ bool ime_backend_supports_runtime_input(const ImePackRecord& pack)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (pack.backend == "builtin-candidate-picker")
|
||||
{
|
||||
return !pack.candidates.empty();
|
||||
}
|
||||
return pack.id == "ru-cyrillic-keyboard" &&
|
||||
pack.backend == "builtin-keyboard-layout" &&
|
||||
pack.layout == "ru-cyrillic";
|
||||
}
|
||||
|
||||
bool parse_ime_candidate_file(const std::string& path, std::vector<std::string>& out)
|
||||
{
|
||||
out.clear();
|
||||
|
||||
std::string contents;
|
||||
if (!::ui::fs::read_text_file(path.c_str(), contents))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t token_start = 0;
|
||||
while (token_start <= contents.size())
|
||||
{
|
||||
const std::size_t token_end = contents.find_first_of(",\r\n", token_start);
|
||||
std::string token = contents.substr(
|
||||
token_start,
|
||||
token_end == std::string::npos ? std::string::npos : (token_end - token_start));
|
||||
const std::size_t comment = token.find('#');
|
||||
if (comment != std::string::npos)
|
||||
{
|
||||
token.resize(comment);
|
||||
}
|
||||
trim_in_place(token);
|
||||
if (!token.empty() &&
|
||||
std::find(out.begin(), out.end(), token) == out.end())
|
||||
{
|
||||
out.push_back(std::move(token));
|
||||
}
|
||||
|
||||
if (token_end == std::string::npos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
token_start = token_end + 1U;
|
||||
}
|
||||
|
||||
return !out.empty();
|
||||
}
|
||||
|
||||
bool ime_backend_can_be_enabled(const ImePackRecord& pack)
|
||||
{
|
||||
return ime_backend_supports_runtime_input(pack);
|
||||
@@ -1207,10 +1252,35 @@ bool ensure_font_pack_loaded(FontPackRecord* pack)
|
||||
return load_font_pack(*pack);
|
||||
}
|
||||
|
||||
bool path_has_prefix(const std::string& path, const char* prefix)
|
||||
{
|
||||
if (path.empty() || prefix == nullptr || prefix[0] == '\0')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return path.rfind(prefix, 0) == 0;
|
||||
}
|
||||
|
||||
bool is_flash_pack_font_source(const FontPackRecord& pack)
|
||||
{
|
||||
#if UI_FS_HAS_FLASH_PACK_STORAGE
|
||||
return path_has_prefix(pack.source_path, "F:/trailmate/packs/");
|
||||
#else
|
||||
(void)pack;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool can_load_font_from_content_hot_path(const FontPackRecord& pack)
|
||||
{
|
||||
#if defined(ESP_PLATFORM) || defined(ARDUINO_ARCH_ESP32)
|
||||
return pack.builtin;
|
||||
// UI render paths must not synchronously touch SD-backed external fonts.
|
||||
// Small Flash-installed content supplements are allowed to become visible
|
||||
// after install without reintroducing the shared SD/display SPI stall class.
|
||||
return pack.builtin ||
|
||||
(is_flash_pack_font_source(pack) &&
|
||||
pack.estimated_ram_bytes > 0 &&
|
||||
pack.estimated_ram_bytes <= kSmallContentSupplementMaxBytes);
|
||||
#else
|
||||
(void)pack;
|
||||
return true;
|
||||
@@ -1227,6 +1297,22 @@ bool can_load_font_from_activation_path(const FontPackRecord& pack)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool can_add_content_supplement(const FontPackRecord& pack);
|
||||
|
||||
bool can_preload_small_content_supplement(const FontPackRecord& pack)
|
||||
{
|
||||
if (pack.builtin || is_font_runtime_loaded(pack))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!font_pack_supports_content(pack) || pack.coverage.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return pack.estimated_ram_bytes > 0 &&
|
||||
pack.estimated_ram_bytes <= kSmallContentSupplementMaxBytes;
|
||||
}
|
||||
|
||||
std::vector<FontPackRecord*> current_content_pack_sequence()
|
||||
{
|
||||
std::vector<FontPackRecord*> packs;
|
||||
@@ -1249,6 +1335,34 @@ void rebuild_runtime_font_chains()
|
||||
::ui::fonts::refresh_locale_font_bindings();
|
||||
}
|
||||
|
||||
bool preload_small_content_supplements()
|
||||
{
|
||||
bool changed = false;
|
||||
for (FontPackRecord& pack : s_font_packs)
|
||||
{
|
||||
if (!can_preload_small_content_supplement(pack) || !can_add_content_supplement(pack))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!ensure_font_pack_loaded(&pack))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
append_unique_pack(s_content_supplement_packs, &pack);
|
||||
changed = true;
|
||||
std::printf("%s font preload id=%s role=content_supplement bytes=%lu source=%s\n",
|
||||
kLogTag,
|
||||
pack.id.c_str(),
|
||||
static_cast<unsigned long>(pack.estimated_ram_bytes),
|
||||
pack.source_path.empty() ? "<none>" : pack.source_path.c_str());
|
||||
}
|
||||
if (changed)
|
||||
{
|
||||
rebuild_runtime_font_chains();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool ensure_active_content_pack_loaded()
|
||||
{
|
||||
bool changed = false;
|
||||
@@ -1548,6 +1662,7 @@ void rebuild_ime_views()
|
||||
view.display_name = pack.display_name.c_str();
|
||||
view.backend = pack.backend.c_str();
|
||||
view.layout = pack.layout.empty() ? nullptr : pack.layout.c_str();
|
||||
view.candidate_count = pack.candidates.size();
|
||||
view.builtin = pack.builtin;
|
||||
s_ime_views.push_back(view);
|
||||
}
|
||||
@@ -1748,12 +1863,24 @@ bool catalog_external_ime_pack(const std::string& pack_dir)
|
||||
{
|
||||
pack.layout = layout;
|
||||
}
|
||||
if (const char* candidates = manifest_value(manifest, "candidates"))
|
||||
{
|
||||
const std::string candidates_path = resolve_pack_file_path(pack_dir, candidates);
|
||||
if (!parse_ime_candidate_file(candidates_path, pack.candidates))
|
||||
{
|
||||
std::printf("%s ime pack candidates unavailable id=%s path=%s\n",
|
||||
kLogTag,
|
||||
pack.id.c_str(),
|
||||
candidates_path.empty() ? "<none>" : candidates_path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::printf("%s ime pack catalog id=%s backend=%s layout=%s\n",
|
||||
std::printf("%s ime pack catalog id=%s backend=%s layout=%s candidates=%lu\n",
|
||||
kLogTag,
|
||||
pack.id.c_str(),
|
||||
pack.backend.c_str(),
|
||||
pack.layout.empty() ? "<none>" : pack.layout.c_str());
|
||||
pack.layout.empty() ? "<none>" : pack.layout.c_str(),
|
||||
static_cast<unsigned long>(pack.candidates.size()));
|
||||
s_ime_packs.push_back(std::move(pack));
|
||||
return true;
|
||||
}
|
||||
@@ -2453,6 +2580,7 @@ void rebuild_registry()
|
||||
s_allow_sync_external_font_activation = true;
|
||||
(void)activate_locale(resolve_active_locale(preferred_locale));
|
||||
s_allow_sync_external_font_activation = false;
|
||||
(void)preload_small_content_supplements();
|
||||
std::printf("%s registry rebuild end active_locale=%s locale_count=%lu ime_count=%lu enabled_ime=%lu active_ime=%s ui_chain=%s content_chain=%s\n",
|
||||
kLogTag,
|
||||
s_active_locale ? s_active_locale->id.c_str() : "<none>",
|
||||
@@ -2707,7 +2835,9 @@ bool ensure_content_font_for_text(const char* text)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (!kAllowSynchronousContentSupplementFontLoad && !is_font_runtime_loaded(*candidate))
|
||||
if (!is_font_runtime_loaded(*candidate) &&
|
||||
!kAllowSynchronousContentSupplementFontLoad &&
|
||||
!can_load_font_from_content_hot_path(*candidate))
|
||||
{
|
||||
log_font_load_deferred(*candidate, "content_supplement", "ui_hot_path");
|
||||
break;
|
||||
@@ -2759,6 +2889,24 @@ const ImeInfo* ime_at(std::size_t index)
|
||||
return &s_ime_views[index];
|
||||
}
|
||||
|
||||
std::size_t ime_candidate_count(const char* ime_id)
|
||||
{
|
||||
ensure_registry();
|
||||
const ImePackRecord* pack = find_pack_by_id(s_ime_packs, ime_id);
|
||||
return pack ? pack->candidates.size() : 0;
|
||||
}
|
||||
|
||||
const char* ime_candidate_at(const char* ime_id, std::size_t index)
|
||||
{
|
||||
ensure_registry();
|
||||
const ImePackRecord* pack = find_pack_by_id(s_ime_packs, ime_id);
|
||||
if (pack == nullptr || index >= pack->candidates.size())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return pack->candidates[index].c_str();
|
||||
}
|
||||
|
||||
std::size_t enabled_ime_count()
|
||||
{
|
||||
ensure_registry();
|
||||
|
||||
@@ -155,9 +155,12 @@ std::string join_values(const std::vector<std::string>& values)
|
||||
return joined;
|
||||
}
|
||||
|
||||
bool is_language_package(const packs::PackageRecord& package)
|
||||
bool is_extension_package(const packs::PackageRecord& package)
|
||||
{
|
||||
return package.package_type.empty() || package.package_type == "locale-bundle";
|
||||
return package.package_type.empty() ||
|
||||
package.package_type == "locale-bundle" ||
|
||||
package.package_type == "content-bundle" ||
|
||||
package.package_type == "input-bundle";
|
||||
}
|
||||
|
||||
bool can_install_or_update(const packs::PackageRecord& package)
|
||||
@@ -460,7 +463,7 @@ void rebuild_filtered_indices()
|
||||
s_runtime.filtered_indices.clear();
|
||||
for (std::size_t i = 0; i < s_runtime.packages.size(); ++i)
|
||||
{
|
||||
if (is_language_package(s_runtime.packages[i]))
|
||||
if (is_extension_package(s_runtime.packages[i]))
|
||||
{
|
||||
s_runtime.filtered_indices.push_back(i);
|
||||
}
|
||||
@@ -822,7 +825,7 @@ void render_current_view()
|
||||
if (s_runtime.view == MainView::Detail)
|
||||
{
|
||||
const std::size_t selected_index = find_package_index(s_runtime.selected_package_id);
|
||||
if (selected_index != kInvalidIndex && is_language_package(s_runtime.packages[selected_index]))
|
||||
if (selected_index != kInvalidIndex && is_extension_package(s_runtime.packages[selected_index]))
|
||||
{
|
||||
render_detail_view(selected_index);
|
||||
return;
|
||||
@@ -878,7 +881,7 @@ void create_filter_panel(lv_obj_t* parent)
|
||||
lv_obj_add_event_cb(s_runtime.filter_btn, on_focus_scroll, LV_EVENT_FOCUSED, nullptr);
|
||||
|
||||
lv_obj_t* label = lv_label_create(s_runtime.filter_btn);
|
||||
::ui::i18n::set_label_text(label, "Language");
|
||||
::ui::i18n::set_label_text(label, "Packs");
|
||||
two_pane_styles::apply_label_primary(label);
|
||||
lv_obj_center(label);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
@@ -35,7 +36,15 @@ enum class ScriptInputKind
|
||||
{
|
||||
None,
|
||||
Pinyin,
|
||||
RuCyrillicKeyboard
|
||||
RuCyrillicKeyboard,
|
||||
CandidatePicker
|
||||
};
|
||||
|
||||
struct ScriptInputDescriptor
|
||||
{
|
||||
ScriptInputKind kind = ScriptInputKind::None;
|
||||
const char* ime_id = nullptr;
|
||||
const char* display_name = nullptr;
|
||||
};
|
||||
|
||||
const ::ui::i18n::ImeInfo* active_ime_info()
|
||||
@@ -58,9 +67,8 @@ const ::ui::i18n::ImeInfo* active_ime_info()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ScriptInputKind active_script_input_kind()
|
||||
ScriptInputKind script_input_kind_for_ime(const ::ui::i18n::ImeInfo* ime)
|
||||
{
|
||||
const ::ui::i18n::ImeInfo* ime = active_ime_info();
|
||||
if (!ime || !ime->id || !ime->backend)
|
||||
{
|
||||
return ScriptInputKind::None;
|
||||
@@ -77,9 +85,73 @@ ScriptInputKind active_script_input_kind()
|
||||
{
|
||||
return ScriptInputKind::RuCyrillicKeyboard;
|
||||
}
|
||||
if (std::strcmp(ime->backend, "builtin-candidate-picker") == 0 &&
|
||||
ime->candidate_count > 0)
|
||||
{
|
||||
return ScriptInputKind::CandidatePicker;
|
||||
}
|
||||
return ScriptInputKind::None;
|
||||
}
|
||||
|
||||
void append_unique_script_input(std::vector<ScriptInputDescriptor>& inputs,
|
||||
const ::ui::i18n::ImeInfo* ime)
|
||||
{
|
||||
const ScriptInputKind kind = script_input_kind_for_ime(ime);
|
||||
if (kind == ScriptInputKind::None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const char* ime_id = ime && ime->id ? ime->id : "";
|
||||
const auto existing = std::find_if(
|
||||
inputs.begin(),
|
||||
inputs.end(),
|
||||
[&](const ScriptInputDescriptor& input)
|
||||
{
|
||||
return input.ime_id != nullptr && std::strcmp(input.ime_id, ime_id) == 0;
|
||||
});
|
||||
if (existing == inputs.end())
|
||||
{
|
||||
inputs.push_back({kind, ime_id, ime && ime->display_name ? ime->display_name : ime_id});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ScriptInputDescriptor> available_script_inputs()
|
||||
{
|
||||
std::vector<ScriptInputDescriptor> inputs;
|
||||
append_unique_script_input(inputs, active_ime_info());
|
||||
|
||||
const std::size_t count = ::ui::i18n::ime_count();
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
const ::ui::i18n::ImeInfo* ime = ::ui::i18n::ime_at(index);
|
||||
if (ime == nullptr || ime->id == nullptr || !::ui::i18n::ime_enabled(ime->id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
append_unique_script_input(inputs, ime);
|
||||
}
|
||||
return inputs;
|
||||
}
|
||||
|
||||
ScriptInputDescriptor selected_script_input(int script_input_index)
|
||||
{
|
||||
const std::vector<ScriptInputDescriptor> inputs = available_script_inputs();
|
||||
if (inputs.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
if (script_input_index < 0 || script_input_index >= static_cast<int>(inputs.size()))
|
||||
{
|
||||
script_input_index = 0;
|
||||
}
|
||||
return inputs[static_cast<std::size_t>(script_input_index)];
|
||||
}
|
||||
|
||||
ScriptInputKind selected_script_input_kind(int script_input_index)
|
||||
{
|
||||
return selected_script_input(script_input_index).kind;
|
||||
}
|
||||
|
||||
bool script_mode(ImeWidget::Mode mode)
|
||||
{
|
||||
return mode == ImeWidget::Mode::SCRIPT;
|
||||
@@ -113,9 +185,58 @@ static const char* kTouchRuCyrillicMap[] = {
|
||||
"я", "ч", "с", "м", "и", "т", "ь", "б", "ю", ",", ".", "?", "\n",
|
||||
"Space", ""};
|
||||
|
||||
static const char* kTouchCandidatePickerMap[] = {
|
||||
"Prev", "Pick", "Next", "Bksp", "\n",
|
||||
"Space", "Enter", ""};
|
||||
|
||||
static constexpr const char* kRuCyrillicFontProbe =
|
||||
"йцукенгшщзхъфывапролджэячсмитьбю";
|
||||
#endif
|
||||
|
||||
const char* candidate_picker_ime_id(int script_input_index)
|
||||
{
|
||||
const ScriptInputDescriptor input = selected_script_input(script_input_index);
|
||||
return input.kind == ScriptInputKind::CandidatePicker ? input.ime_id : nullptr;
|
||||
}
|
||||
|
||||
const char* candidate_picker_display_name(int script_input_index)
|
||||
{
|
||||
const ScriptInputDescriptor input = selected_script_input(script_input_index);
|
||||
return input.kind == ScriptInputKind::CandidatePicker ? input.display_name : nullptr;
|
||||
}
|
||||
|
||||
int candidate_picker_candidate_count(int script_input_index)
|
||||
{
|
||||
const char* ime_id = candidate_picker_ime_id(script_input_index);
|
||||
return static_cast<int>(::ui::i18n::ime_candidate_count(ime_id));
|
||||
}
|
||||
|
||||
const char* candidate_picker_candidate_at(int script_input_index, int index)
|
||||
{
|
||||
if (index < 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const char* ime_id = candidate_picker_ime_id(script_input_index);
|
||||
return ::ui::i18n::ime_candidate_at(ime_id, static_cast<std::size_t>(index));
|
||||
}
|
||||
|
||||
std::vector<std::string> candidate_picker_candidates_vector(int script_input_index)
|
||||
{
|
||||
std::vector<std::string> candidates;
|
||||
candidates.reserve(static_cast<std::size_t>(
|
||||
candidate_picker_candidate_count(script_input_index)));
|
||||
const int count = candidate_picker_candidate_count(script_input_index);
|
||||
for (int index = 0; index < count; ++index)
|
||||
{
|
||||
const char* candidate = candidate_picker_candidate_at(script_input_index, index);
|
||||
if (candidate != nullptr && candidate[0] != '\0')
|
||||
{
|
||||
candidates.emplace_back(candidate);
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
std::string make_candidates_text(const std::vector<std::string>& candidates,
|
||||
int active_idx,
|
||||
int max_show = kCandidatesPerPage,
|
||||
@@ -236,6 +357,9 @@ bool touch_token_is_action(const char* token)
|
||||
return token != nullptr &&
|
||||
(std::strcmp(token, "Bksp") == 0 ||
|
||||
std::strcmp(token, "Enter") == 0 ||
|
||||
std::strcmp(token, "Prev") == 0 ||
|
||||
std::strcmp(token, "Next") == 0 ||
|
||||
std::strcmp(token, "Pick") == 0 ||
|
||||
std::strcmp(token, "Space") == 0);
|
||||
}
|
||||
|
||||
@@ -531,14 +655,22 @@ void ImeWidget::detach()
|
||||
|
||||
void ImeWidget::setMode(Mode mode)
|
||||
{
|
||||
const ScriptInputKind requested_kind = active_script_input_kind();
|
||||
const std::vector<ScriptInputDescriptor> script_inputs = available_script_inputs();
|
||||
if (script_input_index_ < 0 ||
|
||||
script_input_index_ >= static_cast<int>(script_inputs.size()))
|
||||
{
|
||||
script_input_index_ = 0;
|
||||
}
|
||||
const ScriptInputKind requested_kind =
|
||||
script_inputs.empty() ? ScriptInputKind::None
|
||||
: script_inputs[static_cast<std::size_t>(script_input_index_)].kind;
|
||||
if (script_mode(mode) && requested_kind == ScriptInputKind::None)
|
||||
{
|
||||
mode = Mode::EN;
|
||||
}
|
||||
|
||||
mode_ = mode;
|
||||
const bool use_pinyin = pinyin_mode();
|
||||
const bool use_pinyin = script_mode(mode_) && requested_kind == ScriptInputKind::Pinyin;
|
||||
ime_.setEnabled(use_pinyin);
|
||||
if (textarea_)
|
||||
{
|
||||
@@ -549,6 +681,10 @@ void ImeWidget::setMode(Mode mode)
|
||||
ime_.reset();
|
||||
lv_textarea_set_accepted_chars(textarea_, "");
|
||||
}
|
||||
else if (script_mode(mode_) && requested_kind == ScriptInputKind::CandidatePicker)
|
||||
{
|
||||
lv_textarea_set_accepted_chars(textarea_, "");
|
||||
}
|
||||
else
|
||||
{
|
||||
lv_textarea_set_accepted_chars(textarea_, nullptr);
|
||||
@@ -566,18 +702,26 @@ ImeWidget::Mode ImeWidget::mode() const
|
||||
|
||||
bool ImeWidget::pinyin_mode() const
|
||||
{
|
||||
return script_mode(mode_) && active_script_input_kind() == ScriptInputKind::Pinyin;
|
||||
return script_mode(mode_) &&
|
||||
selected_script_input_kind(script_input_index_) == ScriptInputKind::Pinyin;
|
||||
}
|
||||
|
||||
bool ImeWidget::direct_keyboard_mode() const
|
||||
{
|
||||
return script_mode(mode_) &&
|
||||
active_script_input_kind() == ScriptInputKind::RuCyrillicKeyboard;
|
||||
selected_script_input_kind(script_input_index_) == ScriptInputKind::RuCyrillicKeyboard;
|
||||
}
|
||||
|
||||
bool ImeWidget::candidate_picker_mode() const
|
||||
{
|
||||
return script_mode(mode_) &&
|
||||
selected_script_input_kind(script_input_index_) == ScriptInputKind::CandidatePicker;
|
||||
}
|
||||
|
||||
void ImeWidget::cycleMode()
|
||||
{
|
||||
if (active_script_input_kind() == ScriptInputKind::None)
|
||||
const std::vector<ScriptInputDescriptor> script_inputs = available_script_inputs();
|
||||
if (script_inputs.empty())
|
||||
{
|
||||
setMode(mode_ == Mode::EN ? Mode::NUM : Mode::EN);
|
||||
return;
|
||||
@@ -585,11 +729,20 @@ void ImeWidget::cycleMode()
|
||||
|
||||
if (mode_ == Mode::EN)
|
||||
{
|
||||
script_input_index_ = 0;
|
||||
setMode(Mode::SCRIPT);
|
||||
}
|
||||
else if (script_mode(mode_))
|
||||
{
|
||||
setMode(Mode::NUM);
|
||||
if ((script_input_index_ + 1) < static_cast<int>(script_inputs.size()))
|
||||
{
|
||||
++script_input_index_;
|
||||
setMode(Mode::SCRIPT);
|
||||
}
|
||||
else
|
||||
{
|
||||
setMode(Mode::NUM);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -599,6 +752,24 @@ void ImeWidget::cycleMode()
|
||||
|
||||
bool ImeWidget::commit_candidate(int candidate_index)
|
||||
{
|
||||
if (candidate_picker_mode())
|
||||
{
|
||||
if (candidate_index < 0 ||
|
||||
candidate_index >= candidate_picker_candidate_count(script_input_index_))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const char* candidate = candidate_picker_candidate_at(script_input_index_, candidate_index);
|
||||
if (candidate == nullptr || candidate[0] == '\0')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
committed_text_ += candidate;
|
||||
sync_textarea();
|
||||
refresh_labels();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string out;
|
||||
if (!ime_.commitCandidate(candidate_index, out))
|
||||
{
|
||||
@@ -719,6 +890,47 @@ bool ImeWidget::handle_key_code(uint32_t key)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (candidate_picker_mode())
|
||||
{
|
||||
const int count = candidate_picker_candidate_count(script_input_index_);
|
||||
if (key == LV_KEY_BACKSPACE)
|
||||
{
|
||||
erase_last_utf8_char(committed_text_);
|
||||
update_text = true;
|
||||
consumed = true;
|
||||
}
|
||||
else if (key == LV_KEY_UP || key == LV_KEY_LEFT)
|
||||
{
|
||||
if (candidate_picker_index_ > 0)
|
||||
{
|
||||
--candidate_picker_index_;
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
else if (key == LV_KEY_DOWN || key == LV_KEY_RIGHT)
|
||||
{
|
||||
if (candidate_picker_index_ + 1 < count)
|
||||
{
|
||||
++candidate_picker_index_;
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
else if (key == LV_KEY_ENTER || key == ' ')
|
||||
{
|
||||
if (candidate_picker_index_ < 0 || candidate_picker_index_ >= count)
|
||||
{
|
||||
candidate_picker_index_ = 0;
|
||||
}
|
||||
const char* candidate =
|
||||
candidate_picker_candidate_at(script_input_index_, candidate_picker_index_);
|
||||
if (candidate != nullptr && candidate[0] != '\0')
|
||||
{
|
||||
committed_text_ += candidate;
|
||||
update_text = true;
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (key == LV_KEY_BACKSPACE)
|
||||
@@ -760,7 +972,8 @@ bool ImeWidget::handle_key_code(uint32_t key)
|
||||
|
||||
bool ImeWidget::handle_text_token(const char* token)
|
||||
{
|
||||
if (!textarea_ || !token || token[0] == '\0' || !direct_keyboard_mode())
|
||||
if (!textarea_ || !token || token[0] == '\0' ||
|
||||
(!direct_keyboard_mode() && !candidate_picker_mode()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -835,6 +1048,12 @@ void ImeWidget::refresh_touch_keyboard()
|
||||
map = kTouchRuCyrillicMap;
|
||||
font = ::ui::fonts::content_font(kRuCyrillicFontProbe, ::ui::fonts::ui_chrome_font());
|
||||
}
|
||||
else if (candidate_picker_mode())
|
||||
{
|
||||
map = kTouchCandidatePickerMap;
|
||||
const char* first_candidate = candidate_picker_candidate_at(script_input_index_, 0);
|
||||
font = ::ui::fonts::content_font(first_candidate ? first_candidate : "", ::ui::fonts::ui_chrome_font());
|
||||
}
|
||||
lv_obj_set_style_text_font(keyboard_matrix_, font, LV_PART_ITEMS);
|
||||
lv_btnmatrix_set_map(keyboard_matrix_, map);
|
||||
}
|
||||
@@ -846,7 +1065,7 @@ void ImeWidget::refresh_touch_candidates()
|
||||
return;
|
||||
}
|
||||
|
||||
const bool show_candidates = pinyin_mode();
|
||||
const bool show_candidates = pinyin_mode() || candidate_picker_mode();
|
||||
if (!show_candidates)
|
||||
{
|
||||
lv_obj_add_flag(candidate_row_, LV_OBJ_FLAG_HIDDEN);
|
||||
@@ -854,9 +1073,20 @@ void ImeWidget::refresh_touch_candidates()
|
||||
}
|
||||
lv_obj_clear_flag(candidate_row_, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
const auto& candidates = ime_.candidates();
|
||||
const std::vector<std::string> picker_candidates =
|
||||
candidate_picker_mode() ? candidate_picker_candidates_vector(script_input_index_)
|
||||
: std::vector<std::string>{};
|
||||
const auto& candidates = candidate_picker_mode() ? picker_candidates : ime_.candidates();
|
||||
const int total = static_cast<int>(candidates.size());
|
||||
const int active = ime_.candidateIndex();
|
||||
int active = candidate_picker_mode() ? candidate_picker_index_ : ime_.candidateIndex();
|
||||
if (active < 0 || active >= total)
|
||||
{
|
||||
active = 0;
|
||||
if (candidate_picker_mode())
|
||||
{
|
||||
candidate_picker_index_ = 0;
|
||||
}
|
||||
}
|
||||
const int visible_count = static_cast<int>(candidate_btns_.size());
|
||||
|
||||
if (total <= 0)
|
||||
@@ -932,7 +1162,7 @@ void ImeWidget::refresh_labels()
|
||||
return;
|
||||
}
|
||||
|
||||
if (script_mode(mode_) && active_script_input_kind() == ScriptInputKind::None)
|
||||
if (script_mode(mode_) && selected_script_input_kind(script_input_index_) == ScriptInputKind::None)
|
||||
{
|
||||
mode_ = Mode::EN;
|
||||
ime_.setEnabled(false);
|
||||
@@ -940,7 +1170,7 @@ void ImeWidget::refresh_labels()
|
||||
|
||||
if (textarea_)
|
||||
{
|
||||
if (pinyin_mode() && ime_.hasBuffer())
|
||||
if ((pinyin_mode() && ime_.hasBuffer()) || candidate_picker_mode())
|
||||
{
|
||||
if (lv_group_t* g = lv_group_get_default())
|
||||
{
|
||||
@@ -992,7 +1222,7 @@ void ImeWidget::refresh_labels()
|
||||
return;
|
||||
}
|
||||
|
||||
const ScriptInputKind script_kind = active_script_input_kind();
|
||||
const ScriptInputKind script_kind = selected_script_input_kind(script_input_index_);
|
||||
if (script_kind == ScriptInputKind::RuCyrillicKeyboard)
|
||||
{
|
||||
lv_label_set_text(toggle_label_, "RU");
|
||||
@@ -1009,6 +1239,26 @@ void ImeWidget::refresh_labels()
|
||||
return;
|
||||
}
|
||||
|
||||
if (script_kind == ScriptInputKind::CandidatePicker)
|
||||
{
|
||||
lv_label_set_text(toggle_label_, "IM");
|
||||
#if UI_SHARED_TOUCH_IME_ENABLED
|
||||
if (touch_keyboard_enabled_)
|
||||
{
|
||||
const char* display_name = candidate_picker_display_name(script_input_index_);
|
||||
set_candidates_label_text(candidates_label_,
|
||||
display_name && display_name[0] != '\0'
|
||||
? display_name
|
||||
: ::ui::i18n::tr("Input picker"));
|
||||
refresh_touch_keyboard();
|
||||
refresh_touch_candidates();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
refresh_candidates();
|
||||
return;
|
||||
}
|
||||
|
||||
lv_label_set_text(toggle_label_, "CN");
|
||||
#if UI_SHARED_TOUCH_IME_ENABLED
|
||||
if (touch_keyboard_enabled_)
|
||||
@@ -1034,7 +1284,24 @@ void ImeWidget::refresh_candidates()
|
||||
}
|
||||
if (!pinyin_mode() || !ime_.hasBuffer())
|
||||
{
|
||||
set_candidates_label_text(candidates_label_, "");
|
||||
if (candidate_picker_mode())
|
||||
{
|
||||
if (candidate_picker_index_ < 0 ||
|
||||
candidate_picker_index_ >= candidate_picker_candidate_count(script_input_index_))
|
||||
{
|
||||
candidate_picker_index_ = 0;
|
||||
}
|
||||
std::string text =
|
||||
make_candidates_text(candidate_picker_candidates_vector(script_input_index_),
|
||||
candidate_picker_index_,
|
||||
kCompactCandidatesPerPage,
|
||||
" ");
|
||||
set_candidates_label_text(candidates_label_, text.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
set_candidates_label_text(candidates_label_, "");
|
||||
}
|
||||
return;
|
||||
}
|
||||
std::string text =
|
||||
@@ -1080,11 +1347,35 @@ void ImeWidget::on_touch_key_event(lv_event_t* e)
|
||||
}
|
||||
|
||||
const char* token = lv_btnmatrix_get_btn_text(self->keyboard_matrix_, button_id);
|
||||
if (self->direct_keyboard_mode() && !touch_token_is_action(token))
|
||||
if ((self->direct_keyboard_mode() || self->candidate_picker_mode()) &&
|
||||
!touch_token_is_action(token))
|
||||
{
|
||||
(void)self->handle_text_token(token);
|
||||
return;
|
||||
}
|
||||
if (self->candidate_picker_mode() && token != nullptr)
|
||||
{
|
||||
if (std::strcmp(token, "Prev") == 0)
|
||||
{
|
||||
self->handle_key_code(LV_KEY_LEFT);
|
||||
return;
|
||||
}
|
||||
if (std::strcmp(token, "Next") == 0)
|
||||
{
|
||||
self->handle_key_code(LV_KEY_RIGHT);
|
||||
return;
|
||||
}
|
||||
if (std::strcmp(token, "Pick") == 0)
|
||||
{
|
||||
self->handle_key_code(LV_KEY_ENTER);
|
||||
return;
|
||||
}
|
||||
if (std::strcmp(token, "Space") == 0)
|
||||
{
|
||||
(void)self->handle_text_token(" ");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t key = 0;
|
||||
if (!translate_touch_token(token, key))
|
||||
|
||||
Reference in new issue
Block a user