GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
fix(context_menu): a menu with a slider row no longer panics on paint
The slider rows' paint stamp is dropped at the end of paint, under the
CONTEXT_MENU borrow paint_with_labels holds, and its Drop clears widget
references in that same cell: borrow_mut panicked on every paint of such
a menu, killing the designer on any viewport right-click. An already
borrowed cell means the drop came from inside the menu's own code, which
cannot be dropping the menu's target, so there is nothing to clear.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/widget/core.rs | 24 +++++++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/src/widget/core.rs b/src/widget/core.rs
index f6d4068..860b53b 100644
--- a/src/widget/core.rs
+++ b/src/widget/core.rs
@@ -924,7 +924,13 @@ pub mod context_menu {
pub fn clear_if_matches(w: &dyn WidgetHost) {
let id = w.base().id();
CONTEXT_MENU.with(|m| {
- let mut menu = m.borrow_mut();
+ // Already borrowed means the widget is being dropped from INSIDE
+ // the menu's own code — the slider rows' paint stamp, dropped at
+ // the end of `paint` under `paint_with_labels`' borrow. A widget
+ // the menu made for itself cannot be its target, so there is
+ // nothing to clear; `borrow_mut` here panicked on every paint of
+ // a menu with a slider row.
+ let Ok(mut menu) = m.try_borrow_mut() else { return };
if menu.target == Some(id) {
menu.target = None;
menu.visible = false;
@@ -1120,6 +1126,22 @@ mod context_menu_slider_tests {
m.row_y(idx) + ROW_H * 0.5
}
+ /// Painted through the thread-local, as every host paints it: the slider
+ /// stamp is dropped while `CONTEXT_MENU` is borrowed, and its drop clears
+ /// widget references in that same cell. The tests above paint a bare
+ /// `ContextMenuState` and never held the borrow.
+ #[test]
+ fn a_slider_row_paints_through_the_shared_menu() {
+ use super::context_menu as cm;
+ cm::show(100.0, 50.0, vec!["Frame All".into(), "Opacity".into()], 0, WidgetId(7));
+ cm::set_row_slider(1, MenuSlider { value: 50.0, min: 0.0, max: 100.0, step: 5.0, decimals: 0, suffix: "%" });
+ let mut pc = crate::scene::paint::PaintCtx::new();
+ cm::paint_with_labels(&mut pc);
+ cm::paint(&mut pc);
+ assert!(cm::is_visible(), "painting leaves the menu up");
+ cm::hide();
+ }
+
/// A notch over the slider row steps it by `step`, up is more, and the
/// change is reported once; over an action row the wheel is not the
/// menu's. A trackpad's fractions add up to whole steps.