feat: godot-engine-source-4.3-stable

This commit is contained in:
Jan van der Weide 2025-01-17 16:36:38 +01:00
parent c59a7dcade
commit 7125d019b5
11149 changed files with 5070401 additions and 0 deletions

5
engine/scene/gui/SCsub Normal file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env python
Import("env")
env.add_source_files(env.scene_sources, "*.cpp")

View file

@ -0,0 +1,203 @@
/**************************************************************************/
/* aspect_ratio_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 "aspect_ratio_container.h"
#include "scene/gui/texture_rect.h"
Size2 AspectRatioContainer::get_minimum_size() const {
Size2 ms;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE);
if (!c) {
continue;
}
Size2 minsize = c->get_combined_minimum_size();
ms = ms.max(minsize);
}
return ms;
}
void AspectRatioContainer::set_ratio(float p_ratio) {
if (ratio == p_ratio) {
return;
}
ratio = p_ratio;
queue_sort();
}
void AspectRatioContainer::set_stretch_mode(StretchMode p_mode) {
if (stretch_mode == p_mode) {
return;
}
stretch_mode = p_mode;
queue_sort();
}
void AspectRatioContainer::set_alignment_horizontal(AlignmentMode p_alignment_horizontal) {
if (alignment_horizontal == p_alignment_horizontal) {
return;
}
alignment_horizontal = p_alignment_horizontal;
queue_sort();
}
void AspectRatioContainer::set_alignment_vertical(AlignmentMode p_alignment_vertical) {
if (alignment_vertical == p_alignment_vertical) {
return;
}
alignment_vertical = p_alignment_vertical;
queue_sort();
}
Vector<int> AspectRatioContainer::get_allowed_size_flags_horizontal() const {
Vector<int> flags;
flags.append(SIZE_FILL);
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
Vector<int> AspectRatioContainer::get_allowed_size_flags_vertical() const {
Vector<int> flags;
flags.append(SIZE_FILL);
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
void AspectRatioContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_SORT_CHILDREN: {
bool rtl = is_layout_rtl();
Size2 size = get_size();
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
// Temporary fix for editor crash.
TextureRect *trect = Object::cast_to<TextureRect>(c);
if (trect) {
if (trect->get_expand_mode() == TextureRect::EXPAND_FIT_WIDTH_PROPORTIONAL || trect->get_expand_mode() == TextureRect::EXPAND_FIT_HEIGHT_PROPORTIONAL) {
WARN_PRINT_ONCE("Proportional TextureRect is currently not supported inside AspectRatioContainer");
continue;
}
}
Size2 child_minsize = c->get_combined_minimum_size();
Size2 child_size = Size2(ratio, 1.0);
float scale_factor = 1.0;
switch (stretch_mode) {
case STRETCH_WIDTH_CONTROLS_HEIGHT: {
scale_factor = size.x / child_size.x;
} break;
case STRETCH_HEIGHT_CONTROLS_WIDTH: {
scale_factor = size.y / child_size.y;
} break;
case STRETCH_FIT: {
scale_factor = MIN(size.x / child_size.x, size.y / child_size.y);
} break;
case STRETCH_COVER: {
scale_factor = MAX(size.x / child_size.x, size.y / child_size.y);
} break;
}
child_size *= scale_factor;
child_size = child_size.max(child_minsize);
float align_x = 0.5;
switch (alignment_horizontal) {
case ALIGNMENT_BEGIN: {
align_x = 0.0;
} break;
case ALIGNMENT_CENTER: {
align_x = 0.5;
} break;
case ALIGNMENT_END: {
align_x = 1.0;
} break;
}
float align_y = 0.5;
switch (alignment_vertical) {
case ALIGNMENT_BEGIN: {
align_y = 0.0;
} break;
case ALIGNMENT_CENTER: {
align_y = 0.5;
} break;
case ALIGNMENT_END: {
align_y = 1.0;
} break;
}
Vector2 offset = (size - child_size) * Vector2(align_x, align_y);
if (rtl) {
fit_child_in_rect(c, Rect2(Vector2(size.x - offset.x - child_size.x, offset.y), child_size));
} else {
fit_child_in_rect(c, Rect2(offset, child_size));
}
}
} break;
}
}
void AspectRatioContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_ratio", "ratio"), &AspectRatioContainer::set_ratio);
ClassDB::bind_method(D_METHOD("get_ratio"), &AspectRatioContainer::get_ratio);
ClassDB::bind_method(D_METHOD("set_stretch_mode", "stretch_mode"), &AspectRatioContainer::set_stretch_mode);
ClassDB::bind_method(D_METHOD("get_stretch_mode"), &AspectRatioContainer::get_stretch_mode);
ClassDB::bind_method(D_METHOD("set_alignment_horizontal", "alignment_horizontal"), &AspectRatioContainer::set_alignment_horizontal);
ClassDB::bind_method(D_METHOD("get_alignment_horizontal"), &AspectRatioContainer::get_alignment_horizontal);
ClassDB::bind_method(D_METHOD("set_alignment_vertical", "alignment_vertical"), &AspectRatioContainer::set_alignment_vertical);
ClassDB::bind_method(D_METHOD("get_alignment_vertical"), &AspectRatioContainer::get_alignment_vertical);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ratio", PROPERTY_HINT_RANGE, "0.001,10.0,0.0001,or_greater"), "set_ratio", "get_ratio");
ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_mode", PROPERTY_HINT_ENUM, "Width Controls Height,Height Controls Width,Fit,Cover"), "set_stretch_mode", "get_stretch_mode");
ADD_GROUP("Alignment", "alignment_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment_horizontal", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment_horizontal", "get_alignment_horizontal");
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment_vertical", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment_vertical", "get_alignment_vertical");
BIND_ENUM_CONSTANT(STRETCH_WIDTH_CONTROLS_HEIGHT);
BIND_ENUM_CONSTANT(STRETCH_HEIGHT_CONTROLS_WIDTH);
BIND_ENUM_CONSTANT(STRETCH_FIT);
BIND_ENUM_CONSTANT(STRETCH_COVER);
BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN);
BIND_ENUM_CONSTANT(ALIGNMENT_CENTER);
BIND_ENUM_CONSTANT(ALIGNMENT_END);
}

View file

@ -0,0 +1,83 @@
/**************************************************************************/
/* aspect_ratio_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. */
/**************************************************************************/
#ifndef ASPECT_RATIO_CONTAINER_H
#define ASPECT_RATIO_CONTAINER_H
#include "scene/gui/container.h"
class AspectRatioContainer : public Container {
GDCLASS(AspectRatioContainer, Container);
protected:
void _notification(int p_what);
static void _bind_methods();
virtual Size2 get_minimum_size() const override;
public:
enum StretchMode {
STRETCH_WIDTH_CONTROLS_HEIGHT,
STRETCH_HEIGHT_CONTROLS_WIDTH,
STRETCH_FIT,
STRETCH_COVER,
};
enum AlignmentMode {
ALIGNMENT_BEGIN,
ALIGNMENT_CENTER,
ALIGNMENT_END,
};
private:
float ratio = 1.0;
StretchMode stretch_mode = STRETCH_FIT;
AlignmentMode alignment_horizontal = ALIGNMENT_CENTER;
AlignmentMode alignment_vertical = ALIGNMENT_CENTER;
public:
void set_ratio(float p_ratio);
float get_ratio() const { return ratio; }
void set_stretch_mode(StretchMode p_mode);
StretchMode get_stretch_mode() const { return stretch_mode; }
void set_alignment_horizontal(AlignmentMode p_alignment_horizontal);
AlignmentMode get_alignment_horizontal() const { return alignment_horizontal; }
void set_alignment_vertical(AlignmentMode p_alignment_vertical);
AlignmentMode get_alignment_vertical() const { return alignment_vertical; }
virtual Vector<int> get_allowed_size_flags_horizontal() const override;
virtual Vector<int> get_allowed_size_flags_vertical() const override;
};
VARIANT_ENUM_CAST(AspectRatioContainer::StretchMode);
VARIANT_ENUM_CAST(AspectRatioContainer::AlignmentMode);
#endif // ASPECT_RATIO_CONTAINER_H

View file

@ -0,0 +1,557 @@
/**************************************************************************/
/* base_button.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 "base_button.h"
#include "core/config/project_settings.h"
#include "core/os/keyboard.h"
#include "scene/main/window.h"
void BaseButton::_unpress_group() {
if (!button_group.is_valid()) {
return;
}
if (toggle_mode && !button_group->is_allow_unpress()) {
status.pressed = true;
}
for (BaseButton *E : button_group->buttons) {
if (E == this) {
continue;
}
E->set_pressed(false);
}
}
void BaseButton::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (status.disabled) { // no interaction with disabled button
return;
}
Ref<InputEventMouseButton> mouse_button = p_event;
bool ui_accept = p_event->is_action("ui_accept", true) && !p_event->is_echo();
bool button_masked = mouse_button.is_valid() && button_mask.has_flag(mouse_button_to_mask(mouse_button->get_button_index()));
if (button_masked || ui_accept) {
was_mouse_pressed = button_masked;
on_action_event(p_event);
was_mouse_pressed = false;
return;
}
Ref<InputEventMouseMotion> mouse_motion = p_event;
if (mouse_motion.is_valid()) {
if (status.press_attempt) {
bool last_press_inside = status.pressing_inside;
status.pressing_inside = has_point(mouse_motion->get_position());
if (last_press_inside != status.pressing_inside) {
queue_redraw();
}
}
}
}
void BaseButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_MOUSE_ENTER: {
status.hovering = true;
queue_redraw();
} break;
case NOTIFICATION_MOUSE_EXIT: {
status.hovering = false;
queue_redraw();
} break;
case NOTIFICATION_DRAG_BEGIN:
case NOTIFICATION_SCROLL_BEGIN: {
if (status.press_attempt) {
status.press_attempt = false;
queue_redraw();
}
} break;
case NOTIFICATION_FOCUS_ENTER: {
queue_redraw();
} break;
case NOTIFICATION_FOCUS_EXIT: {
if (status.press_attempt) {
status.press_attempt = false;
queue_redraw();
} else if (status.hovering) {
queue_redraw();
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED:
case NOTIFICATION_EXIT_TREE: {
if (p_what == NOTIFICATION_VISIBILITY_CHANGED && is_visible_in_tree()) {
break;
}
if (!toggle_mode) {
status.pressed = false;
}
status.hovering = false;
status.press_attempt = false;
status.pressing_inside = false;
} break;
}
}
void BaseButton::_pressed() {
GDVIRTUAL_CALL(_pressed);
pressed();
emit_signal(SceneStringName(pressed));
}
void BaseButton::_toggled(bool p_pressed) {
GDVIRTUAL_CALL(_toggled, p_pressed);
toggled(p_pressed);
emit_signal(SNAME("toggled"), p_pressed);
}
void BaseButton::on_action_event(Ref<InputEvent> p_event) {
if (p_event->is_pressed()) {
status.press_attempt = true;
status.pressing_inside = true;
emit_signal(SNAME("button_down"));
}
if (status.press_attempt && status.pressing_inside) {
if (toggle_mode) {
bool is_pressed = p_event->is_pressed();
if ((is_pressed && action_mode == ACTION_MODE_BUTTON_PRESS) || (!is_pressed && action_mode == ACTION_MODE_BUTTON_RELEASE)) {
if (action_mode == ACTION_MODE_BUTTON_PRESS) {
status.press_attempt = false;
status.pressing_inside = false;
}
status.pressed = !status.pressed;
_unpress_group();
if (button_group.is_valid()) {
button_group->emit_signal(SceneStringName(pressed), this);
}
_toggled(status.pressed);
_pressed();
}
} else {
if ((p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_PRESS) || (!p_event->is_pressed() && action_mode == ACTION_MODE_BUTTON_RELEASE)) {
_pressed();
}
}
}
if (!p_event->is_pressed()) {
Ref<InputEventMouseButton> mouse_button = p_event;
if (mouse_button.is_valid()) {
if (!has_point(mouse_button->get_position())) {
status.hovering = false;
}
}
status.press_attempt = false;
status.pressing_inside = false;
emit_signal(SNAME("button_up"));
}
queue_redraw();
}
void BaseButton::pressed() {
}
void BaseButton::toggled(bool p_pressed) {
}
void BaseButton::set_disabled(bool p_disabled) {
if (status.disabled == p_disabled) {
return;
}
status.disabled = p_disabled;
if (p_disabled) {
if (!toggle_mode) {
status.pressed = false;
}
status.press_attempt = false;
status.pressing_inside = false;
}
queue_redraw();
}
bool BaseButton::is_disabled() const {
return status.disabled;
}
void BaseButton::set_pressed(bool p_pressed) {
bool prev_pressed = status.pressed;
set_pressed_no_signal(p_pressed);
if (status.pressed == prev_pressed) {
return;
}
if (p_pressed) {
_unpress_group();
if (button_group.is_valid()) {
button_group->emit_signal(SceneStringName(pressed), this);
}
}
_toggled(status.pressed);
}
void BaseButton::set_pressed_no_signal(bool p_pressed) {
if (!toggle_mode) {
return;
}
if (status.pressed == p_pressed) {
return;
}
status.pressed = p_pressed;
queue_redraw();
}
bool BaseButton::is_pressing() const {
return status.press_attempt;
}
bool BaseButton::is_pressed() const {
return toggle_mode ? status.pressed : status.press_attempt;
}
bool BaseButton::is_hovered() const {
return status.hovering;
}
BaseButton::DrawMode BaseButton::get_draw_mode() const {
if (status.disabled) {
return DRAW_DISABLED;
}
if (in_shortcut_feedback) {
return DRAW_HOVER_PRESSED;
}
if (!status.press_attempt && status.hovering) {
if (status.pressed) {
return DRAW_HOVER_PRESSED;
}
return DRAW_HOVER;
} else {
// Determine if pressed or not.
bool pressing;
if (status.press_attempt) {
pressing = (status.pressing_inside || keep_pressed_outside);
if (status.pressed) {
pressing = !pressing;
}
} else {
pressing = status.pressed;
}
if (pressing) {
return DRAW_PRESSED;
} else {
return DRAW_NORMAL;
}
}
}
void BaseButton::set_toggle_mode(bool p_on) {
// Make sure to set 'pressed' to false if we are not in toggle mode
if (!p_on) {
set_pressed(false);
}
toggle_mode = p_on;
update_configuration_warnings();
}
bool BaseButton::is_toggle_mode() const {
return toggle_mode;
}
void BaseButton::set_shortcut_in_tooltip(bool p_on) {
shortcut_in_tooltip = p_on;
}
bool BaseButton::is_shortcut_in_tooltip_enabled() const {
return shortcut_in_tooltip;
}
void BaseButton::set_action_mode(ActionMode p_mode) {
action_mode = p_mode;
}
BaseButton::ActionMode BaseButton::get_action_mode() const {
return action_mode;
}
void BaseButton::set_button_mask(BitField<MouseButtonMask> p_mask) {
button_mask = p_mask;
}
BitField<MouseButtonMask> BaseButton::get_button_mask() const {
return button_mask;
}
void BaseButton::set_keep_pressed_outside(bool p_on) {
keep_pressed_outside = p_on;
}
bool BaseButton::is_keep_pressed_outside() const {
return keep_pressed_outside;
}
void BaseButton::set_shortcut_feedback(bool p_enable) {
shortcut_feedback = p_enable;
}
bool BaseButton::is_shortcut_feedback() const {
return shortcut_feedback;
}
void BaseButton::set_shortcut(const Ref<Shortcut> &p_shortcut) {
shortcut = p_shortcut;
set_process_shortcut_input(shortcut.is_valid());
}
Ref<Shortcut> BaseButton::get_shortcut() const {
return shortcut;
}
void BaseButton::_shortcut_feedback_timeout() {
in_shortcut_feedback = false;
queue_redraw();
}
void BaseButton::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (!is_disabled() && p_event->is_pressed() && is_visible_in_tree() && !p_event->is_echo() && shortcut.is_valid() && shortcut->matches_event(p_event)) {
if (toggle_mode) {
status.pressed = !status.pressed;
_unpress_group();
if (button_group.is_valid()) {
button_group->emit_signal(SceneStringName(pressed), this);
}
_toggled(status.pressed);
_pressed();
} else {
_pressed();
}
queue_redraw();
accept_event();
if (shortcut_feedback && is_inside_tree()) {
if (shortcut_feedback_timer == nullptr) {
shortcut_feedback_timer = memnew(Timer);
shortcut_feedback_timer->set_one_shot(true);
add_child(shortcut_feedback_timer);
shortcut_feedback_timer->set_wait_time(GLOBAL_GET("gui/timers/button_shortcut_feedback_highlight_time"));
shortcut_feedback_timer->connect("timeout", callable_mp(this, &BaseButton::_shortcut_feedback_timeout));
}
in_shortcut_feedback = true;
shortcut_feedback_timer->start();
}
}
}
String BaseButton::get_tooltip(const Point2 &p_pos) const {
String tooltip = Control::get_tooltip(p_pos);
if (shortcut_in_tooltip && shortcut.is_valid() && shortcut->has_valid_event()) {
String text = shortcut->get_name() + " (" + shortcut->get_as_text() + ")";
if (!tooltip.is_empty() && shortcut->get_name().nocasecmp_to(tooltip) != 0) {
text += "\n" + atr(tooltip);
}
tooltip = text;
}
return tooltip;
}
void BaseButton::set_button_group(const Ref<ButtonGroup> &p_group) {
if (button_group.is_valid()) {
button_group->buttons.erase(this);
}
button_group = p_group;
if (button_group.is_valid()) {
button_group->buttons.insert(this);
}
queue_redraw(); //checkbox changes to radio if set a buttongroup
update_configuration_warnings();
}
Ref<ButtonGroup> BaseButton::get_button_group() const {
return button_group;
}
bool BaseButton::_was_pressed_by_mouse() const {
return was_mouse_pressed;
}
PackedStringArray BaseButton::get_configuration_warnings() const {
PackedStringArray warnings = Control::get_configuration_warnings();
if (get_button_group().is_valid() && !is_toggle_mode()) {
warnings.push_back(RTR("ButtonGroup is intended to be used only with buttons that have toggle_mode set to true."));
}
return warnings;
}
void BaseButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &BaseButton::set_pressed);
ClassDB::bind_method(D_METHOD("is_pressed"), &BaseButton::is_pressed);
ClassDB::bind_method(D_METHOD("set_pressed_no_signal", "pressed"), &BaseButton::set_pressed_no_signal);
ClassDB::bind_method(D_METHOD("is_hovered"), &BaseButton::is_hovered);
ClassDB::bind_method(D_METHOD("set_toggle_mode", "enabled"), &BaseButton::set_toggle_mode);
ClassDB::bind_method(D_METHOD("is_toggle_mode"), &BaseButton::is_toggle_mode);
ClassDB::bind_method(D_METHOD("set_shortcut_in_tooltip", "enabled"), &BaseButton::set_shortcut_in_tooltip);
ClassDB::bind_method(D_METHOD("is_shortcut_in_tooltip_enabled"), &BaseButton::is_shortcut_in_tooltip_enabled);
ClassDB::bind_method(D_METHOD("set_disabled", "disabled"), &BaseButton::set_disabled);
ClassDB::bind_method(D_METHOD("is_disabled"), &BaseButton::is_disabled);
ClassDB::bind_method(D_METHOD("set_action_mode", "mode"), &BaseButton::set_action_mode);
ClassDB::bind_method(D_METHOD("get_action_mode"), &BaseButton::get_action_mode);
ClassDB::bind_method(D_METHOD("set_button_mask", "mask"), &BaseButton::set_button_mask);
ClassDB::bind_method(D_METHOD("get_button_mask"), &BaseButton::get_button_mask);
ClassDB::bind_method(D_METHOD("get_draw_mode"), &BaseButton::get_draw_mode);
ClassDB::bind_method(D_METHOD("set_keep_pressed_outside", "enabled"), &BaseButton::set_keep_pressed_outside);
ClassDB::bind_method(D_METHOD("is_keep_pressed_outside"), &BaseButton::is_keep_pressed_outside);
ClassDB::bind_method(D_METHOD("set_shortcut_feedback", "enabled"), &BaseButton::set_shortcut_feedback);
ClassDB::bind_method(D_METHOD("is_shortcut_feedback"), &BaseButton::is_shortcut_feedback);
ClassDB::bind_method(D_METHOD("set_shortcut", "shortcut"), &BaseButton::set_shortcut);
ClassDB::bind_method(D_METHOD("get_shortcut"), &BaseButton::get_shortcut);
ClassDB::bind_method(D_METHOD("set_button_group", "button_group"), &BaseButton::set_button_group);
ClassDB::bind_method(D_METHOD("get_button_group"), &BaseButton::get_button_group);
GDVIRTUAL_BIND(_pressed);
GDVIRTUAL_BIND(_toggled, "toggled_on");
ADD_SIGNAL(MethodInfo("pressed"));
ADD_SIGNAL(MethodInfo("button_up"));
ADD_SIGNAL(MethodInfo("button_down"));
ADD_SIGNAL(MethodInfo("toggled", PropertyInfo(Variant::BOOL, "toggled_on")));
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disabled"), "set_disabled", "is_disabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "toggle_mode"), "set_toggle_mode", "is_toggle_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "button_pressed"), "set_pressed", "is_pressed");
ADD_PROPERTY(PropertyInfo(Variant::INT, "action_mode", PROPERTY_HINT_ENUM, "Button Press,Button Release"), "set_action_mode", "get_action_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "button_mask", PROPERTY_HINT_FLAGS, "Mouse Left, Mouse Right, Mouse Middle"), "set_button_mask", "get_button_mask");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_pressed_outside"), "set_keep_pressed_outside", "is_keep_pressed_outside");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "button_group", PROPERTY_HINT_RESOURCE_TYPE, "ButtonGroup"), "set_button_group", "get_button_group");
ADD_GROUP("Shortcut", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_feedback"), "set_shortcut_feedback", "is_shortcut_feedback");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shortcut_in_tooltip"), "set_shortcut_in_tooltip", "is_shortcut_in_tooltip_enabled");
BIND_ENUM_CONSTANT(DRAW_NORMAL);
BIND_ENUM_CONSTANT(DRAW_PRESSED);
BIND_ENUM_CONSTANT(DRAW_HOVER);
BIND_ENUM_CONSTANT(DRAW_DISABLED);
BIND_ENUM_CONSTANT(DRAW_HOVER_PRESSED);
BIND_ENUM_CONSTANT(ACTION_MODE_BUTTON_PRESS);
BIND_ENUM_CONSTANT(ACTION_MODE_BUTTON_RELEASE);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "gui/timers/button_shortcut_feedback_highlight_time", PROPERTY_HINT_RANGE, "0.01,10,0.01,suffix:s"), 0.2);
}
BaseButton::BaseButton() {
set_focus_mode(FOCUS_ALL);
}
BaseButton::~BaseButton() {
if (button_group.is_valid()) {
button_group->buttons.erase(this);
}
}
void ButtonGroup::get_buttons(List<BaseButton *> *r_buttons) {
for (BaseButton *E : buttons) {
r_buttons->push_back(E);
}
}
TypedArray<BaseButton> ButtonGroup::_get_buttons() {
TypedArray<BaseButton> btns;
for (const BaseButton *E : buttons) {
btns.push_back(E);
}
return btns;
}
BaseButton *ButtonGroup::get_pressed_button() {
for (BaseButton *E : buttons) {
if (E->is_pressed()) {
return E;
}
}
return nullptr;
}
void ButtonGroup::set_allow_unpress(bool p_enabled) {
allow_unpress = p_enabled;
}
bool ButtonGroup::is_allow_unpress() {
return allow_unpress;
}
void ButtonGroup::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_pressed_button"), &ButtonGroup::get_pressed_button);
ClassDB::bind_method(D_METHOD("get_buttons"), &ButtonGroup::_get_buttons);
ClassDB::bind_method(D_METHOD("set_allow_unpress", "enabled"), &ButtonGroup::set_allow_unpress);
ClassDB::bind_method(D_METHOD("is_allow_unpress"), &ButtonGroup::is_allow_unpress);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_unpress"), "set_allow_unpress", "is_allow_unpress");
ADD_SIGNAL(MethodInfo("pressed", PropertyInfo(Variant::OBJECT, "button", PROPERTY_HINT_RESOURCE_TYPE, "BaseButton")));
}
ButtonGroup::ButtonGroup() {
set_local_to_scene(true);
}

View file

@ -0,0 +1,169 @@
/**************************************************************************/
/* base_button.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. */
/**************************************************************************/
#ifndef BASE_BUTTON_H
#define BASE_BUTTON_H
#include "core/input/shortcut.h"
#include "scene/gui/control.h"
class ButtonGroup;
class BaseButton : public Control {
GDCLASS(BaseButton, Control);
public:
enum ActionMode {
ACTION_MODE_BUTTON_PRESS,
ACTION_MODE_BUTTON_RELEASE,
};
private:
BitField<MouseButtonMask> button_mask = MouseButtonMask::LEFT;
bool toggle_mode = false;
bool shortcut_in_tooltip = true;
bool was_mouse_pressed = false;
bool keep_pressed_outside = false;
bool shortcut_feedback = true;
Ref<Shortcut> shortcut;
ObjectID shortcut_context;
ActionMode action_mode = ACTION_MODE_BUTTON_RELEASE;
struct Status {
bool pressed = false;
bool hovering = false;
bool press_attempt = false;
bool pressing_inside = false;
bool disabled = false;
} status;
Ref<ButtonGroup> button_group;
void _unpress_group();
void _pressed();
void _toggled(bool p_pressed);
void on_action_event(Ref<InputEvent> p_event);
Timer *shortcut_feedback_timer = nullptr;
bool in_shortcut_feedback = false;
void _shortcut_feedback_timeout();
protected:
virtual void pressed();
virtual void toggled(bool p_pressed);
static void _bind_methods();
virtual void gui_input(const Ref<InputEvent> &p_event) override;
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
void _notification(int p_what);
bool _was_pressed_by_mouse() const;
GDVIRTUAL0(_pressed)
GDVIRTUAL1(_toggled, bool)
public:
enum DrawMode {
DRAW_NORMAL,
DRAW_PRESSED,
DRAW_HOVER,
DRAW_DISABLED,
DRAW_HOVER_PRESSED,
};
DrawMode get_draw_mode() const;
/* Signals */
bool is_pressed() const; ///< return whether button is pressed (toggled in)
bool is_pressing() const; ///< return whether button is pressed (toggled in)
bool is_hovered() const;
void set_pressed(bool p_pressed); // Only works in toggle mode.
void set_pressed_no_signal(bool p_pressed);
void set_toggle_mode(bool p_on);
bool is_toggle_mode() const;
void set_shortcut_in_tooltip(bool p_on);
bool is_shortcut_in_tooltip_enabled() const;
void set_disabled(bool p_disabled);
bool is_disabled() const;
void set_action_mode(ActionMode p_mode);
ActionMode get_action_mode() const;
void set_keep_pressed_outside(bool p_on);
bool is_keep_pressed_outside() const;
void set_shortcut_feedback(bool p_enable);
bool is_shortcut_feedback() const;
void set_button_mask(BitField<MouseButtonMask> p_mask);
BitField<MouseButtonMask> get_button_mask() const;
void set_shortcut(const Ref<Shortcut> &p_shortcut);
Ref<Shortcut> get_shortcut() const;
virtual String get_tooltip(const Point2 &p_pos) const override;
void set_button_group(const Ref<ButtonGroup> &p_group);
Ref<ButtonGroup> get_button_group() const;
PackedStringArray get_configuration_warnings() const override;
BaseButton();
~BaseButton();
};
VARIANT_ENUM_CAST(BaseButton::DrawMode)
VARIANT_ENUM_CAST(BaseButton::ActionMode)
class ButtonGroup : public Resource {
GDCLASS(ButtonGroup, Resource);
friend class BaseButton;
HashSet<BaseButton *> buttons;
bool allow_unpress = false;
protected:
static void _bind_methods();
public:
BaseButton *get_pressed_button();
void get_buttons(List<BaseButton *> *r_buttons);
TypedArray<BaseButton> _get_buttons();
void set_allow_unpress(bool p_enabled);
bool is_allow_unpress();
ButtonGroup();
};
#endif // BASE_BUTTON_H

View file

@ -0,0 +1,399 @@
/**************************************************************************/
/* box_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 "box_container.h"
#include "scene/gui/label.h"
#include "scene/gui/margin_container.h"
#include "scene/theme/theme_db.h"
struct _MinSizeCache {
int min_size = 0;
bool will_stretch = false;
int final_size = 0;
};
void BoxContainer::_resort() {
/** First pass, determine minimum size AND amount of stretchable elements */
Size2i new_size = get_size();
bool rtl = is_layout_rtl();
bool first = true;
int children_count = 0;
int stretch_min = 0;
int stretch_avail = 0;
float stretch_ratio_total = 0.0;
HashMap<Control *, _MinSizeCache> min_size_cache;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
Size2i size = c->get_combined_minimum_size();
_MinSizeCache msc;
if (vertical) { /* VERTICAL */
stretch_min += size.height;
msc.min_size = size.height;
msc.will_stretch = c->get_v_size_flags().has_flag(SIZE_EXPAND);
} else { /* HORIZONTAL */
stretch_min += size.width;
msc.min_size = size.width;
msc.will_stretch = c->get_h_size_flags().has_flag(SIZE_EXPAND);
}
if (msc.will_stretch) {
stretch_avail += msc.min_size;
stretch_ratio_total += c->get_stretch_ratio();
}
msc.final_size = msc.min_size;
min_size_cache[c] = msc;
children_count++;
}
if (children_count == 0) {
return;
}
int stretch_max = (vertical ? new_size.height : new_size.width) - (children_count - 1) * theme_cache.separation;
int stretch_diff = stretch_max - stretch_min;
if (stretch_diff < 0) {
//avoid negative stretch space
stretch_diff = 0;
}
stretch_avail += stretch_diff; //available stretch space.
/** Second, pass successively to discard elements that can't be stretched, this will run while stretchable
elements exist */
bool has_stretched = false;
while (stretch_ratio_total > 0) { // first of all, don't even be here if no stretchable objects exist
has_stretched = true;
bool refit_successful = true; //assume refit-test will go well
float error = 0.0; // Keep track of accumulated error in pixels
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
ERR_FAIL_COND(!min_size_cache.has(c));
_MinSizeCache &msc = min_size_cache[c];
if (msc.will_stretch) { //wants to stretch
//let's see if it can really stretch
float final_pixel_size = stretch_avail * c->get_stretch_ratio() / stretch_ratio_total;
// Add leftover fractional pixels to error accumulator
error += final_pixel_size - (int)final_pixel_size;
if (final_pixel_size < msc.min_size) {
//if available stretching area is too small for widget,
//then remove it from stretching area
msc.will_stretch = false;
stretch_ratio_total -= c->get_stretch_ratio();
refit_successful = false;
stretch_avail -= msc.min_size;
msc.final_size = msc.min_size;
break;
} else {
msc.final_size = final_pixel_size;
// Dump accumulated error if one pixel or more
if (error >= 1) {
msc.final_size += 1;
error -= 1;
}
}
}
}
if (refit_successful) { //uf refit went well, break
break;
}
}
/** Final pass, draw and stretch elements **/
int ofs = 0;
if (!has_stretched) {
if (!vertical) {
switch (alignment) {
case ALIGNMENT_BEGIN:
if (rtl) {
ofs = stretch_diff;
}
break;
case ALIGNMENT_CENTER:
ofs = stretch_diff / 2;
break;
case ALIGNMENT_END:
if (!rtl) {
ofs = stretch_diff;
}
break;
}
} else {
switch (alignment) {
case ALIGNMENT_BEGIN:
break;
case ALIGNMENT_CENTER:
ofs = stretch_diff / 2;
break;
case ALIGNMENT_END:
ofs = stretch_diff;
break;
}
}
}
first = true;
int idx = 0;
int start;
int end;
int delta;
if (!rtl || vertical) {
start = 0;
end = get_child_count();
delta = +1;
} else {
start = get_child_count() - 1;
end = -1;
delta = -1;
}
for (int i = start; i != end; i += delta) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
_MinSizeCache &msc = min_size_cache[c];
if (first) {
first = false;
} else {
ofs += theme_cache.separation;
}
int from = ofs;
int to = ofs + msc.final_size;
if (msc.will_stretch && idx == children_count - 1) {
//adjust so the last one always fits perfect
//compensating for numerical imprecision
to = vertical ? new_size.height : new_size.width;
}
int size = to - from;
Rect2 rect;
if (vertical) {
rect = Rect2(0, from, new_size.width, size);
} else {
rect = Rect2(from, 0, size, new_size.height);
}
fit_child_in_rect(c, rect);
ofs = to;
idx++;
}
}
Size2 BoxContainer::get_minimum_size() const {
/* Calculate MINIMUM SIZE */
Size2i minimum;
bool first = true;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE);
if (!c) {
continue;
}
Size2i size = c->get_combined_minimum_size();
if (vertical) { /* VERTICAL */
if (size.width > minimum.width) {
minimum.width = size.width;
}
minimum.height += size.height + (first ? 0 : theme_cache.separation);
} else { /* HORIZONTAL */
if (size.height > minimum.height) {
minimum.height = size.height;
}
minimum.width += size.width + (first ? 0 : theme_cache.separation);
}
first = false;
}
return minimum;
}
void BoxContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_SORT_CHILDREN: {
_resort();
} break;
case NOTIFICATION_THEME_CHANGED: {
update_minimum_size();
} break;
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
queue_sort();
} break;
}
}
void BoxContainer::_validate_property(PropertyInfo &p_property) const {
if (is_fixed && p_property.name == "vertical") {
p_property.usage = PROPERTY_USAGE_NONE;
}
}
void BoxContainer::set_alignment(AlignmentMode p_alignment) {
if (alignment == p_alignment) {
return;
}
alignment = p_alignment;
_resort();
}
BoxContainer::AlignmentMode BoxContainer::get_alignment() const {
return alignment;
}
void BoxContainer::set_vertical(bool p_vertical) {
ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + ".");
vertical = p_vertical;
update_minimum_size();
_resort();
}
bool BoxContainer::is_vertical() const {
return vertical;
}
Control *BoxContainer::add_spacer(bool p_begin) {
Control *c = memnew(Control);
c->set_mouse_filter(MOUSE_FILTER_PASS); //allow spacer to pass mouse events
if (vertical) {
c->set_v_size_flags(SIZE_EXPAND_FILL);
} else {
c->set_h_size_flags(SIZE_EXPAND_FILL);
}
add_child(c);
if (p_begin) {
move_child(c, 0);
}
return c;
}
Vector<int> BoxContainer::get_allowed_size_flags_horizontal() const {
Vector<int> flags;
flags.append(SIZE_FILL);
if (!vertical) {
flags.append(SIZE_EXPAND);
}
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
Vector<int> BoxContainer::get_allowed_size_flags_vertical() const {
Vector<int> flags;
flags.append(SIZE_FILL);
if (vertical) {
flags.append(SIZE_EXPAND);
}
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
BoxContainer::BoxContainer(bool p_vertical) {
vertical = p_vertical;
}
void BoxContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_spacer", "begin"), &BoxContainer::add_spacer);
ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &BoxContainer::set_alignment);
ClassDB::bind_method(D_METHOD("get_alignment"), &BoxContainer::get_alignment);
ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &BoxContainer::set_vertical);
ClassDB::bind_method(D_METHOD("is_vertical"), &BoxContainer::is_vertical);
BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN);
BIND_ENUM_CONSTANT(ALIGNMENT_CENTER);
BIND_ENUM_CONSTANT(ALIGNMENT_END);
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment", "get_alignment");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, BoxContainer, separation);
}
MarginContainer *VBoxContainer::add_margin_child(const String &p_label, Control *p_control, bool p_expand) {
Label *l = memnew(Label);
l->set_theme_type_variation("HeaderSmall");
l->set_text(p_label);
add_child(l);
MarginContainer *mc = memnew(MarginContainer);
mc->add_theme_constant_override("margin_left", 0);
mc->add_child(p_control, true);
add_child(mc);
if (p_expand) {
mc->set_v_size_flags(SIZE_EXPAND_FILL);
}
return mc;
}

View file

@ -0,0 +1,101 @@
/**************************************************************************/
/* box_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. */
/**************************************************************************/
#ifndef BOX_CONTAINER_H
#define BOX_CONTAINER_H
#include "scene/gui/container.h"
class BoxContainer : public Container {
GDCLASS(BoxContainer, Container);
public:
enum AlignmentMode {
ALIGNMENT_BEGIN,
ALIGNMENT_CENTER,
ALIGNMENT_END
};
private:
bool vertical = false;
AlignmentMode alignment = ALIGNMENT_BEGIN;
struct ThemeCache {
int separation = 0;
} theme_cache;
void _resort();
protected:
bool is_fixed = false;
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
public:
Control *add_spacer(bool p_begin = false);
void set_alignment(AlignmentMode p_alignment);
AlignmentMode get_alignment() const;
void set_vertical(bool p_vertical);
bool is_vertical() const;
virtual Size2 get_minimum_size() const override;
virtual Vector<int> get_allowed_size_flags_horizontal() const override;
virtual Vector<int> get_allowed_size_flags_vertical() const override;
BoxContainer(bool p_vertical = false);
};
class HBoxContainer : public BoxContainer {
GDCLASS(HBoxContainer, BoxContainer);
public:
HBoxContainer() :
BoxContainer(false) { is_fixed = true; }
};
class MarginContainer;
class VBoxContainer : public BoxContainer {
GDCLASS(VBoxContainer, BoxContainer);
public:
MarginContainer *add_margin_child(const String &p_label, Control *p_control, bool p_expand = false);
VBoxContainer() :
BoxContainer(true) { is_fixed = true; }
};
VARIANT_ENUM_CAST(BoxContainer::AlignmentMode);
#endif // BOX_CONTAINER_H

853
engine/scene/gui/button.cpp Normal file
View file

@ -0,0 +1,853 @@
/**************************************************************************/
/* button.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 "button.h"
#include "core/string/translation.h"
#include "scene/theme/theme_db.h"
#include "servers/rendering_server.h"
Size2 Button::get_minimum_size() const {
Ref<Texture2D> _icon = icon;
if (_icon.is_null() && has_theme_icon(SNAME("icon"))) {
_icon = theme_cache.icon;
}
return get_minimum_size_for_text_and_icon("", _icon);
}
void Button::_set_internal_margin(Side p_side, float p_value) {
_internal_margin[p_side] = p_value;
}
void Button::_queue_update_size_cache() {
}
void Button::_update_theme_item_cache() {
Control::_update_theme_item_cache();
const bool rtl = is_layout_rtl();
if (rtl && has_theme_stylebox(SNAME("normal_mirrored"))) {
theme_cache.max_style_size = theme_cache.normal_mirrored->get_minimum_size();
theme_cache.style_margin_left = theme_cache.normal_mirrored->get_margin(SIDE_LEFT);
theme_cache.style_margin_right = theme_cache.normal_mirrored->get_margin(SIDE_RIGHT);
theme_cache.style_margin_top = theme_cache.normal_mirrored->get_margin(SIDE_TOP);
theme_cache.style_margin_bottom = theme_cache.normal_mirrored->get_margin(SIDE_BOTTOM);
} else {
theme_cache.max_style_size = theme_cache.normal->get_minimum_size();
theme_cache.style_margin_left = theme_cache.normal->get_margin(SIDE_LEFT);
theme_cache.style_margin_right = theme_cache.normal->get_margin(SIDE_RIGHT);
theme_cache.style_margin_top = theme_cache.normal->get_margin(SIDE_TOP);
theme_cache.style_margin_bottom = theme_cache.normal->get_margin(SIDE_BOTTOM);
}
if (has_theme_stylebox("hover_pressed")) {
if (rtl && has_theme_stylebox(SNAME("hover_pressed_mirrored"))) {
theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.hover_pressed_mirrored->get_minimum_size());
theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.hover_pressed_mirrored->get_margin(SIDE_LEFT));
theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.hover_pressed_mirrored->get_margin(SIDE_RIGHT));
theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.hover_pressed_mirrored->get_margin(SIDE_TOP));
theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.hover_pressed_mirrored->get_margin(SIDE_BOTTOM));
} else {
theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.hover_pressed->get_minimum_size());
theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.hover_pressed->get_margin(SIDE_LEFT));
theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.hover_pressed->get_margin(SIDE_RIGHT));
theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.hover_pressed->get_margin(SIDE_TOP));
theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.hover_pressed->get_margin(SIDE_BOTTOM));
}
}
if (rtl && has_theme_stylebox(SNAME("pressed_mirrored"))) {
theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.pressed_mirrored->get_minimum_size());
theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.pressed_mirrored->get_margin(SIDE_LEFT));
theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.pressed_mirrored->get_margin(SIDE_RIGHT));
theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.pressed_mirrored->get_margin(SIDE_TOP));
theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.pressed_mirrored->get_margin(SIDE_BOTTOM));
} else {
theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.pressed->get_minimum_size());
theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.pressed->get_margin(SIDE_LEFT));
theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.pressed->get_margin(SIDE_RIGHT));
theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.pressed->get_margin(SIDE_TOP));
theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.pressed->get_margin(SIDE_BOTTOM));
}
if (rtl && has_theme_stylebox(SNAME("hover_mirrored"))) {
theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.hover_mirrored->get_minimum_size());
theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.hover_mirrored->get_margin(SIDE_LEFT));
theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.hover_mirrored->get_margin(SIDE_RIGHT));
theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.hover_mirrored->get_margin(SIDE_TOP));
theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.hover_mirrored->get_margin(SIDE_BOTTOM));
} else {
theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.hover->get_minimum_size());
theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.hover->get_margin(SIDE_LEFT));
theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.hover->get_margin(SIDE_RIGHT));
theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.hover->get_margin(SIDE_TOP));
theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.hover->get_margin(SIDE_BOTTOM));
}
if (rtl && has_theme_stylebox(SNAME("disabled_mirrored"))) {
theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.disabled_mirrored->get_minimum_size());
theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.disabled_mirrored->get_margin(SIDE_LEFT));
theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.disabled_mirrored->get_margin(SIDE_RIGHT));
theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.disabled_mirrored->get_margin(SIDE_TOP));
theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.disabled_mirrored->get_margin(SIDE_BOTTOM));
} else {
theme_cache.max_style_size = theme_cache.max_style_size.max(theme_cache.disabled->get_minimum_size());
theme_cache.style_margin_left = MAX(theme_cache.style_margin_left, theme_cache.disabled->get_margin(SIDE_LEFT));
theme_cache.style_margin_right = MAX(theme_cache.style_margin_right, theme_cache.disabled->get_margin(SIDE_RIGHT));
theme_cache.style_margin_top = MAX(theme_cache.style_margin_top, theme_cache.disabled->get_margin(SIDE_TOP));
theme_cache.style_margin_bottom = MAX(theme_cache.style_margin_bottom, theme_cache.disabled->get_margin(SIDE_BOTTOM));
}
theme_cache.max_style_size = theme_cache.max_style_size.max(Vector2(theme_cache.style_margin_left + theme_cache.style_margin_right, theme_cache.style_margin_top + theme_cache.style_margin_bottom));
}
Size2 Button::_get_largest_stylebox_size() const {
return theme_cache.max_style_size;
}
Ref<StyleBox> Button::_get_current_stylebox() const {
Ref<StyleBox> stylebox = theme_cache.normal;
const bool rtl = is_layout_rtl();
switch (get_draw_mode()) {
case DRAW_NORMAL: {
if (rtl && has_theme_stylebox(SNAME("normal_mirrored"))) {
stylebox = theme_cache.normal_mirrored;
} else {
stylebox = theme_cache.normal;
}
} break;
case DRAW_HOVER_PRESSED: {
// Edge case for CheckButton and CheckBox.
if (has_theme_stylebox("hover_pressed")) {
if (rtl && has_theme_stylebox(SNAME("hover_pressed_mirrored"))) {
stylebox = theme_cache.hover_pressed_mirrored;
} else {
stylebox = theme_cache.hover_pressed;
}
break;
}
}
[[fallthrough]];
case DRAW_PRESSED: {
if (rtl && has_theme_stylebox(SNAME("pressed_mirrored"))) {
stylebox = theme_cache.pressed_mirrored;
} else {
stylebox = theme_cache.pressed;
}
} break;
case DRAW_HOVER: {
if (rtl && has_theme_stylebox(SNAME("hover_mirrored"))) {
stylebox = theme_cache.hover_mirrored;
} else {
stylebox = theme_cache.hover;
}
} break;
case DRAW_DISABLED: {
if (rtl && has_theme_stylebox(SNAME("disabled_mirrored"))) {
stylebox = theme_cache.disabled_mirrored;
} else {
stylebox = theme_cache.disabled;
}
} break;
}
return stylebox;
}
void Button::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
queue_redraw();
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
xl_text = atr(text);
_shape();
update_minimum_size();
queue_redraw();
} break;
case NOTIFICATION_THEME_CHANGED: {
_shape();
update_minimum_size();
queue_redraw();
} break;
case NOTIFICATION_RESIZED: {
if (autowrap_mode != TextServer::AUTOWRAP_OFF) {
_shape();
update_minimum_size();
queue_redraw();
}
} break;
case NOTIFICATION_DRAW: {
const RID ci = get_canvas_item();
const Size2 size = get_size();
Ref<StyleBox> style = _get_current_stylebox();
// Draws the stylebox in the current state.
if (!flat) {
style->draw(ci, Rect2(Point2(), size));
}
if (has_focus()) {
theme_cache.focus->draw(ci, Rect2(Point2(), size));
}
Ref<Texture2D> _icon = icon;
if (_icon.is_null() && has_theme_icon(SNAME("icon"))) {
_icon = theme_cache.icon;
}
if (xl_text.is_empty() && _icon.is_null()) {
break;
}
const float style_margin_left = (theme_cache.align_to_largest_stylebox) ? theme_cache.style_margin_left : style->get_margin(SIDE_LEFT);
const float style_margin_right = (theme_cache.align_to_largest_stylebox) ? theme_cache.style_margin_right : style->get_margin(SIDE_RIGHT);
const float style_margin_top = (theme_cache.align_to_largest_stylebox) ? theme_cache.style_margin_top : style->get_margin(SIDE_TOP);
const float style_margin_bottom = (theme_cache.align_to_largest_stylebox) ? theme_cache.style_margin_bottom : style->get_margin(SIDE_BOTTOM);
Size2 drawable_size_remained = size;
{ // The size after the stelybox is stripped.
drawable_size_remained.width -= style_margin_left + style_margin_right;
drawable_size_remained.height -= style_margin_top + style_margin_bottom;
}
const int h_separation = MAX(0, theme_cache.h_separation);
float left_internal_margin_with_h_separation = _internal_margin[SIDE_LEFT];
float right_internal_margin_with_h_separation = _internal_margin[SIDE_RIGHT];
{ // The width reserved for internal element in derived classes (and h_separation if needed).
if (_internal_margin[SIDE_LEFT] > 0.0f) {
left_internal_margin_with_h_separation += h_separation;
}
if (_internal_margin[SIDE_RIGHT] > 0.0f) {
right_internal_margin_with_h_separation += h_separation;
}
drawable_size_remained.width -= left_internal_margin_with_h_separation + right_internal_margin_with_h_separation; // The size after the internal element is stripped.
}
HorizontalAlignment icon_align_rtl_checked = horizontal_icon_alignment;
HorizontalAlignment align_rtl_checked = alignment;
// Swap icon and text alignment sides if right-to-left layout is set.
if (is_layout_rtl()) {
if (horizontal_icon_alignment == HORIZONTAL_ALIGNMENT_RIGHT) {
icon_align_rtl_checked = HORIZONTAL_ALIGNMENT_LEFT;
} else if (horizontal_icon_alignment == HORIZONTAL_ALIGNMENT_LEFT) {
icon_align_rtl_checked = HORIZONTAL_ALIGNMENT_RIGHT;
}
if (alignment == HORIZONTAL_ALIGNMENT_RIGHT) {
align_rtl_checked = HORIZONTAL_ALIGNMENT_LEFT;
} else if (alignment == HORIZONTAL_ALIGNMENT_LEFT) {
align_rtl_checked = HORIZONTAL_ALIGNMENT_RIGHT;
}
}
Color font_color;
Color icon_modulate_color(1, 1, 1, 1);
// Get the font color and icon modulate color in the current state.
switch (get_draw_mode()) {
case DRAW_NORMAL: {
// Focus colors only take precedence over normal state.
if (has_focus()) {
font_color = theme_cache.font_focus_color;
if (has_theme_color(SNAME("icon_focus_color"))) {
icon_modulate_color = theme_cache.icon_focus_color;
}
} else {
font_color = theme_cache.font_color;
if (has_theme_color(SNAME("icon_normal_color"))) {
icon_modulate_color = theme_cache.icon_normal_color;
}
}
} break;
case DRAW_HOVER_PRESSED: {
// Edge case for CheckButton and CheckBox.
if (has_theme_stylebox("hover_pressed")) {
if (has_theme_color(SNAME("font_hover_pressed_color"))) {
font_color = theme_cache.font_hover_pressed_color;
}
if (has_theme_color(SNAME("icon_hover_pressed_color"))) {
icon_modulate_color = theme_cache.icon_hover_pressed_color;
}
break;
}
}
[[fallthrough]];
case DRAW_PRESSED: {
if (has_theme_color(SNAME("font_pressed_color"))) {
font_color = theme_cache.font_pressed_color;
} else {
font_color = theme_cache.font_color;
}
if (has_theme_color(SNAME("icon_pressed_color"))) {
icon_modulate_color = theme_cache.icon_pressed_color;
}
} break;
case DRAW_HOVER: {
font_color = theme_cache.font_hover_color;
if (has_theme_color(SNAME("icon_hover_color"))) {
icon_modulate_color = theme_cache.icon_hover_color;
}
} break;
case DRAW_DISABLED: {
font_color = theme_cache.font_disabled_color;
if (has_theme_color(SNAME("icon_disabled_color"))) {
icon_modulate_color = theme_cache.icon_disabled_color;
} else {
icon_modulate_color.a = 0.4;
}
} break;
}
const bool is_clipped = clip_text || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING || autowrap_mode != TextServer::AUTOWRAP_OFF;
const Size2 custom_element_size = drawable_size_remained;
// Draw the icon.
if (_icon.is_valid()) {
Size2 icon_size;
{ // Calculate the drawing size of the icon.
icon_size = _icon->get_size();
if (expand_icon) {
const Size2 text_buf_size = text_buf->get_size();
Size2 _size = custom_element_size;
if (!is_clipped && icon_align_rtl_checked != HORIZONTAL_ALIGNMENT_CENTER && text_buf_size.width > 0.0f) {
// If there is not enough space for icon and h_separation, h_separation will occupy the space first,
// so the icon's width may be negative. Keep it negative to make it easier to calculate the space
// reserved for text later.
_size.width -= text_buf_size.width + h_separation;
}
if (vertical_icon_alignment != VERTICAL_ALIGNMENT_CENTER) {
_size.height -= text_buf_size.height;
}
float icon_width = icon_size.width * _size.height / icon_size.height;
float icon_height = _size.height;
if (icon_width > _size.width) {
icon_width = _size.width;
icon_height = icon_size.height * icon_width / icon_size.width;
}
icon_size = Size2(icon_width, icon_height);
}
icon_size = _fit_icon_size(icon_size);
icon_size = icon_size.round();
}
if (icon_size.width > 0.0f) {
// Calculate the drawing position of the icon.
Point2 icon_ofs;
switch (icon_align_rtl_checked) {
case HORIZONTAL_ALIGNMENT_CENTER: {
icon_ofs.x = (custom_element_size.width - icon_size.width) / 2.0f;
}
[[fallthrough]];
case HORIZONTAL_ALIGNMENT_FILL:
case HORIZONTAL_ALIGNMENT_LEFT: {
icon_ofs.x += style_margin_left;
icon_ofs.x += left_internal_margin_with_h_separation;
} break;
case HORIZONTAL_ALIGNMENT_RIGHT: {
icon_ofs.x = size.x - style_margin_right;
icon_ofs.x -= right_internal_margin_with_h_separation;
icon_ofs.x -= icon_size.width;
} break;
}
switch (vertical_icon_alignment) {
case VERTICAL_ALIGNMENT_CENTER: {
icon_ofs.y = (custom_element_size.height - icon_size.height) / 2.0f;
}
[[fallthrough]];
case VERTICAL_ALIGNMENT_FILL:
case VERTICAL_ALIGNMENT_TOP: {
icon_ofs.y += style_margin_top;
} break;
case VERTICAL_ALIGNMENT_BOTTOM: {
icon_ofs.y = size.y - style_margin_bottom - icon_size.height;
} break;
}
icon_ofs = icon_ofs.floor();
Rect2 icon_region = Rect2(icon_ofs, icon_size);
draw_texture_rect(_icon, icon_region, false, icon_modulate_color);
}
if (!xl_text.is_empty()) {
// Update the size after the icon is stripped. Stripping only when the icon alignments are not center.
if (icon_align_rtl_checked != HORIZONTAL_ALIGNMENT_CENTER) {
// Subtract the space's width occupied by icon and h_separation together.
drawable_size_remained.width -= icon_size.width + h_separation;
}
if (vertical_icon_alignment != VERTICAL_ALIGNMENT_CENTER) {
drawable_size_remained.height -= icon_size.height;
}
}
}
// Draw the text.
if (!xl_text.is_empty()) {
text_buf->set_alignment(align_rtl_checked);
float text_buf_width = Math::ceil(MAX(1.0f, drawable_size_remained.width)); // The space's width filled by the text_buf.
text_buf->set_width(text_buf_width);
Point2 text_ofs;
switch (align_rtl_checked) {
case HORIZONTAL_ALIGNMENT_CENTER: {
text_ofs.x = (drawable_size_remained.width - text_buf_width) / 2.0f;
}
[[fallthrough]];
case HORIZONTAL_ALIGNMENT_FILL:
case HORIZONTAL_ALIGNMENT_LEFT:
case HORIZONTAL_ALIGNMENT_RIGHT: {
text_ofs.x += style_margin_left;
text_ofs.x += left_internal_margin_with_h_separation;
if (icon_align_rtl_checked == HORIZONTAL_ALIGNMENT_LEFT) {
// Offset by the space's width that occupied by icon and h_separation together.
text_ofs.x += custom_element_size.width - drawable_size_remained.width;
}
} break;
}
text_ofs.y = (drawable_size_remained.height - text_buf->get_size().height) / 2.0f + style_margin_top;
if (vertical_icon_alignment == VERTICAL_ALIGNMENT_TOP) {
text_ofs.y += custom_element_size.height - drawable_size_remained.height; // Offset by the icon's height.
}
Color font_outline_color = theme_cache.font_outline_color;
int outline_size = theme_cache.outline_size;
if (outline_size > 0 && font_outline_color.a > 0.0f) {
text_buf->draw_outline(ci, text_ofs, outline_size, font_outline_color);
}
text_buf->draw(ci, text_ofs, font_color);
}
} break;
}
}
Size2 Button::_fit_icon_size(const Size2 &p_size) const {
int max_width = theme_cache.icon_max_width;
Size2 icon_size = p_size;
if (max_width > 0 && icon_size.width > max_width) {
icon_size.height = icon_size.height * max_width / icon_size.width;
icon_size.width = max_width;
}
return icon_size;
}
Size2 Button::get_minimum_size_for_text_and_icon(const String &p_text, Ref<Texture2D> p_icon) const {
// Do not include `_internal_margin`, it's already added in the `get_minimum_size` overrides.
Ref<TextParagraph> paragraph;
if (p_text.is_empty()) {
paragraph = text_buf;
} else {
paragraph.instantiate();
const_cast<Button *>(this)->_shape(paragraph, p_text);
}
Size2 minsize = paragraph->get_size();
if (clip_text || overrun_behavior != TextServer::OVERRUN_NO_TRIMMING || autowrap_mode != TextServer::AUTOWRAP_OFF) {
minsize.width = 0;
}
if (!expand_icon && p_icon.is_valid()) {
Size2 icon_size = _fit_icon_size(p_icon->get_size());
if (vertical_icon_alignment == VERTICAL_ALIGNMENT_CENTER) {
minsize.height = MAX(minsize.height, icon_size.height);
} else {
minsize.height += icon_size.height;
}
if (horizontal_icon_alignment != HORIZONTAL_ALIGNMENT_CENTER) {
minsize.width += icon_size.width;
if (!xl_text.is_empty() || !p_text.is_empty()) {
minsize.width += MAX(0, theme_cache.h_separation);
}
} else {
minsize.width = MAX(minsize.width, icon_size.width);
}
}
if (!xl_text.is_empty() || !p_text.is_empty()) {
Ref<Font> font = theme_cache.font;
float font_height = font->get_height(theme_cache.font_size);
if (vertical_icon_alignment == VERTICAL_ALIGNMENT_CENTER) {
minsize.height = MAX(font_height, minsize.height);
} else {
minsize.height += font_height;
}
}
return (theme_cache.align_to_largest_stylebox ? _get_largest_stylebox_size() : _get_current_stylebox()->get_minimum_size()) + minsize;
}
void Button::_shape(Ref<TextParagraph> p_paragraph, String p_text) {
if (p_paragraph.is_null()) {
p_paragraph = text_buf;
}
if (p_text.is_empty()) {
p_text = xl_text;
}
p_paragraph->clear();
Ref<Font> font = theme_cache.font;
int font_size = theme_cache.font_size;
if (font.is_null() || font_size == 0) {
// Can't shape without a valid font and a non-zero size.
return;
}
BitField<TextServer::LineBreakFlag> autowrap_flags = TextServer::BREAK_MANDATORY;
switch (autowrap_mode) {
case TextServer::AUTOWRAP_WORD_SMART:
autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY;
break;
case TextServer::AUTOWRAP_WORD:
autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY;
break;
case TextServer::AUTOWRAP_ARBITRARY:
autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY;
break;
case TextServer::AUTOWRAP_OFF:
break;
}
autowrap_flags = autowrap_flags | TextServer::BREAK_TRIM_EDGE_SPACES;
p_paragraph->set_break_flags(autowrap_flags);
if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
p_paragraph->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
} else {
p_paragraph->set_direction((TextServer::Direction)text_direction);
}
p_paragraph->add_string(p_text, font, font_size, language);
p_paragraph->set_text_overrun_behavior(overrun_behavior);
}
void Button::set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior) {
if (overrun_behavior != p_behavior) {
bool need_update_cache = overrun_behavior == TextServer::OVERRUN_NO_TRIMMING || p_behavior == TextServer::OVERRUN_NO_TRIMMING;
overrun_behavior = p_behavior;
_shape();
if (need_update_cache) {
_queue_update_size_cache();
}
queue_redraw();
update_minimum_size();
}
}
TextServer::OverrunBehavior Button::get_text_overrun_behavior() const {
return overrun_behavior;
}
void Button::set_text(const String &p_text) {
if (text != p_text) {
text = p_text;
xl_text = atr(text);
_shape();
queue_redraw();
update_minimum_size();
}
}
String Button::get_text() const {
return text;
}
void Button::set_autowrap_mode(TextServer::AutowrapMode p_mode) {
if (autowrap_mode != p_mode) {
autowrap_mode = p_mode;
_shape();
queue_redraw();
update_minimum_size();
}
}
TextServer::AutowrapMode Button::get_autowrap_mode() const {
return autowrap_mode;
}
void Button::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
_shape();
queue_redraw();
}
}
Control::TextDirection Button::get_text_direction() const {
return text_direction;
}
void Button::set_language(const String &p_language) {
if (language != p_language) {
language = p_language;
_shape();
queue_redraw();
}
}
String Button::get_language() const {
return language;
}
void Button::set_icon(const Ref<Texture2D> &p_icon) {
if (icon == p_icon) {
return;
}
if (icon.is_valid()) {
icon->disconnect_changed(callable_mp(this, &Button::_texture_changed));
}
icon = p_icon;
if (icon.is_valid()) {
icon->connect_changed(callable_mp(this, &Button::_texture_changed));
}
queue_redraw();
update_minimum_size();
}
void Button::_texture_changed() {
queue_redraw();
update_minimum_size();
}
Ref<Texture2D> Button::get_icon() const {
return icon;
}
void Button::set_expand_icon(bool p_enabled) {
if (expand_icon != p_enabled) {
expand_icon = p_enabled;
_queue_update_size_cache();
queue_redraw();
update_minimum_size();
}
}
bool Button::is_expand_icon() const {
return expand_icon;
}
void Button::set_flat(bool p_enabled) {
if (flat != p_enabled) {
flat = p_enabled;
queue_redraw();
}
}
bool Button::is_flat() const {
return flat;
}
void Button::set_clip_text(bool p_enabled) {
if (clip_text != p_enabled) {
clip_text = p_enabled;
_queue_update_size_cache();
queue_redraw();
update_minimum_size();
}
}
bool Button::get_clip_text() const {
return clip_text;
}
void Button::set_text_alignment(HorizontalAlignment p_alignment) {
if (alignment != p_alignment) {
alignment = p_alignment;
queue_redraw();
}
}
HorizontalAlignment Button::get_text_alignment() const {
return alignment;
}
void Button::set_icon_alignment(HorizontalAlignment p_alignment) {
if (horizontal_icon_alignment == p_alignment) {
return;
}
horizontal_icon_alignment = p_alignment;
update_minimum_size();
queue_redraw();
}
void Button::set_vertical_icon_alignment(VerticalAlignment p_alignment) {
if (vertical_icon_alignment == p_alignment) {
return;
}
bool need_update_cache = vertical_icon_alignment == VERTICAL_ALIGNMENT_CENTER || p_alignment == VERTICAL_ALIGNMENT_CENTER;
vertical_icon_alignment = p_alignment;
if (need_update_cache) {
_queue_update_size_cache();
}
update_minimum_size();
queue_redraw();
}
HorizontalAlignment Button::get_icon_alignment() const {
return horizontal_icon_alignment;
}
VerticalAlignment Button::get_vertical_icon_alignment() const {
return vertical_icon_alignment;
}
void Button::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_text", "text"), &Button::set_text);
ClassDB::bind_method(D_METHOD("get_text"), &Button::get_text);
ClassDB::bind_method(D_METHOD("set_text_overrun_behavior", "overrun_behavior"), &Button::set_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("get_text_overrun_behavior"), &Button::get_text_overrun_behavior);
ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &Button::set_autowrap_mode);
ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &Button::get_autowrap_mode);
ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &Button::set_text_direction);
ClassDB::bind_method(D_METHOD("get_text_direction"), &Button::get_text_direction);
ClassDB::bind_method(D_METHOD("set_language", "language"), &Button::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &Button::get_language);
ClassDB::bind_method(D_METHOD("set_button_icon", "texture"), &Button::set_icon);
ClassDB::bind_method(D_METHOD("get_button_icon"), &Button::get_icon);
ClassDB::bind_method(D_METHOD("set_flat", "enabled"), &Button::set_flat);
ClassDB::bind_method(D_METHOD("is_flat"), &Button::is_flat);
ClassDB::bind_method(D_METHOD("set_clip_text", "enabled"), &Button::set_clip_text);
ClassDB::bind_method(D_METHOD("get_clip_text"), &Button::get_clip_text);
ClassDB::bind_method(D_METHOD("set_text_alignment", "alignment"), &Button::set_text_alignment);
ClassDB::bind_method(D_METHOD("get_text_alignment"), &Button::get_text_alignment);
ClassDB::bind_method(D_METHOD("set_icon_alignment", "icon_alignment"), &Button::set_icon_alignment);
ClassDB::bind_method(D_METHOD("get_icon_alignment"), &Button::get_icon_alignment);
ClassDB::bind_method(D_METHOD("set_vertical_icon_alignment", "vertical_icon_alignment"), &Button::set_vertical_icon_alignment);
ClassDB::bind_method(D_METHOD("get_vertical_icon_alignment"), &Button::get_vertical_icon_alignment);
ClassDB::bind_method(D_METHOD("set_expand_icon", "enabled"), &Button::set_expand_icon);
ClassDB::bind_method(D_METHOD("is_expand_icon"), &Button::is_expand_icon);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_button_icon", "get_button_icon");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
ADD_GROUP("Text Behavior", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_text_alignment", "get_text_alignment");
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::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "clip_text"), "set_clip_text", "get_clip_text");
ADD_GROUP("Icon Behavior", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "icon_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right"), "set_icon_alignment", "get_icon_alignment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_icon_alignment", PROPERTY_HINT_ENUM, "Top,Center,Bottom"), "set_vertical_icon_alignment", "get_vertical_icon_alignment");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "expand_icon"), "set_expand_icon", "is_expand_icon");
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_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Button, normal);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Button, normal_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Button, pressed);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Button, pressed_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Button, hover);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Button, hover_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Button, hover_pressed);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Button, hover_pressed_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Button, disabled);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Button, disabled_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, Button, focus);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Button, font_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Button, font_focus_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Button, font_pressed_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Button, font_hover_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Button, font_hover_pressed_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Button, font_disabled_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, Button, font);
BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, Button, font_size);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Button, outline_size);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Button, font_outline_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Button, icon_normal_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Button, icon_focus_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Button, icon_pressed_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Button, icon_hover_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Button, icon_hover_pressed_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, Button, icon_disabled_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, Button, icon);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Button, h_separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Button, icon_max_width);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Button, align_to_largest_stylebox);
}
Button::Button(const String &p_text) {
text_buf.instantiate();
text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_TRIM_EDGE_SPACES);
set_mouse_filter(MOUSE_FILTER_STOP);
set_text(p_text);
}
Button::~Button() {
}

164
engine/scene/gui/button.h Normal file
View file

@ -0,0 +1,164 @@
/**************************************************************************/
/* button.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. */
/**************************************************************************/
#ifndef BUTTON_H
#define BUTTON_H
#include "scene/gui/base_button.h"
#include "scene/resources/text_paragraph.h"
class Button : public BaseButton {
GDCLASS(Button, BaseButton);
private:
bool flat = false;
String text;
String xl_text;
Ref<TextParagraph> text_buf;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF;
TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING;
Ref<Texture2D> icon;
bool expand_icon = false;
bool clip_text = false;
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_CENTER;
HorizontalAlignment horizontal_icon_alignment = HORIZONTAL_ALIGNMENT_LEFT;
VerticalAlignment vertical_icon_alignment = VERTICAL_ALIGNMENT_CENTER;
float _internal_margin[4] = {};
struct ThemeCache {
Ref<StyleBox> normal;
Ref<StyleBox> normal_mirrored;
Ref<StyleBox> pressed;
Ref<StyleBox> pressed_mirrored;
Ref<StyleBox> hover;
Ref<StyleBox> hover_mirrored;
Ref<StyleBox> hover_pressed;
Ref<StyleBox> hover_pressed_mirrored;
Ref<StyleBox> disabled;
Ref<StyleBox> disabled_mirrored;
Ref<StyleBox> focus;
Size2 max_style_size;
float style_margin_left = 0;
float style_margin_right = 0;
float style_margin_top = 0;
float style_margin_bottom = 0;
bool align_to_largest_stylebox = false;
Color font_color;
Color font_focus_color;
Color font_pressed_color;
Color font_hover_color;
Color font_hover_pressed_color;
Color font_disabled_color;
Ref<Font> font;
int font_size = 0;
int outline_size = 0;
Color font_outline_color;
Color icon_normal_color;
Color icon_focus_color;
Color icon_pressed_color;
Color icon_hover_color;
Color icon_hover_pressed_color;
Color icon_disabled_color;
Ref<Texture2D> icon;
int h_separation = 0;
int icon_max_width = 0;
} theme_cache;
void _shape(Ref<TextParagraph> p_paragraph = Ref<TextParagraph>(), String p_text = "");
void _texture_changed();
protected:
virtual void _update_theme_item_cache() override;
void _set_internal_margin(Side p_side, float p_value);
virtual void _queue_update_size_cache();
Size2 _fit_icon_size(const Size2 &p_size) const;
Ref<StyleBox> _get_current_stylebox() const;
Size2 _get_largest_stylebox_size() const;
void _notification(int p_what);
static void _bind_methods();
public:
virtual Size2 get_minimum_size() const override;
Size2 get_minimum_size_for_text_and_icon(const String &p_text, Ref<Texture2D> p_icon) const;
void set_text(const String &p_text);
String get_text() const;
void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior);
TextServer::OverrunBehavior get_text_overrun_behavior() const;
void set_autowrap_mode(TextServer::AutowrapMode p_mode);
TextServer::AutowrapMode get_autowrap_mode() const;
void set_text_direction(TextDirection p_text_direction);
TextDirection get_text_direction() const;
void set_language(const String &p_language);
String get_language() const;
void set_icon(const Ref<Texture2D> &p_icon);
Ref<Texture2D> get_icon() const;
void set_expand_icon(bool p_enabled);
bool is_expand_icon() const;
void set_flat(bool p_enabled);
bool is_flat() const;
void set_clip_text(bool p_enabled);
bool get_clip_text() const;
void set_text_alignment(HorizontalAlignment p_alignment);
HorizontalAlignment get_text_alignment() const;
void set_icon_alignment(HorizontalAlignment p_alignment);
void set_vertical_icon_alignment(VerticalAlignment p_alignment);
HorizontalAlignment get_icon_alignment() const;
VerticalAlignment get_vertical_icon_alignment() const;
Button(const String &p_text = String());
~Button();
};
#endif // BUTTON_H

View file

@ -0,0 +1,97 @@
/**************************************************************************/
/* center_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 "center_container.h"
Size2 CenterContainer::get_minimum_size() const {
if (use_top_left) {
return Size2();
}
Size2 ms;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE);
if (!c) {
continue;
}
Size2 minsize = c->get_combined_minimum_size();
ms = ms.max(minsize);
}
return ms;
}
void CenterContainer::set_use_top_left(bool p_enable) {
if (use_top_left == p_enable) {
return;
}
use_top_left = p_enable;
update_minimum_size();
queue_sort();
}
bool CenterContainer::is_using_top_left() const {
return use_top_left;
}
Vector<int> CenterContainer::get_allowed_size_flags_horizontal() const {
return Vector<int>();
}
Vector<int> CenterContainer::get_allowed_size_flags_vertical() const {
return Vector<int>();
}
void CenterContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_SORT_CHILDREN: {
Size2 size = get_size();
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
Size2 minsize = c->get_combined_minimum_size();
Point2 ofs = use_top_left ? (-minsize * 0.5).floor() : ((size - minsize) / 2.0).floor();
fit_child_in_rect(c, Rect2(ofs, minsize));
}
} break;
}
}
void CenterContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_use_top_left", "enable"), &CenterContainer::set_use_top_left);
ClassDB::bind_method(D_METHOD("is_using_top_left"), &CenterContainer::is_using_top_left);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_top_left"), "set_use_top_left", "is_using_top_left");
}
CenterContainer::CenterContainer() {}

View file

@ -0,0 +1,57 @@
/**************************************************************************/
/* center_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. */
/**************************************************************************/
#ifndef CENTER_CONTAINER_H
#define CENTER_CONTAINER_H
#include "scene/gui/container.h"
class CenterContainer : public Container {
GDCLASS(CenterContainer, Container);
bool use_top_left = false;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void set_use_top_left(bool p_enable);
bool is_using_top_left() const;
virtual Size2 get_minimum_size() const override;
virtual Vector<int> get_allowed_size_flags_horizontal() const override;
virtual Vector<int> get_allowed_size_flags_vertical() const override;
CenterContainer();
};
#endif // CENTER_CONTAINER_H

View file

@ -0,0 +1,171 @@
/**************************************************************************/
/* check_box.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 "check_box.h"
#include "scene/theme/theme_db.h"
#include "servers/rendering_server.h"
Size2 CheckBox::get_icon_size() const {
Size2 tex_size = Size2(0, 0);
if (!theme_cache.checked.is_null()) {
tex_size = theme_cache.checked->get_size();
}
if (!theme_cache.unchecked.is_null()) {
tex_size = tex_size.max(theme_cache.unchecked->get_size());
}
if (!theme_cache.radio_checked.is_null()) {
tex_size = tex_size.max(theme_cache.radio_checked->get_size());
}
if (!theme_cache.radio_unchecked.is_null()) {
tex_size = tex_size.max(theme_cache.radio_unchecked->get_size());
}
if (!theme_cache.checked_disabled.is_null()) {
tex_size = tex_size.max(theme_cache.checked_disabled->get_size());
}
if (!theme_cache.unchecked_disabled.is_null()) {
tex_size = tex_size.max(theme_cache.unchecked_disabled->get_size());
}
if (!theme_cache.radio_checked_disabled.is_null()) {
tex_size = tex_size.max(theme_cache.radio_checked_disabled->get_size());
}
if (!theme_cache.radio_unchecked_disabled.is_null()) {
tex_size = tex_size.max(theme_cache.radio_unchecked_disabled->get_size());
}
return _fit_icon_size(tex_size);
}
Size2 CheckBox::get_minimum_size() const {
Size2 minsize = Button::get_minimum_size();
const Size2 tex_size = get_icon_size();
if (tex_size.width > 0 || tex_size.height > 0) {
const Size2 padding = _get_largest_stylebox_size();
Size2 content_size = minsize - padding;
if (content_size.width > 0 && tex_size.width > 0) {
content_size.width += MAX(0, theme_cache.h_separation);
}
content_size.width += tex_size.width;
content_size.height = MAX(content_size.height, tex_size.height);
minsize = content_size + padding;
}
return minsize;
}
void CheckBox::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
if (is_layout_rtl()) {
_set_internal_margin(SIDE_LEFT, 0.f);
_set_internal_margin(SIDE_RIGHT, get_icon_size().width);
} else {
_set_internal_margin(SIDE_LEFT, get_icon_size().width);
_set_internal_margin(SIDE_RIGHT, 0.f);
}
} break;
case NOTIFICATION_DRAW: {
RID ci = get_canvas_item();
Ref<Texture2D> on_tex;
Ref<Texture2D> off_tex;
if (is_radio()) {
if (is_disabled()) {
on_tex = theme_cache.radio_checked_disabled;
off_tex = theme_cache.radio_unchecked_disabled;
} else {
on_tex = theme_cache.radio_checked;
off_tex = theme_cache.radio_unchecked;
}
} else {
if (is_disabled()) {
on_tex = theme_cache.checked_disabled;
off_tex = theme_cache.unchecked_disabled;
} else {
on_tex = theme_cache.checked;
off_tex = theme_cache.unchecked;
}
}
Vector2 ofs;
if (is_layout_rtl()) {
ofs.x = get_size().x - theme_cache.normal_style->get_margin(SIDE_RIGHT) - get_icon_size().width;
} else {
ofs.x = theme_cache.normal_style->get_margin(SIDE_LEFT);
}
ofs.y = int((get_size().height - get_icon_size().height) / 2) + theme_cache.check_v_offset;
if (is_pressed()) {
on_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(on_tex->get_size())));
} else {
off_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(off_tex->get_size())));
}
} break;
}
}
bool CheckBox::is_radio() {
return get_button_group().is_valid();
}
void CheckBox::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, CheckBox, h_separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, CheckBox, check_v_offset);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, CheckBox, normal_style, "normal");
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckBox, checked);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckBox, unchecked);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckBox, radio_checked);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckBox, radio_unchecked);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckBox, checked_disabled);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckBox, unchecked_disabled);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckBox, radio_checked_disabled);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckBox, radio_unchecked_disabled);
}
CheckBox::CheckBox(const String &p_text) :
Button(p_text) {
set_toggle_mode(true);
set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
if (is_layout_rtl()) {
_set_internal_margin(SIDE_RIGHT, get_icon_size().width);
} else {
_set_internal_margin(SIDE_LEFT, get_icon_size().width);
}
}
CheckBox::~CheckBox() {
}

View file

@ -0,0 +1,68 @@
/**************************************************************************/
/* check_box.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. */
/**************************************************************************/
#ifndef CHECK_BOX_H
#define CHECK_BOX_H
#include "scene/gui/button.h"
class CheckBox : public Button {
GDCLASS(CheckBox, Button);
struct ThemeCache {
int h_separation = 0;
int check_v_offset = 0;
Ref<StyleBox> normal_style;
Ref<Texture2D> checked;
Ref<Texture2D> unchecked;
Ref<Texture2D> radio_checked;
Ref<Texture2D> radio_unchecked;
Ref<Texture2D> checked_disabled;
Ref<Texture2D> unchecked_disabled;
Ref<Texture2D> radio_checked_disabled;
Ref<Texture2D> radio_unchecked_disabled;
} theme_cache;
protected:
Size2 get_icon_size() const;
Size2 get_minimum_size() const override;
void _notification(int p_what);
static void _bind_methods();
bool is_radio();
public:
CheckBox(const String &p_text = String());
~CheckBox();
};
#endif // CHECK_BOX_H

View file

@ -0,0 +1,174 @@
/**************************************************************************/
/* check_button.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 "check_button.h"
#include "scene/theme/theme_db.h"
#include "servers/rendering_server.h"
Size2 CheckButton::get_icon_size() const {
Ref<Texture2D> on_tex;
Ref<Texture2D> off_tex;
if (is_layout_rtl()) {
if (is_disabled()) {
on_tex = theme_cache.checked_disabled_mirrored;
off_tex = theme_cache.unchecked_disabled_mirrored;
} else {
on_tex = theme_cache.checked_mirrored;
off_tex = theme_cache.unchecked_mirrored;
}
} else {
if (is_disabled()) {
on_tex = theme_cache.checked_disabled;
off_tex = theme_cache.unchecked_disabled;
} else {
on_tex = theme_cache.checked;
off_tex = theme_cache.unchecked;
}
}
Size2 tex_size = Size2(0, 0);
if (!on_tex.is_null()) {
tex_size = on_tex->get_size();
}
if (!off_tex.is_null()) {
tex_size = tex_size.max(off_tex->get_size());
}
return _fit_icon_size(tex_size);
}
Size2 CheckButton::get_minimum_size() const {
Size2 minsize = Button::get_minimum_size();
const Size2 tex_size = get_icon_size();
if (tex_size.width > 0 || tex_size.height > 0) {
const Size2 padding = _get_largest_stylebox_size();
Size2 content_size = minsize - padding;
if (content_size.width > 0 && tex_size.width > 0) {
content_size.width += MAX(0, theme_cache.h_separation);
}
content_size.width += tex_size.width;
content_size.height = MAX(content_size.height, tex_size.height);
minsize = content_size + padding;
}
return minsize;
}
void CheckButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
if (is_layout_rtl()) {
_set_internal_margin(SIDE_LEFT, get_icon_size().width);
_set_internal_margin(SIDE_RIGHT, 0.f);
} else {
_set_internal_margin(SIDE_LEFT, 0.f);
_set_internal_margin(SIDE_RIGHT, get_icon_size().width);
}
} break;
case NOTIFICATION_DRAW: {
RID ci = get_canvas_item();
bool rtl = is_layout_rtl();
Ref<Texture2D> on_tex;
Ref<Texture2D> off_tex;
if (rtl) {
if (is_disabled()) {
on_tex = theme_cache.checked_disabled_mirrored;
off_tex = theme_cache.unchecked_disabled_mirrored;
} else {
on_tex = theme_cache.checked_mirrored;
off_tex = theme_cache.unchecked_mirrored;
}
} else {
if (is_disabled()) {
on_tex = theme_cache.checked_disabled;
off_tex = theme_cache.unchecked_disabled;
} else {
on_tex = theme_cache.checked;
off_tex = theme_cache.unchecked;
}
}
Vector2 ofs;
Size2 tex_size = get_icon_size();
if (rtl) {
ofs.x = theme_cache.normal_style->get_margin(SIDE_LEFT);
} else {
ofs.x = get_size().width - (tex_size.width + theme_cache.normal_style->get_margin(SIDE_RIGHT));
}
ofs.y = (get_size().height - tex_size.height) / 2 + theme_cache.check_v_offset;
if (is_pressed()) {
on_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(on_tex->get_size())));
} else {
off_tex->draw_rect(ci, Rect2(ofs, _fit_icon_size(off_tex->get_size())));
}
} break;
}
}
void CheckButton::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, CheckButton, h_separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, CheckButton, check_v_offset);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, CheckButton, normal_style, "normal");
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckButton, checked);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckButton, unchecked);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckButton, checked_disabled);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckButton, unchecked_disabled);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckButton, checked_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckButton, unchecked_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckButton, checked_disabled_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, CheckButton, unchecked_disabled_mirrored);
}
CheckButton::CheckButton(const String &p_text) :
Button(p_text) {
set_toggle_mode(true);
set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
if (is_layout_rtl()) {
_set_internal_margin(SIDE_LEFT, get_icon_size().width);
} else {
_set_internal_margin(SIDE_RIGHT, get_icon_size().width);
}
}
CheckButton::~CheckButton() {
}

View file

@ -0,0 +1,66 @@
/**************************************************************************/
/* check_button.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. */
/**************************************************************************/
#ifndef CHECK_BUTTON_H
#define CHECK_BUTTON_H
#include "scene/gui/button.h"
class CheckButton : public Button {
GDCLASS(CheckButton, Button);
struct ThemeCache {
int h_separation = 0;
int check_v_offset = 0;
Ref<StyleBox> normal_style;
Ref<Texture2D> checked;
Ref<Texture2D> unchecked;
Ref<Texture2D> checked_disabled;
Ref<Texture2D> unchecked_disabled;
Ref<Texture2D> checked_mirrored;
Ref<Texture2D> unchecked_mirrored;
Ref<Texture2D> checked_disabled_mirrored;
Ref<Texture2D> unchecked_disabled_mirrored;
} theme_cache;
protected:
Size2 get_icon_size() const;
virtual Size2 get_minimum_size() const override;
void _notification(int p_what);
static void _bind_methods();
public:
CheckButton(const String &p_text = String());
~CheckButton();
};
#endif // CHECK_BUTTON_H

View file

@ -0,0 +1,46 @@
/**************************************************************************/
/* code_edit.compat.inc */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
String CodeEdit::_get_text_for_symbol_lookup_bind_compat_73196() {
return get_text_for_symbol_lookup();
}
void CodeEdit::_add_code_completion_option_compat_84906(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color, const Ref<Resource> &p_icon, const Variant &p_value, int p_location) {
add_code_completion_option(p_type, p_display_text, p_insert_text, p_text_color, p_icon, p_value, p_location);
}
void CodeEdit::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("get_text_for_symbol_lookup"), &CodeEdit::_get_text_for_symbol_lookup_bind_compat_73196);
ClassDB::bind_compatibility_method(D_METHOD("add_code_completion_option", "type", "display_text", "insert_text", "text_color", "icon", "value", "location"), &CodeEdit::_add_code_completion_option_compat_84906, DEFVAL(Color(1, 1, 1)), DEFVAL(Ref<Resource>()), DEFVAL(Variant::NIL), DEFVAL(LOCATION_OTHER));
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,515 @@
/**************************************************************************/
/* code_edit.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. */
/**************************************************************************/
#ifndef CODE_EDIT_H
#define CODE_EDIT_H
#include "core/object/script_language.h"
#include "scene/gui/text_edit.h"
class CodeEdit : public TextEdit {
GDCLASS(CodeEdit, TextEdit)
public:
// Keep enums in sync with:
// core/object/script_language.h - ScriptLanguage::CodeCompletionKind
enum CodeCompletionKind {
KIND_CLASS,
KIND_FUNCTION,
KIND_SIGNAL,
KIND_VARIABLE,
KIND_MEMBER,
KIND_ENUM,
KIND_CONSTANT,
KIND_NODE_PATH,
KIND_FILE_PATH,
KIND_PLAIN_TEXT,
};
// core/object/script_language.h - ScriptLanguage::CodeCompletionLocation
enum CodeCompletionLocation {
LOCATION_LOCAL = 0,
LOCATION_PARENT_MASK = 1 << 8,
LOCATION_OTHER_USER_CODE = 1 << 9,
LOCATION_OTHER = 1 << 10,
};
private:
/* Indent management */
int indent_size = 4;
String indent_text = "\t";
bool auto_indent = false;
HashSet<char32_t> auto_indent_prefixes;
bool indent_using_spaces = false;
int _calculate_spaces_till_next_left_indent(int p_column) const;
int _calculate_spaces_till_next_right_indent(int p_column) const;
void _new_line(bool p_split_current_line = true, bool p_above = false);
/* Auto brace completion */
bool auto_brace_completion_enabled = false;
/* BracePair open_key must be uniquie and ordered by length. */
struct BracePair {
String open_key = "";
String close_key = "";
};
Vector<BracePair> auto_brace_completion_pairs;
int _get_auto_brace_pair_open_at_pos(int p_line, int p_col);
int _get_auto_brace_pair_close_at_pos(int p_line, int p_col);
/* Main Gutter */
enum MainGutterType {
MAIN_GUTTER_BREAKPOINT = 0x01,
MAIN_GUTTER_BOOKMARK = 0x02,
MAIN_GUTTER_EXECUTING = 0x04
};
int main_gutter = -1;
void _update_draw_main_gutter();
void _main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region);
// breakpoints
HashMap<int, bool> breakpointed_lines;
bool draw_breakpoints = false;
// bookmarks
bool draw_bookmarks = false;
// executing lines
bool draw_executing_lines = false;
/* Line numbers */
int line_number_gutter = -1;
int line_number_digits = 1;
String line_number_padding = " ";
void _line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region);
/* Fold Gutter */
int fold_gutter = -1;
bool draw_fold_gutter = false;
void _fold_gutter_draw_callback(int p_line, int p_gutter, Rect2 p_region);
void _gutter_clicked(int p_line, int p_gutter);
void _update_gutter_indexes();
/* Line Folding */
bool line_folding_enabled = false;
String code_region_start_string;
String code_region_end_string;
String code_region_start_tag = "region";
String code_region_end_tag = "endregion";
void _update_code_region_tags();
/* Delimiters */
enum DelimiterType {
TYPE_STRING,
TYPE_COMMENT,
};
struct Delimiter {
DelimiterType type;
String start_key = "";
String end_key = "";
bool line_only = true;
};
bool setting_delimiters = false;
Vector<Delimiter> delimiters;
/*
* Vector entry per line, contains a Map of column numbers to delimiter index, -1 marks the end of a region.
* e.g the following text will be stored as so:
*
* 0: nothing here
* 1:
* 2: # test
* 3: "test" text "multiline
* 4:
* 5: test
* 6: string"
*
* Vector [
* 0 = []
* 1 = []
* 2 = [
* 1 = 1
* 6 = -1
* ]
* 3 = [
* 1 = 0
* 6 = -1
* 13 = 0
* ]
* 4 = [
* 0 = 0
* ]
* 5 = [
* 5 = 0
* ]
* 6 = [
* 7 = -1
* ]
* ]
*/
Vector<RBMap<int, int>> delimiter_cache;
void _update_delimiter_cache(int p_from_line = 0, int p_to_line = -1);
int _is_in_delimiter(int p_line, int p_column, DelimiterType p_type) const;
void _add_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only, DelimiterType p_type);
void _remove_delimiter(const String &p_start_key, DelimiterType p_type);
bool _has_delimiter(const String &p_start_key, DelimiterType p_type) const;
void _set_delimiters(const TypedArray<String> &p_delimiters, DelimiterType p_type);
void _clear_delimiters(DelimiterType p_type);
TypedArray<String> _get_delimiters(DelimiterType p_type) const;
/* Code Hint */
String code_hint = "";
bool code_hint_draw_below = true;
int code_hint_xpos = -0xFFFF;
/* Code Completion */
bool code_completion_enabled = false;
bool code_completion_forced = false;
bool code_completion_active = false;
bool is_code_completion_scroll_hovered = false;
bool is_code_completion_scroll_pressed = false;
bool is_code_completion_drag_started = false;
Vector<ScriptLanguage::CodeCompletionOption> code_completion_options;
int code_completion_line_ofs = 0;
int code_completion_current_selected = 0;
int code_completion_force_item_center = -1;
int code_completion_longest_line = 0;
Rect2i code_completion_rect;
Rect2i code_completion_scroll_rect;
float code_completion_pan_offset = 0.0f;
HashSet<char32_t> code_completion_prefixes;
List<ScriptLanguage::CodeCompletionOption> code_completion_option_submitted;
List<ScriptLanguage::CodeCompletionOption> code_completion_option_sources;
String code_completion_base;
void _update_scroll_selected_line(float p_mouse_y);
void _filter_code_completion_candidates_impl();
bool _should_reset_selected_option_for_new_options(const Vector<ScriptLanguage::CodeCompletionOption> &p_new_options);
/* Line length guidelines */
TypedArray<int> line_length_guideline_columns;
/* Symbol lookup */
bool symbol_lookup_on_click_enabled = false;
String symbol_lookup_new_word = "";
String symbol_lookup_word = "";
Point2i symbol_lookup_pos;
/* Visual */
struct ThemeCache {
/* Gutters */
Color code_folding_color = Color(1, 1, 1);
Color folded_code_region_color = Color(1, 1, 1);
Ref<Texture2D> can_fold_icon;
Ref<Texture2D> folded_icon;
Ref<Texture2D> can_fold_code_region_icon;
Ref<Texture2D> folded_code_region_icon;
Ref<Texture2D> folded_eol_icon;
Color breakpoint_color = Color(1, 1, 1);
Ref<Texture2D> breakpoint_icon = Ref<Texture2D>();
Color bookmark_color = Color(1, 1, 1);
Ref<Texture2D> bookmark_icon = Ref<Texture2D>();
Color executing_line_color = Color(1, 1, 1);
Ref<Texture2D> executing_line_icon = Ref<Texture2D>();
Color line_number_color = Color(1, 1, 1);
/* Code Completion */
Ref<StyleBox> code_completion_style;
int code_completion_icon_separation = 0;
int code_completion_max_width = 0;
int code_completion_max_lines = 7;
int code_completion_scroll_width = 0;
Color code_completion_scroll_color = Color(0, 0, 0, 0);
Color code_completion_scroll_hovered_color = Color(0, 0, 0, 0);
Color code_completion_background_color = Color(0, 0, 0, 0);
Color code_completion_selected_color = Color(0, 0, 0, 0);
Color code_completion_existing_color = Color(0, 0, 0, 0);
/* Code hint */
Ref<StyleBox> code_hint_style;
Color code_hint_color;
/* Line length guideline */
Color line_length_guideline_color;
/* Other visuals */
Ref<StyleBox> style_normal;
Color brace_mismatch_color;
Ref<Font> font;
int font_size = 16;
int line_spacing = 1;
} theme_cache;
virtual Color _get_brace_mismatch_color() const override;
virtual Color _get_code_folding_color() const override;
virtual Ref<Texture2D> _get_folded_eol_icon() const override;
/* Callbacks */
int lines_edited_changed = 0;
int lines_edited_from = -1;
int lines_edited_to = -1;
void _lines_edited_from(int p_from_line, int p_to_line);
void _text_set();
void _text_changed();
protected:
void _notification(int p_what);
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
String _get_text_for_symbol_lookup_bind_compat_73196();
void _add_code_completion_option_compat_84906(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color = Color(1, 1, 1), const Ref<Resource> &p_icon = Ref<Resource>(), const Variant &p_value = Variant::NIL, int p_location = LOCATION_OTHER);
static void _bind_compatibility_methods();
#endif
virtual void _unhide_carets() override;
/* Text manipulation */
// Overridable actions
virtual void _handle_unicode_input_internal(const uint32_t p_unicode, int p_caret) override;
virtual void _backspace_internal(int p_caret) override;
virtual void _cut_internal(int p_caret) override;
GDVIRTUAL1(_confirm_code_completion, bool)
GDVIRTUAL1(_request_code_completion, bool)
GDVIRTUAL1RC(TypedArray<Dictionary>, _filter_code_completion_candidates, TypedArray<Dictionary>)
public:
/* General overrides */
virtual void gui_input(const Ref<InputEvent> &p_gui_input) override;
virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
/* Indent management */
void set_indent_size(const int p_size);
int get_indent_size() const;
void set_indent_using_spaces(const bool p_use_spaces);
bool is_indent_using_spaces() const;
void set_auto_indent_enabled(bool p_enabled);
bool is_auto_indent_enabled() const;
void set_auto_indent_prefixes(const TypedArray<String> &p_prefixes);
TypedArray<String> get_auto_indent_prefixes() const;
void do_indent();
void indent_lines();
void unindent_lines();
void convert_indent(int p_from_line = -1, int p_to_line = -1);
/* Auto brace completion */
void set_auto_brace_completion_enabled(bool p_enabled);
bool is_auto_brace_completion_enabled() const;
void set_highlight_matching_braces_enabled(bool p_enabled);
bool is_highlight_matching_braces_enabled() const;
void add_auto_brace_completion_pair(const String &p_open_key, const String &p_close_key);
void set_auto_brace_completion_pairs(const Dictionary &p_auto_brace_completion_pairs);
Dictionary get_auto_brace_completion_pairs() const;
bool has_auto_brace_completion_open_key(const String &p_open_key) const;
bool has_auto_brace_completion_close_key(const String &p_close_key) const;
String get_auto_brace_completion_close_key(const String &p_open_key) const;
/* Main Gutter */
void set_draw_breakpoints_gutter(bool p_draw);
bool is_drawing_breakpoints_gutter() const;
void set_draw_bookmarks_gutter(bool p_draw);
bool is_drawing_bookmarks_gutter() const;
void set_draw_executing_lines_gutter(bool p_draw);
bool is_drawing_executing_lines_gutter() const;
// breakpoints
void set_line_as_breakpoint(int p_line, bool p_breakpointed);
bool is_line_breakpointed(int p_line) const;
void clear_breakpointed_lines();
PackedInt32Array get_breakpointed_lines() const;
// bookmarks
void set_line_as_bookmarked(int p_line, bool p_bookmarked);
bool is_line_bookmarked(int p_line) const;
void clear_bookmarked_lines();
PackedInt32Array get_bookmarked_lines() const;
// executing lines
void set_line_as_executing(int p_line, bool p_executing);
bool is_line_executing(int p_line) const;
void clear_executing_lines();
PackedInt32Array get_executing_lines() const;
/* Line numbers */
void set_draw_line_numbers(bool p_draw);
bool is_draw_line_numbers_enabled() const;
void set_line_numbers_zero_padded(bool p_zero_padded);
bool is_line_numbers_zero_padded() const;
/* Fold gutter */
void set_draw_fold_gutter(bool p_draw);
bool is_drawing_fold_gutter() const;
/* Line Folding */
void set_line_folding_enabled(bool p_enabled);
bool is_line_folding_enabled() const;
bool can_fold_line(int p_line) const;
void fold_line(int p_line);
void unfold_line(int p_line);
void fold_all_lines();
void unfold_all_lines();
void toggle_foldable_line(int p_line);
void toggle_foldable_lines_at_carets();
bool is_line_folded(int p_line) const;
TypedArray<int> get_folded_lines() const;
/* Code region */
void create_code_region();
String get_code_region_start_tag() const;
String get_code_region_end_tag() const;
void set_code_region_tags(const String &p_start = "region", const String &p_end = "endregion");
bool is_line_code_region_start(int p_line) const;
bool is_line_code_region_end(int p_line) const;
/* Delimiters */
void add_string_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only = false);
void remove_string_delimiter(const String &p_start_key);
bool has_string_delimiter(const String &p_start_key) const;
void set_string_delimiters(const TypedArray<String> &p_string_delimiters);
void clear_string_delimiters();
TypedArray<String> get_string_delimiters() const;
int is_in_string(int p_line, int p_column = -1) const;
void add_comment_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only = false);
void remove_comment_delimiter(const String &p_start_key);
bool has_comment_delimiter(const String &p_start_key) const;
void set_comment_delimiters(const TypedArray<String> &p_comment_delimiters);
void clear_comment_delimiters();
TypedArray<String> get_comment_delimiters() const;
int is_in_comment(int p_line, int p_column = -1) const;
String get_delimiter_start_key(int p_delimiter_idx) const;
String get_delimiter_end_key(int p_delimiter_idx) const;
Point2 get_delimiter_start_position(int p_line, int p_column) const;
Point2 get_delimiter_end_position(int p_line, int p_column) const;
/* Code hint */
void set_code_hint(const String &p_hint);
void set_code_hint_draw_below(bool p_below);
/* Code Completion */
void set_code_completion_enabled(bool p_enable);
bool is_code_completion_enabled() const;
void set_code_completion_prefixes(const TypedArray<String> &p_prefixes);
TypedArray<String> get_code_completion_prefixes() const;
String get_text_for_code_completion() const;
void request_code_completion(bool p_force = false);
void add_code_completion_option(CodeCompletionKind p_type, const String &p_display_text, const String &p_insert_text, const Color &p_text_color = Color(1, 1, 1), const Ref<Resource> &p_icon = Ref<Resource>(), const Variant &p_value = Variant(), int p_location = LOCATION_OTHER);
void update_code_completion_options(bool p_forced = false);
TypedArray<Dictionary> get_code_completion_options() const;
Dictionary get_code_completion_option(int p_index) const;
int get_code_completion_selected_index() const;
void set_code_completion_selected_index(int p_index);
void confirm_code_completion(bool p_replace = false);
void cancel_code_completion();
/* Line length guidelines */
void set_line_length_guidelines(TypedArray<int> p_guideline_columns);
TypedArray<int> get_line_length_guidelines() const;
/* Symbol lookup */
void set_symbol_lookup_on_click_enabled(bool p_enabled);
bool is_symbol_lookup_on_click_enabled() const;
String get_text_for_symbol_lookup() const;
String get_text_with_cursor_char(int p_line, int p_column) const;
void set_symbol_lookup_word_as_valid(bool p_valid);
/* Text manipulation */
void move_lines_up();
void move_lines_down();
void delete_lines();
void duplicate_selection();
void duplicate_lines();
CodeEdit();
~CodeEdit();
};
VARIANT_ENUM_CAST(CodeEdit::CodeCompletionKind);
VARIANT_ENUM_CAST(CodeEdit::CodeCompletionLocation);
// The custom comparer which will sort completion options.
struct CodeCompletionOptionCompare {
_FORCE_INLINE_ bool operator()(const ScriptLanguage::CodeCompletionOption &l, const ScriptLanguage::CodeCompletionOption &r) const;
};
#endif // CODE_EDIT_H

View file

@ -0,0 +1,406 @@
/**************************************************************************/
/* color_mode.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 "color_mode.h"
#include "core/math/color.h"
#include "scene/gui/slider.h"
#include "thirdparty/misc/ok_color.h"
ColorMode::ColorMode(ColorPicker *p_color_picker) {
color_picker = p_color_picker;
}
String ColorModeRGB::get_slider_label(int idx) const {
ERR_FAIL_INDEX_V_MSG(idx, 3, String(), "Couldn't get slider label.");
return labels[idx];
}
float ColorModeRGB::get_slider_max(int idx) const {
ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider max value.");
Color color = color_picker->get_pick_color();
return next_power_of_2(MAX(255, color.components[idx] * 255.0)) - 1;
}
float ColorModeRGB::get_slider_value(int idx) const {
ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider value.");
return color_picker->get_pick_color().components[idx] * 255;
}
Color ColorModeRGB::get_color() const {
Vector<float> values = color_picker->get_active_slider_values();
Color color;
for (int i = 0; i < 4; i++) {
color.components[i] = values[i] / 255.0;
}
return color;
}
void ColorModeRGB::slider_draw(int p_which) {
Vector<Vector2> pos;
pos.resize(4);
Vector<Color> col;
col.resize(4);
HSlider *slider = color_picker->get_slider(p_which);
Size2 size = slider->get_size();
Color left_color;
Color right_color;
Color color = color_picker->get_pick_color();
const real_t margin = 16 * color_picker->theme_cache.base_scale;
if (p_which == ColorPicker::SLIDER_COUNT) {
slider->draw_texture_rect(color_picker->theme_cache.sample_bg, Rect2(Point2(0, 0), Size2(size.x, margin)), true);
left_color = color;
left_color.a = 0;
right_color = color;
right_color.a = 1;
} else {
left_color = Color(
p_which == 0 ? 0 : color.r,
p_which == 1 ? 0 : color.g,
p_which == 2 ? 0 : color.b);
right_color = Color(
p_which == 0 ? 1 : color.r,
p_which == 1 ? 1 : color.g,
p_which == 2 ? 1 : color.b);
}
col.set(0, left_color);
col.set(1, right_color);
col.set(2, right_color);
col.set(3, left_color);
pos.set(0, Vector2(0, 0));
pos.set(1, Vector2(size.x, 0));
pos.set(2, Vector2(size.x, margin));
pos.set(3, Vector2(0, margin));
slider->draw_polygon(pos, col);
}
void ColorModeHSV::_value_changed() {
Vector<float> values = color_picker->get_active_slider_values();
if (values[1] > 0 || values[0] != cached_hue) {
cached_hue = values[0];
}
if (values[2] > 0 || values[1] != cached_saturation) {
cached_saturation = values[1];
}
}
String ColorModeHSV::get_slider_label(int idx) const {
ERR_FAIL_INDEX_V_MSG(idx, 3, String(), "Couldn't get slider label.");
return labels[idx];
}
float ColorModeHSV::get_slider_max(int idx) const {
ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider max value.");
return slider_max[idx];
}
float ColorModeHSV::get_slider_value(int idx) const {
switch (idx) {
case 0: {
if (color_picker->get_pick_color().get_s() > 0) {
return color_picker->get_pick_color().get_h() * 360.0;
} else {
return cached_hue;
}
}
case 1: {
if (color_picker->get_pick_color().get_v() > 0) {
return color_picker->get_pick_color().get_s() * 100.0;
} else {
return cached_saturation;
}
}
case 2:
return color_picker->get_pick_color().get_v() * 100.0;
case 3:
return Math::round(color_picker->get_pick_color().components[3] * 255.0);
default:
ERR_FAIL_V_MSG(0, "Couldn't get slider value.");
}
}
Color ColorModeHSV::get_color() const {
Vector<float> values = color_picker->get_active_slider_values();
Color color;
color.set_hsv(values[0] / 360.0, values[1] / 100.0, values[2] / 100.0, values[3] / 255.0);
return color;
}
void ColorModeHSV::slider_draw(int p_which) {
Vector<Vector2> pos;
pos.resize(4);
Vector<Color> col;
col.resize(4);
HSlider *slider = color_picker->get_slider(p_which);
Size2 size = slider->get_size();
Color left_color;
Color right_color;
Color color = color_picker->get_pick_color();
const real_t margin = 16 * color_picker->theme_cache.base_scale;
if (p_which == ColorPicker::SLIDER_COUNT) {
slider->draw_texture_rect(color_picker->theme_cache.sample_bg, Rect2(Point2(0, 0), Size2(size.x, margin)), true);
left_color = color;
left_color.a = 0;
right_color = color;
right_color.a = 1;
} else if (p_which == 0) {
float v = color.get_v();
left_color = Color(v, v, v);
right_color = left_color;
} else {
Color s_col;
Color v_col;
s_col.set_hsv(color.get_h(), 0, color.get_v());
left_color = (p_which == 1) ? s_col : Color(0, 0, 0);
float s_col_hue = (Math::is_zero_approx(color.get_s())) ? cached_hue / 360.0 : color.get_h();
s_col.set_hsv(s_col_hue, 1, color.get_v());
v_col.set_hsv(color.get_h(), color.get_s(), 1);
right_color = (p_which == 1) ? s_col : v_col;
}
col.set(0, left_color);
col.set(1, right_color);
col.set(2, right_color);
col.set(3, left_color);
pos.set(0, Vector2(0, 0));
pos.set(1, Vector2(size.x, 0));
pos.set(2, Vector2(size.x, margin));
pos.set(3, Vector2(0, margin));
slider->draw_polygon(pos, col);
if (p_which == 0) { // H
Ref<Texture2D> hue = color_picker->theme_cache.color_hue;
slider->draw_texture_rect(hue, Rect2(Vector2(), Vector2(size.x, margin)), false, Color::from_hsv(0, 0, color.get_v(), color.get_s()));
}
}
String ColorModeRAW::get_slider_label(int idx) const {
ERR_FAIL_INDEX_V_MSG(idx, 3, String(), "Couldn't get slider label.");
return labels[idx];
}
float ColorModeRAW::get_slider_max(int idx) const {
ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider max value.");
return slider_max[idx];
}
float ColorModeRAW::get_slider_value(int idx) const {
ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider value.");
return color_picker->get_pick_color().components[idx];
}
Color ColorModeRAW::get_color() const {
Vector<float> values = color_picker->get_active_slider_values();
Color color;
for (int i = 0; i < 4; i++) {
color.components[i] = values[i];
}
return color;
}
void ColorModeRAW::slider_draw(int p_which) {
Vector<Vector2> pos;
pos.resize(4);
Vector<Color> col;
col.resize(4);
HSlider *slider = color_picker->get_slider(p_which);
Size2 size = slider->get_size();
Color left_color;
Color right_color;
Color color = color_picker->get_pick_color();
const real_t margin = 16 * color_picker->theme_cache.base_scale;
if (p_which == ColorPicker::SLIDER_COUNT) {
slider->draw_texture_rect(color_picker->theme_cache.sample_bg, Rect2(Point2(0, 0), Size2(size.x, margin)), true);
left_color = color;
left_color.a = 0;
right_color = color;
right_color.a = 1;
col.set(0, left_color);
col.set(1, right_color);
col.set(2, right_color);
col.set(3, left_color);
pos.set(0, Vector2(0, 0));
pos.set(1, Vector2(size.x, 0));
pos.set(2, Vector2(size.x, margin));
pos.set(3, Vector2(0, margin));
slider->draw_polygon(pos, col);
}
}
bool ColorModeRAW::apply_theme() const {
for (int i = 0; i < 4; i++) {
HSlider *slider = color_picker->get_slider(i);
slider->remove_theme_icon_override("grabber");
slider->remove_theme_icon_override("grabber_highlight");
slider->remove_theme_style_override("slider");
slider->remove_theme_constant_override("grabber_offset");
}
return true;
}
void ColorModeOKHSL::_value_changed() {
Vector<float> values = color_picker->get_active_slider_values();
if (values[1] > 0 || values[0] != cached_hue) {
cached_hue = values[0];
}
if (values[2] > 0 || values[1] != cached_saturation) {
cached_saturation = values[1];
}
}
String ColorModeOKHSL::get_slider_label(int idx) const {
ERR_FAIL_INDEX_V_MSG(idx, 3, String(), "Couldn't get slider label.");
return labels[idx];
}
float ColorModeOKHSL::get_slider_max(int idx) const {
ERR_FAIL_INDEX_V_MSG(idx, 4, 0, "Couldn't get slider max value.");
return slider_max[idx];
}
float ColorModeOKHSL::get_slider_value(int idx) const {
switch (idx) {
case 0: {
if (color_picker->get_pick_color().get_ok_hsl_s() > 0) {
return color_picker->get_pick_color().get_ok_hsl_h() * 360.0;
} else {
return cached_hue;
}
}
case 1: {
if (color_picker->get_pick_color().get_ok_hsl_l() > 0) {
return color_picker->get_pick_color().get_ok_hsl_s() * 100.0;
} else {
return cached_saturation;
}
}
case 2:
return color_picker->get_pick_color().get_ok_hsl_l() * 100.0;
case 3:
return Math::round(color_picker->get_pick_color().components[3] * 255.0);
default:
ERR_FAIL_V_MSG(0, "Couldn't get slider value.");
}
}
Color ColorModeOKHSL::get_color() const {
Vector<float> values = color_picker->get_active_slider_values();
Color color;
color.set_ok_hsl(values[0] / 360.0, values[1] / 100.0, values[2] / 100.0, values[3] / 255.0);
return color;
}
void ColorModeOKHSL::slider_draw(int p_which) {
HSlider *slider = color_picker->get_slider(p_which);
Size2 size = slider->get_size();
const real_t margin = 16 * color_picker->theme_cache.base_scale;
Vector<Vector2> pos;
Vector<Color> col;
Color left_color;
Color right_color;
Color color = color_picker->get_pick_color();
if (p_which == 2) { // L
pos.resize(6);
col.resize(6);
left_color = Color(0, 0, 0);
Color middle_color;
float slider_hue = (Math::is_zero_approx(color.get_ok_hsl_s())) ? cached_hue / 360.0 : color.get_ok_hsl_h();
float slider_sat = (Math::is_zero_approx(color.get_ok_hsl_l())) ? cached_saturation / 100.0 : color.get_ok_hsl_s();
middle_color.set_ok_hsl(slider_hue, slider_sat, 0.5);
right_color.set_ok_hsl(slider_hue, slider_sat, 1);
col.set(0, left_color);
col.set(1, middle_color);
col.set(2, right_color);
col.set(3, right_color);
col.set(4, middle_color);
col.set(5, left_color);
pos.set(0, Vector2(0, 0));
pos.set(1, Vector2(size.x * 0.5, 0));
pos.set(2, Vector2(size.x, 0));
pos.set(3, Vector2(size.x, margin));
pos.set(4, Vector2(size.x * 0.5, margin));
pos.set(5, Vector2(0, margin));
} else {
pos.resize(4);
col.resize(4);
if (p_which == ColorPicker::SLIDER_COUNT) {
slider->draw_texture_rect(color_picker->theme_cache.sample_bg, Rect2(Point2(0, 0), Size2(size.x, margin)), true);
left_color = color;
left_color.a = 0;
right_color = color;
right_color.a = 1;
} else if (p_which == 0) {
float l = color.get_ok_hsl_l();
left_color = Color(l, l, l);
right_color = left_color;
} else {
left_color.set_ok_hsl(color.get_ok_hsl_h(), 0, color.get_ok_hsl_l());
float s_col_hue = (Math::is_zero_approx(color.get_ok_hsl_s())) ? cached_hue / 360.0 : color.get_ok_hsl_h();
right_color.set_ok_hsl(s_col_hue, 1, color.get_ok_hsl_l());
}
col.set(0, left_color);
col.set(1, right_color);
col.set(2, right_color);
col.set(3, left_color);
pos.set(0, Vector2(0, 0));
pos.set(1, Vector2(size.x, 0));
pos.set(2, Vector2(size.x, margin));
pos.set(3, Vector2(0, margin));
}
slider->draw_polygon(pos, col);
if (p_which == 0) { // H
Ref<Texture2D> hue = color_picker->theme_cache.color_okhsl_hue;
slider->draw_texture_rect(hue, Rect2(Vector2(), Vector2(size.x, margin)), false, Color::from_hsv(0, 0, color.get_ok_hsl_l() * 2.0, color.get_ok_hsl_s()));
return;
}
}

View file

@ -0,0 +1,155 @@
/**************************************************************************/
/* color_mode.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. */
/**************************************************************************/
#ifndef COLOR_MODE_H
#define COLOR_MODE_H
#include "scene/gui/color_picker.h"
struct Color;
class ColorMode {
public:
ColorPicker *color_picker = nullptr;
virtual String get_name() const = 0;
virtual int get_slider_count() const { return 3; };
virtual float get_slider_step() const = 0;
virtual float get_spinbox_arrow_step() const { return get_slider_step(); };
virtual String get_slider_label(int idx) const = 0;
virtual float get_slider_max(int idx) const = 0;
virtual float get_slider_value(int idx) const = 0;
virtual Color get_color() const = 0;
virtual void _value_changed(){};
virtual void slider_draw(int p_which) = 0;
virtual bool apply_theme() const { return false; }
virtual ColorPicker::PickerShapeType get_shape_override() const { return ColorPicker::SHAPE_MAX; }
ColorMode(ColorPicker *p_color_picker);
virtual ~ColorMode(){};
};
class ColorModeHSV : public ColorMode {
public:
String labels[3] = { "H", "S", "V" };
float slider_max[4] = { 359, 100, 100, 255 };
float cached_hue = 0.0;
float cached_saturation = 0.0;
virtual String get_name() const override { return "HSV"; }
virtual float get_slider_step() const override { return 1.0; }
virtual String get_slider_label(int idx) const override;
virtual float get_slider_max(int idx) const override;
virtual float get_slider_value(int idx) const override;
virtual Color get_color() const override;
virtual void _value_changed() override;
virtual void slider_draw(int p_which) override;
ColorModeHSV(ColorPicker *p_color_picker) :
ColorMode(p_color_picker){};
};
class ColorModeRGB : public ColorMode {
public:
String labels[3] = { "R", "G", "B" };
virtual String get_name() const override { return "RGB"; }
virtual float get_slider_step() const override { return 1; }
virtual String get_slider_label(int idx) const override;
virtual float get_slider_max(int idx) const override;
virtual float get_slider_value(int idx) const override;
virtual Color get_color() const override;
virtual void slider_draw(int p_which) override;
ColorModeRGB(ColorPicker *p_color_picker) :
ColorMode(p_color_picker){};
};
class ColorModeRAW : public ColorMode {
public:
String labels[3] = { "R", "G", "B" };
float slider_max[4] = { 100, 100, 100, 1 };
virtual String get_name() const override { return "RAW"; }
virtual float get_slider_step() const override { return 0.001; }
virtual float get_spinbox_arrow_step() const override { return 0.01; }
virtual String get_slider_label(int idx) const override;
virtual float get_slider_max(int idx) const override;
virtual float get_slider_value(int idx) const override;
virtual Color get_color() const override;
virtual void slider_draw(int p_which) override;
virtual bool apply_theme() const override;
ColorModeRAW(ColorPicker *p_color_picker) :
ColorMode(p_color_picker){};
};
class ColorModeOKHSL : public ColorMode {
public:
String labels[3] = { "H", "S", "L" };
float slider_max[4] = { 359, 100, 100, 255 };
float cached_hue = 0.0;
float cached_saturation = 0.0;
virtual String get_name() const override { return "OKHSL"; }
virtual float get_slider_step() const override { return 1.0; }
virtual String get_slider_label(int idx) const override;
virtual float get_slider_max(int idx) const override;
virtual float get_slider_value(int idx) const override;
virtual Color get_color() const override;
virtual void _value_changed() override;
virtual void slider_draw(int p_which) override;
virtual ColorPicker::PickerShapeType get_shape_override() const override { return ColorPicker::SHAPE_OKHSL_CIRCLE; }
ColorModeOKHSL(ColorPicker *p_color_picker) :
ColorMode(p_color_picker){};
~ColorModeOKHSL(){};
};
#endif // COLOR_MODE_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,428 @@
/**************************************************************************/
/* color_picker.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. */
/**************************************************************************/
#ifndef COLOR_PICKER_H
#define COLOR_PICKER_H
#include "scene/gui/aspect_ratio_container.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/control.h"
#include "scene/gui/grid_container.h"
#include "scene/gui/label.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/menu_button.h"
#include "scene/gui/option_button.h"
#include "scene/gui/panel.h"
#include "scene/gui/popup.h"
#include "scene/gui/separator.h"
#include "scene/gui/slider.h"
#include "scene/gui/spin_box.h"
#include "scene/gui/texture_rect.h"
#include "scene/resources/style_box_flat.h"
class ColorMode;
class ColorModeRGB;
class ColorModeHSV;
class ColorModeRAW;
class ColorModeOKHSL;
class ColorPresetButton : public BaseButton {
GDCLASS(ColorPresetButton, BaseButton);
Color preset_color;
struct ThemeCache {
Ref<StyleBox> foreground_style;
Ref<Texture2D> background_icon;
Ref<Texture2D> overbright_indicator;
} theme_cache;
protected:
void _notification(int);
static void _bind_methods();
public:
void set_preset_color(const Color &p_color);
Color get_preset_color() const;
ColorPresetButton(Color p_color, int p_size);
~ColorPresetButton();
};
class ColorPicker : public VBoxContainer {
GDCLASS(ColorPicker, VBoxContainer);
// These classes poke into theme items for their internal logic.
friend class ColorModeRGB;
friend class ColorModeHSV;
friend class ColorModeRAW;
friend class ColorModeOKHSL;
public:
enum ColorModeType {
MODE_RGB,
MODE_HSV,
MODE_RAW,
MODE_OKHSL,
MODE_MAX
};
enum PickerShapeType {
SHAPE_HSV_RECTANGLE,
SHAPE_HSV_WHEEL,
SHAPE_VHS_CIRCLE,
SHAPE_OKHSL_CIRCLE,
SHAPE_NONE,
SHAPE_MAX
};
static const int SLIDER_COUNT = 4;
private:
static Ref<Shader> wheel_shader;
static Ref<Shader> circle_shader;
static Ref<Shader> circle_ok_color_shader;
static List<Color> preset_cache;
static List<Color> recent_preset_cache;
#ifdef TOOLS_ENABLED
Object *editor_settings = nullptr;
#endif
int current_slider_count = SLIDER_COUNT;
static const int MODE_BUTTON_COUNT = 3;
bool slider_theme_modified = true;
Vector<ColorMode *> modes;
Popup *picker_window = nullptr;
// Legacy color picking.
TextureRect *picker_texture_rect = nullptr;
Panel *picker_preview = nullptr;
Label *picker_preview_label = nullptr;
Ref<StyleBoxFlat> picker_preview_style_box;
Color picker_color;
MarginContainer *internal_margin = nullptr;
Control *uv_edit = nullptr;
Control *w_edit = nullptr;
AspectRatioContainer *wheel_edit = nullptr;
MarginContainer *wheel_margin = nullptr;
Ref<ShaderMaterial> wheel_mat;
Ref<ShaderMaterial> circle_mat;
Control *wheel = nullptr;
Control *wheel_uv = nullptr;
TextureRect *sample = nullptr;
GridContainer *preset_container = nullptr;
HBoxContainer *recent_preset_hbc = nullptr;
Button *btn_add_preset = nullptr;
Button *btn_pick = nullptr;
Button *btn_preset = nullptr;
Button *btn_recent_preset = nullptr;
PopupMenu *shape_popup = nullptr;
PopupMenu *mode_popup = nullptr;
MenuButton *btn_shape = nullptr;
HBoxContainer *mode_hbc = nullptr;
HBoxContainer *sample_hbc = nullptr;
GridContainer *slider_gc = nullptr;
HBoxContainer *hex_hbc = nullptr;
MenuButton *btn_mode = nullptr;
Button *mode_btns[MODE_BUTTON_COUNT];
Ref<ButtonGroup> mode_group = nullptr;
ColorPresetButton *selected_recent_preset = nullptr;
Ref<ButtonGroup> preset_group;
Ref<ButtonGroup> recent_preset_group;
OptionButton *mode_option_button = nullptr;
HSlider *sliders[SLIDER_COUNT];
SpinBox *values[SLIDER_COUNT];
Label *labels[SLIDER_COUNT];
Button *text_type = nullptr;
LineEdit *c_text = nullptr;
HSlider *alpha_slider = nullptr;
SpinBox *alpha_value = nullptr;
Label *alpha_label = nullptr;
bool edit_alpha = true;
Size2i ms;
bool text_is_constructor = false;
PickerShapeType current_shape = SHAPE_HSV_RECTANGLE;
ColorModeType current_mode = MODE_RGB;
bool colorize_sliders = true;
const int PRESET_COLUMN_COUNT = 9;
int prev_preset_size = 0;
int prev_rencet_preset_size = 0;
List<Color> presets;
List<Color> recent_presets;
Color color;
Color old_color;
bool is_picking_color = false;
bool display_old_color = false;
bool deferred_mode_enabled = false;
bool updating = true;
bool changing_color = false;
bool spinning = false;
bool can_add_swatches = true;
bool presets_visible = true;
bool color_modes_visible = true;
bool sampler_visible = true;
bool sliders_visible = true;
bool hex_visible = true;
bool line_edit_mouse_release = false;
bool text_changed = false;
bool currently_dragging = false;
float h = 0.0;
float s = 0.0;
float v = 0.0;
Color last_color;
struct ThemeCache {
float base_scale = 1.0;
int content_margin = 0;
int label_width = 0;
int sv_height = 0;
int sv_width = 0;
int h_width = 0;
bool center_slider_grabbers = true;
Ref<Texture2D> screen_picker;
Ref<Texture2D> expanded_arrow;
Ref<Texture2D> folded_arrow;
Ref<Texture2D> add_preset;
Ref<Texture2D> shape_rect;
Ref<Texture2D> shape_rect_wheel;
Ref<Texture2D> shape_circle;
Ref<Texture2D> bar_arrow;
Ref<Texture2D> sample_bg;
Ref<Texture2D> sample_revert;
Ref<Texture2D> overbright_indicator;
Ref<Texture2D> picker_cursor;
Ref<Texture2D> color_hue;
Ref<Texture2D> color_okhsl_hue;
/* Mode buttons */
Ref<StyleBox> mode_button_normal;
Ref<StyleBox> mode_button_pressed;
Ref<StyleBox> mode_button_hover;
} theme_cache;
void _copy_color_to_hsv();
void _copy_hsv_to_color();
PickerShapeType _get_actual_shape() const;
void create_slider(GridContainer *gc, int idx);
void _reset_sliders_theme();
void _html_submitted(const String &p_html);
void _slider_drag_started();
void _slider_value_changed();
void _slider_drag_ended();
void _update_controls();
void _update_color(bool p_update_sliders = true);
void _update_text_value();
void _text_type_toggled();
void _sample_input(const Ref<InputEvent> &p_event);
void _sample_draw();
void _hsv_draw(int p_which, Control *c);
void _slider_draw(int p_which);
void _uv_input(const Ref<InputEvent> &p_event, Control *c);
void _w_input(const Ref<InputEvent> &p_event);
void _slider_or_spin_input(const Ref<InputEvent> &p_event);
void _line_edit_input(const Ref<InputEvent> &p_event);
void _preset_input(const Ref<InputEvent> &p_event, const Color &p_color);
void _recent_preset_pressed(const bool pressed, ColorPresetButton *p_preset);
void _text_changed(const String &p_new_text);
void _add_preset_pressed();
void _html_focus_exit();
void _pick_button_pressed();
void _pick_finished();
// Legacy color picking.
void _pick_button_pressed_legacy();
void _picker_texture_input(const Ref<InputEvent> &p_event);
inline int _get_preset_size();
void _add_preset_button(int p_size, const Color &p_color);
void _add_recent_preset_button(int p_size, const Color &p_color);
void _show_hide_preset(const bool &p_is_btn_pressed, Button *p_btn_preset, Container *p_preset_container);
void _update_drop_down_arrow(const bool &p_is_btn_pressed, Button *p_btn_preset);
void _set_mode_popup_value(ColorModeType p_mode);
Variant _get_drag_data_fw(const Point2 &p_point, Control *p_from_control);
bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control) const;
void _drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from_control);
protected:
virtual void _update_theme_item_cache() override;
void _notification(int);
static void _bind_methods();
public:
#ifdef TOOLS_ENABLED
void set_editor_settings(Object *p_editor_settings);
#endif
HSlider *get_slider(int idx);
Vector<float> get_active_slider_values();
static void init_shaders();
static void finish_shaders();
void add_mode(ColorMode *p_mode);
void set_edit_alpha(bool p_show);
bool is_editing_alpha() const;
void _set_pick_color(const Color &p_color, bool p_update_sliders);
void set_pick_color(const Color &p_color);
Color get_pick_color() const;
void set_old_color(const Color &p_color);
Color get_old_color() const;
void set_display_old_color(bool p_enabled);
bool is_displaying_old_color() const;
void set_picker_shape(PickerShapeType p_shape);
PickerShapeType get_picker_shape() const;
void add_preset(const Color &p_color);
void add_recent_preset(const Color &p_color);
void erase_preset(const Color &p_color);
void erase_recent_preset(const Color &p_color);
PackedColorArray get_presets() const;
PackedColorArray get_recent_presets() const;
void _update_presets();
void _update_recent_presets();
void _select_from_preset_container(const Color &p_color);
bool _select_from_recent_preset_hbc(const Color &p_color);
void set_color_mode(ColorModeType p_mode);
ColorModeType get_color_mode() const;
void set_colorize_sliders(bool p_colorize_sliders);
bool is_colorizing_sliders() const;
void set_deferred_mode(bool p_enabled);
bool is_deferred_mode() const;
void set_can_add_swatches(bool p_enabled);
bool are_swatches_enabled() const;
void set_presets_visible(bool p_visible);
bool are_presets_visible() const;
void set_modes_visible(bool p_visible);
bool are_modes_visible() const;
void set_sampler_visible(bool p_visible);
bool is_sampler_visible() const;
void set_sliders_visible(bool p_visible);
bool are_sliders_visible() const;
void set_hex_visible(bool p_visible);
bool is_hex_visible() const;
void set_focus_on_line_edit();
ColorPicker();
~ColorPicker();
};
class ColorPickerPopupPanel : public PopupPanel {
virtual void _input_from_window(const Ref<InputEvent> &p_event) override;
};
class ColorPickerButton : public Button {
GDCLASS(ColorPickerButton, Button);
// Initialization is now done deferred,
// this improves performance in the inspector as the color picker
// can be expensive to initialize.
PopupPanel *popup = nullptr;
ColorPicker *picker = nullptr;
Color color;
bool edit_alpha = true;
struct ThemeCache {
Ref<StyleBox> normal_style;
Ref<Texture2D> background_icon;
Ref<Texture2D> overbright_indicator;
} theme_cache;
void _about_to_popup();
void _color_changed(const Color &p_color);
void _modal_closed();
virtual void pressed() override;
void _update_picker();
protected:
void _notification(int);
static void _bind_methods();
public:
void set_pick_color(const Color &p_color);
Color get_pick_color() const;
void set_edit_alpha(bool p_show);
bool is_editing_alpha() const;
ColorPicker *get_picker();
PopupPanel *get_popup();
ColorPickerButton(const String &p_text = String());
};
VARIANT_ENUM_CAST(ColorPicker::PickerShapeType);
VARIANT_ENUM_CAST(ColorPicker::ColorModeType);
#endif // COLOR_PICKER_H

View file

@ -0,0 +1,58 @@
/**************************************************************************/
/* color_rect.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 "color_rect.h"
void ColorRect::set_color(const Color &p_color) {
if (color == p_color) {
return;
}
color = p_color;
queue_redraw();
}
Color ColorRect::get_color() const {
return color;
}
void ColorRect::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
draw_rect(Rect2(Point2(), get_size()), color);
} break;
}
}
void ColorRect::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_color", "color"), &ColorRect::set_color);
ClassDB::bind_method(D_METHOD("get_color"), &ColorRect::get_color);
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
}

View file

@ -0,0 +1,50 @@
/**************************************************************************/
/* color_rect.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. */
/**************************************************************************/
#ifndef COLOR_RECT_H
#define COLOR_RECT_H
#include "scene/gui/control.h"
class ColorRect : public Control {
GDCLASS(ColorRect, Control);
Color color = Color(1, 1, 1);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void set_color(const Color &p_color);
Color get_color() const;
};
#endif // COLOR_RECT_H

View file

@ -0,0 +1,228 @@
/**************************************************************************/
/* 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 "container.h"
void Container::_child_minsize_changed() {
update_minimum_size();
queue_sort();
}
void Container::add_child_notify(Node *p_child) {
Control::add_child_notify(p_child);
Control *control = Object::cast_to<Control>(p_child);
if (!control) {
return;
}
control->connect(SceneStringName(size_flags_changed), callable_mp(this, &Container::queue_sort));
control->connect(SceneStringName(minimum_size_changed), callable_mp(this, &Container::_child_minsize_changed));
control->connect(SceneStringName(visibility_changed), callable_mp(this, &Container::_child_minsize_changed));
update_minimum_size();
queue_sort();
}
void Container::move_child_notify(Node *p_child) {
Control::move_child_notify(p_child);
if (!Object::cast_to<Control>(p_child)) {
return;
}
update_minimum_size();
queue_sort();
}
void Container::remove_child_notify(Node *p_child) {
Control::remove_child_notify(p_child);
Control *control = Object::cast_to<Control>(p_child);
if (!control) {
return;
}
control->disconnect(SceneStringName(size_flags_changed), callable_mp(this, &Container::queue_sort));
control->disconnect(SceneStringName(minimum_size_changed), callable_mp(this, &Container::_child_minsize_changed));
control->disconnect(SceneStringName(visibility_changed), callable_mp(this, &Container::_child_minsize_changed));
update_minimum_size();
queue_sort();
}
void Container::_sort_children() {
if (!is_inside_tree()) {
pending_sort = false;
return;
}
notification(NOTIFICATION_PRE_SORT_CHILDREN);
emit_signal(SceneStringName(pre_sort_children));
notification(NOTIFICATION_SORT_CHILDREN);
emit_signal(SceneStringName(sort_children));
pending_sort = false;
}
void Container::fit_child_in_rect(Control *p_child, const Rect2 &p_rect) {
ERR_FAIL_NULL(p_child);
ERR_FAIL_COND(p_child->get_parent() != this);
bool rtl = is_layout_rtl();
Size2 minsize = p_child->get_combined_minimum_size();
Rect2 r = p_rect;
if (!(p_child->get_h_size_flags().has_flag(SIZE_FILL))) {
r.size.x = minsize.width;
if (p_child->get_h_size_flags().has_flag(SIZE_SHRINK_END)) {
r.position.x += rtl ? 0 : (p_rect.size.width - minsize.width);
} else if (p_child->get_h_size_flags().has_flag(SIZE_SHRINK_CENTER)) {
r.position.x += Math::floor((p_rect.size.x - minsize.width) / 2);
} else {
r.position.x += rtl ? (p_rect.size.width - minsize.width) : 0;
}
}
if (!(p_child->get_v_size_flags().has_flag(SIZE_FILL))) {
r.size.y = minsize.y;
if (p_child->get_v_size_flags().has_flag(SIZE_SHRINK_END)) {
r.position.y += p_rect.size.height - minsize.height;
} else if (p_child->get_v_size_flags().has_flag(SIZE_SHRINK_CENTER)) {
r.position.y += Math::floor((p_rect.size.y - minsize.height) / 2);
} else {
r.position.y += 0;
}
}
p_child->set_rect(r);
p_child->set_rotation(0);
p_child->set_scale(Vector2(1, 1));
}
void Container::queue_sort() {
if (!is_inside_tree()) {
return;
}
if (pending_sort) {
return;
}
callable_mp(this, &Container::_sort_children).call_deferred();
pending_sort = true;
}
Control *Container::as_sortable_control(Node *p_node, SortableVisbilityMode p_visibility_mode) const {
Control *c = Object::cast_to<Control>(p_node);
if (!c || c->is_set_as_top_level()) {
return nullptr;
}
if (p_visibility_mode == SortableVisbilityMode::VISIBLE && !c->is_visible()) {
return nullptr;
}
if (p_visibility_mode == SortableVisbilityMode::VISIBLE_IN_TREE && !c->is_visible_in_tree()) {
return nullptr;
}
return c;
}
Vector<int> Container::get_allowed_size_flags_horizontal() const {
Vector<int> flags;
if (GDVIRTUAL_CALL(_get_allowed_size_flags_horizontal, flags)) {
return flags;
}
flags.append(SIZE_FILL);
flags.append(SIZE_EXPAND);
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
Vector<int> Container::get_allowed_size_flags_vertical() const {
Vector<int> flags;
if (GDVIRTUAL_CALL(_get_allowed_size_flags_vertical, flags)) {
return flags;
}
flags.append(SIZE_FILL);
flags.append(SIZE_EXPAND);
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
void Container::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_RESIZED:
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_ENTER_TREE: {
queue_sort();
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (is_visible_in_tree()) {
queue_sort();
}
} break;
}
}
PackedStringArray Container::get_configuration_warnings() const {
PackedStringArray warnings = Control::get_configuration_warnings();
if (get_class() == "Container" && get_script().is_null()) {
warnings.push_back(RTR("Container by itself serves no purpose unless a script configures its children placement behavior.\nIf you don't intend to add a script, use a plain Control node instead."));
}
return warnings;
}
void Container::_bind_methods() {
ClassDB::bind_method(D_METHOD("queue_sort"), &Container::queue_sort);
ClassDB::bind_method(D_METHOD("fit_child_in_rect", "child", "rect"), &Container::fit_child_in_rect);
GDVIRTUAL_BIND(_get_allowed_size_flags_horizontal);
GDVIRTUAL_BIND(_get_allowed_size_flags_vertical);
BIND_CONSTANT(NOTIFICATION_PRE_SORT_CHILDREN);
BIND_CONSTANT(NOTIFICATION_SORT_CHILDREN);
ADD_SIGNAL(MethodInfo("pre_sort_children"));
ADD_SIGNAL(MethodInfo("sort_children"));
}
Container::Container() {
// All containers should let mouse events pass by default.
set_mouse_filter(MOUSE_FILTER_PASS);
}

View file

@ -0,0 +1,79 @@
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef CONTAINER_H
#define CONTAINER_H
#include "scene/gui/control.h"
class Container : public Control {
GDCLASS(Container, Control);
bool pending_sort = false;
void _sort_children();
void _child_minsize_changed();
protected:
enum class SortableVisbilityMode {
VISIBLE,
VISIBLE_IN_TREE,
IGNORE,
};
void queue_sort();
Control *as_sortable_control(Node *p_node, SortableVisbilityMode p_visibility_mode = SortableVisbilityMode::VISIBLE_IN_TREE) const;
virtual void add_child_notify(Node *p_child) override;
virtual void move_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
GDVIRTUAL0RC(Vector<int>, _get_allowed_size_flags_horizontal)
GDVIRTUAL0RC(Vector<int>, _get_allowed_size_flags_vertical)
void _notification(int p_what);
static void _bind_methods();
public:
enum {
NOTIFICATION_PRE_SORT_CHILDREN = 50,
NOTIFICATION_SORT_CHILDREN = 51,
};
void fit_child_in_rect(Control *p_child, const Rect2 &p_rect);
virtual Vector<int> get_allowed_size_flags_horizontal() const;
virtual Vector<int> get_allowed_size_flags_vertical() const;
PackedStringArray get_configuration_warnings() const override;
Container();
};
#endif // CONTAINER_H

View file

@ -0,0 +1,48 @@
/**************************************************************************/
/* control.compat.inc */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
void Control::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("get_theme_icon", "name", "theme_type"), &Control::get_theme_icon, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("get_theme_stylebox", "name", "theme_type"), &Control::get_theme_stylebox, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("get_theme_font", "name", "theme_type"), &Control::get_theme_font, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("get_theme_font_size", "name", "theme_type"), &Control::get_theme_font_size, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("get_theme_color", "name", "theme_type"), &Control::get_theme_color, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("get_theme_constant", "name", "theme_type"), &Control::get_theme_constant, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("has_theme_icon", "name", "theme_type"), &Control::has_theme_icon, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("has_theme_stylebox", "name", "theme_type"), &Control::has_theme_stylebox, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("has_theme_font", "name", "theme_type"), &Control::has_theme_font, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("has_theme_font_size", "name", "theme_type"), &Control::has_theme_font_size, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("has_theme_color", "name", "theme_type"), &Control::has_theme_color, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("has_theme_constant", "name", "theme_type"), &Control::has_theme_constant, DEFVAL(""));
}
#endif

3756
engine/scene/gui/control.cpp Normal file

File diff suppressed because it is too large Load diff

669
engine/scene/gui/control.h Normal file
View file

@ -0,0 +1,669 @@
/**************************************************************************/
/* control.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. */
/**************************************************************************/
#ifndef CONTROL_H
#define CONTROL_H
#include "core/math/transform_2d.h"
#include "core/object/gdvirtual.gen.inc"
#include "core/templates/rid.h"
#include "scene/main/canvas_item.h"
#include "scene/main/timer.h"
#include "scene/resources/theme.h"
class Viewport;
class Label;
class Panel;
class ThemeOwner;
class ThemeContext;
class Control : public CanvasItem {
GDCLASS(Control, CanvasItem);
#ifdef TOOLS_ENABLED
bool saving = false;
#endif
public:
enum Anchor {
ANCHOR_BEGIN = 0,
ANCHOR_END = 1
};
enum GrowDirection {
GROW_DIRECTION_BEGIN,
GROW_DIRECTION_END,
GROW_DIRECTION_BOTH
};
enum FocusMode {
FOCUS_NONE,
FOCUS_CLICK,
FOCUS_ALL
};
enum SizeFlags {
SIZE_SHRINK_BEGIN = 0,
SIZE_FILL = 1,
SIZE_EXPAND = 2,
SIZE_SHRINK_CENTER = 4,
SIZE_SHRINK_END = 8,
SIZE_EXPAND_FILL = SIZE_EXPAND | SIZE_FILL,
};
enum MouseFilter {
MOUSE_FILTER_STOP,
MOUSE_FILTER_PASS,
MOUSE_FILTER_IGNORE
};
enum CursorShape {
CURSOR_ARROW,
CURSOR_IBEAM,
CURSOR_POINTING_HAND,
CURSOR_CROSS,
CURSOR_WAIT,
CURSOR_BUSY,
CURSOR_DRAG,
CURSOR_CAN_DROP,
CURSOR_FORBIDDEN,
CURSOR_VSIZE,
CURSOR_HSIZE,
CURSOR_BDIAGSIZE,
CURSOR_FDIAGSIZE,
CURSOR_MOVE,
CURSOR_VSPLIT,
CURSOR_HSPLIT,
CURSOR_HELP,
CURSOR_MAX
};
enum LayoutPreset {
PRESET_TOP_LEFT,
PRESET_TOP_RIGHT,
PRESET_BOTTOM_LEFT,
PRESET_BOTTOM_RIGHT,
PRESET_CENTER_LEFT,
PRESET_CENTER_TOP,
PRESET_CENTER_RIGHT,
PRESET_CENTER_BOTTOM,
PRESET_CENTER,
PRESET_LEFT_WIDE,
PRESET_TOP_WIDE,
PRESET_RIGHT_WIDE,
PRESET_BOTTOM_WIDE,
PRESET_VCENTER_WIDE,
PRESET_HCENTER_WIDE,
PRESET_FULL_RECT
};
enum LayoutPresetMode {
PRESET_MODE_MINSIZE,
PRESET_MODE_KEEP_WIDTH,
PRESET_MODE_KEEP_HEIGHT,
PRESET_MODE_KEEP_SIZE
};
enum LayoutMode {
LAYOUT_MODE_POSITION,
LAYOUT_MODE_ANCHORS,
LAYOUT_MODE_CONTAINER,
LAYOUT_MODE_UNCONTROLLED,
};
enum LayoutDirection {
LAYOUT_DIRECTION_INHERITED,
LAYOUT_DIRECTION_LOCALE,
LAYOUT_DIRECTION_LTR,
LAYOUT_DIRECTION_RTL
};
enum TextDirection {
TEXT_DIRECTION_AUTO = TextServer::DIRECTION_AUTO,
TEXT_DIRECTION_LTR = TextServer::DIRECTION_LTR,
TEXT_DIRECTION_RTL = TextServer::DIRECTION_RTL,
TEXT_DIRECTION_INHERITED = TextServer::DIRECTION_INHERITED,
};
private:
struct CComparator {
bool operator()(const Control *p_a, const Control *p_b) const {
if (p_a->get_canvas_layer() == p_b->get_canvas_layer()) {
return p_b->is_greater_than(p_a);
}
return p_a->get_canvas_layer() < p_b->get_canvas_layer();
}
};
// This Data struct is to avoid namespace pollution in derived classes.
struct Data {
bool initialized = false;
// Global relations.
List<Control *>::Element *RI = nullptr;
Control *parent_control = nullptr;
Window *parent_window = nullptr;
CanvasItem *parent_canvas_item = nullptr;
Callable forward_drag;
Callable forward_can_drop;
Callable forward_drop;
// Positioning and sizing.
LayoutMode stored_layout_mode = LayoutMode::LAYOUT_MODE_POSITION;
bool stored_use_custom_anchors = false;
real_t offset[4] = { 0.0, 0.0, 0.0, 0.0 };
real_t anchor[4] = { ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN, ANCHOR_BEGIN };
FocusMode focus_mode = FOCUS_NONE;
GrowDirection h_grow = GROW_DIRECTION_END;
GrowDirection v_grow = GROW_DIRECTION_END;
real_t rotation = 0.0;
Vector2 scale = Vector2(1, 1);
Vector2 pivot_offset;
Point2 pos_cache;
Size2 size_cache;
Size2 minimum_size_cache;
bool minimum_size_valid = false;
Size2 last_minimum_size;
bool updating_last_minimum_size = false;
bool block_minimum_size_adjust = false;
bool size_warning = true;
// Container sizing.
BitField<SizeFlags> h_size_flags = SIZE_FILL;
BitField<SizeFlags> v_size_flags = SIZE_FILL;
real_t expand = 1.0;
Point2 custom_minimum_size;
// Input events and rendering.
MouseFilter mouse_filter = MOUSE_FILTER_STOP;
bool force_pass_scroll_events = true;
bool clip_contents = false;
bool disable_visibility_clip = false;
CursorShape default_cursor = CURSOR_ARROW;
// Focus.
NodePath focus_neighbor[4];
NodePath focus_next;
NodePath focus_prev;
ObjectID shortcut_context;
// Theming.
ThemeOwner *theme_owner = nullptr;
Ref<Theme> theme;
StringName theme_type_variation;
bool bulk_theme_override = false;
Theme::ThemeIconMap theme_icon_override;
Theme::ThemeStyleMap theme_style_override;
Theme::ThemeFontMap theme_font_override;
Theme::ThemeFontSizeMap theme_font_size_override;
Theme::ThemeColorMap theme_color_override;
Theme::ThemeConstantMap theme_constant_override;
mutable HashMap<StringName, Theme::ThemeIconMap> theme_icon_cache;
mutable HashMap<StringName, Theme::ThemeStyleMap> theme_style_cache;
mutable HashMap<StringName, Theme::ThemeFontMap> theme_font_cache;
mutable HashMap<StringName, Theme::ThemeFontSizeMap> theme_font_size_cache;
mutable HashMap<StringName, Theme::ThemeColorMap> theme_color_cache;
mutable HashMap<StringName, Theme::ThemeConstantMap> theme_constant_cache;
// Internationalization.
LayoutDirection layout_dir = LAYOUT_DIRECTION_INHERITED;
bool is_rtl_dirty = true;
bool is_rtl = false;
bool localize_numeral_system = true;
// Extra properties.
String tooltip;
} data;
// Dynamic properties.
static constexpr unsigned properties_managed_by_container_count = 12;
static String properties_managed_by_container[properties_managed_by_container_count];
// Global relations.
friend class Viewport;
// Positioning and sizing.
void _update_canvas_item_transform();
Transform2D _get_internal_transform() const;
void _set_anchor(Side p_side, real_t p_anchor);
void _set_position(const Point2 &p_point);
void _set_global_position(const Point2 &p_point);
void _set_size(const Size2 &p_size);
void _compute_offsets(Rect2 p_rect, const real_t p_anchors[4], real_t (&r_offsets)[4]);
void _compute_anchors(Rect2 p_rect, const real_t p_offsets[4], real_t (&r_anchors)[4]);
void _set_layout_mode(LayoutMode p_mode);
void _update_layout_mode();
LayoutMode _get_layout_mode() const;
LayoutMode _get_default_layout_mode() const;
void _set_anchors_layout_preset(int p_preset);
int _get_anchors_layout_preset() const;
void _update_minimum_size_cache();
void _update_minimum_size();
void _size_changed();
void _top_level_changed() override {} // Controls don't need to do anything, only other CanvasItems.
void _top_level_changed_on_parent() override;
void _clear_size_warning();
// Input events.
void _call_gui_input(const Ref<InputEvent> &p_event);
// Focus.
void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Point2 *p_points, real_t p_min, real_t &r_closest_dist, Control **r_closest);
Control *_get_focus_neighbor(Side p_side, int p_count = 0);
// Theming.
void _theme_changed();
void _notify_theme_override_changed();
void _invalidate_theme_cache();
// Extra properties.
static int root_layout_direction;
String get_tooltip_text() const;
protected:
// Dynamic properties.
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
void _validate_property(PropertyInfo &p_property) const;
bool _property_can_revert(const StringName &p_name) const;
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
// Theming.
virtual void _update_theme_item_cache();
// Internationalization.
virtual TypedArray<Vector3i> structured_text_parser(TextServer::StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const;
// Base object overrides.
void _notification(int p_notification);
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
static void _bind_compatibility_methods();
#endif
// Exposed virtual methods.
GDVIRTUAL1RC(bool, _has_point, Vector2)
GDVIRTUAL2RC(TypedArray<Vector3i>, _structured_text_parser, Array, String)
GDVIRTUAL0RC(Vector2, _get_minimum_size)
GDVIRTUAL1RC(String, _get_tooltip, Vector2)
GDVIRTUAL1R(Variant, _get_drag_data, Vector2)
GDVIRTUAL2RC(bool, _can_drop_data, Vector2, Variant)
GDVIRTUAL2(_drop_data, Vector2, Variant)
GDVIRTUAL1RC(Object *, _make_custom_tooltip, String)
GDVIRTUAL1(_gui_input, Ref<InputEvent>)
public:
enum {
NOTIFICATION_RESIZED = 40,
NOTIFICATION_MOUSE_ENTER = 41,
NOTIFICATION_MOUSE_EXIT = 42,
NOTIFICATION_FOCUS_ENTER = 43,
NOTIFICATION_FOCUS_EXIT = 44,
NOTIFICATION_THEME_CHANGED = 45,
NOTIFICATION_SCROLL_BEGIN = 47,
NOTIFICATION_SCROLL_END = 48,
NOTIFICATION_LAYOUT_DIRECTION_CHANGED = 49,
NOTIFICATION_MOUSE_ENTER_SELF = 60,
NOTIFICATION_MOUSE_EXIT_SELF = 61,
};
// Editor plugin interoperability.
// TODO: Decouple controls from their editor plugin and get rid of this.
#ifdef TOOLS_ENABLED
virtual Dictionary _edit_get_state() const override;
virtual void _edit_set_state(const Dictionary &p_state) override;
virtual void _edit_set_position(const Point2 &p_position) override;
virtual Point2 _edit_get_position() const override;
virtual void _edit_set_scale(const Size2 &p_scale) override;
virtual Size2 _edit_get_scale() const override;
virtual void _edit_set_rect(const Rect2 &p_edit_rect) override;
virtual Rect2 _edit_get_rect() const override;
virtual bool _edit_use_rect() const override;
virtual void _edit_set_rotation(real_t p_rotation) override;
virtual real_t _edit_get_rotation() const override;
virtual bool _edit_use_rotation() const override;
virtual void _edit_set_pivot(const Point2 &p_pivot) override;
virtual Point2 _edit_get_pivot() const override;
virtual bool _edit_use_pivot() const override;
virtual Size2 _edit_get_minimum_size() const override;
#endif
virtual void reparent(Node *p_parent, bool p_keep_global_transform = true) override;
// Editor integration.
static void set_root_layout_direction(int p_root_dir);
PackedStringArray get_configuration_warnings() const override;
#ifdef TOOLS_ENABLED
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#endif
virtual bool is_text_field() const;
// Global relations.
bool is_top_level_control() const;
Control *get_parent_control() const;
Window *get_parent_window() const;
Control *get_root_parent_control() const;
Size2 get_parent_area_size() const;
Rect2 get_parent_anchorable_rect() const;
// Positioning and sizing.
virtual Transform2D get_transform() const override;
void set_anchor(Side p_side, real_t p_anchor, bool p_keep_offset = true, bool p_push_opposite_anchor = true);
real_t get_anchor(Side p_side) const;
void set_offset(Side p_side, real_t p_value);
real_t get_offset(Side p_side) const;
void set_anchor_and_offset(Side p_side, real_t p_anchor, real_t p_pos, bool p_push_opposite_anchor = true);
// TODO: Rename to set_begin/end_offsets ?
void set_begin(const Point2 &p_point);
Point2 get_begin() const;
void set_end(const Point2 &p_point);
Point2 get_end() const;
void set_h_grow_direction(GrowDirection p_direction);
GrowDirection get_h_grow_direction() const;
void set_v_grow_direction(GrowDirection p_direction);
GrowDirection get_v_grow_direction() const;
void set_anchors_preset(LayoutPreset p_preset, bool p_keep_offsets = true);
void set_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0);
void set_anchors_and_offsets_preset(LayoutPreset p_preset, LayoutPresetMode p_resize_mode = PRESET_MODE_MINSIZE, int p_margin = 0);
void set_grow_direction_preset(LayoutPreset p_preset);
void set_position(const Point2 &p_point, bool p_keep_offsets = false);
void set_global_position(const Point2 &p_point, bool p_keep_offsets = false);
Point2 get_position() const;
Point2 get_global_position() const;
Point2 get_screen_position() const;
void set_size(const Size2 &p_size, bool p_keep_offsets = false);
Size2 get_size() const;
void reset_size();
void set_rect(const Rect2 &p_rect); // Reset anchors to begin and set rect, for faster container children sorting.
Rect2 get_rect() const;
Rect2 get_global_rect() const;
Rect2 get_screen_rect() const;
Rect2 get_anchorable_rect() const override;
void set_scale(const Vector2 &p_scale);
Vector2 get_scale() const;
void set_rotation(real_t p_radians);
void set_rotation_degrees(real_t p_degrees);
real_t get_rotation() const;
real_t get_rotation_degrees() const;
void set_pivot_offset(const Vector2 &p_pivot);
Vector2 get_pivot_offset() const;
void update_minimum_size();
void set_block_minimum_size_adjust(bool p_block);
virtual Size2 get_minimum_size() const;
virtual Size2 get_combined_minimum_size() const;
void set_custom_minimum_size(const Size2 &p_custom);
Size2 get_custom_minimum_size() const;
// Container sizing.
void set_h_size_flags(BitField<SizeFlags> p_flags);
BitField<SizeFlags> get_h_size_flags() const;
void set_v_size_flags(BitField<SizeFlags> p_flags);
BitField<SizeFlags> get_v_size_flags() const;
void set_stretch_ratio(real_t p_ratio);
real_t get_stretch_ratio() const;
// Input events.
virtual void gui_input(const Ref<InputEvent> &p_event);
void accept_event();
virtual bool has_point(const Point2 &p_point) const;
void set_mouse_filter(MouseFilter p_filter);
MouseFilter get_mouse_filter() const;
void set_force_pass_scroll_events(bool p_force_pass_scroll_events);
bool is_force_pass_scroll_events() const;
void warp_mouse(const Point2 &p_position);
bool is_focus_owner_in_shortcut_context() const;
void set_shortcut_context(const Node *p_node);
Node *get_shortcut_context() const;
// Drag and drop handling.
virtual void set_drag_forwarding(const Callable &p_drag, const Callable &p_can_drop, const Callable &p_drop);
virtual Variant get_drag_data(const Point2 &p_point);
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const;
virtual void drop_data(const Point2 &p_point, const Variant &p_data);
void set_drag_preview(Control *p_control);
void force_drag(const Variant &p_data, Control *p_control);
bool is_drag_successful() const;
// Focus.
void set_focus_mode(FocusMode p_focus_mode);
FocusMode get_focus_mode() const;
bool has_focus() const;
void grab_focus();
void grab_click_focus();
void release_focus();
Control *find_next_valid_focus() const;
Control *find_prev_valid_focus() const;
Control *find_valid_focus_neighbor(Side p_size) const;
void set_focus_neighbor(Side p_side, const NodePath &p_neighbor);
NodePath get_focus_neighbor(Side p_side) const;
void set_focus_next(const NodePath &p_next);
NodePath get_focus_next() const;
void set_focus_previous(const NodePath &p_prev);
NodePath get_focus_previous() const;
// Rendering.
void set_default_cursor_shape(CursorShape p_shape);
CursorShape get_default_cursor_shape() const;
virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const;
void set_clip_contents(bool p_clip);
bool is_clipping_contents();
void set_disable_visibility_clip(bool p_ignore);
bool is_visibility_clip_disabled() const;
// Theming.
void set_theme_owner_node(Node *p_node);
Node *get_theme_owner_node() const;
bool has_theme_owner_node() const;
void set_theme_context(ThemeContext *p_context, bool p_propagate = true);
void set_theme(const Ref<Theme> &p_theme);
Ref<Theme> get_theme() const;
void set_theme_type_variation(const StringName &p_theme_type);
StringName get_theme_type_variation() const;
void begin_bulk_theme_override();
void end_bulk_theme_override();
void add_theme_icon_override(const StringName &p_name, const Ref<Texture2D> &p_icon);
void add_theme_style_override(const StringName &p_name, const Ref<StyleBox> &p_style);
void add_theme_font_override(const StringName &p_name, const Ref<Font> &p_font);
void add_theme_font_size_override(const StringName &p_name, int p_font_size);
void add_theme_color_override(const StringName &p_name, const Color &p_color);
void add_theme_constant_override(const StringName &p_name, int p_constant);
void remove_theme_icon_override(const StringName &p_name);
void remove_theme_style_override(const StringName &p_name);
void remove_theme_font_override(const StringName &p_name);
void remove_theme_font_size_override(const StringName &p_name);
void remove_theme_color_override(const StringName &p_name);
void remove_theme_constant_override(const StringName &p_name);
Ref<Texture2D> get_theme_icon(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
Ref<StyleBox> get_theme_stylebox(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
Ref<Font> get_theme_font(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
int get_theme_font_size(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
Color get_theme_color(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
int get_theme_constant(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
Variant get_theme_item(Theme::DataType p_data_type, const StringName &p_name, const StringName &p_theme_type = StringName()) const;
#ifdef TOOLS_ENABLED
Ref<Texture2D> get_editor_theme_icon(const StringName &p_name) const;
#endif
bool has_theme_icon_override(const StringName &p_name) const;
bool has_theme_stylebox_override(const StringName &p_name) const;
bool has_theme_font_override(const StringName &p_name) const;
bool has_theme_font_size_override(const StringName &p_name) const;
bool has_theme_color_override(const StringName &p_name) const;
bool has_theme_constant_override(const StringName &p_name) const;
bool has_theme_icon(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
bool has_theme_stylebox(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
bool has_theme_font(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
bool has_theme_font_size(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
bool has_theme_color(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
bool has_theme_constant(const StringName &p_name, const StringName &p_theme_type = StringName()) const;
float get_theme_default_base_scale() const;
Ref<Font> get_theme_default_font() const;
int get_theme_default_font_size() const;
// Internationalization.
void set_layout_direction(LayoutDirection p_direction);
LayoutDirection get_layout_direction() const;
virtual bool is_layout_rtl() const;
void set_localize_numeral_system(bool p_enable);
bool is_localizing_numeral_system() const;
#ifndef DISABLE_DEPRECATED
void set_auto_translate(bool p_enable);
bool is_auto_translating() const;
#endif
// Extra properties.
void set_tooltip_text(const String &text);
virtual String get_tooltip(const Point2 &p_pos) const;
virtual Control *make_custom_tooltip(const String &p_text) const;
Control();
~Control();
};
VARIANT_ENUM_CAST(Control::FocusMode);
VARIANT_BITFIELD_CAST(Control::SizeFlags);
VARIANT_ENUM_CAST(Control::CursorShape);
VARIANT_ENUM_CAST(Control::LayoutPreset);
VARIANT_ENUM_CAST(Control::LayoutPresetMode);
VARIANT_ENUM_CAST(Control::MouseFilter);
VARIANT_ENUM_CAST(Control::GrowDirection);
VARIANT_ENUM_CAST(Control::Anchor);
VARIANT_ENUM_CAST(Control::LayoutMode);
VARIANT_ENUM_CAST(Control::LayoutDirection);
VARIANT_ENUM_CAST(Control::TextDirection);
// G = get_drag_data_fw, C = can_drop_data_fw, D = drop_data_fw, U = underscore
#define SET_DRAG_FORWARDING_CD(from, to) from->set_drag_forwarding(Callable(), callable_mp(this, &to::can_drop_data_fw).bind(from), callable_mp(this, &to::drop_data_fw).bind(from));
#define SET_DRAG_FORWARDING_CDU(from, to) from->set_drag_forwarding(Callable(), callable_mp(this, &to::_can_drop_data_fw).bind(from), callable_mp(this, &to::_drop_data_fw).bind(from));
#define SET_DRAG_FORWARDING_GCD(from, to) from->set_drag_forwarding(callable_mp(this, &to::get_drag_data_fw).bind(from), callable_mp(this, &to::can_drop_data_fw).bind(from), callable_mp(this, &to::drop_data_fw).bind(from));
#define SET_DRAG_FORWARDING_GCDU(from, to) from->set_drag_forwarding(callable_mp(this, &to::_get_drag_data_fw).bind(from), callable_mp(this, &to::_can_drop_data_fw).bind(from), callable_mp(this, &to::_drop_data_fw).bind(from));
#endif // CONTROL_H

View file

@ -0,0 +1,46 @@
/**************************************************************************/
/* dialogs.compat.inc */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
void AcceptDialog::_register_text_enter_bind_compat_89419(Control *p_line_edit) {
register_text_enter(Object::cast_to<LineEdit>(p_line_edit));
}
void AcceptDialog::_remove_button_bind_compat_89419(Control *p_button) {
remove_button(Object::cast_to<Button>(p_button));
}
void AcceptDialog::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("register_text_enter"), &AcceptDialog::_register_text_enter_bind_compat_89419);
ClassDB::bind_compatibility_method(D_METHOD("remove_button", "quadrant_size"), &AcceptDialog::_remove_button_bind_compat_89419);
}
#endif

View file

@ -0,0 +1,487 @@
/**************************************************************************/
/* dialogs.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 "dialogs.h"
#include "dialogs.compat.inc"
#include "core/os/keyboard.h"
#include "core/string/print_string.h"
#include "core/string/translation.h"
#include "scene/gui/line_edit.h"
#include "scene/theme/theme_db.h"
// AcceptDialog
void AcceptDialog::_input_from_window(const Ref<InputEvent> &p_event) {
if (close_on_escape && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
_cancel_pressed();
}
Window::_input_from_window(p_event);
}
void AcceptDialog::_parent_focused() {
if (popped_up && !is_exclusive() && get_flag(FLAG_POPUP)) {
_cancel_pressed();
}
}
void AcceptDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POST_ENTER_TREE: {
if (is_visible()) {
get_ok_button()->grab_focus();
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (is_visible()) {
if (get_ok_button()->is_inside_tree()) {
get_ok_button()->grab_focus();
}
_update_child_rects();
parent_visible = get_parent_visible_window();
if (parent_visible) {
parent_visible->connect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused));
}
} else {
popped_up = false;
if (parent_visible) {
parent_visible->disconnect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused));
parent_visible = nullptr;
}
}
} break;
case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
if (!is_in_edited_scene_root()) {
if (has_focus()) {
popped_up = true;
}
}
} break;
case NOTIFICATION_THEME_CHANGED: {
bg_panel->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style);
child_controls_changed();
if (is_visible()) {
_update_child_rects();
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (parent_visible) {
parent_visible->disconnect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused));
parent_visible = nullptr;
}
} break;
case NOTIFICATION_READY:
case NOTIFICATION_WM_SIZE_CHANGED: {
if (is_visible()) {
_update_child_rects();
}
} break;
case NOTIFICATION_WM_CLOSE_REQUEST: {
_cancel_pressed();
} break;
}
}
void AcceptDialog::_text_submitted(const String &p_text) {
if (get_ok_button() && get_ok_button()->is_disabled()) {
return; // Do not allow submission if OK button is disabled.
}
_ok_pressed();
}
void AcceptDialog::_post_popup() {
Window::_post_popup();
popped_up = true;
}
void AcceptDialog::_ok_pressed() {
if (hide_on_ok) {
popped_up = false;
set_visible(false);
}
ok_pressed();
emit_signal(SceneStringName(confirmed));
set_input_as_handled();
}
void AcceptDialog::_cancel_pressed() {
popped_up = false;
Window *parent_window = parent_visible;
if (parent_visible) {
parent_visible->disconnect(SceneStringName(focus_entered), callable_mp(this, &AcceptDialog::_parent_focused));
parent_visible = nullptr;
}
callable_mp((Window *)this, &Window::hide).call_deferred();
emit_signal(SNAME("canceled"));
cancel_pressed();
if (parent_window) {
//parent_window->grab_focus();
}
set_input_as_handled();
}
String AcceptDialog::get_text() const {
return message_label->get_text();
}
void AcceptDialog::set_text(String p_text) {
if (message_label->get_text() == p_text) {
return;
}
message_label->set_text(p_text);
child_controls_changed();
if (is_visible()) {
_update_child_rects();
}
}
void AcceptDialog::set_hide_on_ok(bool p_hide) {
hide_on_ok = p_hide;
}
bool AcceptDialog::get_hide_on_ok() const {
return hide_on_ok;
}
void AcceptDialog::set_close_on_escape(bool p_hide) {
close_on_escape = p_hide;
}
bool AcceptDialog::get_close_on_escape() const {
return close_on_escape;
}
void AcceptDialog::set_autowrap(bool p_autowrap) {
message_label->set_autowrap_mode(p_autowrap ? TextServer::AUTOWRAP_WORD : TextServer::AUTOWRAP_OFF);
}
bool AcceptDialog::has_autowrap() {
return message_label->get_autowrap_mode() != TextServer::AUTOWRAP_OFF;
}
void AcceptDialog::set_ok_button_text(String p_ok_button_text) {
ok_button->set_text(p_ok_button_text);
child_controls_changed();
if (is_visible()) {
_update_child_rects();
}
}
String AcceptDialog::get_ok_button_text() const {
return ok_button->get_text();
}
void AcceptDialog::register_text_enter(LineEdit *p_line_edit) {
ERR_FAIL_NULL(p_line_edit);
p_line_edit->connect("text_submitted", callable_mp(this, &AcceptDialog::_text_submitted));
}
void AcceptDialog::_update_child_rects() {
Size2 dlg_size = Vector2(get_size()) / get_content_scale_factor();
float h_margins = theme_cache.panel_style->get_margin(SIDE_LEFT) + theme_cache.panel_style->get_margin(SIDE_RIGHT);
float v_margins = theme_cache.panel_style->get_margin(SIDE_TOP) + theme_cache.panel_style->get_margin(SIDE_BOTTOM);
// Fill the entire size of the window with the background.
bg_panel->set_position(Point2());
bg_panel->set_size(dlg_size);
for (int i = 0; i < buttons_hbox->get_child_count(); i++) {
Button *b = Object::cast_to<Button>(buttons_hbox->get_child(i));
if (!b) {
continue;
}
b->set_custom_minimum_size(Size2(theme_cache.buttons_min_width, theme_cache.buttons_min_height));
}
// Place the buttons from the bottom edge to their minimum required size.
Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size();
Size2 buttons_size = Size2(dlg_size.x - h_margins, buttons_minsize.y);
Point2 buttons_position = Point2(theme_cache.panel_style->get_margin(SIDE_LEFT), dlg_size.y - theme_cache.panel_style->get_margin(SIDE_BOTTOM) - buttons_size.y);
buttons_hbox->set_position(buttons_position);
buttons_hbox->set_size(buttons_size);
// Place the content from the top to fill the rest of the space (minus the separation).
Point2 content_position = Point2(theme_cache.panel_style->get_margin(SIDE_LEFT), theme_cache.panel_style->get_margin(SIDE_TOP));
Size2 content_size = Size2(dlg_size.x - h_margins, dlg_size.y - v_margins - buttons_size.y - theme_cache.buttons_separation);
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
if (!c) {
continue;
}
if (c == buttons_hbox || c == bg_panel || c->is_set_as_top_level()) {
continue;
}
c->set_position(content_position);
c->set_size(content_size);
}
}
Size2 AcceptDialog::_get_contents_minimum_size() const {
// First, we then iterate over the label and any other custom controls
// to try and find the size that encompasses all content.
Size2 content_minsize;
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
if (!c) {
continue;
}
// Buttons will be included afterwards.
// The panel only displays the stylebox and doesn't contribute to the size.
if (c == buttons_hbox || c == bg_panel || c->is_set_as_top_level()) {
continue;
}
Size2 child_minsize = c->get_combined_minimum_size();
content_minsize = child_minsize.max(content_minsize);
}
// Then we add buttons. Horizontally we're interested in whichever
// value is the biggest. Vertically buttons add to the overall size.
Size2 buttons_minsize = buttons_hbox->get_combined_minimum_size();
content_minsize.x = MAX(buttons_minsize.x, content_minsize.x);
content_minsize.y += buttons_minsize.y;
// Plus there is a separation size added on top.
content_minsize.y += theme_cache.buttons_separation;
// Then we take the background panel as it provides the offsets,
// which are always added to the minimum size.
if (theme_cache.panel_style.is_valid()) {
content_minsize += theme_cache.panel_style->get_minimum_size();
}
return content_minsize;
}
void AcceptDialog::_custom_action(const String &p_action) {
emit_signal(SNAME("custom_action"), p_action);
custom_action(p_action);
}
void AcceptDialog::_custom_button_visibility_changed(Button *button) {
Control *right_spacer = Object::cast_to<Control>(button->get_meta("__right_spacer"));
if (right_spacer) {
right_spacer->set_visible(button->is_visible());
}
}
Button *AcceptDialog::add_button(const String &p_text, bool p_right, const String &p_action) {
Button *button = memnew(Button);
button->set_text(p_text);
Control *right_spacer;
if (p_right) {
buttons_hbox->add_child(button);
right_spacer = buttons_hbox->add_spacer();
} else {
buttons_hbox->add_child(button);
buttons_hbox->move_child(button, 0);
right_spacer = buttons_hbox->add_spacer(true);
}
button->set_meta("__right_spacer", right_spacer);
button->connect(SceneStringName(visibility_changed), callable_mp(this, &AcceptDialog::_custom_button_visibility_changed).bind(button));
child_controls_changed();
if (is_visible()) {
_update_child_rects();
}
if (!p_action.is_empty()) {
button->connect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_custom_action).bind(p_action));
}
return button;
}
Button *AcceptDialog::add_cancel_button(const String &p_cancel) {
String c = p_cancel;
if (p_cancel.is_empty()) {
c = ETR("Cancel");
}
Button *b = swap_cancel_ok ? add_button(c, true) : add_button(c);
b->connect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_cancel_pressed));
return b;
}
void AcceptDialog::remove_button(Button *p_button) {
ERR_FAIL_NULL(p_button);
ERR_FAIL_COND_MSG(p_button->get_parent() != buttons_hbox, vformat("Cannot remove button %s as it does not belong to this dialog.", p_button->get_name()));
ERR_FAIL_COND_MSG(p_button == ok_button, "Cannot remove dialog's OK button.");
Control *right_spacer = Object::cast_to<Control>(p_button->get_meta("__right_spacer"));
if (right_spacer) {
ERR_FAIL_COND_MSG(right_spacer->get_parent() != buttons_hbox, vformat("Cannot remove button %s as its associated spacer does not belong to this dialog.", p_button->get_name()));
}
p_button->disconnect(SceneStringName(visibility_changed), callable_mp(this, &AcceptDialog::_custom_button_visibility_changed));
if (p_button->is_connected(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_custom_action))) {
p_button->disconnect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_custom_action));
}
if (p_button->is_connected(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_cancel_pressed))) {
p_button->disconnect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_cancel_pressed));
}
if (right_spacer) {
buttons_hbox->remove_child(right_spacer);
p_button->remove_meta("__right_spacer");
right_spacer->queue_free();
}
buttons_hbox->remove_child(p_button);
child_controls_changed();
if (is_visible()) {
_update_child_rects();
}
}
void AcceptDialog::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_ok_button"), &AcceptDialog::get_ok_button);
ClassDB::bind_method(D_METHOD("get_label"), &AcceptDialog::get_label);
ClassDB::bind_method(D_METHOD("set_hide_on_ok", "enabled"), &AcceptDialog::set_hide_on_ok);
ClassDB::bind_method(D_METHOD("get_hide_on_ok"), &AcceptDialog::get_hide_on_ok);
ClassDB::bind_method(D_METHOD("set_close_on_escape", "enabled"), &AcceptDialog::set_close_on_escape);
ClassDB::bind_method(D_METHOD("get_close_on_escape"), &AcceptDialog::get_close_on_escape);
ClassDB::bind_method(D_METHOD("add_button", "text", "right", "action"), &AcceptDialog::add_button, DEFVAL(false), DEFVAL(""));
ClassDB::bind_method(D_METHOD("add_cancel_button", "name"), &AcceptDialog::add_cancel_button);
ClassDB::bind_method(D_METHOD("remove_button", "button"), &AcceptDialog::remove_button);
ClassDB::bind_method(D_METHOD("register_text_enter", "line_edit"), &AcceptDialog::register_text_enter);
ClassDB::bind_method(D_METHOD("set_text", "text"), &AcceptDialog::set_text);
ClassDB::bind_method(D_METHOD("get_text"), &AcceptDialog::get_text);
ClassDB::bind_method(D_METHOD("set_autowrap", "autowrap"), &AcceptDialog::set_autowrap);
ClassDB::bind_method(D_METHOD("has_autowrap"), &AcceptDialog::has_autowrap);
ClassDB::bind_method(D_METHOD("set_ok_button_text", "text"), &AcceptDialog::set_ok_button_text);
ClassDB::bind_method(D_METHOD("get_ok_button_text"), &AcceptDialog::get_ok_button_text);
ADD_SIGNAL(MethodInfo("confirmed"));
ADD_SIGNAL(MethodInfo("canceled"));
ADD_SIGNAL(MethodInfo("custom_action", PropertyInfo(Variant::STRING_NAME, "action")));
ADD_PROPERTY(PropertyInfo(Variant::STRING, "ok_button_text"), "set_ok_button_text", "get_ok_button_text");
ADD_GROUP("Dialog", "dialog_");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "dialog_text", PROPERTY_HINT_MULTILINE_TEXT), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_hide_on_ok"), "set_hide_on_ok", "get_hide_on_ok");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_close_on_escape"), "set_close_on_escape", "get_close_on_escape");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dialog_autowrap"), "set_autowrap", "has_autowrap");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, AcceptDialog, panel_style, "panel");
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_width);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, AcceptDialog, buttons_min_height);
}
bool AcceptDialog::swap_cancel_ok = false;
void AcceptDialog::set_swap_cancel_ok(bool p_swap) {
swap_cancel_ok = p_swap;
}
AcceptDialog::AcceptDialog() {
set_wrap_controls(true);
set_visible(false);
set_transient(true);
set_exclusive(true);
set_clamp_to_embedder(true);
set_keep_title_visible(true);
bg_panel = memnew(Panel);
add_child(bg_panel, false, INTERNAL_MODE_FRONT);
buttons_hbox = memnew(HBoxContainer);
message_label = memnew(Label);
message_label->set_anchor(SIDE_RIGHT, Control::ANCHOR_END);
message_label->set_anchor(SIDE_BOTTOM, Control::ANCHOR_END);
add_child(message_label, false, INTERNAL_MODE_FRONT);
add_child(buttons_hbox, false, INTERNAL_MODE_FRONT);
buttons_hbox->add_spacer();
ok_button = memnew(Button);
ok_button->set_text(ETR("OK"));
buttons_hbox->add_child(ok_button);
buttons_hbox->add_spacer();
ok_button->connect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_ok_pressed));
set_title(ETR("Alert!"));
}
AcceptDialog::~AcceptDialog() {
}
// ConfirmationDialog
void ConfirmationDialog::set_cancel_button_text(String p_cancel_button_text) {
cancel->set_text(p_cancel_button_text);
}
String ConfirmationDialog::get_cancel_button_text() const {
return cancel->get_text();
}
void ConfirmationDialog::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_cancel_button"), &ConfirmationDialog::get_cancel_button);
ClassDB::bind_method(D_METHOD("set_cancel_button_text", "text"), &ConfirmationDialog::set_cancel_button_text);
ClassDB::bind_method(D_METHOD("get_cancel_button_text"), &ConfirmationDialog::get_cancel_button_text);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "cancel_button_text"), "set_cancel_button_text", "get_cancel_button_text");
}
Button *ConfirmationDialog::get_cancel_button() {
return cancel;
}
ConfirmationDialog::ConfirmationDialog() {
set_title(ETR("Please Confirm..."));
set_min_size(Size2(200, 70));
cancel = add_cancel_button();
}

143
engine/scene/gui/dialogs.h Normal file
View file

@ -0,0 +1,143 @@
/**************************************************************************/
/* dialogs.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. */
/**************************************************************************/
#ifndef DIALOGS_H
#define DIALOGS_H
#include "box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/label.h"
#include "scene/gui/panel.h"
#include "scene/gui/popup.h"
#include "scene/gui/texture_button.h"
#include "scene/main/window.h"
class LineEdit;
class AcceptDialog : public Window {
GDCLASS(AcceptDialog, Window);
Window *parent_visible = nullptr;
Panel *bg_panel = nullptr;
Label *message_label = nullptr;
HBoxContainer *buttons_hbox = nullptr;
Button *ok_button = nullptr;
bool popped_up = false;
bool hide_on_ok = true;
bool close_on_escape = true;
struct ThemeCache {
Ref<StyleBox> panel_style;
int buttons_separation = 0;
int buttons_min_width = 0;
int buttons_min_height = 0;
} theme_cache;
void _custom_action(const String &p_action);
void _custom_button_visibility_changed(Button *button);
void _update_child_rects();
static bool swap_cancel_ok;
void _parent_focused();
protected:
virtual Size2 _get_contents_minimum_size() const override;
virtual void _input_from_window(const Ref<InputEvent> &p_event) override;
virtual void _post_popup() override;
void _notification(int p_what);
static void _bind_methods();
virtual void ok_pressed() {}
virtual void cancel_pressed() {}
virtual void custom_action(const String &) {}
// Not private since used by derived classes signal.
void _text_submitted(const String &p_text);
void _ok_pressed();
void _cancel_pressed();
#ifndef DISABLE_DEPRECATED
void _register_text_enter_bind_compat_89419(Control *p_line_edit);
void _remove_button_bind_compat_89419(Control *p_button);
static void _bind_compatibility_methods();
#endif
public:
Label *get_label() { return message_label; }
static void set_swap_cancel_ok(bool p_swap);
void register_text_enter(LineEdit *p_line_edit);
Button *get_ok_button() { return ok_button; }
Button *add_button(const String &p_text, bool p_right = false, const String &p_action = "");
Button *add_cancel_button(const String &p_cancel = "");
void remove_button(Button *p_button);
void set_hide_on_ok(bool p_hide);
bool get_hide_on_ok() const;
void set_close_on_escape(bool p_enable);
bool get_close_on_escape() const;
void set_text(String p_text);
String get_text() const;
void set_autowrap(bool p_autowrap);
bool has_autowrap();
void set_ok_button_text(String p_ok_button_text);
String get_ok_button_text() const;
AcceptDialog();
~AcceptDialog();
};
class ConfirmationDialog : public AcceptDialog {
GDCLASS(ConfirmationDialog, AcceptDialog);
Button *cancel = nullptr;
protected:
static void _bind_methods();
public:
Button *get_cancel_button();
void set_cancel_button_text(String p_cancel_button_text);
String get_cancel_button_text() const;
ConfirmationDialog();
};
#endif // DIALOGS_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,270 @@
/**************************************************************************/
/* file_dialog.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. */
/**************************************************************************/
#ifndef FILE_DIALOG_H
#define FILE_DIALOG_H
#include "box_container.h"
#include "core/io/dir_access.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/line_edit.h"
#include "scene/gui/option_button.h"
#include "scene/gui/tree.h"
#include "scene/property_list_helper.h"
class GridContainer;
class FileDialog : public ConfirmationDialog {
GDCLASS(FileDialog, ConfirmationDialog);
public:
enum Access {
ACCESS_RESOURCES,
ACCESS_USERDATA,
ACCESS_FILESYSTEM
};
enum FileMode {
FILE_MODE_OPEN_FILE,
FILE_MODE_OPEN_FILES,
FILE_MODE_OPEN_DIR,
FILE_MODE_OPEN_ANY,
FILE_MODE_SAVE_FILE
};
typedef Ref<Texture2D> (*GetIconFunc)(const String &);
typedef void (*RegisterFunc)(FileDialog *);
static GetIconFunc get_icon_func;
static RegisterFunc register_func;
static RegisterFunc unregister_func;
private:
ConfirmationDialog *makedialog = nullptr;
LineEdit *makedirname = nullptr;
Button *makedir = nullptr;
Access access = ACCESS_RESOURCES;
VBoxContainer *vbox = nullptr;
GridContainer *grid_options = nullptr;
FileMode mode;
LineEdit *dir = nullptr;
HBoxContainer *drives_container = nullptr;
HBoxContainer *shortcuts_container = nullptr;
OptionButton *drives = nullptr;
Tree *tree = nullptr;
HBoxContainer *file_box = nullptr;
LineEdit *file = nullptr;
OptionButton *filter = nullptr;
AcceptDialog *mkdirerr = nullptr;
AcceptDialog *exterr = nullptr;
Ref<DirAccess> dir_access;
ConfirmationDialog *confirm_save = nullptr;
Label *message = nullptr;
Button *dir_prev = nullptr;
Button *dir_next = nullptr;
Button *dir_up = nullptr;
Button *refresh = nullptr;
Button *show_hidden = nullptr;
Vector<String> filters;
Vector<String> local_history;
int local_history_pos = 0;
void _push_history();
bool mode_overrides_title = true;
String root_subfolder;
String root_prefix;
static bool default_show_hidden_files;
bool show_hidden_files = false;
bool use_native_dialog = false;
bool is_invalidating = false;
struct ThemeCache {
Ref<Texture2D> parent_folder;
Ref<Texture2D> forward_folder;
Ref<Texture2D> back_folder;
Ref<Texture2D> reload;
Ref<Texture2D> toggle_hidden;
Ref<Texture2D> folder;
Ref<Texture2D> file;
Ref<Texture2D> create_folder;
Color folder_icon_color;
Color file_icon_color;
Color file_disabled_color;
Color icon_normal_color;
Color icon_hover_color;
Color icon_focus_color;
Color icon_pressed_color;
} theme_cache;
struct Option {
String name;
Vector<String> values;
int default_idx = 0;
};
static inline PropertyListHelper base_property_helper;
PropertyListHelper property_helper;
Vector<Option> options;
Dictionary selected_options;
bool options_dirty = false;
void update_dir();
void update_file_name();
void update_file_list();
void update_filters();
void _focus_file_text();
void _tree_multi_selected(Object *p_object, int p_cell, bool p_selected);
void _tree_selected();
void _select_drive(int p_idx);
void _tree_item_activated();
void _dir_submitted(String p_dir);
void _file_submitted(const String &p_file);
void _action_pressed();
void _save_confirm_pressed();
void _cancel_pressed();
void _filter_selected(int);
void _make_dir();
void _make_dir_confirm();
void _go_up();
void _go_back();
void _go_forward();
void _change_dir(const String &p_new_dir);
void _update_drives(bool p_select = true);
void _invalidate();
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
void _native_popup();
void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options);
bool _is_open_should_be_disabled();
TypedArray<Dictionary> _get_options() const;
void _update_option_controls();
void _option_changed_checkbox_toggled(bool p_pressed, const String &p_name);
void _option_changed_item_selected(int p_idx, const String &p_name);
virtual void _post_popup() override;
protected:
void _validate_property(PropertyInfo &p_property) const;
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value) { return property_helper.property_set_value(p_name, p_value); }
bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); }
void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list); }
bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
static void _bind_methods();
public:
virtual void set_visible(bool p_visible) override;
virtual void popup(const Rect2i &p_rect = Rect2i()) override;
void popup_file_dialog();
void clear_filters();
void add_filter(const String &p_filter, const String &p_description = "");
void set_filters(const Vector<String> &p_filters);
Vector<String> get_filters() const;
void set_enable_multiple_selection(bool p_enable);
Vector<String> get_selected_files() const;
String get_current_dir() const;
String get_current_file() const;
String get_current_path() const;
void set_current_dir(const String &p_dir);
void set_current_file(const String &p_file);
void set_current_path(const String &p_path);
String get_option_name(int p_option) const;
Vector<String> get_option_values(int p_option) const;
int get_option_default(int p_option) const;
void set_option_name(int p_option, const String &p_name);
void set_option_values(int p_option, const Vector<String> &p_values);
void set_option_default(int p_option, int p_index);
void add_option(const String &p_name, const Vector<String> &p_values, int p_index);
void set_option_count(int p_count);
int get_option_count() const;
Dictionary get_selected_options() const;
void set_root_subfolder(const String &p_root);
String get_root_subfolder() const;
void set_mode_overrides_title(bool p_override);
bool is_mode_overriding_title() const;
void set_use_native_dialog(bool p_native);
bool get_use_native_dialog() const;
void set_file_mode(FileMode p_mode);
FileMode get_file_mode() const;
VBoxContainer *get_vbox();
LineEdit *get_line_edit() { return file; }
void set_access(Access p_access);
Access get_access() const;
void set_show_hidden_files(bool p_show);
bool is_showing_hidden_files() const;
static void set_default_show_hidden_files(bool p_show);
void invalidate();
void deselect_all();
FileDialog();
~FileDialog();
};
VARIANT_ENUM_CAST(FileDialog::FileMode);
VARIANT_ENUM_CAST(FileDialog::Access);
#endif // FILE_DIALOG_H

View file

@ -0,0 +1,420 @@
/**************************************************************************/
/* flow_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 "flow_container.h"
#include "scene/gui/texture_rect.h"
#include "scene/theme/theme_db.h"
struct _LineData {
int child_count = 0;
int min_line_height = 0;
int min_line_length = 0;
int stretch_avail = 0;
float stretch_ratio_total = 0;
bool is_filled = false;
};
void FlowContainer::_resort() {
// Avoid resorting if invisible.
if (!is_visible_in_tree()) {
return;
}
bool rtl = is_layout_rtl();
HashMap<Control *, Size2i> children_minsize_cache;
Vector<_LineData> lines_data;
Vector2i ofs;
int line_height = 0;
int line_length = 0;
float line_stretch_ratio_total = 0;
int current_container_size = vertical ? get_size().y : get_size().x;
int children_in_current_line = 0;
Control *last_child = nullptr;
// First pass for line wrapping and minimum size calculation.
for (int i = 0; i < get_child_count(); i++) {
Control *child = as_sortable_control(get_child(i));
if (!child) {
continue;
}
Size2i child_msc = child->get_combined_minimum_size();
if (vertical) { /* VERTICAL */
if (children_in_current_line > 0) {
ofs.y += theme_cache.v_separation;
}
if (ofs.y + child_msc.y > current_container_size) {
line_length = ofs.y - theme_cache.v_separation;
lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, true });
// Move in new column (vertical line).
ofs.x += line_height + theme_cache.h_separation;
ofs.y = 0;
line_height = 0;
line_stretch_ratio_total = 0;
children_in_current_line = 0;
}
line_height = MAX(line_height, child_msc.x);
if (child->get_v_size_flags().has_flag(SIZE_EXPAND)) {
line_stretch_ratio_total += child->get_stretch_ratio();
}
ofs.y += child_msc.y;
} else { /* HORIZONTAL */
if (children_in_current_line > 0) {
ofs.x += theme_cache.h_separation;
}
if (ofs.x + child_msc.x > current_container_size) {
line_length = ofs.x - theme_cache.h_separation;
lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, true });
// Move in new line.
ofs.y += line_height + theme_cache.v_separation;
ofs.x = 0;
line_height = 0;
line_stretch_ratio_total = 0;
children_in_current_line = 0;
}
line_height = MAX(line_height, child_msc.y);
if (child->get_h_size_flags().has_flag(SIZE_EXPAND)) {
line_stretch_ratio_total += child->get_stretch_ratio();
}
ofs.x += child_msc.x;
}
last_child = child;
children_minsize_cache[child] = child_msc;
children_in_current_line++;
}
line_length = vertical ? (ofs.y) : (ofs.x);
bool is_filled = false;
if (last_child != nullptr) {
is_filled = vertical ? (ofs.y + last_child->get_combined_minimum_size().y > current_container_size ? true : false) : (ofs.x + last_child->get_combined_minimum_size().x > current_container_size ? true : false);
}
lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, is_filled });
// Second pass for in-line expansion and alignment.
int current_line_idx = 0;
int child_idx_in_line = 0;
ofs.x = 0;
ofs.y = 0;
for (int i = 0; i < get_child_count(); i++) {
Control *child = as_sortable_control(get_child(i));
if (!child) {
continue;
}
Size2i child_size = children_minsize_cache[child];
_LineData line_data = lines_data[current_line_idx];
if (child_idx_in_line >= lines_data[current_line_idx].child_count) {
current_line_idx++;
child_idx_in_line = 0;
if (vertical) {
ofs.x += line_data.min_line_height + theme_cache.h_separation;
ofs.y = 0;
} else {
ofs.x = 0;
ofs.y += line_data.min_line_height + theme_cache.v_separation;
}
line_data = lines_data[current_line_idx];
}
// The first child of each line adds the offset caused by the alignment,
// but only if the line doesn't contain a child that expands.
if (child_idx_in_line == 0 && Math::is_equal_approx(line_data.stretch_ratio_total, 0)) {
int alignment_ofs = 0;
bool is_not_first_line_and_not_filled = current_line_idx != 0 && !line_data.is_filled;
float prior_stretch_avail = is_not_first_line_and_not_filled ? lines_data[current_line_idx - 1].stretch_avail : 0.0;
switch (alignment) {
case ALIGNMENT_BEGIN: {
if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && is_not_first_line_and_not_filled) {
if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_END) {
alignment_ofs = line_data.stretch_avail - prior_stretch_avail;
} else if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_CENTER) {
alignment_ofs = (line_data.stretch_avail - prior_stretch_avail) * 0.5;
}
}
} break;
case ALIGNMENT_CENTER: {
if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && last_wrap_alignment != LAST_WRAP_ALIGNMENT_CENTER && is_not_first_line_and_not_filled) {
if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_END) {
alignment_ofs = line_data.stretch_avail - (prior_stretch_avail * 0.5);
} else { // Is LAST_WRAP_ALIGNMENT_BEGIN
alignment_ofs = prior_stretch_avail * 0.5;
}
} else {
alignment_ofs = line_data.stretch_avail * 0.5;
}
} break;
case ALIGNMENT_END: {
if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && last_wrap_alignment != LAST_WRAP_ALIGNMENT_END && is_not_first_line_and_not_filled) {
if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_BEGIN) {
alignment_ofs = prior_stretch_avail;
} else { // Is LAST_WRAP_ALIGNMENT_CENTER
alignment_ofs = prior_stretch_avail + (line_data.stretch_avail - prior_stretch_avail) * 0.5;
}
} else {
alignment_ofs = line_data.stretch_avail;
}
} break;
default:
break;
}
if (vertical) { /* VERTICAL */
ofs.y += alignment_ofs;
} else { /* HORIZONTAL */
ofs.x += alignment_ofs;
}
}
bool is_unsupported_texture_rect = false;
if (lines_data.size() > 1) {
TextureRect *trect = Object::cast_to<TextureRect>(child);
if (trect) {
TextureRect::ExpandMode mode = trect->get_expand_mode();
if (mode == TextureRect::EXPAND_FIT_WIDTH || mode == TextureRect::EXPAND_FIT_WIDTH_PROPORTIONAL ||
mode == TextureRect::EXPAND_FIT_HEIGHT || mode == TextureRect::EXPAND_FIT_HEIGHT_PROPORTIONAL) {
is_unsupported_texture_rect = true;
}
}
}
if (is_unsupported_texture_rect) {
// Temporary fix for editor crash. Changing size of TextureRect with EXPAND_FIT_* ExpandModes can lead to infinite loop if child items are moved between lines.
WARN_PRINT_ONCE("TextureRects with Fit Expand Modes are currently not supported inside FlowContainers with multiple lines");
child_size = child->get_size();
} else if (vertical) { /* VERTICAL */
if (child->get_h_size_flags().has_flag(SIZE_FILL) || child->get_h_size_flags().has_flag(SIZE_SHRINK_CENTER) || child->get_h_size_flags().has_flag(SIZE_SHRINK_END)) {
child_size.width = line_data.min_line_height;
}
if (child->get_v_size_flags().has_flag(SIZE_EXPAND)) {
int stretch = line_data.stretch_avail * child->get_stretch_ratio() / line_data.stretch_ratio_total;
child_size.height += stretch;
}
} else { /* HORIZONTAL */
if (child->get_v_size_flags().has_flag(SIZE_FILL) || child->get_v_size_flags().has_flag(SIZE_SHRINK_CENTER) || child->get_v_size_flags().has_flag(SIZE_SHRINK_END)) {
child_size.height = line_data.min_line_height;
}
if (child->get_h_size_flags().has_flag(SIZE_EXPAND)) {
int stretch = line_data.stretch_avail * child->get_stretch_ratio() / line_data.stretch_ratio_total;
child_size.width += stretch;
}
}
Rect2 child_rect = Rect2(ofs, child_size);
if (reverse_fill && !vertical) {
child_rect.position.y = get_rect().size.y - child_rect.position.y - child_rect.size.height;
}
if ((rtl && !vertical) || ((rtl != reverse_fill) && vertical)) {
child_rect.position.x = get_rect().size.x - child_rect.position.x - child_rect.size.width;
}
fit_child_in_rect(child, child_rect);
if (vertical) { /* VERTICAL */
ofs.y += child_size.height + theme_cache.v_separation;
} else { /* HORIZONTAL */
ofs.x += child_size.width + theme_cache.h_separation;
}
child_idx_in_line++;
}
cached_size = (vertical ? ofs.x : ofs.y) + line_height;
cached_line_count = lines_data.size();
}
Size2 FlowContainer::get_minimum_size() const {
Size2i minimum;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE);
if (!c) {
continue;
}
Size2i size = c->get_combined_minimum_size();
if (vertical) { /* VERTICAL */
minimum.height = MAX(minimum.height, size.height);
minimum.width = cached_size;
} else { /* HORIZONTAL */
minimum.width = MAX(minimum.width, size.width);
minimum.height = cached_size;
}
}
return minimum;
}
Vector<int> FlowContainer::get_allowed_size_flags_horizontal() const {
Vector<int> flags;
flags.append(SIZE_FILL);
if (!vertical) {
flags.append(SIZE_EXPAND);
}
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
Vector<int> FlowContainer::get_allowed_size_flags_vertical() const {
Vector<int> flags;
flags.append(SIZE_FILL);
if (vertical) {
flags.append(SIZE_EXPAND);
}
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
void FlowContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_SORT_CHILDREN: {
_resort();
update_minimum_size();
} break;
case NOTIFICATION_THEME_CHANGED: {
update_minimum_size();
} break;
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
queue_sort();
} break;
}
}
void FlowContainer::_validate_property(PropertyInfo &p_property) const {
if (is_fixed && p_property.name == "vertical") {
p_property.usage = PROPERTY_USAGE_NONE;
}
}
int FlowContainer::get_line_count() const {
return cached_line_count;
}
void FlowContainer::set_alignment(AlignmentMode p_alignment) {
if (alignment == p_alignment) {
return;
}
alignment = p_alignment;
_resort();
}
FlowContainer::AlignmentMode FlowContainer::get_alignment() const {
return alignment;
}
void FlowContainer::set_last_wrap_alignment(LastWrapAlignmentMode p_last_wrap_alignment) {
if (last_wrap_alignment == p_last_wrap_alignment) {
return;
}
last_wrap_alignment = p_last_wrap_alignment;
_resort();
}
FlowContainer::LastWrapAlignmentMode FlowContainer::get_last_wrap_alignment() const {
return last_wrap_alignment;
}
void FlowContainer::set_vertical(bool p_vertical) {
ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + ".");
vertical = p_vertical;
update_minimum_size();
_resort();
}
bool FlowContainer::is_vertical() const {
return vertical;
}
void FlowContainer::set_reverse_fill(bool p_reverse_fill) {
if (reverse_fill == p_reverse_fill) {
return;
}
reverse_fill = p_reverse_fill;
_resort();
}
bool FlowContainer::is_reverse_fill() const {
return reverse_fill;
}
FlowContainer::FlowContainer(bool p_vertical) {
vertical = p_vertical;
}
void FlowContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_line_count"), &FlowContainer::get_line_count);
ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &FlowContainer::set_alignment);
ClassDB::bind_method(D_METHOD("get_alignment"), &FlowContainer::get_alignment);
ClassDB::bind_method(D_METHOD("set_last_wrap_alignment", "last_wrap_alignment"), &FlowContainer::set_last_wrap_alignment);
ClassDB::bind_method(D_METHOD("get_last_wrap_alignment"), &FlowContainer::get_last_wrap_alignment);
ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &FlowContainer::set_vertical);
ClassDB::bind_method(D_METHOD("is_vertical"), &FlowContainer::is_vertical);
ClassDB::bind_method(D_METHOD("set_reverse_fill", "reverse_fill"), &FlowContainer::set_reverse_fill);
ClassDB::bind_method(D_METHOD("is_reverse_fill"), &FlowContainer::is_reverse_fill);
BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN);
BIND_ENUM_CONSTANT(ALIGNMENT_CENTER);
BIND_ENUM_CONSTANT(ALIGNMENT_END);
BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_INHERIT);
BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_BEGIN);
BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_CENTER);
BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_END);
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment", "get_alignment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "last_wrap_alignment", PROPERTY_HINT_ENUM, "Inherit,Begin,Center,End"), "set_last_wrap_alignment", "get_last_wrap_alignment");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reverse_fill"), "set_reverse_fill", "is_reverse_fill");
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, FlowContainer, h_separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, FlowContainer, v_separation);
}

View file

@ -0,0 +1,117 @@
/**************************************************************************/
/* flow_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. */
/**************************************************************************/
#ifndef FLOW_CONTAINER_H
#define FLOW_CONTAINER_H
#include "scene/gui/container.h"
class FlowContainer : public Container {
GDCLASS(FlowContainer, Container);
public:
enum AlignmentMode {
ALIGNMENT_BEGIN,
ALIGNMENT_CENTER,
ALIGNMENT_END
};
enum LastWrapAlignmentMode {
LAST_WRAP_ALIGNMENT_INHERIT,
LAST_WRAP_ALIGNMENT_BEGIN,
LAST_WRAP_ALIGNMENT_CENTER,
LAST_WRAP_ALIGNMENT_END
};
private:
int cached_size = 0;
int cached_line_count = 0;
bool vertical = false;
bool reverse_fill = false;
AlignmentMode alignment = ALIGNMENT_BEGIN;
LastWrapAlignmentMode last_wrap_alignment = LAST_WRAP_ALIGNMENT_INHERIT;
struct ThemeCache {
int h_separation = 0;
int v_separation = 0;
} theme_cache;
void _resort();
protected:
bool is_fixed = false;
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
public:
int get_line_count() const;
void set_alignment(AlignmentMode p_alignment);
AlignmentMode get_alignment() const;
void set_last_wrap_alignment(LastWrapAlignmentMode p_last_wrap_alignment);
LastWrapAlignmentMode get_last_wrap_alignment() const;
void set_vertical(bool p_vertical);
bool is_vertical() const;
void set_reverse_fill(bool p_reverse_fill);
bool is_reverse_fill() const;
virtual Size2 get_minimum_size() const override;
virtual Vector<int> get_allowed_size_flags_horizontal() const override;
virtual Vector<int> get_allowed_size_flags_vertical() const override;
FlowContainer(bool p_vertical = false);
};
class HFlowContainer : public FlowContainer {
GDCLASS(HFlowContainer, FlowContainer);
public:
HFlowContainer() :
FlowContainer(false) { is_fixed = true; }
};
class VFlowContainer : public FlowContainer {
GDCLASS(VFlowContainer, FlowContainer);
public:
VFlowContainer() :
FlowContainer(true) { is_fixed = true; }
};
VARIANT_ENUM_CAST(FlowContainer::AlignmentMode);
VARIANT_ENUM_CAST(FlowContainer::LastWrapAlignmentMode);
#endif // FLOW_CONTAINER_H

View file

@ -0,0 +1,51 @@
/**************************************************************************/
/* graph_edit.compat.inc */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
bool GraphEdit::_is_arrange_nodes_button_hidden_bind_compat_81582() const {
return !is_showing_arrange_button();
}
void GraphEdit::_set_arrange_nodes_button_hidden_bind_compat_81582(bool p_enable) {
set_show_arrange_button(!p_enable);
}
PackedVector2Array GraphEdit::_get_connection_line_bind_compat_86158(const Vector2 &p_from, const Vector2 &p_to) {
return get_connection_line(p_from, p_to);
}
void GraphEdit::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("is_arrange_nodes_button_hidden"), &GraphEdit::_is_arrange_nodes_button_hidden_bind_compat_81582);
ClassDB::bind_compatibility_method(D_METHOD("set_arrange_nodes_button_hidden", "enable"), &GraphEdit::_set_arrange_nodes_button_hidden_bind_compat_81582);
ClassDB::bind_compatibility_method(D_METHOD("get_connection_line", "from_node", "to_node"), &GraphEdit::_get_connection_line_bind_compat_86158);
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,515 @@
/**************************************************************************/
/* graph_edit.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. */
/**************************************************************************/
#ifndef GRAPH_EDIT_H
#define GRAPH_EDIT_H
#include "scene/gui/box_container.h"
#include "scene/gui/graph_frame.h"
#include "scene/gui/graph_node.h"
class Button;
class GraphEdit;
class GraphEditArranger;
class HScrollBar;
class Label;
class Line2D;
class PanelContainer;
class SpinBox;
class ViewPanner;
class VScrollBar;
class GraphEditFilter : public Control {
GDCLASS(GraphEditFilter, Control);
friend class GraphEdit;
friend class GraphEditMinimap;
GraphEdit *ge = nullptr;
virtual bool has_point(const Point2 &p_point) const override;
public:
GraphEditFilter(GraphEdit *p_edit);
};
class GraphEditMinimap : public Control {
GDCLASS(GraphEditMinimap, Control);
friend class GraphEdit;
friend class GraphEditFilter;
GraphEdit *ge = nullptr;
Vector2 minimap_padding;
Vector2 minimap_offset;
Vector2 graph_proportions = Vector2(1, 1);
Vector2 graph_padding = Vector2(0, 0);
Vector2 camera_position = Vector2(100, 50);
Vector2 camera_size = Vector2(200, 200);
bool is_pressing = false;
bool is_resizing = false;
struct ThemeCache {
Ref<StyleBox> panel;
Ref<StyleBox> node_style;
Ref<StyleBox> camera_style;
Ref<Texture2D> resizer;
Color resizer_color;
} theme_cache;
Vector2 _get_render_size();
Vector2 _get_graph_offset();
Vector2 _get_graph_size();
Vector2 _convert_from_graph_position(const Vector2 &p_position);
Vector2 _convert_to_graph_position(const Vector2 &p_position);
virtual void gui_input(const Ref<InputEvent> &p_ev) override;
void _adjust_graph_scroll(const Vector2 &p_offset);
protected:
static void _bind_methods();
public:
virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
void update_minimap();
Rect2 get_camera_rect();
GraphEditMinimap(GraphEdit *p_edit);
};
class GraphEdit : public Control {
GDCLASS(GraphEdit, Control);
public:
struct Connection : RefCounted {
StringName from_node;
StringName to_node;
int from_port = 0;
int to_port = 0;
float activity = 0.0;
private:
struct Cache {
bool dirty = true;
Vector2 from_pos; // In graph space.
Vector2 to_pos; // In graph space.
Color from_color;
Color to_color;
Rect2 aabb; // In local screen space.
Line2D *line = nullptr; // In local screen space.
} _cache;
friend class GraphEdit;
};
// Should be in sync with ControlScheme in ViewPanner.
enum PanningScheme {
SCROLL_ZOOMS,
SCROLL_PANS,
};
enum GridPattern {
GRID_PATTERN_LINES,
GRID_PATTERN_DOTS
};
private:
struct ConnectionType {
union {
struct {
uint32_t type_a;
uint32_t type_b;
};
uint64_t key = 0;
};
static uint32_t hash(const ConnectionType &p_conn) {
return hash_one_uint64(p_conn.key);
}
bool operator==(const ConnectionType &p_type) const {
return key == p_type.key;
}
ConnectionType(uint32_t a = 0, uint32_t b = 0) {
type_a = a;
type_b = b;
}
};
Label *zoom_label = nullptr;
Button *zoom_minus_button = nullptr;
Button *zoom_reset_button = nullptr;
Button *zoom_plus_button = nullptr;
Button *toggle_snapping_button = nullptr;
SpinBox *snapping_distance_spinbox = nullptr;
Button *toggle_grid_button = nullptr;
Button *minimap_button = nullptr;
Button *arrange_button = nullptr;
HScrollBar *h_scrollbar = nullptr;
VScrollBar *v_scrollbar = nullptr;
Ref<ViewPanner> panner;
bool warped_panning = true;
bool show_menu = true;
bool show_zoom_label = false;
bool show_grid_buttons = true;
bool show_zoom_buttons = true;
bool show_minimap_button = true;
bool show_arrange_button = true;
bool snapping_enabled = true;
int snapping_distance = 20;
bool show_grid = true;
GridPattern grid_pattern = GRID_PATTERN_LINES;
bool connecting = false;
StringName connecting_from_node;
bool connecting_from_output = false;
int connecting_type = 0;
Color connecting_color;
Vector2 connecting_to_point; // In local screen space.
bool connecting_target_valid = false;
StringName connecting_target_node;
int connecting_from_port_index = 0;
int connecting_target_port_index = 0;
bool just_disconnected = false;
bool connecting_valid = false;
Vector2 click_pos;
PanningScheme panning_scheme = SCROLL_ZOOMS;
bool dragging = false;
bool just_selected = false;
bool moving_selection = false;
Vector2 drag_accum;
float zoom = 1.0;
float zoom_step = 1.2;
// Proper values set in constructor.
float zoom_min = 0.0;
float zoom_max = 0.0;
bool box_selecting = false;
bool box_selection_mode_additive = false;
Point2 box_selecting_from;
Point2 box_selecting_to;
Rect2 box_selecting_rect;
List<GraphElement *> prev_selected;
bool setting_scroll_offset = false;
bool right_disconnects = false;
bool updating = false;
bool awaiting_scroll_offset_update = false;
List<Ref<Connection>> connections;
HashMap<StringName, List<Ref<Connection>>> connection_map;
Ref<Connection> hovered_connection;
float lines_thickness = 4.0f;
float lines_curvature = 0.5f;
bool lines_antialiased = true;
PanelContainer *menu_panel = nullptr;
HBoxContainer *menu_hbox = nullptr;
Control *connections_layer = nullptr;
GraphEditFilter *top_connection_layer = nullptr; // Draws a dragged connection. Necessary since the connection line shader can't be applied to the whole top layer.
Line2D *dragged_connection_line = nullptr;
Control *top_layer = nullptr; // Used for drawing the box selection rect. Contains the minimap, menu panel and the scrollbars.
GraphEditMinimap *minimap = nullptr;
static Ref<Shader> default_connections_shader;
Ref<Shader> connections_shader;
Ref<GraphEditArranger> arranger;
HashSet<ConnectionType, ConnectionType> valid_connection_types;
HashSet<int> valid_left_disconnect_types;
HashSet<int> valid_right_disconnect_types;
struct ThemeCache {
float base_scale = 1.0;
Ref<StyleBox> panel;
Color grid_major;
Color grid_minor;
Color activity_color;
Color connection_hover_tint_color;
Color connection_valid_target_tint_color;
Color connection_rim_color;
Color selection_fill;
Color selection_stroke;
Ref<StyleBox> menu_panel;
Ref<Texture2D> zoom_in;
Ref<Texture2D> zoom_out;
Ref<Texture2D> zoom_reset;
Ref<Texture2D> snapping_toggle;
Ref<Texture2D> grid_toggle;
Ref<Texture2D> minimap_toggle;
Ref<Texture2D> layout;
float port_hotzone_inner_extent = 0.0;
float port_hotzone_outer_extent = 0.0;
} theme_cache;
// This separates the children in two layers to ensure the order
// of both background nodes (e.g frame nodes) and foreground nodes (connectable nodes).
int background_nodes_separator_idx = 0;
HashMap<StringName, HashSet<StringName>> frame_attached_nodes;
HashMap<StringName, StringName> linked_parent_map;
void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
void _zoom_minus();
void _zoom_reset();
void _zoom_plus();
void _update_zoom_label();
void _graph_element_selected(Node *p_node);
void _graph_element_deselected(Node *p_node);
void _graph_element_resize_request(const Vector2 &p_new_minsize, Node *p_node);
void _graph_frame_autoshrink_changed(const Vector2 &p_new_minsize, GraphFrame *p_frame);
void _graph_element_moved(Node *p_node);
void _graph_node_slot_updated(int p_index, Node *p_node);
void _graph_node_rect_changed(GraphNode *p_node);
void _ensure_node_order_from_root(const StringName &p_node);
void _ensure_node_order_from(Node *p_node);
void _update_scroll();
void _update_scroll_offset();
void _scroll_moved(double);
virtual void gui_input(const Ref<InputEvent> &p_ev) override;
void _top_connection_layer_input(const Ref<InputEvent> &p_ev);
float _get_shader_line_width();
void _draw_minimap_connection_line(const Vector2 &p_from_graph_position, const Vector2 &p_to_graph_position, const Color &p_from_color, const Color &p_to_color);
void _invalidate_connection_line_cache();
void _update_top_connection_layer();
void _update_connections();
void _top_layer_draw();
void _minimap_draw();
void _draw_grid();
bool is_in_port_hotzone(const Vector2 &p_pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left);
TypedArray<Dictionary> _get_connection_list() const;
Dictionary _get_closest_connection_at_point(const Vector2 &p_point, float p_max_distance = 4.0) const;
TypedArray<Dictionary> _get_connections_intersecting_with_rect(const Rect2 &p_rect) const;
Rect2 _compute_shrinked_frame_rect(const GraphFrame *p_frame);
void _set_drag_frame_attached_nodes(GraphFrame *p_frame, bool p_drag);
void _set_position_of_frame_attached_nodes(GraphFrame *p_frame, const Vector2 &p_pos);
friend class GraphEditFilter;
bool _filter_input(const Point2 &p_point);
void _snapping_toggled();
void _snapping_distance_changed(double);
void _show_grid_toggled();
friend class GraphEditMinimap;
void _minimap_toggled();
bool _check_clickable_control(Control *p_control, const Vector2 &r_mouse_pos, const Vector2 &p_offset);
#ifndef DISABLE_DEPRECATED
bool _is_arrange_nodes_button_hidden_bind_compat_81582() const;
void _set_arrange_nodes_button_hidden_bind_compat_81582(bool p_enable);
PackedVector2Array _get_connection_line_bind_compat_86158(const Vector2 &p_from, const Vector2 &p_to);
#endif
protected:
virtual void _update_theme_item_cache() override;
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
void _notification(int p_what);
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
static void _bind_compatibility_methods();
#endif
virtual bool is_in_input_hotzone(GraphNode *p_graph_node, int p_port_idx, const Vector2 &p_mouse_pos, const Vector2i &p_port_size);
virtual bool is_in_output_hotzone(GraphNode *p_graph_node, int p_port_idx, const Vector2 &p_mouse_pos, const Vector2i &p_port_size);
GDVIRTUAL2RC(Vector<Vector2>, _get_connection_line, Vector2, Vector2)
GDVIRTUAL3R(bool, _is_in_input_hotzone, Object *, int, Vector2)
GDVIRTUAL3R(bool, _is_in_output_hotzone, Object *, int, Vector2)
GDVIRTUAL4R(bool, _is_node_hover_valid, StringName, int, StringName, int);
public:
static void init_shaders();
static void finish_shaders();
virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
PackedStringArray get_configuration_warnings() const override;
// This method has to be public (for undo redo).
// TODO: Find a better way to do this.
void _update_graph_frame(GraphFrame *p_frame);
// Connection related methods.
Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
bool is_node_connected(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
void disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
void clear_connections();
void force_connection_drag_end();
const List<Ref<Connection>> &get_connection_list() const;
virtual PackedVector2Array get_connection_line(const Vector2 &p_from, const Vector2 &p_to) const;
Ref<Connection> get_closest_connection_at_point(const Vector2 &p_point, float p_max_distance = 4.0) const;
List<Ref<Connection>> get_connections_intersecting_with_rect(const Rect2 &p_rect) const;
virtual bool is_node_hover_valid(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port);
void set_connection_activity(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port, float p_activity);
void reset_all_connection_activity();
void add_valid_connection_type(int p_type, int p_with_type);
void remove_valid_connection_type(int p_type, int p_with_type);
bool is_valid_connection_type(int p_type, int p_with_type) const;
// GraphFrame related methods.
void attach_graph_element_to_frame(const StringName &p_graph_element, const StringName &p_parent_frame);
void detach_graph_element_from_frame(const StringName &p_graph_element);
GraphFrame *get_element_frame(const StringName &p_attached_graph_element);
TypedArray<StringName> get_attached_nodes_of_frame(const StringName &p_graph_frame);
void set_panning_scheme(PanningScheme p_scheme);
PanningScheme get_panning_scheme() const;
void set_zoom(float p_zoom);
void set_zoom_custom(float p_zoom, const Vector2 &p_center);
float get_zoom() const;
void set_zoom_min(float p_zoom_min);
float get_zoom_min() const;
void set_zoom_max(float p_zoom_max);
float get_zoom_max() const;
void set_zoom_step(float p_zoom_step);
float get_zoom_step() const;
void set_minimap_size(Vector2 p_size);
Vector2 get_minimap_size() const;
void set_minimap_opacity(float p_opacity);
float get_minimap_opacity() const;
void set_minimap_enabled(bool p_enable);
bool is_minimap_enabled() const;
void set_show_menu(bool p_hidden);
bool is_showing_menu() const;
void set_show_zoom_label(bool p_hidden);
bool is_showing_zoom_label() const;
void set_show_grid_buttons(bool p_hidden);
bool is_showing_grid_buttons() const;
void set_show_zoom_buttons(bool p_hidden);
bool is_showing_zoom_buttons() const;
void set_show_minimap_button(bool p_hidden);
bool is_showing_minimap_button() const;
void set_show_arrange_button(bool p_hidden);
bool is_showing_arrange_button() const;
Control *get_top_layer() const { return top_layer; }
GraphEditMinimap *get_minimap() const { return minimap; }
void override_connections_shader(const Ref<Shader> &p_shader);
void set_right_disconnects(bool p_enable);
bool is_right_disconnects_enabled() const;
void add_valid_right_disconnect_type(int p_type);
void remove_valid_right_disconnect_type(int p_type);
void add_valid_left_disconnect_type(int p_type);
void remove_valid_left_disconnect_type(int p_type);
void set_scroll_offset(const Vector2 &p_ofs);
Vector2 get_scroll_offset() const;
void set_selected(Node *p_child);
void set_snapping_enabled(bool p_enable);
bool is_snapping_enabled() const;
void set_snapping_distance(int p_snapping_distance);
int get_snapping_distance() const;
void set_show_grid(bool p_enable);
bool is_showing_grid() const;
void set_grid_pattern(GridPattern p_pattern);
GridPattern get_grid_pattern() const;
void set_connection_lines_curvature(float p_curvature);
float get_connection_lines_curvature() const;
void set_connection_lines_thickness(float p_thickness);
float get_connection_lines_thickness() const;
void set_connection_lines_antialiased(bool p_antialiased);
bool is_connection_lines_antialiased() const;
HBoxContainer *get_menu_hbox();
Ref<ViewPanner> get_panner();
void set_warped_panning(bool p_warped);
void arrange_nodes();
GraphEdit();
};
VARIANT_ENUM_CAST(GraphEdit::PanningScheme);
VARIANT_ENUM_CAST(GraphEdit::GridPattern);
#endif // GRAPH_EDIT_H

View file

@ -0,0 +1,557 @@
/**************************************************************************/
/* graph_edit_arranger.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 "graph_edit_arranger.h"
#include "scene/gui/graph_edit.h"
void GraphEditArranger::arrange_nodes() {
ERR_FAIL_NULL(graph_edit);
if (!arranging_graph) {
arranging_graph = true;
} else {
return;
}
Dictionary node_names;
HashSet<StringName> selected_nodes;
bool arrange_entire_graph = true;
for (int i = graph_edit->get_child_count() - 1; i >= 0; i--) {
GraphNode *graph_element = Object::cast_to<GraphNode>(graph_edit->get_child(i));
if (!graph_element) {
continue;
}
node_names[graph_element->get_name()] = graph_element;
if (graph_element->is_selected()) {
arrange_entire_graph = false;
}
}
HashMap<StringName, HashSet<StringName>> upper_neighbours;
HashMap<StringName, Pair<int, int>> port_info;
Vector2 origin(FLT_MAX, FLT_MAX);
float gap_v = 100.0f;
float gap_h = 100.0f;
List<Ref<GraphEdit::Connection>> connection_list = graph_edit->get_connection_list();
for (int i = graph_edit->get_child_count() - 1; i >= 0; i--) {
GraphNode *graph_element = Object::cast_to<GraphNode>(graph_edit->get_child(i));
if (!graph_element) {
continue;
}
if (graph_element->is_selected() || arrange_entire_graph) {
selected_nodes.insert(graph_element->get_name());
HashSet<StringName> s;
for (const Ref<GraphEdit::Connection> &connection : connection_list) {
GraphNode *p_from = Object::cast_to<GraphNode>(node_names[connection->from_node]);
if (connection->to_node == graph_element->get_name() && (p_from->is_selected() || arrange_entire_graph) && connection->to_node != connection->from_node) {
if (!s.has(p_from->get_name())) {
s.insert(p_from->get_name());
}
String s_connection = String(p_from->get_name()) + " " + String(connection->to_node);
StringName _connection(s_connection);
Pair<int, int> ports(connection->from_port, connection->to_port);
port_info.insert(_connection, ports);
}
}
upper_neighbours.insert(graph_element->get_name(), s);
}
}
if (!selected_nodes.size()) {
arranging_graph = false;
return;
}
HashMap<int, Vector<StringName>> layers = _layering(selected_nodes, upper_neighbours);
_crossing_minimisation(layers, upper_neighbours);
Dictionary root, align, sink, shift;
_horizontal_alignment(root, align, layers, upper_neighbours, selected_nodes);
HashMap<StringName, Vector2> new_positions;
Vector2 default_position(FLT_MAX, FLT_MAX);
Dictionary inner_shift;
HashSet<StringName> block_heads;
for (const StringName &E : selected_nodes) {
inner_shift[E] = 0.0f;
sink[E] = E;
shift[E] = FLT_MAX;
new_positions.insert(E, default_position);
if ((StringName)root[E] == E) {
block_heads.insert(E);
}
}
_calculate_inner_shifts(inner_shift, root, node_names, align, block_heads, port_info);
for (const StringName &E : block_heads) {
_place_block(E, gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions);
}
origin.y = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().y - (new_positions[layers[0][0]].y + (float)inner_shift[layers[0][0]]);
origin.x = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().x;
for (const StringName &E : block_heads) {
StringName u = E;
float start_from = origin.y + new_positions[E].y;
do {
Vector2 cal_pos;
cal_pos.y = start_from + (real_t)inner_shift[u];
new_positions.insert(u, cal_pos);
u = align[u];
} while (u != E);
}
// Compute horizontal coordinates individually for layers to get uniform gap.
float start_from = origin.x;
float largest_node_size = 0.0f;
for (unsigned int i = 0; i < layers.size(); i++) {
Vector<StringName> layer = layers[i];
for (int j = 0; j < layer.size(); j++) {
float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x;
largest_node_size = MAX(largest_node_size, current_node_size);
}
for (int j = 0; j < layer.size(); j++) {
float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x;
Vector2 cal_pos = new_positions[layer[j]];
if (current_node_size == largest_node_size) {
cal_pos.x = start_from;
} else {
float current_node_start_pos = start_from;
if (current_node_size < largest_node_size / 2) {
if (!(i || j)) {
start_from -= (largest_node_size - current_node_size);
}
current_node_start_pos = start_from + largest_node_size - current_node_size;
}
cal_pos.x = current_node_start_pos;
}
new_positions.insert(layer[j], cal_pos);
}
start_from += largest_node_size + gap_h;
largest_node_size = 0.0f;
}
graph_edit->emit_signal(SNAME("begin_node_move"));
for (const StringName &E : selected_nodes) {
GraphNode *graph_node = Object::cast_to<GraphNode>(node_names[E]);
graph_node->set_drag(true);
Vector2 pos = (new_positions[E]);
if (graph_edit->is_snapping_enabled()) {
float snapping_distance = graph_edit->get_snapping_distance();
pos = pos.snappedf(snapping_distance);
}
graph_node->set_position_offset(pos);
graph_node->set_drag(false);
}
graph_edit->emit_signal(SNAME("end_node_move"));
arranging_graph = false;
}
int GraphEditArranger::_set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v) {
switch (p_operation) {
case GraphEditArranger::IS_EQUAL: {
for (const StringName &E : r_u) {
if (!r_v.has(E)) {
return 0;
}
}
return r_u.size() == r_v.size();
} break;
case GraphEditArranger::IS_SUBSET: {
if (r_u.size() == r_v.size() && !r_u.size()) {
return 1;
}
for (const StringName &E : r_u) {
if (!r_v.has(E)) {
return 0;
}
}
return 1;
} break;
case GraphEditArranger::DIFFERENCE: {
Vector<StringName> common;
for (const StringName &E : r_u) {
if (r_v.has(E)) {
common.append(E);
}
}
for (const StringName &E : common) {
r_u.erase(E);
}
return r_u.size();
} break;
case GraphEditArranger::UNION: {
for (const StringName &E : r_v) {
if (!r_u.has(E)) {
r_u.insert(E);
}
}
return r_u.size();
} break;
default:
break;
}
return -1;
}
HashMap<int, Vector<StringName>> GraphEditArranger::_layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) {
HashMap<int, Vector<StringName>> l;
HashSet<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z;
int current_layer = 0;
bool selected = false;
while (!_set_operations(GraphEditArranger::IS_EQUAL, q, u)) {
_set_operations(GraphEditArranger::DIFFERENCE, p, u);
for (const StringName &E : p) {
HashSet<StringName> n = r_upper_neighbours[E];
if (_set_operations(GraphEditArranger::IS_SUBSET, n, z)) {
Vector<StringName> t;
t.push_back(E);
if (!l.has(current_layer)) {
l.insert(current_layer, Vector<StringName>{});
}
selected = true;
t.append_array(l[current_layer]);
l.insert(current_layer, t);
u.insert(E);
}
}
if (!selected) {
current_layer++;
uint32_t previous_size_z = z.size();
_set_operations(GraphEditArranger::UNION, z, u);
if (z.size() == previous_size_z) {
WARN_PRINT("Graph contains cycle(s). The cycle(s) will not be rearranged accurately.");
Vector<StringName> t;
if (l.has(0)) {
t.append_array(l[0]);
}
for (const StringName &E : p) {
t.push_back(E);
}
l.insert(0, t);
break;
}
}
selected = false;
}
return l;
}
Vector<StringName> GraphEditArranger::_split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings) {
if (!r_layer.size()) {
return Vector<StringName>();
}
const StringName &p = r_layer[Math::random(0, r_layer.size() - 1)];
Vector<StringName> left;
Vector<StringName> right;
for (int i = 0; i < r_layer.size(); i++) {
if (p != r_layer[i]) {
const StringName &q = r_layer[i];
int cross_pq = r_crossings[p][q];
int cross_qp = r_crossings[q][p];
if (cross_pq > cross_qp) {
left.push_back(q);
} else {
right.push_back(q);
}
}
}
left.push_back(p);
left.append_array(right);
return left;
}
void GraphEditArranger::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes) {
for (const StringName &E : r_selected_nodes) {
r_root[E] = E;
r_align[E] = E;
}
if (r_layers.size() == 1) {
return;
}
for (unsigned int i = 1; i < r_layers.size(); i++) {
Vector<StringName> lower_layer = r_layers[i];
Vector<StringName> upper_layer = r_layers[i - 1];
int r = -1;
for (int j = 0; j < lower_layer.size(); j++) {
Vector<Pair<int, StringName>> up;
const StringName &current_node = lower_layer[j];
for (int k = 0; k < upper_layer.size(); k++) {
const StringName &adjacent_neighbour = upper_layer[k];
if (r_upper_neighbours[current_node].has(adjacent_neighbour)) {
up.push_back(Pair<int, StringName>(k, adjacent_neighbour));
}
}
int start = (up.size() - 1) / 2;
int end = (up.size() - 1) % 2 ? start + 1 : start;
for (int p = start; p <= end; p++) {
StringName Align = r_align[current_node];
if (Align == current_node && r < up[p].first) {
r_align[up[p].second] = lower_layer[j];
r_root[current_node] = r_root[up[p].second];
r_align[current_node] = r_root[up[p].second];
r = up[p].first;
}
}
}
}
}
void GraphEditArranger::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) {
if (r_layers.size() == 1) {
return;
}
for (unsigned int i = 1; i < r_layers.size(); i++) {
Vector<StringName> upper_layer = r_layers[i - 1];
Vector<StringName> lower_layer = r_layers[i];
HashMap<StringName, Dictionary> c;
for (int j = 0; j < lower_layer.size(); j++) {
const StringName &p = lower_layer[j];
Dictionary d;
for (int k = 0; k < lower_layer.size(); k++) {
unsigned int crossings = 0;
const StringName &q = lower_layer[k];
if (j != k) {
for (int h = 1; h < upper_layer.size(); h++) {
if (r_upper_neighbours[p].has(upper_layer[h])) {
for (int g = 0; g < h; g++) {
if (r_upper_neighbours[q].has(upper_layer[g])) {
crossings++;
}
}
}
}
}
d[q] = crossings;
}
c.insert(p, d);
}
r_layers.insert(i, _split(lower_layer, c));
}
}
void GraphEditArranger::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) {
for (const StringName &E : r_block_heads) {
real_t left = 0;
StringName u = E;
StringName v = r_align[u];
while (u != v && (StringName)r_root[u] != v) {
String _connection = String(u) + " " + String(v);
GraphNode *gnode_from = Object::cast_to<GraphNode>(r_node_names[u]);
GraphNode *gnode_to = Object::cast_to<GraphNode>(r_node_names[v]);
Pair<int, int> ports = r_port_info[_connection];
int port_from = ports.first;
int port_to = ports.second;
Vector2 pos_from = gnode_from->get_output_port_position(port_from) * graph_edit->get_zoom();
Vector2 pos_to = gnode_to->get_input_port_position(port_to) * graph_edit->get_zoom();
real_t s = (real_t)r_inner_shifts[u] + (pos_from.y - pos_to.y) / graph_edit->get_zoom();
r_inner_shifts[v] = s;
left = MIN(left, s);
u = v;
v = (StringName)r_align[v];
}
u = E;
do {
r_inner_shifts[u] = (real_t)r_inner_shifts[u] - left;
u = (StringName)r_align[u];
} while (u != E);
}
}
float GraphEditArranger::_calculate_threshold(const StringName &p_v, const StringName &p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions) {
#define MAX_ORDER 2147483647
#define ORDER(node, layers) \
for (unsigned int i = 0; i < layers.size(); i++) { \
int index = layers[i].find(node); \
if (index > 0) { \
order = index; \
break; \
} \
order = MAX_ORDER; \
}
int order = MAX_ORDER;
float threshold = p_current_threshold;
if (p_v == p_w) {
int min_order = MAX_ORDER;
Ref<GraphEdit::Connection> incoming;
List<Ref<GraphEdit::Connection>> connection_list = graph_edit->get_connection_list();
for (const Ref<GraphEdit::Connection> &connection : connection_list) {
if (connection->to_node == p_w) {
ORDER(connection->from_node, r_layers);
if (min_order > order) {
min_order = order;
incoming = connection;
}
}
}
if (incoming.is_valid()) {
GraphNode *gnode_from = Object::cast_to<GraphNode>(r_node_names[incoming->from_node]);
GraphNode *gnode_to = Object::cast_to<GraphNode>(r_node_names[p_w]);
Vector2 pos_from = gnode_from->get_output_port_position(incoming->from_port) * graph_edit->get_zoom();
Vector2 pos_to = gnode_to->get_input_port_position(incoming->to_port) * graph_edit->get_zoom();
// If connected block node is selected, calculate thershold or add current block to list.
if (gnode_from->is_selected()) {
Vector2 connected_block_pos = r_node_positions[r_root[incoming->from_node]];
if (connected_block_pos.y != FLT_MAX) {
//Connected block is placed, calculate threshold.
threshold = connected_block_pos.y + (real_t)r_inner_shift[incoming->from_node] - (real_t)r_inner_shift[p_w] + pos_from.y - pos_to.y;
}
}
}
}
if (threshold == FLT_MIN && (StringName)r_align[p_w] == p_v) {
// This time, pick an outgoing edge and repeat as above!
int min_order = MAX_ORDER;
Ref<GraphEdit::Connection> outgoing;
List<Ref<GraphEdit::Connection>> connection_list = graph_edit->get_connection_list();
for (const Ref<GraphEdit::Connection> &connection : connection_list) {
if (connection->from_node == p_w) {
ORDER(connection->to_node, r_layers);
if (min_order > order) {
min_order = order;
outgoing = connection;
}
}
}
if (outgoing.is_valid()) {
GraphNode *gnode_from = Object::cast_to<GraphNode>(r_node_names[p_w]);
GraphNode *gnode_to = Object::cast_to<GraphNode>(r_node_names[outgoing->to_node]);
Vector2 pos_from = gnode_from->get_output_port_position(outgoing->from_port) * graph_edit->get_zoom();
Vector2 pos_to = gnode_to->get_input_port_position(outgoing->to_port) * graph_edit->get_zoom();
// If connected block node is selected, calculate thershold or add current block to list.
if (gnode_to->is_selected()) {
Vector2 connected_block_pos = r_node_positions[r_root[outgoing->to_node]];
if (connected_block_pos.y != FLT_MAX) {
//Connected block is placed. Calculate threshold
threshold = connected_block_pos.y + (real_t)r_inner_shift[outgoing->to_node] - (real_t)r_inner_shift[p_w] + pos_from.y - pos_to.y;
}
}
}
}
#undef MAX_ORDER
#undef ORDER
return threshold;
}
void GraphEditArranger::_place_block(const StringName &p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions) {
#define PRED(node, layers) \
for (unsigned int i = 0; i < layers.size(); i++) { \
int index = layers[i].find(node); \
if (index > 0) { \
predecessor = layers[i][index - 1]; \
break; \
} \
predecessor = StringName(); \
}
StringName predecessor;
StringName successor;
Vector2 pos = r_node_positions[p_v];
if (pos.y == FLT_MAX) {
pos.y = 0;
bool initial = false;
StringName w = p_v;
real_t threshold = FLT_MIN;
do {
PRED(w, r_layers);
if (predecessor != StringName()) {
StringName u = r_root[predecessor];
_place_block(u, p_delta, r_layers, r_root, r_align, r_node_name, r_inner_shift, r_sink, r_shift, r_node_positions);
threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions);
if ((StringName)r_sink[p_v] == p_v) {
r_sink[p_v] = r_sink[u];
}
Vector2 predecessor_root_pos = r_node_positions[u];
Vector2 predecessor_node_size = Object::cast_to<GraphNode>(r_node_name[predecessor])->get_size();
if (r_sink[p_v] != r_sink[u]) {
real_t sc = pos.y + (real_t)r_inner_shift[w] - predecessor_root_pos.y - (real_t)r_inner_shift[predecessor] - predecessor_node_size.y - p_delta;
r_shift[r_sink[u]] = MIN(sc, (real_t)r_shift[r_sink[u]]);
} else {
real_t sb = predecessor_root_pos.y + (real_t)r_inner_shift[predecessor] + predecessor_node_size.y - (real_t)r_inner_shift[w] + p_delta;
sb = MAX(sb, threshold);
if (initial) {
pos.y = sb;
} else {
pos.y = MAX(pos.y, sb);
}
initial = false;
}
}
threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions);
w = r_align[w];
} while (w != p_v);
r_node_positions.insert(p_v, pos);
}
#undef PRED
}

View file

@ -0,0 +1,67 @@
/**************************************************************************/
/* graph_edit_arranger.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. */
/**************************************************************************/
#ifndef GRAPH_EDIT_ARRANGER_H
#define GRAPH_EDIT_ARRANGER_H
#include "core/object/ref_counted.h"
#include "core/templates/hash_map.h"
#include "core/templates/hash_set.h"
class GraphEdit;
class GraphEditArranger : public RefCounted {
enum SET_OPERATIONS {
IS_EQUAL,
IS_SUBSET,
DIFFERENCE,
UNION,
};
GraphEdit *graph_edit = nullptr;
bool arranging_graph = false;
int _set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v);
HashMap<int, Vector<StringName>> _layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours);
Vector<StringName> _split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings);
void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes);
void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours);
void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info);
float _calculate_threshold(const StringName &p_v, const StringName &p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions);
void _place_block(const StringName &p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions);
public:
void arrange_nodes();
GraphEditArranger(GraphEdit *p_graph_edit) :
graph_edit(p_graph_edit) {}
};
#endif // GRAPH_EDIT_ARRANGER_H

View file

@ -0,0 +1,244 @@
/**************************************************************************/
/* graph_element.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 "graph_element.h"
#include "core/string/translation.h"
#include "scene/gui/graph_edit.h"
#include "scene/theme/theme_db.h"
#ifdef TOOLS_ENABLED
void GraphElement::_edit_set_position(const Point2 &p_position) {
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
if (graph) {
Point2 offset = (p_position + graph->get_scroll_offset()) * graph->get_zoom();
set_position_offset(offset);
}
set_position(p_position);
}
#endif
void GraphElement::_resort() {
Size2 size = get_size();
for (int i = 0; i < get_child_count(); i++) {
Control *child = as_sortable_control(get_child(i));
if (!child) {
continue;
}
fit_child_in_rect(child, Rect2(Point2(), size));
}
}
Size2 GraphElement::get_minimum_size() const {
Size2 minsize;
for (int i = 0; i < get_child_count(); i++) {
Control *child = as_sortable_control(get_child(i), SortableVisbilityMode::IGNORE);
if (!child) {
continue;
}
Size2i size = child->get_combined_minimum_size();
minsize = minsize.max(size);
}
return minsize;
}
void GraphElement::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_SORT_CHILDREN: {
_resort();
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
update_minimum_size();
queue_redraw();
} break;
}
}
void GraphElement::_validate_property(PropertyInfo &p_property) const {
Control::_validate_property(p_property);
GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent());
if (graph) {
if (p_property.name == "position") {
p_property.usage |= PROPERTY_USAGE_READ_ONLY;
}
}
}
void GraphElement::set_position_offset(const Vector2 &p_offset) {
if (position_offset == p_offset) {
return;
}
position_offset = p_offset;
emit_signal(SNAME("position_offset_changed"));
queue_redraw();
}
Vector2 GraphElement::get_position_offset() const {
return position_offset;
}
void GraphElement::set_selected(bool p_selected) {
if (!is_selectable() || selected == p_selected) {
return;
}
selected = p_selected;
emit_signal(p_selected ? SNAME("node_selected") : SNAME("node_deselected"));
queue_redraw();
}
bool GraphElement::is_selected() {
return selected;
}
void GraphElement::set_drag(bool p_drag) {
if (p_drag) {
drag_from = get_position_offset();
} else {
emit_signal(SNAME("dragged"), drag_from, get_position_offset()); // Required for undo/redo.
}
}
Vector2 GraphElement::get_drag_from() {
return drag_from;
}
void GraphElement::gui_input(const Ref<InputEvent> &p_ev) {
ERR_FAIL_COND(p_ev.is_null());
Ref<InputEventMouseButton> mb = p_ev;
if (mb.is_valid()) {
ERR_FAIL_NULL_MSG(get_parent_control(), "GraphElement must be the child of a GraphEdit node.");
if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
Vector2 mpos = mb->get_position();
if (resizable && mpos.x > get_size().x - theme_cache.resizer->get_width() && mpos.y > get_size().y - theme_cache.resizer->get_height()) {
resizing = true;
resizing_from = mpos;
resizing_from_size = get_size();
accept_event();
return;
}
emit_signal(SNAME("raise_request"));
}
if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
if (resizing) {
resizing = false;
emit_signal(SNAME("resize_end"), get_size());
return;
}
}
}
Ref<InputEventMouseMotion> mm = p_ev;
if (resizing && mm.is_valid()) {
Vector2 mpos = mm->get_position();
Vector2 diff = mpos - resizing_from;
emit_signal(SNAME("resize_request"), resizing_from_size + diff);
}
}
void GraphElement::set_resizable(bool p_enable) {
if (resizable == p_enable) {
return;
}
resizable = p_enable;
queue_redraw();
}
bool GraphElement::is_resizable() const {
return resizable;
}
void GraphElement::set_draggable(bool p_draggable) {
draggable = p_draggable;
}
bool GraphElement::is_draggable() {
return draggable;
}
void GraphElement::set_selectable(bool p_selectable) {
if (!p_selectable) {
set_selected(false);
}
selectable = p_selectable;
}
bool GraphElement::is_selectable() {
return selectable;
}
void GraphElement::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_resizable", "resizable"), &GraphElement::set_resizable);
ClassDB::bind_method(D_METHOD("is_resizable"), &GraphElement::is_resizable);
ClassDB::bind_method(D_METHOD("set_draggable", "draggable"), &GraphElement::set_draggable);
ClassDB::bind_method(D_METHOD("is_draggable"), &GraphElement::is_draggable);
ClassDB::bind_method(D_METHOD("set_selectable", "selectable"), &GraphElement::set_selectable);
ClassDB::bind_method(D_METHOD("is_selectable"), &GraphElement::is_selectable);
ClassDB::bind_method(D_METHOD("set_selected", "selected"), &GraphElement::set_selected);
ClassDB::bind_method(D_METHOD("is_selected"), &GraphElement::is_selected);
ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &GraphElement::set_position_offset);
ClassDB::bind_method(D_METHOD("get_position_offset"), &GraphElement::get_position_offset);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position_offset"), "set_position_offset", "get_position_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resizable"), "set_resizable", "is_resizable");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draggable"), "set_draggable", "is_draggable");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selectable"), "set_selectable", "is_selectable");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selected"), "set_selected", "is_selected");
ADD_SIGNAL(MethodInfo("node_selected"));
ADD_SIGNAL(MethodInfo("node_deselected"));
ADD_SIGNAL(MethodInfo("raise_request"));
ADD_SIGNAL(MethodInfo("delete_request"));
ADD_SIGNAL(MethodInfo("resize_request", PropertyInfo(Variant::VECTOR2, "new_size")));
ADD_SIGNAL(MethodInfo("resize_end", PropertyInfo(Variant::VECTOR2, "new_size")));
ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::VECTOR2, "from"), PropertyInfo(Variant::VECTOR2, "to")));
ADD_SIGNAL(MethodInfo("position_offset_changed"));
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, GraphElement, resizer);
}

View file

@ -0,0 +1,97 @@
/**************************************************************************/
/* graph_element.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. */
/**************************************************************************/
#ifndef GRAPH_ELEMENT_H
#define GRAPH_ELEMENT_H
#include "scene/gui/container.h"
class GraphElement : public Container {
GDCLASS(GraphElement, Container);
protected:
bool selected = false;
bool resizable = false;
bool resizing = false;
bool draggable = true;
bool selectable = true;
Vector2 drag_from;
Vector2 resizing_from;
Vector2 resizing_from_size;
Vector2 position_offset;
struct ThemeCache {
Ref<Texture2D> resizer;
} theme_cache;
#ifdef TOOLS_ENABLED
void _edit_set_position(const Point2 &p_position) override;
#endif
protected:
virtual void gui_input(const Ref<InputEvent> &p_ev) override;
void _notification(int p_what);
static void _bind_methods();
virtual void _resort();
void _validate_property(PropertyInfo &p_property) const;
public:
void set_position_offset(const Vector2 &p_offset);
Vector2 get_position_offset() const;
void set_selected(bool p_selected);
bool is_selected();
void set_drag(bool p_drag);
Vector2 get_drag_from();
void set_resizable(bool p_enable);
bool is_resizable() const;
void set_draggable(bool p_draggable);
bool is_draggable();
void set_selectable(bool p_selectable);
bool is_selectable();
virtual Size2 get_minimum_size() const override;
bool is_resizing() const {
return resizing;
}
GraphElement() {}
};
#endif // GRAPH_ELEMENT_H

View file

@ -0,0 +1,357 @@
/**************************************************************************/
/* graph_frame.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 "graph_frame.h"
#include "core/string/translation.h"
#include "scene/gui/box_container.h"
#include "scene/gui/label.h"
#include "scene/resources/style_box_flat.h"
#include "scene/resources/style_box_texture.h"
#include "scene/theme/theme_db.h"
void GraphFrame::gui_input(const Ref<InputEvent> &p_ev) {
ERR_FAIL_COND(p_ev.is_null());
Ref<InputEventMouseButton> mb = p_ev;
if (mb.is_valid()) {
ERR_FAIL_NULL_MSG(get_parent_control(), "GraphFrame must be the child of a GraphEdit node.");
if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
Vector2 mpos = mb->get_position();
Ref<Texture2D> resizer = theme_cache.resizer;
if (resizable && mpos.x > get_size().x - resizer->get_width() && mpos.y > get_size().y - resizer->get_height()) {
resizing = true;
resizing_from = mpos;
resizing_from_size = get_size();
accept_event();
return;
}
emit_signal(SNAME("raise_request"));
}
if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
if (resizing) {
resizing = false;
emit_signal(SNAME("resize_end"), get_size());
return;
}
}
}
Ref<InputEventMouseMotion> mm = p_ev;
// Only resize if the frame is not auto-resizing based on linked nodes.
if (resizing && !autoshrink_enabled && mm.is_valid()) {
Vector2 mpos = mm->get_position();
Vector2 diff = mpos - resizing_from;
emit_signal(SNAME("resize_request"), resizing_from_size + diff);
}
}
Control::CursorShape GraphFrame::get_cursor_shape(const Point2 &p_pos) const {
if (resizable && !autoshrink_enabled) {
if (resizing || (p_pos.x > get_size().x - theme_cache.resizer->get_width() && p_pos.y > get_size().y - theme_cache.resizer->get_height())) {
return CURSOR_FDIAGSIZE;
}
}
return Control::get_cursor_shape(p_pos);
}
void GraphFrame::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
// Used for layout calculations.
Ref<StyleBox> sb_panel = theme_cache.panel;
Ref<StyleBox> sb_titlebar = theme_cache.titlebar;
// Used for drawing.
Ref<StyleBox> sb_to_draw_panel = selected ? theme_cache.panel_selected : sb_panel;
Ref<StyleBox> sb_to_draw_titlebar = selected ? theme_cache.titlebar_selected : sb_titlebar;
Ref<StyleBoxFlat> sb_panel_flat = sb_to_draw_panel;
Ref<StyleBoxTexture> sb_panel_texture = sb_to_draw_panel;
Rect2 titlebar_rect(Point2(), titlebar_hbox->get_size() + sb_titlebar->get_minimum_size());
Size2 body_size = get_size();
body_size.y -= titlebar_rect.size.height;
Rect2 body_rect(Point2(0, titlebar_rect.size.height), body_size);
// Draw body stylebox.
if (tint_color_enabled) {
if (sb_panel_flat.is_valid()) {
Color original_border_color = sb_panel_flat->get_border_color();
sb_panel_flat = sb_panel_flat->duplicate();
sb_panel_flat->set_bg_color(tint_color);
sb_panel_flat->set_border_color(selected ? original_border_color : tint_color.lightened(0.3));
draw_style_box(sb_panel_flat, body_rect);
} else if (sb_panel_texture.is_valid()) {
sb_panel_texture = sb_panel_texture->duplicate();
sb_panel_texture->set_modulate(tint_color);
draw_style_box(sb_panel_texture, body_rect);
}
} else {
draw_style_box(sb_panel_flat, body_rect);
}
// Draw title bar stylebox above.
draw_style_box(sb_to_draw_titlebar, titlebar_rect);
// Only draw the resize handle if the frame is not auto-resizing.
if (resizable && !autoshrink_enabled) {
Ref<Texture2D> resizer = theme_cache.resizer;
Color resizer_color = theme_cache.resizer_color;
if (resizable) {
draw_texture(resizer, get_size() - resizer->get_size(), resizer_color);
}
}
} break;
}
}
void GraphFrame::_resort() {
Ref<StyleBox> sb_panel = theme_cache.panel;
Ref<StyleBox> sb_titlebar = theme_cache.titlebar;
// Resort titlebar first.
Size2 titlebar_size = Size2(get_size().width, titlebar_hbox->get_size().height);
titlebar_size -= sb_titlebar->get_minimum_size();
Rect2 titlebar_rect = Rect2(sb_titlebar->get_offset(), titlebar_size);
fit_child_in_rect(titlebar_hbox, titlebar_rect);
// After resort the children of the titlebar container may have changed their height (e.g. Label autowrap).
Size2i titlebar_min_size = titlebar_hbox->get_combined_minimum_size();
Size2 size = get_size() - sb_panel->get_minimum_size() - Size2(0, titlebar_min_size.height + sb_titlebar->get_minimum_size().height);
Point2 offset = Point2(sb_panel->get_margin(SIDE_LEFT), sb_panel->get_margin(SIDE_TOP) + titlebar_min_size.height + sb_titlebar->get_minimum_size().height);
for (int i = 0; i < get_child_count(false); i++) {
Control *child = as_sortable_control(get_child(i, false));
if (!child) {
continue;
}
fit_child_in_rect(child, Rect2(offset, size));
}
}
void GraphFrame::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_title", "title"), &GraphFrame::set_title);
ClassDB::bind_method(D_METHOD("get_title"), &GraphFrame::get_title);
ClassDB::bind_method(D_METHOD("get_titlebar_hbox"), &GraphFrame::get_titlebar_hbox);
ClassDB::bind_method(D_METHOD("set_autoshrink_enabled", "shrink"), &GraphFrame::set_autoshrink_enabled);
ClassDB::bind_method(D_METHOD("is_autoshrink_enabled"), &GraphFrame::is_autoshrink_enabled);
ClassDB::bind_method(D_METHOD("set_autoshrink_margin", "autoshrink_margin"), &GraphFrame::set_autoshrink_margin);
ClassDB::bind_method(D_METHOD("get_autoshrink_margin"), &GraphFrame::get_autoshrink_margin);
ClassDB::bind_method(D_METHOD("set_drag_margin", "drag_margin"), &GraphFrame::set_drag_margin);
ClassDB::bind_method(D_METHOD("get_drag_margin"), &GraphFrame::get_drag_margin);
ClassDB::bind_method(D_METHOD("set_tint_color_enabled", "enable"), &GraphFrame::set_tint_color_enabled);
ClassDB::bind_method(D_METHOD("is_tint_color_enabled"), &GraphFrame::is_tint_color_enabled);
ClassDB::bind_method(D_METHOD("set_tint_color", "color"), &GraphFrame::set_tint_color);
ClassDB::bind_method(D_METHOD("get_tint_color"), &GraphFrame::get_tint_color);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoshrink_enabled"), "set_autoshrink_enabled", "is_autoshrink_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "autoshrink_margin", PROPERTY_HINT_RANGE, "0,128,1"), "set_autoshrink_margin", "get_autoshrink_margin");
ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_margin", PROPERTY_HINT_RANGE, "0,128,1"), "set_drag_margin", "get_drag_margin");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tint_color_enabled"), "set_tint_color_enabled", "is_tint_color_enabled");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_color"), "set_tint_color", "get_tint_color");
ADD_SIGNAL(MethodInfo(SNAME("autoshrink_changed")));
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphFrame, panel);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphFrame, panel_selected);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphFrame, titlebar);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphFrame, titlebar_selected);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, GraphFrame, resizer);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, GraphFrame, resizer_color);
}
void GraphFrame::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "resizable") {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
void GraphFrame::set_title(const String &p_title) {
if (title == p_title) {
return;
}
title = p_title;
if (title_label) {
title_label->set_text(title);
}
update_minimum_size();
}
String GraphFrame::get_title() const {
return title;
}
void GraphFrame::set_autoshrink_enabled(bool p_shrink) {
if (autoshrink_enabled == p_shrink) {
return;
}
autoshrink_enabled = p_shrink;
emit_signal("autoshrink_changed", get_size());
queue_redraw();
}
bool GraphFrame::is_autoshrink_enabled() const {
return autoshrink_enabled;
}
void GraphFrame::set_autoshrink_margin(const int &p_margin) {
if (autoshrink_margin == p_margin) {
return;
}
autoshrink_margin = p_margin;
emit_signal("autoshrink_changed", get_size());
}
int GraphFrame::get_autoshrink_margin() const {
return autoshrink_margin;
}
HBoxContainer *GraphFrame::get_titlebar_hbox() {
return titlebar_hbox;
}
Size2 GraphFrame::get_titlebar_size() const {
return titlebar_hbox->get_size() + theme_cache.titlebar->get_minimum_size();
}
void GraphFrame::set_drag_margin(int p_margin) {
drag_margin = p_margin;
}
int GraphFrame::get_drag_margin() const {
return drag_margin;
}
void GraphFrame::set_tint_color_enabled(bool p_enable) {
tint_color_enabled = p_enable;
queue_redraw();
}
bool GraphFrame::is_tint_color_enabled() const {
return tint_color_enabled;
}
void GraphFrame::set_tint_color(const Color &p_color) {
tint_color = p_color;
queue_redraw();
}
Color GraphFrame::get_tint_color() const {
return tint_color;
}
bool GraphFrame::has_point(const Point2 &p_point) const {
Ref<StyleBox> sb_panel = theme_cache.panel;
Ref<StyleBox> sb_titlebar = theme_cache.titlebar;
Ref<Texture2D> resizer = theme_cache.resizer;
if (Rect2(get_size() - resizer->get_size(), resizer->get_size()).has_point(p_point)) {
return true;
}
// For grabbing on the titlebar.
int titlebar_height = titlebar_hbox->get_size().height + sb_titlebar->get_minimum_size().height;
if (Rect2(0, 0, get_size().width, titlebar_height).has_point(p_point)) {
return true;
}
// Allow grabbing on all sides of the frame.
Rect2 frame_rect = Rect2(0, 0, get_size().width, get_size().height);
Rect2 no_drag_rect = frame_rect.grow(-drag_margin);
if (frame_rect.has_point(p_point) && !no_drag_rect.has_point(p_point)) {
return true;
}
return false;
}
Size2 GraphFrame::get_minimum_size() const {
Ref<StyleBox> sb_panel = theme_cache.panel;
Ref<StyleBox> sb_titlebar = theme_cache.titlebar;
Size2 minsize = titlebar_hbox->get_minimum_size() + sb_titlebar->get_minimum_size();
for (int i = 0; i < get_child_count(false); i++) {
Control *child = as_sortable_control(get_child(i, false));
if (!child) {
continue;
}
Size2i size = child->get_combined_minimum_size();
size.width += sb_panel->get_minimum_size().width;
minsize.x = MAX(minsize.x, size.x);
minsize.y += MAX(minsize.y, size.y);
}
minsize.height += sb_panel->get_minimum_size().height;
return minsize;
}
GraphFrame::GraphFrame() {
titlebar_hbox = memnew(HBoxContainer);
titlebar_hbox->set_h_size_flags(SIZE_EXPAND_FILL);
add_child(titlebar_hbox, false, INTERNAL_MODE_FRONT);
title_label = memnew(Label);
title_label->set_theme_type_variation("GraphFrameTitleLabel");
title_label->set_h_size_flags(SIZE_EXPAND_FILL);
title_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
titlebar_hbox->add_child(title_label);
set_mouse_filter(MOUSE_FILTER_STOP);
}

View file

@ -0,0 +1,109 @@
/**************************************************************************/
/* graph_frame.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. */
/**************************************************************************/
#ifndef GRAPH_FRAME_H
#define GRAPH_FRAME_H
#include "scene/gui/graph_element.h"
class HBoxContainer;
class GraphFrame : public GraphElement {
GDCLASS(GraphFrame, GraphElement);
struct _MinSizeCache {
int min_size = 0;
bool will_stretch = false;
int final_size = 0;
};
struct ThemeCache {
Ref<StyleBox> panel;
Ref<StyleBox> panel_selected;
Ref<StyleBox> titlebar;
Ref<StyleBox> titlebar_selected;
Ref<Texture2D> resizer;
Color resizer_color;
} theme_cache;
private:
String title;
HBoxContainer *titlebar_hbox = nullptr;
Label *title_label = nullptr;
bool autoshrink_enabled = true;
int autoshrink_margin = 40;
int drag_margin = 16;
bool tint_color_enabled = false;
Color tint_color = Color(0.3, 0.3, 0.3, 0.75);
protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
void _notification(int p_what);
static void _bind_methods();
void _validate_property(PropertyInfo &p_property) const;
virtual void _resort() override;
public:
void set_title(const String &p_title);
String get_title() const;
void set_autoshrink_enabled(bool p_enable);
bool is_autoshrink_enabled() const;
void set_autoshrink_margin(const int &p_margin);
int get_autoshrink_margin() const;
HBoxContainer *get_titlebar_hbox();
Size2 get_titlebar_size() const;
void set_drag_margin(int p_margin);
int get_drag_margin() const;
void set_tint_color_enabled(bool p_enable);
bool is_tint_color_enabled() const;
void set_tint_color(const Color &p_tint_color);
Color get_tint_color() const;
virtual bool has_point(const Point2 &p_point) const override;
virtual Size2 get_minimum_size() const override;
GraphFrame();
};
#endif // GRAPH_FRAME_H

View file

@ -0,0 +1,917 @@
/**************************************************************************/
/* graph_node.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 "graph_node.h"
#include "core/string/translation.h"
#include "scene/gui/box_container.h"
#include "scene/gui/label.h"
#include "scene/theme/theme_db.h"
bool GraphNode::_set(const StringName &p_name, const Variant &p_value) {
String str = p_name;
if (!str.begins_with("slot/")) {
return false;
}
int idx = str.get_slice("/", 1).to_int();
String slot_property_name = str.get_slice("/", 2);
Slot slot;
if (slot_table.has(idx)) {
slot = slot_table[idx];
}
if (slot_property_name == "left_enabled") {
slot.enable_left = p_value;
} else if (slot_property_name == "left_type") {
slot.type_left = p_value;
} else if (slot_property_name == "left_icon") {
slot.custom_port_icon_left = p_value;
} else if (slot_property_name == "left_color") {
slot.color_left = p_value;
} else if (slot_property_name == "right_enabled") {
slot.enable_right = p_value;
} else if (slot_property_name == "right_type") {
slot.type_right = p_value;
} else if (slot_property_name == "right_color") {
slot.color_right = p_value;
} else if (slot_property_name == "right_icon") {
slot.custom_port_icon_right = p_value;
} else if (slot_property_name == "draw_stylebox") {
slot.draw_stylebox = p_value;
} else {
return false;
}
set_slot(idx,
slot.enable_left,
slot.type_left,
slot.color_left,
slot.enable_right,
slot.type_right,
slot.color_right,
slot.custom_port_icon_left,
slot.custom_port_icon_right,
slot.draw_stylebox);
queue_redraw();
return true;
}
bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const {
String str = p_name;
if (!str.begins_with("slot/")) {
return false;
}
int idx = str.get_slice("/", 1).to_int();
StringName slot_property_name = str.get_slice("/", 2);
Slot slot;
if (slot_table.has(idx)) {
slot = slot_table[idx];
}
if (slot_property_name == "left_enabled") {
r_ret = slot.enable_left;
} else if (slot_property_name == "left_type") {
r_ret = slot.type_left;
} else if (slot_property_name == "left_color") {
r_ret = slot.color_left;
} else if (slot_property_name == "left_icon") {
r_ret = slot.custom_port_icon_left;
} else if (slot_property_name == "right_enabled") {
r_ret = slot.enable_right;
} else if (slot_property_name == "right_type") {
r_ret = slot.type_right;
} else if (slot_property_name == "right_color") {
r_ret = slot.color_right;
} else if (slot_property_name == "right_icon") {
r_ret = slot.custom_port_icon_right;
} else if (slot_property_name == "draw_stylebox") {
r_ret = slot.draw_stylebox;
} else {
return false;
}
return true;
}
void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const {
int idx = 0;
for (int i = 0; i < get_child_count(false); i++) {
Control *child = as_sortable_control(get_child(i, false), SortableVisbilityMode::IGNORE);
if (!child) {
continue;
}
String base = "slot/" + itos(idx) + "/";
p_list->push_back(PropertyInfo(Variant::BOOL, base + "left_enabled"));
p_list->push_back(PropertyInfo(Variant::INT, base + "left_type"));
p_list->push_back(PropertyInfo(Variant::COLOR, base + "left_color"));
p_list->push_back(PropertyInfo(Variant::OBJECT, base + "left_icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL));
p_list->push_back(PropertyInfo(Variant::BOOL, base + "right_enabled"));
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++;
}
}
void GraphNode::_resort() {
Size2 new_size = get_size();
Ref<StyleBox> sb_panel = theme_cache.panel;
Ref<StyleBox> sb_titlebar = theme_cache.titlebar;
// Resort titlebar first.
Size2 titlebar_size = Size2(new_size.width, titlebar_hbox->get_size().height);
titlebar_size -= sb_titlebar->get_minimum_size();
Rect2 titlebar_rect = Rect2(sb_titlebar->get_offset(), titlebar_size);
fit_child_in_rect(titlebar_hbox, titlebar_rect);
// After resort, the children of the titlebar container may have changed their height (e.g. Label autowrap).
Size2i titlebar_min_size = titlebar_hbox->get_combined_minimum_size();
// First pass, determine minimum size AND amount of stretchable elements.
Ref<StyleBox> sb_slot = theme_cache.slot;
int separation = theme_cache.separation;
int children_count = 0;
int stretch_min = 0;
int available_stretch_space = 0;
float stretch_ratio_total = 0;
HashMap<Control *, _MinSizeCache> min_size_cache;
for (int i = 0; i < get_child_count(false); i++) {
Control *child = as_sortable_control(get_child(i, false));
if (!child) {
continue;
}
Size2i size = child->get_combined_minimum_size() + (slot_table[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2());
stretch_min += size.height;
_MinSizeCache msc;
msc.min_size = size.height;
msc.will_stretch = child->get_v_size_flags().has_flag(SIZE_EXPAND);
msc.final_size = msc.min_size;
min_size_cache[child] = msc;
if (msc.will_stretch) {
available_stretch_space += msc.min_size;
stretch_ratio_total += child->get_stretch_ratio();
}
children_count++;
}
if (children_count == 0) {
return;
}
int stretch_max = new_size.height - (children_count - 1) * separation;
int stretch_diff = stretch_max - stretch_min;
// Avoid negative stretch space.
stretch_diff = MAX(stretch_diff, 0);
available_stretch_space += stretch_diff - sb_panel->get_margin(SIDE_BOTTOM) - sb_panel->get_margin(SIDE_TOP);
// Second pass, discard elements that can't be stretched, this will run while stretchable elements exist.
while (stretch_ratio_total > 0) {
// First of all, don't even be here if no stretchable objects exist.
bool refit_successful = true;
for (int i = 0; i < get_child_count(false); i++) {
Control *child = as_sortable_control(get_child(i, false));
if (!child) {
continue;
}
ERR_FAIL_COND(!min_size_cache.has(child));
_MinSizeCache &msc = min_size_cache[child];
if (msc.will_stretch) {
int final_pixel_size = available_stretch_space * child->get_stretch_ratio() / stretch_ratio_total;
if (final_pixel_size < msc.min_size) {
// If the available stretching area is too small for a Control,
// then remove it from stretching area.
msc.will_stretch = false;
stretch_ratio_total -= child->get_stretch_ratio();
refit_successful = false;
available_stretch_space -= msc.min_size;
msc.final_size = msc.min_size;
break;
} else {
msc.final_size = final_pixel_size;
}
}
}
if (refit_successful) {
break;
}
}
// Final pass, draw and stretch elements.
int ofs_y = sb_panel->get_margin(SIDE_TOP) + titlebar_min_size.height + sb_titlebar->get_minimum_size().height;
slot_y_cache.clear();
int width = new_size.width - sb_panel->get_minimum_size().width;
int valid_children_idx = 0;
for (int i = 0; i < get_child_count(false); i++) {
Control *child = as_sortable_control(get_child(i, false));
if (!child) {
continue;
}
_MinSizeCache &msc = min_size_cache[child];
if (valid_children_idx > 0) {
ofs_y += separation;
}
int from_y_pos = ofs_y;
int to_y_pos = ofs_y + msc.final_size;
// Adjust so the last valid child always fits perfect, compensating for numerical imprecision.
if (msc.will_stretch && valid_children_idx == children_count - 1) {
to_y_pos = new_size.height - sb_panel->get_margin(SIDE_BOTTOM);
}
int height = to_y_pos - from_y_pos;
float margin = sb_panel->get_margin(SIDE_LEFT) + (slot_table[i].draw_stylebox ? sb_slot->get_margin(SIDE_LEFT) : 0);
float final_width = width - (slot_table[i].draw_stylebox ? sb_slot->get_minimum_size().x : 0);
Rect2 rect(margin, from_y_pos, final_width, height);
fit_child_in_rect(child, rect);
slot_y_cache.push_back(from_y_pos - sb_panel->get_margin(SIDE_TOP) + height * 0.5);
ofs_y = to_y_pos;
valid_children_idx++;
}
queue_redraw();
port_pos_dirty = true;
}
void GraphNode::draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color) {
if (GDVIRTUAL_CALL(_draw_port, p_slot_index, p_pos, p_left, p_color)) {
return;
}
Slot slot = slot_table[p_slot_index];
Ref<Texture2D> port_icon = p_left ? slot.custom_port_icon_left : slot.custom_port_icon_right;
Point2 icon_offset;
if (!port_icon.is_valid()) {
port_icon = theme_cache.port;
}
icon_offset = -port_icon->get_size() * 0.5;
port_icon->draw(get_canvas_item(), p_pos + icon_offset, p_color);
}
void GraphNode::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
// Used for layout calculations.
Ref<StyleBox> sb_panel = theme_cache.panel;
Ref<StyleBox> sb_titlebar = theme_cache.titlebar;
// Used for drawing.
Ref<StyleBox> sb_to_draw_panel = selected ? theme_cache.panel_selected : theme_cache.panel;
Ref<StyleBox> sb_to_draw_titlebar = selected ? theme_cache.titlebar_selected : theme_cache.titlebar;
Ref<StyleBox> sb_slot = theme_cache.slot;
int port_h_offset = theme_cache.port_h_offset;
Rect2 titlebar_rect(Point2(), titlebar_hbox->get_size() + sb_titlebar->get_minimum_size());
Size2 body_size = get_size();
titlebar_rect.size.width = body_size.width;
body_size.height -= titlebar_rect.size.height;
Rect2 body_rect(0, titlebar_rect.size.height, body_size.width, body_size.height);
// Draw body (slots area) stylebox.
draw_style_box(sb_to_draw_panel, body_rect);
// Draw title bar stylebox above.
draw_style_box(sb_to_draw_titlebar, titlebar_rect);
int width = get_size().width - sb_panel->get_minimum_size().x;
// Take the HboxContainer child into account.
if (get_child_count(false) > 0) {
int slot_index = 0;
for (const KeyValue<int, Slot> &E : slot_table) {
if (E.key < 0 || E.key >= slot_y_cache.size()) {
continue;
}
if (!slot_table.has(E.key)) {
continue;
}
const Slot &slot = slot_table[E.key];
// Left port.
if (slot.enable_left) {
draw_port(slot_index, Point2i(port_h_offset, slot_y_cache[E.key] + sb_panel->get_margin(SIDE_TOP)), true, slot.color_left);
}
// Right port.
if (slot.enable_right) {
draw_port(slot_index, Point2i(get_size().x - port_h_offset, slot_y_cache[E.key] + sb_panel->get_margin(SIDE_TOP)), false, slot.color_right);
}
// Draw slot stylebox.
if (slot.draw_stylebox) {
Control *child = Object::cast_to<Control>(get_child(E.key, false));
if (!child || !child->is_visible_in_tree()) {
continue;
}
Rect2 child_rect = child->get_rect();
child_rect.position.x = sb_panel->get_margin(SIDE_LEFT);
child_rect.size.width = width;
draw_style_box(sb_slot, child_rect);
}
slot_index++;
}
}
if (resizable) {
draw_texture(theme_cache.resizer, get_size() - theme_cache.resizer->get_size(), theme_cache.resizer_color);
}
} break;
}
}
void GraphNode::set_slot(int p_slot_index, 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_slot_index < 0, vformat("Cannot set slot with index (%d) lesser than zero.", p_slot_index));
if (!p_enable_left && p_type_left == 0 && p_color_left == Color(1, 1, 1, 1) &&
!p_enable_right && p_type_right == 0 && p_color_right == Color(1, 1, 1, 1) &&
!p_custom_left.is_valid() && !p_custom_right.is_valid()) {
slot_table.erase(p_slot_index);
return;
}
Slot slot;
slot.enable_left = p_enable_left;
slot.type_left = p_type_left;
slot.color_left = p_color_left;
slot.enable_right = p_enable_right;
slot.type_right = p_type_right;
slot.color_right = p_color_right;
slot.custom_port_icon_left = p_custom_left;
slot.custom_port_icon_right = p_custom_right;
slot.draw_stylebox = p_draw_stylebox;
slot_table[p_slot_index] = slot;
queue_redraw();
port_pos_dirty = true;
emit_signal(SNAME("slot_updated"), p_slot_index);
}
void GraphNode::clear_slot(int p_slot_index) {
slot_table.erase(p_slot_index);
queue_redraw();
port_pos_dirty = true;
}
void GraphNode::clear_all_slots() {
slot_table.clear();
queue_redraw();
port_pos_dirty = true;
}
bool GraphNode::is_slot_enabled_left(int p_slot_index) const {
if (!slot_table.has(p_slot_index)) {
return false;
}
return slot_table[p_slot_index].enable_left;
}
void GraphNode::set_slot_enabled_left(int p_slot_index, bool p_enable) {
ERR_FAIL_COND_MSG(p_slot_index < 0, vformat("Cannot set enable_left for the slot with index (%d) lesser than zero.", p_slot_index));
if (slot_table[p_slot_index].enable_left == p_enable) {
return;
}
slot_table[p_slot_index].enable_left = p_enable;
queue_redraw();
port_pos_dirty = true;
emit_signal(SNAME("slot_updated"), p_slot_index);
}
void GraphNode::set_slot_type_left(int p_slot_index, int p_type) {
ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set type_left for the slot with index '%d' because it hasn't been enabled.", p_slot_index));
if (slot_table[p_slot_index].type_left == p_type) {
return;
}
slot_table[p_slot_index].type_left = p_type;
queue_redraw();
port_pos_dirty = true;
emit_signal(SNAME("slot_updated"), p_slot_index);
}
int GraphNode::get_slot_type_left(int p_slot_index) const {
if (!slot_table.has(p_slot_index)) {
return 0;
}
return slot_table[p_slot_index].type_left;
}
void GraphNode::set_slot_color_left(int p_slot_index, const Color &p_color) {
ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set color_left for the slot with index '%d' because it hasn't been enabled.", p_slot_index));
if (slot_table[p_slot_index].color_left == p_color) {
return;
}
slot_table[p_slot_index].color_left = p_color;
queue_redraw();
port_pos_dirty = true;
emit_signal(SNAME("slot_updated"), p_slot_index);
}
Color GraphNode::get_slot_color_left(int p_slot_index) const {
if (!slot_table.has(p_slot_index)) {
return Color(1, 1, 1, 1);
}
return slot_table[p_slot_index].color_left;
}
void GraphNode::set_slot_custom_icon_left(int p_slot_index, const Ref<Texture2D> &p_custom_icon) {
ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set custom_port_icon_left for the slot with index '%d' because it hasn't been enabled.", p_slot_index));
if (slot_table[p_slot_index].custom_port_icon_left == p_custom_icon) {
return;
}
slot_table[p_slot_index].custom_port_icon_left = p_custom_icon;
queue_redraw();
port_pos_dirty = true;
emit_signal(SNAME("slot_updated"), p_slot_index);
}
Ref<Texture2D> GraphNode::get_slot_custom_icon_left(int p_slot_index) const {
if (!slot_table.has(p_slot_index)) {
return Ref<Texture2D>();
}
return slot_table[p_slot_index].custom_port_icon_left;
}
bool GraphNode::is_slot_enabled_right(int p_slot_index) const {
if (!slot_table.has(p_slot_index)) {
return false;
}
return slot_table[p_slot_index].enable_right;
}
void GraphNode::set_slot_enabled_right(int p_slot_index, bool p_enable) {
ERR_FAIL_COND_MSG(p_slot_index < 0, vformat("Cannot set enable_right for the slot with index (%d) lesser than zero.", p_slot_index));
if (slot_table[p_slot_index].enable_right == p_enable) {
return;
}
slot_table[p_slot_index].enable_right = p_enable;
queue_redraw();
port_pos_dirty = true;
emit_signal(SNAME("slot_updated"), p_slot_index);
}
void GraphNode::set_slot_type_right(int p_slot_index, int p_type) {
ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set type_right for the slot with index '%d' because it hasn't been enabled.", p_slot_index));
if (slot_table[p_slot_index].type_right == p_type) {
return;
}
slot_table[p_slot_index].type_right = p_type;
queue_redraw();
port_pos_dirty = true;
emit_signal(SNAME("slot_updated"), p_slot_index);
}
int GraphNode::get_slot_type_right(int p_slot_index) const {
if (!slot_table.has(p_slot_index)) {
return 0;
}
return slot_table[p_slot_index].type_right;
}
void GraphNode::set_slot_color_right(int p_slot_index, const Color &p_color) {
ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set color_right for the slot with index '%d' because it hasn't been enabled.", p_slot_index));
if (slot_table[p_slot_index].color_right == p_color) {
return;
}
slot_table[p_slot_index].color_right = p_color;
queue_redraw();
port_pos_dirty = true;
emit_signal(SNAME("slot_updated"), p_slot_index);
}
Color GraphNode::get_slot_color_right(int p_slot_index) const {
if (!slot_table.has(p_slot_index)) {
return Color(1, 1, 1, 1);
}
return slot_table[p_slot_index].color_right;
}
void GraphNode::set_slot_custom_icon_right(int p_slot_index, const Ref<Texture2D> &p_custom_icon) {
ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set custom_port_icon_right for the slot with index '%d' because it hasn't been enabled.", p_slot_index));
if (slot_table[p_slot_index].custom_port_icon_right == p_custom_icon) {
return;
}
slot_table[p_slot_index].custom_port_icon_right = p_custom_icon;
queue_redraw();
port_pos_dirty = true;
emit_signal(SNAME("slot_updated"), p_slot_index);
}
Ref<Texture2D> GraphNode::get_slot_custom_icon_right(int p_slot_index) const {
if (!slot_table.has(p_slot_index)) {
return Ref<Texture2D>();
}
return slot_table[p_slot_index].custom_port_icon_right;
}
bool GraphNode::is_slot_draw_stylebox(int p_slot_index) const {
if (!slot_table.has(p_slot_index)) {
return false;
}
return slot_table[p_slot_index].draw_stylebox;
}
void GraphNode::set_slot_draw_stylebox(int p_slot_index, bool p_enable) {
ERR_FAIL_COND_MSG(p_slot_index < 0, vformat("Cannot set draw_stylebox for the slot with p_index (%d) lesser than zero.", p_slot_index));
slot_table[p_slot_index].draw_stylebox = p_enable;
queue_redraw();
port_pos_dirty = true;
emit_signal(SNAME("slot_updated"), p_slot_index);
}
void GraphNode::set_ignore_invalid_connection_type(bool p_ignore) {
ignore_invalid_connection_type = p_ignore;
}
bool GraphNode::is_ignoring_valid_connection_type() const {
return ignore_invalid_connection_type;
}
Size2 GraphNode::get_minimum_size() const {
Ref<StyleBox> sb_panel = theme_cache.panel;
Ref<StyleBox> sb_titlebar = theme_cache.titlebar;
Ref<StyleBox> sb_slot = theme_cache.slot;
int separation = theme_cache.separation;
Size2 minsize = titlebar_hbox->get_minimum_size() + sb_titlebar->get_minimum_size();
for (int i = 0; i < get_child_count(false); i++) {
Control *child = as_sortable_control(get_child(i, false));
if (!child) {
continue;
}
Size2i size = child->get_combined_minimum_size();
size.width += sb_panel->get_minimum_size().width;
if (slot_table.has(i)) {
size += slot_table[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2();
}
minsize.height += size.height;
minsize.width = MAX(minsize.width, size.width);
if (i > 0) {
minsize.height += separation;
}
}
minsize.height += sb_panel->get_minimum_size().height;
return minsize;
}
void GraphNode::_port_pos_update() {
int edgeofs = theme_cache.port_h_offset;
int separation = theme_cache.separation;
Ref<StyleBox> sb_panel = theme_cache.panel;
Ref<StyleBox> sb_titlebar = theme_cache.titlebar;
left_port_cache.clear();
right_port_cache.clear();
int vertical_ofs = titlebar_hbox->get_size().height + sb_titlebar->get_minimum_size().height + sb_panel->get_margin(SIDE_TOP);
int slot_index = 0;
for (int i = 0; i < get_child_count(false); i++) {
Control *child = as_sortable_control(get_child(i, false), SortableVisbilityMode::IGNORE);
if (!child) {
continue;
}
Size2i size = child->get_rect().size;
if (slot_table.has(slot_index)) {
if (slot_table[slot_index].enable_left) {
PortCache port_cache;
port_cache.pos = Point2i(edgeofs, vertical_ofs + size.height / 2);
port_cache.type = slot_table[slot_index].type_left;
port_cache.color = slot_table[slot_index].color_left;
port_cache.slot_index = slot_index;
left_port_cache.push_back(port_cache);
}
if (slot_table[slot_index].enable_right) {
PortCache port_cache;
port_cache.pos = Point2i(get_size().width - edgeofs, vertical_ofs + size.height / 2);
port_cache.type = slot_table[slot_index].type_right;
port_cache.color = slot_table[slot_index].color_right;
port_cache.slot_index = slot_index;
right_port_cache.push_back(port_cache);
}
}
vertical_ofs += separation;
vertical_ofs += size.height;
slot_index++;
}
port_pos_dirty = false;
}
int GraphNode::get_input_port_count() {
if (port_pos_dirty) {
_port_pos_update();
}
return left_port_cache.size();
}
int GraphNode::get_output_port_count() {
if (port_pos_dirty) {
_port_pos_update();
}
return right_port_cache.size();
}
Vector2 GraphNode::get_input_port_position(int p_port_idx) {
if (port_pos_dirty) {
_port_pos_update();
}
ERR_FAIL_INDEX_V(p_port_idx, left_port_cache.size(), Vector2());
Vector2 pos = left_port_cache[p_port_idx].pos;
return pos;
}
int GraphNode::get_input_port_type(int p_port_idx) {
if (port_pos_dirty) {
_port_pos_update();
}
ERR_FAIL_INDEX_V(p_port_idx, left_port_cache.size(), 0);
return left_port_cache[p_port_idx].type;
}
Color GraphNode::get_input_port_color(int p_port_idx) {
if (port_pos_dirty) {
_port_pos_update();
}
ERR_FAIL_INDEX_V(p_port_idx, left_port_cache.size(), Color());
return left_port_cache[p_port_idx].color;
}
int GraphNode::get_input_port_slot(int p_port_idx) {
if (port_pos_dirty) {
_port_pos_update();
}
ERR_FAIL_INDEX_V(p_port_idx, left_port_cache.size(), -1);
return left_port_cache[p_port_idx].slot_index;
}
Vector2 GraphNode::get_output_port_position(int p_port_idx) {
if (port_pos_dirty) {
_port_pos_update();
}
ERR_FAIL_INDEX_V(p_port_idx, right_port_cache.size(), Vector2());
Vector2 pos = right_port_cache[p_port_idx].pos;
return pos;
}
int GraphNode::get_output_port_type(int p_port_idx) {
if (port_pos_dirty) {
_port_pos_update();
}
ERR_FAIL_INDEX_V(p_port_idx, right_port_cache.size(), 0);
return right_port_cache[p_port_idx].type;
}
Color GraphNode::get_output_port_color(int p_port_idx) {
if (port_pos_dirty) {
_port_pos_update();
}
ERR_FAIL_INDEX_V(p_port_idx, right_port_cache.size(), Color());
return right_port_cache[p_port_idx].color;
}
int GraphNode::get_output_port_slot(int p_port_idx) {
if (port_pos_dirty) {
_port_pos_update();
}
ERR_FAIL_INDEX_V(p_port_idx, right_port_cache.size(), -1);
return right_port_cache[p_port_idx].slot_index;
}
void GraphNode::set_title(const String &p_title) {
if (title == p_title) {
return;
}
title = p_title;
if (title_label) {
title_label->set_text(title);
}
update_minimum_size();
}
String GraphNode::get_title() const {
return title;
}
HBoxContainer *GraphNode::get_titlebar_hbox() {
return titlebar_hbox;
}
Control::CursorShape GraphNode::get_cursor_shape(const Point2 &p_pos) const {
if (resizable) {
if (resizing || (p_pos.x > get_size().x - theme_cache.resizer->get_width() && p_pos.y > get_size().y - theme_cache.resizer->get_height())) {
return CURSOR_FDIAGSIZE;
}
}
return Control::get_cursor_shape(p_pos);
}
Vector<int> GraphNode::get_allowed_size_flags_horizontal() const {
Vector<int> flags;
flags.append(SIZE_FILL);
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
Vector<int> GraphNode::get_allowed_size_flags_vertical() const {
Vector<int> flags;
flags.append(SIZE_FILL);
flags.append(SIZE_EXPAND);
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
void GraphNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_title", "title"), &GraphNode::set_title);
ClassDB::bind_method(D_METHOD("get_title"), &GraphNode::get_title);
ClassDB::bind_method(D_METHOD("get_titlebar_hbox"), &GraphNode::get_titlebar_hbox);
ClassDB::bind_method(D_METHOD("set_slot", "slot_index", "enable_left_port", "type_left", "color_left", "enable_right_port", "type_right", "color_right", "custom_icon_left", "custom_icon_right", "draw_stylebox"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()), DEFVAL(true));
ClassDB::bind_method(D_METHOD("clear_slot", "slot_index"), &GraphNode::clear_slot);
ClassDB::bind_method(D_METHOD("clear_all_slots"), &GraphNode::clear_all_slots);
ClassDB::bind_method(D_METHOD("is_slot_enabled_left", "slot_index"), &GraphNode::is_slot_enabled_left);
ClassDB::bind_method(D_METHOD("set_slot_enabled_left", "slot_index", "enable"), &GraphNode::set_slot_enabled_left);
ClassDB::bind_method(D_METHOD("set_slot_type_left", "slot_index", "type"), &GraphNode::set_slot_type_left);
ClassDB::bind_method(D_METHOD("get_slot_type_left", "slot_index"), &GraphNode::get_slot_type_left);
ClassDB::bind_method(D_METHOD("set_slot_color_left", "slot_index", "color"), &GraphNode::set_slot_color_left);
ClassDB::bind_method(D_METHOD("get_slot_color_left", "slot_index"), &GraphNode::get_slot_color_left);
ClassDB::bind_method(D_METHOD("set_slot_custom_icon_left", "slot_index", "custom_icon"), &GraphNode::set_slot_custom_icon_left);
ClassDB::bind_method(D_METHOD("get_slot_custom_icon_left", "slot_index"), &GraphNode::get_slot_custom_icon_left);
ClassDB::bind_method(D_METHOD("is_slot_enabled_right", "slot_index"), &GraphNode::is_slot_enabled_right);
ClassDB::bind_method(D_METHOD("set_slot_enabled_right", "slot_index", "enable"), &GraphNode::set_slot_enabled_right);
ClassDB::bind_method(D_METHOD("set_slot_type_right", "slot_index", "type"), &GraphNode::set_slot_type_right);
ClassDB::bind_method(D_METHOD("get_slot_type_right", "slot_index"), &GraphNode::get_slot_type_right);
ClassDB::bind_method(D_METHOD("set_slot_color_right", "slot_index", "color"), &GraphNode::set_slot_color_right);
ClassDB::bind_method(D_METHOD("get_slot_color_right", "slot_index"), &GraphNode::get_slot_color_right);
ClassDB::bind_method(D_METHOD("set_slot_custom_icon_right", "slot_index", "custom_icon"), &GraphNode::set_slot_custom_icon_right);
ClassDB::bind_method(D_METHOD("get_slot_custom_icon_right", "slot_index"), &GraphNode::get_slot_custom_icon_right);
ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox", "slot_index"), &GraphNode::is_slot_draw_stylebox);
ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox", "slot_index", "enable"), &GraphNode::set_slot_draw_stylebox);
ClassDB::bind_method(D_METHOD("set_ignore_invalid_connection_type", "ignore"), &GraphNode::set_ignore_invalid_connection_type);
ClassDB::bind_method(D_METHOD("is_ignoring_valid_connection_type"), &GraphNode::is_ignoring_valid_connection_type);
ClassDB::bind_method(D_METHOD("get_input_port_count"), &GraphNode::get_input_port_count);
ClassDB::bind_method(D_METHOD("get_input_port_position", "port_idx"), &GraphNode::get_input_port_position);
ClassDB::bind_method(D_METHOD("get_input_port_type", "port_idx"), &GraphNode::get_input_port_type);
ClassDB::bind_method(D_METHOD("get_input_port_color", "port_idx"), &GraphNode::get_input_port_color);
ClassDB::bind_method(D_METHOD("get_input_port_slot", "port_idx"), &GraphNode::get_input_port_slot);
ClassDB::bind_method(D_METHOD("get_output_port_count"), &GraphNode::get_output_port_count);
ClassDB::bind_method(D_METHOD("get_output_port_position", "port_idx"), &GraphNode::get_output_port_position);
ClassDB::bind_method(D_METHOD("get_output_port_type", "port_idx"), &GraphNode::get_output_port_type);
ClassDB::bind_method(D_METHOD("get_output_port_color", "port_idx"), &GraphNode::get_output_port_color);
ClassDB::bind_method(D_METHOD("get_output_port_slot", "port_idx"), &GraphNode::get_output_port_slot);
GDVIRTUAL_BIND(_draw_port, "slot_index", "position", "left", "color")
ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ignore_invalid_connection_type"), "set_ignore_invalid_connection_type", "is_ignoring_valid_connection_type");
ADD_SIGNAL(MethodInfo("slot_updated", PropertyInfo(Variant::INT, "slot_index")));
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel_selected);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, titlebar);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, titlebar_selected);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, slot);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GraphNode, separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GraphNode, port_h_offset);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, GraphNode, port);
BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, GraphNode, resizer);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, GraphNode, resizer_color);
}
GraphNode::GraphNode() {
titlebar_hbox = memnew(HBoxContainer);
titlebar_hbox->set_h_size_flags(SIZE_EXPAND_FILL);
add_child(titlebar_hbox, false, INTERNAL_MODE_FRONT);
title_label = memnew(Label);
title_label->set_theme_type_variation("GraphNodeTitleLabel");
title_label->set_h_size_flags(SIZE_EXPAND_FILL);
titlebar_hbox->add_child(title_label);
set_mouse_filter(MOUSE_FILTER_STOP);
}

View file

@ -0,0 +1,177 @@
/**************************************************************************/
/* graph_node.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. */
/**************************************************************************/
#ifndef GRAPH_NODE_H
#define GRAPH_NODE_H
#include "scene/gui/graph_element.h"
class HBoxContainer;
class GraphNode : public GraphElement {
GDCLASS(GraphNode, GraphElement);
friend class GraphEdit;
struct Slot {
bool enable_left = false;
int type_left = 0;
Color color_left = Color(1, 1, 1, 1);
Ref<Texture2D> custom_port_icon_left;
bool enable_right = false;
int type_right = 0;
Color color_right = Color(1, 1, 1, 1);
Ref<Texture2D> custom_port_icon_right;
bool draw_stylebox = true;
};
struct PortCache {
Vector2 pos;
int slot_index;
int type = 0;
Color color;
};
struct _MinSizeCache {
int min_size = 0;
bool will_stretch = false;
int final_size = 0;
};
HBoxContainer *titlebar_hbox = nullptr;
Label *title_label = nullptr;
String title;
Vector<PortCache> left_port_cache;
Vector<PortCache> right_port_cache;
HashMap<int, Slot> slot_table;
Vector<int> slot_y_cache;
struct ThemeCache {
Ref<StyleBox> panel;
Ref<StyleBox> panel_selected;
Ref<StyleBox> titlebar;
Ref<StyleBox> titlebar_selected;
Ref<StyleBox> slot;
int separation = 0;
int port_h_offset = 0;
Ref<Texture2D> port;
Ref<Texture2D> resizer;
Color resizer_color;
} theme_cache;
bool port_pos_dirty = true;
bool ignore_invalid_connection_type = false;
void _port_pos_update();
protected:
void _notification(int p_what);
static void _bind_methods();
virtual void _resort() override;
virtual void draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color);
GDVIRTUAL4(_draw_port, int, Point2i, bool, const Color &);
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
public:
void set_title(const String &p_title);
String get_title() const;
HBoxContainer *get_titlebar_hbox();
void set_slot(int p_slot_index, 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_slot_index);
void clear_all_slots();
bool is_slot_enabled_left(int p_slot_index) const;
void set_slot_enabled_left(int p_slot_index, bool p_enable);
void set_slot_type_left(int p_slot_index, int p_type);
int get_slot_type_left(int p_slot_index) const;
void set_slot_color_left(int p_slot_index, const Color &p_color);
Color get_slot_color_left(int p_slot_index) const;
void set_slot_custom_icon_left(int p_slot_index, const Ref<Texture2D> &p_custom_icon);
Ref<Texture2D> get_slot_custom_icon_left(int p_slot_index) const;
bool is_slot_enabled_right(int p_slot_index) const;
void set_slot_enabled_right(int p_slot_index, bool p_enable);
void set_slot_type_right(int p_slot_index, int p_type);
int get_slot_type_right(int p_slot_index) const;
void set_slot_color_right(int p_slot_index, const Color &p_color);
Color get_slot_color_right(int p_slot_index) const;
void set_slot_custom_icon_right(int p_slot_index, const Ref<Texture2D> &p_custom_icon);
Ref<Texture2D> get_slot_custom_icon_right(int p_slot_index) const;
bool is_slot_draw_stylebox(int p_slot_index) const;
void set_slot_draw_stylebox(int p_slot_index, bool p_enable);
void set_ignore_invalid_connection_type(bool p_ignore);
bool is_ignoring_valid_connection_type() const;
int get_input_port_count();
Vector2 get_input_port_position(int p_port_idx);
int get_input_port_type(int p_port_idx);
Color get_input_port_color(int p_port_idx);
int get_input_port_slot(int p_port_idx);
int get_output_port_count();
Vector2 get_output_port_position(int p_port_idx);
int get_output_port_type(int p_port_idx);
Color get_output_port_color(int p_port_idx);
int get_output_port_slot(int p_port_idx);
virtual Size2 get_minimum_size() const override;
virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
virtual Vector<int> get_allowed_size_flags_horizontal() const override;
virtual Vector<int> get_allowed_size_flags_vertical() const override;
GraphNode();
};
#endif // GRAPH_NODE_H

View file

@ -0,0 +1,322 @@
/**************************************************************************/
/* grid_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 "grid_container.h"
#include "core/templates/rb_set.h"
#include "scene/theme/theme_db.h"
void GridContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_SORT_CHILDREN: {
RBMap<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col).
RBMap<int, int> row_minh; // Max of min_height of all controls in each row (indexed by row).
RBSet<int> col_expanded; // Columns which have the SIZE_EXPAND flag set.
RBSet<int> row_expanded; // Rows which have the SIZE_EXPAND flag set.
// Compute the per-column/per-row data.
int valid_controls_index = 0;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
int row = valid_controls_index / columns;
int col = valid_controls_index % columns;
valid_controls_index++;
Size2i ms = c->get_combined_minimum_size();
if (col_minw.has(col)) {
col_minw[col] = MAX(col_minw[col], ms.width);
} else {
col_minw[col] = ms.width;
}
if (row_minh.has(row)) {
row_minh[row] = MAX(row_minh[row], ms.height);
} else {
row_minh[row] = ms.height;
}
if (c->get_h_size_flags().has_flag(SIZE_EXPAND)) {
col_expanded.insert(col);
}
if (c->get_v_size_flags().has_flag(SIZE_EXPAND)) {
row_expanded.insert(row);
}
}
int max_col = MIN(valid_controls_index, columns);
int max_row = ceil((float)valid_controls_index / (float)columns);
// Consider all empty columns expanded.
for (int i = valid_controls_index; i < columns; i++) {
col_expanded.insert(i);
}
// Evaluate the remaining space for expanded columns/rows.
Size2 remaining_space = get_size();
for (const KeyValue<int, int> &E : col_minw) {
if (!col_expanded.has(E.key)) {
remaining_space.width -= E.value;
}
}
for (const KeyValue<int, int> &E : row_minh) {
if (!row_expanded.has(E.key)) {
remaining_space.height -= E.value;
}
}
remaining_space.height -= theme_cache.v_separation * MAX(max_row - 1, 0);
remaining_space.width -= theme_cache.h_separation * MAX(max_col - 1, 0);
bool can_fit = false;
while (!can_fit && col_expanded.size() > 0) {
// Check if all minwidth constraints are OK if we use the remaining space.
can_fit = true;
int max_index = col_expanded.front()->get();
for (const int &E : col_expanded) {
if (col_minw[E] > col_minw[max_index]) {
max_index = E;
}
if (can_fit && (remaining_space.width / col_expanded.size()) < col_minw[E]) {
can_fit = false;
}
}
// If not, the column with maximum minwidth is not expanded.
if (!can_fit) {
col_expanded.erase(max_index);
remaining_space.width -= col_minw[max_index];
}
}
can_fit = false;
while (!can_fit && row_expanded.size() > 0) {
// Check if all minheight constraints are OK if we use the remaining space.
can_fit = true;
int max_index = row_expanded.front()->get();
for (const int &E : row_expanded) {
if (row_minh[E] > row_minh[max_index]) {
max_index = E;
}
if (can_fit && (remaining_space.height / row_expanded.size()) < row_minh[E]) {
can_fit = false;
}
}
// If not, the row with maximum minheight is not expanded.
if (!can_fit) {
row_expanded.erase(max_index);
remaining_space.height -= row_minh[max_index];
}
}
// Finally, fit the nodes.
int col_remaining_pixel = 0;
int col_expand = 0;
if (col_expanded.size() > 0) {
col_expand = remaining_space.width / col_expanded.size();
col_remaining_pixel = remaining_space.width - col_expanded.size() * col_expand;
}
int row_remaining_pixel = 0;
int row_expand = 0;
if (row_expanded.size() > 0) {
row_expand = remaining_space.height / row_expanded.size();
row_remaining_pixel = remaining_space.height - row_expanded.size() * row_expand;
}
bool rtl = is_layout_rtl();
int col_ofs = 0;
int row_ofs = 0;
// Calculate the index of rows and columns that receive the remaining pixel.
int col_remaining_pixel_index = 0;
for (int i = 0; i < max_col; i++) {
if (col_remaining_pixel == 0) {
break;
}
if (col_expanded.has(i)) {
col_remaining_pixel_index = i + 1;
col_remaining_pixel--;
}
}
int row_remaining_pixel_index = 0;
for (int i = 0; i < max_row; i++) {
if (row_remaining_pixel == 0) {
break;
}
if (row_expanded.has(i)) {
row_remaining_pixel_index = i + 1;
row_remaining_pixel--;
}
}
valid_controls_index = 0;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
int row = valid_controls_index / columns;
int col = valid_controls_index % columns;
valid_controls_index++;
if (col == 0) {
if (rtl) {
col_ofs = get_size().width;
} else {
col_ofs = 0;
}
if (row > 0) {
row_ofs += (row_expanded.has(row - 1) ? row_expand : row_minh[row - 1]) + theme_cache.v_separation;
if (row_expanded.has(row - 1) && row - 1 < row_remaining_pixel_index) {
// Apply the remaining pixel of the previous row.
row_ofs++;
}
}
}
Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]);
// Add the remaining pixel to the expanding columns and rows, starting from left and top.
if (col_expanded.has(col) && col < col_remaining_pixel_index) {
s.x++;
}
if (row_expanded.has(row) && row < row_remaining_pixel_index) {
s.y++;
}
if (rtl) {
Point2 p(col_ofs - s.width, row_ofs);
fit_child_in_rect(c, Rect2(p, s));
col_ofs -= s.width + theme_cache.h_separation;
} else {
Point2 p(col_ofs, row_ofs);
fit_child_in_rect(c, Rect2(p, s));
col_ofs += s.width + theme_cache.h_separation;
}
}
} break;
case NOTIFICATION_THEME_CHANGED: {
update_minimum_size();
} break;
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
queue_sort();
} break;
}
}
void GridContainer::set_columns(int p_columns) {
ERR_FAIL_COND(p_columns < 1);
if (columns == p_columns) {
return;
}
columns = p_columns;
queue_sort();
update_minimum_size();
}
int GridContainer::get_columns() const {
return columns;
}
int GridContainer::get_h_separation() const {
return theme_cache.h_separation;
}
void GridContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_columns", "columns"), &GridContainer::set_columns);
ClassDB::bind_method(D_METHOD("get_columns"), &GridContainer::get_columns);
ADD_PROPERTY(PropertyInfo(Variant::INT, "columns", PROPERTY_HINT_RANGE, "1,1024,1"), "set_columns", "get_columns");
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GridContainer, h_separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GridContainer, v_separation);
}
Size2 GridContainer::get_minimum_size() const {
RBMap<int, int> col_minw;
RBMap<int, int> row_minh;
int max_row = 0;
int max_col = 0;
int valid_controls_index = 0;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE);
if (!c) {
continue;
}
int row = valid_controls_index / columns;
int col = valid_controls_index % columns;
valid_controls_index++;
Size2i ms = c->get_combined_minimum_size();
if (col_minw.has(col)) {
col_minw[col] = MAX(col_minw[col], ms.width);
} else {
col_minw[col] = ms.width;
}
if (row_minh.has(row)) {
row_minh[row] = MAX(row_minh[row], ms.height);
} else {
row_minh[row] = ms.height;
}
max_col = MAX(col, max_col);
max_row = MAX(row, max_row);
}
Size2 ms;
for (const KeyValue<int, int> &E : col_minw) {
ms.width += E.value;
}
for (const KeyValue<int, int> &E : row_minh) {
ms.height += E.value;
}
ms.height += theme_cache.v_separation * max_row;
ms.width += theme_cache.h_separation * max_col;
return ms;
}
GridContainer::GridContainer() {}

View file

@ -0,0 +1,60 @@
/**************************************************************************/
/* grid_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. */
/**************************************************************************/
#ifndef GRID_CONTAINER_H
#define GRID_CONTAINER_H
#include "scene/gui/container.h"
class GridContainer : public Container {
GDCLASS(GridContainer, Container);
int columns = 1;
struct ThemeCache {
int h_separation = 0;
int v_separation = 0;
} theme_cache;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void set_columns(int p_columns);
int get_columns() const;
virtual Size2 get_minimum_size() const override;
int get_h_separation() const;
GridContainer();
};
#endif // GRID_CONTAINER_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,306 @@
/**************************************************************************/
/* item_list.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. */
/**************************************************************************/
#ifndef ITEM_LIST_H
#define ITEM_LIST_H
#include "scene/gui/control.h"
#include "scene/gui/scroll_bar.h"
#include "scene/property_list_helper.h"
#include "scene/resources/text_paragraph.h"
class ItemList : public Control {
GDCLASS(ItemList, Control);
public:
enum IconMode {
ICON_MODE_TOP,
ICON_MODE_LEFT
};
enum SelectMode {
SELECT_SINGLE,
SELECT_MULTI
};
private:
struct Item {
Ref<Texture2D> icon;
bool icon_transposed = false;
Rect2i icon_region;
Color icon_modulate = Color(1, 1, 1, 1);
Ref<Texture2D> tag_icon;
String text;
String xl_text;
Ref<TextParagraph> text_buf;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
bool selectable = true;
bool selected = false;
bool disabled = false;
bool tooltip_enabled = true;
Variant metadata;
String tooltip;
Color custom_fg;
Color custom_bg = Color(0.0, 0.0, 0.0, 0.0);
int column = 0;
Rect2 rect_cache;
Rect2 min_rect_cache;
Size2 get_icon_size() const;
bool operator<(const Item &p_another) const { return text < p_another.text; }
Item() {
text_buf.instantiate();
}
Item(bool p_dummy) {}
};
static inline PropertyListHelper base_property_helper;
PropertyListHelper property_helper;
int current = -1;
int hovered = -1;
bool shape_changed = true;
bool ensure_selected_visible = false;
bool same_column_width = false;
bool allow_search = true;
bool auto_height = false;
float auto_height_value = 0.0;
Vector<Item> items;
Vector<int> separators;
SelectMode select_mode = SELECT_SINGLE;
IconMode icon_mode = ICON_MODE_LEFT;
VScrollBar *scroll_bar = nullptr;
TextServer::OverrunBehavior text_overrun_behavior = TextServer::OVERRUN_TRIM_ELLIPSIS;
uint64_t search_time_msec = 0;
String search_string;
int current_columns = 1;
int fixed_column_width = 0;
int max_text_lines = 1;
int max_columns = 1;
Size2 fixed_icon_size;
Size2 max_item_size_cache;
Size2 fixed_tag_icon_size;
int defer_select_single = -1;
bool allow_rmb_select = false;
bool allow_reselect = false;
real_t icon_scale = 1.0;
bool do_autoscroll_to_bottom = false;
struct ThemeCache {
int h_separation = 0;
int v_separation = 0;
Ref<StyleBox> panel_style;
Ref<StyleBox> focus_style;
Ref<Font> font;
int font_size = 0;
Color font_color;
Color font_hovered_color;
Color font_selected_color;
int font_outline_size = 0;
Color font_outline_color;
int line_separation = 0;
int icon_margin = 0;
Ref<StyleBox> hovered_style;
Ref<StyleBox> selected_style;
Ref<StyleBox> selected_focus_style;
Ref<StyleBox> cursor_style;
Ref<StyleBox> cursor_focus_style;
Color guide_color;
} theme_cache;
void _scroll_changed(double);
void _shape_text(int p_idx);
void _mouse_exited();
protected:
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); }
void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list); }
bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
static void _bind_methods();
public:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
int add_item(const String &p_item, const Ref<Texture2D> &p_texture = Ref<Texture2D>(), bool p_selectable = true);
int add_icon_item(const Ref<Texture2D> &p_item, bool p_selectable = true);
void set_item_text(int p_idx, const String &p_text);
String get_item_text(int p_idx) const;
void set_item_text_direction(int p_idx, TextDirection p_text_direction);
TextDirection get_item_text_direction(int p_idx) const;
void set_item_language(int p_idx, const String &p_language);
String get_item_language(int p_idx) const;
void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon);
Ref<Texture2D> get_item_icon(int p_idx) const;
void set_item_icon_transposed(int p_idx, const bool transposed);
bool is_item_icon_transposed(int p_idx) const;
void set_item_icon_region(int p_idx, const Rect2 &p_region);
Rect2 get_item_icon_region(int p_idx) const;
void set_item_icon_modulate(int p_idx, const Color &p_modulate);
Color get_item_icon_modulate(int p_idx) const;
void set_item_selectable(int p_idx, bool p_selectable);
bool is_item_selectable(int p_idx) const;
void set_item_disabled(int p_idx, bool p_disabled);
bool is_item_disabled(int p_idx) const;
void set_item_metadata(int p_idx, const Variant &p_metadata);
Variant get_item_metadata(int p_idx) const;
void set_item_tag_icon(int p_idx, const Ref<Texture2D> &p_tag_icon);
void set_item_tooltip_enabled(int p_idx, const bool p_enabled);
bool is_item_tooltip_enabled(int p_idx) const;
void set_item_tooltip(int p_idx, const String &p_tooltip);
String get_item_tooltip(int p_idx) const;
void set_item_custom_bg_color(int p_idx, const Color &p_custom_bg_color);
Color get_item_custom_bg_color(int p_idx) const;
void set_item_custom_fg_color(int p_idx, const Color &p_custom_fg_color);
Color get_item_custom_fg_color(int p_idx) const;
Rect2 get_item_rect(int p_idx, bool p_expand = true) const;
void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior);
TextServer::OverrunBehavior get_text_overrun_behavior() const;
void select(int p_idx, bool p_single = true);
void deselect(int p_idx);
void deselect_all();
bool is_selected(int p_idx) const;
Vector<int> get_selected_items();
bool is_anything_selected();
void set_current(int p_current);
int get_current() const;
void move_item(int p_from_idx, int p_to_idx);
void set_item_count(int p_count);
int get_item_count() const;
void remove_item(int p_idx);
void clear();
void set_fixed_column_width(int p_size);
int get_fixed_column_width() const;
void set_same_column_width(bool p_enable);
bool is_same_column_width() const;
void set_max_text_lines(int p_lines);
int get_max_text_lines() const;
void set_max_columns(int p_amount);
int get_max_columns() const;
void set_select_mode(SelectMode p_mode);
SelectMode get_select_mode() const;
void set_icon_mode(IconMode p_mode);
IconMode get_icon_mode() const;
void set_fixed_icon_size(const Size2i &p_size);
Size2i get_fixed_icon_size() const;
void set_fixed_tag_icon_size(const Size2i &p_size);
void set_allow_rmb_select(bool p_allow);
bool get_allow_rmb_select() const;
void set_allow_reselect(bool p_allow);
bool get_allow_reselect() const;
void set_allow_search(bool p_allow);
bool get_allow_search() const;
void ensure_current_is_visible();
void sort_items_by_text();
int find_metadata(const Variant &p_metadata) const;
virtual String get_tooltip(const Point2 &p_pos) const override;
int get_item_at_position(const Point2 &p_pos, bool p_exact = false) const;
bool is_pos_at_end_of_items(const Point2 &p_pos) const;
void set_icon_scale(real_t p_scale);
real_t get_icon_scale() const;
void set_auto_height(bool p_enable);
bool has_auto_height() const;
Size2 get_minimum_size() const override;
void set_autoscroll_to_bottom(const bool p_enable);
void force_update_list_size();
VScrollBar *get_v_scroll_bar() { return scroll_bar; }
ItemList();
~ItemList();
};
VARIANT_ENUM_CAST(ItemList::SelectMode);
VARIANT_ENUM_CAST(ItemList::IconMode);
#endif // ITEM_LIST_H

1217
engine/scene/gui/label.cpp Normal file

File diff suppressed because it is too large Load diff

180
engine/scene/gui/label.h Normal file
View file

@ -0,0 +1,180 @@
/**************************************************************************/
/* label.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. */
/**************************************************************************/
#ifndef LABEL_H
#define LABEL_H
#include "scene/gui/control.h"
#include "scene/resources/label_settings.h"
class Label : public Control {
GDCLASS(Label, Control);
private:
enum LabelDrawStep {
DRAW_STEP_SHADOW,
DRAW_STEP_OUTLINE,
DRAW_STEP_TEXT,
DRAW_STEP_MAX,
};
HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT;
VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_TOP;
String text;
String xl_text;
TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF;
BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE;
bool clip = false;
String el_char = U"";
TextServer::OverrunBehavior overrun_behavior = TextServer::OVERRUN_NO_TRIMMING;
Size2 minsize;
bool uppercase = false;
bool lines_dirty = true;
bool dirty = true;
bool font_dirty = true;
RID text_rid;
Vector<RID> lines_rid;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
TextServer::VisibleCharactersBehavior visible_chars_behavior = TextServer::VC_CHARS_BEFORE_SHAPING;
int visible_chars = -1;
float visible_ratio = 1.0;
int lines_skipped = 0;
int max_lines_visible = -1;
PackedFloat32Array tab_stops;
Ref<LabelSettings> settings;
struct ThemeCache {
Ref<StyleBox> normal_style;
Ref<Font> font;
int font_size = 0;
int line_spacing = 0;
Color font_color;
Color font_shadow_color;
Point2 font_shadow_offset;
Color font_outline_color;
int font_outline_size;
int font_shadow_outline_size;
} theme_cache;
void _update_visible();
void _shape();
void _invalidate();
protected:
void _notification(int p_what);
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
bool _set(const StringName &p_name, const Variant &p_value);
#endif
public:
virtual Size2 get_minimum_size() const override;
virtual PackedStringArray get_configuration_warnings() const override;
void set_horizontal_alignment(HorizontalAlignment p_alignment);
HorizontalAlignment get_horizontal_alignment() const;
void set_vertical_alignment(VerticalAlignment p_alignment);
VerticalAlignment get_vertical_alignment() const;
void set_text(const String &p_string);
String get_text() const;
void set_label_settings(const Ref<LabelSettings> &p_settings);
Ref<LabelSettings> get_label_settings() const;
void set_text_direction(TextDirection p_text_direction);
TextDirection get_text_direction() const;
void set_language(const String &p_language);
String get_language() const;
void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
void set_autowrap_mode(TextServer::AutowrapMode p_mode);
TextServer::AutowrapMode get_autowrap_mode() const;
void set_justification_flags(BitField<TextServer::JustificationFlag> p_flags);
BitField<TextServer::JustificationFlag> get_justification_flags() const;
void set_uppercase(bool p_uppercase);
bool is_uppercase() const;
void set_visible_characters_behavior(TextServer::VisibleCharactersBehavior p_behavior);
TextServer::VisibleCharactersBehavior get_visible_characters_behavior() const;
void set_visible_characters(int p_amount);
int get_visible_characters() const;
int get_total_character_count() const;
void set_visible_ratio(float p_ratio);
float get_visible_ratio() const;
void set_clip_text(bool p_clip);
bool is_clipping_text() const;
void set_tab_stops(const PackedFloat32Array &p_tab_stops);
PackedFloat32Array get_tab_stops() const;
void set_text_overrun_behavior(TextServer::OverrunBehavior p_behavior);
TextServer::OverrunBehavior get_text_overrun_behavior() const;
void set_ellipsis_char(const String &p_char);
String get_ellipsis_char() const;
void set_lines_skipped(int p_lines);
int get_lines_skipped() const;
void set_max_lines_visible(int p_lines);
int get_max_lines_visible() const;
int get_line_height(int p_line = -1) const;
int get_line_count() const;
int get_visible_line_count() const;
Rect2 get_character_bounds(int p_pos) const;
Label(const String &p_text = String());
~Label();
};
#endif // LABEL_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,399 @@
/**************************************************************************/
/* line_edit.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. */
/**************************************************************************/
#ifndef LINE_EDIT_H
#define LINE_EDIT_H
#include "scene/gui/control.h"
#include "scene/gui/popup_menu.h"
class LineEdit : public Control {
GDCLASS(LineEdit, Control);
public:
enum MenuItems {
MENU_CUT,
MENU_COPY,
MENU_PASTE,
MENU_CLEAR,
MENU_SELECT_ALL,
MENU_UNDO,
MENU_REDO,
MENU_SUBMENU_TEXT_DIR,
MENU_DIR_INHERITED,
MENU_DIR_AUTO,
MENU_DIR_LTR,
MENU_DIR_RTL,
MENU_DISPLAY_UCC,
MENU_SUBMENU_INSERT_UCC,
MENU_INSERT_LRM,
MENU_INSERT_RLM,
MENU_INSERT_LRE,
MENU_INSERT_RLE,
MENU_INSERT_LRO,
MENU_INSERT_RLO,
MENU_INSERT_PDF,
MENU_INSERT_ALM,
MENU_INSERT_LRI,
MENU_INSERT_RLI,
MENU_INSERT_FSI,
MENU_INSERT_PDI,
MENU_INSERT_ZWJ,
MENU_INSERT_ZWNJ,
MENU_INSERT_WJ,
MENU_INSERT_SHY,
MENU_MAX
};
enum VirtualKeyboardType {
KEYBOARD_TYPE_DEFAULT,
KEYBOARD_TYPE_MULTILINE,
KEYBOARD_TYPE_NUMBER,
KEYBOARD_TYPE_NUMBER_DECIMAL,
KEYBOARD_TYPE_PHONE,
KEYBOARD_TYPE_EMAIL_ADDRESS,
KEYBOARD_TYPE_PASSWORD,
KEYBOARD_TYPE_URL
};
private:
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
bool editable = false;
bool pass = false;
bool text_changed_dirty = false;
bool alt_start = false;
uint32_t alt_code = 0;
String undo_text;
String text;
String placeholder;
String placeholder_translated;
String secret_character = U"";
String ime_text;
Point2 ime_selection;
RID text_rid;
float full_width = 0.0;
bool selecting_enabled = true;
bool deselect_on_focus_loss_enabled = true;
bool drag_and_drop_selection_enabled = true;
bool context_menu_enabled = true;
PopupMenu *menu = nullptr;
PopupMenu *menu_dir = nullptr;
PopupMenu *menu_ctl = nullptr;
bool caret_mid_grapheme_enabled = false;
int caret_column = 0;
float scroll_offset = 0.0;
int max_length = 0; // 0 for no maximum.
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
TextDirection input_direction = TEXT_DIRECTION_LTR;
TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
bool draw_control_chars = false;
bool expand_to_text_length = false;
bool window_has_focus = true;
bool clear_button_enabled = false;
bool shortcut_keys_enabled = true;
bool virtual_keyboard_enabled = true;
VirtualKeyboardType virtual_keyboard_type = KEYBOARD_TYPE_DEFAULT;
bool middle_mouse_paste_enabled = true;
bool drag_action = false;
bool drag_caret_force_displayed = false;
Ref<Texture2D> right_icon;
bool flat = false;
struct Selection {
int begin = 0;
int end = 0;
int start_column = 0;
bool enabled = false;
bool creating = false;
bool double_click = false;
bool drag_attempt = false;
} selection;
struct TextOperation {
int caret_column = 0;
float scroll_offset = 0.0;
String text;
};
List<TextOperation> undo_stack;
List<TextOperation>::Element *undo_stack_pos = nullptr;
struct ClearButtonStatus {
bool press_attempt = false;
bool pressing_inside = false;
} clear_button_status;
uint64_t last_dblclk = 0;
Vector2 last_dblclk_pos;
bool caret_blink_enabled = false;
bool caret_force_displayed = false;
bool draw_caret = true;
float caret_blink_interval = 0.65;
double caret_blink_timer = 0.0;
bool caret_can_draw = false;
bool pending_select_all_on_focus = false;
bool select_all_on_focus = false;
struct ThemeCache {
Ref<StyleBox> normal;
Ref<StyleBox> read_only;
Ref<StyleBox> focus;
Ref<Font> font;
int font_size = 0;
Color font_color;
Color font_uneditable_color;
Color font_selected_color;
int font_outline_size;
Color font_outline_color;
Color font_placeholder_color;
int caret_width = 0;
Color caret_color;
int minimum_character_width = 0;
Color selection_color;
Ref<Texture2D> clear_icon;
Color clear_button_color;
Color clear_button_color_pressed;
float base_scale = 1.0;
} theme_cache;
void _clear_undo_stack();
void _clear_redo();
void _create_undo_state();
Key _get_menu_action_accelerator(const String &p_action);
void _generate_context_menu();
void _update_context_menu();
void _shape();
void _fit_to_width();
void _text_changed();
void _emit_text_change();
void shift_selection_check_pre(bool);
void shift_selection_check_post(bool);
void selection_fill_at_caret();
void set_scroll_offset(float p_pos);
float get_scroll_offset() const;
void set_caret_at_pixel_pos(int p_x);
Vector2 get_caret_pixel_pos();
void _reset_caret_blink_timer();
void _toggle_draw_caret();
void _validate_caret_can_draw();
void clear_internal();
void _editor_settings_changed();
void _swap_current_input_direction();
void _move_caret_left(bool p_select, bool p_move_by_word = false);
void _move_caret_right(bool p_select, bool p_move_by_word = false);
void _move_caret_start(bool p_select);
void _move_caret_end(bool p_select);
void _backspace(bool p_word = false, bool p_all_to_left = false);
void _delete(bool p_word = false, bool p_all_to_right = false);
protected:
bool _is_over_clear_button(const Point2 &p_pos) const;
virtual void _update_theme_item_cache() override;
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
public:
void set_horizontal_alignment(HorizontalAlignment p_alignment);
HorizontalAlignment get_horizontal_alignment() const;
virtual Variant get_drag_data(const Point2 &p_point) override;
virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override;
virtual void drop_data(const Point2 &p_point, const Variant &p_data) override;
virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override;
void menu_option(int p_option);
void set_context_menu_enabled(bool p_enable);
bool is_context_menu_enabled();
PopupMenu *get_menu() const;
bool is_menu_visible() const;
void select(int p_from = 0, int p_to = -1);
void select_all();
void selection_delete();
void deselect();
bool has_selection() const;
String get_selected_text();
int get_selection_from_column() const;
int get_selection_to_column() const;
void delete_char();
void delete_text(int p_from_column, int p_to_column);
void set_text(String p_text);
String get_text() const;
void set_text_with_selection(const String &p_text); // Set text, while preserving selection.
void set_text_direction(TextDirection p_text_direction);
TextDirection get_text_direction() const;
void set_language(const String &p_language);
String get_language() const;
void set_draw_control_chars(bool p_draw_control_chars);
bool get_draw_control_chars() const;
void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
void set_placeholder(String p_text);
String get_placeholder() const;
void set_caret_column(int p_column);
int get_caret_column() const;
void set_max_length(int p_max_length);
int get_max_length() const;
void insert_text_at_caret(String p_text);
void clear();
void set_caret_mid_grapheme_enabled(const bool p_enabled);
bool is_caret_mid_grapheme_enabled() const;
bool is_caret_blink_enabled() const;
void set_caret_blink_enabled(const bool p_enabled);
float get_caret_blink_interval() const;
void set_caret_blink_interval(const float p_interval);
void set_caret_force_displayed(const bool p_enabled);
bool is_caret_force_displayed() const;
void copy_text();
void cut_text();
void paste_text();
bool has_undo() const;
bool has_redo() const;
void undo();
void redo();
void set_editable(bool p_editable);
bool is_editable() const;
void set_secret(bool p_secret);
bool is_secret() const;
void set_secret_character(const String &p_string);
String get_secret_character() const;
virtual Size2 get_minimum_size() const override;
void set_expand_to_text_length_enabled(bool p_enabled);
bool is_expand_to_text_length_enabled() const;
void set_clear_button_enabled(bool p_enabled);
bool is_clear_button_enabled() const;
void set_shortcut_keys_enabled(bool p_enabled);
bool is_shortcut_keys_enabled() const;
void set_virtual_keyboard_enabled(bool p_enable);
bool is_virtual_keyboard_enabled() const;
void set_virtual_keyboard_type(VirtualKeyboardType p_type);
VirtualKeyboardType get_virtual_keyboard_type() const;
void set_middle_mouse_paste_enabled(bool p_enabled);
bool is_middle_mouse_paste_enabled() const;
void set_selecting_enabled(bool p_enabled);
bool is_selecting_enabled() const;
void set_deselect_on_focus_loss_enabled(const bool p_enabled);
bool is_deselect_on_focus_loss_enabled() const;
void set_drag_and_drop_selection_enabled(const bool p_enabled);
bool is_drag_and_drop_selection_enabled() const;
void set_right_icon(const Ref<Texture2D> &p_icon);
Ref<Texture2D> get_right_icon();
void set_flat(bool p_enabled);
bool is_flat() const;
void set_select_all_on_focus(bool p_enabled);
bool is_select_all_on_focus() const;
void clear_pending_select_all_on_focus(); // For other controls, e.g. SpinBox.
virtual bool is_text_field() const override;
PackedStringArray get_configuration_warnings() const override;
void show_virtual_keyboard();
LineEdit(const String &p_placeholder = String());
~LineEdit();
};
VARIANT_ENUM_CAST(LineEdit::MenuItems);
VARIANT_ENUM_CAST(LineEdit::VirtualKeyboardType);
#endif // LINE_EDIT_H

View file

@ -0,0 +1,296 @@
/**************************************************************************/
/* link_button.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 "link_button.h"
#include "core/string/translation.h"
#include "scene/theme/theme_db.h"
void LinkButton::_shape() {
Ref<Font> font = theme_cache.font;
int font_size = theme_cache.font_size;
text_buf->clear();
if (text_direction == Control::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);
}
TS->shaped_text_set_bidi_override(text_buf->get_rid(), structured_text_parser(st_parser, st_args, xl_text));
text_buf->add_string(xl_text, font, font_size, language);
}
void LinkButton::set_text(const String &p_text) {
if (text == p_text) {
return;
}
text = p_text;
xl_text = atr(text);
_shape();
update_minimum_size();
queue_redraw();
}
String LinkButton::get_text() const {
return text;
}
void LinkButton::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) {
if (st_parser != p_parser) {
st_parser = p_parser;
_shape();
queue_redraw();
}
}
TextServer::StructuredTextParser LinkButton::get_structured_text_bidi_override() const {
return st_parser;
}
void LinkButton::set_structured_text_bidi_override_options(Array p_args) {
st_args = p_args;
_shape();
queue_redraw();
}
Array LinkButton::get_structured_text_bidi_override_options() const {
return st_args;
}
void LinkButton::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
_shape();
queue_redraw();
}
}
Control::TextDirection LinkButton::get_text_direction() const {
return text_direction;
}
void LinkButton::set_language(const String &p_language) {
if (language != p_language) {
language = p_language;
_shape();
queue_redraw();
}
}
String LinkButton::get_language() const {
return language;
}
void LinkButton::set_uri(const String &p_uri) {
uri = p_uri;
}
String LinkButton::get_uri() const {
return uri;
}
void LinkButton::set_underline_mode(UnderlineMode p_underline_mode) {
if (underline_mode == p_underline_mode) {
return;
}
underline_mode = p_underline_mode;
queue_redraw();
}
LinkButton::UnderlineMode LinkButton::get_underline_mode() const {
return underline_mode;
}
Ref<Font> LinkButton::get_button_font() const {
return theme_cache.font;
}
void LinkButton::pressed() {
if (uri.is_empty()) {
return;
}
OS::get_singleton()->shell_open(uri);
}
Size2 LinkButton::get_minimum_size() const {
return text_buf->get_size();
}
void LinkButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_TRANSLATION_CHANGED: {
xl_text = atr(text);
_shape();
update_minimum_size();
queue_redraw();
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
queue_redraw();
} break;
case NOTIFICATION_THEME_CHANGED: {
_shape();
update_minimum_size();
queue_redraw();
} break;
case NOTIFICATION_DRAW: {
RID ci = get_canvas_item();
Size2 size = get_size();
Color color;
bool do_underline = false;
switch (get_draw_mode()) {
case DRAW_NORMAL: {
if (has_focus()) {
color = theme_cache.font_focus_color;
} else {
color = theme_cache.font_color;
}
do_underline = underline_mode == UNDERLINE_MODE_ALWAYS;
} break;
case DRAW_HOVER_PRESSED:
case DRAW_PRESSED: {
if (has_theme_color(SNAME("font_pressed_color"))) {
color = theme_cache.font_pressed_color;
} else {
color = theme_cache.font_color;
}
do_underline = underline_mode != UNDERLINE_MODE_NEVER;
} break;
case DRAW_HOVER: {
color = theme_cache.font_hover_color;
do_underline = underline_mode != UNDERLINE_MODE_NEVER;
} break;
case DRAW_DISABLED: {
color = theme_cache.font_disabled_color;
do_underline = underline_mode == UNDERLINE_MODE_ALWAYS;
} break;
}
if (has_focus()) {
Ref<StyleBox> style = theme_cache.focus;
style->draw(ci, Rect2(Point2(), size));
}
int width = text_buf->get_line_width();
Color font_outline_color = theme_cache.font_outline_color;
int outline_size = theme_cache.outline_size;
if (is_layout_rtl()) {
if (outline_size > 0 && font_outline_color.a > 0) {
text_buf->draw_outline(get_canvas_item(), Vector2(size.width - width, 0), outline_size, font_outline_color);
}
text_buf->draw(get_canvas_item(), Vector2(size.width - width, 0), color);
} else {
if (outline_size > 0 && font_outline_color.a > 0) {
text_buf->draw_outline(get_canvas_item(), Vector2(0, 0), outline_size, font_outline_color);
}
text_buf->draw(get_canvas_item(), Vector2(0, 0), color);
}
if (do_underline) {
int underline_spacing = theme_cache.underline_spacing + text_buf->get_line_underline_position();
int y = text_buf->get_line_ascent() + underline_spacing;
int underline_thickness = MAX(1, text_buf->get_line_underline_thickness());
if (is_layout_rtl()) {
draw_line(Vector2(size.width - width, y), Vector2(size.width, y), color, underline_thickness);
} else {
draw_line(Vector2(0, y), Vector2(width, y), color, underline_thickness);
}
}
} break;
}
}
void LinkButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_text", "text"), &LinkButton::set_text);
ClassDB::bind_method(D_METHOD("get_text"), &LinkButton::get_text);
ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &LinkButton::set_text_direction);
ClassDB::bind_method(D_METHOD("get_text_direction"), &LinkButton::get_text_direction);
ClassDB::bind_method(D_METHOD("set_language", "language"), &LinkButton::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &LinkButton::get_language);
ClassDB::bind_method(D_METHOD("set_uri", "uri"), &LinkButton::set_uri);
ClassDB::bind_method(D_METHOD("get_uri"), &LinkButton::get_uri);
ClassDB::bind_method(D_METHOD("set_underline_mode", "underline_mode"), &LinkButton::set_underline_mode);
ClassDB::bind_method(D_METHOD("get_underline_mode"), &LinkButton::get_underline_mode);
ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &LinkButton::set_structured_text_bidi_override);
ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override"), &LinkButton::get_structured_text_bidi_override);
ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &LinkButton::set_structured_text_bidi_override_options);
ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &LinkButton::get_structured_text_bidi_override_options);
BIND_ENUM_CONSTANT(UNDERLINE_MODE_ALWAYS);
BIND_ENUM_CONSTANT(UNDERLINE_MODE_ON_HOVER);
BIND_ENUM_CONSTANT(UNDERLINE_MODE_NEVER);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "text"), "set_text", "get_text");
ADD_PROPERTY(PropertyInfo(Variant::INT, "underline", PROPERTY_HINT_ENUM, "Always,On Hover,Never"), "set_underline_mode", "get_underline_mode");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "uri"), "set_uri", "get_uri");
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");
ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options");
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, LinkButton, focus);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, LinkButton, font_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, LinkButton, font_focus_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, LinkButton, font_pressed_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, LinkButton, font_hover_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, LinkButton, font_hover_pressed_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, LinkButton, font_disabled_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, LinkButton, font);
BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, LinkButton, font_size);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, LinkButton, outline_size);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, LinkButton, font_outline_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, LinkButton, underline_spacing);
}
LinkButton::LinkButton(const String &p_text) {
text_buf.instantiate();
set_focus_mode(FOCUS_NONE);
set_default_cursor_shape(CURSOR_POINTING_HAND);
set_text(p_text);
}

View file

@ -0,0 +1,114 @@
/**************************************************************************/
/* link_button.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. */
/**************************************************************************/
#ifndef LINK_BUTTON_H
#define LINK_BUTTON_H
#include "scene/gui/base_button.h"
#include "scene/resources/text_line.h"
class LinkButton : public BaseButton {
GDCLASS(LinkButton, BaseButton);
public:
enum UnderlineMode {
UNDERLINE_MODE_ALWAYS,
UNDERLINE_MODE_ON_HOVER,
UNDERLINE_MODE_NEVER
};
private:
String text;
String xl_text;
Ref<TextLine> text_buf;
UnderlineMode underline_mode = UNDERLINE_MODE_ALWAYS;
String uri;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
struct ThemeCache {
Ref<StyleBox> focus;
Color font_color;
Color font_focus_color;
Color font_pressed_color;
Color font_hover_color;
Color font_hover_pressed_color;
Color font_disabled_color;
Ref<Font> font;
int font_size = 0;
int outline_size = 0;
Color font_outline_color;
int underline_spacing = 0;
} theme_cache;
void _shape();
protected:
virtual void pressed() override;
virtual Size2 get_minimum_size() const override;
void _notification(int p_what);
static void _bind_methods();
public:
void set_text(const String &p_text);
String get_text() const;
void set_uri(const String &p_uri);
String get_uri() const;
void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
void set_text_direction(TextDirection p_text_direction);
TextDirection get_text_direction() const;
void set_language(const String &p_language);
String get_language() const;
void set_underline_mode(UnderlineMode p_underline_mode);
UnderlineMode get_underline_mode() const;
Ref<Font> get_button_font() const;
LinkButton(const String &p_text = String());
};
VARIANT_ENUM_CAST(LinkButton::UnderlineMode);
#endif // LINK_BUTTON_H

View file

@ -0,0 +1,125 @@
/**************************************************************************/
/* margin_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 "margin_container.h"
#include "scene/theme/theme_db.h"
Size2 MarginContainer::get_minimum_size() const {
Size2 max;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE);
if (!c) {
continue;
}
Size2 s = c->get_combined_minimum_size();
if (s.width > max.width) {
max.width = s.width;
}
if (s.height > max.height) {
max.height = s.height;
}
}
max.width += (theme_cache.margin_left + theme_cache.margin_right);
max.height += (theme_cache.margin_top + theme_cache.margin_bottom);
return max;
}
Vector<int> MarginContainer::get_allowed_size_flags_horizontal() const {
Vector<int> flags;
flags.append(SIZE_FILL);
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
Vector<int> MarginContainer::get_allowed_size_flags_vertical() const {
Vector<int> flags;
flags.append(SIZE_FILL);
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
int MarginContainer::get_margin_size(Side p_side) const {
ERR_FAIL_INDEX_V((int)p_side, 4, 0);
switch (p_side) {
case SIDE_LEFT:
return theme_cache.margin_left;
case SIDE_RIGHT:
return theme_cache.margin_right;
case SIDE_TOP:
return theme_cache.margin_top;
case SIDE_BOTTOM:
return theme_cache.margin_bottom;
}
return 0;
}
void MarginContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_SORT_CHILDREN: {
Size2 s = get_size();
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
int w = s.width - theme_cache.margin_left - theme_cache.margin_right;
int h = s.height - theme_cache.margin_top - theme_cache.margin_bottom;
fit_child_in_rect(c, Rect2(theme_cache.margin_left, theme_cache.margin_top, w, h));
}
} break;
case NOTIFICATION_THEME_CHANGED: {
update_minimum_size();
} break;
}
}
void MarginContainer::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, MarginContainer, margin_left);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, MarginContainer, margin_top);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, MarginContainer, margin_right);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, MarginContainer, margin_bottom);
}
MarginContainer::MarginContainer() {
}

View file

@ -0,0 +1,61 @@
/**************************************************************************/
/* margin_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. */
/**************************************************************************/
#ifndef MARGIN_CONTAINER_H
#define MARGIN_CONTAINER_H
#include "scene/gui/container.h"
class MarginContainer : public Container {
GDCLASS(MarginContainer, Container);
struct ThemeCache {
int margin_left = 0;
int margin_top = 0;
int margin_right = 0;
int margin_bottom = 0;
} theme_cache;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
virtual Size2 get_minimum_size() const override;
virtual Vector<int> get_allowed_size_flags_horizontal() const override;
virtual Vector<int> get_allowed_size_flags_vertical() const override;
int get_margin_size(Side p_side) const;
MarginContainer();
};
#endif // MARGIN_CONTAINER_H

View file

@ -0,0 +1,931 @@
/**************************************************************************/
/* menu_bar.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 "menu_bar.h"
#include "core/os/keyboard.h"
#include "scene/main/window.h"
#include "scene/theme/theme_db.h"
void MenuBar::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (is_native_menu()) {
// Handled by OS.
return;
}
MutexLock lock(mutex);
if (p_event->is_action("ui_left", true) && p_event->is_pressed()) {
int new_sel = selected_menu;
int old_sel = (selected_menu < 0) ? 0 : selected_menu;
do {
new_sel--;
if (new_sel < 0) {
new_sel = menu_cache.size() - 1;
}
if (old_sel == new_sel) {
return;
}
} while (menu_cache[new_sel].hidden || menu_cache[new_sel].disabled);
if (selected_menu != new_sel) {
selected_menu = new_sel;
focused_menu = selected_menu;
if (active_menu >= 0) {
get_menu_popup(active_menu)->hide();
}
_open_popup(selected_menu, true);
}
return;
} else if (p_event->is_action("ui_right", true) && p_event->is_pressed()) {
int new_sel = selected_menu;
int old_sel = (selected_menu < 0) ? menu_cache.size() - 1 : selected_menu;
do {
new_sel++;
if (new_sel >= menu_cache.size()) {
new_sel = 0;
}
if (old_sel == new_sel) {
return;
}
} while (menu_cache[new_sel].hidden || menu_cache[new_sel].disabled);
if (selected_menu != new_sel) {
selected_menu = new_sel;
focused_menu = selected_menu;
if (active_menu >= 0) {
get_menu_popup(active_menu)->hide();
}
_open_popup(selected_menu, true);
}
return;
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
int old_sel = selected_menu;
focused_menu = _get_index_at_point(mm->get_position());
if (focused_menu >= 0) {
selected_menu = focused_menu;
}
if (selected_menu != old_sel) {
queue_redraw();
}
}
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
if (mb->is_pressed() && (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT)) {
int index = _get_index_at_point(mb->get_position());
if (index >= 0) {
_open_popup(index);
}
}
}
}
void MenuBar::_open_popup(int p_index, bool p_focus_item) {
ERR_FAIL_INDEX(p_index, menu_cache.size());
PopupMenu *pm = get_menu_popup(p_index);
if (pm->is_visible()) {
pm->hide();
return;
}
Rect2 item_rect = _get_menu_item_rect(p_index);
Point2 screen_pos = get_screen_position() + item_rect.position * get_viewport()->get_canvas_transform().get_scale();
Size2 screen_size = item_rect.size * get_viewport()->get_canvas_transform().get_scale();
active_menu = p_index;
pm->set_size(Size2(screen_size.x, 0));
screen_pos.y += screen_size.y;
if (is_layout_rtl()) {
screen_pos.x += screen_size.x - pm->get_size().width;
}
pm->set_position(screen_pos);
pm->popup();
if (p_focus_item) {
for (int i = 0; i < pm->get_item_count(); i++) {
if (!pm->is_item_disabled(i)) {
pm->set_focused_item(i);
break;
}
}
}
queue_redraw();
}
void MenuBar::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (disable_shortcuts) {
return;
}
if (p_event->is_pressed() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event) || Object::cast_to<InputEventShortcut>(*p_event))) {
if (!get_parent() || !is_visible_in_tree()) {
return;
}
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < popups.size(); i++) {
if (menu_cache[i].hidden || menu_cache[i].disabled) {
continue;
}
if (popups[i]->activate_item_by_event(p_event, false)) {
accept_event();
return;
}
}
}
}
void MenuBar::_popup_visibility_changed(bool p_visible) {
if (!p_visible) {
active_menu = -1;
focused_menu = -1;
set_process_internal(false);
queue_redraw();
return;
}
if (switch_on_hover) {
set_process_internal(true);
}
}
bool MenuBar::is_native_menu() const {
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
return false;
}
#endif
return (NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU) && prefer_native);
}
void MenuBar::bind_global_menu() {
#ifdef TOOLS_ENABLED
if (is_part_of_edited_scene()) {
return;
}
#endif
if (!NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU)) {
return;
}
if (!global_menu_tag.is_empty()) {
return; // Already bound.
}
NativeMenu *nmenu = NativeMenu::get_singleton();
RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID);
global_menu_tag = "__MenuBar#" + itos(get_instance_id());
int global_start_idx = -1;
int count = nmenu->get_item_count(main_menu);
String prev_tag;
for (int i = 0; i < count; i++) {
String tag = nmenu->get_item_tag(main_menu, i).operator String().get_slice("#", 1);
if (!tag.is_empty() && tag != prev_tag) {
if (i >= start_index) {
global_start_idx = i;
break;
}
}
prev_tag = tag;
}
if (global_start_idx == -1) {
global_start_idx = count;
}
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < menu_cache.size(); i++) {
RID submenu_rid = popups[i]->bind_global_menu();
if (!popups[i]->is_system_menu()) {
int index = nmenu->add_submenu_item(main_menu, menu_cache[i].name, submenu_rid, global_menu_tag + "#" + itos(i), global_start_idx + i);
menu_cache.write[i].submenu_rid = submenu_rid;
nmenu->set_item_hidden(main_menu, index, menu_cache[i].hidden);
nmenu->set_item_disabled(main_menu, index, menu_cache[i].disabled);
nmenu->set_item_tooltip(main_menu, index, menu_cache[i].tooltip);
} else {
menu_cache.write[i].submenu_rid = RID();
}
}
}
void MenuBar::unbind_global_menu() {
if (global_menu_tag.is_empty()) {
return;
}
NativeMenu *nmenu = NativeMenu::get_singleton();
RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID);
Vector<PopupMenu *> popups = _get_popups();
for (int i = menu_cache.size() - 1; i >= 0; i--) {
if (!popups[i]->is_system_menu()) {
if (menu_cache[i].submenu_rid.is_valid()) {
int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[i].submenu_rid);
if (item_idx >= 0) {
nmenu->remove_item(main_menu, item_idx);
}
}
popups[i]->unbind_global_menu();
menu_cache.write[i].submenu_rid = RID();
}
}
global_menu_tag = String();
}
void MenuBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
if (get_menu_count() > 0) {
_refresh_menu_names();
}
if (is_native_menu()) {
bind_global_menu();
}
} break;
case NOTIFICATION_EXIT_TREE: {
unbind_global_menu();
} break;
case NOTIFICATION_MOUSE_EXIT: {
focused_menu = -1;
selected_menu = -1;
queue_redraw();
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
NativeMenu *nmenu = NativeMenu::get_singleton();
bool is_global = !global_menu_tag.is_empty();
RID main_menu = is_global ? nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID) : RID();
for (int i = 0; i < menu_cache.size(); i++) {
shape(menu_cache.write[i]);
if (is_global && menu_cache[i].submenu_rid.is_valid()) {
int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[i].submenu_rid);
if (item_idx >= 0) {
nmenu->set_item_text(main_menu, item_idx, atr(menu_cache[i].name));
}
}
}
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_THEME_CHANGED: {
for (int i = 0; i < menu_cache.size(); i++) {
shape(menu_cache.write[i]);
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (is_native_menu()) {
if (is_visible_in_tree()) {
bind_global_menu();
} else {
unbind_global_menu();
}
}
} break;
case NOTIFICATION_DRAW: {
if (is_native_menu()) {
return;
}
for (int i = 0; i < menu_cache.size(); i++) {
_draw_menu_item(i);
}
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
MutexLock lock(mutex);
if (is_native_menu()) {
// Handled by OS.
return;
}
Vector2 pos = get_local_mouse_position();
if (pos == old_mouse_pos) {
return;
}
old_mouse_pos = pos;
int index = _get_index_at_point(pos);
if (index >= 0 && index != active_menu) {
selected_menu = index;
focused_menu = selected_menu;
if (active_menu >= 0) {
get_menu_popup(active_menu)->hide();
}
_open_popup(index);
}
} break;
}
}
int MenuBar::_get_index_at_point(const Point2 &p_point) const {
Ref<StyleBox> style = theme_cache.normal;
int offset = 0;
Point2 point = p_point;
if (is_layout_rtl()) {
point.x = get_size().x - point.x;
}
for (int i = 0; i < menu_cache.size(); i++) {
if (menu_cache[i].hidden) {
continue;
}
Size2 size = menu_cache[i].text_buf->get_size() + style->get_minimum_size();
if (point.x > offset && point.x < offset + size.x) {
if (point.y > 0 && point.y < size.y) {
return i;
}
}
offset += size.x + theme_cache.h_separation;
}
return -1;
}
Rect2 MenuBar::_get_menu_item_rect(int p_index) const {
ERR_FAIL_INDEX_V(p_index, menu_cache.size(), Rect2());
Ref<StyleBox> style = theme_cache.normal;
int offset = 0;
for (int i = 0; i < p_index; i++) {
if (menu_cache[i].hidden) {
continue;
}
Size2 size = menu_cache[i].text_buf->get_size() + style->get_minimum_size();
offset += size.x + theme_cache.h_separation;
}
Size2 size = menu_cache[p_index].text_buf->get_size() + style->get_minimum_size();
if (is_layout_rtl()) {
return Rect2(Point2(get_size().x - offset - size.x, 0), size);
} else {
return Rect2(Point2(offset, 0), size);
}
}
void MenuBar::_draw_menu_item(int p_index) {
ERR_FAIL_INDEX(p_index, menu_cache.size());
RID ci = get_canvas_item();
bool hovered = (focused_menu == p_index);
bool pressed = (active_menu == p_index);
bool rtl = is_layout_rtl();
if (menu_cache[p_index].hidden) {
return;
}
Color color;
Ref<StyleBox> style;
Rect2 item_rect = _get_menu_item_rect(p_index);
if (menu_cache[p_index].disabled) {
if (rtl && has_theme_stylebox(SNAME("disabled_mirrored"))) {
style = theme_cache.disabled_mirrored;
} else {
style = theme_cache.disabled;
}
if (!flat) {
style->draw(ci, item_rect);
}
color = theme_cache.font_disabled_color;
} else if (hovered && pressed && has_theme_stylebox("hover_pressed")) {
if (rtl && has_theme_stylebox(SNAME("hover_pressed_mirrored"))) {
style = theme_cache.hover_pressed_mirrored;
} else {
style = theme_cache.hover_pressed;
}
if (!flat) {
style->draw(ci, item_rect);
}
if (has_theme_color(SNAME("font_hover_pressed_color"))) {
color = theme_cache.font_hover_pressed_color;
}
} else if (pressed) {
if (rtl && has_theme_stylebox(SNAME("pressed_mirrored"))) {
style = theme_cache.pressed_mirrored;
} else {
style = theme_cache.pressed;
}
if (!flat) {
style->draw(ci, item_rect);
}
if (has_theme_color(SNAME("font_pressed_color"))) {
color = theme_cache.font_pressed_color;
} else {
color = theme_cache.font_color;
}
} else if (hovered) {
if (rtl && has_theme_stylebox(SNAME("hover_mirrored"))) {
style = theme_cache.hover_mirrored;
} else {
style = theme_cache.hover;
}
if (!flat) {
style->draw(ci, item_rect);
}
color = theme_cache.font_hover_color;
} else {
if (rtl && has_theme_stylebox(SNAME("normal_mirrored"))) {
style = theme_cache.normal_mirrored;
} else {
style = theme_cache.normal;
}
if (!flat) {
style->draw(ci, item_rect);
}
// Focus colors only take precedence over normal state.
if (has_focus()) {
color = theme_cache.font_focus_color;
} else {
color = theme_cache.font_color;
}
}
Point2 text_ofs = item_rect.position + Point2(style->get_margin(SIDE_LEFT), style->get_margin(SIDE_TOP));
Color font_outline_color = theme_cache.font_outline_color;
int outline_size = theme_cache.outline_size;
if (outline_size > 0 && font_outline_color.a > 0) {
menu_cache[p_index].text_buf->draw_outline(ci, text_ofs, outline_size, font_outline_color);
}
menu_cache[p_index].text_buf->draw(ci, text_ofs, color);
}
void MenuBar::shape(Menu &p_menu) {
p_menu.text_buf->clear();
if (text_direction == Control::TEXT_DIRECTION_INHERITED) {
p_menu.text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
} else {
p_menu.text_buf->set_direction((TextServer::Direction)text_direction);
}
p_menu.text_buf->add_string(atr(p_menu.name), theme_cache.font, theme_cache.font_size, language);
}
void MenuBar::_refresh_menu_names() {
NativeMenu *nmenu = NativeMenu::get_singleton();
bool is_global = !global_menu_tag.is_empty();
RID main_menu = is_global ? nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID) : RID();
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < popups.size(); i++) {
if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) {
menu_cache.write[i].name = popups[i]->get_name();
shape(menu_cache.write[i]);
if (is_global && menu_cache[i].submenu_rid.is_valid()) {
int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[i].submenu_rid);
if (item_idx >= 0) {
nmenu->set_item_text(main_menu, item_idx, atr(menu_cache[i].name));
}
}
}
}
}
Vector<PopupMenu *> MenuBar::_get_popups() const {
Vector<PopupMenu *> popups;
for (int i = 0; i < get_child_count(); i++) {
PopupMenu *pm = Object::cast_to<PopupMenu>(get_child(i));
if (!pm) {
continue;
}
popups.push_back(pm);
}
return popups;
}
int MenuBar::get_menu_idx_from_control(PopupMenu *p_child) const {
ERR_FAIL_NULL_V(p_child, -1);
ERR_FAIL_COND_V(p_child->get_parent() != this, -1);
Vector<PopupMenu *> popups = _get_popups();
for (int i = 0; i < popups.size(); i++) {
if (popups[i] == p_child) {
return i;
}
}
return -1;
}
void MenuBar::add_child_notify(Node *p_child) {
Control::add_child_notify(p_child);
PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
if (!pm) {
return;
}
Menu menu = Menu(p_child->get_name());
shape(menu);
menu_cache.push_back(menu);
p_child->connect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
p_child->connect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(true));
p_child->connect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(false));
if (!global_menu_tag.is_empty()) {
NativeMenu *nmenu = NativeMenu::get_singleton();
RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID);
RID submenu_rid = pm->bind_global_menu();
if (!pm->is_system_menu()) {
nmenu->add_submenu_item(main_menu, atr(menu.name), submenu_rid, global_menu_tag + "#" + itos(menu_cache.size() - 1), _find_global_start_index() + menu_cache.size() - 1);
menu_cache.write[menu_cache.size() - 1].submenu_rid = submenu_rid;
}
}
update_minimum_size();
}
void MenuBar::move_child_notify(Node *p_child) {
Control::move_child_notify(p_child);
PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
if (!pm) {
return;
}
int old_idx = -1;
String menu_name = String(pm->get_meta("_menu_name", pm->get_name()));
// Find the previous menu index of the control.
for (int i = 0; i < get_menu_count(); i++) {
if (get_menu_title(i) == menu_name) {
old_idx = i;
break;
}
}
Menu menu = menu_cache[old_idx];
menu_cache.remove_at(old_idx);
int new_idx = get_menu_idx_from_control(pm);
menu_cache.insert(new_idx, menu);
if (!global_menu_tag.is_empty()) {
if (!pm->is_system_menu()) {
NativeMenu *nmenu = NativeMenu::get_singleton();
RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID);
int global_start = _find_global_start_index();
if (menu.submenu_rid.is_valid()) {
int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu.submenu_rid);
if (item_idx >= 0) {
nmenu->remove_item(main_menu, item_idx);
}
}
if (new_idx != -1) {
nmenu->add_submenu_item(main_menu, atr(menu.name), menu.submenu_rid, global_menu_tag + "#" + itos(new_idx), global_start + new_idx);
}
}
}
}
void MenuBar::remove_child_notify(Node *p_child) {
Control::remove_child_notify(p_child);
PopupMenu *pm = Object::cast_to<PopupMenu>(p_child);
if (!pm) {
return;
}
int idx = get_menu_idx_from_control(pm);
if (!global_menu_tag.is_empty()) {
if (!pm->is_system_menu()) {
if (menu_cache[idx].submenu_rid.is_valid()) {
NativeMenu *nmenu = NativeMenu::get_singleton();
RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID);
int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[idx].submenu_rid);
if (item_idx >= 0) {
nmenu->remove_item(main_menu, item_idx);
}
}
pm->unbind_global_menu();
}
}
menu_cache.remove_at(idx);
p_child->remove_meta("_menu_name");
p_child->remove_meta("_menu_tooltip");
p_child->disconnect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names));
p_child->disconnect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed));
p_child->disconnect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed));
update_minimum_size();
}
void MenuBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuBar::set_switch_on_hover);
ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuBar::is_switch_on_hover);
ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &MenuBar::set_disable_shortcuts);
ClassDB::bind_method(D_METHOD("set_prefer_global_menu", "enabled"), &MenuBar::set_prefer_global_menu);
ClassDB::bind_method(D_METHOD("is_prefer_global_menu"), &MenuBar::is_prefer_global_menu);
ClassDB::bind_method(D_METHOD("is_native_menu"), &MenuBar::is_native_menu);
ClassDB::bind_method(D_METHOD("get_menu_count"), &MenuBar::get_menu_count);
ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &MenuBar::set_text_direction);
ClassDB::bind_method(D_METHOD("get_text_direction"), &MenuBar::get_text_direction);
ClassDB::bind_method(D_METHOD("set_language", "language"), &MenuBar::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &MenuBar::get_language);
ClassDB::bind_method(D_METHOD("set_flat", "enabled"), &MenuBar::set_flat);
ClassDB::bind_method(D_METHOD("is_flat"), &MenuBar::is_flat);
ClassDB::bind_method(D_METHOD("set_start_index", "enabled"), &MenuBar::set_start_index);
ClassDB::bind_method(D_METHOD("get_start_index"), &MenuBar::get_start_index);
ClassDB::bind_method(D_METHOD("set_menu_title", "menu", "title"), &MenuBar::set_menu_title);
ClassDB::bind_method(D_METHOD("get_menu_title", "menu"), &MenuBar::get_menu_title);
ClassDB::bind_method(D_METHOD("set_menu_tooltip", "menu", "tooltip"), &MenuBar::set_menu_tooltip);
ClassDB::bind_method(D_METHOD("get_menu_tooltip", "menu"), &MenuBar::get_menu_tooltip);
ClassDB::bind_method(D_METHOD("set_menu_disabled", "menu", "disabled"), &MenuBar::set_menu_disabled);
ClassDB::bind_method(D_METHOD("is_menu_disabled", "menu"), &MenuBar::is_menu_disabled);
ClassDB::bind_method(D_METHOD("set_menu_hidden", "menu", "hidden"), &MenuBar::set_menu_hidden);
ClassDB::bind_method(D_METHOD("is_menu_hidden", "menu"), &MenuBar::is_menu_hidden);
// TODO: Properly handle popups when advanced GUI is disabled.
#ifndef ADVANCED_GUI_DISABLED
ClassDB::bind_method(D_METHOD("get_menu_popup", "menu"), &MenuBar::get_menu_popup);
#endif // ADVANCED_GUI_DISABLED
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
ADD_PROPERTY(PropertyInfo(Variant::INT, "start_index"), "set_start_index", "get_start_index");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_hover"), "set_switch_on_hover", "is_switch_on_hover");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "prefer_global_menu"), "set_prefer_global_menu", "is_prefer_global_menu");
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_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, MenuBar, normal);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, MenuBar, normal_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, MenuBar, disabled);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, MenuBar, disabled_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, MenuBar, pressed);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, MenuBar, pressed_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, MenuBar, hover);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, MenuBar, hover_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, MenuBar, hover_pressed);
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, MenuBar, hover_pressed_mirrored);
BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, MenuBar, font);
BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, MenuBar, font_size);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, MenuBar, outline_size);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, MenuBar, font_outline_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, MenuBar, font_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, MenuBar, font_disabled_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, MenuBar, font_pressed_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, MenuBar, font_hover_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, MenuBar, font_hover_pressed_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, MenuBar, font_focus_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, MenuBar, h_separation);
}
void MenuBar::set_switch_on_hover(bool p_enabled) {
switch_on_hover = p_enabled;
}
bool MenuBar::is_switch_on_hover() {
return switch_on_hover;
}
void MenuBar::set_disable_shortcuts(bool p_disabled) {
disable_shortcuts = p_disabled;
}
void MenuBar::set_text_direction(Control::TextDirection p_text_direction) {
ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
if (text_direction != p_text_direction) {
text_direction = p_text_direction;
update_minimum_size();
queue_redraw();
}
}
Control::TextDirection MenuBar::get_text_direction() const {
return text_direction;
}
void MenuBar::set_language(const String &p_language) {
if (language != p_language) {
language = p_language;
update_minimum_size();
queue_redraw();
}
}
String MenuBar::get_language() const {
return language;
}
void MenuBar::set_flat(bool p_enabled) {
if (flat != p_enabled) {
flat = p_enabled;
queue_redraw();
}
}
bool MenuBar::is_flat() const {
return flat;
}
void MenuBar::set_start_index(int p_index) {
if (start_index != p_index) {
start_index = p_index;
if (!global_menu_tag.is_empty()) {
unbind_global_menu();
bind_global_menu();
}
}
}
int MenuBar::get_start_index() const {
return start_index;
}
void MenuBar::set_prefer_global_menu(bool p_enabled) {
if (prefer_native != p_enabled) {
prefer_native = p_enabled;
if (prefer_native) {
bind_global_menu();
} else {
unbind_global_menu();
}
}
}
bool MenuBar::is_prefer_global_menu() const {
return prefer_native;
}
Size2 MenuBar::get_minimum_size() const {
if (is_native_menu()) {
return Size2();
}
Ref<StyleBox> style = theme_cache.normal;
Vector2 size;
for (int i = 0; i < menu_cache.size(); i++) {
if (menu_cache[i].hidden) {
continue;
}
Size2 sz = menu_cache[i].text_buf->get_size() + style->get_minimum_size();
size.y = MAX(size.y, sz.y);
size.x += sz.x;
}
if (menu_cache.size() > 1) {
size.x += theme_cache.h_separation * (menu_cache.size() - 1);
}
return size;
}
int MenuBar::get_menu_count() const {
return menu_cache.size();
}
void MenuBar::set_menu_title(int p_menu, const String &p_title) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
PopupMenu *pm = get_menu_popup(p_menu);
if (p_title == pm->get_name()) {
pm->remove_meta("_menu_name");
} else {
pm->set_meta("_menu_name", p_title);
}
menu_cache.write[p_menu].name = p_title;
shape(menu_cache.write[p_menu]);
if (!global_menu_tag.is_empty() && menu_cache[p_menu].submenu_rid.is_valid()) {
NativeMenu *nmenu = NativeMenu::get_singleton();
RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID);
int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[p_menu].submenu_rid);
if (item_idx >= 0) {
nmenu->set_item_text(main_menu, item_idx, atr(menu_cache[p_menu].name));
}
}
update_minimum_size();
}
String MenuBar::get_menu_title(int p_menu) const {
ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), String());
return menu_cache[p_menu].name;
}
void MenuBar::set_menu_tooltip(int p_menu, const String &p_tooltip) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
PopupMenu *pm = get_menu_popup(p_menu);
pm->set_meta("_menu_tooltip", p_tooltip);
menu_cache.write[p_menu].tooltip = p_tooltip;
if (!global_menu_tag.is_empty() && menu_cache[p_menu].submenu_rid.is_valid()) {
NativeMenu *nmenu = NativeMenu::get_singleton();
RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID);
int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[p_menu].submenu_rid);
if (item_idx >= 0) {
nmenu->set_item_tooltip(main_menu, item_idx, p_tooltip);
}
}
}
String MenuBar::get_menu_tooltip(int p_menu) const {
ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), String());
return menu_cache[p_menu].tooltip;
}
void MenuBar::set_menu_disabled(int p_menu, bool p_disabled) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].disabled = p_disabled;
if (!global_menu_tag.is_empty() && menu_cache[p_menu].submenu_rid.is_valid()) {
NativeMenu *nmenu = NativeMenu::get_singleton();
RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID);
int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[p_menu].submenu_rid);
if (item_idx >= 0) {
nmenu->set_item_disabled(main_menu, item_idx, p_disabled);
}
}
}
bool MenuBar::is_menu_disabled(int p_menu) const {
ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), false);
return menu_cache[p_menu].disabled;
}
void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) {
ERR_FAIL_INDEX(p_menu, menu_cache.size());
menu_cache.write[p_menu].hidden = p_hidden;
if (!global_menu_tag.is_empty() && menu_cache[p_menu].submenu_rid.is_valid()) {
NativeMenu *nmenu = NativeMenu::get_singleton();
RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID);
int item_idx = nmenu->find_item_index_with_submenu(main_menu, menu_cache[p_menu].submenu_rid);
if (item_idx >= 0) {
nmenu->set_item_hidden(main_menu, item_idx, p_hidden);
}
}
update_minimum_size();
}
bool MenuBar::is_menu_hidden(int p_menu) const {
ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), false);
return menu_cache[p_menu].hidden;
}
PopupMenu *MenuBar::get_menu_popup(int p_idx) const {
Vector<PopupMenu *> controls = _get_popups();
if (p_idx >= 0 && p_idx < controls.size()) {
return controls[p_idx];
} else {
return nullptr;
}
}
String MenuBar::get_tooltip(const Point2 &p_pos) const {
int index = _get_index_at_point(p_pos);
if (index >= 0 && index < menu_cache.size()) {
return menu_cache[index].tooltip;
} else {
return String();
}
}
MenuBar::MenuBar() {
set_process_shortcut_input(true);
}
MenuBar::~MenuBar() {
}

198
engine/scene/gui/menu_bar.h Normal file
View file

@ -0,0 +1,198 @@
/**************************************************************************/
/* menu_bar.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. */
/**************************************************************************/
#ifndef MENU_BAR_H
#define MENU_BAR_H
#include "scene/gui/button.h"
#include "scene/gui/popup_menu.h"
class MenuBar : public Control {
GDCLASS(MenuBar, Control);
Mutex mutex;
bool switch_on_hover = true;
bool disable_shortcuts = false;
bool prefer_native = true;
bool flat = false;
int start_index = -1;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
struct Menu {
String name;
String tooltip;
Ref<TextLine> text_buf;
bool hidden = false;
bool disabled = false;
RID submenu_rid;
Menu(const String &p_name) {
name = p_name;
text_buf.instantiate();
}
Menu() {
text_buf.instantiate();
}
};
Vector<Menu> menu_cache;
int focused_menu = -1;
int selected_menu = -1;
int active_menu = -1;
Vector2i old_mouse_pos;
ObjectID shortcut_context;
struct ThemeCache {
Ref<StyleBox> normal;
Ref<StyleBox> normal_mirrored;
Ref<StyleBox> disabled;
Ref<StyleBox> disabled_mirrored;
Ref<StyleBox> pressed;
Ref<StyleBox> pressed_mirrored;
Ref<StyleBox> hover;
Ref<StyleBox> hover_mirrored;
Ref<StyleBox> hover_pressed;
Ref<StyleBox> hover_pressed_mirrored;
Ref<Font> font;
int font_size = 0;
int outline_size = 0;
Color font_outline_color;
Color font_color;
Color font_disabled_color;
Color font_pressed_color;
Color font_hover_color;
Color font_hover_pressed_color;
Color font_focus_color;
int h_separation = 0;
} theme_cache;
int _get_index_at_point(const Point2 &p_point) const;
Rect2 _get_menu_item_rect(int p_index) const;
void _draw_menu_item(int p_index);
void shape(Menu &p_menu);
void _refresh_menu_names();
Vector<PopupMenu *> _get_popups() const;
int get_menu_idx_from_control(PopupMenu *p_child) const;
void _open_popup(int p_index, bool p_focus_item = false);
void _popup_visibility_changed(bool p_visible);
String global_menu_tag;
int _find_global_start_index() {
if (global_menu_tag.is_empty()) {
return -1;
}
NativeMenu *nmenu = NativeMenu::get_singleton();
if (!nmenu) {
return -1;
}
RID main_menu = nmenu->get_system_menu(NativeMenu::MAIN_MENU_ID);
int count = nmenu->get_item_count(main_menu);
for (int i = 0; i < count; i++) {
if (nmenu->get_item_tag(main_menu, i).operator String().begins_with(global_menu_tag)) {
return i;
}
}
return -1;
}
void bind_global_menu();
void unbind_global_menu();
protected:
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
void _notification(int p_what);
virtual void add_child_notify(Node *p_child) override;
virtual void move_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
static void _bind_methods();
public:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
void set_switch_on_hover(bool p_enabled);
bool is_switch_on_hover();
void set_disable_shortcuts(bool p_disabled);
void set_prefer_global_menu(bool p_enabled);
bool is_prefer_global_menu() const;
bool is_native_menu() const;
virtual Size2 get_minimum_size() const override;
int get_menu_count() const;
void set_text_direction(TextDirection p_text_direction);
TextDirection get_text_direction() const;
void set_language(const String &p_language);
String get_language() const;
void set_start_index(int p_index);
int get_start_index() const;
void set_flat(bool p_enabled);
bool is_flat() const;
void set_menu_title(int p_menu, const String &p_title);
String get_menu_title(int p_menu) const;
void set_menu_tooltip(int p_menu, const String &p_tooltip);
String get_menu_tooltip(int p_menu) const;
void set_menu_disabled(int p_menu, bool p_disabled);
bool is_menu_disabled(int p_menu) const;
void set_menu_hidden(int p_menu, bool p_hidden);
bool is_menu_hidden(int p_menu) const;
PopupMenu *get_menu_popup(int p_menu) const;
virtual String get_tooltip(const Point2 &p_pos) const override;
MenuBar();
~MenuBar();
};
#endif // MENU_BAR_H

View file

@ -0,0 +1,230 @@
/**************************************************************************/
/* menu_button.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 "menu_button.h"
#include "core/os/keyboard.h"
#include "scene/main/window.h"
void MenuButton::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (disable_shortcuts) {
return;
}
if (p_event->is_pressed() && !is_disabled() && is_visible_in_tree() && popup->activate_item_by_event(p_event, false)) {
accept_event();
return;
}
Button::shortcut_input(p_event);
}
void MenuButton::_popup_visibility_changed(bool p_visible) {
set_pressed(p_visible);
if (!p_visible) {
set_process_internal(false);
return;
}
if (switch_on_hover) {
set_process_internal(true);
}
}
void MenuButton::pressed() {
if (popup->is_visible()) {
popup->hide();
return;
}
show_popup();
}
PopupMenu *MenuButton::get_popup() const {
return popup;
}
void MenuButton::show_popup() {
if (!get_viewport()) {
return;
}
emit_signal(SNAME("about_to_popup"));
Rect2 rect = get_screen_rect();
rect.position.y += rect.size.height;
rect.size.height = 0;
popup->set_size(rect.size);
if (is_layout_rtl()) {
rect.position.x += rect.size.width - popup->get_size().width;
}
popup->set_position(rect.position);
// If not triggered by the mouse, start the popup with its first enabled item focused.
if (!_was_pressed_by_mouse()) {
for (int i = 0; i < popup->get_item_count(); i++) {
if (!popup->is_item_disabled(i)) {
popup->set_focused_item(i);
break;
}
}
}
popup->popup();
}
void MenuButton::set_switch_on_hover(bool p_enabled) {
switch_on_hover = p_enabled;
}
bool MenuButton::is_switch_on_hover() {
return switch_on_hover;
}
void MenuButton::set_item_count(int p_count) {
ERR_FAIL_COND(p_count < 0);
if (popup->get_item_count() == p_count) {
return;
}
popup->set_item_count(p_count);
notify_property_list_changed();
}
int MenuButton::get_item_count() const {
return popup->get_item_count();
}
void MenuButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
popup->set_layout_direction((Window::LayoutDirection)get_layout_direction());
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible_in_tree()) {
popup->hide();
}
} break;
case NOTIFICATION_INTERNAL_PROCESS: {
MenuButton *menu_btn_other = Object::cast_to<MenuButton>(get_viewport()->gui_find_control(get_viewport()->get_mouse_position()));
if (menu_btn_other && menu_btn_other != this && menu_btn_other->is_switch_on_hover() && !menu_btn_other->is_disabled() &&
(get_parent()->is_ancestor_of(menu_btn_other) || menu_btn_other->get_parent()->is_ancestor_of(popup))) {
popup->hide();
menu_btn_other->pressed();
// As the popup wasn't triggered by a mouse click, the item focus needs to be removed manually.
menu_btn_other->get_popup()->set_focused_item(-1);
}
} break;
}
}
bool MenuButton::_set(const StringName &p_name, const Variant &p_value) {
const String sname = p_name;
if (property_helper.is_property_valid(sname)) {
bool valid;
popup->set(sname.trim_prefix("popup/"), p_value, &valid);
return valid;
}
return false;
}
bool MenuButton::_get(const StringName &p_name, Variant &r_ret) const {
const String sname = p_name;
if (property_helper.is_property_valid(sname)) {
bool valid;
r_ret = popup->get(sname.trim_prefix("popup/"), &valid);
return valid;
}
return false;
}
void MenuButton::_bind_methods() {
// TODO: Properly handle popups when advanced GUI is disabled.
#ifndef ADVANCED_GUI_DISABLED
ClassDB::bind_method(D_METHOD("get_popup"), &MenuButton::get_popup);
#endif // ADVANCED_GUI_DISABLED
ClassDB::bind_method(D_METHOD("show_popup"), &MenuButton::show_popup);
ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuButton::set_switch_on_hover);
ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuButton::is_switch_on_hover);
ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &MenuButton::set_disable_shortcuts);
ClassDB::bind_method(D_METHOD("set_item_count", "count"), &MenuButton::set_item_count);
ClassDB::bind_method(D_METHOD("get_item_count"), &MenuButton::get_item_count);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_hover"), "set_switch_on_hover", "is_switch_on_hover");
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_");
ADD_SIGNAL(MethodInfo("about_to_popup"));
PopupMenu::Item defaults(true);
base_property_helper.set_prefix("popup/item_");
base_property_helper.set_array_length_getter(&MenuButton::get_item_count);
base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text);
base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon);
base_property_helper.register_property(PropertyInfo(Variant::INT, "checkable", PROPERTY_HINT_ENUM, "No,As Checkbox,As Radio Button"), defaults.checkable_type);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "checked"), defaults.checked);
base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator);
PropertyListHelper::register_base_helper(&base_property_helper);
}
void MenuButton::set_disable_shortcuts(bool p_disabled) {
disable_shortcuts = p_disabled;
}
MenuButton::MenuButton(const String &p_text) :
Button(p_text) {
set_flat(true);
set_toggle_mode(true);
set_disable_shortcuts(false);
set_process_shortcut_input(true);
set_focus_mode(FOCUS_NONE);
set_action_mode(ACTION_MODE_BUTTON_PRESS);
popup = memnew(PopupMenu);
popup->hide();
add_child(popup, false, INTERNAL_MODE_FRONT);
popup->connect("about_to_popup", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(true));
popup->connect("popup_hide", callable_mp(this, &MenuButton::_popup_visibility_changed).bind(false));
property_helper.setup_for_instance(base_property_helper, this);
}
MenuButton::~MenuButton() {
}

View file

@ -0,0 +1,78 @@
/**************************************************************************/
/* menu_button.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. */
/**************************************************************************/
#ifndef MENU_BUTTON_H
#define MENU_BUTTON_H
#include "scene/gui/button.h"
#include "scene/gui/popup_menu.h"
#include "scene/property_list_helper.h"
class MenuButton : public Button {
GDCLASS(MenuButton, Button);
bool clicked = false;
bool switch_on_hover = false;
bool disable_shortcuts = false;
PopupMenu *popup = nullptr;
static inline PropertyListHelper base_property_helper;
PropertyListHelper property_helper;
void _popup_visibility_changed(bool p_visible);
protected:
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list); }
bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
static void _bind_methods();
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
public:
virtual void pressed() override;
PopupMenu *get_popup() const;
void show_popup();
void set_switch_on_hover(bool p_enabled);
bool is_switch_on_hover();
void set_disable_shortcuts(bool p_disabled);
void set_item_count(int p_count);
int get_item_count() const;
MenuButton(const String &p_text = String());
~MenuButton();
};
#endif // MENU_BUTTON_H

View file

@ -0,0 +1,195 @@
/**************************************************************************/
/* nine_patch_rect.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 "nine_patch_rect.h"
#include "servers/rendering_server.h"
void NinePatchRect::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
if (texture.is_null()) {
return;
}
Rect2 rect = Rect2(Point2(), get_size());
Rect2 src_rect = region_rect;
texture->get_rect_region(rect, src_rect, rect, src_rect);
RID ci = get_canvas_item();
RS::get_singleton()->canvas_item_add_nine_patch(ci, rect, src_rect, texture->get_rid(), Vector2(margin[SIDE_LEFT], margin[SIDE_TOP]), Vector2(margin[SIDE_RIGHT], margin[SIDE_BOTTOM]), RS::NinePatchAxisMode(axis_h), RS::NinePatchAxisMode(axis_v), draw_center);
} break;
}
}
Size2 NinePatchRect::get_minimum_size() const {
return Size2(margin[SIDE_LEFT] + margin[SIDE_RIGHT], margin[SIDE_TOP] + margin[SIDE_BOTTOM]);
}
void NinePatchRect::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_texture", "texture"), &NinePatchRect::set_texture);
ClassDB::bind_method(D_METHOD("get_texture"), &NinePatchRect::get_texture);
ClassDB::bind_method(D_METHOD("set_patch_margin", "margin", "value"), &NinePatchRect::set_patch_margin);
ClassDB::bind_method(D_METHOD("get_patch_margin", "margin"), &NinePatchRect::get_patch_margin);
ClassDB::bind_method(D_METHOD("set_region_rect", "rect"), &NinePatchRect::set_region_rect);
ClassDB::bind_method(D_METHOD("get_region_rect"), &NinePatchRect::get_region_rect);
ClassDB::bind_method(D_METHOD("set_draw_center", "draw_center"), &NinePatchRect::set_draw_center);
ClassDB::bind_method(D_METHOD("is_draw_center_enabled"), &NinePatchRect::is_draw_center_enabled);
ClassDB::bind_method(D_METHOD("set_h_axis_stretch_mode", "mode"), &NinePatchRect::set_h_axis_stretch_mode);
ClassDB::bind_method(D_METHOD("get_h_axis_stretch_mode"), &NinePatchRect::get_h_axis_stretch_mode);
ClassDB::bind_method(D_METHOD("set_v_axis_stretch_mode", "mode"), &NinePatchRect::set_v_axis_stretch_mode);
ClassDB::bind_method(D_METHOD("get_v_axis_stretch_mode"), &NinePatchRect::get_v_axis_stretch_mode);
ADD_SIGNAL(MethodInfo("texture_changed"));
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draw_center"), "set_draw_center", "is_draw_center_enabled");
ADD_PROPERTY(PropertyInfo(Variant::RECT2, "region_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_region_rect", "get_region_rect");
ADD_GROUP("Patch Margin", "patch_margin_");
ADD_PROPERTYI(PropertyInfo(Variant::INT, "patch_margin_left", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_patch_margin", "get_patch_margin", SIDE_LEFT);
ADD_PROPERTYI(PropertyInfo(Variant::INT, "patch_margin_top", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_patch_margin", "get_patch_margin", SIDE_TOP);
ADD_PROPERTYI(PropertyInfo(Variant::INT, "patch_margin_right", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_patch_margin", "get_patch_margin", SIDE_RIGHT);
ADD_PROPERTYI(PropertyInfo(Variant::INT, "patch_margin_bottom", PROPERTY_HINT_RANGE, "0,16384,1,suffix:px"), "set_patch_margin", "get_patch_margin", SIDE_BOTTOM);
ADD_GROUP("Axis Stretch", "axis_stretch_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "axis_stretch_horizontal", PROPERTY_HINT_ENUM, "Stretch,Tile,Tile Fit"), "set_h_axis_stretch_mode", "get_h_axis_stretch_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "axis_stretch_vertical", PROPERTY_HINT_ENUM, "Stretch,Tile,Tile Fit"), "set_v_axis_stretch_mode", "get_v_axis_stretch_mode");
BIND_ENUM_CONSTANT(AXIS_STRETCH_MODE_STRETCH);
BIND_ENUM_CONSTANT(AXIS_STRETCH_MODE_TILE);
BIND_ENUM_CONSTANT(AXIS_STRETCH_MODE_TILE_FIT);
}
void NinePatchRect::_texture_changed() {
queue_redraw();
update_minimum_size();
}
void NinePatchRect::set_texture(const Ref<Texture2D> &p_tex) {
if (texture == p_tex) {
return;
}
if (texture.is_valid()) {
texture->disconnect_changed(callable_mp(this, &NinePatchRect::_texture_changed));
}
texture = p_tex;
if (texture.is_valid()) {
texture->connect_changed(callable_mp(this, &NinePatchRect::_texture_changed));
}
queue_redraw();
update_minimum_size();
emit_signal(SceneStringName(texture_changed));
}
Ref<Texture2D> NinePatchRect::get_texture() const {
return texture;
}
void NinePatchRect::set_patch_margin(Side p_side, int p_size) {
ERR_FAIL_INDEX((int)p_side, 4);
if (margin[p_side] == p_size) {
return;
}
margin[p_side] = p_size;
queue_redraw();
update_minimum_size();
}
int NinePatchRect::get_patch_margin(Side p_side) const {
ERR_FAIL_INDEX_V((int)p_side, 4, 0);
return margin[p_side];
}
void NinePatchRect::set_region_rect(const Rect2 &p_region_rect) {
if (region_rect == p_region_rect) {
return;
}
region_rect = p_region_rect;
item_rect_changed();
}
Rect2 NinePatchRect::get_region_rect() const {
return region_rect;
}
void NinePatchRect::set_draw_center(bool p_enabled) {
if (draw_center == p_enabled) {
return;
}
draw_center = p_enabled;
queue_redraw();
}
bool NinePatchRect::is_draw_center_enabled() const {
return draw_center;
}
void NinePatchRect::set_h_axis_stretch_mode(AxisStretchMode p_mode) {
if (axis_h == p_mode) {
return;
}
axis_h = p_mode;
queue_redraw();
}
NinePatchRect::AxisStretchMode NinePatchRect::get_h_axis_stretch_mode() const {
return axis_h;
}
void NinePatchRect::set_v_axis_stretch_mode(AxisStretchMode p_mode) {
if (axis_v == p_mode) {
return;
}
axis_v = p_mode;
queue_redraw();
}
NinePatchRect::AxisStretchMode NinePatchRect::get_v_axis_stretch_mode() const {
return axis_v;
}
NinePatchRect::NinePatchRect() {
set_mouse_filter(MOUSE_FILTER_IGNORE);
}
NinePatchRect::~NinePatchRect() {
}

View file

@ -0,0 +1,86 @@
/**************************************************************************/
/* nine_patch_rect.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. */
/**************************************************************************/
#ifndef NINE_PATCH_RECT_H
#define NINE_PATCH_RECT_H
#include "scene/gui/control.h"
class NinePatchRect : public Control {
GDCLASS(NinePatchRect, Control);
public:
enum AxisStretchMode {
AXIS_STRETCH_MODE_STRETCH,
AXIS_STRETCH_MODE_TILE,
AXIS_STRETCH_MODE_TILE_FIT,
};
bool draw_center = true;
int margin[4] = {};
Rect2 region_rect;
Ref<Texture2D> texture;
AxisStretchMode axis_h = AXIS_STRETCH_MODE_STRETCH;
AxisStretchMode axis_v = AXIS_STRETCH_MODE_STRETCH;
void _texture_changed();
protected:
void _notification(int p_what);
virtual Size2 get_minimum_size() const override;
static void _bind_methods();
public:
void set_texture(const Ref<Texture2D> &p_tex);
Ref<Texture2D> get_texture() const;
void set_patch_margin(Side p_side, int p_size);
int get_patch_margin(Side p_side) const;
void set_region_rect(const Rect2 &p_region_rect);
Rect2 get_region_rect() const;
void set_draw_center(bool p_enabled);
bool is_draw_center_enabled() const;
void set_h_axis_stretch_mode(AxisStretchMode p_mode);
AxisStretchMode get_h_axis_stretch_mode() const;
void set_v_axis_stretch_mode(AxisStretchMode p_mode);
AxisStretchMode get_v_axis_stretch_mode() const;
NinePatchRect();
~NinePatchRect();
};
VARIANT_ENUM_CAST(NinePatchRect::AxisStretchMode)
#endif // NINE_PATCH_RECT_H

View file

@ -0,0 +1,605 @@
/**************************************************************************/
/* option_button.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 "option_button.h"
#include "core/os/keyboard.h"
#include "core/string/print_string.h"
#include "scene/theme/theme_db.h"
static const int NONE_SELECTED = -1;
void OptionButton::shortcut_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (disable_shortcuts) {
return;
}
if (p_event->is_pressed() && !p_event->is_echo() && !is_disabled() && is_visible_in_tree() && popup->activate_item_by_event(p_event, false)) {
accept_event();
return;
}
Button::shortcut_input(p_event);
}
Size2 OptionButton::get_minimum_size() const {
Size2 minsize;
if (fit_to_longest_item) {
minsize = _cached_size;
} else {
minsize = Button::get_minimum_size();
}
if (has_theme_icon(SNAME("arrow"))) {
const Size2 padding = _get_largest_stylebox_size();
const Size2 arrow_size = theme_cache.arrow_icon->get_size();
Size2 content_size = minsize - padding;
content_size.width += arrow_size.width + MAX(0, theme_cache.h_separation);
content_size.height = MAX(content_size.height, arrow_size.height);
minsize = content_size + padding;
}
return minsize;
}
void OptionButton::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POSTINITIALIZE: {
_refresh_size_cache();
if (has_theme_icon(SNAME("arrow"))) {
if (is_layout_rtl()) {
_set_internal_margin(SIDE_LEFT, theme_cache.arrow_icon->get_width());
} else {
_set_internal_margin(SIDE_RIGHT, theme_cache.arrow_icon->get_width());
}
}
} break;
case NOTIFICATION_DRAW: {
if (!has_theme_icon(SNAME("arrow"))) {
return;
}
RID ci = get_canvas_item();
Color clr = Color(1, 1, 1);
if (theme_cache.modulate_arrow) {
switch (get_draw_mode()) {
case DRAW_PRESSED:
clr = theme_cache.font_pressed_color;
break;
case DRAW_HOVER:
clr = theme_cache.font_hover_color;
break;
case DRAW_HOVER_PRESSED:
clr = theme_cache.font_hover_pressed_color;
break;
case DRAW_DISABLED:
clr = theme_cache.font_disabled_color;
break;
default:
if (has_focus()) {
clr = theme_cache.font_focus_color;
} else {
clr = theme_cache.font_color;
}
}
}
Size2 size = get_size();
Point2 ofs;
if (is_layout_rtl()) {
ofs = Point2(theme_cache.arrow_margin, int(Math::abs((size.height - theme_cache.arrow_icon->get_height()) / 2)));
} else {
ofs = Point2(size.width - theme_cache.arrow_icon->get_width() - theme_cache.arrow_margin, int(Math::abs((size.height - theme_cache.arrow_icon->get_height()) / 2)));
}
theme_cache.arrow_icon->draw(ci, ofs, clr);
} break;
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
popup->set_layout_direction((Window::LayoutDirection)get_layout_direction());
[[fallthrough]];
}
case NOTIFICATION_THEME_CHANGED: {
if (has_theme_icon(SNAME("arrow"))) {
if (is_layout_rtl()) {
_set_internal_margin(SIDE_LEFT, theme_cache.arrow_icon->get_width());
_set_internal_margin(SIDE_RIGHT, 0.f);
} else {
_set_internal_margin(SIDE_LEFT, 0.f);
_set_internal_margin(SIDE_RIGHT, theme_cache.arrow_icon->get_width());
}
}
_refresh_size_cache();
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible_in_tree()) {
popup->hide();
}
} break;
}
}
bool OptionButton::_set(const StringName &p_name, const Variant &p_value) {
int index;
const String sname = p_name;
if (property_helper.is_property_valid(sname, &index)) {
bool valid;
popup->set(sname.trim_prefix("popup/"), p_value, &valid);
if (index == current) {
// Force refreshing currently displayed item.
current = NONE_SELECTED;
_select(index, false);
}
const String property = sname.get_slice("/", 2);
if (property == "text" || property == "icon") {
_queue_update_size_cache();
}
return valid;
}
return false;
}
void OptionButton::_focused(int p_which) {
emit_signal(SNAME("item_focused"), p_which);
}
void OptionButton::_selected(int p_which) {
_select(p_which, true);
}
void OptionButton::pressed() {
if (popup->is_visible()) {
popup->hide();
return;
}
show_popup();
}
void OptionButton::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id) {
bool first_selectable = !has_selectable_items();
popup->add_icon_radio_check_item(p_icon, p_label, p_id);
if (first_selectable) {
select(get_item_count() - 1);
}
_queue_update_size_cache();
}
void OptionButton::add_item(const String &p_label, int p_id) {
bool first_selectable = !has_selectable_items();
popup->add_radio_check_item(p_label, p_id);
if (first_selectable) {
select(get_item_count() - 1);
}
_queue_update_size_cache();
}
void OptionButton::set_item_text(int p_idx, const String &p_text) {
popup->set_item_text(p_idx, p_text);
if (current == p_idx) {
set_text(p_text);
}
_queue_update_size_cache();
}
void OptionButton::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
popup->set_item_icon(p_idx, p_icon);
if (current == p_idx) {
set_icon(p_icon);
}
_queue_update_size_cache();
}
void OptionButton::set_item_id(int p_idx, int p_id) {
popup->set_item_id(p_idx, p_id);
}
void OptionButton::set_item_metadata(int p_idx, const Variant &p_metadata) {
popup->set_item_metadata(p_idx, p_metadata);
}
void OptionButton::set_item_tooltip(int p_idx, const String &p_tooltip) {
popup->set_item_tooltip(p_idx, p_tooltip);
}
void OptionButton::set_item_disabled(int p_idx, bool p_disabled) {
popup->set_item_disabled(p_idx, p_disabled);
}
String OptionButton::get_item_text(int p_idx) const {
return popup->get_item_text(p_idx);
}
Ref<Texture2D> OptionButton::get_item_icon(int p_idx) const {
return popup->get_item_icon(p_idx);
}
int OptionButton::get_item_id(int p_idx) const {
if (p_idx == NONE_SELECTED) {
return NONE_SELECTED;
}
return popup->get_item_id(p_idx);
}
int OptionButton::get_item_index(int p_id) const {
return popup->get_item_index(p_id);
}
Variant OptionButton::get_item_metadata(int p_idx) const {
return popup->get_item_metadata(p_idx);
}
String OptionButton::get_item_tooltip(int p_idx) const {
return popup->get_item_tooltip(p_idx);
}
bool OptionButton::is_item_disabled(int p_idx) const {
return popup->is_item_disabled(p_idx);
}
bool OptionButton::is_item_separator(int p_idx) const {
return popup->is_item_separator(p_idx);
}
void OptionButton::set_item_count(int p_count) {
ERR_FAIL_COND(p_count < 0);
int count_old = get_item_count();
if (p_count == count_old) {
return;
}
popup->set_item_count(p_count);
if (p_count > count_old) {
for (int i = count_old; i < p_count; i++) {
popup->set_item_as_radio_checkable(i, true);
}
}
if (!initialized) {
if (queued_current != current) {
current = queued_current;
}
initialized = true;
}
_refresh_size_cache();
notify_property_list_changed();
}
bool OptionButton::has_selectable_items() const {
for (int i = 0; i < get_item_count(); i++) {
if (!is_item_disabled(i) && !is_item_separator(i)) {
return true;
}
}
return false;
}
int OptionButton::get_selectable_item(bool p_from_last) const {
if (!p_from_last) {
for (int i = 0; i < get_item_count(); i++) {
if (!is_item_disabled(i) && !is_item_separator(i)) {
return i;
}
}
} else {
for (int i = get_item_count() - 1; i >= 0; i--) {
if (!is_item_disabled(i) && !is_item_separator(i)) {
return i;
}
}
}
return -1;
}
int OptionButton::get_item_count() const {
return popup->get_item_count();
}
void OptionButton::set_fit_to_longest_item(bool p_fit) {
if (p_fit == fit_to_longest_item) {
return;
}
fit_to_longest_item = p_fit;
_refresh_size_cache();
}
bool OptionButton::is_fit_to_longest_item() const {
return fit_to_longest_item;
}
void OptionButton::set_allow_reselect(bool p_allow) {
allow_reselect = p_allow;
}
bool OptionButton::get_allow_reselect() const {
return allow_reselect;
}
void OptionButton::add_separator(const String &p_text) {
popup->add_separator(p_text);
}
void OptionButton::clear() {
popup->clear();
set_text("");
current = NONE_SELECTED;
_refresh_size_cache();
}
void OptionButton::_select(int p_which, bool p_emit) {
if (p_which == current && !allow_reselect) {
return;
}
if (p_which == NONE_SELECTED) {
for (int i = 0; i < popup->get_item_count(); i++) {
popup->set_item_checked(i, false);
}
current = NONE_SELECTED;
set_text("");
set_icon(nullptr);
} else {
ERR_FAIL_INDEX(p_which, popup->get_item_count());
for (int i = 0; i < popup->get_item_count(); i++) {
popup->set_item_checked(i, i == p_which);
}
current = p_which;
set_text(popup->get_item_text(current));
set_icon(popup->get_item_icon(current));
}
if (is_inside_tree() && p_emit) {
emit_signal(SceneStringName(item_selected), current);
}
}
void OptionButton::_select_int(int p_which) {
if (p_which < NONE_SELECTED) {
return;
}
if (p_which >= popup->get_item_count()) {
if (!initialized) {
queued_current = p_which;
}
return;
}
_select(p_which, false);
}
void OptionButton::_refresh_size_cache() {
cache_refresh_pending = false;
if (fit_to_longest_item) {
_cached_size = theme_cache.normal->get_minimum_size();
for (int i = 0; i < get_item_count(); i++) {
_cached_size = _cached_size.max(get_minimum_size_for_text_and_icon(popup->get_item_xl_text(i), get_item_icon(i)));
}
}
update_minimum_size();
}
void OptionButton::_queue_update_size_cache() {
if (cache_refresh_pending) {
return;
}
cache_refresh_pending = true;
callable_mp(this, &OptionButton::_refresh_size_cache).call_deferred();
}
void OptionButton::select(int p_idx) {
_select(p_idx, false);
}
int OptionButton::get_selected() const {
return current;
}
int OptionButton::get_selected_id() const {
return get_item_id(current);
}
Variant OptionButton::get_selected_metadata() const {
int idx = get_selected();
if (idx < 0) {
return Variant();
}
return get_item_metadata(current);
}
void OptionButton::remove_item(int p_idx) {
popup->remove_item(p_idx);
if (current == p_idx) {
_select(NONE_SELECTED);
}
_queue_update_size_cache();
}
PopupMenu *OptionButton::get_popup() const {
return popup;
}
void OptionButton::show_popup() {
if (!get_viewport()) {
return;
}
Rect2 rect = get_screen_rect();
rect.position.y += rect.size.height;
rect.size.height = 0;
popup->set_position(rect.position);
popup->set_size(rect.size);
// If not triggered by the mouse, start the popup with the checked item (or the first enabled one) focused.
if (current != NONE_SELECTED && !popup->is_item_disabled(current)) {
if (!_was_pressed_by_mouse()) {
popup->set_focused_item(current);
} else {
popup->scroll_to_item(current);
}
} else {
for (int i = 0; i < popup->get_item_count(); i++) {
if (!popup->is_item_disabled(i)) {
if (!_was_pressed_by_mouse()) {
popup->set_focused_item(i);
} else {
popup->scroll_to_item(i);
}
break;
}
}
}
popup->popup();
}
void OptionButton::_validate_property(PropertyInfo &p_property) const {
if (p_property.name == "text" || p_property.name == "icon") {
p_property.usage = PROPERTY_USAGE_NONE;
}
}
void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_item", "label", "id"), &OptionButton::add_item, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id"), &OptionButton::add_icon_item, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("set_item_text", "idx", "text"), &OptionButton::set_item_text);
ClassDB::bind_method(D_METHOD("set_item_icon", "idx", "texture"), &OptionButton::set_item_icon);
ClassDB::bind_method(D_METHOD("set_item_disabled", "idx", "disabled"), &OptionButton::set_item_disabled);
ClassDB::bind_method(D_METHOD("set_item_id", "idx", "id"), &OptionButton::set_item_id);
ClassDB::bind_method(D_METHOD("set_item_metadata", "idx", "metadata"), &OptionButton::set_item_metadata);
ClassDB::bind_method(D_METHOD("set_item_tooltip", "idx", "tooltip"), &OptionButton::set_item_tooltip);
ClassDB::bind_method(D_METHOD("get_item_text", "idx"), &OptionButton::get_item_text);
ClassDB::bind_method(D_METHOD("get_item_icon", "idx"), &OptionButton::get_item_icon);
ClassDB::bind_method(D_METHOD("get_item_id", "idx"), &OptionButton::get_item_id);
ClassDB::bind_method(D_METHOD("get_item_index", "id"), &OptionButton::get_item_index);
ClassDB::bind_method(D_METHOD("get_item_metadata", "idx"), &OptionButton::get_item_metadata);
ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &OptionButton::get_item_tooltip);
ClassDB::bind_method(D_METHOD("is_item_disabled", "idx"), &OptionButton::is_item_disabled);
ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &OptionButton::is_item_separator);
ClassDB::bind_method(D_METHOD("add_separator", "text"), &OptionButton::add_separator, DEFVAL(String()));
ClassDB::bind_method(D_METHOD("clear"), &OptionButton::clear);
ClassDB::bind_method(D_METHOD("select", "idx"), &OptionButton::select);
ClassDB::bind_method(D_METHOD("get_selected"), &OptionButton::get_selected);
ClassDB::bind_method(D_METHOD("get_selected_id"), &OptionButton::get_selected_id);
ClassDB::bind_method(D_METHOD("get_selected_metadata"), &OptionButton::get_selected_metadata);
ClassDB::bind_method(D_METHOD("remove_item", "idx"), &OptionButton::remove_item);
ClassDB::bind_method(D_METHOD("_select_int", "idx"), &OptionButton::_select_int);
ClassDB::bind_method(D_METHOD("get_popup"), &OptionButton::get_popup);
ClassDB::bind_method(D_METHOD("show_popup"), &OptionButton::show_popup);
ClassDB::bind_method(D_METHOD("set_item_count", "count"), &OptionButton::set_item_count);
ClassDB::bind_method(D_METHOD("get_item_count"), &OptionButton::get_item_count);
ClassDB::bind_method(D_METHOD("has_selectable_items"), &OptionButton::has_selectable_items);
ClassDB::bind_method(D_METHOD("get_selectable_item", "from_last"), &OptionButton::get_selectable_item, DEFVAL(false));
ClassDB::bind_method(D_METHOD("set_fit_to_longest_item", "fit"), &OptionButton::set_fit_to_longest_item);
ClassDB::bind_method(D_METHOD("is_fit_to_longest_item"), &OptionButton::is_fit_to_longest_item);
ClassDB::bind_method(D_METHOD("set_allow_reselect", "allow"), &OptionButton::set_allow_reselect);
ClassDB::bind_method(D_METHOD("get_allow_reselect"), &OptionButton::get_allow_reselect);
ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &OptionButton::set_disable_shortcuts);
ADD_PROPERTY(PropertyInfo(Variant::INT, "selected"), "_select_int", "get_selected");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fit_to_longest_item"), "set_fit_to_longest_item", "is_fit_to_longest_item");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_reselect"), "set_allow_reselect", "get_allow_reselect");
ADD_ARRAY_COUNT("Items", "item_count", "set_item_count", "get_item_count", "popup/item_");
ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "index")));
ADD_SIGNAL(MethodInfo("item_focused", PropertyInfo(Variant::INT, "index")));
BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, OptionButton, normal);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, OptionButton, font_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, OptionButton, font_focus_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, OptionButton, font_pressed_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, OptionButton, font_hover_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, OptionButton, font_hover_pressed_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, OptionButton, font_disabled_color);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, h_separation);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, OptionButton, arrow_icon, "arrow");
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, arrow_margin);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, OptionButton, modulate_arrow);
PopupMenu::Item defaults(true);
base_property_helper.set_prefix("popup/item_");
base_property_helper.set_array_length_getter(&OptionButton::get_item_count);
base_property_helper.register_property(PropertyInfo(Variant::STRING, "text"), defaults.text, &OptionButton::_dummy_setter, &OptionButton::get_item_text);
base_property_helper.register_property(PropertyInfo(Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), defaults.icon, &OptionButton::_dummy_setter, &OptionButton::get_item_icon);
base_property_helper.register_property(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0,10,1,or_greater"), defaults.id, &OptionButton::_dummy_setter, &OptionButton::get_item_id);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "disabled"), defaults.disabled, &OptionButton::_dummy_setter, &OptionButton::is_item_disabled);
base_property_helper.register_property(PropertyInfo(Variant::BOOL, "separator"), defaults.separator, &OptionButton::_dummy_setter, &OptionButton::is_item_separator);
PropertyListHelper::register_base_helper(&base_property_helper);
}
void OptionButton::set_disable_shortcuts(bool p_disabled) {
disable_shortcuts = p_disabled;
}
OptionButton::OptionButton(const String &p_text) :
Button(p_text) {
set_toggle_mode(true);
set_process_shortcut_input(true);
set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT);
set_action_mode(ACTION_MODE_BUTTON_PRESS);
popup = memnew(PopupMenu);
popup->hide();
add_child(popup, false, INTERNAL_MODE_FRONT);
popup->connect("index_pressed", callable_mp(this, &OptionButton::_selected));
popup->connect("id_focused", callable_mp(this, &OptionButton::_focused));
popup->connect("popup_hide", callable_mp((BaseButton *)this, &BaseButton::set_pressed).bind(false));
property_helper.setup_for_instance(base_property_helper, this);
}
OptionButton::~OptionButton() {
}

View file

@ -0,0 +1,150 @@
/**************************************************************************/
/* option_button.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. */
/**************************************************************************/
#ifndef OPTION_BUTTON_H
#define OPTION_BUTTON_H
#include "scene/gui/button.h"
#include "scene/gui/popup_menu.h"
#include "scene/property_list_helper.h"
class OptionButton : public Button {
GDCLASS(OptionButton, Button);
bool disable_shortcuts = false;
PopupMenu *popup = nullptr;
int current = -1;
bool fit_to_longest_item = true;
Vector2 _cached_size;
bool cache_refresh_pending = false;
bool allow_reselect = false;
bool initialized = false;
int queued_current = -1;
struct ThemeCache {
Ref<StyleBox> normal;
Color font_color;
Color font_focus_color;
Color font_pressed_color;
Color font_hover_color;
Color font_hover_pressed_color;
Color font_disabled_color;
int h_separation = 0;
Ref<Texture2D> arrow_icon;
int arrow_margin = 0;
int modulate_arrow = 0;
} theme_cache;
static inline PropertyListHelper base_property_helper;
PropertyListHelper property_helper;
void _focused(int p_which);
void _selected(int p_which);
void _select(int p_which, bool p_emit = false);
void _select_int(int p_which);
void _refresh_size_cache();
void _dummy_setter() {} // Stub for PropertyListHelper (_set() doesn't use it).
virtual void pressed() override;
protected:
Size2 get_minimum_size() const override;
virtual void _queue_update_size_cache() override;
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); }
void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list); }
bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
public:
// ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes,
// this value should be updated to reflect the new size.
static const int ITEM_PROPERTY_SIZE = 5;
void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1);
void add_item(const String &p_label, int p_id = -1);
void set_item_text(int p_idx, const String &p_text);
void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon);
void set_item_id(int p_idx, int p_id);
void set_item_metadata(int p_idx, const Variant &p_metadata);
void set_item_disabled(int p_idx, bool p_disabled);
void set_item_tooltip(int p_idx, const String &p_tooltip);
String get_item_text(int p_idx) const;
Ref<Texture2D> get_item_icon(int p_idx) const;
int get_item_id(int p_idx) const;
int get_item_index(int p_id) const;
Variant get_item_metadata(int p_idx) const;
bool is_item_disabled(int p_idx) const;
bool is_item_separator(int p_idx) const;
String get_item_tooltip(int p_idx) const;
bool has_selectable_items() const;
int get_selectable_item(bool p_from_last = false) const;
void set_item_count(int p_count);
int get_item_count() const;
void set_fit_to_longest_item(bool p_fit);
bool is_fit_to_longest_item() const;
void set_allow_reselect(bool p_allow);
bool get_allow_reselect() const;
void add_separator(const String &p_text = "");
void clear();
void select(int p_idx);
int get_selected() const;
int get_selected_id() const;
Variant get_selected_metadata() const;
void remove_item(int p_idx);
PopupMenu *get_popup() const;
void show_popup();
void set_disable_shortcuts(bool p_disabled);
OptionButton(const String &p_text = String());
~OptionButton();
};
#endif // OPTION_BUTTON_H

View file

@ -0,0 +1,50 @@
/**************************************************************************/
/* panel.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 "panel.h"
#include "scene/theme/theme_db.h"
void Panel::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
RID ci = get_canvas_item();
theme_cache.panel_style->draw(ci, Rect2(Point2(), get_size()));
} break;
}
}
void Panel::_bind_methods() {
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Panel, panel_style, "panel");
}
Panel::Panel() {
// Has visible stylebox, so stop by default.
set_mouse_filter(MOUSE_FILTER_STOP);
}

51
engine/scene/gui/panel.h Normal file
View file

@ -0,0 +1,51 @@
/**************************************************************************/
/* panel.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. */
/**************************************************************************/
#ifndef PANEL_H
#define PANEL_H
#include "scene/gui/control.h"
class Panel : public Control {
GDCLASS(Panel, Control);
struct ThemeCache {
Ref<StyleBox> panel_style;
} theme_cache;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
Panel();
};
#endif // PANEL_H

View file

@ -0,0 +1,105 @@
/**************************************************************************/
/* panel_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 "panel_container.h"
#include "scene/theme/theme_db.h"
Size2 PanelContainer::get_minimum_size() const {
Size2 ms;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE);
if (!c) {
continue;
}
Size2 minsize = c->get_combined_minimum_size();
ms = ms.max(minsize);
}
if (theme_cache.panel_style.is_valid()) {
ms += theme_cache.panel_style->get_minimum_size();
}
return ms;
}
Vector<int> PanelContainer::get_allowed_size_flags_horizontal() const {
Vector<int> flags;
flags.append(SIZE_FILL);
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
Vector<int> PanelContainer::get_allowed_size_flags_vertical() const {
Vector<int> flags;
flags.append(SIZE_FILL);
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
void PanelContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
RID ci = get_canvas_item();
theme_cache.panel_style->draw(ci, Rect2(Point2(), get_size()));
} break;
case NOTIFICATION_SORT_CHILDREN: {
Size2 size = get_size();
Point2 ofs;
if (theme_cache.panel_style.is_valid()) {
size -= theme_cache.panel_style->get_minimum_size();
ofs += theme_cache.panel_style->get_offset();
}
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
fit_child_in_rect(c, Rect2(ofs, size));
}
} break;
}
}
void PanelContainer::_bind_methods() {
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, PanelContainer, panel_style, "panel");
}
PanelContainer::PanelContainer() {
// Has visible stylebox, so stop by default.
set_mouse_filter(MOUSE_FILTER_STOP);
}

View file

@ -0,0 +1,56 @@
/**************************************************************************/
/* panel_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. */
/**************************************************************************/
#ifndef PANEL_CONTAINER_H
#define PANEL_CONTAINER_H
#include "scene/gui/container.h"
class PanelContainer : public Container {
GDCLASS(PanelContainer, Container);
struct ThemeCache {
Ref<StyleBox> panel_style;
} theme_cache;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
virtual Size2 get_minimum_size() const override;
virtual Vector<int> get_allowed_size_flags_horizontal() const override;
virtual Vector<int> get_allowed_size_flags_vertical() const override;
PanelContainer();
};
#endif // PANEL_CONTAINER_H

291
engine/scene/gui/popup.cpp Normal file
View file

@ -0,0 +1,291 @@
/**************************************************************************/
/* popup.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 "popup.h"
#include "core/config/engine.h"
#include "core/os/keyboard.h"
#include "scene/gui/panel.h"
#include "scene/theme/theme_db.h"
void Popup::_input_from_window(const Ref<InputEvent> &p_event) {
if (get_flag(FLAG_POPUP) && p_event->is_action_pressed(SNAME("ui_cancel"), false, true)) {
hide_reason = HIDE_REASON_CANCELED; // ESC pressed, mark as canceled unconditionally.
_close_pressed();
}
Window::_input_from_window(p_event);
}
void Popup::_initialize_visible_parents() {
if (is_embedded()) {
visible_parents.clear();
Window *parent_window = this;
while (parent_window) {
parent_window = parent_window->get_parent_visible_window();
if (parent_window) {
visible_parents.push_back(parent_window);
parent_window->connect(SceneStringName(focus_entered), callable_mp(this, &Popup::_parent_focused));
parent_window->connect(SceneStringName(tree_exited), callable_mp(this, &Popup::_deinitialize_visible_parents));
}
}
}
}
void Popup::_deinitialize_visible_parents() {
if (is_embedded()) {
for (Window *parent_window : visible_parents) {
parent_window->disconnect(SceneStringName(focus_entered), callable_mp(this, &Popup::_parent_focused));
parent_window->disconnect(SceneStringName(tree_exited), callable_mp(this, &Popup::_deinitialize_visible_parents));
}
visible_parents.clear();
}
}
void Popup::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_in_edited_scene_root()) {
if (is_visible()) {
_initialize_visible_parents();
} else {
_deinitialize_visible_parents();
if (hide_reason == HIDE_REASON_NONE) {
hide_reason = HIDE_REASON_CANCELED;
}
emit_signal(SNAME("popup_hide"));
popped_up = false;
}
}
} break;
case NOTIFICATION_WM_WINDOW_FOCUS_IN: {
if (!is_in_edited_scene_root()) {
if (has_focus()) {
popped_up = true;
hide_reason = HIDE_REASON_NONE;
}
}
} break;
case NOTIFICATION_UNPARENTED:
case NOTIFICATION_EXIT_TREE: {
if (!is_in_edited_scene_root()) {
_deinitialize_visible_parents();
}
} break;
case NOTIFICATION_WM_CLOSE_REQUEST: {
if (!is_in_edited_scene_root()) {
if (hide_reason == HIDE_REASON_NONE) {
hide_reason = HIDE_REASON_UNFOCUSED;
}
_close_pressed();
}
} break;
case NOTIFICATION_APPLICATION_FOCUS_OUT: {
if (!is_in_edited_scene_root() && get_flag(FLAG_POPUP)) {
if (hide_reason == HIDE_REASON_NONE) {
hide_reason = HIDE_REASON_UNFOCUSED;
}
_close_pressed();
}
} break;
}
}
void Popup::_parent_focused() {
if (popped_up && get_flag(FLAG_POPUP)) {
if (hide_reason == HIDE_REASON_NONE) {
hide_reason = HIDE_REASON_UNFOCUSED;
}
_close_pressed();
}
}
void Popup::_close_pressed() {
popped_up = false;
_deinitialize_visible_parents();
callable_mp((Window *)this, &Window::hide).call_deferred();
}
void Popup::_post_popup() {
Window::_post_popup();
popped_up = true;
}
void Popup::_validate_property(PropertyInfo &p_property) const {
if (
p_property.name == "transient" ||
p_property.name == "exclusive" ||
p_property.name == "popup_window" ||
p_property.name == "unfocusable") {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
Rect2i Popup::_popup_adjust_rect() const {
ERR_FAIL_COND_V(!is_inside_tree(), Rect2());
Rect2i parent_rect = get_usable_parent_rect();
if (parent_rect == Rect2i()) {
return Rect2i();
}
Rect2i current(get_position(), get_size());
if (current.position.x + current.size.x > parent_rect.position.x + parent_rect.size.x) {
current.position.x = parent_rect.position.x + parent_rect.size.x - current.size.x;
}
if (current.position.x < parent_rect.position.x) {
current.position.x = parent_rect.position.x;
}
if (current.position.y + current.size.y > parent_rect.position.y + parent_rect.size.y) {
current.position.y = parent_rect.position.y + parent_rect.size.y - current.size.y;
}
if (current.position.y < parent_rect.position.y) {
current.position.y = parent_rect.position.y;
}
if (current.size.y > parent_rect.size.y) {
current.size.y = parent_rect.size.y;
}
if (current.size.x > parent_rect.size.x) {
current.size.x = parent_rect.size.x;
}
// Early out if max size not set.
Size2i popup_max_size = get_max_size();
if (popup_max_size <= Size2()) {
return current;
}
if (current.size.x > popup_max_size.x) {
current.size.x = popup_max_size.x;
}
if (current.size.y > popup_max_size.y) {
current.size.y = popup_max_size.y;
}
return current;
}
void Popup::_bind_methods() {
ADD_SIGNAL(MethodInfo("popup_hide"));
}
Popup::Popup() {
set_wrap_controls(true);
set_visible(false);
set_transient(true);
set_flag(FLAG_BORDERLESS, true);
set_flag(FLAG_RESIZE_DISABLED, true);
set_flag(FLAG_POPUP, true);
}
Popup::~Popup() {
}
Size2 PopupPanel::_get_contents_minimum_size() const {
Size2 ms;
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
if (!c || c == panel) {
continue;
}
if (c->is_set_as_top_level()) {
continue;
}
Size2 cms = c->get_combined_minimum_size();
ms = cms.max(ms);
}
return ms + theme_cache.panel_style->get_minimum_size();
}
void PopupPanel::_update_child_rects() {
Vector2 cpos(theme_cache.panel_style->get_offset());
Vector2 panel_size = Vector2(get_size()) / get_content_scale_factor();
Vector2 csize = panel_size - theme_cache.panel_style->get_minimum_size();
for (int i = 0; i < get_child_count(); i++) {
Control *c = Object::cast_to<Control>(get_child(i));
if (!c) {
continue;
}
if (c->is_set_as_top_level()) {
continue;
}
if (c == panel) {
c->set_position(Vector2());
c->set_size(panel_size);
} else {
c->set_position(cpos);
c->set_size(csize);
}
}
}
void PopupPanel::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY:
case NOTIFICATION_THEME_CHANGED: {
panel->add_theme_style_override(SceneStringName(panel), theme_cache.panel_style);
_update_child_rects();
} break;
case NOTIFICATION_WM_SIZE_CHANGED: {
_update_child_rects();
} break;
}
}
void PopupPanel::_bind_methods() {
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, PopupPanel, panel_style, "panel");
}
PopupPanel::PopupPanel() {
panel = memnew(Panel);
add_child(panel, false, INTERNAL_MODE_FRONT);
}

100
engine/scene/gui/popup.h Normal file
View file

@ -0,0 +1,100 @@
/**************************************************************************/
/* popup.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. */
/**************************************************************************/
#ifndef POPUP_H
#define POPUP_H
#include "scene/main/window.h"
#include "core/templates/local_vector.h"
class Panel;
class Popup : public Window {
GDCLASS(Popup, Window);
LocalVector<Window *> visible_parents;
bool popped_up = false;
public:
enum HideReason {
HIDE_REASON_NONE,
HIDE_REASON_CANCELED, // E.g., because of rupture of UI flow (app unfocused). Includes closed programmatically.
HIDE_REASON_UNFOCUSED, // E.g., user clicked outside.
};
private:
HideReason hide_reason = HIDE_REASON_NONE;
void _initialize_visible_parents();
void _deinitialize_visible_parents();
protected:
void _close_pressed();
virtual Rect2i _popup_adjust_rect() const override;
virtual void _input_from_window(const Ref<InputEvent> &p_event) override;
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
virtual void _parent_focused();
virtual void _post_popup() override;
public:
HideReason get_hide_reason() const { return hide_reason; }
Popup();
~Popup();
};
class PopupPanel : public Popup {
GDCLASS(PopupPanel, Popup);
Panel *panel = nullptr;
struct ThemeCache {
Ref<StyleBox> panel_style;
} theme_cache;
protected:
void _update_child_rects();
void _notification(int p_what);
static void _bind_methods();
virtual Size2 _get_contents_minimum_size() const override;
public:
PopupPanel();
};
#endif // POPUP_H

View file

@ -0,0 +1,81 @@
/**************************************************************************/
/* popup_menu.compat.inc */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
void PopupMenu::_add_shortcut_bind_compat_36493(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
add_shortcut(p_shortcut, p_id, p_global, false);
}
void PopupMenu::_add_icon_shortcut_bind_compat_36493(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) {
add_icon_shortcut(p_icon, p_shortcut, p_id, p_global, false);
}
void PopupMenu::_clear_bind_compat_79965() {
clear(false);
}
void PopupMenu::_set_system_menu_root_compat_87452(const String &p_special) {
if (p_special == "_dock") {
set_system_menu(NativeMenu::DOCK_MENU_ID);
} else if (p_special == "_apple") {
set_system_menu(NativeMenu::APPLICATION_MENU_ID);
} else if (p_special == "_window") {
set_system_menu(NativeMenu::WINDOW_MENU_ID);
} else if (p_special == "_help") {
set_system_menu(NativeMenu::HELP_MENU_ID);
}
}
String PopupMenu::_get_system_menu_root_compat_87452() const {
switch (get_system_menu()) {
case NativeMenu::APPLICATION_MENU_ID:
return "_apple";
case NativeMenu::WINDOW_MENU_ID:
return "_window";
case NativeMenu::HELP_MENU_ID:
return "_help";
case NativeMenu::DOCK_MENU_ID:
return "_dock";
default:
return "";
}
}
void PopupMenu::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("add_shortcut", "shortcut", "id", "global"), &PopupMenu::_add_shortcut_bind_compat_36493, DEFVAL(-1), DEFVAL(false));
ClassDB::bind_compatibility_method(D_METHOD("add_icon_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::_add_icon_shortcut_bind_compat_36493, DEFVAL(-1), DEFVAL(false));
ClassDB::bind_compatibility_method(D_METHOD("clear"), &PopupMenu::_clear_bind_compat_79965);
ClassDB::bind_compatibility_method(D_METHOD("set_system_menu_root", "special"), &PopupMenu::_set_system_menu_root_compat_87452);
ClassDB::bind_compatibility_method(D_METHOD("get_system_menu_root"), &PopupMenu::_get_system_menu_root_compat_87452);
}
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,378 @@
/**************************************************************************/
/* popup_menu.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. */
/**************************************************************************/
#ifndef POPUP_MENU_H
#define POPUP_MENU_H
#include "core/input/shortcut.h"
#include "scene/gui/popup.h"
#include "scene/gui/scroll_container.h"
#include "scene/property_list_helper.h"
#include "scene/resources/text_line.h"
class PopupMenu : public Popup {
GDCLASS(PopupMenu, Popup);
static HashMap<NativeMenu::SystemMenus, PopupMenu *> system_menus;
struct Item {
Ref<Texture2D> icon;
int icon_max_width = 0;
Color icon_modulate = Color(1, 1, 1, 1);
String text;
String xl_text;
Ref<TextLine> text_buf;
Ref<TextLine> accel_text_buf;
String language;
Control::TextDirection text_direction = Control::TEXT_DIRECTION_AUTO;
bool checked = false;
enum {
CHECKABLE_TYPE_NONE,
CHECKABLE_TYPE_CHECK_BOX,
CHECKABLE_TYPE_RADIO_BUTTON,
} checkable_type = CHECKABLE_TYPE_NONE;
int max_states = 0;
int state = 0;
bool separator = false;
bool disabled = false;
bool dirty = true;
int id = 0;
Variant metadata;
String submenu_name; // Compatibility.
PopupMenu *submenu = nullptr;
String tooltip;
Key accel = Key::NONE;
int _ofs_cache = 0;
int _height_cache = 0;
int indent = 0;
Ref<Shortcut> shortcut;
bool shortcut_is_global = false;
bool shortcut_is_disabled = false;
bool allow_echo = false;
bool submenu_bound = false;
// Returns (0,0) if icon is null.
Size2 get_icon_size() const {
return icon.is_null() ? Size2() : icon->get_size();
}
Item() {
text_buf.instantiate();
accel_text_buf.instantiate();
checkable_type = CHECKABLE_TYPE_NONE;
}
Item(bool p_dummy) {}
};
static inline PropertyListHelper base_property_helper;
PropertyListHelper property_helper;
// To make Item available.
friend class OptionButton;
friend class MenuButton;
RID global_menu;
RID system_menu;
NativeMenu::SystemMenus system_menu_id = NativeMenu::INVALID_MENU_ID;
bool prefer_native = false;
bool close_allowed = false;
bool activated_by_keyboard = false;
Timer *minimum_lifetime_timer = nullptr;
Timer *submenu_timer = nullptr;
List<Rect2> autohide_areas;
Vector<Item> items;
BitField<MouseButtonMask> initial_button_mask;
bool during_grabbed_click = false;
bool is_scrolling = false;
int mouse_over = -1;
int submenu_over = -1;
String _get_accel_text(const Item &p_item) const;
int _get_mouse_over(const Point2 &p_over) const;
void _mouse_over_update(const Point2 &p_over);
virtual Size2 _get_contents_minimum_size() const override;
int _get_item_height(int p_idx) const;
int _get_items_total_height() const;
Size2 _get_item_icon_size(int p_idx) const;
void _shape_item(int p_idx);
void _activate_submenu(int p_over, bool p_by_keyboard = false);
void _submenu_timeout();
uint64_t popup_time_msec = 0;
bool hide_on_item_selection = true;
bool hide_on_checkable_item_selection = true;
bool hide_on_multistate_item_selection = false;
Vector2 moved;
HashMap<Ref<Shortcut>, int> shortcut_refcount;
void _ref_shortcut(Ref<Shortcut> p_sc);
void _unref_shortcut(Ref<Shortcut> p_sc);
void _shortcut_changed();
bool allow_search = true;
uint64_t search_time_msec = 0;
String search_string = "";
ScrollContainer *scroll_container = nullptr;
Control *control = nullptr;
const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 0.5;
const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 20;
float gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS;
struct ThemeCache {
Ref<StyleBox> panel_style;
Ref<StyleBox> hover_style;
Ref<StyleBox> separator_style;
Ref<StyleBox> labeled_separator_left;
Ref<StyleBox> labeled_separator_right;
int v_separation = 0;
int h_separation = 0;
int indent = 0;
int item_start_padding = 0;
int item_end_padding = 0;
int icon_max_width = 0;
Ref<Texture2D> checked;
Ref<Texture2D> checked_disabled;
Ref<Texture2D> unchecked;
Ref<Texture2D> unchecked_disabled;
Ref<Texture2D> radio_checked;
Ref<Texture2D> radio_checked_disabled;
Ref<Texture2D> radio_unchecked;
Ref<Texture2D> radio_unchecked_disabled;
Ref<Texture2D> submenu;
Ref<Texture2D> submenu_mirrored;
Ref<Font> font;
int font_size = 0;
Ref<Font> font_separator;
int font_separator_size = 0;
Color font_color;
Color font_hover_color;
Color font_disabled_color;
Color font_accelerator_color;
int font_outline_size = 0;
Color font_outline_color;
Color font_separator_color;
int font_separator_outline_size = 0;
Color font_separator_outline_color;
} theme_cache;
void _draw_items();
void _minimum_lifetime_timeout();
void _close_pressed();
void _menu_changed();
void _input_from_window_internal(const Ref<InputEvent> &p_event);
bool _set_item_accelerator(int p_index, const Ref<InputEventKey> &p_ie);
void _set_item_checkable_type(int p_index, int p_checkable_type);
int _get_item_checkable_type(int p_index) const;
protected:
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
virtual void _input_from_window(const Ref<InputEvent> &p_event) override;
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const { return property_helper.property_get_value(p_name, r_ret); }
void _get_property_list(List<PropertyInfo> *p_list) const { property_helper.get_property_list(p_list); }
bool _property_can_revert(const StringName &p_name) const { return property_helper.property_can_revert(p_name); }
bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return property_helper.property_get_revert(p_name, r_property); }
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
void _add_shortcut_bind_compat_36493(const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
void _add_icon_shortcut_bind_compat_36493(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
void _clear_bind_compat_79965();
void _set_system_menu_root_compat_87452(const String &p_special);
String _get_system_menu_root_compat_87452() const;
static void _bind_compatibility_methods();
#endif
public:
// ATTENTION: This is used by the POT generator's scene parser. If the number of properties returned by `_get_items()` ever changes,
// this value should be updated to reflect the new size.
static const int ITEM_PROPERTY_SIZE = 10;
virtual void _parent_focused() override;
RID bind_global_menu();
void unbind_global_menu();
bool is_system_menu() const;
void set_system_menu(NativeMenu::SystemMenus p_system_menu_id);
NativeMenu::SystemMenus get_system_menu() const;
void add_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_check_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_radio_check_item(const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id = -1, Key p_accel = Key::NONE);
void add_multistate_item(const String &p_label, int p_max_states, int p_default_state = 0, int p_id = -1, Key p_accel = Key::NONE);
void add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false, bool p_allow_echo = false);
void add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false, bool p_allow_echo = false);
void add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
void add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
void add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
void add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id = -1, bool p_global = false);
void add_submenu_item(const String &p_label, const String &p_submenu, int p_id = -1);
void add_submenu_node_item(const String &p_label, PopupMenu *p_submenu, int p_id = -1);
void set_item_text(int p_idx, const String &p_text);
void set_item_text_direction(int p_idx, Control::TextDirection p_text_direction);
void set_item_language(int p_idx, const String &p_language);
void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon);
void set_item_icon_max_width(int p_idx, int p_width);
void set_item_icon_modulate(int p_idx, const Color &p_modulate);
void set_item_checked(int p_idx, bool p_checked);
void set_item_id(int p_idx, int p_id);
void set_item_accelerator(int p_idx, Key p_accel);
void set_item_metadata(int p_idx, const Variant &p_meta);
void set_item_disabled(int p_idx, bool p_disabled);
void set_item_submenu(int p_idx, const String &p_submenu);
void set_item_submenu_node(int p_idx, PopupMenu *p_submenu);
void set_item_as_separator(int p_idx, bool p_separator);
void set_item_as_checkable(int p_idx, bool p_checkable);
void set_item_as_radio_checkable(int p_idx, bool p_radio_checkable);
void set_item_tooltip(int p_idx, const String &p_tooltip);
void set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bool p_global = false);
void set_item_indent(int p_idx, int p_indent);
void set_item_max_states(int p_idx, int p_max_states);
void set_item_multistate(int p_idx, int p_state);
void toggle_item_multistate(int p_idx);
void set_item_shortcut_disabled(int p_idx, bool p_disabled);
void toggle_item_checked(int p_idx);
String get_item_text(int p_idx) const;
String get_item_xl_text(int p_idx) const;
Control::TextDirection get_item_text_direction(int p_idx) const;
String get_item_language(int p_idx) const;
int get_item_idx_from_text(const String &text) const;
Ref<Texture2D> get_item_icon(int p_idx) const;
int get_item_icon_max_width(int p_idx) const;
Color get_item_icon_modulate(int p_idx) const;
bool is_item_checked(int p_idx) const;
int get_item_id(int p_idx) const;
int get_item_index(int p_id) const;
Key get_item_accelerator(int p_idx) const;
Variant get_item_metadata(int p_idx) const;
bool is_item_disabled(int p_idx) const;
String get_item_submenu(int p_idx) const;
PopupMenu *get_item_submenu_node(int p_idx) const;
bool is_item_separator(int p_idx) const;
bool is_item_checkable(int p_idx) const;
bool is_item_radio_checkable(int p_idx) const;
bool is_item_shortcut_disabled(int p_idx) const;
bool is_item_shortcut_global(int p_idx) const;
String get_item_tooltip(int p_idx) const;
Ref<Shortcut> get_item_shortcut(int p_idx) const;
int get_item_indent(int p_idx) const;
int get_item_max_states(int p_idx) const;
int get_item_state(int p_idx) const;
void set_focused_item(int p_idx);
int get_focused_item() const;
void set_item_count(int p_count);
int get_item_count() const;
void set_prefer_native_menu(bool p_enabled);
bool is_prefer_native_menu() const;
bool is_native_menu() const;
void scroll_to_item(int p_idx);
bool activate_item_by_event(const Ref<InputEvent> &p_event, bool p_for_global_only = false);
void activate_item(int p_idx);
void _about_to_popup();
void _about_to_close();
void remove_item(int p_idx);
void add_separator(const String &p_text = String(), int p_id = -1);
void clear(bool p_free_submenus = true);
virtual String get_tooltip(const Point2 &p_pos) const;
void add_autohide_area(const Rect2 &p_area);
void clear_autohide_areas();
void set_hide_on_item_selection(bool p_enabled);
bool is_hide_on_item_selection() const;
void set_hide_on_checkable_item_selection(bool p_enabled);
bool is_hide_on_checkable_item_selection() const;
void set_hide_on_multistate_item_selection(bool p_enabled);
bool is_hide_on_multistate_item_selection() const;
void set_submenu_popup_delay(float p_time);
float get_submenu_popup_delay() const;
void set_allow_search(bool p_allow);
bool get_allow_search() const;
virtual void popup(const Rect2i &p_bounds = Rect2i()) override;
virtual void set_visible(bool p_visible) override;
void take_mouse_focus();
PopupMenu();
~PopupMenu();
};
#endif // POPUP_MENU_H

View file

@ -0,0 +1,283 @@
/**************************************************************************/
/* progress_bar.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 "progress_bar.h"
#include "scene/resources/text_line.h"
#include "scene/theme/theme_db.h"
Size2 ProgressBar::get_minimum_size() const {
Size2 minimum_size = theme_cache.background_style->get_minimum_size();
minimum_size = minimum_size.max(theme_cache.fill_style->get_minimum_size());
if (show_percentage) {
String txt = "100%";
TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size);
minimum_size.height = MAX(minimum_size.height, theme_cache.background_style->get_minimum_size().height + tl.get_size().y);
} else { // this is needed, else the progressbar will collapse
minimum_size = minimum_size.maxf(1);
}
return minimum_size;
}
void ProgressBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_INTERNAL_PROCESS: {
if (is_visible_in_tree()) {
_inderminate_fill_progress += get_process_delta_time() * MAX(indeterminate_min_speed, MAX(get_size().width, get_size().height) / 2);
queue_redraw();
}
} break;
case NOTIFICATION_DRAW: {
draw_style_box(theme_cache.background_style, Rect2(Point2(), get_size()));
if (indeterminate) {
Size2 size = get_size();
real_t fill_size = MIN(size.width, size.height) * 2;
if (Engine::get_singleton()->is_editor_hint() && !editor_preview_indeterminate) {
// Center the filled bar when we're not previewing the animation.
_inderminate_fill_progress = (MAX(size.width, size.height) / 2) + (fill_size / 2);
}
switch (mode) {
case FILL_END_TO_BEGIN:
case FILL_BEGIN_TO_END: {
// Follow the RTL layout with the animation to match how the bar would fill.
bool right_to_left = mode == (is_layout_rtl() ? FILL_BEGIN_TO_END : FILL_END_TO_BEGIN);
if (_inderminate_fill_progress > size.width + fill_size) {
_inderminate_fill_progress = right_to_left ? -fill_size : 0;
}
real_t x = right_to_left ? size.width - _inderminate_fill_progress : _inderminate_fill_progress - fill_size;
draw_style_box(theme_cache.fill_style, Rect2(x, 0, fill_size, size.height).intersection(Rect2(Point2(), size)));
} break;
case FILL_TOP_TO_BOTTOM: {
if (_inderminate_fill_progress > size.height + fill_size) {
_inderminate_fill_progress = 0;
}
draw_style_box(theme_cache.fill_style, Rect2(0, _inderminate_fill_progress - fill_size, size.width, fill_size).intersection(Rect2(Point2(), size)));
} break;
case FILL_BOTTOM_TO_TOP: {
if (_inderminate_fill_progress > size.height + fill_size) {
_inderminate_fill_progress = -fill_size;
}
draw_style_box(theme_cache.fill_style, Rect2(0, size.height - _inderminate_fill_progress, size.width, fill_size).intersection(Rect2(Point2(), size)));
} break;
case FILL_MODE_MAX:
break;
}
return;
}
float r = get_as_ratio();
switch (mode) {
case FILL_BEGIN_TO_END:
case FILL_END_TO_BEGIN: {
int mp = theme_cache.fill_style->get_minimum_size().width;
int p = round(r * (get_size().width - mp));
// We want FILL_BEGIN_TO_END to map to right to left when UI layout is RTL,
// and left to right otherwise. And likewise for FILL_END_TO_BEGIN.
bool right_to_left = mode == (is_layout_rtl() ? FILL_BEGIN_TO_END : FILL_END_TO_BEGIN);
if (p > 0) {
if (right_to_left) {
int p_remaining = round((1.0 - r) * (get_size().width - mp));
draw_style_box(theme_cache.fill_style, Rect2(Point2(p_remaining, 0), Size2(p + theme_cache.fill_style->get_minimum_size().width, get_size().height)));
} else {
draw_style_box(theme_cache.fill_style, Rect2(Point2(0, 0), Size2(p + theme_cache.fill_style->get_minimum_size().width, get_size().height)));
}
}
} break;
case FILL_TOP_TO_BOTTOM:
case FILL_BOTTOM_TO_TOP: {
int mp = theme_cache.fill_style->get_minimum_size().height;
int p = round(r * (get_size().height - mp));
if (p > 0) {
if (mode == FILL_TOP_TO_BOTTOM) {
draw_style_box(theme_cache.fill_style, Rect2(Point2(0, 0), Size2(get_size().width, p + theme_cache.fill_style->get_minimum_size().height)));
} else {
int p_remaining = round((1.0 - r) * (get_size().height - mp));
draw_style_box(theme_cache.fill_style, Rect2(Point2(0, p_remaining), Size2(get_size().width, p + theme_cache.fill_style->get_minimum_size().height)));
}
}
} break;
case FILL_MODE_MAX:
break;
}
if (show_percentage) {
double ratio = 0;
// Avoid division by zero.
if (Math::is_equal_approx(get_max(), get_min())) {
ratio = 1;
} else if (is_ratio_exp() && get_min() >= 0 && get_value() >= 0) {
double exp_min = get_min() == 0 ? 0.0 : Math::log(get_min()) / Math::log((double)2);
double exp_max = Math::log(get_max()) / Math::log((double)2);
double exp_value = get_value() == 0 ? 0.0 : Math::log(get_value()) / Math::log((double)2);
double percentage = (exp_value - exp_min) / (exp_max - exp_min);
ratio = CLAMP(percentage, is_lesser_allowed() ? percentage : 0, is_greater_allowed() ? percentage : 1);
} else {
double percentage = (get_value() - get_min()) / (get_max() - get_min());
ratio = CLAMP(percentage, is_lesser_allowed() ? percentage : 0, is_greater_allowed() ? percentage : 1);
}
String txt = itos(int(ratio * 100));
if (is_localizing_numeral_system()) {
txt = TS->format_number(txt) + TS->percent_sign();
} else {
txt += String("%");
}
TextLine tl = TextLine(txt, theme_cache.font, theme_cache.font_size);
Vector2 text_pos = (Point2(get_size().width - tl.get_size().x, get_size().height - tl.get_size().y) / 2).round();
if (theme_cache.font_outline_size > 0 && theme_cache.font_outline_color.a > 0) {
tl.draw_outline(get_canvas_item(), text_pos, theme_cache.font_outline_size, theme_cache.font_outline_color);
}
tl.draw(get_canvas_item(), text_pos, theme_cache.font_color);
}
} break;
}
}
void ProgressBar::_validate_property(PropertyInfo &p_property) const {
if (indeterminate && p_property.name == "show_percentage") {
p_property.usage |= PROPERTY_USAGE_READ_ONLY;
}
if (!indeterminate && p_property.name == "editor_preview_indeterminate") {
p_property.usage = PROPERTY_USAGE_NONE;
}
}
void ProgressBar::set_fill_mode(int p_fill) {
ERR_FAIL_INDEX(p_fill, FILL_MODE_MAX);
mode = (FillMode)p_fill;
_inderminate_fill_progress = 0;
queue_redraw();
}
int ProgressBar::get_fill_mode() {
return mode;
}
void ProgressBar::set_show_percentage(bool p_visible) {
if (show_percentage == p_visible) {
return;
}
show_percentage = p_visible;
update_minimum_size();
queue_redraw();
}
bool ProgressBar::is_percentage_shown() const {
return show_percentage;
}
void ProgressBar::set_indeterminate(bool p_indeterminate) {
if (indeterminate == p_indeterminate) {
return;
}
indeterminate = p_indeterminate;
_inderminate_fill_progress = 0;
bool should_process = !Engine::get_singleton()->is_editor_hint() || editor_preview_indeterminate;
set_process_internal(indeterminate && should_process);
notify_property_list_changed();
update_minimum_size();
queue_redraw();
}
bool ProgressBar::is_indeterminate() const {
return indeterminate;
}
void ProgressBar::set_editor_preview_indeterminate(bool p_preview_indeterminate) {
if (editor_preview_indeterminate == p_preview_indeterminate) {
return;
}
editor_preview_indeterminate = p_preview_indeterminate;
if (Engine::get_singleton()->is_editor_hint()) {
_inderminate_fill_progress = 0;
set_process_internal(indeterminate && editor_preview_indeterminate);
queue_redraw();
}
}
bool ProgressBar::is_editor_preview_indeterminate_enabled() const {
return editor_preview_indeterminate;
}
void ProgressBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_fill_mode", "mode"), &ProgressBar::set_fill_mode);
ClassDB::bind_method(D_METHOD("get_fill_mode"), &ProgressBar::get_fill_mode);
ClassDB::bind_method(D_METHOD("set_show_percentage", "visible"), &ProgressBar::set_show_percentage);
ClassDB::bind_method(D_METHOD("is_percentage_shown"), &ProgressBar::is_percentage_shown);
ClassDB::bind_method(D_METHOD("set_indeterminate", "indeterminate"), &ProgressBar::set_indeterminate);
ClassDB::bind_method(D_METHOD("is_indeterminate"), &ProgressBar::is_indeterminate);
ClassDB::bind_method(D_METHOD("set_editor_preview_indeterminate", "preview_indeterminate"), &ProgressBar::set_editor_preview_indeterminate);
ClassDB::bind_method(D_METHOD("is_editor_preview_indeterminate_enabled"), &ProgressBar::is_editor_preview_indeterminate_enabled);
ADD_PROPERTY(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Begin to End,End to Begin,Top to Bottom,Bottom to Top"), "set_fill_mode", "get_fill_mode");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_percentage"), "set_show_percentage", "is_percentage_shown");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "indeterminate"), "set_indeterminate", "is_indeterminate");
ADD_GROUP("Editor", "editor_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_preview_indeterminate"), "set_editor_preview_indeterminate", "is_editor_preview_indeterminate_enabled");
BIND_ENUM_CONSTANT(FILL_BEGIN_TO_END);
BIND_ENUM_CONSTANT(FILL_END_TO_BEGIN);
BIND_ENUM_CONSTANT(FILL_TOP_TO_BOTTOM);
BIND_ENUM_CONSTANT(FILL_BOTTOM_TO_TOP);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ProgressBar, background_style, "background");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ProgressBar, fill_style, "fill");
BIND_THEME_ITEM(Theme::DATA_TYPE_FONT, ProgressBar, font);
BIND_THEME_ITEM(Theme::DATA_TYPE_FONT_SIZE, ProgressBar, font_size);
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, ProgressBar, font_color);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_CONSTANT, ProgressBar, font_outline_size, "outline_size");
BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, ProgressBar, font_outline_color);
}
ProgressBar::ProgressBar() {
set_v_size_flags(0);
set_step(0.01);
}

View file

@ -0,0 +1,94 @@
/**************************************************************************/
/* progress_bar.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. */
/**************************************************************************/
#ifndef PROGRESS_BAR_H
#define PROGRESS_BAR_H
#include "scene/gui/range.h"
class ProgressBar : public Range {
GDCLASS(ProgressBar, Range);
bool show_percentage = true;
bool indeterminate = false;
bool editor_preview_indeterminate = false;
struct ThemeCache {
Ref<StyleBox> background_style;
Ref<StyleBox> fill_style;
Ref<Font> font;
int font_size = 0;
Color font_color;
int font_outline_size = 0;
Color font_outline_color;
} theme_cache;
protected:
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
double indeterminate_min_speed = 200.0;
public:
enum FillMode {
FILL_BEGIN_TO_END,
FILL_END_TO_BEGIN,
FILL_TOP_TO_BOTTOM,
FILL_BOTTOM_TO_TOP,
FILL_MODE_MAX
};
void set_fill_mode(int p_fill);
int get_fill_mode();
void set_show_percentage(bool p_visible);
bool is_percentage_shown() const;
void set_indeterminate(bool p_indeterminate);
bool is_indeterminate() const;
void set_editor_preview_indeterminate(bool p_indeterminate_preview);
bool is_editor_preview_indeterminate_enabled() const;
Size2 get_minimum_size() const override;
ProgressBar();
private:
float _inderminate_fill_progress = 0;
FillMode mode = FILL_BEGIN_TO_END;
};
VARIANT_ENUM_CAST(ProgressBar::FillMode);
#endif // PROGRESS_BAR_H

383
engine/scene/gui/range.cpp Normal file
View file

@ -0,0 +1,383 @@
/**************************************************************************/
/* range.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 "range.h"
PackedStringArray Range::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (shared->exp_ratio && shared->min <= 0) {
warnings.push_back(RTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0."));
}
return warnings;
}
void Range::_value_changed(double p_value) {
GDVIRTUAL_CALL(_value_changed, p_value);
}
void Range::_value_changed_notify() {
_value_changed(shared->val);
emit_signal(SceneStringName(value_changed), shared->val);
queue_redraw();
}
void Range::Shared::emit_value_changed() {
for (Range *E : owners) {
Range *r = E;
if (!r->is_inside_tree()) {
continue;
}
r->_value_changed_notify();
}
}
void Range::_changed_notify(const char *p_what) {
emit_signal(CoreStringName(changed));
queue_redraw();
}
void Range::Shared::emit_changed(const char *p_what) {
for (Range *E : owners) {
Range *r = E;
if (!r->is_inside_tree()) {
continue;
}
r->_changed_notify(p_what);
}
}
void Range::Shared::redraw_owners() {
for (Range *E : owners) {
Range *r = E;
if (!r->is_inside_tree()) {
continue;
}
r->queue_redraw();
}
}
void Range::set_value(double p_val) {
double prev_val = shared->val;
_set_value_no_signal(p_val);
if (shared->val != prev_val) {
shared->emit_value_changed();
}
}
void Range::_set_value_no_signal(double p_val) {
if (!Math::is_finite(p_val)) {
return;
}
if (shared->step > 0) {
p_val = Math::round((p_val - shared->min) / shared->step) * shared->step + shared->min;
}
if (_rounded_values) {
p_val = Math::round(p_val);
}
if (!shared->allow_greater && p_val > shared->max - shared->page) {
p_val = shared->max - shared->page;
}
if (!shared->allow_lesser && p_val < shared->min) {
p_val = shared->min;
}
if (shared->val == p_val) {
return;
}
shared->val = p_val;
}
void Range::set_value_no_signal(double p_val) {
double prev_val = shared->val;
_set_value_no_signal(p_val);
if (shared->val != prev_val) {
shared->redraw_owners();
}
}
void Range::set_min(double p_min) {
if (shared->min == p_min) {
return;
}
shared->min = p_min;
shared->max = MAX(shared->max, shared->min);
shared->page = CLAMP(shared->page, 0, shared->max - shared->min);
set_value(shared->val);
shared->emit_changed("min");
update_configuration_warnings();
}
void Range::set_max(double p_max) {
double max_validated = MAX(p_max, shared->min);
if (shared->max == max_validated) {
return;
}
shared->max = max_validated;
shared->page = CLAMP(shared->page, 0, shared->max - shared->min);
set_value(shared->val);
shared->emit_changed("max");
}
void Range::set_step(double p_step) {
if (shared->step == p_step) {
return;
}
shared->step = p_step;
shared->emit_changed("step");
}
void Range::set_page(double p_page) {
double page_validated = CLAMP(p_page, 0, shared->max - shared->min);
if (shared->page == page_validated) {
return;
}
shared->page = page_validated;
set_value(shared->val);
shared->emit_changed("page");
}
double Range::get_value() const {
return shared->val;
}
double Range::get_min() const {
return shared->min;
}
double Range::get_max() const {
return shared->max;
}
double Range::get_step() const {
return shared->step;
}
double Range::get_page() const {
return shared->page;
}
void Range::set_as_ratio(double p_value) {
double v;
if (shared->exp_ratio && get_min() >= 0) {
double exp_min = get_min() == 0 ? 0.0 : Math::log(get_min()) / Math::log((double)2);
double exp_max = Math::log(get_max()) / Math::log((double)2);
v = Math::pow(2, exp_min + (exp_max - exp_min) * p_value);
} else {
double percent = (get_max() - get_min()) * p_value;
if (get_step() > 0) {
double steps = round(percent / get_step());
v = steps * get_step() + get_min();
} else {
v = percent + get_min();
}
}
v = CLAMP(v, get_min(), get_max());
set_value(v);
}
double Range::get_as_ratio() const {
if (Math::is_equal_approx(get_max(), get_min())) {
// Avoid division by zero.
return 1.0;
}
if (shared->exp_ratio && get_min() >= 0) {
double exp_min = get_min() == 0 ? 0.0 : Math::log(get_min()) / Math::log((double)2);
double exp_max = Math::log(get_max()) / Math::log((double)2);
float value = CLAMP(get_value(), shared->min, shared->max);
double v = Math::log(value) / Math::log((double)2);
return CLAMP((v - exp_min) / (exp_max - exp_min), 0, 1);
} else {
float value = CLAMP(get_value(), shared->min, shared->max);
return CLAMP((value - get_min()) / (get_max() - get_min()), 0, 1);
}
}
void Range::_share(Node *p_range) {
Range *r = Object::cast_to<Range>(p_range);
ERR_FAIL_NULL(r);
share(r);
}
void Range::share(Range *p_range) {
ERR_FAIL_NULL(p_range);
p_range->_ref_shared(shared);
p_range->_changed_notify();
p_range->_value_changed_notify();
}
void Range::unshare() {
Shared *nshared = memnew(Shared);
nshared->min = shared->min;
nshared->max = shared->max;
nshared->val = shared->val;
nshared->step = shared->step;
nshared->page = shared->page;
nshared->exp_ratio = shared->exp_ratio;
nshared->allow_greater = shared->allow_greater;
nshared->allow_lesser = shared->allow_lesser;
_unref_shared();
_ref_shared(nshared);
}
void Range::_ref_shared(Shared *p_shared) {
if (shared && p_shared == shared) {
return;
}
_unref_shared();
shared = p_shared;
shared->owners.insert(this);
}
void Range::_unref_shared() {
if (shared) {
shared->owners.erase(this);
if (shared->owners.size() == 0) {
memdelete(shared);
shared = nullptr;
}
}
}
void Range::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_value"), &Range::get_value);
ClassDB::bind_method(D_METHOD("get_min"), &Range::get_min);
ClassDB::bind_method(D_METHOD("get_max"), &Range::get_max);
ClassDB::bind_method(D_METHOD("get_step"), &Range::get_step);
ClassDB::bind_method(D_METHOD("get_page"), &Range::get_page);
ClassDB::bind_method(D_METHOD("get_as_ratio"), &Range::get_as_ratio);
ClassDB::bind_method(D_METHOD("set_value", "value"), &Range::set_value);
ClassDB::bind_method(D_METHOD("set_value_no_signal", "value"), &Range::set_value_no_signal);
ClassDB::bind_method(D_METHOD("set_min", "minimum"), &Range::set_min);
ClassDB::bind_method(D_METHOD("set_max", "maximum"), &Range::set_max);
ClassDB::bind_method(D_METHOD("set_step", "step"), &Range::set_step);
ClassDB::bind_method(D_METHOD("set_page", "pagesize"), &Range::set_page);
ClassDB::bind_method(D_METHOD("set_as_ratio", "value"), &Range::set_as_ratio);
ClassDB::bind_method(D_METHOD("set_use_rounded_values", "enabled"), &Range::set_use_rounded_values);
ClassDB::bind_method(D_METHOD("is_using_rounded_values"), &Range::is_using_rounded_values);
ClassDB::bind_method(D_METHOD("set_exp_ratio", "enabled"), &Range::set_exp_ratio);
ClassDB::bind_method(D_METHOD("is_ratio_exp"), &Range::is_ratio_exp);
ClassDB::bind_method(D_METHOD("set_allow_greater", "allow"), &Range::set_allow_greater);
ClassDB::bind_method(D_METHOD("is_greater_allowed"), &Range::is_greater_allowed);
ClassDB::bind_method(D_METHOD("set_allow_lesser", "allow"), &Range::set_allow_lesser);
ClassDB::bind_method(D_METHOD("is_lesser_allowed"), &Range::is_lesser_allowed);
ClassDB::bind_method(D_METHOD("share", "with"), &Range::_share);
ClassDB::bind_method(D_METHOD("unshare"), &Range::unshare);
ADD_SIGNAL(MethodInfo("value_changed", PropertyInfo(Variant::FLOAT, "value")));
ADD_SIGNAL(MethodInfo("changed"));
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "min_value"), "set_min", "get_min");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_value"), "set_max", "get_max");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "step"), "set_step", "get_step");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "page"), "set_page", "get_page");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "value"), "set_value", "get_value");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ratio", PROPERTY_HINT_RANGE, "0,1,0.01", PROPERTY_USAGE_NONE), "set_as_ratio", "get_as_ratio");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "exp_edit"), "set_exp_ratio", "is_ratio_exp");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rounded"), "set_use_rounded_values", "is_using_rounded_values");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_greater"), "set_allow_greater", "is_greater_allowed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_lesser"), "set_allow_lesser", "is_lesser_allowed");
GDVIRTUAL_BIND(_value_changed, "new_value");
ADD_LINKED_PROPERTY("min_value", "value");
ADD_LINKED_PROPERTY("min_value", "max_value");
ADD_LINKED_PROPERTY("min_value", "page");
ADD_LINKED_PROPERTY("max_value", "value");
ADD_LINKED_PROPERTY("max_value", "page");
}
void Range::set_use_rounded_values(bool p_enable) {
_rounded_values = p_enable;
}
bool Range::is_using_rounded_values() const {
return _rounded_values;
}
void Range::set_exp_ratio(bool p_enable) {
if (shared->exp_ratio == p_enable) {
return;
}
shared->exp_ratio = p_enable;
update_configuration_warnings();
}
bool Range::is_ratio_exp() const {
return shared->exp_ratio;
}
void Range::set_allow_greater(bool p_allow) {
shared->allow_greater = p_allow;
}
bool Range::is_greater_allowed() const {
return shared->allow_greater;
}
void Range::set_allow_lesser(bool p_allow) {
shared->allow_lesser = p_allow;
}
bool Range::is_lesser_allowed() const {
return shared->allow_lesser;
}
Range::Range() {
shared = memnew(Shared);
shared->owners.insert(this);
}
Range::~Range() {
_unref_shared();
}

112
engine/scene/gui/range.h Normal file
View file

@ -0,0 +1,112 @@
/**************************************************************************/
/* range.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. */
/**************************************************************************/
#ifndef RANGE_H
#define RANGE_H
#include "scene/gui/control.h"
class Range : public Control {
GDCLASS(Range, Control);
struct Shared {
double val = 0.0;
double min = 0.0;
double max = 100.0;
double step = 1.0;
double page = 0.0;
bool exp_ratio = false;
bool allow_greater = false;
bool allow_lesser = false;
HashSet<Range *> owners;
void emit_value_changed();
void emit_changed(const char *p_what = "");
void redraw_owners();
};
Shared *shared = nullptr;
void _ref_shared(Shared *p_shared);
void _unref_shared();
void _share(Node *p_range);
void _value_changed_notify();
void _changed_notify(const char *p_what = "");
void _set_value_no_signal(double p_val);
protected:
virtual void _value_changed(double p_value);
void _notify_shared_value_changed() { shared->emit_value_changed(); };
static void _bind_methods();
bool _rounded_values = false;
GDVIRTUAL1(_value_changed, double)
public:
void set_value(double p_val);
void set_value_no_signal(double p_val);
void set_min(double p_min);
void set_max(double p_max);
void set_step(double p_step);
void set_page(double p_page);
void set_as_ratio(double p_value);
double get_value() const;
double get_min() const;
double get_max() const;
double get_step() const;
double get_page() const;
double get_as_ratio() const;
void set_use_rounded_values(bool p_enable);
bool is_using_rounded_values() const;
void set_exp_ratio(bool p_enable);
bool is_ratio_exp() const;
void set_allow_greater(bool p_allow);
bool is_greater_allowed() const;
void set_allow_lesser(bool p_allow);
bool is_lesser_allowed() const;
void share(Range *p_range);
void unshare();
PackedStringArray get_configuration_warnings() const override;
Range();
~Range();
};
#endif // RANGE_H

View file

@ -0,0 +1,101 @@
/**************************************************************************/
/* reference_rect.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 "reference_rect.h"
#include "core/config/engine.h"
void ReferenceRect::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
if (!is_inside_tree()) {
return;
}
if (Engine::get_singleton()->is_editor_hint() || !editor_only) {
draw_rect(Rect2(Point2(), get_size()), border_color, false, border_width);
}
} break;
}
}
void ReferenceRect::set_border_color(const Color &p_color) {
if (border_color == p_color) {
return;
}
border_color = p_color;
queue_redraw();
}
Color ReferenceRect::get_border_color() const {
return border_color;
}
void ReferenceRect::set_border_width(float p_width) {
float width_max = MAX(0.0, p_width);
if (border_width == width_max) {
return;
}
border_width = width_max;
queue_redraw();
}
float ReferenceRect::get_border_width() const {
return border_width;
}
void ReferenceRect::set_editor_only(const bool &p_enabled) {
if (editor_only == p_enabled) {
return;
}
editor_only = p_enabled;
queue_redraw();
}
bool ReferenceRect::get_editor_only() const {
return editor_only;
}
void ReferenceRect::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_border_color"), &ReferenceRect::get_border_color);
ClassDB::bind_method(D_METHOD("set_border_color", "color"), &ReferenceRect::set_border_color);
ClassDB::bind_method(D_METHOD("get_border_width"), &ReferenceRect::get_border_width);
ClassDB::bind_method(D_METHOD("set_border_width", "width"), &ReferenceRect::set_border_width);
ClassDB::bind_method(D_METHOD("get_editor_only"), &ReferenceRect::get_editor_only);
ClassDB::bind_method(D_METHOD("set_editor_only", "enabled"), &ReferenceRect::set_editor_only);
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "border_color"), "set_border_color", "get_border_color");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "border_width", PROPERTY_HINT_RANGE, "0.0,5.0,0.1,or_greater,suffix:px"), "set_border_width", "get_border_width");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editor_only"), "set_editor_only", "get_editor_only");
}

View file

@ -0,0 +1,58 @@
/**************************************************************************/
/* reference_rect.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. */
/**************************************************************************/
#ifndef REFERENCE_RECT_H
#define REFERENCE_RECT_H
#include "scene/gui/control.h"
class ReferenceRect : public Control {
GDCLASS(ReferenceRect, Control);
Color border_color = Color(1, 0, 0);
float border_width = 1.0;
bool editor_only = true;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void set_border_color(const Color &p_color);
Color get_border_color() const;
void set_border_width(float p_width);
float get_border_width() const;
void set_editor_only(const bool &p_enabled);
bool get_editor_only() const;
};
#endif // REFERENCE_RECT_H

View file

@ -0,0 +1,119 @@
/**************************************************************************/
/* rich_text_effect.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 "rich_text_effect.h"
#include "core/object/script_language.h"
CharFXTransform::CharFXTransform() {
}
CharFXTransform::~CharFXTransform() {
environment.clear();
}
void RichTextEffect::_bind_methods(){
GDVIRTUAL_BIND(_process_custom_fx, "char_fx")
}
Variant RichTextEffect::get_bbcode() const {
Variant r;
if (get_script_instance()) {
if (!get_script_instance()->get("bbcode", r)) {
String path = get_script_instance()->get_script()->get_path();
r = path.get_file().get_basename();
}
}
return r;
}
bool RichTextEffect::_process_effect_impl(Ref<CharFXTransform> p_cfx) {
bool return_value = false;
GDVIRTUAL_CALL(_process_custom_fx, p_cfx, return_value);
return return_value;
}
RichTextEffect::RichTextEffect() {
}
void CharFXTransform::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_transform"), &CharFXTransform::get_transform);
ClassDB::bind_method(D_METHOD("set_transform", "transform"), &CharFXTransform::set_transform);
ClassDB::bind_method(D_METHOD("get_range"), &CharFXTransform::get_range);
ClassDB::bind_method(D_METHOD("set_range", "range"), &CharFXTransform::set_range);
ClassDB::bind_method(D_METHOD("get_elapsed_time"), &CharFXTransform::get_elapsed_time);
ClassDB::bind_method(D_METHOD("set_elapsed_time", "time"), &CharFXTransform::set_elapsed_time);
ClassDB::bind_method(D_METHOD("is_visible"), &CharFXTransform::is_visible);
ClassDB::bind_method(D_METHOD("set_visibility", "visibility"), &CharFXTransform::set_visibility);
ClassDB::bind_method(D_METHOD("is_outline"), &CharFXTransform::is_outline);
ClassDB::bind_method(D_METHOD("set_outline", "outline"), &CharFXTransform::set_outline);
ClassDB::bind_method(D_METHOD("get_offset"), &CharFXTransform::get_offset);
ClassDB::bind_method(D_METHOD("set_offset", "offset"), &CharFXTransform::set_offset);
ClassDB::bind_method(D_METHOD("get_color"), &CharFXTransform::get_color);
ClassDB::bind_method(D_METHOD("set_color", "color"), &CharFXTransform::set_color);
ClassDB::bind_method(D_METHOD("get_environment"), &CharFXTransform::get_environment);
ClassDB::bind_method(D_METHOD("set_environment", "environment"), &CharFXTransform::set_environment);
ClassDB::bind_method(D_METHOD("get_glyph_index"), &CharFXTransform::get_glyph_index);
ClassDB::bind_method(D_METHOD("set_glyph_index", "glyph_index"), &CharFXTransform::set_glyph_index);
ClassDB::bind_method(D_METHOD("get_relative_index"), &CharFXTransform::get_relative_index);
ClassDB::bind_method(D_METHOD("set_relative_index", "relative_index"), &CharFXTransform::set_relative_index);
ClassDB::bind_method(D_METHOD("get_glyph_count"), &CharFXTransform::get_glyph_count);
ClassDB::bind_method(D_METHOD("set_glyph_count", "glyph_count"), &CharFXTransform::set_glyph_count);
ClassDB::bind_method(D_METHOD("get_glyph_flags"), &CharFXTransform::get_glyph_flags);
ClassDB::bind_method(D_METHOD("set_glyph_flags", "glyph_flags"), &CharFXTransform::set_glyph_flags);
ClassDB::bind_method(D_METHOD("get_font"), &CharFXTransform::get_font);
ClassDB::bind_method(D_METHOD("set_font", "font"), &CharFXTransform::set_font);
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "transform"), "set_transform", "get_transform");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "range"), "set_range", "get_range");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "elapsed_time"), "set_elapsed_time", "get_elapsed_time");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "visible"), "set_visibility", "is_visible");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "outline"), "set_outline", "is_outline");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "env"), "set_environment", "get_environment");
ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_index"), "set_glyph_index", "get_glyph_index");
ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_count"), "set_glyph_count", "get_glyph_count");
ADD_PROPERTY(PropertyInfo(Variant::INT, "glyph_flags"), "set_glyph_flags", "get_glyph_flags");
ADD_PROPERTY(PropertyInfo(Variant::INT, "relative_index"), "set_relative_index", "get_relative_index");
ADD_PROPERTY(PropertyInfo(Variant::RID, "font"), "set_font", "get_font");
}

View file

@ -0,0 +1,117 @@
/**************************************************************************/
/* rich_text_effect.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. */
/**************************************************************************/
#ifndef RICH_TEXT_EFFECT_H
#define RICH_TEXT_EFFECT_H
#include "core/io/resource.h"
#include "core/object/gdvirtual.gen.inc"
class CharFXTransform : public RefCounted {
GDCLASS(CharFXTransform, RefCounted);
protected:
static void _bind_methods();
public:
Transform2D transform;
Vector2i range;
bool visibility = true;
bool outline = false;
Point2 offset;
Color color;
double elapsed_time = 0.0f;
Dictionary environment;
uint32_t glyph_index = 0;
uint16_t glyph_flags = 0;
uint8_t glyph_count = 0;
int32_t relative_index = 0;
RID font;
CharFXTransform();
~CharFXTransform();
void set_transform(const Transform2D &p_transform) { transform = p_transform; }
const Transform2D &get_transform() { return transform; }
Vector2i get_range() { return range; }
void set_range(const Vector2i &p_range) { range = p_range; }
double get_elapsed_time() { return elapsed_time; }
void set_elapsed_time(double p_elapsed_time) { elapsed_time = p_elapsed_time; }
bool is_visible() { return visibility; }
void set_visibility(bool p_visibility) { visibility = p_visibility; }
bool is_outline() { return outline; }
void set_outline(bool p_outline) { outline = p_outline; }
Point2 get_offset() { return offset; }
void set_offset(Point2 p_offset) { offset = p_offset; }
Color get_color() { return color; }
void set_color(Color p_color) { color = p_color; }
uint32_t get_glyph_index() const { return glyph_index; };
void set_glyph_index(uint32_t p_glyph_index) { glyph_index = p_glyph_index; };
uint16_t get_glyph_flags() const { return glyph_flags; };
void set_glyph_flags(uint16_t p_glyph_flags) { glyph_flags = p_glyph_flags; };
uint8_t get_glyph_count() const { return glyph_count; };
void set_glyph_count(uint8_t p_glyph_count) { glyph_count = p_glyph_count; };
int32_t get_relative_index() const { return relative_index; };
void set_relative_index(int32_t p_relative_index) { relative_index = p_relative_index; };
RID get_font() const { return font; };
void set_font(RID p_font) { font = p_font; };
Dictionary get_environment() { return environment; }
void set_environment(Dictionary p_environment) { environment = p_environment; }
};
class RichTextEffect : public Resource {
GDCLASS(RichTextEffect, Resource);
OBJ_SAVE_TYPE(RichTextEffect);
protected:
static void _bind_methods();
GDVIRTUAL1RC(bool, _process_custom_fx, Ref<CharFXTransform>)
public:
Variant get_bbcode() const;
bool _process_effect_impl(Ref<class CharFXTransform> p_cfx);
RichTextEffect();
};
#endif // RICH_TEXT_EFFECT_H

View file

@ -0,0 +1,51 @@
/**************************************************************************/
/* rich_text_label.compat.inc */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
void RichTextLabel::_push_meta_bind_compat_89024(const Variant &p_meta) {
push_meta(p_meta, RichTextLabel::MetaUnderline::META_UNDERLINE_ALWAYS);
}
void RichTextLabel::_add_image_bind_compat_80410(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region) {
add_image(p_image, p_width, p_height, p_color, p_alignment, p_region, Variant(), false, String(), false);
}
bool RichTextLabel::_remove_paragraph_bind_compat_91098(int p_paragraph) {
return remove_paragraph(p_paragraph, false);
}
void RichTextLabel::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("push_meta", "data"), &RichTextLabel::_push_meta_bind_compat_89024);
ClassDB::bind_compatibility_method(D_METHOD("add_image", "image", "width", "height", "color", "inline_align", "region"), &RichTextLabel::_add_image_bind_compat_80410, DEFVAL(0), DEFVAL(0), DEFVAL(Color(1.0, 1.0, 1.0)), DEFVAL(INLINE_ALIGNMENT_CENTER), DEFVAL(Rect2()));
ClassDB::bind_compatibility_method(D_METHOD("remove_paragraph", "paragraph"), &RichTextLabel::_remove_paragraph_bind_compat_91098);
}
#endif // DISABLE_DEPRECATED

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,855 @@
/**************************************************************************/
/* rich_text_label.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. */
/**************************************************************************/
#ifndef RICH_TEXT_LABEL_H
#define RICH_TEXT_LABEL_H
#include "core/object/worker_thread_pool.h"
#include "core/templates/rid_owner.h"
#include "scene/gui/popup_menu.h"
#include "scene/gui/scroll_bar.h"
#include "scene/resources/text_paragraph.h"
class CharFXTransform;
class RichTextEffect;
class RichTextLabel : public Control {
GDCLASS(RichTextLabel, Control);
enum RTLDrawStep {
DRAW_STEP_BACKGROUND,
DRAW_STEP_SHADOW,
DRAW_STEP_OUTLINE,
DRAW_STEP_TEXT,
DRAW_STEP_FOREGROUND,
DRAW_STEP_MAX,
};
public:
enum ListType {
LIST_NUMBERS,
LIST_LETTERS,
LIST_ROMAN,
LIST_DOTS
};
enum MetaUnderline {
META_UNDERLINE_NEVER,
META_UNDERLINE_ALWAYS,
META_UNDERLINE_ON_HOVER,
};
enum ItemType {
ITEM_FRAME,
ITEM_TEXT,
ITEM_IMAGE,
ITEM_NEWLINE,
ITEM_FONT,
ITEM_FONT_SIZE,
ITEM_FONT_FEATURES,
ITEM_COLOR,
ITEM_OUTLINE_SIZE,
ITEM_OUTLINE_COLOR,
ITEM_UNDERLINE,
ITEM_STRIKETHROUGH,
ITEM_PARAGRAPH,
ITEM_INDENT,
ITEM_LIST,
ITEM_TABLE,
ITEM_FADE,
ITEM_SHAKE,
ITEM_WAVE,
ITEM_TORNADO,
ITEM_RAINBOW,
ITEM_PULSE,
ITEM_BGCOLOR,
ITEM_FGCOLOR,
ITEM_META,
ITEM_HINT,
ITEM_DROPCAP,
ITEM_CUSTOMFX,
ITEM_CONTEXT,
ITEM_LANGUAGE,
};
enum MenuItems {
MENU_COPY,
MENU_SELECT_ALL,
MENU_MAX
};
enum DefaultFont {
NORMAL_FONT,
BOLD_FONT,
ITALICS_FONT,
BOLD_ITALICS_FONT,
MONO_FONT,
CUSTOM_FONT,
};
enum ImageUpdateMask {
UPDATE_TEXTURE = 1 << 0,
UPDATE_SIZE = 1 << 1,
UPDATE_COLOR = 1 << 2,
UPDATE_ALIGNMENT = 1 << 3,
UPDATE_REGION = 1 << 4,
UPDATE_PAD = 1 << 5,
UPDATE_TOOLTIP = 1 << 6,
UPDATE_WIDTH_IN_PERCENT = 1 << 7,
};
protected:
virtual void _update_theme_item_cache() override;
void _notification(int p_what);
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
void _push_meta_bind_compat_89024(const Variant &p_meta);
void _add_image_bind_compat_80410(const Ref<Texture2D> &p_image, const int p_width, const int p_height, const Color &p_color, InlineAlignment p_alignment, const Rect2 &p_region);
bool _remove_paragraph_bind_compat_91098(int p_paragraph);
static void _bind_compatibility_methods();
#endif
private:
struct Item;
struct Line {
Item *from = nullptr;
Ref<TextLine> text_prefix;
float prefix_width = 0;
Ref<TextParagraph> text_buf;
Color dc_color;
int dc_ol_size = 0;
Color dc_ol_color;
Vector2 offset;
int char_offset = 0;
int char_count = 0;
Line() { text_buf.instantiate(); }
_FORCE_INLINE_ float get_height(float line_separation) const {
return offset.y + text_buf->get_size().y + text_buf->get_line_count() * line_separation;
}
};
struct Item {
int index = 0;
int char_ofs = 0;
Item *parent = nullptr;
ItemType type = ITEM_FRAME;
List<Item *> subitems;
List<Item *>::Element *E = nullptr;
ObjectID owner;
int line = 0;
RID rid;
void _clear_children() {
RichTextLabel *owner_rtl = Object::cast_to<RichTextLabel>(ObjectDB::get_instance(owner));
while (subitems.size()) {
Item *subitem = subitems.front()->get();
if (subitem && subitem->rid.is_valid() && owner_rtl) {
owner_rtl->items.free(subitem->rid);
}
memdelete(subitem);
subitems.pop_front();
}
}
virtual ~Item() { _clear_children(); }
};
struct ItemFrame : public Item {
bool cell = false;
LocalVector<Line> lines;
std::atomic<int> first_invalid_line;
std::atomic<int> first_invalid_font_line;
std::atomic<int> first_resized_line;
ItemFrame *parent_frame = nullptr;
Color odd_row_bg = Color(0, 0, 0, 0);
Color even_row_bg = Color(0, 0, 0, 0);
Color border = Color(0, 0, 0, 0);
Size2 min_size_over = Size2(-1, -1);
Size2 max_size_over = Size2(-1, -1);
Rect2 padding;
ItemFrame() {
type = ITEM_FRAME;
first_invalid_line.store(0);
first_invalid_font_line.store(0);
first_resized_line.store(0);
}
};
struct ItemText : public Item {
String text;
ItemText() { type = ITEM_TEXT; }
};
struct ItemDropcap : public Item {
String text;
Ref<Font> font;
int font_size = 0;
Color color;
int ol_size = 0;
Color ol_color;
Rect2 dropcap_margins;
ItemDropcap() { type = ITEM_DROPCAP; }
};
struct ItemImage : public Item {
Ref<Texture2D> image;
InlineAlignment inline_align = INLINE_ALIGNMENT_CENTER;
bool pad = false;
bool size_in_percent = false;
Rect2 region;
Size2 size;
Size2 rq_size;
Color color;
Variant key;
String tooltip;
ItemImage() { type = ITEM_IMAGE; }
~ItemImage() {
if (image.is_valid()) {
RichTextLabel *owner_rtl = Object::cast_to<RichTextLabel>(ObjectDB::get_instance(owner));
if (owner_rtl) {
image->disconnect_changed(callable_mp(owner_rtl, &RichTextLabel::_texture_changed));
}
}
}
};
struct ItemFont : public Item {
DefaultFont def_font = CUSTOM_FONT;
Ref<Font> font;
bool variation = false;
bool def_size = false;
int font_size = 0;
ItemFont() { type = ITEM_FONT; }
};
struct ItemFontSize : public Item {
int font_size = 16;
ItemFontSize() { type = ITEM_FONT_SIZE; }
};
struct ItemColor : public Item {
Color color;
ItemColor() { type = ITEM_COLOR; }
};
struct ItemOutlineSize : public Item {
int outline_size = 0;
ItemOutlineSize() { type = ITEM_OUTLINE_SIZE; }
};
struct ItemOutlineColor : public Item {
Color color;
ItemOutlineColor() { type = ITEM_OUTLINE_COLOR; }
};
struct ItemUnderline : public Item {
ItemUnderline() { type = ITEM_UNDERLINE; }
};
struct ItemStrikethrough : public Item {
ItemStrikethrough() { type = ITEM_STRIKETHROUGH; }
};
struct ItemMeta : public Item {
Variant meta;
MetaUnderline underline = META_UNDERLINE_ALWAYS;
ItemMeta() { type = ITEM_META; }
};
struct ItemHint : public Item {
String description;
ItemHint() { type = ITEM_HINT; }
};
struct ItemLanguage : public Item {
String language;
ItemLanguage() { type = ITEM_LANGUAGE; }
};
struct ItemParagraph : public Item {
HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT;
String language;
Control::TextDirection direction = Control::TEXT_DIRECTION_AUTO;
TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
BitField<TextServer::JustificationFlag> jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE;
PackedFloat32Array tab_stops;
ItemParagraph() { type = ITEM_PARAGRAPH; }
};
struct ItemIndent : public Item {
int level = 0;
ItemIndent() { type = ITEM_INDENT; }
};
struct ItemList : public Item {
ListType list_type = LIST_DOTS;
bool capitalize = false;
int level = 0;
String bullet = U"";
float max_width = 0;
ItemList() { type = ITEM_LIST; }
};
struct ItemNewline : public Item {
ItemNewline() { type = ITEM_NEWLINE; }
};
struct ItemTable : public Item {
struct Column {
bool expand = false;
int expand_ratio = 0;
int min_width = 0;
int max_width = 0;
int width = 0;
};
LocalVector<Column> columns;
LocalVector<float> rows;
LocalVector<float> rows_baseline;
int align_to_row = -1;
int total_width = 0;
int total_height = 0;
InlineAlignment inline_align = INLINE_ALIGNMENT_TOP;
ItemTable() { type = ITEM_TABLE; }
};
struct ItemFade : public Item {
int starting_index = 0;
int length = 0;
ItemFade() { type = ITEM_FADE; }
};
struct ItemFX : public Item {
double elapsed_time = 0.f;
bool connected = true;
};
struct ItemShake : public ItemFX {
int strength = 0;
float rate = 0.0f;
uint64_t _current_rng = 0;
uint64_t _previous_rng = 0;
Vector2 prev_off;
ItemShake() { type = ITEM_SHAKE; }
void reroll_random() {
_previous_rng = _current_rng;
_current_rng = Math::rand();
}
uint64_t offset_random(int p_index) {
return (_current_rng >> (p_index % 64)) |
(_current_rng << (64 - (p_index % 64)));
}
uint64_t offset_previous_random(int p_index) {
return (_previous_rng >> (p_index % 64)) |
(_previous_rng << (64 - (p_index % 64)));
}
};
struct ItemWave : public ItemFX {
float frequency = 1.0f;
float amplitude = 1.0f;
Vector2 prev_off;
ItemWave() { type = ITEM_WAVE; }
};
struct ItemTornado : public ItemFX {
float radius = 1.0f;
float frequency = 1.0f;
Vector2 prev_off;
ItemTornado() { type = ITEM_TORNADO; }
};
struct ItemRainbow : public ItemFX {
float saturation = 0.8f;
float value = 0.8f;
float frequency = 1.0f;
ItemRainbow() { type = ITEM_RAINBOW; }
};
struct ItemPulse : public ItemFX {
Color color = Color(1.0, 1.0, 1.0, 0.25);
float frequency = 1.0f;
float ease = -2.0f;
ItemPulse() { type = ITEM_PULSE; }
};
struct ItemBGColor : public Item {
Color color;
ItemBGColor() { type = ITEM_BGCOLOR; }
};
struct ItemFGColor : public Item {
Color color;
ItemFGColor() { type = ITEM_FGCOLOR; }
};
struct ItemCustomFX : public ItemFX {
Ref<CharFXTransform> char_fx_transform;
Ref<RichTextEffect> custom_effect;
ItemCustomFX();
virtual ~ItemCustomFX();
};
struct ItemContext : public Item {
ItemContext() { type = ITEM_CONTEXT; }
};
ItemFrame *main = nullptr;
Item *current = nullptr;
ItemFrame *current_frame = nullptr;
WorkerThreadPool::TaskID task = WorkerThreadPool::INVALID_TASK_ID;
Mutex data_mutex;
bool threaded = false;
std::atomic<bool> stop_thread;
std::atomic<bool> updating;
std::atomic<bool> validating;
std::atomic<double> loaded;
uint64_t loading_started = 0;
int progress_delay = 1000;
VScrollBar *vscroll = nullptr;
TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_WORD_SMART;
bool scroll_visible = false;
bool scroll_follow = false;
bool scroll_following = false;
bool scroll_active = true;
int scroll_w = 0;
bool scroll_updated = false;
bool updating_scroll = false;
int current_idx = 1;
int current_char_ofs = 0;
int visible_paragraph_count = 0;
int visible_line_count = 0;
int tab_size = 4;
bool underline_meta = true;
bool underline_hint = true;
bool use_selected_font_color = false;
HorizontalAlignment default_alignment = HORIZONTAL_ALIGNMENT_LEFT;
BitField<TextServer::JustificationFlag> default_jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE;
ItemMeta *meta_hovering = nullptr;
Variant current_meta;
Array custom_effects;
void _invalidate_current_line(ItemFrame *p_frame);
void _thread_function(void *p_userdata);
void _thread_end();
void _stop_thread();
bool _validate_line_caches();
void _process_line_caches();
_FORCE_INLINE_ float _update_scroll_exceeds(float p_total_height, float p_ctrl_height, float p_width, int p_idx, float p_old_scroll, float p_text_rect_height);
void _add_item(Item *p_item, bool p_enter = false, bool p_ensure_newline = false);
void _remove_frame(HashSet<Item *> &r_erase_list, ItemFrame *p_frame, int p_line, bool p_erase, int p_char_offset, int p_line_offset);
void _texture_changed(RID p_item);
RID_PtrOwner<Item> items;
String language;
TextDirection text_direction = TEXT_DIRECTION_AUTO;
TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT;
Array st_args;
struct Selection {
ItemFrame *click_frame = nullptr;
int click_line = 0;
Item *click_item = nullptr;
int click_char = 0;
ItemFrame *from_frame = nullptr;
int from_line = 0;
Item *from_item = nullptr;
int from_char = 0;
ItemFrame *to_frame = nullptr;
int to_line = 0;
Item *to_item = nullptr;
int to_char = 0;
bool active = false; // anything selected? i.e. from, to, etc. valid?
bool enabled = false; // allow selections?
bool drag_attempt = false;
};
Selection selection;
bool deselect_on_focus_loss_enabled = true;
bool drag_and_drop_selection_enabled = true;
bool context_menu_enabled = false;
bool shortcut_keys_enabled = true;
// Context menu.
PopupMenu *menu = nullptr;
void _generate_context_menu();
void _update_context_menu();
Key _get_menu_action_accelerator(const String &p_action);
int visible_characters = -1;
float visible_ratio = 1.0;
TextServer::VisibleCharactersBehavior visible_chars_behavior = TextServer::VC_CHARS_BEFORE_SHAPING;
bool _is_click_inside_selection() const;
void _find_click(ItemFrame *p_frame, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool *r_outside = nullptr, bool p_meta = false);
String _get_line_text(ItemFrame *p_frame, int p_line, Selection p_sel) const;
bool _search_line(ItemFrame *p_frame, int p_line, const String &p_string, int p_char_idx, bool p_reverse_search);
bool _search_table(ItemTable *p_table, List<Item *>::Element *p_from, const String &p_string, bool p_reverse_search);
float _shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h, int *r_char_offset);
float _resize_line(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size, int p_width, float p_h);
void _set_table_size(ItemTable *p_table, int p_available_width);
void _update_line_font(ItemFrame *p_frame, int p_line, const Ref<Font> &p_base_font, int p_base_font_size);
int _draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Color &p_base_color, int p_outline_size, const Color &p_outline_color, const Color &p_font_shadow_color, int p_shadow_outline_size, const Point2 &p_shadow_ofs, int &r_processed_glyphs);
float _find_click_in_line(ItemFrame *p_frame, int p_line, const Vector2 &p_ofs, int p_width, const Point2i &p_click, ItemFrame **r_click_frame = nullptr, int *r_click_line = nullptr, Item **r_click_item = nullptr, int *r_click_char = nullptr, bool p_table = false, bool p_meta = false);
String _roman(int p_num, bool p_capitalize) const;
String _letters(int p_num, bool p_capitalize) const;
Item *_find_indentable(Item *p_item);
Item *_get_item_at_pos(Item *p_item_from, Item *p_item_to, int p_position);
void _find_frame(Item *p_item, ItemFrame **r_frame, int *r_line);
ItemFontSize *_find_font_size(Item *p_item);
ItemFont *_find_font(Item *p_item);
int _find_outline_size(Item *p_item, int p_default);
ItemList *_find_list_item(Item *p_item);
ItemDropcap *_find_dc_item(Item *p_item);
int _find_list(Item *p_item, Vector<int> &r_index, Vector<int> &r_count, Vector<ItemList *> &r_list);
int _find_margin(Item *p_item, const Ref<Font> &p_base_font, int p_base_font_size);
PackedFloat32Array _find_tab_stops(Item *p_item);
HorizontalAlignment _find_alignment(Item *p_item);
BitField<TextServer::JustificationFlag> _find_jst_flags(Item *p_item);
TextServer::Direction _find_direction(Item *p_item);
TextServer::StructuredTextParser _find_stt(Item *p_item);
String _find_language(Item *p_item);
Color _find_color(Item *p_item, const Color &p_default_color);
Color _find_outline_color(Item *p_item, const Color &p_default_color);
bool _find_underline(Item *p_item);
bool _find_strikethrough(Item *p_item);
bool _find_meta(Item *p_item, Variant *r_meta, ItemMeta **r_item = nullptr);
bool _find_hint(Item *p_item, String *r_description);
Color _find_bgcolor(Item *p_item);
Color _find_fgcolor(Item *p_item);
bool _find_layout_subitem(Item *from, Item *to);
void _fetch_item_fx_stack(Item *p_item, Vector<ItemFX *> &r_stack);
void _normalize_subtags(Vector<String> &subtags);
void _update_fx(ItemFrame *p_frame, double p_delta_time);
void _scroll_changed(double);
int _find_first_line(int p_from, int p_to, int p_vofs) const;
_FORCE_INLINE_ float _calculate_line_vertical_offset(const Line &line) const;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
virtual String get_tooltip(const Point2 &p_pos) const override;
Item *_get_next_item(Item *p_item, bool p_free = false) const;
Item *_get_prev_item(Item *p_item, bool p_free = false) const;
Rect2 _get_text_rect();
Ref<RichTextEffect> _get_custom_effect_by_code(String p_bbcode_identifier);
virtual Dictionary parse_expressions_for_values(Vector<String> p_expressions);
Size2 _get_image_size(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Rect2 &p_region = Rect2());
String _get_prefix(Item *p_item, const Vector<int> &p_list_index, const Vector<ItemList *> &p_list_items);
#ifndef DISABLE_DEPRECATED
// Kept for compatibility from 3.x to 4.0.
bool _set(const StringName &p_name, const Variant &p_value);
#endif
bool use_bbcode = false;
String text;
void _apply_translation();
bool fit_content = false;
struct ThemeCache {
Ref<StyleBox> normal_style;
Ref<StyleBox> focus_style;
Ref<StyleBox> progress_bg_style;
Ref<StyleBox> progress_fg_style;
int line_separation;
Ref<Font> normal_font;
int normal_font_size;
Color default_color;
Color font_selected_color;
Color selection_color;
Color font_outline_color;
Color font_shadow_color;
int shadow_outline_size;
int shadow_offset_x;
int shadow_offset_y;
int outline_size;
Color outline_color;
Ref<Font> bold_font;
int bold_font_size;
Ref<Font> bold_italics_font;
int bold_italics_font_size;
Ref<Font> italics_font;
int italics_font_size;
Ref<Font> mono_font;
int mono_font_size;
int text_highlight_h_padding;
int text_highlight_v_padding;
int table_h_separation;
int table_v_separation;
Color table_odd_row_bg;
Color table_even_row_bg;
Color table_border;
float base_scale = 1.0;
} theme_cache;
public:
String get_parsed_text() const;
void add_text(const String &p_text);
void add_image(const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), const Variant &p_key = Variant(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false);
void update_image(const Variant &p_key, BitField<ImageUpdateMask> p_mask, const Ref<Texture2D> &p_image, int p_width = 0, int p_height = 0, const Color &p_color = Color(1.0, 1.0, 1.0), InlineAlignment p_alignment = INLINE_ALIGNMENT_CENTER, const Rect2 &p_region = Rect2(), bool p_pad = false, const String &p_tooltip = String(), bool p_size_in_percent = false);
void add_newline();
bool remove_paragraph(int p_paragraph, bool p_no_invalidate = false);
bool invalidate_paragraph(int p_paragraph);
void push_dropcap(const String &p_string, const Ref<Font> &p_font, int p_size, const Rect2 &p_dropcap_margins = Rect2(), const Color &p_color = Color(1, 1, 1), int p_ol_size = 0, const Color &p_ol_color = Color(0, 0, 0, 0));
void _push_def_font(DefaultFont p_def_font);
void _push_def_font_var(DefaultFont p_def_font, const Ref<Font> &p_font, int p_size = -1);
void push_font(const Ref<Font> &p_font, int p_size = 0);
void push_font_size(int p_font_size);
void push_outline_size(int p_font_size);
void push_normal();
void push_bold();
void push_bold_italics();
void push_italics();
void push_mono();
void push_color(const Color &p_color);
void push_outline_color(const Color &p_color);
void push_underline();
void push_strikethrough();
void push_language(const String &p_language);
void push_paragraph(HorizontalAlignment p_alignment, Control::TextDirection p_direction = Control::TEXT_DIRECTION_INHERITED, const String &p_language = "", TextServer::StructuredTextParser p_st_parser = TextServer::STRUCTURED_TEXT_DEFAULT, BitField<TextServer::JustificationFlag> p_jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE, const PackedFloat32Array &p_tab_stops = PackedFloat32Array());
void push_indent(int p_level);
void push_list(int p_level, ListType p_list, bool p_capitalize, const String &p_bullet = String::utf8(""));
void push_meta(const Variant &p_meta, MetaUnderline p_underline_mode = META_UNDERLINE_ALWAYS);
void push_hint(const String &p_string);
void push_table(int p_columns, InlineAlignment p_alignment = INLINE_ALIGNMENT_TOP, int p_align_to_row = -1);
void push_fade(int p_start_index, int p_length);
void push_shake(int p_strength, float p_rate, bool p_connected);
void push_wave(float p_frequency, float p_amplitude, bool p_connected);
void push_tornado(float p_frequency, float p_radius, bool p_connected);
void push_rainbow(float p_saturation, float p_value, float p_frequency);
void push_pulse(const Color &p_color, float p_frequency, float p_ease);
void push_bgcolor(const Color &p_color);
void push_fgcolor(const Color &p_color);
void push_customfx(Ref<RichTextEffect> p_custom_effect, Dictionary p_environment);
void push_context();
void set_table_column_expand(int p_column, bool p_expand, int p_ratio = 1);
void set_cell_row_background_color(const Color &p_odd_row_bg, const Color &p_even_row_bg);
void set_cell_border_color(const Color &p_color);
void set_cell_size_override(const Size2 &p_min_size, const Size2 &p_max_size);
void set_cell_padding(const Rect2 &p_padding);
int get_current_table_column() const;
void push_cell();
void pop();
void pop_context();
void pop_all();
void clear();
void set_offset(int p_pixel);
void set_meta_underline(bool p_underline);
bool is_meta_underlined() const;
void set_hint_underline(bool p_underline);
bool is_hint_underlined() const;
void set_scroll_active(bool p_active);
bool is_scroll_active() const;
void set_scroll_follow(bool p_follow);
bool is_scroll_following() const;
void set_tab_size(int p_spaces);
int get_tab_size() const;
void set_context_menu_enabled(bool p_enabled);
bool is_context_menu_enabled() const;
void set_shortcut_keys_enabled(bool p_enabled);
bool is_shortcut_keys_enabled() const;
void set_fit_content(bool p_enabled);
bool is_fit_content_enabled() const;
bool search(const String &p_string, bool p_from_selection = false, bool p_search_previous = false);
void scroll_to_paragraph(int p_paragraph);
int get_paragraph_count() const;
int get_visible_paragraph_count() const;
float get_line_offset(int p_line);
float get_paragraph_offset(int p_paragraph);
void scroll_to_line(int p_line);
int get_line_count() const;
int get_visible_line_count() const;
int get_content_height() const;
int get_content_width() const;
void scroll_to_selection();
VScrollBar *get_v_scroll_bar() { return vscroll; }
virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override;
virtual Variant get_drag_data(const Point2 &p_point) override;
void set_selection_enabled(bool p_enabled);
bool is_selection_enabled() const;
int get_selection_from() const;
int get_selection_to() const;
String get_selected_text() const;
void select_all();
void selection_copy();
void set_deselect_on_focus_loss_enabled(const bool p_enabled);
bool is_deselect_on_focus_loss_enabled() const;
void set_drag_and_drop_selection_enabled(const bool p_enabled);
bool is_drag_and_drop_selection_enabled() const;
void deselect();
int get_pending_paragraphs() const;
bool is_ready() const;
bool is_updating() const;
void set_threaded(bool p_threaded);
bool is_threaded() const;
void set_progress_bar_delay(int p_delay_ms);
int get_progress_bar_delay() const;
// Context menu.
PopupMenu *get_menu() const;
bool is_menu_visible() const;
void menu_option(int p_option);
void parse_bbcode(const String &p_bbcode);
void append_text(const String &p_bbcode);
void set_use_bbcode(bool p_enable);
bool is_using_bbcode() const;
void set_text(const String &p_bbcode);
String get_text() const;
void set_text_direction(TextDirection p_text_direction);
TextDirection get_text_direction() const;
void set_language(const String &p_language);
String get_language() const;
void set_autowrap_mode(TextServer::AutowrapMode p_mode);
TextServer::AutowrapMode get_autowrap_mode() const;
void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser);
TextServer::StructuredTextParser get_structured_text_bidi_override() const;
void set_structured_text_bidi_override_options(Array p_args);
Array get_structured_text_bidi_override_options() const;
void set_visible_characters(int p_visible);
int get_visible_characters() const;
int get_character_line(int p_char);
int get_character_paragraph(int p_char);
int get_total_character_count() const;
int get_total_glyph_count() const;
void set_visible_ratio(float p_ratio);
float get_visible_ratio() const;
TextServer::VisibleCharactersBehavior get_visible_characters_behavior() const;
void set_visible_characters_behavior(TextServer::VisibleCharactersBehavior p_behavior);
void set_effects(Array p_effects);
Array get_effects();
void install_effect(const Variant effect);
virtual Size2 get_minimum_size() const override;
RichTextLabel(const String &p_text = String());
~RichTextLabel();
};
VARIANT_ENUM_CAST(RichTextLabel::ListType);
VARIANT_ENUM_CAST(RichTextLabel::MenuItems);
VARIANT_ENUM_CAST(RichTextLabel::MetaUnderline);
VARIANT_BITFIELD_CAST(RichTextLabel::ImageUpdateMask);
#endif // RICH_TEXT_LABEL_H

View file

@ -0,0 +1,663 @@
/**************************************************************************/
/* scroll_bar.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 "scroll_bar.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
#include "scene/main/window.h"
#include "scene/theme/theme_db.h"
bool ScrollBar::focus_by_default = false;
void ScrollBar::set_can_focus_by_default(bool p_can_focus) {
focus_by_default = p_can_focus;
}
void ScrollBar::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
Ref<InputEventMouseMotion> m = p_event;
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
accept_event();
if (b->get_button_index() == MouseButton::WHEEL_DOWN && b->is_pressed()) {
double change = get_page() != 0.0 ? get_page() / 4.0 : (get_max() - get_min()) / 16.0;
scroll(MAX(change, get_step()));
accept_event();
}
if (b->get_button_index() == MouseButton::WHEEL_UP && b->is_pressed()) {
double change = get_page() != 0.0 ? get_page() / 4.0 : (get_max() - get_min()) / 16.0;
scroll(-MAX(change, get_step()));
accept_event();
}
if (b->get_button_index() != MouseButton::LEFT) {
return;
}
if (b->is_pressed()) {
double ofs = orientation == VERTICAL ? b->get_position().y : b->get_position().x;
Ref<Texture2D> decr = theme_cache.decrement_icon;
Ref<Texture2D> incr = theme_cache.increment_icon;
double decr_size = orientation == VERTICAL ? decr->get_height() : decr->get_width();
double incr_size = orientation == VERTICAL ? incr->get_height() : incr->get_width();
double grabber_ofs = get_grabber_offset();
double grabber_size = get_grabber_size();
double total = orientation == VERTICAL ? get_size().height : get_size().width;
if (ofs < decr_size) {
decr_active = true;
scroll(-(custom_step >= 0 ? custom_step : get_step()));
queue_redraw();
return;
}
if (ofs > total - incr_size) {
incr_active = true;
scroll(custom_step >= 0 ? custom_step : get_step());
queue_redraw();
return;
}
ofs -= decr_size;
if (ofs < grabber_ofs) {
if (scrolling) {
target_scroll = CLAMP(target_scroll - get_page(), get_min(), get_max() - get_page());
} else {
double change = get_page() != 0.0 ? get_page() : (get_max() - get_min()) / 16.0;
target_scroll = CLAMP(get_value() - change, get_min(), get_max() - get_page());
}
if (smooth_scroll_enabled) {
scrolling = true;
set_physics_process_internal(true);
} else {
scroll_to(target_scroll);
}
return;
}
ofs -= grabber_ofs;
if (ofs < grabber_size) {
drag.active = true;
drag.pos_at_click = grabber_ofs + ofs;
drag.value_at_click = get_as_ratio();
queue_redraw();
} else {
if (scrolling) {
target_scroll = CLAMP(target_scroll + get_page(), get_min(), get_max() - get_page());
} else {
double change = get_page() != 0.0 ? get_page() : (get_max() - get_min()) / 16.0;
target_scroll = CLAMP(get_value() + change, get_min(), get_max() - get_page());
}
if (smooth_scroll_enabled) {
scrolling = true;
set_physics_process_internal(true);
} else {
scroll_to(target_scroll);
}
}
} else {
incr_active = false;
decr_active = false;
drag.active = false;
queue_redraw();
}
}
if (m.is_valid()) {
accept_event();
if (drag.active) {
double ofs = orientation == VERTICAL ? m->get_position().y : m->get_position().x;
Ref<Texture2D> decr = theme_cache.decrement_icon;
double decr_size = orientation == VERTICAL ? decr->get_height() : decr->get_width();
ofs -= decr_size;
double diff = (ofs - drag.pos_at_click) / get_area_size();
double prev_scroll = get_value();
set_as_ratio(drag.value_at_click + diff);
if (!Math::is_equal_approx(prev_scroll, get_value())) {
emit_signal(SNAME("scrolling"));
}
} else {
double ofs = orientation == VERTICAL ? m->get_position().y : m->get_position().x;
Ref<Texture2D> decr = theme_cache.decrement_icon;
Ref<Texture2D> incr = theme_cache.increment_icon;
double decr_size = orientation == VERTICAL ? decr->get_height() : decr->get_width();
double incr_size = orientation == VERTICAL ? incr->get_height() : incr->get_width();
double total = orientation == VERTICAL ? get_size().height : get_size().width;
HighlightStatus new_hilite;
if (ofs < decr_size) {
new_hilite = HIGHLIGHT_DECR;
} else if (ofs > total - incr_size) {
new_hilite = HIGHLIGHT_INCR;
} else {
new_hilite = HIGHLIGHT_RANGE;
}
if (new_hilite != highlight) {
highlight = new_hilite;
queue_redraw();
}
}
}
if (p_event->is_pressed()) {
if (p_event->is_action("ui_left", true)) {
if (orientation != HORIZONTAL) {
return;
}
scroll(-(custom_step >= 0 ? custom_step : get_step()));
} else if (p_event->is_action("ui_right", true)) {
if (orientation != HORIZONTAL) {
return;
}
scroll(custom_step >= 0 ? custom_step : get_step());
} else if (p_event->is_action("ui_up", true)) {
if (orientation != VERTICAL) {
return;
}
scroll(-(custom_step >= 0 ? custom_step : get_step()));
} else if (p_event->is_action("ui_down", true)) {
if (orientation != VERTICAL) {
return;
}
scroll(custom_step >= 0 ? custom_step : get_step());
} else if (p_event->is_action("ui_home", true)) {
scroll_to(get_min());
} else if (p_event->is_action("ui_end", true)) {
scroll_to(get_max());
}
}
}
void ScrollBar::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
RID ci = get_canvas_item();
Ref<Texture2D> decr, incr;
if (decr_active) {
decr = theme_cache.decrement_pressed_icon;
} else if (highlight == HIGHLIGHT_DECR) {
decr = theme_cache.decrement_hl_icon;
} else {
decr = theme_cache.decrement_icon;
}
if (incr_active) {
incr = theme_cache.increment_pressed_icon;
} else if (highlight == HIGHLIGHT_INCR) {
incr = theme_cache.increment_hl_icon;
} else {
incr = theme_cache.increment_icon;
}
Ref<StyleBox> bg = has_focus() ? theme_cache.scroll_focus_style : theme_cache.scroll_style;
Ref<StyleBox> grabber;
if (drag.active) {
grabber = theme_cache.grabber_pressed_style;
} else if (highlight == HIGHLIGHT_RANGE) {
grabber = theme_cache.grabber_hl_style;
} else {
grabber = theme_cache.grabber_style;
}
Point2 ofs;
decr->draw(ci, Point2());
if (orientation == HORIZONTAL) {
ofs.x += decr->get_width();
} else {
ofs.y += decr->get_height();
}
Size2 area = get_size();
if (orientation == HORIZONTAL) {
area.width -= incr->get_width() + decr->get_width();
} else {
area.height -= incr->get_height() + decr->get_height();
}
bg->draw(ci, Rect2(ofs, area));
if (orientation == HORIZONTAL) {
ofs.width += area.width;
} else {
ofs.height += area.height;
}
incr->draw(ci, ofs);
Rect2 grabber_rect;
if (orientation == HORIZONTAL) {
grabber_rect.size.width = get_grabber_size();
grabber_rect.size.height = get_size().height;
grabber_rect.position.y = 0;
grabber_rect.position.x = get_grabber_offset() + decr->get_width() + bg->get_margin(SIDE_LEFT);
} else {
grabber_rect.size.width = get_size().width;
grabber_rect.size.height = get_grabber_size();
grabber_rect.position.y = get_grabber_offset() + decr->get_height() + bg->get_margin(SIDE_TOP);
grabber_rect.position.x = 0;
}
grabber->draw(ci, grabber_rect);
} break;
case NOTIFICATION_ENTER_TREE: {
if (has_node(drag_node_path)) {
Node *n = get_node(drag_node_path);
drag_node = Object::cast_to<Control>(n);
}
if (drag_node) {
drag_node->connect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input));
drag_node->connect(SceneStringName(tree_exiting), callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT);
}
} break;
case NOTIFICATION_EXIT_TREE: {
if (drag_node) {
drag_node->disconnect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input));
drag_node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &ScrollBar::_drag_node_exit));
}
drag_node = nullptr;
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (scrolling) {
if (get_value() != target_scroll) {
double target = target_scroll - get_value();
double dist = abs(target);
double vel = ((target / dist) * 500) * get_physics_process_delta_time();
if (Math::abs(vel) >= dist) {
scroll_to(target_scroll);
scrolling = false;
set_physics_process_internal(false);
} else {
scroll(vel);
}
} else {
scrolling = false;
set_physics_process_internal(false);
}
} else if (drag_node_touching) {
if (drag_node_touching_deaccel) {
Vector2 pos = Vector2(orientation == HORIZONTAL ? get_value() : 0, orientation == VERTICAL ? get_value() : 0);
pos += drag_node_speed * get_physics_process_delta_time();
bool turnoff = false;
if (orientation == HORIZONTAL) {
if (pos.x < 0) {
pos.x = 0;
turnoff = true;
}
if (pos.x > (get_max() - get_page())) {
pos.x = get_max() - get_page();
turnoff = true;
}
scroll_to(pos.x);
float sgn_x = drag_node_speed.x < 0 ? -1 : 1;
float val_x = Math::abs(drag_node_speed.x);
val_x -= 1000 * get_physics_process_delta_time();
if (val_x < 0) {
turnoff = true;
}
drag_node_speed.x = sgn_x * val_x;
} else {
if (pos.y < 0) {
pos.y = 0;
turnoff = true;
}
if (pos.y > (get_max() - get_page())) {
pos.y = get_max() - get_page();
turnoff = true;
}
scroll_to(pos.y);
float sgn_y = drag_node_speed.y < 0 ? -1 : 1;
float val_y = Math::abs(drag_node_speed.y);
val_y -= 1000 * get_physics_process_delta_time();
if (val_y < 0) {
turnoff = true;
}
drag_node_speed.y = sgn_y * val_y;
}
if (turnoff) {
set_physics_process_internal(false);
drag_node_touching = false;
drag_node_touching_deaccel = false;
}
} else {
if (time_since_motion == 0 || time_since_motion > 0.1) {
Vector2 diff = drag_node_accum - last_drag_node_accum;
last_drag_node_accum = drag_node_accum;
drag_node_speed = diff / get_physics_process_delta_time();
}
time_since_motion += get_physics_process_delta_time();
}
}
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible()) {
incr_active = false;
decr_active = false;
drag.active = false;
}
} break;
case NOTIFICATION_MOUSE_EXIT: {
highlight = HIGHLIGHT_NONE;
queue_redraw();
} break;
}
}
double ScrollBar::get_grabber_min_size() const {
Ref<StyleBox> grabber = theme_cache.grabber_style;
Size2 gminsize = grabber->get_minimum_size();
return (orientation == VERTICAL) ? gminsize.height : gminsize.width;
}
double ScrollBar::get_grabber_size() const {
float range = get_max() - get_min();
if (range <= 0) {
return 0;
}
float page = (get_page() > 0) ? get_page() : 0;
double area_size = get_area_size();
double grabber_size = page / range * area_size;
return grabber_size + get_grabber_min_size();
}
double ScrollBar::get_area_size() const {
switch (orientation) {
case VERTICAL: {
double area = get_size().height;
area -= theme_cache.scroll_style->get_minimum_size().height;
area -= theme_cache.increment_icon->get_height();
area -= theme_cache.decrement_icon->get_height();
area -= get_grabber_min_size();
return area;
} break;
case HORIZONTAL: {
double area = get_size().width;
area -= theme_cache.scroll_style->get_minimum_size().width;
area -= theme_cache.increment_icon->get_width();
area -= theme_cache.decrement_icon->get_width();
area -= get_grabber_min_size();
return area;
} break;
default: {
return 0.0;
}
}
}
double ScrollBar::get_grabber_offset() const {
return (get_area_size()) * get_as_ratio();
}
Size2 ScrollBar::get_minimum_size() const {
Ref<Texture2D> incr = theme_cache.increment_icon;
Ref<Texture2D> decr = theme_cache.decrement_icon;
Ref<StyleBox> bg = theme_cache.scroll_style;
Size2 minsize;
if (orientation == VERTICAL) {
minsize.width = MAX(incr->get_size().width, bg->get_minimum_size().width);
minsize.height += incr->get_size().height;
minsize.height += decr->get_size().height;
minsize.height += bg->get_minimum_size().height;
minsize.height += get_grabber_min_size();
}
if (orientation == HORIZONTAL) {
minsize.height = MAX(incr->get_size().height, bg->get_minimum_size().height);
minsize.width += incr->get_size().width;
minsize.width += decr->get_size().width;
minsize.width += bg->get_minimum_size().width;
minsize.width += get_grabber_min_size();
}
return minsize;
}
void ScrollBar::scroll(double p_amount) {
scroll_to(get_value() + p_amount);
}
void ScrollBar::scroll_to(double p_position) {
double prev_scroll = get_value();
set_value(p_position);
if (!Math::is_equal_approx(prev_scroll, get_value())) {
emit_signal(SNAME("scrolling"));
}
}
void ScrollBar::set_custom_step(float p_custom_step) {
custom_step = p_custom_step;
}
float ScrollBar::get_custom_step() const {
return custom_step;
}
void ScrollBar::_drag_node_exit() {
if (drag_node) {
drag_node->disconnect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input));
}
drag_node = nullptr;
}
void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) {
if (!drag_node_enabled) {
return;
}
Ref<InputEventMouseButton> mb = p_input;
if (mb.is_valid()) {
if (mb->get_button_index() != MouseButton::LEFT) {
return;
}
if (mb->is_pressed()) {
drag_node_speed = Vector2();
drag_node_accum = Vector2();
last_drag_node_accum = Vector2();
drag_node_from = Vector2(orientation == HORIZONTAL ? get_value() : 0, orientation == VERTICAL ? get_value() : 0);
drag_node_touching = DisplayServer::get_singleton()->is_touchscreen_available();
drag_node_touching_deaccel = false;
time_since_motion = 0;
if (drag_node_touching) {
set_physics_process_internal(true);
time_since_motion = 0;
}
} else {
if (drag_node_touching) {
if (drag_node_speed == Vector2()) {
drag_node_touching_deaccel = false;
drag_node_touching = false;
set_physics_process_internal(false);
} else {
drag_node_touching_deaccel = true;
}
}
}
}
Ref<InputEventMouseMotion> mm = p_input;
if (mm.is_valid()) {
if (drag_node_touching && !drag_node_touching_deaccel) {
Vector2 motion = mm->get_relative();
drag_node_accum -= motion;
Vector2 diff = drag_node_from + drag_node_accum;
if (orientation == HORIZONTAL) {
scroll_to(diff.x);
}
if (orientation == VERTICAL) {
scroll_to(diff.y);
}
time_since_motion = 0;
}
}
}
void ScrollBar::set_drag_node(const NodePath &p_path) {
if (is_inside_tree()) {
if (drag_node) {
drag_node->disconnect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input));
drag_node->disconnect(SceneStringName(tree_exiting), callable_mp(this, &ScrollBar::_drag_node_exit));
}
}
drag_node = nullptr;
drag_node_path = p_path;
if (is_inside_tree()) {
if (has_node(p_path)) {
Node *n = get_node(p_path);
drag_node = Object::cast_to<Control>(n);
}
if (drag_node) {
drag_node->connect(SceneStringName(gui_input), callable_mp(this, &ScrollBar::_drag_node_input));
drag_node->connect(SceneStringName(tree_exiting), callable_mp(this, &ScrollBar::_drag_node_exit), CONNECT_ONE_SHOT);
}
}
}
NodePath ScrollBar::get_drag_node() const {
return drag_node_path;
}
void ScrollBar::set_drag_node_enabled(bool p_enable) {
drag_node_enabled = p_enable;
}
void ScrollBar::set_smooth_scroll_enabled(bool p_enable) {
smooth_scroll_enabled = p_enable;
}
bool ScrollBar::is_smooth_scroll_enabled() const {
return smooth_scroll_enabled;
}
void ScrollBar::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_custom_step", "step"), &ScrollBar::set_custom_step);
ClassDB::bind_method(D_METHOD("get_custom_step"), &ScrollBar::get_custom_step);
ADD_SIGNAL(MethodInfo("scrolling"));
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_step", PROPERTY_HINT_RANGE, "-1,4096,suffix:px"), "set_custom_step", "get_custom_step");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollBar, scroll_style, "scroll");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollBar, scroll_focus_style, "scroll_focus");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollBar, grabber_style, "grabber");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollBar, grabber_hl_style, "grabber_highlight");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollBar, grabber_pressed_style, "grabber_pressed");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, ScrollBar, increment_icon, "increment");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, ScrollBar, increment_hl_icon, "increment_highlight");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, ScrollBar, increment_pressed_icon, "increment_pressed");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, ScrollBar, decrement_icon, "decrement");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, ScrollBar, decrement_hl_icon, "decrement_highlight");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, ScrollBar, decrement_pressed_icon, "decrement_pressed");
}
ScrollBar::ScrollBar(Orientation p_orientation) {
orientation = p_orientation;
if (focus_by_default) {
set_focus_mode(FOCUS_ALL);
}
set_step(0);
}
ScrollBar::~ScrollBar() {
}

View file

@ -0,0 +1,148 @@
/**************************************************************************/
/* scroll_bar.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. */
/**************************************************************************/
#ifndef SCROLL_BAR_H
#define SCROLL_BAR_H
#include "scene/gui/range.h"
class ScrollBar : public Range {
GDCLASS(ScrollBar, Range);
enum HighlightStatus {
HIGHLIGHT_NONE,
HIGHLIGHT_DECR,
HIGHLIGHT_RANGE,
HIGHLIGHT_INCR,
};
static bool focus_by_default;
Orientation orientation;
Size2 size;
float custom_step = -1.0;
HighlightStatus highlight = HIGHLIGHT_NONE;
bool incr_active = false;
bool decr_active = false;
struct Drag {
bool active = false;
float pos_at_click = 0.0;
float value_at_click = 0.0;
} drag;
double get_grabber_size() const;
double get_grabber_min_size() const;
double get_area_size() const;
double get_grabber_offset() const;
static void set_can_focus_by_default(bool p_can_focus);
Node *drag_node = nullptr;
NodePath drag_node_path;
bool drag_node_enabled = true;
Vector2 drag_node_speed;
Vector2 drag_node_accum;
Vector2 drag_node_from;
Vector2 last_drag_node_accum;
float last_drag_node_time = 0.0;
float time_since_motion = 0.0;
bool drag_node_touching = false;
bool drag_node_touching_deaccel = false;
bool click_handled = false;
bool scrolling = false;
double target_scroll = 0.0;
bool smooth_scroll_enabled = false;
struct ThemeCache {
Ref<StyleBox> scroll_style;
Ref<StyleBox> scroll_focus_style;
Ref<StyleBox> scroll_offset_style;
Ref<StyleBox> grabber_style;
Ref<StyleBox> grabber_hl_style;
Ref<StyleBox> grabber_pressed_style;
Ref<Texture2D> increment_icon;
Ref<Texture2D> increment_hl_icon;
Ref<Texture2D> increment_pressed_icon;
Ref<Texture2D> decrement_icon;
Ref<Texture2D> decrement_hl_icon;
Ref<Texture2D> decrement_pressed_icon;
} theme_cache;
void _drag_node_exit();
void _drag_node_input(const Ref<InputEvent> &p_input);
virtual void gui_input(const Ref<InputEvent> &p_event) override;
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void scroll(double p_amount);
void scroll_to(double p_position);
void set_custom_step(float p_custom_step);
float get_custom_step() const;
void set_drag_node(const NodePath &p_path);
NodePath get_drag_node() const;
void set_drag_node_enabled(bool p_enable);
void set_smooth_scroll_enabled(bool p_enable);
bool is_smooth_scroll_enabled() const;
virtual Size2 get_minimum_size() const override;
ScrollBar(Orientation p_orientation = VERTICAL);
~ScrollBar();
};
class HScrollBar : public ScrollBar {
GDCLASS(HScrollBar, ScrollBar);
public:
HScrollBar() :
ScrollBar(HORIZONTAL) { set_v_size_flags(0); }
};
class VScrollBar : public ScrollBar {
GDCLASS(VScrollBar, ScrollBar);
public:
VScrollBar() :
ScrollBar(VERTICAL) { set_h_size_flags(0); }
};
#endif // SCROLL_BAR_H

View file

@ -0,0 +1,634 @@
/**************************************************************************/
/* scroll_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 "scroll_container.h"
#include "core/config/project_settings.h"
#include "scene/main/window.h"
#include "scene/theme/theme_db.h"
Size2 ScrollContainer::get_minimum_size() const {
Size2 min_size;
// Calculated in this function, as it needs to traverse all child controls once to calculate;
// and needs to be calculated before being used by update_scrollbars().
largest_child_min_size = Size2();
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i), SortableVisbilityMode::VISIBLE);
if (!c) {
continue;
}
if (c == h_scroll || c == v_scroll) {
continue;
}
Size2 child_min_size = c->get_combined_minimum_size();
largest_child_min_size = largest_child_min_size.max(child_min_size);
}
if (horizontal_scroll_mode == SCROLL_MODE_DISABLED) {
min_size.x = MAX(min_size.x, largest_child_min_size.x);
}
if (vertical_scroll_mode == SCROLL_MODE_DISABLED) {
min_size.y = MAX(min_size.y, largest_child_min_size.y);
}
bool h_scroll_show = horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.x > min_size.x);
bool v_scroll_show = vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.y > min_size.y);
if (h_scroll_show && h_scroll->get_parent() == this) {
min_size.y += h_scroll->get_minimum_size().y;
}
if (v_scroll_show && v_scroll->get_parent() == this) {
min_size.x += v_scroll->get_minimum_size().x;
}
min_size += theme_cache.panel_style->get_minimum_size();
return min_size;
}
void ScrollContainer::_cancel_drag() {
set_physics_process_internal(false);
drag_touching_deaccel = false;
drag_touching = false;
drag_speed = Vector2();
drag_accum = Vector2();
last_drag_accum = Vector2();
drag_from = Vector2();
if (beyond_deadzone) {
emit_signal(SNAME("scroll_ended"));
propagate_notification(NOTIFICATION_SCROLL_END);
beyond_deadzone = false;
}
}
void ScrollContainer::gui_input(const Ref<InputEvent> &p_gui_input) {
ERR_FAIL_COND(p_gui_input.is_null());
double prev_v_scroll = v_scroll->get_value();
double prev_h_scroll = h_scroll->get_value();
bool h_scroll_enabled = horizontal_scroll_mode != SCROLL_MODE_DISABLED;
bool v_scroll_enabled = vertical_scroll_mode != SCROLL_MODE_DISABLED;
Ref<InputEventMouseButton> mb = p_gui_input;
if (mb.is_valid()) {
if (mb->is_pressed()) {
bool scroll_value_modified = false;
bool v_scroll_hidden = !v_scroll->is_visible() && vertical_scroll_mode != SCROLL_MODE_SHOW_NEVER;
if (mb->get_button_index() == MouseButton::WHEEL_UP) {
// By default, the vertical orientation takes precedence. This is an exception.
if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) {
h_scroll->scroll(-h_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
} else if (v_scroll_enabled) {
v_scroll->scroll(-v_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
}
}
if (mb->get_button_index() == MouseButton::WHEEL_DOWN) {
if ((h_scroll_enabled && mb->is_shift_pressed()) || v_scroll_hidden) {
h_scroll->scroll(h_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
} else if (v_scroll_enabled) {
v_scroll->scroll(v_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
}
}
bool h_scroll_hidden = !h_scroll->is_visible() && horizontal_scroll_mode != SCROLL_MODE_SHOW_NEVER;
if (mb->get_button_index() == MouseButton::WHEEL_LEFT) {
// By default, the horizontal orientation takes precedence. This is an exception.
if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) {
v_scroll->scroll(-v_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
} else if (h_scroll_enabled) {
h_scroll->scroll(-h_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
}
}
if (mb->get_button_index() == MouseButton::WHEEL_RIGHT) {
if ((v_scroll_enabled && mb->is_shift_pressed()) || h_scroll_hidden) {
v_scroll->scroll(v_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
} else if (h_scroll_enabled) {
h_scroll->scroll(h_scroll->get_page() / 8 * mb->get_factor());
scroll_value_modified = true;
}
}
if (scroll_value_modified && (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll)) {
accept_event(); // Accept event if scroll changed.
return;
}
}
bool is_touchscreen_available = DisplayServer::get_singleton()->is_touchscreen_available();
if (!is_touchscreen_available) {
return;
}
if (mb->get_button_index() != MouseButton::LEFT) {
return;
}
if (mb->is_pressed()) {
if (drag_touching) {
_cancel_drag();
}
drag_speed = Vector2();
drag_accum = Vector2();
last_drag_accum = Vector2();
drag_from = Vector2(prev_h_scroll, prev_v_scroll);
drag_touching = true;
drag_touching_deaccel = false;
beyond_deadzone = false;
time_since_motion = 0;
set_physics_process_internal(true);
time_since_motion = 0;
} else {
if (drag_touching) {
if (drag_speed == Vector2()) {
_cancel_drag();
} else {
drag_touching_deaccel = true;
}
}
}
return;
}
Ref<InputEventMouseMotion> mm = p_gui_input;
if (mm.is_valid()) {
if (drag_touching && !drag_touching_deaccel) {
Vector2 motion = mm->get_relative();
drag_accum -= motion;
if (beyond_deadzone || (h_scroll_enabled && Math::abs(drag_accum.x) > deadzone) || (v_scroll_enabled && Math::abs(drag_accum.y) > deadzone)) {
if (!beyond_deadzone) {
propagate_notification(NOTIFICATION_SCROLL_BEGIN);
emit_signal(SNAME("scroll_started"));
beyond_deadzone = true;
// Resetting drag_accum here ensures smooth scrolling after reaching deadzone.
drag_accum = -motion;
}
Vector2 diff = drag_from + drag_accum;
if (h_scroll_enabled) {
h_scroll->scroll_to(diff.x);
} else {
drag_accum.x = 0;
}
if (v_scroll_enabled) {
v_scroll->scroll_to(diff.y);
} else {
drag_accum.y = 0;
}
time_since_motion = 0;
}
}
if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
accept_event(); // Accept event if scroll changed.
}
return;
}
Ref<InputEventPanGesture> pan_gesture = p_gui_input;
if (pan_gesture.is_valid()) {
if (h_scroll_enabled) {
h_scroll->scroll(h_scroll->get_page() * pan_gesture->get_delta().x / 8);
}
if (v_scroll_enabled) {
v_scroll->scroll(v_scroll->get_page() * pan_gesture->get_delta().y / 8);
}
if (v_scroll->get_value() != prev_v_scroll || h_scroll->get_value() != prev_h_scroll) {
accept_event(); // Accept event if scroll changed.
}
return;
}
}
void ScrollContainer::_update_scrollbar_position() {
if (!_updating_scrollbars) {
return;
}
Size2 hmin = h_scroll->is_visible() ? h_scroll->get_combined_minimum_size() : Size2();
Size2 vmin = v_scroll->is_visible() ? v_scroll->get_combined_minimum_size() : Size2();
int lmar = is_layout_rtl() ? theme_cache.panel_style->get_margin(SIDE_RIGHT) : theme_cache.panel_style->get_margin(SIDE_LEFT);
int rmar = is_layout_rtl() ? theme_cache.panel_style->get_margin(SIDE_LEFT) : theme_cache.panel_style->get_margin(SIDE_RIGHT);
h_scroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_BEGIN, lmar);
h_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, -rmar - vmin.width);
h_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -hmin.height - theme_cache.panel_style->get_margin(SIDE_BOTTOM));
h_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -theme_cache.panel_style->get_margin(SIDE_BOTTOM));
v_scroll->set_anchor_and_offset(SIDE_LEFT, ANCHOR_END, -vmin.width - rmar);
v_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, -rmar);
v_scroll->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, theme_cache.panel_style->get_margin(SIDE_TOP));
v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -hmin.height - theme_cache.panel_style->get_margin(SIDE_BOTTOM));
_updating_scrollbars = false;
}
void ScrollContainer::_gui_focus_changed(Control *p_control) {
if (follow_focus && is_ancestor_of(p_control)) {
ensure_control_visible(p_control);
}
}
void ScrollContainer::ensure_control_visible(Control *p_control) {
ERR_FAIL_COND_MSG(!is_ancestor_of(p_control), "Must be an ancestor of the control.");
Rect2 global_rect = get_global_rect();
Rect2 other_rect = p_control->get_global_rect();
float side_margin = v_scroll->is_visible() ? v_scroll->get_size().x : 0.0f;
float bottom_margin = h_scroll->is_visible() ? h_scroll->get_size().y : 0.0f;
Vector2 diff = Vector2(MAX(MIN(other_rect.position.x - (is_layout_rtl() ? side_margin : 0.0f), global_rect.position.x), other_rect.position.x + other_rect.size.x - global_rect.size.x + (!is_layout_rtl() ? side_margin : 0.0f)),
MAX(MIN(other_rect.position.y, global_rect.position.y), other_rect.position.y + other_rect.size.y - global_rect.size.y + bottom_margin));
set_h_scroll(get_h_scroll() + (diff.x - global_rect.position.x));
set_v_scroll(get_v_scroll() + (diff.y - global_rect.position.y));
}
void ScrollContainer::_reposition_children() {
update_scrollbars();
Size2 size = get_size();
Point2 ofs;
size -= theme_cache.panel_style->get_minimum_size();
ofs += theme_cache.panel_style->get_offset();
bool rtl = is_layout_rtl();
if (h_scroll->is_visible_in_tree() && h_scroll->get_parent() == this) { //scrolls may have been moved out for reasons
size.y -= h_scroll->get_minimum_size().y;
}
if (v_scroll->is_visible_in_tree() && v_scroll->get_parent() == this) { //scrolls may have been moved out for reasons
size.x -= v_scroll->get_minimum_size().x;
}
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
if (c == h_scroll || c == v_scroll) {
continue;
}
Size2 minsize = c->get_combined_minimum_size();
Rect2 r = Rect2(-Size2(get_h_scroll(), get_v_scroll()), minsize);
if (c->get_h_size_flags().has_flag(SIZE_EXPAND)) {
r.size.width = MAX(size.width, minsize.width);
}
if (c->get_v_size_flags().has_flag(SIZE_EXPAND)) {
r.size.height = MAX(size.height, minsize.height);
}
r.position += ofs;
if (rtl && v_scroll->is_visible_in_tree() && v_scroll->get_parent() == this) {
r.position.x += v_scroll->get_minimum_size().x;
}
r.position = r.position.floor();
fit_child_in_rect(c, r);
}
queue_redraw();
}
void ScrollContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {
_updating_scrollbars = true;
callable_mp(this, is_ready() ? &ScrollContainer::_reposition_children : &ScrollContainer::_update_scrollbar_position).call_deferred();
} break;
case NOTIFICATION_READY: {
Viewport *viewport = get_viewport();
ERR_FAIL_NULL(viewport);
viewport->connect("gui_focus_changed", callable_mp(this, &ScrollContainer::_gui_focus_changed));
_reposition_children();
} break;
case NOTIFICATION_SORT_CHILDREN: {
_reposition_children();
} break;
case NOTIFICATION_DRAW: {
draw_style_box(theme_cache.panel_style, Rect2(Vector2(), get_size()));
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (drag_touching) {
if (drag_touching_deaccel) {
Vector2 pos = Vector2(h_scroll->get_value(), v_scroll->get_value());
pos += drag_speed * get_physics_process_delta_time();
bool turnoff_h = false;
bool turnoff_v = false;
if (pos.x < 0) {
pos.x = 0;
turnoff_h = true;
}
if (pos.x > (h_scroll->get_max() - h_scroll->get_page())) {
pos.x = h_scroll->get_max() - h_scroll->get_page();
turnoff_h = true;
}
if (pos.y < 0) {
pos.y = 0;
turnoff_v = true;
}
if (pos.y > (v_scroll->get_max() - v_scroll->get_page())) {
pos.y = v_scroll->get_max() - v_scroll->get_page();
turnoff_v = true;
}
if (horizontal_scroll_mode != SCROLL_MODE_DISABLED) {
h_scroll->scroll_to(pos.x);
}
if (vertical_scroll_mode != SCROLL_MODE_DISABLED) {
v_scroll->scroll_to(pos.y);
}
float sgn_x = drag_speed.x < 0 ? -1 : 1;
float val_x = Math::abs(drag_speed.x);
val_x -= 1000 * get_physics_process_delta_time();
if (val_x < 0) {
turnoff_h = true;
}
float sgn_y = drag_speed.y < 0 ? -1 : 1;
float val_y = Math::abs(drag_speed.y);
val_y -= 1000 * get_physics_process_delta_time();
if (val_y < 0) {
turnoff_v = true;
}
drag_speed = Vector2(sgn_x * val_x, sgn_y * val_y);
if (turnoff_h && turnoff_v) {
_cancel_drag();
}
} else {
if (time_since_motion == 0 || time_since_motion > 0.1) {
Vector2 diff = drag_accum - last_drag_accum;
last_drag_accum = drag_accum;
drag_speed = diff / get_physics_process_delta_time();
}
time_since_motion += get_physics_process_delta_time();
}
}
} break;
}
}
void ScrollContainer::update_scrollbars() {
Size2 size = get_size();
size -= theme_cache.panel_style->get_minimum_size();
Size2 hmin = h_scroll->get_combined_minimum_size();
Size2 vmin = v_scroll->get_combined_minimum_size();
h_scroll->set_visible(horizontal_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (horizontal_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.width > size.width));
v_scroll->set_visible(vertical_scroll_mode == SCROLL_MODE_SHOW_ALWAYS || (vertical_scroll_mode == SCROLL_MODE_AUTO && largest_child_min_size.height > size.height));
h_scroll->set_max(largest_child_min_size.width);
h_scroll->set_page((v_scroll->is_visible() && v_scroll->get_parent() == this) ? size.width - vmin.width : size.width);
v_scroll->set_max(largest_child_min_size.height);
v_scroll->set_page((h_scroll->is_visible() && h_scroll->get_parent() == this) ? size.height - hmin.height : size.height);
// Avoid scrollbar overlapping.
_updating_scrollbars = true;
callable_mp(this, &ScrollContainer::_update_scrollbar_position).call_deferred();
}
void ScrollContainer::_scroll_moved(float) {
queue_sort();
};
void ScrollContainer::set_h_scroll(int p_pos) {
h_scroll->set_value(p_pos);
_cancel_drag();
}
int ScrollContainer::get_h_scroll() const {
return h_scroll->get_value();
}
void ScrollContainer::set_v_scroll(int p_pos) {
v_scroll->set_value(p_pos);
_cancel_drag();
}
int ScrollContainer::get_v_scroll() const {
return v_scroll->get_value();
}
void ScrollContainer::set_horizontal_custom_step(float p_custom_step) {
h_scroll->set_custom_step(p_custom_step);
}
float ScrollContainer::get_horizontal_custom_step() const {
return h_scroll->get_custom_step();
}
void ScrollContainer::set_vertical_custom_step(float p_custom_step) {
v_scroll->set_custom_step(p_custom_step);
}
float ScrollContainer::get_vertical_custom_step() const {
return v_scroll->get_custom_step();
}
void ScrollContainer::set_horizontal_scroll_mode(ScrollMode p_mode) {
if (horizontal_scroll_mode == p_mode) {
return;
}
horizontal_scroll_mode = p_mode;
update_minimum_size();
queue_sort();
}
ScrollContainer::ScrollMode ScrollContainer::get_horizontal_scroll_mode() const {
return horizontal_scroll_mode;
}
void ScrollContainer::set_vertical_scroll_mode(ScrollMode p_mode) {
if (vertical_scroll_mode == p_mode) {
return;
}
vertical_scroll_mode = p_mode;
update_minimum_size();
queue_sort();
}
ScrollContainer::ScrollMode ScrollContainer::get_vertical_scroll_mode() const {
return vertical_scroll_mode;
}
int ScrollContainer::get_deadzone() const {
return deadzone;
}
void ScrollContainer::set_deadzone(int p_deadzone) {
deadzone = p_deadzone;
}
bool ScrollContainer::is_following_focus() const {
return follow_focus;
}
void ScrollContainer::set_follow_focus(bool p_follow) {
follow_focus = p_follow;
}
PackedStringArray ScrollContainer::get_configuration_warnings() const {
PackedStringArray warnings = Container::get_configuration_warnings();
int found = 0;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
continue;
}
if (c == h_scroll || c == v_scroll) {
continue;
}
found++;
}
if (found != 1) {
warnings.push_back(RTR("ScrollContainer is intended to work with a single child control.\nUse a container as child (VBox, HBox, etc.), or a Control and set the custom minimum size manually."));
}
return warnings;
}
HScrollBar *ScrollContainer::get_h_scroll_bar() {
return h_scroll;
}
VScrollBar *ScrollContainer::get_v_scroll_bar() {
return v_scroll;
}
void ScrollContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_h_scroll", "value"), &ScrollContainer::set_h_scroll);
ClassDB::bind_method(D_METHOD("get_h_scroll"), &ScrollContainer::get_h_scroll);
ClassDB::bind_method(D_METHOD("set_v_scroll", "value"), &ScrollContainer::set_v_scroll);
ClassDB::bind_method(D_METHOD("get_v_scroll"), &ScrollContainer::get_v_scroll);
ClassDB::bind_method(D_METHOD("set_horizontal_custom_step", "value"), &ScrollContainer::set_horizontal_custom_step);
ClassDB::bind_method(D_METHOD("get_horizontal_custom_step"), &ScrollContainer::get_horizontal_custom_step);
ClassDB::bind_method(D_METHOD("set_vertical_custom_step", "value"), &ScrollContainer::set_vertical_custom_step);
ClassDB::bind_method(D_METHOD("get_vertical_custom_step"), &ScrollContainer::get_vertical_custom_step);
ClassDB::bind_method(D_METHOD("set_horizontal_scroll_mode", "enable"), &ScrollContainer::set_horizontal_scroll_mode);
ClassDB::bind_method(D_METHOD("get_horizontal_scroll_mode"), &ScrollContainer::get_horizontal_scroll_mode);
ClassDB::bind_method(D_METHOD("set_vertical_scroll_mode", "enable"), &ScrollContainer::set_vertical_scroll_mode);
ClassDB::bind_method(D_METHOD("get_vertical_scroll_mode"), &ScrollContainer::get_vertical_scroll_mode);
ClassDB::bind_method(D_METHOD("set_deadzone", "deadzone"), &ScrollContainer::set_deadzone);
ClassDB::bind_method(D_METHOD("get_deadzone"), &ScrollContainer::get_deadzone);
ClassDB::bind_method(D_METHOD("set_follow_focus", "enabled"), &ScrollContainer::set_follow_focus);
ClassDB::bind_method(D_METHOD("is_following_focus"), &ScrollContainer::is_following_focus);
ClassDB::bind_method(D_METHOD("get_h_scroll_bar"), &ScrollContainer::get_h_scroll_bar);
ClassDB::bind_method(D_METHOD("get_v_scroll_bar"), &ScrollContainer::get_v_scroll_bar);
ClassDB::bind_method(D_METHOD("ensure_control_visible", "control"), &ScrollContainer::ensure_control_visible);
ADD_SIGNAL(MethodInfo("scroll_started"));
ADD_SIGNAL(MethodInfo("scroll_ended"));
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_focus"), "set_follow_focus", "is_following_focus");
ADD_GROUP("Scroll", "scroll_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_horizontal", PROPERTY_HINT_NONE, "suffix:px"), "set_h_scroll", "get_h_scroll");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_vertical", PROPERTY_HINT_NONE, "suffix:px"), "set_v_scroll", "get_v_scroll");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_horizontal_custom_step", PROPERTY_HINT_RANGE, "-1,4096,suffix:px"), "set_horizontal_custom_step", "get_horizontal_custom_step");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "scroll_vertical_custom_step", PROPERTY_HINT_RANGE, "-1,4096,suffix:px"), "set_vertical_custom_step", "get_vertical_custom_step");
ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_scroll_mode", PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show"), "set_horizontal_scroll_mode", "get_horizontal_scroll_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_scroll_mode", PROPERTY_HINT_ENUM, "Disabled,Auto,Always Show,Never Show"), "set_vertical_scroll_mode", "get_vertical_scroll_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scroll_deadzone"), "set_deadzone", "get_deadzone");
BIND_ENUM_CONSTANT(SCROLL_MODE_DISABLED);
BIND_ENUM_CONSTANT(SCROLL_MODE_AUTO);
BIND_ENUM_CONSTANT(SCROLL_MODE_SHOW_ALWAYS);
BIND_ENUM_CONSTANT(SCROLL_MODE_SHOW_NEVER);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, ScrollContainer, panel_style, "panel");
GLOBAL_DEF("gui/common/default_scroll_deadzone", 0);
};
ScrollContainer::ScrollContainer() {
h_scroll = memnew(HScrollBar);
h_scroll->set_name("_h_scroll");
add_child(h_scroll, false, INTERNAL_MODE_BACK);
h_scroll->connect(SceneStringName(value_changed), callable_mp(this, &ScrollContainer::_scroll_moved));
v_scroll = memnew(VScrollBar);
v_scroll->set_name("_v_scroll");
add_child(v_scroll, false, INTERNAL_MODE_BACK);
v_scroll->connect(SceneStringName(value_changed), callable_mp(this, &ScrollContainer::_scroll_moved));
deadzone = GLOBAL_GET("gui/common/default_scroll_deadzone");
set_clip_contents(true);
};

View file

@ -0,0 +1,129 @@
/**************************************************************************/
/* scroll_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. */
/**************************************************************************/
#ifndef SCROLL_CONTAINER_H
#define SCROLL_CONTAINER_H
#include "container.h"
#include "scroll_bar.h"
class ScrollContainer : public Container {
GDCLASS(ScrollContainer, Container);
public:
enum ScrollMode {
SCROLL_MODE_DISABLED = 0,
SCROLL_MODE_AUTO,
SCROLL_MODE_SHOW_ALWAYS,
SCROLL_MODE_SHOW_NEVER,
};
private:
HScrollBar *h_scroll = nullptr;
VScrollBar *v_scroll = nullptr;
mutable Size2 largest_child_min_size; // The largest one among the min sizes of all available child controls.
void update_scrollbars();
Vector2 drag_speed;
Vector2 drag_accum;
Vector2 drag_from;
Vector2 last_drag_accum;
float time_since_motion = 0.0f;
bool drag_touching = false;
bool drag_touching_deaccel = false;
bool beyond_deadzone = false;
ScrollMode horizontal_scroll_mode = SCROLL_MODE_AUTO;
ScrollMode vertical_scroll_mode = SCROLL_MODE_AUTO;
int deadzone = 0;
bool follow_focus = false;
struct ThemeCache {
Ref<StyleBox> panel_style;
} theme_cache;
void _cancel_drag();
protected:
Size2 get_minimum_size() const override;
void _gui_focus_changed(Control *p_control);
void _reposition_children();
void _notification(int p_what);
static void _bind_methods();
bool _updating_scrollbars = false;
void _update_scrollbar_position();
void _scroll_moved(float);
public:
virtual void gui_input(const Ref<InputEvent> &p_gui_input) override;
void set_h_scroll(int p_pos);
int get_h_scroll() const;
void set_v_scroll(int p_pos);
int get_v_scroll() const;
void set_horizontal_custom_step(float p_custom_step);
float get_horizontal_custom_step() const;
void set_vertical_custom_step(float p_custom_step);
float get_vertical_custom_step() const;
void set_horizontal_scroll_mode(ScrollMode p_mode);
ScrollMode get_horizontal_scroll_mode() const;
void set_vertical_scroll_mode(ScrollMode p_mode);
ScrollMode get_vertical_scroll_mode() const;
int get_deadzone() const;
void set_deadzone(int p_deadzone);
bool is_following_focus() const;
void set_follow_focus(bool p_follow);
HScrollBar *get_h_scroll_bar();
VScrollBar *get_v_scroll_bar();
void ensure_control_visible(Control *p_control);
PackedStringArray get_configuration_warnings() const override;
ScrollContainer();
};
VARIANT_ENUM_CAST(ScrollContainer::ScrollMode);
#endif // SCROLL_CONTAINER_H

View file

@ -0,0 +1,77 @@
/**************************************************************************/
/* separator.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 "separator.h"
#include "scene/theme/theme_db.h"
Size2 Separator::get_minimum_size() const {
Size2 ms(3, 3);
if (orientation == VERTICAL) {
ms.x = theme_cache.separation;
} else { // HORIZONTAL
ms.y = theme_cache.separation;
}
return ms;
}
void Separator::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
Size2i size = get_size();
Size2i ssize = theme_cache.separator_style->get_minimum_size();
if (orientation == VERTICAL) {
theme_cache.separator_style->draw(get_canvas_item(), Rect2((size.x - ssize.x) / 2, 0, ssize.x, size.y));
} else {
theme_cache.separator_style->draw(get_canvas_item(), Rect2(0, (size.y - ssize.y) / 2, size.x, ssize.y));
}
} break;
}
}
void Separator::_bind_methods() {
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Separator, separation);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Separator, separator_style, "separator");
}
Separator::Separator() {
}
Separator::~Separator() {
}
HSeparator::HSeparator() {
orientation = HORIZONTAL;
}
VSeparator::VSeparator() {
orientation = VERTICAL;
}

View file

@ -0,0 +1,70 @@
/**************************************************************************/
/* separator.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. */
/**************************************************************************/
#ifndef SEPARATOR_H
#define SEPARATOR_H
#include "scene/gui/control.h"
class Separator : public Control {
GDCLASS(Separator, Control);
struct ThemeCache {
int separation = 0;
Ref<StyleBox> separator_style;
} theme_cache;
protected:
Orientation orientation = Orientation::HORIZONTAL;
void _notification(int p_what);
static void _bind_methods();
public:
virtual Size2 get_minimum_size() const override;
Separator();
~Separator();
};
class VSeparator : public Separator {
GDCLASS(VSeparator, Separator);
public:
VSeparator();
};
class HSeparator : public Separator {
GDCLASS(HSeparator, Separator);
public:
HSeparator();
};
#endif // SEPARATOR_H

407
engine/scene/gui/slider.cpp Normal file
View file

@ -0,0 +1,407 @@
/**************************************************************************/
/* slider.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 "slider.h"
#include "core/os/keyboard.h"
#include "scene/theme/theme_db.h"
Size2 Slider::get_minimum_size() const {
Size2i ss = theme_cache.slider_style->get_minimum_size();
Size2i rs = theme_cache.grabber_icon->get_size();
if (orientation == HORIZONTAL) {
return Size2i(ss.width, MAX(ss.height, rs.height));
} else {
return Size2i(MAX(ss.width, rs.width), ss.height);
}
}
void Slider::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (!editable) {
return;
}
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
Ref<Texture2D> grabber;
if (mouse_inside || has_focus()) {
grabber = theme_cache.grabber_hl_icon;
} else {
grabber = theme_cache.grabber_icon;
}
grab.pos = orientation == VERTICAL ? mb->get_position().y : mb->get_position().x;
grab.value_before_dragging = get_as_ratio();
emit_signal(SNAME("drag_started"));
double grab_width = theme_cache.center_grabber ? 0.0 : (double)grabber->get_width();
double grab_height = theme_cache.center_grabber ? 0.0 : (double)grabber->get_height();
double max = orientation == VERTICAL ? get_size().height - grab_height : get_size().width - grab_width;
set_block_signals(true);
if (orientation == VERTICAL) {
set_as_ratio(1 - (((double)grab.pos - (grab_height / 2.0)) / max));
} else {
set_as_ratio(((double)grab.pos - (grab_width / 2.0)) / max);
}
set_block_signals(false);
grab.active = true;
grab.uvalue = get_as_ratio();
_notify_shared_value_changed();
} else {
grab.active = false;
const bool value_changed = !Math::is_equal_approx((double)grab.value_before_dragging, get_as_ratio());
emit_signal(SNAME("drag_ended"), value_changed);
}
} else if (scrollable) {
if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_UP) {
if (get_focus_mode() != FOCUS_NONE) {
grab_focus();
}
set_value(get_value() + get_step());
} else if (mb->is_pressed() && mb->get_button_index() == MouseButton::WHEEL_DOWN) {
if (get_focus_mode() != FOCUS_NONE) {
grab_focus();
}
set_value(get_value() - get_step());
}
}
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
if (grab.active) {
Size2i size = get_size();
Ref<Texture2D> grabber = theme_cache.grabber_hl_icon;
double grab_width = theme_cache.center_grabber ? 0.0 : (double)grabber->get_width();
double grab_height = theme_cache.center_grabber ? 0.0 : (double)grabber->get_height();
double motion = (orientation == VERTICAL ? mm->get_position().y : mm->get_position().x) - grab.pos;
if (orientation == VERTICAL) {
motion = -motion;
}
double areasize = orientation == VERTICAL ? size.height - grab_height : size.width - grab_width;
if (areasize <= 0) {
return;
}
double umotion = motion / double(areasize);
set_as_ratio(grab.uvalue + umotion);
}
}
Input *input = Input::get_singleton();
Ref<InputEventJoypadMotion> joypadmotion_event = p_event;
Ref<InputEventJoypadButton> joypadbutton_event = p_event;
bool is_joypad_event = (joypadmotion_event.is_valid() || joypadbutton_event.is_valid());
if (!mm.is_valid() && !mb.is_valid()) {
if (p_event->is_action_pressed("ui_left", true)) {
if (orientation != HORIZONTAL) {
return;
}
if (is_joypad_event) {
if (!input->is_action_just_pressed("ui_left", true)) {
return;
}
set_process_internal(true);
}
set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
accept_event();
} else if (p_event->is_action_pressed("ui_right", true)) {
if (orientation != HORIZONTAL) {
return;
}
if (is_joypad_event) {
if (!input->is_action_just_pressed("ui_right", true)) {
return;
}
set_process_internal(true);
}
set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
accept_event();
} else if (p_event->is_action_pressed("ui_up", true)) {
if (orientation != VERTICAL) {
return;
}
if (is_joypad_event) {
if (!input->is_action_just_pressed("ui_up", true)) {
return;
}
set_process_internal(true);
}
set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
accept_event();
} else if (p_event->is_action_pressed("ui_down", true)) {
if (orientation != VERTICAL) {
return;
}
if (is_joypad_event) {
if (!input->is_action_just_pressed("ui_down", true)) {
return;
}
set_process_internal(true);
}
set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
accept_event();
} else if (p_event->is_action("ui_home", true) && p_event->is_pressed()) {
set_value(get_min());
accept_event();
} else if (p_event->is_action("ui_end", true) && p_event->is_pressed()) {
set_value(get_max());
accept_event();
}
}
}
void Slider::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_INTERNAL_PROCESS: {
Input *input = Input::get_singleton();
if (input->is_action_just_released("ui_left") || input->is_action_just_released("ui_right") || input->is_action_just_released("ui_up") || input->is_action_just_released("ui_down")) {
gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS;
set_process_internal(false);
return;
}
gamepad_event_delay_ms -= get_process_delta_time();
if (gamepad_event_delay_ms <= 0) {
gamepad_event_delay_ms = GAMEPAD_EVENT_REPEAT_RATE_MS + gamepad_event_delay_ms;
if (orientation == HORIZONTAL) {
if (input->is_action_pressed("ui_left")) {
set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
}
if (input->is_action_pressed("ui_right")) {
set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
}
} else if (orientation == VERTICAL) {
if (input->is_action_pressed("ui_down")) {
set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
}
if (input->is_action_pressed("ui_up")) {
set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
}
}
}
} break;
case NOTIFICATION_THEME_CHANGED: {
update_minimum_size();
queue_redraw();
} break;
case NOTIFICATION_MOUSE_ENTER: {
mouse_inside = true;
queue_redraw();
} break;
case NOTIFICATION_MOUSE_EXIT: {
mouse_inside = false;
queue_redraw();
} break;
case NOTIFICATION_VISIBILITY_CHANGED:
case NOTIFICATION_EXIT_TREE: {
mouse_inside = false;
grab.active = false;
} break;
case NOTIFICATION_DRAW: {
RID ci = get_canvas_item();
Size2i size = get_size();
double ratio = Math::is_nan(get_as_ratio()) ? 0 : get_as_ratio();
Ref<StyleBox> style = theme_cache.slider_style;
Ref<Texture2D> tick = theme_cache.tick_icon;
bool highlighted = editable && (mouse_inside || has_focus());
Ref<Texture2D> grabber;
if (editable) {
if (highlighted) {
grabber = theme_cache.grabber_hl_icon;
} else {
grabber = theme_cache.grabber_icon;
}
} else {
grabber = theme_cache.grabber_disabled_icon;
}
Ref<StyleBox> grabber_area;
if (highlighted) {
grabber_area = theme_cache.grabber_area_hl_style;
} else {
grabber_area = theme_cache.grabber_area_style;
}
if (orientation == VERTICAL) {
int widget_width = style->get_minimum_size().width;
double areasize = size.height - (theme_cache.center_grabber ? 0 : grabber->get_height());
int grabber_shift = theme_cache.center_grabber ? grabber->get_height() / 2 : 0;
style->draw(ci, Rect2i(Point2i(size.width / 2 - widget_width / 2, 0), Size2i(widget_width, size.height)));
grabber_area->draw(ci, Rect2i(Point2i((size.width - widget_width) / 2, size.height - areasize * ratio - grabber->get_height() / 2 + grabber_shift), Size2i(widget_width, areasize * ratio + grabber->get_height() / 2 - grabber_shift)));
if (ticks > 1) {
int grabber_offset = (grabber->get_height() / 2 - tick->get_height() / 2);
for (int i = 0; i < ticks; i++) {
if (!ticks_on_borders && (i == 0 || i + 1 == ticks)) {
continue;
}
int ofs = (i * areasize / (ticks - 1)) + grabber_offset - grabber_shift;
tick->draw(ci, Point2i((size.width - widget_width) / 2, ofs));
}
}
grabber->draw(ci, Point2i(size.width / 2 - grabber->get_width() / 2 + theme_cache.grabber_offset, size.height - ratio * areasize - grabber->get_height() + grabber_shift));
} else {
int widget_height = style->get_minimum_size().height;
double areasize = size.width - (theme_cache.center_grabber ? 0 : grabber->get_size().width);
int grabber_shift = theme_cache.center_grabber ? -grabber->get_width() / 2 : 0;
style->draw(ci, Rect2i(Point2i(0, (size.height - widget_height) / 2), Size2i(size.width, widget_height)));
grabber_area->draw(ci, Rect2i(Point2i(0, (size.height - widget_height) / 2), Size2i(areasize * ratio + grabber->get_width() / 2 + grabber_shift, widget_height)));
if (ticks > 1) {
int grabber_offset = (grabber->get_width() / 2 - tick->get_width() / 2);
for (int i = 0; i < ticks; i++) {
if ((!ticks_on_borders) && ((i == 0) || ((i + 1) == ticks))) {
continue;
}
int ofs = (i * areasize / (ticks - 1)) + grabber_offset + grabber_shift;
tick->draw(ci, Point2i(ofs, (size.height - widget_height) / 2));
}
}
grabber->draw(ci, Point2i(ratio * areasize + grabber_shift, size.height / 2 - grabber->get_height() / 2 + theme_cache.grabber_offset));
}
} break;
}
}
void Slider::set_custom_step(double p_custom_step) {
custom_step = p_custom_step;
}
double Slider::get_custom_step() const {
return custom_step;
}
void Slider::set_ticks(int p_count) {
if (ticks == p_count) {
return;
}
ticks = p_count;
queue_redraw();
}
int Slider::get_ticks() const {
return ticks;
}
bool Slider::get_ticks_on_borders() const {
return ticks_on_borders;
}
void Slider::set_ticks_on_borders(bool _tob) {
if (ticks_on_borders == _tob) {
return;
}
ticks_on_borders = _tob;
queue_redraw();
}
void Slider::set_editable(bool p_editable) {
if (editable == p_editable) {
return;
}
grab.active = false;
editable = p_editable;
queue_redraw();
}
bool Slider::is_editable() const {
return editable;
}
void Slider::set_scrollable(bool p_scrollable) {
scrollable = p_scrollable;
}
bool Slider::is_scrollable() const {
return scrollable;
}
void Slider::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_ticks", "count"), &Slider::set_ticks);
ClassDB::bind_method(D_METHOD("get_ticks"), &Slider::get_ticks);
ClassDB::bind_method(D_METHOD("get_ticks_on_borders"), &Slider::get_ticks_on_borders);
ClassDB::bind_method(D_METHOD("set_ticks_on_borders", "ticks_on_border"), &Slider::set_ticks_on_borders);
ClassDB::bind_method(D_METHOD("set_editable", "editable"), &Slider::set_editable);
ClassDB::bind_method(D_METHOD("is_editable"), &Slider::is_editable);
ClassDB::bind_method(D_METHOD("set_scrollable", "scrollable"), &Slider::set_scrollable);
ClassDB::bind_method(D_METHOD("is_scrollable"), &Slider::is_scrollable);
ADD_SIGNAL(MethodInfo("drag_started"));
ADD_SIGNAL(MethodInfo("drag_ended", PropertyInfo(Variant::BOOL, "value_changed")));
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "scrollable"), "set_scrollable", "is_scrollable");
ADD_PROPERTY(PropertyInfo(Variant::INT, "tick_count", PROPERTY_HINT_RANGE, "0,4096,1"), "set_ticks", "get_ticks");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ticks_on_borders"), "set_ticks_on_borders", "get_ticks_on_borders");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Slider, slider_style, "slider");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Slider, grabber_area_style, "grabber_area");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, Slider, grabber_area_hl_style, "grabber_area_highlight");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, Slider, grabber_icon, "grabber");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, Slider, grabber_hl_icon, "grabber_highlight");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, Slider, grabber_disabled_icon, "grabber_disabled");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, Slider, tick_icon, "tick");
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Slider, center_grabber);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, Slider, grabber_offset);
}
Slider::Slider(Orientation p_orientation) {
orientation = p_orientation;
set_focus_mode(FOCUS_ALL);
}

115
engine/scene/gui/slider.h Normal file
View file

@ -0,0 +1,115 @@
/**************************************************************************/
/* slider.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. */
/**************************************************************************/
#ifndef SLIDER_H
#define SLIDER_H
#include "scene/gui/range.h"
class Slider : public Range {
GDCLASS(Slider, Range);
struct Grab {
int pos = 0;
double uvalue = 0.0; // Value at `pos`.
double value_before_dragging = 0.0;
bool active = false;
} grab;
int ticks = 0;
bool mouse_inside = false;
Orientation orientation;
double custom_step = -1.0;
bool editable = true;
bool scrollable = true;
const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 0.5;
const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 20;
float gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS;
struct ThemeCache {
Ref<StyleBox> slider_style;
Ref<StyleBox> grabber_area_style;
Ref<StyleBox> grabber_area_hl_style;
Ref<Texture2D> grabber_icon;
Ref<Texture2D> grabber_hl_icon;
Ref<Texture2D> grabber_disabled_icon;
Ref<Texture2D> tick_icon;
bool center_grabber = false;
int grabber_offset = 0;
} theme_cache;
protected:
bool ticks_on_borders = false;
virtual void gui_input(const Ref<InputEvent> &p_event) override;
void _notification(int p_what);
static void _bind_methods();
public:
virtual Size2 get_minimum_size() const override;
void set_custom_step(double p_custom_step);
double get_custom_step() const;
void set_ticks(int p_count);
int get_ticks() const;
void set_ticks_on_borders(bool);
bool get_ticks_on_borders() const;
void set_editable(bool p_editable);
bool is_editable() const;
void set_scrollable(bool p_scrollable);
bool is_scrollable() const;
Slider(Orientation p_orientation = VERTICAL);
};
class HSlider : public Slider {
GDCLASS(HSlider, Slider);
public:
HSlider() :
Slider(HORIZONTAL) { set_v_size_flags(0); }
};
class VSlider : public Slider {
GDCLASS(VSlider, Slider);
public:
VSlider() :
Slider(VERTICAL) { set_h_size_flags(0); }
};
#endif // SLIDER_H

View file

@ -0,0 +1,419 @@
/**************************************************************************/
/* spin_box.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 "spin_box.h"
#include "core/input/input.h"
#include "core/math/expression.h"
#include "scene/theme/theme_db.h"
Size2 SpinBox::get_minimum_size() const {
Size2 ms = line_edit->get_combined_minimum_size();
ms.width += last_w;
return ms;
}
void SpinBox::_update_text(bool p_keep_line_edit) {
String value = String::num(get_value(), Math::range_step_decimals(get_step()));
if (is_localizing_numeral_system()) {
value = TS->format_number(value);
}
if (!line_edit->has_focus()) {
if (!prefix.is_empty()) {
value = prefix + " " + value;
}
if (!suffix.is_empty()) {
value += " " + suffix;
}
}
if (p_keep_line_edit && value == last_updated_text && value != line_edit->get_text()) {
return;
}
line_edit->set_text_with_selection(value);
last_updated_text = value;
}
void SpinBox::_text_submitted(const String &p_string) {
Ref<Expression> expr;
expr.instantiate();
// Convert commas ',' to dots '.' for French/German etc. keyboard layouts.
String text = p_string.replace(",", ".");
text = text.replace(";", ",");
text = TS->parse_number(text);
// Ignore the prefix and suffix in the expression.
text = text.trim_prefix(prefix + " ").trim_suffix(" " + suffix);
Error err = expr->parse(text);
if (err != OK) {
// If the expression failed try without converting commas to dots - they might have been for parameter separation.
text = p_string;
text = TS->parse_number(text);
text = text.trim_prefix(prefix + " ").trim_suffix(" " + suffix);
err = expr->parse(text);
if (err != OK) {
_update_text();
return;
}
}
Variant value = expr->execute(Array(), nullptr, false, true);
if (value.get_type() != Variant::NIL) {
set_value(value);
}
_update_text();
}
void SpinBox::_text_changed(const String &p_string) {
int cursor_pos = line_edit->get_caret_column();
_text_submitted(p_string);
// Line edit 'set_text' method resets the cursor position so we need to undo that.
line_edit->set_caret_column(cursor_pos);
}
LineEdit *SpinBox::get_line_edit() {
return line_edit;
}
void SpinBox::_line_edit_input(const Ref<InputEvent> &p_event) {
}
void SpinBox::_range_click_timeout() {
if (!drag.enabled && Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
bool up = get_local_mouse_position().y < (get_size().height / 2);
double step = get_custom_arrow_step() != 0.0 ? get_custom_arrow_step() : get_step();
set_value(get_value() + (up ? step : -step));
if (range_click_timer->is_one_shot()) {
range_click_timer->set_wait_time(0.075);
range_click_timer->set_one_shot(false);
range_click_timer->start();
}
} else {
range_click_timer->stop();
}
}
void SpinBox::_release_mouse() {
if (drag.enabled) {
drag.enabled = false;
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_HIDDEN);
warp_mouse(drag.capture_pos);
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
}
}
void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (!is_editable()) {
return;
}
Ref<InputEventMouseButton> mb = p_event;
double step = get_custom_arrow_step() != 0.0 ? get_custom_arrow_step() : get_step();
if (mb.is_valid() && mb->is_pressed()) {
bool up = mb->get_position().y < (get_size().height / 2);
switch (mb->get_button_index()) {
case MouseButton::LEFT: {
line_edit->grab_focus();
set_value(get_value() + (up ? step : -step));
range_click_timer->set_wait_time(0.6);
range_click_timer->set_one_shot(true);
range_click_timer->start();
drag.allowed = true;
drag.capture_pos = mb->get_position();
} break;
case MouseButton::RIGHT: {
line_edit->grab_focus();
set_value((up ? get_max() : get_min()));
} break;
case MouseButton::WHEEL_UP: {
if (line_edit->has_focus()) {
set_value(get_value() + step * mb->get_factor());
accept_event();
}
} break;
case MouseButton::WHEEL_DOWN: {
if (line_edit->has_focus()) {
set_value(get_value() - step * mb->get_factor());
accept_event();
}
} break;
default:
break;
}
}
if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
//set_default_cursor_shape(CURSOR_ARROW);
range_click_timer->stop();
_release_mouse();
drag.allowed = false;
line_edit->clear_pending_select_all_on_focus();
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid() && (mm->get_button_mask().has_flag(MouseButtonMask::LEFT))) {
if (drag.enabled) {
drag.diff_y += mm->get_relative().y;
double diff_y = -0.01 * Math::pow(ABS(drag.diff_y), 1.8) * SIGN(drag.diff_y);
set_value(CLAMP(drag.base_val + step * diff_y, get_min(), get_max()));
} else if (drag.allowed && drag.capture_pos.distance_to(mm->get_position()) > 2) {
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
drag.enabled = true;
drag.base_val = get_value();
drag.diff_y = 0;
}
}
}
void SpinBox::_line_edit_focus_enter() {
int col = line_edit->get_caret_column();
_update_text();
line_edit->set_caret_column(col);
// LineEdit text might change and it clears any selection. Have to re-select here.
if (line_edit->is_select_all_on_focus() && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
line_edit->select_all();
}
}
void SpinBox::_line_edit_focus_exit() {
// Discontinue because the focus_exit was caused by left-clicking the arrows.
const Viewport *viewport = get_viewport();
if (!viewport || viewport->gui_get_focus_owner() == get_line_edit()) {
return;
}
// Discontinue because the focus_exit was caused by right-click context menu.
if (line_edit->is_menu_visible()) {
return;
}
// Discontinue because the focus_exit was caused by canceling.
if (Input::get_singleton()->is_action_pressed("ui_cancel")) {
_update_text();
return;
}
_text_submitted(line_edit->get_text());
}
inline void SpinBox::_adjust_width_for_icon(const Ref<Texture2D> &icon) {
int w = icon->get_width();
if ((w != last_w)) {
line_edit->set_offset(SIDE_LEFT, 0);
line_edit->set_offset(SIDE_RIGHT, -w);
last_w = w;
}
}
void SpinBox::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_DRAW: {
_update_text(true);
_adjust_width_for_icon(theme_cache.updown_icon);
RID ci = get_canvas_item();
Size2i size = get_size();
if (is_layout_rtl()) {
theme_cache.updown_icon->draw(ci, Point2i(0, (size.height - theme_cache.updown_icon->get_height()) / 2));
} else {
theme_cache.updown_icon->draw(ci, Point2i(size.width - theme_cache.updown_icon->get_width(), (size.height - theme_cache.updown_icon->get_height()) / 2));
}
} break;
case NOTIFICATION_ENTER_TREE: {
_adjust_width_for_icon(theme_cache.updown_icon);
_update_text();
} break;
case NOTIFICATION_VISIBILITY_CHANGED:
drag.allowed = false;
[[fallthrough]];
case NOTIFICATION_EXIT_TREE: {
_release_mouse();
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
queue_redraw();
} break;
case NOTIFICATION_THEME_CHANGED: {
callable_mp((Control *)this, &Control::update_minimum_size).call_deferred();
callable_mp((Control *)get_line_edit(), &Control::update_minimum_size).call_deferred();
} break;
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
queue_redraw();
} break;
}
}
void SpinBox::set_horizontal_alignment(HorizontalAlignment p_alignment) {
line_edit->set_horizontal_alignment(p_alignment);
}
HorizontalAlignment SpinBox::get_horizontal_alignment() const {
return line_edit->get_horizontal_alignment();
}
void SpinBox::set_suffix(const String &p_suffix) {
if (suffix == p_suffix) {
return;
}
suffix = p_suffix;
_update_text();
}
String SpinBox::get_suffix() const {
return suffix;
}
void SpinBox::set_prefix(const String &p_prefix) {
if (prefix == p_prefix) {
return;
}
prefix = p_prefix;
_update_text();
}
String SpinBox::get_prefix() const {
return prefix;
}
void SpinBox::set_update_on_text_changed(bool p_enabled) {
if (update_on_text_changed == p_enabled) {
return;
}
update_on_text_changed = p_enabled;
if (p_enabled) {
line_edit->connect(SceneStringName(text_changed), callable_mp(this, &SpinBox::_text_changed), CONNECT_DEFERRED);
} else {
line_edit->disconnect(SceneStringName(text_changed), callable_mp(this, &SpinBox::_text_changed));
}
}
bool SpinBox::get_update_on_text_changed() const {
return update_on_text_changed;
}
void SpinBox::set_select_all_on_focus(bool p_enabled) {
line_edit->set_select_all_on_focus(p_enabled);
}
bool SpinBox::is_select_all_on_focus() const {
return line_edit->is_select_all_on_focus();
}
void SpinBox::set_editable(bool p_enabled) {
line_edit->set_editable(p_enabled);
}
bool SpinBox::is_editable() const {
return line_edit->is_editable();
}
void SpinBox::apply() {
_text_submitted(line_edit->get_text());
}
void SpinBox::set_custom_arrow_step(double p_custom_arrow_step) {
custom_arrow_step = p_custom_arrow_step;
}
double SpinBox::get_custom_arrow_step() const {
return custom_arrow_step;
}
void SpinBox::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &SpinBox::set_horizontal_alignment);
ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &SpinBox::get_horizontal_alignment);
ClassDB::bind_method(D_METHOD("set_suffix", "suffix"), &SpinBox::set_suffix);
ClassDB::bind_method(D_METHOD("get_suffix"), &SpinBox::get_suffix);
ClassDB::bind_method(D_METHOD("set_prefix", "prefix"), &SpinBox::set_prefix);
ClassDB::bind_method(D_METHOD("get_prefix"), &SpinBox::get_prefix);
ClassDB::bind_method(D_METHOD("set_editable", "enabled"), &SpinBox::set_editable);
ClassDB::bind_method(D_METHOD("set_custom_arrow_step", "arrow_step"), &SpinBox::set_custom_arrow_step);
ClassDB::bind_method(D_METHOD("get_custom_arrow_step"), &SpinBox::get_custom_arrow_step);
ClassDB::bind_method(D_METHOD("is_editable"), &SpinBox::is_editable);
ClassDB::bind_method(D_METHOD("set_update_on_text_changed", "enabled"), &SpinBox::set_update_on_text_changed);
ClassDB::bind_method(D_METHOD("get_update_on_text_changed"), &SpinBox::get_update_on_text_changed);
ClassDB::bind_method(D_METHOD("set_select_all_on_focus", "enabled"), &SpinBox::set_select_all_on_focus);
ClassDB::bind_method(D_METHOD("is_select_all_on_focus"), &SpinBox::is_select_all_on_focus);
ClassDB::bind_method(D_METHOD("apply"), &SpinBox::apply);
ClassDB::bind_method(D_METHOD("get_line_edit"), &SpinBox::get_line_edit);
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "update_on_text_changed"), "set_update_on_text_changed", "get_update_on_text_changed");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "prefix"), "set_prefix", "get_prefix");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "suffix"), "set_suffix", "get_suffix");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_arrow_step", PROPERTY_HINT_RANGE, "0,10000,0.0001,or_greater"), "set_custom_arrow_step", "get_custom_arrow_step");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "select_all_on_focus"), "set_select_all_on_focus", "is_select_all_on_focus");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, updown_icon, "updown");
}
SpinBox::SpinBox() {
line_edit = memnew(LineEdit);
add_child(line_edit, false, INTERNAL_MODE_FRONT);
line_edit->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
line_edit->set_mouse_filter(MOUSE_FILTER_PASS);
line_edit->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT);
line_edit->connect("text_submitted", callable_mp(this, &SpinBox::_text_submitted), CONNECT_DEFERRED);
line_edit->connect(SceneStringName(focus_entered), callable_mp(this, &SpinBox::_line_edit_focus_enter), CONNECT_DEFERRED);
line_edit->connect(SceneStringName(focus_exited), callable_mp(this, &SpinBox::_line_edit_focus_exit), CONNECT_DEFERRED);
line_edit->connect(SceneStringName(gui_input), callable_mp(this, &SpinBox::_line_edit_input));
range_click_timer = memnew(Timer);
range_click_timer->connect("timeout", callable_mp(this, &SpinBox::_range_click_timeout));
add_child(range_click_timer, false, INTERNAL_MODE_FRONT);
}

113
engine/scene/gui/spin_box.h Normal file
View file

@ -0,0 +1,113 @@
/**************************************************************************/
/* spin_box.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. */
/**************************************************************************/
#ifndef SPIN_BOX_H
#define SPIN_BOX_H
#include "scene/gui/line_edit.h"
#include "scene/gui/range.h"
#include "scene/main/timer.h"
class SpinBox : public Range {
GDCLASS(SpinBox, Range);
LineEdit *line_edit = nullptr;
int last_w = 0;
bool update_on_text_changed = false;
Timer *range_click_timer = nullptr;
void _range_click_timeout();
void _release_mouse();
void _update_text(bool p_keep_line_edit = false);
void _text_submitted(const String &p_string);
void _text_changed(const String &p_string);
String prefix;
String suffix;
String last_updated_text;
double custom_arrow_step = 0.0;
void _line_edit_input(const Ref<InputEvent> &p_event);
struct Drag {
double base_val = 0.0;
bool allowed = false;
bool enabled = false;
Vector2 capture_pos;
double diff_y = 0.0;
} drag;
void _line_edit_focus_enter();
void _line_edit_focus_exit();
inline void _adjust_width_for_icon(const Ref<Texture2D> &icon);
struct ThemeCache {
Ref<Texture2D> updown_icon;
} theme_cache;
protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
void _notification(int p_what);
static void _bind_methods();
public:
LineEdit *get_line_edit();
virtual Size2 get_minimum_size() const override;
void set_horizontal_alignment(HorizontalAlignment p_alignment);
HorizontalAlignment get_horizontal_alignment() const;
void set_editable(bool p_enabled);
bool is_editable() const;
void set_suffix(const String &p_suffix);
String get_suffix() const;
void set_prefix(const String &p_prefix);
String get_prefix() const;
void set_update_on_text_changed(bool p_enabled);
bool get_update_on_text_changed() const;
void set_select_all_on_focus(bool p_enabled);
bool is_select_all_on_focus() const;
void apply();
void set_custom_arrow_step(const double p_custom_arrow_step);
double get_custom_arrow_step() const;
SpinBox();
};
#endif // SPIN_BOX_H

View file

@ -0,0 +1,433 @@
/**************************************************************************/
/* split_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 "split_container.h"
#include "scene/gui/label.h"
#include "scene/gui/margin_container.h"
#include "scene/theme/theme_db.h"
void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || sc->dragger_visibility != SplitContainer::DRAGGER_VISIBLE) {
return;
}
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
if (mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
sc->_compute_middle_sep(true);
dragging = true;
drag_ofs = sc->split_offset;
if (sc->vertical) {
drag_from = get_transform().xform(mb->get_position()).y;
} else {
drag_from = get_transform().xform(mb->get_position()).x;
}
} else {
dragging = false;
queue_redraw();
}
}
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
if (!dragging) {
return;
}
Vector2i in_parent_pos = get_transform().xform(mm->get_position());
if (!sc->vertical && is_layout_rtl()) {
sc->split_offset = drag_ofs - (in_parent_pos.x - drag_from);
} else {
sc->split_offset = drag_ofs + ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from);
}
sc->_compute_middle_sep(true);
sc->queue_sort();
sc->emit_signal(SNAME("dragged"), sc->get_split_offset());
}
}
Control::CursorShape SplitContainerDragger::get_cursor_shape(const Point2 &p_pos) const {
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
if (!sc->collapsed && sc->dragger_visibility == SplitContainer::DRAGGER_VISIBLE) {
return (sc->vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT);
}
return Control::get_cursor_shape(p_pos);
}
void SplitContainerDragger::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_MOUSE_ENTER: {
mouse_inside = true;
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
if (sc->theme_cache.autohide) {
queue_redraw();
}
} break;
case NOTIFICATION_MOUSE_EXIT: {
mouse_inside = false;
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
if (sc->theme_cache.autohide) {
queue_redraw();
}
} break;
case NOTIFICATION_DRAW: {
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
if (!dragging && !mouse_inside && sc->theme_cache.autohide) {
return;
}
Ref<Texture2D> tex = sc->_get_grabber_icon();
draw_texture(tex, (get_size() - tex->get_size()) / 2);
} break;
}
}
Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode) const {
int idx = 0;
for (int i = 0; i < get_child_count(false); i++) {
Control *c = as_sortable_control(get_child(i, false), p_visibility_mode);
if (!c) {
continue;
}
if (idx == p_idx) {
return c;
}
idx++;
}
return nullptr;
}
Ref<Texture2D> SplitContainer::_get_grabber_icon() const {
if (is_fixed) {
return theme_cache.grabber_icon;
} else {
if (vertical) {
return theme_cache.grabber_icon_v;
} else {
return theme_cache.grabber_icon_h;
}
}
}
void SplitContainer::_compute_middle_sep(bool p_clamp) {
Control *first = _get_sortable_child(0);
Control *second = _get_sortable_child(1);
// Determine expanded children.
bool first_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND;
bool second_expanded = (vertical ? second->get_v_size_flags() : second->get_h_size_flags()) & SIZE_EXPAND;
// Compute the minimum size.
int axis = vertical ? 1 : 0;
int size = get_size()[axis];
int ms_first = first->get_combined_minimum_size()[axis];
int ms_second = second->get_combined_minimum_size()[axis];
// Determine the separation between items.
Ref<Texture2D> g = _get_grabber_icon();
int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0;
// Compute the wished separation_point.
int wished_middle_sep = 0;
int split_offset_with_collapse = 0;
if (!collapsed) {
split_offset_with_collapse = split_offset;
}
if (first_expanded && second_expanded) {
float ratio = first->get_stretch_ratio() / (first->get_stretch_ratio() + second->get_stretch_ratio());
wished_middle_sep = size * ratio - sep / 2 + split_offset_with_collapse;
} else if (first_expanded) {
wished_middle_sep = size - sep + split_offset_with_collapse;
} else {
wished_middle_sep = split_offset_with_collapse;
}
// Clamp the middle sep to acceptatble values.
middle_sep = CLAMP(wished_middle_sep, ms_first, size - sep - ms_second);
// Clamp the split_offset if requested.
if (p_clamp) {
split_offset -= wished_middle_sep - middle_sep;
}
}
void SplitContainer::_resort() {
Control *first = _get_sortable_child(0);
Control *second = _get_sortable_child(1);
// If we have only one element.
if (!first || !second) {
if (first) {
fit_child_in_rect(first, Rect2(Point2(), get_size()));
} else if (second) {
fit_child_in_rect(second, Rect2(Point2(), get_size()));
}
dragging_area_control->hide();
return;
}
// If we have more that one.
_compute_middle_sep(false);
// Determine the separation between items.
Ref<Texture2D> g = _get_grabber_icon();
int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0;
// Move the children, including the dragger.
if (vertical) {
fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, middle_sep)));
int sofs = middle_sep + sep;
fit_child_in_rect(second, Rect2(Point2(0, sofs), Size2(get_size().width, get_size().height - sofs)));
} else {
if (is_layout_rtl()) {
middle_sep = get_size().width - middle_sep - sep;
fit_child_in_rect(second, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height)));
int sofs = middle_sep + sep;
fit_child_in_rect(first, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height)));
} else {
fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height)));
int sofs = middle_sep + sep;
fit_child_in_rect(second, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height)));
}
}
// Handle the dragger visibility and position.
if (dragger_visibility == DRAGGER_VISIBLE && !collapsed) {
dragging_area_control->show();
int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness);
if (vertical) {
dragging_area_control->set_rect(Rect2(Point2(0, middle_sep - (dragger_ctrl_size - sep) / 2), Size2(get_size().width, dragger_ctrl_size)));
} else {
dragging_area_control->set_rect(Rect2(Point2(middle_sep - (dragger_ctrl_size - sep) / 2, 0), Size2(dragger_ctrl_size, get_size().height)));
}
dragging_area_control->queue_redraw();
} else {
dragging_area_control->hide();
}
}
Size2 SplitContainer::get_minimum_size() const {
Size2i minimum;
Ref<Texture2D> g = _get_grabber_icon();
int sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()) : 0;
for (int i = 0; i < 2; i++) {
Control *child = _get_sortable_child(i, SortableVisbilityMode::VISIBLE);
if (!child) {
break;
}
if (i == 1) {
if (vertical) {
minimum.height += sep;
} else {
minimum.width += sep;
}
}
Size2 ms = child->get_combined_minimum_size();
if (vertical) {
minimum.height += ms.height;
minimum.width = MAX(minimum.width, ms.width);
} else {
minimum.width += ms.width;
minimum.height = MAX(minimum.height, ms.height);
}
}
return minimum;
}
void SplitContainer::_validate_property(PropertyInfo &p_property) const {
if (is_fixed && p_property.name == "vertical") {
p_property.usage = PROPERTY_USAGE_NONE;
}
}
void SplitContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_TRANSLATION_CHANGED:
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
queue_sort();
} break;
case NOTIFICATION_SORT_CHILDREN: {
_resort();
} break;
case NOTIFICATION_THEME_CHANGED: {
update_minimum_size();
} break;
}
}
void SplitContainer::set_split_offset(int p_offset) {
if (split_offset == p_offset) {
return;
}
split_offset = p_offset;
queue_sort();
}
int SplitContainer::get_split_offset() const {
return split_offset;
}
void SplitContainer::clamp_split_offset() {
if (!_get_sortable_child(0) || !_get_sortable_child(1)) {
return;
}
_compute_middle_sep(true);
queue_sort();
}
void SplitContainer::set_collapsed(bool p_collapsed) {
if (collapsed == p_collapsed) {
return;
}
collapsed = p_collapsed;
queue_sort();
}
void SplitContainer::set_dragger_visibility(DraggerVisibility p_visibility) {
if (dragger_visibility == p_visibility) {
return;
}
dragger_visibility = p_visibility;
queue_sort();
}
SplitContainer::DraggerVisibility SplitContainer::get_dragger_visibility() const {
return dragger_visibility;
}
bool SplitContainer::is_collapsed() const {
return collapsed;
}
void SplitContainer::set_vertical(bool p_vertical) {
ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + ".");
vertical = p_vertical;
update_minimum_size();
_resort();
}
bool SplitContainer::is_vertical() const {
return vertical;
}
Vector<int> SplitContainer::get_allowed_size_flags_horizontal() const {
Vector<int> flags;
flags.append(SIZE_FILL);
if (!vertical) {
flags.append(SIZE_EXPAND);
}
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
Vector<int> SplitContainer::get_allowed_size_flags_vertical() const {
Vector<int> flags;
flags.append(SIZE_FILL);
if (vertical) {
flags.append(SIZE_EXPAND);
}
flags.append(SIZE_SHRINK_BEGIN);
flags.append(SIZE_SHRINK_CENTER);
flags.append(SIZE_SHRINK_END);
return flags;
}
void SplitContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_split_offset", "offset"), &SplitContainer::set_split_offset);
ClassDB::bind_method(D_METHOD("get_split_offset"), &SplitContainer::get_split_offset);
ClassDB::bind_method(D_METHOD("clamp_split_offset"), &SplitContainer::clamp_split_offset);
ClassDB::bind_method(D_METHOD("set_collapsed", "collapsed"), &SplitContainer::set_collapsed);
ClassDB::bind_method(D_METHOD("is_collapsed"), &SplitContainer::is_collapsed);
ClassDB::bind_method(D_METHOD("set_dragger_visibility", "mode"), &SplitContainer::set_dragger_visibility);
ClassDB::bind_method(D_METHOD("get_dragger_visibility"), &SplitContainer::get_dragger_visibility);
ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &SplitContainer::set_vertical);
ClassDB::bind_method(D_METHOD("is_vertical"), &SplitContainer::is_vertical);
ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::INT, "offset")));
ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_split_offset", "get_split_offset");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed");
ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden and Collapsed"), "set_dragger_visibility", "get_dragger_visibility");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
BIND_ENUM_CONSTANT(DRAGGER_VISIBLE);
BIND_ENUM_CONSTANT(DRAGGER_HIDDEN);
BIND_ENUM_CONSTANT(DRAGGER_HIDDEN_COLLAPSED);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SplitContainer, separation);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SplitContainer, minimum_grab_thickness);
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SplitContainer, autohide);
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon, "grabber");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_h, "h_grabber");
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_v, "v_grabber");
}
SplitContainer::SplitContainer(bool p_vertical) {
vertical = p_vertical;
dragging_area_control = memnew(SplitContainerDragger);
add_child(dragging_area_control, false, Node::INTERNAL_MODE_BACK);
}

View file

@ -0,0 +1,134 @@
/**************************************************************************/
/* split_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. */
/**************************************************************************/
#ifndef SPLIT_CONTAINER_H
#define SPLIT_CONTAINER_H
#include "scene/gui/container.h"
class SplitContainerDragger : public Control {
GDCLASS(SplitContainerDragger, Control);
protected:
void _notification(int p_what);
virtual void gui_input(const Ref<InputEvent> &p_event) override;
private:
bool dragging = false;
int drag_from = 0;
int drag_ofs = 0;
bool mouse_inside = false;
public:
virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override;
};
class SplitContainer : public Container {
GDCLASS(SplitContainer, Container);
friend class SplitContainerDragger;
public:
enum DraggerVisibility {
DRAGGER_VISIBLE,
DRAGGER_HIDDEN,
DRAGGER_HIDDEN_COLLAPSED
};
private:
int split_offset = 0;
int middle_sep = 0;
bool vertical = false;
bool collapsed = false;
DraggerVisibility dragger_visibility = DRAGGER_VISIBLE;
SplitContainerDragger *dragging_area_control = nullptr;
struct ThemeCache {
int separation = 0;
int minimum_grab_thickness = 0;
bool autohide = false;
Ref<Texture2D> grabber_icon;
Ref<Texture2D> grabber_icon_h;
Ref<Texture2D> grabber_icon_v;
} theme_cache;
Ref<Texture2D> _get_grabber_icon() const;
void _compute_middle_sep(bool p_clamp);
void _resort();
Control *_get_sortable_child(int p_idx, SortableVisbilityMode p_visibility_mode = SortableVisbilityMode::VISIBLE_IN_TREE) const;
protected:
bool is_fixed = false;
void _notification(int p_what);
void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
public:
void set_split_offset(int p_offset);
int get_split_offset() const;
void clamp_split_offset();
void set_collapsed(bool p_collapsed);
bool is_collapsed() const;
void set_dragger_visibility(DraggerVisibility p_visibility);
DraggerVisibility get_dragger_visibility() const;
void set_vertical(bool p_vertical);
bool is_vertical() const;
virtual Size2 get_minimum_size() const override;
virtual Vector<int> get_allowed_size_flags_horizontal() const override;
virtual Vector<int> get_allowed_size_flags_vertical() const override;
SplitContainer(bool p_vertical = false);
};
VARIANT_ENUM_CAST(SplitContainer::DraggerVisibility);
class HSplitContainer : public SplitContainer {
GDCLASS(HSplitContainer, SplitContainer);
public:
HSplitContainer() :
SplitContainer(false) { is_fixed = true; }
};
class VSplitContainer : public SplitContainer {
GDCLASS(VSplitContainer, SplitContainer);
public:
VSplitContainer() :
SplitContainer(true) { is_fixed = true; }
};
#endif // SPLIT_CONTAINER_H

View file

@ -0,0 +1,298 @@
/**************************************************************************/
/* subviewport_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 "subviewport_container.h"
#include "core/config/engine.h"
#include "scene/main/viewport.h"
Size2 SubViewportContainer::get_minimum_size() const {
if (stretch) {
return Size2();
}
Size2 ms;
for (int i = 0; i < get_child_count(); i++) {
SubViewport *c = Object::cast_to<SubViewport>(get_child(i));
if (!c) {
continue;
}
Size2 minsize = c->get_size();
ms = ms.max(minsize);
}
return ms;
}
void SubViewportContainer::set_stretch(bool p_enable) {
if (stretch == p_enable) {
return;
}
stretch = p_enable;
recalc_force_viewport_sizes();
update_minimum_size();
queue_sort();
queue_redraw();
}
bool SubViewportContainer::is_stretch_enabled() const {
return stretch;
}
void SubViewportContainer::set_stretch_shrink(int p_shrink) {
ERR_FAIL_COND(p_shrink < 1);
if (shrink == p_shrink) {
return;
}
shrink = p_shrink;
recalc_force_viewport_sizes();
queue_redraw();
}
void SubViewportContainer::recalc_force_viewport_sizes() {
if (!stretch) {
return;
}
// If stretch is enabled, make sure that all child SubViwewports have the correct size.
for (int i = 0; i < get_child_count(); i++) {
SubViewport *c = Object::cast_to<SubViewport>(get_child(i));
if (!c) {
continue;
}
c->set_size_force(get_size() / shrink);
}
}
int SubViewportContainer::get_stretch_shrink() const {
return shrink;
}
Vector<int> SubViewportContainer::get_allowed_size_flags_horizontal() const {
return Vector<int>();
}
Vector<int> SubViewportContainer::get_allowed_size_flags_vertical() const {
return Vector<int>();
}
void SubViewportContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_RESIZED: {
recalc_force_viewport_sizes();
} break;
case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_VISIBILITY_CHANGED: {
for (int i = 0; i < get_child_count(); i++) {
SubViewport *c = Object::cast_to<SubViewport>(get_child(i));
if (!c) {
continue;
}
if (is_visible_in_tree()) {
c->set_update_mode(SubViewport::UPDATE_ALWAYS);
} else {
c->set_update_mode(SubViewport::UPDATE_DISABLED);
}
c->set_handle_input_locally(false); //do not handle input locally here
}
} break;
case NOTIFICATION_DRAW: {
for (int i = 0; i < get_child_count(); i++) {
SubViewport *c = Object::cast_to<SubViewport>(get_child(i));
if (!c) {
continue;
}
if (stretch) {
draw_texture_rect(c->get_texture(), Rect2(Vector2(), get_size()));
} else {
draw_texture_rect(c->get_texture(), Rect2(Vector2(), c->get_size()));
}
}
} break;
case NOTIFICATION_FOCUS_ENTER: {
// If focused, send InputEvent to the SubViewport before the Gui-Input stage.
set_process_input(true);
set_process_unhandled_input(false);
} break;
case NOTIFICATION_FOCUS_EXIT: {
// A different Control has focus and should receive Gui-Input before the InputEvent is sent to the SubViewport.
set_process_input(false);
set_process_unhandled_input(true);
} break;
}
}
void SubViewportContainer::_notify_viewports(int p_notification) {
for (int i = 0; i < get_child_count(); i++) {
SubViewport *c = Object::cast_to<SubViewport>(get_child(i));
if (!c) {
continue;
}
c->notification(p_notification);
}
}
void SubViewportContainer::input(const Ref<InputEvent> &p_event) {
_propagate_nonpositional_event(p_event);
}
void SubViewportContainer::unhandled_input(const Ref<InputEvent> &p_event) {
_propagate_nonpositional_event(p_event);
}
void SubViewportContainer::_propagate_nonpositional_event(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
if (_is_propagated_in_gui_input(p_event)) {
return;
}
bool send;
if (GDVIRTUAL_CALL(_propagate_input_event, p_event, send)) {
if (!send) {
return;
}
}
_send_event_to_viewports(p_event);
}
void SubViewportContainer::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
if (!_is_propagated_in_gui_input(p_event)) {
return;
}
bool send;
if (GDVIRTUAL_CALL(_propagate_input_event, p_event, send)) {
if (!send) {
return;
}
}
if (stretch && shrink > 1) {
Transform2D xform;
xform.scale(Vector2(1, 1) / shrink);
_send_event_to_viewports(p_event->xformed_by(xform));
} else {
_send_event_to_viewports(p_event);
}
}
void SubViewportContainer::_send_event_to_viewports(const Ref<InputEvent> &p_event) {
for (int i = 0; i < get_child_count(); i++) {
SubViewport *c = Object::cast_to<SubViewport>(get_child(i));
if (!c || c->is_input_disabled()) {
continue;
}
c->push_input(p_event);
}
}
bool SubViewportContainer::_is_propagated_in_gui_input(const Ref<InputEvent> &p_event) {
// Propagation of events with a position property happen in gui_input
// Propagation of other events happen in input
if (Object::cast_to<InputEventMouse>(*p_event) || Object::cast_to<InputEventScreenDrag>(*p_event) || Object::cast_to<InputEventScreenTouch>(*p_event) || Object::cast_to<InputEventGesture>(*p_event)) {
return true;
}
return false;
}
void SubViewportContainer::add_child_notify(Node *p_child) {
if (Object::cast_to<SubViewport>(p_child)) {
queue_redraw();
}
}
void SubViewportContainer::remove_child_notify(Node *p_child) {
if (Object::cast_to<SubViewport>(p_child)) {
queue_redraw();
}
}
PackedStringArray SubViewportContainer::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
bool has_viewport = false;
for (int i = 0; i < get_child_count(); i++) {
if (Object::cast_to<SubViewport>(get_child(i))) {
has_viewport = true;
break;
}
}
if (!has_viewport) {
warnings.push_back(RTR("This node doesn't have a SubViewport as child, so it can't display its intended content.\nConsider adding a SubViewport as a child to provide something displayable."));
}
if (get_default_cursor_shape() != Control::CURSOR_ARROW) {
warnings.push_back(RTR("The default mouse cursor shape of SubViewportContainer has no effect.\nConsider leaving it at its initial value `CURSOR_ARROW`."));
}
return warnings;
}
void SubViewportContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_stretch", "enable"), &SubViewportContainer::set_stretch);
ClassDB::bind_method(D_METHOD("is_stretch_enabled"), &SubViewportContainer::is_stretch_enabled);
ClassDB::bind_method(D_METHOD("set_stretch_shrink", "amount"), &SubViewportContainer::set_stretch_shrink);
ClassDB::bind_method(D_METHOD("get_stretch_shrink"), &SubViewportContainer::get_stretch_shrink);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch"), "set_stretch", "is_stretch_enabled");
ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_shrink", PROPERTY_HINT_RANGE, "1,32,1,or_greater"), "set_stretch_shrink", "get_stretch_shrink");
GDVIRTUAL_BIND(_propagate_input_event, "event");
}
SubViewportContainer::SubViewportContainer() {
set_process_unhandled_input(true);
set_focus_mode(FOCUS_CLICK);
}

Some files were not shown because too many files have changed in this diff Show more