diff options
author | Rémi Verschelde <rverschelde@gmail.com> | 2022-10-07 09:49:29 +0200 |
---|---|---|
committer | Rémi Verschelde <rverschelde@gmail.com> | 2022-10-07 09:49:29 +0200 |
commit | 5b7f62af55b7f322192f95258d825b163cbf9dc1 (patch) | |
tree | 8413fc58928c27b56c20df4c1af495ea3620ea73 | |
parent | aa20941e68a3d6399d6f740a5d5feaf5f04dc71a (diff) | |
parent | 1dce43d417784e79aedcb0b4d53a4a97145c47af (diff) |
Merge pull request #62910 from Vitika9/gsoc-colorpicker-ux
ColorPicker UX
-rw-r--r-- | doc/classes/ColorPicker.xml | 36 | ||||
-rw-r--r-- | doc/classes/HSlider.xml | 3 | ||||
-rw-r--r-- | doc/classes/VSlider.xml | 3 | ||||
-rw-r--r-- | editor/editor_themes.cpp | 5 | ||||
-rw-r--r-- | editor/icons/PickerShapeCircle.svg | 1 | ||||
-rw-r--r-- | editor/icons/PickerShapeRectangle.svg | 1 | ||||
-rw-r--r-- | editor/icons/PickerShapeRectangleWheel.svg | 58 | ||||
-rw-r--r-- | scene/gui/color_mode.cpp | 55 | ||||
-rw-r--r-- | scene/gui/color_picker.cpp | 565 | ||||
-rw-r--r-- | scene/gui/color_picker.h | 49 | ||||
-rw-r--r-- | scene/gui/slider.cpp | 4 | ||||
-rw-r--r-- | scene/resources/default_theme/default_theme.cpp | 9 | ||||
-rw-r--r-- | scene/resources/default_theme/picker_shape_circle.svg | 1 | ||||
-rw-r--r-- | scene/resources/default_theme/picker_shape_rectangle.svg | 1 | ||||
-rw-r--r-- | scene/resources/default_theme/picker_shape_rectangle_wheel.svg | 58 |
15 files changed, 737 insertions, 112 deletions
diff --git a/doc/classes/ColorPicker.xml b/doc/classes/ColorPicker.xml index e992d6f9d4..d09b7f003e 100644 --- a/doc/classes/ColorPicker.xml +++ b/doc/classes/ColorPicker.xml @@ -19,6 +19,14 @@ [b]Note:[/b] The presets list is only for [i]this[/i] color picker. </description> </method> + <method name="add_recent_preset"> + <return type="void" /> + <param index="0" name="color" type="Color" /> + <description> + Adds the given color to a list of color recent presets so that it can be picked later. Recent presets are the colors that were picked recently, a new preset is automatically created and added to recent presets when you pick a new color. + [b]Note:[/b] The recent presets list is only for [i]this[/i] color picker. + </description> + </method> <method name="erase_preset"> <return type="void" /> <param index="0" name="color" type="Color" /> @@ -26,12 +34,25 @@ Removes the given color from the list of color presets of this color picker. </description> </method> + <method name="erase_recent_preset"> + <return type="void" /> + <param index="0" name="color" type="Color" /> + <description> + Removes the given color from the list of color recent presets of this color picker. + </description> + </method> <method name="get_presets" qualifiers="const"> <return type="PackedColorArray" /> <description> Returns the list of colors in the presets of the color picker. </description> </method> + <method name="get_recent_presets" qualifiers="const"> + <return type="PackedColorArray" /> + <description> + Returns the list of colors in the recent presets of the color picker. + </description> + </method> </methods> <members> <member name="color" type="Color" setter="set_pick_color" getter="get_pick_color" default="Color(1, 1, 1, 1)"> @@ -129,6 +150,12 @@ <theme_item name="color_hue" data_type="icon" type="Texture2D"> Custom texture for the hue selection slider on the right. </theme_item> + <theme_item name="expanded_arrow" data_type="icon" type="Texture2D"> + The icon for color preset drop down menu when expanded. + </theme_item> + <theme_item name="folded_arrow" data_type="icon" type="Texture2D"> + The icon for color preset drop down menu when folded. + </theme_item> <theme_item name="overbright_indicator" data_type="icon" type="Texture2D"> The indicator used to signalize that the color value is outside the 0-1 range. </theme_item> @@ -139,5 +166,14 @@ <theme_item name="screen_picker" data_type="icon" type="Texture2D"> The icon for the screen color picker button. </theme_item> + <theme_item name="shape_circle" data_type="icon" type="Texture2D"> + The icon for circular picker shapes. + </theme_item> + <theme_item name="shape_rect" data_type="icon" type="Texture2D"> + The icon for rectangular picker shapes. + </theme_item> + <theme_item name="shape_rect_wheel" data_type="icon" type="Texture2D"> + The icon for rectangular wheel picker shapes. + </theme_item> </theme_items> </class> diff --git a/doc/classes/HSlider.xml b/doc/classes/HSlider.xml index 60208eee0f..cb0a8b34db 100644 --- a/doc/classes/HSlider.xml +++ b/doc/classes/HSlider.xml @@ -10,6 +10,9 @@ <tutorials> </tutorials> <theme_items> + <theme_item name="grabber_offset" data_type="constant" type="int" default="0"> + Vertical offset of the grabber. + </theme_item> <theme_item name="grabber" data_type="icon" type="Texture2D"> The texture for the grabber (the draggable element). </theme_item> diff --git a/doc/classes/VSlider.xml b/doc/classes/VSlider.xml index 36954a6912..b30349e538 100644 --- a/doc/classes/VSlider.xml +++ b/doc/classes/VSlider.xml @@ -14,6 +14,9 @@ <member name="size_flags_vertical" type="int" setter="set_v_size_flags" getter="get_v_size_flags" overrides="Control" default="1" /> </members> <theme_items> + <theme_item name="grabber_offset" data_type="constant" type="int" default="0"> + Horizontal offset of the grabber. + </theme_item> <theme_item name="grabber" data_type="icon" type="Texture2D"> The texture for the grabber (the draggable element). </theme_item> diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index f4c1f308cc..27ac57216a 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -1471,6 +1471,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_stylebox("slider", "HSlider", make_flat_stylebox(dark_color_3, 0, default_margin_size / 2, 0, default_margin_size / 2, corner_width)); theme->set_stylebox("grabber_area", "HSlider", make_flat_stylebox(contrast_color_1, 0, default_margin_size / 2, 0, default_margin_size / 2, corner_width)); theme->set_stylebox("grabber_area_highlight", "HSlider", make_flat_stylebox(contrast_color_1, 0, default_margin_size / 2, 0, default_margin_size / 2)); + theme->set_constant("grabber_offset", "HSlider", 0); // VSlider theme->set_icon("grabber", "VSlider", theme->get_icon(SNAME("GuiSliderGrabber"), SNAME("EditorIcons"))); @@ -1478,6 +1479,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_stylebox("slider", "VSlider", make_flat_stylebox(dark_color_3, default_margin_size / 2, 0, default_margin_size / 2, 0, corner_width)); theme->set_stylebox("grabber_area", "VSlider", make_flat_stylebox(contrast_color_1, default_margin_size / 2, 0, default_margin_size / 2, 0, corner_width)); theme->set_stylebox("grabber_area_highlight", "VSlider", make_flat_stylebox(contrast_color_1, default_margin_size / 2, 0, default_margin_size / 2, 0)); + theme->set_constant("grabber_offset", "VSlider", 0); // RichTextLabel theme->set_color("default_color", "RichTextLabel", font_color); @@ -1714,6 +1716,9 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_constant("h_width", "ColorPicker", 30 * EDSCALE); theme->set_constant("label_width", "ColorPicker", 10 * EDSCALE); theme->set_icon("screen_picker", "ColorPicker", theme->get_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); + theme->set_icon("shape_circle", "ColorPicker", theme->get_icon(SNAME("PickerShapeCircle"), SNAME("EditorIcons"))); + theme->set_icon("shape_rect", "ColorPicker", theme->get_icon(SNAME("PickerShapeRectangle"), SNAME("EditorIcons"))); + theme->set_icon("shape_rect_wheel", "ColorPicker", theme->get_icon(SNAME("PickerShapeRectangleWheel"), SNAME("EditorIcons"))); theme->set_icon("add_preset", "ColorPicker", theme->get_icon(SNAME("Add"), SNAME("EditorIcons"))); theme->set_icon("sample_bg", "ColorPicker", theme->get_icon(SNAME("GuiMiniCheckerboard"), SNAME("EditorIcons"))); theme->set_icon("overbright_indicator", "ColorPicker", theme->get_icon(SNAME("OverbrightIndicator"), SNAME("EditorIcons"))); diff --git a/editor/icons/PickerShapeCircle.svg b/editor/icons/PickerShapeCircle.svg new file mode 100644 index 0000000000..8e7fb7f06e --- /dev/null +++ b/editor/icons/PickerShapeCircle.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h16v16h-16z"/></clipPath><g clip-path="url(#a)" fill="#eaeaea"><rect height="11" rx="5.5" transform="translate(1 2)" width="11"/><path d="m0 0h2v11h-2z" transform="translate(13 2)"/></g></svg> diff --git a/editor/icons/PickerShapeRectangle.svg b/editor/icons/PickerShapeRectangle.svg new file mode 100644 index 0000000000..3c7dd46884 --- /dev/null +++ b/editor/icons/PickerShapeRectangle.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h16v16h-16z"/></clipPath><g clip-path="url(#a)" fill="#eaeaea"><path d="m0 0h11v11h-11z" transform="translate(1 2)"/><path d="m0 0h2v11h-2z" transform="translate(13 2)"/></g></svg> diff --git a/editor/icons/PickerShapeRectangleWheel.svg b/editor/icons/PickerShapeRectangleWheel.svg new file mode 100644 index 0000000000..e85665a8f2 --- /dev/null +++ b/editor/icons/PickerShapeRectangleWheel.svg @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="16" + viewBox="0 0 16 16" + width="16" + version="1.1" + id="svg11" + sodipodi:docname="PickerShapeRectangleWheel.svg" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs15" /> + <sodipodi:namedview + id="namedview13" + pagecolor="#505050" + bordercolor="#ffffff" + borderopacity="1" + inkscape:pageshadow="0" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="1" + showgrid="true" + inkscape:zoom="16" + inkscape:cx="0.53125" + inkscape:cy="5.28125" + inkscape:window-width="1920" + inkscape:window-height="1001" + inkscape:window-x="-9" + inkscape:window-y="-9" + inkscape:window-maximized="1" + inkscape:current-layer="svg11"> + <inkscape:grid + type="xygrid" + id="grid944" /> + </sodipodi:namedview> + <clipPath + id="a"> + <path + d="m0 0h16v16h-16z" + id="path2" /> + </clipPath> + <g + clip-path="url(#a)" + fill="#eaeaea" + id="g9" + transform="matrix(0.85714286,0,0,0.85714286,1.1428571,1.1428571)"> + <path + d="M 7,2 A 5,5 0 1 0 12,7 5.006,5.006 0 0 0 7,2 M 7,0 A 7,7 0 1 1 0,7 7,7 0 0 1 7,0 Z" + transform="translate(1,1)" + id="path5" /> + <path + d="M 0,0 H 7 V 7 H 0 Z" + transform="translate(4.5,4.5)" + id="path7" /> + </g> +</svg> diff --git a/scene/gui/color_mode.cpp b/scene/gui/color_mode.cpp index ebd86e0937..3a5013dabe 100644 --- a/scene/gui/color_mode.cpp +++ b/scene/gui/color_mode.cpp @@ -73,10 +73,10 @@ void ColorModeRGB::slider_draw(int p_which) { Color left_color; Color right_color; Color color = color_picker->get_pick_color(); - const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + const real_t margin = 16 * color_picker->get_theme_default_base_scale(); if (p_which == ColorPicker::SLIDER_COUNT) { - slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); left_color = color; left_color.a = 0; @@ -97,10 +97,10 @@ void ColorModeRGB::slider_draw(int p_which) { col.set(1, right_color); col.set(2, right_color); col.set(3, left_color); - pos.set(0, Vector2(0, margin)); - pos.set(1, Vector2(size.x, margin)); - pos.set(2, Vector2(size.x, margin * 2)); - pos.set(3, Vector2(0, margin * 2)); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x, 0)); + pos.set(2, Vector2(size.x, margin)); + pos.set(3, Vector2(0, margin)); slider->draw_polygon(pos, col); } @@ -147,10 +147,10 @@ void ColorModeHSV::slider_draw(int p_which) { Color left_color; Color right_color; Color color = color_picker->get_pick_color(); - const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + const real_t margin = 16 * color_picker->get_theme_default_base_scale(); if (p_which == ColorPicker::SLIDER_COUNT) { - slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); left_color = color; left_color.a = 0; @@ -159,7 +159,7 @@ void ColorModeHSV::slider_draw(int p_which) { } else if (p_which == 0) { Ref<Texture2D> hue = color_picker->get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker")); slider->draw_set_transform(Point2(), -Math_PI / 2, Size2(1.0, 1.0)); - slider->draw_texture_rect(hue, Rect2(Vector2(margin * -2, 0), Vector2(margin, size.x)), false); + slider->draw_texture_rect(hue, Rect2(Vector2(margin * -1, 0), Vector2(margin, size.x)), false); return; } else { Color s_col; @@ -174,10 +174,10 @@ void ColorModeHSV::slider_draw(int p_which) { col.set(1, right_color); col.set(2, right_color); col.set(3, left_color); - pos.set(0, Vector2(0, margin)); - pos.set(1, Vector2(size.x, margin)); - pos.set(2, Vector2(size.x, margin * 2)); - pos.set(3, Vector2(0, margin * 2)); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x, 0)); + pos.set(2, Vector2(size.x, margin)); + pos.set(3, Vector2(0, margin)); slider->draw_polygon(pos, col); } @@ -216,10 +216,10 @@ void ColorModeRAW::slider_draw(int p_which) { Color left_color; Color right_color; Color color = color_picker->get_pick_color(); - const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + const real_t margin = 16 * color_picker->get_theme_default_base_scale(); if (p_which == ColorPicker::SLIDER_COUNT) { - slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); left_color = color; left_color.a = 0; @@ -230,10 +230,10 @@ void ColorModeRAW::slider_draw(int p_which) { col.set(1, right_color); col.set(2, right_color); col.set(3, left_color); - pos.set(0, Vector2(0, margin)); - pos.set(1, Vector2(size.x, margin)); - pos.set(2, Vector2(size.x, margin * 2)); - pos.set(3, Vector2(0, margin * 2)); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x, 0)); + pos.set(2, Vector2(size.x, margin)); + pos.set(3, Vector2(0, margin)); slider->draw_polygon(pos, col); } @@ -245,8 +245,7 @@ bool ColorModeRAW::apply_theme() const { slider->remove_theme_icon_override("grabber"); slider->remove_theme_icon_override("grabber_highlight"); slider->remove_theme_style_override("slider"); - slider->remove_theme_style_override("grabber_area"); - slider->remove_theme_style_override("grabber_area_highlight"); + slider->remove_theme_constant_override("grabber_offset"); } return true; @@ -294,10 +293,10 @@ void ColorModeOKHSL::slider_draw(int p_which) { Color left_color; Color right_color; Color color = color_picker->get_pick_color(); - const real_t margin = 4 * color_picker->get_theme_default_base_scale(); + const real_t margin = 16 * color_picker->get_theme_default_base_scale(); if (p_which == ColorPicker::SLIDER_COUNT) { - slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, margin), Size2(size.x, margin)), true); + slider->draw_texture_rect(color_picker->get_theme_icon(SNAME("sample_bg"), SNAME("ColorPicker")), Rect2(Point2(0, 0), Size2(size.x, margin)), true); left_color = color; left_color.a = 0; @@ -306,7 +305,7 @@ void ColorModeOKHSL::slider_draw(int p_which) { } else if (p_which == 0) { Ref<Texture2D> hue = color_picker->get_theme_icon(SNAME("color_hue"), SNAME("ColorPicker")); slider->draw_set_transform(Point2(), -Math_PI / 2, Size2(1.0, 1.0)); - slider->draw_texture_rect(hue, Rect2(Vector2(margin * -2, 0), Vector2(margin, size.x)), false); + slider->draw_texture_rect(hue, Rect2(Vector2(margin * -1, 0), Vector2(margin, size.x)), false); return; } else { Color s_col; @@ -321,10 +320,10 @@ void ColorModeOKHSL::slider_draw(int p_which) { col.set(1, right_color); col.set(2, right_color); col.set(3, left_color); - pos.set(0, Vector2(0, margin)); - pos.set(1, Vector2(size.x, margin)); - pos.set(2, Vector2(size.x, margin * 2)); - pos.set(3, Vector2(0, margin * 2)); + pos.set(0, Vector2(0, 0)); + pos.set(1, Vector2(size.x, 0)); + pos.set(2, Vector2(size.x, margin)); + pos.set(3, Vector2(0, margin)); slider->draw_polygon(pos, col); } diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 5751c54877..36b770408e 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -44,6 +44,7 @@ #include "thirdparty/misc/ok_color_shader.h" List<Color> ColorPicker::preset_cache; +List<Color> ColorPicker::recent_preset_cache; void ColorPicker::_notification(int p_what) { switch (p_what) { @@ -61,14 +62,31 @@ void ColorPicker::_notification(int p_what) { for (int i = 0; i < preset_cache.size(); i++) { presets.push_back(preset_cache[i]); } + + if (recent_preset_cache.is_empty()) { + PackedColorArray saved_recent_presets = EditorSettings::get_singleton()->get_project_metadata("color_picker", "recent_presets", PackedColorArray()); + for (int i = 0; i < saved_recent_presets.size(); i++) { + recent_preset_cache.push_back(saved_recent_presets[i]); + } + } + + for (int i = 0; i < recent_preset_cache.size(); i++) { + recent_presets.push_back(recent_preset_cache[i]); + } } #endif [[fallthrough]]; } case NOTIFICATION_THEME_CHANGED: { btn_pick->set_icon(get_theme_icon(SNAME("screen_picker"), SNAME("ColorPicker"))); + _update_drop_down_arrow(btn_preset->is_pressed(), btn_preset); + _update_drop_down_arrow(btn_recent_preset->is_pressed(), btn_recent_preset); btn_add_preset->set_icon(get_theme_icon(SNAME("add_preset"))); + btn_pick->set_custom_minimum_size(Size2(28 * get_theme_default_base_scale(), 0)); + btn_shape->set_custom_minimum_size(Size2(28 * get_theme_default_base_scale(), 0)); + btn_mode->set_custom_minimum_size(Size2(28 * get_theme_default_base_scale(), 0)); + uv_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("sv_width")), get_theme_constant(SNAME("sv_height")))); w_edit->set_custom_minimum_size(Size2(get_theme_constant(SNAME("h_width")), 0)); @@ -90,12 +108,13 @@ void ColorPicker::_notification(int p_what) { } _update_presets(); + _update_recent_presets(); _update_controls(); } break; case NOTIFICATION_VISIBILITY_CHANGED: { Popup *p = Object::cast_to<Popup>(get_parent()); - if (p) { + if (p && is_visible_in_tree()) { p->set_size(Size2(get_combined_minimum_size().width + get_theme_constant(SNAME("margin")) * 2, get_combined_minimum_size().height + get_theme_constant(SNAME("margin")) * 2)); } } break; @@ -262,6 +281,11 @@ void ColorPicker::_update_controls() { } void ColorPicker::_set_pick_color(const Color &p_color, bool p_update_sliders) { + if (text_changed) { + add_recent_preset(color); + text_changed = false; + } + color = p_color; if (color != last_color) { _copy_color_to_hsv(); @@ -330,7 +354,6 @@ void ColorPicker::_value_changed(double) { void ColorPicker::add_mode(ColorMode *p_mode) { modes.push_back(p_mode); - mode_option_button->add_item(RTR(p_mode->get_name())); } void ColorPicker::create_slider(GridContainer *gc, int idx) { @@ -346,13 +369,21 @@ void ColorPicker::create_slider(GridContainer *gc, int idx) { SpinBox *v = memnew(SpinBox); s->share(v); gc->add_child(v); - v->get_line_edit()->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter)); - v->get_line_edit()->connect("focus_exited", callable_mp(this, &ColorPicker::_focus_exit)); + + LineEdit *vle = v->get_line_edit(); + vle->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter), CONNECT_DEFERRED); + vle->connect("focus_exited", callable_mp(this, &ColorPicker::_focus_exit)); + vle->connect("text_changed", callable_mp(this, &ColorPicker::_text_changed)); + vle->connect("gui_input", callable_mp(this, &ColorPicker::_line_edit_input)); + vle->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); + + v->connect("gui_input", callable_mp(this, &ColorPicker::_slider_or_spin_input)); s->set_h_size_flags(SIZE_EXPAND_FILL); s->connect("value_changed", callable_mp(this, &ColorPicker::_value_changed)); s->connect("draw", callable_mp(this, &ColorPicker::_slider_draw).bind(idx)); + s->connect("gui_input", callable_mp(this, &ColorPicker::_slider_or_spin_input)); if (idx < SLIDER_COUNT) { sliders[idx] = s; @@ -401,25 +432,53 @@ void ColorPicker::_copy_hsv_to_color() { } } +void ColorPicker::_select_from_preset_container(const Color &p_color) { + if (preset_group->get_pressed_button()) { + preset_group->get_pressed_button()->set_pressed(false); + } + + for (int i = 1; i < preset_container->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + current_btn->set_pressed(true); + break; + } + } +} + +bool ColorPicker::_select_from_recent_preset_hbc(const Color &p_color) { + for (int i = 0; i < recent_preset_hbc->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(recent_preset_hbc->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + current_btn->set_pressed(true); + return true; + } + } + return false; +} + ColorPicker::PickerShapeType ColorPicker::_get_actual_shape() const { return modes[current_mode]->get_shape_override() != SHAPE_MAX ? modes[current_mode]->get_shape_override() : current_shape; } void ColorPicker::_reset_theme() { - Ref<StyleBoxEmpty> style_box_empty(memnew(StyleBoxEmpty)); - + Ref<StyleBoxFlat> style_box_flat(memnew(StyleBoxFlat)); + style_box_flat->set_default_margin(SIDE_TOP, 16 * get_theme_default_base_scale()); + style_box_flat->set_bg_color(Color(0.2, 0.23, 0.31).lerp(Color(0, 0, 0, 1), 0.3).clamp()); for (int i = 0; i < SLIDER_COUNT; i++) { sliders[i]->add_theme_icon_override("grabber", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); sliders[i]->add_theme_icon_override("grabber_highlight", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); - sliders[i]->add_theme_style_override("slider", style_box_empty); - sliders[i]->add_theme_style_override("grabber_area", style_box_empty); - sliders[i]->add_theme_style_override("grabber_area_highlight", style_box_empty); + sliders[i]->add_theme_constant_override("grabber_offset", 8 * get_theme_default_base_scale()); + if (!colorize_sliders) { + sliders[i]->add_theme_style_override("slider", style_box_flat); + } } alpha_slider->add_theme_icon_override("grabber", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); alpha_slider->add_theme_icon_override("grabber_highlight", get_theme_icon(SNAME("bar_arrow"), SNAME("ColorPicker"))); - alpha_slider->add_theme_style_override("slider", style_box_empty); - alpha_slider->add_theme_style_override("grabber_area", style_box_empty); - alpha_slider->add_theme_style_override("grabber_area_highlight", style_box_empty); + alpha_slider->add_theme_constant_override("grabber_offset", 8 * get_theme_default_base_scale()); + if (!colorize_sliders) { + alpha_slider->add_theme_style_override("slider", style_box_flat); + } } void ColorPicker::_html_submitted(const String &p_html) { @@ -484,13 +543,41 @@ void ColorPicker::_update_presets() { cpb->set_custom_minimum_size(Size2(preset_size, preset_size)); } } - // Only load preset buttons when the only child is the add-preset button. - if (preset_container->get_child_count() == 1) { - for (int i = 0; i < preset_cache.size(); i++) { - _add_preset_button(preset_size, preset_cache[i]); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + // Only load preset buttons when the only child is the add-preset button. + if (preset_container->get_child_count() == 1) { + for (int i = 0; i < preset_cache.size(); i++) { + _add_preset_button(preset_size, preset_cache[i]); + } + _notification(NOTIFICATION_VISIBILITY_CHANGED); } + } +#endif +} + +void ColorPicker::_update_recent_presets() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + int recent_preset_count = recent_preset_hbc->get_child_count(); + for (int i = 0; i < recent_preset_count; i++) { + memdelete(recent_preset_hbc->get_child(0)); + } + + recent_presets.clear(); + for (int i = 0; i < recent_preset_cache.size(); i++) { + recent_presets.push_back(recent_preset_cache[i]); + } + + int preset_size = _get_preset_size(); + for (int i = 0; i < recent_presets.size(); i++) { + _add_recent_preset_button(preset_size, recent_presets[i]); + } + _notification(NOTIFICATION_VISIBILITY_CHANGED); } +#endif } void ColorPicker::_text_type_toggled() { @@ -500,11 +587,13 @@ void ColorPicker::_text_type_toggled() { text_type->set_icon(get_theme_icon(SNAME("Script"), SNAME("EditorIcons"))); c_text->set_editable(false); + c_text->set_h_size_flags(SIZE_EXPAND_FILL); } else { text_type->set_text("#"); text_type->set_icon(nullptr); c_text->set_editable(true); + c_text->set_h_size_flags(SIZE_FILL); } _update_color(); } @@ -515,9 +604,14 @@ Color ColorPicker::get_pick_color() const { void ColorPicker::set_picker_shape(PickerShapeType p_shape) { ERR_FAIL_INDEX(p_shape, SHAPE_MAX); - if (current_shape == p_shape) { + if (p_shape == current_shape) { return; } + shape_popup->set_item_checked(current_shape, false); + shape_popup->set_item_checked(p_shape, true); + + btn_shape->set_icon(shape_popup->get_item_icon(p_shape)); + current_shape = p_shape; _copy_color_to_hsv(); @@ -531,45 +625,105 @@ ColorPicker::PickerShapeType ColorPicker::get_picker_shape() const { } inline int ColorPicker::_get_preset_size() { - return (int(get_minimum_size().width) - (preset_container->get_theme_constant(SNAME("h_separation")) * (preset_column_count - 1))) / preset_column_count; + return (int(get_minimum_size().width) - (preset_container->get_theme_constant(SNAME("h_separation")) * (PRESET_COLUMN_COUNT - 1))) / PRESET_COLUMN_COUNT; } void ColorPicker::_add_preset_button(int p_size, const Color &p_color) { - ColorPresetButton *btn_preset = memnew(ColorPresetButton(p_color)); - btn_preset->set_preset_color(p_color); - btn_preset->set_custom_minimum_size(Size2(p_size, p_size)); - btn_preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input).bind(p_color)); + ColorPresetButton *btn_preset = memnew(ColorPresetButton(p_color, p_size)); btn_preset->set_tooltip_text(vformat(RTR("Color: #%s\nLMB: Apply color\nRMB: Remove preset"), p_color.to_html(p_color.a < 1))); + btn_preset->set_drag_forwarding(this); + btn_preset->set_button_group(preset_group); preset_container->add_child(btn_preset); + btn_preset->set_pressed(true); + btn_preset->connect("gui_input", callable_mp(this, &ColorPicker::_preset_input).bind(p_color)); } -void ColorPicker::_set_color_mode(ColorModeType p_mode) { - if (slider_theme_modified) { - _reset_theme(); +void ColorPicker::_add_recent_preset_button(int p_size, const Color &p_color) { + ColorPresetButton *btn_preset = memnew(ColorPresetButton(p_color, p_size)); + btn_preset->set_tooltip_text(vformat(RTR("Color: #%s\nLMB: Apply color"), p_color.to_html(p_color.a < 1))); + btn_preset->set_button_group(recent_preset_group); + recent_preset_hbc->add_child(btn_preset); + recent_preset_hbc->move_child(btn_preset, 0); + btn_preset->set_pressed(true); + btn_preset->connect("toggled", callable_mp(this, &ColorPicker::_recent_preset_pressed).bind(btn_preset)); +} + +void ColorPicker::_show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container) { + if (p_is_btn_pressed) { + p_preset_container->show(); + } else { + p_preset_container->hide(); } + _update_drop_down_arrow(p_is_btn_pressed, p_btn_preset); +} - current_mode = p_mode; +void ColorPicker::_update_drop_down_arrow(const bool &p_is_btn_pressed, Button *p_btn_preset) { + if (p_is_btn_pressed) { + p_btn_preset->set_icon(get_theme_icon(SNAME("expanded_arrow"), SNAME("ColorPicker"))); + } else { + p_btn_preset->set_icon(get_theme_icon(SNAME("folded_arrow"), SNAME("ColorPicker"))); + } +} - if (!is_inside_tree()) { +void ColorPicker::_set_mode_popup_value(ColorModeType p_mode) { + ERR_FAIL_INDEX(p_mode, MODE_MAX + 1); + + if (p_mode == MODE_MAX) { + set_colorize_sliders(!colorize_sliders); + } else { + set_color_mode(p_mode); + } +} + +Variant ColorPicker::_get_drag_data_fw(const Point2 &p_point, Control *p_from_control) { + ColorPresetButton *dragged_preset_button = Object::cast_to<ColorPresetButton>(p_from_control); + + if (!dragged_preset_button) { + return Variant(); + } + + ColorPresetButton *drag_preview = memnew(ColorPresetButton(dragged_preset_button->get_preset_color(), _get_preset_size())); + set_drag_preview(drag_preview); + + Dictionary drag_data; + drag_data["type"] = "color_preset"; + drag_data["color_preset"] = dragged_preset_button->get_index(); + + return drag_data; +} + +bool ColorPicker::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) const { + Dictionary d = p_data; + if (!d.has("type") || String(d["type"]) != "color_preset") { + return false; + } + return true; +} + +void ColorPicker::_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) { + Dictionary d = p_data; + if (!d.has("type")) { return; } - _update_controls(); - _update_color(); + if (String(d["type"]) == "color_preset") { + int preset_from_id = d["color_preset"]; + int hover_now = p_from_control->get_index(); + + if (preset_from_id == hover_now || hover_now == -1) { + return; + } + preset_container->move_child(preset_container->get_child(preset_from_id), hover_now); + } } void ColorPicker::add_preset(const Color &p_color) { - if (presets.find(p_color)) { - presets.move_to_back(presets.find(p_color)); + List<Color>::Element *e = presets.find(p_color); + if (e) { + presets.move_to_back(e); + preset_cache.move_to_back(preset_cache.find(p_color)); - // Find button to move to the end. - for (int i = 1; i < preset_container->get_child_count(); i++) { - ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(preset_container->get_child(i)); - if (current_btn && p_color == current_btn->get_preset_color()) { - preset_container->move_child(current_btn, preset_container->get_child_count() - 1); - break; - } - } + preset_container->move_child(preset_group->get_pressed_button(), preset_container->get_child_count() - 1); } else { presets.push_back(p_color); preset_cache.push_back(p_color); @@ -585,9 +739,31 @@ void ColorPicker::add_preset(const Color &p_color) { #endif } +void ColorPicker::add_recent_preset(const Color &p_color) { + if (!_select_from_recent_preset_hbc(p_color)) { + if (recent_preset_hbc->get_child_count() >= PRESET_COLUMN_COUNT) { + recent_preset_cache.pop_front(); + recent_presets.pop_front(); + recent_preset_hbc->get_child(PRESET_COLUMN_COUNT - 1)->queue_delete(); + } + recent_presets.push_back(p_color); + recent_preset_cache.push_back(p_color); + _add_recent_preset_button(_get_preset_size(), p_color); + } + _select_from_preset_container(p_color); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + PackedColorArray arr_to_save = get_recent_presets(); + EditorSettings::get_singleton()->set_project_metadata("color_picker", "recent_presets", arr_to_save); + } +#endif +} + void ColorPicker::erase_preset(const Color &p_color) { - if (presets.find(p_color)) { - presets.erase(presets.find(p_color)); + List<Color>::Element *e = presets.find(p_color); + if (e) { + presets.erase(e); preset_cache.erase(preset_cache.find(p_color)); // Find preset button to remove. @@ -608,6 +784,30 @@ void ColorPicker::erase_preset(const Color &p_color) { } } +void ColorPicker::erase_recent_preset(const Color &p_color) { + List<Color>::Element *e = recent_presets.find(p_color); + if (e) { + recent_presets.erase(e); + recent_preset_cache.erase(recent_preset_cache.find(p_color)); + + // Find recent preset button to remove. + for (int i = 1; i < recent_preset_hbc->get_child_count(); i++) { + ColorPresetButton *current_btn = Object::cast_to<ColorPresetButton>(recent_preset_hbc->get_child(i)); + if (current_btn && p_color == current_btn->get_preset_color()) { + current_btn->queue_delete(); + break; + } + } + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + PackedColorArray arr_to_save = get_recent_presets(); + EditorSettings::get_singleton()->set_project_metadata("color_picker", "recent_presets", arr_to_save); + } +#endif + } +} + PackedColorArray ColorPicker::get_presets() const { PackedColorArray arr; arr.resize(presets.size()); @@ -617,16 +817,84 @@ PackedColorArray ColorPicker::get_presets() const { return arr; } +PackedColorArray ColorPicker::get_recent_presets() const { + PackedColorArray arr; + arr.resize(recent_presets.size()); + for (int i = 0; i < recent_presets.size(); i++) { + arr.set(i, recent_presets[i]); + } + return arr; +} + void ColorPicker::set_color_mode(ColorModeType p_mode) { ERR_FAIL_INDEX(p_mode, MODE_MAX); - mode_option_button->select(p_mode); - _set_color_mode(p_mode); + + if (current_mode == p_mode) { + return; + } + + if (slider_theme_modified) { + _reset_theme(); + } + + mode_popup->set_item_checked(current_mode, false); + mode_popup->set_item_checked(p_mode, true); + + if (p_mode < MODE_BUTTON_COUNT) { + mode_btns[p_mode]->set_pressed(true); + } else if (current_mode < MODE_BUTTON_COUNT) { + mode_btns[current_mode]->set_pressed(false); + } + + current_mode = p_mode; + + if (!is_inside_tree()) { + return; + } + + _update_controls(); + _update_color(); } ColorPicker::ColorModeType ColorPicker::get_color_mode() const { return current_mode; } +void ColorPicker::set_colorize_sliders(bool p_colorize_sliders) { + if (colorize_sliders == p_colorize_sliders) { + return; + } + + colorize_sliders = p_colorize_sliders; + mode_popup->set_item_checked(MODE_MAX + 1, colorize_sliders); + + if (colorize_sliders) { + Ref<StyleBoxEmpty> style_box_empty(memnew(StyleBoxEmpty)); + + if (!slider_theme_modified) { + for (int i = 0; i < SLIDER_COUNT; i++) { + sliders[i]->add_theme_style_override("slider", style_box_empty); + } + } + alpha_slider->add_theme_style_override("slider", style_box_empty); + } else { + Ref<StyleBoxFlat> style_box_flat(memnew(StyleBoxFlat)); + style_box_flat->set_default_margin(SIDE_TOP, 16 * get_theme_default_base_scale()); + style_box_flat->set_bg_color(Color(0.2, 0.23, 0.31).lerp(Color(0, 0, 0, 1), 0.3).clamp()); + + if (!slider_theme_modified) { + for (int i = 0; i < SLIDER_COUNT; i++) { + sliders[i]->add_theme_style_override("slider", style_box_flat); + } + } + alpha_slider->add_theme_style_override("slider", style_box_flat); + } +} + +bool ColorPicker::is_colorizing_sliders() const { + return colorize_sliders; +} + void ColorPicker::set_deferred_mode(bool p_enabled) { deferred_mode_enabled = p_enabled; } @@ -862,17 +1130,19 @@ void ColorPicker::_hsv_draw(int p_which, Control *c) { } void ColorPicker::_slider_draw(int p_which) { - modes[current_mode]->slider_draw(p_which); + if (colorize_sliders) { + modes[current_mode]->slider_draw(p_which); + } } void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { Ref<InputEventMouseButton> bev = p_event; - PickerShapeType current_picker = _get_actual_shape(); + PickerShapeType actual_shape = _get_actual_shape(); if (bev.is_valid()) { if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { Vector2 center = c->get_size() / 2.0; - if (current_picker == SHAPE_VHS_CIRCLE || current_picker == SHAPE_OKHSL_CIRCLE) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { real_t dist = center.distance_to(bev->get_position()); if (dist <= center.x) { real_t rad = center.angle_to_point(bev->get_position()); @@ -920,8 +1190,11 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { if (!deferred_mode_enabled) { emit_signal(SNAME("color_changed"), color); } - } else if (deferred_mode_enabled && !bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { - emit_signal(SNAME("color_changed"), color); + } else if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + if (deferred_mode_enabled) { + emit_signal(SNAME("color_changed"), color); + } + add_recent_preset(color); changing_color = false; spinning = false; } else { @@ -938,7 +1211,7 @@ void ColorPicker::_uv_input(const Ref<InputEvent> &p_event, Control *c) { } Vector2 center = c->get_size() / 2.0; - if (current_picker == SHAPE_VHS_CIRCLE || current_picker == SHAPE_OKHSL_CIRCLE) { + if (actual_shape == SHAPE_VHS_CIRCLE || actual_shape == SHAPE_OKHSL_CIRCLE) { real_t dist = center.distance_to(mev->get_position()); real_t rad = center.angle_to_point(mev->get_position()); h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; @@ -993,9 +1266,10 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { set_pick_color(color); _update_color(); - if (!deferred_mode_enabled) { + if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + add_recent_preset(color); emit_signal(SNAME("color_changed"), color); - } else if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + } else if (!deferred_mode_enabled) { emit_signal(SNAME("color_changed"), color); } } @@ -1024,12 +1298,31 @@ void ColorPicker::_w_input(const Ref<InputEvent> &p_event) { } } +void ColorPicker::_slider_or_spin_input(const Ref<InputEvent> &p_event) { + if (line_edit_mouse_release) { + line_edit_mouse_release = false; + return; + } + Ref<InputEventMouseButton> bev = p_event; + if (bev.is_valid() && !bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + add_recent_preset(color); + } +} + +void ColorPicker::_line_edit_input(const Ref<InputEvent> &p_event) { + Ref<InputEventMouseButton> bev = p_event; + if (bev.is_valid() && !bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { + line_edit_mouse_release = true; + } +} + void ColorPicker::_preset_input(const Ref<InputEvent> &p_event, const Color &p_color) { Ref<InputEventMouseButton> bev = p_event; if (bev.is_valid()) { if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { set_pick_color(p_color); + add_recent_preset(color); emit_signal(SNAME("color_changed"), p_color); } else if (bev->is_pressed() && bev->get_button_index() == MouseButton::RIGHT && presets_enabled) { erase_preset(p_color); @@ -1038,6 +1331,22 @@ void ColorPicker::_preset_input(const Ref<InputEvent> &p_event, const Color &p_c } } +void ColorPicker::_recent_preset_pressed(const bool p_pressed, ColorPresetButton *p_preset) { + if (!p_pressed) { + return; + } + set_pick_color(p_preset->get_preset_color()); + + recent_presets.move_to_back(recent_presets.find(p_preset->get_preset_color())); + List<Color>::Element *e = recent_preset_cache.find(p_preset->get_preset_color()); + if (e) { + recent_preset_cache.move_to_back(e); + } + + recent_preset_hbc->move_child(p_preset, 0); + emit_signal(SNAME("color_changed"), p_preset->get_preset_color()); +} + void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { if (!is_inside_tree()) { return; @@ -1066,6 +1375,10 @@ void ColorPicker::_screen_input(const Ref<InputEvent> &p_event) { } } +void ColorPicker::_text_changed(const String &) { + text_changed = true; +} + void ColorPicker::_add_preset_pressed() { add_preset(color); emit_signal(SNAME("preset_added"), color); @@ -1162,7 +1475,6 @@ void ColorPicker::set_presets_visible(bool p_visible) { return; } presets_visible = p_visible; - preset_separator->set_visible(p_visible); preset_container->set_visible(p_visible); } @@ -1186,9 +1498,16 @@ void ColorPicker::_bind_methods() { ClassDB::bind_method(D_METHOD("add_preset", "color"), &ColorPicker::add_preset); ClassDB::bind_method(D_METHOD("erase_preset", "color"), &ColorPicker::erase_preset); ClassDB::bind_method(D_METHOD("get_presets"), &ColorPicker::get_presets); + ClassDB::bind_method(D_METHOD("add_recent_preset", "color"), &ColorPicker::add_recent_preset); + ClassDB::bind_method(D_METHOD("erase_recent_preset", "color"), &ColorPicker::erase_recent_preset); + ClassDB::bind_method(D_METHOD("get_recent_presets"), &ColorPicker::get_recent_presets); ClassDB::bind_method(D_METHOD("set_picker_shape", "shape"), &ColorPicker::set_picker_shape); ClassDB::bind_method(D_METHOD("get_picker_shape"), &ColorPicker::get_picker_shape); + ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &ColorPicker::_get_drag_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &ColorPicker::_can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &ColorPicker::_drop_data_fw); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_pick_color", "get_pick_color"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edit_alpha"), "set_edit_alpha", "is_editing_alpha"); ADD_PROPERTY(PropertyInfo(Variant::INT, "color_mode", PROPERTY_HINT_ENUM, "RGB,HSV,RAW,OKHSL"), "set_color_mode", "get_color_mode"); @@ -1216,7 +1535,7 @@ ColorPicker::ColorPicker() : BoxContainer(true) { HBoxContainer *hb_edit = memnew(HBoxContainer); add_child(hb_edit, false, INTERNAL_MODE_FRONT); - hb_edit->set_v_size_flags(SIZE_EXPAND_FILL); + hb_edit->set_v_size_flags(SIZE_SHRINK_BEGIN); uv_edit = memnew(Control); hb_edit->add_child(uv_edit); @@ -1229,24 +1548,82 @@ ColorPicker::ColorPicker() : HBoxContainer *hb_smpl = memnew(HBoxContainer); add_child(hb_smpl, false, INTERNAL_MODE_FRONT); + btn_pick = memnew(Button); + hb_smpl->add_child(btn_pick); + btn_pick->set_toggle_mode(true); + btn_pick->set_tooltip_text(RTR("Pick a color from the editor window.")); + btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed)); + sample = memnew(TextureRect); hb_smpl->add_child(sample); sample->set_h_size_flags(SIZE_EXPAND_FILL); sample->connect("gui_input", callable_mp(this, &ColorPicker::_sample_input)); sample->connect("draw", callable_mp(this, &ColorPicker::_sample_draw)); - btn_pick = memnew(Button); - btn_pick->set_flat(true); - hb_smpl->add_child(btn_pick); - btn_pick->set_toggle_mode(true); - btn_pick->set_tooltip_text(RTR("Pick a color from the editor window.")); - btn_pick->connect("pressed", callable_mp(this, &ColorPicker::_screen_pick_pressed)); + btn_shape = memnew(MenuButton); + btn_shape->set_flat(false); + hb_smpl->add_child(btn_shape); + btn_shape->set_toggle_mode(true); + btn_shape->set_tooltip_text(RTR("Select a picker shape.")); + + current_shape = SHAPE_HSV_RECTANGLE; + + shape_popup = btn_shape->get_popup(); + shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_rect"), SNAME("ColorPicker")), "HSV Rectangle", SHAPE_HSV_RECTANGLE); + shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_rect_wheel"), SNAME("ColorPicker")), "HSV Wheel", SHAPE_HSV_WHEEL); + shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_circle"), SNAME("ColorPicker")), "VHS Circle", SHAPE_VHS_CIRCLE); + shape_popup->add_icon_radio_check_item(get_theme_icon(SNAME("shape_circle"), SNAME("ColorPicker")), "OKHSL Circle", SHAPE_OKHSL_CIRCLE); + shape_popup->set_item_checked(current_shape, true); + shape_popup->connect("id_pressed", callable_mp(this, &ColorPicker::set_picker_shape)); + + btn_shape->set_icon(shape_popup->get_item_icon(current_shape)); + + add_mode(new ColorModeRGB(this)); + add_mode(new ColorModeHSV(this)); + add_mode(new ColorModeRAW(this)); + add_mode(new ColorModeOKHSL(this)); + + HBoxContainer *mode_hbc = memnew(HBoxContainer); + add_child(mode_hbc, false, INTERNAL_MODE_FRONT); + + mode_group.instantiate(); + for (int i = 0; i < MODE_BUTTON_COUNT; i++) { + mode_btns[i] = memnew(Button); + mode_hbc->add_child(mode_btns[i]); + mode_btns[i]->set_focus_mode(FOCUS_NONE); + mode_btns[i]->set_h_size_flags(SIZE_EXPAND_FILL); + mode_btns[i]->add_theme_style_override("pressed", get_theme_stylebox("tab_selected", "TabContainer")); + mode_btns[i]->add_theme_style_override("normal", get_theme_stylebox("tab_unselected", "TabContainer")); + mode_btns[i]->add_theme_style_override("hover", get_theme_stylebox("tab_selected", "TabContainer")); + mode_btns[i]->set_toggle_mode(true); + mode_btns[i]->set_text(modes[i]->get_name()); + mode_btns[i]->set_button_group(mode_group); + mode_btns[i]->connect("pressed", callable_mp(this, &ColorPicker::set_color_mode).bind((ColorModeType)i)); + } + mode_btns[0]->set_pressed(true); + + btn_mode = memnew(MenuButton); + btn_mode->set_text("..."); + btn_mode->set_flat(false); + mode_hbc->add_child(btn_mode); + btn_mode->set_toggle_mode(true); + btn_mode->set_tooltip_text(RTR("Select a picker mode.")); + + current_mode = MODE_RGB; + + mode_popup = btn_mode->get_popup(); + for (int i = 0; i < modes.size(); i++) { + mode_popup->add_radio_check_item(modes[i]->get_name(), i); + } + mode_popup->add_separator(); + mode_popup->add_check_item("Colorized Sliders", MODE_MAX); + mode_popup->set_item_checked(current_mode, true); + mode_popup->set_item_checked(MODE_MAX + 1, true); + mode_popup->connect("id_pressed", callable_mp(this, &ColorPicker::_set_mode_popup_value)); VBoxContainer *vbl = memnew(VBoxContainer); add_child(vbl, false, INTERNAL_MODE_FRONT); - add_child(memnew(HSeparator), false, INTERNAL_MODE_FRONT); - VBoxContainer *vbr = memnew(VBoxContainer); add_child(vbr, false, INTERNAL_MODE_FRONT); @@ -1265,16 +1642,10 @@ ColorPicker::ColorPicker() : alpha_label->set_text("A"); HBoxContainer *hhb = memnew(HBoxContainer); + hhb->set_alignment(ALIGNMENT_BEGIN); vbr->add_child(hhb); - mode_option_button = memnew(OptionButton); - - hhb->add_child(mode_option_button); - add_mode(new ColorModeRGB(this)); - add_mode(new ColorModeHSV(this)); - add_mode(new ColorModeRAW(this)); - add_mode(new ColorModeOKHSL(this)); - mode_option_button->connect("item_selected", callable_mp(this, &ColorPicker::_set_color_mode)); + hhb->add_child(memnew(Label("Hex"))); text_type = memnew(Button); hhb->add_child(text_type); @@ -1289,9 +1660,9 @@ ColorPicker::ColorPicker() : c_text = memnew(LineEdit); hhb->add_child(c_text); - c_text->set_h_size_flags(SIZE_EXPAND_FILL); c_text->connect("text_submitted", callable_mp(this, &ColorPicker::_html_submitted)); - c_text->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter)); + c_text->connect("text_changed", callable_mp(this, &ColorPicker::_text_changed)); + c_text->connect("focus_entered", callable_mp(this, &ColorPicker::_focus_enter), CONNECT_DEFERRED); c_text->connect("focus_exited", callable_mp(this, &ColorPicker::_html_focus_exit)); wheel_edit = memnew(AspectRatioContainer); @@ -1328,16 +1699,43 @@ ColorPicker::ColorPicker() : _update_controls(); updating = false; - set_pick_color(Color(1, 1, 1)); - - preset_separator = memnew(HSeparator); - add_child(preset_separator, false, INTERNAL_MODE_FRONT); - preset_container = memnew(GridContainer); preset_container->set_h_size_flags(SIZE_EXPAND_FILL); - preset_container->set_columns(preset_column_count); + preset_container->set_columns(PRESET_COLUMN_COUNT); + preset_container->hide(); + + preset_group.instantiate(); + + btn_preset = memnew(Button); + btn_preset->set_text("Swatches"); + btn_preset->set_flat(true); + btn_preset->set_toggle_mode(true); + btn_preset->set_focus_mode(FOCUS_NONE); + btn_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + btn_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_preset, preset_container)); + add_child(btn_preset, false, INTERNAL_MODE_FRONT); + add_child(preset_container, false, INTERNAL_MODE_FRONT); + recent_preset_hbc = memnew(HBoxContainer); + recent_preset_hbc->set_v_size_flags(SIZE_SHRINK_BEGIN); + recent_preset_hbc->hide(); + + recent_preset_group.instantiate(); + + btn_recent_preset = memnew(Button); + btn_recent_preset->set_text("Recent Colors"); + btn_recent_preset->set_flat(true); + btn_recent_preset->set_toggle_mode(true); + btn_recent_preset->set_focus_mode(FOCUS_NONE); + btn_recent_preset->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + btn_recent_preset->connect("toggled", callable_mp(this, &ColorPicker::_show_hide_preset).bind(btn_recent_preset, recent_preset_hbc)); + add_child(btn_recent_preset, false, INTERNAL_MODE_FRONT); + + add_child(recent_preset_hbc, false, INTERNAL_MODE_FRONT); + + set_pick_color(Color(1, 1, 1)); + btn_add_preset = memnew(Button); btn_add_preset->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); btn_add_preset->set_tooltip_text(RTR("Add current color as a preset.")); @@ -1378,6 +1776,7 @@ void ColorPickerButton::pressed() { popup->reset_size(); picker->_update_presets(); + picker->_update_recent_presets(); Rect2i usable_rect = popup->get_usable_parent_rect(); //let's try different positions to see which one we can use @@ -1484,6 +1883,7 @@ void ColorPickerButton::_update_picker() { picker->connect("color_changed", callable_mp(this, &ColorPickerButton::_color_changed)); popup->connect("about_to_popup", callable_mp(this, &ColorPickerButton::_about_to_popup)); popup->connect("popup_hide", callable_mp(this, &ColorPickerButton::_modal_closed)); + picker->connect("minimum_size_changed", callable_mp((Window *)popup, &Window::reset_size)); picker->set_pick_color(color); picker->set_edit_alpha(edit_alpha); picker->set_display_old_color(true); @@ -1523,6 +1923,13 @@ void ColorPresetButton::_notification(int p_what) { Ref<StyleBoxTexture> sb_texture = sb_raw; if (sb_flat.is_valid()) { + sb_flat->set_border_width(SIDE_BOTTOM, 2); + if (get_draw_mode() == DRAW_PRESSED || get_draw_mode() == DRAW_HOVER_PRESSED) { + sb_flat->set_border_color(Color(1, 1, 1, 1)); + } else { + sb_flat->set_border_color(Color(0, 0, 0, 1)); + } + if (preset_color.a < 1) { // Draw a background pattern when the color is transparent. sb_flat->set_bg_color(Color(1, 1, 1)); @@ -1566,8 +1973,10 @@ Color ColorPresetButton::get_preset_color() const { return preset_color; } -ColorPresetButton::ColorPresetButton(Color p_color) { +ColorPresetButton::ColorPresetButton(Color p_color, int p_size) { preset_color = p_color; + set_toggle_mode(true); + set_custom_minimum_size(Size2(p_size, p_size)); } ColorPresetButton::~ColorPresetButton() { diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 05b760b109..6c91575893 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -38,6 +38,7 @@ #include "scene/gui/grid_container.h" #include "scene/gui/label.h" #include "scene/gui/line_edit.h" +#include "scene/gui/menu_button.h" #include "scene/gui/option_button.h" #include "scene/gui/popup.h" #include "scene/gui/separator.h" @@ -63,7 +64,7 @@ public: void set_preset_color(const Color &p_color); Color get_preset_color() const; - ColorPresetButton(Color p_color); + ColorPresetButton(Color p_color, int p_size); ~ColorPresetButton(); }; @@ -96,8 +97,10 @@ private: static Ref<Shader> circle_shader; static Ref<Shader> circle_ok_color_shader; static List<Color> preset_cache; + static List<Color> recent_preset_cache; int current_slider_count = SLIDER_COUNT; + static const int MODE_BUTTON_COUNT = 3; bool slider_theme_modified = true; @@ -114,9 +117,20 @@ private: Control *wheel_uv = nullptr; TextureRect *sample = nullptr; GridContainer *preset_container = nullptr; - HSeparator *preset_separator = nullptr; + HBoxContainer *recent_preset_hbc = nullptr; Button *btn_add_preset = nullptr; Button *btn_pick = nullptr; + Button *btn_preset = nullptr; + Button *btn_recent_preset = nullptr; + PopupMenu *shape_popup = nullptr; + PopupMenu *mode_popup = nullptr; + MenuButton *btn_shape = nullptr; + MenuButton *btn_mode = nullptr; + Button *mode_btns[MODE_BUTTON_COUNT]; + Ref<ButtonGroup> mode_group = nullptr; + ColorPresetButton *selected_recent_preset = nullptr; + Ref<ButtonGroup> preset_group; + Ref<ButtonGroup> recent_preset_group; OptionButton *mode_option_button = nullptr; @@ -135,10 +149,13 @@ private: bool text_is_constructor = false; PickerShapeType current_shape = SHAPE_HSV_RECTANGLE; ColorModeType current_mode = MODE_RGB; + bool colorize_sliders = true; - const int preset_column_count = 9; + const int PRESET_COLUMN_COUNT = 9; int prev_preset_size = 0; + int prev_rencet_preset_size = 0; List<Color> presets; + List<Color> recent_presets; Color color; Color old_color; @@ -150,6 +167,8 @@ private: bool spinning = false; bool presets_enabled = true; bool presets_visible = true; + bool line_edit_mouse_release = false; + bool text_changed = false; float h = 0.0; float s = 0.0; @@ -175,8 +194,12 @@ private: void _uv_input(const Ref<InputEvent> &p_event, Control *c); void _w_input(const Ref<InputEvent> &p_event); + void _slider_or_spin_input(const Ref<InputEvent> &p_event); + void _line_edit_input(const Ref<InputEvent> &p_event); void _preset_input(const Ref<InputEvent> &p_event, const Color &p_color); + void _recent_preset_pressed(const bool pressed, ColorPresetButton *p_preset); void _screen_input(const Ref<InputEvent> &p_event); + void _text_changed(const String &p_new_text); void _add_preset_pressed(); void _screen_pick_pressed(); void _focus_enter(); @@ -185,8 +208,16 @@ private: inline int _get_preset_size(); void _add_preset_button(int p_size, const Color &p_color); + void _add_recent_preset_button(int p_size, const Color &p_color); - void _set_color_mode(ColorModeType p_mode); + void _show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container); + void _update_drop_down_arrow(const bool &p_is_btn_pressed, Button *p_btn_preset); + + void _set_mode_popup_value(ColorModeType p_mode); + + Variant _get_drag_data_fw(const Point2 &p_point, Control *p_from_control); + bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) const; + void _drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control); protected: void _notification(int); @@ -218,13 +249,23 @@ public: PickerShapeType get_picker_shape() const; void add_preset(const Color &p_color); + void add_recent_preset(const Color &p_color); void erase_preset(const Color &p_color); + void erase_recent_preset(const Color &p_color); PackedColorArray get_presets() const; + PackedColorArray get_recent_presets() const; void _update_presets(); + void _update_recent_presets(); + + void _select_from_preset_container(const Color &p_color); + bool _select_from_recent_preset_hbc(const Color &p_color); void set_color_mode(ColorModeType p_mode); ColorModeType get_color_mode() const; + void set_colorize_sliders(bool p_colorize_sliders); + bool is_colorizing_sliders() const; + void set_deferred_mode(bool p_enabled); bool is_deferred_mode() const; diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index ff3adfb9ac..a7d44c0f3c 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -227,7 +227,7 @@ void Slider::_notification(int p_what) { tick->draw(ci, Point2i((size.width - widget_width) / 2, ofs)); } } - grabber->draw(ci, Point2i(size.width / 2 - grabber->get_size().width / 2, size.height - ratio * areasize - grabber->get_size().height)); + grabber->draw(ci, Point2i(size.width / 2 - grabber->get_size().width / 2 + get_theme_constant(SNAME("grabber_offset")), size.height - ratio * areasize - grabber->get_size().height)); } else { int widget_height = style->get_minimum_size().height + style->get_center_size().height; double areasize = size.width - grabber->get_size().width; @@ -245,7 +245,7 @@ void Slider::_notification(int p_what) { tick->draw(ci, Point2i(ofs, (size.height - widget_height) / 2)); } } - grabber->draw(ci, Point2i(ratio * areasize, size.height / 2 - grabber->get_size().height / 2)); + grabber->draw(ci, Point2i(ratio * areasize, size.height / 2 - grabber->get_size().height / 2 + get_theme_constant(SNAME("grabber_offset")))); } } break; } diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index f03e3813cc..c9d92cea3f 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -553,6 +553,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("grabber_disabled", "HSlider", icons["slider_grabber_disabled"]); theme->set_icon("tick", "HSlider", icons["hslider_tick"]); + theme->set_constant("grabber_offset", "HSlider", 0); + // VSlider theme->set_stylebox("slider", "VSlider", style_slider); @@ -564,6 +566,8 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("grabber_disabled", "VSlider", icons["slider_grabber_disabled"]); theme->set_icon("tick", "VSlider", icons["vslider_tick"]); + theme->set_constant("grabber_offset", "VSlider", 0); + // SpinBox theme->set_icon("updown", "SpinBox", icons["updown"]); @@ -876,7 +880,12 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("h_width", "ColorPicker", 30 * scale); theme->set_constant("label_width", "ColorPicker", 10 * scale); + theme->set_icon("folded_arrow", "ColorPicker", icons["arrow_right"]); + theme->set_icon("expanded_arrow", "ColorPicker", icons["arrow_down"]); theme->set_icon("screen_picker", "ColorPicker", icons["color_picker_pipette"]); + theme->set_icon("shape_circle", "ColorPicker", icons["picker_shape_circle"]); + theme->set_icon("shape_rect", "ColorPicker", icons["picker_shape_rectangle"]); + theme->set_icon("shape_rect_wheel", "ColorPicker", icons["picker_shape_rectangle_wheel"]); theme->set_icon("add_preset", "ColorPicker", icons["add"]); theme->set_icon("color_hue", "ColorPicker", icons["color_picker_hue"]); theme->set_icon("sample_bg", "ColorPicker", icons["mini_checkerboard"]); diff --git a/scene/resources/default_theme/picker_shape_circle.svg b/scene/resources/default_theme/picker_shape_circle.svg new file mode 100644 index 0000000000..8e7fb7f06e --- /dev/null +++ b/scene/resources/default_theme/picker_shape_circle.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h16v16h-16z"/></clipPath><g clip-path="url(#a)" fill="#eaeaea"><rect height="11" rx="5.5" transform="translate(1 2)" width="11"/><path d="m0 0h2v11h-2z" transform="translate(13 2)"/></g></svg> diff --git a/scene/resources/default_theme/picker_shape_rectangle.svg b/scene/resources/default_theme/picker_shape_rectangle.svg new file mode 100644 index 0000000000..3c7dd46884 --- /dev/null +++ b/scene/resources/default_theme/picker_shape_rectangle.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m0 0h16v16h-16z"/></clipPath><g clip-path="url(#a)" fill="#eaeaea"><path d="m0 0h11v11h-11z" transform="translate(1 2)"/><path d="m0 0h2v11h-2z" transform="translate(13 2)"/></g></svg> diff --git a/scene/resources/default_theme/picker_shape_rectangle_wheel.svg b/scene/resources/default_theme/picker_shape_rectangle_wheel.svg new file mode 100644 index 0000000000..e85665a8f2 --- /dev/null +++ b/scene/resources/default_theme/picker_shape_rectangle_wheel.svg @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + height="16" + viewBox="0 0 16 16" + width="16" + version="1.1" + id="svg11" + sodipodi:docname="PickerShapeRectangleWheel.svg" + inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs15" /> + <sodipodi:namedview + id="namedview13" + pagecolor="#505050" + bordercolor="#ffffff" + borderopacity="1" + inkscape:pageshadow="0" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="1" + showgrid="true" + inkscape:zoom="16" + inkscape:cx="0.53125" + inkscape:cy="5.28125" + inkscape:window-width="1920" + inkscape:window-height="1001" + inkscape:window-x="-9" + inkscape:window-y="-9" + inkscape:window-maximized="1" + inkscape:current-layer="svg11"> + <inkscape:grid + type="xygrid" + id="grid944" /> + </sodipodi:namedview> + <clipPath + id="a"> + <path + d="m0 0h16v16h-16z" + id="path2" /> + </clipPath> + <g + clip-path="url(#a)" + fill="#eaeaea" + id="g9" + transform="matrix(0.85714286,0,0,0.85714286,1.1428571,1.1428571)"> + <path + d="M 7,2 A 5,5 0 1 0 12,7 5.006,5.006 0 0 0 7,2 M 7,0 A 7,7 0 1 1 0,7 7,7 0 0 1 7,0 Z" + transform="translate(1,1)" + id="path5" /> + <path + d="M 0,0 H 7 V 7 H 0 Z" + transform="translate(4.5,4.5)" + id="path7" /> + </g> +</svg> |