Skip to content
CrossFly is 100% open sourceFollow us on GitHub
CrossFlyCrossFly
Get started

This page was machine-translated from Portuguese and may contain errors. The Portuguese version is canonical.

The window stack

Understanding the model avoids most navigation bugs.

The model (LIFO)

The EventsProcessor keeps a stack of Window. The top window is the one that receives events and is painted on top. The ones below stay alive (they are not destroyed), just covered.

┌─────────────┐  ← topo: recebe toque/teclado, é pintada
│ TelaDetalhe │
├─────────────┤
│ TelaLista   │  ← coberta, viva
├─────────────┤
│ TelaHome    │  ← base
└─────────────┘
  • show() → push: registers with the EventsProcessor, calls onShow().
  • hide() → pop: unregisters, calls onRemove(); the window below returns to the top.

Per-window lifecycle

Method When Use
onShow() on entering the stack build the UI (always super.onShow())
onReposition() on resize/rotation reorganize/recompute layout
onRemove() on leaving the stack release timers, cursors, listeners

Fullscreen vs popup

The same Window class can be fullscreen or popup — it depends on the WindowStyle (widthRatio/heightRatio < 1 ⇒ popup; see Dialogs and modals). Popups sit over the previous screen (which stays visible behind).

Back button (Android) and tapping outside

  • The physical Back button is captured by the application and, by default, does hide() on the top window.
  • In a popup with canHide = true, tapping outside closes the popup.
  • To prevent leaving (e.g. a discard confirmation), handle the intent before letting hide() happen.

Full example (copy, paste and run)

Demonstrates push/pop on the stack and an entry transition configured in the stacked screen’s WindowStyle.

package com.exemplo;

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

/** Runnable main screen. Point the launcher (classname) and run: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      add(LAY_AUTO, new Label("Base da pilha (MainWindow)"));

      Button empilhar = new Button("Empilhar tela (push)");
      empilhar.onAction(c -> new TelaEmpilhada().show());   // push: goes to the top of the stack
      add(LAY_AUTO, empilhar);
   }
}

/** Screen that enters at the top with a transition and returns with hide() (pop). */
class TelaEmpilhada extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      // entry/exit transition of this window (the one below stays alive behind)
      getStyle().transition(UIConstants.Transition.SLIDE_LEFT);
      setLayout(new VerticalLayout());

      add(LAY_AUTO, new Label("Topo da pilha — a tela de baixo continua viva"));

      Button voltar = new Button("Voltar (pop)");
      voltar.onAction(c -> hide());   // pop: leaves the stack, the one below returns to the top
      add(LAY_AUTO, voltar);
   }
}

The snippets below show only the core (what goes inside onShow()). To run them, place them in the MainWindow skeleton above.

Transitions

The entry/exit animation comes from WindowStyle.transition (UIConstants.Transition): NONE (default), FADE, SLIDE_LEFT/RIGHT/UP/DOWN, SCALE, SCALE_DOWN. Pickers use FADE by default.

getStyle().transition = UIConstants.Transition.SLIDE_LEFT;   // slides in on open

Best practices

  • Don’t hold references to the windows below longer than necessary — let them be collected on hide().
  • Every show() should have a corresponding hide() (avoid stacking screens with no way back).

Common mistakes

  • Rebuilding the UI in onReposition() without preserving state (scroll, selection).
  • Assuming the window below was destroyed when another opened — it stays alive.

See also

Was this page helpful?