GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
dropdown: popover_anchor — the menu can hang off a larger control
An opt-in anchor rect substituted at the top of popover_geom: the open menu
positions and sizes itself off THAT rect instead of the trigger's. The
parameter pane's textpick rows use it to anchor the picker's menu to the
whole text-box well, so the completion list drops as one continuous surface
spanning the field — the button stays a 24px sliver, but the menu is the
field's. Everything downstream (drawn/unified geometry, hit reach, popover
registration) follows popover_geom, so the override carries through
animation, painting, and input for free.
src/widget/container/parameters_bg.rs | 21 +++++++++++++++++++++
src/widget/input/dropdown.rs | 7 +++++++
2 files changed, 28 insertions(+)
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 1c897f3..b8bbc0c 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -614,6 +614,14 @@ impl ParametersBg {
let by = r.1 + label_top + inset;
let bh = (r.3 - label_top - 2.0 * inset).max(8.0);
d.set_rect(r.0 + r.2 - PICK_W - inset - 2.0, by, PICK_W, bh);
+ // The menu hangs off the WHOLE field, not the button
+ // sliver: anchor the popover to the box's well band.
+ d.popover_anchor = Some(Rect {
+ x: r.0,
+ y: r.1 + label_top,
+ width: r.2,
+ height: r.3 - label_top,
+ });
} else {
d.set_rect(r.0, r.1, r.2, r.3);
}
@@ -2852,6 +2860,19 @@ mod tests {
// Both text variants lay out at the same row height.
assert_eq!(p.inner().row_height(0), p.inner().row_height(1));
+
+ // The menu anchors off the WHOLE field: the popover spans at least
+ // the box width and hangs below it, not off the button sliver.
+ let anchor = p.choices[0].as_ref().unwrap().popover_anchor.expect("anchor set");
+ assert_eq!(anchor.x, tx);
+ assert_eq!(anchor.width, tw);
+ let d = p.choices[0].as_ref().unwrap();
+ let (px_, py_, pw, _ph) = d.popover_geom(crate::scene::layout::Rect {
+ x: dx, y: dy, width: dw, height: dh,
+ });
+ assert_eq!(px_, tx, "menu left-aligns with the box");
+ assert!(pw >= tw, "menu at least as wide as the box");
+ assert!(py_ >= ty + th - 1.0, "menu hangs below the box");
}
#[test]
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index 35d79e0..df9c81b 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -93,6 +93,11 @@ pub struct Dropdown {
/// production writer). Read by the Ramp popover clamp and the fade-blend parent color,
/// like the legacy `parent` pointer it replaces.
pub parent_snapshot: Option<ParentSnapshot>,
+ /// Optional popover anchor override: the menu hangs off THIS rect instead
+ /// of the trigger's — for triggers embedded in a larger control (the
+ /// parameter pane's textpick picker button nested in its TextBox), where
+ /// the menu should span the whole field, not the button sliver.
+ pub popover_anchor: Option<Rect>,
pub font_family: String,
pub custom_display_text: Option<String>,
pub open_upward: Option<bool>,
@@ -141,6 +146,7 @@ impl Dropdown {
hovered_item: None,
just_changed: false,
parent_snapshot: None,
+ popover_anchor: None,
font_family: "sans-serif".to_string(),
custom_display_text: None,
open_upward: None,
@@ -335,6 +341,7 @@ impl Dropdown {
/// with the base-rect reads rewritten in content-rect terms (`base.y + base.h` ⇒
/// `content.y + content.height`, `base.y + label_offset` ⇒ `content.y`).
pub fn popover_geom(&self, content: Rect) -> (f32, f32, f32, f32) {
+ let content = self.popover_anchor.unwrap_or(content);
let rw = self.popover_width(content);
let rh = self.options.len() as f32 * 24.0;