@@ -58,8 +58,10 @@ public class CustomGameEditor extends Scene2DPanel {
5858 private final Label hintLabel ;
5959 private final Label rotationOverviewLabel ;
6060 private final Label recentHistoryLabel ;
61+ private final Label recentActionsLabel ;
6162 private final Table rotationOverviewTable ;
6263 private final Table recentHistoryTable ;
64+ private final Table recentActionsTable ;
6365 private final CheckBox cascadeGravityCheckbox ;
6466 private final CheckBox disconnectedGravityCheckbox ;
6567 private final CheckBox chainRowCheckbox ;
@@ -79,6 +81,7 @@ public class CustomGameEditor extends Scene2DPanel {
7981 private GameType currentGameType = new GameType ();
8082 private final GameType [] presetSlots = new GameType [3 ];
8183 private final java .util .ArrayList <RecentGameHistoryEntry > recentHistory = new java .util .ArrayList <RecentGameHistoryEntry >();
84+ private final java .util .ArrayList <RecentEditorActionEntry > recentActions = new java .util .ArrayList <RecentEditorActionEntry >();
8285 private int selectedPieceIndex = -1 ;
8386 private int selectedRotationIndex = 0 ;
8487
@@ -91,6 +94,11 @@ private static class RecentGameHistoryEntry {
9194 public long timestamp ;
9295 }
9396
97+ private static class RecentEditorActionEntry {
98+ public String label ;
99+ public long timestamp ;
100+ }
101+
94102 public CustomGameEditor (Engine engine ) {
95103 super (engine );
96104
@@ -108,8 +116,10 @@ public CustomGameEditor(Engine engine) {
108116 hintLabel = new Label ("Build piece shapes with the 4x4 grid. Add pieces and rotations to sketch rules live." , skin );
109117 rotationOverviewLabel = new Label ("Rotation Overview" , skin );
110118 recentHistoryLabel = new Label ("Recent Share / Import History" , skin );
119+ recentActionsLabel = new Label ("Recent Actions" , skin );
111120 rotationOverviewTable = new Table ();
112121 recentHistoryTable = new Table ();
122+ recentActionsTable = new Table ();
113123 cascadeGravityCheckbox = new CheckBox (" Cascade gravity" , skin );
114124 disconnectedGravityCheckbox = new CheckBox (" Disconnected-only gravity" , skin );
115125 chainRowCheckbox = new CheckBox (" Chain rows" , skin );
@@ -458,6 +468,8 @@ public void clicked(InputEvent event, float x, float y) {
458468 mainTable .add (rotationOverviewTable ).left ().row ();
459469 mainTable .add (recentHistoryLabel ).left ().padTop (8 ).row ();
460470 mainTable .add (recentHistoryTable ).left ().row ();
471+ mainTable .add (recentActionsLabel ).left ().padTop (8 ).row ();
472+ mainTable .add (recentActionsTable ).left ().row ();
461473 mainTable .add (summaryLabel ).width (520 ).left ().padTop (10 ).row ();
462474
463475 addPiece ();
@@ -471,6 +483,7 @@ private void addPiece() {
471483 selectedPieceIndex = currentGameType .pieceTypes .size () - 1 ;
472484 selectedRotationIndex = 0 ;
473485 refreshEditorState ();
486+ pushRecentAction ("Added piece: " + pieceType .name );
474487 }
475488
476489 private void addRotation () {
@@ -484,6 +497,7 @@ private void addRotation() {
484497 pieceType .rotationSet .add (new Piece .Rotation ());
485498 selectedRotationIndex = pieceType .rotationSet .size () - 1 ;
486499 refreshEditorState ();
500+ pushRecentAction ("Added rotation " + selectedRotationIndex + " to " + (pieceType .name == null ? "selected piece" : pieceType .name ) + "." );
487501 }
488502
489503 private GameType deepCloneGameType (GameType source ) {
@@ -503,6 +517,7 @@ private void savePresetSlot(int slotIndex) {
503517 applyRuleCheckboxes ();
504518 presetSlots [slotIndex ] = deepCloneGameType (currentGameType );
505519 summaryLabel .setText ("Saved current ruleset to preset slot " + (slotIndex + 1 ));
520+ pushRecentAction ("Saved the current ruleset to preset slot " + (slotIndex + 1 ) + "." );
506521 }
507522
508523 private void applyPreset (String preset ) {
@@ -574,6 +589,7 @@ private void applyPreset(String preset) {
574589 selectedPieceIndex = currentGameType .pieceTypes .isEmpty () ? -1 : 0 ;
575590 selectedRotationIndex = 0 ;
576591 refreshEditorState ();
592+ pushRecentAction ("Applied preset: " + currentGameType .name );
577593 }
578594
579595 private void loadPresetSlot (int slotIndex ) {
@@ -585,6 +601,7 @@ private void loadPresetSlot(int slotIndex) {
585601 selectedPieceIndex = currentGameType .pieceTypes .isEmpty () ? -1 : 0 ;
586602 selectedRotationIndex = 0 ;
587603 refreshEditorState ();
604+ pushRecentAction ("Loaded preset slot " + (slotIndex + 1 ) + "." );
588605 }
589606
590607 private void showImportDialog () {
@@ -619,6 +636,7 @@ private void importSharedGame(String input) {
619636 pushRecentHistoryEntry ("import" , payload , currentGameType );
620637 refreshEditorState ();
621638 summaryLabel .setText ("Imported shared game configuration." );
639+ pushRecentAction ("Imported shared ruleset: " + (currentGameType .name == null || currentGameType .name .isEmpty () ? "Imported Ruleset" : currentGameType .name ) + "." );
622640 } catch (Exception e ) {
623641 e .printStackTrace ();
624642 summaryLabel .setText ("Failed to import shared game configuration." );
@@ -630,6 +648,7 @@ private void shareGame() {
630648 String payload = currentGameType .toBase64GZippedGSON ();
631649 String url = "https://bobsgame.com/#play=" + payload ;
632650 pushRecentHistoryEntry ("share" , payload , currentGameType );
651+ pushRecentAction ("Generated a share link for " + (currentGameType .name == null || currentGameType .name .isEmpty () ? "current ruleset" : currentGameType .name ) + "." );
633652 if (copyTextToClipboard (url )) {
634653 summaryLabel .setText ("Share link copied to clipboard." );
635654 return ;
@@ -681,6 +700,29 @@ private void pushRecentHistoryEntry(String source, String payload, GameType game
681700 rebuildRecentHistoryTable ();
682701 }
683702
703+ private void pushRecentAction (String label ) {
704+ RecentEditorActionEntry entry = new RecentEditorActionEntry ();
705+ entry .label = label ;
706+ entry .timestamp = System .currentTimeMillis ();
707+ recentActions .add (0 , entry );
708+ while (recentActions .size () > 8 ) recentActions .remove (recentActions .size () - 1 );
709+ rebuildRecentActionsTable ();
710+ }
711+
712+ private void rebuildRecentActionsTable () {
713+ recentActionsTable .clearChildren ();
714+ recentActionsTable .defaults ().left ().pad (4 );
715+ if (recentActions .isEmpty ()) {
716+ recentActionsTable .add (new Label ("No recent editor actions yet." , engine .uiSkin )).left ().row ();
717+ return ;
718+ }
719+ for (int i = 0 ; i < recentActions .size (); i ++) {
720+ RecentEditorActionEntry entry = recentActions .get (i );
721+ String when = new java .text .SimpleDateFormat ("HH:mm" ).format (new java .util .Date (entry .timestamp ));
722+ recentActionsTable .add (new Label (entry .label + " • " + when , engine .uiSkin )).left ().row ();
723+ }
724+ }
725+
684726 private int getTotalRotationCount (GameType gameType ) {
685727 int total = 0 ;
686728 for (PieceType pieceType : gameType .pieceTypes ) {
@@ -798,6 +840,7 @@ private void duplicatePiece() {
798840 selectedPieceIndex = selectedPieceIndex + 1 ;
799841 selectedRotationIndex = 0 ;
800842 refreshEditorState ();
843+ pushRecentAction ("Duplicated piece: " + duplicate .name );
801844 }
802845
803846 private void removePiece () {
@@ -818,6 +861,7 @@ public void onYes() {
818861 selectedRotationIndex = 0 ;
819862 }
820863 refreshEditorState ();
864+ pushRecentAction ("Removed piece: " + pieceName );
821865 }
822866
823867 @ Override
@@ -834,6 +878,7 @@ private void duplicateRotation() {
834878 pieceType .rotationSet .rotations .add (selectedRotationIndex + 1 , duplicate );
835879 selectedRotationIndex = selectedRotationIndex + 1 ;
836880 refreshEditorState ();
881+ pushRecentAction ("Duplicated rotation for " + (pieceType .name == null ? "selected piece" : pieceType .name ) + "." );
837882 }
838883
839884 private void normalizeCurrentRotation () {
@@ -850,13 +895,15 @@ private void normalizeCurrentRotation() {
850895 offset .y -= minY ;
851896 }
852897 refreshEditorState ();
898+ pushRecentAction ("Normalized the current rotation." );
853899 }
854900
855901 private void centerCurrentRotation () {
856902 Piece .Rotation rotation = getSelectedRotation ();
857903 if (rotation == null || rotation .blockOffsets .isEmpty ()) return ;
858904 centerRotationInPlace (rotation );
859905 refreshEditorState ();
906+ pushRecentAction ("Centered the current rotation." );
860907 }
861908
862909 private void centerAllRotations () {
@@ -868,6 +915,7 @@ private void centerAllRotations() {
868915 centerRotationInPlace (rotation );
869916 }
870917 refreshEditorState ();
918+ pushRecentAction ("Centered all rotations for " + (pieceType .name == null ? "selected piece" : pieceType .name ) + "." );
871919 }
872920
873921 private void normalizeAllRotations () {
@@ -888,6 +936,7 @@ private void normalizeAllRotations() {
888936 }
889937 }
890938 refreshEditorState ();
939+ pushRecentAction ("Normalized all rotations for " + (pieceType .name == null ? "selected piece" : pieceType .name ) + "." );
891940 }
892941
893942 private void centerRotationInPlace (Piece .Rotation rotation ) {
@@ -937,6 +986,7 @@ public void onYes() {
937986 selectedRotationIndex = Math .min (selectedRotationIndex , pieceType .rotationSet .size () - 1 );
938987 }
939988 refreshEditorState ();
989+ pushRecentAction ("Cleared " + duplicateIndices .size () + " duplicate rotation(s)." );
940990 }
941991
942992 @ Override
@@ -976,6 +1026,7 @@ public void onYes() {
9761026 selectedRotationIndex = Math .min (selectedRotationIndex , pieceType .rotationSet .size () - 1 );
9771027 }
9781028 refreshEditorState ();
1029+ pushRecentAction ("Cleared " + emptyIndices .size () + " empty rotation(s)." );
9791030 }
9801031
9811032 @ Override
@@ -1001,6 +1052,7 @@ public void onYes() {
10011052 selectedRotationIndex = Math .min (removeIndex , pieceType .rotationSet .size () - 1 );
10021053 }
10031054 refreshEditorState ();
1055+ pushRecentAction ("Removed a rotation from " + (pieceType .name == null ? "selected piece" : pieceType .name ) + "." );
10041056 }
10051057
10061058 @ Override
@@ -1015,6 +1067,7 @@ private void clearRotation() {
10151067 if (rotation == null ) return ;
10161068 rotation .blockOffsets .clear ();
10171069 refreshEditorState ();
1070+ pushRecentAction ("Cleared the current rotation." );
10181071 }
10191072
10201073 private void cyclePiece (int delta ) {
@@ -1248,6 +1301,7 @@ private void refreshEditorState() {
12481301 rotationLabel .setText (rotation == null ? "Rotation: none" : "Rotation: " + selectedRotationIndex + " (" + getFilledCellCount (rotation ) + " blocks)" );
12491302 rebuildRotationOverview (pieceType );
12501303 rebuildRecentHistoryTable ();
1304+ rebuildRecentActionsTable ();
12511305
12521306 for (int y = 0 ; y < 4 ; y ++) {
12531307 for (int x = 0 ; x < 4 ; x ++) {
0 commit comments