1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-20 13:45:06 +00:00
serenity/Userland/Applications/PixelPaint/LayerListWidget.cpp
Mustafa Quraish 285a888b61 PixelPaint: Fix layer dragging bug
A previous commit I made broke layer dragging since the hole_index
was always being computed with respect to the top of the layer list
widget, however we were now drawing layers from the bottom. When
you didn't have enough layers to fill up the full height, dragging
them around would be weird.

This patch computes the hole index correctly using the same offset
we start drawing from, and fixes the behavior.
2021-09-07 00:48:48 +02:00

363 lines
11 KiB
C++

/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Mustafa Quraish <mustafa@cs.toronto.edu>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "LayerListWidget.h"
#include "Image.h"
#include "ImageEditor.h"
#include "Layer.h"
#include <LibGUI/Painter.h>
#include <LibGfx/Palette.h>
REGISTER_WIDGET(PixelPaint, LayerListWidget);
namespace PixelPaint {
LayerListWidget::LayerListWidget()
{
set_should_hide_unnecessary_scrollbars(false);
horizontal_scrollbar().set_visible(false);
}
LayerListWidget::~LayerListWidget()
{
if (m_image)
m_image->remove_client(*this);
}
size_t LayerListWidget::to_gadget_index(size_t layer_index) const
{
return m_image->layer_count() - layer_index - 1;
}
size_t LayerListWidget::to_layer_index(size_t gadget_index) const
{
return m_image->layer_count() - gadget_index - 1;
}
void LayerListWidget::set_image(Image* image)
{
if (m_image == image)
return;
if (m_image)
m_image->remove_client(*this);
m_image = image;
if (m_image)
m_image->add_client(*this);
rebuild_gadgets();
}
void LayerListWidget::rebuild_gadgets()
{
m_gadgets.clear();
if (m_image) {
for (int layer_index = m_image->layer_count() - 1; layer_index >= 0; --layer_index) {
m_gadgets.append({ static_cast<size_t>(layer_index), {}, false, {} });
}
}
relayout_gadgets();
}
void LayerListWidget::resize_event(GUI::ResizeEvent& event)
{
AbstractScrollableWidget::resize_event(event);
relayout_gadgets();
}
void LayerListWidget::get_gadget_rects(Gadget const& gadget, Gfx::IntRect& outer_rect, Gfx::IntRect& thumbnail_rect, Gfx::IntRect& text_rect)
{
outer_rect = gadget.rect;
outer_rect.translate_by(0, -vertical_scrollbar().value());
outer_rect.translate_by(frame_thickness(), frame_thickness());
if (gadget.is_moving) {
outer_rect.translate_by(0, gadget.movement_delta.y());
}
thumbnail_rect = { outer_rect.x(), outer_rect.y(), outer_rect.height(), outer_rect.height() };
thumbnail_rect.shrink(8, 8);
text_rect = { thumbnail_rect.right() + 10, outer_rect.y(), outer_rect.width(), outer_rect.height() };
text_rect.intersect(outer_rect);
}
void LayerListWidget::paint_event(GUI::PaintEvent& event)
{
GUI::Painter painter(*this);
painter.add_clip_rect(event.rect());
painter.fill_rect(event.rect(), palette().button());
if (!m_image)
return;
painter.fill_rect(event.rect(), palette().button());
auto paint_gadget = [&](auto& gadget) {
auto& layer = m_image->layer(gadget.layer_index);
Gfx::IntRect adjusted_rect;
Gfx::IntRect thumbnail_rect;
Gfx::IntRect text_rect;
get_gadget_rects(gadget, adjusted_rect, thumbnail_rect, text_rect);
if (gadget.is_moving) {
painter.fill_rect(adjusted_rect, palette().selection().lightened(1.5f));
} else if (layer.is_selected()) {
painter.fill_rect(adjusted_rect, palette().selection());
}
painter.draw_rect(adjusted_rect, palette().color(ColorRole::BaseText));
painter.draw_scaled_bitmap(thumbnail_rect, layer.bitmap(), layer.bitmap().rect());
if (layer.is_visible()) {
painter.draw_text(text_rect, layer.name(), Gfx::TextAlignment::CenterLeft, layer.is_selected() ? palette().selection_text() : palette().button_text());
painter.draw_rect(thumbnail_rect, palette().color(ColorRole::BaseText));
} else {
painter.draw_text(text_rect, layer.name(), Gfx::TextAlignment::CenterLeft, palette().color(ColorRole::DisabledText));
painter.draw_rect(thumbnail_rect, palette().color(ColorRole::DisabledText));
}
};
for (auto& gadget : m_gadgets) {
if (!gadget.is_moving)
paint_gadget(gadget);
}
if (m_moving_gadget_index.has_value())
paint_gadget(m_gadgets[m_moving_gadget_index.value()]);
Gfx::StylePainter::paint_frame(painter, rect(), palette(), Gfx::FrameShape::Box, Gfx::FrameShadow::Sunken, 2);
}
Optional<size_t> LayerListWidget::gadget_at(Gfx::IntPoint const& position)
{
for (size_t i = 0; i < m_gadgets.size(); ++i) {
if (m_gadgets[i].rect.contains(position))
return i;
}
return {};
}
void LayerListWidget::mousedown_event(GUI::MouseEvent& event)
{
if (!m_image)
return;
if (event.button() != GUI::MouseButton::Left)
return;
Gfx::IntPoint translated_event_point = { 0, vertical_scrollbar().value() + event.y() };
auto maybe_gadget_index = gadget_at(translated_event_point);
if (!maybe_gadget_index.has_value())
return;
auto gadget_index = maybe_gadget_index.value();
m_moving_gadget_index = gadget_index;
m_selected_gadget_index = gadget_index;
m_moving_event_origin = translated_event_point;
auto& gadget = m_gadgets[m_moving_gadget_index.value()];
auto& layer = m_image->layer(to_layer_index(gadget_index));
set_selected_layer(&layer);
gadget.is_moving = true;
gadget.movement_delta = {};
update();
}
void LayerListWidget::mousemove_event(GUI::MouseEvent& event)
{
if (!m_image)
return;
if (!m_moving_gadget_index.has_value())
return;
Gfx::IntPoint translated_event_point = { 0, vertical_scrollbar().value() + event.y() };
auto delta = translated_event_point - m_moving_event_origin;
auto& gadget = m_gadgets[m_moving_gadget_index.value()];
VERIFY(gadget.is_moving);
gadget.movement_delta = delta;
auto adjusted_rect = gadget.rect;
adjusted_rect.translate_by(gadget.movement_delta);
scroll_into_view(adjusted_rect, false, true);
relayout_gadgets();
}
void LayerListWidget::mouseup_event(GUI::MouseEvent& event)
{
if (!m_image)
return;
if (event.button() != GUI::MouseButton::Left)
return;
if (!m_moving_gadget_index.has_value())
return;
size_t old_index = m_moving_gadget_index.value();
size_t new_index = hole_index_during_move();
if (new_index >= m_image->layer_count())
new_index = m_image->layer_count() - 1;
m_moving_gadget_index = {};
auto old_layer_index = to_layer_index(old_index);
auto new_layer_index = to_layer_index(new_index);
m_image->change_layer_index(old_layer_index, new_layer_index);
}
void LayerListWidget::context_menu_event(GUI::ContextMenuEvent& event)
{
Gfx::IntPoint translated_event_point = { 0, vertical_scrollbar().value() + event.position().y() };
auto gadget_index = gadget_at(translated_event_point);
if (gadget_index.has_value()) {
m_selected_gadget_index = gadget_index.value();
auto& layer = m_image->layer(to_layer_index(m_selected_gadget_index));
set_selected_layer(&layer);
}
if (on_context_menu_request)
on_context_menu_request(event);
}
void LayerListWidget::image_did_add_layer(size_t layer_index)
{
if (m_moving_gadget_index.has_value()) {
m_gadgets[m_moving_gadget_index.value()].is_moving = false;
m_moving_gadget_index = {};
}
auto gadget_index = to_gadget_index(layer_index);
Gadget gadget { gadget_index, {}, false, {} };
m_gadgets.insert(gadget_index, gadget);
relayout_gadgets();
}
void LayerListWidget::image_did_remove_layer(size_t layer_index)
{
if (m_moving_gadget_index.has_value()) {
m_gadgets[m_moving_gadget_index.value()].is_moving = false;
m_moving_gadget_index = {};
}
// No -1 here since a layer has already been removed.
auto gadget_index = m_image->layer_count() - layer_index;
m_gadgets.remove(gadget_index);
m_selected_gadget_index = to_gadget_index(0);
relayout_gadgets();
}
void LayerListWidget::image_did_modify_layer_properties(size_t layer_index)
{
update(m_gadgets[to_gadget_index(layer_index)].rect);
}
void LayerListWidget::image_did_modify_layer_bitmap(size_t layer_index)
{
Gfx::IntRect adjusted_rect;
Gfx::IntRect thumbnail_rect;
Gfx::IntRect text_rect;
get_gadget_rects(m_gadgets[to_gadget_index(layer_index)], adjusted_rect, thumbnail_rect, text_rect);
update(thumbnail_rect);
}
void LayerListWidget::image_did_modify_layer_stack()
{
rebuild_gadgets();
}
static constexpr int gadget_height = 40;
static constexpr int gadget_spacing = -1;
static constexpr int vertical_step = gadget_height + gadget_spacing;
size_t LayerListWidget::hole_index_during_move() const
{
VERIFY(is_moving_gadget());
auto& moving_gadget = m_gadgets[m_moving_gadget_index.value()];
int center_y_of_moving_gadget = moving_gadget.rect.translated(0, moving_gadget.movement_delta.y()).center().y();
int top_of_gadgets = max(0, height() - m_total_gadget_height);
return (center_y_of_moving_gadget - top_of_gadgets) / vertical_step;
}
void LayerListWidget::select_bottom_layer()
{
if (!m_image || !m_image->layer_count())
return;
m_selected_gadget_index = to_gadget_index(0);
set_selected_layer(&m_image->layer(0));
}
void LayerListWidget::select_top_layer()
{
if (!m_image || !m_image->layer_count())
return;
m_selected_gadget_index = 0;
set_selected_layer(&m_image->layer(to_layer_index(0)));
}
void LayerListWidget::cycle_through_selection(int delta)
{
if (!m_image || !m_image->layer_count())
return;
auto current_index = static_cast<int>(m_selected_gadget_index);
current_index += delta;
if (current_index < 0)
current_index = m_gadgets.size() - 1;
if (current_index > static_cast<int>(m_gadgets.size()) - 1)
current_index = 0;
m_selected_gadget_index = current_index;
auto selected_layer_index = to_layer_index(m_selected_gadget_index);
set_selected_layer(&m_image->layer(selected_layer_index));
}
void LayerListWidget::relayout_gadgets()
{
m_total_gadget_height = static_cast<int>(m_gadgets.size()) * vertical_step + 6;
int y = max(0, height() - m_total_gadget_height);
Optional<size_t> hole_index;
if (is_moving_gadget())
hole_index = hole_index_during_move();
size_t index = 0;
for (auto& gadget : m_gadgets) {
if (gadget.is_moving)
continue;
if (index == hole_index)
y += vertical_step;
gadget.rect = { 0, y, widget_inner_rect().width(), gadget_height };
y += vertical_step;
++index;
}
set_content_size({ widget_inner_rect().width(), m_total_gadget_height });
vertical_scrollbar().set_range(0, max(m_total_gadget_height - height(), 0));
update();
}
void LayerListWidget::set_selected_layer(Layer* layer)
{
if (!m_image)
return;
for (size_t i = 0; i < m_image->layer_count(); ++i) {
if (layer == &m_image->layer(i)) {
m_image->layer(i).set_selected(true);
m_selected_gadget_index = to_gadget_index(i);
scroll_into_view(m_gadgets[m_selected_gadget_index].rect, false, true);
} else {
m_image->layer(i).set_selected(false);
}
}
if (on_layer_select)
on_layer_select(layer);
update();
}
}