T4.5: Extract settings_view.go
Move settingsView and all its helpers (fyneVersion, mustParseURL, chooseFolder, settingsRow, settingsRowWithStatus) plus their constants out of mainwindow.go into the new settings_view.go. Save/autostart errors were already surfaced to the status label; no logic change. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -272,7 +272,7 @@ Track progress here. Mark tasks complete as they land and pass review.
|
|||||||
- [x] T4.2 — Extract `jobs_view.go`
|
- [x] T4.2 — Extract `jobs_view.go`
|
||||||
- [x] T4.3 — Extract `job_dialog.go`
|
- [x] T4.3 — Extract `job_dialog.go`
|
||||||
- [x] T4.4 — Extract `history_view.go`
|
- [x] T4.4 — Extract `history_view.go`
|
||||||
- [ ] T4.5 — Extract `settings_view.go`
|
- [x] T4.5 — Extract `settings_view.go`
|
||||||
- [ ] T4.6 — Extract `tray.go`, `singleinstance.go`, `layout.go`
|
- [ ] T4.6 — Extract `tray.go`, `singleinstance.go`, `layout.go`
|
||||||
- [ ] T4.7 — Confirm app.go is gone; smoke test both platforms
|
- [ ] T4.7 — Confirm app.go is gone; smoke test both platforms
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,20 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
|
||||||
"runtime"
|
|
||||||
"runtime/debug"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.mixdep.ru/mix/gosentry/assets"
|
"gitea.mixdep.ru/mix/gosentry/assets"
|
||||||
"gitea.mixdep.ru/mix/gosentry/src/app"
|
"gitea.mixdep.ru/mix/gosentry/src/app"
|
||||||
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
||||||
"gitea.mixdep.ru/mix/gosentry/src/platform/autostart"
|
|
||||||
"gitea.mixdep.ru/mix/gosentry/src/platform/desktop"
|
"gitea.mixdep.ru/mix/gosentry/src/platform/desktop"
|
||||||
"gitea.mixdep.ru/mix/gosentry/src/scheduler"
|
"gitea.mixdep.ru/mix/gosentry/src/scheduler"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"fyne.io/fyne/v2/dialog"
|
|
||||||
"fyne.io/fyne/v2/theme"
|
"fyne.io/fyne/v2/theme"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
)
|
)
|
||||||
|
|
||||||
const settingsLabelWidth float32 = 140
|
|
||||||
const settingsControlWidth float32 = 330
|
|
||||||
const settingsStatusWidth float32 = 280
|
|
||||||
const projectRepositoryURL = "https://gitea.mixdep.ru/mix/gosentry"
|
|
||||||
|
|
||||||
// The UI package aliases domain types to keep widget callbacks short. The actual
|
// The UI package aliases domain types to keep widget callbacks short. The actual
|
||||||
// durable model still lives in src/domain, so UI code does not define a second
|
// durable model still lives in src/domain, so UI code does not define a second
|
||||||
// copy of the scheduler data.
|
// copy of the scheduler data.
|
||||||
@@ -135,165 +123,3 @@ func (l minWidthLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
|
|||||||
object.Resize(size)
|
object.Resize(size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
|
||||||
store := svc.Store()
|
|
||||||
startOnLogin := widget.NewCheck("Start on login", nil)
|
|
||||||
startOnLogin.SetChecked(store.Config.StartOnLogin)
|
|
||||||
autostartStatus := widget.NewLabel("")
|
|
||||||
refreshAutostartStatus := func() {
|
|
||||||
ok, message := autostart.AutostartStatus(store.Config.StartOnLogin, store.Paths.ExecutablePath)
|
|
||||||
if ok {
|
|
||||||
autostartStatus.SetText("OK: " + message)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
autostartStatus.SetText("Problem: " + message)
|
|
||||||
}
|
|
||||||
startOnLogin.OnChanged = func(bool) {
|
|
||||||
if startOnLogin.Checked != store.Config.StartOnLogin {
|
|
||||||
autostartStatus.SetText("Pending: save settings to apply")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
refreshAutostartStatus()
|
|
||||||
}
|
|
||||||
refreshAutostartStatus()
|
|
||||||
minimizeToTray := widget.NewCheck("Keep running in the system tray", nil)
|
|
||||||
minimizeToTray.SetChecked(store.Config.KeepRunningInTray)
|
|
||||||
notifications := widget.NewCheck("Show desktop notifications for failed jobs", nil)
|
|
||||||
notifications.SetChecked(store.Config.NotifyOnFailure)
|
|
||||||
jobsDir := widget.NewEntry()
|
|
||||||
jobsDir.SetText(store.Config.JobsDir)
|
|
||||||
jobsDirBrowse := widget.NewButtonWithIcon("Browse", theme.FolderOpenIcon(), func() {
|
|
||||||
chooseFolder(w, jobsDir)
|
|
||||||
})
|
|
||||||
logsDir := widget.NewEntry()
|
|
||||||
logsDir.SetText(store.Config.LogsDir)
|
|
||||||
logsDirBrowse := widget.NewButtonWithIcon("Browse", theme.FolderOpenIcon(), func() {
|
|
||||||
chooseFolder(w, logsDir)
|
|
||||||
})
|
|
||||||
maxLogFiles := widget.NewEntry()
|
|
||||||
maxLogFiles.SetText(strconv.Itoa(store.Config.MaxLogFiles))
|
|
||||||
maxLogAgeDays := widget.NewEntry()
|
|
||||||
maxLogAgeDays.SetText(strconv.Itoa(store.Config.MaxLogAgeDays))
|
|
||||||
settingsStatus := widget.NewLabel("")
|
|
||||||
|
|
||||||
saveSettings := widget.NewButtonWithIcon("Save settings", theme.DocumentSaveIcon(), func() {
|
|
||||||
files, err := strconv.Atoi(strings.TrimSpace(maxLogFiles.Text))
|
|
||||||
if err != nil || files <= 0 {
|
|
||||||
settingsStatus.SetText("Max log files must be a positive number")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
days, err := strconv.Atoi(strings.TrimSpace(maxLogAgeDays.Text))
|
|
||||||
if err != nil || days <= 0 {
|
|
||||||
settingsStatus.SetText("Max log age days must be a positive number")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(jobsDir.Text) == "" {
|
|
||||||
settingsStatus.SetText("Jobs directory is required")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(logsDir.Text) == "" {
|
|
||||||
settingsStatus.SetText("Logs directory is required")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Build the new config from the form and hand it to the Service, which
|
|
||||||
// validates it, persists config and jobs to the (possibly new) directory,
|
|
||||||
// and runs log cleanup so tightened retention limits take effect at once.
|
|
||||||
config := store.Config
|
|
||||||
config.JobsDir = strings.TrimSpace(jobsDir.Text)
|
|
||||||
config.LogsDir = strings.TrimSpace(logsDir.Text)
|
|
||||||
config.MaxLogFiles = files
|
|
||||||
config.MaxLogAgeDays = days
|
|
||||||
config.StartOnLogin = startOnLogin.Checked
|
|
||||||
config.KeepRunningInTray = minimizeToTray.Checked
|
|
||||||
config.NotifyOnFailure = notifications.Checked
|
|
||||||
if err := svc.UpdateSettings(config); err != nil {
|
|
||||||
settingsStatus.SetText("Save failed: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Autostart is platform integration the Service leaves to the caller (until
|
|
||||||
// T5.2 introduces an injectable autostart.Manager), so apply it here.
|
|
||||||
if err := autostart.SetAutostart(store.Config.StartOnLogin, store.Paths.ExecutablePath, store.Paths.DesktopIcon); err != nil {
|
|
||||||
refreshAutostartStatus()
|
|
||||||
settingsStatus.SetText("Saved, autostart failed: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
refreshAutostartStatus()
|
|
||||||
settingsStatus.SetText("Saved")
|
|
||||||
})
|
|
||||||
|
|
||||||
return container.NewPadded(container.NewVBox(
|
|
||||||
widget.NewLabelWithStyle("Application", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
|
||||||
settingsRowWithStatus("Autostart", startOnLogin, autostartStatus),
|
|
||||||
settingsRow("Tray", container.New(minWidthLayout{width: settingsControlWidth}, minimizeToTray)),
|
|
||||||
settingsRow("Notifications", container.New(minWidthLayout{width: settingsControlWidth}, notifications)),
|
|
||||||
widget.NewSeparator(),
|
|
||||||
widget.NewLabelWithStyle("Storage", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
|
||||||
settingsRow("Config YAML", widget.NewLabel(store.Paths.ConfigPath)),
|
|
||||||
settingsRow("Jobs directory", container.NewBorder(nil, nil, nil, jobsDirBrowse, jobsDir)),
|
|
||||||
settingsRow("Logs directory", container.NewBorder(nil, nil, nil, logsDirBrowse, logsDir)),
|
|
||||||
settingsRow("Max log files", maxLogFiles),
|
|
||||||
settingsRow("Max log age days", maxLogAgeDays),
|
|
||||||
saveSettings,
|
|
||||||
settingsStatus,
|
|
||||||
widget.NewSeparator(),
|
|
||||||
widget.NewLabelWithStyle("About", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
|
||||||
settingsRow("GoSentry", widget.NewLabel(app.Version)),
|
|
||||||
settingsRow("Go", widget.NewLabel(runtime.Version())),
|
|
||||||
settingsRow("Fyne", widget.NewLabel(fyneVersion())),
|
|
||||||
settingsRow("Repository", widget.NewHyperlink(projectRepositoryURL, mustParseURL(projectRepositoryURL))),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
func fyneVersion() string {
|
|
||||||
info, ok := debug.ReadBuildInfo()
|
|
||||||
if !ok {
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
for _, dependency := range info.Deps {
|
|
||||||
if dependency.Path == "fyne.io/fyne/v2" {
|
|
||||||
if dependency.Replace != nil && dependency.Replace.Version != "" {
|
|
||||||
return dependency.Replace.Version
|
|
||||||
}
|
|
||||||
if dependency.Version != "" {
|
|
||||||
return dependency.Version
|
|
||||||
}
|
|
||||||
return "local"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustParseURL(raw string) *url.URL {
|
|
||||||
parsed, err := url.Parse(raw)
|
|
||||||
if err != nil {
|
|
||||||
return &url.URL{}
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
func chooseFolder(w fyne.Window, target *widget.Entry) {
|
|
||||||
folderDialog := dialog.NewFolderOpen(func(uri fyne.ListableURI, err error) {
|
|
||||||
if err != nil || uri == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
target.SetText(uri.Path())
|
|
||||||
}, w)
|
|
||||||
// The default folder picker can be cramped on Windows. A larger size makes
|
|
||||||
// long paths readable and avoids forcing the user to resize it every time.
|
|
||||||
folderDialog.Resize(fyne.NewSize(900, 640))
|
|
||||||
folderDialog.Show()
|
|
||||||
}
|
|
||||||
|
|
||||||
func settingsRow(label string, value fyne.CanvasObject) fyne.CanvasObject {
|
|
||||||
caption := widget.NewLabelWithStyle(label, fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
|
||||||
caption.Wrapping = fyne.TextTruncate
|
|
||||||
captionBox := container.New(minWidthLayout{width: settingsLabelWidth}, caption)
|
|
||||||
return container.NewBorder(nil, nil, captionBox, nil, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func settingsRowWithStatus(label string, value fyne.CanvasObject, status fyne.CanvasObject) fyne.CanvasObject {
|
|
||||||
valueBox := container.New(minWidthLayout{width: settingsControlWidth}, value)
|
|
||||||
statusBox := container.New(minWidthLayout{width: settingsStatusWidth}, status)
|
|
||||||
return settingsRow(label, container.NewBorder(nil, nil, valueBox, nil, statusBox))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitea.mixdep.ru/mix/gosentry/src/app"
|
||||||
|
"gitea.mixdep.ru/mix/gosentry/src/platform/autostart"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/dialog"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
)
|
||||||
|
|
||||||
|
const settingsLabelWidth float32 = 140
|
||||||
|
const settingsControlWidth float32 = 330
|
||||||
|
const settingsStatusWidth float32 = 280
|
||||||
|
const projectRepositoryURL = "https://gitea.mixdep.ru/mix/gosentry"
|
||||||
|
|
||||||
|
func settingsView(w fyne.Window, svc *app.Service) fyne.CanvasObject {
|
||||||
|
store := svc.Store()
|
||||||
|
startOnLogin := widget.NewCheck("Start on login", nil)
|
||||||
|
startOnLogin.SetChecked(store.Config.StartOnLogin)
|
||||||
|
autostartStatus := widget.NewLabel("")
|
||||||
|
refreshAutostartStatus := func() {
|
||||||
|
ok, message := autostart.AutostartStatus(store.Config.StartOnLogin, store.Paths.ExecutablePath)
|
||||||
|
if ok {
|
||||||
|
autostartStatus.SetText("OK: " + message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
autostartStatus.SetText("Problem: " + message)
|
||||||
|
}
|
||||||
|
startOnLogin.OnChanged = func(bool) {
|
||||||
|
if startOnLogin.Checked != store.Config.StartOnLogin {
|
||||||
|
autostartStatus.SetText("Pending: save settings to apply")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
refreshAutostartStatus()
|
||||||
|
}
|
||||||
|
refreshAutostartStatus()
|
||||||
|
minimizeToTray := widget.NewCheck("Keep running in the system tray", nil)
|
||||||
|
minimizeToTray.SetChecked(store.Config.KeepRunningInTray)
|
||||||
|
notifications := widget.NewCheck("Show desktop notifications for failed jobs", nil)
|
||||||
|
notifications.SetChecked(store.Config.NotifyOnFailure)
|
||||||
|
jobsDir := widget.NewEntry()
|
||||||
|
jobsDir.SetText(store.Config.JobsDir)
|
||||||
|
jobsDirBrowse := widget.NewButtonWithIcon("Browse", theme.FolderOpenIcon(), func() {
|
||||||
|
chooseFolder(w, jobsDir)
|
||||||
|
})
|
||||||
|
logsDir := widget.NewEntry()
|
||||||
|
logsDir.SetText(store.Config.LogsDir)
|
||||||
|
logsDirBrowse := widget.NewButtonWithIcon("Browse", theme.FolderOpenIcon(), func() {
|
||||||
|
chooseFolder(w, logsDir)
|
||||||
|
})
|
||||||
|
maxLogFiles := widget.NewEntry()
|
||||||
|
maxLogFiles.SetText(strconv.Itoa(store.Config.MaxLogFiles))
|
||||||
|
maxLogAgeDays := widget.NewEntry()
|
||||||
|
maxLogAgeDays.SetText(strconv.Itoa(store.Config.MaxLogAgeDays))
|
||||||
|
settingsStatus := widget.NewLabel("")
|
||||||
|
|
||||||
|
saveSettings := widget.NewButtonWithIcon("Save settings", theme.DocumentSaveIcon(), func() {
|
||||||
|
files, err := strconv.Atoi(strings.TrimSpace(maxLogFiles.Text))
|
||||||
|
if err != nil || files <= 0 {
|
||||||
|
settingsStatus.SetText("Max log files must be a positive number")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
days, err := strconv.Atoi(strings.TrimSpace(maxLogAgeDays.Text))
|
||||||
|
if err != nil || days <= 0 {
|
||||||
|
settingsStatus.SetText("Max log age days must be a positive number")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(jobsDir.Text) == "" {
|
||||||
|
settingsStatus.SetText("Jobs directory is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(logsDir.Text) == "" {
|
||||||
|
settingsStatus.SetText("Logs directory is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Build the new config from the form and hand it to the Service, which
|
||||||
|
// validates it, persists config and jobs to the (possibly new) directory,
|
||||||
|
// and runs log cleanup so tightened retention limits take effect at once.
|
||||||
|
config := store.Config
|
||||||
|
config.JobsDir = strings.TrimSpace(jobsDir.Text)
|
||||||
|
config.LogsDir = strings.TrimSpace(logsDir.Text)
|
||||||
|
config.MaxLogFiles = files
|
||||||
|
config.MaxLogAgeDays = days
|
||||||
|
config.StartOnLogin = startOnLogin.Checked
|
||||||
|
config.KeepRunningInTray = minimizeToTray.Checked
|
||||||
|
config.NotifyOnFailure = notifications.Checked
|
||||||
|
if err := svc.UpdateSettings(config); err != nil {
|
||||||
|
settingsStatus.SetText("Save failed: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Autostart is platform integration the Service leaves to the caller (until
|
||||||
|
// T5.2 introduces an injectable autostart.Manager), so apply it here.
|
||||||
|
if err := autostart.SetAutostart(store.Config.StartOnLogin, store.Paths.ExecutablePath, store.Paths.DesktopIcon); err != nil {
|
||||||
|
refreshAutostartStatus()
|
||||||
|
settingsStatus.SetText("Saved, autostart failed: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
refreshAutostartStatus()
|
||||||
|
settingsStatus.SetText("Saved")
|
||||||
|
})
|
||||||
|
|
||||||
|
return container.NewPadded(container.NewVBox(
|
||||||
|
widget.NewLabelWithStyle("Application", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
|
settingsRowWithStatus("Autostart", startOnLogin, autostartStatus),
|
||||||
|
settingsRow("Tray", container.New(minWidthLayout{width: settingsControlWidth}, minimizeToTray)),
|
||||||
|
settingsRow("Notifications", container.New(minWidthLayout{width: settingsControlWidth}, notifications)),
|
||||||
|
widget.NewSeparator(),
|
||||||
|
widget.NewLabelWithStyle("Storage", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
|
settingsRow("Config YAML", widget.NewLabel(store.Paths.ConfigPath)),
|
||||||
|
settingsRow("Jobs directory", container.NewBorder(nil, nil, nil, jobsDirBrowse, jobsDir)),
|
||||||
|
settingsRow("Logs directory", container.NewBorder(nil, nil, nil, logsDirBrowse, logsDir)),
|
||||||
|
settingsRow("Max log files", maxLogFiles),
|
||||||
|
settingsRow("Max log age days", maxLogAgeDays),
|
||||||
|
saveSettings,
|
||||||
|
settingsStatus,
|
||||||
|
widget.NewSeparator(),
|
||||||
|
widget.NewLabelWithStyle("About", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||||
|
settingsRow("GoSentry", widget.NewLabel(app.Version)),
|
||||||
|
settingsRow("Go", widget.NewLabel(runtime.Version())),
|
||||||
|
settingsRow("Fyne", widget.NewLabel(fyneVersion())),
|
||||||
|
settingsRow("Repository", widget.NewHyperlink(projectRepositoryURL, mustParseURL(projectRepositoryURL))),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fyneVersion() string {
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
for _, dependency := range info.Deps {
|
||||||
|
if dependency.Path == "fyne.io/fyne/v2" {
|
||||||
|
if dependency.Replace != nil && dependency.Replace.Version != "" {
|
||||||
|
return dependency.Replace.Version
|
||||||
|
}
|
||||||
|
if dependency.Version != "" {
|
||||||
|
return dependency.Version
|
||||||
|
}
|
||||||
|
return "local"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustParseURL(raw string) *url.URL {
|
||||||
|
parsed, err := url.Parse(raw)
|
||||||
|
if err != nil {
|
||||||
|
return &url.URL{}
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
func chooseFolder(w fyne.Window, target *widget.Entry) {
|
||||||
|
folderDialog := dialog.NewFolderOpen(func(uri fyne.ListableURI, err error) {
|
||||||
|
if err != nil || uri == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target.SetText(uri.Path())
|
||||||
|
}, w)
|
||||||
|
// The default folder picker can be cramped on Windows. A larger size makes
|
||||||
|
// long paths readable and avoids forcing the user to resize it every time.
|
||||||
|
folderDialog.Resize(fyne.NewSize(900, 640))
|
||||||
|
folderDialog.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func settingsRow(label string, value fyne.CanvasObject) fyne.CanvasObject {
|
||||||
|
caption := widget.NewLabelWithStyle(label, fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
||||||
|
caption.Wrapping = fyne.TextTruncate
|
||||||
|
captionBox := container.New(minWidthLayout{width: settingsLabelWidth}, caption)
|
||||||
|
return container.NewBorder(nil, nil, captionBox, nil, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func settingsRowWithStatus(label string, value fyne.CanvasObject, status fyne.CanvasObject) fyne.CanvasObject {
|
||||||
|
valueBox := container.New(minWidthLayout{width: settingsControlWidth}, value)
|
||||||
|
statusBox := container.New(minWidthLayout{width: settingsStatusWidth}, status)
|
||||||
|
return settingsRow(label, container.NewBorder(nil, nil, valueBox, nil, statusBox))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user