feat(ui): add viewport tooltips

Reuse ribbon tooltip styling for every viewport toolbar action and expose translated action names with related commands.

Refs #482
This commit is contained in:
Hakan Seven 2026-08-04 19:10:47 +03:00
commit 44d0c65ae9
10 changed files with 115 additions and 17 deletions

View file

@ -94,6 +94,10 @@ clipboard-no-supported-content = Die Zwischenablage enthält keinen unterstützt
clipboard-unavailable = Die Systemzwischenablage ist nicht verfügbar. clipboard-unavailable = Die Systemzwischenablage ist nicht verfügbar.
clipboard-occupied = Die Systemzwischenablage wird gerade verwendet. Versuchen Sie es erneut. clipboard-occupied = Die Systemzwischenablage wird gerade verwendet. Versuchen Sie es erneut.
clipboard-conversion-failed = Der Text in der Zwischenablage konnte nicht gelesen werden. clipboard-conversion-failed = Der Text in der Zwischenablage konnte nicht gelesen werden.
viewport-split-vertical = Ansichtsfenster vertikal teilen
viewport-split-horizontal = Ansichtsfenster horizontal teilen
viewport-move = Ansichtsfenster verschieben
viewport-close = Ansichtsfenster schließen
modal-point-style = Punktstil modal-point-style = Punktstil
modal-attribute-editor = Attribut-Editor modal-attribute-editor = Attribut-Editor
modal-save-drawing-as = Zeichnung speichern als modal-save-drawing-as = Zeichnung speichern als

View file

@ -113,6 +113,10 @@ clipboard-no-supported-content = Clipboard does not contain supported text or dr
clipboard-unavailable = System clipboard is unavailable. clipboard-unavailable = System clipboard is unavailable.
clipboard-occupied = System clipboard is currently in use. Try again. clipboard-occupied = System clipboard is currently in use. Try again.
clipboard-conversion-failed = Clipboard text could not be read. clipboard-conversion-failed = Clipboard text could not be read.
viewport-split-vertical = Split Viewport Vertically
viewport-split-horizontal = Split Viewport Horizontally
viewport-move = Move Viewport
viewport-close = Close Viewport
modal-point-style = Point Style modal-point-style = Point Style
modal-attribute-editor = Attribute Editor modal-attribute-editor = Attribute Editor
modal-save-drawing-as = Save Drawing As modal-save-drawing-as = Save Drawing As

View file

@ -94,6 +94,10 @@ clipboard-no-supported-content = Le presse-papiers ne contient aucun texte ni ob
clipboard-unavailable = Le presse-papiers système n’est pas disponible. clipboard-unavailable = Le presse-papiers système n’est pas disponible.
clipboard-occupied = Le presse-papiers système est en cours d’utilisation. Réessayez. clipboard-occupied = Le presse-papiers système est en cours d’utilisation. Réessayez.
clipboard-conversion-failed = Impossible de lire le texte du presse-papiers. clipboard-conversion-failed = Impossible de lire le texte du presse-papiers.
viewport-split-vertical = Diviser la fenêtre verticalement
viewport-split-horizontal = Diviser la fenêtre horizontalement
viewport-move = Déplacer la fenêtre
viewport-close = Fermer la fenêtre
modal-point-style = Style de point modal-point-style = Style de point
modal-attribute-editor = Éditeur d'attributs modal-attribute-editor = Éditeur d'attributs
modal-save-drawing-as = Enregistrer le dessin sous modal-save-drawing-as = Enregistrer le dessin sous

View file

@ -94,6 +94,10 @@ clipboard-no-supported-content = क्लिपबोर्ड में सम
clipboard-unavailable = सिस्टम क्लिपबोर्ड उपलब्ध नहीं है। clipboard-unavailable = सिस्टम क्लिपबोर्ड उपलब्ध नहीं है।
clipboard-occupied = सिस्टम क्लिपबोर्ड अभी उपयोग में है। फिर प्रयास करें। clipboard-occupied = सिस्टम क्लिपबोर्ड अभी उपयोग में है। फिर प्रयास करें।
clipboard-conversion-failed = क्लिपबोर्ड का टेक्स्ट पढ़ा नहीं जा सका। clipboard-conversion-failed = क्लिपबोर्ड का टेक्स्ट पढ़ा नहीं जा सका।
viewport-split-vertical = व्यूपोर्ट को लंबवत विभाजित करें
viewport-split-horizontal = व्यूपोर्ट को क्षैतिज विभाजित करें
viewport-move = व्यूपोर्ट ले जाएँ
viewport-close = व्यूपोर्ट बंद करें
modal-point-style = प्वाइंट स्टाइल modal-point-style = प्वाइंट स्टाइल
modal-attribute-editor = विशेषता संपादक modal-attribute-editor = विशेषता संपादक
modal-save-drawing-as = ड्रॉइंग इस रूप में सहेजें modal-save-drawing-as = ड्रॉइंग इस रूप में सहेजें

