mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 19:57:44 +00:00
PixelPaint: Move Tools to it's own subdirectory
The PixelPaint source directory was getting a bit large, let's move all the Tools to it's own subdirectory. Also remove some unused includes.
This commit is contained in:
parent
61ad239ee0
commit
f9e0815c3b
35 changed files with 67 additions and 69 deletions
183
Userland/Applications/PixelPaint/Tools/BrushTool.cpp
Normal file
183
Userland/Applications/PixelPaint/Tools/BrushTool.cpp
Normal file
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ben Jilks <benjyjilks@gmail.com>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "BrushTool.h"
|
||||
#include "../ImageEditor.h"
|
||||
#include "../Layer.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/ValueSlider.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
BrushTool::BrushTool()
|
||||
{
|
||||
}
|
||||
|
||||
BrushTool::~BrushTool()
|
||||
{
|
||||
}
|
||||
|
||||
void BrushTool::on_mousedown(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto& layer_event = event.layer_event();
|
||||
if (layer_event.button() != GUI::MouseButton::Left && layer_event.button() != GUI::MouseButton::Right)
|
||||
return;
|
||||
|
||||
// Shift+Click draws a line from the last position to current one.
|
||||
if (layer_event.shift() && m_has_clicked) {
|
||||
draw_line(layer->bitmap(), color_for(layer_event), m_last_position, layer_event.position());
|
||||
auto modified_rect = Gfx::IntRect::from_two_points(m_last_position, layer_event.position()).inflated(m_size * 2, m_size * 2);
|
||||
layer->did_modify_bitmap(modified_rect);
|
||||
m_last_position = layer_event.position();
|
||||
return;
|
||||
}
|
||||
|
||||
const int first_draw_opacity = 10;
|
||||
|
||||
for (int i = 0; i < first_draw_opacity; ++i)
|
||||
draw_point(layer->bitmap(), color_for(layer_event), layer_event.position());
|
||||
|
||||
layer->did_modify_bitmap(Gfx::IntRect::centered_on(layer_event.position(), Gfx::IntSize { m_size * 2, m_size * 2 }));
|
||||
m_last_position = layer_event.position();
|
||||
m_has_clicked = true;
|
||||
}
|
||||
|
||||
void BrushTool::on_mousemove(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto& layer_event = event.layer_event();
|
||||
if (!(layer_event.buttons() & GUI::MouseButton::Left || layer_event.buttons() & GUI::MouseButton::Right))
|
||||
return;
|
||||
|
||||
draw_line(layer->bitmap(), color_for(layer_event), m_last_position, layer_event.position());
|
||||
|
||||
auto modified_rect = Gfx::IntRect::from_two_points(m_last_position, layer_event.position()).inflated(m_size * 2, m_size * 2);
|
||||
|
||||
layer->did_modify_bitmap(modified_rect);
|
||||
m_last_position = layer_event.position();
|
||||
m_was_drawing = true;
|
||||
}
|
||||
|
||||
void BrushTool::on_mouseup(Layer*, MouseEvent&)
|
||||
{
|
||||
if (m_was_drawing) {
|
||||
m_editor->did_complete_action();
|
||||
m_was_drawing = false;
|
||||
}
|
||||
}
|
||||
|
||||
Color BrushTool::color_for(GUI::MouseEvent const& event)
|
||||
{
|
||||
return m_editor->color_for(event);
|
||||
}
|
||||
|
||||
void BrushTool::draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point)
|
||||
{
|
||||
for (int y = point.y() - size(); y < point.y() + size(); y++) {
|
||||
for (int x = point.x() - size(); x < point.x() + size(); x++) {
|
||||
auto distance = point.distance_from({ x, y });
|
||||
if (x < 0 || x >= bitmap.width() || y < 0 || y >= bitmap.height())
|
||||
continue;
|
||||
if (distance >= size())
|
||||
continue;
|
||||
|
||||
auto falloff = (1.0 - double { distance / size() }) * (1.0 / (100 - hardness()));
|
||||
auto pixel_color = color;
|
||||
pixel_color.set_alpha(falloff * 255);
|
||||
bitmap.set_pixel(x, y, bitmap.get_pixel(x, y).blend(pixel_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BrushTool::draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end)
|
||||
{
|
||||
int length_x = end.x() - start.x();
|
||||
int length_y = end.y() - start.y();
|
||||
float y_step = length_y == 0 ? 0 : (float)(length_y) / (float)(length_x);
|
||||
if (y_step > abs(length_y))
|
||||
y_step = abs(length_y);
|
||||
if (y_step < -abs(length_y))
|
||||
y_step = -abs(length_y);
|
||||
if (y_step == 0 && start.x() == end.x())
|
||||
return;
|
||||
|
||||
int start_x = start.x();
|
||||
int end_x = end.x();
|
||||
int start_y = start.y();
|
||||
int end_y = end.y();
|
||||
if (start_x > end_x) {
|
||||
swap(start_x, end_x);
|
||||
swap(start_y, end_y);
|
||||
}
|
||||
|
||||
float y = start_y;
|
||||
for (int x = start_x; x <= end_x; x++) {
|
||||
int start_step_y = y;
|
||||
int end_step_y = y + y_step;
|
||||
if (start_step_y > end_step_y)
|
||||
swap(start_step_y, end_step_y);
|
||||
for (int i = start_step_y; i <= end_step_y; i++)
|
||||
draw_point(bitmap, color, { x, i });
|
||||
y += y_step;
|
||||
}
|
||||
}
|
||||
|
||||
GUI::Widget* BrushTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& size_container = m_properties_widget->add<GUI::Widget>();
|
||||
size_container.set_fixed_height(20);
|
||||
size_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& size_label = size_container.add<GUI::Label>("Size:");
|
||||
size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
size_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& size_slider = size_container.add<GUI::ValueSlider>(Orientation::Horizontal, "px");
|
||||
size_slider.set_range(1, 100);
|
||||
size_slider.set_value(m_size);
|
||||
|
||||
size_slider.on_change = [&](int value) {
|
||||
set_size(value);
|
||||
};
|
||||
set_primary_slider(&size_slider);
|
||||
|
||||
auto& hardness_container = m_properties_widget->add<GUI::Widget>();
|
||||
hardness_container.set_fixed_height(20);
|
||||
hardness_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& hardness_label = hardness_container.add<GUI::Label>("Hardness:");
|
||||
hardness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
hardness_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& hardness_slider = hardness_container.add<GUI::ValueSlider>(Orientation::Horizontal, "%");
|
||||
hardness_slider.set_range(1, 99);
|
||||
hardness_slider.set_value(m_hardness);
|
||||
|
||||
hardness_slider.on_change = [&](int value) {
|
||||
set_hardness(value);
|
||||
};
|
||||
set_secondary_slider(&hardness_slider);
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
45
Userland/Applications/PixelPaint/Tools/BrushTool.h
Normal file
45
Userland/Applications/PixelPaint/Tools/BrushTool.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ben Jilks <benjyjilks@gmail.com>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class BrushTool : public Tool {
|
||||
public:
|
||||
BrushTool();
|
||||
virtual ~BrushTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer*, MouseEvent&) override;
|
||||
virtual void on_mousemove(Layer*, MouseEvent&) override;
|
||||
virtual void on_mouseup(Layer*, MouseEvent&) override;
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; }
|
||||
|
||||
void set_size(int size) { m_size = size; }
|
||||
int size() const { return m_size; }
|
||||
|
||||
void set_hardness(int hardness) { m_hardness = hardness; }
|
||||
int hardness() const { return m_hardness; }
|
||||
|
||||
protected:
|
||||
virtual Color color_for(GUI::MouseEvent const& event);
|
||||
virtual void draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point);
|
||||
virtual void draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end);
|
||||
|
||||
private:
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
int m_size { 20 };
|
||||
int m_hardness { 80 };
|
||||
bool m_was_drawing { false };
|
||||
bool m_has_clicked { false };
|
||||
Gfx::IntPoint m_last_position;
|
||||
};
|
||||
|
||||
}
|
123
Userland/Applications/PixelPaint/Tools/BucketTool.cpp
Normal file
123
Userland/Applications/PixelPaint/Tools/BucketTool.cpp
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "BucketTool.h"
|
||||
#include "../ImageEditor.h"
|
||||
#include "../Layer.h"
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/Queue.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/ValueSlider.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
BucketTool::BucketTool()
|
||||
{
|
||||
}
|
||||
|
||||
BucketTool::~BucketTool()
|
||||
{
|
||||
}
|
||||
|
||||
static float color_distance_squared(Gfx::Color const& lhs, Gfx::Color const& rhs)
|
||||
{
|
||||
int a = rhs.red() - lhs.red();
|
||||
int b = rhs.green() - lhs.green();
|
||||
int c = rhs.blue() - lhs.blue();
|
||||
return (a * a + b * b + c * c) / (255.0f * 255.0f);
|
||||
}
|
||||
|
||||
static void flood_fill(Gfx::Bitmap& bitmap, Gfx::IntPoint const& start_position, Color target_color, Color fill_color, int threshold)
|
||||
{
|
||||
VERIFY(bitmap.bpp() == 32);
|
||||
|
||||
if (target_color == fill_color)
|
||||
return;
|
||||
|
||||
if (!bitmap.rect().contains(start_position))
|
||||
return;
|
||||
|
||||
float threshold_normalized_squared = (threshold / 100.0f) * (threshold / 100.0f);
|
||||
|
||||
Queue<Gfx::IntPoint> queue;
|
||||
queue.enqueue(start_position);
|
||||
HashTable<Gfx::IntPoint> visited;
|
||||
while (!queue.is_empty()) {
|
||||
auto position = queue.dequeue();
|
||||
if (visited.contains(position))
|
||||
continue;
|
||||
visited.set(position);
|
||||
|
||||
auto pixel_color = bitmap.get_pixel<Gfx::StorageFormat::BGRA8888>(position.x(), position.y());
|
||||
if (color_distance_squared(pixel_color, target_color) > threshold_normalized_squared)
|
||||
continue;
|
||||
|
||||
bitmap.set_pixel<Gfx::StorageFormat::BGRA8888>(position.x(), position.y(), fill_color);
|
||||
|
||||
if (position.x() != 0)
|
||||
queue.enqueue(position.translated(-1, 0));
|
||||
|
||||
if (position.x() != bitmap.width() - 1)
|
||||
queue.enqueue(position.translated(1, 0));
|
||||
|
||||
if (position.y() != 0)
|
||||
queue.enqueue(position.translated(0, -1));
|
||||
|
||||
if (position.y() != bitmap.height() - 1)
|
||||
queue.enqueue(position.translated(0, 1));
|
||||
}
|
||||
}
|
||||
|
||||
void BucketTool::on_mousedown(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto& layer_event = event.layer_event();
|
||||
if (!layer->rect().contains(layer_event.position()))
|
||||
return;
|
||||
|
||||
GUI::Painter painter(layer->bitmap());
|
||||
auto target_color = layer->bitmap().get_pixel(layer_event.x(), layer_event.y());
|
||||
|
||||
flood_fill(layer->bitmap(), layer_event.position(), target_color, m_editor->color_for(layer_event), m_threshold);
|
||||
|
||||
layer->did_modify_bitmap();
|
||||
m_editor->did_complete_action();
|
||||
}
|
||||
|
||||
GUI::Widget* BucketTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& threshold_container = m_properties_widget->add<GUI::Widget>();
|
||||
threshold_container.set_fixed_height(20);
|
||||
threshold_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& threshold_label = threshold_container.add<GUI::Label>("Threshold:");
|
||||
threshold_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
threshold_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& threshold_slider = threshold_container.add<GUI::ValueSlider>(Orientation::Horizontal, "%");
|
||||
threshold_slider.set_range(0, 100);
|
||||
threshold_slider.set_value(m_threshold);
|
||||
|
||||
threshold_slider.on_change = [&](int value) {
|
||||
m_threshold = value;
|
||||
};
|
||||
set_primary_slider(&threshold_slider);
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
26
Userland/Applications/PixelPaint/Tools/BucketTool.h
Normal file
26
Userland/Applications/PixelPaint/Tools/BucketTool.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class BucketTool final : public Tool {
|
||||
public:
|
||||
BucketTool();
|
||||
virtual ~BucketTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer*, MouseEvent&) override;
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
|
||||
private:
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
int m_threshold { 0 };
|
||||
};
|
||||
|
||||
}
|
179
Userland/Applications/PixelPaint/Tools/CloneTool.cpp
Normal file
179
Userland/Applications/PixelPaint/Tools/CloneTool.cpp
Normal file
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "CloneTool.h"
|
||||
#include "../ImageEditor.h"
|
||||
#include "../Layer.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/ValueSlider.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
void CloneTool::draw_point(Gfx::Bitmap& bitmap, Gfx::Color const&, Gfx::IntPoint const& point)
|
||||
{
|
||||
if (!m_sample_location.has_value())
|
||||
return;
|
||||
|
||||
auto source_point = point - m_cursor_offset.value();
|
||||
for (int y = -size(); y < size(); y++) {
|
||||
for (int x = -size(); x < size(); x++) {
|
||||
auto target_x = point.x() + x;
|
||||
auto target_y = point.y() + y;
|
||||
auto distance = point.distance_from({ target_x, target_y });
|
||||
if (target_x < 0 || target_x >= bitmap.width() || target_y < 0 || target_y >= bitmap.height())
|
||||
continue;
|
||||
if (distance >= size())
|
||||
continue;
|
||||
|
||||
auto source_x = source_point.x() + x;
|
||||
auto source_y = source_point.y() + y;
|
||||
if (source_x < 0 || source_x >= bitmap.width() || source_y < 0 || source_y >= bitmap.height())
|
||||
continue;
|
||||
|
||||
auto falloff = (1.0 - double { distance / size() }) * (1.0 / (100 - hardness()));
|
||||
auto pixel_color = bitmap.get_pixel(source_x, source_y);
|
||||
pixel_color.set_alpha(falloff * 255);
|
||||
bitmap.set_pixel(target_x, target_y, bitmap.get_pixel(target_x, target_y).blend(pixel_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CloneTool::draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end)
|
||||
{
|
||||
if (!m_sample_location.has_value())
|
||||
return;
|
||||
BrushTool::draw_line(bitmap, color, start, end);
|
||||
}
|
||||
|
||||
Gfx::StandardCursor CloneTool::cursor()
|
||||
{
|
||||
if (m_is_selecting_location)
|
||||
return Gfx::StandardCursor::Eyedropper;
|
||||
return Gfx::StandardCursor::Crosshair;
|
||||
}
|
||||
|
||||
void CloneTool::on_mousemove(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
auto& image_event = event.image_event();
|
||||
if (image_event.alt())
|
||||
return;
|
||||
|
||||
if (m_cursor_offset.has_value()) {
|
||||
m_sample_location = image_event.position() - m_cursor_offset.value();
|
||||
// FIXME: This is a really inefficient way to update the marker's location
|
||||
m_editor->update();
|
||||
}
|
||||
|
||||
BrushTool::on_mousemove(layer, event);
|
||||
}
|
||||
|
||||
void CloneTool::on_mousedown(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
auto& image_event = event.image_event();
|
||||
if (image_event.alt()) {
|
||||
m_sample_location = image_event.position();
|
||||
m_cursor_offset = {};
|
||||
// FIXME: This is a really dumb way to get the marker to show up
|
||||
m_editor->update();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_sample_location.has_value())
|
||||
return;
|
||||
|
||||
if (!m_cursor_offset.has_value())
|
||||
m_cursor_offset = event.image_event().position() - m_sample_location.value();
|
||||
|
||||
BrushTool::on_mousedown(layer, event);
|
||||
}
|
||||
|
||||
void CloneTool::on_second_paint(Layer const*, GUI::PaintEvent& event)
|
||||
{
|
||||
if (!m_sample_location.has_value())
|
||||
return;
|
||||
|
||||
GUI::Painter painter(*m_editor);
|
||||
painter.add_clip_rect(event.rect());
|
||||
|
||||
auto sample_pos = m_editor->image_position_to_editor_position(m_sample_location.value());
|
||||
// We don't want the marker to be a single pixel and hide the color.
|
||||
auto offset = AK::max(2, size() / 2);
|
||||
Gfx::IntRect rect = {
|
||||
(int)sample_pos.x() - offset,
|
||||
(int)sample_pos.y() - offset,
|
||||
offset * 2,
|
||||
offset * 2
|
||||
};
|
||||
painter.draw_ellipse_intersecting(rect, m_marker_color, 1);
|
||||
}
|
||||
|
||||
void CloneTool::on_keydown(GUI::KeyEvent& event)
|
||||
{
|
||||
Tool::on_keydown(event);
|
||||
if (event.key() == KeyCode::Key_Alt && !m_is_selecting_location) {
|
||||
m_is_selecting_location = true;
|
||||
m_editor->update_tool_cursor();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CloneTool::on_keyup(GUI::KeyEvent& event)
|
||||
{
|
||||
if (m_is_selecting_location && event.key() == KeyCode::Key_Alt) {
|
||||
m_is_selecting_location = false;
|
||||
m_editor->update_tool_cursor();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
GUI::Widget* CloneTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& size_container = m_properties_widget->add<GUI::Widget>();
|
||||
size_container.set_fixed_height(20);
|
||||
size_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& size_label = size_container.add<GUI::Label>("Size:");
|
||||
size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
size_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& size_slider = size_container.add<GUI::ValueSlider>(Orientation::Horizontal, "px");
|
||||
size_slider.set_range(1, 100);
|
||||
size_slider.set_value(size());
|
||||
|
||||
size_slider.on_change = [&](int value) {
|
||||
set_size(value);
|
||||
};
|
||||
set_primary_slider(&size_slider);
|
||||
|
||||
auto& hardness_container = m_properties_widget->add<GUI::Widget>();
|
||||
hardness_container.set_fixed_height(20);
|
||||
hardness_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& hardness_label = hardness_container.add<GUI::Label>("Hardness:");
|
||||
hardness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
hardness_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& hardness_slider = hardness_container.add<GUI::ValueSlider>(Orientation::Horizontal, "%");
|
||||
hardness_slider.set_range(1, 99);
|
||||
hardness_slider.on_change = [&](int value) {
|
||||
set_hardness(value);
|
||||
};
|
||||
hardness_slider.set_value(99);
|
||||
set_secondary_slider(&hardness_slider);
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
41
Userland/Applications/PixelPaint/Tools/CloneTool.h
Normal file
41
Userland/Applications/PixelPaint/Tools/CloneTool.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BrushTool.h"
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class CloneTool : public BrushTool {
|
||||
public:
|
||||
CloneTool() = default;
|
||||
virtual ~CloneTool() override = default;
|
||||
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
virtual Gfx::StandardCursor cursor() override;
|
||||
|
||||
protected:
|
||||
virtual void draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) override;
|
||||
virtual void draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end) override;
|
||||
|
||||
virtual void on_mousedown(Layer*, MouseEvent&) override;
|
||||
virtual void on_mousemove(Layer*, MouseEvent&) override;
|
||||
virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override;
|
||||
virtual void on_keydown(GUI::KeyEvent&) override;
|
||||
virtual void on_keyup(GUI::KeyEvent&) override;
|
||||
|
||||
private:
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
|
||||
Optional<Gfx::IntPoint> m_sample_location;
|
||||
Optional<Gfx::IntPoint> m_cursor_offset;
|
||||
bool m_is_selecting_location { false };
|
||||
|
||||
Gfx::Color m_marker_color { Gfx::Color::Green };
|
||||
};
|
||||
|
||||
}
|
203
Userland/Applications/PixelPaint/Tools/EllipseTool.cpp
Normal file
203
Userland/Applications/PixelPaint/Tools/EllipseTool.cpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "EllipseTool.h"
|
||||
#include "../ImageEditor.h"
|
||||
#include "../Layer.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/RadioButton.h>
|
||||
#include <LibGUI/TextBox.h>
|
||||
#include <LibGUI/ValueSlider.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
EllipseTool::EllipseTool()
|
||||
{
|
||||
}
|
||||
|
||||
EllipseTool::~EllipseTool()
|
||||
{
|
||||
}
|
||||
|
||||
void EllipseTool::draw_using(GUI::Painter& painter, Gfx::IntPoint const& start_position, Gfx::IntPoint const& end_position, int thickness)
|
||||
{
|
||||
Gfx::IntRect ellipse_intersecting_rect;
|
||||
if (m_draw_mode == DrawMode::FromCenter) {
|
||||
auto delta = end_position - start_position;
|
||||
ellipse_intersecting_rect = Gfx::IntRect::from_two_points(start_position - delta, end_position);
|
||||
} else {
|
||||
ellipse_intersecting_rect = Gfx::IntRect::from_two_points(start_position, end_position);
|
||||
}
|
||||
|
||||
switch (m_fill_mode) {
|
||||
case FillMode::Outline:
|
||||
painter.draw_ellipse_intersecting(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button), thickness);
|
||||
break;
|
||||
case FillMode::Fill:
|
||||
painter.fill_ellipse(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button));
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
void EllipseTool::on_mousedown(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto& layer_event = event.layer_event();
|
||||
if (layer_event.button() != GUI::MouseButton::Left && layer_event.button() != GUI::MouseButton::Right)
|
||||
return;
|
||||
|
||||
if (m_drawing_button != GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
m_drawing_button = layer_event.button();
|
||||
m_ellipse_start_position = layer_event.position();
|
||||
m_ellipse_end_position = layer_event.position();
|
||||
m_editor->update();
|
||||
}
|
||||
|
||||
void EllipseTool::on_mouseup(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
if (event.layer_event().button() == m_drawing_button) {
|
||||
GUI::Painter painter(layer->bitmap());
|
||||
draw_using(painter, m_ellipse_start_position, m_ellipse_end_position, m_thickness);
|
||||
m_drawing_button = GUI::MouseButton::None;
|
||||
layer->did_modify_bitmap();
|
||||
m_editor->update();
|
||||
m_editor->did_complete_action();
|
||||
}
|
||||
}
|
||||
|
||||
void EllipseTool::on_mousemove(Layer*, MouseEvent& event)
|
||||
{
|
||||
if (m_drawing_button == GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
m_draw_mode = event.layer_event().alt() ? DrawMode::FromCenter : DrawMode::FromCorner;
|
||||
|
||||
if (event.layer_event().shift())
|
||||
m_ellipse_end_position = m_ellipse_start_position.end_point_for_aspect_ratio(event.layer_event().position(), 1.0);
|
||||
else if (m_aspect_ratio.has_value())
|
||||
m_ellipse_end_position = m_ellipse_start_position.end_point_for_aspect_ratio(event.layer_event().position(), m_aspect_ratio.value());
|
||||
else
|
||||
m_ellipse_end_position = event.layer_event().position();
|
||||
|
||||
m_editor->update();
|
||||
}
|
||||
|
||||
void EllipseTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event)
|
||||
{
|
||||
if (!layer || m_drawing_button == GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
GUI::Painter painter(*m_editor);
|
||||
painter.add_clip_rect(event.rect());
|
||||
auto preview_start = m_editor->layer_position_to_editor_position(*layer, m_ellipse_start_position).to_type<int>();
|
||||
auto preview_end = m_editor->layer_position_to_editor_position(*layer, m_ellipse_end_position).to_type<int>();
|
||||
draw_using(painter, preview_start, preview_end, AK::max(m_thickness * m_editor->scale(), 1));
|
||||
}
|
||||
|
||||
void EllipseTool::on_keydown(GUI::KeyEvent& event)
|
||||
{
|
||||
Tool::on_keydown(event);
|
||||
if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) {
|
||||
m_drawing_button = GUI::MouseButton::None;
|
||||
m_editor->update();
|
||||
event.accept();
|
||||
}
|
||||
}
|
||||
|
||||
GUI::Widget* EllipseTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& thickness_container = m_properties_widget->add<GUI::Widget>();
|
||||
thickness_container.set_fixed_height(20);
|
||||
thickness_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& thickness_label = thickness_container.add<GUI::Label>("Thickness:");
|
||||
thickness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
thickness_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& thickness_slider = thickness_container.add<GUI::ValueSlider>(Orientation::Horizontal, "px");
|
||||
thickness_slider.set_range(1, 10);
|
||||
thickness_slider.set_value(m_thickness);
|
||||
|
||||
thickness_slider.on_change = [&](int value) {
|
||||
m_thickness = value;
|
||||
};
|
||||
set_primary_slider(&thickness_slider);
|
||||
|
||||
auto& mode_container = m_properties_widget->add<GUI::Widget>();
|
||||
mode_container.set_fixed_height(46);
|
||||
mode_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
auto& mode_label = mode_container.add<GUI::Label>("Mode:");
|
||||
mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
mode_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& mode_radio_container = mode_container.add<GUI::Widget>();
|
||||
mode_radio_container.set_layout<GUI::VerticalBoxLayout>();
|
||||
auto& outline_mode_radio = mode_radio_container.add<GUI::RadioButton>("Outline");
|
||||
auto& fill_mode_radio = mode_radio_container.add<GUI::RadioButton>("Fill");
|
||||
|
||||
outline_mode_radio.on_checked = [&](bool) {
|
||||
m_fill_mode = FillMode::Outline;
|
||||
};
|
||||
fill_mode_radio.on_checked = [&](bool) {
|
||||
m_fill_mode = FillMode::Fill;
|
||||
};
|
||||
|
||||
outline_mode_radio.set_checked(true);
|
||||
|
||||
auto& aspect_container = m_properties_widget->add<GUI::Widget>();
|
||||
aspect_container.set_fixed_height(20);
|
||||
aspect_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& aspect_label = aspect_container.add<GUI::Label>("Aspect Ratio:");
|
||||
aspect_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
aspect_label.set_fixed_size(80, 20);
|
||||
|
||||
m_aspect_w_textbox = aspect_container.add<GUI::TextBox>();
|
||||
m_aspect_w_textbox->set_fixed_height(20);
|
||||
m_aspect_w_textbox->set_fixed_width(25);
|
||||
m_aspect_w_textbox->on_change = [&] {
|
||||
auto x = m_aspect_w_textbox->text().to_int().value_or(0);
|
||||
auto y = m_aspect_h_textbox->text().to_int().value_or(0);
|
||||
if (x > 0 && y > 0) {
|
||||
m_aspect_ratio = (float)x / (float)y;
|
||||
} else {
|
||||
m_aspect_ratio = {};
|
||||
}
|
||||
};
|
||||
|
||||
auto& multiply_label = aspect_container.add<GUI::Label>("x");
|
||||
multiply_label.set_text_alignment(Gfx::TextAlignment::Center);
|
||||
multiply_label.set_fixed_size(10, 20);
|
||||
|
||||
m_aspect_h_textbox = aspect_container.add<GUI::TextBox>();
|
||||
m_aspect_h_textbox->set_fixed_height(20);
|
||||
m_aspect_h_textbox->set_fixed_width(25);
|
||||
m_aspect_h_textbox->on_change = [&] { m_aspect_w_textbox->on_change(); };
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
55
Userland/Applications/PixelPaint/Tools/EllipseTool.h
Normal file
55
Userland/Applications/PixelPaint/Tools/EllipseTool.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
#include <LibGUI/Forward.h>
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class EllipseTool final : public Tool {
|
||||
public:
|
||||
EllipseTool();
|
||||
virtual ~EllipseTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer*, MouseEvent&) override;
|
||||
virtual void on_mousemove(Layer*, MouseEvent&) override;
|
||||
virtual void on_mouseup(Layer*, MouseEvent&) override;
|
||||
virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override;
|
||||
virtual void on_keydown(GUI::KeyEvent&) override;
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; }
|
||||
|
||||
private:
|
||||
enum class FillMode {
|
||||
Outline,
|
||||
Fill,
|
||||
};
|
||||
|
||||
enum class DrawMode {
|
||||
FromCenter,
|
||||
FromCorner,
|
||||
};
|
||||
|
||||
void draw_using(GUI::Painter&, Gfx::IntPoint const& start_position, Gfx::IntPoint const& end_position, int thickness);
|
||||
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
RefPtr<GUI::TextBox> m_aspect_w_textbox;
|
||||
RefPtr<GUI::TextBox> m_aspect_h_textbox;
|
||||
|
||||
GUI::MouseButton m_drawing_button { GUI::MouseButton::None };
|
||||
Gfx::IntPoint m_ellipse_start_position;
|
||||
Gfx::IntPoint m_ellipse_end_position;
|
||||
int m_thickness { 1 };
|
||||
FillMode m_fill_mode { FillMode::Outline };
|
||||
DrawMode m_draw_mode { DrawMode::FromCorner };
|
||||
Optional<float> m_aspect_ratio;
|
||||
};
|
||||
|
||||
}
|
140
Userland/Applications/PixelPaint/Tools/EraseTool.cpp
Normal file
140
Userland/Applications/PixelPaint/Tools/EraseTool.cpp
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "EraseTool.h"
|
||||
#include "../ImageEditor.h"
|
||||
#include "../Layer.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/CheckBox.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/RadioButton.h>
|
||||
#include <LibGUI/ValueSlider.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
EraseTool::EraseTool()
|
||||
{
|
||||
}
|
||||
|
||||
EraseTool::~EraseTool()
|
||||
{
|
||||
}
|
||||
|
||||
Color EraseTool::color_for(GUI::MouseEvent const&)
|
||||
{
|
||||
if (m_use_secondary_color)
|
||||
return m_editor->secondary_color();
|
||||
return Color(255, 255, 255, 0);
|
||||
}
|
||||
|
||||
void EraseTool::draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point)
|
||||
{
|
||||
if (m_draw_mode == DrawMode::Pencil) {
|
||||
int radius = size() / 2;
|
||||
Gfx::IntRect rect { point.x() - radius, point.y() - radius, size(), size() };
|
||||
GUI::Painter painter(bitmap);
|
||||
painter.clear_rect(rect, color);
|
||||
} else {
|
||||
for (int y = point.y() - size(); y < point.y() + size(); y++) {
|
||||
for (int x = point.x() - size(); x < point.x() + size(); x++) {
|
||||
auto distance = point.distance_from({ x, y });
|
||||
if (x < 0 || x >= bitmap.width() || y < 0 || y >= bitmap.height())
|
||||
continue;
|
||||
if (distance >= size())
|
||||
continue;
|
||||
auto old_color = bitmap.get_pixel(x, y);
|
||||
auto falloff = (1.0 - double { distance / size() }) * (1.0 / (100 - hardness()));
|
||||
auto new_color = old_color.interpolate(color, falloff);
|
||||
bitmap.set_pixel(x, y, new_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUI::Widget* EraseTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& size_container = m_properties_widget->add<GUI::Widget>();
|
||||
size_container.set_fixed_height(20);
|
||||
size_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& size_label = size_container.add<GUI::Label>("Size:");
|
||||
size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
size_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& size_slider = size_container.add<GUI::ValueSlider>(Orientation::Horizontal, "px");
|
||||
size_slider.set_range(1, 100);
|
||||
size_slider.set_value(size());
|
||||
|
||||
size_slider.on_change = [&](int value) {
|
||||
set_size(value);
|
||||
};
|
||||
set_primary_slider(&size_slider);
|
||||
|
||||
auto& hardness_container = m_properties_widget->add<GUI::Widget>();
|
||||
hardness_container.set_fixed_height(20);
|
||||
hardness_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& hardness_label = hardness_container.add<GUI::Label>("Hardness:");
|
||||
hardness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
hardness_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& hardness_slider = hardness_container.add<GUI::ValueSlider>(Orientation::Horizontal, "%");
|
||||
hardness_slider.set_range(1, 99);
|
||||
hardness_slider.set_value(hardness());
|
||||
|
||||
hardness_slider.on_change = [&](int value) {
|
||||
set_hardness(value);
|
||||
};
|
||||
set_secondary_slider(&hardness_slider);
|
||||
|
||||
auto& secondary_color_container = m_properties_widget->add<GUI::Widget>();
|
||||
secondary_color_container.set_fixed_height(20);
|
||||
secondary_color_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& use_secondary_color_checkbox = secondary_color_container.add<GUI::CheckBox>();
|
||||
use_secondary_color_checkbox.set_checked(m_use_secondary_color);
|
||||
use_secondary_color_checkbox.set_text("Use secondary color");
|
||||
use_secondary_color_checkbox.on_checked = [&](bool checked) {
|
||||
m_use_secondary_color = checked;
|
||||
};
|
||||
|
||||
auto& mode_container = m_properties_widget->add<GUI::Widget>();
|
||||
mode_container.set_fixed_height(46);
|
||||
mode_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
auto& mode_label = mode_container.add<GUI::Label>("Draw Mode:");
|
||||
mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
mode_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& mode_radio_container = mode_container.add<GUI::Widget>();
|
||||
mode_radio_container.set_layout<GUI::VerticalBoxLayout>();
|
||||
auto& pencil_mode_radio = mode_radio_container.add<GUI::RadioButton>("Pencil");
|
||||
auto& brush_mode_radio = mode_radio_container.add<GUI::RadioButton>("Brush");
|
||||
|
||||
pencil_mode_radio.on_checked = [&](bool) {
|
||||
m_draw_mode = DrawMode::Pencil;
|
||||
hardness_slider.set_enabled(false);
|
||||
};
|
||||
brush_mode_radio.on_checked = [&](bool) {
|
||||
m_draw_mode = DrawMode::Brush;
|
||||
hardness_slider.set_enabled(true);
|
||||
};
|
||||
|
||||
pencil_mode_radio.set_checked(true);
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
39
Userland/Applications/PixelPaint/Tools/EraseTool.h
Normal file
39
Userland/Applications/PixelPaint/Tools/EraseTool.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BrushTool.h"
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
#include <LibGfx/Forward.h>
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class EraseTool final : public BrushTool {
|
||||
public:
|
||||
EraseTool();
|
||||
virtual ~EraseTool() override;
|
||||
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
|
||||
protected:
|
||||
virtual Color color_for(GUI::MouseEvent const& event) override;
|
||||
virtual void draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) override;
|
||||
|
||||
private:
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
|
||||
enum class DrawMode {
|
||||
Pencil,
|
||||
Brush,
|
||||
};
|
||||
DrawMode m_draw_mode { DrawMode::Brush };
|
||||
bool m_use_secondary_color { false };
|
||||
};
|
||||
|
||||
}
|
214
Userland/Applications/PixelPaint/Tools/GuideTool.cpp
Normal file
214
Userland/Applications/PixelPaint/Tools/GuideTool.cpp
Normal file
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "GuideTool.h"
|
||||
#include "../EditGuideDialog.h"
|
||||
#include <LibGUI/Application.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/ValueSlider.h>
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
GuideTool::GuideTool()
|
||||
{
|
||||
}
|
||||
|
||||
GuideTool::~GuideTool()
|
||||
{
|
||||
}
|
||||
|
||||
RefPtr<Guide> GuideTool::closest_guide(const Gfx::IntPoint& point)
|
||||
{
|
||||
auto guides = editor()->guides();
|
||||
Guide* closest_guide = nullptr;
|
||||
int closest_guide_distance = NumericLimits<int>::max();
|
||||
|
||||
for (auto& guide : guides) {
|
||||
int relevant_position = 0;
|
||||
if (guide.orientation() == Guide::Orientation::Horizontal)
|
||||
relevant_position = point.y();
|
||||
else if (guide.orientation() == Guide::Orientation::Vertical)
|
||||
relevant_position = point.x();
|
||||
|
||||
auto distance = abs(relevant_position - (int)guide.offset());
|
||||
|
||||
if (distance < closest_guide_distance) {
|
||||
closest_guide = &guide;
|
||||
closest_guide_distance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if (closest_guide_distance < 20)
|
||||
return closest_guide;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void GuideTool::on_mousedown(Layer*, MouseEvent& event)
|
||||
{
|
||||
if (!m_editor)
|
||||
return;
|
||||
|
||||
auto& image_event = event.image_event();
|
||||
|
||||
if (image_event.button() != GUI::MouseButton::Left)
|
||||
return;
|
||||
|
||||
m_editor->set_guide_visibility(true);
|
||||
|
||||
RefPtr<Guide> new_guide;
|
||||
if (image_event.position().x() < 0 || image_event.position().x() > editor()->image().size().width()) {
|
||||
new_guide = make_ref_counted<Guide>(Guide::Orientation::Vertical, image_event.position().x());
|
||||
} else if (image_event.position().y() < 0 || image_event.position().y() > editor()->image().size().height()) {
|
||||
new_guide = make_ref_counted<Guide>(Guide::Orientation::Horizontal, image_event.position().y());
|
||||
}
|
||||
|
||||
if (new_guide) {
|
||||
m_selected_guide = new_guide;
|
||||
m_guide_origin = 0;
|
||||
editor()->add_guide(new_guide.release_nonnull());
|
||||
return;
|
||||
}
|
||||
|
||||
m_event_origin = image_event.position();
|
||||
|
||||
m_selected_guide = closest_guide(image_event.position());
|
||||
|
||||
if (m_selected_guide) {
|
||||
m_guide_origin = m_selected_guide->offset();
|
||||
GUI::Application::the()->show_tooltip_immediately(String::formatted("{}", m_guide_origin), GUI::Application::the()->tooltip_source_widget());
|
||||
}
|
||||
}
|
||||
|
||||
void GuideTool::on_mouseup(Layer*, MouseEvent&)
|
||||
{
|
||||
m_guide_origin = 0;
|
||||
m_event_origin = { 0, 0 };
|
||||
GUI::Application::the()->hide_tooltip();
|
||||
|
||||
if (!m_selected_guide)
|
||||
return;
|
||||
|
||||
if (m_selected_guide->offset() < 0
|
||||
|| (m_selected_guide->orientation() == Guide::Orientation::Horizontal && m_selected_guide->offset() > editor()->image().size().height())
|
||||
|| (m_selected_guide->orientation() == Guide::Orientation::Vertical && m_selected_guide->offset() > editor()->image().size().width())) {
|
||||
editor()->remove_guide(*m_selected_guide);
|
||||
editor()->layers_did_change();
|
||||
}
|
||||
|
||||
m_selected_guide = nullptr;
|
||||
}
|
||||
|
||||
void GuideTool::on_mousemove(Layer*, MouseEvent& event)
|
||||
{
|
||||
if (!m_selected_guide)
|
||||
return;
|
||||
|
||||
auto& image_event = event.image_event();
|
||||
auto delta = image_event.position() - m_event_origin;
|
||||
|
||||
auto relevant_offset = 0;
|
||||
if (m_selected_guide->orientation() == Guide::Orientation::Horizontal)
|
||||
relevant_offset = delta.y();
|
||||
else if (m_selected_guide->orientation() == Guide::Orientation::Vertical)
|
||||
relevant_offset = delta.x();
|
||||
|
||||
auto new_offset = (float)relevant_offset + m_guide_origin;
|
||||
|
||||
if (image_event.shift() && m_snap_size > 0) {
|
||||
float snap_size_half = m_snap_size / 2.0;
|
||||
new_offset -= fmodf(new_offset + snap_size_half, m_snap_size) - snap_size_half;
|
||||
}
|
||||
|
||||
m_selected_guide->set_offset(new_offset);
|
||||
|
||||
GUI::Application::the()->show_tooltip_immediately(String::formatted("{}", new_offset), GUI::Application::the()->tooltip_source_widget());
|
||||
|
||||
editor()->layers_did_change();
|
||||
}
|
||||
|
||||
void GuideTool::on_context_menu(Layer*, GUI::ContextMenuEvent& event)
|
||||
{
|
||||
if (!m_editor)
|
||||
return;
|
||||
|
||||
m_editor->set_guide_visibility(true);
|
||||
|
||||
if (!m_context_menu) {
|
||||
m_context_menu = GUI::Menu::construct();
|
||||
m_context_menu->add_action(GUI::Action::create(
|
||||
"Set &Offset", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/gear.png"), [this](auto&) {
|
||||
if (!m_context_menu_guide)
|
||||
return;
|
||||
auto dialog = EditGuideDialog::construct(
|
||||
editor()->window(),
|
||||
String::formatted("{}", m_context_menu_guide->offset()),
|
||||
m_context_menu_guide->orientation());
|
||||
if (dialog->exec() != GUI::Dialog::ExecOK)
|
||||
return;
|
||||
auto offset = dialog->offset_as_pixel(*editor());
|
||||
if (!offset.has_value())
|
||||
return;
|
||||
m_context_menu_guide->set_offset(offset.release_value());
|
||||
m_context_menu_guide->set_orientation(dialog->orientation());
|
||||
editor()->layers_did_change();
|
||||
},
|
||||
editor()));
|
||||
m_context_menu->add_action(GUI::Action::create(
|
||||
"&Delete Guide", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/delete.png"), [this](auto&) {
|
||||
if (!m_context_menu_guide)
|
||||
return;
|
||||
editor()->remove_guide(*m_context_menu_guide);
|
||||
m_selected_guide = nullptr;
|
||||
m_guide_origin = 0;
|
||||
editor()->layers_did_change();
|
||||
},
|
||||
editor()));
|
||||
}
|
||||
|
||||
auto image_position = editor()->editor_position_to_image_position(event.position());
|
||||
m_context_menu_guide = closest_guide({ (int)image_position.x(), (int)image_position.y() });
|
||||
if (m_context_menu_guide)
|
||||
m_context_menu->popup(event.screen_position());
|
||||
}
|
||||
|
||||
void GuideTool::on_tool_activation()
|
||||
{
|
||||
if (m_editor)
|
||||
m_editor->set_guide_visibility(true);
|
||||
}
|
||||
|
||||
GUI::Widget* GuideTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& snapping_container = m_properties_widget->add<GUI::Widget>();
|
||||
snapping_container.set_fixed_height(20);
|
||||
snapping_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& snapping_label = snapping_container.add<GUI::Label>("Snap offset:");
|
||||
snapping_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
snapping_label.set_fixed_size(80, 20);
|
||||
snapping_label.set_tooltip("Press Shift to snap");
|
||||
|
||||
auto& snapping_slider = snapping_container.add<GUI::ValueSlider>(Orientation::Horizontal, "px");
|
||||
snapping_slider.set_range(0, 50);
|
||||
snapping_slider.set_value(m_snap_size);
|
||||
|
||||
snapping_slider.on_change = [&](int value) {
|
||||
m_snap_size = value;
|
||||
};
|
||||
set_primary_slider(&snapping_slider);
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
43
Userland/Applications/PixelPaint/Tools/GuideTool.h
Normal file
43
Userland/Applications/PixelPaint/Tools/GuideTool.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Guide.h"
|
||||
#include "Tool.h"
|
||||
#include <AK/RefPtr.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class GuideTool final : public Tool {
|
||||
public:
|
||||
GuideTool();
|
||||
|
||||
virtual ~GuideTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer*, MouseEvent&) override;
|
||||
virtual void on_mousemove(Layer*, MouseEvent&) override;
|
||||
virtual void on_mouseup(Layer*, MouseEvent&) override;
|
||||
virtual void on_context_menu(Layer*, GUI::ContextMenuEvent&) override;
|
||||
|
||||
virtual void on_tool_activation() override;
|
||||
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; }
|
||||
|
||||
private:
|
||||
RefPtr<Guide> closest_guide(Gfx::IntPoint const&);
|
||||
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
|
||||
RefPtr<Guide> m_selected_guide;
|
||||
RefPtr<Guide> m_context_menu_guide;
|
||||
Gfx::IntPoint m_event_origin;
|
||||
float m_guide_origin { 0 };
|
||||
RefPtr<GUI::Menu> m_context_menu;
|
||||
int m_snap_size { 10 };
|
||||
};
|
||||
}
|
153
Userland/Applications/PixelPaint/Tools/LineTool.cpp
Normal file
153
Userland/Applications/PixelPaint/Tools/LineTool.cpp
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "LineTool.h"
|
||||
#include "../ImageEditor.h"
|
||||
#include "../Layer.h"
|
||||
#include <AK/Math.h>
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/ValueSlider.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
static Gfx::IntPoint constrain_line_angle(Gfx::IntPoint const& start_pos, Gfx::IntPoint const& end_pos, float angle_increment)
|
||||
{
|
||||
float current_angle = AK::atan2<float>(end_pos.y() - start_pos.y(), end_pos.x() - start_pos.x()) + float { M_PI * 2 };
|
||||
|
||||
float constrained_angle = ((int)((current_angle + angle_increment / 2) / angle_increment)) * angle_increment;
|
||||
|
||||
auto diff = end_pos - start_pos;
|
||||
float line_length = AK::hypot<float>(diff.x(), diff.y());
|
||||
|
||||
return { start_pos.x() + (int)(AK::cos(constrained_angle) * line_length),
|
||||
start_pos.y() + (int)(AK::sin(constrained_angle) * line_length) };
|
||||
}
|
||||
|
||||
LineTool::LineTool()
|
||||
{
|
||||
}
|
||||
|
||||
LineTool::~LineTool()
|
||||
{
|
||||
}
|
||||
|
||||
void LineTool::on_mousedown(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto& layer_event = event.layer_event();
|
||||
if (layer_event.button() != GUI::MouseButton::Left && layer_event.button() != GUI::MouseButton::Right)
|
||||
return;
|
||||
|
||||
if (m_drawing_button != GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
m_drawing_button = layer_event.button();
|
||||
|
||||
m_drag_start_position = layer_event.position();
|
||||
m_line_start_position = layer_event.position();
|
||||
m_line_end_position = layer_event.position();
|
||||
|
||||
m_editor->update();
|
||||
}
|
||||
|
||||
void LineTool::on_mouseup(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto& layer_event = event.layer_event();
|
||||
if (layer_event.button() == m_drawing_button) {
|
||||
GUI::Painter painter(layer->bitmap());
|
||||
painter.draw_line(m_line_start_position, m_line_end_position, m_editor->color_for(m_drawing_button), m_thickness);
|
||||
m_drawing_button = GUI::MouseButton::None;
|
||||
layer->did_modify_bitmap();
|
||||
m_editor->update();
|
||||
m_editor->did_complete_action();
|
||||
}
|
||||
}
|
||||
|
||||
void LineTool::on_mousemove(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto& layer_event = event.layer_event();
|
||||
if (m_drawing_button == GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
if (layer_event.shift()) {
|
||||
constexpr auto ANGLE_STEP = M_PI / 8;
|
||||
m_line_end_position = constrain_line_angle(m_drag_start_position, layer_event.position(), ANGLE_STEP);
|
||||
} else {
|
||||
m_line_end_position = layer_event.position();
|
||||
}
|
||||
|
||||
if (layer_event.alt()) {
|
||||
m_line_start_position = m_drag_start_position + (m_drag_start_position - m_line_end_position);
|
||||
} else {
|
||||
m_line_start_position = m_drag_start_position;
|
||||
}
|
||||
|
||||
m_editor->update();
|
||||
}
|
||||
|
||||
void LineTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event)
|
||||
{
|
||||
if (!layer || m_drawing_button == GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
GUI::Painter painter(*m_editor);
|
||||
painter.add_clip_rect(event.rect());
|
||||
auto preview_start = editor_stroke_position(m_line_start_position, m_thickness);
|
||||
auto preview_end = editor_stroke_position(m_line_end_position, m_thickness);
|
||||
painter.draw_line(preview_start, preview_end, m_editor->color_for(m_drawing_button), AK::max(m_thickness * m_editor->scale(), 1));
|
||||
}
|
||||
|
||||
void LineTool::on_keydown(GUI::KeyEvent& event)
|
||||
{
|
||||
Tool::on_keydown(event);
|
||||
if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) {
|
||||
m_drawing_button = GUI::MouseButton::None;
|
||||
m_editor->update();
|
||||
event.accept();
|
||||
}
|
||||
}
|
||||
|
||||
GUI::Widget* LineTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& thickness_container = m_properties_widget->add<GUI::Widget>();
|
||||
thickness_container.set_fixed_height(20);
|
||||
thickness_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& thickness_label = thickness_container.add<GUI::Label>("Thickness:");
|
||||
thickness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
thickness_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& thickness_slider = thickness_container.add<GUI::ValueSlider>(Orientation::Horizontal, "px");
|
||||
thickness_slider.set_range(1, 10);
|
||||
thickness_slider.set_value(m_thickness);
|
||||
|
||||
thickness_slider.on_change = [&](int value) {
|
||||
m_thickness = value;
|
||||
};
|
||||
set_primary_slider(&thickness_slider);
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
38
Userland/Applications/PixelPaint/Tools/LineTool.h
Normal file
38
Userland/Applications/PixelPaint/Tools/LineTool.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class LineTool final : public Tool {
|
||||
public:
|
||||
LineTool();
|
||||
virtual ~LineTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer*, MouseEvent&) override;
|
||||
virtual void on_mousemove(Layer*, MouseEvent&) override;
|
||||
virtual void on_mouseup(Layer*, MouseEvent&) override;
|
||||
virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override;
|
||||
virtual void on_keydown(GUI::KeyEvent&) override;
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; }
|
||||
|
||||
private:
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
|
||||
GUI::MouseButton m_drawing_button { GUI::MouseButton::None };
|
||||
Gfx::IntPoint m_drag_start_position;
|
||||
Gfx::IntPoint m_line_start_position;
|
||||
Gfx::IntPoint m_line_end_position;
|
||||
int m_thickness { 1 };
|
||||
};
|
||||
|
||||
}
|
121
Userland/Applications/PixelPaint/Tools/MoveTool.cpp
Normal file
121
Userland/Applications/PixelPaint/Tools/MoveTool.cpp
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "MoveTool.h"
|
||||
#include "../Image.h"
|
||||
#include "../ImageEditor.h"
|
||||
#include "../Layer.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Window.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
MoveTool::MoveTool()
|
||||
{
|
||||
}
|
||||
|
||||
MoveTool::~MoveTool()
|
||||
{
|
||||
}
|
||||
|
||||
void MoveTool::on_mousedown(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (event.image_event().button() == GUI::MouseButton::Right && !m_is_panning) {
|
||||
m_is_panning = true;
|
||||
m_event_origin = event.raw_event().position();
|
||||
m_saved_pan_origin = m_editor->pan_origin();
|
||||
m_editor->set_override_cursor(Gfx::StandardCursor::Drag);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto& layer_event = event.layer_event();
|
||||
auto& image_event = event.image_event();
|
||||
if (layer_event.button() != GUI::MouseButton::Left)
|
||||
return;
|
||||
if (!layer->rect().contains(layer_event.position()))
|
||||
return;
|
||||
m_layer_being_moved = *layer;
|
||||
m_event_origin = image_event.position();
|
||||
m_layer_origin = layer->location();
|
||||
}
|
||||
|
||||
void MoveTool::on_mousemove(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (m_is_panning) {
|
||||
auto& raw_event = event.raw_event();
|
||||
auto delta = raw_event.position() - m_event_origin;
|
||||
m_editor->set_pan_origin(m_saved_pan_origin.translated(
|
||||
-delta.x(),
|
||||
-delta.y()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto& image_event = event.image_event();
|
||||
if (!m_layer_being_moved)
|
||||
return;
|
||||
auto delta = image_event.position() - m_event_origin;
|
||||
m_layer_being_moved->set_location(m_layer_origin.translated(delta));
|
||||
m_editor->layers_did_change();
|
||||
}
|
||||
|
||||
void MoveTool::on_mouseup(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (event.image_event().button() == GUI::MouseButton::Right && m_is_panning) {
|
||||
m_is_panning = false;
|
||||
m_editor->set_override_cursor(cursor());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto& layer_event = event.layer_event();
|
||||
if (layer_event.button() != GUI::MouseButton::Left)
|
||||
return;
|
||||
m_layer_being_moved = nullptr;
|
||||
m_editor->did_complete_action();
|
||||
}
|
||||
|
||||
void MoveTool::on_keydown(GUI::KeyEvent& event)
|
||||
{
|
||||
if (event.modifiers() != 0)
|
||||
return;
|
||||
|
||||
auto* layer = m_editor->active_layer();
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto new_location = layer->location();
|
||||
|
||||
switch (event.key()) {
|
||||
case Key_Up:
|
||||
new_location.translate_by(0, -1);
|
||||
break;
|
||||
case Key_Down:
|
||||
new_location.translate_by(0, 1);
|
||||
break;
|
||||
case Key_Left:
|
||||
new_location.translate_by(-1, 0);
|
||||
break;
|
||||
case Key_Right:
|
||||
new_location.translate_by(1, 0);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
layer->set_location(new_location);
|
||||
m_editor->layers_did_change();
|
||||
}
|
||||
|
||||
}
|
33
Userland/Applications/PixelPaint/Tools/MoveTool.h
Normal file
33
Userland/Applications/PixelPaint/Tools/MoveTool.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class MoveTool final : public Tool {
|
||||
public:
|
||||
MoveTool();
|
||||
virtual ~MoveTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer*, MouseEvent&) override;
|
||||
virtual void on_mousemove(Layer*, MouseEvent&) override;
|
||||
virtual void on_mouseup(Layer*, MouseEvent&) override;
|
||||
virtual void on_keydown(GUI::KeyEvent&) override;
|
||||
virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Move; }
|
||||
|
||||
private:
|
||||
RefPtr<Layer> m_layer_being_moved;
|
||||
Gfx::IntPoint m_event_origin;
|
||||
Gfx::IntPoint m_layer_origin;
|
||||
|
||||
bool m_is_panning { false };
|
||||
Gfx::FloatPoint m_saved_pan_origin;
|
||||
};
|
||||
|
||||
}
|
68
Userland/Applications/PixelPaint/Tools/PenTool.cpp
Normal file
68
Userland/Applications/PixelPaint/Tools/PenTool.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "PenTool.h"
|
||||
#include "../ImageEditor.h"
|
||||
#include "../Layer.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/ValueSlider.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
PenTool::PenTool()
|
||||
{
|
||||
set_size(1);
|
||||
}
|
||||
|
||||
PenTool::~PenTool()
|
||||
{
|
||||
}
|
||||
|
||||
void PenTool::draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point)
|
||||
{
|
||||
GUI::Painter painter(bitmap);
|
||||
painter.draw_line(point, point, color, size());
|
||||
}
|
||||
|
||||
void PenTool::draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end)
|
||||
{
|
||||
GUI::Painter painter(bitmap);
|
||||
painter.draw_line(start, end, color, size());
|
||||
}
|
||||
|
||||
GUI::Widget* PenTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& size_container = m_properties_widget->add<GUI::Widget>();
|
||||
size_container.set_fixed_height(20);
|
||||
size_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& size_label = size_container.add<GUI::Label>("Thickness:");
|
||||
size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
size_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& size_slider = size_container.add<GUI::ValueSlider>(Orientation::Horizontal, "px");
|
||||
size_slider.set_range(1, 20);
|
||||
size_slider.set_value(size());
|
||||
|
||||
size_slider.on_change = [&](int value) {
|
||||
set_size(value);
|
||||
};
|
||||
set_primary_slider(&size_slider);
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
31
Userland/Applications/PixelPaint/Tools/PenTool.h
Normal file
31
Userland/Applications/PixelPaint/Tools/PenTool.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BrushTool.h"
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class PenTool final : public BrushTool {
|
||||
public:
|
||||
PenTool();
|
||||
virtual ~PenTool() override;
|
||||
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
|
||||
protected:
|
||||
virtual void draw_point(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& point) override;
|
||||
virtual void draw_line(Gfx::Bitmap& bitmap, Gfx::Color const& color, Gfx::IntPoint const& start, Gfx::IntPoint const& end) override;
|
||||
|
||||
private:
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
};
|
||||
|
||||
}
|
63
Userland/Applications/PixelPaint/Tools/PickerTool.cpp
Normal file
63
Userland/Applications/PixelPaint/Tools/PickerTool.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "PickerTool.h"
|
||||
#include "../ImageEditor.h"
|
||||
#include "../Layer.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/CheckBox.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
PickerTool::PickerTool()
|
||||
{
|
||||
}
|
||||
|
||||
PickerTool::~PickerTool()
|
||||
{
|
||||
}
|
||||
|
||||
void PickerTool::on_mousedown(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
auto& position = event.layer_event().position();
|
||||
|
||||
Color color;
|
||||
if (m_sample_all_layers) {
|
||||
color = m_editor->image().color_at(position);
|
||||
} else {
|
||||
if (!layer || !layer->rect().contains(position))
|
||||
return;
|
||||
color = layer->bitmap().get_pixel(position);
|
||||
}
|
||||
|
||||
// We picked a transparent pixel, do nothing.
|
||||
if (!color.alpha())
|
||||
return;
|
||||
|
||||
if (event.layer_event().button() == GUI::MouseButton::Left)
|
||||
m_editor->set_primary_color(color);
|
||||
else if (event.layer_event().button() == GUI::MouseButton::Right)
|
||||
m_editor->set_secondary_color(color);
|
||||
}
|
||||
|
||||
GUI::Widget* PickerTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& sample_checkbox = m_properties_widget->add<GUI::CheckBox>("Sample all layers");
|
||||
sample_checkbox.set_checked(m_sample_all_layers);
|
||||
sample_checkbox.on_checked = [&](bool value) {
|
||||
m_sample_all_layers = value;
|
||||
};
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
29
Userland/Applications/PixelPaint/Tools/PickerTool.h
Normal file
29
Userland/Applications/PixelPaint/Tools/PickerTool.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class PickerTool final : public Tool {
|
||||
public:
|
||||
PickerTool();
|
||||
virtual ~PickerTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer*, MouseEvent&) override;
|
||||
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Eyedropper; }
|
||||
|
||||
private:
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
bool m_sample_all_layers { false };
|
||||
};
|
||||
|
||||
}
|
212
Userland/Applications/PixelPaint/Tools/RectangleSelectTool.cpp
Normal file
212
Userland/Applications/PixelPaint/Tools/RectangleSelectTool.cpp
Normal file
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* 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/ValueSlider.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
RectangleSelectTool::RectangleSelectTool()
|
||||
{
|
||||
}
|
||||
|
||||
RectangleSelectTool::~RectangleSelectTool()
|
||||
{
|
||||
}
|
||||
|
||||
void RectangleSelectTool::on_mousedown(Layer*, MouseEvent& event)
|
||||
{
|
||||
auto& image_event = event.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*, MouseEvent& event)
|
||||
{
|
||||
auto& image_event = event.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*, MouseEvent& event)
|
||||
{
|
||||
auto& image_event = event.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)
|
||||
{
|
||||
Tool::on_keydown(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 = 100;
|
||||
auto& feather_slider = feather_container.add<GUI::ValueSlider>(Orientation::Horizontal, "%");
|
||||
feather_slider.set_range(0, feather_slider_max);
|
||||
feather_slider.set_value((int)floorf(m_edge_feathering * (float)feather_slider_max));
|
||||
|
||||
feather_slider.on_change = [&](int value) {
|
||||
m_edge_feathering = (float)value / (float)feather_slider_max;
|
||||
};
|
||||
set_primary_slider(&feather_slider);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
48
Userland/Applications/PixelPaint/Tools/RectangleSelectTool.h
Normal file
48
Userland/Applications/PixelPaint/Tools/RectangleSelectTool.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../Selection.h"
|
||||
#include "Tool.h"
|
||||
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGUI/Widget.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class RectangleSelectTool final : public Tool {
|
||||
public:
|
||||
RectangleSelectTool();
|
||||
virtual ~RectangleSelectTool();
|
||||
|
||||
virtual void on_mousedown(Layer*, MouseEvent& event) override;
|
||||
virtual void on_mousemove(Layer*, MouseEvent& event) override;
|
||||
virtual void on_mouseup(Layer*, MouseEvent& event) override;
|
||||
virtual void on_keydown(GUI::KeyEvent&) override;
|
||||
virtual void on_keyup(GUI::KeyEvent&) override;
|
||||
virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override;
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; }
|
||||
|
||||
private:
|
||||
enum class MovingMode {
|
||||
MovingOrigin,
|
||||
AroundCenter,
|
||||
None,
|
||||
};
|
||||
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
Vector<String> m_merge_mode_names {};
|
||||
Selection::MergeMode m_merge_mode { Selection::MergeMode::Set };
|
||||
float m_edge_feathering { 0.0f };
|
||||
bool m_selecting { false };
|
||||
MovingMode m_moving_mode { MovingMode::None };
|
||||
Gfx::IntPoint m_selection_start;
|
||||
Gfx::IntPoint m_selection_end;
|
||||
};
|
||||
|
||||
}
|
213
Userland/Applications/PixelPaint/Tools/RectangleTool.cpp
Normal file
213
Userland/Applications/PixelPaint/Tools/RectangleTool.cpp
Normal file
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "RectangleTool.h"
|
||||
#include "../ImageEditor.h"
|
||||
#include "../Layer.h"
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/RadioButton.h>
|
||||
#include <LibGUI/TextBox.h>
|
||||
#include <LibGUI/ValueSlider.h>
|
||||
#include <LibGfx/Rect.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
RectangleTool::RectangleTool()
|
||||
{
|
||||
}
|
||||
|
||||
RectangleTool::~RectangleTool()
|
||||
{
|
||||
}
|
||||
|
||||
void RectangleTool::draw_using(GUI::Painter& painter, Gfx::IntPoint const& start_position, Gfx::IntPoint const& end_position, int thickness)
|
||||
{
|
||||
Gfx::IntRect rect;
|
||||
if (m_draw_mode == DrawMode::FromCenter) {
|
||||
auto delta = end_position - start_position;
|
||||
rect = Gfx::IntRect::from_two_points(start_position - delta, end_position);
|
||||
} else {
|
||||
rect = Gfx::IntRect::from_two_points(start_position, end_position);
|
||||
}
|
||||
|
||||
switch (m_fill_mode) {
|
||||
case FillMode::Fill:
|
||||
painter.fill_rect(rect, m_editor->color_for(m_drawing_button));
|
||||
break;
|
||||
case FillMode::Outline:
|
||||
painter.draw_rect_with_thickness(rect, m_editor->color_for(m_drawing_button), thickness);
|
||||
break;
|
||||
case FillMode::Gradient:
|
||||
painter.fill_rect_with_gradient(rect, m_editor->primary_color(), m_editor->secondary_color());
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
void RectangleTool::on_mousedown(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto& layer_event = event.layer_event();
|
||||
if (layer_event.button() != GUI::MouseButton::Left && layer_event.button() != GUI::MouseButton::Right)
|
||||
return;
|
||||
|
||||
if (m_drawing_button != GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
m_drawing_button = layer_event.button();
|
||||
m_rectangle_start_position = layer_event.position();
|
||||
m_rectangle_end_position = layer_event.position();
|
||||
m_editor->update();
|
||||
}
|
||||
|
||||
void RectangleTool::on_mouseup(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
if (event.layer_event().button() == m_drawing_button) {
|
||||
GUI::Painter painter(layer->bitmap());
|
||||
draw_using(painter, m_rectangle_start_position, m_rectangle_end_position, m_thickness);
|
||||
m_drawing_button = GUI::MouseButton::None;
|
||||
layer->did_modify_bitmap();
|
||||
m_editor->update();
|
||||
m_editor->did_complete_action();
|
||||
}
|
||||
}
|
||||
|
||||
void RectangleTool::on_mousemove(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
if (m_drawing_button == GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
m_draw_mode = event.layer_event().alt() ? DrawMode::FromCenter : DrawMode::FromCorner;
|
||||
|
||||
if (event.layer_event().shift())
|
||||
m_rectangle_end_position = m_rectangle_start_position.end_point_for_aspect_ratio(event.layer_event().position(), 1.0);
|
||||
else if (m_aspect_ratio.has_value())
|
||||
m_rectangle_end_position = m_rectangle_start_position.end_point_for_aspect_ratio(event.layer_event().position(), m_aspect_ratio.value());
|
||||
else
|
||||
m_rectangle_end_position = event.layer_event().position();
|
||||
|
||||
m_editor->update();
|
||||
}
|
||||
|
||||
void RectangleTool::on_second_paint(Layer const* layer, GUI::PaintEvent& event)
|
||||
{
|
||||
if (!layer || m_drawing_button == GUI::MouseButton::None)
|
||||
return;
|
||||
|
||||
GUI::Painter painter(*m_editor);
|
||||
painter.add_clip_rect(event.rect());
|
||||
auto start_position = editor_stroke_position(m_rectangle_start_position, m_thickness);
|
||||
auto end_position = editor_stroke_position(m_rectangle_end_position, m_thickness);
|
||||
draw_using(painter, start_position, end_position, AK::max(m_thickness * m_editor->scale(), 1));
|
||||
}
|
||||
|
||||
void RectangleTool::on_keydown(GUI::KeyEvent& event)
|
||||
{
|
||||
Tool::on_keydown(event);
|
||||
if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) {
|
||||
m_drawing_button = GUI::MouseButton::None;
|
||||
m_editor->update();
|
||||
event.accept();
|
||||
}
|
||||
}
|
||||
|
||||
GUI::Widget* RectangleTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& thickness_container = m_properties_widget->add<GUI::Widget>();
|
||||
thickness_container.set_fixed_height(20);
|
||||
thickness_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& thickness_label = thickness_container.add<GUI::Label>("Thickness:");
|
||||
thickness_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
thickness_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& thickness_slider = thickness_container.add<GUI::ValueSlider>(Orientation::Horizontal, "px");
|
||||
thickness_slider.set_range(1, 10);
|
||||
thickness_slider.set_value(m_thickness);
|
||||
|
||||
thickness_slider.on_change = [&](int value) {
|
||||
m_thickness = value;
|
||||
};
|
||||
set_primary_slider(&thickness_slider);
|
||||
|
||||
auto& mode_container = m_properties_widget->add<GUI::Widget>();
|
||||
mode_container.set_fixed_height(70);
|
||||
mode_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
auto& mode_label = mode_container.add<GUI::Label>("Mode:");
|
||||
mode_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
mode_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& mode_radio_container = mode_container.add<GUI::Widget>();
|
||||
mode_radio_container.set_layout<GUI::VerticalBoxLayout>();
|
||||
auto& outline_mode_radio = mode_radio_container.add<GUI::RadioButton>("Outline");
|
||||
auto& fill_mode_radio = mode_radio_container.add<GUI::RadioButton>("Fill");
|
||||
auto& gradient_mode_radio = mode_radio_container.add<GUI::RadioButton>("Gradient");
|
||||
|
||||
outline_mode_radio.on_checked = [&](bool) {
|
||||
m_fill_mode = FillMode::Outline;
|
||||
};
|
||||
fill_mode_radio.on_checked = [&](bool) {
|
||||
m_fill_mode = FillMode::Fill;
|
||||
};
|
||||
gradient_mode_radio.on_checked = [&](bool) {
|
||||
m_fill_mode = FillMode::Gradient;
|
||||
};
|
||||
|
||||
outline_mode_radio.set_checked(true);
|
||||
|
||||
auto& aspect_container = m_properties_widget->add<GUI::Widget>();
|
||||
aspect_container.set_fixed_height(20);
|
||||
aspect_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& aspect_label = aspect_container.add<GUI::Label>("Aspect Ratio:");
|
||||
aspect_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
aspect_label.set_fixed_size(80, 20);
|
||||
|
||||
m_aspect_w_textbox = aspect_container.add<GUI::TextBox>();
|
||||
m_aspect_w_textbox->set_fixed_height(20);
|
||||
m_aspect_w_textbox->set_fixed_width(25);
|
||||
m_aspect_w_textbox->on_change = [&] {
|
||||
auto x = m_aspect_w_textbox->text().to_int().value_or(0);
|
||||
auto y = m_aspect_h_textbox->text().to_int().value_or(0);
|
||||
if (x > 0 && y > 0) {
|
||||
m_aspect_ratio = (float)x / (float)y;
|
||||
} else {
|
||||
m_aspect_ratio = {};
|
||||
}
|
||||
};
|
||||
|
||||
auto& multiply_label = aspect_container.add<GUI::Label>("x");
|
||||
multiply_label.set_text_alignment(Gfx::TextAlignment::Center);
|
||||
multiply_label.set_fixed_size(10, 20);
|
||||
|
||||
m_aspect_h_textbox = aspect_container.add<GUI::TextBox>();
|
||||
m_aspect_h_textbox->set_fixed_height(20);
|
||||
m_aspect_h_textbox->set_fixed_width(25);
|
||||
m_aspect_h_textbox->on_change = [&] { m_aspect_w_textbox->on_change(); };
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
56
Userland/Applications/PixelPaint/Tools/RectangleTool.h
Normal file
56
Userland/Applications/PixelPaint/Tools/RectangleTool.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
#include <LibGUI/Forward.h>
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class RectangleTool final : public Tool {
|
||||
public:
|
||||
RectangleTool();
|
||||
virtual ~RectangleTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer*, MouseEvent&) override;
|
||||
virtual void on_mousemove(Layer*, MouseEvent&) override;
|
||||
virtual void on_mouseup(Layer*, MouseEvent&) override;
|
||||
virtual void on_second_paint(Layer const*, GUI::PaintEvent&) override;
|
||||
virtual void on_keydown(GUI::KeyEvent&) override;
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; }
|
||||
|
||||
private:
|
||||
enum class FillMode {
|
||||
Outline,
|
||||
Fill,
|
||||
Gradient,
|
||||
};
|
||||
|
||||
enum class DrawMode {
|
||||
FromCenter,
|
||||
FromCorner,
|
||||
};
|
||||
|
||||
void draw_using(GUI::Painter&, Gfx::IntPoint const& start_position, Gfx::IntPoint const& end_position, int thickness);
|
||||
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
RefPtr<GUI::TextBox> m_aspect_w_textbox;
|
||||
RefPtr<GUI::TextBox> m_aspect_h_textbox;
|
||||
|
||||
GUI::MouseButton m_drawing_button { GUI::MouseButton::None };
|
||||
Gfx::IntPoint m_rectangle_start_position;
|
||||
Gfx::IntPoint m_rectangle_end_position;
|
||||
FillMode m_fill_mode { FillMode::Outline };
|
||||
DrawMode m_draw_mode { DrawMode::FromCorner };
|
||||
int m_thickness { 1 };
|
||||
Optional<float> m_aspect_ratio;
|
||||
};
|
||||
|
||||
}
|
142
Userland/Applications/PixelPaint/Tools/SprayTool.cpp
Normal file
142
Userland/Applications/PixelPaint/Tools/SprayTool.cpp
Normal file
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "SprayTool.h"
|
||||
#include "../ImageEditor.h"
|
||||
#include "../Layer.h"
|
||||
#include <AK/Math.h>
|
||||
#include <AK/Queue.h>
|
||||
#include <LibGUI/Action.h>
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/Menu.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
#include <LibGUI/ValueSlider.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
SprayTool::SprayTool()
|
||||
{
|
||||
m_timer = Core::Timer::construct();
|
||||
m_timer->on_timeout = [&]() {
|
||||
paint_it();
|
||||
};
|
||||
m_timer->set_interval(200);
|
||||
}
|
||||
|
||||
SprayTool::~SprayTool()
|
||||
{
|
||||
}
|
||||
|
||||
static double nrand()
|
||||
{
|
||||
return double(rand()) / double(RAND_MAX);
|
||||
}
|
||||
|
||||
void SprayTool::paint_it()
|
||||
{
|
||||
auto* layer = m_editor->active_layer();
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto& bitmap = layer->bitmap();
|
||||
GUI::Painter painter(bitmap);
|
||||
VERIFY(bitmap.bpp() == 32);
|
||||
const double minimal_radius = 2;
|
||||
const double base_radius = minimal_radius * m_thickness;
|
||||
for (int i = 0; i < M_PI * base_radius * base_radius * (m_density / 100.0); i++) {
|
||||
double radius = base_radius * nrand();
|
||||
double angle = 2 * M_PI * nrand();
|
||||
const int xpos = m_last_pos.x() + radius * AK::cos(angle);
|
||||
const int ypos = m_last_pos.y() - radius * AK::sin(angle);
|
||||
if (xpos < 0 || xpos >= bitmap.width())
|
||||
continue;
|
||||
if (ypos < 0 || ypos >= bitmap.height())
|
||||
continue;
|
||||
bitmap.set_pixel<Gfx::StorageFormat::BGRA8888>(xpos, ypos, m_color);
|
||||
}
|
||||
|
||||
layer->did_modify_bitmap(Gfx::IntRect::centered_on(m_last_pos, Gfx::IntSize(base_radius * 2, base_radius * 2)));
|
||||
}
|
||||
|
||||
void SprayTool::on_mousedown(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
auto& layer_event = event.layer_event();
|
||||
m_color = m_editor->color_for(layer_event);
|
||||
m_last_pos = layer_event.position();
|
||||
m_timer->start();
|
||||
paint_it();
|
||||
}
|
||||
|
||||
void SprayTool::on_mousemove(Layer* layer, MouseEvent& event)
|
||||
{
|
||||
if (!layer)
|
||||
return;
|
||||
|
||||
m_last_pos = event.layer_event().position();
|
||||
if (m_timer->is_active()) {
|
||||
paint_it();
|
||||
m_timer->restart(m_timer->interval());
|
||||
}
|
||||
}
|
||||
|
||||
void SprayTool::on_mouseup(Layer*, MouseEvent&)
|
||||
{
|
||||
if (m_timer->is_active()) {
|
||||
m_timer->stop();
|
||||
m_editor->did_complete_action();
|
||||
}
|
||||
}
|
||||
|
||||
GUI::Widget* SprayTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& size_container = m_properties_widget->add<GUI::Widget>();
|
||||
size_container.set_fixed_height(20);
|
||||
size_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& size_label = size_container.add<GUI::Label>("Size:");
|
||||
size_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
size_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& size_slider = size_container.add<GUI::ValueSlider>(Orientation::Horizontal, "px");
|
||||
size_slider.set_range(1, 20);
|
||||
size_slider.set_value(m_thickness);
|
||||
|
||||
size_slider.on_change = [&](int value) {
|
||||
m_thickness = value;
|
||||
};
|
||||
set_primary_slider(&size_slider);
|
||||
|
||||
auto& density_container = m_properties_widget->add<GUI::Widget>();
|
||||
density_container.set_fixed_height(20);
|
||||
density_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& density_label = density_container.add<GUI::Label>("Density:");
|
||||
density_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
density_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& density_slider = density_container.add<GUI::ValueSlider>(Orientation::Horizontal, "%");
|
||||
density_slider.set_range(1, 100);
|
||||
density_slider.set_value(m_density);
|
||||
|
||||
density_slider.on_change = [&](int value) {
|
||||
m_density = value;
|
||||
};
|
||||
set_secondary_slider(&density_slider);
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
38
Userland/Applications/PixelPaint/Tools/SprayTool.h
Normal file
38
Userland/Applications/PixelPaint/Tools/SprayTool.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
#include <LibGUI/Painter.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class SprayTool final : public Tool {
|
||||
public:
|
||||
SprayTool();
|
||||
virtual ~SprayTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer*, MouseEvent&) override;
|
||||
virtual void on_mouseup(Layer*, MouseEvent&) override;
|
||||
virtual void on_mousemove(Layer*, MouseEvent&) override;
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Crosshair; }
|
||||
|
||||
private:
|
||||
void paint_it();
|
||||
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
RefPtr<Core::Timer> m_timer;
|
||||
Gfx::IntPoint m_last_pos;
|
||||
Color m_color;
|
||||
int m_thickness { 10 };
|
||||
int m_density { 40 };
|
||||
};
|
||||
|
||||
}
|
64
Userland/Applications/PixelPaint/Tools/Tool.cpp
Normal file
64
Userland/Applications/PixelPaint/Tools/Tool.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "Tool.h"
|
||||
#include "../ImageEditor.h"
|
||||
#include <LibGUI/Action.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
Tool::Tool()
|
||||
{
|
||||
}
|
||||
|
||||
Tool::~Tool()
|
||||
{
|
||||
}
|
||||
|
||||
void Tool::setup(ImageEditor& editor)
|
||||
{
|
||||
m_editor = editor;
|
||||
}
|
||||
|
||||
void Tool::set_action(GUI::Action* action)
|
||||
{
|
||||
m_action = action;
|
||||
}
|
||||
|
||||
void Tool::on_keydown(GUI::KeyEvent& event)
|
||||
{
|
||||
switch (event.key()) {
|
||||
case KeyCode::Key_LeftBracket:
|
||||
if (m_primary_slider)
|
||||
m_primary_slider->set_value(m_primary_slider->value() - 1);
|
||||
break;
|
||||
case KeyCode::Key_RightBracket:
|
||||
if (m_primary_slider)
|
||||
m_primary_slider->set_value(m_primary_slider->value() + 1);
|
||||
break;
|
||||
case KeyCode::Key_LeftBrace:
|
||||
if (m_secondary_slider)
|
||||
m_secondary_slider->set_value(m_secondary_slider->value() - 1);
|
||||
break;
|
||||
case KeyCode::Key_RightBrace:
|
||||
if (m_secondary_slider)
|
||||
m_secondary_slider->set_value(m_secondary_slider->value() + 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Gfx::IntPoint Tool::editor_stroke_position(Gfx::IntPoint const& pixel_coords, int stroke_thickness) const
|
||||
{
|
||||
auto position = m_editor->image_position_to_editor_position(pixel_coords);
|
||||
auto offset = (stroke_thickness % 2 == 0) ? m_editor->scale() : m_editor->scale() / 2;
|
||||
position = position.translated(offset, offset);
|
||||
return position.to_type<int>();
|
||||
}
|
||||
|
||||
}
|
88
Userland/Applications/PixelPaint/Tools/Tool.h
Normal file
88
Userland/Applications/PixelPaint/Tools/Tool.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Mustafa Quraish <mustafa@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGUI/Event.h>
|
||||
#include <LibGUI/Forward.h>
|
||||
#include <LibGUI/ValueSlider.h>
|
||||
#include <LibGfx/StandardCursor.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class ImageEditor;
|
||||
class Layer;
|
||||
|
||||
class Tool {
|
||||
public:
|
||||
virtual ~Tool();
|
||||
|
||||
class MouseEvent {
|
||||
public:
|
||||
enum class Action {
|
||||
MouseDown,
|
||||
MouseMove,
|
||||
MouseUp
|
||||
};
|
||||
|
||||
MouseEvent(Action action, GUI::MouseEvent& layer_event, GUI::MouseEvent& image_event, GUI::MouseEvent& raw_event)
|
||||
: m_action(action)
|
||||
, m_layer_event(layer_event)
|
||||
, m_image_event(image_event)
|
||||
, m_raw_event(raw_event)
|
||||
{
|
||||
}
|
||||
|
||||
Action action() const { return m_action; }
|
||||
GUI::MouseEvent const& layer_event() const { return m_layer_event; }
|
||||
GUI::MouseEvent const& image_event() const { return m_image_event; }
|
||||
GUI::MouseEvent const& raw_event() const { return m_raw_event; }
|
||||
|
||||
private:
|
||||
Action m_action;
|
||||
|
||||
GUI::MouseEvent& m_layer_event;
|
||||
GUI::MouseEvent& m_image_event;
|
||||
GUI::MouseEvent& m_raw_event;
|
||||
};
|
||||
|
||||
virtual void on_mousedown(Layer*, MouseEvent&) { }
|
||||
virtual void on_mousemove(Layer*, MouseEvent&) { }
|
||||
virtual void on_mouseup(Layer*, MouseEvent&) { }
|
||||
virtual void on_context_menu(Layer*, GUI::ContextMenuEvent&) { }
|
||||
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) { }
|
||||
virtual void on_second_paint(Layer const*, GUI::PaintEvent&) { }
|
||||
virtual void on_keydown(GUI::KeyEvent&);
|
||||
virtual void on_keyup(GUI::KeyEvent&) { }
|
||||
virtual void on_tool_activation() { }
|
||||
virtual GUI::Widget* get_properties_widget() { return nullptr; }
|
||||
virtual Gfx::StandardCursor cursor() { return Gfx::StandardCursor::None; }
|
||||
|
||||
void clear() { m_editor = nullptr; }
|
||||
void setup(ImageEditor&);
|
||||
|
||||
ImageEditor const* editor() const { return m_editor; }
|
||||
ImageEditor* editor() { return m_editor; }
|
||||
|
||||
GUI::Action* action() { return m_action; }
|
||||
void set_action(GUI::Action*);
|
||||
|
||||
protected:
|
||||
Tool();
|
||||
WeakPtr<ImageEditor> m_editor;
|
||||
RefPtr<GUI::Action> m_action;
|
||||
|
||||
virtual Gfx::IntPoint editor_stroke_position(Gfx::IntPoint const& pixel_coords, int stroke_thickness) const;
|
||||
|
||||
void set_primary_slider(GUI::ValueSlider* primary) { m_primary_slider = primary; }
|
||||
void set_secondary_slider(GUI::ValueSlider* secondary) { m_secondary_slider = secondary; }
|
||||
|
||||
GUI::ValueSlider* m_primary_slider { nullptr };
|
||||
GUI::ValueSlider* m_secondary_slider { nullptr };
|
||||
};
|
||||
|
||||
}
|
60
Userland/Applications/PixelPaint/Tools/ZoomTool.cpp
Normal file
60
Userland/Applications/PixelPaint/Tools/ZoomTool.cpp
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2021, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "ZoomTool.h"
|
||||
#include "../ImageEditor.h"
|
||||
#include <LibGUI/BoxLayout.h>
|
||||
#include <LibGUI/Label.h>
|
||||
#include <LibGUI/ValueSlider.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
ZoomTool::ZoomTool()
|
||||
{
|
||||
}
|
||||
|
||||
ZoomTool::~ZoomTool()
|
||||
{
|
||||
}
|
||||
|
||||
void ZoomTool::on_mousedown(Layer*, MouseEvent& event)
|
||||
{
|
||||
auto& raw_event = event.raw_event();
|
||||
if (raw_event.button() != GUI::MouseButton::Left && raw_event.button() != GUI::MouseButton::Right)
|
||||
return;
|
||||
|
||||
auto scale_factor = (raw_event.button() == GUI::MouseButton::Left) ? m_sensitivity : -m_sensitivity;
|
||||
m_editor->scale_centered_on_position(raw_event.position(), scale_factor);
|
||||
}
|
||||
|
||||
GUI::Widget* ZoomTool::get_properties_widget()
|
||||
{
|
||||
if (!m_properties_widget) {
|
||||
m_properties_widget = GUI::Widget::construct();
|
||||
m_properties_widget->set_layout<GUI::VerticalBoxLayout>();
|
||||
|
||||
auto& sensitivity_container = m_properties_widget->add<GUI::Widget>();
|
||||
sensitivity_container.set_fixed_height(20);
|
||||
sensitivity_container.set_layout<GUI::HorizontalBoxLayout>();
|
||||
|
||||
auto& sensitivity_label = sensitivity_container.add<GUI::Label>("Sensitivity:");
|
||||
sensitivity_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
|
||||
sensitivity_label.set_fixed_size(80, 20);
|
||||
|
||||
auto& sensitivity_slider = sensitivity_container.add<GUI::ValueSlider>(Orientation::Horizontal, "%");
|
||||
sensitivity_slider.set_range(1, 100);
|
||||
sensitivity_slider.set_value(100 * m_sensitivity);
|
||||
|
||||
sensitivity_slider.on_change = [&](int value) {
|
||||
m_sensitivity = (double)value / 100.0;
|
||||
};
|
||||
set_primary_slider(&sensitivity_slider);
|
||||
}
|
||||
|
||||
return m_properties_widget.ptr();
|
||||
}
|
||||
|
||||
}
|
29
Userland/Applications/PixelPaint/Tools/ZoomTool.h
Normal file
29
Userland/Applications/PixelPaint/Tools/ZoomTool.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2021, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Tool.h"
|
||||
#include <LibGUI/ActionGroup.h>
|
||||
#include <LibGfx/Point.h>
|
||||
|
||||
namespace PixelPaint {
|
||||
|
||||
class ZoomTool final : public Tool {
|
||||
public:
|
||||
ZoomTool();
|
||||
virtual ~ZoomTool() override;
|
||||
|
||||
virtual void on_mousedown(Layer*, MouseEvent&) override;
|
||||
virtual GUI::Widget* get_properties_widget() override;
|
||||
virtual Gfx::StandardCursor cursor() override { return Gfx::StandardCursor::Zoom; }
|
||||
|
||||
private:
|
||||
RefPtr<GUI::Widget> m_properties_widget;
|
||||
double m_sensitivity { 0.5 };
|
||||
};
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue