Further practices#
window options
// ref: https://github.com/gioui/gio/blob/main/app/os.go#L23
// ref: https://github.com/gioui/gio/blob/main/app/window.go#L135
// ref: https://github.com/gioui/gio/blob/main/io/system/decoration.go#L17
package main
import (
"fmt"
"log"
"os"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
)
type (
C = layout.Context
D = layout.Dimensions
)
func main() {
go func() {
w := app.NewWindow(
app.Title("Window options"),
app.Size(unit.Dp(400), unit.Dp(600)),
// app.MaxSize(unit.Dp(500), unit.Dp(700)),
// app.MinSize(unit.Dp(300), unit.Dp(500)),
app.Decorated(true),
// app.Maximized.Option(),
// app.Minimized.Option(),
// app.Fullscreen.Option(),
)
if err := run(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func run(w *app.Window) error {
var ops op.Ops
var button_maximize widget.Clickable
var button_unMaximize widget.Clickable
var button_minimize widget.Clickable
var button_800x600 widget.Clickable
var button_notDecorated widget.Clickable
var button_centerMe widget.Clickable
var button_close widget.Clickable
th := material.NewTheme()
for {
switch e := w.NextEvent().(type) {
case app.FrameEvent:
gtx := app.NewContext(&ops, e)
// BUG: if clicked maximize and then click other buttons, the window will return to the last not-max size
// eg.: when click on 800x600 -> maximize -> 500x500 (it will return to 800x600 window),
// if click on 500x500 again, it will resize to correct 500x500 size.
if button_maximize.Clicked(gtx) {
fmt.Println("Maximize button clicked!")
// w.Option(app.Decorated(true))
// w.Option(app.Maximized.Option())
w.Perform(system.ActionMaximize)
}
if button_unMaximize.Clicked(gtx) {
fmt.Println("Unmaximize button clicked!")
// w.Option(app.Windowed.Option())
w.Perform(system.ActionUnmaximize)
}
if button_minimize.Clicked(gtx) {
fmt.Println("Miniimize button clicked!")
// w.Option(app.Minimized.Option())
w.Perform(system.ActionMinimize)
}
if button_800x600.Clicked(gtx) {
fmt.Println("800x600 button clicked!")
w.Option(app.Decorated(true))
w.Option(app.Size(unit.Dp(800), unit.Dp(600)))
}
if button_notDecorated.Clicked(gtx) {
fmt.Println("NotDecorated button clicked!")
w.Option(app.Size(unit.Dp(500), unit.Dp(500)))
w.Option(app.Decorated(false))
}
if button_centerMe.Clicked(gtx) {
fmt.Println("CenterMe button clicked!")
w.Perform(system.ActionCenter)
}
if button_close.Clicked(gtx) {
fmt.Println("Close button clicked!")
w.Perform(system.ActionClose)
}
btn1 := material.Button(th, &button_maximize, "Maximize")
btn2 := material.Button(th, &button_unMaximize, "UnMaximize")
btn3 := material.Button(th, &button_minimize, "Minimize")
btn4 := material.Button(th, &button_800x600, "800x600")
btn5 := material.Button(th, &button_notDecorated, "NotDecorated(500x500)")
btn6 := material.Button(th, &button_centerMe, "CenterMe")
btn7 := material.Button(th, &button_close, "Close")
inset := layout.Inset{
Top: unit.Dp(0),
Right: unit.Dp(10),
Bottom: unit.Dp(10),
Left: unit.Dp(10),
}
layout.Flex{
Axis: layout.Vertical,
Spacing: layout.SpaceStart,
}.Layout(gtx,
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn1.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn2.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn3.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn4.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn5.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn6.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn7.Layout(gtx)
})
},
),
)
e.Frame(&ops)
case app.ConfigEvent:
fmt.Println(e.Config.Mode.String())
case app.DestroyEvent:
return e.Err
}
}
}
Multi window
// ref: https://github.com/gioui/gio-example/tree/main/multiwindow
package main
import (
"fmt"
"log"
"os"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
)
type (
C = layout.Context
D = layout.Dimensions
)
func main() {
go func() {
w := app.NewWindow(
app.Title("Multi Window"),
app.Size(unit.Dp(400), unit.Dp(600)),
)
if err := run(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func run(w *app.Window) error {
var ops op.Ops
var button_newWindow widget.Clickable
var button_close widget.Clickable
th := material.NewTheme()
var newWinNumber int
for {
switch e := w.NextEvent().(type) {
case app.FrameEvent:
gtx := app.NewContext(&ops, e)
if button_newWindow.Clicked(gtx) {
// fmt.Println("NewWindow button clicked!")
fmt.Printf("Openning new window, number %d\n", newWinNumber)
if err := newWin(newWinNumber); err != nil {
return err
}
newWinNumber += 1
}
if button_close.Clicked(gtx) {
fmt.Println("Close button clicked!")
w.Perform(system.ActionClose)
}
btn1 := material.Button(th, &button_newWindow, "NewWindow")
btn2 := material.Button(th, &button_close, "Close")
inset := layout.Inset{
Top: unit.Dp(0),
Right: unit.Dp(10),
Bottom: unit.Dp(10),
Left: unit.Dp(10),
}
layout.Flex{
Axis: layout.Vertical,
Spacing: layout.SpaceStart,
}.Layout(gtx,
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn1.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn2.Layout(gtx)
})
},
),
)
e.Frame(&ops)
case app.ConfigEvent:
fmt.Println(e.Config.Mode.String())
case app.DestroyEvent:
return e.Err
}
}
}
func newWin(winNumber int) error {
go func() error {
w := app.NewWindow(
app.Title(fmt.Sprintf("New Window Number %d", winNumber)),
app.Size(unit.Dp(200), unit.Dp(200)),
)
var ops op.Ops
th := material.NewTheme()
for {
switch e := w.NextEvent().(type) {
case app.DestroyEvent:
return e.Err
case app.FrameEvent:
gtx := app.NewContext(&ops, e)
label := material.Label(th, unit.Sp(14), fmt.Sprintf("New Window Number %d", winNumber))
label.Layout(gtx)
e.Frame(gtx.Ops)
}
}
}()
return nil
}
Layouts
stack
- stack
- stacked
- expanded
- background
// ref: https://github.com/gioui/gio/blob/main/layout/layout_test.go
// ref: https://github.com/gioui/gio/blob/main/layout/stack.go
package main
import (
"fmt"
"image"
"image/color"
"log"
"os"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
)
type (
C = layout.Context
D = layout.Dimensions
)
func main() {
go func() {
w := app.NewWindow(
app.Title("Layout"),
app.Size(unit.Dp(640), unit.Dp(480)),
)
if err := run(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func run(w *app.Window) error {
var ops op.Ops
var button_stack widget.Clickable
var button_close widget.Clickable
th := material.NewTheme()
for {
switch e := w.NextEvent().(type) {
case app.FrameEvent:
gtx := app.NewContext(&ops, e)
if button_stack.Clicked(gtx) {
if err := runStackLayoutWindow(); err != nil {
return err
}
}
if button_close.Clicked(gtx) {
fmt.Println("Close button clicked!")
w.Perform(system.ActionClose)
}
btn1 := material.Button(th, &button_stack, "Stack")
btn2 := material.Button(th, &button_close, "Close")
inset := layout.Inset{
Top: unit.Dp(0),
Right: unit.Dp(10),
Bottom: unit.Dp(10),
Left: unit.Dp(10),
}
layout.Flex{
Axis: layout.Vertical,
Spacing: layout.SpaceStart,
}.Layout(gtx,
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn1.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn2.Layout(gtx)
})
},
),
)
e.Frame(&ops)
case app.ConfigEvent:
fmt.Println(e.Config.Mode.String())
case app.DestroyEvent:
return e.Err
}
}
}
func runStackLayoutWindow() error {
go func() {
newW := app.NewWindow(
app.Title("Stack layout"),
app.Size(unit.Dp(400), unit.Dp(400)),
)
var ops op.Ops
for {
switch e := newW.NextEvent().(type) {
case app.DestroyEvent:
fmt.Println("Stack layout closed.")
case app.FrameEvent:
// gtx := app.NewContext(&ops, e)
gtx := layout.Context{
Ops: &ops,
Now: e.Now,
Source: e.Source,
Metric: e.Metric,
Constraints: layout.Constraints{
Min: image.Pt(0, 0),
Max: image.Pt(300, 300),
},
}
layout.Stack{Alignment: layout.Center}.Layout(gtx,
layout.Stacked(func(gtx C) D {
fmt.Println("1.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 150, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return layout.Dimensions{Size: gtx.Constraints.Max}
}),
layout.Stacked(func(gtx C) D {
return layout.UniformInset(unit.Dp(30)).Layout(gtx, func(gtx C) D {
fmt.Println("2.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 100, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return layout.Dimensions{Size: gtx.Constraints.Max}
})
}),
layout.Expanded(func(gtx C) D {
return layout.Background{}.Layout(gtx,
func(gtx C) D {
fmt.Println("3.", gtx.Constraints)
paint.Fill(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 100})
return emptyWidget{
Size: image.Point{X: 60, Y: 60},
}.Layout(gtx)
},
func(gtx C) D {
return layoutWidget(gtx, 60, 60)
},
)
// paint.FillShape(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
// return layout.Dimensions{Size: gtx.Constraints.Max}
}),
)
e.Frame(gtx.Ops)
}
}
}()
return nil
}
type emptyWidget struct {
Size image.Point
}
func (w emptyWidget) Layout(gtx C) D {
return layout.Dimensions{Size: w.Size}
}
func layoutWidget(ctx C, width, height int) D {
return layout.Dimensions{
Size: image.Point{
X: width,
Y: height,
},
}
}
showing border for debug
// ref: https://github.com/gioui/gio/blob/main/widget/border.go
// ref: https://github.com/gioui/gio-example/blob/main/fps-table/table.go#L99
package main
import (
"fmt"
"image"
"image/color"
"log"
"os"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
)
type (
C = layout.Context
D = layout.Dimensions
)
func main() {
go func() {
w := app.NewWindow(
app.Title("Layout"),
app.Size(unit.Dp(640), unit.Dp(480)),
)
if err := run(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func run(w *app.Window) error {
var ops op.Ops
var button_stack widget.Clickable
var button_border widget.Clickable
var button_close widget.Clickable
th := material.NewTheme()
for {
switch e := w.NextEvent().(type) {
case app.FrameEvent:
gtx := app.NewContext(&ops, e)
if button_stack.Clicked(gtx) {
if err := runStackLayoutWindow(); err != nil {
return err
}
}
if button_border.Clicked(gtx) {
if err := runBorderLayoutWindow(); err != nil {
return err
}
}
if button_close.Clicked(gtx) {
fmt.Println("Close button clicked!")
w.Perform(system.ActionClose)
}
btn1 := material.Button(th, &button_stack, "Stack")
btn2 := material.Button(th, &button_border, "Border")
btn3 := material.Button(th, &button_close, "Close")
inset := layout.Inset{
Top: unit.Dp(0),
Right: unit.Dp(10),
Bottom: unit.Dp(10),
Left: unit.Dp(10),
}
layout.Flex{
Axis: layout.Vertical,
Spacing: layout.SpaceStart,
}.Layout(gtx,
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn1.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn2.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn3.Layout(gtx)
})
},
),
)
e.Frame(&ops)
case app.ConfigEvent:
fmt.Println(e.Config.Mode.String())
case app.DestroyEvent:
return e.Err
}
}
}
func runStackLayoutWindow() error {
go func() {
newW := app.NewWindow(
app.Title("Stack layout"),
app.Size(unit.Dp(400), unit.Dp(400)),
)
var ops op.Ops
for {
switch e := newW.NextEvent().(type) {
case app.DestroyEvent:
fmt.Println("Stack layout closed.")
case app.FrameEvent:
// gtx := app.NewContext(&ops, e)
gtx := layout.Context{
Ops: &ops,
Now: e.Now,
Source: e.Source,
Metric: e.Metric,
Constraints: layout.Constraints{
Min: image.Pt(0, 0),
Max: image.Pt(300, 300),
},
}
layout.Stack{Alignment: layout.Center}.Layout(gtx,
layout.Stacked(func(gtx C) D {
fmt.Println("3.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 150, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return layout.Dimensions{Size: gtx.Constraints.Max}
}),
layout.Stacked(func(gtx C) D {
return layout.UniformInset(unit.Dp(30)).Layout(gtx, func(gtx C) D {
fmt.Println("2.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 100, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return layout.Dimensions{Size: gtx.Constraints.Max}
})
}),
layout.Expanded(func(gtx C) D {
return layout.Background{}.Layout(gtx,
func(gtx C) D {
fmt.Println("1.", gtx.Constraints)
paint.Fill(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 100})
return emptyWidget{
Size: image.Point{X: 60, Y: 60},
}.Layout(gtx)
},
func(gtx C) D {
return layoutWidget(gtx, 60, 60)
},
)
// paint.FillShape(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return layout.Dimensions{Size: gtx.Constraints.Max}
}),
)
e.Frame(gtx.Ops)
}
}
}()
return nil
}
func runBorderLayoutWindow() error {
go func() {
newW := app.NewWindow(
app.Title("Border layout"),
app.Size(unit.Dp(400), unit.Dp(400)),
)
var ops op.Ops
for {
switch e := newW.NextEvent().(type) {
case app.DestroyEvent:
fmt.Println("Border layout closed.")
case app.FrameEvent:
// gtx := app.NewContext(&ops, e)
gtx := layout.Context{
Ops: &ops,
Now: e.Now,
Source: e.Source,
Metric: e.Metric,
Constraints: layout.Constraints{
Min: image.Pt(0, 0),
Max: image.Pt(300, 300),
},
}
border := widget.Border{
Color: color.NRGBA{R: 255, A: 255},
Width: unit.Dp(1),
}
border.Layout(gtx, func(gtx C) D {
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
layout.Stacked(func(gtx C) D {
fmt.Println("1.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 150, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return border.Layout(gtx, func(gtx C) D {
return layout.Dimensions{Size: gtx.Constraints.Max}
})
}),
layout.Stacked(func(gtx C) D {
return layout.UniformInset(unit.Dp(30)).Layout(gtx, func(gtx C) D {
fmt.Println("2.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 100, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return border.Layout(gtx, func(gtx C) D {
return layout.Dimensions{Size: gtx.Constraints.Max}
})
})
}),
// layout.Expanded(func(gtx C) D {
// return border.Layout(gtx, func(gtx C) D {
// return layout.Background{}.Layout(gtx,
// func(gtx C) D {
// fmt.Println("3.", gtx.Constraints)
// paint.Fill(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 100})
// return emptyWidget{
// Size: image.Point{X: 60, Y: 60},
// }.Layout(gtx)
// },
// func(gtx C) D {
// return layoutWidget(gtx, 60, 60)
// },
// )
// // paint.FillShape(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
// // return layout.Dimensions{Size: gtx.Constraints.Max}
// })
// }),
)
})
e.Frame(gtx.Ops)
}
}
}()
return nil
}
type emptyWidget struct {
Size image.Point
}
func (w emptyWidget) Layout(gtx C) D {
return layout.Dimensions{Size: w.Size}
}
func layoutWidget(ctx C, width, height int) D {
return layout.Dimensions{
Size: image.Point{
X: width,
Y: height,
},
}
}
Flex
Flex put first level child elements in a row, from left to right.Flex-vertical
Flex-vertical put first level child elements in a column, from top to bottom.holy-grail
package main
import (
"fmt"
"image"
"image/color"
"log"
"os"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
)
type (
C = layout.Context
D = layout.Dimensions
)
func main() {
go func() {
w := app.NewWindow(
app.Title("Layout"),
app.Size(unit.Dp(640), unit.Dp(480)),
)
if err := run(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func run(w *app.Window) error {
var ops op.Ops
var button_stack widget.Clickable
var button_border widget.Clickable
var button_holygrail widget.Clickable
var button_close widget.Clickable
th := material.NewTheme()
for {
switch e := w.NextEvent().(type) {
case app.FrameEvent:
gtx := app.NewContext(&ops, e)
if button_stack.Clicked(gtx) {
if err := runStackLayoutWindow(); err != nil {
return err
}
}
if button_border.Clicked(gtx) {
if err := runBorderLayoutWindow(); err != nil {
return err
}
}
if button_holygrail.Clicked(gtx) {
if err := runHolygrailLayoutWindow(); err != nil {
return err
}
}
if button_close.Clicked(gtx) {
fmt.Println("Close button clicked!")
w.Perform(system.ActionClose)
}
btn1 := material.Button(th, &button_stack, "Stack")
btn2 := material.Button(th, &button_border, "Border")
btn3 := material.Button(th, &button_holygrail, "Holy Grail")
btn4 := material.Button(th, &button_close, "Close")
inset := layout.Inset{
Top: unit.Dp(0),
Right: unit.Dp(10),
Bottom: unit.Dp(10),
Left: unit.Dp(10),
}
layout.Flex{
Axis: layout.Vertical,
Spacing: layout.SpaceStart,
}.Layout(gtx,
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn1.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn2.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn3.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn4.Layout(gtx)
})
},
),
)
e.Frame(&ops)
case app.ConfigEvent:
fmt.Println(e.Config.Mode.String())
case app.DestroyEvent:
return e.Err
}
}
}
func runStackLayoutWindow() error {
go func() {
newW := app.NewWindow(
app.Title("Stack layout"),
app.Size(unit.Dp(400), unit.Dp(400)),
)
var ops op.Ops
for {
switch e := newW.NextEvent().(type) {
case app.DestroyEvent:
fmt.Println("Stack layout closed.")
case app.FrameEvent:
// gtx := app.NewContext(&ops, e)
gtx := layout.Context{
Ops: &ops,
Now: e.Now,
Source: e.Source,
Metric: e.Metric,
Constraints: layout.Constraints{
Min: image.Pt(0, 0),
Max: image.Pt(300, 300),
},
}
layout.Stack{Alignment: layout.Center}.Layout(gtx,
layout.Stacked(func(gtx C) D {
fmt.Println("3.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 150, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return layout.Dimensions{Size: gtx.Constraints.Max}
}),
layout.Stacked(func(gtx C) D {
return layout.UniformInset(unit.Dp(30)).Layout(gtx, func(gtx C) D {
fmt.Println("2.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 100, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return layout.Dimensions{Size: gtx.Constraints.Max}
})
}),
layout.Expanded(func(gtx C) D {
return layout.Background{}.Layout(gtx,
func(gtx C) D {
fmt.Println("1.", gtx.Constraints)
paint.Fill(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 100})
return emptyWidget{
Size: image.Point{X: 60, Y: 60},
}.Layout(gtx)
},
func(gtx C) D {
return layoutWidget(gtx, 60, 60)
},
)
// paint.FillShape(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return layout.Dimensions{Size: gtx.Constraints.Max}
}),
)
e.Frame(gtx.Ops)
}
}
}()
return nil
}
func runBorderLayoutWindow() error {
go func() {
newW := app.NewWindow(
app.Title("Border layout"),
app.Size(unit.Dp(400), unit.Dp(400)),
)
var ops op.Ops
for {
switch e := newW.NextEvent().(type) {
case app.DestroyEvent:
fmt.Println("Border layout closed.")
case app.FrameEvent:
// gtx := app.NewContext(&ops, e)
gtx := layout.Context{
Ops: &ops,
Now: e.Now,
Source: e.Source,
Metric: e.Metric,
Constraints: layout.Constraints{
Min: image.Pt(0, 0),
Max: image.Pt(300, 300),
},
}
border := widget.Border{
Color: color.NRGBA{R: 255, A: 255},
Width: unit.Dp(1),
}
border.Layout(gtx, func(gtx C) D {
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
layout.Stacked(func(gtx C) D {
fmt.Println("1.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 150, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return border.Layout(gtx, func(gtx C) D {
return layout.Dimensions{Size: gtx.Constraints.Max}
})
}),
layout.Stacked(func(gtx C) D {
return layout.UniformInset(unit.Dp(30)).Layout(gtx, func(gtx C) D {
fmt.Println("2.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 100, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return border.Layout(gtx, func(gtx C) D {
return layout.Dimensions{Size: gtx.Constraints.Max}
})
})
}),
// layout.Expanded(func(gtx C) D {
// return border.Layout(gtx, func(gtx C) D {
// return layout.Background{}.Layout(gtx,
// func(gtx C) D {
// fmt.Println("3.", gtx.Constraints)
// paint.Fill(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 100})
// return emptyWidget{
// Size: image.Point{X: 60, Y: 60},
// }.Layout(gtx)
// },
// func(gtx C) D {
// return layoutWidget(gtx, 60, 60)
// },
// )
// // paint.FillShape(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
// // return layout.Dimensions{Size: gtx.Constraints.Max}
// })
// }),
)
})
e.Frame(gtx.Ops)
}
}
}()
return nil
}
func runHolygrailLayoutWindow() error {
go func() {
newW := app.NewWindow(
app.Title("Holy grail layout"),
app.Size(unit.Dp(640), unit.Dp(480)),
)
var ops op.Ops
th := material.NewTheme()
for {
switch e := newW.NextEvent().(type) {
case app.DestroyEvent:
fmt.Println("Holy grail layout closed.")
case app.FrameEvent:
// gtx := app.NewContext(&ops, e)
gtx := layout.Context{
Ops: &ops,
Now: e.Now,
Source: e.Source,
Metric: e.Metric,
Constraints: layout.Constraints{
Min: image.Pt(0, 0),
Max: image.Pt(640, 480),
},
}
border := widget.Border{
Color: color.NRGBA{R: 255, A: 255},
Width: unit.Dp(1),
}
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "header").Layout(gtx)
return layoutWidget(gtx, 640, 100)
})
}),
layout.Flexed(1, func(gtx C) D {
// return layoutWidget(gtx, 640, 280)
return layout.Flex{}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "left sidebar").Layout(gtx)
return layoutWidget(gtx, 100, 280)
})
}),
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "content").Layout(gtx)
return layoutWidget(gtx, 440, 280)
})
}),
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "right sidebar").Layout(gtx)
return layoutWidget(gtx, 100, 280)
})
}),
)
}),
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "footer").Layout(gtx)
return layoutWidget(gtx, 640, 100)
})
}),
)
e.Frame(gtx.Ops)
}
}
}()
return nil
}
type emptyWidget struct {
Size image.Point
}
func (w emptyWidget) Layout(gtx C) D {
return layout.Dimensions{Size: w.Size}
}
func layoutWidget(ctx C, width, height int) D {
return layout.Dimensions{
Size: image.Point{
X: width,
Y: height,
},
}
}
holy-grail-extend
package main
import (
"fmt"
"image"
"image/color"
"log"
"os"
"gioui.org/app"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
)
type (
C = layout.Context
D = layout.Dimensions
)
func main() {
go func() {
w := app.NewWindow(
app.Title("Layout"),
app.Size(unit.Dp(640), unit.Dp(480)),
)
if err := run(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func run(w *app.Window) error {
var ops op.Ops
var button_stack widget.Clickable
var button_border widget.Clickable
var button_holygrail widget.Clickable
var button_holygrailExtend widget.Clickable
var button_close widget.Clickable
th := material.NewTheme()
for {
switch e := w.NextEvent().(type) {
case app.FrameEvent:
gtx := app.NewContext(&ops, e)
if button_stack.Clicked(gtx) {
if err := runStackLayoutWindow(); err != nil {
return err
}
}
if button_border.Clicked(gtx) {
if err := runBorderLayoutWindow(); err != nil {
return err
}
}
if button_holygrail.Clicked(gtx) {
if err := runHolygrailLayoutWindow(); err != nil {
return err
}
}
if button_holygrailExtend.Clicked(gtx) {
if err := runHolygrailExtendWindow(); err != nil {
return err
}
}
if button_close.Clicked(gtx) {
fmt.Println("Close button clicked!")
w.Perform(system.ActionClose)
}
btn1 := material.Button(th, &button_stack, "Stack")
btn2 := material.Button(th, &button_border, "Border")
btn3 := material.Button(th, &button_holygrail, "Holy Grail")
btn4 := material.Button(th, &button_holygrailExtend, "Holy Grail Extend")
btn5 := material.Button(th, &button_close, "Close")
inset := layout.Inset{
Top: unit.Dp(0),
Right: unit.Dp(10),
Bottom: unit.Dp(10),
Left: unit.Dp(10),
}
layout.Flex{
Axis: layout.Vertical,
Spacing: layout.SpaceStart,
}.Layout(gtx,
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn1.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn2.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn3.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn4.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn5.Layout(gtx)
})
},
),
)
e.Frame(&ops)
case app.ConfigEvent:
fmt.Println(e.Config.Mode.String())
case app.DestroyEvent:
return e.Err
}
}
}
func runStackLayoutWindow() error {
go func() {
newW := app.NewWindow(
app.Title("Stack layout"),
app.Size(unit.Dp(400), unit.Dp(400)),
)
var ops op.Ops
for {
switch e := newW.NextEvent().(type) {
case app.DestroyEvent:
fmt.Println("Stack layout closed.")
case app.FrameEvent:
// gtx := app.NewContext(&ops, e)
gtx := layout.Context{
Ops: &ops,
Now: e.Now,
Source: e.Source,
Metric: e.Metric,
Constraints: layout.Constraints{
Min: image.Pt(0, 0),
Max: image.Pt(300, 300),
},
}
layout.Stack{Alignment: layout.Center}.Layout(gtx,
layout.Stacked(func(gtx C) D {
fmt.Println("3.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 150, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return layout.Dimensions{Size: gtx.Constraints.Max}
}),
layout.Stacked(func(gtx C) D {
return layout.UniformInset(unit.Dp(30)).Layout(gtx, func(gtx C) D {
fmt.Println("2.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 100, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return layout.Dimensions{Size: gtx.Constraints.Max}
})
}),
layout.Expanded(func(gtx C) D {
return layout.Background{}.Layout(gtx,
func(gtx C) D {
fmt.Println("1.", gtx.Constraints)
paint.Fill(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 100})
return emptyWidget{
Size: image.Point{X: 60, Y: 60},
}.Layout(gtx)
},
func(gtx C) D {
return layoutWidget(gtx, 60, 60)
},
)
// paint.FillShape(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return layout.Dimensions{Size: gtx.Constraints.Max}
}),
)
e.Frame(gtx.Ops)
}
}
}()
return nil
}
func runBorderLayoutWindow() error {
go func() {
newW := app.NewWindow(
app.Title("Border layout"),
app.Size(unit.Dp(400), unit.Dp(400)),
)
var ops op.Ops
for {
switch e := newW.NextEvent().(type) {
case app.DestroyEvent:
fmt.Println("Border layout closed.")
case app.FrameEvent:
// gtx := app.NewContext(&ops, e)
gtx := layout.Context{
Ops: &ops,
Now: e.Now,
Source: e.Source,
Metric: e.Metric,
Constraints: layout.Constraints{
Min: image.Pt(0, 0),
Max: image.Pt(300, 300),
},
}
border := widget.Border{
Color: color.NRGBA{R: 255, A: 255},
Width: unit.Dp(1),
}
border.Layout(gtx, func(gtx C) D {
return layout.Stack{Alignment: layout.Center}.Layout(gtx,
layout.Stacked(func(gtx C) D {
fmt.Println("1.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 150, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return border.Layout(gtx, func(gtx C) D {
return layout.Dimensions{Size: gtx.Constraints.Max}
})
}),
layout.Stacked(func(gtx C) D {
return layout.UniformInset(unit.Dp(30)).Layout(gtx, func(gtx C) D {
fmt.Println("2.", gtx.Constraints)
paint.FillShape(gtx.Ops, color.NRGBA{R: 100, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
return border.Layout(gtx, func(gtx C) D {
return layout.Dimensions{Size: gtx.Constraints.Max}
})
})
}),
// layout.Expanded(func(gtx C) D {
// return border.Layout(gtx, func(gtx C) D {
// return layout.Background{}.Layout(gtx,
// func(gtx C) D {
// fmt.Println("3.", gtx.Constraints)
// paint.Fill(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 100})
// return emptyWidget{
// Size: image.Point{X: 60, Y: 60},
// }.Layout(gtx)
// },
// func(gtx C) D {
// return layoutWidget(gtx, 60, 60)
// },
// )
// // paint.FillShape(gtx.Ops, color.NRGBA{R: 200, B: 200, A: 200}, clip.Rect{Min: gtx.Constraints.Min, Max: gtx.Constraints.Max}.Op())
// // return layout.Dimensions{Size: gtx.Constraints.Max}
// })
// }),
)
})
e.Frame(gtx.Ops)
}
}
}()
return nil
}
func runHolygrailLayoutWindow() error {
go func() {
newW := app.NewWindow(
app.Title("Holy grail layout"),
app.Size(unit.Dp(640), unit.Dp(480)),
)
var ops op.Ops
th := material.NewTheme()
for {
switch e := newW.NextEvent().(type) {
case app.DestroyEvent:
fmt.Println("Holy grail layout closed.")
case app.FrameEvent:
// gtx := app.NewContext(&ops, e)
gtx := layout.Context{
Ops: &ops,
Now: e.Now,
Source: e.Source,
Metric: e.Metric,
Constraints: layout.Constraints{
Min: image.Pt(0, 0),
Max: image.Pt(640, 480),
},
}
border := widget.Border{
Color: color.NRGBA{R: 255, A: 255},
Width: unit.Dp(1),
}
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "header").Layout(gtx)
return layoutWidget(gtx, 640, 100)
})
}),
layout.Flexed(1, func(gtx C) D {
// return layoutWidget(gtx, 640, 280)
return layout.Flex{}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "left sidebar").Layout(gtx)
return layoutWidget(gtx, 100, 280)
})
}),
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "content").Layout(gtx)
return layoutWidget(gtx, 440, 280)
})
}),
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "right sidebar").Layout(gtx)
return layoutWidget(gtx, 100, 280)
})
}),
)
}),
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "footer").Layout(gtx)
return layoutWidget(gtx, 640, 100)
})
}),
)
e.Frame(gtx.Ops)
}
}
}()
return nil
}
func runHolygrailExtendWindow() error {
go func() {
newW := app.NewWindow(
app.Title("Holy grail extend layout"),
app.Size(unit.Dp(640), unit.Dp(480)),
)
var ops op.Ops
th := material.NewTheme()
for {
switch e := newW.NextEvent().(type) {
case app.DestroyEvent:
fmt.Println("Holy grail extend layout closed.")
case app.FrameEvent:
gtx := app.NewContext(&ops, e)
// gtx := layout.Context{
// Ops: &ops,
// Now: e.Now,
// Source: e.Source,
// Metric: e.Metric,
// // Constraints: layout.Constraints{
// // Min: image.Pt(0, 0),
// // Max: image.Pt(640, 480),
// // },
// }
// fmt.Println(gtx.Constraints)
// fmt.Println(gtx)
// fmt.Println(gtx.Metric)
// fmt.Println(gtx.Now)
// fmt.Println(gtx.Locale)
// fmt.Println(gtx.Source)
// fmt.Println(gtx.Ops)
// fmt.Printf("e.Size type: %T\n", e.Size)
fmt.Println(e.Size)
// fmt.Println(e.Size.X)
// fmt.Println(e.Size.Y)
border := widget.Border{
Color: color.NRGBA{R: 255, A: 255},
Width: unit.Dp(1),
}
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "header").Layout(gtx)
return layoutWidget(gtx, e.Size.X, 100)
})
}),
layout.Flexed(1, func(gtx C) D {
// return layoutWidget(gtx, 640, 280)
return layout.Flex{}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "left sidebar").Layout(gtx)
return layoutWidget(gtx, 100, e.Size.Y-200)
})
}),
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "content").Layout(gtx)
return layoutWidget(gtx, e.Size.X-200, e.Size.Y-200)
})
}),
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "right sidebar").Layout(gtx)
return layoutWidget(gtx, 100, e.Size.Y-200)
})
}),
)
}),
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "footer").Layout(gtx)
return layoutWidget(gtx, e.Size.X, 100)
})
}),
)
e.Frame(gtx.Ops)
}
}
}()
return nil
}
type emptyWidget struct {
Size image.Point
}
func (w emptyWidget) Layout(gtx C) D {
return layout.Dimensions{Size: w.Size}
}
func layoutWidget(ctx C, width, height int) D {
return layout.Dimensions{
Size: image.Point{
X: width,
Y: height,
},
}
}
others
centered / background 2 col 3 colholy-grail-extend-responsive
wrap ? ref: gio-x/outlay/flow.go , flow is renamed from grid
RAM (Repeat, Auto, Minmax) ?
grid / gio-x/outlay/flow
12-span-grid
layout reference website:
- layout pattern(flex layout pattern):
- https://web.dev/patterns/layout
- https://web.dev/patterns/components
- https://m3.material.io/foundations/layout
- https://flexboxpatterns.com/
- https://css-tricks.com/snippets/css/a-guide-to-flexbox/
- https://www.freecodecamp.org/news/learn-flexbox-build-5-layouts/
- https://www.patternfly.org/layouts/flex/html/
- https://every-layout.dev/
- https://tobiasahlin.com/blog/common-flexbox-patterns/
- https://www.nngroup.com/articles/design-pattern-guidelines/
- https://ui-patterns.com/patterns
- https://www.interaction-design.org/literature/article/10-great-sites-for-ui-design-patterns
Layout details
-
Border / background / centered
-
Spacing
- Inset
- space-between / space-start / space-end
- layout.SpaceSides / SpaceEnd / SpaceEvenly / SpaceAround
-
Axis Horizontal/Vertical iota
-
Direction (NW/N/NE/E/SE/S/SW/W/Center) iota
-
Alignment
- layout.Alignment Start/End/Middle/Baseline iota
- flex.Alignment is the children’s cross axis alignment, type layout.Alignment
- list.Alignment is the elements’ cross axis alignment, type layout.Alignment
- stack.Alignment type Direction(NW/N/NE/E/SE/S/SW/W/Center) iota
- text.Alignment Start/End/Middle
- editor.Alignment text.Alignment
-
Constraints
- layout.Constrains struct { Min, Max image.Point }
-
Size
- e.Size image.Point{X, Y} (e := w.NextEvent().(type), case FrameEvent)
- w.viewport internal use only. image.Rectangle(min.X, min.Y, max.X, max.Y)
-
ref:
- https://github.com/gioui/gio/blob/main/layout/doc.go
- https://github.com/gioui/gio/blob/main/layout/example_test.go
- https://github.com/gioui/gio/blob/main/widget/border.go#L17
- https://github.com/gioui/gio/blob/main/layout/layout.go
- https://github.com/search?q=repo%3Agioui%2Fgio%20alignment&type=code
- https://github.com/search?q=repo%3Agioui%2Fgio%20constraints&type=code
repro: examples / components / widget
component menu
package main
import (
"fmt"
"image"
"image/color"
"log"
"os"
"gioui.org/app"
"gioui.org/io/pointer"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"gioui.org/x/component"
)
type (
C = layout.Context
D = layout.Dimensions
)
func main() {
go func() {
w := app.NewWindow(
app.Title("Layout"),
app.Size(unit.Dp(640), unit.Dp(480)),
)
if err := run(w); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
app.Main()
}
func run(w *app.Window) error {
var ops op.Ops
var button_menu widget.Clickable
var button_close widget.Clickable
th := material.NewTheme()
for {
switch e := w.NextEvent().(type) {
case app.FrameEvent:
gtx := app.NewContext(&ops, e)
if button_menu.Clicked(gtx) {
if err := runMenuWindow(); err != nil {
return err
}
}
if button_close.Clicked(gtx) {
fmt.Println("Close button clicked!")
w.Perform(system.ActionClose)
}
btn1 := material.Button(th, &button_menu, "Menu")
btn2 := material.Button(th, &button_close, "Close")
inset := layout.Inset{
Top: unit.Dp(0),
Right: unit.Dp(10),
Bottom: unit.Dp(10),
Left: unit.Dp(10),
}
layout.Flex{
Axis: layout.Vertical,
Spacing: layout.SpaceStart,
}.Layout(gtx,
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn1.Layout(gtx)
})
},
),
layout.Rigid(
func(gtx C) D {
return inset.Layout(gtx, func(gtx C) D {
return btn2.Layout(gtx)
})
},
),
)
e.Frame(&ops)
case app.ConfigEvent:
fmt.Println(e.Config.Mode.String())
case app.DestroyEvent:
return e.Err
}
}
}
func runMenuWindow() error {
go func() {
newW := app.NewWindow(
app.Title("Menu layout"),
app.Size(unit.Dp(640), unit.Dp(480)),
)
var ops op.Ops
th := material.NewTheme()
var newfileButton, exitButton, copyButton, pasteButton widget.Clickable
menufileContextArea := component.ContextArea{Activation: pointer.ButtonPrimary, PositionHint: layout.Center}
menueditContextArea := component.ContextArea{Activation: pointer.ButtonPrimary, PositionHint: layout.Center}
for {
switch e := newW.NextEvent().(type) {
case app.DestroyEvent:
fmt.Println("Menu layout closed.")
case app.FrameEvent:
gtx := app.NewContext(&ops, e)
// fmt.Println(e.Size)
if exitButton.Clicked(gtx) {
fmt.Println("exitButton in menu layout clicked.")
}
// could add more menu click actions
border := widget.Border{
Color: color.NRGBA{R: 255, A: 255},
Width: unit.Dp(1),
}
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
// custom menu
layout.Rigid(func(gtx C) D {
menu_File := component.MenuState{
Options: []func(gtx C) D{
component.MenuItem(th, &newfileButton, "New File").Layout,
component.Divider(th).Layout,
component.MenuItem(th, &exitButton, "Exit").Layout,
},
}
menu_Edit := component.MenuState{
Options: []func(gtx C) D{
component.MenuItem(th, ©Button, "Copy").Layout,
component.Divider(th).Layout,
component.MenuItem(th, &pasteButton, "Paste").Layout,
},
}
return widget.Border{
Color: color.NRGBA{A: 255},
Width: unit.Dp(0),
}.Layout(gtx, func(gtx C) D {
return layout.Flex{}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return layout.Stack{}.Layout(gtx,
layout.Stacked(func(gtx C) D {
return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx C) D {
return component.SurfaceStyle{Theme: th, ShadowStyle: component.Shadow(unit.Dp(1), unit.Dp(2))}.Layout(gtx, func(gtx C) D {
return layout.UniformInset(unit.Dp(4)).Layout(gtx, material.Body1(th, "File").Layout)
})
})
}),
layout.Expanded(func(gtx C) D {
return menufileContextArea.Layout(gtx, func(gtx C) D {
gtx.Constraints.Min = image.Point{}
return component.Menu(th, &menu_File).Layout(gtx)
})
}),
)
}),
layout.Rigid(func(gtx C) D {
return layout.Stack{}.Layout(gtx,
layout.Stacked(func(gtx C) D {
return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx C) D {
return component.SurfaceStyle{Theme: th, ShadowStyle: component.Shadow(unit.Dp(1), unit.Dp(2))}.Layout(gtx, func(gtx C) D {
return layout.UniformInset(unit.Dp(4)).Layout(gtx, material.Body1(th, "Edit").Layout)
})
})
}),
layout.Expanded(func(gtx C) D {
return menueditContextArea.Layout(gtx, func(gtx C) D {
gtx.Constraints.Min = image.Point{}
return component.Menu(th, &menu_Edit).Layout(gtx)
})
}),
)
}),
)
})
}),
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "header").Layout(gtx)
return layoutWidget(gtx, e.Size.X, 54)
})
}),
layout.Flexed(1, func(gtx C) D {
return layout.Flex{}.Layout(gtx,
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "left sidebar").Layout(gtx)
return layoutWidget(gtx, 100, e.Size.Y-200)
})
}),
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "content").Layout(gtx)
return layoutWidget(gtx, e.Size.X-200, e.Size.Y-200)
})
}),
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "right sidebar").Layout(gtx)
return layoutWidget(gtx, 100, e.Size.Y-200)
})
}),
)
}),
layout.Rigid(func(gtx C) D {
return border.Layout(gtx, func(gtx C) D {
material.Label(th, unit.Sp(14), "footer").Layout(gtx)
return layoutWidget(gtx, e.Size.X, 100)
})
}),
)
e.Frame(gtx.Ops)
}
}
}()
return nil
}
type emptyWidget struct {
Size image.Point
}
func (w emptyWidget) Layout(gtx C) D {
return layout.Dimensions{Size: w.Size}
}
func layoutWidget(ctx C, width, height int) D {
return layout.Dimensions{
Size: image.Point{
X: width,
Y: height,
},
}
}
others
-
gio example
- 7 gui: counter, temperature, timer
- bidi: word selection
- kitchen: material widgets
- multiwindow
- outlay(grid)
- tabs
-
layout
-
component
- menu
- tabs
- carousels
-
webview
-
scrollbar
-
list rendering with scrollbar
-
“github.com/getlantern/systray”
-
“github.com/go-co-op/gocron”
-
caculator
-
todo list
-
calender
-
ref: https://github.com/gioui/gio/blob/main/widget/example_test.go
- draggable
OSC: Open Source Collaboration
- idiomatic go project structure