DioxusLabs / taffy

A high performance rust-powered UI layout library
https://docs.rs/taffy
Other
2.12k stars 107 forks source link

Fixed-size layout overflows with `measure_function`. #732

Open SoulSharer opened 1 week ago

SoulSharer commented 1 week ago

taffy version

02453b2c47dd639e337294fcc98318232b2e93b4 and fae17c2b2b69282cbf3cef399a56aaca052e7507 (v0.6.1)

Platform

Rust

What you did

I took examples/measure.rs and changed:

Modified measure.rs follows:

mod common {
    pub mod image;
    pub mod text;
}
use common::image::{image_measure_function, ImageContext};
use common::text::{text_measure_function, FontMetrics, TextContext, WritingMode, LOREM_IPSUM};
use taffy::prelude::*;

enum NodeContext {
    Text(TextContext),
    Image(ImageContext),
}

fn measure_function(
    known_dimensions: taffy::geometry::Size<Option<f32>>,
    available_space: taffy::geometry::Size<taffy::style::AvailableSpace>,
    node_context: Option<&mut NodeContext>,
    font_metrics: &FontMetrics,
) -> Size<f32> {
    if let Size { width: Some(width), height: Some(height) } = known_dimensions {
        return Size { width, height };
    }

    match node_context {
        None => Size::ZERO,
        Some(NodeContext::Text(text_context)) => {
            text_measure_function(known_dimensions, available_space, &*text_context, font_metrics)
        }
        Some(NodeContext::Image(image_context)) => image_measure_function(known_dimensions, image_context),
    }
}

fn main() -> Result<(), taffy::TaffyError> {
    let mut taffy: TaffyTree<NodeContext> = TaffyTree::new();

    let font_metrics = FontMetrics { char_width: 10.0, char_height: 10.0 };

    let text_node = taffy.new_leaf_with_context(
        Style {
            flex_shrink: 1.0,
            ..Default::default()
        },
        NodeContext::Text(TextContext { text_content: LOREM_IPSUM.into(), writing_mode: WritingMode::Horizontal }),
    )?;

    let image_node = taffy
        .new_leaf_with_context(Style {
            flex_shrink: 1.0,
            ..Default::default()
        }, NodeContext::Image(ImageContext { width: 600.0, height: 720.0 }))?;

    let root = taffy.new_with_children(
        Style {
            display: Display::Flex,
            flex_direction: FlexDirection::Column,
            size: percent(1.0),
            ..Default::default()
        },
        &[text_node, image_node],
    )?;

    // Compute layout and print result
    taffy.compute_layout_with_measure(
        root,
        Size { width: taffy::AvailableSpace::Definite(512.0), height: taffy::AvailableSpace::Definite(512.0) },
        // Note: this closure is a FnMut closure and can be used to borrow external context for the duration of layout
        // For example, you may wish to borrow a global font registry and pass it into your text measuring function
        |known_dimensions, available_space, _node_id, node_context, _style| {
            measure_function(known_dimensions, available_space, node_context, &font_metrics)
        },
    )?;
    taffy.print_tree(root);

    Ok(())
}

Result is:

TREE
└──  FLEX COL [x: 0    y: 0    w: 512  h: 512  content_w: 600  content_h: 810  border: l:0 r:0 t:0 b:0, padding: l:0 r:0 t:0 b:0] (NodeId(4294967299))
    ├──  LEAF [x: 0    y: 0    w: 512  h: 90   content_w: 512  content_h: 90   border: l:0 r:0 t:0 b:0, padding: l:0 r:0 t:0 b:0] (NodeId(4294967297))
    └──  LEAF [x: 0    y: 90   w: 512  h: 614  content_w: 600  content_h: 720  border: l:0 r:0 t:0 b:0, padding: l:0 r:0 t:0 b:0] (NodeId(4294967298))

Second leaf height overflows available space.

What went wrong

Additional information

I've tried different style configurations, including flex-basis, but all seems to come down to a measure_function which does not receive full information from a parent to properly size image content that would fit the layout.

[examples\common\image.rs:12:5] known_dimensions = Size {
    width: Some(
        512.0,
    ),
    height: None,
}
[examples\common\image.rs:12:5] known_dimensions = Size {
    width: Some(
        512.0,
    ),
    height: None,
}
[examples\common\image.rs:12:5] known_dimensions = Size {
    width: None,
    height: Some(
        614.4,
    ),
}
[examples\common\image.rs:12:5] known_dimensions = Size {
    width: None,
    height: None,
}
nicoburns commented 6 days ago

This seems like a legitimate bug. I think the root of the problem here is that we need to treat "replaced" content such as images specially (how exactly, I'm not quite sure). You might be able to work around it by returning a zero size when the MinContent size is requested.

SoulSharer commented 5 days ago

Appreciate a timely response!

I tried returning Size::ZERO when AvailableSpace::MIN_CONTENT, it now fits perfectly but the whole point of keeping aspect ratio of an image with measure_function is sadly gone (layout bounds are now stretched).