`Image` is the only widget in the repository whose size hint is a length in pixels -- everything else hints a share, or nothing -- so it is the only one that exercises a rule beside a hint, a box a widget knows before it is drawn, and the answer the commit before this one changed. The generated trees had none, which is why nothing there could reach that case. `Kind::Image` is a fifth leaf, drawn one time in five, and it steps to a plain rect when the shrinker reduces it: a picture measures nothing either, but its length is its own, so the leaf that takes whatever it is given is the simpler one. The picture is a 64x64 checkerboard of purple and black in 8 px cells, committed at `src/assets/checkerboard.png` beside the generator that draws it -- the way `examples/tabs` keeps its own -- and included rather than opened, so that growing a tree does not depend on a working directory and one seed is one tree whatever anything else does. One upload per tree, however many images it grows: a `TextureHandle` is a counted reference, so the first image in a tree uploads the checkerboard and every one after it clones the handle. Measured: seed 1 at depth 4 grows 13 images and holds 1 texture, seed 6 grows none and holds none, and `a_tree_of_images_uploads_one_texture` asserts it. `Image::new` is what a caller holding a handle needs, since `image` uploads what it is given. A seed names a tree only while the generator draws the same things in the same order, so every seed now grows a different tree. The seed list in `generated.rs` says so: 20 and 86 no longer grow the trees whose defects they once caught, and both of those live on as shrunk fixtures in `unsettled.rs`, which are trees rather than numbers. The seeds those fixtures name are similarly historical, and their file says that too. Format, clippy with and without layout-diagnostics, and the suite (135 + 19 + 13 + 4) are clean. The cold dump is a new baseline of 34,571 boxes over the 400 depth-5 trees, since the trees themselves changed; all three seed scans pass over the new ones -- 400 at depth 5 in 62.79s, 1,000 at depth 6 in 160.20s, 2,000 at depth 4 in 299.58s -- which is what actually checks that images lay out warm the way they do cold. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
62 lines
1.6 KiB
Rust
62 lines
1.6 KiB
Rust
use crate::prelude::*;
|
|
use image::DynamicImage;
|
|
|
|
pub struct Image {
|
|
handle: TextureHandle,
|
|
}
|
|
|
|
impl Widget for Image {
|
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
painter.primitive(&self.handle);
|
|
Size::px(self.handle.size())
|
|
}
|
|
|
|
fn size_hint(&self, axis: Axis) -> Option<LayoutLen> {
|
|
Some(LayoutLen::px(self.handle.size()[axis]))
|
|
}
|
|
}
|
|
|
|
impl Image {
|
|
/// One texture already uploaded, for a caller holding its handle: [`image`]
|
|
/// uploads what it is given, and several widgets showing one picture want
|
|
/// one upload and one slot between them.
|
|
pub fn new(handle: TextureHandle) -> Self {
|
|
Self { handle }
|
|
}
|
|
}
|
|
|
|
pub fn image<State: UiRsc>(image: impl LoadableImage) -> impl WidgetFn<State, Image> {
|
|
let image = image.get_image().expect("Failed to load image");
|
|
move |state| Image {
|
|
handle: state.ui_mut().textures.add(image),
|
|
}
|
|
}
|
|
|
|
pub trait LoadableImage {
|
|
fn get_image(self) -> Result<DynamicImage, String>;
|
|
}
|
|
|
|
impl LoadableImage for &str {
|
|
fn get_image(self) -> Result<DynamicImage, String> {
|
|
image::open(self).map_err(|e| format!("{e:?}"))
|
|
}
|
|
}
|
|
|
|
impl LoadableImage for String {
|
|
fn get_image(self) -> Result<DynamicImage, String> {
|
|
image::open(self).map_err(|e| format!("{e:?}"))
|
|
}
|
|
}
|
|
|
|
impl<const LEN: usize> LoadableImage for &[u8; LEN] {
|
|
fn get_image(self) -> Result<DynamicImage, String> {
|
|
image::load_from_memory(self).map_err(|e| format!("{e:?}"))
|
|
}
|
|
}
|
|
|
|
impl LoadableImage for DynamicImage {
|
|
fn get_image(self) -> Result<DynamicImage, String> {
|
|
Ok(self)
|
|
}
|
|
}
|