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

Resize animation #20

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1632163
resize animation: implement interface; add some internal stuff
gucio321 May 17, 2023
ad981b4
resize: remove unimplementable logic; add basic implementation of the…
gucio321 May 18, 2023
e6c252c
resize animation: implement cursor adjustment
gucio321 May 18, 2023
cf0832c
resize: remove state system (not needed anymore\!)
gucio321 May 18, 2023
a570e90
example: improve
gucio321 May 18, 2023
3b69480
Merge branch 'master' into resizable-animation
gucio321 May 18, 2023
5e11bf5
example: add easing alg
gucio321 May 18, 2023
0f58b90
fix lint issues
gucio321 May 18, 2023
2634af4
readme: add docs
gucio321 May 18, 2023
65f17bb
Restyled by prettier-markdown
restyled-commits May 18, 2023
bfcfd9b
Merge pull request #21 from gucio321/restyled/resizable-animation
gucio321 May 18, 2023
6fc961f
resize animation: add curosr tricks mechanic (wip)
gucio321 May 20, 2023
2c614ad
Merge branch 'resizable-animation' of github.com:gucio321/giu-animati…
gucio321 May 20, 2023
124e6ea
resize animation: improve cursor trick
gucio321 May 20, 2023
83e4cb2
resize animation: fix cursor tricking after rendering
gucio321 May 21, 2023
2c95157
resize animation: add comments
gucio321 May 21, 2023
87162cd
example: add resize animation testing
gucio321 May 21, 2023
cbef404
Resizing animation: update cursor tricking algorithm
gucio321 May 21, 2023
f6eb526
example: add sameline demo
gucio321 May 21, 2023
2826811
resizable animation: allow giving 0 args
gucio321 Oct 27, 2023
82268c1
Animation: add possibility to manually specify where the trigger effe…
gucio321 Oct 31, 2023
4f374fe
hotfix: add Sameline call
gucio321 Oct 31, 2023
899bba5
Merge branch 'master' into resizable-animation
gucio321 Jan 25, 2024
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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ At the moment, there are three implementations of animations:
- [Color Flow](#color-flow) - you can apply this animation to any widget
You can configure this animation to make your button hover smoothly or change it into a rainbow!
- [Movement](#move) - moves DrawCursor, emulating moving an object (aka `giu.Widget`).
- [Resize](#resize) - resize a widget.

Lets shortly discuss particular types of animations:

Expand Down Expand Up @@ -117,6 +118,23 @@ there are two additional methods of `MoveAnimation`.
- 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)`.

#### Resize

```go
func Resize[T giu.Widget](
w resizable2D[T],
sizes ...imgui.Vec2,
) *ResizeAnimation[T] {...}
```

This will resize `w`.

It is the first animation that requires a [Type Parameter](https://go.dev/tour/generics/1).
It is because first of arguments - a widget `w` needs to implement one extra method -
the `Size(w, h float32)`.

Second argument is a list of sizes to apply as Key Frames.

### Easing

These are some additional ways of controlling the flow of animation:
Expand Down
99 changes: 78 additions & 21 deletions _examples/main.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,51 @@
// Package main contains an example of using giu-animations.
package main

import (
"time"

"golang.org/x/image/colornames"

"github.com/AllenDang/giu"
"github.com/AllenDang/imgui-go"
"golang.org/x/image/colornames"

animations "github.com/gucio321/giu-animations/v2"
)

//nolint:gochecknoglobals // it's an example
var (
easingAlg = animations.EasingAlgNone
playOnHover bool
easingAlg = animations.EasingAlgNone
moveOnHover bool
resizeOnHover bool
resizeFlags = struct {
BeforeX,
BeforeY,
AfterX,
AfterY bool
}{true, true, false, false}
sameline bool
)

//nolint:funlen // it's an example
func loop() {
a := int32(easingAlg)
resizeTrick := animations.TrickNever

if resizeFlags.BeforeX {
resizeTrick |= animations.TrickCursorBeforeX
}

if resizeFlags.BeforeY {
resizeTrick |= animations.TrickCursorBeforeY
}

if resizeFlags.AfterX {
resizeTrick |= animations.TrickCursorAfterX
}

if resizeFlags.AfterY {
resizeTrick |= animations.TrickCursorAfterY
}

animations.Animator(
animations.Transition(
func(starterFunc animations.StarterFunc) {
Expand Down Expand Up @@ -51,25 +79,32 @@ func loop() {
starterFunc.StartCycle(1, animations.PlayForward)
}),
),
giu.Checkbox("Play on hover", &playOnHover),
giu.Checkbox("Move on hover", &moveOnHover),
giu.Checkbox("Resize on hover", &resizeOnHover),
animations.Animator(
animations.Move(func(starter animations.StarterFunc) giu.Widget {
return giu.Child().Layout(
giu.Row(
giu.Label("Set easing alg:"),
giu.SliderInt(&a, 0, int32(animations.EasingAlgMax-1)).Size(100).OnChange(func() {
easingAlg = animations.EasingAlgorithmType(a)
}),
),
giu.Row(
giu.Button("play backwards").OnClick(func() {
starter.Start(animations.PlayBackward)
}),
giu.Button("move me!").OnClick(func() {
starter.Start(animations.PlayForward)
}),
return animations.Animator(animations.Resize[*giu.ChildWidget](
giu.Child().Layout(
giu.Row(
giu.Label("Set easing alg:"),
giu.SliderInt(&a, 0, int32(animations.EasingAlgMax-1)).Size(100).OnChange(func() {
easingAlg = animations.EasingAlgorithmType(a)
}),
),
giu.Row(
giu.Button("play backwards").OnClick(func() {
starter.Start(animations.PlayBackward)
}),
giu.Button("move me!").OnClick(func() {
starter.Start(animations.PlayForward)
}),
),
),
).Size(200, 80)
imgui.Vec2{X: 200, Y: 80},
imgui.Vec2{X: 250, Y: 130},
).TrickCursor(animations.TrickNever)).Trigger(animations.TriggerOnChange, animations.PlayForward, func() bool {
return imgui.IsItemHovered() && resizeOnHover
})
},
animations.Step(20, 100).
Bezier(imgui.Vec2{X: 20, Y: 20}, imgui.Vec2{X: 90}),
Expand All @@ -78,8 +113,30 @@ func loop() {
FPS(120).
EasingAlgorithm(easingAlg).
Trigger(animations.TriggerOnTrue, animations.PlayForward, func() bool {
return playOnHover && giu.IsItemHovered()
return moveOnHover && giu.IsItemHovered()
}),
animations.Animator(animations.Resize[*giu.ButtonWidget](
giu.Button("Resize me!"),
imgui.Vec2{X: 150, Y: 150},
imgui.Vec2{X: 200, Y: 200},
imgui.Vec2{X: 250, Y: 250},
imgui.Vec2{X: 300, Y: 300},
).TrickCursor(resizeTrick)).Trigger(animations.TriggerOnChange, animations.PlayForward, imgui.IsItemHovered).
EasingAlgorithm(animations.EasingAlgOutBounce),

giu.Custom(func() {
if sameline {
imgui.SameLine()
}
}),
giu.TreeNode("Resize flags").Layout(
giu.Checkbox("Before X", &resizeFlags.BeforeX),
giu.Checkbox("Before Y", &resizeFlags.BeforeY),
giu.Checkbox("After X", &resizeFlags.AfterX),
giu.Checkbox("After Y", &resizeFlags.AfterY),
giu.Separator(),
giu.Checkbox("Put me in the same line with resizable button", &sameline),
),
)
},
func(starterFunc animations.StarterFunc) {
Expand Down
4 changes: 3 additions & 1 deletion animation.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package animations

Check failure on line 1 in animation.go

View workflow job for this annotation

GitHub Actions / lint

: # github.com/gucio321/giu-animations/v2 [github.com/gucio321/giu-animations/v2.test]

// Animation is an interface implemented by each animation.
// Every type that implements Animation interface is liable to
Expand All @@ -15,7 +15,9 @@

// BuildNormal is called every frame when animation is not running
// starter is animation link to Animator.Start
BuildNormal(currentKeyFrame KeyFrame, starterFunc StarterFunc)
// triggerCheck should be placed where the animator should check for trigger. If not called - Animator will do that
// after rendering the widget (after calling BuildNormal).
BuildNormal(currentKeyFrame KeyFrame, starterFunc StarterFunc, triggerCheck func())
// BuildAnimation is called when running an animation.
// It receives several important arguments:
// - animationPercentage after applying specified by Animator
Expand Down
11 changes: 9 additions & 2 deletions animator.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,17 @@ func (a *AnimatorWidget) Build() {
return
}

a.animation.BuildNormal(cf, a)
wasCalled := false
triggerValue := false
a.animation.BuildNormal(cf, a, func() {
wasCalled = true
triggerValue = triggerValue || a.triggerFunc()
})

if a.triggerFunc != nil {
triggerValue := a.triggerFunc()
if !wasCalled {
triggerValue = a.triggerFunc()
}

switch a.triggerType {
case TriggerNever:
Expand Down
2 changes: 1 addition & 1 deletion color_flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (c *ColorFlowAnimation) KeyFramesCount() int {
}

// BuildNormal builds animation in normal, not-triggered state.
func (c *ColorFlowAnimation) BuildNormal(currentKeyFrame KeyFrame, _ StarterFunc) {
func (c *ColorFlowAnimation) BuildNormal(currentKeyFrame KeyFrame, _ StarterFunc, _ func()) {
normalColor := c.color[currentKeyFrame]()

c.build(normalColor)
Expand Down
2 changes: 1 addition & 1 deletion move.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (m *MoveAnimation) KeyFramesCount() int {
}

// BuildNormal implements Animation.
func (m *MoveAnimation) BuildNormal(currentKF KeyFrame, starter StarterFunc) {
func (m *MoveAnimation) BuildNormal(currentKF KeyFrame, starter StarterFunc, _ func()) {
imgui.SetCursorPos(m.getPosition(currentKF))

m.widget(starter).Build()
Expand Down
157 changes: 157 additions & 0 deletions resize_animation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package animations

import (
"github.com/AllenDang/giu"
"github.com/AllenDang/imgui-go"
)

// TrickCursor allows to enable special Drawing Cursor behavior.
// If enabled, Drawing Cursor is moved before rendering ResizeAnimation.
// It allows to reach an effect of resizing a UI element from its center.
// NOTE: Be careful as this feature (especially TrickCursorAfterX) may break imgui.Sameline() mechanism.
// NOTE: You can test it in _examples/main.go.
type TrickCursor byte

// These constants work as a bitmask. Feel free to use it like TrickCursorBeforeX | TrickCursorAfterY.
const (
// TrickNever allows to disable TrickCursor.
TrickNever TrickCursor = 1 << iota

// TrickCursorBeforeX allows to move Drawing Cursor before rendering ResizeAnimation on X axis.
TrickCursorBeforeX
// TrickCursorBeforeY allows to move Drawing Cursor before rendering ResizeAnimation on Y axis.
TrickCursorBeforeY
// TrickCursorAfterX allows to move Drawing Cursor after rendering ResizeAnimation on X axis.
// IMPORTANT: Applying this will interfere with imgui.Sameline() mechanism.
TrickCursorAfterX
// TrickCursorAfterY allows to move Drawing Cursor after rendering ResizeAnimation on Y axis.
TrickCursorAfterY

// TrickCursorBefore allows to move Drawing Cursor before rendering ResizeAnimation on both axes.
TrickCursorBefore = TrickCursorBeforeX | TrickCursorBeforeY
// TrickCursorAfter allows to move Drawing Cursor after rendering ResizeAnimation on both axes.
TrickCursorAfter = TrickCursorAfterX | TrickCursorAfterY
// TrickCursorAlways allows to move Drawing Cursor before and after rendering ResizeAnimation on both axes.
TrickCursorAlways = TrickCursorBefore | TrickCursorAfter
)

// ResizeAnimation allows to resize a UI element.
type ResizeAnimation[T giu.Widget] struct {
widget resizable2D[T]
sizes []imgui.Vec2
id string
trickCursor TrickCursor
}

// Resize creates ResizeAnimation.
// It requires Widget type parameter (e.g. *giu.ButtonWidget).
func Resize[T giu.Widget](w resizable2D[T], sizes ...imgui.Vec2) *ResizeAnimation[T] {
return &ResizeAnimation[T]{
id: giu.GenAutoID("giu-animations-ResizeAnimation"),
widget: w,
sizes: sizes,
trickCursor: TrickCursorBeforeY,
}
}

// ID allows to set ID manually.
func (r *ResizeAnimation[T]) ID(id string) {
r.id = id
}

// TrickCursor allows to set TrickCursor.
// Default: TrickCursorBefore.
func (r *ResizeAnimation[T]) TrickCursor(t TrickCursor) *ResizeAnimation[T] {
r.trickCursor = t

return r
}

// Init implements Animation interface.
func (r *ResizeAnimation[T]) Init() {
// noop
}

// Reset implements Animation.
func (r *ResizeAnimation[T]) Reset() {
// noop
}

// KeyFramesCount implements Animation.
func (r *ResizeAnimation[T]) KeyFramesCount() int {
return len(r.sizes)
}

// BuildNormal implements Animation.
func (r *ResizeAnimation[T]) BuildNormal(currentKeyFrame KeyFrame, _ StarterFunc, triggerCheck func()) {
// This may happen if user forgot to pass size vectors. In this case just allow to build unchanged widget.
if int(currentKeyFrame) > len(r.sizes)-1 {
r.widget.Build()

return
}

r.trickCursorBefore(r.sizes[currentKeyFrame], imgui.Vec2{})

r.widget.Size(r.sizes[currentKeyFrame].X, r.sizes[currentKeyFrame].Y).Build()
triggerCheck()

r.trickCursorAfter(r.sizes[currentKeyFrame])
}

// BuildAnimation implements Animation.
func (r *ResizeAnimation[T]) BuildAnimation(
animationPercentage, _ float32,
baseKeyFrame, destinationKeyFrame KeyFrame,
_ PlayMode, _ StarterFunc,
) {
delta := imgui.Vec2{
X: (r.sizes[destinationKeyFrame].X - r.sizes[baseKeyFrame].X) * animationPercentage,
Y: (r.sizes[destinationKeyFrame].Y - r.sizes[baseKeyFrame].Y) * animationPercentage,
}

r.trickCursorBefore(r.sizes[baseKeyFrame], delta)

newSize := imgui.Vec2{
X: r.sizes[baseKeyFrame].X + delta.X,
Y: r.sizes[baseKeyFrame].Y + delta.Y,
}

r.widget.Size(newSize.X, newSize.Y).Build()

r.trickCursorAfter(newSize)
}

func (r *ResizeAnimation[T]) trickCursorBefore(current, delta imgui.Vec2) {
move := imgui.Vec2{}

if r.trickCursor&TrickCursorBeforeX != 0 {
move.X -= (current.X + delta.X - r.sizes[0].X) / 2
}

if r.trickCursor&TrickCursorBeforeY != 0 {
move.Y -= (current.Y + delta.Y - r.sizes[0].Y) / 2
}

imgui.Sameline()

Check failure on line 136 in resize_animation.go

View workflow job for this annotation

GitHub Actions / lint

undefined: imgui.Sameline (typecheck)
imgui.Dummy(move)
}

func (r *ResizeAnimation[T]) trickCursorAfter(currentSize imgui.Vec2) {
move := imgui.Vec2{}

if r.trickCursor&TrickCursorAfterX != 0 {
move.X -= (currentSize.X - r.sizes[0].X) / 2
}

if r.trickCursor&TrickCursorAfterY != 0 {
move.Y -= (currentSize.Y - r.sizes[0].Y) / 2
}

imgui.Dummy(move)
}

type resizable2D[T giu.Widget] interface {
Size(w, h float32) T
giu.Widget
}
2 changes: 1 addition & 1 deletion transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (t *TransitionAnimation) Init() {
}

// BuildNormal implements Animation interface.
func (t *TransitionAnimation) BuildNormal(f KeyFrame, starter StarterFunc) {
func (t *TransitionAnimation) BuildNormal(f KeyFrame, starter StarterFunc, _ func()) {
t.renderers[f](starter)
}

Expand Down
Loading