diff --git a/internal/ui/builder_test.go b/internal/ui/builder_test.go index 6aa73e8..72f9a1f 100644 --- a/internal/ui/builder_test.go +++ b/internal/ui/builder_test.go @@ -4,6 +4,9 @@ import ( "testing" "deedles.dev/trayscale/internal/gutil" + "deedles.dev/trayscale/internal/tsutil" + "tailscale.com/ipn" + "tailscale.com/tailcfg" ) // TestFillFromBuilder builds every .ui file into its Go struct so that @@ -27,20 +30,21 @@ 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 - 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 - 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 - 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/mainwindow.go b/internal/ui/mainwindow.go index 99ab4ac..7cd5658 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/mullvadpage.go b/internal/ui/mullvadpage.go index c0b6155..78858c5 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 0b89c09..56dcdd3 100644 --- a/internal/ui/mullvadpage.ui +++ b/internal/ui/mullvadpage.ui @@ -1,41 +1,45 @@ - + - - Mullvad Exit Nodes - - + diff --git a/internal/ui/offlinepage.go b/internal/ui/offlinepage.go index fcbe3fa..48ee2c9 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 8282a59..10be238 100644 --- a/internal/ui/offlinepage.ui +++ b/internal/ui/offlinepage.ui @@ -1,30 +1,34 @@ - + - + diff --git a/internal/ui/peerpage.go b/internal/ui/peerpage.go index cee5d10..4304405 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 96b2139..1d945d6 100644 --- a/internal/ui/peerpage.ui +++ b/internal/ui/peerpage.ui @@ -1,92 +1,96 @@ - + - - - + copy diff --git a/internal/ui/selfpage.go b/internal/ui/selfpage.go index b3d90db..4271011 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 aee2b95..e35b604 100644 --- a/internal/ui/selfpage.ui +++ b/internal/ui/selfpage.ui @@ -1,187 +1,191 @@ - + - - - +