Skip to content

fix(maps): create the map window with an alpha channel so the backdrop works - #100

Merged
prokopto-dev merged 1 commit into
masterfrom
ao/nparseplus-22/map-backdrop-alpha
Aug 13, 2026
Merged

fix(maps): create the map window with an alpha channel so the backdrop works#100
prokopto-dev merged 1 commit into
masterfrom
ao/nparseplus-22/map-backdrop-alpha

Conversation

@prokopto-dev

Copy link
Copy Markdown
Owner

Closes #99.

What the report was

"After the ghosting fix the map background transparency doesn't work at all",
clarified as "it just seemed to start working after clicking Apply twice,
rather than just once". Reported on Windows.

The actual mechanism (the update-mode hypothesis was wrong)

The value is not one Apply late, and the viewport update mode is not the
problem. Traced in the real app, a single Apply does all of this correctly:

[before]          config=100 canvas=100 alpha=255 mode=MinimalViewportUpdate
  -> apply_backdrop_opacity(40) from sync_map_chrome
[after ONE apply] config=40  canvas=40  alpha=102 mode=FullViewportUpdate
                  paints=[QRect(0, 0, 400, 400), ...]

apply_backdrop_opacity derives the mode from the freshly clamped new
value, and it does force a repaint — the whole viewport is repainted on the
first Apply. Both halves of the hypothesis are false.

What was actually wrong: the maps window had no alpha channel, so a
below-100% backdrop had nothing to composite into and read as opaque black at
every setting.

Maps._build_chrome() set WA_TranslucentBackground, but it runs after
ParserWindow.__init__ has already called self.show() — which it does
whenever the map was open at last quit, i.e. normally. QWidget re-requests
the surface format when the attribute is set, but QWindow::setFormat() after
create() does not recreate the window, so the request was never granted:

attribute BEFORE show -> alphaBufferSize 8
attribute AFTER  show -> alphaBufferSize -1     # no alpha channel
  ... after a setWindowFlags() recreation -> 8

The real app reported alphaBufferSize = -1 at launch and 8 after one
Settings Apply — because Apply reaches apply_window_state()_set_flags()
setWindowFlags(), which recreates the native window and grants the pending
format. That is why the backdrop "starts working after an Apply", and why
the number of clicks was fuzzy: it depends on when the recreated window next
repaints.

Windows is where it bites (alphaBufferSize > 0 is what makes Qt create a
layered window there; macOS windows always carry an alpha channel), but the
ordering bug is platform-independent and is asserted as such.

Why #65 turned it from cosmetic into total

The missing alpha channel predates #65 (it arrives with the map chrome in
07e37d6). #65 changed drawBackground to composite with
CompositionMode_Source, which writes colour and alpha — into a surface
with no alpha channel that discards it outright. The old SourceOver fill
accumulated over the previous frame instead, which looked partly see-through at
first and drifted toward opaque. That accumulation was the ghosting #65
fixed; removing it exposed the missing channel underneath.

The fix

One line moves: WA_TranslucentBackground goes from _build_chrome to
_set_flags. setWindowFlags is the thing that (re)creates the native window,
so the attribute is now set before every creation rather than before none
of them.

#65 is untouched

drawBackground's CompositionMode_Source fill, the by-hand painter-state
restore (DontSavePainterState means Qt does not save/restore around the
call), the antialias-conditional DontAdjustForAntialiasing and the
backdrop-conditional viewport update mode all stay exactly as they are. Its six
tests in tests/ui/test_map_chrome.py still pass.

The antialias toggle does NOT have the same shape

maps.antialias is read only in MapCanvas.__init__, so it is not recomputed
from a stale value — it is not recomputed at all. Flipping the key and driving
the whole apply path leaves DontAdjustForAntialiasing unchanged on the live
canvas; only a new canvas (a restart) picks it up. It also has no settings-window
control, so no user reaches it without editing nparse.config.json. Different
bug shape, not the reported one, left alone.

