~eliasnaur/gio-patches

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
3 3

[PATCH gio-example v2] multiwindow: larger example

~egonelbre
Details
Message ID
<161477125861.1998.4582279061231822911-0@git.sr.ht>
DKIM signature
missing
Download raw message
Patch: +304 -86
From: Egon Elbre <egonelbre@gmail.com>

First time managing and handling windows can be complicated, even if you know
how to create a new window. This tries to expand the example to a multiwindow
project, with different views and inter-window communication.

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
---
 multiwindow/letters.go |  64 +++++++++++++++++
 multiwindow/log.go     |  86 +++++++++++++++++++++++
 multiwindow/main.go    | 154 +++++++++++++++++++++++++++++++++++++++++
 multiwindow/windows.go |  86 -----------------------
 4 files changed, 304 insertions(+), 86 deletions(-)
 create mode 100644 multiwindow/letters.go
 create mode 100644 multiwindow/log.go
 create mode 100644 multiwindow/main.go
 delete mode 100644 multiwindow/windows.go

diff --git a/multiwindow/letters.go b/multiwindow/letters.go
new file mode 100644
index 0000000..dca5f62
--- /dev/null
+++ b/multiwindow/letters.go
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: Unlicense OR MIT

package main

import (
	"gioui.org/app"
	"gioui.org/layout"
	"gioui.org/widget"
	"gioui.org/widget/material"
)

// Letters displays a clickable list of text items that open a new window.
type Letters struct {
	win *Window
	log *Log

	items []*LetterListItem
	list  layout.List
}

// NewLetters creates a new letters view with the provided log.
func NewLetters(log *Log) *Letters {
	view := &Letters{
		log:  log,
		list: layout.List{Axis: layout.Vertical},
	}
	for text := 'a'; text <= 'z'; text++ {
		view.items = append(view.items, &LetterListItem{Text: string(text)})
	}
	return view
}

// Run implements Window.Run method.
func (v *Letters) Run(w *Window) error {
	v.win = w
	return WidgetView(v.Layout).Run(w)
}

// Layout handles drawing the letters view.
func (v *Letters) Layout(gtx layout.Context) layout.Dimensions {
	th := v.win.App.Theme
	return v.list.Layout(gtx, len(v.items), func(gtx layout.Context, index int) layout.Dimensions {
		item := v.items[index]
		for item.Click.Clicked() {
			v.log.Printf("opening %s view", item.Text)

			bigText := material.H1(th, item.Text)
			size := bigText.TextSize
			size.V *= 2
			v.win.App.NewWindow(item.Text,
				WidgetView(func(gtx layout.Context) layout.Dimensions {
					return layout.Center.Layout(gtx, bigText.Layout)
				}),
				app.Size(size, size),
			)
		}
		return material.Button(th, &item.Click, item.Text).Layout(gtx)
	})
}

type LetterListItem struct {
	Text  string
	Click widget.Clickable
}
diff --git a/multiwindow/log.go b/multiwindow/log.go
new file mode 100644
index 0000000..7d826d8
--- /dev/null
+++ b/multiwindow/log.go
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: Unlicense OR MIT

package main

import (
	"fmt"

	"gioui.org/io/system"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/widget"
	"gioui.org/widget/material"
)

// Log shows a list of strings.
type Log struct {
	addLine chan string
	lines   []string

	close widget.Clickable
	list  layout.List
}

// NewLog crates a new log view.
func NewLog() *Log {
	return &Log{
		addLine: make(chan string, 100),
		list:    layout.List{Axis: layout.Vertical},
	}
}

// Printf adds a new line to the log.
func (log *Log) Printf(format string, args ...interface{}) {
	s := fmt.Sprintf(format, args...)

	// ensure that this logging does not block.
	select {
	case log.addLine <- s:
	default:
	}
}

// Run handles window loop for the log.
func (log *Log) Run(w *Window) error {
	var ops op.Ops

	applicationClose := w.App.Context.Done()
	for {
		select {
		case <-applicationClose:
			return nil
		// listen to new lines from Printf and add them to our lines.
		case line := <-log.addLine:
			log.lines = append(log.lines, line)
			w.Invalidate()
		case e := <-w.Events():
			switch e := e.(type) {
			case system.DestroyEvent:
				return e.Err
			case system.FrameEvent:
				gtx := layout.NewContext(&ops, e)
				log.Layout(w, w.App.Theme, gtx)
				e.Frame(gtx.Ops)
			}
		}
	}
}

