Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- The quick filters over the pull request list in Pulls (All, Ready, Drafts, Failing) are lighter: small text with the selected one shaded, as in the dock's tabs, sharing the list's width. A filter with nothing in it is faded and Failing's count turns red when a check fails. They no longer run past the list's edge with a monospace interface font.

### Fixed

- A session card no longer grows and shrinks as its agent starts and stops a tool, waits on you or compacts, pushing every card below it up and down. That line now takes the place of the card's path while it shows, and the path comes back when it goes.
Expand Down
22 changes: 20 additions & 2 deletions native/cmd/lich-native/pulls_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,29 @@ func (p *pullsScreen) filterTabs(th *ui.Theme, counts [pullsFilterCount]int) ui.
triggers := make([]ui.TabsTrigger, pullsFilterCount)
for i := range triggers {
n := counts[i]
count := filterCountColor(th, pullsFilter(i), n)
triggers[i] = ui.TabsTrigger{Text: pullsFilter(i).label(), Trailing: func(gtx C) D {
return th.Text(ui.TextXS, fmt.Sprint(n)).Muted().Layout(gtx)
return th.Text(ui.TextXS, fmt.Sprint(n)).In(count).Layout(gtx)
}}
}
return th.Tabs(&p.filter, triggers...)
tabs := th.Tabs(&p.filter, triggers...)
tabs.Variant = ui.TabsVariantGhost
return tabs
}

// emptyFilterOpacity fades a filter with nothing in it, the chip's opacity-40.
const emptyFilterOpacity = 0.4

// filterCountColor reads a filter's state off its count: a failing pull
// request in the destructive color, an empty filter faded.
func filterCountColor(th *ui.Theme, f pullsFilter, n int) color.NRGBA {
switch {
case n == 0:
return ui.Alpha(th.MutedForeground, emptyFilterOpacity)
case f == filterFailing:
return th.Destructive
}
return th.MutedForeground
}

// listWidth is the list island's width: w-80, or what the filter tabs need
Expand Down
15 changes: 15 additions & 0 deletions native/cmd/lich-native/pulls_width_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,18 @@ func TestPullsListGrowsForLongerFilterLabels(t *testing.T) {
t.Errorf("Portuguese: %d, want the %d the tabs need (bounded by %v)", w, need, pullsListMaxWidth)
}
}

// The counts carry the filter's state: a failing pull request reads in the
// destructive color, an empty filter fades, the rest stay muted.
func TestFilterCountColorReadsTheState(t *testing.T) {
th := &ui.Theme{Palette: ui.Dark}
if got := filterCountColor(th, filterFailing, 1); got != th.Destructive {
t.Errorf("failing: %v, want destructive", got)
}
if got := filterCountColor(th, filterFailing, 0); got != ui.Alpha(th.MutedForeground, emptyFilterOpacity) {
t.Errorf("failing with none: %v, want faded", got)
}
if got := filterCountColor(th, filterReady, 3); got != th.MutedForeground {
t.Errorf("ready: %v, want muted", got)
}
}
10 changes: 9 additions & 1 deletion native/ui/tabs.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@ func (t TabsStyle) tabsList(gtx C) D {
// every trigger as wide as the widest while that fits the width given; a vertical list is w-full over the
// same. The ghost list's triggers keep their own width: in the dock's
// w-fit list the browser sizes the list to the sum of its triggers'
// contents, so flex-1 has no free space to share.
// contents, so flex-1 has no free space to share. A ghost list given a
// minimum width is w-full instead, and flex-1 shares that.
func (t TabsStyle) tabsTriggerSizes(gtx C, geo tabsGeometry) []image.Point {
sizes := make([]image.Point, len(t.Triggers))
var widest image.Point
Expand All @@ -215,6 +216,13 @@ func (t TabsStyle) tabsTriggerSizes(gtx C, geo tabsGeometry) []image.Point {
sizes[i].Y = widest.Y
}
if t.Variant == TabsVariantGhost {
inner := gtx.Constraints.Min.X - (len(sizes)-1)*gtx.Dp(geo.listGap)
if len(sizes)*widest.X <= inner {
for i := range sizes {
sizes[i].X = widest.X
}
}
tabsShareSpare(sizes, inner)
return sizes
}
inner := gtx.Constraints.Max.X - 2*gtx.Dp(geo.listPad) - (len(sizes)-1)*gtx.Dp(geo.listGap)
Expand Down
21 changes: 21 additions & 0 deletions native/ui/tabs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,27 @@ func TestTabsGhostTriggersTakeTheirOwnWidth(t *testing.T) {
}
}

// A ghost list given a minimum width, the Pulls filter's w-full track, splits
// it into equal triggers.
func TestTabsGhostFillsAMinimumWidthEqually(t *testing.T) {
th := testTheme()
s := th.Tabs(new(TabsState), tabsNames("a", "a much longer trigger")...)
s.Variant = TabsVariantGhost
const width = 600
if d := layoutAt(func(gtx C) D { return s.Layout(gtx) }, width, width); d.Size.X != width {
t.Errorf("got list width %d, want the %d it was given", d.Size.X, width)
}
var sizes []image.Point
layoutAt(func(gtx C) D {
s.State.tabsUpdate(gtx, 2)
sizes = s.tabsTriggerSizes(gtx, tabsGeometries[TabsVariantGhost])
return D{}
}, width, width)
if sizes[0].X != sizes[1].X {
t.Errorf("trigger widths %d and %d, want them equal", sizes[0].X, sizes[1].X)
}
}

// NaturalWidth is the narrowest list in which no trigger overflows: laid out
// at it the list takes exactly that width, one pixel less and it spills.
func TestTabsNaturalWidthFitsEveryTriggerAtItsContent(t *testing.T) {
Expand Down
Loading