瀏覽代碼

Merge pull request #51039 from nekomatata/layer-grid-32

Refactor layer property editor grid
Rémi Verschelde 4 年之前
父節點
當前提交
c17a541650
共有 3 個文件被更改,包括 373 次插入68 次删除
  1. 144 0
      doc/classes/ProjectSettings.xml
  2. 225 67
      editor/editor_properties.cpp
  3. 4 1
      scene/register_scene_types.cpp

+ 144 - 0
doc/classes/ProjectSettings.xml

@@ -786,9 +786,45 @@
 		<member name="layer_names/2d_navigation/layer_2" type="String" setter="" getter="" default="&quot;&quot;">
 		<member name="layer_names/2d_navigation/layer_2" type="String" setter="" getter="" default="&quot;&quot;">
 			Optional name for the 2D navigation layer 2. If left empty, the layer will display as "Layer 2".
 			Optional name for the 2D navigation layer 2. If left empty, the layer will display as "Layer 2".
 		</member>
 		</member>
+		<member name="layer_names/2d_navigation/layer_20" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D navigation layer 20. If left empty, the layer will display as "Layer 20".
+		</member>
+		<member name="layer_names/2d_navigation/layer_21" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D navigation layer 21. If left empty, the layer will display as "Layer 21".
+		</member>
+		<member name="layer_names/2d_navigation/layer_22" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D navigation layer 22. If left empty, the layer will display as "Layer 22".
+		</member>
+		<member name="layer_names/2d_navigation/layer_23" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D navigation layer 23. If left empty, the layer will display as "Layer 23".
+		</member>
+		<member name="layer_names/2d_navigation/layer_24" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D navigation layer 24. If left empty, the layer will display as "Layer 24".
+		</member>
+		<member name="layer_names/2d_navigation/layer_25" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D navigation layer 25. If left empty, the layer will display as "Layer 25".
+		</member>
+		<member name="layer_names/2d_navigation/layer_26" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D navigation layer 26. If left empty, the layer will display as "Layer 26".
+		</member>
+		<member name="layer_names/2d_navigation/layer_27" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D navigation layer 27. If left empty, the layer will display as "Layer 27".
+		</member>
+		<member name="layer_names/2d_navigation/layer_28" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D navigation layer 28. If left empty, the layer will display as "Layer 28".
+		</member>
+		<member name="layer_names/2d_navigation/layer_29" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D navigation layer 29. If left empty, the layer will display as "Layer 29".
+		</member>
 		<member name="layer_names/2d_navigation/layer_3" type="String" setter="" getter="" default="&quot;&quot;">
 		<member name="layer_names/2d_navigation/layer_3" type="String" setter="" getter="" default="&quot;&quot;">
 			Optional name for the 2D navigation layer 3. If left empty, the layer will display as "Layer 3".
 			Optional name for the 2D navigation layer 3. If left empty, the layer will display as "Layer 3".
 		</member>
 		</member>
+		<member name="layer_names/2d_navigation/layer_30" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D navigation layer 30. If left empty, the layer will display as "Layer 30".
+		</member>
+		<member name="layer_names/2d_navigation/layer_31" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D navigation layer 31. If left empty, the layer will display as "Layer 31".
+		</member>
 		<member name="layer_names/2d_navigation/layer_4" type="String" setter="" getter="" default="&quot;&quot;">
 		<member name="layer_names/2d_navigation/layer_4" type="String" setter="" getter="" default="&quot;&quot;">
 			Optional name for the 2D navigation layer 4. If left empty, the layer will display as "Layer 4".
 			Optional name for the 2D navigation layer 4. If left empty, the layer will display as "Layer 4".
 		</member>
 		</member>
@@ -846,9 +882,45 @@
 		<member name="layer_names/2d_physics/layer_2" type="String" setter="" getter="" default="&quot;&quot;">
 		<member name="layer_names/2d_physics/layer_2" type="String" setter="" getter="" default="&quot;&quot;">
 			Optional name for the 2D physics layer 2. If left empty, the layer will display as "Layer 2".
 			Optional name for the 2D physics layer 2. If left empty, the layer will display as "Layer 2".
 		</member>
 		</member>
