mirror of
https://github.com/Redot-Engine/redot-engine.git
synced 2025-12-06 07:17:42 -05:00
Merge pull request #105724 from dugramen/recovery/split-commit
Add inline color pickers to script editor
This commit is contained in:
@@ -1224,6 +1224,9 @@
|
||||
<member name="text_editor/appearance/caret/type" type="int" setter="" getter="">
|
||||
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.
|
||||
</member>
|
||||
<member name="text_editor/appearance/enable_inline_color_picker" type="bool" setter="" getter="">
|
||||
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.
|
||||
</member>
|
||||
<member name="text_editor/appearance/guidelines/line_length_guideline_hard_column" type="int" setter="" getter="">
|
||||
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].
|
||||
</member>
|
||||
|
||||
@@ -694,6 +694,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
|
||||
_load_godot2_text_editor_theme();
|
||||
|
||||
// Appearance
|
||||
EDITOR_SETTING_BASIC(Variant::BOOL, PROPERTY_HINT_NONE, "text_editor/appearance/enable_inline_color_picker", true, "");
|
||||
|
||||
// Appearance: Caret
|
||||
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "text_editor/appearance/caret/type", 0, "Line,Block")
|
||||
_initial_set("text_editor/appearance/caret/caret_blink", true, true);
|
||||
|
||||
@@ -396,8 +396,237 @@ bool ScriptTextEditor::show_members_overview() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScriptTextEditor::_is_valid_color_info(const Dictionary &p_info) {
|
||||
if (p_info.get_valid("color").get_type() != Variant::COLOR) {
|
||||
return false;
|
||||
}
|
||||
if (!p_info.get_valid("color_end").is_num() || !p_info.get_valid("color_mode").is_num()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Array ScriptTextEditor::_inline_object_parse(const String &p_text, int p_line) {
|
||||
Array result;
|
||||
int i_end_previous = 0;
|
||||
int i_start = p_text.find("Color");
|
||||
|
||||
while (i_start != -1) {
|
||||
// Ignore words that just have "Color" in them.
|
||||
if (i_start == 0 || !("_" + p_text.substr(i_start - 1, 1)).is_valid_ascii_identifier()) {
|
||||
int i_par_start = p_text.find_char('(', i_start + 5);
|
||||
if (i_par_start != -1) {
|
||||
int i_par_end = p_text.find_char(')', i_start + 5);
|
||||
if (i_par_end != -1) {
|
||||
Dictionary color_info;
|
||||
color_info["line"] = p_line;
|
||||
color_info["column"] = i_start;
|
||||
color_info["width_ratio"] = 1.0;
|
||||
color_info["color_end"] = i_par_end;
|
||||
|
||||
String fn_name = p_text.substr(i_start + 5, i_par_start - i_start - 5);
|
||||
String s_params = p_text.substr(i_par_start + 1, i_par_end - i_par_start - 1);
|
||||
bool has_added_color = false;
|
||||
|
||||
if (fn_name.is_empty()) {
|
||||
String stripped = s_params.strip_edges(true, true);
|
||||
// String constructor.
|
||||
if (stripped.length() > 0 && (stripped[0] == '\"')) {
|
||||
String color_string = stripped.substr(1, stripped.length() - 2);
|
||||
color_info["color"] = Color(color_string);
|
||||
color_info["color_mode"] = MODE_STRING;
|
||||
has_added_color = true;
|
||||
}
|
||||
// Hex constructor.
|
||||
else if (stripped.length() == 10 && stripped.substr(0, 2) == "0x") {
|
||||
color_info["color"] = Color(stripped.substr(2, stripped.length() - 2));
|
||||
color_info["color_mode"] = MODE_HEX;
|
||||
has_added_color = true;
|
||||
}
|
||||
// Empty Color() constructor.
|
||||
else if (stripped.is_empty()) {
|
||||
color_info["color"] = Color();
|
||||
color_info["color_mode"] = MODE_RGB;
|
||||
has_added_color = true;
|
||||
}
|
||||
}
|
||||
// Float & int parameters.
|
||||
if (!has_added_color && s_params.size() > 0) {
|
||||
PackedFloat64Array params = s_params.split_floats(",", false);
|
||||
if (params.size() == 3) {
|
||||
params.resize(4);
|
||||
params.set(3, 1.0);
|
||||
}
|
||||
if (params.size() == 4) {
|
||||
has_added_color = true;
|
||||
if (fn_name == ".from_ok_hsl") {
|
||||
color_info["color"] = Color::from_ok_hsl(params[0], params[1], params[2], params[3]);
|
||||
color_info["color_mode"] = MODE_OKHSL;
|
||||
} else if (fn_name == ".from_hsv") {
|
||||
color_info["color"] = Color::from_hsv(params[0], params[1], params[2], params[3]);
|
||||
color_info["color_mode"] = MODE_HSV;
|
||||
} else if (fn_name == ".from_rgba8") {
|
||||
color_info["color"] = Color::from_rgba8(int(params[0]), int(params[1]), int(params[2]), int(params[3]));
|
||||
color_info["color_mode"] = MODE_RGB8;
|
||||
} else if (fn_name.is_empty()) {
|
||||
color_info["color"] = Color(params[0], params[1], params[2], params[3]);
|
||||
color_info["color_mode"] = MODE_RGB;
|
||||
} else {
|
||||
has_added_color = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (has_added_color) {
|
||||
result.push_back(color_info);
|
||||
i_end_previous = i_par_end + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
i_end_previous = MAX(i_end_previous, i_start);
|
||||
i_start = p_text.find("Color", i_start + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ScriptTextEditor::_inline_object_draw(const Dictionary &p_info, const Rect2 &p_rect) {
|
||||
if (_is_valid_color_info(p_info)) {
|
||||
Rect2 col_rect = p_rect.grow(-4);
|
||||
if (color_alpha_texture.is_null()) {
|
||||
color_alpha_texture = inline_color_picker->get_theme_icon("sample_bg", "ColorPicker");
|
||||
}
|
||||
code_editor->get_text_editor()->draw_texture_rect(color_alpha_texture, col_rect, false);
|
||||
code_editor->get_text_editor()->draw_rect(col_rect, Color(p_info["color"]));
|
||||
code_editor->get_text_editor()->draw_rect(col_rect, Color(1, 1, 1), false, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptTextEditor::_inline_object_handle_click(const Dictionary &p_info, const Rect2 &p_rect) {
|
||||
if (_is_valid_color_info(p_info)) {
|
||||
inline_color_picker->set_pick_color(p_info["color"]);
|
||||
inline_color_line = p_info["line"];
|
||||
inline_color_start = p_info["column"];
|
||||
inline_color_end = p_info["color_end"];
|
||||
|
||||
// Reset tooltip hover timer.
|
||||
code_editor->get_text_editor()->set_symbol_tooltip_on_hover_enabled(false);
|
||||
code_editor->get_text_editor()->set_symbol_tooltip_on_hover_enabled(true);
|
||||
|
||||
_update_color_constructor_options();
|
||||
inline_color_options->select(p_info["color_mode"]);
|
||||
EditorNode::get_singleton()->setup_color_picker(inline_color_picker);
|
||||
|
||||
// Move popup above the line if it's too low.
|
||||
float_t view_h = get_viewport_rect().size.y;
|
||||
float_t pop_h = inline_color_popup->get_contents_minimum_size().y;
|
||||
float_t pop_y = p_rect.get_end().y;
|
||||
float_t pop_x = p_rect.position.x;
|
||||
if (pop_y + pop_h > view_h) {
|
||||
pop_y = p_rect.position.y - pop_h;
|
||||
}
|
||||
// Move popup to the right if it's too high.
|
||||
if (pop_y < 0) {
|
||||
pop_x = p_rect.get_end().x;
|
||||
}
|
||||
|
||||
inline_color_popup->popup(Rect2(pop_x, pop_y, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
String ScriptTextEditor::_picker_color_stringify(const Color &p_color, COLOR_MODE p_mode) {
|
||||
String result;
|
||||
String fname;
|
||||
Vector<String> str_params;
|
||||
switch (p_mode) {
|
||||
case ScriptTextEditor::MODE_STRING: {
|
||||
str_params.push_back("\"" + p_color.to_html() + "\"");
|
||||
} break;
|
||||
case ScriptTextEditor::MODE_HEX: {
|
||||
str_params.push_back("0x" + p_color.to_html());
|
||||
} break;
|
||||
case ScriptTextEditor::MODE_RGB: {
|
||||
str_params = {
|
||||
String::num(p_color.r, 3),
|
||||
String::num(p_color.g, 3),
|
||||
String::num(p_color.b, 3),
|
||||
String::num(p_color.a, 3)
|
||||
};
|
||||
} break;
|
||||
case ScriptTextEditor::MODE_HSV: {
|
||||
str_params = {
|
||||
String::num(p_color.get_h(), 3),
|
||||
String::num(p_color.get_s(), 3),
|
||||
String::num(p_color.get_v(), 3),
|
||||
String::num(p_color.a, 3)
|
||||
};
|
||||
fname = ".from_hsv";
|
||||
} break;
|
||||
case ScriptTextEditor::MODE_OKHSL: {
|
||||
str_params = {
|
||||
String::num(p_color.get_ok_hsl_h(), 3),
|
||||
String::num(p_color.get_ok_hsl_s(), 3),
|
||||
String::num(p_color.get_ok_hsl_l(), 3),
|
||||
String::num(p_color.a, 3)
|
||||
};
|
||||
fname = ".from_ok_hsl";
|
||||
} break;
|
||||
case ScriptTextEditor::MODE_RGB8: {
|
||||
str_params = {
|
||||
itos(p_color.get_r8()),
|
||||
itos(p_color.get_g8()),
|
||||
itos(p_color.get_b8()),
|
||||
itos(p_color.get_a8())
|
||||
};
|
||||
fname = ".from_rgba8";
|
||||
} break;
|
||||
default: {
|
||||
} break;
|
||||
}
|
||||
result = "Color" + fname + "(" + String(", ").join(str_params) + ")";
|
||||
return result;
|
||||
}
|
||||
|
||||
void ScriptTextEditor::_picker_color_changed(const Color &p_color) {
|
||||
_update_color_constructor_options();
|
||||
_update_color_text();
|
||||
}
|
||||
|
||||
void ScriptTextEditor::_update_color_constructor_options() {
|
||||
int item_count = inline_color_options->get_item_count();
|
||||
// Update or add each constructor as an option.
|
||||
for (int i = 0; i < MODE_MAX; i++) {
|
||||
String option_text = _picker_color_stringify(inline_color_picker->get_pick_color(), (COLOR_MODE)i);
|
||||
if (i >= item_count) {
|
||||
inline_color_options->add_item(option_text);
|
||||
} else {
|
||||
inline_color_options->set_item_text(i, option_text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptTextEditor::_update_color_text() {
|
||||
if (inline_color_line < 0) {
|
||||
return;
|
||||
}
|
||||
String result = inline_color_options->get_item_text(inline_color_options->get_selected_id());
|
||||
code_editor->get_text_editor()->begin_complex_operation();
|
||||
code_editor->get_text_editor()->remove_text(inline_color_line, inline_color_start, inline_color_line, inline_color_end + 1);
|
||||
inline_color_end = inline_color_start + result.size() - 2;
|
||||
code_editor->get_text_editor()->insert_text(result, inline_color_line, inline_color_start);
|
||||
code_editor->get_text_editor()->end_complex_operation();
|
||||
}
|
||||
|
||||
void ScriptTextEditor::update_settings() {
|
||||
code_editor->get_text_editor()->set_gutter_draw(connection_gutter, EDITOR_GET("text_editor/appearance/gutters/show_info_gutter"));
|
||||
if (EDITOR_GET("text_editor/appearance/enable_inline_color_picker")) {
|
||||
code_editor->get_text_editor()->set_inline_object_handlers(
|
||||
callable_mp(this, &ScriptTextEditor::_inline_object_parse),
|
||||
callable_mp(this, &ScriptTextEditor::_inline_object_draw),
|
||||
callable_mp(this, &ScriptTextEditor::_inline_object_handle_click));
|
||||
} else {
|
||||
code_editor->get_text_editor()->set_inline_object_handlers(Callable(), Callable(), Callable());
|
||||
}
|
||||
code_editor->update_editor_settings();
|
||||
}
|
||||
|
||||
@@ -1812,6 +2041,9 @@ void ScriptTextEditor::_notification(int p_what) {
|
||||
[[fallthrough]];
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
code_editor->get_text_editor()->set_gutter_width(connection_gutter, code_editor->get_text_editor()->get_line_height());
|
||||
Ref<Font> code_font = get_theme_font("font", "CodeEdit");
|
||||
inline_color_options->add_theme_font_override("font", code_font);
|
||||
inline_color_options->get_popup()->add_theme_font_override("font", code_font);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
@@ -2574,6 +2806,23 @@ ScriptTextEditor::ScriptTextEditor() {
|
||||
bookmarks_menu = memnew(PopupMenu);
|
||||
breakpoints_menu = memnew(PopupMenu);
|
||||
|
||||
inline_color_popup = memnew(PopupPanel);
|
||||
add_child(inline_color_popup);
|
||||
|
||||
inline_color_picker = memnew(ColorPicker);
|
||||
inline_color_picker->set_mouse_filter(MOUSE_FILTER_STOP);
|
||||
inline_color_picker->set_deferred_mode(true);
|
||||
inline_color_picker->set_hex_visible(false);
|
||||
inline_color_picker->connect("color_changed", callable_mp(this, &ScriptTextEditor::_picker_color_changed));
|
||||
inline_color_popup->add_child(inline_color_picker);
|
||||
|
||||
inline_color_options = memnew(OptionButton);
|
||||
inline_color_options->set_h_size_flags(SIZE_FILL);
|
||||
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);
|
||||
|
||||
connection_info_dialog = memnew(ConnectionInfoDialog);
|
||||
|
||||
SET_DRAG_FORWARDING_GCD(code_editor->get_text_editor(), ScriptTextEditor);
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "editor/code_editor.h"
|
||||
#include "scene/gui/color_picker.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/option_button.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class RichTextLabel;
|
||||
@@ -84,6 +85,14 @@ class ScriptTextEditor : public ScriptEditorBase {
|
||||
PopupMenu *highlighter_menu = nullptr;
|
||||
PopupMenu *context_menu = nullptr;
|
||||
|
||||
int inline_color_line = -1;
|
||||
int inline_color_start = -1;
|
||||
int inline_color_end = -1;
|
||||
PopupPanel *inline_color_popup = nullptr;
|
||||
ColorPicker *inline_color_picker = nullptr;
|
||||
OptionButton *inline_color_options = nullptr;
|
||||
Ref<Texture2D> color_alpha_texture;
|
||||
|
||||
GotoLinePopup *goto_line_popup = nullptr;
|
||||
ScriptEditorQuickOpen *quick_open = nullptr;
|
||||
ConnectionInfoDialog *connection_info_dialog = nullptr;
|
||||
@@ -160,6 +169,16 @@ class ScriptTextEditor : public ScriptEditorBase {
|
||||
EDIT_EMOJI_AND_SYMBOL,
|
||||
};
|
||||
|
||||
enum COLOR_MODE {
|
||||
MODE_RGB,
|
||||
MODE_STRING,
|
||||
MODE_HSV,
|
||||
MODE_OKHSL,
|
||||
MODE_RGB8,
|
||||
MODE_HEX,
|
||||
MODE_MAX
|
||||
};
|
||||
|
||||
void _enable_code_editor();
|
||||
|
||||
protected:
|
||||
@@ -185,6 +204,15 @@ protected:
|
||||
void _error_clicked(const Variant &p_line);
|
||||
void _warning_clicked(const Variant &p_line);
|
||||
|
||||
bool _is_valid_color_info(const Dictionary &p_info);
|
||||
Array _inline_object_parse(const String &p_text, int p_line);
|
||||
void _inline_object_draw(const Dictionary &p_info, const Rect2 &p_rect);
|
||||
void _inline_object_handle_click(const Dictionary &p_info, const Rect2 &p_rect);
|
||||
String _picker_color_stringify(const Color &p_color, COLOR_MODE p_mode);
|
||||
void _picker_color_changed(const Color &p_color);
|
||||
void _update_color_constructor_options();
|
||||
void _update_color_text();
|
||||
|
||||
void _notification(int p_what);
|
||||
|
||||
HashMap<String, Ref<EditorSyntaxHighlighter>> highlighters;
|
||||
|
||||
@@ -5204,7 +5204,8 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S
|
||||
int32_t bidi_run_end = _convert_pos(p_sd, ov_start + start + _bidi_run_start + _bidi_run_length);
|
||||
|
||||
for (int j = 0; j < sd_size; j++) {
|
||||
if ((sd_glyphs[j].start >= bidi_run_start) && (sd_glyphs[j].end <= bidi_run_end)) {
|
||||
int col_key_off = (sd_glyphs[j].start == sd_glyphs[j].end) ? 1 : 0;
|
||||
if ((sd_glyphs[j].start >= bidi_run_start) && (sd_glyphs[j].end <= bidi_run_end - col_key_off)) {
|
||||
// Copy glyphs.
|
||||
Glyph gl = sd_glyphs[j];
|
||||
if (gl.span_index >= 0) {
|
||||
@@ -5994,7 +5995,11 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
|
||||
while (i < span_size) {
|
||||
String language = sd->spans[i].language;
|
||||
int r_start = sd->spans[i].start;
|
||||
while (i + 1 < span_size && language == sd->spans[i + 1].language) {
|
||||
if (r_start == sd->spans[i].end) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
while (i + 1 < span_size && (language == sd->spans[i + 1].language || sd->spans[i + 1].start == sd->spans[i + 1].end)) {
|
||||
i++;
|
||||
}
|
||||
int r_end = sd->spans[i].end;
|
||||
@@ -6122,6 +6127,11 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Do not add extra space for color picker object.
|
||||
if (((sd_glyphs[i].flags & GRAPHEME_IS_EMBEDDED_OBJECT) == GRAPHEME_IS_EMBEDDED_OBJECT && sd_glyphs[i].start == sd_glyphs[i].end) || (uint32_t(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)) {
|
||||
i += (sd_glyphs[i].count - 1);
|
||||
continue;
|
||||
}
|
||||
Glyph gl;
|
||||
gl.span_index = sd_glyphs[i].span_index;
|
||||
gl.start = sd_glyphs[i].start;
|
||||
@@ -6991,7 +7001,8 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) {
|
||||
|
||||
for (int k = spn_from; k != spn_to; k += spn_delta) {
|
||||
const ShapedTextDataAdvanced::Span &span = sd->spans[k];
|
||||
if (span.start - sd->start >= script_run_end || span.end - sd->start <= script_run_start) {
|
||||
int col_key_off = (span.start == span.end) ? 1 : 0;
|
||||
if (span.start - sd->start >= script_run_end || span.end - sd->start <= script_run_start - col_key_off) {
|
||||
continue;
|
||||
}
|
||||
if (span.embedded_key != Variant()) {
|
||||
|
||||
@@ -105,6 +105,12 @@ void TextEdit::Text::set_draw_control_chars(bool p_enabled) {
|
||||
is_dirty = true;
|
||||
}
|
||||
|
||||
void TextEdit::Text::set_inline_object_parser(const Callable &p_parser) {
|
||||
inline_object_parser = p_parser;
|
||||
is_dirty = true;
|
||||
invalidate_all();
|
||||
}
|
||||
|
||||
int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const {
|
||||
ERR_FAIL_INDEX_V(p_line, text.size(), 0);
|
||||
if (p_wrap_index != -1) {
|
||||
@@ -244,6 +250,17 @@ void TextEdit::Text::update_accessibility(int p_line, RID p_root) {
|
||||
}
|
||||
}
|
||||
|
||||
inline bool is_inline_info_valid(const Variant &p_info) {
|
||||
if (p_info.get_type() != Variant::DICTIONARY) {
|
||||
return false;
|
||||
}
|
||||
Dictionary info = p_info;
|
||||
if (!info.get_valid("column").is_num() || !info.get_valid("line").is_num() || !info.get_valid("width_ratio").is_num()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextEdit::Text::invalidate_cache(int p_line, bool p_text_changed) {
|
||||
ERR_FAIL_INDEX(p_line, text.size());
|
||||
|
||||
@@ -280,7 +297,41 @@ void TextEdit::Text::invalidate_cache(int p_line, bool p_text_changed) {
|
||||
const Array &bidi_override_with_ime = (!text_line.ime_data.is_empty()) ? text_line.ime_bidi_override : text_line.bidi_override;
|
||||
|
||||
if (p_text_changed) {
|
||||
text_line.data_buf->add_string(text_with_ime, font, font_size, language);
|
||||
int from = 0;
|
||||
if (inline_object_parser.is_valid()) {
|
||||
// Insert inline object.
|
||||
Variant parsed_result = inline_object_parser.call(text_with_ime, p_line);
|
||||
if (parsed_result.is_array()) {
|
||||
Array object_infos = parsed_result;
|
||||
for (Variant val : object_infos) {
|
||||
if (!is_inline_info_valid(val)) {
|
||||
continue;
|
||||
}
|
||||
Dictionary info = val;
|
||||
int start = info["column"];
|
||||
float width_ratio = info["width_ratio"];
|
||||
String left_string = text_with_ime.substr(from, start - from);
|
||||
text_line.data_buf->add_string(left_string, font, font_size, language);
|
||||
text_line.data_buf->add_object(info, Vector2(font_height * width_ratio, font_height), INLINE_ALIGNMENT_TOP, 0);
|
||||
from = start;
|
||||
}
|
||||
}
|
||||
}
|
||||
String remaining_string = text_with_ime.substr(from);
|
||||
text_line.data_buf->add_string(remaining_string, font, font_size, language);
|
||||
|
||||
} else {
|
||||
// Update inline object sizes.
|
||||
for (int i = 0; i < text_line.data_buf->get_line_count(); i++) {
|
||||
for (Variant key : text_line.data_buf->get_line_objects(i)) {
|
||||
if (!is_inline_info_valid(key)) {
|
||||
continue;
|
||||
}
|
||||
Dictionary info = key;
|
||||
float width_ratio = info["width_ratio"];
|
||||
text_line.data_buf->resize_object(info, Vector2(font_height * width_ratio, font_height), INLINE_ALIGNMENT_TOP, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!bidi_override_with_ime.is_empty()) {
|
||||
TS->shaped_text_set_bidi_override(text_line.data_buf->get_rid(), bidi_override_with_ime);
|
||||
@@ -1434,6 +1485,17 @@ void TextEdit::_notification(int p_what) {
|
||||
char_margin += wrap_indent;
|
||||
}
|
||||
|
||||
// Validate inline objects.
|
||||
Vector<Dictionary> object_keys;
|
||||
if (inline_object_drawer.is_valid()) {
|
||||
for (Variant k : ldata->get_line_objects(line_wrap_index)) {
|
||||
if (!is_inline_info_valid(k)) {
|
||||
continue;
|
||||
}
|
||||
object_keys.push_back(k);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw selections.
|
||||
float char_w = theme_cache.font->get_char_size(' ', theme_cache.font_size).width;
|
||||
for (int c = 0; c < get_caret_count(); c++) {
|
||||
@@ -1451,9 +1513,17 @@ void TextEdit::_notification(int p_what) {
|
||||
sel.push_back(Vector2(line_end, line_end + char_w));
|
||||
}
|
||||
}
|
||||
// Show selection for inline objects.
|
||||
for (Dictionary info : object_keys) {
|
||||
int info_column = info["column"];
|
||||
if (info_column >= sel_from && info_column < sel_to) {
|
||||
Rect2 orect = TS->shaped_text_get_object_rect(rid, info);
|
||||
sel.push_back(Vector2(orect.position.x, orect.position.x + orect.size.x));
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < sel.size(); j++) {
|
||||
Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, Math::ceil(sel[j].y) - sel[j].x, row_height);
|
||||
Rect2 rect = Rect2(Math::ceil(sel[j].x) + char_margin + ofs_x, ofs_y, Math::ceil(sel[j].y) - Math::ceil(sel[j].x), row_height);
|
||||
if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) {
|
||||
continue;
|
||||
}
|
||||
@@ -1572,6 +1642,16 @@ void TextEdit::_notification(int p_what) {
|
||||
}
|
||||
char_ofs = 0;
|
||||
}
|
||||
|
||||
// Draw inline objects.
|
||||
for (Dictionary k : object_keys) {
|
||||
Rect2 col_rect = TS->shaped_text_get_object_rect(rid, k);
|
||||
col_rect.position += Vector2(char_margin + ofs_x, ofs_y);
|
||||
if (!clipped && (col_rect.position.x) >= xmargin_beg && (col_rect.position.x + col_rect.size.x) <= xmargin_end) {
|
||||
inline_object_drawer.call(k, col_rect);
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < gl_size; j++) {
|
||||
for (const Pair<int64_t, Color> &color_data : color_map) {
|
||||
if (color_data.first <= glyphs[j].start) {
|
||||
@@ -2294,6 +2374,37 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) {
|
||||
last_dblclk = OS::get_singleton()->get_ticks_msec();
|
||||
last_dblclk_pos = mb->get_position();
|
||||
}
|
||||
|
||||
// Click inline objects.
|
||||
if (inline_object_click_handler.is_valid()) {
|
||||
int xmargin_beg = Math::ceil(theme_cache.style_normal->get_margin(SIDE_LEFT)) + gutters_width + gutter_padding;
|
||||
int wrap_i = get_line_wrap_index_at_column(pos.y, pos.x);
|
||||
int first_indent_line = 0;
|
||||
if (text.is_indent_wrapped_lines()) {
|
||||
_get_wrapped_indent_level(pos.y, first_indent_line);
|
||||
}
|
||||
float wrap_indent = wrap_i > first_indent_line ? MIN(text.get_indent_offset(pos.y, is_layout_rtl()), wrap_at_column * 0.6) : 0.0;
|
||||
|
||||
Ref<TextParagraph> ldata = text.get_line_data(line);
|
||||
for (Variant k : ldata->get_line_objects(wrap_i)) {
|
||||
if (!is_inline_info_valid(k)) {
|
||||
continue;
|
||||
}
|
||||
Dictionary info = k;
|
||||
Rect2 obj_rect = ldata->get_line_object_rect(wrap_i, k);
|
||||
obj_rect.position.x += xmargin_beg + wrap_indent - first_visible_col;
|
||||
|
||||
if (mpos.x > obj_rect.position.x && mpos.x < obj_rect.get_end().x) {
|
||||
Rect2 col_rect = get_rect_at_line_column(line, col);
|
||||
col_rect.position += get_screen_position() + Vector2(col_rect.size.x, 0);
|
||||
col_rect.size = obj_rect.size;
|
||||
set_selection_mode(TextEdit::SelectionMode::SELECTION_MODE_NONE);
|
||||
inline_object_click_handler.call(info, col_rect);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queue_accessibility_update();
|
||||
queue_redraw();
|
||||
}
|
||||
@@ -3487,6 +3598,31 @@ Control::CursorShape TextEdit::get_cursor_shape(const Point2 &p_pos) const {
|
||||
if (draw_minimap && p_pos.x > xmargin_end - minimap_width && p_pos.x <= xmargin_end) {
|
||||
return CURSOR_ARROW;
|
||||
}
|
||||
|
||||
// Hover inline objects.
|
||||
if (inline_object_click_handler.is_valid()) {
|
||||
Point2i pos = get_line_column_at_pos(p_pos);
|
||||
int xmargin_beg = Math::ceil(theme_cache.style_normal->get_margin(SIDE_LEFT)) + gutters_width + gutter_padding;
|
||||
int wrap_i = get_line_wrap_index_at_column(pos.y, pos.x);
|
||||
int first_indent_line = 0;
|
||||
if (text.is_indent_wrapped_lines()) {
|
||||
_get_wrapped_indent_level(pos.y, first_indent_line);
|
||||
}
|
||||
float wrap_indent = wrap_i > first_indent_line ? MIN(text.get_indent_offset(pos.y, is_layout_rtl()), wrap_at_column * 0.6) : 0.0;
|
||||
|
||||
Ref<TextParagraph> ldata = text.get_line_data(pos.y);
|
||||
for (Variant k : ldata->get_line_objects(wrap_i)) {
|
||||
if (!is_inline_info_valid(k)) {
|
||||
continue;
|
||||
}
|
||||
Rect2 obj_rect = ldata->get_line_object_rect(wrap_i, k);
|
||||
obj_rect.position.x += xmargin_beg + wrap_indent - first_visible_col;
|
||||
if (p_pos.x > obj_rect.position.x && p_pos.x < obj_rect.get_end().x) {
|
||||
return CURSOR_POINTING_HAND;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return get_default_cursor_shape();
|
||||
}
|
||||
|
||||
@@ -4287,6 +4423,12 @@ Point2i TextEdit::get_next_visible_line_index_offset_from(int p_line_from, int p
|
||||
return Point2i(num_total, wrap_index);
|
||||
}
|
||||
|
||||
void TextEdit::set_inline_object_handlers(const Callable &p_parser, const Callable &p_drawer, const Callable &p_click_handler) {
|
||||
inline_object_drawer = p_drawer;
|
||||
inline_object_click_handler = p_click_handler;
|
||||
text.set_inline_object_parser(p_parser);
|
||||
}
|
||||
|
||||
// Overridable actions
|
||||
void TextEdit::handle_unicode_input(const uint32_t p_unicode, int p_caret) {
|
||||
if (GDVIRTUAL_CALL(_handle_unicode_input, p_unicode, p_caret)) {
|
||||
|
||||
@@ -185,6 +185,7 @@ private:
|
||||
String custom_word_separators;
|
||||
bool use_default_word_separators = true;
|
||||
bool use_custom_word_separators = false;
|
||||
Callable inline_object_parser;
|
||||
|
||||
mutable bool max_line_width_dirty = true;
|
||||
mutable bool max_line_height_dirty = true;
|
||||
@@ -207,6 +208,7 @@ private:
|
||||
void set_font_size(int p_font_size);
|
||||
void set_direction_and_language(TextServer::Direction p_direction, const String &p_language);
|
||||
void set_draw_control_chars(bool p_enabled);
|
||||
void set_inline_object_parser(const Callable &p_parser);
|
||||
|
||||
int get_line_height() const;
|
||||
int get_line_width(int p_line, int p_wrap_index = -1) const;
|
||||
@@ -355,6 +357,9 @@ private:
|
||||
PopupMenu *menu_dir = nullptr;
|
||||
PopupMenu *menu_ctl = nullptr;
|
||||
|
||||
Callable inline_object_drawer;
|
||||
Callable inline_object_click_handler;
|
||||
|
||||
Key _get_menu_action_accelerator(const String &p_action);
|
||||
void _generate_context_menu();
|
||||
void _update_context_menu();
|
||||
@@ -867,6 +872,8 @@ public:
|
||||
int get_next_visible_line_offset_from(int p_line_from, int p_visible_amount) const;
|
||||
Point2i get_next_visible_line_index_offset_from(int p_line_from, int p_wrap_index_from, int p_visible_amount) const;
|
||||
|
||||
void set_inline_object_handlers(const Callable &p_parser, const Callable &p_drawer, const Callable &p_click_handler);
|
||||
|
||||
// Overridable actions
|
||||
void handle_unicode_input(const uint32_t p_unicode, int p_caret = -1);
|
||||
void backspace(int p_caret = -1);
|
||||
|
||||
@@ -1238,6 +1238,7 @@ CaretInfo TextServer::shaped_text_get_carets(const RID &p_shaped, int64_t p_posi
|
||||
real_t height = (ascent + descent) / 2;
|
||||
|
||||
real_t off = 0.0f;
|
||||
real_t obj_off = -1.0f;
|
||||
CaretInfo caret;
|
||||
caret.l_dir = DIRECTION_AUTO;
|
||||
caret.t_dir = DIRECTION_AUTO;
|
||||
@@ -1247,6 +1248,11 @@ CaretInfo TextServer::shaped_text_get_carets(const RID &p_shaped, int64_t p_posi
|
||||
|
||||
for (int i = 0; i < v_size; i++) {
|
||||
if (glyphs[i].count > 0) {
|
||||
// Skip inline objects.
|
||||
if ((glyphs[i].flags & GRAPHEME_IS_EMBEDDED_OBJECT) == GRAPHEME_IS_EMBEDDED_OBJECT && glyphs[i].start == glyphs[i].end) {
|
||||
obj_off = glyphs[i].advance;
|
||||
continue;
|
||||
}
|
||||
// Caret before grapheme (top / left).
|
||||
if (p_position == glyphs[i].start && ((glyphs[i].flags & GRAPHEME_IS_VIRTUAL) != GRAPHEME_IS_VIRTUAL)) {
|
||||
real_t advance = 0.f;
|
||||
@@ -1335,6 +1341,7 @@ CaretInfo TextServer::shaped_text_get_carets(const RID &p_shaped, int64_t p_posi
|
||||
cr.size.y = char_adv;
|
||||
}
|
||||
}
|
||||
cr.position.x += MAX(0.0, obj_off); // Prevent split caret when on an inline object.
|
||||
caret.l_caret = cr;
|
||||
}
|
||||
// Caret inside grapheme (middle).
|
||||
@@ -1371,6 +1378,10 @@ CaretInfo TextServer::shaped_text_get_carets(const RID &p_shaped, int64_t p_posi
|
||||
}
|
||||
}
|
||||
off += glyphs[i].advance * glyphs[i].repeat;
|
||||
if (obj_off >= 0.0) {
|
||||
off += obj_off;
|
||||
obj_off = -1.0;
|
||||
}
|
||||
}
|
||||
return caret;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user