Add FoldableContainer

Co-authored-by: WhalesState <whalesstate@gmail.com>
This commit is contained in:
kobewi 2025-02-02 17:44:28 +01:00
parent 4248411baf
commit 13741ff913
11 changed files with 1082 additions and 0 deletions

View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="FoldableContainer" inherits="Container" keywords="expandable, collapsible, collapse" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A container that can be expanded/collapsed.
</brief_description>
<description>
A container that can be expanded/collapsed, with a title that can be filled with controls, such as buttons.
The title can be positioned at the top or bottom of the container.
The container can be expanded or collapsed by clicking the title or by pressing [code]ui_accept[/code] when focused.
Child control nodes are hidden when the container is collapsed. Ignores non-control children.
Can allow grouping with other FoldableContainers, check [member foldable_group] and [FoldableGroup].
</description>
<tutorials>
</tutorials>
<methods>
<method name="add_title_bar_control">
<return type="void" />
<param index="0" name="control" type="Control" />
<description>
Adds a [Control] that will be placed next to the container's title, obscuring the clickable area. Prime usage is adding [Button] nodes, but it can be any [Control].
The control will be added as a child of this container and removed from previous parent if necessary. The controls will be placed aligned to the right, with the first added control being the leftmost one.
</description>
</method>
<method name="expand">
<return type="void" />
<description>
Expands the container and emits [signal folding_changed].
</description>
</method>
<method name="fold">
<return type="void" />
<description>
Folds the container and emits [signal folding_changed].
</description>
</method>
<method name="remove_title_bar_control">
<return type="void" />
<param index="0" name="control" type="Control" />
<description>
Removes a [Control] added with [method add_title_bar_control]. The node is not freed automatically, you need to use [method Node.queue_free].
</description>
</method>
</methods>
<members>
<member name="focus_mode" type="int" setter="set_focus_mode" getter="get_focus_mode" overrides="Control" enum="Control.FocusMode" default="2" />
<member name="foldable_group" type="FoldableGroup" setter="set_foldable_group" getter="get_foldable_group">
The [FoldableGroup] associated with the container.
</member>
<member name="folded" type="bool" setter="set_folded" getter="is_folded" default="false">
If [code]true[/code], the container will becomes folded and will hide all its children.
</member>
<member name="language" type="String" setter="set_language" getter="get_language" default="&quot;&quot;">
Language code used for text shaping algorithms. If left empty, current locale is used instead.
</member>
<member name="mouse_filter" type="int" setter="set_mouse_filter" getter="get_mouse_filter" overrides="Control" enum="Control.MouseFilter" default="0" />
<member name="text" type="String" setter="set_text" getter="get_text" default="&quot;&quot;">
The Container's title text.
</member>
<member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" enum="Control.TextDirection" default="0">
Base text writing direction.
</member>
<member name="text_overrun_behavior" type="int" setter="set_text_overrun_behavior" getter="get_text_overrun_behavior" enum="TextServer.OverrunBehavior" default="0">
Defines the behavior of the [FoldableContainer] when the text is longer than the available space.
</member>
<member name="title_alignment" type="int" setter="set_title_alignment" getter="get_title_alignment" enum="HorizontalAlignment" default="0">
Title's horizontal text alignment as defined in the [enum HorizontalAlignment] enum.
</member>
<member name="title_position" type="int" setter="set_title_position" getter="get_title_position" enum="FoldableContainer.TitlePosition" default="0">
Title's position as defined in the [enum TitlePosition] enum.
</member>
</members>
<signals>
<signal name="folding_changed">
<param index="0" name="is_folded" type="bool" />
<description>
Emitted when the container is folded/expanded.
</description>
</signal>
</signals>
<constants>
<constant name="POSITION_TOP" value="0" enum="TitlePosition">
Makes the title appear at the top of the container.
</constant>
<constant name="POSITION_BOTTOM" value="1" enum="TitlePosition">
Makes the title appear at the bottom of the container. Also makes all StyleBoxes flipped vertically.
</constant>
</constants>
<theme_items>
<theme_item name="collapsed_font_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)">
The title's font color when collapsed.
</theme_item>
<theme_item name="font_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)">
The title's font color when expanded.
</theme_item>
<theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)">
The title's font outline color.
</theme_item>
<theme_item name="hover_font_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)">
The title's font hover color.
</theme_item>
<theme_item name="h_separation" data_type="constant" type="int" default="2">
The horizontal separation between the title's icon and text, and between title bar controls.
</theme_item>
<theme_item name="outline_size" data_type="constant" type="int" default="0">
The title's font outline size.
</theme_item>
<theme_item name="font" data_type="font" type="Font">
The title's font.
</theme_item>
<theme_item name="font_size" data_type="font_size" type="int">
The title's font size.
</theme_item>
<theme_item name="expanded_arrow" data_type="icon" type="Texture2D">
The title's icon used when expanded.
</theme_item>
<theme_item name="expanded_arrow_mirrored" data_type="icon" type="Texture2D">
The title's icon used when expanded (for bottom title).
</theme_item>
<theme_item name="folded_arrow" data_type="icon" type="Texture2D">
The title's icon used when folded (for left-to-right layouts).
</theme_item>
<theme_item name="folded_arrow_mirrored" data_type="icon" type="Texture2D">
The title's icon used when collapsed (for right-to-left layouts).
</theme_item>
<theme_item name="focus" data_type="style" type="StyleBox">
Background used when [FoldableContainer] has GUI focus. The [theme_item focus] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons.
</theme_item>
<theme_item name="panel" data_type="style" type="StyleBox">
Default background for the [FoldableContainer].
</theme_item>
<theme_item name="title_collapsed_hover_panel" data_type="style" type="StyleBox">
Background used when the mouse cursor enters the title's area when collapsed.
</theme_item>
<theme_item name="title_collapsed_panel" data_type="style" type="StyleBox">
Default background for the [FoldableContainer]'s title when collapsed.
</theme_item>
<theme_item name="title_hover_panel" data_type="style" type="StyleBox">
Background used when the mouse cursor enters the title's area when expanded.
</theme_item>
<theme_item name="title_panel" data_type="style" type="StyleBox">
Default background for the [FoldableContainer]'s title when expanded.
</theme_item>
</theme_items>
</class>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="FoldableGroup" inherits="Resource" keywords="expandable, collapsible, collapse" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A group of foldable containers that doesn't allow more than one container to be expanded at a time.
</brief_description>
<description>
A group of [FoldableContainer]-derived nodes. Only one container can be expanded at a time.
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_containers" qualifiers="const">
<return type="FoldableContainer[]" />
<description>
Returns an [Array] of [FoldableContainer]s that have this as their FoldableGroup (see [member FoldableContainer.foldable_group]). This is equivalent to [ButtonGroup] but for FoldableContainers.
</description>
</method>
<method name="get_expanded_container" qualifiers="const">
<return type="FoldableContainer" />
<description>
Returns the current expanded container.
</description>
</method>
</methods>
<members>
<member name="allow_folding_all" type="bool" setter="set_allow_folding_all" getter="is_allow_folding_all" default="false">
If [code]true[/code], it is possible to fold all containers in this FoldableGroup.
</member>
<member name="resource_local_to_scene" type="bool" setter="set_local_to_scene" getter="is_local_to_scene" overrides="Resource" default="true" />
</members>
<signals>
<signal name="expanded">
<param index="0" name="container" type="FoldableContainer" />
<description>
Emitted when one of the containers of the group is expanded.
</description>
</signal>
</signals>
</class>

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#8eef97" d="m3 1c-1.1045684 0-2 .8954316-2 2v10c0 1.104568.8954316 2 2 2h10c1.104568 0 2-.895432 2-2v-10c0-1.1045684-.895432-2-2-2zm1 2h8l-4 4zm-1 5h10v5h-10z"/></svg>

