1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 13:27:35 +00:00

PixelPaint: Allow move tool to scale in all directions

It is now possible to scale the current layer using the move tool from
all four corners of the layer boundary. Previously scaling was only
possible from the bottom right of the image.
This commit is contained in:
Tim Ledbetter 2022-12-20 04:31:45 +00:00 committed by Sam Atkins
parent 85bfeba8c6
commit a30b956e94
2 changed files with 80 additions and 35 deletions

View file

@ -33,12 +33,13 @@ void MoveTool::on_mousedown(Layer* layer, MouseEvent& event)
auto& image_event = event.image_event();
if (layer_event.button() != GUI::MouseButton::Primary)
return;
if (!layer->rect().contains(layer_event.position()) && !m_mouse_in_resize_corner)
if (!layer->rect().contains(layer_event.position()) && !m_resize_anchor_location.has_value())
return;
m_scaling = m_mouse_in_resize_corner;
m_scaling = m_resize_anchor_location.has_value();
m_layer_being_moved = *layer;
m_event_origin = image_event.position();
m_layer_origin = layer->location();
m_new_layer_rect = m_editor->active_layer()->rect();
}
void MoveTool::on_mousemove(Layer* layer, MouseEvent& event)
@ -51,37 +52,49 @@ void MoveTool::on_mousemove(Layer* layer, MouseEvent& event)
if (!layer)
return;
constexpr int sensitivity = 20;
auto bottom_right_layer_coordinates = layer->relative_rect().bottom_right().translated(1);
auto bottom_right_frame_coordinates = m_editor->content_to_frame_position(bottom_right_layer_coordinates).to_rounded<int>();
auto grab_rect_top_left = bottom_right_frame_coordinates.translated(-sensitivity / 2);
auto grab_rect = Gfx::IntRect(grab_rect_top_left, Gfx::IntSize(sensitivity, sensitivity));
auto updated_is_in_lower_right_corner = grab_rect.contains(event.raw_event().position()); // check if the mouse is in the lower right corner
if (m_mouse_in_resize_corner != updated_is_in_lower_right_corner) {
m_mouse_in_resize_corner = updated_is_in_lower_right_corner;
m_editor->update_tool_cursor();
if (!m_scaling) {
auto current_resize_anchor_location = resize_anchor_location_from_cursor_position(layer, event);
if (m_resize_anchor_location != current_resize_anchor_location) {
m_resize_anchor_location = current_resize_anchor_location;
m_editor->update_tool_cursor();
}
}
if (!m_layer_being_moved)
return;
auto cursor_position = event.image_event().position();
auto delta = cursor_position - m_event_origin;
auto rect_being_moved = m_layer_being_moved->relative_rect();
Gfx::IntPoint scaling_origin;
Gfx::IntPoint opposite_corner;
if (m_scaling) {
auto cursor_location = event.image_event().position();
auto width = abs(m_layer_being_moved->location().x() - cursor_location.x());
auto height = abs(m_layer_being_moved->location().y() - cursor_location.y());
if (m_keep_ascept_ratio) {
if (abs(width - m_layer_being_moved->size().width()) > abs(height - m_layer_being_moved->size().height())) {
height = width * m_layer_being_moved->size().height() / m_layer_being_moved->size().width();
} else {
width = height * m_layer_being_moved->size().width() / m_layer_being_moved->size().height();
}
VERIFY(m_resize_anchor_location.has_value());
switch (m_resize_anchor_location.value()) {
case ResizeAnchorLocation::TopLeft:
scaling_origin = rect_being_moved.top_left();
opposite_corner = rect_being_moved.bottom_right().translated(1, 1);
break;
case ResizeAnchorLocation::BottomRight:
scaling_origin = rect_being_moved.bottom_right().translated(1, 1);
opposite_corner = rect_being_moved.top_left();
break;
case ResizeAnchorLocation::BottomLeft:
scaling_origin = rect_being_moved.bottom_left().translated(0, 1);
opposite_corner = rect_being_moved.top_right().translated(1, 0);
break;
case ResizeAnchorLocation::TopRight:
scaling_origin = rect_being_moved.top_right().translated(1, 0);
opposite_corner = rect_being_moved.bottom_left().translated(0, 1);
break;
}
m_new_layer_size = Gfx::IntSize(width, height);
// TODO: Change this according to which direction the user is scaling
m_new_scaled_layer_location = m_layer_being_moved->location();
scaling_origin.translate_by(delta);
if (m_keep_ascept_ratio) {
auto aspect_ratio = m_layer_being_moved->size().aspect_ratio();
scaling_origin = opposite_corner.end_point_for_aspect_ratio(scaling_origin, aspect_ratio);
}
m_new_layer_rect = Gfx::IntRect::from_two_points(scaling_origin, opposite_corner);
} else {
auto& image_event = event.image_event();
auto delta = image_event.position() - m_event_origin;
m_layer_being_moved->set_location(m_layer_origin.translated(delta));
}
m_editor->update();
@ -103,7 +116,7 @@ void MoveTool::on_mouseup(Layer* layer, MouseEvent& event)
return;
if (m_scaling) {
auto resized_or_error = m_editor->active_layer()->resize(m_new_layer_size, m_new_scaled_layer_location, Gfx::Painter::ScalingMode::BilinearBlend);
auto resized_or_error = m_editor->active_layer()->resize(m_new_layer_rect, Gfx::Painter::ScalingMode::BilinearBlend);
if (resized_or_error.is_error())
GUI::MessageBox::show_error(m_editor->window(), MUST(String::formatted("Failed to resize layer: {}", resized_or_error.error().string_literal())));
else
@ -169,10 +182,7 @@ void MoveTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event)
GUI::Painter painter(*m_editor);
painter.add_clip_rect(event.rect());
painter.translate(editor_layer_location(*layer));
auto dst_rect = Gfx::IntRect(Gfx::IntPoint(0, 0), m_scaling ? m_new_layer_size : layer->size());
auto rect_in_editor = m_editor->content_to_frame_rect(dst_rect).to_rounded<int>();
auto rect_in_editor = m_editor->content_to_frame_rect(m_new_layer_rect).to_rounded<int>();
painter.draw_rect(rect_in_editor, Color::Black);
if (!m_cached_preview_bitmap.is_null() || !update_cached_preview_bitmap(layer).is_error()) {
painter.add_clip_rect(m_editor->content_rect());
@ -194,10 +204,39 @@ ErrorOr<void> MoveTool::update_cached_preview_bitmap(Layer const* layer)
return {};
}
Optional<ResizeAnchorLocation const> MoveTool::resize_anchor_location_from_cursor_position(Layer const* layer, MouseEvent& event)
{
auto cursor_within_resize_anchor_rect = [&](Gfx::IntPoint layer_position) {
constexpr int sensitivity = 20;
auto resize_anchor_rect_center = m_editor->content_to_frame_position(layer_position).to_rounded<int>();
auto resize_anchor_rect_top_left = resize_anchor_rect_center.translated(-sensitivity / 2);
auto resize_anchor_rect = Gfx::IntRect(resize_anchor_rect_top_left, Gfx::IntSize(sensitivity, sensitivity));
return resize_anchor_rect.contains(event.raw_event().position());
};
auto layer_rect = layer->relative_rect();
if (cursor_within_resize_anchor_rect(layer_rect.top_left()))
return ResizeAnchorLocation::TopLeft;
if (cursor_within_resize_anchor_rect(layer_rect.top_right().translated(1, 0)))
return ResizeAnchorLocation::TopRight;
if (cursor_within_resize_anchor_rect(layer_rect.bottom_left().translated(0, 1)))
return ResizeAnchorLocation::BottomLeft;
if (cursor_within_resize_anchor_rect(layer_rect.bottom_right().translated(1)))
return ResizeAnchorLocation::BottomRight;
return {};
}
Variant<Gfx::StandardCursor, NonnullRefPtr<Gfx::Bitmap>> MoveTool::cursor()
{
if (m_mouse_in_resize_corner || m_scaling)
return Gfx::StandardCursor::ResizeDiagonalTLBR;
if (m_resize_anchor_location.has_value()) {
switch (m_resize_anchor_location.value()) {
case ResizeAnchorLocation::TopLeft:
case ResizeAnchorLocation::BottomRight:
return Gfx::StandardCursor::ResizeDiagonalTLBR;
case ResizeAnchorLocation::BottomLeft:
case ResizeAnchorLocation::TopRight:
return Gfx::StandardCursor::ResizeDiagonalBLTR;
}
}
return Gfx::StandardCursor::Move;
}

View file

@ -12,6 +12,12 @@
namespace PixelPaint {
enum class ResizeAnchorLocation {
BottomLeft,
BottomRight,
TopLeft,
TopRight
};
class MoveTool final : public Tool {
public:
MoveTool() = default;
@ -28,14 +34,14 @@ public:
private:
virtual StringView tool_name() const override { return "Move Tool"sv; }
ErrorOr<void> update_cached_preview_bitmap(Layer const* layer);
Optional<ResizeAnchorLocation const> resize_anchor_location_from_cursor_position(Layer const*, MouseEvent&);
RefPtr<Layer> m_layer_being_moved;
Gfx::IntPoint m_event_origin;
Gfx::IntPoint m_layer_origin;
Gfx::IntPoint m_new_scaled_layer_location;
Gfx::IntSize m_new_layer_size;
Gfx::IntRect m_new_layer_rect;
bool m_scaling { false };
bool m_mouse_in_resize_corner { false };
Optional<ResizeAnchorLocation const> m_resize_anchor_location {};
bool m_keep_ascept_ratio { false };
RefPtr<Gfx::Bitmap> m_cached_preview_bitmap { nullptr };