12#include <gtkmm/enums.h>
14#include <glibmm/i18n.h>
15#include <glibmm/main.h>
16#include <glibmm/ustring.h>
17#include <gdkmm/frameclock.h>
18#include <gtkmm/adjustment.h>
19#include <gtkmm/builder.h>
20#include <gtkmm/button.h>
21#include <gtkmm/flowbox.h>
22#include <gtkmm/flowboxchild.h>
23#include <gtkmm/label.h>
24#include <gtkmm/menubutton.h>
25#include <gtkmm/popover.h>
26#include <gtkmm/checkbutton.h>
27#include <gtkmm/scale.h>
28#include <gtkmm/scrolledwindow.h>
29#include <gtkmm/scrollbar.h>
30#include <gtkmm/scrolledwindow.h>
31#include <gtkmm/separator.h>
32#include <sigc++/functors/mem_fun.h>
46 auto const separator = Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::HORIZONTAL);
47 separator->set_margin_top (5);
48 separator->set_margin_bottom(5);
50 auto const config = Gtk::make_managed<PopoverMenuItem>(_(
"Configure..."),
true);
52 auto menu = std::make_unique<PopoverMenu>(Gtk::PositionType::TOP);
53 menu->add_css_class(
"ColorPalette");
54 menu->append(*separator);
55 menu->append(*config);
57 return std::make_pair(std::move(menu), std::ref(*config));
64 _scroll_btn(
get_widget<
Gtk::FlowBox>(_builder,
"scroll-buttons")),
66 _scroll_right(
get_widget<
Gtk::Button>(_builder,
"btn-right")),
69 _scroll(
get_widget<
Gtk::ScrolledWindow>(_builder,
"scroll-wnd"))
71 get_widget<Gtk::CheckButton>(
_builder,
"show-labels").set_visible(
false);
72 _normal_box.set_filter_func([](Gtk::FlowBoxChild*){
return true; });
74 auto& box = get_widget<Gtk::Box>(
_builder,
"palette-box");
78 _menu = std::move(menu);
79 auto& btn_menu = get_widget<Gtk::MenuButton>(
_builder,
"btn-menu");
80 btn_menu.set_popover(*
_menu);
81 _menu->set_position(Gtk::PositionType::TOP);
83 config.signal_activate().connect([&,
this] {
87 auto&
size = get_widget<Gtk::Scale>(
_builder,
"size-slider");
88 size.signal_change_value().connect([&
size,
this](Gtk::ScrollType,
double val) {
94 auto& aspect = get_widget<Gtk::Scale>(
_builder,
"aspect-slider");
95 aspect.signal_change_value().connect([&aspect,
this](Gtk::ScrollType,
double val) {
102 border.signal_change_value().connect([&
border,
this](Gtk::ScrollType,
double val) {
108 auto& rows = get_widget<Gtk::Scale>(
_builder,
"row-slider");
109 rows.signal_change_value().connect([&rows,
this](Gtk::ScrollType,
double val) {
110 _set_rows(
static_cast<int>(rows.get_value()));
115 auto& sb = get_widget<Gtk::CheckButton>(
_builder,
"use-sb");
117 sb.signal_toggled().connect([&sb,
this](){
122 auto& stretch = get_widget<Gtk::CheckButton>(
_builder,
"stretch");
124 stretch.signal_toggled().connect([&stretch,
this](){
130 auto& large = get_widget<Gtk::CheckButton>(
_builder,
"enlarge");
132 large.signal_toggled().connect([&large,
this](){
138 auto& sl = get_widget<Gtk::CheckButton>(
_builder,
"show-labels");
139 sl.set_visible(
false);
141 sl.signal_toggled().connect([&sl,
this](){
147 _scroll.set_min_content_height(1);
154 set_vexpand_set(
true);
164 if (
auto vert_scrollbar =
_scroll.get_vscrollbar()) {
165 vert_scrollbar->get_adjustment()->signal_value_changed().connect([
this] {
174 return get_widget<Gtk::Popover>(
_builder,
"config-popup");
178 auto& btn_menu = get_widget<Gtk::MenuButton>(
_builder,
"btn-menu");
179 btn_menu.set_visible(show);
183 if (
auto vert =
_scroll.get_vscrollbar()) {
184 vert->get_adjustment()->set_value(vert->get_adjustment()->get_value() + dy);
186 if (
auto horz =
_scroll.get_hscrollbar()) {
187 horz->get_adjustment()->set_value(horz->get_adjustment()->get_value() +
dx);
192 auto adj = sb.get_adjustment();
193 return {adj->get_lower(), adj->get_upper() - adj->get_page_size()};
197 if (
auto vert =
_scroll.get_vscrollbar()) {
198 double value = vert->get_adjustment()->get_value();
199 auto [min_value, max_value] =
get_range(*vert);
201 bool at_top = value <= min_value;
202 bool at_bottom = value >= max_value;
213 auto const timings = clock->get_current_timings();
214 auto const t = timings->get_frame_time();
222 bool fire_again =
false;
224 if (
auto vert =
_scroll.get_vscrollbar()) {
229 constexpr double SCROLL_SPEED = 4.0;
230 double const step = SCROLL_SPEED * dt * 6e-5;
232 auto value = vert->get_adjustment()->get_value();
239 vert->get_adjustment()->set_value(pos);
252 if (
auto vert =
_scroll.get_vscrollbar()) {
253 if (
smooth && dy != 0.0) {
266 vert->get_adjustment()->set_value(vert->get_adjustment()->get_value() + dy);
269 if (
auto horz =
_scroll.get_hscrollbar()) {
270 horz->get_adjustment()->set_value(horz->get_adjustment()->get_value() +
dx);
292 auto& slider = get_widget<Gtk::Scale>(
_builder,
"border-slider");
299 if (border < 0 || border > 100) {
300 g_warning(
"Unexpected tile border size of color palette: %d",
border);
310 auto& slider = get_widget<Gtk::Scale>(
_builder,
"size-slider");
311 slider.set_value(
size);
317 if (size < 1 || size > 1000) {
318 g_warning(
"Unexpected tile size for color palette: %d",
size);
328 auto& slider = get_widget<Gtk::Scale>(
_builder,
"aspect-slider");
329 slider.set_value(aspect);
335 if (aspect < -2.0 || aspect > 2.0) {
336 g_warning(
"Unexpected aspect ratio for color palette: %f", aspect);
351 auto& slider = get_widget<Gtk::Scale>(
_builder,
"row-slider");
352 slider.set_value(rows);
356 if (rows ==
_rows)
return;
358 if (rows < 1 || rows > 1000) {
359 g_warning(
"Unexpected number of rows for color palette: %d", rows);
368 auto& sb = get_widget<Gtk::CheckButton>(
_builder,
"use-sb");
370 bool sens =
_rows == 1;
371 if (sb.get_sensitive() != sens) sb.set_sensitive(sens);
379 get_widget<Gtk::Scale>(
_builder,
"row-slider").set_visible(compact);
380 get_widget<Gtk::Label>(
_builder,
"row-label").set_visible(compact);
381 get_widget<Gtk::CheckButton>(
_builder,
"enlarge").set_visible(compact);
395 auto& stretch = get_widget<Gtk::CheckButton>(
_builder,
"stretch");
396 stretch.set_active(enable);
404 _normal_box.set_halign(enable ? Gtk::Align::FILL : Gtk::Align::START);
410 auto& sl = get_widget<Gtk::CheckButton>(
_builder,
"show-labels");
411 sl.set_active(labels);
420 auto& aspect = get_widget<Gtk::Scale>(
_builder,
"aspect-slider");
422 auto&
label = get_widget<Gtk::Label>(
_builder,
"aspect-label");
427 auto& sb = get_widget<Gtk::CheckButton>(
_builder,
"use-sb");
440 auto &box = get_widget<Gtk::Box>(
_builder,
"palette-box");
441 auto &btn_menu = get_widget<Gtk::MenuButton>(
_builder,
"btn-menu");
443 auto normal_count = std::max(1,
static_cast<int>(colors.size()));
446 _normal_box.set_max_children_per_line(normal_count);
448 _pinned_box.set_max_children_per_line(pinned_count);
451 auto alloc_width =
_normal_box.get_parent()->get_allocated_width();
458 if (
_normal_box.get_max_children_per_line() != cols) {
465 box.set_orientation(Gtk::Orientation::HORIZONTAL);
466 box.set_valign(Gtk::Align::START);
467 box.set_vexpand(
false);
468 btn_menu.set_margin_bottom(0);
469 btn_menu.set_margin_end(0);
471 set_valign(Gtk::Align::START);
474 _scroll.set_valign(Gtk::Align::END);
479 _normal_box.set_min_children_per_line(normal_count);
494 _scroll.set_policy(
_force_scrollbar ? Gtk::PolicyType::ALWAYS : Gtk::PolicyType::EXTERNAL, Gtk::PolicyType::NEVER);
499 _scroll.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::EXTERNAL);
506 _pinned_box.set_max_children_per_line(std::max((pinned_count + div - 1) / div, 1));
510 box.set_orientation(Gtk::Orientation::VERTICAL);
511 box.set_valign(Gtk::Align::FILL);
512 box.set_vexpand(
true);
513 btn_menu.set_margin_bottom(2);
514 btn_menu.set_margin_end(2);
516 set_valign(Gtk::Align::FILL);
524 _scroll.set_valign(Gtk::Align::FILL);
526 _scroll.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::ALWAYS);
540 size =
static_cast<int>(round((1.0 + aspect) *
_size));
542 else if (aspect < 0) {
543 size =
static_cast<int>(round((1.0 / (1.0 - aspect)) *
_size));
564 auto& checkbox = get_widget<Gtk::CheckButton>(
_builder,
"enlarge");
565 checkbox.set_active(large);
587 _scroll.set_size_request(-1, -1);
606 int pinned_width =
width;
607 int pinned_height =
height;
613 item->set_size_request(pinned_width, pinned_height);
622 for (
auto &
item : coloritems) {
625 if (auto label = dynamic_cast<Gtk::Label *>(&w)) {
626 label->set_text(item->get_description());
631 if (
item->is_pinned()) {
642 assert(!
item->get_parent());
644 item->set_valign(Gtk::Align::CENTER);
645 auto const box = Gtk::make_managed<Gtk::Box>();
646 auto const label = Gtk::make_managed<Gtk::Label>(
item->get_description());
654void ColorPalette::rebuild_widgets()
656 _normal_box.freeze_notify();
657 _pinned_box.freeze_notify();
662 for (
auto const &
item : _normal_items) {
664 if (!_show_labels &&
item->is_group())
continue;
667 if (_show_labels &&
item->is_filler())
continue;
669 _normal_box.append(*_get_widget(
item.get()));
671 for (
auto const &
item : _pinned_items) {
672 _pinned_box.append(*_get_widget(
item.get()));
677 _normal_box.thaw_notify();
678 _pinned_box.thaw_notify();
683 ColorPaletteMenuItem(Gtk::CheckButton *&group,
684 Glib::ustring
const &
label,
686 std::vector<rgb_t> colors)
687 :
Glib::ObjectBase{
"ColorPaletteMenuItem"}
689 , _radio_button{
Gtk::make_managed<
Gtk::CheckButton>(
label)}
694 _radio_button->set_group(*group);
696 group = _radio_button;
698 auto const box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 1);
699 box->append(*_radio_button);
700 box->append(*_preview);
704 void set_active(
bool const active) { _radio_button->set_active(active); }
706 Glib::ustring
const id;
709 Gtk::CheckButton *_radio_button =
nullptr;
710 ColorPalettePreview *_preview =
nullptr;
713void ColorPalette::set_palettes(std::vector<palette_t>
const &palettes)
715 for (
auto const &
item: _palette_menu_items) {
716 _menu->remove(*
item);
719 _palette_menu_items.clear();
720 _palette_menu_items.reserve(palettes.size());
722 Gtk::CheckButton *group =
nullptr;
724 for (
auto it = palettes.crbegin(); it != palettes.crend(); ++it) {
725 auto&
name = it->name;
727 auto item = std::make_unique<ColorPaletteMenuItem>(group,
name,
id, it->colors);
728 item->signal_activate().connect([
id,
this](){
731 _signal_palette_selected.emit(
id);
735 item->set_visible(
true);
736 _menu->prepend(*
item);
737 _palette_menu_items.push_back(std::move(
item));
741sigc::signal<void (Glib::ustring)>& ColorPalette::get_palette_selected_signal() {
742 return _signal_palette_selected;
745sigc::signal<void ()>& ColorPalette::get_settings_changed_signal() {
746 return _signal_settings_changed;
749void ColorPalette::set_selected(
const Glib::ustring&
id) {
752 for (
auto const &
item : _palette_menu_items) {
759void ColorPalette::set_page_size(
int page_size) {
760 _page_size = page_size;
764 _normal_box.set_filter_func([=](Gtk::FlowBoxChild*
c){
765 auto child =
c->get_child();
766 if (
auto box =
dynamic_cast<Gtk::Box*
>(
child)) {
770 return filter(*color);
776void ColorPalette::apply_filter() {
777 _normal_box.invalidate_filter();
constexpr C clamp(C val) const
Two-dimensional point with integer coordinates.
Range of real numbers that is never empty.
The color item you see on-screen as a clickable box.
Color item used in palettes and swatches UI.
A Gtk::DrawingArea to preview color palette menu items by showing a small example of the colors.
int sgn(const T &x)
Sign function - indicates the sign of a numeric type.
void remove_all_children(Widget &widget)
For each child in get_children(widget), call widget.remove(*child). May not cause delete child!
std::vector< Gtk::Widget * > get_children(Gtk::Widget &widget)
Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling().
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
Gtk::Widget * for_each_child(Gtk::Widget &widget, Func &&func, bool const plus_self=false, bool const recurse=false, int const level=0)
Call Func with a reference to each child of parent, until it returns _break.
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
Glib::RefPtr< Gtk::Adjustment > smooth