package control import ( "context" "fmt" "log" "github.com/bnema/dumber/pkg/webkit" "github.com/bnema/dumber/services" ) // ZoomController manages zoom functionality for the WebView type ZoomController struct { currentURL string lastZoomDomain string programmaticChange bool browserService *services.BrowserService webView *webkit.WebView } // WebViewInterface defines the interface for WebView zoom operations type WebViewInterface interface { GetCurrentURL() string SetZoom(level float64) error InjectScript(script string) error RegisterZoomChangedHandler(handler func(float64)) RegisterURIChangedHandler(handler func(string)) } // NewZoomController creates a new zoom controller func NewZoomController(browserService *services.BrowserService, webView *webkit.WebView) *ZoomController { return &ZoomController{ currentURL: "dumb://homepage", browserService: browserService, webView: webView, } } // RegisterHandlers sets up the zoom-related event handlers func (z *ZoomController) RegisterHandlers() { z.webView.RegisterZoomChangedHandler(z.handleZoomChange) z.webView.RegisterURIChangedHandler(z.handleURIChange) } // handleURIChange responds to URL changes and applies saved zoom levels func (z *ZoomController) handleURIChange(url string) { z.currentURL = url if url == "" { return } ctx := context.Background() currentDomain := services.ZoomKeyForLog(url) if zoomLevel, err := z.browserService.GetZoomLevel(ctx, url); err == nil { z.programmaticChange = true if err := z.webView.SetZoom(zoomLevel); err == nil { log.Printf("[zoom] loaded %.2f for %s", zoomLevel, currentDomain) // Only show toast when entering a new domain (not on first load) if currentDomain != z.lastZoomDomain && z.lastZoomDomain != "" { z.showZoomToast(zoomLevel) } z.lastZoomDomain = currentDomain } z.programmaticChange = false } } // handleZoomChange responds to zoom level changes and persists them func (z *ZoomController) handleZoomChange(level float64) { url := z.webView.GetCurrentURL() if url == "" { return } ctx := context.Background() if err := z.browserService.SetZoomLevel(ctx, url, level); err != nil { log.Printf("[zoom] failed to save level %.2f for %s: %v", level, url, err) return } key := services.ZoomKeyForLog(url) log.Printf("[zoom] saved %.2f for %s", level, key) // Only show toast for user-initiated zoom changes, not programmatic ones if !z.programmaticChange { z.showZoomToast(level) } } // showZoomToast displays a zoom level notification func (z *ZoomController) showZoomToast(level float64) { js := fmt.Sprintf(`try { if (typeof window.__dumber_showZoomToast === 'function') { window.__dumber_showZoomToast(%f); } else { console.warn('[dumber] Zoom toast function not available yet'); } } catch(e) { console.error('[dumber] Zoom toast error:', e); }`, level) if err := z.webView.InjectScript(js); err != nil { log.Printf("[zoom] failed to show toast: %v", err) } } // ApplyInitialZoom sets the initial zoom level for the current URL func (z *ZoomController) ApplyInitialZoom() { ctx := context.Background() url := z.webView.GetCurrentURL() if url == "" { url = "dumb://homepage" } if zoomLevel, err := z.browserService.GetZoomLevel(ctx, url); err == nil { z.programmaticChange = true if err := z.webView.SetZoom(zoomLevel); err != nil { log.Printf("Warning: failed to set initial zoom: %v", err) } else { key := services.ZoomKeyForLog(url) log.Printf("[zoom] loaded %.2f for %s", zoomLevel, key) } z.programmaticChange = false } } // ApplyZoomForURL applies zoom for a specific URL (used for navigation) func (z *ZoomController) ApplyZoomForURL(url string) { ctx := context.Background() if zoomLevel, err := z.browserService.GetZoomLevel(ctx, url); err == nil { z.programmaticChange = true if err := z.webView.SetZoom(zoomLevel); err != nil { log.Printf("Warning: failed to set zoom: %v", err) } else { key := services.ZoomKeyForLog(url) log.Printf("[zoom] loaded %.2f for %s", zoomLevel, key) } z.programmaticChange = false } }