fix(omnibox): center in-pane omnibox and show 5 of 10 scrollable results
- commit
- c89d89021f9a83e572d66fffcfd322575b8c84fe
- author
- brice · · 2026-10-10 00:58 UTC
- parent
- e7ebbf7
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +67 -20
M internal/ui/component/modal_sizing.go +5 -4
| @@ -43,19 +43,20 @@ var DefaultRowHeights = RowHeightDefaults{ | |||
| 43 | 43 | } | |
| 44 | 44 | ||
| 45 | 45 | // OmniboxSizeDefaults provides default sizing for omnibox modal. | |
| 46 | + | // The in-pane omnibox is vertically centered, so it has no top margin. | |
| 46 | 47 | var OmniboxSizeDefaults = ModalSizeConfig{ | |
| 47 | 48 | WidthPct: 0.8, | |
| 48 | 49 | MaxWidth: 800, | |
| 49 | - | TopMarginPct: 0.2, | |
| 50 | 50 | FallbackWidth: 800, | |
| 51 | 51 | FallbackHeight: 600, | |
| 52 | 52 | } | |
| 53 | 53 | ||
| 54 | - | // OmniboxListDefaults provides display limits for omnibox modal. | |
| 54 | + | // OmniboxListDefaults provides display limits for omnibox modal: | |
| 55 | + | // MaxResults rows are listed and navigable, MaxVisibleRows fit before scrolling. | |
| 55 | 56 | var OmniboxListDefaults = ListDisplayDefaults{ | |
| 56 | - | MaxVisibleRows: 10, | |
| 57 | + | MaxVisibleRows: 5, | |
| 57 | 58 | MaxResults: 10, | |
| 58 | - | SmallMaxVisibleRows: 5, | |
| 59 | + | SmallMaxVisibleRows: 3, | |
| 59 | 60 | } | |
| 60 | 61 | ||
| 61 | 62 | // SessionManagerSizeDefaults provides default sizing for session manager modal. | |
| @@ -43,19 +43,20 @@ var DefaultRowHeights = RowHeightDefaults{ | |||||
| 43 | } | 43 | } | ||
| 44 | 44 | ||||
| 45 | // OmniboxSizeDefaults provides default sizing for omnibox modal. | 45 | // OmniboxSizeDefaults provides default sizing for omnibox modal. | ||
| 46 | + | // The in-pane omnibox is vertically centered, so it has no top margin. | |||
| 46 | var OmniboxSizeDefaults = ModalSizeConfig{ | 47 | var OmniboxSizeDefaults = ModalSizeConfig{ | ||
| 47 | WidthPct: 0.8, | 48 | WidthPct: 0.8, | ||
| 48 | MaxWidth: 800, | 49 | MaxWidth: 800, | ||
| 49 | - | TopMarginPct: 0.2, | |||
| 50 | FallbackWidth: 800, | 50 | FallbackWidth: 800, | ||
| 51 | FallbackHeight: 600, | 51 | FallbackHeight: 600, | ||
| 52 | } | 52 | } | ||
| 53 | 53 | ||||
| 54 | - | // OmniboxListDefaults provides display limits for omnibox modal. | 54 | + | // OmniboxListDefaults provides display limits for omnibox modal: |
| 55 | + | // MaxResults rows are listed and navigable, MaxVisibleRows fit before scrolling. | |||
| 55 | var OmniboxListDefaults = ListDisplayDefaults{ | 56 | var OmniboxListDefaults = ListDisplayDefaults{ | ||
| 56 | - | MaxVisibleRows: 10, | 57 | + | MaxVisibleRows: 5, |
| 57 | MaxResults: 10, | 58 | MaxResults: 10, | ||
| 58 | - | SmallMaxVisibleRows: 5, | 59 | + | SmallMaxVisibleRows: 3, |
| 59 | } | 60 | } | ||
| 60 | 61 | ||||
| 61 | // SessionManagerSizeDefaults provides default sizing for session manager modal. | 62 | // SessionManagerSizeDefaults provides default sizing for session manager modal. | ||
M internal/ui/component/modal_sizing_test.go +15 -3
| @@ -5,6 +5,7 @@ import ( | |||
| 5 | 5 | "testing" | |
| 6 | 6 | ||
| 7 | 7 | layoutmocks "github.com/bnema/dumber/internal/ui/layout/mocks" | |
| 8 | + | "github.com/bnema/puregotk/v4/gtk" | |
| 8 | 9 | "github.com/stretchr/testify/assert" | |
| 9 | 10 | ) | |
| 10 | 11 | ||
| @@ -168,10 +169,21 @@ func TestDefaultRowHeights_Values(t *testing.T) { | |||
| 168 | 169 | func TestOmniboxSizeDefaults_Values(t *testing.T) { | |
| 169 | 170 | assert.InDelta(t, 0.8, OmniboxSizeDefaults.WidthPct, 0.0001) | |
| 170 | 171 | assert.Equal(t, 800, OmniboxSizeDefaults.MaxWidth) | |
| 171 | - | assert.InDelta(t, 0.2, OmniboxSizeDefaults.TopMarginPct, 0.0001) | |
| 172 | + | assert.Zero(t, OmniboxSizeDefaults.TopMarginPct) | |
| 172 | 173 | assert.Equal(t, 600, OmniboxSizeDefaults.FallbackHeight) | |
| 173 | 174 | } | |
| 174 | 175 | ||
| 176 | + | func TestOmniboxValign(t *testing.T) { | |
| 177 | + | assert.Equal(t, gtk.AlignCenterValue, omniboxValign(OmniboxSizeDefaults)) | |
| 178 | + | assert.Equal(t, gtk.AlignStartValue, omniboxValign(ModalSizeConfig{UseFixedTopMargin: true})) | |
| 179 | + | } | |
| 180 | + | ||
| 181 | + | func TestOmniboxListDefaults_ShowFiveScrollTen(t *testing.T) { | |
| 182 | + | assert.Equal(t, 5, OmniboxListDefaults.MaxVisibleRows) | |
| 183 | + | assert.Equal(t, 10, OmniboxListDefaults.MaxResults) | |
| 184 | + | assert.Less(t, OmniboxListDefaults.SmallMaxVisibleRows, OmniboxListDefaults.MaxVisibleRows) | |
| 185 | + | } | |
| 186 | + | ||
| 175 | 187 | func TestSessionManagerSizeDefaults_Values(t *testing.T) { | |
| 176 | 188 | assert.Greater(t, SessionManagerSizeDefaults.WidthPct, 0.0) | |
| 177 | 189 | assert.LessOrEqual(t, SessionManagerSizeDefaults.WidthPct, 1.0) | |
| @@ -325,7 +337,7 @@ func TestCalculateModalDimensionsWithScale_LargeParent(t *testing.T) { | |||
| 325 | 337 | parent := mockParent(t, 3000, 1200) | |
| 326 | 338 | w, m := CalculateModalDimensionsWithScale(parent, cfg, tt.scale) | |
| 327 | 339 | assert.Equal(t, tt.want, w, "scale %v", tt.scale) | |
| 328 | - | assert.Equal(t, 240, m, "top margin unchanged") | |
| 340 | + | assert.Zero(t, m, "omnibox is centered, not top-margined") | |
| 329 | 341 | } | |
| 330 | 342 | } | |
| 331 | 343 | ||
| @@ -351,7 +363,7 @@ func TestCalculateModalDimensionsWithScale_NormalizesBadScale(t *testing.T) { | |||
| 351 | 363 | parent := mockParent(t, 3000, 1080) | |
| 352 | 364 | w, m := CalculateModalDimensionsWithScale(parent, cfg, scale) | |
| 353 | 365 | assert.Equal(t, 800, w, "scale %v", scale) | |
| 354 | - | assert.Equal(t, 216, m, "scale %v", scale) | |
| 366 | + | assert.Zero(t, m, "scale %v", scale) | |
| 355 | 367 | } | |
| 356 | 368 | } | |
| 357 | 369 | ||
| @@ -5,6 +5,7 @@ import ( | |||||
| 5 | "testing" | 5 | "testing" | ||
| 6 | 6 | ||||
| 7 | layoutmocks "github.com/bnema/dumber/internal/ui/layout/mocks" | 7 | layoutmocks "github.com/bnema/dumber/internal/ui/layout/mocks" | ||
| 8 | + | "github.com/bnema/puregotk/v4/gtk" | |||
| 8 | "github.com/stretchr/testify/assert" | 9 | "github.com/stretchr/testify/assert" | ||
| 9 | ) | 10 | ) | ||
| 10 | 11 | ||||
| @@ -168,10 +169,21 @@ func TestDefaultRowHeights_Values(t *testing.T) { | |||||
| 168 | func TestOmniboxSizeDefaults_Values(t *testing.T) { | 169 | func TestOmniboxSizeDefaults_Values(t *testing.T) { | ||
| 169 | assert.InDelta(t, 0.8, OmniboxSizeDefaults.WidthPct, 0.0001) | 170 | assert.InDelta(t, 0.8, OmniboxSizeDefaults.WidthPct, 0.0001) | ||
| 170 | assert.Equal(t, 800, OmniboxSizeDefaults.MaxWidth) | 171 | assert.Equal(t, 800, OmniboxSizeDefaults.MaxWidth) | ||
| 171 | - | assert.InDelta(t, 0.2, OmniboxSizeDefaults.TopMarginPct, 0.0001) | 172 | + | assert.Zero(t, OmniboxSizeDefaults.TopMarginPct) |
| 172 | assert.Equal(t, 600, OmniboxSizeDefaults.FallbackHeight) | 173 | assert.Equal(t, 600, OmniboxSizeDefaults.FallbackHeight) | ||
| 173 | } | 174 | } | ||
| 174 | 175 | ||||
| 176 | + | func TestOmniboxValign(t *testing.T) { | |||
| 177 | + | assert.Equal(t, gtk.AlignCenterValue, omniboxValign(OmniboxSizeDefaults)) | |||
| 178 | + | assert.Equal(t, gtk.AlignStartValue, omniboxValign(ModalSizeConfig{UseFixedTopMargin: true})) | |||
| 179 | + | } | |||
| 180 | + | ||||
| 181 | + | func TestOmniboxListDefaults_ShowFiveScrollTen(t *testing.T) { | |||
| 182 | + | assert.Equal(t, 5, OmniboxListDefaults.MaxVisibleRows) | |||
| 183 | + | assert.Equal(t, 10, OmniboxListDefaults.MaxResults) | |||
| 184 | + | assert.Less(t, OmniboxListDefaults.SmallMaxVisibleRows, OmniboxListDefaults.MaxVisibleRows) | |||
| 185 | + | } | |||
| 186 | + | ||||
| 175 | func TestSessionManagerSizeDefaults_Values(t *testing.T) { | 187 | func TestSessionManagerSizeDefaults_Values(t *testing.T) { | ||
| 176 | assert.Greater(t, SessionManagerSizeDefaults.WidthPct, 0.0) | 188 | assert.Greater(t, SessionManagerSizeDefaults.WidthPct, 0.0) | ||
| 177 | assert.LessOrEqual(t, SessionManagerSizeDefaults.WidthPct, 1.0) | 189 | assert.LessOrEqual(t, SessionManagerSizeDefaults.WidthPct, 1.0) | ||
| @@ -325,7 +337,7 @@ func TestCalculateModalDimensionsWithScale_LargeParent(t *testing.T) { | |||||
| 325 | parent := mockParent(t, 3000, 1200) | 337 | parent := mockParent(t, 3000, 1200) | ||
| 326 | w, m := CalculateModalDimensionsWithScale(parent, cfg, tt.scale) | 338 | w, m := CalculateModalDimensionsWithScale(parent, cfg, tt.scale) | ||
| 327 | assert.Equal(t, tt.want, w, "scale %v", tt.scale) | 339 | assert.Equal(t, tt.want, w, "scale %v", tt.scale) | ||
| 328 | - | assert.Equal(t, 240, m, "top margin unchanged") | 340 | + | assert.Zero(t, m, "omnibox is centered, not top-margined") |
| 329 | } | 341 | } | ||
| 330 | } | 342 | } | ||
| 331 | 343 | ||||
| @@ -351,7 +363,7 @@ func TestCalculateModalDimensionsWithScale_NormalizesBadScale(t *testing.T) { | |||||
| 351 | parent := mockParent(t, 3000, 1080) | 363 | parent := mockParent(t, 3000, 1080) | ||
| 352 | w, m := CalculateModalDimensionsWithScale(parent, cfg, scale) | 364 | w, m := CalculateModalDimensionsWithScale(parent, cfg, scale) | ||
| 353 | assert.Equal(t, 800, w, "scale %v", scale) | 365 | assert.Equal(t, 800, w, "scale %v", scale) | ||
| 354 | - | assert.Equal(t, 216, m, "scale %v", scale) | 366 | + | assert.Zero(t, m, "scale %v", scale) |
| 355 | } | 367 | } | ||
| 356 | } | 368 | } | ||
| 357 | 369 | ||||
M internal/ui/component/omnibox.go +46 -12
| @@ -776,12 +776,21 @@ func (o *Omnibox) initOuterBox() error { | |||
| 776 | 776 | return errNilWidget("outerBox") | |
| 777 | 777 | } | |
| 778 | 778 | o.outerBox.AddCssClass("omnibox-outer") | |
| 779 | - | o.outerBox.SetHalign(gtk.AlignCenterValue) // Center horizontally | |
| 780 | - | o.outerBox.SetValign(gtk.AlignStartValue) // Align to top | |
| 781 | - | o.outerBox.SetVisible(false) // Hidden by default | |
| 779 | + | o.outerBox.SetHalign(gtk.AlignCenterValue) | |
| 780 | + | o.outerBox.SetValign(omniboxValign(o.sizeCfg)) | |
| 781 | + | o.outerBox.SetVisible(false) // Hidden by default | |
| 782 | 782 | return nil | |
| 783 | 783 | } | |
| 784 | 784 | ||
| 785 | + | // omniboxValign centers the in-pane omnibox vertically; a fixed top margin | |
| 786 | + | // (standalone window sized by its content) keeps it top-aligned. | |
| 787 | + | func omniboxValign(cfg ModalSizeConfig) gtk.Align { | |
| 788 | + | if cfg.UseFixedTopMargin { | |
| 789 | + | return gtk.AlignStartValue | |
| 790 | + | } | |
| 791 | + | return gtk.AlignCenterValue | |
| 792 | + | } | |
| 793 | + | ||
| 785 | 794 | func (o *Omnibox) initMainBox() error { | |
| 786 | 795 | o.mainBox = gtk.NewBox(gtk.OrientationVerticalValue, 0) | |
| 787 | 796 | if o.mainBox == nil { | |
| @@ -923,7 +932,7 @@ func (o *Omnibox) initList() error { | |||
| 923 | 932 | return | |
| 924 | 933 | } | |
| 925 | 934 | ||
| 926 | - | targetURL := resolveTargetURLForSelection(mode, idx, o.effectiveMaxRows(), suggestions, favorites) | |
| 935 | + | targetURL := resolveTargetURLForSelection(mode, idx, OmniboxListDefaults.MaxResults, suggestions, favorites) | |
| 927 | 936 | ||
| 928 | 937 | if targetURL != "" { | |
| 929 | 938 | o.submitNavigation(targetURL) | |
| @@ -1503,7 +1512,7 @@ func (o *Omnibox) restoreEntryToRealInput() { | |||
| 1503 | 1512 | // updateGhostFromSelectionWithInput updates ghost text based on selected row and input. | |
| 1504 | 1513 | func (o *Omnibox) updateGhostFromSelectionWithInput(entryText string) { | |
| 1505 | 1514 | log := logging.FromContext(o.ctx) | |
| 1506 | - | maxVisible := o.effectiveMaxRows() | |
| 1515 | + | maxVisible := OmniboxListDefaults.MaxResults | |
| 1507 | 1516 | o.mu.RLock() | |
| 1508 | 1517 | idx := o.selectedIndex | |
| 1509 | 1518 | mode := o.viewMode | |
| @@ -1632,7 +1641,7 @@ func (o *Omnibox) performSearch() { | |||
| 1632 | 1641 | } | |
| 1633 | 1642 | ||
| 1634 | 1643 | // Perform fuzzy history search in background | |
| 1635 | - | o.searchHistory(query, o.effectiveMaxRows(), token) | |
| 1644 | + | o.searchHistory(query, OmniboxListDefaults.MaxResults, token) | |
| 1636 | 1645 | } | |
| 1637 | 1646 | ||
| 1638 | 1647 | // searchHistory runs a fuzzy history search in a background goroutine. | |
| @@ -1706,7 +1715,7 @@ func (o *Omnibox) loadInitialHistory(token uint64) { | |||
| 1706 | 1715 | ||
| 1707 | 1716 | // Capture effective result limit and age window on the GTK main thread | |
| 1708 | 1717 | // before spawning goroutine. | |
| 1709 | - | initialLimit := o.effectiveMaxRows() | |
| 1718 | + | initialLimit := OmniboxListDefaults.MaxResults | |
| 1710 | 1719 | initialBehavior := o.initialBehavior | |
| 1711 | 1720 | maxHistoryDays := o.maxHistoryDays | |
| 1712 | 1721 | ||
| @@ -1962,8 +1971,13 @@ func (o *Omnibox) updateFavorites(favorites []Favorite, query string) { | |||
| 1962 | 1971 | ||
| 1963 | 1972 | // rebuildList rebuilds the ListBox contents. | |
| 1964 | 1973 | func (o *Omnibox) rebuildList() { | |
| 1965 | - | // Clear existing rows | |
| 1974 | + | // Clear existing rows and show new results from the top | |
| 1966 | 1975 | o.listBox.RemoveAll() | |
| 1976 | + | if o.scrolledWin != nil { | |
| 1977 | + | if adj := o.scrolledWin.GetVadjustment(); adj != nil { | |
| 1978 | + | adj.SetValue(0) | |
| 1979 | + | } | |
| 1980 | + | } | |
| 1967 | 1981 | ||
| 1968 | 1982 | o.mu.RLock() | |
| 1969 | 1983 | mode := o.viewMode | |
| @@ -2253,19 +2267,39 @@ func (o *Omnibox) selectIndex(index int) { | |||
| 2253 | 2267 | row := o.listBox.GetRowAtIndex(index) | |
| 2254 | 2268 | if row != nil { | |
| 2255 | 2269 | o.listBox.SelectRow(row) | |
| 2270 | + | o.scrollRowIntoView(row) | |
| 2256 | 2271 | return | |
| 2257 | 2272 | } | |
| 2258 | 2273 | ||
| 2259 | 2274 | o.updateGhostFromSelection() | |
| 2260 | 2275 | } | |
| 2261 | 2276 | ||
| 2277 | + | // scrollRowIntoView scrolls the results list so the row is fully visible. | |
| 2278 | + | // The entry keeps keyboard focus, so GTK does not scroll the list itself. | |
| 2279 | + | func (o *Omnibox) scrollRowIntoView(row *gtk.ListBoxRow) { | |
| 2280 | + | if o.scrolledWin == nil || row == nil { | |
| 2281 | + | return | |
| 2282 | + | } | |
| 2283 | + | adj := o.scrolledWin.GetVadjustment() | |
| 2284 | + | if adj == nil { | |
| 2285 | + | return | |
| 2286 | + | } | |
| 2287 | + | src := &graphene.Point{} | |
| 2288 | + | out := &graphene.Point{} | |
| 2289 | + | if !row.ComputePoint(&o.listBox.Widget, src, out) { | |
| 2290 | + | return | |
| 2291 | + | } | |
| 2292 | + | top := float64(out.Y) | |
| 2293 | + | adj.ClampPage(top, top+float64(row.GetAllocatedHeight())) | |
| 2294 | + | } | |
| 2295 | + | ||
| 2262 | 2296 | // selectNext moves selection down. | |
| 2263 | 2297 | func (o *Omnibox) selectNext() { | |
| 2264 | 2298 | o.mu.Lock() | |
| 2265 | 2299 | current := o.selectedIndex | |
| 2266 | 2300 | mode := o.viewMode | |
| 2267 | 2301 | bangMode := o.bangMode | |
| 2268 | - | maxVisible := o.effectiveMaxRows() | |
| 2302 | + | maxVisible := OmniboxListDefaults.MaxResults | |
| 2269 | 2303 | var maxIndex int | |
| 2270 | 2304 | if bangMode { | |
| 2271 | 2305 | maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1 | |
| @@ -2294,7 +2328,7 @@ func (o *Omnibox) selectPrevious() { | |||
| 2294 | 2328 | current := o.selectedIndex | |
| 2295 | 2329 | mode := o.viewMode | |
| 2296 | 2330 | bangMode := o.bangMode | |
| 2297 | - | maxVisible := o.effectiveMaxRows() | |
| 2331 | + | maxVisible := OmniboxListDefaults.MaxResults | |
| 2298 | 2332 | var maxIndex int | |
| 2299 | 2333 | if bangMode { | |
| 2300 | 2334 | maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1 | |
| @@ -2327,7 +2361,7 @@ func (o *Omnibox) selectAndNavigate(index int) { | |||
| 2327 | 2361 | suggestions := o.suggestions | |
| 2328 | 2362 | favorites := o.favorites | |
| 2329 | 2363 | o.mu.RUnlock() | |
| 2330 | - | maxVisible := o.effectiveMaxRows() | |
| 2364 | + | maxVisible := OmniboxListDefaults.MaxResults | |
| 2331 | 2365 | ||
| 2332 | 2366 | if bangMode { | |
| 2333 | 2367 | o.navigateToSelected() | |
| @@ -2400,7 +2434,7 @@ func (o *Omnibox) navigateToSelected() { | |||
| 2400 | 2434 | var targetURL string | |
| 2401 | 2435 | if idx >= 0 { | |
| 2402 | 2436 | // If user has selected a result, navigate to that result. | |
| 2403 | - | targetURL = resolveTargetURLForSelection(mode, idx, o.effectiveMaxRows(), suggestions, favorites) | |
| 2437 | + | targetURL = resolveTargetURLForSelection(mode, idx, OmniboxListDefaults.MaxResults, suggestions, favorites) | |
| 2404 | 2438 | } else { | |
| 2405 | 2439 | targetURL = o.buildURL(entryText) | |
| 2406 | 2440 | } | |
| @@ -776,12 +776,21 @@ func (o *Omnibox) initOuterBox() error { | |||||
| 776 | return errNilWidget("outerBox") | 776 | return errNilWidget("outerBox") | ||
| 777 | } | 777 | } | ||
| 778 | o.outerBox.AddCssClass("omnibox-outer") | 778 | o.outerBox.AddCssClass("omnibox-outer") | ||
| 779 | - | o.outerBox.SetHalign(gtk.AlignCenterValue) // Center horizontally | 779 | + | o.outerBox.SetHalign(gtk.AlignCenterValue) |
| 780 | - | o.outerBox.SetValign(gtk.AlignStartValue) // Align to top | 780 | + | o.outerBox.SetValign(omniboxValign(o.sizeCfg)) |
| 781 | - | o.outerBox.SetVisible(false) // Hidden by default | 781 | + | o.outerBox.SetVisible(false) // Hidden by default |
| 782 | return nil | 782 | return nil | ||
| 783 | } | 783 | } | ||
| 784 | 784 | ||||
| 785 | + | // omniboxValign centers the in-pane omnibox vertically; a fixed top margin | |||
| 786 | + | // (standalone window sized by its content) keeps it top-aligned. | |||
| 787 | + | func omniboxValign(cfg ModalSizeConfig) gtk.Align { | |||
| 788 | + | if cfg.UseFixedTopMargin { | |||
| 789 | + | return gtk.AlignStartValue | |||
| 790 | + | } | |||
| 791 | + | return gtk.AlignCenterValue | |||
| 792 | + | } | |||
| 793 | + | ||||
| 785 | func (o *Omnibox) initMainBox() error { | 794 | func (o *Omnibox) initMainBox() error { | ||
| 786 | o.mainBox = gtk.NewBox(gtk.OrientationVerticalValue, 0) | 795 | o.mainBox = gtk.NewBox(gtk.OrientationVerticalValue, 0) | ||
| 787 | if o.mainBox == nil { | 796 | if o.mainBox == nil { | ||
| @@ -923,7 +932,7 @@ func (o *Omnibox) initList() error { | |||||
| 923 | return | 932 | return | ||
| 924 | } | 933 | } | ||
| 925 | 934 | ||||
| 926 | - | targetURL := resolveTargetURLForSelection(mode, idx, o.effectiveMaxRows(), suggestions, favorites) | 935 | + | targetURL := resolveTargetURLForSelection(mode, idx, OmniboxListDefaults.MaxResults, suggestions, favorites) |
| 927 | 936 | ||||
| 928 | if targetURL != "" { | 937 | if targetURL != "" { | ||
| 929 | o.submitNavigation(targetURL) | 938 | o.submitNavigation(targetURL) | ||
| @@ -1503,7 +1512,7 @@ func (o *Omnibox) restoreEntryToRealInput() { | |||||
| 1503 | // updateGhostFromSelectionWithInput updates ghost text based on selected row and input. | 1512 | // updateGhostFromSelectionWithInput updates ghost text based on selected row and input. | ||
| 1504 | func (o *Omnibox) updateGhostFromSelectionWithInput(entryText string) { | 1513 | func (o *Omnibox) updateGhostFromSelectionWithInput(entryText string) { | ||
| 1505 | log := logging.FromContext(o.ctx) | 1514 | log := logging.FromContext(o.ctx) | ||
| 1506 | - | maxVisible := o.effectiveMaxRows() | 1515 | + | maxVisible := OmniboxListDefaults.MaxResults |
| 1507 | o.mu.RLock() | 1516 | o.mu.RLock() | ||
| 1508 | idx := o.selectedIndex | 1517 | idx := o.selectedIndex | ||
| 1509 | mode := o.viewMode | 1518 | mode := o.viewMode | ||
| @@ -1632,7 +1641,7 @@ func (o *Omnibox) performSearch() { | |||||
| 1632 | } | 1641 | } | ||
| 1633 | 1642 | ||||
| 1634 | // Perform fuzzy history search in background | 1643 | // Perform fuzzy history search in background | ||
| 1635 | - | o.searchHistory(query, o.effectiveMaxRows(), token) | 1644 | + | o.searchHistory(query, OmniboxListDefaults.MaxResults, token) |
| 1636 | } | 1645 | } | ||
| 1637 | 1646 | ||||
| 1638 | // searchHistory runs a fuzzy history search in a background goroutine. | 1647 | // searchHistory runs a fuzzy history search in a background goroutine. | ||
| @@ -1706,7 +1715,7 @@ func (o *Omnibox) loadInitialHistory(token uint64) { | |||||
| 1706 | 1715 | ||||
| 1707 | // Capture effective result limit and age window on the GTK main thread | 1716 | // Capture effective result limit and age window on the GTK main thread | ||
| 1708 | // before spawning goroutine. | 1717 | // before spawning goroutine. | ||
| 1709 | - | initialLimit := o.effectiveMaxRows() | 1718 | + | initialLimit := OmniboxListDefaults.MaxResults |
| 1710 | initialBehavior := o.initialBehavior | 1719 | initialBehavior := o.initialBehavior | ||
| 1711 | maxHistoryDays := o.maxHistoryDays | 1720 | maxHistoryDays := o.maxHistoryDays | ||
| 1712 | 1721 | ||||
| @@ -1962,8 +1971,13 @@ func (o *Omnibox) updateFavorites(favorites []Favorite, query string) { | |||||
| 1962 | 1971 | ||||
| 1963 | // rebuildList rebuilds the ListBox contents. | 1972 | // rebuildList rebuilds the ListBox contents. | ||
| 1964 | func (o *Omnibox) rebuildList() { | 1973 | func (o *Omnibox) rebuildList() { | ||
| 1965 | - | // Clear existing rows | 1974 | + | // Clear existing rows and show new results from the top |
| 1966 | o.listBox.RemoveAll() | 1975 | o.listBox.RemoveAll() | ||
| 1976 | + | if o.scrolledWin != nil { | |||
| 1977 | + | if adj := o.scrolledWin.GetVadjustment(); adj != nil { | |||
| 1978 | + | adj.SetValue(0) | |||
| 1979 | + | } | |||
| 1980 | + | } | |||
| 1967 | 1981 | ||||
| 1968 | o.mu.RLock() | 1982 | o.mu.RLock() | ||
| 1969 | mode := o.viewMode | 1983 | mode := o.viewMode | ||
| @@ -2253,19 +2267,39 @@ func (o *Omnibox) selectIndex(index int) { | |||||
| 2253 | row := o.listBox.GetRowAtIndex(index) | 2267 | row := o.listBox.GetRowAtIndex(index) | ||
| 2254 | if row != nil { | 2268 | if row != nil { | ||
| 2255 | o.listBox.SelectRow(row) | 2269 | o.listBox.SelectRow(row) | ||
| 2270 | + | o.scrollRowIntoView(row) | |||
| 2256 | return | 2271 | return | ||
| 2257 | } | 2272 | } | ||
| 2258 | 2273 | ||||
| 2259 | o.updateGhostFromSelection() | 2274 | o.updateGhostFromSelection() | ||
| 2260 | } | 2275 | } | ||
| 2261 | 2276 | ||||
| 2277 | + | // scrollRowIntoView scrolls the results list so the row is fully visible. | |||
| 2278 | + | // The entry keeps keyboard focus, so GTK does not scroll the list itself. | |||
| 2279 | + | func (o *Omnibox) scrollRowIntoView(row *gtk.ListBoxRow) { | |||
| 2280 | + | if o.scrolledWin == nil || row == nil { | |||
| 2281 | + | return | |||
| 2282 | + | } | |||
| 2283 | + | adj := o.scrolledWin.GetVadjustment() | |||
| 2284 | + | if adj == nil { | |||
| 2285 | + | return | |||
| 2286 | + | } | |||
| 2287 | + | src := &graphene.Point{} | |||
| 2288 | + | out := &graphene.Point{} | |||
| 2289 | + | if !row.ComputePoint(&o.listBox.Widget, src, out) { | |||
| 2290 | + | return | |||
| 2291 | + | } | |||
| 2292 | + | top := float64(out.Y) | |||
| 2293 | + | adj.ClampPage(top, top+float64(row.GetAllocatedHeight())) | |||
| 2294 | + | } | |||
| 2295 | + | ||||
| 2262 | // selectNext moves selection down. | 2296 | // selectNext moves selection down. | ||
| 2263 | func (o *Omnibox) selectNext() { | 2297 | func (o *Omnibox) selectNext() { | ||
| 2264 | o.mu.Lock() | 2298 | o.mu.Lock() | ||
| 2265 | current := o.selectedIndex | 2299 | current := o.selectedIndex | ||
| 2266 | mode := o.viewMode | 2300 | mode := o.viewMode | ||
| 2267 | bangMode := o.bangMode | 2301 | bangMode := o.bangMode | ||
| 2268 | - | maxVisible := o.effectiveMaxRows() | 2302 | + | maxVisible := OmniboxListDefaults.MaxResults |
| 2269 | var maxIndex int | 2303 | var maxIndex int | ||
| 2270 | if bangMode { | 2304 | if bangMode { | ||
| 2271 | maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1 | 2305 | maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1 | ||
| @@ -2294,7 +2328,7 @@ func (o *Omnibox) selectPrevious() { | |||||
| 2294 | current := o.selectedIndex | 2328 | current := o.selectedIndex | ||
| 2295 | mode := o.viewMode | 2329 | mode := o.viewMode | ||
| 2296 | bangMode := o.bangMode | 2330 | bangMode := o.bangMode | ||
| 2297 | - | maxVisible := o.effectiveMaxRows() | 2331 | + | maxVisible := OmniboxListDefaults.MaxResults |
| 2298 | var maxIndex int | 2332 | var maxIndex int | ||
| 2299 | if bangMode { | 2333 | if bangMode { | ||
| 2300 | maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1 | 2334 | maxIndex = visibleResultCount(len(o.bangSuggestions), maxVisible) - 1 | ||
| @@ -2327,7 +2361,7 @@ func (o *Omnibox) selectAndNavigate(index int) { | |||||
| 2327 | suggestions := o.suggestions | 2361 | suggestions := o.suggestions | ||
| 2328 | favorites := o.favorites | 2362 | favorites := o.favorites | ||
| 2329 | o.mu.RUnlock() | 2363 | o.mu.RUnlock() | ||
| 2330 | - | maxVisible := o.effectiveMaxRows() | 2364 | + | maxVisible := OmniboxListDefaults.MaxResults |
| 2331 | 2365 | ||||
| 2332 | if bangMode { | 2366 | if bangMode { | ||
| 2333 | o.navigateToSelected() | 2367 | o.navigateToSelected() | ||
| @@ -2400,7 +2434,7 @@ func (o *Omnibox) navigateToSelected() { | |||||
| 2400 | var targetURL string | 2434 | var targetURL string | ||
| 2401 | if idx >= 0 { | 2435 | if idx >= 0 { | ||
| 2402 | // If user has selected a result, navigate to that result. | 2436 | // If user has selected a result, navigate to that result. | ||
| 2403 | - | targetURL = resolveTargetURLForSelection(mode, idx, o.effectiveMaxRows(), suggestions, favorites) | 2437 | + | targetURL = resolveTargetURLForSelection(mode, idx, OmniboxListDefaults.MaxResults, suggestions, favorites) |
| 2404 | } else { | 2438 | } else { | ||
| 2405 | targetURL = o.buildURL(entryText) | 2439 | targetURL = o.buildURL(entryText) | ||
| 2406 | } | 2440 | } | ||
M internal/ui/component/omnibox_initial_behavior_test.go +1 -1
| @@ -287,7 +287,7 @@ func TestOmniboxLoadInitialHistory_MostVisitedBoundsSQL(t *testing.T) { | |||
| 287 | 287 | repo := repomocks.NewMockHistoryRepository(t) | |
| 288 | 288 | done := make(chan struct{}) | |
| 289 | 289 | repo.EXPECT(). | |
| 290 | - | GetMostVisitedWithin(mock.Anything, OmniboxListDefaults.MaxVisibleRows, mock.Anything). | |
| 290 | + | GetMostVisitedWithin(mock.Anything, OmniboxListDefaults.MaxResults, mock.Anything). | |
| 291 | 291 | Run(func(context.Context, int, repository.HistoryScope) { close(done) }). | |
| 292 | 292 | Return([]*entity.HistoryEntry{{URL: "https://example.com"}}, nil) | |
| 293 | 293 | ||
| @@ -287,7 +287,7 @@ func TestOmniboxLoadInitialHistory_MostVisitedBoundsSQL(t *testing.T) { | |||||
| 287 | repo := repomocks.NewMockHistoryRepository(t) | 287 | repo := repomocks.NewMockHistoryRepository(t) | ||
| 288 | done := make(chan struct{}) | 288 | done := make(chan struct{}) | ||
| 289 | repo.EXPECT(). | 289 | repo.EXPECT(). | ||
| 290 | - | GetMostVisitedWithin(mock.Anything, OmniboxListDefaults.MaxVisibleRows, mock.Anything). | 290 | + | GetMostVisitedWithin(mock.Anything, OmniboxListDefaults.MaxResults, mock.Anything). |
| 291 | Run(func(context.Context, int, repository.HistoryScope) { close(done) }). | 291 | Run(func(context.Context, int, repository.HistoryScope) { close(done) }). | ||
| 292 | Return([]*entity.HistoryEntry{{URL: "https://example.com"}}, nil) | 292 | Return([]*entity.HistoryEntry{{URL: "https://example.com"}}, nil) | ||
| 293 | 293 | ||||