mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-14 14:22:45 +00:00
M5: presentation tabs: test both owner guards, document the tab functions
Review follow-up. The closed-owner test now picks a device and switches Responsive in the presenter and checks that the file in front keeps its prototype settings, so removing either `live` guard in present::show fails it. The present harness gets frame_output for the AccessKit tree. The new tab functions get doc comments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8d5a29dfda
commit
bbe691cb43
4 files changed
+68
-1
No files matched your search
@@ -330,6 +330,7 @@ impl Presenter {
|
||||
true
|
||||
}
|
||||
|
||||
/// The frame the presentation started from (Restart goes back there).
|
||||
pub fn start(&self) -> NodeId {
|
||||
self.start
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use egui::accesskit::{Node, Role};
|
||||
use egui::{Key, Pos2, pos2};
|
||||
use figcraft_doc::proto::PrototypeSettings;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::present_tests::{Ui, id, setup};
|
||||
@@ -19,6 +21,41 @@ fn presenting(u: &Ui) -> bool {
|
||||
u.app.inspect()["presenting"] == json!(true)
|
||||
}
|
||||
|
||||
fn nodes(u: &mut Ui) -> Vec<Node> {
|
||||
u.ctx.enable_accesskit();
|
||||
u.frame(vec![]);
|
||||
u.frame_output(vec![]).platform_output.accesskit_update.expect("an AccessKit update").nodes.into_iter().map(|(_, n)| n).collect()
|
||||
}
|
||||
|
||||
fn centre(n: &Node) -> Pos2 {
|
||||
let b = n.bounds().unwrap();
|
||||
pos2(((b.x0 + b.x1) / 2.0) as f32, ((b.y0 + b.y1) / 2.0) as f32)
|
||||
}
|
||||
|
||||
fn click_label(u: &mut Ui, label: &str) {
|
||||
let all = nodes(u);
|
||||
let n = all.iter().find(|n| n.label().is_some_and(|l| l.trim() == label)).unwrap_or_else(|| panic!("no {label:?} on screen"));
|
||||
click(u, centre(n));
|
||||
}
|
||||
|
||||
/// The presenter bar's Options button: it has no label, and sits right of Show comments.
|
||||
fn open_options(u: &mut Ui) {
|
||||
let all = nodes(u);
|
||||
let comments = all.iter().find(|n| n.label() == Some("Show comments (Shift+C)")).unwrap().bounds().unwrap();
|
||||
let options = all
|
||||
.iter()
|
||||
.filter(|n| n.role() == Role::Unknown && n.label().is_none())
|
||||
.filter(|n| n.bounds().is_some_and(|b| b.y0 == comments.y0 && b.x0 > comments.x1))
|
||||
.min_by(|a, b| a.bounds().unwrap().x0.total_cmp(&b.bounds().unwrap().x0))
|
||||
.expect("the Options button");
|
||||
click(u, centre(options));
|
||||
}
|
||||
|
||||
fn front_settings(u: &Ui) -> PrototypeSettings {
|
||||
let page = u.app.session.page().unwrap();
|
||||
crate::present::page_settings(u.app.session.doc().unwrap(), page)
|
||||
}
|
||||
|
||||
/// The middle of presentation tab `k` and of its close button, after `files` file tabs named
|
||||
/// "Untitled".
|
||||
fn present_tab(u: &Ui, files: usize, k: usize) -> (Pos2, Pos2) {
|
||||
@@ -109,6 +146,15 @@ fn a_tab_whose_file_closed_leaves_the_file_in_front_alone() {
|
||||
assert!(presenting(&u));
|
||||
assert!(!crate::tabs::present_owner_active(&u.app));
|
||||
assert_eq!(u.app.inspect()["presenter"]["current"], json!(a), "it keeps playing its own copy");
|
||||
let before = front_settings(&u);
|
||||
click_label(&mut u, "iPhone 16");
|
||||
click_label(&mut u, "iPhone 17 402×874");
|
||||
assert!(presenting(&u));
|
||||
for size in ["Fixed size", "Responsive"] {
|
||||
open_options(&mut u);
|
||||
click_label(&mut u, size);
|
||||
}
|
||||
assert_eq!(front_settings(&u), before, "picking a device or Responsive leaves the file in front alone");
|
||||
assert!(!u.app.session.ds().unwrap().is_dirty());
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,18 @@ impl Ui {
|
||||
}
|
||||
|
||||
pub(crate) fn frame(&mut self, events: Vec<Event>) {
|
||||
self.frame_output(events);
|
||||
}
|
||||
|
||||
/// [`Self::frame`], returning what egui output (the AccessKit tree when it's enabled).
|
||||
pub(crate) fn frame_output(&mut self, events: Vec<Event>) -> egui::FullOutput {
|
||||
self.time += 1.0 / 60.0;
|
||||
let input =
|
||||
RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(1440.0, 900.0))), time: Some(self.time), events, ..Default::default() };
|
||||
let app = &mut self.app;
|
||||
self.ctx.run_ui(input, |ui| app.ui(ui)).textures_delta.clear();
|
||||
let mut out = self.ctx.run_ui(input, |ui| app.ui(ui));
|
||||
out.textures_delta.clear();
|
||||
out
|
||||
}
|
||||
|
||||
pub(crate) fn frames(&mut self, n: usize, events: impl Fn() -> Vec<Event>) {
|
||||
|
||||
@@ -377,6 +377,8 @@ pub fn tab_bar(app: &mut FigApp, ui: &mut Ui) {
|
||||
});
|
||||
}
|
||||
|
||||
/// One presentation tab at `x`: its width, whether it was clicked and whether it was closed (its
|
||||
/// X or a middle click).
|
||||
fn present_tab(ui: &mut Ui, t: &Tokens, tab: &PresentTab, x: f32, bar: Rect, active: bool) -> (f32, bool, bool) {
|
||||
let label = present_tab_name(tab);
|
||||
let w = present_tab_width(tab);
|
||||
@@ -407,15 +409,18 @@ fn present_tab_width(tab: &PresentTab) -> f32 {
|
||||
(regular(12.0).size * 0.6 * present_tab_name(tab).chars().count() as f32 + 64.0).clamp(140.0, 260.0)
|
||||
}
|
||||
|
||||
/// "file - page", or the file alone when the page has no name.
|
||||
fn present_tab_name(tab: &PresentTab) -> String {
|
||||
if tab.page_name.is_empty() { tab.file.clone() } else { format!("{} - {}", tab.file, tab.page_name) }
|
||||
}
|
||||
|
||||
/// The presentation tabs for `ui.inspect`: each one's name and whether it is on screen.
|
||||
pub fn present_tabs_inspect(app: &FigApp) -> serde_json::Value {
|
||||
let on_screen = |id| app.ui.present.is_some() && app.ui.present_active == Some(id);
|
||||
app.ui.presents.iter().map(|t| json!({"name": present_tab_name(t), "active": on_screen(t.id)})).collect()
|
||||
}
|
||||
|
||||
/// The presenter on screen, else the paused one of the active presentation tab.
|
||||
pub fn shown_or_paused(app: &FigApp) -> Option<&Presenter> {
|
||||
app.ui.present.as_ref().or_else(|| {
|
||||
let id = app.ui.present_active?;
|
||||
@@ -423,15 +428,19 @@ pub fn shown_or_paused(app: &FigApp) -> Option<&Presenter> {
|
||||
})
|
||||
}
|
||||
|
||||
/// The index of the open file with `uid`, when a file tab shows it.
|
||||
fn owner(app: &FigApp, uid: u64) -> Option<usize> {
|
||||
app.session.docs.iter().position(|d| d.uid == uid && app.shows(uid))
|
||||
}
|
||||
|
||||
/// The presentation on screen plays the file in front (or no tab holds it). Only then may it read
|
||||
/// and change that file.
|
||||
pub fn present_owner_active(app: &FigApp) -> bool {
|
||||
let Some(tab) = app.ui.present_active.and_then(|id| app.ui.presents.iter().find(|t| t.id == id)) else { return true };
|
||||
owner(app, tab.doc_uid).is_some_and(|i| i == app.session.active)
|
||||
}
|
||||
|
||||
/// Puts the presenter on screen back into its tab, where it keeps its state.
|
||||
fn pause_present(app: &mut FigApp) {
|
||||
let Some(p) = app.ui.present.take() else { return };
|
||||
let id = app.ui.present_active;
|
||||
@@ -440,6 +449,8 @@ fn pause_present(app: &mut FigApp) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Presents from `start`: the tab of its page in this file shows again (from `start` when that
|
||||
/// changed), else a new tab opens.
|
||||
pub fn open_present(app: &mut FigApp, start: NodeId) {
|
||||
let Ok(ds) = app.session.ds() else { return };
|
||||
let (uid, path) = (ds.uid, ds.path.clone());
|
||||
@@ -465,6 +476,7 @@ pub fn open_present(app: &mut FigApp, start: NodeId) {
|
||||
app.ui.home = false;
|
||||
}
|
||||
|
||||
/// Brings tab `id` to the front and its file too, when that file is open.
|
||||
fn show_present(app: &mut FigApp, id: u64) {
|
||||
app.ui.home = false;
|
||||
if app.ui.present.is_some() && app.ui.present_active == Some(id) {
|
||||
@@ -481,6 +493,7 @@ fn show_present(app: &mut FigApp, id: u64) {
|
||||
app.ui.present_active = Some(id);
|
||||
}
|
||||
|
||||
/// Closes tab `id`. If it was on screen, its file comes to the front, or Home when it is closed.
|
||||
pub fn close_present(app: &mut FigApp, id: u64) {
|
||||
let Some(k) = app.ui.presents.iter().position(|t| t.id == id) else { return };
|
||||
let tab = app.ui.presents.remove(k);
|
||||
|
||||
Reference in new issue
Block a user