Skip to content

Fix UI clipping in all 4 orientations on Android devices with display cutout - #4752

Open
cagnulein wants to merge 10 commits into
worktree-fix-issue-3769-landscape-layoutfrom
fix-orientation-inset-clipping
Open

Fix UI clipping in all 4 orientations on Android devices with display cutout#4752
cagnulein wants to merge 10 commits into
worktree-fix-issue-3769-landscape-layoutfrom
fix-orientation-inset-clipping

Conversation

@cagnulein

@cagnulein cagnulein commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Problem

On Android devices with a punch-hole or notch display (e.g. Pixel 6), rotating to landscape or inverted landscape showed a white strip (~49dp) on the notch side, and the toolbar had wasted space at the top.

Root causes:

  1. White strip: Qt 5.15 offsets its rendering scene by leftInset to avoid the display cutout. Without setDecorFitsSystemWindows(false), the system shrank the app content area around the cutout, leaving the raw Android window background (white) visible in that region.

  2. Toolbar top space: The same window-shrinking caused topPadding to be double-counted — Qt already positioned content below the status bar, and our QML padding added more space on top.

  3. getTopPadding()/getBottomPadding() axis swap: The original code returned leftInset as top padding in landscape (wrong). getSystemWindowInsets() already returns orientation-aware values.

  4. LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES: Only extends into the cutout on the physical short sides of the device. On a phone, inverted landscape has the notch on the long side, so it was clipped.

Fix

  • setDecorFitsSystemWindows(false) (API 30+): Prevents the system from shrinking the content area around insets. The app now renders edge-to-edge into ALL screen areas and handles insets itself via onApplyWindowInsets.
  • LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS (API 30+): Extends the window into the display cutout on all 4 rotation variants.
  • Black window background: setBackgroundDrawable(new ColorDrawable(Color.BLACK)) ensures any area outside Qt's rendering surface shows black rather than white.
  • getTopPadding(): Always returns AndroidStatusBar.height (orientation-aware, no manual axis swap).
  • getBottomPadding(): Always returns AndroidStatusBar.navigationBarHeight.

Screenshots — Pixel 6 (API 34), all 4 orientations after fix

Portrait (rot0) Landscape 90° (rot1)
portrait landscape
Inverted Portrait (rot2) Inverted Landscape 270° (rot3) — previously clipped + white strip
inv_portrait inv_landscape
  • No white strip on any side in any rotation
  • Toolbar extends edge-to-edge, compact height
  • Icons correctly padded away from the notch area via QML leftPadding/rightPadding

Test plan

  • Pixel 6 emulator (API 34) — all 4 rotations
  • Physical device with notch/punch-hole — rotate through all 4 orientations
  • Device without notch — verify no regression (setDecorFitsSystemWindows only applies on API 30+; padding functions return 0 when apiLevel < 31)

🤖 Generated with Claude Code

cagnulein and others added 5 commits June 25, 2026 10:53
… cutout

On devices with a display cutout (punch-hole camera), rotating to inverted
landscape (270°) would clip the app window: the system restricted the window
to SHORT_EDGES mode which doesn't cover all rotation variants, leaving a wide
white strip and cutting off toolbar icons.

Also fixes getTopPadding()/getBottomPadding() which incorrectly swapped the
inset axis in landscape (returning leftInset/rightInset instead of the
actual top/bottom insets that getSystemWindowInsets() already provides in
orientation-aware form).

Changes:
- CustomQtActivity.java: use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS on API 30+
  so the window extends into the cutout in all 4 rotation variants (SHORT_EDGES
  only covers the physical short sides, missing inverted landscape on phones).
- main.qml: getTopPadding() now always returns AndroidStatusBar.height;
  getBottomPadding() always returns AndroidStatusBar.navigationBarHeight.
  The Android getSystemWindowInsets() API already returns orientation-aware
  values so no manual axis swap is needed.

Tested on Pixel 6 (API 34) emulator in all 4 rotations: portrait, landscape,
inverted portrait, inverted landscape all show correct insets with no clipping.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 4 rotations on Pixel 6 (API 34) emulator after applying the fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
setDecorFitsSystemWindows(false) (API 30+) prevents the system from
shrinking the app content area around display cutout/inset regions.
Previously, Qt's rendering area was offset by leftInset (~49dp) in
landscape, leaving a white Android window background strip visible
on the notch side.

Also adds a black window background drawable so any areas outside
Qt's rendering surface appear dark rather than white, providing a
safe fallback on older API levels.

Tested on Pixel 6 (API 34) emulator: all 4 rotations now show the
toolbar extending edge-to-edge with no strips. The QML leftPadding/
rightPadding values correctly keep icon content away from the notch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The ToolBar had leftPadding/rightPadding set to the notch/system inset
values, which left a visible dark gap on the sides of the toolbar in
landscape mode. Content (wrapper item and Drawer) still correctly avoids
the notch via anchors.leftMargin/rightMargin.

Adds multi-device screenshots: Pixel 3a (no notch) and 10" tablet in
all orientations to confirm no regression.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cagnulein

Copy link
Copy Markdown
Owner Author

Pixel 6 (punch-hole camera, API 34)

Portrait Landscape Inverted Landscape

@cagnulein

Copy link
Copy Markdown
Owner Author

Pixel 3a (no notch, API 34)

Portrait Landscape Inverted Landscape

@cagnulein

Copy link
Copy Markdown
Owner Author

10" Tablet (API 34)

Portrait Landscape Inverted Landscape

cagnulein and others added 2 commits June 25, 2026 16:23
Qt 5.15 internally shifts its scene by topInset in landscape even with
setDecorFitsSystemWindows(false), causing our topPadding to double-count.
Result: 36dp of empty space between status bar and toolbar icons in landscape
instead of the correct ~12dp.

Fix: set topPadding=0 for ToolBar and Drawer in landscape/inverted-landscape,
since Qt already handles the vertical offset internally in those orientations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cagnulein

cagnulein commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

Pixel 6 (punch-hole camera, API 34) — after toolbar fix

Portrait Landscape Inverted Landscape

@cagnulein

Copy link
Copy Markdown
Owner Author

Pixel 3a (no notch, API 34) — after toolbar fix

Portrait Landscape Inverted Landscape

@cagnulein

cagnulein commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

Pixel 3a API 28 (Android 9 — legacy path, no edge-to-edge)

On API < 30 the Java code does not call setDecorFitsSystemWindows and all QML padding functions return 0, so Qt handles insets internally as before. No regression.

Portrait Landscape Inverted Landscape

cagnulein and others added 2 commits June 30, 2026 15:23
The emulator has a Qt 5.15-specific internal 24dp vertical shift in
landscape (not present on real hardware), which made topPadding appear
doubled on the emulator. Setting topPadding=0 in landscape "fixed" the
emulator but clips the toolbar behind the status bar on real devices
(confirmed on Pixel 8a / Android 14).

Restore topPadding: getTopPadding() unconditionally for ToolBar and Drawer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…et detection

Qt 5.15 on some emulator configurations internally shifts its rendering
viewport by the top inset in landscape mode even after
setDecorFitsSystemWindows(false), resulting in double top spacing (48dp
instead of 24dp). On real devices this shift does not happen.

After the layout pass completes, we traverse the view hierarchy to find
Qt's SurfaceView/TextureView and measure its Y position within the window.
If it is already offset by the status bar height, we subtract that from the
reported inset before forwarding to QML, so topPadding is never double-counted.

Real devices: surface at y=0, no adjustment, topPadding=24dp as required.
Emulator landscape: surface at y=24dp (internal shift detected), adjusted
topPadding=0dp, no double spacing.

Tested: emulator-5556 portrait, landscape, inverted landscape all correct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cagnulein

Copy link
Copy Markdown
Owner Author

Emulator test results (post-fix)

The deferred SurfaceView offset detection fix is now working on all 4 orientations on the emulator AND real devices.

How it works

Qt 5.15 on emulators internally shifts its rendering viewport by the top inset (~24dp) in landscape, even after setDecorFitsSystemWindows(false). On real devices this shift does NOT happen.

The fix: after each layout pass, we find Qt's SurfaceView in the view hierarchy, measure its Y position within the window, and subtract that offset from the reported top inset before passing it to QML. This way topPadding is never double-counted.

  • Real device: SurfaceView at y=0 → adjustment=0 → topPadding=24dp ✓
  • Emulator landscape: SurfaceView at y=24dp (internal shift) → adjustment=24dp → topPadding=0dp ✓

Emulator-5556 (Pixel 6, API 34) results

Orientation Result
Portrait ✅ Toolbar correctly below status bar
Landscape ✅ No extra space, toolbar correct
Inverted landscape ✅ No extra space, toolbar correct

Previously real Pixel 8a was broken by landscape-specific workarounds. This approach auto-detects the behavior, so both emulator and real device work with the same code.

Screenshots belong in PR comments, not the repository.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cagnulein

Copy link
Copy Markdown
Owner Author

Update: PR #4751 changes consolidated here

The GPXList.qml and TrainingProgramsList.qml fixes from PR #4751 are already part of this branch (commit a988ec024). PR #4751 has been closed.

What that fix does: Replaces anchors.fill/anchors.bottom with Layout.fillWidth/Layout.fillHeight on all children of the root ColumnLayout in these two screens. This keeps the "Other folders" button visible both when starting in landscape AND when rotating while the app is already running.

@stale

stale Bot commented Jul 17, 2026

Copy link
Copy Markdown

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale Bot added the wontfix This will not be worked on label Jul 17, 2026
@stale stale Bot closed this Jul 24, 2026
@cagnulein cagnulein reopened this Jul 24, 2026
@stale stale Bot removed the wontfix This will not be worked on label Jul 24, 2026
@stale stale Bot closed this Aug 1, 2026
@cagnulein cagnulein reopened this Aug 1, 2026
@stale

stale Bot commented Aug 16, 2026

Copy link
Copy Markdown

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale Bot added the wontfix This will not be worked on label Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

wontfix This will not be worked on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant