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

PixelPaint: Rename from PaintBrush :^)

This commit is contained in:
Andreas Kling 2020-05-20 20:35:35 +02:00
parent ff4eaa12b4
commit 58fa9c6e89
41 changed files with 58 additions and 58 deletions

View file

@ -0,0 +1,93 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "BucketTool.h"
#include "ImageEditor.h"
#include "Layer.h"
#include <AK/Queue.h>
#include <AK/SinglyLinkedList.h>
#include <LibGUI/Painter.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Rect.h>
namespace PixelPaint {
BucketTool::BucketTool()
{
}
BucketTool::~BucketTool()
{
}
static void flood_fill(Gfx::Bitmap& bitmap, const Gfx::Point& start_position, Color target_color, Color fill_color)
{
ASSERT(bitmap.bpp() == 32);
if (target_color == fill_color)
return;
if (!bitmap.rect().contains(start_position))
return;
Queue<Gfx::Point> queue;
queue.enqueue(start_position);
while (!queue.is_empty()) {
auto position = queue.dequeue();
if (bitmap.get_pixel<Gfx::BitmapFormat::RGBA32>(position.x(), position.y()) != target_color)
continue;
bitmap.set_pixel<Gfx::BitmapFormat::RGBA32>(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, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (!layer.rect().contains(event.position()))
return;
GUI::Painter painter(layer.bitmap());
auto target_color = layer.bitmap().get_pixel(event.x(), event.y());
flood_fill(layer.bitmap(), event.position(), target_color, m_editor->color_for(event));
m_editor->update();
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "Tool.h"
namespace PixelPaint {
class BucketTool final : public Tool {
public:
BucketTool();
virtual ~BucketTool() override;
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
private:
virtual const char* class_name() const override { return "BucketTool"; }
};
}

View file

@ -0,0 +1,23 @@
set(SOURCES
BucketTool.cpp
CreateNewLayerDialog.cpp
EllipseTool.cpp
EraseTool.cpp
Image.cpp
ImageEditor.cpp
Layer.cpp
LayerModel.cpp
LineTool.cpp
main.cpp
MoveTool.cpp
PaletteWidget.cpp
PenTool.cpp
PickerTool.cpp
RectangleTool.cpp
SprayTool.cpp
ToolboxWidget.cpp
Tool.cpp
)
serenity_bin(PixelPaint)
target_link_libraries(PixelPaint LibGUI LibGfx)

View file

@ -0,0 +1,94 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "CreateNewLayerDialog.h"
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/Label.h>
#include <LibGUI/SpinBox.h>
#include <LibGUI/TextBox.h>
namespace PixelPaint {
CreateNewLayerDialog::CreateNewLayerDialog(const Gfx::Size& suggested_size, GUI::Window* parent_window)
: Dialog(parent_window)
{
set_title("Create new layer");
resize(200, 200);
auto& main_widget = set_main_widget<GUI::Widget>();
main_widget.set_fill_with_background_color(true);
auto& layout = main_widget.set_layout<GUI::VerticalBoxLayout>();
layout.set_margins({ 4, 4, 4, 4 });
auto& name_label = main_widget.add<GUI::Label>("Name:");
name_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
m_name_textbox = main_widget.add<GUI::TextBox>();
m_name_textbox->on_change = [this] {
m_layer_name = m_name_textbox->text();
};
auto& width_label = main_widget.add<GUI::Label>("Width:");
width_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
auto& width_spinbox = main_widget.add<GUI::SpinBox>();
auto& height_label = main_widget.add<GUI::Label>("Height:");
height_label.set_text_alignment(Gfx::TextAlignment::CenterLeft);
auto& height_spinbox = main_widget.add<GUI::SpinBox>();
auto& button_container = main_widget.add<GUI::Widget>();
button_container.set_layout<GUI::HorizontalBoxLayout>();
auto& ok_button = button_container.add<GUI::Button>("OK");
ok_button.on_click = [this](auto) {
done(ExecOK);
};
auto& cancel_button = button_container.add<GUI::Button>("Cancel");
cancel_button.on_click = [this](auto) {
done(ExecCancel);
};
width_spinbox.on_change = [this](int value) {
m_layer_size.set_width(value);
};
height_spinbox.on_change = [this](int value) {
m_layer_size.set_height(value);
};
width_spinbox.set_range(0, 16384);
height_spinbox.set_range(0, 16384);
width_spinbox.set_value(suggested_size.width());
height_spinbox.set_value(suggested_size.height());
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <LibGUI/Dialog.h>
namespace PixelPaint {
class CreateNewLayerDialog final : public GUI::Dialog {
C_OBJECT(CreateNewLayerDialog);
public:
const Gfx::Size& layer_size() const { return m_layer_size; }
const String& layer_name() const { return m_layer_name; }
private:
CreateNewLayerDialog(const Gfx::Size& suggested_size, GUI::Window* parent_window);
Gfx::Size m_layer_size;
String m_layer_name;
RefPtr<GUI::TextBox> m_name_textbox;
};
}

View file

@ -0,0 +1,136 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "EllipseTool.h"
#include "ImageEditor.h"
#include "Layer.h"
#include <LibGUI/Action.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Painter.h>
#include <LibGfx/Rect.h>
#include <LibM/math.h>
namespace PixelPaint {
EllipseTool::EllipseTool()
{
}
EllipseTool::~EllipseTool()
{
}
void EllipseTool::draw_using(GUI::Painter& painter)
{
auto ellipse_intersecting_rect = Gfx::Rect::from_two_points(m_ellipse_start_position, m_ellipse_end_position);
switch (m_mode) {
case Mode::Outline:
painter.draw_ellipse_intersecting(ellipse_intersecting_rect, m_editor->color_for(m_drawing_button), m_thickness);
break;
default:
ASSERT_NOT_REACHED();
}
}
void EllipseTool::on_mousedown(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (event.button() != GUI::MouseButton::Left && event.button() != GUI::MouseButton::Right)
return;
if (m_drawing_button != GUI::MouseButton::None)
return;
m_drawing_button = event.button();
m_ellipse_start_position = event.position();
m_ellipse_end_position = event.position();
m_editor->update();
}
void EllipseTool::on_mouseup(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (event.button() == m_drawing_button) {
GUI::Painter painter(layer.bitmap());
draw_using(painter);
m_drawing_button = GUI::MouseButton::None;
m_editor->update();
}
}
void EllipseTool::on_mousemove(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (m_drawing_button == GUI::MouseButton::None)
return;
m_ellipse_end_position = event.position();
m_editor->update();
}
void EllipseTool::on_second_paint(const Layer& layer, GUI::PaintEvent& event)
{
if (m_drawing_button == GUI::MouseButton::None)
return;
GUI::Painter painter(*m_editor);
painter.add_clip_rect(event.rect());
painter.translate(layer.location());
draw_using(painter);
}
void EllipseTool::on_keydown(GUI::KeyEvent& event)
{
if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) {
m_drawing_button = GUI::MouseButton::None;
m_editor->update();
event.accept();
}
}
void EllipseTool::on_tool_button_contextmenu(GUI::ContextMenuEvent& event)
{
if (!m_context_menu) {
m_context_menu = GUI::Menu::construct();
m_context_menu->add_action(GUI::Action::create("Outline", [this](auto&) {
m_mode = Mode::Outline;
}));
m_context_menu->add_separator();
m_thickness_actions.set_exclusive(true);
auto insert_action = [&](int size, bool checked = false) {
auto action = GUI::Action::create_checkable(String::number(size), [this, size](auto&) {
m_thickness = size;
});
action->set_checked(checked);
m_thickness_actions.add_action(*action);
m_context_menu->add_action(move(action));
};
insert_action(1, true);
insert_action(2);
insert_action(3);
insert_action(4);
}
m_context_menu->popup(event.screen_position());
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "Tool.h"
#include <LibGfx/Point.h>
#include <LibGUI/ActionGroup.h>
namespace PixelPaint {
class EllipseTool final : public Tool {
public:
EllipseTool();
virtual ~EllipseTool() override;
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) override;
virtual void on_second_paint(const Layer&, GUI::PaintEvent&) override;
virtual void on_keydown(GUI::KeyEvent&) override;
private:
enum class Mode {
Outline,
// FIXME: Add Mode::Fill
};
virtual const char* class_name() const override { return "EllipseTool"; }
void draw_using(GUI::Painter& painter);
GUI::MouseButton m_drawing_button { GUI::MouseButton::None };
Gfx::Point m_ellipse_start_position;
Gfx::Point m_ellipse_end_position;
RefPtr<GUI::Menu> m_context_menu;
int m_thickness { 1 };
GUI::ActionGroup m_thickness_actions;
Mode m_mode { Mode::Outline };
};
}

View file

@ -0,0 +1,113 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "EraseTool.h"
#include "ImageEditor.h"
#include "Layer.h"
#include <LibGUI/Action.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Painter.h>
#include <LibGfx/Bitmap.h>
namespace PixelPaint {
EraseTool::EraseTool()
{
}
EraseTool::~EraseTool()
{
}
Gfx::Rect EraseTool::build_rect(const Gfx::Point& pos, const Gfx::Rect& widget_rect)
{
const int base_eraser_size = 10;
const int eraser_size = (base_eraser_size * m_thickness);
const int eraser_radius = eraser_size / 2;
const auto ex = pos.x();
const auto ey = pos.y();
return Gfx::Rect(ex - eraser_radius, ey - eraser_radius, eraser_size, eraser_size).intersected(widget_rect);
}
void EraseTool::on_mousedown(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (event.button() != GUI::MouseButton::Left && event.button() != GUI::MouseButton::Right)
return;
Gfx::Rect r = build_rect(event.position(), layer.rect());
GUI::Painter painter(layer.bitmap());
painter.clear_rect(r, get_color());
m_editor->update();
}
void EraseTool::on_mousemove(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (event.buttons() & GUI::MouseButton::Left || event.buttons() & GUI::MouseButton::Right) {
Gfx::Rect r = build_rect(event.position(), layer.rect());
GUI::Painter painter(layer.bitmap());
painter.clear_rect(r, get_color());
m_editor->update();
}
}
void EraseTool::on_tool_button_contextmenu(GUI::ContextMenuEvent& event)
{
if (!m_context_menu) {
m_context_menu = GUI::Menu::construct();
auto eraser_color_toggler = GUI::Action::create_checkable("Use secondary color", [&](auto& action) {
m_use_secondary_color = action.is_checked();
});
eraser_color_toggler->set_checked(m_use_secondary_color);
m_context_menu->add_action(eraser_color_toggler);
m_context_menu->add_separator();
m_thickness_actions.set_exclusive(true);
auto insert_action = [&](int size, bool checked = false) {
auto action = GUI::Action::create_checkable(String::number(size), [this, size](auto&) {
m_thickness = size;
});
action->set_checked(checked);
m_thickness_actions.add_action(*action);
m_context_menu->add_action(move(action));
};
insert_action(1, true);
insert_action(2);
insert_action(3);
insert_action(4);
}
m_context_menu->popup(event.screen_position());
}
Color EraseTool::get_color() const
{
if (m_use_secondary_color)
return m_editor->secondary_color();
return Color(255, 255, 255, 0);
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "Tool.h"
#include <LibGUI/ActionGroup.h>
#include <LibGfx/Point.h>
#include <LibGfx/Forward.h>
namespace PixelPaint {
class EraseTool final : public Tool {
public:
EraseTool();
virtual ~EraseTool() override;
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) override;
private:
Gfx::Color get_color() const;
virtual const char* class_name() const override { return "EraseTool"; }
Gfx::Rect build_rect(const Gfx::Point& pos, const Gfx::Rect& widget_rect);
RefPtr<GUI::Menu> m_context_menu;
bool m_use_secondary_color { false };
int m_thickness { 1 };
GUI::ActionGroup m_thickness_actions;
};
}

View file

@ -0,0 +1,134 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Image.h"
#include "Layer.h"
#include "LayerModel.h"
#include <LibGUI/Painter.h>
//#define PAINT_DEBUG
namespace PixelPaint {
RefPtr<Image> Image::create_with_size(const Gfx::Size& size)
{
if (size.is_empty())
return nullptr;
if (size.width() > 16384 || size.height() > 16384)
return nullptr;
return adopt(*new Image(size));
}
Image::Image(const Gfx::Size& size)
: m_size(size)
{
}
void Image::paint_into(GUI::Painter& painter, const Gfx::Rect& dest_rect, const Gfx::Rect& src_rect)
{
(void) src_rect;
Gfx::PainterStateSaver saver(painter);
painter.add_clip_rect(dest_rect);
for (auto& layer : m_layers) {
auto target = dest_rect.translated(layer.location());
target.set_size(layer.size());
#ifdef IMAGE_DEBUG
dbg() << "Composite layer " << layer.name() << " target: " << target << ", layer.rect: " << layer.rect();
#endif
painter.draw_scaled_bitmap(target, layer.bitmap(), layer.rect());
}
}
void Image::add_layer(NonnullRefPtr<Layer> layer)
{
for (auto& existing_layer : m_layers) {
ASSERT(&existing_layer != layer.ptr());
}
m_layers.append(move(layer));
}
GUI::Model& Image::layer_model()
{
if (!m_layer_model)
m_layer_model = LayerModel::create(*this);
return *m_layer_model;
}
size_t Image::index_of(const Layer& layer) const
{
for (size_t i = 0; i < m_layers.size(); ++i) {
if (&m_layers.at(i) == &layer)
return i;
}
ASSERT_NOT_REACHED();
}
void Image::move_layer_to_back(Layer& layer)
{
NonnullRefPtr<Layer> protector(layer);
auto index = index_of(layer);
m_layers.remove(index);
m_layers.prepend(layer);
}
void Image::move_layer_to_front(Layer& layer)
{
NonnullRefPtr<Layer> protector(layer);
auto index = index_of(layer);
m_layers.remove(index);
m_layers.append(layer);
}
void Image::move_layer_down(Layer& layer)
{
NonnullRefPtr<Layer> protector(layer);
auto index = index_of(layer);
if (!index)
return;
m_layers.remove(index);
m_layers.insert(index - 1, layer);
}
void Image::move_layer_up(Layer& layer)
{
NonnullRefPtr<Layer> protector(layer);
auto index = index_of(layer);
if (index == m_layers.size() - 1)
return;
m_layers.remove(index);
m_layers.insert(index + 1, layer);
}
void Image::remove_layer(Layer& layer)
{
NonnullRefPtr<Layer> protector(layer);
auto index = index_of(layer);
m_layers.remove(index);
}
}

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/NonnullRefPtrVector.h>
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <AK/Vector.h>
#include <LibGUI/Forward.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Rect.h>
#include <LibGfx/Size.h>
namespace PixelPaint {
class Layer;
class Image : public RefCounted<Image> {
public:
static RefPtr<Image> create_with_size(const Gfx::Size&);
size_t layer_count() const { return m_layers.size(); }
const Layer& layer(size_t index) const { return m_layers.at(index); }
const Gfx::Size& size() const { return m_size; }
Gfx::Rect rect() const { return { {}, m_size }; }
void add_layer(NonnullRefPtr<Layer>);
void paint_into(GUI::Painter&, const Gfx::Rect& dest_rect, const Gfx::Rect& src_rect);
GUI::Model& layer_model();
void move_layer_to_front(Layer&);
void move_layer_to_back(Layer&);
void move_layer_up(Layer&);
void move_layer_down(Layer&);
void remove_layer(Layer&);
private:
explicit Image(const Gfx::Size&);
size_t index_of(const Layer&) const;
Gfx::Size m_size;
NonnullRefPtrVector<Layer> m_layers;
RefPtr<GUI::Model> m_layer_model;
};
}

View file

@ -0,0 +1,229 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "ImageEditor.h"
#include "Image.h"
#include "Layer.h"
#include "LayerModel.h"
#include "Tool.h"
#include <LibGUI/Painter.h>
#include <LibGfx/Palette.h>
namespace PixelPaint {
ImageEditor::ImageEditor()
{
}
void ImageEditor::set_image(RefPtr<Image> image)
{
m_image = move(image);
update();
}
void ImageEditor::paint_event(GUI::PaintEvent& event)
{
GUI::Frame::paint_event(event);
GUI::Painter painter(*this);
painter.add_clip_rect(event.rect());
painter.add_clip_rect(frame_inner_rect());
painter.translate(frame_thickness(), frame_thickness());
painter.fill_rect_with_checkerboard(rect(), { 8, 8 }, palette().base().darkened(0.9), palette().base());
if (m_image) {
painter.draw_rect(m_image->rect().inflated(2, 2), Color::Black);
m_image->paint_into(painter, m_image->rect(), m_image->rect());
}
if (m_active_layer) {
painter.draw_rect(m_active_layer->relative_rect().inflated(2, 2), Color::Black);
}
}
void ImageEditor::second_paint_event(GUI::PaintEvent& event)
{
if (m_active_tool && m_active_layer)
m_active_tool->on_second_paint(*m_active_layer, event);
}
static GUI::MouseEvent event_adjusted_for_layer(const GUI::MouseEvent& original_event, const Layer& layer)
{
auto position_in_active_layer_coordinates = original_event.position().translated(-layer.location());
return {
static_cast<GUI::Event::Type>(original_event.type()),
position_in_active_layer_coordinates, original_event.buttons(),
original_event.button(),
original_event.modifiers(),
original_event.wheel_delta()
};
}
void ImageEditor::mousedown_event(GUI::MouseEvent& event)
{
if (!m_active_tool)
return;
if (m_active_tool->is_move_tool()) {
if (auto* other_layer = layer_at(event.position())) {
set_active_layer(other_layer);
}
}
if (!m_active_layer)
return;
auto layer_event = event_adjusted_for_layer(event, *m_active_layer);
m_active_tool->on_mousedown(*m_active_layer, layer_event, event);
}
void ImageEditor::mousemove_event(GUI::MouseEvent& event)
{
if (!m_active_layer || !m_active_tool)
return;
auto layer_event = event_adjusted_for_layer(event, *m_active_layer);
m_active_tool->on_mousemove(*m_active_layer, layer_event, event);
}
void ImageEditor::mouseup_event(GUI::MouseEvent& event)
{
if (!m_active_layer || !m_active_tool)
return;
auto layer_event = event_adjusted_for_layer(event, *m_active_layer);
m_active_tool->on_mouseup(*m_active_layer, layer_event, event);
}
void ImageEditor::context_menu_event(GUI::ContextMenuEvent& event)
{
if (!m_active_layer || !m_active_tool)
return;
m_active_tool->on_context_menu(*m_active_layer, event);
}
void ImageEditor::keydown_event(GUI::KeyEvent& event)
{
if (m_active_tool)
m_active_tool->on_keydown(event);
}
void ImageEditor::keyup_event(GUI::KeyEvent& event)
{
if (m_active_tool)
m_active_tool->on_keyup(event);
}
void ImageEditor::set_active_layer(Layer* layer)
{
if (m_active_layer == layer)
return;
m_active_layer = layer;
if (m_active_layer) {
size_t index = 0;
for (; index < m_image->layer_count(); ++index) {
if (&m_image->layer(index) == layer)
break;
}
if (on_active_layer_change)
on_active_layer_change(m_image->layer_model().index(index));
} else {
if (on_active_layer_change)
on_active_layer_change({});
}
layers_did_change();
}
void ImageEditor::set_active_tool(Tool* tool)
{
if (m_active_tool == tool)
return;
if (m_active_tool)
m_active_tool->clear();
m_active_tool = tool;
if (m_active_tool)
m_active_tool->setup(*this);
}
void ImageEditor::layers_did_change()
{
static_cast<LayerModel&>(m_image->layer_model()).update_without_invalidating_indexes();
update();
}
Color ImageEditor::color_for(GUI::MouseButton button) const
{
if (button == GUI::MouseButton::Left)
return m_primary_color;
if (button == GUI::MouseButton::Right)
return m_secondary_color;
ASSERT_NOT_REACHED();
}
Color ImageEditor::color_for(const GUI::MouseEvent& event) const
{
if (event.buttons() & GUI::MouseButton::Left)
return m_primary_color;
if (event.buttons() & GUI::MouseButton::Right)
return m_secondary_color;
ASSERT_NOT_REACHED();
}
void ImageEditor::set_primary_color(Color color)
{
if (m_primary_color == color)
return;
m_primary_color = color;
if (on_primary_color_change)
on_primary_color_change(color);
}
void ImageEditor::set_secondary_color(Color color)
{
if (m_secondary_color == color)
return;
m_secondary_color = color;
if (on_secondary_color_change)
on_secondary_color_change(color);
}
Layer* ImageEditor::layer_at(const Gfx::Point& position)
{
if (!m_image)
return nullptr;
for (ssize_t i = m_image->layer_count() - 1; i >= 0; --i) {
auto& layer = m_image->layer(i);
if (layer.relative_rect().contains(position))
return const_cast<Layer*>(&layer);
}
return nullptr;
}
}

View file

@ -0,0 +1,93 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <LibGUI/Frame.h>
namespace PixelPaint {
class Image;
class Layer;
class Tool;
class ImageEditor final : public GUI::Frame {
C_OBJECT(ImageEditor);
public:
const Image* image() const { return m_image; }
Image* image() { return m_image; }
void set_image(RefPtr<Image>);
Layer* active_layer() { return m_active_layer; }
void set_active_layer(Layer*);
Tool* active_tool() { return m_active_tool; }
void set_active_tool(Tool*);
void layers_did_change();
Layer* layer_at(const Gfx::Point&);
Color primary_color() const { return m_primary_color; }
void set_primary_color(Color);
Color secondary_color() const { return m_secondary_color; }
void set_secondary_color(Color);
Color color_for(const GUI::MouseEvent&) const;
Color color_for(GUI::MouseButton) const;
Function<void(Color)> on_primary_color_change;
Function<void(Color)> on_secondary_color_change;
Function<void(const GUI::ModelIndex&)> on_active_layer_change;
private:
ImageEditor();
virtual bool accepts_focus() const override { return true; }
virtual void paint_event(GUI::PaintEvent&) override;
virtual void second_paint_event(GUI::PaintEvent&) override;
virtual void mousedown_event(GUI::MouseEvent&) override;
virtual void mousemove_event(GUI::MouseEvent&) override;
virtual void mouseup_event(GUI::MouseEvent&) override;
virtual void keydown_event(GUI::KeyEvent&) override;
virtual void keyup_event(GUI::KeyEvent&) override;
virtual void context_menu_event(GUI::ContextMenuEvent&) override;
RefPtr<Image> m_image;
RefPtr<Layer> m_active_layer;
Tool* m_active_tool { nullptr };
Color m_primary_color { Color::Black };
Color m_secondary_color { Color::White };
};
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Layer.h"
#include <LibGfx/Bitmap.h>
namespace PixelPaint {
RefPtr<Layer> Layer::create_with_size(const Gfx::Size& size, const String& name)
{
if (size.is_empty())
return nullptr;
if (size.width() > 16384 || size.height() > 16384)
return nullptr;
return adopt(*new Layer(size, name));
}
Layer::Layer(const Gfx::Size& size, const String& name)
: m_name(name)
{
m_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::RGBA32, size);
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/Noncopyable.h>
#include <AK/RefCounted.h>
#include <AK/String.h>
#include <LibGfx/Bitmap.h>
namespace PixelPaint {
class Layer : public RefCounted<Layer> {
AK_MAKE_NONCOPYABLE(Layer);
AK_MAKE_NONMOVABLE(Layer);
public:
static RefPtr<Layer> create_with_size(const Gfx::Size&, const String& name);
~Layer() {}
const Gfx::Point& location() const { return m_location; }
void set_location(const Gfx::Point& location) { m_location = location; }
const Gfx::Bitmap& bitmap() const { return *m_bitmap; }
Gfx::Bitmap& bitmap() { return *m_bitmap; }
Gfx::Size size() const { return bitmap().size(); }
Gfx::Rect relative_rect() const { return { location(), size() }; }
Gfx::Rect rect() const { return { {}, size() }; }
const String& name() const { return m_name; }
void set_name(const String& name) { m_name = name; }
private:
explicit Layer(const Gfx::Size&, const String& name);
String m_name;
Gfx::Point m_location;
RefPtr<Gfx::Bitmap> m_bitmap;
};
}

View file

@ -0,0 +1,77 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "LayerModel.h"
#include "Image.h"
#include "Layer.h"
namespace PixelPaint {
NonnullRefPtr<LayerModel> LayerModel::create(Image& image)
{
return adopt(*new LayerModel(image));
}
LayerModel::LayerModel(Image& image)
: m_image(image)
{
}
int LayerModel::row_count(const GUI::ModelIndex&) const
{
return m_image.layer_count();
}
String LayerModel::column_name(int column) const
{
switch (column) {
case Column::Name:
return "Name";
case Column::Size:
return "Size";
case Column::Location:
return "Location";
}
ASSERT_NOT_REACHED();
}
GUI::Variant LayerModel::data(const GUI::ModelIndex& index, Role role) const
{
auto& layer = m_image.layer(index.row());
if (role == Role::Display) {
switch (index.column()) {
case Column::Name:
return layer.name();
case Column::Size:
return layer.size();
case Column::Location:
return layer.location();
}
}
return {};
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <LibGUI/Model.h>
namespace PixelPaint {
class Image;
class LayerModel final : public GUI::Model {
public:
enum Column {
Name,
Size,
Location,
__Count
};
static NonnullRefPtr<LayerModel> create(Image&);
virtual int row_count(const GUI::ModelIndex&) const override;
virtual int column_count(const GUI::ModelIndex&) const override { return Column::__Count; }
virtual String column_name(int) const override;
virtual GUI::Variant data(const GUI::ModelIndex&, Role = Role::Display) const override;
virtual void update() override { did_update(); }
void update_without_invalidating_indexes() { did_update(0); }
private:
explicit LayerModel(Image&);
Image& m_image;
};
}

View file

@ -0,0 +1,152 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "LineTool.h"
#include "ImageEditor.h"
#include "Layer.h"
#include <LibGUI/Action.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Painter.h>
#include <LibM/math.h>
namespace PixelPaint {
static Gfx::Point constrain_line_angle(const Gfx::Point& start_pos, const Gfx::Point& end_pos, float angle_increment)
{
float current_angle = atan2(end_pos.y() - start_pos.y(), end_pos.x() - start_pos.x()) + 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 = sqrt(diff.x() * diff.x() + diff.y() * diff.y());
return { start_pos.x() + (int)(cos(constrained_angle) * line_length),
start_pos.y() + (int)(sin(constrained_angle) * line_length) };
}
LineTool::LineTool()
{
}
LineTool::~LineTool()
{
}
void LineTool::on_mousedown(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (event.button() != GUI::MouseButton::Left && event.button() != GUI::MouseButton::Right)
return;
if (m_drawing_button != GUI::MouseButton::None)
return;
m_drawing_button = event.button();
m_line_start_position = event.position();
m_line_end_position = event.position();
m_editor->update();
}
void LineTool::on_mouseup(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (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;
m_editor->update();
}
}
void LineTool::on_mousemove(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (m_drawing_button == GUI::MouseButton::None)
return;
if (!m_constrain_angle) {
m_line_end_position = event.position();
} else {
const float ANGLE_STEP = M_PI / 8.0f;
m_line_end_position = constrain_line_angle(m_line_start_position, event.position(), ANGLE_STEP);
}
m_editor->update();
}
void LineTool::on_second_paint(const Layer& layer, GUI::PaintEvent& event)
{
if (m_drawing_button == GUI::MouseButton::None)
return;
GUI::Painter painter(*m_editor);
painter.add_clip_rect(event.rect());
painter.translate(layer.location());
painter.draw_line(m_line_start_position, m_line_end_position, m_editor->color_for(m_drawing_button), m_thickness);
}
void LineTool::on_keydown(GUI::KeyEvent& event)
{
if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) {
m_drawing_button = GUI::MouseButton::None;
m_editor->update();
event.accept();
}
if (event.key() == Key_Shift) {
m_constrain_angle = true;
m_editor->update();
event.accept();
}
}
void LineTool::on_keyup(GUI::KeyEvent& event)
{
if (event.key() == Key_Shift) {
m_constrain_angle = false;
m_editor->update();
event.accept();
}
}
void LineTool::on_tool_button_contextmenu(GUI::ContextMenuEvent& event)
{
if (!m_context_menu) {
m_context_menu = GUI::Menu::construct();
m_thickness_actions.set_exclusive(true);
auto insert_action = [&](int size, bool checked = false) {
auto action = GUI::Action::create_checkable(String::number(size), [this, size](auto&) {
m_thickness = size;
});
action->set_checked(checked);
m_thickness_actions.add_action(*action);
m_context_menu->add_action(move(action));
};
insert_action(1, true);
insert_action(2);
insert_action(3);
insert_action(4);
}
m_context_menu->popup(event.screen_position());
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) override;
virtual void on_second_paint(const Layer&, GUI::PaintEvent&) override;
virtual void on_keydown(GUI::KeyEvent&) override;
virtual void on_keyup(GUI::KeyEvent&) override;
private:
virtual const char* class_name() const override { return "LineTool"; }
GUI::MouseButton m_drawing_button { GUI::MouseButton::None };
Gfx::Point m_line_start_position;
Gfx::Point m_line_end_position;
RefPtr<GUI::Menu> m_context_menu;
GUI::ActionGroup m_thickness_actions;
int m_thickness { 1 };
bool m_constrain_angle { false };
};
}

View file

@ -0,0 +1,132 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "MoveTool.h"
#include "Image.h"
#include "ImageEditor.h"
#include "Layer.h"
#include <LibGUI/Action.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Window.h>
#include <LibGfx/Bitmap.h>
namespace PixelPaint {
MoveTool::MoveTool()
{
}
MoveTool::~MoveTool()
{
}
void MoveTool::on_mousedown(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent& original_event)
{
if (event.button() != GUI::MouseButton::Left)
return;
if (!layer.rect().contains(event.position()))
return;
m_layer_being_moved = layer;
m_event_origin = original_event.position();
m_layer_origin = layer.location();
m_editor->window()->set_override_cursor(GUI::StandardCursor::Move);
}
void MoveTool::on_mousemove(Layer&, GUI::MouseEvent&, GUI::MouseEvent& original_event)
{
if (!m_layer_being_moved)
return;
auto delta = original_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&, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (event.button() != GUI::MouseButton::Left)
return;
m_layer_being_moved = nullptr;
m_editor->window()->set_override_cursor(GUI::StandardCursor::None);
}
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.move_by(0, -1);
break;
case Key_Down:
new_location.move_by(0, 1);
break;
case Key_Left:
new_location.move_by(-1, 0);
break;
case Key_Right:
new_location.move_by(1, 0);
break;
default:
return;
}
layer->set_location(new_location);
m_editor->layers_did_change();
}
void MoveTool::on_context_menu(Layer& layer, GUI::ContextMenuEvent& event)
{
if (!m_context_menu) {
m_context_menu = GUI::Menu::construct();
m_context_menu->add_action(GUI::CommonActions::make_move_to_front_action([this](auto&) {
m_editor->image()->move_layer_to_front(*m_context_menu_layer);
m_editor->layers_did_change();
}, m_editor));
m_context_menu->add_action(GUI::CommonActions::make_move_to_back_action([this](auto&) {
m_editor->image()->move_layer_to_back(*m_context_menu_layer);
m_editor->layers_did_change();
}, m_editor));
m_context_menu->add_separator();
m_context_menu->add_action(GUI::Action::create("Delete layer", Gfx::Bitmap::load_from_file("/res/icons/16x16/delete.png"), [this](auto&) {
m_editor->image()->remove_layer(*m_context_menu_layer);
// FIXME: This should not be done imperatively here. Perhaps a Image::Client interface that ImageEditor can implement?
if (m_editor->active_layer() == m_context_menu_layer)
m_editor->set_active_layer(nullptr);
m_editor->layers_did_change();
}, m_editor));
}
m_context_menu_layer = layer;
m_context_menu->popup(event.screen_position());
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "Tool.h"
namespace PixelPaint {
class MoveTool final : public Tool {
public:
MoveTool();
virtual ~MoveTool() override;
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_keydown(GUI::KeyEvent&) override;
virtual void on_context_menu(Layer&, GUI::ContextMenuEvent&) override;
private:
virtual const char* class_name() const override { return "MoveTool"; }
virtual bool is_move_tool() const override { return true; }
RefPtr<Layer> m_layer_being_moved;
Gfx::Point m_event_origin;
Gfx::Point m_layer_origin;
RefPtr<GUI::Menu> m_context_menu;
RefPtr<Layer> m_context_menu_layer;
};
}

View file

@ -0,0 +1,179 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "PaletteWidget.h"
#include "ImageEditor.h"
#include <LibGUI/BoxLayout.h>
#include <LibGUI/ColorPicker.h>
#include <LibGfx/Palette.h>
namespace PixelPaint {
class ColorWidget : public GUI::Frame {
C_OBJECT(ColorWidget);
public:
explicit ColorWidget(Color color, PaletteWidget& palette_widget)
: m_palette_widget(palette_widget)
, m_color(color)
{
}
virtual ~ColorWidget() override
{
}
virtual void mousedown_event(GUI::MouseEvent& event) override
{
if (event.modifiers() & KeyModifier::Mod_Ctrl && event.button() == GUI::MouseButton::Left) {
auto dialog = GUI::ColorPicker::construct(m_color, window());
if (dialog->exec() == GUI::Dialog::ExecOK) {
m_color = dialog->color();
auto pal = palette();
pal.set_color(ColorRole::Background, m_color);
set_palette(pal);
update();
}
return;
}
if (event.button() == GUI::MouseButton::Left)
m_palette_widget.set_primary_color(m_color);
else if (event.button() == GUI::MouseButton::Right)
m_palette_widget.set_secondary_color(m_color);
}
private:
PaletteWidget& m_palette_widget;
Color m_color;
};
PaletteWidget::PaletteWidget(ImageEditor& editor)
: m_editor(editor)
{
set_frame_shape(Gfx::FrameShape::Panel);
set_frame_shadow(Gfx::FrameShadow::Raised);
set_frame_thickness(0);
set_fill_with_background_color(true);
set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
set_preferred_size(0, 34);
m_secondary_color_widget = add<GUI::Frame>();
m_secondary_color_widget->set_relative_rect({ 2, 2, 60, 31 });
m_secondary_color_widget->set_fill_with_background_color(true);
set_secondary_color(m_editor.secondary_color());
m_primary_color_widget = add<GUI::Frame>();
Gfx::Rect rect { 0, 0, 38, 15 };
rect.center_within(m_secondary_color_widget->relative_rect());
m_primary_color_widget->set_relative_rect(rect);
m_primary_color_widget->set_fill_with_background_color(true);
set_primary_color(m_editor.primary_color());
m_editor.on_primary_color_change = [this](Color color) {
set_primary_color(color);
};
m_editor.on_secondary_color_change = [this](Color color) {
set_secondary_color(color);
};
auto& color_container = add<GUI::Widget>();
color_container.set_relative_rect(m_secondary_color_widget->relative_rect().right() + 2, 2, 500, 32);
color_container.set_layout<GUI::VerticalBoxLayout>();
color_container.layout()->set_spacing(1);
auto& top_color_container = color_container.add<GUI::Widget>();
top_color_container.set_layout<GUI::HorizontalBoxLayout>();
top_color_container.layout()->set_spacing(1);
auto& bottom_color_container = color_container.add<GUI::Widget>();
bottom_color_container.set_layout<GUI::HorizontalBoxLayout>();
bottom_color_container.layout()->set_spacing(1);
auto add_color_widget = [&](GUI::Widget& container, Color color) {
auto& color_widget = container.add<ColorWidget>(color, *this);
color_widget.set_fill_with_background_color(true);
auto pal = color_widget.palette();
pal.set_color(ColorRole::Background, color);
color_widget.set_palette(pal);
};
add_color_widget(top_color_container, Color::from_rgb(0x000000));
add_color_widget(top_color_container, Color::from_rgb(0x808080));
add_color_widget(top_color_container, Color::from_rgb(0x800000));
add_color_widget(top_color_container, Color::from_rgb(0x808000));
add_color_widget(top_color_container, Color::from_rgb(0x008000));
add_color_widget(top_color_container, Color::from_rgb(0x008080));
add_color_widget(top_color_container, Color::from_rgb(0x000080));
add_color_widget(top_color_container, Color::from_rgb(0x800080));
add_color_widget(top_color_container, Color::from_rgb(0x808040));
add_color_widget(top_color_container, Color::from_rgb(0x004040));
add_color_widget(top_color_container, Color::from_rgb(0x0080ff));
add_color_widget(top_color_container, Color::from_rgb(0x004080));
add_color_widget(top_color_container, Color::from_rgb(0x8000ff));
add_color_widget(top_color_container, Color::from_rgb(0x804000));
add_color_widget(bottom_color_container, Color::from_rgb(0xffffff));
add_color_widget(bottom_color_container, Color::from_rgb(0xc0c0c0));
add_color_widget(bottom_color_container, Color::from_rgb(0xff0000));
add_color_widget(bottom_color_container, Color::from_rgb(0xffff00));
add_color_widget(bottom_color_container, Color::from_rgb(0x00ff00));
add_color_widget(bottom_color_container, Color::from_rgb(0x00ffff));
add_color_widget(bottom_color_container, Color::from_rgb(0x0000ff));
add_color_widget(bottom_color_container, Color::from_rgb(0xff00ff));
add_color_widget(bottom_color_container, Color::from_rgb(0xffff80));
add_color_widget(bottom_color_container, Color::from_rgb(0x00ff80));
add_color_widget(bottom_color_container, Color::from_rgb(0x80ffff));
add_color_widget(bottom_color_container, Color::from_rgb(0x8080ff));
add_color_widget(bottom_color_container, Color::from_rgb(0xff0080));
add_color_widget(bottom_color_container, Color::from_rgb(0xff8040));
}
PaletteWidget::~PaletteWidget()
{
}
void PaletteWidget::set_primary_color(Color color)
{
m_editor.set_primary_color(color);
auto pal = m_primary_color_widget->palette();
pal.set_color(ColorRole::Background, color);
m_primary_color_widget->set_palette(pal);
m_primary_color_widget->update();
}
void PaletteWidget::set_secondary_color(Color color)
{
m_editor.set_secondary_color(color);
auto pal = m_secondary_color_widget->palette();
pal.set_color(ColorRole::Background, color);
m_secondary_color_widget->set_palette(pal);
m_secondary_color_widget->update();
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <LibGUI/Frame.h>
namespace PixelPaint {
class ImageEditor;
class PaletteWidget final : public GUI::Frame {
C_OBJECT(PaletteWidget);
public:
virtual ~PaletteWidget() override;
void set_primary_color(Color);
void set_secondary_color(Color);
private:
explicit PaletteWidget(ImageEditor&);
ImageEditor& m_editor;
RefPtr<GUI::Frame> m_primary_color_widget;
RefPtr<GUI::Frame> m_secondary_color_widget;
};
}

View file

@ -0,0 +1,97 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "PenTool.h"
#include "ImageEditor.h"
#include "Layer.h"
#include <LibGUI/Action.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Painter.h>
namespace PixelPaint {
PenTool::PenTool()
{
}
PenTool::~PenTool()
{
}
void PenTool::on_mousedown(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (event.button() != GUI::MouseButton::Left && event.button() != GUI::MouseButton::Right)
return;
GUI::Painter painter(layer.bitmap());
painter.draw_line(event.position(), event.position(), m_editor->color_for(event), m_thickness);
m_editor->update();
m_last_drawing_event_position = event.position();
}
void PenTool::on_mouseup(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (event.button() == GUI::MouseButton::Left || event.button() == GUI::MouseButton::Right)
m_last_drawing_event_position = { -1, -1 };
}
void PenTool::on_mousemove(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (!(event.buttons() & GUI::MouseButton::Left || event.buttons() & GUI::MouseButton::Right))
return;
GUI::Painter painter(layer.bitmap());
if (m_last_drawing_event_position != Gfx::Point(-1, -1))
painter.draw_line(m_last_drawing_event_position, event.position(), m_editor->color_for(event), m_thickness);
else
painter.draw_line(event.position(), event.position(), m_editor->color_for(event), m_thickness);
m_editor->update();
m_last_drawing_event_position = event.position();
}
void PenTool::on_tool_button_contextmenu(GUI::ContextMenuEvent& event)
{
if (!m_context_menu) {
m_context_menu = GUI::Menu::construct();
m_thickness_actions.set_exclusive(true);
auto insert_action = [&](int size, bool checked = false) {
auto action = GUI::Action::create_checkable(String::number(size), [this, size](auto&) {
m_thickness = size;
});
action->set_checked(checked);
m_thickness_actions.add_action(*action);
m_context_menu->add_action(move(action));
};
insert_action(1, true);
insert_action(2);
insert_action(3);
insert_action(4);
}
m_context_menu->popup(event.screen_position());
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "Tool.h"
#include <LibGfx/Point.h>
#include <LibGUI/ActionGroup.h>
namespace PixelPaint {
class PenTool final : public Tool {
public:
PenTool();
virtual ~PenTool() override;
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) override;
private:
virtual const char* class_name() const override { return "PenTool"; }
Gfx::Point m_last_drawing_event_position { -1, -1 };
RefPtr<GUI::Menu> m_context_menu;
int m_thickness { 1 };
GUI::ActionGroup m_thickness_actions;
};
}

View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "PickerTool.h"
#include "ImageEditor.h"
#include "Layer.h"
#include <LibGfx/Bitmap.h>
namespace PixelPaint {
PickerTool::PickerTool()
{
}
PickerTool::~PickerTool()
{
}
void PickerTool::on_mousedown(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (!layer.rect().contains(event.position()))
return;
auto color = layer.bitmap().get_pixel(event.position());
if (event.button() == GUI::MouseButton::Left)
m_editor->set_primary_color(color);
else if (event.button() == GUI::MouseButton::Right)
m_editor->set_secondary_color(color);
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "Tool.h"
namespace PixelPaint {
class PickerTool final : public Tool {
public:
PickerTool();
virtual ~PickerTool() override;
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
private:
virtual const char* class_name() const override { return "PickerTool"; }
};
}

View file

@ -0,0 +1,134 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "RectangleTool.h"
#include "ImageEditor.h"
#include "Layer.h"
#include <LibGUI/Action.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Painter.h>
#include <LibGfx/Rect.h>
#include <LibM/math.h>
namespace PixelPaint {
RectangleTool::RectangleTool()
{
}
RectangleTool::~RectangleTool()
{
}
void RectangleTool::draw_using(GUI::Painter& painter)
{
auto rect_to_draw = Gfx::Rect::from_two_points(m_rectangle_start_position, m_rectangle_end_position);
switch (m_mode) {
case Mode::Fill:
painter.fill_rect(rect_to_draw, m_editor->color_for(m_drawing_button));
break;
case Mode::Outline:
painter.draw_rect(rect_to_draw, m_editor->color_for(m_drawing_button));
break;
case Mode::Gradient:
painter.fill_rect_with_gradient(rect_to_draw, m_editor->primary_color(), m_editor->secondary_color());
break;
default:
ASSERT_NOT_REACHED();
}
}
void RectangleTool::on_mousedown(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (event.button() != GUI::MouseButton::Left && event.button() != GUI::MouseButton::Right)
return;
if (m_drawing_button != GUI::MouseButton::None)
return;
m_drawing_button = event.button();
m_rectangle_start_position = event.position();
m_rectangle_end_position = event.position();
m_editor->update();
}
void RectangleTool::on_mouseup(Layer& layer, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (event.button() == m_drawing_button) {
GUI::Painter painter(layer.bitmap());
draw_using(painter);
m_drawing_button = GUI::MouseButton::None;
m_editor->update();
}
}
void RectangleTool::on_mousemove(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
{
if (m_drawing_button == GUI::MouseButton::None)
return;
m_rectangle_end_position = event.position();
m_editor->update();
}
void RectangleTool::on_second_paint(const Layer& layer, GUI::PaintEvent& event)
{
if (m_drawing_button == GUI::MouseButton::None)
return;
GUI::Painter painter(*m_editor);
painter.add_clip_rect(event.rect());
painter.translate(layer.location());
draw_using(painter);
}
void RectangleTool::on_keydown(GUI::KeyEvent& event)
{
if (event.key() == Key_Escape && m_drawing_button != GUI::MouseButton::None) {
m_drawing_button = GUI::MouseButton::None;
m_editor->update();
event.accept();
}
}
void RectangleTool::on_tool_button_contextmenu(GUI::ContextMenuEvent& event)
{
if (!m_context_menu) {
m_context_menu = GUI::Menu::construct();
m_context_menu->add_action(GUI::Action::create("Fill", [this](auto&) {
m_mode = Mode::Fill;
}));
m_context_menu->add_action(GUI::Action::create("Outline", [this](auto&) {
m_mode = Mode::Outline;
}));
m_context_menu->add_action(GUI::Action::create("Gradient", [this](auto&) {
m_mode = Mode::Gradient;
}));
}
m_context_menu->popup(event.screen_position());
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "Tool.h"
#include <LibGfx/Point.h>
#include <LibGUI/Forward.h>
namespace PixelPaint {
class RectangleTool final : public Tool {
public:
RectangleTool();
virtual ~RectangleTool() override;
virtual void on_mousedown(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) override;
virtual void on_second_paint(const Layer&, GUI::PaintEvent&) override;
virtual void on_keydown(GUI::KeyEvent&) override;
private:
enum class Mode {
Outline,
Fill,
Gradient,
};
virtual const char* class_name() const override { return "RectangleTool"; }
void draw_using(GUI::Painter&);
GUI::MouseButton m_drawing_button { GUI::MouseButton::None };
Gfx::Point m_rectangle_start_position;
Gfx::Point m_rectangle_end_position;
RefPtr<GUI::Menu> m_context_menu;
Mode m_mode { Mode::Outline };
};
}

View file

@ -0,0 +1,127 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "SprayTool.h"
#include "ImageEditor.h"
#include "Layer.h"
#include <AK/Queue.h>
#include <AK/SinglyLinkedList.h>
#include <LibGUI/Action.h>
#include <LibGUI/Menu.h>
#include <LibGUI/Painter.h>
#include <LibGfx/Bitmap.h>
#include <LibM/math.h>
#include <stdio.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);
ASSERT(bitmap.bpp() == 32);
m_editor->update();
const double minimal_radius = 10;
const double base_radius = minimal_radius * m_thickness;
for (int i = 0; i < 100 + (nrand() * 800); i++) {
double radius = base_radius * nrand();
double angle = 2 * M_PI * nrand();
const int xpos = m_last_pos.x() + radius * cos(angle);
const int ypos = m_last_pos.y() - radius * sin(angle);
if (xpos < 0 || xpos >= bitmap.width())
continue;
if (ypos < 0 || ypos >= bitmap.height())
continue;
bitmap.set_pixel<Gfx::BitmapFormat::RGB32>(xpos, ypos, m_color);
}
}
void SprayTool::on_mousedown(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
{
m_color = m_editor->color_for(event);
m_last_pos = event.position();
m_timer->start();
paint_it();
}
void SprayTool::on_mousemove(Layer&, GUI::MouseEvent& event, GUI::MouseEvent&)
{
m_last_pos = event.position();
if (m_timer->is_active()) {
paint_it();
m_timer->restart(m_timer->interval());
}
}
void SprayTool::on_mouseup(Layer&, GUI::MouseEvent&, GUI::MouseEvent&)
{
m_timer->stop();
}
void SprayTool::on_tool_button_contextmenu(GUI::ContextMenuEvent& event)
{
if (!m_context_menu) {
m_context_menu = GUI::Menu::construct();
m_thickness_actions.set_exclusive(true);
auto insert_action = [&](int size, bool checked = false) {
auto action = GUI::Action::create_checkable(String::number(size), [this, size](auto&) {
m_thickness = size;
});
action->set_checked(checked);
m_thickness_actions.add_action(*action);
m_context_menu->add_action(move(action));
};
insert_action(1, true);
insert_action(2);
insert_action(3);
insert_action(4);
}
m_context_menu->popup(event.screen_position());
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#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&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_mouseup(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_mousemove(Layer&, GUI::MouseEvent& layer_event, GUI::MouseEvent& original_event) override;
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) override;
private:
virtual const char* class_name() const override { return "SprayTool"; }
void paint_it();
RefPtr<Core::Timer> m_timer;
Gfx::Point m_last_pos;
Color m_color;
RefPtr<GUI::Menu> m_context_menu;
GUI::ActionGroup m_thickness_actions;
int m_thickness { 1 };
};
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Tool.h"
#include "ImageEditor.h"
#include <LibGUI/Action.h>
namespace PixelPaint {
Tool::Tool()
{
}
Tool::~Tool()
{
}
void Tool::setup(ImageEditor& editor)
{
m_editor = editor.make_weak_ptr();
}
void Tool::set_action(GUI::Action* action)
{
m_action = action;
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <LibGUI/Event.h>
#include <LibGUI/Forward.h>
namespace PixelPaint {
class ImageEditor;
class Layer;
class Tool {
public:
virtual ~Tool();
virtual const char* class_name() const = 0;
virtual void on_mousedown(Layer&, GUI::MouseEvent&, GUI::MouseEvent&) {}
virtual void on_mousemove(Layer&, GUI::MouseEvent&, GUI::MouseEvent&) {}
virtual void on_mouseup(Layer&, GUI::MouseEvent&, GUI::MouseEvent&) {}
virtual void on_context_menu(Layer&, GUI::ContextMenuEvent&) {}
virtual void on_tool_button_contextmenu(GUI::ContextMenuEvent&) {}
virtual void on_second_paint(const Layer&, GUI::PaintEvent&) {}
virtual void on_keydown(GUI::KeyEvent&) {}
virtual void on_keyup(GUI::KeyEvent&) {}
virtual bool is_move_tool() const { return false; }
void clear() { m_editor = nullptr; }
void setup(ImageEditor&);
GUI::Action* action() { return m_action; }
void set_action(GUI::Action*);
protected:
Tool();
WeakPtr<ImageEditor> m_editor;
RefPtr<GUI::Action> m_action;
};
}

View file

@ -0,0 +1,129 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "ToolboxWidget.h"
#include "BucketTool.h"
#include "EllipseTool.h"
#include "EraseTool.h"
#include "LineTool.h"
#include "MoveTool.h"
#include "PenTool.h"
#include "PickerTool.h"
#include "RectangleTool.h"
#include "SprayTool.h"
#include <AK/StringBuilder.h>
#include <LibGUI/Action.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
namespace PixelPaint {
class ToolButton final : public GUI::Button {
C_OBJECT(ToolButton)
public:
ToolButton(ToolboxWidget& toolbox, const String& name, const GUI::Shortcut& shortcut, OwnPtr<Tool> tool)
: m_toolbox(toolbox)
, m_tool(move(tool))
{
StringBuilder builder;
builder.append(name);
builder.append(" (");
builder.append(shortcut.to_string());
builder.append(")");
set_tooltip(builder.to_string());
m_action = GUI::Action::create_checkable(name, shortcut, [this](auto& action) {
if (action.is_checked())
m_toolbox.on_tool_selection(m_tool);
else
m_toolbox.on_tool_selection(nullptr);
});
m_tool->set_action(m_action);
set_action(*m_action);
m_toolbox.m_action_group.add_action(*m_action);
}
const Tool& tool() const { return *m_tool; }
Tool& tool() { return *m_tool; }
virtual bool is_uncheckable() const override { return false; }
virtual void context_menu_event(GUI::ContextMenuEvent& event) override
{
m_action->activate();
m_tool->on_tool_button_contextmenu(event);
}
private:
ToolboxWidget& m_toolbox;
OwnPtr<Tool> m_tool;
RefPtr<GUI::Action> m_action;
};
ToolboxWidget::ToolboxWidget()
{
set_fill_with_background_color(true);
set_frame_thickness(1);
set_frame_shape(Gfx::FrameShape::Panel);
set_frame_shadow(Gfx::FrameShadow::Raised);
set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill);
set_preferred_size(48, 0);
set_layout<GUI::VerticalBoxLayout>();
layout()->set_margins({ 4, 4, 4, 4 });
m_action_group.set_exclusive(true);
m_action_group.set_unchecking_allowed(false);
auto add_tool = [&](const StringView& name, const StringView& icon_name, const GUI::Shortcut& shortcut, NonnullOwnPtr<Tool> tool) -> ToolButton& {
m_tools.append(tool.ptr());
auto& button = add<ToolButton>(*this, name, shortcut, move(tool));
button.set_size_policy(GUI::SizePolicy::Fill, GUI::SizePolicy::Fixed);
button.set_preferred_size(0, 32);
button.set_checkable(true);
button.set_icon(Gfx::Bitmap::load_from_file(String::format("/res/icons/paintbrush/%s.png", icon_name.to_string().characters())));
return button;
};
add_tool("Move", "move", { 0, Key_M }, make<MoveTool>());
add_tool("Pen", "pen", { 0, Key_N }, make<PenTool>());
add_tool("Bucket Fill", "bucket", { Mod_Shift, Key_B }, make<BucketTool>());
add_tool("Spray", "spray", { Mod_Shift, Key_S }, make<SprayTool>());
add_tool("Color Picker", "picker", { 0, Key_O }, make<PickerTool>());
add_tool("Erase", "eraser", { Mod_Shift, Key_E }, make<EraseTool>());
add_tool("Line", "line", { Mod_Ctrl | Mod_Shift, Key_L }, make<LineTool>());
add_tool("Rectangle", "rectangle", { Mod_Ctrl | Mod_Shift, Key_R }, make<RectangleTool>());
add_tool("Ellipse", "circle", { Mod_Ctrl | Mod_Shift, Key_E }, make<EllipseTool>());
}
ToolboxWidget::~ToolboxWidget()
{
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <LibGUI/ActionGroup.h>
#include <LibGUI/Frame.h>
namespace PixelPaint {
class Tool;
class ToolboxWidget final : public GUI::Frame {
C_OBJECT(ToolboxWidget)
public:
virtual ~ToolboxWidget() override;
Function<void(Tool*)> on_tool_selection;
template<typename Callback>
void for_each_tool(Callback callback)
{
for (auto& tool : m_tools)
callback(*tool);
}
private:
friend class ToolButton;
explicit ToolboxWidget();
GUI::ActionGroup m_action_group;
Vector<Tool*> m_tools;
};
}

View file

@ -0,0 +1,213 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "CreateNewLayerDialog.h"
#include "Image.h"
#include "ImageEditor.h"
#include "Layer.h"
#include "PaletteWidget.h"
#include "Tool.h"
#include "ToolboxWidget.h"
#include <LibGUI/AboutDialog.h>
#include <LibGUI/Action.h>
#include <LibGUI/Application.h>
#include <LibGUI/BoxLayout.h>
#include <LibGUI/FilePicker.h>
#include <LibGUI/Menu.h>
#include <LibGUI/MenuBar.h>
#include <LibGUI/MessageBox.h>
#include <LibGUI/Model.h>
#include <LibGUI/TableView.h>
#include <LibGUI/Window.h>
#include <LibGfx/Bitmap.h>
#include <stdio.h>
int main(int argc, char** argv)
{
if (pledge("stdio thread shared_buffer accept rpath unix wpath cpath fattr", nullptr) < 0) {
perror("pledge");
return 1;
}
GUI::Application app(argc, argv);
if (pledge("stdio thread shared_buffer accept rpath wpath cpath", nullptr) < 0) {
perror("pledge");
return 1;
}
auto window = GUI::Window::construct();
window->set_title("PixelPaint");
window->set_rect(40, 100, 950, 570);
window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-paintbrush.png"));
auto& horizontal_container = window->set_main_widget<GUI::Widget>();
horizontal_container.set_layout<GUI::HorizontalBoxLayout>();
horizontal_container.layout()->set_spacing(0);
auto& toolbox = horizontal_container.add<PixelPaint::ToolboxWidget>();
auto& vertical_container = horizontal_container.add<GUI::Widget>();
vertical_container.set_layout<GUI::VerticalBoxLayout>();
vertical_container.layout()->set_spacing(0);
auto& image_editor = vertical_container.add<PixelPaint::ImageEditor>();
image_editor.set_focus(true);
toolbox.on_tool_selection = [&](auto* tool) {
image_editor.set_active_tool(tool);
};
vertical_container.add<PixelPaint::PaletteWidget>(image_editor);
auto& right_panel = horizontal_container.add<GUI::Widget>();
right_panel.set_size_policy(GUI::SizePolicy::Fixed, GUI::SizePolicy::Fill);
right_panel.set_preferred_size(230, 0);
right_panel.set_layout<GUI::VerticalBoxLayout>();
auto& layer_table_view = right_panel.add<GUI::TableView>();
layer_table_view.set_size_columns_to_fit_content(true);
window->show();
auto menubar = GUI::MenuBar::construct();
auto& app_menu = menubar->add_menu("PixelPaint");
app_menu.add_action(GUI::CommonActions::make_open_action([&](auto&) {
Optional<String> open_path = GUI::FilePicker::get_open_filepath();
if (!open_path.has_value())
return;
auto bitmap = Gfx::Bitmap::load_from_file(open_path.value());
if (!bitmap) {
GUI::MessageBox::show(String::format("Failed to load '%s'", open_path.value().characters()), "Open failed", GUI::MessageBox::Type::Error, GUI::MessageBox::InputType::OK, window);
return;
}
}));
app_menu.add_separator();
app_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
GUI::Application::the().quit(0);
return;
}));
menubar->add_menu("Edit");
auto& tool_menu = menubar->add_menu("Tool");
toolbox.for_each_tool([&](auto& tool) {
if (tool.action())
tool_menu.add_action(*tool.action());
return IterationDecision::Continue;
});
auto& layer_menu = menubar->add_menu("Layer");
layer_menu.add_action(GUI::Action::create("Create new layer...", { Mod_Ctrl | Mod_Shift, Key_N }, [&](auto&) {
auto dialog = PixelPaint::CreateNewLayerDialog::construct(image_editor.image()->size(), window);
if (dialog->exec() == GUI::Dialog::ExecOK) {
auto layer = PixelPaint::Layer::create_with_size(dialog->layer_size(), dialog->layer_name());
if (!layer) {
GUI::MessageBox::show_error(String::format("Unable to create layer with size %s", dialog->size().to_string().characters()));
return;
}
image_editor.image()->add_layer(layer.release_nonnull());
image_editor.layers_did_change();
}
}, window));
layer_menu.add_separator();
layer_menu.add_action(GUI::Action::create("Select previous layer", { 0, Key_PageUp }, [&](auto&) {
layer_table_view.move_selection(1);
}, window));
layer_menu.add_action(GUI::Action::create("Select next layer", { 0, Key_PageDown }, [&](auto&) {
layer_table_view.move_selection(-1);
}, window));
layer_menu.add_action(GUI::Action::create("Select top layer", { 0, Key_Home }, [&](auto&) {
layer_table_view.selection().set(layer_table_view.model()->index(image_editor.image()->layer_count() - 1));
}, window));
layer_menu.add_action(GUI::Action::create("Select bottom layer", { 0, Key_End }, [&](auto&) {
layer_table_view.selection().set(layer_table_view.model()->index(0));
}, window));
layer_menu.add_separator();
layer_menu.add_action(GUI::Action::create("Move active layer up", { Mod_Ctrl, Key_PageUp }, [&](auto&) {
auto active_layer = image_editor.active_layer();
if(!active_layer)
return;
image_editor.image()->move_layer_up(*active_layer);
layer_table_view.move_selection(1);
image_editor.layers_did_change();
}, window));
layer_menu.add_action(GUI::Action::create("Move active layer down", { Mod_Ctrl, Key_PageDown }, [&](auto&) {
auto active_layer = image_editor.active_layer();
if(!active_layer)
return;
image_editor.image()->move_layer_down(*active_layer);
layer_table_view.move_selection(-1);
image_editor.layers_did_change();
}, window));
layer_menu.add_separator();
layer_menu.add_action(GUI::Action::create("Remove active layer", { Mod_Ctrl , Key_D }, [&](auto&) {
auto active_layer = image_editor.active_layer();
if(!active_layer)
return;
image_editor.image()->remove_layer(*active_layer);
image_editor.set_active_layer(nullptr);
image_editor.layers_did_change();
}, window));
auto& help_menu = menubar->add_menu("Help");
help_menu.add_action(GUI::Action::create("About", [&](auto&) {
GUI::AboutDialog::show("PixelPaint", Gfx::Bitmap::load_from_file("/res/icons/32x32/app-paintbrush.png"), window);
}));
app.set_menubar(move(menubar));
image_editor.on_active_layer_change = [&](auto& index) {
if (index.is_valid())
layer_table_view.selection().set(index);
else
layer_table_view.selection().clear();
};
auto image = PixelPaint::Image::create_with_size({ 640, 480 });
auto bg_layer = PixelPaint::Layer::create_with_size({ 640, 480 }, "Background");
image->add_layer(*bg_layer);
bg_layer->bitmap().fill(Color::White);
layer_table_view.set_model(image->layer_model());
layer_table_view.on_selection_change = [&] {
auto index = layer_table_view.selection().first();
if (index.is_valid())
image_editor.set_active_layer(const_cast<PixelPaint::Layer*>(&image->layer(index.row())));
else
image_editor.set_active_layer(nullptr);
};
image_editor.set_image(image);
image_editor.set_active_layer(bg_layer);
return app.exec();
}