Experimental ComposableElementView - #548
Conversation
…3617c7-97d3-4d36-8529-8dca87615d75-577a Co-authored-by: Cedrick Cooke <development@cedrickc.net>
Replace the per-element clip+clickable/hoverable interaction with a single pointer-input overlay that reuses RootElement.onHover/onClick + isPointInPath (as ElementView does). This fixes hover/click correctness under transforms and at non-unit density, and restores onClickFallback support. Revert clickHandler/ hoverHandler and ComposeKanvas.scope to internal (no longer needed as public). Add Compose UI interaction tests covering click/hover, occlusion, translate, scale, pivoted rotate, and non-unit density. Co-authored-by: Cedrick Cooke <development@cedrickc.net>
- core: ExperimentalKrayonApi annotation - kanvas: Transform.Scale/Rotate.isPivoted - element: @stable $stable fields, Element.mutableChildren - compose: ComposableElement, ComposableElementView, ImmutableMatrix Co-authored-by: Cedrick Cooke <development@cedrickc.net>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 367706d. Configure here.
| if (width != newWidth || height != newHeight) { | ||
| width = newWidth | ||
| height = newHeight | ||
| update(dataState.value) |
There was a problem hiding this comment.
State written during measure
Medium Severity
ComposableElementView assigns width and height and calls update inside the SubcomposeLayout measure lambda. That mutates Compose state and the element tree during measurement, which Compose can apply unpredictably (extra passes, skipped frames, or runtime errors) unlike ElementView, which applies size via SideEffect.
Reviewed by Cursor Bugbot for commit 367706d. Configure here.
| fun update(data: T) { | ||
| if (width != 0f && height != 0f) { | ||
| updateElements.update(root, width, height, data) | ||
| } |
There was a problem hiding this comment.
Negative size not rejected
Low Severity
Chart updates run when width and height are merely non-zero. ElementView only updates when both dimensions are strictly positive, avoiding negative constraint sizes Compose can report; this guard uses != 0f instead.
Reviewed by Cursor Bugbot for commit 367706d. Configure here.
| if (dataState.value != NotSet) { | ||
| @Suppress("UNCHECKED_CAST") | ||
| ComposableElementView(dataState as State<T>, updateElements, modifier) | ||
| } |
There was a problem hiding this comment.
Flow overload renders nothing initially
Low Severity
The Flow overload withholds the entire ComposableElementView until the first emission because collectAsState starts at NotSet. ElementView always mounts its canvas immediately, so cold or slow flows show blank UI instead of an empty chart.
Reviewed by Cursor Bugbot for commit 367706d. Configure here.


Right now it's difficult to test that Krayon charts are working at the level of a integration test. This exploration provides a way to emit elements into the actual UI tree instead of just drawing to a canvas, which should allow for querying them via things like Espresso or Appium.
At time of writing, this is a very incomplete implementation. It falls back to a canvas for everything except touch targets (which have no way to actually select them programmatically; they have been tested on desktop only) and text. Text in particular should be able to provide the bare minimum amount of testability.
Note
Medium Risk
Large experimental surface area across element, compose, and interaction paths; behavior may diverge from canvas
ElementViewand APIs are unstable underExperimentalKrayonApi.Overview
Adds an experimental Compose path that renders Krayon element trees as real UI nodes (not only a single canvas), aimed at integration testing and tooling such as Espresso/Appium.
ComposableElementViewwalks the sameRootElement/UpdateElementflow as canvasElementView, but maps groups, clips, transforms,TextElement(BasicText), and customComposableElementinto composables; everything else still draws via nestedKanvas. Pointer hover/click goes through existingRootElementhit-testing (including transforms and density), with JVM Compose UI tests covering clicks, hover, occlusion, translate/scale/rotate, and non-unit density.Supporting pieces include
ImmutableMatrix(KrayonTransform→ Compose graphics),ExperimentalKrayonApi, global opt-in for experimental/internal APIs inkotlin-conventions, and Compose-stable element trees (mutableStateListOf/mutableStateMapOf,@Stabletypes, exposedmutableChildren).Transform.Rotate/ScalegainisPivotedfor matrix splitting;PathusesInfiniteCache. The desktop sample switches fromElementViewtoComposableElementView.Reviewed by Cursor Bugbot for commit 367706d. Configure here.