Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animate spreading colour in a Line #184

Open
Plecra opened this issue Feb 4, 2023 · 10 comments
Open

Animate spreading colour in a Line #184

Plecra opened this issue Feb 4, 2023 · 10 comments
Labels
a-2d Relates to the 2d package b-enhancement New feature or request c-accepted The issue is ready to be worked on

Comments

@Plecra
Copy link

Plecra commented Feb 4, 2023

Description
I would like a way to draw a line, then fill it with a colour (blue) starting at one end and progressing to the other over time.

This would be an alternative to manually drawing this animation:

  const filled = createSignal(0);
  // (I couldn't figure out how to render Lines, they kept complaining with errors in vite :P)
  view.add(<Rect width={420} height={20} x={0} fill="white"/>)
  view.add(<Rect width={() => filled() * 420} height={20} x={() => (filled() - 1) * 210} fill="blue"/>)
  
  yield* filled(1, 2);

I would hope that a proper API would allow us to floodfill objects without rebuilding them with manual size/pos calculations.

Proposed solution

Nothing yet! I haven't got far enough into the app's guts to know how we'd need to implement this. It's easy if we can use shaders, but a bit of a PITA with html/css. The best option would be using gradients across the rendered boxes

@Plecra
Copy link
Author

Plecra commented Feb 4, 2023

Oh, I see we're rendering on canvases for the shapes! Gradient should be perfect for this, but I can't figure out how to make it work :/

@Plecra
Copy link
Author

Plecra commented Feb 4, 2023

Hahah!

const filled = createSignal(0);
const myRect = createRef<Rect>();
view.add(<Rect ref={myRect} width={420} height={20} x={0} fill={() => new Gradient({
  from: myRect().cacheRect().topLeft,
  to: myRect().cacheRect().topRight,
  stops: [{ color: "blue", offset: filled() }, { color: "white", offset: filled() }]
})}/>);
yield* filled(1, 2, linear);

That's lovely, pretty much what I wanted :) I'll leave it here in case you want to pop it on the docs for Gradient?

@Plecra
Copy link
Author

Plecra commented Feb 4, 2023

(Related feature request: It'd be rlly awesome to be able to use absolute positions with Gradient so I can use it across multiple nodes)

@Plecra
Copy link
Author

Plecra commented Feb 4, 2023

That would be to simplify this:

const filled = createSignal(0);
const space = createSignal<Bounds>();

const layout = <Layout layout direction="column" gap={40} alignItems="center">
  {[[300, 100], [420, 20]].map(([w, h]) => {
    const rect = <Rect width={w} height={h}/> as Rect;
    rect.fill(() => {
      const bounds = space();
      return new Gradient({
        from: bounds.center.transformAsPoint(rect.worldToLocal()),
        to: bounds.center.transformAsPoint(rect.worldToLocal()),
        type: "radial",
        toRadius: Math.sqrt(bounds.width**2 + bounds.height**2) / 2,
        stops: [{ color: "blue", offset: filled() }, { color: "white", offset: filled() }]
      })
    })
    return rect;
  })}
</Layout>;
space(() => layout.cacheRect().transform(layout.localToWorld()));
view.add(layout);

yield* filled(1, 2, linear);

too um 🤔

const filled = createSignal(0);
const space = createSignal<Bounds>();
const gradient = createSignal(() => new Gradient({
    from: space().center,
    to: space().center,
    type: "absolute-radial",
    toRadius: Math.sqrt(space().width**2 + space().height**2) / 2,
    stops: [{ color: "blue", offset: filled() }, { color: "white", offset: filled() }]
}))

const layout = <Layout layout direction="column" gap={40} alignItems="center">
  {[[300, 100], [420, 20]].map(([w, h]) => <Rect width={w} height={h} fill={gradient}/>)}
</Layout>;
space(() => layout.cacheRect().transform(layout.localToWorld()));
view.add(layout);

yield* filled(1, 2, linear);

@aarthificial
Copy link
Contributor

I think the best solution would be to turn Gradient into a node so that it could be placed in the scene:

const gradient = createRef<Gradient>();

view.add(
  <>
    <Gradient ref={gradient} />
    <Rect fill={gradient()} />
  </>
);

It wouldn't render anything, but moving it around would move the gradient from/to points. This way you could not only have absolute gradients but also parent them to other nodes.

@aarthificial aarthificial removed their assignment Feb 6, 2023
@aarthificial aarthificial added b-enhancement New feature or request c-accepted The issue is ready to be worked on a-2d Relates to the 2d package labels Feb 6, 2023
@brandonpelfrey
Copy link

One possible solution to this might be something like...

<Rect radius={10} width={200} height={20} clip >
  <Rect fill={gradient} {/* Other props */} />
</Rect>

So the outer Rect is the actual line being filled, specified with clip properties so that none of the child Rect is drawn outside. Meanwhile the child Rect can be resized or style animated in order to get the style to animate within that line. Would this work? Agreed having a nicer integration with Gradient would be nice.

@Plecra
Copy link
Author

Plecra commented Feb 6, 2023

Hmm could the sizes be specified as percentages of the parent? I don't want to have to manually scale filled

@aarthificial
Copy link
Contributor

aarthificial commented Feb 6, 2023

If by "percentages of the parent" you mean its scale, then yes. After turning Gradient into a Node, putting it inside of another node would make it scale together with it:

const gradient = createRef<Gradient>();

view.add(
  <Rect fill={gradient()}>
    <Gradient ref={gradient} />
  </Rect>
);

But if you mean the size, then no. It's difficult to imagine how something like this would work since the gradient doesn't have to be aligned with the node - it could be diagonal, for example.

@Plecra
Copy link
Author

Plecra commented Feb 6, 2023

Oh, that was confusing of me! :D I'd only seen @brandonpelfrey's comment when I sent that.

As for Gradient as a Node, I don't really understand what you're proposing. Gradients have N stops, they need colour information; and what I'm looking for is being able to build a gradient relative to the Node we're drawing it in, which I can't see the Gradient nodes' solution for. What do you imagine we'd write to render progress bar?

@chris-findlay
Copy link

Regarding progress along a Line, it'd be nice if a line had start% and end% props, as then we could render a line with start% = filled() and a clone with end% = filled(). Same for splines when we get them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a-2d Relates to the 2d package b-enhancement New feature or request c-accepted The issue is ready to be worked on
Projects
None yet
Development

No branches or pull requests

4 participants