feat: godot-engine-source-4.3-stable
This commit is contained in:
parent
c59a7dcade
commit
7125d019b5
11149 changed files with 5070401 additions and 0 deletions
5
engine/scene/gui/SCsub
Normal file
5
engine/scene/gui/SCsub
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.scene_sources, "*.cpp")
|
||||
203
engine/scene/gui/aspect_ratio_container.cpp
Normal file
203
engine/scene/gui/aspect_ratio_container.cpp
Normal 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);
|
||||
}
|
||||
83
engine/scene/gui/aspect_ratio_container.h
Normal file
83
engine/scene/gui/aspect_ratio_container.h
Normal 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
|
||||
557
engine/scene/gui/base_button.cpp
Normal file
557
engine/scene/gui/base_button.cpp
Normal 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);
|
||||
}
|
||||
169
engine/scene/gui/base_button.h
Normal file
169
engine/scene/gui/base_button.h
Normal 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
|
||||
399
engine/scene/gui/box_container.cpp
Normal file
399
engine/scene/gui/box_container.cpp
Normal 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;
|
||||
}
|
||||
101
engine/scene/gui/box_container.h
Normal file
101
engine/scene/gui/box_container.h
Normal 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
853
engine/scene/gui/button.cpp
Normal 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
164
engine/scene/gui/button.h
Normal 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
|
||||
97
engine/scene/gui/center_container.cpp
Normal file
97
engine/scene/gui/center_container.cpp
Normal 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() {}
|
||||
57
engine/scene/gui/center_container.h
Normal file
57
engine/scene/gui/center_container.h
Normal 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
|
||||
171
engine/scene/gui/check_box.cpp
Normal file
171
engine/scene/gui/check_box.cpp
Normal 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() {
|
||||
}
|
||||
68
engine/scene/gui/check_box.h
Normal file
68
engine/scene/gui/check_box.h
Normal 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
|
||||
174
engine/scene/gui/check_button.cpp
Normal file
174
engine/scene/gui/check_button.cpp
Normal 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() {
|
||||
}
|
||||
66
engine/scene/gui/check_button.h
Normal file
66
engine/scene/gui/check_button.h
Normal 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
|
||||
46
engine/scene/gui/code_edit.compat.inc
Normal file
46
engine/scene/gui/code_edit.compat.inc
Normal 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
|
||||
3722
engine/scene/gui/code_edit.cpp
Normal file
3722
engine/scene/gui/code_edit.cpp
Normal file
File diff suppressed because it is too large
Load diff
515
engine/scene/gui/code_edit.h
Normal file
515
engine/scene/gui/code_edit.h
Normal 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
|
||||
406
engine/scene/gui/color_mode.cpp
Normal file
406
engine/scene/gui/color_mode.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
155
engine/scene/gui/color_mode.h
Normal file
155
engine/scene/gui/color_mode.h
Normal 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
|
||||
2285
engine/scene/gui/color_picker.cpp
Normal file
2285
engine/scene/gui/color_picker.cpp
Normal file
File diff suppressed because it is too large
Load diff
428
engine/scene/gui/color_picker.h
Normal file
428
engine/scene/gui/color_picker.h
Normal 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
|
||||
58
engine/scene/gui/color_rect.cpp
Normal file
58
engine/scene/gui/color_rect.cpp
Normal 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");
|
||||
}
|
||||
50
engine/scene/gui/color_rect.h
Normal file
50
engine/scene/gui/color_rect.h
Normal 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
|
||||
228
engine/scene/gui/container.cpp
Normal file
228
engine/scene/gui/container.cpp
Normal 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);
|
||||
}
|
||||
79
engine/scene/gui/container.h
Normal file
79
engine/scene/gui/container.h
Normal 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
|
||||
48
engine/scene/gui/control.compat.inc
Normal file
48
engine/scene/gui/control.compat.inc
Normal 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
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
669
engine/scene/gui/control.h
Normal 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
|
||||
46
engine/scene/gui/dialogs.compat.inc
Normal file
46
engine/scene/gui/dialogs.compat.inc
Normal 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
|
||||
487
engine/scene/gui/dialogs.cpp
Normal file
487
engine/scene/gui/dialogs.cpp
Normal 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
143
engine/scene/gui/dialogs.h
Normal 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
|
||||
1539
engine/scene/gui/file_dialog.cpp
Normal file
1539
engine/scene/gui/file_dialog.cpp
Normal file
File diff suppressed because it is too large
Load diff
270
engine/scene/gui/file_dialog.h
Normal file
270
engine/scene/gui/file_dialog.h
Normal 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
|
||||
420
engine/scene/gui/flow_container.cpp
Normal file
420
engine/scene/gui/flow_container.cpp
Normal 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);
|
||||
}
|
||||
117
engine/scene/gui/flow_container.h
Normal file
117
engine/scene/gui/flow_container.h
Normal 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
|
||||
51
engine/scene/gui/graph_edit.compat.inc
Normal file
51
engine/scene/gui/graph_edit.compat.inc
Normal 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
|
||||
2951
engine/scene/gui/graph_edit.cpp
Normal file
2951
engine/scene/gui/graph_edit.cpp
Normal file
File diff suppressed because it is too large
Load diff
515
engine/scene/gui/graph_edit.h
Normal file
515
engine/scene/gui/graph_edit.h
Normal 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
|
||||
557
engine/scene/gui/graph_edit_arranger.cpp
Normal file
557
engine/scene/gui/graph_edit_arranger.cpp
Normal 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 ¤t_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
|
||||
}
|
||||
67
engine/scene/gui/graph_edit_arranger.h
Normal file
67
engine/scene/gui/graph_edit_arranger.h
Normal 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
|
||||
244
engine/scene/gui/graph_element.cpp
Normal file
244
engine/scene/gui/graph_element.cpp
Normal 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);
|
||||
}
|
||||
97
engine/scene/gui/graph_element.h
Normal file
97
engine/scene/gui/graph_element.h
Normal 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
|
||||
357
engine/scene/gui/graph_frame.cpp
Normal file
357
engine/scene/gui/graph_frame.cpp
Normal 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);
|
||||
}
|
||||
109
engine/scene/gui/graph_frame.h
Normal file
109
engine/scene/gui/graph_frame.h
Normal 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
|
||||
917
engine/scene/gui/graph_node.cpp
Normal file
917
engine/scene/gui/graph_node.cpp
Normal 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);
|
||||
}
|
||||
177
engine/scene/gui/graph_node.h
Normal file
177
engine/scene/gui/graph_node.h
Normal 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
|
||||
322
engine/scene/gui/grid_container.cpp
Normal file
322
engine/scene/gui/grid_container.cpp
Normal 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() {}
|
||||
60
engine/scene/gui/grid_container.h
Normal file
60
engine/scene/gui/grid_container.h
Normal 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
|
||||
1925
engine/scene/gui/item_list.cpp
Normal file
1925
engine/scene/gui/item_list.cpp
Normal file
File diff suppressed because it is too large
Load diff
306
engine/scene/gui/item_list.h
Normal file
306
engine/scene/gui/item_list.h
Normal 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
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
180
engine/scene/gui/label.h
Normal 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
|
||||
2745
engine/scene/gui/line_edit.cpp
Normal file
2745
engine/scene/gui/line_edit.cpp
Normal file
File diff suppressed because it is too large
Load diff
399
engine/scene/gui/line_edit.h
Normal file
399
engine/scene/gui/line_edit.h
Normal 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
|
||||
296
engine/scene/gui/link_button.cpp
Normal file
296
engine/scene/gui/link_button.cpp
Normal 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);
|
||||
}
|
||||
114
engine/scene/gui/link_button.h
Normal file
114
engine/scene/gui/link_button.h
Normal 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
|
||||
125
engine/scene/gui/margin_container.cpp
Normal file
125
engine/scene/gui/margin_container.cpp
Normal 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() {
|
||||
}
|
||||
61
engine/scene/gui/margin_container.h
Normal file
61
engine/scene/gui/margin_container.h
Normal 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
|
||||
931
engine/scene/gui/menu_bar.cpp
Normal file
931
engine/scene/gui/menu_bar.cpp
Normal 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
198
engine/scene/gui/menu_bar.h
Normal 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
|
||||
230
engine/scene/gui/menu_button.cpp
Normal file
230
engine/scene/gui/menu_button.cpp
Normal 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() {
|
||||
}
|
||||
78
engine/scene/gui/menu_button.h
Normal file
78
engine/scene/gui/menu_button.h
Normal 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
|
||||
195
engine/scene/gui/nine_patch_rect.cpp
Normal file
195
engine/scene/gui/nine_patch_rect.cpp
Normal 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() {
|
||||
}
|
||||
86
engine/scene/gui/nine_patch_rect.h
Normal file
86
engine/scene/gui/nine_patch_rect.h
Normal 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
|
||||
605
engine/scene/gui/option_button.cpp
Normal file
605
engine/scene/gui/option_button.cpp
Normal 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() {
|
||||
}
|
||||
150
engine/scene/gui/option_button.h
Normal file
150
engine/scene/gui/option_button.h
Normal 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
|
||||
50
engine/scene/gui/panel.cpp
Normal file
50
engine/scene/gui/panel.cpp
Normal 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
51
engine/scene/gui/panel.h
Normal 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
|
||||
105
engine/scene/gui/panel_container.cpp
Normal file
105
engine/scene/gui/panel_container.cpp
Normal 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);
|
||||
}
|
||||
56
engine/scene/gui/panel_container.h
Normal file
56
engine/scene/gui/panel_container.h
Normal 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
291
engine/scene/gui/popup.cpp
Normal 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
100
engine/scene/gui/popup.h
Normal 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
|
||||
81
engine/scene/gui/popup_menu.compat.inc
Normal file
81
engine/scene/gui/popup_menu.compat.inc
Normal 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
|
||||
2911
engine/scene/gui/popup_menu.cpp
Normal file
2911
engine/scene/gui/popup_menu.cpp
Normal file
File diff suppressed because it is too large
Load diff
378
engine/scene/gui/popup_menu.h
Normal file
378
engine/scene/gui/popup_menu.h
Normal 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
|
||||
283
engine/scene/gui/progress_bar.cpp
Normal file
283
engine/scene/gui/progress_bar.cpp
Normal 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);
|
||||
}
|
||||
94
engine/scene/gui/progress_bar.h
Normal file
94
engine/scene/gui/progress_bar.h
Normal 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
383
engine/scene/gui/range.cpp
Normal 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
112
engine/scene/gui/range.h
Normal 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
|
||||
101
engine/scene/gui/reference_rect.cpp
Normal file
101
engine/scene/gui/reference_rect.cpp
Normal 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");
|
||||
}
|
||||
58
engine/scene/gui/reference_rect.h
Normal file
58
engine/scene/gui/reference_rect.h
Normal 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
|
||||
119
engine/scene/gui/rich_text_effect.cpp
Normal file
119
engine/scene/gui/rich_text_effect.cpp
Normal 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");
|
||||
}
|
||||
117
engine/scene/gui/rich_text_effect.h
Normal file
117
engine/scene/gui/rich_text_effect.h
Normal 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
|
||||
51
engine/scene/gui/rich_text_label.compat.inc
Normal file
51
engine/scene/gui/rich_text_label.compat.inc
Normal 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
|
||||
6462
engine/scene/gui/rich_text_label.cpp
Normal file
6462
engine/scene/gui/rich_text_label.cpp
Normal file
File diff suppressed because it is too large
Load diff
855
engine/scene/gui/rich_text_label.h
Normal file
855
engine/scene/gui/rich_text_label.h
Normal 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
|
||||
663
engine/scene/gui/scroll_bar.cpp
Normal file
663
engine/scene/gui/scroll_bar.cpp
Normal 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() {
|
||||
}
|
||||
148
engine/scene/gui/scroll_bar.h
Normal file
148
engine/scene/gui/scroll_bar.h
Normal 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
|
||||
634
engine/scene/gui/scroll_container.cpp
Normal file
634
engine/scene/gui/scroll_container.cpp
Normal 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);
|
||||
};
|
||||
129
engine/scene/gui/scroll_container.h
Normal file
129
engine/scene/gui/scroll_container.h
Normal 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
|
||||
77
engine/scene/gui/separator.cpp
Normal file
77
engine/scene/gui/separator.cpp
Normal 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;
|
||||
}
|
||||
70
engine/scene/gui/separator.h
Normal file
70
engine/scene/gui/separator.h
Normal 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
407
engine/scene/gui/slider.cpp
Normal 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
115
engine/scene/gui/slider.h
Normal 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
|
||||
419
engine/scene/gui/spin_box.cpp
Normal file
419
engine/scene/gui/spin_box.cpp
Normal 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
113
engine/scene/gui/spin_box.h
Normal 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
|
||||
433
engine/scene/gui/split_container.cpp
Normal file
433
engine/scene/gui/split_container.cpp
Normal 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);
|
||||
}
|
||||
134
engine/scene/gui/split_container.h
Normal file
134
engine/scene/gui/split_container.h
Normal 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
|
||||
298
engine/scene/gui/subviewport_container.cpp
Normal file
298
engine/scene/gui/subviewport_container.cpp
Normal 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
Loading…
Add table
Add a link
Reference in a new issue