# Overview

MicroEJ Add-on Library: `Navigation Framework`.

This library structures multi-screen MicroEJ applications around a stack-based navigation model.
Navigation is **by page key**: the application calls a navigation operation with the `int` key of the
screen to show, and a single page factory builds the corresponding page. The application never hands a
page instance to the framework. A single `Navigator` owns the navigation history, performs the
navigation operations, animates each page change with a transition, delivers page lifecycle callbacks,
and reports navigation events to registered `NavigationListener` instances.

The main concepts are:

- **`Page`**: the unit of navigation. An application extends it and implements `getContent()` to
  build the screen's visual content, which is rebuilt each time the page is shown. The page is also
  notified when it is entered and exited through the `onEntered()` and `onExited()` lifecycle
  callbacks. A page receives its per-navigation data through its own constructor, called by the
  factory; the framework owns no parameter slot.
- **`PageFactory`**: the single component that maps a page key to a fresh `Page`, through
  `create(int, Object)`. The application implements it once, typically as one `switch` over its key
  constants, and rejects an unknown key with `IllegalArgumentException`. Along with the key it receives
  the argument the navigation carried, and injects it into the page's own constructor. It is consulted
  only by forward navigation; back navigation re-shows the page instance held in the history.
- **`Navigator`**: the controller and entry point, and a singleton. The application initializes it
  once with the MicroUI `Desktop` it drives and the factory
  (`Navigator.initialize(Desktop, PageFactory)`), then obtains it with `Navigator.getInstance()`. It
  exposes the navigation operations (`navigateTo(int)`, `navigateBack()`, `navigateBackTo(int)`,
  `replaceWith(int)`, each with a transition overload, and the two page-building ones also with an
  argument-carrying overload), the history queries (`getActivePage`,
  `getActiveKey`, `getHistory`, `getHistoryKeys`), the default transition (`setTransition`), and the
  listener registration. The first navigation shows the desktop, or lays it out again when the
  application already shows it, and makes its page the root at the bottom of the history. Any method
  called before initialization throws `IllegalStateException`.
- **`Transition`**: draws the frames of a page change. It and the shipped transitions live in the
  `ej.navigation.transition` sub-package: `Transition.IMMEDIATE` (the instant, non-animated default),
  `FadeTransition`, and `SlideTransition`. A transition is a painter, not an animator: it declares its
  duration with `getDuration()` and draws one frame per
  `render(GraphicsContext, TransitionContext, float)` call, while the navigator owns the clock, stops
  at the duration, and shows the incoming page live afterwards. A frame draws over the pixels the
  previous frame left on the display, which are the outgoing page — so a transition adds the
  incoming page over a stationary outgoing one, and cannot move or fade the outgoing one. Transitions
  are parameterized objects, not enum constants; an application supplies its own by implementing the
  interface.
- **`NavigationListener`**: an external observer notified once per navigation, of the outgoing and the
  incoming page.

Two rules shape the model. **Identity is per history entry**: the same key may appear more than once
in the history, and every forward navigation asks the factory for a fresh page, so a parameterized
drill-down such as *list → detail(A) → detail(B)* needs nothing special. And **`replaceWith` discards
the page it replaces**: it swaps the active page in place without growing the history, so the next
`navigateBack()` reveals the entry beneath the replacing one.

# Usage

Add the following line to your `build.gradle.kts`:

```kotlin
implementation("ej.library.ui:navigation-framework:2.0.0")
```

Name each screen with an `int` constant (no enums):

```java
public final class Pages {

	public static final int HOME = 0;
	public static final int DETAILS = 1;

	private Pages() {
		// Constants holder: not instantiable.
	}
}
```

Write each screen as a `Page`, taking its per-navigation data through its constructor and navigating
through the navigator:

```java
public final class DetailsPage extends Page {

	private final Item item;

	public DetailsPage(Item item) {
		this.item = item;
	}

	@Override
	protected Widget getContent() {
		// Build and return the screen's MWT widget tree. A "Back" button calls
		// Navigator.getInstance().navigateBack().
	}
}
```

Map the keys to the pages in a single factory:

```java
public final class AppPageFactory implements PageFactory {

	@Override
	public Page create(int key, @Nullable Object argument) {
		switch (key) {
		case Pages.HOME:
			return new HomePage();
		case Pages.DETAILS:
			return new DetailsPage((Item) argument);
		default:
			throw new IllegalArgumentException();
		}
	}
}
```

A minimal navigation flow:

```java
MicroUI.start();
Desktop desktop = new Desktop();
desktop.setStylesheet(createStylesheet());          // the application owns the desktop and its styling

Navigator.initialize(desktop, new AppPageFactory());
Navigator navigator = Navigator.getInstance();
navigator.setTransition(new SlideTransition());     // default for the no-transition overloads

navigator.navigateTo(Pages.HOME);                   // shows the desktop; HOME becomes the root
navigator.navigateTo(Pages.DETAILS, selectedItem);  // the argument reaches DetailsPage's constructor
navigator.navigateBack();                           // back to HOME
navigator.replaceWith(Pages.DETAILS, otherItem, new FadeTransition());
```

A transition of your own draws the incoming page over what is already on the screen:

```java
public final class WipeTransition implements Transition {

	@Override
	public int getDuration() {
		return 250;
	}

	@Override
	public void start(TransitionContext context) {
		// Nothing to prepare here; the incoming page is already captured.
	}

	@Override
	public void render(GraphicsContext g, TransitionContext context, float progress) {
		// Uncover the incoming page from the left, leaving the outgoing page visible on the right.
		int width = (int) (progress * context.getContentWidth());
		g.intersectClip(0, 0, width, context.getContentHeight());
		context.drawIncoming(g, 0, 0, GraphicsContext.OPAQUE);
	}
}
```

MicroUI must be started before the first navigation, and every navigator method must be called from
the MicroUI thread. A navigation requested from a lifecycle callback or a listener callback is not run
inline: it is deferred with `MicroUI.callSerially()` and performed once the navigation in progress has
been dispatched. If that navigation's transition is still animating, it is snapped to its end first.

## Transition Snapshots

An animated transition captures the incoming page into an image the size of the page content area,
and draws its frames from that image. The `ej.navigation.transition.snapshots` BON constant controls
that image. The library defines it as `true`. To remove the image, define the constant as `false` in
a `*.constants.list` file of your application, for example
`src/main/resources/myapp.constants.list`:

```properties
ej.navigation.transition.snapshots=false
```

With Architecture 8.3.0 or later, the same constant can also be an Application Option in the
application's `configuration/common.properties`. An Application Option takes precedence over the
classpath:

```properties
microej.constant.ej.navigation.transition.snapshots=false
```

When the constant is `false`:

- The transitions allocate no image, so the images heap does not need room for one page-sized image.
- The frames draw the incoming page widget live. `TransitionContext.drawIncoming()` ignores its
  opacity argument, and `TransitionContext.getIncomingImage()` returns `null`.
- `SlideTransition` still animates. `FadeTransition` needs the image to blend, so it becomes an
  instant page change.

# Requirements

This library requires the following Foundation Libraries:

| Foundation Library | Version |
| ------------------ | ------- |
| EDC                | 1.3     |
| BON                | 1.4     |
| MicroUI            | 3.6     |

**The images heap must hold one image the size of the page content area, unless the snapshots are
disabled.** An animated transition captures the incoming page into that image. The first animated
transition allocates it, and the following ones reuse it. Size `ej.microui.memory.imagesheap.size`
accordingly. To remove the image instead, disable the snapshots (see *Transition Snapshots* under
*Usage*): the frames then draw the incoming page live, `SlideTransition` still animates, and
`FadeTransition` becomes an instant page change.

# Dependencies

_All dependencies are retrieved transitively by Gradle_.

| Add-On Library | Version |
| -------------- | ------- |
| MWT            | 3.7     |
| Motion         | 4.0     |

# Source

N/A.

# Restrictions

- **The navigator is a singleton**: one navigator, driving one `Desktop`, per application. It must be
  initialized before any other call, and initializing it again restarts it from a clean state (empty
  history, no listener, instant default transition).
- **A navigation carries at most one argument, and the framework does not type it.** It is handed as
  an `Object` to `PageFactory.create(int, Object)`, which is responsible for checking what it received:
  the navigator neither inspects the argument nor matches it against the key. It reaches the page
  through the page's own constructor; the framework keeps no reference to it and imposes no parameter
  slot on `Page`.
- **Back navigation carries no argument.** `navigateBack()` and `navigateBackTo(int)` re-show the page
  instance the history holds and never consult the factory.
- **The framework never accepts a page instance**, and the pages it builds are not reachable for
  reuse: forward navigation always builds a fresh page. Back navigation re-shows the page instance the
  history holds, so a page's fields survive a round trip, but its content widget is rebuilt on every
  show.
- **The outgoing page is not reachable from a transition.** It is the frame on the display, not a
  widget nor an image, so a transition can neither move it nor fade it: `SlideTransition` moves the
  incoming page over a stationary outgoing one. A transition that must animate both is out of reach of
  this model.
- **An animated transition draws the incoming page from a snapshot**, so a page whose content animates
  itself is frozen for the duration of the transition, and one image the size of the content area
  stays allocated between transitions. `Navigator.releaseTransitionBuffer()` gives that memory back,
  and `Transition.IMMEDIATE` never allocates it.

_Copyright 2026 MicroEJ Corp. All rights reserved._\
_This library is provided in source code for use, modification and test, subject to license terms._\
_Any modification of the source code will break MicroEJ Corp. warranties on the whole library._\
_Build: 7E4D1F7C_
