Skip to content

fix(ui): stable widget ids for Layers rows and XPS workbench tabs - #88

Merged
nmrtist merged 1 commit into
nmrtist:mainfrom
Limdongcheng:fix/egui-id-stability
Sep 4, 2026
Merged

fix(ui): stable widget ids for Layers rows and XPS workbench tabs#88
nmrtist merged 1 commit into
nmrtist:mainfrom
Limdongcheng:fix/egui-id-stability

Conversation

@Limdongcheng

Copy link
Copy Markdown
Contributor

Summary

egui assigns widget ids by position, and two surfaces re-issued ids to the wrong widgets:

  • Layers tree. Inserting a row above (a new canvas, dataset, or result) handed every row below a fresh id at an unchanged rect, which dropped keyboard focus and drag state mid-gesture and tripped egui's id-change debug overlay. Panel, content, and object rows now live in child scopes keyed by their typed ids (PanelId is a UUID; ContentId and ObjectId are canvas-local and pair with the canvas resource id), so a row's ids follow the row. A salted non-global scope is not enough: its unique id still embeds the parent's positional counter.
  • XPS workbench. The four tabs place different controls at the same rects; switching tabs handed the previous tab's ids, and with them any edit or focus state, to the next tab's controls. Each tab body now carries its own id salt.

object_list moves from primary_sidebar.rs (at the 800-line limit) into layers_tree.rs unchanged apart from the new scope.

Validation

  • cargo pr-check passes (fmt, file size, dependency policy, both frontends, clippy with denied warnings, tests).
  • Verified against egui 0.34.3 that UiBuilder::id seeds the child's ids independently of the parent's positional counter, that the three scope keys cannot collide with each other or with the app's existing global scopes, and that nothing in the XPS tabs relied on ids shared across tabs.

UI display logic

  • New or changed actions go through the command catalog (CommandId +
    describe) and are searchable in the command palette. (No actions added.)
  • Hiding happens only at Ribbon-group level (dataset kind or width
    budget); transient states disable with a disabled_reason that says how
    to unblock.
  • No layout changes from background events; panels are never auto-closed.
  • New panels or empty regions show an empty state with a next step. (None
    added.)
  • Docs updated in docs/ (English and zh-CN) for user-visible behavior.
    (No documented behavior changes.)

egui assigns widget ids by position, so two surfaces re-issued ids to
the wrong widgets:

- In the Layers tree, inserting a row above (a new canvas, dataset, or
  result) handed every row below a fresh id at an unchanged rect, which
  dropped keyboard focus and drag state mid-gesture and tripped egui's
  id-change debug overlay. Panel, content, and object rows now live in
  child scopes keyed by their typed ids (PanelId is a UUID; ContentId and
  ObjectId are canvas-local and pair with the canvas resource id), so a
  row's ids follow the row. A salted non-global scope is not enough: its
  unique id still embeds the parent's positional counter.
- The XPS workbench's four tabs place different controls at the same
  rects; a tab switch handed the previous tab's ids, and with them any
  edit or focus state, to the next tab's controls. Each tab body now
  carries its own id salt.

object_list moves from primary_sidebar.rs (at the 800-line limit) into
layers_tree.rs unchanged apart from the new scope.
@nmrtist
nmrtist merged commit d23d44f into nmrtist:main Sep 4, 2026
11 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants