Merge branch 'pr-518' into batch/t5

# Conflicts:
#	contributors/people.toml
This commit is contained in:
bflatastic
2026-10-16 01:35:50 +07:00
3 changed files with 55 additions and 6 deletions
+3
View File
@@ -41,3 +41,6 @@ real_name = "Justin John"
[people.Nael-Nathanael]
sync_github_name = true
[people.volkovskey]
real_name = "Andrii Volkov"
+19
View File
@@ -190,6 +190,25 @@ fn a_toast_wakes_the_ui_only_to_go_away() {
assert!(h.app.ui.toast.is_none(), "gone after 3 s");
}
#[test]
fn a_toast_raised_by_a_slow_frame_still_shows_for_3_s() {
let (mut h, _) = document();
h.settle();
// The Open dialog and a failed open block the frame that raises the error: the next frame
// comes 5 s after it started.
h.app.toast("can't open file.fig");
h.frame(vec![]);
h.time += 5.0;
h.frame(vec![]);
assert!(h.app.ui.toast.is_some(), "shown, not expired by the wait");
h.time += 2.0;
h.frame(vec![]);
assert!(h.app.ui.toast.is_some());
h.time += 1.5;
h.frame(vec![]);
assert!(h.app.ui.toast.is_none(), "gone after 3 s");
}
#[test]
fn a_finished_background_render_wakes_the_ui() {
let ctx = egui::Context::default();
+33 -6
View File
@@ -337,6 +337,17 @@ pub type AutoSaveEncoding = std::thread::JoinHandle<(figcraft_engine::cmd::file:
/// How long a toast shows (seconds).
const TOAST_SECS: f64 = 3.0;
/// How long a toast has been showing ([`UiState::toast`]).
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ToastClock {
/// Raised, not drawn yet.
Raised,
/// Drawn once; the next frame starts the clock.
Drawn,
/// Showing since this time (UI clock, seconds).
Since(f64),
}
/// Draw another frame `secs` from now (nothing else needs drawing until then: an idle window draws
/// no frames).
#[track_caller]
@@ -402,8 +413,8 @@ pub struct UiState {
pub title_bar: TitleBar,
/// Every icon on one screen, for reviewing the set (control channel `ui.set {iconGallery}`).
pub icon_gallery: bool,
/// A message at the bottom of the window and when it first showed (UI clock, seconds).
pub toast: Option<(String, Option<f64>)>,
/// A message at the bottom of the window and how long it has been showing.
pub toast: Option<(String, ToastClock)>,
pub present: Option<present::Presenter>,
/// The full-window presenter's options (how it sizes the prototype), kept between
/// presentations.
@@ -740,7 +751,7 @@ impl FigApp {
}
pub fn toast(&mut self, msg: impl Into<String>) {
self.ui.toast = Some((msg.into(), None));
self.ui.toast = Some((msg.into(), ToastClock::Raised));
}
/// Say that `n` documents left unsaved by a crash were opened again from recovery copies.
@@ -1379,16 +1390,32 @@ impl FigApp {
}
fn toast_ui(&mut self, ctx: &egui::Context) {
let Some((msg, shown)) = &mut self.ui.toast else { return };
let Some((msg, clock)) = &mut self.ui.toast else { return };
let now = ctx.input(|i| i.time);
let left = TOAST_SECS - (now - *shown.get_or_insert(now));
// The clock starts on the frame after the one that first draws the toast: that frame's
// time was taken before whatever raised it ran (a file dialog, a long open), and would
// count that wait against it, so a failed open's message expired before it was seen.
let left = match *clock {
ToastClock::Raised => {
*clock = ToastClock::Drawn;
ctx.request_repaint();
TOAST_SECS
}
ToastClock::Drawn => {
*clock = ToastClock::Since(now);
TOAST_SECS
}
ToastClock::Since(t) => TOAST_SECS - (now - t),
};
if left <= 0.0 {
self.ui.toast = None;
return;
}
let msg = msg.clone();
// Nothing moves while it shows: the next frame it needs is the one that hides it.
repaint_in(ctx, left);
if matches!(clock, ToastClock::Since(_)) {
repaint_in(ctx, left);
}
let screen = ctx.content_rect();
egui::Area::new(egui::Id::new("toast"))
.fixed_pos(egui::pos2(screen.center().x, screen.max.y - 96.0))