diff --git a/po/POTFILES.src.in b/po/POTFILES.src.in index 22fd4c802234face5710f538d2e7a7dc3bd94066..5a4c22df24664ae814a9334515961ed9ed823db2 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/ui/dialog/color-item.cpp b/src/ui/dialog/color-item.cpp index 42de70f29c5e00f04378a736fafece6a5241aed5..338f884cdd251f0db842b176f7debad4b516acd7 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 61d1078c544d414cef6b4ee932167c08d11c8b6e..6877bbbb6b0da25a4101edd275f482baacd583c6 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 ae529a703d390e21c6a1ee1dd26a309b9176974b..f513d2931231c1d4ee27dc6987077995d368358e 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 65387cc78595135e89f3ec33d876a6c7a42ae590..ebbaa47baa707903bd7a922ce5bab8d6971ea978 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 a8cd719c3856f5d82703f374a8d91ff2871dae37..3c5950e54914f617e4336c8dfa68e23e4306d802 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 dba066f837df3915bcde5418868280586ee5b2a1..f94efe0889995f267401a17850cfd64341f35425 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 0ded741b5715ef667587bc084aaa5777d5b30909..ff740f1b9f975e3151052a5b575257bf262bb955 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 0e6f7f3395508b2004d6058fb0a4bfb9eeb2d8f5..9074850279ea75d4ce7793c0a395d974cffd2612 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 9dfaf8b334d6c7c5c57c6a84f65c732ae6b0d2d5..59506c76421384b190a009a3c17776404a5c8854 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 32fc564402995b128e9076c5ed8e6d9e76fda34c..3b252720d2c9ae7ba7a71d534486dab1f49333a4 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 33f726f4f2db072b5c6ffcc04d2c6591ba8ac43a..b52bafa65b5609211fa207f5c7c525a04d16d16a 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 e0781d1005a76db78fbdb58efc4f6ba053ce1a44..650a1bd977ac6ce35db6d3819851697a81dddfa5 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 3685da6bb8b7ec3cc67b47ce1f1d29edb949f9c4..0550b6de051aa72129a71e61dff4c10af38ca90a 100644 --- a/src/widgets/paintdef.cpp +++ b/src/widgets/paintdef.cpp @@ -37,33 +37,25 @@ #include "paintdef.h" -#include -#include -#include #include -#include #include #include -#include #include +#include +#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] != '#') { @@ -83,34 +75,35 @@ 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; - }; + unsigned const r = rgb[0]; + unsigned const g = rgb[1]; + unsigned const b = rgb[2]; - auto const [r, g, b] = rgb; - - 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::NONE: + case PaintDef::ColorType::NONE: tmp += ""; break; default: @@ -121,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::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::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); + 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 68da249943954cc815be5d9dc019b5ba1b8959c6..24a2733786e90afe469a7d6c42ab3bbd492d0b59 100644 --- a/src/widgets/paintdef.h +++ b/src/widgets/paintdef.h @@ -38,12 +38,14 @@ #ifndef INKSCAPE_WIDGETS_PAINTDEF_H #define INKSCAPE_WIDGETS_PAINTDEF_H +#include +#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 +57,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); + std::vector getMIMEData(std::string_view mime_type) const; + bool fromMIMEData(std::string_view 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