+		<member name="layer_names/2d_physics/layer_20" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D physics layer 20. If left empty, the layer will display as "Layer 20".
+		</member>
+		<member name="layer_names/2d_physics/layer_21" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D physics layer 21. If left empty, the layer will display as "Layer 21".
+		</member>
+		<member name="layer_names/2d_physics/layer_22" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D physics layer 22. If left empty, the layer will display as "Layer 22".
+		</member>
+		<member name="layer_names/2d_physics/layer_23" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D physics layer 23. If left empty, the layer will display as "Layer 23".
+		</member>
+		<member name="layer_names/2d_physics/layer_24" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D physics layer 24. If left empty, the layer will display as "Layer 24".
+		</member>
+		<member name="layer_names/2d_physics/layer_25" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D physics layer 25. If left empty, the layer will display as "Layer 25".
+		</member>
+		<member name="layer_names/2d_physics/layer_26" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D physics layer 26. If left empty, the layer will display as "Layer 26".
+		</member>
+		<member name="layer_names/2d_physics/layer_27" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D physics layer 27. If left empty, the layer will display as "Layer 27".
+		</member>
+		<member name="layer_names/2d_physics/layer_28" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D physics layer 28. If left empty, the layer will display as "Layer 28".
+		</member>
+		<member name="layer_names/2d_physics/layer_29" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D physics layer 29. If left empty, the layer will display as "Layer 29".
+		</member>
 		<member name="layer_names/2d_physics/layer_3" type="String" setter="" getter="" default="&quot;&quot;">
 		<member name="layer_names/2d_physics/layer_3" type="String" setter="" getter="" default="&quot;&quot;">
 			Optional name for the 2D physics layer 3. If left empty, the layer will display as "Layer 3".
 			Optional name for the 2D physics layer 3. If left empty, the layer will display as "Layer 3".
 		</member>
 		</member>
+		<member name="layer_names/2d_physics/layer_30" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D physics layer 30. If left empty, the layer will display as "Layer 30".
+		</member>
+		<member name="layer_names/2d_physics/layer_31" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 2D physics layer 31. If left empty, the layer will display as "Layer 31".
+		</member>
 		<member name="layer_names/2d_physics/layer_4" type="String" setter="" getter="" default="&quot;&quot;">
 		<member name="layer_names/2d_physics/layer_4" type="String" setter="" getter="" default="&quot;&quot;">
 			Optional name for the 2D physics layer 4. If left empty, the layer will display as "Layer 4".
 			Optional name for the 2D physics layer 4. If left empty, the layer will display as "Layer 4".
 		</member>
 		</member>
@@ -966,9 +1038,45 @@
 		<member name="layer_names/3d_navigation/layer_2" type="String" setter="" getter="" default="&quot;&quot;">
 		<member name="layer_names/3d_navigation/layer_2" type="String" setter="" getter="" default="&quot;&quot;">
 			Optional name for the 3D navigation layer 2. If left empty, the layer will display as "Layer 2".
 			Optional name for the 3D navigation layer 2. If left empty, the layer will display as "Layer 2".
 		</member>
 		</member>
