Skip to content

fix(timerangeselector): support uifigure parents (replace uicontrol labels with uilabel when needed)#145

Merged
HanSur94 merged 3 commits into
mainfrom
claude/fix-trs-uifigure-compat
May 19, 2026
Merged

fix(timerangeselector): support uifigure parents (replace uicontrol labels with uilabel when needed)#145
HanSur94 merged 3 commits into
mainfrom
claude/fix-trs-uifigure-compat

Conversation

@HanSur94
Copy link
Copy Markdown
Owner

Root cause

TimeRangeSelector is used in two contexts:

  1. Classical figuresDashboardEngine.render() creates a figure(...); uicontrol is fine.
  2. uifiguresCompanionEventViewer.buildFigure_() creates a uifigure(...); uicontrol errors out with "Functionality not supported with figures created with the uifigure function."

The latter is currently broken — the MATLAB Tests (A-D) CI failure on main (TestCompanionEventViewer/testConstructorOpensFigure and ~14 sibling tests) all blow up at TimeRangeSelector/buildGraphics_:726 where the first of three uicontrol('Style', 'text', ...) calls runs.

Fix

  • Detect the parent figure type once in the constructor via isprop(hAncFig, 'AutoResizeChildren'). That property exists on uifigure only; classical figure() handles lack it even though both report class matlab.ui.Figure on R2020b+. Stored in a new private IsUIFigureParent_ flag.
  • Branch in buildGraphics_:
    • Classical → existing uicontrol('Style','text', ...) with normalized Position (extracted into buildLabelsClassical_, bit-for-bit identical to the previous code).
    • uifigure → new buildLabelsUIFigure_ that creates uilabel widgets with pixel Position and installs a SizeChangedFcn on obj.hPanel to recompute positions on resize. The pixel rectangles mirror the classical normalized layout ([0.045 0.05 0.30 0.32], [0.36 ...], [0.66 ...]). The same pattern (panel-SizeChangedFcn → recompute pixel layout) is used by MultiStatusWidget, IconCardWidget, and TextWidget.
  • Chain any prior SizeChangedFcn on the panel so we don't strand sibling resize handlers; the saved handle is restored in restoreCallbacks_ (called from delete).
  • Refactor setRangeLabels to dispatch through a new private setLabelText_(hLabel, str) helper that picks Text (uilabel) or String (uicontrol) based on IsUIFigureParent_, with a defensive fallback to the other property if the primary set throws. setRangeLabels itself is now branch-free.

Files changed

  • libs/Dashboard/TimeRangeSelector.m — 178 insertions, 21 deletions (1 file)

New private members:

  • properties: IsUIFigureParent_, OldPanelSizeChangedFcn_
  • methods: buildLabelsClassical_, buildLabelsUIFigure_, layoutUIFigureLabels_, onPanelResized_, setLabelText_

restoreCallbacks_ extended to restore the saved SizeChangedFcn when we hijacked it.

Backward compatibility

  • The dashboard path (DashboardEngine.render → classical figure(...)) hits buildLabelsClassical_, which contains the original three uicontrol calls verbatim — same options, same positions, same String writes via setLabelText_.
  • Public API of TimeRangeSelector is unchanged. setRangeLabels(leftText, rightText, middleText) signature is unchanged. Public properties (hRangeLabelLeft/Middle/Right, RangeLeftText/MiddleText/RightText) are unchanged.
  • The hRangeLabel* handles change underlying class (uicontroluilabel) on the uifigure path. They're declared SetAccess = private and a grep across libs/, tests/, examples/, benchmarks/, demo/ shows no external code touches .String, .Text, .ForegroundColor, .BackgroundColor, or any other widget-type-dependent property — the only readers are inside TimeRangeSelector.m itself, which already dispatches through setLabelText_.
  • No new toolboxes; pure MATLAB.
  • Namespaced error IDs preserved (TimeRangeSelector:*).

Test files exercised by this change

Existing tests still hold (no API surface changed):

  • tests/test_time_range_selector.m — 6 invariants over setSelection/setDataRange/getSelection/OnRangeChanged (uses classical figure('Visible','off') → classical path)
  • tests/test_time_range_selector_event_markers.msetEventMarkers polyline behavior (classical path)
  • tests/test_time_range_selector_reinstall_after_rerender.mreinstallCallbacks after rerender (classical path)
  • tests/suite/TestTimeRangeSelectorEventMarkers.m — class-based variant (classical path)
  • tests/test_dashboard_preview_overlay.m, test_dashboard_preview_envelope.m, test_dashboard_range_selector_integration.m, test_dashboard_engine_event_markers.m — DashboardEngine integration (classical path)
  • tests/suite/TestCompanionEventViewer.mthe failing suite (uifigure path). testConstructorOpensFigure + ~14 siblings should now pass since buildGraphics_ no longer throws when the parent is a uifigure.

