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:
liu weikai committed 2026-06-17 10:47:41 +08:00
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))