Key tips: reference command for shared codes, no code over labels

A code the reference shows on two buttons runs the button whose command
the reference runs for it (VC runs Viewport Configuration), decided by
the code table's order. Labels, combo values and panel titles report
their bounds while key tips are open, and each code takes the first
spot nearest the reference place that covers none of them nor another
code; a collapsed panel's title is measured to place its code beside
the words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-10-14 15:17:38 +03:00
1 parent b5ce285386
commit c2c34e45fa
4 files changed
+270 -97

No files matched your search

+17 -6
View File
@@ -302,9 +302,11 @@ pub(super) fn group_title<'a>(
active_tool: Option<&str>, active_tool: Option<&str>,
) -> Element<'a, Message> { ) -> Element<'a, Message> {
let Some(panel) = panel_for_group(title, group_ids) else { let Some(panel) = panel_for_group(title, group_ids) else {
return container(text(t!(title)).size(9).style(muted_text_style)) let label = keytips::label_anchor(
.padding([1, 4]) || format!("title:{title}"),
.into(); text(t!(title)).size(9).style(muted_text_style),
);
return container(label).padding([1, 4]).into();
}; };
let expanded = open.as_deref().and_then(parent_panel) == Some(panel.id); let expanded = open.as_deref().and_then(parent_panel) == Some(panel.id);
// A command running from the closed slide-out lights its title, the way a // A command running from the closed slide-out lights its title, the way a
@@ -323,7 +325,10 @@ pub(super) fn group_title<'a>(
panel.id, panel.id,
button( button(
row![ row![
PosReport::new(panel.title_id, text(t!(title)).size(9)), keytips::label_anchor(
|| keytips::label_id(panel.id),
PosReport::new(panel.title_id, text(t!(title)).size(9)),
),
arrow arrow
] ]
.spacing(4) .spacing(4)
@@ -417,7 +422,10 @@ fn reference_overlay<'a>(ribbon: &Ribbon, panel: Panel, win: (f32, f32)) -> Elem
let edit = button( let edit = button(
row![ row![
make_icon(IconKind::Svg(tool.icon), 16.0), make_icon(IconKind::Svg(tool.icon), 16.0),
text(t!(tool.label)).size(11) keytips::label_anchor(
|| keytips::label_id(&keytips::item_anchor(panel.id, tool.command)),
text(t!(tool.label)).size(11),
),
] ]
.spacing(6) .spacing(6)
.align_y(iced::Center), .align_y(iced::Center),
@@ -457,7 +465,10 @@ fn reference_overlay<'a>(ribbon: &Ribbon, panel: Panel, win: (f32, f32)) -> Elem
); );
let fading = row![ let fading = row![
switch, switch,
text(t!("Xref fading")).size(11), keytips::label_anchor(
|| keytips::label_id(&keytips::item_anchor(panel.id, keytips::XREF_FADE)),
text(t!("Xref fading")).size(11),
),
iced::widget::slider(0..=90u8, amount, Message::XrefFadeSlide) iced::widget::slider(0..=90u8, amount, Message::XrefFadeSlide)
.on_release(Message::XrefFadeCommit) .on_release(Message::XrefFadeCommit)
.width(Fill), .width(Fill),
+193 -62
View File
@@ -86,6 +86,9 @@ static ALT_ALONE: AtomicBool = AtomicBool::new(false);
thread_local! { thread_local! {
/// Anchors that carried a code in the last drawn frame. /// Anchors that carried a code in the last drawn frame.
static SHOWN: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) }; static SHOWN: RefCell<Vec<String>> = const { RefCell::new(Vec::new()) };
/// Label and panel-title widgets a code must not cover.
static OBSTACLES: RefCell<rustc_hash::FxHashSet<String>> =
RefCell::new(rustc_hash::FxHashSet::default());
} }
/// Id of the whole ribbon (tab row and tools), for the hint bar. /// Id of the whole ribbon (tab row and tools), for the hint bar.
@@ -121,6 +124,27 @@ pub(super) fn anchor_with<'a>(
} }
} }
/// Record the bounds of a label or panel title under `id` while key tips
/// show, so no code is drawn over it.
pub(super) fn label_anchor<'a>(
id: impl FnOnce() -> String,
el: impl Into<Element<'a, Message>>,
) -> Element<'a, Message> {
if !active() {
return el.into();
}
let id = id();
OBSTACLES.with(|ids| {
ids.borrow_mut().insert(id.clone());
});
PosReport::owned(id, el).into()
}
/// Id of the label (or panel title) of the widget with anchor `anchor`.
pub(super) fn label_id(anchor: &str) -> String {
format!("label:{anchor}")
}
/// Anchor id of item `cmd` in dropdown or slide-out `dropdown`. /// Anchor id of item `cmd` in dropdown or slide-out `dropdown`.
pub(super) fn item_anchor(dropdown: &str, cmd: &str) -> String { pub(super) fn item_anchor(dropdown: &str, cmd: &str) -> String {
format!("{dropdown}/{cmd}") format!("{dropdown}/{cmd}")
@@ -210,7 +234,8 @@ const QUICK_ACCESS: &[(&str, &str)] = &[
/// the target is a tool id, a dropdown id or a group title; a group title's /// the target is a tool id, a dropdown id or a group title; a group title's
/// code opens the panel's slide-out or, while the panel is collapsed, its /// code opens the panel's slide-out or, while the panel is collapsed, its
/// flyout. In a dropdown's or slide-out's scope the target is an item's /// flyout. In a dropdown's or slide-out's scope the target is an item's
/// command. /// command. When two buttons share a code the earlier entry runs, as the
/// reference's command for that code.
const CODES: &[(&str, &str, &str)] = &[ const CODES: &[(&str, &str, &str)] = &[
// Draw (the reference Home tab). // Draw (the reference Home tab).
("draw", "Draw", "DW"), ("draw", "Draw", "DW"),
@@ -397,7 +422,7 @@ const CODES: &[(&str, &str, &str)] = &[
("annotate", "DATALINK", "DL"), ("annotate", "DATALINK", "DL"),
("annotate", "WIPEOUT", "WI"), ("annotate", "WIPEOUT", "WI"),
("annotate", "REVCLOUD", "AC"), ("annotate", "REVCLOUD", "AC"),
// The reference shows AC here too; the first one in ribbon order runs. // The reference shows AC here too; typing it runs Revision Cloud.
("annotate", "ANNOTATION_CURRENT_SCALE", "AC"), ("annotate", "ANNOTATION_CURRENT_SCALE", "AC"),
("annotate", "SCALELISTEDIT", "SL"), ("annotate", "SCALELISTEDIT", "SL"),
("annotate", "OBJECTSCALE", "AS"), ("annotate", "OBJECTSCALE", "AS"),
@@ -405,9 +430,10 @@ const CODES: &[(&str, &str, &str)] = &[
// View. // View.
("view", "Viewport Tools", "VT"), ("view", "Viewport Tools", "VT"),
("view", "UCSICON", "UI"), ("view", "UCSICON", "UI"),
("view", "NAVVCUBE", "VC"), // The reference shows VC on Viewport Configuration and on the ViewCube;
// Viewport Configuration carries VC too in the reference. // typing it runs Viewport Configuration.
("view", "VPORTS", "VC"), ("view", "VPORTS", "VC"),
("view", "NAVVCUBE", "VC"),
("view", "PAN", "PA"), ("view", "PAN", "PA"),
("view", "3DORBIT", "OR"), ("view", "3DORBIT", "OR"),
("view", "Model Viewports", "VP"), ("view", "Model Viewports", "VP"),
@@ -445,7 +471,8 @@ const CODES: &[(&str, &str, &str)] = &[
("block_editor", "CCONSTRAINT", "CI"), ("block_editor", "CCONSTRAINT", "CI"),
("block_editor", "PCONSTRAINT", "PA"), ("block_editor", "PCONSTRAINT", "PA"),
("block_editor", "TCONSTRAINT", "TA"), ("block_editor", "TCONSTRAINT", "TA"),
// Collinear and Concentric share CO in the reference; the first runs. // Collinear and Concentric share CO in the reference; typing it runs
// Collinear.
("block_editor", "LCONSTRAINT", "CO"), ("block_editor", "LCONSTRAINT", "CO"),
("block_editor", "GCCONCENTRIC", "CO"), ("block_editor", "GCCONCENTRIC", "CO"),
("block_editor", "QCONSTRAINT", "PE"), ("block_editor", "QCONSTRAINT", "PE"),
@@ -559,7 +586,24 @@ impl Ribbon {
return KeyTipStep::Ignore; return KeyTipStep::Ignore;
}; };
let codes = || tips.iter().filter_map(|tip| tip.code.as_deref()); let codes = || tips.iter().filter_map(|tip| tip.code.as_deref());
let exact = |typed: &str| tips.iter().find(|tip| tip.code.as_deref() == Some(typed)); let scope = if state.top {
""
} else {
self.open_dropdown
.as_deref()
.unwrap_or_else(|| self.modules.get(self.active).map_or("", |m| m.id()))
};
let rank = |tip: &Tip| {
CODES
.iter()
.position(|(s, t, _)| *s == scope && *t == tip.target)
.unwrap_or(usize::MAX)
};
let exact = |typed: &str| {
tips.iter()
.filter(|tip| tip.code.as_deref() == Some(typed))
.min_by_key(|tip| rank(tip))
};
let longer = |typed: &str| codes().any(|code| code.len() > typed.len() && code.starts_with(typed)); let longer = |typed: &str| codes().any(|code| code.len() > typed.len() && code.starts_with(typed));
let typed = ch.map(|ch| format!("{}{ch}", state.typed)); let typed = ch.map(|ch| format!("{}{ch}", state.typed));
let chosen = match typed { let chosen = match typed {
@@ -810,13 +854,21 @@ impl Ribbon {
/// The hint bar layer and the codes layer. /// The hint bar layer and the codes layer.
pub fn keytip_layers(&self) -> Option<(Element<'_, Message>, Element<'_, Message>)> { pub fn keytip_layers(&self) -> Option<(Element<'_, Message>, Element<'_, Message>)> {
let state = self.keytips.as_ref()?; let state = self.keytips.as_ref()?;
let tips: Vec<(String, String, bool)> = self let tips: Vec<(String, String, bool, f32)> = self
.keytip_list() .keytip_list()
.into_iter() .into_iter()
.filter_map(|tip| { .filter_map(|tip| {
let code = tip.code?; let code = tip.code?;
let dropdown = matches!(tip.action, Action::Open(Message::ToggleRibbonDropdown(_))); let dropdown = matches!(tip.action, Action::Open(Message::ToggleRibbonDropdown(_)));
code.starts_with(&state.typed).then_some((tip.anchor, code, dropdown)) // A collapsed panel's title is centred in a wider box: its
// text width places the code beside the words.
let title_w = if tip.anchor.starts_with(COLLAPSED_PREFIX) {
text_width(&crate::t!(tip.target), 9.0, iced::Font::default())
} else {
0.0
};
code.starts_with(&state.typed)
.then_some((tip.anchor, code, dropdown, title_w))
}) })
.collect(); .collect();
let mut path = state.path.join(" › "); let mut path = state.path.join(" › ");
@@ -862,8 +914,8 @@ impl Ribbon {
/// they sit above flyouts and dropdowns, after their widgets reported where /// they sit above flyouts and dropdowns, after their widgets reported where
/// they are. /// they are.
struct KeyTipLayer { struct KeyTipLayer {
/// (anchor, code, opens a dropdown). /// (anchor, code, opens a dropdown, collapsed panel title width).
tips: Vec<(String, String, bool)>, tips: Vec<(String, String, bool, f32)>,
/// Characters of a partial code, shown dimmed on matching tips. /// Characters of a partial code, shown dimmed on matching tips.
typed: usize, typed: usize,
bar: Option<[String; 3]>, bar: Option<[String; 3]>,
@@ -1018,36 +1070,58 @@ impl overlay::Overlay<Message, Theme, Renderer> for KeyTipOverlay<'_> {
let font = bold(renderer); let font = bold(renderer);
let mut shown = Vec::new(); let mut shown = Vec::new();
renderer.with_layer(window, |renderer| { renderer.with_layer(window, |renderer| {
// Place every code; a panel-title code keeps the reference place // Place every code at the first spot, nearest the reference
// and a button code it would cover rises just clear of it. // place first, that covers no label, panel title or code placed
let mut pills: Vec<(&String, &String, Rectangle, bool)> = self // before it.
.layer // A collapsed panel's title counts only where its words are.
.tips let narrow = |id: &str, rect: Rectangle| {
.iter() let title_w = self.layer.tips.iter().find_map(|(anchor, _, _, title_w)| {
.filter_map(|(anchor, code, dropdown)| { (*title_w > 0.0 && label_id(anchor) == id).then_some(*title_w)
let b = fresh_bounds(anchor)?; });
let width = text_width(code, PILL_TEXT, font) + 10.0; match title_w {
let (center, panel) = pill_center(anchor, b, width, *dropdown); Some(w) if w < rect.width => Rectangle::new(
let x = (center.x - width / 2.0) Point::new(rect.center_x() - w / 2.0, rect.y),
.clamp(window.x, (window.width - width).max(window.x)); Size::new(w, rect.height),
let y = (center.y - PILL_H / 2.0) ),
.clamp(window.y, (window.height - PILL_H).max(window.y)); _ => rect,
Some((anchor, code, Rectangle::new(Point::new(x, y), Size::new(width, PILL_H)), panel))
})
.collect();
let panels: Vec<Rectangle> = pills.iter().filter(|p| p.3).map(|p| p.2).collect();
for pill in pills.iter_mut().filter(|p| !p.3) {
for panel in &panels {
// Touching edges do not count.
if panel
.intersection(&pill.2)
.is_some_and(|r| r.width > 1.0 && r.height > 1.0)
{
pill.2.y = pill.2.y.min(panel.y - PILL_H - 1.0);
}
} }
};
let mut blocked: Vec<Rectangle> = OBSTACLES.with(|ids| {
ids.borrow()
.iter()
.filter_map(|id| fresh_bounds(id).map(|rect| narrow(id, rect)))
.collect()
});
let free = |rect: &Rectangle, blocked: &[Rectangle]| {
// Touching edges do not count.
!blocked.iter().any(|other| {
other.intersection(rect).is_some_and(|r| r.width > 1.0 && r.height > 1.0)
})
};
let mut pills: Vec<(&String, &String, Rectangle)> = Vec::new();
for (anchor, code, dropdown, title_w) in &self.layer.tips {
let Some(b) = fresh_bounds(anchor) else {
continue;
};
let width = text_width(code, PILL_TEXT, font) + 10.0;
let rects: Vec<Rectangle> = pill_candidates(anchor, b, width, *dropdown, *title_w)
.into_iter()
.map(|center| {
let x = (center.x - width / 2.0)
.clamp(window.x, (window.width - width).max(window.x));
let y = (center.y - PILL_H / 2.0)
.clamp(window.y, (window.height - PILL_H).max(window.y));
Rectangle::new(Point::new(x, y), Size::new(width, PILL_H))
})
.collect();
let Some(&first) = rects.first() else {
continue;
};
let pill = rects.iter().copied().find(|rect| free(rect, &blocked)).unwrap_or(first);
blocked.push(pill);
pills.push((anchor, code, pill));
} }
for (anchor, code, pill, _) in pills { for (anchor, code, pill) in pills {
renderer.fill_quad( renderer.fill_quad(
renderer::Quad { renderer::Quad {
bounds: pill, bounds: pill,
@@ -1138,43 +1212,100 @@ pub(super) fn ribbon_area<'a>(el: impl Into<Element<'a, Message>>) -> Element<'a
anchor(RIBBON_AREA, el) anchor(RIBBON_AREA, el)
} }
/// Where a code sits on its widget, after the reference ribbon: under a /// Spots for a code on its widget, nearest the reference ribbon's place
/// tab's label; with its corner at the icon's centre on quick-access, small /// first: under a tab's label; with its corner at the icon's centre on
/// and slide-out buttons; centred on a panel title or the corner launcher /// quick-access, small and slide-out buttons; under a large button's label or
/// (`true`); under a large button's label, or on a large dropdown's arrow. /// on a large dropdown's arrow strip; beside a panel title (the reference
/// Menu rows of dropdown lists, which the reference gives no codes, carry it /// centres it on the title) or on the corner launcher. Menu rows of dropdown
/// at their right end. /// lists, which the reference gives no codes, carry it at their right end.
fn pill_center(anchor: &str, b: Rectangle, width: f32, dropdown: bool) -> (Point, bool) { /// Later spots move off a label: the icon's other corners, a combo box's
let top_left = |x: f32, y: f32| Point::new(x + width / 2.0, y + PILL_H / 2.0); /// arrow, above the label, beside the widget.
fn pill_candidates(
anchor: &str,
b: Rectangle,
width: f32,
dropdown: bool,
title_w: f32,
) -> Vec<Point> {
let corner = |x: f32, y: f32| Point::new(x + width / 2.0, y + PILL_H / 2.0);
let left_corner = |x: f32, y: f32| Point::new(x - width / 2.0, y + PILL_H / 2.0);
let label = fresh_bounds(&label_id(anchor));
if anchor.starts_with("tab:") { if anchor.starts_with("tab:") {
return (Point::new(b.center_x(), b.y + b.height - 4.0 + PILL_H / 2.0), false); return vec![Point::new(b.center_x(), b.y + b.height - 4.0 + PILL_H / 2.0)];
} }
if anchor.starts_with("qa:") if anchor.starts_with("qa:")
|| anchor == super::widgets::UNDO_HISTORY_ID || anchor == super::widgets::UNDO_HISTORY_ID
|| anchor == super::widgets::REDO_HISTORY_ID || anchor == super::widgets::REDO_HISTORY_ID
{ {
return (top_left(b.x + 14.0, b.center_y()), false); return vec![corner(b.x + 14.0, b.center_y()), Point::new(b.x + 14.0, b.center_y())];
} }
if anchor.ends_with("_extension") if anchor == XREF_LAUNCHER {
|| anchor == XREF_LAUNCHER return vec![
|| (anchor.starts_with(COLLAPSED_PREFIX) && b.height < 40.0) b.center(),
{ Point::new(b.x + b.width + 2.0 + width / 2.0, b.center_y()),
return (b.center(), true); Point::new(b.center_x(), b.y - 1.0 - PILL_H / 2.0),
Point::new(b.x - 2.0 - width / 2.0, b.center_y()),
];
}
if anchor.ends_with("_extension") || anchor.starts_with(COLLAPSED_PREFIX) {
let mut title = label.unwrap_or(b);
if title_w > 0.0 && title_w < title.width {
title = Rectangle::new(
Point::new(title.center_x() - title_w / 2.0, title.y),
Size::new(title_w, title.height),
);
}
let mut spots = vec![
Point::new(title.x + title.width + 3.0 + width / 2.0, title.center_y()),
Point::new(title.x - 3.0 - width / 2.0, title.center_y()),
Point::new(title.center_x(), title.y - PILL_H / 2.0 - 1.0),
];
if b.height >= 40.0 {
spots.insert(0, Point::new(b.center_x(), b.y + b.height + 1.0));
}
return spots;
} }
let slide_out_item = anchor let slide_out_item = anchor
.split_once('/') .split_once('/')
.is_some_and(|(parent, _)| parent.ends_with("_extension")); .is_some_and(|(parent, _)| parent.ends_with("_extension"));
if anchor.contains('/') && !slide_out_item && b.width >= 60.0 { if anchor.contains('/') && !slide_out_item && b.width >= 60.0 {
return (Point::new(b.x + b.width - width / 2.0 - 4.0, b.center_y()), false); return vec![
Point::new(b.x + b.width - width / 2.0 - 4.0, b.center_y()),
Point::new(b.x + width / 2.0 + 2.0, b.center_y()),
];
} }
if b.height >= 40.0 { if b.height >= 40.0 {
// A large dropdown's arrow strip lies under its label. let label = label.unwrap_or(Rectangle::new(
let below_label = if dropdown { Point::new(b.x, b.y + b.height - 18.0),
b.y + b.height - super::widgets::LARGE_ARR / 2.0 Size::new(b.width, 14.0),
} else { ));
b.y + b.height + 1.0 let icon = Point::new(b.center_x(), (b.y + label.y) / 2.0);
}; let mut spots = Vec::new();
return (Point::new(b.center_x(), below_label), false); if dropdown {
// The arrow strip lies under the label.
spots.push(Point::new(b.center_x(), b.y + b.height - super::widgets::LARGE_ARR / 2.0));
}
spots.extend([
Point::new(b.center_x(), label.y + label.height + 1.0 + PILL_H / 2.0),
corner(icon.x, icon.y),
left_corner(icon.x, icon.y),
Point::new(b.center_x(), label.y - 1.0 - PILL_H / 2.0),
icon,
]);
return spots;
} }
(top_left(b.x + (b.width / 2.0).min(15.0), b.center_y()), false) let icon = Point::new(b.x + (b.width / 2.0).min(15.0), b.center_y());
let mut spots = vec![corner(icon.x, icon.y), icon, left_corner(icon.x, icon.y)];
if b.width >= 60.0 {
// A combo box: on its arrow, at the right end.
spots.push(Point::new(b.x + b.width - width / 2.0 - 2.0, b.center_y()));
}
spots.extend([
corner(icon.x, icon.y - PILL_H),
left_corner(icon.x, icon.y - PILL_H),
Point::new(b.x - 2.0 - width / 2.0, b.center_y()),
Point::new(b.x + b.width + 2.0 + width / 2.0, b.center_y()),
Point::new(icon.x, b.y + b.height + PILL_H / 2.0 - 2.0),
]);
spots
} }
+20 -12
View File
@@ -979,6 +979,8 @@ impl Ribbon {
color: (!is_current || disabled) color: (!is_current || disabled)
.then_some(theme.palette().background.base.text.scale_alpha(0.72)), .then_some(theme.palette().background.base.text.scale_alpha(0.72)),
}); });
let label_el =
keytips::label_anchor(|| keytips::label_id(&keytips::item_anchor(dd_id, cmd)), label_el);
let item = button( let item = button(
row![checkmark, icon_el, label_el] row![checkmark, icon_el, label_el]
@@ -1560,12 +1562,15 @@ fn collapse_button<'a>(
column![ column![
icon, icon,
row![ row![
text(localized_title.clone()) keytips::label_anchor(
.size(9) || keytips::label_id(&keytips::collapsed_anchor(title)),
.width(Fill) text(localized_title.clone())
.align_x(iced::Center) .size(9)
.wrapping(iced::advanced::text::Wrapping::Word) .width(Fill)
.style(muted_text_style), .align_x(iced::Center)
.wrapping(iced::advanced::text::Wrapping::Word)
.style(muted_text_style),
),
crate::ui::icons::themed_secondary_arrow_down(8.0), crate::ui::icons::themed_secondary_arrow_down(8.0),
] ]
.spacing(3) .spacing(3)
@@ -1632,12 +1637,15 @@ fn collapse_button<'a>(
let opener = button( let opener = button(
row![ row![
text(localized_title.clone()) keytips::label_anchor(
.size(9) || keytips::label_id(&keytips::collapsed_anchor(title)),
.width(Fill) text(localized_title.clone())
.align_x(iced::Center) .size(9)
.wrapping(iced::advanced::text::Wrapping::Word) .width(Fill)
.style(muted_text_style), .align_x(iced::Center)
.wrapping(iced::advanced::text::Wrapping::Word)
.style(muted_text_style),
),
crate::ui::icons::themed_secondary_arrow_down(8.0), crate::ui::icons::themed_secondary_arrow_down(8.0),
] ]
.spacing(3) .spacing(3)
+40 -17
View File
@@ -560,7 +560,10 @@ pub(super) fn render_small<'a>(
let tip_text = tool_tip_text(&label, t.id); let tip_text = tool_tip_text(&label, t.id);
let content = row![ let content = row![
container(make_icon(t.icon, SMALL_ICON)).width(Length::Fixed(SMALL_W)), container(make_icon(t.icon, SMALL_ICON)).width(Length::Fixed(SMALL_W)),
text(label).size(10).wrapping(advanced_text::Wrapping::None), super::keytips::label_anchor(
|| super::keytips::label_id(t.id),
text(label).size(10).wrapping(advanced_text::Wrapping::None),
),
] ]
.spacing(3) .spacing(3)
.align_y(iced::Center); .align_y(iced::Center);
@@ -699,9 +702,12 @@ pub(super) fn render_small<'a>(
}; };
let face = row![ let face = row![
container(make_icon(cur_icon, SMALL_ICON)).width(Length::Fixed(SMALL_W)), container(make_icon(cur_icon, SMALL_ICON)).width(Length::Fixed(SMALL_W)),
text(localized_label.clone()) super::keytips::label_anchor(
.size(10) || super::keytips::label_id(id),
.wrapping(advanced_text::Wrapping::None), text(localized_label.clone())
.size(10)
.wrapping(advanced_text::Wrapping::None),
),
] ]
.spacing(3) .spacing(3)
.align_y(iced::Center); .align_y(iced::Center);
@@ -832,11 +838,14 @@ pub(super) fn render_large_dropdown<'a>(
.height(Fill) .height(Fill)
.align_x(iced::Center) .align_x(iced::Center)
.align_y(iced::Center), .align_y(iced::Center),
text(label.clone()) super::keytips::label_anchor(
.size(10) || super::keytips::label_id(id),
.width(Fill) text(label.clone())
.align_x(iced::Center) .size(10)
.wrapping(advanced_text::Wrapping::Word), .width(Fill)
.align_x(iced::Center)
.wrapping(advanced_text::Wrapping::Word),
),
] ]
.align_x(iced::Center) .align_x(iced::Center)
.spacing(0) .spacing(0)
@@ -957,11 +966,14 @@ pub(super) fn render_large<'a>(item: &RibbonItem, ctx: &RenderCtx<'_>) -> Elemen
.height(Fill) .height(Fill)
.align_x(iced::Center) .align_x(iced::Center)
.align_y(iced::Center), .align_y(iced::Center),
text(label.clone()) super::keytips::label_anchor(
.size(10) || super::keytips::label_id(t.id),
.width(Fill) text(label.clone())
.align_x(iced::Center) .size(10)
.wrapping(advanced_text::Wrapping::Word), .width(Fill)
.align_x(iced::Center)
.wrapping(advanced_text::Wrapping::Word),
),
] ]
.align_x(iced::Center) .align_x(iced::Center)
.spacing(0) .spacing(0)
@@ -1087,7 +1099,10 @@ pub(super) fn render_large<'a>(item: &RibbonItem, ctx: &RenderCtx<'_>) -> Elemen
freeze_icon, freeze_icon,
lock_icon, lock_icon,
swatch, swatch,
container(text(active_layer_label).size(11)) container(super::keytips::label_anchor(
|| super::keytips::label_id(LAYER_COMBO_ID),
text(active_layer_label).size(11),
))
.width(name_w) .width(name_w)
.clip(true), .clip(true),
icons::themed_arrow_down(9.0), icons::themed_arrow_down(9.0),
@@ -1157,7 +1172,12 @@ pub(super) fn render_large<'a>(item: &RibbonItem, ctx: &RenderCtx<'_>) -> Elemen
button( button(
row![ row![
swatch_el, swatch_el,
container(text(label).size(10)).width(Fill).clip(true), container(super::keytips::label_anchor(
|| super::keytips::label_id(dd_id),
text(label).size(10),
))
.width(Fill)
.clip(true),
if is_open { if is_open {
icons::themed_arrow_up(8.0) icons::themed_arrow_up(8.0)
} else { } else {
@@ -1226,7 +1246,10 @@ pub(super) fn render_large<'a>(item: &RibbonItem, ctx: &RenderCtx<'_>) -> Elemen
// ── combo button ── // ── combo button ──
let combo_btn = button( let combo_btn = button(
row![ row![
container(text(active.clone()).size(11)) container(super::keytips::label_anchor(
|| super::keytips::label_id(combo_id),
text(active.clone()).size(11),
))
.width(Fill) .width(Fill)
.clip(true), .clip(true),
if is_open { if is_open {