This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
FunGraphics is a small Scala 2.13 / Java2D drawing library used to teach first-year programming at ISC (HES-SO Valais). It is distributed to students as a single jar they drop into IntelliJ projects, so public signatures in hevs.graphics and hevs.utils are a de-facto frozen API: lab material across several years depends on them. Add, don't rename or remove.
Gradle wrapper 7.4.2, Java toolchain 17 (the Scala plugin, not sbt). Source indentation is tabs.
./gradlew build # compile, run tests, scaladoc, jar (jar depends on test and scaladoc)
./gradlew test # JUnit 5 tests only
./gradlew test --tests 'hevs.graphics.utils.DisplaySetupTest' # a single test class
./gradlew scaladoc # output in build/docs/scaladoc (what the GitHub Pages site serves)Outputs: build/libs/FunGraphics-<gitversion>.jar and a constant-name copy build/FunGraphics-dev.jar (the name comes from the directory name because settings.gradle sets no rootProject.name; the README's lowercase fungraphics-dev.jar is wrong on a case-sensitive FS).
The jar bundles the .scala/.java sources and the scaladoc HTML (so IntelliJ shows docs), plus res/generated/version.txt written by the versionGen task from com.palantir.git-version. FunGraphics.version reads that file; running from classes instead of the jar prints a "version will be wrong" warning, and an untagged commit prints a "non-release version" warning. Both are expected.
The jar does not contain the Scala standard library. To run the built-in demo you need it on the classpath, e.g. from the Gradle cache:
SL=$(find ~/.gradle/caches/modules-2 -name 'scala-library-2.13.9.jar' | head -1)
java -cp build/FunGraphics-dev.jar:$SL hevs.graphics.FunGraphicsTests open real windows: BasicTest uses assumeFalse(headless) and is skipped without a display; the other tests are pure. Nothing in CI runs tests: the only workflow (.github/workflows/scaladoc.yml) publishes scaladoc to GitHub Pages when a tag is pushed. Releases are manual (tag MAJOR.MINOR.SUB, build, upload the jar to a GitHub release, see README).
Two independent window types live in hevs.graphics:
AcceleratedDisplay→FunGraphics→TurtleGraphics/advanced.ListGraphics.AcceleratedDisplayowns theJFrameand two translucentBufferedImages (backBuffer,frontBuffer) sized to the drawing area. ASwingWorkerrender thread copies back then front onto the frame'sBufferStrategyat the screen refresh rate (GraphicTimer.sync).FunGraphicsimplements theinterfaces.Graphicsdrawing API andDualLayerGraphics:g2dpoints at the front or the back buffer depending ondrawForeground()/drawBackground(), and every primitive just draws ong2d. Student game loops draw on the main thread and callsyncGameLogic(fps); they synchronize onfrontBufferwhen flicker matters.ListGraphicskeeps a list ofDrawables and redraws them inrepaint().ImageGraphicsis a separateJFramesubclass for the image-processing lab (pixel arrays), unrelated to the above.
hevs.utils holds console helpers (Input, TextTools, StringFunctions, DateUtils) with no graphics dependency. ScalaDocSetup.java exists only because IntelliJ needs one .java file to treat the jar as having sources.
DisplaySetup.prepare() must run before any AWT/Swing class is touched (it is the first statement of AcceleratedDisplay.initFrame and runs through a constructor-argument trick in ImageGraphics). On Wayland desktops where X11 is unusable (typically IntelliJ installed as a Flatpak, which only has fallback-x11), it selects the Wayland toolkit if the JDK ships one (JetBrains Runtime). That toolkit has no page flipping, so AcceleratedDisplay then paints through a JPanel content pane (renderPanel) instead of a BufferStrategy. DisplaySetup.openingDisplay { ... } wraps the first display access and prints a tailored diagnosis (Flatpak override command, missing Xwayland, SSH, X authorization) before rethrowing.
To reproduce display failures locally, use DISPLAY=unix:99 (fails instantly); plain DISPLAY=:99 hangs for a minute under WSL because of the TCP fallback. WSLg provides both XWayland (DISPLAY=:0) and a Wayland socket at /mnt/wslg/runtime-dir/wayland-0 (set XDG_RUNTIME_DIR=/mnt/wslg/runtime-dir to test a Wayland toolkit).
SwingUtilities.invokeLater _ -> { ... }inAcceleratedDisplay.initFramebuilds a tuple, so the block runs synchronously on the calling thread, not on the EDT. The window is deliberately created and shown off-EDT; the rest ofinitFramerelies onmainFramebeing set right after.- Mouse and key listeners are attached to
mainFrameitself (not the content pane), so mouse coordinates include the frame insets. Student code has adapted to that; changing it would silently shift every mouse-driven lab. - Buffers are created with
Transparency.TRANSLUCENT;FunGraphicsclears the front buffer to white in its constructor and the background buffer stays transparent so the front layer composites over it. - Resources are loaded with absolute classpath paths (
/res/img/...,/res/generated/...) relative tosrc/main/resources. - Scaladoc emits ~40 "Could not find any member to link" warnings for
[[String]]-style links; they are harmless and the build is still green.
GitHub issues #6 (NPE in internalRender when a window is disposed, which also kills the render thread) and #7 (window not or partially painted on Fedora/GNOME with OpenJDK 25) both live in the BufferStrategy render path of AcceleratedDisplay. The Swing-painting path added for Wayland is a candidate replacement for that whole mechanism.