Skip to content

Commit

Permalink
Show visual explosion effect when nuking worktree (#2861)
Browse files Browse the repository at this point in the history
  • Loading branch information
jesseduffield authored Aug 1, 2023
2 parents 53f0c1e + f43fd7a commit ec5bc93
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 6 deletions.
5 changes: 3 additions & 2 deletions demo/record_demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ mkdir -p demo/output

terminalizer -c demo/config.yml record --skip-sharing -d "go run cmd/integration_test/main.go cli --slow $TEST" "demo/output/$NAME"
terminalizer render "demo/output/$NAME" -o "demo/output/$NAME.gif"
gifsicle --colors 256 --use-col=web -O3 < "demo/output/$NAME.gif" > "demo/output/$NAME-compressed.gif"
COMPRESSED_PATH="demo/output/$NAME-compressed.gif"
gifsicle --colors 256 --use-col=web -O3 < "demo/output/$NAME.gif" > "$COMPRESSED_PATH"

echo "Demo recorded to demo/$NAME-compressed.gif"
echo "Demo recorded to $COMPRESSED_PATH"
1 change: 1 addition & 0 deletions docs/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ gui:
splitDiff: 'auto' # one of 'auto' | 'always'
skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
border: 'single' # one of 'single' | 'double' | 'rounded' | 'hidden'
animateExplosion: true # shows an explosion animation when nuking the working tree
git:
paging:
colorArg: always
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/user_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type GuiConfig struct {
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
WindowSize string `yaml:"windowSize"`
Border string `yaml:"border"`
AnimateExplosion bool `yaml:"animateExplosion"`
}

type ThemeConfig struct {
Expand Down Expand Up @@ -454,6 +455,7 @@ func GetDefaultConfig() *UserConfig {
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
Border: "single",
AnimateExplosion: true,
},
Git: GitConfig{
Paging: PagingConfig{
Expand Down
108 changes: 108 additions & 0 deletions pkg/gui/controllers/workspace_reset_controller.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package controllers

import (
"bytes"
"fmt"
"math"
"math/rand"
"time"

"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
Expand All @@ -29,6 +34,10 @@ func (self *FilesController) createResetMenu() error {
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}})
},
Key: 'x',
Expand Down Expand Up @@ -135,3 +144,102 @@ func (self *FilesController) createResetMenu() error {

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()
}
2 changes: 1 addition & 1 deletion pkg/gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ func initialWindowViewNameMap(contextTree *context.ContextTree) *utils.ThreadSaf

func initialScreenMode(startArgs appTypes.StartArgs, config config.AppConfigurer) types.WindowMaximisation {
if startArgs.FilterPath != "" || startArgs.GitArg != appTypes.GitArgNone {
return types.SCREEN_HALF
return types.SCREEN_FULL
} else {
defaultWindowSize := config.GetUserConfig().Gui.WindowSize

Expand Down
1 change: 0 additions & 1 deletion pkg/integration/components/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ func (self *IntegrationTest) Run(gui integrationTypes.GuiDriver) {
// Setting caption to clear the options menu from whatever it starts with
testDriver.SetCaption("")
testDriver.SetCaptionPrefix("")
testDriver.Wait(1000)
}

self.run(testDriver, keys)
Expand Down
2 changes: 1 addition & 1 deletion pkg/integration/tests/demo/bisect.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var Bisect = NewIntegrationTest(NewIntegrationTestArgs{
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.SetCaptionPrefix("Git bisect")
t.Wait(1000)

markCommitAsBad := func() {
t.Views().Commits().
Expand All @@ -45,7 +46,6 @@ var Bisect = NewIntegrationTest(NewIntegrationTestArgs{

t.Views().Commits().
IsFocused().
Press(keys.Universal.NextScreenMode).
Tap(func() {
markCommitAsBad()

Expand Down
1 change: 1 addition & 0 deletions pkg/integration/tests/demo/cherry_pick.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var CherryPick = NewIntegrationTest(NewIntegrationTestArgs{
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.SetCaptionPrefix("Cherry pick commits from another branch")
t.Wait(1000)

t.Views().Branches().
Focus().
Expand Down
1 change: 1 addition & 0 deletions pkg/integration/tests/demo/commit_and_push.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var CommitAndPush = NewIntegrationTest(NewIntegrationTestArgs{
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.SetCaptionPrefix("Stage a file")
t.Wait(1000)

t.Views().Files().
IsFocused().
Expand Down
2 changes: 1 addition & 1 deletion pkg/integration/tests/demo/interactive_rebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ var InteractiveRebase = NewIntegrationTest(NewIntegrationTestArgs{
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.SetCaptionPrefix("Interactive rebase")
t.Wait(1000)

t.Views().Commits().
IsFocused().
Press(keys.Universal.NextScreenMode).
NavigateToLine(Contains("Add TypeScript types to User module")).
Press(keys.Universal.Edit).
SelectPreviousItem().
Expand Down
46 changes: 46 additions & 0 deletions pkg/integration/tests/demo/nuke_working_tree.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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.Wait(1000)

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()
})
},
})
1 change: 1 addition & 0 deletions pkg/integration/tests/test_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ var tests = []*components.IntegrationTest{
demo.CherryPick,
demo.CommitAndPush,
demo.InteractiveRebase,
demo.NukeWorkingTree,
diff.Diff,
diff.DiffAndApplyPatch,
diff.DiffCommits,
Expand Down
1 change: 1 addition & 0 deletions test/default_test_config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ gui:
- reverse
# Not important in tests but it creates clutter in demos
showRandomTip: false
animateExplosion: false # takes too long
git:
# 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
Expand Down

0 comments on commit ec5bc93

Please sign in to comment.