Skip to main content

render_hosting

Function render_hosting 

Source
pub fn render_hosting(hv: &HostingView, width: u32, height: u32) -> Rendered
Expand 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}