Browse Source

Merge pull request #68091 from AThousandShips/sprite_frames_order

Improve SpriteFrameEditor frame addition ordering
Yuri Sizov 2 years ago
parent
commit
920e8067f7

+ 316 - 95
editor/plugins/sprite_frames_editor_plugin.cpp

@@ -42,6 +42,7 @@
 #include "editor/scene_tree_dock.h"
 #include "scene/gui/center_container.h"
 #include "scene/gui/margin_container.h"
+#include "scene/gui/option_button.h"
 #include "scene/gui/panel_container.h"
 #include "scene/gui/separator.h"
 
@@ -131,8 +132,14 @@ void SpriteFramesEditor::_sheet_preview_draw() {
 
 	Color accent = get_theme_color("accent_color", "Editor");
 
-	for (const int &E : frames_selected) {
-		const int idx = E;
+	_sheet_sort_frames();
+
+	Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
+	int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
+
+	for (int i = 0; i < frames_ordered.size(); ++i) {
+		const int idx = frames_ordered[i].second;
+
 		const int x = idx % frame_count.x;
 		const int y = idx / frame_count.x;
 		const Point2 pos = draw_offset + Point2(x, y) * (draw_frame_size + draw_sep);
@@ -143,6 +150,15 @@ void SpriteFramesEditor::_sheet_preview_draw() {
 		split_sheet_preview->draw_rect(Rect2(pos + Size2(3, 3), draw_frame_size - Size2(6, 6)), accent, false);
 		split_sheet_preview->draw_rect(Rect2(pos + Size2(4, 4), draw_frame_size - Size2(8, 8)), Color(0, 0, 0, 1), false);
 		split_sheet_preview->draw_rect(Rect2(pos + Size2(5, 5), draw_frame_size - Size2(10, 10)), Color(0, 0, 0, 1), false);
+
+		const String text = itos(i);
+		const Vector2 string_size = font->get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
+
+		// Stop rendering text if too large.
+		if (string_size.x + 6 < draw_frame_size.x && string_size.y / 2 + 10 < draw_frame_size.y) {
+			split_sheet_preview->draw_string_outline(font, pos + Size2(5, 7) + Size2(0, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_LEFT, string_size.x, font_size, 1, Color(0, 0, 0, 1));
+			split_sheet_preview->draw_string(font, pos + Size2(5, 7) + Size2(0, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_LEFT, string_size.x, font_size, Color(1, 1, 1));
+		}
 	}
 
 	split_sheet_dialog->get_ok_button()->set_disabled(false);
@@ -156,21 +172,24 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {
 
 		if (idx != -1) {
 			if (mb->is_shift_pressed() && last_frame_selected >= 0) {
-				//select multiple
-				int from = idx;
-				int to = last_frame_selected;
-				if (from > to) {
-					SWAP(from, to);
-				}
+				// Select multiple frames.
+				const int from = last_frame_selected;
+				const int to = idx;
+
+				const int diff = ABS(to - from);
+				const int dir = SIGN(to - from);
+
+				for (int i = 0; i <= diff; i++) {
+					const int this_idx = from + i * dir;
 
-				for (int i = from; i <= to; i++) {
 					// Prevent double-toggling the same frame when moving the mouse when the mouse button is still held.
-					frames_toggled_by_mouse_hover.insert(idx);
+					frames_toggled_by_mouse_hover.insert(this_idx);
 
 					if (mb->is_ctrl_pressed()) {
-						frames_selected.erase(i);
-					} else {
-						frames_selected.insert(i);
+						frames_selected.erase(this_idx);
+					} else if (!frames_selected.has(this_idx)) {
+						frames_selected.insert(this_idx, selected_count);
+						selected_count++;
 					}
 				}
 			} else {
@@ -180,13 +199,15 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {
 				if (frames_selected.has(idx)) {
 					frames_selected.erase(idx);
 				} else {
-					frames_selected.insert(idx);
+					frames_selected.insert(idx, selected_count);
+					selected_count++;
 				}
 			}
 		}
 
 		if (last_frame_selected != idx || idx != -1) {
 			last_frame_selected = idx;
+			frames_need_sort = true;
 			split_sheet_preview->queue_redraw();
 		}
 	}
@@ -209,13 +230,19 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {
 			if (frames_selected.has(idx)) {
 				frames_selected.erase(idx);
 			} else {
-				frames_selected.insert(idx);
+				frames_selected.insert(idx, selected_count);
+				selected_count++;
 			}
 
 			last_frame_selected = idx;
+			frames_need_sort = true;
 			split_sheet_preview->queue_redraw();
 		}
 	}
+
+	if (frames_selected.is_empty()) {
+		selected_count = 0;
+	}
 }
 
 void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) {
@@ -254,8 +281,11 @@ void SpriteFramesEditor::_sheet_add_frames() {
 	undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene());
 	int fc = frames->get_frame_count(edited_anim);
 
-	for (const int &E : frames_selected) {
-		int idx = E;
+	_sheet_sort_frames();
+
+	for (const Pair<int, int> &pair : frames_ordered) {
+		const int idx = pair.second;
+
 		const Point2 frame_coords(idx % frame_count.x, idx / frame_count.x);
 
 		Ref<AtlasTexture> at;
@@ -300,21 +330,103 @@ void SpriteFramesEditor::_sheet_zoom_reset() {
 	split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom);
 }
 
-void SpriteFramesEditor::_sheet_select_clear_all_frames() {
-	bool should_clear = true;
+void SpriteFramesEditor::_sheet_order_selected(int p_option) {
+	frames_need_sort = true;
+	split_sheet_preview->queue_redraw();
+}
+
+void SpriteFramesEditor::_sheet_select_all_frames() {
 	for (int i = 0; i < split_sheet_h->get_value() * split_sheet_v->get_value(); i++) {
 		if (!frames_selected.has(i)) {
-			frames_selected.insert(i);
-			should_clear = false;
+			frames_selected.insert(i, selected_count);
+			selected_count++;
+			frames_need_sort = true;
 		}
 	}
-	if (should_clear) {
-		frames_selected.clear();
-	}
 
 	split_sheet_preview->queue_redraw();
 }
 
+void SpriteFramesEditor::_sheet_clear_all_frames() {
+	frames_selected.clear();
+	selected_count = 0;
+
+	split_sheet_preview->queue_redraw();
+}
+
+void SpriteFramesEditor::_sheet_sort_frames() {
+	if (!frames_need_sort) {
+		return;
+	}
+	frames_need_sort = false;
+	frames_ordered.resize(frames_selected.size());
+	if (frames_selected.is_empty()) {
+		return;
+	}
+
+	const Size2i frame_count = _get_frame_count();
+	const int frame_order = split_sheet_order->get_selected_id();
+	int index = 0;
+
+	// Fill based on order.
+	for (const KeyValue<int, int> &from_pair : frames_selected) {
+		const int idx = from_pair.key;
+
+		const int selection_order = from_pair.value;
+
+		// Default to using selection order.
+		int order_by = selection_order;
+
+		// Extract coordinates for sorting.
+		const int pos_frame_x = idx % frame_count.x;
+		const int pos_frame_y = idx / frame_count.x;
+
+		const int neg_frame_x = frame_count.x - (pos_frame_x + 1);
+		const int neg_frame_y = frame_count.y - (pos_frame_y + 1);
+
+		switch (frame_order) {
+			case FRAME_ORDER_LEFT_RIGHT_TOP_BOTTOM: {
+				order_by = frame_count.x * pos_frame_y + pos_frame_x;
+			} break;
+
+			case FRAME_ORDER_LEFT_RIGHT_BOTTOM_TOP: {
+				order_by = frame_count.x * neg_frame_y + pos_frame_x;
+			} break;
+
+			case FRAME_ORDER_RIGHT_LEFT_TOP_BOTTOM: {
+				order_by = frame_count.x * pos_frame_y + neg_frame_x;
+			} break;
+
+			case FRAME_ORDER_RIGHT_LEFT_BOTTOM_TOP: {
+				order_by = frame_count.x * neg_frame_y + neg_frame_x;
+			} break;
+
+			case FRAME_ORDER_TOP_BOTTOM_LEFT_RIGHT: {
+				order_by = pos_frame_y + frame_count.y * pos_frame_x;
+			} break;
+
+			case FRAME_ORDER_TOP_BOTTOM_RIGHT_LEFT: {
+				order_by = pos_frame_y + frame_count.y * neg_frame_x;
+			} break;
+
+			case FRAME_ORDER_BOTTOM_TOP_LEFT_RIGHT: {
+				order_by = neg_frame_y + frame_count.y * pos_frame_x;
+			} break;
+
+			case FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT: {
+				order_by = neg_frame_y + frame_count.y * neg_frame_x;
+			} break;
+		}
+
+		// Assign in vector.
+		frames_ordered.set(index, Pair<int, int>(order_by, idx));
+		index++;
+	}
+
+	// Sort frames.
+	frames_ordered.sort_custom<PairSort<int, int>>();
+}
+
 void SpriteFramesEditor::_sheet_spin_changed(double p_value, int p_dominant_param) {
 	if (updating_split_settings) {
 		return;
@@ -367,10 +479,25 @@ void SpriteFramesEditor::_sheet_spin_changed(double p_value, int p_dominant_para
 	updating_split_settings = false;
 
 	frames_selected.clear();
+	selected_count = 0;
 	last_frame_selected = -1;
 	split_sheet_preview->queue_redraw();
 }
 
+void SpriteFramesEditor::_toggle_show_settings() {
+	split_sheet_settings_vb->set_visible(!split_sheet_settings_vb->is_visible());
+
+	_update_show_settings();
+}
+
+void SpriteFramesEditor::_update_show_settings() {
+	if (is_layout_rtl()) {
+		toggle_settings_button->set_icon(get_theme_icon(split_sheet_settings_vb->is_visible() ? SNAME("Back") : SNAME("Forward"), SNAME("EditorIcons")));
+	} else {
+		toggle_settings_button->set_icon(get_theme_icon(split_sheet_settings_vb->is_visible() ? SNAME("Forward") : SNAME("Back"), SNAME("EditorIcons")));
+	}
+}
+
 void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) {
 	Ref<Texture2D> texture = ResourceLoader::load(p_file);
 	if (texture.is_null()) {
@@ -378,6 +505,7 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) {
 		ERR_FAIL_COND(texture.is_null());
 	}
 	frames_selected.clear();
+	selected_count = 0;
 	last_frame_selected = -1;
 
 	bool new_texture = texture != split_sheet_preview->get_texture();
@@ -408,6 +536,7 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) {
 		// Reset zoom.
 		_sheet_zoom_reset();
 	}
+
 	split_sheet_dialog->popup_centered_ratio(0.65);
 }
 
@@ -450,6 +579,8 @@ void SpriteFramesEditor::_notification(int p_what) {
 			split_sheet_zoom_reset->set_icon(get_theme_icon(SNAME("ZoomReset"), SNAME("EditorIcons")));
 			split_sheet_zoom_in->set_icon(get_theme_icon(SNAME("ZoomMore"), SNAME("EditorIcons")));
 			split_sheet_scroll->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree")));
+
+			_update_show_settings();
 		} break;
 
 		case NOTIFICATION_READY: {
@@ -1769,82 +1900,58 @@ SpriteFramesEditor::SpriteFramesEditor() {
 
 	split_sheet_dialog = memnew(ConfirmationDialog);
 	add_child(split_sheet_dialog);
-	VBoxContainer *split_sheet_vb = memnew(VBoxContainer);
-	split_sheet_dialog->add_child(split_sheet_vb);
 	split_sheet_dialog->set_title(TTR("Select Frames"));
 	split_sheet_dialog->connect("confirmed", callable_mp(this, &SpriteFramesEditor::_sheet_add_frames));
 
 	HBoxContainer *split_sheet_hb = memnew(HBoxContainer);
+	split_sheet_dialog->add_child(split_sheet_hb);
+	split_sheet_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_hb->set_v_size_flags(SIZE_EXPAND_FILL);
 
-	split_sheet_hb->add_child(memnew(Label(TTR("Horizontal:"))));
-	split_sheet_h = memnew(SpinBox);
-	split_sheet_h->set_min(1);
-	split_sheet_h->set_max(128);
-	split_sheet_h->set_step(1);
-	split_sheet_hb->add_child(split_sheet_h);
-	split_sheet_h->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));
-
-	split_sheet_hb->add_child(memnew(Label(TTR("Vertical:"))));
-	split_sheet_v = memnew(SpinBox);
-	split_sheet_v->set_min(1);
-	split_sheet_v->set_max(128);
-	split_sheet_v->set_step(1);
-	split_sheet_hb->add_child(split_sheet_v);
-	split_sheet_v->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));
-
-	split_sheet_hb->add_child(memnew(VSeparator));
-	split_sheet_hb->add_child(memnew(Label(TTR("Size:"))));
-	split_sheet_size_x = memnew(SpinBox);
-	split_sheet_size_x->set_min(1);
-	split_sheet_size_x->set_step(1);
-	split_sheet_size_x->set_suffix("px");
-	split_sheet_size_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));
-	split_sheet_hb->add_child(split_sheet_size_x);
-	split_sheet_size_y = memnew(SpinBox);
-	split_sheet_size_y->set_min(1);
-	split_sheet_size_y->set_step(1);
-	split_sheet_size_y->set_suffix("px");
-	split_sheet_size_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));
-	split_sheet_hb->add_child(split_sheet_size_y);
-
-	split_sheet_hb->add_child(memnew(VSeparator));
-	split_sheet_hb->add_child(memnew(Label(TTR("Separation:"))));
-	split_sheet_sep_x = memnew(SpinBox);
-	split_sheet_sep_x->set_min(0);
-	split_sheet_sep_x->set_step(1);
-	split_sheet_sep_x->set_suffix("px");
-	split_sheet_sep_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
-	split_sheet_hb->add_child(split_sheet_sep_x);
-	split_sheet_sep_y = memnew(SpinBox);
-	split_sheet_sep_y->set_min(0);
-	split_sheet_sep_y->set_step(1);
-	split_sheet_sep_y->set_suffix("px");
-	split_sheet_sep_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
-	split_sheet_hb->add_child(split_sheet_sep_y);
-
-	split_sheet_hb->add_child(memnew(VSeparator));
-	split_sheet_hb->add_child(memnew(Label(TTR("Offset:"))));
-	split_sheet_offset_x = memnew(SpinBox);
-	split_sheet_offset_x->set_min(0);
-	split_sheet_offset_x->set_step(1);
-	split_sheet_offset_x->set_suffix("px");
-	split_sheet_offset_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
-	split_sheet_hb->add_child(split_sheet_offset_x);
-	split_sheet_offset_y = memnew(SpinBox);
-	split_sheet_offset_y->set_min(0);
-	split_sheet_offset_y->set_step(1);
-	split_sheet_offset_y->set_suffix("px");
-	split_sheet_offset_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
-	split_sheet_hb->add_child(split_sheet_offset_y);
-
-	split_sheet_hb->add_spacer();
-
-	Button *select_clear_all = memnew(Button);
-	select_clear_all->set_text(TTR("Select/Clear All Frames"));
-	select_clear_all->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_select_clear_all_frames));
-	split_sheet_hb->add_child(select_clear_all);
-
-	split_sheet_vb->add_child(split_sheet_hb);
+	VBoxContainer *split_sheet_vb = memnew(VBoxContainer);
+	split_sheet_hb->add_child(split_sheet_vb);
+	split_sheet_vb->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_vb->set_v_size_flags(SIZE_EXPAND_FILL);
+
+	HBoxContainer *split_sheet_menu_hb = memnew(HBoxContainer);
+
+	split_sheet_menu_hb->add_child(memnew(Label(TTR("Frame Order"))));
+
+	split_sheet_order = memnew(OptionButton);
+	split_sheet_order->add_item(TTR("As Selected"), FRAME_ORDER_SELECTION);
+	split_sheet_order->add_separator(TTR("By Row"));
+	split_sheet_order->add_item(TTR("Left to Right, Top to Bottom"), FRAME_ORDER_LEFT_RIGHT_TOP_BOTTOM);
+	split_sheet_order->add_item(TTR("Left to Right, Bottom to Top"), FRAME_ORDER_LEFT_RIGHT_BOTTOM_TOP);
+	split_sheet_order->add_item(TTR("Right to Left, Top to Bottom"), FRAME_ORDER_RIGHT_LEFT_TOP_BOTTOM);
+	split_sheet_order->add_item(TTR("Right to Left, Bottom to Top"), FRAME_ORDER_RIGHT_LEFT_BOTTOM_TOP);
+	split_sheet_order->add_separator(TTR("By Column"));
+	split_sheet_order->add_item(TTR("Top to Bottom, Left to Right"), FRAME_ORDER_TOP_BOTTOM_LEFT_RIGHT);
+	split_sheet_order->add_item(TTR("Top to Bottom, Right to Left"), FRAME_ORDER_TOP_BOTTOM_RIGHT_LEFT);
+	split_sheet_order->add_item(TTR("Bottom to Top, Left to Right"), FRAME_ORDER_BOTTOM_TOP_LEFT_RIGHT);
+	split_sheet_order->add_item(TTR("Bottom to Top, Right to Left"), FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT);
+	split_sheet_order->connect("item_selected", callable_mp(this, &SpriteFramesEditor::_sheet_order_selected));
+	split_sheet_menu_hb->add_child(split_sheet_order);
+
+	Button *select_all = memnew(Button);
+	select_all->set_text(TTR("Select All"));
+	select_all->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_select_all_frames));
+	split_sheet_menu_hb->add_child(select_all);
+
+	Button *clear_all = memnew(Button);
+	clear_all->set_text(TTR("Select None"));
+	clear_all->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_clear_all_frames));
+	split_sheet_menu_hb->add_child(clear_all);
+
+	split_sheet_menu_hb->add_spacer();
+
+	toggle_settings_button = memnew(Button);
+	toggle_settings_button->set_h_size_flags(SIZE_SHRINK_END);
+	toggle_settings_button->set_flat(true);
+	toggle_settings_button->connect("pressed", callable_mp(this, &SpriteFramesEditor::_toggle_show_settings));
+	toggle_settings_button->set_tooltip_text(TTR("Toggle Settings Panel"));
+	split_sheet_menu_hb->add_child(toggle_settings_button);
+
+	split_sheet_vb->add_child(split_sheet_menu_hb);
 
 	PanelContainer *split_sheet_panel = memnew(PanelContainer);
 	split_sheet_panel->set_h_size_flags(SIZE_EXPAND_FILL);
