Merge pull request #60463 from Geometror/improve-vs-1
This commit is contained in:
commit
df2de05c5f
9 changed files with 248 additions and 57 deletions
|
@ -119,6 +119,13 @@
|
|||
Returns the right (output) type of the slot [code]idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_slot_draw_stylebox" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="idx" type="int" />
|
||||
<description>
|
||||
Returns true if the background [StyleBox] of the slot [code]idx[/code] is drawn.
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_slot_enabled_left" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="idx" type="int" />
|
||||
|
@ -152,6 +159,7 @@
|
|||
<argument index="6" name="color_right" type="Color" />
|
||||
<argument index="7" name="custom_left" type="Texture2D" default="null" />
|
||||
<argument index="8" name="custom_right" type="Texture2D" default="null" />
|
||||
<argument index="9" name="enable" type="bool" default="true" />
|
||||
<description>
|
||||
Sets properties of the slot with ID [code]idx[/code].
|
||||
If [code]enable_left[/code]/[code]right[/code], a port will appear and the slot will be able to be connected from this side.
|
||||
|
@ -178,6 +186,14 @@
|
|||
Sets the [Color] of the right (output) side of the slot [code]idx[/code] to [code]color_right[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_slot_draw_stylebox">
|
||||
<return type="void" />
|
||||
<argument index="0" name="idx" type="int" />
|
||||
<argument index="1" name="draw_stylebox" type="bool" />
|
||||
<description>
|
||||
Toggles the background [StyleBox] of the slot [code]idx[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="set_slot_enabled_left">
|
||||
<return type="void" />
|
||||
<argument index="0" name="idx" type="int" />
|
||||
|
@ -301,6 +317,8 @@
|
|||
<theme_item name="title_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)">
|
||||
Color of the title text.
|
||||
</theme_item>
|
||||
<theme_item name="close_h_offset" data_type="constant" type="int" default="22">
|
||||
</theme_item>
|
||||
<theme_item name="close_offset" data_type="constant" type="int" default="22">
|
||||
The vertical offset of the close button.
|
||||
</theme_item>
|
||||
|
@ -343,5 +361,8 @@
|
|||
<theme_item name="selected_frame" data_type="style" type="StyleBox">
|
||||
The background used when the [GraphNode] is selected.
|
||||
</theme_item>
|
||||
<theme_item name="slot" data_type="style" type="StyleBox">
|
||||
The [StyleBox] used for each slot of the [GraphNode].
|
||||
</theme_item>
|
||||
</theme_items>
|
||||
</class>
|
||||
|
|
|
@ -110,6 +110,32 @@
|
|||
m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \
|
||||
MAKE_FALLBACKS(m_name);
|
||||
|
||||
#define MAKE_DEFAULT_FONT_MSDF(m_name, m_variations) \
|
||||
Ref<Font> m_name; \
|
||||
m_name.instantiate(); \
|
||||
if (CustomFont.is_valid()) { \
|
||||
m_name->add_data(CustomFontMSDF); \
|
||||
m_name->add_data(DefaultFontMSDF); \
|
||||
} else { \
|
||||
m_name->add_data(DefaultFontMSDF); \
|
||||
} \
|
||||
{ \
|
||||
Dictionary variations; \
|
||||
if (!m_variations.is_empty()) { \
|
||||
Vector<String> variation_tags = m_variations.split(","); \
|
||||
for (int i = 0; i < variation_tags.size(); i++) { \
|
||||
Vector<String> tokens = variation_tags[i].split("="); \
|
||||
if (tokens.size() == 2) { \
|
||||
variations[tokens[0]] = tokens[1].to_float(); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
m_name->set_variation_coordinates(variations); \
|
||||
} \
|
||||
m_name->set_spacing(TextServer::SPACING_TOP, -EDSCALE); \
|
||||
m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \
|
||||
MAKE_FALLBACKS(m_name);
|
||||
|
||||
#define MAKE_SLANTED_FONT(m_name, m_variations) \
|
||||
Ref<Font> m_name; \
|
||||
m_name.instantiate(); \
|
||||
|
@ -163,6 +189,32 @@
|
|||
m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \
|
||||
MAKE_FALLBACKS_BOLD(m_name);
|
||||
|
||||
#define MAKE_BOLD_FONT_MSDF(m_name, m_variations) \
|
||||
Ref<Font> m_name; \
|
||||
m_name.instantiate(); \
|
||||
if (CustomFontBold.is_valid()) { \
|
||||
m_name->add_data(CustomFontBoldMSDF); \
|
||||
m_name->add_data(DefaultFontBoldMSDF); \
|
||||
} else { \
|
||||
m_name->add_data(DefaultFontBoldMSDF); \
|
||||
} \
|
||||
{ \
|
||||
Dictionary variations; \
|
||||
if (!m_variations.is_empty()) { \
|
||||
Vector<String> variation_tags = m_variations.split(","); \
|
||||
for (int i = 0; i < variation_tags.size(); i++) { \
|
||||
Vector<String> tokens = variation_tags[i].split("="); \
|
||||
if (tokens.size() == 2) { \
|
||||
variations[tokens[0]] = tokens[1].to_float(); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
m_name->set_variation_coordinates(variations); \
|
||||
} \
|
||||
m_name->set_spacing(TextServer::SPACING_TOP, -EDSCALE); \
|
||||
m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \
|
||||
MAKE_FALLBACKS_BOLD(m_name);
|
||||
|
||||
#define MAKE_SOURCE_FONT(m_name, m_variations) \
|
||||
Ref<Font> m_name; \
|
||||
m_name.instantiate(); \
|
||||
|
@ -189,13 +241,14 @@
|
|||
m_name->set_spacing(TextServer::SPACING_BOTTOM, -EDSCALE); \
|
||||
MAKE_FALLBACKS(m_name);
|
||||
|
||||
Ref<FontData> load_cached_external_font(const String &p_path, TextServer::Hinting p_hinting, bool p_aa, bool p_autohint, TextServer::SubpixelPositioning p_font_subpixel_positioning) {
|
||||
Ref<FontData> load_cached_external_font(const String &p_path, TextServer::Hinting p_hinting, bool p_aa, bool p_autohint, TextServer::SubpixelPositioning p_font_subpixel_positioning, bool p_msdf = false) {
|
||||
Ref<FontData> font;
|
||||
font.instantiate();
|
||||
|
||||
Vector<uint8_t> data = FileAccess::get_file_as_array(p_path);
|
||||
|
||||
font->set_data(data);
|
||||
font->set_multichannel_signed_distance_field(p_msdf);
|
||||
font->set_antialiased(p_aa);
|
||||
font->set_hinting(p_hinting);
|
||||
font->set_force_autohinter(p_autohint);
|
||||
|
@ -204,11 +257,12 @@ Ref<FontData> load_cached_external_font(const String &p_path, TextServer::Hintin
|
|||
return font;
|
||||
}
|
||||
|
||||
Ref<FontData> load_cached_internal_font(const uint8_t *p_data, size_t p_size, TextServer::Hinting p_hinting, bool p_aa, bool p_autohint, TextServer::SubpixelPositioning p_font_subpixel_positioning) {
|
||||
Ref<FontData> load_cached_internal_font(const uint8_t *p_data, size_t p_size, TextServer::Hinting p_hinting, bool p_aa, bool p_autohint, TextServer::SubpixelPositioning p_font_subpixel_positioning, bool p_msdf = false) {
|
||||
Ref<FontData> font;
|
||||
font.instantiate();
|
||||
|
||||
font->set_data_ptr(p_data, p_size);
|
||||
font->set_multichannel_signed_distance_field(p_msdf);
|
||||
font->set_antialiased(p_aa);
|
||||
font->set_hinting(p_hinting);
|
||||
font->set_force_autohinter(p_autohint);
|
||||
|
@ -261,6 +315,13 @@ void editor_register_fonts(Ref<Theme> p_theme) {
|
|||
EditorSettings::get_singleton()->set_manually("interface/editor/main_font", "");
|
||||
}
|
||||
|
||||
Ref<FontData> CustomFontMSDF;
|
||||
if (custom_font_path.length() > 0 && dir->file_exists(custom_font_path)) {
|
||||
CustomFontMSDF = load_cached_external_font(custom_font_path, font_hinting, font_antialiased, true, font_subpixel_positioning, true);
|
||||
} else {
|
||||
EditorSettings::get_singleton()->set_manually("interface/editor/main_font", "");
|
||||
}
|
||||
|
||||
Ref<FontData> CustomFontSlanted;
|
||||
if (CustomFont.is_valid()) {
|
||||
CustomFontSlanted = CustomFont->duplicate();
|
||||
|
@ -282,6 +343,13 @@ void editor_register_fonts(Ref<Theme> p_theme) {
|
|||
CustomFontBold->set_embolden(embolden_strength);
|
||||
}
|
||||
|
||||
Ref<FontData> CustomFontBoldMSDF;
|
||||
if (custom_font_path.length() > 0 && dir->file_exists(custom_font_path)) {
|
||||
CustomFontBoldMSDF = load_cached_external_font(custom_font_path, font_hinting, font_antialiased, true, font_subpixel_positioning, true);
|
||||
} else {
|
||||
EditorSettings::get_singleton()->set_manually("interface/editor/main_font_bold", "");
|
||||
}
|
||||
|
||||
/* Custom source code font */
|
||||
|
||||
String custom_font_path_source = EditorSettings::get_singleton()->get("interface/editor/code_font");
|
||||
|
@ -295,7 +363,9 @@ void editor_register_fonts(Ref<Theme> p_theme) {
|
|||
/* Noto Sans */
|
||||
|
||||
Ref<FontData> DefaultFont = load_cached_internal_font(_font_NotoSans_Regular, _font_NotoSans_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
|
||||
Ref<FontData> DefaultFontMSDF = load_cached_internal_font(_font_NotoSans_Regular, _font_NotoSans_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning, true);
|
||||
Ref<FontData> DefaultFontBold = load_cached_internal_font(_font_NotoSans_Bold, _font_NotoSans_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
|
||||
Ref<FontData> DefaultFontBoldMSDF = load_cached_internal_font(_font_NotoSans_Bold, _font_NotoSans_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning, true);
|
||||
Ref<FontData> FontArabic = load_cached_internal_font(_font_NotoNaskhArabicUI_Regular, _font_NotoNaskhArabicUI_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
|
||||
Ref<FontData> FontArabicBold = load_cached_internal_font(_font_NotoNaskhArabicUI_Bold, _font_NotoNaskhArabicUI_Bold_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
|
||||
Ref<FontData> FontBengali = load_cached_internal_font(_font_NotoSansBengaliUI_Regular, _font_NotoSansBengaliUI_Regular_size, font_hinting, font_antialiased, true, font_subpixel_positioning);
|
||||
|
@ -347,12 +417,18 @@ void editor_register_fonts(Ref<Theme> p_theme) {
|
|||
p_theme->set_font_size("main_size", "EditorFonts", default_font_size);
|
||||
p_theme->set_font("main", "EditorFonts", df);
|
||||
|
||||
MAKE_DEFAULT_FONT_MSDF(df_msdf, String());
|
||||
p_theme->set_font("main_msdf", "EditorFonts", df_msdf);
|
||||
|
||||
// Bold font
|
||||
MAKE_BOLD_FONT(df_bold, String());
|
||||
MAKE_SLANTED_FONT(df_italic, String());
|
||||
p_theme->set_font_size("bold_size", "EditorFonts", default_font_size);
|
||||
p_theme->set_font("bold", "EditorFonts", df_bold);
|
||||
|
||||
MAKE_BOLD_FONT_MSDF(df_bold_msdf, String());
|
||||
p_theme->set_font("main_bold_msdf", "EditorFonts", df_bold_msdf);
|
||||
|
||||
// Title font
|
||||
p_theme->set_font_size("title_size", "EditorFonts", default_font_size + 1 * EDSCALE);
|
||||
p_theme->set_font("title", "EditorFonts", df_bold);
|
||||
|
|
|
@ -681,6 +681,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
|
|||
|
||||
// Visual editors
|
||||
EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/visual_editors/minimap_opacity", 0.85, "0.0,1.0,0.01")
|
||||
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "editors/visual_editors/visualshader/port_preview_size", 160, "100,400,0.01")
|
||||
|
||||
/* Run */
|
||||
|
||||
|
|
|
@ -741,17 +741,28 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
|
|||
theme->set_stylebox("pressed", "EditorLogFilterButton", editor_log_button_pressed);
|
||||
|
||||
// OptionButton
|
||||
theme->set_stylebox("focus", "OptionButton", style_widget_focus);
|
||||
Ref<StyleBoxFlat> style_option_button_focus = style_widget_focus->duplicate();
|
||||
Ref<StyleBoxFlat> style_option_button_normal = style_widget->duplicate();
|
||||
Ref<StyleBoxFlat> style_option_button_hover = style_widget_hover->duplicate();
|
||||
Ref<StyleBoxFlat> style_option_button_pressed = style_widget_pressed->duplicate();
|
||||
Ref<StyleBoxFlat> style_option_button_disabled = style_widget_disabled->duplicate();
|
||||
|
||||
style_option_button_focus->set_default_margin(SIDE_RIGHT, 4 * EDSCALE);
|
||||
style_option_button_normal->set_default_margin(SIDE_RIGHT, 4 * EDSCALE);
|
||||
style_option_button_hover->set_default_margin(SIDE_RIGHT, 4 * EDSCALE);
|
||||
style_option_button_pressed->set_default_margin(SIDE_RIGHT, 4 * EDSCALE);
|
||||
style_option_button_disabled->set_default_margin(SIDE_RIGHT, 4 * EDSCALE);
|
||||
|
||||
theme->set_stylebox("focus", "OptionButton", style_option_button_focus);
|
||||
theme->set_stylebox("normal", "OptionButton", style_widget);
|
||||
theme->set_stylebox("hover", "OptionButton", style_widget_hover);
|
||||
theme->set_stylebox("pressed", "OptionButton", style_widget_pressed);
|
||||
theme->set_stylebox("disabled", "OptionButton", style_widget_disabled);
|
||||
|
||||
theme->set_stylebox("normal_mirrored", "OptionButton", style_widget);
|
||||
theme->set_stylebox("hover_mirrored", "OptionButton", style_widget_hover);
|
||||
theme->set_stylebox("pressed_mirrored", "OptionButton", style_widget_pressed);
|
||||
theme->set_stylebox("disabled_mirrored", "OptionButton", style_widget_disabled);
|
||||
theme->set_stylebox("normal_mirrored", "OptionButton", style_option_button_normal);
|
||||
theme->set_stylebox("hover_mirrored", "OptionButton", style_option_button_hover);
|
||||
theme->set_stylebox("pressed_mirrored", "OptionButton", style_option_button_pressed);
|
||||
theme->set_stylebox("disabled_mirrored", "OptionButton", style_option_button_disabled);
|
||||
|
||||
theme->set_color("font_color", "OptionButton", font_color);
|
||||
theme->set_color("font_hover_color", "OptionButton", font_hover_color);
|
||||
|
@ -1435,7 +1446,6 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
|
|||
style_minimap_node = make_flat_stylebox(Color(0, 0, 0), 0, 0, 0, 0);
|
||||
}
|
||||
style_minimap_camera->set_border_width_all(1);
|
||||
style_minimap_node->set_corner_radius_all(1);
|
||||
theme->set_stylebox("camera", "GraphEditMinimap", style_minimap_camera);
|
||||
theme->set_stylebox("node", "GraphEditMinimap", style_minimap_node);
|
||||
|
||||
|
@ -1450,20 +1460,26 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
|
|||
theme->set_color("resizer_color", "GraphEditMinimap", minimap_resizer_color);
|
||||
|
||||
// GraphNode
|
||||
const int gn_margin_side = 28;
|
||||
const int gn_margin_side = 2;
|
||||
const int gn_margin_bottom = 2;
|
||||
|
||||
Ref<StyleBoxFlat> graphsb = make_flat_stylebox(dark_color_3 * Color(1, 1, 1, 0.7), gn_margin_side, 24, gn_margin_side, 5, corner_width);
|
||||
Color graphnode_bg = dark_color_3;
|
||||
if (!dark_theme) {
|
||||
graphnode_bg = prop_section_color;
|
||||
}
|
||||
|
||||
Ref<StyleBoxFlat> graphsb = make_flat_stylebox(graphnode_bg.lerp(style_tree_bg->get_bg_color(), 0.3), gn_margin_side, 24, gn_margin_side, gn_margin_bottom, corner_width);
|
||||
graphsb->set_border_width_all(border_width);
|
||||
graphsb->set_border_color(dark_color_3);
|
||||
Ref<StyleBoxFlat> graphsbselected = make_flat_stylebox(dark_color_3 * Color(1, 1, 1, 0.9), gn_margin_side, 24, gn_margin_side, 5, corner_width);
|
||||
graphsb->set_border_color(graphnode_bg);
|
||||
Ref<StyleBoxFlat> graphsbselected = make_flat_stylebox(graphnode_bg * Color(1, 1, 1, 1), gn_margin_side, 24, gn_margin_side, gn_margin_bottom, corner_width);
|
||||
graphsbselected->set_border_width_all(2 * EDSCALE + border_width);
|
||||
graphsbselected->set_border_color(Color(accent_color.r, accent_color.g, accent_color.b, 0.9));
|
||||
Ref<StyleBoxFlat> graphsbcomment = make_flat_stylebox(dark_color_3 * Color(1, 1, 1, 0.3), gn_margin_side, 24, gn_margin_side, 5, corner_width);
|
||||
graphsbselected->set_border_color(Color(accent_color.r, accent_color.g, accent_color.b, 0.6));
|
||||
Ref<StyleBoxFlat> graphsbcomment = make_flat_stylebox(graphnode_bg * Color(1, 1, 1, 0.3), gn_margin_side, 24, gn_margin_side, gn_margin_bottom, corner_width);
|
||||
graphsbcomment->set_border_width_all(border_width);
|
||||
graphsbcomment->set_border_color(dark_color_3);
|
||||
Ref<StyleBoxFlat> graphsbcommentselected = make_flat_stylebox(dark_color_3 * Color(1, 1, 1, 0.4), gn_margin_side, 24, gn_margin_side, 5, corner_width);
|
||||
graphsbcomment->set_border_color(graphnode_bg);
|
||||
Ref<StyleBoxFlat> graphsbcommentselected = make_flat_stylebox(graphnode_bg * Color(1, 1, 1, 0.4), gn_margin_side, 24, gn_margin_side, gn_margin_bottom, corner_width);
|
||||
graphsbcommentselected->set_border_width_all(border_width);
|
||||
graphsbcommentselected->set_border_color(dark_color_3);
|
||||
graphsbcommentselected->set_border_color(graphnode_bg);
|
||||
Ref<StyleBoxFlat> graphsbbreakpoint = graphsbselected->duplicate();
|
||||
graphsbbreakpoint->set_draw_center(false);
|
||||
graphsbbreakpoint->set_border_color(warning_color);
|
||||
|
@ -1472,10 +1488,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
|
|||
graphsbposition->set_draw_center(false);
|
||||
graphsbposition->set_border_color(error_color);
|
||||
graphsbposition->set_shadow_color(error_color * Color(1.0, 1.0, 1.0, 0.2));
|
||||
Ref<StyleBoxFlat> smgraphsb = make_flat_stylebox(dark_color_3 * Color(1, 1, 1, 0.7), gn_margin_side, 24, gn_margin_side, 5, corner_width);
|
||||
Ref<StyleBoxEmpty> graphsbslot = make_empty_stylebox(12, 0, 12, 0);
|
||||
Ref<StyleBoxFlat> smgraphsb = make_flat_stylebox(dark_color_3 * Color(1, 1, 1, 0.7), gn_margin_side, 24, gn_margin_side, gn_margin_bottom, corner_width);
|
||||
smgraphsb->set_border_width_all(border_width);
|
||||
smgraphsb->set_border_color(dark_color_3);
|
||||
Ref<StyleBoxFlat> smgraphsbselected = make_flat_stylebox(dark_color_3 * Color(1, 1, 1, 0.9), gn_margin_side, 24, gn_margin_side, 5, corner_width);
|
||||
smgraphsb->set_border_color(graphnode_bg);
|
||||
Ref<StyleBoxFlat> smgraphsbselected = make_flat_stylebox(graphnode_bg * Color(1, 1, 1, 0.9), gn_margin_side, 24, gn_margin_side, gn_margin_bottom, corner_width);
|
||||
smgraphsbselected->set_border_width_all(2 * EDSCALE + border_width);
|
||||
smgraphsbselected->set_border_color(Color(accent_color.r, accent_color.g, accent_color.b, 0.9));
|
||||
smgraphsbselected->set_shadow_size(8 * EDSCALE);
|
||||
|
@ -1492,19 +1509,20 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
|
|||
theme->set_stylebox("comment_focus", "GraphNode", graphsbcommentselected);
|
||||
theme->set_stylebox("breakpoint", "GraphNode", graphsbbreakpoint);
|
||||
theme->set_stylebox("position", "GraphNode", graphsbposition);
|
||||
theme->set_stylebox("slot", "GraphNode", graphsbslot);
|
||||
theme->set_stylebox("state_machine_frame", "GraphNode", smgraphsb);
|
||||
theme->set_stylebox("state_machine_selected_frame", "GraphNode", smgraphsbselected);
|
||||
|
||||
Color default_node_color = dark_color_1.inverted();
|
||||
theme->set_color("title_color", "GraphNode", default_node_color);
|
||||
default_node_color.a = 0.7;
|
||||
theme->set_color("close_color", "GraphNode", default_node_color);
|
||||
theme->set_color("resizer_color", "GraphNode", default_node_color);
|
||||
Color node_decoration_color = dark_color_1.inverted();
|
||||
theme->set_color("title_color", "GraphNode", node_decoration_color);
|
||||
node_decoration_color.a = 0.7;
|
||||
theme->set_color("close_color", "GraphNode", node_decoration_color);
|
||||
theme->set_color("resizer_color", "GraphNode", node_decoration_color);
|
||||
|
||||
theme->set_constant("port_offset", "GraphNode", 14 * EDSCALE);
|
||||
theme->set_constant("title_h_offset", "GraphNode", -16 * EDSCALE);
|
||||
theme->set_constant("title_offset", "GraphNode", 20 * EDSCALE);
|
||||
theme->set_constant("close_h_offset", "GraphNode", 20 * EDSCALE);
|
||||
theme->set_constant("port_offset", "GraphNode", 0);
|
||||
theme->set_constant("title_h_offset", "GraphNode", 12 * EDSCALE);
|
||||
theme->set_constant("title_offset", "GraphNode", 21 * EDSCALE);
|
||||
theme->set_constant("close_h_offset", "GraphNode", -2 * EDSCALE);
|
||||
theme->set_constant("close_offset", "GraphNode", 20 * EDSCALE);
|
||||
theme->set_constant("separation", "GraphNode", 1 * EDSCALE);
|
||||
|
||||
|
@ -1512,6 +1530,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
|
|||
theme->set_icon("resizer", "GraphNode", theme->get_icon(SNAME("GuiResizer"), SNAME("EditorIcons")));
|
||||
theme->set_icon("port", "GraphNode", theme->get_icon(SNAME("GuiGraphNodePort"), SNAME("EditorIcons")));
|
||||
|
||||
theme->set_font("title_font", "GraphNode", theme->get_font(SNAME("main_bold_msdf"), SNAME("EditorFonts")));
|
||||
|
||||
// GridContainer
|
||||
theme->set_constant("v_separation", "GridContainer", Math::round(widget_default_margin.y - 2 * EDSCALE));
|
||||
|
||||
|
|
|
@ -148,6 +148,7 @@ void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p
|
|||
if (links[p_node_id].preview_pos != -1) {
|
||||
links[p_node_id].graph_node->move_child(vbox, links[p_node_id].preview_pos);
|
||||
}
|
||||
links[p_node_id].graph_node->set_slot_draw_stylebox(vbox->get_index(), false);
|
||||
|
||||
Control *offset = memnew(Control);
|
||||
offset->set_custom_minimum_size(Size2(0, 5 * EDSCALE));
|
||||
|
@ -386,6 +387,14 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
|
|||
"alpha"
|
||||
};
|
||||
|
||||
// Visual shader specific theme for MSDF font.
|
||||
Ref<Theme> vstheme;
|
||||
vstheme.instantiate();
|
||||
Ref<Font> label_font = EditorNode::get_singleton()->get_editor_theme()->get_font("main_msdf", "EditorFonts");
|
||||
vstheme->set_font("font", "Label", label_font);
|
||||
vstheme->set_font("font", "LineEdit", label_font);
|
||||
vstheme->set_font("font", "Button", label_font);
|
||||
|
||||
Ref<VisualShaderNode> vsnode = visual_shader->get_node(p_type, p_id);
|
||||
|
||||
Ref<VisualShaderNodeResizableBase> resizable_node = Object::cast_to<VisualShaderNodeResizableBase>(vsnode.ptr());
|
||||
|
@ -406,8 +415,10 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
|
|||
custom_node->_set_initialized(true);
|
||||
}
|
||||
|
||||
// Create graph node.
|
||||
GraphNode *node = memnew(GraphNode);
|
||||
graph->add_child(node);
|
||||
node->set_theme(vstheme);
|
||||
editor->_update_created_node(node);
|
||||
register_link(p_type, p_id, vsnode.ptr(), node);
|
||||
|
||||
|
@ -942,12 +953,12 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) {
|
|||
|
||||
if (vsnode->get_output_port_for_preview() >= 0) {
|
||||
show_port_preview(p_type, p_id, vsnode->get_output_port_for_preview());
|
||||
} else {
|
||||
offset = memnew(Control);
|
||||
offset->set_custom_minimum_size(Size2(0, 4 * EDSCALE));
|
||||
node->add_child(offset);
|
||||
}
|
||||
|
||||
offset = memnew(Control);
|
||||
offset->set_custom_minimum_size(Size2(0, 4 * EDSCALE));
|
||||
node->add_child(offset);
|
||||
|
||||
String error = vsnode->get_warning(mode, p_type);
|
||||
if (!error.is_empty()) {
|
||||
Label *error_label = memnew(Label);
|
||||
|
@ -4652,6 +4663,7 @@ VisualShaderEditor::VisualShaderEditor() {
|
|||
graph->get_zoom_hbox()->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
graph->set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
graph->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
graph->set_show_zoom_label(true);
|
||||
add_child(graph);
|
||||
graph->set_drag_forwarding(this);
|
||||
float graph_minimap_opacity = EditorSettings::get_singleton()->get("editors/visual_editors/minimap_opacity");
|
||||
|
@ -6252,7 +6264,8 @@ void VisualShaderNodePortPreview::setup(const Ref<VisualShader> &p_shader, Visua
|
|||
}
|
||||
|
||||
Size2 VisualShaderNodePortPreview::get_minimum_size() const {
|
||||
return Size2(100, 100) * EDSCALE;
|
||||
int port_preview_size = EditorSettings::get_singleton()->get("editors/visual_editors/visualshader/port_preview_size");
|
||||
return Size2(port_preview_size, port_preview_size) * EDSCALE;
|
||||
}
|
||||
|
||||
void VisualShaderNodePortPreview::_notification(int p_what) {
|
||||
|
|
|
@ -647,6 +647,14 @@ void VisualScriptEditor::_update_graph(int p_only_id) {
|
|||
Control::get_theme_icon(SNAME("PackedColorArray"), SNAME("EditorIcons"))
|
||||
};
|
||||
|
||||
// Visual script specific theme for MSDF font.
|
||||
Ref<Theme> vstheme;
|
||||
vstheme.instantiate();
|
||||
Ref<Font> label_font = EditorNode::get_singleton()->get_editor_theme()->get_font("main_msdf", "EditorFonts");
|
||||
vstheme->set_font("font", "Label", label_font);
|
||||
vstheme->set_font("font", "LineEdit", label_font);
|
||||
vstheme->set_font("font", "Button", label_font);
|
||||
|
||||
Ref<Texture2D> seq_port = Control::get_theme_icon(SNAME("VisualShaderPort"), SNAME("EditorIcons"));
|
||||
List<int> node_ids;
|
||||
script->get_node_list(&node_ids);
|
||||
|
@ -960,9 +968,8 @@ void VisualScriptEditor::_update_graph(int p_only_id) {
|
|||
|
||||
slot_idx++;
|
||||
}
|
||||
|
||||
graph->add_child(gnode);
|
||||
|
||||
gnode->set_theme(vstheme);
|
||||
if (gnode->is_comment()) {
|
||||
graph->move_child(gnode, 0);
|
||||
}
|
||||
|
@ -4575,6 +4582,7 @@ VisualScriptEditor::VisualScriptEditor() {
|
|||
add_child(graph);
|
||||
graph->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
graph->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
|
||||
graph->set_show_zoom_label(true);
|
||||
graph->connect("node_selected", callable_mp(this, &VisualScriptEditor::_node_selected));
|
||||
graph->connect("begin_node_move", callable_mp(this, &VisualScriptEditor::_begin_node_move));
|
||||
graph->connect("end_node_move", callable_mp(this, &VisualScriptEditor::_end_node_move));
|
||||
|
|
|
@ -93,11 +93,13 @@ bool GraphNode::_set(const StringName &p_name, const Variant &p_value) {
|
|||
si.color_right = p_value;
|
||||
} else if (what == "right_icon") {
|
||||
si.custom_slot_right = p_value;
|
||||
} else if (what == "draw_stylebox") {
|
||||
si.draw_stylebox = p_value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
set_slot(idx, si.enable_left, si.type_left, si.color_left, si.enable_right, si.type_right, si.color_right, si.custom_slot_left, si.custom_slot_right);
|
||||
set_slot(idx, si.enable_left, si.type_left, si.color_left, si.enable_right, si.type_right, si.color_right, si.custom_slot_left, si.custom_slot_right, si.draw_stylebox);
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
|
@ -144,6 +146,8 @@ bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const {
|
|||
r_ret = si.color_right;
|
||||
} else if (what == "right_icon") {
|
||||
r_ret = si.custom_slot_right;
|
||||
} else if (what == "draw_stylebox") {
|
||||
r_ret = si.draw_stylebox;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -175,7 +179,7 @@ void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const {
|
|||
p_list->push_back(PropertyInfo(Variant::INT, base + "right_type"));
|
||||
p_list->push_back(PropertyInfo(Variant::COLOR, base + "right_color"));
|
||||
p_list->push_back(PropertyInfo(Variant::OBJECT, base + "right_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL));
|
||||
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, base + "draw_stylebox"));
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
@ -185,6 +189,7 @@ void GraphNode::_resort() {
|
|||
|
||||
Size2i new_size = get_size();
|
||||
Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame"));
|
||||
Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot"));
|
||||
|
||||
int sep = get_theme_constant(SNAME("separation"));
|
||||
|
||||
|
@ -204,7 +209,7 @@ void GraphNode::_resort() {
|
|||
continue;
|
||||
}
|
||||
|
||||
Size2i size = c->get_combined_minimum_size();
|
||||
Size2i size = c->get_combined_minimum_size() + (slot_info[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2());
|
||||
_MinSizeCache msc;
|
||||
|
||||
stretch_min += size.height;
|
||||
|
@ -312,7 +317,9 @@ void GraphNode::_resort() {
|
|||
|
||||
int size = to - from;
|
||||
|
||||
Rect2 rect(sb->get_margin(SIDE_LEFT), from, w, size);
|
||||
float margin = sb->get_margin(SIDE_LEFT) + (slot_info[i].draw_stylebox ? sb_slot->get_margin(SIDE_LEFT) : 0);
|
||||
float width = w - (slot_info[i].draw_stylebox ? sb_slot->get_minimum_size().x : 0);
|
||||
Rect2 rect(margin, from, width, size);
|
||||
|
||||
fit_child_in_rect(c, rect);
|
||||
cache_y.push_back(from - sb->get_margin(SIDE_TOP) + size * 0.5);
|
||||
|
@ -351,14 +358,14 @@ void GraphNode::_notification(int p_what) {
|
|||
Ref<StyleBox> sb;
|
||||
|
||||
if (comment) {
|
||||
sb = get_theme_stylebox(selected ? "comment_focus" : "comment");
|
||||
sb = get_theme_stylebox(selected ? SNAME("comment_focus") : SNAME("comment"));
|
||||
|
||||
} else {
|
||||
sb = get_theme_stylebox(selected ? "selected_frame" : "frame");
|
||||
sb = get_theme_stylebox(selected ? SNAME("selected_frame") : SNAME("frame"));
|
||||
}
|
||||
|
||||
//sb=sb->duplicate();
|
||||
//sb->call("set_modulate",modulate);
|
||||
Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot"));
|
||||
|
||||
Ref<Texture2D> port = get_theme_icon(SNAME("port"));
|
||||
Ref<Texture2D> close = get_theme_icon(SNAME("close"));
|
||||
Ref<Texture2D> resizer = get_theme_icon(SNAME("resizer"));
|
||||
|
@ -389,13 +396,9 @@ void GraphNode::_notification(int p_what) {
|
|||
|
||||
int w = get_size().width - sb->get_minimum_size().x;
|
||||
|
||||
if (show_close) {
|
||||
w -= close->get_width();
|
||||
}
|
||||
|
||||
title_buf->draw(get_canvas_item(), Point2(sb->get_margin(SIDE_LEFT) + title_h_offset, -title_buf->get_size().y + title_offset), title_color);
|
||||
if (show_close) {
|
||||
Vector2 cpos = Point2(w + sb->get_margin(SIDE_LEFT) + close_h_offset, -close->get_height() + close_offset);
|
||||
Vector2 cpos = Point2(w + sb->get_margin(SIDE_LEFT) + close_h_offset - close->get_width(), -close->get_height() + close_offset);
|
||||
draw_texture(close, cpos, close_color);
|
||||
close_rect.position = cpos;
|
||||
close_rect.size = close->get_size();
|
||||
|
@ -411,7 +414,7 @@ void GraphNode::_notification(int p_what) {
|
|||
continue;
|
||||
}
|
||||
const Slot &s = slot_info[E.key];
|
||||
//left
|
||||
// Left port.
|
||||
if (s.enable_left) {
|
||||
Ref<Texture2D> p = port;
|
||||
if (s.custom_slot_left.is_valid()) {
|
||||
|
@ -419,6 +422,7 @@ void GraphNode::_notification(int p_what) {
|
|||
}
|
||||
p->draw(get_canvas_item(), icofs + Point2(edgeofs, cache_y[E.key]), s.color_left);
|
||||
}
|
||||
// Right port.
|
||||
if (s.enable_right) {
|
||||
Ref<Texture2D> p = port;
|
||||
if (s.custom_slot_right.is_valid()) {
|
||||
|
@ -426,6 +430,15 @@ void GraphNode::_notification(int p_what) {
|
|||
}
|
||||
p->draw(get_canvas_item(), icofs + Point2(get_size().x - edgeofs, cache_y[E.key]), s.color_right);
|
||||
}
|
||||
|
||||
// Draw slot stylebox.
|
||||
if (s.draw_stylebox) {
|
||||
Control *c = Object::cast_to<Control>(get_child(E.key));
|
||||
Rect2 c_rect = c->get_rect();
|
||||
c_rect.position.x = sb->get_margin(SIDE_LEFT);
|
||||
c_rect.size.width = w;
|
||||
draw_style_box(sb_slot, c_rect);
|
||||
}
|
||||
}
|
||||
|
||||
if (resizable) {
|
||||
|
@ -482,7 +495,7 @@ void GraphNode::_validate_property(PropertyInfo &property) const {
|
|||
}
|
||||
#endif
|
||||
|
||||
void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right) {
|
||||
void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right, bool p_draw_stylebox) {
|
||||
ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set slot with p_idx (%d) lesser than zero.", p_idx));
|
||||
|
||||
if (!p_enable_left && p_type_left == 0 && p_color_left == Color(1, 1, 1, 1) &&
|
||||
|
@ -501,6 +514,7 @@ void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const C
|
|||
s.color_right = p_color_right;
|
||||
s.custom_slot_left = p_custom_left;
|
||||
s.custom_slot_right = p_custom_right;
|
||||
s.draw_stylebox = p_draw_stylebox;
|
||||
slot_info[p_idx] = s;
|
||||
update();
|
||||
connpos_dirty = true;
|
||||
|
@ -622,16 +636,39 @@ Color GraphNode::get_slot_color_right(int p_idx) const {
|
|||
return slot_info[p_idx].color_right;
|
||||
}
|
||||
|
||||
bool GraphNode::is_slot_draw_stylebox(int p_idx) const {
|
||||
if (!slot_info.has(p_idx)) {
|
||||
return false;
|
||||
}
|
||||
return slot_info[p_idx].draw_stylebox;
|
||||
}
|
||||
|
||||
void GraphNode::set_slot_draw_stylebox(int p_idx, bool p_enable) {
|
||||
ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set draw_stylebox for the slot with p_idx (%d) lesser than zero.", p_idx));
|
||||
|
||||
slot_info[p_idx].draw_stylebox = p_enable;
|
||||
update();
|
||||
connpos_dirty = true;
|
||||
|
||||
emit_signal(SNAME("slot_updated"), p_idx);
|
||||
}
|
||||
|
||||
Size2 GraphNode::get_minimum_size() const {
|
||||
int sep = get_theme_constant(SNAME("separation"));
|
||||
Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame"));
|
||||
Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot"));
|
||||
|
||||
int sep = get_theme_constant(SNAME("separation"));
|
||||
int title_h_offset = get_theme_constant(SNAME("title_h_offset"));
|
||||
|
||||
bool first = true;
|
||||
|
||||
Size2 minsize;
|
||||
minsize.x = title_buf->get_size().x;
|
||||
minsize.x = title_buf->get_size().x + title_h_offset;
|
||||
if (show_close) {
|
||||
int close_h_offset = get_theme_constant(SNAME("close_h_offset"));
|
||||
Ref<Texture2D> close = get_theme_icon(SNAME("close"));
|
||||
minsize.x += sep + close->get_width();
|
||||
//TODO: Remove this magic number after GraphNode rework.
|
||||
minsize.x += 12 + close->get_width() + close_h_offset;
|
||||
}
|
||||
|
||||
for (int i = 0; i < get_child_count(); i++) {
|
||||
|
@ -644,6 +681,9 @@ Size2 GraphNode::get_minimum_size() const {
|
|||
}
|
||||
|
||||
Size2i size = c->get_combined_minimum_size();
|
||||
if (slot_info.has(i)) {
|
||||
size += slot_info[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2();
|
||||
}
|
||||
|
||||
minsize.y += size.y;
|
||||
minsize.x = MAX(minsize.x, size.x);
|
||||
|
@ -989,7 +1029,7 @@ void GraphNode::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_language", "language"), &GraphNode::set_language);
|
||||
ClassDB::bind_method(D_METHOD("get_language"), &GraphNode::get_language);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()));
|
||||
ClassDB::bind_method(D_METHOD("set_slot", "idx", "enable_left", "type_left", "color_left", "enable_right", "type_right", "color_right", "custom_left", "custom_right", "enable"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()), DEFVAL(true));
|
||||
ClassDB::bind_method(D_METHOD("clear_slot", "idx"), &GraphNode::clear_slot);
|
||||
ClassDB::bind_method(D_METHOD("clear_all_slots"), &GraphNode::clear_all_slots);
|
||||
|
||||
|
@ -1011,6 +1051,9 @@ void GraphNode::_bind_methods() {
|
|||
ClassDB::bind_method(D_METHOD("set_slot_color_right", "idx", "color_right"), &GraphNode::set_slot_color_right);
|
||||
ClassDB::bind_method(D_METHOD("get_slot_color_right", "idx"), &GraphNode::get_slot_color_right);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox", "idx"), &GraphNode::is_slot_draw_stylebox);
|
||||
ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox", "idx", "draw_stylebox"), &GraphNode::set_slot_draw_stylebox);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &GraphNode::set_position_offset);
|
||||
ClassDB::bind_method(D_METHOD("get_position_offset"), &GraphNode::get_position_offset);
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ private:
|
|||
Color color_right = Color(1, 1, 1, 1);
|
||||
Ref<Texture2D> custom_slot_left;
|
||||
Ref<Texture2D> custom_slot_right;
|
||||
bool draw_stylebox = true;
|
||||
};
|
||||
|
||||
String title;
|
||||
|
@ -115,7 +116,7 @@ protected:
|
|||
public:
|
||||
bool has_point(const Point2 &p_point) const override;
|
||||
|
||||
void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>());
|
||||
void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>(), bool p_draw_stylebox = true);
|
||||
void clear_slot(int p_idx);
|
||||
void clear_all_slots();
|
||||
|
||||
|
@ -137,6 +138,9 @@ public:
|
|||
void set_slot_color_right(int p_idx, const Color &p_color_right);
|
||||
Color get_slot_color_right(int p_idx) const;
|
||||
|
||||
bool is_slot_draw_stylebox(int p_idx) const;
|
||||
void set_slot_draw_stylebox(int p_idx, bool p_enable);
|
||||
|
||||
void set_title(const String &p_title);
|
||||
String get_title() const;
|
||||
|
||||
|
@ -185,7 +189,9 @@ public:
|
|||
virtual Vector<int> get_allowed_size_flags_horizontal() const override;
|
||||
virtual Vector<int> get_allowed_size_flags_vertical() const override;
|
||||
|
||||
bool is_resizing() const { return resizing; }
|
||||
bool is_resizing() const {
|
||||
return resizing;
|
||||
}
|
||||
|
||||
GraphNode();
|
||||
};
|
||||
|
|
|
@ -686,6 +686,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
|
|||
graphnode_breakpoint->set_border_color(Color(0.9, 0.29, 0.3));
|
||||
Ref<StyleBoxFlat> graphnode_position = make_flat_stylebox(style_pressed_color, 18, 42, 18, 12, 6, true, 4);
|
||||
graphnode_position->set_border_color(Color(0.98, 0.89, 0.27));
|
||||
Ref<StyleBoxEmpty> graphnode_slot = make_empty_stylebox(0, 0, 0, 0);
|
||||
|
||||
theme->set_stylebox("frame", "GraphNode", graphnode_normal);
|
||||
theme->set_stylebox("selected_frame", "GraphNode", graphnode_selected);
|
||||
|
@ -693,6 +694,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
|
|||
theme->set_stylebox("comment_focus", "GraphNode", graphnode_comment_selected);
|
||||
theme->set_stylebox("breakpoint", "GraphNode", graphnode_breakpoint);
|
||||
theme->set_stylebox("position", "GraphNode", graphnode_position);
|
||||
theme->set_stylebox("slot", "GraphNode", graphnode_slot);
|
||||
|
||||
theme->set_icon("port", "GraphNode", icons["graph_port"]);
|
||||
theme->set_icon("close", "GraphNode", icons["close"]);
|
||||
|
@ -704,6 +706,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
|
|||
theme->set_constant("separation", "GraphNode", 2 * scale);
|
||||
theme->set_constant("title_offset", "GraphNode", 26 * scale);
|
||||
theme->set_constant("close_offset", "GraphNode", 22 * scale);
|
||||
theme->set_constant("close_h_offset", "GraphNode", 22 * scale);
|
||||
theme->set_constant("port_offset", "GraphNode", 0);
|
||||
|
||||
// Tree
|
||||
|
|
Loading…
Reference in a new issue