summaryrefslogtreecommitdiff
path: root/Userland/Applications/PixelPaint
diff options
context:
space:
mode:
authorMustafa Quraish <mustafaq9@gmail.com>2021-09-06 00:11:46 -0400
committerAndreas Kling <kling@serenityos.org>2021-09-06 10:36:08 +0200
commit5a8c6b95e61da7717388241e0c4733de8c1d267f (patch)
tree17800e74aff7cda7d5c3af12d71d008d2e92aa6c /Userland/Applications/PixelPaint
parentbbddfeef4bf5dcebd50828806d55a60224ece6b9 (diff)
downloadserenity-5a8c6b95e61da7717388241e0c4733de8c1d267f.zip
PixelPaint: Refactor `main.cpp` into `MainWidget`
Previously, all the UI setup was done in `main.cpp`, with a whole bunch of lambdas,, and actions etc just being stored in the main function. This is probably an artifact from back when it was first created. Most other applications now have a "MainWidget" class of some sort which handles setting up all the UI/menubars, etc. More importantly,, it also lets us handle application-wide events which we were previously not able to do directly, since the main widget was just a default GUI::Widget. This patch moves all the core functionality of the PixelPaint application into PixelPaint::MainWidget, which is then instantiated by the main function. There is likely some more refactoring that would help, but this commit is big enough as it is doing mostly a direct port.
Diffstat (limited to 'Userland/Applications/PixelPaint')
-rw-r--r--Userland/Applications/PixelPaint/CMakeLists.txt1
-rw-r--r--Userland/Applications/PixelPaint/MainWidget.cpp776
-rw-r--r--Userland/Applications/PixelPaint/MainWidget.h73
-rw-r--r--Userland/Applications/PixelPaint/main.cpp791
4 files changed, 856 insertions, 785 deletions
diff --git a/Userland/Applications/PixelPaint/CMakeLists.txt b/Userland/Applications/PixelPaint/CMakeLists.txt
index 3fe320a349..7c87a29487 100644
--- a/Userland/Applications/PixelPaint/CMakeLists.txt
+++ b/Userland/Applications/PixelPaint/CMakeLists.txt
@@ -24,6 +24,7 @@ set(SOURCES
LayerListWidget.cpp
LayerPropertiesWidget.cpp
LineTool.cpp
+ MainWidget.cpp
main.cpp
MoveTool.cpp
PaletteWidget.cpp
diff --git a/Userland/Applications/PixelPaint/MainWidget.cpp b/Userland/Applications/PixelPaint/MainWidget.cpp
new file mode 100644
index 0000000000..116fef232f
--- /dev/null
+++ b/Userland/Applications/PixelPaint/MainWidget.cpp
@@ -0,0 +1,776 @@
+/*
+ * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, Mustafa Quraish <mustafa@cs.toronto.edu>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "MainWidget.h"
+#include "CreateNewImageDialog.h"
+#include "CreateNewLayerDialog.h"
+#include "EditGuideDialog.h"
+#include "FilterParams.h"
+#include <Applications/PixelPaint/PixelPaintWindowGML.h>
+#include <LibCore/File.h>
+#include <LibCore/MimeData.h>
+#include <LibFileSystemAccessClient/Client.h>
+#include <LibGUI/Application.h>
+#include <LibGUI/Clipboard.h>
+#include <LibGUI/Icon.h>
+#include <LibGUI/Menubar.h>
+#include <LibGUI/MessageBox.h>
+#include <LibGUI/Toolbar.h>
+#include <LibGfx/Bitmap.h>
+
+namespace PixelPaint {
+
+MainWidget::MainWidget()
+ : Widget()
+{
+ load_from_gml(pixel_paint_window_gml);
+
+ m_toolbox = find_descendant_of_type_named<PixelPaint::ToolboxWidget>("toolbox");
+ m_statusbar = *find_descendant_of_type_named<GUI::Statusbar>("statusbar");
+
+ m_tab_widget = find_descendant_of_type_named<GUI::TabWidget>("tab_widget");
+ m_tab_widget->set_container_margins({ 4, 5, 5, 4 });
+ m_tab_widget->set_close_button_enabled(true);
+
+ m_palette_widget = *find_descendant_of_type_named<PixelPaint::PaletteWidget>("palette_widget");
+
+ m_layer_list_widget = *find_descendant_of_type_named<PixelPaint::LayerListWidget>("layer_list_widget");
+ m_layer_list_widget->on_layer_select = [&](auto* layer) {
+ if (auto* editor = current_image_editor())
+ editor->set_active_layer(layer);
+ };
+
+ m_layer_properties_widget = *find_descendant_of_type_named<PixelPaint::LayerPropertiesWidget>("layer_properties_widget");
+ m_tool_properties_widget = *find_descendant_of_type_named<PixelPaint::ToolPropertiesWidget>("tool_properties_widget");
+
+ m_toolbox->on_tool_selection = [&](auto* tool) {
+ if (auto* editor = current_image_editor())
+ editor->set_active_tool(tool);
+ m_tool_properties_widget->set_active_tool(tool);
+ };
+
+ m_tab_widget->on_tab_close_click = [&](auto& widget) {
+ auto& image_editor = verify_cast<PixelPaint::ImageEditor>(widget);
+
+ if (m_tab_widget->children().size() == 1) {
+ m_layer_list_widget->set_image(nullptr);
+ m_layer_properties_widget->set_layer(nullptr);
+ }
+
+ m_tab_widget->deferred_invoke([&] {
+ m_tab_widget->remove_tab(image_editor);
+ });
+ };
+
+ m_tab_widget->on_change = [&](auto& widget) {
+ auto& image_editor = verify_cast<PixelPaint::ImageEditor>(widget);
+ m_palette_widget->set_image_editor(image_editor);
+ m_layer_list_widget->set_image(&image_editor.image());
+ m_layer_properties_widget->set_layer(image_editor.active_layer());
+ // FIXME: This is badly factored. It transfers tools from the previously active editor to the new one.
+ m_toolbox->template for_each_tool([&](auto& tool) {
+ if (tool.editor()) {
+ tool.editor()->set_active_tool(nullptr);
+ image_editor.set_active_tool(&tool);
+ }
+ });
+ m_show_guides_action->set_checked(image_editor.guide_visibility());
+ };
+}
+
+void MainWidget::initialize_menubar(GUI::Window& window)
+{
+ auto& file_menu = window.add_menu("&File");
+
+ m_new_image_action = GUI::Action::create(
+ "&New Image...", { Mod_Ctrl, Key_N }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/new.png"), [&](auto&) {
+ auto dialog = PixelPaint::CreateNewImageDialog::construct(&window);
+ if (dialog->exec() == GUI::Dialog::ExecOK) {
+ auto image = PixelPaint::Image::try_create_with_size(dialog->image_size());
+ auto bg_layer = PixelPaint::Layer::try_create_with_size(*image, image->size(), "Background");
+ VERIFY(bg_layer);
+ image->add_layer(*bg_layer);
+ bg_layer->bitmap().fill(Color::White);
+ auto image_title = dialog->image_name().trim_whitespace();
+ image->set_title(image_title.is_empty() ? "Untitled" : image_title);
+
+ create_new_editor(*image);
+ m_layer_list_widget->set_image(image);
+ m_layer_list_widget->set_selected_layer(bg_layer);
+ }
+ });
+
+ m_open_image_action = GUI::CommonActions::make_open_action([&](auto&) {
+ auto result = FileSystemAccessClient::Client::the().open_file(window.window_id());
+ if (result.error != 0)
+ return;
+
+ open_image_fd(*result.fd, *result.chosen_file);
+ });
+
+ m_save_image_as_action = GUI::CommonActions::make_save_as_action([&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ auto save_result = FileSystemAccessClient::Client::the().save_file(window.window_id(), "untitled", "pp");
+ if (save_result.error != 0)
+ return;
+ auto result = editor->save_project_to_fd_and_close(*save_result.fd);
+ if (result.is_error()) {
+ GUI::MessageBox::show_error(&window, String::formatted("Could not save {}: {}", *save_result.chosen_file, result.error()));
+ return;
+ }
+ editor->image().set_path(*save_result.chosen_file);
+ });
+
+ file_menu.add_action(*m_new_image_action);
+ file_menu.add_action(*m_open_image_action);
+ file_menu.add_action(*m_save_image_as_action);
+
+ auto& export_submenu = file_menu.add_submenu("&Export");
+
+ export_submenu.add_action(
+ GUI::Action::create(
+ "As &BMP", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ auto save_result = FileSystemAccessClient::Client::the().save_file(window.window_id(), "untitled", "bmp");
+ if (save_result.error != 0)
+ return;
+ auto preserve_alpha_channel = GUI::MessageBox::show(&window, "Do you wish to preserve transparency?", "Preserve transparency?", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
+ auto result = editor->image().export_bmp_to_fd_and_close(*save_result.fd, preserve_alpha_channel == GUI::MessageBox::ExecYes);
+ if (result.is_error())
+ GUI::MessageBox::show_error(&window, String::formatted("Export to BMP failed: {}", result.error()));
+ }));
+
+ export_submenu.add_action(
+ GUI::Action::create(
+ "As &PNG", [&](auto&) {
+ auto* editor = current_image_editor();
+ auto save_result = FileSystemAccessClient::Client::the().save_file(window.window_id(), "untitled", "png");
+ if (save_result.error != 0)
+ return;
+ auto preserve_alpha_channel = GUI::MessageBox::show(&window, "Do you wish to preserve transparency?", "Preserve transparency?", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
+ auto result = editor->image().export_png_to_fd_and_close(*save_result.fd, preserve_alpha_channel == GUI::MessageBox::ExecYes);
+ if (result.is_error())
+ GUI::MessageBox::show_error(&window, String::formatted("Export to PNG failed: {}", result.error()));
+ }));
+
+ file_menu.add_separator();
+ file_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
+ GUI::Application::the()->quit();
+ }));
+
+ auto& edit_menu = window.add_menu("&Edit");
+
+ m_copy_action = GUI::CommonActions::make_copy_action([&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ if (!editor->active_layer()) {
+ dbgln("Cannot copy with no active layer selected");
+ return;
+ }
+ auto bitmap = editor->active_layer()->try_copy_bitmap(editor->selection());
+ if (!bitmap) {
+ dbgln("try_copy_bitmap() from Layer failed");
+ return;
+ }
+ GUI::Clipboard::the().set_bitmap(*bitmap);
+ });
+
+ m_copy_merged_action = GUI::Action::create(
+ "Copy &Merged", { Mod_Ctrl | Mod_Shift, Key_C }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/edit-copy.png"),
+ [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ auto bitmap = editor->image().try_copy_bitmap(editor->selection());
+ if (!bitmap) {
+ dbgln("try_copy_bitmap() from Image failed");
+ return;
+ }
+ GUI::Clipboard::the().set_bitmap(*bitmap);
+ });
+
+ m_paste_action = GUI::CommonActions::make_paste_action([&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ auto bitmap = GUI::Clipboard::the().bitmap();
+ if (!bitmap)
+ return;
+
+ auto layer = PixelPaint::Layer::try_create_with_bitmap(editor->image(), *bitmap, "Pasted layer");
+ VERIFY(layer);
+ editor->image().add_layer(*layer);
+ editor->set_active_layer(layer);
+ editor->selection().clear();
+ });
+ GUI::Clipboard::the().on_change = [&](auto& mime_type) {
+ m_paste_action->set_enabled(mime_type == "image/x-serenityos");
+ };
+ m_paste_action->set_enabled(GUI::Clipboard::the().mime_type() == "image/x-serenityos");
+
+ m_undo_action = GUI::CommonActions::make_undo_action([&](auto&) {
+ if (auto* editor = current_image_editor())
+ editor->undo();
+ });
+
+ m_redo_action = GUI::CommonActions::make_redo_action([&](auto&) {
+ if (auto* editor = current_image_editor())
+ editor->redo();
+ });
+
+ edit_menu.add_action(*m_copy_action);
+ edit_menu.add_action(*m_copy_merged_action);
+ edit_menu.add_action(*m_paste_action);
+
+ edit_menu.add_action(*m_undo_action);
+ edit_menu.add_action(*m_redo_action);
+
+ edit_menu.add_separator();
+ edit_menu.add_action(GUI::CommonActions::make_select_all_action([&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor->active_layer())
+ return;
+ editor->selection().merge(editor->active_layer()->relative_rect(), PixelPaint::Selection::MergeMode::Set);
+ }));
+ edit_menu.add_action(GUI::Action::create(
+ "Clear &Selection", { Mod_Ctrl | Mod_Shift, Key_A }, [&](auto&) {
+ if (auto* editor = current_image_editor())
+ editor->selection().clear();
+ }));
+
+ edit_menu.add_separator();
+ edit_menu.add_action(GUI::Action::create(
+ "&Swap Colors", { Mod_None, Key_X }, [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ auto old_primary_color = editor->primary_color();
+ editor->set_primary_color(editor->secondary_color());
+ editor->set_secondary_color(old_primary_color);
+ }));
+ edit_menu.add_action(GUI::Action::create(
+ "&Default Colors", { Mod_None, Key_D }, [&](auto&) {
+ if (auto* editor = current_image_editor()) {
+ editor->set_primary_color(Color::Black);
+ editor->set_secondary_color(Color::White);
+ }
+ }));
+ edit_menu.add_action(GUI::Action::create(
+ "&Load Color Palette", [&](auto&) {
+ auto open_result = FileSystemAccessClient::Client::the().open_file(window.window_id(), "Load Color Palette");
+ if (open_result.error != 0)
+ return;
+
+ auto result = PixelPaint::PaletteWidget::load_palette_fd_and_close(*open_result.fd);
+ if (result.is_error()) {
+ GUI::MessageBox::show_error(&window, String::formatted("Loading color palette failed: {}", result.error()));
+ return;
+ }
+
+ m_palette_widget->display_color_list(result.value());
+ }));
+ edit_menu.add_action(GUI::Action::create(
+ "Sa&ve Color Palette", [&](auto&) {
+ auto save_result = FileSystemAccessClient::Client::the().save_file(window.window_id(), "untitled", "palette");
+ if (save_result.error != 0)
+ return;
+
+ auto result = PixelPaint::PaletteWidget::save_palette_fd_and_close(m_palette_widget->colors(), *save_result.fd);
+ if (result.is_error())
+ GUI::MessageBox::show_error(&window, String::formatted("Writing color palette failed: {}", result.error()));
+ }));
+
+ auto& view_menu = window.add_menu("&View");
+
+ m_zoom_in_action = GUI::CommonActions::make_zoom_in_action(
+ [&](auto&) {
+ if (auto* editor = current_image_editor())
+ editor->scale_by(0.1f);
+ });
+
+ m_zoom_out_action = GUI::CommonActions::make_zoom_out_action(
+ [&](auto&) {
+ if (auto* editor = current_image_editor())
+ editor->scale_by(-0.1f);
+ });
+
+ m_reset_zoom_action = GUI::CommonActions::make_reset_zoom_action(
+ [&](auto&) {
+ if (auto* editor = current_image_editor())
+ editor->reset_scale_and_position();
+ });
+
+ m_add_guide_action = GUI::Action::create(
+ "Add Guide", [&](auto&) {
+ auto dialog = PixelPaint::EditGuideDialog::construct(&window);
+ if (dialog->exec() != GUI::Dialog::ExecOK)
+ return;
+ if (auto* editor = current_image_editor()) {
+ auto offset = dialog->offset_as_pixel(*editor);
+ if (!offset.has_value())
+ return;
+ editor->add_guide(PixelPaint::Guide::construct(dialog->orientation(), offset.value()));
+ }
+ });
+
+ // Save this so other methods can use it
+ m_show_guides_action = GUI::Action::create_checkable(
+ "Show Guides", [&](auto& action) {
+ if (auto* editor = current_image_editor()) {
+ editor->set_guide_visibility(action.is_checked());
+ }
+ });
+ m_show_guides_action->set_checked(true);
+
+ view_menu.add_action(*m_zoom_in_action);
+ view_menu.add_action(*m_zoom_out_action);
+ view_menu.add_action(*m_reset_zoom_action);
+ view_menu.add_separator();
+ view_menu.add_action(*m_add_guide_action);
+ view_menu.add_action(*m_show_guides_action);
+
+ auto& tool_menu = window.add_menu("&Tool");
+ m_toolbox->for_each_tool([&](auto& tool) {
+ if (tool.action())
+ tool_menu.add_action(*tool.action());
+ return IterationDecision::Continue;
+ });
+
+ auto& image_menu = window.add_menu("&Image");
+ image_menu.add_action(GUI::Action::create(
+ "Flip &Vertically", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ editor->image().flip(Gfx::Orientation::Vertical);
+ }));
+ image_menu.add_action(GUI::Action::create(
+ "Flip &Horizontally", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ editor->image().flip(Gfx::Orientation::Horizontal);
+ }));
+ image_menu.add_separator();
+ image_menu.add_action(GUI::Action::create(
+ "Rotate &Left", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ editor->image().rotate(Gfx::RotationDirection::CounterClockwise);
+ }));
+ image_menu.add_action(GUI::Action::create(
+ "Rotate &Right", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ editor->image().rotate(Gfx::RotationDirection::Clockwise);
+ }));
+
+ auto& layer_menu = window.add_menu("&Layer");
+ layer_menu.add_action(GUI::Action::create(
+ "New &Layer...", { Mod_Ctrl | Mod_Shift, Key_N }, [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ auto dialog = PixelPaint::CreateNewLayerDialog::construct(editor->image().size(), &window);
+ if (dialog->exec() == GUI::Dialog::ExecOK) {
+ auto layer = PixelPaint::Layer::try_create_with_size(editor->image(), dialog->layer_size(), dialog->layer_name());
+ if (!layer) {
+ GUI::MessageBox::show_error(&window, String::formatted("Unable to create layer with size {}", dialog->size().to_string()));
+ return;
+ }
+ editor->image().add_layer(layer.release_nonnull());
+ editor->layers_did_change();
+ m_layer_list_widget->select_top_layer();
+ }
+ }));
+
+ layer_menu.add_separator();
+ layer_menu.add_action(GUI::Action::create(
+ "Select &Previous Layer", { 0, Key_PageUp }, [&](auto&) {
+ m_layer_list_widget->cycle_through_selection(1);
+ }));
+ layer_menu.add_action(GUI::Action::create(
+ "Select &Next Layer", { 0, Key_PageDown }, [&](auto&) {
+ m_layer_list_widget->cycle_through_selection(-1);
+ }));
+ layer_menu.add_action(GUI::Action::create(
+ "Select &Top Layer", { 0, Key_Home }, [&](auto&) {
+ m_layer_list_widget->select_top_layer();
+ }));
+ layer_menu.add_action(GUI::Action::create(
+ "Select &Bottom Layer", { 0, Key_End }, [&](auto&) {
+ m_layer_list_widget->select_bottom_layer();
+ }));
+ layer_menu.add_separator();
+ layer_menu.add_action(GUI::CommonActions::make_move_to_front_action(
+ [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ auto active_layer = editor->active_layer();
+ if (!active_layer)
+ return;
+ editor->image().move_layer_to_front(*active_layer);
+ editor->layers_did_change();
+ }));
+ layer_menu.add_action(GUI::CommonActions::make_move_to_back_action(
+ [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ auto active_layer = editor->active_layer();
+ if (!active_layer)
+ return;
+ editor->image().move_layer_to_back(*active_layer);
+ editor->layers_did_change();
+ }));
+ layer_menu.add_separator();
+ layer_menu.add_action(GUI::Action::create(
+ "Move Active Layer &Up", { Mod_Ctrl, Key_PageUp }, [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ auto active_layer = editor->active_layer();
+ if (!active_layer)
+ return;
+ editor->image().move_layer_up(*active_layer);
+ }));
+ layer_menu.add_action(GUI::Action::create(
+ "Move Active Layer &Down", { Mod_Ctrl, Key_PageDown }, [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ auto active_layer = editor->active_layer();
+ if (!active_layer)
+ return;
+ editor->image().move_layer_down(*active_layer);
+ }));
+ layer_menu.add_separator();
+ layer_menu.add_action(GUI::Action::create(
+ "&Remove Active Layer", { Mod_Ctrl, Key_D }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/delete.png"), [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ auto active_layer = editor->active_layer();
+ if (!active_layer)
+ return;
+
+ auto active_layer_index = editor->image().index_of(*active_layer);
+ editor->image().remove_layer(*active_layer);
+
+ if (editor->image().layer_count()) {
+ auto& next_active_layer = editor->image().layer(active_layer_index > 0 ? active_layer_index - 1 : 0);
+ editor->set_active_layer(&next_active_layer);
+ } else {
+ editor->set_active_layer(nullptr);
+ }
+ }));
+
+ m_layer_list_widget->on_context_menu_request = [&](auto& event) {
+ layer_menu.popup(event.screen_position());
+ };
+ layer_menu.add_separator();
+ layer_menu.add_action(GUI::Action::create(
+ "&Flatten Image", { Mod_Ctrl, Key_F }, [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ editor->image().flatten_all_layers();
+ editor->did_complete_action();
+ }));
+
+ layer_menu.add_action(GUI::Action::create(
+ "&Merge Visible", { Mod_Ctrl, Key_M }, [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ editor->image().merge_visible_layers();
+ editor->did_complete_action();
+ }));
+
+ layer_menu.add_action(GUI::Action::create(
+ "M&erge Active Layer Down", { Mod_Ctrl, Key_E }, [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ auto active_layer = editor->active_layer();
+ if (!active_layer)
+ return;
+ editor->image().merge_active_layer_down(*active_layer);
+ editor->did_complete_action();
+ }));
+
+ auto& filter_menu = window.add_menu("&Filter");
+ auto& spatial_filters_menu = filter_menu.add_submenu("&Spatial");
+
+ auto& edge_detect_submenu = spatial_filters_menu.add_submenu("&Edge Detect");
+ edge_detect_submenu.add_action(GUI::Action::create("Laplacian (&Cardinal)", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ if (auto* layer = editor->active_layer()) {
+ Gfx::LaplacianFilter filter;
+ if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(false)) {
+ filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
+ editor->did_complete_action();
+ }
+ }
+ }));
+ edge_detect_submenu.add_action(GUI::Action::create("Laplacian (&Diagonal)", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ if (auto* layer = editor->active_layer()) {
+ Gfx::LaplacianFilter filter;
+ if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(true)) {
+ filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
+ editor->did_complete_action();
+ }
+ }
+ }));
+ auto& blur_submenu = spatial_filters_menu.add_submenu("&Blur and Sharpen");
+ blur_submenu.add_action(GUI::Action::create("&Gaussian Blur (3x3)", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ if (auto* layer = editor->active_layer()) {
+ Gfx::SpatialGaussianBlurFilter<3> filter;
+ if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<3>>::get()) {
+ filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
+ editor->did_complete_action();
+ }
+ }
+ }));
+ blur_submenu.add_action(GUI::Action::create("G&aussian Blur (5x5)", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ if (auto* layer = editor->active_layer()) {
+ Gfx::SpatialGaussianBlurFilter<5> filter;
+ if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<5>>::get()) {
+ filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
+ editor->did_complete_action();
+ }
+ }
+ }));
+ blur_submenu.add_action(GUI::Action::create("&Box Blur (3x3)", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ if (auto* layer = editor->active_layer()) {
+ Gfx::BoxBlurFilter<3> filter;
+ if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<3>>::get()) {
+ filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
+ editor->did_complete_action();
+ }
+ }
+ }));
+ blur_submenu.add_action(GUI::Action::create("B&ox Blur (5x5)", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ if (auto* layer = editor->active_layer()) {
+ Gfx::BoxBlurFilter<5> filter;
+ if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<5>>::get()) {
+ filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
+ editor->did_complete_action();
+ }
+ }
+ }));
+ blur_submenu.add_action(GUI::Action::create("&Sharpen", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ if (auto* layer = editor->active_layer()) {
+ Gfx::SharpenFilter filter;
+ if (auto parameters = PixelPaint::FilterParameters<Gfx::SharpenFilter>::get()) {
+ filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
+ editor->did_complete_action();
+ }
+ }
+ }));
+
+ spatial_filters_menu.add_separator();
+ spatial_filters_menu.add_action(GUI::Action::create("Generic 5x5 &Convolution", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ if (auto* layer = editor->active_layer()) {
+ Gfx::GenericConvolutionFilter<5> filter;
+ if (auto parameters = PixelPaint::FilterParameters<Gfx::GenericConvolutionFilter<5>>::get(&window)) {
+ filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
+ editor->did_complete_action();
+ }
+ }
+ }));
+
+ auto& color_filters_menu = filter_menu.add_submenu("&Color");
+ color_filters_menu.add_action(GUI::Action::create("Grayscale", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ if (auto* layer = editor->active_layer()) {
+ Gfx::GrayscaleFilter filter;
+ filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect());
+ editor->did_complete_action();
+ }
+ }));
+ color_filters_menu.add_action(GUI::Action::create("Invert", [&](auto&) {
+ auto* editor = current_image_editor();
+ if (!editor)
+ return;
+ if (auto* layer = editor->active_layer()) {
+ Gfx::InvertFilter filter;
+ filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect());
+ editor->did_complete_action();
+ }
+ }));
+
+ auto& help_menu = window.add_menu("&Help");
+ help_menu.add_action(GUI::CommonActions::make_about_action("Pixel Paint", GUI::Icon::default_icon("app-pixel-paint"), &window));
+
+ auto& toolbar = *find_descendant_of_type_named<GUI::Toolbar>("toolbar");
+ toolbar.add_action(*m_new_image_action);
+ toolbar.add_action(*m_open_image_action);
+ toolbar.add_action(*m_save_image_as_action);
+ toolbar.add_separator();
+ toolbar.add_action(*m_copy_action);
+ toolbar.add_action(*m_paste_action);
+ toolbar.add_action(*m_undo_action);
+ toolbar.add_action(*m_redo_action);
+ toolbar.add_separator();
+ toolbar.add_action(*m_zoom_in_action);
+ toolbar.add_action(*m_zoom_out_action);
+ toolbar.add_action(*m_reset_zoom_action);
+}
+
+void MainWidget::open_image_fd(int fd, String const& path)
+{
+ auto try_load = m_loader.try_load_from_fd_and_close(fd, path);
+
+ if (try_load.is_error()) {
+ GUI::MessageBox::show_error(window(), String::formatted("Unable to open file: {}, {}", path, try_load.error()));
+ return;
+ }
+
+ auto& image = *m_loader.release_image();
+ create_new_editor(image);
+ m_layer_list_widget->set_image(&image);
+}
+
+void MainWidget::open_image_file(String const& path)
+{
+ auto try_load = m_loader.try_load_from_path(path);
+ if (try_load.is_error()) {
+ GUI::MessageBox::show_error(window(), String::formatted("Unable to open file: {}", path));
+ return;
+ }
+ auto& image = *m_loader.release_image();
+ create_new_editor(image);
+ m_layer_list_widget->set_image(&image);
+}
+
+void MainWidget::create_default_image()
+{
+ auto image = Image::try_create_with_size({ 480, 360 });
+
+ auto bg_layer = Layer::try_create_with_size(*image, image->size(), "Background");
+ VERIFY(bg_layer);
+ image->add_layer(*bg_layer);
+ bg_layer->bitmap().fill(Color::White);
+
+ m_layer_list_widget->set_image(image);
+
+ auto& editor = create_new_editor(*image);
+ editor.set_active_layer(bg_layer);
+}
+
+ImageEditor* MainWidget::current_image_editor()
+{
+ if (!m_tab_widget->active_widget())
+ return nullptr;
+ return verify_cast<PixelPaint::ImageEditor>(m_tab_widget->active_widget());
+}
+
+ImageEditor& MainWidget::create_new_editor(NonnullRefPtr<Image> image)
+{
+ auto& image_editor = m_tab_widget->add_tab<PixelPaint::ImageEditor>("Untitled", image);
+
+ image_editor.on_active_layer_change = [&](auto* layer) {
+ if (current_image_editor() != &image_editor)
+ return;
+ m_layer_list_widget->set_selected_layer(layer);
+ m_layer_properties_widget->set_layer(layer);
+ };
+
+ image_editor.on_image_title_change = [&](auto const& title) {
+ m_tab_widget->set_tab_title(image_editor, title);
+ };
+
+ image_editor.on_image_mouse_position_change = [&](auto const& mouse_position) {
+ auto const& image_size = current_image_editor()->image().size();
+ auto image_rectangle = Gfx::IntRect { 0, 0, image_size.width(), image_size.height() };
+ if (image_rectangle.contains(mouse_position)) {
+ m_statusbar->set_override_text(mouse_position.to_string());
+ } else {
+ m_statusbar->set_override_text({});
+ }
+ };
+
+ image_editor.on_leave = [&]() {
+ m_statusbar->set_override_text({});
+ };
+
+ image_editor.on_set_guide_visibility = [&](bool show_guides) {
+ m_show_guides_action->set_checked(show_guides);
+ };
+
+ // NOTE: We invoke the above hook directly here to make sure the tab title is set up.
+ image_editor.on_image_title_change(image->title());
+
+ if (image->layer_count())
+ image_editor.set_active_layer(&image->layer(0));
+
+ if (!m_loader.is_raw_image()) {
+ m_loader.json_metadata().for_each([&](JsonValue const& value) {
+ if (!value.is_object())
+ return;
+ auto& json_object = value.as_object();
+ auto orientation_value = json_object.get("orientation"sv);
+ if (!orientation_value.is_string())
+ return;
+
+ auto offset_value = json_object.get("offset"sv);
+ if (!offset_value.is_number())
+ return;
+
+ auto orientation_string = orientation_value.as_string();
+ PixelPaint::Guide::Orientation orientation;
+ if (orientation_string == "horizontal"sv)
+ orientation = PixelPaint::Guide::Orientation::Horizontal;
+ else if (orientation_string == "vertical"sv)
+ orientation = PixelPaint::Guide::Orientation::Vertical;
+ else
+ return;
+
+ image_editor.add_guide(PixelPaint::Guide::construct(orientation, offset_value.to_number<float>()));
+ });
+ }
+
+ m_tab_widget->set_active_widget(&image_editor);
+ image_editor.set_focus(true);
+ return image_editor;
+}
+
+}
diff --git a/Userland/Applications/PixelPaint/MainWidget.h b/Userland/Applications/PixelPaint/MainWidget.h
new file mode 100644
index 0000000000..4294608499
--- /dev/null
+++ b/Userland/Applications/PixelPaint/MainWidget.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2021, Mustafa Quraish <mustafa@cs.toronto.edu>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include "Guide.h"
+#include "Image.h"
+#include "ImageEditor.h"
+#include "Layer.h"
+#include "LayerListWidget.h"
+#include "LayerPropertiesWidget.h"
+#include "PaletteWidget.h"
+#include "ProjectLoader.h"
+#include "Tool.h"
+#include "ToolPropertiesWidget.h"
+#include "ToolboxWidget.h"
+#include <LibGUI/Action.h>
+#include <LibGUI/Forward.h>
+#include <LibGUI/Statusbar.h>
+#include <LibGUI/TabWidget.h>
+#include <LibGUI/Widget.h>
+
+namespace PixelPaint {
+
+class MainWidget : public GUI::Widget {
+ C_OBJECT(MainWidget);
+
+public:
+ virtual ~MainWidget() {};
+
+ void initialize_menubar(GUI::Window&);
+
+ void open_image_fd(int fd, String const& path);
+ void open_image_file(String const& path);
+ void create_default_image();
+
+private:
+ MainWidget();
+
+ ImageEditor* current_image_editor();
+ ImageEditor& create_new_editor(NonnullRefPtr<Image>);
+
+ ProjectLoader m_loader;
+
+ RefPtr<ToolboxWidget> m_toolbox;
+ RefPtr<PaletteWidget> m_palette_widget;
+ RefPtr<LayerListWidget> m_layer_list_widget;
+ RefPtr<LayerPropertiesWidget> m_layer_properties_widget;
+ RefPtr<ToolPropertiesWidget> m_tool_properties_widget;
+ RefPtr<GUI::TabWidget> m_tab_widget;
+ RefPtr<GUI::Statusbar> m_statusbar;
+
+ RefPtr<GUI::Action> m_new_image_action;
+ RefPtr<GUI::Action> m_open_image_action;
+ RefPtr<GUI::Action> m_save_image_as_action;
+
+ RefPtr<GUI::Action> m_copy_action;
+ RefPtr<GUI::Action> m_copy_merged_action;
+ RefPtr<GUI::Action> m_paste_action;
+ RefPtr<GUI::Action> m_undo_action;
+ RefPtr<GUI::Action> m_redo_action;
+
+ RefPtr<GUI::Action> m_zoom_in_action;
+ RefPtr<GUI::Action> m_zoom_out_action;
+ RefPtr<GUI::Action> m_reset_zoom_action;
+ RefPtr<GUI::Action> m_add_guide_action;
+ RefPtr<GUI::Action> m_show_guides_action;
+};
+
+}
diff --git a/Userland/Applications/PixelPaint/main.cpp b/Userland/Applications/PixelPaint/main.cpp
index 3c96565994..c2ecb89fda 100644
--- a/Userland/Applications/PixelPaint/main.cpp
+++ b/Userland/Applications/PixelPaint/main.cpp
@@ -5,36 +5,16 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
-#include "CreateNewImageDialog.h"
-#include "CreateNewLayerDialog.h"
-#include "EditGuideDialog.h"
-#include "FilterParams.h"
-#include "Guide.h"
-#include "Image.h"
-#include "ImageEditor.h"
-#include "Layer.h"
-#include "LayerListWidget.h"
-#include "LayerPropertiesWidget.h"
-#include "PaletteWidget.h"
-#include "ProjectLoader.h"
-#include "Tool.h"
-#include "ToolPropertiesWidget.h"
-#include "ToolboxWidget.h"
-#include <Applications/PixelPaint/PixelPaintWindowGML.h>
+#include "MainWidget.h"
#include <LibCore/ArgsParser.h>
#include <LibCore/File.h>
#include <LibFileSystemAccessClient/Client.h>
#include <LibGUI/Action.h>
#include <LibGUI/Application.h>
-#include <LibGUI/Clipboard.h>
#include <LibGUI/Icon.h>
-#include <LibGUI/Menubar.h>
-#include <LibGUI/MessageBox.h>
#include <LibGUI/Statusbar.h>
-#include <LibGUI/TabWidget.h>
-#include <LibGUI/Toolbar.h>
#include <LibGUI/Window.h>
-#include <LibGfx/Bitmap.h>
+#include <LibGfx/Painter.h>
#include <stdio.h>
#include <unistd.h>
@@ -98,772 +78,13 @@ int main(int argc, char** argv)
window->resize(800, 510);
window->set_icon(app_icon.bitmap_for_size(16));
- auto& main_widget = window->set_main_widget<GUI::Widget>();
- main_widget.load_from_gml(pixel_paint_window_gml);
-
- auto& toolbox = *main_widget.find_descendant_of_type_named<PixelPaint::ToolboxWidget>("toolbox");
- auto& tab_widget = *main_widget.find_descendant_of_type_named<GUI::TabWidget>("tab_widget");
- tab_widget.set_container_margins({ 4, 5, 5, 4 });
- tab_widget.set_close_button_enabled(true);
-
- auto& palette_widget = *main_widget.find_descendant_of_type_named<PixelPaint::PaletteWidget>("palette_widget");
-
- auto current_image_editor = [&]() -> PixelPaint::ImageEditor* {
- if (!tab_widget.active_widget())
- return nullptr;
- return verify_cast<PixelPaint::ImageEditor>(tab_widget.active_widget());
- };
-
- Function<PixelPaint::ImageEditor&(NonnullRefPtr<PixelPaint::Image>)> create_new_editor;
- PixelPaint::ProjectLoader loader;
-
- auto& layer_list_widget = *main_widget.find_descendant_of_type_named<PixelPaint::LayerListWidget>("layer_list_widget");
- layer_list_widget.on_layer_select = [&](auto* layer) {
- if (auto* editor = current_image_editor())
- editor->set_active_layer(layer);
- };
-
- auto& layer_properties_widget = *main_widget.find_descendant_of_type_named<PixelPaint::LayerPropertiesWidget>("layer_properties_widget");
-
- auto& tool_properties_widget = *main_widget.find_descendant_of_type_named<PixelPaint::ToolPropertiesWidget>("tool_properties_widget");
-
- toolbox.on_tool_selection = [&](auto* tool) {
- if (auto* editor = current_image_editor())
- editor->set_active_tool(tool);
- tool_properties_widget.set_active_tool(tool);
- };
-
- auto new_image_action = GUI::Action::create(
- "&New Image...", { Mod_Ctrl, Key_N }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/new.png"), [&](auto&) {
- auto dialog = PixelPaint::CreateNewImageDialog::construct(window);
- if (dialog->exec() == GUI::Dialog::ExecOK) {
- auto image = PixelPaint::Image::try_create_with_size(dialog->image_size());
- auto bg_layer = PixelPaint::Layer::try_create_with_size(*image, image->size(), "Background");
- VERIFY(bg_layer);
- image->add_layer(*bg_layer);
- bg_layer->bitmap().fill(Color::White);
- auto image_title = dialog->image_name().trim_whitespace();
- image->set_title(image_title.is_empty() ? "Untitled" : image_title);
-
- create_new_editor(*image);
- layer_list_widget.set_image(image);
- layer_list_widget.set_selected_layer(bg_layer);
- }
- },
- window);
-
- auto open_image_file = [&](auto& path) {
- auto try_load = loader.try_load_from_path(path);
- if (try_load.is_error()) {
- GUI::MessageBox::show_error(window, String::formatted("Unable to open file: {}", path));
- return;
- }
- auto& image = *loader.release_image();
- create_new_editor(image);
- layer_list_widget.set_image(&image);
- };
-
- auto open_image_fd = [&](int fd, auto& path) {
- auto try_load = loader.try_load_from_fd_and_close(fd, path);
-
- if (try_load.is_error()) {
- GUI::MessageBox::show_error(window, String::formatted("Unable to open file: {}, {}", path, try_load.error()));
- return;
- }
-
- auto& image = *loader.release_image();
- create_new_editor(image);
- layer_list_widget.set_image(&image);
- };
-
- auto open_image_action = GUI::CommonActions::make_open_action([&](auto&) {
- auto result = FileSystemAccessClient::Client::the().open_file(window->window_id());
- if (result.error != 0)
- return;
-
- open_image_fd(*result.fd, *result.chosen_file);
- });
-
- auto save_image_as_action = GUI::CommonActions::make_save_as_action([&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- auto save_result = FileSystemAccessClient::Client::the().save_file(window->window_id(), "untitled", "pp");
- if (save_result.error != 0)
- return;
- auto result = editor->save_project_to_fd_and_close(*save_result.fd);
- if (result.is_error()) {
- GUI::MessageBox::show_error(window, String::formatted("Could not save {}: {}", *save_result.chosen_file, result.error()));
- return;
- }
- editor->image().set_path(*save_result.chosen_file);
- });
-
- auto& file_menu = window->add_menu("&File");
-
- file_menu.add_action(new_image_action);
- file_menu.add_action(open_image_action);
- file_menu.add_action(save_image_as_action);
- auto& export_submenu = file_menu.add_submenu("&Export");
- export_submenu.add_action(
- GUI::Action::create(
- "As &BMP", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- auto save_result = FileSystemAccessClient::Client::the().save_file(window->window_id(), "untitled", "bmp");
- if (save_result.error != 0)
- return;
- auto preserve_alpha_channel = GUI::MessageBox::show(window, "Do you wish to preserve transparency?", "Preserve transparency?", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
- auto result = editor->image().export_bmp_to_fd_and_close(*save_result.fd, preserve_alpha_channel == GUI::MessageBox::ExecYes);
- if (result.is_error())
- GUI::MessageBox::show_error(window, String::formatted("Export to BMP failed: {}", result.error()));
- },
- window));
- export_submenu.add_action(
- GUI::Action::create(
- "As &PNG", [&](auto&) {
- auto* editor = current_image_editor();
- auto save_result = FileSystemAccessClient::Client::the().save_file(window->window_id(), "untitled", "png");
- if (save_result.error != 0)
- return;
- auto preserve_alpha_channel = GUI::MessageBox::show(window, "Do you wish to preserve transparency?", "Preserve transparency?", GUI::MessageBox::Type::Question, GUI::MessageBox::InputType::YesNo);
- auto result = editor->image().export_png_to_fd_and_close(*save_result.fd, preserve_alpha_channel == GUI::MessageBox::ExecYes);
- if (result.is_error())
- GUI::MessageBox::show_error(window, String::formatted("Export to PNG failed: {}", result.error()));
- },
- window));
-
- file_menu.add_separator();
- file_menu.add_action(GUI::CommonActions::make_quit_action([](auto&) {
- GUI::Application::the()->quit();
- }));
-
- auto& edit_menu = window->add_menu("&Edit");
-
- auto copy_action = GUI::CommonActions::make_copy_action([&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- if (!editor->active_layer()) {
- dbgln("Cannot copy with no active layer selected");
- return;
- }
- auto bitmap = editor->active_layer()->try_copy_bitmap(editor->selection());
- if (!bitmap) {
- dbgln("try_copy_bitmap() from Layer failed");
- return;
- }
- GUI::Clipboard::the().set_bitmap(*bitmap);
- });
- auto copy_merged_action = GUI::Action::create(
- "Copy &Merged", { Mod_Ctrl | Mod_Shift, Key_C }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/edit-copy.png"),
- [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- auto bitmap = editor->image().try_copy_bitmap(editor->selection());
- if (!bitmap) {
- dbgln("try_copy_bitmap() from Image failed");
- return;
- }
- GUI::Clipboard::the().set_bitmap(*bitmap);
- },
- window);
-
- auto paste_action = GUI::CommonActions::make_paste_action([&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- auto bitmap = GUI::Clipboard::the().bitmap();
- if (!bitmap)
- return;
-
- auto layer = PixelPaint::Layer::try_create_with_bitmap(editor->image(), *bitmap, "Pasted layer");
- VERIFY(layer);
- editor->image().add_layer(*layer);
- editor->set_active_layer(layer);
- editor->selection().clear();
- });
- GUI::Clipboard::the().on_change = [&](auto& mime_type) {
- paste_action->set_enabled(mime_type == "image/x-serenityos");
- };
- paste_action->set_enabled(GUI::Clipboard::the().mime_type() == "image/x-serenityos");
-
- edit_menu.add_action(copy_action);
- edit_menu.add_action(copy_merged_action);
- edit_menu.add_action(paste_action);
-
- auto undo_action = GUI::CommonActions::make_undo_action([&](auto&) {
- if (auto* editor = current_image_editor())
- editor->undo();
- });
- edit_menu.add_action(undo_action);
-
- auto redo_action = GUI::CommonActions::make_redo_action([&](auto&) {
- if (auto* editor = current_image_editor())
- editor->redo();
- });
- edit_menu.add_action(redo_action);
-
- edit_menu.add_separator();
- edit_menu.add_action(GUI::CommonActions::make_select_all_action([&](auto&) {
- auto* editor = current_image_editor();
- if (!editor->active_layer())
- return;
- editor->selection().merge(editor->active_layer()->relative_rect(), PixelPaint::Selection::MergeMode::Set);
- }));
- edit_menu.add_action(GUI::Action::create(
- "Clear &Selection", { Mod_Ctrl | Mod_Shift, Key_A }, [&](auto&) {
- if (auto* editor = current_image_editor())
- editor->selection().clear();
- },
- window));
-
- edit_menu.add_separator();
- edit_menu.add_action(GUI::Action::create(
- "&Swap Colors", { Mod_None, Key_X }, [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- auto old_primary_color = editor->primary_color();
- editor->set_primary_color(editor->secondary_color());
- editor->set_secondary_color(old_primary_color);
- },
- window));
- edit_menu.add_action(GUI::Action::create(
- "&Default Colors", { Mod_None, Key_D }, [&](auto&) {
- if (auto* editor = current_image_editor()) {
- editor->set_primary_color(Color::Black);
- editor->set_secondary_color(Color::White);
- }
- },
- window));
- edit_menu.add_action(GUI::Action::create(
- "&Load Color Palette", [&](auto&) {
- auto open_result = FileSystemAccessClient::Client::the().open_file(window->window_id(), "Load Color Palette");
- if (open_result.error != 0)
- return;
-
- auto result = PixelPaint::PaletteWidget::load_palette_fd_and_close(*open_result.fd);
- if (result.is_error()) {
- GUI::MessageBox::show_error(window, String::formatted("Loading color palette failed: {}", result.error()));
- return;
- }
-
- palette_widget.display_color_list(result.value());
- },
- window));
- edit_menu.add_action(GUI::Action::create(
- "Sa&ve Color Palette", [&](auto&) {
- auto save_result = FileSystemAccessClient::Client::the().save_file(window->window_id(), "untitled", "palette");
- if (save_result.error != 0)
- return;
-
- auto result = PixelPaint::PaletteWidget::save_palette_fd_and_close(palette_widget.colors(), *save_result.fd);
- if (result.is_error())
- GUI::MessageBox::show_error(window, String::formatted("Writing color palette failed: {}", result.error()));
- },
- window));
-
- auto& view_menu = window->add_menu("&View");
-
- auto zoom_in_action = GUI::CommonActions::make_zoom_in_action(
- [&](auto&) {
- if (auto* editor = current_image_editor())
- editor->scale_by(0.1f);
- },
- window);
-
- auto zoom_out_action = GUI::CommonActions::make_zoom_out_action(
- [&](auto&) {
- if (auto* editor = current_image_editor())
- editor->scale_by(-0.1f);
- },
- window);
-
- auto reset_zoom_action = GUI::CommonActions::make_reset_zoom_action(
- [&](auto&) {
- if (auto* editor = current_image_editor())
- editor->reset_scale_and_position();
- },
- window);
-
- auto add_guide_action = GUI::Action::create(
- "Add Guide", [&](auto&) {
- auto dialog = PixelPaint::EditGuideDialog::construct(window);
- if (dialog->exec() != GUI::Dialog::ExecOK)
- return;
- if (auto* editor = current_image_editor()) {
- auto offset = dialog->offset_as_pixel(*editor);
- if (!offset.has_value())
- return;
- editor->add_guide(PixelPaint::Guide::construct(dialog->orientation(), offset.value()));
- }
- },
- window);
-
- auto show_guides_action = GUI::Action::create_checkable(
- "Show Guides", [&](auto& action) {
- if (auto* editor = current_image_editor()) {
- editor->set_guide_visibility(action.is_checked());
- }
- },
- window);
- show_guides_action->set_checked(true);
-
- view_menu.add_action(zoom_in_action);
- view_menu.add_action(zoom_out_action);
- view_menu.add_action(reset_zoom_action);
- view_menu.add_separator();
- view_menu.add_action(add_guide_action);
- view_menu.add_action(show_guides_action);
-
- auto& tool_menu = window->add_menu("&Tool");
- toolbox.for_each_tool([&](auto& tool) {
- if (tool.action())
- tool_menu.add_action(*tool.action());
- return IterationDecision::Continue;
- });
-
- auto& image_menu = window->add_menu("&Image");
- image_menu.add_action(GUI::Action::create(
- "Flip &Vertically", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- editor->image().flip(Gfx::Orientation::Vertical);
- },
- window));
- image_menu.add_action(GUI::Action::create(
- "Flip &Horizontally", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- editor->image().flip(Gfx::Orientation::Horizontal);
- },
- window));
- image_menu.add_separator();
- image_menu.add_action(GUI::Action::create(
- "Rotate &Left", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- editor->image().rotate(Gfx::RotationDirection::CounterClockwise);
- },
- window));
- image_menu.add_action(GUI::Action::create(
- "Rotate &Right", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- editor->image().rotate(Gfx::RotationDirection::Clockwise);
- },
- window));
-
- auto& layer_menu = window->add_menu("&Layer");
- layer_menu.add_action(GUI::Action::create(
- "New &Layer...", { Mod_Ctrl | Mod_Shift, Key_N }, [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- auto dialog = PixelPaint::CreateNewLayerDialog::construct(editor->image().size(), window);
- if (dialog->exec() == GUI::Dialog::ExecOK) {
- auto layer = PixelPaint::Layer::try_create_with_size(editor->image(), dialog->layer_size(), dialog->layer_name());
- if (!layer) {
- GUI::MessageBox::show_error(window, String::formatted("Unable to create layer with size {}", dialog->size().to_string()));
- return;
- }
- editor->image().add_layer(layer.release_nonnull());
- editor->layers_did_change();
- layer_list_widget.select_top_layer();
- }
- },
- window));
-
- layer_menu.add_separator();
- layer_menu.add_action(GUI::Action::create(
- "Select &Previous Layer", { 0, Key_PageUp }, [&](auto&) {
- layer_list_widget.cycle_through_selection(1);
- },
- window));
- layer_menu.add_action(GUI::Action::create(
- "Select &Next Layer", { 0, Key_PageDown }, [&](auto&) {
- layer_list_widget.cycle_through_selection(-1);
- },
- window));
- layer_menu.add_action(GUI::Action::create(
- "Select &Top Layer", { 0, Key_Home }, [&](auto&) {
- layer_list_widget.select_top_layer();
- },
- window));
- layer_menu.add_action(GUI::Action::create(
- "Select &Bottom Layer", { 0, Key_End }, [&](auto&) {
- layer_list_widget.select_bottom_layer();
- },
- window));
- layer_menu.add_separator();
- layer_menu.add_action(GUI::CommonActions::make_move_to_front_action(
- [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- auto active_layer = editor->active_layer();
- if (!active_layer)
- return;
- editor->image().move_layer_to_front(*active_layer);
- editor->layers_did_change();
- },
- window));
- layer_menu.add_action(GUI::CommonActions::make_move_to_back_action(
- [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- auto active_layer = editor->active_layer();
- if (!active_layer)
- return;
- editor->image().move_layer_to_back(*active_layer);
- editor->layers_did_change();
- },
- window));
- layer_menu.add_separator();
- layer_menu.add_action(GUI::Action::create(
- "Move Active Layer &Up", { Mod_Ctrl, Key_PageUp }, [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- auto active_layer = editor->active_layer();
- if (!active_layer)
- return;
- editor->image().move_layer_up(*active_layer);
- },
- window));
- layer_menu.add_action(GUI::Action::create(
- "Move Active Layer &Down", { Mod_Ctrl, Key_PageDown }, [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- auto active_layer = editor->active_layer();
- if (!active_layer)
- return;
- editor->image().move_layer_down(*active_layer);
- },
- window));
- layer_menu.add_separator();
- layer_menu.add_action(GUI::Action::create(
- "&Remove Active Layer", { Mod_Ctrl, Key_D }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/delete.png"), [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- auto active_layer = editor->active_layer();
- if (!active_layer)
- return;
-
- auto active_layer_index = editor->image().index_of(*active_layer);
- editor->image().remove_layer(*active_layer);
-
- if (editor->image().layer_count()) {
- auto& next_active_layer = editor->image().layer(active_layer_index > 0 ? active_layer_index - 1 : 0);
- editor->set_active_layer(&next_active_layer);
- } else {
- editor->set_active_layer(nullptr);
- }
- },
- window));
-
- layer_list_widget.on_context_menu_request = [&](auto& event) {
- layer_menu.popup(event.screen_position());
- };
- layer_menu.add_separator();
- layer_menu.add_action(GUI::Action::create(
- "&Flatten Image", { Mod_Ctrl, Key_F }, [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- editor->image().flatten_all_layers();
- editor->did_complete_action();
- },
- window));
-
- layer_menu.add_action(GUI::Action::create(
- "&Merge Visible", { Mod_Ctrl, Key_M }, [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- editor->image().merge_visible_layers();
- editor->did_complete_action();
- },
- window));
-
- layer_menu.add_action(GUI::Action::create(
- "M&erge Active Layer Down", { Mod_Ctrl, Key_E }, [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- auto active_layer = editor->active_layer();
- if (!active_layer)
- return;
- editor->image().merge_active_layer_down(*active_layer);
- editor->did_complete_action();
- },
- window));
-
- auto& filter_menu = window->add_menu("&Filter");
- auto& spatial_filters_menu = filter_menu.add_submenu("&Spatial");
-
- auto& edge_detect_submenu = spatial_filters_menu.add_submenu("&Edge Detect");
- edge_detect_submenu.add_action(GUI::Action::create("Laplacian (&Cardinal)", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- if (auto* layer = editor->active_layer()) {
- Gfx::LaplacianFilter filter;
- if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(false)) {
- filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
- editor->did_complete_action();
- }
- }
- }));
- edge_detect_submenu.add_action(GUI::Action::create("Laplacian (&Diagonal)", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- if (auto* layer = editor->active_layer()) {
- Gfx::LaplacianFilter filter;
- if (auto parameters = PixelPaint::FilterParameters<Gfx::LaplacianFilter>::get(true)) {
- filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
- editor->did_complete_action();
- }
- }
- }));
- auto& blur_submenu = spatial_filters_menu.add_submenu("&Blur and Sharpen");
- blur_submenu.add_action(GUI::Action::create("&Gaussian Blur (3x3)", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- if (auto* layer = editor->active_layer()) {
- Gfx::SpatialGaussianBlurFilter<3> filter;
- if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<3>>::get()) {
- filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
- editor->did_complete_action();
- }
- }
- }));
- blur_submenu.add_action(GUI::Action::create("G&aussian Blur (5x5)", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- if (auto* layer = editor->active_layer()) {
- Gfx::SpatialGaussianBlurFilter<5> filter;
- if (auto parameters = PixelPaint::FilterParameters<Gfx::SpatialGaussianBlurFilter<5>>::get()) {
- filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
- editor->did_complete_action();
- }
- }
- }));
- blur_submenu.add_action(GUI::Action::create("&Box Blur (3x3)", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- if (auto* layer = editor->active_layer()) {
- Gfx::BoxBlurFilter<3> filter;
- if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<3>>::get()) {
- filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
- editor->did_complete_action();
- }
- }
- }));
- blur_submenu.add_action(GUI::Action::create("B&ox Blur (5x5)", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- if (auto* layer = editor->active_layer()) {
- Gfx::BoxBlurFilter<5> filter;
- if (auto parameters = PixelPaint::FilterParameters<Gfx::BoxBlurFilter<5>>::get()) {
- filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
- editor->did_complete_action();
- }
- }
- }));
- blur_submenu.add_action(GUI::Action::create("&Sharpen", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- if (auto* layer = editor->active_layer()) {
- Gfx::SharpenFilter filter;
- if (auto parameters = PixelPaint::FilterParameters<Gfx::SharpenFilter>::get()) {
- filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
- editor->did_complete_action();
- }
- }
- }));
-
- spatial_filters_menu.add_separator();
- spatial_filters_menu.add_action(GUI::Action::create("Generic 5x5 &Convolution", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- if (auto* layer = editor->active_layer()) {
- Gfx::GenericConvolutionFilter<5> filter;
- if (auto parameters = PixelPaint::FilterParameters<Gfx::GenericConvolutionFilter<5>>::get(window)) {
- filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect(), *parameters);
- editor->did_complete_action();
- }
- }
- }));
-
- auto& color_filters_menu = filter_menu.add_submenu("&Color");
- color_filters_menu.add_action(GUI::Action::create("Grayscale", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- if (auto* layer = editor->active_layer()) {
- Gfx::GrayscaleFilter filter;
- filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect());
- editor->did_complete_action();
- }
- }));
- color_filters_menu.add_action(GUI::Action::create("Invert", [&](auto&) {
- auto* editor = current_image_editor();
- if (!editor)
- return;
- if (auto* layer = editor->active_layer()) {
- Gfx::InvertFilter filter;
- filter.apply(layer->bitmap(), layer->rect(), layer->bitmap(), layer->rect());
- editor->did_complete_action();
- }
- }));
-
- auto& help_menu = window->add_menu("&Help");
- help_menu.add_action(GUI::CommonActions::make_about_action("Pixel Paint", app_icon, window));
-
- auto& toolbar = *main_widget.find_descendant_of_type_named<GUI::Toolbar>("toolbar");
- toolbar.add_action(new_image_action);
- toolbar.add_action(open_image_action);
- toolbar.add_action(save_image_as_action);
- toolbar.add_separator();
- toolbar.add_action(copy_action);
- toolbar.add_action(paste_action);
- toolbar.add_action(undo_action);
- toolbar.add_action(redo_action);
- toolbar.add_separator();
- toolbar.add_action(zoom_in_action);
- toolbar.add_action(zoom_out_action);
- toolbar.add_action(reset_zoom_action);
-
- create_new_editor = [&](NonnullRefPtr<PixelPaint::Image> image) -> PixelPaint::ImageEditor& {
- auto& image_editor = tab_widget.add_tab<PixelPaint::ImageEditor>("Untitled", image);
-
- image_editor.on_active_layer_change = [&](auto* layer) {
- if (current_image_editor() != &image_editor)
- return;
- layer_list_widget.set_selected_layer(layer);
- layer_properties_widget.set_layer(layer);
- };
-
- image_editor.on_image_title_change = [&](auto const& title) {
- tab_widget.set_tab_title(image_editor, title);
- };
-
- auto& statusbar = *main_widget.find_descendant_of_type_named<GUI::Statusbar>("statusbar");
- image_editor.on_image_mouse_position_change = [&](auto const& mouse_position) {
- auto const& image_size = current_image_editor()->image().size();
- auto image_rectangle = Gfx::IntRect { 0, 0, image_size.width(), image_size.height() };
- if (image_rectangle.contains(mouse_position)) {
- statusbar.set_override_text(mouse_position.to_string());
- } else {
- statusbar.set_override_text({});
- }
- };
-
- image_editor.on_leave = [&]() {
- statusbar.set_override_text({});
- };
-
- image_editor.on_set_guide_visibility = [&](bool show_guides) {
- show_guides_action->set_checked(show_guides);
- };
-
- // NOTE: We invoke the above hook directly here to make sure the tab title is set up.
- image_editor.on_image_title_change(image->title());
-
- if (image->layer_count())
- image_editor.set_active_layer(&image->layer(0));
-
- if (!loader.is_raw_image()) {
- loader.json_metadata().for_each([&](JsonValue const& value) {
- if (!value.is_object())
- return;
- auto& json_object = value.as_object();
- auto orientation_value = json_object.get("orientation"sv);
- if (!orientation_value.is_string())
- return;
-
- auto offset_value = json_object.get("offset"sv);
- if (!offset_value.is_number())
- return;
-
- auto orientation_string = orientation_value.as_string();
- PixelPaint::Guide::Orientation orientation;
- if (orientation_string == "horizontal"sv)
- orientation = PixelPaint::Guide::Orientation::Horizontal;
- else if (orientation_string == "vertical"sv)
- orientation = PixelPaint::Guide::Orientation::Vertical;
- else
- return;
-
- image_editor.add_guide(PixelPaint::Guide::construct(orientation, offset_value.to_number<float>()));
- });
- }
-
- tab_widget.set_active_widget(&image_editor);
- image_editor.set_focus(true);
- return image_editor;
- };
-
- tab_widget.on_tab_close_click = [&](auto& widget) {
- auto& image_editor = verify_cast<PixelPaint::ImageEditor>(widget);
-
- if (tab_widget.children().size() == 1) {
- layer_list_widget.set_image(nullptr);
- layer_properties_widget.set_layer(nullptr);
- }
-
- tab_widget.deferred_invoke([&] {
- tab_widget.remove_tab(image_editor);
- });
- };
-
- tab_widget.on_change = [&](auto& widget) {
- auto& image_editor = verify_cast<PixelPaint::ImageEditor>(widget);
- palette_widget.set_image_editor(image_editor);
- layer_list_widget.set_image(&image_editor.image());
- layer_properties_widget.set_layer(image_editor.active_layer());
- // FIXME: This is badly factored. It transfers tools from the previously active editor to the new one.
- toolbox.template for_each_tool([&](auto& tool) {
- if (tool.editor()) {
- tool.editor()->set_active_tool(nullptr);
- image_editor.set_active_tool(&tool);
- }
- });
- show_guides_action->set_checked(image_editor.guide_visibility());
- };
+ auto& main_widget = window->set_main_widget<PixelPaint::MainWidget>();
+ main_widget.initialize_menubar(*window);
if (Core::File::exists(file_to_edit_full_path)) {
- open_image_file(file_to_edit_full_path);
+ main_widget.open_image_file(file_to_edit_full_path);
} else {
- auto image = PixelPaint::Image::try_create_with_size({ 480, 360 });
-
- auto bg_layer = PixelPaint::Layer::try_create_with_size(*image, image->size(), "Background");
- VERIFY(bg_layer);
- image->add_layer(*bg_layer);
- bg_layer->bitmap().fill(Color::White);
-
- layer_list_widget.set_image(image);
-
- auto& editor = create_new_editor(*image);
- editor.set_active_layer(bg_layer);
+ main_widget.create_default_image();
}
auto& statusbar = *main_widget.find_descendant_of_type_named<GUI::Statusbar>("statusbar");