View file

@ -94,6 +94,10 @@ clipboard-no-supported-content = Het klembord bevat geen ondersteunde tekst of t
clipboard-unavailable = Het systeemklembord is niet beschikbaar. clipboard-unavailable = Het systeemklembord is niet beschikbaar.
clipboard-occupied = Het systeemklembord is momenteel in gebruik. Probeer het opnieuw. clipboard-occupied = Het systeemklembord is momenteel in gebruik. Probeer het opnieuw.
clipboard-conversion-failed = De tekst op het klembord kon niet worden gelezen. clipboard-conversion-failed = De tekst op het klembord kon niet worden gelezen.
viewport-split-vertical = Viewport verticaal splitsen
viewport-split-horizontal = Viewport horizontaal splitsen
viewport-move = Viewport verplaatsen
viewport-close = Viewport sluiten
modal-point-style = Puntstijl modal-point-style = Puntstijl
modal-attribute-editor = Kenmerkeditor modal-attribute-editor = Kenmerkeditor
modal-save-drawing-as = Tekening opslaan als modal-save-drawing-as = Tekening opslaan als

View file

@ -94,6 +94,10 @@ clipboard-no-supported-content = Буфер обмена не содержит
clipboard-unavailable = Системный буфер обмена недоступен. clipboard-unavailable = Системный буфер обмена недоступен.
clipboard-occupied = Системный буфер обмена сейчас используется. Повторите попытку. clipboard-occupied = Системный буфер обмена сейчас используется. Повторите попытку.
clipboard-conversion-failed = Не удалось прочитать текст из буфера обмена. clipboard-conversion-failed = Не удалось прочитать текст из буфера обмена.
viewport-split-vertical = Разделить видовой экран по вертикали
viewport-split-horizontal = Разделить видовой экран по горизонтали
viewport-move = Переместить видовой экран
viewport-close = Закрыть видовой экран
modal-point-style = Стиль точек modal-point-style = Стиль точек
modal-attribute-editor = Редактор атрибутов modal-attribute-editor = Редактор атрибутов
modal-save-drawing-as = Сохранить чертеж как modal-save-drawing-as = Сохранить чертеж как

View file

@ -113,6 +113,10 @@ clipboard-no-supported-content = Panoda desteklenen metin veya çizim nesnesi yo
clipboard-unavailable = Sistem panosuna erişilemiyor. clipboard-unavailable = Sistem panosuna erişilemiyor.
clipboard-occupied = Sistem panosu şu anda kullanılıyor. Yeniden deneyin. clipboard-occupied = Sistem panosu şu anda kullanılıyor. Yeniden deneyin.
clipboard-conversion-failed = Panodaki metin okunamadı. clipboard-conversion-failed = Panodaki metin okunamadı.
viewport-split-vertical = Görünüm Penceresini Dikey Böl
viewport-split-horizontal = Görünüm Penceresini Yatay Böl
viewport-move = Görünüm Penceresini Taşı
viewport-close = Görünüm Penceresini Kapat
modal-point-style = Nokta Stili modal-point-style = Nokta Stili
modal-attribute-editor = Öznitelik Düzenleyicisi modal-attribute-editor = Öznitelik Düzenleyicisi
modal-save-drawing-as = Çizimi Farklı Kaydet modal-save-drawing-as = Çizimi Farklı Kaydet

View file

