feat: godot-engine-source-4.3-stable

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

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

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

View 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));
}

View 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

View 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));
}

View 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

File diff suppressed because it is too large Load diff

View 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

View 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."));
}

View 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

View 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));
}

View 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

View 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);
}

View 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

View 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));
}

View 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

View 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;
}

View 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

View 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);
}

View 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

View 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);
}

View 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

View 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);
}

View 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

File diff suppressed because it is too large Load diff

View 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