forked from joejulian/gio
theme/material: add the material theme
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -3,6 +3,7 @@ module gioui.org
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9
|
||||
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
|
||||
)
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9 h1:uc17S921SPw5F2gJo7slQ3aqvr2RwpL7eb3+DZncu3s=
|
||||
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
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/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 h1:n9HxLrNxWWtEb1cA950nuEEj3QnKbtsCJ6KjcgisNUs=
|
||||
golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/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/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package widget
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/gesture"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
)
|
||||
|
||||
type Button struct {
|
||||
click gesture.Click
|
||||
clicks int
|
||||
history []Click
|
||||
}
|
||||
|
||||
// Click represents a historic click.
|
||||
type Click struct {
|
||||
Position f32.Point
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
func (b *Button) Clicked(gtx *layout.Context) bool {
|
||||
for _, e := range b.click.Events(gtx) {
|
||||
switch e.Type {
|
||||
case gesture.TypeClick:
|
||||
b.clicks++
|
||||
case gesture.TypePress:
|
||||
b.history = append(b.history, Click{
|
||||
Position: e.Position,
|
||||
Time: gtx.Now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
if b.clicks > 0 {
|
||||
b.clicks--
|
||||
if b.clicks > 0 {
|
||||
// Ensure timely delivery of remaining clicks.
|
||||
op.InvalidateOp{}.Add(gtx.Ops)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *Button) Active() bool {
|
||||
return b.click.Active()
|
||||
}
|
||||
|
||||
func (b *Button) History() []Click {
|
||||
return b.history
|
||||
}
|
||||
|
||||
func (b *Button) Layout(gtx *layout.Context) {
|
||||
b.click.Add(gtx.Ops)
|
||||
if !b.Active() {
|
||||
b.clicks = 0
|
||||
}
|
||||
for len(b.history) > 0 {
|
||||
c := b.history[0]
|
||||
if gtx.Now().Sub(c.Time) < 1*time.Second {
|
||||
break
|
||||
}
|
||||
copy(b.history, b.history[1:])
|
||||
b.history = b.history[:len(b.history)-1]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package widget implements common user interface controls. Widgets
|
||||
// contain peristent state and process user events. Theme packages
|
||||
// such as `widget/material` implements drawing of widgets.
|
||||
package widget
|
||||
@@ -0,0 +1,209 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package material implements the Material design.
|
||||
package material
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/io/pointer"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/paint"
|
||||
"gioui.org/text"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"golang.org/x/exp/shiny/iconvg"
|
||||
)
|
||||
|
||||
type Button struct {
|
||||
Text string
|
||||
// Color is the text color.
|
||||
Color color.RGBA
|
||||
Font text.Font
|
||||
Background color.RGBA
|
||||
|
||||
shaper *text.Shaper
|
||||
}
|
||||
|
||||
type IconButton struct {
|
||||
Background color.RGBA
|
||||
Icon *Icon
|
||||
Size unit.Value
|
||||
Padding unit.Value
|
||||
}
|
||||
|
||||
type Icon struct {
|
||||
src []byte
|
||||
size unit.Value
|
||||
|
||||
// Cached values.
|
||||
img image.Image
|
||||
imgSize int
|
||||
}
|
||||
|
||||
func (t *Theme) Button(txt string) Button {
|
||||
return Button{
|
||||
Text: txt,
|
||||
Color: rgb(0xffffff),
|
||||
Background: t.Color.Primary,
|
||||
Font: text.Font{
|
||||
Size: t.TextSize.Scale(14.0 / 16.0),
|
||||
},
|
||||
shaper: t.Shaper,
|
||||
}
|
||||
}
|
||||
|
||||
// NewIcon returns a new Icon from IconVG data.
|
||||
func NewIcon(data []byte) (*Icon, error) {
|
||||
_, err := iconvg.DecodeMetadata(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Icon{src: data}, nil
|
||||
}
|
||||
|
||||
func (t *Theme) IconButton(icon *Icon) IconButton {
|
||||
return IconButton{
|
||||
Background: t.Color.Primary,
|
||||
Icon: icon,
|
||||
Size: unit.Dp(56),
|
||||
Padding: unit.Dp(20),
|
||||
}
|
||||
}
|
||||
|
||||
func (b Button) Layout(gtx *layout.Context, button *widget.Button) {
|
||||
col := b.Color
|
||||
bgcol := b.Background
|
||||
if !button.Active() {
|
||||
col.A = 0xaa
|
||||
bgcol.A = 0xaa
|
||||
}
|
||||
st := layout.Stack{}
|
||||
lbl := st.Rigid(gtx, func() {
|
||||
layout.UniformInset(unit.Dp(16)).Layout(gtx, func() {
|
||||
paint.ColorOp{Color: col}.Add(gtx.Ops)
|
||||
widget.Label{Alignment: text.Middle}.Layout(gtx, b.shaper, b.Font, b.Text)
|
||||
})
|
||||
pointer.RectAreaOp{Rect: image.Rectangle{Max: gtx.Dimensions.Size}}.Add(gtx.Ops)
|
||||
button.Layout(gtx)
|
||||
})
|
||||
bg := st.Expand(gtx, func() {
|
||||
rr := float32(gtx.Px(unit.Dp(4)))
|
||||
rrect(gtx.Ops,
|
||||
float32(gtx.Constraints.Width.Max),
|
||||
float32(gtx.Constraints.Height.Max),
|
||||
rr, rr, rr, rr,
|
||||
)
|
||||
fill(gtx, bgcol)
|
||||
for _, c := range button.History() {
|
||||
drawInk(gtx, c)
|
||||
}
|
||||
})
|
||||
st.Layout(gtx, bg, lbl)
|
||||
}
|
||||
|
||||
func (b IconButton) Layout(gtx *layout.Context, button *widget.Button) {
|
||||
st := layout.Stack{}
|
||||
ico := st.Rigid(gtx, func() {
|
||||
layout.UniformInset(b.Padding).Layout(gtx, func() {
|
||||
size := gtx.Px(b.Size) - gtx.Px(b.Padding)
|
||||
ico := b.Icon.image(size)
|
||||
paint.ImageOp{Src: ico, Rect: ico.Bounds()}.Add(gtx.Ops)
|
||||
paint.PaintOp{
|
||||
Rect: toRectF(ico.Bounds()),
|
||||
}.Add(gtx.Ops)
|
||||
gtx.Dimensions = layout.Dimensions{
|
||||
Size: image.Point{X: size, Y: size},
|
||||
}
|
||||
})
|
||||
pointer.EllipseAreaOp{Rect: image.Rectangle{Max: gtx.Dimensions.Size}}.Add(gtx.Ops)
|
||||
button.Layout(gtx)
|
||||
})
|
||||
bgcol := b.Background
|
||||
if !button.Active() {
|
||||
bgcol.A = 0xaa
|
||||
}
|
||||
bg := st.Expand(gtx, func() {
|
||||
size := float32(gtx.Constraints.Width.Max)
|
||||
rr := float32(size) * .5
|
||||
rrect(gtx.Ops,
|
||||
size,
|
||||
size,
|
||||
rr, rr, rr, rr,
|
||||
)
|
||||
fill(gtx, bgcol)
|
||||
for _, c := range button.History() {
|
||||
drawInk(gtx, c)
|
||||
}
|
||||
})
|
||||
st.Layout(gtx, bg, ico)
|
||||
}
|
||||
|
||||
func (ic *Icon) image(sz int) image.Image {
|
||||
if sz == ic.imgSize {
|
||||
return ic.img
|
||||
}
|
||||
m, _ := iconvg.DecodeMetadata(ic.src)
|
||||
dx, dy := m.ViewBox.AspectRatio()
|
||||
img := image.NewRGBA(image.Rectangle{Max: image.Point{X: sz, Y: int(float32(sz) * dy / dx)}})
|
||||
var ico iconvg.Rasterizer
|
||||
ico.SetDstImage(img, img.Bounds(), draw.Src)
|
||||
// Use white for icons.
|
||||
m.Palette[0] = color.RGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff}
|
||||
iconvg.Decode(&ico, ic.src, &iconvg.DecodeOptions{
|
||||
Palette: &m.Palette,
|
||||
})
|
||||
ic.img = img
|
||||
ic.imgSize = sz
|
||||
return img
|
||||
}
|
||||
|
||||
func fab(gtx *layout.Context, ico image.Image, col color.RGBA, size int) {
|
||||
dp := image.Point{X: (size - ico.Bounds().Dx()) / 2, Y: (size - ico.Bounds().Dy()) / 2}
|
||||
dims := image.Point{X: size, Y: size}
|
||||
rr := float32(size) * .5
|
||||
rrect(gtx.Ops, float32(size), float32(size), rr, rr, rr, rr)
|
||||
paint.ColorOp{Color: col}.Add(gtx.Ops)
|
||||
paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{X: float32(size), Y: float32(size)}}}.Add(gtx.Ops)
|
||||
paint.ImageOp{Src: ico, Rect: ico.Bounds()}.Add(gtx.Ops)
|
||||
paint.PaintOp{
|
||||
Rect: toRectF(ico.Bounds().Add(dp)),
|
||||
}.Add(gtx.Ops)
|
||||
gtx.Dimensions = layout.Dimensions{Size: dims}
|
||||
}
|
||||
|
||||
func toRectF(r image.Rectangle) f32.Rectangle {
|
||||
return f32.Rectangle{
|
||||
Min: f32.Point{X: float32(r.Min.X), Y: float32(r.Min.Y)},
|
||||
Max: f32.Point{X: float32(r.Max.X), Y: float32(r.Max.Y)},
|
||||
}
|
||||
}
|
||||
|
||||
func drawInk(gtx *layout.Context, c widget.Click) {
|
||||
d := gtx.Now().Sub(c.Time)
|
||||
t := float32(d.Seconds())
|
||||
const duration = 0.5
|
||||
if t > duration {
|
||||
return
|
||||
}
|
||||
t = t / duration
|
||||
var stack op.StackOp
|
||||
stack.Push(gtx.Ops)
|
||||
size := float32(gtx.Px(unit.Dp(700))) * t
|
||||
rr := size * .5
|
||||
col := byte(0xaa * (1 - t*t))
|
||||
ink := paint.ColorOp{Color: color.RGBA{A: col, R: col, G: col, B: col}}
|
||||
ink.Add(gtx.Ops)
|
||||
op.TransformOp{}.Offset(c.Position).Offset(f32.Point{
|
||||
X: -rr,
|
||||
Y: -rr,
|
||||
}).Add(gtx.Ops)
|
||||
rrect(gtx.Ops, float32(size), float32(size), rr, rr, rr, rr)
|
||||
paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{X: float32(size), Y: float32(size)}}}.Add(gtx.Ops)
|
||||
stack.Pop()
|
||||
op.InvalidateOp{}.Add(gtx.Ops)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package material
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/paint"
|
||||
"gioui.org/text"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
)
|
||||
|
||||
type Editor struct {
|
||||
Font text.Font
|
||||
// Color is the text color.
|
||||
Color color.RGBA
|
||||
// Hint contains the text displayed when the editor is empty.
|
||||
Hint string
|
||||
// HintColor is the color of hint text.
|
||||
HintColor color.RGBA
|
||||
|
||||
shaper *text.Shaper
|
||||
}
|
||||
|
||||
func (t *Theme) Editor(hint string) Editor {
|
||||
return Editor{
|
||||
Font: text.Font{
|
||||
Size: unit.Sp(16),
|
||||
},
|
||||
Color: t.Color.Text,
|
||||
shaper: t.Shaper,
|
||||
Hint: hint,
|
||||
HintColor: t.Color.Hint,
|
||||
}
|
||||
}
|
||||
|
||||
func (e Editor) Layout(gtx *layout.Context, editor *widget.Editor) {
|
||||
var stack op.StackOp
|
||||
stack.Push(gtx.Ops)
|
||||
var macro op.MacroOp
|
||||
macro.Record(gtx.Ops)
|
||||
paint.ColorOp{Color: e.HintColor}.Add(gtx.Ops)
|
||||
tl := widget.Label{Alignment: editor.Alignment}
|
||||
tl.Layout(gtx, e.shaper, e.Font, e.Hint)
|
||||
macro.Stop()
|
||||
if w := gtx.Dimensions.Size.X; gtx.Constraints.Width.Min < w {
|
||||
gtx.Constraints.Width.Min = w
|
||||
}
|
||||
if h := gtx.Dimensions.Size.Y; gtx.Constraints.Height.Min < h {
|
||||
gtx.Constraints.Height.Min = h
|
||||
}
|
||||
editor.Layout(gtx, e.shaper, e.Font)
|
||||
if editor.Len() > 0 {
|
||||
paint.ColorOp{Color: e.Color}.Add(gtx.Ops)
|
||||
editor.PaintText(gtx)
|
||||
} else {
|
||||
macro.Add(gtx.Ops)
|
||||
}
|
||||
paint.ColorOp{Color: e.Color}.Add(gtx.Ops)
|
||||
editor.PaintCaret(gtx)
|
||||
stack.Pop()
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package widget implements common widgets.
|
||||
package widget
|
||||
package material
|
||||
|
||||
import (
|
||||
"image"
|
||||
@@ -19,22 +18,22 @@ type Image struct {
|
||||
// Rect is the source rectangle.
|
||||
Rect image.Rectangle
|
||||
// Scale is the ratio of image pixels to
|
||||
// device pixels. If zero, a scale that
|
||||
// makes the image appear at approximately
|
||||
// 72 DPI is used.
|
||||
// dps.
|
||||
Scale float32
|
||||
}
|
||||
|
||||
func (t *Theme) Image(img image.Image) Image {
|
||||
return Image{
|
||||
Src: img,
|
||||
Rect: img.Bounds(),
|
||||
Scale: 160 / 72, // About 72 DPI.
|
||||
}
|
||||
}
|
||||
|
||||
func (im Image) Layout(gtx *layout.Context) {
|
||||
size := im.Src.Bounds()
|
||||
wf, hf := float32(size.Dx()), float32(size.Dy())
|
||||
var w, h int
|
||||
if im.Scale == 0 {
|
||||
const dpPrPx = 160 / 72
|
||||
w, h = gtx.Px(unit.Dp(wf*dpPrPx)), gtx.Px(unit.Dp(hf*dpPrPx))
|
||||
} else {
|
||||
w, h = int(wf*im.Scale+.5), int(hf*im.Scale+.5)
|
||||
}
|
||||
w, h := gtx.Px(unit.Dp(wf*im.Scale)), gtx.Px(unit.Dp(hf*im.Scale))
|
||||
cs := gtx.Constraints
|
||||
d := image.Point{X: cs.Width.Constrain(w), Y: cs.Height.Constrain(h)}
|
||||
aspect := float32(w) / float32(h)
|
||||
@@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
package material
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op/paint"
|
||||
"gioui.org/text"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
)
|
||||
|
||||
type Label struct {
|
||||
// Face defines the text style.
|
||||
Font text.Font
|
||||
// Color is the text color.
|
||||
Color color.RGBA
|
||||
// Alignment specify the text alignment.
|
||||
Alignment text.Alignment
|
||||
// MaxLines limits the number of lines. Zero means no limit.
|
||||
MaxLines int
|
||||
Text string
|
||||
|
||||
shaper *text.Shaper
|
||||
}
|
||||
|
||||
func (t *Theme) H1(txt string) Label {
|
||||
return t.Label(t.TextSize.Scale(96.0/16.0), txt)
|
||||
}
|
||||
|
||||
func (t *Theme) H2(txt string) Label {
|
||||
return t.Label(t.TextSize.Scale(60.0/16.0), txt)
|
||||
}
|
||||
|
||||
func (t *Theme) H3(txt string) Label {
|
||||
return t.Label(t.TextSize.Scale(48.0/16.0), txt)
|
||||
}
|
||||
|
||||
func (t *Theme) H4(txt string) Label {
|
||||
return t.Label(t.TextSize.Scale(34.0/16.0), txt)
|
||||
}
|
||||
|
||||
func (t *Theme) H5(txt string) Label {
|
||||
return t.Label(t.TextSize.Scale(24.0/16.0), txt)
|
||||
}
|
||||
|
||||
func (t *Theme) H6(txt string) Label {
|
||||
return t.Label(t.TextSize.Scale(20.0/16.0), txt)
|
||||
}
|
||||
|
||||
func (t *Theme) Body1(txt string) Label {
|
||||
return t.Label(t.TextSize, txt)
|
||||
}
|
||||
|
||||
func (t *Theme) Body2(txt string) Label {
|
||||
return t.Label(t.TextSize.Scale(14.0/16.0), txt)
|
||||
}
|
||||
|
||||
func (t *Theme) Caption(txt string) Label {
|
||||
return t.Label(t.TextSize.Scale(12.0/16.0), txt)
|
||||
}
|
||||
|
||||
func (t *Theme) Label(size unit.Value, txt string) Label {
|
||||
return Label{
|
||||
Text: txt,
|
||||
Color: t.Color.Text,
|
||||
Font: text.Font{
|
||||
Size: size,
|
||||
},
|
||||
shaper: t.Shaper,
|
||||
}
|
||||
}
|
||||
|
||||
func (l Label) Layout(gtx *layout.Context) {
|
||||
paint.ColorOp{Color: l.Color}.Add(gtx.Ops)
|
||||
tl := widget.Label{Alignment: l.Alignment, MaxLines: l.MaxLines}
|
||||
tl.Layout(gtx, l.shaper, l.Font, l.Text)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// SPDX-License-Identifier: Unlicense OR MIT
|
||||
|
||||
// Package material implements the Material design.
|
||||
package material
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"gioui.org/f32"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/paint"
|
||||
"gioui.org/text"
|
||||
"gioui.org/unit"
|
||||
)
|
||||
|
||||
type Theme struct {
|
||||
Shaper *text.Shaper
|
||||
Color struct {
|
||||
Primary color.RGBA
|
||||
Text color.RGBA
|
||||
Hint color.RGBA
|
||||
}
|
||||
TextSize unit.Value
|
||||
}
|
||||
|
||||
func NewTheme(shaper *text.Shaper) *Theme {
|
||||
t := &Theme{
|
||||
Shaper: shaper,
|
||||
}
|
||||
t.Color.Primary = rgb(0x3f51b5)
|
||||
t.Color.Text = rgb(0x000000)
|
||||
t.Color.Hint = rgb(0xbbbbbb)
|
||||
t.TextSize = unit.Sp(16)
|
||||
return t
|
||||
}
|
||||
|
||||
func rgb(c uint32) color.RGBA {
|
||||
return argb(0xff000000 | c)
|
||||
}
|
||||
|
||||
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 fill(gtx *layout.Context, col color.RGBA) {
|
||||
cs := gtx.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)},
|
||||
}
|
||||
paint.ColorOp{Color: col}.Add(gtx.Ops)
|
||||
paint.PaintOp{Rect: dr}.Add(gtx.Ops)
|
||||
gtx.Dimensions = layout.Dimensions{Size: d, Baseline: d.Y}
|
||||
}
|
||||
|
||||
// https://pomax.github.io/bezierinfo/#circles_cubic.
|
||||
func rrect(ops *op.Ops, width, height, se, sw, nw, ne float32) {
|
||||
w, h := float32(width), float32(height)
|
||||
const c = 0.55228475 // 4*(sqrt(2)-1)/3
|
||||
var b paint.Path
|
||||
b.Begin(ops)
|
||||
b.Move(f32.Point{X: w, Y: h - se})
|
||||
b.Cube(f32.Point{X: 0, Y: se * c}, f32.Point{X: -se + se*c, Y: se}, f32.Point{X: -se, Y: se}) // SE
|
||||
b.Line(f32.Point{X: sw - w + se, Y: 0})
|
||||
b.Cube(f32.Point{X: -sw * c, Y: 0}, f32.Point{X: -sw, Y: -sw + sw*c}, f32.Point{X: -sw, Y: -sw}) // SW
|
||||
b.Line(f32.Point{X: 0, Y: nw - h + sw})
|
||||
b.Cube(f32.Point{X: 0, Y: -nw * c}, f32.Point{X: nw - nw*c, Y: -nw}, f32.Point{X: nw, Y: -nw}) // NW
|
||||
b.Line(f32.Point{X: w - ne - nw, Y: 0})
|
||||
b.Cube(f32.Point{X: ne * c, Y: 0}, f32.Point{X: ne, Y: ne - ne*c}, f32.Point{X: ne, Y: ne}) // NE
|
||||
b.End().Add(ops)
|
||||
}
|
||||
Reference in New Issue
Block a user