After

Width:  |  Height:  |  Size: 243 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12"><path fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".4" stroke-width="2" d="m3 7.9999586 3-3 3 3"/></svg>

After

Width:  |  Height:  |  Size: 212 B

View File

@ -1295,6 +1295,40 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the
// GridContainer.
p_theme->set_constant("v_separation", "GridContainer", Math::round(p_config.widget_margin.y - 2 * EDSCALE));
// FoldableContainer
Ref<StyleBoxFlat> foldable_container_title = make_flat_stylebox(p_config.dark_color_1.darkened(0.125), p_config.base_margin, p_config.base_margin, p_config.base_margin, p_config.base_margin);
foldable_container_title->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
foldable_container_title->set_corner_radius(CORNER_BOTTOM_RIGHT, 0);
p_theme->set_stylebox("title_panel", "FoldableContainer", foldable_container_title);
Ref<StyleBoxFlat> foldable_container_hover = make_flat_stylebox(p_config.dark_color_1.lerp(p_config.base_color, 0.4), p_config.base_margin, p_config.base_margin, p_config.base_margin, p_config.base_margin);
foldable_container_hover->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
foldable_container_hover->set_corner_radius(CORNER_BOTTOM_RIGHT, 0);
p_theme->set_stylebox("title_hover_panel", "FoldableContainer", foldable_container_hover);
p_theme->set_stylebox("title_collapsed_panel", "FoldableContainer", make_flat_stylebox(p_config.dark_color_1.darkened(0.125), p_config.base_margin, p_config.base_margin, p_config.base_margin, p_config.base_margin));
p_theme->set_stylebox("title_collapsed_hover_panel", "FoldableContainer", make_flat_stylebox(p_config.dark_color_1.lerp(p_config.base_color, 0.4), p_config.base_margin, p_config.base_margin, p_config.base_margin, p_config.base_margin));
Ref<StyleBoxFlat> foldable_container_panel = make_flat_stylebox(p_config.dark_color_1, p_config.base_margin, p_config.base_margin, p_config.base_margin, p_config.base_margin);
foldable_container_panel->set_corner_radius(CORNER_TOP_LEFT, 0);
foldable_container_panel->set_corner_radius(CORNER_TOP_RIGHT, 0);
p_theme->set_stylebox(SceneStringName(panel), "FoldableContainer", foldable_container_panel);
p_theme->set_stylebox("focus", "FoldableContainer", p_config.button_style_focus);
p_theme->set_font(SceneStringName(font), "FoldableContainer", p_theme->get_font(SceneStringName(font), SNAME("HeaderSmall")));
p_theme->set_font_size(SceneStringName(font_size), "FoldableContainer", p_theme->get_font_size(SceneStringName(font_size), SNAME("HeaderSmall")));
p_theme->set_color(SceneStringName(font_color), "FoldableContainer", p_config.font_color);
p_theme->set_color("hover_font_color", "FoldableContainer", p_config.font_hover_color);
p_theme->set_color("collapsed_font_color", "FoldableContainer", p_config.font_pressed_color);
p_theme->set_color("font_outline_color", "FoldableContainer", p_config.font_outline_color);
p_theme->set_icon("expanded_arrow", "FoldableContainer", p_theme->get_icon(SNAME("GuiTreeArrowDown"), EditorStringName(EditorIcons)));
p_theme->set_icon("expanded_arrow_mirrored", "FoldableContainer", p_theme->get_icon(SNAME("GuiArrowUp"), EditorStringName(EditorIcons)));
p_theme->set_icon("folded_arrow", "FoldableContainer", p_theme->get_icon(SNAME("GuiTreeArrowRight"), EditorStringName(EditorIcons)));
p_theme->set_icon("folded_arrow_mirrored", "FoldableContainer", p_theme->get_icon(SNAME("GuiTreeArrowLeft"), EditorStringName(EditorIcons)));
p_theme->set_constant("outline_size", "FoldableContainer", 0);
p_theme->set_constant("h_separation", "FoldableContainer", p_config.separation_margin);
}
// Window and dialogs.

