feat: godot-engine-source-4.3-stable
This commit is contained in:
parent
c59a7dcade
commit
7125d019b5
11149 changed files with 5070401 additions and 0 deletions
5
engine/editor/gui/SCsub
Normal file
5
engine/editor/gui/SCsub
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.editor_sources, "*.cpp")
|
||||
299
engine/editor/gui/editor_bottom_panel.cpp
Normal file
299
engine/editor/gui/editor_bottom_panel.cpp
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
/**************************************************************************/
|
||||
/* 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 "core/os/time.h"
|
||||
#include "core/version.h"
|
||||
#include "editor/debugger/editor_debugger_node.h"
|
||||
#include "editor/editor_about.h"
|
||||
#include "editor/editor_command_palette.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "editor/engine_update_label.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/link_button.h"
|
||||
|
||||
// The metadata key used to store and retrieve the version text to copy to the clipboard.
|
||||
static const String META_TEXT_TO_COPY = "text_to_copy";
|
||||
|
||||
void EditorBottomPanel::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
expand_button->set_icon(get_editor_theme_icon(SNAME("ExpandBottomDock")));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBottomPanel::_switch_by_control(bool p_visible, Control *p_control) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items[i].control == p_control) {
|
||||
_switch_to_item(p_visible, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx) {
|
||||
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) {
|
||||
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);
|
||||
if (expand_button->is_pressed()) {
|
||||
EditorNode::get_top_split()->hide();
|
||||
}
|
||||
expand_button->show();
|
||||
} 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);
|
||||
expand_button->hide();
|
||||
if (expand_button->is_pressed()) {
|
||||
EditorNode::get_top_split()->show();
|
||||
}
|
||||
}
|
||||
|
||||
last_opened_control = items[p_idx].control;
|
||||
}
|
||||
|
||||
void EditorBottomPanel::_expand_button_toggled(bool p_pressed) {
|
||||
EditorNode::get_top_split()->set_visible(!p_pressed);
|
||||
}
|
||||
|
||||
void EditorBottomPanel::_version_button_pressed() {
|
||||
DisplayServer::get_singleton()->clipboard_set(version_btn->get_meta(META_TEXT_TO_COPY));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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("toggled", callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item));
|
||||
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);
|
||||
} 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) {
|
||||
_switch_by_control(p_visible, p_item);
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
// Open the first panel in the list if no panel was opened this session.
|
||||
_switch_to_item(true, 0);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
button_hbox = memnew(HBoxContainer);
|
||||
button_hbox->set_h_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
bottom_hbox->add_child(button_hbox);
|
||||
|
||||
editor_toaster = memnew(EditorToaster);
|
||||
bottom_hbox->add_child(editor_toaster);
|
||||
|
||||
version_btn = memnew(LinkButton);
|
||||
version_btn->set_text(VERSION_FULL_CONFIG);
|
||||
String hash = String(VERSION_HASH);
|
||||
if (hash.length() != 0) {
|
||||
hash = " " + vformat("[%s]", hash.left(9));
|
||||
}
|
||||
// Set the text to copy in metadata as it slightly differs from the button's text.
|
||||
version_btn->set_meta(META_TEXT_TO_COPY, "v" VERSION_FULL_BUILD + hash);
|
||||
// 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_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)");
|
||||
}
|
||||
version_btn->set_tooltip_text(vformat(TTR("Git commit date: %s\nClick to copy the version information."), build_date));
|
||||
version_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_version_button_pressed));
|
||||
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);
|
||||
|
||||
expand_button = memnew(Button);
|
||||
bottom_hbox->add_child(expand_button);
|
||||
expand_button->hide();
|
||||
expand_button->set_flat(false);
|
||||
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", TTR("Expand Bottom Panel"), KeyModifierMask::SHIFT | Key::F12));
|
||||
expand_button->connect("toggled", callable_mp(this, &EditorBottomPanel::_expand_button_toggled));
|
||||
}
|
||||
86
engine/editor/gui/editor_bottom_panel.h
Normal file
86
engine/editor/gui/editor_bottom_panel.h
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/**************************************************************************/
|
||||
/* 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 LinkButton;
|
||||
class VBoxContainer;
|
||||
|
||||
class EditorBottomPanel : public PanelContainer {
|
||||
GDCLASS(EditorBottomPanel, PanelContainer);
|
||||
|
||||
struct BottomPanelItem {
|
||||
String name;
|
||||
Control *control = nullptr;
|
||||
Button *button = nullptr;
|
||||
};
|
||||
|
||||
Vector<BottomPanelItem> items;
|
||||
|
||||
VBoxContainer *item_vbox = nullptr;
|
||||
HBoxContainer *bottom_hbox = nullptr;
|
||||
HBoxContainer *button_hbox = nullptr;
|
||||
EditorToaster *editor_toaster = nullptr;
|
||||
LinkButton *version_btn = nullptr;
|
||||
Button *expand_button = nullptr;
|
||||
Control *last_opened_control = nullptr;
|
||||
|
||||
void _switch_by_control(bool p_visible, Control *p_control);
|
||||
void _switch_to_item(bool p_visible, int p_idx);
|
||||
void _expand_button_toggled(bool p_pressed);
|
||||
void _version_button_pressed();
|
||||
|
||||
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);
|
||||
void move_item_to_end(Control *p_item);
|
||||
void hide_bottom_panel();
|
||||
void toggle_last_opened_bottom_panel();
|
||||
|
||||
EditorBottomPanel();
|
||||
};
|
||||
|
||||
#endif // EDITOR_BOTTOM_PANEL_H
|
||||
232
engine/editor/gui/editor_dir_dialog.cpp
Normal file
232
engine/editor/gui/editor_dir_dialog.cpp
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
/**************************************************************************/
|
||||
/* 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);
|
||||
makedialog->config(ti->get_metadata(0));
|
||||
makedialog->popup_centered();
|
||||
}
|
||||
|
||||
void EditorDirDialog::_make_dir_confirm(const String &p_path) {
|
||||
// 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 + "/";
|
||||
EditorFileSystem::get_singleton()->scan_changes(); // We created a dir, so rescan changes.
|
||||
}
|
||||
|
||||
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);
|
||||
vb->add_child(tree);
|
||||
tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
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);
|
||||
makedialog->connect("dir_created", callable_mp(this, &EditorDirDialog::_make_dir_confirm));
|
||||
}
|
||||
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);
|
||||
|
||||
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
|
||||
2358
engine/editor/gui/editor_file_dialog.cpp
Normal file
2358
engine/editor/gui/editor_file_dialog.cpp
Normal file
File diff suppressed because it is too large
Load diff
350
engine/editor/gui/editor_file_dialog.h
Normal file
350
engine/editor/gui/editor_file_dialog.h
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
/**************************************************************************/
|
||||
/* 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 "scene/gui/dialogs.h"
|
||||
#include "scene/property_list_helper.h"
|
||||
|
||||
class GridContainer;
|
||||
class DependencyRemoveDialog;
|
||||
class HSplitContainer;
|
||||
class ItemList;
|
||||
class OptionButton;
|
||||
class PopupMenu;
|
||||
class TextureRect;
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
ConfirmationDialog *makedialog = nullptr;
|
||||
LineEdit *makedirname = nullptr;
|
||||
|
||||
Button *makedir = nullptr;
|
||||
Access access = ACCESS_RESOURCES;
|
||||
|
||||
GridContainer *grid_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 *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;
|
||||
|
||||
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> reload;
|
||||
Ref<Texture2D> toggle_hidden;
|
||||
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> 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;
|
||||
|
||||
void update_dir();
|
||||
void update_file_name();
|
||||
void update_file_list();
|
||||
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 _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 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);
|
||||
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
|
||||
258
engine/editor/gui/editor_object_selector.cpp
Normal file
258
engine/editor/gui/editor_object_selector.cpp
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
/**************************************************************************/
|
||||
/* 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/multi_node_edit.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->take_mouse_focus();
|
||||
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;
|
||||
if (Object::cast_to<MultiNodeEdit>(obj)) {
|
||||
obj_icon = EditorNode::get_singleton()->get_class_icon(Object::cast_to<MultiNodeEdit>(obj)->get_edited_class_name());
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorObjectSelector::_bind_methods() {
|
||||
}
|
||||
|
||||
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."));
|
||||
}
|
||||
73
engine/editor/gui/editor_object_selector.h
Normal file
73
engine/editor/gui/editor_object_selector.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/**************************************************************************/
|
||||
/* 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);
|
||||
static void _bind_methods();
|
||||
|
||||
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
|
||||
453
engine/editor/gui/editor_run_bar.cpp
Normal file
453
engine/editor/gui/editor_run_bar.cpp
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
/**************************************************************************/
|
||||
/* 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/editor_command_palette.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/editor_quick_open.h"
|
||||
#include "editor/editor_run_native.h"
|
||||
#include "editor/editor_settings.h"
|
||||
#include "editor/editor_string_names.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/panel_container.h"
|
||||
|
||||
EditorRunBar *EditorRunBar::singleton = nullptr;
|
||||
|
||||
void EditorRunBar::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_POSTINITIALIZE: {
|
||||
_reset_play_buttons();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
_update_play_buttons();
|
||||
pause_button->set_icon(get_editor_theme_icon(SNAME("Pause")));
|
||||
stop_button->set_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_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() {
|
||||
play_button->set_pressed(false);
|
||||
play_button->set_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_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_icon(get_editor_theme_icon(SNAME("PlayCustom")));
|
||||
play_custom_scene_button->set_tooltip_text(TTR("Play a custom scene."));
|
||||
}
|
||||
|
||||
void EditorRunBar::_update_play_buttons() {
|
||||
_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_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)));
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::_quick_run_selected() {
|
||||
play_custom_scene(quick_run->get_selected());
|
||||
}
|
||||
|
||||
void EditorRunBar::_play_custom_pressed() {
|
||||
if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CUSTOM) {
|
||||
stop_playing();
|
||||
|
||||
quick_run->popup_dialog("PackedScene", true);
|
||||
quick_run->set_title(TTR("Quick Run Scene..."));
|
||||
play_custom_scene_button->set_pressed(false);
|
||||
} else {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::_play_current_pressed() {
|
||||
if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CURRENT) {
|
||||
play_current_scene();
|
||||
} else {
|
||||
// Reload if already running the current scene.
|
||||
play_current_scene(true);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::_run_scene(const String &p_scene_path) {
|
||||
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);
|
||||
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::play_main_scene(bool p_from_native) {
|
||||
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) {
|
||||
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);
|
||||
} else {
|
||||
_run_scene();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorRunBar::play_custom_scene(const String &p_custom) {
|
||||
stop_playing();
|
||||
|
||||
current_mode = RunMode::RUN_CUSTOM;
|
||||
_run_scene(p_custom);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
main_panel = memnew(PanelContainer);
|
||||
add_child(main_panel);
|
||||
|
||||
main_hbox = memnew(HBoxContainer);
|
||||
main_panel->add_child(main_hbox);
|
||||
|
||||
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(TTR("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", TTR("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(TTR("Pause the running project's execution for debugging."));
|
||||
pause_button->set_disabled(true);
|
||||
|
||||
ED_SHORTCUT("editor/pause_running_project", TTR("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(TTR("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", TTR("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));
|
||||
|
||||
play_scene_button = memnew(Button);
|
||||
main_hbox->add_child(play_scene_button);
|
||||
play_scene_button->set_theme_type_variation("RunBarButton");
|
||||
play_scene_button->set_toggle_mode(true);
|
||||
play_scene_button->set_focus_mode(Control::FOCUS_NONE);
|
||||
play_scene_button->set_tooltip_text(TTR("Run the currently edited scene."));
|
||||
play_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_current_pressed));
|
||||
|
||||
ED_SHORTCUT_AND_COMMAND("editor/run_current_scene", TTR("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"));
|
||||
|
||||
play_custom_scene_button = memnew(Button);
|
||||
main_hbox->add_child(play_custom_scene_button);
|
||||
play_custom_scene_button->set_theme_type_variation("RunBarButton");
|
||||
play_custom_scene_button->set_toggle_mode(true);
|
||||
play_custom_scene_button->set_focus_mode(Control::FOCUS_NONE);
|
||||
play_custom_scene_button->set_tooltip_text(TTR("Run a specific scene."));
|
||||
play_custom_scene_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::_play_custom_pressed));
|
||||
|
||||
ED_SHORTCUT_AND_COMMAND("editor/run_specific_scene", TTR("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("toggled", callable_mp(this, &EditorRunBar::_write_movie_toggled));
|
||||
|
||||
quick_run = memnew(EditorQuickOpen);
|
||||
add_child(quick_run);
|
||||
quick_run->connect("quick_open", callable_mp(this, &EditorRunBar::_quick_run_selected));
|
||||
}
|
||||
119
engine/editor/gui/editor_run_bar.h
Normal file
119
engine/editor/gui/editor_run_bar.h
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/**************************************************************************/
|
||||
/* 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 EditorQuickOpen;
|
||||
class PanelContainer;
|
||||
class HBoxContainer;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
EditorQuickOpen *quick_run = 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();
|
||||
|
||||
void _play_current_pressed();
|
||||
void _play_custom_pressed();
|
||||
|
||||
void _run_scene(const String &p_scene_path = "");
|
||||
void _run_native(const Ref<EditorExportPreset> &p_preset);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
static EditorRunBar *get_singleton() { return singleton; }
|
||||
|
||||
void play_main_scene(bool p_from_native = false);
|
||||
void play_current_scene(bool p_reload = false);
|
||||
void play_custom_scene(const String &p_custom);
|
||||
|
||||
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);
|
||||
|
||||
void set_movie_maker_enabled(bool p_enabled);
|
||||
bool is_movie_maker_enabled() const;
|
||||
|
||||
Button *get_pause_button() { return pause_button; }
|
||||
|
||||
HBoxContainer *get_buttons_container();
|
||||
|
||||
EditorRunBar();
|
||||
};
|
||||
|
||||
#endif // EDITOR_RUN_BAR_H
|
||||
442
engine/editor/gui/editor_scene_tabs.cpp
Normal file
442
engine/editor/gui/editor_scene_tabs.cpp
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
/**************************************************************************/
|
||||
/* 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_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/inspector_dock.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"
|
||||
|
||||
EditorSceneTabs *EditorSceneTabs::singleton = nullptr;
|
||||
|
||||
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_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;
|
||||
}
|
||||
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() {
|
||||
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_menu_option_if(EditorNode::FILE_SAVE_SCENE, no_root_node);
|
||||
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_scene_as"), EditorNode::FILE_SAVE_AS_SCENE);
|
||||
_disable_menu_option_if(EditorNode::FILE_SAVE_AS_SCENE, no_root_node);
|
||||
}
|
||||
|
||||
scene_tabs_context_menu->add_shortcut(ED_GET_SHORTCUT("editor/save_all_scenes"), EditorNode::FILE_SAVE_ALL_SCENES);
|
||||
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;
|
||||
}
|
||||
}
|
||||
_disable_menu_option_if(EditorNode::FILE_SAVE_ALL_SCENES, !can_save_all_scenes);
|
||||
|
||||
if (tab_id >= 0) {
|
||||
scene_tabs_context_menu->add_separator();
|
||||
scene_tabs_context_menu->add_item(TTR("Show in FileSystem"), EditorNode::FILE_SHOW_IN_FILESYSTEM);
|
||||
_disable_menu_option_if(EditorNode::FILE_SHOW_IN_FILESYSTEM, !ResourceLoader::exists(EditorNode::get_editor_data().get_scene_path(tab_id)));
|
||||
scene_tabs_context_menu->add_item(TTR("Play This Scene"), EditorNode::FILE_RUN_SCENE);
|
||||
_disable_menu_option_if(EditorNode::FILE_RUN_SCENE, 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(scene_tabs_context_menu->get_item_index(EditorNode::FILE_CLOSE), 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(scene_tabs_context_menu->get_item_index(EditorNode::FILE_OPEN_PREV), TTR("Undo Close Tab"));
|
||||
_disable_menu_option_if(EditorNode::FILE_OPEN_PREV, !EditorNode::get_singleton()->has_previous_scenes());
|
||||
scene_tabs_context_menu->add_item(TTR("Close Other Tabs"), EditorNode::FILE_CLOSE_OTHERS);
|
||||
_disable_menu_option_if(EditorNode::FILE_CLOSE_OTHERS, EditorNode::get_editor_data().get_edited_scene_count() <= 1);
|
||||
scene_tabs_context_menu->add_item(TTR("Close Tabs to the Right"), EditorNode::FILE_CLOSE_RIGHT);
|
||||
_disable_menu_option_if(EditorNode::FILE_CLOSE_RIGHT, EditorNode::get_editor_data().get_edited_scene_count() == tab_id + 1);
|
||||
scene_tabs_context_menu->add_item(TTR("Close All Tabs"), EditorNode::FILE_CLOSE_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorSceneTabs::_disable_menu_option_if(int p_option, bool p_condition) {
|
||||
if (p_condition) {
|
||||
scene_tabs_context_menu->set_item_disabled(scene_tabs_context_menu->get_item_index(p_option), true);
|
||||
}
|
||||
}
|
||||
|
||||
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_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);
|
||||
}
|
||||
98
engine/editor/gui/editor_scene_tabs.h
Normal file
98
engine/editor/gui/editor_scene_tabs.h
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/**************************************************************************/
|
||||
/* 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);
|
||||
|
||||
static EditorSceneTabs *singleton;
|
||||
|
||||
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;
|
||||
|
||||
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 _disable_menu_option_if(int p_option, bool p_condition);
|
||||
|
||||
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
|
||||
739
engine/editor/gui/editor_spin_slider.cpp
Normal file
739
engine/editor/gui/editor_spin_slider.cpp
Normal file
|
|
@ -0,0 +1,739 @@
|
|||
/**************************************************************************/
|
||||
/* 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"
|
||||
|
||||
bool EditorSpinSlider::is_text_field() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
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 (is_read_only()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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() && !is_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(is_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(is_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(is_read_only() ? SNAME("font_uneditable_color") : SceneStringName(font_color), SNAME("LineEdit"));
|
||||
Color lc = get_theme_color(is_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) {
|
||||
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 (get_step() == 1) {
|
||||
Ref<Texture2D> updown2 = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox"));
|
||||
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(get_global_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_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 (is_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 (value_input_closed_frame != Engine::get_singleton()->get_frames_drawn()) {
|
||||
if (value_input_popup) {
|
||||
value_input_popup->hide();
|
||||
}
|
||||
//tab was pressed
|
||||
} else {
|
||||
//enter, click, esc
|
||||
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->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() {
|
||||
_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);
|
||||
|
||||
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_SIGNAL(MethodInfo("grabbed"));
|
||||
ADD_SIGNAL(MethodInfo("ungrabbed"));
|
||||
ADD_SIGNAL(MethodInfo("value_focus_entered"));
|
||||
ADD_SIGNAL(MethodInfo("value_focus_exited"));
|
||||
}
|
||||
|
||||
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_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("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_as_top_level(true);
|
||||
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));
|
||||
}
|
||||
128
engine/editor/gui/editor_spin_slider.h
Normal file
128
engine/editor/gui/editor_spin_slider.h
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/**************************************************************************/
|
||||
/* 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;
|
||||
|
||||
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();
|
||||
|
||||
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:
|
||||
virtual bool is_text_field() const override;
|
||||
|
||||
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_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
|
||||
86
engine/editor/gui/editor_title_bar.cpp
Normal file
86
engine/editor/gui/editor_title_bar.cpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/**************************************************************************/
|
||||
/* 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()) {
|
||||
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
|
||||
574
engine/editor/gui/editor_toaster.cpp
Normal file
574
engine/editor/gui/editor_toaster.cpp
Normal file
|
|
@ -0,0 +1,574 @@
|
|||
/**************************************************************************/
|
||||
/* 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_ENTER_TREE:
|
||||
case NOTIFICATION_THEME_CHANGED: {
|
||||
if (vbox_container->is_visible()) {
|
||||
main_button->set_icon(get_editor_theme_icon(SNAME("Notification")));
|
||||
} else {
|
||||
main_button->set_icon(get_editor_theme_icon(SNAME("NotificationDisabled")));
|
||||
}
|
||||
disable_notifications_button->set_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)));
|
||||
|
||||
main_button->queue_redraw();
|
||||
disable_notifications_button->queue_redraw();
|
||||
} 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);
|
||||
} 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_icon(get_editor_theme_icon(SNAME("Notification")));
|
||||
} else {
|
||||
main_button->set_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));
|
||||
|
||||
// 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);
|
||||
|
||||
// Close button.
|
||||
if (p_time > 0.0) {
|
||||
Button *close_button = memnew(Button);
|
||||
close_button->set_flat(true);
|
||||
close_button->set_icon(get_editor_theme_icon(SNAME("Close")));
|
||||
close_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::close).bind(panel));
|
||||
close_button->connect(SceneStringName(theme_changed), callable_mp(this, &EditorToaster::_close_button_theme_changed).bind(close_button));
|
||||
hbox_container->add_child(close_button);
|
||||
}
|
||||
|
||||
toasts[panel].severity = p_severity;
|
||||
if (p_time > 0.0) {
|
||||
toasts[panel].duration = p_time;
|
||||
toasts[panel].remaining_time = p_time;
|
||||
} else {
|
||||
toasts[panel].duration = -1.0;
|
||||
}
|
||||
toasts[panel].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).bind(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);
|
||||
toasts[control].message = p_message;
|
||||
toasts[control].tooltip = p_tooltip;
|
||||
toasts[control].count = 1;
|
||||
toasts[control].message_label = label;
|
||||
toasts[control].message_count_label = count_label;
|
||||
} else {
|
||||
if (toasts[control].popped) {
|
||||
toasts[control].count += 1;
|
||||
} else {
|
||||
toasts[control].count = 1;
|
||||
}
|
||||
toasts[control].remaining_time = toasts[control].duration;
|
||||
toasts[control].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;
|
||||
}
|
||||
|
||||
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::_close_button_theme_changed(Control *p_close_button) {
|
||||
Button *close_button = Object::cast_to<Button>(p_close_button);
|
||||
if (close_button) {
|
||||
close_button->set_icon(get_editor_theme_icon(SNAME("Close")));
|
||||
}
|
||||
}
|
||||
|
||||
EditorToaster *EditorToaster::get_singleton() {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
EditorToaster::EditorToaster() {
|
||||
set_notify_transform(true);
|
||||
set_process_internal(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);
|
||||
}
|
||||
124
engine/editor/gui/editor_toaster.h
Normal file
124
engine/editor/gui/editor_toaster.h
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/**************************************************************************/
|
||||
/* 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 "core/string/ustring.h"
|
||||
#include "core/templates/local_vector.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;
|
||||
|
||||
// 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 _close_button_theme_changed(Control *p_close_button);
|
||||
|
||||
protected:
|
||||
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);
|
||||
|
||||
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
|
||||
236
engine/editor/gui/editor_zoom_widget.cpp
Normal file
236
engine/editor/gui/editor_zoom_widget.cpp
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
/**************************************************************************/
|
||||
/* 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 {
|
||||
// Base increment factor defined as the twelveth root of two.
|
||||
// This allows for a smooth geometric evolution of the zoom, with the advantage of
|
||||
// visiting all integer power of two scale factors.
|
||||
// Note: this is analogous to the 'semitone' interval in the music world
|
||||
// In order to avoid numerical imprecisions, we compute and edit a zoom index
|
||||
// with the following relation: zoom = 2 ^ (index / 12)
|
||||
|
||||
if (zoom < CMP_EPSILON || p_increment_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// zoom = 2**(index/12) => log2(zoom) = index/12
|
||||
float closest_zoom_index = Math::round(Math::log(zoom_noscale) * 12.f / Math::log(2.f));
|
||||
|
||||
float new_zoom_index = closest_zoom_index + p_increment_count;
|
||||
float new_zoom = Math::pow(2.f, new_zoom_index / 12.f);
|
||||
|
||||
// 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_icon(get_editor_theme_icon(SNAME("ZoomLess")));
|
||||
zoom_plus->set_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", TTR("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("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", TTR("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
|
||||
1773
engine/editor/gui/scene_tree_editor.cpp
Normal file
1773
engine/editor/gui/scene_tree_editor.cpp
Normal file
File diff suppressed because it is too large
Load diff
215
engine/editor/gui/scene_tree_editor.h
Normal file
215
engine/editor/gui/scene_tree_editor.h
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
/**************************************************************************/
|
||||
/* 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_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,
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
bool auto_expand_selected = true;
|
||||
bool connect_to_script_mode = false;
|
||||
bool connecting_signal = false;
|
||||
|
||||
int blocked;
|
||||
|
||||
void _compute_hash(Node *p_node, uint64_t &hash);
|
||||
|
||||
void _add_nodes(Node *p_node, TreeItem *p_parent);
|
||||
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 _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);
|
||||
|
||||
Timer *update_timer = nullptr;
|
||||
|
||||
List<StringName> *script_types;
|
||||
bool _is_script_type(const StringName &p_type) const;
|
||||
|
||||
Vector<StringName> valid_types;
|
||||
|
||||
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);
|
||||
|
||||
void update_tree() { _update_tree(); }
|
||||
|
||||
void set_auto_expand_selected(bool p_auto, bool p_update_settings);
|
||||
void set_connect_to_script_mode(bool p_enable);
|
||||
void set_connecting_signal(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;
|
||||
|
||||
void _select();
|
||||
void _cancel();
|
||||
void _selected_changed();
|
||||
void _filter_changed(const String &p_filter);
|
||||
void _show_all_nodes_changed(bool p_button_pressed);
|
||||
|
||||
protected:
|
||||
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