feat(ui): 条目管理增强 — 重命名/删除/重排序
每行右侧增加 ⋯ 按钮,弹出菜单支持重命名、删除、上移、下移。 - renameEntry / deleteEntry / moveEntry 三个 appState 方法 - rowActionMenu 弹出菜单 + renameDialog + confirmDeleteDialog - 删除自动注销快捷键,所有操作触发配置保存 - 保留顶栏 Add/Remove 全局按钮不变
This commit is contained in:
@@ -465,3 +465,41 @@ other = "Beim Schließen in den Tray minimieren"
|
|||||||
|
|
||||||
[hint_minimize_to_tray]
|
[hint_minimize_to_tray]
|
||||||
other = "Schließen des Fensters versteckt es im System-Tray statt das Programm zu beenden."
|
other = "Schließen des Fensters versteckt es im System-Tray statt das Programm zu beenden."
|
||||||
|
|
||||||
|
# --- Eintragsaktionen (umbenennen / löschen / verschieben) ---
|
||||||
|
|
||||||
|
[btn_more]
|
||||||
|
other = "⋯"
|
||||||
|
|
||||||
|
[btn_delete]
|
||||||
|
other = "Löschen"
|
||||||
|
|
||||||
|
[dialog_row_actions_title]
|
||||||
|
other = "Eintragsaktionen"
|
||||||
|
|
||||||
|
[action_rename]
|
||||||
|
other = "Umbenennen..."
|
||||||
|
|
||||||
|
[action_delete]
|
||||||
|
other = "Löschen..."
|
||||||
|
|
||||||
|
[action_move_up]
|
||||||
|
other = "Nach oben"
|
||||||
|
|
||||||
|
[action_move_down]
|
||||||
|
other = "Nach unten"
|
||||||
|
|
||||||
|
[dialog_rename_title]
|
||||||
|
other = "Eintrag umbenennen"
|
||||||
|
|
||||||
|
[label_entry_name]
|
||||||
|
other = "Neuer Name"
|
||||||
|
|
||||||
|
[msg_empty_name]
|
||||||
|
other = "Name darf nicht leer sein."
|
||||||
|
|
||||||
|
[dialog_confirm_delete_title]
|
||||||
|
other = "Löschen bestätigen"
|
||||||
|
|
||||||
|
[msg_confirm_delete]
|
||||||
|
other = "Eintrag \"%s\" wirklich löschen? Dies kann nicht rückgängig gemacht werden."
|
||||||
|
|||||||
@@ -467,3 +467,41 @@ other = "Minimize to tray when closing the window"
|
|||||||
|
|
||||||
[hint_minimize_to_tray]
|
[hint_minimize_to_tray]
|
||||||
other = "Closing the window hides it to the system tray instead of exiting."
|
other = "Closing the window hides it to the system tray instead of exiting."
|
||||||
|
|
||||||
|
# --- Entry actions (rename / delete / reorder) ---
|
||||||
|
|
||||||
|
[btn_more]
|
||||||
|
other = "⋯"
|
||||||
|
|
||||||
|
[btn_delete]
|
||||||
|
other = "Delete"
|
||||||
|
|
||||||
|
[dialog_row_actions_title]
|
||||||
|
other = "Entry actions"
|
||||||
|
|
||||||
|
[action_rename]
|
||||||
|
other = "Rename..."
|
||||||
|
|
||||||
|
[action_delete]
|
||||||
|
other = "Delete..."
|
||||||
|
|
||||||
|
[action_move_up]
|
||||||
|
other = "Move up"
|
||||||
|
|
||||||
|
[action_move_down]
|
||||||
|
other = "Move down"
|
||||||
|
|
||||||
|
[dialog_rename_title]
|
||||||
|
other = "Rename entry"
|
||||||
|
|
||||||
|
[label_entry_name]
|
||||||
|
other = "New name"
|
||||||
|
|
||||||
|
[msg_empty_name]
|
||||||
|
other = "Name cannot be empty."
|
||||||
|
|
||||||
|
[dialog_confirm_delete_title]
|
||||||
|
other = "Confirm delete"
|
||||||
|
|
||||||
|
[msg_confirm_delete]
|
||||||
|
other = "Delete the entry \"%s\"? This cannot be undone."
|
||||||
|
|||||||
@@ -465,3 +465,41 @@ other = "关闭窗口时最小化到托盘"
|
|||||||
|
|
||||||
[hint_minimize_to_tray]
|
[hint_minimize_to_tray]
|
||||||
other = "勾选后点击窗口关闭按钮将隐藏到系统托盘而非退出程序。"
|
other = "勾选后点击窗口关闭按钮将隐藏到系统托盘而非退出程序。"
|
||||||
|
|
||||||
|
# --- 条目操作(重命名 / 删除 / 重排序) ---
|
||||||
|
|
||||||
|
[btn_more]
|
||||||
|
other = "⋯"
|
||||||
|
|
||||||
|
[btn_delete]
|
||||||
|
other = "删除"
|
||||||
|
|
||||||
|
[dialog_row_actions_title]
|
||||||
|
other = "条目操作"
|
||||||
|
|
||||||
|
[action_rename]
|
||||||
|
other = "重命名..."
|
||||||
|
|
||||||
|
[action_delete]
|
||||||
|
other = "删除..."
|
||||||
|
|
||||||
|
[action_move_up]
|
||||||
|
other = "上移"
|
||||||
|
|
||||||
|
[action_move_down]
|
||||||
|
other = "下移"
|
||||||
|
|
||||||
|
[dialog_rename_title]
|
||||||
|
other = "重命名条目"
|
||||||
|
|
||||||
|
[label_entry_name]
|
||||||
|
other = "新名称"
|
||||||
|
|
||||||
|
[msg_empty_name]
|
||||||
|
other = "名称不能为空。"
|
||||||
|
|
||||||
|
[dialog_confirm_delete_title]
|
||||||
|
other = "确认删除"
|
||||||
|
|
||||||
|
[msg_confirm_delete]
|
||||||
|
other = "确定删除条目 \"%s\"?此操作不可撤销。"
|
||||||
|
|||||||
+128
-1
@@ -73,6 +73,8 @@ type entry struct {
|
|||||||
hotkeyBtn widget.Clickable
|
hotkeyBtn widget.Clickable
|
||||||
// copyBtn copies the current code to the clipboard.
|
// copyBtn copies the current code to the clipboard.
|
||||||
copyBtn widget.Clickable
|
copyBtn widget.Clickable
|
||||||
|
// moreBtn opens the per-row ⋯ popup (rename / delete / move).
|
||||||
|
moreBtn widget.Clickable
|
||||||
}
|
}
|
||||||
|
|
||||||
type appState struct {
|
type appState struct {
|
||||||
@@ -97,6 +99,15 @@ type appState struct {
|
|||||||
prefsDialog *preferencesDialog
|
prefsDialog *preferencesDialog
|
||||||
aboutDialog *aboutDialog
|
aboutDialog *aboutDialog
|
||||||
|
|
||||||
|
// Per-row action popup state. rowMenu owns the visible popup;
|
||||||
|
// renameDlg / confirmDelDlg are the follow-up modals it spawns.
|
||||||
|
// rowTargetIdx is the entry index the popup applies to; it is
|
||||||
|
// re-resolved on each frame in case the entries slice mutated.
|
||||||
|
rowMenu *rowActionMenu
|
||||||
|
renameDlg *renameDialog
|
||||||
|
confirmDelDlg *confirmDeleteDialog
|
||||||
|
rowTargetIdx int
|
||||||
|
|
||||||
store *store
|
store *store
|
||||||
saveErr string // surfaced in the top bar
|
saveErr string // surfaced in the top bar
|
||||||
|
|
||||||
@@ -298,6 +309,57 @@ func (st *appState) mergeImportedConfig(cfg *config.Config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// renameEntry sets a new display name for the entry at idx. Empty names
|
||||||
|
// are rejected by the caller; here we trust the input and only guard
|
||||||
|
// the index. Pushes a save on success.
|
||||||
|
func (st *appState) renameEntry(idx int, name string) {
|
||||||
|
st.mu.Lock()
|
||||||
|
if idx < 0 || idx >= len(st.entries) {
|
||||||
|
st.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st.entries[idx].Name = name
|
||||||
|
st.mu.Unlock()
|
||||||
|
st.store.Push()
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteEntry removes the entry at idx, unregistering its hotkey if
|
||||||
|
// any. Bounds-checked; out-of-range indexes are a silent no-op so a
|
||||||
|
// stale popup that survived a concurrent mutation cannot panic.
|
||||||
|
func (st *appState) deleteEntry(idx int) {
|
||||||
|
const fn = "internal.ui.appState.deleteEntry"
|
||||||
|
st.mu.Lock()
|
||||||
|
if idx < 0 || idx >= len(st.entries) {
|
||||||
|
st.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
en := st.entries[idx]
|
||||||
|
if en.hotkeyID != 0 && st.hkMgr != nil {
|
||||||
|
if err := st.hkMgr.Unregister(en.hotkeyID); err != nil {
|
||||||
|
global.Log.WithField("func", fn).WithError(err).
|
||||||
|
Warn("unregister hotkey during delete failed")
|
||||||
|
}
|
||||||
|
en.hotkeyID = 0
|
||||||
|
}
|
||||||
|
st.entries = append(st.entries[:idx], st.entries[idx+1:]...)
|
||||||
|
st.mu.Unlock()
|
||||||
|
st.store.Push()
|
||||||
|
}
|
||||||
|
|
||||||
|
// moveEntry swaps the entry at idx with its neighbour delta steps away
|
||||||
|
// (typically ±1). Bounds-checked. Pushes a save on a successful swap.
|
||||||
|
func (st *appState) moveEntry(idx, delta int) {
|
||||||
|
st.mu.Lock()
|
||||||
|
j := idx + delta
|
||||||
|
if idx < 0 || idx >= len(st.entries) || j < 0 || j >= len(st.entries) {
|
||||||
|
st.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st.entries[idx], st.entries[j] = st.entries[j], st.entries[idx]
|
||||||
|
st.mu.Unlock()
|
||||||
|
st.store.Push()
|
||||||
|
}
|
||||||
|
|
||||||
func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Window) layout.Dimensions {
|
func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Window) layout.Dimensions {
|
||||||
// Auto-lock check runs before any input is dispatched so a user who
|
// Auto-lock check runs before any input is dispatched so a user who
|
||||||
// returns mid-frame still has to type the passphrase before they can
|
// returns mid-frame still has to type the passphrase before they can
|
||||||
@@ -345,7 +407,8 @@ func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Wind
|
|||||||
var tradesTarget *entry
|
var tradesTarget *entry
|
||||||
var hotkeyTarget *entry
|
var hotkeyTarget *entry
|
||||||
var copyTarget *entry
|
var copyTarget *entry
|
||||||
for _, en := range st.entries {
|
moreTargetIdx := -1
|
||||||
|
for i, en := range st.entries {
|
||||||
if en.Auth.Name() == "steam" {
|
if en.Auth.Name() == "steam" {
|
||||||
if en.tradesBtn.Clicked(gtx) {
|
if en.tradesBtn.Clicked(gtx) {
|
||||||
tradesTarget = en
|
tradesTarget = en
|
||||||
@@ -360,6 +423,10 @@ func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Wind
|
|||||||
copyTarget = en
|
copyTarget = en
|
||||||
st.lastActivity = time.Now()
|
st.lastActivity = time.Now()
|
||||||
}
|
}
|
||||||
|
if en.moreBtn.Clicked(gtx) {
|
||||||
|
moreTargetIdx = i
|
||||||
|
st.lastActivity = time.Now()
|
||||||
|
}
|
||||||
if en.Auth.Name() == "hotp" {
|
if en.Auth.Name() == "hotp" {
|
||||||
if en.click.Clicked(gtx) {
|
if en.click.Clicked(gtx) {
|
||||||
if code, err := en.Auth.CurrentCode(); err == nil {
|
if code, err := en.Auth.CurrentCode(); err == nil {
|
||||||
@@ -393,6 +460,13 @@ func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Wind
|
|||||||
if copyTarget != nil {
|
if copyTarget != nil {
|
||||||
st.copyCodeToClipboard(copyTarget, w)
|
st.copyCodeToClipboard(copyTarget, w)
|
||||||
}
|
}
|
||||||
|
if moreTargetIdx >= 0 {
|
||||||
|
st.mu.Lock()
|
||||||
|
total := len(st.entries)
|
||||||
|
st.mu.Unlock()
|
||||||
|
st.rowTargetIdx = moreTargetIdx
|
||||||
|
st.rowMenu = newRowActionMenu(moreTargetIdx, total)
|
||||||
|
}
|
||||||
|
|
||||||
// Password retry / first-decrypt loop.
|
// Password retry / first-decrypt loop.
|
||||||
if st.pwDialog != nil {
|
if st.pwDialog != nil {
|
||||||
@@ -558,6 +632,55 @@ func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Wind
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per-row action popup. Must be resolved before the entry list
|
||||||
|
// renders so the popup overlays the main UI without interference.
|
||||||
|
if st.rowMenu != nil {
|
||||||
|
action, closed := st.rowMenu.Pick(gtx)
|
||||||
|
if closed {
|
||||||
|
idx := st.rowTargetIdx
|
||||||
|
st.rowMenu = nil
|
||||||
|
switch action {
|
||||||
|
case rowActionRename:
|
||||||
|
st.mu.Lock()
|
||||||
|
if idx >= 0 && idx < len(st.entries) {
|
||||||
|
st.renameDlg = newRenameDialog(st.entries[idx].Name)
|
||||||
|
}
|
||||||
|
st.mu.Unlock()
|
||||||
|
case rowActionDelete:
|
||||||
|
st.mu.Lock()
|
||||||
|
if idx >= 0 && idx < len(st.entries) {
|
||||||
|
st.confirmDelDlg = newConfirmDeleteDialog(st.entries[idx].Name)
|
||||||
|
}
|
||||||
|
st.mu.Unlock()
|
||||||
|
case rowActionMoveUp:
|
||||||
|
st.moveEntry(idx, -1)
|
||||||
|
case rowActionMoveDown:
|
||||||
|
st.moveEntry(idx, +1)
|
||||||
|
}
|
||||||
|
w.Invalidate()
|
||||||
|
} else {
|
||||||
|
return st.rowMenu.Layout(gtx, th)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if st.renameDlg != nil {
|
||||||
|
return st.renameDlg.Layout(gtx, th, func(name string, cancel bool) {
|
||||||
|
if !cancel {
|
||||||
|
st.renameEntry(st.rowTargetIdx, name)
|
||||||
|
}
|
||||||
|
st.renameDlg = nil
|
||||||
|
w.Invalidate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if st.confirmDelDlg != nil {
|
||||||
|
return st.confirmDelDlg.Layout(gtx, th, func(confirmed bool) {
|
||||||
|
if confirmed {
|
||||||
|
st.deleteEntry(st.rowTargetIdx)
|
||||||
|
}
|
||||||
|
st.confirmDelDlg = nil
|
||||||
|
w.Invalidate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if st.tradesDialog != nil {
|
if st.tradesDialog != nil {
|
||||||
return st.tradesDialog.Layout(gtx, th)
|
return st.tradesDialog.Layout(gtx, th)
|
||||||
}
|
}
|
||||||
@@ -647,6 +770,10 @@ func entryRow(gtx layout.Context, th *material.Theme, en *entry) layout.Dimensio
|
|||||||
return layout.Inset{Right: unit.Dp(8)}.Layout(gtx,
|
return layout.Inset{Right: unit.Dp(8)}.Layout(gtx,
|
||||||
material.Button(th, &en.copyBtn, i18n.T("btn_copy")).Layout)
|
material.Button(th, &en.copyBtn, i18n.T("btn_copy")).Layout)
|
||||||
}),
|
}),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Inset{Right: unit.Dp(8)}.Layout(gtx,
|
||||||
|
material.Button(th, &en.moreBtn, i18n.T("btn_more")).Layout)
|
||||||
|
}),
|
||||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
lbl := material.H6(th, en.Code)
|
lbl := material.H6(th, en.Code)
|
||||||
lbl.Color = activePalette.RingFg
|
lbl.Color = activePalette.RingFg
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gioui.org/layout"
|
||||||
|
"gioui.org/widget"
|
||||||
|
"gioui.org/widget/material"
|
||||||
|
|
||||||
|
"git.wxccs.org/iceking2nd/winauth-go/internal/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// confirmDeleteDialog asks the user to confirm deletion of a specific
|
||||||
|
// entry. The dialog displays the target name verbatim in the prompt so
|
||||||
|
// the user can sanity-check what they are about to lose.
|
||||||
|
type confirmDeleteDialog struct {
|
||||||
|
name string
|
||||||
|
okBtn widget.Clickable
|
||||||
|
cancelBtn widget.Clickable
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfirmDeleteDialog(name string) *confirmDeleteDialog {
|
||||||
|
return &confirmDeleteDialog{name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *confirmDeleteDialog) Layout(
|
||||||
|
gtx layout.Context, th *material.Theme,
|
||||||
|
onDone func(confirmed bool),
|
||||||
|
) layout.Dimensions {
|
||||||
|
if d.cancelBtn.Clicked(gtx) {
|
||||||
|
onDone(false)
|
||||||
|
return layout.Dimensions{Size: gtx.Constraints.Max}
|
||||||
|
}
|
||||||
|
if d.okBtn.Clicked(gtx) {
|
||||||
|
onDone(true)
|
||||||
|
return layout.Dimensions{Size: gtx.Constraints.Max}
|
||||||
|
}
|
||||||
|
|
||||||
|
body := func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(material.Body1(th,
|
||||||
|
fmt.Sprintf(i18n.T("msg_confirm_delete"), d.name)).Layout),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return modalCard(gtx, th, i18n.T("dialog_confirm_delete_title"),
|
||||||
|
i18n.T("btn_delete"), i18n.T("btn_cancel"),
|
||||||
|
&d.okBtn, &d.cancelBtn, body)
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gioui.org/layout"
|
||||||
|
"gioui.org/widget"
|
||||||
|
"gioui.org/widget/material"
|
||||||
|
|
||||||
|
"git.wxccs.org/iceking2nd/winauth-go/internal/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
type renameDialog struct {
|
||||||
|
ed widget.Editor
|
||||||
|
okBtn widget.Clickable
|
||||||
|
cancel widget.Clickable
|
||||||
|
errMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRenameDialog(currentName string) *renameDialog {
|
||||||
|
d := &renameDialog{}
|
||||||
|
d.ed.SingleLine = true
|
||||||
|
d.ed.SetText(currentName)
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *renameDialog) Layout(
|
||||||
|
gtx layout.Context, th *material.Theme,
|
||||||
|
onDone func(string, bool),
|
||||||
|
) layout.Dimensions {
|
||||||
|
if d.cancel.Clicked(gtx) {
|
||||||
|
onDone("", true)
|
||||||
|
return layout.Dimensions{Size: gtx.Constraints.Max}
|
||||||
|
}
|
||||||
|
if d.okBtn.Clicked(gtx) {
|
||||||
|
name := strings.TrimSpace(d.ed.Text())
|
||||||
|
if name == "" {
|
||||||
|
d.errMsg = i18n.T("msg_empty_name")
|
||||||
|
} else {
|
||||||
|
onDone(name, false)
|
||||||
|
return layout.Dimensions{Size: gtx.Constraints.Max}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body := func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(labeledEditor(th, i18n.T("label_entry_name"), &d.ed, "")),
|
||||||
|
layout.Rigid(errorLabel(th, d.errMsg)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return modalCard(gtx, th, i18n.T("dialog_rename_title"),
|
||||||
|
i18n.T("btn_ok"), i18n.T("btn_cancel"),
|
||||||
|
&d.okBtn, &d.cancel, body)
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gioui.org/layout"
|
||||||
|
"gioui.org/unit"
|
||||||
|
"gioui.org/widget"
|
||||||
|
"gioui.org/widget/material"
|
||||||
|
|
||||||
|
"git.wxccs.org/iceking2nd/winauth-go/internal/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rowAction is what the per-row ⋯ popup returns to the caller.
|
||||||
|
type rowAction int
|
||||||
|
|
||||||
|
const (
|
||||||
|
rowActionNone rowAction = iota
|
||||||
|
rowActionRename
|
||||||
|
rowActionDelete
|
||||||
|
rowActionMoveUp
|
||||||
|
rowActionMoveDown
|
||||||
|
)
|
||||||
|
|
||||||
|
// rowActionMenu is the small popup opened by the ⋯ button on each entry
|
||||||
|
// row. It carries the target entry index so the caller knows which row
|
||||||
|
// the chosen action applies to. moveUp/moveDown are disabled (rendered
|
||||||
|
// but inert) when the index is at a boundary.
|
||||||
|
type rowActionMenu struct {
|
||||||
|
idx int
|
||||||
|
canMoveUp bool
|
||||||
|
canMoveDown bool
|
||||||
|
|
||||||
|
renameBtn widget.Clickable
|
||||||
|
deleteBtn widget.Clickable
|
||||||
|
moveUpBtn widget.Clickable
|
||||||
|
moveDownBtn widget.Clickable
|
||||||
|
cancelBtn widget.Clickable
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRowActionMenu(idx, total int) *rowActionMenu {
|
||||||
|
return &rowActionMenu{
|
||||||
|
idx: idx,
|
||||||
|
canMoveUp: idx > 0,
|
||||||
|
canMoveDown: idx < total-1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick reports the user's choice and whether the menu should close.
|
||||||
|
func (m *rowActionMenu) Pick(gtx layout.Context) (rowAction, bool) {
|
||||||
|
switch {
|
||||||
|
case m.renameBtn.Clicked(gtx):
|
||||||
|
return rowActionRename, true
|
||||||
|
case m.deleteBtn.Clicked(gtx):
|
||||||
|
return rowActionDelete, true
|
||||||
|
case m.moveUpBtn.Clicked(gtx):
|
||||||
|
if !m.canMoveUp {
|
||||||
|
return rowActionNone, false
|
||||||
|
}
|
||||||
|
return rowActionMoveUp, true
|
||||||
|
case m.moveDownBtn.Clicked(gtx):
|
||||||
|
if !m.canMoveDown {
|
||||||
|
return rowActionNone, false
|
||||||
|
}
|
||||||
|
return rowActionMoveDown, true
|
||||||
|
case m.cancelBtn.Clicked(gtx):
|
||||||
|
return rowActionNone, true
|
||||||
|
}
|
||||||
|
return rowActionNone, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *rowActionMenu) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
|
||||||
|
fillBackground(gtx, activePalette.ScrimBg)
|
||||||
|
|
||||||
|
row := func(btn *widget.Clickable, label string, enabled bool) layout.FlexChild {
|
||||||
|
return layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Inset{Top: unit.Dp(4), Bottom: unit.Dp(4)}.Layout(gtx,
|
||||||
|
func(gtx layout.Context) layout.Dimensions {
|
||||||
|
gtx.Constraints.Min.X = gtx.Constraints.Max.X
|
||||||
|
b := material.Button(th, btn, label)
|
||||||
|
if !enabled {
|
||||||
|
// Visually dim the disabled state; Pick also
|
||||||
|
// refuses to dispatch the action so clicks
|
||||||
|
// are inert beyond the colour change.
|
||||||
|
b.Background = activePalette.RingBg
|
||||||
|
b.Color = activePalette.MutedFg
|
||||||
|
}
|
||||||
|
return b.Layout(gtx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
gtx.Constraints.Max.X = gtx.Dp(260)
|
||||||
|
return widget.Border{
|
||||||
|
Color: activePalette.DialogBorder,
|
||||||
|
CornerRadius: unit.Dp(4),
|
||||||
|
Width: unit.Dp(1),
|
||||||
|
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
fillBackground(gtx, activePalette.DialogBg)
|
||||||
|
return layout.UniformInset(unit.Dp(16)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
|
layout.Rigid(material.H6(th, i18n.T("dialog_row_actions_title")).Layout),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout),
|
||||||
|
row(&m.renameBtn, i18n.T("action_rename"), true),
|
||||||
|
row(&m.deleteBtn, i18n.T("action_delete"), true),
|
||||||
|
row(&m.moveUpBtn, i18n.T("action_move_up"), m.canMoveUp),
|
||||||
|
row(&m.moveDownBtn, i18n.T("action_move_down"), m.canMoveDown),
|
||||||
|
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||||
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||||
|
return layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceStart}.Layout(gtx,
|
||||||
|
layout.Rigid(material.Button(th, &m.cancelBtn, i18n.T("btn_cancel")).Layout),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user