Browse Source

ColorPicker: Add okhsl HS and HL rectangular picker shapes

LuoZhihao 1 month ago
parent
commit
7b4c95e6d8

+ 6 - 0
doc/classes/ColorPicker.xml

@@ -144,6 +144,12 @@
 		<constant name="SHAPE_NONE" value="4" enum="PickerShapeType">
 			The color space shape and the shape select button are hidden. Can't be selected from the shapes popup.
 		</constant>
+		<constant name="SHAPE_OK_HS_RECTANGLE" value="5" enum="PickerShapeType">
+			OKHSL Color Model rectangle with constant lightness.
+		</constant>
+		<constant name="SHAPE_OK_HL_RECTANGLE" value="6" enum="PickerShapeType">
+			OKHSL Color Model rectangle with constant saturation.
+		</constant>
 	</constants>
 	<theme_items>
 		<theme_item name="focused_not_editing_cursor_color" data_type="color" type="Color" default="Color(1, 1, 1, 0.275)">

+ 1 - 1
editor/editor_settings.cpp

@@ -569,7 +569,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
 	_initial_set("interface/inspector/resources_to_open_in_new_inspector", open_in_new_inspector_defaults);
 
 	EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_ENUM, "interface/inspector/default_color_picker_mode", (int32_t)ColorPicker::MODE_RGB, "RGB,HSV,RAW,OKHSL")
