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

inbetweening #10

Merged
merged 33 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3dc4ae4
inbetweening: add skeleton variables for KeyFrames mechanism
gucio321 Mar 11, 2023
b08e2c2
animation: update interface
gucio321 Mar 11, 2023
c3124d2
animator: add more variants of Start method
gucio321 Mar 11, 2023
fb52e1c
key frame: add mechanism used for obtaining a next/previous KeyFrame
gucio321 Mar 11, 2023
0904bbd
animator: implement certain Start* methods
gucio321 Mar 11, 2023
68064ad
animator: use break to exit loop
gucio321 Mar 11, 2023
5084634
animator: when playing animation, range over all its KeyFrames requested
gucio321 Mar 11, 2023
c6cb897
animator: implement KeyFrames in Build
gucio321 Mar 11, 2023
531e4c4
implement transition animation
gucio321 Mar 11, 2023
1f305a0
transition: finish implementation; improve example
gucio321 Mar 11, 2023
962f0c6
trigger: take PlayMode as an argument
gucio321 Mar 11, 2023
e41c59a
color flow: implement KeyFrames system
gucio321 Mar 12, 2023
a2eb6d6
move: work on implementing KeyFrames system
gucio321 Mar 12, 2023
43f9f9d
key_frames: add a special case if only one KeyFrame available
gucio321 Mar 12, 2023
0cef920
move: fix a few minor bugs
gucio321 Mar 12, 2023
4bcd605
code cleanup
gucio321 Mar 12, 2023
3c2e1bc
readme: update
gucio321 Mar 12, 2023
4f79ed6
Restyled by prettier-markdown
restyled-commits Mar 12, 2023
b2ef2d7
Merge pull request #11 from gucio321/restyled/inbetweening
gucio321 Mar 12, 2023
23d669c
readme: fix some typos
gucio321 Mar 12, 2023
fda6b88
rename: KeyFrames -> KeyFramesCount
gucio321 Mar 12, 2023
f7f94c7
transition: extend starter func to also take a PlayMode
gucio321 Mar 12, 2023
1ef35f5
animation: percentage progress always increases (don't reverse in there)
gucio321 Mar 12, 2023
6c771af
play mode; remove PlayAuto (not needed and confusing now)
gucio321 Mar 12, 2023
ddc50f3
code: cleanup
gucio321 Mar 12, 2023
ca43114
examples; cleanup
gucio321 Mar 12, 2023
fff1bbe
starter: create separated interface for starter functions
gucio321 Mar 12, 2023
088e656
readme: update
gucio321 Mar 12, 2023
c2e8240
animator: add ID method
gucio321 Mar 12, 2023
20acfdc
bugfix: fix bug in StartWhole
gucio321 Mar 12, 2023
7dafb01
animator: fix one datat race
gucio321 Mar 13, 2023
aba4b12
animation: pass PlayMode to BuildANimation
gucio321 Mar 13, 2023
5d30bfe
readme: update'
gucio321 Mar 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 101 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,85 +6,100 @@
This is a module for [giu](https://github.com/AllenDang/giu) providing an
animation system.

![giu-animations demo](./docs/demo.gif)

# Documentation

## How to use?

For complete code, please check out [`_examples`](./_examples)
For complete code, please check out [examples](./_examples)

### important note

Please make shure that you're using the same version of giu
Please make sure that you're using the same version of giu
as this project (technically, you need to use giu version
that uses the same imgui-go version as yours)

### Defining an animation

At the moment, there are two implementations of animations:
At the moment, there are three implementations of animations:

- [Transition](#transition) - a smooth transition between two windows/sets of windows e.t.c.
**NOTE** applying this animation to single widgets is not implemented yet and may
not work as expected.
- [on-hover color change](#hover-color) - you can apply this animation to any **single widget**
that `imgui.IsItemHovered` applies for.
You can specify any style color you want.
- [Movement](#move) - moves DrawCursor emulating moving an object (`giu.Widget`).
- [Color Flow](#color-flow) - you can apply this animation to any widget
You can configure this animation to make your button hover smoother or change it into a rainbow!
- [Movement](#move) - moves DrawCursor emulating moving an object (aka `giu.Widget`).

Lets shortly discuss particular types of animations:

#### transition
#### Transition

Lets look at the API:

```go
func Transition(renderer1, renderer2 func(starter func()) *TransitionAnimation {...}
func Transition(renderers ...func(starter func(mode PlayMode))) *TransitionAnimation {...}
```

`renderer1` and `renderer2` are just two stages of trasition.
In stage 1, `renderer1` is called, and in stage 2 - `renderer2`.
When animation is being plaid, both renderers are called and suitable
alpha value is pushed to imgui style for both of them.

The argument to the poth renderers is a pointer to Animator.Start (see later)
`renderers` are just [key frames](key-frame) of trasition.
In each stage appropiate renderer is called.
The argument to the renderers is a pointer to Animator.Start (see later)
so that you can call it to play the animation.

#### Hover color
#### Color flow

```go
func HoverColor(
func ColorFlow(
widget giu.Widget,
hoverColor, normalColor func() color.RGBA,
hoverID, normalID giu.StyleColorID,
) *HoverColorAnimation {...}
applying []giu.StyleColorID,
colors ...func() color.RGBA,
) *ColorFlowAnimation {...}
```

- The first argument is a widget that should apply to
- hoverColor and normalColor are functions that returns hover and standard (non-hover) colors
- hoverID, normalID - style IDs that hovering applies to.
- The first argument is a **function** producing a **widget** that the animation should apply to
- next is the list of style-color identifiers. Color changes applies to all of them.
- and the last list of arguments are [key frames](#key-frame) of color flow.

There is also a variant of the above method called `HoverColorStyle`, which does not need
`hoverColor` and `normalColor` arguments. These colors are obtained
There is also a variant of the above method called `ColorFlowStyle`, which does not need
colors list. These colors are obtained
by function like this:

```go
func() color.RGBA {
return imgui.CurrentStyle().GetStyleColor(styleID)
}
```

#### Move
#### Move

```go
Move(
w giu.Widget,
delta imgui.Vec2,
) *MoveAnimation {...}
func Move(w func(starter StarterFunc) giu.Widget, steps ...*MoveStep) *MoveAnimation {...}
```

This will mmove `w` from the position, it was
at the moment of calling `Move(...)` (called `start`)
to `start` + `delta`.

##### Easing
This will move `w` around the steps.
Lets take a closer look on steps now:

- You create a step with `Step` or `StepVec` methods.
- You have two options of specifying position:
- you can make it relative to the previous step. This way the system will
take position and add it to the position of the previous step
(and do it until it reaches first step or any step with absolute position)
- After calling `Absolute()` method of the MoveStep, its position becomes
absolute so that it does not rely on any previous step.
- An additional feature of Steps is a Bezier Curve implementation.
In order to enable it, simply call `Bezier` method and specify as meny points as you wish.

One more important thing to mention is the first step.
By default, position of the first step you specify **will be treated
absolute, even thouth it wasn't set to be.** To change this
there are two additional methods of `MoveAnimation`.

- the first one is called `StartPos` and takes one argument of the following type:
`func(startPos imgui.Vec2) *MoveStep`. It is expected to return non-nil MoveStep.
`startPos` argument is the position of drawing cursor at the moment **of first call** of
`Animator`.
- another method is tu simply call `DefaultStartPos` method. It takes no arguments and acts
like most users would like to use `StartPos` - it returns `Step(startPos)`.

### Easing

There are some extra ways of playing animation flow:

Expand All @@ -108,24 +123,14 @@ const (

for further reference, see https://easings.net

##### Bezier curve
### Note about StarterFunc

Move animation supports [Bezier Curve](https://pomax.github.io/bezierinfo/).
It meas that move animation could be very smooth if you find it necessary.
You have two ways to calculate these points:
- go through [google](https://google.com) and use tone of paper to understand this strange math or
- just type random values and check what happens :smile:
This interface holds a reference to the part of `AnimatorWidget` responsible
for starting animations. At the moment, there are three functions

The api looks as follows:

```go
func (m *MoveAnimation) Bezier(controlPoints ...imgui.Vec2) *MoveAnimation {...}
```

you can add as many control points as you which to.
Each point will make the curve stranger.
The only thing you need to remember is, that **these points
are relative to `startPos`**. They will automatically become `startPos` + `controlPoint`.
- Start(PlayMode) go to the next KeyFrame (forwards or backwards)
- StartKF(base, destiny KeyFrame, mode PlayMode) go from `base` to `destiny` in `mode` direction (frame by frame)
- StartWhole(mode) - play from 0 to last Key Frame

### Using animator

Expand All @@ -138,24 +143,56 @@ animator's api is designed so that you don't need to do so every time.
As an argument to `Animator(...)` constuctor, you pass perviously created animation.

Animator has some useful methods:

- `Duration` allows you to specify animation's duration (default is 0.25 s)
- `FPS` sets Frames per second value for animation playback (default is 60)
**NOTE** it is not real application's FPS! It just describes how often
animation's status is updated.
**NOTE** it is not real application's FPS! It just describes how often
animation's status is updated.
- `Start` - this method you can use to invoke animation play.
- `IsRunning` returns true, if animation is being plaid right now.

#### ID

`AnimatorWidget` has a special ID method that allows you to specify
your own giu-ID. This ID is used to store an internal aniamtor's state
inside of giu context.
Using this method is extremely important if you want to avoid confusing panics
when using TransitionAnimation along with sub-animators inide that animation.
It may happen, that one animator receives the same ID as the previous one.
This may lead to unexpected behaviour or even panic! Its good practice to set
unique ID everywhere!

### Auto triggering

Animator provides a simple way of automatical animations start.
You can do this by using `Trigger` method. This method takes three arguments:

- `TriggerType` (TriggerNever, TriggerOnChange, TriggerOnTrue) tells Animator when
to start the animation.
- play mode - tells how to start it
- and the last argument is a trigger function `func() bool` (**TIP** you can use any of `imgui` functions like `imgui.IsItemHovered`)

### Key Frame

Key frames are states of animation with a specified animation states.
All other states between them are calculated on the go.
Key frames system in this module is not much experienced, but it should
suit needs of most users. For more information about implementation
of this system in particular animation types, see above.

## Creating your own animation

You can use this API to create your own animation.
To do soo, lets take a look on `Animation` interface.

```go
type Animation interface {
Init()
Reset()
KeyFramesCount() int

BuildNormal(starter func())
BuildAnimation(animationPercentage float32, starter func())
BuildNormal(currentKeyFrame KeyFrame, starter func())
BuildAnimation(animationPercentage, animationPurePercentage float32, startKeyFrame, destinationKeyFrame KeyFrame, starter func())
}
```

Expand All @@ -170,6 +207,11 @@ you can put some initialization here.

Reset is called along with `(*Animator).Start`

### KeyFramesCount

Returns a number of key frames the animation implements.
This number determines behaviour of Animator while calling Start\*

### BuildNormal

is called when `!(*Animator).IsRunning()`
Expand All @@ -186,7 +228,7 @@ You can do some calculations there.
# Contribution

If you implement something interessting, find any bugs, or
improvements and would be so kind to open a PR,
improvements and if you would be so kind to open a PR,
your contribution is welcome!

# Motivation
Expand All @@ -196,4 +238,4 @@ But (as I'm an author of that system) I've decided to share it for public - feel

# License

This project is shared under (attached) MIT License.
This project is shared under (attached) [MIT License](LICENSE).
70 changes: 53 additions & 17 deletions _examples/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,38 @@ func loop() {
a := int32(easingAlg)
animations.Animator(
animations.Transition(
func(starter func()) {
func(starterFunc animations.StarterFunc) {
giu.Window("window1").Layout(
giu.Label("I'm a window 1"),
animations.Animator(
animations.ColorFlow(
giu.Button("start transition").OnClick(func() {
starter()
starterFunc.Start(animations.PlayForward)
}),
[]giu.StyleColorID{
giu.StyleColorButtonHovered,
giu.StyleColorButton,
},
func() color.RGBA {
return colornames.Blue
},
func() color.RGBA {
return colornames.Red
},
func() color.RGBA {
return colornames.Blue
return colornames.Green
},
func() color.RGBA {
return colornames.Yellow
},
giu.StyleColorButtonHovered,
giu.StyleColorButton,
),
).
Duration(time.Second).
FPS(60).
Trigger(animations.TriggerOnChange, imgui.IsItemHovered),
Trigger(animations.TriggerOnTrue, animations.PlayForward, imgui.IsItemHovered),
giu.Button("Play whole transition!").OnClick(func() {
starterFunc.StartWhole(animations.PlayForward)
}),
giu.Checkbox("Play on hover", &playOnHover),
animations.Animator(
animations.Move(func(starter animations.StarterFunc) giu.Widget {
Expand All @@ -52,26 +63,51 @@ func loop() {
easingAlg = animations.EasingAlgorithmType(a)
}),
),
giu.Button("move me!").OnClick(func() {
starter(animations.PlayForward)
}),
giu.Row(
giu.Button("play backwards").OnClick(func() {
starter.Start(animations.PlayBackwards)
}),
giu.Button("move me!").OnClick(func() {
starter.Start(animations.PlayForward)
}),
),
).Size(200, 80)
}, imgui.Vec2{X: 20, Y: 100}).
Bezier(imgui.Vec2{X: 20, Y: 20}, imgui.Vec2{X: 90}),
},
animations.Step(20, 100).
Bezier(imgui.Vec2{X: 20, Y: 20}, imgui.Vec2{X: 90}),
).DefaultStartPos(),
).Duration(time.Second*3).
FPS(120).
EasingAlgorithm(easingAlg).
Trigger(animations.TriggerOnTrue, func() bool {
Trigger(animations.TriggerOnTrue, animations.PlayForward, func() bool {
return playOnHover && giu.IsItemHovered()
}),
)
},
func(starter func()) {
func(starterFunc animations.StarterFunc) {
giu.Window("window2").Layout(
giu.Label("I'm a window 1"),
giu.Button("start transition").OnClick(func() {
starter()
}),
giu.Label("I'm a window 2"),
animations.Animator(
animations.ColorFlowStyle(
giu.Button("start transition").OnClick(func() {
starterFunc.Start(animations.PlayForward)
}),
giu.StyleColorButton, giu.StyleColorButtonHovered,
),
).Trigger(animations.TriggerOnChange, animations.PlayForward, imgui.IsItemHovered),
)
},
func(starterFunc animations.StarterFunc) {
giu.Window("window 3").Layout(
giu.Label("I'm third window!"),
giu.Row(
giu.Button("<< Previous Window").OnClick(func() {
starterFunc.Start(animations.PlayBackwards)
}),
giu.Button("Next Window >>").OnClick(func() {
starterFunc.Start(animations.PlayForward)
}),
),
)
},
),
Expand Down
15 changes: 11 additions & 4 deletions animation.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package animations

type StarterFunc func(playMode PlayMode)

// Animation is an interface implemented by each animation.
type Animation interface {
// Init is called once, immediately on start.
Init()
// Reset is called whenever needs to restart animation.
Reset()

// KeyFramesCount is used mainly by the AnimatorWidget.
// It returns a number of key frames.
KeyFramesCount() int

// BuildNormal is called every frame when animation is not running
// starter is a link to Animator.Start
BuildNormal(starterFunc StarterFunc)
BuildNormal(currentKeyFrame KeyFrame, starterFunc StarterFunc)
// BuildAnimation is called when running an animation.
// It receives two values:
// - first one is animationPercentage after applying specified by Animator
Expand All @@ -25,5 +27,10 @@ type Animation interface {
// does not want this. Exceptions, I see for now may be:
// - your animation does not accept negative (or larger than 1) progress values
// starter is a link to (*Animator).Start() method.
BuildAnimation(animationPercentage, arbitraryPercentage float32, starterFunc StarterFunc)
BuildAnimation(
animationPercentage, arbitraryPercentage float32,
baseKeyFrame, destinationKeyFrame KeyFrame,
mode PlayMode,
starterFunc StarterFunc,
)
}
Loading