summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2023-08-01 22:31:29 +1000
committerGitHub <noreply@github.com>2023-08-01 22:31:29 +1000
commitec5bc935769fd41dcedf33c6cfb76580009528c7 (patch)
tree96ff0a75b7b8d5575d1e9c681a87c32a6a81a75e
parent53f0c1eb4fea90abe47819b1802dd23719654b29 (diff)
parentf43fd7af79c712126c8d9f6e1933d26de9034c56 (diff)
Show visual explosion effect when nuking worktree (#2861)
-rwxr-xr-xdemo/record_demo.sh5
-rw-r--r--docs/Config.md1
-rw-r--r--pkg/config/user_config.go2
-rw-r--r--pkg/gui/controllers/workspace_reset_controller.go108
-rw-r--r--pkg/gui/gui.go2
-rw-r--r--pkg/integration/components/test.go1
-rw-r--r--pkg/integration/tests/demo/bisect.go2
-rw-r--r--pkg/integration/tests/demo/cherry_pick.go1
-rw-r--r--pkg/integration/tests/demo/commit_and_push.go1
-rw-r--r--pkg/integration/tests/demo/interactive_rebase.go2
-rw-r--r--pkg/integration/tests/demo/nuke_working_tree.go46
-rw-r--r--pkg/integration/tests/test_list.go1
-rw-r--r--test/default_test_config/config.yml1
13 files changed, 167 insertions, 6 deletions
diff --git a/demo/record_demo.sh b/demo/record_demo.sh
index d86e14ca5..0ef379377 100755
--- a/demo/record_demo.sh
+++ b/demo/record_demo.sh
@@ -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"
diff --git a/docs/Config.md b/docs/Config.md
index 447ccae5f..a4987268d 100644
--- a/docs/Config.md
+++ b/docs/Config.md
@@ -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
diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go
index 0426086fc..050ac7099 100644
--- a/pkg/config/user_config.go
+++ b/pkg/config/user_config.go
@@ -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 {
@@ -454,6 +455,7 @@ func GetDefaultConfig() *UserConfig {
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
Border: "single",
+ AnimateExplosion: true,
},
Git: GitConfig{
Paging: PagingConfig{
diff --git a/pkg/gui/controllers/workspace_reset_controller.go b/pkg/gui/controllers/workspace_reset_controller.go
index db247ae25..195a9699e 100644
--- a/pkg/gui/controllers/workspace_reset_controller.go
+++ b/pkg/gui/controllers/workspace_reset_controller.go
@@ -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"
)
@@ -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',
@@ -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()
+}
diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go
index f099db67e..f1e20da41 100644
--- a/pkg/gui/gui.go
+++ b/pkg/gui/gui.go
@@ -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
diff --git a/pkg/integration/components/test.go b/pkg/integration/components/test.go
index d24e20bd5..130ce8774 100644
--- a/pkg/integration/components/test.go
+++ b/pkg/integration/components/test.go
@@ -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)
diff --git a/pkg/integration/tests/demo/bisect.go b/pkg/integration/tests/demo/bisect.go
index d6191b6ea..b001708ea 100644
--- a/pkg/integration/tests/demo/bisect.go
+++ b/pkg/integration/tests/demo/bisect.go
@@ -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().
@@ -45,7 +46,6 @@ var Bisect = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Commits().
IsFocused().
- Press(keys.Universal.NextScreenMode).
Tap(func() {
markCommitAsBad()
diff --git a/pkg/integration/tests/demo/cherry_pick.go b/pkg/integration/tests/demo/cherry_pick.go
index 5732a5b95..0dd34c8b9 100644
--- a/pkg/integration/tests/demo/cherry_pick.go
+++ b/pkg/integration/tests/demo/cherry_pick.go
@@ -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().
diff --git a/pkg/integration/tests/demo/commit_and_push.go b/pkg/integration/tests/demo/commit_and_push.go
index e897413e9..a0e196cf0 100644
--- a/pkg/integration/tests/demo/commit_and_push.go
+++ b/pkg/integration/tests/demo/commit_and_push.go
@@ -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().
diff --git a/pkg/integration/tests/demo/interactive_rebase.go b/pkg/integration/tests/demo/interactive_rebase.go
index ca400a342..fd19e3657 100644
--- a/pkg/integration/tests/demo/interactive_rebase.go
+++ b/pkg/integration/tests/demo/interactive_rebase.go
@@ -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().
diff --git a/pkg/integration/tests/demo/nuke_working_tree.go b/pkg/integration/tests/demo/nuke_working_tree.go
new file mode 100644
index 000000000..dba670c39
--- /dev/null
+++ b/pkg/integration/tests/demo/nuke_working_tree.go
@@ -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()
+ })
+ },
+})
diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go
index 4d19c5d84..27cb3513e 100644
--- a/pkg/integration/tests/test_list.go
+++ b/pkg/integration/tests/test_list.go
@@ -93,6 +93,7 @@ var tests = []*components.IntegrationTest{
demo.CherryPick,
demo.CommitAndPush,
demo.InteractiveRebase,
+ demo.NukeWorkingTree,
diff.Diff,
diff.DiffAndApplyPatch,
diff.DiffCommits,
diff --git a/test/default_test_config/config.yml b/test/default_test_config/config.yml
index 04f7422cb..4f481f0bc 100644
--- a/test/default_test_config/config.yml
+++ b/test/default_test_config/config.yml
@@ -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