diff options
Diffstat (limited to 'editor')
28 files changed, 2539 insertions, 1146 deletions
diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index da376c588e..67cdba043a 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -35,6 +35,8 @@ #include "scene/gui/view_panner.h" #include "scene/resources/text_line.h" +#include <limits.h> + float AnimationBezierTrackEdit::_bezier_h_to_pixel(float p_h) { float h = p_h; h = (h - v_scroll) / v_zoom; @@ -55,15 +57,16 @@ static _FORCE_INLINE_ Vector2 _bezier_interp(real_t t, const Vector2 &start, con void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { float scale = timeline->get_zoom_scale(); + int limit = timeline->get_name_limit(); - int right_limit = get_size().width - timeline->get_buttons_width(); + int right_limit = get_size().width; //selection may have altered the order of keys Map<float, int> key_order; for (int i = 0; i < animation->track_get_key_count(p_track); i++) { float ofs = animation->track_get_key_time(p_track, i); - if (moving_selection && track == p_track && selection.has(i)) { + if (moving_selection && selection.has(IntPair(p_track, i))) { ofs += moving_selection_offset.x; } @@ -82,11 +85,11 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { float offset = animation->track_get_key_time(p_track, i); float height = animation->bezier_track_get_key_value(p_track, i); Vector2 out_handle = animation->bezier_track_get_key_out_handle(p_track, i); - if (track == p_track && moving_handle != 0 && moving_handle_key == i) { + if (p_track == moving_handle_track && moving_handle != 0 && moving_handle_key == i) { out_handle = moving_handle_right; } - if (moving_selection && track == p_track && selection.has(i)) { + if (moving_selection && selection.has(IntPair(p_track, i))) { offset += moving_selection_offset.x; height += moving_selection_offset.y; } @@ -96,11 +99,11 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { float offset_n = animation->track_get_key_time(p_track, i_n); float height_n = animation->bezier_track_get_key_value(p_track, i_n); Vector2 in_handle = animation->bezier_track_get_key_in_handle(p_track, i_n); - if (track == p_track && moving_handle != 0 && moving_handle_key == i_n) { + if (p_track == moving_handle_track && moving_handle != 0 && moving_handle_key == i_n) { in_handle = moving_handle_left; } - if (moving_selection && track == p_track && selection.has(i_n)) { + if (moving_selection && selection.has(IntPair(p_track, i_n))) { offset_n += moving_selection_offset.x; height_n += moving_selection_offset.y; } @@ -221,20 +224,10 @@ void AnimationBezierTrackEdit::_notification(int p_what) { panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/animation_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); } if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { - close_button->set_icon(get_theme_icon(SNAME("Close"), SNAME("EditorIcons"))); - bezier_icon = get_theme_icon(SNAME("KeyBezierPoint"), SNAME("EditorIcons")); bezier_handle_icon = get_theme_icon(SNAME("KeyBezierHandle"), SNAME("EditorIcons")); selected_icon = get_theme_icon(SNAME("KeyBezierSelected"), SNAME("EditorIcons")); } - if (p_what == NOTIFICATION_RESIZED) { - int right_limit = get_size().width - timeline->get_buttons_width(); - int hsep = get_theme_constant(SNAME("hseparation"), SNAME("ItemList")); - int vsep = get_theme_constant(SNAME("vseparation"), SNAME("ItemList")); - - right_column->set_position(Vector2(right_limit + hsep, vsep)); - right_column->set_size(Vector2(timeline->get_buttons_width() - hsep * 2, get_size().y - vsep * 2)); - } if (p_what == NOTIFICATION_DRAW) { if (animation.is_null()) { return; @@ -258,101 +251,191 @@ void AnimationBezierTrackEdit::_notification(int p_what) { draw_line(Point2(limit, 0), Point2(limit, get_size().height), linecolor, Math::round(EDSCALE)); - int right_limit = get_size().width - timeline->get_buttons_width(); + int right_limit = get_size().width; - draw_line(Point2(right_limit, 0), Point2(right_limit, get_size().height), linecolor, Math::round(EDSCALE)); - - String base_path = animation->track_get_path(track); - int end = base_path.find(":"); - if (end != -1) { - base_path = base_path.substr(0, end + 1); - } - - // NAMES AND ICON int vofs = vsep; int margin = 0; - { - NodePath path = animation->track_get_path(track); + Map<int, Color> subtrack_colors; + Color selected_track_color; + subtracks.clear(); + subtrack_icons.clear(); + + Map<String, Vector<int>> track_indices; + int track_count = animation->get_track_count(); + for (int i = 0; i < track_count; ++i) { + if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER) { + continue; + } - Node *node = nullptr; + String base_path = animation->track_get_path(i); + if (is_filtered) { + if (root && root->has_node(base_path)) { + Node *node = root->get_node(base_path); + if (!node) { + continue; // No node, no filter. + } + if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { + continue; // Skip track due to not selected. + } + } + } - if (root && root->has_node(path)) { - node = root->get_node(path); + int end = base_path.find(":"); + if (end != -1) { + base_path = base_path.substr(0, end + 1); } + Vector<int> indices = track_indices.has(base_path) ? track_indices[base_path] : Vector<int>(); + indices.push_back(i); + track_indices[base_path] = indices; + } - String text; + for (const KeyValue<String, Vector<int>> &E : track_indices) { + String base_path = E.key; - if (node) { - int ofs = 0; + Vector<int> tracks = E.value; - Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node, "Node"); + // NAMES AND ICON + { + NodePath path = animation->track_get_path(tracks[0]); - text = node->get_name(); - ofs += hsep; - ofs += icon->get_width(); + Node *node = nullptr; - TextLine text_buf = TextLine(text, font, font_size); - text_buf.set_width(limit - ofs - hsep); + if (root && root->has_node(path)) { + node = root->get_node(path); + } - int h = MAX(text_buf.get_size().y, icon->get_height()); + String text; - draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2)); + if (node) { + int ofs = 0; - margin = icon->get_width(); + Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(node, "Node"); - Vector2 string_pos = Point2(ofs, vofs + (h - text_buf.get_size().y) / 2 + text_buf.get_line_ascent()); - string_pos = string_pos.floor(); - text_buf.draw(get_canvas_item(), string_pos, color); + text = node->get_name(); + ofs += hsep; - vofs += h + vsep; - } - } + TextLine text_buf = TextLine(text, font, font_size); + text_buf.set_width(limit - ofs - icon->get_width() - hsep); - // RELATED TRACKS TITLES + int h = MAX(text_buf.get_size().y, icon->get_height()); - Map<int, Color> subtrack_colors; - subtracks.clear(); + draw_texture(icon, Point2(ofs, vofs + int(h - icon->get_height()) / 2)); + ofs += icon->get_width(); - for (int i = 0; i < animation->get_track_count(); i++) { - if (animation->track_get_type(i) != Animation::TYPE_BEZIER) { - continue; - } - String path = animation->track_get_path(i); - if (!path.begins_with(base_path)) { - continue; //another node + margin = icon->get_width(); + + Vector2 string_pos = Point2(ofs, vofs); + string_pos = string_pos.floor(); + text_buf.draw(get_canvas_item(), string_pos, color); + + vofs += h + vsep; + } } - path = path.replace_first(base_path, ""); - Color cc = color; - TextLine text_buf = TextLine(path, font, font_size); - text_buf.set_width(limit - margin - hsep); + Ref<Texture2D> remove = get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")); + float remove_hpos = limit - hsep - remove->get_width(); + + Ref<Texture2D> lock = get_theme_icon(SNAME("Lock"), SNAME("EditorIcons")); + Ref<Texture2D> unlock = get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons")); + float lock_hpos = remove_hpos - hsep - lock->get_width(); + + Ref<Texture2D> visible = get_theme_icon(SNAME("GuiVisibilityVisible"), SNAME("EditorIcons")); + Ref<Texture2D> hidden = get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons")); + float visibility_hpos = lock_hpos - hsep - visible->get_width(); + + Ref<Texture2D> solo = get_theme_icon(SNAME("AudioBusSolo"), SNAME("EditorIcons")); + float solo_hpos = visibility_hpos - hsep - solo->get_width(); + + float buttons_width = remove->get_width() + lock->get_width() + visible->get_width() + solo->get_width() + hsep * 3; + + for (int i = 0; i < tracks.size(); ++i) { + // RELATED TRACKS TITLES + + int current_track = tracks[i]; + + String path = animation->track_get_path(current_track); + path = path.replace_first(base_path, ""); + + Color cc = color; + TextLine text_buf = TextLine(path, font, font_size); + text_buf.set_width(limit - margin - buttons_width); + + Rect2 rect = Rect2(margin, vofs, solo_hpos - hsep - solo->get_width(), text_buf.get_size().y + vsep); - Rect2 rect = Rect2(margin, vofs, limit - margin - hsep, text_buf.get_size().y + vsep); - if (i != track) { cc.a *= 0.7; - uint32_t hash = path.hash(); - hash = ((hash >> 16) ^ hash) * 0x45d9f3b; - hash = ((hash >> 16) ^ hash) * 0x45d9f3b; - hash = (hash >> 16) ^ hash; - float h = (hash % 65535) / 65536.0; - Color subcolor; - subcolor.set_hsv(h, 0.2, 0.8); - subcolor.a = 0.5; - draw_rect(Rect2(0, vofs + text_buf.get_size().y * 0.1, margin - hsep, text_buf.get_size().y * 0.8), subcolor); - subtrack_colors[i] = subcolor; - - subtracks[i] = rect; - } else { - Color ac = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - ac.a = 0.5; - draw_rect(rect, ac); - } + float h; + if (path.ends_with(":x")) { + h = 0; + } else if (path.ends_with(":y")) { + h = 0.33f; + } else if (path.ends_with(":z")) { + h = 0.66f; + } else { + uint32_t hash = path.hash(); + hash = ((hash >> 16) ^ hash) * 0x45d9f3b; + hash = ((hash >> 16) ^ hash) * 0x45d9f3b; + hash = (hash >> 16) ^ hash; + h = (hash % 65535) / 65536.0; + } + + if (current_track != selected_track) { + Color track_color; + if (locked_tracks.has(current_track)) { + track_color.set_hsv(h, 0, 0.4); + } else { + track_color.set_hsv(h, 0.2, 0.8); + } + track_color.a = 0.5; + draw_rect(Rect2(0, vofs, margin - hsep, text_buf.get_size().y * 0.8), track_color); + subtrack_colors[current_track] = track_color; + + subtracks[current_track] = rect; + } else { + Color ac = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + ac.a = 0.5; + draw_rect(rect, ac); + if (locked_tracks.has(selected_track)) { + selected_track_color.set_hsv(h, 0.0, 0.4); + } else { + selected_track_color.set_hsv(h, 0.8, 0.8); + } + } + + Vector2 string_pos = Point2(margin, vofs); + text_buf.draw(get_canvas_item(), string_pos, cc); + + float icon_start_height = vofs + rect.size.y / 2; + Rect2 remove_rect = Rect2(remove_hpos, icon_start_height - remove->get_height() / 2, remove->get_width(), remove->get_height()); + draw_texture(remove, remove_rect.position); + + Rect2 lock_rect = Rect2(lock_hpos, icon_start_height - lock->get_height() / 2, lock->get_width(), lock->get_height()); + if (locked_tracks.has(current_track)) { + draw_texture(lock, lock_rect.position); + } else { + draw_texture(unlock, lock_rect.position); + } + + Rect2 visible_rect = Rect2(visibility_hpos, icon_start_height - visible->get_height() / 2, visible->get_width(), visible->get_height()); + if (hidden_tracks.has(current_track)) { + draw_texture(hidden, visible_rect.position); + } else { + draw_texture(visible, visible_rect.position); + } - Vector2 string_pos = Point2(margin, vofs + text_buf.get_line_ascent()); - text_buf.draw(get_canvas_item(), string_pos, cc); + Rect2 solo_rect = Rect2(solo_hpos, icon_start_height - solo->get_height() / 2, solo->get_width(), solo->get_height()); + draw_texture(solo, solo_rect.position); - vofs += text_buf.get_size().y + vsep; + Map<int, Rect2> track_icons; + track_icons[REMOVE_ICON] = remove_rect; + track_icons[LOCK_ICON] = lock_rect; + track_icons[VISIBILITY_ICON] = visible_rect; + track_icons[SOLO_ICON] = solo_rect; + + subtrack_icons[current_track] = track_icons; + + vofs += text_buf.get_size().y + vsep; + } } Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); @@ -398,6 +481,9 @@ void AnimationBezierTrackEdit::_notification(int p_what) { float scale = timeline->get_zoom_scale(); Ref<Texture2D> point = get_theme_icon(SNAME("KeyValue"), SNAME("EditorIcons")); for (const KeyValue<int, Color> &E : subtrack_colors) { + if (hidden_tracks.has(E.key)) { + continue; + } _draw_track(E.key, E.value); for (int i = 0; i < animation->track_get_key_count(E.key); i++) { @@ -412,70 +498,116 @@ void AnimationBezierTrackEdit::_notification(int p_what) { } } - //draw edited curve - const Color highlight = get_theme_color(SNAME("highlight_color"), SNAME("Editor")); - _draw_track(track, highlight); + if (track_count > 0 && !hidden_tracks.has(selected_track)) { + //draw edited curve + _draw_track(selected_track, selected_track_color); + } } //draw editor handles { edit_points.clear(); - float scale = timeline->get_zoom_scale(); - for (int i = 0; i < animation->track_get_key_count(track); i++) { - float offset = animation->track_get_key_time(track, i); - float value = animation->bezier_track_get_key_value(track, i); - if (moving_selection && selection.has(i)) { - offset += moving_selection_offset.x; - value += moving_selection_offset.y; + for (int i = 0; i < track_count; ++i) { + if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER || hidden_tracks.has(i)) { + continue; } - Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value)); - - Vector2 in_vec = animation->bezier_track_get_key_in_handle(track, i); - if (moving_handle != 0 && moving_handle_key == i) { - in_vec = moving_handle_left; + if (hidden_tracks.has(i) || locked_tracks.has(i)) { + continue; } - Vector2 pos_in(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y)); - Vector2 out_vec = animation->bezier_track_get_key_out_handle(track, i); + int key_count = animation->track_get_key_count(i); + String path = animation->track_get_path(i); - if (moving_handle != 0 && moving_handle_key == i) { - out_vec = moving_handle_right; + if (is_filtered) { + if (root && root->has_node(path)) { + Node *node = root->get_node(path); + if (!node) { + continue; // No node, no filter. + } + if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { + continue; // Skip track due to not selected. + } + } } - Vector2 pos_out(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y)); + for (int j = 0; j < key_count; ++j) { + float offset = animation->track_get_key_time(i, j); + float value = animation->bezier_track_get_key_value(i, j); + + if (moving_selection && selection.has(IntPair(i, j))) { + offset += moving_selection_offset.x; + value += moving_selection_offset.y; + } - _draw_line_clipped(pos, pos_in, accent, limit, right_limit); - _draw_line_clipped(pos, pos_out, accent, limit, right_limit); + Vector2 pos((offset - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value)); - EditPoint ep; - if (pos.x >= limit && pos.x <= right_limit) { - ep.point_rect.position = (pos - bezier_icon->get_size() / 2).floor(); - ep.point_rect.size = bezier_icon->get_size(); - if (selection.has(i)) { - draw_texture(selected_icon, ep.point_rect.position); - draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + TS->format_number(rtos(Math::snapped(offset, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); - draw_string(font, ep.point_rect.position + Vector2(8, -8), TTR("Value:") + " " + TS->format_number(rtos(Math::snapped(value, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); - } else { - draw_texture(bezier_icon, ep.point_rect.position); + Vector2 in_vec = animation->bezier_track_get_key_in_handle(i, j); + if (moving_handle != 0 && moving_handle_track == i && moving_handle_key == j) { + in_vec = moving_handle_left; + } + Vector2 pos_in(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y)); + + Vector2 out_vec = animation->bezier_track_get_key_out_handle(i, j); + + if (moving_handle != 0 && moving_handle_track == i && moving_handle_key == j) { + out_vec = moving_handle_right; + } + + Vector2 pos_out(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y)); + + if (i == selected_track || selection.has(IntPair(i, j))) { + _draw_line_clipped(pos, pos_in, accent, limit, right_limit); + _draw_line_clipped(pos, pos_out, accent, limit, right_limit); + } + + EditPoint ep; + ep.track = i; + ep.key = j; + if (pos.x >= limit && pos.x <= right_limit) { + ep.point_rect.position = (pos - bezier_icon->get_size() / 2).floor(); + ep.point_rect.size = bezier_icon->get_size(); + if (selection.has(IntPair(i, j))) { + draw_texture(selected_icon, ep.point_rect.position); + draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + TS->format_number(rtos(Math::snapped(offset, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); + draw_string(font, ep.point_rect.position + Vector2(8, -8), TTR("Value:") + " " + TS->format_number(rtos(Math::snapped(value, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); + } else { + Color track_color = Color(1, 1, 1, 1); + if (i != selected_track) { + track_color = subtrack_colors[i]; + } + draw_texture(bezier_icon, ep.point_rect.position, track_color); + } + ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5); + } + if (i == selected_track || selection.has(IntPair(i, j))) { + if (pos_in.x >= limit && pos_in.x <= right_limit) { + ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2).floor(); + ep.in_rect.size = bezier_handle_icon->get_size(); + draw_texture(bezier_handle_icon, ep.in_rect.position); + ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5); + } + if (pos_out.x >= limit && pos_out.x <= right_limit) { + ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2).floor(); + ep.out_rect.size = bezier_handle_icon->get_size(); + draw_texture(bezier_handle_icon, ep.out_rect.position); + ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5); + } + } + if (!locked_tracks.has(i)) { + edit_points.push_back(ep); } - ep.point_rect = ep.point_rect.grow(ep.point_rect.size.width * 0.5); - } - if (pos_in.x >= limit && pos_in.x <= right_limit) { - ep.in_rect.position = (pos_in - bezier_handle_icon->get_size() / 2).floor(); - ep.in_rect.size = bezier_handle_icon->get_size(); - draw_texture(bezier_handle_icon, ep.in_rect.position); - ep.in_rect = ep.in_rect.grow(ep.in_rect.size.width * 0.5); } - if (pos_out.x >= limit && pos_out.x <= right_limit) { - ep.out_rect.position = (pos_out - bezier_handle_icon->get_size() / 2).floor(); - ep.out_rect.size = bezier_handle_icon->get_size(); - draw_texture(bezier_handle_icon, ep.out_rect.position); - ep.out_rect = ep.out_rect.grow(ep.out_rect.size.width * 0.5); + } + + for (int i = 0; i < edit_points.size(); ++i) { + if (edit_points[i].track == selected_track) { + EditPoint ep = edit_points[i]; + edit_points.remove_at(i); + edit_points.insert(0, ep); } - edit_points.push_back(ep); } } @@ -506,15 +638,7 @@ Ref<Animation> AnimationBezierTrackEdit::get_animation() const { void AnimationBezierTrackEdit::set_animation_and_track(const Ref<Animation> &p_animation, int p_track) { animation = p_animation; - track = p_track; - if (is_connected("select_key", Callable(editor, "_key_selected"))) { - disconnect("select_key", Callable(editor, "_key_selected")); - } - if (is_connected("deselect_key", Callable(editor, "_key_deselected"))) { - disconnect("deselect_key", Callable(editor, "_key_deselected")); - } - connect("select_key", Callable(editor, "_key_selected"), varray(p_track), CONNECT_DEFERRED); - connect("deselect_key", Callable(editor, "_key_deselected"), varray(p_track), CONNECT_DEFERRED); + selected_track = p_track; update(); } @@ -529,11 +653,14 @@ void AnimationBezierTrackEdit::set_undo_redo(UndoRedo *p_undo_redo) { void AnimationBezierTrackEdit::set_timeline(AnimationTimelineEdit *p_timeline) { timeline = p_timeline; timeline->connect("zoom_changed", callable_mp(this, &AnimationBezierTrackEdit::_zoom_changed)); + timeline->connect("name_limit_changed", callable_mp(this, &AnimationBezierTrackEdit::_zoom_changed)); } void AnimationBezierTrackEdit::set_editor(AnimationTrackEditor *p_editor) { editor = p_editor; connect("clear_selection", Callable(editor, "_clear_selection"), varray(false)); + connect("select_key", Callable(editor, "_key_selected"), varray(), CONNECT_DEFERRED); + connect("deselect_key", Callable(editor, "_key_deselected"), varray(), CONNECT_DEFERRED); } void AnimationBezierTrackEdit::_play_position_draw() { @@ -544,9 +671,11 @@ void AnimationBezierTrackEdit::_play_position_draw() { float scale = timeline->get_zoom_scale(); int h = get_size().height; - int px = (-timeline->get_value() + play_position_pos) * scale + timeline->get_name_limit(); + int limit = timeline->get_name_limit(); + + int px = (-timeline->get_value() + play_position_pos) * scale + limit; - if (px >= timeline->get_name_limit() && px < (get_size().width - timeline->get_buttons_width())) { + if (px >= limit && px < (get_size().width)) { Color color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); play_position->draw_line(Point2(px, 0), Point2(px, h), color, Math::round(2 * EDSCALE)); } @@ -565,11 +694,84 @@ void AnimationBezierTrackEdit::set_root(Node *p_root) { root = p_root; } +void AnimationBezierTrackEdit::set_filtered(bool p_filtered) { + is_filtered = p_filtered; + if (animation == nullptr) { + return; + } + String base_path = animation->track_get_path(selected_track); + if (is_filtered) { + if (root && root->has_node(base_path)) { + Node *node = root->get_node(base_path); + if (!node || !EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { + for (int i = 0; i < animation->get_track_count(); ++i) { + if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER) { + continue; + } + + base_path = animation->track_get_path(i); + if (root && root->has_node(base_path)) { + node = root->get_node(base_path); + if (!node) { + continue; // No node, no filter. + } + if (!EditorNode::get_singleton()->get_editor_selection()->is_selected(node)) { + continue; // Skip track due to not selected. + } + + set_animation_and_track(animation, i); + break; + } + } + } + } + } + update(); +} + void AnimationBezierTrackEdit::_zoom_changed() { update(); play_position->update(); } +void AnimationBezierTrackEdit::_update_locked_tracks_after(int p_track) { + if (locked_tracks.has(p_track)) { + locked_tracks.erase(p_track); + } + + Vector<int> updated_locked_tracks; + for (Set<int>::Element *E = locked_tracks.front(); E; E = E->next()) { + updated_locked_tracks.push_back(E->get()); + } + locked_tracks.clear(); + for (int i = 0; i < updated_locked_tracks.size(); ++i) { + if (updated_locked_tracks[i] > p_track) { + locked_tracks.insert(updated_locked_tracks[i] - 1); + } else { + locked_tracks.insert(updated_locked_tracks[i]); + } + } +} + +void AnimationBezierTrackEdit::_update_hidden_tracks_after(int p_track) { + if (hidden_tracks.has(p_track)) { + hidden_tracks.erase(p_track); + } + + Vector<int> updated_hidden_tracks; + for (Set<int>::Element *E = hidden_tracks.front(); E; E = E->next()) { + updated_hidden_tracks.push_back(E->get()); + } + hidden_tracks.clear(); + for (int i = 0; i < updated_hidden_tracks.size(); ++i) { + if (updated_hidden_tracks[i] > p_track) { + hidden_tracks.insert(updated_hidden_tracks[i] - 1); + } else { + hidden_tracks.insert(updated_hidden_tracks[i]); + } + } +} + String AnimationBezierTrackEdit::get_tooltip(const Point2 &p_pos) const { return Control::get_tooltip(p_pos); } @@ -583,10 +785,10 @@ void AnimationBezierTrackEdit::_clear_selection() { void AnimationBezierTrackEdit::_change_selected_keys_handle_mode(Animation::HandleMode p_mode) { undo_redo->create_action(TTR("Update Selected Key Handles")); double ratio = timeline->get_zoom_scale() * v_zoom; - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - const int key_index = E->get(); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_handle_mode", track, key_index, animation->bezier_track_get_key_handle_mode(track, key_index), ratio); - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_handle_mode", track, key_index, p_mode, ratio); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + const IntPair track_key_pair = E->get(); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_handle_mode", track_key_pair.first, track_key_pair.second, animation->bezier_track_get_key_handle_mode(track_key_pair.first, track_key_pair.second), ratio); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_handle_mode", track_key_pair.first, track_key_pair.second, p_mode, ratio); } undo_redo->commit_action(); } @@ -606,8 +808,8 @@ void AnimationBezierTrackEdit::_select_at_anim(const Ref<Animation> &p_anim, int int idx = animation->track_find_key(p_track, p_pos, true); ERR_FAIL_COND(idx < 0); - selection.insert(idx); - emit_signal(SNAME("select_key"), idx, true); + selection.insert(IntPair(p_track, idx)); + emit_signal(SNAME("select_key"), p_track, idx, true); update(); } @@ -631,14 +833,100 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { } } + Ref<InputEventKey> key_press = p_event; + + if (key_press.is_valid() && key_press->is_pressed()) { + if (ED_GET_SHORTCUT("animation_bezier_editor/focus")->matches_event(p_event)) { + SelectionSet focused_keys; + if (selection.is_empty()) { + for (int i = 0; i < edit_points.size(); ++i) { + IntPair key_pair = IntPair(edit_points[i].track, edit_points[i].key); + focused_keys.insert(key_pair); + } + } else { + for (SelectionSet::Element *E = selection.front(); E; E = E->next()) { + focused_keys.insert(E->get()); + if (E->get().second > 0) { + IntPair previous_key = IntPair(E->get().first, E->get().second - 1); + focused_keys.insert(previous_key); + } + if (E->get().second < animation->track_get_key_count(E->get().first) - 1) { + IntPair next_key = IntPair(E->get().first, E->get().second + 1); + focused_keys.insert(next_key); + } + } + } + if (focused_keys.is_empty()) { + accept_event(); + return; + } + + float minimum_time = INFINITY; + float maximum_time = -INFINITY; + float minimum_value = INFINITY; + float maximum_value = -INFINITY; + + for (SelectionSet::Element *E = focused_keys.front(); E; E = E->next()) { + IntPair key_pair = E->get(); + + float time = animation->track_get_key_time(key_pair.first, key_pair.second); + float value = animation->bezier_track_get_key_value(key_pair.first, key_pair.second); + + minimum_time = MIN(time, minimum_time); + maximum_time = MAX(time, maximum_time); + minimum_value = MIN(value, minimum_value); + maximum_value = MAX(value, maximum_value); + } + + float width = get_size().width - timeline->get_name_limit() - timeline->get_buttons_width(); + float padding = width * 0.1; + float desired_scale = (width - padding / 2) / (maximum_time - minimum_time); + minimum_time = MAX(0, minimum_time - (padding / 2) / desired_scale); + + float zv = Math::pow(100 / desired_scale, 0.125f); + if (zv < 1) { + zv = Math::pow(desired_scale / 100, 0.125f) - 1; + zv = 1 - zv; + } + float zoom_value = timeline->get_zoom()->get_max() - zv; + + timeline->get_zoom()->set_value(zoom_value); + timeline->call_deferred("set_value", minimum_time); + + v_scroll = (maximum_value + minimum_value) / 2.0; + v_zoom = (maximum_value - minimum_value) / ((get_size().height - timeline->get_size().height) * 0.9); + + update(); + accept_event(); + return; + } else if (ED_GET_SHORTCUT("animation_bezier_editor/select_all_keys")->matches_event(p_event)) { + for (int i = 0; i < edit_points.size(); ++i) { + selection.insert(IntPair(edit_points[i].track, edit_points[i].key)); + } + + update(); + accept_event(); + return; + } else if (ED_GET_SHORTCUT("animation_bezier_editor/deselect_all_keys")->matches_event(p_event)) { + selection.clear(); + + update(); + accept_event(); + return; + } + } + Ref<InputEventMouseButton> mb = p_event; + int limit = timeline->get_name_limit(); if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { menu_insert_key = mb->get_position(); - if (menu_insert_key.x >= timeline->get_name_limit() && menu_insert_key.x <= get_size().width - timeline->get_buttons_width()) { + if (menu_insert_key.x >= limit && menu_insert_key.x <= get_size().width) { Vector2 popup_pos = get_screen_position() + mb->get_position(); menu->clear(); - menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT); + if (!locked_tracks.has(selected_track) || locked_tracks.has(selected_track)) { + menu->add_icon_item(bezier_icon, TTR("Insert Key Here"), MENU_KEY_INSERT); + } if (selection.size()) { menu->add_separator(); menu->add_icon_item(get_theme_icon(SNAME("Duplicate"), SNAME("EditorIcons")), TTR("Duplicate Selected Key(s)"), MENU_KEY_DUPLICATE); @@ -649,50 +937,163 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { menu->add_icon_item(get_theme_icon(SNAME("BezierHandlesBalanced"), SNAME("EditorIcons")), TTR("Make Handles Balanced"), MENU_KEY_SET_HANDLE_BALANCED); } - menu->set_as_minsize(); - menu->set_position(popup_pos); - menu->popup(); + if (menu->get_item_count()) { + menu->set_as_minsize(); + menu->set_position(popup_pos); + menu->popup(); + } } } if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { for (const KeyValue<int, Rect2> &E : subtracks) { if (E.value.has_point(mb->get_position())) { - set_animation_and_track(animation, E.key); - _clear_selection(); + if (!locked_tracks.has(E.key) && !hidden_tracks.has(E.key)) { + set_animation_and_track(animation, E.key); + _clear_selection(); + } return; } } + for (const KeyValue<int, Map<int, Rect2>> &E : subtrack_icons) { + int track = E.key; + Map<int, Rect2> track_icons = E.value; + for (const KeyValue<int, Rect2> &I : track_icons) { + if (I.value.has_point(mb->get_position())) { + if (I.key == REMOVE_ICON) { + undo_redo->create_action("Remove Bezier Track"); + + undo_redo->add_do_method(this, "_update_locked_tracks_after", track); + undo_redo->add_do_method(this, "_update_hidden_tracks_after", track); + + undo_redo->add_do_method(animation.ptr(), "remove_track", track); + + undo_redo->add_undo_method(animation.ptr(), "add_track", Animation::TrackType::TYPE_BEZIER, track); + undo_redo->add_undo_method(animation.ptr(), "track_set_path", track, animation->track_get_path(track)); + + for (int i = 0; i < animation->track_get_key_count(track); ++i) { + undo_redo->add_undo_method( + animation.ptr(), + "bezier_track_insert_key", + track, animation->track_get_key_time(track, i), + animation->bezier_track_get_key_value(track, i), + animation->bezier_track_get_key_in_handle(track, i), + animation->bezier_track_get_key_out_handle(track, i), + animation->bezier_track_get_key_handle_mode(track, i)); + } + + undo_redo->commit_action(); + + selected_track = CLAMP(selected_track, 0, animation->get_track_count() - 1); + return; + } else if (I.key == LOCK_ICON) { + if (locked_tracks.has(track)) { + locked_tracks.erase(track); + } else { + locked_tracks.insert(track); + if (selected_track == track) { + for (int i = 0; i < animation->get_track_count(); ++i) { + if (!locked_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + set_animation_and_track(animation, i); + break; + } + } + } + } + update(); + return; + } else if (I.key == VISIBILITY_ICON) { + if (hidden_tracks.has(track)) { + hidden_tracks.erase(track); + } else { + hidden_tracks.insert(track); + if (selected_track == track) { + for (int i = 0; i < animation->get_track_count(); ++i) { + if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + set_animation_and_track(animation, i); + break; + } + } + } + } + + Vector<int> visible_tracks; + for (int i = 0; i < animation->get_track_count(); ++i) { + if (!hidden_tracks.has(i) && animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + visible_tracks.push_back(i); + } + } + + if (visible_tracks.size() == 1) { + solo_track = visible_tracks[0]; + } else { + solo_track = -1; + } + + update(); + return; + } else if (I.key == SOLO_ICON) { + if (solo_track == track) { + solo_track = -1; + + hidden_tracks.clear(); + } else { + if (hidden_tracks.has(track)) { + hidden_tracks.erase(track); + } + for (int i = 0; i < animation->get_track_count(); ++i) { + if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + if (i != track && !hidden_tracks.has(i)) { + hidden_tracks.insert(i); + } + } + } + + set_animation_and_track(animation, track); + solo_track = track; + } + update(); + return; + } + return; + } + } + } + for (int i = 0; i < edit_points.size(); i++) { //first check point //command makes it ignore the main point, so control point editors can be force-edited //path 2D editing in the 3D and 2D editors works the same way if (!mb->is_command_pressed()) { if (edit_points[i].point_rect.has_point(mb->get_position())) { + IntPair pair = IntPair(edit_points[i].track, edit_points[i].key); if (mb->is_shift_pressed()) { //add to selection - if (selection.has(i)) { - selection.erase(i); + if (selection.has(pair)) { + selection.erase(pair); } else { - selection.insert(i); + selection.insert(pair); } update(); - select_single_attempt = -1; - } else if (selection.has(i)) { + select_single_attempt = IntPair(-1, -1); + } else if (selection.has(pair)) { moving_selection_attempt = true; moving_selection = false; - moving_selection_from_key = i; + moving_selection_from_key = pair.second; + moving_selection_from_track = pair.first; moving_selection_offset = Vector2(); - select_single_attempt = i; + select_single_attempt = pair; update(); } else { moving_selection_attempt = true; moving_selection = true; - moving_selection_from_key = i; + moving_selection_from_key = pair.second; + moving_selection_from_track = pair.first; moving_selection_offset = Vector2(); + set_animation_and_track(animation, pair.first); selection.clear(); - selection.insert(i); + selection.insert(pair); update(); } return; @@ -701,26 +1102,27 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { if (edit_points[i].in_rect.has_point(mb->get_position())) { moving_handle = -1; - moving_handle_key = i; - moving_handle_left = animation->bezier_track_get_key_in_handle(track, i); - moving_handle_right = animation->bezier_track_get_key_out_handle(track, i); + moving_handle_key = edit_points[i].key; + moving_handle_track = edit_points[i].track; + moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key); + moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key); update(); return; } if (edit_points[i].out_rect.has_point(mb->get_position())) { moving_handle = 1; - moving_handle_key = i; - moving_handle_left = animation->bezier_track_get_key_in_handle(track, i); - moving_handle_right = animation->bezier_track_get_key_out_handle(track, i); + moving_handle_key = edit_points[i].key; + moving_handle_track = edit_points[i].track; + moving_handle_left = animation->bezier_track_get_key_in_handle(edit_points[i].track, edit_points[i].key); + moving_handle_right = animation->bezier_track_get_key_out_handle(edit_points[i].track, edit_points[i].key); update(); return; - ; } } //insert new point - if (mb->is_command_pressed() && mb->get_position().x >= timeline->get_name_limit() && mb->get_position().x < get_size().width - timeline->get_buttons_width()) { + if (mb->get_position().x >= limit && mb->get_position().x < get_size().width && mb->is_command_pressed()) { Array new_point; new_point.resize(6); @@ -733,34 +1135,35 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { new_point[4] = 0; new_point[5] = 0; - float time = ((mb->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); - while (animation->track_find_key(track, time, true) != -1) { + float time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); + while (animation->track_find_key(selected_track, time, true) != -1) { time += 0.001; } undo_redo->create_action(TTR("Add Bezier Point")); - undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point); - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, time); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time); undo_redo->commit_action(); //then attempt to move - int index = animation->track_find_key(track, time, true); + int index = animation->track_find_key(selected_track, time, true); ERR_FAIL_COND(index == -1); _clear_selection(); - selection.insert(index); + selection.insert(IntPair(selected_track, index)); moving_selection_attempt = true; moving_selection = false; moving_selection_from_key = index; + moving_selection_from_track = selected_track; moving_selection_offset = Vector2(); - select_single_attempt = -1; + select_single_attempt = IntPair(-1, -1); update(); return; } //box select - if (mb->get_position().x >= timeline->get_name_limit() && mb->get_position().x < get_size().width - timeline->get_buttons_width()) { + if (mb->get_position().x >= limit && mb->get_position().x < get_size().width) { box_selecting_attempt = true; box_selecting = false; box_selecting_add = false; @@ -786,14 +1189,44 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { } Rect2 selection_rect(bs_from, bs_to - bs_from); + bool track_set = false; for (int i = 0; i < edit_points.size(); i++) { if (edit_points[i].point_rect.intersects(selection_rect)) { - selection.insert(i); + selection.insert(IntPair(edit_points[i].track, edit_points[i].key)); + if (!track_set) { + track_set = true; + set_animation_and_track(animation, edit_points[i].track); + } } } } else { _clear_selection(); //clicked and nothing happened, so clear the selection + + //select by clicking on curve + int track_count = animation->get_track_count(); + + float animation_length = animation->get_length(); + animation->set_length(real_t(INT_MAX)); //bezier_track_interpolate doesn't find keys if they exist beyond anim length + + float time = ((mb->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); + + for (int i = 0; i < track_count; ++i) { + if (animation->track_get_type(i) != Animation::TrackType::TYPE_BEZIER || hidden_tracks.has(i) || locked_tracks.has(i)) { + continue; + } + + float track_h = animation->bezier_track_interpolate(i, time); + float track_height = _bezier_h_to_pixel(track_h); + + if (abs(mb->get_position().y - track_height) < 10) { + set_animation_and_track(animation, i); + break; + } + } + + animation->set_length(animation_length); } + box_selecting_attempt = false; box_selecting = false; update(); @@ -801,10 +1234,10 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { if (moving_handle != 0 && mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { undo_redo->create_action(TTR("Move Bezier Points")); - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, moving_handle_left); - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, moving_handle_right); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, animation->bezier_track_get_key_in_handle(track, moving_handle_key)); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, animation->bezier_track_get_key_out_handle(track, moving_handle_key)); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, moving_handle_left); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, moving_handle_right); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_in_handle(selected_track, moving_handle_key)); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", selected_track, moving_handle_key, animation->bezier_track_get_key_out_handle(selected_track, moving_handle_key)); undo_redo->commit_action(); moving_handle = 0; @@ -819,60 +1252,60 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { List<AnimMoveRestore> to_restore; // 1-remove the keys - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, E->get()); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second); } // 2- remove overlapped keys - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float newtime = editor->snap_time(animation->track_get_key_time(track, E->get()) + moving_selection_offset.x); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float newtime = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x); - int idx = animation->track_find_key(track, newtime, true); + int idx = animation->track_find_key(E->get().first, newtime, true); if (idx == -1) { continue; } - if (selection.has(idx)) { + if (selection.has(IntPair(E->get().first, idx))) { continue; //already in selection, don't save } - undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", track, newtime); + undo_redo->add_do_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newtime); AnimMoveRestore amr; - amr.key = animation->track_get_key_value(track, idx); - amr.track = track; + amr.key = animation->track_get_key_value(E->get().first, idx); + amr.track = E->get().first; amr.time = newtime; to_restore.push_back(amr); } // 3-move the keys (re insert them) - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float newpos = editor->snap_time(animation->track_get_key_time(track, E->get()) + moving_selection_offset.x); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x); /* if (newpos<0) continue; //no add at the beginning */ - Array key = animation->track_get_key_value(track, E->get()); + Array key = animation->track_get_key_value(E->get().first, E->get().second); float h = key[0]; h += moving_selection_offset.y; key[0] = h; - undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, newpos, key, 1); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, newpos, key, 1); } // 4-(undo) remove inserted keys - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float newpos = editor->snap_time(animation->track_get_key_time(track, E->get()) + moving_selection_offset.x); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float newpos = editor->snap_time(animation->track_get_key_time(E->get().first, E->get().second) + moving_selection_offset.x); /* if (newpos<0) continue; //no remove what no inserted */ - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, newpos); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, newpos); } // 5-(undo) reinsert keys - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float oldpos = animation->track_get_key_time(track, E->get()); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, oldpos, animation->track_get_key_value(track, E->get()), 1); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float oldpos = animation->track_get_key_time(E->get().first, E->get().second); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, oldpos, animation->track_get_key_value(E->get().first, E->get().second), 1); } // 6-(undo) reinsert overlapped keys @@ -885,20 +1318,21 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { // 7-reselect - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float oldpos = animation->track_get_key_time(track, E->get()); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float oldpos = animation->track_get_key_time(E->get().first, E->get().second); float newpos = editor->snap_time(oldpos + moving_selection_offset.x); - undo_redo->add_do_method(this, "_select_at_anim", animation, track, newpos); - undo_redo->add_undo_method(this, "_select_at_anim", animation, track, oldpos); + undo_redo->add_do_method(this, "_select_at_anim", animation, E->get().first, newpos); + undo_redo->add_undo_method(this, "_select_at_anim", animation, E->get().first, oldpos); } undo_redo->commit_action(); moving_selection = false; - } else if (select_single_attempt != -1) { + } else if (select_single_attempt != IntPair(-1, -1)) { selection.clear(); selection.insert(select_single_attempt); + set_animation_and_track(animation, select_single_attempt.first); } moving_selection_attempt = false; @@ -909,13 +1343,13 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { if (moving_selection_attempt && mm.is_valid()) { if (!moving_selection) { moving_selection = true; - select_single_attempt = -1; + select_single_attempt = IntPair(-1, -1); } float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll; - float x = editor->snap_time(((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value()); + float x = editor->snap_time(((mm->get_position().x - limit) / timeline->get_zoom_scale()) + timeline->get_value()); - moving_selection_offset = Vector2(x - animation->track_get_key_time(track, moving_selection_from_key), y - animation->bezier_track_get_key_value(track, moving_selection_from_key)); + moving_selection_offset = Vector2(x - animation->track_get_key_time(moving_selection_from_track, moving_selection_from_key), y - animation->bezier_track_get_key_value(moving_selection_from_track, moving_selection_from_key)); update(); } @@ -938,17 +1372,17 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { float y = (get_size().height / 2 - mm->get_position().y) * v_zoom + v_scroll; float x = editor->snap_time((mm->get_position().x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); - Vector2 key_pos = Vector2(animation->track_get_key_time(track, moving_handle_key), animation->bezier_track_get_key_value(track, moving_handle_key)); + Vector2 key_pos = Vector2(animation->track_get_key_time(selected_track, moving_handle_key), animation->bezier_track_get_key_value(selected_track, moving_handle_key)); Vector2 moving_handle_value = Vector2(x, y) - key_pos; - moving_handle_left = animation->bezier_track_get_key_in_handle(track, moving_handle_key); - moving_handle_right = animation->bezier_track_get_key_out_handle(track, moving_handle_key); + moving_handle_left = animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key); + moving_handle_right = animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key); if (moving_handle == -1) { moving_handle_left = moving_handle_value; - if (animation->bezier_track_get_key_handle_mode(track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) { + if (animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) { double ratio = timeline->get_zoom_scale() * v_zoom; Transform2D xform; xform.set_scale(Vector2(1.0, 1.0 / ratio)); @@ -961,7 +1395,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { } else if (moving_handle == 1) { moving_handle_right = moving_handle_value; - if (animation->bezier_track_get_key_handle_mode(track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) { + if (animation->bezier_track_get_key_handle_mode(moving_handle_track, moving_handle_key) == Animation::HANDLE_MODE_BALANCED) { double ratio = timeline->get_zoom_scale() * v_zoom; Transform2D xform; xform.set_scale(Vector2(1.0, 1.0 / ratio)); @@ -980,12 +1414,12 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) { undo_redo->create_action(TTR("Move Bezier Points")); if (moving_handle == -1) { double ratio = timeline->get_zoom_scale() * v_zoom; - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, moving_handle_left, ratio); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", track, moving_handle_key, animation->bezier_track_get_key_in_handle(track, moving_handle_key), ratio); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, moving_handle_left, ratio); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_in_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_in_handle(moving_handle_track, moving_handle_key), ratio); } else if (moving_handle == 1) { double ratio = timeline->get_zoom_scale() * v_zoom; - undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, moving_handle_right, ratio); - undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", track, moving_handle_key, animation->bezier_track_get_key_out_handle(track, moving_handle_key), ratio); + undo_redo->add_do_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, moving_handle_right, ratio); + undo_redo->add_undo_method(animation.ptr(), "bezier_track_set_key_out_handle", moving_handle_track, moving_handle_key, animation->bezier_track_get_key_out_handle(moving_handle_track, moving_handle_key), ratio); } undo_redo->commit_action(); @@ -1028,27 +1462,32 @@ void AnimationBezierTrackEdit::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_or void AnimationBezierTrackEdit::_menu_selected(int p_index) { switch (p_index) { case MENU_KEY_INSERT: { - Array new_point; - new_point.resize(6); + if (animation->get_track_count() > 0) { + Array new_point; + new_point.resize(6); - float h = (get_size().height / 2 - menu_insert_key.y) * v_zoom + v_scroll; + float h = (get_size().height / 2 - menu_insert_key.y) * v_zoom + v_scroll; - new_point[0] = h; - new_point[1] = -0.25; - new_point[2] = 0; - new_point[3] = 0.25; - new_point[4] = 0; - new_point[5] = Animation::HANDLE_MODE_BALANCED; + new_point[0] = h; + new_point[1] = -0.25; + new_point[2] = 0; + new_point[3] = 0.25; + new_point[4] = 0; + new_point[5] = Animation::HANDLE_MODE_BALANCED; - float time = ((menu_insert_key.x - timeline->get_name_limit()) / timeline->get_zoom_scale()) + timeline->get_value(); - while (animation->track_find_key(track, time, true) != -1) { - time += 0.001; - } + int limit = timeline->get_name_limit(); - undo_redo->create_action(TTR("Add Bezier Point")); - undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, time, new_point); - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, time); - undo_redo->commit_action(); + float time = ((menu_insert_key.x - limit) / timeline->get_zoom_scale()) + timeline->get_value(); + + while (animation->track_find_key(selected_track, time, true) != -1) { + time += 0.001; + } + + undo_redo->create_action(TTR("Add Bezier Point")); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time); + undo_redo->commit_action(); + } } break; case MENU_KEY_DUPLICATE: { @@ -1072,8 +1511,8 @@ void AnimationBezierTrackEdit::duplicate_selection() { } float top_time = 1e10; - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float t = animation->track_get_key_time(track, E->get()); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float t = animation->track_get_key_time(E->get().first, E->get().second); if (t < top_time) { top_time = t; } @@ -1083,21 +1522,21 @@ void AnimationBezierTrackEdit::duplicate_selection() { List<Pair<int, float>> new_selection_values; - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - float t = animation->track_get_key_time(track, E->get()); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + float t = animation->track_get_key_time(E->get().first, E->get().second); float dst_time = t + (timeline->get_play_position() - top_time); - int existing_idx = animation->track_find_key(track, dst_time, true); + int existing_idx = animation->track_find_key(E->get().first, dst_time, true); - undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, dst_time, animation->track_get_key_value(track, E->get()), animation->track_get_key_transition(track, E->get())); - undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, dst_time); + undo_redo->add_do_method(animation.ptr(), "track_insert_key", E->get().first, dst_time, animation->track_get_key_value(E->get().first, E->get().second), animation->track_get_key_transition(E->get().first, E->get().second)); + undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", E->get().first, dst_time); Pair<int, float> p; - p.first = track; + p.first = E->get().first; p.second = dst_time; new_selection_values.push_back(p); if (existing_idx != -1) { - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, dst_time, animation->track_get_key_value(track, existing_idx), animation->track_get_key_transition(track, existing_idx)); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, dst_time, animation->track_get_key_value(E->get().first, existing_idx), animation->track_get_key_transition(E->get().first, existing_idx)); } } @@ -1116,7 +1555,7 @@ void AnimationBezierTrackEdit::duplicate_selection() { continue; } - selection.insert(existing_idx); + selection.insert(IntPair(track, existing_idx)); } update(); @@ -1126,9 +1565,9 @@ void AnimationBezierTrackEdit::delete_selection() { if (selection.size()) { undo_redo->create_action(TTR("Anim Delete Keys")); - for (Set<int>::Element *E = selection.back(); E; E = E->prev()) { - undo_redo->add_do_method(animation.ptr(), "track_remove_key", track, E->get()); - undo_redo->add_undo_method(animation.ptr(), "track_insert_key", track, animation->track_get_key_time(track, E->get()), animation->track_get_key_value(track, E->get()), 1); + for (SelectionSet::Element *E = selection.back(); E; E = E->prev()) { + undo_redo->add_do_method(animation.ptr(), "track_remove_key", E->get().first, E->get().second); + undo_redo->add_undo_method(animation.ptr(), "track_insert_key", E->get().first, animation->track_get_key_time(E->get().first, E->get().second), animation->track_get_key_value(E->get().first, E->get().second), 1); } undo_redo->add_do_method(this, "_clear_selection_for_anim", animation); undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation); @@ -1142,12 +1581,14 @@ void AnimationBezierTrackEdit::_bind_methods() { ClassDB::bind_method("_clear_selection", &AnimationBezierTrackEdit::_clear_selection); ClassDB::bind_method("_clear_selection_for_anim", &AnimationBezierTrackEdit::_clear_selection_for_anim); ClassDB::bind_method("_select_at_anim", &AnimationBezierTrackEdit::_select_at_anim); + ClassDB::bind_method("_update_hidden_tracks_after", &AnimationBezierTrackEdit::_update_hidden_tracks_after); + ClassDB::bind_method("_update_locked_tracks_after", &AnimationBezierTrackEdit::_update_locked_tracks_after); ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "drag"))); ADD_SIGNAL(MethodInfo("remove_request", PropertyInfo(Variant::INT, "track"))); ADD_SIGNAL(MethodInfo("insert_key", PropertyInfo(Variant::FLOAT, "ofs"))); - ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"))); - ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"))); + ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "track"), PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"))); + ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "track"), PropertyInfo(Variant::INT, "index"))); ADD_SIGNAL(MethodInfo("clear_selection")); ADD_SIGNAL(MethodInfo("close_request")); @@ -1170,14 +1611,9 @@ AnimationBezierTrackEdit::AnimationBezierTrackEdit() { set_clip_contents(true); - close_button = memnew(Button); - close_button->connect("pressed", Callable(this, SNAME("emit_signal")), varray(SNAME("close_request"))); - close_button->set_text(TTR("Close")); - - right_column = memnew(VBoxContainer); - right_column->add_child(close_button); - right_column->add_spacer(); - add_child(right_column); + ED_SHORTCUT("animation_bezier_editor/focus", TTR("Focus"), Key::F); + ED_SHORTCUT("animation_bezier_editor/select_all_keys", TTR("Select All Keys"), KeyModifierMask::CMD | Key::A); + ED_SHORTCUT("animation_bezier_editor/deselect_all_keys", TTR("Deselect All Keys"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::A); menu = memnew(PopupMenu); add_child(menu); diff --git a/editor/animation_bezier_editor.h b/editor/animation_bezier_editor.h index cf719a0355..fa6fc405f2 100644 --- a/editor/animation_bezier_editor.h +++ b/editor/animation_bezier_editor.h @@ -46,9 +46,6 @@ class AnimationBezierTrackEdit : public Control { MENU_KEY_SET_HANDLE_BALANCED, }; - VBoxContainer *right_column; - Button *close_button; - AnimationTimelineEdit *timeline = nullptr; UndoRedo *undo_redo = nullptr; Node *root = nullptr; @@ -56,7 +53,7 @@ class AnimationBezierTrackEdit : public Control { float play_position_pos = 0; Ref<Animation> animation; - int track; + int selected_track; Vector<Rect2> view_rects; @@ -66,6 +63,19 @@ class AnimationBezierTrackEdit : public Control { Map<int, Rect2> subtracks; + enum { + REMOVE_ICON, + LOCK_ICON, + SOLO_ICON, + VISIBILITY_ICON + }; + + Map<int, Map<int, Rect2>> subtrack_icons; + Set<int> locked_tracks; + Set<int> hidden_tracks; + int solo_track = -1; + bool is_filtered = false; + float v_scroll = 0; float v_zoom = 1; @@ -73,6 +83,9 @@ class AnimationBezierTrackEdit : public Control { void _zoom_changed(); + void _update_locked_tracks_after(int p_track); + void _update_hidden_tracks_after(int p_track); + virtual void gui_input(const Ref<InputEvent> &p_event) override; void _menu_selected(int p_index); @@ -80,10 +93,13 @@ class AnimationBezierTrackEdit : public Control { Vector2 insert_at_pos; + typedef Pair<int, int> IntPair; + bool moving_selection_attempt = false; - int select_single_attempt = -1; + IntPair select_single_attempt; bool moving_selection = false; int moving_selection_from_key; + int moving_selection_from_track; Vector2 moving_selection_offset; @@ -95,6 +111,7 @@ class AnimationBezierTrackEdit : public Control { int moving_handle = 0; //0 no move -1 or +1 out int moving_handle_key = 0; + int moving_handle_track = 0; Vector2 moving_handle_left; Vector2 moving_handle_right; int moving_handle_mode; // value from Animation::HandleMode @@ -119,11 +136,25 @@ class AnimationBezierTrackEdit : public Control { Rect2 point_rect; Rect2 in_rect; Rect2 out_rect; + int track; + int key; }; Vector<EditPoint> edit_points; - Set<int> selection; + struct SelectionCompare { + bool operator()(const IntPair &lh, const IntPair &rh) { + if (lh.first == rh.first) { + return lh.second < rh.second; + } else { + return lh.first < rh.first; + } + } + }; + + typedef Set<IntPair, SelectionCompare> SelectionSet; + + SelectionSet selection; Ref<ViewPanner> panner; void _scroll_callback(Vector2 p_scroll_vec, bool p_alt); @@ -151,6 +182,7 @@ public: void set_timeline(AnimationTimelineEdit *p_timeline); void set_editor(AnimationTrackEditor *p_editor); void set_root(Node *p_root); + void set_filtered(bool p_filtered); void set_play_position(float p_pos); void update_play_position(); diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 2f33619a52..de924e84dc 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -570,7 +570,7 @@ public: p_list->push_back(PropertyInfo(Variant::VECTOR3, "position")); } break; case Animation::TYPE_ROTATION_3D: { - p_list->push_back(PropertyInfo(Variant::VECTOR3, "rotation")); + p_list->push_back(PropertyInfo(Variant::QUATERNION, "rotation")); } break; case Animation::TYPE_SCALE_3D: { p_list->push_back(PropertyInfo(Variant::VECTOR3, "scale")); @@ -2118,23 +2118,19 @@ void AnimationTrackEdit::_notification(int p_what) { update_mode_rect.position.y = 0; update_mode_rect.size.y = get_size().height; - ofs += update_icon->get_width() + hsep; - update_mode_rect.size.x += hsep; + ofs += update_icon->get_width() + hsep / 2; + update_mode_rect.size.x += hsep / 2; if (animation->track_get_type(track) == Animation::TYPE_VALUE) { draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); update_mode_rect.size.x += down_icon->get_width(); - bezier_edit_rect = Rect2(); } else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) { Ref<Texture2D> bezier_icon = get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons")); update_mode_rect.size.x += down_icon->get_width(); - bezier_edit_rect.position = update_mode_rect.position + (update_mode_rect.size - bezier_icon->get_size()) / 2; - bezier_edit_rect.size = bezier_icon->get_size(); - draw_texture(bezier_icon, bezier_edit_rect.position); + update_mode_rect = Rect2(); } else { update_mode_rect = Rect2(); - bezier_edit_rect = Rect2(); } ofs += down_icon->get_width(); @@ -2160,8 +2156,8 @@ void AnimationTrackEdit::_notification(int p_what) { interp_mode_rect.position.y = 0; interp_mode_rect.size.y = get_size().height; - ofs += icon->get_width() + hsep; - interp_mode_rect.size.x += hsep; + ofs += icon->get_width() + hsep / 2; + interp_mode_rect.size.x += hsep / 2; if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) { draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); @@ -2193,8 +2189,8 @@ void AnimationTrackEdit::_notification(int p_what) { loop_wrap_rect.position.y = 0; loop_wrap_rect.size.y = get_size().height; - ofs += icon->get_width() + hsep; - loop_wrap_rect.size.x += hsep; + ofs += icon->get_width() + hsep / 2; + loop_wrap_rect.size.x += hsep / 2; if (!animation->track_is_compressed(track) && (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_BLEND_SHAPE || animation->track_get_type(track) == Animation::TYPE_POSITION_3D || animation->track_get_type(track) == Animation::TYPE_SCALE_3D || animation->track_get_type(track) == Animation::TYPE_ROTATION_3D)) { draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2)); @@ -2213,7 +2209,7 @@ void AnimationTrackEdit::_notification(int p_what) { Ref<Texture2D> icon = get_theme_icon(animation->track_is_compressed(track) ? SNAME("Lock") : SNAME("Remove"), SNAME("EditorIcons")); - remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width()) / 2; + remove_rect.position.x = ofs + ((get_size().width - ofs) - icon->get_width()); remove_rect.position.y = int(get_size().height - icon->get_height()) / 2; remove_rect.size = icon->get_size(); @@ -2792,11 +2788,6 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) { return; } - if (bezier_edit_rect.has_point(pos)) { - emit_signal(SNAME("bezier_edit")); - accept_event(); - } - // Check keyframes. if (!animation->track_is_compressed(track)) { // Selecting compressed keyframes for editing is not possible. @@ -3326,10 +3317,21 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) { snap->set_disabled(false); snap_mode->set_disabled(false); + bezier_edit_icon->set_disabled(true); + imported_anim_warning->hide(); + bool import_warning_done = false; + bool bezier_done = false; for (int i = 0; i < animation->get_track_count(); i++) { if (animation->track_is_imported(i)) { imported_anim_warning->show(); + import_warning_done = true; + } + if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + bezier_edit_icon->set_disabled(false); + bezier_done = true; + } + if (import_warning_done && bezier_done) { break; } } @@ -3343,6 +3345,7 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim) { step->set_read_only(true); snap->set_disabled(true); snap_mode->set_disabled(true); + bezier_edit_icon->set_disabled(true); } } @@ -4167,13 +4170,15 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD } break; case Animation::TYPE_BEZIER: { Array array; - array.resize(5); + array.resize(6); array[0] = p_id.value; array[1] = -0.25; array[2] = 0; array[3] = 0.25; array[4] = 0; + array[5] = Animation::HANDLE_MODE_BALANCED; value = array; + bezier_edit_icon->set_disabled(false); } break; case Animation::TYPE_ANIMATION: { @@ -4399,7 +4404,6 @@ void AnimationTrackEditor::_update_tracks() { track_edit->connect("insert_key", callable_mp(this, &AnimationTrackEditor::_insert_key_from_track), varray(i), CONNECT_DEFERRED); track_edit->connect("select_key", callable_mp(this, &AnimationTrackEditor::_key_selected), varray(i), CONNECT_DEFERRED); track_edit->connect("deselect_key", callable_mp(this, &AnimationTrackEditor::_key_deselected), varray(i), CONNECT_DEFERRED); - track_edit->connect("bezier_edit", callable_mp(this, &AnimationTrackEditor::_bezier_edit), varray(i), CONNECT_DEFERRED); track_edit->connect("move_selection_begin", callable_mp(this, &AnimationTrackEditor::_move_selection_begin)); track_edit->connect("move_selection", callable_mp(this, &AnimationTrackEditor::_move_selection)); track_edit->connect("move_selection_commit", callable_mp(this, &AnimationTrackEditor::_move_selection_commit)); @@ -4515,6 +4519,7 @@ void AnimationTrackEditor::_notification(int p_what) { if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { zoom_icon->set_texture(get_theme_icon(SNAME("Zoom"), SNAME("EditorIcons"))); + bezier_edit_icon->set_icon(get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons"))); snap->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); view_group->set_icon(get_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup"), SNAME("EditorIcons"))); selected_filter->set_icon(get_theme_icon(SNAME("AnimationFilter"), SNAME("EditorIcons"))); @@ -4630,6 +4635,7 @@ void AnimationTrackEditor::_new_track_node_selected(NodePath p_path) { adding_track_path = path_to; prop_selector->set_type_filter(filter); prop_selector->select_property_from_instance(node); + bezier_edit_icon->set_disabled(false); } break; case Animation::TYPE_AUDIO: { if (!node->is_class("AudioStreamPlayer") && !node->is_class("AudioStreamPlayer2D") && !node->is_class("AudioStreamPlayer3D")) { @@ -4946,7 +4952,7 @@ void AnimationTrackEditor::_add_method_key(const String &p_method) { EditorNode::get_singleton()->show_warning(TTR("Method not found in object: ") + p_method); } -void AnimationTrackEditor::_key_selected(int p_key, bool p_single, int p_track) { +void AnimationTrackEditor::_key_selected(int p_track, int p_key, bool p_single) { ERR_FAIL_INDEX(p_track, animation->get_track_count()); ERR_FAIL_INDEX(p_key, animation->track_get_key_count(p_track)); @@ -5294,6 +5300,20 @@ void AnimationTrackEditor::_scroll_input(const Ref<InputEvent> &p_event) { } } +void AnimationTrackEditor::_toggle_bezier_edit() { + if (bezier_edit->is_visible()) { + _cancel_bezier_edit(); + } else { + int track_count = animation->get_track_count(); + for (int i = 0; i < track_count; ++i) { + if (animation->track_get_type(i) == Animation::TrackType::TYPE_BEZIER) { + _bezier_edit(i); + return; + } + } + } +} + void AnimationTrackEditor::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) { if (p_alt) { if (p_scroll_vec.x < 0 || p_scroll_vec.y < 0) { @@ -5322,6 +5342,7 @@ void AnimationTrackEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin void AnimationTrackEditor::_cancel_bezier_edit() { bezier_edit->hide(); scroll->show(); + bezier_edit_icon->set_pressed(false); } void AnimationTrackEditor::_bezier_edit(int p_for_track) { @@ -5908,6 +5929,7 @@ void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) { void AnimationTrackEditor::_view_group_toggle() { _update_tracks(); view_group->set_icon(get_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup"), SNAME("EditorIcons"))); + bezier_edit->set_filtered(selected_filter->is_pressed()); } bool AnimationTrackEditor::is_grouping_tracks() { @@ -6153,6 +6175,15 @@ AnimationTrackEditor::AnimationTrackEditor() { bottom_hb->add_spacer(); + bezier_edit_icon = memnew(Button); + bezier_edit_icon->set_flat(true); + bezier_edit_icon->set_disabled(true); + bezier_edit_icon->set_toggle_mode(true); + bezier_edit_icon->connect("pressed", callable_mp(this, &AnimationTrackEditor::_toggle_bezier_edit)); + bezier_edit_icon->set_tooltip(TTR("Toggle between the bezier curve editor and track editor.")); + + bottom_hb->add_child(bezier_edit_icon); + selected_filter = memnew(Button); selected_filter->set_flat(true); selected_filter->connect("pressed", callable_mp(this, &AnimationTrackEditor::_view_group_toggle)); // Same function works the same. diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 50c5c692c0..edba784310 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -164,7 +164,6 @@ class AnimationTrackEdit : public Control { Rect2 interp_mode_rect; Rect2 loop_wrap_rect; Rect2 remove_rect; - Rect2 bezier_edit_rect; Ref<Texture2D> type_icon; Ref<Texture2D> selected_icon; @@ -300,6 +299,7 @@ class AnimationTrackEditor : public VBoxContainer { EditorSpinSlider *step; TextureRect *zoom_icon; Button *snap; + Button *bezier_edit_icon; OptionButton *snap_mode; Button *imported_anim_warning; @@ -406,7 +406,7 @@ class AnimationTrackEditor : public VBoxContainer { Map<SelectedKey, KeyInfo> selection; - void _key_selected(int p_key, bool p_single, int p_track); + void _key_selected(int p_track, int p_key, bool p_single); void _key_deselected(int p_key, int p_track); bool moving_selection; @@ -431,6 +431,7 @@ class AnimationTrackEditor : public VBoxContainer { Vector<Ref<AnimationTrackEditPlugin>> track_edit_plugins; + void _toggle_bezier_edit(); void _cancel_bezier_edit(); void _bezier_edit(int p_for_track); diff --git a/editor/debugger/editor_network_profiler.cpp b/editor/debugger/editor_network_profiler.cpp index 698e950f57..b05134144e 100644 --- a/editor/debugger/editor_network_profiler.cpp +++ b/editor/debugger/editor_network_profiler.cpp @@ -56,7 +56,7 @@ void EditorNetworkProfiler::_update_frame() { TreeItem *root = counters_display->create_item(); - for (const KeyValue<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo> &E : nodes_data) { + for (const KeyValue<ObjectID, SceneDebugger::RPCNodeInfo> &E : nodes_data) { TreeItem *node = counters_display->create_item(root); for (int j = 0; j < counters_display->get_columns(); ++j) { @@ -65,9 +65,7 @@ void EditorNetworkProfiler::_update_frame() { node->set_text(0, E.value.node_path); node->set_text(1, E.value.incoming_rpc == 0 ? "-" : itos(E.value.incoming_rpc)); - node->set_text(2, E.value.incoming_rset == 0 ? "-" : itos(E.value.incoming_rset)); - node->set_text(3, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc)); - node->set_text(4, E.value.outgoing_rset == 0 ? "-" : itos(E.value.outgoing_rset)); + node->set_text(2, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc)); } } @@ -91,14 +89,12 @@ void EditorNetworkProfiler::_clear_pressed() { } } -void EditorNetworkProfiler::add_node_frame_data(const DebuggerMarshalls::MultiplayerNodeInfo p_frame) { +void EditorNetworkProfiler::add_node_frame_data(const SceneDebugger::RPCNodeInfo p_frame) { if (!nodes_data.has(p_frame.node)) { nodes_data.insert(p_frame.node, p_frame); } else { nodes_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc; - nodes_data[p_frame.node].incoming_rset += p_frame.incoming_rset; nodes_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc; - nodes_data[p_frame.node].outgoing_rset += p_frame.outgoing_rset; } if (frame_delay->is_stopped()) { @@ -174,7 +170,7 @@ EditorNetworkProfiler::EditorNetworkProfiler() { counters_display->set_v_size_flags(SIZE_EXPAND_FILL); counters_display->set_hide_folding(true); counters_display->set_hide_root(true); - counters_display->set_columns(5); + counters_display->set_columns(3); counters_display->set_column_titles_visible(true); counters_display->set_column_title(0, TTR("Node")); counters_display->set_column_expand(0, true); @@ -184,18 +180,10 @@ EditorNetworkProfiler::EditorNetworkProfiler() { counters_display->set_column_expand(1, false); counters_display->set_column_clip_content(1, true); counters_display->set_column_custom_minimum_width(1, 120 * EDSCALE); - counters_display->set_column_title(2, TTR("Incoming RSET")); + counters_display->set_column_title(2, TTR("Outgoing RPC")); counters_display->set_column_expand(2, false); counters_display->set_column_clip_content(2, true); counters_display->set_column_custom_minimum_width(2, 120 * EDSCALE); - counters_display->set_column_title(3, TTR("Outgoing RPC")); - counters_display->set_column_expand(3, false); - counters_display->set_column_clip_content(3, true); - counters_display->set_column_custom_minimum_width(3, 120 * EDSCALE); - counters_display->set_column_title(4, TTR("Outgoing RSET")); - counters_display->set_column_expand(4, false); - counters_display->set_column_clip_content(4, true); - counters_display->set_column_custom_minimum_width(4, 120 * EDSCALE); add_child(counters_display); frame_delay = memnew(Timer); diff --git a/editor/debugger/editor_network_profiler.h b/editor/debugger/editor_network_profiler.h index 320dd2a826..3e95eb0de6 100644 --- a/editor/debugger/editor_network_profiler.h +++ b/editor/debugger/editor_network_profiler.h @@ -31,7 +31,7 @@ #ifndef EDITORNETWORKPROFILER_H #define EDITORNETWORKPROFILER_H -#include "core/debugger/debugger_marshalls.h" +#include "scene/debugger/scene_debugger.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" #include "scene/gui/label.h" @@ -50,7 +50,7 @@ private: Timer *frame_delay; - Map<ObjectID, DebuggerMarshalls::MultiplayerNodeInfo> nodes_data; + Map<ObjectID, SceneDebugger::RPCNodeInfo> nodes_data; void _update_frame(); @@ -62,7 +62,7 @@ protected: static void _bind_methods(); public: - void add_node_frame_data(const DebuggerMarshalls::MultiplayerNodeInfo p_frame); + void add_node_frame_data(const SceneDebugger::RPCNodeInfo p_frame); void set_bandwidth(int p_incoming, int p_outgoing); bool is_profiling(); diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 6aedfa6ccb..28e8edb26e 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -36,7 +36,6 @@ #include "core/io/marshalls.h" #include "core/string/ustring.h" #include "core/version.h" -#include "core/version_hash.gen.h" #include "editor/debugger/debug_adapter/debug_adapter_protocol.h" #include "editor/debugger/editor_network_profiler.h" #include "editor/debugger/editor_performance_profiler.h" @@ -64,6 +63,7 @@ #include "scene/gui/texture_button.h" #include "scene/gui/tree.h" #include "scene/resources/packed_scene.h" +#include "servers/debugger/servers_debugger.h" #include "servers/display_server.h" using CameraOverride = EditorDebuggerNode::CameraOverride; @@ -128,6 +128,7 @@ void ScriptEditorDebugger::debug_continue() { _clear_execution(); _put_msg("continue", Array()); + _put_msg("servers:foreground", Array()); } void ScriptEditorDebugger::update_tabs() { @@ -278,7 +279,7 @@ void ScriptEditorDebugger::_remote_object_property_updated(ObjectID p_id, const } void ScriptEditorDebugger::_video_mem_request() { - _put_msg("core:memory", Array()); + _put_msg("servers:memory", Array()); } void ScriptEditorDebugger::_video_mem_export() { @@ -344,15 +345,15 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da if (id.is_valid()) { emit_signal(SNAME("remote_object_updated"), id); } - } else if (p_msg == "memory:usage") { + } else if (p_msg == "servers:memory_usage") { vmem_tree->clear(); TreeItem *root = vmem_tree->create_item(); - DebuggerMarshalls::ResourceUsage usage; + ServersDebugger::ResourceUsage usage; usage.deserialize(p_data); uint64_t total = 0; - for (const DebuggerMarshalls::ResourceInfo &E : usage.infos) { + for (const ServersDebugger::ResourceInfo &E : usage.infos) { TreeItem *it = vmem_tree->create_item(root); String type = E.type; int bytes = E.vram; @@ -445,7 +446,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da performance_profiler->add_profile_frame(frame_data); } else if (p_msg == "visual:profile_frame") { - DebuggerMarshalls::VisualProfilerFrame frame; + ServersDebugger::VisualProfilerFrame frame; frame.deserialize(p_data); EditorVisualProfiler::Metric metric; @@ -592,13 +593,13 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } else if (p_msg == "servers:function_signature") { // Cache a profiler signature. - DebuggerMarshalls::ScriptFunctionSignature sig; + ServersDebugger::ScriptFunctionSignature sig; sig.deserialize(p_data); profiler_signature[sig.id] = sig.name; } else if (p_msg == "servers:profile_frame" || p_msg == "servers:profile_total") { EditorProfiler::Metric metric; - DebuggerMarshalls::ServersProfilerFrame frame; + ServersDebugger::ServersProfilerFrame frame; frame.deserialize(p_data); metric.valid = true; metric.frame_number = frame.frame_number; @@ -642,7 +643,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da } for (int i = 0; i < frame.servers.size(); i++) { - const DebuggerMarshalls::ServerInfo &srv = frame.servers[i]; + const ServersDebugger::ServerInfo &srv = frame.servers[i]; EditorProfiler::Metric::Category c; const String name = srv.name; c.name = name.capitalize(); @@ -709,14 +710,14 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da profiler->add_frame_metric(metric, true); } - } else if (p_msg == "network:profile_frame") { - DebuggerMarshalls::NetworkProfilerFrame frame; + } else if (p_msg == "multiplayer:rpc") { + SceneDebugger::RPCProfilerFrame frame; frame.deserialize(p_data); for (int i = 0; i < frame.infos.size(); i++) { network_profiler->add_node_frame_data(frame.infos[i]); } - } else if (p_msg == "network:bandwidth") { + } else if (p_msg == "multiplayer:bandwidth") { ERR_FAIL_COND(p_data.size() < 2); network_profiler->set_bandwidth(p_data[0], p_data[1]); @@ -833,6 +834,9 @@ void ScriptEditorDebugger::_notification(int p_what) { msg.push_back(cam->get_far()); _put_msg("scene:override_camera_3D:transform", msg); } + if (breaked) { + _put_msg("servers:draw", Array()); + } } const uint64_t until = OS::get_singleton()->get_ticks_msec() + 20; @@ -971,7 +975,8 @@ void ScriptEditorDebugger::_profiler_activate(bool p_enable, int p_type) { data.push_back(p_enable); switch (p_type) { case PROFILER_NETWORK: - _put_msg("profiler:network", data); + _put_msg("profiler:multiplayer", data); + _put_msg("profiler:rpc", data); break; case PROFILER_VISUAL: _put_msg("profiler:visual", data); @@ -1543,19 +1548,10 @@ void ScriptEditorDebugger::_item_menu_id_pressed(int p_option) { const int line_number = file_line_number[1].to_int(); // Construct a GitHub repository URL and open it in the user's default web browser. - if (String(VERSION_HASH).length() >= 1) { - // Git commit hash information available; use it for greater accuracy, including for development versions. - OS::get_singleton()->shell_open(vformat("https://github.com/godotengine/godot/blob/%s/%s#L%d", - VERSION_HASH, - file, - line_number)); - } else { - // Git commit hash information unavailable; fall back to tagged releases. - OS::get_singleton()->shell_open(vformat("https://github.com/godotengine/godot/blob/%s-stable/%s#L%d", - VERSION_NUMBER, - file, - line_number)); - } + // If the commit hash is available, use it for greater accuracy. Otherwise fall back to tagged release. + String git_ref = String(VERSION_HASH).is_empty() ? String(VERSION_NUMBER) + "-stable" : String(VERSION_HASH); + OS::get_singleton()->shell_open(vformat("https://github.com/godotengine/godot/blob/%s/%s#L%d", + git_ref, file, line_number)); } break; case ACTION_DELETE_BREAKPOINT: { const TreeItem *selected = breakpoints_tree->get_selected(); diff --git a/editor/editor_about.cpp b/editor/editor_about.cpp index 54377971c6..4309f55a2b 100644 --- a/editor/editor_about.cpp +++ b/editor/editor_about.cpp @@ -29,13 +29,12 @@ /*************************************************************************/ #include "editor_about.h" -#include "editor_node.h" #include "core/authors.gen.h" #include "core/donors.gen.h" #include "core/license.gen.h" #include "core/version.h" -#include "core/version_hash.gen.h" +#include "editor_node.h" // The metadata key used to store and retrieve the version text to copy to the clipboard. static const String META_TEXT_TO_COPY = "text_to_copy"; diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index cbfd6ae6de..d94f1d80e5 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -1181,6 +1181,15 @@ void EditorInspectorSection::_notification(int p_what) { header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); int inspector_margin = get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); + int section_indent_size = get_theme_constant(SNAME("indent_size"), SNAME("EditorInspectorSection")); + if (indent_depth > 0 && section_indent_size > 0) { + inspector_margin += indent_depth * section_indent_size; + } + Ref<StyleBoxFlat> section_indent_style = get_theme_stylebox(SNAME("indent_box"), SNAME("EditorInspectorSection")); + if (indent_depth > 0 && section_indent_style.is_valid()) { + inspector_margin += section_indent_style->get_margin(SIDE_LEFT) + section_indent_style->get_margin(SIDE_RIGHT); + } + Size2 size = get_size() - Vector2(inspector_margin, 0); Vector2 offset = Vector2(is_layout_rtl() ? 0 : inspector_margin, header_height); for (int i = 0; i < get_child_count(); i++) { @@ -1216,14 +1225,31 @@ void EditorInspectorSection::_notification(int p_what) { bool rtl = is_layout_rtl(); - // Compute the height of the section header. + // Compute the height and width of the section header. int header_height = font->get_height(font_size); if (arrow.is_valid()) { header_height = MAX(header_height, arrow->get_height()); } header_height += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - Rect2 header_rect = Rect2(Vector2(), Vector2(get_size().width, header_height)); + int section_indent = 0; + int section_indent_size = get_theme_constant(SNAME("indent_size"), SNAME("EditorInspectorSection")); + if (indent_depth > 0 && section_indent_size > 0) { + section_indent = indent_depth * section_indent_size; + } + Ref<StyleBoxFlat> section_indent_style = get_theme_stylebox(SNAME("indent_box"), SNAME("EditorInspectorSection")); + if (indent_depth > 0 && section_indent_style.is_valid()) { + section_indent += section_indent_style->get_margin(SIDE_LEFT) + section_indent_style->get_margin(SIDE_RIGHT); + } + + int header_width = get_size().width - section_indent; + int header_offset_x = 0.0; + if (!rtl) { + header_offset_x += section_indent; + } + + // Draw header area. + Rect2 header_rect = Rect2(Vector2(header_offset_x, 0.0), Vector2(header_width, header_height)); Color c = bg_color; c.a *= 0.4; if (foldable && header_rect.has_point(get_local_mouse_position())) { @@ -1231,24 +1257,46 @@ void EditorInspectorSection::_notification(int p_what) { } draw_rect(header_rect, c); + // Draw header title and folding arrow. const int arrow_margin = 2; const int arrow_width = arrow.is_valid() ? arrow->get_width() : 0; Color color = get_theme_color(SNAME("font_color")); - float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE); - draw_string(font, Point2(rtl ? 0 : Math::round(arrow_width + arrow_margin * EDSCALE), font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2).floor(), label, rtl ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT, text_width, font_size, color); + float text_width = get_size().width - Math::round(arrow_width + arrow_margin * EDSCALE) - section_indent; + Point2 text_offset = Point2(0, font->get_ascent(font_size) + (header_height - font->get_height(font_size)) / 2); + HorizontalAlignment text_align = HORIZONTAL_ALIGNMENT_LEFT; + if (rtl) { + text_align = HORIZONTAL_ALIGNMENT_RIGHT; + } else { + text_offset.x = section_indent + Math::round(arrow_width + arrow_margin * EDSCALE); + } + draw_string(font, text_offset.floor(), label, text_align, text_width, font_size, color); if (arrow.is_valid()) { + Point2 arrow_position = Point2(0, (header_height - arrow->get_height()) / 2); if (rtl) { - draw_texture(arrow, Point2(get_size().width - arrow->get_width() - Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor()); + arrow_position.x = get_size().width - section_indent - arrow->get_width() - Math::round(arrow_margin * EDSCALE); } else { - draw_texture(arrow, Point2(Math::round(arrow_margin * EDSCALE), (header_height - arrow->get_height()) / 2).floor()); + arrow_position.x = section_indent + Math::round(arrow_margin * EDSCALE); } + draw_texture(arrow, arrow_position.floor()); } + // Draw dropping highlight. if (dropping && !vbox->is_visible_in_tree()) { Color accent_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); draw_rect(Rect2(Point2(), get_size()), accent_color, false); } + + // Draw section indentation. + if (section_indent_style.is_valid() && section_indent > 0) { + Rect2 indent_rect = Rect2(Vector2(), Vector2(indent_depth * section_indent_size, get_size().height)); + if (rtl) { + indent_rect.position.x = get_size().width - section_indent + section_indent_style->get_margin(SIDE_RIGHT); + } else { + indent_rect.position.x = section_indent_style->get_margin(SIDE_LEFT); + } + draw_style_box(section_indent_style, indent_rect); + } } break; case NOTIFICATION_DRAG_BEGIN: { Dictionary dd = get_viewport()->gui_get_drag_data(); @@ -1311,15 +1359,25 @@ Size2 EditorInspectorSection::get_minimum_size() const { ms.height += font->get_height(font_size) + get_theme_constant(SNAME("vseparation"), SNAME("Tree")); ms.width += get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); + int section_indent_size = get_theme_constant(SNAME("indent_size"), SNAME("EditorInspectorSection")); + if (indent_depth > 0 && section_indent_size > 0) { + ms.width += indent_depth * section_indent_size; + } + Ref<StyleBoxFlat> section_indent_style = get_theme_stylebox(SNAME("indent_box"), SNAME("EditorInspectorSection")); + if (indent_depth > 0 && section_indent_style.is_valid()) { + ms.width += section_indent_style->get_margin(SIDE_LEFT) + section_indent_style->get_margin(SIDE_RIGHT); + } + return ms; } -void EditorInspectorSection::setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable) { +void EditorInspectorSection::setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable, int p_indent_depth) { section = p_section; label = p_label; object = p_object; bg_color = p_bg_color; foldable = p_foldable; + indent_depth = p_indent_depth; if (!foldable && !vbox_added) { add_child(vbox); @@ -1401,12 +1459,8 @@ void EditorInspectorSection::_bind_methods() { } EditorInspectorSection::EditorInspectorSection() { - object = nullptr; - foldable = false; vbox = memnew(VBoxContainer); - vbox_added = false; - dropping = false; dropping_unfold_timer = memnew(Timer); dropping_unfold_timer->set_wait_time(0.6); dropping_unfold_timer->set_one_shot(true); @@ -1422,6 +1476,7 @@ EditorInspectorSection::~EditorInspectorSection() { //////////////////////////////////////////////// //////////////////////////////////////////////// + int EditorInspectorArray::_get_array_count() { if (mode == MODE_USE_MOVE_ARRAY_ELEMENT_FUNCTION) { List<PropertyInfo> object_property_list; @@ -1440,31 +1495,8 @@ void EditorInspectorArray::_add_button_pressed() { _move_element(-1, -1); } -void EditorInspectorArray::_first_page_button_pressed() { - emit_signal(SNAME("page_change_request"), 0); -} - -void EditorInspectorArray::_prev_page_button_pressed() { - emit_signal(SNAME("page_change_request"), MAX(0, page - 1)); -} - -void EditorInspectorArray::_page_line_edit_text_submitted(String p_text) { - if (p_text.is_valid_int()) { - int new_page = p_text.to_int() - 1; - new_page = MIN(MAX(0, new_page), max_page); - page_line_edit->set_text(Variant(new_page)); - emit_signal(SNAME("page_change_request"), new_page); - } else { - page_line_edit->set_text(Variant(page)); - } -} - -void EditorInspectorArray::_next_page_button_pressed() { - emit_signal(SNAME("page_change_request"), MIN(max_page, page + 1)); -} - -void EditorInspectorArray::_last_page_button_pressed() { - emit_signal(SNAME("page_change_request"), max_page); +void EditorInspectorArray::_paginator_page_changed(int p_page) { + emit_signal("page_change_request", p_page); } void EditorInspectorArray::_rmb_popup_id_pressed(int p_id) { @@ -1636,17 +1668,17 @@ void EditorInspectorArray::_move_element(int p_element_index, int p_to_pos) { // Handle page change and update counts. if (p_element_index < 0) { int added_index = p_to_pos < 0 ? count : p_to_pos; - emit_signal(SNAME("page_change_request"), added_index / page_lenght); + emit_signal(SNAME("page_change_request"), added_index / page_length); count += 1; } else if (p_to_pos < 0) { count -= 1; - if (page == max_page && (MAX(0, count - 1) / page_lenght != max_page)) { + if (page == max_page && (MAX(0, count - 1) / page_length != max_page)) { emit_signal(SNAME("page_change_request"), max_page - 1); } } - begin_array_index = page * page_lenght; - end_array_index = MIN(count, (page + 1) * page_lenght); - max_page = MAX(0, count - 1) / page_lenght; + begin_array_index = page * page_length; + end_array_index = MIN(count, (page + 1) * page_length); + max_page = MAX(0, count - 1) / page_length; } void EditorInspectorArray::_clear_array() { @@ -1860,9 +1892,9 @@ void EditorInspectorArray::_resize_dialog_confirmed() { void EditorInspectorArray::_setup() { // Setup counts. count = _get_array_count(); - begin_array_index = page * page_lenght; - end_array_index = MIN(count, (page + 1) * page_lenght); - max_page = MAX(0, count - 1) / page_lenght; + begin_array_index = page * page_length; + end_array_index = MIN(count, (page + 1) * page_length); + max_page = MAX(0, count - 1) / page_length; array_elements.resize(MAX(0, end_array_index - begin_array_index)); if (page < 0 || page > max_page) { WARN_PRINT(vformat("Invalid page number %d", page)); @@ -1920,18 +1952,12 @@ void EditorInspectorArray::_setup() { // Hide/show the add button. add_button->set_visible(page == max_page); - if (max_page == 0) { - hbox_pagination->hide(); - } else { - // Update buttons. - first_page_button->set_disabled(page == 0); - prev_page_button->set_disabled(page == 0); - next_page_button->set_disabled(page == max_page); - last_page_button->set_disabled(page == max_page); - - // Update page number and page count. - page_line_edit->set_text(vformat("%d", page + 1)); - page_count_label->set_text(vformat("/ %d", max_page + 1)); + // Add paginator if there's more than 1 page. + if (max_page > 0) { + EditorPaginator *paginator = memnew(EditorPaginator); + paginator->update(page, max_page); + paginator->connect("page_changed", callable_mp(this, &EditorInspectorArray::_paginator_page_changed)); + vbox->add_child(paginator); } } @@ -1996,10 +2022,6 @@ void EditorInspectorArray::_notification(int p_what) { } add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - first_page_button->set_icon(get_theme_icon(SNAME("PageFirst"), SNAME("EditorIcons"))); - prev_page_button->set_icon(get_theme_icon(SNAME("PagePrevious"), SNAME("EditorIcons"))); - next_page_button->set_icon(get_theme_icon(SNAME("PageNext"), SNAME("EditorIcons"))); - last_page_button->set_icon(get_theme_icon(SNAME("PageLast"), SNAME("EditorIcons"))); update_minimum_size(); } break; case NOTIFICATION_DRAG_BEGIN: { @@ -2036,7 +2058,7 @@ void EditorInspectorArray::setup_with_move_element_function(Object *p_object, St array_element_prefix = p_array_element_prefix; page = p_page; - EditorInspectorSection::setup(String(p_array_element_prefix) + "_array", p_label, p_object, p_bg_color, p_foldable); + EditorInspectorSection::setup(String(p_array_element_prefix) + "_array", p_label, p_object, p_bg_color, p_foldable, 0); _setup(); } @@ -2047,7 +2069,7 @@ void EditorInspectorArray::setup_with_count_property(Object *p_object, String p_ array_element_prefix = p_array_element_prefix; page = p_page; - EditorInspectorSection::setup(String(count_property) + "_array", p_label, p_object, p_bg_color, p_foldable); + EditorInspectorSection::setup(String(count_property) + "_array", p_label, p_object, p_bg_color, p_foldable, 0); _setup(); } @@ -2092,38 +2114,6 @@ EditorInspectorArray::EditorInspectorArray() { add_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_add_button_pressed)); vbox->add_child(add_button); - hbox_pagination = memnew(HBoxContainer); - hbox_pagination->set_h_size_flags(SIZE_EXPAND_FILL); - hbox_pagination->set_alignment(BoxContainer::ALIGNMENT_CENTER); - vbox->add_child(hbox_pagination); - - first_page_button = memnew(Button); - first_page_button->set_flat(true); - first_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_first_page_button_pressed)); - hbox_pagination->add_child(first_page_button); - - prev_page_button = memnew(Button); - prev_page_button->set_flat(true); - prev_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_prev_page_button_pressed)); - hbox_pagination->add_child(prev_page_button); - - page_line_edit = memnew(LineEdit); - page_line_edit->connect("text_submitted", callable_mp(this, &EditorInspectorArray::_page_line_edit_text_submitted)); - page_line_edit->add_theme_constant_override("minimum_character_width", 2); - hbox_pagination->add_child(page_line_edit); - - page_count_label = memnew(Label); - hbox_pagination->add_child(page_count_label); - next_page_button = memnew(Button); - next_page_button->set_flat(true); - next_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_next_page_button_pressed)); - hbox_pagination->add_child(next_page_button); - - last_page_button = memnew(Button); - last_page_button->set_flat(true); - last_page_button->connect("pressed", callable_mp(this, &EditorInspectorArray::_last_page_button_pressed)); - hbox_pagination->add_child(last_page_button); - control_dropping = memnew(Control); control_dropping->connect("draw", callable_mp(this, &EditorInspectorArray::_control_dropping_draw)); control_dropping->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); @@ -2149,6 +2139,97 @@ EditorInspectorArray::EditorInspectorArray() { //////////////////////////////////////////////// //////////////////////////////////////////////// +void EditorPaginator::_first_page_button_pressed() { + emit_signal("page_changed", 0); +} + +void EditorPaginator::_prev_page_button_pressed() { + emit_signal("page_changed", MAX(0, page - 1)); +} + +void EditorPaginator::_page_line_edit_text_submitted(String p_text) { + if (p_text.is_valid_int()) { + int new_page = p_text.to_int() - 1; + new_page = MIN(MAX(0, new_page), max_page); + page_line_edit->set_text(Variant(new_page)); + emit_signal("page_changed", new_page); + } else { + page_line_edit->set_text(Variant(page)); + } +} + +void EditorPaginator::_next_page_button_pressed() { + emit_signal("page_changed", MIN(max_page, page + 1)); +} + +void EditorPaginator::_last_page_button_pressed() { + emit_signal("page_changed", max_page); +} + +void EditorPaginator::update(int p_page, int p_max_page) { + page = p_page; + max_page = p_max_page; + + // Update buttons. + first_page_button->set_disabled(page == 0); + prev_page_button->set_disabled(page == 0); + next_page_button->set_disabled(page == max_page); + last_page_button->set_disabled(page == max_page); + + // Update page number and page count. + page_line_edit->set_text(vformat("%d", page + 1)); + page_count_label->set_text(vformat("/ %d", max_page + 1)); +} + +void EditorPaginator::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + first_page_button->set_icon(get_theme_icon(SNAME("PageFirst"), SNAME("EditorIcons"))); + prev_page_button->set_icon(get_theme_icon(SNAME("PagePrevious"), SNAME("EditorIcons"))); + next_page_button->set_icon(get_theme_icon(SNAME("PageNext"), SNAME("EditorIcons"))); + last_page_button->set_icon(get_theme_icon(SNAME("PageLast"), SNAME("EditorIcons"))); + } +} + +void EditorPaginator::_bind_methods() { + ADD_SIGNAL(MethodInfo("page_changed", PropertyInfo(Variant::INT, "page"))); +} + +EditorPaginator::EditorPaginator() { + set_h_size_flags(SIZE_EXPAND_FILL); + set_alignment(ALIGNMENT_CENTER); + + first_page_button = memnew(Button); + first_page_button->set_flat(true); + first_page_button->connect("pressed", callable_mp(this, &EditorPaginator::_first_page_button_pressed)); + add_child(first_page_button); + + prev_page_button = memnew(Button); + prev_page_button->set_flat(true); + prev_page_button->connect("pressed", callable_mp(this, &EditorPaginator::_prev_page_button_pressed)); + add_child(prev_page_button); + + page_line_edit = memnew(LineEdit); + page_line_edit->connect("text_submitted", callable_mp(this, &EditorPaginator::_page_line_edit_text_submitted)); + page_line_edit->add_theme_constant_override("minimum_character_width", 2); + add_child(page_line_edit); + + page_count_label = memnew(Label); + add_child(page_count_label); + + next_page_button = memnew(Button); + next_page_button->set_flat(true); + next_page_button->connect("pressed", callable_mp(this, &EditorPaginator::_next_page_button_pressed)); + add_child(next_page_button); + + last_page_button = memnew(Button); + last_page_button->set_flat(true); + last_page_button->connect("pressed", callable_mp(this, &EditorPaginator::_last_page_button_pressed)); + add_child(last_page_button); +} + +//////////////////////////////////////////////// +//////////////////////////////////////////////// + Ref<EditorInspectorPlugin> EditorInspector::inspector_plugins[MAX_PLUGINS]; int EditorInspector::inspector_plugin_count = 0; @@ -2349,6 +2430,7 @@ void EditorInspector::update_tree() { String group_base; String subgroup; String subgroup_base; + int section_depth = 0; VBoxContainer *category_vbox = nullptr; List<PropertyInfo> plist; @@ -2373,14 +2455,29 @@ void EditorInspector::update_tree() { if (p.usage & PROPERTY_USAGE_SUBGROUP) { // Setup a property sub-group. subgroup = p.name; - subgroup_base = p.hint_string; + + Vector<String> hint_parts = p.hint_string.split(","); + subgroup_base = hint_parts[0]; + if (hint_parts.size() > 1) { + section_depth = hint_parts[1].to_int(); + } else { + section_depth = 0; + } continue; } else if (p.usage & PROPERTY_USAGE_GROUP) { // Setup a property group. group = p.name; - group_base = p.hint_string; + + Vector<String> hint_parts = p.hint_string.split(","); + group_base = hint_parts[0]; + if (hint_parts.size() > 1) { + section_depth = hint_parts[1].to_int(); + } else { + section_depth = 0; + } + subgroup = ""; subgroup_base = ""; @@ -2392,6 +2489,7 @@ void EditorInspector::update_tree() { group_base = ""; subgroup = ""; subgroup_base = ""; + section_depth = 0; if (!show_categories) { continue; @@ -2633,7 +2731,7 @@ void EditorInspector::update_tree() { Color c = sscolor; c.a /= level; - section->setup(acc_path, component, object, c, use_folding); + section->setup(acc_path, component, object, c, use_folding, section_depth); // Add editors at the start of a group. for (Ref<EditorInspectorPlugin> &ped : valid_plugins) { diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 09b25065dc..43f71740e3 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -261,17 +261,18 @@ class EditorInspectorSection : public Container { String label; String section; - bool vbox_added; // Optimization. + bool vbox_added = false; // Optimization. Color bg_color; - bool foldable; + bool foldable = false; + int indent_depth = 0; Timer *dropping_unfold_timer; - bool dropping; + bool dropping = false; void _test_unfold(); protected: - Object *object; + Object *object = nullptr; VBoxContainer *vbox; void _notification(int p_what); @@ -281,7 +282,7 @@ protected: public: virtual Size2 get_minimum_size() const override; - void setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable); + void setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable, int p_indent_depth = 0); VBoxContainer *get_vbox(); void unfold(); void fold(); @@ -317,18 +318,11 @@ class EditorInspectorArray : public EditorInspectorSection { LineEdit *new_size_line_edit; // Pagination - int page_lenght = 5; + int page_length = 5; int page = 0; int max_page = 0; int begin_array_index = 0; int end_array_index = 0; - HBoxContainer *hbox_pagination; - Button *first_page_button; - Button *prev_page_button; - LineEdit *page_line_edit; - Label *page_count_label; - Button *next_page_button; - Button *last_page_button; enum MenuOptions { OPTION_MOVE_UP = 0, @@ -356,12 +350,7 @@ class EditorInspectorArray : public EditorInspectorSection { int _get_array_count(); void _add_button_pressed(); - - void _first_page_button_pressed(); - void _prev_page_button_pressed(); - void _page_line_edit_text_submitted(String p_text); - void _next_page_button_pressed(); - void _last_page_button_pressed(); + void _paginator_page_changed(int p_page); void _rmb_popup_id_pressed(int p_id); @@ -402,6 +391,34 @@ public: EditorInspectorArray(); }; +class EditorPaginator : public HBoxContainer { + GDCLASS(EditorPaginator, HBoxContainer); + + int page = 0; + int max_page = 0; + Button *first_page_button; + Button *prev_page_button; + LineEdit *page_line_edit; + Label *page_count_label; + Button *next_page_button; + Button *last_page_button; + + void _first_page_button_pressed(); + void _prev_page_button_pressed(); + void _page_line_edit_text_submitted(String p_text); + void _next_page_button_pressed(); + void _last_page_button_pressed(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void update(int p_page, int p_max_page); + + EditorPaginator(); +}; + class EditorInspector : public ScrollContainer { GDCLASS(EditorInspector, ScrollContainer); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 10ce7228e0..cf5af38a07 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -47,7 +47,6 @@ #include "core/string/print_string.h" #include "core/string/translation.h" #include "core/version.h" -#include "core/version_hash.gen.h" #include "main/main.h" #include "scene/3d/importer_mesh_instance_3d.h" #include "scene/gui/center_container.h" @@ -133,6 +132,7 @@ #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/collision_polygon_2d_editor_plugin.h" #include "editor/plugins/collision_shape_2d_editor_plugin.h" +#include "editor/plugins/control_editor_plugin.h" #include "editor/plugins/cpu_particles_2d_editor_plugin.h" #include "editor/plugins/cpu_particles_3d_editor_plugin.h" #include "editor/plugins/curve_editor_plugin.h" @@ -7025,6 +7025,7 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(InputEventEditorPlugin(this))); add_editor_plugin(memnew(SubViewportPreviewEditorPlugin(this))); add_editor_plugin(memnew(TextControlEditorPlugin(this))); + add_editor_plugin(memnew(ControlEditorPlugin(this))); for (int i = 0; i < EditorPlugins::get_plugin_count(); i++) { add_editor_plugin(EditorPlugins::create(i, this)); diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index c28c7db2cb..fc0c0e7d7b 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -259,8 +259,8 @@ void EditorPropertyArray::update_property() { } int size = array.call("size"); - int pages = MAX(0, size - 1) / page_length + 1; - page_index = MIN(page_index, pages - 1); + int max_page = MAX(0, size - 1) / page_length; + page_index = MIN(page_index, max_page); int offset = page_index * page_length; edit->set_text(arrtype + " (size " + itos(size) + ")"); @@ -292,35 +292,37 @@ void EditorPropertyArray::update_property() { size_slider->connect("value_changed", callable_mp(this, &EditorPropertyArray::_length_changed)); hbox->add_child(size_slider); - page_hbox = memnew(HBoxContainer); - vbox->add_child(page_hbox); + property_vbox = memnew(VBoxContainer); + property_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + vbox->add_child(property_vbox); - label = memnew(Label(TTR("Page: "))); - label->set_h_size_flags(SIZE_EXPAND_FILL); - page_hbox->add_child(label); + button_add_item = memnew(Button); + button_add_item->set_text(TTR("Add Element")); + button_add_item->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + button_add_item->connect(SNAME("pressed"), callable_mp(this, &EditorPropertyArray::_add_element)); + vbox->add_child(button_add_item); - page_slider = memnew(EditorSpinSlider); - page_slider->set_step(1); - page_slider->set_h_size_flags(SIZE_EXPAND_FILL); - page_slider->connect("value_changed", callable_mp(this, &EditorPropertyArray::_page_changed)); - page_hbox->add_child(page_slider); + paginator = memnew(EditorPaginator); + paginator->connect("page_changed", callable_mp(this, &EditorPropertyArray::_page_changed)); + vbox->add_child(paginator); } else { // Bye bye children of the box. - for (int i = vbox->get_child_count() - 1; i >= 2; i--) { - Node *child = vbox->get_child(i); + for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { + Node *child = property_vbox->get_child(i); if (child == reorder_selected_element_hbox) { continue; // Don't remove the property that the user is moving. } child->queue_delete(); // Button still needed after pressed is called. - vbox->remove_child(child); + property_vbox->remove_child(child); } } size_slider->set_value(size); - page_slider->set_max(pages); - page_slider->set_value(page_index); - page_hbox->set_visible(pages > 1); + property_vbox->set_visible(size > 0); + button_add_item->set_visible(page_index == max_page); + paginator->update(page_index, max_page); + paginator->set_visible(max_page > 0); if (array.get_type() == Variant::ARRAY) { array = array.call("duplicate"); @@ -343,7 +345,7 @@ void EditorPropertyArray::update_property() { } HBoxContainer *hbox = memnew(HBoxContainer); - vbox->add_child(hbox); + property_vbox->add_child(hbox); Button *reorder_button = memnew(Button); reorder_button->set_icon(get_theme_icon(SNAME("TripleBar"), SNAME("EditorIcons"))); @@ -397,7 +399,7 @@ void EditorPropertyArray::update_property() { } if (reorder_to_index % page_length > 0) { - vbox->move_child(vbox->get_child(2), reorder_to_index % page_length + 2); + property_vbox->move_child(property_vbox->get_child(0), reorder_to_index % page_length); } updating = false; @@ -510,6 +512,10 @@ void EditorPropertyArray::_notification(int p_what) { } change_type->add_separator(); change_type->add_icon_item(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), TTR("Remove Item"), Variant::VARIANT_MAX); + + if (Object::cast_to<Button>(button_add_item)) { + button_add_item->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + } } if (p_what == NOTIFICATION_DRAG_BEGIN) { @@ -542,7 +548,7 @@ void EditorPropertyArray::_edit_pressed() { update_property(); } -void EditorPropertyArray::_page_changed(double p_page) { +void EditorPropertyArray::_page_changed(int p_page) { if (updating) { return; } @@ -589,6 +595,10 @@ void EditorPropertyArray::_length_changed(double p_page) { update_property(); } +void EditorPropertyArray::_add_element() { + _length_changed(double(object->get_array().call("size")) + 1.0); +} + void EditorPropertyArray::setup(Variant::Type p_array_type, const String &p_hint_string) { array_type = p_array_type; @@ -633,9 +643,9 @@ void EditorPropertyArray::_reorder_button_gui_input(const Ref<InputEvent> &p_eve reorder_to_index += direction; if ((direction < 0 && reorder_to_index % page_length == page_length - 1) || (direction > 0 && reorder_to_index % page_length == 0)) { // Automatically move to the next/previous page. - page_slider->set_value(page_index + direction); + _page_changed(page_index + direction); } - vbox->move_child(reorder_selected_element_hbox, reorder_to_index % page_length + 2); + property_vbox->move_child(reorder_selected_element_hbox, reorder_to_index % page_length); // Ensure the moving element is visible. InspectorDock::get_inspector_singleton()->ensure_control_visible(reorder_selected_element_hbox); } @@ -645,7 +655,7 @@ void EditorPropertyArray::_reorder_button_gui_input(const Ref<InputEvent> &p_eve void EditorPropertyArray::_reorder_button_down(int p_index) { reorder_from_index = p_index; reorder_to_index = p_index; - reorder_selected_element_hbox = Object::cast_to<HBoxContainer>(vbox->get_child(p_index % page_length + 2)); + reorder_selected_element_hbox = Object::cast_to<HBoxContainer>(property_vbox->get_child(p_index % page_length)); reorder_selected_button = Object::cast_to<Button>(reorder_selected_element_hbox->get_child(0)); // Ideally it'd to be able to show the mouse but I had issues with // Control's `mouse_exit()`/`mouse_entered()` signals not getting called. @@ -685,6 +695,7 @@ void EditorPropertyArray::_bind_methods() { EditorPropertyArray::EditorPropertyArray() { object.instantiate(); page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page")); + edit = memnew(Button); edit->set_h_size_flags(SIZE_EXPAND_FILL); edit->set_clip_text(true); @@ -694,9 +705,12 @@ EditorPropertyArray::EditorPropertyArray() { edit->connect("draw", callable_mp(this, &EditorPropertyArray::_button_draw)); add_child(edit); add_focusable(edit); + vbox = nullptr; - page_slider = nullptr; + property_vbox = nullptr; size_slider = nullptr; + button_add_item = nullptr; + paginator = nullptr; updating = false; change_type = memnew(PopupMenu); add_child(change_type); @@ -824,35 +838,32 @@ void EditorPropertyDictionary::update_property() { add_child(vbox); set_bottom_editor(vbox); - page_hbox = memnew(HBoxContainer); - vbox->add_child(page_hbox); - Label *label = memnew(Label(TTR("Page: "))); - label->set_h_size_flags(SIZE_EXPAND_FILL); - page_hbox->add_child(label); - page_slider = memnew(EditorSpinSlider); - page_slider->set_step(1); - page_hbox->add_child(page_slider); - page_slider->set_h_size_flags(SIZE_EXPAND_FILL); - page_slider->connect("value_changed", callable_mp(this, &EditorPropertyDictionary::_page_changed)); + property_vbox = memnew(VBoxContainer); + property_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + vbox->add_child(property_vbox); + + paginator = memnew(EditorPaginator); + paginator->connect("page_changed", callable_mp(this, &EditorPropertyDictionary::_page_changed)); + vbox->add_child(paginator); } else { // Queue children for deletion, deleting immediately might cause errors. - for (int i = 1; i < vbox->get_child_count(); i++) { - vbox->get_child(i)->queue_delete(); + for (int i = property_vbox->get_child_count() - 1; i >= 0; i--) { + property_vbox->get_child(i)->queue_delete(); } } int size = dict.size(); - int pages = MAX(0, size - 1) / page_length + 1; + int max_page = MAX(0, size - 1) / page_length; + page_index = MIN(page_index, max_page); - page_slider->set_max(pages); - page_index = MIN(page_index, pages - 1); - page_slider->set_value(page_index); - page_hbox->set_visible(pages > 1); + paginator->update(page_index, max_page); + paginator->set_visible(max_page > 0); int offset = page_index * page_length; int amount = MIN(size - offset, page_length); + int total_amount = page_index == max_page ? amount + 2 : amount; // For the "Add Key/Value Pair" box on last page. dict = dict.duplicate(); @@ -860,7 +871,7 @@ void EditorPropertyDictionary::update_property() { VBoxContainer *add_vbox = nullptr; double default_float_step = EDITOR_GET("interface/inspector/default_float_step"); - for (int i = 0; i < amount + 2; i++) { + for (int i = 0; i < total_amount; i++) { String prop_name; Variant key; Variant value; @@ -1074,15 +1085,9 @@ void EditorPropertyDictionary::update_property() { if (i == amount) { PanelContainer *pc = memnew(PanelContainer); - vbox->add_child(pc); - Ref<StyleBoxFlat> flat; - flat.instantiate(); - for (int j = 0; j < 4; j++) { - flat->set_default_margin(Side(j), 2 * EDSCALE); - } - flat->set_bg_color(get_theme_color(SNAME("prop_subsection"), SNAME("Editor"))); + property_vbox->add_child(pc); + pc->add_theme_style_override(SNAME("panel"), get_theme_stylebox(SNAME("DictionaryAddItem"), SNAME("EditorStyles"))); - pc->add_theme_style_override("panel", flat); add_vbox = memnew(VBoxContainer); pc->add_child(add_vbox); } @@ -1110,7 +1115,7 @@ void EditorPropertyDictionary::update_property() { if (add_vbox) { add_vbox->add_child(hbox); } else { - vbox->add_child(hbox); + property_vbox->add_child(hbox); } hbox->add_child(prop); prop->set_h_size_flags(SIZE_EXPAND_FILL); @@ -1173,7 +1178,7 @@ void EditorPropertyDictionary::_edit_pressed() { update_property(); } -void EditorPropertyDictionary::_page_changed(double p_page) { +void EditorPropertyDictionary::_page_changed(int p_page) { if (updating) { return; } @@ -1187,6 +1192,7 @@ void EditorPropertyDictionary::_bind_methods() { EditorPropertyDictionary::EditorPropertyDictionary() { object.instantiate(); page_length = int(EDITOR_GET("interface/inspector/max_array_dictionary_items_per_page")); + edit = memnew(Button); edit->set_h_size_flags(SIZE_EXPAND_FILL); edit->set_clip_text(true); @@ -1194,9 +1200,10 @@ EditorPropertyDictionary::EditorPropertyDictionary() { edit->set_toggle_mode(true); add_child(edit); add_focusable(edit); + vbox = nullptr; - page_slider = nullptr; button_add_item = nullptr; + paginator = nullptr; updating = false; change_type = memnew(PopupMenu); add_child(change_type); diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h index bb10cdf97e..292de6d6db 100644 --- a/editor/editor_properties_array_dict.h +++ b/editor/editor_properties_array_dict.h @@ -89,9 +89,10 @@ class EditorPropertyArray : public EditorProperty { int changing_type_index; Button *edit; VBoxContainer *vbox; + VBoxContainer *property_vbox; EditorSpinSlider *size_slider; - EditorSpinSlider *page_slider; - HBoxContainer *page_hbox; + Button *button_add_item; + EditorPaginator *paginator; Variant::Type array_type; Variant::Type subtype; PropertyHint subtype_hint; @@ -103,8 +104,9 @@ class EditorPropertyArray : public EditorProperty { HBoxContainer *reorder_selected_element_hbox = nullptr; Button *reorder_selected_button = nullptr; - void _page_changed(double p_page); + void _page_changed(int p_page); void _length_changed(double p_page); + void _add_element(); void _edit_pressed(); void _property_changed(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false); void _change_type(Object *p_button, int p_index); @@ -144,12 +146,12 @@ class EditorPropertyDictionary : public EditorProperty { int changing_type_index; Button *edit; VBoxContainer *vbox; + VBoxContainer *property_vbox; EditorSpinSlider *size_slider; - EditorSpinSlider *page_slider; - HBoxContainer *page_hbox; Button *button_add_item; + EditorPaginator *paginator; - void _page_changed(double p_page); + void _page_changed(int p_page); void _edit_pressed(); void _property_changed(const String &p_property, Variant p_value, const String &p_name = "", bool p_changing = false); void _change_type(Object *p_button, int p_index); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 1e373239a6..6d3dcd6bc9 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -159,7 +159,7 @@ void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme = ADD_CONVERT_COLOR(dark_icon_color_dictionary, "#ffffff", "#414141"); // Pure white ADD_CONVERT_COLOR(dark_icon_color_dictionary, "#000000", "#bfbfbf"); // Pure black - // Keep pure RGB colors as is, but list them for explicity. + // Keep pure RGB colors as is, but list them for explicitly. ADD_CONVERT_COLOR(dark_icon_color_dictionary, "#ff0000", "#ff0000"); // Pure red ADD_CONVERT_COLOR(dark_icon_color_dictionary, "#00ff00", "#00ff00"); // Pure green ADD_CONVERT_COLOR(dark_icon_color_dictionary, "#0000ff", "#0000ff"); // Pure blue @@ -466,6 +466,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("font_color", "Editor", font_color); theme->set_color("highlighted_font_color", "Editor", font_hover_color); theme->set_color("disabled_font_color", "Editor", font_disabled_color); + theme->set_color("readonly_font_color", "Editor", font_readonly_color); theme->set_color("mono_color", "Editor", mono_color); @@ -875,9 +876,21 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("readonly_color", "EditorProperty", readonly_color); theme->set_color("readonly_warning_color", "EditorProperty", readonly_warning_color); + Ref<StyleBoxFlat> style_property_group_note = style_default->duplicate(); + Color property_group_note_color = accent_color; + property_group_note_color.a = 0.1; + style_property_group_note->set_bg_color(property_group_note_color); + theme->set_stylebox("bg_group_note", "EditorProperty", style_property_group_note); + Color inspector_section_color = font_color.lerp(Color(0.5, 0.5, 0.5), 0.35); theme->set_color("font_color", "EditorInspectorSection", inspector_section_color); + Color inspector_indent_color = accent_color; + inspector_indent_color.a = 0.2; + Ref<StyleBoxFlat> inspector_indent_style = make_flat_stylebox(inspector_indent_color, 2.0 * EDSCALE, 0, 2.0 * EDSCALE, 0); + theme->set_stylebox("indent_box", "EditorInspectorSection", inspector_indent_style); + theme->set_constant("indent_size", "EditorInspectorSection", 6.0 * EDSCALE); + theme->set_constant("inspector_margin", "Editor", 12 * EDSCALE); // Tree & ItemList background @@ -1501,6 +1514,9 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { Ref<StyleBoxFlat> theme_preview_picker_label_sb = make_flat_stylebox(theme_preview_picker_label_bg_color, 4.0, 1.0, 4.0, 3.0); theme->set_stylebox("preview_picker_label", "ThemeEditor", theme_preview_picker_label_sb); + // Dictionary editor add item. + theme->set_stylebox("DictionaryAddItem", "EditorStyles", make_flat_stylebox(prop_subsection_color, 4, 4, 4, 4, corner_radius)); + // adaptive script theme constants // for comments and elements with lower relevance const Color dim_color = Color(font_color.r, font_color.g, font_color.b, 0.5); diff --git a/editor/icons/ControlAlignBottomCenter.svg b/editor/icons/ControlAlignCenterBottom.svg index ca7f0c2e01..ca7f0c2e01 100644 --- a/editor/icons/ControlAlignBottomCenter.svg +++ b/editor/icons/ControlAlignCenterBottom.svg diff --git a/editor/icons/ControlAlignLeftCenter.svg b/editor/icons/ControlAlignCenterLeft.svg index 612c36b4d6..612c36b4d6 100644 --- a/editor/icons/ControlAlignLeftCenter.svg +++ b/editor/icons/ControlAlignCenterLeft.svg diff --git a/editor/icons/ControlAlignRightCenter.svg b/editor/icons/ControlAlignCenterRight.svg index 43f8618c80..43f8618c80 100644 --- a/editor/icons/ControlAlignRightCenter.svg +++ b/editor/icons/ControlAlignCenterRight.svg diff --git a/editor/icons/ControlAlignTopCenter.svg b/editor/icons/ControlAlignCenterTop.svg index dca9c84ce6..dca9c84ce6 100644 --- a/editor/icons/ControlAlignTopCenter.svg +++ b/editor/icons/ControlAlignCenterTop.svg diff --git a/editor/icons/ControlHcenterWide.svg b/editor/icons/ControlAlignHCenterWide.svg index af3f9b495b..af3f9b495b 100644 --- a/editor/icons/ControlHcenterWide.svg +++ b/editor/icons/ControlAlignHCenterWide.svg diff --git a/editor/icons/ControlVcenterWide.svg b/editor/icons/ControlAlignVCenterWide.svg index decd1cbd12..decd1cbd12 100644 --- a/editor/icons/ControlVcenterWide.svg +++ b/editor/icons/ControlAlignVCenterWide.svg diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index a6fc286f35..a3ae0d9623 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -747,11 +747,11 @@ bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_po return still_selected; } -List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retreive_locked, bool remove_canvas_item_if_parent_in_selection) { +List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retrieve_locked, bool remove_canvas_item_if_parent_in_selection) { List<CanvasItem *> selection; for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); - if (canvas_item && canvas_item->is_visible_in_tree() && canvas_item->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retreive_locked || !_is_node_locked(canvas_item))) { + if (canvas_item && canvas_item->is_visible_in_tree() && canvas_item->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retrieve_locked || !_is_node_locked(canvas_item))) { CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); if (se) { selection.push_back(canvas_item); @@ -3696,8 +3696,6 @@ void CanvasItemEditor::_notification(int p_what) { if (p_what == NOTIFICATION_PHYSICS_PROCESS) { EditorNode::get_singleton()->get_scene_root()->set_snap_controls_to_pixels(GLOBAL_GET("gui/common/snap_controls_to_pixels")); - bool has_container_parents = false; - int nb_control = 0; int nb_having_pivot = 0; // Update the viewport if the canvas_item changes @@ -3738,11 +3736,6 @@ void CanvasItemEditor::_notification(int p_what) { se->prev_anchors[SIDE_BOTTOM] = anchors[SIDE_BOTTOM]; viewport->update(); } - nb_control++; - - if (Object::cast_to<Container>(control->get_parent())) { - has_container_parents = true; - } } if (canvas_item->_edit_use_pivot()) { @@ -3753,28 +3746,6 @@ void CanvasItemEditor::_notification(int p_what) { // Activate / Deactivate the pivot tool pivot_button->set_disabled(nb_having_pivot == 0); - // Show / Hide the layout and anchors mode buttons - if (nb_control > 0 && nb_control == selection.size()) { - presets_menu->set_visible(true); - anchor_mode_button->set_visible(true); - - // Disable if the selected node is child of a container - if (has_container_parents) { - presets_menu->set_disabled(true); - presets_menu->set_tooltip(TTR("Children of containers have their anchors and margins values overridden by their parent.")); - anchor_mode_button->set_disabled(true); - anchor_mode_button->set_tooltip(TTR("Children of containers have their anchors and margins values overridden by their parent.")); - } else { - presets_menu->set_disabled(false); - presets_menu->set_tooltip(TTR("Presets for the anchors and margins values of a Control node.")); - anchor_mode_button->set_disabled(false); - anchor_mode_button->set_tooltip(TTR("When active, moving Control nodes changes their anchors instead of their margins.")); - } - } else { - presets_menu->set_visible(false); - anchor_mode_button->set_visible(false); - } - // Update the viewport if bones changes for (KeyValue<BoneKey, BoneList> &E : bone_list) { Object *b = ObjectDB::get_instance(E.key.from); @@ -3852,58 +3823,6 @@ void CanvasItemEditor::_notification(int p_what) { _update_context_menu_stylebox(); - presets_menu->set_icon(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); - - PopupMenu *p = presets_menu->get_popup(); - - p->clear(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftCenter"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopCenter"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightCenter"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomCenter"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_AND_OFFSETS_PRESET_CENTER); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlVcenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlHcenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_AND_OFFSETS_PRESET_WIDE); - p->add_icon_item(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")), TTR("Keep Ratio"), ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO); - p->add_separator(); - p->add_submenu_item(TTR("Anchors only"), "Anchors"); - p->set_item_icon(21, get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); - - anchors_popup->clear(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_PRESET_TOP_LEFT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_PRESET_TOP_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_PRESET_BOTTOM_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_PRESET_BOTTOM_LEFT); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftCenter"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_PRESET_CENTER_LEFT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopCenter"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_PRESET_CENTER_TOP); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightCenter"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_PRESET_CENTER_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomCenter"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_PRESET_CENTER_BOTTOM); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_PRESET_CENTER); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_PRESET_LEFT_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_PRESET_TOP_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_PRESET_RIGHT_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_PRESET_BOTTOM_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlVcenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_PRESET_VCENTER_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlHcenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_PRESET_HCENTER_WIDE); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_WIDE); - - anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); - panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/2d_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); pan_speed = int(EditorSettings::get_singleton()->get("editors/panning/2d_editor_pan_speed")); warped_panning = bool(EditorSettings::get_singleton()->get("editors/panning/warped_mouse_panning")); @@ -3920,27 +3839,6 @@ void CanvasItemEditor::_notification(int p_what) { } void CanvasItemEditor::_selection_changed() { - // Update the anchors_mode - int nbValidControls = 0; - int nbAnchorsMode = 0; - List<Node *> selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (!control) { - continue; - } - if (Object::cast_to<Container>(control->get_parent())) { - continue; - } - - nbValidControls++; - if (control->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_")) { - nbAnchorsMode++; - } - } - anchors_mode = (nbValidControls == nbAnchorsMode); - anchor_mode_button->set_pressed(anchors_mode); - if (!selected_from_canvas) { drag_type = DRAG_NONE; } @@ -4072,90 +3970,6 @@ void CanvasItemEditor::_update_scroll(real_t) { viewport->update(); } -void CanvasItemEditor::_set_anchors_and_offsets_preset(Control::LayoutPreset p_preset) { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Anchors and Offsets")); - - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - undo_redo->add_do_method(control, "set_anchors_preset", p_preset); - switch (p_preset) { - case PRESET_TOP_LEFT: - case PRESET_TOP_RIGHT: - case PRESET_BOTTOM_LEFT: - case PRESET_BOTTOM_RIGHT: - case PRESET_CENTER_LEFT: - case PRESET_CENTER_TOP: - case PRESET_CENTER_RIGHT: - case PRESET_CENTER_BOTTOM: - case PRESET_CENTER: - undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_KEEP_SIZE); - break; - case PRESET_LEFT_WIDE: - case PRESET_TOP_WIDE: - case PRESET_RIGHT_WIDE: - case PRESET_BOTTOM_WIDE: - case PRESET_VCENTER_WIDE: - case PRESET_HCENTER_WIDE: - case PRESET_WIDE: - undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_MINSIZE); - break; - } - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - } - } - - undo_redo->commit_action(); - - anchors_mode = false; - anchor_mode_button->set_pressed(anchors_mode); -} - -void CanvasItemEditor::_set_anchors_and_offsets_to_keep_ratio() { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Anchors and Offsets")); - - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - Point2 top_left_anchor = _position_to_anchor(control, Point2()); - Point2 bottom_right_anchor = _position_to_anchor(control, control->get_size()); - undo_redo->add_do_method(control, "set_anchor", SIDE_LEFT, top_left_anchor.x, false, true); - undo_redo->add_do_method(control, "set_anchor", SIDE_RIGHT, bottom_right_anchor.x, false, true); - undo_redo->add_do_method(control, "set_anchor", SIDE_TOP, top_left_anchor.y, false, true); - undo_redo->add_do_method(control, "set_anchor", SIDE_BOTTOM, bottom_right_anchor.y, false, true); - undo_redo->add_do_method(control, "set_meta", "_edit_use_anchors_", true); - - bool use_anchors = control->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_"); - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - undo_redo->add_undo_method(control, "set_meta", "_edit_use_anchors_", use_anchors); - - anchors_mode = true; - anchor_mode_button->set_pressed(anchors_mode); - } - } - - undo_redo->commit_action(); -} - -void CanvasItemEditor::_set_anchors_preset(Control::LayoutPreset p_preset) { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Anchors")); - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - undo_redo->add_do_method(control, "set_anchors_preset", p_preset); - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - } - } - - undo_redo->commit_action(); -} - void CanvasItemEditor::_zoom_on_position(real_t p_zoom, Point2 p_position) { p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM); @@ -4298,21 +4112,6 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, } } -void CanvasItemEditor::_button_toggle_anchor_mode(bool p_status) { - List<CanvasItem *> selection = _get_edited_canvas_items(false, false); - for (CanvasItem *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (!control || Object::cast_to<Container>(control->get_parent())) { - continue; - } - - control->set_meta("_edit_use_anchors_", p_status); - } - - anchors_mode = p_status; - viewport->update(); -} - void CanvasItemEditor::_update_override_camera_button(bool p_game_running) { if (p_game_running) { override_camera_button->set_disabled(false); @@ -4534,106 +4333,6 @@ void CanvasItemEditor::_popup_callback(int p_op) { undo_redo->add_undo_method(viewport, "update", Variant()); undo_redo->commit_action(); } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT: { - _set_anchors_and_offsets_preset(PRESET_TOP_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_TOP_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT: { - _set_anchors_and_offsets_preset(PRESET_CENTER_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_CENTER_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP: { - _set_anchors_and_offsets_preset(PRESET_CENTER_TOP); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM: { - _set_anchors_and_offsets_preset(PRESET_CENTER_BOTTOM); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER: { - _set_anchors_and_offsets_preset(PRESET_CENTER); - } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE: { - _set_anchors_and_offsets_preset(PRESET_TOP_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE: { - _set_anchors_and_offsets_preset(PRESET_LEFT_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE: { - _set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE: { - _set_anchors_and_offsets_preset(PRESET_VCENTER_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE: { - _set_anchors_and_offsets_preset(PRESET_HCENTER_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_WIDE: { - _set_anchors_and_offsets_preset(Control::PRESET_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO: { - _set_anchors_and_offsets_to_keep_ratio(); - } break; - - case ANCHORS_PRESET_TOP_LEFT: { - _set_anchors_preset(PRESET_TOP_LEFT); - } break; - case ANCHORS_PRESET_TOP_RIGHT: { - _set_anchors_preset(PRESET_TOP_RIGHT); - } break; - case ANCHORS_PRESET_BOTTOM_LEFT: { - _set_anchors_preset(PRESET_BOTTOM_LEFT); - } break; - case ANCHORS_PRESET_BOTTOM_RIGHT: { - _set_anchors_preset(PRESET_BOTTOM_RIGHT); - } break; - case ANCHORS_PRESET_CENTER_LEFT: { - _set_anchors_preset(PRESET_CENTER_LEFT); - } break; - case ANCHORS_PRESET_CENTER_RIGHT: { - _set_anchors_preset(PRESET_CENTER_RIGHT); - } break; - case ANCHORS_PRESET_CENTER_TOP: { - _set_anchors_preset(PRESET_CENTER_TOP); - } break; - case ANCHORS_PRESET_CENTER_BOTTOM: { - _set_anchors_preset(PRESET_CENTER_BOTTOM); - } break; - case ANCHORS_PRESET_CENTER: { - _set_anchors_preset(PRESET_CENTER); - } break; - case ANCHORS_PRESET_TOP_WIDE: { - _set_anchors_preset(PRESET_TOP_WIDE); - } break; - case ANCHORS_PRESET_LEFT_WIDE: { - _set_anchors_preset(PRESET_LEFT_WIDE); - } break; - case ANCHORS_PRESET_RIGHT_WIDE: { - _set_anchors_preset(PRESET_RIGHT_WIDE); - } break; - case ANCHORS_PRESET_BOTTOM_WIDE: { - _set_anchors_preset(PRESET_BOTTOM_WIDE); - } break; - case ANCHORS_PRESET_VCENTER_WIDE: { - _set_anchors_preset(PRESET_VCENTER_WIDE); - } break; - case ANCHORS_PRESET_HCENTER_WIDE: { - _set_anchors_preset(PRESET_HCENTER_WIDE); - } break; - case ANCHORS_PRESET_WIDE: { - _set_anchors_preset(Control::PRESET_WIDE); - } break; case ANIM_INSERT_KEY: case ANIM_INSERT_KEY_EXISTING: { @@ -5128,63 +4827,21 @@ void CanvasItemEditor::focus_selection() { } CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { - key_pos = true; - key_rot = true; - key_scale = false; - - show_grid = false; - show_origin = true; - show_viewport = true; - show_helpers = false; - show_rulers = true; - show_guides = true; - show_transformation_gizmos = true; - show_edit_locks = true; zoom = 1.0 / MAX(1, EDSCALE); view_offset = Point2(-150 - RULER_WIDTH, -95 - RULER_WIDTH); previous_update_view_offset = view_offset; // Moves the view a little bit to the left so that (0,0) is visible. The values a relative to a 16/10 screen + grid_offset = Point2(); grid_step = Point2(8, 8); // A power-of-two value works better as a default primary_grid_steps = 8; // A power-of-two value works better as a default grid_step_multiplier = 0; + snap_rotation_offset = 0; snap_rotation_step = Math::deg2rad(15.0); snap_scale_step = 0.1f; - smart_snap_active = false; - grid_snap_active = false; - snap_node_parent = true; - snap_node_anchors = true; - snap_node_sides = true; - snap_node_center = true; - snap_other_nodes = true; - snap_guides = true; - snap_rotation = false; - snap_scale = false; - snap_relative = false; - // Enable pixel snapping even if pixel snap rendering is disabled in the Project Settings. - // This results in crisper visuals by preventing 2D nodes from being placed at subpixel coordinates. - snap_pixel = true; snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; - selected_from_canvas = false; - anchors_mode = false; - - drag_type = DRAG_NONE; - drag_from = Vector2(); - drag_to = Vector2(); - dragged_guide_pos = Point2(); - dragged_guide_index = -1; - is_hovering_h_guide = false; - is_hovering_v_guide = false; - pan_pressed = false; - - ruler_tool_active = false; - ruler_tool_origin = Point2(); - - bone_last_frame = 0; - - tool = TOOL_SELECT; undo_redo = p_editor->get_undo_redo(); editor = p_editor; editor_selection = p_editor->get_editor_selection(); @@ -5261,8 +4918,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { viewport->add_child(controls_vb); - updating_scroll = false; - // Add some margin to the left for better aesthetics. // This prevents the first button's hover/pressed effect from "touching" the panel's border, // which looks ugly. @@ -5492,28 +5147,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { hb->add_child(context_menu_container); _update_context_menu_stylebox(); - presets_menu = memnew(MenuButton); - presets_menu->set_shortcut_context(this); - presets_menu->set_text(TTR("Layout")); - hbc_context_menu->add_child(presets_menu); - presets_menu->hide(); - presets_menu->set_switch_on_hover(true); - - p = presets_menu->get_popup(); - p->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); - - anchors_popup = memnew(PopupMenu); - p->add_child(anchors_popup); - anchors_popup->set_name("Anchors"); - anchors_popup->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); - - anchor_mode_button = memnew(Button); - anchor_mode_button->set_flat(true); - hbc_context_menu->add_child(anchor_mode_button); - anchor_mode_button->set_toggle_mode(true); - anchor_mode_button->hide(); - anchor_mode_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_anchor_mode)); - + // Animation controls. animation_hb = memnew(HBoxContainer); hbc_context_menu->add_child(animation_hb); animation_hb->add_child(memnew(VSeparator)); @@ -5599,6 +5233,8 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { divide_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/divide_grid_step", TTR("Divide grid step by 2"), Key::KP_DIVIDE); skeleton_menu->get_popup()->set_item_checked(skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES), true); + + // Store the singleton instance. singleton = this; // To ensure that scripts can parse the list of shortcuts correctly, we have to define @@ -5955,8 +5591,6 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian continue; } memdelete(instantiated_scene); - } else { - continue; } can_instantiate = true; break; diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index 9fa44bfb25..73829b8abe 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef CONTROL_EDITOR_PLUGIN_H -#define CONTROL_EDITOR_PLUGIN_H +#ifndef CANVAS_ITEM_EDITOR_PLUGIN_H +#define CANVAS_ITEM_EDITOR_PLUGIN_H #include "editor/editor_node.h" #include "editor/editor_plugin.h" @@ -39,6 +39,7 @@ #include "scene/gui/label.h" #include "scene/gui/panel_container.h" #include "scene/gui/spin_box.h" +#include "scene/gui/texture_rect.h" #include "scene/main/canvas_item.h" class CanvasItemEditorViewport; @@ -128,55 +129,6 @@ private: UNLOCK_SELECTED, GROUP_SELECTED, UNGROUP_SELECTED, - ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT, - ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP, - ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM, - ANCHORS_AND_OFFSETS_PRESET_CENTER, - ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE, - ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE, - ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE, - ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE, - ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE, - ANCHORS_AND_OFFSETS_PRESET_WIDE, - ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO, - ANCHORS_PRESET_TOP_LEFT, - ANCHORS_PRESET_TOP_RIGHT, - ANCHORS_PRESET_BOTTOM_LEFT, - ANCHORS_PRESET_BOTTOM_RIGHT, - ANCHORS_PRESET_CENTER_LEFT, - ANCHORS_PRESET_CENTER_RIGHT, - ANCHORS_PRESET_CENTER_TOP, - ANCHORS_PRESET_CENTER_BOTTOM, - ANCHORS_PRESET_CENTER, - ANCHORS_PRESET_TOP_WIDE, - ANCHORS_PRESET_LEFT_WIDE, - ANCHORS_PRESET_RIGHT_WIDE, - ANCHORS_PRESET_BOTTOM_WIDE, - ANCHORS_PRESET_VCENTER_WIDE, - ANCHORS_PRESET_HCENTER_WIDE, - ANCHORS_PRESET_WIDE, - OFFSETS_PRESET_TOP_LEFT, - OFFSETS_PRESET_TOP_RIGHT, - OFFSETS_PRESET_BOTTOM_LEFT, - OFFSETS_PRESET_BOTTOM_RIGHT, - OFFSETS_PRESET_CENTER_LEFT, - OFFSETS_PRESET_CENTER_RIGHT, - OFFSETS_PRESET_CENTER_TOP, - OFFSETS_PRESET_CENTER_BOTTOM, - OFFSETS_PRESET_CENTER, - OFFSETS_PRESET_TOP_WIDE, - OFFSETS_PRESET_LEFT_WIDE, - OFFSETS_PRESET_RIGHT_WIDE, - OFFSETS_PRESET_BOTTOM_WIDE, - OFFSETS_PRESET_VCENTER_WIDE, - OFFSETS_PRESET_HCENTER_WIDE, - OFFSETS_PRESET_WIDE, ANIM_INSERT_KEY, ANIM_INSERT_KEY_EXISTING, ANIM_INSERT_POS, @@ -226,7 +178,7 @@ private: bool selection_menu_additive_selection; - Tool tool; + Tool tool = TOOL_SELECT; Control *viewport; Control *viewport_scrollable; @@ -239,21 +191,20 @@ private: HBoxContainer *hbc_context_menu; Transform2D transform; - bool show_grid; - bool show_rulers; - bool show_guides; - bool show_origin; - bool show_viewport; - bool show_helpers; - bool show_edit_locks; - bool show_transformation_gizmos; + bool show_grid = false; + bool show_rulers = true; + bool show_guides = true; + bool show_origin = true; + bool show_viewport = true; + bool show_helpers = false; + bool show_edit_locks = true; + bool show_transformation_gizmos = true; real_t zoom; Point2 view_offset; Point2 previous_update_view_offset; - bool selected_from_canvas; - bool anchors_mode; + bool selected_from_canvas = false; Point2 grid_offset; Point2 grid_step; @@ -263,26 +214,30 @@ private: real_t snap_rotation_step; real_t snap_rotation_offset; real_t snap_scale_step; - bool smart_snap_active; - bool grid_snap_active; - - bool snap_node_parent; - bool snap_node_anchors; - bool snap_node_sides; - bool snap_node_center; - bool snap_other_nodes; - bool snap_guides; - bool snap_rotation; - bool snap_scale; - bool snap_relative; - bool snap_pixel; - bool key_pos; - bool key_rot; - bool key_scale; - bool pan_pressed; - - bool ruler_tool_active; - Point2 ruler_tool_origin; + bool smart_snap_active = false; + bool grid_snap_active = false; + + bool snap_node_parent = true; + bool snap_node_anchors = true; + bool snap_node_sides = true; + bool snap_node_center = true; + bool snap_other_nodes = true; + bool snap_guides = true; + bool snap_rotation = false; + bool snap_scale = false; + bool snap_relative = false; + // Enable pixel snapping even if pixel snap rendering is disabled in the Project Settings. + // This results in crisper visuals by preventing 2D nodes from being placed at subpixel coordinates. + bool snap_pixel = true; + + bool key_pos = true; + bool key_rot = true; + bool key_scale = false; + + bool pan_pressed = false; + + bool ruler_tool_active = false; + Point2 ruler_tool_origin = Point2(); Point2 node_create_position; MenuOption last_option; @@ -310,7 +265,7 @@ private: uint64_t last_pass = 0; }; - uint64_t bone_last_frame; + uint64_t bone_last_frame = 0; struct BoneKey { ObjectID from; @@ -363,12 +318,6 @@ private: HBoxContainer *animation_hb; MenuButton *animation_menu; - MenuButton *presets_menu; - PopupMenu *anchors_and_margins_popup; - PopupMenu *anchors_popup; - - Button *anchor_mode_button; - Button *key_loc_button; Button *key_rot_button; Button *key_scale_button; @@ -382,15 +331,15 @@ private: Control *left_ruler; Point2 drag_start_origin; - DragType drag_type; - Point2 drag_from; - Point2 drag_to; + DragType drag_type = DRAG_NONE; + Point2 drag_from = Vector2(); + Point2 drag_to = Vector2(); Point2 drag_rotation_center; List<CanvasItem *> drag_selection; - int dragged_guide_index; - Point2 dragged_guide_pos; - bool is_hovering_h_guide; - bool is_hovering_v_guide; + int dragged_guide_index = -1; + Point2 dragged_guide_pos = Point2(); + bool is_hovering_h_guide = false; + bool is_hovering_v_guide = false; bool updating_value_dialog; @@ -432,7 +381,7 @@ private: Vector2 _position_to_anchor(const Control *p_control, Vector2 position); void _popup_callback(int p_op); - bool updating_scroll; + bool updating_scroll = false; void _update_scroll(real_t); void _update_scrollbars(); void _snap_changed(); @@ -444,7 +393,7 @@ private: UndoRedo *undo_redo; - List<CanvasItem *> _get_edited_canvas_items(bool retreive_locked = false, bool remove_canvas_item_if_parent_in_selection = true); + List<CanvasItem *> _get_edited_canvas_items(bool retrieve_locked = false, bool remove_canvas_item_if_parent_in_selection = true); Rect2 _get_encompassing_rect_from_list(List<CanvasItem *> p_list); void _expand_encompassing_rect_using_children(Rect2 &r_rect, const Node *p_node, bool &r_first, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D(), bool include_locked_nodes = true); Rect2 _get_encompassing_rect(const Node *p_node); @@ -518,12 +467,6 @@ private: const SnapTarget p_snap_target, List<const CanvasItem *> p_exceptions, const Node *p_current); - void _set_anchors_preset(Control::LayoutPreset p_preset); - void _set_anchors_and_offsets_preset(Control::LayoutPreset p_preset); - void _set_anchors_and_offsets_to_keep_ratio(); - - void _button_toggle_anchor_mode(bool p_status); - VBoxContainer *controls_vb; EditorZoomWidget *zoom_widget; void _update_zoom(real_t p_zoom); @@ -602,8 +545,6 @@ public: void focus_selection(); - bool is_anchors_mode_enabled() { return anchors_mode; }; - EditorSelection *editor_selection; CanvasItemEditor(EditorNode *p_editor); @@ -683,4 +624,4 @@ public: ~CanvasItemEditorViewport(); }; -#endif +#endif //CANVAS_ITEM_EDITOR_PLUGIN_H diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp new file mode 100644 index 0000000000..c4ea098e92 --- /dev/null +++ b/editor/plugins/control_editor_plugin.cpp @@ -0,0 +1,1023 @@ +/*************************************************************************/ +/* control_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "control_editor_plugin.h" + +#include "editor/plugins/canvas_item_editor_plugin.h" + +void ControlPositioningWarning::_update_warning() { + if (!control_node) { + title_icon->set_texture(nullptr); + title_label->set_text(""); + hint_label->set_text(""); + return; + } + + Node *parent_node = control_node->get_parent_control(); + if (!parent_node) { + title_icon->set_texture(get_theme_icon(SNAME("SubViewport"), SNAME("EditorIcons"))); + title_label->set_text(TTR("This node doesn't have a control parent.")); + hint_label->set_text(TTR("Use the appropriate layout properties depending on where you are going to put it.")); + } else if (Object::cast_to<Container>(parent_node)) { + title_icon->set_texture(get_theme_icon(SNAME("Container"), SNAME("EditorIcons"))); + title_label->set_text(TTR("This node is a child of a container.")); + hint_label->set_text(TTR("Use container properties for positioning.")); + } else { + title_icon->set_texture(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); + title_label->set_text(TTR("This node is a child of a regular control.")); + hint_label->set_text(TTR("Use anchors and the rectangle for positioning.")); + } + + bg_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg_group_note"), SNAME("EditorProperty"))); +} + +void ControlPositioningWarning::_update_toggler() { + Ref<Texture2D> arrow; + if (hint_label->is_visible()) { + arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); + set_tooltip(TTR("Collapse positioning hint.")); + } else { + if (is_layout_rtl()) { + arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + } else { + arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + } + set_tooltip(TTR("Expand positioning hint.")); + } + + hint_icon->set_texture(arrow); +} + +void ControlPositioningWarning::set_control(Control *p_node) { + control_node = p_node; + _update_warning(); +} + +void ControlPositioningWarning::gui_input(const Ref<InputEvent> &p_event) { + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + bool state = !hint_label->is_visible(); + + hint_filler_left->set_visible(state); + hint_label->set_visible(state); + hint_filler_right->set_visible(state); + + _update_toggler(); + } +} + +void ControlPositioningWarning::_notification(int p_notification) { + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + _update_warning(); + _update_toggler(); + break; + } +} + +ControlPositioningWarning::ControlPositioningWarning() { + set_mouse_filter(MOUSE_FILTER_STOP); + + bg_panel = memnew(PanelContainer); + bg_panel->set_mouse_filter(MOUSE_FILTER_IGNORE); + add_child(bg_panel); + + grid = memnew(GridContainer); + grid->set_columns(3); + bg_panel->add_child(grid); + + title_icon = memnew(TextureRect); + title_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); + grid->add_child(title_icon); + + title_label = memnew(Label); + title_label->set_autowrap_mode(Label::AutowrapMode::AUTOWRAP_WORD); + title_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + title_label->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); + grid->add_child(title_label); + + hint_icon = memnew(TextureRect); + hint_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); + grid->add_child(hint_icon); + + // Filler. + hint_filler_left = memnew(Control); + hint_filler_left->hide(); + grid->add_child(hint_filler_left); + + hint_label = memnew(Label); + hint_label->set_autowrap_mode(Label::AutowrapMode::AUTOWRAP_WORD); + hint_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hint_label->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); + hint_label->hide(); + grid->add_child(hint_label); + + // Filler. + hint_filler_right = memnew(Control); + hint_filler_right->hide(); + grid->add_child(hint_filler_right); +} + +void EditorPropertyAnchorsPreset::_set_read_only(bool p_read_only) { + options->set_disabled(p_read_only); +}; + +void EditorPropertyAnchorsPreset::_option_selected(int p_which) { + int64_t val = options->get_item_metadata(p_which); + emit_changed(get_edited_property(), val); +} + +void EditorPropertyAnchorsPreset::update_property() { + int64_t which = get_edited_object()->get(get_edited_property()); + + for (int i = 0; i < options->get_item_count(); i++) { + Variant val = options->get_item_metadata(i); + if (val != Variant() && which == (int64_t)val) { + options->select(i); + return; + } + } +} + +void EditorPropertyAnchorsPreset::setup(const Vector<String> &p_options) { + options->clear(); + + Vector<String> split_after; + split_after.append("Custom"); + split_after.append("PresetWide"); + split_after.append("PresetBottomLeft"); + split_after.append("PresetCenter"); + + for (int i = 0, j = 0; i < p_options.size(); i++, j++) { + Vector<String> text_split = p_options[i].split(":"); + int64_t current_val = text_split[1].to_int(); + + String humanized_name = text_split[0]; + if (humanized_name.begins_with("Preset")) { + if (humanized_name == "PresetWide") { + humanized_name = "Full Rect"; + } else { + humanized_name = humanized_name.trim_prefix("Preset"); + humanized_name = humanized_name.capitalize(); + } + + String icon_name = text_split[0].trim_prefix("Preset"); + icon_name = "ControlAlign" + icon_name; + options->add_icon_item(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(icon_name, "EditorIcons"), humanized_name); + } else { + options->add_item(humanized_name); + } + + options->set_item_metadata(j, current_val); + if (split_after.has(text_split[0])) { + options->add_separator(); + j++; + } + } +} + +EditorPropertyAnchorsPreset::EditorPropertyAnchorsPreset() { + options = memnew(OptionButton); + options->set_clip_text(true); + options->set_flat(true); + add_child(options); + add_focusable(options); + options->connect("item_selected", callable_mp(this, &EditorPropertyAnchorsPreset::_option_selected)); +} + +void EditorPropertySizeFlags::_set_read_only(bool p_read_only) { + for (CheckBox *check : flag_checks) { + check->set_disabled(p_read_only); + } + flag_presets->set_disabled(p_read_only); +}; + +void EditorPropertySizeFlags::_preset_selected(int p_which) { + int preset = flag_presets->get_item_id(p_which); + if (preset == SIZE_FLAGS_PRESET_CUSTOM) { + flag_options->set_visible(true); + return; + } + flag_options->set_visible(false); + + uint32_t value = 0; + switch (preset) { + case SIZE_FLAGS_PRESET_FILL: + value = Control::SIZE_FILL; + break; + case SIZE_FLAGS_PRESET_SHRINK_BEGIN: + value = Control::SIZE_SHRINK_BEGIN; + break; + case SIZE_FLAGS_PRESET_SHRINK_CENTER: + value = Control::SIZE_SHRINK_CENTER; + break; + case SIZE_FLAGS_PRESET_SHRINK_END: + value = Control::SIZE_SHRINK_END; + break; + } + + bool is_expand = flag_expand->is_visible() && flag_expand->is_pressed(); + if (is_expand) { + value |= Control::SIZE_EXPAND; + } + + emit_changed(get_edited_property(), value); +} + +void EditorPropertySizeFlags::_expand_toggled() { + uint32_t value = get_edited_object()->get(get_edited_property()); + + if (flag_expand->is_visible() && flag_expand->is_pressed()) { + value |= Control::SIZE_EXPAND; + } else { + value ^= Control::SIZE_EXPAND; + } + + // Keep the custom preset selected as we toggle individual flags. + keep_selected_preset = true; + emit_changed(get_edited_property(), value); +} + +void EditorPropertySizeFlags::_flag_toggled() { + uint32_t value = 0; + for (int i = 0; i < flag_checks.size(); i++) { + if (flag_checks[i]->is_pressed()) { + int flag_value = flag_checks[i]->get_meta("_value"); + value |= flag_value; + } + } + + bool is_expand = flag_expand->is_visible() && flag_expand->is_pressed(); + if (is_expand) { + value |= Control::SIZE_EXPAND; + } + + // Keep the custom preset selected as we toggle individual flags. + keep_selected_preset = true; + emit_changed(get_edited_property(), value); +} + +void EditorPropertySizeFlags::update_property() { + uint32_t value = get_edited_object()->get(get_edited_property()); + + for (int i = 0; i < flag_checks.size(); i++) { + int flag_value = flag_checks[i]->get_meta("_value"); + if (value & flag_value) { + flag_checks[i]->set_pressed(true); + } else { + flag_checks[i]->set_pressed(false); + } + } + + bool is_expand = value & Control::SIZE_EXPAND; + flag_expand->set_pressed(is_expand); + + if (keep_selected_preset) { + keep_selected_preset = false; + return; + } + + FlagPreset preset = SIZE_FLAGS_PRESET_CUSTOM; + if (value == Control::SIZE_FILL || value == (Control::SIZE_FILL | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_FILL; + } else if (value == Control::SIZE_SHRINK_BEGIN || value == (Control::SIZE_SHRINK_BEGIN | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_SHRINK_BEGIN; + } else if (value == Control::SIZE_SHRINK_CENTER || value == (Control::SIZE_SHRINK_CENTER | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_SHRINK_CENTER; + } else if (value == Control::SIZE_SHRINK_END || value == (Control::SIZE_SHRINK_END | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_SHRINK_END; + } + + int preset_idx = flag_presets->get_item_index(preset); + if (preset_idx >= 0) { + flag_presets->select(preset_idx); + } + flag_options->set_visible(preset == SIZE_FLAGS_PRESET_CUSTOM); +} + +void EditorPropertySizeFlags::setup(const Vector<String> &p_options, bool p_vertical) { + vertical = p_vertical; + + if (p_options.size() == 0) { + flag_presets->clear(); + flag_presets->add_item(TTR("Container Default")); + flag_presets->set_disabled(true); + flag_expand->set_visible(false); + return; + } + + Map<int, String> flags; + for (int i = 0, j = 0; i < p_options.size(); i++, j++) { + Vector<String> text_split = p_options[i].split(":"); + int64_t current_val = text_split[1].to_int(); + flags[current_val] = text_split[0]; + + if (current_val == SIZE_EXPAND) { + continue; + } + + CheckBox *cb = memnew(CheckBox); + cb->set_text(text_split[0]); + cb->set_clip_text(true); + cb->set_meta("_value", current_val); + cb->connect("pressed", callable_mp(this, &EditorPropertySizeFlags::_flag_toggled)); + add_focusable(cb); + + flag_options->add_child(cb); + flag_checks.append(cb); + } + + Control *gui_base = EditorNode::get_singleton()->get_gui_base(); + String wide_preset_icon = SNAME("ControlAlignHCenterWide"); + if (vertical) { + wide_preset_icon = SNAME("ControlAlignVCenterWide"); + } + + flag_presets->clear(); + if (flags.has(SIZE_FILL)) { + flag_presets->add_icon_item(gui_base->get_theme_icon(wide_preset_icon, SNAME("EditorIcons")), TTR("Fill"), SIZE_FLAGS_PRESET_FILL); + } + // Shrink Begin is the same as no flags at all, as such it cannot be disabled. + flag_presets->add_icon_item(gui_base->get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Shrink Begin"), SIZE_FLAGS_PRESET_SHRINK_BEGIN); + if (flags.has(SIZE_SHRINK_CENTER)) { + flag_presets->add_icon_item(gui_base->get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), SIZE_FLAGS_PRESET_SHRINK_CENTER); + } + if (flags.has(SIZE_SHRINK_END)) { + flag_presets->add_icon_item(gui_base->get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Shrink End"), SIZE_FLAGS_PRESET_SHRINK_END); + } + flag_presets->add_separator(); + flag_presets->add_item(TTR("Custom"), SIZE_FLAGS_PRESET_CUSTOM); + + flag_expand->set_visible(flags.has(SIZE_EXPAND)); +} + +EditorPropertySizeFlags::EditorPropertySizeFlags() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + + flag_presets = memnew(OptionButton); + flag_presets->set_clip_text(true); + flag_presets->set_flat(true); + vb->add_child(flag_presets); + add_focusable(flag_presets); + set_label_reference(flag_presets); + flag_presets->connect("item_selected", callable_mp(this, &EditorPropertySizeFlags::_preset_selected)); + + flag_options = memnew(VBoxContainer); + flag_options->hide(); + vb->add_child(flag_options); + + flag_expand = memnew(CheckBox); + flag_expand->set_text(TTR("Expand")); + vb->add_child(flag_expand); + add_focusable(flag_expand); + flag_expand->connect("pressed", callable_mp(this, &EditorPropertySizeFlags::_expand_toggled)); +} + +bool EditorInspectorPluginControl::can_handle(Object *p_object) { + return Object::cast_to<Control>(p_object) != nullptr; +} + +void EditorInspectorPluginControl::parse_group(Object *p_object, const String &p_group) { + Control *control = Object::cast_to<Control>(p_object); + if (!control || p_group != "Layout") { + return; + } + + ControlPositioningWarning *pos_warning = memnew(ControlPositioningWarning); + pos_warning->set_control(control); + add_custom_control(pos_warning); +} + +bool EditorInspectorPluginControl::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { + Control *control = Object::cast_to<Control>(p_object); + if (!control) { + return false; + } + + if (p_path == "anchors_preset") { + EditorPropertyAnchorsPreset *prop_editor = memnew(EditorPropertyAnchorsPreset); + Vector<String> options = p_hint_text.split(","); + prop_editor->setup(options); + add_property_editor(p_path, prop_editor); + + return true; + } + + if (p_path == "size_flags_horizontal" || p_path == "size_flags_vertical") { + EditorPropertySizeFlags *prop_editor = memnew(EditorPropertySizeFlags); + Vector<String> options; + if (!p_hint_text.is_empty()) { + options = p_hint_text.split(","); + } + prop_editor->setup(options, p_path == "size_flags_vertical"); + add_property_editor(p_path, prop_editor); + + return true; + } + + return false; +} + +void ControlEditorToolbar::_set_anchors_and_offsets_preset(Control::LayoutPreset p_preset) { + List<Node *> selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Change Anchors and Offsets")); + + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + undo_redo->add_do_method(control, "set_anchors_preset", p_preset); + switch (p_preset) { + case PRESET_TOP_LEFT: + case PRESET_TOP_RIGHT: + case PRESET_BOTTOM_LEFT: + case PRESET_BOTTOM_RIGHT: + case PRESET_CENTER_LEFT: + case PRESET_CENTER_TOP: + case PRESET_CENTER_RIGHT: + case PRESET_CENTER_BOTTOM: + case PRESET_CENTER: + undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_KEEP_SIZE); + break; + case PRESET_LEFT_WIDE: + case PRESET_TOP_WIDE: + case PRESET_RIGHT_WIDE: + case PRESET_BOTTOM_WIDE: + case PRESET_VCENTER_WIDE: + case PRESET_HCENTER_WIDE: + case PRESET_WIDE: + undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_MINSIZE); + break; + } + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + undo_redo->commit_action(); + + anchors_mode = false; + anchor_mode_button->set_pressed(anchors_mode); +} + +void ControlEditorToolbar::_set_anchors_and_offsets_to_keep_ratio() { + List<Node *> selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Change Anchors and Offsets")); + + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + Point2 top_left_anchor = _position_to_anchor(control, Point2()); + Point2 bottom_right_anchor = _position_to_anchor(control, control->get_size()); + undo_redo->add_do_method(control, "set_anchor", SIDE_LEFT, top_left_anchor.x, false, true); + undo_redo->add_do_method(control, "set_anchor", SIDE_RIGHT, bottom_right_anchor.x, false, true); + undo_redo->add_do_method(control, "set_anchor", SIDE_TOP, top_left_anchor.y, false, true); + undo_redo->add_do_method(control, "set_anchor", SIDE_BOTTOM, bottom_right_anchor.y, false, true); + undo_redo->add_do_method(control, "set_meta", "_edit_use_anchors_", true); + + bool use_anchors = control->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_"); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + undo_redo->add_undo_method(control, "set_meta", "_edit_use_anchors_", use_anchors); + + anchors_mode = true; + anchor_mode_button->set_pressed(anchors_mode); + } + } + + undo_redo->commit_action(); +} + +void ControlEditorToolbar::_set_anchors_preset(Control::LayoutPreset p_preset) { + List<Node *> selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Change Anchors")); + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + undo_redo->add_do_method(control, "set_anchors_preset", p_preset); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + undo_redo->commit_action(); +} + +void ControlEditorToolbar::_set_container_h_preset(Control::SizeFlags p_preset) { + List<Node *> selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Change Horizontal Size Flags")); + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + undo_redo->add_do_method(control, "set_h_size_flags", p_preset); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + undo_redo->commit_action(); +} + +void ControlEditorToolbar::_set_container_v_preset(Control::SizeFlags p_preset) { + List<Node *> selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Change Horizontal Size Flags")); + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + undo_redo->add_do_method(control, "set_v_size_flags", p_preset); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + undo_redo->commit_action(); +} + +Vector2 ControlEditorToolbar::_anchor_to_position(const Control *p_control, Vector2 anchor) { + ERR_FAIL_COND_V(!p_control, Vector2()); + + Transform2D parent_transform = p_control->get_transform().affine_inverse(); + Rect2 parent_rect = p_control->get_parent_anchorable_rect(); + + if (p_control->is_layout_rtl()) { + return parent_transform.xform(parent_rect.position + Vector2(parent_rect.size.x - parent_rect.size.x * anchor.x, parent_rect.size.y * anchor.y)); + } else { + return parent_transform.xform(parent_rect.position + Vector2(parent_rect.size.x * anchor.x, parent_rect.size.y * anchor.y)); + } +} + +Vector2 ControlEditorToolbar::_position_to_anchor(const Control *p_control, Vector2 position) { + ERR_FAIL_COND_V(!p_control, Vector2()); + + Rect2 parent_rect = p_control->get_parent_anchorable_rect(); + + Vector2 output = Vector2(); + if (p_control->is_layout_rtl()) { + output.x = (parent_rect.size.x == 0) ? 0.0 : (parent_rect.size.x - p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x; + } else { + output.x = (parent_rect.size.x == 0) ? 0.0 : (p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x; + } + output.y = (parent_rect.size.y == 0) ? 0.0 : (p_control->get_transform().xform(position).y - parent_rect.position.y) / parent_rect.size.y; + return output; +} + +void ControlEditorToolbar::_button_toggle_anchor_mode(bool p_status) { + List<Control *> selection = _get_edited_controls(false, false); + for (Control *E : selection) { + if (Object::cast_to<Container>(E->get_parent())) { + continue; + } + + E->set_meta("_edit_use_anchors_", p_status); + } + + anchors_mode = p_status; + CanvasItemEditor::get_singleton()->update_viewport(); +} + +bool ControlEditorToolbar::_is_node_locked(const Node *p_node) { + return p_node->has_meta("_edit_lock_") && p_node->get_meta("_edit_lock_"); +} + +List<Control *> ControlEditorToolbar::_get_edited_controls(bool retrieve_locked, bool remove_controls_if_parent_in_selection) { + List<Control *> selection; + for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { + Control *control = Object::cast_to<Control>(E.key); + if (control && control->is_visible_in_tree() && control->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retrieve_locked || !_is_node_locked(control))) { + selection.push_back(control); + } + } + + if (remove_controls_if_parent_in_selection) { + List<Control *> filtered_selection; + for (Control *E : selection) { + if (!selection.find(E->get_parent())) { + filtered_selection.push_back(E); + } + } + return filtered_selection; + } + + return selection; +} + +void ControlEditorToolbar::_popup_callback(int p_op) { + switch (p_op) { + case ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT: { + _set_anchors_and_offsets_preset(PRESET_TOP_LEFT); + } break; + case ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT: { + _set_anchors_and_offsets_preset(PRESET_TOP_RIGHT); + } break; + case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT: { + _set_anchors_and_offsets_preset(PRESET_BOTTOM_LEFT); + } break; + case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT: { + _set_anchors_and_offsets_preset(PRESET_BOTTOM_RIGHT); + } break; + case ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT: { + _set_anchors_and_offsets_preset(PRESET_CENTER_LEFT); + } break; + case ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT: { + _set_anchors_and_offsets_preset(PRESET_CENTER_RIGHT); + } break; + case ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP: { + _set_anchors_and_offsets_preset(PRESET_CENTER_TOP); + } break; + case ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM: { + _set_anchors_and_offsets_preset(PRESET_CENTER_BOTTOM); + } break; + case ANCHORS_AND_OFFSETS_PRESET_CENTER: { + _set_anchors_and_offsets_preset(PRESET_CENTER); + } break; + case ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE: { + _set_anchors_and_offsets_preset(PRESET_TOP_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE: { + _set_anchors_and_offsets_preset(PRESET_LEFT_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE: { + _set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE: { + _set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE: { + _set_anchors_and_offsets_preset(PRESET_VCENTER_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE: { + _set_anchors_and_offsets_preset(PRESET_HCENTER_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_WIDE: { + _set_anchors_and_offsets_preset(Control::PRESET_WIDE); + } break; + case ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO: { + _set_anchors_and_offsets_to_keep_ratio(); + } break; + + case ANCHORS_PRESET_TOP_LEFT: { + _set_anchors_preset(PRESET_TOP_LEFT); + } break; + case ANCHORS_PRESET_TOP_RIGHT: { + _set_anchors_preset(PRESET_TOP_RIGHT); + } break; + case ANCHORS_PRESET_BOTTOM_LEFT: { + _set_anchors_preset(PRESET_BOTTOM_LEFT); + } break; + case ANCHORS_PRESET_BOTTOM_RIGHT: { + _set_anchors_preset(PRESET_BOTTOM_RIGHT); + } break; + case ANCHORS_PRESET_CENTER_LEFT: { + _set_anchors_preset(PRESET_CENTER_LEFT); + } break; + case ANCHORS_PRESET_CENTER_RIGHT: { + _set_anchors_preset(PRESET_CENTER_RIGHT); + } break; + case ANCHORS_PRESET_CENTER_TOP: { + _set_anchors_preset(PRESET_CENTER_TOP); + } break; + case ANCHORS_PRESET_CENTER_BOTTOM: { + _set_anchors_preset(PRESET_CENTER_BOTTOM); + } break; + case ANCHORS_PRESET_CENTER: { + _set_anchors_preset(PRESET_CENTER); + } break; + case ANCHORS_PRESET_TOP_WIDE: { + _set_anchors_preset(PRESET_TOP_WIDE); + } break; + case ANCHORS_PRESET_LEFT_WIDE: { + _set_anchors_preset(PRESET_LEFT_WIDE); + } break; + case ANCHORS_PRESET_RIGHT_WIDE: { + _set_anchors_preset(PRESET_RIGHT_WIDE); + } break; + case ANCHORS_PRESET_BOTTOM_WIDE: { + _set_anchors_preset(PRESET_BOTTOM_WIDE); + } break; + case ANCHORS_PRESET_VCENTER_WIDE: { + _set_anchors_preset(PRESET_VCENTER_WIDE); + } break; + case ANCHORS_PRESET_HCENTER_WIDE: { + _set_anchors_preset(PRESET_HCENTER_WIDE); + } break; + case ANCHORS_PRESET_WIDE: { + _set_anchors_preset(Control::PRESET_WIDE); + } break; + + case CONTAINERS_H_PRESET_FILL: { + _set_container_h_preset(Control::SIZE_FILL); + } break; + case CONTAINERS_H_PRESET_FILL_EXPAND: { + _set_container_h_preset(Control::SIZE_EXPAND_FILL); + } break; + case CONTAINERS_H_PRESET_SHRINK_BEGIN: { + _set_container_h_preset(Control::SIZE_SHRINK_BEGIN); + } break; + case CONTAINERS_H_PRESET_SHRINK_CENTER: { + _set_container_h_preset(Control::SIZE_SHRINK_CENTER); + } break; + case CONTAINERS_H_PRESET_SHRINK_END: { + _set_container_h_preset(Control::SIZE_SHRINK_END); + } break; + + case CONTAINERS_V_PRESET_FILL: { + _set_container_v_preset(Control::SIZE_FILL); + } break; + case CONTAINERS_V_PRESET_FILL_EXPAND: { + _set_container_v_preset(Control::SIZE_EXPAND_FILL); + } break; + case CONTAINERS_V_PRESET_SHRINK_BEGIN: { + _set_container_v_preset(Control::SIZE_SHRINK_BEGIN); + } break; + case CONTAINERS_V_PRESET_SHRINK_CENTER: { + _set_container_v_preset(Control::SIZE_SHRINK_CENTER); + } break; + case CONTAINERS_V_PRESET_SHRINK_END: { + _set_container_v_preset(Control::SIZE_SHRINK_END); + } break; + } +} + +void ControlEditorToolbar::_selection_changed() { + // Update the anchors_mode. + int nb_controls = 0; + int nb_valid_controls = 0; + int nb_anchors_mode = 0; + + List<Node *> selection = editor_selection->get_selected_node_list(); + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (!control) { + continue; + } + + nb_controls++; + if (Object::cast_to<Container>(control->get_parent())) { + continue; + } + + nb_valid_controls++; + if (control->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_")) { + nb_anchors_mode++; + } + } + + anchors_mode = (nb_valid_controls == nb_anchors_mode); + anchor_mode_button->set_pressed(anchors_mode); + + if (nb_controls > 0) { + set_physics_process(true); + } else { + set_physics_process(false); + set_visible(false); + } +} + +void ControlEditorToolbar::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + anchor_layouts_icon->set_texture(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); + + PopupMenu *p = anchor_presets_menu->get_popup(); + p->clear(); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT); + p->add_separator(); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_AND_OFFSETS_PRESET_CENTER); + p->add_separator(); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE); + p->add_separator(); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_AND_OFFSETS_PRESET_WIDE); + p->add_icon_item(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")), TTR("Keep Current Ratio"), ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO); + p->set_item_tooltip(19, TTR("Adjust anchors and offsets to match the current rect size.")); + + p->add_separator(); + p->add_submenu_item(TTR("Anchors only"), "Anchors"); + p->set_item_icon(21, get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); + + anchors_popup->clear(); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_PRESET_TOP_LEFT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_PRESET_TOP_RIGHT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_PRESET_BOTTOM_RIGHT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_PRESET_BOTTOM_LEFT); + anchors_popup->add_separator(); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_PRESET_CENTER_LEFT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_PRESET_CENTER_TOP); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_PRESET_CENTER_RIGHT); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_PRESET_CENTER_BOTTOM); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_PRESET_CENTER); + anchors_popup->add_separator(); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_PRESET_LEFT_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_PRESET_TOP_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_PRESET_RIGHT_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_PRESET_BOTTOM_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_PRESET_VCENTER_WIDE); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_PRESET_HCENTER_WIDE); + anchors_popup->add_separator(); + anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_WIDE); + + anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); + + container_layouts_icon->set_texture(get_theme_icon(SNAME("Container"), SNAME("EditorIcons"))); + + p = container_h_presets_menu->get_popup(); + p->clear(); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("Fill"), CONTAINERS_H_PRESET_FILL); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("Fill & Expand"), CONTAINERS_H_PRESET_FILL_EXPAND); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Shrink Begin"), CONTAINERS_H_PRESET_SHRINK_BEGIN); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), CONTAINERS_H_PRESET_SHRINK_CENTER); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Shrink End"), CONTAINERS_H_PRESET_SHRINK_END); + + p = container_v_presets_menu->get_popup(); + p->clear(); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("Fill"), CONTAINERS_V_PRESET_FILL); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("Fill & Expand"), CONTAINERS_V_PRESET_FILL_EXPAND); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Shrink Begin"), CONTAINERS_V_PRESET_SHRINK_BEGIN); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), CONTAINERS_V_PRESET_SHRINK_CENTER); + p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")), TTR("Shrink End"), CONTAINERS_V_PRESET_SHRINK_END); + } break; + + case NOTIFICATION_PHYSICS_PROCESS: { + bool has_control_parents = false; + bool has_container_parents = false; + + // Update the viewport if the canvas_item changes + List<Control *> selection = _get_edited_controls(true); + for (Control *control : selection) { + if (Object::cast_to<Control>(control->get_parent())) { + has_control_parents = true; + } + if (Object::cast_to<Container>(control->get_parent())) { + has_container_parents = true; + } + } + + // Show / Hide the control layout buttons. + if (selection.size() > 0) { + set_visible(true); + + // Toggle anchor and container layout buttons depending on parents of the selected nodes. + // - If there are no control parents, enable everything. + // - If there are container parents, then enable only container buttons. + // - If there are NO container parents, then enable only anchor buttons. + bool enable_anchors = false; + bool enable_containers = false; + if (!has_control_parents) { + enable_anchors = true; + enable_containers = true; + } else if (has_container_parents) { + enable_containers = true; + } else { + enable_anchors = true; + } + + if (enable_anchors) { + anchor_presets_menu->set_disabled(false); + anchor_presets_menu->set_tooltip(TTR("Presets for the anchor and offset values of a Control node.")); + anchor_mode_button->set_disabled(false); + anchor_mode_button->set_tooltip(TTR("When active, moving Control nodes changes their anchors instead of their offsets.")); + } else { + anchor_presets_menu->set_disabled(true); + anchor_presets_menu->set_tooltip(TTR("Children of containers have their anchors and offsets values controlled by their parent.")); + anchor_mode_button->set_disabled(true); + anchor_mode_button->set_tooltip(TTR("Children of containers have their anchors and offsets values controlled by their parent.")); + } + + if (enable_containers) { + container_h_presets_menu->set_disabled(false); + container_h_presets_menu->set_tooltip(TTR("Horizontal sizing setting for children of a Container node.")); + container_v_presets_menu->set_disabled(false); + container_v_presets_menu->set_tooltip(TTR("Vertical sizing setting for children of a Container node.")); + } else { + container_h_presets_menu->set_disabled(true); + container_h_presets_menu->set_tooltip(TTR("Children of regular controls are controlled by their anchors and offsets.")); + container_v_presets_menu->set_disabled(true); + container_v_presets_menu->set_tooltip(TTR("Children of regular controls are controlled by their anchors and offsets.")); + } + } else { + set_visible(false); + } + } break; + } +} + +ControlEditorToolbar::ControlEditorToolbar(EditorNode *p_editor) { + anchor_layouts_icon = memnew(TextureRect); + anchor_layouts_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); + add_child(anchor_layouts_icon); + + Label *l = memnew(Label); + l->set_text(TTR("Anchors")); + l->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); + add_child(l); + + anchor_presets_menu = memnew(MenuButton); + anchor_presets_menu->set_shortcut_context(this); + anchor_presets_menu->set_text(TTR("Preset")); + add_child(anchor_presets_menu); + anchor_presets_menu->set_switch_on_hover(true); + + PopupMenu *p = anchor_presets_menu->get_popup(); + p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); + + anchors_popup = memnew(PopupMenu); + p->add_child(anchors_popup); + anchors_popup->set_name("Anchors"); + anchors_popup->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); + + anchor_mode_button = memnew(Button); + anchor_mode_button->set_flat(true); + anchor_mode_button->set_toggle_mode(true); + add_child(anchor_mode_button); + anchor_mode_button->connect("toggled", callable_mp(this, &ControlEditorToolbar::_button_toggle_anchor_mode)); + + add_child(memnew(VSeparator)); + + container_layouts_icon = memnew(TextureRect); + container_layouts_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); + add_child(container_layouts_icon); + + l = memnew(Label); + l->set_text(TTR("Containers")); + l->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); + add_child(l); + + container_h_presets_menu = memnew(MenuButton); + container_h_presets_menu->set_shortcut_context(this); + container_h_presets_menu->set_text(TTR("Horizontal")); + add_child(container_h_presets_menu); + container_h_presets_menu->set_switch_on_hover(true); + + p = container_h_presets_menu->get_popup(); + p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); + + container_v_presets_menu = memnew(MenuButton); + container_v_presets_menu->set_shortcut_context(this); + container_v_presets_menu->set_text(TTR("Vertical")); + add_child(container_v_presets_menu); + container_v_presets_menu->set_switch_on_hover(true); + + p = container_v_presets_menu->get_popup(); + p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); + + undo_redo = p_editor->get_undo_redo(); + editor_selection = p_editor->get_editor_selection(); + editor_selection->add_editor_plugin(this); + editor_selection->connect("selection_changed", callable_mp(this, &ControlEditorToolbar::_selection_changed)); + + singleton = this; +} + +ControlEditorToolbar *ControlEditorToolbar::singleton = nullptr; + +ControlEditorPlugin::ControlEditorPlugin(EditorNode *p_editor) { + editor = p_editor; + + toolbar = memnew(ControlEditorToolbar(editor)); + toolbar->hide(); + add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar); + + Ref<EditorInspectorPluginControl> plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} diff --git a/editor/plugins/control_editor_plugin.h b/editor/plugins/control_editor_plugin.h new file mode 100644 index 0000000000..610846a97b --- /dev/null +++ b/editor/plugins/control_editor_plugin.h @@ -0,0 +1,255 @@ +/*************************************************************************/ +/* control_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 CONTROL_EDITOR_PLUGIN_H +#define CONTROL_EDITOR_PLUGIN_H + +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" + +#include "scene/gui/box_container.h" +#include "scene/gui/check_box.h" +#include "scene/gui/control.h" +#include "scene/gui/label.h" +#include "scene/gui/margin_container.h" +#include "scene/gui/option_button.h" +#include "scene/gui/panel_container.h" +#include "scene/gui/texture_rect.h" + +class ControlPositioningWarning : public MarginContainer { + GDCLASS(ControlPositioningWarning, MarginContainer); + + Control *control_node = nullptr; + + PanelContainer *bg_panel = nullptr; + GridContainer *grid = nullptr; + TextureRect *title_icon = nullptr; + TextureRect *hint_icon = nullptr; + Label *title_label = nullptr; + Label *hint_label = nullptr; + Control *hint_filler_left = nullptr; + Control *hint_filler_right = nullptr; + + void _update_warning(); + void _update_toggler(); + virtual void gui_input(const Ref<InputEvent> &p_event) override; + +protected: + void _notification(int p_notification); + +public: + void set_control(Control *p_node); + + ControlPositioningWarning(); +}; + +class EditorPropertyAnchorsPreset : public EditorProperty { + GDCLASS(EditorPropertyAnchorsPreset, EditorProperty); + OptionButton *options; + + void _option_selected(int p_which); + +protected: + virtual void _set_read_only(bool p_read_only) override; + +public: + void setup(const Vector<String> &p_options); + virtual void update_property() override; + EditorPropertyAnchorsPreset(); +}; + +class EditorPropertySizeFlags : public EditorProperty { + GDCLASS(EditorPropertySizeFlags, EditorProperty); + + enum FlagPreset { + SIZE_FLAGS_PRESET_FILL, + SIZE_FLAGS_PRESET_SHRINK_BEGIN, + SIZE_FLAGS_PRESET_SHRINK_CENTER, + SIZE_FLAGS_PRESET_SHRINK_END, + SIZE_FLAGS_PRESET_CUSTOM, + }; + + OptionButton *flag_presets; + CheckBox *flag_expand; + VBoxContainer *flag_options; + Vector<CheckBox *> flag_checks; + + bool vertical = false; + + bool keep_selected_preset = false; + + void _preset_selected(int p_which); + void _expand_toggled(); + void _flag_toggled(); + +protected: + virtual void _set_read_only(bool p_read_only) override; + +public: + void setup(const Vector<String> &p_options, bool p_vertical); + virtual void update_property() override; + EditorPropertySizeFlags(); +}; + +class EditorInspectorPluginControl : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginControl, EditorInspectorPlugin); + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_group(Object *p_object, const String &p_group) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; +}; + +class ControlEditorToolbar : public HBoxContainer { + GDCLASS(ControlEditorToolbar, HBoxContainer); + + UndoRedo *undo_redo; + EditorSelection *editor_selection; + + enum MenuOption { + ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT, + ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT, + ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT, + ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT, + ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT, + ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT, + ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP, + ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM, + ANCHORS_AND_OFFSETS_PRESET_CENTER, + ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE, + ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE, + ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE, + ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE, + ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE, + ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE, + ANCHORS_AND_OFFSETS_PRESET_WIDE, + + ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO, + + ANCHORS_PRESET_TOP_LEFT, + ANCHORS_PRESET_TOP_RIGHT, + ANCHORS_PRESET_BOTTOM_LEFT, + ANCHORS_PRESET_BOTTOM_RIGHT, + ANCHORS_PRESET_CENTER_LEFT, + ANCHORS_PRESET_CENTER_RIGHT, + ANCHORS_PRESET_CENTER_TOP, + ANCHORS_PRESET_CENTER_BOTTOM, + ANCHORS_PRESET_CENTER, + ANCHORS_PRESET_TOP_WIDE, + ANCHORS_PRESET_LEFT_WIDE, + ANCHORS_PRESET_RIGHT_WIDE, + ANCHORS_PRESET_BOTTOM_WIDE, + ANCHORS_PRESET_VCENTER_WIDE, + ANCHORS_PRESET_HCENTER_WIDE, + ANCHORS_PRESET_WIDE, + + // Offsets Presets are not currently in use. + OFFSETS_PRESET_TOP_LEFT, + OFFSETS_PRESET_TOP_RIGHT, + OFFSETS_PRESET_BOTTOM_LEFT, + OFFSETS_PRESET_BOTTOM_RIGHT, + OFFSETS_PRESET_CENTER_LEFT, + OFFSETS_PRESET_CENTER_RIGHT, + OFFSETS_PRESET_CENTER_TOP, + OFFSETS_PRESET_CENTER_BOTTOM, + OFFSETS_PRESET_CENTER, + OFFSETS_PRESET_TOP_WIDE, + OFFSETS_PRESET_LEFT_WIDE, + OFFSETS_PRESET_RIGHT_WIDE, + OFFSETS_PRESET_BOTTOM_WIDE, + OFFSETS_PRESET_VCENTER_WIDE, + OFFSETS_PRESET_HCENTER_WIDE, + OFFSETS_PRESET_WIDE, + + CONTAINERS_H_PRESET_FILL, + CONTAINERS_H_PRESET_FILL_EXPAND, + CONTAINERS_H_PRESET_SHRINK_BEGIN, + CONTAINERS_H_PRESET_SHRINK_CENTER, + CONTAINERS_H_PRESET_SHRINK_END, + CONTAINERS_V_PRESET_FILL, + CONTAINERS_V_PRESET_FILL_EXPAND, + CONTAINERS_V_PRESET_SHRINK_BEGIN, + CONTAINERS_V_PRESET_SHRINK_CENTER, + CONTAINERS_V_PRESET_SHRINK_END, + }; + + TextureRect *anchor_layouts_icon; + MenuButton *anchor_presets_menu; + PopupMenu *anchors_popup; + TextureRect *container_layouts_icon; + MenuButton *container_h_presets_menu; + MenuButton *container_v_presets_menu; + + Button *anchor_mode_button; + + bool anchors_mode = false; + + void _set_anchors_preset(Control::LayoutPreset p_preset); + void _set_anchors_and_offsets_preset(Control::LayoutPreset p_preset); + void _set_anchors_and_offsets_to_keep_ratio(); + void _set_container_h_preset(Control::SizeFlags p_preset); + void _set_container_v_preset(Control::SizeFlags p_preset); + + Vector2 _anchor_to_position(const Control *p_control, Vector2 anchor); + Vector2 _position_to_anchor(const Control *p_control, Vector2 position); + + void _button_toggle_anchor_mode(bool p_status); + + bool _is_node_locked(const Node *p_node); + List<Control *> _get_edited_controls(bool retrieve_locked = false, bool remove_controls_if_parent_in_selection = true); + void _popup_callback(int p_op); + void _selection_changed(); + +protected: + void _notification(int p_notification); + + static ControlEditorToolbar *singleton; + +public: + bool is_anchors_mode_enabled() { return anchors_mode; }; + + static ControlEditorToolbar *get_singleton() { return singleton; } + + ControlEditorToolbar(EditorNode *p_editor); +}; + +class ControlEditorPlugin : public EditorPlugin { + GDCLASS(ControlEditorPlugin, EditorPlugin); + + EditorNode *editor; + ControlEditorToolbar *toolbar; + +public: + virtual String get_name() const override { return "Control"; } + + ControlEditorPlugin(EditorNode *p_editor); +}; + +#endif //CONTROL_EDITOR_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index f79b5027cb..9884c45916 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -385,8 +385,6 @@ int Node3DEditorViewport::get_selected_count() const { } void Node3DEditorViewport::cancel_transform() { - _edit.mode = TRANSFORM_NONE; - List<Node *> &selection = editor_selection->get_selected_node_list(); for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { @@ -402,7 +400,8 @@ void Node3DEditorViewport::cancel_transform() { sp->set_global_transform(se->original); } - surface->update(); + + finish_transform(); set_message(TTR("Transform Aborted."), 3); } @@ -886,15 +885,13 @@ void Node3DEditorViewport::_update_name() { view_menu->reset_size(); } -void Node3DEditorViewport::_compute_edit(const Point2 &p_point, const bool p_auto_center) { +void Node3DEditorViewport::_compute_edit(const Point2 &p_point) { _edit.original_local = spatial_editor->are_local_coords_enabled(); _edit.click_ray = _get_ray(p_point); _edit.click_ray_pos = _get_ray_pos(p_point); _edit.plane = TRANSFORM_VIEW; - if (p_auto_center) { - _edit.center = spatial_editor->get_gizmo_transform().origin; - } spatial_editor->update_transform_gizmo(); + _edit.center = spatial_editor->get_gizmo_transform().origin; Node3D *selected = spatial_editor->get_single_selected_node(); Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr; @@ -1366,7 +1363,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } - _edit.center = spatial_editor->get_gizmo_target_center(); Ref<InputEventMouseButton> b = p_event; if (b.is_valid()) { @@ -1471,6 +1467,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } break; case MouseButton::LEFT: { if (b->is_pressed()) { + clicked_wants_append = b->is_shift_pressed(); + if (_edit.mode != TRANSFORM_NONE && _edit.instant) { commit_transform(); break; // just commit the edit, stop processing the event so we don't deselect the object @@ -1600,8 +1598,6 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { //clicking is always deferred to either move or release - clicked_wants_append = b->is_shift_pressed(); - if (clicked.is_null()) { //default to regionselect cursor.region_select = true; @@ -1730,6 +1726,12 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { _edit.mode = TRANSFORM_TRANSLATE; } + // enable region-select if nothing has been selected yet or multi-select (shift key) is active + if (movement_threshold_passed && (get_selected_count() == 0 || clicked_wants_append)) { + cursor.region_select = true; + cursor.region_begin = _edit.original_mouse_pos; + } + if (cursor.region_select) { cursor.region_end = m->get_position(); surface->update(); @@ -3367,7 +3369,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() { Transform3D xform = spatial_editor->get_gizmo_transform(); - const Transform3D camera_xform = camera->get_transform(); + Transform3D camera_xform = camera->get_transform(); if (xform.origin.is_equal_approx(camera_xform.origin)) { for (int i = 0; i < 3; i++) { @@ -3386,63 +3388,11 @@ void Node3DEditorViewport::update_transform_gizmo_view() { const Vector3 camz = -camera_xform.get_basis().get_axis(2).normalized(); const Vector3 camy = -camera_xform.get_basis().get_axis(1).normalized(); const Plane p = Plane(camz, camera_xform.origin); - const real_t gizmo_d = CLAMP(Math::abs(p.distance_to(xform.origin)), camera->get_near() * 2, camera->get_far() / 2); + const real_t gizmo_d = MAX(Math::abs(p.distance_to(xform.origin)), CMP_EPSILON); const real_t d0 = camera->unproject_position(camera_xform.origin + camz * gizmo_d).y; const real_t d1 = camera->unproject_position(camera_xform.origin + camz * gizmo_d + camy).y; const real_t dd = MAX(Math::abs(d0 - d1), CMP_EPSILON); - // This code ensures the gizmo stays on the screen. This includes if - // the gizmo would otherwise be behind the camera, to the sides of - // the camera, too close to the edge of the screen, too close to - // the camera, or too far away from the camera. First we calculate - // where the gizmo would go on screen, then we put it there. - const Vector3 object_position = spatial_editor->get_gizmo_target_center(); - Vector2 gizmo_screen_position = camera->unproject_position(object_position); - const Vector2 viewport_size = viewport->get_size(); - // We would use "camera.is_position_behind(parent_translation)" instead of dot, - // except that it also accounts for the near clip plane, which we don't want. - const bool is_in_front = camera_xform.basis.get_column(2).dot(object_position - camera_xform.origin) < 0; - const bool is_in_viewport = is_in_front && Rect2(Vector2(0, 0), viewport_size).has_point(gizmo_screen_position); - if (spatial_editor->is_keep_gizmo_onscreen_enabled()) { - if (!spatial_editor->is_gizmo_visible() || is_in_viewport) { - // In this case, the gizmo is either not visible, or in the viewport - // already, so we should hide the offscreen line. - gizmo_offscreen_line->hide(); - } else { - // In this case, the point is not "normally" on screen, and - // it should be placed in the center. - const Vector2 half_viewport_size = viewport_size / 2; - gizmo_screen_position = half_viewport_size; - // The rest of this is for drawing the offscreen line. - // One point goes in the center of the viewport. - // Calculate where to put the other point of the line. - Vector2 unprojected_position = camera->unproject_position(object_position); - if (!is_in_front) { - // When the object is behind, we need to flip and grow the line. - unprojected_position -= half_viewport_size; - unprojected_position = unprojected_position.normalized() * -half_viewport_size.length_squared(); - unprojected_position += half_viewport_size; - } - gizmo_offscreen_line->point1 = half_viewport_size; - gizmo_offscreen_line->point2 = unprojected_position; - gizmo_offscreen_line->update(); - gizmo_offscreen_line->show(); - } - // Update the gizmo's position using what we calculated. - xform.origin = camera->project_position(gizmo_screen_position, gizmo_d); - } else { - // In this case, the user does not want the gizmo to be - // kept on screen, so we should hide the offscreen line, - // and just use the gizmo's unmodified position. - gizmo_offscreen_line->hide(); - if (is_in_viewport) { - xform.origin = camera->project_position(gizmo_screen_position, gizmo_d); - } else { - xform.origin = object_position; - } - } - spatial_editor->set_gizmo_transform(xform); - const real_t gizmo_size = EditorSettings::get_singleton()->get("editors/3d/manipulator_gizmo_size"); // At low viewport heights, multiply the gizmo scale based on the viewport height. // This prevents the gizmo from growing very large and going outside the viewport. @@ -3451,6 +3401,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() { (gizmo_size / Math::abs(dd)) * MAX(1, EDSCALE) * MIN(viewport_base_height, subviewport_container->get_size().height) / viewport_base_height / subviewport_container->get_stretch_shrink(); + Vector3 scale = Vector3(1, 1, 1) * gizmo_scale; // if the determinant is zero, we should disable the gizmo from being rendered // this prevents supplying bad values to the renderer and then having to filter it out again @@ -3472,7 +3423,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() { if (xform.basis.get_axis(i).normalized().dot(xform.basis.get_axis((i + 1) % 3).normalized()) < 1.0) { axis_angle = axis_angle.looking_at(xform.basis.get_axis(i).normalized(), xform.basis.get_axis((i + 1) % 3).normalized()); } - axis_angle.basis *= gizmo_scale; + axis_angle.basis.scale(scale); axis_angle.origin = xform.origin; RenderingServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], axis_angle); RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE)); @@ -3495,7 +3446,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() { // Rotation white outline xform.orthonormalize(); - xform.basis *= gizmo_scale; + xform.basis.scale(scale); RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform); RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE)); } @@ -4007,8 +3958,6 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant continue; } memdelete(instantiated_scene); - } else { - continue; } can_instantiate = true; break; @@ -4077,7 +4026,7 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_ void Node3DEditorViewport::begin_transform(TransformMode p_mode, bool instant) { if (get_selected_count() > 0) { _edit.mode = p_mode; - _compute_edit(_edit.mouse_pos, false); + _compute_edit(_edit.mouse_pos); _edit.instant = instant; _edit.snap = spatial_editor->is_snap_enabled(); } @@ -4111,12 +4060,9 @@ void Node3DEditorViewport::commit_transform() { undo_redo->add_undo_method(sp, "set_global_transform", se->original); } undo_redo->commit_action(); - _edit.mode = TRANSFORM_NONE; - _edit.instant = false; - spatial_editor->set_local_coords_enabled(_edit.original_local); + + finish_transform(); set_message(""); - spatial_editor->update_transform_gizmo(); - surface->update(); } void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) { @@ -4455,6 +4401,14 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) { } } +void Node3DEditorViewport::finish_transform() { + spatial_editor->set_local_coords_enabled(_edit.original_local); + spatial_editor->update_transform_gizmo(); + _edit.mode = TRANSFORM_NONE; + _edit.instant = false; + surface->update(); +} + Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, EditorNode *p_editor, int p_index) { cpu_time_history_index = 0; gpu_time_history_index = 0; @@ -4480,14 +4434,15 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito zoom_indicator_delay = 0.0; spatial_editor = p_spatial_editor; - subviewport_container = memnew(SubViewportContainer); - subviewport_container->set_stretch(true); - add_child(subviewport_container); - subviewport_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + SubViewportContainer *c = memnew(SubViewportContainer); + subviewport_container = c; + c->set_stretch(true); + add_child(c); + c->set_anchors_and_offsets_preset(Control::PRESET_WIDE); viewport = memnew(SubViewport); viewport->set_disable_input(true); - subviewport_container->add_child(viewport); + c->add_child(viewport); surface = memnew(Control); surface->set_drag_forwarding(this); add_child(surface); @@ -4500,9 +4455,6 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito camera->make_current(); surface->set_focus_mode(FOCUS_ALL); - gizmo_offscreen_line = memnew(GizmoOffScreenLine); - subviewport_container->add_child(gizmo_offscreen_line); - VBoxContainer *vbox = memnew(VBoxContainer); surface->add_child(vbox); vbox->set_offset(SIDE_LEFT, 10 * EDSCALE); @@ -5127,7 +5079,6 @@ void Node3DEditor::update_transform_gizmo() { gizmo.visible = count > 0; gizmo.transform.origin = (count > 0) ? gizmo_center / count : Vector3(); gizmo.transform.basis = (count == 1) ? gizmo_basis : Basis(); - gizmo.target_center = gizmo_center / count; for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) { viewports[i]->update_transform_gizmo_view(); @@ -5280,7 +5231,6 @@ Dictionary Node3DEditor::get_state() const { d["show_grid"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_GRID)); d["show_origin"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN)); - d["keep_gizmo_onscreen"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_KEEP_GIZMO_ONSCREEN)); d["fov"] = get_fov(); d["znear"] = get_znear(); d["zfar"] = get_zfar(); @@ -5405,13 +5355,6 @@ void Node3DEditor::set_state(const Dictionary &p_state) { RenderingServer::get_singleton()->instance_set_visible(origin_instance, use); } } - if (d.has("keep_gizmo_onscreen")) { - bool use = d["keep_gizmo_onscreen"]; - - if (use != view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(MENU_KEEP_GIZMO_ONSCREEN))) { - _menu_item_pressed(MENU_KEEP_GIZMO_ONSCREEN); - } - } if (d.has("gizmos_status")) { Dictionary gizmos_status = d["gizmos_status"]; @@ -5765,13 +5708,7 @@ void Node3DEditor::_menu_item_pressed(int p_option) { _init_grid(); view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(p_option), grid_enabled); - } break; - case MENU_KEEP_GIZMO_ONSCREEN: { - keep_gizmo_onscreen = !view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(p_option)); - for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) { - get_editor_viewport(i)->update_transform_gizmo_view(); - } - view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(p_option), keep_gizmo_onscreen); + } break; case MENU_VIEW_CAMERA_SETTINGS: { settings_dialog->popup_centered(settings_vbc->get_combined_minimum_size() + Size2(50, 50)); @@ -7756,14 +7693,12 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { p->add_separator(); p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTR("View Origin")), MENU_VIEW_ORIGIN); p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid"), Key::NUMBERSIGN), MENU_VIEW_GRID); - p->add_check_shortcut(ED_SHORTCUT("spatial_editor/keep_gizmo_onscreen", TTR("Keep Gizmo On Screen"), KeyModifierMask::CMD + KeyModifierMask::ALT + Key::G), MENU_KEEP_GIZMO_ONSCREEN); p->add_separator(); p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTR("Settings...")), MENU_VIEW_CAMERA_SETTINGS); p->set_item_checked(p->get_item_index(MENU_VIEW_ORIGIN), true); p->set_item_checked(p->get_item_index(MENU_VIEW_GRID), true); - p->set_item_checked(p->get_item_index(MENU_KEEP_GIZMO_ONSCREEN), true); p->connect("id_pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed)); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index f14f8b90b9..cb7c5a714b 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -87,19 +87,6 @@ public: void set_viewport(Node3DEditorViewport *p_viewport); }; -class GizmoOffScreenLine : public Control { - GDCLASS(GizmoOffScreenLine, Control); - -public: - Vector2 point1; - Vector2 point2; - void _notification(int p_what) { - if (p_what == NOTIFICATION_DRAW && is_visible()) { - draw_line(point1, point2, Color(0.0f, 0.75f, 0.75f), 2); - } - } -}; - class Node3DEditorViewport : public Control { GDCLASS(Node3DEditorViewport, Control); friend class Node3DEditor; @@ -214,7 +201,6 @@ private: CheckBox *preview_camera; SubViewportContainer *subviewport_container; - GizmoOffScreenLine *gizmo_offscreen_line; MenuButton *view_menu; PopupMenu *display_submenu; @@ -251,7 +237,7 @@ private: }; void _update_name(); - void _compute_edit(const Point2 &p_point, const bool p_auto_center = true); + void _compute_edit(const Point2 &p_point); void _clear_selected(); void _select_clicked(bool p_allow_locked); ObjectID _select_ray(const Point2 &p_pos); @@ -423,6 +409,7 @@ private: void begin_transform(TransformMode p_mode, bool instant); void commit_transform(); void update_transform(Point2 p_mousepos, bool p_shift); + void finish_transform(); protected: void _notification(int p_what); @@ -521,35 +508,6 @@ class Node3DEditor : public VBoxContainer { public: static const unsigned int VIEWPORTS_COUNT = 4; - enum MenuOption { - MENU_GROUP_SELECTED, - MENU_KEEP_GIZMO_ONSCREEN, - MENU_LOCK_SELECTED, - MENU_SNAP_TO_FLOOR, - MENU_TOOL_LIST_SELECT, - MENU_TOOL_LOCAL_COORDS, - MENU_TOOL_MOVE, - MENU_TOOL_OVERRIDE_CAMERA, - MENU_TOOL_ROTATE, - MENU_TOOL_SCALE, - MENU_TOOL_SELECT, - MENU_TOOL_USE_SNAP, - MENU_TRANSFORM_CONFIGURE_SNAP, - MENU_TRANSFORM_DIALOG, - MENU_UNGROUP_SELECTED, - MENU_UNLOCK_SELECTED, - MENU_VIEW_CAMERA_SETTINGS, - MENU_VIEW_GIZMOS_3D_ICONS, - MENU_VIEW_GRID, - MENU_VIEW_ORIGIN, - MENU_VIEW_USE_1_VIEWPORT, - MENU_VIEW_USE_2_VIEWPORTS, - MENU_VIEW_USE_2_VIEWPORTS_ALT, - MENU_VIEW_USE_3_VIEWPORTS, - MENU_VIEW_USE_3_VIEWPORTS_ALT, - MENU_VIEW_USE_4_VIEWPORTS, - }; - enum ToolMode { TOOL_MODE_SELECT, TOOL_MODE_MOVE, @@ -568,6 +526,7 @@ public: TOOL_OPT_USE_SNAP, TOOL_OPT_OVERRIDE_CAMERA, TOOL_OPT_MAX + }; private: @@ -596,7 +555,6 @@ private: Camera3D::Projection grid_camera_last_update_perspective = Camera3D::PROJECTION_PERSPECTIVE; Vector3 grid_camera_last_update_position = Vector3(); - bool keep_gizmo_onscreen = true; Ref<ArrayMesh> move_gizmo[3], move_plane_gizmo[3], rotate_gizmo[4], scale_gizmo[3], scale_plane_gizmo[3], axis_gizmo[3]; Ref<StandardMaterial3D> gizmo_color[3]; Ref<StandardMaterial3D> plane_gizmo_color[3]; @@ -630,10 +588,37 @@ private: struct Gizmo { bool visible = false; real_t scale = 0; - Vector3 target_center; Transform3D transform; } gizmo; + enum MenuOption { + MENU_TOOL_SELECT, + MENU_TOOL_MOVE, + MENU_TOOL_ROTATE, + MENU_TOOL_SCALE, + MENU_TOOL_LIST_SELECT, + MENU_TOOL_LOCAL_COORDS, + MENU_TOOL_USE_SNAP, + MENU_TOOL_OVERRIDE_CAMERA, + MENU_TRANSFORM_CONFIGURE_SNAP, + MENU_TRANSFORM_DIALOG, + MENU_VIEW_USE_1_VIEWPORT, + MENU_VIEW_USE_2_VIEWPORTS, + MENU_VIEW_USE_2_VIEWPORTS_ALT, + MENU_VIEW_USE_3_VIEWPORTS, + MENU_VIEW_USE_3_VIEWPORTS_ALT, + MENU_VIEW_USE_4_VIEWPORTS, + MENU_VIEW_ORIGIN, + MENU_VIEW_GRID, + MENU_VIEW_GIZMOS_3D_ICONS, + MENU_VIEW_CAMERA_SETTINGS, + MENU_LOCK_SELECTED, + MENU_UNLOCK_SELECTED, + MENU_GROUP_SELECTED, + MENU_UNGROUP_SELECTED, + MENU_SNAP_TO_FLOOR + }; + Button *tool_button[TOOL_MAX]; Button *tool_option_button[TOOL_OPT_MAX]; @@ -792,10 +777,7 @@ public: float get_zfar() const { return settings_zfar->get_value(); } float get_fov() const { return settings_fov->get_value(); } - Vector3 get_gizmo_target_center() const { return gizmo.target_center; } Transform3D get_gizmo_transform() const { return gizmo.transform; } - void set_gizmo_transform(const Transform3D &p_transform) { gizmo.transform = p_transform; } - bool is_keep_gizmo_onscreen_enabled() const { return keep_gizmo_onscreen; } bool is_gizmo_visible() const; ToolMode get_tool_mode() const { return tool_mode; } diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index cfb42c0741..57e47a15fd 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -40,7 +40,6 @@ #include "core/os/os.h" #include "core/string/translation.h" #include "core/version.h" -#include "core/version_hash.gen.h" #include "editor/editor_vcs_interface.h" #include "editor_scale.h" #include "editor_settings.h" diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 0e362b13c6..a4f9563840 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1560,7 +1560,7 @@ void SceneTreeDock::perform_node_renames(Node *p_base, Map<Node *, NodePath> *p_ for (int i = 0; i < anim->get_track_count(); i++) { NodePath track_np = anim->track_get_path(i); - Node *n = root->get_node(track_np); + Node *n = root->get_node_or_null(track_np); if (!n) { continue; } |