diff options
author | Mustafa Quraish <mustafaq9@gmail.com> | 2021-09-06 00:11:46 -0400 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-09-06 10:36:08 +0200 |
commit | 5a8c6b95e61da7717388241e0c4733de8c1d267f (patch) | |
tree | 17800e74aff7cda7d5c3af12d71d008d2e92aa6c /Userland/Applications/PixelPaint | |
parent | bbddfeef4bf5dcebd50828806d55a60224ece6b9 (diff) | |
download | serenity-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.txt | 1 | ||||
-rw-r--r-- | Userland/Applications/PixelPaint/MainWidget.cpp | 776 | ||||
-rw-r--r-- | Userland/Applications/PixelPaint/MainWidget.h | 73 | ||||
-rw-r--r-- | Userland/Applications/PixelPaint/main.cpp | 791 |
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"); |