From edca0aae13f24dcc280101e8156934e01ed1c776 Mon Sep 17 00:00:00 2001 From: Coder Date: Fri, 16 Oct 2026 16:50:54 -0400 Subject: [PATCH 1/4] internal/ui: make the offline page a template class --- internal/ui/builder_test.go | 3 +-- internal/ui/mainwindow.go | 2 +- internal/ui/offlinepage.go | 23 +++++++++------------- internal/ui/offlinepage.ui | 38 ++++++++++++++++++++----------------- 4 files changed, 32 insertions(+), 34 deletions(-) diff --git a/internal/ui/builder_test.go b/internal/ui/builder_test.go index 6aa73e81..7903db48 100644 --- a/internal/ui/builder_test.go +++ b/internal/ui/builder_test.go @@ -27,8 +27,7 @@ func TestFillFromBuilder(t *testing.T) { fill(t, func() { gutil.FillFromUI(&win, menuXML, mainWindowXML); gutil.UnrefFields(&win) }) }) t.Run("OfflinePage", func(t *testing.T) { - var page OfflinePage - fill(t, func() { gutil.FillFromUI(&page, offlinePageXML); gutil.UnrefFields(&page) }) + fill(t, func() { offlinePageClass.New().Destroy() }) }) t.Run("PeerPage", func(t *testing.T) { var page PeerPage diff --git a/internal/ui/mainwindow.go b/internal/ui/mainwindow.go index 99ab4ac2..7cd56586 100644 --- a/internal/ui/mainwindow.go +++ b/internal/ui/mainwindow.go @@ -330,7 +330,7 @@ func (win *MainWindow) updatePeers(status *tsutil.IPNStatus) { win.ipn = status if !status.Online() { if _, ok := win.pages["offline"]; !ok { - win.addPage("offline", NewOfflinePage(win.app)) + win.addPage("offline", offlinePageClass.New()) } win.updatePages(status) return diff --git a/internal/ui/offlinepage.go b/internal/ui/offlinepage.go index fcbe3fa7..48ee2c94 100644 --- a/internal/ui/offlinepage.go +++ b/internal/ui/offlinepage.go @@ -5,31 +5,26 @@ import ( "codeberg.org/puregotk/puregotk/v4/adw" "codeberg.org/puregotk/puregotk/v4/gio" - "codeberg.org/puregotk/puregotk/v4/gtk" - "deedles.dev/trayscale/internal/gutil" + "deedles.dev/trayscale/internal/gobj" "deedles.dev/trayscale/internal/tsutil" ) //go:embed offlinepage.ui var offlinePageXML string +var offlinePageClass = gobj.Widget[OfflinePage](gobj.WidgetSpec{ + Name: "TrayscaleOfflinePage", + Parent: adw.BinGLibType, + Template: offlinePageXML, +}) + type OfflinePage struct { - app *App + gobj.Base Page *adw.StatusPage NeedsAuthGroup *adw.PreferencesGroup } -func NewOfflinePage(app *App) *OfflinePage { - page := OfflinePage{app: app} - gutil.FillFromUI(&page, offlinePageXML) - return &page -} - -func (page *OfflinePage) Widget() *gtk.Widget { - return &page.Page.Widget -} - func (page *OfflinePage) Actions() *gio.SimpleActionGroup { return nil } @@ -40,7 +35,7 @@ func (page *OfflinePage) Bind(stackPage *adw.ViewStackPage) { } func (page *OfflinePage) Destroy() { - gutil.UnrefFields(page) + page.Widget().Unref() } func (page *OfflinePage) Update(status tsutil.Status) bool { diff --git a/internal/ui/offlinepage.ui b/internal/ui/offlinepage.ui index 8282a59c..10be238e 100644 --- a/internal/ui/offlinepage.ui +++ b/internal/ui/offlinepage.ui @@ -1,30 +1,34 @@ - + - + From 2fcd36d57350d26873b7b80983da26538935862d Mon Sep 17 00:00:00 2001 From: Coder Date: Fri, 16 Oct 2026 16:54:48 -0400 Subject: [PATCH 2/4] internal/ui: make the peer page a template class --- internal/ui/builder_test.go | 8 ++- internal/ui/peerpage.go | 24 ++++--- internal/ui/peerpage.ui | 134 +++++++++++++++++++----------------- 3 files changed, 90 insertions(+), 76 deletions(-) diff --git a/internal/ui/builder_test.go b/internal/ui/builder_test.go index 7903db48..7eec5d45 100644 --- a/internal/ui/builder_test.go +++ b/internal/ui/builder_test.go @@ -4,6 +4,8 @@ import ( "testing" "deedles.dev/trayscale/internal/gutil" + "tailscale.com/ipn" + "tailscale.com/tailcfg" ) // TestFillFromBuilder builds every .ui file into its Go struct so that @@ -30,8 +32,10 @@ func TestFillFromBuilder(t *testing.T) { fill(t, func() { offlinePageClass.New().Destroy() }) }) t.Run("PeerPage", func(t *testing.T) { - var page PeerPage - fill(t, func() { gutil.FillFromUI(&page, peerPageXML); gutil.UnrefFields(&page) }) + peer := layoutPeer(t, "peer", "peer", true, false, false) + status := layoutStatus([]tailcfg.NodeView{peer}) + status.Prefs = ipn.NewPrefs().View() + fill(t, func() { NewPeerPage(&App{}, status, peer).Destroy() }) }) t.Run("SelfPage", func(t *testing.T) { var page SelfPage diff --git a/internal/ui/peerpage.go b/internal/ui/peerpage.go index cee5d108..43044059 100644 --- a/internal/ui/peerpage.go +++ b/internal/ui/peerpage.go @@ -29,7 +29,15 @@ import ( //go:embed peerpage.ui var peerPageXML string +var peerPageClass = gobj.Widget[PeerPage](gobj.WidgetSpec{ + Name: "TrayscalePeerPage", + Parent: adw.BinGLibType, + Template: peerPageXML, +}) + type PeerPage struct { + gobj.Base + app *App stackPage *adw.ViewStackPage peer tailcfg.NodeView @@ -63,10 +71,9 @@ type PeerPage struct { } func NewPeerPage(a *App, status *tsutil.IPNStatus, peer tailcfg.NodeView) *PeerPage { - var page PeerPage - gutil.FillFromUI(&page, peerPageXML) + page := peerPageClass.New() page.init(a, status, peer) - return &page + return page } func (page *PeerPage) init(a *App, status *tsutil.IPNStatus, peer tailcfg.NodeView) { @@ -113,7 +120,7 @@ func (page *PeerPage) init(a *App, status *tsutil.IPNStatus, peer tailcfg.NodeVi })) }) - page.DropTarget.Ref() // AddController takes a reference; the field keeps its own. + page.DropTarget.Ref() // AddController takes over a reference; the template keeps its own. page.Page.AddController(&page.DropTarget.EventController) page.DropTarget.SetGtypes([]types.GType{gio.FileGLibType()}, 1) gutil.Connect(page.DropTarget, page.DropTarget.ConnectDrop, func(_ gtk.DropTarget, val uintptr, x, y float64) bool { @@ -170,20 +177,19 @@ func (page *PeerPage) init(a *App, status *tsutil.IPNStatus, peer tailcfg.NodeVi }) } -func (page *PeerPage) Widget() *gtk.Widget { - return &page.Page.Widget -} - func (page *PeerPage) Actions() *gio.SimpleActionGroup { return page.actions } func (page *PeerPage) Destroy() { + page.Widget().Unref() +} + +func (page *PeerPage) Dispose() { page.addrModel.Unref() page.routeModel.Unref() page.sendFileAction.Unref() page.actions.Unref() - gutil.UnrefFields(page) } func (page *PeerPage) Bind(stackPage *adw.ViewStackPage) { diff --git a/internal/ui/peerpage.ui b/internal/ui/peerpage.ui index 96b21397..1d945d67 100644 --- a/internal/ui/peerpage.ui +++ b/internal/ui/peerpage.ui @@ -1,92 +1,96 @@ - + - - - + copy From 1062550459b3b53636c1b44ea5788f626f53dbdc Mon Sep 17 00:00:00 2001 From: Coder Date: Fri, 16 Oct 2026 16:55:24 -0400 Subject: [PATCH 3/4] internal/ui: make the self page a template class --- internal/ui/builder_test.go | 5 +- internal/ui/selfpage.go | 22 ++- internal/ui/selfpage.ui | 290 ++++++++++++++++++------------------ 3 files changed, 164 insertions(+), 153 deletions(-) diff --git a/internal/ui/builder_test.go b/internal/ui/builder_test.go index 7eec5d45..deb1110b 100644 --- a/internal/ui/builder_test.go +++ b/internal/ui/builder_test.go @@ -4,6 +4,7 @@ import ( "testing" "deedles.dev/trayscale/internal/gutil" + "deedles.dev/trayscale/internal/tsutil" "tailscale.com/ipn" "tailscale.com/tailcfg" ) @@ -38,8 +39,8 @@ func TestFillFromBuilder(t *testing.T) { fill(t, func() { NewPeerPage(&App{}, status, peer).Destroy() }) }) t.Run("SelfPage", func(t *testing.T) { - var page SelfPage - fill(t, func() { gutil.FillFromUI(&page, selfPageXML); gutil.UnrefFields(&page) }) + status := &tsutil.IPNStatus{State: ipn.Running, Prefs: ipn.NewPrefs().View()} + fill(t, func() { NewSelfPage(&App{}, status).Destroy() }) }) t.Run("MullvadPage", func(t *testing.T) { var page MullvadPage diff --git a/internal/ui/selfpage.go b/internal/ui/selfpage.go index b3d90dbf..4271011c 100644 --- a/internal/ui/selfpage.go +++ b/internal/ui/selfpage.go @@ -27,7 +27,15 @@ import ( //go:embed selfpage.ui var selfPageXML string +var selfPageClass = gobj.Widget[SelfPage](gobj.WidgetSpec{ + Name: "TrayscaleSelfPage", + Parent: adw.BinGLibType, + Template: selfPageXML, +}) + type SelfPage struct { + gobj.Base + app *App stackPage *adw.ViewStackPage peer tailcfg.NodeView @@ -76,10 +84,9 @@ type SelfPage struct { } func NewSelfPage(a *App, status *tsutil.IPNStatus) *SelfPage { - var page SelfPage - gutil.FillFromUI(&page, selfPageXML) + page := selfPageClass.New() page.init(a, status) - return &page + return page } func (page *SelfPage) init(a *App, status *tsutil.IPNStatus) { @@ -316,21 +323,20 @@ func (page *SelfPage) fileRow(file apitype.WaitingFile) *gtk.Widget { return &row.Widget } -func (page *SelfPage) Widget() *gtk.Widget { - return &page.Page.Widget -} - func (page *SelfPage) Actions() *gio.SimpleActionGroup { return page.actions } func (page *SelfPage) Destroy() { + page.Widget().Unref() +} + +func (page *SelfPage) Dispose() { page.setLatencies(nil) page.addrModel.Unref() page.routeModel.Unref() page.fileModel.Unref() page.actions.Unref() - gutil.UnrefFields(page) } func (page *SelfPage) Bind(stackPage *adw.ViewStackPage) { diff --git a/internal/ui/selfpage.ui b/internal/ui/selfpage.ui index aee2b95f..e35b6046 100644 --- a/internal/ui/selfpage.ui +++ b/internal/ui/selfpage.ui @@ -1,187 +1,191 @@ - + - - - + From 057e8d9b28f94f5a9bca435246daaa6271deba6b Mon Sep 17 00:00:00 2001 From: Coder Date: Fri, 16 Oct 2026 16:55:56 -0400 Subject: [PATCH 4/4] internal/ui: make the mullvad page a template class --- internal/ui/builder_test.go | 4 +-- internal/ui/mullvadpage.go | 30 +++++++++++-------- internal/ui/mullvadpage.ui | 60 ++++++++++++++++++++----------------- 3 files changed, 52 insertions(+), 42 deletions(-) diff --git a/internal/ui/builder_test.go b/internal/ui/builder_test.go index deb1110b..72f9a1f0 100644 --- a/internal/ui/builder_test.go +++ b/internal/ui/builder_test.go @@ -43,8 +43,8 @@ func TestFillFromBuilder(t *testing.T) { fill(t, func() { NewSelfPage(&App{}, status).Destroy() }) }) t.Run("MullvadPage", func(t *testing.T) { - var page MullvadPage - fill(t, func() { gutil.FillFromUI(&page, mullvadPageXML); gutil.UnrefFields(&page) }) + status := &tsutil.IPNStatus{State: ipn.Running, Prefs: ipn.NewPrefs().View()} + fill(t, func() { NewMullvadPage(&App{}, status).Destroy() }) }) t.Run("PreferencesDialog", func(t *testing.T) { var dialog PreferencesDialog diff --git a/internal/ui/mullvadpage.go b/internal/ui/mullvadpage.go index c0b61550..78858c5f 100644 --- a/internal/ui/mullvadpage.go +++ b/internal/ui/mullvadpage.go @@ -14,6 +14,7 @@ import ( "codeberg.org/puregotk/puregotk/v4/adw" "codeberg.org/puregotk/puregotk/v4/gio" "codeberg.org/puregotk/puregotk/v4/gtk" + "deedles.dev/trayscale/internal/gobj" "deedles.dev/trayscale/internal/gutil" "deedles.dev/trayscale/internal/locale" "deedles.dev/trayscale/internal/peersearch" @@ -25,7 +26,15 @@ import ( //go:embed mullvadpage.ui var mullvadPageXML string +var mullvadPageClass = gobj.Widget[MullvadPage](gobj.WidgetSpec{ + Name: "TrayscaleMullvadPage", + Parent: adw.BinGLibType, + Template: mullvadPageXML, +}) + type MullvadPage struct { + gobj.Base + app *App stackPage *adw.ViewStackPage @@ -40,12 +49,10 @@ type MullvadPage struct { } func NewMullvadPage(a *App, status *tsutil.IPNStatus) *MullvadPage { - page := MullvadPage{ - app: a, - locations: make(map[string]*adw.ExpanderRow), - exitNodes: make(map[tailcfg.StableNodeID]*mullvadExitNodeRow), - } - gutil.FillFromUI(&page, mullvadPageXML) + page := mullvadPageClass.New() + page.app = a + page.locations = make(map[string]*adw.ExpanderRow) + page.exitNodes = make(map[tailcfg.StableNodeID]*mullvadExitNodeRow) setSortFunc(page.LocationList, page.compareListRows) setFilterFunc(page.LocationList, page.listRowVisible) @@ -54,7 +61,7 @@ func NewMullvadPage(a *App, status *tsutil.IPNStatus) *MullvadPage { page.setQuery(page.SearchEntry.GetText()) }) - return &page + return page } func (page *MullvadPage) setQuery(q string) { @@ -100,15 +107,15 @@ func (page *MullvadPage) expanderTitle(row *gtk.ListBoxRow) string { return row.GetName() } -func (page *MullvadPage) Widget() *gtk.Widget { - return &page.Page.Widget -} - func (page *MullvadPage) Actions() *gio.SimpleActionGroup { return nil } func (page *MullvadPage) Destroy() { + page.Widget().Unref() +} + +func (page *MullvadPage) Dispose() { for _, node := range page.exitNodes { node.row.Unref() } @@ -117,7 +124,6 @@ func (page *MullvadPage) Destroy() { loc.Unref() } clear(page.locations) - gutil.UnrefFields(page) } func (page *MullvadPage) Bind(stackPage *adw.ViewStackPage) { diff --git a/internal/ui/mullvadpage.ui b/internal/ui/mullvadpage.ui index 0b89c09a..56dcdd36 100644 --- a/internal/ui/mullvadpage.ui +++ b/internal/ui/mullvadpage.ui @@ -1,41 +1,45 @@ - + - - Mullvad Exit Nodes - - +