diff --git a/Userland/Applications/PixelPaint/Image.cpp b/Userland/Applications/PixelPaint/Image.cpp index 8c6a979ea4..90359033ef 100644 --- a/Userland/Applications/PixelPaint/Image.cpp +++ b/Userland/Applications/PixelPaint/Image.cpp @@ -200,19 +200,30 @@ ErrorOr Image::export_qoi_to_file(NonnullOwnPtr stream) const return {}; } -void Image::add_layer(NonnullRefPtr layer) +void Image::insert_layer(NonnullRefPtr 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) +{ + insert_layer(move(layer), m_layers.size()); +} + ErrorOr> Image::take_snapshot() const { auto snapshot = TRY(create_with_size(m_size)); diff --git a/Userland/Applications/PixelPaint/Image.h b/Userland/Applications/PixelPaint/Image.h index 07c1c7e754..37f3f3b733 100644 --- a/Userland/Applications/PixelPaint/Image.h +++ b/Userland/Applications/PixelPaint/Image.h @@ -65,6 +65,7 @@ public: Gfx::IntRect rect() const { return { {}, m_size }; } void add_layer(NonnullRefPtr); + void insert_layer(NonnullRefPtr, size_t index); ErrorOr> take_snapshot() const; ErrorOr restore_snapshot(Image const&); @@ -124,6 +125,8 @@ private: ErrorOr merge_layers(LayerMergeMode); ErrorOr merge_active_layer(NonnullRefPtr const&, LayerMergeDirection); + DeprecatedString generate_unique_layer_name(DeprecatedString const& original_name); + Gfx::IntSize m_size; Vector> m_layers; diff --git a/Userland/Applications/PixelPaint/ImageEditor.cpp b/Userland/Applications/PixelPaint/ImageEditor.cpp index c87690e5f7..b37e47ac2f 100644 --- a/Userland/Applications/PixelPaint/ImageEditor.cpp +++ b/Userland/Applications/PixelPaint/ImageEditor.cpp @@ -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(); +} + } diff --git a/Userland/Applications/PixelPaint/ImageEditor.h b/Userland/Applications/PixelPaint/ImageEditor.h index c8228fc2aa..a349ecb323 100644 --- a/Userland/Applications/PixelPaint/ImageEditor.h +++ b/Userland/Applications/PixelPaint/ImageEditor.h @@ -127,6 +127,7 @@ public: Function 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); diff --git a/Userland/Applications/PixelPaint/Layer.cpp b/Userland/Applications/PixelPaint/Layer.cpp index 88e811f747..1c3d495b79 100644 --- a/Userland/Applications/PixelPaint/Layer.cpp +++ b/Userland/Applications/PixelPaint/Layer.cpp @@ -405,4 +405,12 @@ Optional Layer::nonempty_content_bounding_rect() const }; } +ErrorOr> 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; +} + } diff --git a/Userland/Applications/PixelPaint/Layer.h b/Userland/Applications/PixelPaint/Layer.h index 684004606c..37ffe37364 100644 --- a/Userland/Applications/PixelPaint/Layer.h +++ b/Userland/Applications/PixelPaint/Layer.h @@ -102,6 +102,8 @@ public: Gfx::Bitmap& currently_edited_bitmap(); + ErrorOr> duplicate(DeprecatedString name); + private: Layer(Image&, NonnullRefPtr, DeprecatedString name); diff --git a/Userland/Applications/PixelPaint/MainWidget.cpp b/Userland/Applications/PixelPaint/MainWidget.cpp index 13d25cac84..c7c0479ff1 100644 --- a/Userland/Applications/PixelPaint/MainWidget.cpp +++ b/Userland/Applications/PixelPaint/MainWidget.cpp @@ -724,6 +724,28 @@ ErrorOr 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();