1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 08:47:34 +00:00

PixelPaint: Add a Duplicate Layer action

The "Duplicate Layer" action inserts a copy of the selected layer into
the layer stack. The new layer is placed above the selected layer.
This commit is contained in:
Tim Ledbetter 2023-03-24 17:51:24 +00:00 committed by Jelle Raaijmakers
parent 55feecee36
commit 3faf089be5
7 changed files with 83 additions and 3 deletions

View file

@ -200,19 +200,30 @@ ErrorOr<void> Image::export_qoi_to_file(NonnullOwnPtr<Stream> stream) const
return {};
}
void Image::add_layer(NonnullRefPtr<Layer> layer)
void Image::insert_layer(NonnullRefPtr<Layer> layer, size_t index)
{
VERIFY(index <= m_layers.size());
for (auto& existing_layer : m_layers) {
VERIFY(existing_layer != layer);
}
m_layers.append(move(layer));
if (index == m_layers.size())
m_layers.append(move(layer));
else
m_layers.insert(index, move(layer));
for (auto* client : m_clients)
client->image_did_add_layer(m_layers.size() - 1);
client->image_did_add_layer(index);
did_modify_layer_stack();
}
void Image::add_layer(NonnullRefPtr<Layer> layer)
{
insert_layer(move(layer), m_layers.size());
}
ErrorOr<NonnullRefPtr<Image>> Image::take_snapshot() const
{
auto snapshot = TRY(create_with_size(m_size));

View file

@ -65,6 +65,7 @@ public:
Gfx::IntRect rect() const { return { {}, m_size }; }
void add_layer(NonnullRefPtr<Layer>);
void insert_layer(NonnullRefPtr<Layer>, size_t index);
ErrorOr<NonnullRefPtr<Image>> take_snapshot() const;
ErrorOr<void> restore_snapshot(Image const&);
@ -124,6 +125,8 @@ private:
ErrorOr<void> merge_layers(LayerMergeMode);
ErrorOr<void> merge_active_layer(NonnullRefPtr<Layer> const&, LayerMergeDirection);
DeprecatedString generate_unique_layer_name(DeprecatedString const& original_name);
Gfx::IntSize m_size;
Vector<NonnullRefPtr<Layer>> m_layers;

View file

@ -914,4 +914,37 @@ void ImageEditor::set_appended_status_info(DeprecatedString new_status_info)
on_appended_status_info_change(m_appended_status_info);
}
DeprecatedString ImageEditor::generate_unique_layer_name(DeprecatedString const& original_layer_name)
{
constexpr StringView copy_string_view = " copy"sv;
auto copy_suffix_index = original_layer_name.find_last(copy_string_view);
if (!copy_suffix_index.has_value())
return DeprecatedString::formatted("{}{}", original_layer_name, copy_string_view);
auto after_copy_suffix_view = original_layer_name.substring_view(copy_suffix_index.value() + copy_string_view.length());
if (!after_copy_suffix_view.is_empty()) {
auto after_copy_suffix_number = after_copy_suffix_view.trim_whitespace().to_int();
if (!after_copy_suffix_number.has_value())
return DeprecatedString::formatted("{}{}", original_layer_name, copy_string_view);
}
auto layer_with_name_exists = [this](auto name) {
for (size_t i = 0; i < image().layer_count(); ++i) {
if (image().layer(i).name() == name)
return true;
}
return false;
};
auto base_layer_name = original_layer_name.substring_view(0, copy_suffix_index.value());
StringBuilder new_layer_name;
auto duplicate_name_count = 0;
do {
new_layer_name.clear();
new_layer_name.appendff("{}{} {}", base_layer_name, copy_string_view, ++duplicate_name_count);
} while (layer_with_name_exists(new_layer_name.string_view()));
return new_layer_name.to_deprecated_string();
}
}

View file

@ -127,6 +127,7 @@ public:
Function<void(DeprecatedString)> on_appended_status_info_change;
DeprecatedString appended_status_info() { return m_appended_status_info; };
void set_appended_status_info(DeprecatedString);
DeprecatedString generate_unique_layer_name(DeprecatedString const& original_layer_name);
private:
explicit ImageEditor(NonnullRefPtr<Image>);

View file

@ -405,4 +405,12 @@ Optional<Gfx::IntRect> Layer::nonempty_content_bounding_rect() const
};
}
ErrorOr<NonnullRefPtr<Layer>> Layer::duplicate(DeprecatedString name)
{
auto duplicated_layer = TRY(Layer::create_snapshot(m_image, *this));
duplicated_layer->m_name = move(name);
duplicated_layer->m_selected = false;
return duplicated_layer;
}
}

View file

@ -102,6 +102,8 @@ public:
Gfx::Bitmap& currently_edited_bitmap();
ErrorOr<NonnullRefPtr<Layer>> duplicate(DeprecatedString name);
private:
Layer(Image&, NonnullRefPtr<Gfx::Bitmap>, DeprecatedString name);

View file

@ -724,6 +724,28 @@ ErrorOr<void> MainWidget::initialize_menubar(GUI::Window& window)
}
})));
TRY(m_layer_menu->try_add_action(GUI::Action::create(
"Duplicate Layer", { Mod_Ctrl | Mod_Shift, Key_D }, g_icon_bag.new_layer, [&](auto&) {
auto* editor = current_image_editor();
VERIFY(editor);
auto* active_layer = editor->active_layer();
if (!active_layer)
return;
auto layer_index = editor->image().index_of(*active_layer);
auto new_layer_name = editor->generate_unique_layer_name(active_layer->name());
auto duplicated_layer_or_error = active_layer->duplicate(new_layer_name);
if (duplicated_layer_or_error.is_error()) {
GUI::MessageBox::show_error(&window, MUST(String::formatted("Unable to create duplicate layer: {}", duplicated_layer_or_error.release_error())));
return;
}
auto duplicated_layer = duplicated_layer_or_error.release_value();
editor->image().insert_layer(duplicated_layer, layer_index + 1);
editor->image().select_layer(duplicated_layer);
editor->layers_did_change();
editor->did_complete_action("Duplicate Layer"sv);
})));
m_layer_via_copy = GUI::Action::create(
"Layer via Copy", { Mod_Ctrl | Mod_Shift, Key_C }, g_icon_bag.new_layer, [&](auto&) {
auto add_layer_success = current_image_editor()->add_new_layer_from_selection();