-	EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_ENUM, "interface/inspector/default_color_picker_shape", (int32_t)ColorPicker::SHAPE_OKHSL_CIRCLE, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle,OKHSL Circle")
+	EDITOR_SETTING_BASIC(Variant::INT, PROPERTY_HINT_ENUM, "interface/inspector/default_color_picker_shape", (int32_t)ColorPicker::SHAPE_OKHSL_CIRCLE, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle,OKHSL Circle,OK HS Rectangle:5,OK HL Rectangle") // `SHAPE_NONE` is 4.
 	EDITOR_SETTING_BASIC(Variant::BOOL, PROPERTY_HINT_NONE, "interface/inspector/color_picker_show_intensity", true, "");
 
 	// Theme

+ 2 - 1
editor/plugins/script_text_editor.cpp

@@ -43,6 +43,7 @@
 #include "editor/gui/editor_toaster.h"
 #include "editor/plugins/editor_context_menu_plugin.h"
 #include "editor/themes/editor_scale.h"
+#include "scene/gui/grid_container.h"
 #include "scene/gui/menu_button.h"
 #include "scene/gui/rich_text_label.h"
 #include "scene/gui/slider.h"
@@ -2845,7 +2846,7 @@ ScriptTextEditor::ScriptTextEditor() {
 	inline_color_options->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
 	inline_color_options->set_fit_to_longest_item(false);
 	inline_color_options->connect("item_selected", callable_mp(this, &ScriptTextEditor::_update_color_text).unbind(1));
-	inline_color_picker->get_slider(ColorPicker::SLIDER_COUNT)->get_parent()->add_sibling(inline_color_options);
+	inline_color_picker->get_slider_container()->add_sibling(inline_color_options);
 
 	connection_info_dialog = memnew(ConnectionInfoDialog);
 

+ 60 - 25
scene/gui/color_picker.cpp

@@ -139,10 +139,10 @@ void ColorPicker::_notification(int p_what) {
 			}
 
 			if (current_shape != SHAPE_NONE) {
-				btn_shape->set_button_icon(shape_popup->get_item_icon(current_shape));
+				btn_shape->set_button_icon(shape_popup->get_item_icon(get_current_shape_index()));
 			}
 
-			for (int i = 0; i < SLIDER_COUNT; i++) {
+			for (int i = 0; i < MODE_SLIDER_COUNT; i++) {
 				labels[i]->set_custom_minimum_size(Size2(theme_cache.label_width, 0));
 				sliders[i]->add_theme_constant_override(SNAME("center_grabber"), theme_cache.center_slider_grabbers);
 			}
@@ -185,7 +185,7 @@ void ColorPicker::_notification(int p_what) {
 		case NOTIFICATION_FOCUS_ENTER:
 		case NOTIFICATION_FOCUS_EXIT: {
 			if (current_shape != SHAPE_NONE) {
-				shapes[current_shape]->cursor_editing = false;
+				shapes[get_current_shape_index()]->cursor_editing = false;
 			}
 		} break;
 
@@ -198,7 +198,7 @@ void ColorPicker::_notification(int p_what) {
 						input->is_action_just_released("ui_down")) {
 					gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS;
 					if (current_shape == SHAPE_NONE) {
-						shapes[current_shape]->echo_multiplier = 1;
+						shapes[get_current_shape_index()]->echo_multiplier = 1;
 					}
 					accept_event();
 					set_process_internal(false);
@@ -217,7 +217,7 @@ void ColorPicker::_notification(int p_what) {
 							input->is_action_pressed("ui_right") - input->is_action_pressed("ui_left"),
 							input->is_action_pressed("ui_down") - input->is_action_pressed("ui_up"));
 
-					shapes[current_shape]->update_cursor(color_change_vector, true);
+					shapes[get_current_shape_index()]->update_cursor(color_change_vector, true);
 					accept_event();
 				}
 				return;
@@ -309,7 +309,7 @@ void fragment() {
 
 	circle_ok_color_shader.instantiate();
 	circle_ok_color_shader->set_code(OK_COLOR_SHADER + R"(
-// ColorPicker ok color hsv circle shader.
+// ColorPicker ok color hsl circle shader.
 
 uniform float ok_hsl_l = 1.0;
 
@@ -330,12 +330,40 @@ void fragment() {
 	float b4 = float(sqrt(x * x + y * y) < 0.5);
 	COLOR = vec4(col, (b + b2 + b3 + b4) / 4.00);
 })");
+
+	rectangle_ok_color_hs_shader.instantiate();
+	rectangle_ok_color_hs_shader->set_code(OK_COLOR_SHADER + R"(
+// ColorPicker ok color hs rectangle shader.
+
+uniform float ok_hsl_l = 0.0;
+
+void fragment() {
+	float h = UV.x;
+	float s = 1.0 - UV.y;
+	vec3 col = okhsl_to_srgb(vec3(h, s, ok_hsl_l));
+	COLOR = vec4(col, 1.0);
+})");
+
+	rectangle_ok_color_hl_shader.instantiate();
+	rectangle_ok_color_hl_shader->set_code(OK_COLOR_SHADER + R"(
+// ColorPicker ok color hl rectangle shader.
+
+uniform float ok_hsl_s = 0.0;
+
+void fragment() {
+	float h = UV.x;
+	float l = 1.0 - UV.y;
+	vec3 col = okhsl_to_srgb(vec3(h, ok_hsl_s, l));
+	COLOR = vec4(col, 1.0);
+})");
 }
 
 void ColorPicker::finish_shaders() {
 	wheel_shader.unref();
 	circle_shader.unref();
 	circle_ok_color_shader.unref();
+	rectangle_ok_color_hs_shader.unref();
+	rectangle_ok_color_hl_shader.unref();
 }
 
 void ColorPicker::set_focus_on_line_edit() {
@@ -343,7 +371,7 @@ void ColorPicker::set_focus_on_line_edit() {
 }
 
 void ColorPicker::set_focus_on_picker_shape() {
-	shapes[current_shape]->grab_focus();
+	shapes[get_current_shape_index()]->grab_focus();
 }
 
 void ColorPicker::_update_controls() {
@@ -384,7 +412,7 @@ void ColorPicker::_update_controls() {
 
 	int i = 0;
 	for (ColorPickerShape *shape : shapes) {
-		bool is_active = current_shape == i;
+		bool is_active = get_current_shape_index() == i;
 		i++;
 
 		if (!shape->is_initialized) {
@@ -552,14 +580,14 @@ void ColorPicker::create_slider(GridContainer *gc, int idx) {
 	slider->connect("drag_started", callable_mp(this, &ColorPicker::_slider_drag_started));
 	slider->connect(SceneStringName(value_changed), callable_mp(this, &ColorPicker::_slider_value_changed).unbind(1));
 	slider->connect("drag_ended", callable_mp(this, &ColorPicker::_slider_drag_ended).unbind(1));
-	if (idx < SLIDER_COUNT) {
+	if (idx < MODE_SLIDER_COUNT) {
 		slider->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_slider_draw).bind(idx));
 	} else if (idx == SLIDER_ALPHA) {
 		slider->connect(SceneStringName(draw), callable_mp(this, &ColorPicker::_alpha_slider_draw));
 	}
 	slider->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_slider_or_spin_input));
 
-	if (idx < SLIDER_COUNT) {
+	if (idx < MODE_SLIDER_COUNT) {
 		sliders[idx] = slider;
 		values[idx] = val;
 		labels[idx] = lbl;
@@ -618,10 +646,8 @@ void ColorPicker::set_palette_saved_callback(const Callable &p_palette_saved) {
 #endif
 
 HSlider *ColorPicker::get_slider(int p_idx) {
-	if (p_idx < SLIDER_COUNT) {
-		return sliders[p_idx];
-	}
-	return alpha_slider;
+	ERR_FAIL_INDEX_V(p_idx, MODE_MAX, nullptr);
+	return sliders[p_idx];
 }
 
 Vector<float> ColorPicker::get_active_slider_values() {
@@ -649,7 +675,7 @@ void ColorPicker::_copy_normalized_to_hsv_okhsl() {
 }
 
 void ColorPicker::_copy_hsv_okhsl_to_normalized() {
-	if (current_shape != SHAPE_NONE && shapes[current_shape]->is_ok_hsl()) {
+	if (current_shape != SHAPE_NONE && shapes[get_current_shape_index()]->is_ok_hsl()) {
 		color_normalized.set_ok_hsl(ok_hsl_h, ok_hsl_s, ok_hsl_l, color_normalized.a);
 	} else {
 		color_normalized.set_hsv(h, s, v, color_normalized.a);
@@ -712,7 +738,7 @@ void ColorPicker::_reset_sliders_theme() {
 	style_box_flat->set_content_margin(SIDE_TOP, 16 * theme_cache.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++) {
+	for (int i = 0; i < MODE_SLIDER_COUNT; i++) {
 		sliders[i]->begin_bulk_theme_override();
 		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);
@@ -815,7 +841,7 @@ void ColorPicker::_update_color(bool p_update_sliders) {
 	_update_text_value();
 
 	if (current_shape != SHAPE_NONE) {
-		for (Control *control : shapes[current_shape]->controls) {
+		for (Control *control : shapes[get_current_shape_index()]->controls) {
 			control->queue_redraw();
 		}
 	}
@@ -931,11 +957,11 @@ void ColorPicker::set_picker_shape(PickerShapeType p_shape) {
 		return;
 	}
 	if (current_shape != SHAPE_NONE) {
-		shape_popup->set_item_checked(current_shape, false);
+		shape_popup->set_item_checked(get_current_shape_index(), false);
 	}
 	if (p_shape != SHAPE_NONE) {
-		shape_popup->set_item_checked(p_shape, true);
-		btn_shape->set_button_icon(shape_popup->get_item_icon(p_shape));
+		shape_popup->set_item_checked(shape_to_index(p_shape), true);
+		btn_shape->set_button_icon(shape_popup->get_item_icon(shape_to_index(p_shape)));
 	}
 
 	current_shape = p_shape;
@@ -1018,6 +1044,11 @@ void ColorPicker::_quick_open_palette_file_selected(const String &p_path) {
 	file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE);
 	_palette_file_selected(p_path);
 }
+
+GridContainer *ColorPicker::get_slider_container() {
+	return slider_gc;
+}
+
 #endif // ifdef TOOLS_ENABLED
 
 void ColorPicker::_palette_file_selected(const String &p_path) {
@@ -1344,7 +1375,7 @@ void ColorPicker::set_colorize_sliders(bool p_colorize_sliders) {
 	if (colorize_sliders) {
 		Ref<StyleBoxEmpty> style_box_empty(memnew(StyleBoxEmpty));
 
-		for (int i = 0; i < SLIDER_COUNT; i++) {
+		for (int i = 0; i < MODE_SLIDER_COUNT; i++) {
 			sliders[i]->add_theme_style_override("slider", style_box_empty);
 		}
 
@@ -1354,7 +1385,7 @@ void ColorPicker::set_colorize_sliders(bool p_colorize_sliders) {
 		style_box_flat->set_content_margin(SIDE_TOP, 16 * theme_cache.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++) {
+		for (int i = 0; i < MODE_SLIDER_COUNT; i++) {
 			sliders[i]->add_theme_style_override("slider", style_box_flat);
 		}
 
@@ -2051,7 +2082,7 @@ void ColorPicker::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edit_intensity"), "set_edit_intensity", "is_editing_intensity");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "color_mode", PROPERTY_HINT_ENUM, "RGB,HSV,LINEAR,OKHSL"), "set_color_mode", "get_color_mode");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "deferred_mode"), "set_deferred_mode", "is_deferred_mode");
-	ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle,OKHSL Circle,None"), "set_picker_shape", "get_picker_shape");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "picker_shape", PROPERTY_HINT_ENUM, "HSV Rectangle,HSV Rectangle Wheel,VHS Circle,OKHSL Circle,OK HS Rectangle:5,OK HL Rectangle,None:4"), "set_picker_shape", "get_picker_shape");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "can_add_swatches"), "set_can_add_swatches", "are_swatches_enabled");
 	ADD_GROUP("Customization", "");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "sampler_visible"), "set_sampler_visible", "is_sampler_visible");
@@ -2077,6 +2108,8 @@ void ColorPicker::_bind_methods() {
 	BIND_ENUM_CONSTANT(SHAPE_VHS_CIRCLE);
 	BIND_ENUM_CONSTANT(SHAPE_OKHSL_CIRCLE);
 	BIND_ENUM_CONSTANT(SHAPE_NONE);
+	BIND_ENUM_CONSTANT(SHAPE_OK_HS_RECTANGLE);
+	BIND_ENUM_CONSTANT(SHAPE_OK_HL_RECTANGLE);
 
 	BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_CONSTANT, ColorPicker, content_margin, "margin");
 	BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, ColorPicker, label_width);
@@ -2158,16 +2191,18 @@ ColorPicker::ColorPicker() {
 	add_shape(memnew(ColorPickerShapeWheel(this)));
 	add_shape(memnew(ColorPickerShapeVHSCircle(this)));
 	add_shape(memnew(ColorPickerShapeOKHSLCircle(this)));
+	add_shape(memnew(ColorPickerShapeOKHSRectangle(this)));
+	add_shape(memnew(ColorPickerShapeOKHLRectangle(this)));
 
 	shape_popup = btn_shape->get_popup();
 	{
 		int i = 0;
 		for (const ColorPickerShape *shape : shapes) {
-			shape_popup->add_radio_check_item(shape->get_name(), i);
+			shape_popup->add_radio_check_item(shape->get_name(), index_to_shape(i));
 			i++;
 		}
 	}
-	shape_popup->set_item_checked(current_shape, true);
+	shape_popup->set_item_checked(get_current_shape_index(), 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));

+ 40 - 4
scene/gui/color_picker.h

@@ -88,6 +88,8 @@ class ColorPicker : public VBoxContainer {
 	friend class ColorPickerShapeCircle;
 	friend class ColorPickerShapeVHSCircle;
 	friend class ColorPickerShapeOKHSLCircle;
+	friend class ColorPickerShapeOKHSRectangle;
+	friend class ColorPickerShapeOKHLRectangle;
 
 	friend class ColorModeRGB;
 	friend class ColorModeHSV;
@@ -113,19 +115,49 @@ public:
 		SHAPE_VHS_CIRCLE,
 		SHAPE_OKHSL_CIRCLE,
 		SHAPE_NONE,
+		SHAPE_OK_HS_RECTANGLE,
+		SHAPE_OK_HL_RECTANGLE,
 
 		SHAPE_MAX
 	};
 
-	static const int SLIDER_COUNT = 3;
+private:
+	// Ideally, `SHAPE_NONE` should be -1 so that we don't need to convert shape type to index.
+	// In order to avoid breaking compatibility, we have to use these methods for conversion.
+	inline int get_current_shape_index() {
+		return shape_to_index(current_shape);
+	}
+
+	static inline int shape_to_index(PickerShapeType p_shape) {
+		if (p_shape == SHAPE_NONE) {
+			return -1;
+		}
+		if (p_shape > SHAPE_NONE) {
+			return p_shape - 1;
+		}
+		return p_shape;
+	}
+
+	static inline PickerShapeType index_to_shape(int p_index) {
+		if (p_index == -1) {
+			return SHAPE_NONE;
+		}
+		if (p_index >= SHAPE_NONE) {
+			return (PickerShapeType)(p_index + 1);
+		}
+		return (PickerShapeType)p_index;
+	}
+
+public:
+	static const int MODE_SLIDER_COUNT = 3;
+
 	enum SLIDER_EXTRA {
-		SLIDER_INTENSITY = 3,
+		SLIDER_INTENSITY = MODE_SLIDER_COUNT,
 		SLIDER_ALPHA,
 
 		SLIDER_MAX
 	};
 
-private:
 	enum class MenuOption {
 		MENU_SAVE,
 		MENU_SAVE_AS,
@@ -134,9 +166,12 @@ private:
 		MENU_CLEAR,
 	};
 
+private:
 	static inline Ref<Shader> wheel_shader;
 	static inline Ref<Shader> circle_shader;
 	static inline Ref<Shader> circle_ok_color_shader;
+	static inline Ref<Shader> rectangle_ok_color_hs_shader;
+	static inline Ref<Shader> rectangle_ok_color_hl_shader;
 	static inline List<Color> preset_cache;
 	static inline List<Color> recent_preset_cache;
 
@@ -144,7 +179,7 @@ private:
 	Object *editor_settings = nullptr;
 #endif
 
-	int current_slider_count = SLIDER_COUNT;
+	int current_slider_count = MODE_SLIDER_COUNT;
 
 	const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 1.0 / 2;
 	const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 30;
@@ -394,6 +429,7 @@ public:
 	void _quick_open_palette_file_selected(const String &p_path);
 #endif
 
+	GridContainer *get_slider_container();
 	HSlider *get_slider(int idx);
 	Vector<float> get_active_slider_values();
 

+ 220 - 6
scene/gui/color_picker_shape.cpp

@@ -131,7 +131,7 @@ void ColorPickerShape::draw_sv_square(Control *p_control, const Rect2 &p_square,
 		Color(1, 1, 1, 1),
 		Color(1, 1, 1, 1),
 		Color(0, 0, 0, 1),
-		Color(0, 0, 0, 1)
+		Color(0, 0, 0, 1),
 	};
 	p_control->draw_polygon(points, colors);
 
@@ -139,7 +139,7 @@ void ColorPickerShape::draw_sv_square(Control *p_control, const Rect2 &p_square,
 		Color(color1, 0),
 		Color(color1, 1),
 		Color(color2, 1),
-		Color(color2, 0)
+		Color(color2, 0),
 	};
 	p_control->draw_polygon(points, colors);
 
@@ -356,6 +356,220 @@ void ColorPickerShapeRectangle::grab_focus() {
 	hue_slider->grab_focus();
 }
 
+void ColorPickerShapeOKHSRectangle::_initialize_controls() {
+	rectangle_margin = memnew(MarginContainer);
+	color_picker->shape_container->add_child(rectangle_margin);
+
+	Ref<ShaderMaterial> material;
+	material.instantiate();
+	material->set_shader(_get_shader());
+
+	square = memnew(Control);
+	rectangle_margin->add_child(square);
+	square->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeOKHSRectangle::_square_draw));
+	square->set_material(material);
+
+	square_overlay = memnew(Control);
+	rectangle_margin->add_child(square_overlay);
+	square_overlay->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
+	square_overlay->connect(SceneStringName(gui_input), callable_mp(this, &ColorPickerShapeOKHSRectangle::_square_overlay_input));
+	square_overlay->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeOKHSRectangle::_square_overlay_draw));
+	connect_shape_focus(square_overlay);
+
+	value_slider = memnew(Control);
+	color_picker->shape_container->add_child(value_slider);
+	value_slider->connect(SceneStringName(gui_input), callable_mp(this, &ColorPickerShapeOKHSRectangle::_value_slider_input));
+	value_slider->connect(SceneStringName(draw), callable_mp(this, &ColorPickerShapeOKHSRectangle::_value_slider_draw));
+	connect_shape_focus(value_slider);
+
+	controls.append(rectangle_margin);
+	controls.append(square);
+	controls.append(square_overlay);
+	controls.append(value_slider);
+}
+
+void ColorPickerShapeOKHSRectangle::update_theme() {
+	const ColorPicker::ThemeCache &theme_cache = color_picker->theme_cache;
+	rectangle_margin->set_custom_minimum_size(Size2(theme_cache.sv_width, theme_cache.sv_height));
+	value_slider->set_custom_minimum_size(Size2(theme_cache.h_width, 0));
+}
+
+void ColorPickerShapeOKHSRectangle::grab_focus() {
+	square_overlay->grab_focus();
+}
+
+void ColorPickerShapeOKHSRectangle::_update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) {
+	if (square_overlay->has_focus()) {
+		color_picker->ok_hsl_h = CLAMP(color_picker->ok_hsl_h + p_color_change_vector.x / 100.0, 0, 1);
+		color_picker->ok_hsl_s = CLAMP(color_picker->ok_hsl_s - p_color_change_vector.y / 100.0, 0, 1);
+	} else if (value_slider->has_focus()) {
+		color_picker->ok_hsl_l = CLAMP(color_picker->ok_hsl_l + p_color_change_vector.y * echo_multiplier / 360.0, 0, 1);
+	}
+}
+
+void ColorPickerShapeOKHSRectangle::_square_draw() {
+	Ref<ShaderMaterial> material = square->get_material();
+	material->set_shader_parameter(SNAME("ok_hsl_l"), color_picker->ok_hsl_l);
+	square->draw_rect(Rect2(Point2(), square->get_size()), Color(1, 1, 1));
+}
+
+void ColorPickerShapeOKHSRectangle::_square_overlay_input(const Ref<InputEvent> &p_event) {
+	handle_cursor_editing(p_event, square_overlay);
+
+	Vector2 event_position;
+	if (!can_handle(p_event, event_position)) {
+		return;
+	}
+	event_position = (event_position / square_overlay->get_size()).clampf(0.0, 1.0);
+
+	color_picker->ok_hsl_h = event_position.x;
+	color_picker->ok_hsl_s = 1.0 - event_position.y;
+
+	apply_color();
+}
+
+void ColorPickerShapeOKHSRectangle::_square_overlay_draw() {
+	const Rect2 rect = Rect2(Vector2(), square_overlay->get_size());
+	const Vector2 end = rect.get_end();
+	Vector2 cursor_pos;
+	cursor_pos.x = CLAMP(rect.position.x + rect.size.x * color_picker->ok_hsl_h, rect.position.x, end.x);
+	cursor_pos.y = CLAMP(rect.position.y + rect.size.y * (1.0 - color_picker->ok_hsl_s), rect.position.y, end.y);
+
+	draw_focus_rect(square_overlay);
+	draw_cursor(square_overlay, cursor_pos);
+}
+
+void ColorPickerShapeOKHSRectangle::_value_slider_input(const Ref<InputEvent> &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 - CLAMP(event_position.y / value_slider->get_size().y, 0.0, 1.0);
+	apply_color();
+}
+
+void ColorPickerShapeOKHSRectangle::_value_slider_draw() {
+	const float ok_hsl_h = color_picker->ok_hsl_h;
+	const float ok_hsl_s = color_picker->ok_hsl_s;
+
+	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(color_picker->ok_hsl_l, 0, 1));
+	const Color color = Color::from_ok_hsl(ok_hsl_h, 1, color_picker->ok_hsl_l);
+	value_slider->draw_line(Vector2(0, y), Vector2(size.x, y), color.inverted());
+}
+
+void ColorPickerShapeOKHLRectangle::_update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) {
+	if (square_overlay->has_focus()) {
+		color_picker->ok_hsl_h = CLAMP(color_picker->ok_hsl_h + p_color_change_vector.x / 100.0, 0, 1);
+		color_picker->ok_hsl_l = CLAMP(color_picker->ok_hsl_l - p_color_change_vector.y / 100.0, 0, 1);
+	} else if (value_slider->has_focus()) {
+		color_picker->ok_hsl_s = CLAMP(color_picker->ok_hsl_s + p_color_change_vector.y * echo_multiplier / 360.0, 0, 1);
+	}
+}
+
+void ColorPickerShapeOKHLRectangle::_square_overlay_input(const Ref<InputEvent> &p_event) {
+	handle_cursor_editing(p_event, square_overlay);
+
+	Vector2 event_position;
+	if (!can_handle(p_event, event_position)) {
+		return;
+	}
+	event_position = (event_position / square_overlay->get_size()).clampf(0.0, 1.0);
+
+	color_picker->ok_hsl_h = event_position.x;
+	color_picker->ok_hsl_l = 1.0 - event_position.y;
+
+	apply_color();
+}
+
+void ColorPickerShapeOKHLRectangle::_square_overlay_draw() {
+	const Rect2 rect = Rect2(Vector2(), square_overlay->get_size());
+	const Vector2 end = rect.get_end();
+	Vector2 cursor_pos;
+	cursor_pos.x = CLAMP(rect.position.x + rect.size.x * color_picker->ok_hsl_h, rect.position.x, end.x);
+	cursor_pos.y = CLAMP(rect.position.y + rect.size.y * (1.0 - color_picker->ok_hsl_l), rect.position.y, end.y);
+
+	draw_focus_rect(square_overlay);
+	draw_cursor(square_overlay, cursor_pos);
+}
+
+void ColorPickerShapeOKHLRectangle::_square_draw() {
+	Ref<ShaderMaterial> material = square->get_material();
+	material->set_shader_parameter(SNAME("ok_hsl_s"), color_picker->ok_hsl_s);
+	square->draw_rect(Rect2(Point2(), square->get_size()), Color(1, 1, 1));
+}
+
+void ColorPickerShapeOKHLRectangle::_value_slider_input(const Ref<InputEvent> &p_event) {
+	handle_cursor_editing(p_event, value_slider);
+
+	Vector2 event_position;
+	if (!can_handle(p_event, event_position)) {
+		return;
+	}
+	color_picker->ok_hsl_s = 1 - CLAMP(event_position.y / value_slider->get_size().y, 0.0, 1.0);
+	apply_color();
+}
+
+void ColorPickerShapeOKHLRectangle::_value_slider_draw() {
+	const float ok_hsl_h = color_picker->ok_hsl_h;
+	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, 1, ok_hsl_l);
+	Color color2 = Color::from_ok_hsl(ok_hsl_h, 0.5, ok_hsl_l);
+	Color color3 = Color::from_ok_hsl(ok_hsl_h, 0, ok_hsl_l);
+	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(color_picker->ok_hsl_s, 0, 1));
+	const Color color = Color::from_ok_hsl(ok_hsl_h, 1, ok_hsl_l);
+	value_slider->draw_line(Vector2(0, y), Vector2(size.x, y), color.inverted());
+}
+
 float ColorPickerShapeWheel::_get_h_on_wheel(const Vector2 &p_color_change_vector) {
 	int h_change = get_edge_h_change(p_color_change_vector);
 
@@ -645,7 +859,7 @@ void ColorPickerShapeVHSCircle::_value_slider_draw() {
 		Vector2(),
 		Vector2(size.x, 0),
 		size,
-		Vector2(0, size.y)
+		Vector2(0, size.y),
 	};
 
 	Color color = Color::from_hsv(color_picker->h, color_picker->s, 1);
@@ -653,7 +867,7 @@ void ColorPickerShapeVHSCircle::_value_slider_draw() {
 		color,
 		color,
 		Color(),
-		Color()
+		Color(),
 	};
 
 	value_slider->draw_polygon(points, colors);
@@ -736,7 +950,7 @@ void ColorPickerShapeOKHSLCircle::_value_slider_draw() {
 		size,
 		Vector2(0, size.y),
 		Vector2(0, size.y * 0.5),
-		Vector2()
+		Vector2(),
 	};
 
 	Color color1 = Color::from_ok_hsl(ok_hsl_h, ok_hsl_s, 1);
@@ -755,7 +969,7 @@ void ColorPickerShapeOKHSLCircle::_value_slider_draw() {
 	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());
+	value_slider->draw_line(Vector2(0, y), Vector2(size.x, y), Color::from_ok_hsl(ok_hsl_h, 1, ok_hsl_l).inverted());
 }
 
 void ColorPickerShapeOKHSLCircle::_update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) {

+ 54 - 0
scene/gui/color_picker_shape.h

@@ -104,6 +104,60 @@ public:
 			ColorPickerShape(p_color_picker) {}
 };
 
+class ColorPickerShapeOKHSRectangle : public ColorPickerShape {
+	GDCLASS(ColorPickerShapeOKHSRectangle, ColorPickerShape);
+
+	MarginContainer *rectangle_margin = nullptr;
+
+protected:
+	Control *square = nullptr;
+	Control *square_overlay = nullptr;
+	Control *value_slider = nullptr;
+	virtual Ref<Shader> _get_shader() const { return ColorPicker::rectangle_ok_color_hs_shader; }
+	virtual void _initialize_controls() override;
+	virtual void _update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) override;
+
+	virtual void _square_draw();
+	virtual void _square_overlay_input(const Ref<InputEvent> &p_event);
+	virtual void _square_overlay_draw();
+
+	virtual void _value_slider_input(const Ref<InputEvent> &p_event);
+	virtual void _value_slider_draw();
+
+public:
+	virtual String get_name() const override { return ETR("OK HS Rectangle"); }
+	virtual bool is_ok_hsl() const override { return true; }
+	virtual Ref<Texture2D> get_icon() const override { return color_picker->theme_cache.shape_rect; }
+	virtual void update_theme() override;
+	virtual void grab_focus() override;
+
+	ColorPickerShapeOKHSRectangle(ColorPicker *p_color_picker) :
+			ColorPickerShape(p_color_picker) {}
+};
+
+class ColorPickerShapeOKHLRectangle : public ColorPickerShapeOKHSRectangle {
+	GDCLASS(ColorPickerShapeOKHLRectangle, ColorPickerShapeOKHSRectangle);
+
+protected:
+	virtual Ref<Shader> _get_shader() const override { return ColorPicker::rectangle_ok_color_hl_shader; }
+	virtual void _update_cursor(const Vector2 &p_color_change_vector, bool p_is_echo) override;
+
+	virtual void _square_draw() override;
+	virtual void _square_overlay_input(const Ref<InputEvent> &p_event) override;
+	virtual void _square_overlay_draw() override;
+
+	virtual void _value_slider_input(const Ref<InputEvent> &p_event) override;
+	virtual void _value_slider_draw() override;
+
+public:
+	virtual String get_name() const override { return ETR("OK HL Rectangle"); }
+	virtual bool is_ok_hsl() const override { return true; }
+	virtual Ref<Texture2D> get_icon() const override { return color_picker->theme_cache.shape_rect; }
+
+	ColorPickerShapeOKHLRectangle(ColorPicker *p_color_picker) :
+			ColorPickerShapeOKHSRectangle(p_color_picker) {}
+};
+
 class ColorPickerShapeWheel : public ColorPickerShape {
 	GDCLASS(ColorPickerShapeWheel, ColorPickerShape);