+		<member name="layer_names/3d_navigation/layer_20" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D navigation layer 20. If left empty, the layer will display as "Layer 20".
+		</member>
+		<member name="layer_names/3d_navigation/layer_21" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D navigation layer 21. If left empty, the layer will display as "Layer 21".
+		</member>
+		<member name="layer_names/3d_navigation/layer_22" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D navigation layer 22. If left empty, the layer will display as "Layer 22".
+		</member>
+		<member name="layer_names/3d_navigation/layer_23" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D navigation layer 23. If left empty, the layer will display as "Layer 23".
+		</member>
+		<member name="layer_names/3d_navigation/layer_24" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D navigation layer 24. If left empty, the layer will display as "Layer 24".
+		</member>
+		<member name="layer_names/3d_navigation/layer_25" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D navigation layer 25. If left empty, the layer will display as "Layer 25".
+		</member>
+		<member name="layer_names/3d_navigation/layer_26" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D navigation layer 26. If left empty, the layer will display as "Layer 26".
+		</member>
+		<member name="layer_names/3d_navigation/layer_27" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D navigation layer 27. If left empty, the layer will display as "Layer 27".
+		</member>
+		<member name="layer_names/3d_navigation/layer_28" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D navigation layer 28. If left empty, the layer will display as "Layer 28".
+		</member>
+		<member name="layer_names/3d_navigation/layer_29" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D navigation layer 29. If left empty, the layer will display as "Layer 29".
+		</member>
 		<member name="layer_names/3d_navigation/layer_3" type="String" setter="" getter="" default="&quot;&quot;">
 		<member name="layer_names/3d_navigation/layer_3" type="String" setter="" getter="" default="&quot;&quot;">
 			Optional name for the 3D navigation layer 3. If left empty, the layer will display as "Layer 3".
 			Optional name for the 3D navigation layer 3. If left empty, the layer will display as "Layer 3".
 		</member>
 		</member>
+		<member name="layer_names/3d_navigation/layer_30" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D navigation layer 30. If left empty, the layer will display as "Layer 30".
+		</member>
+		<member name="layer_names/3d_navigation/layer_31" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D navigation layer 31. If left empty, the layer will display as "Layer 31".
+		</member>
 		<member name="layer_names/3d_navigation/layer_4" type="String" setter="" getter="" default="&quot;&quot;">
 		<member name="layer_names/3d_navigation/layer_4" type="String" setter="" getter="" default="&quot;&quot;">
 			Optional name for the 3D navigation layer 4. If left empty, the layer will display as "Layer 4".
 			Optional name for the 3D navigation layer 4. If left empty, the layer will display as "Layer 4".
 		</member>
 		</member>
@@ -1026,9 +1134,45 @@
 		<member name="layer_names/3d_physics/layer_2" type="String" setter="" getter="" default="&quot;&quot;">
 		<member name="layer_names/3d_physics/layer_2" type="String" setter="" getter="" default="&quot;&quot;">
 			Optional name for the 3D physics layer 2. If left empty, the layer will display as "Layer 2".
 			Optional name for the 3D physics layer 2. If left empty, the layer will display as "Layer 2".
 		</member>
 		</member>
+		<member name="layer_names/3d_physics/layer_20" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D physics layer 20. If left empty, the layer will display as "Layer 20".
+		</member>
+		<member name="layer_names/3d_physics/layer_21" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D physics layer 21. If left empty, the layer will display as "Layer 21".
+		</member>
+		<member name="layer_names/3d_physics/layer_22" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D physics layer 22. If left empty, the layer will display as "Layer 22".
+		</member>
+		<member name="layer_names/3d_physics/layer_23" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D physics layer 23. If left empty, the layer will display as "Layer 23".
+		</member>
+		<member name="layer_names/3d_physics/layer_24" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D physics layer 24. If left empty, the layer will display as "Layer 24".
+		</member>
+		<member name="layer_names/3d_physics/layer_25" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D physics layer 25. If left empty, the layer will display as "Layer 25".
+		</member>
+		<member name="layer_names/3d_physics/layer_26" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D physics layer 26. If left empty, the layer will display as "Layer 26".
+		</member>
+		<member name="layer_names/3d_physics/layer_27" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D physics layer 27. If left empty, the layer will display as "Layer 27".
+		</member>
+		<member name="layer_names/3d_physics/layer_28" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D physics layer 28. If left empty, the layer will display as "Layer 28".
+		</member>
+		<member name="layer_names/3d_physics/layer_29" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D physics layer 29. If left empty, the layer will display as "Layer 29".
+		</member>
 		<member name="layer_names/3d_physics/layer_3" type="String" setter="" getter="" default="&quot;&quot;">
 		<member name="layer_names/3d_physics/layer_3" type="String" setter="" getter="" default="&quot;&quot;">
 			Optional name for the 3D physics layer 3. If left empty, the layer will display as "Layer 3".
 			Optional name for the 3D physics layer 3. If left empty, the layer will display as "Layer 3".
 		</member>
 		</member>
