DioxusLabs / taffy

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

Provide a builder pattern interface to the construction layout tree #275

Open giantpand2000 opened 1 year ago

giantpand2000 commented 1 year ago

What problem does this solve or what need does it fill?

Provides a more compact, write-friendly, ergonomic interface

What solution would you like?

@LayoutSpec
public class PlaygroundComponentSpec {
  @OnCreateLayout
  static Component onCreateLayout(ComponentContext c) {
    return Row.create(c)
      .child(
        Row.create(c)
          .widthDip(100)
          .heightDip(100))
      .child(
        Row.create(c)
          .widthDip(100)
          .heightDip(100)
          .marginDip(YogaEdge.HORIZONTAL, 20)
          .flexGrow(1))
      .child(
        Row.create(c)
          .widthDip(100)
          .heightDip(100))
      .widthDip(500)
      .heightDip(500)
      .alignItems(YogaAlign.FLEX_START)
      .paddingDip(YogaEdge.ALL, 20)
      .build();
  }
}

What alternative(s) have you considered?

Since many anonymous nodes are used, a method of result feedback is needed, such as having the node save a callback function, or a Trait that provides result feedback

Additional context

The sample code comes from the Yoga home page, a Java package called litho. But it can be translated quite directly to Rust code.

alice-i-cecile commented 1 year ago

I'm on board :) I think this is very useful when using this crate more directly.

giantpand2000 commented 1 year ago

Additions:

What alternative(s) have you considered?

Using NodeRef(s) might be a better way to get information about individual nodes from the tree builder. The idea comes from Yew's NodeRef. Both interior mutability and initialization tests are provided.

nicoburns commented 1 year ago

Inspired by this discussion on Discord, I have come to the opinion that an API that automatically creates a Style::DEFAULT and then passes a mutable reference to the created style object into a closure might be particularly nice to use:

Something like:

Style::build(|style| {
    style.width = points(100.);
    style.height = points(200.);
});

where the style parameter is an &mut Style. With setters on Style this could become:

Style::build(|style| style.width(100.).height(200.))

where the style parameter is an &mut Style. And if trait a were used to accept a tuple of callbacks / types implementing a trait, than we could potentially create an API like the following (which would also allow the easier creation of user-defined style helpers):

Style::build((width(100.), height(200.))
Weibye commented 1 year ago
Style::build((width(100.), height(200.))

I really like this pattern!

nicoburns commented 1 year ago

I think something like the following would be quite easy to create:

let root_node = Style::column()
    .width(800)
    .height(100)
    .with_children(|tree| {
        Style::leaf().width(800).height(100).build(&mut tree);
        Style::leaf().width(800).flex_grow(1.0).build(&mut tree);
    })
    .build(&mut tree);

This is the same as the example in the README. It's quite an improvement!

nicoburns commented 1 year ago

A couple of other API idea that I think would be doable.

With nested builders, closures taking &mut Style and dedicated constructors for row/column/grid:

    let root_node_id = taffy.new_flex_column(
        |style| style.width(800).height(600),
        |cx| {
            cx.new_leaf(|style| style.height(100));
            cx.new_leaf(|style| style.flex_grow(1.0));
        },
    );

Using a macro (yoga-rs implements an API like this using a simple macro_rules macro):

    let root_node_id = taffy.new_flex_column(style! {width: 800 px, height: 600 px}, |cx| {
        cx.new_leaf(style! {height: 800 px});
        cx.new_leaf(style! {flex_grow: 1.0});
    });
alice-i-cecile commented 1 year ago

The builder approach is my favorite here stylistically :) Would like to avoid macros if possible, and closures can be rough for beginners.