@@ -1896,6 +2003,120 @@ SpriteFramesEditor::SpriteFramesEditor() {
 	split_sheet_zoom_in->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_in));
 	split_sheet_zoom_hb->add_child(split_sheet_zoom_in);
 
+	split_sheet_settings_vb = memnew(VBoxContainer);
+	split_sheet_settings_vb->set_v_size_flags(SIZE_EXPAND_FILL);
+
+	HBoxContainer *split_sheet_h_hb = memnew(HBoxContainer);
+	split_sheet_h_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+
+	Label *split_sheet_h_label = memnew(Label(TTR("Horizontal")));
+	split_sheet_h_label->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_h_hb->add_child(split_sheet_h_label);
+
+	split_sheet_h = memnew(SpinBox);
+	split_sheet_h->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_h->set_min(1);
+	split_sheet_h->set_max(128);
+	split_sheet_h->set_step(1);
+	split_sheet_h_hb->add_child(split_sheet_h);
+	split_sheet_h->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));
+	split_sheet_settings_vb->add_child(split_sheet_h_hb);
+
+	HBoxContainer *split_sheet_v_hb = memnew(HBoxContainer);
+	split_sheet_v_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+
+	Label *split_sheet_v_label = memnew(Label(TTR("Vertical")));
+	split_sheet_v_label->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_v_hb->add_child(split_sheet_v_label);
+
+	split_sheet_v = memnew(SpinBox);
+	split_sheet_v->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_v->set_min(1);
+	split_sheet_v->set_max(128);
+	split_sheet_v->set_step(1);
+	split_sheet_v_hb->add_child(split_sheet_v);
+	split_sheet_v->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));
+	split_sheet_settings_vb->add_child(split_sheet_v_hb);
+
+	HBoxContainer *split_sheet_size_hb = memnew(HBoxContainer);
+	split_sheet_size_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+
+	Label *split_sheet_size_label = memnew(Label(TTR("Size")));
+	split_sheet_size_label->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_size_label->set_v_size_flags(SIZE_SHRINK_BEGIN);
+	split_sheet_size_hb->add_child(split_sheet_size_label);
+
+	VBoxContainer *split_sheet_size_vb = memnew(VBoxContainer);
+	split_sheet_size_vb->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_size_x = memnew(SpinBox);
+	split_sheet_size_x->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_size_x->set_min(1);
+	split_sheet_size_x->set_step(1);
+	split_sheet_size_x->set_suffix("px");
+	split_sheet_size_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));
+	split_sheet_size_vb->add_child(split_sheet_size_x);
+	split_sheet_size_y = memnew(SpinBox);
+	split_sheet_size_y->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_size_y->set_min(1);
+	split_sheet_size_y->set_step(1);
+	split_sheet_size_y->set_suffix("px");
+	split_sheet_size_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));
+	split_sheet_size_vb->add_child(split_sheet_size_y);
+	split_sheet_size_hb->add_child(split_sheet_size_vb);
+	split_sheet_settings_vb->add_child(split_sheet_size_hb);
+
+	HBoxContainer *split_sheet_sep_hb = memnew(HBoxContainer);
+	split_sheet_sep_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+
+	Label *split_sheet_sep_label = memnew(Label(TTR("Separation")));
+	split_sheet_sep_label->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_sep_label->set_v_size_flags(SIZE_SHRINK_BEGIN);
+	split_sheet_sep_hb->add_child(split_sheet_sep_label);
+
+	VBoxContainer *split_sheet_sep_vb = memnew(VBoxContainer);
+	split_sheet_sep_vb->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_sep_x = memnew(SpinBox);
+	split_sheet_sep_x->set_min(0);
+	split_sheet_sep_x->set_step(1);
+	split_sheet_sep_x->set_suffix("px");
+	split_sheet_sep_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
+	split_sheet_sep_vb->add_child(split_sheet_sep_x);
+	split_sheet_sep_y = memnew(SpinBox);
+	split_sheet_sep_y->set_min(0);
+	split_sheet_sep_y->set_step(1);
+	split_sheet_sep_y->set_suffix("px");
+	split_sheet_sep_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
+	split_sheet_sep_vb->add_child(split_sheet_sep_y);
+	split_sheet_sep_hb->add_child(split_sheet_sep_vb);
+	split_sheet_settings_vb->add_child(split_sheet_sep_hb);
+
+	HBoxContainer *split_sheet_offset_hb = memnew(HBoxContainer);
+	split_sheet_offset_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+
+	Label *split_sheet_offset_label = memnew(Label(TTR("Offset")));
+	split_sheet_offset_label->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_offset_label->set_v_size_flags(SIZE_SHRINK_BEGIN);
+	split_sheet_offset_hb->add_child(split_sheet_offset_label);
+
+	VBoxContainer *split_sheet_offset_vb = memnew(VBoxContainer);
+	split_sheet_offset_vb->set_h_size_flags(SIZE_EXPAND_FILL);
+	split_sheet_offset_x = memnew(SpinBox);
+	split_sheet_offset_x->set_min(0);
+	split_sheet_offset_x->set_step(1);
+	split_sheet_offset_x->set_suffix("px");
+	split_sheet_offset_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
+	split_sheet_offset_vb->add_child(split_sheet_offset_x);
+	split_sheet_offset_y = memnew(SpinBox);
+	split_sheet_offset_y->set_min(0);
+	split_sheet_offset_y->set_step(1);
+	split_sheet_offset_y->set_suffix("px");
+	split_sheet_offset_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
+	split_sheet_offset_vb->add_child(split_sheet_offset_y);
+	split_sheet_offset_hb->add_child(split_sheet_offset_vb);
+	split_sheet_settings_vb->add_child(split_sheet_offset_hb);
+
+	split_sheet_hb->add_child(split_sheet_settings_vb);
+
 	file_split_sheet = memnew(EditorFileDialog);
 	file_split_sheet->set_title(TTR("Create Frames from Sprite Sheet"));
 	file_split_sheet->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);

