Codename One runs on a wide range of hardware, from phones and tablets to desktops, foldables, and devices driven by a mouse or a stylus. This chapter covers the cross-platform APIs that expose that hardware: rich pointer detail (mouse buttons, pen pressure and tilt, the pointing device type), the mouse wheel, foldable device posture, and multi-display / desktop-window awareness.

These APIs are additive: on a device or platform that doesn’t report a given capability the value falls back to a sensible default, so a single code base keeps working everywhere.

Rich pointer events

The classic pointerPressed, pointerReleased and pointerDragged callbacks carry only the x/y coordinates of the touch. Modern devices provide far more detail: which mouse button was used, the kind of pointing device (finger, mouse, stylus or eraser), the pressure applied, the stylus tilt, the contact area and the keyboard modifiers held at the time.

That detail is exposed through the immutable PointerEvent snapshot. There are two ways to read it.

From an ActionEvent

Any pointer listener receives an ActionEvent; call getPointerEvent() on it:

myComponent.addPointerPressedListener(e -> {
    PointerEvent pe = e.getPointerEvent();
    if (pe.isSecondaryButton()) {
        showContextMenu(pe.getX(), pe.getY());
    }
    System.out.println("pressure=" + pe.getPressure() + " type=" + pe.getPointerType());
});

By polling

When you’re inside a pointer callback you can also poll the current values directly from CN (or Display), mirroring the existing CN.isShiftKeyDown() family:

int button = CN.getPointerButton();      // PointerEvent.BUTTON_PRIMARY, BUTTON_SECONDARY, ...
float pressure = CN.getPointerPressure(); // 0.0 - 1.0, defaults to 1.0
boolean pen = CN.isStylusPointer();
PointerEvent current = CN.getCurrentPointerEvent();
Values have safe defaults. On a plain touch screen the button is BUTTON_PRIMARY, the pressure is 1.0 and the type is TYPE_TOUCH (or TYPE_UNKNOWN on ports that don’t classify the device).

Two and three button mice

PointerEvent.getButton() returns one of BUTTON_PRIMARY, BUTTON_SECONDARY, BUTTON_MIDDLE, BUTTON_BACK or BUTTON_FORWARD. CN.getPressedButtonMask() returns a bitmask of every button currently held (MASK_PRIMARY, MASK_SECONDARY, …​), which is useful during a drag.

The real button is reported on every desktop port - macOS, Windows and Linux - and in the browser, so the right, middle and (where the mouse has them) back and forward buttons are all distinguished. On a plain touch screen there is only the primary button.

For the common case of a context menu, use the unified addContextMenuListener. It fires for a secondary (right) mouse click, a stylus barrel button click and a long press, so the same code handles desktop and touch:

label.addContextMenuListener(e -> {
    e.consume(); // suppress the normal click/long-press handling
    showMyContextMenu(e.getX(), e.getY());
});

Mouse wheel and trackpad scrolling

By default the mouse wheel and trackpad scroll a scrollable container automatically. To react to the wheel yourself - for example control plus wheel to zoom, or horizontal scrolling - add a addMouseWheelListener. The listener receives a WheelEvent; consuming it prevents the default scroll.

A positive getDeltaY() reveals content above (the user scrolled down); a positive getDeltaX() reveals content to the left. isPrecise() is true for high resolution devices such as trackpads. On the desktop simulator, hold shift while turning the wheel to produce horizontal scrolling.

WheelEvent is a single universal scroll-gesture API: it’s fed by the mouse wheel and trackpad on the desktop, by the wheel / external trackpad on Android, and by the trackpad, Magic Mouse and mouse wheel on iOS and iPadOS (captured natively and routed through the same pipeline). Continuous trackpad scrolling reports isPrecise() == true, while a notched wheel reports false. On the Apple Watch the Digital Crown also feeds this same pipeline, so a list scrolls under the crown and a mouse wheel listener receives the crown as a WheelEvent.

Trackpad gestures (magnify and rotate)

Beyond scrolling, the trackpad reports magnify (pinch) and rotate gestures. Override pinch(float scale) to zoom and rotation(float radians) to rotate; both return true to mark the gesture as handled. pinch is also driven by a two finger pinch on touch screens, so the same code works on mobile and the desktop.

The trackpad gestures are wired on every desktop port: the macOS trackpad, the Windows touch screen and gesture-capable touchpads (through the system WM_GESTURE messages), and the Linux touchpad (through the GTK touchpad gesture events). scale is an incremental zoom factor (just above or below 1.0) and radians is the incremental rotation since the previous callback, so accumulate them to track the total.

