From d79f6b36eac8851b9f694c9c26fdc2b2aef17584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=85=E6=88=8E=E6=B0=8F?= Date: Mon, 23 Jan 2023 21:42:27 +0800 Subject: [PATCH] feat(navigator,selector): vertical navigation refactor `KeyBindingProcessor`: make `KeyBindingProcessor::Handler` return bool; return false to fall back to the next processor; include mutiple `Keymap`s for defining key bindings for each `(Horizontal, Vertical) x (Stacked, Linear)` combination. closes #543 --- src/rime/gear/editor.cc | 76 ++++--- src/rime/gear/key_binding_processor.h | 33 ++- src/rime/gear/key_binding_processor_impl.h | 61 ++++-- src/rime/gear/navigator.cc | 70 ++++-- src/rime/gear/navigator.h | 7 +- src/rime/gear/selector.cc | 235 ++++++++++++--------- src/rime/gear/selector.h | 30 ++- 7 files changed, 312 insertions(+), 200 deletions(-) diff --git a/src/rime/gear/editor.cc b/src/rime/gear/editor.cc index ca0612b449..f37b6d19a3 100644 --- a/src/rime/gear/editor.cc +++ b/src/rime/gear/editor.cc @@ -88,49 +88,57 @@ void Editor::LoadConfig() { } } -void Editor::Confirm(Context* ctx) { +bool Editor::Confirm(Context* ctx) { ctx->ConfirmCurrentSelection() || ctx->Commit(); + return true; } -void Editor::ToggleSelection(Context* ctx) { +bool Editor::ToggleSelection(Context* ctx) { ctx->ReopenPreviousSegment() || ctx->ConfirmCurrentSelection(); + return true; } -void Editor::CommitComment(Context* ctx) { +bool Editor::CommitComment(Context* ctx) { if (auto cand = ctx->GetSelectedCandidate()) { if (!cand->comment().empty()) { engine_->sink()(cand->comment()); ctx->Clear(); } } + return true; } -void Editor::CommitScriptText(Context* ctx) { +bool Editor::CommitScriptText(Context* ctx) { engine_->sink()(ctx->GetScriptText()); ctx->Clear(); + return true; } -void Editor::CommitRawInput(Context* ctx) { +bool Editor::CommitRawInput(Context* ctx) { ctx->ClearNonConfirmedComposition(); ctx->Commit(); + return true; } -void Editor::CommitComposition(Context* ctx) { +bool Editor::CommitComposition(Context* ctx) { if (!ctx->ConfirmCurrentSelection() || !ctx->HasMenu()) ctx->Commit(); + return true; } -void Editor::RevertLastEdit(Context* ctx) { +bool Editor::RevertLastEdit(Context* ctx) { // different behavior in regard to previous operation type ctx->ReopenPreviousSelection() || (ctx->PopInput() && ctx->ReopenPreviousSegment()); + return true; } -void Editor::BackToPreviousInput(Context* ctx) { +bool Editor::BackToPreviousInput(Context* ctx) { ctx->ReopenPreviousSegment() || ctx->ReopenPreviousSelection() || ctx->PopInput(); + return true; } static bool pop_input_by_syllable(Context* ctx) { @@ -149,23 +157,27 @@ static bool pop_input_by_syllable(Context* ctx) { return false; } -void Editor::BackToPreviousSyllable(Context* ctx) { +bool Editor::BackToPreviousSyllable(Context* ctx) { ctx->ReopenPreviousSelection() || ((pop_input_by_syllable(ctx) || ctx->PopInput()) && ctx->ReopenPreviousSegment()); + return true; } -void Editor::DeleteCandidate(Context* ctx) { +bool Editor::DeleteCandidate(Context* ctx) { ctx->DeleteCurrentSelection(); + return true; } -void Editor::DeleteChar(Context* ctx) { +bool Editor::DeleteChar(Context* ctx) { ctx->DeleteInput(); + return true; } -void Editor::CancelComposition(Context* ctx) { +bool Editor::CancelComposition(Context* ctx) { if (!ctx->ClearPreviousSegment()) ctx->Clear(); + return true; } ProcessResult Editor::DirectCommit(Context* ctx, int ch) { @@ -180,30 +192,32 @@ ProcessResult Editor::AddToInput(Context* ctx, int ch) { } FluidEditor::FluidEditor(const Ticket& ticket) : Editor(ticket, false) { - Bind({XK_space, 0}, &Editor::Confirm); - Bind({XK_BackSpace, 0}, &Editor::BackToPreviousInput); // - Bind({XK_BackSpace, kControlMask}, &Editor::BackToPreviousSyllable); - Bind({XK_Return, 0}, &Editor::CommitComposition); // - Bind({XK_Return, kControlMask}, &Editor::CommitRawInput); // - Bind({XK_Return, kShiftMask}, &Editor::CommitScriptText); // - Bind({XK_Return, kControlMask | kShiftMask}, &Editor::CommitComment); - Bind({XK_Delete, 0}, &Editor::DeleteChar); - Bind({XK_Delete, kControlMask}, &Editor::DeleteCandidate); - Bind({XK_Escape, 0}, &Editor::CancelComposition); + auto& keymap = get_keymap(); + keymap.Bind({XK_space, 0}, &Editor::Confirm); + keymap.Bind({XK_BackSpace, 0}, &Editor::BackToPreviousInput); // + keymap.Bind({XK_BackSpace, kControlMask}, &Editor::BackToPreviousSyllable); + keymap.Bind({XK_Return, 0}, &Editor::CommitComposition); // + keymap.Bind({XK_Return, kControlMask}, &Editor::CommitRawInput); // + keymap.Bind({XK_Return, kShiftMask}, &Editor::CommitScriptText); // + keymap.Bind({XK_Return, kControlMask | kShiftMask}, &Editor::CommitComment); + keymap.Bind({XK_Delete, 0}, &Editor::DeleteChar); + keymap.Bind({XK_Delete, kControlMask}, &Editor::DeleteCandidate); + keymap.Bind({XK_Escape, 0}, &Editor::CancelComposition); char_handler_ = &Editor::AddToInput; // LoadConfig(); } ExpressEditor::ExpressEditor(const Ticket& ticket) : Editor(ticket, true) { - Bind({XK_space, 0}, &Editor::Confirm); - Bind({XK_BackSpace, 0}, &Editor::RevertLastEdit); // - Bind({XK_BackSpace, kControlMask}, &Editor::BackToPreviousSyllable); - Bind({XK_Return, 0}, &Editor::CommitRawInput); // - Bind({XK_Return, kControlMask}, &Editor::CommitScriptText); // - Bind({XK_Return, kControlMask | kShiftMask}, &Editor::CommitComment); - Bind({XK_Delete, 0}, &Editor::DeleteChar); - Bind({XK_Delete, kControlMask}, &Editor::DeleteCandidate); - Bind({XK_Escape, 0}, &Editor::CancelComposition); + auto& keymap = get_keymap(); + keymap.Bind({XK_space, 0}, &Editor::Confirm); + keymap.Bind({XK_BackSpace, 0}, &Editor::RevertLastEdit); // + keymap.Bind({XK_BackSpace, kControlMask}, &Editor::BackToPreviousSyllable); + keymap.Bind({XK_Return, 0}, &Editor::CommitRawInput); // + keymap.Bind({XK_Return, kControlMask}, &Editor::CommitScriptText); // + keymap.Bind({XK_Return, kControlMask | kShiftMask}, &Editor::CommitComment); + keymap.Bind({XK_Delete, 0}, &Editor::DeleteChar); + keymap.Bind({XK_Delete, kControlMask}, &Editor::DeleteCandidate); + keymap.Bind({XK_Escape, 0}, &Editor::CancelComposition); char_handler_ = &Editor::DirectCommit; // LoadConfig(); } diff --git a/src/rime/gear/key_binding_processor.h b/src/rime/gear/key_binding_processor.h index 1df8a71dc6..8dab13f649 100644 --- a/src/rime/gear/key_binding_processor.h +++ b/src/rime/gear/key_binding_processor.h @@ -13,11 +13,12 @@ namespace rime { -template +template class KeyBindingProcessor { public: - typedef void Handler(Context* ctx); - using HandlerPtr = void (T::*)(Context* ctx); + typedef bool Handler(Context* ctx); + using HandlerPtr = bool (T::*)(Context* ctx); + struct ActionDef { const char* name; HandlerPtr action; @@ -25,18 +26,28 @@ class KeyBindingProcessor { static const ActionDef kActionNoop; - KeyBindingProcessor(ActionDef* action_definitions) + explicit KeyBindingProcessor(ActionDef* action_definitions) : action_definitions_(action_definitions) {} - ProcessResult ProcessKeyEvent(const KeyEvent& key_event, Context* ctx); - bool Accept(const KeyEvent& key_event, Context* ctx); - void Bind(KeyEvent key_event, HandlerPtr action); - void LoadConfig(Config* config, const string& section); + + ProcessResult ProcessKeyEvent(const KeyEvent& key_event, + Context* ctx, + int keymap_selector = 0); + void LoadConfig(Config* config, + const string& section, + int kemap_selector = 0); + + protected: + struct Keymap : map { + void Bind(KeyEvent key_event, HandlerPtr action); + }; + + Keymap& get_keymap(int keymap_selector = 0); + + bool Accept(const KeyEvent& key_event, Context* ctx, Keymap& keymap); private: ActionDef* action_definitions_; - - using KeyBindingMap = map; - KeyBindingMap key_bindings_; + Keymap keymaps_[N]; }; } // namespace rime diff --git a/src/rime/gear/key_binding_processor_impl.h b/src/rime/gear/key_binding_processor_impl.h index e4b89471e6..79f7f07bef 100644 --- a/src/rime/gear/key_binding_processor_impl.h +++ b/src/rime/gear/key_binding_processor_impl.h @@ -5,15 +5,16 @@ namespace rime { -template -const typename KeyBindingProcessor::ActionDef - KeyBindingProcessor::kActionNoop = { "noop", nullptr }; +template +const typename KeyBindingProcessor::ActionDef + KeyBindingProcessor::kActionNoop = { "noop", nullptr }; -template -ProcessResult KeyBindingProcessor::ProcessKeyEvent( - const KeyEvent& key_event, Context* ctx) { +template +ProcessResult KeyBindingProcessor::ProcessKeyEvent( + const KeyEvent& key_event, Context* ctx, int keymap_selector) { + auto& keymap = get_keymap(keymap_selector); // exact match - if (Accept(key_event, ctx)) { + if (Accept(key_event, ctx, keymap)) { return kAccepted; } // fallback: compatible modifiers @@ -25,14 +26,14 @@ ProcessResult KeyBindingProcessor::ProcessKeyEvent( key_event.keycode(), (key_event.modifier() & ~kShiftMask) | kControlMask }; - if (Accept(shift_as_ctrl, ctx)) { + if (Accept(shift_as_ctrl, ctx, keymap)) { return kAccepted; } KeyEvent ignore_shift{ key_event.keycode(), key_event.modifier() & ~kShiftMask }; - if (Accept(ignore_shift, ctx)) { + if (Accept(ignore_shift, ctx, keymap)) { return kAccepted; } } @@ -40,30 +41,44 @@ ProcessResult KeyBindingProcessor::ProcessKeyEvent( return kNoop; } -template -bool KeyBindingProcessor::Accept(const KeyEvent& key_event, Context* ctx) { - auto binding = key_bindings_.find(key_event); - if (binding != key_bindings_.end()) { +template +typename KeyBindingProcessor::Keymap& +KeyBindingProcessor::get_keymap(int keymap_selector) { + DCHECK_LT(keymap_selector, N); + return keymaps_[keymap_selector]; +} + +template +bool KeyBindingProcessor::Accept(const KeyEvent& key_event, + Context* ctx, + Keymap& keymap) { + auto binding = keymap.find(key_event); + if (binding != keymap.end()) { auto action = binding->second; - RIME_THIS_CALL_AS(T, action)(ctx); - DLOG(INFO) << "action key accepted: " << key_event.repr(); - return true; + if (RIME_THIS_CALL_AS(T, action)(ctx)) { + DLOG(INFO) << "action key accepted: " << key_event.repr(); + return true; + } } return false; } -template -void KeyBindingProcessor::Bind(KeyEvent key_event, HandlerPtr action) { +template +void KeyBindingProcessor::Keymap::Bind(KeyEvent key_event, + HandlerPtr action) { if (action) { - key_bindings_[key_event] = action; + (*this)[key_event] = action; } else { - key_bindings_.erase(key_event); + this->erase(key_event); } } -template -void KeyBindingProcessor::LoadConfig(Config* config, const string& section) { +template +void KeyBindingProcessor::LoadConfig(Config* config, + const string& section, + int keymap_selector) { + auto& keymap = get_keymap(keymap_selector); if (auto bindings = config->GetMap(section + "/bindings")) { for (auto it = bindings->begin(); it != bindings->end(); ++it) { auto value = As(it->second); @@ -83,7 +98,7 @@ void KeyBindingProcessor::LoadConfig(Config* config, const string& section) { LOG(WARNING) << "[" << section << "] invalid key: " << it->first; continue; } - Bind(ke, p->action); + keymap.Bind(ke, p->action); } } } diff --git a/src/rime/gear/navigator.cc b/src/rime/gear/navigator.cc index 13d499b877..f2e3735223 100644 --- a/src/rime/gear/navigator.cc +++ b/src/rime/gear/navigator.cc @@ -29,21 +29,40 @@ static Navigator::ActionDef navigation_actions[] = { }; Navigator::Navigator(const Ticket& ticket) - : Processor(ticket), KeyBindingProcessor(navigation_actions) { - // Default key binding. - Bind({XK_Left, 0}, &Navigator::Rewind); - Bind({XK_Left, kControlMask}, &Navigator::LeftBySyllable); - Bind({XK_KP_Left, 0}, &Navigator::LeftByChar); - Bind({XK_Right, 0}, &Navigator::RightByChar); - Bind({XK_Right, kControlMask}, &Navigator::RightBySyllable); - Bind({XK_KP_Right, 0}, &Navigator::RightByChar); - Bind({XK_Home, 0}, &Navigator::Home); - Bind({XK_KP_Home, 0}, &Navigator::Home); - Bind({XK_End, 0}, &Navigator::End); - Bind({XK_KP_End, 0}, &Navigator::End); + : Processor(ticket), + KeyBindingProcessor(navigation_actions) +{ + // default key bindings + { + auto& keymap = get_keymap(Horizontal); + keymap.Bind({XK_Left, 0}, &Navigator::Rewind); + keymap.Bind({XK_Left, kControlMask}, &Navigator::LeftBySyllable); + keymap.Bind({XK_KP_Left, 0}, &Navigator::LeftByChar); + keymap.Bind({XK_Right, 0}, &Navigator::RightByChar); + keymap.Bind({XK_Right, kControlMask}, &Navigator::RightBySyllable); + keymap.Bind({XK_KP_Right, 0}, &Navigator::RightByChar); + keymap.Bind({XK_Home, 0}, &Navigator::Home); + keymap.Bind({XK_KP_Home, 0}, &Navigator::Home); + keymap.Bind({XK_End, 0}, &Navigator::End); + keymap.Bind({XK_KP_End, 0}, &Navigator::End); + } + { + auto& keymap = get_keymap(Vertical); + keymap.Bind({XK_Up, 0}, &Navigator::Rewind); + keymap.Bind({XK_Up, kControlMask}, &Navigator::LeftBySyllable); + keymap.Bind({XK_KP_Up, 0}, &Navigator::LeftByChar); + keymap.Bind({XK_Down, 0}, &Navigator::RightByChar); + keymap.Bind({XK_Down, kControlMask}, &Navigator::RightBySyllable); + keymap.Bind({XK_KP_Down, 0}, &Navigator::RightByChar); + keymap.Bind({XK_Home, 0}, &Navigator::Home); + keymap.Bind({XK_KP_Home, 0}, &Navigator::Home); + keymap.Bind({XK_End, 0}, &Navigator::End); + keymap.Bind({XK_KP_End, 0}, &Navigator::End); + } Config* config = engine_->schema()->config(); - KeyBindingProcessor::LoadConfig(config, "navigator"); + LoadConfig(config, "navigator", Horizontal); + LoadConfig(config, "navigator/vertical", Vertical); } ProcessResult Navigator::ProcessKeyEvent(const KeyEvent& key_event) { @@ -52,21 +71,25 @@ ProcessResult Navigator::ProcessKeyEvent(const KeyEvent& key_event) { Context* ctx = engine_->context(); if (!ctx->IsComposing()) return kNoop; - return KeyBindingProcessor::ProcessKeyEvent(key_event, ctx); + TextOrientation text_orientation = + ctx->get_option("_vertical") ? Vertical : Horizontal; + return KeyBindingProcessor::ProcessKeyEvent(key_event, ctx, text_orientation); } -void Navigator::LeftBySyllable(Context* ctx) { +bool Navigator::LeftBySyllable(Context* ctx) { BeginMove(ctx); size_t confirmed_pos = ctx->composition().GetConfirmedPosition(); JumpLeft(ctx, confirmed_pos) || GoToEnd(ctx); + return true; } -void Navigator::LeftByChar(Context* ctx) { +bool Navigator::LeftByChar(Context* ctx) { BeginMove(ctx); MoveLeft(ctx) || GoToEnd(ctx); + return true; } -void Navigator::Rewind(Context* ctx) { +bool Navigator::Rewind(Context* ctx) { BeginMove(ctx); // take a jump leftwards when there are multiple spans, // but not from the middle of a span. @@ -74,27 +97,32 @@ void Navigator::Rewind(Context* ctx) { spans_.Count() > 1 && spans_.HasVertex(ctx->caret_pos()) ? JumpLeft(ctx) : MoveLeft(ctx) ) || GoToEnd(ctx); + return true; } -void Navigator::RightBySyllable(Context* ctx) { +bool Navigator::RightBySyllable(Context* ctx) { BeginMove(ctx); size_t confirmed_pos = ctx->composition().GetConfirmedPosition(); JumpRight(ctx, confirmed_pos) || GoToEnd(ctx); + return true; } -void Navigator::RightByChar(Context* ctx) { +bool Navigator::RightByChar(Context* ctx) { BeginMove(ctx); MoveRight(ctx) || GoHome(ctx); + return true; } -void Navigator::Home(Context* ctx) { +bool Navigator::Home(Context* ctx) { BeginMove(ctx); GoHome(ctx); + return true; } -void Navigator::End(Context* ctx) { +bool Navigator::End(Context* ctx) { BeginMove(ctx); GoToEnd(ctx); + return true; } void Navigator::BeginMove(Context* ctx) { diff --git a/src/rime/gear/navigator.h b/src/rime/gear/navigator.h index f85194e196..53ea35cb48 100644 --- a/src/rime/gear/navigator.h +++ b/src/rime/gear/navigator.h @@ -15,8 +15,13 @@ namespace rime { -class Navigator : public Processor, public KeyBindingProcessor { +class Navigator : public Processor, public KeyBindingProcessor { public: + enum TextOrientation { + Horizontal = 0, + Vertical = 1, + }; + explicit Navigator(const Ticket& ticket); ProcessResult ProcessKeyEvent(const KeyEvent& key_event) override; diff --git a/src/rime/gear/selector.cc b/src/rime/gear/selector.cc index f45d37cdc4..4f1d5a0e04 100644 --- a/src/rime/gear/selector.cc +++ b/src/rime/gear/selector.cc @@ -16,15 +16,105 @@ namespace rime { -// Direction of candidate list. -using Direction = int; -constexpr Direction kDirectionVoid = -1; -constexpr Direction kDirectionDown = 0; -constexpr Direction kDirectionLeft = 1; -constexpr Direction kDirectionUp = 2; -constexpr Direction kDirectionRight = 3; - -Selector::Selector(const Ticket& ticket) : Processor(ticket) { +static Selector::ActionDef selector_actions[] = { + { "previous_candidate", &Selector::PreviousCandidate }, + { "next_candidate", &Selector::NextCandidate }, + { "previous_page", &Selector::PreviousPage }, + { "next_page", &Selector::NextPage }, + { "home", &Selector::Home }, + { "end", &Selector::End }, + Selector::kActionNoop, +}; + +Selector::Selector(const Ticket& ticket) + : Processor(ticket), + KeyBindingProcessor(selector_actions) +{ + // default key bindings + { + auto& keymap = get_keymap(Horizontal | Stacked); + keymap.Bind({XK_Up, 0}, &Selector::PreviousCandidate); + keymap.Bind({XK_KP_Up, 0}, &Selector::PreviousCandidate); + keymap.Bind({XK_Down, 0}, &Selector::NextCandidate); + keymap.Bind({XK_KP_Down, 0}, &Selector::NextCandidate); + keymap.Bind({XK_Prior, 0}, &Selector::PreviousPage); + keymap.Bind({XK_KP_Prior, 0}, &Selector::PreviousPage); + keymap.Bind({XK_Next, 0}, &Selector::NextPage); + keymap.Bind({XK_KP_Next, 0}, &Selector::NextPage); + keymap.Bind({XK_Home, 0}, &Selector::Home); + keymap.Bind({XK_KP_Home, 0}, &Selector::Home); + keymap.Bind({XK_End, 0}, &Selector::End); + keymap.Bind({XK_KP_End, 0}, &Selector::End); + } + { + auto& keymap = get_keymap(Horizontal | Linear); + keymap.Bind({XK_Left, 0}, &Selector::PreviousCandidate); + keymap.Bind({XK_KP_Left, 0}, &Selector::PreviousCandidate); + keymap.Bind({XK_Right, 0}, &Selector::NextCandidate); + keymap.Bind({XK_KP_Right, 0}, &Selector::NextCandidate); + keymap.Bind({XK_Up, 0}, &Selector::PreviousPage); + keymap.Bind({XK_KP_Up, 0}, &Selector::PreviousPage); + keymap.Bind({XK_Down, 0}, &Selector::NextPage); + keymap.Bind({XK_KP_Down, 0}, &Selector::NextPage); + keymap.Bind({XK_Prior, 0}, &Selector::PreviousPage); + keymap.Bind({XK_KP_Prior, 0}, &Selector::PreviousPage); + keymap.Bind({XK_Next, 0}, &Selector::NextPage); + keymap.Bind({XK_KP_Next, 0}, &Selector::NextPage); + keymap.Bind({XK_Home, 0}, &Selector::Home); + keymap.Bind({XK_KP_Home, 0}, &Selector::Home); + keymap.Bind({XK_End, 0}, &Selector::End); + keymap.Bind({XK_KP_End, 0}, &Selector::End); + } + { + auto& keymap = get_keymap(Vertical | Stacked); + keymap.Bind({XK_Right, 0}, &Selector::PreviousCandidate); + keymap.Bind({XK_KP_Right, 0}, &Selector::PreviousCandidate); + keymap.Bind({XK_Left, 0}, &Selector::NextCandidate); + keymap.Bind({XK_KP_Left, 0}, &Selector::NextCandidate); + keymap.Bind({XK_Prior, 0}, &Selector::PreviousPage); + keymap.Bind({XK_KP_Prior, 0}, &Selector::PreviousPage); + keymap.Bind({XK_Next, 0}, &Selector::NextPage); + keymap.Bind({XK_KP_Next, 0}, &Selector::NextPage); + keymap.Bind({XK_Home, 0}, &Selector::Home); + keymap.Bind({XK_KP_Home, 0}, &Selector::Home); + keymap.Bind({XK_End, 0}, &Selector::End); + keymap.Bind({XK_KP_End, 0}, &Selector::End); + } + { + auto& keymap = get_keymap(Vertical | Linear); + keymap.Bind({XK_Up, 0}, &Selector::PreviousCandidate); + keymap.Bind({XK_KP_Up, 0}, &Selector::PreviousCandidate); + keymap.Bind({XK_Down, 0}, &Selector::NextCandidate); + keymap.Bind({XK_KP_Down, 0}, &Selector::NextCandidate); + keymap.Bind({XK_Right, 0}, &Selector::PreviousPage); + keymap.Bind({XK_KP_Right, 0}, &Selector::PreviousPage); + keymap.Bind({XK_Left, 0}, &Selector::NextPage); + keymap.Bind({XK_KP_Left, 0}, &Selector::NextPage); + keymap.Bind({XK_Prior, 0}, &Selector::PreviousPage); + keymap.Bind({XK_KP_Prior, 0}, &Selector::PreviousPage); + keymap.Bind({XK_Next, 0}, &Selector::NextPage); + keymap.Bind({XK_KP_Next, 0}, &Selector::NextPage); + keymap.Bind({XK_Home, 0}, &Selector::Home); + keymap.Bind({XK_KP_Home, 0}, &Selector::Home); + keymap.Bind({XK_End, 0}, &Selector::End); + keymap.Bind({XK_KP_End, 0}, &Selector::End); + } + + Config* config = engine_->schema()->config(); + LoadConfig(config, "selector", Horizontal | Stacked); + LoadConfig(config, "selector/linear", Horizontal | Linear); + LoadConfig(config, "selector/vertical", Vertical | Stacked); + LoadConfig(config, "selector/vertical/linear", Vertical | Linear); +} + +inline static bool is_vertical_text(Context* ctx) { + return ctx->get_option("_vertical"); +} + +inline static bool is_linear_layout(Context* ctx) { + return ctx->get_option("_linear") || + // Deprecated. equivalent to {_linear: true, _vertical: false} + ctx->get_option("_horizontal"); } ProcessResult Selector::ProcessKeyEvent(const KeyEvent& key_event) { @@ -36,94 +126,18 @@ ProcessResult Selector::ProcessKeyEvent(const KeyEvent& key_event) { Segment& current_segment(ctx->composition().back()); if (!current_segment.menu || current_segment.HasTag("raw")) return kNoop; - bool is_linear_candidate_list = ctx->get_option("_linear"); - bool is_vertical_text = ctx->get_option("_vertical"); - // Deprecated. equivalent to {_linear: true, _vertical: false} - bool is_horizontal_layout = ctx->get_option("_horizontal"); - Direction next_candidate = kDirectionDown; - Direction next_page = kDirectionDown; - if (is_vertical_text) { - // +90 degrees - next_candidate = (next_candidate + 1) % 4; - next_page = (next_page + 1) % 4; - } - if (is_linear_candidate_list || is_horizontal_layout) { - // -90 degrees - next_candidate = (next_candidate + 3) % 4; - } - if (next_page == next_candidate) { - // this is no-op. just to clarify that arrow keys don't change page. - next_page = kDirectionVoid; + + TextOrientation text_orientation = + is_vertical_text(ctx) ? Vertical : Horizontal; + CandidateListLayout candidate_list_layout = + is_linear_layout(ctx) ? Linear : Stacked; + auto result = KeyBindingProcessor::ProcessKeyEvent( + key_event, ctx, text_orientation | candidate_list_layout); + if (result != kNoop) { + return result; } + int ch = key_event.keycode(); - if (ch == XK_Prior || ch == XK_KP_Prior) { - PageUp(ctx); - return kAccepted; - } - if (ch == XK_Next || ch == XK_KP_Next) { - PageDown(ctx); - return kAccepted; - } - if (ch == XK_Up || ch == XK_KP_Up) { - if (next_candidate == kDirectionDown) { - CursorUp(ctx); - } else if (next_page == kDirectionDown) { - PageUp(ctx); - } - return kAccepted; - } - if (ch == XK_Down || ch == XK_KP_Down) { - if (next_candidate == kDirectionDown) { - CursorDown(ctx); - } else if (next_page == kDirectionDown) { - PageDown(ctx); - } - return kAccepted; - } - if (ch == XK_Left || ch == XK_KP_Left) { - if (!key_event.ctrl() && - !key_event.shift() && - ctx->caret_pos() == ctx->input().length()) { - if (next_candidate == kDirectionRight && - CursorUp(ctx)) { - return kAccepted; - } - if (next_candidate == kDirectionLeft) { - CursorDown(ctx); - return kAccepted; - } - if (next_page == kDirectionLeft) { - PageDown(ctx); - return kAccepted; - } - } - return kNoop; - } - if (ch == XK_Right || ch == XK_KP_Right) { - if (!key_event.ctrl() && - !key_event.shift() && - ctx->caret_pos() == ctx->input().length()) { - if (next_candidate == kDirectionRight) { - CursorDown(ctx); - return kAccepted; - } - if (next_candidate == kDirectionLeft) { - CursorUp(ctx); - return kAccepted; - } - if (next_page == kDirectionLeft) { - PageUp(ctx); - return kAccepted; - } - } - return kNoop; - } - if (ch == XK_Home || ch == XK_KP_Home) { - return Home(ctx) ? kAccepted : kNoop; - } - if (ch == XK_End || ch == XK_KP_End) { - return End(ctx) ? kAccepted : kNoop; - } int index = -1; const string& select_keys(engine_->schema()->select_keys()); if (!select_keys.empty() && @@ -146,7 +160,7 @@ ProcessResult Selector::ProcessKeyEvent(const KeyEvent& key_event) { return kNoop; } -bool Selector::PageUp(Context* ctx) { +bool Selector::PreviousPage(Context* ctx) { Composition& comp = ctx->composition(); if (comp.empty()) return false; @@ -158,7 +172,7 @@ bool Selector::PageUp(Context* ctx) { return true; } -bool Selector::PageDown(Context* ctx) { +bool Selector::NextPage(Context* ctx) { Composition& comp = ctx->composition(); if (comp.empty() || !comp.back().menu) return false; @@ -171,7 +185,8 @@ bool Selector::PageDown(Context* ctx) { if (page_down_cycle) {// Cycle back to page 1 if true index = 0; } else { - return false; + // no-op; consume the key event so that page down is not sent to the app. + return true; } } else if (index >= candidate_count) { index = candidate_count - 1; @@ -179,29 +194,42 @@ bool Selector::PageDown(Context* ctx) { comp.back().selected_index = index; comp.back().tags.insert("paging"); return true; +} +inline static bool caret_at_end_of_input(Context* ctx) { + return ctx->caret_pos() >= ctx->input().length(); } -bool Selector::CursorUp(Context* ctx) { +bool Selector::PreviousCandidate(Context* ctx) { + if (is_linear_layout(ctx) && !caret_at_end_of_input(ctx)) { + // let navigator handle the arrow key. + return false; + } Composition& comp = ctx->composition(); if (comp.empty()) return false; int index = comp.back().selected_index; - if (index <= 0) - return false; + if (index <= 0) { + // in case of linear layout, fall back to navigator + return !is_linear_layout(ctx); + } comp.back().selected_index = index - 1; comp.back().tags.insert("paging"); return true; } -bool Selector::CursorDown(Context* ctx) { +bool Selector::NextCandidate(Context* ctx) { + if (is_linear_layout(ctx) && !caret_at_end_of_input(ctx)) { + // let navigator handle the arrow key. + return false; + } Composition& comp = ctx->composition(); if (comp.empty() || !comp.back().menu) return false; int index = comp.back().selected_index + 1; int candidate_count = comp.back().menu->Prepare(index + 1); if (candidate_count <= index) - return false; + return true; comp.back().selected_index = index; comp.back().tags.insert("paging"); return true; @@ -215,6 +243,7 @@ bool Selector::Home(Context* ctx) { seg.selected_index = 0; return true; } + // let navigator handle the key event. return false; } diff --git a/src/rime/gear/selector.h b/src/rime/gear/selector.h index e9335acfbf..5e4bfce00c 100644 --- a/src/rime/gear/selector.h +++ b/src/rime/gear/selector.h @@ -10,22 +10,32 @@ #include #include #include +#include namespace rime { -class Selector : public Processor { +class Selector : public Processor, public KeyBindingProcessor { public: - Selector(const Ticket& ticket); + enum TextOrientation { + Horizontal = 0, + Vertical = 1, + }; + enum CandidateListLayout { + Stacked = 0, + Linear = 2, + }; - virtual ProcessResult ProcessKeyEvent(const KeyEvent& key_event); + explicit Selector(const Ticket& ticket); + + ProcessResult ProcessKeyEvent(const KeyEvent& key_event) override; + + Handler PreviousCandidate; + Handler NextCandidate; + Handler PreviousPage; + Handler NextPage; + Handler Home; + Handler End; - protected: - bool PageUp(Context* ctx); - bool PageDown(Context* ctx); - bool CursorUp(Context* ctx); - bool CursorDown(Context* ctx); - bool Home(Context* ctx); - bool End(Context* ctx); bool SelectCandidateAt(Context* ctx, int index); };