mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 09:55:40 +00:00
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
|
go 1.13
|
||||||
|
|
||||||
require (
|
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
|
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=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
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 h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
// Package widget implements common widgets.
|
package material
|
||||||
package widget
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
@@ -19,22 +18,22 @@ type Image struct {
|
|||||||
// Rect is the source rectangle.
|
// Rect is the source rectangle.
|
||||||
Rect image.Rectangle
|
Rect image.Rectangle
|
||||||
// Scale is the ratio of image pixels to
|
// Scale is the ratio of image pixels to
|
||||||
// device pixels. If zero, a scale that
|
// dps.
|
||||||
// makes the image appear at approximately
|
|
||||||
// 72 DPI is used.
|
|
||||||
Scale float32
|
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) {
|
func (im Image) Layout(gtx *layout.Context) {
|
||||||
size := im.Src.Bounds()
|
size := im.Src.Bounds()
|
||||||
wf, hf := float32(size.Dx()), float32(size.Dy())
|
wf, hf := float32(size.Dx()), float32(size.Dy())
|
||||||
var w, h int
|
w, h := gtx.Px(unit.Dp(wf*im.Scale)), gtx.Px(unit.Dp(hf*im.Scale))
|
||||||
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)
|
|
||||||
}
|
|
||||||
cs := gtx.Constraints
|
cs := gtx.Constraints
|
||||||
d := image.Point{X: cs.Width.Constrain(w), Y: cs.Height.Constrain(h)}
|
d := image.Point{X: cs.Width.Constrain(w), Y: cs.Height.Constrain(h)}
|
||||||
aspect := float32(w) / float32(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