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

- While a session works, the window redraws its busy spinner at 30 frames a second rather than at the display's rate, a card builds its right-click menu only while that menu is open, and a dialog that is shut builds nothing at all.

### Fixed

- Branch, changes and pull request on the cards and the footer catch up within a second of a commit, checkout or edit made outside a turn, where they could lag up to three seconds, and selecting a card reads its checkout at once instead of showing what the last read found.
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/close_worktree.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ func (u *window) layoutCloseWorktreeConfirm(gtx C, th *ui.Theme) {
c := &u.closeWorktree
c.mu.Lock()
defer c.mu.Unlock()
if !c.open && !c.dialog.Animating(gtx) {
return
}
if c.confirm.Clicked(gtx) && c.open {
c.open = false
project, path, force := c.project, c.path, c.dirty
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/dock_comment_edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ type commentEditDialog struct {
// layout is laid out by the window before its content, as every dialog is.
func (d *commentEditDialog) layout(gtx C, th *ui.Theme, store *commentStore) {
c, open := store.edited()
if !open && !d.dialog.Animating(gtx) {
return
}
if open && d.shown != c.id {
d.fill(gtx, c)
}
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/dock_review.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,9 @@ func revertStat(lines []backend.RevertLine) (added, deleted int) {
// layoutDialogs is the discard confirmation, laid out by the window before
// its content so it takes Escape first.
func (p *reviewPanel) layoutDialogs(gtx C, th *ui.Theme) {
if p.pendingDiscard == nil && !p.dialog.Animating(gtx) {
return
}
if p.pendingDiscard != nil {
p.discardShown = *p.pendingDiscard
}
Expand Down
2 changes: 1 addition & 1 deletion native/cmd/lich-native/dock_review_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ func (c *fileCard) body(gtx C, p *reviewPanel) D {
return bodyLine(gtx, th, i18n.T("diff.fileDiff.readFailed", i18n.Args{"path": c.git.path}))
}
return layout.Inset{Top: ui.Space(1), Bottom: ui.Space(1)}.Layout(gtx, func(gtx C) D {
return th.ContextMenu(p.root, &c.menu, c.menuEntries(p)).Layout(gtx, func(gtx C) D {
return th.ContextMenuLazy(p.root, &c.menu, func() []ui.MenuEntry { return c.menuEntries(p) }).Layout(gtx, func(gtx C) D {
return c.diff(gtx, p, view)
})
})
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/git_missing.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ func (u *window) checkGit(refresh bool) {

func (u *window) layoutGitMissing(gtx C, th *ui.Theme) {
g := &u.gitMissing
if !g.missing.Load() && !g.dialog.Animating(gtx) {
return
}
if g.notNow.Clicked(gtx) {
g.missing.Store(false)
}
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/layouts_window.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ func (u *window) askMoveDelegates(orchestrator string, ids []string, away int) {
// with.
func (u *window) layoutMoveConfirm(gtx C, th *ui.Theme) {
c := &u.layoutStage.move
if !c.open && !c.dialog.Animating(gtx) {
return
}
if c.confirm.Clicked(gtx) && c.open {
c.open = false
if c.delegates != nil {
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/notify_prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ type notifyOptIn struct {
// button is an answer, stored; Escape or the backdrop closes it unanswered.
func (u *window) layoutNotifyOptIn(gtx C, th *ui.Theme) {
o := &u.optIn
if _, asking := u.notifier.snapshot(); !asking && !o.dialog.Animating(gtx) {
return
}
if o.no.Clicked(gtx) {
u.notifier.setAttention(false)
}
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/palette_window.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ func (u *window) paletteAvailable(id hotkeyID) bool {
// there.
func (u *window) layoutPalette(gtx C, th *ui.Theme) {
p := u.palette
if !p.open && !p.dialog.Animating(gtx) {
return
}
on := th.On(th.Popover)
// The list fades out as the frame it closed on, rather than as a query
// that found nothing.
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/plugin_gate.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ func (u *window) layoutPluginGate(gtx C, th *ui.Theme) {
g.mu.Lock()
offer, installing := g.offer, g.installing
g.mu.Unlock()
if len(offer) == 0 && !g.dialog.Animating(gtx) {
return
}
closeOffer := func() {
g.mu.Lock()
g.offer = nil
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/pulls_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,9 @@ func (u *window) layoutRemoveConfirm(gtx C, th *ui.Theme) {
c := &u.pulls.confirm
c.mu.Lock()
defer c.mu.Unlock()
if !c.open && !c.dialog.Animating(gtx) {
return
}
if c.confirm.Clicked(gtx) && c.open {
c.open = false
project, path := c.project, c.path
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/pulls_comment_edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ func (u *window) deleteComment(path string, number int, t commentTarget) {
func (u *window) layoutDeleteCommentDialog(gtx C, th *ui.Theme) {
p := u.pulls
s := &p.view.talk
if s.deleting == nil && !s.deleteDialog.Animating(gtx) {
return
}
if s.deleting != nil {
s.deleteShown = *s.deleting
}
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/pulls_merge_dialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ func (u *window) layoutMergeDialog(gtx C, th *ui.Theme) {
v := &u.pulls.view
m := &v.mergeEdit
p := u.pulls
if !m.open && !m.dialog.Animating(gtx) {
return
}
if m.cancel.Clicked(gtx) {
m.open = false
}
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/pulls_submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ func (u *window) layoutSubmitDialog(gtx C, th *ui.Theme) {
v := &u.pulls.view
s := &v.submit
p := u.pulls
if s.verdict == "" && !s.dialog.Animating(gtx) {
return
}
if s.cancel.Clicked(gtx) {
s.verdict = ""
}
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/resume_dialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ type resumeDialog struct {
func (r *resumeDialog) layout(ctx context.Context, gtx C, th *ui.Theme, g *spawnGate, m *model) {
s := g.prompt()
open := s != nil
if !open && !r.dialog.Animating(gtx) {
return
}
if open && r.shown.ID != s.ID {
r.shown, r.provider, r.remember.Value = *s, m.providerName(s.Kind), false
}
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/settings_font_replicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ func (s *settingsScreen) layoutFontReplication(gtx C, th *ui.Theme) {
family := r.family
s.mu.Unlock()
open := family != "" && family == s.look.prefs.uiFont
if !open && !r.state.Animating(gtx) {
return
}
closed := r.keep.Clicked(gtx)
if r.apply.Clicked(gtx) && open {
p := s.look.prefs
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/settings_language.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ func wholeMegabytes(n int64) int64 { return (n + bytesPerMB/2) / bytesPerMB }
// at launch it asks before Settings ever opens.
func (s *settingsScreen) layoutHanFontDialog(gtx C, th *ui.Theme) {
o := &s.languages.offer
if !o.open && !o.dialog.Animating(gtx) {
return
}
if r := o.outcome(); r != nil {
s.finishHanFontDownload(r)
}
Expand Down
6 changes: 5 additions & 1 deletion native/cmd/lich-native/settings_profiles_dialogs.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,15 @@ func (s *settingsScreen) layoutProfileDialogs(gtx C, th *ui.Theme) {
// s.invalidate.
func (s *settingsScreen) layoutProfileDialog(gtx C, th *ui.Theme, c *profileSettings) {
f := &c.flow
if !(f.view().open && s.open) && !f.dialog.Animating(gtx) {
return
}
s.flowInput(gtx, f, f.view())
d := f.view()
open := d.open && s.open
on := th.On(th.Popover)
title, desc := flowHeading(c.provider, d)
dlg := th.Dialog(&f.dialog, d.open && s.open, title, desc)
dlg := th.Dialog(&f.dialog, open, title, desc)
dlg.MaxWidth, dlg.Root = ui.DialogWidthXL, s.root
dlg.Body = func(gtx C) D { return s.flowBody(gtx, on, f, d) }
dlg.Actions = s.flowActions(on, f, d)
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/settings_providers_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,9 @@ func (s *settingsScreen) writeSkip(kind, level string) {
// with its own constraints, as every dialog.
func (s *settingsScreen) layoutProviderDialog(gtx C, th *ui.Theme) {
pr := &s.providers
if !(pr.asking && s.open) && !pr.dialog.Animating(gtx) {
return
}
if pr.confirm.Clicked(gtx) && pr.asking {
s.writeSkip(pr.change.provider.id, pr.change.level)
pr.asking = false
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/settings_sandbox_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ func proseLine(th *ui.Theme, text string) layout.Widget {
// with its own constraints, as every dialog.
func (s *settingsScreen) layoutSandboxDialog(gtx C, th *ui.Theme) {
sb := &s.sandbox
if !(sb.asking && s.open) && !sb.dialog.Animating(gtx) {
return
}
if sb.confirm.Clicked(gtx) && sb.asking {
s.writeSandbox(sandboxKey(sb.change.providerID), sb.change.level)
sb.asking = false
Expand Down
6 changes: 6 additions & 0 deletions native/cmd/lich-native/settings_theme_dialogs.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ type confirmSpec struct {
// confirmDialog is ConfirmDialog: Cancel and one destructive action at
// sm:max-w-xl. It reports whether the action was pressed.
func (p *themePicker) confirmDialog(gtx C, th *ui.Theme, c confirmSpec) bool {
if !c.open && !c.state.Animating(gtx) {
return false
}
d := &p.dialogs
pressed := d.confirm[c.slot].Clicked(gtx) && c.open && !c.busy
if d.cancel[c.slot].Clicked(gtx) {
Expand All @@ -106,6 +109,9 @@ func (p *themePicker) confirmDialog(gtx C, th *ui.Theme, c confirmSpec) bool {
// file through the backend's picker, and the template to start one from.
func (p *themePicker) importDialog(gtx C, th *ui.Theme, t themeTasks) {
d := &p.dialogs
if !t.importOpen && !d.imp.Animating(gtx) {
return
}
url := strings.TrimSpace(d.url.Text())
submitted := false
for {
Expand Down
6 changes: 5 additions & 1 deletion native/cmd/lich-native/settings_updates_notes.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,16 +239,20 @@ func (s *settingsScreen) layoutPatchNotesDialog(gtx C, th *ui.Theme) {
if notes == nil {
return
}
if !(v.open && (s.open || v.startup)) && !v.dialog.Animating(gtx) {
return
}
if v.gotIt.Clicked(gtx) {
s.closePatchNotes(notes.Version)
}
if v.changelog.Clicked(gtx) {
s.openExternal(helpRepoURL + updatesReleaseTagAt + notes.Version)
}
open := v.open && (s.open || v.startup)
on := th.On(th.Popover)
got := on.Button(&v.gotIt, i18n.T("shell.patchNotesDialog.gotIt"))
got.Size = ui.SizeSM
dlg := th.Dialog(&v.dialog, v.open && (s.open || v.startup), i18n.T("shell.patchNotesDialog.title", i18n.Args{"version": "v" + notes.Version}), notesCounts(notes.Groups))
dlg := th.Dialog(&v.dialog, open, i18n.T("shell.patchNotesDialog.title", i18n.Args{"version": "v" + notes.Version}), notesCounts(notes.Groups))
dlg.MaxWidth, dlg.HideClose = ui.DialogWidthXL, true
dlg.Body = func(gtx C) D { return v.body(gtx, on, notes) }
dlg.Actions = []layout.Widget{func(gtx C) D {
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/shortcuts_overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ type shortcutsOverlay struct {
// layout draws the overlay over the window. Call it before the content under
// it, so it takes Escape ahead of any key filter there.
func (o *shortcutsOverlay) layout(gtx C, th *ui.Theme, bound hotkeyBindings) {
if !o.open && !o.dialog.Animating(gtx) {
return
}
on := th.On(th.Popover)
dlg := th.Dialog(&o.dialog, o.open, i18n.T("shell.shortcutsOverlay.title"), i18n.T("shell.shortcutsOverlay.rebind"))
dlg.MaxWidth, dlg.HideClose, dlg.Scrim = shortcutsWidth, true, shortcutsScrim
Expand Down
20 changes: 13 additions & 7 deletions native/cmd/lich-native/sidebar_card.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,13 @@ func (s *sidebar) card(gtx C, th *ui.Theme, sv *sessionView, active bool, folder
rename = new(ui.InlineEdit)
s.renames[sv.ID] = rename
}
var extra []ui.MenuEntry
if len(sv.continueIn) > 0 {
extra = append(extra, continueInItem(th, sv, &act))
}
entries := cardMenu(sv, folders, &act, func() { rename.Start(sv.Label) }, func() { s.askFolderName([]string{sv.ID}) }, extra...)
d := th.ContextMenu(s.root, menu, entries).Layout(gtx, func(gtx C) D {
d := th.ContextMenuLazy(s.root, menu, func() []ui.MenuEntry {
var extra []ui.MenuEntry
if len(sv.continueIn) > 0 {
extra = append(extra, continueInItem(th, sv, &act))
}
return cardMenu(sv, folders, &act, func() { rename.Start(sv.Label) }, func() { s.askFolderName([]string{sv.ID}) }, extra...)
}).Layout(gtx, func(gtx C) D {
return tooltip.Layout(gtx, func(gtx C) D { return s.cardFace(gtx, th, sv, click, pinClick, closeClick, prChip, fill) })
})
if s.renamed.id == sv.ID {
Expand Down Expand Up @@ -327,6 +328,11 @@ func (s *sidebar) statusRing(gtx C, th *ui.Theme, sv *sessionView) D {
return s.morph.glyph(gtx, glyphSession+sv.ID, func(gtx C) D { return s.statusGlyph(gtx, th, sv) })
}

// spinnerFrame is how often a busy session's ring redraws. The ring shares
// the window's frame, so its rate is the whole window's repaint rate: 30fps
// reads as smooth on a 22px ring and costs half of a display's.
const spinnerFrame = 33 * time.Millisecond

func (s *sidebar) statusGlyph(gtx C, th *ui.Theme, sv *sessionView) D {
size := gtx.Dp(22)
stroke := float32(gtx.Dp(1.25))
Expand All @@ -337,7 +343,7 @@ func (s *sidebar) statusGlyph(gtx C, th *ui.Theme, sv *sessionView) D {
ui.StrokeArc(gtx, center, radius, stroke, 0, 2*math.Pi, th.Over(ui.Alpha(th.MutedForeground, 0.25)))
turn := float32(time.Since(s.started).Seconds()) * 2 * math.Pi
ui.StrokeArc(gtx, center, radius, stroke, turn, math.Pi/2, th.MutedForeground)
gtx.Execute(op.InvalidateCmd{})
gtx.Execute(op.InvalidateCmd{At: gtx.Now.Add(spinnerFrame)})
case "waiting":
ui.StrokeArc(gtx, center, radius, stroke, 0, 2*math.Pi, th.ToneWait)
case "done":
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/sidebar_folder_dialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ func folderCommit(typed string, existing []string) (name string, ok, known bool)
// claims Escape ahead of anything under it; it reports the filing it commits.
func (s *sidebar) layoutFolderDialog(gtx C, th *ui.Theme, existing []string) fileRequest {
n := &s.naming
if !n.open && !n.dialog.Animating(gtx) {
return fileRequest{}
}
name, ok, known := folderCommit(n.name.Text(), existing)
submitted := false
for {
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/topbar_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ type closeConfirm struct {
// "" for none.
func (t *topbar) layoutConfirm(gtx C, th *ui.Theme) string {
c := &t.confirm
if !c.open && !c.dialog.Animating(gtx) {
return ""
}
closed := ""
if c.confirm.Clicked(gtx) && c.open {
closed, c.open = c.id, false
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/window_close.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ func (u *window) layoutClosePrompt(gtx C, th *ui.Theme) {
c.mu.Lock()
running := c.running
c.mu.Unlock()
if running == 0 && !c.dialog.Animating(gtx) {
return
}
answer := func(choice string, act func()) {
if c.remember.Value {
p := u.settings.look.prefs
Expand Down
3 changes: 3 additions & 0 deletions native/cmd/lich-native/worktree_dialog_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ func baseLabel(base string) string {
// it.
func (u *window) layoutWorktreeDialog(gtx C, th *ui.Theme) {
d := u.worktree
if !d.open && !d.dialog.Animating(gtx) {
return
}
if d.view().done {
d.open = false
}
Expand Down
17 changes: 15 additions & 2 deletions native/ui/context_menu.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,22 @@ type ContextMenuStyle struct {
Root *Root
State *MenuState
Entries []MenuEntry
theme *Theme
// build is Entries made on demand, and only while the menu is open: a
// closed menu's entries cost every frame they are built and never show.
build func() []MenuEntry
theme *Theme
}

func (th *Theme) ContextMenu(root *Root, state *MenuState, entries []MenuEntry) ContextMenuStyle {
return ContextMenuStyle{Root: root, State: state, Entries: entries, theme: th}
}

// ContextMenuLazy is ContextMenu whose entries are built only while the menu
// is open, for a trigger that draws every frame and carries a large menu.
func (th *Theme) ContextMenuLazy(root *Root, state *MenuState, entries func() []MenuEntry) ContextMenuStyle {
return ContextMenuStyle{Root: root, State: state, build: entries, theme: th}
}

// Layout lays out trigger, the area that answers a right press, and the
// menu when open.
func (c ContextMenuStyle) Layout(gtx C, trigger layout.Widget) D {
Expand All @@ -40,6 +49,10 @@ func (c ContextMenuStyle) Layout(gtx C, trigger layout.Widget) D {
pointerAt := c.Root.pointer.Round()
s.menuOpen(image.Rectangle{Min: pointerAt, Max: pointerAt}, c.Root.origin(at))
}
menuCore{th: c.theme, root: c.Root, s: s, entries: c.Entries, geo: &contextGeometry}.layout(gtx)
entries := c.Entries
if c.build != nil && s.open {
entries = c.build()
}
menuCore{th: c.theme, root: c.Root, s: s, entries: entries, geo: &contextGeometry}.layout(gtx)
return dims
}
Loading
Loading