From 3f5d4385dd8db10290c4b44fdb38bfec84d1791c Mon Sep 17 00:00:00 2001
From: liangforstudy <36067395+liangforstudy@users.noreply.github.com>
Date: Sun, 11 Oct 2026 17:44:07 +0800
Subject: [PATCH] Help menu: say where each item goes; keep the address when no
browser opens
Hovering a Help menu item now tells you what it does: the links (ArtCraft
Discord, website, app page, GitHub, Report an Issue, FilmCraft Help) show
"Opens in your web browser:
", and Reveal Log Files, System
Compatibility Report and About FilmCraft say they stay in FilmCraft (no
web page).
When the host can't open a browser, the click was only logged. Now the
address is copied to the clipboard and the status bar names the reason
and the address.
Co-Authored-By: Claude Opus 5.5
---
crates/ui-egui/src/i18n/es.tsv | 5 +++
crates/ui-egui/src/i18n/ja.tsv | 5 +++
crates/ui-egui/src/i18n/zh-cn.tsv | 5 +++
crates/ui-egui/src/links.rs | 54 +++++++++++++++++++++++++++++--
crates/ui-egui/src/menus.rs | 6 +++-
5 files changed, 72 insertions(+), 3 deletions(-)
diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv
index 6119ccc9..6baf943b 100644
--- a/crates/ui-egui/src/i18n/es.tsv
+++ b/crates/ui-egui/src/i18n/es.tsv
@@ -3586,3 +3586,8 @@
Export the labels to a preset file… Exportar las etiquetas a un archivo de ajuste preestablecido…
Show the presets folder Mostrar la carpeta de ajustes preestablecidos
Delete this preset Eliminar este ajuste preestablecido
+ Opens in your web browser: {url} Se abre en el navegador web: {url}
+ Saves this session's log and shows it in your file manager (no web page). Guarda el registro de esta sesión y lo muestra en el explorador de archivos (no abre ninguna página web).
+ Shows a report on this computer in a FilmCraft window (no web page). Muestra un informe sobre este equipo en una ventana de FilmCraft (no abre ninguna página web).
+ Shows the version, community links and credits in a FilmCraft window (no web page). Muestra la versión, los enlaces de la comunidad y los créditos en una ventana de FilmCraft (no abre ninguna página web).
+ Couldn't open your web browser ({error}). The address is copied to the clipboard: {url} No se pudo abrir el navegador web ({error}). La dirección se ha copiado al portapapeles: {url}
diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv
index d142b44b..e641321b 100644
--- a/crates/ui-egui/src/i18n/ja.tsv
+++ b/crates/ui-egui/src/i18n/ja.tsv
@@ -3588,3 +3588,8 @@
Export the labels to a preset file… ラベルをプリセットファイルに書き出し…
Show the presets folder プリセットフォルダーを表示
Delete this preset このプリセットを削除
+ Opens in your web browser: {url} ウェブブラウザーで開きます:{url}
+ Saves this session's log and shows it in your file manager (no web page). このセッションのログを保存し、ファイルマネージャーで表示します(ウェブページは開きません)。
+ Shows a report on this computer in a FilmCraft window (no web page). このコンピューターのレポートを FilmCraft のウィンドウに表示します(ウェブページは開きません)。
+ Shows the version, community links and credits in a FilmCraft window (no web page). バージョン、コミュニティへのリンク、クレジットを FilmCraft のウィンドウに表示します(ウェブページは開きません)。
+ Couldn't open your web browser ({error}). The address is copied to the clipboard: {url} ウェブブラウザーを開けませんでした({error})。アドレスをクリップボードにコピーしました:{url}
diff --git a/crates/ui-egui/src/i18n/zh-cn.tsv b/crates/ui-egui/src/i18n/zh-cn.tsv
index 15bc785f..423f2c07 100644
--- a/crates/ui-egui/src/i18n/zh-cn.tsv
+++ b/crates/ui-egui/src/i18n/zh-cn.tsv
@@ -3564,3 +3564,8 @@
Export the labels to a preset file… 将标签导出为预设文件…
Show the presets folder 显示预设文件夹
Delete this preset 删除此预设
+ Opens in your web browser: {url} 在网页浏览器中打开:{url}
+ Saves this session's log and shows it in your file manager (no web page). 保存本次会话的日志并在文件管理器中显示(不打开网页)。
+ Shows a report on this computer in a FilmCraft window (no web page). 在 FilmCraft 窗口中显示本机的报告(不打开网页)。
+ Shows the version, community links and credits in a FilmCraft window (no web page). 在 FilmCraft 窗口中显示版本、社区链接和致谢(不打开网页)。
+ Couldn't open your web browser ({error}). The address is copied to the clipboard: {url} 无法打开网页浏览器({error})。地址已复制到剪贴板:{url}
diff --git a/crates/ui-egui/src/links.rs b/crates/ui-egui/src/links.rs
index 6d980e6c..5c4d257c 100644
--- a/crates/ui-egui/src/links.rs
+++ b/crates/ui-egui/src/links.rs
@@ -31,15 +31,39 @@ pub fn url_for(command: &str) -> Option<&'static str> {
ALL.iter().find(|(id, _, _)| *id == command).map(|(_, _, u)| *u)
}
+/// What a Help menu item does, shown when the pointer rests on it: the address a link opens in
+/// the web browser, or that the item stays inside FilmCraft.
+pub fn menu_hint(command: &str) -> Option {
+ let url = if command == "help.filmcraftHelp" { Some(crate::panels::keyboard::HELP_URL) } else { url_for(command) };
+ if let Some(url) = url {
+ return Some(tlf!("Opens in your web browser: {url}", url));
+ }
+ Some(
+ match command {
+ "help.revealLogFiles" => tl!("Saves this session's log and shows it in your file manager (no web page)."),
+ "help.systemCompatibilityReport" => tl!("Shows a report on this computer in a FilmCraft window (no web page)."),
+ "app.about" => tl!("Shows the version, community links and credits in a FilmCraft window (no web page)."),
+ _ => return None,
+ }
+ .to_string(),
+ )
+}
+
/// Open `url` in the system browser (a new tab on the web). The desktop host's
/// [`HostHooks::open_url`](crate::HostHooks::open_url) opens it; without the hook, or when it
/// fails, it goes through `ctx.open_url`, which only does something on the web: the native egui
-/// backend is built without its `links` feature, so there it would only log (#642).
+/// backend is built without its `links` feature, so there it would only log (#642). When the
+/// host can't open a browser, the address goes to the clipboard and the status bar says so, so
+/// the click is never silently lost.
pub fn open(app: &mut crate::FilmcraftApp, ctx: &egui::Context, url: &str) {
if let Some(f) = app.hooks.open_url.as_mut() {
match f(url) {
Ok(()) => return,
- Err(e) => log::warn!("can't open {url} in the browser: {e}"),
+ Err(error) => {
+ log::warn!("can't open {url} in the browser: {error}");
+ ctx.copy_text(url.to_string());
+ app.ui.status = tlf!("Couldn't open your web browser ({error}). The address is copied to the clipboard: {url}", error, url);
+ }
}
}
ctx.open_url(egui::OpenUrl::new_tab(url));
@@ -87,5 +111,31 @@ mod tests {
let fallback: Vec =
ctx.output(|o| o.commands.iter().filter_map(|c| if let egui::OutputCommand::OpenUrl(u) = c { Some(u.url.clone()) } else { None }).collect());
assert_eq!(fallback, [ISSUES]);
+ // ... and keeps the address at hand: copied, and named in the status bar with the reason
+ let copied: Vec =
+ ctx.output(|o| o.commands.iter().filter_map(|c| if let egui::OutputCommand::CopyText(t) = c { Some(t.clone()) } else { None }).collect());
+ assert_eq!(copied, [ISSUES]);
+ assert!(app.ui.status.contains(ISSUES) && app.ui.status.contains("no browser"), "{}", app.ui.status);
+ }
+
+ #[test]
+ fn help_menu_items_say_where_they_go() {
+ // links name their address; the other Help items say they stay inside FilmCraft
+ for (id, _, url) in ALL {
+ let hint = menu_hint(id).unwrap();
+ assert!(hint.contains(url) && hint.contains("web browser"), "{id}: {hint}");
+ }
+ assert!(menu_hint("help.filmcraftHelp").unwrap().contains(crate::panels::keyboard::HELP_URL));
+ let help_items: Vec = crate::menus::menu_items(&crate::FilmcraftApp::new(filmcraft_engine::Session::default()))
+ .into_iter()
+ .filter(|it| it.path.first().map(String::as_str) == Some("Help"))
+ .map(|it| it.id)
+ .collect();
+ assert!(help_items.len() >= ALL.len() + 3, "{help_items:?}");
+ for id in &help_items {
+ let hint = menu_hint(id).unwrap_or_else(|| panic!("{id} has no hover hint"));
+ assert!(hint.contains("web browser") || hint.contains("no web page"), "{id}: {hint}");
+ }
+ assert_eq!(menu_hint("edit.undo"), None);
}
}
diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs
index 77833a1b..04b2b891 100644
--- a/crates/ui-egui/src/menus.rs
+++ b/crates/ui-egui/src/menus.rs
@@ -931,7 +931,11 @@ fn menu_entry(ui: &mut egui::Ui, it: &MenuItem, auto: &mut crate::automation::Re
if let Some(s) = &it.shortcut {
b = b.shortcut_text(shortcut_text(s));
}
- let r = ui.add_enabled(it.enabled, b);
+ let mut r = ui.add_enabled(it.enabled, b);
+ // Help items say whether they open the web browser (and which address) or stay in FilmCraft
+ if let Some(hint) = crate::links::menu_hint(&it.id) {
+ r = r.on_hover_text(hint);
+ }
auto.add(&format!("menu.{}", it.id), r.rect, &it.label);
if let Some(color) = swatch {
paint_swatch(ui, r.rect, color);