diff options
Diffstat (limited to 'editor')
31 files changed, 2286 insertions, 370 deletions
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 19bcb28fa5..24e86770bf 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -650,12 +650,12 @@ void CodeTextEditor::_text_editor_gui_input(const Ref<InputEvent> &p_event) { } void CodeTextEditor::_zoom_in() { - font_resize_val += EDSCALE; + font_resize_val += MAX(EDSCALE, 1.0f); _zoom_changed(); } void CodeTextEditor::_zoom_out() { - font_resize_val -= EDSCALE; + font_resize_val -= MAX(EDSCALE, 1.0f); _zoom_changed(); } diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index ef133e2589..7f93917744 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -35,6 +35,7 @@ #include "plugins/script_editor_plugin.h" #include "print_string.h" #include "scene/gui/label.h" +#include "scene/gui/popup_menu.h" class ConnectDialogBinds : public Object { @@ -87,51 +88,12 @@ public: } }; -void ConnectDialog::_notification(int p_what) { - - if (p_what == NOTIFICATION_ENTER_TREE) { - bind_editor->edit(cdbinds); - } -} - -void ConnectDialog::_tree_node_selected() { - - //dst_method_list->get_popup()->clear(); - Node *current = tree->get_selected(); - - if (!current) { - make_callback->hide(); - return; - } - - if (current->get_script().is_null()) - make_callback->hide(); - else - make_callback->show(); - - dst_path->set_text(node->get_path_to(current)); -} - -void ConnectDialog::edit(Node *p_node) { - - node = p_node; - - //dst_method_list->get_popup()->clear(); - - tree->set_selected(NULL); - tree->set_marked(node, true); - dst_path->set_text(""); - dst_method->set_text(""); - deferred->set_pressed(false); - oneshot->set_pressed(false); - cdbinds->params.clear(); - cdbinds->notify_changed(); -} - +/* +Signal automatically called by parent dialog. +*/ void ConnectDialog::ok_pressed() { if (dst_method->get_text() == "") { - error->set_text(TTR("Method in target Node must be specified!")); error->popup_centered_minsize(); return; @@ -147,39 +109,35 @@ void ConnectDialog::ok_pressed() { emit_signal("connected"); hide(); } + void ConnectDialog::_cancel_pressed() { hide(); } -NodePath ConnectDialog::get_dst_path() const { - - return dst_path->get_text(); -} - -bool ConnectDialog::get_deferred() const { - - return deferred->is_pressed(); -} - -bool ConnectDialog::get_oneshot() const { - - return oneshot->is_pressed(); -} +/* +Called each time a target node is selected within the target node tree. +*/ +void ConnectDialog::_tree_node_selected() { -StringName ConnectDialog::get_dst_method() const { + Node *current = tree->get_selected(); - String txt = dst_method->get_text(); - if (txt.find("(") != -1) - txt = txt.left(txt.find("(")).strip_edges(); - return txt; -} + if (!current) { + make_callback->hide(); + return; + } -Vector<Variant> ConnectDialog::get_binds() const { + if (current->get_script().is_null()) + make_callback->hide(); + else + make_callback->show(); - return cdbinds->params; + dst_path->set_text(source->get_path_to(current)); } +/* +Adds a new parameter bind to connection. +*/ void ConnectDialog::_add_bind() { if (cdbinds->params.size() >= VARIANT_ARG_MAX) @@ -189,7 +147,6 @@ void ConnectDialog::_add_bind() { Variant value; switch (vt) { - case Variant::BOOL: value = false; break; case Variant::INT: value = 0; break; case Variant::REAL: value = 0.0; break; @@ -203,7 +160,6 @@ void ConnectDialog::_add_bind() { case Variant::BASIS: value = Basis(); break; case Variant::TRANSFORM: value = Transform(); break; case Variant::COLOR: value = Color(); break; - default: { ERR_FAIL(); } break; } @@ -213,6 +169,9 @@ void ConnectDialog::_add_bind() { cdbinds->notify_changed(); } +/* +Remove parameter bind from connection. +*/ void ConnectDialog::_remove_bind() { String st = bind_editor->get_selected_path(); @@ -225,25 +184,106 @@ void ConnectDialog::_remove_bind() { cdbinds->notify_changed(); } +void ConnectDialog::_notification(int p_what) { + + if (p_what == NOTIFICATION_ENTER_TREE) { + bind_editor->edit(cdbinds); + } +} + +void ConnectDialog::_bind_methods() { + + ClassDB::bind_method("_cancel", &ConnectDialog::_cancel_pressed); + ClassDB::bind_method("_tree_node_selected", &ConnectDialog::_tree_node_selected); + ClassDB::bind_method("_add_bind", &ConnectDialog::_add_bind); + ClassDB::bind_method("_remove_bind", &ConnectDialog::_remove_bind); + + ADD_SIGNAL(MethodInfo("connected")); +} + +Node *ConnectDialog::get_source() const { + + return source; +} + +StringName ConnectDialog::get_signal_name() const { + + return signal; +} + +NodePath ConnectDialog::get_dst_path() const { + + return dst_path->get_text(); +} + void ConnectDialog::set_dst_node(Node *p_node) { tree->set_selected(p_node); } +StringName ConnectDialog::get_dst_method_name() const { + + String txt = dst_method->get_text(); + if (txt.find("(") != -1) + txt = txt.left(txt.find("(")).strip_edges(); + return txt; +} + void ConnectDialog::set_dst_method(const StringName &p_method) { dst_method->set_text(p_method); } -void ConnectDialog::_bind_methods() { +Vector<Variant> ConnectDialog::get_binds() const { - ClassDB::bind_method("_cancel", &ConnectDialog::_cancel_pressed); - ClassDB::bind_method("_tree_node_selected", &ConnectDialog::_tree_node_selected); + return cdbinds->params; +} - ClassDB::bind_method("_add_bind", &ConnectDialog::_add_bind); - ClassDB::bind_method("_remove_bind", &ConnectDialog::_remove_bind); +bool ConnectDialog::get_deferred() const { - ADD_SIGNAL(MethodInfo("connected")); + return deferred->is_pressed(); +} + +bool ConnectDialog::get_oneshot() const { + + return oneshot->is_pressed(); +} + +/* +Returns true if ConnectDialog is being used to edit an existing connection. +*/ +bool ConnectDialog::is_editing() const { + + return bEditMode; +} + +/* +Initialize ConnectDialog and populate fields with expected data. +If creating a connection from scratch, sensible defaults are used. +If editing an existing connection, previous data is retained. +*/ +void ConnectDialog::init(Connection c, bool bEdit) { + + source = static_cast<Node *>(c.source); + signal = c.signal; + + tree->set_selected(NULL); + tree->set_marked(source, true); + + set_dst_node(static_cast<Node *>(c.target)); + set_dst_method(c.method); + + bool bDeferred = (c.flags & CONNECT_DEFERRED) == CONNECT_DEFERRED; + bool bOneshot = (c.flags & CONNECT_ONESHOT) == CONNECT_ONESHOT; + + deferred->set_pressed(bDeferred); + oneshot->set_pressed(bOneshot); + + cdbinds->params.clear(); + cdbinds->params = c.binds; + cdbinds->notify_changed(); + + bEditMode = bEdit; } ConnectDialog::ConnectDialog() { @@ -261,6 +301,8 @@ ConnectDialog::ConnectDialog() { tree = memnew(SceneTreeEditor(false)); tree->get_scene_tree()->connect("item_activated", this, "_ok"); + tree->connect("node_selected", this, "_tree_node_selected"); + vbc_left->add_margin_child(TTR("Connect To Node:"), tree, true); VBoxContainer *vbc_right = memnew(VBoxContainer); @@ -272,12 +314,10 @@ ConnectDialog::ConnectDialog() { type_list = memnew(OptionButton); type_list->set_h_size_flags(SIZE_EXPAND_FILL); add_bind_hb->add_child(type_list); - type_list->add_item("bool", Variant::BOOL); type_list->add_item("int", Variant::INT); type_list->add_item("real", Variant::REAL); type_list->add_item("string", Variant::STRING); - //type_list->add_separator(); type_list->add_item("Vector2", Variant::VECTOR2); type_list->add_item("Rect2", Variant::RECT2); type_list->add_item("Vector3", Variant::VECTOR3); @@ -286,12 +326,10 @@ ConnectDialog::ConnectDialog() { type_list->add_item("AABB", Variant::AABB); type_list->add_item("Basis", Variant::BASIS); type_list->add_item("Transform", Variant::TRANSFORM); - //type_list->add_separator(); type_list->add_item("Color", Variant::COLOR); type_list->select(0); Button *add_bind = memnew(Button); - add_bind->set_text(TTR("Add")); add_bind_hb->add_child(add_bind); add_bind->connect("pressed", this, "_add_bind"); @@ -318,7 +356,8 @@ ConnectDialog::ConnectDialog() { dst_method->set_h_size_flags(SIZE_EXPAND_FILL); dstm_hb->add_child(dst_method); - /*dst_method_list = memnew( MenuButton ); + /* + dst_method_list = memnew( MenuButton ); dst_method_list->set_text("List..."); dst_method_list->set_anchor( MARGIN_RIGHT, ANCHOR_END ); dst_method_list->set_anchor( MARGIN_LEFT, ANCHOR_END ); @@ -327,7 +366,6 @@ ConnectDialog::ConnectDialog() { dst_method_list->set_begin( Point2( 70,59) ); dst_method_list->set_end( Point2( 15,39 ) ); */ - //add_child(dst_method_list); make_callback = memnew(CheckButton); make_callback->set_toggle_mode(true); @@ -343,8 +381,6 @@ ConnectDialog::ConnectDialog() { oneshot->set_text(TTR("Oneshot")); dstm_hb->add_child(oneshot); - tree->connect("node_selected", this, "_tree_node_selected"); - set_as_toplevel(true); cdbinds = memnew(ConnectDialogBinds); @@ -356,134 +392,365 @@ ConnectDialog::ConnectDialog() { } ConnectDialog::~ConnectDialog() { + memdelete(cdbinds); } -void ConnectionsDock::_notification(int p_what) { - - if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { - update_tree(); - } -} +//ConnectionsDock ========================== -void ConnectionsDock::_close() { +struct _ConnectionsDockMethodInfoSort { - hide(); -} + _FORCE_INLINE_ bool operator()(const MethodInfo &a, const MethodInfo &b) const { + return a.name < b.name; + } +}; -void ConnectionsDock::_connect() { +/* +Post-ConnectDialog callback for creating/editing connections. +Creates or edits connections based on state of the ConnectDialog when "Connect" is pressed. +*/ +void ConnectionsDock::_make_or_edit_connection() { TreeItem *it = tree->get_selected(); ERR_FAIL_COND(!it); - String signal = it->get_metadata(0).operator Dictionary()["name"]; NodePath dst_path = connect_dialog->get_dst_path(); - Node *target = node->get_node(dst_path); + Node *target = selectedNode->get_node(dst_path); ERR_FAIL_COND(!target); - StringName dst_method = connect_dialog->get_dst_method(); + Connection cToMake; + cToMake.source = connect_dialog->get_source(); + cToMake.target = target; + cToMake.signal = connect_dialog->get_signal_name(); + cToMake.method = connect_dialog->get_dst_method_name(); + cToMake.binds = connect_dialog->get_binds(); bool defer = connect_dialog->get_deferred(); bool oshot = connect_dialog->get_oneshot(); - Vector<Variant> binds = connect_dialog->get_binds(); - PoolStringArray args = it->get_metadata(0).operator Dictionary()["args"]; - int flags = CONNECT_PERSIST | (defer ? CONNECT_DEFERRED : 0) | (oshot ? CONNECT_ONESHOT : 0); + cToMake.flags = CONNECT_PERSIST | (defer ? CONNECT_DEFERRED : 0) | (oshot ? CONNECT_ONESHOT : 0); - undo_redo->create_action(vformat(TTR("Connect '%s' to '%s'"), signal, String(dst_method))); - undo_redo->add_do_method(node, "connect", signal, target, dst_method, binds, flags); - undo_redo->add_undo_method(node, "disconnect", signal, target, dst_method); + if (connect_dialog->is_editing()) { + _disconnect(*it); + _connect(cToMake); + } else { + _connect(cToMake); + } + + if (connect_dialog->get_make_callback()) { + PoolStringArray args = it->get_metadata(0).operator Dictionary()["args"]; + editor->emit_signal("script_add_function_request", target, cToMake.method, args); + hide(); + } + + update_tree(); +} + +/* +Creates single connection w/ undo-redo functionality. +*/ +void ConnectionsDock::_connect(Connection cToMake) { + + Node *source = static_cast<Node *>(cToMake.source); + Node *target = static_cast<Node *>(cToMake.target); + + if (!source || !target) + return; + + undo_redo->create_action(vformat(TTR("Connect '%s' to '%s'"), String(cToMake.signal), String(cToMake.method))); + + undo_redo->add_do_method(source, "connect", cToMake.signal, target, cToMake.method, cToMake.binds, cToMake.flags); + undo_redo->add_undo_method(source, "disconnect", cToMake.signal, target, cToMake.method); undo_redo->add_do_method(this, "update_tree"); undo_redo->add_undo_method(this, "update_tree"); undo_redo->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); //to force redraw of scene tree - undo_redo->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); //to force redraw of scene tree + undo_redo->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); undo_redo->commit_action(); +} - if (connect_dialog->get_make_callback()) { +/* +Break single connection w/ undo-redo functionality. +*/ +void ConnectionsDock::_disconnect(TreeItem &item) { - print_line("request connect"); - editor->emit_signal("script_add_function_request", target, dst_method, args); - hide(); + Connection c = item.get_metadata(0); + ERR_FAIL_COND(c.source != selectedNode); //shouldn't happen but...bugcheck + + undo_redo->create_action(vformat(TTR("Disconnect '%s' from '%s'"), c.signal, c.method)); + + undo_redo->add_do_method(selectedNode, "disconnect", c.signal, c.target, c.method); + undo_redo->add_undo_method(selectedNode, "connect", c.signal, c.target, c.method, c.binds, c.flags); + undo_redo->add_do_method(this, "update_tree"); + undo_redo->add_undo_method(this, "update_tree"); + undo_redo->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); //to force redraw of scene tree + undo_redo->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); + + undo_redo->commit_action(); +} + +/* +Break all conections of currently selected signal. +Can undo-redo as a single action. +*/ +void ConnectionsDock::_disconnect_all() { + + TreeItem *item = tree->get_selected(); + + if (!_is_item_signal(*item)) + return; + + TreeItem *child = item->get_children(); + String signalName = item->get_metadata(0).operator Dictionary()["name"]; + undo_redo->create_action(vformat(TTR("Disconnect all from signal: '%s'"), signalName)); + + while (child) { + Connection c = child->get_metadata(0); + undo_redo->add_do_method(selectedNode, "disconnect", c.signal, c.target, c.method); + undo_redo->add_undo_method(selectedNode, "connect", c.signal, c.target, c.method, c.binds, c.flags); + child = child->get_next(); } - update_tree(); + undo_redo->add_do_method(this, "update_tree"); + undo_redo->add_undo_method(this, "update_tree"); + undo_redo->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); + undo_redo->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); + + undo_redo->commit_action(); } -void ConnectionsDock::_connect_pressed() { +void ConnectionsDock::_tree_item_selected() { TreeItem *item = tree->get_selected(); - if (!item) { - //no idea how this happened, but disable + if (!item) { //Unlikely. Disable button just in case. + connect_button->set_text(TTR("Connect...")); connect_button->set_disabled(true); + } else if (_is_item_signal(*item)) { + connect_button->set_text(TTR("Connect...")); + connect_button->set_disabled(false); + } else { + connect_button->set_text(TTR("Disconnect")); + connect_button->set_disabled(false); + } +} + +void ConnectionsDock::_tree_item_activated() { //"Activation" on double-click. + + TreeItem *item = tree->get_selected(); + + if (!item) return; + + if (_is_item_signal(*item)) { + _open_connection_dialog(*item); + } else { + _go_to_script(*item); } - if (item->get_parent() == tree->get_root() || item->get_parent()->get_parent() == tree->get_root()) { - //a signal - connect - String signal = item->get_metadata(0).operator Dictionary()["name"]; - String signalname = signal; - String midname = node->get_name(); - for (int i = 0; i < midname.length(); i++) { - CharType c = midname[i]; - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') { - //all good - } else if (c == ' ') { +} + +bool ConnectionsDock::_is_item_signal(TreeItem &item) { + + return (item.get_parent() == tree->get_root() || item.get_parent()->get_parent() == tree->get_root()); +} + +/* +Open connection dialog with TreeItem data to CREATE a brand-new connection. +*/ +void ConnectionsDock::_open_connection_dialog(TreeItem &item) { + + String signal = item.get_metadata(0).operator Dictionary()["name"]; + String signalname = signal; + String midname = selectedNode->get_name(); + for (int i = 0; i < midname.length(); i++) { //TODO: Regex filter may be cleaner. + CharType c = midname[i]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) { + if (c == ' ') { + //Replace spaces with underlines. c = '_'; } else { + //Remove any other characters. midname.remove(i); i--; continue; } - - midname[i] = c; } + midname[i] = c; + } + + Node *dst_node = selectedNode->get_owner() ? selectedNode->get_owner() : selectedNode; + StringName dst_method = "_on_" + midname + "_" + signal; + + Connection c; + c.source = selectedNode; + c.signal = StringName(signalname); + c.target = dst_node; + c.method = dst_method; + + connect_dialog->init(c); + connect_dialog->set_title(TTR("Connect Signal: ") + signalname); + connect_dialog->popup_centered_ratio(); +} + +/* +Open connection dialog with Connection data to EDIT an existing connection. +*/ +void ConnectionsDock::_open_connection_dialog(Connection cToEdit) { + + Node *src = static_cast<Node *>(cToEdit.source); + Node *dst = static_cast<Node *>(cToEdit.target); - connect_dialog->edit(node); + if (src && dst) { + connect_dialog->init(cToEdit, true); + connect_dialog->set_title(TTR("Edit Connection: ") + cToEdit.signal); connect_dialog->popup_centered_ratio(); - connect_dialog->set_title(TTR("Connecting Signal:") + " " + signalname); - connect_dialog->set_dst_method("_on_" + midname + "_" + signal); - connect_dialog->set_dst_node(node->get_owner() ? node->get_owner() : node); + } +} + +/* +Open slot method location in script editor. +*/ +void ConnectionsDock::_go_to_script(TreeItem &item) { + + if (_is_item_signal(item)) + return; + + Connection c = item.get_metadata(0); + ERR_FAIL_COND(c.source != selectedNode); //shouldn't happen but...bugcheck + + if (!c.target) + return; + + Ref<Script> script = c.target->get_script(); + + if (script.is_null()) + return; + + if (script.is_valid() && ScriptEditor::get_singleton()->script_goto_method(script, c.method)) { + editor->call("_editor_select", EditorNode::EDITOR_SCRIPT); + } +} + +void ConnectionsDock::_handle_signal_menu_option(int option) { + + TreeItem *item = tree->get_selected(); + + if (!item) + return; + + switch (option) { + case CONNECT: { + _open_connection_dialog(*item); + } break; + case DISCONNECT_ALL: { + StringName signalName = item->get_metadata(0).operator Dictionary()["name"]; + disconnect_all_dialog->set_text(TTR("Are you sure you want to remove all connections from the \"") + signalName + "\" signal?"); + disconnect_all_dialog->popup_centered(); + } break; + } +} + +void ConnectionsDock::_handle_slot_menu_option(int option) { + + TreeItem *item = tree->get_selected(); + + if (!item) + return; + + switch (option) { + case EDIT: { + Connection c = item->get_metadata(0); + _open_connection_dialog(c); + } break; + case GO_TO_SCRIPT: { + _go_to_script(*item); + } break; + case DISCONNECT: { + _disconnect(*item); + update_tree(); + } break; + } +} + +void ConnectionsDock::_rmb_pressed(Vector2 position) { + TreeItem *item = tree->get_selected(); + + if (!item) + return; + + Vector2 global_position = tree->get_global_position() + position; + + if (_is_item_signal(*item)) { + signal_menu->set_position(global_position); + signal_menu->popup(); } else { - //a slot- disconnect - Connection c = item->get_metadata(0); - ERR_FAIL_COND(c.source != node); //shouldn't happen but...bugcheck - - undo_redo->create_action(vformat(TTR("Disconnect '%s' from '%s'"), c.signal, c.method)); - undo_redo->add_do_method(node, "disconnect", c.signal, c.target, c.method); - undo_redo->add_undo_method(node, "connect", c.signal, c.target, c.method, Vector<Variant>(), c.flags); - undo_redo->add_do_method(this, "update_tree"); - undo_redo->add_undo_method(this, "update_tree"); - undo_redo->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); //to force redraw of scene tree - undo_redo->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor(), "update_tree"); //to force redraw of scene tree - undo_redo->commit_action(); - - c.source->disconnect(c.signal, c.target, c.method); + slot_menu->set_position(global_position); + slot_menu->popup(); + } +} + +void ConnectionsDock::_close() { + + hide(); +} + +void ConnectionsDock::_connect_pressed() { + + TreeItem *item = tree->get_selected(); + if (!item) { + connect_button->set_disabled(true); + return; + } + + if (_is_item_signal(*item)) { + _open_connection_dialog(*item); + } else { + _disconnect(*item); update_tree(); } } -struct _ConnectionsDockMethodInfoSort { +void ConnectionsDock::_notification(int p_what) { - _FORCE_INLINE_ bool operator()(const MethodInfo &a, const MethodInfo &b) const { - return a.name < b.name; + if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { + update_tree(); } -}; +} + +void ConnectionsDock::_bind_methods() { + + ClassDB::bind_method("_make_or_edit_connection", &ConnectionsDock::_make_or_edit_connection); + ClassDB::bind_method("_disconnect_all", &ConnectionsDock::_disconnect_all); + ClassDB::bind_method("_tree_item_selected", &ConnectionsDock::_tree_item_selected); + ClassDB::bind_method("_tree_item_activated", &ConnectionsDock::_tree_item_activated); + ClassDB::bind_method("_handle_signal_menu_option", &ConnectionsDock::_handle_signal_menu_option); + ClassDB::bind_method("_handle_slot_menu_option", &ConnectionsDock::_handle_slot_menu_option); + ClassDB::bind_method("_rmb_pressed", &ConnectionsDock::_rmb_pressed); + ClassDB::bind_method("_close", &ConnectionsDock::_close); + ClassDB::bind_method("_connect_pressed", &ConnectionsDock::_connect_pressed); + ClassDB::bind_method("update_tree", &ConnectionsDock::update_tree); +} + +void ConnectionsDock::set_node(Node *p_node) { + + selectedNode = p_node; + update_tree(); +} void ConnectionsDock::update_tree() { tree->clear(); - if (!node) + if (!selectedNode) return; TreeItem *root = tree->create_item(); List<MethodInfo> node_signals; - node->get_signal_list(&node_signals); + selectedNode->get_signal_list(&node_signals); //node_signals.sort_custom<_ConnectionsDockMethodInfoSort>(); bool did_script = false; - StringName base = node->get_class(); + StringName base = selectedNode->get_class(); while (base) { @@ -493,7 +760,7 @@ void ConnectionsDock::update_tree() { if (!did_script) { - Ref<Script> scr = node->get_script(); + Ref<Script> scr = selectedNode->get_script(); if (scr.is_valid()) { scr->get_script_signal_list(&node_signals); if (scr->get_path().is_resource_file()) @@ -563,7 +830,7 @@ void ConnectionsDock::update_tree() { item->set_icon(0, get_icon("Signal", "EditorIcons")); List<Object::Connection> connections; - node->get_signal_connection_list(mi.name, &connections); + selectedNode->get_signal_connection_list(mi.name, &connections); for (List<Object::Connection>::Element *F = connections.front(); F; F = F->next()) { @@ -575,7 +842,7 @@ void ConnectionsDock::update_tree() { if (!target) continue; - String path = String(node->get_path_to(target)) + " :: " + c.method + "()"; + String path = String(selectedNode->get_path_to(target)) + " :: " + c.method + "()"; if (c.flags & CONNECT_DEFERRED) path += " (deferred)"; if (c.flags & CONNECT_ONESHOT) @@ -610,88 +877,6 @@ void ConnectionsDock::update_tree() { connect_button->set_disabled(true); } -void ConnectionsDock::set_node(Node *p_node) { - - node = p_node; - update_tree(); -} - -void ConnectionsDock::_something_selected() { - - TreeItem *item = tree->get_selected(); - if (!item) { - //no idea how this happened, but disable - connect_button->set_text(TTR("Connect...")); - connect_button->set_disabled(true); - - } else if (item->get_parent() == tree->get_root() || item->get_parent()->get_parent() == tree->get_root()) { - //a signal - connect - connect_button->set_text(TTR("Connect...")); - connect_button->set_disabled(false); - - } else { - //a slot- disconnect - connect_button->set_text(TTR("Disconnect")); - connect_button->set_disabled(false); - } -} - -void ConnectionsDock::_something_activated() { - - TreeItem *item = tree->get_selected(); - - if (!item) - return; - - if (item->get_parent() == tree->get_root() || item->get_parent()->get_parent() == tree->get_root()) { - // a signal - connect - String signal = item->get_metadata(0).operator Dictionary()["name"]; - String midname = node->get_name(); - for (int i = 0; i < midname.length(); i++) { - CharType c = midname[i]; - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') { - //all good - } else if (c == ' ') { - c = '_'; - } else { - midname.remove(i); - i--; - continue; - } - - midname[i] = c; - } - - connect_dialog->edit(node); - connect_dialog->popup_centered_ratio(); - connect_dialog->set_dst_method("_on_" + midname + "_" + signal); - connect_dialog->set_dst_node(node->get_owner() ? node->get_owner() : node); - } else { - // a slot - go to target method - Connection c = item->get_metadata(0); - ERR_FAIL_COND(c.source != node); //shouldn't happen but...bugcheck - - if (!c.target) - return; - - Ref<Script> script = c.target->get_script(); - - if (script.is_valid() && ScriptEditor::get_singleton()->script_goto_method(script, c.method)) { - editor->call("_editor_select", EditorNode::EDITOR_SCRIPT); - } - } -} - -void ConnectionsDock::_bind_methods() { - - ClassDB::bind_method("_connect", &ConnectionsDock::_connect); - ClassDB::bind_method("_something_selected", &ConnectionsDock::_something_selected); - ClassDB::bind_method("_something_activated", &ConnectionsDock::_something_activated); - ClassDB::bind_method("_close", &ConnectionsDock::_close); - ClassDB::bind_method("_connect_pressed", &ConnectionsDock::_connect_pressed); - ClassDB::bind_method("update_tree", &ConnectionsDock::update_tree); -} - ConnectionsDock::ConnectionsDock(EditorNode *p_editor) { editor = p_editor; @@ -705,6 +890,7 @@ ConnectionsDock::ConnectionsDock(EditorNode *p_editor) { tree->set_hide_root(true); vbc->add_child(tree); tree->set_v_size_flags(SIZE_EXPAND_FILL); + tree->set_allow_rmb_select(true); connect_button = memnew(Button); connect_button->set_text(TTR("Connect")); @@ -713,15 +899,29 @@ ConnectionsDock::ConnectionsDock(EditorNode *p_editor) { hb->add_spacer(); hb->add_child(connect_button); connect_button->connect("pressed", this, "_connect_pressed"); - //add_child(tree); connect_dialog = memnew(ConnectDialog); connect_dialog->set_as_toplevel(true); add_child(connect_dialog); - remove_confirm = memnew(ConfirmationDialog); - remove_confirm->set_as_toplevel(true); - add_child(remove_confirm); + disconnect_all_dialog = memnew(ConfirmationDialog); + disconnect_all_dialog->set_as_toplevel(true); + add_child(disconnect_all_dialog); + disconnect_all_dialog->connect("confirmed", this, "_disconnect_all"); + disconnect_all_dialog->set_text(TTR("Are you sure you want to remove all connections from this signal?")); + + signal_menu = memnew(PopupMenu); + add_child(signal_menu); + signal_menu->connect("id_pressed", this, "_handle_signal_menu_option"); + signal_menu->add_item(TTR("Connect..."), CONNECT); + signal_menu->add_item(TTR("Disconnect All"), DISCONNECT_ALL); + + slot_menu = memnew(PopupMenu); + add_child(slot_menu); + slot_menu->connect("id_pressed", this, "_handle_slot_menu_option"); + slot_menu->add_item(TTR("Edit..."), EDIT); + slot_menu->add_item(TTR("Go To Method"), GO_TO_SCRIPT); + slot_menu->add_item(TTR("Disconnect"), DISCONNECT); /* node_only->set_anchor( MARGIN_TOP, ANCHOR_END ); @@ -732,10 +932,10 @@ ConnectionsDock::ConnectionsDock(EditorNode *p_editor) { node_only->set_end( Point2( 10,44) ); */ - remove_confirm->connect("confirmed", this, "_remove_confirm"); - connect_dialog->connect("connected", this, "_connect"); - tree->connect("item_selected", this, "_something_selected"); - tree->connect("item_activated", this, "_something_activated"); + connect_dialog->connect("connected", this, "_make_or_edit_connection"); + tree->connect("item_selected", this, "_tree_item_selected"); + tree->connect("item_activated", this, "_tree_item_activated"); + tree->connect("item_rmb_selected", this, "_rmb_pressed"); add_constant_override("separation", 3 * EDSCALE); } diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h index 5e26e00f67..932ff693e4 100644 --- a/editor/connections_dialog.h +++ b/editor/connections_dialog.h @@ -28,6 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +/** +@author Juan Linietsky <reduzio@gmail.com> +*/ + #ifndef CONNECTIONS_DIALOG_H #define CONNECTIONS_DIALOG_H @@ -42,27 +46,28 @@ #include "scene/gui/tree.h" #include "undo_redo.h" -/** -@author Juan Linietsky <reduzio@gmail.com> -*/ - +class PopupMenu; class ConnectDialogBinds; class ConnectDialog : public ConfirmationDialog { GDCLASS(ConnectDialog, ConfirmationDialog); - ConfirmationDialog *error; + Node *source; + StringName signal; LineEdit *dst_path; LineEdit *dst_method; + ConnectDialogBinds *cdbinds; + bool bEditMode; + SceneTreeEditor *tree; + ConfirmationDialog *error; + PropertyEditor *bind_editor; OptionButton *type_list; CheckButton *deferred; CheckButton *oneshot; CheckButton *make_callback; - PropertyEditor *bind_editor; - Node *node; - ConnectDialogBinds *cdbinds; + void ok_pressed(); void _cancel_pressed(); void _tree_node_selected(); @@ -74,37 +79,71 @@ protected: static void _bind_methods(); public: - bool get_make_callback() { return make_callback->is_visible() && make_callback->is_pressed(); } + Node *get_source() const; + StringName get_signal_name() const; NodePath get_dst_path() const; - StringName get_dst_method() const; + void set_dst_node(Node *p_node); + StringName get_dst_method_name() const; + void set_dst_method(const StringName &p_method); + Vector<Variant> get_binds() const; + + bool get_make_callback() { return make_callback->is_visible() && make_callback->is_pressed(); } bool get_deferred() const; bool get_oneshot() const; - Vector<Variant> get_binds() const; - void set_dst_method(const StringName &p_method); - void set_dst_node(Node *p_node); + bool is_editing() const; - void edit(Node *p_node); + void init(Connection c, bool bEdit = false); ConnectDialog(); ~ConnectDialog(); }; +//======================================== + class ConnectionsDock : public VBoxContainer { GDCLASS(ConnectionsDock, VBoxContainer); - Button *connect_button; - EditorNode *editor; - Node *node; + //Right-click Pop-up Menu Options. + enum SignalMenuOption { + CONNECT, + DISCONNECT_ALL + }; + + enum SlotMenuOption { + EDIT, + GO_TO_SCRIPT, + DISCONNECT + }; + + Node *selectedNode; Tree *tree; - ConfirmationDialog *remove_confirm; + EditorNode *editor; + + ConfirmationDialog *disconnect_all_dialog; ConnectDialog *connect_dialog; + Button *connect_button; + PopupMenu *signal_menu; + PopupMenu *slot_menu; + UndoRedo *undo_redo; + + void _make_or_edit_connection(); + void _connect(Connection cToMake); + void _disconnect(TreeItem &item); + void _disconnect_all(); + void _tree_item_selected(); + void _tree_item_activated(); + bool _is_item_signal(TreeItem &item); + + void _open_connection_dialog(TreeItem &item); + void _open_connection_dialog(Connection cToEdit); + void _go_to_script(TreeItem &item); + + void _handle_signal_menu_option(int option); + void _handle_slot_menu_option(int option); + void _rmb_pressed(Vector2 position); void _close(); - void _connect(); - void _something_selected(); - void _something_activated(); - UndoRedo *undo_redo; protected: void _connect_pressed(); @@ -113,9 +152,7 @@ protected: public: void set_undoredo(UndoRedo *p_undo_redo) { undo_redo = p_undo_redo; } - void set_node(Node *p_node); - String get_selected_type(); void update_tree(); ConnectionsDock(EditorNode *p_editor = NULL); diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index 78fb35e354..36978e37a5 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -87,7 +87,14 @@ void CreateDialog::popup_create(bool p_dont_clear, bool p_replace_mode) { if (EditorSettings::get_singleton()->has_setting("interface/dialogs/create_new_node_bounds")) { popup(EditorSettings::get_singleton()->get("interface/dialogs/create_new_node_bounds")); } else { - popup_centered_ratio(); + + Size2 popup_size = Size2(900, 700) * editor_get_scale(); + Size2 window_size = get_viewport_rect().size; + + popup_size.x = MIN(window_size.x * 0.8, popup_size.x); + popup_size.y = MIN(window_size.y * 0.8, popup_size.y); + + popup_centered(popup_size); } if (p_dont_clear) { diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index 37a35b6ebf..4ac3e33cb9 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -364,6 +364,14 @@ void EditorData::notify_edited_scene_changed() { } } +void EditorData::notify_resource_saved(const Ref<Resource> &p_resource) { + + for (int i = 0; i < editor_plugins.size(); i++) { + + editor_plugins[i]->notify_resource_saved(p_resource); + } +} + void EditorData::clear_editor_states() { for (int i = 0; i < editor_plugins.size(); i++) { @@ -890,7 +898,7 @@ Array EditorSelection::_get_transformable_selected_nodes() { return ret; } -Array EditorSelection::_get_selected_nodes() { +Array EditorSelection::get_selected_nodes() { Array ret; @@ -908,7 +916,7 @@ void EditorSelection::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &EditorSelection::clear); ClassDB::bind_method(D_METHOD("add_node", "node"), &EditorSelection::add_node); ClassDB::bind_method(D_METHOD("remove_node", "node"), &EditorSelection::remove_node); - ClassDB::bind_method(D_METHOD("get_selected_nodes"), &EditorSelection::_get_selected_nodes); + ClassDB::bind_method(D_METHOD("get_selected_nodes"), &EditorSelection::get_selected_nodes); ClassDB::bind_method(D_METHOD("get_transformable_selected_nodes"), &EditorSelection::_get_transformable_selected_nodes); ClassDB::bind_method(D_METHOD("_emit_change"), &EditorSelection::_emit_change); ADD_SIGNAL(MethodInfo("selection_changed")); diff --git a/editor/editor_data.h b/editor/editor_data.h index 1a498a6150..f020d07ea7 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -204,6 +204,7 @@ public: void save_edited_scene_state(EditorSelection *p_selection, EditorHistory *p_history, const Dictionary &p_custom); Dictionary restore_edited_scene_state(EditorSelection *p_selection, EditorHistory *p_history); void notify_edited_scene_changed(); + void notify_resource_saved(const Ref<Resource> &p_resource); EditorData(); }; @@ -225,7 +226,6 @@ private: List<Node *> selected_node_list; void _update_nl(); - Array _get_selected_nodes(); Array _get_transformable_selected_nodes(); void _emit_change(); @@ -233,6 +233,7 @@ protected: static void _bind_methods(); public: + Array get_selected_nodes(); void add_node(Node *p_node); void remove_node(Node *p_node); bool is_selected(Node *) const; diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp index 2ec3cdb08f..26f16e282e 100644 --- a/editor/editor_fonts.cpp +++ b/editor/editor_fonts.cpp @@ -85,6 +85,20 @@ static Ref<BitmapFont> make_font(int p_height, int p_ascent, int p_valign, int p m_name->set_spacing(DynamicFont::SPACING_BOTTOM, -EDSCALE); \ MAKE_FALLBACKS(m_name); +#define MAKE_BOLD_FONT(m_name, m_size) \ + Ref<DynamicFont> m_name; \ + m_name.instance(); \ + m_name->set_size(m_size); \ + if (CustomFont.is_valid()) { \ + m_name->set_font_data(CustomFontBold); \ + m_name->add_fallback(DefaultFontBold); \ + } else { \ + m_name->set_font_data(DefaultFontBold); \ + } \ + m_name->set_spacing(DynamicFont::SPACING_TOP, -EDSCALE); \ + m_name->set_spacing(DynamicFont::SPACING_BOTTOM, -EDSCALE); \ + MAKE_FALLBACKS(m_name); + #define MAKE_SOURCE_FONT(m_name, m_size) \ Ref<DynamicFont> m_name; \ m_name.instance(); \ @@ -102,25 +116,37 @@ static Ref<BitmapFont> make_font(int p_height, int p_ascent, int p_valign, int p void editor_register_fonts(Ref<Theme> p_theme) { /* Custom font */ - String custom_font = EditorSettings::get_singleton()->get("interface/editor/main_font"); DynamicFontData::Hinting font_hinting = (DynamicFontData::Hinting)(int)EditorSettings::get_singleton()->get("interface/editor/main_font_hinting"); + + String custom_font_path = EditorSettings::get_singleton()->get("interface/editor/main_font"); Ref<DynamicFontData> CustomFont; - if (custom_font.length() > 0) { + if (custom_font_path.length() > 0) { CustomFont.instance(); CustomFont->set_hinting(font_hinting); - CustomFont->set_font_path(custom_font); + CustomFont->set_font_path(custom_font_path); CustomFont->set_force_autohinter(true); //just looks better..i think? } + /* Custom Bold font */ + + String custom_font_path_bold = EditorSettings::get_singleton()->get("interface/editor/main_font_bold"); + Ref<DynamicFontData> CustomFontBold; + if (custom_font_path_bold.length() > 0) { + CustomFontBold.instance(); + CustomFontBold->set_hinting(font_hinting); + CustomFontBold->set_font_path(custom_font_path_bold); + CustomFontBold->set_force_autohinter(true); //just looks better..i think? + } + /* Custom source code font */ - String custom_font_source = EditorSettings::get_singleton()->get("interface/editor/code_font"); + String custom_font_path_source = EditorSettings::get_singleton()->get("interface/editor/code_font"); DynamicFontData::Hinting font_source_hinting = (DynamicFontData::Hinting)(int)EditorSettings::get_singleton()->get("interface/editor/code_font_hinting"); Ref<DynamicFontData> CustomFontSource; - if (custom_font_source.length() > 0) { + if (custom_font_path_source.length() > 0) { CustomFontSource.instance(); CustomFontSource->set_hinting(font_source_hinting); - CustomFontSource->set_font_path(custom_font_source); + CustomFontSource->set_font_path(custom_font_path_source); } /* Droid Sans */ @@ -131,6 +157,12 @@ void editor_register_fonts(Ref<Theme> p_theme) { DefaultFont->set_font_ptr(_font_NotoSansUI_Regular, _font_NotoSansUI_Regular_size); DefaultFont->set_force_autohinter(true); //just looks better..i think? + Ref<DynamicFontData> DefaultFontBold; + DefaultFontBold.instance(); + DefaultFont->set_hinting(font_hinting); + DefaultFontBold->set_font_ptr(_font_NotoSansUI_Bold, _font_NotoSansUI_Bold_size); + DefaultFontBold->set_force_autohinter(true); // just looks better..i think? + Ref<DynamicFontData> FontFallback; FontFallback.instance(); FontFallback->set_hinting(font_hinting); @@ -170,29 +202,38 @@ void editor_register_fonts(Ref<Theme> p_theme) { //dfd->set_force_autohinter(true); //just looks better..i think? int default_font_size = int(EditorSettings::get_singleton()->get("interface/editor/main_font_size")) * EDSCALE; - MAKE_DEFAULT_FONT(df, default_font_size); + // Default font + MAKE_DEFAULT_FONT(df, default_font_size); p_theme->set_default_theme_font(df); - MAKE_DEFAULT_FONT(df_title, default_font_size + 2 * EDSCALE); + // Bold font + MAKE_BOLD_FONT(df_bold, default_font_size); + p_theme->set_font("bold", "EditorFonts", df_bold); + + // Title font + MAKE_BOLD_FONT(df_title, default_font_size + 2 * EDSCALE); p_theme->set_font("title", "EditorFonts", df_title); - MAKE_DEFAULT_FONT(df_doc_title, int(EDITOR_DEF("text_editor/help/help_title_font_size", 23)) * EDSCALE); + // Doc font + MAKE_BOLD_FONT(df_doc_title, int(EDITOR_DEF("text_editor/help/help_title_font_size", 23)) * EDSCALE); MAKE_DEFAULT_FONT(df_doc, int(EDITOR_DEF("text_editor/help/help_font_size", 15)) * EDSCALE); p_theme->set_font("doc", "EditorFonts", df_doc); p_theme->set_font("doc_title", "EditorFonts", df_doc_title); + MAKE_SOURCE_FONT(df_doc_code, int(EDITOR_DEF("text_editor/help/help_source_font_size", 14)) * EDSCALE); + p_theme->set_font("doc_source", "EditorFonts", df_doc_code); + + // Ruler font MAKE_DEFAULT_FONT(df_rulers, 8 * EDSCALE); p_theme->set_font("rulers", "EditorFonts", df_rulers); + // Code font MAKE_SOURCE_FONT(df_code, int(EditorSettings::get_singleton()->get("interface/editor/code_font_size")) * EDSCALE); p_theme->set_font("source", "EditorFonts", df_code); - MAKE_SOURCE_FONT(df_doc_code, int(EDITOR_DEF("text_editor/help/help_source_font_size", 14)) * EDSCALE); - p_theme->set_font("doc_source", "EditorFonts", df_doc_code); - MAKE_SOURCE_FONT(df_output_code, int(EDITOR_DEF("run/output/font_size", 13)) * EDSCALE); p_theme->set_font("output_source", "EditorFonts", df_output_code); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index d8c85df83d..fd5a6dffc9 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -93,6 +93,7 @@ #include "editor/plugins/particles_editor_plugin.h" #include "editor/plugins/path_2d_editor_plugin.h" #include "editor/plugins/path_editor_plugin.h" +#include "editor/plugins/physical_bone_plugin.h" #include "editor/plugins/polygon_2d_editor_plugin.h" #include "editor/plugins/resource_preloader_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" @@ -100,6 +101,7 @@ #include "editor/plugins/shader_editor_plugin.h" #include "editor/plugins/shader_graph_editor_plugin.h" #include "editor/plugins/skeleton_2d_editor_plugin.h" +#include "editor/plugins/skeleton_editor_plugin.h" #include "editor/plugins/spatial_editor_plugin.h" #include "editor/plugins/sprite_editor_plugin.h" #include "editor/plugins/sprite_frames_editor_plugin.h" @@ -631,6 +633,7 @@ void EditorNode::save_resource_in_path(const Ref<Resource> &p_resource, const St ((Resource *)p_resource.ptr())->set_path(path); emit_signal("resource_saved", p_resource); + editor_data.notify_resource_saved(p_resource); } void EditorNode::save_resource(const Ref<Resource> &p_resource) { @@ -5880,6 +5883,8 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor))); add_editor_plugin(memnew(AudioBusesEditorPlugin(audio_bus_editor))); add_editor_plugin(memnew(NavigationMeshEditorPlugin(this))); + add_editor_plugin(memnew(SkeletonEditorPlugin(this))); + add_editor_plugin(memnew(PhysicalBonePlugin(this))); // FIXME: Disabled as (according to reduz) users were complaining that it gets in the way // Waiting for PropertyEditor rewrite (planned for 3.1) to be refactored. diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index 336eaf719c..2e4e887165 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -473,6 +473,10 @@ void EditorPlugin::notify_scene_closed(const String &scene_filepath) { emit_signal("scene_closed", scene_filepath); } +void EditorPlugin::notify_resource_saved(const Ref<Resource> &p_resource) { + emit_signal("resource_saved", p_resource); +} + Ref<SpatialEditorGizmo> EditorPlugin::create_spatial_gizmo(Spatial *p_spatial) { //?? if (get_script_instance() && get_script_instance()->has_method("create_spatial_gizmo")) { @@ -757,6 +761,7 @@ void EditorPlugin::_bind_methods() { ADD_SIGNAL(MethodInfo("scene_changed", PropertyInfo(Variant::OBJECT, "scene_root", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("scene_closed", PropertyInfo(Variant::STRING, "filepath"))); ADD_SIGNAL(MethodInfo("main_screen_changed", PropertyInfo(Variant::STRING, "screen_name"))); + ADD_SIGNAL(MethodInfo("resource_saved", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"))); BIND_ENUM_CONSTANT(CONTAINER_TOOLBAR); BIND_ENUM_CONSTANT(CONTAINER_SPATIAL_EDITOR_MENU); diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h index 05cc6e07e3..ebc4afdfeb 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -167,6 +167,7 @@ public: void notify_main_screen_changed(const String &screen_name); void notify_scene_changed(const Node *scn_root); void notify_scene_closed(const String &scene_filepath); + void notify_resource_saved(const Ref<Resource> &p_resource); virtual Ref<SpatialEditorGizmo> create_spatial_gizmo(Spatial *p_spatial); virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 9f55ae32b0..6eae7be9d5 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -298,6 +298,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { hints["interface/editor/code_font_hinting"] = PropertyInfo(Variant::INT, "interface/editor/code_font_hinting", PROPERTY_HINT_ENUM, "None,Light,Normal", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("interface/editor/main_font", ""); hints["interface/editor/main_font"] = PropertyInfo(Variant::STRING, "interface/editor/main_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + _initial_set("interface/editor/main__bold_font", ""); + hints["interface/editor/main_font_bold"] = PropertyInfo(Variant::STRING, "interface/editor/main_bold_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("interface/editor/code_font", ""); hints["interface/editor/code_font"] = PropertyInfo(Variant::STRING, "interface/editor/code_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("interface/editor/dim_editor_on_dialog_popup", true); @@ -400,8 +402,17 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("editors/grid_map/pick_distance", 5000.0); - _initial_set("editors/3d/grid_color", Color::html("808080")); - hints["editors/3d/grid_color"] = PropertyInfo(Variant::COLOR, "editors/3d/grid_color", PROPERTY_HINT_COLOR_NO_ALPHA, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + _initial_set("editors/3d/primary_grid_color", Color::html("909090")); + hints["editors/3d/primary_grid_color"] = PropertyInfo(Variant::COLOR, "editors/3d/primary_grid_color", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + + _initial_set("editors/3d/secondary_grid_color", Color::html("606060")); + hints["editors/3d/secondary_grid_color"] = PropertyInfo(Variant::COLOR, "editors/3d/secondary_grid_color", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + + _initial_set("editors/3d/grid_size", 50); + hints["editors/3d/grid_size"] = PropertyInfo(Variant::INT, "editors/3d/grid_size", PROPERTY_HINT_RANGE, "1,500,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); + + _initial_set("editors/3d/primary_grid_steps", 10); + hints["editors/3d/primary_grid_steps"] = PropertyInfo(Variant::INT, "editors/3d/primary_grid_steps", PROPERTY_HINT_RANGE, "1,100,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED); _initial_set("editors/3d/default_fov", 70.0); _initial_set("editors/3d/default_z_near", 0.05); diff --git a/editor/icons/icon_physical_bone.svg b/editor/icons/icon_physical_bone.svg new file mode 100644 index 0000000000..2efcab3e20 --- /dev/null +++ b/editor/icons/icon_physical_bone.svg @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + version="1.1" + viewBox="0 0 16 16" + id="svg2" + inkscape:version="0.91 r13725" + sodipodi:docname="icon_physical_bone.svg"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1027" + id="namedview8" + showgrid="false" + inkscape:zoom="16" + inkscape:cx="14.674088" + inkscape:cy="7.3239349" + inkscape:window-x="-8" + inkscape:window-y="-8" + inkscape:window-maximized="1" + inkscape:current-layer="svg2" /> + <g + id="g4505" + transform="translate(-2.5625,-18.4375)"> + <path + inkscape:connector-curvature="0" + id="path6-9-8" + d="m 13.107422,19.382812 a 2.4664,2.4663 0 0 0 -1.78125,0.720704 2.4664,2.4663 0 0 0 -0.185547,0.21289 L 12.472656,22.75 10.867187,23.353516 7.453125,26.767578 a 2.4664,2.4663 0 0 0 -3.1015625,0.3125 2.4664,2.4663 0 0 0 0,3.488281 2.4664,2.4663 0 0 0 1.3964844,0.695313 2.4664,2.4663 0 0 0 0.6953125,1.396484 2.4664,2.4663 0 0 0 3.4882812,0 2.4664,2.4663 0 0 0 0.3144534,-3.103515 l 3.560547,-3.560547 a 2.4664,2.4663 0 0 0 3.099609,-0.310547 2.4664,2.4663 0 0 0 0,-3.488281 A 2.4664,2.4663 0 0 0 15.509766,21.5 2.4664,2.4663 0 0 0 14.814453,20.103516 2.4664,2.4663 0 0 0 13.107422,19.382812 Z" + style="fill:#fc9c9c" /> + <path + sodipodi:nodetypes="cccc" + id="rect4140-5-1-4-3-7-9-03" + d="m 3.7211033,21.208326 0.9608286,4.82644 1.3962404,-0.524494 z" + style="opacity:1;fill:#fc9c9c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.42799997;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + inkscape:connector-curvature="0" /> + <path + sodipodi:nodetypes="cccc" + id="rect4140-5-1-4-3-7-9-5-3" + d="m 6.4843278,19.465234 0.9608285,4.82644 1.3962404,-0.524494 z" + style="opacity:1;fill:#fc9c9c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.42799997;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + inkscape:connector-curvature="0" /> + <path + sodipodi:nodetypes="cccc" + id="rect4140-5-1-4-3-7-9-5-3-8" + d="m 9.6964655,19.33678 0.7108285,3.51394 1.39624,-0.524494 z" + style="opacity:1;fill:#fc9c9c;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.42799997;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 8cad40c9ce..93aeca6632 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -467,32 +467,28 @@ void CanvasItemEditor::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_no const real_t grab_distance = EDITOR_DEF("editors/poly_editor/point_grab_radius", 8); CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); - bool locked = p_node->has_meta("_edit_lock_") && p_node->get_meta("_edit_lock_"); - - if (!locked) { - for (int i = p_node->get_child_count() - 1; i >= 0; i--) { - if (canvas_item && !canvas_item->is_set_as_toplevel()) { - _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_limit, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); - } else { - CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); - _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_limit, Transform2D(), cl ? cl->get_transform() : p_canvas_xform); - } - if (p_limit != 0 && r_items.size() >= p_limit) - return; + for (int i = p_node->get_child_count() - 1; i >= 0; i--) { + if (canvas_item && !canvas_item->is_set_as_toplevel()) { + _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_limit, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); + } else { + CanvasLayer *cl = Object::cast_to<CanvasLayer>(p_node); + _find_canvas_items_at_pos(p_pos, p_node->get_child(i), r_items, p_limit, Transform2D(), cl ? cl->get_transform() : p_canvas_xform); } + if (p_limit != 0 && r_items.size() >= p_limit) + return; + } - if (canvas_item && canvas_item->is_visible_in_tree()) { - Transform2D xform = (p_parent_xform * p_canvas_xform * canvas_item->get_transform()).affine_inverse(); - const real_t local_grab_distance = xform.basis_xform(Vector2(grab_distance, 0)).length(); - if (canvas_item->_edit_is_selected_on_click(xform.xform(p_pos), local_grab_distance)) { - Node2D *node = Object::cast_to<Node2D>(canvas_item); + if (canvas_item && canvas_item->is_visible_in_tree()) { + Transform2D xform = (p_parent_xform * p_canvas_xform * canvas_item->get_transform()).affine_inverse(); + const real_t local_grab_distance = xform.basis_xform(Vector2(grab_distance, 0)).length(); + if (canvas_item->_edit_is_selected_on_click(xform.xform(p_pos), local_grab_distance)) { + Node2D *node = Object::cast_to<Node2D>(canvas_item); - _SelectResult res; - res.item = canvas_item; - res.z_index = node ? node->get_z_index() : 0; - res.has_z = node; - r_items.push_back(res); - } + _SelectResult res; + res.item = canvas_item; + res.z_index = node ? node->get_z_index() : 0; + res.has_z = node; + r_items.push_back(res); } } @@ -509,14 +505,14 @@ void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_Sel for (int i = 0; i < r_items.size(); i++) { Node *node = r_items[i].item; - // Make sure the selected node is in the current scene - while (node && node != scene && node->get_owner() != scene) { + // Make sure the selected node is in the current scene, or editable + while (node && node != get_tree()->get_edited_scene_root() && node->get_owner() != scene && !scene->is_editable_instance(node->get_owner())) { node = node->get_parent(); }; // Replace the node by the group if grouped CanvasItem *canvas_item = Object::cast_to<CanvasItem>(node); - while (node && node != scene) { + while (node && node != scene->get_parent()) { CanvasItem *canvas_item_tmp = Object::cast_to<CanvasItem>(node); if (canvas_item_tmp && node->has_meta("_edit_group_")) { canvas_item = canvas_item_tmp; @@ -525,7 +521,7 @@ void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_Sel } //Remove the item if invalid - if (!canvas_item || (canvas_item != scene && canvas_item->get_owner() != scene && !scene->is_editable_instance(canvas_item->get_owner()))) { + if (!canvas_item || (canvas_item != scene && canvas_item->get_owner() != scene && !scene->is_editable_instance(canvas_item->get_owner())) || (canvas_item->has_meta("_edit_lock_") && canvas_item->get_meta("_edit_lock_"))) { r_items.remove(i); i--; } else { @@ -541,13 +537,13 @@ void CanvasItemEditor::_find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_n return; CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); + Node *scene = editor->get_edited_scene(); - bool inherited = p_node != get_tree()->get_edited_scene_root() && p_node->get_filename() != ""; - bool editable = !inherited || EditorNode::get_singleton()->get_edited_scene()->is_editable_instance(p_node); + bool editable = p_node == scene || p_node->get_owner() == scene || scene->is_editable_instance(p_node->get_owner()); bool lock_children = p_node->has_meta("_edit_group_") && p_node->get_meta("_edit_group_"); bool locked = p_node->has_meta("_edit_lock_") && p_node->get_meta("_edit_lock_"); - if (!lock_children && !locked && editable) { + if (!lock_children || !editable) { for (int i = p_node->get_child_count() - 1; i >= 0; i--) { if (canvas_item && !canvas_item->is_set_as_toplevel()) { _find_canvas_items_in_rect(p_rect, p_node->get_child(i), r_items, p_parent_xform * canvas_item->get_transform(), p_canvas_xform); @@ -558,7 +554,7 @@ void CanvasItemEditor::_find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_n } } - if (canvas_item && canvas_item->is_visible_in_tree() && !canvas_item->has_meta("_edit_lock_")) { + if (canvas_item && canvas_item->is_visible_in_tree() && !locked && editable) { Transform2D xform = p_parent_xform * p_canvas_xform * canvas_item->get_transform(); if (canvas_item->_edit_use_rect()) { @@ -2593,9 +2589,6 @@ void CanvasItemEditor::_draw_bones() { for (Map<BoneKey, BoneList>::Element *E = bone_list.front(); E; E = E->next()) { - E->get().from = Vector2(); - E->get().to = Vector2(); - Node2D *from_node = Object::cast_to<Node2D>(ObjectDB::get_instance(E->key().from)); Node2D *to_node = Object::cast_to<Node2D>(ObjectDB::get_instance(E->key().to)); @@ -2615,9 +2608,6 @@ void CanvasItemEditor::_draw_bones() { else to = transform.xform(from_node->get_global_transform().xform(Vector2(E->get().length, 0))); - E->get().from = from; - E->get().to = to; - Vector2 rel = to - from; Vector2 relt = rel.tangent().normalized() * bone_width; Vector2 reln = rel.normalized(); @@ -2679,7 +2669,7 @@ void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Trans ERR_FAIL_COND(!p_node); Node *scene = editor->get_edited_scene(); - if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node)) + if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node->get_owner())) return; CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); if (canvas_item && !canvas_item->is_visible()) @@ -2805,27 +2795,20 @@ bool CanvasItemEditor::_build_bones_list(Node *p_node) { } } - CanvasItem *c = Object::cast_to<CanvasItem>(p_node); - if (!c) { - return false; - } - - Node *p = c->get_parent(); - if (!p) { - return false; - } - - if (!c->is_visible()) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); + Node *scene = editor->get_edited_scene(); + if (!canvas_item || !canvas_item->is_visible() || (canvas_item != scene && canvas_item->get_owner() != scene && !scene->is_editable_instance(canvas_item->get_owner()))) { return false; } - if (Object::cast_to<Bone2D>(c)) { + Node *parent = canvas_item->get_parent(); - if (Object::cast_to<Bone2D>(p)) { - //add as bone->parent relationship + if (Object::cast_to<Bone2D>(canvas_item)) { + if (Object::cast_to<Bone2D>(parent)) { + // Add as bone->parent relationship BoneKey bk; - bk.from = p->get_instance_id(); - bk.to = c->get_instance_id(); + bk.from = parent->get_instance_id(); + bk.to = canvas_item->get_instance_id(); if (!bone_list.has(bk)) { BoneList b; b.length = 0; @@ -2836,8 +2819,9 @@ bool CanvasItemEditor::_build_bones_list(Node *p_node) { } if (!has_child_bones) { + // Add a last bone if the Bone2D has no Bone2D child BoneKey bk; - bk.from = c->get_instance_id(); + bk.from = canvas_item->get_instance_id(); bk.to = 0; if (!bone_list.has(bk)) { BoneList b; @@ -2849,11 +2833,12 @@ bool CanvasItemEditor::_build_bones_list(Node *p_node) { return true; } - if (c->has_meta("_edit_bone_")) { + if (canvas_item->has_meta("_edit_bone_")) { + // Add a "custom bone" BoneKey bk; - bk.from = c->get_parent()->get_instance_id(); - bk.to = c->get_instance_id(); + bk.from = parent->get_instance_id(); + bk.to = canvas_item->get_instance_id(); if (!bone_list.has(bk)) { BoneList b; b.length = 0; @@ -2938,6 +2923,7 @@ void CanvasItemEditor::_notification(int p_what) { int nb_control = 0; int nb_having_pivot = 0; + // Update the viewport if the canvas_item changes List<CanvasItem *> selection = _get_edited_canvas_items(); for (List<CanvasItem *>::Element *E = selection.front(); E; E = E->next()) { CanvasItem *canvas_item = E->get(); @@ -2983,12 +2969,14 @@ void CanvasItemEditor::_notification(int p_what) { nb_having_pivot++; } } + // Activate / Deactivate the pivot tool pivot_button->set_disabled(nb_having_pivot == 0); // Show / Hide the layout button presets_menu->set_visible(nb_control > 0 && nb_control == selection.size()); + // Update the viewport if bones changes for (Map<BoneKey, BoneList>::Element *E = bone_list.front(); E; E = E->next()) { Object *b = ObjectDB::get_instance(E->key().from); @@ -2999,7 +2987,7 @@ void CanvasItemEditor::_notification(int p_what) { } Node2D *b2 = Object::cast_to<Node2D>(b); - if (!b2) { + if (!b2 || !b2->is_inside_tree()) { continue; } diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index eb3595cae6..a1957b892e 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -266,8 +266,6 @@ class CanvasItemEditor : public VBoxContainer { Transform2D xform; float length; uint64_t last_pass; - Vector2 from; - Vector2 to; }; uint64_t bone_last_frame; diff --git a/editor/plugins/physical_bone_plugin.cpp b/editor/plugins/physical_bone_plugin.cpp new file mode 100644 index 0000000000..42f1adcadf --- /dev/null +++ b/editor/plugins/physical_bone_plugin.cpp @@ -0,0 +1,123 @@ +/*************************************************************************/ +/* physical_bone_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "physical_bone_plugin.h" +#include "editor/plugins/spatial_editor_plugin.h" +#include "scene/3d/physics_body.h" + +void PhysicalBoneEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_on_toggle_button_transform_joint", "is_pressed"), &PhysicalBoneEditor::_on_toggle_button_transform_joint); +} + +void PhysicalBoneEditor::_on_toggle_button_transform_joint(bool p_is_pressed) { + + _set_move_joint(); +} + +void PhysicalBoneEditor::_set_move_joint() { + if (selected) { + selected->_set_gizmo_move_joint(button_transform_joint->is_pressed()); + } +} + +PhysicalBoneEditor::PhysicalBoneEditor(EditorNode *p_editor) : + editor(p_editor), + selected(NULL) { + + spatial_editor_hb = memnew(HBoxContainer); + spatial_editor_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + spatial_editor_hb->set_alignment(BoxContainer::ALIGN_BEGIN); + SpatialEditor::get_singleton()->add_control_to_menu_panel(spatial_editor_hb); + + spatial_editor_hb->add_child(memnew(VSeparator)); + + button_transform_joint = memnew(ToolButton); + spatial_editor_hb->add_child(button_transform_joint); + + button_transform_joint->set_text(TTR("Move joint")); + button_transform_joint->set_icon(SpatialEditor::get_singleton()->get_icon("PhysicalBone", "EditorIcons")); + button_transform_joint->set_toggle_mode(true); + button_transform_joint->connect("toggled", this, "_on_toggle_button_transform_joint"); + + hide(); +} + +PhysicalBoneEditor::~PhysicalBoneEditor() { + // TODO the spatial_editor_hb should be removed from SpatialEditor, but in this moment it's not possible + for (int i = spatial_editor_hb->get_child_count() - 1; 0 <= i; --i) { + Node *n = spatial_editor_hb->get_child(i); + spatial_editor_hb->remove_child(n); + memdelete(n); + } + memdelete(spatial_editor_hb); +} + +void PhysicalBoneEditor::set_selected(PhysicalBone *p_pb) { + + button_transform_joint->set_pressed(false); + + _set_move_joint(); + selected = p_pb; + _set_move_joint(); +} + +void PhysicalBoneEditor::hide() { + spatial_editor_hb->hide(); +} + +void PhysicalBoneEditor::show() { + spatial_editor_hb->show(); +} + +PhysicalBonePlugin::PhysicalBonePlugin(EditorNode *p_editor) : + editor(p_editor), + selected(NULL) { + + physical_bone_editor = memnew(PhysicalBoneEditor(editor)); +} + +void PhysicalBonePlugin::make_visible(bool p_visible) { + if (p_visible) { + + physical_bone_editor->show(); + } else { + + physical_bone_editor->hide(); + physical_bone_editor->set_selected(NULL); + selected = NULL; + } +} + +void PhysicalBonePlugin::edit(Object *p_node) { + selected = static_cast<PhysicalBone *>(p_node); // Trust it + ERR_FAIL_COND(!selected); + + physical_bone_editor->set_selected(selected); +} diff --git a/editor/plugins/physical_bone_plugin.h b/editor/plugins/physical_bone_plugin.h new file mode 100644 index 0000000000..9e7a50307a --- /dev/null +++ b/editor/plugins/physical_bone_plugin.h @@ -0,0 +1,78 @@ +/*************************************************************************/ +/* physical_bone_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 PHYSICAL_BONE_PLUGIN_H +#define PHYSICAL_BONE_PLUGIN_H + +#include "editor/editor_node.h" + +class PhysicalBoneEditor : public Object { + GDCLASS(PhysicalBoneEditor, Object); + + EditorNode *editor; + HBoxContainer *spatial_editor_hb; + ToolButton *button_transform_joint; + + PhysicalBone *selected; + +protected: + static void _bind_methods(); + +private: + void _on_toggle_button_transform_joint(bool p_is_pressed); + void _set_move_joint(); + +public: + PhysicalBoneEditor(EditorNode *p_editor); + ~PhysicalBoneEditor(); + + void set_selected(PhysicalBone *p_pb); + + void hide(); + void show(); +}; + +class PhysicalBonePlugin : public EditorPlugin { + GDCLASS(PhysicalBonePlugin, EditorPlugin); + + EditorNode *editor; + PhysicalBone *selected; + PhysicalBoneEditor *physical_bone_editor; + +public: + virtual String get_name() const { return "PhysicalBone"; } + virtual bool handles(Object *p_object) const { return p_object->is_class("PhysicalBone"); } + virtual void make_visible(bool p_visible); + virtual void edit(Object *p_node); + + PhysicalBonePlugin(EditorNode *p_editor); +}; + +#endif diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index c872a6f28b..45f5e667fa 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -746,6 +746,8 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c _goto_line(p_row); + result.class_name = result.class_name.trim_prefix("_"); + switch (result.type) { case ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION: { @@ -1007,6 +1009,10 @@ void ScriptTextEditor::_edit_option(int p_op) { } int next_line = to_line + 1; + if (to_line >= tx->get_line_count() - 1) { + tx->set_line(to_line, tx->get_line(to_line) + "\n"); + } + tx->begin_complex_operation(); for (int i = from_line; i <= to_line; i++) { diff --git a/editor/plugins/skeleton_editor_plugin.cpp b/editor/plugins/skeleton_editor_plugin.cpp new file mode 100644 index 0000000000..c41e3b546f --- /dev/null +++ b/editor/plugins/skeleton_editor_plugin.cpp @@ -0,0 +1,189 @@ +/*************************************************************************/ +/* skeleton_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "skeleton_editor_plugin.h" +#include "scene/3d/collision_shape.h" +#include "scene/3d/physics_body.h" +#include "scene/3d/physics_joint.h"; +#include "scene/resources/capsule_shape.h" +#include "scene/resources/sphere_shape.h" +#include "spatial_editor_plugin.h" + +void SkeletonEditor::_on_click_option(int p_option) { + if (!skeleton) { + return; + } + + switch (p_option) { + case MENU_OPTION_CREATE_PHYSICAL_SKELETON: { + create_physical_skeleton(); + } break; + } +} + +void SkeletonEditor::create_physical_skeleton() { + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + Node *owner = skeleton == get_tree()->get_edited_scene_root() ? skeleton : skeleton->get_owner(); + + const int bc = skeleton->get_bone_count(); + + if (!bc) { + return; + } + + Vector<BoneInfo> bones_infos; + bones_infos.resize(bc); + + for (int bone_id = 0; bc > bone_id; ++bone_id) { + + const int parent = skeleton->get_bone_parent(bone_id); + const int parent_parent = skeleton->get_bone_parent(parent); + + if (parent < 0) { + + bones_infos[bone_id].relative_rest = skeleton->get_bone_rest(bone_id); + + } else { + + bones_infos[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id); + + /// create physical bone on parent + if (!bones_infos[parent].physical_bone) { + + bones_infos[parent].physical_bone = create_physical_bone(parent, bone_id, bones_infos); + + ur->create_action(TTR("Create physical bones")); + ur->add_do_method(skeleton, "add_child", bones_infos[parent].physical_bone); + ur->add_do_reference(bones_infos[parent].physical_bone); + ur->add_undo_method(skeleton, "remove_child", bones_infos[parent].physical_bone); + ur->commit_action(); + + bones_infos[parent].physical_bone->set_bone_name(skeleton->get_bone_name(parent)); + bones_infos[parent].physical_bone->set_owner(owner); + bones_infos[parent].physical_bone->get_child(0)->set_owner(owner); // set shape owner + + /// Create joint between parent of parent + if (-1 != parent_parent) { + + bones_infos[parent].physical_bone->set_joint_type(PhysicalBone::JOINT_TYPE_PIN); + } + } + } + } +} + +PhysicalBone *SkeletonEditor::create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos) { + + real_t half_height(skeleton->get_bone_rest(bone_child_id).origin.length() * 0.5); + real_t radius(half_height * 0.2); + + CapsuleShape *bone_shape_capsule = memnew(CapsuleShape); + bone_shape_capsule->set_height((half_height - radius) * 2); + bone_shape_capsule->set_radius(radius); + + CollisionShape *bone_shape = memnew(CollisionShape); + bone_shape->set_shape(bone_shape_capsule); + + Transform body_transform; + body_transform.origin = Vector3(0, 0, -half_height); + + Transform joint_transform; + joint_transform.origin = Vector3(0, 0, half_height); + + PhysicalBone *physical_bone = memnew(PhysicalBone); + physical_bone->add_child(bone_shape); + physical_bone->set_name("Physical Bone " + skeleton->get_bone_name(bone_id)); + physical_bone->set_body_offset(body_transform); + physical_bone->set_joint_offset(joint_transform); + return physical_bone; +} + +void SkeletonEditor::edit(Skeleton *p_node) { + skeleton = p_node; +} + +void SkeletonEditor::_node_removed(Node *p_node) { + + if (p_node == skeleton) { + skeleton = NULL; + options->hide(); + } +} + +void SkeletonEditor::_bind_methods() { + ClassDB::bind_method("_on_click_option", &SkeletonEditor::_on_click_option); +} + +SkeletonEditor::SkeletonEditor() { + options = memnew(MenuButton); + SpatialEditor::get_singleton()->add_control_to_menu_panel(options); + + options->set_text(TTR("Skeleton")); + options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("Skeleton", "EditorIcons")); + + options->get_popup()->add_item(TTR("Create physical skeleton"), MENU_OPTION_CREATE_PHYSICAL_SKELETON); + + options->get_popup()->connect("id_pressed", this, "_on_click_option"); + options->hide(); +} + +SkeletonEditor::~SkeletonEditor() { + SpatialEditor::get_singleton()->remove_child(options); + memdelete(options); +} + +void SkeletonEditorPlugin::edit(Object *p_object) { + skeleton_editor->edit(Object::cast_to<Skeleton>(p_object)); +} + +bool SkeletonEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("Skeleton"); +} + +void SkeletonEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + skeleton_editor->options->show(); + } else { + + skeleton_editor->options->hide(); + skeleton_editor->edit(NULL); + } +} + +SkeletonEditorPlugin::SkeletonEditorPlugin(EditorNode *p_node) { + editor = p_node; + skeleton_editor = memnew(SkeletonEditor); + editor->get_viewport()->add_child(skeleton_editor); +} + +SkeletonEditorPlugin::~SkeletonEditorPlugin() { + editor->get_viewport()->remove_child(skeleton_editor); + memdelete(skeleton_editor); +} diff --git a/editor/plugins/skeleton_editor_plugin.h b/editor/plugins/skeleton_editor_plugin.h new file mode 100644 index 0000000000..b9bdf91902 --- /dev/null +++ b/editor/plugins/skeleton_editor_plugin.h @@ -0,0 +1,95 @@ +/*************************************************************************/ +/* skeleton_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 SKELETON_EDITOR_PLUGIN_H +#define SKELETON_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "scene/3d/skeleton.h" + +class PhysicalBone; +class Joint; + +class SkeletonEditor : public Node { + GDCLASS(SkeletonEditor, Node); + + enum Menu { + MENU_OPTION_CREATE_PHYSICAL_SKELETON + }; + + struct BoneInfo { + PhysicalBone *physical_bone; + Transform relative_rest; // Relative to skeleton node + BoneInfo() : + physical_bone(NULL) {} + }; + + Skeleton *skeleton; + + MenuButton *options; + + void _on_click_option(int p_option); + + friend class SkeletonEditorPlugin; + +protected: + void _node_removed(Node *p_node); + static void _bind_methods(); + + void create_physical_skeleton(); + PhysicalBone *create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos); + +public: + void edit(Skeleton *p_mesh); + + SkeletonEditor(); + ~SkeletonEditor(); +}; + +class SkeletonEditorPlugin : public EditorPlugin { + + GDCLASS(SkeletonEditorPlugin, EditorPlugin); + + EditorNode *editor; + SkeletonEditor *skeleton_editor; + +public: + virtual String get_name() const { return "Skeleton"; } + virtual bool has_main_screen() const { return false; } + virtual void edit(Object *p_object); + virtual bool handles(Object *p_object) const; + virtual void make_visible(bool p_visible); + + SkeletonEditorPlugin(EditorNode *p_node); + ~SkeletonEditorPlugin(); +}; + +#endif // SKELETON_EDITOR_PLUGIN_H diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index 1443f22c8f..5b713ef3c4 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -72,6 +72,14 @@ #define MIN_FOV 0.01 #define MAX_FOV 179 +#ifdef TOOLS_ENABLED +#define get_global_gizmo_transform get_global_gizmo_transform +#define get_local_gizmo_transform get_local_gizmo_transform +#else +#define get_global_gizmo_transform get_global_transform +#define get_local_gizmo_transform get_transform +#endif + void SpatialEditorViewport::_update_camera(float p_interp_delta) { bool is_orthogonal = camera->get_projection() == Camera::PROJECTION_ORTHOGONAL; @@ -584,8 +592,8 @@ void SpatialEditorViewport::_compute_edit(const Point2 &p_point) { if (!se) continue; - se->original = se->sp->get_global_transform(); - se->original_local = se->sp->get_transform(); + se->original = se->sp->get_global_gizmo_transform(); + se->original_local = se->sp->get_local_gizmo_transform(); } } @@ -1184,7 +1192,7 @@ void SpatialEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (!se) continue; - undo_redo->add_do_method(sp, "set_global_transform", sp->get_global_transform()); + undo_redo->add_do_method(sp, "set_global_transform", sp->get_global_gizmo_transform()); undo_redo->add_undo_method(sp, "set_global_transform", se->original); } undo_redo->commit_action(); @@ -2150,7 +2158,7 @@ void SpatialEditorViewport::_notification(int p_what) { se->aabb = vi ? vi->get_aabb() : AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4)); } - Transform t = sp->get_global_transform(); + Transform t = sp->get_global_gizmo_transform(); t.translate(se->aabb.position); // apply AABB scaling before item's global transform @@ -2503,7 +2511,7 @@ void SpatialEditorViewport::_menu_option(int p_option) { xform.scale_basis(sp->get_scale()); undo_redo->add_do_method(sp, "set_global_transform", xform); - undo_redo->add_undo_method(sp, "set_global_transform", sp->get_global_transform()); + undo_redo->add_undo_method(sp, "set_global_transform", sp->get_global_gizmo_transform()); } undo_redo->commit_action(); } break; @@ -2961,7 +2969,7 @@ void SpatialEditorViewport::focus_selection() { if (!se) continue; - center += sp->get_global_transform().origin; + center += sp->get_global_gizmo_transform().origin; count++; } @@ -3043,7 +3051,7 @@ AABB SpatialEditorViewport::_calculate_spatial_bounds(const Spatial *p_parent, c MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(child); if (mesh_instance) { AABB mesh_instance_bounds = mesh_instance->get_aabb(); - mesh_instance_bounds.position += mesh_instance->get_global_transform().origin - p_parent->get_global_transform().origin; + mesh_instance_bounds.position += mesh_instance->get_global_gizmo_transform().origin - p_parent->get_global_gizmo_transform().origin; bounds.merge_with(mesh_instance_bounds); } bounds = _calculate_spatial_bounds(child, bounds); @@ -3121,7 +3129,7 @@ bool SpatialEditorViewport::_create_instance(Node *parent, String &path, const P if (!scene.is_valid()) { // invalid scene return false; } else { - instanced_scene = scene->instance(); + instanced_scene = scene->instance(PackedScene::GEN_EDIT_STATE_INSTANCE); } } } @@ -3154,7 +3162,7 @@ bool SpatialEditorViewport::_create_instance(Node *parent, String &path, const P Transform global_transform; Spatial *parent_spatial = Object::cast_to<Spatial>(parent); if (parent_spatial) - global_transform = parent_spatial->get_global_transform(); + global_transform = parent_spatial->get_global_gizmo_transform(); global_transform.origin = spatial_editor->snap_point(_get_instance_position(p_point)); @@ -3787,7 +3795,8 @@ void SpatialEditor::update_transform_gizmo() { if (!se) continue; - Transform xf = se->sp->get_global_transform(); + Transform xf = se->sp->get_global_gizmo_transform(); + if (first) { center.position = xf.origin; first = false; @@ -4054,7 +4063,7 @@ void SpatialEditor::_xform_dialog_action() { bool post = xform_type->get_selected() > 0; - Transform tr = sp->get_global_transform(); + Transform tr = sp->get_global_gizmo_transform(); if (post) tr = tr * t; else { @@ -4064,7 +4073,7 @@ void SpatialEditor::_xform_dialog_action() { } undo_redo->add_do_method(sp, "set_global_transform", tr); - undo_redo->add_undo_method(sp, "set_global_transform", sp->get_global_transform()); + undo_redo->add_undo_method(sp, "set_global_transform", sp->get_global_gizmo_transform()); } undo_redo->commit_action(); } @@ -4598,7 +4607,10 @@ void SpatialEditor::_init_grid() { PoolVector<Color> grid_colors[3]; PoolVector<Vector3> grid_points[3]; - Color grid_color = EditorSettings::get_singleton()->get("editors/3d/grid_color"); + Color primary_grid_color = EditorSettings::get_singleton()->get("editors/3d/primary_grid_color"); + Color secondary_grid_color = EditorSettings::get_singleton()->get("editors/3d/secondary_grid_color"); + int grid_size = EditorSettings::get_singleton()->get("editors/3d/grid_size"); + int primary_grid_steps = EditorSettings::get_singleton()->get("editors/3d/primary_grid_steps"); for (int i = 0; i < 3; i++) { Vector3 axis; @@ -4608,19 +4620,17 @@ void SpatialEditor::_init_grid() { Vector3 axis_n2; axis_n2[(i + 2) % 3] = 1; -#define ORIGIN_GRID_SIZE 50 - - for (int j = -ORIGIN_GRID_SIZE; j <= ORIGIN_GRID_SIZE; j++) { - Vector3 p1 = axis_n1 * j + axis_n2 * -ORIGIN_GRID_SIZE; + for (int j = -grid_size; j <= grid_size; j++) { + Vector3 p1 = axis_n1 * j + axis_n2 * -grid_size; Vector3 p1_dest = p1 * (-axis_n2 + axis_n1); - Vector3 p2 = axis_n2 * j + axis_n1 * -ORIGIN_GRID_SIZE; + Vector3 p2 = axis_n2 * j + axis_n1 * -grid_size; Vector3 p2_dest = p2 * (-axis_n1 + axis_n2); - Color line_color = grid_color; + Color line_color = secondary_grid_color; if (j == 0) { continue; - } else if (j % 10 == 0) { - line_color *= 1.5; + } else if (j % primary_grid_steps == 0) { + line_color = primary_grid_color; } grid_points[i].push_back(p1); diff --git a/editor/project_export.cpp b/editor/project_export.cpp index 8b8c756219..9f87fc82b5 100644 --- a/editor/project_export.cpp +++ b/editor/project_export.cpp @@ -81,7 +81,14 @@ void ProjectExportDialog::popup_export() { if (EditorSettings::get_singleton()->has_setting("interface/dialogs/export_bounds")) { popup(EditorSettings::get_singleton()->get("interface/dialogs/export_bounds")); } else { - popup_centered_ratio(); + + Size2 popup_size = Size2(900, 700) * editor_get_scale(); + Size2 window_size = get_viewport_rect().size; + + popup_size.x = MIN(window_size.x * 0.8, popup_size.x); + popup_size.y = MIN(window_size.y * 0.8, popup_size.y); + + popup_centered(popup_size); } } diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 21f5e596be..a4265b4e32 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -768,7 +768,14 @@ void ProjectSettingsEditor::popup_project_settings() { if (EditorSettings::get_singleton()->has_setting("interface/dialogs/project_settings_bounds")) { popup(EditorSettings::get_singleton()->get("interface/dialogs/project_settings_bounds")); } else { - popup_centered_ratio(); + + Size2 popup_size = Size2(900, 700) * editor_get_scale(); + Size2 window_size = get_viewport_rect().size; + + popup_size.x = MIN(window_size.x * 0.8, popup_size.x); + popup_size.y = MIN(window_size.y * 0.8, popup_size.y); + + popup_centered(popup_size); } globals_editor->update_category_list(); _update_translations(); diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp new file mode 100644 index 0000000000..d617089142 --- /dev/null +++ b/editor/rename_dialog.cpp @@ -0,0 +1,691 @@ +/*************************************************************************/ +/* rename_dialog.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 "rename_dialog.h" + +#include "editor_node.h" +#include "editor_settings.h" +#include "editor_themes.h" +#include "modules/regex/regex.h" +#include "plugins/script_editor_plugin.h" +#include "print_string.h" +#include "scene/gui/control.h" +#include "scene/gui/label.h" +#include "scene/gui/tab_container.h" + +RenameDialog::RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_undo_redo) { + + scene_tree_editor = p_scene_tree_editor; + undo_redo = p_undo_redo; + preview_node = NULL; + + set_title(TTR("Batch Rename")); + + VBoxContainer *vbc = memnew(VBoxContainer); + add_child(vbc); + + // -- Search/Replace Area + + GridContainer *grd_main = memnew(GridContainer); + grd_main->set_columns(2); + grd_main->set_v_size_flags(SIZE_EXPAND_FILL); + vbc->add_child(grd_main); + + // ---- 1st & 2nd row + + Label *lbl_search = memnew(Label); + lbl_search->set_text(TTR("Search")); + + lne_search = memnew(LineEdit); + lne_search->set_placeholder(TTR("Search")); + lne_search->set_name("lne_search"); + lne_search->set_h_size_flags(SIZE_EXPAND_FILL); + + Label *lbl_replace = memnew(Label); + lbl_replace->set_text(TTR("Replace")); + + lne_replace = memnew(LineEdit); + lne_replace->set_placeholder(TTR("Replace")); + lne_replace->set_name("lne_replace"); + lne_replace->set_h_size_flags(SIZE_EXPAND_FILL); + + grd_main->add_child(lbl_search); + grd_main->add_child(lbl_replace); + grd_main->add_child(lne_search); + grd_main->add_child(lne_replace); + + // ---- 3rd & 4th row + + Label *lbl_prefix = memnew(Label); + lbl_prefix->set_text(TTR("Prefix")); + + lne_prefix = memnew(LineEdit); + lne_prefix->set_placeholder(TTR("Prefix")); + lne_prefix->set_name("lne_prefix"); + lne_prefix->set_h_size_flags(SIZE_EXPAND_FILL); + + Label *lbl_suffix = memnew(Label); + lbl_suffix->set_text(TTR("Suffix")); + + lne_suffix = memnew(LineEdit); + lne_suffix->set_placeholder(TTR("Suffix")); + lne_suffix->set_name("lne_suffix"); + lne_suffix->set_h_size_flags(SIZE_EXPAND_FILL); + + grd_main->add_child(lbl_prefix); + grd_main->add_child(lbl_suffix); + grd_main->add_child(lne_prefix); + grd_main->add_child(lne_suffix); + + // -- Feature Tabs + + const int feature_min_height = 160; + + Ref<Theme> collapse_theme = create_editor_theme(); + collapse_theme->set_icon("checked", "CheckBox", collapse_theme->get_icon("GuiTreeArrowDown", "EditorIcons")); + collapse_theme->set_icon("unchecked", "CheckBox", collapse_theme->get_icon("GuiTreeArrowRight", "EditorIcons")); + + CheckBox *chk_collapse_features = memnew(CheckBox); + chk_collapse_features->set_text(TTR("Advanced options")); + chk_collapse_features->set_theme(collapse_theme); + chk_collapse_features->set_focus_mode(FOCUS_NONE); + vbc->add_child(chk_collapse_features); + + tabc_features = memnew(TabContainer); + tabc_features->set_tab_align(TabContainer::ALIGN_LEFT); + vbc->add_child(tabc_features); + + // ---- Tab Substitute + + VBoxContainer *vbc_substitute = memnew(VBoxContainer); + vbc_substitute->set_h_size_flags(SIZE_EXPAND_FILL); + vbc_substitute->set_custom_minimum_size(Size2(0, feature_min_height)); + + vbc_substitute->set_name(TTR("Substitute")); + tabc_features->add_child(vbc_substitute); + + cbut_substitute = memnew(CheckButton); + cbut_substitute->set_text(TTR("Substitute")); + vbc_substitute->add_child(cbut_substitute); + + GridContainer *grd_substitute = memnew(GridContainer); + grd_substitute->set_columns(3); + vbc_substitute->add_child(grd_substitute); + + // Name + + but_insert_name = memnew(Button); + but_insert_name->set_text("NAME"); + but_insert_name->set_tooltip(String("${NAME}\n") + TTR("Node name")); + but_insert_name->set_focus_mode(FOCUS_NONE); + but_insert_name->connect("pressed", this, "_insert_text", make_binds("${NAME}")); + but_insert_name->set_h_size_flags(SIZE_EXPAND_FILL); + grd_substitute->add_child(but_insert_name); + + // Parent + + but_insert_parent = memnew(Button); + but_insert_parent->set_text("PARENT"); + but_insert_parent->set_tooltip(String("${PARENT}\n") + TTR("Node's parent name, if available")); + but_insert_parent->set_focus_mode(FOCUS_NONE); + but_insert_parent->connect("pressed", this, "_insert_text", make_binds("${PARENT}")); + but_insert_parent->set_h_size_flags(SIZE_EXPAND_FILL); + grd_substitute->add_child(but_insert_parent); + + // Type + + but_insert_type = memnew(Button); + but_insert_type->set_text("TYPE"); + but_insert_type->set_tooltip(String("${TYPE}\n") + TTR("Node type")); + but_insert_type->set_focus_mode(FOCUS_NONE); + but_insert_type->connect("pressed", this, "_insert_text", make_binds("${TYPE}")); + but_insert_type->set_h_size_flags(SIZE_EXPAND_FILL); + grd_substitute->add_child(but_insert_type); + + // Scene + + but_insert_scene = memnew(Button); + but_insert_scene->set_text("SCENE"); + but_insert_scene->set_tooltip(String("${SCENE}\n") + TTR("Current scene name")); + but_insert_scene->set_focus_mode(FOCUS_NONE); + but_insert_scene->connect("pressed", this, "_insert_text", make_binds("${SCENE}")); + but_insert_scene->set_h_size_flags(SIZE_EXPAND_FILL); + grd_substitute->add_child(but_insert_scene); + + // Root + + but_insert_root = memnew(Button); + but_insert_root->set_text("ROOT"); + but_insert_root->set_tooltip(String("${ROOT}\n") + TTR("Root node name")); + but_insert_root->set_focus_mode(FOCUS_NONE); + but_insert_root->connect("pressed", this, "_insert_text", make_binds("${ROOT}")); + but_insert_root->set_h_size_flags(SIZE_EXPAND_FILL); + grd_substitute->add_child(but_insert_root); + + // Count + + but_insert_count = memnew(Button); + but_insert_count->set_text("COUNTER"); + but_insert_count->set_tooltip(String("${COUNTER}\n") + TTR("Sequential integer counter.\nCompare counter options.")); + but_insert_count->set_focus_mode(FOCUS_NONE); + but_insert_count->connect("pressed", this, "_insert_text", make_binds("${COUNTER}")); + but_insert_count->set_h_size_flags(SIZE_EXPAND_FILL); + grd_substitute->add_child(but_insert_count); + + chk_per_level_counter = memnew(CheckBox); + chk_per_level_counter->set_text(TTR("Per Level counter")); + chk_per_level_counter->set_tooltip(TTR("If set the counter restarts for each group of child nodes")); + vbc_substitute->add_child(chk_per_level_counter); + + HBoxContainer *hbc_count_options = memnew(HBoxContainer); + vbc_substitute->add_child(hbc_count_options); + + Label *lbl_count_start = memnew(Label); + lbl_count_start->set_text(TTR("Start")); + lbl_count_start->set_tooltip(TTR("Initial value for the counter")); + hbc_count_options->add_child(lbl_count_start); + + spn_count_start = memnew(SpinBox); + spn_count_start->set_tooltip(TTR("Initial value for the counter")); + spn_count_start->set_step(1); + spn_count_start->set_min(0); + hbc_count_options->add_child(spn_count_start); + + Label *lbl_count_step = memnew(Label); + lbl_count_step->set_text(TTR("Step")); + lbl_count_step->set_tooltip(TTR("Ammount by which counter is incremented for each node")); + hbc_count_options->add_child(lbl_count_step); + + spn_count_step = memnew(SpinBox); + spn_count_step->set_tooltip(TTR("Ammount by which counter is incremented for each node")); + spn_count_step->set_step(1); + hbc_count_options->add_child(spn_count_step); + + Label *lbl_count_padding = memnew(Label); + lbl_count_padding->set_text(TTR("Padding")); + lbl_count_padding->set_tooltip(TTR("Minium number of digits for the counter.\nMissing digits are padded with leading zeros.")); + hbc_count_options->add_child(lbl_count_padding); + + spn_count_padding = memnew(SpinBox); + spn_count_padding->set_tooltip(TTR("Minium number of digits for the counter.\nMissing digits are padded with leading zeros.")); + spn_count_padding->set_step(1); + hbc_count_options->add_child(spn_count_padding); + + // ---- Tab RegEx + + VBoxContainer *vbc_regex = memnew(VBoxContainer); + vbc_regex->set_h_size_flags(SIZE_EXPAND_FILL); + vbc_regex->set_name(TTR("Regular Expressions")); + vbc_regex->set_custom_minimum_size(Size2(0, feature_min_height)); + tabc_features->add_child(vbc_regex); + + cbut_regex = memnew(CheckButton); + cbut_regex->set_text(TTR("Regular Expressions")); + vbc_regex->add_child(cbut_regex); + + // ---- Tab Process + + VBoxContainer *vbc_process = memnew(VBoxContainer); + vbc_process->set_h_size_flags(SIZE_EXPAND_FILL); + vbc_process->set_name(TTR("Post-Process")); + vbc_process->set_custom_minimum_size(Size2(0, feature_min_height)); + tabc_features->add_child(vbc_process); + + cbut_process = memnew(CheckButton); + cbut_process->set_text(TTR("Post-Process")); + vbc_process->add_child(cbut_process); + + // ------ Style + + HBoxContainer *hbc_style = memnew(HBoxContainer); + vbc_process->add_child(hbc_style); + + Label *lbl_style = memnew(Label); + lbl_style->set_text(TTR("Style")); + hbc_style->add_child(lbl_style); + + opt_style = memnew(OptionButton); + opt_style->add_item(TTR("Keep")); + opt_style->add_item(TTR("CamelCase to under_scored")); + opt_style->add_item(TTR("under_scored to CamelCase")); + hbc_style->add_child(opt_style); + + // ------ Case + + HBoxContainer *hbc_case = memnew(HBoxContainer); + vbc_process->add_child(hbc_case); + + Label *lbl_case = memnew(Label); + lbl_case->set_text(TTR("Case")); + hbc_case->add_child(lbl_case); + + opt_case = memnew(OptionButton); + opt_case->add_item(TTR("Keep")); + opt_case->add_item(TTR("To Lowercase")); + opt_case->add_item(TTR("To Uppercase")); + hbc_case->add_child(opt_case); + + // -- Preview + + HSeparator *sep_preview = memnew(HSeparator); + sep_preview->set_custom_minimum_size(Size2(10, 20)); + vbc->add_child(sep_preview); + + lbl_preview_title = memnew(Label); + lbl_preview_title->set_text(TTR("Preview")); + vbc->add_child(lbl_preview_title); + + lbl_preview = memnew(Label); + lbl_preview->set_text(""); + lbl_preview->add_color_override("font_color", Color(1, 0.5f, 0, 1)); + vbc->add_child(lbl_preview); + + // ---- Dialog related + + set_custom_minimum_size(Size2(383, 0)); + set_as_toplevel(true); + get_ok()->set_text(TTR("Rename")); + Button *but_reset = add_button(TTR("Reset")); + + eh.errfunc = _error_handler; + eh.userdata = this; + + // ---- Connections + + chk_collapse_features->connect("toggled", this, "_features_toggled"); + + // Substitite Buttons + + lne_search->connect("focus_entered", this, "_update_substitute"); + lne_search->connect("focus_exited", this, "_update_substitute"); + lne_replace->connect("focus_entered", this, "_update_substitute"); + lne_replace->connect("focus_exited", this, "_update_substitute"); + lne_prefix->connect("focus_entered", this, "_update_substitute"); + lne_prefix->connect("focus_exited", this, "_update_substitute"); + lne_suffix->connect("focus_entered", this, "_update_substitute"); + lne_suffix->connect("focus_exited", this, "_update_substitute"); + + // Preview + + lne_prefix->connect("text_changed", this, "_update_preview"); + lne_suffix->connect("text_changed", this, "_update_preview"); + lne_search->connect("text_changed", this, "_update_preview"); + lne_replace->connect("text_changed", this, "_update_preview"); + spn_count_start->connect("value_changed", this, "_update_preview_int"); + spn_count_step->connect("value_changed", this, "_update_preview_int"); + spn_count_padding->connect("value_changed", this, "_update_preview_int"); + opt_style->connect("item_selected", this, "_update_preview_int"); + opt_case->connect("item_selected", this, "_update_preview_int"); + cbut_substitute->connect("pressed", this, "_update_preview", varray("")); + cbut_regex->connect("pressed", this, "_update_preview", varray("")); + cbut_process->connect("pressed", this, "_update_preview", varray("")); + + but_reset->connect("pressed", this, "reset"); + + reset(); + _features_toggled(false); +} + +void RenameDialog::_bind_methods() { + + ClassDB::bind_method("_features_toggled", &RenameDialog::_features_toggled); + ClassDB::bind_method("_update_preview", &RenameDialog::_update_preview); + ClassDB::bind_method("_update_preview_int", &RenameDialog::_update_preview_int); + ClassDB::bind_method("_insert_text", &RenameDialog::_insert_text); + ClassDB::bind_method("_update_substitute", &RenameDialog::_update_substitute); + ClassDB::bind_method("reset", &RenameDialog::reset); + ClassDB::bind_method("rename", &RenameDialog::rename); +} + +void RenameDialog::_update_substitute() { + + LineEdit *focus_owner_line_edit = Object::cast_to<LineEdit>(get_focus_owner()); + bool is_main_field = _is_main_field(focus_owner_line_edit); + + but_insert_name->set_disabled(!is_main_field); + but_insert_parent->set_disabled(!is_main_field); + but_insert_type->set_disabled(!is_main_field); + but_insert_scene->set_disabled(!is_main_field); + but_insert_root->set_disabled(!is_main_field); + but_insert_count->set_disabled(!is_main_field); + + // The focus mode seems to be reset when disabling/re-enabling + but_insert_name->set_focus_mode(FOCUS_NONE); + but_insert_parent->set_focus_mode(FOCUS_NONE); + but_insert_type->set_focus_mode(FOCUS_NONE); + but_insert_scene->set_focus_mode(FOCUS_NONE); + but_insert_root->set_focus_mode(FOCUS_NONE); + but_insert_count->set_focus_mode(FOCUS_NONE); +} + +void RenameDialog::_post_popup() { + + EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); + preview_node = NULL; + + Array selected_node_list = editor_selection->get_selected_nodes(); + ERR_FAIL_COND(selected_node_list.size() == 0); + + preview_node = selected_node_list[0]; + + _update_preview(); + _update_substitute(); +} + +void RenameDialog::_update_preview_int(int new_value) { + _update_preview(); +} + +void RenameDialog::_update_preview(String new_text) { + + if (lock_preview_update || preview_node == NULL) + return; + + has_errors = false; + add_error_handler(&eh); + + String new_name = _apply_rename(preview_node, spn_count_start->get_value()); + + if (!has_errors) { + + lbl_preview_title->set_text(TTR("Preview")); + lbl_preview->set_text(new_name); + + if (new_name == preview_node->get_name()) { + lbl_preview->add_color_override("font_color", Color(0, 0.5f, 0.25f, 1)); + } else { + lbl_preview->add_color_override("font_color", Color(0, 1, 0.5f, 1)); + } + } + + remove_error_handler(&eh); +} + +String RenameDialog::_apply_rename(const Node *node, int count) { + + String search = lne_search->get_text(); + String replace = lne_replace->get_text(); + String prefix = lne_prefix->get_text(); + String suffix = lne_suffix->get_text(); + String new_name = node->get_name(); + + if (cbut_substitute->is_pressed()) { + search = _substitute(search, node, count); + replace = _substitute(replace, node, count); + prefix = _substitute(prefix, node, count); + suffix = _substitute(suffix, node, count); + } + + if (cbut_regex->is_pressed()) { + + new_name = _regex(search, new_name, replace); + } else { + new_name = new_name.replace(search, replace); + } + + new_name = prefix + new_name + suffix; + + if (cbut_process->is_pressed()) { + new_name = _postprocess(new_name); + } + + return new_name; +} + +String RenameDialog::_substitute(const String &subject, const Node *node, int count) { + + String result = subject.replace("${COUNTER}", vformat("%0" + itos(spn_count_padding->get_value()) + "d", count)); + + if (node) { + result = result.replace("${NAME}", node->get_name()); + result = result.replace("${TYPE}", node->get_class()); + } + + int current = EditorNode::get_singleton()->get_editor_data().get_edited_scene(); + result = result.replace("${SCENE}", EditorNode::get_singleton()->get_editor_data().get_scene_title(current)); + + Node *root_node = SceneTree::get_singleton()->get_edited_scene_root(); + if (root_node) { + result = result.replace("${ROOT}", root_node->get_name()); + } + + Node *parent_node = node->get_parent(); + if (parent_node) { + if (node == root_node) { + // Can not substitute parent of root. + result = result.replace("${PARENT}", ""); + } else { + result = result.replace("${PARENT}", parent_node->get_name()); + } + } + + return result; +} + +void RenameDialog::_error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, ErrorHandlerType p_type) { + + RenameDialog *self = (RenameDialog *)p_self; + String source_file(p_file); + + // Only show first error that is related to "regex" + if (self->has_errors || source_file.find("regex") < 0) + return; + + String err_str; + if (p_errorexp && p_errorexp[0]) { + err_str = p_errorexp; + } else { + err_str = p_error; + } + + self->has_errors = true; + self->lbl_preview_title->set_text(TTR("Error")); + self->lbl_preview->add_color_override("font_color", Color(1, 0.25f, 0, 1)); + self->lbl_preview->set_text(err_str); +} + +String RenameDialog::_regex(const String &pattern, const String &subject, const String &replacement) { + + RegEx regex(pattern); + + return regex.sub(subject, replacement, true); +} + +String RenameDialog::_postprocess(const String &subject) { + + int style_id = opt_style->get_selected(); + + String result = subject; + + if (style_id == 1) { + + // CamelCase to Under_Line + result = result.camelcase_to_underscore(true); + result = _regex("_+", result, "_"); + + } else if (style_id == 2) { + + // Under_Line to CamelCase + RegEx pattern("_+(.?)"); + Array matches = pattern.search_all(result); + + // _ name would become empty. Ignore + if (matches.size() && result != "_") { + String buffer; + int start = 0; + int end = 0; + for (int i = 0; i < matches.size(); ++i) { + start = ((Ref<RegExMatch>)matches[i])->get_start(1); + buffer += result.substr(end, start - end - 1); + buffer += result.substr(start, 1).to_upper(); + end = start + 1; + } + buffer += result.substr(end, result.size() - (end + 1)); + result = buffer.replace("_", "").capitalize(); + } + } + + int case_id = opt_case->get_selected(); + + if (case_id == 1) { + // To Lowercase + result = result.to_lower(); + } else if (case_id == 2) { + // To Upercase + result = result.to_upper(); + } + + return result; +} + +void RenameDialog::_iterate_scene(const Node *node, const Array &selection, int *counter) { + + if (!node) + return; + + if (selection.has(node)) { + + String new_name = _apply_rename(node, *counter); + + if (node->get_name() != new_name) { + Pair<NodePath, String> rename_item; + rename_item.first = node->get_path(); + rename_item.second = new_name; + to_rename.push_back(rename_item); + } + + *counter += spn_count_step->get_value(); + } + + int *cur_counter = counter; + int level_counter = spn_count_start->get_value(); + + if (chk_per_level_counter->is_pressed()) { + cur_counter = &level_counter; + } + + for (int i = 0; i < node->get_child_count(); ++i) { + _iterate_scene(node->get_child(i), selection, cur_counter); + } +} + +void RenameDialog::rename() { + + // Editor selection is not ordered via scene tree. Instead iterate + // over scene tree until all selected nodes are found in order. + + EditorSelection *editor_selection = EditorNode::get_singleton()->get_editor_selection(); + Array selected_node_list = editor_selection->get_selected_nodes(); + Node *root_node = SceneTree::get_singleton()->get_edited_scene_root(); + + global_count = spn_count_start->get_value(); + to_rename.clear(); + + // Forward recursive as opposed to the actual renaming. + _iterate_scene(root_node, selected_node_list, &global_count); + + if (undo_redo && !to_rename.empty()) { + + undo_redo->create_action(TTR("Batch Rename")); + + // Make sure to iterate reversed so that child nodes will find parents. + for (int i = to_rename.size() - 1; i >= 0; --i) { + + Node *n = root_node->get_node(to_rename[i].first); + const String &new_name = to_rename[i].second; + + if (!n) { + ERR_PRINTS("Skipping missing node: " + to_rename[i].first.get_concatenated_subnames()); + continue; + } + + scene_tree_editor->emit_signal("node_prerename", n, new_name); + undo_redo->add_do_method(scene_tree_editor, "_rename_node", n->get_instance_id(), new_name); + undo_redo->add_undo_method(scene_tree_editor, "_rename_node", n->get_instance_id(), n->get_name()); + } + + undo_redo->commit_action(); + } +} + +void RenameDialog::reset() { + + lock_preview_update = true; + + lne_prefix->clear(); + lne_suffix->clear(); + lne_search->clear(); + lne_replace->clear(); + + cbut_substitute->set_pressed(false); + cbut_regex->set_pressed(false); + cbut_process->set_pressed(false); + + chk_per_level_counter->set_pressed(true); + + spn_count_start->set_value(1); + spn_count_step->set_value(1); + spn_count_padding->set_value(1); + + opt_style->select(0); + opt_case->select(0); + + lock_preview_update = false; + _update_preview(); +} + +bool RenameDialog::_is_main_field(LineEdit *line_edit) { + return line_edit && + (line_edit == lne_search || line_edit == lne_replace || line_edit == lne_prefix || line_edit == lne_suffix); +} + +void RenameDialog::_insert_text(String text) { + + LineEdit *focus_owner = Object::cast_to<LineEdit>(get_focus_owner()); + + if (_is_main_field(focus_owner)) { + focus_owner->selection_delete(); + focus_owner->append_at_cursor(text); + _update_preview(); + } +} + +void RenameDialog::_features_toggled(bool pressed) { + if (pressed) { + tabc_features->show(); + } else { + tabc_features->hide(); + } + + // Adjust to minimum size in y + Size2i size = get_size(); + size.y = 0; + set_size(size); +} diff --git a/editor/rename_dialog.h b/editor/rename_dialog.h new file mode 100644 index 0000000000..c5ebc30c0c --- /dev/null +++ b/editor/rename_dialog.h @@ -0,0 +1,119 @@ +/*************************************************************************/ +/* rename_dialog.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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 RENAME_DIALOG_H +#define RENAME_DIALOG_H + +#include "scene/gui/check_box.h" +#include "scene/gui/check_button.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/option_button.h" +#include "scene/gui/spin_box.h" + +#include "editor/scene_tree_editor.h" +#include "undo_redo.h" + +/** +@author Blazej Floch +*/ + +class RenameDialog : public ConfirmationDialog { + + GDCLASS(RenameDialog, ConfirmationDialog); + + virtual void ok_pressed() { rename(); }; + void _cancel_pressed(){}; + void _features_toggled(bool pressed); + void _insert_text(String text); + void _update_substitute(); + bool _is_main_field(LineEdit *line_edit); + + void _iterate_scene(const Node *node, const Array &selection, int *count); + String _apply_rename(const Node *node, int count); + String _substitute(const String &subject, const Node *node, int count); + String _regex(const String &pattern, const String &subject, const String &replacement); + String _postprocess(const String &subject); + void _update_preview(String new_text = ""); + void _update_preview_int(int new_value = 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, ErrorHandlerType p_type); + + SceneTreeEditor *scene_tree_editor; + UndoRedo *undo_redo; + int global_count; + + LineEdit *lne_search; + LineEdit *lne_replace; + LineEdit *lne_prefix; + LineEdit *lne_suffix; + + TabContainer *tabc_features; + + CheckButton *cbut_substitute; + CheckButton *cbut_regex; + CheckButton *cbut_process; + CheckBox *chk_per_level_counter; + + Button *but_insert_name; + Button *but_insert_parent; + Button *but_insert_type; + Button *but_insert_scene; + Button *but_insert_root; + Button *but_insert_count; + + SpinBox *spn_count_start; + SpinBox *spn_count_step; + SpinBox *spn_count_padding; + + OptionButton *opt_style; + OptionButton *opt_case; + + Label *lbl_preview_title; + Label *lbl_preview; + + List<Pair<NodePath, String> > to_rename; + Node *preview_node; + bool lock_preview_update; + ErrorHandlerList eh; + bool has_errors; + +protected: + void _notification(int p_what){}; + static void _bind_methods(); + virtual void _post_popup(); + +public: + void reset(); + void rename(); + + RenameDialog(SceneTreeEditor *p_scene_tree_editor, UndoRedo *p_undo_redo = NULL); + ~RenameDialog(){}; +}; + +#endif diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 38027a34a7..8b99a3d503 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -73,7 +73,9 @@ void SceneTreeDock::_unhandled_key_input(Ref<InputEvent> p_event) { if (!p_event->is_pressed() || p_event->is_echo()) return; - if (ED_IS_SHORTCUT("scene_tree/add_child_node", p_event)) { + if (ED_IS_SHORTCUT("scene_tree/batch_rename", p_event)) { + _tool_selected(TOOL_BATCH_RENAME); + } else if (ED_IS_SHORTCUT("scene_tree/add_child_node", p_event)) { _tool_selected(TOOL_NEW); } else if (ED_IS_SHORTCUT("scene_tree/instance_scene", p_event)) { _tool_selected(TOOL_INSTANCE); @@ -107,7 +109,12 @@ void SceneTreeDock::_unhandled_key_input(Ref<InputEvent> p_event) { void SceneTreeDock::instance(const String &p_file) { Node *parent = scene_tree->get_selected(); - if (!parent || !edited_scene) { + + if (!parent) { + Node *parent = edited_scene; + }; + + if (!edited_scene) { current_option = -1; accept->get_ok()->set_text(TTR("OK :(")); @@ -280,6 +287,12 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { switch (p_tool) { + case TOOL_BATCH_RENAME: { + Tree *tree = scene_tree->get_scene_tree(); + if (tree->is_anything_selected()) { + rename_dialog->popup_centered(); + } + } break; case TOOL_NEW: { String preferred = ""; @@ -1857,6 +1870,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { menu->clear(); + menu->add_icon_shortcut(get_icon("Rename", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/batch_rename"), TOOL_BATCH_RENAME); if (selection.size() == 1) { subresources.clear(); @@ -2050,6 +2064,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel filter_hbc->add_constant_override("separate", 0); ToolButton *tb; + ED_SHORTCUT("scene_tree/batch_rename", TTR("Batch Rename"), KEY_MASK_CMD | KEY_F2); ED_SHORTCUT("scene_tree/add_child_node", TTR("Add Child Node"), KEY_MASK_CMD | KEY_A); ED_SHORTCUT("scene_tree/instance_scene", TTR("Instance Child Scene")); ED_SHORTCUT("scene_tree/change_node_type", TTR("Change Type")); @@ -2148,6 +2163,9 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel add_child(create_dialog); create_dialog->connect("create", this, "_create"); + rename_dialog = memnew(RenameDialog(scene_tree, &editor_data->get_undo_redo())); + add_child(rename_dialog); + script_create_dialog = memnew(ScriptCreateDialog); add_child(script_create_dialog); script_create_dialog->connect("script_created", this, "_script_created"); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index a4f36e31ee..fd7d616a80 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -36,6 +36,7 @@ #include "editor/editor_data.h" #include "editor/editor_sub_scene.h" #include "editor/groups_editor.h" +#include "editor/rename_dialog.h" #include "editor/reparent_dialog.h" #include "editor/script_create_dialog.h" #include "scene/animation/animation_player.h" @@ -58,6 +59,7 @@ class SceneTreeDock : public VBoxContainer { TOOL_NEW, TOOL_INSTANCE, + TOOL_BATCH_RENAME, TOOL_REPLACE, TOOL_ATTACH_SCRIPT, TOOL_CLEAR_SCRIPT, @@ -90,6 +92,7 @@ class SceneTreeDock : public VBoxContainer { int current_option; CreateDialog *create_dialog; + RenameDialog *rename_dialog; ToolButton *button_add; ToolButton *button_instance; diff --git a/editor/script_editor_debugger.cpp b/editor/script_editor_debugger.cpp index 50519e2c6e..a83de1627d 100644 --- a/editor/script_editor_debugger.cpp +++ b/editor/script_editor_debugger.cpp @@ -46,6 +46,7 @@ #include "scene/gui/tab_container.h" #include "scene/gui/texture_button.h" #include "scene/gui/tree.h" +#include "scene/resources/packed_scene.h" #include "ustring.h" class ScriptEditorDebuggerVariables : public Object { @@ -286,6 +287,30 @@ void ScriptEditorDebugger::_scene_tree_selected() { ppeer->put_var(msg); } +void ScriptEditorDebugger::_scene_tree_rmb_selected(const Vector2 &p_position) { + + TreeItem *item = inspect_scene_tree->get_item_at_position(p_position); + if (!item) + return; + + item->select(0); + + item_menu->clear(); + item_menu->add_icon_item(get_icon("CreateNewSceneFrom", "EditorIcons"), TTR("Save Branch as Scene"), ITEM_MENU_SAVE_REMOTE_NODE); + item_menu->set_global_position(get_global_mouse_position()); + item_menu->popup(); +} + +void ScriptEditorDebugger::_file_selected(const String &p_file) { + if (file_dialog->get_mode() == EditorFileDialog::MODE_SAVE_FILE) { + Array msg; + msg.push_back("save_node"); + msg.push_back(inspected_object_id); + msg.push_back(p_file); + ppeer->put_var(msg); + } +} + void ScriptEditorDebugger::_scene_tree_property_value_edited(const String &p_prop, const Variant &p_value) { Array msg; @@ -1754,6 +1779,21 @@ void ScriptEditorDebugger::_item_menu_id_pressed(int p_option) { OS::get_singleton()->set_clipboard(title + "\n----------\n" + desc); } break; + case ITEM_MENU_SAVE_REMOTE_NODE: { + + file_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES); + file_dialog->set_mode(EditorFileDialog::MODE_SAVE_FILE); + + List<String> extensions; + Ref<PackedScene> sd = memnew(PackedScene); + ResourceSaver::get_recognized_extensions(sd, &extensions); + file_dialog->clear_filters(); + for (int i = 0; i < extensions.size(); i++) { + file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); + } + + file_dialog->popup_centered_ratio(); + } break; } } @@ -1788,6 +1828,8 @@ void ScriptEditorDebugger::_bind_methods() { ClassDB::bind_method(D_METHOD("_scene_tree_selected"), &ScriptEditorDebugger::_scene_tree_selected); ClassDB::bind_method(D_METHOD("_scene_tree_folded"), &ScriptEditorDebugger::_scene_tree_folded); + ClassDB::bind_method(D_METHOD("_scene_tree_rmb_selected"), &ScriptEditorDebugger::_scene_tree_rmb_selected); + ClassDB::bind_method(D_METHOD("_file_selected"), &ScriptEditorDebugger::_file_selected); ClassDB::bind_method(D_METHOD("live_debug_create_node"), &ScriptEditorDebugger::live_debug_create_node); ClassDB::bind_method(D_METHOD("live_debug_instance_node"), &ScriptEditorDebugger::live_debug_instance_node); @@ -1960,7 +2002,8 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { inspect_scene_tree->set_v_size_flags(SIZE_EXPAND_FILL); inspect_scene_tree->connect("cell_selected", this, "_scene_tree_selected"); inspect_scene_tree->connect("item_collapsed", this, "_scene_tree_folded"); - + inspect_scene_tree->set_allow_rmb_select(true); + inspect_scene_tree->connect("item_rmb_selected", this, "_scene_tree_rmb_selected"); auto_switch_remote_scene_tree = EDITOR_DEF("debugger/auto_switch_to_remote_scene_tree", false); inspect_scene_tree_timeout = EDITOR_DEF("debugger/remote_scene_tree_refresh_interval", 1.0); inspect_edited_object_timeout = EDITOR_DEF("debugger/remote_inspect_refresh_interval", 0.2); @@ -1968,6 +2011,12 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) { updating_scene_tree = false; } + { // File dialog + file_dialog = memnew(EditorFileDialog); + file_dialog->connect("file_selected", this, "_file_selected"); + add_child(file_dialog); + } + { //profiler profiler = memnew(EditorProfiler); profiler->set_name(TTR("Profiler")); diff --git a/editor/script_editor_debugger.h b/editor/script_editor_debugger.h index 669d8737fe..f7fe348b65 100644 --- a/editor/script_editor_debugger.h +++ b/editor/script_editor_debugger.h @@ -65,6 +65,7 @@ class ScriptEditorDebugger : public Control { enum ItemMenu { ITEM_MENU_COPY_ERROR, + ITEM_MENU_SAVE_REMOTE_NODE, }; AcceptDialog *msgdialog; @@ -92,6 +93,7 @@ class ScriptEditorDebugger : public Control { Tree *inspect_scene_tree; Button *clearbutton; PopupMenu *item_menu; + EditorFileDialog *file_dialog; int error_count; int last_error_count; @@ -155,6 +157,8 @@ class ScriptEditorDebugger : public Control { void _scene_tree_folded(Object *obj); void _scene_tree_selected(); + void _scene_tree_rmb_selected(const Vector2 &p_position); + void _file_selected(const String &p_file); void _scene_tree_request(); void _parse_message(const String &p_msg, const Array &p_data); void _set_reason_text(const String &p_reason, MessageType p_type); diff --git a/editor/settings_config_dialog.cpp b/editor/settings_config_dialog.cpp index f50f9f6f5f..c3e9e4ab62 100644 --- a/editor/settings_config_dialog.cpp +++ b/editor/settings_config_dialog.cpp @@ -101,7 +101,14 @@ void EditorSettingsDialog::popup_edit_settings() { if (EditorSettings::get_singleton()->has_setting("interface/dialogs/editor_settings_bounds")) { popup(EditorSettings::get_singleton()->get("interface/dialogs/editor_settings_bounds")); } else { - popup_centered_ratio(0.7); + + Size2 popup_size = Size2(900, 700) * editor_get_scale(); + Size2 window_size = get_viewport_rect().size; + + popup_size.x = MIN(window_size.x * 0.8, popup_size.x); + popup_size.y = MIN(window_size.y * 0.8, popup_size.y); + + popup_centered(popup_size); } _focus_current_search_box(); diff --git a/editor/spatial_editor_gizmos.cpp b/editor/spatial_editor_gizmos.cpp index c7654d0e69..2652b09763 100644 --- a/editor/spatial_editor_gizmos.cpp +++ b/editor/spatial_editor_gizmos.cpp @@ -1534,6 +1534,120 @@ SkeletonSpatialGizmo::SkeletonSpatialGizmo(Skeleton *p_skel) { set_spatial_node(p_skel); } +PhysicalBoneSpatialGizmo::PhysicalBoneSpatialGizmo(PhysicalBone *p_pb) : + EditorSpatialGizmo(), + physical_bone(p_pb) { + set_spatial_node(p_pb); +} + +void PhysicalBoneSpatialGizmo::redraw() { + + clear(); + + if (!physical_bone) + return; + + Skeleton *sk(physical_bone->find_skeleton_parent()); + PhysicalBone *pb(sk->get_physical_bone(physical_bone->get_bone_id())); + PhysicalBone *pbp(sk->get_physical_bone_parent(physical_bone->get_bone_id())); + + Vector<Vector3> points; + + switch (physical_bone->get_joint_type()) { + case PhysicalBone::JOINT_TYPE_PIN: { + + PinJointSpatialGizmo::CreateGizmo(physical_bone->get_joint_offset(), points); + } break; + case PhysicalBone::JOINT_TYPE_CONE: { + + const PhysicalBone::ConeJointData *cjd(static_cast<const PhysicalBone::ConeJointData *>(physical_bone->get_joint_data())); + ConeTwistJointSpatialGizmo::CreateGizmo( + physical_bone->get_joint_offset(), + physical_bone->get_global_transform() * physical_bone->get_joint_offset(), + pb ? pb->get_global_transform() : Transform(), + pbp ? pbp->get_global_transform() : Transform(), + cjd->swing_span, + cjd->twist_span, + points, + pb ? &points : NULL, + pbp ? &points : NULL); + } break; + case PhysicalBone::JOINT_TYPE_HINGE: { + + const PhysicalBone::HingeJointData *hjd(static_cast<const PhysicalBone::HingeJointData *>(physical_bone->get_joint_data())); + HingeJointSpatialGizmo::CreateGizmo( + physical_bone->get_joint_offset(), + physical_bone->get_global_transform() * physical_bone->get_joint_offset(), + pb ? pb->get_global_transform() : Transform(), + pbp ? pbp->get_global_transform() : Transform(), + hjd->angular_limit_lower, + hjd->angular_limit_upper, + hjd->angular_limit_enabled, + points, + pb ? &points : NULL, + pbp ? &points : NULL); + } break; + case PhysicalBone::JOINT_TYPE_SLIDER: { + + const PhysicalBone::SliderJointData *sjd(static_cast<const PhysicalBone::SliderJointData *>(physical_bone->get_joint_data())); + SliderJointSpatialGizmo::CreateGizmo( + physical_bone->get_joint_offset(), + physical_bone->get_global_transform() * physical_bone->get_joint_offset(), + pb ? pb->get_global_transform() : Transform(), + pbp ? pbp->get_global_transform() : Transform(), + sjd->angular_limit_lower, + sjd->angular_limit_upper, + sjd->linear_limit_lower, + sjd->linear_limit_upper, + points, + pb ? &points : NULL, + pbp ? &points : NULL); + } break; + case PhysicalBone::JOINT_TYPE_6DOF: { + + const PhysicalBone::SixDOFJointData *sdofjd(static_cast<const PhysicalBone::SixDOFJointData *>(physical_bone->get_joint_data())); + Generic6DOFJointSpatialGizmo::CreateGizmo( + physical_bone->get_joint_offset(), + + physical_bone->get_global_transform() * physical_bone->get_joint_offset(), + pb ? pb->get_global_transform() : Transform(), + pbp ? pbp->get_global_transform() : Transform(), + + sdofjd->axis_data[0].angular_limit_lower, + sdofjd->axis_data[0].angular_limit_upper, + sdofjd->axis_data[0].linear_limit_lower, + sdofjd->axis_data[0].linear_limit_upper, + sdofjd->axis_data[0].angular_limit_enabled, + sdofjd->axis_data[0].linear_limit_enabled, + + sdofjd->axis_data[1].angular_limit_lower, + sdofjd->axis_data[1].angular_limit_upper, + sdofjd->axis_data[1].linear_limit_lower, + sdofjd->axis_data[1].linear_limit_upper, + sdofjd->axis_data[1].angular_limit_enabled, + sdofjd->axis_data[1].linear_limit_enabled, + + sdofjd->axis_data[2].angular_limit_lower, + sdofjd->axis_data[2].angular_limit_upper, + sdofjd->axis_data[2].linear_limit_lower, + sdofjd->axis_data[2].linear_limit_upper, + sdofjd->axis_data[2].angular_limit_enabled, + sdofjd->axis_data[2].linear_limit_enabled, + + points, + pb ? &points : NULL, + pbp ? &points : NULL); + } break; + default: + return; + } + + Ref<Material> material = create_material("joint_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/joint")); + + add_collision_segments(points); + add_lines(points, material); +} + // FIXME: Kept as reference for reimplementation in 3.1+ #if 0 void RoomSpatialGizmo::redraw() { @@ -3735,6 +3849,12 @@ Ref<SpatialEditorGizmo> SpatialEditorGizmos::get_gizmo(Spatial *p_spatial) { return lsg; } + if (Object::cast_to<PhysicalBone>(p_spatial)) { + + Ref<PhysicalBoneSpatialGizmo> pbsg = memnew(PhysicalBoneSpatialGizmo(Object::cast_to<PhysicalBone>(p_spatial))); + return pbsg; + } + if (Object::cast_to<Position3D>(p_spatial)) { Ref<Position3DSpatialGizmo> lsg = memnew(Position3DSpatialGizmo(Object::cast_to<Position3D>(p_spatial))); diff --git a/editor/spatial_editor_gizmos.h b/editor/spatial_editor_gizmos.h index b96c75e0c6..c5dc36cb22 100644 --- a/editor/spatial_editor_gizmos.h +++ b/editor/spatial_editor_gizmos.h @@ -214,6 +214,17 @@ public: SkeletonSpatialGizmo(Skeleton *p_skel = NULL); }; +class PhysicalBoneSpatialGizmo : public EditorSpatialGizmo { + GDCLASS(PhysicalBoneSpatialGizmo, EditorSpatialGizmo); + + PhysicalBone *physical_bone; + +public: + //virtual Transform get_global_gizmo_transform(); + virtual void redraw(); + PhysicalBoneSpatialGizmo(PhysicalBone *p_pb = NULL); +}; + #if 0 class PortalSpatialGizmo : public EditorSpatialGizmo { |