nicbarker / clay

High performance UI layout library in C.
https://nicbarker.com/clay
zlib License
1.29k stars 31 forks source link

[Feature Request] Seed id hash with parent's id #26

Open bullno1 opened 2 months ago

bullno1 commented 2 months ago

Currently, element id needs to be globally unique. Copy&paste or refactoring reusable components into functions would be a bit of a pain since you'd need to parameterize all the ids.

What if the id only has to be unique among siblings? Clay__Rehash already exists but it is only used for debugger. The fact that it exists mean that kind of id is useful.

CLAY_ID_AUTO on the other hand, does not allow providing a string and counter which are needed to identify elements independent of orders. e.g: In a list of items.

There are 2 ways of doing this:

I can make PR for either of these.

nicbarker commented 2 months ago

Hello! Firstly, I completely agree with your assertion that we need a local context-aware way to auto generate IDs. At present it's quite painful to have to uniquely everything in the hierarchy, even for immediate mode renderers where it's almost entirely unnecessary. I'll merge your PR in the short term to address that particular problem.

Secondly given that you've dug in here already, I'd like to get your opinion on an API change I'm working on at the moment. It replaces all the type-specific element macros with a single generic CLAY() macro, and allows you to configure this element by attaching whichever configs you want:

carbon(88)

I specifically started this change to address the painful ergonomics of working on an interface where most elements needed a border and a rectangle.

The main idea is that in the new api, all configs are optional, including ID and Layout, e.g:

// Create an empty layout element with no children and the default layout config
CLAY()

// Create a scrolling element with a border and background
CLAY(CLAY_SCROLL(...etc), CLAY_BORDER(...etc), CLAY_RECTANGLE(...etc)) {
    // ...children
}

// Tag a rectangle with a specific ID for mouse interactions, etc
CLAY(CLAY_ID("Button"), CLAY_RECTANGLE(...etc)) {
    // ...children
}

With the refactor being relevant to this situation because I'm planning to attach generated ids to each element if the user chooses not to provide one. It makes sense for the auto generated ID to be derived from the parent ID, and positional based on the sibling index.

nicbarker commented 2 months ago

This was addressed in https://github.com/nicbarker/clay/pull/27 but I will leave it open until I've updated the README to include documentation of the new feature.

bullno1 commented 2 months ago

It replaces all the type-specific element macros with a single generic CLAY() macro, and allows you to configure this element by attaching whichever configs you want

Since the user can pick a combination of features, how about having just one type: Clay_Element? Then features can be turned on by initializing it.

CLAY(
    CLAY_SCROLL(...), // Enable scrolls
    CLAY_BORDER(...), // Enable border
) {
}

The API is essentially the same but the implementation is different. There is only Clay_Element. The renderer just have to check whether a feature is enabled like:

if (cmd.has_border) {
    // draw border
}

if (cmd.has_background) {
    // draw solid background
}

The macro CLAY_SCROLL(...) for example expands to something like: .scroll = { .enabled = true, __VA_ARGS__}. With 0 initialization by default, if a feature is not included, it will be disabled.

There would be fewer elements to name or inspect.

Maybe scroll is somewhat special but image, rect, border and (plain) container are just decorations and styles.

Another radical idea about id: Given that local id seems to be more prevalent, how about make it the default (CLAY_ID is local)? And have CLAY_ID_GLOBAL to create global id instead?

nicbarker commented 2 months ago

Yep, you're describing pretty much my exact line of thinking - a single macro that can be configured with whichever features. There is a little internal work to do to support this but it shouldn't take too long.

Another radical idea about id: Given that local id seems to be more prevalent, how about make it the default (CLAY_ID is local)? And have CLAY_ID_GLOBAL to create global id instead?

Yes, I'm very much thinking along the same lines. I want it to be obvious when an element is tagged with a global ID for a reason - it's used for click handling etc.

I actually would prefer if the IDs were local internally, but mostly auto generated. e.g. if you don't specify an ID at all:

CLAY_ELEMENT(CLAY_SCROLL(), CLAY_RECTANGLE()) { // Automatically assigned CLAY_ID_LOCAL("0")
    CLAY_ELEMENT() // Automatically assigned CLAY_ID_LOCAL("0")
    CLAY_ELEMENT() // Automatically assigned CLAY_ID_LOCAL("1")
    CLAY_ELEMENT() // Automatically assigned CLAY_ID_LOCAL("2")
    CLAY_ELEMENT() // Automatically assigned CLAY_ID_LOCAL("3")
    CLAY_ELEMENT(CLAY_TAG("ClickableButton")) {} // ID is already specified with a global so no local ID is attached
}

This way IDs would be internally be represented by trees of parent -> sibling index, i.e. a local id might end up being equivalent to CLAY_ID("0214"), which would mean 0th sibling -> children(2nd sibling) -> children(1st sibling) -> children(4th sibling)