diff --git a/scene/gui/color_mode.h b/scene/gui/color_mode.h index 38e3b9f0ed4..9f228412e0f 100644 --- a/scene/gui/color_mode.h +++ b/scene/gui/color_mode.h @@ -146,6 +146,4 @@ public: ColorModeOKHSL(ColorPicker *p_color_picker) : ColorMode(p_color_picker) {} - - ~ColorModeOKHSL() {} }; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 426e0092d71..40eeca5a78e 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -33,6 +33,7 @@ #include "core/io/image.h" #include "scene/gui/aspect_ratio_container.h" #include "scene/gui/color_mode.h" +#include "scene/gui/color_picker_shape.h" #include "scene/gui/file_dialog.h" #include "scene/gui/grid_container.h" #include "scene/gui/label.h" @@ -69,12 +70,6 @@ void ColorPicker::_notification(int p_what) { btn_pick->set_tooltip_text(ETR("Pick a color from the application window.")); btn_pick->connect(SceneStringName(pressed), callable_mp(this, &ColorPicker::_pick_button_pressed_legacy)); } - - // In HSV Wheel shape the wheel is around the rectangle and the engine cannot automatically select - // the square while pressing ui_up to change focused control from the wheel. - wheel->set_focus_neighbor(SIDE_TOP, wheel_uv->get_path()); - wheel_uv->set_focus_neighbor(SIDE_BOTTOM, wheel->get_path()); - } break; case NOTIFICATION_TRANSLATION_CHANGED: { @@ -104,11 +99,20 @@ void ColorPicker::_notification(int p_what) { btn_shape->set_custom_minimum_size(Size2(28 * theme_cache.base_scale, 0)); btn_mode->set_custom_minimum_size(Size2(28 * theme_cache.base_scale, 0)); - uv_edit->set_custom_minimum_size(Size2(theme_cache.sv_width, theme_cache.sv_height)); - w_edit->set_custom_minimum_size(Size2(theme_cache.h_width, 0)); + { + int i = 0; + for (ColorPickerShape *shape : shapes) { + if (shape->is_initialized) { + shape->update_theme(); + } + shape_popup->set_item_icon(i, shape->get_icon()); + i++; + } + } - wheel_edit->set_custom_minimum_size(Size2(theme_cache.sv_width, theme_cache.sv_height)); - wheel_margin->add_theme_constant_override("margin_bottom", 8 * theme_cache.base_scale); + if (current_shape != SHAPE_NONE) { + btn_shape->set_button_icon(shape_popup->get_item_icon(current_shape)); + } for (int i = 0; i < SLIDER_COUNT; i++) { labels[i]->set_custom_minimum_size(Size2(theme_cache.label_width, 0)); @@ -125,15 +129,6 @@ void ColorPicker::_notification(int p_what) { mode_btns[i]->end_bulk_theme_override(); } - shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_HSV_RECTANGLE), theme_cache.shape_rect); - shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_HSV_WHEEL), theme_cache.shape_rect_wheel); - shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_VHS_CIRCLE), theme_cache.shape_circle); - shape_popup->set_item_icon(shape_popup->get_item_index(SHAPE_OKHSL_CIRCLE), theme_cache.shape_circle); - - if (current_shape != SHAPE_NONE) { - btn_shape->set_button_icon(shape_popup->get_item_icon(current_shape)); - } - internal_margin->begin_bulk_theme_override(); internal_margin->add_theme_constant_override(SNAME("margin_bottom"), theme_cache.content_margin); internal_margin->add_theme_constant_override(SNAME("margin_left"), theme_cache.content_margin); @@ -161,25 +156,31 @@ void ColorPicker::_notification(int p_what) { case NOTIFICATION_FOCUS_ENTER: case NOTIFICATION_FOCUS_EXIT: { - cursor_editing = false; + if (current_shape != SHAPE_NONE) { + shapes[current_shape]->cursor_editing = false; + } } break; case NOTIFICATION_INTERNAL_PROCESS: { if (!is_picking_color) { Input *input = Input::get_singleton(); - bool is_picker_focused = uv_edit->has_focus() || wheel_uv->has_focus() || wheel->has_focus(); - if (input->is_action_just_released("ui_left") || input->is_action_just_released("ui_right") || input->is_action_just_released("ui_up") || input->is_action_just_released("ui_down")) { gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS; - echo_multiplier = 1; + if (current_shape == SHAPE_NONE) { + shapes[current_shape]->echo_multiplier = 1; + } accept_event(); set_process_internal(false); return; } + if (current_shape == SHAPE_NONE) { + return; + } + gamepad_event_delay_ms -= get_process_delta_time(); if (gamepad_event_delay_ms <= 0) { gamepad_event_delay_ms = GAMEPAD_EVENT_REPEAT_RATE_MS + gamepad_event_delay_ms; @@ -187,11 +188,9 @@ void ColorPicker::_notification(int p_what) { Vector2 color_change_vector = Vector2( input->is_action_pressed("ui_right") - input->is_action_pressed("ui_left"), input->is_action_pressed("ui_down") - input->is_action_pressed("ui_up")); - if (is_picker_focused) { - _update_uv_cursor(color_change_vector, true); - } else if (w_edit->has_focus()) { - _update_w_cursor(color_change_vector.y, true); - } + + shapes[current_shape]->update_cursor(color_change_vector, true); + accept_event(); } return; } @@ -316,39 +315,7 @@ void ColorPicker::set_focus_on_line_edit() { } void ColorPicker::set_focus_on_picker_shape() { - switch (current_shape) { - case SHAPE_HSV_RECTANGLE: - callable_mp(w_edit, &Control::grab_focus).call_deferred(); - break; - case SHAPE_HSV_WHEEL: - callable_mp(wheel, &Control::grab_focus).call_deferred(); - break; - case SHAPE_VHS_CIRCLE: - case SHAPE_OKHSL_CIRCLE: - callable_mp(wheel_uv, &Control::grab_focus).call_deferred(); - break; - default: { - } - } -} - -void ColorPicker::_picker_shape_focus_entered() { - Input *input = Input::get_singleton(); - if (!(input->is_action_pressed("ui_up") || input->is_action_pressed("ui_down") || input->is_action_pressed("ui_left") || input->is_action_pressed("ui_right"))) { - cursor_editing = true; - } - - if (current_shape == SHAPE_HSV_WHEEL) { - wheel_focus_mode = wheel->has_focus() ? 2 : 1; - } -} - -void ColorPicker::_picker_shape_focus_exited() { - cursor_editing = false; - if (wheel_focus_mode == 2) { - wheel_uv->queue_redraw(); - } - wheel_focus_mode = 0; + shapes[current_shape]->grab_focus(); } void ColorPicker::_update_controls() { @@ -383,82 +350,25 @@ void ColorPicker::_update_controls() { alpha_label->hide(); } - bool pre_update_cursor_editing = cursor_editing; - switch (current_shape) { - case SHAPE_HSV_RECTANGLE: - w_edit->show(); - uv_edit->show(); - btn_shape->show(); + int i = 0; + for (ColorPickerShape *shape : shapes) { + bool is_active = current_shape == i; + i++; - if (wheel_focus_mode == 1) { - uv_edit->grab_focus(); - } else if (wheel_focus_mode == 2) { - w_edit->grab_focus(); + if (!shape->is_initialized) { + if (is_active) { + // Controls are initialized on demand, because ColorPicker does not need them all at once. + shape->initialize_controls(); + } else { + continue; } + } - wheel_edit->hide(); - wheel->set_focus_mode(FOCUS_NONE); - break; - case SHAPE_HSV_WHEEL: - wheel_edit->show(); - wheel->set_focus_mode(FOCUS_ALL); - - if (w_edit->has_focus()) { - wheel_focus_mode = 2; - wheel->grab_focus(); - } else if (uv_edit->has_focus()) { - wheel_uv->grab_focus(); - } - - w_edit->hide(); - uv_edit->hide(); - btn_shape->show(); - wheel->set_material(wheel_mat); - break; - case SHAPE_VHS_CIRCLE: - wheel_edit->show(); - w_edit->show(); - - if (uv_edit->has_focus()) { - wheel_uv->grab_focus(); - } else if (wheel->has_focus()) { - w_edit->grab_focus(); - } - - uv_edit->hide(); - btn_shape->show(); - wheel->set_material(circle_mat); - circle_mat->set_shader(circle_shader); - wheel->set_focus_mode(FOCUS_NONE); - break; - case SHAPE_OKHSL_CIRCLE: - wheel_edit->show(); - w_edit->show(); - - if (uv_edit->has_focus()) { - wheel_uv->grab_focus(); - } else if (wheel->has_focus()) { - w_edit->grab_focus(); - } - - uv_edit->hide(); - btn_shape->show(); - wheel->set_material(circle_mat); - circle_mat->set_shader(circle_ok_color_shader); - wheel->set_focus_mode(FOCUS_NONE); - break; - case SHAPE_NONE: - wheel->set_focus_mode(FOCUS_NONE); - wheel_edit->hide(); - w_edit->hide(); - uv_edit->hide(); - btn_shape->hide(); - break; - default: { + for (Control *control : shape->controls) { + control->set_visible(is_active); } } - - cursor_editing = pre_update_cursor_editing; + btn_shape->set_visible(current_shape != SHAPE_NONE); } void ColorPicker::_set_pick_color(const Color &p_color, bool p_update_sliders) { @@ -566,6 +476,10 @@ void ColorPicker::add_mode(ColorMode *p_mode) { modes.push_back(p_mode); } +void ColorPicker::add_shape(ColorPickerShape *p_shape) { + shapes.push_back(p_shape); +} + void ColorPicker::create_slider(GridContainer *gc, int idx) { Label *lbl = memnew(Label); lbl->set_v_size_flags(SIZE_SHRINK_CENTER); @@ -677,7 +591,7 @@ void ColorPicker::_copy_color_to_hsv() { } void ColorPicker::_copy_hsv_to_color() { - if (current_shape == SHAPE_OKHSL_CIRCLE) { + if (current_shape != SHAPE_NONE && shapes[current_shape]->is_ok_hsl()) { color.set_ok_hsl(ok_hsl_h, ok_hsl_s, ok_hsl_l, color.a); } else { color.set_hsv(h, s, v, color.a); @@ -716,21 +630,21 @@ void ColorPicker::_reset_sliders_theme() { for (int i = 0; i < SLIDER_COUNT; i++) { sliders[i]->begin_bulk_theme_override(); - sliders[i]->add_theme_icon_override("grabber", theme_cache.bar_arrow); - sliders[i]->add_theme_icon_override("grabber_highlight", theme_cache.bar_arrow); - sliders[i]->add_theme_constant_override("grabber_offset", 8 * theme_cache.base_scale); + sliders[i]->add_theme_icon_override(SNAME("grabber"), theme_cache.bar_arrow); + sliders[i]->add_theme_icon_override(SNAME("grabber_highlight"), theme_cache.bar_arrow); + sliders[i]->add_theme_constant_override(SNAME("grabber_offset"), 8 * theme_cache.base_scale); if (!colorize_sliders) { - sliders[i]->add_theme_style_override("slider", style_box_flat); + sliders[i]->add_theme_style_override(SNAME("slider"), style_box_flat); } sliders[i]->end_bulk_theme_override(); } alpha_slider->begin_bulk_theme_override(); - alpha_slider->add_theme_icon_override("grabber", theme_cache.bar_arrow); - alpha_slider->add_theme_icon_override("grabber_highlight", theme_cache.bar_arrow); - alpha_slider->add_theme_constant_override("grabber_offset", 8 * theme_cache.base_scale); + alpha_slider->add_theme_icon_override(SNAME("grabber"), theme_cache.bar_arrow); + alpha_slider->add_theme_icon_override(SNAME("grabber_highlight"), theme_cache.bar_arrow); + alpha_slider->add_theme_constant_override(SNAME("grabber_offset"), 8 * theme_cache.base_scale); if (!colorize_sliders) { - alpha_slider->add_theme_style_override("slider", style_box_flat); + alpha_slider->add_theme_style_override(SNAME("slider"), style_box_flat); } alpha_slider->end_bulk_theme_override(); } @@ -796,15 +710,18 @@ void ColorPicker::_update_color(bool p_update_sliders) { _update_text_value(); + if (current_shape != SHAPE_NONE) { + for (Control *control : shapes[current_shape]->controls) { + control->queue_redraw(); + } + } + sample->queue_redraw(); - uv_edit->queue_redraw(); - w_edit->queue_redraw(); + for (int i = 0; i < current_slider_count; i++) { sliders[i]->queue_redraw(); } alpha_slider->queue_redraw(); - wheel->queue_redraw(); - wheel_uv->queue_redraw(); updating = false; } @@ -1446,187 +1363,7 @@ void ColorPicker::_sample_draw() { if (color.r > 1 || color.g > 1 || color.b > 1) { // Draw an indicator to denote that the new color is "overbright" and can't be displayed accurately in the preview. - sample->draw_texture(theme_cache.overbright_indicator, Point2(display_old_color ? sample->get_size().width * 0.5 : 0, 0)); - } -} - -void ColorPicker::_draw_focus_stylebox(Control *p_c, Rect2 p_focus_rect, Ref &p_focus_stylebox) { - if (p_c->has_focus()) { - RID ci = p_c->get_canvas_item(); - if (wheel_focus_mode == 2) { - ci = wheel_uv->get_canvas_item(); - p_focus_stylebox = theme_cache.picker_focus_circle; - } - - if (!cursor_editing) { - Color not_editing_color = theme_cache.focused_not_editing_cursor_color; - if (p_focus_stylebox == theme_cache.picker_focus_circle) { - RenderingServer::get_singleton()->canvas_item_add_circle(ci, p_focus_rect.get_center(), p_focus_rect.size.y / 2, not_editing_color); - } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, p_focus_rect, not_editing_color); - } - } - p_focus_stylebox->draw(ci, p_focus_rect); - } -} - -void ColorPicker::_hsv_draw(int p_which, Control *c) { - if (!c) { - return; - } - Rect2 focus_rect = Rect2(Point2(), c->get_size()); - Ref focus_stylebox = theme_cache.picker_focus_rectangle; - - if (p_which == 0) { - Color col = color; - Vector2 center = c->get_size() / 2.0; - - if (current_shape == SHAPE_HSV_RECTANGLE || current_shape == SHAPE_HSV_WHEEL) { - Vector points; - Vector colors; - Vector colors2; - points.resize(4); - colors.resize(4); - colors2.resize(4); - if (current_shape == SHAPE_HSV_RECTANGLE) { - points.set(0, Vector2()); - points.set(1, Vector2(c->get_size().x, 0)); - points.set(2, c->get_size()); - points.set(3, Vector2(0, c->get_size().y)); - } else { - real_t ring_radius_x = Math_SQRT12 * c->get_size().width * WHEEL_RADIUS; - real_t ring_radius_y = Math_SQRT12 * c->get_size().height * WHEEL_RADIUS; - - points.set(0, center - Vector2(ring_radius_x, ring_radius_y)); - points.set(1, center + Vector2(ring_radius_x, -ring_radius_y)); - points.set(2, center + Vector2(ring_radius_x, ring_radius_y)); - points.set(3, center + Vector2(-ring_radius_x, ring_radius_y)); - } - colors.set(0, Color(1, 1, 1, 1)); - colors.set(1, Color(1, 1, 1, 1)); - colors.set(2, Color(0, 0, 0, 1)); - colors.set(3, Color(0, 0, 0, 1)); - c->draw_polygon(points, colors); - - col.set_hsv(h, 1, 1); - col.a = 0; - colors2.set(0, col); - col.a = 1; - colors2.set(1, col); - col.set_hsv(h, 1, 0); - colors2.set(2, col); - col.a = 0; - colors2.set(3, col); - c->draw_polygon(points, colors2); - } - - int x; - int y; - if (current_shape == SHAPE_VHS_CIRCLE || current_shape == SHAPE_OKHSL_CIRCLE) { - Vector2 hue_offset; - if (current_shape == SHAPE_OKHSL_CIRCLE) { - hue_offset = center * Vector2(Math::cos(ok_hsl_h * Math_TAU), Math::sin(ok_hsl_h * Math_TAU)) * ok_hsl_s; - } else { - hue_offset = center * Vector2(Math::cos(h * Math_TAU), Math::sin(h * Math_TAU)) * s; - } - x = center.x + hue_offset.x - (theme_cache.picker_cursor->get_width() / 2); - y = center.y + hue_offset.y - (theme_cache.picker_cursor->get_height() / 2); - - focus_stylebox = theme_cache.picker_focus_circle; - } else { - real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * WHEEL_RADIUS : 0; - real_t corner_y = (c == wheel_uv) ? center.y - Math_SQRT12 * c->get_size().height * WHEEL_RADIUS : 0; - - Size2 real_size(c->get_size().x - corner_x * 2, c->get_size().y - corner_y * 2); - x = CLAMP(real_size.x * s, 0, real_size.x) + corner_x - (theme_cache.picker_cursor->get_width() / 2); - y = CLAMP(real_size.y - real_size.y * v, 0, real_size.y) + corner_y - (theme_cache.picker_cursor->get_height() / 2); - if (c == wheel_uv) { - focus_rect = Rect2(Point2(corner_x, corner_y), real_size); - } - } - _draw_focus_stylebox(c, focus_rect, focus_stylebox); - Color _col = color; - _col.a = 1.0; - c->draw_texture(theme_cache.picker_cursor_bg, Point2(x, y), _col); - c->draw_texture(theme_cache.picker_cursor, Point2(x, y)); - - if (current_shape == SHAPE_HSV_WHEEL) { - float _radius = WHEEL_RADIUS * 2.0; - _radius += (1.0 - _radius) * 0.5; - Point2 pos = center - (theme_cache.picker_cursor->get_size() * 0.5) + Point2(center.x * Math::cos(h * Math_TAU) * _radius, center.y * Math::sin(h * Math_TAU) * _radius); - c->draw_texture(theme_cache.picker_cursor, pos); - } - - } else if (p_which == 1) { - if (current_shape == SHAPE_HSV_RECTANGLE) { - c->draw_set_transform(Point2(), -Math_PI / 2, Size2(c->get_size().x, -c->get_size().y)); - c->draw_texture_rect(theme_cache.color_hue, Rect2(Point2(), Size2(1, 1))); - c->draw_set_transform(Point2(), 0, Size2(1, 1)); - int y = c->get_size().y - c->get_size().y * (1.0 - h); - Color col; - col.set_hsv(h, 1, 1); - _draw_focus_stylebox(c, focus_rect, focus_stylebox); - c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted()); - } else if (current_shape == SHAPE_OKHSL_CIRCLE) { - Vector points; - Vector colors; - Color col; - col.set_ok_hsl(ok_hsl_h, ok_hsl_s, 1); - Color col2; - col2.set_ok_hsl(ok_hsl_h, ok_hsl_s, 0.5); - Color col3; - col3.set_ok_hsl(ok_hsl_h, ok_hsl_s, 0); - points.resize(6); - colors.resize(6); - points.set(0, Vector2(c->get_size().x, 0)); - points.set(1, Vector2(c->get_size().x, c->get_size().y * 0.5)); - points.set(2, c->get_size()); - points.set(3, Vector2(0, c->get_size().y)); - points.set(4, Vector2(0, c->get_size().y * 0.5)); - points.set(5, Vector2()); - colors.set(0, col); - colors.set(1, col2); - colors.set(2, col3); - colors.set(3, col3); - colors.set(4, col2); - colors.set(5, col); - c->draw_polygon(points, colors); - int y = c->get_size().y - c->get_size().y * CLAMP(ok_hsl_l, 0, 1); - col.set_ok_hsl(ok_hsl_h, 1, ok_hsl_l); - _draw_focus_stylebox(c, focus_rect, focus_stylebox); - c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted()); - } else if (current_shape == SHAPE_VHS_CIRCLE) { - Vector points; - Vector colors; - Color col; - col.set_hsv(h, s, 1); - points.resize(4); - colors.resize(4); - points.set(0, Vector2()); - points.set(1, Vector2(c->get_size().x, 0)); - points.set(2, c->get_size()); - points.set(3, Vector2(0, c->get_size().y)); - colors.set(0, col); - colors.set(1, col); - colors.set(2, Color(0, 0, 0)); - colors.set(3, Color(0, 0, 0)); - c->draw_polygon(points, colors); - int y = c->get_size().y - c->get_size().y * CLAMP(v, 0, 1); - col.set_hsv(h, 1, v); - _draw_focus_stylebox(c, focus_rect, focus_stylebox); - c->draw_line(Point2(0, y), Point2(c->get_size().x, y), col.inverted()); - } - } else if (p_which == 2) { - c->draw_rect(Rect2(Point2(), c->get_size()), Color(1, 1, 1)); - if (current_shape == SHAPE_VHS_CIRCLE) { - circle_mat->set_shader_parameter("v", v); - } else if (current_shape == SHAPE_OKHSL_CIRCLE) { - circle_mat->set_shader_parameter("ok_hsl_l", ok_hsl_l); - } - - if (wheel_focus_mode == 2) { - _draw_focus_stylebox(c, focus_rect, focus_stylebox); - } + sample->draw_texture(theme_cache.overbright_indicator, Point2(sample->get_size().width * 0.5, 0)); } } @@ -1636,396 +1373,6 @@ void ColorPicker::_slider_draw(int p_which) { } } -int ColorPicker::_get_edge_h_change(const Vector2 &p_color_change_vector) { - int h_change = 0; - - if (h > 0 && h < 0.5) { - h_change -= p_color_change_vector.x; - } else if (h > 0.5 && h < 1) { - h_change += p_color_change_vector.x; - } - - if (h > 0.25 && h < 0.75) { - h_change -= p_color_change_vector.y; - } else if (h < 0.25 || h > 0.75) { - h_change += p_color_change_vector.y; - } - - return h_change; -} - -float ColorPicker::_get_h_on_circle_edge(const Vector2 &p_color_change_vector) { - int h_change = _get_edge_h_change(p_color_change_vector); - - float target_h = Math::wrapf(h + h_change / 360.0, 0, 1); - int current_quarter = h * 4; - int future_quarter = target_h * 4; - if (p_color_change_vector.y > 0 && ((future_quarter == 0 && current_quarter == 1) || (future_quarter == 1 && current_quarter == 0))) { - target_h = 0.25f; - } else if (p_color_change_vector.y < 0 && ((future_quarter == 2 && current_quarter == 3) || (future_quarter == 3 && current_quarter == 2))) { - target_h = 0.75f; - } else if (p_color_change_vector.x < 0 && ((future_quarter == 1 && current_quarter == 2) || (future_quarter == 2 && current_quarter == 1))) { - target_h = 0.5f; - } else if (p_color_change_vector.x > 0 && ((future_quarter == 3 && current_quarter == 0) || (future_quarter == 0 && current_quarter == 3))) { - target_h = 0; - } - return target_h; -} - -float ColorPicker::_get_h_on_wheel(const Vector2 &p_color_change_vector) { - int h_change = _get_edge_h_change(p_color_change_vector); - - float target_h = Math::wrapf(h + h_change / 360.0, 0, 1); - int current_quarter = h * 4; - int future_quarter = target_h * 4; - - if (p_color_change_vector.y > 0 && ((future_quarter == 0 && current_quarter == 1) || (future_quarter == 1 && current_quarter == 0))) { - rotate_next_echo_event = !rotate_next_echo_event; - } else if (p_color_change_vector.y < 0 && ((future_quarter == 2 && current_quarter == 3) || (future_quarter == 3 && current_quarter == 2))) { - rotate_next_echo_event = !rotate_next_echo_event; - } else if (p_color_change_vector.x < 0 && ((future_quarter == 1 && current_quarter == 2) || (future_quarter == 2 && current_quarter == 1))) { - rotate_next_echo_event = !rotate_next_echo_event; - } else if (p_color_change_vector.x > 0 && ((future_quarter == 3 && current_quarter == 0) || (future_quarter == 0 && current_quarter == 3))) { - rotate_next_echo_event = !rotate_next_echo_event; - } - - return target_h; -} - -void ColorPicker::_update_uv_cursor(Vector2 &p_color_change_vector, bool p_is_echo) { - echo_multiplier = p_is_echo ? CLAMP(echo_multiplier * echo_multiplier_step, 1, 25) : 1; - - if (!p_color_change_vector.is_zero_approx()) { - p_color_change_vector *= echo_multiplier; - - if (current_shape == SHAPE_HSV_RECTANGLE) { - s = CLAMP(s + p_color_change_vector.x / 100.0, 0, 1); - v = CLAMP(v - p_color_change_vector.y / 100.0, 0, 1); - } else if (current_shape == SHAPE_VHS_CIRCLE || current_shape == SHAPE_OKHSL_CIRCLE) { - Vector2 center = wheel_uv->get_size() / 2.0; - - if (circle_keyboard_joypad_picker_cursor_position == Vector2i()) { - Vector2 hue_offset; - if (current_shape == SHAPE_OKHSL_CIRCLE) { - hue_offset = center * Vector2(Math::cos(ok_hsl_h * Math_TAU), Math::sin(ok_hsl_h * Math_TAU)) * ok_hsl_s; - } else { - hue_offset = center * Vector2(Math::cos(h * Math_TAU), Math::sin(h * Math_TAU)) * s; - } - circle_keyboard_joypad_picker_cursor_position = center + hue_offset; - } - - Vector2i potential_cursor_position = circle_keyboard_joypad_picker_cursor_position + p_color_change_vector; - real_t potential_new_cursor_distance = center.distance_to(potential_cursor_position); - real_t dist_pre = center.distance_to(circle_keyboard_joypad_picker_cursor_position); - if (s < 1 || potential_new_cursor_distance < dist_pre) { - circle_keyboard_joypad_picker_cursor_position += p_color_change_vector; - real_t dist = center.distance_to(circle_keyboard_joypad_picker_cursor_position); - real_t rad = center.angle_to_point(circle_keyboard_joypad_picker_cursor_position); - h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; - s = CLAMP(dist / center.x, 0, 1); - } else { - h = _get_h_on_circle_edge(p_color_change_vector); - circle_keyboard_joypad_picker_cursor_position = Vector2i(); - } - - ok_hsl_h = h; - ok_hsl_s = s; - } else if (current_shape == SHAPE_HSV_WHEEL) { - if (wheel_focus_mode == 1) { - s = CLAMP(s + p_color_change_vector.x / 100.0, 0, 1); - v = CLAMP(v - p_color_change_vector.y / 100.0, 0, 1); - } else if (wheel_focus_mode == 2) { - if (p_is_echo && rotate_next_echo_event) { - p_color_change_vector *= -1; - } else { - rotate_next_echo_event = false; - } - - h = _get_h_on_wheel(p_color_change_vector); - wheel_uv->queue_redraw(); - } - } - - _copy_hsv_to_color(); - last_color = color; - set_pick_color(color); - - emit_signal(SNAME("color_changed"), color); - } else { - echo_multiplier = 1; - } - - accept_event(); -} - -void ColorPicker::_update_cursor_editing(const Ref &p_event, Control *p_c) { - if (p_event->is_action_pressed("ui_accept", false, true)) { - cursor_editing = !cursor_editing; - accept_event(); - - if (wheel_focus_mode == 2) { - wheel_uv->queue_redraw(); - } - p_c->queue_redraw(); - } - - if (cursor_editing && p_event->is_action_pressed("ui_cancel", false, true)) { - cursor_editing = false; - accept_event(); - - if (wheel_focus_mode == 2) { - wheel_uv->queue_redraw(); - } - p_c->queue_redraw(); - } -} - -void ColorPicker::_uv_input(const Ref &p_event, Control *c) { - _update_cursor_editing(p_event, c); - - Ref bev = p_event; - - if (bev.is_valid()) { - if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { - Vector2 center = c->get_size() / 2.0; - if (current_shape == SHAPE_VHS_CIRCLE || current_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()); - h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; - s = CLAMP(dist / center.x, 0, 1); - ok_hsl_h = h; - ok_hsl_s = s; - circle_keyboard_joypad_picker_cursor_position = Vector2i(); - } else { - return; - } - } else { - real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * 0.42 : 0; - real_t corner_y = (c == wheel_uv) ? center.y - Math_SQRT12 * c->get_size().height * 0.42 : 0; - Size2 real_size(c->get_size().x - corner_x * 2, c->get_size().y - corner_y * 2); - - if (bev->get_position().x < corner_x || bev->get_position().x > c->get_size().x - corner_x || - bev->get_position().y < corner_y || bev->get_position().y > c->get_size().y - corner_y) { - { - real_t dist = center.distance_to(bev->get_position()); - if (dist >= center.x * 0.84 && dist <= center.x) { - real_t rad = center.angle_to_point(bev->get_position()); - h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; - spinning = true; - wheel->grab_focus(); - wheel_uv->queue_redraw(); - } else { - return; - } - } - } - - if (!spinning) { - real_t x = CLAMP(bev->get_position().x - corner_x, 0, real_size.x); - real_t y = CLAMP(bev->get_position().y - corner_y, 0, real_size.y); - - s = x / real_size.x; - v = 1.0 - y / real_size.y; - wheel_focus_mode = 1; - } - } - - changing_color = true; - - _copy_hsv_to_color(); - last_color = color; - set_pick_color(color); - - if (!deferred_mode_enabled) { - 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 { - changing_color = false; - spinning = false; - } - } - - Ref mev = p_event; - - if (mev.is_valid()) { - if (!changing_color) { - return; - } - - Vector2 center = c->get_size() / 2.0; - if (current_shape == SHAPE_VHS_CIRCLE || current_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; - s = CLAMP(dist / center.x, 0, 1); - ok_hsl_h = h; - ok_hsl_s = s; - circle_keyboard_joypad_picker_cursor_position = Vector2i(); - } else { - if (spinning) { - real_t rad = center.angle_to_point(mev->get_position()); - h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; - wheel_uv->queue_redraw(); - } else { - real_t corner_x = (c == wheel_uv) ? center.x - Math_SQRT12 * c->get_size().width * 0.42 : 0; - real_t corner_y = (c == wheel_uv) ? center.y - Math_SQRT12 * c->get_size().height * 0.42 : 0; - Size2 real_size(c->get_size().x - corner_x * 2, c->get_size().y - corner_y * 2); - - real_t x = CLAMP(mev->get_position().x - corner_x, 0, real_size.x); - real_t y = CLAMP(mev->get_position().y - corner_y, 0, real_size.y); - - s = x / real_size.x; - v = 1.0 - y / real_size.y; - } - } - - _copy_hsv_to_color(); - last_color = color; - set_pick_color(color); - - if (!deferred_mode_enabled) { - emit_signal(SNAME("color_changed"), color); - } - } - - if (!cursor_editing) { - return; - } - - Ref joypadmotion_event = p_event; - Ref joypadbutton_event = p_event; - bool is_joypad_event = (joypadmotion_event.is_valid() || joypadbutton_event.is_valid()); - bool is_echo = p_event->is_echo(); - - if (p_event->is_action_pressed("ui_left", true) || - p_event->is_action_pressed("ui_right", true) || - p_event->is_action_pressed("ui_up", true) || - p_event->is_action_pressed("ui_down", true)) { - if (is_joypad_event) { - // Make sure moving joypad axis further in the same direction is not handled here, as NOTIFICATION_INTERNAL_PROCESS will handle it. - if (is_processing_internal()) { - accept_event(); - return; - } - set_process_internal(true); - } - - // Treat any input from joypad axis as -1, 0, or 1, as the value is added to Vector2i and would be lost. - Vector2 color_change_vector = Vector2( - p_event->is_action_pressed("ui_right", true) - p_event->is_action_pressed("ui_left", true), - p_event->is_action_pressed("ui_down", true) - p_event->is_action_pressed("ui_up", true)); - _update_uv_cursor(color_change_vector, is_echo); - } -} - -void ColorPicker::_update_w_cursor(float p_color_change, bool p_is_echo) { - echo_multiplier = p_is_echo ? CLAMP(echo_multiplier * echo_multiplier_step, 1, 25) : 1; - - if (!Math::is_zero_approx(p_color_change)) { - p_color_change *= echo_multiplier; - if (current_shape == SHAPE_HSV_RECTANGLE) { - h = CLAMP(h + p_color_change / 360.0, 0, 1); - } else if (current_shape == SHAPE_VHS_CIRCLE || current_shape == SHAPE_OKHSL_CIRCLE) { - v = CLAMP(v - p_color_change / 100.0, 0, 1); - ok_hsl_l = CLAMP(ok_hsl_l - p_color_change / 100.0, 0, 1); - } - - _copy_hsv_to_color(); - last_color = color; - set_pick_color(color); - - emit_signal(SNAME("color_changed"), color); - } else { - echo_multiplier = 1; - } - - accept_event(); -} - -void ColorPicker::_w_input(const Ref &p_event) { - _update_cursor_editing(p_event, w_edit); - - Ref bev = p_event; - - if (bev.is_valid()) { - if (bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { - changing_color = true; - float y = CLAMP((float)bev->get_position().y, 0, w_edit->get_size().height); - if (current_shape == SHAPE_VHS_CIRCLE || current_shape == SHAPE_OKHSL_CIRCLE) { - v = 1.0 - (y / w_edit->get_size().height); - ok_hsl_l = v; - } else { - h = y / w_edit->get_size().height; - } - } else { - changing_color = false; - } - - _copy_hsv_to_color(); - last_color = color; - set_pick_color(color); - - if (!bev->is_pressed() && bev->get_button_index() == MouseButton::LEFT) { - add_recent_preset(color); - emit_signal(SNAME("color_changed"), color); - } else if (!deferred_mode_enabled) { - emit_signal(SNAME("color_changed"), color); - } - } - - Ref mev = p_event; - - if (mev.is_valid()) { - if (!changing_color) { - return; - } - float y = CLAMP((float)mev->get_position().y, 0, w_edit->get_size().height); - if (current_shape == SHAPE_VHS_CIRCLE || current_shape == SHAPE_OKHSL_CIRCLE) { - v = 1.0 - (y / w_edit->get_size().height); - ok_hsl_l = v; - } else { - h = y / w_edit->get_size().height; - } - - _copy_hsv_to_color(); - last_color = color; - set_pick_color(color); - - if (!deferred_mode_enabled) { - emit_signal(SNAME("color_changed"), color); - } - } - - if (!cursor_editing) { - return; - } - - Ref joypadmotion_event = p_event; - Ref joypadbutton_event = p_event; - bool is_joypad_event = (joypadmotion_event.is_valid() || joypadbutton_event.is_valid()); - bool is_echo = p_event->is_echo(); - - if (p_event->is_action_pressed("ui_left", true) || - p_event->is_action_pressed("ui_right", true) || - p_event->is_action_pressed("ui_up", true) || - p_event->is_action_pressed("ui_down", true)) { - if (is_joypad_event) { - set_process_internal(true); - } - - float color_change = Input::get_singleton()->get_axis("ui_up", "ui_down"); - _update_w_cursor(color_change, is_echo); - } -} - void ColorPicker::_slider_or_spin_input(const Ref &p_event) { if (line_edit_mouse_release) { line_edit_mouse_release = false; @@ -2616,20 +1963,9 @@ ColorPicker::ColorPicker() { VBoxContainer *real_vbox = memnew(VBoxContainer); internal_margin->add_child(real_vbox); - HBoxContainer *hb_edit = memnew(HBoxContainer); - real_vbox->add_child(hb_edit); - hb_edit->set_v_size_flags(SIZE_SHRINK_BEGIN); - - uv_edit = memnew(Control); - hb_edit->add_child(uv_edit); - uv_edit->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_uv_input).bind(uv_edit)); - uv_edit->set_mouse_filter(MOUSE_FILTER_PASS); - uv_edit->set_focus_mode(FOCUS_ALL); - uv_edit->set_h_size_flags(SIZE_EXPAND_FILL); - uv_edit->set_v_size_flags(SIZE_EXPAND_FILL); - uv_edit->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_hsv_draw).bind(0, uv_edit)); - uv_edit->connect(SceneStringName(focus_entered), callable_mp(this, &ColorPicker::_picker_shape_focus_entered)); - uv_edit->connect(SceneStringName(focus_exited), callable_mp(this, &ColorPicker::_picker_shape_focus_exited)); + shape_container = memnew(HBoxContainer); + shape_container->set_v_size_flags(SIZE_SHRINK_BEGIN); + real_vbox->add_child(shape_container); sample_hbc = memnew(HBoxContainer); real_vbox->add_child(sample_hbc); @@ -2652,22 +1988,28 @@ ColorPicker::ColorPicker() { btn_shape->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); btn_shape->set_focus_mode(FOCUS_ALL); - current_shape = SHAPE_HSV_RECTANGLE; + add_shape(memnew(ColorPickerShapeRectangle(this))); + add_shape(memnew(ColorPickerShapeWheel(this))); + add_shape(memnew(ColorPickerShapeVHSCircle(this))); + add_shape(memnew(ColorPickerShapeOKHSLCircle(this))); shape_popup = btn_shape->get_popup(); - shape_popup->add_radio_check_item("HSV Rectangle", SHAPE_HSV_RECTANGLE); - shape_popup->add_radio_check_item("HSV Wheel", SHAPE_HSV_WHEEL); - shape_popup->add_radio_check_item("VHS Circle", SHAPE_VHS_CIRCLE); - shape_popup->add_radio_check_item("OKHSL Circle", SHAPE_OKHSL_CIRCLE); + { + int i = 0; + for (const ColorPickerShape *shape : shapes) { + shape_popup->add_radio_check_item(shape->get_name(), i); + i++; + } + } shape_popup->set_item_checked(current_shape, true); shape_popup->connect(SceneStringName(id_pressed), callable_mp(this, &ColorPicker::set_picker_shape)); shape_popup->connect("about_to_popup", callable_mp(this, &ColorPicker::_block_input_on_popup_show)); shape_popup->connect(SNAME("popup_hide"), callable_mp(this, &ColorPicker::_enable_input_on_popup_hide)); - add_mode(new ColorModeRGB(this)); - add_mode(new ColorModeHSV(this)); - add_mode(new ColorModeRAW(this)); - add_mode(new ColorModeOKHSL(this)); + add_mode(memnew(ColorModeRGB(this))); + add_mode(memnew(ColorModeHSV(this))); + add_mode(memnew(ColorModeRAW(this))); + add_mode(memnew(ColorModeOKHSL(this))); mode_hbc = memnew(HBoxContainer); real_vbox->add_child(mode_hbc); @@ -2694,11 +2036,13 @@ ColorPicker::ColorPicker() { btn_mode->set_tooltip_text(ETR("Select a picker mode.")); btn_mode->set_focus_mode(FOCUS_ALL); - 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); + { + int i = 0; + for (const ColorMode *mode : modes) { + mode_popup->add_radio_check_item(mode->get_name(), i); + i++; + } } mode_popup->add_separator(); mode_popup->add_check_item(ETR("Colorized Sliders"), MODE_MAX); @@ -2748,47 +2092,6 @@ ColorPicker::ColorPicker() { c_text->connect(SceneStringName(text_changed), callable_mp(this, &ColorPicker::_text_changed)); c_text->connect(SceneStringName(focus_exited), callable_mp(this, &ColorPicker::_html_focus_exit)); - wheel_edit = memnew(AspectRatioContainer); - wheel_edit->set_h_size_flags(SIZE_EXPAND_FILL); - wheel_edit->set_v_size_flags(SIZE_EXPAND_FILL); - hb_edit->add_child(wheel_edit); - - wheel_mat.instantiate(); - wheel_mat->set_shader(wheel_shader); - wheel_mat->set_shader_parameter("wheel_radius", WHEEL_RADIUS); - circle_mat.instantiate(); - circle_mat->set_shader(circle_shader); - - wheel_margin = memnew(MarginContainer); - wheel_margin->add_theme_constant_override("margin_bottom", 8); - wheel_edit->add_child(wheel_margin); - - wheel = memnew(Control); - wheel_margin->add_child(wheel); - wheel->set_mouse_filter(MOUSE_FILTER_PASS); - wheel->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_hsv_draw).bind(2, wheel)); - wheel->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_uv_input).bind(wheel)); - wheel->connect(SceneStringName(focus_entered), callable_mp(this, &ColorPicker::_picker_shape_focus_entered)); - wheel->connect(SceneStringName(focus_exited), callable_mp(this, &ColorPicker::_picker_shape_focus_exited)); - - wheel_uv = memnew(Control); - wheel_margin->add_child(wheel_uv); - wheel_uv->set_focus_mode(FOCUS_ALL); - wheel_uv->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_uv_input).bind(wheel_uv)); - wheel_uv->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_hsv_draw).bind(0, wheel_uv)); - wheel_uv->connect(SceneStringName(focus_entered), callable_mp(this, &ColorPicker::_picker_shape_focus_entered)); - wheel_uv->connect(SceneStringName(focus_exited), callable_mp(this, &ColorPicker::_picker_shape_focus_exited)); - - w_edit = memnew(Control); - hb_edit->add_child(w_edit); - w_edit->set_focus_mode(FOCUS_ALL); - w_edit->set_h_size_flags(SIZE_FILL); - w_edit->set_v_size_flags(SIZE_EXPAND_FILL); - w_edit->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_w_input)); - w_edit->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_hsv_draw).bind(1, w_edit)); - w_edit->connect(SceneStringName(focus_entered), callable_mp(this, &ColorPicker::_picker_shape_focus_entered)); - w_edit->connect(SceneStringName(focus_exited), callable_mp(this, &ColorPicker::_picker_shape_focus_exited)); - _update_controls(); updating = false; @@ -2861,8 +2164,11 @@ ColorPicker::ColorPicker() { } ColorPicker::~ColorPicker() { - for (int i = 0; i < modes.size(); i++) { - delete modes[i]; + for (ColorMode *mode : modes) { + memdelete(mode); + } + for (ColorPickerShape *shape : shapes) { + memdelete(shape); } } diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 1c5fe3ffe8d..75eeb2b5ec4 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -79,6 +79,13 @@ class ColorPicker : public VBoxContainer { GDCLASS(ColorPicker, VBoxContainer); // These classes poke into theme items for their internal logic. + friend class ColorPickerShape; + friend class ColorPickerShapeRectangle; + friend class ColorPickerShapeWheel; + friend class ColorPickerShapeCircle; + friend class ColorPickerShapeVHSCircle; + friend class ColorPickerShapeOKHSLCircle; + friend class ColorModeRGB; friend class ColorModeHSV; friend class ColorModeRAW; @@ -127,21 +134,17 @@ private: int current_slider_count = SLIDER_COUNT; Vector2i circle_keyboard_joypad_picker_cursor_position; - float echo_multiplier = 1; - float echo_multiplier_step = 1.1; - bool rotate_next_echo_event = false; const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 1.0 / 2; const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 30; float gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS; - bool cursor_editing = false; - int wheel_focus_mode = 0; - static const int MODE_BUTTON_COUNT = 3; - const float WHEEL_RADIUS = 0.42; + + static constexpr int MODE_BUTTON_COUNT = 3; bool slider_theme_modified = true; - Vector modes; + LocalVector modes; + LocalVector shapes; Popup *picker_window = nullptr; TextureRect *picker_texture_zoom = nullptr; @@ -158,14 +161,7 @@ private: PopupMenu *options_menu = nullptr; MarginContainer *internal_margin = nullptr; - Control *uv_edit = nullptr; - Control *w_edit = nullptr; - AspectRatioContainer *wheel_edit = nullptr; - MarginContainer *wheel_margin = nullptr; - Ref wheel_mat; - Ref circle_mat; - Control *wheel = nullptr; - Control *wheel_uv = nullptr; + HBoxContainer *shape_container = nullptr; TextureRect *sample = nullptr; VBoxContainer *swatches_vbc = nullptr; GridContainer *preset_container = nullptr; @@ -189,6 +185,7 @@ private: ColorPresetButton *selected_recent_preset = nullptr; Ref preset_group; Ref recent_preset_group; + #ifdef TOOLS_ENABLED Callable quick_open_callback; Callable palette_saved_callback; @@ -304,18 +301,8 @@ private: void _text_type_toggled(); void _sample_input(const Ref &p_event); void _sample_draw(); - void _draw_focus_stylebox(Control *p_c, Rect2 p_focus_rect, Ref &p_focus_stylebox); - void _hsv_draw(int p_which, Control *c); void _slider_draw(int p_which); - int _get_edge_h_change(const Vector2 &p_color_change_vector); - float _get_h_on_circle_edge(const Vector2 &p_color_change_vector); - float _get_h_on_wheel(const Vector2 &p_color_change_vector); - void _update_uv_cursor(Vector2 &p_color_change_vector, bool p_is_echo); - void _update_cursor_editing(const Ref &p_event, Control *p_c); - void _uv_input(const Ref &p_event, Control *c); - void _update_w_cursor(float p_color_change, bool p_is_echo); - void _w_input(const Ref &p_event); void _slider_or_spin_input(const Ref &p_event); void _line_edit_input(const Ref &p_event); void _preset_input(const Ref &p_event, const Color &p_color); @@ -369,6 +356,7 @@ public: static void finish_shaders(); void add_mode(ColorMode *p_mode); + void add_shape(ColorPickerShape *p_shape); void set_edit_alpha(bool p_show); bool is_editing_alpha() const; @@ -429,8 +417,6 @@ public: void set_focus_on_line_edit(); void set_focus_on_picker_shape(); - void _picker_shape_focus_entered(); - void _picker_shape_focus_exited(); ColorPicker(); ~ColorPicker(); }; diff --git a/scene/gui/color_picker_shape.cpp b/scene/gui/color_picker_shape.cpp new file mode 100644 index 00000000000..96bc81cef39 --- /dev/null +++ b/scene/gui/color_picker_shape.cpp @@ -0,0 +1,771 @@ +/**************************************************************************/ +/* color_picker_shape.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "color_picker_shape.h" + +#include "scene/gui/margin_container.h" + +void ColorPickerShape::_emit_color_changed() { + color_picker->emit_signal(SNAME("color_changed"), color_picker->color); +} + +bool ColorPickerShape::can_handle(const Ref &p_event, Vector2 &r_position, bool *r_is_click) { + Ref mb = p_event; + if (mb.is_valid()) { + if (mb->get_button_index() != MouseButton::LEFT) { + return false; + } + + if (r_is_click) { + *r_is_click = true; + } + + if (mb->is_pressed()) { + is_dragging = true; + r_position = mb->get_position(); + return true; + } else { + _emit_color_changed(); + color_picker->add_recent_preset(color_picker->color); + is_dragging = false; + return false; + } + } + + Ref mm = p_event; + if (is_dragging && mm.is_valid()) { + r_position = mm->get_position(); + return true; + } + return false; +} + +void ColorPickerShape::apply_color() { + color_picker->_copy_hsv_to_color(); + color_picker->last_color = color_picker->color; + color_picker->set_pick_color(color_picker->color); + + if (!color_picker->deferred_mode_enabled) { + _emit_color_changed(); + } +} + +void ColorPickerShape::cancel_event() { + is_dragging = false; +} + +void ColorPickerShape::draw_focus_rect(Control *p_control, const Rect2 &p_rect) { + if (!p_control->has_focus()) { + return; + } + + Rect2 focus_rect; + if (p_rect.has_area()) { + focus_rect = p_rect; + } else { + focus_rect = Rect2(Vector2(), p_control->get_size()); + } + + const RID ci = p_control->get_canvas_item(); + if (!cursor_editing) { + RenderingServer::get_singleton()->canvas_item_add_rect(ci, focus_rect, color_picker->theme_cache.focused_not_editing_cursor_color); + } + color_picker->theme_cache.picker_focus_rectangle->draw(ci, focus_rect); +} + +void ColorPickerShape::draw_focus_circle(Control *p_control) { + if (!p_control->has_focus()) { + return; + } + + const Rect2 focus_rect(Vector2(), p_control->get_size()); + const RID ci = p_control->get_canvas_item(); + if (!cursor_editing) { + RenderingServer::get_singleton()->canvas_item_add_circle(ci, focus_rect.get_center(), focus_rect.get_size().y * 0.5, color_picker->theme_cache.focused_not_editing_cursor_color); + } + color_picker->theme_cache.picker_focus_circle->draw(ci, focus_rect); +} + +void ColorPickerShape::draw_sv_square(Control *p_control, const Rect2 &p_square, bool p_draw_focus) { + const Vector2 end = p_square.get_end(); + PackedVector2Array points = { + p_square.position, + Vector2(end.x, p_square.position.y), + end, + Vector2(p_square.position.x, end.y), + }; + + Color color1 = color_picker->color; + color1.set_hsv(color_picker->h, 1, 1); + Color color2 = color1; + color2.set_hsv(color_picker->h, 1, 0); + + PackedColorArray colors = { + Color(1, 1, 1, 1), + Color(1, 1, 1, 1), + Color(0, 0, 0, 1), + Color(0, 0, 0, 1) + }; + p_control->draw_polygon(points, colors); + + colors = { + Color(color1, 0), + Color(color1, 1), + Color(color2, 1), + Color(color2, 0) + }; + p_control->draw_polygon(points, colors); + + Vector2 cursor_pos; + cursor_pos.x = CLAMP(p_square.position.x + p_square.size.x * color_picker->s, p_square.position.x, end.x); + cursor_pos.y = CLAMP(p_square.position.y + p_square.size.y * (1.0 - color_picker->v), p_square.position.y, end.y); + + if (p_draw_focus) { + draw_focus_rect(p_control, p_square); + } + draw_cursor(p_control, cursor_pos); +} + +void ColorPickerShape::draw_cursor(Control *p_control, const Vector2 &p_center, bool p_draw_bg) { + const Vector2 position = p_center - color_picker->theme_cache.picker_cursor->get_size() * 0.5; + if (p_draw_bg) { + p_control->draw_texture(color_picker->theme_cache.picker_cursor_bg, position, Color(color_picker->color, 1.0)); + } + p_control->draw_texture(color_picker->theme_cache.picker_cursor, position); +} + +void ColorPickerShape::draw_circle_cursor(Control *p_control, float p_hue) { + const Vector2 center = p_control->get_size() * 0.5; + const Vector2 cursor_pos( + center.x + (center.x * Math::cos(p_hue * Math_TAU) * color_picker->s), + center.y + (center.y * Math::sin(p_hue * Math_TAU) * color_picker->s)); + + draw_cursor(p_control, cursor_pos); +} + +void ColorPickerShape::connect_shape_focus(Control *p_shape) { + p_shape->set_focus_mode(Control::FOCUS_ALL); + p_shape->connect(SceneStringName(focus_entered), callable_mp(this, &ColorPickerShape::shape_focus_entered)); + p_shape->connect(SceneStringName(focus_exited), callable_mp(this, &ColorPickerShape::shape_focus_exited)); +} + +void ColorPickerShape::shape_focus_entered() { + Input *input = Input::get_singleton(); + if (!(input->is_action_pressed("ui_up") || input->is_action_pressed("ui_down") || input->is_action_pressed("ui_left") || input->is_action_pressed("ui_right"))) { + cursor_editing = true; + } +} + +void ColorPickerShape::shape_focus_exited() { + cursor_editing = false; +} + +void ColorPickerShape::handle_cursor_editing(const Ref &p_event, Control *p_control) { + if (p_event->is_action_pressed("ui_accept", false, true)) { + cursor_editing = !cursor_editing; + p_control->queue_redraw(); + color_picker->accept_event(); + } + + if (cursor_editing && p_event->is_action_pressed("ui_cancel", false, true)) { + cursor_editing = false; + p_control->queue_redraw(); + color_picker->accept_event(); + } + + if (!cursor_editing) { + return; + } + + Input *input = Input::get_singleton(); + bool is_joypad_event = Object::cast_to(p_event.ptr()) || Object::cast_to(p_event.ptr()); + + if (p_event->is_action_pressed("ui_left", true) || p_event->is_action_pressed("ui_right", true) || p_event->is_action_pressed("ui_up", true) || p_event->is_action_pressed("ui_down", true)) { + if (is_joypad_event) { + if (color_picker->is_processing_internal()) { + color_picker->accept_event(); + return; + } + color_picker->set_process_internal(true); + } + + Vector2 color_change_vector = Vector2( + input->is_action_pressed("ui_right") - input->is_action_pressed("ui_left"), + input->is_action_pressed("ui_down") - input->is_action_pressed("ui_up")); + update_cursor(color_change_vector, p_event->is_echo()); + color_picker->accept_event(); + } +} + +int ColorPickerShape::get_edge_h_change(const Vector2 &p_color_change_vector) { + int h_change = 0; + + if (color_picker->h > 0 && color_picker->h < 0.5) { + h_change -= p_color_change_vector.x; + } else if (color_picker->h > 0.5 && color_picker->h < 1) { + h_change += p_color_change_vector.x; + } + + if (color_picker->h > 0.25 && color_picker->h < 0.75) { + h_change -= p_color_change_vector.y; + } else if (color_picker->h < 0.25 || color_picker->h > 0.75) { + h_change += p_color_change_vector.y; + } + return h_change; +} + +float ColorPickerShape::get_h_on_circle_edge(const Vector2 &p_color_change_vector) { + int h_change = get_edge_h_change(p_color_change_vector); + + float target_h = Math::wrapf(color_picker->h + h_change / 360.0, 0, 1); + int current_quarter = color_picker->h * 4; + int future_quarter = target_h * 4; + if (p_color_change_vector.y > 0 && ((future_quarter == 0 && current_quarter == 1) || (future_quarter == 1 && current_quarter == 0))) { + target_h = 0.25f; + } else if (p_color_change_vector.y < 0 && ((future_quarter == 2 && current_quarter == 3) || (future_quarter == 3 && current_quarter == 2))) { + target_h = 0.75f; + } else if (p_color_change_vector.x < 0 && ((future_quarter == 1 && current_quarter == 2) || (future_quarter == 2 && current_quarter == 1))) { + target_h = 0.5f; + } else if (p_color_change_vector.x > 0 && ((future_quarter == 3 && current_quarter == 0) || (future_quarter == 0 && current_quarter == 3))) { + target_h = 0; + } + return target_h; +} + +void ColorPickerShape::initialize_controls() { + _initialize_controls(); + update_theme(); + is_initialized = true; +} + +void ColorPickerShape::update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) { + if (p_color_change_vector.is_zero_approx()) { + echo_multiplier = 1.0; + } else { + echo_multiplier = p_is_echo ? CLAMP(echo_multiplier * 1.1, 1, 25) : 1; + _update_cursor(p_color_change_vector * echo_multiplier, p_is_echo); + apply_color(); + } +} + +ColorPickerShape::ColorPickerShape(ColorPicker *p_color_picker) { + color_picker = p_color_picker; +} + +void ColorPickerShapeRectangle::_sv_square_input(const Ref &p_event) { + handle_cursor_editing(p_event, sv_square); + + Vector2 event_position; + if (!can_handle(p_event, event_position)) { + return; + } + event_position = (event_position / sv_square->get_size()).clampf(0.0, 1.0); + + color_picker->s = event_position.x; + color_picker->v = 1.0 - event_position.y; + + apply_color(); +} + +void ColorPickerShapeRectangle::_hue_slider_input(const Ref &p_event) { + handle_cursor_editing(p_event, hue_slider); + + Vector2 event_position; + if (!can_handle(p_event, event_position)) { + return; + } + color_picker->h = CLAMP(event_position.y / hue_slider->get_size().y, 0.0, 1.0); + apply_color(); +} + +void ColorPickerShapeRectangle::_sv_square_draw() { + draw_sv_square(sv_square, Rect2(Vector2(), sv_square->get_size())); +} + +void ColorPickerShapeRectangle::_hue_slider_draw() { + const Vector2 size = hue_slider->get_size(); + hue_slider->draw_texture_rect(color_picker->theme_cache.color_hue, Rect2(0, 0, -size.y, size.x), false, Color(1, 1, 1), true); + + draw_focus_rect(hue_slider); + + int y = size.y * color_picker->h; + const Color color = Color::from_hsv(color_picker->h, 1, 1); + hue_slider->draw_line(Vector2(0, y), Vector2(size.x, y), color.inverted()); +} + +void ColorPickerShapeRectangle::_initialize_controls() { + sv_square = memnew(Control); + color_picker->shape_container->add_child(sv_square); + sv_square->connect(SceneStringName(gui_input), callable_mp(this, &ColorPickerShapeRectangle::_sv_square_input)); + sv_square->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeRectangle::_sv_square_draw)); + connect_shape_focus(sv_square); + + hue_slider = memnew(Control); + color_picker->shape_container->add_child(hue_slider); + hue_slider->connect(SceneStringName(gui_input), callable_mp(this, &ColorPickerShapeRectangle::_hue_slider_input)); + hue_slider->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeRectangle::_hue_slider_draw)); + connect_shape_focus(hue_slider); + + controls.append(sv_square); + controls.append(hue_slider); +} + +void ColorPickerShapeRectangle::_update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) { + if (sv_square->has_focus()) { + color_picker->s = CLAMP(color_picker->s + p_color_change_vector.x / 100.0, 0, 1); + color_picker->v = CLAMP(color_picker->v - p_color_change_vector.y / 100.0, 0, 1); + } else if (hue_slider->has_focus()) { + color_picker->h = CLAMP(color_picker->h + p_color_change_vector.y * echo_multiplier / 360.0, 0, 1); + } +} + +void ColorPickerShapeRectangle::update_theme() { + const ColorPicker::ThemeCache &theme_cache = color_picker->theme_cache; + sv_square->set_custom_minimum_size(Size2(theme_cache.sv_width, theme_cache.sv_height)); + hue_slider->set_custom_minimum_size(Size2(theme_cache.h_width, 0)); +} + +void ColorPickerShapeRectangle::grab_focus() { + hue_slider->grab_focus(); +} + +float ColorPickerShapeWheel::_get_h_on_wheel(const Vector2 &p_color_change_vector) { + int h_change = get_edge_h_change(p_color_change_vector); + + float target_h = Math::wrapf(color_picker->h + h_change / 360.0, 0, 1); + int current_quarter = color_picker->h * 4; + int future_quarter = target_h * 4; + + if (p_color_change_vector.y > 0 && ((future_quarter == 0 && current_quarter == 1) || (future_quarter == 1 && current_quarter == 0))) { + rotate_next_echo_event = !rotate_next_echo_event; + } else if (p_color_change_vector.y < 0 && ((future_quarter == 2 && current_quarter == 3) || (future_quarter == 3 && current_quarter == 2))) { + rotate_next_echo_event = !rotate_next_echo_event; + } else if (p_color_change_vector.x < 0 && ((future_quarter == 1 && current_quarter == 2) || (future_quarter == 2 && current_quarter == 1))) { + rotate_next_echo_event = !rotate_next_echo_event; + } else if (p_color_change_vector.x > 0 && ((future_quarter == 3 && current_quarter == 0) || (future_quarter == 0 && current_quarter == 3))) { + rotate_next_echo_event = !rotate_next_echo_event; + } + + return target_h; +} + +void ColorPickerShapeWheel::_reset_wheel_focus() { + wheel_focused = true; +} + +void ColorPickerShapeWheel::_wheel_input(const Ref &p_event) { + handle_cursor_editing(p_event, wheel_uv); + if (!cursor_editing) { + // Wheel and inner square are the same control, so focus has to be moved manually. + if (!wheel_focused && p_event->is_action_pressed("ui_down", true)) { + wheel_focused = true; + wheel_uv->queue_redraw(); + color_picker->accept_event(); + return; + } else if (wheel_focused && p_event->is_action_pressed("ui_up", true)) { + wheel_focused = false; + wheel_uv->queue_redraw(); + color_picker->accept_event(); + return; + } + } + + Vector2 event_position; + bool is_click = false; + if (!can_handle(p_event, event_position, &is_click)) { + if (is_click) { + // Released mouse button while dragging wheel. + spinning = false; + } + return; + } + const Vector2 uv_size = wheel_uv->get_size(); + const Vector2 ring_radius = uv_size * Math_SQRT12 * WHEEL_RADIUS; + const Vector2 center = uv_size * 0.5; + + if (is_click && !spinning) { + real_t dist = center.distance_to(event_position); + if (dist >= center.x * WHEEL_RADIUS * 2.0 && dist <= center.x) { + spinning = true; + if (!wheel_focused) { + cursor_editing = true; + wheel_focused = true; + } + } else if (dist > center.x) { + // Clicked outside the wheel. + cancel_event(); + return; + } + }; + + if (spinning) { + real_t rad = center.angle_to_point(event_position); + color_picker->h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; + apply_color(); + return; + } + + const Rect2 uv_rect(center - ring_radius, ring_radius * 2.0); + event_position -= uv_rect.position; + event_position /= uv_rect.size; + + if (is_click && (event_position.x < 0 || event_position.x > 1 || event_position.y < 0 || event_position.y > 1)) { + // Clicked inside the wheel, but outside the square. + cancel_event(); + return; + } + + event_position = event_position.clampf(0.0, 1.0); + + color_picker->s = event_position.x; + color_picker->v = 1.0 - event_position.y; + if (wheel_focused) { + cursor_editing = true; + wheel_focused = false; + } + + apply_color(); +} + +void ColorPickerShapeWheel::_wheel_draw() { + wheel->draw_rect(Rect2(Point2(), wheel->get_size()), Color(1, 1, 1)); +} + +void ColorPickerShapeWheel::_wheel_uv_draw() { + const Vector2 uv_size = wheel_uv->get_size(); + const Vector2 ring_radius = uv_size * Math_SQRT12 * WHEEL_RADIUS; + const Vector2 center = uv_size * 0.5; + + const Rect2 uv_rect(center - ring_radius, ring_radius * 2.0); + draw_sv_square(wheel_uv, uv_rect, !wheel_focused); + if (wheel_focused) { + draw_focus_circle(wheel_uv); + } + + float radius = WHEEL_RADIUS * 2.0; + radius += (1.0 - radius) * 0.5; + const Vector2 cursor_pos = center + + Vector2(center.x * Math::cos(color_picker->h * Math_TAU) * radius, + center.y * Math::sin(color_picker->h * Math_TAU) * radius); + draw_cursor(wheel_uv, cursor_pos, false); +} + +void ColorPickerShapeWheel::_initialize_controls() { + wheel_margin = memnew(MarginContainer); + color_picker->shape_container->add_child(wheel_margin); + + Ref material; + material.instantiate(); + material->set_shader(ColorPicker::wheel_shader); + material->set_shader_parameter("wheel_radius", WHEEL_RADIUS); + + wheel = memnew(Control); + wheel->set_material(material); + wheel_margin->add_child(wheel); + wheel->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeWheel::_wheel_draw)); + + wheel_uv = memnew(Control); + wheel_margin->add_child(wheel_uv); + wheel_uv->connect(SceneStringName(focus_entered), callable_mp(this, &ColorPickerShapeWheel::_reset_wheel_focus)); + wheel_uv->connect(SceneStringName(gui_input), callable_mp(this, &ColorPickerShapeWheel::_wheel_input)); + wheel_uv->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeWheel::_wheel_uv_draw)); + connect_shape_focus(wheel_uv); + + controls.append(wheel_margin); + controls.append(wheel); + controls.append(wheel_uv); +} + +void ColorPickerShapeWheel::_update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) { + if (wheel_focused) { + if (p_is_echo && rotate_next_echo_event) { + color_picker->h = _get_h_on_wheel(-p_color_change_vector); + } else { + rotate_next_echo_event = false; + color_picker->h = _get_h_on_wheel(p_color_change_vector); + } + } else { + color_picker->s = CLAMP(color_picker->s + p_color_change_vector.x / 100.0, 0, 1); + color_picker->v = CLAMP(color_picker->v - p_color_change_vector.y / 100.0, 0, 1); + } +} + +void ColorPickerShapeWheel::update_theme() { + const ColorPicker::ThemeCache &theme_cache = color_picker->theme_cache; + wheel_margin->set_custom_minimum_size(Size2(theme_cache.sv_width, theme_cache.sv_height)); + wheel_margin->add_theme_constant_override(SNAME("margin_bottom"), 8 * theme_cache.base_scale); +} + +void ColorPickerShapeWheel::grab_focus() { + wheel_uv->grab_focus(); +} + +void ColorPickerShapeCircle::update_circle_cursor(const Vector2 &p_color_change_vector, const Vector2 &p_center, const Vector2 &p_hue_offset) { + if (circle_keyboard_joypad_picker_cursor_position == Vector2i()) { + circle_keyboard_joypad_picker_cursor_position = p_center + p_hue_offset; + } + + Vector2i potential_cursor_position = circle_keyboard_joypad_picker_cursor_position + p_color_change_vector; + real_t potential_new_cursor_distance = p_center.distance_to(potential_cursor_position); + real_t dist_pre = p_center.distance_to(circle_keyboard_joypad_picker_cursor_position); + if (color_picker->s < 1 || potential_new_cursor_distance < dist_pre) { + circle_keyboard_joypad_picker_cursor_position += p_color_change_vector; + real_t dist = p_center.distance_to(circle_keyboard_joypad_picker_cursor_position); + real_t rad = p_center.angle_to_point(circle_keyboard_joypad_picker_cursor_position); + color_picker->h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; + color_picker->s = CLAMP(dist / p_center.x, 0, 1); + } else { + color_picker->h = get_h_on_circle_edge(p_color_change_vector); + circle_keyboard_joypad_picker_cursor_position = Vector2i(); + } +} + +void ColorPickerShapeCircle::_initialize_controls() { + circle_margin = memnew(MarginContainer); + color_picker->shape_container->add_child(circle_margin); + + Ref material; + material.instantiate(); + material->set_shader(_get_shader()); + + circle = memnew(Control); + circle->set_material(material); + circle_margin->add_child(circle); + circle->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeCircle::_circle_draw)); + + circle_overlay = memnew(Control); + circle_overlay->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + circle->add_child(circle_overlay); + circle_overlay->connect(SceneStringName(gui_input), callable_mp(this, &ColorPickerShapeCircle::_circle_input)); + circle_overlay->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeCircle::_circle_overlay_draw)); + connect_shape_focus(circle_overlay); + + value_slider = memnew(Control); + color_picker->shape_container->add_child(value_slider); + value_slider->connect(SceneStringName(gui_input), callable_mp(this, &ColorPickerShapeCircle::_value_slider_input)); + value_slider->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeCircle::_value_slider_draw)); + connect_shape_focus(value_slider); + + controls.append(circle_margin); + controls.append(circle); + controls.append(circle_overlay); + controls.append(value_slider); +} + +void ColorPickerShapeCircle::update_theme() { + const ColorPicker::ThemeCache &theme_cache = color_picker->theme_cache; + circle_margin->set_custom_minimum_size(Size2(theme_cache.sv_width, theme_cache.sv_height)); + circle_margin->add_theme_constant_override(SNAME("margin_bottom"), 8 * theme_cache.base_scale); + value_slider->set_custom_minimum_size(Size2(theme_cache.h_width, 0)); +} + +void ColorPickerShapeCircle::grab_focus() { + circle_overlay->grab_focus(); +} + +void ColorPickerShapeVHSCircle::_circle_input(const Ref &p_event) { + handle_cursor_editing(p_event, circle_overlay); + + Vector2 event_position; + bool is_click = false; + if (!can_handle(p_event, event_position, &is_click)) { + return; + } + + Vector2 center = circle->get_size() * 0.5; + real_t dist = center.distance_to(event_position); + if (is_click && dist > center.x) { + // Clicked outside the circle. + cancel_event(); + return; + } + + real_t rad = center.angle_to_point(event_position); + color_picker->h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; + color_picker->s = CLAMP(dist / center.x, 0, 1); + color_picker->ok_hsl_h = color_picker->h; + color_picker->ok_hsl_s = color_picker->s; + circle_keyboard_joypad_picker_cursor_position = Vector2i(); + + apply_color(); +} + +void ColorPickerShapeVHSCircle::_value_slider_input(const Ref &p_event) { + handle_cursor_editing(p_event, value_slider); + + Vector2 event_position; + if (!can_handle(p_event, event_position)) { + return; + } + color_picker->v = 1.0 - CLAMP(event_position.y / value_slider->get_size().y, 0.0, 1.0); + apply_color(); +} + +void ColorPickerShapeVHSCircle::_circle_draw() { + Ref material = circle->get_material(); + material->set_shader_parameter(SNAME("v"), color_picker->v); + circle->draw_rect(Rect2(Point2(), circle->get_size()), Color(1, 1, 1)); +} + +void ColorPickerShapeVHSCircle::_circle_overlay_draw() { + draw_focus_circle(circle_overlay); + draw_circle_cursor(circle_overlay, color_picker->h); +} + +void ColorPickerShapeVHSCircle::_value_slider_draw() { + const Vector2 size = value_slider->get_size(); + PackedVector2Array points{ + Vector2(), + Vector2(size.x, 0), + size, + Vector2(0, size.y) + }; + + Color color = Color::from_hsv(color_picker->h, color_picker->s, 1); + PackedColorArray colors = { + color, + color, + Color(), + Color() + }; + + value_slider->draw_polygon(points, colors); + + draw_focus_rect(value_slider); + + int y = size.y * (1 - CLAMP(color_picker->v, 0, 1)); + color.set_hsv(color_picker->h, 1, color_picker->v); + value_slider->draw_line(Vector2(0, y), Vector2(size.x, y), color.inverted()); +} + +void ColorPickerShapeVHSCircle::_update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) { + if (circle_overlay->has_focus()) { + const Vector2 center = circle_overlay->get_size() / 2.0; + const Vector2 hue_offset = center * Vector2(Math::cos(color_picker->h * Math_TAU), Math::sin(color_picker->h * Math_TAU)) * color_picker->s; + update_circle_cursor(p_color_change_vector, center, hue_offset); + } else if (value_slider->has_focus()) { + color_picker->v = CLAMP(color_picker->v - p_color_change_vector.y * echo_multiplier / 100.0, 0, 1); + } +} + +void ColorPickerShapeOKHSLCircle::_circle_input(const Ref &p_event) { + handle_cursor_editing(p_event, circle_overlay); + + Vector2 event_position; + bool is_click = false; + if (!can_handle(p_event, event_position, &is_click)) { + return; + } + + const Vector2 center = circle->get_size() * 0.5; + real_t dist = center.distance_to(event_position); + if (is_click && dist > center.x) { + // Clicked outside the circle. + cancel_event(); + return; + } + + real_t rad = center.angle_to_point(event_position); + color_picker->h = ((rad >= 0) ? rad : (Math_TAU + rad)) / Math_TAU; + color_picker->s = CLAMP(dist / center.x, 0, 1); + color_picker->ok_hsl_h = color_picker->h; + color_picker->ok_hsl_s = color_picker->s; + circle_keyboard_joypad_picker_cursor_position = Vector2i(); + + apply_color(); +} + +void ColorPickerShapeOKHSLCircle::_value_slider_input(const Ref &p_event) { + handle_cursor_editing(p_event, value_slider); + + Vector2 event_position; + if (!can_handle(p_event, event_position)) { + return; + } + color_picker->ok_hsl_l = 1.0 - CLAMP(event_position.y / value_slider->get_size().y, 0.0, 1.0); + apply_color(); +} + +void ColorPickerShapeOKHSLCircle::_circle_draw() { + Ref material = circle->get_material(); + material->set_shader_parameter(SNAME("ok_hsl_l"), color_picker->ok_hsl_l); + circle->draw_rect(Rect2(Point2(), circle->get_size()), Color(1, 1, 1)); +} + +void ColorPickerShapeOKHSLCircle::_circle_overlay_draw() { + draw_focus_circle(circle_overlay); + draw_circle_cursor(circle_overlay, color_picker->ok_hsl_h); +} + +void ColorPickerShapeOKHSLCircle::_value_slider_draw() { + const float ok_hsl_h = color_picker->ok_hsl_h; + const float ok_hsl_s = color_picker->ok_hsl_s; + const float ok_hsl_l = color_picker->ok_hsl_l; + + const Vector2 size = value_slider->get_size(); + PackedVector2Array points{ + Vector2(size.x, 0), + Vector2(size.x, size.y * 0.5), + size, + Vector2(0, size.y), + Vector2(0, size.y * 0.5), + Vector2() + }; + + Color color1 = Color::from_ok_hsl(ok_hsl_h, ok_hsl_s, 1); + Color color2 = Color::from_ok_hsl(ok_hsl_h, ok_hsl_s, 0.5); + Color color3 = Color::from_ok_hsl(ok_hsl_h, ok_hsl_s, 0); + PackedColorArray colors = { + color1, + color2, + color3, + color3, + color2, + color1, + }; + value_slider->draw_polygon(points, colors); + + draw_focus_rect(value_slider); + + int y = size.y * (1 - CLAMP(ok_hsl_l, 0, 1)); + value_slider->draw_line(Vector2(0, y), Vector2(size.x, y), Color::from_hsv(ok_hsl_h, 1, ok_hsl_l).inverted()); +} + +void ColorPickerShapeOKHSLCircle::_update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) { + if (circle_overlay->has_focus()) { + const Vector2 center = circle_overlay->get_size() / 2.0; + const Vector2 hue_offset = center * Vector2(Math::cos(color_picker->ok_hsl_h * Math_TAU), Math::sin(color_picker->ok_hsl_h * Math_TAU)) * color_picker->ok_hsl_s; + update_circle_cursor(p_color_change_vector, center, hue_offset); + color_picker->ok_hsl_h = color_picker->h; + color_picker->ok_hsl_s = color_picker->s; + } else if (value_slider->has_focus()) { + color_picker->ok_hsl_l = CLAMP(color_picker->ok_hsl_l - p_color_change_vector.y * echo_multiplier / 100.0, 0, 1); + } +} diff --git a/scene/gui/color_picker_shape.h b/scene/gui/color_picker_shape.h new file mode 100644 index 00000000000..bb87a289921 --- /dev/null +++ b/scene/gui/color_picker_shape.h @@ -0,0 +1,215 @@ +/**************************************************************************/ +/* color_picker_shape.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "scene/gui/color_picker.h" + +class ColorPickerShape : public Object { + GDCLASS(ColorPickerShape, Object); + + void _emit_color_changed(); + +protected: + ColorPicker *color_picker = nullptr; + bool is_dragging = false; + + virtual void _initialize_controls() = 0; + virtual void _update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) = 0; + + bool can_handle(const Ref &p_event, Vector2 &r_position, bool *r_is_click = nullptr); + void apply_color(); + void cancel_event(); + + void draw_focus_rect(Control *p_control, const Rect2 &p_rect = Rect2()); + void draw_focus_circle(Control *p_control); + void draw_sv_square(Control *p_control, const Rect2 &p_square, bool p_draw_focus = true); + void draw_cursor(Control *p_control, const Vector2 &p_center, bool p_draw_bg = true); + void draw_circle_cursor(Control *p_control, float p_hue); + + void connect_shape_focus(Control *p_shape); + void shape_focus_entered(); + void shape_focus_exited(); + + void handle_cursor_editing(const Ref &p_event, Control *p_control); + int get_edge_h_change(const Vector2 &p_color_change_vector); + float get_h_on_circle_edge(const Vector2 &p_color_change_vector); + +public: + Vector controls; + bool is_initialized = false; + bool cursor_editing = false; + float echo_multiplier = 1; + + virtual String get_name() const = 0; + virtual Ref get_icon() const = 0; + virtual bool is_ok_hsl() const { return false; } + + void initialize_controls(); + virtual void update_theme() = 0; + void update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo); + virtual void grab_focus() = 0; + + ColorPickerShape(ColorPicker *p_color_picker); +}; + +class ColorPickerShapeRectangle : public ColorPickerShape { + Control *sv_square = nullptr; + Control *hue_slider = nullptr; + + void _sv_square_input(const Ref &p_event); + void _hue_slider_input(const Ref &p_event); + + void _sv_square_draw(); + void _hue_slider_draw(); + +protected: + virtual void _initialize_controls() override; + virtual void _update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) override; + +public: + virtual String get_name() const override { return ETR("HSV Rectangle"); } + virtual Ref get_icon() const override { return color_picker->theme_cache.shape_rect; } + virtual void update_theme() override; + virtual void grab_focus() override; + + ColorPickerShapeRectangle(ColorPicker *p_color_picker) : + ColorPickerShape(p_color_picker) {} +}; + +class ColorPickerShapeWheel : public ColorPickerShape { + GDCLASS(ColorPickerShapeWheel, ColorPickerShape); + + inline static constexpr float WHEEL_RADIUS = 0.42; + + MarginContainer *wheel_margin = nullptr; + Control *wheel = nullptr; + Control *wheel_uv = nullptr; + + bool wheel_focused = true; + bool spinning = false; + bool rotate_next_echo_event = false; + + float _get_h_on_wheel(const Vector2 &p_color_change_vector); + void _reset_wheel_focus(); + + void _wheel_input(const Ref &p_event); + + void _wheel_draw(); + void _wheel_uv_draw(); + +protected: + virtual void _initialize_controls() override; + virtual void _update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) override; + +public: + virtual String get_name() const override { return ETR("HSV Wheel"); } + virtual Ref get_icon() const override { return color_picker->theme_cache.shape_rect_wheel; } + virtual void update_theme() override; + virtual void grab_focus() override; + + ColorPickerShapeWheel(ColorPicker *p_color_picker) : + ColorPickerShape(p_color_picker) {} +}; + +class ColorPickerShapeCircle : public ColorPickerShape { + GDCLASS(ColorPickerShapeCircle, ColorPickerShape); + +protected: + MarginContainer *circle_margin = nullptr; + Control *circle = nullptr; + Control *circle_overlay = nullptr; + Control *value_slider = nullptr; + + Vector2i circle_keyboard_joypad_picker_cursor_position; + + virtual void _circle_input(const Ref &p_event) = 0; + virtual void _value_slider_input(const Ref &p_event) = 0; + + virtual void _circle_draw() = 0; + virtual void _circle_overlay_draw() = 0; + virtual void _value_slider_draw() = 0; + + void update_circle_cursor(const Vector2 &p_color_change_vector, const Vector2 &p_center, const Vector2 &p_hue_offset); + +public: + virtual Ref _get_shader() const = 0; + virtual void _initialize_controls() override; + + virtual Ref get_icon() const override { return color_picker->theme_cache.shape_circle; } + virtual void update_theme() override; + virtual void grab_focus() override; + + ColorPickerShapeCircle(ColorPicker *p_color_picker) : + ColorPickerShape(p_color_picker) {} +}; + +class ColorPickerShapeVHSCircle : public ColorPickerShapeCircle { + GDCLASS(ColorPickerShapeVHSCircle, ColorPickerShapeCircle); + +protected: + virtual void _update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) override; + virtual Ref _get_shader() const override { return ColorPicker::circle_shader; } + + virtual void _circle_input(const Ref &p_event) override; + virtual void _value_slider_input(const Ref &p_event) override; + + virtual void _circle_draw() override; + virtual void _circle_overlay_draw() override; + virtual void _value_slider_draw() override; + +public: + virtual String get_name() const override { return ETR("VHS Circle"); } + + ColorPickerShapeVHSCircle(ColorPicker *p_color_picker) : + ColorPickerShapeCircle(p_color_picker) {} +}; + +class ColorPickerShapeOKHSLCircle : public ColorPickerShapeCircle { + GDCLASS(ColorPickerShapeOKHSLCircle, ColorPickerShapeCircle); + +protected: + virtual void _update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) override; + virtual Ref _get_shader() const override { return ColorPicker::circle_ok_color_shader; } + + virtual void _circle_input(const Ref &p_event) override; + virtual void _value_slider_input(const Ref &p_event) override; + + virtual void _circle_draw() override; + virtual void _circle_overlay_draw() override; + virtual void _value_slider_draw() override; + +public: + virtual String get_name() const override { return ETR("OKHSL Circle"); } + virtual bool is_ok_hsl() const override { return true; } + + ColorPickerShapeOKHSLCircle(ColorPicker *p_color_picker) : + ColorPickerShapeCircle(p_color_picker) {} +};