@ -94,6 +94,10 @@ clipboard-no-supported-content = 剪贴板中没有受支持的文本或图形
clipboard-unavailable = 系统剪贴板不可用。 clipboard-unavailable = 系统剪贴板不可用。
clipboard-occupied = 系统剪贴板当前正被使用,请重试。 clipboard-occupied = 系统剪贴板当前正被使用,请重试。
clipboard-conversion-failed = 无法读取剪贴板文本。 clipboard-conversion-failed = 无法读取剪贴板文本。
viewport-split-vertical = 垂直拆分视口
viewport-split-horizontal = 水平拆分视口
viewport-move = 移动视口
viewport-close = 关闭视口
modal-point-style = 点样式 modal-point-style = 点样式
modal-attribute-editor = 属性编辑器 modal-attribute-editor = 属性编辑器
modal-save-drawing-as = 图纸另存为 modal-save-drawing-as = 图纸另存为

View file

@ -1,10 +1,26 @@
use super::*; use super::*;
use super::super::document::{DynComponent, DynFieldEntry}; use super::super::document::{DynComponent, DynFieldEntry};
use super::super::Message; use super::super::Message;
use iced::widget::{ use iced::widget::{button, container, mouse_area, row, tooltip};
button, container, mouse_area, row,
};
use iced::{Background, Border, Element, Theme}; use iced::{Background, Border, Element, Theme};
use std::time::Duration;
fn viewport_tooltip<'a>(
control: impl Into<Element<'a, Message>>,
title: String,
command: &'static str,
) -> Element<'a, Message> {
let text = format!("{title}\n{} {command}", crate::t!("Command:"));
tooltip(
control,
crate::ui::ribbon::tooltip_content(text),
tooltip::Position::Bottom,
)
.gap(6.0)
.delay(Duration::from_millis(400))
.style(crate::ui::ribbon::tooltip_style)
.into()
}
pub(super) fn viewport_controls<'a>( pub(super) fn viewport_controls<'a>(
render_mode: acadrust::entities::ViewportRenderMode, render_mode: acadrust::entities::ViewportRenderMode,
@ -23,8 +39,11 @@ pub(super) fn viewport_controls<'a>(
RenderModeChoice(M::FlatShadedWithEdges), RenderModeChoice(M::FlatShadedWithEdges),
RenderModeChoice(M::GouraudShadedWithEdges), RenderModeChoice(M::GouraudShadedWithEdges),
]; ];
let danger_btn = move |bytes: &'static [u8], msg: Message| { let danger_btn = move |bytes: &'static [u8],
button(crate::ui::icons::themed_danger(bytes, 15.0)) msg: Message,
title: String,
command: &'static str| {
let button = button(crate::ui::icons::themed_danger(bytes, 15.0))
.on_press(msg) .on_press(msg)
.padding([4, 6]) .padding([4, 6])
.style(move |theme: &Theme, status| iced::widget::button::Style { .style(move |theme: &Theme, status| iced::widget::button::Style {
@ -33,26 +52,29 @@ pub(super) fn viewport_controls<'a>(
iced::widget::button::Status::Hovered iced::widget::button::Status::Hovered
| iced::widget::button::Status::Pressed | iced::widget::button::Status::Pressed
) )
.then_some(Background::Color( .then_some(Background::Color(theme.palette().danger.weak.color)),
theme.palette().danger.weak.color
)),
border: Border { border: Border {
radius: 3.0.into(), radius: 3.0.into(),
..Default::default() ..Default::default()
}, },
text_color: theme.palette().danger.base.color, text_color: theme.palette().danger.base.color,
..Default::default() ..Default::default()
}) });
viewport_tooltip(button, title, command)
}; };
// Borderless icon button; an `active` toggle gets an accent tint + fill. // Borderless icon button; an `active` toggle gets an accent tint + fill.
let icon_btn = move |bytes: &'static [u8], active: bool, msg: Message| { let icon_btn = move |bytes: &'static [u8],
active: bool,
msg: Message,
title: String,
command: &'static str| {
let icon = if active { let icon = if active {
crate::ui::icons::themed_primary(bytes, 15.0) crate::ui::icons::themed_primary(bytes, 15.0)
} else { } else {
crate::ui::icons::themed(bytes, 15.0) crate::ui::icons::themed(bytes, 15.0)
}; };
button(icon) let button = button(icon)
.on_press(msg) .on_press(msg)
.padding([4, 6]) .padding([4, 6])
.style(move |theme: &Theme, status| { .style(move |theme: &Theme, status| {
@ -75,7 +97,8 @@ pub(super) fn viewport_controls<'a>(
.unwrap_or(palette.background.base.text), .unwrap_or(palette.background.base.text),
..Default::default() ..Default::default()
} }
}) });
viewport_tooltip(button, title, command)
}; };
// Render-mode picker, restyled borderless so the outer chip frames it. // Render-mode picker, restyled borderless so the outer chip frames it.
@ -100,6 +123,11 @@ pub(super) fn viewport_controls<'a>(
handle_color: text, handle_color: text,
} }
}); });
let picker = viewport_tooltip(
picker,
crate::t!("Visual Style").into_owned(),
"VISUALSTYLES",
);
// Thin vertical divider between control groups. // Thin vertical divider between control groups.
let sep = || { let sep = || {
@ -117,17 +145,41 @@ pub(super) fn viewport_controls<'a>(
.spacing(3) .spacing(3)
.align_y(iced::alignment::Vertical::Center); .align_y(iced::alignment::Vertical::Center);
bar = bar bar = bar
.push(icon_btn(crate::ui::icons::GRID, show_grid, Message::ToggleGrid)) .push(icon_btn(
crate::ui::icons::GRID,
show_grid,
Message::ToggleGrid,
crate::t!("Toggle Grid").into_owned(),
"GRID",
))
.push(sep()) .push(sep())
.push(icon_btn(crate::ui::icons::SNAP, snap_on, Message::ToggleGridSnap)) .push(icon_btn(
crate::ui::icons::SNAP,
snap_on,
Message::ToggleGridSnap,
crate::t!("Toggle Grid Snap").into_owned(),
"SNAP",
))
.push(sep()) .push(sep())
.push(picker); .push(picker);
if include_split { if include_split {
bar = bar bar = bar
.push(sep()) .push(sep())
.push(icon_btn(crate::ui::icons::SPLIT_V, false, Message::SplitModelViewport(false))) .push(icon_btn(
crate::ui::icons::SPLIT_V,
false,
Message::SplitModelViewport(false),
crate::tr!("viewport-split-vertical"),
"VPORTS 2V",
))
.push(sep()) .push(sep())
.push(icon_btn(crate::ui::icons::SPLIT_H, false, Message::SplitModelViewport(true))); .push(icon_btn(
crate::ui::icons::SPLIT_H,
false,
Message::SplitModelViewport(true),
crate::tr!("viewport-split-horizontal"),
"VPORTS 2H",
));
// Drag handle + close: only meaningful with more than one model tile. // Drag handle + close: only meaningful with more than one model tile.
// The handle is a `mouse_area` (not a button) so it fires on press-DOWN, // The handle is a `mouse_area` (not a button) so it fires on press-DOWN,
// letting the drag continue onto the target pane to swap them (a button // letting the drag continue onto the target pane to swap them (a button
@ -146,11 +198,17 @@ pub(super) fn viewport_controls<'a>(
) )
.interaction(iced::mouse::Interaction::Grab) .interaction(iced::mouse::Interaction::Grab)
.on_press(Message::PaneMoveStart); .on_press(Message::PaneMoveStart);
let drag = viewport_tooltip(drag, crate::tr!("viewport-move"), "VPORTS");
bar = bar bar = bar
.push(sep()) .push(sep())
.push(drag) .push(drag)
.push(sep()) .push(sep())
.push(danger_btn(crate::ui::icons::CLOSE, Message::CloseModelViewport)); .push(danger_btn(
crate::ui::icons::CLOSE,
Message::CloseModelViewport,
crate::tr!("viewport-close"),
"VPORTS SINGLE",
));
} }
} }

View file

@ -28,6 +28,14 @@ pub use collapse::CollapseMode;
use crate::ui::wrap_bar::{PosReport, WrapBar, WrapFlow}; use crate::ui::wrap_bar::{PosReport, WrapBar, WrapFlow};
use crate::t; use crate::t;
pub(crate) fn tooltip_content(text: String) -> Element<'static, Message> {
widgets::make_tip(text)
}
pub(crate) fn tooltip_style(theme: &Theme) -> container::Style {
widgets::tip_style(theme)
}
// ── Ribbon state ─────────────────────────────────────────────────────────── // ── Ribbon state ───────────────────────────────────────────────────────────
pub struct Ribbon { pub struct Ribbon {