From 0ba51d151035eb80512d20eb5285ff84f1b4deab Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Fri, 10 May 2019 11:00:16 +0200 Subject: ui: somewhat aggressively cache icons Signed-off-by: Jason A. Donenfeld --- ui/aboutdialog.go | 10 +++--- ui/confview.go | 50 +++++++++++++++-------------- ui/iconprovider.go | 92 +++++++++++++++++++++++++++++++++++++++++------------- ui/listview.go | 1 - ui/managewindow.go | 5 ++- ui/tray.go | 12 ++----- ui/updatepage.go | 2 -- 7 files changed, 107 insertions(+), 65 deletions(-) (limited to 'ui') diff --git a/ui/aboutdialog.go b/ui/aboutdialog.go index fe0639d5..1c4528dc 100644 --- a/ui/aboutdialog.go +++ b/ui/aboutdialog.go @@ -25,23 +25,23 @@ func onAbout(owner walk.Form) { dlg, _ := walk.NewDialogWithFixedSize(owner) dlg.SetTitle("About WireGuard") dlg.SetLayout(vbl) - wireguardIcon, err := walk.NewIconFromResourceWithSize("$wireguard.ico", walk.Size{dlg.DPI() / 3, dlg.DPI() / 3}) //TODO: calculate DPI dynamically - if err == nil { - dlg.SetIcon(wireguardIcon) + if icon, err := loadLogoIcon(dlg.DPI() / 3); err == nil { //TODO: calculate DPI dynamically + dlg.SetIcon(icon) } font, _ := walk.NewFont("Segoe UI", 9, 0) dlg.SetFont(font) iv, _ := walk.NewImageView(dlg) - logo, _ := walk.NewIconFromResourceWithSize("$wireguard.ico", walk.Size{dlg.DPI() * 4 / 3, dlg.DPI() * 4 / 3}) //TODO: calculate DPI dynamically iv.SetCursor(walk.CursorHand()) iv.MouseUp().Attach(func(x, y int, button walk.MouseButton) { if button == walk.LeftButton { win.ShellExecute(dlg.Handle(), nil, windows.StringToUTF16Ptr("https://www.wireguard.com/"), nil, nil, win.SW_SHOWNORMAL) } }) - iv.SetImage(logo) + if logo, err := loadLogoIcon(dlg.DPI() * 4 / 3); err == nil { //TODO: calculate DPI dynamically + iv.SetImage(logo) + } wgLbl, _ := walk.NewTextLabel(dlg) wgFont, _ := walk.NewFont("Segoe UI", 16, walk.FontBold) diff --git a/ui/confview.go b/ui/confview.go index ca14abea..b157406e 100644 --- a/ui/confview.go +++ b/ui/confview.go @@ -25,11 +25,18 @@ type widgetsLinesView interface { widgetsLines() []widgetsLine } +type rectAndSizeAndState struct { + rect walk.Rectangle + size walk.Size + state service.TunnelState +} + type labelStatusLine struct { label *walk.TextLabel statusComposite *walk.Composite statusImage *walk.ImageView statusLabel *walk.LineEdit + cachedImages map[rectAndSizeAndState]walk.Image } type labelTextLine struct { @@ -80,31 +87,26 @@ func (lsl *labelStatusLine) widgets() (walk.Widget, walk.Widget) { func (lsl *labelStatusLine) update(state service.TunnelState) { margin := lsl.label.DPI() / 48 //TODO: Do some sort of dynamic DPI calculation here - labelSize := lsl.label.SizeHint() - imageRect := walk.Rectangle{0, 0, labelSize.Height, labelSize.Height} - img, _ := walk.NewBitmapWithTransparentPixels(imageRect.Size()) - canvas, _ := walk.NewCanvasFromImage(img) - imageRect.X += margin - imageRect.Y += margin * 2 //TODO: the *2 here fixes weird alignment bugs. Why? - imageRect.Height -= margin * 2 - imageRect.Width -= margin * 2 - icon, err := iconForState(state, imageRect.Size().Width) - if err == nil { - canvas.DrawImageStretched(icon, imageRect) - icon.Dispose() - canvas.Dispose() - prior := lsl.statusImage.Image() - lsl.statusImage.SetImage(img) - if prior != nil { - prior.Dispose() - } - } else { - prior := lsl.statusImage.Image() - lsl.statusImage.SetImage(nil) - if prior != nil { - prior.Dispose() + imageSize := lsl.label.SizeHint() + imageSize.Width = imageSize.Height + //TODO: the *2 in the Y coordinate fixes weird alignment bugs. Why? + imageRect := walk.Rectangle{margin, margin * 2, imageSize.Width - margin*2, imageSize.Height - margin*2} + img := lsl.cachedImages[rectAndSizeAndState{imageRect, imageSize, state}] + if img == nil { + img, _ = walk.NewBitmapWithTransparentPixels(imageSize) + canvas, _ := walk.NewCanvasFromImage(img) + icon, err := iconForState(state, imageRect.Size().Width) + if err == nil { + canvas.DrawImageStretched(icon, imageRect) + canvas.Dispose() + lsl.cachedImages[rectAndSizeAndState{imageRect, imageSize, state}] = img + } else { + canvas.Dispose() + img.Dispose() + img = nil } } + lsl.statusImage.SetImage(img) s, e := lsl.statusLabel.TextSelection() switch state { case service.TunnelStarted: @@ -128,6 +130,8 @@ func (lsl *labelStatusLine) update(state service.TunnelState) { func newLabelStatusLine(parent walk.Container) *labelStatusLine { lsl := new(labelStatusLine) + lsl.cachedImages = make(map[rectAndSizeAndState]walk.Image) + lsl.label, _ = walk.NewTextLabel(parent) lsl.label.SetText("Status:") lsl.label.SetTextAlignment(walk.AlignHFarVNear) diff --git a/ui/iconprovider.go b/ui/iconprovider.go index e2647939..9f6cc433 100644 --- a/ui/iconprovider.go +++ b/ui/iconprovider.go @@ -12,29 +12,47 @@ import ( "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/windows/service" "path" + "syscall" ) -func iconWithOverlayForState(state service.TunnelState, size int) (*walk.Icon, error) { - wireguardIcon, err := walk.NewIconFromResourceWithSize("$wireguard.ico", walk.Size{size, size}) +type widthAndState struct { + width int + state service.TunnelState +} + +type widthAndDllIdx struct { + width int + idx int32 + dll string +} + +var cachedOverlayIconsForWidthAndState = make(map[widthAndState]*walk.Icon) + +func iconWithOverlayForState(state service.TunnelState, size int) (icon *walk.Icon, err error) { + icon = cachedOverlayIconsForWidthAndState[widthAndState{size, state}] + if icon != nil { + return + } + wireguardIcon, err := loadLogoIcon(size) if err != nil { - return nil, err + return } - defer wireguardIcon.Dispose() iconSize := wireguardIcon.Size() bmp, err := walk.NewBitmapWithTransparentPixels(iconSize) if err != nil { - return nil, err + return } defer bmp.Dispose() canvas, err := walk.NewCanvasFromImage(bmp) if err != nil { - return nil, err + return } defer canvas.Dispose() - if err := canvas.DrawImage(wireguardIcon, walk.Point{}); err != nil { - return nil, err + err = canvas.DrawImage(wireguardIcon, walk.Point{}) + if err != nil { + return } w := int(float64(iconSize.Width) * 0.65) @@ -42,44 +60,76 @@ func iconWithOverlayForState(state service.TunnelState, size int) (*walk.Icon, e bounds := walk.Rectangle{iconSize.Width - w, iconSize.Height - h, w, h} overlayIcon, err := iconForState(state, bounds.Width) if err != nil { - return nil, err + return } defer overlayIcon.Dispose() - if err := canvas.DrawImageStretched(overlayIcon, bounds); err != nil { - return nil, err + err = canvas.DrawImageStretched(overlayIcon, bounds) + if err != nil { + return } canvas.Dispose() - icon, err := walk.NewIconFromBitmap(bmp) - if err != nil { - return nil, err + icon, err = walk.NewIconFromBitmap(bmp) + if err == nil { + cachedOverlayIconsForWidthAndState[widthAndState{size, state}] = icon } - return icon, nil + return } +var cachedIconsForWidthAndState = make(map[widthAndState]*walk.Icon) + func iconForState(state service.TunnelState, size int) (icon *walk.Icon, err error) { + icon = cachedIconsForWidthAndState[widthAndState{size, state}] + if icon != nil { + return + } switch state { case service.TunnelStarted: icon, err = loadSystemIcon("imageres", 101, size) - case service.TunnelStopped: icon, err = walk.NewIconFromResourceWithSize("dot-gray.ico", walk.Size{size, size}) //TODO: replace with real icon - default: icon, err = loadSystemIcon("shell32", 238, size) //TODO: this doesn't look that great overlayed on the app icon } + if err == nil { + cachedIconsForWidthAndState[widthAndState{size, state}] = icon + } return } -func loadSystemIcon(dll string, index int32, size int) (*walk.Icon, error) { +var cachedSystemIconsForWidthAndDllIdx = make(map[widthAndDllIdx]*walk.Icon) + +func loadSystemIcon(dll string, index int32, size int) (icon *walk.Icon, err error) { + icon = cachedSystemIconsForWidthAndDllIdx[widthAndDllIdx{size, index, dll}] + if icon != nil { + return + } system32, err := windows.GetSystemDirectory() if err != nil { - return nil, err + return } var hicon win.HICON ret := win.SHDefExtractIcon(windows.StringToUTF16Ptr(path.Join(system32, dll+".dll")), index, 0, &hicon, nil, uint32(size)) if ret != 0 { - return nil, fmt.Errorf("Unable to find icon %d of %s due to error %d", index, dll, ret) + return nil, fmt.Errorf("Unable to find icon %d of %s: %v", index, dll, syscall.Errno(ret)) + } + icon, err = walk.NewIconFromHICON(hicon) + if err == nil { + cachedSystemIconsForWidthAndDllIdx[widthAndDllIdx{size, index, dll}] = icon + } + return +} + +var cachedLogoIconsForWidth = make(map[int]*walk.Icon) + +func loadLogoIcon(size int) (icon *walk.Icon, err error) { + icon = cachedLogoIconsForWidth[size] + if icon != nil { + return + } + icon, err = walk.NewIconFromResourceWithSize("$wireguard.ico", walk.Size{size, size}) + if err == nil { + cachedLogoIconsForWidth[size] = icon } - return walk.NewIconFromHICON(hicon) + return } diff --git a/ui/listview.go b/ui/listview.go index 0a15c5da..6870e91c 100644 --- a/ui/listview.go +++ b/ui/listview.go @@ -137,7 +137,6 @@ func (tv *ListView) StyleCell(style *walk.CellStyle) { return } canvas.DrawImageStretched(icon, b) - icon.Dispose() } func (tv *ListView) CurrentTunnel() *service.Tunnel { diff --git a/ui/managewindow.go b/ui/managewindow.go index 54d6b1e9..b13e0afd 100644 --- a/ui/managewindow.go +++ b/ui/managewindow.go @@ -36,9 +36,8 @@ func NewManageTunnelsWindow() (*ManageTunnelsWindow, error) { } disposables.Add(mtw) - wireguardIcon, err := walk.NewIconFromResourceWithSize("$wireguard.ico", walk.Size{mtw.DPI() / 3, mtw.DPI() / 3}) //TODO: calculate DPI dynamically - if err == nil { - mtw.SetIcon(wireguardIcon) + if icon, err := loadLogoIcon(mtw.DPI() / 3); err == nil { //TODO: calculate DPI dynamically + mtw.SetIcon(icon) } mtw.SetTitle("WireGuard") font, err := walk.NewFont("Segoe UI", 9, 0) diff --git a/ui/tray.go b/ui/tray.go index 3861f9c9..668163da 100644 --- a/ui/tray.go +++ b/ui/tray.go @@ -54,9 +54,8 @@ func (tray *Tray) setup() error { tray.SetToolTip("WireGuard: Deactivated") tray.SetVisible(true) - wireguardIcon, err := walk.NewIconFromResourceWithSize("$wireguard.ico", walk.Size{tray.mtw.DPI() / 6, tray.mtw.DPI() / 6}) //TODO: calculate DPI dynamically - if err == nil { - tray.SetIcon(wireguardIcon) + if icon, err := loadLogoIcon(tray.mtw.DPI() / 6); err == nil { //TODO: calculate DPI dynamically + tray.SetIcon(icon) } tray.MouseDown().Attach(func(x, y int, button walk.MouseButton) { @@ -220,11 +219,7 @@ func (tray *Tray) onTunnelChange(tunnel *service.Tunnel, state service.TunnelSta func (tray *Tray) updateGlobalState(globalState service.TunnelState) { if icon, err := iconWithOverlayForState(globalState, tray.mtw.DPI()/6); err == nil { //TODO: calculate DPI dynamically - prior := tray.Icon() tray.SetIcon(icon) - if prior != nil { - prior.Dispose() - } } actions := tray.ContextMenu().Actions() @@ -304,7 +299,6 @@ func (tray *Tray) SetTunnelState(tunnel *service.Tunnel, state service.TunnelSta tunnelAction.SetChecked(false) if wasChecked && showNotifications { icon, _ := loadSystemIcon("imageres", 26, tray.mtw.DPI()*4/3) //TODO: this icon isn't very good..., also calculate dpi dynamically - defer icon.Dispose() tray.ShowCustom("WireGuard Deactivated", fmt.Sprintf("The %s tunnel has been deactivated.", tunnel.Name), icon) } } @@ -316,7 +310,6 @@ func (tray *Tray) UpdateFound() { iconSize := tray.mtw.DPI() / 6 //TODO: This should use dynamic DPI. menuIcon, _ := loadSystemIcon("imageres", 1, iconSize) bitmap, _ := walk.NewBitmapFromIcon(menuIcon, walk.Size{iconSize, iconSize}) - menuIcon.Dispose() action.SetImage(bitmap) action.SetDefault(true) showUpdateTab := func() { @@ -333,7 +326,6 @@ func (tray *Tray) UpdateFound() { showUpdateBalloon := func() { icon, _ := loadSystemIcon("imageres", 1, tray.mtw.DPI()*4/3) //TODO: calculate DPI dynamically tray.ShowCustom("WireGuard Update Available", "An update to WireGuard is now available. You are advised to update as soon as possible.", icon) - icon.Dispose() } timeSinceStart := time.Now().Sub(startTime) diff --git a/ui/updatepage.go b/ui/updatepage.go index a5471e9f..b228fb2d 100644 --- a/ui/updatepage.go +++ b/ui/updatepage.go @@ -28,11 +28,9 @@ func NewUpdatePage() (*UpdatePage, error) { iconSize := up.DPI() / 6 tabIcon, _ := loadSystemIcon("imageres", 1, iconSize) - defer tabIcon.Dispose() bitmap, _ := walk.NewBitmapFromIcon(tabIcon, walk.Size{iconSize, iconSize}) //TODO: this should use dynamic DPI up.SetImage(bitmap) - //TODO: make title bold up.SetLayout(walk.NewVBoxLayout()) instructions, _ := walk.NewTextLabel(up) -- cgit v1.2.3-59-g8ed1b