generated from hertog/godot-module-template
Initial commit
This commit is contained in:
commit
65227bf3a5
12416 changed files with 6001067 additions and 0 deletions
6
engine/editor/gui/SCsub
Normal file
6
engine/editor/gui/SCsub
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.editor_sources, "*.cpp")
|
||||
366
engine/editor/gui/editor_bottom_panel.cpp
Normal file
366
engine/editor/gui/editor_bottom_panel.cpp
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
/**************************************************************************/
|
||||
/* editor_bottom_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 "editor_bottom_panel.h"
|
||||
|
||||
#include "editor/debugger/editor_debugger_node.h"
|
||||
#include "editor/editor_command_palette.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/gui/editor_toaster.h"
|
||||
#include "editor/gui/editor_version_button.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/scroll_container.h"
|
||||
#include "scene/gui/split_container.h"
|
||||
|
||||
void EditorBottomPanel::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
pin_button->set_button_icon(get_editor_theme_icon(SNAME("Pin")));
|
||||
expand_button->set_button_icon(get_editor_theme_icon(SNAME("ExpandBottomDock")));
|
||||
left_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));
|
||||
right_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_TRANSLATION_CHANGED:
|
||||
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
|
||||
if (is_layout_rtl()) {
|
||||
bottom_hbox->move_child(left_button, button_scroll->get_index() + 1);
|
||||
bottom_hbox->move_child(right_button, 0);
|
||||
} else {
|
||||
bottom_hbox->move_child(right_button, button_scroll->get_index() + 1);
|
||||
bottom_hbox->move_child(left_button, 0);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBottomPanel::_switch_by_control(bool p_visible, Control *p_control, bool p_ignore_lock) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items[i].control == p_control) {
|
||||
_switch_to_item(p_visible, i, p_ignore_lock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBottomPanel::_scroll(bool p_right) {
|
||||
HScrollBar *h_scroll = button_scroll->get_h_scroll_bar();
|
||||
if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
|
||||
h_scroll->set_value(p_right ? h_scroll->get_max() : 0);
|
||||
} else if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
|
||||
h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * (p_right ? 1 : -1));
|
||||
} else {
|
||||
h_scroll->set_value(h_scroll->get_value() + (h_scroll->get_page() * 0.5) * (p_right ? 1 : -1));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBottomPanel::_update_scroll_buttons() {
|
||||
bool show_arrows = button_hbox->get_size().width > button_scroll->get_size().width;
|
||||
left_button->set_visible(show_arrows);
|
||||
right_button->set_visible(show_arrows);
|
||||
|
||||
if (show_arrows) {
|
||||
_update_disabled_buttons();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBottomPanel::_update_disabled_buttons() {
|
||||
HScrollBar *h_scroll = button_scroll->get_h_scroll_bar();
|
||||
left_button->set_disabled(h_scroll->get_value() == 0);
|
||||
right_button->set_disabled(h_scroll->get_value() + h_scroll->get_page() == h_scroll->get_max());
|
||||
}
|
||||
|
||||
void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock) {
|
||||
ERR_FAIL_INDEX(p_idx, items.size());
|
||||
|
||||
if (items[p_idx].control->is_visible() == p_visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
SplitContainer *center_split = Object::cast_to<SplitContainer>(get_parent());
|
||||
ERR_FAIL_NULL(center_split);
|
||||
|
||||
if (p_visible) {
|
||||
if (!p_ignore_lock && lock_panel_switching && pin_button->is_visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
items[i].button->set_pressed_no_signal(i == p_idx);
|
||||
items[i].control->set_visible(i == p_idx);
|
||||
}
|
||||
if (EditorDebuggerNode::get_singleton() == items[p_idx].control) {
|
||||
// This is the debug panel which uses tabs, so the top section should be smaller.
|
||||
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles)));
|
||||
} else {
|
||||
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
|
||||
}
|
||||
|
||||
center_split->set_dragger_visibility(SplitContainer::DRAGGER_VISIBLE);
|
||||
center_split->set_collapsed(false);
|
||||
pin_button->show();
|
||||
|
||||
expand_button->show();
|
||||
if (expand_button->is_pressed()) {
|
||||
EditorNode::get_top_split()->hide();
|
||||
}
|
||||
callable_mp(button_scroll, &ScrollContainer::ensure_control_visible).call_deferred(items[p_idx].button);
|
||||
} else {
|
||||
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
|
||||
items[p_idx].button->set_pressed_no_signal(false);
|
||||
items[p_idx].control->set_visible(false);
|
||||
center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
|
||||
center_split->set_collapsed(true);
|
||||
pin_button->hide();
|
||||
|
||||
expand_button->hide();
|
||||
if (expand_button->is_pressed()) {
|
||||
EditorNode::get_top_split()->show();
|
||||
}
|
||||
}
|
||||
|
||||
last_opened_control = items[p_idx].control;
|
||||
}
|
||||
|
||||
void EditorBottomPanel::_pin_button_toggled(bool p_pressed) {
|
||||
lock_panel_switching = p_pressed;
|
||||
}
|
||||
|
||||
void EditorBottomPanel::_expand_button_toggled(bool p_pressed) {
|
||||
EditorNode::get_top_split()->set_visible(!p_pressed);
|
||||
}
|
||||
|
||||
bool EditorBottomPanel::_button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control) {
|
||||
if (!p_button->is_pressed()) {
|
||||
_switch_by_control(true, p_control, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void EditorBottomPanel::save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const {
|
||||
int selected_item_idx = -1;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items[i].button->is_pressed()) {
|
||||
selected_item_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (selected_item_idx != -1) {
|
||||
p_config_file->set_value(p_section, "selected_bottom_panel_item", selected_item_idx);
|
||||
} else {
|
||||
p_config_file->set_value(p_section, "selected_bottom_panel_item", Variant());
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBottomPanel::load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section) {
|
||||
bool has_active_tab = false;
|
||||
if (p_config_file->has_section_key(p_section, "selected_bottom_panel_item")) {
|
||||
int selected_item_idx = p_config_file->get_value(p_section, "selected_bottom_panel_item");
|
||||
if (selected_item_idx >= 0 && selected_item_idx < items.size()) {
|
||||
// Make sure we don't try to open contextual editors which are not enabled in the current context.
|
||||
if (items[selected_item_idx].button->is_visible()) {
|
||||
_switch_to_item(true, selected_item_idx);
|
||||
has_active_tab = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there is no active tab we need to collapse the panel.
|
||||
if (!has_active_tab) {
|
||||
items[0].control->show(); // _switch_to_item() can collapse only visible tabs.
|
||||
_switch_to_item(false, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref<Shortcut> &p_shortcut, bool p_at_front) {
|
||||
Button *tb = memnew(Button);
|
||||
tb->set_theme_type_variation("BottomPanelButton");
|
||||
tb->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item, true));
|
||||
tb->set_drag_forwarding(Callable(), callable_mp(this, &EditorBottomPanel::_button_drag_hover).bind(tb, p_item), Callable());
|
||||
tb->set_text(p_text);
|
||||
tb->set_shortcut(p_shortcut);
|
||||
tb->set_toggle_mode(true);
|
||||
tb->set_focus_mode(Control::FOCUS_NONE);
|
||||
item_vbox->add_child(p_item);
|
||||
|
||||
bottom_hbox->move_to_front();
|
||||
button_hbox->add_child(tb);
|
||||
if (p_at_front) {
|
||||
button_hbox->move_child(tb, 0);
|
||||
}
|
||||
p_item->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
p_item->hide();
|
||||
|
||||
BottomPanelItem bpi;
|
||||
bpi.button = tb;
|
||||
bpi.control = p_item;
|
||||
bpi.name = p_text;
|
||||
if (p_at_front) {
|
||||
items.insert(0, bpi);
|
||||
} else {
|
||||
items.push_back(bpi);
|
||||
}
|
||||
|
||||
return tb;
|
||||
}
|
||||
|
||||
void EditorBottomPanel::remove_item(Control *p_item) {
|
||||
bool was_visible = false;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items[i].control == p_item) {
|
||||
if (p_item->is_visible_in_tree()) {
|
||||
was_visible = true;
|
||||
}
|
||||
item_vbox->remove_child(items[i].control);
|
||||
button_hbox->remove_child(items[i].button);
|
||||
memdelete(items[i].button);
|
||||
items.remove_at(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (was_visible) {
|
||||
// Open the first panel to ensure that if the removed dock was visible, the bottom
|
||||
// panel will not collapse.
|
||||
_switch_to_item(true, 0, true);
|
||||
} else if (last_opened_control == p_item) {
|
||||
// When a dock is removed by plugins, it might not have been visible, and it
|
||||
// might have been the last_opened_control. We need to make sure to reset the last opened control.
|
||||
last_opened_control = items[0].control;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBottomPanel::make_item_visible(Control *p_item, bool p_visible, bool p_ignore_lock) {
|
||||
_switch_by_control(p_visible, p_item, p_ignore_lock);
|
||||
}
|
||||
|
||||
void EditorBottomPanel::move_item_to_end(Control *p_item) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items[i].control == p_item) {
|
||||
items[i].button->move_to_front();
|
||||
SWAP(items.write[i], items.write[items.size() - 1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBottomPanel::hide_bottom_panel() {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items[i].control->is_visible()) {
|
||||
_switch_to_item(false, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBottomPanel::toggle_last_opened_bottom_panel() {
|
||||
// Select by control instead of index, so that the last bottom panel is opened correctly
|
||||
// if it's been reordered since.
|
||||
if (last_opened_control) {
|
||||
_switch_by_control(!last_opened_control->is_visible(), last_opened_control, true);
|
||||
} else {
|
||||
// Open the first panel in the list if no panel was opened this session.
|
||||
_switch_to_item(true, 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBottomPanel::set_expanded(bool p_expanded) {
|
||||
expand_button->set_pressed(p_expanded);
|
||||
}
|
||||
|
||||
EditorBottomPanel::EditorBottomPanel() {
|
||||
item_vbox = memnew(VBoxContainer);
|
||||
add_child(item_vbox);
|
||||
|
||||
bottom_hbox = memnew(HBoxContainer);
|
||||
bottom_hbox->set_custom_minimum_size(Size2(0, 24 * EDSCALE)); // Adjust for the height of the "Expand Bottom Dock" icon.
|
||||
item_vbox->add_child(bottom_hbox);
|
||||
|
||||
left_button = memnew(Button);
|
||||
left_button->set_tooltip_text(TTR("Scroll Left\nHold Ctrl to scroll to the begin.\nHold Shift to scroll one page."));
|
||||
left_button->set_theme_type_variation("BottomPanelButton");
|
||||
left_button->set_focus_mode(Control::FOCUS_NONE);
|
||||
left_button->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_scroll).bind(false));
|
||||
bottom_hbox->add_child(left_button);
|
||||
left_button->hide();
|
||||
|
||||
button_scroll = memnew(ScrollContainer);
|
||||
button_scroll->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
button_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_NEVER);
|
||||
button_scroll->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
|
||||
button_scroll->get_h_scroll_bar()->connect(CoreStringName(changed), callable_mp(this, &EditorBottomPanel::_update_scroll_buttons), CONNECT_DEFERRED);
|
||||
button_scroll->get_h_scroll_bar()->connect(SceneStringName(value_changed), callable_mp(this, &EditorBottomPanel::_update_disabled_buttons).unbind(1), CONNECT_DEFERRED);
|
||||
bottom_hbox->add_child(button_scroll);
|
||||
|
||||
right_button = memnew(Button);
|
||||
right_button->set_tooltip_text(TTR("Scroll Right\nHold Ctrl to scroll to the end.\nHold Shift to scroll one page."));
|
||||
right_button->set_theme_type_variation("BottomPanelButton");
|
||||
right_button->set_focus_mode(Control::FOCUS_NONE);
|
||||
right_button->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_scroll).bind(true));
|
||||
bottom_hbox->add_child(right_button);
|
||||
right_button->hide();
|
||||
|
||||
callable_mp(this, &EditorBottomPanel::_update_scroll_buttons).call_deferred();
|
||||
|
||||
button_hbox = memnew(HBoxContainer);
|
||||
button_hbox->set_h_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_BEGIN);
|
||||
button_scroll->add_child(button_hbox);
|
||||
|
||||
editor_toaster = memnew(EditorToaster);
|
||||
bottom_hbox->add_child(editor_toaster);
|
||||
|
||||
EditorVersionButton *version_btn = memnew(EditorVersionButton(EditorVersionButton::FORMAT_BASIC));
|
||||
// Fade out the version label to be less prominent, but still readable.
|
||||
version_btn->set_self_modulate(Color(1, 1, 1, 0.65));
|
||||
version_btn->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
|
||||
bottom_hbox->add_child(version_btn);
|
||||
|
||||
// Add a dummy control node for horizontal spacing.
|
||||
Control *h_spacer = memnew(Control);
|
||||
bottom_hbox->add_child(h_spacer);
|
||||
|
||||
pin_button = memnew(Button);
|
||||
bottom_hbox->add_child(pin_button);
|
||||
pin_button->hide();
|
||||
pin_button->set_theme_type_variation("FlatMenuButton");
|
||||
pin_button->set_toggle_mode(true);
|
||||
pin_button->set_tooltip_text(TTR("Pin Bottom Panel Switching"));
|
||||
pin_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_pin_button_toggled));
|
||||
|
||||
expand_button = memnew(Button);
|
||||
bottom_hbox->add_child(expand_button);
|
||||
expand_button->hide();
|
||||
expand_button->set_theme_type_variation("FlatMenuButton");
|
||||
expand_button->set_toggle_mode(true);
|
||||
expand_button->set_shortcut(ED_SHORTCUT_AND_COMMAND("editor/bottom_panel_expand", TTRC("Expand Bottom Panel"), KeyModifierMask::SHIFT | Key::F12));
|
||||
expand_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_expand_button_toggled));
|
||||
}
|
||||
94
engine/editor/gui/editor_bottom_panel.h
Normal file
94
engine/editor/gui/editor_bottom_panel.h
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**************************************************************************/
|
||||
/* editor_bottom_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 EDITOR_BOTTOM_PANEL_H
|
||||
#define EDITOR_BOTTOM_PANEL_H
|
||||
|
||||
#include "scene/gui/panel_container.h"
|
||||
|
||||
class Button;
|
||||
class ConfigFile;
|
||||
class EditorToaster;
|
||||
class HBoxContainer;
|
||||
class VBoxContainer;
|
||||
class ScrollContainer;
|
||||
|
||||
class EditorBottomPanel : public PanelContainer {
|
||||
GDCLASS(EditorBottomPanel, PanelContainer);
|
||||
|
||||
struct BottomPanelItem {
|
||||
String name;
|
||||
Control *control = nullptr;
|
||||
Button *button = nullptr;
|
||||
};
|
||||
|
||||
Vector<BottomPanelItem> items;
|
||||
bool lock_panel_switching = false;
|
||||
|
||||
VBoxContainer *item_vbox = nullptr;
|
||||
HBoxContainer *bottom_hbox = nullptr;
|
||||
Button *left_button = nullptr;
|
||||
Button *right_button = nullptr;
|
||||
ScrollContainer *button_scroll = nullptr;
|
||||
HBoxContainer *button_hbox = nullptr;
|
||||
EditorToaster *editor_toaster = nullptr;
|
||||
Button *pin_button = nullptr;
|
||||
Button *expand_button = nullptr;
|
||||
Control *last_opened_control = nullptr;
|
||||
|
||||
void _switch_by_control(bool p_visible, Control *p_control, bool p_ignore_lock = false);
|
||||
void _switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock = false);
|
||||
void _pin_button_toggled(bool p_pressed);
|
||||
void _expand_button_toggled(bool p_pressed);
|
||||
void _scroll(bool p_right);
|
||||
void _update_scroll_buttons();
|
||||
void _update_disabled_buttons();
|
||||
|
||||
bool _button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const;
|
||||
void load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section);
|
||||
|
||||
Button *add_item(String p_text, Control *p_item, const Ref<Shortcut> &p_shortcut = nullptr, bool p_at_front = false);
|
||||
void remove_item(Control *p_item);
|
||||
void make_item_visible(Control *p_item, bool p_visible = true, bool p_ignore_lock = false);
|
||||
void move_item_to_end(Control *p_item);
|
||||
void hide_bottom_panel();
|
||||
void toggle_last_opened_bottom_panel();
|
||||
void set_expanded(bool p_expanded);
|
||||
|
||||
EditorBottomPanel();
|
||||
};
|
||||
|
||||
#endif // EDITOR_BOTTOM_PANEL_H
|
||||
234
engine/editor/gui/editor_dir_dialog.cpp
Normal file
234
engine/editor/gui/editor_dir_dialog.cpp
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
/**************************************************************************/
|
||||
/* editor_dir_dialog.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 "editor_dir_dialog.h"
|
||||
|
||||
#include "editor/directory_create_dialog.h"
|
||||
#include "editor/editor_file_system.h"
|
||||
#include "editor/filesystem_dock.h"
|
||||
#include "editor/themes/editor_theme_manager.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/tree.h"
|
||||
#include "servers/display_server.h"
|
||||
|
||||
void EditorDirDialog::_update_dir(const Color &p_default_folder_color, const Dictionary &p_assigned_folder_colors, const HashMap<String, Color> &p_folder_colors, bool p_is_dark_theme, TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path) {
|
||||
updating = true;
|
||||
|
||||
const String path = p_dir->get_path();
|
||||
|
||||
p_item->set_metadata(0, path);
|
||||
p_item->set_icon(0, tree->get_editor_theme_icon(SNAME("Folder")));
|
||||
|
||||
if (!p_item->get_parent()) {
|
||||
p_item->set_text(0, "res://");
|
||||
p_item->set_icon_modulate(0, p_default_folder_color);
|
||||
} else {
|
||||
if (!opened_paths.has(path) && (p_select_path.is_empty() || !p_select_path.begins_with(path))) {
|
||||
p_item->set_collapsed(true);
|
||||
}
|
||||
|
||||
p_item->set_text(0, p_dir->get_name());
|
||||
|
||||
if (p_assigned_folder_colors.has(path)) {
|
||||
const Color &folder_color = p_folder_colors[p_assigned_folder_colors[path]];
|
||||
p_item->set_icon_modulate(0, p_is_dark_theme ? folder_color : folder_color * FileSystemDock::ITEM_COLOR_SCALE);
|
||||
p_item->set_custom_bg_color(0, Color(folder_color, p_is_dark_theme ? FileSystemDock::ITEM_ALPHA_MIN : FileSystemDock::ITEM_ALPHA_MAX));
|
||||
} else {
|
||||
TreeItem *parent_item = p_item->get_parent();
|
||||
Color parent_bg_color = parent_item->get_custom_bg_color(0);
|
||||
if (parent_bg_color != Color()) {
|
||||
p_item->set_custom_bg_color(0, p_assigned_folder_colors.has(parent_item->get_metadata(0)) ? parent_bg_color.darkened(FileSystemDock::ITEM_BG_DARK_SCALE) : parent_bg_color);
|
||||
p_item->set_icon_modulate(0, parent_item->get_icon_modulate(0));
|
||||
} else {
|
||||
p_item->set_icon_modulate(0, p_default_folder_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (path == new_dir_path || !p_item->get_parent()) {
|
||||
p_item->select(0);
|
||||
}
|
||||
|
||||
updating = false;
|
||||
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
|
||||
TreeItem *ti = tree->create_item(p_item);
|
||||
_update_dir(p_default_folder_color, p_assigned_folder_colors, p_folder_colors, p_is_dark_theme, ti, p_dir->get_subdir(i));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDirDialog::config(const Vector<String> &p_paths) {
|
||||
ERR_FAIL_COND(p_paths.is_empty());
|
||||
|
||||
if (p_paths.size() == 1) {
|
||||
String path = p_paths[0];
|
||||
if (path.ends_with("/")) {
|
||||
path = path.substr(0, path.length() - 1);
|
||||
}
|
||||
// TRANSLATORS: %s is the file name that will be moved or duplicated.
|
||||
set_title(vformat(TTR("Move/Duplicate: %s"), path.get_file()));
|
||||
} else {
|
||||
// TRANSLATORS: %d is the number of files that will be moved or duplicated.
|
||||
set_title(vformat(TTRN("Move/Duplicate %d Item", "Move/Duplicate %d Items", p_paths.size()), p_paths.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDirDialog::reload(const String &p_path) {
|
||||
if (!is_visible()) {
|
||||
must_reload = true;
|
||||
return;
|
||||
}
|
||||
|
||||
tree->clear();
|
||||
TreeItem *root = tree->create_item();
|
||||
_update_dir(tree->get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog")), FileSystemDock::get_singleton()->get_assigned_folder_colors(), FileSystemDock::get_singleton()->get_folder_colors(), EditorThemeManager::is_dark_theme(), root, EditorFileSystem::get_singleton()->get_filesystem(), p_path);
|
||||
_item_collapsed(root);
|
||||
new_dir_path.clear();
|
||||
must_reload = false;
|
||||
}
|
||||
|
||||
void EditorDirDialog::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
FileSystemDock::get_singleton()->connect("folder_color_changed", callable_mp(this, &EditorDirDialog::reload).bind(""));
|
||||
EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &EditorDirDialog::reload).bind(""));
|
||||
reload();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
EditorFileSystem::get_singleton()->disconnect("filesystem_changed", callable_mp(this, &EditorDirDialog::reload));
|
||||
FileSystemDock::get_singleton()->disconnect("folder_color_changed", callable_mp(this, &EditorDirDialog::reload));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||
if (must_reload && is_visible()) {
|
||||
reload();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDirDialog::_item_collapsed(Object *p_item) {
|
||||
TreeItem *item = Object::cast_to<TreeItem>(p_item);
|
||||
|
||||
if (updating) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item->is_collapsed()) {
|
||||
opened_paths.erase(item->get_metadata(0));
|
||||
} else {
|
||||
opened_paths.insert(item->get_metadata(0));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDirDialog::_item_activated() {
|
||||
TreeItem *ti = tree->get_selected();
|
||||
ERR_FAIL_NULL(ti);
|
||||
if (ti->get_child_count() > 0) {
|
||||
ti->set_collapsed(!ti->is_collapsed());
|
||||
}
|
||||
}
|
||||
|
||||
void EditorDirDialog::_copy_pressed() {
|
||||
TreeItem *ti = tree->get_selected();
|
||||
ERR_FAIL_NULL(ti);
|
||||
|
||||
hide();
|
||||
emit_signal(SNAME("copy_pressed"), ti->get_metadata(0));
|
||||
}
|
||||
|
||||
void EditorDirDialog::ok_pressed() {
|
||||
TreeItem *ti = tree->get_selected();
|
||||
ERR_FAIL_NULL(ti);
|
||||
|
||||
hide();
|
||||
emit_signal(SNAME("move_pressed"), ti->get_metadata(0));
|
||||
}
|
||||
|
||||
void EditorDirDialog::_make_dir() {
|
||||
TreeItem *ti = tree->get_selected();
|
||||
ERR_FAIL_NULL(ti);
|
||||
const String &directory = ti->get_metadata(0);
|
||||
makedialog->config(directory, callable_mp(this, &EditorDirDialog::_make_dir_confirm).bind(directory), DirectoryCreateDialog::MODE_DIRECTORY, "new folder");
|
||||
makedialog->popup_centered();
|
||||
}
|
||||
|
||||
void EditorDirDialog::_make_dir_confirm(const String &p_path, const String &p_base_dir) {
|
||||
FileSystemDock::get_singleton()->create_directory(p_path, p_base_dir);
|
||||
|
||||
// Multiple level of directories can be created at once.
|
||||
String base_dir = p_path.get_base_dir();
|
||||
while (true) {
|
||||
opened_paths.insert(base_dir + "/");
|
||||
if (base_dir == "res://") {
|
||||
break;
|
||||
}
|
||||
base_dir = base_dir.get_base_dir();
|
||||
}
|
||||
|
||||
new_dir_path = p_path + "/";
|
||||
}
|
||||
|
||||
void EditorDirDialog::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("copy_pressed", PropertyInfo(Variant::STRING, "dir")));
|
||||
ADD_SIGNAL(MethodInfo("move_pressed", PropertyInfo(Variant::STRING, "dir")));
|
||||
}
|
||||
|
||||
EditorDirDialog::EditorDirDialog() {
|
||||
set_hide_on_ok(false);
|
||||
|
||||
VBoxContainer *vb = memnew(VBoxContainer);
|
||||
add_child(vb);
|
||||
|
||||
HBoxContainer *hb = memnew(HBoxContainer);
|
||||
vb->add_child(hb);
|
||||
|
||||
hb->add_child(memnew(Label(TTR("Choose target directory:"))));
|
||||
hb->add_spacer();
|
||||
|
||||
makedir = memnew(Button(TTR("Create Folder")));
|
||||
hb->add_child(makedir);
|
||||
makedir->connect(SceneStringName(pressed), callable_mp(this, &EditorDirDialog::_make_dir));
|
||||
|
||||
tree = memnew(Tree);
|
||||
tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
vb->add_child(tree);
|
||||
tree->connect("item_activated", callable_mp(this, &EditorDirDialog::_item_activated));
|
||||
tree->connect("item_collapsed", callable_mp(this, &EditorDirDialog::_item_collapsed), CONNECT_DEFERRED);
|
||||
|
||||
set_ok_button_text(TTR("Move"));
|
||||
|
||||
copy = add_button(TTR("Copy"), !DisplayServer::get_singleton()->get_swap_cancel_ok());
|
||||
copy->connect(SceneStringName(pressed), callable_mp(this, &EditorDirDialog::_copy_pressed));
|
||||
|
||||
makedialog = memnew(DirectoryCreateDialog);
|
||||
add_child(makedialog);
|
||||
}
|
||||
77
engine/editor/gui/editor_dir_dialog.h
Normal file
77
engine/editor/gui/editor_dir_dialog.h
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/**************************************************************************/
|
||||
/* editor_dir_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 EDITOR_DIR_DIALOG_H
|
||||
#define EDITOR_DIR_DIALOG_H
|
||||
|
||||
#include "scene/gui/dialogs.h"
|
||||
|
||||
class DirectoryCreateDialog;
|
||||
class EditorFileSystemDirectory;
|
||||
class Tree;
|
||||
class TreeItem;
|
||||
|
||||
class EditorDirDialog : public ConfirmationDialog {
|
||||
GDCLASS(EditorDirDialog, ConfirmationDialog);
|
||||
|
||||
DirectoryCreateDialog *makedialog = nullptr;
|
||||
|
||||
Button *makedir = nullptr;
|
||||
Button *copy = nullptr;
|
||||
HashSet<String> opened_paths;
|
||||
String new_dir_path;
|
||||
|
||||
Tree *tree = nullptr;
|
||||
bool updating = false;
|
||||
|
||||
void _item_collapsed(Object *p_item);
|
||||
void _item_activated();
|
||||
void _update_dir(const Color &p_default_folder_color, const Dictionary &p_assigned_folder_colors, const HashMap<String, Color> &p_folder_colors, bool p_is_dark_theme, TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path = String());
|
||||
|
||||
void _make_dir();
|
||||
void _make_dir_confirm(const String &p_path, const String &p_base_dir);
|
||||
|
||||
void _copy_pressed();
|
||||
void ok_pressed() override;
|
||||
|
||||
bool must_reload = false;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void config(const Vector<String> &p_paths);
|
||||
void reload(const String &p_path = "");
|
||||
|
||||
EditorDirDialog();
|
||||
};
|
||||
|
||||
#endif // EDITOR_DIR_DIALOG_H
|
||||
2704
engine/editor/gui/editor_file_dialog.cpp
Normal file
2704
engine/editor/gui/editor_file_dialog.cpp
Normal file
File diff suppressed because it is too large
Load diff
382
engine/editor/gui/editor_file_dialog.h
Normal file
382
engine/editor/gui/editor_file_dialog.h
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
/**************************************************************************/
|
||||
/* editor_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 EDITOR_FILE_DIALOG_H
|
||||
#define EDITOR_FILE_DIALOG_H
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "editor/file_info.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/property_list_helper.h"
|
||||
|
||||
class DependencyRemoveDialog;
|
||||
class GridContainer;
|
||||
class HSplitContainer;
|
||||
class HFlowContainer;
|
||||
class ItemList;
|
||||
class MenuButton;
|
||||
class OptionButton;
|
||||
class PopupMenu;
|
||||
class TextureRect;
|
||||
class VSeparator;
|
||||
|
||||
class EditorFileDialog : public ConfirmationDialog {
|
||||
GDCLASS(EditorFileDialog, ConfirmationDialog);
|
||||
|
||||
public:
|
||||
enum DisplayMode {
|
||||
DISPLAY_THUMBNAILS,
|
||||
DISPLAY_LIST
|
||||
};
|
||||
|
||||
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)(EditorFileDialog *);
|
||||
|
||||
static GetIconFunc get_icon_func;
|
||||
static GetIconFunc get_thumbnail_func;
|
||||
static RegisterFunc register_func;
|
||||
static RegisterFunc unregister_func;
|
||||
|
||||
private:
|
||||
enum ItemMenu {
|
||||
ITEM_MENU_COPY_PATH,
|
||||
ITEM_MENU_DELETE,
|
||||
ITEM_MENU_REFRESH,
|
||||
ITEM_MENU_NEW_FOLDER,
|
||||
ITEM_MENU_SHOW_IN_EXPLORER,
|
||||
ITEM_MENU_SHOW_BUNDLE_CONTENT,
|
||||
};
|
||||
|
||||
ConfirmationDialog *makedialog = nullptr;
|
||||
LineEdit *makedirname = nullptr;
|
||||
|
||||
VSeparator *makedir_sep = nullptr;
|
||||
Button *makedir = nullptr;
|
||||
Access access = ACCESS_RESOURCES;
|
||||
|
||||
HFlowContainer *flow_checkbox_options = nullptr;
|
||||
GridContainer *grid_select_options = nullptr;
|
||||
VBoxContainer *vbox = nullptr;
|
||||
FileMode mode = FILE_MODE_SAVE_FILE;
|
||||
bool can_create_dir = false;
|
||||
LineEdit *dir = nullptr;
|
||||
|
||||
Button *dir_prev = nullptr;
|
||||
Button *dir_next = nullptr;
|
||||
Button *dir_up = nullptr;
|
||||
|
||||
HBoxContainer *drives_container = nullptr;
|
||||
HBoxContainer *shortcuts_container = nullptr;
|
||||
OptionButton *drives = nullptr;
|
||||
ItemList *item_list = nullptr;
|
||||
PopupMenu *item_menu = nullptr;
|
||||
TextureRect *preview = nullptr;
|
||||
VBoxContainer *preview_vb = nullptr;
|
||||
HSplitContainer *body_hsplit = nullptr;
|
||||
HSplitContainer *list_hb = nullptr;
|
||||
HBoxContainer *file_box = nullptr;
|
||||
LineEdit *file = nullptr;
|
||||
OptionButton *filter = nullptr;
|
||||
AcceptDialog *error_dialog = nullptr;
|
||||
Ref<DirAccess> dir_access;
|
||||
ConfirmationDialog *confirm_save = nullptr;
|
||||
DependencyRemoveDialog *dep_remove_dialog = nullptr;
|
||||
ConfirmationDialog *global_remove_dialog = nullptr;
|
||||
VBoxContainer *side_vbox = nullptr;
|
||||
VBoxContainer *vbc = nullptr;
|
||||
HBoxContainer *pathhb = nullptr;
|
||||
|
||||
Button *mode_thumbnails = nullptr;
|
||||
Button *mode_list = nullptr;
|
||||
|
||||
Button *refresh = nullptr;
|
||||
Button *favorite = nullptr;
|
||||
Button *show_hidden = nullptr;
|
||||
Button *show_search_filter_button = nullptr;
|
||||
|
||||
String search_string;
|
||||
bool show_search_filter = false;
|
||||
HBoxContainer *filter_hb = nullptr;
|
||||
LineEdit *filter_box = nullptr;
|
||||
FileSortOption file_sort = FileSortOption::FILE_SORT_NAME;
|
||||
MenuButton *file_sort_button = nullptr;
|
||||
|
||||
Button *fav_up = nullptr;
|
||||
Button *fav_down = nullptr;
|
||||
|
||||
ItemList *favorites = nullptr;
|
||||
ItemList *recent = nullptr;
|
||||
|
||||
Vector<String> local_history;
|
||||
int local_history_pos = 0;
|
||||
void _push_history();
|
||||
|
||||
Vector<String> filters;
|
||||
Vector<String> processed_filters;
|
||||
|
||||
bool previews_enabled = true;
|
||||
bool preview_waiting = false;
|
||||
int preview_wheel_index = 0;
|
||||
float preview_wheel_timeout = 0.0f;
|
||||
|
||||
static bool default_show_hidden_files;
|
||||
static DisplayMode default_display_mode;
|
||||
bool show_hidden_files;
|
||||
DisplayMode display_mode;
|
||||
|
||||
bool disable_overwrite_warning = false;
|
||||
bool is_invalidating = false;
|
||||
|
||||
struct ThemeCache {
|
||||
Ref<Texture2D> parent_folder;
|
||||
Ref<Texture2D> forward_folder;
|
||||
Ref<Texture2D> back_folder;
|
||||
Ref<Texture2D> open_folder;
|
||||
Ref<Texture2D> reload;
|
||||
Ref<Texture2D> toggle_hidden;
|
||||
Ref<Texture2D> toggle_filename_filter;
|
||||
Ref<Texture2D> favorite;
|
||||
Ref<Texture2D> mode_thumbnails;
|
||||
Ref<Texture2D> mode_list;
|
||||
Ref<Texture2D> create_folder;
|
||||
Ref<Texture2D> favorites_up;
|
||||
Ref<Texture2D> favorites_down;
|
||||
|
||||
Ref<Texture2D> filter_box;
|
||||
Ref<Texture2D> file_sort_button;
|
||||
|
||||
Ref<Texture2D> folder;
|
||||
Color folder_icon_color;
|
||||
|
||||
Ref<Texture2D> action_copy;
|
||||
Ref<Texture2D> action_delete;
|
||||
Ref<Texture2D> filesystem;
|
||||
|
||||
Ref<Texture2D> folder_medium_thumbnail;
|
||||
Ref<Texture2D> file_medium_thumbnail;
|
||||
Ref<Texture2D> folder_big_thumbnail;
|
||||
Ref<Texture2D> file_big_thumbnail;
|
||||
|
||||
Ref<Texture2D> progress[8]{};
|
||||
} 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;
|
||||
String full_dir;
|
||||
|
||||
void update_dir();
|
||||
void update_file_name();
|
||||
void update_file_list();
|
||||
void update_search_filter_gui();
|
||||
void update_filters();
|
||||
|
||||
void _focus_file_text();
|
||||
|
||||
void _update_favorites();
|
||||
void _favorite_pressed();
|
||||
void _favorite_selected(int p_idx);
|
||||
void _favorite_move_up();
|
||||
void _favorite_move_down();
|
||||
|
||||
void _update_recent();
|
||||
void _recent_selected(int p_idx);
|
||||
|
||||
void _item_selected(int p_item);
|
||||
void _multi_selected(int p_item, bool p_selected);
|
||||
void _items_clear_selection(const Vector2 &p_pos, MouseButton p_mouse_button_index);
|
||||
void _item_dc_selected(int p_item);
|
||||
|
||||
void _item_list_item_rmb_clicked(int p_item, const Vector2 &p_pos, MouseButton p_mouse_button_index);
|
||||
void _item_list_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index);
|
||||
void _item_menu_id_pressed(int p_option);
|
||||
|
||||
void _select_drive(int p_idx);
|
||||
void _dir_submitted(const String &p_dir);
|
||||
void _action_pressed();
|
||||
void _save_confirm_pressed();
|
||||
void _cancel_pressed();
|
||||
void _filter_selected(int);
|
||||
void _make_dir();
|
||||
void _make_dir_confirm();
|
||||
|
||||
void _focus_filter_box();
|
||||
void _filter_changed(const String &p_text);
|
||||
void _search_filter_selected();
|
||||
void _file_sort_popup(int p_id);
|
||||
|
||||
void _delete_items();
|
||||
void _delete_files_global();
|
||||
|
||||
void _update_drives(bool p_select = true);
|
||||
void _update_icons();
|
||||
|
||||
void _go_up();
|
||||
void _go_back();
|
||||
void _go_forward();
|
||||
|
||||
void _invalidate();
|
||||
|
||||
virtual void _post_popup() override;
|
||||
|
||||
void _save_to_recent();
|
||||
// Callback function is callback(String p_path,Ref<Texture2D> preview,Variant udata) preview null if could not load.
|
||||
|
||||
void _thumbnail_result(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata);
|
||||
void _thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata);
|
||||
void _request_single_thumbnail(const String &p_path);
|
||||
|
||||
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
bool _is_open_should_be_disabled();
|
||||
|
||||
void _update_side_menu_visibility(bool p_native_dlg);
|
||||
|
||||
void _native_popup();
|
||||
void _native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter, const Dictionary &p_selected_options);
|
||||
|
||||
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);
|
||||
|
||||
protected:
|
||||
virtual void _update_theme_item_cache() override;
|
||||
|
||||
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:
|
||||
Color get_dir_icon_color(const String &p_dir_path);
|
||||
|
||||
virtual void set_visible(bool p_visible) override;
|
||||
virtual void popup(const Rect2i &p_rect = Rect2i()) override;
|
||||
|
||||
// Public for use with callable_mp.
|
||||
void _file_submitted(const String &p_file);
|
||||
|
||||
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 clear_search_filter();
|
||||
void set_search_filter(const String &p_search_filter);
|
||||
String get_search_filter() 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_display_mode(DisplayMode p_mode);
|
||||
DisplayMode get_display_mode() 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;
|
||||
|
||||
static void set_default_show_hidden_files(bool p_show);
|
||||
static void set_default_display_mode(DisplayMode p_mode);
|
||||
void set_show_hidden_files(bool p_show);
|
||||
void set_show_search_filter(bool p_show);
|
||||
bool is_showing_hidden_files() const;
|
||||
|
||||
void invalidate();
|
||||
|
||||
void set_disable_overwrite_warning(bool p_disable);
|
||||
bool is_overwrite_warning_disabled() const;
|
||||
|
||||
void set_previews_enabled(bool p_enabled);
|
||||
bool are_previews_enabled();
|
||||
|
||||
void add_side_menu(Control *p_menu, const String &p_title = "");
|
||||
|
||||
EditorFileDialog();
|
||||
~EditorFileDialog();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(EditorFileDialog::FileMode);
|
||||
VARIANT_ENUM_CAST(EditorFileDialog::Access);
|
||||
VARIANT_ENUM_CAST(EditorFileDialog::DisplayMode);
|
||||
|
||||
#endif // EDITOR_FILE_DIALOG_H
|
||||
248
engine/editor/gui/editor_object_selector.cpp
Normal file
248
engine/editor/gui/editor_object_selector.cpp
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
/**************************************************************************/
|
||||
/* editor_object_selector.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 "editor_object_selector.h"
|
||||
|
||||
#include "editor/editor_data.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
|
||||
Size2 EditorObjectSelector::get_minimum_size() const {
|
||||
Ref<Font> font = get_theme_font(SceneStringName(font));
|
||||
int font_size = get_theme_font_size(SceneStringName(font_size));
|
||||
return Button::get_minimum_size() + Size2(0, font->get_height(font_size));
|
||||
}
|
||||
|
||||
void EditorObjectSelector::_add_children_to_popup(Object *p_obj, int p_depth) {
|
||||
if (p_depth > 8) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<PropertyInfo> pinfo;
|
||||
p_obj->get_property_list(&pinfo);
|
||||
for (const PropertyInfo &E : pinfo) {
|
||||
if (!(E.usage & PROPERTY_USAGE_EDITOR)) {
|
||||
continue;
|
||||
}
|
||||
if (E.hint != PROPERTY_HINT_RESOURCE_TYPE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Variant value = p_obj->get(E.name);
|
||||
if (value.get_type() != Variant::OBJECT) {
|
||||
continue;
|
||||
}
|
||||
Object *obj = value;
|
||||
if (!obj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Ref<Texture2D> obj_icon = EditorNode::get_singleton()->get_object_icon(obj);
|
||||
|
||||
String proper_name = "";
|
||||
Vector<String> name_parts = E.name.split("/");
|
||||
|
||||
for (int i = 0; i < name_parts.size(); i++) {
|
||||
if (i > 0) {
|
||||
proper_name += " > ";
|
||||
}
|
||||
proper_name += name_parts[i].capitalize();
|
||||
}
|
||||
|
||||
int index = sub_objects_menu->get_item_count();
|
||||
sub_objects_menu->add_icon_item(obj_icon, proper_name, objects.size());
|
||||
sub_objects_menu->set_item_indent(index, p_depth);
|
||||
objects.push_back(obj->get_instance_id());
|
||||
|
||||
_add_children_to_popup(obj, p_depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorObjectSelector::_show_popup() {
|
||||
if (sub_objects_menu->is_visible()) {
|
||||
sub_objects_menu->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
sub_objects_menu->clear();
|
||||
|
||||
Size2 size = get_size();
|
||||
Point2 gp = get_screen_position();
|
||||
gp.y += size.y;
|
||||
|
||||
sub_objects_menu->set_position(gp);
|
||||
sub_objects_menu->set_size(Size2(size.width, 1));
|
||||
|
||||
sub_objects_menu->popup();
|
||||
}
|
||||
|
||||
void EditorObjectSelector::_about_to_show() {
|
||||
Object *obj = ObjectDB::get_instance(history->get_path_object(history->get_path_size() - 1));
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
objects.clear();
|
||||
|
||||
_add_children_to_popup(obj);
|
||||
if (sub_objects_menu->get_item_count() == 0) {
|
||||
sub_objects_menu->add_item(TTR("No sub-resources found."));
|
||||
sub_objects_menu->set_item_disabled(0, true);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorObjectSelector::update_path() {
|
||||
for (int i = 0; i < history->get_path_size(); i++) {
|
||||
Object *obj = ObjectDB::get_instance(history->get_path_object(i));
|
||||
if (!obj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Ref<Texture2D> obj_icon = EditorNode::get_singleton()->get_object_icon(obj);
|
||||
|
||||
if (obj_icon.is_valid()) {
|
||||
current_object_icon->set_texture(obj_icon);
|
||||
}
|
||||
|
||||
if (i == history->get_path_size() - 1) {
|
||||
String name;
|
||||
if (obj->has_method("_get_editor_name")) {
|
||||
name = obj->call("_get_editor_name");
|
||||
} else if (Object::cast_to<Resource>(obj)) {
|
||||
Resource *r = Object::cast_to<Resource>(obj);
|
||||
if (r->get_path().is_resource_file()) {
|
||||
name = r->get_path().get_file();
|
||||
} else {
|
||||
name = r->get_name();
|
||||
}
|
||||
|
||||
if (name.is_empty()) {
|
||||
name = r->get_class();
|
||||
}
|
||||
} else if (obj->is_class("EditorDebuggerRemoteObject")) {
|
||||
name = obj->call("get_title");
|
||||
} else if (Object::cast_to<Node>(obj)) {
|
||||
name = Object::cast_to<Node>(obj)->get_name();
|
||||
} else if (Object::cast_to<Resource>(obj) && !Object::cast_to<Resource>(obj)->get_name().is_empty()) {
|
||||
name = Object::cast_to<Resource>(obj)->get_name();
|
||||
} else {
|
||||
name = obj->get_class();
|
||||
}
|
||||
|
||||
current_object_label->set_text(name);
|
||||
set_tooltip_text(obj->get_class());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorObjectSelector::clear_path() {
|
||||
set_disabled(true);
|
||||
set_tooltip_text("");
|
||||
|
||||
current_object_label->set_text("");
|
||||
current_object_icon->set_texture(nullptr);
|
||||
sub_objects_icon->hide();
|
||||
}
|
||||
|
||||
void EditorObjectSelector::enable_path() {
|
||||
set_disabled(false);
|
||||
sub_objects_icon->show();
|
||||
}
|
||||
|
||||
void EditorObjectSelector::_id_pressed(int p_idx) {
|
||||
ERR_FAIL_INDEX(p_idx, objects.size());
|
||||
|
||||
Object *obj = ObjectDB::get_instance(objects[p_idx]);
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
EditorNode::get_singleton()->push_item(obj);
|
||||
}
|
||||
|
||||
void EditorObjectSelector::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
update_path();
|
||||
|
||||
int icon_size = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
|
||||
|
||||
current_object_icon->set_custom_minimum_size(Size2(icon_size, icon_size));
|
||||
current_object_label->add_theme_font_override(SceneStringName(font), get_theme_font(SNAME("main"), EditorStringName(EditorFonts)));
|
||||
sub_objects_icon->set_texture(get_theme_icon(SNAME("arrow"), SNAME("OptionButton")));
|
||||
sub_objects_menu->add_theme_constant_override("icon_max_width", icon_size);
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_READY: {
|
||||
connect(SceneStringName(pressed), callable_mp(this, &EditorObjectSelector::_show_popup));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
EditorObjectSelector::EditorObjectSelector(EditorSelectionHistory *p_history) {
|
||||
history = p_history;
|
||||
|
||||
MarginContainer *main_mc = memnew(MarginContainer);
|
||||
main_mc->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
|
||||
main_mc->add_theme_constant_override("margin_left", 4 * EDSCALE);
|
||||
main_mc->add_theme_constant_override("margin_right", 6 * EDSCALE);
|
||||
add_child(main_mc);
|
||||
|
||||
HBoxContainer *main_hb = memnew(HBoxContainer);
|
||||
main_mc->add_child(main_hb);
|
||||
|
||||
current_object_icon = memnew(TextureRect);
|
||||
current_object_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
|
||||
current_object_icon->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
|
||||
main_hb->add_child(current_object_icon);
|
||||
|
||||
current_object_label = memnew(Label);
|
||||
current_object_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
|
||||
current_object_label->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
current_object_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
|
||||
current_object_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
main_hb->add_child(current_object_label);
|
||||
|
||||
sub_objects_icon = memnew(TextureRect);
|
||||
sub_objects_icon->hide();
|
||||
sub_objects_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
|
||||
main_hb->add_child(sub_objects_icon);
|
||||
|
||||
sub_objects_menu = memnew(PopupMenu);
|
||||
sub_objects_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
add_child(sub_objects_menu);
|
||||
sub_objects_menu->connect("about_to_popup", callable_mp(this, &EditorObjectSelector::_about_to_show));
|
||||
sub_objects_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorObjectSelector::_id_pressed));
|
||||
|
||||
set_tooltip_text(TTR("Open a list of sub-resources."));
|
||||
}
|
||||
72
engine/editor/gui/editor_object_selector.h
Normal file
72
engine/editor/gui/editor_object_selector.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/**************************************************************************/
|
||||
/* editor_object_selector.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 EDITOR_OBJECT_SELECTOR_H
|
||||
#define EDITOR_OBJECT_SELECTOR_H
|
||||
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/popup_menu.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
|
||||
class EditorSelectionHistory;
|
||||
|
||||
class EditorObjectSelector : public Button {
|
||||
GDCLASS(EditorObjectSelector, Button);
|
||||
|
||||
EditorSelectionHistory *history = nullptr;
|
||||
|
||||
TextureRect *current_object_icon = nullptr;
|
||||
Label *current_object_label = nullptr;
|
||||
TextureRect *sub_objects_icon = nullptr;
|
||||
PopupMenu *sub_objects_menu = nullptr;
|
||||
|
||||
Vector<ObjectID> objects;
|
||||
|
||||
void _show_popup();
|
||||
void _id_pressed(int p_idx);
|
||||
void _about_to_show();
|
||||
void _add_children_to_popup(Object *p_obj, int p_depth = 0);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
virtual Size2 get_minimum_size() const override;
|
||||
|
||||
void update_path();
|
||||
void clear_path();
|
||||
void enable_path();
|
||||
|
||||
EditorObjectSelector(EditorSelectionHistory *p_history);
|
||||
};
|
||||
|
||||
#endif // EDITOR_OBJECT_SELECTOR_H
|
||||
1092
engine/editor/gui/editor_quick_open_dialog.cpp
Normal file
1092
engine/editor/gui/editor_quick_open_dialog.cpp
Normal file
File diff suppressed because it is too large
Load diff
272
engine/editor/gui/editor_quick_open_dialog.h
Normal file
272
engine/editor/gui/editor_quick_open_dialog.h
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
/**************************************************************************/
|
||||
/* editor_quick_open_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 EDITOR_QUICK_OPEN_DIALOG_H
|
||||
#define EDITOR_QUICK_OPEN_DIALOG_H
|
||||
|
||||
#include "core/templates/oa_hash_map.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
|
||||
class Button;
|
||||
class CenterContainer;
|
||||
class CheckButton;
|
||||
class ConfigFile;
|
||||
class EditorFileSystemDirectory;
|
||||
class LineEdit;
|
||||
class HFlowContainer;
|
||||
class MarginContainer;
|
||||
class PanelContainer;
|
||||
class PopupMenu;
|
||||
class ScrollContainer;
|
||||
class StringName;
|
||||
class Texture2D;
|
||||
class TextureRect;
|
||||
class VBoxContainer;
|
||||
|
||||
class FuzzySearchResult;
|
||||
|
||||
class QuickOpenResultItem;
|
||||
|
||||
enum class QuickOpenDisplayMode {
|
||||
GRID,
|
||||
LIST,
|
||||
};
|
||||
|
||||
struct QuickOpenResultCandidate {
|
||||
String file_path;
|
||||
Ref<Texture2D> thumbnail;
|
||||
const FuzzySearchResult *result = nullptr;
|
||||
};
|
||||
|
||||
class HighlightedLabel : public Label {
|
||||
GDCLASS(HighlightedLabel, Label)
|
||||
|
||||
Vector<Vector2i> highlights;
|
||||
|
||||
void draw_substr_rects(const Vector2i &p_substr, Vector2 p_offset, int p_line_limit, int line_spacing);
|
||||
|
||||
public:
|
||||
void add_highlight(const Vector2i &p_interval);
|
||||
void reset_highlights();
|
||||
|
||||
protected:
|
||||
void _notification(int p_notification);
|
||||
};
|
||||
|
||||
class QuickOpenResultContainer : public VBoxContainer {
|
||||
GDCLASS(QuickOpenResultContainer, VBoxContainer)
|
||||
|
||||
enum {
|
||||
FILE_SHOW_IN_FILESYSTEM,
|
||||
FILE_SHOW_IN_FILE_MANAGER
|
||||
};
|
||||
|
||||
public:
|
||||
void init(const Vector<StringName> &p_base_types);
|
||||
void handle_search_box_input(const Ref<InputEvent> &p_ie);
|
||||
void set_query_and_update(const String &p_query);
|
||||
void update_results();
|
||||
|
||||
bool has_nothing_selected() const;
|
||||
String get_selected() const;
|
||||
|
||||
void save_selected_item();
|
||||
void cleanup();
|
||||
|
||||
QuickOpenResultContainer();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
private:
|
||||
static constexpr int SHOW_ALL_FILES_THRESHOLD = 30;
|
||||
static constexpr int MAX_HISTORY_SIZE = 20;
|
||||
|
||||
Vector<FuzzySearchResult> search_results;
|
||||
Vector<StringName> base_types;
|
||||
Vector<String> filepaths;
|
||||
OAHashMap<String, StringName> filetypes;
|
||||
Vector<QuickOpenResultCandidate> candidates;
|
||||
|
||||
OAHashMap<StringName, Vector<QuickOpenResultCandidate>> selected_history;
|
||||
|
||||
String query;
|
||||
int selection_index = -1;
|
||||
int num_visible_results = 0;
|
||||
int max_total_results = 0;
|
||||
|
||||
bool showing_history = false;
|
||||
bool never_opened = true;
|
||||
Ref<ConfigFile> history_file;
|
||||
|
||||
QuickOpenDisplayMode content_display_mode = QuickOpenDisplayMode::LIST;
|
||||
Vector<QuickOpenResultItem *> result_items;
|
||||
|
||||
ScrollContainer *scroll_container = nullptr;
|
||||
VBoxContainer *list = nullptr;
|
||||
HFlowContainer *grid = nullptr;
|
||||
PopupMenu *file_context_menu = nullptr;
|
||||
|
||||
PanelContainer *panel_container = nullptr;
|
||||
CenterContainer *no_results_container = nullptr;
|
||||
Label *no_results_label = nullptr;
|
||||
|
||||
Label *file_details_path = nullptr;
|
||||
Button *display_mode_toggle = nullptr;
|
||||
CheckButton *include_addons_toggle = nullptr;
|
||||
CheckButton *fuzzy_search_toggle = nullptr;
|
||||
|
||||
OAHashMap<StringName, Ref<Texture2D>> file_type_icons;
|
||||
|
||||
static QuickOpenDisplayMode get_adaptive_display_mode(const Vector<StringName> &p_base_types);
|
||||
|
||||
void _ensure_result_vector_capacity();
|
||||
void _create_initial_results();
|
||||
void _find_filepaths_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons);
|
||||
|
||||
void _setup_candidate(QuickOpenResultCandidate &p_candidate, const String &p_filepath);
|
||||
void _setup_candidate(QuickOpenResultCandidate &p_candidate, const FuzzySearchResult &p_result);
|
||||
void _update_fuzzy_search_results();
|
||||
void _use_default_candidates();
|
||||
void _score_and_sort_candidates();
|
||||
void _update_result_items(int p_new_visible_results_count, int p_new_selection_index);
|
||||
|
||||
void _move_selection_index(Key p_key);
|
||||
void _select_item(int p_index);
|
||||
|
||||
void _item_input(const Ref<InputEvent> &p_ev, int p_index);
|
||||
|
||||
CanvasItem *_get_result_root();
|
||||
void _layout_result_item(QuickOpenResultItem *p_item);
|
||||
void _set_display_mode(QuickOpenDisplayMode p_display_mode);
|
||||
void _toggle_display_mode();
|
||||
void _toggle_include_addons(bool p_pressed);
|
||||
void _toggle_fuzzy_search(bool p_pressed);
|
||||
void _menu_option(int p_option);
|
||||
|
||||
String _get_cache_file_path() const;
|
||||
|
||||
static void _bind_methods();
|
||||
};
|
||||
|
||||
class QuickOpenResultGridItem : public MarginContainer {
|
||||
GDCLASS(QuickOpenResultGridItem, MarginContainer)
|
||||
|
||||
public:
|
||||
QuickOpenResultGridItem();
|
||||
|
||||
void reset();
|
||||
void set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight);
|
||||
void highlight_item(const Color &p_color);
|
||||
void remove_highlight();
|
||||
|
||||
private:
|
||||
VBoxContainer *vbc = nullptr;
|
||||
TextureRect *thumbnail = nullptr;
|
||||
HighlightedLabel *name = nullptr;
|
||||
};
|
||||
|
||||
class QuickOpenResultListItem : public MarginContainer {
|
||||
GDCLASS(QuickOpenResultListItem, MarginContainer)
|
||||
|
||||
public:
|
||||
QuickOpenResultListItem();
|
||||
|
||||
void reset();
|
||||
void set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight);
|
||||
void highlight_item(const Color &p_color);
|
||||
void remove_highlight();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
private:
|
||||
HBoxContainer *hbc = nullptr;
|
||||
VBoxContainer *text_container = nullptr;
|
||||
|
||||
TextureRect *thumbnail = nullptr;
|
||||
HighlightedLabel *name = nullptr;
|
||||
HighlightedLabel *path = nullptr;
|
||||
};
|
||||
|
||||
class QuickOpenResultItem : public HBoxContainer {
|
||||
GDCLASS(QuickOpenResultItem, HBoxContainer)
|
||||
|
||||
public:
|
||||
QuickOpenResultItem();
|
||||
|
||||
bool enable_highlights = true;
|
||||
|
||||
void reset();
|
||||
void set_content(const QuickOpenResultCandidate &p_candidate);
|
||||
void set_display_mode(QuickOpenDisplayMode p_display_mode);
|
||||
void highlight_item(bool p_enabled);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
private:
|
||||
QuickOpenResultListItem *list_item = nullptr;
|
||||
QuickOpenResultGridItem *grid_item = nullptr;
|
||||
|
||||
Ref<StyleBox> selected_stylebox;
|
||||
Ref<StyleBox> hovering_stylebox;
|
||||
Color highlighted_font_color;
|
||||
|
||||
bool is_hovering = false;
|
||||
bool is_selected = false;
|
||||
|
||||
void _set_enabled(bool p_enabled);
|
||||
};
|
||||
|
||||
class EditorQuickOpenDialog : public AcceptDialog {
|
||||
GDCLASS(EditorQuickOpenDialog, AcceptDialog);
|
||||
|
||||
public:
|
||||
void popup_dialog(const Vector<StringName> &p_base_types, const Callable &p_item_selected_callback);
|
||||
EditorQuickOpenDialog();
|
||||
|
||||
protected:
|
||||
virtual void cancel_pressed() override;
|
||||
virtual void ok_pressed() override;
|
||||
|
||||
private:
|
||||
static String get_dialog_title(const Vector<StringName> &p_base_types);
|
||||
|
||||
LineEdit *search_box = nullptr;
|
||||
QuickOpenResultContainer *container = nullptr;
|
||||
|
||||
Callable item_selected_callback;
|
||||
|
||||
void _search_box_text_changed(const String &p_query);
|
||||
};
|
||||
|
||||
#endif // EDITOR_QUICK_OPEN_DIALOG_H
|
||||
654
engine/editor/gui/editor_run_bar.cpp
Normal file
654
engine/editor/gui/editor_run_bar.cpp
Normal file
|
|
@ -0,0 +1,654 @@
|
|||
/**************************************************************************/
|
||||
/* editor_run_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 "editor_run_bar.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "editor/debugger/editor_debugger_node.h"
|
||||
#include "editor/debugger/script_editor_debugger.h"
|
||||
#include "editor/editor_command_palette.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_run_native.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/gui/editor_bottom_panel.h"
|
||||
#include "editor/gui/editor_quick_open_dialog.h"
|
||||
#include "editor/gui/editor_toaster.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/menu_button.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
#include "servers/xr_server.h"
|
||||
#endif // _3D_DISABLED
|
||||
|
||||
EditorRunBar *EditorRunBar::singleton = nullptr;
|
||||
|
||||
void EditorRunBar::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_POSTINITIALIZE: {
|
||||
_reset_play_buttons();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_READY: {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
recovery_mode_show_dialog();
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
main_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadRecoveryMode"), EditorStringName(EditorStyles)));
|
||||
recovery_mode_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("RecoveryModeButton"), EditorStringName(EditorStyles)));
|
||||
recovery_mode_button->add_theme_style_override("hover", get_theme_stylebox(SNAME("RecoveryModeButton"), EditorStringName(EditorStyles)));
|
||||
|
||||
recovery_mode_button->set_button_icon(get_editor_theme_icon(SNAME("NodeWarning")));
|
||||
recovery_mode_reload_button->set_button_icon(get_editor_theme_icon(SNAME("Reload")));
|
||||
|
||||
recovery_mode_button->begin_bulk_theme_override();
|
||||
recovery_mode_button->add_theme_color_override("icon_normal_color", Color(0.3, 0.3, 0.3, 1));
|
||||
recovery_mode_button->add_theme_color_override("icon_pressed_color", Color(0.4, 0.4, 0.4, 1));
|
||||
recovery_mode_button->add_theme_color_override("icon_hover_color", Color(0.6, 0.6, 0.6, 1));
|
||||
Color dark_color = get_theme_color("recovery_mode_text_color", EditorStringName(Editor));
|
||||
recovery_mode_button->add_theme_color_override(SceneStringName(font_color), dark_color);
|
||||
recovery_mode_button->add_theme_color_override("font_pressed_color", dark_color.lightened(0.2));
|
||||
recovery_mode_button->add_theme_color_override("font_hover_color", dark_color.lightened(0.4));
|
||||
recovery_mode_button->add_theme_color_override("font_hover_pressed_color", dark_color.lightened(0.2));
|
||||
recovery_mode_button->end_bulk_theme_override();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_update_play_buttons();
|
||||
profiler_autostart_indicator->set_button_icon(get_editor_theme_icon(SNAME("ProfilerAutostartWarning")));
|
||||
pause_button->set_button_icon(get_editor_theme_icon(SNAME("Pause")));
|
||||
stop_button->set_button_icon(get_editor_theme_icon(SNAME("Stop")));
|
||||
|
||||
if (is_movie_maker_enabled()) {
|
||||
main_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadMovieMode"), EditorStringName(EditorStyles)));
|
||||
write_movie_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("MovieWriterButtonPressed"), EditorStringName(EditorStyles)));
|
||||
} else {
|
||||
main_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadNormal"), EditorStringName(EditorStyles)));
|
||||
write_movie_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("MovieWriterButtonNormal"), EditorStringName(EditorStyles)));
|
||||
}
|
||||
|
||||
write_movie_button->set_button_icon(get_editor_theme_icon(SNAME("MainMovieWrite")));
|
||||
// This button behaves differently, so color it as such.
|
||||
write_movie_button->begin_bulk_theme_override();
|
||||
write_movie_button->add_theme_color_override("icon_normal_color", get_theme_color(SNAME("movie_writer_icon_normal"), EditorStringName(EditorStyles)));
|
||||
write_movie_button->add_theme_color_override("icon_pressed_color", get_theme_color(SNAME("movie_writer_icon_pressed"), EditorStringName(EditorStyles)));
|
||||
write_movie_button->add_theme_color_override("icon_hover_color", get_theme_color(SNAME("movie_writer_icon_hover"), EditorStringName(EditorStyles)));
|
||||
write_movie_button->add_theme_color_override("icon_hover_pressed_color", get_theme_color(SNAME("movie_writer_icon_hover_pressed"), EditorStringName(EditorStyles)));
|
||||
write_movie_button->end_bulk_theme_override();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::_reset_play_buttons() {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
play_button->set_pressed(false);
|
||||
play_button->set_button_icon(get_editor_theme_icon(SNAME("MainPlay")));
|
||||
play_button->set_tooltip_text(TTR("Play the project."));
|
||||
|
||||
play_scene_button->set_pressed(false);
|
||||
play_scene_button->set_button_icon(get_editor_theme_icon(SNAME("PlayScene")));
|
||||
play_scene_button->set_tooltip_text(TTR("Play the edited scene."));
|
||||
|
||||
play_custom_scene_button->set_pressed(false);
|
||||
play_custom_scene_button->set_button_icon(get_editor_theme_icon(SNAME("PlayCustom")));
|
||||
play_custom_scene_button->set_tooltip_text(TTR("Play a custom scene."));
|
||||
}
|
||||
|
||||
void EditorRunBar::_update_play_buttons() {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_reset_play_buttons();
|
||||
if (!is_playing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Button *active_button = nullptr;
|
||||
if (current_mode == RUN_CURRENT) {
|
||||
active_button = play_scene_button;
|
||||
} else if (current_mode == RUN_CUSTOM) {
|
||||
active_button = play_custom_scene_button;
|
||||
} else {
|
||||
active_button = play_button;
|
||||
}
|
||||
|
||||
if (active_button) {
|
||||
active_button->set_pressed(true);
|
||||
active_button->set_button_icon(get_editor_theme_icon(SNAME("Reload")));
|
||||
active_button->set_tooltip_text(TTR("Reload the played scene."));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::_write_movie_toggled(bool p_enabled) {
|
||||
if (p_enabled) {
|
||||
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadMovieMode"), EditorStringName(EditorStyles)));
|
||||
write_movie_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("MovieWriterButtonPressed"), EditorStringName(EditorStyles)));
|
||||
} else {
|
||||
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadNormal"), EditorStringName(EditorStyles)));
|
||||
write_movie_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("MovieWriterButtonNormal"), EditorStringName(EditorStyles)));
|
||||
}
|
||||
}
|
||||
|
||||
Vector<String> EditorRunBar::_get_xr_mode_play_args(int p_xr_mode_id) {
|
||||
Vector<String> play_args;
|
||||
if (p_xr_mode_id == 0) {
|
||||
// Play in regular mode, xr mode off.
|
||||
play_args.push_back("--xr-mode");
|
||||
play_args.push_back("off");
|
||||
} else if (p_xr_mode_id == 1) {
|
||||
// Play in xr mode.
|
||||
play_args.push_back("--xr-mode");
|
||||
play_args.push_back("on");
|
||||
}
|
||||
return play_args;
|
||||
}
|
||||
|
||||
void EditorRunBar::_quick_run_selected(const String &p_file_path, int p_id) {
|
||||
play_custom_scene(p_file_path, _get_xr_mode_play_args(p_id));
|
||||
}
|
||||
|
||||
void EditorRunBar::_play_custom_pressed(int p_id) {
|
||||
if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CUSTOM) {
|
||||
stop_playing();
|
||||
|
||||
EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog({ "PackedScene" }, callable_mp(this, &EditorRunBar::_quick_run_selected).bind(p_id));
|
||||
play_custom_scene_button->set_pressed(false);
|
||||
} else {
|
||||
Vector<String> play_args = _get_xr_mode_play_args(p_id);
|
||||
|
||||
// Reload if already running a custom scene.
|
||||
String last_custom_scene = run_custom_filename; // This is necessary to have a copy of the string.
|
||||
play_custom_scene(last_custom_scene, play_args);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::_play_current_pressed(int p_id) {
|
||||
Vector<String> play_args = _get_xr_mode_play_args(p_id);
|
||||
|
||||
if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CURRENT) {
|
||||
play_current_scene(false, play_args);
|
||||
} else {
|
||||
// Reload if already running the current scene.
|
||||
play_current_scene(true, play_args);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::_run_scene(const String &p_scene_path, const Vector<String> &p_run_args) {
|
||||
ERR_FAIL_COND_MSG(current_mode == RUN_CUSTOM && p_scene_path.is_empty(), "Attempting to run a custom scene with an empty path.");
|
||||
|
||||
if (editor_run.get_status() == EditorRun::STATUS_PLAY) {
|
||||
return;
|
||||
}
|
||||
|
||||
_reset_play_buttons();
|
||||
|
||||
String write_movie_file;
|
||||
if (is_movie_maker_enabled()) {
|
||||
if (current_mode == RUN_CURRENT) {
|
||||
Node *scene_root = nullptr;
|
||||
if (p_scene_path.is_empty()) {
|
||||
scene_root = get_tree()->get_edited_scene_root();
|
||||
} else {
|
||||
int scene_index = EditorNode::get_editor_data().get_edited_scene_from_path(p_scene_path);
|
||||
if (scene_index >= 0) {
|
||||
scene_root = EditorNode::get_editor_data().get_edited_scene_root(scene_index);
|
||||
}
|
||||
}
|
||||
|
||||
if (scene_root && scene_root->has_meta("movie_file")) {
|
||||
// If the scene file has a movie_file metadata set, use this as file.
|
||||
// Quick workaround if you want to have multiple scenes that write to
|
||||
// multiple movies.
|
||||
write_movie_file = scene_root->get_meta("movie_file");
|
||||
}
|
||||
}
|
||||
|
||||
if (write_movie_file.is_empty()) {
|
||||
write_movie_file = GLOBAL_GET("editor/movie_writer/movie_file");
|
||||
}
|
||||
|
||||
if (write_movie_file.is_empty()) {
|
||||
// TODO: Provide options to directly resolve the issue with a custom dialog.
|
||||
EditorNode::get_singleton()->show_accept(TTR("Movie Maker mode is enabled, but no movie file path has been specified.\nA default movie file path can be specified in the project settings under the Editor > Movie Writer category.\nAlternatively, for running single scenes, a `movie_file` string metadata can be added to the root node,\nspecifying the path to a movie file that will be used when recording that scene."), TTR("OK"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String run_filename;
|
||||
switch (current_mode) {
|
||||
case RUN_CUSTOM: {
|
||||
run_filename = p_scene_path;
|
||||
run_custom_filename = run_filename;
|
||||
} break;
|
||||
|
||||
case RUN_CURRENT: {
|
||||
if (!p_scene_path.is_empty()) {
|
||||
run_filename = p_scene_path;
|
||||
run_current_filename = run_filename;
|
||||
break;
|
||||
}
|
||||
|
||||
Node *scene_root = get_tree()->get_edited_scene_root();
|
||||
if (!scene_root) {
|
||||
EditorNode::get_singleton()->show_accept(TTR("There is no defined scene to run."), TTR("OK"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (scene_root->get_scene_file_path().is_empty()) {
|
||||
EditorNode::get_singleton()->save_before_run();
|
||||
return;
|
||||
}
|
||||
|
||||
run_filename = scene_root->get_scene_file_path();
|
||||
run_current_filename = run_filename;
|
||||
} break;
|
||||
|
||||
default: {
|
||||
if (!EditorNode::get_singleton()->ensure_main_scene(false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
run_filename = GLOBAL_GET("application/run/main_scene");
|
||||
} break;
|
||||
}
|
||||
|
||||
EditorNode::get_singleton()->try_autosave();
|
||||
if (!EditorNode::get_singleton()->call_build()) {
|
||||
return;
|
||||
}
|
||||
|
||||
EditorDebuggerNode::get_singleton()->start();
|
||||
Error error = editor_run.run(run_filename, write_movie_file, p_run_args);
|
||||
if (error != OK) {
|
||||
EditorDebuggerNode::get_singleton()->stop();
|
||||
EditorNode::get_singleton()->show_accept(TTR("Could not start subprocess(es)!"), TTR("OK"));
|
||||
return;
|
||||
}
|
||||
|
||||
_update_play_buttons();
|
||||
stop_button->set_disabled(false);
|
||||
|
||||
emit_signal(SNAME("play_pressed"));
|
||||
}
|
||||
|
||||
void EditorRunBar::_run_native(const Ref<EditorExportPreset> &p_preset) {
|
||||
EditorNode::get_singleton()->try_autosave();
|
||||
|
||||
if (run_native->is_deploy_debug_remote_enabled()) {
|
||||
stop_playing();
|
||||
|
||||
if (!EditorNode::get_singleton()->call_build()) {
|
||||
return; // Build failed.
|
||||
}
|
||||
|
||||
EditorDebuggerNode::get_singleton()->start(p_preset->get_platform()->get_debug_protocol());
|
||||
emit_signal(SNAME("play_pressed"));
|
||||
editor_run.run_native_notify();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::_profiler_autostart_indicator_pressed() {
|
||||
// Switch to the first profiler tab in the bottom panel.
|
||||
EditorNode::get_singleton()->get_bottom_panel()->make_item_visible(EditorDebuggerNode::get_singleton(), true);
|
||||
|
||||
if (EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_profiler", false)) {
|
||||
EditorDebuggerNode::get_singleton()->get_current_debugger()->switch_to_debugger(3);
|
||||
} else if (EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_visual_profiler", false)) {
|
||||
EditorDebuggerNode::get_singleton()->get_current_debugger()->switch_to_debugger(4);
|
||||
} else {
|
||||
// Switch to the network profiler tab.
|
||||
EditorDebuggerNode::get_singleton()->get_current_debugger()->switch_to_debugger(8);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::recovery_mode_show_dialog() {
|
||||
recovery_mode_popup->popup_centered();
|
||||
}
|
||||
|
||||
void EditorRunBar::recovery_mode_reload_project() {
|
||||
EditorNode::get_singleton()->trigger_menu_option(EditorNode::PROJECT_RELOAD_CURRENT_PROJECT, false);
|
||||
}
|
||||
|
||||
void EditorRunBar::play_main_scene(bool p_from_native) {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_from_native) {
|
||||
run_native->resume_run_native();
|
||||
} else {
|
||||
stop_playing();
|
||||
|
||||
current_mode = RunMode::RUN_MAIN;
|
||||
_run_scene();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::play_current_scene(bool p_reload, const Vector<String> &p_play_args) {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
String last_current_scene = run_current_filename; // This is necessary to have a copy of the string.
|
||||
|
||||
EditorNode::get_singleton()->save_default_environment();
|
||||
stop_playing();
|
||||
|
||||
current_mode = RunMode::RUN_CURRENT;
|
||||
if (p_reload) {
|
||||
_run_scene(last_current_scene, p_play_args);
|
||||
} else {
|
||||
_run_scene("", p_play_args);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::play_custom_scene(const String &p_custom, const Vector<String> &p_play_args) {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING);
|
||||
return;
|
||||
}
|
||||
|
||||
stop_playing();
|
||||
|
||||
current_mode = RunMode::RUN_CUSTOM;
|
||||
_run_scene(p_custom, p_play_args);
|
||||
}
|
||||
|
||||
void EditorRunBar::stop_playing() {
|
||||
if (editor_run.get_status() == EditorRun::STATUS_STOP) {
|
||||
return;
|
||||
}
|
||||
|
||||
current_mode = RunMode::STOPPED;
|
||||
editor_run.stop();
|
||||
EditorDebuggerNode::get_singleton()->stop();
|
||||
|
||||
run_custom_filename.clear();
|
||||
run_current_filename.clear();
|
||||
stop_button->set_pressed(false);
|
||||
stop_button->set_disabled(true);
|
||||
_reset_play_buttons();
|
||||
|
||||
emit_signal(SNAME("stop_pressed"));
|
||||
}
|
||||
|
||||
bool EditorRunBar::is_playing() const {
|
||||
EditorRun::Status status = editor_run.get_status();
|
||||
return (status == EditorRun::STATUS_PLAY || status == EditorRun::STATUS_PAUSED);
|
||||
}
|
||||
|
||||
String EditorRunBar::get_playing_scene() const {
|
||||
String run_filename = editor_run.get_running_scene();
|
||||
if (run_filename.is_empty() && is_playing()) {
|
||||
run_filename = GLOBAL_GET("application/run/main_scene"); // Must be the main scene then.
|
||||
}
|
||||
|
||||
return run_filename;
|
||||
}
|
||||
|
||||
Error EditorRunBar::start_native_device(int p_device_id) {
|
||||
return run_native->start_run_native(p_device_id);
|
||||
}
|
||||
|
||||
OS::ProcessID EditorRunBar::has_child_process(OS::ProcessID p_pid) const {
|
||||
return editor_run.has_child_process(p_pid);
|
||||
}
|
||||
|
||||
void EditorRunBar::stop_child_process(OS::ProcessID p_pid) {
|
||||
if (!has_child_process(p_pid)) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor_run.stop_child_process(p_pid);
|
||||
if (!editor_run.get_child_process_count()) { // All children stopped. Closing.
|
||||
stop_playing();
|
||||
}
|
||||
}
|
||||
|
||||
OS::ProcessID EditorRunBar::get_current_process() const {
|
||||
return editor_run.get_current_process();
|
||||
}
|
||||
|
||||
void EditorRunBar::set_movie_maker_enabled(bool p_enabled) {
|
||||
write_movie_button->set_pressed(p_enabled);
|
||||
}
|
||||
|
||||
bool EditorRunBar::is_movie_maker_enabled() const {
|
||||
return write_movie_button->is_pressed();
|
||||
}
|
||||
|
||||
void EditorRunBar::update_profiler_autostart_indicator() {
|
||||
bool profiler_active = EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_profiler", false);
|
||||
bool visual_profiler_active = EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_visual_profiler", false);
|
||||
bool network_profiler_active = EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_network_profiler", false);
|
||||
bool any_profiler_active = profiler_active | visual_profiler_active | network_profiler_active;
|
||||
any_profiler_active &= !Engine::get_singleton()->is_recovery_mode_hint();
|
||||
profiler_autostart_indicator->set_visible(any_profiler_active);
|
||||
if (any_profiler_active) {
|
||||
String tooltip = TTR("Autostart is enabled for the following profilers, which can have a performance impact:");
|
||||
if (profiler_active) {
|
||||
tooltip += "\n- " + TTR("Profiler");
|
||||
}
|
||||
if (visual_profiler_active) {
|
||||
tooltip += "\n- " + TTR("Visual Profiler");
|
||||
}
|
||||
if (network_profiler_active) {
|
||||
tooltip += "\n- " + TTR("Network Profiler");
|
||||
}
|
||||
tooltip += "\n\n" + TTR("Click to open the first profiler for which autostart is enabled.");
|
||||
profiler_autostart_indicator->set_tooltip_text(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
HBoxContainer *EditorRunBar::get_buttons_container() {
|
||||
return main_hbox;
|
||||
}
|
||||
|
||||
void EditorRunBar::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("play_pressed"));
|
||||
ADD_SIGNAL(MethodInfo("stop_pressed"));
|
||||
}
|
||||
|
||||
EditorRunBar::EditorRunBar() {
|
||||
singleton = this;
|
||||
|
||||
outer_hbox = memnew(HBoxContainer);
|
||||
add_child(outer_hbox);
|
||||
|
||||
// Use a button for the indicator since it comes with a background panel and pixel perfect centering of an icon.
|
||||
profiler_autostart_indicator = memnew(Button);
|
||||
profiler_autostart_indicator->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
||||
profiler_autostart_indicator->set_focus_mode(FOCUS_NONE);
|
||||
profiler_autostart_indicator->set_theme_type_variation("ProfilerAutostartIndicator");
|
||||
profiler_autostart_indicator->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_profiler_autostart_indicator_pressed));
|
||||
outer_hbox->add_child(profiler_autostart_indicator);
|
||||
update_profiler_autostart_indicator();
|
||||
|
||||
main_panel = memnew(PanelContainer);
|
||||
outer_hbox->add_child(main_panel);
|
||||
|
||||
main_hbox = memnew(HBoxContainer);
|
||||
main_panel->add_child(main_hbox);
|
||||
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
recovery_mode_popup = memnew(AcceptDialog);
|
||||
recovery_mode_popup->set_min_size(Size2(550, 70) * EDSCALE);
|
||||
recovery_mode_popup->set_title(TTR("Recovery Mode"));
|
||||
recovery_mode_popup->set_text(
|
||||
TTR("Godot opened the project in Recovery Mode, which is a special mode that can help recover projects that crash the engine upon initialization. The following features have been temporarily disabled:") +
|
||||
String::utf8("\n\n• ") + TTR("Tool scripts") +
|
||||
String::utf8("\n• ") + TTR("Editor plugins") +
|
||||
String::utf8("\n• ") + TTR("GDExtension addons") +
|
||||
String::utf8("\n• ") + TTR("Automatic scene restoring") +
|
||||
String::utf8("\n\n") + TTR("If the project cannot be opened outside of this mode, then it's very likely any of these components is preventing this project from launching. This mode is intended only for basic editing to troubleshoot such issues, and therefore it is not possible to run a project in this mode.") +
|
||||
String::utf8("\n\n") + TTR("To disable Recovery Mode, reload the project by pressing the Reload button next to the Recovery Mode banner, or by reopening the project normally."));
|
||||
recovery_mode_popup->set_autowrap(true);
|
||||
add_child(recovery_mode_popup);
|
||||
|
||||
recovery_mode_reload_button = memnew(Button);
|
||||
main_hbox->add_child(recovery_mode_reload_button);
|
||||
recovery_mode_reload_button->set_theme_type_variation("RunBarButton");
|
||||
recovery_mode_reload_button->set_focus_mode(Control::FOCUS_NONE);
|
||||
recovery_mode_reload_button->set_tooltip_text(TTR("Disable recovery mode and reload the project."));
|
||||
recovery_mode_reload_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::recovery_mode_reload_project));
|
||||
|
||||
recovery_mode_panel = memnew(PanelContainer);
|
||||
main_hbox->add_child(recovery_mode_panel);
|
||||
|
||||
recovery_mode_button = memnew(Button);
|
||||
recovery_mode_panel->add_child(recovery_mode_button);
|
||||
recovery_mode_button->set_theme_type_variation("RunBarButton");
|
||||
recovery_mode_button->set_focus_mode(Control::FOCUS_NONE);
|
||||
recovery_mode_button->set_text(TTR("Recovery Mode"));
|
||||
recovery_mode_button->set_tooltip_text(TTR("Recovery Mode is enabled. Click for more details."));
|
||||
recovery_mode_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::recovery_mode_show_dialog));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
play_button = memnew(Button);
|
||||
main_hbox->add_child(play_button);
|
||||
play_button->set_theme_type_variation("RunBarButton");
|
||||
play_button->set_toggle_mode(true);
|
||||
play_button->set_focus_mode(Control::FOCUS_NONE);
|
||||
play_button->set_tooltip_text(TTRC("Run the project's default scene."));
|
||||
play_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::play_main_scene).bind(false));
|
||||
|
||||
ED_SHORTCUT_AND_COMMAND("editor/run_project", TTRC("Run Project"), Key::F5);
|
||||
ED_SHORTCUT_OVERRIDE("editor/run_project", "macos", KeyModifierMask::META | Key::B);
|
||||
play_button->set_shortcut(ED_GET_SHORTCUT("editor/run_project"));
|
||||
|
||||
pause_button = memnew(Button);
|
||||
main_hbox->add_child(pause_button);
|
||||
pause_button->set_theme_type_variation("RunBarButton");
|
||||
pause_button->set_toggle_mode(true);
|
||||
pause_button->set_focus_mode(Control::FOCUS_NONE);
|
||||
pause_button->set_tooltip_text(TTRC("Pause the running project's execution for debugging."));
|
||||
pause_button->set_disabled(true);
|
||||
|
||||
ED_SHORTCUT("editor/pause_running_project", TTRC("Pause Running Project"), Key::F7);
|
||||
ED_SHORTCUT_OVERRIDE("editor/pause_running_project", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::Y);
|
||||
pause_button->set_shortcut(ED_GET_SHORTCUT("editor/pause_running_project"));
|
||||
|
||||
stop_button = memnew(Button);
|
||||
main_hbox->add_child(stop_button);
|
||||
stop_button->set_theme_type_variation("RunBarButton");
|
||||
stop_button->set_focus_mode(Control::FOCUS_NONE);
|
||||
stop_button->set_tooltip_text(TTRC("Stop the currently running project."));
|
||||
stop_button->set_disabled(true);
|
||||
stop_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::stop_playing));
|
||||
|
||||
ED_SHORTCUT("editor/stop_running_project", TTRC("Stop Running Project"), Key::F8);
|
||||
ED_SHORTCUT_OVERRIDE("editor/stop_running_project", "macos", KeyModifierMask::META | Key::PERIOD);
|
||||
stop_button->set_shortcut(ED_GET_SHORTCUT("editor/stop_running_project"));
|
||||
|
||||
run_native = memnew(EditorRunNative);
|
||||
main_hbox->add_child(run_native);
|
||||
run_native->connect("native_run", callable_mp(this, &EditorRunBar::_run_native));
|
||||
|
||||
bool add_play_xr_mode_options = false;
|
||||
#ifndef _3D_DISABLED
|
||||
if (OS::get_singleton()->has_feature("xr_editor") &&
|
||||
(XRServer::get_xr_mode() == XRServer::XRMODE_ON ||
|
||||
(XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT && GLOBAL_GET("xr/openxr/enabled")))) {
|
||||
// If this is the XR editor and openxr is enabled, we turn the `play_scene_button` and
|
||||
// `play_custom_scene_button` into MenuButtons to provide the option to start a scene in
|
||||
// either regular mode or XR mode.
|
||||
add_play_xr_mode_options = true;
|
||||
}
|
||||
#endif // _3D_DISABLED
|
||||
|
||||
if (add_play_xr_mode_options) {
|
||||
MenuButton *menu_button = memnew(MenuButton);
|
||||
PopupMenu *popup = menu_button->get_popup();
|
||||
popup->add_item(TTRC("Run Scene in Regular Mode"), 0);
|
||||
popup->add_item(TTRC("Run Scene in XR Mode"), 1);
|
||||
popup->connect(SceneStringName(id_pressed), callable_mp(this, &EditorRunBar::_play_current_pressed));
|
||||
play_scene_button = menu_button;
|
||||
} else {
|
||||
play_scene_button = memnew(Button);
|
||||
play_scene_button->set_toggle_mode(true);
|
||||
play_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_current_pressed).bind(-1));
|
||||
}
|
||||
main_hbox->add_child(play_scene_button);
|
||||
play_scene_button->set_theme_type_variation("RunBarButton");
|
||||
play_scene_button->set_focus_mode(Control::FOCUS_NONE);
|
||||
play_scene_button->set_tooltip_text(TTRC("Run the currently edited scene."));
|
||||
|
||||
ED_SHORTCUT_AND_COMMAND("editor/run_current_scene", TTRC("Run Current Scene"), Key::F6);
|
||||
ED_SHORTCUT_OVERRIDE("editor/run_current_scene", "macos", KeyModifierMask::META | Key::R);
|
||||
play_scene_button->set_shortcut(ED_GET_SHORTCUT("editor/run_current_scene"));
|
||||
|
||||
if (add_play_xr_mode_options) {
|
||||
MenuButton *menu_button = memnew(MenuButton);
|
||||
PopupMenu *popup = menu_button->get_popup();
|
||||
popup->add_item(TTRC("Run in Regular Mode"), 0);
|
||||
popup->add_item(TTRC("Run in XR Mode"), 1);
|
||||
popup->connect(SceneStringName(id_pressed), callable_mp(this, &EditorRunBar::_play_custom_pressed));
|
||||
play_custom_scene_button = menu_button;
|
||||
} else {
|
||||
play_custom_scene_button = memnew(Button);
|
||||
play_custom_scene_button->set_toggle_mode(true);
|
||||
play_custom_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_custom_pressed).bind(-1));
|
||||
}
|
||||
main_hbox->add_child(play_custom_scene_button);
|
||||
play_custom_scene_button->set_theme_type_variation("RunBarButton");
|
||||
play_custom_scene_button->set_focus_mode(Control::FOCUS_NONE);
|
||||
play_custom_scene_button->set_tooltip_text(TTRC("Run a specific scene."));
|
||||
|
||||
ED_SHORTCUT_AND_COMMAND("editor/run_specific_scene", TTRC("Run Specific Scene"), KeyModifierMask::CTRL | KeyModifierMask::SHIFT | Key::F5);
|
||||
ED_SHORTCUT_OVERRIDE("editor/run_specific_scene", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::R);
|
||||
play_custom_scene_button->set_shortcut(ED_GET_SHORTCUT("editor/run_specific_scene"));
|
||||
|
||||
write_movie_panel = memnew(PanelContainer);
|
||||
main_hbox->add_child(write_movie_panel);
|
||||
|
||||
write_movie_button = memnew(Button);
|
||||
write_movie_panel->add_child(write_movie_button);
|
||||
write_movie_button->set_theme_type_variation("RunBarButton");
|
||||
write_movie_button->set_toggle_mode(true);
|
||||
write_movie_button->set_pressed(false);
|
||||
write_movie_button->set_focus_mode(Control::FOCUS_NONE);
|
||||
write_movie_button->set_tooltip_text(TTR("Enable Movie Maker mode.\nThe project will run at stable FPS and the visual and audio output will be recorded to a video file."));
|
||||
write_movie_button->connect(SceneStringName(toggled), callable_mp(this, &EditorRunBar::_write_movie_toggled));
|
||||
}
|
||||
136
engine/editor/gui/editor_run_bar.h
Normal file
136
engine/editor/gui/editor_run_bar.h
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/**************************************************************************/
|
||||
/* editor_run_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 EDITOR_RUN_BAR_H
|
||||
#define EDITOR_RUN_BAR_H
|
||||
|
||||
#include "editor/editor_run.h"
|
||||
#include "editor/export/editor_export.h"
|
||||
#include "scene/gui/margin_container.h"
|
||||
|
||||
class Button;
|
||||
class EditorRunNative;
|
||||
class PanelContainer;
|
||||
class HBoxContainer;
|
||||
class AcceptDialog;
|
||||
|
||||
class EditorRunBar : public MarginContainer {
|
||||
GDCLASS(EditorRunBar, MarginContainer);
|
||||
|
||||
static EditorRunBar *singleton;
|
||||
|
||||
enum RunMode {
|
||||
STOPPED = 0,
|
||||
RUN_MAIN,
|
||||
RUN_CURRENT,
|
||||
RUN_CUSTOM,
|
||||
};
|
||||
|
||||
PanelContainer *main_panel = nullptr;
|
||||
HBoxContainer *main_hbox = nullptr;
|
||||
HBoxContainer *outer_hbox = nullptr;
|
||||
|
||||
Button *profiler_autostart_indicator = nullptr;
|
||||
|
||||
PanelContainer *recovery_mode_panel = nullptr;
|
||||
Button *recovery_mode_button = nullptr;
|
||||
Button *recovery_mode_reload_button = nullptr;
|
||||
AcceptDialog *recovery_mode_popup = nullptr;
|
||||
|
||||
Button *play_button = nullptr;
|
||||
Button *pause_button = nullptr;
|
||||
Button *stop_button = nullptr;
|
||||
Button *play_scene_button = nullptr;
|
||||
Button *play_custom_scene_button = nullptr;
|
||||
|
||||
EditorRun editor_run;
|
||||
EditorRunNative *run_native = nullptr;
|
||||
|
||||
PanelContainer *write_movie_panel = nullptr;
|
||||
Button *write_movie_button = nullptr;
|
||||
|
||||
RunMode current_mode = RunMode::STOPPED;
|
||||
String run_custom_filename;
|
||||
String run_current_filename;
|
||||
|
||||
void _reset_play_buttons();
|
||||
void _update_play_buttons();
|
||||
|
||||
void _write_movie_toggled(bool p_enabled);
|
||||
void _quick_run_selected(const String &p_file_path, int p_id = -1);
|
||||
|
||||
void _play_current_pressed(int p_id = -1);
|
||||
void _play_custom_pressed(int p_id = -1);
|
||||
|
||||
void _run_scene(const String &p_scene_path = "", const Vector<String> &p_run_args = Vector<String>());
|
||||
void _run_native(const Ref<EditorExportPreset> &p_preset);
|
||||
|
||||
void _profiler_autostart_indicator_pressed();
|
||||
|
||||
private:
|
||||
static Vector<String> _get_xr_mode_play_args(int p_xr_mode_id);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static EditorRunBar *get_singleton() { return singleton; }
|
||||
|
||||
void recovery_mode_show_dialog();
|
||||
void recovery_mode_reload_project();
|
||||
|
||||
void play_main_scene(bool p_from_native = false);
|
||||
void play_current_scene(bool p_reload = false, const Vector<String> &p_play_args = Vector<String>());
|
||||
void play_custom_scene(const String &p_custom, const Vector<String> &p_play_args = Vector<String>());
|
||||
|
||||
void stop_playing();
|
||||
bool is_playing() const;
|
||||
String get_playing_scene() const;
|
||||
|
||||
Error start_native_device(int p_device_id);
|
||||
|
||||
OS::ProcessID has_child_process(OS::ProcessID p_pid) const;
|
||||
void stop_child_process(OS::ProcessID p_pid);
|
||||
OS::ProcessID get_current_process() const;
|
||||
|
||||
void set_movie_maker_enabled(bool p_enabled);
|
||||
bool is_movie_maker_enabled() const;
|
||||
|
||||
void update_profiler_autostart_indicator();
|
||||
|
||||
Button *get_pause_button() { return pause_button; }
|
||||
|
||||
HBoxContainer *get_buttons_container();
|
||||
|
||||
EditorRunBar();
|
||||
};
|
||||
|
||||
#endif // EDITOR_RUN_BAR_H
|
||||
465
engine/editor/gui/editor_scene_tabs.cpp
Normal file
465
engine/editor/gui/editor_scene_tabs.cpp
Normal file
|
|
@ -0,0 +1,465 @@
|
|||
/**************************************************************************/
|
||||
/* editor_scene_tabs.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 "editor_scene_tabs.h"
|
||||
|
||||
#include "editor/editor_main_screen.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_resource_preview.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/editor_undo_redo_manager.h"
|
||||
#include "editor/gui/editor_run_bar.h"
|
||||
#include "editor/inspector_dock.h"
|
||||
#include "editor/plugins/editor_context_menu_plugin.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/panel.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/gui/popup_menu.h"
|
||||
#include "scene/gui/tab_bar.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
|
||||
void EditorSceneTabs::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
tabbar_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("tabbar_background"), SNAME("TabContainer")));
|
||||
scene_tabs->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)));
|
||||
|
||||
scene_tab_add->set_button_icon(get_editor_theme_icon(SNAME("Add")));
|
||||
scene_tab_add->add_theme_color_override("icon_normal_color", Color(0.6f, 0.6f, 0.6f, 0.8f));
|
||||
|
||||
scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size());
|
||||
} break;
|
||||
|
||||
case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
|
||||
if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/scene_tabs")) {
|
||||
scene_tabs->set_tab_close_display_policy((TabBar::CloseButtonDisplayPolicy)EDITOR_GET("interface/scene_tabs/display_close_button").operator int());
|
||||
scene_tabs->set_max_tab_width(int(EDITOR_GET("interface/scene_tabs/maximum_width")) * EDSCALE);
|
||||
_scene_tabs_resized();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_scene_tab_changed(int p_tab) {
|
||||
tab_preview_panel->hide();
|
||||
|
||||
emit_signal("tab_changed", p_tab);
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_scene_tab_script_edited(int p_tab) {
|
||||
Ref<Script> scr = EditorNode::get_editor_data().get_scene_root_script(p_tab);
|
||||
if (scr.is_valid()) {
|
||||
InspectorDock::get_singleton()->edit_resource(scr);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_scene_tab_closed(int p_tab) {
|
||||
emit_signal("tab_closed", p_tab);
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_scene_tab_hovered(int p_tab) {
|
||||
if (!bool(EDITOR_GET("interface/scene_tabs/show_thumbnail_on_hover"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Currently the tab previews are displayed under the running game process when embed.
|
||||
// Right now, the easiest technique to fix that is to prevent displaying the tab preview
|
||||
// when the user is in the Game View.
|
||||
if (EditorNode::get_singleton()->get_editor_main_screen()->get_selected_index() == EditorMainScreen::EDITOR_GAME && EditorRunBar::get_singleton()->is_playing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int current_tab = scene_tabs->get_current_tab();
|
||||
|
||||
if (p_tab == current_tab || p_tab < 0) {
|
||||
tab_preview_panel->hide();
|
||||
} else {
|
||||
String path = EditorNode::get_editor_data().get_scene_path(p_tab);
|
||||
if (!path.is_empty()) {
|
||||
EditorResourcePreview::get_singleton()->queue_resource_preview(path, this, "_tab_preview_done", p_tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_scene_tab_exit() {
|
||||
tab_preview_panel->hide();
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_scene_tab_input(const Ref<InputEvent> &p_input) {
|
||||
int tab_id = scene_tabs->get_hovered_tab();
|
||||
Ref<InputEventMouseButton> mb = p_input;
|
||||
|
||||
if (mb.is_valid()) {
|
||||
if (tab_id >= 0) {
|
||||
if (mb->get_button_index() == MouseButton::MIDDLE && mb->is_pressed()) {
|
||||
_scene_tab_closed(tab_id);
|
||||
}
|
||||
} else if (mb->get_button_index() == MouseButton::LEFT && mb->is_double_click()) {
|
||||
int tab_buttons = 0;
|
||||
if (scene_tabs->get_offset_buttons_visible()) {
|
||||
tab_buttons = get_theme_icon(SNAME("increment"), SNAME("TabBar"))->get_width() + get_theme_icon(SNAME("decrement"), SNAME("TabBar"))->get_width();
|
||||
}
|
||||
|
||||
if ((is_layout_rtl() && mb->get_position().x > tab_buttons) || (!is_layout_rtl() && mb->get_position().x < scene_tabs->get_size().width - tab_buttons)) {
|
||||
EditorNode::get_singleton()->trigger_menu_option(EditorNode::FILE_NEW_SCENE, true);
|
||||
}
|
||||
}
|
||||
if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) {
|
||||
// Context menu.
|
||||
_update_context_menu();
|
||||
|
||||
scene_tabs_context_menu->set_position(scene_tabs->get_screen_position() + mb->get_position());
|
||||
scene_tabs_context_menu->reset_size();
|
||||
scene_tabs_context_menu->popup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneTabs::unhandled_key_input(const Ref<InputEvent> &p_event) {
|
||||
if (!tab_preview_panel->is_visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<InputEventKey> k = p_event;
|
||||
if (k.is_valid() && k->is_action_pressed(SNAME("ui_cancel"), false, true)) {
|
||||
tab_preview_panel->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_reposition_active_tab(int p_to_index) {
|
||||
EditorNode::get_editor_data().move_edited_scene_to_index(p_to_index);
|
||||
update_scene_tabs();
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_update_context_menu() {
|
||||
#define DISABLE_LAST_OPTION_IF(m_condition) \
|
||||
if (m_condition) { \
|
||||
scene_tabs_context_menu->set_item_disabled(-1, true); \
|
||||
}
|
||||
|
||||
scene_tabs_context_menu->clear();
|
||||
scene_tabs_context_menu->reset_size();
|
||||
|
||||
int tab_id = scene_tabs->get_hovered_tab();
|
||||
bool no_root_node = !EditorNode::get_editor_data().get_edited_scene_root(tab_id);
|
||||
|
||||
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/new_scene"), EditorNode::FILE_NEW_SCENE);
|
||||
if (tab_id >= 0) {
|
||||
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene"), EditorNode::FILE_SAVE_SCENE);
|
||||
DISABLE_LAST_OPTION_IF(no_root_node);
|
||||
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene_as"), EditorNode::FILE_SAVE_AS_SCENE);
|
||||
DISABLE_LAST_OPTION_IF(no_root_node);
|
||||
}
|
||||
|
||||
bool can_save_all_scenes = false;
|
||||
for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) {
|
||||
if (!EditorNode::get_editor_data().get_scene_path(i).is_empty() && EditorNode::get_editor_data().get_edited_scene_root(i)) {
|
||||
can_save_all_scenes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_all_scenes"), EditorNode::FILE_SAVE_ALL_SCENES);
|
||||
DISABLE_LAST_OPTION_IF(!can_save_all_scenes);
|
||||
|
||||
if (tab_id >= 0) {
|
||||
scene_tabs_context_menu->add_separator();
|
||||
scene_tabs_context_menu->add_item(TTR("Show in FileSystem"), SCENE_SHOW_IN_FILESYSTEM);
|
||||
DISABLE_LAST_OPTION_IF(!ResourceLoader::exists(EditorNode::get_editor_data().get_scene_path(tab_id)));
|
||||
scene_tabs_context_menu->add_item(TTR("Play This Scene"), SCENE_RUN);
|
||||
DISABLE_LAST_OPTION_IF(no_root_node);
|
||||
|
||||
scene_tabs_context_menu->add_separator();
|
||||
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/close_scene"), EditorNode::FILE_CLOSE);
|
||||
scene_tabs_context_menu->set_item_text(-1, TTR("Close Tab"));
|
||||
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/reopen_closed_scene"), EditorNode::FILE_OPEN_PREV);
|
||||
scene_tabs_context_menu->set_item_text(-1, TTR("Undo Close Tab"));
|
||||
DISABLE_LAST_OPTION_IF(!EditorNode::get_singleton()->has_previous_scenes());
|
||||
scene_tabs_context_menu->add_item(TTR("Close Other Tabs"), SCENE_CLOSE_OTHERS);
|
||||
DISABLE_LAST_OPTION_IF(EditorNode::get_editor_data().get_edited_scene_count() <= 1);
|
||||
scene_tabs_context_menu->add_item(TTR("Close Tabs to the Right"), SCENE_CLOSE_RIGHT);
|
||||
DISABLE_LAST_OPTION_IF(EditorNode::get_editor_data().get_edited_scene_count() == tab_id + 1);
|
||||
scene_tabs_context_menu->add_item(TTR("Close All Tabs"), SCENE_CLOSE_ALL);
|
||||
|
||||
const PackedStringArray paths = { EditorNode::get_editor_data().get_scene_path(tab_id) };
|
||||
EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(scene_tabs_context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, paths);
|
||||
} else {
|
||||
EditorContextMenuPluginManager::get_singleton()->add_options_from_plugins(scene_tabs_context_menu, EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, {});
|
||||
}
|
||||
#undef DISABLE_LAST_OPTION_IF
|
||||
|
||||
last_hovered_tab = tab_id;
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_custom_menu_option(int p_option) {
|
||||
if (p_option >= EditorContextMenuPlugin::BASE_ID) {
|
||||
EditorContextMenuPluginManager::get_singleton()->activate_custom_option(EditorContextMenuPlugin::CONTEXT_SLOT_SCENE_TABS, p_option, last_hovered_tab >= 0 ? EditorNode::get_editor_data().get_scene_path(last_hovered_tab) : String());
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneTabs::update_scene_tabs() {
|
||||
static bool menu_initialized = false;
|
||||
tab_preview_panel->hide();
|
||||
|
||||
if (menu_initialized && scene_tabs->get_tab_count() == EditorNode::get_editor_data().get_edited_scene_count()) {
|
||||
_update_tab_titles();
|
||||
return;
|
||||
}
|
||||
menu_initialized = true;
|
||||
|
||||
if (NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU)) {
|
||||
RID dock_rid = NativeMenu::get_singleton()->get_system_menu(NativeMenu::DOCK_MENU_ID);
|
||||
NativeMenu::get_singleton()->clear(dock_rid);
|
||||
}
|
||||
|
||||
scene_tabs->set_block_signals(true);
|
||||
scene_tabs->set_tab_count(EditorNode::get_editor_data().get_edited_scene_count());
|
||||
scene_tabs->set_block_signals(false);
|
||||
|
||||
if (NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU)) {
|
||||
RID dock_rid = NativeMenu::get_singleton()->get_system_menu(NativeMenu::DOCK_MENU_ID);
|
||||
for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) {
|
||||
int global_menu_index = NativeMenu::get_singleton()->add_item(dock_rid, EditorNode::get_editor_data().get_scene_title(i), callable_mp(this, &EditorSceneTabs::_global_menu_scene), Callable(), i);
|
||||
scene_tabs->set_tab_metadata(i, global_menu_index);
|
||||
}
|
||||
NativeMenu::get_singleton()->add_separator(dock_rid);
|
||||
NativeMenu::get_singleton()->add_item(dock_rid, TTR("New Window"), callable_mp(this, &EditorSceneTabs::_global_menu_new_window));
|
||||
}
|
||||
|
||||
_update_tab_titles();
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_update_tab_titles() {
|
||||
bool show_rb = EDITOR_GET("interface/scene_tabs/show_script_button");
|
||||
|
||||
// Get all scene names, which may be ambiguous.
|
||||
Vector<String> disambiguated_scene_names;
|
||||
Vector<String> full_path_names;
|
||||
for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) {
|
||||
disambiguated_scene_names.append(EditorNode::get_editor_data().get_scene_title(i));
|
||||
full_path_names.append(EditorNode::get_editor_data().get_scene_path(i));
|
||||
}
|
||||
EditorNode::disambiguate_filenames(full_path_names, disambiguated_scene_names);
|
||||
|
||||
Ref<Texture2D> script_icon = get_editor_theme_icon(SNAME("Script"));
|
||||
for (int i = 0; i < EditorNode::get_editor_data().get_edited_scene_count(); i++) {
|
||||
Node *type_node = EditorNode::get_editor_data().get_edited_scene_root(i);
|
||||
Ref<Texture2D> icon;
|
||||
if (type_node) {
|
||||
icon = EditorNode::get_singleton()->get_object_icon(type_node, "Node");
|
||||
}
|
||||
scene_tabs->set_tab_icon(i, icon);
|
||||
|
||||
bool unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(EditorNode::get_editor_data().get_scene_history_id(i));
|
||||
scene_tabs->set_tab_title(i, disambiguated_scene_names[i] + (unsaved ? "(*)" : ""));
|
||||
|
||||
if (NativeMenu::get_singleton()->has_feature(NativeMenu::FEATURE_GLOBAL_MENU)) {
|
||||
RID dock_rid = NativeMenu::get_singleton()->get_system_menu(NativeMenu::DOCK_MENU_ID);
|
||||
int global_menu_index = scene_tabs->get_tab_metadata(i);
|
||||
NativeMenu::get_singleton()->set_item_text(dock_rid, global_menu_index, EditorNode::get_editor_data().get_scene_title(i) + (unsaved ? "(*)" : ""));
|
||||
NativeMenu::get_singleton()->set_item_tag(dock_rid, global_menu_index, i);
|
||||
}
|
||||
|
||||
if (show_rb && EditorNode::get_editor_data().get_scene_root_script(i).is_valid()) {
|
||||
scene_tabs->set_tab_button_icon(i, script_icon);
|
||||
} else {
|
||||
scene_tabs->set_tab_button_icon(i, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
int current_tab = EditorNode::get_editor_data().get_edited_scene();
|
||||
if (scene_tabs->get_tab_count() > 0 && scene_tabs->get_current_tab() != current_tab) {
|
||||
scene_tabs->set_block_signals(true);
|
||||
scene_tabs->set_current_tab(current_tab);
|
||||
scene_tabs->set_block_signals(false);
|
||||
}
|
||||
|
||||
_scene_tabs_resized();
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_scene_tabs_resized() {
|
||||
const Size2 add_button_size = Size2(scene_tab_add->get_size().x, scene_tabs->get_size().y);
|
||||
if (scene_tabs->get_offset_buttons_visible()) {
|
||||
// Move the add button to a fixed position.
|
||||
if (scene_tab_add->get_parent() == scene_tabs) {
|
||||
scene_tabs->remove_child(scene_tab_add);
|
||||
scene_tab_add_ph->add_child(scene_tab_add);
|
||||
scene_tab_add->set_rect(Rect2(Point2(), add_button_size));
|
||||
}
|
||||
} else {
|
||||
// Move the add button to be after the last tab.
|
||||
if (scene_tab_add->get_parent() == scene_tab_add_ph) {
|
||||
scene_tab_add_ph->remove_child(scene_tab_add);
|
||||
scene_tabs->add_child(scene_tab_add);
|
||||
}
|
||||
|
||||
if (scene_tabs->get_tab_count() == 0) {
|
||||
scene_tab_add->set_rect(Rect2(Point2(), add_button_size));
|
||||
return;
|
||||
}
|
||||
|
||||
Rect2 last_tab = scene_tabs->get_tab_rect(scene_tabs->get_tab_count() - 1);
|
||||
int hsep = scene_tabs->get_theme_constant(SNAME("h_separation"));
|
||||
if (scene_tabs->is_layout_rtl()) {
|
||||
scene_tab_add->set_rect(Rect2(Point2(last_tab.position.x - add_button_size.x - hsep, last_tab.position.y), add_button_size));
|
||||
} else {
|
||||
scene_tab_add->set_rect(Rect2(Point2(last_tab.position.x + last_tab.size.width + hsep, last_tab.position.y), add_button_size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_tab_preview_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) {
|
||||
int p_tab = p_udata;
|
||||
if (p_preview.is_valid()) {
|
||||
tab_preview->set_texture(p_preview);
|
||||
|
||||
Rect2 rect = scene_tabs->get_tab_rect(p_tab);
|
||||
rect.position += scene_tabs->get_global_position();
|
||||
tab_preview_panel->set_global_position(rect.position + Vector2(0, rect.size.height));
|
||||
tab_preview_panel->show();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_global_menu_scene(const Variant &p_tag) {
|
||||
int idx = (int)p_tag;
|
||||
scene_tabs->set_current_tab(idx);
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_global_menu_new_window(const Variant &p_tag) {
|
||||
if (OS::get_singleton()->get_main_loop()) {
|
||||
List<String> args;
|
||||
args.push_back("-p");
|
||||
OS::get_singleton()->create_instance(args);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneTabs::shortcut_input(const Ref<InputEvent> &p_event) {
|
||||
ERR_FAIL_COND(p_event.is_null());
|
||||
|
||||
Ref<InputEventKey> k = p_event;
|
||||
if ((k.is_valid() && k->is_pressed() && !k->is_echo()) || Object::cast_to<InputEventShortcut>(*p_event)) {
|
||||
if (ED_IS_SHORTCUT("editor/next_tab", p_event)) {
|
||||
int next_tab = EditorNode::get_editor_data().get_edited_scene() + 1;
|
||||
next_tab %= EditorNode::get_editor_data().get_edited_scene_count();
|
||||
_scene_tab_changed(next_tab);
|
||||
}
|
||||
if (ED_IS_SHORTCUT("editor/prev_tab", p_event)) {
|
||||
int next_tab = EditorNode::get_editor_data().get_edited_scene() - 1;
|
||||
next_tab = next_tab >= 0 ? next_tab : EditorNode::get_editor_data().get_edited_scene_count() - 1;
|
||||
_scene_tab_changed(next_tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneTabs::add_extra_button(Button *p_button) {
|
||||
tabbar_container->add_child(p_button);
|
||||
}
|
||||
|
||||
void EditorSceneTabs::set_current_tab(int p_tab) {
|
||||
scene_tabs->set_current_tab(p_tab);
|
||||
}
|
||||
|
||||
int EditorSceneTabs::get_current_tab() const {
|
||||
return scene_tabs->get_current_tab();
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("tab_changed", PropertyInfo(Variant::INT, "tab_index")));
|
||||
ADD_SIGNAL(MethodInfo("tab_closed", PropertyInfo(Variant::INT, "tab_index")));
|
||||
|
||||
ClassDB::bind_method("_tab_preview_done", &EditorSceneTabs::_tab_preview_done);
|
||||
}
|
||||
|
||||
EditorSceneTabs::EditorSceneTabs() {
|
||||
singleton = this;
|
||||
|
||||
set_process_shortcut_input(true);
|
||||
set_process_unhandled_key_input(true);
|
||||
|
||||
tabbar_panel = memnew(PanelContainer);
|
||||
add_child(tabbar_panel);
|
||||
tabbar_container = memnew(HBoxContainer);
|
||||
tabbar_panel->add_child(tabbar_container);
|
||||
|
||||
scene_tabs = memnew(TabBar);
|
||||
scene_tabs->set_select_with_rmb(true);
|
||||
scene_tabs->add_tab("unsaved");
|
||||
scene_tabs->set_tab_close_display_policy((TabBar::CloseButtonDisplayPolicy)EDITOR_GET("interface/scene_tabs/display_close_button").operator int());
|
||||
scene_tabs->set_max_tab_width(int(EDITOR_GET("interface/scene_tabs/maximum_width")) * EDSCALE);
|
||||
scene_tabs->set_drag_to_rearrange_enabled(true);
|
||||
scene_tabs->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
scene_tabs->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
tabbar_container->add_child(scene_tabs);
|
||||
|
||||
scene_tabs->connect("tab_changed", callable_mp(this, &EditorSceneTabs::_scene_tab_changed));
|
||||
scene_tabs->connect("tab_button_pressed", callable_mp(this, &EditorSceneTabs::_scene_tab_script_edited));
|
||||
scene_tabs->connect("tab_close_pressed", callable_mp(this, &EditorSceneTabs::_scene_tab_closed));
|
||||
scene_tabs->connect("tab_hovered", callable_mp(this, &EditorSceneTabs::_scene_tab_hovered));
|
||||
scene_tabs->connect(SceneStringName(mouse_exited), callable_mp(this, &EditorSceneTabs::_scene_tab_exit));
|
||||
scene_tabs->connect(SceneStringName(gui_input), callable_mp(this, &EditorSceneTabs::_scene_tab_input));
|
||||
scene_tabs->connect("active_tab_rearranged", callable_mp(this, &EditorSceneTabs::_reposition_active_tab));
|
||||
scene_tabs->connect(SceneStringName(resized), callable_mp(this, &EditorSceneTabs::_scene_tabs_resized), CONNECT_DEFERRED);
|
||||
|
||||
scene_tabs_context_menu = memnew(PopupMenu);
|
||||
tabbar_container->add_child(scene_tabs_context_menu);
|
||||
scene_tabs_context_menu->connect(SceneStringName(id_pressed), callable_mp(EditorNode::get_singleton(), &EditorNode::trigger_menu_option).bind(false));
|
||||
scene_tabs_context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorSceneTabs::_custom_menu_option));
|
||||
|
||||
scene_tab_add = memnew(Button);
|
||||
scene_tab_add->set_flat(true);
|
||||
scene_tab_add->set_tooltip_text(TTR("Add a new scene."));
|
||||
scene_tabs->add_child(scene_tab_add);
|
||||
scene_tab_add->connect(SceneStringName(pressed), callable_mp(EditorNode::get_singleton(), &EditorNode::trigger_menu_option).bind(EditorNode::FILE_NEW_SCENE, false));
|
||||
|
||||
scene_tab_add_ph = memnew(Control);
|
||||
scene_tab_add_ph->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
|
||||
scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size());
|
||||
tabbar_container->add_child(scene_tab_add_ph);
|
||||
|
||||
// On-hover tab preview.
|
||||
|
||||
Control *tab_preview_anchor = memnew(Control);
|
||||
tab_preview_anchor->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
|
||||
add_child(tab_preview_anchor);
|
||||
|
||||
tab_preview_panel = memnew(Panel);
|
||||
tab_preview_panel->set_size(Size2(100, 100) * EDSCALE);
|
||||
tab_preview_panel->hide();
|
||||
tab_preview_panel->set_self_modulate(Color(1, 1, 1, 0.7));
|
||||
tab_preview_anchor->add_child(tab_preview_panel);
|
||||
|
||||
tab_preview = memnew(TextureRect);
|
||||
tab_preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
|
||||
tab_preview->set_size(Size2(96, 96) * EDSCALE);
|
||||
tab_preview->set_position(Point2(2, 2) * EDSCALE);
|
||||
tab_preview_panel->add_child(tab_preview);
|
||||
}
|
||||
110
engine/editor/gui/editor_scene_tabs.h
Normal file
110
engine/editor/gui/editor_scene_tabs.h
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/**************************************************************************/
|
||||
/* editor_scene_tabs.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 EDITOR_SCENE_TABS_H
|
||||
#define EDITOR_SCENE_TABS_H
|
||||
|
||||
#include "scene/gui/margin_container.h"
|
||||
|
||||
class Button;
|
||||
class HBoxContainer;
|
||||
class Panel;
|
||||
class PanelContainer;
|
||||
class PopupMenu;
|
||||
class TabBar;
|
||||
class TextureRect;
|
||||
|
||||
class EditorSceneTabs : public MarginContainer {
|
||||
GDCLASS(EditorSceneTabs, MarginContainer);
|
||||
|
||||
inline static EditorSceneTabs *singleton = nullptr;
|
||||
|
||||
public:
|
||||
enum {
|
||||
SCENE_SHOW_IN_FILESYSTEM = 3000, // Prevents conflicts with EditorNode options.
|
||||
SCENE_RUN,
|
||||
SCENE_CLOSE_OTHERS,
|
||||
SCENE_CLOSE_RIGHT,
|
||||
SCENE_CLOSE_ALL,
|
||||
};
|
||||
|
||||
private:
|
||||
PanelContainer *tabbar_panel = nullptr;
|
||||
HBoxContainer *tabbar_container = nullptr;
|
||||
|
||||
TabBar *scene_tabs = nullptr;
|
||||
PopupMenu *scene_tabs_context_menu = nullptr;
|
||||
Button *scene_tab_add = nullptr;
|
||||
Control *scene_tab_add_ph = nullptr;
|
||||
|
||||
Panel *tab_preview_panel = nullptr;
|
||||
TextureRect *tab_preview = nullptr;
|
||||
|
||||
int last_hovered_tab = -1;
|
||||
|
||||
void _scene_tab_changed(int p_tab);
|
||||
void _scene_tab_script_edited(int p_tab);
|
||||
void _scene_tab_closed(int p_tab);
|
||||
void _scene_tab_hovered(int p_tab);
|
||||
void _scene_tab_exit();
|
||||
void _scene_tab_input(const Ref<InputEvent> &p_input);
|
||||
void _scene_tabs_resized();
|
||||
|
||||
void _update_tab_titles();
|
||||
void _reposition_active_tab(int p_to_index);
|
||||
void _update_context_menu();
|
||||
void _custom_menu_option(int p_option);
|
||||
|
||||
void _tab_preview_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata);
|
||||
|
||||
void _global_menu_scene(const Variant &p_tag);
|
||||
void _global_menu_new_window(const Variant &p_tag);
|
||||
|
||||
virtual void shortcut_input(const Ref<InputEvent> &p_event) override;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override;
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static EditorSceneTabs *get_singleton() { return singleton; }
|
||||
|
||||
void add_extra_button(Button *p_button);
|
||||
|
||||
void set_current_tab(int p_tab);
|
||||
int get_current_tab() const;
|
||||
|
||||
void update_scene_tabs();
|
||||
|
||||
EditorSceneTabs();
|
||||
};
|
||||
|
||||
#endif // EDITOR_SCENE_TABS_H
|
||||
759
engine/editor/gui/editor_spin_slider.cpp
Normal file
759
engine/editor/gui/editor_spin_slider.cpp
Normal file
|
|
@ -0,0 +1,759 @@
|
|||
/**************************************************************************/
|
||||
/* editor_spin_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 "editor_spin_slider.h"
|
||||
|
||||
#include "core/input/input.h"
|
||||
#include "core/math/expression.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/theme/theme_db.h"
|
||||
|
||||
String EditorSpinSlider::get_tooltip(const Point2 &p_pos) const {
|
||||
if (!read_only && grabber->is_visible()) {
|
||||
Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL;
|
||||
return TS->format_number(rtos(get_value())) + "\n\n" + vformat(TTR("Hold %s to round to integers.\nHold Shift for more precise changes."), find_keycode_name(key));
|
||||
}
|
||||
return TS->format_number(rtos(get_value()));
|
||||
}
|
||||
|
||||
String EditorSpinSlider::get_text_value() const {
|
||||
return TS->format_number(String::num(get_value(), Math::range_step_decimals(get_step())));
|
||||
}
|
||||
|
||||
void EditorSpinSlider::gui_input(const Ref<InputEvent> &p_event) {
|
||||
ERR_FAIL_COND(p_event.is_null());
|
||||
|
||||
if (read_only) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<InputEventMouseButton> mb = p_event;
|
||||
if (mb.is_valid()) {
|
||||
if (mb->get_button_index() == MouseButton::LEFT) {
|
||||
if (mb->is_pressed()) {
|
||||
if (updown_offset != -1 && ((!is_layout_rtl() && mb->get_position().x > updown_offset) || (is_layout_rtl() && mb->get_position().x < updown_offset))) {
|
||||
// Updown pressed.
|
||||
if (mb->get_position().y < get_size().height / 2) {
|
||||
set_value(get_value() + get_step());
|
||||
} else {
|
||||
set_value(get_value() - get_step());
|
||||
}
|
||||
emit_signal("updown_pressed");
|
||||
return;
|
||||
}
|
||||
_grab_start();
|
||||
} else {
|
||||
_grab_end();
|
||||
}
|
||||
} else if (mb->get_button_index() == MouseButton::RIGHT) {
|
||||
if (mb->is_pressed() && is_grabbing()) {
|
||||
_grab_end();
|
||||
set_value(pre_grab_value);
|
||||
}
|
||||
} else if (mb->get_button_index() == MouseButton::WHEEL_UP || mb->get_button_index() == MouseButton::WHEEL_DOWN) {
|
||||
if (grabber->is_visible()) {
|
||||
callable_mp((CanvasItem *)this, &CanvasItem::queue_redraw).call_deferred();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<InputEventMouseMotion> mm = p_event;
|
||||
if (mm.is_valid()) {
|
||||
if (grabbing_spinner_attempt) {
|
||||
double diff_x = mm->get_relative().x;
|
||||
if (mm->is_shift_pressed() && grabbing_spinner) {
|
||||
diff_x *= 0.1;
|
||||
}
|
||||
grabbing_spinner_dist_cache += diff_x * grabbing_spinner_speed;
|
||||
|
||||
if (!grabbing_spinner && ABS(grabbing_spinner_dist_cache) > 4 * grabbing_spinner_speed * EDSCALE) {
|
||||
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_CAPTURED);
|
||||
grabbing_spinner = true;
|
||||
}
|
||||
|
||||
if (grabbing_spinner) {
|
||||
// Don't make the user scroll all the way back to 'in range' if they went off the end.
|
||||
if (pre_grab_value < get_min() && !is_lesser_allowed()) {
|
||||
pre_grab_value = get_min();
|
||||
}
|
||||
if (pre_grab_value > get_max() && !is_greater_allowed()) {
|
||||
pre_grab_value = get_max();
|
||||
}
|
||||
|
||||
if (mm->is_command_or_control_pressed()) {
|
||||
// If control was just pressed, don't make the value do a huge jump in magnitude.
|
||||
if (grabbing_spinner_dist_cache != 0) {
|
||||
pre_grab_value += grabbing_spinner_dist_cache * get_step();
|
||||
grabbing_spinner_dist_cache = 0;
|
||||
}
|
||||
|
||||
set_value(Math::round(pre_grab_value + get_step() * grabbing_spinner_dist_cache * 10));
|
||||
} else {
|
||||
set_value(pre_grab_value + get_step() * grabbing_spinner_dist_cache);
|
||||
}
|
||||
}
|
||||
} else if (updown_offset != -1) {
|
||||
bool new_hover = (!is_layout_rtl() && mm->get_position().x > updown_offset) || (is_layout_rtl() && mm->get_position().x < updown_offset);
|
||||
if (new_hover != hover_updown) {
|
||||
hover_updown = new_hover;
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<InputEventKey> k = p_event;
|
||||
if (k.is_valid() && k->is_pressed()) {
|
||||
if (k->is_action("ui_accept", true)) {
|
||||
_focus_entered();
|
||||
} else if (is_grabbing()) {
|
||||
if (k->is_action("ui_cancel", true)) {
|
||||
_grab_end();
|
||||
set_value(pre_grab_value);
|
||||
}
|
||||
accept_event();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSpinSlider::_grab_start() {
|
||||
grabbing_spinner_attempt = true;
|
||||
grabbing_spinner_dist_cache = 0;
|
||||
pre_grab_value = get_value();
|
||||
grabbing_spinner = false;
|
||||
grabbing_spinner_mouse_pos = get_global_mouse_position();
|
||||
emit_signal("grabbed");
|
||||
}
|
||||
|
||||
void EditorSpinSlider::_grab_end() {
|
||||
if (grabbing_spinner_attempt) {
|
||||
if (grabbing_spinner) {
|
||||
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
|
||||
Input::get_singleton()->warp_mouse(grabbing_spinner_mouse_pos);
|
||||
queue_redraw();
|
||||
grabbing_spinner = false;
|
||||
emit_signal("ungrabbed");
|
||||
} else {
|
||||
_focus_entered();
|
||||
}
|
||||
|
||||
grabbing_spinner_attempt = false;
|
||||
}
|
||||
|
||||
if (grabbing_grabber) {
|
||||
grabbing_grabber = false;
|
||||
mousewheel_over_grabber = false;
|
||||
emit_signal("ungrabbed");
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) {
|
||||
if (read_only) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<InputEventMouseButton> mb = p_event;
|
||||
|
||||
if (grabbing_grabber) {
|
||||
if (mb.is_valid()) {
|
||||
if (mb->get_button_index() == MouseButton::WHEEL_UP) {
|
||||
set_value(get_value() + get_step());
|
||||
mousewheel_over_grabber = true;
|
||||
} else if (mb->get_button_index() == MouseButton::WHEEL_DOWN) {
|
||||
set_value(get_value() - get_step());
|
||||
mousewheel_over_grabber = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
|
||||
if (mb->is_pressed()) {
|
||||
grabbing_grabber = true;
|
||||
pre_grab_value = get_value();
|
||||
if (!mousewheel_over_grabber) {
|
||||
grabbing_ratio = get_as_ratio();
|
||||
grabbing_from = grabber->get_transform().xform(mb->get_position()).x;
|
||||
}
|
||||
grab_focus();
|
||||
emit_signal("grabbed");
|
||||
} else {
|
||||
grabbing_grabber = false;
|
||||
mousewheel_over_grabber = false;
|
||||
emit_signal("ungrabbed");
|
||||
}
|
||||
} else if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT) {
|
||||
if (mb->is_pressed() && grabbing_grabber) {
|
||||
grabbing_grabber = false;
|
||||
mousewheel_over_grabber = false;
|
||||
set_value(pre_grab_value);
|
||||
emit_signal("ungrabbed");
|
||||
}
|
||||
}
|
||||
|
||||
Ref<InputEventMouseMotion> mm = p_event;
|
||||
if (mm.is_valid() && grabbing_grabber) {
|
||||
if (mousewheel_over_grabber) {
|
||||
return;
|
||||
}
|
||||
|
||||
float scale_x = get_global_transform_with_canvas().get_scale().x;
|
||||
ERR_FAIL_COND(Math::is_zero_approx(scale_x));
|
||||
float grabbing_ofs = (grabber->get_transform().xform(mm->get_position()).x - grabbing_from) / float(grabber_range) / scale_x;
|
||||
set_as_ratio(grabbing_ratio + grabbing_ofs);
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSpinSlider::_value_input_gui_input(const Ref<InputEvent> &p_event) {
|
||||
Ref<InputEventKey> k = p_event;
|
||||
if (k.is_valid() && k->is_pressed() && !read_only) {
|
||||
Key code = k->get_keycode();
|
||||
|
||||
switch (code) {
|
||||
case Key::UP:
|
||||
case Key::DOWN: {
|
||||
double step = get_step();
|
||||
if (step < 1) {
|
||||
double divisor = 1.0 / step;
|
||||
|
||||
if (trunc(divisor) == divisor) {
|
||||
step = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (k->is_command_or_control_pressed()) {
|
||||
step *= 100.0;
|
||||
} else if (k->is_shift_pressed()) {
|
||||
step *= 10.0;
|
||||
} else if (k->is_alt_pressed()) {
|
||||
step *= 0.1;
|
||||
}
|
||||
|
||||
_evaluate_input_text();
|
||||
|
||||
double last_value = get_value();
|
||||
if (code == Key::DOWN) {
|
||||
step *= -1;
|
||||
}
|
||||
set_value(last_value + step);
|
||||
|
||||
value_input_dirty = true;
|
||||
set_process_internal(true);
|
||||
} break;
|
||||
case Key::ESCAPE: {
|
||||
value_input_closed_frame = Engine::get_singleton()->get_frames_drawn();
|
||||
if (value_input_popup) {
|
||||
value_input_popup->hide();
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSpinSlider::_update_value_input_stylebox() {
|
||||
if (!value_input) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a left margin to the stylebox to make the number align with the Label
|
||||
// when it's edited. The LineEdit "focus" stylebox uses the "normal" stylebox's
|
||||
// default margins.
|
||||
Ref<StyleBox> stylebox = get_theme_stylebox(CoreStringName(normal), SNAME("LineEdit"))->duplicate();
|
||||
// EditorSpinSliders with a label have more space on the left, so add an
|
||||
// higher margin to match the location where the text begins.
|
||||
// The margin values below were determined by empirical testing.
|
||||
if (is_layout_rtl()) {
|
||||
stylebox->set_content_margin(SIDE_RIGHT, (!get_label().is_empty() ? 23 : 16) * EDSCALE);
|
||||
} else {
|
||||
stylebox->set_content_margin(SIDE_LEFT, (!get_label().is_empty() ? 23 : 16) * EDSCALE);
|
||||
}
|
||||
|
||||
value_input->add_theme_style_override(CoreStringName(normal), stylebox);
|
||||
}
|
||||
|
||||
void EditorSpinSlider::_draw_spin_slider() {
|
||||
updown_offset = -1;
|
||||
|
||||
RID ci = get_canvas_item();
|
||||
bool rtl = is_layout_rtl();
|
||||
Vector2 size = get_size();
|
||||
|
||||
Ref<StyleBox> sb = get_theme_stylebox(read_only ? SNAME("read_only") : CoreStringName(normal), SNAME("LineEdit"));
|
||||
if (!flat) {
|
||||
draw_style_box(sb, Rect2(Vector2(), size));
|
||||
}
|
||||
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("LineEdit"));
|
||||
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("LineEdit"));
|
||||
int sep_base = 4 * EDSCALE;
|
||||
int sep = sep_base + sb->get_offset().x; //make it have the same margin on both sides, looks better
|
||||
|
||||
int label_width = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width;
|
||||
int number_width = size.width - sb->get_minimum_size().width - label_width - sep;
|
||||
|
||||
Ref<Texture2D> updown = get_theme_icon(read_only ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
|
||||
|
||||
String numstr = get_text_value();
|
||||
|
||||
int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size);
|
||||
|
||||
Color fc = get_theme_color(read_only ? SNAME("font_uneditable_color") : SceneStringName(font_color), SNAME("LineEdit"));
|
||||
Color lc = get_theme_color(read_only ? SNAME("read_only_label_color") : SNAME("label_color"));
|
||||
|
||||
if (flat && !label.is_empty()) {
|
||||
Ref<StyleBox> label_bg = get_theme_stylebox(SNAME("label_bg"), SNAME("EditorSpinSlider"));
|
||||
if (rtl) {
|
||||
draw_style_box(label_bg, Rect2(Vector2(size.width - (sb->get_offset().x * 2 + label_width), 0), Vector2(sb->get_offset().x * 2 + label_width, size.height)));
|
||||
} else {
|
||||
draw_style_box(label_bg, Rect2(Vector2(), Vector2(sb->get_offset().x * 2 + label_width, size.height)));
|
||||
}
|
||||
}
|
||||
|
||||
if (has_focus()) {
|
||||
Ref<StyleBox> focus = get_theme_stylebox(SNAME("focus"), SNAME("LineEdit"));
|
||||
draw_style_box(focus, Rect2(Vector2(), size));
|
||||
}
|
||||
|
||||
if (rtl) {
|
||||
draw_string(font, Vector2(Math::round(size.width - sb->get_offset().x - label_width), vofs), label, HORIZONTAL_ALIGNMENT_RIGHT, -1, font_size, lc * Color(1, 1, 1, 0.5));
|
||||
} else {
|
||||
draw_string(font, Vector2(Math::round(sb->get_offset().x), vofs), label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, lc * Color(1, 1, 1, 0.5));
|
||||
}
|
||||
|
||||
int suffix_start = numstr.length();
|
||||
RID num_rid = TS->create_shaped_text();
|
||||
TS->shaped_text_add_string(num_rid, numstr + U"\u2009" + suffix, font->get_rids(), font_size, font->get_opentype_features());
|
||||
|
||||
float text_start = rtl ? Math::round(sb->get_offset().x) : Math::round(sb->get_offset().x + label_width + sep);
|
||||
Vector2 text_ofs = rtl ? Vector2(text_start + (number_width - TS->shaped_text_get_width(num_rid)), vofs) : Vector2(text_start, vofs);
|
||||
int v_size = TS->shaped_text_get_glyph_count(num_rid);
|
||||
const Glyph *glyphs = TS->shaped_text_get_glyphs(num_rid);
|
||||
for (int i = 0; i < v_size; i++) {
|
||||
for (int j = 0; j < glyphs[i].repeat; j++) {
|
||||
if (text_ofs.x >= text_start && (text_ofs.x + glyphs[i].advance) <= (text_start + number_width)) {
|
||||
Color color = fc;
|
||||
if (glyphs[i].start >= suffix_start) {
|
||||
color.a *= 0.4;
|
||||
}
|
||||
if (glyphs[i].font_rid != RID()) {
|
||||
TS->font_draw_glyph(glyphs[i].font_rid, ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color);
|
||||
} else if (((glyphs[i].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) && ((glyphs[i].flags & TextServer::GRAPHEME_IS_EMBEDDED_OBJECT) != TextServer::GRAPHEME_IS_EMBEDDED_OBJECT)) {
|
||||
TS->draw_hex_code_box(ci, glyphs[i].font_size, text_ofs + Vector2(glyphs[i].x_off, glyphs[i].y_off), glyphs[i].index, color);
|
||||
}
|
||||
}
|
||||
text_ofs.x += glyphs[i].advance;
|
||||
}
|
||||
}
|
||||
TS->free_rid(num_rid);
|
||||
|
||||
if (!hide_slider) {
|
||||
if (editing_integer) {
|
||||
Ref<Texture2D> updown2 = read_only ? theme_cache.updown_disabled_icon : theme_cache.updown_icon;
|
||||
int updown_vofs = (size.height - updown2->get_height()) / 2;
|
||||
if (rtl) {
|
||||
updown_offset = sb->get_margin(SIDE_LEFT);
|
||||
} else {
|
||||
updown_offset = size.width - sb->get_margin(SIDE_RIGHT) - updown2->get_width();
|
||||
}
|
||||
Color c(1, 1, 1);
|
||||
if (hover_updown) {
|
||||
c *= Color(1.2, 1.2, 1.2);
|
||||
}
|
||||
draw_texture(updown2, Vector2(updown_offset, updown_vofs), c);
|
||||
if (rtl) {
|
||||
updown_offset += updown2->get_width();
|
||||
}
|
||||
if (grabber->is_visible()) {
|
||||
grabber->hide();
|
||||
}
|
||||
} else {
|
||||
const int grabber_w = 4 * EDSCALE;
|
||||
const int width = size.width - sb->get_minimum_size().width - grabber_w;
|
||||
const int ofs = sb->get_offset().x;
|
||||
const int svofs = (size.height + vofs) / 2 - 1;
|
||||
Color c = fc;
|
||||
|
||||
// Draw the horizontal slider's background.
|
||||
c.a = 0.2;
|
||||
draw_rect(Rect2(ofs, svofs + 1, width, 2 * EDSCALE), c);
|
||||
|
||||
// Draw the horizontal slider's filled part on the left.
|
||||
const int gofs = get_as_ratio() * width;
|
||||
c.a = 0.45;
|
||||
draw_rect(Rect2(ofs, svofs + 1, gofs, 2 * EDSCALE), c);
|
||||
|
||||
// Draw the horizontal slider's grabber.
|
||||
c.a = 0.9;
|
||||
const Rect2 grabber_rect = Rect2(ofs + gofs, svofs, grabber_w, 4 * EDSCALE);
|
||||
draw_rect(grabber_rect, c);
|
||||
|
||||
grabbing_spinner_mouse_pos = get_global_position() + grabber_rect.get_center();
|
||||
|
||||
bool display_grabber = !read_only && (grabbing_grabber || mouse_over_spin || mouse_over_grabber) && !grabbing_spinner && !(value_input_popup && value_input_popup->is_visible());
|
||||
if (grabber->is_visible() != display_grabber) {
|
||||
grabber->set_visible(display_grabber);
|
||||
}
|
||||
|
||||
if (display_grabber) {
|
||||
Ref<Texture2D> grabber_tex;
|
||||
if (mouse_over_grabber) {
|
||||
grabber_tex = get_theme_icon(SNAME("grabber_highlight"), SNAME("HSlider"));
|
||||
} else {
|
||||
grabber_tex = get_theme_icon(SNAME("grabber"), SNAME("HSlider"));
|
||||
}
|
||||
|
||||
if (grabber->get_texture() != grabber_tex) {
|
||||
grabber->set_texture(grabber_tex);
|
||||
}
|
||||
|
||||
Vector2 scale = get_global_transform_with_canvas().get_scale();
|
||||
grabber->set_scale(scale);
|
||||
grabber->reset_size();
|
||||
grabber->set_position((grabber_rect.get_center() - grabber->get_size() * 0.5) * scale);
|
||||
|
||||
if (mousewheel_over_grabber) {
|
||||
Input::get_singleton()->warp_mouse(grabber->get_position() + grabber_rect.size);
|
||||
}
|
||||
|
||||
grabber_range = width;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSpinSlider::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
grabbing_spinner_speed = EditorSettings::get_singleton()->get("interface/inspector/float_drag_speed");
|
||||
_update_value_input_stylebox();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
_update_value_input_stylebox();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_INTERNAL_PROCESS: {
|
||||
if (value_input_dirty) {
|
||||
value_input_dirty = false;
|
||||
value_input->set_text(get_text_value());
|
||||
}
|
||||
set_process_internal(false);
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_DRAW: {
|
||||
_draw_spin_slider();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_WM_WINDOW_FOCUS_IN:
|
||||
case NOTIFICATION_WM_WINDOW_FOCUS_OUT:
|
||||
case NOTIFICATION_WM_CLOSE_REQUEST:
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
if (grabbing_spinner) {
|
||||
grabber->hide();
|
||||
Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE);
|
||||
Input::get_singleton()->warp_mouse(grabbing_spinner_mouse_pos);
|
||||
grabbing_spinner = false;
|
||||
grabbing_spinner_attempt = false;
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_MOUSE_ENTER: {
|
||||
mouse_over_spin = true;
|
||||
queue_redraw();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_MOUSE_EXIT: {
|
||||
mouse_over_spin = false;
|
||||
queue_redraw();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_FOCUS_ENTER: {
|
||||
if ((Input::get_singleton()->is_action_pressed("ui_focus_next") || Input::get_singleton()->is_action_pressed("ui_focus_prev")) && value_input_closed_frame != Engine::get_singleton()->get_frames_drawn()) {
|
||||
_focus_entered();
|
||||
}
|
||||
value_input_closed_frame = 0;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
LineEdit *EditorSpinSlider::get_line_edit() {
|
||||
_ensure_input_popup();
|
||||
return value_input;
|
||||
}
|
||||
|
||||
Size2 EditorSpinSlider::get_minimum_size() const {
|
||||
Ref<StyleBox> sb = get_theme_stylebox(CoreStringName(normal), SNAME("LineEdit"));
|
||||
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("LineEdit"));
|
||||
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("LineEdit"));
|
||||
|
||||
Size2 ms = sb->get_minimum_size();
|
||||
ms.height += font->get_height(font_size);
|
||||
|
||||
return ms;
|
||||
}
|
||||
|
||||
void EditorSpinSlider::set_hide_slider(bool p_hide) {
|
||||
hide_slider = p_hide;
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
bool EditorSpinSlider::is_hiding_slider() const {
|
||||
return hide_slider;
|
||||
}
|
||||
|
||||
void EditorSpinSlider::set_editing_integer(bool p_editing_integer) {
|
||||
if (p_editing_integer == editing_integer) {
|
||||
return;
|
||||
}
|
||||
|
||||
editing_integer = p_editing_integer;
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
bool EditorSpinSlider::is_editing_integer() const {
|
||||
return editing_integer;
|
||||
}
|
||||
|
||||
void EditorSpinSlider::set_label(const String &p_label) {
|
||||
label = p_label;
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
String EditorSpinSlider::get_label() const {
|
||||
return label;
|
||||
}
|
||||
|
||||
void EditorSpinSlider::set_suffix(const String &p_suffix) {
|
||||
suffix = p_suffix;
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
String EditorSpinSlider::get_suffix() const {
|
||||
return suffix;
|
||||
}
|
||||
|
||||
void EditorSpinSlider::_evaluate_input_text() {
|
||||
Ref<Expression> expr;
|
||||
expr.instantiate();
|
||||
|
||||
// Convert commas ',' to dots '.' for French/German etc. keyboard layouts.
|
||||
String text = value_input->get_text().replace(",", ".");
|
||||
text = text.replace(";", ",");
|
||||
text = TS->parse_number(text);
|
||||
|
||||
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 = value_input->get_text();
|
||||
text = TS->parse_number(text);
|
||||
|
||||
err = expr->parse(text);
|
||||
if (err != OK) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Variant v = expr->execute(Array(), nullptr, false, true);
|
||||
if (v.get_type() == Variant::NIL) {
|
||||
return;
|
||||
}
|
||||
set_value(v);
|
||||
}
|
||||
|
||||
//text_submitted signal
|
||||
void EditorSpinSlider::_value_input_submitted(const String &p_text) {
|
||||
value_input_closed_frame = Engine::get_singleton()->get_frames_drawn();
|
||||
if (value_input_popup) {
|
||||
value_input_popup->hide();
|
||||
}
|
||||
}
|
||||
|
||||
//modal_closed signal
|
||||
void EditorSpinSlider::_value_input_closed() {
|
||||
_evaluate_input_text();
|
||||
value_input_closed_frame = Engine::get_singleton()->get_frames_drawn();
|
||||
}
|
||||
|
||||
//focus_exited signal
|
||||
void EditorSpinSlider::_value_focus_exited() {
|
||||
// discontinue because the focus_exit was caused by right-click context menu
|
||||
if (value_input->is_menu_visible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (read_only) {
|
||||
// Spin slider has become read only while it was being edited.
|
||||
return;
|
||||
}
|
||||
|
||||
_evaluate_input_text();
|
||||
// focus is not on the same element after the value_input was exited
|
||||
// -> focus is on next element
|
||||
// -> TAB was pressed
|
||||
// -> modal_close was not called
|
||||
// -> need to close/hide manually
|
||||
if (!is_visible_in_tree() || value_input_closed_frame != Engine::get_singleton()->get_frames_drawn()) {
|
||||
// Hidden or something else took focus.
|
||||
if (value_input_popup) {
|
||||
value_input_popup->hide();
|
||||
}
|
||||
} else {
|
||||
// Enter or Esc was pressed.
|
||||
grab_focus();
|
||||
}
|
||||
|
||||
emit_signal("value_focus_exited");
|
||||
}
|
||||
|
||||
void EditorSpinSlider::_grabber_mouse_entered() {
|
||||
mouse_over_grabber = true;
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
void EditorSpinSlider::_grabber_mouse_exited() {
|
||||
mouse_over_grabber = false;
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
void EditorSpinSlider::set_read_only(bool p_enable) {
|
||||
read_only = p_enable;
|
||||
if (read_only && value_input && value_input->is_inside_tree()) {
|
||||
value_input->release_focus();
|
||||
}
|
||||
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
bool EditorSpinSlider::is_read_only() const {
|
||||
return read_only;
|
||||
}
|
||||
|
||||
void EditorSpinSlider::set_flat(bool p_enable) {
|
||||
flat = p_enable;
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
bool EditorSpinSlider::is_flat() const {
|
||||
return flat;
|
||||
}
|
||||
|
||||
bool EditorSpinSlider::is_grabbing() const {
|
||||
return grabbing_grabber || grabbing_spinner;
|
||||
}
|
||||
|
||||
void EditorSpinSlider::_focus_entered() {
|
||||
if (read_only) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ensure_input_popup();
|
||||
value_input->set_text(get_text_value());
|
||||
value_input_popup->set_size(get_size());
|
||||
value_input->set_focus_next(find_next_valid_focus()->get_path());
|
||||
value_input->set_focus_previous(find_prev_valid_focus()->get_path());
|
||||
callable_mp((CanvasItem *)value_input_popup, &CanvasItem::show).call_deferred();
|
||||
callable_mp((Control *)value_input, &Control::grab_focus).call_deferred();
|
||||
callable_mp(value_input, &LineEdit ::select_all).call_deferred();
|
||||
emit_signal("value_focus_entered");
|
||||
}
|
||||
|
||||
void EditorSpinSlider::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_label", "label"), &EditorSpinSlider::set_label);
|
||||
ClassDB::bind_method(D_METHOD("get_label"), &EditorSpinSlider::get_label);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_suffix", "suffix"), &EditorSpinSlider::set_suffix);
|
||||
ClassDB::bind_method(D_METHOD("get_suffix"), &EditorSpinSlider::get_suffix);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_read_only", "read_only"), &EditorSpinSlider::set_read_only);
|
||||
ClassDB::bind_method(D_METHOD("is_read_only"), &EditorSpinSlider::is_read_only);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_flat", "flat"), &EditorSpinSlider::set_flat);
|
||||
ClassDB::bind_method(D_METHOD("is_flat"), &EditorSpinSlider::is_flat);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_hide_slider", "hide_slider"), &EditorSpinSlider::set_hide_slider);
|
||||
ClassDB::bind_method(D_METHOD("is_hiding_slider"), &EditorSpinSlider::is_hiding_slider);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_editing_integer", "editing_integer"), &EditorSpinSlider::set_editing_integer);
|
||||
ClassDB::bind_method(D_METHOD("is_editing_integer"), &EditorSpinSlider::is_editing_integer);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "label"), "set_label", "get_label");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING, "suffix"), "set_suffix", "get_suffix");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "is_read_only");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "hide_slider"), "set_hide_slider", "is_hiding_slider");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editing_integer"), "set_editing_integer", "is_editing_integer");
|
||||
|
||||
ADD_SIGNAL(MethodInfo("grabbed"));
|
||||
ADD_SIGNAL(MethodInfo("ungrabbed"));
|
||||
ADD_SIGNAL(MethodInfo("updown_pressed"));
|
||||
ADD_SIGNAL(MethodInfo("value_focus_entered"));
|
||||
ADD_SIGNAL(MethodInfo("value_focus_exited"));
|
||||
|
||||
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, EditorSpinSlider, updown_icon, "updown");
|
||||
BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, EditorSpinSlider, updown_disabled_icon, "updown_disabled");
|
||||
}
|
||||
|
||||
void EditorSpinSlider::_ensure_input_popup() {
|
||||
if (value_input_popup) {
|
||||
return;
|
||||
}
|
||||
|
||||
value_input_popup = memnew(Control);
|
||||
value_input_popup->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
|
||||
add_child(value_input_popup);
|
||||
|
||||
value_input = memnew(LineEdit);
|
||||
value_input->set_emoji_menu_enabled(false);
|
||||
value_input->set_focus_mode(FOCUS_CLICK);
|
||||
value_input_popup->add_child(value_input);
|
||||
value_input->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
|
||||
value_input_popup->connect(SceneStringName(hidden), callable_mp(this, &EditorSpinSlider::_value_input_closed));
|
||||
value_input->connect(SceneStringName(text_submitted), callable_mp(this, &EditorSpinSlider::_value_input_submitted));
|
||||
value_input->connect(SceneStringName(focus_exited), callable_mp(this, &EditorSpinSlider::_value_focus_exited));
|
||||
value_input->connect(SceneStringName(gui_input), callable_mp(this, &EditorSpinSlider::_value_input_gui_input));
|
||||
|
||||
if (is_inside_tree()) {
|
||||
_update_value_input_stylebox();
|
||||
}
|
||||
}
|
||||
|
||||
EditorSpinSlider::EditorSpinSlider() {
|
||||
set_focus_mode(FOCUS_ALL);
|
||||
grabber = memnew(TextureRect);
|
||||
add_child(grabber);
|
||||
grabber->hide();
|
||||
grabber->set_z_index(1);
|
||||
grabber->set_mouse_filter(MOUSE_FILTER_STOP);
|
||||
grabber->connect(SceneStringName(mouse_entered), callable_mp(this, &EditorSpinSlider::_grabber_mouse_entered));
|
||||
grabber->connect(SceneStringName(mouse_exited), callable_mp(this, &EditorSpinSlider::_grabber_mouse_exited));
|
||||
grabber->connect(SceneStringName(gui_input), callable_mp(this, &EditorSpinSlider::_grabber_gui_input));
|
||||
}
|
||||
135
engine/editor/gui/editor_spin_slider.h
Normal file
135
engine/editor/gui/editor_spin_slider.h
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/**************************************************************************/
|
||||
/* editor_spin_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 EDITOR_SPIN_SLIDER_H
|
||||
#define EDITOR_SPIN_SLIDER_H
|
||||
|
||||
#include "scene/gui/line_edit.h"
|
||||
#include "scene/gui/range.h"
|
||||
#include "scene/gui/texture_rect.h"
|
||||
|
||||
class EditorSpinSlider : public Range {
|
||||
GDCLASS(EditorSpinSlider, Range);
|
||||
|
||||
String label;
|
||||
String suffix;
|
||||
int updown_offset = -1;
|
||||
bool hover_updown = false;
|
||||
bool mouse_hover = false;
|
||||
|
||||
TextureRect *grabber = nullptr;
|
||||
int grabber_range = 1;
|
||||
|
||||
bool mouse_over_spin = false;
|
||||
bool mouse_over_grabber = false;
|
||||
bool mousewheel_over_grabber = false;
|
||||
|
||||
bool grabbing_grabber = false;
|
||||
int grabbing_from = 0;
|
||||
float grabbing_ratio = 0.0f;
|
||||
|
||||
bool grabbing_spinner_attempt = false;
|
||||
bool grabbing_spinner = false;
|
||||
|
||||
bool read_only = false;
|
||||
float grabbing_spinner_dist_cache = 0.0f;
|
||||
float grabbing_spinner_speed = 0.0f;
|
||||
Vector2 grabbing_spinner_mouse_pos;
|
||||
double pre_grab_value = 0.0;
|
||||
|
||||
Control *value_input_popup = nullptr;
|
||||
LineEdit *value_input = nullptr;
|
||||
uint64_t value_input_closed_frame = 0;
|
||||
bool value_input_dirty = false;
|
||||
|
||||
bool hide_slider = false;
|
||||
bool flat = false;
|
||||
bool editing_integer = false;
|
||||
|
||||
void _grab_start();
|
||||
void _grab_end();
|
||||
|
||||
void _grabber_gui_input(const Ref<InputEvent> &p_event);
|
||||
void _value_input_closed();
|
||||
void _value_input_submitted(const String &);
|
||||
void _value_focus_exited();
|
||||
void _value_input_gui_input(const Ref<InputEvent> &p_event);
|
||||
|
||||
void _evaluate_input_text();
|
||||
|
||||
void _update_value_input_stylebox();
|
||||
void _ensure_input_popup();
|
||||
void _draw_spin_slider();
|
||||
|
||||
struct ThemeCache {
|
||||
Ref<Texture2D> updown_icon;
|
||||
Ref<Texture2D> updown_disabled_icon;
|
||||
} theme_cache;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
static void _bind_methods();
|
||||
void _grabber_mouse_entered();
|
||||
void _grabber_mouse_exited();
|
||||
void _focus_entered();
|
||||
|
||||
public:
|
||||
String get_tooltip(const Point2 &p_pos) const override;
|
||||
|
||||
String get_text_value() const;
|
||||
void set_label(const String &p_label);
|
||||
String get_label() const;
|
||||
|
||||
void set_suffix(const String &p_suffix);
|
||||
String get_suffix() const;
|
||||
|
||||
void set_hide_slider(bool p_hide);
|
||||
bool is_hiding_slider() const;
|
||||
|
||||
void set_editing_integer(bool p_editing_integer);
|
||||
bool is_editing_integer() const;
|
||||
|
||||
void set_read_only(bool p_enable);
|
||||
bool is_read_only() const;
|
||||
|
||||
void set_flat(bool p_enable);
|
||||
bool is_flat() const;
|
||||
|
||||
bool is_grabbing() const;
|
||||
|
||||
void setup_and_show() { _focus_entered(); }
|
||||
LineEdit *get_line_edit();
|
||||
|
||||
virtual Size2 get_minimum_size() const override;
|
||||
EditorSpinSlider();
|
||||
};
|
||||
|
||||
#endif // EDITOR_SPIN_SLIDER_H
|
||||
90
engine/editor/gui/editor_title_bar.cpp
Normal file
90
engine/editor/gui/editor_title_bar.cpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**************************************************************************/
|
||||
/* editor_title_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 "editor_title_bar.h"
|
||||
|
||||
void EditorTitleBar::gui_input(const Ref<InputEvent> &p_event) {
|
||||
if (!can_move) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<InputEventMouseMotion> mm = p_event;
|
||||
if (mm.is_valid() && moving) {
|
||||
if (mm->get_button_mask().has_flag(MouseButtonMask::LEFT)) {
|
||||
Window *w = Object::cast_to<Window>(get_viewport());
|
||||
if (w) {
|
||||
Point2 mouse = DisplayServer::get_singleton()->mouse_get_position();
|
||||
w->set_position(mouse - click_pos);
|
||||
}
|
||||
} else {
|
||||
moving = false;
|
||||
}
|
||||
}
|
||||
|
||||
Ref<InputEventMouseButton> mb = p_event;
|
||||
if (mb.is_valid() && has_point(mb->get_position())) {
|
||||
Window *w = Object::cast_to<Window>(get_viewport());
|
||||
if (w) {
|
||||
if (mb->get_button_index() == MouseButton::LEFT) {
|
||||
if (mb->is_pressed()) {
|
||||
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_WINDOW_DRAG)) {
|
||||
DisplayServer::get_singleton()->window_start_drag(w->get_window_id());
|
||||
} else {
|
||||
click_pos = DisplayServer::get_singleton()->mouse_get_position() - w->get_position();
|
||||
moving = true;
|
||||
}
|
||||
} else {
|
||||
moving = false;
|
||||
}
|
||||
}
|
||||
if (mb->get_button_index() == MouseButton::LEFT && mb->is_double_click() && mb->is_pressed()) {
|
||||
if (DisplayServer::get_singleton()->window_maximize_on_title_dbl_click()) {
|
||||
if (w->get_mode() == Window::MODE_WINDOWED) {
|
||||
w->set_mode(Window::MODE_MAXIMIZED);
|
||||
} else if (w->get_mode() == Window::MODE_MAXIMIZED) {
|
||||
w->set_mode(Window::MODE_WINDOWED);
|
||||
}
|
||||
} else if (DisplayServer::get_singleton()->window_minimize_on_title_dbl_click()) {
|
||||
w->set_mode(Window::MODE_MINIMIZED);
|
||||
}
|
||||
moving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorTitleBar::set_can_move_window(bool p_enabled) {
|
||||
can_move = p_enabled;
|
||||
set_process_input(can_move);
|
||||
}
|
||||
|
||||
bool EditorTitleBar::get_can_move_window() const {
|
||||
return can_move;
|
||||
}
|
||||
53
engine/editor/gui/editor_title_bar.h
Normal file
53
engine/editor/gui/editor_title_bar.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**************************************************************************/
|
||||
/* editor_title_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 EDITOR_TITLE_BAR_H
|
||||
#define EDITOR_TITLE_BAR_H
|
||||
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/main/window.h"
|
||||
|
||||
class EditorTitleBar : public HBoxContainer {
|
||||
GDCLASS(EditorTitleBar, HBoxContainer);
|
||||
|
||||
Point2i click_pos;
|
||||
bool moving = false;
|
||||
bool can_move = false;
|
||||
|
||||
protected:
|
||||
virtual void gui_input(const Ref<InputEvent> &p_event) override;
|
||||
static void _bind_methods() {}
|
||||
|
||||
public:
|
||||
void set_can_move_window(bool p_enabled);
|
||||
bool get_can_move_window() const;
|
||||
};
|
||||
|
||||
#endif // EDITOR_TITLE_BAR_H
|
||||
606
engine/editor/gui/editor_toaster.cpp
Normal file
606
engine/editor/gui/editor_toaster.cpp
Normal file
|
|
@ -0,0 +1,606 @@
|
|||
/**************************************************************************/
|
||||
/* editor_toaster.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 "editor_toaster.h"
|
||||
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/label.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
#include "scene/resources/style_box_flat.h"
|
||||
|
||||
EditorToaster *EditorToaster::singleton = nullptr;
|
||||
|
||||
void EditorToaster::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_INTERNAL_PROCESS: {
|
||||
double delta = get_process_delta_time();
|
||||
|
||||
// Check if one element is hovered, if so, don't elapse time.
|
||||
bool hovered = false;
|
||||
for (const KeyValue<Control *, Toast> &element : toasts) {
|
||||
if (Rect2(Vector2(), element.key->get_size()).has_point(element.key->get_local_mouse_position())) {
|
||||
hovered = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Elapses the time and remove toasts if needed.
|
||||
if (!hovered) {
|
||||
for (const KeyValue<Control *, Toast> &element : toasts) {
|
||||
if (!element.value.popped || element.value.duration <= 0) {
|
||||
continue;
|
||||
}
|
||||
toasts[element.key].remaining_time -= delta;
|
||||
if (toasts[element.key].remaining_time < 0) {
|
||||
close(element.key);
|
||||
}
|
||||
element.key->queue_redraw();
|
||||
}
|
||||
} else {
|
||||
// Reset the timers when hovered.
|
||||
for (const KeyValue<Control *, Toast> &element : toasts) {
|
||||
if (!element.value.popped || element.value.duration <= 0) {
|
||||
continue;
|
||||
}
|
||||
toasts[element.key].remaining_time = element.value.duration;
|
||||
element.key->queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
// Change alpha over time.
|
||||
bool needs_update = false;
|
||||
for (const KeyValue<Control *, Toast> &element : toasts) {
|
||||
Color modulate_fade = element.key->get_modulate();
|
||||
|
||||
// Change alpha over time.
|
||||
if (element.value.popped && modulate_fade.a < 1.0) {
|
||||
modulate_fade.a += delta * 3;
|
||||
element.key->set_modulate(modulate_fade);
|
||||
} else if (!element.value.popped && modulate_fade.a > 0.0) {
|
||||
modulate_fade.a -= delta * 2;
|
||||
element.key->set_modulate(modulate_fade);
|
||||
}
|
||||
|
||||
// Hide element if it is not visible anymore.
|
||||
if (modulate_fade.a <= 0.0 && element.key->is_visible()) {
|
||||
element.key->hide();
|
||||
needs_update = true;
|
||||
} else if (modulate_fade.a > 0.0 && !element.key->is_visible()) {
|
||||
element.key->show();
|
||||
needs_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_update) {
|
||||
_update_vbox_position();
|
||||
_update_disable_notifications_button();
|
||||
main_button->queue_redraw();
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
if (vbox_container->is_visible()) {
|
||||
main_button->set_button_icon(get_editor_theme_icon(SNAME("Notification")));
|
||||
} else {
|
||||
main_button->set_button_icon(get_editor_theme_icon(SNAME("NotificationDisabled")));
|
||||
}
|
||||
disable_notifications_button->set_button_icon(get_editor_theme_icon(SNAME("NotificationDisabled")));
|
||||
|
||||
// Styleboxes background.
|
||||
info_panel_style_background->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)));
|
||||
|
||||
warning_panel_style_background->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)));
|
||||
warning_panel_style_background->set_border_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
|
||||
|
||||
error_panel_style_background->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)));
|
||||
error_panel_style_background->set_border_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
|
||||
|
||||
// Styleboxes progress.
|
||||
info_panel_style_progress->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)).lightened(0.03));
|
||||
|
||||
warning_panel_style_progress->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)).lightened(0.03));
|
||||
warning_panel_style_progress->set_border_color(get_theme_color(SNAME("warning_color"), EditorStringName(Editor)));
|
||||
|
||||
error_panel_style_progress->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)).lightened(0.03));
|
||||
error_panel_style_progress->set_border_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_TRANSFORM_CHANGED: {
|
||||
_update_vbox_position();
|
||||
_update_disable_notifications_button();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorToaster::_error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type) {
|
||||
// This may be called from a thread. Since we will deal with non-thread-safe elements,
|
||||
// we have to put it in the queue for safety.
|
||||
callable_mp_static(&EditorToaster::_error_handler_impl).call_deferred(String::utf8(p_file), p_line, String::utf8(p_error), String::utf8(p_errorexp), p_editor_notify, p_type);
|
||||
}
|
||||
|
||||
void EditorToaster::_error_handler_impl(const String &p_file, int p_line, const String &p_error, const String &p_errorexp, bool p_editor_notify, int p_type) {
|
||||
if (!EditorToaster::get_singleton() || !EditorToaster::get_singleton()->is_inside_tree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef DEV_ENABLED
|
||||
bool in_dev = true;
|
||||
#else
|
||||
bool in_dev = false;
|
||||
#endif
|
||||
|
||||
int show_all_setting = EDITOR_GET("interface/editor/show_internal_errors_in_toast_notifications");
|
||||
|
||||
if (p_editor_notify || (show_all_setting == 0 && in_dev) || show_all_setting == 1) {
|
||||
String err_str = !p_errorexp.is_empty() ? p_errorexp : p_error;
|
||||
String tooltip_str = p_file + ":" + itos(p_line);
|
||||
|
||||
if (!p_editor_notify) {
|
||||
if (p_type == ERR_HANDLER_WARNING) {
|
||||
err_str = "INTERNAL WARNING: " + err_str;
|
||||
} else {
|
||||
err_str = "INTERNAL ERROR: " + err_str;
|
||||
}
|
||||
}
|
||||
|
||||
Severity severity = ((ErrorHandlerType)p_type == ERR_HANDLER_WARNING) ? SEVERITY_WARNING : SEVERITY_ERROR;
|
||||
EditorToaster::get_singleton()->popup_str(err_str, severity, tooltip_str);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorToaster::_update_vbox_position() {
|
||||
// This is kind of a workaround because it's hard to keep the VBox anchroed to the bottom.
|
||||
vbox_container->set_size(Vector2());
|
||||
vbox_container->set_position(get_global_position() - vbox_container->get_size() + Vector2(get_size().x, -5 * EDSCALE));
|
||||
}
|
||||
|
||||
void EditorToaster::_update_disable_notifications_button() {
|
||||
bool any_visible = false;
|
||||
for (KeyValue<Control *, Toast> element : toasts) {
|
||||
if (element.key->is_visible()) {
|
||||
any_visible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!any_visible || !vbox_container->is_visible()) {
|
||||
disable_notifications_panel->hide();
|
||||
} else {
|
||||
disable_notifications_panel->show();
|
||||
disable_notifications_panel->set_position(get_global_position() + Vector2(5 * EDSCALE, -disable_notifications_panel->get_minimum_size().y) + Vector2(get_size().x, -5 * EDSCALE));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorToaster::_auto_hide_or_free_toasts() {
|
||||
// Hide or free old temporary items.
|
||||
int visible_temporary = 0;
|
||||
int temporary = 0;
|
||||
LocalVector<Control *> to_delete;
|
||||
for (int i = vbox_container->get_child_count() - 1; i >= 0; i--) {
|
||||
Control *control = Object::cast_to<Control>(vbox_container->get_child(i));
|
||||
if (toasts[control].duration <= 0) {
|
||||
continue; // Ignore non-temporary toasts.
|
||||
}
|
||||
|
||||
temporary++;
|
||||
if (control->is_visible()) {
|
||||
visible_temporary++;
|
||||
}
|
||||
|
||||
// Hide
|
||||
if (visible_temporary > max_temporary_count) {
|
||||
close(control);
|
||||
}
|
||||
|
||||
// Free
|
||||
if (temporary > max_temporary_count * 2) {
|
||||
to_delete.push_back(control);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the control right away (removed as child) as it might cause issues otherwise when iterative over the vbox_container children.
|
||||
for (Control *c : to_delete) {
|
||||
vbox_container->remove_child(c);
|
||||
c->queue_free();
|
||||
toasts.erase(c);
|
||||
}
|
||||
|
||||
if (toasts.is_empty()) {
|
||||
main_button->set_tooltip_text(TTR("No notifications."));
|
||||
main_button->set_modulate(Color(0.5, 0.5, 0.5));
|
||||
main_button->set_disabled(true);
|
||||
set_process_internal(false);
|
||||
} else {
|
||||
main_button->set_tooltip_text(TTR("Show notifications."));
|
||||
main_button->set_modulate(Color(1, 1, 1));
|
||||
main_button->set_disabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorToaster::_draw_button() {
|
||||
bool has_one = false;
|
||||
Severity highest_severity = SEVERITY_INFO;
|
||||
for (const KeyValue<Control *, Toast> &element : toasts) {
|
||||
if (!element.key->is_visible()) {
|
||||
continue;
|
||||
}
|
||||
has_one = true;
|
||||
if (element.value.severity > highest_severity) {
|
||||
highest_severity = element.value.severity;
|
||||
}
|
||||
}
|
||||
|
||||
if (!has_one) {
|
||||
return;
|
||||
}
|
||||
|
||||
Color color;
|
||||
real_t button_radius = main_button->get_size().x / 8;
|
||||
switch (highest_severity) {
|
||||
case SEVERITY_INFO:
|
||||
color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
|
||||
break;
|
||||
case SEVERITY_WARNING:
|
||||
color = get_theme_color(SNAME("warning_color"), EditorStringName(Editor));
|
||||
break;
|
||||
case SEVERITY_ERROR:
|
||||
color = get_theme_color(SNAME("error_color"), EditorStringName(Editor));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
main_button->draw_circle(Vector2(button_radius * 2, button_radius * 2), button_radius, color);
|
||||
}
|
||||
|
||||
void EditorToaster::_draw_progress(Control *panel) {
|
||||
if (toasts.has(panel) && toasts[panel].remaining_time > 0 && toasts[panel].duration > 0) {
|
||||
Size2 size = panel->get_size();
|
||||
size.x *= MIN(1, Math::remap(toasts[panel].remaining_time, 0, toasts[panel].duration, 0, 2));
|
||||
|
||||
Ref<StyleBoxFlat> stylebox;
|
||||
switch (toasts[panel].severity) {
|
||||
case SEVERITY_INFO:
|
||||
stylebox = info_panel_style_progress;
|
||||
break;
|
||||
case SEVERITY_WARNING:
|
||||
stylebox = warning_panel_style_progress;
|
||||
break;
|
||||
case SEVERITY_ERROR:
|
||||
stylebox = error_panel_style_progress;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
panel->draw_style_box(stylebox, Rect2(Vector2(), size));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorToaster::_set_notifications_enabled(bool p_enabled) {
|
||||
vbox_container->set_visible(p_enabled);
|
||||
if (p_enabled) {
|
||||
main_button->set_button_icon(get_editor_theme_icon(SNAME("Notification")));
|
||||
} else {
|
||||
main_button->set_button_icon(get_editor_theme_icon(SNAME("NotificationDisabled")));
|
||||
}
|
||||
_update_disable_notifications_button();
|
||||
}
|
||||
|
||||
void EditorToaster::_repop_old() {
|
||||
// Repop olds, up to max_temporary_count
|
||||
bool needs_update = false;
|
||||
int visible_count = 0;
|
||||
for (int i = vbox_container->get_child_count() - 1; i >= 0; i--) {
|
||||
Control *control = Object::cast_to<Control>(vbox_container->get_child(i));
|
||||
if (!control->is_visible()) {
|
||||
control->show();
|
||||
toasts[control].remaining_time = toasts[control].duration;
|
||||
toasts[control].popped = true;
|
||||
needs_update = true;
|
||||
}
|
||||
visible_count++;
|
||||
if (visible_count >= max_temporary_count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (needs_update) {
|
||||
_update_vbox_position();
|
||||
_update_disable_notifications_button();
|
||||
main_button->queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
Control *EditorToaster::popup(Control *p_control, Severity p_severity, double p_time, const String &p_tooltip) {
|
||||
// Create the panel according to the severity.
|
||||
PanelContainer *panel = memnew(PanelContainer);
|
||||
panel->set_tooltip_text(p_tooltip);
|
||||
switch (p_severity) {
|
||||
case SEVERITY_INFO:
|
||||
panel->add_theme_style_override(SceneStringName(panel), info_panel_style_background);
|
||||
break;
|
||||
case SEVERITY_WARNING:
|
||||
panel->add_theme_style_override(SceneStringName(panel), warning_panel_style_background);
|
||||
break;
|
||||
case SEVERITY_ERROR:
|
||||
panel->add_theme_style_override(SceneStringName(panel), error_panel_style_background);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
panel->set_modulate(Color(1, 1, 1, 0));
|
||||
panel->connect(SceneStringName(draw), callable_mp(this, &EditorToaster::_draw_progress).bind(panel));
|
||||
panel->connect(SceneStringName(theme_changed), callable_mp(this, &EditorToaster::_toast_theme_changed).bind(panel));
|
||||
|
||||
Toast &toast = toasts[panel];
|
||||
|
||||
// Horizontal container.
|
||||
HBoxContainer *hbox_container = memnew(HBoxContainer);
|
||||
hbox_container->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
panel->add_child(hbox_container);
|
||||
|
||||
// Content control.
|
||||
p_control->set_h_size_flags(SIZE_EXPAND_FILL);
|
||||
hbox_container->add_child(p_control);
|
||||
|
||||
// Add buttons.
|
||||
if (p_time > 0.0) {
|
||||
Button *copy_button = memnew(Button);
|
||||
copy_button->set_flat(true);
|
||||
copy_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::copy).bind(panel));
|
||||
hbox_container->add_child(copy_button);
|
||||
|
||||
Button *close_button = memnew(Button);
|
||||
close_button->set_flat(true);
|
||||
close_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::instant_close).bind(panel));
|
||||
hbox_container->add_child(close_button);
|
||||
|
||||
toast.copy_button = copy_button;
|
||||
toast.close_button = close_button;
|
||||
}
|
||||
|
||||
toast.severity = p_severity;
|
||||
if (p_time > 0.0) {
|
||||
toast.duration = p_time;
|
||||
toast.remaining_time = p_time;
|
||||
} else {
|
||||
toast.duration = -1.0;
|
||||
}
|
||||
toast.popped = true;
|
||||
vbox_container->add_child(panel);
|
||||
_auto_hide_or_free_toasts();
|
||||
_update_vbox_position();
|
||||
_update_disable_notifications_button();
|
||||
main_button->queue_redraw();
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
void EditorToaster::popup_str(const String &p_message, Severity p_severity, const String &p_tooltip) {
|
||||
if (is_processing_error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Since "_popup_str" adds nodes to the tree, and since the "add_child" method is not
|
||||
// thread-safe, it's better to defer the call to the next cycle to be thread-safe.
|
||||
is_processing_error = true;
|
||||
MessageQueue::get_main_singleton()->push_callable(callable_mp(this, &EditorToaster::_popup_str), p_message, p_severity, p_tooltip);
|
||||
is_processing_error = false;
|
||||
}
|
||||
|
||||
void EditorToaster::_popup_str(const String &p_message, Severity p_severity, const String &p_tooltip) {
|
||||
is_processing_error = true;
|
||||
// Check if we already have a popup with the given message.
|
||||
Control *control = nullptr;
|
||||
for (KeyValue<Control *, Toast> element : toasts) {
|
||||
if (element.value.message == p_message && element.value.severity == p_severity && element.value.tooltip == p_tooltip) {
|
||||
control = element.key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new message if needed.
|
||||
if (control == nullptr) {
|
||||
HBoxContainer *hb = memnew(HBoxContainer);
|
||||
hb->add_theme_constant_override("separation", 0);
|
||||
|
||||
Label *label = memnew(Label);
|
||||
hb->add_child(label);
|
||||
|
||||
Label *count_label = memnew(Label);
|
||||
hb->add_child(count_label);
|
||||
|
||||
control = popup(hb, p_severity, default_message_duration, p_tooltip);
|
||||
|
||||
Toast &toast = toasts[control];
|
||||
toast.message = p_message;
|
||||
toast.tooltip = p_tooltip;
|
||||
toast.count = 1;
|
||||
toast.message_label = label;
|
||||
toast.message_count_label = count_label;
|
||||
} else {
|
||||
Toast &toast = toasts[control];
|
||||
if (toast.popped) {
|
||||
toast.count += 1;
|
||||
} else {
|
||||
toast.count = 1;
|
||||
}
|
||||
toast.remaining_time = toast.duration;
|
||||
toast.popped = true;
|
||||
control->show();
|
||||
vbox_container->move_child(control, vbox_container->get_child_count());
|
||||
_auto_hide_or_free_toasts();
|
||||
_update_vbox_position();
|
||||
_update_disable_notifications_button();
|
||||
main_button->queue_redraw();
|
||||
}
|
||||
|
||||
// Retrieve the label back, then update the text.
|
||||
Label *message_label = toasts[control].message_label;
|
||||
ERR_FAIL_NULL(message_label);
|
||||
message_label->set_text(p_message);
|
||||
message_label->set_text_overrun_behavior(TextServer::OVERRUN_NO_TRIMMING);
|
||||
message_label->set_custom_minimum_size(Size2());
|
||||
|
||||
Size2i size = message_label->get_combined_minimum_size();
|
||||
int limit_width = get_viewport_rect().size.x / 2; // Limit label size to half the viewport size.
|
||||
if (size.x > limit_width) {
|
||||
message_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
|
||||
message_label->set_custom_minimum_size(Size2(limit_width, 0));
|
||||
}
|
||||
|
||||
// Retrieve the count label back, then update the text.
|
||||
Label *message_count_label = toasts[control].message_count_label;
|
||||
if (toasts[control].count == 1) {
|
||||
message_count_label->hide();
|
||||
} else {
|
||||
message_count_label->set_text(vformat("(%d)", toasts[control].count));
|
||||
message_count_label->show();
|
||||
}
|
||||
|
||||
vbox_container->reset_size();
|
||||
|
||||
is_processing_error = false;
|
||||
set_process_internal(true);
|
||||
}
|
||||
|
||||
void EditorToaster::_toast_theme_changed(Control *p_control) {
|
||||
ERR_FAIL_COND(!toasts.has(p_control));
|
||||
|
||||
Toast &toast = toasts[p_control];
|
||||
if (toast.close_button) {
|
||||
toast.close_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));
|
||||
}
|
||||
if (toast.copy_button) {
|
||||
toast.copy_button->set_button_icon(get_editor_theme_icon(SNAME("ActionCopy")));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorToaster::close(Control *p_control) {
|
||||
ERR_FAIL_COND(!toasts.has(p_control));
|
||||
toasts[p_control].remaining_time = -1.0;
|
||||
toasts[p_control].popped = false;
|
||||
}
|
||||
|
||||
void EditorToaster::instant_close(Control *p_control) {
|
||||
close(p_control);
|
||||
p_control->set_modulate(Color(1, 1, 1, 0));
|
||||
}
|
||||
|
||||
void EditorToaster::copy(Control *p_control) {
|
||||
ERR_FAIL_COND(!toasts.has(p_control));
|
||||
DisplayServer::get_singleton()->clipboard_set(toasts[p_control].message);
|
||||
}
|
||||
|
||||
void EditorToaster::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("push_toast", "message", "severity", "tooltip"), &EditorToaster::_popup_str, DEFVAL(EditorToaster::SEVERITY_INFO), DEFVAL(String()));
|
||||
|
||||
BIND_ENUM_CONSTANT(SEVERITY_INFO);
|
||||
BIND_ENUM_CONSTANT(SEVERITY_WARNING);
|
||||
BIND_ENUM_CONSTANT(SEVERITY_ERROR);
|
||||
}
|
||||
|
||||
EditorToaster *EditorToaster::get_singleton() {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
EditorToaster::EditorToaster() {
|
||||
set_notify_transform(true);
|
||||
|
||||
// VBox.
|
||||
vbox_container = memnew(VBoxContainer);
|
||||
vbox_container->set_as_top_level(true);
|
||||
vbox_container->connect(SceneStringName(resized), callable_mp(this, &EditorToaster::_update_vbox_position));
|
||||
add_child(vbox_container);
|
||||
|
||||
// Theming (background).
|
||||
info_panel_style_background.instantiate();
|
||||
info_panel_style_background->set_corner_radius_all(stylebox_radius * EDSCALE);
|
||||
|
||||
warning_panel_style_background.instantiate();
|
||||
warning_panel_style_background->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE);
|
||||
warning_panel_style_background->set_corner_radius_all(stylebox_radius * EDSCALE);
|
||||
|
||||
error_panel_style_background.instantiate();
|
||||
error_panel_style_background->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE);
|
||||
error_panel_style_background->set_corner_radius_all(stylebox_radius * EDSCALE);
|
||||
|
||||
Ref<StyleBoxFlat> boxes[] = { info_panel_style_background, warning_panel_style_background, error_panel_style_background };
|
||||
for (int i = 0; i < 3; i++) {
|
||||
boxes[i]->set_content_margin_individual(int(stylebox_radius * 2.5), 3, int(stylebox_radius * 2.5), 3);
|
||||
}
|
||||
|
||||
// Theming (progress).
|
||||
info_panel_style_progress.instantiate();
|
||||
info_panel_style_progress->set_corner_radius_all(stylebox_radius * EDSCALE);
|
||||
|
||||
warning_panel_style_progress.instantiate();
|
||||
warning_panel_style_progress->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE);
|
||||
warning_panel_style_progress->set_corner_radius_all(stylebox_radius * EDSCALE);
|
||||
|
||||
error_panel_style_progress.instantiate();
|
||||
error_panel_style_progress->set_border_width(SIDE_LEFT, stylebox_radius * EDSCALE);
|
||||
error_panel_style_progress->set_corner_radius_all(stylebox_radius * EDSCALE);
|
||||
|
||||
// Main button.
|
||||
main_button = memnew(Button);
|
||||
main_button->set_tooltip_text(TTR("No notifications."));
|
||||
main_button->set_modulate(Color(0.5, 0.5, 0.5));
|
||||
main_button->set_disabled(true);
|
||||
main_button->set_theme_type_variation("FlatMenuButton");
|
||||
main_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::_set_notifications_enabled).bind(true));
|
||||
main_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::_repop_old));
|
||||
main_button->connect(SceneStringName(draw), callable_mp(this, &EditorToaster::_draw_button));
|
||||
add_child(main_button);
|
||||
|
||||
// Disable notification button.
|
||||
disable_notifications_panel = memnew(PanelContainer);
|
||||
disable_notifications_panel->set_as_top_level(true);
|
||||
disable_notifications_panel->add_theme_style_override(SceneStringName(panel), info_panel_style_background);
|
||||
add_child(disable_notifications_panel);
|
||||
|
||||
disable_notifications_button = memnew(Button);
|
||||
disable_notifications_button->set_tooltip_text(TTR("Silence the notifications."));
|
||||
disable_notifications_button->set_flat(true);
|
||||
disable_notifications_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::_set_notifications_enabled).bind(false));
|
||||
disable_notifications_panel->add_child(disable_notifications_button);
|
||||
|
||||
// Other
|
||||
singleton = this;
|
||||
|
||||
eh.errfunc = _error_handler;
|
||||
add_error_handler(&eh);
|
||||
}
|
||||
|
||||
EditorToaster::~EditorToaster() {
|
||||
singleton = nullptr;
|
||||
remove_error_handler(&eh);
|
||||
}
|
||||
129
engine/editor/gui/editor_toaster.h
Normal file
129
engine/editor/gui/editor_toaster.h
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/**************************************************************************/
|
||||
/* editor_toaster.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 EDITOR_TOASTER_H
|
||||
#define EDITOR_TOASTER_H
|
||||
|
||||
#include "scene/gui/box_container.h"
|
||||
|
||||
class Button;
|
||||
class PanelContainer;
|
||||
class StyleBoxFlat;
|
||||
|
||||
class EditorToaster : public HBoxContainer {
|
||||
GDCLASS(EditorToaster, HBoxContainer);
|
||||
|
||||
public:
|
||||
enum Severity {
|
||||
SEVERITY_INFO = 0,
|
||||
SEVERITY_WARNING,
|
||||
SEVERITY_ERROR,
|
||||
};
|
||||
|
||||
private:
|
||||
ErrorHandlerList eh;
|
||||
|
||||
const int stylebox_radius = 3;
|
||||
|
||||
Ref<StyleBoxFlat> info_panel_style_background;
|
||||
Ref<StyleBoxFlat> warning_panel_style_background;
|
||||
Ref<StyleBoxFlat> error_panel_style_background;
|
||||
|
||||
Ref<StyleBoxFlat> info_panel_style_progress;
|
||||
Ref<StyleBoxFlat> warning_panel_style_progress;
|
||||
Ref<StyleBoxFlat> error_panel_style_progress;
|
||||
|
||||
Button *main_button = nullptr;
|
||||
PanelContainer *disable_notifications_panel = nullptr;
|
||||
Button *disable_notifications_button = nullptr;
|
||||
|
||||
VBoxContainer *vbox_container = nullptr;
|
||||
const int max_temporary_count = 5;
|
||||
struct Toast {
|
||||
Severity severity = SEVERITY_INFO;
|
||||
|
||||
// Timing.
|
||||
real_t duration = -1.0;
|
||||
real_t remaining_time = 0.0;
|
||||
bool popped = false;
|
||||
|
||||
// Buttons
|
||||
Button *copy_button = nullptr;
|
||||
Button *close_button = nullptr;
|
||||
|
||||
// Messages
|
||||
String message;
|
||||
String tooltip;
|
||||
int count = 0;
|
||||
Label *message_label = nullptr;
|
||||
Label *message_count_label = nullptr;
|
||||
};
|
||||
HashMap<Control *, Toast> toasts;
|
||||
|
||||
bool is_processing_error = false; // Makes sure that we don't handle errors that are triggered within the EditorToaster error processing.
|
||||
|
||||
const double default_message_duration = 5.0;
|
||||
|
||||
static void _error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type);
|
||||
static void _error_handler_impl(const String &p_file, int p_line, const String &p_error, const String &p_errorexp, bool p_editor_notify, int p_type);
|
||||
void _update_vbox_position();
|
||||
void _update_disable_notifications_button();
|
||||
void _auto_hide_or_free_toasts();
|
||||
|
||||
void _draw_button();
|
||||
void _draw_progress(Control *panel);
|
||||
|
||||
void _set_notifications_enabled(bool p_enabled);
|
||||
void _repop_old();
|
||||
void _popup_str(const String &p_message, Severity p_severity, const String &p_tooltip);
|
||||
void _toast_theme_changed(Control *p_control);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
static EditorToaster *singleton;
|
||||
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
static EditorToaster *get_singleton();
|
||||
|
||||
Control *popup(Control *p_control, Severity p_severity = SEVERITY_INFO, double p_time = 0.0, const String &p_tooltip = String());
|
||||
void popup_str(const String &p_message, Severity p_severity = SEVERITY_INFO, const String &p_tooltip = String());
|
||||
void close(Control *p_control);
|
||||
void instant_close(Control *p_control);
|
||||
void copy(Control *p_control);
|
||||
|
||||
EditorToaster();
|
||||
~EditorToaster();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(EditorToaster::Severity);
|
||||
|
||||
#endif // EDITOR_TOASTER_H
|
||||
136
engine/editor/gui/editor_validation_panel.cpp
Normal file
136
engine/editor/gui/editor_validation_panel.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/**************************************************************************/
|
||||
/* editor_validation_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 "editor_validation_panel.h"
|
||||
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/label.h"
|
||||
|
||||
void EditorValidationPanel::_update() {
|
||||
for (const KeyValue<int, String> &E : valid_messages) {
|
||||
set_message(E.key, E.value, MSG_OK);
|
||||
}
|
||||
|
||||
valid = true;
|
||||
update_callback.callv(Array());
|
||||
|
||||
if (accept_button) {
|
||||
accept_button->set_disabled(!valid);
|
||||
}
|
||||
pending_update = false;
|
||||
}
|
||||
|
||||
void EditorValidationPanel::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
theme_cache.valid_color = get_theme_color(SNAME("success_color"), EditorStringName(Editor));
|
||||
theme_cache.warning_color = get_theme_color(SNAME("warning_color"), EditorStringName(Editor));
|
||||
theme_cache.error_color = get_theme_color(SNAME("error_color"), EditorStringName(Editor));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorValidationPanel::add_line(int p_id, const String &p_valid_message) {
|
||||
ERR_FAIL_COND(valid_messages.has(p_id));
|
||||
|
||||
Label *label = memnew(Label);
|
||||
message_container->add_child(label);
|
||||
label->set_custom_minimum_size(Size2(200 * EDSCALE, 0));
|
||||
label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
|
||||
label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
|
||||
|
||||
valid_messages[p_id] = p_valid_message;
|
||||
labels[p_id] = label;
|
||||
}
|
||||
|
||||
void EditorValidationPanel::set_accept_button(Button *p_button) {
|
||||
accept_button = p_button;
|
||||
}
|
||||
|
||||
void EditorValidationPanel::set_update_callback(const Callable &p_callback) {
|
||||
update_callback = p_callback;
|
||||
}
|
||||
|
||||
void EditorValidationPanel::update() {
|
||||
ERR_FAIL_COND(!update_callback.is_valid());
|
||||
|
||||
if (pending_update) {
|
||||
return;
|
||||
}
|
||||
pending_update = true;
|
||||
callable_mp(this, &EditorValidationPanel::_update).call_deferred();
|
||||
}
|
||||
|
||||
void EditorValidationPanel::set_message(int p_id, const String &p_text, MessageType p_type, bool p_auto_prefix) {
|
||||
ERR_FAIL_COND(!valid_messages.has(p_id));
|
||||
|
||||
Label *label = labels[p_id];
|
||||
if (p_text.is_empty()) {
|
||||
label->hide();
|
||||
return;
|
||||
}
|
||||
label->show();
|
||||
|
||||
if (p_auto_prefix) {
|
||||
label->set_text(String(U"• ") + p_text);
|
||||
} else {
|
||||
label->set_text(p_text);
|
||||
}
|
||||
|
||||
switch (p_type) {
|
||||
case MSG_OK:
|
||||
label->add_theme_color_override(SceneStringName(font_color), theme_cache.valid_color);
|
||||
break;
|
||||
case MSG_WARNING:
|
||||
label->add_theme_color_override(SceneStringName(font_color), theme_cache.warning_color);
|
||||
break;
|
||||
case MSG_ERROR:
|
||||
label->add_theme_color_override(SceneStringName(font_color), theme_cache.error_color);
|
||||
valid = false;
|
||||
break;
|
||||
case MSG_INFO:
|
||||
label->remove_theme_color_override(SceneStringName(font_color));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorValidationPanel::is_valid() const {
|
||||
return valid;
|
||||
}
|
||||
|
||||
EditorValidationPanel::EditorValidationPanel() {
|
||||
set_v_size_flags(SIZE_EXPAND_FILL);
|
||||
|
||||
message_container = memnew(VBoxContainer);
|
||||
add_child(message_container);
|
||||
}
|
||||
88
engine/editor/gui/editor_validation_panel.h
Normal file
88
engine/editor/gui/editor_validation_panel.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/**************************************************************************/
|
||||
/* editor_validation_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 EDITOR_VALIDATION_PANEL_H
|
||||
#define EDITOR_VALIDATION_PANEL_H
|
||||
|
||||
#include "scene/gui/panel_container.h"
|
||||
|
||||
class Button;
|
||||
class Label;
|
||||
class VBoxContainer;
|
||||
|
||||
class EditorValidationPanel : public PanelContainer {
|
||||
GDCLASS(EditorValidationPanel, PanelContainer);
|
||||
|
||||
public:
|
||||
enum MessageType {
|
||||
MSG_OK,
|
||||
MSG_WARNING,
|
||||
MSG_ERROR,
|
||||
MSG_INFO,
|
||||
};
|
||||
|
||||
static const int MSG_ID_DEFAULT = 0; // Avoids hard-coding ID in dialogs with single-line validation.
|
||||
|
||||
private:
|
||||
VBoxContainer *message_container = nullptr;
|
||||
|
||||
HashMap<int, String> valid_messages;
|
||||
HashMap<int, Label *> labels;
|
||||
|
||||
bool valid = false;
|
||||
bool pending_update = false;
|
||||
|
||||
struct ThemeCache {
|
||||
Color valid_color;
|
||||
Color warning_color;
|
||||
Color error_color;
|
||||
} theme_cache;
|
||||
|
||||
void _update();
|
||||
|
||||
Callable update_callback;
|
||||
Button *accept_button = nullptr;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
void add_line(int p_id, const String &p_valid_message = "");
|
||||
void set_accept_button(Button *p_button);
|
||||
void set_update_callback(const Callable &p_callback);
|
||||
|
||||
void update();
|
||||
void set_message(int p_id, const String &p_text, MessageType p_type, bool p_auto_prefix = true);
|
||||
bool is_valid() const;
|
||||
|
||||
EditorValidationPanel();
|
||||
};
|
||||
|
||||
#endif // EDITOR_VALIDATION_PANEL_H
|
||||
85
engine/editor/gui/editor_version_button.cpp
Normal file
85
engine/editor/gui/editor_version_button.cpp
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/**************************************************************************/
|
||||
/* editor_version_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 "editor_version_button.h"
|
||||
|
||||
#include "core/os/time.h"
|
||||
#include "core/version.h"
|
||||
|
||||
String _get_version_string(EditorVersionButton::VersionFormat p_format) {
|
||||
String main;
|
||||
switch (p_format) {
|
||||
case EditorVersionButton::FORMAT_BASIC: {
|
||||
return VERSION_FULL_CONFIG;
|
||||
} break;
|
||||
case EditorVersionButton::FORMAT_WITH_BUILD: {
|
||||
main = "v" VERSION_FULL_BUILD;
|
||||
} break;
|
||||
case EditorVersionButton::FORMAT_WITH_NAME_AND_BUILD: {
|
||||
main = VERSION_FULL_NAME;
|
||||
} break;
|
||||
default: {
|
||||
ERR_FAIL_V_MSG(VERSION_FULL_NAME, "Unexpected format: " + itos(p_format));
|
||||
} break;
|
||||
}
|
||||
|
||||
String hash = VERSION_HASH;
|
||||
if (!hash.is_empty()) {
|
||||
hash = vformat(" [%s]", hash.left(9));
|
||||
}
|
||||
return main + hash;
|
||||
}
|
||||
|
||||
void EditorVersionButton::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_POSTINITIALIZE: {
|
||||
// This can't be done in the constructor because theme cache is not ready yet.
|
||||
set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
|
||||
set_text(_get_version_string(format));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorVersionButton::pressed() {
|
||||
DisplayServer::get_singleton()->clipboard_set(_get_version_string(FORMAT_WITH_BUILD));
|
||||
}
|
||||
|
||||
EditorVersionButton::EditorVersionButton(VersionFormat p_format) {
|
||||
format = p_format;
|
||||
set_underline_mode(LinkButton::UNDERLINE_MODE_ON_HOVER);
|
||||
|
||||
String build_date;
|
||||
if (VERSION_TIMESTAMP > 0) {
|
||||
build_date = Time::get_singleton()->get_datetime_string_from_unix_time(VERSION_TIMESTAMP, true) + " UTC";
|
||||
} else {
|
||||
build_date = TTR("(unknown)");
|
||||
}
|
||||
set_tooltip_text(vformat(TTR("Git commit date: %s\nClick to copy the version information."), build_date));
|
||||
}
|
||||
61
engine/editor/gui/editor_version_button.h
Normal file
61
engine/editor/gui/editor_version_button.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/**************************************************************************/
|
||||
/* editor_version_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 EDITOR_VERSION_BUTTON_H
|
||||
#define EDITOR_VERSION_BUTTON_H
|
||||
|
||||
#include "scene/gui/link_button.h"
|
||||
|
||||
class EditorVersionButton : public LinkButton {
|
||||
GDCLASS(EditorVersionButton, LinkButton);
|
||||
|
||||
public:
|
||||
enum VersionFormat {
|
||||
// 4.3.2.stable
|
||||
FORMAT_BASIC,
|
||||
// v4.3.2.stable.mono [HASH]
|
||||
FORMAT_WITH_BUILD,
|
||||
// Godot Engine v4.3.2.stable.mono.official [HASH]
|
||||
FORMAT_WITH_NAME_AND_BUILD,
|
||||
};
|
||||
|
||||
private:
|
||||
VersionFormat format = FORMAT_WITH_NAME_AND_BUILD;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
||||
virtual void pressed() override;
|
||||
|
||||
public:
|
||||
EditorVersionButton(VersionFormat p_format);
|
||||
};
|
||||
|
||||
#endif // EDITOR_VERSION_BUTTON_H
|
||||
229
engine/editor/gui/editor_zoom_widget.cpp
Normal file
229
engine/editor/gui/editor_zoom_widget.cpp
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
/**************************************************************************/
|
||||
/* editor_zoom_widget.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 "editor_zoom_widget.h"
|
||||
|
||||
#include "core/os/keyboard.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/themes/editor_scale.h"
|
||||
|
||||
void EditorZoomWidget::_update_zoom_label() {
|
||||
String zoom_text;
|
||||
// The zoom level displayed is relative to the editor scale
|
||||
// (like in most image editors). Its lower bound is clamped to 1 as some people
|
||||
// lower the editor scale to increase the available real estate,
|
||||
// even if their display doesn't have a particularly low DPI.
|
||||
if (zoom >= 10) {
|
||||
zoom_text = TS->format_number(rtos(Math::round((zoom / MAX(1, EDSCALE)) * 100)));
|
||||
} else {
|
||||
// 2 decimal places if the zoom is below 10%, 1 decimal place if it's below 1000%.
|
||||
zoom_text = TS->format_number(rtos(Math::snapped((zoom / MAX(1, EDSCALE)) * 100, (zoom >= 0.1) ? 0.1 : 0.01)));
|
||||
}
|
||||
zoom_text += " " + TS->percent_sign();
|
||||
zoom_reset->set_text(zoom_text);
|
||||
}
|
||||
|
||||
void EditorZoomWidget::_button_zoom_minus() {
|
||||
set_zoom_by_increments(-6, Input::get_singleton()->is_key_pressed(Key::ALT));
|
||||
emit_signal(SNAME("zoom_changed"), zoom);
|
||||
}
|
||||
|
||||
void EditorZoomWidget::_button_zoom_reset() {
|
||||
set_zoom(1.0 * MAX(1, EDSCALE));
|
||||
emit_signal(SNAME("zoom_changed"), zoom);
|
||||
}
|
||||
|
||||
void EditorZoomWidget::_button_zoom_plus() {
|
||||
set_zoom_by_increments(6, Input::get_singleton()->is_key_pressed(Key::ALT));
|
||||
emit_signal(SNAME("zoom_changed"), zoom);
|
||||
}
|
||||
|
||||
float EditorZoomWidget::get_zoom() {
|
||||
return zoom;
|
||||
}
|
||||
|
||||
void EditorZoomWidget::set_zoom(float p_zoom) {
|
||||
float new_zoom = CLAMP(p_zoom, min_zoom, max_zoom);
|
||||
if (zoom != new_zoom) {
|
||||
zoom = new_zoom;
|
||||
_update_zoom_label();
|
||||
}
|
||||
}
|
||||
|
||||
float EditorZoomWidget::get_min_zoom() {
|
||||
return min_zoom;
|
||||
}
|
||||
|
||||
float EditorZoomWidget::get_max_zoom() {
|
||||
return max_zoom;
|
||||
}
|
||||
|
||||
void EditorZoomWidget::setup_zoom_limits(float p_min, float p_max) {
|
||||
ERR_FAIL_COND(p_min < 0 || p_min > p_max);
|
||||
|
||||
min_zoom = p_min;
|
||||
max_zoom = p_max;
|
||||
|
||||
if (zoom > max_zoom) {
|
||||
set_zoom(max_zoom);
|
||||
emit_signal(SNAME("zoom_changed"), zoom);
|
||||
} else if (zoom < min_zoom) {
|
||||
set_zoom(min_zoom);
|
||||
emit_signal(SNAME("zoom_changed"), zoom);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorZoomWidget::set_zoom_by_increments(int p_increment_count, bool p_integer_only) {
|
||||
// Remove editor scale from the index computation.
|
||||
const float zoom_noscale = zoom / MAX(1, EDSCALE);
|
||||
|
||||
if (p_integer_only) {
|
||||
// Only visit integer scaling factors above 100%, and fractions with an integer denominator below 100%
|
||||
// (1/2 = 50%, 1/3 = 33.33%, 1/4 = 25%, …).
|
||||
// This is useful when working on pixel art projects to avoid distortion.
|
||||
// This algorithm is designed to handle fractional start zoom values correctly
|
||||
// (e.g. 190% will zoom up to 200% and down to 100%).
|
||||
if (zoom_noscale + p_increment_count * 0.001 >= 1.0 - CMP_EPSILON) {
|
||||
// New zoom is certain to be above 100%.
|
||||
if (p_increment_count >= 1) {
|
||||
// Zooming.
|
||||
set_zoom(Math::floor(zoom_noscale + p_increment_count) * MAX(1, EDSCALE));
|
||||
} else {
|
||||
// Dezooming.
|
||||
set_zoom(Math::ceil(zoom_noscale + p_increment_count) * MAX(1, EDSCALE));
|
||||
}
|
||||
} else {
|
||||
if (p_increment_count >= 1) {
|
||||
// Zooming in. Convert the current zoom into a denominator.
|
||||
float new_zoom = 1.0 / Math::ceil(1.0 / zoom_noscale - p_increment_count);
|
||||
if (Math::is_equal_approx(zoom_noscale, new_zoom)) {
|
||||
// New zoom is identical to the old zoom, so try again.
|
||||
// This can happen due to floating-point precision issues.
|
||||
new_zoom = 1.0 / Math::ceil(1.0 / zoom_noscale - p_increment_count - 1);
|
||||
}
|
||||
set_zoom(new_zoom * MAX(1, EDSCALE));
|
||||
} else {
|
||||
// Zooming out. Convert the current zoom into a denominator.
|
||||
float new_zoom = 1.0 / Math::floor(1.0 / zoom_noscale - p_increment_count);
|
||||
if (Math::is_equal_approx(zoom_noscale, new_zoom)) {
|
||||
// New zoom is identical to the old zoom, so try again.
|
||||
// This can happen due to floating-point precision issues.
|
||||
new_zoom = 1.0 / Math::floor(1.0 / zoom_noscale - p_increment_count + 1);
|
||||
}
|
||||
set_zoom(new_zoom * MAX(1, EDSCALE));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (zoom < CMP_EPSILON || p_increment_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Zoom is calculated as pow(zoom_factor, zoom_step).
|
||||
// This ensures the zoom will always equal 100% when zoom_step is 0.
|
||||
float zoom_factor = EDITOR_GET("editors/2d/zoom_speed_factor");
|
||||
float current_zoom_step = Math::round(Math::log(zoom_noscale) / Math::log(zoom_factor));
|
||||
float new_zoom = Math::pow(zoom_factor, current_zoom_step + p_increment_count);
|
||||
|
||||
// Restore Editor scale transformation.
|
||||
new_zoom *= MAX(1, EDSCALE);
|
||||
|
||||
set_zoom(new_zoom);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorZoomWidget::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE:
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
zoom_minus->set_button_icon(get_editor_theme_icon(SNAME("ZoomLess")));
|
||||
zoom_plus->set_button_icon(get_editor_theme_icon(SNAME("ZoomMore")));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorZoomWidget::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_zoom", "zoom"), &EditorZoomWidget::set_zoom);
|
||||
ClassDB::bind_method(D_METHOD("get_zoom"), &EditorZoomWidget::get_zoom);
|
||||
ClassDB::bind_method(D_METHOD("set_zoom_by_increments", "increment", "integer_only"), &EditorZoomWidget::set_zoom_by_increments);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom"), "set_zoom", "get_zoom");
|
||||
|
||||
ADD_SIGNAL(MethodInfo("zoom_changed", PropertyInfo(Variant::FLOAT, "zoom")));
|
||||
}
|
||||
|
||||
void EditorZoomWidget::set_shortcut_context(Node *p_node) const {
|
||||
zoom_minus->set_shortcut_context(p_node);
|
||||
zoom_plus->set_shortcut_context(p_node);
|
||||
zoom_reset->set_shortcut_context(p_node);
|
||||
}
|
||||
|
||||
EditorZoomWidget::EditorZoomWidget() {
|
||||
// Zoom buttons
|
||||
zoom_minus = memnew(Button);
|
||||
zoom_minus->set_flat(true);
|
||||
zoom_minus->set_shortcut(ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_minus", TTRC("Zoom Out"), { int32_t(KeyModifierMask::CMD_OR_CTRL | Key::MINUS), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_SUBTRACT) }));
|
||||
zoom_minus->set_shortcut_context(this);
|
||||
zoom_minus->set_focus_mode(FOCUS_NONE);
|
||||
add_child(zoom_minus);
|
||||
zoom_minus->connect(SceneStringName(pressed), callable_mp(this, &EditorZoomWidget::_button_zoom_minus));
|
||||
|
||||
zoom_reset = memnew(Button);
|
||||
zoom_reset->set_flat(true);
|
||||
|
||||
Ref<StyleBoxEmpty> empty_stylebox = memnew(StyleBoxEmpty);
|
||||
zoom_reset->add_theme_style_override(CoreStringName(normal), empty_stylebox);
|
||||
zoom_reset->add_theme_style_override(SceneStringName(hover), empty_stylebox);
|
||||
zoom_reset->add_theme_style_override("focus", empty_stylebox);
|
||||
zoom_reset->add_theme_style_override(SceneStringName(pressed), empty_stylebox);
|
||||
zoom_reset->add_theme_constant_override("outline_size", Math::ceil(2 * EDSCALE));
|
||||
zoom_reset->add_theme_color_override("font_outline_color", Color(0, 0, 0));
|
||||
zoom_reset->add_theme_color_override(SceneStringName(font_color), Color(1, 1, 1));
|
||||
|
||||
zoom_reset->set_shortcut(ED_GET_SHORTCUT("canvas_item_editor/zoom_100_percent"));
|
||||
zoom_reset->set_shortcut_context(this);
|
||||
zoom_reset->set_focus_mode(FOCUS_NONE);
|
||||
zoom_reset->set_text_alignment(HORIZONTAL_ALIGNMENT_CENTER);
|
||||
// Prevent the button's size from changing when the text size changes
|
||||
zoom_reset->set_custom_minimum_size(Size2(56 * EDSCALE, 0));
|
||||
add_child(zoom_reset);
|
||||
zoom_reset->connect(SceneStringName(pressed), callable_mp(this, &EditorZoomWidget::_button_zoom_reset));
|
||||
|
||||
zoom_plus = memnew(Button);
|
||||
zoom_plus->set_flat(true);
|
||||
zoom_plus->set_shortcut(ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_plus", TTRC("Zoom In"), { int32_t(KeyModifierMask::CMD_OR_CTRL | Key::EQUAL), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_ADD) }));
|
||||
zoom_plus->set_shortcut_context(this);
|
||||
zoom_plus->set_focus_mode(FOCUS_NONE);
|
||||
add_child(zoom_plus);
|
||||
zoom_plus->connect(SceneStringName(pressed), callable_mp(this, &EditorZoomWidget::_button_zoom_plus));
|
||||
|
||||
_update_zoom_label();
|
||||
|
||||
add_theme_constant_override("separation", 0);
|
||||
}
|
||||
71
engine/editor/gui/editor_zoom_widget.h
Normal file
71
engine/editor/gui/editor_zoom_widget.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/**************************************************************************/
|
||||
/* editor_zoom_widget.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 EDITOR_ZOOM_WIDGET_H
|
||||
#define EDITOR_ZOOM_WIDGET_H
|
||||
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
|
||||
class EditorZoomWidget : public HBoxContainer {
|
||||
GDCLASS(EditorZoomWidget, HBoxContainer);
|
||||
|
||||
Button *zoom_minus = nullptr;
|
||||
Button *zoom_reset = nullptr;
|
||||
Button *zoom_plus = nullptr;
|
||||
|
||||
float zoom = 1.0;
|
||||
float min_zoom = 1.0 / 128;
|
||||
float max_zoom = 128.0;
|
||||
void _update_zoom_label();
|
||||
void _button_zoom_minus();
|
||||
void _button_zoom_reset();
|
||||
void _button_zoom_plus();
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
EditorZoomWidget();
|
||||
|
||||
float get_zoom();
|
||||
void set_zoom(float p_zoom);
|
||||
void set_zoom_by_increments(int p_increment_count, bool p_integer_only = false);
|
||||
|
||||
float get_min_zoom();
|
||||
float get_max_zoom();
|
||||
// It's best to setup simultaneously, so min < max can be checked easily.
|
||||
void setup_zoom_limits(float p_min, float p_max);
|
||||
// Sets the shortcut context for the zoom buttons. By default their context is this EditorZoomWidget control.
|
||||
void set_shortcut_context(Node *p_node) const;
|
||||
};
|
||||
|
||||
#endif // EDITOR_ZOOM_WIDGET_H
|
||||
2505
engine/editor/gui/scene_tree_editor.cpp
Normal file
2505
engine/editor/gui/scene_tree_editor.cpp
Normal file
File diff suppressed because it is too large
Load diff
297
engine/editor/gui/scene_tree_editor.h
Normal file
297
engine/editor/gui/scene_tree_editor.h
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
/**************************************************************************/
|
||||
/* scene_tree_editor.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 SCENE_TREE_EDITOR_H
|
||||
#define SCENE_TREE_EDITOR_H
|
||||
|
||||
#include "scene/gui/check_box.h"
|
||||
#include "scene/gui/check_button.h"
|
||||
#include "scene/gui/dialogs.h"
|
||||
#include "scene/gui/tree.h"
|
||||
|
||||
class EditorSelection;
|
||||
class TextureRect;
|
||||
class Timer;
|
||||
|
||||
class SceneTreeEditor : public Control {
|
||||
GDCLASS(SceneTreeEditor, Control);
|
||||
|
||||
EditorSelection *editor_selection = nullptr;
|
||||
|
||||
enum SceneTreeEditorButton {
|
||||
BUTTON_SUBSCENE = 0,
|
||||
BUTTON_VISIBILITY = 1,
|
||||
BUTTON_SCRIPT = 2,
|
||||
BUTTON_LOCK = 3,
|
||||
BUTTON_GROUP = 4,
|
||||
BUTTON_WARNING = 5,
|
||||
BUTTON_SIGNALS = 6,
|
||||
BUTTON_GROUPS = 7,
|
||||
BUTTON_PIN = 8,
|
||||
BUTTON_UNIQUE = 9,
|
||||
};
|
||||
|
||||
struct CachedNode {
|
||||
Node *node = nullptr;
|
||||
TreeItem *item = nullptr;
|
||||
int index = -1;
|
||||
bool dirty = true;
|
||||
bool has_moved_children = false;
|
||||
bool removed = false;
|
||||
|
||||
// Store the iterator for faster removal. This is safe as
|
||||
// HashMap never moves elements.
|
||||
HashMap<Node *, CachedNode>::Iterator cache_iterator;
|
||||
// This is safe because it gets compared to a uint8_t.
|
||||
uint16_t delete_serial = UINT16_MAX;
|
||||
|
||||
// To know whether to update children or not.
|
||||
bool can_process = false;
|
||||
|
||||
CachedNode() = delete; // Always an error.
|
||||
CachedNode(Node *p_node, TreeItem *p_item) :
|
||||
node(p_node), item(p_item) {}
|
||||
};
|
||||
|
||||
struct NodeCache {
|
||||
~NodeCache() {
|
||||
clear();
|
||||
}
|
||||
|
||||
NodeCache(SceneTreeEditor *p_editor) :
|
||||
editor(p_editor) {}
|
||||
|
||||
HashMap<Node *, CachedNode>::Iterator add(Node *p_node, TreeItem *p_item);
|
||||
HashMap<Node *, CachedNode>::Iterator get(Node *p_node, bool p_deleted_ok = true);
|
||||
bool has(Node *p_node);
|
||||
void remove(Node *p_node, bool p_recursive = false);
|
||||
void mark_dirty(Node *p_node, bool p_parents = true);
|
||||
void mark_children_dirty(Node *p_node, bool p_recursive = false);
|
||||
|
||||
void delete_pending();
|
||||
void clear();
|
||||
|
||||
SceneTreeEditor *editor;
|
||||
HashMap<Node *, CachedNode> cache;
|
||||
HashSet<CachedNode *> to_delete;
|
||||
Node *current_scene_node = nullptr;
|
||||
Node *current_pinned_node = nullptr;
|
||||
bool current_has_pin = false;
|
||||
bool force_update = false;
|
||||
uint8_t delete_serial = 0;
|
||||
};
|
||||
|
||||
NodeCache node_cache;
|
||||
|
||||
Tree *tree = nullptr;
|
||||
Node *selected = nullptr;
|
||||
ObjectID instance_node;
|
||||
|
||||
String filter;
|
||||
String filter_term_warning;
|
||||
bool show_all_nodes = false;
|
||||
|
||||
AcceptDialog *error = nullptr;
|
||||
AcceptDialog *warning = nullptr;
|
||||
|
||||
ConfirmationDialog *revoke_dialog = nullptr;
|
||||
Label *revoke_dialog_label = nullptr;
|
||||
CheckBox *ask_before_revoke_checkbox = nullptr;
|
||||
Node *revoke_node = nullptr;
|
||||
|
||||
bool auto_expand_selected = true;
|
||||
bool hide_filtered_out_parents = false;
|
||||
bool connect_to_script_mode = false;
|
||||
bool connecting_signal = false;
|
||||
bool update_when_invisible = true;
|
||||
|
||||
int blocked;
|
||||
|
||||
void _compute_hash(Node *p_node, uint64_t &hash);
|
||||
void _reset();
|
||||
PackedStringArray _get_node_configuration_warnings(Node *p_node);
|
||||
|
||||
void _update_node_path(Node *p_node, bool p_recursive = true);
|
||||
void _update_node_subtree(Node *p_node, TreeItem *p_parent, bool p_force = false);
|
||||
void _update_node(Node *p_node, TreeItem *p_item, bool p_part_of_subscene);
|
||||
void _update_if_clean();
|
||||
|
||||
void _test_update_tree();
|
||||
bool _update_filter(TreeItem *p_parent = nullptr, bool p_scroll_to_selected = false);
|
||||
bool _item_matches_all_terms(TreeItem *p_item, const PackedStringArray &p_terms);
|
||||
void _tree_changed();
|
||||
void _tree_process_mode_changed();
|
||||
|
||||
void _move_node_children(HashMap<Node *, CachedNode>::Iterator &p_I);
|
||||
void _move_node_item(TreeItem *p_parent, HashMap<Node *, CachedNode>::Iterator &p_I);
|
||||
|
||||
void _node_child_order_changed(Node *p_node);
|
||||
void _node_editor_state_changed(Node *p_node);
|
||||
void _node_added(Node *p_node);
|
||||
void _node_removed(Node *p_node);
|
||||
void _node_renamed(Node *p_node);
|
||||
|
||||
TreeItem *_find(TreeItem *p_node, const NodePath &p_path);
|
||||
void _notification(int p_what);
|
||||
void _selected_changed();
|
||||
void _deselect_items();
|
||||
|
||||
void _cell_collapsed(Object *p_obj);
|
||||
|
||||
uint64_t last_hash;
|
||||
|
||||
bool can_rename;
|
||||
bool can_open_instance;
|
||||
bool updating_tree = false;
|
||||
bool show_enabled_subscene = false;
|
||||
bool is_scene_tree_dock = false;
|
||||
|
||||
void _edited();
|
||||
void _renamed(TreeItem *p_item, TreeItem *p_batch_item, Node *p_node = nullptr);
|
||||
|
||||
HashSet<Node *> marked;
|
||||
bool marked_selectable = false;
|
||||
bool marked_children_selectable = false;
|
||||
bool display_foreign = false;
|
||||
bool tree_dirty = true;
|
||||
bool pending_test_update = false;
|
||||
Timer *update_node_tooltip_delay = nullptr;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
void _cell_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
|
||||
void _toggle_visible(Node *p_node);
|
||||
void _cell_multi_selected(Object *p_object, int p_cell, bool p_selected);
|
||||
void _update_selection(TreeItem *item);
|
||||
void _node_script_changed(Node *p_node);
|
||||
void _node_visibility_changed(Node *p_node);
|
||||
void _update_visibility_color(Node *p_node, TreeItem *p_item);
|
||||
void _set_item_custom_color(TreeItem *p_item, Color p_color);
|
||||
void _update_node_tooltip(Node *p_node, TreeItem *p_item);
|
||||
void _queue_update_node_tooltip(Node *p_node, TreeItem *p_item);
|
||||
void _tree_scroll_to_item(ObjectID p_item_id);
|
||||
|
||||
void _selection_changed();
|
||||
Node *get_scene_node() const;
|
||||
|
||||
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
|
||||
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
|
||||
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
|
||||
|
||||
void _empty_clicked(const Vector2 &p_pos, MouseButton p_button);
|
||||
void _rmb_select(const Vector2 &p_pos, MouseButton p_button = MouseButton::RIGHT);
|
||||
|
||||
void _warning_changed(Node *p_for_node);
|
||||
void _update_marking_list(const HashSet<Node *> &p_marked);
|
||||
|
||||
Timer *update_timer = nullptr;
|
||||
|
||||
List<StringName> *script_types;
|
||||
bool _is_script_type(const StringName &p_type) const;
|
||||
|
||||
Vector<StringName> valid_types;
|
||||
|
||||
void _update_ask_before_revoking_unique_name();
|
||||
void _revoke_unique_name();
|
||||
|
||||
public:
|
||||
// Public for use with callable_mp.
|
||||
void _update_tree(bool p_scroll_to_selected = false);
|
||||
|
||||
void rename_node(Node *p_node, const String &p_name, TreeItem *p_item = nullptr);
|
||||
|
||||
void set_filter(const String &p_filter);
|
||||
String get_filter() const;
|
||||
String get_filter_term_warning();
|
||||
void set_show_all_nodes(bool p_show_all_nodes);
|
||||
|
||||
void set_as_scene_tree_dock();
|
||||
void set_display_foreign_nodes(bool p_display);
|
||||
|
||||
void set_marked(const HashSet<Node *> &p_marked, bool p_selectable = true, bool p_children_selectable = true);
|
||||
void set_marked(Node *p_marked, bool p_selectable = true, bool p_children_selectable = true);
|
||||
void set_selected(Node *p_node, bool p_emit_selected = true);
|
||||
Node *get_selected();
|
||||
void set_can_rename(bool p_can_rename) { can_rename = p_can_rename; }
|
||||
void set_editor_selection(EditorSelection *p_selection);
|
||||
|
||||
void set_show_enabled_subscene(bool p_show) { show_enabled_subscene = p_show; }
|
||||
void set_valid_types(const Vector<StringName> &p_valid);
|
||||
|
||||
inline void update_tree() { _update_tree(); }
|
||||
|
||||
void set_auto_expand_selected(bool p_auto, bool p_update_settings);
|
||||
void set_hide_filtered_out_parents(bool p_hide, bool p_update_settings);
|
||||
void set_connect_to_script_mode(bool p_enable);
|
||||
void set_connecting_signal(bool p_enable);
|
||||
void set_update_when_invisible(bool p_enable);
|
||||
|
||||
Tree *get_scene_tree() { return tree; }
|
||||
|
||||
void update_warning();
|
||||
|
||||
SceneTreeEditor(bool p_label = true, bool p_can_rename = false, bool p_can_open_instance = false);
|
||||
~SceneTreeEditor();
|
||||
};
|
||||
|
||||
class SceneTreeDialog : public ConfirmationDialog {
|
||||
GDCLASS(SceneTreeDialog, ConfirmationDialog);
|
||||
|
||||
VBoxContainer *content = nullptr;
|
||||
SceneTreeEditor *tree = nullptr;
|
||||
LineEdit *filter = nullptr;
|
||||
CheckButton *show_all_nodes = nullptr;
|
||||
LocalVector<TextureRect *> valid_type_icons;
|
||||
HBoxContainer *allowed_types_hbox = nullptr;
|
||||
|
||||
void _select();
|
||||
void _cancel();
|
||||
void _selected_changed();
|
||||
void _filter_changed(const String &p_filter);
|
||||
void _on_filter_gui_input(const Ref<InputEvent> &p_event);
|
||||
void _show_all_nodes_changed(bool p_button_pressed);
|
||||
|
||||
protected:
|
||||
void _update_valid_type_icons();
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
void popup_scenetree_dialog(Node *p_selected_node = nullptr, Node *p_marked_node = nullptr, bool p_marked_node_selectable = true, bool p_marked_node_children_selectable = true);
|
||||
void set_valid_types(const Vector<StringName> &p_valid);
|
||||
|
||||
SceneTreeEditor *get_scene_tree() { return tree; }
|
||||
LineEdit *get_filter_line_edit() { return filter; }
|
||||
|
||||
SceneTreeDialog();
|
||||
~SceneTreeDialog();
|
||||
};
|
||||
|
||||
#endif // SCENE_TREE_EDITOR_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue