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: <address>", 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 <[email protected]>
This commit is contained in:
liangforstudyandClaude Opus 5.5 committed 2026-10-12 10:25:53 +08:00
1 parent 9432300f0e
commit 3f5d4385dd
5 files changed
+72 -3

No files matched your search

+5
View File
@@ -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}
Can't render this file because it contains an unexpected character in line 3 and column 95.
+5
View File
@@ -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}
Can't render this file because it contains an unexpected character in line 11 and column 59.
+5
View File
@@ -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}
Can't render this file because it contains an unexpected character in line 3 and column 95.
+52 -2
View File
@@ -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<String> {
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<String> =
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<String> =
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<String> = 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);
}
}
+5 -1
View File
@@ -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);