60aceb75af
Part A stages 1-3 of the GUI layout cleanup plan: - Stage 1 (F1-F3): delete settingsControlWidth's redundant wrapper (the Border centre slot already stretches controls), truncate the config path label, and name the default window size so it can be asserted against. Settings no longer widens the window past what it asks for. - Stage 2 (F4-F5): drop compactVBoxLayout for the stock layout.NewCustomPaddedVBoxLayout and a single derived rowOverlap() spacing, replacing three hand-tuned spacing constants. - Stage 3 (F7): delete the inert 400px sidebar width floor; the Border left slot already renders it at content MinSize. See docs/PLAN-gui-layout.md.
92 lines
3.5 KiB
Go
92 lines
3.5 KiB
Go
package ui
|
|
|
|
import (
|
|
"runtime"
|
|
"time"
|
|
|
|
"gitea.mixdep.ru/mix/gosentry/assets"
|
|
"gitea.mixdep.ru/mix/gosentry/src/app"
|
|
|
|
"fyne.io/fyne/v2"
|
|
fyneapp "fyne.io/fyne/v2/app"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/widget"
|
|
)
|
|
|
|
const appID = "ru.mixeme.gosentry.desktop"
|
|
|
|
// defaultWindowWidth and defaultWindowHeight are the size the window opens at
|
|
// on first launch (later launches restore the last size from preferences).
|
|
// Fyne enforces the assembled content's MinSize as a hard floor over these, so
|
|
// they only take effect if the content actually fits within them.
|
|
const defaultWindowWidth = 1024
|
|
const defaultWindowHeight = 660
|
|
|
|
// Run is the application entry point. It owns the process lifecycle — single
|
|
// instance arbitration, Fyne app + window construction, tray wiring, and the
|
|
// startup-timing record — and delegates all view construction to newMainView in
|
|
// mainwindow.go. Keeping lifecycle here and the view there is the run.go /
|
|
// mainwindow.go split keeps lifecycle separate from view construction.
|
|
func Run(startInTray bool) {
|
|
started := time.Now()
|
|
instanceListener, primary := acquireSingleInstance(!startInTray)
|
|
if !primary {
|
|
return
|
|
}
|
|
if instanceListener != nil {
|
|
defer instanceListener.Close()
|
|
}
|
|
|
|
// A stable app ID lets Fyne persist desktop preferences consistently across
|
|
// launches and gives tray/window integration a predictable identity.
|
|
a := fyneapp.NewWithID(appID)
|
|
// On Windows the multi-resolution gosentry.ico (embedded under the GLFW_ICON
|
|
// resource name) drives the window: GLFW picks the hand-tuned 16x16 for the
|
|
// titlebar and the large artwork for the bigger taskbar icon — size-appropriate
|
|
// in a way a single Fyne SetIcon resource cannot be, since one PNG would be
|
|
// scaled to both sizes.
|
|
//
|
|
// Other platforms have no PE icon. Fyne's single SetIcon resource feeds
|
|
// _NET_WM_ICON, which the window manager renders small (~16px) in the titlebar,
|
|
// so use the hand-tuned small icon there to keep it crisp. The larger
|
|
// dock/launcher icon comes from the .desktop entry installed by
|
|
// InstallDesktopIcon, which uses the big artwork.
|
|
if runtime.GOOS != "windows" {
|
|
a.SetIcon(assets.IconSmall())
|
|
}
|
|
|
|
w := a.NewWindow("GoSentry " + app.Version)
|
|
configureSystemTray(a, w)
|
|
prefs := a.Preferences()
|
|
winW := float32(prefs.FloatWithFallback("window.width", defaultWindowWidth))
|
|
winH := float32(prefs.FloatWithFallback("window.height", defaultWindowHeight))
|
|
w.Resize(fyne.NewSize(winW, winH))
|
|
svc, err := app.Open()
|
|
if err != nil {
|
|
w.SetContent(container.NewPadded(widget.NewLabel("Failed to load GoSentry configuration: " + err.Error())))
|
|
a.Run()
|
|
return
|
|
}
|
|
// Apply the persisted theme before building content so the window renders in
|
|
// the chosen theme from the first frame rather than flashing the default one.
|
|
applyTheme(a, svc.Store().Config.Theme)
|
|
content, recordStartup := newMainView(w, svc)
|
|
w.SetContent(content)
|
|
serveSingleInstance(instanceListener, w)
|
|
if startInTray {
|
|
// Autostart launches intentionally stay hidden, so "window shown" would be
|
|
// a misleading metric. Record a separate startup event for the tray path
|
|
// instead of forcing one timing definition onto two different UX flows.
|
|
recordStartup(time.Since(started), false)
|
|
a.Run()
|
|
return
|
|
}
|
|
// Show the window before recording startup time. Measuring earlier, during
|
|
// widget construction, looked cheaper in History than the user-perceived
|
|
// startup really was. The current point is less abstract: it ends when the
|
|
// window has actually been handed to the desktop for display.
|
|
w.Show()
|
|
recordStartup(time.Since(started), true)
|
|
a.Run()
|
|
}
|