From dc6511de2db6820ca617c2435bcc2165df333393 Mon Sep 17 00:00:00 2001 From: ftomara Date: Sat, 24 May 2025 00:05:53 +0300 Subject: [PATCH 1/8] Recolor Artwork project Implements https://gitlab.com/inkscape/ux/-/issues/318 Fixes https://gitlab.com/inkscape/inbox/-/issues/6095 --- po/POTFILES.src.in | 2 + po/POTFILES.ui.in | 1 + .../symbolic/actions/go-right-symbolic.svg | 1 + .../symbolic/actions/lightness-symbolic.svg | 115 +++ .../actions/object-recolor-art-symbolic.svg | 5 + .../actions/reset-colors-symbolic.svg | 4 + .../symbolic/actions/saturation-symbolic.svg | 1 + .../symbolic/actions/go-right-symbolic.svg | 1 + .../symbolic/actions/lightness-symbolic.svg | 115 +++ .../actions/object-recolor-art-symbolic.svg | 5 + .../actions/reset-colors-symbolic.svg | 4 + .../symbolic/actions/reset-symbolic.svg | 61 +- .../symbolic/actions/saturation-symbolic.svg | 1 + .../symbolic/actions/go-right-symbolic.svg | 1 + .../symbolic/actions/lightness-symbolic.svg | 115 +++ .../actions/object-recolor-art-symbolic.svg | 5 + .../actions/reset-colors-symbolic.svg | 4 + .../symbolic/actions/reset-symbolic.svg | 58 -- .../symbolic/actions/saturation-symbolic.svg | 1 + share/ui/pattern-edit.glade | 2 +- share/ui/style.css | 46 ++ share/ui/widget-recolor.ui | 176 +++++ src/CMakeLists.txt | 2 + src/desktop.cpp | 9 +- src/desktop.h | 11 + src/object-colors.cpp | 416 +++++++++++ src/object-colors.h | 117 +++ src/selcue.cpp | 10 +- src/selcue.h | 3 + src/seltrans.h | 1 + src/ui/CMakeLists.txt | 8 +- src/ui/tools/select-tool.cpp | 5 + src/ui/tools/select-tool.h | 1 + src/ui/tools/tool-base.cpp | 2 + src/ui/tools/tool-base.h | 3 + src/ui/widget/color-notebook.cpp | 12 +- src/ui/widget/color-notebook.h | 1 + src/ui/widget/color-page.h | 9 + src/ui/widget/color-preview.cpp | 4 +- src/ui/widget/fill-style.cpp | 3 + src/ui/widget/gradient-editor.cpp | 9 +- src/ui/widget/gradient-editor.h | 2 + src/ui/widget/ink-color-wheel.cpp | 694 +++++++++++++++++- src/ui/widget/ink-color-wheel.h | 103 +++ src/ui/widget/marker-combo-box.cpp | 33 +- src/ui/widget/marker-combo-box.h | 27 +- src/ui/widget/multi-marker-color-plate.cpp | 187 +++++ src/ui/widget/multi-marker-color-plate.h | 119 +++ src/ui/widget/paint-selector.cpp | 220 +++++- src/ui/widget/paint-selector.h | 19 +- src/ui/widget/recolor-art.cpp | 603 +++++++++++++++ src/ui/widget/recolor-art.h | 129 ++++ testfiles/CMakeLists.txt | 2 + .../src/multi-marker-color-wheel-test.cpp | 100 +++ testfiles/src/object-colors-test.cpp | 249 +++++++ 55 files changed, 3687 insertions(+), 150 deletions(-) create mode 100644 share/icons/Dash/symbolic/actions/go-right-symbolic.svg create mode 100644 share/icons/Dash/symbolic/actions/lightness-symbolic.svg create mode 100644 share/icons/Dash/symbolic/actions/object-recolor-art-symbolic.svg create mode 100644 share/icons/Dash/symbolic/actions/reset-colors-symbolic.svg create mode 100644 share/icons/Dash/symbolic/actions/saturation-symbolic.svg create mode 100644 share/icons/hicolor/symbolic/actions/go-right-symbolic.svg create mode 100644 share/icons/hicolor/symbolic/actions/lightness-symbolic.svg create mode 100644 share/icons/hicolor/symbolic/actions/object-recolor-art-symbolic.svg create mode 100644 share/icons/hicolor/symbolic/actions/reset-colors-symbolic.svg create mode 100644 share/icons/hicolor/symbolic/actions/saturation-symbolic.svg create mode 100644 share/icons/multicolor/symbolic/actions/go-right-symbolic.svg create mode 100644 share/icons/multicolor/symbolic/actions/lightness-symbolic.svg create mode 100644 share/icons/multicolor/symbolic/actions/object-recolor-art-symbolic.svg create mode 100644 share/icons/multicolor/symbolic/actions/reset-colors-symbolic.svg delete mode 100644 share/icons/multicolor/symbolic/actions/reset-symbolic.svg create mode 100644 share/icons/multicolor/symbolic/actions/saturation-symbolic.svg create mode 100644 share/ui/widget-recolor.ui create mode 100644 src/object-colors.cpp create mode 100644 src/object-colors.h create mode 100644 src/ui/widget/multi-marker-color-plate.cpp create mode 100644 src/ui/widget/multi-marker-color-plate.h create mode 100644 src/ui/widget/recolor-art.cpp create mode 100644 src/ui/widget/recolor-art.h create mode 100644 testfiles/src/multi-marker-color-wheel-test.cpp create mode 100644 testfiles/src/object-colors-test.cpp diff --git a/po/POTFILES.src.in b/po/POTFILES.src.in index b227ec488b..d9c3bdfbb1 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 64483fff91..26dd7fecf7 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 0000000000..5d08323d90 --- /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 0000000000..1e8d4bc38e --- /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 0000000000..b078cf421c --- /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 0000000000..7d47572893 --- /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 0000000000..7f0b0fda5a --- /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 0000000000..5d08323d90 --- /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 0000000000..1e8d4bc38e --- /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 0000000000..b078cf421c --- /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 0000000000..7d47572893 --- /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 7dcef2b3f7..09c40a1e30 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 0000000000..7f0b0fda5a --- /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 0000000000..5d08323d90 --- /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 0000000000..1e8d4bc38e --- /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 0000000000..b078cf421c --- /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 0000000000..7d47572893 --- /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 7dcef2b3f7..0000000000 --- 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 0000000000..7f0b0fda5a --- /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 7993af04ab..59e90b9c2b 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 597ca11e19..b17f235f67 100644 --- a/share/ui/style.css +++ b/share/ui/style.css @@ -1721,3 +1721,49 @@ 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; +} + +#recolor-art scrollbar slider { + min-width: 0px; + min-height: 0px; + margin: 0px; + padding: 0px; + background: transparent; + border: none; +} \ No newline at end of file diff --git a/share/ui/widget-recolor.ui b/share/ui/widget-recolor.ui new file mode 100644 index 0000000000..e1ea7ca509 --- /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/CMakeLists.txt b/src/CMakeLists.txt index a7ca11c1a4..daa9b86527 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,6 +44,7 @@ set(inkscape_SRC message-context.cpp message-stack.cpp mod360.cpp + object-colors.cpp object-hierarchy.cpp object-snapper.cpp page-manager.cpp @@ -126,6 +127,7 @@ set(inkscape_SRC message.h mod360.h number-opt-number.h + object-colors.h object-hierarchy.h object-snapper.h page-manager.h diff --git a/src/desktop.cpp b/src/desktop.cpp index f499f3c6ff..9b1dce62d5 100644 --- a/src/desktop.cpp +++ b/src/desktop.cpp @@ -212,6 +212,14 @@ void SPDesktop::setDesktopWidget(SPDesktopWidget *dtw) _widget = dtw; } +void SPDesktop::setHideSelectionBoxes(bool hide) +{ + if (_hide_selection_boxes != hide) { + _hide_selection_boxes = hide; + _signal_hide_selection_boxes_changed.emit(hide); + } +} + //-------------------------------------------------------------------- /* Public methods */ @@ -1184,7 +1192,6 @@ SPDesktop::getCurrentOrToolStylePath(Glib::ustring const &tool_path) } } - void SPDesktop::setToolboxFocusTo(char const * const label) { diff --git a/src/desktop.h b/src/desktop.h index 8f9d1e8a47..57be44d08d 100644 --- a/src/desktop.h +++ b/src/desktop.h @@ -162,6 +162,9 @@ public: void setDesktopWidget(SPDesktopWidget *dtw); + void setHideSelectionBoxes(bool hide); + bool getHideSelectionBoxes() const { return _hide_selection_boxes; } + private: SPDocument *document = nullptr; std::unique_ptr _message_stack; @@ -183,6 +186,8 @@ private: std::unique_ptr canvas; + bool _hide_selection_boxes = false; + public: Inkscape::UI::Tools::ToolBase *getTool () const { return _tool.get(); } Inkscape::Selection *getSelection () const { return _selection.get(); } @@ -270,6 +275,10 @@ public: return _query_style_signal.connect(std::forward(slot)); } + template sigc::connection connectHideSelectionBoxes(F &&slot) { + return _signal_hide_selection_boxes_changed.connect(std::forward(slot)); + } + // there's an object selected and it has a gradient fill and/or stroke; one of the gradient stops has been selected // callback receives sender pointer and selected stop pointer sigc::connection connect_gradient_stop_selected(sigc::slot const &slot); @@ -549,6 +558,8 @@ private: sigc::signal _control_point_selected; sigc::signal _text_cursor_moved; + sigc::signal _signal_hide_selection_boxes_changed; + sigc::scoped_connection _reconstruction_start_connection; sigc::scoped_connection _reconstruction_finish_connection; sigc::scoped_connection _schedule_zoom_from_document_connection; diff --git a/src/object-colors.cpp b/src/object-colors.cpp new file mode 100644 index 0000000000..58a88f12d1 --- /dev/null +++ b/src/object-colors.cpp @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "object-colors.h" + +#include "desktop-style.h" +#include "document.h" +#include "gradient-chemistry.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-pattern.h" +#include "object/sp-radial-gradient.h" +#include "object/sp-stop.h" +#include "object/sp-tspan.h" +#include "object/sp-text.h" +#include "style.h" + +namespace Inkscape { + +/* + * reset selected object colors to their original colors all at once + * used when LP checkbox is unchecked + */ +void ObjectColorSet::revertToOriginalColors(bool is_reset_clicked) +{ + for (auto &[key, items] : _selected_colors) { + if (is_reset_clicked) { + items.second->new_color = items.second->old_color; + } + applyNewColorToSelection(key, items.second->old_color); + } +} + +/* + * convert selected object colors to the new choosen colors all at once + * used when LP checkbox is unchecked then checked again + */ +void ObjectColorSet::convertToRecoloredColors() +{ + for (auto const &[key, items] : _selected_colors) { + if (items.second) { + Color new_color = items.second->new_color; + applyNewColorToSelection(key, 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 ObjectColorSet::changeOpacity(bool change_opacity,uint32_t color ,bool is_preview) +{ + for (auto const &[key, value] : _selected_colors) { + Color new_color = is_preview ? value.second->new_color : value.second->old_color; + if (change_opacity && key != color) { + new_color.setOpacity(0.05); + } + applyNewColorToSelection(key, new_color); + } +} + +/* + * get stops vector from the _gradient_stops map and loop over it to + * set them to the new color + */ +void ObjectColorSet::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 ObjectColorSet::populateStopsMap(SPStop *stop) +{ + 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 ObjectColorSet::populateMap(Color color, SPObject *item, ObjectStyleType type, std::string kind) +{ + color.enableOpacity(true); + ColorRef ref {item, kind , type}; + 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 ObjectColorSet::changeObjectColor(ColorRef const &item, Color color) +{ + std::string c = color.toString(true); + if (item.kind == "stop") { + return; + } + + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property_string(css, item.kind.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 ObjectColorSet::findSelectedColor(uint32_t key_color) const +{ + return _selected_colors.contains(key_color); +} + +void ObjectColorSet::clearData() +{ + colors.clear(); + _gradient_stops.clear(); + _selected_colors.clear(); + color_wheel_colors_map.clear(); +} + +bool ObjectColorSet::setSelectedNewColor(std::vector &new_colors) +{ + if (new_colors.empty() || new_colors.size() != colors.size()) { + return false; + } + for (auto &[key, value] : _selected_colors) { + int index = color_wheel_colors_map[key]; + value.second->new_color = new_colors[index]; + } + return true; +} + +std::vector &ObjectColorSet::getSelectedItems(uint32_t key_color) +{ + static std::vector empty; + if (findSelectedColor(key_color)) { + return _selected_colors[key_color].first; + } + return empty; +} + +int ObjectColorSet::getColorIndex(uint32_t key_color) +{ + if (color_wheel_colors_map.contains(key_color)) { + return color_wheel_colors_map[key_color]; + } + return -1; +} + +std::optional ObjectColorSet::getColor(int index) const +{ + if (index < 0 || index >= colors.size()) { + return {}; + } + + return colors[index]; +} + +bool ObjectColorSet::applyNewColorToSelection(uint32_t key_color, Color new_color) +{ + std::vector &items = getSelectedItems(key_color); + if (items.empty()) { + return false; + } + for (auto const &item : items) { + changeObjectColor(item, new_color); + } + recolorStops(key_color, new_color); + return true; +} + +void ObjectColorSet::setSelectedNewColor(uint32_t key_color , Color new_color) +{ + _selected_colors[key_color].second->new_color = new_color; +} + +std::optional ObjectColorSet::getSelectedNewColor(uint32_t key_color) const +{ + auto _selected = _selected_colors.find(key_color); + if (_selected != _selected_colors.end()) { + return _selected->second.second->new_color; + } + return {}; +} + +/* + * 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::vector objects, ObjectStyleType type) +{ + for (auto object : objects) { + if (auto item = cast(object)) { + if (auto mask = cast(item->getMaskObject())) { + std::vector children_vec; + for (auto &child : mask->children) { + children_vec.push_back(&child); + } + collectColors(children_vec, ObjectStyleType::Mask); + } + if (auto text = cast(item)) { // handle text objects color collection by collecting the colors of its tspans children + if (auto tspan = 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, type); + continue; + } + } + } + extractObjectColors(object, type); + } +} + +/* + * 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, ObjectStyleType type) +{ + if (auto group = cast(object)) { + for (auto &child : group->children) { + extractObjectColors(&child, type); + } + } else if (object) { + extractObjectStyle(object, type); + } +} + +/* + * 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, ObjectStyleType type) +{ + // 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(); + ObjectStyleType fill_type = type == ObjectStyleType::None ? ObjectStyleType::Fill : type; + manager->populateMap(color, object, fill_type, "fill"); + + } else if (style->fill.isPaintserver()) { + // paint server can be pattern or gradient + // get gradient stops strokes + auto ps = style->getFillPaintServer(); + if (auto pattern = cast(ps)) { + extractPatternColors(pattern); + } + extractGradientStops(object, true); + } + + if (style->stroke.isColor()) { + auto color = style->stroke.getColor(); + ObjectStyleType stroke_type = type == ObjectStyleType::None ? ObjectStyleType::Stroke : type; + manager->populateMap(color, object,stroke_type, "stroke");//ObjectColorSet + } else if (style->stroke.isPaintserver()) { + // get gradient stops strokes + auto ps = style->getStrokePaintServer(); + if (auto pattern = 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) +{ + auto paint_server = isFill ? object->style->getFillPaintServer() : object->style->getStrokePaintServer(); + if (paint_server && cast(paint_server)) { + auto gradient = cast(paint_server); + if (!gradient) { + return; + } + if (auto vectorGradient = gradient->getVector()) { + if (vectorGradient->hasPatches()) { + vectorGradient->ensureArray(); + std::unique_ptr nodeArray; + if (auto mesh = cast(gradient)) { + nodeArray = std::make_unique(mesh); + extractMeshStops(nodeArray->nodes, object,ObjectStyleType::Mesh); + } + + } else { + gradient = sp_gradient_get_forked_vector_if_necessary(gradient, true); + if (!gradient) { + return; + } + gradient->ensureVector(); + manager->populateStopsMap(gradient->getFirstStop()); + } + } + bool is_swatch = gradient->getVector()->isSwatch(); + ObjectStyleType type; + if (is_swatch) { + type = ObjectStyleType::Swatch; + } else if (is(gradient)) { + type = ObjectStyleType::Linear; + } else if (is(gradient)) { + type = ObjectStyleType::Radial; + } + for (auto stop : gradient->getGradientVector().stops) { + if (stop.color.has_value()) { + manager->populateMap(stop.color.value(), object, type, "stop"); + } + } + } +} + +/* + * 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, ObjectStyleType type) +{ + for (auto const &nodes : mesh_nodes) { + for (auto const &node : nodes) { + manager->populateStopsMap(node->stop); + if (node->color.has_value()) { + manager->populateMap(node->color.value(), item, type , "stop"); + } + } + } +} + +/* + * 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) +{ + auto root = pattern->rootPattern(); + for (auto &child : root->children) { + extractObjectColors(&child, ObjectStyleType::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 const &marker, SPObject *object) +{ + if (marker.size() >= 5 && object->document) { + std::string marker_id = marker.substr(4, marker.size() - 5); + auto m = object->document->getObjectByHref(marker_id); + if (!m) { + return; + } + if (auto marker_obj = cast(m)) { + for (auto child : marker_obj->item_list()) { + extractObjectColors(child, ObjectStyleType::Marker); + } + } + } +} + +} // namespace Inkscape diff --git a/src/object-colors.h b/src/object-colors.h new file mode 100644 index 0000000000..4002a67bf8 --- /dev/null +++ b/src/object-colors.h @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * this file is for the logic behind recolor-art.cpp/h widget + * extracting colors by the color extractor function populating maps in the object color set + */ +/* + * Authors: + * Fatma Omara + * + * Copyright (C) 2025 authors + */ + +#ifndef WIDGET_OBJECT_COLORS_SET_H +#define WIDGET_OBJECT_COLORS_SET_H + +#include "style-internal.h" +#include "object/sp-mesh-array.h" + +namespace Inkscape::Colors { +class Color; +class ColorSet; +namespace Space { +class AnySpace; +} +} // namespace Inkscape::Colors + + +namespace Inkscape { +namespace Tools { +class ToolBase; +} + +enum class ObjectStyleType +{ + None, + Fill, + Stroke, + Pattern, + Swatch, + Linear, + Radial, + Mesh, + Mask, + Marker, +}; + +struct ColorRef +{ + SPObject *item; + std::string kind; + ObjectStyleType type; +}; + +struct ColorPair +{ + Colors::Color old_color; + Colors::Color new_color; +}; + +class ObjectColorSet +{ +public: + using SelectedColorsMap = std::unordered_map, std::optional>>; + + void populateMap(Colors::Color color, SPObject *style, ObjectStyleType type, std::string kind); + void revertToOriginalColors(bool is_reset_clicked = false); + void convertToRecoloredColors(); + void populateStopsMap(SPStop *stop); + void recolorStops(uint32_t old_color, Colors::Color new_color); + void changeObjectColor(const ColorRef& item, Colors::Color color); + void changeOpacity(bool change_opacity = false , uint32_t color= 0 ,bool is_preview = true); + void setSelectedNewColor(uint32_t key_color, Colors::Color new_color); + std::optional getSelectedNewColor(uint32_t key_color) const; + bool findSelectedColor(uint32_t key_color) const; + bool isSelectedColorsEmpty()const {return _selected_colors.empty();} + bool isGradientStopsEmpty() const {return _gradient_stops.empty();} + bool isColorWheelColorsMapEmpty() const {return color_wheel_colors_map.empty();} + bool isColorsEmpty() const { return colors.empty(); } + void clearData(); + bool setSelectedNewColor(std::vector &new_colors); + uint32_t getFirstKey() const { return _selected_colors.begin()->first; } + std::vector &getSelectedItems(uint32_t key_color); + int getColorIndex(uint32_t key_color); + std::vector &getColors() { return colors; } + std::optionalgetColor(int index) const; + SelectedColorsMap &getSelectedColorsMap() { return _selected_colors; } + bool applyNewColorToSelection(uint32_t key_color, Colors::Color new_color); + +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{std::move(m)} + {} + + void collectColors(std::vector objects, ObjectStyleType type = ObjectStyleType::None); + +private: + std::shared_ptr manager; + void extractGradientStops(SPObject *object, bool isFill); + void extractMeshStops(std::vector> &mesh_nodes, SPObject *object, ObjectStyleType type); + void extractObjectColors(SPObject *object, ObjectStyleType type = ObjectStyleType::None); + void extractObjectStyle(SPObject *object, ObjectStyleType type = ObjectStyleType::None); + void extractPatternColors(SPPattern *pattern); + void extractMarkerColors(Glib::ustring const &marker, SPObject *object); +}; + +} // namespace Inkscape + +#endif // WIDGET_OBJECT_COLORS_SET_H diff --git a/src/selcue.cpp b/src/selcue.cpp index d52bbb8f69..b3ee8bcc3a 100644 --- a/src/selcue.cpp +++ b/src/selcue.cpp @@ -102,7 +102,7 @@ void SelCue::_updateItemBboxes(gint mode, int prefs_bbox) } else if (auto rect = dynamic_cast(canvas_item)) { rect->set_rect(*b); } - canvas_item->set_visible(true); + canvas_item->set_visible(_bboxes_visible); } else { // no bbox canvas_item->set_visible(false); } @@ -150,7 +150,7 @@ void SelCue::_newItemBboxes() if (canvas_item) { canvas_item->set_pickable(false); canvas_item->lower_to_bottom(); // Just low enough to not get in the way of other draggable knots. - canvas_item->set_visible(true); + canvas_item->set_visible(_bboxes_visible); _item_bboxes.emplace_back(std::move(canvas_item)); } } @@ -219,6 +219,12 @@ void SelCue::_boundingBoxPrefsChanged(int prefs_bbox) _updateItemBboxes(mode, prefs_bbox); } +void SelCue::setBboxesVisible(bool visible) +{ + _bboxes_visible = visible; + _updateItemBboxes(); +} + } // namespace Inkscape /* diff --git a/src/selcue.h b/src/selcue.h index 0cb55b486b..246b8c1aa7 100644 --- a/src/selcue.h +++ b/src/selcue.h @@ -43,6 +43,8 @@ public: BBOX }; + void setBboxesVisible(bool visible); + private: class BoundingBoxPrefsObserver: public Preferences::Observer { @@ -65,6 +67,7 @@ private: void _newTextBaselines(); void _boundingBoxPrefsChanged(int prefs_bbox); + bool _bboxes_visible = true; SPDesktop *_desktop; Selection *_selection; sigc::connection _sel_changed_connection; diff --git a/src/seltrans.h b/src/seltrans.h index 64695430fe..2b85a2f621 100644 --- a/src/seltrans.h +++ b/src/seltrans.h @@ -97,6 +97,7 @@ public: } void getNextClosestPoint(bool reverse); + SelCue &getSelCue() { return _selcue; } private: class BoundingBoxPrefsObserver: public Preferences::Observer diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 5c9d9c3544..f833e4356c 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -299,10 +299,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 @@ -627,7 +627,9 @@ set(ui_SRC widget/widget-vfuncs-class-init.h 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/tools/select-tool.cpp b/src/ui/tools/select-tool.cpp index f351015c51..9cb7c2dd4b 100644 --- a/src/ui/tools/select-tool.cpp +++ b/src/ui/tools/select-tool.cpp @@ -1047,6 +1047,11 @@ std::pair SelectTool::get_default_rubberba return {mode, handle}; } +void SelectTool::onHideSelectionChanged(bool hide) +{ + _seltrans->getSelCue().setBboxesVisible(!hide); +} + } // namespace Inkscape::UI::Tools /* diff --git a/src/ui/tools/select-tool.h b/src/ui/tools/select-tool.h index 7880e351e1..1da9ad25f7 100644 --- a/src/ui/tools/select-tool.h +++ b/src/ui/tools/select-tool.h @@ -61,6 +61,7 @@ private: bool _force_dragging = false; std::string _default_cursor; + void onHideSelectionChanged(bool hide) override; }; } // namespace Inkscape::UI::Tools diff --git a/src/ui/tools/tool-base.cpp b/src/ui/tools/tool-base.cpp index 93badabfe7..677962bcd5 100644 --- a/src/ui/tools/tool-base.cpp +++ b/src/ui/tools/tool-base.cpp @@ -113,6 +113,8 @@ ToolBase::ToolBase(SPDesktop *desktop, std::string &&prefs_path, std::string &&c sp_event_context_read(this, "changelayer"); sp_event_context_read(this, "changepage"); + + _hide_selection_connection = desktop->connectHideSelectionBoxes(sigc::mem_fun(*this, &ToolBase::onHideSelectionChanged)); } ToolBase::~ToolBase() diff --git a/src/ui/tools/tool-base.h b/src/ui/tools/tool-base.h index e66df77882..207c39abf7 100644 --- a/src/ui/tools/tool-base.h +++ b/src/ui/tools/tool-base.h @@ -152,6 +152,9 @@ protected: EventType::BUTTON_PRESS); void ungrabCanvasEvents(); + sigc::scoped_connection _hide_selection_connection; + virtual void onHideSelectionChanged(bool hide) {}; + private: enum Panning { diff --git a/src/ui/widget/color-notebook.cpp b/src/ui/widget/color-notebook.cpp index 39117b6506..b2ee1cbb6c 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 637379c716..d0d9e9cc97 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 eb7efc7ffc..3b5ed08c98 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; @@ -55,6 +57,13 @@ 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 4854cbbad6..ecf80c0255 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/fill-style.cpp b/src/ui/widget/fill-style.cpp index a364570aeb..b46882da9f 100644 --- a/src/ui/widget/fill-style.cpp +++ b/src/ui/widget/fill-style.cpp @@ -128,6 +128,9 @@ void FillNStroke::setDesktop(SPDesktop *desktop) stop_selected_connection.disconnect(); } _desktop = desktop; + if (_psel) { + _psel->setDesktop(desktop); + } if (desktop && desktop->getSelection()) { subselChangedConn = desktop->connect_text_cursor_moved([this] (Inkscape::UI::Tools::TextTool *) { performUpdate(); diff --git a/src/ui/widget/gradient-editor.cpp b/src/ui/widget/gradient-editor.cpp index 3aa4c6d1c1..b7b2c67ff4 100644 --- a/src/ui/widget/gradient-editor.cpp +++ b/src/ui/widget/gradient-editor.cpp @@ -66,13 +66,12 @@ GradientEditor::GradientEditor(const char* prefs, Space::Type space, bool show_t _turn_gradient(get_widget(_builder, "turnBtn")), _angle_adj(get_object(_builder, "adjustmentAngle")), _angle_btn(get_widget(_builder, "angle")), + _main_box(get_widget(_builder, "main-box")), _color_picker(ColorPickerPanel::create(space, get_plate_type_preference(prefs, ColorPickerPanel::None), _colors)), _linear_btn(get_widget(_builder, "type-linear")), _radial_btn(get_widget(_builder, "type-radial")), _repeat_mode_btn(get_widget(_builder, "repeat-mode")) { - auto& main_box(get_widget(_builder, "main-box")); - // gradient type buttons _linear_btn.set_active(); _linear_btn.signal_clicked().connect([this]{ fire_change_type(true); }); @@ -114,11 +113,11 @@ GradientEditor::GradientEditor(const char* prefs, Space::Type space, bool show_t expander->property_expanded().signal_changed().connect([this, expander]{ _color_picker->set_plate_type(expander->get_expanded() ? ColorPickerPanel::Circle : ColorPickerPanel::None); }); - main_box.append(*expander); + _main_box.append(*expander); } // add color selector - main_box.append(*_color_picker); + _main_box.append(*_color_picker); // gradient library in a popup get_widget(_builder, "libraryPopover").set_child(*_selector); @@ -169,7 +168,7 @@ GradientEditor::GradientEditor(const char* prefs, Space::Type space, bool show_t _color_picker->get_last_column_size()->add_widget(get_widget(_builder, "offset-box")); _color_picker->get_last_column_size()->add_widget(get_widget(_builder, "angle-box")); - append(main_box); + append(_main_box); } void GradientEditor::set_stop_color(Inkscape::Colors::Color const &color) diff --git a/src/ui/widget/gradient-editor.h b/src/ui/widget/gradient-editor.h index 7123f91a53..f457e41a44 100644 --- a/src/ui/widget/gradient-editor.h +++ b/src/ui/widget/gradient-editor.h @@ -70,6 +70,7 @@ public: ColorPickerPanel::PlateType get_color_picker_plate() const; SPGradientType get_type() const; ColorPickerPanel& get_picker() { return *_color_picker; } + Gtk::Box &getColorBox() { return _main_box; } private: void set_gradient(SPGradient* gradient); @@ -102,6 +103,7 @@ private: InkSpinButton& _offset_btn; InkSpinButton& _angle_btn; int _current_stop_index = 0; + Gtk::Box &_main_box; SPGradient* _gradient = nullptr; SPDocument* _document = nullptr; OperationBlocker _update; diff --git a/src/ui/widget/ink-color-wheel.cpp b/src/ui/widget/ink-color-wheel.cpp index 8561f92cab..5d19f2304d 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; @@ -133,8 +134,9 @@ ColorWheelBase::ColorWheelBase(BaseObjectType* cobject, const Glib::RefPtr slot) @@ -598,13 +598,6 @@ Gtk::EventSequenceState ColorWheelHSL::on_click_released(int /*n_press*/, double void ColorWheelHSL::on_motion(Gtk::EventControllerMotion const &motion, double x, double y) { 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) { _update_ring_color(x, y); @@ -637,6 +630,7 @@ bool ColorWheelHSL::on_key_pressed(unsigned keyval, unsigned /*keycode*/, Gdk::M case GDK_KEY_Right: case GDK_KEY_KP_Right: dx = +1.0; + break; } if (dx == 0.0 && dy == 0.0) return false; @@ -762,6 +756,684 @@ 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 /*overrideHue*/, bool 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; + if (index == _active_index) { + cr->set_source_rgb(0.2588, 0.5216, 0.9255); + } else { + cr->set_source_rgb(a, a, a); + } + cr->set_dash(std::valarray(), 0); + 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(Geom::Point const &p) +{ + for (int i = 0; i < _values_vector.size(); i++) { + auto m = get_marker_point(i); + if (Geom::distance(p, m) <= 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 && !_hue_lock) { + cr->set_dash(focus_dash, 0); + cr->set_line_width(1.0); + } + else if(!_hue_lock) + { + auto const dash = std::vector{3.0}; // wider dashes for focused line + cr->set_dash(dash,0); + cr->set_line_width(2.0); + } else { + cr->set_dash(std::valarray(), 0); + if (index == _active_index) + cr->set_line_width(3.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; + 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; +} + +/** + * if not adusting a marker + * it detects if the point is on or near some marker gets its index and emits _signal_color_hovered + * so the marker gets redrawed with a bigger radius and call for any action related to hover + * (e.g. highlighting objects that has the hovered marker color) + * -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) +{ + if (!_adjusting) { + int hover_index = _get_marker_index({x, y}); + _signal_color_hovered.emit(); + if (_hover_index != hover_index) { + _hover_index = hover_index; + if (hover_index >= 0 && hover_index < _values_vector.size()) { + queue_drawing_area_draw(); + } + } + 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; + } + } + } + if (changed) { + color_changed(); + } + } + } +} + +/** + * 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; + } + + _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); + return *_radii; +} + +/** + * if the marker isn't in _markers_points it calculates the marker position + * by the hue angel and saturation as the distance from the center to the desired color + */ +Geom::Point MultiMarkerWheel::get_marker_point(int index) +{ + if (index < 0 || index >= _values_vector.size()) { + return {}; + } + + 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]; + double angle = (1.0 - hue) * 2 * M_PI; + _markers_points[index].emplace(); + auto &[mx, my] = *_markers_points[index]; + mx = cx + r_max * saturation * cos(angle); // polar cooordinates to cartisian coordinates calculation + my = cy + r_max * saturation * sin(angle); + return *_markers_points[index]; +} + +MultiMarkerWheel::MultiMarkerWheel() + : Glib::ObjectBase{"MultiMarkerWheel"} + , WidgetVfuncsClassInit{} // All the calculations are based on HSV, not HSL + , ColorWheelBase(Type::HSV, {0.5, 0.2, 0.7, 1}) // redundant values nothing important +{} + +MultiMarkerWheel::MultiMarkerWheel(BaseObjectType *cobject, Glib::RefPtr 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 8169bd86b9..97b5f2f2b2 100644 --- a/src/ui/widget/ink-color-wheel.h +++ b/src/ui/widget/ink-color-wheel.h @@ -225,6 +225,109 @@ 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); + void redrawOnHueLocked(){queue_drawing_area_draw();} + +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(Geom::Point const &p); + 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; + 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; + MinMax const &get_radii(); + Geom::Point 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 1c54b1d794..ce706efdf5 100644 --- a/src/ui/widget/marker-combo-box.cpp +++ b/src/ui/widget/marker-combo-box.cpp @@ -159,7 +159,10 @@ MarkerComboBox::MarkerComboBox(Glib::ustring id, int l) : _orient_auto(get_widget(_builder, "orient-auto")), _orient_angle(get_widget(_builder, "orient-angle")), _orient_flip_horz(get_widget(_builder, "btn-horz-flip")), - _edit_marker(get_widget(_builder, "edit-marker")) + _edit_marker(get_widget(_builder, "edit-marker")), + _recolorButtonTrigger_1(Gtk::make_managed()), + _recolorArtWdgt_1(Gtk::make_managed()), + _recolorPopOver_1(Gtk::make_managed()) { set_name("MarkerComboBox"); @@ -345,6 +348,14 @@ MarkerComboBox::MarkerComboBox(Glib::ustring id, int l) : init_combo(); update_scale_link(); update_menu_btn(); + + setupRecolorButton(_recolorButtonTrigger_1, _recolorPopOver_1, _recolorArtWdgt_1); + _grid.add_full_row(_recolorButtonTrigger_1); + _recolorButtonTrigger_1->signal_clicked().connect([this]() { + _recolorPopOver_1->popup(); + _recolorArtWdgt_1->performUpdate(); + }); + _recolorButtonTrigger_1->hide(); set_visible(true); } @@ -379,7 +390,10 @@ void MarkerComboBox::update_widgets_from_marker(SPMarker* marker) { _orient_angle.set_active(); _angle_btn.set_sensitive(true); } - } + _recolorButtonTrigger_1->show(); + + }else + _recolorButtonTrigger_1->hide(); } void MarkerComboBox::update_scale_link() { @@ -777,6 +791,21 @@ sigc::connection MarkerComboBox::connect_edit(sigc::slot slot) void MarkerComboBox::set_flat(bool flat) { set_always_show_arrow(!flat); + +} + +void MarkerComboBox::setupRecolorButton(Gtk::Button *recolorButtonTrigger, Gtk::Popover *recolorPopOver , UI::Widget::RecolorArt *recolorArtWdgt) +{ + recolorButtonTrigger->set_label("Recolor Selection"); + recolorButtonTrigger->set_hexpand(false); + recolorButtonTrigger->set_vexpand(false); + recolorButtonTrigger->set_size_request(180); + recolorButtonTrigger->set_halign(Gtk::Align::CENTER); + recolorButtonTrigger->set_valign(Gtk::Align::START); + recolorPopOver->set_parent(*recolorButtonTrigger); + recolorPopOver->set_child(*recolorArtWdgt); + recolorPopOver->set_position(Gtk::PositionType::LEFT); + recolorButtonTrigger->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 1af86289e3..98fae09e5a 100644 --- a/src/ui/widget/marker-combo-box.h +++ b/src/ui/widget/marker-combo-box.h @@ -18,6 +18,8 @@ #define SEEN_SP_MARKER_COMBO_BOX_H #include +#include +#include #include #include "display/drawing.h" @@ -26,6 +28,8 @@ #include "ink-spin-button.h" #include "snapshot-widget.h" #include "ui/operation-blocker.h" +#include "ui/widget/widget-vfuncs-class-init.h" +#include "ui/widget/recolor-art.h" namespace Gtk { class Builder; @@ -123,8 +127,29 @@ private: std::unique_ptr _sandbox; InkPropertyGrid _grid; WidgetGroup _widgets; + Gtk::CellRendererPixbuf _image_renderer; + + UI::Widget::RecolorArt *_recolorArtWdgt_1 = nullptr; + Gtk::Button *_recolorButtonTrigger_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 *recolorButtonTrigger, 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 0000000000..216f0449b5 --- /dev/null +++ b/src/ui/widget/multi-marker-color-plate.cpp @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "multi-marker-color-plate.h" +#include + +#include +#include +#include +#include + + +#include "colors/spaces/base.h" +#include "colors/spaces/enum.h" +#include "ui/icon-names.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" + +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); + } + _color_wheel->redrawOnHueLocked(); + }); + _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); +} + +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 0000000000..bbc367bd8c --- /dev/null +++ b/src/ui/widget/multi-marker-color-plate.h @@ -0,0 +1,119 @@ +// 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 "src/colors/color-set.h" +#include "src/colors/manager.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 "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); + 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; } + void setActiveIndex(int index) { _color_wheel->setActiveIndex(index); } + void toggleHueLock(bool locked) { _color_wheel->toggleHueLock(locked); _hue_lock.set_active(locked);} + std::vector getColors() const { return _color_wheel->getColors(); } + Colors::Color getColor() const { return _color_wheel->getColor(); } + bool getHueLock() const { return _color_wheel->getHueLock(); } + int getActiveIndex() const { return _color_wheel->getActiveIndex(); } + int getHoverIndex() const { return _color_wheel->getHoverIndex(); } + void connect_color_hovered(sigc::slot slot) { _color_wheel->connect_color_hovered(slot); } + void connect_color_changed(sigc::slot slot) { _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::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::scoped_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 a858c807ec..1c7f5fe36d 100644 --- a/src/ui/widget/paint-selector.cpp +++ b/src/ui/widget/paint-selector.cpp @@ -25,19 +25,28 @@ #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 "pattern-manipulation.h" +#include "selection.h" +#include "style.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,11 +123,15 @@ GradientSelectorInterface *PaintSelector::getGradientFromData() const PaintSelector::PaintSelector(FillOrStroke kind, std::shared_ptr colors) : _selected_colors(std::move(colors)) -{ +{ set_orientation(Gtk::Orientation::VERTICAL); _mode = static_cast(-1); // huh? do you mean 0xff? -- I think this means "not in the enum" + for (int i = 0; i < 5; i++) { + _recolorButtonTrigger[i] = std::make_unique(); + } + /* Paint style button box */ _style = Gtk::make_managed(); _style->set_name("PaintSelector"); @@ -188,6 +201,68 @@ PaintSelector::PaintSelector(FillOrStroke kind, std::shared_ptrset_visible(kind == FILL); + + for (auto const &b : _recolorButtonTrigger) { + b->set_label(_("Recolor Selection")); + b->set_hexpand(false); + b->set_vexpand(false); + b->set_size_request(180); + b->set_halign(Gtk::Align::CENTER); + b->set_valign(Gtk::Align::START); + b->set_margin_top(8); + b->set_visible(false); + + b->signal_clicked().connect([b = b.get(), this] { + auto guard = _blocker.block(); + if (!_recolorPopOver) { + // Lazy-load the recolour widget and popover. + _recolorArtWdgt = std::make_unique(); + _recolorArtWdgt->setDesktop(_desktop); + + _recolorPopOver = std::make_unique(); + _recolorPopOver->set_autohide(false); + _recolorPopOver->set_position(Gtk::PositionType::LEFT); + _recolorPopOver->set_parent(*b); + _recolorPopOver->set_child(*_recolorArtWdgt); + } else if (_recolorPopOver->get_parent() != b) { + // Reparent the popover to this button if necessary. + _recolorPopOver->unparent(); + _recolorPopOver->set_parent(*b); + + } + _recolorPopOver->popup(); + _recolorArtWdgt->performUpdate(); + }); + } + + _frame->append(*_recolorButtonTrigger[0]); +} + +void PaintSelector::setDesktop(SPDesktop *desktop) +{ + if (_desktop == desktop) return; + + if (_recolorPopOver) { + _recolorPopOver->popdown(); + } + + if (_selection_changed_connection) { + _selection_changed_connection.disconnect(); + } + + _desktop = desktop; + + if (_desktop) { + auto selection = _desktop->getSelection(); + if (selection) { + _selection_changed_connection = + selection->connectChanged(sigc::mem_fun(*this, &PaintSelector::onSelectionChanged)); + } + } + + if (_recolorArtWdgt) { + _recolorArtWdgt->setDesktop(_desktop); + } } StyleToggleButton *PaintSelector::style_button_add(gchar const *pixmap, PaintSelector::Mode mode, gchar const *tip) @@ -225,7 +300,11 @@ void PaintSelector::fillrule_toggled(FillRuleRadioButton *tb) } } -void PaintSelector::setMode(Mode mode) { +void PaintSelector::setMode(Mode mode) +{ + if ((_recolorPopOver && _recolorPopOver->get_visible()) && checkSelection(_desktop->getSelection())) { + return; + } set_mode_ex(mode, false); } @@ -276,6 +355,11 @@ void PaintSelector::set_mode_ex(Mode mode, bool switch_style) { } _mode = mode; _signal_mode_changed.emit(_mode, switch_style); + if (_desktop) { + if (auto sel = _desktop->getSelection()) { + onSelectionChanged(sel); + } + } _update = false; } } @@ -497,12 +581,15 @@ 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, *_recolorButtonTrigger[1], false, false); + /* 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")); @@ -547,6 +634,7 @@ 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(*_recolorButtonTrigger[2]); _frame->append(*_selector_gradient); } catch (std::exception& ex) { @@ -816,6 +904,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(*_recolorButtonTrigger[0], *_selector_mesh); } _selector_mesh->set_visible(true); @@ -902,8 +991,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) @@ -932,7 +1023,9 @@ 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(); }); + _recolorButtonTrigger[3]->set_label(_("Recolor Pattern")); _frame->append(*_selector_pattern); + _frame->append(*_recolorButtonTrigger[3]); } SPDocument* document = SP_ACTIVE_DOCUMENT; @@ -1059,6 +1152,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(*_recolorButtonTrigger[4]); + _recolorButtonTrigger[4]->hide(); // Pack everything to frame _frame->append(*_selector_swatch); } else { @@ -1123,9 +1218,126 @@ PaintSelector::Mode PaintSelector::getModeForStyle(SPStyle const &style, FillOrS return mode; } +void PaintSelector::onSelectionChanged(Inkscape::Selection *selection) +{ + if (_blocker.pending()) { + return; + } + + if (checkSelection(selection)) { + if (_mode == MODE_MULTIPLE || _mode == MODE_UNSET || _mode == MODE_GRADIENT_MESH) { + hideAllExcept(_recolorButtonTrigger[0].get()); + } else if (_mode == MODE_SOLID_COLOR) { + hideAllExcept(_recolorButtonTrigger[1].get()); + } else if (_mode == MODE_GRADIENT_RADIAL || _mode == MODE_GRADIENT_LINEAR) { + hideAllExcept(_recolorButtonTrigger[2].get()); + } else if (_mode == MODE_PATTERN) { + hideAllExcept(_recolorButtonTrigger[3].get()); + } else if (_mode == MODE_SWATCH) { + hideAllExcept(_recolorButtonTrigger[4].get()); + } else { + hideAllExcept(); + } + } else { + hideAllExcept(); + } + + if (_recolorPopOver && _recolorPopOver->get_visible() && checkSelection(selection)) { + auto guard = _blocker.block(); + _recolorArtWdgt->performUpdate(); + } +} + +bool PaintSelector::checkSelection(Inkscape::Selection *selection) +{ + auto group = cast(selection->single()); + auto use_group = cast(selection->single()); + auto item = cast(selection->single()); + bool pattern_colors = false; + SPMask *mask = nullptr; + if (item) { + mask = cast(item->getMaskObject()); + pattern_colors = has_colors_pattern(item); + } + auto check_mesh_object = [&, this]() { + if (selection->items().empty()) { + return false; + } + auto fill_gradient = cast(selection->single()->style->getFillPaintServer()); + auto stroke_gradient = cast(selection->single()->style->getStrokePaintServer()); + SPGradient *gradient = + fill_gradient ? cast(fill_gradient) : cast(stroke_gradient); + if (gradient && gradient->hasPatches()) { + return true; + } + return false; + }; + return selection->size() > 1 || group || use_group || mask || + (_mode == MODE_GRADIENT_MESH && (selection->size() > 1 || check_mesh_object())) || pattern_colors; +} + +void PaintSelector::hideAllExcept(Gtk::Button *recolorButtonTrigger) +{ + if (recolorButtonTrigger) { + recolorButtonTrigger->show(); + } + + for (auto const &b : _recolorButtonTrigger) { + if (b.get() != recolorButtonTrigger) { + b->hide(); + } + } +} + +bool PaintSelector::has_colors_pattern(SPItem *item) +{ + std::setcolors; + SPPattern *patternstroke = nullptr; + SPPattern *patternfill = nullptr; + SPPattern *pattern = nullptr; + if (item && item->style) { + patternstroke = cast(item->style->getStrokePaintServer()); + patternfill = cast(item->style->getFillPaintServer()); + } + + if (patternstroke) pattern = patternstroke; + if (patternfill) pattern = patternfill; + if (!pattern) return false; + SPPattern *root = pattern->rootPattern(); + for (auto &child : root->children) { + if (auto group = cast(&child)) { + 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 } // namespace Inkscape + /* Local Variables: mode:c++ diff --git a/src/ui/widget/paint-selector.h b/src/ui/widget/paint-selector.h index c126f04252..b4dff9b1df 100644 --- a/src/ui/widget/paint-selector.h +++ b/src/ui/widget/paint-selector.h @@ -17,8 +17,10 @@ #define SEEN_SP_PAINT_SELECTOR_H #include "fill-or-stroke.h" +#include "ui/operation-blocker.h" #include "ui/widget/gradient-selector.h" #include "ui/widget/swatch-selector.h" +#include "selection.h" class SPGradient; class SPLinearGradient; @@ -27,12 +29,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 +46,7 @@ class FillRuleRadioButton; class GradientEditor; class PatternEditor; class StyleToggleButton; +class RecolorArt; /** * Generic paint selector widget. @@ -93,7 +99,15 @@ class PaintSelector : public Gtk::Box { Gtk::Box *_selector_mesh = nullptr; SwatchSelector *_selector_swatch = nullptr; PatternEditor* _selector_pattern = nullptr; - + + std::unique_ptr _recolorArtWdgt; + std::array, 5> _recolorButtonTrigger; + std::unique_ptr _recolorPopOver; + OperationBlocker _blocker; + SPDesktop *_desktop = nullptr; + void onSelectionChanged(Inkscape::Selection *selection); + bool checkSelection(Inkscape::Selection *selection); + void hideAllExcept(Gtk::Button *recolorButtonTrigger = nullptr); Gtk::Label *_label; GtkWidget *_patternmenu = nullptr; bool _patternmenu_update = false; @@ -116,6 +130,7 @@ class PaintSelector : public Gtk::Box { sigc::signal _signal_changed; sigc::signal _signal_stop_selected; sigc::signal _signal_edit_pattern; + sigc::scoped_connection _selection_changed_connection; StyleToggleButton *style_button_add(gchar const *px, PaintSelector::Mode mode, gchar const *tip); void style_button_toggled(StyleToggleButton *tb); @@ -192,6 +207,8 @@ class PaintSelector : public Gtk::Box { Geom::Scale get_pattern_gap(); Glib::ustring get_pattern_label(); bool is_pattern_scale_uniform(); + bool has_colors_pattern(SPItem *item); + void setDesktop(SPDesktop *desktop); }; enum { diff --git a/src/ui/widget/recolor-art.cpp b/src/ui/widget/recolor-art.cpp new file mode 100644 index 0000000000..dae2e04ed0 --- /dev/null +++ b/src/ui/widget/recolor-art.cpp @@ -0,0 +1,603 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "recolor-art.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "actions/actions-tools.h" +#include "color-notebook.h" +#include "desktop.h" +#include "document-undo.h" +#include "multi-marker-color-plate.h" +#include "ui/builder-utils.h" +#include "ui/icon-names.h" +#include "ui/widget/color-preview.h" +#include "selcue.h" +#include "seltrans.h" +#include "selection.h" +#include "ui/tools/select-tool.h" + +namespace Inkscape::UI::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 + * 3- object-colors manages data and extract objects colors + */ + +RecolorArt::RecolorArt() + : _builder{create_builder("widget-recolor.ui")} + , _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")) +{ + 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(false,0,_is_preview); + if (!_is_preview) { + _manager->convertToRecoloredColors(); + DocumentUndo::done(_desktop->getDocument(), _("changed Item color"), + INKSCAPE_ICON("object-recolor-art")); + } + } + if (_desktop) { + _desktop->setHideSelectionBoxes(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([this]() { + if (_desktop) { + _desktop->setHideSelectionBoxes(true); + } + return false; + }); + }); + _color_wheel->connect_color_changed(static_cast>([this]() { + if(_blocker.pending()) { + return; // to stop recursive 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); + if (_is_preview) + _manager->convertToRecoloredColors(); + } + } + })); + _color_wheel->setRecolorWidget(this); + // add hover opacity effect when hovering over markers in the wheel + _color_wheel->connect_color_hovered([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 ,_is_preview); + } else { + _manager->changeOpacity(false,0,_is_preview); + } + }); + + 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() || !box->get_last_child()) { + 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 ,_is_preview);}); + item_controller->signal_leave().connect([box, this]() { _manager->changeOpacity(false,0,_is_preview); }); + + 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); +} + +void RecolorArt::setDesktop(SPDesktop *desktop) +{ + if (_desktop != desktop) { + _desktop = desktop; + _color_wheel->toggleHueLock(false); + _color_wheel->setLightness(100.0); + _color_wheel->setSaturation(100.0); + } +} + +/* + * 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(); + std::vector> items; + for (auto const &[key, value] : _manager->getSelectedColorsMap()) { + auto old_color = value.second.value().old_color; + auto new_color = value.second.value().new_color; + items.push_back(ColorItem::create(key, old_color, new_color)); + } + _color_model->splice(0,0,items); + 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.type == ObjectStyleType::Fill) { + kinds[INKSCAPE_ICON("object-fill")].first++; + kinds[INKSCAPE_ICON("object-fill")].second = "fill"; + } else if (item.type == ObjectStyleType::Stroke) { + kinds[INKSCAPE_ICON("object-stroke")].first++; + kinds[INKSCAPE_ICON("object-stroke")].second = "stroke"; + } else if (item.type == ObjectStyleType::Mesh) { + kinds[INKSCAPE_ICON("paint-gradient-mesh")].first++; + kinds[INKSCAPE_ICON("paint-gradient-mesh")].second = "mesh gradient"; + } else if (item.type == ObjectStyleType::Linear) { + kinds[INKSCAPE_ICON("paint-gradient-linear")].first++; + kinds[INKSCAPE_ICON("paint-gradient-linear")].second = "linear gradient"; + } else if (item.type == ObjectStyleType::Radial) { + kinds[INKSCAPE_ICON("paint-gradient-radial")].first++; + kinds[INKSCAPE_ICON("paint-gradient-radial")].second = "radial gradient"; + } else if (item.type == ObjectStyleType::Pattern) { + kinds[INKSCAPE_ICON("paint-pattern")].first++; + kinds[INKSCAPE_ICON("paint-pattern")].second = "pattern"; + } else if (item.type == ObjectStyleType::Marker) { + kinds[INKSCAPE_ICON("markers")].first++; + kinds[INKSCAPE_ICON("markers")].second = "marker"; + } else if (item.type == ObjectStyleType::Mask) { + kinds[INKSCAPE_ICON("overlay-mask")].first++; + kinds[INKSCAPE_ICON("overlay-mask")].second = "mask"; + } else if (item.type == ObjectStyleType::Swatch) { + size = "" + std::to_string(items.size() / 2) + ""; + kinds[INKSCAPE_ICON("paint-swatch")].first++; + kinds[INKSCAPE_ICON("paint-swatch")].second = "swatch"; + } + } + auto label = Gtk::make_managed(); + 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; + if (auto color = _manager->getSelectedNewColor(color_id)) { + _solid_colors->set(*color); // 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->applyNewColorToSelection(_current_color_id, new_color.value())) { + return; + } + + DocumentUndo::maybeDone(_desktop->getDocument(), _("changed Item color"), _("Recolor 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) +{ + auto guard = _blocker.block(); + std::optional new_color = wheel ? color : _solid_colors->get(); + if (!new_color.has_value()) { + 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) { + _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) { + 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}); +} + +/* + * update color model to refresh the listview ui with the new chossen colors + */ +void RecolorArt::updateColorModel(std::vector new_colors) +{ + if (!new_colors.empty() && new_colors.size() != _color_model->get_n_items()) { + return; + } + std::vector> new_colors_buttons; + for (auto i = 0; i < _color_model->get_n_items(); i++) { + auto item = _color_model->get_item(i); + if (!item) { + continue; + } + auto color_item = std::dynamic_pointer_cast(item); + if(!color_item) { + continue; + } + 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 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 the 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(); + } +} + +/* + * 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) { + g_warning("Desktop is NULL in Performupdate in recolor widget"); + return; + } + if (_manager) { + _manager->clearData(); + } + if (auto selection = _desktop->getSelection()) { + selection->unlinkRecursive(false, true, 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); + } + } +} + +} // namespace Inkscape::UI::Widget diff --git a/src/ui/widget/recolor-art.h b/src/ui/widget/recolor-art.h new file mode 100644 index 0000000000..293e935570 --- /dev/null +++ b/src/ui/widget/recolor-art.h @@ -0,0 +1,129 @@ +// 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 "colors/color-set.h" +#include "object-colors.h" +#include "ui/operation-blocker.h" +#include "ui/widget/ink-color-wheel.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 ColorItem : public Glib::Object +{ + uint32_t key; + Color old_color = Color(0x00000000); + Color new_color = Color(0x00000000); + + static Glib::RefPtr create(uint32_t 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 RecolorArt : public Gtk::Box +{ +public: + RecolorArt(); + + void performUpdate(); + bool isInPreviewMode() const { return _is_preview; } + void setDesktop(SPDesktop *desktop); + void onResetClicked(); + +private: + SPDesktop *_desktop = nullptr; + Glib::RefPtr _builder; + Gtk::Notebook &_notebook; + Gtk::Box &_color_wheel_page; + 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 = true; + + 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; + + OperationBlocker _blocker; + + void generateVisualList(); + void layoutColorPicker(std::shared_ptr updated_color = nullptr); + void colorButtons(Gtk::Box *button, Color color, bool is_original = false); + + // signal handlers + 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::pair,guint> findColorItemByKey(uint32_t key); +}; + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +#endif // WIDGET_RECOLOR_ART_H diff --git a/testfiles/CMakeLists.txt b/testfiles/CMakeLists.txt index 54c73019b6..77c656dd45 100644 --- a/testfiles/CMakeLists.txt +++ b/testfiles/CMakeLists.txt @@ -113,6 +113,8 @@ set(TEST_SOURCES lpe-test ui-util-test sp-document-test + object-colors-test + multi-marker-color-wheel-test ${LPE_TESTS_64bit} ) diff --git a/testfiles/src/multi-marker-color-wheel-test.cpp b/testfiles/src/multi-marker-color-wheel-test.cpp new file mode 100644 index 0000000000..9fb2aec1b0 --- /dev/null +++ b/testfiles/src/multi-marker-color-wheel-test.cpp @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Test the multimarker color wheel public functions functionality of Recolor Art Widget + * + * Authors: + * Fatma Omara + * + * Copyright (C) 2025 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include + +#include "src/colors/color.h" +#include "ui/widget/ink-color-wheel.h" + +using namespace Inkscape; +using Inkscape::Colors::Color; +using namespace Inkscape::Colors::Space; +class ColorWheelTestFixture : public ::testing::Test +{ +protected: + std::unique_ptr wheel; + std::vector colors; + + void SetUp() override + { + char const *gui_env = std::getenv("INKSCAPE_TEST_GUI"); + if (!gui_env || std::string(gui_env) != "1") { + GTEST_SKIP() << "Skipping GUI tests: GUI testing not enabled"; + } else { + gtk_init(); + } + colors = {Color(Type::CMYK, {0.1, 0.8, 0.0, 0.0}), + Color(0xff0000ff), + Color(0x00ff007f), + Color(0x0000ff32), + Color(0x7e1a9cff), + Color(Type::HSLUV, {120.0, 100.0, 50.0}), + Color(Type::HSL, {0.33, 1.0, 0.5}), + Color(Type::HSV, {0.66, 1.0, 1.0}), + Color(Type::LAB, {60.0, -40.0, 30.0})}; + + wheel = std::make_unique(); + } +}; + +TEST_F(ColorWheelTestFixture, TestColorWheelBasics) +{ + EXPECT_TRUE(wheel->getColors().empty()); + EXPECT_EQ(wheel->getActiveIndex(), -1); + + EXPECT_TRUE(wheel->setColor(colors)); + EXPECT_EQ(wheel->getColors().size(), 9); + EXPECT_EQ(wheel->getActiveIndex(), 0); +} + +TEST_F(ColorWheelTestFixture, TestColorWheelActiveIndex) +{ + wheel->setColor(colors); + EXPECT_TRUE(wheel->setActiveIndex(8)); + EXPECT_EQ(wheel->getActiveIndex(), 8); + + EXPECT_FALSE(wheel->setActiveIndex(-1)); + EXPECT_FALSE(wheel->setActiveIndex(99)); + EXPECT_EQ(wheel->getActiveIndex(), 8); +} + +TEST_F(ColorWheelTestFixture, TestColorWheelLightnessAndSaturation) +{ + wheel->setColor(colors); + wheel->setLightness(90); + EXPECT_EQ(wheel->getColor()[2], 0.9); + + wheel->setSaturation(40); + EXPECT_EQ(wheel->getColor()[1], 0.4); + + auto color = Color(0xffffffff); + EXPECT_TRUE(wheel->changeColor(8, color)); + EXPECT_TRUE(wheel->setActiveIndex(8)); + EXPECT_EQ(wheel->getColor().toRGBA(), color.toRGBA()); +} + +TEST_F(ColorWheelTestFixture, TestColorWheelHueLocking) +{ + wheel->setColor(colors); + EXPECT_FALSE(wheel->getColors().empty()); + wheel->toggleHueLock(true); + EXPECT_EQ(wheel->getHueLock(), true); + + wheel->setLightness(50); + wheel->setSaturation(83); + EXPECT_EQ(wheel->getColors()[4][2], 0.5); + + for (auto color : wheel->getColors()) { + EXPECT_EQ(color[2], 0.5); + EXPECT_EQ(color[1], 0.83); + } +} diff --git a/testfiles/src/object-colors-test.cpp b/testfiles/src/object-colors-test.cpp new file mode 100644 index 0000000000..22e28c2d91 --- /dev/null +++ b/testfiles/src/object-colors-test.cpp @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Test the Object Colors Extraction and Data Population functionality of Recolor Art Widget + * + * Authors: + * Fatma Omara + * + * Copyright (C) 2025 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include "src/object-colors.h" + +#include +#include + +#include "object/sp-defs.h" +#include "object/sp-gradient.h" +#include "object/sp-object.h" +#include "object/sp-stop.h" +#include "src/colors/color.h" +#include "src/document.h" +#include "xml/node.h" +#include "xml/repr.h" +#include "style.h" +using namespace Inkscape; + +class ObjectColorSetFixture : public DocPerCaseTest +{ +protected: + std::shared_ptr set; + SPDocument *document; + ColorsExtractor extractor; + std::vector Vector; + std::vector nodes; + + ObjectColorSetFixture() + : set(std::make_shared()) + , extractor(set) + { + DocPerCaseTest::SetUp(); + document = _doc.get(); + Vector = std::vector(6); + nodes = std::vector(6); + setUpDoc(); + extractor.collectColors(Vector); + } + + void setUpDoc() + { + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + ASSERT_NE(xml_doc, nullptr); + + Inkscape::XML::Node *grad_node = xml_doc->createElement("svg:linearGradient"); + grad_node->setAttribute("id", "test-gradient"); + + Inkscape::XML::Node *stop1_node = xml_doc->createElement("svg:stop"); + stop1_node->setAttribute("offset", "0"); + stop1_node->setAttribute("stop-color", "#ffA000ff"); + stop1_node->setAttribute("stop-opacity", "1"); + grad_node->appendChild(stop1_node); + + Inkscape::XML::Node *stop2_node = xml_doc->createElement("svg:stop"); + stop2_node->setAttribute("offset", "1"); + stop2_node->setAttribute("stop-color", "#00ffffff"); + stop2_node->setAttribute("stop-opacity", "1"); + grad_node->appendChild(stop2_node); + + for (int i = 0; i < 6; i++) { + Inkscape::XML::Node *rect = xml_doc->createElement("svg:rect"); + nodes[i] = rect; + } + + nodes[0]->setAttribute("fill", "#ffff00ff"); + nodes[0]->setAttribute("stroke", "#6c7ad2ff"); + nodes[1]->setAttribute("fill", "#6c7ad2ff"); + nodes[1]->setAttribute("stroke", "#ffff00ff"); + nodes[2]->setAttribute("fill", "#ff00d4ff"); + nodes[2]->setAttribute("stroke", "#ff00d4ff"); + nodes[3]->setAttribute("fill", "#ff0000ff"); + nodes[3]->setAttribute("stroke", "#ff70ffff"); + nodes[4]->setAttribute("fill", "#00ff00ff"); + nodes[4]->setAttribute("stroke", "#ba6cd2ff"); + nodes[5]->setAttribute("fill", "url(#test-gradient)"); + + document->getDefs()->getRepr()->appendChild(grad_node); + + for (int i = 0; i < 6; i++) { + document->getDefs()->getRepr()->appendChild(nodes[i]); + } + + for (int i = 0; i < 6; i++) { + Vector[i] = dynamic_cast(document->getObjectByRepr(nodes[i])); + } + } +}; + +TEST_F(ObjectColorSetFixture, HandleEmptyObjects) +{ + set->clearData(); + std::vector empty_vector; + extractor.collectColors(empty_vector); + EXPECT_TRUE(set->isSelectedColorsEmpty()); +} + +TEST_F(ObjectColorSetFixture, HandleNullObjects) +{ + set->clearData(); + std::vector null_vector{nullptr}; + extractor.collectColors(null_vector); + EXPECT_TRUE(set->isSelectedColorsEmpty()); +} + +TEST_F(ObjectColorSetFixture, PopulateAndFindColor) +{ + EXPECT_FALSE(set->isSelectedColorsEmpty()); + EXPECT_FALSE(set->isGradientStopsEmpty()); + EXPECT_EQ(set->getColors().size(), 9); + + auto key = Colors::Color(0xffff00ff).toRGBA(); + EXPECT_TRUE(set->findSelectedColor(key)); + + EXPECT_EQ(set->getColorIndex(key), 0); + EXPECT_EQ(set->getColor(0).value().toRGBA(), key); + + auto false_key = Colors::Color(0x000000ff).toRGBA(); + EXPECT_FALSE(set->findSelectedColor(false_key)); + EXPECT_EQ(set->getColorIndex(false_key), -1); +} + +TEST_F(ObjectColorSetFixture, ClearData) +{ + EXPECT_EQ(set->getColors().size(), 9); + set->clearData(); + EXPECT_TRUE(set->isSelectedColorsEmpty()); + EXPECT_TRUE(set->isGradientStopsEmpty()); + EXPECT_TRUE(set->isColorWheelColorsMapEmpty()); + EXPECT_TRUE(set->isColorsEmpty()); +} + +TEST_F(ObjectColorSetFixture, SetAndGetSelectedColors) +{ + Colors::Color new_color(0xff00ffff); + auto key = Colors::Color(0xffff00ff).toRGBA(); + set->setSelectedNewColor(key, new_color); + EXPECT_EQ(set->getSelectedNewColor(key).value().toRGBA(), new_color.toRGBA()); +} + +TEST_F(ObjectColorSetFixture, SetSelectedNewColors) +{ + std::vector colors{Colors::Color(Colors::Space::Type::CMYK, {0.1, 0.8, 0.0, 0.0}), + Colors::Color(0xff0000ff), + Colors::Color(0x00ff00ff), + Colors::Color(0x0000ffff), + Colors::Color(0x7e1a9cff), + Colors::Color(Colors::Space::Type::HSLUV, {120.0, 100.0, 50.0}), + Colors::Color(Colors::Space::Type::HSL, {0.33, 1.0, 0.5}), + Colors::Color(Colors::Space::Type::HSV, {0.66, 1.0, 1.0}), + Colors::Color(Colors::Space::Type::LAB, {60.0, -40.0, 30.0})}; + set->setSelectedNewColor(colors); + std::vector new_colors; + for (auto [key, value] : set->getSelectedColorsMap()) { + new_colors.push_back(value.second.value().new_color.toRGBA()); + } + std::vector color{ + Colors::Color(Colors::Space::Type::CMYK, {0.1, 0.8, 0.0, 0.0}).toRGBA(), + Colors::Color(0xff0000ff).toRGBA(), + Colors::Color(0x00ff00ff).toRGBA(), + Colors::Color(0x0000ffff).toRGBA(), + Colors::Color(0x7e1a9cff).toRGBA(), + Colors::Color(Colors::Space::Type::HSLUV, {120.0, 100.0, 50.0}).toRGBA(), + Colors::Color(Colors::Space::Type::HSL, {0.33, 1.0, 0.5}).toRGBA(), + Colors::Color(Colors::Space::Type::HSV, {0.66, 1.0, 1.0}).toRGBA(), + Colors::Color(Colors::Space::Type::LAB, {60.0, -40.0, 30.0}).toRGBA()}; + sort(color.begin(), color.end()); + sort(new_colors.begin(), new_colors.end()); + EXPECT_EQ(color, new_colors); + + set->convertToRecoloredColors(); + EXPECT_EQ(Vector[0]->style->fill.getColor().toRGBA(), Colors::Color(Colors::Space::Type::CMYK, {0.1, 0.8, 0.0, 0.0}).toRGBA()); + char const *style1 = nodes[0]->attribute("style"); + EXPECT_NE(strstr(style1, "fill:device-cmyk(0.1 0.8 0 0)"), nullptr); + + // test reseting just object color without reseting it in the map for livepreview purposes + set->revertToOriginalColors(false); + EXPECT_EQ(Vector[0]->style->fill.getColor().toRGBA(), Colors::Color(0xffff00ff).toRGBA()); + char const *style2 = nodes[0]->attribute("style"); + EXPECT_NE(strstr(style2, "fill:#ffff00ff"), nullptr); + auto map1 = set->getSelectedColorsMap(); + auto value1 = map1[Colors::Color(0xffff00ff).toRGBA()].second.value().new_color.toRGBA(); + EXPECT_NE(value1 , Colors::Color(0xffff00ff).toRGBA()); + + // test with resetting map entry for reset button + set->revertToOriginalColors(true); + auto map2 = set->getSelectedColorsMap(); + auto value2 = map2[Colors::Color(0xffff00ff).toRGBA()].second.value().new_color.toRGBA(); + EXPECT_EQ(value2 , Colors::Color(0xffff00ff).toRGBA()); + +} + +TEST_F(ObjectColorSetFixture, ChangeObjectsColors) +{ + EXPECT_FALSE(set->isSelectedColorsEmpty()); + + EXPECT_FALSE(set->isGradientStopsEmpty()); + EXPECT_EQ(set->getColors().size(), 9); + + set->applyNewColorToSelection(Colors::Color(0xffff00ff).toRGBA(), Colors::Color(0x7e1a9cff)); + EXPECT_EQ(Vector[0]->style->fill.getColor().toRGBA(), Colors::Color(0x7e1a9cff).toRGBA()); + EXPECT_EQ(Vector[1]->style->stroke.getColor().toRGBA(), Colors::Color(0x7e1a9cff).toRGBA()); + + char const *style1 = nodes[0]->attribute("style"); + char const *style2 = nodes[1]->attribute("style"); + EXPECT_NE(strstr(style1, "fill:#7e1a9cff"), nullptr); + EXPECT_NE(strstr(style2, "stroke:#7e1a9cff"), nullptr); +} + +TEST_F(ObjectColorSetFixture, HandleLargeColorSets) +{ + set->clearData(); + Inkscape::XML::Document *xml_doc = document->getReprDoc(); + std::vector large_vector; + + for (int i = 0; i < 100000; ++i) { + Inkscape::XML::Node *rect = xml_doc->createElement("svg:rect"); + char color_str[16]; + snprintf(color_str, sizeof(color_str), "#%06xff", i % 0xFFFFFF); + rect->setAttribute("fill", color_str); + + document->getDefs()->getRepr()->appendChild(rect); + large_vector.push_back(document->getObjectByRepr(rect)); + } + + extractor.collectColors(large_vector); + EXPECT_EQ(set->getColors().size(), 100000); +} + +TEST_F(ObjectColorSetFixture, TestColorIndexBoundaryConditions) +{ + EXPECT_EQ(set->getColor(-1), std::nullopt); + EXPECT_EQ(set->getColor(set->getColors().size()), std::nullopt); + EXPECT_EQ(set->getColorIndex(0x99999999), -1); +} + +TEST_F(ObjectColorSetFixture, TestColorApplicationFailure) +{ + auto false_key = Colors::Color(0x99999999).toRGBA(); + EXPECT_FALSE(set->applyNewColorToSelection(false_key, Colors::Color(0xff0000ff))); +} -- GitLab From efa5ba46df6d99c0010e48442ff25c87842a7309 Mon Sep 17 00:00:00 2001 From: ftomara Date: Fri, 12 Sep 2025 08:59:07 +0300 Subject: [PATCH 2/8] removed onhover effect --- src/ui/widget/recolor-art.cpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/ui/widget/recolor-art.cpp b/src/ui/widget/recolor-art.cpp index dae2e04ed0..8b9cd1aa8d 100644 --- a/src/ui/widget/recolor-art.cpp +++ b/src/ui/widget/recolor-art.cpp @@ -51,7 +51,6 @@ RecolorArt::RecolorArt() // and reshow selection boxes again signal_unmap().connect([&]() { if (_manager) { - _manager->changeOpacity(false,0,_is_preview); if (!_is_preview) { _manager->convertToRecoloredColors(); DocumentUndo::done(_desktop->getDocument(), _("changed Item color"), @@ -109,9 +108,6 @@ RecolorArt::RecolorArt() return; } _current_color_id = _manager->getColor(index).value().toRGBA(); - _manager->changeOpacity(true , _current_color_id ,_is_preview); - } else { - _manager->changeOpacity(false,0,_is_preview); } }); @@ -190,12 +186,6 @@ RecolorArt::RecolorArt() 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 ,_is_preview);}); - item_controller->signal_leave().connect([box, this]() { _manager->changeOpacity(false,0,_is_preview); }); - - box->add_controller(item_controller); auto original = dynamic_cast(box->get_first_child()); auto recolored = dynamic_cast(box->get_last_child()); @@ -585,18 +575,23 @@ void RecolorArt::performUpdate() if (_manager) { _manager->clearData(); } + + _color_wheel->toggleHueLock(false); + _color_wheel->setLightness(100.0); + _color_wheel->setSaturation(100.0); + if (auto selection = _desktop->getSelection()) { selection->unlinkRecursive(false, true, 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); } + if (!_manager->isColorsEmpty()) { + _color_wheel->setColor(_manager->getColors()); + } } } -- GitLab From 6c30c706508a1d3d29af2f122c432165bb0c23d2 Mon Sep 17 00:00:00 2001 From: ftomara Date: Tue, 16 Sep 2025 18:56:05 +0300 Subject: [PATCH 3/8] made manager class for recolorwidget and its popover , made function for recoloring only selected marker --- src/ui/CMakeLists.txt | 2 + src/ui/widget/marker-combo-box.cpp | 61 +++++++----- src/ui/widget/marker-combo-box.h | 10 +- src/ui/widget/paint-selector.cpp | 119 ++++++----------------- src/ui/widget/paint-selector.h | 7 +- src/ui/widget/recolor-art-manager.cpp | 130 ++++++++++++++++++++++++++ src/ui/widget/recolor-art-manager.h | 40 ++++++++ src/ui/widget/recolor-art.cpp | 35 +++++++ src/ui/widget/recolor-art.h | 4 +- src/ui/widget/stroke-style.cpp | 3 + 10 files changed, 288 insertions(+), 123 deletions(-) create mode 100644 src/ui/widget/recolor-art-manager.cpp create mode 100644 src/ui/widget/recolor-art-manager.h diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index f833e4356c..50673216db 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -301,6 +301,7 @@ set(ui_SRC widget/property-utils.cpp widget/recolor-art.cpp widget/multi-marker-color-plate.cpp + widget/recolor-art-manager.cpp view/svg-view-widget.cpp # ------- @@ -629,6 +630,7 @@ set(ui_SRC widget/property-utils.h widget/recolor-art.h widget/multi-marker-color-plate.h + widget/recolor-art-manager.h view/svg-view-widget.h ) diff --git a/src/ui/widget/marker-combo-box.cpp b/src/ui/widget/marker-combo-box.cpp index ce706efdf5..12568f96f4 100644 --- a/src/ui/widget/marker-combo-box.cpp +++ b/src/ui/widget/marker-combo-box.cpp @@ -160,9 +160,7 @@ MarkerComboBox::MarkerComboBox(Glib::ustring id, int l) : _orient_angle(get_widget(_builder, "orient-angle")), _orient_flip_horz(get_widget(_builder, "btn-horz-flip")), _edit_marker(get_widget(_builder, "edit-marker")), - _recolorButtonTrigger_1(Gtk::make_managed()), - _recolorArtWdgt_1(Gtk::make_managed()), - _recolorPopOver_1(Gtk::make_managed()) + _recolorButtonTrigger_1(Gtk::make_managed()) { set_name("MarkerComboBox"); @@ -349,12 +347,33 @@ MarkerComboBox::MarkerComboBox(Glib::ustring id, int l) : update_scale_link(); update_menu_btn(); - setupRecolorButton(_recolorButtonTrigger_1, _recolorPopOver_1, _recolorArtWdgt_1); + _recolorButtonTrigger_1->set_label("Recolor Marker"); + _recolorButtonTrigger_1->set_hexpand(true); + _recolorButtonTrigger_1->set_vexpand(false); + _recolorButtonTrigger_1->set_size_request(180); + _recolorButtonTrigger_1->set_halign(Gtk::Align::FILL); + _recolorButtonTrigger_1->set_valign(Gtk::Align::START); + _recolorButtonTrigger_1->set_margin_top(8); + _grid.add_full_row(_recolorButtonTrigger_1); _recolorButtonTrigger_1->signal_clicked().connect([this]() { - _recolorPopOver_1->popup(); - _recolorArtWdgt_1->performUpdate(); + if (!_recolorManager) { + // Lazy-load the recolour widget and popover. + _recolorManager = RecolorArtManager::getRecolorWidgetInstance().get(); + if (_recolorManager->getPopOver()->get_parent()) + _recolorManager->getPopOver()->unparent(); + _recolorManager->getPopOver()->set_parent(*_recolorButtonTrigger_1); + _recolorManager->setDesktop(_desktop); + + } else if (_recolorManager->getPopOver()->get_parent() != _recolorButtonTrigger_1) { + // Reparent the popover to this button if necessary. + _recolorManager->getPopOver()->unparent(); + _recolorManager->getPopOver()->set_parent(*_recolorButtonTrigger_1); + } + _recolorManager->getPopOver()->popup(); + _recolorManager->performMarkerUpdate(get_current()); }); + _recolorButtonTrigger_1->hide(); set_visible(true); } @@ -392,7 +411,7 @@ void MarkerComboBox::update_widgets_from_marker(SPMarker* marker) { } _recolorButtonTrigger_1->show(); - }else + } else _recolorButtonTrigger_1->hide(); } @@ -492,7 +511,21 @@ Glib::RefPtr MarkerComboBox::get_active() { return empty; } } +void MarkerComboBox::setDesktop(SPDesktop *desktop) +{ + if (_desktop == desktop) + return; + + if (_recolorManager && _recolorManager->getPopOver()) { + _recolorManager->getPopOver()->popdown(); + } + _desktop = desktop; + + if (_recolorManager) { + _recolorManager->setDesktop(_desktop); + } +} void MarkerComboBox::setDocument(SPDocument *document) { if (_document != document) { @@ -794,20 +827,6 @@ void MarkerComboBox::set_flat(bool flat) { } -void MarkerComboBox::setupRecolorButton(Gtk::Button *recolorButtonTrigger, Gtk::Popover *recolorPopOver , UI::Widget::RecolorArt *recolorArtWdgt) -{ - recolorButtonTrigger->set_label("Recolor Selection"); - recolorButtonTrigger->set_hexpand(false); - recolorButtonTrigger->set_vexpand(false); - recolorButtonTrigger->set_size_request(180); - recolorButtonTrigger->set_halign(Gtk::Align::CENTER); - recolorButtonTrigger->set_valign(Gtk::Align::START); - recolorPopOver->set_parent(*recolorButtonTrigger); - recolorPopOver->set_child(*recolorArtWdgt); - recolorPopOver->set_position(Gtk::PositionType::LEFT); - recolorButtonTrigger->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 98fae09e5a..b014791fec 100644 --- a/src/ui/widget/marker-combo-box.h +++ b/src/ui/widget/marker-combo-box.h @@ -30,6 +30,7 @@ #include "ui/operation-blocker.h" #include "ui/widget/widget-vfuncs-class-init.h" #include "ui/widget/recolor-art.h" +#include "ui/widget/recolor-art-manager.h" namespace Gtk { class Builder; @@ -59,7 +60,7 @@ public: MarkerComboBox(Glib::ustring id, int loc); void setDocument(SPDocument *); - + void setDesktop(SPDesktop *desktop); void set_current(SPObject *marker); std::string get_active_marker_uri(); bool in_update() const { return _update.pending(); }; @@ -129,9 +130,10 @@ private: WidgetGroup _widgets; Gtk::CellRendererPixbuf _image_renderer; - UI::Widget::RecolorArt *_recolorArtWdgt_1 = nullptr; + UI::Widget::RecolorArtManager* _recolorManager = nullptr; + SPDesktop *_desktop = nullptr; + sigc::scoped_connection _selection_changed_connection; Gtk::Button *_recolorButtonTrigger_1 = nullptr; - Gtk::Popover* _recolorPopOver_1=nullptr; class MarkerColumns : public Gtk::TreeModel::ColumnRecord { public: Gtk::TreeModelColumn label; @@ -147,8 +149,6 @@ private: }; MarkerColumns marker_columns; - void setupRecolorButton(Gtk::Button *recolorButtonTrigger, Gtk::Popover *recolorPopOver, - UI::Widget::RecolorArt *recolorArtWdgt); void update_ui(SPMarker *marker, bool select); void update_widgets_from_marker(SPMarker* marker); void update_store(); diff --git a/src/ui/widget/paint-selector.cpp b/src/ui/widget/paint-selector.cpp index 1c7f5fe36d..b474ebb182 100644 --- a/src/ui/widget/paint-selector.cpp +++ b/src/ui/widget/paint-selector.cpp @@ -46,8 +46,10 @@ #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 "ui/widget/recolor-art.h" +#include "ui/widget/recolor-art-manager.h" #include "widgets/widget-sizes.h" +#include "recolor-art-manager.h" #ifdef SP_PS_VERBOSE static gchar const *modeStrings[] = { @@ -214,24 +216,22 @@ PaintSelector::PaintSelector(FillOrStroke kind, std::shared_ptrsignal_clicked().connect([b = b.get(), this] { auto guard = _blocker.block(); - if (!_recolorPopOver) { + if (!_recolorManager) { // Lazy-load the recolour widget and popover. - _recolorArtWdgt = std::make_unique(); - _recolorArtWdgt->setDesktop(_desktop); - - _recolorPopOver = std::make_unique(); - _recolorPopOver->set_autohide(false); - _recolorPopOver->set_position(Gtk::PositionType::LEFT); - _recolorPopOver->set_parent(*b); - _recolorPopOver->set_child(*_recolorArtWdgt); - } else if (_recolorPopOver->get_parent() != b) { - // Reparent the popover to this button if necessary. - _recolorPopOver->unparent(); - _recolorPopOver->set_parent(*b); + _recolorManager = RecolorArtManager::getRecolorWidgetInstance().get(); + if(_recolorManager->getPopOver()->get_parent()) + _recolorManager->getPopOver()->unparent(); + _recolorManager->getPopOver()->set_parent(*b); + _recolorManager->setDesktop(_desktop); + } else if (_recolorManager->getPopOver()->get_parent() != b) { + // Reparent the popover to this button if necessary. + _recolorManager->getPopOver()->unparent(); + _recolorManager->getPopOver()->set_parent(*b); } - _recolorPopOver->popup(); - _recolorArtWdgt->performUpdate(); + _recolorManager->getPopOver()->popup(); + _recolorManager->performUpdate(); + }); } @@ -242,8 +242,8 @@ void PaintSelector::setDesktop(SPDesktop *desktop) { if (_desktop == desktop) return; - if (_recolorPopOver) { - _recolorPopOver->popdown(); + if (_recolorManager && _recolorManager->getPopOver()) { + _recolorManager->getPopOver()->popdown(); } if (_selection_changed_connection) { @@ -260,8 +260,8 @@ void PaintSelector::setDesktop(SPDesktop *desktop) } } - if (_recolorArtWdgt) { - _recolorArtWdgt->setDesktop(_desktop); + if (_recolorManager) { + _recolorManager->setDesktop(_desktop); } } @@ -302,7 +302,7 @@ void PaintSelector::fillrule_toggled(FillRuleRadioButton *tb) void PaintSelector::setMode(Mode mode) { - if ((_recolorPopOver && _recolorPopOver->get_visible()) && checkSelection(_desktop->getSelection())) { + if ((_recolorManager->getPopOver() && _recolorManager->getPopOver()->get_visible()) && checkSelection(_desktop->getSelection())) { return; } set_mode_ex(mode, false); @@ -1242,41 +1242,19 @@ void PaintSelector::onSelectionChanged(Inkscape::Selection *selection) hideAllExcept(); } - if (_recolorPopOver && _recolorPopOver->get_visible() && checkSelection(selection)) { + if (_recolorManager->getPopOver() && _recolorManager->getPopOver()->get_visible() && checkSelection(selection)) { auto guard = _blocker.block(); - _recolorArtWdgt->performUpdate(); + _recolorManager->performUpdate(); } } bool PaintSelector::checkSelection(Inkscape::Selection *selection) { - auto group = cast(selection->single()); - auto use_group = cast(selection->single()); - auto item = cast(selection->single()); - bool pattern_colors = false; - SPMask *mask = nullptr; - if (item) { - mask = cast(item->getMaskObject()); - pattern_colors = has_colors_pattern(item); - } - auto check_mesh_object = [&, this]() { - if (selection->items().empty()) { - return false; - } - auto fill_gradient = cast(selection->single()->style->getFillPaintServer()); - auto stroke_gradient = cast(selection->single()->style->getStrokePaintServer()); - SPGradient *gradient = - fill_gradient ? cast(fill_gradient) : cast(stroke_gradient); - if (gradient && gradient->hasPatches()) { - return true; - } - return false; - }; - return selection->size() > 1 || group || use_group || mask || - (_mode == MODE_GRADIENT_MESH && (selection->size() > 1 || check_mesh_object())) || pattern_colors; + return (_mode == MODE_GRADIENT_MESH && (selection->size() > 1 || _recolorManager->checkMeshObject(selection))) || + _recolorManager->checkSelection(selection); } -void PaintSelector::hideAllExcept(Gtk::Button *recolorButtonTrigger) +void PaintSelector::hideAllExcept(Gtk::Button *recolorButtonTrigger) { if (recolorButtonTrigger) { recolorButtonTrigger->show(); @@ -1289,51 +1267,6 @@ void PaintSelector::hideAllExcept(Gtk::Button *recolorButtonTrigger) } } -bool PaintSelector::has_colors_pattern(SPItem *item) -{ - std::setcolors; - SPPattern *patternstroke = nullptr; - SPPattern *patternfill = nullptr; - SPPattern *pattern = nullptr; - if (item && item->style) { - patternstroke = cast(item->style->getStrokePaintServer()); - patternfill = cast(item->style->getFillPaintServer()); - } - - if (patternstroke) pattern = patternstroke; - if (patternfill) pattern = patternfill; - if (!pattern) return false; - SPPattern *root = pattern->rootPattern(); - for (auto &child : root->children) { - if (auto group = cast(&child)) { - 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 } // namespace Inkscape diff --git a/src/ui/widget/paint-selector.h b/src/ui/widget/paint-selector.h index b4dff9b1df..4d90584ea1 100644 --- a/src/ui/widget/paint-selector.h +++ b/src/ui/widget/paint-selector.h @@ -21,6 +21,7 @@ #include "ui/widget/gradient-selector.h" #include "ui/widget/swatch-selector.h" #include "selection.h" +#include "ui/widget/recolor-art-manager.h" class SPGradient; class SPLinearGradient; @@ -47,6 +48,7 @@ class GradientEditor; class PatternEditor; class StyleToggleButton; class RecolorArt; +class RecolorArtManager; /** * Generic paint selector widget. @@ -100,9 +102,9 @@ class PaintSelector : public Gtk::Box { SwatchSelector *_selector_swatch = nullptr; PatternEditor* _selector_pattern = nullptr; - std::unique_ptr _recolorArtWdgt; + + UI::Widget::RecolorArtManager* _recolorManager; std::array, 5> _recolorButtonTrigger; - std::unique_ptr _recolorPopOver; OperationBlocker _blocker; SPDesktop *_desktop = nullptr; void onSelectionChanged(Inkscape::Selection *selection); @@ -207,7 +209,6 @@ class PaintSelector : public Gtk::Box { Geom::Scale get_pattern_gap(); Glib::ustring get_pattern_label(); bool is_pattern_scale_uniform(); - bool has_colors_pattern(SPItem *item); void setDesktop(SPDesktop *desktop); }; diff --git a/src/ui/widget/recolor-art-manager.cpp b/src/ui/widget/recolor-art-manager.cpp new file mode 100644 index 0000000000..03ce142fa0 --- /dev/null +++ b/src/ui/widget/recolor-art-manager.cpp @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Authors: + * Fatma Omara + * + * Copyright (C) 2025 authors + * + */ + +#include "recolor-art-manager.h" + +#include "object/sp-mask.h" +#include "object/sp-mesh-gradient.h" +#include "object/sp-pattern.h" +#include "object/sp-use.h" +#include "style.h" + +namespace Inkscape::UI::Widget { + +std::unique_ptr &RecolorArtManager::getRecolorWidgetInstance() +{ + if (!_recolorPopOver) { + _recolor_widget = std::make_unique(); + _recolorPopOver = std::make_unique(); + _recolorPopOver->set_autohide(false); + _recolorPopOver->set_position(Gtk::PositionType::LEFT); + _recolorPopOver->set_child(*_recolor_widget); + _recolor_manager = std::make_unique(); + } + return _recolor_manager; +} +bool RecolorArtManager::checkSelection(Inkscape::Selection *selection) +{ + auto group = cast(selection->single()); + auto use_group = cast(selection->single()); + auto item = cast(selection->single()); + bool pattern_colors = false; + SPMask *mask = nullptr; + if (item) { + mask = cast(item->getMaskObject()); + pattern_colors = has_colors_pattern(item); + } + return selection->size() > 1 || group || use_group || mask || pattern_colors; +} +bool RecolorArtManager::has_colors_pattern(SPItem *item) +{ + std::set colors; + SPPattern *patternstroke = nullptr; + SPPattern *patternfill = nullptr; + SPPattern *pattern = nullptr; + if (item && item->style) { + patternstroke = cast(item->style->getStrokePaintServer()); + patternfill = cast(item->style->getFillPaintServer()); + } + + if (patternstroke) + pattern = patternstroke; + if (patternfill) + pattern = patternfill; + if (!pattern) + return false; + SPPattern *root = pattern->rootPattern(); + for (auto &child : root->children) { + if (auto group = cast(&child)) { + 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; +} + +bool RecolorArtManager::checkMeshObject(Inkscape::Selection *selection) +{ + if (selection->items().empty()) { + return false; + } + auto fill_gradient = cast(selection->single()->style->getFillPaintServer()); + auto stroke_gradient = cast(selection->single()->style->getStrokePaintServer()); + SPGradient *gradient = fill_gradient ? cast(fill_gradient) : cast(stroke_gradient); + if (gradient && gradient->hasPatches()) { + return true; + } + return false; +} +void RecolorArtManager::setDesktop(SPDesktop *desktop) +{ + if (_recolor_widget) + _recolor_widget->setDesktop(desktop); +} +void RecolorArtManager::performUpdate() +{ + if (_recolor_widget) + _recolor_widget->performUpdate(); +} + +void RecolorArtManager::performMarkerUpdate(SPMarker *marker) +{ + if (_recolor_widget) + _recolor_widget->performMarkerUpdate(marker); +} + +std::unique_ptr &RecolorArtManager::getPopOver() const +{ + return _recolorPopOver; +} + +} // namespace Inkscape::UI::Widget \ No newline at end of file diff --git a/src/ui/widget/recolor-art-manager.h b/src/ui/widget/recolor-art-manager.h new file mode 100644 index 0000000000..777c7f26ea --- /dev/null +++ b/src/ui/widget/recolor-art-manager.h @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef RECOLOR_ART_MANAGER_H +#define RECOLOR_ART_MANAGER_H +/* + * + * Authors: + * Fatma Omara + * + * Copyright (C) 2025 authors + * + */ +#include + +#include "ui/widget/recolor-art.h" +#include "selection.h" + + +namespace Inkscape::UI::Widget { + +class RecolorArtManager +{ +public: + static std::unique_ptr &getRecolorWidgetInstance(); + std::unique_ptr &getPopOver() const; + bool checkSelection(Inkscape::Selection *selection); + bool checkMeshObject(Inkscape::Selection *selection); + void setDesktop(SPDesktop *desktop); + void performUpdate(); + void performMarkerUpdate(SPMarker *marker); + +private: + inline static std::unique_ptr _recolor_widget = nullptr; + inline static std::unique_ptr _recolorPopOver = nullptr; + inline static std::unique_ptr _recolor_manager = nullptr; + bool has_colors_pattern(SPItem *item); +}; + +} // namespace Inkscape::UI::Widget +#endif // RECOLOR_ART_MANAGER_H \ No newline at end of file diff --git a/src/ui/widget/recolor-art.cpp b/src/ui/widget/recolor-art.cpp index 8b9cd1aa8d..188a894b4d 100644 --- a/src/ui/widget/recolor-art.cpp +++ b/src/ui/widget/recolor-art.cpp @@ -22,6 +22,8 @@ #include "seltrans.h" #include "selection.h" #include "ui/tools/select-tool.h" +#include "object/sp-marker.h" + namespace Inkscape::UI::Widget { @@ -568,6 +570,9 @@ void RecolorArt::onLivePreviewToggled() */ void RecolorArt::performUpdate() { + if (_selection_blocker.pending()) { + return; + } if (!_desktop) { g_warning("Desktop is NULL in Performupdate in recolor widget"); return; @@ -581,6 +586,7 @@ void RecolorArt::performUpdate() _color_wheel->setSaturation(100.0); if (auto selection = _desktop->getSelection()) { + auto guard = _selection_blocker.block(); selection->unlinkRecursive(false, true, false); std::vector vec(selection->items().begin(), selection->items().end()); _extractor->collectColors(vec); @@ -595,4 +601,33 @@ void RecolorArt::performUpdate() } } +void RecolorArt::performMarkerUpdate(SPMarker *marker) +{ + if (!marker) { + return; + } + if (!_desktop) { + g_warning("Desktop is NULL in Performupdate in recolor widget"); + return; + } + if (_manager) { + _manager->clearData(); + } + + _color_wheel->toggleHueLock(false); + _color_wheel->setLightness(100.0); + _color_wheel->setSaturation(100.0); + + std::vector vec = {marker}; + _extractor->collectColors(vec); + if (!_manager->isSelectedColorsEmpty()) { + generateVisualList(); + auto first_button_id = _manager->getFirstKey(); + onOriginalColorClicked(first_button_id); + } + if (!_manager->isColorsEmpty()) { + _color_wheel->setColor(_manager->getColors()); + } +} + } // namespace Inkscape::UI::Widget diff --git a/src/ui/widget/recolor-art.h b/src/ui/widget/recolor-art.h index 293e935570..60f2afc8a9 100644 --- a/src/ui/widget/recolor-art.h +++ b/src/ui/widget/recolor-art.h @@ -20,6 +20,7 @@ #include #include #include +#include #include "colors/color-set.h" #include "object-colors.h" @@ -76,11 +77,11 @@ class RecolorArt : public Gtk::Box { public: RecolorArt(); - void performUpdate(); bool isInPreviewMode() const { return _is_preview; } void setDesktop(SPDesktop *desktop); void onResetClicked(); + void performMarkerUpdate(SPMarker* marker); private: SPDesktop *_desktop = nullptr; @@ -107,6 +108,7 @@ private: MultiMarkerColorPlate *_color_wheel = nullptr; OperationBlocker _blocker; + OperationBlocker _selection_blocker; void generateVisualList(); void layoutColorPicker(std::shared_ptr updated_color = nullptr); diff --git a/src/ui/widget/stroke-style.cpp b/src/ui/widget/stroke-style.cpp index fbfbab6a51..5e12c35954 100644 --- a/src/ui/widget/stroke-style.cpp +++ b/src/ui/widget/stroke-style.cpp @@ -489,6 +489,9 @@ void StrokeStyle::setDesktop(SPDesktop *desktop) desktop->connectDocumentReplaced(sigc::mem_fun(*this, &StrokeStyle::_handleDocumentReplaced)); _handleDocumentReplaced(nullptr, desktop->getDocument()); + for (MarkerComboBox *combo : {startMarkerCombo, midMarkerCombo, endMarkerCombo}) { + combo->setDesktop(desktop); + } updateLine(); } -- GitLab From fcffb0e9d0293b2cb5df70b46defed90e3619500 Mon Sep 17 00:00:00 2001 From: PBS Date: Thu, 9 Oct 2025 23:07:14 +0200 Subject: [PATCH 4/8] Simplify ColorsExtractor/ObjectColorSet usage * Move ColorsExtractor to .cpp file - it doesn't need to be part of the API, only the function collectColors() does. * Remove std::shared_ptr wrapping of ObjectColorSet. * Avoid "using namespace" in .h file. --- src/object-colors.cpp | 43 ++++++-- src/object-colors.h | 48 +++------ src/ui/widget/recolor-art.cpp | 93 +++++++++-------- src/ui/widget/recolor-art.h | 35 ++++--- testfiles/src/object-colors-test.cpp | 143 ++++++++++++--------------- 5 files changed, 173 insertions(+), 189 deletions(-) diff --git a/src/object-colors.cpp b/src/object-colors.cpp index 58a88f12d1..8255152995 100644 --- a/src/object-colors.cpp +++ b/src/object-colors.cpp @@ -211,6 +211,28 @@ std::optional ObjectColorSet::getSelectedNewColor(uint32_t key_color) con return {}; } +namespace { + +class ColorsExtractor +{ +public: + explicit ColorsExtractor(ObjectColorSet &m) + : manager{m} + {} + + void collectColors(std::vector objects, ObjectStyleType type = ObjectStyleType::None); + +private: + ObjectColorSet &manager; + + void extractGradientStops(SPObject *object, bool isFill); + void extractMeshStops(std::vector> &mesh_nodes, SPObject *object, ObjectStyleType type); + void extractObjectColors(SPObject *object, ObjectStyleType type = ObjectStyleType::None); + void extractObjectStyle(SPObject *object, ObjectStyleType type = ObjectStyleType::None); + void extractPatternColors(SPPattern *pattern); + void extractMarkerColors(Glib::ustring const &marker, SPObject *object); +}; + /* * 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 @@ -285,7 +307,7 @@ void ColorsExtractor::extractObjectStyle(SPObject *object, ObjectStyleType type) if (style->fill.isColor()) { auto color = style->fill.getColor(); ObjectStyleType fill_type = type == ObjectStyleType::None ? ObjectStyleType::Fill : type; - manager->populateMap(color, object, fill_type, "fill"); + manager.populateMap(color, object, fill_type, "fill"); } else if (style->fill.isPaintserver()) { // paint server can be pattern or gradient @@ -300,7 +322,7 @@ void ColorsExtractor::extractObjectStyle(SPObject *object, ObjectStyleType type) if (style->stroke.isColor()) { auto color = style->stroke.getColor(); ObjectStyleType stroke_type = type == ObjectStyleType::None ? ObjectStyleType::Stroke : type; - manager->populateMap(color, object,stroke_type, "stroke");//ObjectColorSet + manager.populateMap(color, object,stroke_type, "stroke");//ObjectColorSet } else if (style->stroke.isPaintserver()) { // get gradient stops strokes auto ps = style->getStrokePaintServer(); @@ -343,7 +365,7 @@ void ColorsExtractor::extractGradientStops(SPObject *object, bool isFill) return; } gradient->ensureVector(); - manager->populateStopsMap(gradient->getFirstStop()); + manager.populateStopsMap(gradient->getFirstStop()); } } bool is_swatch = gradient->getVector()->isSwatch(); @@ -357,7 +379,7 @@ void ColorsExtractor::extractGradientStops(SPObject *object, bool isFill) } for (auto stop : gradient->getGradientVector().stops) { if (stop.color.has_value()) { - manager->populateMap(stop.color.value(), object, type, "stop"); + manager.populateMap(stop.color.value(), object, type, "stop"); } } } @@ -371,9 +393,9 @@ void ColorsExtractor::extractMeshStops(std::vector> &m { for (auto const &nodes : mesh_nodes) { for (auto const &node : nodes) { - manager->populateStopsMap(node->stop); + manager.populateStopsMap(node->stop); if (node->color.has_value()) { - manager->populateMap(node->color.value(), item, type , "stop"); + manager.populateMap(node->color.value(), item, type , "stop"); } } } @@ -413,4 +435,13 @@ void ColorsExtractor::extractMarkerColors(Glib::ustring const &marker, SPObject } } +} // namespace + +ObjectColorSet collect_colours(std::vector const &objects, ObjectStyleType type) +{ + ObjectColorSet result; + ColorsExtractor(result).collectColors(objects, type); + return result; +} + } // namespace Inkscape diff --git a/src/object-colors.h b/src/object-colors.h index 4002a67bf8..b52b2c3f3d 100644 --- a/src/object-colors.h +++ b/src/object-colors.h @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /** @file - * this file is for the logic behind recolor-art.cpp/h widget - * extracting colors by the color extractor function populating maps in the object color set + * This file is for the logic behind the RecolorArt widget. */ /* * Authors: @@ -10,25 +9,16 @@ * Copyright (C) 2025 authors */ -#ifndef WIDGET_OBJECT_COLORS_SET_H -#define WIDGET_OBJECT_COLORS_SET_H +#ifndef INKSCAPE_OBJECT_COLORS_H +#define INKSCAPE_OBJECT_COLORS_H -#include "style-internal.h" -#include "object/sp-mesh-array.h" - -namespace Inkscape::Colors { -class Color; -class ColorSet; -namespace Space { -class AnySpace; -} -} // namespace Inkscape::Colors +#include +#include "colors/color.h" +class SPObject; +class SPStop; namespace Inkscape { -namespace Tools { -class ToolBase; -} enum class ObjectStyleType { @@ -83,7 +73,7 @@ public: int getColorIndex(uint32_t key_color); std::vector &getColors() { return colors; } std::optionalgetColor(int index) const; - SelectedColorsMap &getSelectedColorsMap() { return _selected_colors; } + SelectedColorsMap const &getSelectedColorsMap() const { return _selected_colors; } bool applyNewColorToSelection(uint32_t key_color, Colors::Color new_color); private: @@ -93,25 +83,9 @@ private: std::unordered_map color_wheel_colors_map; }; -class ColorsExtractor -{ -public: - ColorsExtractor(std::shared_ptr m) - : manager{std::move(m)} - {} - - void collectColors(std::vector objects, ObjectStyleType type = ObjectStyleType::None); - -private: - std::shared_ptr manager; - void extractGradientStops(SPObject *object, bool isFill); - void extractMeshStops(std::vector> &mesh_nodes, SPObject *object, ObjectStyleType type); - void extractObjectColors(SPObject *object, ObjectStyleType type = ObjectStyleType::None); - void extractObjectStyle(SPObject *object, ObjectStyleType type = ObjectStyleType::None); - void extractPatternColors(SPPattern *pattern); - void extractMarkerColors(Glib::ustring const &marker, SPObject *object); -}; +/// Extract the colors from a list of objects. +ObjectColorSet collect_colours(std::vector const &objects, ObjectStyleType type = ObjectStyleType::None); } // namespace Inkscape -#endif // WIDGET_OBJECT_COLORS_SET_H +#endif // INKSCAPE_OBJECT_COLORS_H diff --git a/src/ui/widget/recolor-art.cpp b/src/ui/widget/recolor-art.cpp index 188a894b4d..c4cd644c09 100644 --- a/src/ui/widget/recolor-art.cpp +++ b/src/ui/widget/recolor-art.cpp @@ -52,12 +52,10 @@ RecolorArt::RecolorArt() // 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) { - if (!_is_preview) { - _manager->convertToRecoloredColors(); - DocumentUndo::done(_desktop->getDocument(), _("changed Item color"), - INKSCAPE_ICON("object-recolor-art")); - } + if (!_is_preview) { + _manager.convertToRecoloredColors(); + DocumentUndo::done(_desktop->getDocument(), _("changed Item color"), + INKSCAPE_ICON("object-recolor-art")); } if (_desktop) { _desktop->setHideSelectionBoxes(false); @@ -81,21 +79,22 @@ RecolorArt::RecolorArt() Color c(cc,true); if(_color_wheel->getActiveIndex() != -1) { int index = _color_wheel->getActiveIndex(); - if (!_manager->getColor(index).has_value()) + if (!_manager.getColor(index)) { return; - _current_color_id = _manager->getColor(index).value().toRGBA(); + } + _current_color_id = _manager.getColor(index)->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()) + if (_manager.isSelectedColorsEmpty()) return; std::vector new_colors = _color_wheel->getColors(); - _manager->setSelectedNewColor(new_colors); + _manager.setSelectedNewColor(new_colors); updateColorModel(new_colors); if (_is_preview) - _manager->convertToRecoloredColors(); + _manager.convertToRecoloredColors(); } } })); @@ -106,10 +105,10 @@ RecolorArt::RecolorArt() Color c(cc,true); if (_color_wheel->getHoverIndex() != -1) { int index = _color_wheel->getHoverIndex(); - if (!_manager->getColor(index).has_value()) { + if (!_manager.getColor(index)) { return; } - _current_color_id = _manager->getColor(index).value().toRGBA(); + _current_color_id = _manager.getColor(index)->toRGBA(); } }); @@ -266,7 +265,7 @@ void RecolorArt::generateVisualList() { _color_model->remove_all(); std::vector> items; - for (auto const &[key, value] : _manager->getSelectedColorsMap()) { + for (auto const &[key, value] : _manager.getSelectedColorsMap()) { auto old_color = value.second.value().old_color; auto new_color = value.second.value().new_color; items.push_back(ColorItem::create(key, old_color, new_color)); @@ -326,7 +325,7 @@ void RecolorArt::setUpTypeBox(Gtk::Box *box, Color color) return; } - auto items = _manager->getSelectedItems(color.toRGBA()); + auto items = _manager.getSelectedItems(color.toRGBA()); if (!items.empty()) { std::string size = "" + std::to_string(items.size()) + ""; std::map> kinds; @@ -399,14 +398,14 @@ void RecolorArt::setUpTypeBox(Gtk::Box *box, Color color) */ void RecolorArt::onOriginalColorClicked(uint32_t color_id) { - if (_color_wheel && !_manager->isColorWheelColorsMapEmpty()) { - int index = _manager->getColorIndex(color_id); + if (_color_wheel && !_manager.isColorWheelColorsMapEmpty()) { + int index = _manager.getColorIndex(color_id); if (index > -1) { - _color_wheel->setActiveIndex(_manager->getColorIndex(color_id)); + _color_wheel->setActiveIndex(_manager.getColorIndex(color_id)); } } _current_color_id = color_id; - if (auto color = _manager->getSelectedNewColor(color_id)) { + if (auto color = _manager.getSelectedNewColor(color_id)) { _solid_colors->set(*color); // 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 @@ -425,7 +424,7 @@ void RecolorArt::lpChecked(Color color, bool wheel) if (!new_color.has_value()) { return; } - if (!_manager->applyNewColorToSelection(_current_color_id, new_color.value())) { + if (!_manager.applyNewColorToSelection(_current_color_id, new_color.value())) { return; } @@ -449,8 +448,8 @@ void RecolorArt::onColorPickerChanged(Color color, bool wheel) return; } std::string _color_string = new_color.value().toString(); - if (_manager->findSelectedColor(_current_color_id)) { - _manager->setSelectedNewColor(_current_color_id, new_color.value()); + if (_manager.findSelectedColor(_current_color_id)) { + _manager.setSelectedNewColor(_current_color_id, new_color.value()); } // apply changes to selected items @@ -461,13 +460,14 @@ void RecolorArt::onColorPickerChanged(Color color, bool wheel) lpChecked(); } guint index = _selection_model->get_selected(); - Glib::RefPtr item = nullptr; + Glib::RefPtr item; // if change is coming from colorlist page sync that to colorwheel page if (!wheel){ - if (!_manager || index < 0) + if (index < 0) { return; + } item = _color_model->get_item(index); - int i = _manager->getColorIndex(_current_color_id); + int i = _manager.getColorIndex(_current_color_id); if (i > -1) { _color_wheel->changeColor(i, new_color.value()); } @@ -504,7 +504,7 @@ void RecolorArt::updateColorModel(std::vector new_colors) if(!color_item) { continue; } - int index = _manager->getColorIndex(color_item->key); + 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); @@ -536,9 +536,9 @@ void RecolorArt::onResetClicked() _color_wheel->toggleHueLock(false); _color_wheel->setLightness(100.0); _color_wheel->setSaturation(100.0); - _color_wheel->setColor(_manager->getColors()); + _color_wheel->setColor(_manager.getColors()); updateColorModel(); - _manager->revertToOriginalColors(true); + _manager.revertToOriginalColors(true); guint index = _selection_model->get_selected(); auto item = _color_model->get_item(index); auto color_item = std::dynamic_pointer_cast(item); @@ -554,9 +554,9 @@ void RecolorArt::onLivePreviewToggled() { _is_preview = _live_preview.property_active(); if (_is_preview) { - _manager->convertToRecoloredColors(); + _manager.convertToRecoloredColors(); } else { - _manager->revertToOriginalColors(); + _manager.revertToOriginalColors(); } } @@ -577,9 +577,8 @@ void RecolorArt::performUpdate() g_warning("Desktop is NULL in Performupdate in recolor widget"); return; } - if (_manager) { - _manager->clearData(); - } + + _manager.clearData(); _color_wheel->toggleHueLock(false); _color_wheel->setLightness(100.0); @@ -588,15 +587,15 @@ void RecolorArt::performUpdate() if (auto selection = _desktop->getSelection()) { auto guard = _selection_blocker.block(); selection->unlinkRecursive(false, true, false); - std::vector vec(selection->items().begin(), selection->items().end()); - _extractor->collectColors(vec); - if (!_manager->isSelectedColorsEmpty()) { + auto vec = std::vector(selection->items().begin(), selection->items().end()); + _manager = collect_colours(vec); + if (!_manager.isSelectedColorsEmpty()) { generateVisualList(); - auto first_button_id = _manager->getFirstKey(); + auto first_button_id = _manager.getFirstKey(); onOriginalColorClicked(first_button_id); } - if (!_manager->isColorsEmpty()) { - _color_wheel->setColor(_manager->getColors()); + if (!_manager.isColorsEmpty()) { + _color_wheel->setColor(_manager.getColors()); } } } @@ -610,23 +609,21 @@ void RecolorArt::performMarkerUpdate(SPMarker *marker) g_warning("Desktop is NULL in Performupdate in recolor widget"); return; } - if (_manager) { - _manager->clearData(); - } + + _manager.clearData(); _color_wheel->toggleHueLock(false); _color_wheel->setLightness(100.0); _color_wheel->setSaturation(100.0); - std::vector vec = {marker}; - _extractor->collectColors(vec); - if (!_manager->isSelectedColorsEmpty()) { + _manager = collect_colours({marker}); + if (!_manager.isSelectedColorsEmpty()) { generateVisualList(); - auto first_button_id = _manager->getFirstKey(); + auto first_button_id = _manager.getFirstKey(); onOriginalColorClicked(first_button_id); } - if (!_manager->isColorsEmpty()) { - _color_wheel->setColor(_manager->getColors()); + if (!_manager.isColorsEmpty()) { + _color_wheel->setColor(_manager.getColors()); } } diff --git a/src/ui/widget/recolor-art.h b/src/ui/widget/recolor-art.h index 60f2afc8a9..69b0a5d3aa 100644 --- a/src/ui/widget/recolor-art.h +++ b/src/ui/widget/recolor-art.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later -#ifndef WIDGET_RECOLOR_ART_H -#define WIDGET_RECOLOR_ART_H +#ifndef INKSCAPE_UI_WIDGET_RECOLOR_ART_H +#define INKSCAPE_UI_WIDGET_RECOLOR_ART_H /* * * Authors: @@ -27,7 +27,7 @@ #include "ui/operation-blocker.h" #include "ui/widget/ink-color-wheel.h" -using namespace Inkscape::Colors; +class SPMarker; namespace Inkscape::Colors { class Color; @@ -56,14 +56,13 @@ namespace Widget { class ColorNotebook; class MultiMarkerColorPlate; - struct ColorItem : public Glib::Object { uint32_t key; - Color old_color = Color(0x00000000); - Color new_color = Color(0x00000000); + Colors::Color old_color{0}; + Colors::Color new_color{0}; - static Glib::RefPtr create(uint32_t k, Color const &old_c, Color const &new_c) + static Glib::RefPtr create(uint32_t k, Colors::Color const &old_c, Colors::Color const &new_c) { auto item = Glib::make_refptr_for_instance(new ColorItem()); item->key = k; @@ -77,11 +76,12 @@ class RecolorArt : public Gtk::Box { public: RecolorArt(); + void performUpdate(); bool isInPreviewMode() const { return _is_preview; } void setDesktop(SPDesktop *desktop); void onResetClicked(); - void performMarkerUpdate(SPMarker* marker); + void performMarkerUpdate(SPMarker *marker); private: SPDesktop *_desktop = nullptr; @@ -89,11 +89,11 @@ private: Gtk::Notebook &_notebook; Gtk::Box &_color_wheel_page; std::shared_ptr _solid_colors = std::make_shared(); - sigc::connection _solid_color_changed ; - Gtk::Box &_color_list ; + sigc::connection _solid_color_changed; + Gtk::Box &_color_list; Gtk::Button &_reset; Gtk::CheckButton &_live_preview; - Inkscape::UI::Widget::ColorNotebook *_color_picker_wdgt = nullptr ; + Inkscape::UI::Widget::ColorNotebook *_color_picker_wdgt = nullptr; Gtk::ListView *_list_view = nullptr; uint32_t _current_color_id; bool _is_preview = true; @@ -102,8 +102,7 @@ private: Glib::RefPtr _color_factory; Glib::RefPtr _selection_model; - std::shared_ptr _manager = std::make_shared();; - std::unique_ptr _extractor = std::make_unique(_manager);; + ObjectColorSet _manager; MultiMarkerColorPlate *_color_wheel = nullptr; @@ -112,14 +111,14 @@ private: void generateVisualList(); void layoutColorPicker(std::shared_ptr updated_color = nullptr); - void colorButtons(Gtk::Box *button, Color color, bool is_original = false); + void colorButtons(Gtk::Box *button, Colors::Color color, bool is_original = false); // signal handlers void onOriginalColorClicked(uint32_t color_id); - void onColorPickerChanged(Color color = Color(0x00000000), bool wheel = false); + void onColorPickerChanged(Colors::Color color = Colors::Color{0}, bool wheel = false); void onLivePreviewToggled(); - void lpChecked(Color color = Color(0x00000000), bool wheel = false); - void setUpTypeBox(Gtk::Box *box, Color color); + void lpChecked(Colors::Color color = Colors::Color(0), bool wheel = false); + void setUpTypeBox(Gtk::Box *box, Colors::Color color); void updateColorModel(std::vector new_colors = {}); std::pair,guint> findColorItemByKey(uint32_t key); }; @@ -128,4 +127,4 @@ private: } // namespace UI } // namespace Inkscape -#endif // WIDGET_RECOLOR_ART_H +#endif // INKSCAPE_UI_WIDGET_RECOLOR_ART_H diff --git a/testfiles/src/object-colors-test.cpp b/testfiles/src/object-colors-test.cpp index 22e28c2d91..55f696af99 100644 --- a/testfiles/src/object-colors-test.cpp +++ b/testfiles/src/object-colors-test.cpp @@ -15,41 +15,28 @@ #include #include "object/sp-defs.h" -#include "object/sp-gradient.h" #include "object/sp-object.h" +#include "object/sp-root.h" #include "object/sp-stop.h" #include "src/colors/color.h" #include "src/document.h" #include "xml/node.h" -#include "xml/repr.h" #include "style.h" using namespace Inkscape; class ObjectColorSetFixture : public DocPerCaseTest { protected: - std::shared_ptr set; - SPDocument *document; - ColorsExtractor extractor; - std::vector Vector; std::vector nodes; + std::vector vector; + + ObjectColorSet set; ObjectColorSetFixture() - : set(std::make_shared()) - , extractor(set) { - DocPerCaseTest::SetUp(); - document = _doc.get(); - Vector = std::vector(6); - nodes = std::vector(6); - setUpDoc(); - extractor.collectColors(Vector); - } + SetUpTestCase(); - void setUpDoc() - { - Inkscape::XML::Document *xml_doc = document->getReprDoc(); - ASSERT_NE(xml_doc, nullptr); + Inkscape::XML::Document *xml_doc = _doc->getReprDoc(); Inkscape::XML::Node *grad_node = xml_doc->createElement("svg:linearGradient"); grad_node->setAttribute("id", "test-gradient"); @@ -67,8 +54,7 @@ protected: grad_node->appendChild(stop2_node); for (int i = 0; i < 6; i++) { - Inkscape::XML::Node *rect = xml_doc->createElement("svg:rect"); - nodes[i] = rect; + nodes.push_back(xml_doc->createElement("svg:rect")); } nodes[0]->setAttribute("fill", "#ffff00ff"); @@ -83,67 +69,65 @@ protected: nodes[4]->setAttribute("stroke", "#ba6cd2ff"); nodes[5]->setAttribute("fill", "url(#test-gradient)"); - document->getDefs()->getRepr()->appendChild(grad_node); + _doc->getDefs()->getRepr()->appendChild(grad_node); for (int i = 0; i < 6; i++) { - document->getDefs()->getRepr()->appendChild(nodes[i]); + _doc->getRoot()->getRepr()->appendChild(nodes[i]); + vector.push_back(_doc->getObjectByRepr(nodes[i])); } - for (int i = 0; i < 6; i++) { - Vector[i] = dynamic_cast(document->getObjectByRepr(nodes[i])); - } + set = collect_colours(vector); + } + + ~ObjectColorSetFixture() + { + TearDownTestCase(); } }; -TEST_F(ObjectColorSetFixture, HandleEmptyObjects) +TEST(ObjectColorSet, HandleEmptyObjects) { - set->clearData(); - std::vector empty_vector; - extractor.collectColors(empty_vector); - EXPECT_TRUE(set->isSelectedColorsEmpty()); + EXPECT_TRUE(collect_colours({}).isSelectedColorsEmpty()); } -TEST_F(ObjectColorSetFixture, HandleNullObjects) +TEST(ObjectColorSet, HandleNullObjects) { - set->clearData(); - std::vector null_vector{nullptr}; - extractor.collectColors(null_vector); - EXPECT_TRUE(set->isSelectedColorsEmpty()); + EXPECT_TRUE(collect_colours({nullptr}).isSelectedColorsEmpty()); } TEST_F(ObjectColorSetFixture, PopulateAndFindColor) { - EXPECT_FALSE(set->isSelectedColorsEmpty()); - EXPECT_FALSE(set->isGradientStopsEmpty()); - EXPECT_EQ(set->getColors().size(), 9); + EXPECT_FALSE(set.isSelectedColorsEmpty()); + EXPECT_FALSE(set.isGradientStopsEmpty()); + EXPECT_EQ(set.getColors().size(), 9); auto key = Colors::Color(0xffff00ff).toRGBA(); - EXPECT_TRUE(set->findSelectedColor(key)); + EXPECT_TRUE(set.findSelectedColor(key)); - EXPECT_EQ(set->getColorIndex(key), 0); - EXPECT_EQ(set->getColor(0).value().toRGBA(), key); + EXPECT_EQ(set.getColorIndex(key), 0); + EXPECT_EQ(set.getColor(0).value().toRGBA(), key); auto false_key = Colors::Color(0x000000ff).toRGBA(); - EXPECT_FALSE(set->findSelectedColor(false_key)); - EXPECT_EQ(set->getColorIndex(false_key), -1); + EXPECT_FALSE(set.findSelectedColor(false_key)); + EXPECT_EQ(set.getColorIndex(false_key), -1); } TEST_F(ObjectColorSetFixture, ClearData) { - EXPECT_EQ(set->getColors().size(), 9); - set->clearData(); - EXPECT_TRUE(set->isSelectedColorsEmpty()); - EXPECT_TRUE(set->isGradientStopsEmpty()); - EXPECT_TRUE(set->isColorWheelColorsMapEmpty()); - EXPECT_TRUE(set->isColorsEmpty()); + EXPECT_EQ(set.getColors().size(), 9); + set.clearData(); + EXPECT_TRUE(set.isSelectedColorsEmpty()); + EXPECT_TRUE(set.isGradientStopsEmpty()); + EXPECT_TRUE(set.isColorWheelColorsMapEmpty()); + EXPECT_TRUE(set.isColorsEmpty()); } TEST_F(ObjectColorSetFixture, SetAndGetSelectedColors) { Colors::Color new_color(0xff00ffff); auto key = Colors::Color(0xffff00ff).toRGBA(); - set->setSelectedNewColor(key, new_color); - EXPECT_EQ(set->getSelectedNewColor(key).value().toRGBA(), new_color.toRGBA()); + set.setSelectedNewColor(key, new_color); + EXPECT_EQ(set.getSelectedNewColor(key).value().toRGBA(), new_color.toRGBA()); } TEST_F(ObjectColorSetFixture, SetSelectedNewColors) @@ -157,9 +141,9 @@ TEST_F(ObjectColorSetFixture, SetSelectedNewColors) Colors::Color(Colors::Space::Type::HSL, {0.33, 1.0, 0.5}), Colors::Color(Colors::Space::Type::HSV, {0.66, 1.0, 1.0}), Colors::Color(Colors::Space::Type::LAB, {60.0, -40.0, 30.0})}; - set->setSelectedNewColor(colors); + set.setSelectedNewColor(colors); std::vector new_colors; - for (auto [key, value] : set->getSelectedColorsMap()) { + for (auto [key, value] : set.getSelectedColorsMap()) { new_colors.push_back(value.second.value().new_color.toRGBA()); } std::vector color{ @@ -176,38 +160,37 @@ TEST_F(ObjectColorSetFixture, SetSelectedNewColors) sort(new_colors.begin(), new_colors.end()); EXPECT_EQ(color, new_colors); - set->convertToRecoloredColors(); - EXPECT_EQ(Vector[0]->style->fill.getColor().toRGBA(), Colors::Color(Colors::Space::Type::CMYK, {0.1, 0.8, 0.0, 0.0}).toRGBA()); + set.convertToRecoloredColors(); + EXPECT_EQ(vector[0]->style->fill.getColor().toRGBA(), Colors::Color(Colors::Space::Type::CMYK, {0.1, 0.8, 0.0, 0.0}).toRGBA()); char const *style1 = nodes[0]->attribute("style"); EXPECT_NE(strstr(style1, "fill:device-cmyk(0.1 0.8 0 0)"), nullptr); // test reseting just object color without reseting it in the map for livepreview purposes - set->revertToOriginalColors(false); - EXPECT_EQ(Vector[0]->style->fill.getColor().toRGBA(), Colors::Color(0xffff00ff).toRGBA()); + set.revertToOriginalColors(false); + EXPECT_EQ(vector[0]->style->fill.getColor().toRGBA(), Colors::Color(0xffff00ff).toRGBA()); char const *style2 = nodes[0]->attribute("style"); EXPECT_NE(strstr(style2, "fill:#ffff00ff"), nullptr); - auto map1 = set->getSelectedColorsMap(); + auto map1 = set.getSelectedColorsMap(); auto value1 = map1[Colors::Color(0xffff00ff).toRGBA()].second.value().new_color.toRGBA(); - EXPECT_NE(value1 , Colors::Color(0xffff00ff).toRGBA()); + EXPECT_NE(value1, Colors::Color(0xffff00ff).toRGBA()); // test with resetting map entry for reset button - set->revertToOriginalColors(true); - auto map2 = set->getSelectedColorsMap(); + set.revertToOriginalColors(true); + auto map2 = set.getSelectedColorsMap(); auto value2 = map2[Colors::Color(0xffff00ff).toRGBA()].second.value().new_color.toRGBA(); - EXPECT_EQ(value2 , Colors::Color(0xffff00ff).toRGBA()); - + EXPECT_EQ(value2, Colors::Color(0xffff00ff).toRGBA()); } TEST_F(ObjectColorSetFixture, ChangeObjectsColors) { - EXPECT_FALSE(set->isSelectedColorsEmpty()); + EXPECT_FALSE(set.isSelectedColorsEmpty()); - EXPECT_FALSE(set->isGradientStopsEmpty()); - EXPECT_EQ(set->getColors().size(), 9); + EXPECT_FALSE(set.isGradientStopsEmpty()); + EXPECT_EQ(set.getColors().size(), 9); - set->applyNewColorToSelection(Colors::Color(0xffff00ff).toRGBA(), Colors::Color(0x7e1a9cff)); - EXPECT_EQ(Vector[0]->style->fill.getColor().toRGBA(), Colors::Color(0x7e1a9cff).toRGBA()); - EXPECT_EQ(Vector[1]->style->stroke.getColor().toRGBA(), Colors::Color(0x7e1a9cff).toRGBA()); + set.applyNewColorToSelection(Colors::Color(0xffff00ff).toRGBA(), Colors::Color(0x7e1a9cff)); + EXPECT_EQ(vector[0]->style->fill.getColor().toRGBA(), Colors::Color(0x7e1a9cff).toRGBA()); + EXPECT_EQ(vector[1]->style->stroke.getColor().toRGBA(), Colors::Color(0x7e1a9cff).toRGBA()); char const *style1 = nodes[0]->attribute("style"); char const *style2 = nodes[1]->attribute("style"); @@ -217,8 +200,8 @@ TEST_F(ObjectColorSetFixture, ChangeObjectsColors) TEST_F(ObjectColorSetFixture, HandleLargeColorSets) { - set->clearData(); - Inkscape::XML::Document *xml_doc = document->getReprDoc(); + set.clearData(); + Inkscape::XML::Document *xml_doc = _doc->getReprDoc(); std::vector large_vector; for (int i = 0; i < 100000; ++i) { @@ -227,23 +210,23 @@ TEST_F(ObjectColorSetFixture, HandleLargeColorSets) snprintf(color_str, sizeof(color_str), "#%06xff", i % 0xFFFFFF); rect->setAttribute("fill", color_str); - document->getDefs()->getRepr()->appendChild(rect); - large_vector.push_back(document->getObjectByRepr(rect)); + _doc->getDefs()->getRepr()->appendChild(rect); + large_vector.push_back(_doc->getObjectByRepr(rect)); } - extractor.collectColors(large_vector); - EXPECT_EQ(set->getColors().size(), 100000); + set = collect_colours(large_vector); + EXPECT_EQ(set.getColors().size(), 100000); } TEST_F(ObjectColorSetFixture, TestColorIndexBoundaryConditions) { - EXPECT_EQ(set->getColor(-1), std::nullopt); - EXPECT_EQ(set->getColor(set->getColors().size()), std::nullopt); - EXPECT_EQ(set->getColorIndex(0x99999999), -1); + EXPECT_EQ(set.getColor(-1), std::nullopt); + EXPECT_EQ(set.getColor(set.getColors().size()), std::nullopt); + EXPECT_EQ(set.getColorIndex(0x99999999), -1); } TEST_F(ObjectColorSetFixture, TestColorApplicationFailure) { auto false_key = Colors::Color(0x99999999).toRGBA(); - EXPECT_FALSE(set->applyNewColorToSelection(false_key, Colors::Color(0xff0000ff))); + EXPECT_FALSE(set.applyNewColorToSelection(false_key, Colors::Color(0xff0000ff))); } -- GitLab From eb1a495570706eebe8dbee57a2a07bb522527022 Mon Sep 17 00:00:00 2001 From: PBS Date: Wed, 15 Oct 2025 00:10:18 +0200 Subject: [PATCH 5/8] Fix after std::ranges change --- src/ui/widget/recolor-art.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/widget/recolor-art.cpp b/src/ui/widget/recolor-art.cpp index c4cd644c09..b7bf212b82 100644 --- a/src/ui/widget/recolor-art.cpp +++ b/src/ui/widget/recolor-art.cpp @@ -587,7 +587,8 @@ void RecolorArt::performUpdate() if (auto selection = _desktop->getSelection()) { auto guard = _selection_blocker.block(); selection->unlinkRecursive(false, true, false); - auto vec = std::vector(selection->items().begin(), selection->items().end()); + auto items = selection->items(); + auto vec = std::vector(items.begin(), items.end()); _manager = collect_colours(vec); if (!_manager.isSelectedColorsEmpty()) { generateVisualList(); -- GitLab From 6038d41a79d0c268708a303750a1ea89bc790458 Mon Sep 17 00:00:00 2001 From: PBS Date: Wed, 15 Oct 2025 00:04:06 +0200 Subject: [PATCH 6/8] Refactor RecolorArtManager static instance * Make methods static if they can be. * Move has_colors_pattern to cpp file. * Make get(), getPopover() return reference. Other more trivial changes: * Revert slight change in logic to ColorWheelBase::_on_motion. * Remove using namespace in headers. Remaining changes are typo fixes and harmless refactoring. --- src/ui/widget/color-page.h | 1 - src/ui/widget/ink-color-wheel.cpp | 113 ++++++++++----------- src/ui/widget/ink-color-wheel.h | 11 +- src/ui/widget/marker-combo-box.cpp | 52 +++++----- src/ui/widget/marker-combo-box.h | 6 +- src/ui/widget/multi-marker-color-plate.cpp | 21 ++-- src/ui/widget/multi-marker-color-plate.h | 42 +++----- src/ui/widget/paint-selector.cpp | 34 +++---- src/ui/widget/paint-selector.h | 3 +- src/ui/widget/recolor-art-manager.cpp | 81 ++++++++------- src/ui/widget/recolor-art-manager.h | 30 +++--- src/ui/widget/recolor-art.cpp | 2 +- 12 files changed, 181 insertions(+), 215 deletions(-) diff --git a/src/ui/widget/color-page.h b/src/ui/widget/color-page.h index 3b5ed08c98..26a28a54a8 100644 --- a/src/ui/widget/color-page.h +++ b/src/ui/widget/color-page.h @@ -24,7 +24,6 @@ #include "ui/widget/color-slider.h" #include "ui/widget/ink-color-wheel.h" - using namespace Inkscape::Colors; namespace Gtk { diff --git a/src/ui/widget/ink-color-wheel.cpp b/src/ui/widget/ink-color-wheel.cpp index 5d19f2304d..0f21882bcd 100644 --- a/src/ui/widget/ink-color-wheel.cpp +++ b/src/ui/widget/ink-color-wheel.cpp @@ -136,14 +136,16 @@ ColorWheelBase::ColorWheelBase(BaseObjectType* cobject, const Glib::RefPtr slot) @@ -760,7 +762,7 @@ bool ColorWheelHSLuv::setColor(Color const &color, /** * 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 + * them if emit is true it calls the color_changed() to emit signal changed then queue redraw the area */ bool MultiMarkerWheel::setColor(Color const &color, bool /*overrideHue*/, bool emit) { @@ -820,7 +822,7 @@ void MultiMarkerWheel::_draw_marker(Cairo::RefPtr const &cr, Col 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); + _draw_line_to_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; @@ -888,35 +890,38 @@ int MultiMarkerWheel::_get_marker_index(Geom::Point const &p) */ 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); + if (!_hue_lock) { + return; + } + + std::vector delta_angles; + auto active_hue = _values_vector[_active_index][0]; + for (int i = 0; i < _values_vector.size(); i++) { + if (i == _active_index) { + delta_angles.push_back(0.0); + continue; } - _relative_hue_angles = delat_angles; + 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; + delta_angles.push_back(delta_hue); } + + _relative_hue_angles = delta_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 + * function to draw line to the begining of the marker by calculating the distance from the wheel center + * to marker center + * normalize the differences 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) +void MultiMarkerWheel::_draw_line_to_marker(Cairo::RefPtr const &cr, double mx, double my, double cx, + double cy, Colors::Color const &value, int index) { auto const &[r_min, r_max] = get_radii(); @@ -924,8 +929,7 @@ void MultiMarkerWheel::_draw_line_2_marker(Cairo::RefPtr const & double dy = my-cy; double dx = mx-cx; double len = std::sqrt(dx*dx+dy*dy); - if(len > 1e-5) - { + if (len > 1e-5) { dx /= len; dy /= len; } @@ -935,23 +939,22 @@ void MultiMarkerWheel::_draw_line_2_marker(Cairo::RefPtr const & 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) + 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 && !_hue_lock) { cr->set_dash(focus_dash, 0); cr->set_line_width(1.0); - } - else if(!_hue_lock) - { + } else if (!_hue_lock) { auto const dash = std::vector{3.0}; // wider dashes for focused line cr->set_dash(dash,0); cr->set_line_width(2.0); } else { cr->set_dash(std::valarray(), 0); - if (index == _active_index) + if (index == _active_index) { cr->set_line_width(3.0); + } } cr->stroke(); cr->restore(); @@ -1013,24 +1016,18 @@ void MultiMarkerWheel::update_wheel_source() * 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) +bool MultiMarkerWheel::changeColor(int index, Colors::Color const &color) { - if(index >=0 && index < _values_vector.size()) - { - // std::cout<<" index : "<= _values_vector.size()) { + return false; } - // std::cout<<"changed color went bad\n"; + + if (_values_vector[index].set(color, true)) { + _markers_points[index].reset(); + color_changed(); + return true; + } + return false; } @@ -1063,7 +1060,7 @@ void MultiMarkerWheel::setLightness(double value) /** * set saturation for all colors in the wheel when hue lock is on - * if it is off just changed saturation for the active color + * if it is off just change saturation for the active color */ void MultiMarkerWheel::setSaturation(double value) { @@ -1078,14 +1075,12 @@ void MultiMarkerWheel::setSaturation(double value) color_changed(); } else { int index = getActiveIndex(); - if(index>-1) - { - _values_vector[index].set(1,saturation); + 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) @@ -1193,8 +1188,8 @@ bool MultiMarkerWheel::_is_in_wheel(double x, double y) /** * used to update colors shen markers pressed or moves by - * calculating the angel of the line from the point to the wheel center to calculate the new hue value - * if angel less than 0 normalize it then flip it to follow the mouse movement by subtracting it from 1 + * calculating the angle of the line from the point to the wheel center to calculate the new hue value + * if angle less than 0 normalize it then flip it to follow the mouse movement by subtracting it from 1 * calculate the distance from the point to the center of the wheel to get the saturation value * then set them in the _values_vector[index] reset its old marker and emit color changed signal */ @@ -1419,7 +1414,7 @@ Geom::Point MultiMarkerWheel::get_marker_point(int index) double angle = (1.0 - hue) * 2 * M_PI; _markers_points[index].emplace(); auto &[mx, my] = *_markers_points[index]; - mx = cx + r_max * saturation * cos(angle); // polar cooordinates to cartisian coordinates calculation + mx = cx + r_max * saturation * cos(angle); // polar cooordinates to cartesian coordinates calculation my = cy + r_max * saturation * sin(angle); return *_markers_points[index]; } diff --git a/src/ui/widget/ink-color-wheel.h b/src/ui/widget/ink-color-wheel.h index 97b5f2f2b2..afba4be37a 100644 --- a/src/ui/widget/ink-color-wheel.h +++ b/src/ui/widget/ink-color-wheel.h @@ -266,11 +266,11 @@ public: 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)); } + bool changeColor(int index, Colors::Color const &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 ;} + std::vector const &getColors() const { return _values_vector; } void setLightness(double value); void setSaturation(double value); void redrawOnHueLocked(){queue_drawing_area_draw();} @@ -280,14 +280,13 @@ private: 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 _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_line_to_marker(Cairo::RefPtr const &cr, double mx, double my, double cx, double cy, Colors::Color const &value , int index); void _draw_marker(Cairo::RefPtr const &cr,Colors::Color value , int index); int _get_marker_index(Geom::Point const &p); void _update_hue_lock_positions(); - enum class DragMode { NONE, diff --git a/src/ui/widget/marker-combo-box.cpp b/src/ui/widget/marker-combo-box.cpp index 12568f96f4..e5ad229ab6 100644 --- a/src/ui/widget/marker-combo-box.cpp +++ b/src/ui/widget/marker-combo-box.cpp @@ -16,7 +16,6 @@ #include "marker-combo-box.h" #include -#include #include #include #include @@ -160,7 +159,7 @@ MarkerComboBox::MarkerComboBox(Glib::ustring id, int l) : _orient_angle(get_widget(_builder, "orient-angle")), _orient_flip_horz(get_widget(_builder, "btn-horz-flip")), _edit_marker(get_widget(_builder, "edit-marker")), - _recolorButtonTrigger_1(Gtk::make_managed()) + _recolorButtonTrigger(Gtk::make_managed()) { set_name("MarkerComboBox"); @@ -347,34 +346,35 @@ MarkerComboBox::MarkerComboBox(Glib::ustring id, int l) : update_scale_link(); update_menu_btn(); - _recolorButtonTrigger_1->set_label("Recolor Marker"); - _recolorButtonTrigger_1->set_hexpand(true); - _recolorButtonTrigger_1->set_vexpand(false); - _recolorButtonTrigger_1->set_size_request(180); - _recolorButtonTrigger_1->set_halign(Gtk::Align::FILL); - _recolorButtonTrigger_1->set_valign(Gtk::Align::START); - _recolorButtonTrigger_1->set_margin_top(8); + _recolorButtonTrigger->set_label("Recolor Marker"); + _recolorButtonTrigger->set_hexpand(true); + _recolorButtonTrigger->set_vexpand(false); + _recolorButtonTrigger->set_size_request(180); + _recolorButtonTrigger->set_halign(Gtk::Align::FILL); + _recolorButtonTrigger->set_valign(Gtk::Align::START); + _recolorButtonTrigger->set_margin_top(8); - _grid.add_full_row(_recolorButtonTrigger_1); - _recolorButtonTrigger_1->signal_clicked().connect([this]() { + _grid.add_full_row(_recolorButtonTrigger); + _recolorButtonTrigger->signal_clicked().connect([this] { if (!_recolorManager) { // Lazy-load the recolour widget and popover. - _recolorManager = RecolorArtManager::getRecolorWidgetInstance().get(); - if (_recolorManager->getPopOver()->get_parent()) - _recolorManager->getPopOver()->unparent(); - _recolorManager->getPopOver()->set_parent(*_recolorButtonTrigger_1); + _recolorManager = &RecolorArtManager::get(); + if (_recolorManager->getPopOver().get_parent()) { + _recolorManager->getPopOver().unparent(); + } + _recolorManager->getPopOver().set_parent(*_recolorButtonTrigger); _recolorManager->setDesktop(_desktop); - } else if (_recolorManager->getPopOver()->get_parent() != _recolorButtonTrigger_1) { + } else if (_recolorManager->getPopOver().get_parent() != _recolorButtonTrigger) { // Reparent the popover to this button if necessary. - _recolorManager->getPopOver()->unparent(); - _recolorManager->getPopOver()->set_parent(*_recolorButtonTrigger_1); + _recolorManager->getPopOver().unparent(); + _recolorManager->getPopOver().set_parent(*_recolorButtonTrigger); } - _recolorManager->getPopOver()->popup(); + _recolorManager->getPopOver().popup(); _recolorManager->performMarkerUpdate(get_current()); }); - _recolorButtonTrigger_1->hide(); + _recolorButtonTrigger->hide(); set_visible(true); } @@ -409,10 +409,10 @@ void MarkerComboBox::update_widgets_from_marker(SPMarker* marker) { _orient_angle.set_active(); _angle_btn.set_sensitive(true); } - _recolorButtonTrigger_1->show(); + _recolorButtonTrigger->show(); } else - _recolorButtonTrigger_1->hide(); + _recolorButtonTrigger->hide(); } void MarkerComboBox::update_scale_link() { @@ -513,11 +513,12 @@ Glib::RefPtr MarkerComboBox::get_active() { } void MarkerComboBox::setDesktop(SPDesktop *desktop) { - if (_desktop == desktop) + if (_desktop == desktop) { return; + } - if (_recolorManager && _recolorManager->getPopOver()) { - _recolorManager->getPopOver()->popdown(); + if (_recolorManager) { + _recolorManager->getPopOver().popdown(); } _desktop = desktop; @@ -824,7 +825,6 @@ sigc::connection MarkerComboBox::connect_edit(sigc::slot slot) void MarkerComboBox::set_flat(bool flat) { set_always_show_arrow(!flat); - } } // namespace Inkscape::UI::Widget diff --git a/src/ui/widget/marker-combo-box.h b/src/ui/widget/marker-combo-box.h index b014791fec..cadb5c5874 100644 --- a/src/ui/widget/marker-combo-box.h +++ b/src/ui/widget/marker-combo-box.h @@ -133,8 +133,10 @@ private: UI::Widget::RecolorArtManager* _recolorManager = nullptr; SPDesktop *_desktop = nullptr; sigc::scoped_connection _selection_changed_connection; - Gtk::Button *_recolorButtonTrigger_1 = nullptr; - class MarkerColumns : public Gtk::TreeModel::ColumnRecord { + Gtk::Button *_recolorButtonTrigger = nullptr; + + class MarkerColumns : public Gtk::TreeModel::ColumnRecord + { public: Gtk::TreeModelColumn label; Gtk::TreeModelColumn marker; // ustring doesn't work here on windows due to unicode diff --git a/src/ui/widget/multi-marker-color-plate.cpp b/src/ui/widget/multi-marker-color-plate.cpp index 216f0449b5..922c4e3504 100644 --- a/src/ui/widget/multi-marker-color-plate.cpp +++ b/src/ui/widget/multi-marker-color-plate.cpp @@ -1,13 +1,12 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "multi-marker-color-plate.h" -#include +#include #include #include #include #include - #include "colors/spaces/base.h" #include "colors/spaces/enum.h" #include "ui/icon-names.h" @@ -17,14 +16,12 @@ #include "ui/widget/icon-combobox.h" #include "ui/widget/ink-spin-button.h" -namespace Inkscape { -namespace UI { -namespace Widget { +namespace Inkscape::UI::Widget { -MultiMarkerColorPlate::MultiMarkerColorPlate(std::shared_ptr colors) +MultiMarkerColorPlate::MultiMarkerColorPlate(Colors::ColorSet const &colors) : Gtk::Box(Gtk::Orientation::VERTICAL) , _specific_colors(std::make_shared(manager->find(Space::Type::HSL), - colors->getAlphaConstraint().value_or(true))) + colors.getAlphaConstraint().value_or(true))) , _color_wheel(Gtk::make_managed()) , _hue_lock(*Gtk::make_managed()) , _lightness_bar(*Gtk::make_managed(adjustment, Gtk::Orientation::HORIZONTAL)) @@ -77,7 +74,6 @@ MultiMarkerColorPlate::MultiMarkerColorPlate(std::shared_ptr c _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); @@ -107,7 +103,6 @@ MultiMarkerColorPlate::MultiMarkerColorPlate(std::shared_ptr c _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); @@ -119,7 +114,7 @@ MultiMarkerColorPlate::MultiMarkerColorPlate(std::shared_ptr c image->set_from_icon_name(INKSCAPE_ICON("reset-settings")); _reset->set_child(*image); _reset->set_margin_top(8); - _reset->signal_clicked().connect([this]() { + _reset->signal_clicked().connect([this] { if (_ra) { _ra->onResetClicked(); } @@ -139,8 +134,6 @@ MultiMarkerColorPlate::MultiMarkerColorPlate(std::shared_ptr c _spaces_stack->set_visible_child("RGB"); - - append(*box); append(*_color_wheel); append(*_lightness_box); @@ -182,6 +175,4 @@ void MultiMarkerColorPlate::_createSlidersForSpace(std::shared_ptradd(*_grid, mode_name, mode_name); } -} // namespace Widget -} // namespace UI -} // namespace Inkscape +} // namespace Inkscape::UI::Widget diff --git a/src/ui/widget/multi-marker-color-plate.h b/src/ui/widget/multi-marker-color-plate.h index bbc367bd8c..2458a9b26b 100644 --- a/src/ui/widget/multi-marker-color-plate.h +++ b/src/ui/widget/multi-marker-color-plate.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later -#ifndef WIDGET_MULTI_MARKER_COLOR_PLATE_H -#define WIDGET_MULTI_MARKER_COLOR_PLATE_H +#ifndef INKSCAPE_UI_WIDGET_MULTI_MARKER_COLOR_PLATE_H +#define INKSCAPE_UI_WIDGET_MULTI_MARKER_COLOR_PLATE_H #include #include @@ -16,16 +16,12 @@ #include "src/colors/color-set.h" #include "src/colors/manager.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 "recolor-art.h" - -using namespace Inkscape::Colors; - namespace Inkscape::Colors { class Color; class ColorSet; @@ -34,24 +30,13 @@ class AnySpace; } } // namespace Inkscape::Colors -namespace Gtk { -class Widget; -} // namespace Gtk - -class SPDesktop; - -namespace Inkscape { -namespace UI { -namespace Tools { -class ToolBase; -} - -namespace Widget { +namespace Inkscape::UI::Widget { class MultiMarkerColorPlate : public Gtk::Box { public: - MultiMarkerColorPlate(std::shared_ptr colors); + explicit MultiMarkerColorPlate(Colors::ColorSet const &colors); + void setColor(std::vector &colors) { _color_wheel->setColor(colors); } void setLightness(double value) { @@ -83,12 +68,12 @@ public: private: MultiMarkerWheel *_color_wheel = nullptr; - Gtk::Image*_lightness_icon = Gtk::make_managed(); + Gtk::Image *_lightness_icon = Gtk::make_managed(); 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::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; @@ -103,17 +88,16 @@ private: IconComboBox *_spaces_combo = nullptr; Gtk::Stack *_spaces_stack = nullptr; Gtk::StackSwitcher *_switcher = nullptr; - RecolorArt* _ra = nullptr; - Gtk::Button* _reset = nullptr; + RecolorArt *_ra = nullptr; + Gtk::Button *_reset = nullptr; std::map>> _color_sets; - sigc::scoped_connection _specific_colors_changed ; + sigc::scoped_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 +} // namespace Inkscape::UI::Widget -#endif // WIDGET_MULTI_MARKER_COLOR_PLATE_H +#endif // INKSCAPE_UI_WIDGET_MULTI_MARKER_COLOR_PLATE_H diff --git a/src/ui/widget/paint-selector.cpp b/src/ui/widget/paint-selector.cpp index b474ebb182..a8ca584623 100644 --- a/src/ui/widget/paint-selector.cpp +++ b/src/ui/widget/paint-selector.cpp @@ -33,8 +33,6 @@ #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 "pattern-manipulation.h" @@ -46,7 +44,6 @@ #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 "ui/widget/recolor-art-manager.h" #include "widgets/widget-sizes.h" #include "recolor-art-manager.h" @@ -218,20 +215,19 @@ PaintSelector::PaintSelector(FillOrStroke kind, std::shared_ptrgetPopOver()->get_parent()) - _recolorManager->getPopOver()->unparent(); - _recolorManager->getPopOver()->set_parent(*b); + _recolorManager = &RecolorArtManager::get(); + if (_recolorManager->getPopOver().get_parent()) { + _recolorManager->getPopOver().unparent(); + } + _recolorManager->getPopOver().set_parent(*b); _recolorManager->setDesktop(_desktop); - - } else if (_recolorManager->getPopOver()->get_parent() != b) { + } else if (_recolorManager->getPopOver().get_parent() != b) { // Reparent the popover to this button if necessary. - _recolorManager->getPopOver()->unparent(); - _recolorManager->getPopOver()->set_parent(*b); + _recolorManager->getPopOver().unparent(); + _recolorManager->getPopOver().set_parent(*b); } - _recolorManager->getPopOver()->popup(); + _recolorManager->getPopOver().popup(); _recolorManager->performUpdate(); - }); } @@ -242,8 +238,8 @@ void PaintSelector::setDesktop(SPDesktop *desktop) { if (_desktop == desktop) return; - if (_recolorManager && _recolorManager->getPopOver()) { - _recolorManager->getPopOver()->popdown(); + if (_recolorManager) { + _recolorManager->getPopOver().popdown(); } if (_selection_changed_connection) { @@ -302,7 +298,7 @@ void PaintSelector::fillrule_toggled(FillRuleRadioButton *tb) void PaintSelector::setMode(Mode mode) { - if ((_recolorManager->getPopOver() && _recolorManager->getPopOver()->get_visible()) && checkSelection(_desktop->getSelection())) { + if (_recolorManager && _recolorManager->getPopOver().get_visible() && checkSelection(_desktop->getSelection())) { return; } set_mode_ex(mode, false); @@ -1242,7 +1238,7 @@ void PaintSelector::onSelectionChanged(Inkscape::Selection *selection) hideAllExcept(); } - if (_recolorManager->getPopOver() && _recolorManager->getPopOver()->get_visible() && checkSelection(selection)) { + if (_recolorManager && _recolorManager->getPopOver().get_visible() && checkSelection(selection)) { auto guard = _blocker.block(); _recolorManager->performUpdate(); } @@ -1250,8 +1246,8 @@ void PaintSelector::onSelectionChanged(Inkscape::Selection *selection) bool PaintSelector::checkSelection(Inkscape::Selection *selection) { - return (_mode == MODE_GRADIENT_MESH && (selection->size() > 1 || _recolorManager->checkMeshObject(selection))) || - _recolorManager->checkSelection(selection); + return (_mode == MODE_GRADIENT_MESH && (selection->size() > 1 || RecolorArtManager::checkMeshObject(selection))) || + RecolorArtManager::checkSelection(selection); } void PaintSelector::hideAllExcept(Gtk::Button *recolorButtonTrigger) diff --git a/src/ui/widget/paint-selector.h b/src/ui/widget/paint-selector.h index 4d90584ea1..b17b44e273 100644 --- a/src/ui/widget/paint-selector.h +++ b/src/ui/widget/paint-selector.h @@ -102,8 +102,7 @@ class PaintSelector : public Gtk::Box { SwatchSelector *_selector_swatch = nullptr; PatternEditor* _selector_pattern = nullptr; - - UI::Widget::RecolorArtManager* _recolorManager; + UI::Widget::RecolorArtManager *_recolorManager = nullptr; std::array, 5> _recolorButtonTrigger; OperationBlocker _blocker; SPDesktop *_desktop = nullptr; diff --git a/src/ui/widget/recolor-art-manager.cpp b/src/ui/widget/recolor-art-manager.cpp index 03ce142fa0..dfb2c45dbc 100644 --- a/src/ui/widget/recolor-art-manager.cpp +++ b/src/ui/widget/recolor-art-manager.cpp @@ -1,49 +1,23 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * * Authors: * Fatma Omara * * Copyright (C) 2025 authors - * */ #include "recolor-art-manager.h" +#include "object/sp-gradient.h" #include "object/sp-mask.h" -#include "object/sp-mesh-gradient.h" #include "object/sp-pattern.h" #include "object/sp-use.h" #include "style.h" namespace Inkscape::UI::Widget { +namespace { -std::unique_ptr &RecolorArtManager::getRecolorWidgetInstance() -{ - if (!_recolorPopOver) { - _recolor_widget = std::make_unique(); - _recolorPopOver = std::make_unique(); - _recolorPopOver->set_autohide(false); - _recolorPopOver->set_position(Gtk::PositionType::LEFT); - _recolorPopOver->set_child(*_recolor_widget); - _recolor_manager = std::make_unique(); - } - return _recolor_manager; -} -bool RecolorArtManager::checkSelection(Inkscape::Selection *selection) -{ - auto group = cast(selection->single()); - auto use_group = cast(selection->single()); - auto item = cast(selection->single()); - bool pattern_colors = false; - SPMask *mask = nullptr; - if (item) { - mask = cast(item->getMaskObject()); - pattern_colors = has_colors_pattern(item); - } - return selection->size() > 1 || group || use_group || mask || pattern_colors; -} -bool RecolorArtManager::has_colors_pattern(SPItem *item) +bool has_colors_pattern(SPItem const *item) { std::set colors; SPPattern *patternstroke = nullptr; @@ -92,6 +66,35 @@ bool RecolorArtManager::has_colors_pattern(SPItem *item) return colors.size() > 1; } +} // namespace + +RecolorArtManager &RecolorArtManager::get() +{ + static RecolorArtManager instance; + return instance; +} + +RecolorArtManager::RecolorArtManager() +{ + _recolorPopOver.set_autohide(false); + _recolorPopOver.set_position(Gtk::PositionType::LEFT); + _recolorPopOver.set_child(_recolor_widget); +} + +bool RecolorArtManager::checkSelection(Inkscape::Selection *selection) +{ + auto group = cast(selection->single()); + auto use_group = cast(selection->single()); + auto item = cast(selection->single()); + bool pattern_colors = false; + SPMask *mask = nullptr; + if (item) { + mask = cast(item->getMaskObject()); + pattern_colors = has_colors_pattern(item); + } + return selection->size() > 1 || group || use_group || mask || pattern_colors; +} + bool RecolorArtManager::checkMeshObject(Inkscape::Selection *selection) { if (selection->items().empty()) { @@ -100,31 +103,27 @@ bool RecolorArtManager::checkMeshObject(Inkscape::Selection *selection) auto fill_gradient = cast(selection->single()->style->getFillPaintServer()); auto stroke_gradient = cast(selection->single()->style->getStrokePaintServer()); SPGradient *gradient = fill_gradient ? cast(fill_gradient) : cast(stroke_gradient); - if (gradient && gradient->hasPatches()) { - return true; - } - return false; + return gradient && gradient->hasPatches(); } + void RecolorArtManager::setDesktop(SPDesktop *desktop) { - if (_recolor_widget) - _recolor_widget->setDesktop(desktop); + _recolor_widget.setDesktop(desktop); } + void RecolorArtManager::performUpdate() { - if (_recolor_widget) - _recolor_widget->performUpdate(); + _recolor_widget.performUpdate(); } void RecolorArtManager::performMarkerUpdate(SPMarker *marker) { - if (_recolor_widget) - _recolor_widget->performMarkerUpdate(marker); + _recolor_widget.performMarkerUpdate(marker); } -std::unique_ptr &RecolorArtManager::getPopOver() const +Gtk::Popover &RecolorArtManager::getPopOver() { return _recolorPopOver; } -} // namespace Inkscape::UI::Widget \ No newline at end of file +} // namespace Inkscape::UI::Widget diff --git a/src/ui/widget/recolor-art-manager.h b/src/ui/widget/recolor-art-manager.h index 777c7f26ea..be30750e56 100644 --- a/src/ui/widget/recolor-art-manager.h +++ b/src/ui/widget/recolor-art-manager.h @@ -1,40 +1,42 @@ // SPDX-License-Identifier: GPL-2.0-or-later -#ifndef RECOLOR_ART_MANAGER_H -#define RECOLOR_ART_MANAGER_H +#ifndef INKSCAPE_UI_WIDGET_RECOLOR_ART_MANAGER_H +#define INKSCAPE_UI_WIDGET_RECOLOR_ART_MANAGER_H /* - * * Authors: * Fatma Omara * * Copyright (C) 2025 authors - * */ + #include #include "ui/widget/recolor-art.h" #include "selection.h" - namespace Inkscape::UI::Widget { class RecolorArtManager { public: - static std::unique_ptr &getRecolorWidgetInstance(); - std::unique_ptr &getPopOver() const; - bool checkSelection(Inkscape::Selection *selection); - bool checkMeshObject(Inkscape::Selection *selection); + static RecolorArtManager &get(); + + Gtk::Popover &getPopOver(); + + static bool checkSelection(Inkscape::Selection *selection); + static bool checkMeshObject(Inkscape::Selection *selection); + void setDesktop(SPDesktop *desktop); void performUpdate(); void performMarkerUpdate(SPMarker *marker); private: - inline static std::unique_ptr _recolor_widget = nullptr; - inline static std::unique_ptr _recolorPopOver = nullptr; - inline static std::unique_ptr _recolor_manager = nullptr; - bool has_colors_pattern(SPItem *item); + RecolorArtManager(); + + RecolorArt _recolor_widget; + Gtk::Popover _recolorPopOver; }; } // namespace Inkscape::UI::Widget -#endif // RECOLOR_ART_MANAGER_H \ No newline at end of file + +#endif // INKSCAPE_UI_WIDGET_RECOLOR_ART_MANAGER_H diff --git a/src/ui/widget/recolor-art.cpp b/src/ui/widget/recolor-art.cpp index b7bf212b82..88f6a4106c 100644 --- a/src/ui/widget/recolor-art.cpp +++ b/src/ui/widget/recolor-art.cpp @@ -41,7 +41,7 @@ RecolorArt::RecolorArt() : _builder{create_builder("widget-recolor.ui")} , _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_wheel(Gtk::make_managed(Colors::ColorSet{})) , _color_list(*_builder->get_widget("colors-list")) , _reset(*_builder->get_widget("reset")) , _live_preview(*_builder->get_widget("liveP")) -- GitLab From 8c54e9ffa926e2ccedb3e285deb00f65e10b1c24 Mon Sep 17 00:00:00 2001 From: PBS Date: Sat, 25 Oct 2025 01:13:32 +0200 Subject: [PATCH 7/8] Fix popover and lifetime issues Fixes a crash on exit, and separately, GTK popover warnings on exit. Allows dismissing the popover by clicking the recolor button again. * Use Gtk::MenuButton instead of Gtk::Button. Fixes GTK warnings on exit. * Only change Recolor widget's desktop on showing it. * Revert logic change to PaintSelector::setMode(). * Avoid string keys in has_colors_pattern, and quit quickly as soon as >= 2 colours found. * Make RecolorArtManager's widget and popover public, as it has no invariants to protect. * Repurpose performUpdate() -> showForSelection(). This function now tells the RecolorArt widget to stick to the desktop's selection, updating with it. * Repurpose performMarkerUpdate() -> showForObject(). Allow it to work for any object, not just markers. * Make RecolorArt responsible for disconnecting from desktop on destruction, rather than the containing widget. --- src/ui/widget/marker-combo-box.cpp | 45 +++----- src/ui/widget/marker-combo-box.h | 3 +- src/ui/widget/paint-selector.cpp | 92 ++++----------- src/ui/widget/paint-selector.h | 8 +- src/ui/widget/recolor-art-manager.cpp | 157 ++++++++++++++------------ src/ui/widget/recolor-art-manager.h | 14 +-- src/ui/widget/recolor-art.cpp | 84 ++++++++------ src/ui/widget/recolor-art.h | 33 ++---- 8 files changed, 192 insertions(+), 244 deletions(-) diff --git a/src/ui/widget/marker-combo-box.cpp b/src/ui/widget/marker-combo-box.cpp index e5ad229ab6..3806f36eda 100644 --- a/src/ui/widget/marker-combo-box.cpp +++ b/src/ui/widget/marker-combo-box.cpp @@ -159,7 +159,7 @@ MarkerComboBox::MarkerComboBox(Glib::ustring id, int l) : _orient_angle(get_widget(_builder, "orient-angle")), _orient_flip_horz(get_widget(_builder, "btn-horz-flip")), _edit_marker(get_widget(_builder, "edit-marker")), - _recolorButtonTrigger(Gtk::make_managed()) + _recolorButtonTrigger(Gtk::make_managed()) { set_name("MarkerComboBox"); @@ -346,36 +346,22 @@ MarkerComboBox::MarkerComboBox(Glib::ustring id, int l) : update_scale_link(); update_menu_btn(); - _recolorButtonTrigger->set_label("Recolor Marker"); + _recolorButtonTrigger->set_label(_("Recolor Marker")); _recolorButtonTrigger->set_hexpand(true); _recolorButtonTrigger->set_vexpand(false); _recolorButtonTrigger->set_size_request(180); _recolorButtonTrigger->set_halign(Gtk::Align::FILL); _recolorButtonTrigger->set_valign(Gtk::Align::START); _recolorButtonTrigger->set_margin_top(8); - + _recolorButtonTrigger->set_direction(Gtk::ArrowType::NONE); + _recolorButtonTrigger->set_visible(false); _grid.add_full_row(_recolorButtonTrigger); - _recolorButtonTrigger->signal_clicked().connect([this] { - if (!_recolorManager) { - // Lazy-load the recolour widget and popover. - _recolorManager = &RecolorArtManager::get(); - if (_recolorManager->getPopOver().get_parent()) { - _recolorManager->getPopOver().unparent(); - } - _recolorManager->getPopOver().set_parent(*_recolorButtonTrigger); - _recolorManager->setDesktop(_desktop); - } else if (_recolorManager->getPopOver().get_parent() != _recolorButtonTrigger) { - // Reparent the popover to this button if necessary. - _recolorManager->getPopOver().unparent(); - _recolorManager->getPopOver().set_parent(*_recolorButtonTrigger); - } - _recolorManager->getPopOver().popup(); - _recolorManager->performMarkerUpdate(get_current()); + _recolorButtonTrigger->set_create_popup_func([this] { + auto &mgr = RecolorArtManager::get(); + mgr.reparentPopoverTo(*_recolorButtonTrigger); + mgr.widget.showForObject(_desktop, get_current()); }); - - _recolorButtonTrigger->hide(); - set_visible(true); } void MarkerComboBox::update_widgets_from_marker(SPMarker* marker) { @@ -409,10 +395,9 @@ void MarkerComboBox::update_widgets_from_marker(SPMarker* marker) { _orient_angle.set_active(); _angle_btn.set_sensitive(true); } - _recolorButtonTrigger->show(); + } - } else - _recolorButtonTrigger->hide(); + _recolorButtonTrigger->set_visible(marker); } void MarkerComboBox::update_scale_link() { @@ -511,22 +496,18 @@ Glib::RefPtr MarkerComboBox::get_active() { return empty; } } + void MarkerComboBox::setDesktop(SPDesktop *desktop) { if (_desktop == desktop) { return; } - if (_recolorManager) { - _recolorManager->getPopOver().popdown(); - } + RecolorArtManager::get().popover.popdown(); _desktop = desktop; - - if (_recolorManager) { - _recolorManager->setDesktop(_desktop); - } } + void MarkerComboBox::setDocument(SPDocument *document) { if (_document != document) { diff --git a/src/ui/widget/marker-combo-box.h b/src/ui/widget/marker-combo-box.h index cadb5c5874..1493578b9e 100644 --- a/src/ui/widget/marker-combo-box.h +++ b/src/ui/widget/marker-combo-box.h @@ -130,10 +130,9 @@ private: WidgetGroup _widgets; Gtk::CellRendererPixbuf _image_renderer; - UI::Widget::RecolorArtManager* _recolorManager = nullptr; SPDesktop *_desktop = nullptr; sigc::scoped_connection _selection_changed_connection; - Gtk::Button *_recolorButtonTrigger = nullptr; + Gtk::MenuButton *_recolorButtonTrigger = nullptr; class MarkerColumns : public Gtk::TreeModel::ColumnRecord { diff --git a/src/ui/widget/paint-selector.cpp b/src/ui/widget/paint-selector.cpp index a8ca584623..93e9314724 100644 --- a/src/ui/widget/paint-selector.cpp +++ b/src/ui/widget/paint-selector.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "desktop-style.h" #include "desktop.h" @@ -128,7 +129,7 @@ PaintSelector::PaintSelector(FillOrStroke kind, std::shared_ptr(-1); // huh? do you mean 0xff? -- I think this means "not in the enum" for (int i = 0; i < 5; i++) { - _recolorButtonTrigger[i] = std::make_unique(); + _recolorButtonTrigger[i] = std::make_unique(); } /* Paint style button box */ @@ -209,25 +210,13 @@ PaintSelector::PaintSelector(FillOrStroke kind, std::shared_ptrset_halign(Gtk::Align::CENTER); b->set_valign(Gtk::Align::START); b->set_margin_top(8); + b->set_direction(Gtk::ArrowType::NONE); b->set_visible(false); - b->signal_clicked().connect([b = b.get(), this] { - auto guard = _blocker.block(); - if (!_recolorManager) { - // Lazy-load the recolour widget and popover. - _recolorManager = &RecolorArtManager::get(); - if (_recolorManager->getPopOver().get_parent()) { - _recolorManager->getPopOver().unparent(); - } - _recolorManager->getPopOver().set_parent(*b); - _recolorManager->setDesktop(_desktop); - } else if (_recolorManager->getPopOver().get_parent() != b) { - // Reparent the popover to this button if necessary. - _recolorManager->getPopOver().unparent(); - _recolorManager->getPopOver().set_parent(*b); - } - _recolorManager->getPopOver().popup(); - _recolorManager->performUpdate(); + b->set_create_popup_func([b = b.get(), this] { + auto &mgr = RecolorArtManager::get(); + mgr.reparentPopoverTo(*b); + mgr.widget.showForSelection(_desktop); }); } @@ -236,11 +225,11 @@ PaintSelector::PaintSelector(FillOrStroke kind, std::shared_ptrgetPopOver().popdown(); + if (_desktop == desktop) { + return; } + + RecolorArtManager::get().popover.popdown(); if (_selection_changed_connection) { _selection_changed_connection.disconnect(); @@ -248,17 +237,12 @@ void PaintSelector::setDesktop(SPDesktop *desktop) _desktop = desktop; - if (_desktop) { - auto selection = _desktop->getSelection(); - if (selection) { + if (_desktop) { + if (auto selection = _desktop->getSelection()) { _selection_changed_connection = selection->connectChanged(sigc::mem_fun(*this, &PaintSelector::onSelectionChanged)); } } - - if (_recolorManager) { - _recolorManager->setDesktop(_desktop); - } } StyleToggleButton *PaintSelector::style_button_add(gchar const *pixmap, PaintSelector::Mode mode, gchar const *tip) @@ -298,9 +282,6 @@ void PaintSelector::fillrule_toggled(FillRuleRadioButton *tb) void PaintSelector::setMode(Mode mode) { - if (_recolorManager && _recolorManager->getPopOver().get_visible() && checkSelection(_desktop->getSelection())) { - return; - } set_mode_ex(mode, false); } @@ -1216,50 +1197,27 @@ PaintSelector::Mode PaintSelector::getModeForStyle(SPStyle const &style, FillOrS void PaintSelector::onSelectionChanged(Inkscape::Selection *selection) { - if (_blocker.pending()) { - return; - } + bool show_recolor = (_mode == MODE_GRADIENT_MESH && RecolorArtManager::checkMeshObject(selection)) || + RecolorArtManager::checkSelection(selection); + + int btn_index = -1; - if (checkSelection(selection)) { + if (show_recolor) { if (_mode == MODE_MULTIPLE || _mode == MODE_UNSET || _mode == MODE_GRADIENT_MESH) { - hideAllExcept(_recolorButtonTrigger[0].get()); + btn_index = 0; } else if (_mode == MODE_SOLID_COLOR) { - hideAllExcept(_recolorButtonTrigger[1].get()); + btn_index = 1; } else if (_mode == MODE_GRADIENT_RADIAL || _mode == MODE_GRADIENT_LINEAR) { - hideAllExcept(_recolorButtonTrigger[2].get()); + btn_index = 2; } else if (_mode == MODE_PATTERN) { - hideAllExcept(_recolorButtonTrigger[3].get()); + btn_index = 3; } else if (_mode == MODE_SWATCH) { - hideAllExcept(_recolorButtonTrigger[4].get()); - } else { - hideAllExcept(); + btn_index = 4; } - } else { - hideAllExcept(); - } - - if (_recolorManager && _recolorManager->getPopOver().get_visible() && checkSelection(selection)) { - auto guard = _blocker.block(); - _recolorManager->performUpdate(); - } -} - -bool PaintSelector::checkSelection(Inkscape::Selection *selection) -{ - return (_mode == MODE_GRADIENT_MESH && (selection->size() > 1 || RecolorArtManager::checkMeshObject(selection))) || - RecolorArtManager::checkSelection(selection); -} - -void PaintSelector::hideAllExcept(Gtk::Button *recolorButtonTrigger) -{ - if (recolorButtonTrigger) { - recolorButtonTrigger->show(); } - for (auto const &b : _recolorButtonTrigger) { - if (b.get() != recolorButtonTrigger) { - b->hide(); - } + for (int i = 0; i < _recolorButtonTrigger.size(); i++) { + _recolorButtonTrigger[i]->set_visible(i == btn_index); } } diff --git a/src/ui/widget/paint-selector.h b/src/ui/widget/paint-selector.h index b17b44e273..a0913a071e 100644 --- a/src/ui/widget/paint-selector.h +++ b/src/ui/widget/paint-selector.h @@ -17,7 +17,6 @@ #define SEEN_SP_PAINT_SELECTOR_H #include "fill-or-stroke.h" -#include "ui/operation-blocker.h" #include "ui/widget/gradient-selector.h" #include "ui/widget/swatch-selector.h" #include "selection.h" @@ -38,6 +37,7 @@ namespace Gtk { class Label; class ToggleButton; class Button; +class MenuButton; class Popover; } // namespace Gtk @@ -102,13 +102,9 @@ class PaintSelector : public Gtk::Box { SwatchSelector *_selector_swatch = nullptr; PatternEditor* _selector_pattern = nullptr; - UI::Widget::RecolorArtManager *_recolorManager = nullptr; - std::array, 5> _recolorButtonTrigger; - OperationBlocker _blocker; + std::array, 5> _recolorButtonTrigger; SPDesktop *_desktop = nullptr; void onSelectionChanged(Inkscape::Selection *selection); - bool checkSelection(Inkscape::Selection *selection); - void hideAllExcept(Gtk::Button *recolorButtonTrigger = nullptr); Gtk::Label *_label; GtkWidget *_patternmenu = nullptr; bool _patternmenu_update = false; diff --git a/src/ui/widget/recolor-art-manager.cpp b/src/ui/widget/recolor-art-manager.cpp index dfb2c45dbc..8780d3a25c 100644 --- a/src/ui/widget/recolor-art-manager.cpp +++ b/src/ui/widget/recolor-art-manager.cpp @@ -8,8 +8,9 @@ #include "recolor-art-manager.h" +#include + #include "object/sp-gradient.h" -#include "object/sp-mask.h" #include "object/sp-pattern.h" #include "object/sp-use.h" #include "style.h" @@ -19,51 +20,54 @@ namespace { bool has_colors_pattern(SPItem const *item) { - std::set colors; - SPPattern *patternstroke = nullptr; - SPPattern *patternfill = nullptr; - SPPattern *pattern = nullptr; - if (item && item->style) { - patternstroke = cast(item->style->getStrokePaintServer()); - patternfill = cast(item->style->getFillPaintServer()); + if (!item || !item->style) { + return false; } - if (patternstroke) - pattern = patternstroke; - if (patternfill) - pattern = patternfill; - if (!pattern) - return false; - SPPattern *root = pattern->rootPattern(); - for (auto &child : root->children) { - if (auto group = cast(&child)) { - 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); + std::optional first_col; + + // Return true when a second colour is found. + auto check_color = [&] (SPIPaint const &paint) { + if (!paint.isColor()) { + return false; + } + + if (!first_col) { + first_col = paint.getColor(); + return false; + } else { + return paint.getColor() != first_col; + } + }; + + // Search a pattern for colours, returning true when a second colour is found. + auto search_pattern = [&] (SPPaintServer const *ps) { + auto pat = cast(ps); + if (!pat) { + return false; + } + + for (auto const &child : pat->rootPattern()->children) { + if (auto group = cast(&child)) { + for (auto const &child : group->children) { + if (auto c = cast(&child)) { + if (check_color(c->style->fill) || check_color(c->style->stroke)) { + return true; + } } } } - } - 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); + if (check_color(child.style->fill) || check_color(child.style->stroke)) { + return true; + } } - } - return colors.size() > 1; + + return false; + }; + + return search_pattern(item->style->getFillPaintServer()) || + search_pattern(item->style->getStrokePaintServer()); } } // namespace @@ -74,56 +78,63 @@ RecolorArtManager &RecolorArtManager::get() return instance; } +void RecolorArtManager::reparentPopoverTo(Gtk::MenuButton &button) +{ + if (popover.get_parent() == &button) { + return; + } + + if (auto oldbutton = dynamic_cast(popover.get_parent())) { + oldbutton->unset_popover(); + } + + button.set_popover(popover); + + // The previous call causes GTK to reset the popover direction to down. Override it to left. + popover.set_position(Gtk::PositionType::LEFT); +} + RecolorArtManager::RecolorArtManager() { - _recolorPopOver.set_autohide(false); - _recolorPopOver.set_position(Gtk::PositionType::LEFT); - _recolorPopOver.set_child(_recolor_widget); + popover.set_autohide(false); + popover.set_child(widget); } bool RecolorArtManager::checkSelection(Inkscape::Selection *selection) { - auto group = cast(selection->single()); - auto use_group = cast(selection->single()); - auto item = cast(selection->single()); - bool pattern_colors = false; - SPMask *mask = nullptr; - if (item) { - mask = cast(item->getMaskObject()); - pattern_colors = has_colors_pattern(item); + if (selection->size() > 1) { + return true; } - return selection->size() > 1 || group || use_group || mask || pattern_colors; -} -bool RecolorArtManager::checkMeshObject(Inkscape::Selection *selection) -{ - if (selection->items().empty()) { + auto item = selection->singleItem(); + if (!item) { return false; } - auto fill_gradient = cast(selection->single()->style->getFillPaintServer()); - auto stroke_gradient = cast(selection->single()->style->getStrokePaintServer()); - SPGradient *gradient = fill_gradient ? cast(fill_gradient) : cast(stroke_gradient); - return gradient && gradient->hasPatches(); -} -void RecolorArtManager::setDesktop(SPDesktop *desktop) -{ - _recolor_widget.setDesktop(desktop); + return is(item) || + is(item) || + item->getMaskObject() || + has_colors_pattern(item); } -void RecolorArtManager::performUpdate() +bool RecolorArtManager::checkMeshObject(Inkscape::Selection *selection) { - _recolor_widget.performUpdate(); -} + if (selection->size() > 1) { + return true; + } -void RecolorArtManager::performMarkerUpdate(SPMarker *marker) -{ - _recolor_widget.performMarkerUpdate(marker); -} + auto item = selection->singleItem(); + if (!item) { + return false; + } -Gtk::Popover &RecolorArtManager::getPopOver() -{ - return _recolorPopOver; + auto is_mesh = [] (SPPaintServer *ps) { + auto grad = cast(ps); + return grad && grad->hasPatches(); + }; + + return is_mesh(item->style->getFillPaintServer()) || + is_mesh(item->style->getStrokePaintServer()); } } // namespace Inkscape::UI::Widget diff --git a/src/ui/widget/recolor-art-manager.h b/src/ui/widget/recolor-art-manager.h index be30750e56..0726aecf75 100644 --- a/src/ui/widget/recolor-art-manager.h +++ b/src/ui/widget/recolor-art-manager.h @@ -14,6 +14,8 @@ #include "ui/widget/recolor-art.h" #include "selection.h" +namespace Gtk { class MenuButton; } + namespace Inkscape::UI::Widget { class RecolorArtManager @@ -21,20 +23,16 @@ class RecolorArtManager public: static RecolorArtManager &get(); - Gtk::Popover &getPopOver(); + RecolorArt widget; + Gtk::Popover popover; + + void reparentPopoverTo(Gtk::MenuButton &button); static bool checkSelection(Inkscape::Selection *selection); static bool checkMeshObject(Inkscape::Selection *selection); - void setDesktop(SPDesktop *desktop); - void performUpdate(); - void performMarkerUpdate(SPMarker *marker); - private: RecolorArtManager(); - - RecolorArt _recolor_widget; - Gtk::Popover _recolorPopOver; }; } // namespace Inkscape::UI::Widget diff --git a/src/ui/widget/recolor-art.cpp b/src/ui/widget/recolor-art.cpp index 88f6a4106c..413a5afb59 100644 --- a/src/ui/widget/recolor-art.cpp +++ b/src/ui/widget/recolor-art.cpp @@ -24,7 +24,6 @@ #include "ui/tools/select-tool.h" #include "object/sp-marker.h" - namespace Inkscape::UI::Widget { /* @@ -249,12 +248,24 @@ RecolorArt::RecolorArt() void RecolorArt::setDesktop(SPDesktop *desktop) { - if (_desktop != desktop) { - _desktop = desktop; - _color_wheel->toggleHueLock(false); - _color_wheel->setLightness(100.0); - _color_wheel->setSaturation(100.0); + if (_desktop == desktop) { + return; } + + _sel_changed_conn.disconnect(); + _desktop_destroyed_conn.disconnect(); + + _desktop = desktop; + + if (_desktop) { + _desktop_destroyed_conn = _desktop->connectDestroy([this] (auto) { + setDesktop(nullptr); + }); + } + + _color_wheel->toggleHueLock(false); + _color_wheel->setLightness(100.0); + _color_wheel->setSaturation(100.0); } /* @@ -568,15 +579,23 @@ void RecolorArt::onLivePreviewToggled() * 4- call collect colors func * 5- put the generated list in the UI */ -void RecolorArt::performUpdate() +void RecolorArt::showForSelection(SPDesktop *desktop) +{ + assert(desktop); + + setDesktop(desktop); + _sel_changed_conn = _desktop->getSelection()->connectChanged([this] (auto) { + updateFromSelection(); + }); + + updateFromSelection(); +} + +void RecolorArt::updateFromSelection() { if (_selection_blocker.pending()) { return; } - if (!_desktop) { - g_warning("Desktop is NULL in Performupdate in recolor widget"); - return; - } _manager.clearData(); @@ -584,32 +603,29 @@ void RecolorArt::performUpdate() _color_wheel->setLightness(100.0); _color_wheel->setSaturation(100.0); - if (auto selection = _desktop->getSelection()) { - auto guard = _selection_blocker.block(); - selection->unlinkRecursive(false, true, false); - auto items = selection->items(); - auto vec = std::vector(items.begin(), items.end()); - _manager = collect_colours(vec); - if (!_manager.isSelectedColorsEmpty()) { - generateVisualList(); - auto first_button_id = _manager.getFirstKey(); - onOriginalColorClicked(first_button_id); - } - if (!_manager.isColorsEmpty()) { - _color_wheel->setColor(_manager.getColors()); - } + auto selection = _desktop->getSelection(); + + auto guard = _selection_blocker.block(); + selection->unlinkRecursive(false, true, false); + auto items = selection->items(); + auto vec = std::vector(items.begin(), items.end()); + _manager = collect_colours(vec); + if (!_manager.isSelectedColorsEmpty()) { + generateVisualList(); + auto first_button_id = _manager.getFirstKey(); + onOriginalColorClicked(first_button_id); + } + if (!_manager.isColorsEmpty()) { + _color_wheel->setColor(_manager.getColors()); } } -void RecolorArt::performMarkerUpdate(SPMarker *marker) +void RecolorArt::showForObject(SPDesktop *desktop, SPObject *object) { - if (!marker) { - return; - } - if (!_desktop) { - g_warning("Desktop is NULL in Performupdate in recolor widget"); - return; - } + assert(desktop); + assert(object); + + setDesktop(desktop); _manager.clearData(); @@ -617,7 +633,7 @@ void RecolorArt::performMarkerUpdate(SPMarker *marker) _color_wheel->setLightness(100.0); _color_wheel->setSaturation(100.0); - _manager = collect_colours({marker}); + _manager = collect_colours({object}); if (!_manager.isSelectedColorsEmpty()) { generateVisualList(); auto first_button_id = _manager.getFirstKey(); diff --git a/src/ui/widget/recolor-art.h b/src/ui/widget/recolor-art.h index 69b0a5d3aa..eb8f83cb90 100644 --- a/src/ui/widget/recolor-art.h +++ b/src/ui/widget/recolor-art.h @@ -3,13 +3,12 @@ #ifndef INKSCAPE_UI_WIDGET_RECOLOR_ART_H #define INKSCAPE_UI_WIDGET_RECOLOR_ART_H /* - * * Authors: * Fatma Omara * * Copyright (C) 2025 authors - * */ + #include #include #include @@ -27,18 +26,12 @@ #include "ui/operation-blocker.h" #include "ui/widget/ink-color-wheel.h" -class SPMarker; - namespace Inkscape::Colors { class Color; class ColorSet; -namespace Space { -class AnySpace; -} } // namespace Inkscape::Colors namespace Gtk { -class Widget; class Builder; class ListStore; class Notebook; @@ -46,13 +39,8 @@ class Notebook; class SPDesktop; -namespace Inkscape { -namespace UI { -namespace Tools { -class ToolBase; -} +namespace Inkscape::UI::Widget { -namespace Widget { class ColorNotebook; class MultiMarkerColorPlate; @@ -77,19 +65,19 @@ class RecolorArt : public Gtk::Box public: RecolorArt(); - void performUpdate(); - bool isInPreviewMode() const { return _is_preview; } - void setDesktop(SPDesktop *desktop); + void showForSelection(SPDesktop *desktop); + void showForObject(SPDesktop *desktop, SPObject *object); + void onResetClicked(); - void performMarkerUpdate(SPMarker *marker); private: SPDesktop *_desktop = nullptr; + sigc::scoped_connection _sel_changed_conn; + sigc::scoped_connection _desktop_destroyed_conn; Glib::RefPtr _builder; Gtk::Notebook &_notebook; Gtk::Box &_color_wheel_page; std::shared_ptr _solid_colors = std::make_shared(); - sigc::connection _solid_color_changed; Gtk::Box &_color_list; Gtk::Button &_reset; Gtk::CheckButton &_live_preview; @@ -109,6 +97,9 @@ private: OperationBlocker _blocker; OperationBlocker _selection_blocker; + void setDesktop(SPDesktop *desktop); + void updateFromSelection(); + void generateVisualList(); void layoutColorPicker(std::shared_ptr updated_color = nullptr); void colorButtons(Gtk::Box *button, Colors::Color color, bool is_original = false); @@ -123,8 +114,6 @@ private: std::pair,guint> findColorItemByKey(uint32_t key); }; -} // namespace Widget -} // namespace UI -} // namespace Inkscape +} // namespace Inkscape::UI::Widget #endif // INKSCAPE_UI_WIDGET_RECOLOR_ART_H -- GitLab From aad71f72ca18db6a5787f336ab9ea7470372875a Mon Sep 17 00:00:00 2001 From: ftomara Date: Fri, 24 Oct 2025 16:47:17 +0300 Subject: [PATCH 8/8] added changing opacity on hover --- src/ui/widget/recolor-art.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ui/widget/recolor-art.cpp b/src/ui/widget/recolor-art.cpp index 413a5afb59..9bc5c873ec 100644 --- a/src/ui/widget/recolor-art.cpp +++ b/src/ui/widget/recolor-art.cpp @@ -51,6 +51,7 @@ RecolorArt::RecolorArt() // when recolor widget is closed it resets opacity to mitigate the effect of getSelection function // and reshow selection boxes again signal_unmap().connect([&]() { + _manager.changeOpacity(false,0,_is_preview); if (!_is_preview) { _manager.convertToRecoloredColors(); DocumentUndo::done(_desktop->getDocument(), _("changed Item color"), @@ -108,6 +109,9 @@ RecolorArt::RecolorArt() return; } _current_color_id = _manager.getColor(index)->toRGBA(); + _manager.changeOpacity(true, _current_color_id, _is_preview); + } else { + _manager.changeOpacity(false, 0, _is_preview); } }); @@ -186,6 +190,12 @@ RecolorArt::RecolorArt() 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 ,_is_preview);}); + item_controller->signal_leave().connect([box, this]() { _manager.changeOpacity(false,0,_is_preview); }); + + box->add_controller(item_controller); auto original = dynamic_cast(box->get_first_child()); auto recolored = dynamic_cast(box->get_last_child()); -- GitLab