mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 17:37:37 +00:00
PixelPaint: Move selection from ImageEditor to Image
This is preparation for making selection state undoable.
This commit is contained in:
parent
67596d9546
commit
d571159aeb
8 changed files with 70 additions and 32 deletions
|
@ -37,6 +37,7 @@ ErrorOr<NonnullRefPtr<Image>> Image::try_create_with_size(Gfx::IntSize const& si
|
||||||
|
|
||||||
Image::Image(Gfx::IntSize const& size)
|
Image::Image(Gfx::IntSize const& size)
|
||||||
: m_size(size)
|
: m_size(size)
|
||||||
|
, m_selection(*this)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Selection.h"
|
||||||
#include <AK/HashTable.h>
|
#include <AK/HashTable.h>
|
||||||
#include <AK/JsonObjectSerializer.h>
|
#include <AK/JsonObjectSerializer.h>
|
||||||
#include <AK/NonnullRefPtrVector.h>
|
#include <AK/NonnullRefPtrVector.h>
|
||||||
|
@ -55,6 +56,9 @@ public:
|
||||||
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> try_compose_bitmap(Gfx::BitmapFormat format) const;
|
ErrorOr<NonnullRefPtr<Gfx::Bitmap>> try_compose_bitmap(Gfx::BitmapFormat format) const;
|
||||||
RefPtr<Gfx::Bitmap> try_copy_bitmap(Selection const&) const;
|
RefPtr<Gfx::Bitmap> try_copy_bitmap(Selection const&) const;
|
||||||
|
|
||||||
|
Selection& selection() { return m_selection; }
|
||||||
|
Selection const& selection() const { return m_selection; }
|
||||||
|
|
||||||
size_t layer_count() const { return m_layers.size(); }
|
size_t layer_count() const { return m_layers.size(); }
|
||||||
Layer const& layer(size_t index) const { return m_layers.at(index); }
|
Layer const& layer(size_t index) const { return m_layers.at(index); }
|
||||||
Layer& layer(size_t index) { return m_layers.at(index); }
|
Layer& layer(size_t index) { return m_layers.at(index); }
|
||||||
|
@ -114,6 +118,8 @@ private:
|
||||||
NonnullRefPtrVector<Layer> m_layers;
|
NonnullRefPtrVector<Layer> m_layers;
|
||||||
|
|
||||||
HashTable<ImageClient*> m_clients;
|
HashTable<ImageClient*> m_clients;
|
||||||
|
|
||||||
|
Selection m_selection;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ImageUndoCommand : public GUI::Command {
|
class ImageUndoCommand : public GUI::Command {
|
||||||
|
|
|
@ -30,11 +30,11 @@ constexpr int marching_ant_length = 4;
|
||||||
ImageEditor::ImageEditor(NonnullRefPtr<Image> image)
|
ImageEditor::ImageEditor(NonnullRefPtr<Image> image)
|
||||||
: m_image(move(image))
|
: m_image(move(image))
|
||||||
, m_title("Untitled")
|
, m_title("Untitled")
|
||||||
, m_selection(*this)
|
|
||||||
{
|
{
|
||||||
set_focus_policy(GUI::FocusPolicy::StrongFocus);
|
set_focus_policy(GUI::FocusPolicy::StrongFocus);
|
||||||
m_undo_stack.push(make<ImageUndoCommand>(*m_image, String()));
|
m_undo_stack.push(make<ImageUndoCommand>(*m_image, String()));
|
||||||
m_image->add_client(*this);
|
m_image->add_client(*this);
|
||||||
|
m_image->selection().add_client(*this);
|
||||||
set_original_rect(m_image->rect());
|
set_original_rect(m_image->rect());
|
||||||
set_scale_bounds(0.1f, 100.0f);
|
set_scale_bounds(0.1f, 100.0f);
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ ImageEditor::ImageEditor(NonnullRefPtr<Image> image)
|
||||||
m_marching_ants_timer = Core::Timer::create_repeating(80, [this] {
|
m_marching_ants_timer = Core::Timer::create_repeating(80, [this] {
|
||||||
++m_marching_ants_offset;
|
++m_marching_ants_offset;
|
||||||
m_marching_ants_offset %= (marching_ant_length * 2);
|
m_marching_ants_offset %= (marching_ant_length * 2);
|
||||||
if (!m_selection.is_empty() || m_selection.in_interactive_selection())
|
if (!m_image->selection().is_empty() || m_image->selection().in_interactive_selection())
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
m_marching_ants_timer->start();
|
m_marching_ants_timer->start();
|
||||||
|
@ -55,6 +55,7 @@ ImageEditor::ImageEditor(NonnullRefPtr<Image> image)
|
||||||
|
|
||||||
ImageEditor::~ImageEditor()
|
ImageEditor::~ImageEditor()
|
||||||
{
|
{
|
||||||
|
m_image->selection().remove_client(*this);
|
||||||
m_image->remove_client(*this);
|
m_image->remove_client(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,8 +373,8 @@ void ImageEditor::context_menu_event(GUI::ContextMenuEvent& event)
|
||||||
|
|
||||||
void ImageEditor::keydown_event(GUI::KeyEvent& event)
|
void ImageEditor::keydown_event(GUI::KeyEvent& event)
|
||||||
{
|
{
|
||||||
if (event.key() == Key_Delete && !selection().is_empty() && active_layer()) {
|
if (event.key() == Key_Delete && !m_image->selection().is_empty() && active_layer()) {
|
||||||
active_layer()->erase_selection(selection());
|
active_layer()->erase_selection(m_image->selection());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,10 +663,10 @@ void ImageEditor::set_loaded_from_image(bool loaded_from_image)
|
||||||
|
|
||||||
void ImageEditor::paint_selection(Gfx::Painter& painter)
|
void ImageEditor::paint_selection(Gfx::Painter& painter)
|
||||||
{
|
{
|
||||||
if (m_selection.is_empty())
|
if (m_image->selection().is_empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
draw_marching_ants(painter, m_selection.mask());
|
draw_marching_ants(painter, m_image->selection().mask());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageEditor::draw_marching_ants(Gfx::Painter& painter, Gfx::IntRect const& rect) const
|
void ImageEditor::draw_marching_ants(Gfx::Painter& painter, Gfx::IntRect const& rect) const
|
||||||
|
@ -751,4 +752,9 @@ void ImageEditor::draw_marching_ants_pixel(Gfx::Painter& painter, int x, int y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImageEditor::selection_did_change()
|
||||||
|
{
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,8 @@ class Tool;
|
||||||
|
|
||||||
class ImageEditor final
|
class ImageEditor final
|
||||||
: public GUI::AbstractZoomPanWidget
|
: public GUI::AbstractZoomPanWidget
|
||||||
, public ImageClient {
|
, public ImageClient
|
||||||
|
, public SelectionClient {
|
||||||
C_OBJECT(ImageEditor);
|
C_OBJECT(ImageEditor);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -71,9 +72,6 @@ public:
|
||||||
Color secondary_color() const { return m_secondary_color; }
|
Color secondary_color() const { return m_secondary_color; }
|
||||||
void set_secondary_color(Color);
|
void set_secondary_color(Color);
|
||||||
|
|
||||||
Selection& selection() { return m_selection; }
|
|
||||||
Selection const& selection() const { return m_selection; }
|
|
||||||
|
|
||||||
Color color_for(GUI::MouseEvent const&) const;
|
Color color_for(GUI::MouseEvent const&) const;
|
||||||
Color color_for(GUI::MouseButton) const;
|
Color color_for(GUI::MouseButton) const;
|
||||||
|
|
||||||
|
@ -134,6 +132,8 @@ private:
|
||||||
virtual void image_did_change_rect(Gfx::IntRect const&) override;
|
virtual void image_did_change_rect(Gfx::IntRect const&) override;
|
||||||
virtual void image_select_layer(Layer*) override;
|
virtual void image_select_layer(Layer*) override;
|
||||||
|
|
||||||
|
virtual void selection_did_change() override;
|
||||||
|
|
||||||
GUI::MouseEvent event_adjusted_for_layer(GUI::MouseEvent const&, Layer const&) const;
|
GUI::MouseEvent event_adjusted_for_layer(GUI::MouseEvent const&, Layer const&) const;
|
||||||
GUI::MouseEvent event_with_pan_and_scale_applied(GUI::MouseEvent const&) const;
|
GUI::MouseEvent event_with_pan_and_scale_applied(GUI::MouseEvent const&) const;
|
||||||
|
|
||||||
|
@ -173,8 +173,6 @@ private:
|
||||||
|
|
||||||
Variant<Gfx::StandardCursor, NonnullRefPtr<Gfx::Bitmap>> m_active_cursor { Gfx::StandardCursor::None };
|
Variant<Gfx::StandardCursor, NonnullRefPtr<Gfx::Bitmap>> m_active_cursor { Gfx::StandardCursor::None };
|
||||||
|
|
||||||
Selection m_selection;
|
|
||||||
|
|
||||||
bool m_loaded_from_image { true };
|
bool m_loaded_from_image { true };
|
||||||
|
|
||||||
RefPtr<Core::Timer> m_marching_ants_timer;
|
RefPtr<Core::Timer> m_marching_ants_timer;
|
||||||
|
|
|
@ -267,13 +267,13 @@ void MainWidget::initialize_menubar(GUI::Window& window)
|
||||||
dbgln("Cannot cut with no active layer selected");
|
dbgln("Cannot cut with no active layer selected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto bitmap = editor->active_layer()->try_copy_bitmap(editor->selection());
|
auto bitmap = editor->active_layer()->try_copy_bitmap(editor->image().selection());
|
||||||
if (!bitmap) {
|
if (!bitmap) {
|
||||||
dbgln("try_copy_bitmap() from Layer failed");
|
dbgln("try_copy_bitmap() from Layer failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GUI::Clipboard::the().set_bitmap(*bitmap);
|
GUI::Clipboard::the().set_bitmap(*bitmap);
|
||||||
editor->active_layer()->erase_selection(editor->selection());
|
editor->active_layer()->erase_selection(editor->image().selection());
|
||||||
});
|
});
|
||||||
|
|
||||||
m_copy_action = GUI::CommonActions::make_copy_action([&](auto&) {
|
m_copy_action = GUI::CommonActions::make_copy_action([&](auto&) {
|
||||||
|
@ -284,7 +284,7 @@ void MainWidget::initialize_menubar(GUI::Window& window)
|
||||||
dbgln("Cannot copy with no active layer selected");
|
dbgln("Cannot copy with no active layer selected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto bitmap = editor->active_layer()->try_copy_bitmap(editor->selection());
|
auto bitmap = editor->active_layer()->try_copy_bitmap(editor->image().selection());
|
||||||
if (!bitmap) {
|
if (!bitmap) {
|
||||||
dbgln("try_copy_bitmap() from Layer failed");
|
dbgln("try_copy_bitmap() from Layer failed");
|
||||||
return;
|
return;
|
||||||
|
@ -297,7 +297,7 @@ void MainWidget::initialize_menubar(GUI::Window& window)
|
||||||
auto* editor = current_image_editor();
|
auto* editor = current_image_editor();
|
||||||
VERIFY(editor);
|
VERIFY(editor);
|
||||||
|
|
||||||
auto bitmap = editor->image().try_copy_bitmap(editor->selection());
|
auto bitmap = editor->image().try_copy_bitmap(editor->image().selection());
|
||||||
if (!bitmap) {
|
if (!bitmap) {
|
||||||
dbgln("try_copy_bitmap() from Image failed");
|
dbgln("try_copy_bitmap() from Image failed");
|
||||||
return;
|
return;
|
||||||
|
@ -319,7 +319,7 @@ void MainWidget::initialize_menubar(GUI::Window& window)
|
||||||
auto layer = PixelPaint::Layer::try_create_with_bitmap(editor->image(), *bitmap, "Pasted layer").release_value_but_fixme_should_propagate_errors();
|
auto layer = PixelPaint::Layer::try_create_with_bitmap(editor->image(), *bitmap, "Pasted layer").release_value_but_fixme_should_propagate_errors();
|
||||||
editor->image().add_layer(*layer);
|
editor->image().add_layer(*layer);
|
||||||
editor->set_active_layer(layer);
|
editor->set_active_layer(layer);
|
||||||
editor->selection().clear();
|
editor->image().selection().clear();
|
||||||
});
|
});
|
||||||
GUI::Clipboard::the().on_change = [&](auto& mime_type) {
|
GUI::Clipboard::the().on_change = [&](auto& mime_type) {
|
||||||
m_paste_action->set_enabled(mime_type == "image/x-serenityos");
|
m_paste_action->set_enabled(mime_type == "image/x-serenityos");
|
||||||
|
@ -351,13 +351,13 @@ void MainWidget::initialize_menubar(GUI::Window& window)
|
||||||
VERIFY(editor);
|
VERIFY(editor);
|
||||||
if (!editor->active_layer())
|
if (!editor->active_layer())
|
||||||
return;
|
return;
|
||||||
editor->selection().merge(editor->active_layer()->relative_rect(), PixelPaint::Selection::MergeMode::Set);
|
editor->image().selection().merge(editor->active_layer()->relative_rect(), PixelPaint::Selection::MergeMode::Set);
|
||||||
}));
|
}));
|
||||||
m_edit_menu->add_action(GUI::Action::create(
|
m_edit_menu->add_action(GUI::Action::create(
|
||||||
"Clear &Selection", g_icon_bag.clear_selection, [&](auto&) {
|
"Clear &Selection", g_icon_bag.clear_selection, [&](auto&) {
|
||||||
auto* editor = current_image_editor();
|
auto* editor = current_image_editor();
|
||||||
VERIFY(editor);
|
VERIFY(editor);
|
||||||
editor->selection().clear();
|
editor->image().selection().clear();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
m_edit_menu->add_separator();
|
m_edit_menu->add_separator();
|
||||||
|
@ -552,11 +552,11 @@ void MainWidget::initialize_menubar(GUI::Window& window)
|
||||||
auto* editor = current_image_editor();
|
auto* editor = current_image_editor();
|
||||||
VERIFY(editor);
|
VERIFY(editor);
|
||||||
// FIXME: disable this action if there is no selection
|
// FIXME: disable this action if there is no selection
|
||||||
if (editor->selection().is_empty())
|
if (editor->image().selection().is_empty())
|
||||||
return;
|
return;
|
||||||
auto crop_rect = editor->image().rect().intersected(editor->selection().bounding_rect());
|
auto crop_rect = editor->image().rect().intersected(editor->image().selection().bounding_rect());
|
||||||
editor->image().crop(crop_rect);
|
editor->image().crop(crop_rect);
|
||||||
editor->selection().clear();
|
editor->image().selection().clear();
|
||||||
editor->did_complete_action("Crop Image to Selection"sv);
|
editor->did_complete_action("Crop Image to Selection"sv);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,16 @@
|
||||||
|
|
||||||
namespace PixelPaint {
|
namespace PixelPaint {
|
||||||
|
|
||||||
Selection::Selection(ImageEditor& editor)
|
Selection::Selection(Image& image)
|
||||||
: m_editor(editor)
|
: m_image(image)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void Selection::clear()
|
void Selection::clear()
|
||||||
{
|
{
|
||||||
m_mask = {};
|
m_mask = {};
|
||||||
m_editor.update();
|
for (auto* client : m_clients)
|
||||||
|
client->selection_did_change();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Selection::merge(Mask const& mask, MergeMode mode)
|
void Selection::merge(Mask const& mask, MergeMode mode)
|
||||||
|
@ -41,4 +42,16 @@ void Selection::merge(Mask const& mask, MergeMode mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Selection::add_client(SelectionClient& client)
|
||||||
|
{
|
||||||
|
VERIFY(!m_clients.contains(&client));
|
||||||
|
m_clients.set(&client);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Selection::remove_client(SelectionClient& client)
|
||||||
|
{
|
||||||
|
VERIFY(m_clients.contains(&client));
|
||||||
|
m_clients.remove(&client);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,15 @@
|
||||||
|
|
||||||
namespace PixelPaint {
|
namespace PixelPaint {
|
||||||
|
|
||||||
class ImageEditor;
|
class Image;
|
||||||
|
|
||||||
|
class SelectionClient {
|
||||||
|
public:
|
||||||
|
virtual void selection_did_change() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual ~SelectionClient() = default;
|
||||||
|
};
|
||||||
|
|
||||||
// Coordinates are image-relative.
|
// Coordinates are image-relative.
|
||||||
class Selection {
|
class Selection {
|
||||||
|
@ -26,7 +34,7 @@ public:
|
||||||
__Count,
|
__Count,
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit Selection(ImageEditor&);
|
explicit Selection(Image&);
|
||||||
|
|
||||||
bool is_empty() const { return m_mask.is_null(); }
|
bool is_empty() const { return m_mask.is_null(); }
|
||||||
void clear();
|
void clear();
|
||||||
|
@ -47,9 +55,15 @@ public:
|
||||||
|
|
||||||
bool in_interactive_selection() { return m_in_interactive_selection; }
|
bool in_interactive_selection() { return m_in_interactive_selection; }
|
||||||
|
|
||||||
|
void add_client(SelectionClient&);
|
||||||
|
void remove_client(SelectionClient&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ImageEditor& m_editor;
|
Image& m_image;
|
||||||
Mask m_mask;
|
Mask m_mask;
|
||||||
|
|
||||||
|
HashTable<SelectionClient*> m_clients;
|
||||||
|
|
||||||
bool m_in_interactive_selection { false };
|
bool m_in_interactive_selection { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ void RectangleSelectTool::on_mousedown(Layer*, MouseEvent& event)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_selecting = true;
|
m_selecting = true;
|
||||||
m_editor->selection().begin_interactive_selection();
|
m_editor->image().selection().begin_interactive_selection();
|
||||||
|
|
||||||
m_selection_start = image_event.position();
|
m_selection_start = image_event.position();
|
||||||
m_selection_end = image_event.position();
|
m_selection_end = image_event.position();
|
||||||
|
@ -58,7 +58,7 @@ void RectangleSelectTool::on_mouseup(Layer*, MouseEvent& event)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_selecting = false;
|
m_selecting = false;
|
||||||
m_editor->selection().end_interactive_selection();
|
m_editor->image().selection().end_interactive_selection();
|
||||||
|
|
||||||
m_editor->update();
|
m_editor->update();
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ void RectangleSelectTool::on_mouseup(Layer*, MouseEvent& event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_editor->selection().merge(mask, m_merge_mode);
|
m_editor->image().selection().merge(mask, m_merge_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RectangleSelectTool::on_keydown(GUI::KeyEvent& key_event)
|
void RectangleSelectTool::on_keydown(GUI::KeyEvent& key_event)
|
||||||
|
@ -113,7 +113,7 @@ void RectangleSelectTool::on_keydown(GUI::KeyEvent& key_event)
|
||||||
if (m_selecting)
|
if (m_selecting)
|
||||||
m_selecting = false;
|
m_selecting = false;
|
||||||
else
|
else
|
||||||
m_editor->selection().clear();
|
m_editor->image().selection().clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue