# How to centralize navigation in a controller

> Encapsulate push/pop of the EventsProcessor stack in your own controller, for testable and decoupled navigation.

Source: https://crossfly.dev/docs/cookbook/nav/controller-de-navegacao — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# How to centralize navigation in a controller

**Purpose.** Scattering `new X().show()` and `hide()` across screens couples the UI to the navigation graph. A **controller** concentrates push/pop, gives a single point for logging/guards, and enables "close down to the root". It merely orchestrates the real `EventsProcessor` stack.

## APIs used
- `Window.show()` / `Window.hide()` — push/pop on the stack (the controller merely delegates to them).
- `EventsProcessor.topWindow()` — **static** method that returns the top window (`instance.windows.peek()`).
- `EventsProcessor.instance.getLastWindow()` — **instance** method; returns the real top of the stack (or `null` if empty). Use it when you need to check whether something is stacked without throwing an exception.

> Names confirmed in the `EventsProcessor` source: `topWindow()` (static) and `getLastWindow()` (instance). `topWindow()` does `windows.peek()` and throws if the stack is empty; `getLastWindow()` returns `null` in that case — prefer this one for defensive checks.

## Complete example (copy, paste and run)
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;

/** Runnable main screen: uses the Nav controller to push/pop screens. */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      add(new Label("Tela raiz"));
      add(new Button("Abrir detalhe").onAction(e -> Nav.push(new OutraTela())));  // push via controller
   }
}

/** Stacked screen: closes via controller (pop). */
class OutraTela extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      add(new Label("Detalhe"));
      add(new Button("Voltar").onAction(e -> Nav.pop()));  // pop via controller
   }
}

/** Controller: thin facade over the real EventsProcessor stack. */
final class Nav
{
   private Nav() {}

   /** push: stacks and displays. */
   public static void push(Window w)
   {
      w.show();
   }

   /** pop: closes the topmost window, if any. */
   public static void pop()
   {
      Window top = EventsProcessor.instance.getLastWindow();  // null-safe
      if (top != null)
         top.hide();
   }

   /** window currently on top (may be null). */
   public static Window current()
   {
      return EventsProcessor.instance.getLastWindow();
   }
}
```

> The snippet below shows **only the core**. To run it, place it inside the `MainWindow` skeleton above.

## Minimal snippet
```java
// verified in the source: EventsProcessor.topWindow() (static) and instance.getLastWindow() (returns null if empty)
public final class Nav
{
   private Nav() {}

   /** push: stacks and displays. */
   public static void push(Window w)
   {
      w.show();
   }

   /** pop: closes the topmost window, if any. */
   public static void pop()
   {
      Window top = EventsProcessor.instance.getLastWindow();  // null-safe
      if (top != null)
         top.hide();
   }

   /** repeated pop until only 1 window remains (the root). */
   public static void popToRoot()
   {
      Window top;
      // while there is more than one window stacked, drop the top one
      while ((top = EventsProcessor.instance.getLastWindow()) != null
             && top != EventsProcessor.topWindow()) { top.hide(); }
      // (to "return to root", each hide() pops one; adjust the stopping criterion to your app)
   }

   /** window currently on top (may be null). */
   public static Window current()
   {
      return EventsProcessor.instance.getLastWindow();
   }
}

// usage in screens:
Nav.push(new DetalheTela());
// ...
Nav.pop();
```

Best practices:

- **Do not** reimplement the stack: the `EventsProcessor` is already the source of truth. The controller is a thin facade over `show()`/`hide()`.
- Navigation guards (e.g., "are there unsaved changes?") fit naturally here: check before `pop()` and, if needed, fire a confirmation `Alert`.
- Be careful with `topWindow()` when the stack may be empty — it does `peek()` and throws. For defensive code use `getLastWindow()`.

## Go deeper
- [The window stack](/docs/ui/navigation/the-window-stack) · [Navigate between screens](/docs/ui/navigation/navigate-between-screens)
- Sibling recipe: [Open and close a screen](/docs/cookbook/nav/abrir-tela)