+		<member name="layer_names/3d_physics/layer_30" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D physics layer 30. If left empty, the layer will display as "Layer 30".
+		</member>
+		<member name="layer_names/3d_physics/layer_31" type="String" setter="" getter="" default="&quot;&quot;">
+			Optional name for the 3D physics layer 31. If left empty, the layer will display as "Layer 31".
+		</member>
 		<member name="layer_names/3d_physics/layer_4" type="String" setter="" getter="" default="&quot;&quot;">
 		<member name="layer_names/3d_physics/layer_4" type="String" setter="" getter="" default="&quot;&quot;">
 			Optional name for the 3D physics layer 4. If left empty, the layer will display as "Layer 4".
 			Optional name for the 3D physics layer 4. If left empty, the layer will display as "Layer 4".
 		</member>
 		</member>

+ 225 - 67
editor/editor_properties.cpp

@@ -691,17 +691,39 @@ EditorPropertyFlags::EditorPropertyFlags() {
 class EditorPropertyLayersGrid : public Control {
 class EditorPropertyLayersGrid : public Control {
 	GDCLASS(EditorPropertyLayersGrid, Control);
 	GDCLASS(EditorPropertyLayersGrid, Control);
 
 
-public:
-	uint32_t value;
+private:
 	Vector<Rect2> flag_rects;
 	Vector<Rect2> flag_rects;
+	Rect2 expand_rect;
+	bool expand_hovered = false;
+	bool expanded = false;
+	int expansion_rows = 0;
+	int hovered_index = -1;
+
+	Size2 get_grid_size() const {
+		Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
+		int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
+		return Vector2(0, font->get_height(font_size) * 3);
+	}
+
+public:
+	uint32_t value = 0;
+	int layer_group_size = 0;
+	int layer_count = 0;
 	Vector<String> names;
 	Vector<String> names;
 	Vector<String> tooltips;
 	Vector<String> tooltips;
-	int hovered_index;
 
 
 	virtual Size2 get_minimum_size() const override {
 	virtual Size2 get_minimum_size() const override {
-		Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
-		int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Label"));
-		return Vector2(0, font->get_height(font_size) * 2);
+		Size2 min_size = get_grid_size();
+
+		// Add extra rows when expanded.
+		if (expanded) {
+			const int bsize = (min_size.height * 80 / 100) / 2;
+			for (int i = 0; i < expansion_rows; ++i) {
+				min_size.y += 2 * (bsize + 1) + 3;
+			}
+		}
+
+		return min_size;
 	}
 	}
 
 
 	virtual String get_tooltip(const Point2 &p_pos) const override {
 	virtual String get_tooltip(const Point2 &p_pos) const override {
@@ -712,80 +734,192 @@ public:
 		}
 		}
 		return String();
 		return String();
 	}
 	}
+
 	void _gui_input(const Ref<InputEvent> &p_ev) {
 	void _gui_input(const Ref<InputEvent> &p_ev) {
 		const Ref<InputEventMouseMotion> mm = p_ev;
 		const Ref<InputEventMouseMotion> mm = p_ev;
-
 		if (mm.is_valid()) {
 		if (mm.is_valid()) {
-			for (int i = 0; i < flag_rects.size(); i++) {
-				if (flag_rects[i].has_point(mm->get_position())) {
-					// Used to highlight the hovered flag in the layers grid.
-					hovered_index = i;
-					update();
-					break;
+			bool expand_was_hovered = expand_hovered;
+			expand_hovered = expand_rect.has_point(mm->get_position());
+			if (expand_hovered != expand_was_hovered) {
+				update();
+			}
+
+			if (!expand_hovered) {
+				for (int i = 0; i < flag_rects.size(); i++) {
+					if (flag_rects[i].has_point(mm->get_position())) {
+						// Used to highlight the hovered flag in the layers grid.
+						hovered_index = i;
+						update();
+						return;
+					}
 				}
 				}
 			}
 			}
+
+			// Remove highlight when no square is hovered.
+			if (hovered_index != -1) {
+				hovered_index = -1;
+				update();
+			}
+
+			return;
 		}
 		}
 
 
 		const Ref<InputEventMouseButton> mb = p_ev;
 		const Ref<InputEventMouseButton> mb = p_ev;
+		if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) {
+			if (hovered_index >= 0) {
+				// Toggle the flag.
+				// We base our choice on the hovered flag, so that it always matches the hovered flag.
+				if (value & (1 << hovered_index)) {
+					value &= ~(1 << hovered_index);
+				} else {
+					value |= (1 << hovered_index);
+				}
 
 
-		if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed() && hovered_index >= 0) {
-			// Toggle the flag.
-			// We base our choice on the hovered flag, so that it always matches the hovered flag.
-			if (value & (1 << hovered_index)) {
-				value &= ~(1 << hovered_index);
-			} else {
-				value |= (1 << hovered_index);
+				emit_signal(SNAME("flag_changed"), value);
+				update();
+			} else if (expand_hovered) {
+				expanded = !expanded;
+				minimum_size_changed();
+				update();
 			}
 			}
-
-			emit_signal(SNAME("flag_changed"), value);
-			update();
 		}
 		}
 	}
 	}
 
 
 	void _notification(int p_what) {
 	void _notification(int p_what) {
 		switch (p_what) {
 		switch (p_what) {
 			case NOTIFICATION_DRAW: {
 			case NOTIFICATION_DRAW: {
-				Rect2 rect;
-				rect.size = get_size();
+				Size2 grid_size = get_grid_size();
+				grid_size.x = get_size().x;
+
 				flag_rects.clear();
 				flag_rects.clear();
 
 
-				const int bsize = (rect.size.height * 80 / 100) / 2;
+				int prev_expansion_rows = expansion_rows;
+				expansion_rows = 0;
+
+				const int bsize = (grid_size.height * 80 / 100) / 2;
 				const int h = bsize * 2 + 1;
 				const int h = bsize * 2 + 1;
-				const int vofs = (rect.size.height - h) / 2;
 
 
 				Color color = get_theme_color(SNAME("highlight_color"), SNAME("Editor"));
 				Color color = get_theme_color(SNAME("highlight_color"), SNAME("Editor"));
-				for (int i = 0; i < 2; i++) {
-					Point2 ofs(4, vofs);
-					if (i == 1) {
+
+				Color text_color = get_theme_color(SNAME("font_color"), SNAME("Editor"));
+				text_color.a *= 0.5;
+
+				Color text_color_on = get_theme_color(SNAME("font_hover_color"), SNAME("Editor"));
+				text_color_on.a *= 0.7;
+
+				const int vofs = (grid_size.height - h) / 2;
+
+				int layer_index = 0;
+				int block_index = 0;
+
+				Point2 arrow_pos;
+
+				Point2 block_ofs(4, vofs);
+
+				while (true) {
+					Point2 ofs = block_ofs;
+
+					for (int i = 0; i < 2; i++) {
+						for (int j = 0; j < layer_group_size; j++) {
+							const bool on = value & (1 << layer_index);
+							Rect2 rect2 = Rect2(ofs, Size2(bsize, bsize));
+
+							color.a = on ? 0.6 : 0.2;
+							if (layer_index == hovered_index) {
+								// Add visual feedback when hovering a flag.
+								color.a += 0.15;
+							}
+
+							draw_rect(rect2, color);
+							flag_rects.push_back(rect2);
+
+							Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Label"));
+							Vector2 offset;
+							offset.y = rect2.size.y * 0.75;
+
+							draw_string(font, rect2.position + offset, itos(layer_index), HALIGN_CENTER, rect2.size.x, -1, on ? text_color_on : text_color);
+
+							ofs.x += bsize + 1;
+
+							++layer_index;
+						}
+
+						ofs.x = block_ofs.x;
 						ofs.y += bsize + 1;
 						ofs.y += bsize + 1;
 					}
 					}
 
 
-					ofs += rect.position;
-					for (int j = 0; j < 10; j++) {
-						Point2 o = ofs + Point2(j * (bsize + 1), 0);
-						if (j >= 5) {
-							o.x += 1;
+					if (layer_index >= layer_count) {
+						if (!flag_rects.is_empty() && (expansion_rows == 0)) {
+							const Rect2 &last_rect = flag_rects[flag_rects.size() - 1];
+							arrow_pos = last_rect.position + last_rect.size;
 						}
 						}
+						break;
+					}
 
 
-						const int idx = i * 10 + j;
-						const bool on = value & (1 << idx);
-						Rect2 rect2 = Rect2(o, Size2(bsize, bsize));
+					int block_size_x = layer_group_size * (bsize + 1);
+					block_ofs.x += block_size_x + 3;
 
 
-						color.a = on ? 0.6 : 0.2;
-						if (idx == hovered_index) {
-							// Add visual feedback when hovering a flag.
-							color.a += 0.15;
+					if (block_ofs.x + block_size_x + 12 > grid_size.width) {
+						// Keep last valid cell position for the expansion icon.
+						if (!flag_rects.is_empty() && (expansion_rows == 0)) {
+							const Rect2 &last_rect = flag_rects[flag_rects.size() - 1];
+							arrow_pos = last_rect.position + last_rect.size;
+						}
+						++expansion_rows;
+
+						if (expanded) {
+							// Expand grid to next line.
+							block_ofs.x = 4;
+							block_ofs.y += 2 * (bsize + 1) + 3;
+						} else {
+							// Skip remaining blocks.
+							break;
 						}
 						}
-
-						draw_rect(rect2, color);
-						flag_rects.push_back(rect2);
 					}
 					}
+
+					++block_index;
 				}
 				}
+
+				if ((expansion_rows != prev_expansion_rows) && expanded) {
+					minimum_size_changed();
+				}
+
+				if ((expansion_rows == 0) && (layer_index == layer_count)) {
+					// Whole grid was drawn, no need for expansion icon.
+					break;
+				}
+
+				Ref<Texture2D> arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree"));
+				ERR_FAIL_COND(arrow.is_null());
+
+				Color arrow_color = get_theme_color(SNAME("highlight_color"), SNAME("Editor"));
+				arrow_color.a = expand_hovered ? 1.0 : 0.6;
+
+				arrow_pos.x += 2.0;
+				arrow_pos.y -= arrow->get_height();
+
+				Rect2 arrow_draw_rect(arrow_pos, arrow->get_size());
+				expand_rect = arrow_draw_rect;
+				if (expanded) {
+					arrow_draw_rect.size.y *= -1.0; // Flip arrow vertically when expanded.
+				}
+
+				RID ci = get_canvas_item();
+				arrow->draw_rect(ci, arrow_draw_rect, false, arrow_color);
+
 			} break;
 			} break;
+
 			case NOTIFICATION_MOUSE_EXIT: {
 			case NOTIFICATION_MOUSE_EXIT: {
-				hovered_index = -1;
-				update();
+				if (expand_hovered) {
+					expand_hovered = false;
+					update();
+				}
+				if (hovered_index != -1) {
+					hovered_index = -1;
+					update();
+				}
 			} break;
 			} break;
+
 			default:
 			default:
 				break;
 				break;
 		}
 		}
@@ -800,12 +934,8 @@ public:
 		ClassDB::bind_method(D_METHOD("_gui_input"), &EditorPropertyLayersGrid::_gui_input);
 		ClassDB::bind_method(D_METHOD("_gui_input"), &EditorPropertyLayersGrid::_gui_input);
 		ADD_SIGNAL(MethodInfo("flag_changed", PropertyInfo(Variant::INT, "flag")));
 		ADD_SIGNAL(MethodInfo("flag_changed", PropertyInfo(Variant::INT, "flag")));
 	}
 	}
