mirror of
https://github.com/RGBCube/serenity
synced 2025-05-17 21:45:06 +00:00

This adds a tooltip to all the slider properties showing their current value. Previously there was no indication of what value they had. Also rename the SprayTool property 'thickness' to 'size' like BrushTool calls it.
210 lines
7 KiB
C++
210 lines
7 KiB
C++
/*
|
|
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include "RectangleSelectTool.h"
|
|
#include "ImageEditor.h"
|
|
#include "Layer.h"
|
|
#include <LibGUI/BoxLayout.h>
|
|
#include <LibGUI/Button.h>
|
|
#include <LibGUI/ComboBox.h>
|
|
#include <LibGUI/ItemListModel.h>
|
|
#include <LibGUI/Label.h>
|
|
#include <LibGUI/Model.h>
|
|
#include <LibGUI/Painter.h>
|
|
#include <LibGUI/Slider.h>
|
|
|
|
namespace PixelPaint {
|
|
|
|
RectangleSelectTool::RectangleSelectTool()
|
|
{
|
|
}
|
|
|
|
RectangleSelectTool::~RectangleSelectTool()
|
|
{
|
|
}
|
|
|
|
void RectangleSelectTool::on_mousedown(Layer&, GUI::MouseEvent&, GUI::MouseEvent& image_event)
|
|
{
|
|
if (image_event.button() != GUI::MouseButton::Left)
|
|
return;
|
|
|
|
m_selecting = true;
|
|
m_editor->selection().begin_interactive_selection();
|
|
|
|
m_selection_start = image_event.position();
|
|
m_selection_end = image_event.position();
|
|
m_editor->update();
|
|
}
|
|
|
|
void RectangleSelectTool::on_mousemove(Layer&, GUI::MouseEvent&, GUI::MouseEvent& image_event)
|
|
{
|
|
if (!m_selecting)
|
|
return;
|
|
|
|
if (m_moving_mode != MovingMode::None) {
|
|
auto delta = m_selection_end - image_event.position();
|
|
if (m_moving_mode == MovingMode::MovingOrigin)
|
|
m_selection_start -= delta;
|
|
else if (m_moving_mode == MovingMode::AroundCenter)
|
|
m_selection_start += delta;
|
|
}
|
|
|
|
m_selection_end = image_event.position();
|
|
m_editor->update();
|
|
}
|
|
|
|
void RectangleSelectTool::on_mouseup(Layer&, GUI::MouseEvent&, GUI::MouseEvent& image_event)
|
|
{
|
|
if (!m_selecting || image_event.button() != GUI::MouseButton::Left)
|
|
return;
|
|
|
|
m_selecting = false;
|
|
m_editor->selection().end_interactive_selection();
|
|
|
|
m_editor->update();
|
|
|
|
auto rect_in_image = Gfx::IntRect::from_two_points(m_selection_start, m_selection_end);
|
|
auto mask = Mask::full(rect_in_image);
|
|
|
|
auto feathering = ((mask.bounding_rect().size().to_type<float>() * .5f) * m_edge_feathering).to_type<int>();
|
|
|
|
// Multiply the alpha instead of setting it to ensure corners are feathered correctly
|
|
auto multiply_alpha = [&mask](int x, int y, float alpha) {
|
|
Gfx::IntPoint point { x, y };
|
|
point += mask.bounding_rect().top_left();
|
|
|
|
float old_alpha = mask.getf(point);
|
|
mask.setf(point, old_alpha * alpha);
|
|
};
|
|
|
|
// Horizontal feathering
|
|
for (int offset = 0; offset < feathering.width(); offset++) {
|
|
// Add 1 to offset before dividing to ensure the first pixel won't always be transparent
|
|
float alpha = (float)(offset + 1) / (float)feathering.width();
|
|
|
|
for (int y = 0; y < mask.bounding_rect().height(); y++) {
|
|
multiply_alpha(offset, y, alpha);
|
|
multiply_alpha(mask.bounding_rect().width() - offset - 1, y, alpha);
|
|
}
|
|
}
|
|
|
|
// Vertical feathering
|
|
for (int offset = 0; offset < feathering.height(); offset++) {
|
|
// Add 1 to offset before dividing to ensure the first pixel won't always be transparent
|
|
float alpha = (float)(offset + 1) / (float)feathering.height();
|
|
|
|
for (int x = 0; x < mask.bounding_rect().width(); x++) {
|
|
multiply_alpha(x, offset, alpha);
|
|
multiply_alpha(x, mask.bounding_rect().height() - offset - 1, alpha);
|
|
}
|
|
}
|
|
|
|
m_editor->selection().merge(mask, m_merge_mode);
|
|
}
|
|
|
|
void RectangleSelectTool::on_keydown(GUI::KeyEvent& key_event)
|
|
{
|
|
if (key_event.key() == KeyCode::Key_Space)
|
|
m_moving_mode = MovingMode::MovingOrigin;
|
|
else if (key_event.key() == KeyCode::Key_Control)
|
|
m_moving_mode = MovingMode::AroundCenter;
|
|
}
|
|
|
|
void RectangleSelectTool::on_keyup(GUI::KeyEvent& key_event)
|
|
{
|
|
if (key_event.key() == KeyCode::Key_Space && m_moving_mode == MovingMode::MovingOrigin)
|
|
m_moving_mode = MovingMode::None;
|
|
else if (key_event.key() == KeyCode::Key_Control && m_moving_mode == MovingMode::AroundCenter)
|
|
m_moving_mode = MovingMode::None;
|
|
}
|
|
|
|
void RectangleSelectTool::on_second_paint(Layer const&, GUI::PaintEvent& event)
|
|
{
|
|
if (!m_selecting)
|
|
return;
|
|
|
|
GUI::Painter painter(*m_editor);
|
|
painter.add_clip_rect(event.rect());
|
|
|
|
auto rect_in_image = Gfx::IntRect::from_two_points(m_selection_start, m_selection_end);
|
|
auto rect_in_editor = m_editor->image_rect_to_editor_rect(rect_in_image);
|
|
|
|
m_editor->selection().draw_marching_ants(painter, rect_in_editor.to_type<int>());
|
|
}
|
|
|
|
GUI::Widget* RectangleSelectTool::get_properties_widget()
|
|
{
|
|
if (m_properties_widget) {
|
|
return m_properties_widget.ptr();
|
|
}
|
|
|
|
m_properties_widget = GUI::Widget::construct();
|
|
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
|
|
|
auto& feather_container = m_properties_widget->add<GUI::Widget>();
|
|
feather_container.set_fixed_height(20);
|
|
feather_container.set_layout<GUI::HorizontalBoxLayout>();
|
|
|
|
auto& feather_label = feather_container.add<GUI::Label>();
|
|
feather_label.set_text("Feather:");
|
|
feather_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
|
feather_label.set_fixed_size(80, 20);
|
|
|
|
const int feather_slider_max = 10000;
|
|
auto& feather_slider = feather_container.add<GUI::HorizontalSlider>();
|
|
feather_slider.set_fixed_height(20);
|
|
feather_slider.set_range(0, feather_slider_max);
|
|
feather_slider.set_value((int)floorf(m_edge_feathering * (float)feather_slider_max));
|
|
feather_slider.set_tooltip(String::formatted("{:.2}", (float)m_edge_feathering / (float)feather_slider_max));
|
|
|
|
feather_slider.on_change = [&](int value) {
|
|
m_edge_feathering = (float)value / (float)feather_slider_max;
|
|
feather_slider.set_tooltip(String::formatted("{:.2}", (float)value / (float)feather_slider_max));
|
|
};
|
|
|
|
auto& mode_container = m_properties_widget->add<GUI::Widget>();
|
|
mode_container.set_fixed_height(20);
|
|
mode_container.set_layout<GUI::HorizontalBoxLayout>();
|
|
|
|
auto& mode_label = mode_container.add<GUI::Label>();
|
|
mode_label.set_text("Mode:");
|
|
mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
|
mode_label.set_fixed_size(80, 20);
|
|
|
|
for (int i = 0; i < (int)Selection::MergeMode::__Count; i++) {
|
|
switch ((Selection::MergeMode)i) {
|
|
case Selection::MergeMode::Set:
|
|
m_merge_mode_names.append("Set");
|
|
break;
|
|
case Selection::MergeMode::Add:
|
|
m_merge_mode_names.append("Add");
|
|
break;
|
|
case Selection::MergeMode::Subtract:
|
|
m_merge_mode_names.append("Subtract");
|
|
break;
|
|
case Selection::MergeMode::Intersect:
|
|
m_merge_mode_names.append("Intersect");
|
|
break;
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
auto& mode_combo = mode_container.add<GUI::ComboBox>();
|
|
mode_combo.set_only_allow_values_from_model(true);
|
|
mode_combo.set_model(*GUI::ItemListModel<String>::create(m_merge_mode_names));
|
|
mode_combo.set_selected_index((int)m_merge_mode);
|
|
mode_combo.on_change = [this](auto&&, GUI::ModelIndex const& index) {
|
|
VERIFY(index.row() >= 0);
|
|
VERIFY(index.row() < (int)Selection::MergeMode::__Count);
|
|
|
|
m_merge_mode = (Selection::MergeMode)index.row();
|
|
};
|
|
|
|
return m_properties_widget.ptr();
|
|
}
|
|
|
|
}
|