Gio – Split Widget (golang)
Si te ha interesado la interfaz gráfica Gio, talvez esta sea una de las primeras cosas que has tratado de hacer, pero como veremos no es tan fácil y en el apartado donde trata esta cuestión no está tan completo, así que tendremos que leer un poco más para entender como hacer las separaciones en esta interfaz.
Introducción
Para empezar le diré que no nos da un main para ya ir entendiendo el split widget de Gio. Pero podemos crear nuestro propio main leyendo un poco en Get Started si hacemos estos pasos veremos que crear nuestra función main es algo como sigue:
main.go function main()func main() { go func() { w := app.NewWindow()// crea una nueva ventana err := run(w) if err != nil { log.Fatal(err)//da un error fatal } os.Exit(0)//termina el proceso de la go routine esto no terminara el programa }() app.Main() }
Hasta aquí todo bien, tenemos nuestra función main, que crea una ventana, corre la función run()
y si esta da un error se sale del programa, vea que todo esto lo hacemos dentro de una rutina de go y, por lo tanto, cuando corramos el os.Exit(0)
terminara el proceso no el programa.
La línea que llama a app.Main
comenzara el ciclo de vida de la aplicación.
Entendiendo todo esto deberíamos ir por lo que sería nuestra función run()
, veamos como sería esta:
func run(w *app.Window) error { th := material.NewTheme(gofont.Collection()) var ops op.Ops for { e := <-w.Events() switch e := e.(type) { case system.DestroyEvent: return e.Err case system.FrameEvent: // This graphics context is used for managing the rendering state. gtx := layout.NewContext(&ops, e) // Define an large label with an appropriate text: title := material.H1(th, "Hello, Gio") // Change the color of the label. maroon := color.NRGBA{R: 127, G: 0, B: 0, A: 255} title.Color = maroon // Change the position of the label. title.Alignment = text.Middle // Draw the label to the graphics context. title.Layout(gtx) // Pass the drawing operations to the GPU. e.Frame(gtx.Ops) } } }
Esta función es más simple de lo que parece a simple vista, la única dificultad que puede ver es la <-
la cual indica un canal que no vemos cuando es creado y además tenemos este ops que por ahora no estaríamos sabiendo que es, sobre el switch es un type switch no posee mayor dificultad.
El canal probablemente lo tenga que usar para poder comunicarse
El ops como puede ver es del paquete op
(operations) esta variable tendrá una lista de operaciones para poder actualizar la interfaz gráfica.
Nuestro gtx
será un layout.context
y el resto lo que hace no da para mucho más setea el título, color , layout donde estará este y marco.
Si corremos este con go run .
lo que tenemos en este momento nos debería aparecer en pantalla lo siguiente:
Split Widgets
Cuando necesitamos un diseño personalizado para una widget o layout, utilizaremos split widgets, esto no quiere decir que no puedas hacer separaciones con layout, pero muchas veces queremos esto debido a que es mucho más personalizado que hacer las separaciones con layout.
En este apartado veremos primero lo que nos da GIO en su tutorial, para hacer el Split Widget. Lo malo de esto es que no está tan profundizado como se debería. Aunque si lees sobre layout y get started de seguro obtienes lo que deseas.
Lo primero que necesitaremos es una estructura para hacer el split, nuestro objeto si así lo queré ver.
main.go estructura SplitVisualtype SplitVisual struct{}
Luego de esta línea tenemos un tipo SplitVisual en el cual podemos tener realizar métodos y es exactamente lo que hace el siguiente código agregando a este un método Layout.
main.go (s SplitVisual) Layoutfunc (s SplitVisual) Layout(gtx layout.Context, left, right layout.Widget) layout.Dimensions { leftsize := gtx.Constraints.Min.X / 2 rightsize := gtx.Constraints.Min.X - leftsize { gtx := gtx gtx.Constraints = layout.Exact(image.Pt(leftsize, gtx.Constraints.Max.Y)) left(gtx) } { gtx := gtx gtx.Constraints = layout.Exact(image.Pt(rightsize, gtx.Constraints.Max.Y)) trans := op.Offset(image.Pt(leftsize, 0)).Push(gtx.Ops) right(gtx) trans.Pop() } return layout.Dimensions{Size: gtx.Constraints.Max} }
No analizaremos esta función, pero si podemos decir es que dentro de ella ocurre la división que queremos.
En el codigo siguente ya vemos como la función exampleSplitVisual creara nustro objeto SplitVisual
main.go function exampleSplitVisualfunc exampleSplitVisual(gtx layout.Context, th *material.Theme) layout.Dimensions { return SplitVisual{}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { return FillWithLabel(gtx, th, "Left", red) }, func(gtx layout.Context) layout.Dimensions { return FillWithLabel(gtx, th, "Right", blue) }) }
También tenemos que tener en cuenta la siguiente función, ya que esta creara los Labels y el ColorBox
func FillWithLabel(gtx layout.Context, th *material.Theme, text string, backgroundColor color.NRGBA) layout.Dimensions { ColorBox(gtx, gtx.Constraints.Max, backgroundColor) return layout.Center.Layout(gtx, material.H3(th, text).Layout) }
Ahora podemos ver que dentro de FillWithLabel
llamamos a la función ColorBox
y, por tanto, necesitaremos esta función, pero esta no forma parte del packete layout. Asi que investigando un poco encontramos que nos proporcionan esta función dentro de la documentación de Layouts aunque esta no forma parte así pues la tendremos que crear.
// Test colors. var ( background = color.NRGBA{R: 0xC0, G: 0xC0, B: 0xC0, A: 0xFF} red = color.NRGBA{R: 0xC0, G: 0x40, B: 0x40, A: 0xFF} green = color.NRGBA{R: 0x40, G: 0xC0, B: 0x40, A: 0xFF} blue = color.NRGBA{R: 0x40, G: 0x40, B: 0xC0, A: 0xFF} ) // ColorBox creates a widget with the specified dimensions and color. func ColorBox(gtx layout.Context, size image.Point, color color.NRGBA) layout.Dimensions { defer clip.Rect{Max: size}.Push(gtx.Ops).Pop() paint.ColorOp{Color: color}.Add(gtx.Ops) paint.PaintOp{}.Add(gtx.Ops) return layout.Dimensions{Size: size} }
Juntemos todo y veamos que nos queda: Excelente aunque tengo algo de código de más
package main import ( "image" "image/color" "log" "os" "gioui.org/app" "gioui.org/font/gofont" "gioui.org/io/system" "gioui.org/layout" "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/text" "gioui.org/widget/material" ) var ( background = color.NRGBA{R: 0xC0, G: 0xC0, B: 0xC0, A: 0xFF} red = color.NRGBA{R: 0xC0, G: 0x40, B: 0x40, A: 0xFF} green = color.NRGBA{R: 0x40, G: 0xC0, B: 0x40, A: 0xFF} blue = color.NRGBA{R: 0x40, G: 0x40, B: 0xC0, A: 0xFF} gtx layout.Context th *material.Theme ) type SplitVisual struct { } func (s SplitVisual) Layout(gtx layout.Context, left, right layout.Widget) layout.Dimensions { leftsize := gtx.Constraints.Min.X / 2 rightsize := gtx.Constraints.Min.X - leftsize { gtx := gtx gtx.Constraints = layout.Exact(image.Pt(leftsize, gtx.Constraints.Max.Y)) left(gtx) } { gtx := gtx gtx.Constraints = layout.Exact(image.Pt(rightsize, gtx.Constraints.Max.Y)) trans := op.Offset(image.Pt(leftsize, 0)).Push(gtx.Ops) right(gtx) trans.Pop() } return layout.Dimensions{Size: gtx.Constraints.Max} } func main() { go func() { w := app.NewWindow() err := run(w) if err != nil { log.Fatal(err) } os.Exit(0) }() app.Main() } func run(w *app.Window) error { th := material.NewTheme(gofont.Collection()) var ops op.Ops for { e := <-w.Events() switch e := e.(type) { case system.DestroyEvent: return e.Err case system.FrameEvent: // This graphics context is used for managing the rendering state. gtx := layout.NewContext(&ops, e) // Define an large label with an appropriate text: title := material.H1(th, "Hello, Gio") // Change the color of the label. maroon := color.NRGBA{R: 127, G: 0, B: 0, A: 255} title.Color = maroon // Change the position of the label. title.Alignment = text.Middle // Draw the label to the graphics context. title.Layout(gtx) exampleSplitVisual(gtx, th) // Pass the drawing operations to the GPU. e.Frame(gtx.Ops) } } } func exampleSplitVisual(gtx layout.Context, th *material.Theme) layout.Dimensions { return SplitVisual{}.Layout(gtx, func(gtx layout.Context) layout.Dimensions { return FillWithLabel(gtx, th, "Left", red) }, func(gtx layout.Context) layout.Dimensions { return FillWithLabel(gtx, th, "Right", blue) }) } func FillWithLabel(gtx layout.Context, th *material.Theme, text string, backgroundColor color.NRGBA) layout.Dimensions { ColorBox(gtx, gtx.Constraints.Max, backgroundColor) return layout.Center.Layout(gtx, material.H3(th, text).Layout) } func ColorBox(gtx layout.Context, size image.Point, color color.NRGBA) layout.Dimensions { defer clip.Rect{Max: size}.Push(gtx.Ops).Pop() paint.ColorOp{Color: color}.Add(gtx.Ops) paint.PaintOp{}.Add(gtx.Ops) return layout.Dimensions{Size: size} }
Veamos que tal funciona, corremos go build
y go run .
y nuestoro resultado es ele siguiente:
Lo que podemos ver aquí es que me ha pisado el título que hemos hecho en el inicio, pero eso es un problema de no especificar bien los Contextos
Si quiere un ejercicio sobre esto, simplemente páselo en limpio, hágalo vertical y arregle el problema antes mencionado.