Skip to content

Commit bbef60e

Browse files
committed
feat(transition | tui): add expand transition animation
- add `expand.go` with center-outward expanding transition effect - register expand transition in transition factory function - implement spring-based animation with harmonica library
1 parent bcdd43f commit bbef60e

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

internal/tui/transitions/expand.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package transitions
2+
3+
import (
4+
"math"
5+
"strings"
6+
"time"
7+
8+
tea "github.com/charmbracelet/bubbletea"
9+
"github.com/charmbracelet/harmonica"
10+
charmansi "github.com/charmbracelet/x/ansi"
11+
"github.com/muesli/reflow/truncate"
12+
13+
"github.com/museslabs/kyma/internal/skip"
14+
)
15+
16+
type expand struct {
17+
width int
18+
fps int
19+
spring harmonica.Spring
20+
progress float64
21+
vel float64
22+
animating bool
23+
direction direction
24+
}
25+
26+
func newExpand(fps int) expand {
27+
const frequency = 7.0
28+
const damping = 0.6
29+
30+
return expand{
31+
fps: fps,
32+
spring: harmonica.NewSpring(harmonica.FPS(fps), frequency, damping),
33+
}
34+
}
35+
36+
func (t expand) Start(width, _ int, direction direction) Transition {
37+
t.width = width
38+
t.animating = true
39+
t.progress = 0
40+
t.vel = 0
41+
t.direction = direction
42+
return t
43+
}
44+
45+
func (t expand) Animating() bool {
46+
return t.animating
47+
}
48+
49+
func (t expand) Update() (Transition, tea.Cmd) {
50+
targetProgress := 1.0
51+
52+
t.progress, t.vel = t.spring.Update(t.progress, t.vel, targetProgress)
53+
54+
if t.progress >= 0.99 {
55+
t.animating = false
56+
t.progress = 1.0
57+
return t, nil
58+
}
59+
60+
return t, Animate(time.Duration(t.fps))
61+
}
62+
63+
func (t expand) View(prev, next string) string {
64+
var s strings.Builder
65+
66+
// Calculate how much should expand from center outward
67+
expandWidth := int(math.Round(t.progress * float64(t.width) / 2))
68+
centerStart := t.width/2 - expandWidth
69+
centerEnd := t.width/2 + expandWidth
70+
71+
prevLines := strings.Split(prev, "\n")
72+
nextLines := strings.Split(next, "\n")
73+
74+
// Ensure slides are equal height
75+
maxLines := max(len(nextLines), len(prevLines))
76+
77+
for i := range maxLines {
78+
var prevLine, nextLine string
79+
80+
if i < len(prevLines) {
81+
prevLine = prevLines[i]
82+
}
83+
if i < len(nextLines) {
84+
nextLine = nextLines[i]
85+
}
86+
87+
var line string
88+
if expandWidth >= t.width/2 {
89+
// Animation complete, show next content
90+
line = truncate.String(nextLine, uint(t.width))
91+
} else {
92+
// Build the line with expand effect from center outward
93+
// Left and right parts: show prev content
94+
leftPrev := truncate.String(prevLine, uint(centerStart))
95+
96+
rightPrev := ""
97+
if centerEnd < t.width {
98+
rightPrev = charmansi.TruncateLeft(prevLine, centerEnd, "")
99+
}
100+
101+
// Center portion: show next content expanding from center
102+
var center string
103+
if centerEnd > centerStart {
104+
truncatedNext := truncate.String(nextLine, uint(centerEnd))
105+
center = skip.String(truncatedNext, uint(centerStart))
106+
}
107+
108+
line = leftPrev + center + rightPrev
109+
line = truncate.String(line, uint(t.width))
110+
}
111+
112+
s.WriteString(line)
113+
if i < maxLines-1 {
114+
s.WriteString("\n")
115+
}
116+
}
117+
118+
return s.String()
119+
}
120+
121+
func (t expand) Name() string {
122+
return "expand"
123+
}
124+
125+
func (t expand) Opposite() Transition {
126+
return newExpand(t.fps)
127+
}
128+
129+
func (t expand) Direction() direction {
130+
return t.direction
131+
}

internal/tui/transitions/transition.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ func Get(name string, fps int) Transition {
4545
return newSwipeRight(fps)
4646
case "flip":
4747
return newFlipRight(fps)
48+
case "expand":
49+
return newExpand(fps)
4850
default:
4951
return newNoTransition(fps)
5052
}

0 commit comments

Comments
 (0)