Route canvas hotkeys through app core
This commit is contained in:
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;
|
||||
|
||||
Reference in New Issue
Block a user