gogo-gio

How to set an exact position for a button in a window?


I'm comfortable with Go and I'm trying to start using Gio (or Gioui), so I'm beginner in Gio. I have a code to draw a text and a button on a window (code and screenshot). I have 2 questions:

My code is


import (
    "fmt"
    "log"
    "os"

    "gioui.org/app"
    "gioui.org/layout"
    "gioui.org/op"
    "gioui.org/text"
    "gioui.org/unit"
    "gioui.org/widget"
    "gioui.org/widget/material"
)

func main() {
    go func() {
        w := new(app.Window)
        w.Option(app.Title("Hello World"))
        w.Option(app.MaxSize(unit.Dp(400), unit.Dp(600)))
        if err := draw(w); err != nil {
            log.Fatal(err)
        }
        os.Exit(0)
    }()
    app.Main()
}

func draw(w *app.Window) error {
    var ops op.Ops
    var lblButton widget.Clickable
    var btnClickArea widget.Clickable

    th := material.NewTheme()
    btn := layout.Rigid(
        func(gtx layout.Context) layout.Dimensions {
            btn := material.Button(th, &btnClickArea, "Button1")
            return btn.Layout(gtx)
        },
    )
    columns := layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceAround}
    for {
        switch e := w.Event().(type) {
        case app.FrameEvent:
            gtx := app.NewContext(&ops, e)

            if lblButton.Clicked(gtx) {
                fmt.Println("the label was clicked")
            }
            if btnClickArea.Clicked(gtx) {
                fmt.Println("button1 was clicked")
            }

            layout.Flex{
                Axis:    layout.Vertical,
                Spacing: layout.SpaceStart,
            }.Layout(gtx,
                layout.Rigid(
                    layout.Spacer{Height: unit.Dp(250)}.Layout,
                ),
                layout.Rigid(
                    func(gtx layout.Context) layout.Dimensions {
                        lbl := material.Label(th, unit.Sp(50), "hello world")
                        lbl.Alignment = text.Middle
                        lblButton.Layout(gtx, lbl.Layout)
                        return lbl.Layout(gtx)
                    },
                ),
                layout.Rigid(
                    func(gtx layout.Context) layout.Dimensions {
                        return columns.Layout(gtx, btn)
                    },
                ),
            )
            e.Frame(gtx.Ops)
        case app.DestroyEvent:
            return e.Err
        }
    }
    return nil
}

The code will produce the following window Code's result

How can I have the following window (my 2 above questions) What I want


Solution

  • What you seem to want is two nested layout.Flexes. The top-level one divides the window vertically into a rigid area for the text and a flexible area for the rest of the space. Then a horizontal flex divides the bottom area into your two panes. Inside of the right-hand pane, you can then use an inset and direction to position your button. Here's a working example:

    package main
    
    import (
        "fmt"
        "image"
        "log"
        "os"
    
        "gioui.org/app"
        "gioui.org/layout"
        "gioui.org/op"
        "gioui.org/text"
        "gioui.org/unit"
        "gioui.org/widget"
        "gioui.org/widget/material"
    )
    
    func main() {
        go func() {
            w := new(app.Window)
            w.Option(app.Title("Hello World"))
            w.Option(app.MaxSize(unit.Dp(400), unit.Dp(600)))
            if err := draw(w); err != nil {
                log.Fatal(err)
            }
            os.Exit(0)
        }()
        app.Main()
    }
    
    func draw(w *app.Window) error {
        var ops op.Ops
        var lblButton widget.Clickable
        var btnClickArea widget.Clickable
    
        th := material.NewTheme()
        for {
            switch e := w.Event().(type) {
            case app.FrameEvent:
                gtx := app.NewContext(&ops, e)
    
                if lblButton.Clicked(gtx) {
                    fmt.Println("the label was clicked")
                }
                if btnClickArea.Clicked(gtx) {
                    fmt.Println("button1 was clicked")
                }
    
                // This flex splits the window vertically.
                layout.Flex{
                    Axis: layout.Vertical,
                }.Layout(gtx,
                    layout.Rigid(func(gtx layout.Context) layout.Dimensions {
                        lbl := material.Label(th, unit.Sp(50), "hello world")
                        lbl.Alignment = text.Middle
                        // Here we use the material.Clickable wrapper func to animate button clicks.
                        return material.Clickable(gtx, &lblButton, lbl.Layout)
                    }),
                    layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
                        // This flex splits the bottom pane horizontally.
                        return layout.Flex{}.Layout(gtx,
                            layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
                                // This returns an empty left-hand pane.
                                return layout.Dimensions{Size: gtx.Constraints.Min}
                            }),
                            layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
                                // Here we position the button at the "north" or top-center of the available space.
                                return layout.N.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
                                    // Here we set the minimum constraints to zero. This allows our button to be smaller
                                    // than the entire right-side pane in the UI. Without this change, the button is forced
                                    // to occupy all of the space.
                                    gtx.Constraints.Min = image.Point{}
                                    // Here we inset the button a little bit on all sides.
                                    return layout.UniformInset(8).Layout(gtx,
                                        material.Button(th, &btnClickArea, "Button1").Layout,
                                    )
                                })
    
                            }),
                        )
                    }),
                )
                e.Frame(gtx.Ops)
            case app.DestroyEvent:
                return e.Err
            }
        }
        return nil
    }