add mascot
This commit is contained in:
328
difflet.go
Normal file
328
difflet.go
Normal file
@@ -0,0 +1,328 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
const (
|
||||
diffletWidth = 9
|
||||
diffletHeight = 4
|
||||
diffletGap = 2
|
||||
diffletMinimumHeaderWidth = 10
|
||||
diffletFallbackRightPadding = 1
|
||||
)
|
||||
|
||||
type DiffletExpression string
|
||||
|
||||
const (
|
||||
DiffletIdle DiffletExpression = "idle"
|
||||
DiffletFocused DiffletExpression = "focused"
|
||||
DiffletHappy DiffletExpression = "happy"
|
||||
DiffletApproved DiffletExpression = "approved"
|
||||
DiffletSurprised DiffletExpression = "surprised"
|
||||
DiffletConcerned DiffletExpression = "concerned"
|
||||
DiffletConfused DiffletExpression = "confused"
|
||||
DiffletSad DiffletExpression = "sad"
|
||||
DiffletAnnoyed DiffletExpression = "annoyed"
|
||||
DiffletError DiffletExpression = "error"
|
||||
DiffletSleeping DiffletExpression = "sleeping"
|
||||
DiffletCurious DiffletExpression = "curious"
|
||||
)
|
||||
|
||||
var diffletFaces = map[DiffletExpression]string{
|
||||
DiffletIdle: "•_•",
|
||||
DiffletFocused: "-_-",
|
||||
DiffletHappy: "^_^",
|
||||
DiffletApproved: "^o^",
|
||||
DiffletSurprised: "•o•",
|
||||
DiffletConcerned: "•^•",
|
||||
DiffletConfused: "•~•",
|
||||
DiffletSad: ";_;",
|
||||
DiffletAnnoyed: ">_<",
|
||||
DiffletError: "x_x",
|
||||
DiffletSleeping: "u_u",
|
||||
DiffletCurious: "o_o",
|
||||
}
|
||||
|
||||
type DiffletFrame struct {
|
||||
Expression DiffletExpression
|
||||
Feet string
|
||||
}
|
||||
|
||||
func (frame DiffletFrame) lines() []string {
|
||||
feet := frame.Feet
|
||||
if feet == "" {
|
||||
feet = " ▝ ▘"
|
||||
}
|
||||
return []string{
|
||||
"▄███████▄",
|
||||
"█+ " + diffletFaces[frame.Expression] + " -█",
|
||||
"▀███████▀",
|
||||
feet + strings.Repeat(" ", max(0, diffletWidth-lipgloss.Width(feet))),
|
||||
}
|
||||
}
|
||||
|
||||
func (frame DiffletFrame) render() []string {
|
||||
lines := frame.lines()
|
||||
lines[1] = "█" + okStyle.Render("+") + " " + diffletFaces[frame.Expression] +
|
||||
" " + badStyle.Render("-") + "█"
|
||||
return lines
|
||||
}
|
||||
|
||||
type diffletAnimation struct {
|
||||
frames []DiffletFrame
|
||||
interval time.Duration
|
||||
loop bool
|
||||
settle DiffletExpression
|
||||
}
|
||||
|
||||
type diffletState int
|
||||
|
||||
const (
|
||||
diffletIdle diffletState = iota
|
||||
diffletLoading
|
||||
diffletFocused
|
||||
diffletConcerned
|
||||
diffletHappy
|
||||
diffletApproved
|
||||
diffletSleeping
|
||||
diffletNewComment
|
||||
diffletSuccess
|
||||
diffletRecoverableError
|
||||
diffletFatalError
|
||||
diffletSad
|
||||
)
|
||||
|
||||
type diffletTickMsg struct {
|
||||
generation uint64
|
||||
}
|
||||
|
||||
type diffletModel struct {
|
||||
enabled bool
|
||||
visible bool
|
||||
expressive bool
|
||||
animated bool
|
||||
state diffletState
|
||||
expression DiffletExpression
|
||||
animation diffletAnimation
|
||||
frame int
|
||||
generation uint64
|
||||
blinking bool
|
||||
blinkCycle uint64
|
||||
}
|
||||
|
||||
func newDifflet(enabled, expressive, animated bool) diffletModel {
|
||||
model := diffletModel{
|
||||
enabled: enabled, visible: true, expressive: expressive, animated: animated,
|
||||
state: diffletLoading, expression: DiffletIdle,
|
||||
}
|
||||
_ = model.setState(diffletLoading)
|
||||
return model
|
||||
}
|
||||
|
||||
func (d *diffletModel) start() tea.Cmd {
|
||||
if !d.enabled || !d.visible || !d.animated {
|
||||
return nil
|
||||
}
|
||||
if d.state == diffletIdle {
|
||||
return d.tick(d.blinkDelay())
|
||||
}
|
||||
if len(d.animation.frames) > 0 {
|
||||
return d.tick(d.animation.interval)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *diffletModel) setState(state diffletState) tea.Cmd {
|
||||
d.state = state
|
||||
d.generation++
|
||||
d.frame = 0
|
||||
d.blinking = false
|
||||
d.animation = diffletAnimation{}
|
||||
d.expression = d.staticExpression(state)
|
||||
if !d.enabled || !d.visible || !d.animated {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch state {
|
||||
case diffletIdle:
|
||||
return d.tick(d.blinkDelay())
|
||||
case diffletLoading:
|
||||
d.animation = diffletAnimation{
|
||||
frames: []DiffletFrame{
|
||||
{Expression: DiffletIdle, Feet: " ▝ ▘"},
|
||||
{Expression: DiffletIdle, Feet: " ▝ ▘"},
|
||||
{Expression: DiffletIdle, Feet: " ▝ ▘"},
|
||||
{Expression: DiffletIdle, Feet: " ▝ ▘"},
|
||||
},
|
||||
interval: 180 * time.Millisecond,
|
||||
loop: true,
|
||||
settle: DiffletIdle,
|
||||
}
|
||||
case diffletNewComment:
|
||||
d.animation = diffletAnimation{
|
||||
frames: []DiffletFrame{
|
||||
{Expression: DiffletIdle},
|
||||
{Expression: DiffletSurprised},
|
||||
{Expression: DiffletConcerned},
|
||||
},
|
||||
interval: 200 * time.Millisecond,
|
||||
settle: DiffletConcerned,
|
||||
}
|
||||
case diffletSuccess:
|
||||
d.animation = diffletAnimation{
|
||||
frames: []DiffletFrame{
|
||||
{Expression: DiffletIdle},
|
||||
{Expression: DiffletHappy},
|
||||
{Expression: DiffletApproved},
|
||||
{Expression: DiffletHappy},
|
||||
},
|
||||
interval: 175 * time.Millisecond,
|
||||
settle: DiffletHappy,
|
||||
}
|
||||
case diffletRecoverableError:
|
||||
if d.expressive {
|
||||
d.animation = diffletAnimation{
|
||||
frames: []DiffletFrame{
|
||||
{Expression: DiffletIdle},
|
||||
{Expression: DiffletAnnoyed},
|
||||
{Expression: DiffletSad},
|
||||
},
|
||||
interval: 220 * time.Millisecond,
|
||||
settle: DiffletSad,
|
||||
}
|
||||
}
|
||||
case diffletFatalError:
|
||||
if d.expressive {
|
||||
d.animation = diffletAnimation{
|
||||
frames: []DiffletFrame{
|
||||
{Expression: DiffletIdle},
|
||||
{Expression: DiffletAnnoyed},
|
||||
{Expression: DiffletError},
|
||||
},
|
||||
interval: 220 * time.Millisecond,
|
||||
settle: DiffletError,
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(d.animation.frames) == 0 {
|
||||
return nil
|
||||
}
|
||||
d.expression = d.animation.frames[0].Expression
|
||||
return d.tick(d.animation.interval)
|
||||
}
|
||||
|
||||
func (d *diffletModel) setVisible(visible bool) tea.Cmd {
|
||||
if d.visible == visible {
|
||||
return nil
|
||||
}
|
||||
d.visible = visible
|
||||
if !visible {
|
||||
d.generation++
|
||||
d.animation = diffletAnimation{}
|
||||
d.blinking = false
|
||||
return nil
|
||||
}
|
||||
return d.setState(d.state)
|
||||
}
|
||||
|
||||
func (d *diffletModel) staticExpression(state diffletState) DiffletExpression {
|
||||
switch state {
|
||||
case diffletFocused:
|
||||
return DiffletFocused
|
||||
case diffletConcerned, diffletNewComment:
|
||||
return DiffletConcerned
|
||||
case diffletHappy, diffletSuccess:
|
||||
return DiffletHappy
|
||||
case diffletApproved:
|
||||
return DiffletApproved
|
||||
case diffletSleeping:
|
||||
return DiffletSleeping
|
||||
case diffletRecoverableError:
|
||||
if d.expressive {
|
||||
return DiffletAnnoyed
|
||||
}
|
||||
return DiffletConcerned
|
||||
case diffletFatalError:
|
||||
return DiffletError
|
||||
case diffletSad:
|
||||
if d.expressive {
|
||||
return DiffletSad
|
||||
}
|
||||
return DiffletConcerned
|
||||
default:
|
||||
return DiffletIdle
|
||||
}
|
||||
}
|
||||
|
||||
func (d *diffletModel) update(msg diffletTickMsg) tea.Cmd {
|
||||
if !d.enabled || !d.visible || !d.animated || msg.generation != d.generation {
|
||||
return nil
|
||||
}
|
||||
if d.state == diffletIdle {
|
||||
if !d.blinking {
|
||||
d.blinking = true
|
||||
d.expression = DiffletFocused
|
||||
return d.tick(125 * time.Millisecond)
|
||||
}
|
||||
d.blinking = false
|
||||
d.expression = DiffletIdle
|
||||
d.blinkCycle++
|
||||
return d.tick(d.blinkDelay())
|
||||
}
|
||||
if len(d.animation.frames) == 0 {
|
||||
return nil
|
||||
}
|
||||
next := d.frame + 1
|
||||
if next >= len(d.animation.frames) {
|
||||
if !d.animation.loop {
|
||||
settle := d.animation.settle
|
||||
d.animation = diffletAnimation{}
|
||||
d.expression = settle
|
||||
return nil
|
||||
}
|
||||
next = 0
|
||||
}
|
||||
d.frame = next
|
||||
d.expression = d.animation.frames[next].Expression
|
||||
return d.tick(d.animation.interval)
|
||||
}
|
||||
|
||||
func (d diffletModel) frameLines() []string {
|
||||
if !d.enabled || !d.visible {
|
||||
return nil
|
||||
}
|
||||
if len(d.animation.frames) > 0 && d.frame < len(d.animation.frames) {
|
||||
frame := d.animation.frames[d.frame]
|
||||
frame.Expression = d.expression
|
||||
return frame.render()
|
||||
}
|
||||
return (DiffletFrame{Expression: d.expression}).render()
|
||||
}
|
||||
|
||||
func diffletHeaderWidth(width int) int {
|
||||
centeredLeft := max(0, (width-diffletWidth)/2)
|
||||
if centeredLeft-diffletGap >= diffletMinimumHeaderWidth {
|
||||
return centeredLeft - diffletGap
|
||||
}
|
||||
rightPadding := 0
|
||||
if width-diffletWidth-diffletGap > 1 {
|
||||
rightPadding = diffletFallbackRightPadding
|
||||
}
|
||||
return max(1, width-diffletWidth-diffletGap-rightPadding)
|
||||
}
|
||||
|
||||
func (d diffletModel) tick(after time.Duration) tea.Cmd {
|
||||
generation := d.generation
|
||||
return tea.Tick(after, func(time.Time) tea.Msg {
|
||||
return diffletTickMsg{generation: generation}
|
||||
})
|
||||
}
|
||||
|
||||
func (d diffletModel) blinkDelay() time.Duration {
|
||||
return 5*time.Second + time.Duration((d.generation+d.blinkCycle*3)%6)*650*time.Millisecond
|
||||
}
|
||||
Reference in New Issue
Block a user