mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-01-06 03:53:59 +02:00
Add explosion animation when nuking working tree
I've been thinking about this for a while: I think it looks really cool if nuking your working tree actually results in a nuke animation. So I've added an opt-out config for it
This commit is contained in:
parent
37dc31b815
commit
a200fccba9
@ -70,6 +70,7 @@ gui:
|
|||||||
splitDiff: 'auto' # one of 'auto' | 'always'
|
splitDiff: 'auto' # one of 'auto' | 'always'
|
||||||
skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
|
skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
|
||||||
border: 'single' # one of 'single' | 'double' | 'rounded' | 'hidden'
|
border: 'single' # one of 'single' | 'double' | 'rounded' | 'hidden'
|
||||||
|
animateExplosion: true # shows an explosion animation when nuking the working tree
|
||||||
git:
|
git:
|
||||||
paging:
|
paging:
|
||||||
colorArg: always
|
colorArg: always
|
||||||
|
@ -56,6 +56,7 @@ type GuiConfig struct {
|
|||||||
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
|
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
|
||||||
WindowSize string `yaml:"windowSize"`
|
WindowSize string `yaml:"windowSize"`
|
||||||
Border string `yaml:"border"`
|
Border string `yaml:"border"`
|
||||||
|
AnimateExplosion bool `yaml:"animateExplosion"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ThemeConfig struct {
|
type ThemeConfig struct {
|
||||||
@ -454,6 +455,7 @@ func GetDefaultConfig() *UserConfig {
|
|||||||
SplitDiff: "auto",
|
SplitDiff: "auto",
|
||||||
SkipRewordInEditorWarning: false,
|
SkipRewordInEditorWarning: false,
|
||||||
Border: "single",
|
Border: "single",
|
||||||
|
AnimateExplosion: true,
|
||||||
},
|
},
|
||||||
Git: GitConfig{
|
Git: GitConfig{
|
||||||
Paging: PagingConfig{
|
Paging: PagingConfig{
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
@ -29,6 +34,10 @@ func (self *FilesController) createResetMenu() error {
|
|||||||
return self.c.Error(err)
|
return self.c.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.c.UserConfig.Gui.AnimateExplosion {
|
||||||
|
self.animateExplosion()
|
||||||
|
}
|
||||||
|
|
||||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
||||||
},
|
},
|
||||||
Key: 'x',
|
Key: 'x',
|
||||||
@ -135,3 +144,102 @@ func (self *FilesController) createResetMenu() error {
|
|||||||
|
|
||||||
return self.c.Menu(types.CreateMenuOptions{Title: "", Items: menuItems})
|
return self.c.Menu(types.CreateMenuOptions{Title: "", Items: menuItems})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *FilesController) animateExplosion() {
|
||||||
|
self.Explode(self.c.Views().Files, func() {
|
||||||
|
err := self.c.PostRefreshUpdate(self.c.Contexts().Files)
|
||||||
|
if err != nil {
|
||||||
|
self.c.Log.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animates an explosion within the view by drawing a bunch of flamey characters
|
||||||
|
func (self *FilesController) Explode(v *gocui.View, onDone func()) {
|
||||||
|
width := v.InnerWidth()
|
||||||
|
height := v.InnerHeight() + 1
|
||||||
|
styles := []style.TextStyle{
|
||||||
|
style.FgLightWhite.SetBold(),
|
||||||
|
style.FgYellow.SetBold(),
|
||||||
|
style.FgRed.SetBold(),
|
||||||
|
style.FgBlue.SetBold(),
|
||||||
|
style.FgBlack.SetBold(),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.c.OnWorker(func(_ gocui.Task) {
|
||||||
|
max := 25
|
||||||
|
for i := 0; i < max; i++ {
|
||||||
|
image := getExplodeImage(width, height, i, max)
|
||||||
|
style := styles[(i*len(styles)/max)%len(styles)]
|
||||||
|
coloredImage := style.Sprint(image)
|
||||||
|
self.c.OnUIThread(func() error {
|
||||||
|
v.SetContent(coloredImage)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
time.Sleep(time.Millisecond * 20)
|
||||||
|
}
|
||||||
|
self.c.OnUIThread(func() error {
|
||||||
|
v.Clear()
|
||||||
|
onDone()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render an explosion in the given bounds.
|
||||||
|
func getExplodeImage(width int, height int, frame int, max int) string {
|
||||||
|
// Predefine the explosion symbols
|
||||||
|
explosionChars := []rune{'*', '.', '@', '#', '&', '+', '%'}
|
||||||
|
|
||||||
|
// Initialize a buffer to build our string
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Initialize RNG seed
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
|
// calculate the center of explosion
|
||||||
|
centerX, centerY := width/2, height/2
|
||||||
|
|
||||||
|
// calculate the max radius (hypotenuse of the view)
|
||||||
|
maxRadius := math.Hypot(float64(centerX), float64(centerY))
|
||||||
|
|
||||||
|
// calculate frame as a proportion of max, apply square root to create the non-linear effect
|
||||||
|
progress := math.Sqrt(float64(frame) / float64(max))
|
||||||
|
|
||||||
|
// calculate radius of explosion according to frame and max
|
||||||
|
radius := progress * maxRadius * 2
|
||||||
|
|
||||||
|
// introduce a new radius for the inner boundary of the explosion (the shockwave effect)
|
||||||
|
var innerRadius float64
|
||||||
|
if progress > 0.5 {
|
||||||
|
innerRadius = (progress - 0.5) * 2 * maxRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
for y := 0; y < height; y++ {
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
// calculate distance from center, scale x by 2 to compensate for character aspect ratio
|
||||||
|
distance := math.Hypot(float64(x-centerX), float64(y-centerY)*2)
|
||||||
|
|
||||||
|
// if distance is less than radius and greater than innerRadius, draw explosion char
|
||||||
|
if distance <= radius && distance >= innerRadius {
|
||||||
|
// Make placement random and less likely as explosion progresses
|
||||||
|
if rand.Float64() > progress {
|
||||||
|
// Pick a random explosion char
|
||||||
|
char := explosionChars[rand.Intn(len(explosionChars))]
|
||||||
|
buf.WriteRune(char)
|
||||||
|
} else {
|
||||||
|
buf.WriteRune(' ')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If not explosion, then it's empty space
|
||||||
|
buf.WriteRune(' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// End of line
|
||||||
|
if y < height-1 {
|
||||||
|
buf.WriteRune('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
45
pkg/integration/tests/demo/nuke_working_tree.go
Normal file
45
pkg/integration/tests/demo/nuke_working_tree.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package demo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
var NukeWorkingTree = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
|
Description: "Nuke the working tree",
|
||||||
|
ExtraCmdArgs: []string{"status"},
|
||||||
|
Skip: false,
|
||||||
|
IsDemo: true,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {
|
||||||
|
// No idea why I had to use version 2: it should be using my own computer's
|
||||||
|
// font and the one iterm uses is version 3.
|
||||||
|
config.UserConfig.Gui.NerdFontsVersion = "2"
|
||||||
|
config.UserConfig.Gui.AnimateExplosion = true
|
||||||
|
},
|
||||||
|
SetupRepo: func(shell *Shell) {
|
||||||
|
shell.EmptyCommit("blah")
|
||||||
|
shell.CreateFile("controllers/red_controller.rb", "")
|
||||||
|
shell.CreateFile("controllers/green_controller.rb", "")
|
||||||
|
shell.CreateFileAndAdd("controllers/blue_controller.rb", "")
|
||||||
|
shell.CreateFile("controllers/README.md", "")
|
||||||
|
shell.CreateFileAndAdd("views/helpers/list.rb", "")
|
||||||
|
shell.CreateFile("views/helpers/sort.rb", "")
|
||||||
|
shell.CreateFileAndAdd("views/users_view.rb", "")
|
||||||
|
},
|
||||||
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
|
t.SetCaptionPrefix("Nuke the working tree")
|
||||||
|
|
||||||
|
t.Views().Files().
|
||||||
|
IsFocused().
|
||||||
|
Wait(1000).
|
||||||
|
Press(keys.Files.ViewResetOptions).
|
||||||
|
Tap(func() {
|
||||||
|
t.Wait(1000)
|
||||||
|
|
||||||
|
t.ExpectPopup().Menu().
|
||||||
|
Title(Equals("")).
|
||||||
|
Select(Contains("Nuke working tree")).
|
||||||
|
Confirm()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
@ -93,6 +93,7 @@ var tests = []*components.IntegrationTest{
|
|||||||
demo.CherryPick,
|
demo.CherryPick,
|
||||||
demo.CommitAndPush,
|
demo.CommitAndPush,
|
||||||
demo.InteractiveRebase,
|
demo.InteractiveRebase,
|
||||||
|
demo.NukeWorkingTree,
|
||||||
diff.Diff,
|
diff.Diff,
|
||||||
diff.DiffAndApplyPatch,
|
diff.DiffAndApplyPatch,
|
||||||
diff.DiffCommits,
|
diff.DiffCommits,
|
||||||
|
@ -14,6 +14,7 @@ gui:
|
|||||||
- reverse
|
- reverse
|
||||||
# Not important in tests but it creates clutter in demos
|
# Not important in tests but it creates clutter in demos
|
||||||
showRandomTip: false
|
showRandomTip: false
|
||||||
|
animateExplosion: false # takes too long
|
||||||
git:
|
git:
|
||||||
# We don't want to run any periodic background git commands because it'll introduce race conditions and flakiness.
|
# We don't want to run any periodic background git commands because it'll introduce race conditions and flakiness.
|
||||||
# If we need to refresh something from within the test (which should only really happen if we've invoked a
|
# If we need to refresh something from within the test (which should only really happen if we've invoked a
|
||||||
|
Loading…
Reference in New Issue
Block a user