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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
17 changes: 15 additions & 2 deletions docs/developer/architecture.md

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions docs/developer/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ malformed-input tests or fuzz targets.
on such a line (2026-10-12).
- Verify every visual change with an offscreen render (Fyne's `test` package) or a screenshot, in
dark and light themes, and attach before/after images to the pull request.
- Escape closes the innermost thing open ([D-116](decisions.md#d-116-escape-closes-the-innermost-thing),
[`escape.go`](../../internal/ui/fyneui/escape.go)): show a Fyne dialog with `sh.showDialog`, never
its own `Show`; show a `widget.PopUp` through `onShown` with what closes it; give a dialog's
fields the types that pass Escape on to the dialog's Cancel (`newFieldEntry`,
`newFieldSelectEntry`, `newFieldCheck`, `newFieldSlider`), never Fyne's plain ones
(`TestEveryDialogTakesEscape` reads the sources for the first two).
- Every dropdown is a `selectField` (`newSelectField`, [`selectfield.go`](../../internal/ui/fyneui/selectfield.go)),
never Fyne's `widget.NewSelect`: it opens over its field, the current entry checked at the
field's place, as a macOS pop-up button (the owner's GW5, 2026-10-20: Fyne's list showed the
Expand Down
105 changes: 99 additions & 6 deletions docs/developer/decisions.md

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion docs/developer/design-language.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ brackets with end ticks, lowercase labels with a middle dot, a faint perspective

The palettes in [theme.go](../../internal/ui/fyneui/theme.go) replace Solarized (D-22) on this branch.
Dark: stage `#0d121a`, chrome `#0a0e14`, sidebar `#0b0f16`, drawer `#10161f`, overlay `#121925`,
hairline `#1b2330`; text `#d5dde8`, muted `#8592a5` (5.9:1 on the stage); glow blue `#4aa8ff`, cyan
hairline `#1b2330`, a well's or card's edge `#263042`; a field's outline `#3e4f6a`, light `#a1acbc`,
2:1 on every surface a field stands on, since a closed dropdown on a dialog read as the dialog's own
colour (the owner's AW1 of 2026-10-22, [D-104](decisions.md#d-104-dialog-fields-their-hints-and-dropdowns-the-gate-walks-gw2-gw5-gw6)); text `#d5dde8`, muted `#8592a5` (5.9:1 on the stage); glow blue `#4aa8ff`, cyan
`#5ad1e3`, violet `#a48bff`; status mint `#3dd68c`, amber `#f2c14e`, orange `#ff9f5a`, coral `#ff6b74`,
rose `#ee82b8` (Terminating; violet is the agent's), blue `#5ab8ff` (Succeeded). Light keeps the same
roles on white plates with the lights deepened for 4.5:1 text. Every pair still passes
Expand Down
20 changes: 20 additions & 0 deletions docs/developer/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -1409,6 +1409,26 @@ table's newest (accurate to weeks; checked against every minor since 1.19).
The gear before the root's dot opens the cluster's Settings, as ⌘⇧, does
([D-92](decisions.md#d-92-a-gear-in-the-navigators-cluster-row)).

### Escape

Since 2026-10-22 (the owner's alpha walk, [D-116](decisions.md#d-116-escape-closes-the-innermost-thing)),
Escape closes the innermost thing open, and only that; press it again to go one step further out.

| Where you are | Escape |
|---|---|
| An open dropdown's list | closes it without a choice; the field is no longer highlighted |
| A dialog (a confirmation, Rollback, Scale, a create dialog, Connection Updates, About, …), whatever field has the keyboard | is its Cancel, or its Close where it has none; the drawer behind it stays open |
| A pop-up list (the namespace filter, the status filter, the columns, the agent's history) or the palette, the bell's list, the MCP details | closes it |
| A dock tab's find bar (its fields or the editor) | closes the bar; the editor gets the keys, the find text stays for the next ⌘F |
| A list's search (⌘F) | clears it; the list gets the keys |
| The logs tab's search, Argo CD's activity filter | clears it |
| A settings page (Preferences, Cluster Settings) | closes the page (a text field on it keeps Escape) |
| The list or the navigator | closes the drawer |

A selected text (a log, a toast, the agent's answer) drops its selection first, and the
navigator's search opened by ⌘P and the namespace filter's search clear a typed text first, as
before.

### Selecting and copying a log's, a notification's and the agent's text

Since 2026-10-19 (the owner's WF36 and WF41,
Expand Down
30 changes: 22 additions & 8 deletions docs/parity/gaps.md

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions docs/parity/ui-parity.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion internal/ui/fyneui/about.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (sh *shell) showAbout() { sh.showAboutFor(buildinfo.Read()) }

// showAboutFor shows About Cogpit for a build. No title: the name is the dialog's heading.
func (sh *shell) showAboutFor(info buildinfo.Info) {
dialog.NewCustom("", "Close", sh.aboutContent(info), sh.win).Show()
sh.showDialog(dialog.NewCustom("", "Close", sh.aboutContent(info), sh.win))
}

// aboutContent is the dialog's column, every line centred.
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/fyneui/alerts.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ func (r *alertsRenderer) Refresh() {
r.bg.FillColor = themeColor(th, colorRowHover)
}
// A hairline keeps the neutral plate on a light bar, where chip and chrome are close.
r.bg.StrokeColor, r.bg.StrokeWidth = themeColor(th, theme.ColorNameInputBorder), 1
r.bg.StrokeColor, r.bg.StrokeWidth = themeColor(th, colorHairline), 1
}
for i, sg := range r.segs {
tone := themeColor(th, alertSegments[i].tone)
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/fyneui/argocd.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func (sh *shell) openArgoSyncDialog(refs []model.ObjectRef) {
if len(refs) == 0 {
return
}
prune := widget.NewCheck("Prune resources not in Git", nil)
prune := newFieldCheck("Prune resources not in Git")
width := sh.app.Settings().Theme().Size(sizeConfirmWidth)
hint := newParagraph("Pruning deletes the live resources the Application manages that are no longer in Git; Cogpit asks once more before it does.", colorMuted, width)
sh.argoDialog("Sync", refs, container.NewVBox(prune, hint), func(chosen []model.ObjectRef) {
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/fyneui/argocd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ func TestArgoSyncWithPruneConfirms(t *testing.T) {
}
}
q.until(t, "the sync dialog", func() bool { return w.Canvas().Overlays().Top() != nil })
for _, c := range findAll[*widget.Check](w.Canvas().Overlays().Top()) {
for _, c := range checksIn(w.Canvas().Overlays().Top()) {
c.SetChecked(true)
}
argoTapButton(t, w, "Sync")
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/fyneui/chart_hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ func (r *chartRenderer) buildOverlay() {
py := fyne.Max(0, fyne.Min(g.top+m.pad, size.Height-hgt))

bg := canvas.NewRectangle(col(colorTooltip))
bg.StrokeColor, bg.StrokeWidth = col(theme.ColorNameInputBorder), 1
bg.StrokeColor, bg.StrokeWidth = col(colorHairline), 1
bg.CornerRadius = th.Size(theme.SizeNameInputRadius)
bg.Move(fyne.NewPos(px, py))
bg.Resize(fyne.NewSize(w, hgt))
Expand Down
10 changes: 6 additions & 4 deletions internal/ui/fyneui/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,9 @@ func (c *chatPanel) showHistoryList(ctx string, list []engine.ChatConversationIn
content.SetMinSize(fyne.NewSize(width, min(float32(len(rows))*32+16, th.Size(sizePopupMaxList))))
c.history = widget.NewPopUp(content, c.sh.win.Canvas())
pos := fyne.CurrentApp().Driver().AbsolutePositionForObject(c.historyB)
c.history.ShowAtPosition(fyne.NewPos(pos.X+c.historyB.Size().Width-width-th.Size(theme.SizeNameInnerPadding), pos.Y+c.historyB.Size().Height))
at := fyne.NewPos(pos.X+c.historyB.Size().Width-width-th.Size(theme.SizeNameInnerPadding), pos.Y+c.historyB.Size().Height)
h := c.history
onShown(c.sh.win.Canvas(), func() { h.ShowAtPosition(at) }, h.Hide) // Escape closes it (escape.go)
}

// historyRow is one conversation of the history: its title and age (a running turn says so, and a
Expand Down Expand Up @@ -769,7 +771,7 @@ func (c *chatPanel) confirmClear(ctx string, n int) {
}()
}, c.sh.win)
d.SetConfirmText("Delete")
d.Show()
c.sh.showDialog(d)
}

// showPayload shows the exact request body the turn of a user's message sent, read-only (H-13:
Expand Down Expand Up @@ -828,7 +830,7 @@ func (c *chatPanel) payloadDialog(body, endpoint string, private bool) {
copyB := widget.NewButtonWithIcon("Copy", theme.ContentCopyIcon(), func() { c.sh.app.Clipboard().SetContent(body) })
d := dialog.NewCustom("What this turn sent", "Close", container.NewBorder(info, container.NewHBox(copyB), nil, nil, scroll), c.sh.win)
c.payload = d
d.Show()
c.sh.showDialog(d)
}

// --- the pinned message ---
Expand Down Expand Up @@ -876,7 +878,7 @@ var _ fyne.Tappable = (*chatPin)(nil)
func newChatPin(c *chatPanel) *chatPin {
p := &chatPin{c: c}
p.card = newSurface(colorDrawerHeader)
p.card.radius, p.card.stroke = theme.SizeNameInputRadius, theme.ColorNameInputBorder
p.card.radius, p.card.stroke = theme.SizeNameInputRadius, colorHairline
p.chip = newChatChip(iconTarget, nil)
p.text = newMDFlow(nil, theme.ColorNameForeground, nil)
p.text.maxLines = 2
Expand Down
4 changes: 2 additions & 2 deletions internal/ui/fyneui/chat_items.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ type chatUserView struct {
func newChatUserView(c *chatPanel, idx int) *chatUserView {
v := &chatUserView{c: c, idx: idx}
v.card = newSurface(colorDrawerHeader)
v.card.radius, v.card.stroke = theme.SizeNameInputRadius, theme.ColorNameInputBorder
v.card.radius, v.card.stroke = theme.SizeNameInputRadius, colorHairline
v.chip = newChatChip(iconTarget, nil)
v.text = newMDText(theme.ColorNameForeground, c.mdHooks())
v.sent = newChatSentLine(func() { c.showPayload(v.idx) })
Expand Down Expand Up @@ -254,7 +254,7 @@ func (r *chatUserRenderer) Layout(size fyne.Size) {
ch := v.cardHeight()
v.card.Move(fyne.NewPos(0, 0))
v.card.Resize(fyne.NewSize(size.Width, ch))
v.card.fill, v.card.stroke = colorDrawerHeader, theme.ColorNameInputBorder
v.card.fill, v.card.stroke = colorDrawerHeader, colorHairline
if v.editor != nil {
r.layoutEditor(size, ch)
return
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/fyneui/chat_markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -1375,7 +1375,7 @@ func (r *mdTableRenderer) Layout(fyne.Size) {
th := v.Theme()
pad := v.cellPad()
tw := v.tableWidth()
line := themeColor(th, theme.ColorNameInputBorder)
line := themeColor(th, colorHairline)
nLines := max(len(v.rowsH)-1, 0) + max(len(v.cols)-1, 0)
for len(v.lines) < nLines {
v.lines = append(v.lines, canvas.NewRectangle(line))
Expand Down
8 changes: 4 additions & 4 deletions internal/ui/fyneui/clustersettings.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func (p *settingsPage) clusterGeneralSection() fyne.CanvasObject {
sentence := newTextLine("", theme.ColorNameForeground, theme.SizeNameText, fyne.TextStyle{})
sentence.truncate = true
pathBg := newSurface(theme.ColorNameInputBackground)
pathBg.radius, pathBg.stroke = theme.SizeNameInputRadius, theme.ColorNameInputBorder
pathBg.radius, pathBg.stroke = theme.SizeNameInputRadius, colorHairline
open := newTipButton(p.sh, theme.FolderOpenIcon(), "Show the kubeconfig's folder in the file manager", nil)
open.OnTapped = func() {
if f := p.clusterView().Kubeconfig; f != "" {
Expand Down Expand Up @@ -349,17 +349,17 @@ func (p *settingsPage) pickIconWithDialog(done func(path string)) {
done(path)
}, p.sh.win)
d.SetFilter(storage.NewExtensionFileFilter([]string{".png", ".jpg", ".jpeg", ".svg"}))
d.Show()
p.sh.showDialog(d)
}

// pickFolderWithDialog asks for a folder in Fyne's dialog: pickFolder's fallback where the
// system has no panel (filepicker_choosers.go).
func (p *settingsPage) pickFolderWithDialog(done func(path string)) {
dialog.NewFolderOpen(func(l fyne.ListableURI, err error) {
p.sh.showDialog(dialog.NewFolderOpen(func(l fyne.ListableURI, err error) {
if err == nil && l != nil {
done(filepath.FromSlash(l.Path()))
}
}, p.sh.win).Show()
}, p.sh.win))
}

// openFolderWithFileManager shows a folder in the file manager (Finder on macOS): the folder that
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/fyneui/clustersettings_accent.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (p *settingsPage) pickColor(title string, set func(hex string)) {
set(fmt.Sprintf("#%02x%02x%02x", r>>8, g>>8, b>>8))
}, p.sh.win)
d.Advanced = true
d.Show()
p.sh.showDialog(d)
}

// noneSwatch is the accent row's None: an empty square crossed by a line, ringed while the cluster
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/fyneui/clustersettings_agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (p *settingsPage) confirmAgentWrites(ctx string, done func(bool)) {
time.AfterFunc(delay, func() { sh.do(cd.confirm.Enable) })
}
p.agentsDialog = cd
cd.d.Show()
sh.showDialog(cd.d) // Escape closes it, answering no (SetOnClosed)
}

// allowanceHint is the confirmation's line about the allowance (Cluster Settings › Agents) for an
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/fyneui/clustersettings_namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (p *settingsPage) clusterNamespacesSection() fyne.CanvasObject {
name := newTextLine(ns, theme.ColorNameForeground, theme.SizeNameText, fyne.TextStyle{Monospace: true})
name.truncate = true
bg := newSurface(theme.ColorNameInputBackground)
bg.radius, bg.stroke = theme.SizeNameInputRadius, theme.ColorNameInputBorder
bg.radius, bg.stroke = theme.SizeNameInputRadius, colorHairline
objs = append(objs, container.NewStack(bg, container.NewBorder(nil, nil, nil, remove,
inset(theme.SizeNameInnerPadding, theme.SizeNamePadding, name))))
}
Expand Down
4 changes: 2 additions & 2 deletions internal/ui/fyneui/connectlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (k *kubeconfigUI) openConnectLog(contextID string, v connectLogView) {
cl := &connectLogDialog{ctx: contextID, rows: container.NewVBox()}
cl.scroll = container.NewVScroll(cl.rows)
bg := newSurface(theme.ColorNameInputBackground)
bg.radius, bg.stroke = theme.SizeNameInputRadius, theme.ColorNameInputBorder
bg.radius, bg.stroke = theme.SizeNameInputRadius, colorHairline
content := sized(sizePickerWidth, sizePickerHeight, container.NewStack(bg, inset(theme.SizeNamePadding, theme.SizeNamePadding, cl.scroll)))

cl.d = dialog.NewCustomWithoutButtons("Connection Updates: "+kubeconfig.DisplayText(contextID, maxShownArg), content, k.sh.win)
Expand All @@ -137,7 +137,7 @@ func (k *kubeconfigUI) openConnectLog(contextID string, v connectLogView) {
})
k.connectLog = cl
k.fillConnectLog(cl, v)
cl.d.Show()
k.sh.showDialog(cl.d) // Escape closes it (AW19)
}

// connectLogChanged follows the engine's EventConnectLog: the open dialog of that context reads
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/fyneui/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,7 @@ func (r *copyButtonRenderer) Layout(size fyne.Size) {
func (r *copyButtonRenderer) Refresh() {
th := r.b.Theme()
r.bg.FillColor = themeColor(th, colorChip)
r.bg.StrokeColor = themeColor(th, theme.ColorNameInputBorder)
r.bg.StrokeColor = themeColor(th, colorHairline)
r.bg.StrokeWidth = th.Size(theme.SizeNameInputBorder)
r.bg.CornerRadius = th.Size(theme.SizeNameInputRadius)
if r.b.done {
Expand Down
30 changes: 15 additions & 15 deletions internal/ui/fyneui/createdialog.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,21 @@ type createForm struct {
cancel *widget.Button
status *paragraph

name *widget.Entry
ns *widget.SelectEntry // nil for a cluster-scoped kind
name *fieldEntry
ns *fieldSelectEntry // nil for a cluster-scoped kind
// The binding dialogs' role (roleKind nil in a cluster role binding's) and every list of rows.
roleKind *selectField
roleName *widget.SelectEntry
roleName *fieldSelectEntry
editors []*rowEditor
secret *secretForm // the Create Secret dialog's own fields (createdialog_secret.go)
offered map[*widget.SelectEntry][]string // what each field offers from the cache (offer)
secret *secretForm // the Create Secret dialog's own fields (createdialog_secret.go)
offered map[*fieldSelectEntry][]string // what each field offers from the cache (offer)
// writers add the dialog's other fields to the parameters, or say what is wrong with them.
writers []func(map[string]any) error
// onNamespace runs when the namespace changes: options that depend on it are offered again.
onNamespace []func(string)
// nsFields are the fields that offer the namespaces; they follow the names while the dialog is
// open (stopNS ends it), so that a namespace created since is offered (WF10).
nsFields []*widget.SelectEntry
nsFields []*fieldSelectEntry
stopNS func()

shown bool
Expand Down Expand Up @@ -205,8 +205,8 @@ func (sh *shell) newCreateForm(cmd *engine.Command, spec createDialogSpec) *crea

// namespaceField returns a field that offers the namespaces the window knows, kept current while
// the dialog is open.
func (f *createForm) namespaceField() *widget.SelectEntry {
e := widget.NewSelectEntry(slices.Clone(f.sh.namespaces))
func (f *createForm) namespaceField() *fieldSelectEntry {
e := newFieldSelectEntry(slices.Clone(f.sh.namespaces))
f.nsFields = append(f.nsFields, e)
return e
}
Expand Down Expand Up @@ -256,7 +256,7 @@ func (f *createForm) caption(text string) {
// addName adds the object's name.
func (f *createForm) addName(placeholder string) {
f.caption("NAME")
f.name = widget.NewEntry()
f.name = newFieldEntry()
f.name.SetPlaceHolder(placeholder)
f.name.OnSubmitted = func(string) { f.submit() }
f.content.Add(f.name)
Expand Down Expand Up @@ -427,9 +427,9 @@ func (f *createForm) loadNames(kind string, namespaces []string, done func([]mod
}

// offer sets the names a field offers, and remembers them (Fyne's SelectEntry does not say).
func (f *createForm) offer(e *widget.SelectEntry, options []string) {
func (f *createForm) offer(e *fieldSelectEntry, options []string) {
if f.offered == nil {
f.offered = map[*widget.SelectEntry][]string{}
f.offered = map[*fieldSelectEntry][]string{}
}
f.offered[e] = options
e.SetOptions(options)
Expand Down Expand Up @@ -568,7 +568,7 @@ func buildRoleForm(f *createForm, cluster bool) {
resourcesHint = "pods, /healthz"
}
rules := f.addRows("RULES", []string{"API groups", "Resources", "Verbs"}, "Add Rule", func() *editorRow {
groups, resources, verbs := widget.NewEntry(), widget.NewEntry(), widget.NewEntry()
groups, resources, verbs := newFieldEntry(), newFieldEntry(), newFieldEntry()
groups.SetPlaceHolder("core")
resources.SetPlaceHolder(resourcesHint)
verbs.SetPlaceHolder("get, list, watch")
Expand Down Expand Up @@ -613,7 +613,7 @@ func buildRoleForm(f *createForm, cluster bool) {
func buildBindingForm(f *createForm, cluster bool) {
var (
roleKind *selectField
roleName = widget.NewSelectEntry(nil)
roleName = newFieldSelectEntry(nil)
autoName string
accounts = map[string][]string{} // service account names by namespace, from the cache
roleSeq int
Expand Down Expand Up @@ -668,7 +668,7 @@ func buildBindingForm(f *createForm, cluster bool) {
}
subjects = f.addRows("SUBJECTS", []string{"Kind", "Name", "Namespace"}, "Add Subject", func() *editorRow {
kind := newSelectField([]string{subjectUser, subjectGroup, subjectServiceAccount}, nil)
name, ns := widget.NewSelectEntry(nil), f.namespaceField()
name, ns := newFieldSelectEntry(nil), f.namespaceField()
hint := "the binding's" // a role binding's service account defaults to its namespace
if cluster {
hint = "its namespace"
Expand Down Expand Up @@ -732,7 +732,7 @@ func buildQuotaForm(f *createForm) {
f.addName("compute")
f.addNamespace()
limits := f.addRows("HARD LIMITS", []string{"Resource", "Limit"}, "Add Limit", func() *editorRow {
res, qty := widget.NewSelectEntry(quotaResources), widget.NewEntry()
res, qty := newFieldSelectEntry(quotaResources), newFieldEntry()
res.SetPlaceHolder("pods")
qty.SetPlaceHolder("10")
return &editorRow{fields: []fyne.CanvasObject{res, qty}, value: func() (any, bool) {
Expand Down
15 changes: 12 additions & 3 deletions internal/ui/fyneui/createdialog_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func buildSecretForm(f *createForm) {
// newSecretDataRow is one data row: the key, and the value masked in a field that takes several
// lines, with the eye that shows it.
func newSecretDataRow(sh *shell) *editorRow {
key := widget.NewEntry()
key := newFieldEntry()
key.SetPlaceHolder("password")
value := newSecretEntry(sh)
value.SetMinRowsVisible(1)
Expand All @@ -94,7 +94,7 @@ func newSecretDataRow(sh *shell) *editorRow {

// newKeyValueRow is one label or annotation row.
func newKeyValueRow(keyHint, valueHint string) *editorRow {
key, value := widget.NewEntry(), widget.NewEntry()
key, value := newFieldEntry(), newFieldEntry()
key.SetPlaceHolder(keyHint)
value.SetPlaceHolder(valueHint)
return &editorRow{fields: []fyne.CanvasObject{key, value}, value: func() (any, bool) {
Expand All @@ -107,7 +107,7 @@ func newKeyValueRow(keyHint, valueHint string) *editorRow {
}

// rowKey is a row's key field.
func rowKey(r *editorRow) *widget.Entry { return r.fields[0].(*widget.Entry) }
func rowKey(r *editorRow) *fieldEntry { return r.fields[0].(*fieldEntry) }

// prefillKeys puts each of keys that no row has yet into the first empty row, else into a new one.
func prefillKeys(e *rowEditor, keys []string) {
Expand Down Expand Up @@ -147,6 +147,15 @@ func newSecretEntry(sh *shell) *secretEntry {
return e
}

// TypedKey implements fyne.Focusable: Escape goes on to the dialog's Cancel (escape.go).
func (e *secretEntry) TypedKey(ev *fyne.KeyEvent) {
if ev.Name == fyne.KeyEscape {
passEscape(e)
return
}
e.Entry.TypedKey(ev)
}

// TypedShortcut sends a copy or cut of the shown value through the shell's clipboard for Secret
// values; every other shortcut is the entry's.
func (e *secretEntry) TypedShortcut(s fyne.Shortcut) {
Expand Down
Loading
Loading