Route canvas hotkeys through app core
This commit is contained in:
@@ -227,6 +227,7 @@ add_library(pp_app_core STATIC
|
||||
src/app_core/app_preferences.h
|
||||
src/app_core/app_status.h
|
||||
src/app_core/brush_ui.h
|
||||
src/app_core/canvas_hotkey.h
|
||||
src/app_core/canvas_tool_ui.h
|
||||
src/app_core/document_animation.h
|
||||
src/app_core/document_canvas.h
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -535,8 +535,12 @@ before legacy toolbar selection, `Canvas` mode, pen picking, touch-lock, and
|
||||
transform state adapters continue. `pano_cli plan-canvas-tool-state` exposes
|
||||
the matching toolbar active-state refresh used by `App::update` before legacy
|
||||
`Canvas` mode state remains the source of truth. `NodeCanvas` stylus eraser
|
||||
and `E` key draw/erase mode switching also consume the same app-core executor
|
||||
before legacy canvas mode execution continues.
|
||||
mode switching consumes the same app-core executor before legacy canvas mode
|
||||
execution continues. `NodeCanvas` keyboard and touch command handling now
|
||||
consumes `pp_app_core` canvas-hotkey planning for E draw/erase, Ctrl+Z,
|
||||
Ctrl+Shift+Z, Ctrl+S, Ctrl+Shift+S, Tab UI toggle, brush-size brackets,
|
||||
Android back, Alt cursor reveal, and two-finger undo before legacy UI/canvas
|
||||
adapters execute the command.
|
||||
`pano_cli plan-canvas-clear` exposes app-core planning for the main toolbar
|
||||
clear-current-layer command, including clear color validation, no-canvas
|
||||
handling, undo recording intent, and dirty-state intent; live toolbar execution
|
||||
@@ -1333,6 +1337,17 @@ Results:
|
||||
touch-lock toggling, plus toolbar active-state derivation for draw, copy, and
|
||||
bucket modes, service dispatch ordering, pick no-op execution, and malformed
|
||||
execution payload rejection.
|
||||
- `pp_app_core_canvas_hotkey_tests` passed, covering E draw/erase toggles,
|
||||
Ctrl+Z/Ctrl+Shift+Z history planning, Ctrl+S/Ctrl+Shift+S document save
|
||||
intents, Tab UI toggles, brush-size brackets, Android back and two-finger
|
||||
undo, no-op Ctrl-less Z, bad-count rejection, executor dispatch, and
|
||||
malformed brush-size execution rejection.
|
||||
- `pano_cli_plan_canvas_hotkey_ctrl_z_smoke`,
|
||||
`pano_cli_plan_canvas_hotkey_save_dirty_version_smoke`,
|
||||
`pano_cli_plan_canvas_hotkey_erase_smoke`,
|
||||
`pano_cli_plan_canvas_hotkey_two_finger_undo_smoke`, and
|
||||
`pano_cli_plan_canvas_hotkey_rejects_bad_count` passed and expose live
|
||||
canvas keyboard/touch command planning as JSON automation.
|
||||
- `pano_cli_plan_canvas_tool_draw_smoke`,
|
||||
`pano_cli_plan_canvas_tool_copy_smoke`,
|
||||
`pano_cli_plan_canvas_tool_pick_noop_smoke`,
|
||||
|
||||
225
src/app_core/canvas_hotkey.h
Normal file
225
src/app_core/canvas_hotkey.h
Normal file
@@ -0,0 +1,225 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_core/canvas_tool_ui.h"
|
||||
#include "app_core/document_session.h"
|
||||
#include "app_core/history_ui.h"
|
||||
#include "foundation/result.h"
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class CanvasHotkeyEvent {
|
||||
key_down,
|
||||
key_up,
|
||||
touch_tap,
|
||||
};
|
||||
|
||||
enum class CanvasHotkeyKey {
|
||||
other,
|
||||
android_back,
|
||||
alt,
|
||||
e,
|
||||
s,
|
||||
tab,
|
||||
z,
|
||||
bracket_left,
|
||||
bracket_right,
|
||||
};
|
||||
|
||||
enum class CanvasHotkeyAction {
|
||||
none,
|
||||
select_tool,
|
||||
history,
|
||||
save_document,
|
||||
toggle_ui,
|
||||
adjust_brush_size,
|
||||
show_cursor,
|
||||
};
|
||||
|
||||
struct CanvasHotkeyState {
|
||||
bool ctrl_down = false;
|
||||
bool shift_down = false;
|
||||
bool mouse_focused = false;
|
||||
int undo_count = 0;
|
||||
int redo_count = 0;
|
||||
int touch_finger_count = 0;
|
||||
};
|
||||
|
||||
struct CanvasHotkeyPlan {
|
||||
CanvasHotkeyAction action = CanvasHotkeyAction::none;
|
||||
CanvasHotkeyEvent event = CanvasHotkeyEvent::key_up;
|
||||
CanvasHotkeyKey key = CanvasHotkeyKey::other;
|
||||
CanvasToolPlan tool;
|
||||
HistoryUiPlan history;
|
||||
DocumentSaveIntent save_intent = DocumentSaveIntent::save;
|
||||
float brush_size_delta = 0.0F;
|
||||
bool no_op = true;
|
||||
};
|
||||
|
||||
class CanvasHotkeyServices {
|
||||
public:
|
||||
virtual ~CanvasHotkeyServices() = default;
|
||||
|
||||
virtual pp::foundation::Status execute_tool(const CanvasToolPlan& plan) = 0;
|
||||
virtual pp::foundation::Status execute_history(const HistoryUiPlan& plan) = 0;
|
||||
virtual void save_document(DocumentSaveIntent intent) = 0;
|
||||
virtual void toggle_ui() = 0;
|
||||
virtual void adjust_brush_size(float delta) = 0;
|
||||
virtual void show_cursor() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_canvas_hotkey_state(
|
||||
const CanvasHotkeyState& state) noexcept
|
||||
{
|
||||
if (state.undo_count < 0) {
|
||||
return pp::foundation::Status::out_of_range("undo action count must not be negative");
|
||||
}
|
||||
if (state.redo_count < 0) {
|
||||
return pp::foundation::Status::out_of_range("redo action count must not be negative");
|
||||
}
|
||||
if (state.touch_finger_count < 0) {
|
||||
return pp::foundation::Status::out_of_range("touch finger count must not be negative");
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<CanvasHotkeyPlan> plan_canvas_hotkey(
|
||||
CanvasHotkeyEvent event,
|
||||
CanvasHotkeyKey key,
|
||||
const CanvasHotkeyState& state)
|
||||
{
|
||||
const auto state_status = validate_canvas_hotkey_state(state);
|
||||
if (!state_status.ok()) {
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::failure(state_status);
|
||||
}
|
||||
|
||||
CanvasHotkeyPlan plan;
|
||||
plan.event = event;
|
||||
plan.key = key;
|
||||
|
||||
if (event == CanvasHotkeyEvent::touch_tap) {
|
||||
if (state.touch_finger_count == 2) {
|
||||
auto history = plan_history_undo(state.undo_count);
|
||||
if (!history) {
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::failure(history.status());
|
||||
}
|
||||
plan.action = CanvasHotkeyAction::history;
|
||||
plan.history = history.value();
|
||||
plan.no_op = plan.history.no_op;
|
||||
}
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::success(plan);
|
||||
}
|
||||
|
||||
if (event == CanvasHotkeyEvent::key_down) {
|
||||
switch (key) {
|
||||
case CanvasHotkeyKey::e:
|
||||
plan.action = CanvasHotkeyAction::select_tool;
|
||||
plan.tool = plan_canvas_tool_select(CanvasToolMode::erase);
|
||||
plan.no_op = false;
|
||||
break;
|
||||
case CanvasHotkeyKey::android_back: {
|
||||
auto history = plan_history_undo(state.undo_count);
|
||||
if (!history) {
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::failure(history.status());
|
||||
}
|
||||
plan.action = CanvasHotkeyAction::history;
|
||||
plan.history = history.value();
|
||||
plan.no_op = plan.history.no_op;
|
||||
break;
|
||||
}
|
||||
case CanvasHotkeyKey::alt:
|
||||
if (state.mouse_focused) {
|
||||
plan.action = CanvasHotkeyAction::show_cursor;
|
||||
plan.no_op = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::success(plan);
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case CanvasHotkeyKey::e:
|
||||
plan.action = CanvasHotkeyAction::select_tool;
|
||||
plan.tool = plan_canvas_tool_select(CanvasToolMode::draw);
|
||||
plan.no_op = false;
|
||||
break;
|
||||
case CanvasHotkeyKey::tab:
|
||||
plan.action = CanvasHotkeyAction::toggle_ui;
|
||||
plan.no_op = false;
|
||||
break;
|
||||
case CanvasHotkeyKey::z:
|
||||
if (state.ctrl_down) {
|
||||
auto history = state.shift_down
|
||||
? plan_history_redo(state.redo_count)
|
||||
: plan_history_undo(state.undo_count);
|
||||
if (!history) {
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::failure(history.status());
|
||||
}
|
||||
plan.action = CanvasHotkeyAction::history;
|
||||
plan.history = history.value();
|
||||
plan.no_op = plan.history.no_op;
|
||||
}
|
||||
break;
|
||||
case CanvasHotkeyKey::s:
|
||||
if (state.ctrl_down) {
|
||||
plan.action = CanvasHotkeyAction::save_document;
|
||||
plan.save_intent = state.shift_down
|
||||
? DocumentSaveIntent::save_dirty_version
|
||||
: DocumentSaveIntent::save;
|
||||
plan.no_op = false;
|
||||
}
|
||||
break;
|
||||
case CanvasHotkeyKey::bracket_left:
|
||||
plan.action = CanvasHotkeyAction::adjust_brush_size;
|
||||
plan.brush_size_delta = -0.05F;
|
||||
plan.no_op = false;
|
||||
break;
|
||||
case CanvasHotkeyKey::bracket_right:
|
||||
plan.action = CanvasHotkeyAction::adjust_brush_size;
|
||||
plan.brush_size_delta = 0.05F;
|
||||
plan.no_op = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_canvas_hotkey_plan(
|
||||
const CanvasHotkeyPlan& plan,
|
||||
CanvasHotkeyServices& services)
|
||||
{
|
||||
if (plan.no_op || plan.action == CanvasHotkeyAction::none) {
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
switch (plan.action) {
|
||||
case CanvasHotkeyAction::select_tool:
|
||||
return services.execute_tool(plan.tool);
|
||||
case CanvasHotkeyAction::history:
|
||||
return services.execute_history(plan.history);
|
||||
case CanvasHotkeyAction::save_document:
|
||||
services.save_document(plan.save_intent);
|
||||
return pp::foundation::Status::success();
|
||||
case CanvasHotkeyAction::toggle_ui:
|
||||
services.toggle_ui();
|
||||
return pp::foundation::Status::success();
|
||||
case CanvasHotkeyAction::adjust_brush_size:
|
||||
if (plan.brush_size_delta == 0.0F) {
|
||||
return pp::foundation::Status::invalid_argument("brush-size hotkey plan must include a delta");
|
||||
}
|
||||
services.adjust_brush_size(plan.brush_size_delta);
|
||||
return pp::foundation::Status::success();
|
||||
case CanvasHotkeyAction::show_cursor:
|
||||
services.show_cursor();
|
||||
return pp::foundation::Status::success();
|
||||
case CanvasHotkeyAction::none:
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown canvas hotkey action");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "app_core/canvas_hotkey.h"
|
||||
#include "app_core/canvas_tool_ui.h"
|
||||
#include "app_core/history_ui.h"
|
||||
#include "app.h"
|
||||
@@ -69,20 +70,6 @@ pp::paint_renderer::CanvasBlendGatePlan node_canvas_blend_gate_plan(
|
||||
return fallback;
|
||||
}
|
||||
|
||||
void run_history_undo_if_available()
|
||||
{
|
||||
const auto plan = pp::app::plan_history_undo(static_cast<int>(ActionManager::I.m_actions.size()));
|
||||
if (plan && plan.value().invokes_undo)
|
||||
ActionManager::undo();
|
||||
}
|
||||
|
||||
void run_history_redo_if_available()
|
||||
{
|
||||
const auto plan = pp::app::plan_history_redo(static_cast<int>(ActionManager::I.m_redos.size()));
|
||||
if (plan && plan.value().invokes_redo)
|
||||
ActionManager::redo();
|
||||
}
|
||||
|
||||
class LegacyNodeCanvasToolServices final : public pp::app::CanvasToolServices {
|
||||
public:
|
||||
void select_toolbar_button(pp::app::CanvasToolMode) override
|
||||
@@ -116,6 +103,124 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class LegacyNodeCanvasHistoryServices final : public pp::app::HistoryUiServices {
|
||||
public:
|
||||
void invoke_undo() override
|
||||
{
|
||||
ActionManager::undo();
|
||||
}
|
||||
|
||||
void invoke_redo() override
|
||||
{
|
||||
ActionManager::redo();
|
||||
}
|
||||
|
||||
void clear_history() override
|
||||
{
|
||||
ActionManager::clear();
|
||||
}
|
||||
};
|
||||
|
||||
class LegacyNodeCanvasHotkeyServices final : public pp::app::CanvasHotkeyServices {
|
||||
public:
|
||||
pp::foundation::Status execute_tool(const pp::app::CanvasToolPlan& plan) override
|
||||
{
|
||||
LegacyNodeCanvasToolServices services;
|
||||
return pp::app::execute_canvas_tool_plan(plan, services);
|
||||
}
|
||||
|
||||
pp::foundation::Status execute_history(const pp::app::HistoryUiPlan& plan) override
|
||||
{
|
||||
LegacyNodeCanvasHistoryServices services;
|
||||
return pp::app::execute_history_ui_plan(plan, services);
|
||||
}
|
||||
|
||||
void save_document(pp::app::DocumentSaveIntent intent) override
|
||||
{
|
||||
App::I->save_document(intent);
|
||||
}
|
||||
|
||||
void toggle_ui() override
|
||||
{
|
||||
App::I->toggle_ui();
|
||||
}
|
||||
|
||||
void adjust_brush_size(float delta) override
|
||||
{
|
||||
if (!App::I || !App::I->stroke || !App::I->stroke->m_tip_size)
|
||||
return;
|
||||
|
||||
const float value = App::I->stroke->m_tip_size->get_value();
|
||||
const float next_value = glm::clamp<float>(value + delta, 0.0F, 1.0F);
|
||||
App::I->stroke->set_size(next_value, true, true);
|
||||
}
|
||||
|
||||
void show_cursor() override
|
||||
{
|
||||
App::I->show_cursor();
|
||||
}
|
||||
};
|
||||
|
||||
pp::app::CanvasHotkeyKey canvas_hotkey_key(kKey key) noexcept
|
||||
{
|
||||
switch (key) {
|
||||
case kKey::AndroidBack:
|
||||
return pp::app::CanvasHotkeyKey::android_back;
|
||||
case kKey::KeyAlt:
|
||||
return pp::app::CanvasHotkeyKey::alt;
|
||||
case kKey::KeyE:
|
||||
return pp::app::CanvasHotkeyKey::e;
|
||||
case kKey::KeyS:
|
||||
return pp::app::CanvasHotkeyKey::s;
|
||||
case kKey::KeyTab:
|
||||
return pp::app::CanvasHotkeyKey::tab;
|
||||
case kKey::KeyZ:
|
||||
return pp::app::CanvasHotkeyKey::z;
|
||||
case kKey::KeyBracketLeft:
|
||||
return pp::app::CanvasHotkeyKey::bracket_left;
|
||||
case kKey::KeyBracketRight:
|
||||
return pp::app::CanvasHotkeyKey::bracket_right;
|
||||
default:
|
||||
return pp::app::CanvasHotkeyKey::other;
|
||||
}
|
||||
}
|
||||
|
||||
pp::app::CanvasHotkeyState canvas_hotkey_state(bool mouse_focused, int touch_finger_count = 0) noexcept
|
||||
{
|
||||
pp::app::CanvasHotkeyState state;
|
||||
state.ctrl_down = App::I && App::I->keys[(int)kKey::KeyCtrl];
|
||||
state.shift_down = App::I && App::I->keys[(int)kKey::KeyShift];
|
||||
state.mouse_focused = mouse_focused;
|
||||
state.undo_count = static_cast<int>(ActionManager::I.m_actions.size());
|
||||
state.redo_count = static_cast<int>(ActionManager::I.m_redos.size());
|
||||
state.touch_finger_count = touch_finger_count;
|
||||
return state;
|
||||
}
|
||||
|
||||
void execute_canvas_hotkey_plan(const pp::app::CanvasHotkeyPlan& plan)
|
||||
{
|
||||
LegacyNodeCanvasHotkeyServices services;
|
||||
const auto status = pp::app::execute_canvas_hotkey_plan(plan, services);
|
||||
if (!status.ok())
|
||||
LOG("Canvas hotkey action failed: %s", status.message);
|
||||
}
|
||||
|
||||
void run_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent event,
|
||||
kKey key,
|
||||
bool mouse_focused,
|
||||
int touch_finger_count = 0)
|
||||
{
|
||||
const auto plan = pp::app::plan_canvas_hotkey(
|
||||
event,
|
||||
canvas_hotkey_key(key),
|
||||
canvas_hotkey_state(mouse_focused, touch_finger_count));
|
||||
if (plan)
|
||||
execute_canvas_hotkey_plan(plan.value());
|
||||
else
|
||||
LOG("Canvas hotkey planning failed: %s", plan.status().message);
|
||||
}
|
||||
|
||||
void run_canvas_tool_mode(pp::app::CanvasToolMode mode)
|
||||
{
|
||||
const auto plan = pp::app::plan_canvas_tool_select(mode);
|
||||
@@ -700,43 +805,19 @@ kEventResult NodeCanvas::handle_event(Event* e)
|
||||
update_cursor();
|
||||
break;
|
||||
case kEventType::KeyDown:
|
||||
if (ke->m_key == kKey::KeyE)
|
||||
run_canvas_tool_mode(pp::app::CanvasToolMode::erase);
|
||||
if (ke->m_key == kKey::AndroidBack)
|
||||
run_history_undo_if_available();
|
||||
if (ke->m_key == kKey::KeyAlt && m_mouse_focus)
|
||||
App::I->show_cursor();
|
||||
run_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_down,
|
||||
ke->m_key,
|
||||
m_mouse_focus);
|
||||
for (auto& mode : *m_canvas->m_mode)
|
||||
mode->on_KeyEvent(ke);
|
||||
break;
|
||||
case kEventType::KeyUp:
|
||||
update_cursor();
|
||||
if (ke->m_key == kKey::KeyE)
|
||||
run_canvas_tool_mode(pp::app::CanvasToolMode::draw);
|
||||
if (ke->m_key == kKey::KeyTab)
|
||||
App::I->toggle_ui();
|
||||
if (ke->m_key == kKey::KeyZ && App::I->keys[(int)kKey::KeyCtrl])
|
||||
App::I->keys[(int)kKey::KeyShift] ? run_history_redo_if_available() : run_history_undo_if_available();
|
||||
if (ke->m_key == kKey::KeyS && App::I->keys[(int)kKey::KeyCtrl] && !App::I->keys[(int)kKey::KeyShift])
|
||||
{
|
||||
App::I->save_document(pp::app::DocumentSaveIntent::save);
|
||||
}
|
||||
if (ke->m_key == kKey::KeyS && App::I->keys[(int)kKey::KeyCtrl] && App::I->keys[(int)kKey::KeyShift])
|
||||
{
|
||||
App::I->save_document(pp::app::DocumentSaveIntent::save_dirty_version);
|
||||
}
|
||||
if (ke->m_key == kKey::KeyBracketLeft)
|
||||
{
|
||||
float v = App::I->stroke->m_tip_size->get_value();
|
||||
float nv = glm::clamp<float>(v - 0.05, 0, 1);
|
||||
App::I->stroke->set_size(nv, true, true);
|
||||
}
|
||||
if (ke->m_key == kKey::KeyBracketRight)
|
||||
{
|
||||
float v = App::I->stroke->m_tip_size->get_value();
|
||||
float nv = glm::clamp<float>(v + 0.05, 0, 1);
|
||||
App::I->stroke->set_size(nv, true, true);
|
||||
}
|
||||
run_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_up,
|
||||
ke->m_key,
|
||||
m_mouse_focus);
|
||||
for (auto& mode : *m_canvas->m_mode)
|
||||
mode->on_KeyEvent(ke);
|
||||
break;
|
||||
@@ -755,8 +836,11 @@ kEventResult NodeCanvas::handle_event(Event* e)
|
||||
mode->on_GestureEvent(ge);
|
||||
break;
|
||||
case kEventType::TouchTap:
|
||||
if (te->m_finger_count == 2)
|
||||
run_history_undo_if_available();
|
||||
run_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::touch_tap,
|
||||
kKey::Unknown,
|
||||
m_mouse_focus,
|
||||
te->m_finger_count);
|
||||
break;
|
||||
default:
|
||||
return kEventResult::Available;
|
||||
|
||||
@@ -298,6 +298,16 @@ add_test(NAME pp_app_core_canvas_tool_ui_tests COMMAND pp_app_core_canvas_tool_u
|
||||
set_tests_properties(pp_app_core_canvas_tool_ui_tests PROPERTIES
|
||||
LABELS "app;ui;desktop-fast;fuzz")
|
||||
|
||||
add_executable(pp_app_core_canvas_hotkey_tests
|
||||
app_core/canvas_hotkey_tests.cpp)
|
||||
target_link_libraries(pp_app_core_canvas_hotkey_tests PRIVATE
|
||||
pp_app_core
|
||||
pp_test_harness)
|
||||
|
||||
add_test(NAME pp_app_core_canvas_hotkey_tests COMMAND pp_app_core_canvas_hotkey_tests)
|
||||
set_tests_properties(pp_app_core_canvas_hotkey_tests PROPERTIES
|
||||
LABELS "app;ui;document;paint;desktop-fast;fuzz")
|
||||
|
||||
add_executable(pp_app_core_grid_ui_tests
|
||||
app_core/grid_ui_tests.cpp)
|
||||
target_link_libraries(pp_app_core_grid_ui_tests PRIVATE
|
||||
@@ -1274,6 +1284,36 @@ if(TARGET pano_cli)
|
||||
LABELS "renderer;paint;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_canvas_hotkey_ctrl_z_smoke
|
||||
COMMAND pano_cli plan-canvas-hotkey --event key-up --key z --ctrl --undo-count 2)
|
||||
set_tests_properties(pano_cli_plan_canvas_hotkey_ctrl_z_smoke PROPERTIES
|
||||
LABELS "app;document;ui;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-hotkey\".*\"key\":\"z\".*\"ctrl\":true.*\"action\":\"history\".*\"historyOperation\":\"undo\".*\"historyNoOp\":false")
|
||||
|
||||
add_test(NAME pano_cli_plan_canvas_hotkey_save_dirty_version_smoke
|
||||
COMMAND pano_cli plan-canvas-hotkey --event key-up --key s --ctrl --shift)
|
||||
set_tests_properties(pano_cli_plan_canvas_hotkey_save_dirty_version_smoke PROPERTIES
|
||||
LABELS "app;document;ui;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-hotkey\".*\"key\":\"s\".*\"shift\":true.*\"action\":\"save-document\".*\"saveIntent\":\"save-dirty-version\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_canvas_hotkey_erase_smoke
|
||||
COMMAND pano_cli plan-canvas-hotkey --event key-down --key e)
|
||||
set_tests_properties(pano_cli_plan_canvas_hotkey_erase_smoke PROPERTIES
|
||||
LABELS "app;paint;ui;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-hotkey\".*\"event\":\"key-down\".*\"key\":\"e\".*\"action\":\"select-tool\".*\"toolMode\":\"erase\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_canvas_hotkey_two_finger_undo_smoke
|
||||
COMMAND pano_cli plan-canvas-hotkey --event touch-tap --key other --touch-fingers 2 --undo-count 1)
|
||||
set_tests_properties(pano_cli_plan_canvas_hotkey_two_finger_undo_smoke PROPERTIES
|
||||
LABELS "app;document;ui;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-canvas-hotkey\".*\"event\":\"touch-tap\".*\"touchFingers\":2.*\"action\":\"history\".*\"historyOperation\":\"undo\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_canvas_hotkey_rejects_bad_count
|
||||
COMMAND pano_cli plan-canvas-hotkey --event key-down --key android-back --undo-count -1)
|
||||
set_tests_properties(pano_cli_plan_canvas_hotkey_rejects_bad_count PROPERTIES
|
||||
LABELS "app;document;ui;integration;desktop-fast;fuzz"
|
||||
WILL_FAIL TRUE)
|
||||
|
||||
add_test(NAME pano_cli_plan_canvas_tool_draw_smoke
|
||||
COMMAND pano_cli plan-canvas-tool --kind draw)
|
||||
set_tests_properties(pano_cli_plan_canvas_tool_draw_smoke PROPERTIES
|
||||
|
||||
297
tests/app_core/canvas_hotkey_tests.cpp
Normal file
297
tests/app_core/canvas_hotkey_tests.cpp
Normal file
@@ -0,0 +1,297 @@
|
||||
#include "app_core/canvas_hotkey.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
|
||||
class FakeCanvasHotkeyServices final : public pp::app::CanvasHotkeyServices {
|
||||
public:
|
||||
pp::foundation::Status execute_tool(const pp::app::CanvasToolPlan& plan) override
|
||||
{
|
||||
tool_calls += 1;
|
||||
last_tool_mode = plan.mode;
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status execute_history(const pp::app::HistoryUiPlan& plan) override
|
||||
{
|
||||
history_calls += 1;
|
||||
last_history_operation = plan.operation;
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
void save_document(pp::app::DocumentSaveIntent intent) override
|
||||
{
|
||||
save_calls += 1;
|
||||
last_save_intent = intent;
|
||||
}
|
||||
|
||||
void toggle_ui() override { toggle_ui_calls += 1; }
|
||||
|
||||
void adjust_brush_size(float delta) override
|
||||
{
|
||||
brush_adjust_calls += 1;
|
||||
last_brush_delta = delta;
|
||||
}
|
||||
|
||||
void show_cursor() override { show_cursor_calls += 1; }
|
||||
|
||||
int tool_calls = 0;
|
||||
int history_calls = 0;
|
||||
int save_calls = 0;
|
||||
int toggle_ui_calls = 0;
|
||||
int brush_adjust_calls = 0;
|
||||
int show_cursor_calls = 0;
|
||||
pp::app::CanvasToolMode last_tool_mode = pp::app::CanvasToolMode::draw;
|
||||
pp::app::HistoryUiOperation last_history_operation = pp::app::HistoryUiOperation::undo;
|
||||
pp::app::DocumentSaveIntent last_save_intent = pp::app::DocumentSaveIntent::save;
|
||||
float last_brush_delta = 0.0F;
|
||||
};
|
||||
|
||||
pp::app::CanvasHotkeyState default_state() noexcept
|
||||
{
|
||||
pp::app::CanvasHotkeyState state;
|
||||
state.undo_count = 2;
|
||||
state.redo_count = 1;
|
||||
return state;
|
||||
}
|
||||
|
||||
void e_key_toggles_erase_and_draw(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto down = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_down,
|
||||
pp::app::CanvasHotkeyKey::e,
|
||||
default_state());
|
||||
const auto up = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_up,
|
||||
pp::app::CanvasHotkeyKey::e,
|
||||
default_state());
|
||||
|
||||
PP_EXPECT(harness, down);
|
||||
PP_EXPECT(harness, up);
|
||||
if (down) {
|
||||
PP_EXPECT(harness, down.value().action == pp::app::CanvasHotkeyAction::select_tool);
|
||||
PP_EXPECT(harness, down.value().tool.mode == pp::app::CanvasToolMode::erase);
|
||||
PP_EXPECT(harness, !down.value().no_op);
|
||||
}
|
||||
if (up) {
|
||||
PP_EXPECT(harness, up.value().action == pp::app::CanvasHotkeyAction::select_tool);
|
||||
PP_EXPECT(harness, up.value().tool.mode == pp::app::CanvasToolMode::draw);
|
||||
PP_EXPECT(harness, !up.value().no_op);
|
||||
}
|
||||
}
|
||||
|
||||
void ctrl_z_and_shift_z_plan_history(pp::tests::Harness& harness)
|
||||
{
|
||||
auto state = default_state();
|
||||
state.ctrl_down = true;
|
||||
const auto undo = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_up,
|
||||
pp::app::CanvasHotkeyKey::z,
|
||||
state);
|
||||
state.shift_down = true;
|
||||
const auto redo = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_up,
|
||||
pp::app::CanvasHotkeyKey::z,
|
||||
state);
|
||||
|
||||
PP_EXPECT(harness, undo);
|
||||
PP_EXPECT(harness, redo);
|
||||
if (undo) {
|
||||
PP_EXPECT(harness, undo.value().action == pp::app::CanvasHotkeyAction::history);
|
||||
PP_EXPECT(harness, undo.value().history.operation == pp::app::HistoryUiOperation::undo);
|
||||
PP_EXPECT(harness, undo.value().history.invokes_undo);
|
||||
}
|
||||
if (redo) {
|
||||
PP_EXPECT(harness, redo.value().action == pp::app::CanvasHotkeyAction::history);
|
||||
PP_EXPECT(harness, redo.value().history.operation == pp::app::HistoryUiOperation::redo);
|
||||
PP_EXPECT(harness, redo.value().history.invokes_redo);
|
||||
}
|
||||
}
|
||||
|
||||
void save_hotkeys_plan_document_intents(pp::tests::Harness& harness)
|
||||
{
|
||||
auto state = default_state();
|
||||
state.ctrl_down = true;
|
||||
const auto save = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_up,
|
||||
pp::app::CanvasHotkeyKey::s,
|
||||
state);
|
||||
state.shift_down = true;
|
||||
const auto save_version = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_up,
|
||||
pp::app::CanvasHotkeyKey::s,
|
||||
state);
|
||||
|
||||
PP_EXPECT(harness, save);
|
||||
PP_EXPECT(harness, save_version);
|
||||
if (save) {
|
||||
PP_EXPECT(harness, save.value().action == pp::app::CanvasHotkeyAction::save_document);
|
||||
PP_EXPECT(harness, save.value().save_intent == pp::app::DocumentSaveIntent::save);
|
||||
}
|
||||
if (save_version) {
|
||||
PP_EXPECT(harness, save_version.value().action == pp::app::CanvasHotkeyAction::save_document);
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
save_version.value().save_intent == pp::app::DocumentSaveIntent::save_dirty_version);
|
||||
}
|
||||
}
|
||||
|
||||
void app_and_brush_hotkeys_are_planned(pp::tests::Harness& harness)
|
||||
{
|
||||
auto state = default_state();
|
||||
state.mouse_focused = true;
|
||||
const auto alt = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_down,
|
||||
pp::app::CanvasHotkeyKey::alt,
|
||||
state);
|
||||
const auto tab = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_up,
|
||||
pp::app::CanvasHotkeyKey::tab,
|
||||
state);
|
||||
const auto smaller = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_up,
|
||||
pp::app::CanvasHotkeyKey::bracket_left,
|
||||
state);
|
||||
const auto larger = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_up,
|
||||
pp::app::CanvasHotkeyKey::bracket_right,
|
||||
state);
|
||||
|
||||
PP_EXPECT(harness, alt);
|
||||
PP_EXPECT(harness, tab);
|
||||
PP_EXPECT(harness, smaller);
|
||||
PP_EXPECT(harness, larger);
|
||||
if (alt) {
|
||||
PP_EXPECT(harness, alt.value().action == pp::app::CanvasHotkeyAction::show_cursor);
|
||||
}
|
||||
if (tab) {
|
||||
PP_EXPECT(harness, tab.value().action == pp::app::CanvasHotkeyAction::toggle_ui);
|
||||
}
|
||||
if (smaller) {
|
||||
PP_EXPECT(harness, smaller.value().brush_size_delta < 0.0F);
|
||||
}
|
||||
if (larger) {
|
||||
PP_EXPECT(harness, larger.value().brush_size_delta > 0.0F);
|
||||
}
|
||||
}
|
||||
|
||||
void touch_and_android_back_plan_undo(pp::tests::Harness& harness)
|
||||
{
|
||||
auto state = default_state();
|
||||
const auto android = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_down,
|
||||
pp::app::CanvasHotkeyKey::android_back,
|
||||
state);
|
||||
state.touch_finger_count = 2;
|
||||
const auto tap = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::touch_tap,
|
||||
pp::app::CanvasHotkeyKey::other,
|
||||
state);
|
||||
|
||||
PP_EXPECT(harness, android);
|
||||
PP_EXPECT(harness, tap);
|
||||
if (android) {
|
||||
PP_EXPECT(harness, android.value().action == pp::app::CanvasHotkeyAction::history);
|
||||
PP_EXPECT(harness, android.value().history.operation == pp::app::HistoryUiOperation::undo);
|
||||
}
|
||||
if (tap) {
|
||||
PP_EXPECT(harness, tap.value().action == pp::app::CanvasHotkeyAction::history);
|
||||
PP_EXPECT(harness, tap.value().history.operation == pp::app::HistoryUiOperation::undo);
|
||||
}
|
||||
}
|
||||
|
||||
void planner_preserves_no_ops_and_rejects_bad_counts(pp::tests::Harness& harness)
|
||||
{
|
||||
auto state = default_state();
|
||||
const auto no_ctrl_z = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_up,
|
||||
pp::app::CanvasHotkeyKey::z,
|
||||
state);
|
||||
state.undo_count = -1;
|
||||
const auto bad_undo = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_down,
|
||||
pp::app::CanvasHotkeyKey::android_back,
|
||||
state);
|
||||
|
||||
PP_EXPECT(harness, no_ctrl_z);
|
||||
if (no_ctrl_z) {
|
||||
PP_EXPECT(harness, no_ctrl_z.value().action == pp::app::CanvasHotkeyAction::none);
|
||||
PP_EXPECT(harness, no_ctrl_z.value().no_op);
|
||||
}
|
||||
PP_EXPECT(harness, !bad_undo);
|
||||
if (!bad_undo) {
|
||||
PP_EXPECT(harness, bad_undo.status().code == pp::foundation::StatusCode::out_of_range);
|
||||
}
|
||||
}
|
||||
|
||||
void executor_dispatches_actions(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeCanvasHotkeyServices services;
|
||||
auto state = default_state();
|
||||
state.ctrl_down = true;
|
||||
|
||||
const auto save = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_up,
|
||||
pp::app::CanvasHotkeyKey::s,
|
||||
state);
|
||||
const auto undo = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_up,
|
||||
pp::app::CanvasHotkeyKey::z,
|
||||
state);
|
||||
const auto erase = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_down,
|
||||
pp::app::CanvasHotkeyKey::e,
|
||||
state);
|
||||
const auto brush = pp::app::plan_canvas_hotkey(
|
||||
pp::app::CanvasHotkeyEvent::key_up,
|
||||
pp::app::CanvasHotkeyKey::bracket_right,
|
||||
state);
|
||||
|
||||
PP_EXPECT(harness, save && undo && erase && brush);
|
||||
if (save && undo && erase && brush) {
|
||||
PP_EXPECT(harness, pp::app::execute_canvas_hotkey_plan(save.value(), services).ok());
|
||||
PP_EXPECT(harness, pp::app::execute_canvas_hotkey_plan(undo.value(), services).ok());
|
||||
PP_EXPECT(harness, pp::app::execute_canvas_hotkey_plan(erase.value(), services).ok());
|
||||
PP_EXPECT(harness, pp::app::execute_canvas_hotkey_plan(brush.value(), services).ok());
|
||||
}
|
||||
|
||||
PP_EXPECT(harness, services.save_calls == 1);
|
||||
PP_EXPECT(harness, services.history_calls == 1);
|
||||
PP_EXPECT(harness, services.tool_calls == 1);
|
||||
PP_EXPECT(harness, services.brush_adjust_calls == 1);
|
||||
PP_EXPECT(harness, services.last_save_intent == pp::app::DocumentSaveIntent::save);
|
||||
PP_EXPECT(harness, services.last_history_operation == pp::app::HistoryUiOperation::undo);
|
||||
PP_EXPECT(harness, services.last_tool_mode == pp::app::CanvasToolMode::erase);
|
||||
PP_EXPECT(harness, std::fabs(services.last_brush_delta - 0.05F) < 0.001F);
|
||||
}
|
||||
|
||||
void executor_rejects_malformed_brush_adjust(pp::tests::Harness& harness)
|
||||
{
|
||||
FakeCanvasHotkeyServices services;
|
||||
pp::app::CanvasHotkeyPlan malformed;
|
||||
malformed.action = pp::app::CanvasHotkeyAction::adjust_brush_size;
|
||||
malformed.no_op = false;
|
||||
|
||||
const auto status = pp::app::execute_canvas_hotkey_plan(malformed, services);
|
||||
PP_EXPECT(harness, !status.ok());
|
||||
PP_EXPECT(harness, status.code == pp::foundation::StatusCode::invalid_argument);
|
||||
PP_EXPECT(harness, services.brush_adjust_calls == 0);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
pp::tests::Harness harness;
|
||||
harness.run("e key toggles erase and draw", e_key_toggles_erase_and_draw);
|
||||
harness.run("ctrl z and shift z plan history", ctrl_z_and_shift_z_plan_history);
|
||||
harness.run("save hotkeys plan document intents", save_hotkeys_plan_document_intents);
|
||||
harness.run("app and brush hotkeys are planned", app_and_brush_hotkeys_are_planned);
|
||||
harness.run("touch and android back plan undo", touch_and_android_back_plan_undo);
|
||||
harness.run("planner preserves no ops and rejects bad counts", planner_preserves_no_ops_and_rejects_bad_counts);
|
||||
harness.run("executor dispatches actions", executor_dispatches_actions);
|
||||
harness.run("executor rejects malformed brush adjust", executor_rejects_malformed_brush_adjust);
|
||||
return harness.finish();
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "app_core/app_preferences.h"
|
||||
#include "app_core/app_status.h"
|
||||
#include "app_core/brush_ui.h"
|
||||
#include "app_core/canvas_hotkey.h"
|
||||
#include "app_core/canvas_tool_ui.h"
|
||||
#include "app_core/document_animation.h"
|
||||
#include "app_core/document_canvas.h"
|
||||
@@ -406,6 +407,17 @@ struct PlanCanvasToolArgs {
|
||||
bool current_mode_draw = false;
|
||||
};
|
||||
|
||||
struct PlanCanvasHotkeyArgs {
|
||||
std::string event = "key-up";
|
||||
std::string key = "z";
|
||||
bool ctrl_down = false;
|
||||
bool shift_down = false;
|
||||
bool mouse_focused = false;
|
||||
int undo_count = 1;
|
||||
int redo_count = 1;
|
||||
int touch_finger_count = 0;
|
||||
};
|
||||
|
||||
struct PlanCanvasToolStateArgs {
|
||||
std::string mode = "draw";
|
||||
bool picking = false;
|
||||
@@ -1323,6 +1335,68 @@ const char* canvas_tool_transform_action_name(pp::app::CanvasToolTransformAction
|
||||
return "none";
|
||||
}
|
||||
|
||||
const char* canvas_hotkey_event_name(pp::app::CanvasHotkeyEvent event) noexcept
|
||||
{
|
||||
switch (event) {
|
||||
case pp::app::CanvasHotkeyEvent::key_down:
|
||||
return "key-down";
|
||||
case pp::app::CanvasHotkeyEvent::key_up:
|
||||
return "key-up";
|
||||
case pp::app::CanvasHotkeyEvent::touch_tap:
|
||||
return "touch-tap";
|
||||
}
|
||||
|
||||
return "key-up";
|
||||
}
|
||||
|
||||
const char* canvas_hotkey_key_name(pp::app::CanvasHotkeyKey key) noexcept
|
||||
{
|
||||
switch (key) {
|
||||
case pp::app::CanvasHotkeyKey::other:
|
||||
return "other";
|
||||
case pp::app::CanvasHotkeyKey::android_back:
|
||||
return "android-back";
|
||||
case pp::app::CanvasHotkeyKey::alt:
|
||||
return "alt";
|
||||
case pp::app::CanvasHotkeyKey::e:
|
||||
return "e";
|
||||
case pp::app::CanvasHotkeyKey::s:
|
||||
return "s";
|
||||
case pp::app::CanvasHotkeyKey::tab:
|
||||
return "tab";
|
||||
case pp::app::CanvasHotkeyKey::z:
|
||||
return "z";
|
||||
case pp::app::CanvasHotkeyKey::bracket_left:
|
||||
return "bracket-left";
|
||||
case pp::app::CanvasHotkeyKey::bracket_right:
|
||||
return "bracket-right";
|
||||
}
|
||||
|
||||
return "other";
|
||||
}
|
||||
|
||||
const char* canvas_hotkey_action_name(pp::app::CanvasHotkeyAction action) noexcept
|
||||
{
|
||||
switch (action) {
|
||||
case pp::app::CanvasHotkeyAction::none:
|
||||
return "none";
|
||||
case pp::app::CanvasHotkeyAction::select_tool:
|
||||
return "select-tool";
|
||||
case pp::app::CanvasHotkeyAction::history:
|
||||
return "history";
|
||||
case pp::app::CanvasHotkeyAction::save_document:
|
||||
return "save-document";
|
||||
case pp::app::CanvasHotkeyAction::toggle_ui:
|
||||
return "toggle-ui";
|
||||
case pp::app::CanvasHotkeyAction::adjust_brush_size:
|
||||
return "adjust-brush-size";
|
||||
case pp::app::CanvasHotkeyAction::show_cursor:
|
||||
return "show-cursor";
|
||||
}
|
||||
|
||||
return "none";
|
||||
}
|
||||
|
||||
const char* grid_ui_operation_name(pp::app::GridUiOperation operation) noexcept
|
||||
{
|
||||
switch (operation) {
|
||||
@@ -1796,6 +1870,7 @@ void print_help()
|
||||
<< " plan-brush-stroke-control --kind float|bool|blend|tip-aspect-reset|default-reset [--setting NAME] [--value N] [--enabled|--disabled] [--blend-mode N]\n"
|
||||
<< " plan-paint-feedback [--width N] [--height N] [--simple|--complex] [--framebuffer-fetch] [--texture-copy] [--blit] [--explicit-transitions] [--render-only] [--depth]\n"
|
||||
<< " plan-stroke-composite [--width N] [--height N] [--layer-blend N] [--stroke-blend N] [--dual-blend] [--pattern-blend] [--framebuffer-fetch] [--texture-copy] [--blit] [--explicit-transitions] [--render-only] [--depth]\n"
|
||||
<< " plan-canvas-hotkey --event key-down|key-up|touch-tap --key e|z|s|tab|alt|android-back|bracket-left|bracket-right [--ctrl] [--shift] [--mouse-focus] [--undo-count N] [--redo-count N] [--touch-fingers N]\n"
|
||||
<< " plan-canvas-tool --kind draw|erase|line|camera|grid|copy|cut|fill|mask-free|mask-line|bucket|pick|touch-lock [--current-mode-draw]\n"
|
||||
<< " plan-canvas-tool-state [--mode draw|erase|line|camera|grid|copy|cut|fill|mask-free|mask-line|bucket] [--picking] [--touch-lock]\n"
|
||||
<< " plan-grid-operation --kind pick|load|reload|clear|render|commit [--path FILE] [--no-heightmap] [--no-canvas] [--float32] [--float16] [--texture-resolution N] [--samples N]\n"
|
||||
@@ -5340,6 +5415,166 @@ int plan_canvas_tool(int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Result<pp::app::CanvasHotkeyEvent> parse_canvas_hotkey_event(std::string_view event)
|
||||
{
|
||||
if (event == "key-down") {
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyEvent>::success(
|
||||
pp::app::CanvasHotkeyEvent::key_down);
|
||||
}
|
||||
if (event == "key-up") {
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyEvent>::success(
|
||||
pp::app::CanvasHotkeyEvent::key_up);
|
||||
}
|
||||
if (event == "touch-tap") {
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyEvent>::success(
|
||||
pp::app::CanvasHotkeyEvent::touch_tap);
|
||||
}
|
||||
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyEvent>::failure(
|
||||
pp::foundation::Status::invalid_argument("unknown canvas hotkey event"));
|
||||
}
|
||||
|
||||
pp::foundation::Result<pp::app::CanvasHotkeyKey> parse_canvas_hotkey_key(std::string_view key)
|
||||
{
|
||||
if (key == "other") {
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyKey>::success(
|
||||
pp::app::CanvasHotkeyKey::other);
|
||||
}
|
||||
if (key == "android-back") {
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyKey>::success(
|
||||
pp::app::CanvasHotkeyKey::android_back);
|
||||
}
|
||||
if (key == "alt") {
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyKey>::success(
|
||||
pp::app::CanvasHotkeyKey::alt);
|
||||
}
|
||||
if (key == "e") {
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyKey>::success(pp::app::CanvasHotkeyKey::e);
|
||||
}
|
||||
if (key == "s") {
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyKey>::success(pp::app::CanvasHotkeyKey::s);
|
||||
}
|
||||
if (key == "tab") {
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyKey>::success(pp::app::CanvasHotkeyKey::tab);
|
||||
}
|
||||
if (key == "z") {
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyKey>::success(pp::app::CanvasHotkeyKey::z);
|
||||
}
|
||||
if (key == "bracket-left") {
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyKey>::success(
|
||||
pp::app::CanvasHotkeyKey::bracket_left);
|
||||
}
|
||||
if (key == "bracket-right") {
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyKey>::success(
|
||||
pp::app::CanvasHotkeyKey::bracket_right);
|
||||
}
|
||||
|
||||
return pp::foundation::Result<pp::app::CanvasHotkeyKey>::failure(
|
||||
pp::foundation::Status::invalid_argument("unknown canvas hotkey key"));
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_canvas_hotkey_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
PlanCanvasHotkeyArgs& args)
|
||||
{
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
const std::string_view key(argv[i]);
|
||||
if (key == "--event" || key == "--key") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
if (key == "--event") {
|
||||
args.event = argv[++i];
|
||||
} else {
|
||||
args.key = argv[++i];
|
||||
}
|
||||
} else if (key == "--undo-count" || key == "--redo-count" || key == "--touch-fingers") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
const auto value = parse_i32_arg(argv[++i]);
|
||||
if (!value) {
|
||||
return value.status();
|
||||
}
|
||||
if (key == "--undo-count") {
|
||||
args.undo_count = value.value();
|
||||
} else if (key == "--redo-count") {
|
||||
args.redo_count = value.value();
|
||||
} else {
|
||||
args.touch_finger_count = value.value();
|
||||
}
|
||||
} else if (key == "--ctrl") {
|
||||
args.ctrl_down = true;
|
||||
} else if (key == "--shift") {
|
||||
args.shift_down = true;
|
||||
} else if (key == "--mouse-focus") {
|
||||
args.mouse_focused = true;
|
||||
} else {
|
||||
return pp::foundation::Status::invalid_argument("unknown option");
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
int plan_canvas_hotkey(int argc, char** argv)
|
||||
{
|
||||
PlanCanvasHotkeyArgs args;
|
||||
const auto status = parse_plan_canvas_hotkey_args(argc, argv, args);
|
||||
if (!status.ok()) {
|
||||
print_error("plan-canvas-hotkey", status.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto event = parse_canvas_hotkey_event(args.event);
|
||||
if (!event) {
|
||||
print_error("plan-canvas-hotkey", event.status().message);
|
||||
return 2;
|
||||
}
|
||||
const auto key = parse_canvas_hotkey_key(args.key);
|
||||
if (!key) {
|
||||
print_error("plan-canvas-hotkey", key.status().message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
pp::app::CanvasHotkeyState state;
|
||||
state.ctrl_down = args.ctrl_down;
|
||||
state.shift_down = args.shift_down;
|
||||
state.mouse_focused = args.mouse_focused;
|
||||
state.undo_count = args.undo_count;
|
||||
state.redo_count = args.redo_count;
|
||||
state.touch_finger_count = args.touch_finger_count;
|
||||
|
||||
const auto plan = pp::app::plan_canvas_hotkey(event.value(), key.value(), state);
|
||||
if (!plan) {
|
||||
print_error("plan-canvas-hotkey", plan.status().message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto& value = plan.value();
|
||||
std::cout << "{\"ok\":true,\"command\":\"plan-canvas-hotkey\""
|
||||
<< ",\"state\":{\"event\":\"" << json_escape(args.event)
|
||||
<< "\",\"key\":\"" << json_escape(args.key)
|
||||
<< "\",\"ctrl\":" << json_bool(args.ctrl_down)
|
||||
<< ",\"shift\":" << json_bool(args.shift_down)
|
||||
<< ",\"mouseFocus\":" << json_bool(args.mouse_focused)
|
||||
<< ",\"undoCount\":" << args.undo_count
|
||||
<< ",\"redoCount\":" << args.redo_count
|
||||
<< ",\"touchFingers\":" << args.touch_finger_count
|
||||
<< "},\"plan\":{\"event\":\"" << canvas_hotkey_event_name(value.event)
|
||||
<< "\",\"key\":\"" << canvas_hotkey_key_name(value.key)
|
||||
<< "\",\"action\":\"" << canvas_hotkey_action_name(value.action)
|
||||
<< "\",\"toolMode\":\"" << canvas_tool_mode_name(value.tool.mode)
|
||||
<< "\",\"historyOperation\":\"" << history_ui_operation_name(value.history.operation)
|
||||
<< "\",\"historyNoOp\":" << json_bool(value.history.no_op)
|
||||
<< ",\"saveIntent\":\"" << document_save_intent_name(value.save_intent)
|
||||
<< "\",\"brushSizeDelta\":" << value.brush_size_delta
|
||||
<< ",\"noOp\":" << json_bool(value.no_op)
|
||||
<< "}}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_canvas_tool_state_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
@@ -8329,6 +8564,10 @@ int main(int argc, char** argv)
|
||||
return plan_stroke_composite(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-canvas-hotkey") {
|
||||
return plan_canvas_hotkey(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-canvas-tool") {
|
||||
return plan_canvas_tool(argc, argv);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user