feat(ui): align settings spacing, fix label clipping, gate Save button
- Give the Queue selects the same default spacing as the Storage fields - Widen the settings caption column so "Default overlap policy" fits - Disable Save until a field differs from the saved config, re-enabling on change and re-disabling after a successful save Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+49
-4
@@ -17,7 +17,10 @@ import (
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
const settingsLabelWidth float32 = 140
|
||||
// settingsLabelWidth is wide enough to show the longest caption ("Default
|
||||
// overlap policy") in full; the captions truncate, so a narrower width would
|
||||
// clip it. All rows share this width so their value controls stay aligned.
|
||||
const settingsLabelWidth float32 = 180
|
||||
const settingsControlWidth float32 = 330
|
||||
const projectRepositoryURL = "https://gitea.mixdep.ru/mix/gosentry"
|
||||
|
||||
@@ -28,6 +31,10 @@ const settingsRowSpacing float32 = -6
|
||||
|
||||
func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
||||
store := svc.Store()
|
||||
// updateSaveState compares the form to the saved config and enables Save only
|
||||
// when something differs. It is defined below (once Save and every field
|
||||
// exist) but declared here so the field change handlers can reference it.
|
||||
var updateSaveState func()
|
||||
startOnLogin := widget.NewCheck("Start on login", nil)
|
||||
startOnLogin.SetChecked(store.Config.StartOnLogin)
|
||||
autostartStatus := widget.NewLabel("")
|
||||
@@ -42,39 +49,48 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
||||
startOnLogin.OnChanged = func(bool) {
|
||||
if startOnLogin.Checked != store.Config.StartOnLogin {
|
||||
autostartStatus.SetText("Pending: save settings to apply")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
refreshAutostartStatus()
|
||||
}
|
||||
updateSaveState()
|
||||
}
|
||||
refreshAutostartStatus()
|
||||
minimizeToTray := widget.NewCheck("Keep running in the system tray", nil)
|
||||
minimizeToTray.SetChecked(store.Config.KeepRunningInTray)
|
||||
minimizeToTray.OnChanged = func(bool) { updateSaveState() }
|
||||
notifications := widget.NewCheck("Show desktop notifications for failed jobs", nil)
|
||||
notifications.SetChecked(store.Config.NotifyOnFailure)
|
||||
notifications.OnChanged = func(bool) { updateSaveState() }
|
||||
executionModeSelect := widget.NewSelect(
|
||||
[]string{string(domain.ExecutionModeParallel), string(domain.ExecutionModeSequential)},
|
||||
nil,
|
||||
)
|
||||
executionModeSelect.SetSelected(string(store.Config.ExecutionMode))
|
||||
executionModeSelect.OnChanged = func(string) { updateSaveState() }
|
||||
overlapPolicySelect := widget.NewSelect(
|
||||
[]string{string(domain.OverlapPolicySkip), string(domain.OverlapPolicyQueue)},
|
||||
nil,
|
||||
)
|
||||
overlapPolicySelect.SetSelected(string(store.Config.OverlapPolicy))
|
||||
overlapPolicySelect.OnChanged = func(string) { updateSaveState() }
|
||||
jobsDir := widget.NewEntry()
|
||||
jobsDir.SetText(store.Config.JobsDir)
|
||||
jobsDir.OnChanged = func(string) { updateSaveState() }
|
||||
jobsDirBrowse := widget.NewButtonWithIcon("Browse", theme.FolderOpenIcon(), func() {
|
||||
chooseFolder(w, jobsDir)
|
||||
})
|
||||
logsDir := widget.NewEntry()
|
||||
logsDir.SetText(store.Config.LogsDir)
|
||||
logsDir.OnChanged = func(string) { updateSaveState() }
|
||||
logsDirBrowse := widget.NewButtonWithIcon("Browse", theme.FolderOpenIcon(), func() {
|
||||
chooseFolder(w, logsDir)
|
||||
})
|
||||
maxLogFiles := widget.NewEntry()
|
||||
maxLogFiles.SetText(strconv.Itoa(store.Config.MaxLogFiles))
|
||||
maxLogFiles.OnChanged = func(string) { updateSaveState() }
|
||||
maxLogAgeDays := widget.NewEntry()
|
||||
maxLogAgeDays.SetText(strconv.Itoa(store.Config.MaxLogAgeDays))
|
||||
maxLogAgeDays.OnChanged = func(string) { updateSaveState() }
|
||||
// Autostart status sits on its own row beneath the checkbox (rather than
|
||||
// beside it) so the Application section fits within a half-width column.
|
||||
// Truncating keeps a long status message from forcing the column wider.
|
||||
@@ -124,8 +140,33 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
||||
}
|
||||
refreshAutostartStatus()
|
||||
settingsStatus.SetText("Saved")
|
||||
// The form now matches the persisted config, so disable Save again.
|
||||
updateSaveState()
|
||||
})
|
||||
|
||||
// Save stays disabled until a field differs from the saved config, so the
|
||||
// button only invites a click when there is something to persist. The numeric
|
||||
// fields compare against their canonical string form; any unparsable text
|
||||
// counts as a change so the user can click Save and see the validation error.
|
||||
updateSaveState = func() {
|
||||
c := store.Config
|
||||
changed := startOnLogin.Checked != c.StartOnLogin ||
|
||||
minimizeToTray.Checked != c.KeepRunningInTray ||
|
||||
notifications.Checked != c.NotifyOnFailure ||
|
||||
executionModeSelect.Selected != string(c.ExecutionMode) ||
|
||||
overlapPolicySelect.Selected != string(c.OverlapPolicy) ||
|
||||
strings.TrimSpace(jobsDir.Text) != c.JobsDir ||
|
||||
strings.TrimSpace(logsDir.Text) != c.LogsDir ||
|
||||
strings.TrimSpace(maxLogFiles.Text) != strconv.Itoa(c.MaxLogFiles) ||
|
||||
strings.TrimSpace(maxLogAgeDays.Text) != strconv.Itoa(c.MaxLogAgeDays)
|
||||
if changed {
|
||||
saveSettings.Enable()
|
||||
} else {
|
||||
saveSettings.Disable()
|
||||
}
|
||||
}
|
||||
updateSaveState()
|
||||
|
||||
// The form is split into two columns so a wide window uses its horizontal
|
||||
// space instead of stretching into one tall strip. The left column holds the
|
||||
// toggles (Application, Queue); the right holds the editable Storage fields and
|
||||
@@ -140,7 +181,11 @@ func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
||||
settingsRow("Notifications", container.New(minWidthLayout{width: settingsControlWidth}, notifications)),
|
||||
),
|
||||
widget.NewSeparator(),
|
||||
settingsSection("Queue",
|
||||
// Queue holds the execution mode and overlap policy comboboxes. Like
|
||||
// Storage, it uses the default VBox spacing (not the condensed section
|
||||
// layout) so the comboboxes keep a visible gap between them.
|
||||
container.NewVBox(
|
||||
widget.NewLabelWithStyle("Queue", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||
settingsRow("Execution mode", container.New(minWidthLayout{width: settingsControlWidth}, executionModeSelect)),
|
||||
settingsRow("Default overlap policy", container.New(minWidthLayout{width: settingsControlWidth}, overlapPolicySelect)),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user