Skip to main content

brep_app/panels/
info.rs

1//! The **Info** window — the licences, and what this build is running on.
2//!
3//! The toolbar's ℹ button opens it. Two things live here because a user asking
4//! either question is in the same place: "what am I allowed to do with this"
5//! and "what is it running on".
6//!
7//! * **Licences.** The project licence and the maintained third-party notices,
8//!   compiled into the binary from the very files the help site renders
9//!   (`LICENSE.md`, `THIRD-PARTY-NOTICES.md`) — so a build that is not served
10//!   beside its `web/help/` still carries them. The GENERATED crate inventory
11//!   (every shipped dependency, grouped by licence expression) is built from
12//!   `cargo metadata` at docs time and cannot be compiled in, so the window
13//!   links to its help page instead.
14//! * **Diagnostics.** [`crate::diagnostics::Diagnostics`], rendered row for row.
15//!   The window OWNS no diagnostic of its own: it is handed the app's one
16//!   instance, the same one the problem report embeds, so what a user reads
17//!   here and what a triager reads in their report cannot differ.
18//!
19//! Not to be confused with [`crate::panels::info_windows`], the pinned
20//! per-ENTITY inspectors opened from the context bar. This window is about the
21//! application; those are about a face, an edge or a solid.
22
23use crate::automation::hit_keys::HitKeyDoc;
24use crate::diagnostics::Diagnostics;
25use eframe::egui;
26use std::collections::HashMap;
27
28/// The project licence, as authored. `BREP_docs` renders this same file as the
29/// help site's first Licences page.
30///
31/// This is the crate's OWN copy, not the repository root's. An `include_str!`
32/// that climbs out of the package compiles in a checkout and fails in the
33/// tarball `cargo publish` verifies, which is exactly where nobody looks —
34/// `build.rs` holds the two copies identical so the distinction stays
35/// bookkeeping rather than a second source of truth.
36const PROJECT_LICENCE: &str = include_str!("../../LICENSE.md");
37
38/// The maintained third-party notices, as authored — the embedded fonts and the
39/// material whose licence requires its notice to travel with it. The crate's own
40/// copy, for the reason given above.
41const THIRD_PARTY_NOTICES: &str = include_str!("../../THIRD-PARTY-NOTICES.md");
42
43/// The generated crate inventory on the help site: every crate the application
44/// ships, grouped under its declared licence expression. Generated from `cargo
45/// metadata` by `BREP_docs` (see its `licences` module), so it exists only
46/// beside the served page — hence a link rather than an include.
47const INVENTORY_URL: &str = "help/licences/third-party-crates.html";
48
49/// The Info window's state. Its whole model is `open`: everything it draws is
50/// either a compiled-in constant or read live from the app's [`Diagnostics`].
51#[derive(Default)]
52pub struct InfoPanel {
53    /// Whether the window is showing — the toolbar ℹ toggle's flag, also set by
54    /// the `info_window` command.
55    pub open: bool,
56    /// Per-frame widget screen rects for the headed verifier, like every other
57    /// panel.
58    hits: HashMap<String, egui::Rect>,
59}
60
61impl InfoPanel {
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    /// Draw the floating window (if open) at ctx level, after the panels, so it
67    /// floats over the shell. Idempotent while closed.
68    pub fn show(&mut self, ctx: &egui::Context, diagnostics: &Diagnostics) {
69        if !self.open {
70            return;
71        }
72        // `egui::Window::open` needs its own `&mut bool`; borrow a copy so the
73        // draw closure can still take `&mut self`, then fold the close back in
74        // (the Part Properties window's rule).
75        let mut open = true;
76        egui::Window::new("Info")
77            .open(&mut open)
78            .movable(true)
79            .resizable(true)
80            // A bounded default plus the filling ScrollArea in `body`: without a
81            // filling child egui hugs the window to its content and the user
82            // cannot drag it larger.
83            .default_size([560.0, 520.0])
84            // Clear of the Settings (620, 80) and Part Properties (660, 96)
85            // rest positions, so opening all three does not stack them.
86            .default_pos([120.0, 120.0])
87            .show(ctx, |ui| self.body(ui, diagnostics));
88        self.open = open;
89
90        if crate::automation::registry::enabled() {
91            crate::automation::registry::publish(
92                "__brepInfoHit",
93                "info window widget rects (copy, inventory, licence:*)",
94                &self.hits_json(),
95            );
96        }
97    }
98
99    fn body(&mut self, ui: &mut egui::Ui, diagnostics: &Diagnostics) {
100        self.hits.clear();
101        egui::ScrollArea::vertical()
102            .auto_shrink([false; 2])
103            .show(ui, |ui| {
104                self.diagnostics_section(ui, diagnostics);
105                ui.add_space(12.0);
106                ui.separator();
107                ui.add_space(6.0);
108                self.licences_section(ui);
109            });
110    }
111
112    /// The diagnostics grid, then the one button that puts the very same text a
113    /// problem report would carry on the clipboard — for a user reporting
114    /// somewhere other than the in-app form.
115    fn diagnostics_section(&mut self, ui: &mut egui::Ui, diagnostics: &Diagnostics) {
116        ui.heading("Diagnostics");
117        ui.label(
118            egui::RichText::new("Sent with every problem report from the Submit Bug button.")
119                .weak()
120                .small(),
121        );
122        ui.add_space(4.0);
123        egui::Grid::new("brep-info-diagnostics")
124            .num_columns(2)
125            .spacing([12.0, 4.0])
126            .striped(true)
127            .show(ui, |ui| {
128                for (label, value) in diagnostics.rows() {
129                    ui.label(egui::RichText::new(label).strong());
130                    ui.label(egui::RichText::new(value).monospace());
131                    ui.end_row();
132                }
133            });
134        ui.add_space(6.0);
135        let copy = ui.button("Copy diagnostics");
136        self.hit("copy", &copy);
137        if copy.clicked() {
138            ui.ctx().copy_text(diagnostics.report_text());
139        }
140    }
141
142    /// The licences: the project's own, the maintained notices, and a link to
143    /// the generated crate inventory. Each authored file is behind a collapsing
144    /// header — several thousand words of licence text opened by default would
145    /// bury the diagnostics above it.
146    fn licences_section(&mut self, ui: &mut egui::Ui) {
147        ui.heading("Licences");
148        ui.add_space(4.0);
149        let project = egui::CollapsingHeader::new("Project licence (LICENSE.md)")
150            .id_salt("brep-info-licence-project")
151            .show(ui, |ui| licence_text(ui, PROJECT_LICENCE));
152        self.hit("licence:project", &project.header_response);
153        let notices = egui::CollapsingHeader::new("Third-party notices (THIRD-PARTY-NOTICES.md)")
154            .id_salt("brep-info-licence-notices")
155            .show(ui, |ui| licence_text(ui, THIRD_PARTY_NOTICES));
156        self.hit("licence:notices", &notices.header_response);
157        ui.add_space(6.0);
158        ui.label(
159            egui::RichText::new(
160                "Every crate this application ships, grouped by its licence, is generated \
161                 into the help site from the dependency graph itself:",
162            )
163            .weak()
164            .small(),
165        );
166        let inventory = ui.button("Open the third-party crate inventory");
167        self.hit("inventory", &inventory);
168        if inventory.clicked() {
169            ui.ctx().open_url(egui::OpenUrl::new_tab(INVENTORY_URL));
170        }
171    }
172
173    /// Record a widget's screen rect for the headed verifier.
174    fn hit(&mut self, key: &str, resp: &egui::Response) {
175        self.hits.insert(key.to_string(), resp.rect);
176    }
177
178    /// The published widget hit-rects (egui points) for the headed verifier.
179    pub fn hits_json(&self) -> String {
180        let map: serde_json::Map<String, serde_json::Value> = self
181            .hits
182            .iter()
183            .map(|(k, r)| {
184                (
185                    k.clone(),
186                    serde_json::json!([r.center().x, r.center().y, r.width(), r.height()]),
187                )
188            })
189            .collect();
190        serde_json::Value::Object(map).to_string()
191    }
192}
193
194/// One authored licence file, verbatim. Markdown as typed — the app has no
195/// markdown renderer, and a licence is one of the few texts where showing
196/// exactly the bytes that ship is the right answer anyway. Selectable, so a
197/// reader can copy a clause out.
198fn licence_text(ui: &mut egui::Ui, text: &str) {
199    ui.add(egui::Label::new(egui::RichText::new(text).monospace().small()).wrap());
200}
201
202/// The hit keys this panel publishes (see `automation::hit_keys`).
203/// Every `command` here is `None`, and honestly so: the registered command is
204/// the one that DOES what a click does, and no command copies to a clipboard,
205/// opens this particular help page or expands a header. The window itself is
206/// reachable without a pointer (`info_window`), and the diagnostics it shows are
207/// readable without one (`diagnostics`) — naming either against a button that
208/// does something else would put a promise in the generated widget docs that
209/// `tools/call` cannot keep.
210pub static HIT_KEYS: &[HitKeyDoc] = &[
211    HitKeyDoc { panel: "info", prefix: "copy", meaning: "copy the diagnostics block to the clipboard (the same rows `diagnostics` returns)", command: None },
212    HitKeyDoc { panel: "info", prefix: "inventory", meaning: "open the generated third-party crate inventory on the help site", command: None },
213    HitKeyDoc { panel: "info", prefix: "licence:", meaning: "expand a licence text (licence:project, licence:notices)", command: None },
214];
215
216// BREP private tests: 5a4d0f8c91b2e6d7