Shiny is an experimental, portable GUI framework for Go that provides a platform-independent abstraction for creating graphical user interfaces. It supports multiple platforms including Linux (X11, OpenGL driver), Windows, and mobile systems through a consistent API. Shiny offers low-level screen and drawing primitives, high-level widget components, and comprehensive input event handling for building desktop applications.
go get golang.org/x/exp/shiny@latestimport (
"golang.org/x/exp/shiny/driver"
"golang.org/x/exp/shiny/screen"
"golang.org/x/exp/shiny/widget"
"golang.org/x/exp/shiny/gesture"
"golang.org/x/mobile/event/lifecycle"
)package main
import (
"golang.org/x/exp/shiny/driver"
"golang.org/x/exp/shiny/screen"
"golang.org/x/mobile/event/lifecycle"
"image"
"image/color"
)
func main() {
driver.Main(func(s screen.Screen) {
w, err := s.NewWindow(nil)
if err != nil {
panic(err)
}
defer w.Release()
for {
switch e := w.NextEvent().(type) {
case lifecycle.Event:
if e.To == lifecycle.StageDead {
return
}
}
}
})
}Shiny's architecture is built on several key layers:
The screen package provides low-level access to graphics buffers and windows, forming the foundation of Shiny's graphics operations.
type Screen interface {
// NewBuffer returns a new Buffer for this screen.
NewBuffer(size image.Point) (Buffer, error)
// NewTexture returns a new Texture for this screen.
NewTexture(size image.Point) (Texture, error)
// NewWindow returns a new Window for this screen.
// A nil opts is valid and means to use the default option values.
NewWindow(opts *NewWindowOptions) (Window, error)
}type Buffer interface {
// Release releases the Buffer's resources.
Release()
// Size returns the size of the Buffer's image.
Size() image.Point
// Bounds returns the bounds of the Buffer's image.
Bounds() image.Rectangle
// RGBA returns the pixel buffer as an *image.RGBA.
RGBA() *image.RGBA
}
type Texture interface {
// Release releases the Texture's resources.
Release()
// Size returns the size of the Texture's image.
Size() image.Point
// Bounds returns the bounds of the Texture's image.
Bounds() image.Rectangle
Uploader
}
type Uploader interface {
// Upload uploads the sub-Buffer defined by src and sr to the destination.
Upload(dp image.Point, src Buffer, sr image.Rectangle)
// Fill fills that part of the destination with the given color.
Fill(dr image.Rectangle, src color.Color, op draw.Op)
}
type Drawer interface {
// Draw draws the sub-Texture defined by src and sr to the destination.
Draw(src2dst f64.Aff3, src Texture, sr image.Rectangle, op draw.Op, opts *DrawOptions)
// DrawUniform is like Draw except that the src is a uniform color instead of a Texture.
DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op, opts *DrawOptions)
// Copy copies the sub-Texture defined by src and sr to the destination.
Copy(dp image.Point, src Texture, sr image.Rectangle, op draw.Op, opts *DrawOptions)
// Scale scales the sub-Texture defined by src and sr to the destination.
Scale(dr image.Rectangle, src Texture, sr image.Rectangle, op draw.Op, opts *DrawOptions)
}
type Window interface {
Release()
EventDeque
Uploader
Drawer
Publish() PublishResult
}
type EventDeque interface {
// Send adds an event to the end of the deque.
Send(event interface{})
// SendFirst adds an event to the start of the deque.
SendFirst(event interface{})
// NextEvent returns the next event in the deque.
// Typical event types include lifecycle.Event, size.Event, paint.Event, key.Event, mouse.Event, touch.Event.
NextEvent() interface{}
}
type NewWindowOptions struct {
// Width and Height specify the dimensions of the new window.
Width, Height int
// Title specifies the window title.
Title string
}
type PublishResult struct {
// BackBufferPreserved is whether the contents of the back buffer was preserved.
BackBufferPreserved bool
}
type DrawOptions struct {
}The driver package provides the entry point for Shiny applications and platform-specific implementations.
// Main is called by the program's main function to run the graphical application.
// It calls f on the Screen, possibly in a separate goroutine, as some OS-specific
// libraries require being on 'the main thread'. It returns when f returns.
func Main(f func(screen.Screen))Windows Driver (golang.org/x/exp/shiny/driver/windriver):
package windriver
// Main provides the Windows driver for accessing a screen.
func Main(f func(screen.Screen))X11 Driver (golang.org/x/exp/shiny/driver/x11driver):
package x11driver
// Main provides the X11 driver for accessing a screen.
func Main(f func(screen.Screen))OpenGL Driver (golang.org/x/exp/shiny/driver/gldriver):
package gldriver
// Main provides an OpenGL driver for accessing a screen.
func Main(f func(screen.Screen))
// NewContext creates an OpenGL ES context with a dedicated processing thread.
func NewContext() (gl.Context, error)The gesture package provides higher-level gesture events derived from lower-level mouse and touch events.
type Event struct {
// Type is the gesture type.
Type Type
// Drag, LongPress and DoublePress are set when the gesture is recognized.
Drag bool
LongPress bool
DoublePress bool
// InitialPos is the initial position of the button press or touch.
InitialPos Point
// CurrentPos is the current position of the button or touch event.
CurrentPos Point
// Time is the event's time.
Time time.Time
}
type EventFilter struct {
EventDeque screen.EventDeque
}
// Filter filters the event from lower-level events, generating gesture events.
func (f *EventFilter) Filter(e interface{}) interface{}
type Point struct {
X, Y float32
}
type Type uint8
const (
// TypeStart and TypeEnd are the start and end of a gesture.
TypeStart Type = 0
TypeEnd Type = 1
// TypeIsLongPress, TypeIsDoublePress, TypeIsDrag indicate gesture recognition.
TypeIsLongPress Type = 10
TypeIsDoublePress Type = 11
TypeIsDrag Type = 12
// TypeTap and TypeDrag are tap and drag events.
TypeTap Type = 20
TypeDrag Type = 21
)The widget package provides high-level GUI components for building user interfaces. Widgets are organized in a tree structure and support layout, measurement, and event handling.
// RunWindow creates a new window for s, with the given widget tree, and runs its event loop.
func RunWindow(s screen.Screen, root node.Node, opts *RunWindowOptions) error
// WithLayoutData returns the given node after setting its embedded LayoutData field.
func WithLayoutData(n node.Node, layoutData interface{}) node.Node
type Axis uint8
const (
AxisNone = Axis(0)
AxisHorizontal = Axis(1)
AxisVertical = Axis(2)
AxisBoth = Axis(3) // AxisHorizontal | AxisVertical
)
type RunWindowOptions struct {
NewWindowOptions screen.NewWindowOptions
Theme theme.Theme
}type Flow struct {
node.ContainerEmbed
Axis Axis
}
// NewFlow returns a new Flow widget containing the given children.
func NewFlow(a Axis, children ...node.Node) *Flow
type Label struct {
node.LeafEmbed
Text string
ThemeColor theme.Color
}
// NewLabel returns a new Label widget.
func NewLabel(text string) *Label
type Image struct {
node.LeafEmbed
Src image.Image
SrcRect image.Rectangle
}
// NewImage returns a new Image widget for the part of a source image.
func NewImage(src image.Image, srcRect image.Rectangle) *Image
type Space struct {
node.LeafEmbed
}
// NewSpace returns a new Space widget.
func NewSpace() *Space
type Text struct {
node.LeafEmbed
}
// NewText returns a new Text widget.
func NewText(text string) *Texttype Padder struct {
node.ShellEmbed
Axis Axis
Margin unit.Value
}
// NewPadder returns a new Padder widget.
func NewPadder(a Axis, margin unit.Value, inner node.Node) *Padder
type Sizer struct {
node.ShellEmbed
NaturalWidth unit.Value
NaturalHeight unit.Value
}
// NewSizer returns a new Sizer widget of the given natural size.
func NewSizer(naturalWidth, naturalHeight unit.Value, inner node.Node) *Sizer
type Uniform struct {
node.ShellEmbed
ThemeColor theme.Color
}
// NewUniform returns a new Uniform widget of the given color.
func NewUniform(c theme.Color, inner node.Node) *Uniform
type Sheet struct {
node.ShellEmbed
}
// NewSheet returns a new Sheet widget.
func NewSheet(inner node.Node) *Sheettype FlowLayoutData struct {
// AlongWeight is the relative weight for distributing space along the Flow's axis.
AlongWeight int
// ExpandAlong controls if the child should expand along the Flow's axis.
ExpandAlong bool
// ShrinkAlong controls if the child should shrink along the Flow's axis.
ShrinkAlong bool
// ExpandAcross controls if the child should expand along the cross-axis.
ExpandAcross bool
// ShrinkAcross controls if the child should shrink along the cross-axis.
ShrinkAcross bool
}The widget/flex package provides CSS flexbox-inspired layout for complex UI arrangements.
type Flex struct {
node.ContainerEmbed
Direction Direction
Wrap FlexWrap
Justify Justify
AlignItems AlignItem
AlignContent AlignContent
}
// NewFlex returns a new Flex widget containing the given children.
func NewFlex(children ...node.Node) *Flex
type Direction uint8
const (
Row Direction = iota
RowReverse
Column
ColumnReverse
)
type FlexWrap uint8
const (
NoWrap FlexWrap = iota
Wrap
WrapReverse
)
type Justify uint8
const (
JustifyStart Justify = iota
JustifyEnd
JustifyCenter
JustifySpaceBetween
JustifySpaceAround
)
type AlignItem uint8
const (
AlignItemAuto AlignItem = iota
AlignItemStart
AlignItemEnd
AlignItemCenter
AlignItemBaseline
AlignItemStretch
)
type AlignContent uint8
const (
AlignContentStretch AlignContent = iota
AlignContentStart
AlignContentEnd
AlignContentCenter
AlignContentSpaceBetween
AlignContentSpaceAround
)
type Basis uint8
const (
Auto Basis = iota
Definite
)
type LayoutData struct {
MinSize image.Point
MaxSize *image.Point
// Grow determines how much a node will grow relative to its siblings.
Grow float64
// Shrink determines how much a node will shrink relative to its siblings.
Shrink *float64
// Basis determines the initial size of the node on the main axis.
Basis Basis
BasisPx int
Align AlignItem
// BreakAfter forces the next node onto the next flex line.
BreakAfter bool
}The widget/node package provides the foundational structure for building custom widgets through a composition-based approach.
type Node interface {
Measure(t *theme.Theme, widthHint, heightHint int)
Layout(t *theme.Theme)
Paint(ctx *PaintContext, origin image.Point) error
PaintBase(ctx *PaintBaseContext, origin image.Point) error
OnChildMarked(child Node, newMarks Marks)
OnInputEvent(e interface{}, origin image.Point) EventHandled
OnLifecycleEvent(e lifecycle.Event)
FirstChild() Node
LastChild() Node
Parent() Node
}
type Embed struct {
// Wrapper is the outer type that wraps (embeds) this type.
Wrapper Node
// Parent, FirstChild, LastChild, PrevSibling and NextSibling describe the widget tree structure.
Parent, FirstChild, LastChild, PrevSibling, NextSibling *Embed
// LayoutData is layout-specific data for this node.
LayoutData interface{}
// MeasuredSize is the widget's natural size, in pixels.
MeasuredSize image.Point
// Rect is the widget's position and actual size, in pixels.
Rect image.Rectangle
// Marks are a bitfield of node state.
Marks Marks
}
type ContainerEmbed struct { Embed }
type LeafEmbed struct { Embed }
type ShellEmbed struct { Embed }
type EventHandled bool
const (
NotHandled = EventHandled(false)
Handled = EventHandled(true)
)
type Marks uint8
const (
// MarkNeedsMeasureLayout marks this node as needing Measure and Layout calls.
MarkNeedsMeasureLayout = Marks(1 << 0)
// MarkNeedsPaint marks this node as needing a Paint call.
MarkNeedsPaint = Marks(1 << 1)
// MarkNeedsPaintBase marks this node as needing a PaintBase call.
MarkNeedsPaintBase = Marks(1 << 2)
)
const NoHint = -1The widget/glwidget package provides OpenGL rendering capabilities within the widget system.
type GL struct {
node.LeafEmbed
Ctx gl.Context
}
// NewGL creates a GL widget with a Draw function called when painted.
func NewGL(drawFunc func(*GL)) *GL
// Publish renders the default framebuffer of Ctx onto the area of the window occupied by the widget.
func (w *GL) Publish()The widget/theme package provides theme management for consistent styling across widgets.
type Theme struct {
// Has unexported fields
}
type Color interface {
Color(*Theme) color.Color
Uniform(*Theme) *image.Uniform
}
// StaticColor adapts a color.Color to a theme Color.
func StaticColor(c color.Color) Color
type FontFaceCatalog interface {
AcquireFontFace(FontFaceOptions) font.Face
ReleaseFontFace(FontFaceOptions, font.Face)
}
type FontFaceOptions struct {
Style font.Style
Weight font.Weight
}
type Palette [PaletteLen]image.Uniform
const (
Light = PaletteIndex(0)
Neutral = PaletteIndex(1)
Dark = PaletteIndex(2)
Accent = PaletteIndex(3)
Foreground = PaletteIndex(4)
Background = PaletteIndex(5)
PaletteLen = 6
)
const DefaultDPI = 72.0
var DefaultFontFaceCatalog FontFaceCatalog
var DefaultPalette Palette
var Default *ThemeThe unit package provides flexible unit conversion for graphics measurements.
type Unit uint8
const (
// Px is a physical pixel, regardless of the DPI resolution.
Px Unit = iota
// Dp is 1 density independent pixel: 1/160th of an inch.
Dp
// Pt is 1 point: 1/72nd of an inch.
Pt
// Mm is 1 millimetre: 1/25.4th of an inch.
Mm
// In is 1 inch.
In
// Em is the height of the active font face.
Em
// Ex is the x-height of the active font face.
Ex
// Ch is the character width of the numeral zero glyph.
Ch
)
type Value struct {
F float64
U Unit
}
// Constructor functions for values in different units
func Pixels(f float64) Value
func DIPs(f float64) Value
func Points(f float64) Value
func Millimetres(f float64) Value
func Inches(f float64) Value
func Ems(f float64) Value
func Exs(f float64) Value
func Chs(f float64) Value
type Converter interface {
// Convert converts v to the given unit.
Convert(v Value, to Unit) Value
// Pixels converts v to a 26.6 fixed-point number of physical pixels.
Pixels(v Value) fixed.Int26_6
}
const (
DensityIndependentPixelsPerInch = 160
MillimetresPerInch = 25.4
PointsPerInch = 72
)The text package provides paragraph text layout and manipulation capabilities.
type Frame struct {
// Has unexported fields
}
// NewCaret returns a new Caret at the start of this Frame.
func (f *Frame) NewCaret() *Caret
// Height returns the height in pixels of this Frame.
func (f *Frame) Height() int
// Len returns the number of bytes in the Frame's text.
func (f *Frame) Len() int
// LineCount returns the number of Lines in this Frame.
func (f *Frame) LineCount() int
// ParagraphCount returns the number of Paragraphs in this Frame.
func (f *Frame) ParagraphCount() int
// FirstParagraph returns the first paragraph of this frame.
func (f *Frame) FirstParagraph() *Paragraph
// SetFace sets the font face for measuring text.
func (f *Frame) SetFace(face font.Face)
// SetMaxWidth sets the target maximum width of a Line of text.
func (f *Frame) SetMaxWidth(m fixed.Int26_6)
type Caret struct {
// Has unexported fields
}
// Read satisfies the io.Reader interface.
func (c *Caret) Read(buf []byte) (n int, err error)
// ReadByte returns the next byte after the Caret and increments the Caret.
func (c *Caret) ReadByte() (x byte, err error)
// ReadRune returns the next rune after the Caret and increments the Caret.
func (c *Caret) ReadRune() (r rune, size int, err error)
// Write inserts s into the Frame's text at the Caret and increments the Caret.
func (c *Caret) Write(s []byte) (n int, err error)
// WriteByte inserts x into the Frame's text at the Caret and increments the Caret.
func (c *Caret) WriteByte(x byte) error
// WriteRune inserts r into the Frame's text at the Caret and increments the Caret.
func (c *Caret) WriteRune(r rune) (size int, err error)
// WriteString inserts s into the Frame's text at the Caret and increments the Caret.
func (c *Caret) WriteString(s string) (n int, err error)
// Seek satisfies the io.Seeker interface.
func (c *Caret) Seek(offset int64, whence int) (int64, error)
// Delete deletes nBytes bytes in the specified direction from the Caret's location.
func (c *Caret) Delete(dir Direction, nBytes int) (dBytes int)
// DeleteRunes deletes nRunes runes in the specified direction.
func (c *Caret) DeleteRunes(dir Direction, nRunes int) (dRunes, dBytes int)
// Close closes the Caret.
func (c *Caret) Close() error
type Paragraph struct {
// Has unexported fields
}
// FirstLine returns the first Line of this Paragraph.
func (p *Paragraph) FirstLine(f *Frame) *Line
// Next returns the next Paragraph after this one.
func (p *Paragraph) Next(f *Frame) *Paragraph
type Line struct {
// Has unexported fields
}
// FirstBox returns the first Box of this Line.
func (l *Line) FirstBox(f *Frame) *Box
// Height returns the height in pixels of this Line.
func (l *Line) Height(f *Frame) int
// Next returns the next Line after this one in the Paragraph.
func (l *Line) Next(f *Frame) *Line
type Box struct {
// Has unexported fields
}
// Text returns the Box's text.
func (b *Box) Text(f *Frame) []byte
// TrimmedText returns the Box's text, trimmed right of any white space if last Box in Line.
func (b *Box) TrimmedText(f *Frame) []byte
// Next returns the next Box after this one in the Line.
func (b *Box) Next(f *Frame) *Box
type Direction bool
const (
Forwards Direction = false
Backwards Direction = true
)
const (
SeekSet int = 0
SeekCur int = 1
SeekEnd int = 2
)The iconvg package provides support for compact binary vector graphics format, useful for icons and logos.
// Decode decodes an IconVG graphic.
func Decode(dst Destination, src []byte, opts *DecodeOptions) error
// UpgradeToFileFormatVersion1 upgrades IconVG data from version 0 to version 1.
func UpgradeToFileFormatVersion1(v0 []byte, opts *UpgradeToFileFormatVersion1Options) (v1 []byte, retErr error)
type Destination interface {
Reset(m Metadata)
SetCSel(cSel uint8)
SetNSel(nSel uint8)
SetCReg(adj uint8, incr bool, c Color)
SetNReg(adj uint8, incr bool, f float32)
SetLOD(lod0, lod1 float32)
StartPath(adj uint8, x, y float32)
ClosePathEndPath()
ClosePathAbsMoveTo(x, y float32)
ClosePathRelMoveTo(x, y float32)
AbsHLineTo(x float32)
RelHLineTo(x float32)
AbsVLineTo(y float32)
RelVLineTo(y float32)
AbsLineTo(x, y float32)
RelLineTo(x, y float32)
AbsSmoothQuadTo(x, y float32)
RelSmoothQuadTo(x, y float32)
AbsQuadTo(x1, y1, x, y float32)
RelQuadTo(x1, y1, x, y float32)
AbsSmoothCubeTo(x2, y2, x, y float32)
RelSmoothCubeTo(x2, y2, x, y float32)
AbsCubeTo(x1, y1, x2, y2, x, y float32)
RelCubeTo(x1, y1, x2, y2, x, y float32)
AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)
RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)
}
type Color struct {
// Has unexported fields
}
// RGBAColor returns a direct Color.
func RGBAColor(c color.RGBA) Color
// PaletteIndexColor returns an indirect Color referring to a palette index.
func PaletteIndexColor(i uint8) Color
// CRegColor returns an indirect Color referring to a color register.
func CRegColor(i uint8) Color
// BlendColor returns an indirect Color that blends two other Colors.
func BlendColor(t, c0, c1 uint8) Color
// Resolve resolves the Color's RGBA value.
func (c Color) Resolve(pal *Palette, cReg *[64]color.RGBA) color.RGBA
type ColorType uint8
const (
ColorTypeRGBA ColorType = iota
ColorTypePaletteIndex
ColorTypeCReg
ColorTypeBlend
)
type Encoder struct {
// HighResolutionCoordinates is whether coordinates should be encoded at best resolution.
HighResolutionCoordinates bool
}
// Reset resets the Encoder for the given Metadata.
func (e *Encoder) Reset(m Metadata)
// Bytes returns the encoded form.
func (e *Encoder) Bytes() ([]byte, error)
// Encoder drawing methods: AbsLineTo, RelLineTo, etc.
func (e *Encoder) AbsLineTo(x, y float32)
func (e *Encoder) RelLineTo(x, y float32)
// ... additional drawing methods
type DecodeOptions struct {
// Palette is an optional 64 color palette.
Palette *Palette
}
type Palette [64]color.RGBA
var DefaultPalette Palette
var DefaultViewBox RectangleThe imageutil package provides utility functions for image manipulation.
// Border returns four rectangles that together contain those points between r and r.Inset(inset).
func Border(r image.Rectangle, inset int) [4]image.RectangleThe materialdesign/colornames package provides named colors from the Material Design specification.
// Extensive set of named color variables following Material Design guidelines
var (
Red50, Red100, Red200, ... color.RGBA
Pink50, Pink100, Pink200, ... color.RGBA
Purple50, Purple100, ... color.RGBA
// ... and many more color palettes
// Neutral colors
Grey50, Grey100, ... Grey900 color.RGBA
BlueGrey50, BlueGrey100, ... BlueGrey900 color.RGBA
Black, White color.RGBA
)
// Map provides string-to-color lookup
var Map map[string]color.RGBApackage main
import (
"fmt"
"golang.org/x/exp/shiny/driver"
"golang.org/x/exp/shiny/screen"
"golang.org/x/exp/shiny/widget"
"golang.org/x/exp/shiny/widget/node"
"golang.org/x/mobile/event/lifecycle"
"image/color"
)
func main() {
driver.Main(func(s screen.Screen) {
// Create a simple label widget
label := widget.NewLabel("Hello, Shiny!")
// Create a flow layout with vertical axis
root := widget.NewFlow(widget.AxisVertical, label)
// Run the window with the widget tree
err := widget.RunWindow(s, root, &widget.RunWindowOptions{
NewWindowOptions: screen.NewWindowOptions{
Width: 400,
Height: 300,
Title: "Shiny App",
},
})
if err != nil {
fmt.Println("Error:", err)
}
})
}