// Layout displays the log with a close button.
func (log *Log) Layout(w *Window, th *material.Theme, gtx layout.Context) {
	// This is here to demonstrate programmatic closing of a window,
	// however it's probably better to use OS close button instead.
	for log.close.Clicked() {
		w.Window.Close()
	}

	layout.Flex{Axis: layout.Vertical}.Layout(gtx,
		layout.Rigid(material.Button(th, &log.close, "Close").Layout),
		layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
			return log.list.Layout(gtx, len(log.lines), func(gtx layout.Context, i int) layout.Dimensions {
				return material.Body1(th, log.lines[i]).Layout(gtx)
			})
		}),
	)

}
diff --git a/multiwindow/main.go b/multiwindow/main.go
new file mode 100644
index 0000000..6e2b8bc
--- /dev/null
+++ b/multiwindow/main.go
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: Unlicense OR MIT

package main

// This projects demonstrates one way to manage and use multiple windows.
//
// It shows:
//   * how to track multiple windows,
//   * how to communicate between windows,
//   * how to create new windows.

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"sync"

	"gioui.org/app"
	"gioui.org/io/system"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/widget/material"

	"gioui.org/font/gofont"
)

func main() {
	// With Go 1.16, this can be replaced with:
	//
	//   signal.NotifyContext(context.Background(), os.Interrupt)
	ctx, stop := cancelOnInterrupt(context.Background())
	defer stop()

	go func() {
		a := NewApplication(ctx)

		log := NewLog()
		log.Printf("[Application Started]")
		letters := NewLetters(log)

		a.NewWindow("Log", log)
		a.NewWindow("Letters", letters)

		a.Wait()

		os.Exit(0)
	}()

	app.Main()
}

// Application keeps track of all the windows and global state.
type Application struct {
	// Context is used to broadcast application shutdown.
	Context context.Context
	// Shutdown shuts down all windows.
	Shutdown func()
	// Theme is the application wide theme.
	Theme *material.Theme
	// active keeps track the open windows, such that application
	// can shut down, when all of them are closed.
	active sync.WaitGroup
}

func NewApplication(ctx context.Context) *Application {
	ctx, cancel := context.WithCancel(ctx)
	return &Application{
		Context:  ctx,
		Shutdown: cancel,

		Theme: material.NewTheme(gofont.Collection()),
	}
}

// Wait waits for all windows to close.
func (a *Application) Wait() {
	a.active.Wait()
}

// NewWindow creates a new tracked window.
func (a *Application) NewWindow(title string, view View, opts ...app.Option) {
	opts = append(opts, app.Title(title))
	w := &Window{
		App:    a,
		Window: app.NewWindow(opts...),
	}
	a.active.Add(1)
	go func() {
		defer a.active.Done()
		if err := view.Run(w); err != nil {
			fmt.Fprintf(os.Stderr, "view returned an error: %v\n", err)
		}
	}()
}

// Window holds window state.
type Window struct {
	App *Application
	*app.Window
}

// View describes .
type View interface {
	// Run handles the window event loop.
	Run(w *Window) error
}

// WidgetView allows to use layout.Widget as a view.
type WidgetView func(gtx layout.Context) layout.Dimensions

// Run displays the widget with default handling.
func (view WidgetView) Run(w *Window) error {
	var ops op.Ops

	applicationClose := w.App.Context.Done()
	for {
		select {
		case <-applicationClose:
			return nil
		case e := <-w.Events():
			switch e := e.(type) {
			case system.DestroyEvent:
				return e.Err
			case system.FrameEvent:
				gtx := layout.NewContext(&ops, e)
				view(gtx)
				e.Frame(gtx.Ops)
			}
		}
	}
}

// cancelOnInterrupt returns a context that will be cancelled when
// an os.Interrupt is sent to the program.
func cancelOnInterrupt(parent context.Context) (ctx context.Context, stop context.CancelFunc) {
	ctx, cancel := context.WithCancel(parent)
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)
	if ctx.Err() == nil {
		go func() {
			defer cancel()
			select {
			case <-ch:
			case <-ctx.Done():
			}
		}()
	}

	return ctx, func() {
		cancel()
		signal.Stop(ch)
	}
}
diff --git a/multiwindow/windows.go b/multiwindow/windows.go
deleted file mode 100644
index 116441f..0000000
--- a/multiwindow/windows.go
@@ -1,86 +0,0 @@
// SPDX-License-Identifier: Unlicense OR MIT