Tests

Three added to tests/ui/test_maps_window_chrome.py, on a new maps_open
fixture (toggled = True, so ParserWindow.__init__ actually shows the window
— which is the moment the platform window is created; the existing maps
fixture sets it False and so never hit this):

  • test_the_map_window_is_created_with_an_alpha_channelfails without the fix
  • test_the_alpha_channel_does_not_wait_for_a_settings_applyfails without the fix
  • test_one_apply_is_enough_to_move_the_rendered_backdrop — drives the apply
    path once and reads the pixels out of a persistent QImage seeded with the
    previous (opaque) frame, reusing the fix(maps): stop the map ghosting when the backdrop is below 100% #65 double-render harness. This one
    passed before too, which is the point: it pins the half that was never broken.

Verification

QT_QPA_PLATFORM=offscreen uv run pytest → 2220 passed, 2 deselected.
uv run ruff check . && uv run ruff format . → clean.

Driven end-to-end against the real app offscreen (the verify skill), reading
the composited window rather than a forced re-render:

before the fix after the fix
at launch opaque opaque (backdrop is 100)
after ONE apply to 40% still opaque 0.40 ✓
after TWO applies still opaque 0.40
after an explicit viewport().repaint() still opaque 0.40
after a resize (recreates the surface) 0.40 0.40

Risks / follow-ups

  • parsers/discord.py sets WA_TranslucentBackground after ParserWindow
    has shown its window too, so it has the same latent ordering bug. Left out
    of this PR to keep the diff to the reported surface; worth its own issue.

🤖 Generated with Claude Code

…p works

The backdrop opacity appeared to do nothing after the ghosting fix (#65), and
then "seemed to start working after clicking Apply twice". The value was never
late: tracing a real Apply shows _apply_maps writing the legacy key, then
config_updated reaching Maps.sync_map_chrome, which calls
apply_backdrop_opacity ONCE with the new value — brush, mode and a full
viewport repaint all correct on the first click. What an Apply actually did
was reach ParserWindow.apply_window_state -> _set_flags -> setWindowFlags,
which RECREATES the native window.

That recreation was the fix, because the window had no alpha channel to
composite into. Maps._build_chrome set WA_TranslucentBackground, but it runs
after ParserWindow.__init__ has already shown the window (it does whenever the
map was open at last quit — the normal case). QWidget re-requests the surface
format when the attribute is set, but QWindow::setFormat() after create() does
not recreate the window, so the request was never granted: the real app
reported alphaBufferSize -1 at launch and 8 only after an Apply. Windows is
where it bites, since alphaBufferSize > 0 is what makes Qt create a layered
window there while macOS windows always carry alpha; the ordering itself is
platform-independent and is asserted as such.

#65 is what turned this from cosmetic into total. Its drawBackground writes
colour AND alpha with CompositionMode_Source, and a surface with no alpha
channel discards the alpha outright, so every value below 100% read as opaque
black. The old SourceOver fill accumulated over the previous frame instead —
which looked partly see-through at first and drifted toward opaque. That
accumulation WAS the ghosting; removing it exposed the missing channel.

The attribute moves to _set_flags, which is the thing that (re)creates the
native window, so it now runs before every creation rather than before none of
them. Nothing in #65 changes: Source composition, the by-hand painter-state
restore and the update-mode split all stay, and its six tests still pass.

maps.antialias is unaffected — it is read only in MapCanvas.__init__, so it
needs a restart rather than a second Apply, and it has no settings UI.

Closes #99

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@prokopto-dev prokopto-dev left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. The translucent-background attribute is now applied through the virtual flag setup before an initially visible Maps window creates its native surface, and the regression coverage exercises that startup path.

@prokopto-dev
prokopto-dev merged commit 34b06e3 into master Aug 13, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Map backdrop opacity does nothing until an Apply recreates the window (regression from #65)

1 participant