Extract history UI operation planning

This commit is contained in:
2026-06-03 11:13:57 +02:00
parent 8dc476d205
commit 58afa672c7
9 changed files with 392 additions and 7 deletions

110
src/app_core/history_ui.h Normal file
View File

@@ -0,0 +1,110 @@
#pragma once
#include "foundation/result.h"
namespace pp::app {
enum class HistoryUiOperation {
undo,
redo,
clear,
};
struct HistoryUiPlan {
HistoryUiOperation operation = HistoryUiOperation::undo;
int undo_count = 0;
int redo_count = 0;
int memory_bytes = 0;
bool invokes_undo = false;
bool invokes_redo = false;
bool clears_history = false;
bool updates_memory_label = false;
bool updates_title = false;
bool no_op = false;
};
[[nodiscard]] inline pp::foundation::Status validate_history_metric(int value, const char* message) noexcept
{
if (value < 0) {
return pp::foundation::Status::out_of_range(message);
}
return pp::foundation::Status::success();
}
[[nodiscard]] inline pp::foundation::Result<HistoryUiPlan> plan_history_undo(int undo_count)
{
const auto count_status = validate_history_metric(undo_count, "undo action count must not be negative");
if (!count_status.ok()) {
return pp::foundation::Result<HistoryUiPlan>::failure(count_status);
}
HistoryUiPlan plan;
plan.operation = HistoryUiOperation::undo;
plan.undo_count = undo_count;
if (undo_count == 0) {
plan.no_op = true;
return pp::foundation::Result<HistoryUiPlan>::success(plan);
}
plan.invokes_undo = true;
plan.updates_memory_label = true;
plan.updates_title = true;
return pp::foundation::Result<HistoryUiPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<HistoryUiPlan> plan_history_redo(int redo_count)
{
const auto count_status = validate_history_metric(redo_count, "redo action count must not be negative");
if (!count_status.ok()) {
return pp::foundation::Result<HistoryUiPlan>::failure(count_status);
}
HistoryUiPlan plan;
plan.operation = HistoryUiOperation::redo;
plan.redo_count = redo_count;
if (redo_count == 0) {
plan.no_op = true;
return pp::foundation::Result<HistoryUiPlan>::success(plan);
}
plan.invokes_redo = true;
plan.updates_memory_label = true;
plan.updates_title = true;
return pp::foundation::Result<HistoryUiPlan>::success(plan);
}
[[nodiscard]] inline pp::foundation::Result<HistoryUiPlan> plan_history_clear(
int undo_count,
int redo_count,
int memory_bytes)
{
const auto undo_status = validate_history_metric(undo_count, "undo action count must not be negative");
if (!undo_status.ok()) {
return pp::foundation::Result<HistoryUiPlan>::failure(undo_status);
}
const auto redo_status = validate_history_metric(redo_count, "redo action count must not be negative");
if (!redo_status.ok()) {
return pp::foundation::Result<HistoryUiPlan>::failure(redo_status);
}
const auto memory_status = validate_history_metric(memory_bytes, "history memory bytes must not be negative");
if (!memory_status.ok()) {
return pp::foundation::Result<HistoryUiPlan>::failure(memory_status);
}
HistoryUiPlan plan;
plan.operation = HistoryUiOperation::clear;
plan.undo_count = undo_count;
plan.redo_count = redo_count;
plan.memory_bytes = memory_bytes;
if (undo_count == 0 && redo_count == 0 && memory_bytes == 0) {
plan.no_op = true;
return pp::foundation::Result<HistoryUiPlan>::success(plan);
}
plan.clears_history = true;
plan.updates_memory_label = true;
return pp::foundation::Result<HistoryUiPlan>::success(plan);
}
} // namespace pp::app

View File

@@ -10,6 +10,7 @@
#include "app_core/brush_ui.h"
#include "app_core/document_layer.h"
#include "app_core/app_status.h"
#include "app_core/history_ui.h"
#include "settings.h"
#include "serializer.h"
#include "font.h"
@@ -119,19 +120,28 @@ void App::init_toolbar_main()
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-undo"))
{
button->on_click = [this, button](Node*) {
ActionManager::undo();
const auto plan = pp::app::plan_history_undo(static_cast<int>(ActionManager::I.m_actions.size()));
if (plan && plan.value().invokes_undo)
ActionManager::undo();
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-redo"))
{
button->on_click = [this, button](Node*) {
ActionManager::redo();
const auto plan = pp::app::plan_history_redo(static_cast<int>(ActionManager::I.m_redos.size()));
if (plan && plan.value().invokes_redo)
ActionManager::redo();
};
}
if (auto* button = layout[main_id]->find<NodeButtonCustom>("btn-clean-memory"))
{
button->on_click = [this](Node*) {
ActionManager::clear();
const auto plan = pp::app::plan_history_clear(
static_cast<int>(ActionManager::I.m_actions.size()),
static_cast<int>(ActionManager::I.m_redos.size()),
static_cast<int>(ActionManager::I.m_memory));
if (plan && plan.value().clears_history)
ActionManager::clear();
};
}
if (auto* button = layout[main_id]->find<NodeButton>("btn-clear"))

View File

@@ -2,6 +2,7 @@
#include <cstdint>
#include "app_core/history_ui.h"
#include "app.h"
#include "log.h"
#include "node_canvas.h"
@@ -21,6 +22,20 @@ void unbind_texture_2d()
glBindTexture(pp::renderer::gl::texture_2d_target(), 0);
}
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();
}
}
Node* NodeCanvas::clone_instantiate() const
@@ -600,8 +615,7 @@ kEventResult NodeCanvas::handle_event(Event* e)
if (ke->m_key == kKey::KeyE)
Canvas::set_mode(kCanvasMode::Erase);
if (ke->m_key == kKey::AndroidBack)
if (!ActionManager::empty())
ActionManager::undo();
run_history_undo_if_available();
if (ke->m_key == kKey::KeyAlt && m_mouse_focus)
App::I->show_cursor();
for (auto& mode : *m_canvas->m_mode)
@@ -614,7 +628,7 @@ kEventResult NodeCanvas::handle_event(Event* e)
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] ? ActionManager::redo() : ActionManager::undo();
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);
@@ -654,7 +668,7 @@ kEventResult NodeCanvas::handle_event(Event* e)
break;
case kEventType::TouchTap:
if (te->m_finger_count == 2)
ActionManager::undo();
run_history_undo_if_available();
break;
default:
return kEventResult::Available;