detached 506f1e5

feat(config): add animations and animations.slowdown keys

commit
506f1e553be33b938dea2681d3e2f0d116b48c18
author
brice · · 2026-10-04 15:38 UTC
committer
brice · · 2026-10-04 19:08 UTC
parent
ae51138
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +42 -1
M internal/adapters/config/config.go +14 -0
@@ -169,6 +169,8 @@ func Defaults() ports.Config {
169169 c.Touchpad.ScrollFactor = 1
170170 c.Mouse.AccelProfile = ports.AccelAdaptive
171171 c.Cursor.HideAfter = 5 * time.Second
172+ c.Animations.On = true
173+ c.Animations.Slowdown = 1
172174 c.Focus.Animation = ports.FocusAnimationPulse
173175 c.Focus.Effect = ports.FocusEffectScreen
174176 c.Focus.Strength = 0.04
@@ -749,6 +751,18 @@ func set(c *ports.Config, key, v string) error {
749751 return fmt.Errorf("must be off or a duration between 100ms and %s, e.g. 5s", maxCursorHideAfter)
750752 }
751753 c.Cursor.HideAfter = d
754+ case "animations":
755+ b, err := onOff(v)
756+ if err != nil {
757+ return err
758+ }
759+ c.Animations.On = b
760+ case "animations.slowdown":
761+ f, err := strconv.ParseFloat(v, 64)
762+ if err != nil || !(f >= 0.1 && f <= 10) {
763+ return fmt.Errorf("must be between 0.1 and 10")
764+ }
765+ c.Animations.Slowdown = f
752766 case "focus.follow-move":
753767 b, err := onOff(v)
754768 if err != nil {
@@ -169,6 +169,8 @@ func Defaults() ports.Config {
169 c.Touchpad.ScrollFactor = 1169 c.Touchpad.ScrollFactor = 1
170 c.Mouse.AccelProfile = ports.AccelAdaptive170 c.Mouse.AccelProfile = ports.AccelAdaptive
171 c.Cursor.HideAfter = 5 * time.Second171 c.Cursor.HideAfter = 5 * time.Second
172+ c.Animations.On = true
173+ c.Animations.Slowdown = 1
172 c.Focus.Animation = ports.FocusAnimationPulse174 c.Focus.Animation = ports.FocusAnimationPulse
173 c.Focus.Effect = ports.FocusEffectScreen175 c.Focus.Effect = ports.FocusEffectScreen
174 c.Focus.Strength = 0.04176 c.Focus.Strength = 0.04
@@ -749,6 +751,18 @@ func set(c *ports.Config, key, v string) error {
749 return fmt.Errorf("must be off or a duration between 100ms and %s, e.g. 5s", maxCursorHideAfter)751 return fmt.Errorf("must be off or a duration between 100ms and %s, e.g. 5s", maxCursorHideAfter)
750 }752 }
751 c.Cursor.HideAfter = d753 c.Cursor.HideAfter = d
754+ case "animations":
755+ b, err := onOff(v)
756+ if err != nil {
757+ return err
758+ }
759+ c.Animations.On = b
760+ case "animations.slowdown":
761+ f, err := strconv.ParseFloat(v, 64)
762+ if err != nil || !(f >= 0.1 && f <= 10) {
763+ return fmt.Errorf("must be between 0.1 and 10")
764+ }
765+ c.Animations.Slowdown = f
752 case "focus.follow-move":766 case "focus.follow-move":
753 b, err := onOff(v)767 b, err := onOff(v)
754 if err != nil {768 if err != nil {
M internal/adapters/config/config_test.go +21 -0
@@ -78,6 +78,27 @@ func TestFollowMove(t *testing.T) {
7878 }
7979 }
8080
81+func TestAnimations(t *testing.T) {
82+ d := Defaults().Animations
83+ if !d.On || d.Slowdown != 1 {
84+ t.Fatal("animations defaults", d)
85+ }
86+ c, w := parseString(t, "animations = off\nanimations.slowdown = 2\n")
87+ if c.Animations.On || c.Animations.Slowdown != 2 || len(w) != 0 {
88+ t.Fatal(c.Animations, w)
89+ }
90+ for _, line := range []string{"animations = maybe", "animations.slowdown = 0", "animations.slowdown = 11"} {
91+ c, w = parseString(t, line+"\n")
92+ if c.Animations != d || len(w) != 1 {
93+ t.Fatal(line, c.Animations, w)
94+ }
95+ }
96+ _, w = parseString(t, "animations.slowdown = 11\n")
97+ if len(w) != 1 || !strings.Contains(w[0].Msg, "must be between 0.1 and 10") {
98+ t.Fatal(w)
99+ }
100+}
101+
81102 func TestFocusIndicator(t *testing.T) {
82103 d := Defaults().Focus
83104 if d.Animation != ports.FocusAnimationPulse || d.Effect != ports.FocusEffectScreen || d.Strength != 0.04 {
@@ -78,6 +78,27 @@ func TestFollowMove(t *testing.T) {
78 }78 }
79 }79 }
80 80
81+func TestAnimations(t *testing.T) {
82+ d := Defaults().Animations
83+ if !d.On || d.Slowdown != 1 {
84+ t.Fatal("animations defaults", d)
85+ }
86+ c, w := parseString(t, "animations = off\nanimations.slowdown = 2\n")
87+ if c.Animations.On || c.Animations.Slowdown != 2 || len(w) != 0 {
88+ t.Fatal(c.Animations, w)
89+ }
90+ for _, line := range []string{"animations = maybe", "animations.slowdown = 0", "animations.slowdown = 11"} {
91+ c, w = parseString(t, line+"\n")
92+ if c.Animations != d || len(w) != 1 {
93+ t.Fatal(line, c.Animations, w)
94+ }
95+ }
96+ _, w = parseString(t, "animations.slowdown = 11\n")
97+ if len(w) != 1 || !strings.Contains(w[0].Msg, "must be between 0.1 and 10") {
98+ t.Fatal(w)
99+ }
100+}
101+
81 func TestFocusIndicator(t *testing.T) {102 func TestFocusIndicator(t *testing.T) {
82 d := Defaults().Focus103 d := Defaults().Focus
83 if d.Animation != ports.FocusAnimationPulse || d.Effect != ports.FocusEffectScreen || d.Strength != 0.04 {104 if d.Animation != ports.FocusAnimationPulse || d.Effect != ports.FocusEffectScreen || d.Strength != 0.04 {
M internal/ports/config.go +7 -1
@@ -143,7 +143,13 @@ type Config struct {
143143 // Cursor.HideAfter hides the pointer cursor after this long without
144144 // motion; the next motion shows it. 0 never hides it.
145145 Cursor struct{ HideAfter time.Duration }
146- Focus struct {
146+ // Animations: On enables motion (off lands everything instantly);
147+ // Slowdown scales every duration, 0.1 to 10.
148+ Animations struct {
149+ On bool
150+ Slowdown float64
151+ }
152+ Focus struct {
147153 // FollowMove shows the target workspace after a column or window
148154 // moves to it.
149155 FollowMove bool
@@ -143,7 +143,13 @@ type Config struct {
143 // Cursor.HideAfter hides the pointer cursor after this long without143 // Cursor.HideAfter hides the pointer cursor after this long without
144 // motion; the next motion shows it. 0 never hides it.144 // motion; the next motion shows it. 0 never hides it.
145 Cursor struct{ HideAfter time.Duration }145 Cursor struct{ HideAfter time.Duration }
146- Focus struct {146+ // Animations: On enables motion (off lands everything instantly);
147+ // Slowdown scales every duration, 0.1 to 10.
148+ Animations struct {
149+ On bool
150+ Slowdown float64
151+ }
152+ Focus struct {
147 // FollowMove shows the target workspace after a column or window153 // FollowMove shows the target workspace after a column or window
148 // moves to it.154 // moves to it.
149 FollowMove bool155 FollowMove bool

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 23

Tags 10

↑↓ moveenter openesc close