Chapter 8 · Part V

A Live Desktop Dashboard

A real window, with real buttons, wired to real Java event listeners written in Nex. This is the one chapter in this book that crosses out of the language entirely, into the host platform's own GUI toolkit — a Nex class standing in directly for a Java interface, called back into from inside the JVM's own event-dispatch machinery.

Four buttons (+, -, reset, quit), each wired to a real javax.swing.ActionListener implemented as a Nex class, updating a counter shown in a live window. The counter itself is trivial. What makes this chapter different from every one before it: Swing isn't Nex's own type system reasoning about Nex's own values. It's a real Java GUI toolkit, calling back into Nex code from inside the JVM's own event dispatch machinery, the same way it would call back into any Java class implementing that interface.

Problem

A Nex class can implement a Java interface directly: inherit ActionListener, get wired to a real JButton via addActionListener, and have actionPerformed fire on an actual click — on the JVM backend, correct state throughout, no fallback required. This project puts that capability to work behind a real window: four buttons, one counter, and a live label that stays in sync with it.

The design question worth deciding before any code: where does a click's effect live, and where does the window's redraw happen. Those are two different responsibilities, and this chapter keeps them apart on purpose.

Design as Contract

The design this project follows keeps listeners and rendering strictly apart: capture the event in plain Nex state; render that state separately. The alternative — a listener holding a JLabel field and calling setText directly from actionPerformed — is the single most natural-looking shape for code like this. It would work. But it ties widget mutation to Swing's own event-dispatch thread, scattered across as many listeners as there are buttons, instead of living in one place a reader can find without hunting.

Dashboard_State holds nothing but plain Nex fields — no Java type anywhere in sight:

class Dashboard_State
create
  make() do
    count := 0
    should_quit := false
  end
feature
  count: Integer
  should_quit: Boolean

  increment() do count := count + 1 end
  decrement() do count := count - 1 end
  reset() do count := 0 end
  quit() do should_quit := true end
end

Each listener holds exactly one field (a Dashboard_State) and does exactly one thing when its event fires: call the one method on that state which describes what happened.

class Increment_Listener
inherit
  ActionListener
create
  make(state: Dashboard_State) do
    this.state := state
  end
feature
  state: Dashboard_State

  actionPerformed(e: ActionEvent) do
    state.increment()
  end
end

Dashboard_State is a Nex class, not a Java one — no widget reference anywhere near it, by design. Three more listeners (Decrement, Reset, Quit) follow the identical shape.

Build

Source

The complete, current code for this project is at examples/contracts_at_work/08_dashboard on GitHub.

dashboard_main.nex is the only file in this project that ever touches a JLabel; every widget lives here, and the listeners never see one:

with "java" do
  let frame: JFrame := JFrame.new("Contracts at Work — Dashboard")
  let label: JLabel := JLabel.new("count: 0")
  let inc_button: JButton := JButton.new("+")
  ...
  inc_button.addActionListener(create Increment_Listener.make(state))
  ...
  frame.setVisible(true)

  from
  until
    state.should_quit
  do
    Thread.sleep(100)
    label.setText("count: " + state.count)
  end

  frame.dispose()
end

The polling loop is deliberately the simplest thing that could work — no Task, just a tight sleep-and-redraw cycle reading state.count every hundred milliseconds. It's the single point of contact between "what happened" (an event, captured as a state mutation by a listener running on Swing's own event-dispatch thread) and "what the user sees" (a label's text, mutated only here, only from this loop). Even should_quit follows the same rule: a listener sets a flag rather than closing the window directly. The listener describes an intent, and only the loop that owns the window acts on it.

Keeping this split rigid — no listener ever touches a widget, full stop — is what makes the polling loop the one and only place rendering happens. It separates "what happened" from "how it's shown," the same separation of concerns this book has argued for since Chapter 1's split between Word_Stats and its CLI wrapper.

Test

checks.nex drives the listeners through real JButton.doClick() calls, not direct method calls on the listener objects. That's the only style of check that actually proves the Swing wiring works, rather than just proving the underlying state methods work in isolation:

inc_button.doClick()
inc_button.doClick()
inc_button.doClick()
c.check("three increments", "3", "" + state.count)

dec_button.doClick()
c.check("one decrement", "2", "" + state.count)

quit_button.doClick()
c.check("quit after click", "true", "" + state.should_quit)

No visible window is required for this. JButton and ActionListener both function without ever being shown on screen — which is what makes it possible to check real event dispatch in an automated suite at all, rather than only by a human clicking a real window.

Takeaways

When a design's whole point is to react to a real external event, test it by triggering that real event, not by calling the code underneath it directly. A real JButton.doClick() proves the wiring works in a way a direct call on the listener object never could. That's the same principle behind every integration test in this book, just applied here to a GUI instead of a socket or an HTTP connection. And capturing an event as a plain state mutation, then rendering that state separately from one single loop, is a pattern worth reaching for anywhere "what happened" and "how it's shown" can be pulled apart. It keeps a UI's own bookkeeping out of the code that actually responds to the user.

Implementing a Java interface from Nex costs nothing beyond an ordinary inherit clause. The real design work in this chapter was never about whether that boundary holds — it does — but about keeping what a listener captures separate from how a window renders it, so neither side ever has to know about the other's timing.