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:
parent
55feecee36
commit
3faf089be5
7 changed files with 83 additions and 3 deletions
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -102,6 +102,8 @@ public:
|
|||
|
||||
Gfx::Bitmap& currently_edited_bitmap();
|
||||
|
||||
ErrorOr<NonnullRefPtr<Layer>> duplicate(DeprecatedString name);
|
||||
|
||||
private:
|
||||
Layer(Image&, NonnullRefPtr<Gfx::Bitmap>, DeprecatedString name);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue