From c243c470f9954f519f6254d7c32fb620ebe5418e Mon Sep 17 00:00:00 2001 From: Rafael Siejakowski Date: Sun, 31 Mar 2024 17:12:55 +0200 Subject: [PATCH 1/2] Refactor Swatches Dialog and Color Palette Change color item ownership: they're now owned by the palette and not managed by Gtk. Simplify the code where possible. Improve the conformance with the project's coding style guidelines, by selectively running clang-format on touched parts of the source files. Directly update fill and stroke indicators in Swatches Dialog from the selection change/modification notification signals, instead of trying to queue an artificial resize which is never dispatched by Gtk4. The same applies to the handlers for the modification signals of document resources, needed to clear pointers to possibly removed gradients. Use std::visit instead of manual variant type checking. --- src/ui/dialog/color-item.cpp | 274 ++++++++++--------- src/ui/dialog/color-item.h | 28 +- src/ui/dialog/global-palettes.cpp | 136 +++++---- src/ui/dialog/global-palettes.h | 18 +- src/ui/dialog/swatches.cpp | 283 ++++++++----------- src/ui/dialog/swatches.h | 29 +- src/ui/drag-and-drop.cpp | 2 +- src/ui/util.cpp | 16 ++ src/ui/util.h | 7 + src/ui/widget/color-palette.cpp | 441 +++++++++++++++--------------- src/ui/widget/color-palette.h | 95 +++++-- src/ui/widget/selected-style.cpp | 2 +- src/widgets/paintdef.cpp | 26 +- src/widgets/paintdef.h | 22 +- 14 files changed, 713 insertions(+), 666 deletions(-) diff --git a/src/ui/dialog/color-item.cpp b/src/ui/dialog/color-item.cpp index 42de70f29c..338f884cdd 100644 --- a/src/ui/dialog/color-item.cpp +++ b/src/ui/dialog/color-item.cpp @@ -13,21 +13,23 @@ #include #include #include +#include #include +// #include #include #include -#include -#include -#include -#include -#include -#include -#include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -35,19 +37,19 @@ #include #include -#include "desktop-style.h" -#include "document.h" -#include "document-undo.h" -#include "hsluv.h" -#include "message-context.h" -#include "preferences.h" -#include "selection.h" #include "actions/actions-tools.h" +#include "desktop-style.h" #include "display/cairo-utils.h" +#include "document-undo.h" +#include "document.h" #include "helper/sigc-track-obj.h" +#include "hsluv.h" #include "io/resource.h" +#include "message-context.h" #include "object/sp-gradient.h" #include "object/tags.h" +#include "preferences.h" +#include "selection.h" #include "svg/svg-color.h" #include "ui/containerize.h" #include "ui/controller.h" @@ -55,39 +57,33 @@ #include "ui/dialog/dialog-container.h" #include "ui/icon-names.h" #include "ui/util.h" +#include "util/variant-visitor.h" namespace Inkscape::UI::Dialog { namespace { -// Return the result of executing a lambda, and cache the result for future calls. -template -auto &staticify(F &&f) -{ - static auto result = std::forward(f)(); - return result; -} - -// Get the "remove-color" image. +/// Get the "remove-color" image. Glib::RefPtr get_removecolor() { - return staticify([] { + // Paint the pixbuf only once + static const Glib::RefPtr remove_color = [] { auto path = IO::Resource::get_path(IO::Resource::SYSTEM, IO::Resource::UIS, "resources", "remove-color.png"); auto pixbuf = Gdk::Pixbuf::create_from_file(path.pointer()); if (!pixbuf) { std::cerr << "Null pixbuf for " << Glib::filename_to_utf8(path.pointer()) << std::endl; } return pixbuf; - }); + }(); + return remove_color; } - } // namespace ColorItem::ColorItem(PaintDef const &paintdef, DialogBase *dialog) : dialog(dialog) { - if (paintdef.get_type() == PaintDef::RGB) { + if (paintdef.get_type() == PaintDef::ColorType::RGB) { pinned_default = false; - data = RGBData{paintdef.get_rgb()}; + data = paintdef.get_rgb(); } else { pinned_default = true; data = PaintNone{}; @@ -107,9 +103,7 @@ ColorItem::ColorItem(SPGradient *gradient, DialogBase *dialog) description = gradient->defaultLabel(); color_id = gradient->getId(); - gradient->connectRelease(SIGC_TRACKING_ADAPTOR([this] (SPObject*) { - std::get(data).gradient = nullptr; - }, *this)); + gradient->connectRelease(SIGC_TRACKING_ADAPTOR([this](SPObject *) { data = (GradientData) nullptr; }, *this)); gradient->connectModified(SIGC_TRACKING_ADAPTOR([this] (SPObject *obj, unsigned flags) { if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { @@ -181,51 +175,59 @@ void ColorItem::set_pinned_pref(const std::string &path) void ColorItem::draw_color(Cairo::RefPtr const &cr, int w, int h) const { - if (std::holds_alternative(data)) { - // there's no color to paint; indicate clearly that there is nothing to select: - auto y = h / 2 + 0.5; - auto width = w / 4; - auto x = (w - width) / 2 - 0.5; - cr->move_to(x, y); - cr->line_to(x + width, y); - auto const fg = get_color(); - cr->set_source_rgba(fg.get_red(), fg.get_green(), fg.get_blue(), 0.5); - cr->set_line_width(1); - cr->stroke(); - } - else if (is_paint_none()) { - if (auto const pixbuf = get_removecolor()) { - const auto device_scale = get_scale_factor(); - cr->save(); - cr->scale((double)w / pixbuf->get_width() / device_scale, (double)h / pixbuf->get_height() / device_scale); - Gdk::Cairo::set_source_pixbuf(cr, pixbuf, 0, 0); - cr->paint(); - cr->restore(); - } - } else if (auto const rgbdata = std::get_if(&data)) { - auto [r, g, b] = rgbdata->rgb; - cr->set_source_rgb(r / 255.0, g / 255.0, b / 255.0); - cr->paint(); - // there's no way to query background color to check if color item stands out, - // so we apply faint outline to let users make out color shapes blending with background - auto const fg = get_color(); - cr->rectangle(0.5, 0.5, w - 1, h - 1); - cr->set_source_rgba(fg.get_red(), fg.get_green(), fg.get_blue(), 0.07); - cr->set_line_width(1); - cr->stroke(); - } else if (auto const graddata = std::get_if(&data)) { - // Gradient pointer may be null if the gradient was destroyed. - auto grad = graddata->gradient; - if (!grad) return; - - auto pat_checkerboard = Cairo::RefPtr(new Cairo::Pattern(ink_cairo_pattern_create_checkerboard(), true)); - auto pat_gradient = Cairo::RefPtr(new Cairo::Pattern(grad->create_preview_pattern(w), true)); - - cr->set_source(pat_checkerboard); - cr->paint(); - cr->set_source(pat_gradient); - cr->paint(); - } + std::visit(VariantVisitor{[this, w, h, &cr](Undefined) { + // there's no color to paint; + // indicate clearly that there is nothing to select: + auto y = h / 2 + 0.5; + auto width = w / 4; + auto x = (w - width) / 2 - 0.5; + cr->move_to(x, y); + cr->line_to(x + width, y); + auto const fg = get_color(); + cr->set_source_rgba(fg.get_red(), fg.get_green(), fg.get_blue(), 0.5); + cr->set_line_width(1); + cr->stroke(); + }, + [this, w, h, &cr](PaintNone) { + if (auto const pixbuf = get_removecolor()) { + const auto device_scale = get_scale_factor(); + cr->save(); + cr->scale((double)w / pixbuf->get_width() / device_scale, + (double)h / pixbuf->get_height() / device_scale); + Gdk::Cairo::set_source_pixbuf(cr, pixbuf, 0, 0); + cr->paint(); + cr->restore(); + } + }, + [this, w, h, &cr](const RGBData &rgbdata) { + auto [r, g, b] = rgbdata; + cr->set_source_rgb(r / 255.0, g / 255.0, b / 255.0); + cr->paint(); + // there's no way to query background color to check if color item stands + // out, so we apply faint outline to let users make out color shapes + // blending with background + auto const fg = get_color(); + cr->rectangle(0.5, 0.5, w - 1, h - 1); + cr->set_source_rgba(fg.get_red(), fg.get_green(), fg.get_blue(), 0.07); + cr->set_line_width(1); + cr->stroke(); + }, + [w, &cr](GradientData grad) { + // Gradient pointer may be null if the gradient was destroyed. + if (!grad) + return; + + auto pat_checkerboard = Cairo::RefPtr( + new Cairo::Pattern(ink_cairo_pattern_create_checkerboard(), true)); + auto pat_gradient = Cairo::RefPtr( + new Cairo::Pattern(grad->create_preview_pattern(w), true)); + + cr->set_source(pat_checkerboard); + cr->paint(); + cr->set_source(pat_gradient); + cr->paint(); + }}, + data); } void ColorItem::draw_func(Cairo::RefPtr const &cr, int const w, int const h) @@ -255,7 +257,7 @@ void ColorItem::draw_func(Cairo::RefPtr const &cr, int const w, } // Draw fill/stroke indicators. - if (is_fill || is_stroke) { + if (_is_fill || _is_stroke) { double const lightness = Hsluv::rgb_to_perceptual_lightness(average_color()); auto [gray, alpha] = Hsluv::get_contrasting_color(lightness); cr->set_source_rgba(gray, gray, gray, alpha); @@ -266,12 +268,12 @@ void ColorItem::draw_func(Cairo::RefPtr const &cr, int const w, cr->scale(minwh / 2.0, minwh / 2.0); cr->translate(1.0, 1.0); - if (is_fill) { + if (_is_fill) { cr->arc(0.0, 0.0, 0.35, 0.0, 2 * M_PI); cr->fill(); } - if (is_stroke) { + if (_is_stroke) { cr->set_fill_rule(Cairo::Context::FillRule::EVEN_ODD); cr->arc(0.0, 0.0, 0.65, 0.0, 2 * M_PI); cr->arc(0.0, 0.0, 0.5, 0.0, 2 * M_PI); @@ -352,16 +354,17 @@ void ColorItem::on_click(bool stroke) sp_repr_css_set_property(css.get(), attr_name, "none"); descr = stroke ? _("Set stroke color to none") : _("Set fill color to none"); } else if (auto const rgbdata = std::get_if(&data)) { - auto [r, g, b] = rgbdata->rgb; + auto [r, g, b] = *rgbdata; std::uint32_t rgba = (r << 24) | (g << 16) | (b << 8) | 0xff; char buf[64]; sp_svg_write_color(buf, sizeof(buf), rgba); sp_repr_css_set_property(css.get(), attr_name, buf); descr = stroke ? _("Set stroke color from swatch") : _("Set fill color from swatch"); - } else if (auto const graddata = std::get_if(&data)) { - auto grad = graddata->gradient; - if (!grad) return; - auto colorspec = "url(#" + Glib::ustring(grad->getId()) + ")"; + } else if (auto const grad = std::get_if(&data)) { + if (!*grad) { + return; + } + auto colorspec = "url(#" + Glib::ustring((*grad)->getId()) + ")"; sp_repr_css_set_property(css.get(), attr_name, colorspec.c_str()); descr = stroke ? _("Set stroke color from swatch") : _("Set fill color from swatch"); } @@ -449,7 +452,7 @@ void ColorItem::action_set_stroke() void ColorItem::action_delete() { - auto const grad = std::get(data).gradient; + auto const grad = std::get(data); if (!grad) return; grad->setSwatch(false); @@ -458,7 +461,7 @@ void ColorItem::action_delete() void ColorItem::action_edit() { - auto const grad = std::get(data).gradient; + auto const grad = std::get(data); if (!grad) return; auto desktop = dialog->getDesktop(); @@ -485,7 +488,7 @@ void ColorItem::action_edit() void ColorItem::action_toggle_pin() { if (auto const graddata = std::get_if(&data)) { - auto const grad = graddata->gradient; + auto const grad = *graddata; if (!grad) return; grad->setPinned(!is_pinned()); @@ -513,19 +516,19 @@ void ColorItem::action_convert(Glib::ustring const &name) PaintDef ColorItem::to_paintdef() const { - if (is_paint_none()) { - return PaintDef(); - } else if (auto const rgbdata = std::get_if(&data)) { - return PaintDef(rgbdata->rgb, description, ""); - } else if (auto const graddata = std::get_if(&data)) { - auto const grad = graddata->gradient; - assert(grad != nullptr); - return PaintDef({0, 0, 0}, grad->getId(), ""); - } - - // unreachable - assert(false); - return {}; + return std::visit(VariantVisitor{[](Undefined) -> PaintDef { + assert(false); + return {}; + }, + [](PaintNone) -> PaintDef { return {}; }, + [this](RGBData const &rgb) -> PaintDef { + return {rgb, description, ""}; + }, + [this](GradientData gradient_data) -> PaintDef { + g_assert(gradient_data); + return {{0, 0, 0}, gradient_data->getId(), ""}; + }}, + data); } Glib::RefPtr ColorItem::on_drag_prepare(Gtk::DragSource const &, double, double) @@ -550,22 +553,32 @@ void ColorItem::on_drag_begin(Gtk::DragSource &source, Glib::RefPtr c source.set_icon(texture, 0, 0); } -void ColorItem::set_fill(bool b) +void ColorItem::_set_indicator_impl(bool enable, bool ColorItem::*indicator) { - is_fill = b; - queue_draw(); + if (this->*indicator != enable) { + this->*indicator = enable; + queue_draw(); + } } -void ColorItem::set_stroke(bool b) +void ColorItem::refresh_label_text() { - is_stroke = b; - queue_draw(); + auto *parent = get_parent(); + if (!parent) { + return; + } + UI::for_each_child(*parent, [this](Gtk::Widget &sibling) { + if (auto label = dynamic_cast(&sibling)) { + label->set_text(description); + } + return UI::ForEachResult::_continue; + }); } bool ColorItem::is_pinned() const { if (auto const graddata = std::get_if(&data)) { - auto const grad = graddata->gradient; + auto const grad = *graddata; return grad && grad->isPinned(); } else { return Inkscape::Preferences::get()->getBool(pinned_pref, pinned_default); @@ -574,34 +587,37 @@ bool ColorItem::is_pinned() const std::array ColorItem::average_color() const { - if (is_paint_none()) { - return {1.0, 1.0, 1.0}; - } else if (auto const rgbdata = std::get_if(&data)) { - auto [r, g, b] = rgbdata->rgb; - return {r / 255.0, g / 255.0, b / 255.0}; - } else if (auto const graddata = std::get_if(&data)) { - auto grad = graddata->gradient; - auto pat = Cairo::RefPtr(new Cairo::Pattern(grad->create_preview_pattern(1), true)); - auto img = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, 1, 1); - auto cr = Cairo::Context::create(img); - cr->set_source_rgb(196.0 / 255.0, 196.0 / 255.0, 196.0 / 255.0); - cr->paint(); - cr->set_source(pat); - cr->paint(); - auto rgb = img->get_data(); - return {rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0}; - } - - // unreachable - assert(false); - return {1.0, 1.0, 1.0}; + using Ret = std::array; + return std::visit(VariantVisitor{[](Undefined) -> Ret { + assert(false); + return {1.0, 1.0, 1.0}; + }, + [](PaintNone) -> Ret { + return {1.0, 1.0, 1.0}; + }, + + [](const RGBData &rgb) -> Ret { + return {rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0}; + }, + [](GradientData grad) -> Ret { + auto pat = Cairo::RefPtr( + new Cairo::Pattern(grad->create_preview_pattern(1), true)); + auto img = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, 1, 1); + auto cr = Cairo::Context::create(img); + cr->set_source_rgb(196.0 / 255.0, 196.0 / 255.0, 196.0 / 255.0); + cr->paint(); + cr->set_source(pat); + cr->paint(); + auto rgb = img->get_data(); + return {rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0}; + }}, + data); } bool ColorItem::is_paint_none() const { return std::holds_alternative(data); } - } // namespace Inkscape::UI::Dialog /* diff --git a/src/ui/dialog/color-item.h b/src/ui/dialog/color-item.h index 61d1078c54..6877bbbb6b 100644 --- a/src/ui/dialog/color-item.h +++ b/src/ui/dialog/color-item.h @@ -11,14 +11,15 @@ #define INKSCAPE_UI_DIALOG_COLOR_ITEM_H #include -#include -#include #include +#include #include #include #include // GtkEventControllerMotion #include #include // Gtk::EventSequenceState +#include +#include #include "widgets/paintdef.h" @@ -71,15 +72,17 @@ public: ColorItem(SPGradient*, DialogBase*); /// Update the fill indicator, showing this widget is the fill of the current selection. - void set_fill(bool); + void set_fill_indicator(bool show_indicator) { _set_indicator_impl(show_indicator, &ColorItem::_is_fill); } /// Update the stroke indicator, showing this widget is the stroke of the current selection. - void set_stroke(bool); + void set_stroke_indicator(bool show_indicator) { _set_indicator_impl(show_indicator, &ColorItem::_is_stroke); } /// Update whether this item is pinned. bool is_pinned() const; void set_pinned_pref(const std::string &path); + void refresh_label_text(); + const Glib::ustring &get_description() const { return description; } sigc::signal& signal_modified() { return _signal_modified; }; @@ -94,6 +97,7 @@ private: // Common post-construction setup. void common_setup(); + void _set_indicator_impl(bool enable, bool ColorItem::*indicator); void on_motion_enter(GtkEventControllerMotion const *motion, double x, double y); void on_motion_leave(GtkEventControllerMotion const *motion); @@ -134,18 +138,22 @@ private: bool pinned_default = false; // The color. - struct Undefined {}; - struct PaintNone {}; - struct RGBData { std::array rgb; }; - struct GradientData { SPGradient *gradient; }; + struct Undefined + { + }; + struct PaintNone + { + }; + using RGBData = std::array; + using GradientData = SPGradient *; std::variant data; // The dialog this widget belongs to. Used for determining what desktop to take action on. DialogBase *dialog = nullptr; // Whether this color is in use as the fill or stroke of the current selection. - bool is_fill = false; - bool is_stroke = false; + bool _is_fill = false; + bool _is_stroke = false; // A cache of the widget contents, if necessary. Cairo::RefPtr cache; diff --git a/src/ui/dialog/global-palettes.cpp b/src/ui/dialog/global-palettes.cpp index ae529a703d..f513d29312 100644 --- a/src/ui/dialog/global-palettes.cpp +++ b/src/ui/dialog/global-palettes.cpp @@ -45,10 +45,9 @@ using Inkscape::Util::delete_with; namespace { Glib::ustring get_extension(Glib::ustring const &name) { - auto extpos = name.rfind('.'); + auto const extpos = name.rfind('.'); if (extpos != Glib::ustring::npos) { - auto ext = name.substr(extpos).casefold(); - return ext; + return name.substr(extpos).casefold(); } return {}; } @@ -110,6 +109,14 @@ void skip(const Glib::RefPtr& s, size_t bytes) { using namespace Inkscape::UI::Dialog; +namespace ColorBook { +// Color space codes in ACB color book palettes +constexpr uint16_t RgbColorspace = 0; +constexpr uint16_t CmykColorspace = 2; +constexpr uint16_t LabColorspace = 7; +constexpr uint16_t GrayscaleColorspace = 8; +} // namespace ColorBook + // Load Adobe ACB color book void load_acb_palette(PaletteFileData& palette, std::string const &fname) { auto file = Gio::File::create_for_path(fname); @@ -147,24 +154,24 @@ void load_acb_palette(PaletteFileData& palette, std::string const &fname) { PaletteFileData::ColorSpace color_space; switch (cs) { - case 0: // RGB - components = 3; - color_space = PaletteFileData::Rgb255; - break; - case 2: // CMYK - components = 4; - color_space = PaletteFileData::Cmyk100; - break; - case 7: // LAB - components = 3; - color_space = PaletteFileData::Lab100; - break; - case 8: // Grayscale - components = 1; - color_space = PaletteFileData::Rgb255; // using RGB for grayscale - break; - default: - throw std::runtime_error(_("ACB file color space not supported.")); + case ColorBook::RgbColorspace: + components = 3; + color_space = PaletteFileData::ColorSpace::Rgb8bit; + break; + case ColorBook::CmykColorspace: + components = 4; + color_space = PaletteFileData::ColorSpace::Cmyk100; + break; + case ColorBook::LabColorspace: + components = 3; + color_space = PaletteFileData::ColorSpace::Lab100; + break; + case ColorBook::GrayscaleColorspace: + components = 1; + color_space = PaletteFileData::ColorSpace::Rgb8bit; // using RGB for grayscale + break; + default: + throw std::runtime_error(_("ACB file color space not supported.")); } auto ext = get_extension(ttl); @@ -197,20 +204,19 @@ void load_acb_palette(PaletteFileData& palette, std::string const &fname) { ost.precision(3); switch (color_space) { - case PaletteFileData::Lab100: { + case PaletteFileData::ColorSpace::Lab100: { auto l = std::floor(channels[0] / 2.55f + 0.5f); auto a = channels[1] - 128.0f; auto b = channels[2] - 128.0f; auto rgb = Hsluv::lab_to_rgb(l, a, b); - unsigned ur = rgb[0] * 255; - unsigned ug = rgb[1] * 255; - unsigned ub = rgb[2] * 255; + uint8_t const ur = rgb[0] * 255; + uint8_t const ug = rgb[1] * 255; + uint8_t const ub = rgb[2] * 255; color = {{l, a, b, 0}, color_space, {ur, ug, ub}}; ost << "L: " << l << " a: " << a << " b: " << b; - } - break; + } break; - case PaletteFileData::Cmyk100: { + case PaletteFileData::ColorSpace::Cmyk100: { // 0% - 100% auto c = std::floor((255 - channels[0]) / 2.55f + 0.5f); auto m = std::floor((255 - channels[1]) / 2.55f + 0.5f); @@ -219,16 +225,15 @@ void load_acb_palette(PaletteFileData& palette, std::string const &fname) { auto [r, g, b] = convert.cmyk_to_rgb(c, m, y, k); color = {{c, m, y, k}, color_space, {r, g, b}}; ost << "C: " << c << "% M: " << m << "% Y: " << y << "% K: " << k << '%'; - } - break; + } break; - case PaletteFileData::Rgb255: { + case PaletteFileData::ColorSpace::Rgb8bit: { float r = channels[0]; float g = cs == 1 ? r : channels[1]; float b = cs == 1 ? r : channels[2]; - unsigned ur = r; - unsigned ug = g; - unsigned ub = b; + uint8_t const ur = r; + uint8_t const ug = g; + uint8_t const ub = b; color = {{r, g, b, 0}, color_space, {ur, ug, ub}}; if (cs == 1) { // grayscale @@ -237,8 +242,7 @@ void load_acb_palette(PaletteFileData& palette, std::string const &fname) { else { ost << "R: " << ur << " G: " << ug << " B: " << ub; } - } - break; + } break; default: throw std::runtime_error(_("Palette color space unexpected.")); @@ -299,9 +303,12 @@ void load_ase_swatches(PaletteFileData& palette, std::string const &fname) { auto mode = to_mode(type); auto [r, g, b] = convert.cmyk_to_rgb(c, m, y, k); ost << "C: " << c << "% M: " << m << "% Y: " << y << "% K: " << k << '%'; - palette.colors.emplace_back( - PaletteFileData::Color {{c, m, y, k}, PaletteFileData::Cmyk100, {r, g, b}, color_name, ost.str(), mode} - ); + palette.colors.emplace_back(PaletteFileData::Color{{c, m, y, k}, + PaletteFileData::ColorSpace::Cmyk100, + {r, g, b}, + color_name, + ost.str(), + mode}); } else if (mode == "RGB ") { auto r = read_float(stream) * 255; @@ -309,9 +316,12 @@ void load_ase_swatches(PaletteFileData& palette, std::string const &fname) { auto b = read_float(stream) * 255; auto type = read_value(stream); auto mode = to_mode(type); - palette.colors.emplace_back( - PaletteFileData::Color {{r, g, b, 0}, PaletteFileData::Rgb255, {(unsigned)r, (unsigned)g, (unsigned)b}, color_name, "", mode} - ); + palette.colors.emplace_back(PaletteFileData::Color{{r, g, b, 0}, + PaletteFileData::ColorSpace::Rgb8bit, + {(uint8_t)r, (uint8_t)g, (uint8_t)b}, + color_name, + "", + mode}); } else if (mode == "LAB ") { //TODO - verify scale @@ -321,21 +331,27 @@ void load_ase_swatches(PaletteFileData& palette, std::string const &fname) { auto type = read_value(stream); auto mode = to_mode(type); auto rgb = Hsluv::lab_to_rgb(l, a, b); - unsigned ur = rgb[0] * 255; - unsigned ug = rgb[1] * 255; - unsigned ub = rgb[2] * 255; + uint8_t const red = rgb[0] * 255; + uint8_t const green = rgb[1] * 255; + uint8_t const blue = rgb[2] * 255; ost << "L: " << l << " a: " << a << " b: " << b; - palette.colors.emplace_back( - PaletteFileData::Color {{l, a, b, 0}, PaletteFileData::Lab100, {ur, ug, ub}, color_name, ost.str(), mode} - ); + palette.colors.emplace_back(PaletteFileData::Color{{l, a, b, 0}, + PaletteFileData::ColorSpace::Lab100, + {red, green, blue}, + color_name, + ost.str(), + mode}); } else if (mode == "Gray") { auto g = read_float(stream) * 255; auto type = read_value(stream); auto mode = to_mode(type); - palette.colors.emplace_back( - PaletteFileData::Color {{g, g, g, 0}, PaletteFileData::Rgb255, {(unsigned)g, (unsigned)g, (unsigned)g}, color_name, "", mode} - ); + palette.colors.emplace_back(PaletteFileData::Color{{g, g, g, 0}, + PaletteFileData::ColorSpace::Rgb8bit, + {(uint8_t)g, (uint8_t)g, (uint8_t)g}, + color_name, + "", + mode}); } else { std::ostringstream ost; @@ -379,14 +395,14 @@ void load_gimp_palette(PaletteFileData& palette, std::string const &path) auto line = Glib::ustring(buf); // Unnecessary copy required until using a glibmm with support for string views. TODO: Fix when possible. Glib::MatchInfo match; if (regex_rgb->match(line, match)) { // ::regex_match(line, match, boost::regex(), boost::regex_constants::match_continuous)) { - // RGB color, followed by an optional name. + // 8-bit RGB color, followed by an optional name. PaletteFileData::Color color; for (int i = 0; i < 3; i++) { - color.rgb[i] = std::clamp(std::stoi(match.fetch(i + 1)), 0, 255); + color.rgb[i] = std::clamp(std::stoul(match.fetch(i + 1)), 0UL, 255UL); color.channels[i] = color.rgb[i]; } color.channels[3] = 0; - color.space = PaletteFileData::Rgb255; + color.space = PaletteFileData::ColorSpace::Rgb8bit; color.name = match.fetch(4); if (!color.name.empty()) { @@ -394,11 +410,13 @@ void load_gimp_palette(PaletteFileData& palette, std::string const &path) color.name = g_dpgettext2(nullptr, "Palette", color.name.c_str()); } else { // Otherwise, set the name to be the hex value. - color.name = Glib::ustring::compose("#%1%2%3", - Inkscape::ustring::format_classic(std::hex, std::setw(2), std::setfill('0'), color.rgb[0]), - Inkscape::ustring::format_classic(std::hex, std::setw(2), std::setfill('0'), color.rgb[1]), - Inkscape::ustring::format_classic(std::hex, std::setw(2), std::setfill('0'), color.rgb[2]) - ).uppercase(); + auto const format_hex_byte = [](uint8_t byte) { + return Inkscape::ustring::format_classic(std::hex, std::setw(2), std::setfill('0'), + static_cast(byte)); + }; + color.name = Glib::ustring::compose("#%1%2%3", format_hex_byte(color.rgb[0]), + format_hex_byte(color.rgb[1]), format_hex_byte(color.rgb[2])) + .uppercase(); } palette.colors.emplace_back(std::move(color)); diff --git a/src/ui/dialog/global-palettes.h b/src/ui/dialog/global-palettes.h index 65387cc785..ebbaa47baa 100644 --- a/src/ui/dialog/global-palettes.h +++ b/src/ui/dialog/global-palettes.h @@ -44,14 +44,16 @@ struct PaletteFileData /// Color space of all colors in this palette. Original definitions are kept in "Color.channels" /// for use with ICC profiles. Preview sRGB colors are inside "Color.rgb" - enum ColorSpace { + enum class ColorSpace + { Undefined, // not a valid color definition - Rgb255, // RGB 0..255 - Lab100, // Cie*Lab, L 0..100, a, b -128..127 - Cmyk100 // CMYK 0%..100% + Rgb8bit, // RGB 0..255 + Lab100, // Cie*Lab, L 0..100, a, b -128..127 + Cmyk100 // CMYK 0%..100% }; - enum ColorMode: uint8_t { + enum ColorMode : uint8_t + { Normal, Global, Spot, @@ -63,10 +65,10 @@ struct PaletteFileData std::array channels; /// Color space of this color. - ColorSpace space = Undefined; + ColorSpace space = ColorSpace::Undefined; - /// RGB color. - std::array rgb; + /// 8-bit RGB color. + std::array rgb; /// Name of the color, either specified in the file or generated from the rgb. Glib::ustring name; diff --git a/src/ui/dialog/swatches.cpp b/src/ui/dialog/swatches.cpp index a8cd719c38..3c5950e549 100644 --- a/src/ui/dialog/swatches.cpp +++ b/src/ui/dialog/swatches.cpp @@ -56,7 +56,7 @@ #include "ui/widget/color-palette.h" #include "ui/widget/color-palette-preview.h" #include "ui/widget/popover-menu-item.h" -#include "util/variant-visitor.h" + #include "widgets/paintdef.h" namespace Inkscape::UI::Dialog { @@ -204,15 +204,15 @@ SwatchesPanel::~SwatchesPanel() void SwatchesPanel::documentReplaced() { - if (getDocument()) { - if (_current_palette_id == auto_id) { - track_gradients(); - } + const bool is_auto_palette = _current_palette_id == auto_id; + if (getDocument() && is_auto_palette) { + track_gradients(); } else { untrack_gradients(); } - if (_current_palette_id == auto_id) { + if (is_auto_palette) { + rebuild_isswatch(); rebuild(); } } @@ -265,33 +265,31 @@ void SwatchesPanel::track_gradients() auto doc = getDocument(); // Subscribe to the addition and removal of gradients. - conn_gradients.disconnect(); conn_gradients = doc->connectResourcesChanged("gradient", [this] { - gradients_changed = true; - queue_resize(); + assert(_current_palette_id == auto_id); + // We are in the "Auto" palette, and a gradient was added or removed. + // The list of widgets has therefore changed, and must be completely rebuilt. + // We must also rebuild the tracking information for each gradient's isSwatch() status. + rebuild_isswatch(); + rebuild(); }); // Subscribe to child modifications of the defs section. We will use this to monitor // each gradient for whether its isSwatch() status changes. - conn_defs.disconnect(); - conn_defs = doc->getDefs()->connectModified([this] (SPObject*, unsigned flags) { - if (flags & SP_OBJECT_CHILD_MODIFIED_FLAG) { - defs_changed = true; - queue_resize(); + conn_defs = doc->getDefs()->connectModified([this](SPObject *, unsigned flags) { + assert(_current_palette_id == auto_id); + if (flags & SP_OBJECT_CHILD_MODIFIED_FLAG && update_isswatch()) { + // We are in the "Auto" palette, and a gradient's isSwatch() status was possibly modified. + // Check if it has; if so, then the list of widgets has changed, and must be rebuilt. + rebuild(); } }); - - gradients_changed = false; - defs_changed = false; - rebuild_isswatch(); } void SwatchesPanel::untrack_gradients() { conn_gradients.disconnect(); conn_defs.disconnect(); - gradients_changed = false; - defs_changed = false; } /* @@ -300,49 +298,14 @@ void SwatchesPanel::untrack_gradients() void SwatchesPanel::selectionChanged(Selection*) { - selection_changed = true; - queue_resize(); + update_fillstroke_indicators(); } void SwatchesPanel::selectionModified(Selection*, guint flags) { if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { - selection_changed = true; - queue_resize(); - } -} - -// Document updates are handled asynchronously by setting a flag and queuing a resize. This results in -// the following function being run at the last possible moment before the widget will be repainted. -// This ensures that multiple document updates only result in a single UI update. -void SwatchesPanel::size_allocate_vfunc(int const width, int const height, int const baseline) -{ - if (gradients_changed) { - assert(_current_palette_id == auto_id); - // We are in the "Auto" palette, and a gradient was added or removed. - // The list of widgets has therefore changed, and must be completely rebuilt. - // We must also rebuild the tracking information for each gradient's isSwatch() status. - rebuild_isswatch(); - rebuild(); - } else if (defs_changed) { - assert(_current_palette_id == auto_id); - // We are in the "Auto" palette, and a gradient's isSwatch() status was possibly modified. - // Check if it has; if so, then the list of widgets has changed, and must be rebuilt. - if (update_isswatch()) { - rebuild(); - } - } - - if (selection_changed) { update_fillstroke_indicators(); } - - selection_changed = false; - gradients_changed = false; - defs_changed = false; - - // Necessary to perform *after* the above widget modifications, so GTK can process the new layout. - DialogBase::size_allocate_vfunc(width, height, baseline); } void SwatchesPanel::rebuild_isswatch() @@ -352,7 +315,7 @@ void SwatchesPanel::rebuild_isswatch() isswatch.resize(grads.size()); for (int i = 0; i < grads.size(); i++) { - isswatch[i] = static_cast(grads[i])->isSwatch(); + isswatch[i] = static_cast(grads[i])->isSwatch(); } } @@ -375,82 +338,59 @@ bool SwatchesPanel::update_isswatch() return modified; } -static auto spcolor_to_rgb(SPColor const &color) +static Widget::ColorPalette::Rgb8bit spcolor_to_rgb(SPColor const &color) { float rgbf[3]; color.get_rgb_floatv(rgbf); - std::array rgb; + Widget::ColorPalette::Rgb8bit rgb; for (int i = 0; i < 3; i++) { rgb[i] = SP_COLOR_F_TO_U(rgbf[i]); - }; - + } return rgb; } -void SwatchesPanel::update_fillstroke_indicators() -{ - auto doc = getDocument(); - auto style = SPStyle(doc); - - // Get the current fill or stroke as a ColorKey. - auto current_color = [&, this] (bool fill) -> std::optional { - switch (sp_desktop_query_style(getDesktop(), &style, fill ? QUERY_STYLE_PROPERTY_FILL : QUERY_STYLE_PROPERTY_STROKE)) - { - case QUERY_STYLE_SINGLE: - case QUERY_STYLE_MULTIPLE_AVERAGED: - case QUERY_STYLE_MULTIPLE_SAME: - break; - default: - return {}; - } - - auto attr = style.getFillOrStroke(fill); - if (!attr->set) { +static std::optional compute_key_for_current_color(SPDesktop *desktop, bool is_fill) { + SPDocument *document = desktop->getDocument(); + SPStyle style{document}; + + switch (sp_desktop_query_style(desktop, &style, is_fill ? QUERY_STYLE_PROPERTY_FILL : QUERY_STYLE_PROPERTY_STROKE)) + { + case QUERY_STYLE_SINGLE: + case QUERY_STYLE_MULTIPLE_AVERAGED: + case QUERY_STYLE_MULTIPLE_SAME: + break; + default: return {}; - } - - if (attr->isNone()) { - return std::monostate{}; - } else if (attr->isColor()) { - return spcolor_to_rgb(attr->value.color); - } else if (attr->isPaintserver()) { - if (auto grad = cast(fill ? style.getFillPaintServer() : style.getStrokePaintServer())) { - if (grad->isSwatch()) { - return grad; - } else if (grad->ref) { - if (auto ref = grad->ref->getObject(); ref && ref->isSwatch()) { - return ref; - } - } - } - } + } + auto attr = style.getFillOrStroke(is_fill); + if (!attr->set) { return {}; - }; - - for (auto w : current_fill) w->set_fill(false); - for (auto w : current_stroke) w->set_stroke(false); - - current_fill.clear(); - current_stroke.clear(); - - if (auto fill = current_color(true)) { - auto range = widgetmap.equal_range(*fill); - for (auto it = range.first; it != range.second; ++it) { - current_fill.emplace_back(it->second); - } } - if (auto stroke = current_color(false)) { - auto range = widgetmap.equal_range(*stroke); - for (auto it = range.first; it != range.second; ++it) { - current_stroke.emplace_back(it->second); + if (attr->isNone()) { + return Widget::ColorPalette::NoColor{}; + } else if (attr->isColor()) { + return spcolor_to_rgb(attr->value.color); + } else if (attr->isPaintserver()) { + if (auto grad = cast(is_fill ? style.getFillPaintServer() : style.getStrokePaintServer())) { + if (grad->isSwatch()) { + return grad; + } else if (grad->ref) { + if (auto ref = grad->ref->getObject(); ref && ref->isSwatch()) { + return ref; + } + } } } + return {}; +} - for (auto w : current_fill) w->set_fill(true); - for (auto w : current_stroke) w->set_stroke(true); +void SwatchesPanel::update_fillstroke_indicators() +{ + _palette->set_fill_stroke_indicators(compute_key_for_current_color(getDesktop(), true), + compute_key_for_current_color(getDesktop(), false)); } [[nodiscard]] static auto to_palette_t(PaletteFileData const &p) @@ -459,14 +399,13 @@ void SwatchesPanel::update_fillstroke_indicators() palette.name = p.name; palette.id = p.id; for (auto const &c : p.colors) { - std::visit(VariantVisitor { - [&](const PaletteFileData::Color& c) { - auto [r, g, b] = c.rgb; - palette.colors.push_back({r / 255.0, g / 255.0, b / 255.0}); - }, - [](const PaletteFileData::SpacerItem&) {}, - [](const PaletteFileData::GroupStart&) {} - }, c); + std::visit(VariantVisitor{[&](const PaletteFileData::Color &c) { + auto [r, g, b] = c.rgb; + palette.colors.push_back({r / 255.0, g / 255.0, b / 255.0}); + }, + [](const PaletteFileData::SpacerItem &) {}, + [](const PaletteFileData::GroupStart &) {}}, + c); } return palette; } @@ -474,26 +413,30 @@ void SwatchesPanel::update_fillstroke_indicators() /** * Process the list of available palettes and update the list in the _palette widget. */ -void SwatchesPanel::update_palettes(bool compact) { +void SwatchesPanel::update_palettes(bool compact) +{ std::vector palettes; palettes.reserve(1 + GlobalPalettes::get().palettes().size()); // The first palette in the list is always the "Auto" palette. Although this // will contain colors when selected, the preview we show for it is empty. - palettes.push_back({_("Document swatches"), auto_id, {}}); + palettes.emplace_back(UI::Widget::palette_t{ + .name = _("Document swatches"), + .id = auto_id + }); // The remaining palettes in the list are the global palettes. - for (auto &p : GlobalPalettes::get().palettes()) { - auto palette = to_palette_t(p); - palettes.emplace_back(std::move(palette)); + for (auto const &palette : GlobalPalettes::get().palettes()) { + palettes.emplace_back(to_palette_t(palette)); } _palette->set_palettes(palettes); _palettes.clear(); _palettes.reserve(palettes.size()); - std::transform(palettes.begin(), palettes.end(), std::back_inserter(_palettes), - [](auto &&palette){ return PaletteLoaded{std::move(palette), false}; }); + std::transform(palettes.begin(), palettes.end(), std::back_inserter(_palettes), [](auto &&palette) { + return PaletteLoaded{std::move(palette), false}; + }); } /** @@ -501,54 +444,41 @@ void SwatchesPanel::update_palettes(bool compact) { */ void SwatchesPanel::rebuild() { - std::vector palette; - - // The pointers in widgetmap are to widgets owned by the ColorPalette. It is assumed it will not - // delete them unless we ask, via the call to set_colors() later in this function. - widgetmap.clear(); - current_fill.clear(); - current_stroke.clear(); + _palette->clear(); // Add the "remove-color" color. - auto const w = Gtk::make_managed(PaintDef(), this); - w->set_pinned_pref(_prefs_path); - palette.emplace_back(w); - widgetmap.emplace(std::monostate{}, w); - _palette->set_page_size(0); + auto remove_color = std::make_unique(PaintDef(), this); + remove_color->set_pinned_pref(_prefs_path); + _palette->add_item(std::move(remove_color), UI::Widget::ColorPalette::NoColor{}); + if (auto pal = get_palette(_current_palette_id)) { _palette->set_page_size(pal->columns); - palette.reserve(palette.size() + pal->colors.size()); - for (auto &c : pal->colors) { - auto dialog = this; - auto w = std::visit(VariantVisitor { - [](const PaletteFileData::SpacerItem&) { - return Gtk::make_managed(""); - }, - [](const PaletteFileData::GroupStart& g) { - return Gtk::make_managed(g.name); - }, - [=, this](const PaletteFileData::Color& c) { - auto w = Gtk::make_managed(PaintDef(c.rgb, c.name, c.definition), dialog); - w->set_pinned_pref(_prefs_path); - widgetmap.emplace(c.rgb, w); - return w; - }, - }, c); - - palette.emplace_back(w); + for (auto const &color : pal->colors) { + std::visit(VariantVisitor{ + [this](PaletteFileData::SpacerItem const &) { + _palette->add_item(std::make_unique(""), {}); + }, + [this](PaletteFileData::GroupStart const &group) { + _palette->add_item(std::make_unique(group.name), {}); + }, + [this](PaletteFileData::Color const &c) { + auto color_item = + std::make_unique(PaintDef(c.rgb, c.name, c.definition), this); + color_item->set_pinned_pref(_prefs_path); + _palette->add_item(std::move(color_item), c.rgb); + }, + }, + color); } } else if (_current_palette_id == auto_id && getDocument()) { auto grads = getDocument()->getResourceList("gradient"); for (auto obj : grads) { auto grad = cast_unsafe(obj); if (grad->isSwatch()) { - auto const w = Gtk::make_managed(grad, this); - palette.emplace_back(w); - widgetmap.emplace(grad, w); + auto color_item = std::make_unique(grad, this); // Rebuild if the gradient gets pinned or unpinned - w->signal_pinned().connect([this]{ - rebuild(); - }); + color_item->signal_pinned().connect([this] { rebuild(); }); + _palette->add_item(std::move(color_item), grad); } } } @@ -557,11 +487,12 @@ void SwatchesPanel::rebuild() update_fillstroke_indicators(); } - _palette->set_colors(palette); + _palette->rebuild(); _palette->set_selected(_current_palette_id); } -bool SwatchesPanel::load_swatches() { +bool SwatchesPanel::load_swatches() +{ auto window = dynamic_cast(get_root()); auto file = choose_palette_file(window); auto loaded = false; @@ -592,7 +523,8 @@ bool SwatchesPanel::load_swatches(std::string const &path) return false; } -void SwatchesPanel::update_loaded_palette_entry() { +void SwatchesPanel::update_loaded_palette_entry() +{ // add or update last entry in a store to match loaded palette if (_palettes.empty() || !_palettes.back().second) { // last palette !loaded _palettes.emplace_back(); @@ -698,14 +630,16 @@ void SwatchesPanel::update_selector_label(Glib::ustring const &active_id) _selector_label.set_label(it->first.name); } -void SwatchesPanel::clear_filter() { +void SwatchesPanel::clear_filter() +{ if (_color_filter_text.empty()) return; _color_filter_text.erase(); _palette->apply_filter(); } -void SwatchesPanel::filter_colors(const Glib::ustring& text) { +void SwatchesPanel::filter_colors(const Glib::ustring& text) +{ auto search = text.lowercase(); if (_color_filter_text == search) return; @@ -713,7 +647,8 @@ void SwatchesPanel::filter_colors(const Glib::ustring& text) { _palette->apply_filter(); } -bool SwatchesPanel::filter_callback(const Dialog::ColorItem& color) const { +bool SwatchesPanel::filter_callback(const Dialog::ColorItem& color) const +{ if (_color_filter_text.empty()) return true; // let's hide group headers and fillers when searching for a matching color diff --git a/src/ui/dialog/swatches.h b/src/ui/dialog/swatches.h index dba066f837..f94efe0889 100644 --- a/src/ui/dialog/swatches.h +++ b/src/ui/dialog/swatches.h @@ -13,17 +13,14 @@ #ifndef UI_DIALOG_SWATCHES_H #define UI_DIALOG_SWATCHES_H -#include -#include -#include -#include -#include - #include #include #include +#include +#include -#include "preferences.h" // PrefObserver +#include "helper/auto-connection.h" +#include "preferences.h" // PrefObserver #include "ui/dialog/dialog-base.h" #include "ui/dialog/global-palettes.h" #include "ui/widget/palette_t.h" @@ -36,8 +33,6 @@ class MenuButton; class ToggleButton; } // namespace Gtk -class SPGradient; - namespace Inkscape::UI { namespace Widget { @@ -69,8 +64,6 @@ private: void selectionChanged(Selection *selection) final; void selectionModified(Selection *selection, guint flags) final; - void size_allocate_vfunc(int width, int height, int baseline) final; - void update_palettes(bool compact); void rebuild(); bool load_swatches(); @@ -93,11 +86,8 @@ private: const PaletteFileData* get_palette(const Glib::ustring& id); // Asynchronous update mechanism. - sigc::connection conn_gradients; - sigc::connection conn_defs; - bool gradients_changed = false; - bool defs_changed = false; - bool selection_changed = false; + auto_connection conn_gradients; + auto_connection conn_defs; void track_gradients(); void untrack_gradients(); @@ -105,13 +95,6 @@ private: std::vector isswatch; void rebuild_isswatch(); bool update_isswatch(); - - // A map from colors to their respective widgets. Used to quickly find the widgets corresponding - // to the current fill/stroke color, in order to update their fill/stroke indicators. - using ColorKey = std::variant, SPGradient *>; - boost::unordered_multimap widgetmap; // need boost for array hash - std::vector current_fill; - std::vector current_stroke; void update_fillstroke_indicators(); Inkscape::PrefObserver _pinned_observer; diff --git a/src/ui/drag-and-drop.cpp b/src/ui/drag-and-drop.cpp index 0ded741b57..ff740f1b9f 100644 --- a/src/ui/drag-and-drop.cpp +++ b/src/ui/drag-and-drop.cpp @@ -274,7 +274,7 @@ bool on_drop(Glib::ValueBase const &value, double x, double y, SPDesktopWidget * }; std::string colorspec; - if (paintdef->get_type() == PaintDef::NONE) { + if (paintdef->get_type() == PaintDef::ColorType::NONE) { colorspec = "none"; } else { if (auto const grad = find_gradient()) { diff --git a/src/ui/util.cpp b/src/ui/util.cpp index 0e6f7f3395..9074850279 100644 --- a/src/ui/util.cpp +++ b/src/ui/util.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -143,6 +144,15 @@ void resize_widget_children(Gtk::Widget *widget) { */ } +unsigned get_child_count(Gtk::Widget const &widget) +{ + unsigned count = 0; + for (auto child = widget.get_first_child(); child; child = child->get_next_sibling()) { + count++; + } + return count; +} + std::vector get_children(Gtk::Widget &widget) { auto children = std::vector{}; @@ -163,6 +173,12 @@ Gtk::Widget &get_nth_child(Gtk::Widget &widget, std::size_t const index) return *child; } +Geom::Interval get_scrollbar_range(Gtk::Scrollbar const &scrollbar) +{ + auto const adj = scrollbar.get_adjustment(); + return {adj->get_lower(), adj->get_upper() - adj->get_page_size()}; +} + /** * Returns a named descendent of parent, which has the given name, or nullptr if there's none. * diff --git a/src/ui/util.h b/src/ui/util.h index 9dfaf8b334..59506c7642 100644 --- a/src/ui/util.h +++ b/src/ui/util.h @@ -28,6 +28,7 @@ #include <2geom/affine.h> #include <2geom/point.h> #include <2geom/rect.h> +#include <2geom/interval.h> /* * Use these errors when building from glade files for graceful @@ -48,6 +49,7 @@ class ustring; namespace Gtk { class Label; +class Scrollbar; class TextBuffer; class Widget; } // namespace Gtk @@ -76,6 +78,8 @@ enum class ForEachResult { _skip // do not recurse into current widget, go to the next one }; +/// Get the number of the widget's children, from get_first_child() through each get_next_sibling(). +unsigned get_child_count(Gtk::Widget const &widget); /// Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling(). std::vector get_children(Gtk::Widget &widget); /// Get the widgetʼs child at the given position. Throws std::out_of_range if the index is invalid. @@ -88,6 +92,9 @@ template void remove_all_children(Widget &widget) } } +/// Return the current scrolling range of a scrollbar. +Geom::Interval get_scrollbar_range(Gtk::Scrollbar const &scrollbar); + /// Call Func with a reference to each child of parent, until it returns _break. /// Accessing children changes between GTK3 & GTK4, so best consolidate it here. /// @param widget The initial widget at the top of the hierarchy, to start at diff --git a/src/ui/widget/color-palette.cpp b/src/ui/widget/color-palette.cpp index 32fc564402..3b252720d2 100644 --- a/src/ui/widget/color-palette.cpp +++ b/src/ui/widget/color-palette.cpp @@ -84,43 +84,43 @@ ColorPalette::ColorPalette(): }); auto& size = get_widget(_builder, "size-slider"); - size.signal_change_value().connect([=,&size](Gtk::ScrollType, double val) { + size.signal_change_value().connect([this, &size](Gtk::ScrollType, double) { _set_tile_size(static_cast(size.get_value())); _signal_settings_changed.emit(); return true; }, true); auto& aspect = get_widget(_builder, "aspect-slider"); - aspect.signal_change_value().connect([=,&aspect](Gtk::ScrollType, double val) { + aspect.signal_change_value().connect([this, &aspect](Gtk::ScrollType, double) { _set_aspect(aspect.get_value()); _signal_settings_changed.emit(); return true; }, true); auto& border = get_widget(_builder, "border-slider"); - border.signal_change_value().connect([=,&border](Gtk::ScrollType, double val) { + border.signal_change_value().connect([this, &border](Gtk::ScrollType, double) { _set_tile_border(static_cast(border.get_value())); _signal_settings_changed.emit(); return true; }, true); auto& rows = get_widget(_builder, "row-slider"); - rows.signal_change_value().connect([=,&rows](Gtk::ScrollType, double val) { + rows.signal_change_value().connect([this, &rows](Gtk::ScrollType, double) { _set_rows(static_cast(rows.get_value())); _signal_settings_changed.emit(); return true; }, true); - auto& sb = get_widget(_builder, "use-sb"); - sb.set_active(_force_scrollbar); - sb.signal_toggled().connect([=,&sb](){ - _enable_scrollbar(sb.get_active()); + auto &use_scrollbar = get_widget(_builder, "use-sb"); + use_scrollbar.set_active(_force_scrollbar); + use_scrollbar.signal_toggled().connect([this, &use_scrollbar]() { + _enable_scrollbar(use_scrollbar.get_active()); _signal_settings_changed.emit(); }); auto& stretch = get_widget(_builder, "stretch"); stretch.set_active(_force_scrollbar); - stretch.signal_toggled().connect([=,&stretch](){ + stretch.signal_toggled().connect([this, &stretch]() { _enable_stretch(stretch.get_active()); _signal_settings_changed.emit(); }); @@ -128,36 +128,46 @@ ColorPalette::ColorPalette(): auto& large = get_widget(_builder, "enlarge"); large.set_active(_large_pinned_panel); - large.signal_toggled().connect([=,&large](){ + large.signal_toggled().connect([this, &large]() { _set_large_pinned_panel(large.get_active()); _signal_settings_changed.emit(); }); update_checkbox(); - auto& sl = get_widget(_builder, "show-labels"); - sl.set_visible(false); - sl.set_active(_show_labels); - sl.signal_toggled().connect([=,&sl](){ - _show_labels = sl.get_active(); + auto &show_labels = get_widget(_builder, "show-labels"); + show_labels.set_visible(false); + show_labels.set_active(_show_labels); + show_labels.signal_toggled().connect([this, &show_labels]() { + _show_labels = show_labels.get_active(); _signal_settings_changed.emit(); - rebuild_widgets(); + rebuild(); }); _scroll.set_min_content_height(1); - _scroll_down.signal_clicked().connect([=](){ scroll(0, get_palette_height(), get_tile_height() + _border, true); }); - _scroll_up.signal_clicked().connect([=](){ scroll(0, -get_palette_height(), get_tile_height() + _border, true); }); - _scroll_left.signal_clicked().connect([=](){ scroll(-10 * (get_tile_width() + _border), 0, 0.0, false); }); - _scroll_right.signal_clicked().connect([=](){ scroll(10 * (get_tile_width() + _border), 0, 0.0, false); }); + _scroll_down.signal_clicked().connect([this]() { + scroll(0, get_palette_height(), _compute_tile_height() + _border, true); + }); + _scroll_up.signal_clicked().connect([this]() { + scroll(0, -get_palette_height(), _compute_tile_height() + _border, true); + }); + _scroll_left.signal_clicked().connect([this]() { + scroll(-10 * (_compute_tile_width() + _border), 0, 0.0, false); + }); + _scroll_right.signal_clicked().connect([this]() { + scroll(10 * (_compute_tile_width() + _border), 0, 0.0, false); + }); set_vexpand_set(true); - set_up_scrolling(); + _set_up_scrolling(); - connectAfterResize([this] (int w, int h, int) { + connectAfterResize([this](int w, int h, int) { auto const a = Geom::IntPoint{w, h}; - if (_allocation == a) return; + if (_allocation == a) { + return; + } _allocation = a; - set_up_scrolling(); + _set_up_scrolling(); }); } @@ -167,16 +177,18 @@ ColorPalette::~ColorPalette() { } } -Gtk::Popover& ColorPalette::get_settings_popover() { +Gtk::Popover& ColorPalette::get_settings_popover() +{ return get_widget(_builder, "config-popup"); } -void ColorPalette::set_settings_visibility(bool show) { - auto& btn_menu = get_widget(_builder, "btn-menu"); - btn_menu.set_visible(show); +void ColorPalette::set_settings_visibility(bool show) +{ + get_widget(_builder, "btn-menu").set_visible(show); } -void ColorPalette::do_scroll(int dx, int dy) { +void ColorPalette::do_scroll(int dx, int dy) +{ if (auto vert = _scroll.get_vscrollbar()) { vert->get_adjustment()->set_value(vert->get_adjustment()->get_value() + dy); } @@ -185,12 +197,8 @@ void ColorPalette::do_scroll(int dx, int dy) { } } -std::pair get_range(Gtk::Scrollbar& sb) { - auto adj = sb.get_adjustment(); - return std::make_pair(adj->get_lower(), adj->get_upper() - adj->get_page_size()); -} - -gboolean ColorPalette::scroll_cb(gpointer self) { +gboolean ColorPalette::scroll_callback(gpointer self) +{ auto ptr = static_cast(self); bool fire_again = false; @@ -202,12 +210,11 @@ gboolean ColorPalette::scroll_cb(gpointer self) { fire_again = false; // cancel timer } else { - auto pos = value + ptr->_scroll_step; + auto const pos = value + ptr->_scroll_step; vert->get_adjustment()->set_value(pos); - auto range = get_range(*vert); - if (pos > range.first && pos < range.second) { - // not yet done - fire_again = true; // fire this callback again + if (get_scrollbar_range(*vert).interiorContains(pos)) { + // not yet done, fire this callback again + fire_again = true; } } } @@ -219,7 +226,8 @@ gboolean ColorPalette::scroll_cb(gpointer self) { return fire_again; } -void ColorPalette::scroll(int dx, int dy, double snap, bool smooth) { +void ColorPalette::scroll(int dx, int dy, double snap, bool smooth) +{ if (auto vert = _scroll.get_vscrollbar()) { if (smooth && dy != 0.0) { _scroll_final = vert->get_adjustment()->get_value() + dy; @@ -227,17 +235,12 @@ void ColorPalette::scroll(int dx, int dy, double snap, bool smooth) { // round it to whole 'dy' increments _scroll_final -= fmod(_scroll_final, snap); } - auto range = get_range(*vert); - if (_scroll_final < range.first) { - _scroll_final = range.first; - } - else if (_scroll_final > range.second) { - _scroll_final = range.second; - } + _scroll_final = get_scrollbar_range(*vert).clamp(_scroll_final); + _scroll_step = dy / 4.0; if (!_active_timeout && vert->get_adjustment()->get_value() != _scroll_final) { // limit refresh to 60 fps, in practice it will be slower - _active_timeout = g_timeout_add(1000 / 60, &ColorPalette::scroll_cb, this); + _active_timeout = g_timeout_add(1000 / 60, &ColorPalette::scroll_callback, this); } } else { @@ -249,29 +252,14 @@ void ColorPalette::scroll(int dx, int dy, double snap, bool smooth) { } } -int ColorPalette::get_tile_size() const { - return _size; -} - -int ColorPalette::get_tile_border() const { - return _border; -} - -int ColorPalette::get_rows() const { - return _rows; -} - -double ColorPalette::get_aspect() const { - return _aspect; -} - -void ColorPalette::set_tile_border(int border) { +void ColorPalette::set_tile_border(int border) +{ _set_tile_border(border); - auto& slider = get_widget(_builder, "border-slider"); - slider.set_value(border); + get_widget(_builder, "border-slider").set_value(border); } -void ColorPalette::_set_tile_border(int border) { +void ColorPalette::_set_tile_border(int border) +{ if (border == _border) return; if (border < 0 || border > 100) { @@ -280,16 +268,17 @@ void ColorPalette::_set_tile_border(int border) { } _border = border; - refresh(); + _refresh(); } -void ColorPalette::set_tile_size(int size) { +void ColorPalette::set_tile_size(int size) +{ _set_tile_size(size); - auto& slider = get_widget(_builder, "size-slider"); - slider.set_value(size); + get_widget(_builder, "size-slider").set_value(size); } -void ColorPalette::_set_tile_size(int size) { +void ColorPalette::_set_tile_size(int size) +{ if (size == _size) return; if (size < 1 || size > 1000) { @@ -298,13 +287,13 @@ void ColorPalette::_set_tile_size(int size) { } _size = size; - refresh(); + _refresh(); } -void ColorPalette::set_aspect(double aspect) { +void ColorPalette::set_aspect(double aspect) +{ _set_aspect(aspect); - auto& slider = get_widget(_builder, "aspect-slider"); - slider.set_value(aspect); + get_widget(_builder, "aspect-slider").set_value(aspect); } void ColorPalette::_set_aspect(double aspect) { @@ -316,21 +305,23 @@ void ColorPalette::_set_aspect(double aspect) { } _aspect = aspect; - refresh(); + _refresh(); } -void ColorPalette::refresh() { - set_up_scrolling(); +void ColorPalette::_refresh() +{ + _set_up_scrolling(); queue_resize(); } -void ColorPalette::set_rows(int rows) { +void ColorPalette::set_rows(int rows) +{ _set_rows(rows); - auto& slider = get_widget(_builder, "row-slider"); - slider.set_value(rows); + get_widget(_builder, "row-slider").set_value(rows); } -void ColorPalette::_set_rows(int rows) { +void ColorPalette::_set_rows(int rows) +{ if (rows == _rows) return; if (rows < 1 || rows > 1000) { @@ -339,87 +330,84 @@ void ColorPalette::_set_rows(int rows) { } _rows = rows; update_checkbox(); - refresh(); + _refresh(); } -void ColorPalette::update_checkbox() { - auto& sb = get_widget(_builder, "use-sb"); +void ColorPalette::update_checkbox() +{ + auto &use_scrollbar = get_widget(_builder, "use-sb"); // scrollbar can only be applied to single-row layouts - bool sens = _rows == 1; - if (sb.get_sensitive() != sens) sb.set_sensitive(sens); + bool const sens = _rows == 1; + if (use_scrollbar.get_sensitive() != sens) { + use_scrollbar.set_sensitive(sens); + } } -void ColorPalette::set_compact(bool compact) { +void ColorPalette::set_compact(bool compact) +{ if (_compact != compact) { _compact = compact; - set_up_scrolling(); + _set_up_scrolling(); get_widget(_builder, "row-slider").set_visible(compact); get_widget(_builder, "row-label").set_visible(compact); get_widget(_builder, "enlarge").set_visible(compact); - // get_widget(_builder, "show-labels").set_visible(false); } } -bool ColorPalette::is_scrollbar_enabled() const { - return _force_scrollbar; -} - -bool ColorPalette::is_stretch_enabled() const { - return _stretch_tiles; -} - -void ColorPalette::enable_stretch(bool enable) { +void ColorPalette::enable_stretch(bool enable) +{ auto& stretch = get_widget(_builder, "stretch"); stretch.set_active(enable); _enable_stretch(enable); } -void ColorPalette::_enable_stretch(bool enable) { +void ColorPalette::_enable_stretch(bool enable) +{ if (_stretch_tiles == enable) return; _stretch_tiles = enable; _normal_box.set_halign(enable ? Gtk::Align::FILL : Gtk::Align::START); update_stretch(); - refresh(); + _refresh(); } -void ColorPalette::enable_labels(bool labels) { - auto& sl = get_widget(_builder, "show-labels"); - sl.set_active(labels); +void ColorPalette::enable_labels(bool labels) +{ + get_widget(_builder, "show-labels").set_active(labels); if (_show_labels != labels) { _show_labels = labels; - rebuild_widgets(); - refresh(); + rebuild(); } } -void ColorPalette::update_stretch() { - auto& aspect = get_widget(_builder, "aspect-slider"); - aspect.set_sensitive(!_stretch_tiles); - auto& label = get_widget(_builder, "aspect-label"); - label.set_sensitive(!_stretch_tiles); +void ColorPalette::update_stretch() +{ + get_widget(_builder, "aspect-slider").set_sensitive(!_stretch_tiles); + get_widget(_builder, "aspect-label").set_sensitive(!_stretch_tiles); } -void ColorPalette::enable_scrollbar(bool show) { - auto& sb = get_widget(_builder, "use-sb"); - sb.set_active(show); +void ColorPalette::enable_scrollbar(bool show) +{ + get_widget(_builder, "use-sb").set_active(show); _enable_scrollbar(show); } -void ColorPalette::_enable_scrollbar(bool show) { +void ColorPalette::_enable_scrollbar(bool show) +{ if (_force_scrollbar == show) return; _force_scrollbar = show; - set_up_scrolling(); + _set_up_scrolling(); } -void ColorPalette::set_up_scrolling() { +void ColorPalette::_set_up_scrolling() +{ auto &box = get_widget(_builder, "palette-box"); auto &btn_menu = get_widget(_builder, "btn-menu"); - auto const colors = UI::get_children(_normal_box); - auto normal_count = std::max(1, static_cast(colors.size())); - auto pinned_count = std::max(1, static_cast(UI::get_children(_pinned_box).size())); + unsigned const num_colors = get_child_count(_normal_box); + unsigned const normal_count = std::max(1U, num_colors); + unsigned const pinned_count = std::max(1U, get_child_count(_pinned_box)); _normal_box.set_max_children_per_line(normal_count); _normal_box.set_min_children_per_line(1); @@ -428,8 +416,8 @@ void ColorPalette::set_up_scrolling() { auto alloc_width = _normal_box.get_parent()->get_allocated_width(); // if page-size is defined, align color tiles in columns - if (_page_size > 1 && alloc_width > 1 && !_show_labels && !colors.empty()) { - int width = get_tile_width(); + if (_page_size > 1 && alloc_width > 1 && !_show_labels && num_colors) { + int const width = _compute_tile_width(); if (width > 1) { int cols = alloc_width / (width + _border); cols = std::max(cols - cols % _page_size, _page_size); @@ -480,8 +468,8 @@ void ColorPalette::set_up_scrolling() { _scroll_btn.set_visible(true); } - int div = _large_pinned_panel ? (_rows > 2 ? 2 : 1) : _rows; - _pinned_box.set_max_children_per_line(std::max((pinned_count + div - 1) / div, 1)); + int const div = _large_pinned_panel ? (_rows > 2 ? 2 : 1) : _rows; + _pinned_box.set_max_children_per_line(std::max((pinned_count + div - 1) / div, 1U)); _pinned_box.set_margin_end(_border); } else { @@ -507,18 +495,19 @@ void ColorPalette::set_up_scrolling() { resize(); } -int ColorPalette::get_tile_size(bool horz) const { +int ColorPalette::_compute_tile_size(bool horz) const +{ if (_stretch_tiles) return _size; - double aspect = horz ? _aspect : -_aspect; - int scale = _show_labels ? 2.0 : 1.0; + double const aspect = horz ? _aspect : -_aspect; + int const scale = _show_labels ? 2 : 1; int size = 0; if (aspect > 0) { - size = static_cast(round((1.0 + aspect) * _size)); + size = static_cast(std::round((1.0 + aspect) * _size)); } else if (aspect < 0) { - size = static_cast(round((1.0 / (1.0 - aspect)) * _size)); + size = static_cast(std::round((1.0 / (1.0 - aspect)) * _size)); } else { size = _size; @@ -526,40 +515,30 @@ int ColorPalette::get_tile_size(bool horz) const { return size * scale; } -int ColorPalette::get_tile_width() const { - return get_tile_size(true); -} - -int ColorPalette::get_tile_height() const { - return get_tile_size(false); -} +int ColorPalette::_compute_tile_width() const { return _compute_tile_size(true ); } +int ColorPalette::_compute_tile_height() const { return _compute_tile_size(false); } -int ColorPalette::get_palette_height() const { - return (get_tile_height() + _border) * _rows; +int ColorPalette::get_palette_height() const +{ + return (_compute_tile_height() + _border) * _rows; } -void ColorPalette::set_large_pinned_panel(bool large) { - auto& checkbox = get_widget(_builder, "enlarge"); - checkbox.set_active(large); +void ColorPalette::set_large_pinned_panel(bool large) +{ + get_widget(_builder, "enlarge").set_active(large); _set_large_pinned_panel(large); } -void ColorPalette::_set_large_pinned_panel(bool large) { +void ColorPalette::_set_large_pinned_panel(bool large) +{ if (_large_pinned_panel == large) return; _large_pinned_panel = large; - refresh(); -} - -bool ColorPalette::is_pinned_panel_large() const { - return _large_pinned_panel; + _refresh(); } -bool ColorPalette::are_labels_enabled() const { - return _show_labels; -} - -void ColorPalette::resize() { +void ColorPalette::resize() +{ if ((_rows == 1 && _force_scrollbar) || !_compact) { // auto size for single row to allocate space for scrollbar _scroll.set_size_request(-1, -1); @@ -575,9 +554,9 @@ void ColorPalette::resize() { _pinned_box.set_column_spacing(_border); _pinned_box.set_row_spacing(_border); - int width = get_tile_width(); - int height = get_tile_height(); - for (auto item : _normal_items) { + int const width = _compute_tile_width(); + int const height = _compute_tile_height(); + for (auto &item : _normal_items) { item->set_size_request(width, height); } @@ -587,52 +566,96 @@ void ColorPalette::resize() { double mult = _rows > 2 ? _rows / 2.0 : 2.0; pinned_width = pinned_height = static_cast((height + _border) * mult - _border); } - for (auto item : _pinned_items) { + for (auto &item : _pinned_items) { item->set_size_request(pinned_width, pinned_height); } } -void ColorPalette::set_colors(std::vector const &swatches) +void ColorPalette::set_fill_stroke_indicators(std::optional fill_key, std::optional stroke_key) +{ + // Clear fill and stroke indicators from items currently carrying them + if (_current_fill_key) { + auto const &highlighted_items = _color_to_widgets[*_current_fill_key]; + std::for_each(highlighted_items.begin(), + highlighted_items.end(), + [](auto *item) { item->set_fill_indicator(false); }); + } + if (_current_stroke_key) { + auto const &highlighted_items = _color_to_widgets[*_current_stroke_key]; + std::for_each(highlighted_items.begin(), + highlighted_items.end(), + [](auto *item) { item->set_stroke_indicator(false); }); + } + + // Set new fill and stroke indicators + _current_fill_key = fill_key; + _current_stroke_key = stroke_key; + if (_current_fill_key) { + auto const &items_to_highlight = _color_to_widgets[*_current_fill_key]; + std::for_each(items_to_highlight.begin(), + items_to_highlight.end(), + [](auto *item) { item->set_fill_indicator(true); }); + } + if (_current_stroke_key) { + auto const &items_to_highlight = _color_to_widgets[*_current_stroke_key]; + std::for_each(items_to_highlight.begin(), + items_to_highlight.end(), + [](auto *item) { item->set_stroke_indicator(true); }); + } + queue_draw(); +} + +void ColorPalette::add_item(std::unique_ptr swatch, std::optional key) +{ + if (!swatch) { + return; + } + + if (key) { + _color_to_widgets[*key].emplace_back(swatch.get()); + } + (swatch->is_pinned() ? _pinned_items : _normal_items).emplace_back(std::move(swatch)); +} + +void ColorPalette::clear() { + _color_to_widgets.clear(); _normal_items.clear(); _pinned_items.clear(); - - for (auto item : swatches) { - if (item->is_pinned()) { - _pinned_items.emplace_back(item); - } else { - _normal_items.emplace_back(item); - } - item->signal_modified().connect([=] { - UI::for_each_child(*item->get_parent(), [=](Gtk::Widget& w) { - if (auto label = dynamic_cast(&w)) { - label->set_text(item->get_description()); - } - return UI::ForEachResult::_continue; - }); - }); - } - rebuild_widgets(); - refresh(); + set_page_size(0); } -Gtk::Widget *ColorPalette::_get_widget(Dialog::ColorItem *item) { - if (auto parent = item->get_parent()) { - auto &flowbox = dynamic_cast(*parent); - flowbox.remove(*item); +/// Return either the passed color item or a widget wrapping it along with a label. +Gtk::Widget &ColorPalette::_create_wrapper_widget(Dialog::ColorItem &item) const +{ + if (auto flowbox = dynamic_cast(item.get_parent())) { + flowbox->remove(item); } if (_show_labels) { - item->set_valign(Gtk::Align::CENTER); + item.set_valign(Gtk::Align::CENTER); auto const box = Gtk::make_managed(); - auto const label = Gtk::make_managed(item->get_description()); - box->append(*item); + auto const label = Gtk::make_managed(item.get_description()); + box->append(item); box->append(*label); - return box; + return *box; } - return Gtk::manage(item); + return item; } -void ColorPalette::rebuild_widgets() +bool ColorPalette::_should_show_normal_item(Dialog::ColorItem const *item) const +{ + // in the tile mode (no labels), group headers are hidden: + if (!_show_labels && item->is_group()) { + return false; + } + // in the list mode with labels, do not show fillers: + if (_show_labels && item->is_filler()) { + return false; + } + return true; +} + +void ColorPalette::rebuild() { _normal_box.freeze_notify(); _pinned_box.freeze_notify(); @@ -640,21 +663,17 @@ void ColorPalette::rebuild_widgets() UI::remove_all_children(_normal_box); UI::remove_all_children(_pinned_box); - for (auto item : _normal_items) { - // in a tile mode (no labels) group headers are hidden: - if (!_show_labels && item->is_group()) continue; - - // in a list mode with labels, do not show fillers: - if (_show_labels && item->is_filler()) continue; - - _normal_box.append(*_get_widget(item)); + for (auto &item : _normal_items) { + if (_should_show_normal_item(item.get())) { + _normal_box.append(_create_wrapper_widget(*item)); + } } - for (auto item : _pinned_items) { - _pinned_box.append(*_get_widget(item)); + for (auto &item : _pinned_items) { + _pinned_box.append(_create_wrapper_widget(*item)); } - set_up_scrolling(); - + _set_up_scrolling(); + _refresh(); _normal_box.thaw_notify(); _pinned_box.thaw_notify(); } @@ -706,7 +725,7 @@ void ColorPalette::set_palettes(std::vector const &palettes) auto& name = it->name; auto& id = it->id; auto item = std::make_unique(group, name, id, it->colors); - item->signal_activate().connect([=](){ + item->signal_activate().connect([this, id](){ if (!_in_update) { _in_update = true; _signal_palette_selected.emit(id); @@ -719,15 +738,8 @@ void ColorPalette::set_palettes(std::vector const &palettes) } } -sigc::signal& ColorPalette::get_palette_selected_signal() { - return _signal_palette_selected; -} - -sigc::signal& ColorPalette::get_settings_changed_signal() { - return _signal_settings_changed; -} - -void ColorPalette::set_selected(const Glib::ustring& id) { +void ColorPalette::set_selected(const Glib::ustring& id) +{ _in_update = true; for (auto const &item : _palette_menu_items) { @@ -737,11 +749,13 @@ void ColorPalette::set_selected(const Glib::ustring& id) { _in_update = false; } -void ColorPalette::set_page_size(int page_size) { +void ColorPalette::set_page_size(int page_size) +{ _page_size = page_size; } -void ColorPalette::set_filter(std::function filter) { +void ColorPalette::set_filter(std::function filter) +{ _normal_box.set_filter_func([=](Gtk::FlowBoxChild* c){ auto child = c->get_child(); if (auto box = dynamic_cast(child)) { @@ -754,7 +768,8 @@ void ColorPalette::set_filter(std::function fil }); } -void ColorPalette::apply_filter() { +void ColorPalette::apply_filter() +{ _normal_box.invalidate_filter(); } diff --git a/src/ui/widget/color-palette.h b/src/ui/widget/color-palette.h index 33f726f4f2..b52bafa65b 100644 --- a/src/ui/widget/color-palette.h +++ b/src/ui/widget/color-palette.h @@ -14,6 +14,8 @@ #include #include +#include +#include #include #include #include @@ -22,6 +24,7 @@ #include "ui/widget/palette_t.h" #include "ui/widget/bin.h" +#include "util/variant-visitor.h" namespace Gtk { class Builder; @@ -31,6 +34,33 @@ class Popover; class ScrolledWindow; } // namespace Gtk +class SPGradient; + +namespace detail { +using NoColor = std::monostate; +using Rgb8bit = std::array; +using ColorKey = std::variant; +} // namespace detail + +namespace std { +template <> +struct hash { + /// Hash a ColorKey to enable its use in an unordered_map. + size_t operator()(detail::ColorKey key) const { + return std::visit(Inkscape::VariantVisitor{[](detail::NoColor) -> size_t { return 0x0ULL; }, + [](detail::Rgb8bit const &rgb) -> size_t { + return static_cast(rgb[0]) << 24 | + static_cast(rgb[1]) << 16 | + static_cast(rgb[2]) << 8 | + 0xCAFEBABE'000000'01; // odd number + }, + // Note: memory addresses are even due to alignment + [](SPGradient *ptr) -> size_t { return (size_t)(ptr) & ~0x1ULL; }}, + key); + } +}; +} // namespace std + namespace Inkscape::UI { namespace Dialog { @@ -45,11 +75,28 @@ class PopoverMenu; class ColorPalette : public UI::Widget::Bin { public: + using ColorKey = detail::ColorKey; + using NoColor = detail::NoColor; + using Rgb8bit = detail::Rgb8bit; + ColorPalette(); ~ColorPalette() override; // set colors presented in a palette void set_colors(std::vector const &swatches); + + /// Remove all colors from the palette + void clear(); + + /// Add a ColorItem to the palette and optionally tie it to a key. + void add_item(std::unique_ptr swatch, std::optional key); + + /// Reconstruct the widget after colors have been added/removed. + void rebuild(); + + /// Display fill and stroke indicators for the given color keys or clear them by passing empty optionals. + void set_fill_stroke_indicators(std::optional fill_key, std::optional stroke_key); + // list of palettes to present in the menu void set_palettes(const std::vector& palettes); // enable compact mode (true) with mini-scroll buttons, or normal mode (false) with regular scrollbars @@ -72,31 +119,31 @@ public: // Show/hide settings void set_settings_visibility(bool show); - int get_tile_size() const; - int get_tile_border() const; - int get_rows() const; - double get_aspect() const; - bool is_scrollbar_enabled() const; - bool is_stretch_enabled() const; - bool is_pinned_panel_large() const; - bool are_labels_enabled() const; + int get_tile_size() const { return _size; } + int get_tile_border() const { return _border; } + int get_rows() const { return _rows; } + double get_aspect() const { return _aspect; } + bool is_scrollbar_enabled() const { return _force_scrollbar; } + bool is_stretch_enabled() const { return _stretch_tiles; } + bool is_pinned_panel_large() const {return _large_pinned_panel; } + bool are_labels_enabled() const { return _show_labels; } void set_selected(const Glib::ustring& id); - sigc::signal& get_palette_selected_signal(); - sigc::signal& get_settings_changed_signal(); + sigc::signal &get_palette_selected_signal() { return _signal_palette_selected; } + sigc::signal &get_settings_changed_signal() { return _signal_settings_changed; } - Gtk::Popover& get_settings_popover(); + Gtk::Popover &get_settings_popover(); void set_filter(std::function filter); void apply_filter(); private: void resize(); - void set_up_scrolling(); + void _set_up_scrolling(); void scroll(int dx, int dy, double snap, bool smooth); void do_scroll(int dx, int dy); - static gboolean scroll_cb(gpointer self); + static gboolean scroll_callback(gpointer self); void _set_tile_size(int size_px); void _set_tile_border(int border_px); void _set_rows(int rows); @@ -104,19 +151,25 @@ private: void _enable_scrollbar(bool show); void _enable_stretch(bool enable); void _set_large_pinned_panel(bool large); + bool _should_show_normal_item(Dialog::ColorItem const *item) const; void update_checkbox(); void update_stretch(); - int get_tile_size(bool horz) const; - int get_tile_width() const; - int get_tile_height() const; + int _compute_tile_size(bool horz) const; + int _compute_tile_width() const; + int _compute_tile_height() const; int get_palette_height() const; + Gtk::Widget &_create_wrapper_widget(Dialog::ColorItem &item) const; + void _refresh(); + + std::vector> _normal_items; + std::vector> _pinned_items; - Gtk::Widget *_get_widget(Dialog::ColorItem *item); - void rebuild_widgets(); - void refresh(); + // A map from colors to their respective widgets. Used to quickly find the widgets corresponding + // to the current fill/stroke color, in order to update their fill/stroke indicators. + std::unordered_map> _color_to_widgets; - std::vector _normal_items; - std::vector _pinned_items; + std::optional _current_fill_key; + std::optional _current_stroke_key; Glib::RefPtr _builder; Gtk::FlowBox& _normal_box; diff --git a/src/ui/widget/selected-style.cpp b/src/ui/widget/selected-style.cpp index e0781d1005..650a1bd977 100644 --- a/src/ui/widget/selected-style.cpp +++ b/src/ui/widget/selected-style.cpp @@ -182,7 +182,7 @@ SelectedStyle::SelectedStyle() // copied from drag-and-drop.cpp, case PaintDef std::string colorspec; - if (paintdef.get_type() == PaintDef::NONE) { + if (paintdef.get_type() == PaintDef::ColorType::NONE) { colorspec = "none"; } else { auto const [r, g, b] = paintdef.get_rgb(); diff --git a/src/widgets/paintdef.cpp b/src/widgets/paintdef.cpp index 3685da6bb8..17a3400075 100644 --- a/src/widgets/paintdef.cpp +++ b/src/widgets/paintdef.cpp @@ -38,8 +38,6 @@ #include "paintdef.h" #include -#include -#include #include #include #include @@ -47,23 +45,17 @@ #include #include -PaintDef::PaintDef() - : description(C_("Paint", "None")) - , type(NONE) - , rgb({0, 0, 0}) -{ -} - -PaintDef::PaintDef(std::array const &rgb, std::string description, Glib::ustring tooltip) - : description(std::move(description)), tooltip(std::move(tooltip)) - , type(RGB) +PaintDef::PaintDef(Rgb8bit const &rgb, std::string description, Glib::ustring tooltip) + : description(std::move(description)) + , tooltip(std::move(tooltip)) + , type(ColorType::RGB) , rgb(rgb) { } std::string PaintDef::get_color_id() const { - if (type == NONE) { + if (type == ColorType::NONE) { return "none"; } if (!description.empty() && description[0] != '#') { @@ -110,7 +102,7 @@ std::vector PaintDef::getMIMEData(char const *mime_type) const } else if (std::strcmp(mime_type, mimeOSWB_COLOR) == 0) { std::string tmp(""); switch (get_type()) { - case PaintDef::NONE: + case PaintDef::ColorType::NONE: tmp += ""; break; default: @@ -136,7 +128,7 @@ bool PaintDef::fromMIMEData(char const *mime_type, std::span data) if (std::strcmp(mime_type, mimeX_COLOR) == 0) { if (data.size() == 8) { // Careful about endian issues. - type = PaintDef::RGB; + type = PaintDef::ColorType::RGB; auto const vals = reinterpret_cast(data.data()); rgb[0] = 0x0ff & (vals[0] >> 8); rgb[1] = 0x0ff & (vals[1] >> 8); @@ -146,12 +138,12 @@ bool PaintDef::fromMIMEData(char const *mime_type, std::span data) } else if (std::strcmp(mime_type, mimeOSWB_COLOR) == 0) { std::string xml(data.data(), data.size()); if (xml.find("") != std::string::npos) { - type = PaintDef::NONE; + type = PaintDef::ColorType::NONE; rgb = {0, 0, 0}; return true; } else if (auto pos = xml.find("", pos)); - type = PaintDef::RGB; + type = PaintDef::ColorType::RGB; if (auto numPos = srgb.find("r="); numPos != std::string::npos) { double dbl = Glib::Ascii::strtod(srgb.substr(numPos + 3)); rgb[0] = static_cast(255 * dbl); diff --git a/src/widgets/paintdef.h b/src/widgets/paintdef.h index 68da249943..6692912ec5 100644 --- a/src/widgets/paintdef.h +++ b/src/widgets/paintdef.h @@ -38,12 +38,13 @@ #ifndef INKSCAPE_WIDGETS_PAINTDEF_H #define INKSCAPE_WIDGETS_PAINTDEF_H +#include +#include +#include +#include #include #include #include -#include -#include -#include inline constexpr auto mimeOSWB_COLOR = "application/x-oswb-color"; inline constexpr auto mimeX_COLOR = "application/x-color"; @@ -55,33 +56,34 @@ inline constexpr auto mimeTEXT = "text/plain"; class PaintDef { public: - enum ColorType + enum class ColorType { NONE, RGB }; + using Rgb8bit = std::array; /// Create a color of type NONE - PaintDef(); + PaintDef() = default; /// Create a color of type RGB - PaintDef(std::array const &rgb, std::string description, Glib::ustring tooltip); + PaintDef(Rgb8bit const &rgb, std::string description, Glib::ustring tooltip); std::string get_color_id() const; const Glib::ustring& get_tooltip() const; std::string const &get_description() const { return description; } ColorType get_type() const { return type; } - std::array const &get_rgb() const { return rgb; } + Rgb8bit const &get_rgb() const { return rgb; } std::vector getMIMEData(char const *mime_type) const; bool fromMIMEData(char const *mime_type, std::span data); protected: - std::string description; + std::string description = C_("Paint", "None"); Glib::ustring tooltip; - ColorType type; - std::array rgb; + ColorType type = ColorType::NONE; + Rgb8bit rgb = {0, 0, 0}; }; #endif // INKSCAPE_WIDGETS_PAINTDEF_H -- GitLab From 60895fe67cd6ad293fd7dbcc73790ee731ad4223 Mon Sep 17 00:00:00 2001 From: Rafael Siejakowski Date: Sat, 27 Apr 2024 19:09:55 +0200 Subject: [PATCH 2/2] PaintDef: improve code readability This commit improves the readability of code in the member function implementations of the class PaintDef. --- po/POTFILES.src.in | 2 +- src/widgets/paintdef.cpp | 82 ++++++++++++++++++++-------------------- src/widgets/paintdef.h | 5 ++- 3 files changed, 46 insertions(+), 43 deletions(-) diff --git a/po/POTFILES.src.in b/po/POTFILES.src.in index 22fd4c8022..5a4c22df24 100644 --- a/po/POTFILES.src.in +++ b/po/POTFILES.src.in @@ -431,5 +431,5 @@ ${_build_dir}/share/templates/templates.h ../src/util/font-collections.h ../src/util/paper.cpp ../src/vanishing-point.cpp -../src/widgets/paintdef.cpp +../src/widgets/paintdef.h ../src/widgets/sp-attribute-widget.cpp diff --git a/src/widgets/paintdef.cpp b/src/widgets/paintdef.cpp index 17a3400075..0550b6de05 100644 --- a/src/widgets/paintdef.cpp +++ b/src/widgets/paintdef.cpp @@ -37,13 +37,13 @@ #include "paintdef.h" -#include #include -#include #include #include -#include #include +#include +#include +#include PaintDef::PaintDef(Rgb8bit const &rgb, std::string description, Glib::ustring tooltip) : description(std::move(description)) @@ -75,31 +75,32 @@ std::string PaintDef::get_color_id() const auto [r, g, b] = rgb; char buf[12]; std::snprintf(buf, 12, "rgb%02x%02x%02x", r, g, b); - return std::string(buf); + return buf; } const Glib::ustring& PaintDef::get_tooltip() const { return tooltip; } -std::vector PaintDef::getMIMEData(char const *mime_type) const +std::vector PaintDef::getMIMEData(std::string_view mime_type) const { - auto from_data = [] (void const *p, int len) { - std::vector v(len); - std::memcpy(v.data(), p, len); - return v; - }; - - auto const [r, g, b] = rgb; + unsigned const r = rgb[0]; + unsigned const g = rgb[1]; + unsigned const b = rgb[2]; - if (std::strcmp(mime_type, mimeTEXT) == 0) { - std::array tmp; - std::snprintf(tmp.data(), 8, "#%02x%02x%02x", r, g, b); - return from_data(tmp.data(), tmp.size()); - } else if (std::strcmp(mime_type, mimeX_COLOR) == 0) { - auto const tmp = std::to_array({(uint16_t)((r << 8) | r), (uint16_t)((g << 8) | g), (uint16_t)((b << 8) | b), uint16_t{0xffff}}); - return from_data(tmp.data(), tmp.size() * sizeof(decltype(tmp)::value_type)); - } else if (std::strcmp(mime_type, mimeOSWB_COLOR) == 0) { + if (mime_type == mimeTEXT) { + constexpr size_t buflen = 8; + char tmp[buflen]; + std::snprintf(tmp, buflen, "#%02x%02x%02x", r, g, b); + return {tmp, tmp + buflen}; + } + if (mime_type == mimeX_COLOR) { + constexpr size_t buflen = 4; + uint16_t tmp[buflen] = {(uint16_t)((r << 8) | r), (uint16_t)((g << 8) | g), (uint16_t)((b << 8) | b), + uint16_t{0xffff}}; + return {(char *)tmp, (char *)tmp + buflen * sizeof(uint16_t) / sizeof(char)}; + } + if (mime_type == mimeOSWB_COLOR) { std::string tmp(""); switch (get_type()) { case PaintDef::ColorType::NONE: @@ -113,48 +114,49 @@ std::vector PaintDef::getMIMEData(char const *mime_type) const tmp += Glib::Ascii::dtostr(g / 255.0); tmp += "\" b=\""; tmp += Glib::Ascii::dtostr(b / 255.0); - tmp += "\"/>"; - tmp += ""; + tmp += "\"/>"; } tmp += ""; - return from_data(tmp.c_str(), tmp.size()); - } else { - return {}; + return {tmp.c_str(), tmp.c_str() + tmp.size()}; } + return {}; } -bool PaintDef::fromMIMEData(char const *mime_type, std::span data) +bool PaintDef::fromMIMEData(std::string_view mime_type, std::span data) { - if (std::strcmp(mime_type, mimeX_COLOR) == 0) { - if (data.size() == 8) { - // Careful about endian issues. - type = PaintDef::ColorType::RGB; - auto const vals = reinterpret_cast(data.data()); - rgb[0] = 0x0ff & (vals[0] >> 8); - rgb[1] = 0x0ff & (vals[1] >> 8); - rgb[2] = 0x0ff & (vals[2] >> 8); - return true; + if (mime_type == mimeX_COLOR) { + if (data.size() != 8) { + return false; } - } else if (std::strcmp(mime_type, mimeOSWB_COLOR) == 0) { + // Careful about endian issues. + type = PaintDef::ColorType::RGB; + auto const vals = reinterpret_cast(data.data()); + rgb[0] = vals[0] >> 8; + rgb[1] = vals[1] >> 8; + rgb[2] = vals[2] >> 8; + return true; + } + if (mime_type == mimeOSWB_COLOR) { std::string xml(data.data(), data.size()); if (xml.find("") != std::string::npos) { type = PaintDef::ColorType::NONE; rgb = {0, 0, 0}; return true; - } else if (auto pos = xml.find("", pos)); type = PaintDef::ColorType::RGB; if (auto numPos = srgb.find("r="); numPos != std::string::npos) { double dbl = Glib::Ascii::strtod(srgb.substr(numPos + 3)); - rgb[0] = static_cast(255 * dbl); + rgb[0] = static_cast(255 * dbl); } if (auto numPos = srgb.find("g="); numPos != std::string::npos) { double dbl = Glib::Ascii::strtod(srgb.substr(numPos + 3)); - rgb[1] = static_cast(255 * dbl); + rgb[1] = static_cast(255 * dbl); } if (auto numPos = srgb.find("b="); numPos != std::string::npos) { double dbl = Glib::Ascii::strtod(srgb.substr(numPos + 3)); - rgb[2] = static_cast(255 * dbl); + rgb[2] = static_cast(255 * dbl); } if (auto pos = xml.find("", pos)); diff --git a/src/widgets/paintdef.h b/src/widgets/paintdef.h index 6692912ec5..24a2733786 100644 --- a/src/widgets/paintdef.h +++ b/src/widgets/paintdef.h @@ -44,6 +44,7 @@ #include #include #include +#include #include inline constexpr auto mimeOSWB_COLOR = "application/x-oswb-color"; @@ -76,8 +77,8 @@ public: ColorType get_type() const { return type; } Rgb8bit const &get_rgb() const { return rgb; } - std::vector getMIMEData(char const *mime_type) const; - bool fromMIMEData(char const *mime_type, std::span data); + std::vector getMIMEData(std::string_view mime_type) const; + bool fromMIMEData(std::string_view mime_type, std::span data); protected: std::string description = C_("Paint", "None"); -- GitLab