Skip to content

Commit b4742ff

Browse files
committed
feat(editor): add scene2d recent action breadcrumbs (v2.0.19)
1 parent f516ef1 commit b4742ff

4 files changed

Lines changed: 73 additions & 1 deletion

File tree

‎CHANGELOG.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [2.0.19] - 2026-04-05
6+
7+
### Added
8+
- **Scene2D Recent Action Breadcrumbs:** `CustomGameEditor.java` now shows a bounded recent-action panel that tracks meaningful authoring operations like presets, imports, shares, piece/rotation edits, and cleanup helpers.
9+
10+
### Changed
11+
- Kept the breadcrumb feed intentionally focused on meaningful edits rather than noisy navigation clicks, making it useful as lightweight workflow memory without pretending to be full undo.
12+
- Bumped `VERSION.md` to `2.0.19`.
13+
14+
### Validation
15+
- `./gradlew compileJava` ✅
16+
517
## [2.0.18] - 2026-04-05
618

719
### Added

‎HANDOFF.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ This session focused on the newer Scene2D `CustomGameEditor` so the Java port ha
105105
- Reused the same centering logic for both single-rotation and full-set centering to keep geometry behavior consistent.
106106
- Bumped the Java repo version again to `2.0.18`.
107107

108+
### Additional Follow-Up - 2026-04-05 (Recent Action Breadcrumbs)
109+
- Added a bounded recent-action panel that tracks meaningful authoring operations like presets, imports, shares, geometry helpers, and cleanup actions.
110+
- Intentionally left noisy navigation clicks out of the breadcrumb feed so it behaves like useful workflow memory instead of spam.
111+
- Positioned this as lightweight undo-adjacent guidance rather than a fake full undo system.
112+
- Bumped the Java repo version again to `2.0.19`.
113+
108114
### Recommended Next Steps
109115
1. Add color/block-type controls to the Scene2D editor.
110116
2. Bridge Scene2D custom editor state into the older TWL editor stack where useful.

‎VERSION.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.0.18
1+
2.0.19

‎src/main/java/com/bobsgame/client/engine/game/gui/customGameEditor/CustomGameEditor.java‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)