1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-28 10:15:07 +00:00
serenity/Userland/Applications/PixelPaint/ImageEditor.cpp
Andreas Kling 161568103e LibGUI: Remove UndoStack's automatic command combo'ing
UndoStack will now merge adjacent commands *if they want to be merged*
instead of bundling everything you push onto it until you tell it
to "finalize the combo."

This uses less memory and gives applications full control over how
their undo stacks end up. :^)
2021-05-08 22:17:51 +02:00

425 lines
11 KiB
C++

/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "ImageEditor.h"
#include "Image.h"
#include "Layer.h"
#include "MoveTool.h"
#include "Tool.h"
#include <LibGUI/Command.h>
#include <LibGUI/Painter.h>
#include <LibGfx/Palette.h>
#include <LibGfx/Rect.h>
namespace PixelPaint {
ImageEditor::ImageEditor()
: m_undo_stack(make<GUI::UndoStack>())
{
set_focus_policy(GUI::FocusPolicy::StrongFocus);
}
ImageEditor::~ImageEditor()
{
if (m_image)
m_image->remove_client(*this);
}
void ImageEditor::set_image(RefPtr<Image> image)
{
if (m_image)
m_image->remove_client(*this);
m_image = move(image);
m_active_layer = nullptr;
m_undo_stack = make<GUI::UndoStack>();
m_undo_stack->push(make<ImageUndoCommand>(*m_image));
update();
relayout();
if (m_image)
m_image->add_client(*this);
}
void ImageEditor::did_complete_action()
{
if (!m_image)
return;
m_undo_stack->push(make<ImageUndoCommand>(*m_image));
}
bool ImageEditor::undo()
{
if (!m_image)
return false;
if (m_undo_stack->can_undo()) {
m_undo_stack->undo();
layers_did_change();
return true;
}
return false;
}
bool ImageEditor::redo()
{
if (!m_image)
return false;
if (m_undo_stack->can_redo()) {
m_undo_stack->redo();
layers_did_change();
return true;
}
return false;
}
void ImageEditor::paint_event(GUI::PaintEvent& event)
{
GUI::Frame::paint_event(event);
GUI::Painter painter(*this);
painter.add_clip_rect(event.rect());
painter.add_clip_rect(frame_inner_rect());
Gfx::StylePainter::paint_transparency_grid(painter, rect(), palette());
if (m_image) {
painter.draw_rect(m_editor_image_rect.inflated(2, 2), Color::Black);
m_image->paint_into(painter, m_editor_image_rect);
}
if (m_active_layer) {
painter.draw_rect(enclosing_int_rect(image_rect_to_editor_rect(m_active_layer->relative_rect())).inflated(2, 2), Color::Black);
}
}
Gfx::FloatRect ImageEditor::layer_rect_to_editor_rect(const Layer& layer, const Gfx::IntRect& layer_rect) const
{
return image_rect_to_editor_rect(layer_rect.translated(layer.location()));
}
Gfx::FloatRect ImageEditor::image_rect_to_editor_rect(const Gfx::IntRect& image_rect) const
{
Gfx::FloatRect editor_rect;
editor_rect.set_location(image_position_to_editor_position(image_rect.location()));
editor_rect.set_width((float)image_rect.width() * m_scale);
editor_rect.set_height((float)image_rect.height() * m_scale);
return editor_rect;
}
Gfx::FloatRect ImageEditor::editor_rect_to_image_rect(const Gfx::IntRect& editor_rect) const
{
Gfx::FloatRect image_rect;
image_rect.set_location(editor_position_to_image_position(editor_rect.location()));
image_rect.set_width((float)editor_rect.width() / m_scale);
image_rect.set_height((float)editor_rect.height() / m_scale);
return image_rect;
}
Gfx::FloatPoint ImageEditor::layer_position_to_editor_position(const Layer& layer, const Gfx::IntPoint& layer_position) const
{
return image_position_to_editor_position(layer_position.translated(layer.location()));
}
Gfx::FloatPoint ImageEditor::image_position_to_editor_position(const Gfx::IntPoint& image_position) const
{
Gfx::FloatPoint editor_position;
editor_position.set_x(m_editor_image_rect.x() + ((float)image_position.x() * m_scale));
editor_position.set_y(m_editor_image_rect.y() + ((float)image_position.y() * m_scale));
return editor_position;
}
Gfx::FloatPoint ImageEditor::editor_position_to_image_position(const Gfx::IntPoint& editor_position) const
{
Gfx::FloatPoint image_position;
image_position.set_x(((float)editor_position.x() - m_editor_image_rect.x()) / m_scale);
image_position.set_y(((float)editor_position.y() - m_editor_image_rect.y()) / m_scale);
return image_position;
}
void ImageEditor::second_paint_event(GUI::PaintEvent& event)
{
if (m_active_tool && m_active_layer)
m_active_tool->on_second_paint(*m_active_layer, event);
}
GUI::MouseEvent ImageEditor::event_with_pan_and_scale_applied(const GUI::MouseEvent& event) const
{
auto image_position = editor_position_to_image_position(event.position());
return {
static_cast<GUI::Event::Type>(event.type()),
Gfx::IntPoint(image_position.x(), image_position.y()),
event.buttons(),
event.button(),
event.modifiers(),
event.wheel_delta()
};
}
GUI::MouseEvent ImageEditor::event_adjusted_for_layer(const GUI::MouseEvent& event, const Layer& layer) const
{
auto image_position = editor_position_to_image_position(event.position());
image_position.translate_by(-layer.location().x(), -layer.location().y());
return {
static_cast<GUI::Event::Type>(event.type()),
Gfx::IntPoint(image_position.x(), image_position.y()),
event.buttons(),
event.button(),
event.modifiers(),
event.wheel_delta()
};
}
void ImageEditor::mousedown_event(GUI::MouseEvent& event)
{
if (event.button() == GUI::MouseButton::Middle) {
m_click_position = event.position();
m_saved_pan_origin = m_pan_origin;
return;
}
if (!m_active_tool)
return;
if (is<MoveTool>(*m_active_tool)) {
if (auto* other_layer = layer_at_editor_position(event.position())) {
set_active_layer(other_layer);
}
}
if (!m_active_layer)
return;
auto layer_event = event_adjusted_for_layer(event, *m_active_layer);
auto image_event = event_with_pan_and_scale_applied(event);
m_active_tool->on_mousedown(*m_active_layer, layer_event, image_event);
}
void ImageEditor::mousemove_event(GUI::MouseEvent& event)
{
if (event.buttons() & GUI::MouseButton::Middle) {
auto delta = event.position() - m_click_position;
m_pan_origin = m_saved_pan_origin.translated(
-delta.x() / m_scale,
-delta.y() / m_scale);
relayout();
return;
}
if (!m_active_layer || !m_active_tool)
return;
auto layer_event = event_adjusted_for_layer(event, *m_active_layer);
auto image_event = event_with_pan_and_scale_applied(event);
m_active_tool->on_mousemove(*m_active_layer, layer_event, image_event);
}
void ImageEditor::mouseup_event(GUI::MouseEvent& event)
{
if (!m_active_layer || !m_active_tool)
return;
auto layer_event = event_adjusted_for_layer(event, *m_active_layer);
auto image_event = event_with_pan_and_scale_applied(event);
m_active_tool->on_mouseup(*m_active_layer, layer_event, image_event);
}
void ImageEditor::mousewheel_event(GUI::MouseEvent& event)
{
auto scale_delta = -event.wheel_delta() * 0.1f;
scale_centered_on_position(event.position(), scale_delta);
}
void ImageEditor::context_menu_event(GUI::ContextMenuEvent& event)
{
if (!m_active_layer || !m_active_tool)
return;
m_active_tool->on_context_menu(*m_active_layer, event);
}
void ImageEditor::resize_event(GUI::ResizeEvent& event)
{
relayout();
GUI::Frame::resize_event(event);
}
void ImageEditor::keydown_event(GUI::KeyEvent& event)
{
if (m_active_tool)
m_active_tool->on_keydown(event);
}
void ImageEditor::keyup_event(GUI::KeyEvent& event)
{
if (m_active_tool)
m_active_tool->on_keyup(event);
}
void ImageEditor::set_active_layer(Layer* layer)
{
if (m_active_layer == layer)
return;
m_active_layer = layer;
if (m_active_layer) {
size_t index = 0;
for (; index < m_image->layer_count(); ++index) {
if (&m_image->layer(index) == layer)
break;
}
if (on_active_layer_change)
on_active_layer_change(layer);
} else {
if (on_active_layer_change)
on_active_layer_change({});
}
layers_did_change();
}
void ImageEditor::set_active_tool(Tool* tool)
{
if (m_active_tool == tool)
return;
if (m_active_tool)
m_active_tool->clear();
m_active_tool = tool;
if (m_active_tool)
m_active_tool->setup(*this);
}
void ImageEditor::layers_did_change()
{
update();
}
Color ImageEditor::color_for(GUI::MouseButton button) const
{
if (button == GUI::MouseButton::Left)
return m_primary_color;
if (button == GUI::MouseButton::Right)
return m_secondary_color;
VERIFY_NOT_REACHED();
}
Color ImageEditor::color_for(const GUI::MouseEvent& event) const
{
if (event.buttons() & GUI::MouseButton::Left)
return m_primary_color;
if (event.buttons() & GUI::MouseButton::Right)
return m_secondary_color;
VERIFY_NOT_REACHED();
}
void ImageEditor::set_primary_color(Color color)
{
if (m_primary_color == color)
return;
m_primary_color = color;
if (on_primary_color_change)
on_primary_color_change(color);
}
void ImageEditor::set_secondary_color(Color color)
{
if (m_secondary_color == color)
return;
m_secondary_color = color;
if (on_secondary_color_change)
on_secondary_color_change(color);
}
Layer* ImageEditor::layer_at_editor_position(const Gfx::IntPoint& editor_position)
{
if (!m_image)
return nullptr;
auto image_position = editor_position_to_image_position(editor_position);
for (ssize_t i = m_image->layer_count() - 1; i >= 0; --i) {
auto& layer = m_image->layer(i);
if (!layer.is_visible())
continue;
if (layer.relative_rect().contains(Gfx::IntPoint(image_position.x(), image_position.y())))
return const_cast<Layer*>(&layer);
}
return nullptr;
}
void ImageEditor::clamped_scale(float scale_delta)
{
m_scale += scale_delta;
if (m_scale < 0.1f)
m_scale = 0.1f;
if (m_scale > 100.0f)
m_scale = 100.0f;
}
void ImageEditor::scale_centered_on_position(const Gfx::IntPoint& position, float scale_delta)
{
auto old_scale = m_scale;
clamped_scale(scale_delta);
Gfx::FloatPoint focus_point {
m_pan_origin.x() - (position.x() - width() / 2.0f) / old_scale,
m_pan_origin.y() - (position.y() - height() / 2.0f) / old_scale
};
m_pan_origin = Gfx::FloatPoint(
focus_point.x() - m_scale / old_scale * (focus_point.x() - m_pan_origin.x()),
focus_point.y() - m_scale / old_scale * (focus_point.y() - m_pan_origin.y()));
if (old_scale != m_scale)
relayout();
}
void ImageEditor::scale_by(float scale_delta)
{
if (scale_delta != 0) {
clamped_scale(scale_delta);
relayout();
}
}
void ImageEditor::reset_scale_and_position()
{
if (m_scale != 1.0f)
m_scale = 1.0f;
m_pan_origin = Gfx::FloatPoint(0, 0);
relayout();
}
void ImageEditor::relayout()
{
if (!image())
return;
auto& image = *this->image();
Gfx::IntSize new_size;
new_size.set_width(image.size().width() * m_scale);
new_size.set_height(image.size().height() * m_scale);
m_editor_image_rect.set_size(new_size);
Gfx::IntPoint new_location;
new_location.set_x((width() / 2) - (new_size.width() / 2) - (m_pan_origin.x() * m_scale));
new_location.set_y((height() / 2) - (new_size.height() / 2) - (m_pan_origin.y() * m_scale));
m_editor_image_rect.set_location(new_location);
update();
}
void ImageEditor::image_did_change()
{
update();
}
void ImageEditor::image_select_layer(Layer* layer)
{
set_active_layer(layer);
}
}