Skip to content

Commit f99a6f3

Browse files
committed
Add color pickers to script editor
1 parent 1f787b6 commit f99a6f3

File tree

9 files changed

+387
-4
lines changed

9 files changed

+387
-4
lines changed

doc/classes/EditorSettings.xml

+3
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,9 @@
12161216
<member name="text_editor/appearance/caret/type" type="int" setter="" getter="">
12171217
The shape of the caret to use in the script editor. [b]Line[/b] displays a vertical line to the left of the current character, whereas [b]Block[/b] displays an outline over the current character.
12181218
</member>
1219+
<member name="text_editor/appearance/enable_inline_color_picker" type="bool" setter="" getter="">
1220+
If [code]true[/code], displays a colored button before any [Color] constructor in the script editor. Clicking on them allows the color to be modified through a color picker.
1221+
</member>
12191222
<member name="text_editor/appearance/guidelines/line_length_guideline_hard_column" type="int" setter="" getter="">
12201223
The column at which to display a subtle line as a line length guideline for scripts. This should generally be greater than [member text_editor/appearance/guidelines/line_length_guideline_soft_column].
12211224
</member>

editor/editor_settings.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
683683
_load_godot2_text_editor_theme();
684684

685685
// Appearance
686+
EDITOR_SETTING_BASIC(Variant::BOOL, PROPERTY_HINT_NONE, "text_editor/appearance/enable_inline_color_picker", true, "");
687+
686688
// Appearance: Caret
687689
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "text_editor/appearance/caret/type", 0, "Line,Block")
688690
_initial_set("text_editor/appearance/caret/caret_blink", true, true);

editor/plugins/script_text_editor.cpp

+218
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,215 @@ bool ScriptTextEditor::show_members_overview() {
396396
return true;
397397
}
398398