-
-	EditorPropertyLayersGrid() {
-		value = 0;
-		hovered_index = -1; // Nothing is hovered.
-	}
 };
 };
+
 void EditorPropertyLayers::_grid_changed(uint32_t p_grid) {
 void EditorPropertyLayers::_grid_changed(uint32_t p_grid) {
 	emit_changed(get_edited_property(), p_grid);
 	emit_changed(get_edited_property(), p_grid);
 }
 }
@@ -818,30 +948,49 @@ void EditorPropertyLayers::update_property() {
 
 
 void EditorPropertyLayers::setup(LayerType p_layer_type) {
 void EditorPropertyLayers::setup(LayerType p_layer_type) {
 	String basename;
 	String basename;
+	int layer_group_size = 0;
+	int layer_count = 0;
 	switch (p_layer_type) {
 	switch (p_layer_type) {
-		case LAYER_RENDER_2D:
+		case LAYER_RENDER_2D: {
 			basename = "layer_names/2d_render";
 			basename = "layer_names/2d_render";
-			break;
-		case LAYER_PHYSICS_2D:
+			layer_group_size = 5;
+			layer_count = 20;
+		} break;
+
+		case LAYER_PHYSICS_2D: {
 			basename = "layer_names/2d_physics";
 			basename = "layer_names/2d_physics";
-			break;
-		case LAYER_NAVIGATION_2D:
+			layer_group_size = 4;
+			layer_count = 32;
+		} break;
+
+		case LAYER_NAVIGATION_2D: {
 			basename = "layer_names/2d_navigation";
 			basename = "layer_names/2d_navigation";
-			break;
-		case LAYER_RENDER_3D:
+			layer_group_size = 4;
+			layer_count = 32;
+		} break;
+
+		case LAYER_RENDER_3D: {
 			basename = "layer_names/3d_render";
 			basename = "layer_names/3d_render";
-			break;
-		case LAYER_PHYSICS_3D:
+			layer_group_size = 5;
+			layer_count = 20;
+		} break;
+
+		case LAYER_PHYSICS_3D: {
 			basename = "layer_names/3d_physics";
 			basename = "layer_names/3d_physics";
-			break;
-		case LAYER_NAVIGATION_3D:
+			layer_group_size = 4;
+			layer_count = 32;
+		} break;
+
+		case LAYER_NAVIGATION_3D: {
 			basename = "layer_names/3d_navigation";
 			basename = "layer_names/3d_navigation";
-			break;
+			layer_group_size = 4;
+			layer_count = 32;
+		} break;
 	}
 	}
 
 
 	Vector<String> names;
 	Vector<String> names;
 	Vector<String> tooltips;
 	Vector<String> tooltips;
-	for (int i = 0; i < 20; i++) {
+	for (int i = 0; i < layer_count; i++) {
 		String name;
 		String name;
 
 
 		if (ProjectSettings::get_singleton()->has_setting(basename + vformat("/layer_%d", i))) {
 		if (ProjectSettings::get_singleton()->has_setting(basename + vformat("/layer_%d", i))) {
@@ -858,12 +1007,17 @@ void EditorPropertyLayers::setup(LayerType p_layer_type) {
 
 
 	grid->names = names;
 	grid->names = names;
 	grid->tooltips = tooltips;
 	grid->tooltips = tooltips;
+	grid->layer_group_size = layer_group_size;
+	grid->layer_count = layer_count;
 }
 }
 
 
 void EditorPropertyLayers::_button_pressed() {
 void EditorPropertyLayers::_button_pressed() {
+	int layer_count = grid->layer_count;
+	int layer_group_size = grid->layer_group_size;
+
 	layers->clear();
 	layers->clear();
-	for (int i = 0; i < 20; i++) {
-		if (i == 5 || i == 10 || i == 15) {
+	for (int i = 0; i < layer_count; i++) {
+		if ((i != 0) && ((i % layer_group_size) == 0)) {
 			layers->add_separator();
 			layers->add_separator();
 		}
 		}
 		layers->add_check_item(grid->names[i], i);
 		layers->add_check_item(grid->names[i], i);
@@ -894,17 +1048,21 @@ void EditorPropertyLayers::_bind_methods() {
 
 
 EditorPropertyLayers::EditorPropertyLayers() {
 EditorPropertyLayers::EditorPropertyLayers() {
 	HBoxContainer *hb = memnew(HBoxContainer);
 	HBoxContainer *hb = memnew(HBoxContainer);
+	hb->set_clip_contents(true);
 	add_child(hb);
 	add_child(hb);
 	grid = memnew(EditorPropertyLayersGrid);
 	grid = memnew(EditorPropertyLayersGrid);
 	grid->connect("flag_changed", callable_mp(this, &EditorPropertyLayers::_grid_changed));
 	grid->connect("flag_changed", callable_mp(this, &EditorPropertyLayers::_grid_changed));
 	grid->set_h_size_flags(SIZE_EXPAND_FILL);
 	grid->set_h_size_flags(SIZE_EXPAND_FILL);
 	hb->add_child(grid);
 	hb->add_child(grid);
+
 	button = memnew(Button);
 	button = memnew(Button);
 	button->set_toggle_mode(true);
 	button->set_toggle_mode(true);
 	button->set_text("...");
 	button->set_text("...");
 	button->connect("pressed", callable_mp(this, &EditorPropertyLayers::_button_pressed));
 	button->connect("pressed", callable_mp(this, &EditorPropertyLayers::_button_pressed));
 	hb->add_child(button);
 	hb->add_child(button);
+
 	set_bottom_editor(hb);
 	set_bottom_editor(hb);
+
 	layers = memnew(PopupMenu);
 	layers = memnew(PopupMenu);
 	add_child(layers);
 	add_child(layers);
 	layers->set_hide_on_checkable_item_selection(false);
 	layers->set_hide_on_checkable_item_selection(false);

+ 4 - 1
scene/register_scene_types.cpp

@@ -1000,9 +1000,12 @@ void register_scene_types() {
 
 
 	for (int i = 0; i < 20; i++) {
 	for (int i = 0; i < 20; i++) {
 		GLOBAL_DEF_BASIC(vformat("layer_names/2d_render/layer_%d", i), "");
 		GLOBAL_DEF_BASIC(vformat("layer_names/2d_render/layer_%d", i), "");
+		GLOBAL_DEF_BASIC(vformat("layer_names/3d_render/layer_%d", i), "");
+	}
+
+	for (int i = 0; i < 32; i++) {
 		GLOBAL_DEF_BASIC(vformat("layer_names/2d_physics/layer_%d", i), "");
 		GLOBAL_DEF_BASIC(vformat("layer_names/2d_physics/layer_%d", i), "");
 		GLOBAL_DEF_BASIC(vformat("layer_names/2d_navigation/layer_%d", i), "");
 		GLOBAL_DEF_BASIC(vformat("layer_names/2d_navigation/layer_%d", i), "");
-		GLOBAL_DEF_BASIC(vformat("layer_names/3d_render/layer_%d", i), "");
 		GLOBAL_DEF_BASIC(vformat("layer_names/3d_physics/layer_%d", i), "");
 		GLOBAL_DEF_BASIC(vformat("layer_names/3d_physics/layer_%d", i), "");
 		GLOBAL_DEF_BASIC(vformat("layer_names/3d_navigation/layer_%d", i), "");
 		GLOBAL_DEF_BASIC(vformat("layer_names/3d_navigation/layer_%d", i), "");
 	}
 	}