Release assets are now the programs themselves: SolveCraft and solvecraft-cli .exe for Windows,
plain Linux binaries plus the AppImage, a macOS .dmg (SolveCraft.app with an Applications link) and
the universal CLI binary; only the web app stays a zip. The notes template takes {tag}, says to
chmod +x on Linux and macOS, and GitHub adds the change list.
Since no LICENSE or NOTICE file rides along any more, the programs carry them:
THIRD-PARTY-LICENSES.txt (written by `cargo xtask licences` from cargo metadata and each shipped
crate's own licence files, 419 crates; `cargo xtask ci` checks it is current) is embedded with
LICENSE-MIT, LICENSE-APACHE and NOTICE in solvecraft_engine::licences, shown in Help ▸ About ▸
Licences (third-party text laid out by visible rows) and printed by `solvecraft-cli licences`.
Scenario s44_about_licences.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
170 lines
7.5 KiB
Rust
170 lines
7.5 KiB
Rust
//! The Help menu (the ? button on the application bar): Documentation (the online book), About
|
|
//! SolveCraft (version, commit, build date, licences, attributions, fonts), the keyboard shortcuts
|
|
//! (read only), and Report an Issue, which copies diagnostic information to the clipboard.
|
|
|
|
use egui::{RichText, vec2};
|
|
|
|
use crate::SolveApp;
|
|
use crate::theme::Tokens;
|
|
|
|
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
|
pub const COMMIT: &str = env!("SOLVECRAFT_COMMIT");
|
|
pub const BUILD_DATE: &str = env!("SOLVECRAFT_BUILD_DATE");
|
|
/// The user and developer guide (docs/book, published by .github/workflows/pages.yml).
|
|
pub const DOCS_URL: &str = "https://bherbruck.github.io/solvecraft/";
|
|
const ATTRIBUTION: &str = include_str!("../../../ATTRIBUTION.md");
|
|
use solvecraft_engine::licences::{LICENSE_APACHE, LICENSE_MIT, NOTICE, THIRD_PARTY};
|
|
/// Fonts: what this build carries.
|
|
pub const FONTS: &str = "The interface uses egui's built-in fonts (Ubuntu Light and Hack, under the Ubuntu Font Licence and \
|
|
the MIT/Bitstream Vera licence; Noto Emoji and the emoji icon font, under the OFL and MIT). Sketch text uses Liberation Sans \
|
|
(SIL OFL 1.1). The Crafting Apps' optional font pack, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans \
|
|
CJK SC, Noto Sans Arabic; SIL OFL 1.1), is not bundled in SolveCraft builds.";
|
|
|
|
#[derive(Default)]
|
|
pub struct HelpState {
|
|
pub menu: bool,
|
|
pub about: bool,
|
|
/// The GPU the window renders with (set by the host).
|
|
pub gpu: Option<String>,
|
|
}
|
|
|
|
/// One line per fact, for bug reports.
|
|
pub fn diagnostics(app: &SolveApp) -> String {
|
|
let renderer = if app.viewport.gpu.is_some() { "GPU" } else { "CPU" };
|
|
format!(
|
|
"SolveCraft {VERSION} ({COMMIT}, built {BUILD_DATE})\nOS: {} {}\nRenderer: {renderer}{}\nWindow: {}\nDesign: {} ({} features, {} bodies)",
|
|
std::env::consts::OS,
|
|
std::env::consts::ARCH,
|
|
app.help.gpu.as_ref().map(|g| format!(", {g}")).unwrap_or_default(),
|
|
if app.custom_titlebar { "custom title bar" } else { "system title bar" },
|
|
app.session.doc.name,
|
|
app.session.doc.features.len(),
|
|
app.session.world_state().bodies.len(),
|
|
)
|
|
}
|
|
|
|
/// Run a Help menu item: `docs`, `about`, `shortcuts`, `report`.
|
|
pub fn run(app: &mut SolveApp, ctx: &egui::Context, item: &str) {
|
|
app.help.menu = false;
|
|
match item {
|
|
"docs" => ctx.open_url(egui::OpenUrl::new_tab(DOCS_URL)),
|
|
"about" => app.help.about = true,
|
|
"shortcuts" => {
|
|
app.keymap.open = true;
|
|
app.keymap.read_only = true;
|
|
}
|
|
"report" => {
|
|
ctx.copy_text(diagnostics(app));
|
|
app.set_status("Diagnostic information copied: paste it into your issue report", false);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
/// The Help menu under the ? button, and the About window.
|
|
pub fn show(app: &mut SolveApp, ctx: &egui::Context, anchor: egui::Pos2) {
|
|
if app.help.menu {
|
|
let mut pick: Option<&str> = None;
|
|
let resp = egui::Area::new(egui::Id::new("sc_help_menu")).fixed_pos(anchor).constrain(true).order(egui::Order::Foreground).show(ctx, |ui| {
|
|
crate::context_menu::menu_frame(ui, |ui| {
|
|
for (id, label) in
|
|
[("docs", "Documentation"), ("about", "About SolveCraft"), ("shortcuts", "Keyboard Shortcuts"), ("report", "Report an Issue…")]
|
|
{
|
|
if crate::context_menu::list_row(ui, &crate::context_menu::Item::action(id, label, "")).is_some() {
|
|
pick = Some(id);
|
|
}
|
|
}
|
|
})
|
|
});
|
|
if let Some(p) = pick {
|
|
run(app, ctx, p);
|
|
} else if ctx.input(|i| i.pointer.any_pressed() || i.key_pressed(egui::Key::Escape)) && !resp.response.contains_pointer() {
|
|
// Pressed elsewhere (the ? button toggles it itself).
|
|
if !ctx.data(|d| d.get_temp::<bool>(egui::Id::new("sc_help_opened")).unwrap_or(false)) {
|
|
app.help.menu = false;
|
|
}
|
|
}
|
|
ctx.data_mut(|d| d.insert_temp(egui::Id::new("sc_help_opened"), false));
|
|
}
|
|
about(app, ctx);
|
|
}
|
|
|
|
fn about(app: &mut SolveApp, ctx: &egui::Context) {
|
|
if !app.help.about {
|
|
return;
|
|
}
|
|
let t = Tokens::get();
|
|
let mut open = true;
|
|
crate::frame::window(ctx, "About SolveCraft", crate::frame::Width::Normal)
|
|
.id(egui::Id::new("sc_about"))
|
|
.open(&mut open)
|
|
.default_size(vec2(440.0, 560.0))
|
|
.show(ctx, |ui| {
|
|
ui.label(RichText::new("SolveCraft").size(22.0).strong());
|
|
ui.label(format!("Version {VERSION} · commit {COMMIT} · built {BUILD_DATE}"));
|
|
ui.label(RichText::new("Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.").color(t.text_dim));
|
|
ui.add_space(6.0);
|
|
ui.horizontal(|ui| {
|
|
if ui.button("Copy diagnostic information").clicked() {
|
|
ctx.copy_text(diagnostics(app));
|
|
}
|
|
});
|
|
ui.separator();
|
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
|
egui::CollapsingHeader::new("Attributions (ATTRIBUTION.md)").show(ui, |ui| {
|
|
ui.label(RichText::new(ATTRIBUTION).monospace().size(11.0));
|
|
});
|
|
egui::CollapsingHeader::new("Fonts").show(ui, |ui| {
|
|
ui.label(FONTS);
|
|
});
|
|
egui::CollapsingHeader::new("Licences").show(ui, |ui| {
|
|
egui::CollapsingHeader::new("NOTICE").show(ui, |ui| {
|
|
ui.label(RichText::new(NOTICE).monospace().size(11.0));
|
|
});
|
|
egui::CollapsingHeader::new("MIT licence").show(ui, |ui| {
|
|
ui.label(RichText::new(LICENSE_MIT).monospace().size(11.0));
|
|
});
|
|
egui::CollapsingHeader::new("Apache licence 2.0").show(ui, |ui| {
|
|
ui.label(RichText::new(LICENSE_APACHE).monospace().size(11.0));
|
|
});
|
|
egui::CollapsingHeader::new("Third-party licences").show(ui, |ui| {
|
|
if ui.button("Copy all licences").clicked() {
|
|
ctx.copy_text(solvecraft_engine::licences::all());
|
|
}
|
|
third_party(ui);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
if !open {
|
|
app.help.about = false;
|
|
}
|
|
}
|
|
|
|
/// THIRD-PARTY-LICENSES.txt (over 10 000 lines): only the lines in view are laid out.
|
|
fn third_party(ui: &mut egui::Ui) {
|
|
let lines: Vec<&str> = THIRD_PARTY.lines().collect();
|
|
let font = egui::FontId::monospace(11.0);
|
|
let row_h = ui.fonts_mut(|f| f.row_height(&font));
|
|
egui::ScrollArea::both().id_salt("sc_third_party").max_height(360.0).show_rows(ui, row_h, lines.len(), |ui, rows| {
|
|
for line in lines.get(rows).unwrap_or_default() {
|
|
// One row per line (show_rows needs equal rows): long lines scroll sideways.
|
|
ui.add(egui::Label::new(RichText::new(*line).font(font.clone())).extend());
|
|
}
|
|
});
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn about_knows_its_build_and_diagnostics_say_where_it_runs() {
|
|
assert!(!COMMIT.is_empty() && BUILD_DATE.len() == 10, "{COMMIT} {BUILD_DATE}");
|
|
assert!(ATTRIBUTION.contains("ATTRIBUTION") || ATTRIBUTION.contains("Attribution"));
|
|
let app = SolveApp::new(solvecraft_engine::Session::default(), Default::default());
|
|
let d = diagnostics(&app);
|
|
assert!(d.contains(VERSION) && d.contains(std::env::consts::OS) && d.contains("Renderer: CPU"), "{d}");
|
|
}
|
|
}
|