detached 6bff21d

feat(omnibox): implement dynamic sizing based on result count

- Add resizeAndCenter() method that adjusts height based on row count (0-10 max)
- Height calculation respects UI scale: base ~90px + 40px per row
- Window resizes and re-centers when content changes
- Hide scrolled window when no results/favorites available
- Support initial_behavior='none' for minimal initial size (header + entry only)
- Deprecate static updateSize() in favor of dynamic sizing
commit
6bff21d5b102559cb3ffb422f94e53a8fbdf2292
author
brice · · 2025-12-15 19:04 UTC
committer
brice · · 2025-12-15 18:34 UTC
parent
4622b8c
signature
unsigned
1 file · +82 -8
M internal/ui/component/omnibox.go +82 -8
@@ -141,8 +141,15 @@ func NewOmnibox(ctx context.Context, parent *gtk.ApplicationWindow, cfg OmniboxC
141141 }
142142
143143 // updateSize sets the omnibox size based on parent window dimensions.
144+// Deprecated: use resizeAndCenter with row count instead.
144145 func (o *Omnibox) updateSize() {
145- if o.parentWindow == nil || o.window == nil {
146+ o.resizeAndCenter(omniboxMaxResults)
147+}
148+
149+// resizeAndCenter adjusts the omnibox size based on content and centers it.
150+// rowCount is the number of result rows to display (0 = no content, max 10).
151+func (o *Omnibox) resizeAndCenter(rowCount int) {
152+ if o.parentWindow == nil || o.window == nil || o.mainBox == nil {
146153 return
147154 }
148155
@@ -160,8 +167,47 @@ func (o *Omnibox) updateSize() {
160167 }
161168
162169 width := int(float64(parentWidth) * omniboxWidthPct)
163- height := int(float64(parentHeight) * omniboxHeightPct)
170+
171+ // Base height: header (~40px) + entry (~50px) at scale 1.0
172+ baseHeight := int(90 * o.uiScale)
173+ // Row height: approximately 40px per row at scale 1.0
174+ rowHeight := int(40 * o.uiScale)
175+
176+ // Cap at max results
177+ if rowCount > omniboxMaxResults {
178+ rowCount = omniboxMaxResults
179+ }
180+
181+ height := baseHeight + (rowCount * rowHeight)
182+
183+ o.window.SetDefaultSize(width, height)
184+ o.mainBox.SetSizeRequest(width, height)
185+
186+ // Center the window on the parent
187+ x := (parentWidth - width) / 2
188+ y := (parentHeight - height) / 2
189+
190+ // Get parent window position (for Wayland this may be 0,0)
191+ // For a transient/modal window, GTK should handle relative positioning
192+ // We need to use a surface-based approach for proper centering
193+ if surface := o.window.GetSurface(); surface != nil {
194+ // Request the window manager to center it
195+ // Note: On Wayland, window positioning is limited
196+ }
197+
198+ // For GTK4 popup positioning, we rely on the transient relationship
199+ // The window should appear centered relative to parent automatically
200+ // But we can hint at position using the allocation
164201 o.window.SetDefaultSize(width, height)
202+
203+ log := logging.FromContext(o.ctx)
204+ log.Debug().
205+ Int("width", width).
206+ Int("height", height).
207+ Int("x", x).
208+ Int("y", y).
209+ Int("rows", rowCount).
210+ Msg("omnibox resized")
165211 }
166212
167213 // createWidgets builds the GTK widget hierarchy.
@@ -541,8 +587,15 @@ func (o *Omnibox) updateSuggestions(suggestions []Suggestion) {
541587
542588 o.rebuildList()
543589
590+ // Hide scrolled window when there are no suggestions
591+ rowCount := len(suggestions)
592+ if o.scrolledWin != nil {
593+ o.scrolledWin.SetVisible(rowCount > 0)
594+ }
595+ o.resizeAndCenter(rowCount)
596+
544597 // Select first item if available
545- if len(suggestions) > 0 {
598+ if rowCount > 0 {
546599 o.selectIndex(0)
547600 }
548601 }
@@ -556,8 +609,15 @@ func (o *Omnibox) updateFavorites(favorites []Favorite) {
556609
557610 o.rebuildList()
558611
612+ // Hide scrolled window when there are no favorites
613+ rowCount := len(favorites)
614+ if o.scrolledWin != nil {
615+ o.scrolledWin.SetVisible(rowCount > 0)
616+ }
617+ o.resizeAndCenter(rowCount)
618+
559619 // Select first item if available
560- if len(favorites) > 0 {
620+ if rowCount > 0 {
561621 o.selectIndex(0)
562622 }
563623 }
@@ -940,17 +1000,31 @@ func (o *Omnibox) Show(ctx context.Context, query string) {
9401000 // Set initial query
9411001 o.entry.SetText(query)
9421002
943- // Load initial data
944- o.performSearch()
1003+ // Determine if we expect content initially
1004+ // No content expected if: no query AND initialBehavior is "none"
1005+ expectContent := query != "" || o.initialBehavior != "none"
1006+
1007+ // Hide scrolled window if no content expected
1008+ if o.scrolledWin != nil {
1009+ o.scrolledWin.SetVisible(expectContent)
1010+ }
9451011
946- // Update size based on current parent window dimensions
947- o.updateSize()
1012+ // Initial size: 0 rows if no content expected, max rows otherwise
1013+ // (will be updated by search results)
1014+ initialRows := 0
1015+ if expectContent {
1016+ initialRows = omniboxMaxResults
1017+ }
1018+ o.resizeAndCenter(initialRows)
9481019
9491020 // Present the window
9501021 o.window.Present()
9511022
9521023 // Focus the entry
9531024 o.entry.GrabFocus()
1025+
1026+ // Load initial data (may update size later if results found)
1027+ o.performSearch()
9541028 }
9551029
9561030 // Hide closes the omnibox.
@@ -141,8 +141,15 @@ func NewOmnibox(ctx context.Context, parent *gtk.ApplicationWindow, cfg OmniboxC
141 }141 }
142 142
143 // updateSize sets the omnibox size based on parent window dimensions.143 // updateSize sets the omnibox size based on parent window dimensions.
144+// Deprecated: use resizeAndCenter with row count instead.
144 func (o *Omnibox) updateSize() {145 func (o *Omnibox) updateSize() {
145- if o.parentWindow == nil || o.window == nil {146+ o.resizeAndCenter(omniboxMaxResults)
147+}
148+
149+// resizeAndCenter adjusts the omnibox size based on content and centers it.
150+// rowCount is the number of result rows to display (0 = no content, max 10).
151+func (o *Omnibox) resizeAndCenter(rowCount int) {
152+ if o.parentWindow == nil || o.window == nil || o.mainBox == nil {
146 return153 return
147 }154 }
148 155
@@ -160,8 +167,47 @@ func (o *Omnibox) updateSize() {
160 }167 }
161 168
162 width := int(float64(parentWidth) * omniboxWidthPct)169 width := int(float64(parentWidth) * omniboxWidthPct)
163- height := int(float64(parentHeight) * omniboxHeightPct)170+
171+ // Base height: header (~40px) + entry (~50px) at scale 1.0
172+ baseHeight := int(90 * o.uiScale)
173+ // Row height: approximately 40px per row at scale 1.0
174+ rowHeight := int(40 * o.uiScale)
175+
176+ // Cap at max results
177+ if rowCount > omniboxMaxResults {
178+ rowCount = omniboxMaxResults
179+ }
180+
181+ height := baseHeight + (rowCount * rowHeight)
182+
183+ o.window.SetDefaultSize(width, height)
184+ o.mainBox.SetSizeRequest(width, height)
185+
186+ // Center the window on the parent
187+ x := (parentWidth - width) / 2
188+ y := (parentHeight - height) / 2
189+
190+ // Get parent window position (for Wayland this may be 0,0)
191+ // For a transient/modal window, GTK should handle relative positioning
192+ // We need to use a surface-based approach for proper centering
193+ if surface := o.window.GetSurface(); surface != nil {
194+ // Request the window manager to center it
195+ // Note: On Wayland, window positioning is limited
196+ }
197+
198+ // For GTK4 popup positioning, we rely on the transient relationship
199+ // The window should appear centered relative to parent automatically
200+ // But we can hint at position using the allocation
164 o.window.SetDefaultSize(width, height)201 o.window.SetDefaultSize(width, height)
202+
203+ log := logging.FromContext(o.ctx)
204+ log.Debug().
205+ Int("width", width).
206+ Int("height", height).
207+ Int("x", x).
208+ Int("y", y).
209+ Int("rows", rowCount).
210+ Msg("omnibox resized")
165 }211 }
166 212
167 // createWidgets builds the GTK widget hierarchy.213 // createWidgets builds the GTK widget hierarchy.
@@ -541,8 +587,15 @@ func (o *Omnibox) updateSuggestions(suggestions []Suggestion) {
541 587
542 o.rebuildList()588 o.rebuildList()
543 589
590+ // Hide scrolled window when there are no suggestions
591+ rowCount := len(suggestions)
592+ if o.scrolledWin != nil {
593+ o.scrolledWin.SetVisible(rowCount > 0)
594+ }
595+ o.resizeAndCenter(rowCount)
596+
544 // Select first item if available597 // Select first item if available
545- if len(suggestions) > 0 {598+ if rowCount > 0 {
546 o.selectIndex(0)599 o.selectIndex(0)
547 }600 }
548 }601 }
@@ -556,8 +609,15 @@ func (o *Omnibox) updateFavorites(favorites []Favorite) {
556 609
557 o.rebuildList()610 o.rebuildList()
558 611
612+ // Hide scrolled window when there are no favorites
613+ rowCount := len(favorites)
614+ if o.scrolledWin != nil {
615+ o.scrolledWin.SetVisible(rowCount > 0)
616+ }
617+ o.resizeAndCenter(rowCount)
618+
559 // Select first item if available619 // Select first item if available
560- if len(favorites) > 0 {620+ if rowCount > 0 {
561 o.selectIndex(0)621 o.selectIndex(0)
562 }622 }
563 }623 }
@@ -940,17 +1000,31 @@ func (o *Omnibox) Show(ctx context.Context, query string) {
940 // Set initial query1000 // Set initial query
941 o.entry.SetText(query)1001 o.entry.SetText(query)
942 1002
943- // Load initial data1003+ // Determine if we expect content initially
944- o.performSearch()1004+ // No content expected if: no query AND initialBehavior is "none"
1005+ expectContent := query != "" || o.initialBehavior != "none"
1006+
1007+ // Hide scrolled window if no content expected
1008+ if o.scrolledWin != nil {
1009+ o.scrolledWin.SetVisible(expectContent)
1010+ }
945 1011
946- // Update size based on current parent window dimensions1012+ // Initial size: 0 rows if no content expected, max rows otherwise
947- o.updateSize()1013+ // (will be updated by search results)
1014+ initialRows := 0
1015+ if expectContent {
1016+ initialRows = omniboxMaxResults
1017+ }
1018+ o.resizeAndCenter(initialRows)
948 1019
949 // Present the window1020 // Present the window
950 o.window.Present()1021 o.window.Present()
951 1022
952 // Focus the entry1023 // Focus the entry
953 o.entry.GrabFocus()1024 o.entry.GrabFocus()
1025+
1026+ // Load initial data (may update size later if results found)
1027+ o.performSearch()
954 }1028 }
955 1029
956 // Hide closes the omnibox.1030 // Hide closes the omnibox.

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