PixelPaint: Show more specific Undo/Redo action text

The Undo/Redo actions now tell you what kind of action will be
undone/redone. This is achieved by adding an "action text" field to the
ImageUndoCommand and having everyone who calls did_complete_action()
provide this text.
This commit is contained in:
Andreas Kling 2022-08-21 20:33:03 +02:00
parent 101eb53de5
commit bf25b0a0b5
Notes: sideshowbarker 2024-07-17 08:05:26 +09:00
15 changed files with 57 additions and 20 deletions

View File

@ -43,7 +43,7 @@ void Filter::apply() const
if (auto* layer = m_editor->active_layer()) {
apply(layer->content_bitmap(), layer->content_bitmap());
layer->did_modify_bitmap(layer->rect());
m_editor->did_complete_action();
m_editor->did_complete_action(String::formatted("Filter {}", filter_name()));
}
}

View File

@ -481,9 +481,10 @@ void Image::did_change_rect(Gfx::IntRect const& a_modified_rect)
client->image_did_change_rect(modified_rect);
}
ImageUndoCommand::ImageUndoCommand(Image& image)
ImageUndoCommand::ImageUndoCommand(Image& image, String action_text)
: m_snapshot(image.take_snapshot().release_value_but_fixme_should_propagate_errors())
, m_image(image)
, m_action_text(move(action_text))
{
}

View File

@ -116,14 +116,16 @@ private:
class ImageUndoCommand : public GUI::Command {
public:
ImageUndoCommand(Image& image);
ImageUndoCommand(Image&, String action_text);
virtual void undo() override;
virtual void redo() override;
virtual String action_text() const override { return m_action_text; }
private:
RefPtr<Image> m_snapshot;
Image& m_image;
String m_action_text;
};
}

View File

@ -31,7 +31,7 @@ ImageEditor::ImageEditor(NonnullRefPtr<Image> image)
, m_selection(*this)
{
set_focus_policy(GUI::FocusPolicy::StrongFocus);
m_undo_stack.push(make<ImageUndoCommand>(*m_image));
m_undo_stack.push(make<ImageUndoCommand>(*m_image, String()));
m_image->add_client(*this);
set_original_rect(m_image->rect());
set_scale_bounds(0.1f, 100.0f);
@ -48,11 +48,11 @@ ImageEditor::~ImageEditor()
m_image->remove_client(*this);
}
void ImageEditor::did_complete_action()
void ImageEditor::did_complete_action(String action_text)
{
if (on_modified_change)
on_modified_change(true);
m_undo_stack.push(make<ImageUndoCommand>(*m_image));
m_undo_stack.push(make<ImageUndoCommand>(*m_image, move(action_text)));
}
bool ImageEditor::is_modified()

View File

@ -40,7 +40,7 @@ public:
void set_active_tool(Tool*);
void update_tool_cursor();
void did_complete_action();
void did_complete_action(String action_text);
bool undo();
bool redo();

View File