View File

@ -141,6 +141,7 @@ const Vector<String> prop_allowed_inherited_member_hiding = {
"MenuBar.TextDirection",
"RichTextLabel.TextDirection",
"TextEdit.TextDirection",
"FoldableContainer.TextDirection",
"VisualShaderNodeReroute.PortType",
// The following instances are uniquely egregious violations, hiding `GetType()` from `object`.
// Included for the sake of CI, with the understanding that they *deserve* warnings.

View File

@ -0,0 +1,652 @@
/**************************************************************************/
/* foldable_container.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "foldable_container.h"
#include "scene/resources/text_line.h"
#include "scene/theme/theme_db.h"
Size2 FoldableContainer::get_minimum_size() const {
_update_title_min_size();
if (folded) {
return title_minimum_size;
}
Size2 ms;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
ms = ms.max(c->get_combined_minimum_size());
}
ms += theme_cache.panel_style->get_minimum_size();
return Size2(MAX(ms.width, title_minimum_size.width), ms.height + title_minimum_size.height);
}
void FoldableContainer::fold() {
set_folded(true);
emit_signal(SNAME("folding_changed"), folded);
}
void FoldableContainer::expand() {
set_folded(false);
emit_signal(SNAME("folding_changed"), folded);
}
void FoldableContainer::set_folded(bool p_folded) {
if (folded != p_folded) {
if (!changing_group && foldable_group.is_valid()) {
if (!p_folded) {
_update_group();
foldable_group->emit_signal(SNAME("expanded"), this);
} else if (!foldable_group->updating_group && foldable_group->get_expanded_container() == this && !foldable_group->is_allow_folding_all()) {
return;
}
}
folded = p_folded;
update_minimum_size();
queue_sort();
queue_redraw();
}
}
bool FoldableContainer::is_folded() const {
return folded;
}
void FoldableContainer::set_foldable_group(const Ref<FoldableGroup> &p_group) {
if (foldable_group.is_valid()) {
foldable_group->containers.erase(this);
}
foldable_group = p_group;
if (foldable_group.is_valid()) {
changing_group = true;
if (folded && !foldable_group->get_expanded_container() && !foldable_group->is_allow_folding_all()) {
set_folded(false);
} else if (!folded && foldable_group->get_expanded_container()) {
set_folded(true);
}
foldable_group->containers.insert(this);
changing_group = false;
}
queue_redraw();
}
Ref<FoldableGroup> FoldableContainer::get_foldable_group() const {
return foldable_group;
}
void FoldableContainer::set_text(const String &p_text) {
if (text == p_text) {
return;
}
text = p_text;
_shape();
update_minimum_size();
queue_redraw();
}
String FoldableContainer::get_text() const {
return text;
}
void FoldableContainer::set_text_alignment(HorizontalAlignment p_alignment) {
ERR_FAIL_INDEX((int)p_alignment, 3);
text_alignment = p_alignment;
if (_get_actual_alignment() != text_buf->get_horizontal_alignment()) {
_shape();
queue_redraw();
}
}
HorizontalAlignment FoldableContainer::get_text_alignment() const {
return text_alignment;
}
void FoldableContainer::set_language(const String &p_language) {
if (language == p_language) {
return;
}
language = p_language;
_shape();
update_minimum_size();
queue_redraw();
}
String FoldableContainer::get_language() const {
return language;
}
void FoldableContainer::set_text_direction(TextDirection p_text_direction) {
ERR_FAIL_INDEX(int(p_text_direction), 4);
if (text_direction == p_text_direction) {
return;
}
text_direction = p_text_direction;
_shape();
queue_redraw();
}
Control::TextDirection FoldableContainer::get_text_direction() const {
return text_direction;
}
void FoldableContainer::set_text_overrun_behavior(TextServer::OverrunBehavior p_overrun_behavior) {
if (overrun_behavior == p_overrun_behavior) {
return;
}
overrun_behavior = p_overrun_behavior;
_shape();
update_minimum_size();
queue_redraw();
}
TextServer::OverrunBehavior FoldableContainer::get_text_overrun_behavior() const {
return overrun_behavior;
}
void FoldableContainer::set_title_position(TitlePosition p_title_position) {
ERR_FAIL_INDEX(p_title_position, POSITION_MAX);
if (title_position == p_title_position) {
return;
}
title_position = p_title_position;
queue_redraw();
queue_sort();
}
FoldableContainer::TitlePosition FoldableContainer::get_title_position() const {
return title_position;
}
void FoldableContainer::add_title_bar_control(Control *p_control) {
ERR_FAIL_NULL(p_control);
if (p_control->get_parent()) {
p_control->get_parent()->remove_child(p_control);
ERR_FAIL_COND_MSG(p_control->get_parent() != nullptr, "Failed to remove control from parent.");
}
add_child(p_control, false, INTERNAL_MODE_FRONT);
title_controls.push_back(p_control);
}
void FoldableContainer::remove_title_bar_control(Control *p_control) {
ERR_FAIL_NULL(p_control);
int64_t index = title_controls.find(p_control);
ERR_FAIL_COND_MSG(index == -1, "Can't remove control from title bar.");
title_controls.remove_at(index);
remove_child(p_control);
}
void FoldableContainer::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
Ref<InputEventMouseMotion> m = p_event;
if (m.is_valid()) {
Rect2 title_rect = Rect2(0, (title_position == POSITION_TOP) ? 0 : get_size().height - title_minimum_size.height, get_size().width, title_minimum_size.height);
if (title_rect.has_point(m->get_position())) {
if (!is_hovering) {
is_hovering = true;
queue_redraw();
}
} else if (is_hovering) {
is_hovering = false;
queue_redraw();
}
return;
}
if (p_event->is_action_pressed(SNAME("ui_accept"), false, true)) {
set_folded(!folded);
emit_signal(SNAME("folding_changed"), folded);
accept_event();
return;
}
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
Rect2 title_rect = Rect2(0, (title_position == POSITION_TOP) ? 0 : get_size().height - title_minimum_size.height, get_size().width, title_minimum_size.height);
if (b->get_button_index() == MouseButton::LEFT && b->is_pressed() && title_rect.has_point(b->get_position())) {
set_folded(!folded);
emit_signal(SNAME("folding_changed"), folded);
accept_event();
}
}
}
String FoldableContainer::get_tooltip(const Point2 &p_pos) const {
if (Rect2(0, (title_position == POSITION_TOP) ? 0 : get_size().height - title_minimum_size.height, get_size().width, title_minimum_size.height).has_point(p_pos)) {
return Control::get_tooltip(p_pos);
}
return String();
}
void FoldableContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
RID ci = get_canvas_item();
Size2 size = get_size();
int h_separation = _get_h_separation();
Ref<StyleBox> title_style = _get_title_style();
Ref<Texture2D> icon = _get_title_icon();
real_t title_controls_width = _get_title_controls_width();
if (title_controls_width > 0) {
title_controls_width += h_separation;
}
Rect2 title_rect(
Point2(0, (title_position == POSITION_TOP) ? 0 : size.height - title_minimum_size.height),
Size2(size.width, title_minimum_size.height));
_draw_flippable_stylebox(title_style, title_rect);
Size2 title_ms = title_style->get_minimum_size();
int title_text_width = size.width - title_ms.width;
int title_style_ofs = (title_position == POSITION_TOP) ? title_style->get_margin(SIDE_TOP) : title_style->get_margin(SIDE_BOTTOM);
Point2 title_text_pos(title_style->get_margin(SIDE_LEFT), title_style_ofs);
title_text_pos.y += MAX((title_minimum_size.height - title_ms.height - text_buf->get_size().height) * 0.5, 0);
title_text_width -= icon->get_width() + h_separation + title_controls_width;
Point2 icon_pos(0, MAX((title_minimum_size.height - title_ms.height - icon->get_height()) * 0.5, 0) + title_style_ofs);
bool rtl = is_layout_rtl();
if (rtl) {
icon_pos.x = size.width - title_style->get_margin(SIDE_RIGHT) - icon->get_width();
title_text_pos.x += title_controls_width;
} else {
icon_pos.x = title_style->get_margin(SIDE_LEFT);
title_text_pos.x += icon->get_width() + h_separation;
}
icon->draw(ci, title_rect.position + icon_pos);
Color font_color = folded ? theme_cache.title_collapsed_font_color : theme_cache.title_font_color;
if (is_hovering) {
font_color = theme_cache.title_hovered_font_color;
}
text_buf->set_width(title_text_width);
if (title_text_width > 0) {
if (theme_cache.title_font_outline_size > 0 && theme_cache.title_font_outline_color.a > 0) {
text_buf->draw_outline(ci, title_rect.position + title_text_pos, theme_cache.title_font_outline_size, theme_cache.title_font_outline_color);
}
text_buf->draw(ci, title_rect.position + title_text_pos, font_color);
}
if (!folded) {
Rect2 panel_rect(
Point2(0, (title_position == POSITION_TOP) ? title_minimum_size.height : 0),
Size2(size.width, size.height - title_minimum_size.height));
_draw_flippable_stylebox(theme_cache.panel_style, panel_rect);
}
if (has_focus()) {
Rect2 focus_rect = folded ? title_rect : Rect2(Point2(), size);
_draw_flippable_stylebox(theme_cache.focus_style, focus_rect);
}
} break;
case NOTIFICATION_SORT_CHILDREN: {
bool rtl = is_layout_rtl();
const Vector2 size = get_size();
const Ref<StyleBox> title_style = _get_title_style();
uint32_t title_count = title_controls.size();
if (title_count > 0) {
int h_separation = MAX(theme_cache.h_separation, 0);
real_t offset = 0.0;
if (rtl) {
offset = title_style->get_margin(SIDE_LEFT);
} else {
offset = _get_title_controls_width();
offset = size.x - title_style->get_margin(SIDE_RIGHT) - offset;
}
real_t v_center = title_minimum_size.y * 0.5;
if (title_position == POSITION_BOTTOM) {
v_center = size.y - v_center + (title_style->get_margin(SIDE_BOTTOM) - title_style->get_margin(SIDE_TOP)) * 0.5;
} else {
v_center += (title_style->get_margin(SIDE_TOP) - title_style->get_margin(SIDE_BOTTOM)) * 0.5;
}
for (uint32_t i = 0; i < title_count; i++) {
Control *control = title_controls[rtl ? title_count - i - 1 : i];
if (!control->is_visible()) {
continue;
}
Rect2 rect(Vector2(), control->get_combined_minimum_size());
rect.position.x = offset;
rect.position.y = v_center - rect.size.y * 0.5;
fit_child_in_rect(control, rect);
offset += rect.size.x + h_separation;
}
}
Rect2 inner_rect;
inner_rect.position.x = rtl ? theme_cache.panel_style->get_margin(SIDE_RIGHT) : theme_cache.panel_style->get_margin(SIDE_LEFT);
inner_rect.size.x = size.x - theme_cache.panel_style->get_margin(SIDE_LEFT) - theme_cache.panel_style->get_margin(SIDE_RIGHT);
inner_rect.position.y = theme_cache.panel_style->get_margin(SIDE_TOP);
inner_rect.size.y = size.y - theme_cache.panel_style->get_margin(SIDE_TOP) - theme_cache.panel_style->get_margin(SIDE_BOTTOM) - title_minimum_size.y;
if (title_position == POSITION_TOP) {
inner_rect.position.y += title_minimum_size.y;
}
for (int i = 0; i < get_child_count(false); i++) {
Control *c = as_sortable_control(get_child(i, false), SortableVisibilityMode::IGNORE);
if (!c) {
continue;
}
c->set_visible(!folded);
if (!folded) {
fit_child_in_rect(c, inner_rect);
}
}
} break;
case NOTIFICATION_MOUSE_EXIT: {
if (is_hovering) {
is_hovering = false;
queue_redraw();
}
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
_shape();
update_minimum_size();
queue_redraw();
} break;
}
}
real_t FoldableContainer::_get_title_controls_width() const {
real_t width = 0.0;
int visible_controls = 0;
for (const Control *control : title_controls) {
if (control->is_visible()) {
width += control->get_combined_minimum_size().x;
visible_controls++;
}
}
if (visible_controls > 1) {
width += _get_h_separation() * (visible_controls - 1);
}
return width;
}
Ref<StyleBox> FoldableContainer::_get_title_style() const {
if (is_hovering) {
return folded ? theme_cache.title_collapsed_hover_style : theme_cache.title_hover_style;
}
return folded ? theme_cache.title_collapsed_style : theme_cache.title_style;
}
Ref<Texture2D> FoldableContainer::_get_title_icon() const {
if (!folded) {
return (title_position == POSITION_TOP) ? theme_cache.expanded_arrow : theme_cache.expanded_arrow_mirrored;
} else if (is_layout_rtl()) {
return theme_cache.folded_arrow_mirrored;
}
return theme_cache.folded_arrow;
}
void FoldableContainer::_update_title_min_size() const {
Ref<StyleBox> title_style = folded ? theme_cache.title_collapsed_style : theme_cache.title_style;
Ref<Texture2D> icon = _get_title_icon();
Size2 title_ms = title_style->get_minimum_size();
int h_separation = _get_h_separation();
title_minimum_size = title_ms;
title_minimum_size.width += icon->get_width();
if (!text.is_empty()) {
title_minimum_size.width += h_separation;
Size2 text_size = text_buf->get_size();
title_minimum_size.height += MAX(text_size.height, icon->get_height());
if (overrun_behavior == TextServer::OverrunBehavior::OVERRUN_NO_TRIMMING) {
title_minimum_size.width += text_size.width;
}
} else {
title_minimum_size.height += icon->get_height();
}
if (!title_controls.is_empty()) {
real_t controls_height = 0;
int visible_controls = 0;
for (const Control *control : title_controls) {
if (!control->is_visible()) {
continue;
}
Vector2 size = control->get_combined_minimum_size();
title_minimum_size.width += size.width;
controls_height = MAX(controls_height, size.height);
visible_controls++;
}
if (visible_controls > 0) {
title_minimum_size.width += h_separation * visible_controls;
}
title_minimum_size.height = MAX(title_minimum_size.height, title_ms.height + controls_height);
}
}
void FoldableContainer::_shape() {
Ref<Font> font = theme_cache.title_font;
int font_size = theme_cache.title_font_size;
if (font.is_null() || font_size == 0) {
return;
}
text_buf->clear();
text_buf->set_width(-1);
if (text_direction == TEXT_DIRECTION_INHERITED) {
text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
} else {
text_buf->set_direction((TextServer::Direction)text_direction);
}
text_buf->set_horizontal_alignment(_get_actual_alignment());
text_buf->set_text_overrun_behavior(overrun_behavior);
text_buf->add_string(atr(text), font, font_size, language);
}
HorizontalAlignment FoldableContainer::_get_actual_alignment() const {
if (is_layout_rtl()) {
if (text_alignment == HORIZONTAL_ALIGNMENT_RIGHT) {
return HORIZONTAL_ALIGNMENT_LEFT;
} else if (text_alignment == HORIZONTAL_ALIGNMENT_LEFT) {
return HORIZONTAL_ALIGNMENT_RIGHT;
}
}
return text_alignment;
}
void FoldableContainer::_update_group() {
foldable_group->updating_group = true;
for (FoldableContainer *container : foldable_group->containers) {
if (container != this) {
container->set_folded(true);
}
}
foldable_group->updating_group = false;
}
void FoldableContainer::_draw_flippable_stylebox(const Ref<StyleBox> p_stylebox, const Rect2 &p_rect) {
if (title_position == POSITION_BOTTOM) {
Rect2 rect(-p_rect.position, p_rect.size);
draw_set_transform(Point2(0.0, p_stylebox->get_draw_rect(rect).size.height), 0.0, Size2(1.0, -1.0));
p_stylebox->draw(get_canvas_item(), rect);
draw_set_transform_matrix(Transform2D());
} else {
p_stylebox->draw(get_canvas_item(), p_rect);
}
}
void FoldableContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("fold"), &FoldableContainer::fold);
ClassDB::bind_method(D_METHOD("expand"), &FoldableContainer::expand);
ClassDB::bind_method(D_METHOD("set_folded", "folded"), &FoldableContainer::set_folded);
ClassDB::bind_method(D_METHOD("is_folded"), &FoldableContainer::is_folded);
ClassDB::bind_method(D_METHOD("set_foldable_group", "button_group"), &FoldableContainer::set_foldable_group);
ClassDB::bind_method(D_METHOD("get_foldable_group"), &FoldableContainer::get_foldable_group);
ClassDB::bind_method(D_METHOD("set_text", "text"), &FoldableContainer::set_text);
ClassDB::bind_method(D_METHOD("get_text"), &FoldableContainer::get_text);
ClassDB::bind_method(D_METHOD("set_title_alignment", "alignment"), &FoldableContainer::set_text_alignment);
ClassDB::bind_method(D_METHOD("get_title_alignment"), &FoldableContainer::get_text_alignment);
ClassDB::bind_method(D_METHOD("set_language", "language"), &FoldableContainer::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &FoldableContainer::get_language);
ClassDB::bind_method(D_METHOD("set_text_direction", "text_direction"), &FoldableContainer::set_text_direction);
ClassDB::bind_method(D_METHOD("get_text_direction"), &FoldableContainer::get_text_direction);
ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &FoldableContainer::set_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &FoldableContainer::get_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("set_title_position", "title_position"), &FoldableContainer::set_title_position);
ClassDB::bind_method(D_METHOD("get_title_position"), &FoldableContainer::get_title_position);
ClassDB::bind_method(D_METHOD("add_title_bar_control", "control"), &FoldableContainer::add_title_bar_control);
ClassDB::bind_method(D_METHOD("remove_title_bar_control", "control"), &FoldableContainer::remove_title_bar_control);
ADD_SIGNAL(MethodInfo("folding_changed", PropertyInfo(Variant::BOOL, "is_folded")));
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "folded"), "set_folded", "is_folded");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::INT, "title_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_title_alignment", "get_title_alignment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "title_position", PROPERTY_HINT_ENUM, "Top,Bottom"), "set_title_position", "get_title_position");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_overrun_behavior", PROPERTY_HINT_ENUM, "Trim Nothing,Trim Characters,Trim Words,Ellipsis,Word Ellipsis"), "set_text_overrun_behavior", "get_text_overrun_behavior");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "foldable_group", PROPERTY_HINT_RESOURCE_TYPE, "FoldableGroup"), "set_foldable_group", "get_foldable_group");
ADD_GROUP("BiDi", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID), "set_language", "get_language");
BIND_ENUM_CONSTANT(POSITION_TOP);
BIND_ENUM_CONSTANT(POSITION_BOTTOM);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, FoldableContainer, title_style, "title_panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, FoldableContainer, title_hover_style, "title_hover_panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, FoldableContainer, title_collapsed_style, "title_collapsed_panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, FoldableContainer, title_collapsed_hover_style, "title_collapsed_hover_panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, FoldableContainer, focus_style, "focus");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, FoldableContainer, panel_style, "panel");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_FONT, FoldableContainer, title_font, "font");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_FONT_SIZE, FoldableContainer, title_font_size, "font_size");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_CONSTANT, FoldableContainer, title_font_outline_size, "outline_size");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, FoldableContainer, title_font_color, "font_color");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, FoldableContainer, title_hovered_font_color, "hover_font_color");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, FoldableContainer, title_collapsed_font_color, "collapsed_font_color");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_COLOR, FoldableContainer, title_font_outline_color, "font_outline_color");
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FoldableContainer, expanded_arrow);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FoldableContainer, expanded_arrow_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FoldableContainer, folded_arrow);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, FoldableContainer, folded_arrow_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, FoldableContainer, h_separation);
}
FoldableContainer::FoldableContainer(const String &p_text) {
text_buf.instantiate();
set_text(p_text);
set_focus_mode(FOCUS_ALL);
set_mouse_filter(MOUSE_FILTER_STOP);
}
FoldableContainer::~FoldableContainer() {
if (foldable_group.is_valid()) {
foldable_group->containers.erase(this);
}
}
FoldableContainer *FoldableGroup::get_expanded_container() const {
for (FoldableContainer *container : containers) {
if (!container->is_folded()) {
return container;
}
}
return nullptr;
}
void FoldableGroup::set_allow_folding_all(bool p_enabled) {
allow_folding_all = p_enabled;
if (!allow_folding_all && !get_expanded_container() && containers.size() > 0) {
updating_group = true;
(*containers.begin())->set_folded(false);
updating_group = false;
}
}
bool FoldableGroup::is_allow_folding_all() const {
return allow_folding_all;
}
void FoldableGroup::get_containers(List<FoldableContainer *> *r_containers) const {
for (FoldableContainer *container : containers) {
r_containers->push_back(container);
}
}
TypedArray<FoldableContainer> FoldableGroup::_get_containers() const {
TypedArray<FoldableContainer> foldable_containers;
for (const FoldableContainer *container : containers) {
foldable_containers.push_back(container);
}
return foldable_containers;
}
void FoldableGroup::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_expanded_container"), &FoldableGroup::get_expanded_container);
ClassDB::bind_method(D_METHOD("get_containers"), &FoldableGroup::_get_containers);
ClassDB::bind_method(D_METHOD("set_allow_folding_all", "enabled"), &FoldableGroup::set_allow_folding_all);
ClassDB::bind_method(D_METHOD("is_allow_folding_all"), &FoldableGroup::is_allow_folding_all);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_folding_all"), "set_allow_folding_all", "is_allow_folding_all");
ADD_SIGNAL(MethodInfo("expanded", PropertyInfo(Variant::OBJECT, "container", PROPERTY_HINT_RESOURCE_TYPE, "FoldableContainer")));
}
FoldableGroup::FoldableGroup() {
set_local_to_scene(true);
}

View File

@ -0,0 +1,171 @@
/**************************************************************************/
/* foldable_container.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "scene/gui/container.h"
class FoldableGroup;
class TextLine;
class FoldableContainer : public Container {
GDCLASS(FoldableContainer, Container);
public:
enum TitlePosition {
POSITION_TOP,
POSITION_BOTTOM,
POSITION_MAX
};
private:
bool folded = false;
String text;
Ref<FoldableGroup> foldable_group;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
HorizontalAlignment text_alignment = HORIZONTAL_ALIGNMENT_LEFT;
TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING;
TitlePosition title_position = POSITION_TOP;
Ref<TextLine> text_buf;
bool changing_group = false;
bool is_hovering = false;
mutable Vector2 title_minimum_size;
LocalVector<Control *> title_controls;
struct ThemeCache {
Ref<StyleBox> title_style;
Ref<StyleBox> title_hover_style;
Ref<StyleBox> title_collapsed_style;
Ref<StyleBox> title_collapsed_hover_style;
Ref<StyleBox> panel_style;
Ref<StyleBox> focus_style;
Color title_font_color;
Color title_hovered_font_color;
Color title_collapsed_font_color;
Color title_font_outline_color;
Ref<Font> title_font;
int title_font_size = 0;
int title_font_outline_size = 0;
Ref<Texture2D> expanded_arrow;
Ref<Texture2D> expanded_arrow_mirrored;
Ref<Texture2D> folded_arrow;
Ref<Texture2D> folded_arrow_mirrored;
int h_separation = 0;
} theme_cache;
Ref<StyleBox> _get_title_style() const;
Ref<Texture2D> _get_title_icon() const;
int _get_h_separation() const { return MAX(theme_cache.h_separation, 0); }
real_t _get_title_controls_width() const;
void _update_title_min_size() const;
void _shape();
HorizontalAlignment _get_actual_alignment() const;
void _update_group();
void _draw_flippable_stylebox(const Ref<StyleBox> p_stylebox, const Rect2 &p_rect);
protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
virtual String get_tooltip(const Point2 &p_pos) const override;
void _notification(int p_what);
static void _bind_methods();
public:
void fold();
void expand();
void set_folded(bool p_folded);
bool is_folded() const;
void set_foldable_group(const Ref<FoldableGroup> &p_group);
Ref<FoldableGroup> get_foldable_group() const;
void set_text(const String &p_text);
String get_text() const;
void set_text_alignment(HorizontalAlignment p_alignment);
HorizontalAlignment get_text_alignment() const;
void set_text_direction(TextDirection p_text_direction);
TextDirection get_text_direction() const;
void set_text_overrun_behavior(TextServer::OverrunBehavior p_overrun_behavior);
TextServer::OverrunBehavior get_text_overrun_behavior() const;
void set_language(const String &p_language);
String get_language() const;
void set_title_position(TitlePosition p_title_position);
TitlePosition get_title_position() const;
void add_title_bar_control(Control *p_control);
void remove_title_bar_control(Control *p_control);
virtual Size2 get_minimum_size() const override;
virtual Vector<int> get_allowed_size_flags_horizontal() const override { return { SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END }; }
virtual Vector<int> get_allowed_size_flags_vertical() const override { return { SIZE_FILL, SIZE_SHRINK_BEGIN, SIZE_SHRINK_CENTER, SIZE_SHRINK_END }; }
FoldableContainer(const String &p_text = String());
~FoldableContainer();
};
VARIANT_ENUM_CAST(FoldableContainer::TitlePosition);
class FoldableGroup : public Resource {
GDCLASS(FoldableGroup, Resource);
friend class FoldableContainer;
HashSet<FoldableContainer *> containers;
bool allow_folding_all = false;
bool updating_group = false;
protected:
static void _bind_methods();
public:
FoldableContainer *get_expanded_container() const;
void get_containers(List<FoldableContainer *> *r_containers) const;
TypedArray<FoldableContainer> _get_containers() const;
void set_allow_folding_all(bool p_enabled);
bool is_allow_folding_all() const;
FoldableGroup();
};

View File

@ -57,6 +57,7 @@
#include "scene/gui/dialogs.h"
#include "scene/gui/file_dialog.h"
#include "scene/gui/flow_container.h"
#include "scene/gui/foldable_container.h"
#include "scene/gui/graph_edit.h"
#include "scene/gui/graph_frame.h"
#include "scene/gui/graph_node.h"
@ -519,6 +520,9 @@ void register_scene_types() {
GDREGISTER_CLASS(GraphFrame);
GDREGISTER_CLASS(GraphEdit);
GDREGISTER_CLASS(FoldableGroup);
GDREGISTER_CLASS(FoldableContainer);
OS::get_singleton()->yield(); // may take time to init
int swap_cancel_ok = GLOBAL_DEF(PropertyInfo(Variant::INT, "gui/common/swap_cancel_ok", PROPERTY_HINT_ENUM, "Auto,Cancel First,OK First"), 0);

View File

@ -1259,6 +1259,40 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_color("connection_valid_target_tint_color", "GraphEdit", Color(1, 1, 1, 0.4));
theme->set_color("connection_rim_color", "GraphEdit", style_normal_color);
Ref<StyleBoxFlat> foldable_container_title = make_flat_stylebox(style_pressed_color);
foldable_container_title->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
foldable_container_title->set_corner_radius(CORNER_BOTTOM_RIGHT, 0);
theme->set_stylebox("title_panel", "FoldableContainer", foldable_container_title);
Ref<StyleBoxFlat> foldable_container_hover = make_flat_stylebox(style_hover_color);
foldable_container_hover->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
foldable_container_hover->set_corner_radius(CORNER_BOTTOM_RIGHT, 0);
theme->set_stylebox("title_hover_panel", "FoldableContainer", foldable_container_hover);
theme->set_stylebox("title_collapsed_panel", "FoldableContainer", make_flat_stylebox(style_pressed_color));
theme->set_stylebox("title_collapsed_hover_panel", "FoldableContainer", make_flat_stylebox(style_hover_color));
Ref<StyleBoxFlat> foldable_container_panel = make_flat_stylebox(style_normal_color);
foldable_container_panel->set_content_margin_all(default_margin);
foldable_container_panel->set_corner_radius(CORNER_TOP_LEFT, 0);
foldable_container_panel->set_corner_radius(CORNER_TOP_RIGHT, 0);
theme->set_stylebox(SceneStringName(panel), "FoldableContainer", foldable_container_panel);
Ref<StyleBoxFlat> foldable_focus_style = make_flat_stylebox(style_focus_color, default_margin, default_margin, default_margin, default_margin, default_corner_radius, false, 2);
theme->set_stylebox("focus", "FoldableContainer", foldable_focus_style);
theme->set_font(SceneStringName(font), "FoldableContainer", Ref<Font>());
theme->set_font_size(SceneStringName(font_size), "FoldableContainer", default_font_size);
theme->set_color(SceneStringName(font_color), "FoldableContainer", control_font_color);
theme->set_color("hover_font_color", "FoldableContainer", control_font_hover_color);
theme->set_color("collapsed_font_color", "FoldableContainer", control_font_pressed_color);
theme->set_color("font_outline_color", "FoldableContainer", Color(1, 1, 1));
theme->set_icon("expanded_arrow", "FoldableContainer", icons["arrow_down"]);
theme->set_icon("expanded_arrow_mirrored", "FoldableContainer", icons["arrow_up"]);
theme->set_icon("folded_arrow", "FoldableContainer", icons["arrow_right"]);
theme->set_icon("folded_arrow_mirrored", "FoldableContainer", icons["arrow_left"]);
theme->set_constant("outline_size", "FoldableContainer", 0);
theme->set_constant("h_separation", "FoldableContainer", Math::round(2 * scale));
// Visual Node Ports
theme->set_constant("port_hotzone_inner_extent", "GraphEdit", 22 * scale);

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="none" stroke="#b2b2b2" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".45" stroke-width="2" d="m11.011063 9.9776246-3.0222094-2.9776246-2.9776247 3.02221"/></svg>

After

Width:  |  Height:  |  Size: 254 B