From d20d9f14e79590b2f192d1f5eca5304ac13dab50 Mon Sep 17 00:00:00 2001 From: Mike Barkmin Date: Fri, 9 Oct 2026 09:58:32 +0200 Subject: [PATCH 1/3] Open the stage code and designer again after switching projects Closing or switching a project cleared the tab pane but not the maps of open code and tool tabs, so opening a file that was open before selected a tab that no longer showed and nothing appeared. --- .../main/java/org/openpatch/scratch4j/ui/EditorTabs.java | 7 +++++++ ui/src/main/java/org/openpatch/scratch4j/ui/StudioApp.java | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ui/src/main/java/org/openpatch/scratch4j/ui/EditorTabs.java b/ui/src/main/java/org/openpatch/scratch4j/ui/EditorTabs.java index f172f35..5bcf330 100644 --- a/ui/src/main/java/org/openpatch/scratch4j/ui/EditorTabs.java +++ b/ui/src/main/java/org/openpatch/scratch4j/ui/EditorTabs.java @@ -412,6 +412,13 @@ boolean close(Tab tab) { return true; } + /** Drops every tab without asking (the project closes or another one opens). */ + void clear() { + getTabs().clear(); + openTabs.clear(); + toolTabs.clear(); + } + /** Right-click on a tab: close it, the others, or all. */ private javafx.scene.control.ContextMenu tabMenu(Tab tab) { javafx.scene.control.MenuItem close = new javafx.scene.control.MenuItem( diff --git a/ui/src/main/java/org/openpatch/scratch4j/ui/StudioApp.java b/ui/src/main/java/org/openpatch/scratch4j/ui/StudioApp.java index b4bd359..8d56768 100644 --- a/ui/src/main/java/org/openpatch/scratch4j/ui/StudioApp.java +++ b/ui/src/main/java/org/openpatch/scratch4j/ui/StudioApp.java @@ -596,7 +596,7 @@ private void closeProject() { checkDelay.stop(); hotSwapDelay.stop(); stuckDelay.stop(); - editor.getTabs().clear(); + editor.clear(); project.set(null); hasProject.set(false); stage.setTitle("Scratch for Java Studio"); @@ -1031,7 +1031,7 @@ private void open(ScratchProject p) { projectTitle.setText(p.name()); hasProject.set(true); fileTree.setRoot(p.root()); - editor.getTabs().clear(); + editor.clear(); selector = new StageSelectorView(p, this::openDesigner, cls -> editor.open(p.root().resolve(cls + ".java")), () -> { From 6b36d5f483eeebff06e7c03ca0118ec45380b0e8 Mon Sep 17 00:00:00 2001 From: Mike Barkmin Date: Fri, 9 Oct 2026 10:00:08 +0200 Subject: [PATCH 2/3] Release Studio 0.1.0-alpha.11 --- README.md | 2 +- app/pom.xml | 2 +- core/pom.xml | 2 +- export/pom.xml | 2 +- pom.xml | 2 +- runner/pom.xml | 2 +- sound/pom.xml | 2 +- ui/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c0bc81c..28d44a9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Scratch for Java Studio is a desktop IDE for students moving from Scratch to Java. It builds projects with [Scratch for Java](https://github.com/openpatch/scratch-for-java) and keeps them as ordinary Java source files that also work in BlueJ and VS Code. -**Status: 0.1.0-alpha.10.** The IDE and its +**Status: 0.1.0-alpha.11.** The IDE and its project format may change while we test it with students and teachers. ![Scratch for Java Studio with the code editor and the visual stage designer side by side](docs/screenshot.png) diff --git a/app/pom.xml b/app/pom.xml index 31ea4f5..11cc3f6 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -7,7 +7,7 @@ org.openpatch.scratch4j scratch4j-ide - 0.1.0-alpha.10 + 0.1.0-alpha.11 app diff --git a/core/pom.xml b/core/pom.xml index 7f0ac7a..9d9cd8e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -7,7 +7,7 @@ org.openpatch.scratch4j scratch4j-ide - 0.1.0-alpha.10 + 0.1.0-alpha.11 core diff --git a/export/pom.xml b/export/pom.xml index 9fe0b52..8cbe789 100644 --- a/export/pom.xml +++ b/export/pom.xml @@ -7,7 +7,7 @@ org.openpatch.scratch4j scratch4j-ide - 0.1.0-alpha.10 + 0.1.0-alpha.11 export diff --git a/pom.xml b/pom.xml index 69c0889..cf09b41 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.openpatch.scratch4j scratch4j-ide - 0.1.0-alpha.10 + 0.1.0-alpha.11 pom Scratch for Java Studio diff --git a/runner/pom.xml b/runner/pom.xml index 2b209c2..81189d3 100644 --- a/runner/pom.xml +++ b/runner/pom.xml @@ -7,7 +7,7 @@ org.openpatch.scratch4j scratch4j-ide - 0.1.0-alpha.10 + 0.1.0-alpha.11 runner diff --git a/sound/pom.xml b/sound/pom.xml index c512779..861d009 100644 --- a/sound/pom.xml +++ b/sound/pom.xml @@ -7,7 +7,7 @@ org.openpatch.scratch4j scratch4j-ide - 0.1.0-alpha.10 + 0.1.0-alpha.11 sound diff --git a/ui/pom.xml b/ui/pom.xml index 2d76c54..12fb056 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -7,7 +7,7 @@ org.openpatch.scratch4j scratch4j-ide - 0.1.0-alpha.10 + 0.1.0-alpha.11 ui From 94136ed6763b4a612e840eaccadd5e7acd30c324 Mon Sep 17 00:00:00 2001 From: Mike Barkmin Date: Fri, 9 Oct 2026 10:06:29 +0200 Subject: [PATCH 3/3] Give the loading screen time before calling a program frozen The heartbeat says when the library still shows its loading screen, and the watchdog then waits 20 seconds instead of 3: slow school PCs draw nothing for a few seconds at 100% while the stage takes over. --- .../scratch4j/runner/LauncherSource.java | 22 ++++++++++++++++++- .../openpatch/scratch4j/runner/RunHandle.java | 4 +++- .../scratch4j/runner/RunListener.java | 8 +++++++ .../scratch4j/runner/IdeControlIT.java | 8 ++++++- .../org/openpatch/scratch4j/ui/StudioApp.java | 10 +++++---- 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/runner/src/main/java/org/openpatch/scratch4j/runner/LauncherSource.java b/runner/src/main/java/org/openpatch/scratch4j/runner/LauncherSource.java index 5fdd0b6..fe09f93 100644 --- a/runner/src/main/java/org/openpatch/scratch4j/runner/LauncherSource.java +++ b/runner/src/main/java/org/openpatch/scratch4j/runner/LauncherSource.java @@ -184,7 +184,9 @@ static void start() { new Hook(); hooked = true; } - System.err.println(MARK + "frames " + frames); + // "loading" while the library's loading screen shows (slow PCs stop + // drawing for a while when the stage takes over) + System.err.println(MARK + "frames " + frames + (loading() ? " loading" : "")); try { Thread.sleep(1000); } catch (InterruptedException e) { @@ -358,6 +360,24 @@ static Object call(Object target, String name, Class[] types, Object... args) } /** The library's Processing sketch (reflective: no Processing types needed to compile). */ + private static java.lang.reflect.Field state; + + /** The library still shows its loading screen (before the stage runs). */ + static boolean loading() { + Object applet = applet(); + if (applet == null) return false; + try { + if (state == null) { + java.lang.reflect.Field field = applet.getClass().getDeclaredField("state"); + field.setAccessible(true); + state = field; + } + return String.valueOf(state.get(applet)).equals("LOADING"); + } catch (ReflectiveOperationException | RuntimeException e) { + return false; // an older library: no loading screen to wait for + } + } + static Object applet() { try { return Class.forName("org.openpatch.scratch.internal.Applet") diff --git a/runner/src/main/java/org/openpatch/scratch4j/runner/RunHandle.java b/runner/src/main/java/org/openpatch/scratch4j/runner/RunHandle.java index 0d4e655..0a9ee17 100644 --- a/runner/src/main/java/org/openpatch/scratch4j/runner/RunHandle.java +++ b/runner/src/main/java/org/openpatch/scratch4j/runner/RunHandle.java @@ -88,7 +88,9 @@ public void stop() { private static void control(String message, RunListener listener) { if (message.startsWith("frames ")) { try { - listener.onFrames(Long.parseLong(message.substring("frames ".length()).trim())); + String[] beat = message.substring("frames ".length()).trim().split(" "); + listener.onFrames(Long.parseLong(beat[0]), + beat.length > 1 && beat[1].equals("loading")); } catch (NumberFormatException ignored) { // a garbled heartbeat is skipped } diff --git a/runner/src/main/java/org/openpatch/scratch4j/runner/RunListener.java b/runner/src/main/java/org/openpatch/scratch4j/runner/RunListener.java index 8db2bf3..a7a67b1 100644 --- a/runner/src/main/java/org/openpatch/scratch4j/runner/RunListener.java +++ b/runner/src/main/java/org/openpatch/scratch4j/runner/RunListener.java @@ -18,6 +18,14 @@ default void onCompileFailed(org.openpatch.scratch4j.core.compile.CompileResult */ default void onFrames(long frames) {} + /** + * The heartbeat with whether the library still shows its loading screen + * (where a slow PC may draw nothing for a while). Defaults to {@link #onFrames(long)}. + */ + default void onFrames(long frames, boolean loading) { + onFrames(frames); + } + /** The game loop stopped ({@code pause}, or after a {@code step}) at this frame. */ default void onPaused(long frame) {} diff --git a/runner/src/test/java/org/openpatch/scratch4j/runner/IdeControlIT.java b/runner/src/test/java/org/openpatch/scratch4j/runner/IdeControlIT.java index 5f05c26..0a0b7ec 100644 --- a/runner/src/test/java/org/openpatch/scratch4j/runner/IdeControlIT.java +++ b/runner/src/test/java/org/openpatch/scratch4j/runner/IdeControlIT.java @@ -231,10 +231,14 @@ void aForeverLoopInRunStopsTheFrameCount() throws Exception { + " while (true) { }\n }\n }")); ScratchProject project = ScratchProject.open(tmp.resolve("frozen")); List beats = new CopyOnWriteArrayList<>(); + List loading = new CopyOnWriteArrayList<>(); RunHandle handle = new ProjectRunner().run(project, RunConfig.of(project.startStage()).withControl(), new RunListener() { - @Override public void onFrames(long f) { beats.add(f); } + @Override public void onFrames(long f, boolean l) { + beats.add(f); + loading.add(l); + } }); try { waitFor(() -> beats.size() >= 8 && beats.get(beats.size() - 1) > 0, 20000); @@ -243,6 +247,8 @@ void aForeverLoopInRunStopsTheFrameCount() throws Exception { assertThat(beats.get(beats.size() - 1)).as("frozen: no new frames").isEqualTo(last); // run() starts a few frames after the first drawn frame assertThat(last).isBetween(55L, 200L); + // past the loading screen: the IDE's short freeze limit applies + assertThat(loading.get(loading.size() - 1)).as("loading flags %s", loading).isFalse(); } finally { handle.stop(); } diff --git a/ui/src/main/java/org/openpatch/scratch4j/ui/StudioApp.java b/ui/src/main/java/org/openpatch/scratch4j/ui/StudioApp.java index 8d56768..ca23b1a 100644 --- a/ui/src/main/java/org/openpatch/scratch4j/ui/StudioApp.java +++ b/ui/src/main/java/org/openpatch/scratch4j/ui/StudioApp.java @@ -1531,8 +1531,10 @@ private Path mediaFile(String extension) { * A program whose frame count stops for 3 seconds (or that never shows a * window) is frozen - almost always a loop that never ends. The console * explains it once; the Problems tab shows where the transition lint found it. + * On the loading screen it gets 20 seconds: a slow PC draws nothing for a + * while when the stage takes over. */ - void watchdog(long frames) { + void watchdog(long frames, boolean loading) { long now = System.currentTimeMillis(); if (frames != watchFrames) { if (watchWarned && frames > watchFrames && watchFrames >= 0) { @@ -1558,7 +1560,7 @@ void watchdog(long frames) { watchChanged = now; return; } - boolean stalled = frames >= 0 && now - watchChanged >= 3000; + boolean stalled = frames >= 0 && now - watchChanged >= (loading ? 20000 : 3000); boolean noWindow = frames < 0 && now - watchStarted >= 10000; if ((stalled || noWindow) && !watchWarned && running.get()) { watchWarned = true; @@ -3092,8 +3094,8 @@ private void runStage(String startStage, boolean debug) { liveSwap = new org.openpatch.scratch4j.runner.HotSwap(p, session); RunHandle handle = runner.run(p, config, new RunListener() { - @Override public void onFrames(long frames) { - Platform.runLater(() -> watchdog(frames)); + @Override public void onFrames(long frames, boolean loading) { + Platform.runLater(() -> watchdog(frames, loading)); } @Override public void onPaused(long frame) {