feat(candidate): support cross-page arrow navigation
- move down from the last candidate to the next page - move up from the first candidate to the previous page - preserve selection when expanding initial candidates - keep navigation bounded at the first and last candidates - add regression tests for cross-page selection
This commit is contained in:
1 parent
06173c1e25
commit
e41bee9a20
4 files changed
+79
-6
No files matched your search
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
@@ -132,18 +133,26 @@ struct CandidateUiState
|
||||
|
||||
bool move_selection(int offset)
|
||||
{
|
||||
if (page_size <= 0)
|
||||
{
|
||||
page_index = 0;
|
||||
selected_index_in_page = 0;
|
||||
return false;
|
||||
}
|
||||
const int count = current_page_count();
|
||||
if (count <= 0)
|
||||
{
|
||||
selected_index_in_page = 0;
|
||||
return false;
|
||||
}
|
||||
const int next = selected_index_in_page + offset;
|
||||
if (next < 0 || next >= count)
|
||||
selected_index_in_page = std::clamp(selected_index_in_page, 0, count - 1);
|
||||
const int next = current_page_start() + selected_index_in_page + offset;
|
||||
if (next < 0 || next >= item_total_count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
selected_index_in_page = next;
|
||||
page_index = next / page_size;
|
||||
selected_index_in_page = next % page_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2391,11 +2391,11 @@ void HandleImeKey(uint64_t client_id, uint64_t activation_epoch, uint64_t reques
|
||||
UINT result = Global::DataFromServerMsgType::NavigationIgnored;
|
||||
bool refresh = false;
|
||||
|
||||
const auto move_page = [&](int offset, UINT response_type) {
|
||||
result = response_type;
|
||||
if (offset > 0 && ui.page_index == 0 && g_inputSession->expand_initial_candidates())
|
||||
const auto expand_initial_candidates = [&] {
|
||||
if (g_inputSession->expand_initial_candidates())
|
||||
{
|
||||
const int current_page = ui.page_index;
|
||||
const int current_selection = ui.selected_index_in_page;
|
||||
auto expanded = g_inputSession->get_candidates();
|
||||
user_dictionary::apply_fixed_positions(
|
||||
user_dictionary::default_user_db_path(), CurrentRankingContextKey(), expanded, true,
|
||||
@@ -2404,6 +2404,16 @@ void HandleImeKey(uint64_t client_id, uint64_t activation_epoch, uint64_t reques
|
||||
});
|
||||
ui.set_items(std::move(expanded));
|
||||
ui.page_index = current_page;
|
||||
ui.selected_index_in_page = current_selection;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const auto move_page = [&](int offset, UINT response_type) {
|
||||
result = response_type;
|
||||
if (offset > 0 && ui.page_index == 0)
|
||||
{
|
||||
expand_initial_candidates();
|
||||
}
|
||||
if (offset < 0 ? ui.has_prev_page() : ui.has_next_page())
|
||||
{
|
||||
@@ -2413,6 +2423,10 @@ void HandleImeKey(uint64_t client_id, uint64_t activation_epoch, uint64_t reques
|
||||
};
|
||||
const auto move_selection = [&](int offset, UINT response_type) {
|
||||
result = response_type;
|
||||
if (offset > 0 && ui.selected_index_in_page + 1 >= ui.current_page_count())
|
||||
{
|
||||
expand_initial_candidates();
|
||||
}
|
||||
if (ui.move_selection(offset))
|
||||
{
|
||||
refresh = true;
|
||||
|
||||
@@ -14,6 +14,7 @@ set(TEST_SOURCE_FILES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_input_key_policy.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_candidate_ui_owner.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_candidate_ui_action_policy.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_candidate_ui_state.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_ipc_protocol_constants.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_floating_toolbar_visibility_policy.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/test_terminal_deactivation_policy.cpp"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#include "global/globals.h"
|
||||
#include "tests/includes/test_framework.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
Global::CandidateUiState MakeCandidateUiState(int item_count, int page_size)
|
||||
{
|
||||
Global::CandidateUiState state;
|
||||
state.item_total_count = item_count;
|
||||
state.page_size = page_size;
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(candidate_ui_selection_moves_from_page_end_to_next_page_start)
|
||||
{
|
||||
auto state = MakeCandidateUiState(8, 3);
|
||||
state.page_index = 0;
|
||||
state.selected_index_in_page = 2;
|
||||
|
||||
REQUIRE(state.move_selection(1));
|
||||
REQUIRE_EQ(state.page_index, 1);
|
||||
REQUIRE_EQ(state.selected_index_in_page, 0);
|
||||
}
|
||||
|
||||
TEST_CASE(candidate_ui_selection_moves_from_page_start_to_previous_page_end)
|
||||
{
|
||||
auto state = MakeCandidateUiState(8, 3);
|
||||
state.page_index = 1;
|
||||
state.selected_index_in_page = 0;
|
||||
|
||||
REQUIRE(state.move_selection(-1));
|
||||
REQUIRE_EQ(state.page_index, 0);
|
||||
REQUIRE_EQ(state.selected_index_in_page, 2);
|
||||
}
|
||||
|
||||
TEST_CASE(candidate_ui_selection_stops_at_first_and_last_candidate)
|
||||
{
|
||||
auto state = MakeCandidateUiState(8, 3);
|
||||
REQUIRE(!state.move_selection(-1));
|
||||
REQUIRE_EQ(state.page_index, 0);
|
||||
REQUIRE_EQ(state.selected_index_in_page, 0);
|
||||
|
||||
state.page_index = 2;
|
||||
state.selected_index_in_page = 1;
|
||||
REQUIRE(!state.move_selection(1));
|
||||
REQUIRE_EQ(state.page_index, 2);
|
||||
REQUIRE_EQ(state.selected_index_in_page, 1);
|
||||
}
|
||||
Reference in new issue
Block a user