detached 5df8873

perf(ui): skip legend placement work when the rectangle is unchanged

commit
5df88734c95629aee2fb16e05cd182035ae438a6
author
brice · · 2026-10-14 08:21 UTC
parent
ef74c93
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +22 -18
M internal/ui/mode_toaster_view.go +22 -18
@@ -97,12 +97,17 @@ type gtkModeToasterView struct {
9797
9898 bodyWanted bool
9999 lingering bool
100- // placement is the legend rectangle in overlay coordinates; nil hides it.
101- placement func() (x, y, width, height int, ok bool)
100+ // placement is the legend rectangle in overlay coordinates; it is only
101+ // meaningful while placed is set, which otherwise hides the legend.
102+ placement overlayRect
103+ placed bool
102104 lastColumns int
103105 lastWidth, lastMaxContent int
104106 }
105107
108+// overlayRect is a rectangle in overlay coordinates.
109+type overlayRect struct{ x, y, width, height int }
110+
106111 func newGTKModeToasterView(overlay *gtk.Overlay, window *gtk.ApplicationWindow, events modeToasterEvents) *gtkModeToasterView {
107112 if overlay == nil || window == nil {
108113 return nil
@@ -117,11 +122,11 @@ func newGTKModeToasterView(overlay *gtk.Overlay, window *gtk.ApplicationWindow,
117122 overlay.SetClipOverlay(&v.root.Widget, false)
118123 overlay.SetMeasureOverlay(&v.root.Widget, false)
119124 cb := func(_ gtk.Overlay, widgetPtr uintptr, allocation *uintptr) bool {
120- if v.root == nil || widgetPtr != v.root.GoPointer() || v.placement == nil {
125+ if v.root == nil || widgetPtr != v.root.GoPointer() || !v.placed {
121126 return false
122127 }
123- x, y, w, h, ok := v.placement()
124- return ok && writeOverlayAllocation(allocation, x, y, w, h)
128+ r := v.placement
129+ return writeOverlayAllocation(allocation, r.x, r.y, r.width, r.height)
125130 }
126131 v.positionHandlerID = overlay.ConnectGetChildPosition(&cb)
127132 v.installWindowControllers()
@@ -605,15 +610,16 @@ func (v *gtkModeToasterView) setBodyVisible(visible bool) {
605610
606611 func (v *gtkModeToasterView) hideBody() {
607612 v.stopRefresh()
608- v.placement = nil
613+ v.placed = false
609614 v.root.SetVisible(false)
610615 }
611616
612617 func (v *gtkModeToasterView) lingerRect() (x, y, width, height int, ok bool) {
613- if v.placement == nil {
618+ if !v.placed {
614619 return 0, 0, 0, 0, false
615620 }
616- return v.placement()
621+ r := v.placement
622+ return r.x, r.y, r.width, r.height, true
617623 }
618624
619625 func (v *gtkModeToasterView) scrollLegend(delta float64) bool {
@@ -693,22 +699,20 @@ func (v *gtkModeToasterView) refreshGeometry() {
693699 return
694700 }
695701 y := max(bottom-naturalHeight, 0)
696- previous := v.placement
697- v.placement = func() (int, int, int, int, bool) { return left, y, bandWidth, naturalHeight, true }
698- v.root.SetVisible(true)
699- if previous != nil {
700- if ox, oy, ow, oh, _ := previous(); ox == left && oy == y && ow == bandWidth && oh == naturalHeight {
701- return
702- }
702+ next := overlayRect{x: left, y: y, width: bandWidth, height: naturalHeight}
703+ if v.placed && v.placement == next {
704+ return
703705 }
706+ v.placement, v.placed = next, true
707+ v.root.SetVisible(true)
704708 v.root.QueueAllocate()
705709 }
706710
707711 func (v *gtkModeToasterView) clearPlacement() {
708- if v.placement == nil {
712+ if !v.placed {
709713 return
710714 }
711- v.placement = nil
715+ v.placed = false
712716 v.root.SetVisible(false)
713717 v.root.QueueAllocate()
714718 }
@@ -725,7 +729,7 @@ func (v *gtkModeToasterView) destroy() {
725729 gobject.SignalHandlerDisconnect(gobject.ObjectNewFromInternalPtr(v.overlay.GoPointer()), v.positionHandlerID)
726730 v.positionHandlerID = 0
727731 }
728- v.placement = nil
732+ v.placed = false
729733 v.clearColumns()
730734 v.releaseLegendWidgets()
731735 v.overlay.RemoveOverlay(&v.pill.Widget)
@@ -97,12 +97,17 @@ type gtkModeToasterView struct {
97 97
98 bodyWanted bool98 bodyWanted bool
99 lingering bool99 lingering bool
100- // placement is the legend rectangle in overlay coordinates; nil hides it.100+ // placement is the legend rectangle in overlay coordinates; it is only
101- placement func() (x, y, width, height int, ok bool)101+ // meaningful while placed is set, which otherwise hides the legend.
102+ placement overlayRect
103+ placed bool
102 lastColumns int104 lastColumns int
103 lastWidth, lastMaxContent int105 lastWidth, lastMaxContent int
104 }106 }
105 107
108+// overlayRect is a rectangle in overlay coordinates.
109+type overlayRect struct{ x, y, width, height int }
110+
106 func newGTKModeToasterView(overlay *gtk.Overlay, window *gtk.ApplicationWindow, events modeToasterEvents) *gtkModeToasterView {111 func newGTKModeToasterView(overlay *gtk.Overlay, window *gtk.ApplicationWindow, events modeToasterEvents) *gtkModeToasterView {
107 if overlay == nil || window == nil {112 if overlay == nil || window == nil {
108 return nil113 return nil
@@ -117,11 +122,11 @@ func newGTKModeToasterView(overlay *gtk.Overlay, window *gtk.ApplicationWindow,
117 overlay.SetClipOverlay(&v.root.Widget, false)122 overlay.SetClipOverlay(&v.root.Widget, false)
118 overlay.SetMeasureOverlay(&v.root.Widget, false)123 overlay.SetMeasureOverlay(&v.root.Widget, false)
119 cb := func(_ gtk.Overlay, widgetPtr uintptr, allocation *uintptr) bool {124 cb := func(_ gtk.Overlay, widgetPtr uintptr, allocation *uintptr) bool {
120- if v.root == nil || widgetPtr != v.root.GoPointer() || v.placement == nil {125+ if v.root == nil || widgetPtr != v.root.GoPointer() || !v.placed {
121 return false126 return false
122 }127 }
123- x, y, w, h, ok := v.placement()128+ r := v.placement
124- return ok && writeOverlayAllocation(allocation, x, y, w, h)129+ return writeOverlayAllocation(allocation, r.x, r.y, r.width, r.height)
125 }130 }
126 v.positionHandlerID = overlay.ConnectGetChildPosition(&cb)131 v.positionHandlerID = overlay.ConnectGetChildPosition(&cb)
127 v.installWindowControllers()132 v.installWindowControllers()
@@ -605,15 +610,16 @@ func (v *gtkModeToasterView) setBodyVisible(visible bool) {
605 610
606 func (v *gtkModeToasterView) hideBody() {611 func (v *gtkModeToasterView) hideBody() {
607 v.stopRefresh()612 v.stopRefresh()
608- v.placement = nil613+ v.placed = false
609 v.root.SetVisible(false)614 v.root.SetVisible(false)
610 }615 }
611 616
612 func (v *gtkModeToasterView) lingerRect() (x, y, width, height int, ok bool) {617 func (v *gtkModeToasterView) lingerRect() (x, y, width, height int, ok bool) {
613- if v.placement == nil {618+ if !v.placed {
614 return 0, 0, 0, 0, false619 return 0, 0, 0, 0, false
615 }620 }
616- return v.placement()621+ r := v.placement
622+ return r.x, r.y, r.width, r.height, true
617 }623 }
618 624
619 func (v *gtkModeToasterView) scrollLegend(delta float64) bool {625 func (v *gtkModeToasterView) scrollLegend(delta float64) bool {
@@ -693,22 +699,20 @@ func (v *gtkModeToasterView) refreshGeometry() {
693 return699 return
694 }700 }
695 y := max(bottom-naturalHeight, 0)701 y := max(bottom-naturalHeight, 0)
696- previous := v.placement702+ next := overlayRect{x: left, y: y, width: bandWidth, height: naturalHeight}
697- v.placement = func() (int, int, int, int, bool) { return left, y, bandWidth, naturalHeight, true }703+ if v.placed && v.placement == next {
698- v.root.SetVisible(true)704+ return
699- if previous != nil {
700- if ox, oy, ow, oh, _ := previous(); ox == left && oy == y && ow == bandWidth && oh == naturalHeight {
701- return
702- }
703 }705 }
706+ v.placement, v.placed = next, true
707+ v.root.SetVisible(true)
704 v.root.QueueAllocate()708 v.root.QueueAllocate()
705 }709 }
706 710
707 func (v *gtkModeToasterView) clearPlacement() {711 func (v *gtkModeToasterView) clearPlacement() {
708- if v.placement == nil {712+ if !v.placed {
709 return713 return
710 }714 }
711- v.placement = nil715+ v.placed = false
712 v.root.SetVisible(false)716 v.root.SetVisible(false)
713 v.root.QueueAllocate()717 v.root.QueueAllocate()
714 }718 }
@@ -725,7 +729,7 @@ func (v *gtkModeToasterView) destroy() {
725 gobject.SignalHandlerDisconnect(gobject.ObjectNewFromInternalPtr(v.overlay.GoPointer()), v.positionHandlerID)729 gobject.SignalHandlerDisconnect(gobject.ObjectNewFromInternalPtr(v.overlay.GoPointer()), v.positionHandlerID)
726 v.positionHandlerID = 0730 v.positionHandlerID = 0
727 }731 }
728- v.placement = nil732+ v.placed = false
729 v.clearColumns()733 v.clearColumns()
730 v.releaseLegendWidgets()734 v.releaseLegendWidgets()
731 v.overlay.RemoveOverlay(&v.pill.Widget)735 v.overlay.RemoveOverlay(&v.pill.Widget)

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 8

Tags 42

↑↓ moveenter openesc close