A Windows precision touchpad usually reports a pinch as control plus wheel rather than a system gesture, so it also arrives through the universal wheel API. Handle that case with a addMouseWheelListener that checks isControlDown() (shown above) if you target those devices.

Stylus and pen support

When a stylus is used (Apple Pencil, S-Pen, Surface Pen) the pointer type is TYPE_STYLUS, or TYPE_ERASER for the eraser end. The PointerEvent then also carries:

  • getPressure() - normalized pressure between 0.0 and 1.0.

  • getTiltX() / getTiltY() - tilt in degrees (altitude and azimuth on iOS).

  • getContactSize() - normalized size of the contact area.

For drawing surfaces a dedicated addStylusListener fires on press, drag and release only when the pointer is a stylus or eraser:

drawingArea.addStylusListener(e -> {
    PointerEvent pe = e.getPointerEvent();
    float width = 1f + pe.getPressure() * 9f; // pressure controls stroke width
    if (pe.isEraser()) {
        erase(pe.getX(), pe.getY());
    } else {
        drawPoint(pe.getX(), pe.getY(), width);
    }
});

Pen hover (the pen moving above the screen without touching, available on devices such as the S-Pen and the M2 iPad with Apple Pencil) flows through the existing pointerHover callbacks; the snapshot reports isHovering() as true.

Touch-enabled desktops are handled too: on a Windows or Linux PC with a touch screen the finger reports TYPE_TOUCH, and a Windows pen reports TYPE_STYLUS, so the same drawing and gesture code runs on a touch laptop as on a phone. Display.getInstance().isTouchScreenDevice() is true on those machines.

Foldable devices and posture

Foldable and dual screen devices (Galaxy Fold, Galaxy Flip, Pixel Fold, Surface Duo) change shape at runtime. Query the live posture through DevicePosture:

DevicePosture p = CN.getDevicePosture();
// isTableTop() is true for book posture too, so check the hinge is
// horizontal before laying out top-and-bottom
if (p.isFoldable() && p.isTableTop()
        && p.getFoldOrientation() == DevicePosture.FOLD_ORIENTATION_HORIZONTAL) {
    layoutForTableTop(p.getFoldBounds(null));
}
  • getPosture() - POSTURE_FLAT, POSTURE_HALF_OPENED, POSTURE_CLOSED or POSTURE_UNKNOWN.

  • getHingeAngle() - hinge angle in degrees (-1 when unknown).

  • getFoldOrientation() - FOLD_ORIENTATION_VERTICAL, FOLD_ORIENTATION_HORIZONTAL or FOLD_ORIENTATION_NONE.

  • getFoldBounds(Rectangle) - the region the hinge occludes, in display coordinates, or null when there is no separating fold. Treat it like Form.getSafeArea(): keep interactive content out of it, or split a master and detail layout across the two halves.

React to fold changes with a posture listener:

class FoldAwareForm extends Form {
    private ActionListener postureListener;

    protected void initComponent() {
        super.initComponent();
        postureListener = e -> relayoutForPosture(CN.getDevicePosture());
        CN.addPostureListener(postureListener);
    }

    protected void deinitialize() {
        // CN registers on the singleton Display, so a screen that never
        // unregisters stays reachable and keeps receiving posture changes
        CN.removePostureListener(postureListener);
        super.deinitialize();
    }
}

To exercise this in the desktop simulator, use the Simulate → Foldable menu: enable foldable mode, then pick a posture (flat, half-opened or closed), the fold orientation (vertical or horizontal) and the hinge angle. The running app receives the same posture changes and listener callbacks it would on a real foldable device.

Foldable support on Android is opt-in so it never adds weight to apps that don’t use it. Enable it with the android.foldableSupport=true build hint (the version can be overridden with android.windowVersion). Note that the hinge angle isn’t available on Android, so getHingeAngle() returns -1 there.

Desktop windowing and external displays

Some mobile devices run in a desktop windowing mode where the app shares the screen with other windows: Samsung DeX, Android desktop windowing and iPad Stage Manager. CN.isDesktopMode() reports this. It’s distinct from CN.isDesktop(), which reports a genuine desktop platform (Windows, macOS or Linux).

CN.getDisplayCount() returns the number of attached displays and CN.isExternalDisplayConnected() is a convenience for getDisplayCount() > 1. On the desktop port these reflect the real monitors attached to the machine.

Apple TV remote

On Apple TV the Siri Remote is delivered through the standard key handling, so the same keyPressed/keyReleased and game-key code you write for other platforms works on the TV. The directional buttons map to the arrow / game keys, the click maps to enter (fire), the menu button maps to the back key, and play/pause maps to the media play/pause key. No TV specific API is required.