@ -60,7 +60,7 @@ LevelsDialog::LevelsDialog(GUI::Window* parent_window, ImageEditor* editor)
apply_button->on_click = [this](auto) {
if (m_did_change) {
m_editor->on_modified_change(true);
m_editor->did_complete_action();
m_editor->did_complete_action("Levels"sv);
}
cleanup_resources();

View File

@ -100,9 +100,41 @@ MainWidget::MainWidget()
m_show_guides_action->set_checked(image_editor.guide_visibility());
m_show_rulers_action->set_checked(image_editor.ruler_visibility());
image_editor.on_scale_change(image_editor.scale());
image_editor.undo_stack().on_state_change = [this] {
image_editor_did_update_undo_stack();
};
// Ensure that our undo/redo actions are in sync with the current editor.
image_editor_did_update_undo_stack();
};
}
void MainWidget::image_editor_did_update_undo_stack()
{
auto* image_editor = current_image_editor();
if (!image_editor) {
m_undo_action->set_enabled(false);
m_redo_action->set_enabled(false);
return;
}
auto make_action_text = [](auto prefix, auto suffix) {
StringBuilder builder;
builder.append(prefix);
if (suffix.has_value()) {
builder.append(' ');
builder.append(suffix.value());
}
return builder.to_string();
};
auto& undo_stack = image_editor->undo_stack();
m_undo_action->set_enabled(undo_stack.can_undo());
m_redo_action->set_enabled(undo_stack.can_redo());
m_undo_action->set_text(make_action_text("&Undo"sv, undo_stack.undo_action_text()));
m_redo_action->set_text(make_action_text("&Redo"sv, undo_stack.redo_action_text()));
}
// Note: Update these together! v
static Vector<String> const s_suggested_zoom_levels { "25%", "50%", "100%", "200%", "300%", "400%", "800%", "1600%", "Fit to width", "Fit to height", "Fit entire image" };
static constexpr int s_zoom_level_fit_width = 8;
@ -642,7 +674,7 @@ void MainWidget::initialize_menubar(GUI::Window& window)
auto* editor = current_image_editor();
VERIFY(editor);
editor->image().flatten_all_layers();
editor->did_complete_action();
editor->did_complete_action("Flatten Image"sv);
}));
m_layer_menu->add_action(GUI::Action::create(
@ -650,7 +682,7 @@ void MainWidget::initialize_menubar(GUI::Window& window)
auto* editor = current_image_editor();
VERIFY(editor);
editor->image().merge_visible_layers();
editor->did_complete_action();
editor->did_complete_action("Merge Visible"sv);
}));
m_layer_menu->add_action(GUI::Action::create(
@ -661,7 +693,7 @@ void MainWidget::initialize_menubar(GUI::Window& window)
if (!active_layer)
return;
editor->image().merge_active_layer_up(*active_layer);
editor->did_complete_action();
editor->did_complete_action("Merge Active Layer Up"sv);
}));
m_layer_menu->add_action(GUI::Action::create(
@ -672,7 +704,7 @@ void MainWidget::initialize_menubar(GUI::Window& window)
if (!active_layer)
return;
editor->image().merge_active_layer_down(*active_layer);
editor->did_complete_action();
editor->did_complete_action("Merge Active Layer Down"sv);
}));
m_filter_menu = window.add_menu("&Filter");
@ -694,7 +726,7 @@ void MainWidget::initialize_menubar(GUI::Window& window)
if (auto parameters = PixelPaint::FilterParameters<Gfx::GenericConvolutionFilter<5>>::get(&window)) {
filter.apply(layer->content_bitmap(), layer->rect(), layer->content_bitmap(), layer->rect(), *parameters);
layer->did_modify_bitmap(layer->rect());
editor->did_complete_action();
editor->did_complete_action("Generic 5x5 Convolution"sv);
}
}
}));

View File

@ -51,6 +51,8 @@ private:
ImageEditor& create_new_editor(NonnullRefPtr<Image>);
void create_image_from_clipboard();
void image_editor_did_update_undo_stack();
void set_actions_enabled(bool enabled);
virtual void drop_event(GUI::DropEvent&) override;

View File

@ -67,7 +67,7 @@ void BrushTool::on_mousemove(Layer* layer, MouseEvent& event)
void BrushTool::on_mouseup(Layer*, MouseEvent&)
{
if (m_was_drawing) {
m_editor->did_complete_action();
m_editor->did_complete_action(tool_name());
m_was_drawing = false;
}
}

View File

@ -134,7 +134,7 @@ void BucketTool::on_mousedown(Layer* layer, MouseEvent& event)
flood_fill(layer->currently_edited_bitmap(), layer_event.position(), target_color, m_editor->color_for(layer_event), m_threshold);
layer->did_modify_bitmap();
m_editor->did_complete_action();
m_editor->did_complete_action(tool_name());
}
GUI::Widget* BucketTool::get_properties_widget()

View File

@ -88,7 +88,7 @@ void EllipseTool::on_mouseup(Layer* layer, MouseEvent& event)
m_drawing_button = GUI::MouseButton::None;
layer->did_modify_bitmap();
m_editor->update();
m_editor->did_complete_action();
m_editor->did_complete_action(tool_name());
}
}

View File

@ -81,7 +81,7 @@ void LineTool::on_mouseup(Layer* layer, MouseEvent& event)
m_drawing_button = GUI::MouseButton::None;
layer->did_modify_bitmap();
m_editor->update();
m_editor->did_complete_action();
m_editor->did_complete_action(tool_name());
}
}

View File

@ -69,7 +69,7 @@ void MoveTool::on_mouseup(Layer* layer, MouseEvent& event)
if (layer_event.button() != GUI::MouseButton::Primary)
return;
m_layer_being_moved = nullptr;
m_editor->did_complete_action();
m_editor->did_complete_action(tool_name());
}
void MoveTool::on_keydown(GUI::KeyEvent& event)

View File

@ -92,7 +92,7 @@ void RectangleTool::on_mouseup(Layer* layer, MouseEvent& event)
m_drawing_button = GUI::MouseButton::None;
layer->did_modify_bitmap();
m_editor->update();
m_editor->did_complete_action();
m_editor->did_complete_action(tool_name());
}
}

View File

@ -88,7 +88,7 @@ void SprayTool::on_mouseup(Layer*, MouseEvent&)
{
if (m_timer->is_active()) {
m_timer->stop();
m_editor->did_complete_action();
m_editor->did_complete_action(tool_name());
}
}