399+
bool ScriptTextEditor::_is_valid_color_info(const Array &p_info) {
400+
if (p_info.size() < INFO_COLOR_MAX) {
401+
return false;
402+
}
403+
if (p_info[INFO_COLOR].get_type() != Variant::COLOR) {
404+
return false;
405+
}
406+
if (!p_info[INFO_COLOR_END].is_num() || !p_info[INFO_COLOR_MODE].is_num()) {
407+
return false;
408+
}
409+
return true;
410+
}
411+
412+
Array ScriptTextEditor::_inline_object_parse(const String &p_text, int p_line) {
413+
Array result;
414+
int i_end_previous = 0;
415+
int i_start = p_text.find("Color");
416+
417+
while (i_start != -1) {
418+
int i_par_start = p_text.find_char('(', i_start + 5);
419+
if (i_par_start != -1) {
420+
int i_par_end = p_text.find_char(')', i_start + 5);
421+
if (i_par_end != -1) {
422+
Array color_info;
423+
color_info.resize(INFO_COLOR_MAX);
424+
color_info[TextEdit::INFO_LINE] = p_line;
425+
color_info[TextEdit::INFO_COLUMN] = i_start;
426+
color_info[TextEdit::INFO_WIDTH_RATIO] = 1.0;
427+
color_info[INFO_COLOR_END] = i_par_end;
428+
429+
String fn_name = p_text.substr(i_start + 5, i_par_start - i_start - 5);
430+
String s_params = p_text.substr(i_par_start + 1, i_par_end - i_par_start - 1);
431+
bool has_added_color = false;
432+
433+
if (fn_name.is_empty() || fn_name == ".from_string") {
434+
String stripped = s_params.strip_edges(true, true);
435+
// String parameter.
436+
if (stripped.length() > 0 && (stripped[0] == '\"')) {
437+
String color_string = stripped.substr(1, stripped.length() - 2);
438+
color_info[INFO_COLOR] = Color(color_string);
439+
color_info[INFO_COLOR_MODE] = -1;
440+
has_added_color = true;
441+
}
442+
// Empty Color() constructor.
443+
else if (stripped.is_empty()) {
444+
color_info[INFO_COLOR] = Color();
445+
color_info[INFO_COLOR_MODE] = -1;
446+
has_added_color = true;
447+
}
448+
}
449+
// Float & int parameters.
450+
if (!has_added_color && s_params.size() > 0) {
451+
PackedFloat64Array params = s_params.split_floats(",", false);
452+
if (params.size() == 3) {
453+
params.resize(4);
454+
params.set(3, 1.0);
455+
}
456+
if (params.size() == 4) {
457+
has_added_color = true;
458+
if (fn_name == ".from_ok_hsl") {
459+
color_info[INFO_COLOR] = Color::from_ok_hsl(params[0], params[1], params[2], params[3]);
460+
color_info[INFO_COLOR_MODE] = ColorPicker::MODE_OKHSL;
461+
} else if (fn_name == ".from_hsv") {
462+
color_info[INFO_COLOR] = Color::from_hsv(params[0], params[1], params[2], params[3]);
463+
color_info[INFO_COLOR_MODE] = ColorPicker::MODE_HSV;
464+
} else if (fn_name == ".from_rgba8") {
465+
color_info[INFO_COLOR] = Color::from_rgba8(int(params[0]), int(params[1]), int(params[2]), int(params[3]));
466+
color_info[INFO_COLOR_MODE] = ColorPicker::MODE_RGB;
467+
} else if (fn_name.is_empty()) {
468+
color_info[INFO_COLOR] = Color(params[0], params[1], params[2], params[3]);
469+
color_info[INFO_COLOR_MODE] = ColorPicker::MODE_RAW;
470+
} else {
471+
has_added_color = false;
472+
}
473+
}
474+
}
475+
476+
if (has_added_color) {
477+
result.push_back(color_info);
478+
i_end_previous = i_par_end + 1;
479+
}
480+
}
481+
}
482+
i_end_previous = MAX(i_end_previous, i_start);
483+
i_start = p_text.find("Color", i_start + 1);
484+
}
485+
return result;
486+
}
487+
488+
void ScriptTextEditor::_inline_object_draw(const Array &p_info, const Rect2 &p_rect) {
489+
if (_is_valid_color_info(p_info)) {
490+
Rect2 col_rect = p_rect.grow(-4);
491+
code_editor->get_text_editor()->draw_rect(col_rect, Color(p_info[INFO_COLOR]));
492+
code_editor->get_text_editor()->draw_rect(col_rect, Color(1, 1, 1), false, 1);
493+
}
494+
}
495+
496+
void ScriptTextEditor::_inline_object_handle_click(const Array &p_info, const Rect2 &p_rect) {
497+
if (_is_valid_color_info(p_info)) {
498+
int mode = p_info[INFO_COLOR_MODE];
499+
if (mode < 0 || mode >= ColorPicker::MODE_MAX) {
500+
inline_color_picker->set_using_hex_string(true);
501+
} else {
502+
inline_color_picker->set_using_hex_string(false);
503+
inline_color_picker->set_color_mode((ColorPicker::ColorModeType)mode);
504+
}
505+
506+
inline_color_picker->set_pick_color(p_info[INFO_COLOR]);
507+
inline_color_line = p_info[TextEdit::INFO_LINE];
508+
inline_color_start = p_info[TextEdit::INFO_COLUMN];
509+
inline_color_end = p_info[INFO_COLOR_END];
510+
511+
// Move popup above the line if it's too low.
512+
float_t view_h = get_viewport_rect().size.y;
513+
float_t pop_h = inline_color_popup->get_contents_minimum_size().y;
514+
float_t pop_y = p_rect.get_end().y;
515+
float_t pop_x = p_rect.position.x;
516+
if (pop_y + pop_h > view_h) {
517+
pop_y = p_rect.position.y - pop_h;
518+
}
519+
// Move popup to the right if it's too high.
520+
if (pop_y < 0) {
521+
pop_x = p_rect.get_end().x;
522+
}
523+
524+
inline_color_popup->popup(Rect2(pop_x, pop_y, 0, 0));
525+
code_editor->get_text_editor()->set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_NONE);
526+
}
527+
}
528+
529+
String ScriptTextEditor::_picker_color_stringify(const Color &p_color, int p_mode) {
530+
String result;
531+
String fname;
532+
Vector<String> str_params;
533+
// HTML string.
534+
if (p_mode < 0) {
535+
str_params.push_back("\"" + p_color.to_html() + "\"");
536+
}
537+
// RGBA int parameters (0-255).
538+
else if (p_mode == ColorPicker::MODE_RGB) {
539+
str_params = {
540+
itos(p_color.get_r8()),
541+
itos(p_color.get_g8()),
542+
itos(p_color.get_b8()),
543+
itos(p_color.get_a8())
544+
};
545+
fname = ".from_rgba8";
546+
}
547+
// HSL, HSV, and RAW Float parameters.
548+
else {
549+
PackedFloat64Array flt_params;
550+
str_params.resize(4);
551+
switch (p_mode) {
552+
case ColorPicker::MODE_HSV: {
553+
flt_params = { p_color.get_h(), p_color.get_s(), p_color.get_v(), p_color.a };
554+
fname = ".from_hsv";
555+
} break;
556+
case ColorPicker::MODE_OKHSL: {
557+
flt_params = { p_color.get_ok_hsl_h(), p_color.get_ok_hsl_s(), p_color.get_ok_hsl_l(), p_color.a };
558+
fname = ".from_ok_hsl";
559+
} break;
560+
case ColorPicker::MODE_RAW: {
561+
flt_params = { p_color.r, p_color.g, p_color.b, p_color.a };
562+
fname = "";
563+
} break;
564+
}
565+
for (int ind = 0; ind < flt_params.size(); ind++) {
566+
str_params.set(ind, String::num(flt_params[ind], 3));
567+
}
568+
}
569+
result = "Color" + fname + "(" + String(", ").join(str_params) + ")";
570+
return result;
571+
}
572+
573+
void ScriptTextEditor::_picker_color_changed(const Color &p_color) {
574+
if (inline_color_line < 0) {
575+
return;
576+
}
577+
int mode = inline_color_picker->get_color_mode();
578+
if (inline_color_picker->is_using_hex_string()) {
579+
mode = -1;
580+
}
581+
String result = _picker_color_stringify(p_color, mode);
582+
code_editor->get_text_editor()->begin_complex_operation();
583+
code_editor->get_text_editor()->remove_text(inline_color_line, inline_color_start, inline_color_line, inline_color_end + 1);
584+
inline_color_end = inline_color_start + result.size() - 2;
585+
code_editor->get_text_editor()->insert_text(result, inline_color_line, inline_color_start);
586+
code_editor->get_text_editor()->end_complex_operation();
587+
}
588+
589+
void ScriptTextEditor::_picker_opening() {
590+
previous_tooltip_enabled = code_editor->get_text_editor()->is_symbol_tooltip_on_hover_enabled();
591+
code_editor->get_text_editor()->set_symbol_tooltip_on_hover_enabled(false);
592+
}
593+
594+
void ScriptTextEditor::_picker_closed() {
595+
code_editor->get_text_editor()->set_symbol_tooltip_on_hover_enabled(previous_tooltip_enabled);
596+
}
597+
399598
void ScriptTextEditor::update_settings() {
400599
code_editor->get_text_editor()->set_gutter_draw(connection_gutter, EDITOR_GET("text_editor/appearance/gutters/show_info_gutter"));
600+
if (EDITOR_GET("text_editor/appearance/enable_inline_color_picker")) {
601+
code_editor->get_text_editor()->set_inline_object_handlers(
602+
callable_mp(this, &ScriptTextEditor::_inline_object_parse),
603+
callable_mp(this, &ScriptTextEditor::_inline_object_draw),
604+
callable_mp(this, &ScriptTextEditor::_inline_object_handle_click));
605+
} else {
606+
code_editor->get_text_editor()->set_inline_object_handlers(Callable(), Callable(), Callable());
607+
}
401608
code_editor->update_editor_settings();
402609
}
403610

