~whereswaldon/sprig

2e30e44a3c58a0e7fa7aa9cc94aa13f1bf9d5137 — Chris Waldon 1 year, 6 months ago a374d6a
deps: port to newer gio version
M community-menu-view.go => community-menu-view.go +49 -46
@@ 1,6 1,7 @@
package main

import (
	"image"
	"log"

	"gioui.org/layout"


@@ 36,90 37,92 @@ func NewCommunityMenuView(settings *Settings, arborState *ArborState, theme *mat
	return c
}

func (c *CommunityMenuView) Update(gtx *layout.Context) {
	if c.BackButton.Clicked(gtx) {
func (c *CommunityMenuView) Update(gtx layout.Context) {
	if c.BackButton.Clicked() {
		c.manager.RequestViewSwitch(ConnectForm)
	}
	for i := range c.CommunityBoxes {
		box := &c.CommunityBoxes[i]
		if box.Update(gtx) {
		if box.Changed() {
			log.Println("updated")
		}
	}
	if c.ViewButton.Clicked(gtx) {
	if c.ViewButton.Clicked() {
		c.manager.RequestViewSwitch(ReplyView)
	}
	if c.IdentityButton.Clicked(gtx) {
	if c.IdentityButton.Clicked() {
		c.manager.RequestViewSwitch(IdentityForm)
	}
}

func (c *CommunityMenuView) Layout(gtx *layout.Context) {
func (c *CommunityMenuView) Layout(gtx layout.Context) layout.Dimensions {
	theme := c.Theme
	c.CommunityList.Axis = layout.Vertical
	layout.NW.Layout(gtx, func() {
		layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
			material.IconButton(theme, icons.BackIcon).Layout(gtx, &c.BackButton)
		})
	layout.NW.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		return layout.UniformInset(unit.Dp(4)).Layout(
			gtx,
			material.IconButton(theme, &c.BackButton, icons.BackIcon).Layout,
		)
	})
	width := gtx.Constraints.Width.Constrain(gtx.Px(unit.Dp(200)))
	layout.Center.Layout(gtx, func() {
		gtx.Constraints.Width.Max = width
		layout.Flex{Axis: layout.Vertical}.Layout(gtx,
			layout.Rigid(func() {
	width := gtx.Constraints.Constrain(image.Point{X: gtx.Px(unit.Dp(200))}).X
	return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		gtx.Constraints.Max.X = width
		return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				if c.Settings.ActiveIdentity != nil {
					material.Body1(c.Theme, "Identity: "+c.Settings.ActiveIdentity.String()).Layout(gtx)
					return material.Body1(c.Theme, "Identity: "+c.Settings.ActiveIdentity.String()).Layout(gtx)
				} else {
					material.Button(c.Theme, "Create new Identity").Layout(gtx, &c.IdentityButton)
					return material.Button(c.Theme, &c.IdentityButton, "Create new Identity").Layout(gtx)
				}
			}),
			layout.Rigid(func() {
				gtx.Constraints.Width.Max = width
				layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
					material.Body1(theme, "Choose communities to join:").Layout(gtx)
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				gtx.Constraints.Max.X = width
				return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					return material.Body1(theme, "Choose communities to join:").Layout(gtx)
				})
			}),
			layout.Rigid(func() {
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				var dims layout.Dimensions
				c.ArborState.CommunityList.WithCommunities(func(communities []*forest.Community) {
					gtx.Constraints.Width.Max = width
					gtx.Constraints.Max.X = width
					newCommunities := len(communities) - len(c.CommunityBoxes)
					for ; newCommunities > 0; newCommunities-- {
						c.CommunityBoxes = append(c.CommunityBoxes, widget.Bool{})
					}
					c.CommunityList.Layout(gtx, len(communities), func(index int) {
						gtx.Constraints.Width.Max = width
					dims = c.CommunityList.Layout(gtx, len(communities), func(gtx layout.Context, index int) layout.Dimensions {
						gtx.Constraints.Max.X = width
						community := communities[index]
						checkbox := &c.CommunityBoxes[index]
						layout.Flex{Axis: layout.Vertical}.Layout(gtx,
							layout.Rigid(func() {
								layout.Flex{}.Layout(gtx,
									layout.Rigid(func() {
										layout.UniformInset(unit.Dp(8)).Layout(gtx, func() {
											box := material.CheckBox(theme, "")
											box.Layout(gtx, checkbox)
										})
						return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
							layout.Rigid(func(gtx layout.Context) layout.Dimensions {
								return layout.Flex{}.Layout(gtx,
									layout.Rigid(func(gtx layout.Context) layout.Dimensions {
										return layout.UniformInset(unit.Dp(8)).Layout(gtx,
											material.CheckBox(theme, checkbox, "").Layout,
										)
									}),
									layout.Rigid(func() {
										layout.UniformInset(unit.Dp(8)).Layout(gtx, func() {
											material.H6(theme, string(community.Name.Blob)).Layout(gtx)
										})
									layout.Rigid(func(gtx layout.Context) layout.Dimensions {
										return layout.UniformInset(unit.Dp(8)).Layout(gtx,
											material.H6(theme, string(community.Name.Blob)).Layout,
										)
									}),
								)
							}),
							layout.Rigid(func() {
								layout.UniformInset(unit.Dp(8)).Layout(gtx, func() {
									material.Body2(theme, community.ID().String()).Layout(gtx)
								})
							layout.Rigid(func(gtx layout.Context) layout.Dimensions {
								return layout.UniformInset(unit.Dp(8)).Layout(gtx,
									material.Body2(theme, community.ID().String()).Layout,
								)
							}),
						)
					})
				})
				return dims
			}),
			layout.Rigid(func() {
				gtx.Constraints.Width.Max = width
				layout.Center.Layout(gtx, func() {
					gtx.Constraints.Width.Max = width
					material.Button(theme, "View These Communities").Layout(gtx, &c.ViewButton)
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				gtx.Constraints.Max.X = width
				return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					gtx.Constraints.Max.X = width
					return material.Button(theme, &c.ViewButton, "View These Communities").Layout(gtx)
				})
			}),
		)

M connect-form-view.go => connect-form-view.go +20 -20
@@ 35,9 35,9 @@ func NewConnectFormView(settings *Settings, arborState *ArborState, theme *mater
	return c
}

func (c *ConnectFormView) Update(gtx *layout.Context) {
func (c *ConnectFormView) Update(gtx layout.Context) {
	switch {
	case c.ConnectButton.Clicked(gtx):
	case c.ConnectButton.Clicked():
		c.Settings.Address = c.Editor.Text()
		go c.Settings.Persist()
		fallthrough


@@ 48,29 48,29 @@ func (c *ConnectFormView) Update(gtx *layout.Context) {
	c.initialized = true
}

func (c *ConnectFormView) Layout(gtx *layout.Context) {
func (c *ConnectFormView) Layout(gtx layout.Context) layout.Dimensions {
	theme := c.Theme
	layout.Center.Layout(gtx, func() {
		layout.Flex{Axis: layout.Vertical}.Layout(gtx,
			layout.Rigid(func() {
				layout.Center.Layout(gtx, func() {
					layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
						material.Body1(theme, "Arbor Relay Address:").Layout(gtx)
					})
	return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					return layout.UniformInset(unit.Dp(4)).Layout(gtx,
						material.Body1(theme, "Arbor Relay Address:").Layout,
					)
				})
			}),
			layout.Rigid(func() {
				layout.Center.Layout(gtx, func() {
					layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
						material.Editor(theme, "HOST:PORT").Layout(gtx, &(c.Editor))
					})
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					return layout.UniformInset(unit.Dp(4)).Layout(gtx,
						material.Editor(theme, &(c.Editor), "HOST:PORT").Layout,
					)
				})
			}),
			layout.Rigid(func() {
				layout.Center.Layout(gtx, func() {
					layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
						material.Button(theme, "Connect").Layout(gtx, &(c.ConnectButton))
					})
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					return layout.UniformInset(unit.Dp(4)).Layout(gtx,
						material.Button(theme, &(c.ConnectButton), "Connect").Layout,
					)
				})
			}),
		)

M go.mod => go.mod +2 -3
@@ 3,13 3,12 @@ module git.sr.ht/~whereswaldon/sprig
go 1.14

require (
	gioui.org v0.0.0-20200511145007-062e2bc54b10
	gioui.org/cmd v0.0.0-20200514114436-31acd5451e03 // indirect
	gioui.org v0.0.0-20200524174833-ad93e3212824
	git.sr.ht/~whereswaldon/forest-go v0.0.0-20200517003538-529ac9248d93
	git.sr.ht/~whereswaldon/sprout-go v0.0.0-20200517010141-a4188845a9a8
	git.sr.ht/~whereswaldon/wisteria v0.0.12-0.20200517191829-5c1c1cff11e2
	golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c
	golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5
	golang.org/x/text v0.3.2 // indirect
)

replace golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20200416114516-1fa7f403fb9c

M go.sum => go.sum +2 -47
@@ 1,58 1,16 @@
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gioui.org v0.0.0-20200510064036-c2cbcee78de0/go.mod h1:AHI9rFr6AEEHCb8EPVtb/p5M+NMJRKH58IOp8O3Je04=
gioui.org v0.0.0-20200511145007-062e2bc54b10 h1:xdzV3vgzeaf7+n+ON0xMjim0rKQeZ47ZJD+7t7TrUWk=
gioui.org v0.0.0-20200511145007-062e2bc54b10/go.mod h1:AHI9rFr6AEEHCb8EPVtb/p5M+NMJRKH58IOp8O3Je04=
gioui.org v0.0.0-20200514114436-31acd5451e03 h1:7y8Hok1HPkhkiPNXyuOqf3X9Sa4arQv0gQVaH9BKXp0=
gioui.org/cmd v0.0.0-20200514114436-31acd5451e03 h1:J7aLFGDZFj/6BKliYc48jb+Z1nPUbP9CT8I3RB+i05I=
gioui.org/cmd v0.0.0-20200514114436-31acd5451e03/go.mod h1:o91DwJztzpmnGKoR08+hCT/m7QyISZwBrngk5vVMoFs=
git.sr.ht/~whereswaldon/forest-go v0.0.0-20200207033954-0859340e8253 h1:/2kO5f1X7Ge4efmRwPF5MHiZMmKonBsypTiPnZeQwys=
git.sr.ht/~whereswaldon/forest-go v0.0.0-20200207033954-0859340e8253/go.mod h1:LV1LNV6Mg4ajBo2fOTk16RIaC9Oh8pB+SpgXev6kAoU=
git.sr.ht/~whereswaldon/forest-go v0.0.0-20200319194448-e3a47dd95cda h1:ZF4rIf3sXbTsw92VhjZM51t8FqqNBjfnm/sgdILCInU=
git.sr.ht/~whereswaldon/forest-go v0.0.0-20200319194448-e3a47dd95cda/go.mod h1:I3iVoJydL66XlhJQ9+L1RJHk/cSlsxDxEG0BbU5Senk=
gioui.org v0.0.0-20200524174833-ad93e3212824 h1:vQP8qwWQXun8lXmj707eZcsuem2X6aF3w0z8CnrL8PI=
gioui.org v0.0.0-20200524174833-ad93e3212824/go.mod h1:AHI9rFr6AEEHCb8EPVtb/p5M+NMJRKH58IOp8O3Je04=
git.sr.ht/~whereswaldon/forest-go v0.0.0-20200517003538-529ac9248d93 h1:DXtB8FXojNZAj7+sqXhvVKM6Az36a18B1WuqvjG0D1Q=
git.sr.ht/~whereswaldon/forest-go v0.0.0-20200517003538-529ac9248d93/go.mod h1:aGmm4R7ifFBvJWOHINDvZcKVOu+ODkD75NmNm/O0zME=
git.sr.ht/~whereswaldon/sprout-go v0.0.0-20200208174132-0b59703c7bc8 h1:CvK7aHEGlshVyM5QyhkMIqRS+Pnz/P6cAOWp1ZmU1bY=
git.sr.ht/~whereswaldon/sprout-go v0.0.0-20200208174132-0b59703c7bc8/go.mod h1:OxbuKgcepjADdfl75kPkSmUmgKYWBWaSq2OrL2T7zkg=
git.sr.ht/~whereswaldon/sprout-go v0.0.0-20200319194723-df82b3bc1ee9 h1:1yN441LUagDMslcIct4YqUJZl4I3nK1J7fPURoipQS4=
git.sr.ht/~whereswaldon/sprout-go v0.0.0-20200319194723-df82b3bc1ee9/go.mod h1:/geNVTdJAS4wMB5Z0/HgkZdrT+MNZMg4Su40+4IZE0s=
git.sr.ht/~whereswaldon/sprout-go v0.0.0-20200517010141-a4188845a9a8 h1:Cl6MTNMZJgVVxe4vBXeqjAp1zfiQMXejn7VHra55Oqc=
git.sr.ht/~whereswaldon/sprout-go v0.0.0-20200517010141-a4188845a9a8/go.mod h1:aMg78wuZrnmbesMseUyCaQdj6OG1laIYpR5HbDJfG7Y=
git.sr.ht/~whereswaldon/wisteria v0.0.11 h1:jASYKlGx1OCEnbEotSnq16hEgsxV0B1UmiiLIkl8SRk=
git.sr.ht/~whereswaldon/wisteria v0.0.11/go.mod h1:neLsLisvj26K1k7PTcE+1l37z3Mod/slHQPiI4FveWg=
git.sr.ht/~whereswaldon/wisteria v0.0.12-0.20200517013608-53a00c8ed27d h1:K7R7rfx/YpqLjyU6211W2B5FSjDH+uxAxWBcraz1qDQ=
git.sr.ht/~whereswaldon/wisteria v0.0.12-0.20200517013608-53a00c8ed27d/go.mod h1:kVjyUMWEC7feeKaY6CmMlt5GrLXtke0gj0BRhNxe6gg=
git.sr.ht/~whereswaldon/wisteria v0.0.12-0.20200517191829-5c1c1cff11e2 h1:9stZUFmO4TEG0KYafj5QcBO3Uq9oXPvVvhMJxwFx4zo=
git.sr.ht/~whereswaldon/wisteria v0.0.12-0.20200517191829-5c1c1cff11e2/go.mod h1:kVjyUMWEC7feeKaY6CmMlt5GrLXtke0gj0BRhNxe6gg=
github.com/0xAX/notificator v0.0.0-20181105090803-d81462e38c21/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/ProtonMail/crypto v0.0.0-20200416114516-1fa7f403fb9c h1:DAvlgde2Stu18slmjwikiMPs/CKPV35wSvmJS34z0FU=
github.com/ProtonMail/crypto v0.0.0-20200416114516-1fa7f403fb9c/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
github.com/awnumar/memcall v0.0.0-20191004114545-73db50fd9f80/go.mod h1:S911igBPR9CThzd/hYQQmTc9SWNu3ZHIlCGaWsWsoJo=
github.com/awnumar/memguard v0.21.0/go.mod h1:+ejY3DekvjnDWBXHwL5xB5p4Il77kDsrIz+UOUNrm2Q=
github.com/bbrks/wrap/v2 v2.3.1-0.20191113183707-81f8a5d714b8/go.mod h1:FdEamYFrsjX8zlv3UXgnT3JxirrDv67jCDYaE0Q/qww=
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
github.com/chromedp/chromedp v0.5.2/go.mod h1:rsTo/xRo23KZZwFmWk2Ui79rBaVRRATCjLzNQlOFSiA=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/pkg/profile v1.3.0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4=
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo=


@@ 73,9 31,6 @@ golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=

M identity-form.go => identity-form.go +25 -25
@@ 29,43 29,43 @@ func NewIdentityFormView(settings *Settings, arborState *ArborState, theme *mate
	return c
}

func (c *IdentityFormView) Update(gtx *layout.Context) {
	if c.CreateButton.Clicked(gtx) {
func (c *IdentityFormView) Update(gtx layout.Context) {
	if c.CreateButton.Clicked() {
		c.Settings.CreateIdentity(c.Editor.Text())
		c.manager.RequestViewSwitch(CommunityMenu)
	}
}

func (c *IdentityFormView) Layout(gtx *layout.Context) {
func (c *IdentityFormView) Layout(gtx layout.Context) layout.Dimensions {
	theme := c.Theme
	layout.Center.Layout(gtx, func() {
		layout.Flex{Axis: layout.Vertical}.Layout(gtx,
			layout.Rigid(func() {
				layout.Center.Layout(gtx, func() {
					layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
						material.Body1(theme, "Your Arbor Username:").Layout(gtx)
					})
	return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
		return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					return layout.UniformInset(unit.Dp(4)).Layout(gtx,
						material.Body1(theme, "Your Arbor Username:").Layout,
					)
				})
			}),
			layout.Rigid(func() {
				layout.Center.Layout(gtx, func() {
					layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
						material.Editor(theme, "username").Layout(gtx, &(c.Editor))
					})
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					return layout.UniformInset(unit.Dp(4)).Layout(gtx,
						material.Editor(theme, &(c.Editor), "username").Layout,
					)
				})
			}),
			layout.Rigid(func() {
				layout.Center.Layout(gtx, func() {
					layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
						material.Body2(theme, "Your username is public, and cannot currently be changed once it is chosen.").Layout(gtx)
					})
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					return layout.UniformInset(unit.Dp(4)).Layout(gtx,
						material.Body2(theme, "Your username is public, and cannot currently be changed once it is chosen.").Layout,
					)
				})
			}),
			layout.Rigid(func() {
				layout.Center.Layout(gtx, func() {
					layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
						material.Button(theme, "Create").Layout(gtx, &(c.CreateButton))
					})
			layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				return layout.Center.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
					return layout.UniformInset(unit.Dp(4)).Layout(gtx,
						material.Button(theme, &(c.CreateButton), "Create").Layout,
					)
				})
			}),
		)

M main.go => main.go +4 -5
@@ 13,6 13,7 @@ import (
	"gioui.org/font/gofont"
	"gioui.org/io/system"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/widget/material"
	forest "git.sr.ht/~whereswaldon/forest-go"
	"git.sr.ht/~whereswaldon/forest-go/fields"


@@ 61,19 62,17 @@ func eventLoop(w *app.Window) error {
	appState.SubscribableStore.SubscribeToNewMessages(func(n forest.Node) {
		w.Invalidate()
	})
	gtx := new(layout.Context)
	var ops op.Ops
	for {
		switch event := (<-w.Events()).(type) {
		case system.DestroyEvent:
			return event.Err
		case system.FrameEvent:
			gtx.Reset(event.Queue, event.Config, event.Size)
			gtx := layout.NewContext(&ops, event.Queue, event.Config, event.Size)
			layout.Inset{
				Bottom: event.Insets.Bottom,
				Top:    event.Insets.Top,
			}.Layout(gtx, func() {
				viewManager.Layout(gtx)
			})
			}.Layout(gtx, viewManager.Layout)
			event.Frame(gtx.Ops)
		}
	}

M reply-view.go => reply-view.go +104 -97
@@ 1,6 1,7 @@
package main

import (
	"image"
	"image/color"
	"log"



@@ 73,10 74,10 @@ func NewReplyListView(settings *Settings, arborState *ArborState, theme *materia
	return c
}

func (c *ReplyListView) Update(gtx *layout.Context) {
func (c *ReplyListView) Update(gtx layout.Context) {
	for i := range c.ReplyStates {
		clickHandler := &c.ReplyStates[i]
		if clickHandler.Clicked(gtx) {
		if clickHandler.Clicked() {
			log.Printf("clicked %s", clickHandler.Reply)
			if c.Selected == nil || !clickHandler.Reply.Equals(c.Selected) {
				c.StateRefreshNeeded = true


@@ 93,17 94,17 @@ func (c *ReplyListView) Update(gtx *layout.Context) {
		c.Ancestry, _ = c.ArborState.SubscribableStore.AncestryOf(c.Selected)
		c.Descendants, _ = c.ArborState.SubscribableStore.DescendantsOf(c.Selected)
	}
	if c.BackButton.Clicked(gtx) {
	if c.BackButton.Clicked() {
		c.manager.RequestViewSwitch(CommunityMenu)
	}
	if c.DeselectButton.Clicked(gtx) {
	if c.DeselectButton.Clicked() {
		c.Selected = nil
	}
	c.updateReplyEditState(gtx)
}

func (c *ReplyListView) updateReplyEditState(gtx *layout.Context) {
	if c.Selected != nil && c.CreateReplyButton.Clicked(gtx) {
func (c *ReplyListView) updateReplyEditState(gtx layout.Context) {
	if c.Selected != nil && c.CreateReplyButton.Clicked() {
		reply, _, err := c.ArborState.SubscribableStore.Get(c.Selected)
		if err != nil {
			log.Printf("failed looking up selected message: %v", err)


@@ 117,13 118,13 @@ func (c *ReplyListView) updateReplyEditState(gtx *layout.Context) {
			}
		}
	}
	if c.CreateConversationButton.Clicked(gtx) {
	if c.CreateConversationButton.Clicked() {
		c.CreatingConversation = true
	}
	if c.CancelReplyButton.Clicked(gtx) {
	if c.CancelReplyButton.Clicked() {
		c.resetReplyState()
	}
	if c.SendReplyButton.Clicked(gtx) {
	if c.SendReplyButton.Clicked() {
		var newReply *forest.Reply
		var author *forest.Identity
		if c.CreatingConversation {


@@ 224,60 225,61 @@ func (c *ReplyListView) statusOf(reply *forest.Reply) replyStatus {
	return none
}

func (c *ReplyListView) Layout(gtx *layout.Context) {
	layout.Stack{}.Layout(gtx,
		layout.Stacked(func() {
			layout.Flex{Axis: layout.Vertical}.Layout(gtx,
				layout.Flexed(1, func() {
					c.layoutReplyList(gtx)
func (c *ReplyListView) Layout(gtx layout.Context) layout.Dimensions {
	return layout.Stack{}.Layout(gtx,
		layout.Stacked(func(gtx layout.Context) layout.Dimensions {
			return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
				layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
					return c.layoutReplyList(gtx)
				}),
				layout.Rigid(func() {
				layout.Rigid(func(gtx layout.Context) layout.Dimensions {
					if c.ReplyingTo != nil || c.CreatingConversation {
						c.layoutEditor(gtx)
						return c.layoutEditor(gtx)
					}
					return layout.Dimensions{}
				}),
			)
		}),
		layout.Stacked(func() {
			gtx.Constraints.Width.Min = gtx.Constraints.Width.Max
		layout.Stacked(func(gtx layout.Context) layout.Dimensions {
			gtx.Constraints.Min.X = gtx.Constraints.Max.X
			buttons := []layout.FlexChild{}
			buttons = append(buttons, layout.Rigid(func() {
				layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
					material.IconButton(c.Theme, icons.BackIcon).Layout(gtx, &c.BackButton)
				})
			buttons = append(buttons, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
				return layout.UniformInset(unit.Dp(4)).Layout(gtx,
					material.IconButton(c.Theme, &c.BackButton, icons.BackIcon).Layout,
				)
			}))
			if c.Selected != nil && c.ReplyingTo == nil {
				buttons = append(buttons, layout.Rigid(func() {
					layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
						material.IconButton(c.Theme, icons.ReplyIcon).Layout(gtx, &c.CreateReplyButton)
					})
				buttons = append(buttons, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
					return layout.UniformInset(unit.Dp(4)).Layout(gtx,
						material.IconButton(c.Theme, &c.CreateReplyButton, icons.ReplyIcon).Layout,
					)
				}))
			}
			if c.ReplyingTo != nil || c.CreatingConversation {
				buttons = append(buttons, layout.Rigid(func() {
					layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
						material.IconButton(c.Theme, icons.CancelReplyIcon).Layout(gtx, &c.CancelReplyButton)
					})
				buttons = append(buttons, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
					return layout.UniformInset(unit.Dp(4)).Layout(gtx,
						material.IconButton(c.Theme, &c.CancelReplyButton, icons.CancelReplyIcon).Layout,
					)
				}))
			}
			if !c.CreatingConversation {
				buttons = append(buttons,
					layout.Rigid(func() {
						layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
							material.IconButton(c.Theme, icons.CreateConversationIcon).Layout(gtx, &c.CreateConversationButton)
						})
					layout.Rigid(func(gtx layout.Context) layout.Dimensions {
						return layout.UniformInset(unit.Dp(4)).Layout(gtx,
							material.IconButton(c.Theme, &c.CreateConversationButton, icons.CreateConversationIcon).Layout,
						)
					}))
			}
			if c.Selected != nil {
				buttons = append(buttons,
					layout.Rigid(func() {
						layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
							material.IconButton(c.Theme, icons.ClearIcon).Layout(gtx, &c.DeselectButton)
						})
					layout.Rigid(func(gtx layout.Context) layout.Dimensions {
						return layout.UniformInset(unit.Dp(4)).Layout(gtx,
							material.IconButton(c.Theme, &c.DeselectButton, icons.ClearIcon).Layout,
						)
					}))
			}

			layout.Flex{Spacing: layout.SpaceBetween}.Layout(gtx, buttons...)
			return layout.Flex{Spacing: layout.SpaceBetween}.Layout(gtx, buttons...)
		}),
	)
}


@@ 294,15 296,15 @@ var (
//lightGray = color.RGBA{R: 230, G: 230, B: 230, A: 255}
)

func (c *ReplyListView) layoutReplyList(gtx *layout.Context) {
	gtx.Constraints.Height.Min = gtx.Constraints.Height.Max
	gtx.Constraints.Width.Min = gtx.Constraints.Width.Max
func (c *ReplyListView) layoutReplyList(gtx layout.Context) layout.Dimensions {
	gtx.Constraints.Min = gtx.Constraints.Max

	theme := c.Theme
	c.ReplyList.Axis = layout.Vertical
	stateIndex := 0
	var dims layout.Dimensions
	c.ArborState.ReplyList.WithReplies(func(replies []*forest.Reply) {
		c.ReplyList.Layout(gtx, len(replies), func(index int) {
		dims = c.ReplyList.Layout(gtx, len(replies), func(gtx layout.Context, index int) layout.Dimensions {
			if stateIndex >= len(c.ReplyStates) {
				c.ReplyStates = append(c.ReplyStates, sprigWidget.Reply{})
			}


@@ 343,7 345,7 @@ func (c *ReplyListView) layoutReplyList(gtx *layout.Context) {
			default:
				if c.Filtered {
					// do not render
					return
					return layout.Dimensions{}
				}
				leftInset = sideInset
				background = teal


@@ 352,103 354,108 @@ func (c *ReplyListView) layoutReplyList(gtx *layout.Context) {
				background.B += 10
				textColor = black
			}
			messageWidth := gtx.Constraints.Width.Max - gtx.Px(unit.Dp(36))
			layout.Stack{}.Layout(gtx,
				layout.Stacked(func() {
					gtx.Constraints.Width.Min = gtx.Constraints.Width.Max
					layout.Stack{}.Layout(gtx,
						layout.Expanded(func() {
			messageWidth := gtx.Constraints.Max.X - gtx.Px(unit.Dp(36))
			stateIndex++
			return layout.Stack{}.Layout(gtx,
				layout.Stacked(func(gtx layout.Context) layout.Dimensions {
					gtx.Constraints.Min.X = gtx.Constraints.Max.X
					return layout.Stack{}.Layout(gtx,
						layout.Expanded(func(gtx layout.Context) layout.Dimensions {
							paintOp := paint.ColorOp{Color: color.RGBA{G: 128, B: 128, A: 255}}
							paintOp.Add(gtx.Ops)
							paint.PaintOp{Rect: f32.Rectangle{
								Max: f32.Point{
									X: float32(gtx.Constraints.Width.Max),
									Y: float32(gtx.Constraints.Height.Max),
									X: float32(gtx.Constraints.Max.X),
									Y: float32(gtx.Constraints.Max.Y),
								},
							}}.Add(gtx.Ops)
							return layout.Dimensions{}
						}),
						layout.Stacked(func() {
						layout.Stacked(func(gtx layout.Context) layout.Dimensions {
							margin := unit.Dp(6)
							if collapseMetadata {
								margin = unit.Dp(3)
							}
							layout.Inset{Left: leftInset, Top: margin, Right: sideInset}.Layout(gtx, func() {
								gtx.Constraints.Width.Max = messageWidth
							return layout.Inset{Left: leftInset, Top: margin, Right: sideInset}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
								gtx.Constraints.Max.X = messageWidth
								replyWidget := sprigTheme.Reply(theme)
								replyWidget.Background = background
								replyWidget.TextColor = textColor
								replyWidget.CollapseMetadata = collapseMetadata
								replyWidget.Layout(gtx, reply, author)
								return replyWidget.Layout(gtx, reply, author)
							})
						}),
					)
				}),
				layout.Expanded(func() {
					state.Clickable.Layout(gtx)
				layout.Expanded(func(gtx layout.Context) layout.Dimensions {
					dims := state.Clickable.Layout(gtx)
					state.Reply = reply.ID()
					return dims
				}),
			)
			stateIndex++
		})
	})
	return dims
}

func (c *ReplyListView) layoutEditor(gtx *layout.Context) {
	layout.Stack{}.Layout(gtx,
		layout.Expanded(func() {
func (c *ReplyListView) layoutEditor(gtx layout.Context) layout.Dimensions {
	return layout.Stack{}.Layout(gtx,
		layout.Expanded(func(gtx layout.Context) layout.Dimensions {
			paintOp := paint.ColorOp{Color: brightTeal}
			paintOp.Add(gtx.Ops)
			paint.PaintOp{Rect: f32.Rectangle{
				Max: f32.Point{
					X: float32(gtx.Constraints.Width.Max),
					Y: float32(gtx.Constraints.Height.Max),
					X: float32(gtx.Constraints.Max.X),
					Y: float32(gtx.Constraints.Max.Y),
				},
			}}.Add(gtx.Ops)
			return layout.Dimensions{Size: gtx.Constraints.Max}
		}),
		layout.Stacked(func() {
			layout.Flex{Axis: layout.Vertical}.Layout(gtx,
				layout.Rigid(func() {
					layout.Flex{}.Layout(gtx,
						layout.Rigid(func() {
							layout.UniformInset(unit.Dp(6)).Layout(gtx, func() {
		layout.Stacked(func(gtx layout.Context) layout.Dimensions {
			return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
				layout.Rigid(func(gtx layout.Context) layout.Dimensions {
					return layout.Flex{}.Layout(gtx,
						layout.Rigid(func(gtx layout.Context) layout.Dimensions {
							return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
								if c.CreatingConversation {
									material.Body1(c.Theme, "New Conversation in:").Layout(gtx)
								} else {
									material.Body1(c.Theme, "Replying to:").Layout(gtx)
									return material.Body1(c.Theme, "New Conversation in:").Layout(gtx)
								}
								return material.Body1(c.Theme, "Replying to:").Layout(gtx)

							})
						}),
						layout.Flexed(1, func() {
							layout.UniformInset(unit.Dp(6)).Layout(gtx, func() {
						layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
							return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
								if c.CreatingConversation {
									var dims layout.Dimensions
									c.ArborState.CommunityList.WithCommunities(func(comms []*forest.Community) {
										c.CommunitList.Axis = layout.Vertical
										c.CommunitList.Layout(gtx, len(comms), func(index int) {
										dims = c.CommunitList.Layout(gtx, len(comms), func(gtx layout.Context, index int) layout.Dimensions {
											community := comms[index]
											material.RadioButton(c.Theme, community.ID().String(), string(community.Name.Blob)).Layout(gtx, &c.CommunityChoice)
											return material.RadioButton(c.Theme, &c.CommunityChoice, community.ID().String(), string(community.Name.Blob)).Layout(gtx)
										})
									})
								} else {
									sprigTheme.Reply(c.Theme).Layout(gtx, c.ReplyingTo, c.ReplyingToAuthor)
									return dims
								}
								return sprigTheme.Reply(c.Theme).Layout(gtx, c.ReplyingTo, c.ReplyingToAuthor)
							})
						}),
					)
				}),
				layout.Rigid(func() {
					layout.Flex{}.Layout(gtx,
						layout.Flexed(1, func() {
							layout.UniformInset(unit.Dp(6)).Layout(gtx, func() {
								layout.Stack{}.Layout(gtx,
									layout.Expanded(func() {
				layout.Rigid(func(gtx layout.Context) layout.Dimensions {
					return layout.Flex{}.Layout(gtx,
						layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
							return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
								return layout.Stack{}.Layout(gtx,
									layout.Expanded(func(gtx layout.Context) layout.Dimensions {
										var stack op.StackOp
										stack.Push(gtx.Ops)
										paintOp := paint.ColorOp{Color: white}
										paintOp.Add(gtx.Ops)
										bounds := f32.Rectangle{
											Max: f32.Point{
												X: float32(gtx.Constraints.Width.Max),
												Y: float32(gtx.Constraints.Height.Min),
												X: float32(gtx.Constraints.Max.X),
												Y: float32(gtx.Constraints.Min.Y),
											},
										}
										radii := float32(gtx.Px(unit.Dp(5)))


@@ 461,21 468,21 @@ func (c *ReplyListView) layoutEditor(gtx *layout.Context) {
										}.Op(gtx.Ops).Add(gtx.Ops)
										paint.PaintOp{Rect: bounds}.Add(gtx.Ops)
										stack.Pop()
										return layout.Dimensions{Size: image.Point{X: gtx.Constraints.Max.X, Y: gtx.Constraints.Min.Y}}
									}),
									layout.Stacked(func() {
										layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
											material.Editor(c.Theme, "type your reply here").Layout(gtx, &c.ReplyEditor)
										})
									layout.Stacked(func(gtx layout.Context) layout.Dimensions {
										return layout.UniformInset(unit.Dp(4)).Layout(gtx,
											material.Editor(c.Theme, &c.ReplyEditor, "type your reply here").Layout,
										)
									}),
								)
							})
						}),
						layout.Rigid(func() {
							layout.UniformInset(unit.Dp(6)).Layout(gtx, func() {
								sendButton := material.IconButton(c.Theme, icons.SendReplyIcon)
						layout.Rigid(func(gtx layout.Context) layout.Dimensions {
							return layout.UniformInset(unit.Dp(6)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
								sendButton := material.IconButton(c.Theme, &c.SendReplyButton, icons.SendReplyIcon)
								sendButton.Size = unit.Dp(40)
								sendButton.Padding = unit.Dp(10)
								sendButton.Layout(gtx, &c.SendReplyButton)
								return sendButton.Layout(gtx)
							})
						}),
					)

M view-manager.go => view-manager.go +3 -3
@@ 5,7 5,7 @@ import "gioui.org/layout"
type ViewManager interface {
	RequestViewSwitch(ViewID)
	RegisterView(ViewID, View)
	Layout(gtx *layout.Context)
	Layout(gtx layout.Context) layout.Dimensions
}

type viewManager struct {


@@ 29,7 29,7 @@ func (vm *viewManager) RequestViewSwitch(id ViewID) {
	vm.current = id
}

func (vm *viewManager) Layout(gtx *layout.Context) {
func (vm *viewManager) Layout(gtx layout.Context) layout.Dimensions {
	vm.views[vm.current].Update(gtx)
	vm.views[vm.current].Layout(gtx)
	return vm.views[vm.current].Layout(gtx)
}

M view.go => view.go +2 -2
@@ 4,6 4,6 @@ import "gioui.org/layout"

type View interface {
	SetManager(ViewManager)
	Update(gtx *layout.Context)
	Layout(gtx *layout.Context)
	Update(gtx layout.Context)
	Layout(gtx layout.Context) layout.Dimensions
}

A widget/composer.go => widget/composer.go +27 -0
@@ 0,0 1,27 @@
package widget

import (
	"gioui.org/layout"
	"gioui.org/widget"
	"git.sr.ht/~whereswaldon/forest-go"
)

type ComposerMode uint

const (
	CreatingConversation ComposerMode = iota
	CreatingReply
)

type Composer struct {
	SendButton, CancelButton, CopyButton, PasteButton widget.Clickable
	widget.Editor

	Mode ComposerMode

	Communities layout.List
	Community   widget.Enum

	ReplyingTo       *forest.Reply
	ReplyingToAuthor *forest.Identity
}

M widget/theme/reply.go => widget/theme/reply.go +44 -34
@@ 1,6 1,7 @@
package theme

import (
	"image"
	"image/color"

	"gioui.org/f32"


@@ 34,23 35,24 @@ func Reply(th *material.Theme) ReplyStyle {
	}
}

func (r ReplyStyle) Layout(gtx *layout.Context, reply *forest.Reply, author *forest.Identity) {
func (r ReplyStyle) Layout(gtx layout.Context, reply *forest.Reply, author *forest.Identity) layout.Dimensions {
	// higher-level state to track the height of the dynamic content. This
	// is set by the Stacked layout function, but used by the Expanded one.
	// It's counterintuitive, but it works because the stacked child is
	// evaluated first by the layout.
	var height float32
	layout.Stack{}.Layout(gtx,
		layout.Expanded(func() {
	return layout.Stack{}.Layout(gtx,
		layout.Expanded(func(gtx layout.Context) layout.Dimensions {
			var stack op.StackOp
			stack.Push(gtx.Ops)
			paintOp := paint.ColorOp{Color: r.Background}
			paintOp.Add(gtx.Ops)
			max := f32.Point{
				X: float32(gtx.Constraints.Max.X),
				Y: float32(height),
			}
			bounds := f32.Rectangle{
				Max: f32.Point{
					X: float32(gtx.Constraints.Width.Max),
					Y: float32(height),
				},
				Max: max,
			}
			radii := float32(gtx.Px(unit.Dp(5)))
			clip.Rect{


@@ 64,57 66,65 @@ func (r ReplyStyle) Layout(gtx *layout.Context, reply *forest.Reply, author *for
				Rect: bounds,
			}.Add(gtx.Ops)
			stack.Pop()
			return layout.Dimensions{Size: image.Pt(int(max.X), int(max.Y))}
		}),
		layout.Stacked(func() {
			layout.UniformInset(unit.Dp(4)).Layout(gtx, func() {
		layout.Stacked(func(gtx layout.Context) layout.Dimensions {
			dim := layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
				if !r.CollapseMetadata {
					layout.Flex{Axis: layout.Vertical}.Layout(gtx,
						layout.Rigid(func() {
							gtx.Constraints.Width.Min = gtx.Constraints.Width.Max
							layout.NW.Layout(gtx, func() {
								r.layoutAuthor(gtx, author)
					return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
						layout.Rigid(func(gtx layout.Context) layout.Dimensions {
							var dim layout.Dimensions
							gtx.Constraints.Min.X = gtx.Constraints.Max.X
							dim.Size.X = gtx.Constraints.Max.X
							textDim := layout.NW.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
								return r.layoutAuthor(gtx, author)
							})
							layout.NE.Layout(gtx, func() {
								r.layoutDate(gtx, reply)
							dim.Size.Y = textDim.Size.Y
							textDim = layout.NE.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
								return r.layoutDate(gtx, reply)
							})
							if textDim.Size.Y > dim.Size.Y {
								dim.Size.Y = textDim.Size.Y
							}
							return dim
						}),
						layout.Rigid(func() {
							r.layoutContent(gtx, reply)
						}),
					)
				} else {
					layout.Flex{Spacing: layout.SpaceBetween}.Layout(gtx,
						layout.Flexed(1, func() {
							r.layoutContent(gtx, reply)
						}),
						layout.Rigid(func() {
							r.layoutDate(gtx, reply)
						layout.Rigid(func(gtx layout.Context) layout.Dimensions {
							return r.layoutContent(gtx, reply)
						}),
					)
				}
				return layout.Flex{Spacing: layout.SpaceBetween}.Layout(gtx,
					layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
						return r.layoutContent(gtx, reply)
					}),
					layout.Rigid(func(gtx layout.Context) layout.Dimensions {
						return r.layoutDate(gtx, reply)
					}),
				)
			})
			height = float32(gtx.Dimensions.Size.Y)
			height = float32(dim.Size.Y)
			return dim
		}),
	)
}

func (r ReplyStyle) layoutAuthor(gtx *layout.Context, author *forest.Identity) {
func (r ReplyStyle) layoutAuthor(gtx layout.Context, author *forest.Identity) layout.Dimensions {
	name := material.Body2(r.Theme, string(author.Name.Blob))
	name.Font.Weight = text.Bold
	name.Color = r.TextColor
	name.Layout(gtx)
	return name.Layout(gtx)
}

func (r ReplyStyle) layoutDate(gtx *layout.Context, reply *forest.Reply) {
func (r ReplyStyle) layoutDate(gtx layout.Context, reply *forest.Reply) layout.Dimensions {
	date := material.Body2(r.Theme, reply.Created.Time().Local().Format("2006/01/02 15:04"))
	date.Color = r.TextColor
	date.Color.A = 200
	date.TextSize = unit.Dp(12)
	date.Layout(gtx)
	return date.Layout(gtx)
}

func (r ReplyStyle) layoutContent(gtx *layout.Context, reply *forest.Reply) {
func (r ReplyStyle) layoutContent(gtx layout.Context, reply *forest.Reply) layout.Dimensions {
	content := material.Body1(r.Theme, string(reply.Content.Blob))
	content.Color = r.TextColor
	content.Layout(gtx)
	return content.Layout(gtx)
}