diff --git a/po/POTFILES.src.in b/po/POTFILES.src.in index b227ec488bf11c7813eb09b040f0f4eed95672d4..d9c3bdfbb19c7a043a2b125b01b84f511c6820f4 100644 --- a/po/POTFILES.src.in +++ b/po/POTFILES.src.in @@ -425,6 +425,7 @@ ${_build_dir}/share/templates/templates.h ../src/ui/widget/memory.cpp ../src/ui/widget/mesh-editor.cpp ../src/ui/widget/messages.cpp +../src/ui/widget/multi-marker-color-plate.cpp ../src/ui/widget/object-composite-settings.cpp ../src/ui/widget/page-properties.cpp ../src/ui/widget/page-selector.cpp @@ -435,6 +436,7 @@ ${_build_dir}/share/templates/templates.h ../src/ui/widget/preferences-widget.cpp ../src/ui/widget/property-utils.cpp ../src/ui/widget/random.cpp +../src/ui/widget/recolor-art.cpp ../src/ui/widget/registered-widget.cpp ../src/ui/widget/rendering-options.cpp ../src/ui/widget/selected-style.cpp diff --git a/po/POTFILES.ui.in b/po/POTFILES.ui.in index 64483fff91eb40edab6e04193a4317d16912478c..26dd7fecf7f5b1ec594a909dfda59aadd534a4d2 100644 --- a/po/POTFILES.ui.in +++ b/po/POTFILES.ui.in @@ -72,3 +72,4 @@ ../share/ui/toolbar-tool-prefs.ui ../share/ui/toolbar-tweak.ui ../share/ui/toolbar-zoom.ui +../share/ui/widget-recolor.ui diff --git a/share/icons/Dash/symbolic/actions/go-right-symbolic.svg b/share/icons/Dash/symbolic/actions/go-right-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..5d08323d90b8625acee64e431693cc7e5df4dc6a --- /dev/null +++ b/share/icons/Dash/symbolic/actions/go-right-symbolic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/share/icons/Dash/symbolic/actions/lightness-symbolic.svg b/share/icons/Dash/symbolic/actions/lightness-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..1e8d4bc38e00616b6ad24fb329583bbf39a72f27 --- /dev/null +++ b/share/icons/Dash/symbolic/actions/lightness-symbolic.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/share/icons/Dash/symbolic/actions/object-recolor-art-symbolic.svg b/share/icons/Dash/symbolic/actions/object-recolor-art-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..b078cf421c5c1beec4852445a720247bf9c2827c --- /dev/null +++ b/share/icons/Dash/symbolic/actions/object-recolor-art-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/share/icons/Dash/symbolic/actions/reset-colors-symbolic.svg b/share/icons/Dash/symbolic/actions/reset-colors-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..7d47572893ed89f875798b4b0e353615b756fc70 --- /dev/null +++ b/share/icons/Dash/symbolic/actions/reset-colors-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/share/icons/Dash/symbolic/actions/saturation-symbolic.svg b/share/icons/Dash/symbolic/actions/saturation-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..7f0b0fda5a8f13cc8345ac01a092b97f67785489 --- /dev/null +++ b/share/icons/Dash/symbolic/actions/saturation-symbolic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/share/icons/hicolor/symbolic/actions/go-right-symbolic.svg b/share/icons/hicolor/symbolic/actions/go-right-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..5d08323d90b8625acee64e431693cc7e5df4dc6a --- /dev/null +++ b/share/icons/hicolor/symbolic/actions/go-right-symbolic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/share/icons/hicolor/symbolic/actions/lightness-symbolic.svg b/share/icons/hicolor/symbolic/actions/lightness-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..1e8d4bc38e00616b6ad24fb329583bbf39a72f27 --- /dev/null +++ b/share/icons/hicolor/symbolic/actions/lightness-symbolic.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/share/icons/hicolor/symbolic/actions/object-recolor-art-symbolic.svg b/share/icons/hicolor/symbolic/actions/object-recolor-art-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..b078cf421c5c1beec4852445a720247bf9c2827c --- /dev/null +++ b/share/icons/hicolor/symbolic/actions/object-recolor-art-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/share/icons/hicolor/symbolic/actions/reset-colors-symbolic.svg b/share/icons/hicolor/symbolic/actions/reset-colors-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..7d47572893ed89f875798b4b0e353615b756fc70 --- /dev/null +++ b/share/icons/hicolor/symbolic/actions/reset-colors-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/share/icons/hicolor/symbolic/actions/reset-symbolic.svg b/share/icons/hicolor/symbolic/actions/reset-symbolic.svg index 7dcef2b3f70fffc0d4e14bd1fdd4381979bf9177..09c40a1e3021e4ad13a7d5a0086ef0a972d948e6 100644 --- a/share/icons/hicolor/symbolic/actions/reset-symbolic.svg +++ b/share/icons/hicolor/symbolic/actions/reset-symbolic.svg @@ -1,58 +1,5 @@ - - - - - - - - - - - - + + + + diff --git a/share/icons/hicolor/symbolic/actions/saturation-symbolic.svg b/share/icons/hicolor/symbolic/actions/saturation-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..7f0b0fda5a8f13cc8345ac01a092b97f67785489 --- /dev/null +++ b/share/icons/hicolor/symbolic/actions/saturation-symbolic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/share/icons/multicolor/symbolic/actions/go-right-symbolic.svg b/share/icons/multicolor/symbolic/actions/go-right-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..5d08323d90b8625acee64e431693cc7e5df4dc6a --- /dev/null +++ b/share/icons/multicolor/symbolic/actions/go-right-symbolic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/share/icons/multicolor/symbolic/actions/lightness-symbolic.svg b/share/icons/multicolor/symbolic/actions/lightness-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..1e8d4bc38e00616b6ad24fb329583bbf39a72f27 --- /dev/null +++ b/share/icons/multicolor/symbolic/actions/lightness-symbolic.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/share/icons/multicolor/symbolic/actions/object-recolor-art-symbolic.svg b/share/icons/multicolor/symbolic/actions/object-recolor-art-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..b078cf421c5c1beec4852445a720247bf9c2827c --- /dev/null +++ b/share/icons/multicolor/symbolic/actions/object-recolor-art-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/share/icons/multicolor/symbolic/actions/reset-colors-symbolic.svg b/share/icons/multicolor/symbolic/actions/reset-colors-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..7d47572893ed89f875798b4b0e353615b756fc70 --- /dev/null +++ b/share/icons/multicolor/symbolic/actions/reset-colors-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/share/icons/multicolor/symbolic/actions/reset-symbolic.svg b/share/icons/multicolor/symbolic/actions/reset-symbolic.svg deleted file mode 100644 index 7dcef2b3f70fffc0d4e14bd1fdd4381979bf9177..0000000000000000000000000000000000000000 --- a/share/icons/multicolor/symbolic/actions/reset-symbolic.svg +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - diff --git a/share/icons/multicolor/symbolic/actions/saturation-symbolic.svg b/share/icons/multicolor/symbolic/actions/saturation-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..7f0b0fda5a8f13cc8345ac01a092b97f67785489 --- /dev/null +++ b/share/icons/multicolor/symbolic/actions/saturation-symbolic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/share/ui/pattern-edit.glade b/share/ui/pattern-edit.glade index 7993af04ab677e093c0d5a2e12460de5fcd18df0..59e90b9c2b141154d899dc68c18515101e06e23a 100644 --- a/share/ui/pattern-edit.glade +++ b/share/ui/pattern-edit.glade @@ -367,7 +367,7 @@ Gap 0 - 7 + 8 diff --git a/share/ui/style.css b/share/ui/style.css index 6217045bb1c1aad19f21c81484caf14f905a97fc..c0bf2e126551c090d970306b54e2913545e69d73 100644 --- a/share/ui/style.css +++ b/share/ui/style.css @@ -1673,3 +1673,40 @@ button.reduced-padding { #DialogMultipaned:drop(active), #DialogMultipaned:drop(active):focus { box-shadow: none; } + +/* +********************** +* Recolor Art class * +********************** +*/ + + +#recolor-art #original, +#recolored { + min-height: 12px; + border-radius: 4px; + /* border: 2px solid transparent; */ +} + +#original-recolor-box{ +padding:0 1px; +} +.type_box { + background-color: alpha(@theme_bg_color,0.8); + border-radius: 2px; + padding-left: 2px; + padding-right: 6px; +} +#recolor-art listview row:selected{ + background-color: transparent; + border: 2px solid @theme_selected_bg_color; + border-radius: 4px; + color: @theme_fg_color; +} + +#recolor-art gridview child:selected { + background-color: transparent; + border: 1px solid @theme_selected_bg_color; + border-radius: 1px; + color: @theme_fg_color; +} diff --git a/share/ui/widget-recolor.ui b/share/ui/widget-recolor.ui new file mode 100644 index 0000000000000000000000000000000000000000..e1ea7ca50934cd8e61faf7bd99cec22d72dc8f62 --- /dev/null +++ b/share/ui/widget-recolor.ui @@ -0,0 +1,176 @@ + + + + + recolor-art + vertical + true + true + + + list-wheel-box + top + false + 250 + + + original-recolor-colors-list-box + vertical + true + true + + + original-reset-recolor + 3 + none + + + Original + fill + + + + + reset + true + false + false + center + 16 + + + reset-settings + normal + + + + + + + New + center + + + + + + + true + true + true + never + false + 240 + + + colors-list + vertical + true + fill + fill + + + false + fill + true + fill + + + + + + + + + + vertical + true + + + + + + + + + horizontal + 6 + center + true + + + hamburger-menu + normal + + + + + Color List + center + center + + + + + + + + color-wheel-page + vertical + true + true + + + + + + horizontal + 6 + center + true + + + color-wheel-symbolic + normal + + + + + Color Wheel + center + center + + + + + + + + + + + + liveP-apply + horizontal + 300 + + + liveP + Live Preview + end + start + + + + + + + + \ No newline at end of file diff --git a/src/3rdparty/2geom b/src/3rdparty/2geom index 53f0cc304297294a0b7ada73c28e2da8d4aeba61..5957e3234b40da48d50bc5f90160541e129d5f08 160000 --- a/src/3rdparty/2geom +++ b/src/3rdparty/2geom @@ -1 +1 @@ -Subproject commit 53f0cc304297294a0b7ada73c28e2da8d4aeba61 +Subproject commit 5957e3234b40da48d50bc5f90160541e129d5f08 diff --git a/src/3rdparty/libcroco b/src/3rdparty/libcroco index 0b5d1c68a3fd1452bd1671ed4fb493ee88fdaf13..ea3de62c70310d5aa09aa7e5c5e0f7298290a573 160000 --- a/src/3rdparty/libcroco +++ b/src/3rdparty/libcroco @@ -1 +1 @@ -Subproject commit 0b5d1c68a3fd1452bd1671ed4fb493ee88fdaf13 +Subproject commit ea3de62c70310d5aa09aa7e5c5e0f7298290a573 diff --git a/src/desktop.cpp b/src/desktop.cpp index 7abd51c830883fcbfefbc9be61a2bc0a44390135..1e50f22c36e7a10334db6d5e5405e2f59047a64a 100644 --- a/src/desktop.cpp +++ b/src/desktop.cpp @@ -1124,6 +1124,11 @@ void SPDesktop::applyCurrentOrToolStyle(SPObject *obj, Glib::ustring const &tool } } +void SPDesktop::hideSelectionBoxes(bool hide) +{ + _canvas_group_controls->hide_selection_boxes(hide); +} + void SPDesktop::setToolboxFocusTo(char const * const label) { diff --git a/src/desktop.h b/src/desktop.h index 63c4c010643b62bdc5e63bdf9e25c6a21e27983d..190273222e9feb3449f16972d234024d22f82ac3 100644 --- a/src/desktop.h +++ b/src/desktop.h @@ -432,6 +432,7 @@ public: void applyCurrentOrToolStyle(SPObject *obj, Glib::ustring const &tool_path, bool with_text); + void hideSelectionBoxes(bool hide); private: SPDesktopWidget *_widget = nullptr; diff --git a/src/display/control/canvas-item-group.cpp b/src/display/control/canvas-item-group.cpp index ef02d4890d0724129c39c61c63cd9416512b6c21..ea468c4e298b3d415fc28aa1de59e20454c593f2 100644 --- a/src/display/control/canvas-item-group.cpp +++ b/src/display/control/canvas-item-group.cpp @@ -15,6 +15,7 @@ #include #include "canvas-item-group.h" +#include "canvas-item-rect.h" constexpr bool DEBUG_LOGGING = false; @@ -111,6 +112,20 @@ CanvasItem *CanvasItemGroup::pick_item(Geom::Point const &p) return nullptr; } +void CanvasItemGroup::hide_selection_boxes(bool hide) +{ + for (auto &item : items) { + std::string name = item.get_name(); + if (dynamic_cast(&item)) { + item.set_visible(!hide); + item.set_force_hide(!hide); + } + } + + // _update(true); +} + + } // namespace Inkscape /* diff --git a/src/display/control/canvas-item-group.h b/src/display/control/canvas-item-group.h index 2bccdeab129095cc90355fb4a8e1dad1b7892473..e8d9da5d7d7c91e74a685f439235d8b5ffe61985 100644 --- a/src/display/control/canvas-item-group.h +++ b/src/display/control/canvas-item-group.h @@ -31,6 +31,7 @@ public: // Selection CanvasItem *pick_item(Geom::Point const &p); + void hide_selection_boxes(bool hide); protected: friend class CanvasItem; // access to items diff --git a/src/display/control/canvas-item.cpp b/src/display/control/canvas-item.cpp index 617903b92dda3d7f63549b94003d29baccef2432..7640b5bff43e84ce5d951d503f0af3085895abd3 100644 --- a/src/display/control/canvas-item.cpp +++ b/src/display/control/canvas-item.cpp @@ -149,6 +149,7 @@ void CanvasItem::request_update() void CanvasItem::update(bool propagate) { + if(!_recolor_force_hide)return; if (!_visible) { _mark_net_invisible(); return; diff --git a/src/display/control/canvas-item.h b/src/display/control/canvas-item.h index 14d4c71d2163981d576d628f86c42876119980d7..6f4320e4a4b529f7ad0a7af160d046705f7bf312 100644 --- a/src/display/control/canvas-item.h +++ b/src/display/control/canvas-item.h @@ -110,7 +110,8 @@ public: // Recursively print CanvasItem tree. void canvas_item_print_tree(int level = 0, int zorder = 0) const; - + void set_force_hide(bool hide){_recolor_force_hide = hide;} + bool get_force_hide(){return _recolor_force_hide;} // Boost linked list member hook, speeds deletion. boost::intrusive::list_member_hook<> member_hook; @@ -134,6 +135,7 @@ protected: // Display bool _visible = true; + bool _recolor_force_hide = true; bool _net_visible = true; virtual void _render(Inkscape::CanvasItemBuffer &buf) const = 0; diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 314919702b5ee42fee7545c2412dc5770b630660..75a79574f938ec9ca701a7bc8ef3481d3d3a3714 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -298,10 +298,10 @@ set(ui_SRC widget/widget-vfuncs-class-init.cpp widget/xml-treeview.cpp widget/property-utils.cpp - + widget/recolor-art.cpp + widget/multi-marker-color-plate.cpp view/svg-view-widget.cpp - # ------- # Headers builder-utils.h @@ -626,6 +626,9 @@ set(ui_SRC widget/xml-treeview.h widget/property-utils.h + widget/recolor-art.h + widget/multi-marker-color-plate.h + view/svg-view-widget.h ) diff --git a/src/ui/dialog/fill-and-stroke.cpp b/src/ui/dialog/fill-and-stroke.cpp index a60cbf731f5178fc2d352e99216c52c14e246c41..d1eb81236a51320474c40981b9831b7d26004ad9 100644 --- a/src/ui/dialog/fill-and-stroke.cpp +++ b/src/ui/dialog/fill-and-stroke.cpp @@ -21,14 +21,17 @@ #include #include +#include "desktop-style.h" #include "desktop.h" #include "preferences.h" +#include "selection.h" #include "ui/icon-loader.h" #include "ui/icon-names.h" #include "ui/pack.h" #include "ui/widget/fill-style.h" -#include "ui/widget/stroke-style.h" #include "ui/widget/notebook-page.h" +#include "ui/widget/recolor-art.h" +#include "ui/widget/stroke-style.h" namespace Inkscape::UI::Dialog { @@ -37,12 +40,9 @@ FillAndStroke::FillAndStroke() , _page_fill(Gtk::make_managed(1, 1)) , _page_stroke_paint(Gtk::make_managed(1, 1)) , _page_stroke_style(Gtk::make_managed(1, 1)) - , _composite_settings(INKSCAPE_ICON("dialog-fill-and-stroke"), - "fillstroke", - UI::Widget::SimpleFilterModifier::ISOLATION | - UI::Widget::SimpleFilterModifier::BLEND | - UI::Widget::SimpleFilterModifier::BLUR | - UI::Widget::SimpleFilterModifier::OPACITY) + , _composite_settings(INKSCAPE_ICON("dialog-fill-and-stroke"), "fillstroke", + UI::Widget::SimpleFilterModifier::ISOLATION | UI::Widget::SimpleFilterModifier::BLEND | + UI::Widget::SimpleFilterModifier::BLUR | UI::Widget::SimpleFilterModifier::OPACITY) , fillWdgt(nullptr) , strokeWdgt(nullptr) { @@ -51,7 +51,8 @@ FillAndStroke::FillAndStroke() _notebook.append_page(*_page_fill, _createPageTabLabel(_("_Fill"), INKSCAPE_ICON("object-fill"))); _notebook.append_page(*_page_stroke_paint, _createPageTabLabel(_("Stroke _paint"), INKSCAPE_ICON("object-stroke"))); - _notebook.append_page(*_page_stroke_style, _createPageTabLabel(_("Stroke st_yle"), INKSCAPE_ICON("object-stroke-style"))); + _notebook.append_page(*_page_stroke_style, + _createPageTabLabel(_("Stroke st_yle"), INKSCAPE_ICON("object-stroke-style"))); _notebook.set_vexpand(true); _switch_page_conn = _notebook.signal_switch_page().connect(sigc::mem_fun(*this, &FillAndStroke::_onSwitchPage)); @@ -77,6 +78,7 @@ FillAndStroke::~FillAndStroke() void FillAndStroke::selectionChanged(Selection *selection) { + if (!page_changed) { changed_fill = true; changed_stroke = true; @@ -91,6 +93,7 @@ void FillAndStroke::selectionChanged(Selection *selection) if (strokeStyleWdgt && npage == 2) { strokeStyleWdgt->selectionChangedCB(); } + } void FillAndStroke::selectionModified(Selection *selection, guint flags) @@ -107,6 +110,7 @@ void FillAndStroke::selectionModified(Selection *selection, guint flags) if (strokeStyleWdgt && npage == 2) { strokeStyleWdgt->selectionModifiedCB(flags); } + } void FillAndStroke::desktopReplaced() @@ -126,7 +130,7 @@ void FillAndStroke::desktopReplaced() _subject.setDesktop(getDesktop()); } -void FillAndStroke::_onSwitchPage(Gtk::Widget * page, guint pagenum) +void FillAndStroke::_onSwitchPage(Gtk::Widget *page, guint pagenum) { npage = pagenum; if (page->is_visible()) { @@ -150,30 +154,26 @@ void FillAndStroke::_onSwitchPage(Gtk::Widget * page, guint pagenum) _savePagePref(pagenum); } -void -FillAndStroke::_savePagePref(guint page_num) +void FillAndStroke::_savePagePref(guint page_num) { // remember the current page Inkscape::Preferences *prefs = Inkscape::Preferences::get(); prefs->setInt("/dialogs/fillstroke/page", page_num); } -void -FillAndStroke::_layoutPageFill() +void FillAndStroke::_layoutPageFill() { fillWdgt = Gtk::make_managed(FILL); _page_fill->table().attach(*fillWdgt, 0, 0, 1, 1); } -void -FillAndStroke::_layoutPageStrokePaint() +void FillAndStroke::_layoutPageStrokePaint() { strokeWdgt = Gtk::make_managed(STROKE); _page_stroke_paint->table().attach(*strokeWdgt, 0, 0, 1, 1); } -void -FillAndStroke::_layoutPageStrokeStyle() +void FillAndStroke::_layoutPageStrokeStyle() { strokeStyleWdgt = Gtk::make_managed(); strokeStyleWdgt->set_hexpand(); @@ -181,34 +181,30 @@ FillAndStroke::_layoutPageStrokeStyle() _page_stroke_style->table().attach(*strokeStyleWdgt, 0, 0, 1, 1); } -void -FillAndStroke::showPageFill() +void FillAndStroke::showPageFill() { blink(); _notebook.set_current_page(0); _savePagePref(0); - } -void -FillAndStroke::showPageStrokePaint() +void FillAndStroke::showPageStrokePaint() { blink(); _notebook.set_current_page(1); _savePagePref(1); } -void -FillAndStroke::showPageStrokeStyle() +void FillAndStroke::showPageStrokeStyle() { blink(); _notebook.set_current_page(2); _savePagePref(2); - } -Gtk::Box& -FillAndStroke::_createPageTabLabel(const Glib::ustring& label, const char *label_image) + + +Gtk::Box &FillAndStroke::_createPageTabLabel(Glib::ustring const &label, char const *label_image) { auto const _tab_label_box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL, 4); diff --git a/src/ui/dialog/fill-and-stroke.h b/src/ui/dialog/fill-and-stroke.h index 0a918df2f0af4d1b9ec2a65a397ca69653e66a77..d8ac27dfe5b13627e880dfc1820a06745c67d7dc 100644 --- a/src/ui/dialog/fill-and-stroke.h +++ b/src/ui/dialog/fill-and-stroke.h @@ -24,6 +24,7 @@ namespace Gtk { class Box; +class Popover; } // namespace Gtk namespace Inkscape::UI { @@ -32,6 +33,7 @@ namespace Widget { class FillNStroke; class NotebookPage; class StrokeStyle; +class RecolorArt; } // namespace Widget namespace Dialog { @@ -47,6 +49,7 @@ public: void showPageFill(); void showPageStrokePaint(); void showPageStrokeStyle(); + void showPageRecolorArt(); protected: Gtk::Notebook _notebook; @@ -78,7 +81,6 @@ private: UI::Widget::FillNStroke *fillWdgt = nullptr; UI::Widget::FillNStroke *strokeWdgt = nullptr; UI::Widget::StrokeStyle *strokeStyleWdgt = nullptr; - sigc::scoped_connection _switch_page_conn; }; diff --git a/src/ui/widget/color-notebook.cpp b/src/ui/widget/color-notebook.cpp index 39117b6506e65a3f4fefd789ad14cccf764c81e6..b2ee1cbb6c06c69d0a9aa4a1e4ad5da5ed391929 100644 --- a/src/ui/widget/color-notebook.cpp +++ b/src/ui/widget/color-notebook.cpp @@ -126,7 +126,7 @@ void ColorNotebook::_initUI() _book->set_margin_start(2); _book->set_margin_end(2); _book->set_hexpand(); - _book->set_vexpand(); + _book->set_vexpand(false); attach(*_book, 0, row, 2, 1); // restore the last active page @@ -193,7 +193,7 @@ void ColorNotebook::_initUI() gtk_widget_set_margin_start(rgbabox, XPAD); gtk_widget_set_margin_end(rgbabox, XPAD); - gtk_widget_set_margin_top(rgbabox, YPAD); + gtk_widget_set_margin_top(rgbabox, 8); gtk_widget_set_margin_bottom(rgbabox, YPAD); attach(*Glib::wrap(rgbabox), 0, row, 2, 1); @@ -311,6 +311,14 @@ void ColorNotebook::_addPageForSpace(std::shared_ptr sp _visibility_observers.emplace_back(std::move(obs)); } +void ColorNotebook::setCurrentColor(std::shared_ptr colors) +{ + auto visible_child = _book->get_visible_child(); + if (auto current_page = dynamic_cast(visible_child)) { + current_page->setCurrentColor(colors); + } +} + } // namespace Inkscape::UI::Widget /* diff --git a/src/ui/widget/color-notebook.h b/src/ui/widget/color-notebook.h index 637379c716012ac639c1d30007f84e06f34c5c33..d0d9e9cc979f9576eae9a391ccf43eebada24cd0 100644 --- a/src/ui/widget/color-notebook.h +++ b/src/ui/widget/color-notebook.h @@ -39,6 +39,7 @@ public: ~ColorNotebook() override; void set_label(const Glib::ustring& label); + void setCurrentColor(std::shared_ptr colors); protected: void _initUI(); diff --git a/src/ui/widget/color-page.h b/src/ui/widget/color-page.h index 6a3a2377a11e80624866852bfd949247c2922fea..1eea249b7e4d4c7fc6082ba357ca6c62fca9c6be 100644 --- a/src/ui/widget/color-page.h +++ b/src/ui/widget/color-page.h @@ -22,6 +22,8 @@ #include "color-slider.h" #include "color-preview.h" #include "ui/widget/color-slider.h" +#include "ui/widget/ink-color-wheel.h" + using namespace Inkscape::Colors; @@ -56,6 +58,12 @@ public: void attach_page(Glib::RefPtr first_column, Glib::RefPtr last_column); void detach_page(Glib::RefPtr first_column, Glib::RefPtr last_column); + void setCurrentColor(std::shared_ptr color) + { + if (_color_wheel) + _color_wheel->set_color(color->get().value()); + } + protected: std::shared_ptr _space; std::shared_ptr _selected_colors; diff --git a/src/ui/widget/color-preview.cpp b/src/ui/widget/color-preview.cpp index 4854cbbad6a6ef66a70975c078d2067684389d99..ecf80c025570c9c3df534c3fe191ef08698ce53a 100644 --- a/src/ui/widget/color-preview.cpp +++ b/src/ui/widget/color-preview.cpp @@ -49,8 +49,8 @@ void ColorPreview::setRgba32(std::uint32_t const rgba) { _gradient.clear(); queue_draw(); } - -void ColorPreview::setPattern(Cairo::RefPtr pattern) { +void ColorPreview::setPattern(Cairo::RefPtr pattern) +{ if (_pattern == pattern) return; _pattern = pattern; diff --git a/src/ui/widget/gradient-editor.h b/src/ui/widget/gradient-editor.h index dd4a0c76483ec0d08c0300e653ee475f1d8afe31..f5da2cbad01b15c9627c2cf0897335be1ae13967 100644 --- a/src/ui/widget/gradient-editor.h +++ b/src/ui/widget/gradient-editor.h @@ -73,6 +73,8 @@ public: ColorPickerPanel::PlateType get_color_picker_plate() const; SPGradientType get_type() const; ColorPickerPanel& get_picker() { return *_color_picker; } + Gtk::Box& getColorBox(){return _colors_box ; }; + private: void set_gradient(SPGradient* gradient); diff --git a/src/ui/widget/ink-color-wheel.cpp b/src/ui/widget/ink-color-wheel.cpp index 8561f92cab9cf6f47f84fc9ff9874d024ca0e2c8..4ea25be74651995b87ba82679a1207edecee1b54 100644 --- a/src/ui/widget/ink-color-wheel.cpp +++ b/src/ui/widget/ink-color-wheel.cpp @@ -27,6 +27,7 @@ #include "ui/widget/bin.h" #include "util/drawing-utils.h" #include "util/theme-utils.h" +#include "ink-color-wheel.h" using namespace Inkscape::Colors; using Inkscape::Colors::Space::Luv; @@ -761,6 +762,679 @@ bool ColorWheelHSLuv::setColor(Color const &color, } return false; } +/*multi marker color wheel*/ + +/** + * takes the color parameter and push it into the _values_vectors vector + * them if emit is true it calls the color_changed() to emit signal changed then queue redrae the area + */ +bool MultiMarkerWheel::setColor(Color const &color, bool const /*overrideHue*/, bool const emit) +{ + if (_values.set(color, true)) { + _values_vector.push_back(_values); + if (emit) + color_changed(); + else + queue_drawing_area_draw(); + return true; + } + return false; +} +/** + * it takes a vector of colors then clears the current _values_vector and _markers_points + * and resets the color wheel then repopulates it with the new colors + * then emit the color changed signal + */ +bool MultiMarkerWheel::setColor(std::vector const &color) +{ + _values_vector.clear(); + _markers_points.clear(); + _source_wheel.reset(); + for (auto c : color) { + if(!setColor(c,false,false)) return false; + } + _markers_points.resize(_values_vector.size()); + _reset_markers(); + color_changed(); + return true ; +} + +void MultiMarkerWheel::_reset_markers() { + for(auto& _marker_point : _markers_points) + _marker_point.reset(); + _markers_points.resize(_values_vector.size()); +} +/** + * takes cairo context , color value and index of the color + * get the center of the colorwheel by dividing width and height by 2 + * get the center of the marker by passing the index to get_marker_point function + * then choose the color of the marker (black or white) based on its luminance + * then start start drawing with cairo , if the index == hover index just make the radius bigger + * then if the marker has foucs add focus dash to it + */ +void MultiMarkerWheel::_draw_marker(Cairo::RefPtr const &cr , Colors::Color value , int index) +{ + auto const [width, height] = *_cache_size; + auto const cx = width / 2.0; + auto const cy = height / 2.0; + auto const &[mx, my] = get_marker_point(index); + + _draw_line_2_marker(cr,mx,my,cx,cy,value,index); + + auto color_on_wheel = Color(Type::HSV, {value[0], 1.0, 1.0}); + double a = luminance(color_on_wheel) < 0.5 ? 1.0 : 0.0; + cr->set_source_rgb(a, a, a); + cr->begin_new_path(); + if (index == _hover_index) { + cr->arc(mx, my, marker_radius+2, 0, 2 * M_PI); + } + else{ + cr->arc(mx, my, marker_radius, 0, 2 * M_PI); + } + cr->stroke(); + + // Draw focus + if (drawing_area_has_focus()) { + // The focus_dash width & alpha(foreground_color) are from GTK3 Adwaita. + if (index == _active_index) { + cr->set_dash(focus_dash, 0); + cr->set_line_width(1.0); + cr->set_source_rgb(1 - a, 1 - a, 1 - a); + cr->begin_new_path(); + cr->arc(mx, my, marker_radius + focus_padding, 0, 2 * M_PI); + } + + cr->stroke(); + } +} +/** + * try to get marker index from the input position (x,y) + * by getting the distance between the marker center and the point (x,y) + * if it is less than marker_radius + marker_click_tolerance it means that + * the point is inside the marker area then return its index + * if not found return -1 + */ +int MultiMarkerWheel::_get_marker_index(double x, double y) +{ + for(int i =0 ; i<_values_vector.size();i++) + { + auto const& [mx,my] = get_marker_point(i); + double dx = x-mx; + double dy = y-my; + double distance = std::sqrt(dx*dx + dy*dy); + if(distance <= marker_radius + marker_click_tolerance) + return i; + } + return -1; +} +/** + * If hue lock is enabled, this function calculates how far each marker's hue + * is from the active marker's hue. + * + * Because hue is a circle (0.0 and 1.0 are the same color), the raw difference + * can sometimes look too big (e.g. 0.9 - 0.1 = 0.8), even though the real + * distance around the circle is much smaller (0.2). + * + * To fix this, the delta is adjusted: + * - If delta > 0.5 → subtract 1.0 + * - If delta < -0.5 → add 1.0 + * + * This makes sure the difference is always the shortest distance on the color wheel, + * inside the range [-0.5, +0.5]. + */ +void MultiMarkerWheel::_update_hue_lock_positions() +{ + if (_hue_lock) { + std::vector delat_angles; + auto active_hue = _values_vector[_active_index][0]; + for (int i = 0; i < _values_vector.size(); i++) { + if (i == _active_index) { + delat_angles.push_back(0.0); + continue; + } + auto delta_hue = _values_vector[i][0] - active_hue; + if (delta_hue > 0.5) delta_hue -= 1.0; + if (delta_hue < -0.5) delta_hue += 1.0; + delat_angles.push_back(delta_hue); + } + _relative_hue_angles = delat_angles; +} +} +/** + * function to draw line to the begining of the marker by calculating the distnace from the wheel center + * to makrer center + * normalize the differnces between centers by dividing them by the length of the line + * to be a unit vector between [-1,1] + * then calculate the end points ty,tx by subtracting the marker radius multiplied by the direction of the vector + * from the marker center , calculate the luminance of the line + * move the cairo context tot the center of the colorwheel by converting the polar coordinates to cartisain ones + * then draw the line to point (tx,ty) + */ +void MultiMarkerWheel::_draw_line_2_marker(Cairo::RefPtr const &cr, double mx, double my, double cx, + double cy, Colors::Color value , int index) +{ + + auto const &[r_min, r_max] = get_radii(); + auto color_on_wheel = Color(Type::HSV, {value[0], 1.0, 1.0}); + double dy = my-cy; + double dx = mx-cx; + double len = std::sqrt(dx*dx+dy*dy); + if(len > 1e-5) + { + dx /= len; + dy /= len; + } + double mr = index == _hover_index ? marker_radius+2 : marker_radius; // bigger radius for on hover effect + double tx = mx - dx * mr; + double ty = my - dy * mr; + double l = luminance(color_on_wheel) < 0.5 ? 1.0 : 0.0; + cr->save(); + cr->set_source_rgb(l, l, l); + cr->move_to(cx + cos(value[0] * M_PI * 2.0) * (r_min), + cy - sin(value[0] * M_PI * 2.0) * (r_min)); // x = r*cos(angel) , y = r*sin(angel) + // adding cx and subtracting cy to start from wheel center not the origin (0,0) + cr->line_to(tx,ty); + if (index != _active_index) { + cr->set_dash(focus_dash, 0); + cr->set_line_width(1.0); + } + if (index == _hover_index) { + cr->set_dash(std::valarray(), 0); + } + cr->stroke(); + cr->restore(); +} +/** + * draw the colorwheel pixel by pixel + */ +void MultiMarkerWheel::update_wheel_source() +{ + if (_radii && _source_wheel) + return; + + auto const [width, height] = *_cache_size; + auto const cx = width / 2.0; + auto const cy = height / 2.0; + + auto const stride = Cairo::ImageSurface::format_stride_for_width(Cairo::Surface::Format::RGB24, width); + _source_wheel.reset(); + _buffer_wheel.resize(height * stride / 4); + + auto const &[r_min, r_max] = get_radii(); + double r2_max = (r_max + 2) * (r_max + 2); // Must expand a bit to avoid edge effects. + double r2_min = (r_min - 2) * (r_min - 2); // Must shrink a bit to avoid edge effects. + + for (int i = 0; i < height; ++i) { + auto p = _buffer_wheel.data() + i * width; + double dy = (cy - i); + for (int j = 0; j < width; ++j) { + double dx = (j - cx); + double r2 = dx * dx + dy * dy; + if (r2 < r2_min || r2 > r2_max) { + *p++ = 0; // Save calculation time. + } else { + double angle = atan2(dy, dx); + if (angle < 0.0) { + angle += 2.0 * M_PI; + } + double hue = angle / (2.0 * M_PI); + + double saturation = sqrt(r2)/r_max; + saturation = std::clamp(saturation,0.0,1.0); + // double value = 1.0 - ((dy+(height/2.0))/height); + // value = std::clamp(value,0.0,1.0); + + *p++ = Color(Type::HSV, {hue, saturation,lightness}).toARGB(); + } + } + } + + auto const data = reinterpret_cast(_buffer_wheel.data()); + _source_wheel = Cairo::ImageSurface::create(data, Cairo::Surface::Format::RGB24, width, height, stride); +} +/** + * takes index of the requested changed color and the new color + * change it in the _values_vector and reset its marker and emit color changed signal to update the widget + * and return true if succeeded + * used to sync wheel's colors if the color chnaged from the colorlist + */ +bool MultiMarkerWheel::changeColor(int index, Colors::Color& color) +{ + if(index >=0 && index < _values_vector.size()) + { + // std::cout<<" index : "<-1) + { + _values_vector[index].set(2,lightness); + _markers_points[index].reset(); + color_changed(); + } + } +} +/** + * set saturation for all colors in the wheel when hue lock is on + * if it is off just changed saturation for the active color + */ +void MultiMarkerWheel::setSaturation(double value) +{ + saturation = value / 100.0; + // _source_wheel.reset(); + if (_hue_lock) { + for (size_t i = 0; i < _values_vector.size(); i++) { + _values_vector[i].set(1, saturation); + if (i < _markers_points.size()) { + _markers_points[i].reset(); + } + } + color_changed(); + } + else + { + int index = getActiveIndex(); + if(index>-1) + { + _values_vector[index].set(1,saturation); + _markers_points[index].reset(); + color_changed(); + } + } + +} + +void MultiMarkerWheel::on_drawing_area_size(int width, int height, int baseline) +{ + auto const size = Geom::IntPoint{width, height}; + if (size == _cache_size) + return; + _cache_size = size; + _radii.reset(); + _source_wheel.reset(); +} +/** + * main function for drawing the whole wheel and markers and lines + */ +void MultiMarkerWheel::on_drawing_area_draw(Cairo::RefPtr const &cr, int, int) +{ + + auto const [width, height] = *_cache_size; + auto const cx = width / 2.0; + auto const cy = height / 2.0; + + cr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL); + + // Update caches + update_wheel_source(); + auto const &[r_min, r_max] = get_radii(); + + // Paint with ring surface, clipping to ring. + cr->save(); + cr->set_source(_source_wheel, 0, 0); + cr->set_line_width(r_max - r_min); + cr->begin_new_path(); + cr->arc(cx, cy, (r_max + r_min) / 2.0, 0, 2.0 * M_PI); + cr->stroke(); + cr->restore(); + // Paint line to markers and markers + if (_markers_points.size() != _values_vector.size()) { + _markers_points.resize(_values_vector.size()); + } + + for (int i = 0; i < _values_vector.size(); i++) { + _draw_marker(cr, _values_vector[i], i); + } +} + +std::optional MultiMarkerWheel::focus(Gtk::DirectionType const direction) +{ + // Any focus change must update focus indicators (add or remove). + queue_drawing_area_draw(); + + // In forward direction, focus passes from no focus to ring focus to triangle + // focus to no focus. + if (!drawing_area_has_focus()) { + _focus_on_wheel = (direction == Gtk::DirectionType::TAB_FORWARD); + focus_drawing_area(); + return true; + } + + // Already have focus + bool keep_focus = true; + + switch (direction) { + case Gtk::DirectionType::TAB_BACKWARD: + if (!_focus_on_wheel) { + _focus_on_wheel = true; + } else { + keep_focus = false; + } + break; + + case Gtk::DirectionType::TAB_FORWARD: + if (_focus_on_wheel) { + _focus_on_wheel = false; + } else { + keep_focus = false; + } + } + + return keep_focus; +} +/** + * checks whether the point is inside the wheel or not + * by checking if the distance is less than the wheel radius + */ +bool MultiMarkerWheel::_is_in_wheel(double x, double y) +{ + // std::cout<<"x: "<= 0) + _active_index = index; + _update_hue_lock_positions(); + _update_wheel_color(x, y,_active_index); + return Gtk::EventSequenceState::CLAIMED; + } + + return Gtk::EventSequenceState::NONE; +} + +Gtk::EventSequenceState MultiMarkerWheel::on_click_released(int /*n_press*/, double /*x*/, double /*y*/) +{ + _mode = DragMode::NONE; + _adjusting = false; + return Gtk::EventSequenceState::CLAIMED; +} +/** + * a part of _on_motion signal handler that handels the marker movement and emits _signal_color_hovered + * with -1 _hover_index to cancel the hover effect when start moving the marker + * also checks for the _hue_lock to change reset of the markers accordingly if it is on + */ +void MultiMarkerWheel::on_motion(Gtk::EventControllerMotion const &motion, double x, double y) +{ + // std::cout<<"on motion \n"; + if (!_adjusting) + return; + auto state = motion.get_current_event_state(); + if (!Controller::has_flag(state, Gdk::ModifierType::BUTTON1_MASK)) { + // lost button release event + _mode = DragMode::NONE; + _adjusting = false; + return; + } + + if (_mode == DragMode::HUE || _mode == DragMode::SATURATION_VALUE) { + _hover_index = -1; + _signal_color_hovered.emit(); + _update_wheel_color(x, y, _active_index); + if (_hue_lock && !_relative_hue_angles.empty()) { + bool changed = false; + double hue = _values_vector[_active_index][0]; + for (int i = 0; i < _values_vector.size(); i++) { + if (i != _active_index) { + double new_hue = hue + _relative_hue_angles[i]; + new_hue = fmod(new_hue + 1.0, 1.0); + if (_values_vector[i].set(0, new_hue)) { + _markers_points[i].reset(); + changed = true; + // std::cout<<"hue changed : "<= 0 && hover_index < _values_vector.size()) + { + // std::cout << "hover color : " << _values_vector[hover_index].toString() << std::endl; + queue_drawing_area_draw(); + } + else + { + // std::cout << "invalid index : " << hover_index << std::endl; + } + } + return; + } + + auto state = motion.get_current_event_state(); + if (!Controller::has_flag(state, Gdk::ModifierType::BUTTON1_MASK)) { + // lost button release event + on_click_released(0, x, y); + return; + } + + on_motion(motion, x, y); +} +/** + * signal handler function that handels keyboard adjustments to the wheel on hue and saturation values + * same as the one in ColorWheelHSL too + */ +bool MultiMarkerWheel::on_key_pressed(unsigned keyval, unsigned /*keycode*/, Gdk::ModifierType state) +{ + static constexpr double delta_hue = 2.0 / MAX_HUE; + static constexpr double delta_sat = 2.0 / MAX_SATURATION; + auto dx = 0.0, dy = 0.0; + + switch (keyval) { + case GDK_KEY_Up: + case GDK_KEY_KP_Up: + dy = -1.0; + break; + + case GDK_KEY_Down: + case GDK_KEY_KP_Down: + dy = +1.0; + break; + + case GDK_KEY_Left: + case GDK_KEY_KP_Left: + dx = -1.0; + break; + + case GDK_KEY_Right: + case GDK_KEY_KP_Right: + dx = +1.0; + } + + if (dx == 0.0 && dy == 0.0) + return false; + + bool changed = false; + if (_focus_on_wheel) { + changed = _values_vector[_active_index].set(0, _values_vector[_active_index][0] - ((dx != 0 ? dx : dy) * delta_hue)); + changed = _values_vector[_active_index].set(1, _values_vector[_active_index][1] - ((dy != 0 ? dy : dx) * delta_sat)); + } + + + _values_vector[_active_index].normalize(); + + if (changed) { + _markers_points[_active_index].reset(); + color_changed(); + } + + return changed; +} +/** + * that is the same as get radii in the ColorWheelHSL i didn't change any thing even + * though it has only one radius (r_max = r_min) now as a whole circle not a ring but i dare not change a working function + * (and i am lazy too) + */ +MultiMarkerWheel::MinMax const &MultiMarkerWheel::get_radii() +{ + if (_radii) + return *_radii; + + // Force calc others, too. + + _radii.emplace(); + auto &[r_min, r_max] = *_radii; + auto const [width, height] = *_cache_size; + r_max = std::min(width, height) / 2.0 - 2 * (focus_line_width + focus_padding); + r_min = r_max * (1.0 - _wheel_width); + // std::cout<<"r max : "<= _values_vector.size()) { + static Geom::Point default_point{0, 0}; + return default_point; + } + + if (index >= _markers_points.size()) { + _markers_points.resize(_values_vector.size()); + } + + if (_markers_points[index]) + return *_markers_points[index]; + + auto const [width, height] = *_cache_size; + auto const cx = width / 2.0; + auto const cy = height / 2.0; + auto const&[r_min,r_max] = get_radii(); + double hue = _values_vector[index][0]; + double saturation = _values_vector[index][1]; + // std::cout<<"hue : "< const &builder) + : ColorWheelBase(cobject, builder, Type::HSV, {0.5, 0.2, 0.7, 1}) // redundant values nothing important +{} /** * Update the PickerGeometry structure owned by the instance. diff --git a/src/ui/widget/ink-color-wheel.h b/src/ui/widget/ink-color-wheel.h index 8169bd86b9f0461eb47d955cfa5e648d849f2857..918bc5a12eb9f6e926795cc34a327d91430f92ee 100644 --- a/src/ui/widget/ink-color-wheel.h +++ b/src/ui/widget/ink-color-wheel.h @@ -113,7 +113,7 @@ private: virtual Gtk::EventSequenceState on_click_released(int n_press, double x, double y) = 0; virtual void on_motion(Gtk::EventControllerMotion const &motion, double x, double y) = 0; - void _on_motion(Gtk::EventControllerMotion const &motion, double x, double y); + virtual void _on_motion(Gtk::EventControllerMotion const &motion, double x, double y); virtual bool on_key_pressed(unsigned keyval, unsigned keycode, Gdk::ModifierType state) { @@ -225,6 +225,108 @@ private: int _square_size = 1; }; +/** + * @class MultiMarkerWheel + */ +class MultiMarkerWheel + : public WidgetVfuncsClassInit + , public ColorWheelBase +{ +public: + MultiMarkerWheel(); + MultiMarkerWheel(BaseObjectType *cobject, Glib::RefPtr const &builder); + bool setColor(Colors::Color const &color, bool overrideHue = true, bool emit = true) override; + bool setColor(std::vector const &color); + Colors::Color getColor() const override + { + if (!_values_vector.empty() && _active_index >= 0 && _active_index < _values_vector.size()) + return _values_vector[_active_index]; + else + return Colors::Color(0x00000000); + } + bool setActiveIndex(int index) + { + if (!_values_vector.empty() && index >= 0 && index < _values_vector.size()) { + _active_index = index; + return true; + } else + return false; + } + int getActiveIndex() + { + if (!_values_vector.empty() && _active_index >= 0 && _active_index < _values_vector.size()) + return _active_index; + else + return -1; + } + int getHoverIndex() + { + if (!_values_vector.empty() && _hover_index >= 0 && _hover_index < _values_vector.size()) + return _hover_index; + else + return -1; + } + bool changeColor(int index , Colors::Color& color); + sigc::connection connect_color_hovered(sigc::slot slot) { return _signal_color_hovered.connect(std::move(slot)); } + void toggleHueLock(bool locked){_hue_lock = locked ;} + bool getHueLock(){return _hue_lock;} + std::vector getColors(){return _values_vector ;} + void setLightness(double value); + void setSaturation(double value); +private: + void on_drawing_area_size(int width, int height, int baseline) override; + void on_drawing_area_draw(Cairo::RefPtr const &cr, int, int) override; + std::optional focus(Gtk::DirectionType direction) override; + bool _is_in_wheel(double x, double y); + void _update_wheel_color(double x, double y , int index); + void _reset_markers(); + void _draw_line_2_marker(Cairo::RefPtr const &cr , double mx, double my ,double cx, double cy , Colors::Color value , int index); + void _draw_marker(Cairo::RefPtr const &cr,Colors::Color value , int index); + int _get_marker_index(double x , double y); + void _update_hue_lock_positions(); + + + enum class DragMode + { + NONE, + HUE, + SATURATION_VALUE + }; + + static constexpr double _wheel_width = 1.0; + DragMode _mode = DragMode::NONE; + bool _focus_on_wheel = true; + + Gtk::EventSequenceState on_click_pressed(Gtk::GestureClick const &controller, int n_press, double x, + double y) final; + Gtk::EventSequenceState on_click_released(int n_press, double x, double y) final; + void on_motion(Gtk::EventControllerMotion const &motion, double x, double y) final; + void _on_motion(Gtk::EventControllerMotion const &motion, double x, double y) override ; + bool on_key_pressed(unsigned keyval, unsigned keycode, Gdk::ModifierType state) final; + + // caches to speed up drawing + using MinMax = std::array; + using MarkerPoints = std::vector>; + std::vector _values_vector; + std::optional _cache_size; + std::optional _radii; + std::optional _marker_point; + MarkerPoints _markers_points; + std::vector _buffer_wheel; + Cairo::RefPtr _source_wheel; + [[nodiscard]] MinMax const &get_radii(); + [[nodiscard]] Geom::Point const &get_marker_point(int index); + int _active_index = 0; + int _hover_index = -1; + bool _hue_lock = 0; + std::vector_relative_hue_angles; + static constexpr double marker_click_tolerance = 5.0; + sigc::signal _signal_color_hovered; + double lightness = 1.0; + double saturation = 1.0; + void update_wheel_source(); +}; + } // namespace Inkscape::UI::Widget #endif // INK_COLORWHEEL_HSLUV_H diff --git a/src/ui/widget/marker-combo-box.cpp b/src/ui/widget/marker-combo-box.cpp index c71602e3d270c65e59c553d0c4ca35a31b2daa9c..62472141b7e23d0fcc0736e2f1e414864942341a 100644 --- a/src/ui/widget/marker-combo-box.cpp +++ b/src/ui/widget/marker-combo-box.cpp @@ -132,7 +132,10 @@ MarkerComboBox::MarkerComboBox(Glib::ustring id, int l) : _orient_angle(get_widget(_builder, "orient-angle")), _orient_flip_horz(get_widget(_builder, "btn-horz-flip")), _current_img(get_widget(_builder, "current-img")), - _edit_marker(get_widget(_builder, "edit-marker")) + _edit_marker(get_widget(_builder, "edit-marker")), + _recolorButtonTriger_1(Gtk::make_managed()), + _recolorArtWdgt_1(Gtk::make_managed()), + _recolorPopOver_1(Gtk::make_managed()) { auto& input_grid = get_widget(_builder, "input-grid"); _widgets = reparent_properties(input_grid, _grid, true, false, 1); @@ -317,6 +320,14 @@ MarkerComboBox::MarkerComboBox(Glib::ustring id, int l) : update_scale_link(); _current_img.set_paintable(to_texture(g_image_none[_loc])); + + setupRecolorButton(_recolorButtonTriger_1, _recolorPopOver_1, _recolorArtWdgt_1); + _grid.add_full_row(_recolorButtonTriger_1); + _recolorButtonTriger_1->signal_clicked().connect([this]() { + _recolorPopOver_1->popup(); + _recolorArtWdgt_1->performUpdate(); + }); + _recolorButtonTriger_1->hide(); set_visible(true); } @@ -351,7 +362,10 @@ void MarkerComboBox::update_widgets_from_marker(SPMarker* marker) { _orient_angle.set_active(); _angle_btn.set_sensitive(true); } - } + _recolorButtonTriger_1->show(); + + }else + _recolorButtonTriger_1->hide(); } void MarkerComboBox::update_scale_link() { @@ -831,6 +845,19 @@ void MarkerComboBox::preview_scale(double scale) { _current_img.set_size_request(static_cast(std::round(scale * ITEM_WIDTH)), static_cast(std::round(scale * ITEM_HEIGHT))); } } +void MarkerComboBox::setupRecolorButton(Gtk::Button *recolorButtonTriger, Gtk::Popover *recolorPopOver , UI::Widget::RecolorArt *recolorArtWdgt) +{ + recolorButtonTriger->set_label("Recolor Selection"); + recolorButtonTriger->set_hexpand(false); + recolorButtonTriger->set_vexpand(false); + recolorButtonTriger->set_size_request(180); + recolorButtonTriger->set_halign(Gtk::Align::CENTER); + recolorButtonTriger->set_valign(Gtk::Align::START); + recolorPopOver->set_parent(*recolorButtonTriger); + recolorPopOver->set_child(*recolorArtWdgt); + recolorPopOver->set_position(Gtk::PositionType::LEFT); + recolorButtonTriger->set_margin_top(8); +} } // namespace Inkscape::UI::Widget diff --git a/src/ui/widget/marker-combo-box.h b/src/ui/widget/marker-combo-box.h index 2db8fc167243de1be1afa57aa929753a4ea91b09..c814e7a166dbf90a27d1a53d05ec5c7b39498787 100644 --- a/src/ui/widget/marker-combo-box.h +++ b/src/ui/widget/marker-combo-box.h @@ -17,6 +17,8 @@ #define SEEN_SP_MARKER_COMBO_BOX_H #include +#include +#include #include #include @@ -26,6 +28,7 @@ #include "ink-spin-button.h" #include "ui/operation-blocker.h" #include "ui/widget/widget-vfuncs-class-init.h" +#include "ui/widget/recolor-art.h" namespace Gtk { class Builder; @@ -134,8 +137,29 @@ private: std::unique_ptr _sandbox; InkPropertyGrid _grid; WidgetGroup _widgets; + Gtk::CellRendererPixbuf _image_renderer; + + UI::Widget::RecolorArt *_recolorArtWdgt_1 = nullptr; + Gtk::Button *_recolorButtonTriger_1 = nullptr; + Gtk::Popover* _recolorPopOver_1=nullptr; + class MarkerColumns : public Gtk::TreeModel::ColumnRecord { + public: + Gtk::TreeModelColumn label; + Gtk::TreeModelColumn marker; // ustring doesn't work here on windows due to unicode + Gtk::TreeModelColumn stock; + Gtk::TreeModelColumn> pixbuf; + Gtk::TreeModelColumn history; + Gtk::TreeModelColumn separator; + + MarkerColumns() { + add(label); add(stock); add(marker); add(history); add(separator); add(pixbuf); + } + }; + MarkerColumns marker_columns; - void update_ui(SPMarker* marker, bool select); + void setupRecolorButton(Gtk::Button *recolorButtonTriger, Gtk::Popover *recolorPopOver, + UI::Widget::RecolorArt *recolorArtWdgt); + void update_ui(SPMarker *marker, bool select); void update_widgets_from_marker(SPMarker* marker); void update_store(); Glib::RefPtr add_separator(bool filler); diff --git a/src/ui/widget/multi-marker-color-plate.cpp b/src/ui/widget/multi-marker-color-plate.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5077b994b1184fb1fede9e29c4ce82ae6434f9a3 --- /dev/null +++ b/src/ui/widget/multi-marker-color-plate.cpp @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include +#include +#include + +#include "actions/actions-tools.h" +#include "canvas.h" +#include "color-notebook.h" +#include "colors/spaces/base.h" +#include "colors/spaces/enum.h" +#include "desktop-style.h" +#include "desktop.h" +#include "document-undo.h" +#include "document.h" +#include "gradient-chemistry.h" +#include "ink-color-wheel.h" +#include "inkscape.h" +#include "multi-marker-color-plate.h" +#include "object/sp-defs.h" +#include "object/sp-gradient.h" +#include "object/sp-linear-gradient.h" +#include "object/sp-marker.h" +#include "object/sp-mask.h" +#include "object/sp-mesh-gradient.h" +#include "object/sp-object.h" +#include "object/sp-pattern.h" +#include "object/sp-radial-gradient.h" +#include "object/sp-stop.h" +#include "object/sp-text.h" +#include "object/sp-use.h" +#include "pattern-manipulation.h" +#include "selection.h" +#include "style-internal.h" +#include "style.h" +#include "ui/builder-utils.h" +#include "ui/dialog/dialog-base.h" +#include "ui/icon-names.h" +#include "ui/pack.h" +#include "ui/widget/color-page.h" +#include "ui/widget/color-preview.h" +#include "ui/widget/color-slider.h" +#include "ui/widget/icon-combobox.h" +#include "ui/widget/ink-spin-button.h" +#include "xml/repr.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +MultiMarkerColorPlate::MultiMarkerColorPlate(std::shared_ptr colors) + : Gtk::Box(Gtk::Orientation::VERTICAL) + , _specific_colors(std::make_shared(manager->find(Space::Type::HSL), + colors->getAlphaConstraint().value_or(true))) + , _color_wheel(Gtk::make_managed()) + , _hue_lock(*Gtk::make_managed()) + , _lightness_bar(*Gtk::make_managed(adjustment, Gtk::Orientation::HORIZONTAL)) + , _saturation_bar(*Gtk::make_managed(_saturation_adjustment, Gtk::Orientation::HORIZONTAL)) + , _color_wheel_preview(Gtk::make_managed()) + , _grid(Gtk::make_managed()) + , _spaces_combo(Gtk::make_managed()) + , _switcher(Gtk::make_managed()) + , _spaces_stack(Gtk::make_managed()) + , _reset(Gtk::make_managed()) +{ + _specific_colors->set(Color(0xFF0000FF)); + + _switcher->set_stack(*_spaces_stack); + + _spaces_combo->add_css_class("regular"); + _spaces_combo->set_focusable(false); + _spaces_combo->set_tooltip_text(_("Choose style of color selection")); + _spaces_combo->set_hexpand(false); + _spaces_combo->set_halign(Gtk::Align::END); + _spaces_combo->set_margin_top(4); + _spaces_combo->set_margin_bottom(8); + + _spaces_combo->signal_changed().connect([this](int index) { + _specific_colors = _color_sets[index].second; + _spaces_stack->set_visible_child(_color_sets[index].first); + _specific_colors_changed.disconnect(); + _specific_colors_changed = _specific_colors->signal_changed.connect([this]() { + Color new_color = _specific_colors->get().value(); + if (_color_wheel->getActiveIndex() != -1) { + _color_wheel->changeColor(_color_wheel->getActiveIndex(), new_color); + _color_wheel_preview->setRgba32(new_color.toRGBA()); + } + }); + }); + + int index = 0; + for (auto &space : Colors::Manager::get().spaces(Space::Traits::Picker)) { + _createSlidersForSpace(space, _specific_colors, index); + _addPageForSpace(space, index++); + } + + _lightness_icon->set_from_icon_name(INKSCAPE_ICON("lightness")); + _lightness_icon->set_tooltip_text(_("change saturation for all if hue lock is on")); + _lightness_bar.set_value_pos(Gtk::PositionType::RIGHT); + _lightness_bar.set_hexpand(true); + _lightness_bar.set_draw_value(true); + _lightness_bar.signal_value_changed().connect([this]() { + double value = _lightness_bar.get_value(); + _color_wheel->setLightness(value); + }); + + + _saturation_icon->set_from_icon_name(INKSCAPE_ICON("saturation")); + _saturation_icon->set_tooltip_text(_("change saturation for all if hue lock is on")); + _saturation_bar.set_value_pos(Gtk::PositionType::RIGHT); + _saturation_bar.set_hexpand(true); + _saturation_bar.set_draw_value(true); + _saturation_bar.signal_value_changed().connect([this]() { + double value = _saturation_bar.get_value(); + _color_wheel->setSaturation(value); + }); + + _hue_lock_image = Gtk::make_managed(); + _hue_lock_image->set_from_icon_name(INKSCAPE_ICON("object-unlocked")); + _hue_lock.set_child(*_hue_lock_image); + _hue_lock.signal_toggled().connect([this]() { + _color_wheel->toggleHueLock(_hue_lock.get_active()); + if (_hue_lock.get_active()) { + _hue_lock_image->set_from_icon_name(INKSCAPE_ICON("object-locked")); + _hue_lock.set_child(*_hue_lock_image); + } else { + _hue_lock_image->set_from_icon_name(INKSCAPE_ICON("object-unlocked")); + _hue_lock.set_child(*_hue_lock_image); + } + }); + _hue_lock.set_tooltip_text(_("lock hue angles for colors set")); + _hue_lock.set_hexpand(false); + _hue_lock.set_margin_top(8); + _hue_lock.set_halign(Gtk::Align::END); + + + _color_wheel_preview->set_hexpand(false); + _color_wheel_preview->set_can_focus(false); + _color_wheel_preview->set_size_request(35,35); + _color_wheel_preview->set_halign(Gtk::Align::START); + _color_wheel_preview->set_margin_top(8); + _color_wheel_preview->setStyle(_color_wheel_preview->Style::Outlined); + + auto image = Gtk::make_managed(); + image->set_from_icon_name(INKSCAPE_ICON("reset-settings")); + _reset->set_child(*image); + _reset->set_margin_top(8); + _reset->signal_clicked().connect([this]() { + if (_ra) { + _ra->onResetClicked(); + } + }); + + auto box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL); + box->set_spacing(64); + box->append(*_color_wheel_preview); + box->append(*_reset); + box->append(_hue_lock); + + _lightness_box->append(*_lightness_icon); + _lightness_box->append(_lightness_bar); + + _saturation_box->append(*_saturation_icon); + _saturation_box->append(_saturation_bar); + + _spaces_stack->set_visible_child("RGB"); + + + + append(*box); + append(*_color_wheel); + append(*_lightness_box); + append(*_saturation_box); + append(*_spaces_combo); + append(*_spaces_stack); +} + +MultiMarkerColorPlate::~MultiMarkerColorPlate() { + if(_specific_colors_changed.connected()) + { + _specific_colors_changed.disconnect(); + } +} + +void MultiMarkerColorPlate::_addPageForSpace(std::shared_ptr space, int page_num) +{ + auto mode_name = space->getName(); + _spaces_combo->add_row(space->getIcon(), mode_name, page_num); +} + +void MultiMarkerColorPlate::_createSlidersForSpace(std::shared_ptr space, + std::shared_ptr &colors, int index) +{ + auto mode_name = space->getName(); + Gtk::Grid *_grid = Gtk::make_managed(); + auto new_colors = std::make_shared(space, colors->getAlphaConstraint().value_or(true)); + new_colors->set(colors->get().value()); + int row = 0; + for (auto &component : new_colors->getComponents()) { + auto label = Gtk::make_managed(); + auto slider = Gtk::make_managed(new_colors, component); + auto spin = Gtk::make_managed(); + spin->set_digits(component.id == "alpha" ? 0 : 1); + if (component.scale < 100) { + // for small values increase precision + spin->set_digits(2); + spin->get_adjustment()->set_step_increment(0.1); + } + _grid->attach(*label, 0, row); + _grid->attach(*slider, 1, row); + _grid->attach(*spin, 2, row++); + _channels.emplace_back(std::make_unique(new_colors, *label, *slider, *spin)); + } + _color_sets[index] = {mode_name, new_colors}; + _spaces_stack->add(*_grid, mode_name, mode_name); +} + +} // namespace Widget +} // namespace UI +} // namespace Inkscape diff --git a/src/ui/widget/multi-marker-color-plate.h b/src/ui/widget/multi-marker-color-plate.h new file mode 100644 index 0000000000000000000000000000000000000000..91ea86452bbb2c2b92d750a1af05b1941ca8bc9f --- /dev/null +++ b/src/ui/widget/multi-marker-color-plate.h @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef WIDGET_MULTI_MARKER_COLOR_PLATE_H +#define WIDGET_MULTI_MARKER_COLOR_PLATE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "canvas.h" +#include "src/colors/color-set.h" +#include "src/colors/manager.h" +#include "style-internal.h" +#include "style.h" +#include "ui/builder-utils.h" +#include "ui/widget/color-page.h" +#include "ui/widget/color-preview.h" +#include "ui/widget/icon-combobox.h" +#include "ui/widget/ink-color-wheel.h" +#include "ui/widget/paint-selector.h" +#include "ui/widget/recolor-art.h" + +using namespace Inkscape::Colors; + +namespace Inkscape::Colors { +class Color; +class ColorSet; +namespace Space { +class AnySpace; +} +} // namespace Inkscape::Colors + +namespace Gtk { +class Widget; +} // namespace Gtk + +class SPDesktop; + +namespace Inkscape { +namespace UI { +namespace Tools { +class ToolBase; +} + +namespace Widget { + +class MultiMarkerColorPlate : public Gtk::Box +{ +public: + MultiMarkerColorPlate(std::shared_ptr colors); + ~MultiMarkerColorPlate(); + void setColor(std::vector &colors) { _color_wheel->setColor(colors); } + void setLightness(double value) + { + _color_wheel->setLightness(value); + _lightness_bar.set_value(value); + } + + void setSaturation(double value) + { + _color_wheel->setSaturation(value); + _saturation_bar.set_value(value); + } + void setRecolorWidget(RecolorArt *ra) { _ra = ra; } + Colors::Color getColor() { return _color_wheel->getColor(); } + std::vector getColors() { return _color_wheel->getColors(); } + bool getHueLock() { return _color_wheel->getHueLock(); } + void toggleHueLock(bool locked) { _color_wheel->toggleHueLock(locked); _hue_lock.set_active(locked);} + int getActiveIndex() { return _color_wheel->getActiveIndex(); } + void setActiveIndex(int index) { _color_wheel->setActiveIndex(index); } + int getHoverIndex() { return _color_wheel->getHoverIndex(); } + // bool isLightnessChanged(){return _color_wheel->isLightnessChanged();} + sigc::connection connect_color_hovered(sigc::slot slot) + { + return _color_wheel->connect_color_hovered(slot); + } + sigc::connection connect_color_changed(sigc::slot slot) + { + return _color_wheel->connect_color_changed(slot); + } + void changeColor(int index, Color color) + { + _color_wheel->changeColor(index, color); + _color_wheel_preview->setRgba32(color.toRGBA()); + _specific_colors->set(color); + } + +private: + MultiMarkerWheel *_color_wheel = nullptr; + Gtk::Image*_lightness_icon = Gtk::make_managed(); + // Gtk::Label *_lightness_label = Gtk::make_managed("Lightness : "); + Gtk::Box *_lightness_box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL); + Glib::RefPtr adjustment = Gtk::Adjustment::create(100.0, 0.0, 100.0, 1.0, 10.0); + Gtk::Scale &_lightness_bar; + + Gtk::Image*_saturation_icon = Gtk::make_managed(); + Gtk::Box *_saturation_box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL); + Glib::RefPtr _saturation_adjustment = Gtk::Adjustment::create(100.0, 0.0, 100.0, 1.0, 10.0); + Gtk::Scale &_saturation_bar; + + Gtk::ToggleButton &_hue_lock; + ColorPreview *_color_wheel_preview = nullptr; + Manager *manager = &Manager::get(); + Gtk::Grid *_grid = nullptr; + std::shared_ptr _specific_colors; + std::vector> _channels; + Gtk::Image *_hue_lock_image = nullptr; + IconComboBox *_spaces_combo = nullptr; + Gtk::Stack *_spaces_stack = nullptr; + Gtk::StackSwitcher *_switcher = nullptr; + RecolorArt* _ra = nullptr; + Gtk::Button* _reset = nullptr; + std::map>> _color_sets; + sigc::connection _specific_colors_changed ; + void _addPageForSpace(std::shared_ptr space, int page_num); + void _createSlidersForSpace(std::shared_ptr space, + std::shared_ptr &colors, int index); +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // WIDGET_MULTI_MARKER_COLOR_PLATE_H diff --git a/src/ui/widget/paint-selector.cpp b/src/ui/widget/paint-selector.cpp index 431c069465745ab4c2fe67ef022201b84b9e10d6..a3bf6d7189a92973e90a35d94a5644d7d29e00b8 100644 --- a/src/ui/widget/paint-selector.cpp +++ b/src/ui/widget/paint-selector.cpp @@ -25,19 +25,31 @@ #include #include +#include "desktop-style.h" +#include "desktop.h" #include "document.h" #include "inkscape.h" #include "object/sp-hatch.h" #include "object/sp-linear-gradient.h" #include "object/sp-mesh-gradient.h" #include "object/sp-pattern.h" +#include "object/sp-use.h" +#include "object/sp-mask.h" #include "object/sp-radial-gradient.h" #include "object/sp-stop.h" +#include "path-prefix.h" +#include "pattern-manipulation.h" +#include "selection.h" +#include "style.h" +#include "svg/css-ostringstream.h" +#include "ui/icon-loader.h" #include "ui/icon-names.h" #include "ui/pack.h" #include "ui/widget/color-notebook.h" #include "ui/widget/gradient-editor.h" #include "ui/widget/pattern-editor.h" +#include "ui/widget/swatch-selector.h" +#include "ui/widget/recolor-art.h" #include "widgets/widget-sizes.h" #ifdef SP_PS_VERBOSE @@ -114,7 +126,23 @@ GradientSelectorInterface *PaintSelector::getGradientFromData() const PaintSelector::PaintSelector(FillOrStroke kind, std::shared_ptr colors) : _selected_colors(std::move(colors)) + , _recolorArtWdgt_1(Gtk::make_managed()) + , _recolorArtWdgt_2(Gtk::make_managed()) + , _recolorArtWdgt_3(Gtk::make_managed()) + , _recolorArtWdgt_4(Gtk::make_managed()) + , _recolorArtWdgt_5(Gtk::make_managed()) + , _recolorButtonTriger_1(Gtk::make_managed()) + , _recolorButtonTriger_2(Gtk::make_managed()) + , _recolorButtonTriger_3(Gtk::make_managed()) + , _recolorButtonTriger_4(Gtk::make_managed()) + , _recolorButtonTriger_5(Gtk::make_managed()) + , _recolorPopOver_1(Gtk::make_managed()) + , _recolorPopOver_2(Gtk::make_managed()) + , _recolorPopOver_3(Gtk::make_managed()) + , _recolorPopOver_4(Gtk::make_managed()) + , _recolorPopOver_5(Gtk::make_managed()) { + set_orientation(Gtk::Orientation::VERTICAL); _mode = static_cast(-1); // huh? do you mean 0xff? -- I think this means "not in the enum" @@ -188,6 +216,53 @@ PaintSelector::PaintSelector(FillOrStroke kind, std::shared_ptrset_visible(kind == FILL); + + _desktop = SP_ACTIVE_DESKTOP; + if (_desktop) { + auto selection = _desktop->getSelection(); + if (selection) { + selection->connectChanged(sigc::mem_fun(*this, &PaintSelector::onSelectionChanged)); + } + } + + setupRecolorButton(_recolorButtonTriger_1, _recolorPopOver_1, _recolorArtWdgt_1); + setupRecolorButton(_recolorButtonTriger_2, _recolorPopOver_2, _recolorArtWdgt_2); + setupRecolorButton(_recolorButtonTriger_3, _recolorPopOver_3, _recolorArtWdgt_3); + setupRecolorButton(_recolorButtonTriger_4, _recolorPopOver_4, _recolorArtWdgt_4); + setupRecolorButton(_recolorButtonTriger_5, _recolorPopOver_5, _recolorArtWdgt_5); + + _recolorButtonTriger_1->signal_clicked().connect([this]() { + _recolorPopOver_1->popup(); + _recolorArtWdgt_1->performUpdate(); + }); + + _recolorButtonTriger_2->signal_clicked().connect([this]() { + _recolorPopOver_2->popup(); + _recolorArtWdgt_2->performUpdate(); + }); + + _recolorButtonTriger_3->signal_clicked().connect([this]() { + _recolorPopOver_3->popup(); + _recolorArtWdgt_3->performUpdate(); + }); + + _recolorButtonTriger_4->signal_clicked().connect([this]() { + _recolorPopOver_4->popup(); + _recolorArtWdgt_4->performUpdate(); + }); + + _recolorButtonTriger_5->signal_clicked().connect([this]() { + _recolorPopOver_5->popup(); + _recolorArtWdgt_5->performUpdate(); + }); + + _frame->append(*_recolorButtonTriger_1); + _recolorButtonTriger_1->hide(); + if (_desktop = SP_ACTIVE_DESKTOP) { + if (auto sel = _desktop->getSelection()) { + onSelectionChanged(sel); + } + } } StyleToggleButton *PaintSelector::style_button_add(gchar const *pixmap, PaintSelector::Mode mode, gchar const *tip) @@ -225,7 +300,9 @@ void PaintSelector::fillrule_toggled(FillRuleRadioButton *tb) } } -void PaintSelector::setMode(Mode mode) { +void PaintSelector::setMode(Mode mode) +{ + if(_recolorPopOver_1->get_visible()||_recolorPopOver_2->get_visible()||_recolorPopOver_3->get_visible()||_recolorPopOver_4->get_visible()) return; set_mode_ex(mode, false); } @@ -276,6 +353,11 @@ void PaintSelector::set_mode_ex(Mode mode, bool switch_style) { } _mode = mode; _signal_mode_changed.emit(_mode, switch_style); + if (_desktop = SP_ACTIVE_DESKTOP) { + if (auto sel = _desktop->getSelection()) { + onSelectionChanged(sel); + } +} _update = false; } } @@ -506,12 +588,16 @@ void PaintSelector::set_mode_color() auto const color_selector = Gtk::make_managed(_selected_colors); color_selector->set_visible(true); UI::pack_start(*_selector_solid_color, *color_selector, true, true); + UI::pack_start(*_selector_solid_color, *_recolorButtonTriger_2, false, false); + _recolorButtonTriger_2->hide(); + /* Pack everything to frame */ _frame->append(*_selector_solid_color); color_selector->set_label(_("Flat color")); } _selector_solid_color->set_visible(true); + _selector_solid_color->set_vexpand(false); } _label->set_markup(""); //_("Flat color")); @@ -556,6 +642,8 @@ void PaintSelector::set_mode_gradient(PaintSelector::Mode mode) _selector_gradient->signal_changed().connect(sigc::mem_fun(*this, &PaintSelector::gradient_changed)); _selector_gradient->signal_stop_selected().connect([this](SPStop* stop) { _signal_stop_selected.emit(stop); }); /* Pack everything to frame */ + _selector_gradient->getColorBox().append(*_recolorButtonTriger_3); + _recolorButtonTriger_3->hide(); _frame->append(*_selector_gradient); } catch (std::exception& ex) { @@ -825,6 +913,7 @@ void PaintSelector::set_mode_mesh(PaintSelector::Mode mode) UI::pack_start(*_selector_mesh, *hb2, false, false, AUX_BETWEEN_BUTTON_GROUPS); _frame->append(*_selector_mesh); + _frame->reorder_child_after(*_recolorButtonTriger_1,*_selector_mesh); } _selector_mesh->set_visible(true); @@ -911,8 +1000,10 @@ void PaintSelector::pattern_destroy(GtkWidget *widget, PaintSelector * /*psel*/) g_object_unref(G_OBJECT(widget)); } -void PaintSelector::pattern_change(GtkWidget * /*widget*/, PaintSelector *psel) { psel->_signal_changed.emit(); } - +void PaintSelector::pattern_change(GtkWidget * /*widget*/, PaintSelector *psel) +{ + psel->_signal_changed.emit(); +} /*update pattern list*/ void PaintSelector::updatePatternList(SPPattern *pattern) @@ -941,7 +1032,12 @@ void PaintSelector::set_mode_pattern(PaintSelector::Mode mode) _selector_pattern->signal_changed().connect([this](){ _signal_changed.emit(); }); _selector_pattern->signal_color_changed().connect([this](Colors::Color const &){ _signal_changed.emit(); }); _selector_pattern->signal_edit().connect([this](){ _signal_edit_pattern.emit(); }); + // _selector_pattern->get_input_grid().attach(*_recolorButtonTriger_4,0,10); + // _recolorButtonTriger_4->set_size_request(-1); + _recolorButtonTriger_4->set_label("Recolor Pattern"); _frame->append(*_selector_pattern); + _frame->append(*_recolorButtonTriger_4); + _recolorButtonTriger_4->hide(); } SPDocument* document = SP_ACTIVE_DOCUMENT; @@ -1068,6 +1164,8 @@ void PaintSelector::set_mode_swatch(PaintSelector::Mode mode) gsel->signal_released().connect(sigc::mem_fun(*this, &PaintSelector::gradient_released)); gsel->signal_changed().connect(sigc::mem_fun(*this, &PaintSelector::gradient_changed)); + _selector_swatch->append(*_recolorButtonTriger_5); + _recolorButtonTriger_5->hide(); // Pack everything to frame _frame->append(*_selector_swatch); } else { @@ -1131,6 +1229,184 @@ PaintSelector::Mode PaintSelector::getModeForStyle(SPStyle const &style, FillOrS return mode; } +void PaintSelector::onSelectionChanged(Inkscape::Selection *selection) +{ + if (_change_sel) { + std::cout << "return\n"; + return; + } + auto group = dynamic_cast(selection->single()); + auto use_group = dynamic_cast(selection->single()); + auto item = dynamic_cast(selection->single()); + bool pattern_colors = false; + SPMask *mask = nullptr; + if (item) + { + mask = dynamic_cast(item->getMaskObject()); + pattern_colors = has_colors_pattern(item); + } + if (selection->size() > 1 || group || use_group || mask || _mode == MODE_GRADIENT_MESH || pattern_colors) { + if (_mode == MODE_MULTIPLE || _mode == MODE_UNSET || _mode == MODE_GRADIENT_MESH) { + _recolorButtonTriger_1->show(); + _recolorButtonTriger_2->hide(); + _recolorButtonTriger_3->hide(); + _recolorButtonTriger_4->hide(); + _recolorButtonTriger_5->hide(); + // std::cout<<"shoed 1 hide 2\n"; + } else if (_mode == MODE_SOLID_COLOR) { + _recolorButtonTriger_2->show(); + _recolorButtonTriger_1->hide(); + _recolorButtonTriger_3->hide(); + _recolorButtonTriger_4->hide(); + _recolorButtonTriger_5->hide(); + // std::cout<<"shoed 2 hide 1\n"; + } else if (_mode == MODE_GRADIENT_RADIAL || _mode == MODE_GRADIENT_LINEAR) { + _recolorButtonTriger_1->hide(); + _recolorButtonTriger_2->hide(); + _recolorButtonTriger_3->show(); + _recolorButtonTriger_4->hide(); + _recolorButtonTriger_5->hide(); + // std::cout<<"hide 1,2 selection size : "<size()<<" mode : "<<_mode<hide(); + _recolorButtonTriger_2->hide(); + _recolorButtonTriger_3->hide(); + _recolorButtonTriger_4->show(); + _recolorButtonTriger_5->hide(); + } + else if(_mode == MODE_SWATCH) + { + _recolorButtonTriger_1->hide(); + _recolorButtonTriger_2->hide(); + _recolorButtonTriger_3->hide(); + _recolorButtonTriger_4->hide(); + _recolorButtonTriger_5->show(); + } + else { + _recolorButtonTriger_1->hide(); + _recolorButtonTriger_2->hide(); + _recolorButtonTriger_3->hide(); + _recolorButtonTriger_4->hide(); + _recolorButtonTriger_5->hide(); + // std::cout<<"hide 1,2 selection size : "<size()<<" mode : "<<_mode<hide(); + _recolorButtonTriger_2->hide(); + _recolorButtonTriger_3->hide(); + _recolorButtonTriger_4->hide(); + _recolorButtonTriger_5->hide(); + // std::cout<<"hide 1,2\n"; + } + + if (_recolorPopOver_1->get_visible()) { + if (!_change_sel) { + _change_sel = true; + _recolorArtWdgt_1->performUpdate(); + _change_sel = false; + } + return; + } + + if (_recolorPopOver_2->get_visible()) { + if (!_change_sel) { + _change_sel = true; + _recolorArtWdgt_2->performUpdate(); + _change_sel = false; + } + return; + } + + if (_recolorPopOver_3->get_visible()) { + if (!_change_sel) { + _change_sel = true; + _recolorArtWdgt_3->performUpdate(); + _change_sel = false; + } + return; + } + + if (_recolorPopOver_4->get_visible()) { + if (!_change_sel) { + _change_sel = true; + _recolorArtWdgt_4->performUpdate(); + _change_sel = false; + } + return; + } +} + +void PaintSelector::setupRecolorButton(Gtk::Button *recolorButtonTriger, Gtk::Popover *recolorPopOver , UI::Widget::RecolorArt *recolorArtWdgt) +{ + recolorArtWdgt->setPaintSelector(this); + recolorButtonTriger->set_label("Recolor Selection"); + recolorButtonTriger->set_hexpand(false); + recolorButtonTriger->set_vexpand(false); + recolorButtonTriger->set_size_request(180); + recolorButtonTriger->set_halign(Gtk::Align::CENTER); + recolorButtonTriger->set_valign(Gtk::Align::START); + recolorPopOver->set_parent(*recolorButtonTriger); + recolorPopOver->set_child(*recolorArtWdgt); + recolorPopOver->set_autohide(false); + recolorPopOver->set_position(Gtk::PositionType::LEFT); + recolorButtonTriger->set_margin_top(8); +} + +void PaintSelector::set_change_selection(bool value) +{ + _change_sel = value; +} + +bool PaintSelector::has_colors_pattern(SPItem * item) +{ + std::setcolors; + SPPattern* patternstroke = nullptr; + SPPattern* patternfill = nullptr; + SPPattern* pattern = nullptr; + if(item && item->style) + { + patternstroke = dynamic_cast(item->style->getStrokePaintServer()); + patternfill = dynamic_cast(item->style->getFillPaintServer()); + if(patternstroke || patternfill)std::cout<<"pattern\n"; + } + if(patternstroke) pattern = patternstroke; + if(patternfill) pattern = patternfill; + if(!pattern) return false; + SPPattern *root = pattern->rootPattern(); + for (auto &child : root->children) { + auto group = cast(&child); + if (group) { + std::cout<<"group pattern\n"; + for (auto &child : group->children) { + if (auto c = dynamic_cast(&child)) { + if (c->style->fill.isColor()) { + std::string rgba = c->style->fill.getColor().toString(true); + colors.insert(rgba); + } + if (c->style->stroke.isColor()) { + std::string rgba = c->style->stroke.getColor().toString(true); + colors.insert(rgba); + } + } + } + } + auto item = cast(&child); + if (!item || !item->style) continue; + + if (item->style->fill.isColor()) { + std::string rgba = item->style->fill.getColor().toString(true); + colors.insert(rgba); + } + if (item->style->stroke.isColor()) { + std::string rgba = item->style->stroke.getColor().toString(true); + colors.insert(rgba); + } + } + return colors.size()>1; +} } // namespace Widget } // namespace UI diff --git a/src/ui/widget/paint-selector.h b/src/ui/widget/paint-selector.h index c126f04252b3f904aa42a84ac35c06979bd515dd..97fe1a52ec090e38f99bed8eeeb2d09b7e3e5df6 100644 --- a/src/ui/widget/paint-selector.h +++ b/src/ui/widget/paint-selector.h @@ -19,6 +19,7 @@ #include "fill-or-stroke.h" #include "ui/widget/gradient-selector.h" #include "ui/widget/swatch-selector.h" +#include "selection.h" class SPGradient; class SPLinearGradient; @@ -27,12 +28,15 @@ class SPRadialGradient; class SPMeshGradient; #endif class SPDesktop; +class Selection; class SPPattern; class SPStyle; namespace Gtk { class Label; class ToggleButton; +class Button; +class Popover; } // namespace Gtk namespace Inkscape::UI::Widget { @@ -41,6 +45,7 @@ class FillRuleRadioButton; class GradientEditor; class PatternEditor; class StyleToggleButton; +class RecolorArt; /** * Generic paint selector widget. @@ -93,6 +98,26 @@ class PaintSelector : public Gtk::Box { Gtk::Box *_selector_mesh = nullptr; SwatchSelector *_selector_swatch = nullptr; PatternEditor* _selector_pattern = nullptr; + + UI::Widget::RecolorArt *_recolorArtWdgt_1 = nullptr; + UI::Widget::RecolorArt *_recolorArtWdgt_2 = nullptr; + UI::Widget::RecolorArt *_recolorArtWdgt_3 = nullptr; + UI::Widget::RecolorArt *_recolorArtWdgt_4 = nullptr; + UI::Widget::RecolorArt *_recolorArtWdgt_5 = nullptr; + Gtk::Button *_recolorButtonTriger_1 = nullptr; + Gtk::Button *_recolorButtonTriger_2 = nullptr; + Gtk::Button *_recolorButtonTriger_3 = nullptr; + Gtk::Button *_recolorButtonTriger_4 = nullptr; + Gtk::Button *_recolorButtonTriger_5 = nullptr; + Gtk::Popover* _recolorPopOver_1=nullptr; + Gtk::Popover* _recolorPopOver_2=nullptr; + Gtk::Popover* _recolorPopOver_3=nullptr; + Gtk::Popover* _recolorPopOver_4=nullptr; + Gtk::Popover* _recolorPopOver_5=nullptr; + bool _change_sel=false; + SPDesktop*_desktop=nullptr; + void onSelectionChanged(Inkscape::Selection *selection); + void setupRecolorButton(Gtk::Button *recolorButtonTriger , Gtk::Popover*recolorPopOver , UI::Widget::RecolorArt *recolorArtWdgt); Gtk::Label *_label; GtkWidget *_patternmenu = nullptr; @@ -192,6 +217,8 @@ class PaintSelector : public Gtk::Box { Geom::Scale get_pattern_gap(); Glib::ustring get_pattern_label(); bool is_pattern_scale_uniform(); + void set_change_selection(bool value); + bool has_colors_pattern(SPItem * item); }; enum { diff --git a/src/ui/widget/pattern-editor.h b/src/ui/widget/pattern-editor.h index c1753c375dbe8a1809444b7967884f8da25a1164..a4e15902e084115aa967d394b1aeeacb27b44b45 100644 --- a/src/ui/widget/pattern-editor.h +++ b/src/ui/widget/pattern-editor.h @@ -68,6 +68,9 @@ public: // get pattern label Glib::ustring get_label(); + Gtk::Grid& get_input_grid(){return _input_grid;}; + + private: sigc::signal _signal_changed; sigc::signal _signal_color_changed; diff --git a/src/ui/widget/recolor-art.cpp b/src/ui/widget/recolor-art.cpp new file mode 100644 index 0000000000000000000000000000000000000000..171f6d55d20b7b00e226109f99700d3c71f0f7a3 --- /dev/null +++ b/src/ui/widget/recolor-art.cpp @@ -0,0 +1,1034 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "recolor-art.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// #include +#include +#include + +#include "actions/actions-tools.h" +#include "canvas.h" +#include "color-notebook.h" +#include "desktop-style.h" +#include "desktop.h" +#include "document-undo.h" +#include "document.h" +#include "gradient-chemistry.h" +#include "ink-color-wheel.h" +#include "inkscape.h" +#include "object/sp-defs.h" +#include "object/sp-gradient.h" +#include "object/sp-linear-gradient.h" +#include "object/sp-marker.h" +#include "object/sp-mask.h" +#include "object/sp-mesh-gradient.h" +#include "object/sp-object.h" +#include "object/sp-pattern.h" +#include "object/sp-radial-gradient.h" +#include "object/sp-stop.h" +#include "object/sp-tspan.h" +#include "object/sp-text.h" +#include "object/sp-use.h" +#include "pattern-manipulation.h" +#include "selection.h" +#include "style-internal.h" +#include "style.h" +#include "ui/builder-utils.h" +#include "ui/dialog/dialog-base.h" +#include "ui/icon-names.h" +#include "ui/widget/color-preview.h" +#include "xml/repr.h" +#include "multi-marker-color-plate.h" + +namespace Inkscape { +namespace UI { +namespace Widget { +/* +this class is showed by paint-selector class that has a button +to trigger the popover that has this widget as its child + +related classes : +1- ink-colorwheel for the multimarkercolorwheel in the colorwheel page +2- multi-marker-color-wheel-plate that manges the multimarkercolorwheel and sliders under it +*/ + + +/* +* loops over the vector of objects , firstly try to dynamically cast the spobject to spitem +* if it is casted check if it is a mask or not if mask extract the spobjects +* from it push into vector of spobjects then call collectColors recursivley with this vector +* after this it calls extractObjectColors on the object it self +*/ +void ColorsExtractor::collectColors(std::vectorobjects, std::string kind) +{ + for (auto object : objects) { + auto it = dynamic_cast(object); + if (it) { + auto mask = dynamic_cast(it->getMaskObject()); + if (mask) { + std::vector children_vec; + for (auto &child : mask->children) { + children_vec.push_back(&child); + } + collectColors(children_vec, "mask"); + } + auto text = dynamic_cast(it); // handle text objects color collection by collecting the colors of its tspans children + if(text) + { + if(auto tspan = dynamic_cast(&text->children.front())) + { + std::vector children_vec; + bool noid=true; + for (auto &child : tspan->children) { + if (!child.getId() && noid) { + children_vec.push_back(&child); + noid = false; + } + else if (child.getId()) { + children_vec.push_back(&child); + } + } + collectColors(children_vec, kind); + continue; + } + + } + } + extractObjectColors(object, kind); + } +} + +/* +* checks if object is an spgroup if it is loop over group's children +* call extractObjectColors recursivley on group's children +* if it is not group call extractObjectStyle +*/ +void ColorsExtractor::extractObjectColors(SPObject *object, std::string kind) +{ + if (auto group = dynamic_cast(object)) { + for (SPObject &child : group->children) { + extractObjectColors(&child, kind); + } + } else if (object) { + extractObjectStyle(object, kind); + } +} + +/* +* firstly extract the objects markers value which has 3 markers per object (optional) +* check for fill types (flat fill ,pattern fill , gradient fill) to populate the _selected_colors map +* do same for stroke types +*/ +void ColorsExtractor::extractObjectStyle(SPObject *object, std::string kind) +{ + // check object style + if (!object || !object->style) + return; + SPStyle *style = object->style; + extractMarkerColors(style->marker_start.get_value(), object); + extractMarkerColors(style->marker_mid.get_value(), object); + extractMarkerColors(style->marker_end.get_value(), object); + + // get flat fills + if (style->fill.isColor()) { + auto color = style->fill.getColor(); + manager->populateMap(color, object, (kind == "" ? "fill" : kind + "-fill"));//DataManager + } + + // paint server can be pattern or gradient + // get gradient stops strokes + else if (style->fill.isPaintserver()) { + auto ps = style->getFillPaintServer(); + if (auto pattern = dynamic_cast(ps)) { + extractPatternColors(pattern); + // std::cout << "pattern detected\n"; + } + extractGradientStops(object, true); + } + + if (style->stroke.isColor()) { + auto color = style->stroke.getColor(); + manager->populateMap(color, object, (kind == "" ? "stroke" : kind + "-stroke"));//DataManager + } + // get gradient stops strokes + else if (style->stroke.isPaintserver()) { + auto ps = style->getStrokePaintServer(); + if (auto pattern = dynamic_cast(ps)) { + extractPatternColors(pattern); + } + extractGradientStops(object, false); + } +} + +/* +* check if paint server is spgradient then check if it has patches for extracting mesh gradient +* if it is mesh get its node array and pass it to extractMeshStops +* if not mesh we firstly fork the gradient so we unlink its shared stops with other similar gradients +* so change in selected one doesn't affect the unselected similar one (has same stops colors) +* then call populateStopsMap to save stops refrencess +* then call populateMap to save the spgradient object as a whole to have gradients colors in +* the color list +*/ +void ColorsExtractor::extractGradientStops(SPObject *object, bool isFill) +{ + SPPaintServer *paint_server = isFill ? object->style->getFillPaintServer() : object->style->getStrokePaintServer(); + if (paint_server && dynamic_cast(paint_server)) { + SPGradient *gradient = dynamic_cast(paint_server); + if (!gradient) + return; + SPGradient *vectorGradient = gradient->getVector(); + if (vectorGradient) { + if (vectorGradient->hasPatches()) { + vectorGradient->ensureArray(); + std::unique_ptr nodeArray; + if (auto mesh = dynamic_cast(gradient)) { + nodeArray = std::make_unique(mesh); + extractMeshStops(nodeArray->nodes, object, "mesh"); + } + + } else { + gradient = sp_gradient_get_forked_vector_if_necessary(gradient, true); + if (!gradient) + return; + gradient->ensureVector(); + manager->populateStopsMap(gradient->getFirstStop());//DataManager + } + } + bool is_swatch = gradient->getVector()->isSwatch(); + std::string kind = ""; + if (is_swatch) + { kind = "swatch"; + // std::cout<<"swatches size : "<getGradientVector().stops.size()<(gradient)) + kind = "linear"; + else if (is(gradient)) + kind = "ridal"; + for (auto stop : gradient->getGradientVector().stops) { + if (stop.color.has_value()) { + // if(kind == "swatch") std::cout<<"swatch found : "<populateMap(stop.color.value(), object, kind);//DataManager + } + } + } +} + +/* +* mesh_nodes is a vector of vector of stops so we loop over it normally +* call populateStopsMap and populateMap to populate both maps +*/ +void ColorsExtractor::extractMeshStops(std::vector>& mesh_nodes, SPObject *item, std::string kind) +{ + for (auto nodes : mesh_nodes) { + for (auto node : nodes) { + manager->populateStopsMap(node->stop);//DataManager + if (node->color.has_value()) { + manager->populateMap(node->color.value(), item, kind);//DataManager + } + } + } +} + +/* +* get root pattern then loop over its children and do the whole extraction process by calling +* extractObjectColors to check for spgroups in pattern children +* +*/ +void ColorsExtractor::extractPatternColors(SPPattern *pattern) +{ + SPPattern *root = pattern->rootPattern(); + for (auto &child : root->children) { + extractObjectColors(&child, "pattern"); + } +} + +/* +* extract marker id from marker to get it by herf from the xml tree +* then try to cast the result to spmarker +* loop over the spmarker children and do the extraction process on every child +* by calling extractObjectColors +*/ +void ColorsExtractor::extractMarkerColors(Glib::ustring marker, SPObject *object) +{ + if (marker.size() > 0 && object->document) { + std::string marker_id = std::string(marker.c_str() + 4, std::strlen(marker.c_str()) - 5); + SPObject *m = object->document->getObjectByHref(marker_id); + if (!m) return; + if (auto marker_obj = dynamic_cast(m)) { + for (auto &child : marker_obj->item_list()) { + extractObjectColors(&*child, "marker"); + } + } + } else + return; +} + +/* +* reset selected object colors to their original colors all at once +* used when LP checkbox is unchecked +*/ +void DataManager::revertToOriginalColors(bool is_reset_clicked) +{ + for (auto &[key, items] : _selected_colors) { + if (is_reset_clicked) + items.second.value().new_color = items.second.value().old_color; + for (auto &item : items.first) { + changeObjectColor(item,items.second.value().old_color); + } + recolorStops(key, items.second.value().old_color); + } +} + +/* +* convert selected object colors to the new choosen colors all at once +* used when LP checkbox is unchecked then checked again +*/ +void DataManager::convertToRecoloredColors() +{ + for (auto [key, items] : _selected_colors) { + if (items.second.has_value()) { + Color new_color = items.second.value().new_color; + for (auto &item : items.first) { + changeObjectColor(item,new_color); + } + recolorStops(key, items.second.value().new_color); + } + } +} + +/* +* loop over selection and lowers opacity for items with color +* that doesn't match the parameter color for when user hovers on +* any colorpreview in the list to highlight the hovered on colored objects +*/ +void DataManager::changeOpacity(bool change_opacity , uint32_t color) +{ + for (auto &[key, value] : _selected_colors) { + Color new_color = value.second.value().new_color; + if(change_opacity && key != color) + new_color.setOpacity(0.05); + for (auto &item : value.first) { + changeObjectColor(item, new_color); + } + recolorStops(key, new_color); + } +} + +/* +* get stops vector from the _gradient_stops map and loop over it to +* set them to the new color +*/ +void DataManager::recolorStops(uint32_t old_color, Color new_color) +{ + auto stops_vector = _gradient_stops.find(old_color); + if (stops_vector != _gradient_stops.end()) { + for (auto &stop : stops_vector->second) { + stop->setColor(new_color); + } + } +} + +/* +* loop over stops list and populate the _gradient_stops +* it has a different type of access than the _selected_colors map +* so it has a independent map as it stores just a part of the item not the whole item +* like _selected_colors map +*/ +void DataManager::populateStopsMap(SPStop *stop) +{ + // g_message("populateStopsMap in"); + while (stop) { + uint32_t color = stop->getColor().toRGBA(); + _gradient_stops[color].push_back(stop); + stop = stop->getNextStop(); + } +} + +/* +* populate _selected_colors map with the color as a string key with vector of objects that +* have the same color and a pair of colors that has the old and new colors of type color +* to ensure easy access on both colors +* +*/ +void DataManager::populateMap(Color color, SPObject *item, std::string kind) +{ + color.addOpacity(); + ColorRef ref {item, kind}; + ColorPair pair {color, color}; + uint32_t color_rgba = color.toRGBA(); + auto _selected = _selected_colors.find(color_rgba); + // search if key exist and just push the object to the objects vector + if (_selected != _selected_colors.end()) { + _selected->second.first.push_back(ref); + } else { // create key and push the object and their color ref + colors.push_back(color); + color_wheel_colors_map[color_rgba]=colors.size()-1; + _selected_colors.emplace(color_rgba, std::make_pair(std::vector{ref}, pair)); + } +} + + + +void DataManager::changeObjectColor(const ColorRef& item, Color color) +{ + std::string c = color.toString(true); + if (item.kind == "stop") + return; + std::string k = (item.kind.find("fill") != std::string::npos) + ? "fill" + : (item.kind.find("stroke") != std::string::npos ? "stroke" : ""); + if (k == "") + return; + + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property_string(css, k.c_str(), c); + if (!item.item->getId()) // for handling text content that inheirts its style from parent + { + auto repr = item.item->parent->getRepr(); + sp_repr_css_change(repr, css, "style"); + } else + sp_desktop_apply_css_recursive(item.item, css, true); + sp_repr_css_attr_unref(css); +} + + + +bool DataManager::findSelectedColor(uint32_t key_color) +{ + auto _selected = _selected_colors.find(key_color); + if (_selected == _selected_colors.end()) + return false; + else + return true; +} + +void DataManager::clearData() +{ + colors.clear(); + _gradient_stops.clear(); + _selected_colors.clear(); + color_wheel_colors_map.clear(); +} + +void DataManager::setSelectedNewColor(std::vector &new_colors) +{ + for (auto &[key, value] : _selected_colors) { + int index = color_wheel_colors_map[key]; + value.second.value().new_color = new_colors[index]; + } +} + +const std::vector& DataManager::getSelectedItems(uint32_t key_color) +{ + if(findSelectedColor(key_color)) + return _selected_colors[key_color].first; + return {}; +} + +int DataManager::getColorIndex(uint32_t key_color) +{ + if (color_wheel_colors_map.find(key_color) != color_wheel_colors_map.end()) + return color_wheel_colors_map[key_color]; + return -1; +} + + +std::optional DataManager::getColor(int index) +{ + if (index < 0 || index >= colors.size()) + return std::nullopt; + + return colors[index]; +} + +void DataManager::setSelectedNewColor(uint32_t key_color , Color new_color) +{ + _selected_colors[key_color].second.value().new_color = new_color; +} + +std::optional DataManager::getSelectedNewColor(uint32_t key_color) +{ + auto _selected = _selected_colors.find(key_color); + if (_selected != _selected_colors.end()) + return _selected->second.second.value().new_color; + return std::nullopt; +} + +RecolorArt::RecolorArt() + : Gtk::Box() + ,_notebook(*_builder->get_widget("list-wheel-box")) + ,_color_wheel_page(*_builder->get_widget("color-wheel-page")) + ,_color_wheel(Gtk::make_managed(std::make_shared())) + ,_color_list(*_builder->get_widget("colors-list")) + ,_reset(*_builder->get_widget("reset")) + ,_live_preview(*_builder->get_widget("liveP")) +{ + _desktop = SP_ACTIVE_DESKTOP; + set_name("RecolorArt"); + append(get_widget(_builder, "recolor-art")); + _solid_colors->set(Color(0x000000ff)); + // when recolor widget is closed it resets opacity to mitigate the effect of getSelection function + // and reshow selection boxes again + signal_unmap().connect([&]() { + if (_manager) + _manager->changeOpacity(); + if (_desktop) + _desktop->hideSelectionBoxes(false); + }); + // hide selection boxes after widget gets mapped (this is why it connects to signal idle to activate after finishing mapping) + signal_map().connect([&]() { Glib::signal_idle().connect_once([this]() { if (_desktop) _desktop->hideSelectionBoxes(true); }); }); + _color_wheel->connect_color_changed(static_cast>([this]() { + if(_is_updating) return; // to stop recuresive calling to signal if changed from the color list page + uint32_t cc = _color_wheel->getColor().toRGBA(); + Color c(cc,true); + if(_color_wheel->getActiveIndex() != -1) { + int index = _color_wheel->getActiveIndex(); + if (!_manager->getColor(index).has_value()) + return; + _current_color_id = _manager->getColor(index).value().toRGBA(); + auto idx = findColorItemByKey(_current_color_id); + _selection_model->set_selected(idx.second); + onColorPickerChanged(c, true); + onOriginalColorClicked(_current_color_id); + if (_color_wheel->getHueLock()) { + if (_manager->isSelectedColorsEmpty()) + return; + std::vector new_colors = _color_wheel->getColors(); + _manager->setSelectedNewColor(new_colors); + updateColorModel(new_colors); + _manager->convertToRecoloredColors(); + } + } + })); + _color_wheel->setRecolorWidget(this); + // add hover opacity effect when hovering over markers in the wheel + _color_wheel->connect_color_hovered(static_cast>([this]() { + uint32_t cc = _color_wheel->getColor().toRGBA(); + Color c(cc,true); + if (_color_wheel->getHoverIndex() != -1) { + int index = _color_wheel->getHoverIndex(); + if (!_manager->getColor(index).has_value()) + return; + _current_color_id = _manager->getColor(index).value().toRGBA(); + _manager->changeOpacity(true , _current_color_id); + } + else + _manager->changeOpacity(); + })); + + + + layoutColorPicker(); + _live_preview.signal_toggled().connect(sigc::mem_fun(*this, &RecolorArt::onLivePreviewToggled)); + _live_preview.set_active(true); + _reset.signal_clicked().connect(sigc::mem_fun(*this, &RecolorArt::onResetClicked)); + + // setting up list view for the color list + _list_view = _builder->get_widget("recolor-art-list"); + _color_model = Gio::ListStore::create(); + _selection_model = Gtk::SingleSelection::create(_color_model); + _color_factory = Gtk::SignalListItemFactory::create(); + + // setup how the list item should look + _color_factory->signal_setup().connect([](Glib::RefPtr const &list_item) { + auto box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL); + auto original = Gtk::make_managed(); + auto arrow = Gtk::make_managed(); + auto recolored = Gtk::make_managed(); + + auto original_preview = Gtk::make_managed(Color(0x00000000).toRGBA()); + auto recolored_preview = Gtk::make_managed(Color(0x00000000).toRGBA()); + + auto type_box = Gtk::make_managed(Gtk::Orientation::HORIZONTAL); + type_box->set_spacing(2); + type_box->set_margin_start(4); + type_box->set_hexpand(false); + type_box->set_vexpand(false); + type_box->set_halign(Gtk::Align::START); + type_box->set_valign(Gtk::Align::CENTER); + type_box->get_style_context()->add_class("type_box"); + + original_preview->set_hexpand(true); + recolored_preview->set_hexpand(true); + + original_preview->set_vexpand(true); + recolored_preview->set_vexpand(true); + + auto original_overlay = Gtk::make_managed(); + + original_overlay->set_child(*original_preview); + original_overlay->add_overlay(*type_box); + + original->append(*original_overlay); + recolored->append(*recolored_preview); + + original->set_hexpand(true); + + recolored->set_hexpand(true); + + arrow->set_from_icon_name(INKSCAPE_ICON("go-right")); + arrow->set_halign(Gtk::Align::CENTER); + arrow->set_valign(Gtk::Align::CENTER); + arrow->set_margin_top(3); + arrow->set_margin_start(6); + arrow->set_margin_end(6); + + box->set_name("original-recolor-box"); + box->append(*original); + box->append(*arrow); + box->append(*recolored); + list_item->set_data("typebox", type_box); + list_item->set_child(*box); + }); + + // setup signals for the list item children after they are created + _color_factory->signal_bind().connect([this](Glib::RefPtr const &list_item) { + auto item = std::dynamic_pointer_cast(list_item->get_item()); + if (!item) + return; + + auto box = dynamic_cast(list_item->get_child()); + if (!box || box->get_first_child() == nullptr || box->get_last_child() == nullptr) + return; + + // for hover effect + auto item_controller = Gtk::EventControllerMotion::create(); + item_controller->signal_enter().connect([box, item, this](double x, double y) { _manager->changeOpacity(true,item->key);}); + item_controller->signal_leave().connect([box, this]() { _manager->changeOpacity(); }); + + box->add_controller(item_controller); + + auto original = dynamic_cast(box->get_first_child()); + auto recolored = dynamic_cast(box->get_last_child()); + + if (original && recolored) { + colorButtons(original, item->old_color, true); + colorButtons(recolored, item->new_color); + setUpTypeBox(static_cast(list_item->get_data("typebox")), item->old_color); + + original->set_name("original"); + recolored->set_name("recolored"); + + auto original_click = Gtk::GestureClick::create(); + auto recolored_click = Gtk::GestureClick::create(); + + original_click->signal_pressed().connect( + [this, item, index = list_item->get_position()](gint n_press, gdouble x, gdouble y) { + _selection_model->set_selected(index); + onOriginalColorClicked(item->key); + }); + + recolored_click->signal_pressed().connect( + [this, item, index = list_item->get_position()](gint n_press, gdouble x, gdouble y) { + _selection_model->set_selected(index); + onOriginalColorClicked(item->key); + }); + + original->add_controller(original_click); + recolored->add_controller(recolored_click); + } + }); + + _list_view->set_model(_selection_model); + _list_view->set_factory(_color_factory); + + + auto lm = _list_view->get_layout_manager(); + if (auto grid_layout = std::dynamic_pointer_cast(lm)) { + grid_layout->set_row_spacing(0); + } + _list_view->set_hexpand(false); + _list_view->set_vexpand(false); + + _selection_model->signal_selection_changed().connect([this](guint pos, guint n_items) { + int index = _selection_model->get_selected(); + if (index < 0) + return; + + auto item = _color_model->get_item(index); + auto color_item = std::dynamic_pointer_cast(item); + if (!color_item) + return; + + onOriginalColorClicked(color_item->key); + }); + + _color_wheel_page.append(*_color_wheel); + // _apply = _builder->get_widget("apply"); + // _apply->signal_clicked().connect(sigc::mem_fun(*this, &RecolorArt::_onApplyButtonClicked)); +} +RecolorArt::~RecolorArt() {} + +void RecolorArt::setDesktop(SPDesktop *desktop) +{ + if (_desktop != desktop) { + _desktop = desktop; + } + // g_message("setDesktop\n"); +} + +/* +* prepare color model by creating color items and populate the color model +* then push the _list_view to color list page to show it in the ui +*/ +void RecolorArt::generateVisualList() +{ + _color_model->remove_all(); + for (auto &[key, value] : _manager->getSelectedColorsMap()) { + auto old_color = value.second.value().old_color; + auto new_color = value.second.value().new_color; + _color_model->append(ColorItem::create(key, old_color, new_color)); + } + _color_list.append(*_list_view); + if (_color_model->get_n_items() > 0) { + _selection_model->set_selected(0); + } +} + +/* +* setup the layout of the colornotebook ui in the colorlist page +* connect _solid_colors to color changed signal and call the signal handler +*/ +void RecolorArt::layoutColorPicker(std::shared_ptr updated_color) +{ + _color_picker_wdgt = Gtk::make_managed(_solid_colors); + _color_picker_wdgt->set_visible(true); + _color_picker_wdgt->set_label(_("Selected Color")); + + _solid_colors->signal_changed.connect([this]() { onColorPickerChanged(); }); + + auto container = _builder->get_widget("color-picker"); + if (container) { + for (auto child : container->get_children()) + container->remove(*child); + container->append(*_color_picker_wdgt); + } else { + g_warning("color picker not found"); + } +} + +/* +* set colorpreview colors used in color factory signal bind +* +*/ +void RecolorArt::colorButtons(Gtk::Box *button, Color color, bool is_original) +{ + if (button) { + auto child = + is_original + ? dynamic_cast(dynamic_cast(button->get_first_child())->get_child()) + : dynamic_cast(button->get_first_child()); + if (child) { + auto rgba = color.toRGBA(); + child->setRgba32(rgba); + } + } +} + +/* +* set up the type box which shows the color usage in what kind (fill,strok,pattern...etc) +*/ +void RecolorArt::setUpTypeBox(Gtk::Box *box, Color color) +{ + if (!box->get_children().empty()) + return; + + auto items = _manager->getSelectedItems(color.toRGBA()); + if (!items.empty()) { + std::string size = "" + std::to_string(items.size()) + ""; + std::map> kinds; + for (auto item : items) { + if (item.kind == "fill") { + kinds[INKSCAPE_ICON("object-fill")].first++; + kinds[INKSCAPE_ICON("object-fill")].second = "fill"; + } else if (item.kind == "stroke") { + kinds[INKSCAPE_ICON("object-stroke")].first++; + kinds[INKSCAPE_ICON("object-stroke")].second = "stroke"; + } else if (item.kind == "mesh") { + kinds[INKSCAPE_ICON("paint-gradient-mesh")].first++; + kinds[INKSCAPE_ICON("paint-gradient-mesh")].second = "mesh"; + } else if (item.kind == "linear") { + kinds[INKSCAPE_ICON("paint-gradient-linear")].first++; + kinds[INKSCAPE_ICON("paint-gradient-linear")].second = "linear gradient"; + } else if (item.kind == "ridal") { + kinds[INKSCAPE_ICON("paint-gradient-radial")].first++; + kinds[INKSCAPE_ICON("paint-gradient-radial")].second = "radial gradient"; + } else if (item.kind.find("pattern") != std::string::npos) { + kinds[INKSCAPE_ICON("paint-pattern")].first++; + kinds[INKSCAPE_ICON("paint-pattern")].second = "pattern"; + } else if (item.kind.find("marker") != std::string::npos) { + kinds[INKSCAPE_ICON("markers")].first++; + kinds[INKSCAPE_ICON("markers")].second = "marker"; + } else if (item.kind.find("mask") != std::string::npos) { + kinds[INKSCAPE_ICON("overlay-mask")].first++; + kinds[INKSCAPE_ICON("overlay-mask")].second = "mask"; + } else if (item.kind.find("swatch") != std::string::npos) { + size = "" + std::to_string(items.size() / 2) + ""; + kinds[INKSCAPE_ICON("paint-swatch")].first++; + kinds[INKSCAPE_ICON("paint-swatch")].second = "swatch"; + // std::cout<<"Swatch : "; + // std::cout<(); + label->set_use_markup(true); + label->set_markup(size); + box->append(*label); + std::string tooltip=""; + int sz = kinds.size() , s=0; + for (auto [icon,pair] : kinds) { + Gtk::Image *img = nullptr; + img = Gtk::make_managed(); + img->set_from_icon_name(icon); + + if (img) { + if (icon == INKSCAPE_ICON("overlay-mask")) { + img->set_pixel_size(16); + img->set_halign(Gtk::Align::CENTER); + img->set_valign(Gtk::Align::CENTER); + } else + img->set_pixel_size(8); + box->append(*img); + if(icon == INKSCAPE_ICON("paint-swatch")) pair.first/=2; + tooltip+=std::to_string(pair.first)+" x "+pair.second; + if(s!=sz-1)tooltip+="\n"; + + } + s++; + } + box->set_tooltip_text(tooltip); + } +} + + + +/* +* signal handler to set solid colors(color notebook at color list page),_color_picker_wdgt and the active index in +* colorwheel page to the color of the colorpreview clicked +*/ +void RecolorArt::onOriginalColorClicked(uint32_t color_id) +{ + if (_color_wheel && !_manager->isColorWheelColorsMapEmpty()) + { + int index = _manager->getColorIndex(color_id); + if (index > -1) + _color_wheel->setActiveIndex(_manager->getColorIndex(color_id)); + } + _current_color_id = color_id; + std::optional color = _manager->getSelectedNewColor(color_id); + if (color.has_value()) { + _solid_colors->set(color.value()); // update sliders under the colorwheel in the colorlist page + _color_picker_wdgt->setCurrentColor(_solid_colors); /* solves the issue of needing to create new + colornotebook every time the _solid_colors changes because it only changes the sliders not the + color wheel it self in colornotebook */ + } +} + +/* +* if LP is checked it searches for the items that has a key matching to the parameter color +* and loop on them to change their color lively +* put the recolor action into the undo stack as well +*/ +void RecolorArt::lpChecked(Color color, bool wheel) +{ + std::optional new_color = wheel ? color : _solid_colors->get(); + if (!new_color.has_value()) { + return; + } + if (!_manager->findSelectedColor(_current_color_id)) { + return; + } + _manager->setSelectedNewColor(_current_color_id,new_color.value()); + for (auto &item : _manager->getSelectedItems(_current_color_id)) { + _manager->changeObjectColor(item,new_color.value()); + } + _manager->recolorStops(_current_color_id, new_color.value()); + DocumentUndo::done(_desktop->getDocument(), _("Recolored items"), INKSCAPE_ICON("object-recolor-art")); +} +/* +* this is a signal handler for when solid color changes either in the sliders or the color wheels in +* both notebook pages +* searches for the selected color items then change them through lpchecked() function and update the pair +* in _selected_colors map +* sync the change through notebook pages what happens in one get updated in the other +* update color model item to refresh the listview ui +*/ +void RecolorArt::onColorPickerChanged(Color color, bool wheel) +{ + std::optional new_color = wheel ? color : _solid_colors->get(); + if (!new_color.has_value()) { + // g_message("there is no color"); + return; + } + std::string _color_string = new_color.value().toString(); + if (_manager->findSelectedColor(_current_color_id)) + _manager->setSelectedNewColor(_current_color_id,new_color.value()); + + // apply changes to selected items + if (_live_preview.property_active()) { + if (wheel) + lpChecked(color, wheel); + else + lpChecked(); + } + guint index = _selection_model->get_selected(); + Glib::RefPtr item = nullptr; + // if change is coming from colorlist page sync that to colorwheel page + if (!wheel){ + if (!_manager || index < 0) + return; + item = _color_model->get_item(index); + int i = _manager->getColorIndex(_current_color_id); + if (i > -1) { + _is_updating = true; + _color_wheel->changeColor(i, new_color.value()); + } + } + else { // if change is coming from colorwheel page sync that to colorlist page + auto item_index = findColorItemByKey(_current_color_id); + item = item_index.first; + index = item_index.second; + } + if (!item) { + g_message("ERROR: item is null"); + return; + } + // update colormodel item to refresh listview ui + auto color_item = std::dynamic_pointer_cast(item); + auto new_item = ColorItem::create(color_item->key, color_item->old_color, new_color.value()); + _color_model->splice(index, 1, {new_item}); + _is_updating = false; + // g_message("color picker changed"); +} + +/* +* update color model to refresh the listview ui with the new chossen colors +*/ +void RecolorArt::updateColorModel(std::vector new_colors) +{ + std::vector> new_colors_buttons; + for (auto i = 0; i < _color_model->get_n_items(); i++) { + auto item = _color_model->get_item(i); + auto color_item = std::dynamic_pointer_cast(item); + int index = _manager->getColorIndex(color_item->key); + auto new_item = ColorItem::create(color_item->key, color_item->old_color, + new_colors.empty() ? color_item->old_color : new_colors[index]); + new_colors_buttons.push_back(new_item); + } + _color_model->splice(0, _color_model->get_n_items(), new_colors_buttons); +} + +/* +* finding a color model item by key +*/ +std::pair, guint> RecolorArt::findColorItemByKey(uint32_t const &key) +{ + for (auto i = 0; i < _color_model->get_n_items(); i++) { + auto item = _color_model->get_item(i); + auto color_item = std::dynamic_pointer_cast(item); + if (key == color_item->key) { + return {item, i}; + } + } + return {nullptr, -1}; +} +/* +* signal function handler for reset button clicked +* that reset every thing to its original states +*/ +void RecolorArt::onResetClicked() +{ + _color_wheel->toggleHueLock(false); + _color_wheel->setLightness(100.0); + _color_wheel->setSaturation(100.0); + _color_wheel->setColor(_manager->getColors()); + updateColorModel(); + _manager->revertToOriginalColors(true); + guint index = _selection_model->get_selected(); + auto item = _color_model->get_item(index); + auto color_item = std::dynamic_pointer_cast(item); + + onOriginalColorClicked(color_item->key); +} + +/* +* apply recoloring when th LP check box is checked +* and get back to original colors when it is unchecked +*/ +void RecolorArt::onLivePreviewToggled() +{ + _is_preview = _live_preview.property_active(); + if (_is_preview) { + _manager->convertToRecoloredColors(); + } else { + _manager->revertToOriginalColors(); + } + // g_message(_is_preview ? "is true" : "is false"); + // g_message("LP toggled"); +} +/* +* set paint selector to avoid recuresive signals calling +*/ +void RecolorArt::setPaintSelector(PaintSelector *ps) +{ + _paint_selector = ps; +} + +/* +* main function that : +* 1- clears old data +* 2- get selection items from desktop +* 3- unlink selection items if there are clones +* 4- call collect colors func +* 5- put the generated list in the UI +*/ +void RecolorArt::performUpdate() +{ + if (!_desktop) return; + for (auto child : _color_list.get_children()) { + _color_list.remove(*child); + } + if(_manager) _manager->clearData(); + // _current_color_id = ""; + if (auto selection = _desktop->getSelection()) { + if (_paint_selector) + _paint_selector->set_change_selection(true); + selection->unlinkRecursive(false, true, false); + if (_paint_selector) + _paint_selector->set_change_selection(false); + std::vector vec(selection->items().begin(), selection->items().end()); + _extractor->collectColors(vec); + if (!_manager->isColorsEmpty()) + _color_wheel->setColor(_manager->getColors()); + if (!_manager->isSelectedColorsEmpty()) { + generateVisualList(); + auto first_button_id = _manager->getFirstKey(); + onOriginalColorClicked(first_button_id); + } + // g_message("Performing Update\n"); + } else + g_message("Desktop is NULL in Performupdate in recolor widegt\n"); + +} + +} // namespace Widget +} // namespace UI +} // namespace Inkscape \ No newline at end of file diff --git a/src/ui/widget/recolor-art.h b/src/ui/widget/recolor-art.h new file mode 100644 index 0000000000000000000000000000000000000000..5e38ef839247a465fe4575a88640ea551565e067 --- /dev/null +++ b/src/ui/widget/recolor-art.h @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef WIDGET_RECOLOR_ART_H +#define WIDGET_RECOLOR_ART_H +/* + * + * Authors: + * Fatma Omara + * + * Copyright (C) 2025 authors + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ui/builder-utils.h" + +#include "canvas.h" +#include "style-internal.h" +#include "style.h" +#include "ui/widget/paint-selector.h" +#include "ui/widget/ink-color-wheel.h" +#include "src/colors/color-set.h" + +using namespace Inkscape::Colors; + +namespace Inkscape::Colors { +class Color; +class ColorSet; +namespace Space { +class AnySpace; +} +} // namespace Inkscape::Colors + +namespace Gtk { +class Widget; +class Builder; +class ListStore; +class Notebook; +} // namespace Gtk + +class SPDesktop; + +namespace Inkscape { +namespace UI { +namespace Tools { +class ToolBase; +} + +namespace Widget { +class ColorNotebook; +class MultiMarkerColorPlate; + +struct ColorRef +{ + SPObject *item; + std::string kind; +}; + +struct ColorPair +{ + Color old_color; + Color new_color; +}; + +struct ColorItem : public Glib::Object +{ + uint32_t key; + Color old_color = Color(0x00000000); + Color new_color = Color(0x00000000); + + static Glib::RefPtr create(uint32_t const& k, Color const& old_c, Color const& new_c) + { + auto item = Glib::make_refptr_for_instance(new ColorItem()); + item->key = k; + item->old_color = old_c; + item->new_color = new_c; + return item; + } +}; + +class DataManager +{ + public : + using SelectedColorsMap = + std::unordered_map, std::optional>>; + DataManager(){} + ~DataManager(){} + void populateMap(Color color, SPObject *style, std::string kind); + void revertToOriginalColors(bool is_reset_clicked = false); + void convertToRecoloredColors(); + void populateStopsMap(SPStop *stop); + void recolorStops(uint32_t old_color, Color new_color); + void changeObjectColor(const ColorRef& item , Color color); + void changeOpacity(bool change_opacity = false , uint32_t color= 0); + void setSelectedNewColor(uint32_t key_color,Color new_color); + std::optional getSelectedNewColor(uint32_t key_color); + bool findSelectedColor(uint32_t key_color); + bool isSelectedColorsEmpty(){return _selected_colors.empty();} + bool isGradientStopsEmpty(){return _gradient_stops.empty();} + bool isColorWheelColorsMapEmpty(){return color_wheel_colors_map.empty();} + bool isColorsEmpty(){return colors.empty();} + void clearData(); + void setSelectedNewColor(std::vector &new_colors); + uint32_t getFirstKey(){return _selected_colors.begin()->first;} + const std::vector& getSelectedItems(uint32_t key_color); + int getColorIndex(uint32_t key_color); + std::vector& getColors(){return colors;} + std::optionalgetColor(int index); + SelectedColorsMap& getSelectedColorsMap(){return _selected_colors;} + private : + SelectedColorsMap _selected_colors; + std::unordered_map> _gradient_stops; + std::vector colors; + std::unordered_map color_wheel_colors_map; + +}; + +class ColorsExtractor +{ +public: + ColorsExtractor(std::shared_ptr m){ + manager = m; + }; + ~ColorsExtractor(){} + void collectColors(std::vector objects, std::string kind = ""); + +private: + std::shared_ptr manager; + void extractGradientStops(SPObject *object, bool isFill); + void extractMeshStops(std::vector> &mesh_nodes, SPObject *object, std::string kind); + void extractObjectColors(SPObject *object, std::string kind = ""); + void extractObjectStyle(SPObject *object, std::string kind = ""); + void extractPatternColors(SPPattern *pattern); + void extractMarkerColors(Glib::ustring marker, SPObject *object); +}; + + + +class RecolorArt : public Gtk::Box +{ +private: + SPDesktop *_desktop = nullptr; + Glib::RefPtr _builder = create_builder("widget-recolor.ui"); + Gtk::Notebook &_notebook; + Gtk::Box &_color_wheel_page; + PaintSelector *_paint_selector; + std::shared_ptr _solid_colors = std::make_shared(); + sigc::connection _solid_color_changed ; + Gtk::Box &_color_list ; + Gtk::Button &_reset; + Gtk::CheckButton &_live_preview; + Inkscape::UI::Widget::ColorNotebook *_color_picker_wdgt = nullptr ; + Gtk::ListView *_list_view = nullptr; + uint32_t _current_color_id; + bool _is_preview = false; + + Glib::RefPtr> _color_model; + Glib::RefPtr _color_factory; + Glib::RefPtr _selection_model; + + std::shared_ptr _manager = std::make_shared();; + std::unique_ptr _extractor = std::make_unique(_manager);; + + MultiMarkerColorPlate *_color_wheel = nullptr; + + int colors_index=0; + bool _is_updating = false; + bool _is_wheel_page = false; + + void generateVisualList(); + void layoutColorPicker(std::shared_ptr updated_color = nullptr); + void colorButtons(Gtk::Box *button, Color color, bool is_original = false); + + // signals handelrs + void onOriginalColorClicked(uint32_t color_id); + void onColorPickerChanged(Color color=Color(0x00000000) , bool wheel = false); + void onLivePreviewToggled(); + void lpChecked(Color color=Color(0x00000000) , bool wheel = false); + void setUpTypeBox(Gtk::Box *box, Color color); + void updateColorModel(std::vector new_colors = std::vector{}); + std::pair,guint> findColorItemByKey(const uint32_t& key); + +public: + RecolorArt(); + ~RecolorArt(); + void performUpdate(); + bool isInPreviewMode() { return _is_preview; } + void setDesktop(SPDesktop *desktop); + void setPaintSelector(PaintSelector *ps); + void onResetClicked(); +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // WIDGET_RECOLOR_ART_H