2d8289889f439cfd0980fad4d7a527e14b2e8715 — Egon Elbre 7 months ago be66210
7gui/counter: button with a label

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
2 files changed, 154 insertions(+), 0 deletions(-)

A 7gui/README.md
A 7gui/counter/main.go
A 7gui/README.md => 7gui/README.md +12 -0
@@ 0,0 1,12 @@
# 7 GUIs

This demonstrates several classic GUI problems based on [7GUIs](https://eugenkiss.github.io/7guis/).

The examples are over-commented to help understand the structure better, in practice, you don't need that many comments.

## Counter

This shows basic usage of the Gio and how to write interactions.
The counter shows how to display a count value that increases when you press a button.

\ No newline at end of file

A 7gui/counter/main.go => 7gui/counter/main.go +142 -0
@@ 0,0 1,142 @@
package main

import (

	"gioui.org/app"             // app contains Window handling.
	"gioui.org/font/gofont"     // gofont is used for loading the default font.
	"gioui.org/io/key"          // key is used for keyboard events.
	"gioui.org/io/system"       // system is used for system events (e.g. closing the window).
	"gioui.org/layout"          // layout is used for layouting widgets.
	"gioui.org/op"              // op is used for recording different operations.
	"gioui.org/unit"            // unit is used to define pixel-independent sizes
	"gioui.org/widget"          // widget contains state handling for widgets.
	"gioui.org/widget/material" // material contains material design widgets.

func main() {
	// The ui loop is separated from the application window creation
	// such that it can be used for testing.
	ui := NewUI()

	// This creates a new application window and starts the UI.
	go func() {
		w := app.NewWindow(
			app.Size(unit.Dp(240), unit.Dp(70)),
		if err := ui.Run(w); err != nil {

	// This starts Gio main.

// defaultMargin is a margin applied in multiple places to give
// widgets room to breathe.
var defaultMargin = unit.Dp(10)

// UI holds all of the application state.
type UI struct {
	// Theme is used to hold the fonts used throughout the application.
	Theme *material.Theme

	// Counter displays and keeps the state of the counter.
	Counter Counter

// NewUI creates a new UI using the Go Fonts.
func NewUI() *UI {
	ui := &UI{}
	ui.Theme = material.NewTheme(gofont.Collection())
	return ui

// Run handles window events and renders the application.
func (ui *UI) Run(w *app.Window) error {
	var ops op.Ops

	// listen for events happening on the window.
	for e := range w.Events() {
		// detect the type of the event.
		switch e := e.(type) {
		// this is sent when the application should re-render.
		case system.FrameEvent:
			// gtx is used to pass around rendering and event information.
			gtx := layout.NewContext(&ops, e)
			// render and handle UI.
			// render and handle the operations from the UI.

		// handle a global key press.
		case key.Event:
			switch e.Name {
			// when we click escape, let's close the window.
			case key.NameEscape:
				return nil

		// this is sent when the application is closed.
		case system.DestroyEvent:
			return e.Err

	return nil

// Layout displays the main program layout.
func (ui *UI) Layout(gtx layout.Context) layout.Dimensions {
	// inset is used to add padding around the window border.
	inset := layout.UniformInset(defaultMargin)
	return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		return ui.Counter.Layout(ui.Theme, gtx)

// Counter is a component that keeps track of it's state and
// displays itself as a label and a button.
type Counter struct {
	// Count is the current value.
	Count int

	// increase is used to track button clicks.
	increase widget.Clickable

// Layout lays out the counter and handles input.
func (counter *Counter) Layout(th *material.Theme, gtx layout.Context) layout.Dimensions {
	// Flex layout lays out widgets from left to right by default.
	return layout.Flex{}.Layout(gtx,
		// We use weight 1 for both text and count to make them the same size.
		layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
			// We center align the text to the area available.
			return layout.Center.Layout(gtx,
				// Body1 is the default text size for reading.
				material.Body1(th, strconv.Itoa(counter.Count)).Layout)
		// We use an empty widget to add spacing between the text
		// and the button.
		layout.Rigid(func(gtx layout.Context) layout.Dimensions {
			return layout.Dimensions{
				Size: image.Pt(gtx.Px(defaultMargin), 0),
		layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
			// For every click on the button increment the count.
			for range counter.increase.Clicks() {
			// Finally display the button.
			return material.Button(th, &counter.increase, "Count").Layout(gtx)