From c36baf973e18e174a4ebe926754bf5f365c65d8a Mon Sep 17 00:00:00 2001 From: Chris Waldon Date: Fri, 11 Dec 2020 21:23:34 -0500 Subject: [PATCH] gio-extras/materials: add materials example Signed-off-by: Chris Waldon --- gio-extras/materials/main.go | 642 +++++++++++++++++++++++++++++++++++ go.mod | 7 +- go.sum | 13 +- 3 files changed, 657 insertions(+), 5 deletions(-) create mode 100644 gio-extras/materials/main.go diff --git a/gio-extras/materials/main.go b/gio-extras/materials/main.go new file mode 100644 index 0000000..b342925 --- /dev/null +++ b/gio-extras/materials/main.go @@ -0,0 +1,642 @@ +package main + +import ( + "flag" + "image/color" + "log" + "os" + "time" + "unicode" + + "gioui.org/app" + "gioui.org/font/gofont" + "gioui.org/io/system" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/unit" + "gioui.org/widget" + "gioui.org/widget/material" + "golang.org/x/exp/shiny/materialdesign/icons" + + "git.sr.ht/~whereswaldon/materials" +) + +type ( + C = layout.Context + D = layout.Dimensions +) + +var MenuIcon *widget.Icon = func() *widget.Icon { + icon, _ := widget.NewIcon(icons.NavigationMenu) + return icon +}() + +var HomeIcon *widget.Icon = func() *widget.Icon { + icon, _ := widget.NewIcon(icons.ActionHome) + return icon +}() + +var SettingsIcon *widget.Icon = func() *widget.Icon { + icon, _ := widget.NewIcon(icons.ActionSettings) + return icon +}() + +var OtherIcon *widget.Icon = func() *widget.Icon { + icon, _ := widget.NewIcon(icons.ActionHelp) + return icon +}() + +var HeartIcon *widget.Icon = func() *widget.Icon { + icon, _ := widget.NewIcon(icons.ActionFavorite) + return icon +}() + +var PlusIcon *widget.Icon = func() *widget.Icon { + icon, _ := widget.NewIcon(icons.ContentAdd) + return icon +}() + +var EditIcon *widget.Icon = func() *widget.Icon { + icon, _ := widget.NewIcon(icons.ContentCreate) + return icon +}() + +var barOnBottom bool + +func main() { + flag.BoolVar(&barOnBottom, "bottom-bar", false, "place the app bar on the bottom of the screen instead of the top") + flag.Parse() + go func() { + w := app.NewWindow() + if err := loop(w); err != nil { + log.Fatal(err) + } + os.Exit(0) + }() + app.Main() +} + +const ( + settingNameColumnWidth = .3 + settingDetailsColumnWidth = 1 - settingNameColumnWidth +) + +func LayoutAppBarPage(gtx C) D { + return layout.Flex{ + Alignment: layout.Middle, + Axis: layout.Vertical, + }.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return inset.Layout(gtx, material.Body1(th, `The app bar widget provides a consistent interface element for triggering navigation and page-specific actions. + +The controls below allow you to see the various features available in our App Bar implementation.`).Layout) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Alignment: layout.Baseline}.Layout(gtx, + layout.Flexed(settingNameColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body1(th, "Contextual App Bar").Layout) + }), + layout.Flexed(settingDetailsColumnWidth, func(gtx C) D { + if contextBtn.Clicked() { + bar.SetContextualActions( + []materials.AppBarAction{ + materials.SimpleIconAction(th, &red, HeartIcon, + materials.OverflowAction{ + Name: "House", + Tag: &red, + }, + ), + }, + []materials.OverflowAction{ + { + Name: "foo", + Tag: &blue, + }, + { + Name: "bar", + Tag: &green, + }, + }, + ) + bar.ToggleContextual(gtx.Now, "Contextual Title") + } + return material.Button(th, &contextBtn, "Trigger").Layout(gtx) + }), + ) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, + layout.Flexed(settingNameColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body1(th, "Bottom App Bar").Layout) + }), + layout.Flexed(settingDetailsColumnWidth, func(gtx C) D { + if bottomBar.Changed() { + if bottomBar.Value { + nav.Anchor = materials.Bottom + } else { + nav.Anchor = materials.Top + } + } + + return inset.Layout(gtx, material.Switch(th, &bottomBar).Layout) + }), + ) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, + layout.Flexed(settingNameColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body1(th, "Custom Navigation Icon").Layout) + }), + layout.Flexed(settingDetailsColumnWidth, func(gtx C) D { + if customNavIcon.Changed() { + if customNavIcon.Value { + bar.NavigationIcon = HomeIcon + } else { + bar.NavigationIcon = MenuIcon + } + } + return inset.Layout(gtx, material.Switch(th, &customNavIcon).Layout) + }), + ) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Alignment: layout.Baseline}.Layout(gtx, + layout.Flexed(settingNameColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body1(th, "Animated Resize").Layout) + }), + layout.Flexed(settingDetailsColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body2(th, "Resize the width of your screen to see app bar actions collapse into or emerge from the overflow menu (as size permits).").Layout) + }), + ) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Alignment: layout.Baseline}.Layout(gtx, + layout.Flexed(settingNameColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body1(th, "Custom Action Buttons").Layout) + }), + layout.Flexed(settingDetailsColumnWidth, func(gtx C) D { + if heartBtn.Clicked() { + favorited = !favorited + } + return inset.Layout(gtx, material.Body2(th, "Click the heart action to see custom button behavior.").Layout) + }), + ) + }), + ) +} + +func LayoutNavDrawerPage(gtx C) D { + return layout.Flex{ + Alignment: layout.Middle, + Axis: layout.Vertical, + }.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return inset.Layout(gtx, material.Body1(th, `The nav drawer widget provides a consistent interface element for navigation. + +The controls below allow you to see the various features available in our Navigation Drawer implementation.`).Layout) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, + layout.Flexed(settingNameColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body1(th, "Use non-modal drawer").Layout) + }), + layout.Flexed(settingDetailsColumnWidth, func(gtx C) D { + if nonModalDrawer.Changed() { + if nonModalDrawer.Value { + navAnim.Appear(gtx.Now) + } else { + navAnim.Disappear(gtx.Now) + } + } + return inset.Layout(gtx, material.Switch(th, &nonModalDrawer).Layout) + }), + ) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Alignment: layout.Baseline}.Layout(gtx, + layout.Flexed(settingNameColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body1(th, "Drag to Close").Layout) + }), + layout.Flexed(settingDetailsColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body2(th, "You can close the modal nav drawer by dragging it to the left.").Layout) + }), + ) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Alignment: layout.Baseline}.Layout(gtx, + layout.Flexed(settingNameColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body1(th, "Touch Scrim to Close").Layout) + }), + layout.Flexed(settingDetailsColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body2(th, "You can close the modal nav drawer touching anywhere in the translucent scrim to the right.").Layout) + }), + ) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Alignment: layout.Baseline}.Layout(gtx, + layout.Flexed(settingNameColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body1(th, "Bottom content anchoring").Layout) + }), + layout.Flexed(settingDetailsColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body2(th, "If you toggle support for the bottom app bar in the App Bar settings, nav drawer content will anchor to the bottom of the drawer area instead of the top.").Layout) + }), + ) + }), + ) +} + +const ( + sponsorEliasURL = "https://github.com/sponsors/eliasnaur" + sponsorChrisURLGitHub = "https://github.com/sponsors/whereswaldon" + sponsorChrisURLLiberapay = "https://liberapay.com/whereswaldon/" +) + +func LayoutAboutPage(gtx C) D { + return layout.Flex{ + Alignment: layout.Middle, + Axis: layout.Vertical, + }.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return inset.Layout(gtx, material.Body1(th, `This library implements material design components from https://material.io using https://gioui.org. + +Materials (this library) would not be possible without the incredible work of Elias Naur and the Gio community. Materials is maintained by Chris Waldon. + + +If you like this library and work like it, please consider sponsoring Elias and/or Chris!`).Layout) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, + layout.Flexed(settingDetailsColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body1(th, "Elias Naur can be sponsored on GitHub at "+sponsorEliasURL).Layout) + }), + layout.Flexed(settingNameColumnWidth, func(gtx C) D { + if eliasCopyButton.Clicked() { + clipboardRequests <- sponsorEliasURL + } + return inset.Layout(gtx, material.Button(th, &eliasCopyButton, "Copy Sponsorship URL").Layout) + }), + ) + }), + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return layout.Flex{Alignment: layout.Middle}.Layout(gtx, + layout.Flexed(settingDetailsColumnWidth, func(gtx C) D { + return inset.Layout(gtx, material.Body1(th, "Chris Waldon can be sponsored on GitHub at "+sponsorChrisURLGitHub+" and on Liberapay at "+sponsorChrisURLLiberapay).Layout) + }), + layout.Flexed(settingNameColumnWidth, func(gtx C) D { + if chrisCopyButtonGH.Clicked() { + clipboardRequests <- sponsorChrisURLGitHub + } + if chrisCopyButtonLP.Clicked() { + clipboardRequests <- sponsorChrisURLLiberapay + } + return inset.Layout(gtx, func(gtx C) D { + return layout.Flex{}.Layout(gtx, + layout.Flexed(.5, material.Button(th, &chrisCopyButtonGH, "Copy GitHub URL").Layout), + layout.Flexed(.5, material.Button(th, &chrisCopyButtonLP, "Copy Liberapay URL").Layout), + ) + }) + }), + ) + }), + ) +} + +func LayoutTextFieldPage(gtx C) D { + return layout.Flex{ + Axis: layout.Vertical, + }.Layout( + gtx, + layout.Rigid(func(gtx C) D { + nameInput.Alignment = inputAlignment + return nameInput.Layout(gtx, th, "Name") + }), + layout.Rigid(func(gtx C) D { + return inset.Layout(gtx, material.Body2(th, "Responds to hover events.").Layout) + }), + layout.Rigid(func(gtx C) D { + addressInput.Alignment = inputAlignment + return addressInput.Layout(gtx, th, "Address") + }), + layout.Rigid(func(gtx C) D { + return inset.Layout(gtx, material.Body2(th, "Label animates properly when you click to select the text field.").Layout) + }), + layout.Rigid(func(gtx C) D { + priceInput.Prefix = func(gtx C) D { + th := *th + th.Palette.Fg = color.NRGBA{R: 100, G: 100, B: 100, A: 255} + return material.Label(&th, th.TextSize, "$").Layout(gtx) + } + priceInput.Suffix = func(gtx C) D { + th := *th + th.Palette.Fg = color.NRGBA{R: 100, G: 100, B: 100, A: 255} + return material.Label(&th, th.TextSize, ".00").Layout(gtx) + } + priceInput.SingleLine = true + priceInput.Alignment = inputAlignment + return priceInput.Layout(gtx, th, "Price") + }), + layout.Rigid(func(gtx C) D { + return inset.Layout(gtx, material.Body2(th, "Can have prefix and suffix elements.").Layout) + }), + layout.Rigid(func(gtx C) D { + if err := func() string { + for _, r := range numberInput.Text() { + if !unicode.IsDigit(r) { + return "Must contain only digits" + } + } + return "" + }(); err != "" { + numberInput.SetError(err) + } else { + numberInput.ClearError() + } + numberInput.SingleLine = true + numberInput.Alignment = inputAlignment + return numberInput.Layout(gtx, th, "Number") + }), + layout.Rigid(func(gtx C) D { + return inset.Layout(gtx, material.Body2(th, "Can be validated.").Layout) + }), + layout.Rigid(func(gtx C) D { + if tweetInput.TextTooLong() { + tweetInput.SetError("Too many characters") + } else { + tweetInput.ClearError() + } + tweetInput.CharLimit = 128 + tweetInput.Helper = "Tweets have a limited character count" + tweetInput.Alignment = inputAlignment + return tweetInput.Layout(gtx, th, "Tweet") + }), + layout.Rigid(func(gtx C) D { + return inset.Layout(gtx, material.Body2(th, "Can have a character counter and help text.").Layout) + }), + layout.Rigid(func(gtx C) D { + if inputAlignmentEnum.Changed() { + switch inputAlignmentEnum.Value { + case layout.Start.String(): + inputAlignment = layout.Start + case layout.Middle.String(): + inputAlignment = layout.Middle + case layout.End.String(): + inputAlignment = layout.End + default: + inputAlignment = layout.Start + } + op.InvalidateOp{}.Add(gtx.Ops) + } + return inset.Layout( + gtx, + func(gtx C) D { + return layout.Flex{ + Axis: layout.Vertical, + }.Layout( + gtx, + layout.Rigid(func(gtx C) D { + return material.Body2(th, "Text Alignment").Layout(gtx) + }), + layout.Rigid(func(gtx C) D { + return layout.Flex{ + Axis: layout.Vertical, + }.Layout( + gtx, + layout.Rigid(func(gtx C) D { + return material.RadioButton( + th, + &inputAlignmentEnum, + layout.Start.String(), + "Start", + ).Layout(gtx) + }), + layout.Rigid(func(gtx C) D { + return material.RadioButton( + th, + &inputAlignmentEnum, + layout.Middle.String(), + "Middle", + ).Layout(gtx) + }), + layout.Rigid(func(gtx C) D { + return material.RadioButton( + th, + &inputAlignmentEnum, + layout.End.String(), + "End", + ).Layout(gtx) + }), + ) + }), + ) + }, + ) + }), + layout.Rigid(func(gtx C) D { + return inset.Layout(gtx, material.Body2(th, "This text field implementation was contributed by Jack Mordaunt. Thanks Jack!").Layout) + }), + ) +} + +type Page struct { + layout func(layout.Context) layout.Dimensions + materials.NavItem + Actions []materials.AppBarAction + Overflow []materials.OverflowAction + + // laying each page out within a layout.List enables scrolling for the page + // content. + layout.List +} + +var ( + // initialize channel to send clipboard content requests on + clipboardRequests = make(chan string, 1) + + // initialize modal layer to draw modal components + modal = materials.NewModal() + navAnim = materials.VisibilityAnimation{ + Duration: time.Millisecond * 100, + State: materials.Invisible, + } + nav = materials.NewNav(th, "Navigation Drawer", "This is an example.") + modalNav = materials.ModalNavFrom(&nav, modal) + + bar = materials.NewAppBar(th, modal) + + inset = layout.UniformInset(unit.Dp(8)) + th = material.NewTheme(gofont.Collection()) + + heartBtn, plusBtn, exampleOverflowState widget.Clickable + red, green, blue widget.Clickable + contextBtn widget.Clickable + eliasCopyButton, chrisCopyButtonGH, chrisCopyButtonLP widget.Clickable + bottomBar widget.Bool + customNavIcon widget.Bool + nonModalDrawer widget.Bool + favorited bool + inputAlignment layout.Alignment + inputAlignmentEnum widget.Enum + nameInput materials.TextField + addressInput materials.TextField + priceInput materials.TextField + tweetInput materials.TextField + numberInput materials.TextField + + pages = []Page{ + { + NavItem: materials.NavItem{ + Name: "App Bar Features", + Icon: HomeIcon, + }, + layout: LayoutAppBarPage, + Actions: []materials.AppBarAction{ + { + OverflowAction: materials.OverflowAction{ + Name: "Favorite", + Tag: &heartBtn, + }, + Layout: func(gtx layout.Context, bg, fg color.NRGBA) layout.Dimensions { + btn := materials.SimpleIconButton(th, &heartBtn, HeartIcon) + btn.Background = bg + if favorited { + btn.Color = color.NRGBA{R: 200, A: 255} + } else { + btn.Color = fg + } + return btn.Layout(gtx) + }, + }, + materials.SimpleIconAction(th, &plusBtn, PlusIcon, + materials.OverflowAction{ + Name: "Create", + Tag: &plusBtn, + }, + ), + }, + Overflow: []materials.OverflowAction{ + { + Name: "Example 1", + Tag: &exampleOverflowState, + }, + { + Name: "Example 2", + Tag: &exampleOverflowState, + }, + }, + }, + { + NavItem: materials.NavItem{ + Name: "Nav Drawer Features", + Icon: SettingsIcon, + }, + layout: LayoutNavDrawerPage, + }, + { + NavItem: materials.NavItem{ + Name: "Text Field Features", + Icon: EditIcon, + }, + layout: LayoutTextFieldPage, + }, + { + NavItem: materials.NavItem{ + Name: "About this library", + Icon: OtherIcon, + }, + layout: LayoutAboutPage, + Actions: []materials.AppBarAction{}, + }, + } +) + +func loop(w *app.Window) error { + var ops op.Ops + + bar.NavigationIcon = MenuIcon + if barOnBottom { + bar.Anchor = materials.Bottom + nav.Anchor = materials.Bottom + } + + // assign navigation tags and configure navigation bar with all pages + for i := range pages { + page := &pages[i] + page.List.Axis = layout.Vertical + page.NavItem.Tag = i + nav.AddNavItem(page.NavItem) + } + + // configure app bar initial state + page := pages[nav.CurrentNavDestination().(int)] + bar.Title = page.Name + bar.SetActions(page.Actions, page.Overflow) + + for { + select { + case content := <-clipboardRequests: + w.WriteClipboard(content) + case e := <-w.Events(): + switch e := e.(type) { + case system.DestroyEvent: + return e.Err + case system.FrameEvent: + gtx := layout.NewContext(&ops, e) + for _, event := range bar.Events(gtx) { + switch event := event.(type) { + case materials.AppBarNavigationClicked: + if nonModalDrawer.Value { + navAnim.ToggleVisibility(gtx.Now) + } else { + modalNav.Appear(gtx.Now) + navAnim.Disappear(gtx.Now) + } + case materials.AppBarContextMenuDismissed: + log.Printf("Context menu dismissed: %v", event) + case materials.AppBarOverflowActionClicked: + log.Printf("Overflow action selected: %v", event) + } + } + if nav.NavDestinationChanged() { + page := pages[nav.CurrentNavDestination().(int)] + bar.Title = page.Name + bar.SetActions(page.Actions, page.Overflow) + } + layout.Inset{ + Top: e.Insets.Top, + Bottom: e.Insets.Bottom, + Left: e.Insets.Left, + Right: e.Insets.Right, + }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { + content := layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { + return layout.Flex{}.Layout(gtx, + layout.Rigid(func(gtx layout.Context) layout.Dimensions { + gtx.Constraints.Max.X /= 3 + return nav.Layout(gtx, &navAnim) + }), + layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { + page := &pages[nav.CurrentNavDestination().(int)] + return page.List.Layout(gtx, 1, func(gtx C, _ int) D { + return layout.UniformInset(unit.Dp(4)).Layout(gtx, func(gtx layout.Context) layout.Dimensions { + return page.layout(gtx) + }) + }) + }), + ) + }) + bar := layout.Rigid(func(gtx layout.Context) layout.Dimensions { + return bar.Layout(gtx) + }) + flex := layout.Flex{Axis: layout.Vertical} + if bottomBar.Value { + flex.Layout(gtx, content, bar) + } else { + flex.Layout(gtx, bar, content) + } + modal.Layout(gtx) + return layout.Dimensions{Size: gtx.Constraints.Max} + }) + e.Frame(gtx.Ops) + } + } + } +} diff --git a/go.mod b/go.mod index 1d4c37a..0ba55de 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,17 @@ module gioui.org/example go 1.13 require ( - gioui.org v0.0.0-20201211172859-bd7bb4d5d2f3 + gioui.org v0.0.0-20201211192434-745bb949bb45 git.sr.ht/~whereswaldon/colorpicker v0.0.0-20201207220634-905cd7cc7248 git.sr.ht/~whereswaldon/haptic v0.0.0-20201207220958-78675dee81dd + git.sr.ht/~whereswaldon/materials v0.0.0-20201212021906-748774a2ad9b git.sr.ht/~whereswaldon/niotify v0.0.3 git.sr.ht/~whereswaldon/outlay v0.0.0-20201207220906-cbe824700857 git.sr.ht/~whereswaldon/scroll v0.0.0-20201208022259-cc815a044b0b github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 github.com/google/go-github/v24 v24.0.1 - golang.org/x/exp v0.0.0-20201203231725-fa01524bc59d - golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 + golang.org/x/exp v0.0.0-20201210212021-a20c86df00b4 + golang.org/x/image v0.0.0-20201208152932-35266b937fa6 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 ) diff --git a/go.sum b/go.sum index b74b266..47a233f 100644 --- a/go.sum +++ b/go.sum @@ -2,12 +2,14 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20200619180744-e2f3bbdfc367/go.mod h1:jiUwifN9cRl/zmco43aAqh0aV+s9GbhG13KcD+gEpkU= gioui.org v0.0.0-20201206220452-acc3f704e478/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B2LGoT8JtFgvmYmRzTw= -gioui.org v0.0.0-20201211172859-bd7bb4d5d2f3 h1:MeGkzCHegIlIebxPbC+I1xwNQ7vjTYPxZQ6lzrJydSg= -gioui.org v0.0.0-20201211172859-bd7bb4d5d2f3/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B2LGoT8JtFgvmYmRzTw= +gioui.org v0.0.0-20201211192434-745bb949bb45 h1:4g9VJg+Rt/lYUuPQMcSXsOM8KTU3/twXLS1nHtvP0XA= +gioui.org v0.0.0-20201211192434-745bb949bb45/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B2LGoT8JtFgvmYmRzTw= git.sr.ht/~whereswaldon/colorpicker v0.0.0-20201207220634-905cd7cc7248 h1:sI70yHfHsuzyAL72ikrsOecjRT3dgQw9rCCbFd/goDA= git.sr.ht/~whereswaldon/colorpicker v0.0.0-20201207220634-905cd7cc7248/go.mod h1:6dPWP8F87bsIhQuwg0l5hH0TSDyk414e1xe3q+8BUho= git.sr.ht/~whereswaldon/haptic v0.0.0-20201207220958-78675dee81dd h1:xTijdESZL/kM3nS7v/N1yJ/X8eInbsyqDLOz9ZFHpsE= git.sr.ht/~whereswaldon/haptic v0.0.0-20201207220958-78675dee81dd/go.mod h1:lFvegCF1P7IXfv5FpnnvKFdoAQWTgJZhx8aWOBgE0yg= +git.sr.ht/~whereswaldon/materials v0.0.0-20201212021906-748774a2ad9b h1:XDu43OWpqnqDH/IrnU7kmxHzpLZp5bMu2aGptPlr+Nw= +git.sr.ht/~whereswaldon/materials v0.0.0-20201212021906-748774a2ad9b/go.mod h1:T+qQ+uWh7paSXI7QzQViqUPnxd+axqH6Wn9JG5sxiWY= git.sr.ht/~whereswaldon/niotify v0.0.3 h1:EWRqPOzqTLU92A9h207LkS/U/nQxuawJ0PF7UEDApi0= git.sr.ht/~whereswaldon/niotify v0.0.3/go.mod h1:itJ9vAQqq8+liURizx7mAdIY4o8gRDF6SAVfswYVg1U= git.sr.ht/~whereswaldon/outlay v0.0.0-20201207220906-cbe824700857 h1:Sc+1cZRrwGyiBYgqIto5OlK+RTea1T7FYmZj+JC6RZI= @@ -45,11 +47,15 @@ golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgn golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw= golang.org/x/exp v0.0.0-20201203231725-fa01524bc59d h1:FscZqdyN/qhN9in1p2FLXl6vsrWY792O5bak6GHqVs0= golang.org/x/exp v0.0.0-20201203231725-fa01524bc59d/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw= +golang.org/x/exp v0.0.0-20201210212021-a20c86df00b4 h1:c0f5UxfZlsfpBf1TXaGk9aMJGMWvPQBf9FdM8pAYZok= +golang.org/x/exp v0.0.0-20201210212021-a20c86df00b4/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM= golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -74,12 +80,15 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w 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/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88 h1:KmZPnMocC93w341XZp26yTJg8Za7lhb2KhkYmixoeso= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -- 2.30.2