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();
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.
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 between0.0and1.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_CLOSEDorPOSTURE_UNKNOWN.getHingeAngle()- hinge angle in degrees (-1when unknown).getFoldOrientation()-FOLD_ORIENTATION_VERTICAL,FOLD_ORIENTATION_HORIZONTALorFOLD_ORIENTATION_NONE.getFoldBounds(Rectangle)- the region the hinge occludes, in display coordinates, or null when there is no separating fold. Treat it likeForm.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.