@@ -2594,6 +2801,17 @@ ScriptTextEditor::ScriptTextEditor() {
25942801
bookmarks_menu = memnew(PopupMenu);
25952802
breakpoints_menu = memnew(PopupMenu);
25962803

2804+
inline_color_popup = memnew(PopupPanel);
2805+
inline_color_popup->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_picker_opening));
2806+
inline_color_popup->connect("popup_hide", callable_mp(this, &ScriptTextEditor::_picker_closed));
2807+
add_child(inline_color_popup);
2808+
2809+
inline_color_picker = memnew(ColorPicker);
2810+
inline_color_picker->set_mouse_filter(MOUSE_FILTER_STOP);
2811+
inline_color_picker->set_deferred_mode(true);
2812+
inline_color_picker->connect("color_changed", callable_mp(this, &ScriptTextEditor::_picker_color_changed));
2813+
inline_color_popup->add_child(inline_color_picker);
2814+
25972815
connection_info_dialog = memnew(ConnectionInfoDialog);
25982816

25992817
SET_DRAG_FORWARDING_GCD(code_editor->get_text_editor(), ScriptTextEditor);

editor/plugins/script_text_editor.h

+23
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ class ScriptTextEditor : public ScriptEditorBase {
8484
PopupMenu *highlighter_menu = nullptr;
8585
PopupMenu *context_menu = nullptr;
8686

87+
int inline_color_line = -1;
88+
int inline_color_start = -1;
89+
int inline_color_end = -1;
90+
PopupPanel *inline_color_popup = nullptr;
91+
ColorPicker *inline_color_picker = nullptr;
92+
bool previous_tooltip_enabled = true;
93+
8794
GotoLinePopup *goto_line_popup = nullptr;
8895
ScriptEditorQuickOpen *quick_open = nullptr;
8996
ConnectionInfoDialog *connection_info_dialog = nullptr;
@@ -160,6 +167,13 @@ class ScriptTextEditor : public ScriptEditorBase {
160167
EDIT_EMOJI_AND_SYMBOL,
161168
};
162169

170+
enum ColorInfo {
171+
INFO_COLOR = TextEdit::INFO_MAX,
172+
INFO_COLOR_MODE,
173+
INFO_COLOR_END,
174+
INFO_COLOR_MAX
175+
};
176+
163177
void _enable_code_editor();
164178

165179
protected:
@@ -185,6 +199,15 @@ class ScriptTextEditor : public ScriptEditorBase {
185199
void _error_clicked(const Variant &p_line);
186200
void _warning_clicked(const Variant &p_line);
187201

202+
bool _is_valid_color_info(const Array &p_info);
203+
Array _inline_object_parse(const String &p_text, int p_line);
204+
void _inline_object_draw(const Array &p_info, const Rect2 &p_rect);
205+
void _inline_object_handle_click(const Array &p_info, const Rect2 &p_rect);
206+
String _picker_color_stringify(const Color &p_color, int p_mode);
207+
void _picker_color_changed(const Color &p_color);
208+
void _picker_opening();
209+
void _picker_closed();
210+
188211
void _notification(int p_what);
189212

190213
HashMap<String, Ref<EditorSyntaxHighlighter>> highlighters;

modules/text_server_adv/text_server_adv.cpp

+14-3
Original file line numberDiff line numberDiff line change
@@ -5196,7 +5196,8 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
51965196
int32_t bidi_run_end = _convert_pos(p_sd, ov_start + start + _bidi_run_start + _bidi_run_length);
51975197

51985198
for (int j = 0; j < sd_size; j++) {
5199-
if ((sd_glyphs[j].start >= bidi_run_start) && (sd_glyphs[j].end <= bidi_run_end)) {
5199+
int col_key_off = (sd_glyphs[j].start == sd_glyphs[j].end) ? 1 : 0;
5200+
if ((sd_glyphs[j].start >= bidi_run_start) && (sd_glyphs[j].end <= bidi_run_end - col_key_off)) {
52005201
// Copy glyphs.
52015202
Glyph gl = sd_glyphs[j];
52025203
if (gl.span_index >= 0) {
@@ -5986,7 +5987,11 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
59865987
while (i < span_size) {
59875988
String language = sd->spans[i].language;
59885989
int r_start = sd->spans[i].start;
5989-
while (i + 1 < span_size && language == sd->spans[i + 1].language) {
5990+
if (r_start == sd->spans[i].end) {
5991+
i++;
5992+
continue;
5993+
}
5994+
while (i + 1 < span_size && (language == sd->spans[i + 1].language || sd->spans[i + 1].start == sd->spans[i + 1].end)) {
59905995
i++;
59915996
}
59925997
int r_end = sd->spans[i].end;
@@ -6114,6 +6119,11 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
61146119
continue;
61156120
}
61166121
}
6122+
// Do not add extra space for color picker object.
6123+
if (((sd_glyphs[i].flags & GRAPHEME_IS_EMBEDDED_OBJECT) == GRAPHEME_IS_EMBEDDED_OBJECT && sd_glyphs[i].start == sd_glyphs[i].end) || (i + 1 < sd->glyphs.size() && (sd_glyphs[i + 1].flags & GRAPHEME_IS_EMBEDDED_OBJECT) == GRAPHEME_IS_EMBEDDED_OBJECT && sd_glyphs[i + 1].start == sd_glyphs[i + 1].end)) {
6124+
i += (sd_glyphs[i].count - 1);
6125+
continue;
6126+
}
61176127
Glyph gl;
61186128
gl.span_index = sd_glyphs[i].span_index;
61196129
gl.start = sd_glyphs[i].start;
@@ -6983,7 +6993,8 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
69836993

69846994
for (int k = spn_from; k != spn_to; k += spn_delta) {
69856995
const ShapedTextDataAdvanced::Span &span = sd->spans[k];
6986-
if (span.start - sd->start >= script_run_end || span.end - sd->start <= script_run_start) {
6996+
int col_key_off = (span.start == span.end) ? 1 : 0;
6997+
if (span.start - sd->start >= script_run_end || span.end - sd->start <= script_run_start - col_key_off) {
69876998
continue;
69886999
}
69897000
if (span.embedded_key != Variant()) {

scene/gui/color_picker.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,16 @@ bool ColorPicker::is_editing_alpha() const {
452452
return edit_alpha;
453453
}
454454

455+
void ColorPicker::set_using_hex_string(bool p_using_hex_string) {
456+
if (p_using_hex_string == text_is_constructor) {
457+
_text_type_toggled();
458+
}
459+
}
460+
461+
bool ColorPicker::is_using_hex_string() {
462+
return !text_is_constructor;
463+
}
464+
455465
void ColorPicker::_slider_drag_started() {
456466
currently_dragging = true;
457467
}

scene/gui/color_picker.h

+3
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,9 @@ class ColorPicker : public VBoxContainer {
368368
void set_edit_alpha(bool p_show);
369369
bool is_editing_alpha() const;
370370

371+
void set_using_hex_string(bool p_using_hex_string);
372+
bool is_using_hex_string();
373+
371374
void _set_pick_color(const Color &p_color, bool p_update_sliders);
372375
void set_pick_color(const Color &p_color);
373376
Color get_pick_color() const;

0 commit comments

Comments
 (0)