package main

// Multiple windows in Gio.

import (
	"log"
	"os"
	"sync/atomic"

	"gioui.org/app"
	"gioui.org/io/event"
	"gioui.org/io/system"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/unit"
	"gioui.org/widget"
	"gioui.org/widget/material"

	"gioui.org/font/gofont"
)

type window struct {
	*app.Window

	more  widget.Clickable
	close widget.Clickable
}

func main() {
	newWindow()
	app.Main()
}

var windowCount int32

func newWindow() {
	atomic.AddInt32(&windowCount, +1)
	go func() {
		w := new(window)
		w.Window = app.NewWindow()
		if err := w.loop(w.Events()); err != nil {
			log.Fatal(err)
		}
		if c := atomic.AddInt32(&windowCount, -1); c == 0 {
			os.Exit(0)
		}
	}()
}

func (w *window) loop(events <-chan event.Event) error {
	th := material.NewTheme(gofont.Collection())
	var ops op.Ops
	for {
		e := <-events
		switch e := e.(type) {
		case system.DestroyEvent:
			return e.Err
		case system.FrameEvent:
			for w.more.Clicked() {
				newWindow()
			}
			for w.close.Clicked() {
				w.Close()
			}
			gtx := layout.NewContext(&ops, e)

			layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
				return layout.Flex{
					Alignment: layout.Middle,
				}.Layout(gtx,
					RigidInset(material.Button(th, &w.more, "More!").Layout),
					RigidInset(material.Button(th, &w.close, "Close").Layout),
				)
			})
			e.Frame(gtx.Ops)
		}
	}
}

func RigidInset(w layout.Widget) layout.FlexChild {
	return layout.Rigid(func(gtx layout.Context) layout.Dimensions {
		return layout.UniformInset(unit.Sp(5)).Layout(gtx, w)
	})
}
-- 
2.30.1

[gio-example/patches/linux.yml] build success

builds.sr.ht
Details
Message ID
<C9NOOI1UIKXN.R0C078HDPMD5@cirno>
In-Reply-To
<161477125861.1998.4582279061231822911-0@git.sr.ht> (view parent)
DKIM signature
missing
Download raw message
gio-example/patches/linux.yml: SUCCESS in 1m57s

[multiwindow: larger example][0] v2 from [~egonelbre][1]

[0]: https://lists.sr.ht/~eliasnaur/gio-patches/patches/20749
[1]: mailto:egonelbre@gmail.com

✓ #446894 SUCCESS gio-example/patches/linux.yml https://builds.sr.ht/~eliasnaur/job/446894
Details
Message ID
<C9NPU5VG89N4.2BMALIEQKB7N7@testmac>
In-Reply-To
<161477125861.1998.4582279061231822911-0@git.sr.ht> (view parent)
DKIM signature
fail
Download raw message
DKIM signature: fail
On Wed Mar 3, 2021 at 09:33 CET, ~egonelbre wrote:
> +func main() {
> +	// With Go 1.16, this can be replaced with:
> +	//
> +	//   signal.NotifyContext(context.Background(), os.Interrupt)
> +	ctx, stop := cancelOnInterrupt(context.Background())

Why not bump the builders to Go 1.16? I don't think the example
repository needs to be backwards compatible.

Elias
Details
Message ID
<CANtNKfp=ojgsfXSo+WrmcstKpi-JWq6VbOxJ0zcO37fnOdvysA@mail.gmail.com>
In-Reply-To
<C9NPU5VG89N4.2BMALIEQKB7N7@testmac> (view parent)
DKIM signature
pass
Download raw message
I'm not sure which version is preferred for the example repository.
I avoided changing the version in this specific changeset.

I don't have a strong opinion either way.

+ Egon

On Wed, Mar 3, 2021 at 2:30 PM Elias Naur <mail@eliasnaur.com> wrote:
>
> On Wed Mar 3, 2021 at 09:33 CET, ~egonelbre wrote:
> > +func main() {
> > +     // With Go 1.16, this can be replaced with:
> > +     //
> > +     //   signal.NotifyContext(context.Background(), os.Interrupt)
> > +     ctx, stop := cancelOnInterrupt(context.Background())
>
> Why not bump the builders to Go 1.16? I don't think the example
> repository needs to be backwards compatible.
>
> Elias
Reply to thread Export thread (mbox)