Fyne — Interfaces#
Interface catalog#
CanvasObject#
- Package:
fyne.io/fyne/v2 - File:
canvasobject.go - Methods:
MinSize() Size Move(Position) Position() Position Resize(Size) Size() Size Hide() Visible() bool Show() Refresh() - Purpose: The root contract for every visual element. Defines geometry (position, size, min-size) and visibility. Any object that can appear on a canvas must satisfy this interface.
- Implementations: Every widget and primitive in the toolkit —
widget.Button,widget.Entry,canvas.Rectangle,canvas.Image,canvas.Text, and all containers embedinternal/widget.Basewhich provides these methods. - Design quality: Well-focused. Nine methods, all tightly related to placement and visibility. Notably does NOT include rendering or styling — those are delegated to
WidgetRenderer. Follows ISP cleanly.
Widget#
- Package:
fyne.io/fyne/v2 - File:
widget.go - Methods:
CanvasObject // embedded CreateRenderer() WidgetRenderer - Purpose: Distinguishes stateful widgets from primitive canvas objects. The single extension point (
CreateRenderer) decouples widget state from the render implementation; the framework calls this once per widget instance and caches the result. - Implementations: All ~40 public widgets in
widget/:Button,Entry,Label,List,Tree,Table,RichText,TextGrid, etc. All embedinternal/widget.Base. - Design quality: Minimal by design — one method beyond
CanvasObject. The interface is thin because rendering is fully delegated toWidgetRenderer. This is textbook ISP: clients that only need geometry useCanvasObject; clients that need rendering useWidget.
WidgetRenderer#
- Package:
fyne.io/fyne/v2 - File:
widget.go - Methods:
Destroy() Layout(Size) MinSize() Size Objects() []CanvasObject Refresh() - Purpose: The visual representation of a widget, separated from its state. Renderers are created on demand by
Widget.CreateRenderer(), cached byinternal/cache(one per widget instance), and destroyed when the widget leaves the scene. This split allows GPU resources to be held only while the widget is visible. - Implementations: Every widget provides an unexported renderer struct (e.g.,
buttonRendererinwidget/button.go). Thetestpackage providesWindowlessCanvasandSoftwarePainterfor headless rendering. - Design quality: Five methods in a cohesive group (layout, draw, cleanup).
Objects()returns the children the framework should recurse over, so the renderer fully controls what gets painted without breaking the object tree.
App#
- Package:
fyne.io/fyne/v2 - File:
app.go - Methods:
NewWindow(title string) Window OpenURL(url *url.URL) error Icon() Resource SetIcon(Resource) Run() Quit() Driver() Driver UniqueID() string SendNotification(*Notification) Settings() Settings Preferences() Preferences Storage() Storage Lifecycle() Lifecycle Metadata() AppMetadata CloudProvider() CloudProvider SetCloudProvider(CloudProvider) Clipboard() Clipboard - Purpose: The top-level application contract. Aggregates all cross-cutting concerns: window management, notifications, theming, persistent preferences, storage, and the app lifecycle. Provides access to all major sub-systems through accessor methods.
- Implementations:
app.fyneApp(the only implementation, wired inapp/via build tags). There is one global instance per process, stored in anatomic.Pointer[App]. - Design quality: Broad (17 methods) but coherent — this is a facade interface, not a focused component interface. Each method group (
Settings,Preferences,Storage,Lifecycle) could be its own narrower interface (and is, for internal use). The breadth is a trade-off for developer convenience: users hold a singlefyne.Appand get everything from it.
Canvas#
- Package:
fyne.io/fyne/v2 - File:
canvas.go - Methods:
Content() CanvasObject SetContent(CanvasObject) Refresh(CanvasObject) Focus(Focusable) FocusNext() FocusPrevious() Unfocus() Focused() Focusable Size() Size Scale() float32 Overlays() OverlayStack OnTypedRune() func(rune) SetOnTypedRune(func(rune)) OnTypedKey() func(*KeyEvent) SetOnTypedKey(func(*KeyEvent)) AddShortcut(shortcut Shortcut, handler func(shortcut Shortcut)) RemoveShortcut(shortcut Shortcut) Capture() image.Image PixelCoordinateForPosition(Position) (int, int) InteractiveArea() (Position, Size) - Purpose: The surface on which all content is drawn. Manages content root, focus management, shortcut registration, overlay stack, and pixel/coordinate mapping. Also provides
Capture()for screenshot and testing. - Implementations:
internal/driver/glfw.glCanvas(desktop),internal/driver/mobile.mobileCanvas, both embeddinginternal/driver/common.Canvaswhich provides all shared logic.test.WindowlessCanvasfor headless testing. - Design quality: 20 methods — the broadest interface in the root package. The focus management methods (
Focus,FocusNext,FocusPrevious,Unfocus,Focused) could arguably be aFocusManagersub-interface, but grouping them here gives widget authors a single surface to work with.
Driver#
- Package:
fyne.io/fyne/v2 - File:
driver.go - Methods:
CreateWindow(string) Window AllWindows() []Window RenderedTextSize(text string, fontSize float32, style TextStyle, source Resource) (Size, float32) CanvasForObject(CanvasObject) Canvas AbsolutePositionForObject(CanvasObject) Position Device() Device Run() Quit() StartAnimation(*Animation) StopAnimation(*Animation) DoubleTapDelay() time.Duration SetDisableScreenBlanking(bool) DoFromGoroutine(fn func(), wait bool) - Purpose: The platform abstraction for a rendering backend. Manages windows, the event loop, text rendering, coordinate mapping, animations, and the critical
DoFromGoroutinemechanism for marshalling work to the UI thread. - Implementations:
internal/driver/glfw.gLDriver(desktop OpenGL via GLFW),internal/driver/mobile.mobileDriver(iOS/Android),internal/driver/software.SoftwareDriver/embedded driver (headless/CI). Backend is selected at compile time via build tags inapp/. - Design quality: 13 methods covering genuinely disparate concerns (windows, text measurement, animation, threading). This interface exists primarily as the seam between
app/and theinternal/driver/implementations; application code rarely interacts withDriverdirectly.
Theme#
- Package:
fyne.io/fyne/v2 - File:
theme.go - Methods:
Color(ThemeColorName, ThemeVariant) color.Color Font(TextStyle) Resource Icon(ThemeIconName) Resource Size(ThemeSizeName) float32 - Purpose: Provides the visual tokens (colours, fonts, icons, sizes) for the entire toolkit. All widgets query the current theme through these four lookup methods using named constants (e.g.,
theme.ColorNamePrimary,theme.SizeNamePadding). - Implementations:
theme.defaultTheme(built-in light/dark), any app-supplied customThemeimplementation (a popular extension point).internal/theme.FeatureThemeadds optional feature-gated colours. - Design quality: Exceptionally well-designed. Four methods, fully orthogonal. The use of named string types (
ThemeColorName,ThemeIconName,ThemeSizeName) as keys (rather than separate methods per token) means adding new tokens doesn’t break the interface. Contrast withLegacyTheme(21 methods, one per token) — the v2 refactor is a textbook ISP improvement.
DataItem / DataListener#
- Package:
fyne.io/fyne/v2/data/binding - File:
binding.go - Methods:
// DataItem: AddListener(DataListener) RemoveListener(DataListener) // DataListener: DataChanged() - Purpose: The observer pattern foundation for the data binding system.
DataItemis the observable;DataListeneris the observer. Thebasestruct in the same package provides the shared listener-list implementation embedded by all concrete binding types. - Implementations: Every binding type implements
DataItem—Item[T],DataList,DataMap,DataTreeand their external variants.DataListeneris implemented by widget connector types andNewDataListener(fn func())for inline callbacks. - Design quality: Minimal and clean. Two methods on
DataItem, one onDataListener. The generics-basedItem[T]interface (added in 2.6) extendsDataItemwith typedGet()/Set(), preserving backward compatibility.
Item[T] / ExternalItem[T]#
- Package:
fyne.io/fyne/v2/data/binding - File:
items.go - Methods:
// Item[T]: DataItem // embedded Get() (T, error) Set(T) error // ExternalItem[T]: Item[T] // embedded Reload() error - Purpose: Generic typed bindings (Go 1.18+).
Item[T]wraps a managed value;ExternalItem[T]wraps a pointer to an existing variable. Type aliases for concrete types (Bool = Item[bool],String = Item[string], etc.) provide backward-compatible named types. - Implementations:
item[T](internal),externalItem[T](internal). Type aliases likebinding.Bool,binding.String,binding.Intare defined as= Item[bool],= Item[string], etc. - Design quality: Excellent use of generics. The interface hierarchy (
DataItem→Item[T]→ExternalItem[T]) is a clean three-level embedding chain. TheReload()method onExternalItem[T]is the only addition for external values — models minimal extension principle.
Repository (and extensions)#
- Package:
fyne.io/fyne/v2/storage/repository - File:
repository.go - Methods (base Repository):
Exists(fyne.URI) (bool, error) Reader(fyne.URI) (fyne.URIReadCloser, error) CanRead(fyne.URI) (bool, error) Destroy(string) - Extension interfaces (all embed
Repository):CustomURIRepository: addsParseURI(string) (fyne.URI, error)WritableRepository: addsWriter,CanWrite,DeleteAppendableRepository: extendsWritableRepository, addsAppenderListableRepository: addsCanList,List,CreateListableHierarchicalRepository: addsParent,ChildCopyableRepository: addsCopyMovableRepository: addsMoveDeleteAllRepository: extendsWritableRepository, addsDeleteAll
- Purpose: A URI-scheme-based storage abstraction. Backends are registered per URI scheme via
Register(scheme, Repository). Higher-levelstorage.*functions use type assertions to discover optional capabilities at runtime. - Implementations: Built-in
fileRepository(local filesystem, registered for “file” scheme),httpRepository(HTTP read-only), plus app-specific repositories. Thestorage/repositorypackage itself provides generic fallback implementations (GenericCopy,GenericMove,GenericParent,GenericDeleteAll). - Design quality: The interface hierarchy is a deliberate ISP showcase. The base
Repository(4 methods) covers the minimum. Optional capabilities are discovered via type assertion rather than empty methods or flags. This is the same pattern asio.Reader/io.ReadWriter/io.ReadWriteSeeker— incrementally composable capability interfaces. The use of type assertions (not embedding in consumers) keeps the runtime dispatch explicit and auditable.
Painter (internal)#
- Package:
fyne.io/fyne/v2/internal/painter/gl - File:
painter.go - Methods:
Init() Capture(fyne.Canvas) image.Image Clear() Free(fyne.CanvasObject) Paint(fyne.CanvasObject, fyne.Position, fyne.Size) SetFrameBufferScale(float32) SetOutputSize(int, int) StartClipping(fyne.Position, fyne.Size) StopClipping() - Purpose: The internal seam between the canvas event/layout system and the actual rendering backend. Both
glPainter(OpenGL via CGo) andsoftwarePainter(stdlibimage/*) implement this interface, making backend substitution transparent to the canvas layer. - Implementations:
gl.painter(OpenGL, production),software.painter(CPU-only, used in tests and the embedded driver). Switching is done at compile time via build tags. - Design quality: 9 methods, all rendering-lifecycle concerns. The interface lives in
internal/— it is an implementation detail, not part of the public API. Its existence is what enables CI to run the full widget test suite without GPU hardware.
Behavior interfaces (Tappable, Draggable, Focusable, Scrollable, Disableable, etc.)#
- Package:
fyne.io/fyne/v2 - File:
canvasobject.go - Methods per interface: 1–4 methods each
Tappable: Tapped(*PointEvent) DoubleTappable: DoubleTapped(*PointEvent) SecondaryTappable:TappedSecondary(*PointEvent) Draggable: Dragged(*DragEvent), DragEnd() Focusable: FocusGained(), FocusLost(), TypedRune(rune), TypedKey(*KeyEvent) Scrollable: Scrolled(*ScrollEvent) Disableable: Enable(), Disable(), Disabled() bool Shortcutable: TypedShortcut(Shortcut) Tabbable: AcceptsTab() bool Validatable: Validate() error - Purpose: Opt-in interaction contracts. The canvas and driver hit-test each
CanvasObjectand perform type assertions to discover capabilities at runtime. A widget gains click-handling by implementingTappable, without touching any base class. - Implementations: Any widget that needs the behaviour implements it.
widget.ButtonimplementsTappable+Disableable.widget.EntryimplementsFocusable+Tappable+Draggable+Scrollable+Shortcutable+Tabbable+Validatable. Desktop-specific capabilities live indriver/desktop/(Hoverable,Cursorable,Keyable,Mouseable). - Design quality: Near-perfect ISP application. Every interface has 1–4 methods. Widgets only implement what they need. The runtime type-assertion dispatch means capabilities compose without inheritance. The split into
canvasobject.go(touch/keyboard) anddriver/desktop/(desktop-only) avoids polluting mobile builds with desktop-only method sets.
Interface patterns#
- Size distribution: Extremely small. The root-package behavior interfaces average 1.8 methods. Even
App(the broadest facade, 17 methods) andCanvas(20 methods) are outliers justified by their role as top-level facades. The median across all ~90 interfaces is 2 methods. - Embedding: Pervasive and systematic.
WidgetembedsCanvasObject.Item[T]embedsDataItem.ExternalItem[T]embedsItem[T]. The entireRepositoryhierarchy composes via embedding:WritableRepositoryembedsRepository,AppendableRepositoryembedsWritableRepository.driver/desktop.Canvasembedsfyne.Canvas. - Implicit satisfaction: Consumer-defined throughout. The root package defines all interfaces;
internal/andapp/packages implement them. Application code and third-party packages implement behavior interfaces (Tappable,Theme,DataItem) to hook into the framework. No explicitimplementsdeclarations exist anywhere. - stdlib interfaces used:
io.Reader/io.Closer→URIReadCloserembeds bothio.Writer/io.Closer→URIWriteCloserembeds bothimage.Image→ returned byCanvas.Capture()and used inPainter.Capture()color.Color→ returned byTheme.Color()fmt.Stringer→URIhas aString() stringmethod (satisfiesStringerimplicitly)
Key abstractions#
CanvasObject+ behavior interfaces (Tappable, Focusable, etc.): The composition model that allows any object to acquire interaction capabilities without inheritance. This is Fyne’s central architectural choice — it replaces the class hierarchy typical of traditional GUI frameworks (Qt’s QAbstractItem, Java’s JComponent) with small, composable interfaces. The canvas discovers capabilities at runtime via type assertions, which is idiomatic Go and keeps widgets lightweight.Widget/WidgetRenderersplit: The only two-interface pattern in Fyne that is explicitly a framework contract (not just capability opt-in).Widgetowns state;WidgetRendererowns pixels. The cache ininternal/cachebridges them. This separation allows the framework to destroy and recreate renderers on theme changes or when widgets leave the viewport, while widget state persists. It is a deliberate Model/View split enforced by Go interfaces.Theme(v2 design): The 4-method lookup-by-name design is one of the cleanest theme API designs in any Go GUI framework. It is extensible (new named constants don’t break the interface), testable (any struct with 4 methods satisfies it), and avoids the 21-method explosion ofLegacyTheme. Worth citing as an ISP success story.Repositoryhierarchy: The capability-composition pattern for storage backends is a masterclass in progressive interface extension. The baseRepository(4 methods) is genuinely minimally viable. Each extension interface adds exactly the methods for one capability. Type assertions at thestorage.*function level replace method dispatch — callers opt into advanced features only when the backend supports them. No method stubs, no panics, noNotImplementedreturns.DataItem/Item[T]/ExternalItem[T]chain: Shows how generics (Go 1.18) extend an existing non-generic interface hierarchy without breaking backward compatibility. TheDataIteminterface (added in 2.0) is unmodified; typed access is layered on top asItem[T]. Named type aliases (Bool = Item[bool]) preserve the pre-generics API surface. This is a textbook backward-compatible generics migration.
Interface-driven extensibility#
Fyne uses interfaces for extensibility at three levels:
1. Rendering backends (Driver, Painter): The entire platform abstraction is hidden behind fyne.Driver and the internal gl.Painter. Adding a new backend (e.g., a WASM renderer or a Vulkan backend) requires only satisfying these interfaces and adding a build-tagged file in app/. Application code is completely unaffected.
2. Behavior opt-in (Tappable, Theme, DataItem, etc.): Third-party widgets gain framework integration by implementing any behavior interface. This is the primary extension point for widget library authors. There is no registration step — the framework discovers capabilities at runtime via type assertion. The driver/desktop/ sub-package extends this with desktop-only behavior interfaces (Hoverable, Cursorable), which the GLFW driver checks and the mobile driver ignores.
3. Storage backends (Repository hierarchy): The scheme-based Repository registry allows third-party packages to add new URI schemes (e.g., S3, SFTP, in-memory) with full integration into the storage.* API. The type-assertion capability pattern means third parties implement only what their backend supports, with no stubs or no-op implementations required.
Theme customization is the most commonly exercised extensibility point: any application can supply a custom Theme implementation and call app.Settings().SetTheme(myTheme). The 4-method interface makes this trivial — a struct that overrides only Color() while delegating the rest to the default theme is ~15 lines of code.