+ 30 - 2
editor/plugins/sprite_frames_editor_plugin.h

@@ -45,6 +45,7 @@
 #include "scene/gui/texture_rect.h"
 #include "scene/gui/tree.h"
 
+class OptionButton;
 class EditorFileDialog;
 
 class EditorSpriteFramesFrame : public Resource {
@@ -68,6 +69,22 @@ class SpriteFramesEditor : public HSplitContainer {
 	};
 	int dominant_param = PARAM_FRAME_COUNT;
 
+	enum {
+		FRAME_ORDER_SELECTION, // Order frames were selected in.
+
+		// By Row.
+		FRAME_ORDER_LEFT_RIGHT_TOP_BOTTOM,
+		FRAME_ORDER_LEFT_RIGHT_BOTTOM_TOP,
+		FRAME_ORDER_RIGHT_LEFT_TOP_BOTTOM,
+		FRAME_ORDER_RIGHT_LEFT_BOTTOM_TOP,
+
+		// By Column.
+		FRAME_ORDER_TOP_BOTTOM_LEFT_RIGHT,
+		FRAME_ORDER_TOP_BOTTOM_RIGHT_LEFT,
+		FRAME_ORDER_BOTTOM_TOP_LEFT_RIGHT,
+		FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT,
+	};
+
 	bool read_only = false;
 
 	Ref<Texture2D> autoplay_icon;
@@ -121,6 +138,7 @@ class SpriteFramesEditor : public HSplitContainer {
 	ConfirmationDialog *split_sheet_dialog = nullptr;
 	ScrollContainer *split_sheet_scroll = nullptr;
 	TextureRect *split_sheet_preview = nullptr;
+	VBoxContainer *split_sheet_settings_vb = nullptr;
 	SpinBox *split_sheet_h = nullptr;
 	SpinBox *split_sheet_v = nullptr;
 	SpinBox *split_sheet_size_x = nullptr;
@@ -132,9 +150,14 @@ class SpriteFramesEditor : public HSplitContainer {
 	Button *split_sheet_zoom_out = nullptr;
 	Button *split_sheet_zoom_reset = nullptr;
 	Button *split_sheet_zoom_in = nullptr;
+	Button *toggle_settings_button = nullptr;
+	OptionButton *split_sheet_order = nullptr;
 	EditorFileDialog *file_split_sheet = nullptr;
-	HashSet<int> frames_selected;
+	HashMap<int, int> frames_selected; // Key is frame index. Value is selection order.
 	HashSet<int> frames_toggled_by_mouse_hover;
+	Vector<Pair<int, int>> frames_ordered; // First is the index to be ordered by. Second is the actual frame index.
+	int selected_count = 0;
+	bool frames_need_sort = false;
 	int last_frame_selected = 0;
 
 	float scale_ratio;
@@ -206,7 +229,12 @@ class SpriteFramesEditor : public HSplitContainer {
 	void _sheet_zoom_in();
 	void _sheet_zoom_out();
 	void _sheet_zoom_reset();
-	void _sheet_select_clear_all_frames();
+	void _sheet_order_selected(int p_option);
+	void _sheet_select_all_frames();
+	void _sheet_clear_all_frames();
+	void _sheet_sort_frames();
+	void _toggle_show_settings();
+	void _update_show_settings();
 
 	void _edit();
 	void _regist_scene_undo(EditorUndoRedoManager *undo_redo);