Test plan

  • CI: MATLAB Tests (A-D) — TestCompanionEventViewer/testConstructorOpensFigure and siblings flip green
  • CI: MATLAB Tests for TestRangeSelector / TestTimeRangeSelectorEventMarkers / classical TimeRangeSelector tests stay green
  • CI: Dashboard integration tests (test_dashboard_range_selector_integration.m etc.) stay green
  • Manual: open a dashboard via DashboardEngine — slider labels still update on selection drag (classical path)
  • Manual: open CompanionEventViewer — slider mounts inside the uifigure without error and label text updates on drag (uifigure path)

Caveats / known limitations

  • The SizeChangedFcn chaining is best-effort. If a future parent (e.g. some uigridlayout cell) refuses SizeChangedFcn, the labels stay at their initial pixel position. This is acceptable for a fixed-height slider strip and isolated by try/catch so it never breaks construction.
  • The fallback paths inside setLabelText_ (try Text → fall back to String, and vice versa) exist purely for defensive parity if a future refactor crosses widget types under this class. Today only one branch ever fires.

Pre-existing mh_style warnings (bg unused at L440, try, ... catch, end extra-comma at L702/703) were left untouched — they are not on lines this PR modifies.

Generated with Claude Code

uicontrol is not supported inside figures created with uifigure(...),
so TimeRangeSelector crashes when CompanionEventViewer hosts it
(MATLAB Tests (A-D) CI: TestCompanionEventViewer ~15 tests).

Detect uifigure parents in the constructor via isprop(hAncFig,
'AutoResizeChildren') — that property exists on uifigure only; classical
figure handles lack it even though both report class matlab.ui.Figure.
buildGraphics_ then dispatches to either buildLabelsClassical_ (existing
uicontrol path, normalized Position, unchanged) or buildLabelsUIFigure_
(new uilabel path, pixel Position recomputed on every hPanel
SizeChangedFcn — same pattern as MultiStatusWidget, IconCardWidget,
TextWidget).

setRangeLabels now routes through a single setLabelText_ helper that
writes Text on uilabel and String on uicontrol, so the public contract
is unchanged for DashboardEngine. The panel's previous SizeChangedFcn
is saved and re-fired so we don't strand sibling resize handlers. The
saved handle is restored on delete via restoreCallbacks_.

Backward compatible with the dashboard path (DashboardEngine still
creates a classical figure() and the uicontrol code path is bit-for-bit
identical). No new toolboxes; pure MATLAB.

Verified statically: `mh_style` clean on touched code (3 pre-existing
warnings on lines unrelated to this fix); only uicontrol callsites in
the file are inside buildLabelsClassical_; setRangeLabels' label-handle
access now lives in one helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 19, 2026

Codecov Report

❌ Patch coverage is 67.10526% with 25 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
libs/Dashboard/TimeRangeSelector.m 67.10% 25 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'FastSense Performance'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.10.

Benchmark suite Current: bbf3e4c Previous: c271749 Ratio
Downsample mean std(5M) 0.099 ms 0.063 ms 1.57
Render mean std(5M) 1.701 ms 0.121 ms 14.06
Zoom cycle mean std(5M) 0.726 ms 0.377 ms 1.93
Downsample mean std10M) 0.213 ms 0.075 ms 2.84
Instantiation mean std10M) 0.901 ms 0.79 ms 1.14
Render mean std10M) 3.855 ms 2.544 ms 1.52
Downsample mean (50M) 86.153 ms 77.586 ms 1.11
Instantiation mean std50M) 14.535 ms 2.592 ms 5.61
Zoom cycle mean std50M) 0.439 ms 0.352 ms 1.25
Downsample mean (100M) 173.133 ms 152.908 ms 1.13
Downsample mean ( std00M) 1.684 ms 0.548 ms 3.07
Instantiation mean ( std00M) 148.409 ms 46.52 ms 3.19
Downsample mean ( std00M) 3.504 ms 0.548 ms 6.39
Instantiation mean ( std00M) 718.82 ms 46.52 ms 15.45
Render mean ( std00M) 94.167 ms 2.858 ms 32.95
Zoom cycle mean ( std00M) 1.069 ms 0.617 ms 1.73

This comment was automatically generated by workflow using github-action-benchmark.

CC: @HanSur94

HanSur94 added 2 commits May 19, 2026 08:53
…n isprop heuristic)

The original isprop(fig, 'AutoResizeChildren') discriminator returns TRUE for BOTH classical figures AND uifigures on R2020b+ — confirmed in a live MATLAB probe. Result: every classical-figure construction misroutes to buildLabelsUIFigure_, which then errors because uilabel cannot be parented to a classical figure's uipanel on CI Linux MATLAB. This broke ~14 test files in batch Dashboard and 4 other batches on PR #145's CI.

Replace with a hidden-probe pattern: try uicontrol on hPanel; if MATLAB rejects it ("Functionality not supported with figures created with the uifigure function"), switch to uilabel. Bulletproof across MATLAB releases — actually tests parent compatibility rather than guessing from properties.

Verified locally: classical figure → classical path (uicontrol), label class = matlab.ui.control.UIControl. uifigure on local macOS also takes the classical path because macOS MATLAB allows uicontrol-in-uifigure (CI Linux rejects it, which is the case the probe catches).
@HanSur94 HanSur94 merged commit 5d70901 into main May 19, 2026
17 of 18 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.

1 participant