Skip to content

Commit 0ff105e

Browse files
committed
feat(transition | tui): add collapse transition animation
- add new `collapse.go` with collapse transition implementation - implement edge-to-center collapsing animation with spring physics - fix `expand.go` opposite method to return collapse instead of expand - register collapse transition in transition factory
1 parent bbef60e commit 0ff105e

File tree

3 files changed

+134
-1
lines changed

3 files changed

+134
-1
lines changed
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 collapse 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 newCollapse(fps int) collapse {
27+
const frequency = 7.0
28+
const damping = 0.6
29+
30+
return collapse{
31+
fps: fps,
32+
spring: harmonica.NewSpring(harmonica.FPS(fps), frequency, damping),
33+
}
34+
}
35+
36+
func (t collapse) 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 collapse) Animating() bool {
46+
return t.animating
47+
}
48+
49+
func (t collapse) 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 collapse) View(prev, next string) string {
64+
var s strings.Builder
65+
66+
// Calculate how much should collapse from edges toward center
67+
collapseWidth := int(math.Round((1.0 - t.progress) * float64(t.width) / 2))
68+
centerStart := t.width/2 - collapseWidth
69+
centerEnd := t.width/2 + collapseWidth
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 collapseWidth <= 0 {
89+
// Animation complete, show next content
90+
line = truncate.String(nextLine, uint(t.width))
91+
} else {
92+
// Build the line with collapse effect from edges toward center
93+
// Center portion: show prev content that's collapsing
94+
var center string
95+
if centerEnd > centerStart {
96+
truncatedPrev := truncate.String(prevLine, uint(centerEnd))
97+
center = skip.String(truncatedPrev, uint(centerStart))
98+
}
99+
100+
// Left and right parts: show next content appearing from edges
101+
leftNext := truncate.String(nextLine, uint(centerStart))
102+
103+
rightNext := ""
104+
if centerEnd < t.width {
105+
rightNext = charmansi.TruncateLeft(nextLine, centerEnd, "")
106+
}
107+
108+
line = leftNext + center + rightNext
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 collapse) Name() string {
122+
return "collapse"
123+
}
124+
125+
func (t collapse) Opposite() Transition {
126+
return newExpand(t.fps)
127+
}
128+
129+
func (t collapse) Direction() direction {
130+
return t.direction
131+
}

internal/tui/transitions/expand.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func (t expand) Name() string {
123123
}
124124

125125
func (t expand) Opposite() Transition {
126-
return newExpand(t.fps)
126+
return newCollapse(t.fps)
127127
}
128128

129129
func (t expand) Direction() direction {

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 "collapse":
49+
return newCollapse(fps)
4850
case "expand":
4951
return newExpand(fps)
5052
default:

0 commit comments

Comments
 (0)