fix(ribbon): float annotate style dropdowns so they aren't clipped (#153)
The text / dimension / multileader / table style combos rendered their open list inline inside the ribbon row, so the popup was clipped to the fixed ribbon-row height and got cut off even with only two entries. The Draw-tab dropdowns and the layer combo avoid this by rendering as floating overlays. Render the style list as an overlay too: the combo now draws only its button inline, and a new `style_combo_overlay` builds the list as a floating panel that grows to fit its entries, positioned with the existing `compute_dropdown_left` (which already accounts for StyleComboGroup widths) and mirroring `layer_combo_overlay`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7086a79c79
commit
73497850e2
2 changed files with 141 additions and 132 deletions
|
|
@ -567,6 +567,13 @@ impl Ribbon {
|
||||||
return self.prop_lw_overlay();
|
return self.prop_lw_overlay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Style combo dropdowns (annotate tab) float as overlays so the list
|
||||||
|
// isn't clipped by the fixed ribbon-row height, the way the Draw-tab
|
||||||
|
// dropdowns already are. (#153)
|
||||||
|
if let Some(ov) = self.style_combo_overlay(open_id) {
|
||||||
|
return Some(ov);
|
||||||
|
}
|
||||||
|
|
||||||
let module = self.modules.get(self.active)?;
|
let module = self.modules.get(self.active)?;
|
||||||
let groups = module.ribbon_groups();
|
let groups = module.ribbon_groups();
|
||||||
let mut items_list: Option<Vec<(&'static str, &'static str, IconKind)>> = None;
|
let mut items_list: Option<Vec<(&'static str, &'static str, IconKind)>> = None;
|
||||||
|
|
@ -804,6 +811,131 @@ impl Ribbon {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Floating popup for an annotate-tab style combo (text / dimension /
|
||||||
|
/// multileader / table style). Returns `None` when `open_id` is not a
|
||||||
|
/// style combo. Built as an overlay — like the layer combo and the
|
||||||
|
/// Draw-tab dropdowns — so the list grows to fit its entries instead of
|
||||||
|
/// being clipped to the ribbon-row height. (#153)
|
||||||
|
fn style_combo_overlay(&self, open_id: &str) -> Option<Element<'_, Message>> {
|
||||||
|
let groups = self.modules.get(self.active)?.ribbon_groups();
|
||||||
|
|
||||||
|
// Locate the open style combo; capture its style key + manager command.
|
||||||
|
let mut found: Option<(crate::modules::StyleKey, Option<&'static str>)> = None;
|
||||||
|
'outer: for group in &groups {
|
||||||
|
for item in &group.tools {
|
||||||
|
if let RibbonItem::StyleComboGroup {
|
||||||
|
style_key,
|
||||||
|
combo_id,
|
||||||
|
manager_cmd,
|
||||||
|
..
|
||||||
|
} = item
|
||||||
|
{
|
||||||
|
if *combo_id == open_id {
|
||||||
|
found = Some((*style_key, *manager_cmd));
|
||||||
|
break 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (style_key, manager_cmd) = found?;
|
||||||
|
|
||||||
|
let ctx = StyleContext {
|
||||||
|
text_style_names: self.text_style_names.clone(),
|
||||||
|
active_text_style: self.active_text_style.clone(),
|
||||||
|
dim_style_names: self.dim_style_names.clone(),
|
||||||
|
active_dim_style: self.active_dim_style.clone(),
|
||||||
|
mleader_style_names: self.mleader_style_names.clone(),
|
||||||
|
active_mleader_style: self.active_mleader_style.clone(),
|
||||||
|
table_style_names: self.table_style_names.clone(),
|
||||||
|
active_table_style: self.active_table_style.clone(),
|
||||||
|
};
|
||||||
|
let active = ctx.active_for(style_key).to_string();
|
||||||
|
|
||||||
|
let row_style = |_: &Theme, status: button::Status| button::Style {
|
||||||
|
background: Some(Background::Color(match status {
|
||||||
|
button::Status::Hovered | button::Status::Pressed => ROW_HOVER,
|
||||||
|
_ => Color::TRANSPARENT,
|
||||||
|
})),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut rows: Vec<Element<Message>> = ctx
|
||||||
|
.names_for(style_key)
|
||||||
|
.iter()
|
||||||
|
.map(|name| {
|
||||||
|
let is_sel = name.as_str() == active.as_str();
|
||||||
|
let n = name.clone();
|
||||||
|
let checkmark: Element<Message> = container(if is_sel {
|
||||||
|
crate::ui::icons::tinted(crate::ui::icons::CHECK, 11.0, CHECK_COLOR)
|
||||||
|
} else {
|
||||||
|
iced::widget::Space::new().width(0).into()
|
||||||
|
})
|
||||||
|
.width(Length::Fixed(14.0))
|
||||||
|
.into();
|
||||||
|
button(
|
||||||
|
row![
|
||||||
|
checkmark,
|
||||||
|
text(name.clone())
|
||||||
|
.size(11)
|
||||||
|
.color(if is_sel { LABEL_ON } else { LABEL_OFF }),
|
||||||
|
]
|
||||||
|
.spacing(4)
|
||||||
|
.align_y(iced::Center),
|
||||||
|
)
|
||||||
|
.on_press(Message::RibbonStyleChanged {
|
||||||
|
key: style_key,
|
||||||
|
name: n,
|
||||||
|
})
|
||||||
|
.style(row_style)
|
||||||
|
.width(Fill)
|
||||||
|
.padding([4, 10])
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if let Some(mgr) = manager_cmd {
|
||||||
|
rows.push(
|
||||||
|
button(text("Manage…").size(11).color(LABEL_ON))
|
||||||
|
.on_press(Message::Command(mgr.to_string()))
|
||||||
|
.style(row_style)
|
||||||
|
.width(Fill)
|
||||||
|
.padding([4, 10])
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let panel = container(column(rows))
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(PANEL_BG)),
|
||||||
|
border: Border {
|
||||||
|
color: PANEL_BORDER,
|
||||||
|
width: 1.0,
|
||||||
|
radius: 3.0.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Length::Fixed(LARGE_W * 2.3));
|
||||||
|
|
||||||
|
let top_offset = 28.0 + TOOL_BAR_H;
|
||||||
|
let left_offset = compute_dropdown_left(&groups, open_id);
|
||||||
|
let positioned = container(panel)
|
||||||
|
.align_left(Fill)
|
||||||
|
.align_top(Fill)
|
||||||
|
.padding(Padding {
|
||||||
|
top: top_offset,
|
||||||
|
left: left_offset,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill);
|
||||||
|
|
||||||
|
Some(
|
||||||
|
mouse_area(positioned)
|
||||||
|
.on_press(Message::CloseRibbonDropdown)
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn prop_color_overlay(&self) -> Option<Element<'_, Message>> {
|
fn prop_color_overlay(&self) -> Option<Element<'_, Message>> {
|
||||||
let picker = crate::ui::color_select::color_list(
|
let picker = crate::ui::color_select::color_list(
|
||||||
crate::ui::color_select::ColorExtras {
|
crate::ui::color_select::ColorExtras {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use acadrust::types::{Color as AcadColor, LineWeight};
|
||||||
// Ribbon tooltips anchor to the right of their button so the cursor — which
|
// Ribbon tooltips anchor to the right of their button so the cursor — which
|
||||||
// rests on the button itself — never covers the tip text. (#143)
|
// rests on the button itself — never covers the tip text. (#143)
|
||||||
use iced::widget::tooltip::Position as TipPos;
|
use iced::widget::tooltip::Position as TipPos;
|
||||||
use iced::widget::{button, column, container, row, scrollable, svg, text, tooltip};
|
use iced::widget::{button, column, container, row, svg, text, tooltip};
|
||||||
use iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};
|
use iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};
|
||||||
|
|
||||||
use crate::app::Message;
|
use crate::app::Message;
|
||||||
|
|
@ -177,7 +177,7 @@ pub(super) struct StyleContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyleContext {
|
impl StyleContext {
|
||||||
fn names_for(&self, key: StyleKey) -> &[String] {
|
pub(super) fn names_for(&self, key: StyleKey) -> &[String] {
|
||||||
match key {
|
match key {
|
||||||
StyleKey::TextStyle => &self.text_style_names,
|
StyleKey::TextStyle => &self.text_style_names,
|
||||||
StyleKey::DimStyle => &self.dim_style_names,
|
StyleKey::DimStyle => &self.dim_style_names,
|
||||||
|
|
@ -185,7 +185,7 @@ impl StyleContext {
|
||||||
StyleKey::TableStyle => &self.table_style_names,
|
StyleKey::TableStyle => &self.table_style_names,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn active_for(&self, key: StyleKey) -> &str {
|
pub(super) fn active_for(&self, key: StyleKey) -> &str {
|
||||||
match key {
|
match key {
|
||||||
StyleKey::TextStyle => &self.active_text_style,
|
StyleKey::TextStyle => &self.active_text_style,
|
||||||
StyleKey::DimStyle => &self.active_dim_style,
|
StyleKey::DimStyle => &self.active_dim_style,
|
||||||
|
|
@ -859,11 +859,10 @@ pub(super) fn render_large<'a>(
|
||||||
RibbonItem::StyleComboGroup {
|
RibbonItem::StyleComboGroup {
|
||||||
style_key,
|
style_key,
|
||||||
combo_id,
|
combo_id,
|
||||||
manager_cmd,
|
|
||||||
rows,
|
rows,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
const STYLE_COMBO_W: f32 = LARGE_W * 2.3;
|
const STYLE_COMBO_W: f32 = LARGE_W * 2.3;
|
||||||
let names: Vec<String> = style_ctx.names_for(style_key).to_vec();
|
|
||||||
let active: String = style_ctx.active_for(style_key).to_string();
|
let active: String = style_ctx.active_for(style_key).to_string();
|
||||||
let is_open = open_dd.as_deref() == Some(combo_id);
|
let is_open = open_dd.as_deref() == Some(combo_id);
|
||||||
|
|
||||||
|
|
@ -933,133 +932,11 @@ pub(super) fn render_large<'a>(
|
||||||
.padding([3, 8])
|
.padding([3, 8])
|
||||||
.width(Fill);
|
.width(Fill);
|
||||||
|
|
||||||
// ── style items panel (when open) ──
|
// The open style list renders as a floating overlay
|
||||||
let items_panel: Element<Message> = if is_open {
|
// (`Ribbon::style_combo_overlay`) so it isn't clipped by the fixed
|
||||||
let items_col: Vec<Element<Message>> = names
|
// ribbon-row height — matching the Draw-tab dropdowns. (#153)
|
||||||
.into_iter()
|
let items_panel: Element<Message> =
|
||||||
.map(|name| {
|
iced::widget::Space::new().width(0).height(0).into();
|
||||||
let is_sel = name.as_str() == active.as_str();
|
|
||||||
let n = name.clone();
|
|
||||||
let key = style_key;
|
|
||||||
button(
|
|
||||||
row![
|
|
||||||
container(if is_sel {
|
|
||||||
icons::tinted(
|
|
||||||
icons::CHECK,
|
|
||||||
10.0,
|
|
||||||
Color {
|
|
||||||
r: 0.2,
|
|
||||||
g: 0.8,
|
|
||||||
b: 0.4,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
iced::widget::Space::new().width(0).into()
|
|
||||||
})
|
|
||||||
.width(12),
|
|
||||||
text(name).size(11).color(Color::WHITE),
|
|
||||||
]
|
|
||||||
.spacing(6)
|
|
||||||
.align_y(iced::Center),
|
|
||||||
)
|
|
||||||
.on_press(Message::RibbonStyleChanged { key, name: n })
|
|
||||||
.style(move |_: &Theme, status| button::Style {
|
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered | button::Status::Pressed => Color {
|
|
||||||
r: 0.28,
|
|
||||||
g: 0.28,
|
|
||||||
b: 0.28,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
_ if is_sel => Color {
|
|
||||||
r: 0.20,
|
|
||||||
g: 0.35,
|
|
||||||
b: 0.55,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
_ => Color {
|
|
||||||
r: 0.16,
|
|
||||||
g: 0.16,
|
|
||||||
b: 0.16,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([4, 10])
|
|
||||||
.width(Fill)
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Optional "Open Manager…" row
|
|
||||||
let mut full_col = items_col;
|
|
||||||
if let Some(mgr_cmd) = manager_cmd {
|
|
||||||
full_col.push(
|
|
||||||
button(text(format!("Manage…")).size(10).color(Color {
|
|
||||||
r: 0.5,
|
|
||||||
g: 0.8,
|
|
||||||
b: 1.0,
|
|
||||||
a: 1.0,
|
|
||||||
}))
|
|
||||||
.on_press(Message::Command(mgr_cmd.to_string()))
|
|
||||||
.style(|_: &Theme, status| button::Style {
|
|
||||||
background: Some(Background::Color(match status {
|
|
||||||
button::Status::Hovered => Color {
|
|
||||||
r: 0.24,
|
|
||||||
g: 0.24,
|
|
||||||
b: 0.24,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
_ => Color {
|
|
||||||
r: 0.13,
|
|
||||||
g: 0.13,
|
|
||||||
b: 0.13,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.padding([4, 10])
|
|
||||||
.width(Fill)
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
container(
|
|
||||||
scrollable(
|
|
||||||
container(column(full_col).spacing(1))
|
|
||||||
.width(Fill)
|
|
||||||
.padding(4),
|
|
||||||
)
|
|
||||||
.height(Length::Shrink),
|
|
||||||
)
|
|
||||||
.max_height(180.0)
|
|
||||||
.width(Length::Fixed(STYLE_COMBO_W))
|
|
||||||
.style(|_: &Theme| container::Style {
|
|
||||||
background: Some(Background::Color(Color {
|
|
||||||
r: 0.14,
|
|
||||||
g: 0.14,
|
|
||||||
b: 0.14,
|
|
||||||
a: 0.98,
|
|
||||||
})),
|
|
||||||
border: Border {
|
|
||||||
color: Color {
|
|
||||||
r: 0.35,
|
|
||||||
g: 0.35,
|
|
||||||
b: 0.35,
|
|
||||||
a: 1.0,
|
|
||||||
},
|
|
||||||
width: 1.0,
|
|
||||||
radius: 4.0.into(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
iced::widget::Space::new().width(0).height(0).into()
|
|
||||||
};
|
|
||||||
|
|
||||||
// ── tool rows below combo ──
|
// ── tool rows below combo ──
|
||||||
let make_tool_row = |tools: Vec<ToolDef>| -> Element<Message> {
|
let make_tool_row = |tools: Vec<ToolDef>| -> Element<Message> {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue