pub fn render_hosting(hv: &HostingView, width: u32, height: u32) -> RenderedExpand description
Render an existing hosting view — used when the root is swapped between frames.
Examples found in repository?
examples/hot_reload.rs (line 365)
356fn drive() {
357 let p = path();
358 std::fs::write(&p, STARTER).expect("write starter");
359 reload(&p);
360 watch(p.clone());
361
362 let window = swiftui_native::Window::new(&content_view(), "Hot reload", 360.0, 420.0);
363 let shot = |n: &str| {
364 let out = format!("{}/../../target/hot_reload_live_{n}.png", env!("CARGO_MANIFEST_DIR"));
365 let r = swiftui_native::render::render_hosting(window.hosting(), 360, 420);
366 r.write_png(&out);
367 (out, r)
368 };
369
370 // Pixels differing *above* the status strip.
371 //
372 // The strip reports the generation, so it changes on every reload whether
373 // or not the screen did — comparing whole frames would make "the screen
374 // survived a file that does not parse" indistinguishable from "the screen
375 // changed".
376 fn screen_diff(
377 a: &swiftui_native::render::Rendered,
378 b: &swiftui_native::render::Rendered,
379 ) -> u32 {
380 if a.width != b.width || a.height != b.height {
381 return u32::MAX;
382 }
383 // Measured, not estimated: with a 420-point capture the strip's own
384 // pixels start at row 294, so everything from 290 down is excluded.
385 // Guessing 90 left part of the strip in the comparison and reported a
386 // screen that had not moved as having moved.
387 const SCREEN_ROWS: u32 = 290;
388 let keep = (SCREEN_ROWS.min(a.height) * a.width * 4) as usize;
389 a.pixels[..keep]
390 .chunks_exact(4)
391 .zip(b.pixels[..keep].chunks_exact(4))
392 .filter(|(x, y)| x != y)
393 .count() as u32
394 }
395
396 // Let the window settle before the first capture, or it is caught blank.
397 swiftui_native::render::pump_events(1.0);
398 let (first_path, first) = shot("1_start");
399
400 // A user changes the toggle. Everything after this checks it stays
401 // changed, which is what separates reloading from restarting.
402 spec::set_flag("dark", true);
403 swiftui_native::state::refresh();
404 window.set_root(&content_view());
405 swiftui_native::render::pump_events(0.4);
406
407 /// One edit, and whether the screen should follow it.
408 struct Edit {
409 name: &'static str,
410 src: &'static str,
411 /// `false` for a spec that does not parse: the screen must *not*
412 /// change, because the last good one has to stay put.
413 expect_change: bool,
414 }
415
416 let edits = &[
417 Edit {
418 name: "2_edited",
419 src: "vstack spacing=10 pad=20 align=leading {\n \
420 text \"Edited while running\" style=title bold color=red\n \
421 text \"the process never restarted\" color=secondary size=12\n \
422 toggle \"Dark mode\" @dark\n \
423 gauge 0.9 label=\"reloaded\"\n}",
424 expect_change: true,
425 },
426 Edit {
427 name: "3_restructured",
428 src: "zstack align=center frame=300x200 {\n \
429 color #223344 frame=280x180 corner=14\n \
430 text \"a different shape entirely\" color=white size=14\n}",
431 expect_change: true,
432 },
433 Edit {
434 // Mid-edit: a brace that is never closed. The screen must survive.
435 name: "4_broken",
436 src: "zstack align=center frame=300x200 {\n color #223344",
437 expect_change: false,
438 },
439 ];
440
441 let mut shots = vec![(first_path, first)];
442 let mut ok = true;
443 for e in edits {
444 std::fs::write(&p, e.src).expect("write edit");
445 let deadline = std::time::Instant::now() + Duration::from_secs(4);
446 while !PENDING.load(Ordering::Relaxed) && std::time::Instant::now() < deadline {
447 swiftui_native::render::pump_events(0.05);
448 }
449 if !PENDING.swap(false, Ordering::Relaxed) {
450 eprintln!("{}: the watcher never fired", e.name);
451 std::process::exit(1);
452 }
453 swiftui_native::state::refresh();
454 window.set_root(&content_view());
455 swiftui_native::render::pump_events(0.6);
456 let (path_now, shot_now) = shot(e.name);
457
458 let (prev_path, prev) = shots.last().expect("a previous shot");
459 let d = screen_diff(prev, &shot_now);
460 println!("{prev_path}\n -> {path_now}\n {d} pixels differ above the status strip");
461 match (e.expect_change, d) {
462 (true, 0) => {
463 eprintln!(" the window did not follow the edit");
464 ok = false;
465 }
466 (false, n) if n > 0 => {
467 eprintln!(" a file that does not parse replaced the screen");
468 ok = false;
469 }
470 _ => {}
471 }
472 shots.push((path_now, shot_now));
473 }
474
475 // …and the toggle is still on, after every reload and a broken file.
476 let dark = spec::slot_summary().into_iter().find(|(k, _)| k == "dark").map(|(_, v)| v);
477 println!("slot `dark` after every reload: {dark:?}");
478 if dark.as_deref() != Some("true") {
479 eprintln!(" a reload reset the slot");
480 ok = false;
481 }
482
483 std::fs::write(&p, STARTER).expect("restore spec");
484 if !ok {
485 std::process::exit(1);
486 }
487 println!("followed every valid edit, survived an invalid one, kept its state");
488}