belly icon indicating copy to clipboard operation
belly copied to clipboard

[wip] collections & list widget

Open jkb0o opened this issue 2 years ago • 0 comments

This is introduction to list widgets & collections. It introduces:

  • the Database system param. You can thing of it as of the entity with multiple collections as children.
  • the Collection component.
  • the Item<T> component. Entity with Collection contains multiple Item<T> entities as children.
  • the CollectionRef<T> struct. It has vector-like api: you can push, pop, iter items, etc.
  • the <list> widget. The <list> accepts collection:Entity and render-item:Fn(Entity) -> Eml callback. <list> is responsible for reacting on collection changes (adding or removing items), calling render-item callback when item added to collection and keeping track of rendered items, i.e. map item-entity -> rendered-entity.

This is currently working example:

pub struct TaskData {
    name: String,
    complete: bool,
}

impl Task {
    pub fn complete_label(&self) -> &'static str {
        if self.complete {
            "Completed"
        } else {
            "Complete"
        }
    }
}


fn setup(mut commands: Commands, mut db: Database<'w, 's, Task>) {
    commands.spawn(Camera2dBundle::default());

    let todo = db
        .add_collection()
        .push(Task {
            name: "hello".into(),
            complete: false,
        })
        .push(Task {
            name: "".into(),
            complete: true,
        })
        .id();

    commands.add(eml! {
        <body>
            <list collection=todo render-item=|item| eml! {
                <span c:task>
                    <textinput
                        bind:value=from!(item, Item<Task>:name)
                        bind:value=to!(item, Item<Task>:name)
                    />
                    <button mode="toggle"
                        bind:pressed=from!(item, Item<Task>:complete)
                        bind:pressed=to!(item, Item<Task>:complete)
                    >
                        {from!(item, Item<Task>:complete_label())}
                    </button>
                </span>
            }/>
            <button on:press=run!(|ctx| {
                ctx.push_item(todo, Task {
                    name: format!("Yet another task"),
                    complete: false
                });
            })>"Add task"</button>
        </body>
    });
}

Things to be done:

  • [ ] Implement missed methods for CollectionRef like pop(), remove(), swap(), etc.
  • [ ] change render-item signature - now it accepts only item entity, but should accept both collection and item entities
  • [ ] Support SystemParam parameters for event handlers
  • [ ] Implement named render template tag and use it instead of callback

What is render template tag?

// this is how render-item defines now
eml! {
 <list render-item=|item| { ... } />
}

There are several problems with this approach:

  • render-item passes as param. There is no compile-time checks, only runtime warnings.
  • there is no way to translate this part to asset-based eml.

It is required to introduce <render> compile-time tag that act just like callback

eml! {
  <list collection=collection_entity>
    <render:item task>
      <label bind:value=from!(task, Item<Task>:name) />
    </render>
  </list>
}

jkb0o avatar Aug 17 '23 17:08 jkb0o