Skip to content

Getting Started

SpinyOwl edited this page Sep 29, 2026 · 3 revisions

Getting Started

Prerequisites

  • Java 25 or later.
  • A platform capable of opening an LWJGL/OpenGL window for the native examples.
  • No separate Gradle installation: use the repository wrapper.

Clone the repository and run the smallest windowed example:

.\\gradlew.bat :spinygui.demo.simple:runHelloWorldExample

On macOS or Linux, use ./gradlew instead. The example opens a native GLFW window, so it is not a headless test and must be run in a desktop session.

Standalone native window

NvgLwjglApplication is the convenience base for an application that owns its window, the normal frame pipeline, NanoVG renderer, input bridge, and teardown. Add DOM-like nodes to its Frame, then attach a stylesheet.

import static com.spinyowl.spinygui.core.node.NodeBuilder.div;
import static com.spinyowl.spinygui.core.node.NodeBuilder.text;

import com.spinyowl.spinygui.core.backend.renderer.lwjgl.LwjglApplicationConfiguration;
import com.spinyowl.spinygui.core.backend.renderer.lwjgl.NvgLwjglApplication;
import java.util.Map;

public final class Starter extends NvgLwjglApplication {
  public Starter() {
    super(LwjglApplicationConfiguration.windowed(720, 420, "My SpinyGUI app"));
  }

  @Override
  protected void initializeGui() {
    frame().addChild(div(Map.of("class", "card"), text("Hello, SpinyGUI")));
    addStyleSheet("""
        winframe { background-color: #eef2ff; padding: 48px; }
        .card { background-color: white; color: #172554; padding: 24px; }
        """);
  }

  public static void main(String[] args) {
    new Starter().run();
  }
}

The checked-in HelloWorldExample is the canonical runnable version.

Choose an integration level

Need Code to start from
A standalone native window with normal defaults HelloWorldExample
Navigation and modal windows NavigationModalHostExample
SpinyGUI inside an existing GLFW/callback lifecycle LwjglApplicationHost and EmbeddedCallbackCoexistenceSmoke
Parsed XML plus named handlers XmlEventBindingLoader and HandlerRegistry

Navigation and modal windows

Choose the owned host when SpinyGUI should create the window, standard services, callback bridge, and renderer. The navigator is available to the lifecycle after initialization, so UI callbacks can move between frames or show a modal on the current frame.

Frame initialFrame = new Frame();
try (LwjglApplicationHost host = LwjglApplicationHost.owned(
    LwjglApplicationConfiguration.windowed(760, 520, "My application"),
    initialFrame,
    8,
    lifecycle)) {
  host.run();
}

The runnable NavigationModalHostExample shows navigator.navigate(...) and frame.topLayer().showModal(...), including nested modals.

Embed in an existing GLFW application

Use the injected host when the caller already owns the native window, renderer, services, and loop resources. The host uses those dependencies but does not close them; its lifecycle must therefore follow the embedding application's teardown rules.

LwjglApplicationHost host = LwjglApplicationHost.injected(
    navigator, pipeline, renderer, window, services, clock, lifecycle);
host.run(); // caller still owns renderer, services, window, and GLFW

For an already-installed callback chain, use an attach-only bridge and retain its registration:

GlfwSystemEventBridge bridge = GlfwSystemEventBridge.attached(
    navigator, systemEvents, keyPolicy, callerOwnedChains);
try (LwjglCallbackInstaller.Registration registration = bridge.install(nativeWindow)) {
  // Run the caller-owned event loop. Closing registration removes only bridge handlers.
}

The embedded callback smoke demonstrates callback coexistence and exact detachment. Read LWJGL Application Host before composing this path.

Parse XML with named event handlers

Wrap the normal parser only when parsed XML should refer to caller-owned named handlers. The registry remains explicit; this is not reflection-based controller discovery or data binding.

HandlerRegistry handlers = new HandlerRegistry();
handlers.register("save", ActionEvent.class, event -> saveDocument());

NodeParser parser = new XmlEventBindingLoader(
    new DefaultNodeParser(), handlers, XmlEventBindingOptions.defaults());
Frame frame = parser.fromHtml(
    "<winframe><button on-action=\"save\">Save</button></winframe>").frame();

See XML Event Handlers for the supported attributes and missing-handler policy.

Clone this wiki locally