~eliasnaur/scatter

60352f08e4f8dd9224d565c90074688ec11c9db7 — Elias Naur 1 year, 1 month ago e52153f
cmd/scatter: update layouts to use layout.Context

Signed-off-by: Elias Naur <mail@eliasnaur.com>
3 files changed, 203 insertions(+), 223 deletions(-)

M cmd/scatter/ui.go
M go.mod
M go.sum
M cmd/scatter/ui.go => cmd/scatter/ui.go +200 -214
@@ 72,7 72,7 @@ type pageStack struct {
type Page interface {
	Start(stop <-chan struct{})
	Event(c ui.Config, q input.Queue) interface{}
	Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions
	Layout(c ui.Config, q input.Queue, ops *ui.Ops, ctx *layout.Context)
}

type signInPage struct {


@@ 226,6 226,7 @@ func (a *App) run() error {
	var updates <-chan struct{}
	ops := new(ui.Ops)
	var cfg ui.Config
	ctx := new(layout.Context)
	for {
		select {
		case <-updates:


@@ 282,10 283,10 @@ func (a *App) run() error {
					Right:  e.Insets.Right,
					Bottom: e.Insets.Bottom,
				}
				cs := layout.RigidConstraints(e.Size)
				a.Layout(cfg, q, ops, cs)
				ctx.Constraints = layout.RigidConstraints(e.Size)
				a.Layout(cfg, q, ops, ctx)
				if a.profiling {
					a.layoutTimings(cfg, q, ops, cs)
					a.layoutTimings(cfg, q, ops, ctx)
				}
				a.w.Update(ops)
			}


@@ 309,10 310,9 @@ func (t *Transition) Event(c ui.Config, q input.Queue) interface{} {
	return t.page.Event(c, q)
}

func (t *Transition) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
func (t *Transition) Layout(c ui.Config, q input.Queue, ops *ui.Ops, ctx *layout.Context) {
	var stack ui.StackOp
	stack.Push(ops)
	var dims layout.Dimensions
	prev, page := t.prev, t.page
	if prev != nil {
		if t.reverse {


@@ 322,7 322,8 @@ func (t *Transition) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.C
		if t.time.IsZero() {
			t.time = now
		}
		prev.Layout(c, q, ops, cs)
		prev.Layout(c, q, ops, ctx)
		cs := ctx.Constraints
		size := f32.Point{X: float32(cs.Width.Max), Y: float32(cs.Height.Max)}
		max := float32(math.Sqrt(float64(size.X*size.X + size.Y*size.Y)))
		progress := float32(now.Sub(t.time).Seconds()) * 3


@@ 343,11 344,10 @@ func (t *Transition) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.C
		off.Add(ops)
		rrect(ops, diameter, diameter, radius, radius, radius, radius)
		off.Invert().Add(ops)
		fill{theme.white}.Layout(ops, cs)
		fill{theme.white}.Layout(ops, ctx)
	}
	dims = page.Layout(c, q, ops, cs)
	page.Layout(c, q, ops, ctx)
	stack.Pop()
	return dims
}

func (s *pageStack) Len() int {


@@ 431,12 431,12 @@ func argb(c uint32) color.RGBA {
	return color.RGBA{A: uint8(c >> 24), R: uint8(c >> 16), G: uint8(c >> 8), B: uint8(c)}
}

func (a *App) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
func (a *App) Layout(c ui.Config, q input.Queue, ops *ui.Ops, ctx *layout.Context) {
	a.update(c, q)
	return a.stack.Current().Layout(c, q, ops, cs)
	a.stack.Current().Layout(c, q, ops, ctx)
}

func (a *App) layoutTimings(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
func (a *App) layoutTimings(c ui.Config, q input.Queue, ops *ui.Ops, ctx *layout.Context) {
	for e, ok := q.Next(a); ok; e, ok = q.Next(a) {
		if e, ok := e.(system.ProfileEvent); ok {
			a.profile = e


@@ 449,12 449,12 @@ func (a *App) layoutTimings(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.C
	mallocs := mstats.Mallocs - a.lastMallocs
	a.lastMallocs = mstats.Mallocs
	al := layout.Align{Alignment: layout.NE}
	return al.Layout(ops, cs, func(cs layout.Constraints) layout.Dimensions {
	al.Layout(ops, ctx, func() {
		in := a.env.insets
		in.Top = ui.Max(c, ui.Dp(16), in.Top)
		return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
		in.Layout(c, ops, ctx, func() {
			txt := fmt.Sprintf("m: %d %s", mallocs, a.profile.Timings)
			return text.Label{Material: theme.text, Face: a.env.faces.For(fonts.mono, ui.Sp(10)), Text: txt}.Layout(ops, cs)
			text.Label{Material: theme.text, Face: a.env.faces.For(fonts.mono, ui.Sp(10)), Text: txt}.Layout(ops, ctx)
		})
	})
}


@@ 538,7 538,7 @@ func (p *contactsPage) queryContacts(q string) {
	}()
}

func (p *contactsPage) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
func (p *contactsPage) Layout(c ui.Config, q input.Queue, ops *ui.Ops, ctx *layout.Context) {
	for e := p.Event(c, q); e != nil; e = p.Event(c, q) {
	}
	l := p.list


@@ 546,22 546,22 @@ func (p *contactsPage) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout
		key.HideInputOp{}.Add(ops)
	}
	f := layout.Flex{Axis: layout.Vertical}
	f.Init(ops, cs)
	c1 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
		return p.topbar.Layout(c, p.env.insets, ops, cs, func(cs layout.Constraints) layout.Dimensions {
			return p.searchEdit.Layout(c, q, ops, cs)
	f.Init(ops, ctx)
	c1 := f.Rigid(func() {
		p.topbar.Layout(c, p.env.insets, ops, ctx, func() {
			p.searchEdit.Layout(c, q, ops, ctx)
		})
	})
	c2 := f.Flexible(1, func(cs layout.Constraints) layout.Dimensions {
		cs.Height.Min = cs.Height.Max
		return l.Layout(c, q, ops, cs, len(p.contacts), func(cs layout.Constraints, i int) layout.Dimensions {
			return p.contact(c, ops, cs, i)
	c2 := f.Flexible(1, func() {
		ctx.Constraints.Height.Min = ctx.Constraints.Height.Max
		l.Layout(c, q, ops, ctx, len(p.contacts), func(i int) {
			p.contact(c, ops, ctx, i)
		})
	})
	return f.Layout(c1, c2)
	f.Layout(c1, c2)
}

func (p *contactsPage) contact(c ui.Config, ops *ui.Ops, cs layout.Constraints, index int) layout.Dimensions {
func (p *contactsPage) contact(c ui.Config, ops *ui.Ops, ctx *layout.Context, index int) {
	in := layout.Inset{
		Top:    ui.Dp(16),
		Bottom: ui.Dp(16),


@@ 570,28 570,27 @@ func (p *contactsPage) contact(c ui.Config, ops *ui.Ops, cs layout.Constraints, 
	}
	contact := p.contacts[index]
	click := &p.clicks[index]
	dims := in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
		f := (&layout.Flex{Alignment: layout.Middle}).Init(ops, cs)
		c1 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
	in.Layout(c, ops, ctx, func() {
		f := (&layout.Flex{Alignment: layout.Middle}).Init(ops, ctx)
		c1 := f.Rigid(func() {
			in := layout.Inset{Right: ui.Dp(8)}
			return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
			in.Layout(c, ops, ctx, func() {
				cc := clipCircle{}
				return cc.Layout(ops, cs, func(cs layout.Constraints) layout.Dimensions {
				cc.Layout(ops, ctx, func() {
					sz := image.Point{X: c.Px(ui.Dp(48)), Y: c.Px(ui.Dp(48))}
					cs = layout.RigidConstraints(cs.Constrain(sz))
					return fill{theme.brand}.Layout(ops, cs)
					ctx.Constraints = layout.RigidConstraints(ctx.Constraints.Constrain(sz))
					fill{theme.brand}.Layout(ops, ctx)
				})
			})
		})
		c2 := f.Flexible(1, func(cs layout.Constraints) layout.Dimensions {
			return text.Label{Material: theme.text, Face: p.env.faces.For(fonts.regular, ui.Sp(18)), Text: contact.Address}.Layout(ops, cs)
		c2 := f.Flexible(1, func() {
			text.Label{Material: theme.text, Face: p.env.faces.For(fonts.regular, ui.Sp(18)), Text: contact.Address}.Layout(ops, ctx)
		})

		return f.Layout(c1, c2)
		f.Layout(c1, c2)
	})
	pointer.RectAreaOp{Rect: image.Rectangle{Max: dims.Size}}.Add(ops)
	pointer.RectAreaOp{Rect: image.Rectangle{Max: ctx.Dimensions.Size}}.Add(ops)
	click.Add(ops)
	return dims
}

func (t *Topbar) Event(c ui.Config, q input.Queue) interface{} {


@@ 603,7 602,7 @@ func (t *Topbar) Event(c ui.Config, q input.Queue) interface{} {
	return nil
}

func (t *Topbar) Layout(c ui.Config, insets layout.Inset, ops *ui.Ops, cs layout.Constraints, w layout.Widget) layout.Dimensions {
func (t *Topbar) Layout(c ui.Config, insets layout.Inset, ops *ui.Ops, ctx *layout.Context, w layout.Widget) {
	stack := layout.Stack{Alignment: layout.SW}
	insets = layout.Inset{
		Top:    ui.Add(c, insets.Top, ui.Dp(16)),


@@ 611,32 610,28 @@ func (t *Topbar) Layout(c ui.Config, insets layout.Inset, ops *ui.Ops, cs layout
		Left:   ui.Max(c, insets.Left, ui.Dp(16)),
		Right:  ui.Max(c, insets.Right, ui.Dp(16)),
	}
	stack.Init(ops, cs)
	stackContent := stack.Rigid(func(cs layout.Constraints) layout.Dimensions {
		return insets.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
	stack.Init(ops, ctx)
	stackContent := stack.Rigid(func() {
		insets.Layout(c, ops, ctx, func() {
			flex := layout.Flex{Alignment: layout.Middle}
			flex.Init(ops, cs)
			backChild := flex.Rigid(func(cs layout.Constraints) layout.Dimensions {
				dims := layout.Dimensions{}
			flex.Init(ops, ctx)
			backChild := flex.Rigid(func() {
				if t.Back {
					ico := (&icon{src: icons.NavigationArrowBack, size: ui.Dp(24)}).image(c, rgb(0xffffff))
					dims = widget.Image{Src: ico, Rect: ico.Bounds(), Scale: 1}.Layout(c, ops, cs)
					dims.Size.X += c.Px(ui.Dp(4))
					pointer.RectAreaOp{Rect: image.Rectangle{Max: dims.Size}}.Add(ops)
					widget.Image{Src: ico, Rect: ico.Bounds(), Scale: 1}.Layout(c, ops, ctx)
					ctx.Dimensions.Size.X += c.Px(ui.Dp(4))
					pointer.RectAreaOp{Rect: image.Rectangle{Max: ctx.Dimensions.Size}}.Add(ops)
					t.backClick.Add(ops)
				}
				return dims
			})
			content := flex.Flexible(1, func(cs layout.Constraints) layout.Dimensions {
				return w(cs)
			})
			return flex.Layout(backChild, content)
			content := flex.Flexible(1, w)
			flex.Layout(backChild, content)
		})
	})
	bg := stack.Expand(func(cs layout.Constraints) layout.Dimensions {
		return fill{theme.brand}.Layout(ops, cs)
	bg := stack.Expand(func() {
		fill{theme.brand}.Layout(ops, ctx)
	})
	return stack.Layout(bg, stackContent)
	stack.Layout(bg, stackContent)
}

func newSignInPage(env *Env) *signInPage {


@@ 690,88 685,84 @@ func (p *signInPage) Event(c ui.Config, q input.Queue) interface{} {
	return nil
}

func (p *signInPage) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
func (p *signInPage) Layout(c ui.Config, q input.Queue, ops *ui.Ops, ctx *layout.Context) {
	f := layout.Flex{Axis: layout.Vertical}
	f.Init(ops, cs)
	f.Init(ops, ctx)

	c1 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
	c1 := f.Rigid(func() {
		var t Topbar
		return t.Layout(c, p.env.insets, ops, cs, func(cs layout.Constraints) layout.Dimensions {
			return text.Label{Material: colorMaterial(ops, rgb(0xffffff)), Face: p.env.faces.For(fonts.regular, ui.Sp(20)), Text: "Sign in"}.Layout(ops, cs)
		t.Layout(c, p.env.insets, ops, ctx, func() {
			text.Label{Material: colorMaterial(ops, rgb(0xffffff)), Face: p.env.faces.For(fonts.regular, ui.Sp(20)), Text: "Sign in"}.Layout(ops, ctx)
		})
	})

	c2 := f.Flexible(1, func(cs layout.Constraints) layout.Dimensions {
		return p.layoutSigninForm(c, q, ops, cs)
	c2 := f.Flexible(1, func() {
		p.layoutSigninForm(c, q, ops, ctx)
	})
	return f.Layout(c1, c2)
	f.Layout(c1, c2)
}

func (p *signInPage) layoutSigninForm(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
func (p *signInPage) layoutSigninForm(c ui.Config, q input.Queue, ops *ui.Ops, ctx *layout.Context) {
	l := p.list
	inset := layout.Inset{
		Left:  ui.Max(c, ui.Dp(32), p.env.insets.Left),
		Right: ui.Max(c, ui.Dp(32), p.env.insets.Right),
	}
	return l.Layout(c, q, ops, cs, len(p.fields)+1, func(cs layout.Constraints, i int) layout.Dimensions {
	l.Layout(c, q, ops, ctx, len(p.fields)+1, func(i int) {
		in := inset
		var dims layout.Dimensions
		switch {
		case i < len(p.fields):
			in.Bottom = ui.Dp(12)
			if i == 0 {
				in.Top = ui.Dp(32)
			}
			dims = in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
				return p.fields[i].Layout(c, q, ops, cs)
			in.Layout(c, ops, ctx, func() {
				p.fields[i].Layout(c, q, ops, ctx)
			})
		default:
			in.Bottom = ui.Max(c, ui.Dp(32), p.env.insets.Bottom)
			align := layout.Align{Alignment: layout.E}
			dims = align.Layout(ops, cs, func(cs layout.Constraints) layout.Dimensions {
				return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
					return p.submit.Layout(c, p.env, ops, cs)
			align.Layout(ops, ctx, func() {
				in.Layout(c, ops, ctx, func() {
					p.submit.Layout(c, p.env, ops, ctx)
				})
			})
		}
		return dims
	})
}

func (f *formField) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
func (f *formField) Layout(c ui.Config, q input.Queue, ops *ui.Ops, ctx *layout.Context) {
	theme.text.Add(ops)
	fl := (&layout.Flex{Axis: layout.Vertical}).Init(ops, cs)
	fl := (&layout.Flex{Axis: layout.Vertical}).Init(ops, ctx)

	header := text.Label{Material: theme.text, Text: f.Header, Face: f.env.faces.For(fonts.bold, ui.Sp(12))}
	c1 := fl.Rigid(func(cs layout.Constraints) layout.Dimensions {
		cs.Width.Min = cs.Width.Max
		dims := header.Layout(ops, cs)
		dims.Size.Y += c.Px(ui.Dp(4))
		return dims
	c1 := fl.Rigid(func() {
		ctx.Constraints.Width.Min = ctx.Constraints.Width.Max
		header.Layout(ops, ctx)
		ctx.Dimensions.Size.Y += c.Px(ui.Dp(4))
	})
	c2 := fl.Rigid(func(cs layout.Constraints) layout.Dimensions {
		return f.edit.Layout(c, q, ops, cs)
	c2 := fl.Rigid(func() {
		f.edit.Layout(c, q, ops, ctx)
	})
	return fl.Layout(c1, c2)
	fl.Layout(c1, c2)
}

func (b *Button) Next(q input.Queue) (gesture.ClickEvent, bool) {
	return b.click.Next(q)
}

func (b *Button) Layout(c ui.Config, env *Env, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
func (b *Button) Layout(c ui.Config, env *Env, ops *ui.Ops, ctx *layout.Context) {
	bg := Background{
		Material: theme.brand,
		Radius:   ui.Dp(4),
		Inset:    layout.UniformInset(ui.Dp(8)),
	}
	dims := bg.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
	bg.Layout(c, ops, ctx, func() {
		lbl := text.Label{Material: theme.white, Face: env.faces.For(fonts.regular, ui.Sp(16)), Text: b.Label, Alignment: text.Middle}
		return lbl.Layout(ops, cs)
		lbl.Layout(ops, ctx)
	})
	pointer.RectAreaOp{Rect: image.Rectangle{Max: dims.Size}}.Add(ops)
	pointer.RectAreaOp{Rect: image.Rectangle{Max: ctx.Dimensions.Size}}.Add(ops)
	b.click.Add(ops)
	return dims
}

type Background struct {


@@ 780,16 771,15 @@ type Background struct {
	Inset    layout.Inset
}

func (b *Background) Layout(c ui.Config, ops *ui.Ops, cs layout.Constraints, w layout.Widget) layout.Dimensions {
func (b *Background) Layout(c ui.Config, ops *ui.Ops, ctx *layout.Context, w layout.Widget) {
	var macro ui.MacroOp
	macro.Record(ops)
	dims := b.Inset.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
		return w(cs)
	})
	b.Inset.Layout(c, ops, ctx, w)
	macro.Stop()
	var stack ui.StackOp
	stack.Push(ops)
	width, height := float32(dims.Size.X), float32(dims.Size.Y)
	size := ctx.Dimensions.Size
	width, height := float32(size.X), float32(size.Y)
	if r := float32(c.Px(b.Radius)); r > 0 {
		if r > width/2 {
			r = width / 2


@@ 803,7 793,6 @@ func (b *Background) Layout(c ui.Config, ops *ui.Ops, cs layout.Constraints, w l
	paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{X: width, Y: height}}}.Add(ops)
	macro.Add(ops)
	stack.Pop()
	return dims
}

func newThreadsPage(env *Env) *threadsPage {


@@ 869,47 858,47 @@ func (p *threadsPage) fetchThreads() {
	}()
}

func (p *threadsPage) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
func (p *threadsPage) Layout(c ui.Config, q input.Queue, ops *ui.Ops, ctx *layout.Context) {
	st := layout.Stack{Alignment: layout.Center}
	st.Init(ops, cs)
	st.Init(ops, ctx)

	c1 := st.Rigid(func(cs layout.Constraints) layout.Dimensions {
	c1 := st.Rigid(func() {
		f := layout.Flex{Axis: layout.Vertical}
		f.Init(ops, cs)
		f.Init(ops, ctx)

		c1 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
		c1 := f.Rigid(func() {
			var t Topbar
			return t.Layout(c, p.env.insets, ops, cs, func(cs layout.Constraints) layout.Dimensions {
				return text.Label{Material: theme.white, Face: p.env.faces.For(fonts.regular, ui.Sp(20)), Text: p.account.User}.Layout(ops, cs)
			t.Layout(c, p.env.insets, ops, ctx, func() {
				text.Label{Material: theme.white, Face: p.env.faces.For(fonts.regular, ui.Sp(20)), Text: p.account.User}.Layout(ops, ctx)
			})
		})

		c2 := f.Flexible(1, func(cs layout.Constraints) layout.Dimensions {
			return p.layoutThreads(c, q, ops, cs)
		c2 := f.Flexible(1, func() {
			p.layoutThreads(c, q, ops, ctx)
		})
		return f.Layout(c1, c2)
		f.Layout(c1, c2)
	})
	c2 := st.Rigid(func(cs layout.Constraints) layout.Dimensions {
	c2 := st.Rigid(func() {
		al := layout.Align{Alignment: layout.SE}
		in := layout.Inset{
			Right:  ui.Max(c, ui.Dp(16), p.env.insets.Right),
			Bottom: ui.Max(c, ui.Dp(16), p.env.insets.Bottom),
		}
		return al.Layout(ops, cs, func(cs layout.Constraints) layout.Dimensions {
			return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
				return p.fab.Layout(c, p.env, ops, cs)
		al.Layout(ops, ctx, func() {
			in.Layout(c, ops, ctx, func() {
				p.fab.Layout(c, p.env, ops, ctx)
			})
		})
	})
	return st.Layout(c1, c2)
	st.Layout(c1, c2)
}

func (p *threadsPage) layoutThreads(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
func (p *threadsPage) layoutThreads(c ui.Config, q input.Queue, ops *ui.Ops, ctx *layout.Context) {
	l := p.list
	if l.Dragging() {
		key.HideInputOp{}.Add(ops)
	}
	return l.Layout(c, q, ops, cs, len(p.threads), func(cs layout.Constraints, i int) layout.Dimensions {
	l.Layout(c, q, ops, ctx, len(p.threads), func(i int) {
		in := layout.Inset{}
		switch i {
		case 0:


@@ 917,8 906,8 @@ func (p *threadsPage) layoutThreads(c ui.Config, q input.Queue, ops *ui.Ops, cs 
		case len(p.threads) - 1:
			in.Bottom = ui.Max(c, ui.Dp(4), p.env.insets.Bottom)
		}
		return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
			return p.thread(c, ops, cs, i)
		in.Layout(c, ops, ctx, func() {
			p.thread(c, ops, ctx, i)
		})
	})
}


@@ 932,7 921,7 @@ var contactColors = []color.RGBA{
	{A: 0xff, R: 0x00, G: 0x89, B: 0x7b},
}

func (p *threadsPage) thread(c ui.Config, ops *ui.Ops, cs layout.Constraints, index int) layout.Dimensions {
func (p *threadsPage) thread(c ui.Config, ops *ui.Ops, ctx *layout.Context, index int) {
	t := p.threads[index]
	bgtexmat := theme.tertText
	font := fonts.regular


@@ 945,83 934,82 @@ func (p *threadsPage) thread(c ui.Config, ops *ui.Ops, cs layout.Constraints, in
		Left:  ui.Max(c, ui.Dp(16), p.env.insets.Left),
		Right: ui.Max(c, ui.Dp(16), p.env.insets.Right),
	}
	return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
	in.Layout(c, ops, ctx, func() {
		elem := layout.Flex{Axis: layout.Vertical}
		elem.Init(ops, cs)
		c1 := elem.Rigid(func(cs layout.Constraints) layout.Dimensions {
		elem.Init(ops, ctx)
		c1 := elem.Rigid(func() {
			in := layout.Inset{Top: ui.Dp(8), Bottom: ui.Dp(8)}
			dims := in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
			in.Layout(c, ops, ctx, func() {
				f := centerRowOpts()
				f.Init(ops, cs)
				c1 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
				f.Init(ops, ctx)
				c1 := f.Rigid(func() {
					in := layout.Inset{Right: ui.Dp(12)}
					cc := clipCircle{}
					return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
						return cc.Layout(ops, cs, func(cs layout.Constraints) layout.Dimensions {
					in.Layout(c, ops, ctx, func() {
						cc.Layout(ops, ctx, func() {
							st := layout.Stack{Alignment: layout.Center}
							st.Init(ops, cs)
							st.Init(ops, ctx)

							// Background color
							c1 := st.Rigid(func(cs layout.Constraints) layout.Dimensions {
							c1 := st.Rigid(func() {
								sz := image.Point{X: c.Px(ui.Dp(48)), Y: c.Px(ui.Dp(48))}
								cs = layout.RigidConstraints(cs.Constrain(sz))
								ctx.Constraints = layout.RigidConstraints(ctx.Constraints.Constrain(sz))
								color := contactColors[index%len(contactColors)]
								mat := colorMaterial(ops, color)
								return fill{mat}.Layout(ops, cs)
								fill{mat}.Layout(ops, ctx)
							})

							// Contact initial.
							c2 := st.Rigid(func(cs layout.Constraints) layout.Dimensions {
							c2 := st.Rigid(func() {
								initial := ""
								for _, c := range t.ID {
									initial = string(unicode.ToUpper(c))
									break
								}
								face := p.env.faces.For(fonts.regular, ui.Sp(24))
								return text.Label{Material: theme.white, Face: face, Text: initial}.Layout(ops, cs)
								text.Label{Material: theme.white, Face: face, Text: initial}.Layout(ops, ctx)
							})
							return st.Layout(c1, c2)
							st.Layout(c1, c2)
						})
					})
				})
				c2 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
				c2 := f.Rigid(func() {
					f := column()
					f.Init(ops, cs)
					c1 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
					f.Init(ops, ctx)
					c1 := f.Rigid(func() {
						f := baseline()
						f.Init(ops, cs)
						c1 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
							return text.Label{Material: theme.text, Face: p.env.faces.For(font, ui.Sp(18)), Text: t.ID}.Layout(ops, cs)
						f.Init(ops, ctx)
						c1 := f.Rigid(func() {
							text.Label{Material: theme.text, Face: p.env.faces.For(font, ui.Sp(18)), Text: t.ID}.Layout(ops, ctx)
						})
						c2 := f.Flexible(1, func(cs layout.Constraints) layout.Dimensions {
							cs.Width.Min = cs.Width.Max
						c2 := f.Flexible(1, func() {
							ctx.Constraints.Width.Min = ctx.Constraints.Width.Max
							in := layout.Inset{Left: ui.Dp(2)}
							return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
								return text.Label{
							in.Layout(c, ops, ctx, func() {
								text.Label{
									Alignment: text.End,
									Material:  bgtexmat,
									Face:      p.env.faces.For(font, ui.Sp(12)),
									Text:      formatTime(t.Updated),
								}.Layout(ops, cs)
								}.Layout(ops, ctx)
							})
						})
						return f.Layout(c1, c2)
						f.Layout(c1, c2)
					})
					c2 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
					c2 := f.Rigid(func() {
						in := layout.Inset{Top: ui.Dp(6)}
						return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
							return text.Label{Material: bgtexmat, Face: p.env.faces.For(font, ui.Sp(14)), MaxLines: 1, Text: t.Snippet}.Layout(ops, cs)
						in.Layout(c, ops, ctx, func() {
							text.Label{Material: bgtexmat, Face: p.env.faces.For(font, ui.Sp(14)), MaxLines: 1, Text: t.Snippet}.Layout(ops, ctx)
						})
					})
					return f.Layout(c1, c2)
					f.Layout(c1, c2)
				})
				return f.Layout(c1, c2)
				f.Layout(c1, c2)
			})
			pointer.RectAreaOp{Rect: image.Rectangle{Max: dims.Size}}.Add(ops)
			pointer.RectAreaOp{Rect: image.Rectangle{Max: ctx.Dimensions.Size}}.Add(ops)
			click.Add(ops)
			return dims
		})
		return elem.Layout(c1)
		elem.Layout(c1)
	})
}



@@ 1113,7 1101,7 @@ func (p *threadPage) sendMessage() {
	}
}

func (p *threadPage) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
func (p *threadPage) Layout(c ui.Config, q input.Queue, ops *ui.Ops, ctx *layout.Context) {
	l := p.list
	if l.Dragging() {
		key.HideInputOp{}.Add(ops)


@@ 1123,79 1111,78 @@ func (p *threadPage) Layout(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.C
	default:
	}
	f := layout.Flex{Axis: layout.Vertical}
	f.Init(ops, cs)
	c1 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
		return p.topbar.Layout(c, p.env.insets, ops, cs, func(cs layout.Constraints) layout.Dimensions {
			return text.Label{Material: theme.white, Face: p.env.faces.For(fonts.regular, ui.Sp(20)), Text: p.thread.ID}.Layout(ops, cs)
	f.Init(ops, ctx)
	c1 := f.Rigid(func() {
		p.topbar.Layout(c, p.env.insets, ops, ctx, func() {
			text.Label{Material: theme.white, Face: p.env.faces.For(fonts.regular, ui.Sp(20)), Text: p.thread.ID}.Layout(ops, ctx)
		})
	})

	c3 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
	c3 := f.Rigid(func() {
		in := layout.Inset{
			Top:    ui.Dp(16),
			Left:   ui.Max(c, ui.Dp(16), p.env.insets.Left),
			Right:  ui.Max(c, ui.Dp(16), p.env.insets.Right),
			Bottom: ui.Max(c, ui.Dp(16), p.env.insets.Bottom),
		}
		return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
		in.Layout(c, ops, ctx, func() {
			switch {
			case p.thread.PendingInvitation:
				return p.accept.Layout(c, p.env, ops, cs)
				p.accept.Layout(c, p.env, ops, ctx)
			case p.env.client.ContainsSession(p.thread.ID):
				return p.layoutMessageBox(c, q, ops, cs)
				p.layoutMessageBox(c, q, ops, ctx)
			default:
				return p.invite.Layout(c, p.env, ops, cs)
				p.invite.Layout(c, p.env, ops, ctx)
			}
		})
	})

	c2 := f.Flexible(1, func(cs layout.Constraints) layout.Dimensions {
		cs.Height.Min = cs.Height.Max
		return l.Layout(c, q, ops, cs, len(p.messages), func(cs layout.Constraints, i int) layout.Dimensions {
			return p.message(c, ops, cs, i)
	c2 := f.Flexible(1, func() {
		ctx.Constraints.Height.Min = ctx.Constraints.Height.Max
		l.Layout(c, q, ops, ctx, len(p.messages), func(i int) {
			p.message(c, ops, ctx, i)
		})
	})
	return f.Layout(c1, c2, c3)
	f.Layout(c1, c2, c3)
}

func (p *threadPage) layoutMessageBox(c ui.Config, q input.Queue, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
	if mh := c.Px(ui.Dp(100)); cs.Height.Max > mh {
		cs.Height.Max = mh
func (p *threadPage) layoutMessageBox(c ui.Config, q input.Queue, ops *ui.Ops, ctx *layout.Context) {
	if mh := c.Px(ui.Dp(100)); ctx.Constraints.Height.Max > mh {
		ctx.Constraints.Height.Max = mh
	}
	f := (&layout.Flex{Alignment: layout.End}).Init(ops, cs)
	f := (&layout.Flex{Alignment: layout.End}).Init(ops, ctx)

	var sendHeight int
	c2 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
	c2 := f.Rigid(func() {
		in := layout.Inset{Left: ui.Dp(8)}
		return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
			dims := p.send.Layout(c, p.env, ops, cs)
			sendHeight = dims.Size.Y
			return dims
		in.Layout(c, ops, ctx, func() {
			p.send.Layout(c, p.env, ops, ctx)
			sendHeight = ctx.Dimensions.Size.Y
		})
	})

	c1 := f.Flexible(1, func(cs layout.Constraints) layout.Dimensions {
		cs.Width.Min = cs.Width.Max
		if cs.Height.Min < sendHeight {
			cs.Height.Min = sendHeight
	c1 := f.Flexible(1, func() {
		ctx.Constraints.Width.Min = ctx.Constraints.Width.Max
		if ctx.Constraints.Height.Min < sendHeight {
			ctx.Constraints.Height.Min = sendHeight
		}
		bg := Background{
			Material: colorMaterial(ops, rgb(0xeeeeee)),
			Inset:    layout.UniformInset(ui.Dp(8)),
			Radius:   ui.Dp(10),
		}
		return bg.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
		bg.Layout(c, ops, ctx, func() {
			align := layout.Align{Alignment: layout.W}
			return align.Layout(ops, cs, func(cs layout.Constraints) layout.Dimensions {
				cs.Width.Min = cs.Width.Max
				return p.msgEdit.Layout(c, q, ops, cs)
			align.Layout(ops, ctx, func() {
				ctx.Constraints.Width.Min = ctx.Constraints.Width.Max
				p.msgEdit.Layout(c, q, ops, ctx)
			})
		})
	})
	return f.Layout(c1, c2)
	f.Layout(c1, c2)
}

func (p *threadPage) message(c ui.Config, ops *ui.Ops, cs layout.Constraints, index int) layout.Dimensions {
func (p *threadPage) message(c ui.Config, ops *ui.Ops, ctx *layout.Context, index int) {
	msg := p.messages[index]
	in := layout.Inset{Top: ui.Dp(16), Left: ui.Dp(16), Right: ui.Dp(40)}
	align := layout.Align{Alignment: layout.W}


@@ 1211,58 1198,57 @@ func (p *threadPage) message(c ui.Config, ops *ui.Ops, cs layout.Constraints, in
	}
	in.Left = ui.Max(c, in.Left, p.env.insets.Left)
	in.Right = ui.Max(c, in.Right, p.env.insets.Right)
	return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
		return align.Layout(ops, cs, func(cs layout.Constraints) layout.Dimensions {
	in.Layout(c, ops, ctx, func() {
		align.Layout(ops, ctx, func() {
			bg := Background{
				Material: bgcol,
				Inset:    layout.Inset{Top: ui.Dp(8), Bottom: ui.Dp(8), Left: ui.Dp(12), Right: ui.Dp(12)},
				Radius:   ui.Dp(10),
			}
			return bg.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
			bg.Layout(c, ops, ctx, func() {
				f := layout.Flex{Axis: layout.Vertical}
				f.Init(ops, cs)
				f.Init(ops, ctx)

				var msgWidth int
				c1 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
				c1 := f.Rigid(func() {
					label := text.Label{Material: msgMat, Face: p.env.faces.For(fonts.regular, ui.Sp(14)), Text: msg.Message}
					dims := label.Layout(ops, cs)
					dims.Size.Y += c.Px(ui.Dp(4))
					msgWidth = dims.Size.X
					return dims
					label.Layout(ops, ctx)
					ctx.Dimensions.Size.Y += c.Px(ui.Dp(4))
					msgWidth = ctx.Dimensions.Size.X
				})

				c2 := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
					cs.Width.Min = msgWidth
				c2 := f.Rigid(func() {
					ctx.Constraints.Width.Min = msgWidth
					f := layout.Flex{Axis: layout.Horizontal, Spacing: layout.SpaceBetween, Alignment: layout.Middle}
					f.Init(ops, cs)
					f.Init(ops, ctx)

					var children []layout.FlexChild
					child := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
					child := f.Rigid(func() {
						time := formatTime(msg.Time)
						tlbl := text.Label{Material: colorMaterial(ops, timecol), Face: p.env.faces.For(fonts.regular, ui.Sp(10)), Text: time}
						return tlbl.Layout(ops, cs)
						tlbl.Layout(ops, ctx)
					})
					children = append(children, child)

					if msg.Own {
						child := f.Rigid(func(cs layout.Constraints) layout.Dimensions {
						child := f.Rigid(func() {
							in := layout.Inset{Left: ui.Dp(12)}
							return in.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
							in.Layout(c, ops, ctx, func() {
								checkmark := p.checkmark.image(c, timecol)
								r := checkmark.Bounds()
								if msg.Sent {
									paint.ImageOp{Src: checkmark, Rect: r}.Add(ops)
									paint.PaintOp{Rect: toRectF(r)}.Add(ops)
								}
								return layout.Dimensions{Size: r.Size()}
								ctx.Dimensions = layout.Dimensions{Size: r.Size()}
							})
						})
						children = append(children, child)
					}
					return f.Layout(children...)
					f.Layout(children...)
				})

				return f.Layout(c1, c2)
				f.Layout(c1, c2)
			})
		})
	})


@@ 1301,21 1287,20 @@ func (b *IconButton) Next(q input.Queue) (gesture.ClickEvent, bool) {
	return b.click.Next(q)
}

func (b *IconButton) Layout(c ui.Config, env *Env, ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
func (b *IconButton) Layout(c ui.Config, env *Env, ops *ui.Ops, ctx *layout.Context) {
	ico := b.Icon.image(c, rgb(0xffffff))
	bg := Background{
		Material: theme.brand,
		Radius:   ui.Px(1e6),
		Inset:    b.Inset,
	}
	dims := bg.Layout(c, ops, cs, func(cs layout.Constraints) layout.Dimensions {
	bg.Layout(c, ops, ctx, func() {
		sz := image.Point{X: ico.Bounds().Dx(), Y: ico.Bounds().Dy()}
		cs = layout.RigidConstraints(cs.Constrain(sz))
		return widget.Image{Src: ico, Rect: ico.Bounds(), Scale: 1}.Layout(c, ops, cs)
		ctx.Constraints = layout.RigidConstraints(ctx.Constraints.Constrain(sz))
		widget.Image{Src: ico, Rect: ico.Bounds(), Scale: 1}.Layout(c, ops, ctx)
	})
	pointer.EllipseAreaOp{Rect: image.Rectangle{Max: dims.Size}}.Add(ops)
	pointer.EllipseAreaOp{Rect: image.Rectangle{Max: ctx.Dimensions.Size}}.Add(ops)
	b.click.Add(ops)
	return dims
}

func (a *App) update(c ui.Config, q input.Queue) {


@@ 1341,14 1326,15 @@ type fill struct {
	material ui.MacroOp
}

func (f fill) Layout(ops *ui.Ops, cs layout.Constraints) layout.Dimensions {
func (f fill) Layout(ops *ui.Ops, ctx *layout.Context) {
	cs := ctx.Constraints
	d := image.Point{X: cs.Width.Max, Y: cs.Height.Max}
	dr := f32.Rectangle{
		Max: f32.Point{X: float32(d.X), Y: float32(d.Y)},
	}
	f.material.Add(ops)
	paint.PaintOp{Rect: dr}.Add(ops)
	return layout.Dimensions{Size: d, Baseline: d.Y}
	ctx.Dimensions = layout.Dimensions{Size: d, Baseline: d.Y}
}

func column() layout.Flex {


@@ 1366,10 1352,11 @@ func baseline() layout.Flex {
type clipCircle struct {
}

func (c *clipCircle) Layout(ops *ui.Ops, cs layout.Constraints, w layout.Widget) layout.Dimensions {
func (c *clipCircle) Layout(ops *ui.Ops, ctx *layout.Context, w layout.Widget) {
	var macro ui.MacroOp
	macro.Record(ops)
	dims := w(cs)
	w()
	dims := ctx.Dimensions
	macro.Stop()
	max := dims.Size.X
	if dy := dims.Size.Y; dy > max {


@@ 1382,7 1369,6 @@ func (c *clipCircle) Layout(ops *ui.Ops, cs layout.Constraints, w layout.Widget)
	rrect(ops, szf, szf, rr, rr, rr, rr)
	macro.Add(ops)
	stack.Pop()
	return dims
}

func toRectF(r image.Rectangle) f32.Rectangle {

M go.mod => go.mod +1 -2
@@ 3,8 3,7 @@ module scatter.im
go 1.13

require (
	gioui.org/cmd v0.0.0-20190918194648-1767588028e4 // indirect
	gioui.org/ui v0.0.0-20190918182616-7ca1d7a3fee0
	gioui.org/ui v0.0.0-20190924171458-ec307008db58
	github.com/eliasnaur/libsignal-protocol-go v0.0.0-20190626062856-3295f72b181e
	github.com/emersion/go-imap v1.0.0-rc.1
	github.com/emersion/go-imap-idle v0.0.0-20190519112320-2704abd7050e

M go.sum => go.sum +2 -7
@@ 1,8 1,5 @@
gioui.org v0.0.0-20190918182616-7ca1d7a3fee0 h1:YPIcZW30+ZqAknfTzCZJb2AydnTXJbtYABSmC/r31pQ=
gioui.org/cmd v0.0.0-20190918194648-1767588028e4 h1:BWeSUFGNdPWzjgOANEH1BiPtNbgepZenn9U/MxwKhFM=
gioui.org/cmd v0.0.0-20190918194648-1767588028e4/go.mod h1:kMRzpXdArk4aGFuBDFqLSax2l/DhbnFWtEZJOPmrAX0=
gioui.org/ui v0.0.0-20190918182616-7ca1d7a3fee0 h1:1popnecUCpLbiELuAfx0id4JtlN2xehC6QRLITvug30=
gioui.org/ui v0.0.0-20190918182616-7ca1d7a3fee0/go.mod h1:PssKPKlqVIeyaed+0w492Xc2NgX5M3n6oZKOAj5rxoE=
gioui.org/ui v0.0.0-20190924171458-ec307008db58 h1:nNoxWhsj7N/cwdB5F1+yEfXAS3FN5piTrEOzv0DejHg=
gioui.org/ui v0.0.0-20190924171458-ec307008db58/go.mod h1:PssKPKlqVIeyaed+0w492Xc2NgX5M3n6oZKOAj5rxoE=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/RadicalApp/complete v0.0.0-20170329192659-17e6c0ee499b h1:cAULFohNVfNzco0flF4okSPg3s7/tCj+hMIldtYZo4c=
github.com/RadicalApp/complete v0.0.0-20170329192659-17e6c0ee499b/go.mod h1:zZ3+l0EkpT2ZPnoamPBG50PBUtQrXwwyJ6elQZMmqgk=


@@ 54,8 51,6 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=