├── example ├── text │ ├── .gitignore │ ├── AndroidManifest.xml │ └── main.go └── boxes │ ├── AndroidManifest.xml │ └── main.go ├── text ├── .gitignore ├── genout.go ├── gen.go └── texcoords.go ├── assets ├── assets.go ├── gen.go ├── environment-vert.glsl ├── environment-frag.glsl └── glsl.go ├── .gitignore ├── icon ├── makeicons.hbs ├── makeicons.go └── icon_mdpi.go ├── go.mod ├── debug.go ├── atlas ├── atlas_test.go └── atlas.go ├── LICENSE.bsd ├── size.go ├── anim.go ├── adaptive.go ├── README.md ├── layout.go ├── glutil ├── glutil.go └── gl.go ├── material.go ├── color.go └── environment.go /example/text/.gitignore: -------------------------------------------------------------------------------- 1 | assets/ 2 | -------------------------------------------------------------------------------- /text/.gitignore: -------------------------------------------------------------------------------- 1 | *.png 2 | *.ttf 3 | -------------------------------------------------------------------------------- /assets/assets.go: -------------------------------------------------------------------------------- 1 | //go:generate go run gen.go 2 | 3 | package assets 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | \#*\# 3 | .\#* 4 | *.prof 5 | *.test 6 | 7 | *.png 8 | *.apk 9 | -------------------------------------------------------------------------------- /icon/makeicons.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#each layouts}} 3 | {{#each sprites}} 4 | 5 | {{../classname}} 6 | {{url}} 7 | 8 | {{/each}} 9 | 10 | {{#each layout.items}} 11 | 12 | {{../classname}} 13 | {{meta.name}} 14 | {{x}} 15 | {{y}} 16 | 17 | {{/each}} 18 | {{/each}} 19 | 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module dasa.cc/material 2 | 3 | go 1.14 4 | 5 | require ( 6 | dasa.cc/signal v0.0.0-20221023170706-f88a600dd4cc 7 | dasa.cc/simplex v0.0.0-20180617055632-ae0aeef7c530 8 | dasa.cc/snd v0.0.0-20200301082333-54fd236f2e18 9 | dasa.cc/x v0.0.0-20221023141954-69d42d7509f2 10 | golang.org/x/image v0.1.0 11 | golang.org/x/mobile v0.0.0-20221020085226-b36e6246172e 12 | gopkg.in/fsnotify.v1 v1.4.7 13 | ) 14 | -------------------------------------------------------------------------------- /text/genout.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "strconv" 10 | ) 11 | 12 | const tmpl = `// File is automatically generated by genout.go. DO NOT EDIT. 13 | 14 | package text 15 | 16 | var Texture = []byte(%s)` 17 | 18 | func main() { 19 | b, err := ioutil.ReadFile("out.png") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | if err := ioutil.WriteFile("out.go", []byte(fmt.Sprintf(tmpl, strconv.Quote(string(b)))), 0644); err != nil { 24 | log.Fatal(err) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /assets/gen.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | ) 11 | 12 | func mustreadfile(name string) []byte { 13 | b, err := ioutil.ReadFile(name) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | return b 18 | } 19 | 20 | func main() { 21 | vert := mustreadfile("environment-vert.glsl") 22 | frag := mustreadfile("environment-frag.glsl") 23 | 24 | buf := new(bytes.Buffer) 25 | buf.WriteString("// generated by gen.go; DO NOT EDIT\npackage assets\n\n") 26 | fmt.Fprintf(buf, "var VertexShader = `%s`\n\n", string(vert)) 27 | fmt.Fprintf(buf, "var FragmentShader = `%s`\n", string(frag)) 28 | if err := ioutil.WriteFile("glsl.go", buf.Bytes(), 0644); err != nil { 29 | log.Fatal(err) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/text/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/boxes/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import ( 4 | "log" 5 | "path/filepath" 6 | "strings" 7 | 8 | "gopkg.in/fsnotify.v1" 9 | ) 10 | 11 | func watchShaders() (chan string, chan bool) { 12 | watcher, err := fsnotify.NewWatcher() 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | 17 | event := make(chan string) 18 | quit := make(chan bool) 19 | 20 | // Process events 21 | go func() { 22 | for { 23 | select { 24 | case ev := <-watcher.Events: 25 | log.Println(ev) 26 | // expects filenames such as triangle-vert.glsl or triangle-frag.glsl 27 | if filepath.Ext(ev.Name) == ".glsl" && ev.Op == fsnotify.Write { 28 | b := filepath.Base(ev.Name) 29 | i := strings.LastIndex(b, "-") 30 | b = b[:i] 31 | event <- b 32 | } 33 | case err := <-watcher.Errors: 34 | log.Println("watch error:", err) 35 | case <-quit: 36 | watcher.Close() 37 | return 38 | } 39 | } 40 | }() 41 | 42 | err = watcher.Add("./assets") 43 | if err != nil { 44 | log.Fatal("Failed to watch folder.", err) 45 | } 46 | 47 | return event, quit 48 | } 49 | -------------------------------------------------------------------------------- /assets/environment-vert.glsl: -------------------------------------------------------------------------------- 1 | #version 100 2 | 3 | attribute vec4 vertex; 4 | attribute vec4 color; 5 | 6 | // sample with xy, offset at zw 7 | attribute vec4 texcoord; 8 | 9 | // xy is relative position of originating touch event 10 | // z is state of touch event; begin (0), move (1), end (2) 11 | // w is timing information 12 | attribute vec4 touch; 13 | 14 | // v0, v1 == 1 && v2, v3 == 0, interpolated in fragment shader 15 | // to determine location since all materials are drawn as single 16 | // mesh. 17 | attribute vec4 dist; 18 | 19 | // uniform mat4 world; 20 | uniform mat4 view; 21 | uniform mat4 proj; 22 | 23 | varying vec4 vcolor; 24 | varying vec4 vtexcoord; 25 | varying vec4 vdist; 26 | varying vec4 vvertex; 27 | varying vec4 vtouch; 28 | 29 | void main() { 30 | // TODO review if fragment shader *really* needs access to z coord 31 | // of shadow's material. 32 | vec4 vert = vec4(vertex.xyz, 1.0); 33 | if (vert.z < 0.0) { 34 | vert.z = 0.0; 35 | } 36 | //gl_Position = vert * view * proj; 37 | gl_Position = proj * view * vert; 38 | vcolor = color; 39 | vtexcoord = texcoord; 40 | vvertex = vertex; 41 | vdist = dist; 42 | vtouch = touch; 43 | } 44 | -------------------------------------------------------------------------------- /atlas/atlas_test.go: -------------------------------------------------------------------------------- 1 | package atlas 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "math/rand" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestAdd(t *testing.T) { 13 | rand.Seed(time.Now().UnixNano()) 14 | atl := New(512, 512) 15 | 16 | const iter = 1000 17 | var errc int 18 | for i := 0; i < iter; i++ { 19 | m := image.NewNRGBA(image.Rect(0, 0, rand.Intn(50)+10, rand.Intn(50)+10)) 20 | r, g, b := uint8(rand.Intn(200))+55, uint8(rand.Intn(200))+55, uint8(rand.Intn(200))+55 21 | draw.Draw(m, m.Bounds(), image.NewUniform(color.NRGBA{R: r, G: g, B: b, A: 120}), image.ZP, draw.Src) 22 | 23 | if _, err := atl.Add(m); err != nil { 24 | errc++ 25 | } 26 | } 27 | 28 | t.Logf("inserted %v of %v", iter-errc, iter) 29 | if err := atl.writeFile("out.png"); err != nil { 30 | t.Error(err) 31 | } 32 | } 33 | 34 | func BenchmarkAdd(b *testing.B) { 35 | b.ReportAllocs() 36 | atl := New(512, 512) 37 | for n := 0; n < b.N; n++ { 38 | src := image.NewNRGBA(image.Rect(0, 0, rand.Intn(50)+10, rand.Intn(50)+10)) 39 | r, g, b := uint8(rand.Intn(200))+55, uint8(rand.Intn(200))+55, uint8(rand.Intn(200))+55 40 | draw.Draw(src, src.Bounds(), image.NewUniform(color.NRGBA{R: r, G: g, B: b, A: 120}), image.ZP, draw.Src) 41 | atl.Add(src) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE.bsd: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Daniel Skinner 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /size.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import ( 4 | "golang.org/x/mobile/event/size" 5 | "golang.org/x/mobile/exp/f32" 6 | ) 7 | 8 | var windowSize size.Event 9 | 10 | // Uton converts unit to norm. 11 | func Uton(u float32) float32 { return 2*u - 1 } 12 | 13 | // Ntou converts norm to unit. 14 | func Ntou(n float32) float32 { return (n + 1) / 2 } 15 | 16 | // Mtoa converts mat4 to affine. 17 | // TODO this only exists since I'm doing 2D atm 18 | // and mat4 doesn't have inverse method. 19 | func Mtoa(m f32.Mat4) (a f32.Affine) { 20 | a[0][0] = m[0][0] 21 | a[0][1] = m[0][1] 22 | a[0][2] = m[0][3] 23 | a[1][0] = m[1][0] 24 | a[1][1] = m[1][1] 25 | a[1][2] = m[1][3] 26 | return 27 | } 28 | 29 | func ScreenToUnit(x, y float32) (float32, float32) { 30 | // TODO determine y direction, don't assume 31 | return x / float32(windowSize.WidthPx), 1 - (y / float32(windowSize.HeightPx)) 32 | } 33 | 34 | func ScreenToNorm(x, y float32) (float32, float32) { 35 | x, y = ScreenToUnit(x, y) 36 | return Uton(x), Uton(y) 37 | } 38 | 39 | func ScreenToWorld(x, y, z float32, view, proj f32.Mat4) (float32, float32) { 40 | x, y = ScreenToNorm(x, y) 41 | return NormToWorld(x, y, z, view, proj) 42 | } 43 | 44 | func UnitToWorld(x, y, z float32, view, proj f32.Mat4) (float32, float32) { 45 | wx, wy := NormToWorld(Uton(x), Uton(y), Uton(z), view, proj) 46 | return 1 + wx, -wy 47 | } 48 | 49 | func NormToWorld(x, y, z float32, view, proj f32.Mat4) (float32, float32) { 50 | nv := f32.Vec3{x, y, z} 51 | 52 | unproj := Mtoa(proj) 53 | unproj.Inverse(&unproj) 54 | unview := Mtoa(view) 55 | unview.Inverse(&unview) 56 | 57 | nv[0], nv[1] = nv.Dot(&unproj[0]), nv.Dot(&unproj[1]) 58 | nv[0], nv[1] = nv.Dot(&unview[0]), nv.Dot(&unview[1]) 59 | 60 | return nv[0], nv[1] 61 | } 62 | 63 | func NormToView(x, y, z float32, proj f32.Mat4) (float32, float32) { 64 | nv := f32.Vec3{x, y, z} 65 | unproj := Mtoa(proj) 66 | unproj.Inverse(&unproj) 67 | nv[0], nv[1] = nv.Dot(&unproj[0]), nv.Dot(&unproj[1]) 68 | return nv[0], nv[1] 69 | } 70 | -------------------------------------------------------------------------------- /anim.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import ( 4 | "time" 5 | 6 | "dasa.cc/signal" 7 | "golang.org/x/mobile/exp/f32" 8 | ) 9 | 10 | var ( 11 | ExpSig, LinSig signal.Discrete 12 | ) 13 | 14 | func LinearDrive() signal.Discrete { 15 | sig := make(signal.Discrete, 1024) 16 | sig.Sample(func(t float64) float64 { return t }, 1./1024, 0) 17 | return sig 18 | } 19 | 20 | func init() { 21 | ExpSig = signal.ExpDecay() 22 | ExpSig.UnitInverse() 23 | LinSig = LinearDrive() 24 | } 25 | 26 | type Interpolator struct { 27 | Sig signal.Discrete 28 | Dur time.Duration 29 | Loop bool 30 | } 31 | 32 | type Animation struct { 33 | Sig signal.Discrete 34 | Dur time.Duration 35 | Loop bool 36 | Start func() 37 | Interp func(dt float32) 38 | End func() 39 | } 40 | 41 | func (anim Animation) Do() (quit chan struct{}) { 42 | quit = make(chan struct{}, 1) 43 | go func() { 44 | ticker := time.NewTicker(16 * time.Millisecond) 45 | start := time.Now() 46 | if anim.Start != nil { 47 | anim.Start() 48 | } 49 | for { 50 | select { 51 | case <-quit: 52 | ticker.Stop() 53 | if anim.End != nil { 54 | anim.End() 55 | } 56 | return 57 | case now := <-ticker.C: 58 | since := now.Sub(start) 59 | t := float64(since%anim.Dur) / float64(anim.Dur) 60 | if !anim.Loop && since >= anim.Dur { 61 | quit <- struct{}{} 62 | t = 1 63 | } 64 | dt := float32(anim.Sig.At(t)) 65 | if anim.Interp != nil { 66 | anim.Interp(dt) 67 | } 68 | } 69 | } 70 | }() 71 | return quit 72 | } 73 | 74 | func Animate(mat *f32.Mat4, interp Interpolator, fn func(m *f32.Mat4, dt float32)) (quit chan struct{}) { 75 | m := *mat // copy; translate is always relative to resting position 76 | quit = make(chan struct{}, 1) 77 | go func() { 78 | ticker := time.NewTicker(16 * time.Millisecond) 79 | start := time.Now() 80 | for { 81 | select { 82 | case <-quit: 83 | ticker.Stop() 84 | return 85 | case now := <-ticker.C: 86 | since := now.Sub(start) 87 | t := float64(since%interp.Dur) / float64(interp.Dur) 88 | if !interp.Loop && since >= interp.Dur { 89 | quit <- struct{}{} 90 | t = 1 91 | } 92 | dt := float32(interp.Sig.At(t)) 93 | fn(&m, dt) 94 | } 95 | } 96 | }() 97 | return quit 98 | } 99 | 100 | func AnimateRotate(angle f32.Radian, axis f32.Vec3, mat *f32.Mat4, interp Interpolator) (quit chan struct{}) { 101 | return Animate(mat, interp, func(m *f32.Mat4, dt float32) { 102 | mat.Rotate(m, f32.Radian(dt*float32(angle)), &axis) 103 | }) 104 | } 105 | -------------------------------------------------------------------------------- /icon/makeicons.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "encoding/xml" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "os/exec" 12 | "sort" 13 | "strings" 14 | "text/template" 15 | ) 16 | 17 | const tmpl = `// generated by makeicons.go; DO NOT EDIT 18 | package icon 19 | 20 | type Icon int 21 | 22 | func (ic Icon) Texcoords() (float32, float32) { 23 | tc := texcoords[int(ic)] 24 | return tc[0], tc[1] 25 | } 26 | 27 | const ({{range $i, $ic := .Icons}} 28 | {{if eq $i 0}}{{$ic.Name}} Icon = iota{{else}}{{$ic.Name}}{{end}}{{end}} 29 | ) 30 | 31 | var texcoords = [][2]float32{ 32 | {{range $i, $ic := .Icons}}{{"{"}}{{$ic.X}}, {{$ic.Y}}{{"}"}},{{end}} 33 | } 34 | ` 35 | 36 | type Icon struct { 37 | Name string 38 | Set string 39 | X, Y float32 40 | } 41 | 42 | type Set struct { 43 | Name string 44 | Url string 45 | } 46 | 47 | type ByName []Icon 48 | 49 | func (a ByName) Len() int { return len(a) } 50 | func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 51 | func (a ByName) Less(i, j int) bool { return a[i].Name < a[j].Name } 52 | 53 | func main() { 54 | gopath := os.Getenv("GOPATH") 55 | iconpath := gopath + "/src/github.com/google/material-design-icons/" 56 | glob := "*/drawable-mdpi/*black_48dp.png" 57 | name := "material-icons-black-mdpi" 58 | cmd := exec.Command("sprity", "create", ".", iconpath+glob, "--margin=0", "--orientation=binary-tree", "--name="+name, "--template=makeicons.hbs", "--style=makeicons.json", "--css-path=''", "--prefix=Foo") 59 | cmd.Stderr = os.Stderr 60 | cmd.Stdout = os.Stdout 61 | if err := cmd.Run(); err != nil { 62 | log.Fatal(err) 63 | } 64 | 65 | f, err := os.Open("makeicons.json") 66 | if err != nil { 67 | log.Fatal(err) 68 | } 69 | dec := xml.NewDecoder(f) 70 | 71 | ics := struct { 72 | Sets []Set `xml:"Set"` 73 | Icons []Icon `xml:"Icon"` 74 | }{} 75 | dec.Decode(&ics) 76 | 77 | for i, ic := range ics.Icons { 78 | name := strings.Split(ic.Name, "-") 79 | a := strings.Title(name[0]) 80 | b := strings.Replace(name[3][3:], "_black_48dp", "", 1) 81 | c := strings.Split(b, "_") 82 | for i, s := range c { 83 | c[i] = strings.Title(s) 84 | } 85 | b = strings.Join(c, "") 86 | ic.Name = a + b 87 | ic.X, ic.Y = ic.X/2048, ic.Y/2048 88 | ics.Icons[i] = ic 89 | } 90 | 91 | sort.Sort(ByName(ics.Icons)) 92 | 93 | buf := new(bytes.Buffer) 94 | t, err := template.New("").Parse(tmpl) 95 | if err != nil { 96 | log.Fatal(err) 97 | } 98 | t.Execute(buf, ics) 99 | if err := ioutil.WriteFile("icon_mdpi.go", buf.Bytes(), 0644); err != nil { 100 | log.Fatal(err) 101 | } 102 | os.Remove("makeicons.json") 103 | } 104 | -------------------------------------------------------------------------------- /adaptive.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import ( 4 | "golang.org/x/mobile/exp/f32" 5 | "golang.org/x/mobile/gl" 6 | ) 7 | 8 | type Behavior int 9 | 10 | const ( 11 | // When screen space is available, a surface is always visible. 12 | VisibilityPermanent Behavior = iota 13 | 14 | // Surface visibility can be toggled between visible and hidden. When visible, 15 | // interacting with other elements on the screen does not change visibility. 16 | VisibilityPersistent 17 | 18 | // Surface visibility can be toggled between visible and hidden. When visible, 19 | // interacting with other elements on the screen toggles the surface to become 20 | // hidden or minimized. 21 | VisibilityTemporary 22 | 23 | // Element width stays the same when screen size changes. 24 | WidthFixed 25 | 26 | // Element width grows as screen size changes. 27 | WidthFluid 28 | 29 | // Element width is fixed until influenced by another element or breakpoint. 30 | WidthSticky 31 | 32 | // Element width contracts as a panel is revealed 33 | WidthSqueeze 34 | 35 | // Element width stays the same, its position changes horizontally as a panel 36 | // appears, and it may be partially occluded by a screen’s edge. 37 | WidthPush 38 | 39 | // Element width and position stays the same as a panel appears over content. 40 | WidthOverlay 41 | 42 | // The z position, and shadow of an element. A flat element will have no shadow. 43 | DescriptorFlat 44 | DescriptorRaised 45 | ) 46 | 47 | type Grid struct { 48 | Margin float32 49 | Gutter float32 50 | Columns int 51 | 52 | debug *Material 53 | } 54 | 55 | func (gd *Grid) StepSize() float32 { 56 | return (float32(windowSize.WidthPx) - (gd.Margin * 2)) / float32(gd.Columns) 57 | } 58 | 59 | // TODO avoid the pointer 60 | func NewGrid() *Grid { 61 | // by breakpoints 62 | g := &Grid{Margin: 24, Gutter: 24, Columns: 12} 63 | if windowSize.WidthPx < int(Dp(600).Px()) || windowSize.HeightPx < int(Dp(600).Px()) { 64 | g.Margin, g.Gutter = 16, 16 // TODO dp vals 65 | } 66 | if windowSize.WidthPx < int(Dp(480).Px()) { 67 | g.Columns = 4 68 | } else if windowSize.WidthPx < int(Dp(720).Px()) { 69 | g.Columns = 8 70 | } 71 | return g 72 | } 73 | 74 | func (gd *Grid) draw(ctx gl.Context, view, proj f32.Mat4) { 75 | if gd.debug == nil { 76 | gd.debug = New(ctx, Color(0x03A9F499)) 77 | gd.debug.world.Identity() 78 | gd.debug.world[0][0] = gd.Gutter + gd.Margin 79 | gd.debug.world[1][1] = float32(windowSize.HeightPx) 80 | } 81 | 82 | step := gd.StepSize() 83 | for i := 0; i <= gd.Columns; i++ { 84 | if i == 0 { 85 | gd.debug.world[0][0] = gd.Margin 86 | gd.debug.world[0][3] = 0 87 | } else if i == gd.Columns { 88 | gd.debug.world[0][0] = gd.Margin 89 | gd.debug.world[0][3] = gd.Margin + float32(i)*step 90 | } else { 91 | gd.debug.world[0][0] = gd.Gutter 92 | gd.debug.world[0][3] = gd.Margin + float32(i)*step - gd.Gutter/2.0 93 | } 94 | // TODO add to environment 95 | // gd.debug.Draw(ctx, view, proj) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /atlas/atlas.go: -------------------------------------------------------------------------------- 1 | package atlas 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "image/draw" 8 | "image/png" 9 | "os" 10 | "sort" 11 | 12 | "dasa.cc/material/glutil" 13 | "dasa.cc/x/octree" 14 | "golang.org/x/mobile/gl" 15 | ) 16 | 17 | var ( 18 | DefaultFilter = glutil.TextureFilter(gl.LINEAR, gl.LINEAR) 19 | DefaultWrap = glutil.TextureWrap(gl.REPEAT, gl.REPEAT) 20 | ) 21 | 22 | type Atlas struct { 23 | tex glutil.Texture 24 | img *image.NRGBA 25 | regions []image.Rectangle 26 | // codes []image.Rectangle 27 | } 28 | 29 | func New(w, h int) *Atlas { 30 | r := image.Rect(0, 0, w, h) 31 | return &Atlas{ 32 | img: image.NewNRGBA(r), 33 | regions: []image.Rectangle{r}, 34 | } 35 | } 36 | 37 | func (atlas *Atlas) Create(ctx gl.Context) { 38 | atlas.tex.Create(ctx) 39 | } 40 | 41 | func (atlas *Atlas) Bind(ctx gl.Context) { 42 | atlas.tex.Bind(ctx, DefaultFilter, DefaultWrap) 43 | } 44 | 45 | func (atlas *Atlas) Update(ctx gl.Context) { 46 | if atlas.tex.Value == gl.TEXTURE0 { 47 | atlas.tex.Create(ctx) 48 | atlas.tex.Bind(ctx, DefaultFilter, DefaultWrap) 49 | atlas.tex.Update(ctx, 0, atlas.img.Bounds().Dx(), atlas.img.Bounds().Dy(), atlas.img.Pix) 50 | } else { 51 | atlas.tex.Bind(ctx, DefaultFilter, DefaultWrap) 52 | atlas.tex.Sub(ctx, 0, atlas.img.Bounds().Dx(), atlas.img.Bounds().Dy(), atlas.img.Pix) 53 | } 54 | } 55 | 56 | func (atlas *Atlas) Add(src image.Image) (image.Rectangle, error) { 57 | // first fit decreasing 58 | // TODO look at improved bin packing to replace this later on: 59 | // http://moose.cs.ucla.edu/publications/schreiber_korf_ijcai13.pdf 60 | sz := image.Rectangle{Max: src.Bounds().Size()} 61 | for i, r := range atlas.regions { 62 | s := sz.Add(r.Min) 63 | if s.In(r) { 64 | draw.Draw(atlas.img, s, src, src.Bounds().Min, draw.Over) 65 | // atlas.codes = append(atlas.codes, s) 66 | 67 | right, bottom := r, r 68 | right.Min.X += sz.Max.X 69 | bottom.Min.Y += sz.Max.Y 70 | if dt := r.Max.Sub(sz.Max); dt.X > dt.Y { 71 | bottom.Max.X = right.Min.X 72 | } else { 73 | right.Max.Y = bottom.Min.Y 74 | } 75 | atlas.regions = append(atlas.regions[:i], atlas.regions[i+1:]...) 76 | atlas.regions = append(atlas.regions, right, bottom) 77 | sort.Sort(sort.Reverse(byArea(atlas.regions))) 78 | return s, nil 79 | } 80 | } 81 | 82 | return image.ZR, fmt.Errorf("no available space to add image with size %+v", sz.Max) 83 | } 84 | 85 | func (atl *Atlas) writeFile(name string) error { 86 | out, err := os.Create(name) 87 | if err != nil { 88 | return err 89 | } 90 | defer out.Close() 91 | 92 | m := image.NewNRGBA(atl.img.Bounds()) 93 | draw.Draw(m, m.Bounds(), image.NewUniform(color.White), image.ZP, draw.Src) 94 | draw.Draw(m, m.Bounds(), atl.img, image.ZP, draw.Over) 95 | 96 | return png.Encode(out, m) 97 | } 98 | 99 | type byArea []image.Rectangle 100 | 101 | func (a byArea) Len() int { return len(a) } 102 | func (a byArea) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 103 | func (a byArea) Less(i, j int) bool { 104 | p, q := a[i].Size(), a[j].Size() 105 | return p.X*p.Y < q.X*q.Y 106 | } 107 | 108 | func interleave(x0, y0, x1, y1 uint16) uint64 { 109 | return octree.Dilate16(y1) | octree.Dilate16(x1)<<1 | octree.Dilate16(y0)<<2 | octree.Dilate16(x0)<<3 110 | } 111 | 112 | func deinterleave(a uint64) (x0, y0, x1, y1 uint16) { 113 | return octree.Undilate16(a >> 3), octree.Undilate16(a >> 2), octree.Undilate16(a >> 1), octree.Undilate16(a) 114 | } 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # material 2 | 3 | This package is a work-in-progress for providing an implementation of material design for gomobile and potentially exp/shiny. 4 | 5 | The core goal of this package is to provide an implementation of material design as defined here: https://www.google.com/design/spec/material-design/introduction.html 6 | 7 | Nothing more. 8 | 9 | Features currently provided were written to determine the exact nature of moving forward with a proper implementation. These include the following: 10 | 11 | * Constraint based layouts using simplex method. See https://github.com/dskinner/simplex package. A trivial box model has been written on top of this. The intended usage of the constraint based layout is to fulfill the adaptive layout requirements of material design. 12 | 13 | * Key light shadows. These shadows are not approximated on the material. This can be seen by tilting a view matrix and inspecting. The current implementation is fairly performant on mid ranged android devices. 14 | 15 | * Material design icons, mdpi. See material/icon package. This currently provides 913 icons at 48x48 px as a single texture. While not suitable for hires display, this is more than suitable for development of the package itself. A script in material/icon can be expanded to provide desired sets of icons at high resolution. Grab a copy of the texture here: https://drive.google.com/a/dasa.cc/file/d/0B6hxg-gC2Uz_cG1DakFNcDFxYlk/view 16 | 17 | * Text is provided with signed-distance-field texture. See material/text source for generating a texture and material/example/text for usage. 18 | 19 | * Material. This also includes 1dp of thickness, though the exact nature of this fades when transforming rectangles to ellipses. Only two behavior flags are currently recognized declaring material flat (transparent) or raised (has shadow). 20 | 21 | * Color. A full list of colors as defined is the spec is available as uint32s, e.g. BlueGrey500 22 | 23 | * Environment. While more of an abstraction in the design spec, there is an environment type that accepts a theme palette and is used for creation of new material types such as button and toolbar. 24 | 25 | ## Roadmap 26 | 27 | One of the most important aspects of any UI is to have an effective way to layout items. Such a solution is purely numerical. With that in mind, the current focus is to provide an implementation of material design's [Adaptive UI](https://www.google.com/design/spec/layout/adaptive-ui.html). The implementation should not be interdependent on any other portion of package material. It's conceivable for example to make use of such a package for reflowing terminal based UIs. 28 | 29 | The package shall be well developed, documented, and tested. There are enough minimal implementations of other portions of material design to provide meaningful examples of such a package once complete. Only upon completion of Adaptive UI will a determination of the next step be made. 30 | 31 | As for a timeframe on the whole of package material with only the spare time of a single developer, I could only wildly guesstimate a time frame of 2+ years for completion. Part of this is a strong urge to take a slow-and-steady pace for each portion of package material to best ensure design choices, proper documentation, and reasonably complete testing. 32 | 33 | ## Contributing 34 | 35 | Everything in this package is in-flux. If you're interested in contributing, understand the current focus is iterating on the currently provided features. New features will not be accepted unless they are important for establishing a baseline in determining future functionality and api. Anything beyond the material design spec is out of scope for this package and will not be accepted. 36 | 37 | Please open an issue and discuss your thoughts first. 38 | -------------------------------------------------------------------------------- /layout.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import ( 4 | "dasa.cc/simplex" 5 | "golang.org/x/mobile/exp/f32" 6 | ) 7 | 8 | type Box struct { 9 | l, r, b, t, z simplex.Var 10 | world f32.Mat4 11 | } 12 | 13 | func NewBox(prg *simplex.Program) (a Box) { 14 | a.l, a.r, a.b, a.t, a.z = prg.Var(1), prg.Var(1), prg.Var(1), prg.Var(1), prg.Var(1) 15 | return 16 | } 17 | 18 | func (a Box) Width(x float32) simplex.Constraint { 19 | return simplex.Constrain(simplex.Coef{1, a.r}, simplex.Coef{-1, a.l}).Equal(float64(x)) 20 | } 21 | 22 | func (a Box) Height(x float32) simplex.Constraint { 23 | return simplex.Constrain(simplex.Coef{1, a.t}, simplex.Coef{-1, a.b}).Equal(float64(x)) 24 | } 25 | 26 | func (a Box) Start(x float32) simplex.Constraint { 27 | return simplex.Constrain(simplex.Coef{1, a.l}).Equal(float64(x)) 28 | } 29 | 30 | func (a Box) End(x float32) simplex.Constraint { 31 | return simplex.Constrain(simplex.Coef{1, a.r}).Equal(float64(x)) 32 | } 33 | 34 | func (a Box) Bottom(x float32) simplex.Constraint { 35 | return simplex.Constrain(simplex.Coef{1, a.b}).Equal(float64(x)) 36 | } 37 | 38 | func (a Box) Top(x float32) simplex.Constraint { 39 | return simplex.Constrain(simplex.Coef{1, a.t}).Equal(float64(x)) 40 | } 41 | 42 | func (a Box) Z(z float32) simplex.Constraint { 43 | return simplex.Constrain(simplex.Coef{1, a.z}).Equal(float64(z)) 44 | } 45 | 46 | func (a Box) StartIn(b Box, by float32) simplex.Constraint { 47 | return simplex.Constrain(simplex.Coef{1, a.l}, simplex.Coef{-1, b.l}).GreaterEq(float64(by)) 48 | } 49 | 50 | func (a Box) EndIn(b Box, by float32) simplex.Constraint { 51 | return simplex.Constrain(simplex.Coef{1, b.r}, simplex.Coef{-1, a.r}).GreaterEq(float64(by)) 52 | } 53 | 54 | func (a Box) BottomIn(b Box, by float32) simplex.Constraint { 55 | return simplex.Constrain(simplex.Coef{1, a.b}, simplex.Coef{-1, b.b}).GreaterEq(float64(by)) 56 | } 57 | 58 | func (a Box) TopIn(b Box, by float32) simplex.Constraint { 59 | return simplex.Constrain(simplex.Coef{1, b.t}, simplex.Coef{-1, a.t}).GreaterEq(float64(by)) 60 | } 61 | 62 | func (a Box) CenterVerticalIn(b Box) simplex.Constraint { 63 | return simplex.Constrain(simplex.Coef{1, b.b}, simplex.Coef{1, b.t}, simplex.Coef{-1, a.b}, simplex.Coef{-1, a.t}) 64 | } 65 | 66 | func (a Box) CenterHorizontalIn(b Box) simplex.Constraint { 67 | return simplex.Constrain(simplex.Coef{1, b.l}, simplex.Coef{1, b.r}, simplex.Coef{-1, a.l}, simplex.Coef{-1, a.r}) 68 | } 69 | 70 | func (a Box) Before(b Box, by float32) simplex.Constraint { 71 | return simplex.Constrain(simplex.Coef{1, b.l}, simplex.Coef{-1, a.r}).GreaterEq(float64(by)) 72 | } 73 | 74 | func (a Box) After(b Box, by float32) simplex.Constraint { 75 | // TODO this is the crux of adaptive layout model, along with a Before method. 76 | // Consider how box a would be after box b if room, otherwise box a is below box b. 77 | // Note in the latter case, box a should not be aligned after box b when below. 78 | return simplex.Constrain(simplex.Coef{1, a.l}, simplex.Coef{-1, b.r}).GreaterEq(float64(by)) 79 | } 80 | 81 | func (a Box) Below(b Box, by float32) simplex.Constraint { 82 | return simplex.Constrain(simplex.Coef{1, b.b}, simplex.Coef{-1, a.t}).GreaterEq(float64(by)) 83 | } 84 | 85 | func (a Box) Above(b Box, by float32) simplex.Constraint { 86 | return simplex.Constrain(simplex.Coef{1, a.b}, simplex.Coef{-1, b.t}).GreaterEq(float64(by)) 87 | } 88 | 89 | func (a Box) AlignBottoms(b Box, by float32) simplex.Constraint { 90 | return simplex.Constrain(simplex.Coef{1, b.b}, simplex.Coef{-1, a.b}).GreaterEq(float64(by)) 91 | } 92 | 93 | func (a Box) AlignTops(b Box, by float32) simplex.Constraint { 94 | return simplex.Constrain(simplex.Coef{1, b.t}, simplex.Coef{-1, a.t}).GreaterEq(float64(by)) 95 | } 96 | 97 | func (a Box) Bounds(l, r, b, t float32) []simplex.Constraint { 98 | return []simplex.Constraint{ 99 | simplex.Constrain(simplex.Coef{1, a.l}).GreaterEq(float64(l)), 100 | simplex.Constrain(simplex.Coef{1, a.r}).LessEq(float64(r)), 101 | simplex.Constrain(simplex.Coef{1, a.b}).GreaterEq(float64(b)), 102 | simplex.Constrain(simplex.Coef{1, a.t}).LessEq(float64(t)), 103 | } 104 | } 105 | 106 | func (a *Box) UpdateWorld(prg *simplex.Program) { 107 | prg.For(&a.l, &a.r, &a.b, &a.t, &a.z) 108 | a.world.Identity() 109 | a.world.Translate(&a.world, float32(a.l.Val), float32(a.b.Val), 0) 110 | a.world.Scale(&a.world, float32(a.r.Val-a.l.Val), float32(a.t.Val-a.b.Val), 1) 111 | a.world[2][3] = float32(a.z.Val) 112 | } 113 | -------------------------------------------------------------------------------- /example/text/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "dasa.cc/material" 8 | "golang.org/x/mobile/app" 9 | "golang.org/x/mobile/event/lifecycle" 10 | "golang.org/x/mobile/event/paint" 11 | "golang.org/x/mobile/event/size" 12 | "golang.org/x/mobile/event/touch" 13 | "golang.org/x/mobile/gl" 14 | ) 15 | 16 | var ( 17 | env = new(material.Environment) 18 | 19 | t112, t56, t45, t34, t24, t20, t16, t14, t12 *material.Button 20 | ) 21 | 22 | func init() { 23 | env.SetPalette(material.Palette{ 24 | Primary: material.BlueGrey500, 25 | Dark: material.BlueGrey700, 26 | Light: material.BlueGrey100, 27 | Accent: material.DeepOrangeA200, 28 | }) 29 | } 30 | 31 | func onStart(ctx gl.Context) { 32 | ctx.Enable(gl.BLEND) 33 | ctx.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 34 | ctx.Enable(gl.CULL_FACE) 35 | ctx.CullFace(gl.BACK) 36 | 37 | env.Load(ctx) 38 | env.LoadGlyphs(ctx) 39 | 40 | t112 = env.NewButton(ctx) 41 | t112.SetTextColor(material.White) 42 | t112.SetText("AAAH`e_llo |jJ go 112px") 43 | t112.BehaviorFlags = material.DescriptorFlat 44 | 45 | t56 = env.NewButton(ctx) 46 | t56.SetTextColor(material.White) 47 | t56.SetText("Hello go 56px") 48 | t56.BehaviorFlags = material.DescriptorFlat 49 | 50 | t45 = env.NewButton(ctx) 51 | t45.SetTextColor(material.White) 52 | t45.SetText("Hello go 45px") 53 | t45.BehaviorFlags = material.DescriptorFlat 54 | 55 | t34 = env.NewButton(ctx) 56 | t34.SetTextColor(material.White) 57 | t34.SetText("Hello go 34px") 58 | t34.BehaviorFlags = material.DescriptorFlat 59 | 60 | t24 = env.NewButton(ctx) 61 | t24.SetTextColor(material.White) 62 | t24.SetText("Hello go 24px") 63 | t24.BehaviorFlags = material.DescriptorFlat 64 | 65 | t20 = env.NewButton(ctx) 66 | t20.SetTextColor(material.White) 67 | t20.SetText("Hello go 20px") 68 | t20.BehaviorFlags = material.DescriptorFlat 69 | 70 | t16 = env.NewButton(ctx) 71 | t16.SetTextColor(material.White) 72 | t16.SetText("Hello go 16px") 73 | t16.BehaviorFlags = material.DescriptorFlat 74 | 75 | t14 = env.NewButton(ctx) 76 | t14.SetTextColor(material.White) 77 | t14.SetText("Hello go 14px") 78 | t14.BehaviorFlags = material.DescriptorFlat 79 | 80 | t12 = env.NewButton(ctx) 81 | t12.SetTextColor(material.White) 82 | t12.SetText("Hello go 12px") 83 | t12.BehaviorFlags = material.DescriptorFlat 84 | } 85 | 86 | func onLayout(sz size.Event) { 87 | env.SetOrtho(sz) 88 | env.StartLayout() 89 | env.AddConstraints( 90 | t112.Width(1290), t112.Height(112), t112.Z(1), t112.StartIn(env.Box, env.Grid.Gutter), t112.TopIn(env.Box, env.Grid.Gutter), 91 | t56.Width(620), t56.Height(56), t56.Z(1), t56.StartIn(env.Box, env.Grid.Gutter), t56.Below(t112.Box, env.Grid.Gutter), 92 | t45.Width(500), t45.Height(45), t45.Z(1), t45.StartIn(env.Box, env.Grid.Gutter), t45.Below(t56.Box, env.Grid.Gutter), 93 | t34.Width(380), t34.Height(34), t34.Z(1), t34.StartIn(env.Box, env.Grid.Gutter), t34.Below(t45.Box, env.Grid.Gutter), 94 | t24.Width(270), t24.Height(24), t24.Z(1), t24.StartIn(env.Box, env.Grid.Gutter), t24.Below(t34.Box, env.Grid.Gutter), 95 | t20.Width(230), t20.Height(20), t20.Z(1), t20.StartIn(env.Box, env.Grid.Gutter), t20.Below(t24.Box, env.Grid.Gutter), 96 | t16.Width(180), t16.Height(16), t16.Z(1), t16.StartIn(env.Box, env.Grid.Gutter), t16.Below(t20.Box, env.Grid.Gutter), 97 | t14.Width(155), t14.Height(14), t14.Z(1), t14.StartIn(env.Box, env.Grid.Gutter), t14.Below(t16.Box, env.Grid.Gutter), 98 | t12.Width(135), t12.Height(12), t12.Z(1), t12.StartIn(env.Box, env.Grid.Gutter), t12.Below(t14.Box, env.Grid.Gutter), 99 | ) 100 | log.Println("starting layout") 101 | t := time.Now() 102 | env.FinishLayout() 103 | log.Printf("finished layout in %s\n", time.Now().Sub(t)) 104 | } 105 | 106 | var lastpaint time.Time 107 | var fps int 108 | 109 | func onPaint(ctx gl.Context) { 110 | ctx.ClearColor(material.BlueGrey500.RGBA()) 111 | ctx.Clear(gl.COLOR_BUFFER_BIT) 112 | env.Draw(ctx) 113 | now := time.Now() 114 | fps = int(time.Second / now.Sub(lastpaint)) 115 | lastpaint = now 116 | } 117 | 118 | func main() { 119 | app.Main(func(a app.App) { 120 | var glctx gl.Context 121 | for ev := range a.Events() { 122 | switch ev := a.Filter(ev).(type) { 123 | case lifecycle.Event: 124 | switch ev.Crosses(lifecycle.StageVisible) { 125 | case lifecycle.CrossOn: 126 | go func() { 127 | for range time.Tick(time.Second) { 128 | log.Printf("fps=%-4v\n", fps) 129 | } 130 | }() 131 | glctx = ev.DrawContext.(gl.Context) 132 | onStart(glctx) 133 | a.Send(paint.Event{}) 134 | case lifecycle.CrossOff: 135 | env.Unload(glctx) 136 | glctx = nil 137 | } 138 | case size.Event: 139 | if glctx == nil { 140 | a.Send(ev) // republish event until onStart is called 141 | } else { 142 | onLayout(ev) 143 | } 144 | case touch.Event: 145 | env.Touch(ev) 146 | case paint.Event: 147 | if glctx == nil || ev.External { 148 | continue 149 | } 150 | onPaint(glctx) 151 | a.Publish() 152 | a.Send(paint.Event{}) 153 | } 154 | } 155 | }) 156 | } 157 | -------------------------------------------------------------------------------- /glutil/glutil.go: -------------------------------------------------------------------------------- 1 | package glutil 2 | 3 | import ( 4 | "math" 5 | 6 | "golang.org/x/mobile/exp/f32" 7 | "golang.org/x/mobile/gl" 8 | ) 9 | 10 | var ident f32.Mat4 11 | 12 | func init() { 13 | ident.Identity() 14 | } 15 | 16 | // Ortho provides a general purpose orthographic projection. 17 | // TODO probably just get rid of this and pass in zero'd out z to perspective 18 | func Ortho(m *f32.Mat4, l, r float32, b, t float32, n, f float32) { 19 | m.Identity() 20 | m.Scale(m, 2/(r-l), 2/(t-b), 2/(f-n)) 21 | m.Translate(m, -((l + r) / 2), -((t + b) / 2), (f+n)/2) 22 | } 23 | 24 | // Perspective sets m to a screen space perspective with origin at bottom-left. 25 | func Perspective(m *f32.Mat4, l, r float32, b, t float32) { 26 | m.Identity() 27 | // TODO i think [2][2] is best as t-b, or, shortest path, but maybe worth picking something that's consistent 28 | // e.g. always 1000 29 | (*m)[0][0] = 2 / (r - l) 30 | (*m)[0][3] = -1 // offset for [0][0] 31 | (*m)[1][1] = 2 / (t - b) 32 | (*m)[1][3] = -1 // offset for [1][1] 33 | (*m)[2][2] = 2 / (r * 10) //(r - l) // TODO should maybe pick consistent result, such as whichever is smaller; r-l or t-b 34 | (*m)[3][2] = -(*m)[2][2] * 10 // pronounced z effect with increased factor 35 | } 36 | 37 | type Drawer interface { 38 | Draw(ctx gl.Context, view, proj f32.Mat4) 39 | } 40 | 41 | type DrawerFunc func(ctx gl.Context, view, proj f32.Mat4) 42 | 43 | func (fn DrawerFunc) Draw(ctx gl.Context, view, proj f32.Mat4) { 44 | fn(ctx, view, proj) 45 | } 46 | 47 | type FloatBuffer interface { 48 | Bind(gl.Context) 49 | Update(gl.Context, []float32) 50 | Draw(gl.Context, Program, gl.Enum) 51 | Delete(gl.Context) 52 | } 53 | 54 | type floatBuffer struct { 55 | gl.Buffer 56 | bin []byte 57 | count int 58 | usage gl.Enum 59 | } 60 | 61 | func NewFloatBuffer(ctx gl.Context, data []float32, usage gl.Enum) FloatBuffer { 62 | buf := &floatBuffer{Buffer: ctx.CreateBuffer(), usage: usage} 63 | buf.Bind(ctx) 64 | buf.Update(ctx, data) 65 | return buf 66 | } 67 | 68 | func (buf *floatBuffer) Bind(ctx gl.Context) { ctx.BindBuffer(gl.ARRAY_BUFFER, buf.Buffer) } 69 | 70 | func (buf *floatBuffer) Update(ctx gl.Context, data []float32) { 71 | buf.count = len(data) 72 | subok := len(buf.bin) > 0 && len(data)*4 <= len(buf.bin) 73 | if !subok { 74 | buf.bin = make([]byte, len(data)*4) 75 | } 76 | for i, x := range data { 77 | u := math.Float32bits(x) 78 | buf.bin[4*i+0] = byte(u >> 0) 79 | buf.bin[4*i+1] = byte(u >> 8) 80 | buf.bin[4*i+2] = byte(u >> 16) 81 | buf.bin[4*i+3] = byte(u >> 24) 82 | } 83 | if subok { 84 | ctx.BufferSubData(gl.ARRAY_BUFFER, 0, buf.bin) 85 | } else { 86 | ctx.BufferData(gl.ARRAY_BUFFER, buf.bin, buf.usage) 87 | } 88 | } 89 | 90 | func (buf *floatBuffer) Draw(ctx gl.Context, prg Program, mode gl.Enum) { 91 | ctx.DrawArrays(mode, 0, buf.count) 92 | } 93 | 94 | func (buf *floatBuffer) Delete(ctx gl.Context) { 95 | ctx.DeleteBuffer(buf.Buffer) 96 | } 97 | 98 | type UintBuffer interface { 99 | Bind(gl.Context) 100 | Update(gl.Context, []uint32) 101 | Draw(gl.Context, Program, gl.Enum) 102 | Delete(gl.Context) 103 | } 104 | 105 | type uintBuffer struct { 106 | gl.Buffer 107 | bin []byte 108 | count int 109 | usage gl.Enum 110 | } 111 | 112 | func NewUintBuffer(ctx gl.Context, data []uint32, usage gl.Enum) UintBuffer { 113 | buf := &uintBuffer{Buffer: ctx.CreateBuffer(), usage: usage} 114 | buf.Bind(ctx) 115 | buf.Update(ctx, data) 116 | return buf 117 | } 118 | 119 | func (buf *uintBuffer) Bind(ctx gl.Context) { ctx.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, buf.Buffer) } 120 | 121 | func (buf *uintBuffer) Update(ctx gl.Context, data []uint32) { 122 | buf.count = len(data) 123 | subok := len(buf.bin) > 0 && len(data)*4 <= len(buf.bin) 124 | if !subok { 125 | buf.bin = make([]byte, len(data)*4) 126 | } 127 | for i, u := range data { 128 | buf.bin[4*i+0] = byte(u >> 0) 129 | buf.bin[4*i+1] = byte(u >> 8) 130 | buf.bin[4*i+2] = byte(u >> 16) 131 | buf.bin[4*i+3] = byte(u >> 24) 132 | } 133 | if subok { 134 | ctx.BufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, buf.bin) 135 | } else { 136 | ctx.BufferData(gl.ELEMENT_ARRAY_BUFFER, buf.bin, buf.usage) 137 | } 138 | } 139 | 140 | func (buf *uintBuffer) Draw(ctx gl.Context, prg Program, mode gl.Enum) { 141 | ctx.DrawElements(mode, buf.count, gl.UNSIGNED_INT, 0) 142 | } 143 | 144 | func (buf *uintBuffer) Delete(ctx gl.Context) { 145 | ctx.DeleteBuffer(buf.Buffer) 146 | } 147 | 148 | var ( 149 | int32v4 = make([]int32, 4) 150 | int32v2 = make([]int32, 2) 151 | ) 152 | 153 | type TextureFramebuffer struct { 154 | fbo Framebuffer 155 | tex Texture 156 | w, h int 157 | 158 | def, filter, wrap func(gl.Context, Texture) 159 | withtex func(gl.Context, Framebuffer) 160 | } 161 | 162 | func (buf *TextureFramebuffer) Tex() Texture { return buf.tex } 163 | 164 | // TODO pass in an actual Texture ... 165 | func NewTextureBuffer(ctx gl.Context, width, height int) *TextureFramebuffer { 166 | buf := &TextureFramebuffer{w: width, h: height} 167 | buf.fbo = Framebuffer{ctx.CreateFramebuffer()} 168 | buf.tex = Texture{ctx.CreateTexture()} 169 | 170 | buf.def = TextureDef(0, width, height, gl.RGBA, nil) 171 | buf.filter = TextureFilter(gl.LINEAR, gl.LINEAR) 172 | buf.wrap = TextureWrap(gl.REPEAT, gl.REPEAT) 173 | buf.withtex = FramebufferWithTex(buf.tex, 0, buf.def, buf.filter, buf.wrap) 174 | return buf 175 | } 176 | 177 | func (buf *TextureFramebuffer) Delete(ctx gl.Context) { 178 | ctx.DeleteFramebuffer(buf.fbo.Framebuffer) 179 | ctx.DeleteTexture(buf.tex.Texture) 180 | } 181 | 182 | func (buf *TextureFramebuffer) StartSample(ctx gl.Context) { 183 | buf.fbo.Bind(ctx, buf.withtex) 184 | ctx.GetIntegerv(int32v4, gl.VIEWPORT) 185 | ctx.Viewport(0, 0, buf.w, buf.h) 186 | ctx.ClearColor(0, 0, 0, 0) 187 | ctx.Clear(gl.COLOR_BUFFER_BIT) 188 | } 189 | 190 | func (buf *TextureFramebuffer) StopSample(ctx gl.Context) { 191 | ctx.Viewport(int(int32v4[0]), int(int32v4[1]), int(int32v4[2]), int(int32v4[3])) 192 | // TODO Unbind should maybe take options too? 193 | buf.fbo.Unbind(ctx) 194 | // sbuf.tex.Unbind(ctx) 195 | } 196 | -------------------------------------------------------------------------------- /assets/environment-frag.glsl: -------------------------------------------------------------------------------- 1 | #version 100 2 | #define onesqrt2 0.70710678118 3 | #define sqrt2 1.41421356237 4 | #define pi 3.14159265359 5 | #define twopi 6.28318530718 6 | 7 | #define touchBegin 0.0 8 | #define touchMove 1.0 9 | #define touchEnd 2.0 10 | precision mediump float; 11 | 12 | // TODO pass this in some other way so sampler can be selected 13 | // 0:fontsize, 1:pad, 2:edge 14 | uniform vec4 glyphconf; 15 | 16 | uniform sampler2D image; 17 | 18 | uniform sampler2D texglyph; 19 | uniform sampler2D texicon; 20 | uniform vec2 glyph; 21 | uniform vec4 shadowColor; 22 | 23 | varying vec4 vtexcoord; 24 | varying vec4 vvertex; 25 | varying vec4 vtouch; 26 | 27 | // interpolated distance, and size values 28 | // x, y is unit value [0..1] 29 | // z, w is material width, height 30 | varying vec4 vdist; 31 | varying vec4 vcolor; 32 | 33 | vec4 sampleIcon() { 34 | vec4 clr = texture2D(texicon, vtexcoord.xy); 35 | clr.rgb += vcolor.rgb; 36 | clr.a *= 0.54; // https://www.google.com/design/spec/style/color.html#color-ui-color-application 37 | return clr; 38 | } 39 | 40 | vec4 sampleImage() { 41 | vec4 clr = texture2D(image, vtexcoord.xy); 42 | return clr; 43 | } 44 | 45 | // four values for a texture might look like this 46 | // [ 47 | // morton-encoded bounds, 48 | // texture size, 49 | // ?type? so as to invoke specific shader bits (glyph, icon, image) 50 | // alpha 51 | // ] 52 | // 53 | // numbers are 32-bit 54 | // must have four values to id rectangle for texture coordinates. 55 | // could reduce this to 3 numbers if two were uint16 containers and one 56 | // specified texture size. If texture size could be set only once then 57 | // it could be two. Perhaps set texture size in a uniform? that would 58 | // allow for 2 numbers to specify rectangle and 1 for type and 1 for alpha. 59 | 60 | vec4 sampleGlyph() { 61 | float fontsize = glyphconf.x; 62 | float pad = glyphconf.y; 63 | float edge = glyphconf.z; 64 | 65 | float d = texture2D(texglyph, vtexcoord.xy).a; 66 | float h = vdist.w; 67 | float gamma = 0.22/(pad*(h/fontsize)); 68 | 69 | // d += 0.2; // bold 70 | // d -= 0.2; // thin 71 | 72 | vec4 clr = vcolor; 73 | clr.a = smoothstep(edge-gamma, edge+gamma, d); 74 | clr.a *= 0.87; // secondary text 75 | return clr; 76 | } 77 | 78 | // TODO drop this 79 | bool shouldcolor(vec2 pos, float sz) { 80 | // maps 0.0 .. 0.5 .. 1.0 81 | // to 0.0 .. 0.5 .. 0.0 82 | pos = 0.5-abs(pos-0.5); 83 | 84 | // multiply by width/height 85 | pos *= vdist.zw; 86 | 87 | // 88 | if (pos.x <= sz && pos.y <= sz) { 89 | float d = length(1.0-(pos/sz)); 90 | if (d > 1.0) { 91 | return false; 92 | } 93 | } 94 | return true; 95 | } 96 | 97 | float shade(vec2 pos, float sz) { 98 | pos = 0.5-abs(pos-0.5); 99 | pos *= vdist.zw; 100 | 101 | if (pos.x <= sz && pos.y <= sz) { 102 | float d = length(1.0-(pos/sz)); 103 | if (d > 1.0) { // TODO consider moving this as discard into top of main 104 | return 0.0; 105 | } 106 | return 1.0-d; 107 | } else if (pos.x <= sz && pos.y > sz) { 108 | return pos.x/sz; 109 | } else if (pos.x > sz && pos.y <= sz) { 110 | return pos.y/sz; 111 | } 112 | return 1.0; 113 | } 114 | 115 | void main() { 116 | float roundness = vvertex.w; 117 | 118 | if (vtexcoord.x >= 0.0) { 119 | if (vtexcoord.z == 3.0) { 120 | gl_FragColor = sampleImage(); 121 | } else if (vtexcoord.z == 1.0) { 122 | gl_FragColor = sampleIcon(); 123 | } else { 124 | gl_FragColor = sampleGlyph(); 125 | } 126 | } else if (vvertex.z <= 0.0) { // draw shadow 127 | if (roundness < 8.0) { 128 | roundness = 8.0; 129 | } 130 | 131 | // TODO ellipsis are being over-rounded resulting in a distortion 132 | // in the shadow. The distortion isn't very noticable unless drawing 133 | // only shadows, but this does affect outline of material making it 134 | // more difficult to see material edges. Clamping roundness results 135 | // in a strange distortion when animating roundness and the cause is 136 | // not currently clear. 137 | // 138 | // At the same time, the distortion looks better for different cases. 139 | roundness += -vvertex.z; 140 | // if (roundness > vdist.z/2.0) { 141 | // roundness = vdist.z/2.0; 142 | // } 143 | 144 | gl_FragColor = shadowColor; 145 | 146 | // maps roundness to 1 - [0..0.66] and helps shadows cast by rectangles and ellipses 147 | // look similar at the same z index. 148 | // TODO vvertex.w is roundness, double check usage here as was used before float roundness declared 149 | float e = 1.0 - (vvertex.w/vdist.z/0.75); 150 | 151 | gl_FragColor.a = smoothstep(0.0, e, shade(vdist.xy, roundness)); 152 | vec2 n = abs(vdist.xy*2.0 - 1.0); 153 | n = n*n*n*n; 154 | n = 1.0-n; 155 | 156 | // reduce alpha/strength as z-index increases 157 | float f = 1.0 + (-vvertex.z*0.1); 158 | 159 | gl_FragColor.a *= n.x*n.y/f; 160 | // gl_FragColor.a *= 10.0; 161 | } else { // draw material 162 | if (shouldcolor(vdist.xy, roundness)) { 163 | gl_FragColor = vcolor; 164 | 165 | // anti-alias roundness 166 | if (gl_FragColor.a != 0.0) { // guard against exposing bg of text and icon content 167 | float dist = 1.0-shade(vdist.xy, roundness); 168 | // fractional based on largest size, approximates a consistent value across resolutions 169 | float dt = (5.0/max(vdist.z, vdist.w)); 170 | gl_FragColor.a = 1.0-smoothstep(1.0-dt, 1.0, dist); 171 | } 172 | 173 | // respond to touch with radial 174 | const float dur = 200.0; 175 | const float clr = 0.035; 176 | 177 | float since = vtouch.w; 178 | vec4 react = vec4(0.0); 179 | 180 | // float d = length(vtouch.xy-vdist.xy); 181 | // float d = length(vtouch.xx-vdist.xx); // horizontal sweep 182 | 183 | const float magic = 100.0; 184 | float d = length((vtouch.xy*vdist.zw)-(vdist.xy*vdist.zw)); 185 | d /= magic; 186 | 187 | float t = since/dur; 188 | if (d < 2.0*t) { 189 | if (t < sqrt2) { // color in 190 | react = vec4(clr); 191 | } else if (t < pi) { // fade out 192 | float fac = 1.0 - (t-sqrt2)/(pi-sqrt2); 193 | react = vec4(fac*clr); 194 | } 195 | } 196 | 197 | gl_FragColor += react; 198 | } else { 199 | discard; 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /example/boxes/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "dasa.cc/material" 9 | "dasa.cc/signal" 10 | "golang.org/x/mobile/app" 11 | "golang.org/x/mobile/event/lifecycle" 12 | "golang.org/x/mobile/event/paint" 13 | "golang.org/x/mobile/event/size" 14 | "golang.org/x/mobile/event/touch" 15 | "golang.org/x/mobile/exp/f32" 16 | "golang.org/x/mobile/gl" 17 | ) 18 | 19 | var ( 20 | env = new(material.Environment) 21 | boxes [9]*material.Material 22 | sig signal.Discrete 23 | quits []chan struct{} 24 | ) 25 | 26 | func onStart(ctx gl.Context) { 27 | env.SetPalette(material.Palette{ 28 | Primary: material.BlueGrey500, 29 | Dark: material.BlueGrey700, 30 | Light: material.BlueGrey100, 31 | Accent: material.DeepOrangeA200, 32 | }) 33 | 34 | quits = []chan struct{}{} 35 | 36 | sig = make(signal.Discrete, len(material.ExpSig)) 37 | copy(sig, material.ExpSig) 38 | rsig := make(signal.Discrete, len(material.ExpSig)) 39 | copy(rsig, material.ExpSig) 40 | rsig.UnitInverse() 41 | sig = append(sig, rsig...) 42 | sig.NormalizeRange(0, 1) 43 | 44 | ctx.Enable(gl.BLEND) 45 | ctx.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 46 | 47 | ctx.Enable(gl.CULL_FACE) 48 | ctx.CullFace(gl.BACK) 49 | 50 | env.Load(ctx) 51 | env.LoadGlyphs(ctx) 52 | 53 | for i := range boxes { 54 | boxes[i] = env.NewMaterial(ctx) 55 | boxes[i].SetColor(material.BlueGrey200) 56 | } 57 | } 58 | 59 | func onStop(ctx gl.Context) { 60 | env.Unload(ctx) 61 | for i := range boxes { 62 | boxes[i] = nil 63 | } 64 | for _, q := range quits { 65 | q <- struct{}{} 66 | } 67 | } 68 | 69 | func onLayout(sz size.Event) { 70 | env.SetOrtho(sz) 71 | // env.SetPerspective(sz) 72 | env.StartLayout() 73 | 74 | for i, box := range boxes { 75 | env.AddConstraints(box.Width(100), box.Height(100), box.Z(float32(i+1))) 76 | } 77 | 78 | b, p := env.Box, env.Grid.Gutter 79 | env.AddConstraints( 80 | boxes[0].StartIn(b, p), boxes[0].TopIn(b, p), 81 | boxes[1].CenterHorizontalIn(b), boxes[1].TopIn(b, p), 82 | boxes[2].EndIn(b, p), boxes[2].TopIn(b, p), 83 | boxes[3].CenterVerticalIn(b), boxes[3].StartIn(b, p), 84 | boxes[4].CenterVerticalIn(b), boxes[4].CenterHorizontalIn(b), 85 | boxes[5].CenterVerticalIn(b), boxes[5].EndIn(b, p), 86 | boxes[6].StartIn(b, p), boxes[6].BottomIn(b, p), 87 | boxes[7].CenterHorizontalIn(b), boxes[7].BottomIn(b, p), 88 | boxes[8].EndIn(b, p), boxes[8].BottomIn(b, p), 89 | ) 90 | 91 | boxes[4].SetTextColor(material.Black) 92 | boxes[4].SetTextHeight(material.Dp(24).Px()) 93 | 94 | for _, q := range quits { 95 | q <- struct{}{} 96 | } 97 | quits = quits[:0] 98 | 99 | log.Println("starting layout") 100 | t := time.Now() 101 | env.FinishLayout() 102 | log.Printf("finished layout in %s\n", time.Now().Sub(t)) 103 | 104 | func() { 105 | m := boxes[1].World() 106 | x, z := m[0][3], m[2][3] 107 | quits = append(quits, material.Animation{ 108 | Sig: sig, 109 | Dur: 2000 * time.Millisecond, 110 | Loop: true, 111 | Interp: func(dt float32) { 112 | m[0][3] = x + 200*dt 113 | }, 114 | }.Do()) 115 | quits = append(quits, material.Animation{ 116 | Sig: sig, 117 | Dur: 1000 * time.Millisecond, 118 | Loop: true, 119 | Interp: func(dt float32) { 120 | m[2][3] = z + 4*dt 121 | }, 122 | }.Do()) 123 | }() 124 | 125 | func() { 126 | m := boxes[2].World() 127 | z := m[2][3] 128 | quits = append(quits, material.Animation{ 129 | Sig: sig, 130 | Dur: 2000 * time.Millisecond, 131 | Loop: true, 132 | Interp: func(dt float32) { 133 | m[2][3] = z + 10*dt 134 | }, 135 | }.Do()) 136 | }() 137 | 138 | func() { 139 | m := boxes[4].World() 140 | x, y := m[0][3], m[1][3] 141 | w, h := m[0][0], m[1][1] 142 | quits = append(quits, material.Animation{ 143 | Sig: sig, 144 | Dur: 4000 * time.Millisecond, 145 | Loop: true, 146 | Interp: func(dt float32) { 147 | m[0][0] = w + 200*dt 148 | m[0][3] = x - 200*dt/2 149 | boxes[4].SetText(fmt.Sprintf("w: %.2f\nh: %.2f", m[0][0], m[1][1])) 150 | }, 151 | }.Do()) 152 | quits = append(quits, material.Animation{ 153 | Sig: sig, 154 | Dur: 2000 * time.Millisecond, 155 | Loop: true, 156 | Interp: func(dt float32) { 157 | m[1][1] = h + 200*dt 158 | m[1][3] = y - 200*dt/2 159 | }, 160 | }.Do()) 161 | }() 162 | 163 | func() { 164 | m := boxes[6].World() 165 | w, h := m[0][0], m[1][1] 166 | z := m[2][3] 167 | quits = append(quits, material.Animation{ 168 | Sig: sig, 169 | Dur: 4000 * time.Millisecond, 170 | Loop: true, 171 | Interp: func(dt float32) { 172 | boxes[6].Roundness = 50 * (1 - dt) 173 | m[0][0] = w + 200*dt 174 | m[1][1] = h + 200*dt 175 | }, 176 | }.Do()) 177 | quits = append(quits, material.Animation{ 178 | Sig: sig, 179 | Dur: 8000 * time.Millisecond, 180 | Loop: true, 181 | Interp: func(dt float32) { 182 | m[2][3] = z + 7*dt 183 | }, 184 | }.Do()) 185 | }() 186 | 187 | func() { 188 | m := boxes[8].World() 189 | w := m[0][0] 190 | quits = append(quits, material.Animation{ 191 | Sig: sig, 192 | Dur: 2000 * time.Millisecond, 193 | Loop: true, 194 | Interp: func(dt float32) { 195 | boxes[8].Roundness = (w / 2) * dt 196 | }, 197 | }.Do()) 198 | }() 199 | 200 | _ = f32.Vec3{} 201 | // env.View.Translate(&env.View, 400, 250, 400) 202 | // env.View.Rotate(&env.View, 0.785, &f32.Vec3{0, 0, 1}) 203 | // env.View.Rotate(&env.View, 0.785, &f32.Vec3{0, 1, 0}) 204 | } 205 | 206 | var lastpaint time.Time 207 | var fps int 208 | 209 | func onPaint(ctx gl.Context) { 210 | ctx.ClearColor(material.BlueGrey100.RGBA()) 211 | ctx.Clear(gl.COLOR_BUFFER_BIT) 212 | env.Draw(ctx) 213 | now := time.Now() 214 | fps = int(time.Second / now.Sub(lastpaint)) 215 | lastpaint = now 216 | } 217 | 218 | func main() { 219 | app.Main(func(a app.App) { 220 | var glctx gl.Context 221 | var ticker *time.Ticker 222 | for ev := range a.Events() { 223 | switch ev := a.Filter(ev).(type) { 224 | case lifecycle.Event: 225 | switch ev.Crosses(lifecycle.StageVisible) { 226 | case lifecycle.CrossOn: 227 | if ticker != nil { 228 | ticker.Stop() 229 | } 230 | ticker = time.NewTicker(time.Second) 231 | go func() { 232 | for range ticker.C { 233 | log.Printf("fps=%-4v\n", fps) 234 | } 235 | }() 236 | glctx = ev.DrawContext.(gl.Context) 237 | onStart(glctx) 238 | a.Send(paint.Event{}) 239 | case lifecycle.CrossOff: 240 | if ticker != nil { 241 | ticker.Stop() 242 | } 243 | onStop(glctx) 244 | glctx = nil 245 | } 246 | case size.Event: 247 | if glctx == nil { 248 | a.Send(ev) // republish event until onStart is called 249 | } else { 250 | onLayout(ev) 251 | } 252 | case paint.Event: 253 | if glctx == nil || ev.External { 254 | continue 255 | } 256 | onPaint(glctx) 257 | a.Publish() 258 | a.Send(paint.Event{}) 259 | case touch.Event: 260 | env.Touch(ev) 261 | } 262 | } 263 | }) 264 | } 265 | -------------------------------------------------------------------------------- /assets/glsl.go: -------------------------------------------------------------------------------- 1 | // generated by gen.go; DO NOT EDIT 2 | package assets 3 | 4 | var VertexShader = `#version 100 5 | 6 | attribute vec4 vertex; 7 | attribute vec4 color; 8 | 9 | // sample with xy, offset at zw 10 | attribute vec4 texcoord; 11 | 12 | // xy is relative position of originating touch event 13 | // z is state of touch event; begin (0), move (1), end (2) 14 | // w is timing information 15 | attribute vec4 touch; 16 | 17 | // v0, v1 == 1 && v2, v3 == 0, interpolated in fragment shader 18 | // to determine location since all materials are drawn as single 19 | // mesh. 20 | attribute vec4 dist; 21 | 22 | // uniform mat4 world; 23 | uniform mat4 view; 24 | uniform mat4 proj; 25 | 26 | varying vec4 vcolor; 27 | varying vec4 vtexcoord; 28 | varying vec4 vdist; 29 | varying vec4 vvertex; 30 | varying vec4 vtouch; 31 | 32 | void main() { 33 | // TODO review if fragment shader *really* needs access to z coord 34 | // of shadow's material. 35 | vec4 vert = vec4(vertex.xyz, 1.0); 36 | if (vert.z < 0.0) { 37 | //vert.z = 0.0; 38 | } 39 | //gl_Position = vert * view * proj; 40 | //gl_Position = proj * view * vert; 41 | gl_Position = proj * view * vert; 42 | vcolor = color; 43 | vtexcoord = texcoord; 44 | vvertex = vertex; 45 | vdist = dist; 46 | vtouch = touch; 47 | } 48 | ` 49 | 50 | var FragmentShader = `#version 100 51 | #define onesqrt2 0.70710678118 52 | #define sqrt2 1.41421356237 53 | #define pi 3.14159265359 54 | #define twopi 6.28318530718 55 | 56 | #define touchBegin 0.0 57 | #define touchMove 1.0 58 | #define touchEnd 2.0 59 | precision mediump float; 60 | 61 | // TODO pass this in some other way so sampler can be selected 62 | // 0:fontsize, 1:pad, 2:edge 63 | uniform vec4 glyphconf; 64 | 65 | uniform sampler2D image; 66 | 67 | uniform sampler2D texglyph; 68 | uniform sampler2D texicon; 69 | uniform vec2 glyph; 70 | uniform vec4 shadowColor; 71 | 72 | varying vec4 vtexcoord; 73 | varying vec4 vvertex; 74 | varying vec4 vtouch; 75 | 76 | // interpolated distance, and size values 77 | // x, y is unit value [0..1] 78 | // z, w is material width, height 79 | varying vec4 vdist; 80 | varying vec4 vcolor; 81 | 82 | vec4 sampleIcon() { 83 | vec4 clr = texture2D(texicon, vtexcoord.xy); 84 | clr.rgb += vcolor.rgb; 85 | clr.a *= 0.54; // https://www.google.com/design/spec/style/color.html#color-ui-color-application 86 | return clr; 87 | } 88 | 89 | vec4 sampleImage() { 90 | vec4 clr = texture2D(image, vtexcoord.xy); 91 | return clr; 92 | } 93 | 94 | // four values for a texture might look like this 95 | // [ 96 | // morton-encoded bounds, 97 | // texture size, 98 | // ?type? so as to invoke specific shader bits (glyph, icon, image) 99 | // alpha 100 | // ] 101 | // 102 | // numbers are 32-bit 103 | // must have four values to id rectangle for texture coordinates. 104 | // could reduce this to 3 numbers if two were uint16 containers and one 105 | // specified texture size. If texture size could be set only once then 106 | // it could be two. Perhaps set texture size in a uniform? that would 107 | // allow for 2 numbers to specify rectangle and 1 for type and 1 for alpha. 108 | 109 | vec4 sampleGlyph() { 110 | float fontsize = glyphconf.x; 111 | float pad = glyphconf.y; 112 | float edge = glyphconf.z; 113 | 114 | float d = texture2D(texglyph, vtexcoord.xy).a; 115 | float h = vdist.w; 116 | float gamma = 0.22/(pad*(h/fontsize)); 117 | 118 | // d += 0.2; // bold 119 | // d -= 0.2; // thin 120 | 121 | vec4 clr = vcolor; 122 | clr.a = smoothstep(edge-gamma, edge+gamma, d); 123 | clr.a *= 0.87; // secondary text 124 | return clr; 125 | } 126 | 127 | // TODO drop this 128 | bool shouldcolor(vec2 pos, float sz) { 129 | pos = 0.5-abs(pos-0.5); 130 | pos *= vdist.zw; 131 | 132 | if (pos.x <= sz && pos.y <= sz) { 133 | float d = length(1.0-(pos/sz)); 134 | if (d > 1.0) { 135 | return false; 136 | } 137 | } 138 | return true; 139 | } 140 | 141 | float shade(vec2 pos, float sz) { 142 | pos = 0.5-abs(pos-0.5); 143 | pos *= vdist.zw; 144 | 145 | if (pos.x <= sz && pos.y <= sz) { 146 | float d = length(1.0-(pos/sz)); 147 | if (d > 1.0) { // TODO consider moving this as discard into top of main 148 | return 0.0; 149 | } 150 | return 1.0-d; 151 | } else if (pos.x <= sz && pos.y > sz) { 152 | return pos.x/sz; 153 | } else if (pos.x > sz && pos.y <= sz) { 154 | return pos.y/sz; 155 | } 156 | return 1.0; 157 | } 158 | 159 | void main() { 160 | float roundness = vvertex.w; 161 | 162 | if (vtexcoord.x >= 0.0) { 163 | if (vtexcoord.z == 3.0) { 164 | gl_FragColor = sampleImage(); 165 | } else if (vtexcoord.z == 1.0) { 166 | gl_FragColor = sampleIcon(); 167 | } else { 168 | gl_FragColor = sampleGlyph(); 169 | } 170 | } else if (vvertex.z <= 0.0) { // draw shadow 171 | if (roundness < 8.0) { 172 | roundness = 8.0; 173 | } 174 | 175 | // TODO ellipsis are being over-rounded resulting in a distortion 176 | // in the shadow. The distortion isn't very noticable unless drawing 177 | // only shadows, but this does affect outline of material making it 178 | // more difficult to see material edges. Clamping roundness results 179 | // in a strange distortion when animating roundness and the cause is 180 | // not currently clear. 181 | // 182 | // At the same time, the distortion looks better for different cases. 183 | roundness += -vvertex.z; 184 | // if (roundness > vdist.z/2.0) { 185 | // roundness = vdist.z/2.0; 186 | // } 187 | 188 | gl_FragColor = shadowColor; 189 | 190 | // maps roundness to 1 - [0..0.66] and helps shadows cast by rectangles and ellipses 191 | // look similar at the same z index. 192 | // TODO vvertex.w is roundness, double check usage here as was used before float roundness declared 193 | float e = 1.0 - (vvertex.w/vdist.z/0.75); 194 | 195 | gl_FragColor.a = smoothstep(0.0, e, shade(vdist.xy, roundness)); 196 | vec2 n = abs(vdist.xy*2.0 - 1.0); 197 | n = n*n*n*n; 198 | n = 1.0-n; 199 | 200 | // reduce alpha/strength as z-index increases 201 | float f = 1.0 + (-vvertex.z*0.1); 202 | 203 | gl_FragColor.a *= n.x*n.y/f; 204 | // gl_FragColor.a *= 10.0; 205 | } else { // draw material 206 | if (shouldcolor(vdist.xy, roundness)) { 207 | gl_FragColor = vcolor; 208 | 209 | // anti-alias roundness 210 | if (gl_FragColor.a != 0.0) { // guard against exposing bg of text and icon content 211 | float dist = 1.0-shade(vdist.xy, roundness); 212 | // fractional based on largest size, approximates a consistent value across resolutions 213 | float dt = (5.0/max(vdist.z, vdist.w)); 214 | gl_FragColor.a = 1.0-smoothstep(1.0-dt, 1.0, dist); 215 | } 216 | 217 | // respond to touch with radial 218 | const float dur = 200.0; 219 | const float clr = 0.035; 220 | 221 | float since = vtouch.w; 222 | vec4 react = vec4(0.0); 223 | 224 | // float d = length(vtouch.xy-vdist.xy); 225 | // float d = length(vtouch.xx-vdist.xx); // horizontal sweep 226 | 227 | const float magic = 100.0; 228 | float d = length((vtouch.xy*vdist.zw)-(vdist.xy*vdist.zw)); 229 | d /= magic; 230 | 231 | float t = since/dur; 232 | if (d < 2.0*t) { 233 | if (t < sqrt2) { // color in 234 | react = vec4(clr); 235 | } else if (t < pi) { // fade out 236 | float fac = 1.0 - (t-sqrt2)/(pi-sqrt2); 237 | react = vec4(fac*clr); 238 | } 239 | } 240 | 241 | gl_FragColor += react; 242 | } else { 243 | discard; 244 | } 245 | } 246 | } 247 | ` 248 | -------------------------------------------------------------------------------- /material.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import ( 4 | "time" 5 | 6 | "dasa.cc/material/glutil" 7 | "dasa.cc/material/icon" 8 | "dasa.cc/simplex" 9 | "golang.org/x/mobile/event/touch" 10 | "golang.org/x/mobile/exp/f32" 11 | "golang.org/x/mobile/gl" 12 | ) 13 | 14 | var ( 15 | DefaultFilter = glutil.TextureFilter(gl.LINEAR, gl.LINEAR) 16 | DefaultWrap = glutil.TextureWrap(gl.REPEAT, gl.REPEAT) 17 | 18 | linearFilter = glutil.TextureFilter(gl.LINEAR, gl.LINEAR) 19 | nearestFilter = glutil.TextureFilter(gl.NEAREST, gl.NEAREST) 20 | glyphsFilter = glutil.TextureFilter(gl.LINEAR_MIPMAP_LINEAR, gl.LINEAR) 21 | ) 22 | 23 | type Material struct { 24 | Box 25 | 26 | Drawer glutil.DrawerFunc 27 | 28 | col4, col8, col12 int 29 | 30 | hidden bool 31 | 32 | BehaviorFlags Behavior 33 | 34 | text struct { 35 | value string 36 | height float32 37 | r, g, b, a float32 38 | } 39 | 40 | icon struct { 41 | x, y float32 42 | r, g, b, a float32 43 | } 44 | 45 | cr, cg, cb, ca float32 // color for uniform 46 | 47 | IsCircle bool 48 | Roundness float32 49 | 50 | touch struct { 51 | state touch.Type 52 | x, y float32 53 | start time.Time 54 | } 55 | 56 | ShowImage bool 57 | Rotate float32 // Radian 58 | } 59 | 60 | func (mtrl *Material) Span(col4, col8, col12 int) { 61 | mtrl.col4, mtrl.col8, mtrl.col12 = col4, col8, col12 62 | } 63 | 64 | func New(ctx gl.Context, color Color) *Material { 65 | mtrl := &Material{ 66 | BehaviorFlags: DescriptorRaised, 67 | } 68 | mtrl.icon.x, mtrl.icon.y = -1, -1 69 | mtrl.touch.state = touch.TypeEnd 70 | mtrl.cr, mtrl.cg, mtrl.cb, mtrl.ca = color.RGBA() 71 | 72 | return mtrl 73 | } 74 | 75 | // SetColor sets background color of material unless material flags contains DescriptorFlat. 76 | func (mtrl *Material) SetColor(color Color) { 77 | mtrl.cr, mtrl.cg, mtrl.cb, mtrl.ca = color.RGBA() 78 | } 79 | 80 | func (mtrl *Material) SetIcon(ic icon.Icon) { 81 | mtrl.icon.x, mtrl.icon.y = ic.Texcoords() 82 | } 83 | 84 | func (mtrl *Material) SetIconColor(color Color) { 85 | mtrl.icon.r, mtrl.icon.g, mtrl.icon.b, mtrl.icon.a = color.RGBA() 86 | } 87 | 88 | func (mtrl *Material) SetTextColor(color Color) { 89 | mtrl.text.r, mtrl.text.g, mtrl.text.b, mtrl.text.a = color.RGBA() 90 | } 91 | 92 | func (mtrl *Material) SetTextHeight(h float32) { 93 | mtrl.text.height = h 94 | } 95 | 96 | func (mtrl *Material) SetText(s string) { 97 | mtrl.text.value = s 98 | } 99 | 100 | func (mtrl *Material) Bind(lpro *simplex.Program) { 101 | mtrl.Box = NewBox(lpro) 102 | } 103 | 104 | func (mtrl *Material) World() *f32.Mat4 { return &mtrl.world } 105 | 106 | func (mtrl *Material) Hidden() bool { return mtrl.hidden } 107 | 108 | func (mtrl *Material) M() *Material { return mtrl } 109 | 110 | func (mtrl *Material) Contains(tx, ty float32) bool { 111 | x, y, w, h := mtrl.world[0][3], mtrl.world[1][3], mtrl.world[0][0], mtrl.world[1][1] 112 | return x <= tx && tx <= x+w && y <= ty && ty <= y+h 113 | } 114 | 115 | func (mtrl *Material) RelativeCoords(tx, ty float32) (float32, float32) { 116 | x, y, w, h := mtrl.world[0][3], mtrl.world[1][3], mtrl.world[0][0], mtrl.world[1][1] 117 | return (tx - x) / w, (ty - y) / h 118 | } 119 | 120 | func (mtrl *Material) Constraints(env *Environment) []simplex.Constraint { 121 | return nil 122 | } 123 | 124 | // TODO seems to slow down goimport ... 125 | var shdr, shdg, shdb, shda = BlueGrey900.RGBA() 126 | 127 | type Button struct { 128 | *Material 129 | OnPress func() 130 | OnTouch func(touch.Event) 131 | } 132 | 133 | type FloatingActionButton struct { 134 | *Material 135 | Mini bool 136 | OnPress func() 137 | OnTouch func(touch.Event) 138 | } 139 | 140 | func (fab *FloatingActionButton) Constraints(env *Environment) []simplex.Constraint { 141 | var size float32 142 | switch env.Grid.Columns { 143 | case 4, 8: 144 | if fab.Mini { 145 | size = Dp(40).Px() 146 | } else { 147 | size = Dp(56).Px() 148 | } 149 | case 12: 150 | if fab.Mini { 151 | size = Dp(48).Px() // TODO size unconfirmed 152 | } else { 153 | size = Dp(64).Px() 154 | } 155 | } 156 | fab.Roundness = size / 2 // TODO consider how this should work 157 | return []simplex.Constraint{fab.Width(size), fab.Height(size), fab.Z(6)} 158 | } 159 | 160 | // TODO https://www.google.com/design/spec/layout/structure.html#structure-toolbars 161 | type Toolbar struct { 162 | *Material 163 | Nav *Button 164 | Title *Material 165 | actions []*Button 166 | } 167 | 168 | func (bar *Toolbar) AddAction(btn *Button) { 169 | btn.BehaviorFlags = DescriptorFlat 170 | btn.SetIconColor(Black) 171 | bar.actions = append(bar.actions, btn) 172 | } 173 | 174 | func (tb *Toolbar) Constraints(env *Environment) []simplex.Constraint { 175 | stp := env.Grid.StepSize() 176 | var ( 177 | width, height float32 178 | btnsize float32 179 | titleStart float32 180 | ) 181 | 182 | switch env.Grid.Columns { 183 | case 4: 184 | width = float32(tb.col4) * stp 185 | height = Dp(56).Px() 186 | btnsize = Dp(24).Px() 187 | titleStart = Dp(48).Px() 188 | case 8: 189 | width = float32(tb.col8) * stp 190 | height = Dp(56).Px() 191 | btnsize = Dp(24).Px() 192 | titleStart = Dp(72).Px() 193 | case 12: 194 | width = float32(tb.col12) * stp 195 | height = Dp(64).Px() 196 | btnsize = Dp(32).Px() 197 | titleStart = Dp(72).Px() 198 | } 199 | nav := tb.Nav 200 | title := tb.Title 201 | cns := []simplex.Constraint{ 202 | tb.Width(width), tb.Height(height), tb.Z(4), 203 | tb.StartIn(env.Box, env.Grid.Margin), tb.TopIn(env.Box, env.Grid.Margin), 204 | nav.Width(btnsize), nav.Height(btnsize), nav.Z(5), 205 | nav.StartIn(tb.Box, env.Grid.Gutter), 206 | nav.CenterVerticalIn(tb.Box), 207 | title.StartIn(tb.Box, titleStart), title.Before(tb.actions[len(tb.actions)-1].Box, 0), 208 | title.CenterVerticalIn(tb.Box), title.Height(btnsize), title.Z(5), 209 | } 210 | 211 | for i, btn := range tb.actions { 212 | cns = append(cns, btn.Width(btnsize), btn.Height(btnsize), btn.Z(5), btn.CenterVerticalIn(tb.Box)) 213 | if i == 0 { 214 | cns = append(cns, btn.EndIn(tb.Box, env.Grid.Gutter)) 215 | } else { 216 | cns = append(cns, btn.Before(tb.actions[i-1].Box, env.Grid.Gutter)) 217 | } 218 | } 219 | 220 | return cns 221 | } 222 | 223 | type NavDrawer struct { 224 | *Material 225 | } 226 | 227 | type Menu struct { 228 | *Material 229 | selected int 230 | actions []*Button 231 | } 232 | 233 | func (mu *Menu) AddAction(btn *Button) { 234 | btn.BehaviorFlags = DescriptorFlat 235 | btn.SetIconColor(Black) 236 | btn.hidden = mu.hidden 237 | mu.actions = append(mu.actions, btn) 238 | } 239 | 240 | func (mu *Menu) ShowAt(m *f32.Mat4) { 241 | x := mu.Box.world[0][3] 242 | y := mu.Box.world[1][3] 243 | mu.Box.world[0][3] = m[0][3] 244 | mu.Box.world[1][3] = m[1][3] + m[1][1] - mu.Box.world[1][1] 245 | dx := mu.Box.world[0][3] - x 246 | dy := mu.Box.world[1][3] - y 247 | for _, btn := range mu.actions { 248 | btn.Box.world[0][3] += dx 249 | btn.Box.world[1][3] += dy 250 | } 251 | mu.Show() 252 | } 253 | 254 | func (mu *Menu) Show() { 255 | go func() { 256 | h := mu.Box.world[1][1] 257 | y := mu.Box.world[1][3] 258 | anim := Animation{ 259 | Sig: ExpSig, 260 | Dur: 300 * time.Millisecond, 261 | Start: func() { 262 | mu.Box.world[1][1] = 0 263 | mu.Box.world[1][3] = y + h 264 | mu.hidden = false 265 | for _, btn := range mu.actions { 266 | btn.hidden = false 267 | } 268 | }, 269 | Interp: func(dt float32) { 270 | mu.Box.world[1][1] = h * dt 271 | mu.Box.world[1][3] = (y + h) - h*dt 272 | }, 273 | End: func() { 274 | mu.Box.world[1][1] = h 275 | mu.Box.world[1][3] = y 276 | }, 277 | } 278 | anim.Do() 279 | }() 280 | } 281 | 282 | func (mu *Menu) Hide() { 283 | h := mu.Box.world[1][1] 284 | y := mu.Box.world[1][3] 285 | Animation{ 286 | Sig: ExpSig, 287 | Dur: 200 * time.Millisecond, 288 | Interp: func(dt float32) { 289 | mu.Box.world[1][1] = h * (1 - dt) 290 | mu.Box.world[1][3] = (y + h) - h*(1-dt) 291 | }, 292 | End: func() { 293 | mu.hidden = true 294 | for _, btn := range mu.actions { 295 | btn.hidden = true 296 | } 297 | mu.Box.world[1][1] = h 298 | mu.Box.world[1][3] = y 299 | }, 300 | }.Do() 301 | } 302 | 303 | func (mu *Menu) Constraints(env *Environment) []simplex.Constraint { 304 | cns := []simplex.Constraint{ 305 | mu.Width(Dp(100).Px()), mu.Z(8), 306 | mu.StartIn(env.Box, env.Grid.Margin), mu.TopIn(env.Box, env.Grid.Margin), 307 | } 308 | 309 | for i, btn := range mu.actions { 310 | cns = append(cns, btn.Width(Dp(100).Px()), btn.Height(Dp(16).Px()), btn.Z(9), btn.StartIn(mu.Box, Dp(16).Px())) 311 | if i == 0 { 312 | cns = append(cns, btn.TopIn(mu.Box, env.Grid.Gutter)) 313 | } else { 314 | cns = append(cns, btn.Below(mu.actions[i-1].Box, Dp(20).Px())) 315 | } 316 | } 317 | cns = append(cns, mu.AlignBottoms(mu.actions[len(mu.actions)-1].Box, Dp(20).Px())) 318 | 319 | return cns 320 | } 321 | -------------------------------------------------------------------------------- /glutil/gl.go: -------------------------------------------------------------------------------- 1 | package glutil 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "math" 7 | 8 | "golang.org/x/mobile/asset" 9 | "golang.org/x/mobile/exp/f32" 10 | "golang.org/x/mobile/gl" 11 | ) 12 | 13 | func MustOpen(name string) asset.File { 14 | f, err := asset.Open(name) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | return f 19 | } 20 | 21 | func MustReadAll(name string) []byte { 22 | f := MustOpen(name) 23 | b, err := ioutil.ReadAll(f) 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | return b 28 | } 29 | 30 | func ShaderAsset(typ gl.Enum, name string) func(gl.Context) gl.Shader { 31 | return ShaderCompile(typ, name, string(MustReadAll(name))) 32 | } 33 | 34 | func VertAsset(name string) func(gl.Context) gl.Shader { 35 | return ShaderAsset(gl.VERTEX_SHADER, name) 36 | } 37 | 38 | func FragAsset(name string) func(gl.Context) gl.Shader { 39 | return ShaderAsset(gl.FRAGMENT_SHADER, name) 40 | } 41 | 42 | func ShaderCompile(typ gl.Enum, name string, src string) func(gl.Context) gl.Shader { 43 | return func(ctx gl.Context) gl.Shader { 44 | s := ctx.CreateShader(typ) 45 | ctx.ShaderSource(s, src) 46 | ctx.CompileShader(s) 47 | if ctx.GetShaderi(s, gl.COMPILE_STATUS) == 0 { 48 | log.Fatalf("glutil %s: %s\n", name, ctx.GetShaderInfoLog(s)) 49 | } 50 | return s 51 | } 52 | } 53 | 54 | // TODO what if Program could use reflection and map functions? 55 | // really, it doesn't need to do functions at all! just iter over 56 | // attribs and uniforms and match 57 | // TODO use reflection to set values 58 | // type TexProgram struct { 59 | // Program 60 | // setworld func(f32.Mat4) 61 | // setsampler func(int) 62 | // world gl.Uniform 63 | // view gl.Uniform 64 | // matrix f32.Mat4 `glsl:"gl.Uniform"` // not really necessary, could just fail if same name exists in vert and frag shader 65 | // sampler gl.Uniform 66 | // position gl.Attrib 67 | // texcoord gl.Attrib 68 | // } 69 | type Program struct{ gl.Program } 70 | 71 | func (prg *Program) CreateAndLink(ctx gl.Context, compilers ...func(gl.Context) gl.Shader) { 72 | prg.Program = ctx.CreateProgram() 73 | for _, c := range compilers { 74 | s := c(ctx) 75 | ctx.AttachShader(prg.Program, s) 76 | defer ctx.DeleteShader(s) 77 | } 78 | ctx.LinkProgram(prg.Program) 79 | if ctx.GetProgrami(prg.Program, gl.LINK_STATUS) == 0 { 80 | log.Fatalf("program link: %s", ctx.GetProgramInfoLog(prg.Program)) 81 | } 82 | } 83 | 84 | func (prg Program) Delete(ctx gl.Context) { 85 | ctx.DeleteProgram(prg.Program) 86 | } 87 | 88 | func (prg Program) Uniform(ctx gl.Context, name string) gl.Uniform { 89 | return ctx.GetUniformLocation(prg.Program, name) 90 | } 91 | 92 | func (prg Program) Attrib(ctx gl.Context, name string) gl.Attrib { 93 | return ctx.GetAttribLocation(prg.Program, name) 94 | } 95 | 96 | func (prg Program) Use(ctx gl.Context, options ...func(gl.Context, Program)) { 97 | ctx.UseProgram(prg.Program) 98 | for _, opt := range options { 99 | opt(ctx, prg) 100 | } 101 | } 102 | 103 | func (prg Program) Mat4(ctx gl.Context, dst gl.Uniform, src f32.Mat4) { 104 | ctx.UniformMatrix4fv(dst, []float32{ 105 | src[0][0], src[1][0], src[2][0], src[3][0], 106 | src[0][1], src[1][1], src[2][1], src[3][1], 107 | src[0][2], src[1][2], src[2][2], src[3][2], 108 | src[0][3], src[1][3], src[2][3], src[3][3], 109 | }) 110 | } 111 | 112 | func (prg Program) U1i(ctx gl.Context, dst gl.Uniform, v int) { 113 | ctx.Uniform1i(dst, v) 114 | } 115 | 116 | func (prg Program) U2i(ctx gl.Context, dst gl.Uniform, v0, v1 int) { 117 | ctx.Uniform2i(dst, v0, v1) 118 | } 119 | 120 | func (prg Program) U1f(ctx gl.Context, dst gl.Uniform, v float32) { 121 | ctx.Uniform1f(dst, v) 122 | } 123 | 124 | func (prg Program) U2f(ctx gl.Context, dst gl.Uniform, v0, v1 float32) { 125 | ctx.Uniform2f(dst, v0, v1) 126 | } 127 | 128 | func (prg Program) U4f(ctx gl.Context, dst gl.Uniform, v0, v1, v2, v3 float32) { 129 | ctx.Uniform4f(dst, v0, v1, v2, v3) 130 | } 131 | 132 | // TODO an Attrib type that describes it's format would be useful here 133 | func (prg Program) Pointer(ctx gl.Context, a gl.Attrib, size int) { 134 | ctx.EnableVertexAttribArray(a) 135 | ctx.VertexAttribPointer(a, size, gl.FLOAT, false, 0, 0) 136 | } 137 | 138 | // func UniformMat4(dst gl.Uniform) func(gl.Context, f32.Mat4) { 139 | // src := make([]float32, 16) 140 | // return func(ctx gl.Context, m f32.Mat4) { 141 | // for i, v := range m { 142 | // for j, x := range v { 143 | // src[i*4+j] = x 144 | // } 145 | // } 146 | // ctx.UniformMatrix4fv(dst, src) 147 | // } 148 | // } 149 | 150 | // TODO want this ??? 151 | type Uniform4fFunc func(gl.Context, f32.Vec4) 152 | 153 | func UniformVec4(dst gl.Uniform) func(gl.Context, f32.Vec4) { 154 | return func(ctx gl.Context, v f32.Vec4) { ctx.Uniform4f(dst, v[0], v[1], v[2], v[3]) } 155 | } 156 | 157 | func UniformFloat(dst gl.Uniform) func(gl.Context, float32) { 158 | return func(ctx gl.Context, x float32) { ctx.Uniform1f(dst, x) } 159 | } 160 | 161 | type Buffer struct { 162 | gl.Buffer 163 | target gl.Enum 164 | } 165 | 166 | func (buf *Buffer) Create(ctx gl.Context, target gl.Enum) { 167 | buf.Buffer = ctx.CreateBuffer() 168 | // buf.Update = BufferFloatData(target, usage) 169 | buf.target = target 170 | } 171 | 172 | func (buf Buffer) Delete(ctx gl.Context) { 173 | ctx.DeleteBuffer(buf.Buffer) 174 | // buf.Update = nil 175 | } 176 | 177 | func (buf Buffer) Bind(ctx gl.Context, after ...func(gl.Context)) { 178 | ctx.BindBuffer(buf.target, buf.Buffer) 179 | for _, fn := range after { 180 | fn(ctx) 181 | } 182 | } 183 | 184 | func (buf Buffer) Draw(ctx gl.Context, mode gl.Enum, first int, count int, before ...func(gl.Context)) { 185 | buf.Bind(ctx, before...) 186 | ctx.DrawArrays(mode, first, count) 187 | } 188 | 189 | func (buf Buffer) DrawElements(ctx gl.Context, mode gl.Enum, count int, typ gl.Enum, offset int, before ...func(gl.Context)) { 190 | buf.Bind(ctx, before...) 191 | ctx.DrawElements(mode, count, typ, offset) 192 | } 193 | 194 | func VertexAttrib(a gl.Attrib, size int, typ gl.Enum, normalize bool, stride int, offset int) func(gl.Context) { 195 | return func(ctx gl.Context) { 196 | ctx.EnableVertexAttribArray(a) 197 | ctx.VertexAttribPointer(a, size, typ, normalize, stride, offset) 198 | } 199 | } 200 | 201 | func BufferFloatData(target, usage gl.Enum) func(gl.Context, []float32) { 202 | var bin []byte 203 | return func(ctx gl.Context, data []float32) { 204 | subok := len(bin) > 0 && len(data)*4 <= len(bin) 205 | if !subok { 206 | bin = make([]byte, len(data)*4) 207 | } 208 | for i, x := range data { 209 | u := math.Float32bits(x) 210 | bin[4*i+0] = byte(u >> 0) 211 | bin[4*i+1] = byte(u >> 8) 212 | bin[4*i+2] = byte(u >> 16) 213 | bin[4*i+3] = byte(u >> 24) 214 | } 215 | if subok { 216 | ctx.BufferSubData(target, 0, bin) 217 | } else { 218 | ctx.BufferData(target, bin, usage) 219 | } 220 | } 221 | } 222 | 223 | func BufferUintData(target, usage gl.Enum) func(gl.Context, []uint32) { 224 | var bin []byte 225 | return func(ctx gl.Context, data []uint32) { 226 | subok := len(bin) > 0 && len(data)*4 <= len(bin) 227 | if !subok { 228 | bin = make([]byte, len(data)*4) 229 | } 230 | for i, u := range data { 231 | bin[4*i+0] = byte(u >> 0) 232 | bin[4*i+1] = byte(u >> 8) 233 | bin[4*i+2] = byte(u >> 16) 234 | bin[4*i+3] = byte(u >> 24) 235 | } 236 | if subok { 237 | ctx.BufferSubData(target, 0, bin) 238 | } else { 239 | ctx.BufferData(target, bin, usage) 240 | } 241 | } 242 | } 243 | 244 | type Texture struct{ gl.Texture } 245 | 246 | func (tex *Texture) Create(ctx gl.Context) { 247 | tex.Texture = ctx.CreateTexture() 248 | } 249 | 250 | func (tex Texture) Delete(ctx gl.Context) { 251 | ctx.DeleteTexture(tex.Texture) 252 | } 253 | 254 | func (tex Texture) Bind(ctx gl.Context, options ...func(gl.Context, Texture)) { 255 | ctx.ActiveTexture(gl.Enum(uint32(gl.TEXTURE0) + tex.Value - 1)) 256 | ctx.BindTexture(gl.TEXTURE_2D, tex.Texture) 257 | for _, opt := range options { 258 | opt(ctx, tex) 259 | } 260 | } 261 | 262 | func (tex Texture) Unbind(ctx gl.Context) { 263 | ctx.BindTexture(gl.TEXTURE_2D, gl.Texture{Value: 0}) 264 | } 265 | 266 | func (tex Texture) Update(ctx gl.Context, lvl int, width int, height int, data []byte) { 267 | ctx.TexImage2D(gl.TEXTURE_2D, lvl, int(gl.RGBA), width, height, gl.RGBA, gl.UNSIGNED_BYTE, data) 268 | if lvl > 0 { 269 | ctx.GenerateMipmap(gl.TEXTURE_2D) 270 | } 271 | } 272 | 273 | // TODO incorporate into Update, see FloatBuffer and UintBuffer 274 | func (tex Texture) Sub(ctx gl.Context, lvl int, width int, height int, data []byte) { 275 | ctx.TexSubImage2D(gl.TEXTURE_2D, lvl, 0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data) 276 | if lvl > 0 { 277 | ctx.GenerateMipmap(gl.TEXTURE_2D) 278 | } 279 | } 280 | 281 | func TextureDef(lvl int, width, height int, format gl.Enum, data []byte) func(gl.Context, Texture) { 282 | return func(ctx gl.Context, tex Texture) { 283 | ctx.TexImage2D(gl.TEXTURE_2D, lvl, int(format), width, height, format, gl.UNSIGNED_BYTE, data) 284 | } 285 | } 286 | 287 | func TextureFilter(min, mag int) func(gl.Context, Texture) { 288 | return func(ctx gl.Context, tex Texture) { 289 | ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, min) 290 | ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, mag) 291 | } 292 | } 293 | 294 | func TextureWrap(s, t int) func(gl.Context, Texture) { 295 | return func(ctx gl.Context, tex Texture) { 296 | ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, s) 297 | ctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, t) 298 | } 299 | } 300 | 301 | type Framebuffer struct{ gl.Framebuffer } 302 | 303 | func (fbo *Framebuffer) Create(ctx gl.Context) { 304 | fbo.Framebuffer = ctx.CreateFramebuffer() 305 | } 306 | 307 | func (fbo Framebuffer) Delete(ctx gl.Context) { 308 | ctx.DeleteFramebuffer(fbo.Framebuffer) 309 | } 310 | 311 | func (fbo Framebuffer) Bind(ctx gl.Context, options ...func(gl.Context, Framebuffer)) { 312 | ctx.BindFramebuffer(gl.FRAMEBUFFER, fbo.Framebuffer) 313 | for _, opt := range options { 314 | opt(ctx, fbo) 315 | } 316 | } 317 | 318 | func (fbo Framebuffer) Unbind(ctx gl.Context) { 319 | ctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0}) 320 | } 321 | 322 | func FramebufferTex(tex Texture, lvl int) func(gl.Context, Framebuffer) { 323 | return func(ctx gl.Context, fbo Framebuffer) { 324 | ctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex.Texture, lvl) 325 | } 326 | } 327 | 328 | func FramebufferWithTex(tex Texture, lvl int, options ...func(gl.Context, Texture)) func(gl.Context, Framebuffer) { 329 | fbotex := FramebufferTex(tex, lvl) 330 | return func(ctx gl.Context, fbo Framebuffer) { 331 | tex.Bind(ctx, options...) 332 | fbotex(ctx, fbo) 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /color.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | type Color uint32 4 | 5 | // RGBA returns the unit value of each component. 6 | func (c Color) RGBA() (r, g, b, a float32) { 7 | ur := uint8(c >> 24) 8 | ug := uint8(c >> 16) 9 | ub := uint8(c >> 8) 10 | ua := uint8(c) 11 | r, g, b, a = float32(ur)/255, float32(ug)/255, float32(ub)/255, float32(ua)/255 12 | return 13 | } 14 | 15 | func (c Color) RGBA64() (r, g, b, a float64) { 16 | ur := uint8(c >> 24) 17 | ug := uint8(c >> 16) 18 | ub := uint8(c >> 8) 19 | ua := uint8(c) 20 | r, g, b, a = float64(ur)/255, float64(ug)/255, float64(ub)/255, float64(ua)/255 21 | return 22 | } 23 | 24 | func (c Color) WithAlpha(a uint8) Color { 25 | return c ^ Color(255-a) 26 | } 27 | 28 | const ( 29 | RedPrimary Color = Red500 30 | Red50 Color = 0xFFEBEEFF 31 | Red100 Color = 0xFFCDD2FF 32 | Red200 Color = 0xEF9A9AFF 33 | Red300 Color = 0xE57373FF 34 | Red400 Color = 0xEF5350FF 35 | Red500 Color = 0xF44336FF 36 | Red600 Color = 0xE53935FF 37 | Red700 Color = 0xD32F2FFF 38 | Red800 Color = 0xC62828FF 39 | Red900 Color = 0xB71C1CFF 40 | RedA100 Color = 0xFF8A80FF 41 | RedA200 Color = 0xFF5252FF 42 | RedA400 Color = 0xFF1744FF 43 | RedA700 Color = 0xD50000FF 44 | 45 | PinkPrimary Color = Pink500 46 | Pink50 Color = 0xFCE4ECFF 47 | Pink100 Color = 0xF8BBD0FF 48 | Pink200 Color = 0xF48FB1FF 49 | Pink300 Color = 0xF06292FF 50 | Pink400 Color = 0xEC407AFF 51 | Pink500 Color = 0xE91E63FF 52 | Pink600 Color = 0xD81B60FF 53 | Pink700 Color = 0xC2185BFF 54 | Pink800 Color = 0xAD1457FF 55 | Pink900 Color = 0x880E4FFF 56 | PinkA100 Color = 0xFF80ABFF 57 | PinkA200 Color = 0xFF4081FF 58 | PinkA400 Color = 0xF50057FF 59 | PinkA700 Color = 0xC51162FF 60 | 61 | PurplePrimary Color = Purple500 62 | Purple50 Color = 0xF3E5F5FF 63 | Purple100 Color = 0xE1BEE7FF 64 | Purple200 Color = 0xCE93D8FF 65 | Purple300 Color = 0xBA68C8FF 66 | Purple400 Color = 0xAB47BCFF 67 | Purple500 Color = 0x9C27B0FF 68 | Purple600 Color = 0x8E24AAFF 69 | Purple700 Color = 0x7B1FA2FF 70 | Purple800 Color = 0x6A1B9AFF 71 | Purple900 Color = 0x4A148CFF 72 | PurpleA100 Color = 0xEA80FCFF 73 | PurpleA200 Color = 0xE040FBFF 74 | PurpleA400 Color = 0xD500F9FF 75 | PurpleA700 Color = 0xAA00FFFF 76 | 77 | DeepPurplePrimary Color = DeepPurple500 78 | DeepPurple50 Color = 0xEDE7F6FF 79 | DeepPurple100 Color = 0xD1C4E9FF 80 | DeepPurple200 Color = 0xB39DDBFF 81 | DeepPurple300 Color = 0x9575CDFF 82 | DeepPurple400 Color = 0x7E57C2FF 83 | DeepPurple500 Color = 0x673AB7FF 84 | DeepPurple600 Color = 0x5E35B1FF 85 | DeepPurple700 Color = 0x512DA8FF 86 | DeepPurple800 Color = 0x4527A0FF 87 | DeepPurple900 Color = 0x311B92FF 88 | DeepPurpleA100 Color = 0xB388FFFF 89 | DeepPurpleA200 Color = 0x7C4DFFFF 90 | DeepPurpleA400 Color = 0x651FFFFF 91 | DeepPurpleA700 Color = 0x6200EAFF 92 | 93 | IndigoPrimary Color = Indigo500 94 | Indigo50 Color = 0xE8EAF6FF 95 | Indigo100 Color = 0xC5CAE9FF 96 | Indigo200 Color = 0x9FA8DAFF 97 | Indigo300 Color = 0x7986CBFF 98 | Indigo400 Color = 0x5C6BC0FF 99 | Indigo500 Color = 0x3F51B5FF 100 | Indigo600 Color = 0x3949ABFF 101 | Indigo700 Color = 0x303F9FFF 102 | Indigo800 Color = 0x283593FF 103 | Indigo900 Color = 0x1A237EFF 104 | IndigoA100 Color = 0x8C9EFFFF 105 | IndigoA200 Color = 0x536DFEFF 106 | IndigoA400 Color = 0x3D5AFEFF 107 | IndigoA700 Color = 0x304FFEFF 108 | 109 | BluePrimary Color = Blue500 110 | Blue50 Color = 0xE3F2FDFF 111 | Blue100 Color = 0xBBDEFBFF 112 | Blue200 Color = 0x90CAF9FF 113 | Blue300 Color = 0x64B5F6FF 114 | Blue400 Color = 0x42A5F5FF 115 | Blue500 Color = 0x2196F3FF 116 | Blue600 Color = 0x1E88E5FF 117 | Blue700 Color = 0x1976D2FF 118 | Blue800 Color = 0x1565C0FF 119 | Blue900 Color = 0x0D47A1FF 120 | BlueA100 Color = 0x82B1FFFF 121 | BlueA200 Color = 0x448AFFFF 122 | BlueA400 Color = 0x2979FFFF 123 | BlueA700 Color = 0x2962FFFF 124 | 125 | LightBluePrimary Color = LightBlue500 126 | LightBlue50 Color = 0xE1F5FEFF 127 | LightBlue100 Color = 0xB3E5FCFF 128 | LightBlue200 Color = 0x81D4FAFF 129 | LightBlue300 Color = 0x4FC3F7FF 130 | LightBlue400 Color = 0x29B6F6FF 131 | LightBlue500 Color = 0x03A9F4FF 132 | LightBlue600 Color = 0x039BE5FF 133 | LightBlue700 Color = 0x0288D1FF 134 | LightBlue800 Color = 0x0277BDFF 135 | LightBlue900 Color = 0x01579BFF 136 | LightBlueA100 Color = 0x80D8FFFF 137 | LightBlueA200 Color = 0x40C4FFFF 138 | LightBlueA400 Color = 0x00B0FFFF 139 | LightBlueA700 Color = 0x0091EAFF 140 | 141 | CyanPrimary Color = Cyan500 142 | Cyan50 Color = 0xE0F7FAFF 143 | Cyan100 Color = 0xB2EBF2FF 144 | Cyan200 Color = 0x80DEEAFF 145 | Cyan300 Color = 0x4DD0E1FF 146 | Cyan400 Color = 0x26C6DAFF 147 | Cyan500 Color = 0x00BCD4FF 148 | Cyan600 Color = 0x00ACC1FF 149 | Cyan700 Color = 0x0097A7FF 150 | Cyan800 Color = 0x00838FFF 151 | Cyan900 Color = 0x006064FF 152 | CyanA100 Color = 0x84FFFFFF 153 | CyanA200 Color = 0x18FFFFFF 154 | CyanA400 Color = 0x00E5FFFF 155 | CyanA700 Color = 0x00B8D4FF 156 | 157 | TealPrimary Color = Teal500 158 | Teal50 Color = 0xE0F2F1FF 159 | Teal100 Color = 0xB2DFDBFF 160 | Teal200 Color = 0x80CBC4FF 161 | Teal300 Color = 0x4DB6ACFF 162 | Teal400 Color = 0x26A69AFF 163 | Teal500 Color = 0x009688FF 164 | Teal600 Color = 0x00897BFF 165 | Teal700 Color = 0x00796BFF 166 | Teal800 Color = 0x00695CFF 167 | Teal900 Color = 0x004D40FF 168 | TealA100 Color = 0xA7FFEBFF 169 | TealA200 Color = 0x64FFDAFF 170 | TealA400 Color = 0x1DE9B6FF 171 | TealA700 Color = 0x00BFA5FF 172 | 173 | GreenPrimary Color = Green500 174 | Green50 Color = 0xE8F5E9FF 175 | Green100 Color = 0xC8E6C9FF 176 | Green200 Color = 0xA5D6A7FF 177 | Green300 Color = 0x81C784FF 178 | Green400 Color = 0x66BB6AFF 179 | Green500 Color = 0x4CAF50FF 180 | Green600 Color = 0x43A047FF 181 | Green700 Color = 0x388E3CFF 182 | Green800 Color = 0x2E7D32FF 183 | Green900 Color = 0x1B5E20FF 184 | GreenA100 Color = 0xB9F6CAFF 185 | GreenA200 Color = 0x69F0AEFF 186 | GreenA400 Color = 0x00E676FF 187 | GreenA700 Color = 0x00C853FF 188 | 189 | LightGreenPrimary Color = LightGreen500 190 | LightGreen50 Color = 0xF1F8E9FF 191 | LightGreen100 Color = 0xDCEDC8FF 192 | LightGreen200 Color = 0xC5E1A5FF 193 | LightGreen300 Color = 0xAED581FF 194 | LightGreen400 Color = 0x9CCC65FF 195 | LightGreen500 Color = 0x8BC34AFF 196 | LightGreen600 Color = 0x7CB342FF 197 | LightGreen700 Color = 0x689F38FF 198 | LightGreen800 Color = 0x558B2FFF 199 | LightGreen900 Color = 0x33691EFF 200 | LightGreenA100 Color = 0xCCFF90FF 201 | LightGreenA200 Color = 0xB2FF59FF 202 | LightGreenA400 Color = 0x76FF03FF 203 | LightGreenA700 Color = 0x64DD17FF 204 | 205 | LimePrimary Color = Lime500 206 | Lime50 Color = 0xF9FBE7FF 207 | Lime100 Color = 0xF0F4C3FF 208 | Lime200 Color = 0xE6EE9CFF 209 | Lime300 Color = 0xDCE775FF 210 | Lime400 Color = 0xD4E157FF 211 | Lime500 Color = 0xCDDC39FF 212 | Lime600 Color = 0xC0CA33FF 213 | Lime700 Color = 0xAFB42BFF 214 | Lime800 Color = 0x9E9D24FF 215 | Lime900 Color = 0x827717FF 216 | LimeA100 Color = 0xF4FF81FF 217 | LimeA200 Color = 0xEEFF41FF 218 | LimeA400 Color = 0xC6FF00FF 219 | LimeA700 Color = 0xAEEA00FF 220 | 221 | YellowPrimary Color = Yellow500 222 | Yellow50 Color = 0xFFFDE7FF 223 | Yellow100 Color = 0xFFF9C4FF 224 | Yellow200 Color = 0xFFF59DFF 225 | Yellow300 Color = 0xFFF176FF 226 | Yellow400 Color = 0xFFEE58FF 227 | Yellow500 Color = 0xFFEB3BFF 228 | Yellow600 Color = 0xFDD835FF 229 | Yellow700 Color = 0xFBC02DFF 230 | Yellow800 Color = 0xF9A825FF 231 | Yellow900 Color = 0xF57F17FF 232 | YellowA100 Color = 0xFFFF8DFF 233 | YellowA200 Color = 0xFFFF00FF 234 | YellowA400 Color = 0xFFEA00FF 235 | YellowA700 Color = 0xFFD600FF 236 | 237 | AmberPrimary Color = Amber500 238 | Amber50 Color = 0xFFF8E1FF 239 | Amber100 Color = 0xFFECB3FF 240 | Amber200 Color = 0xFFE082FF 241 | Amber300 Color = 0xFFD54FFF 242 | Amber400 Color = 0xFFCA28FF 243 | Amber500 Color = 0xFFC107FF 244 | Amber600 Color = 0xFFB300FF 245 | Amber700 Color = 0xFFA000FF 246 | Amber800 Color = 0xFF8F00FF 247 | Amber900 Color = 0xFF6F00FF 248 | AmberA100 Color = 0xFFE57FFF 249 | AmberA200 Color = 0xFFD740FF 250 | AmberA400 Color = 0xFFC400FF 251 | AmberA700 Color = 0xFFAB00FF 252 | 253 | OrangePrimary Color = Orange500 254 | Orange50 Color = 0xFFF3E0FF 255 | Orange100 Color = 0xFFE0B2FF 256 | Orange200 Color = 0xFFCC80FF 257 | Orange300 Color = 0xFFB74DFF 258 | Orange400 Color = 0xFFA726FF 259 | Orange500 Color = 0xFF9800FF 260 | Orange600 Color = 0xFB8C00FF 261 | Orange700 Color = 0xF57C00FF 262 | Orange800 Color = 0xEF6C00FF 263 | Orange900 Color = 0xE65100FF 264 | OrangeA100 Color = 0xFFD180FF 265 | OrangeA200 Color = 0xFFAB40FF 266 | OrangeA400 Color = 0xFF9100FF 267 | OrangeA700 Color = 0xFF6D00FF 268 | 269 | DeepOrangePrimary Color = DeepOrange500 270 | DeepOrange50 Color = 0xFBE9E7FF 271 | DeepOrange100 Color = 0xFFCCBCFF 272 | DeepOrange200 Color = 0xFFAB91FF 273 | DeepOrange300 Color = 0xFF8A65FF 274 | DeepOrange400 Color = 0xFF7043FF 275 | DeepOrange500 Color = 0xFF5722FF 276 | DeepOrange600 Color = 0xF4511EFF 277 | DeepOrange700 Color = 0xE64A19FF 278 | DeepOrange800 Color = 0xD84315FF 279 | DeepOrange900 Color = 0xBF360CFF 280 | DeepOrangeA100 Color = 0xFF9E80FF 281 | DeepOrangeA200 Color = 0xFF6E40FF 282 | DeepOrangeA400 Color = 0xFF3D00FF 283 | DeepOrangeA700 Color = 0xDD2C00FF 284 | 285 | BrownPrimary Color = Brown500 286 | Brown50 Color = 0xEFEBE9FF 287 | Brown100 Color = 0xD7CCC8FF 288 | Brown200 Color = 0xBCAAA4FF 289 | Brown300 Color = 0xA1887FFF 290 | Brown400 Color = 0x8D6E63FF 291 | Brown500 Color = 0x795548FF 292 | Brown600 Color = 0x6D4C41FF 293 | Brown700 Color = 0x5D4037FF 294 | Brown800 Color = 0x4E342EFF 295 | Brown900 Color = 0x3E2723FF 296 | 297 | GreyPrimary Color = Grey500 298 | Grey50 Color = 0xFAFAFAFF 299 | Grey100 Color = 0xF5F5F5FF 300 | Grey200 Color = 0xEEEEEEFF 301 | Grey300 Color = 0xE0E0E0FF 302 | Grey400 Color = 0xBDBDBDFF 303 | Grey500 Color = 0x9E9E9EFF 304 | Grey600 Color = 0x757575FF 305 | Grey700 Color = 0x616161FF 306 | Grey800 Color = 0x424242FF 307 | Grey900 Color = 0x212121FF 308 | 309 | BlueGreyPrimary Color = BlueGrey500 310 | BlueGrey50 Color = 0xECEFF1FF 311 | BlueGrey100 Color = 0xCFD8DCFF 312 | BlueGrey200 Color = 0xB0BEC5FF 313 | BlueGrey300 Color = 0x90A4AEFF 314 | BlueGrey400 Color = 0x78909CFF 315 | BlueGrey500 Color = 0x607D8BFF 316 | BlueGrey600 Color = 0x546E7AFF 317 | BlueGrey700 Color = 0x455A64FF 318 | BlueGrey800 Color = 0x37474FFF 319 | BlueGrey900 Color = 0x263238FF 320 | 321 | Black Color = 0x000000FF 322 | White Color = 0xFFFFFFFF 323 | ) 324 | 325 | type Palette struct { 326 | Primary, Dark, Light Color 327 | Accent Color 328 | } 329 | -------------------------------------------------------------------------------- /text/gen.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "flag" 8 | "fmt" 9 | "image" 10 | "image/color" 11 | "image/draw" 12 | "image/png" 13 | "io/ioutil" 14 | "log" 15 | "math" 16 | "os" 17 | "path/filepath" 18 | "sort" 19 | "sync" 20 | 21 | "github.com/golang/freetype/truetype" 22 | "github.com/nfnt/resize" 23 | "golang.org/x/image/font" 24 | "golang.org/x/image/math/fixed" 25 | ) 26 | 27 | var ( 28 | flagOutdir = flag.String("outdir", ".", "directory to write generated files") 29 | flagPkgname = flag.String("pkgname", "text", "package name of generated go source files") 30 | flagFontfile = flag.String("fontfile", "", "filename of the ttf font") 31 | flagTSize = flag.Int("tsize", 2048, "width and height of texture; 0 means pick smallest power of 2") 32 | flagFSize = flag.Float64("fsize", 72, "font size in points") 33 | flagPad = flag.Int("pad", 4, "amounted of padding for calculating sdf") 34 | flagScale = flag.Int("scale", 1, "scale inputs for calculating sdf, linear resizing final ouput to inputs") 35 | flagBorder = flag.Int("border", 1, "space around glyph") 36 | flagAscii = flag.Bool("ascii", false, "only process ascii glyphs") 37 | ) 38 | 39 | var ascii = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/? " 40 | 41 | const EdgeAlpha = 0x7f 42 | 43 | type SDF struct { 44 | src *image.NRGBA // where glyphs are first written 45 | dst *image.NRGBA // where sdf calculation is written 46 | out *image.NRGBA // final output, scaled if needed 47 | 48 | tsize int 49 | fsize float64 50 | pad int 51 | border int 52 | 53 | padr int 54 | } 55 | 56 | func NewSDF(textureSize int, fontSize float64, pad int, scale int, border int) *SDF { 57 | sdf := &SDF{ 58 | tsize: textureSize * scale, 59 | fsize: fontSize * float64(scale), 60 | pad: pad * scale, 61 | padr: pad, 62 | border: border * scale, 63 | } 64 | 65 | sdf.src = image.NewNRGBA(image.Rect(0, 0, sdf.tsize, sdf.tsize)) 66 | sdf.dst = image.NewNRGBA(image.Rect(0, 0, sdf.tsize, sdf.tsize)) 67 | if scale > 1 { 68 | sdf.out = image.NewNRGBA(image.Rect(0, 0, textureSize, textureSize)) 69 | } else { 70 | sdf.out = image.NewNRGBA(image.Rect(0, 0, sdf.tsize, sdf.tsize)) 71 | } 72 | 73 | return sdf 74 | } 75 | 76 | func (sdf *SDF) writeSrc() { 77 | out, err := os.Create(filepath.Join(*flagOutdir, "src.png")) 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | defer out.Close() 82 | 83 | if err := png.Encode(out, sdf.src); err != nil { 84 | log.Fatal(err) 85 | } 86 | } 87 | 88 | func (sdf *SDF) writeDst() { 89 | out, err := os.Create(filepath.Join(*flagOutdir, "dst.png")) 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | defer out.Close() 94 | 95 | if err := png.Encode(out, sdf.dst); err != nil { 96 | log.Fatal(err) 97 | } 98 | } 99 | 100 | func (sdf *SDF) writeOut() { 101 | out, err := os.Create(filepath.Join(*flagOutdir, "out.png")) 102 | if err != nil { 103 | log.Fatal(err) 104 | } 105 | defer out.Close() 106 | 107 | if sdf.dst.Bounds().Eq(sdf.out.Bounds()) { 108 | draw.Draw(sdf.out, sdf.out.Bounds(), sdf.dst, image.ZP, draw.Src) 109 | } else { 110 | b := sdf.out.Bounds() 111 | // TODO get back to bilinear. Originally did bilinear with alpha only image which 112 | // works well, but this bilinear filter with the new color images doesn't preserve 113 | // colors well. Could be NRGBA use, could just be the lib, but gimp does what's desired 114 | // with linear filter on resize. 115 | rs := resize.Resize(uint(b.Dx()), uint(b.Dy()), sdf.dst, resize.Bilinear) 116 | draw.Draw(sdf.out, sdf.out.Bounds(), rs, image.ZP, draw.Over) 117 | } 118 | 119 | if err := png.Encode(out, sdf.out); err != nil { 120 | log.Fatal(err) 121 | } 122 | } 123 | 124 | func (sdf *SDF) calc(m image.Image) { 125 | max := dist(0, 0, sdf.pad, sdf.pad) - 1 126 | b := m.Bounds() 127 | for y := b.Min.Y; y < b.Max.Y; y++ { 128 | for x := b.Min.X; x < b.Max.X; x++ { 129 | ma := m.At(x, y).(color.NRGBA).A 130 | c := nearest(x, y, m.(*image.NRGBA).SubImage(image.Rect(x-sdf.pad, y-sdf.pad, x+sdf.pad, y+sdf.pad))) 131 | if c == 0xFF { 132 | // check if pixel is inside as a center of opposing edges 133 | if ma != 0 { 134 | sdf.dst.Set(x, y, color.RGBA{A: 0xFF}) 135 | // sdf.dst.Set(x, y, color.White) 136 | } 137 | continue 138 | } 139 | 140 | // return from nearest is always >= 1 141 | // decrement so that c/max returns a unit value inclusive of zero 142 | c-- 143 | 144 | n := 0xFF * (1 - (float64(c) / float64(max))) 145 | if ma != 0 { // inside edge 146 | sdf.dst.Set(x, y, color.RGBA{A: 0xFF - uint8(n/2)}) 147 | } else { // outside edge 148 | step := float64(0xFF) / float64(max) 149 | if n = n - step; n < 0 { 150 | n = 0 151 | } 152 | sdf.dst.Set(x, y, color.RGBA{A: uint8(n / 2)}) 153 | } 154 | } 155 | } 156 | } 157 | 158 | // nearest returns the distance to the closest pixel of 159 | // opposite color from (mx, my) in a subspace. 160 | func nearest(mx, my int, m image.Image) float64 { 161 | var min float64 = 0xFF 162 | ma := m.At(mx, my).(color.NRGBA).A 163 | b := m.Bounds() 164 | for y := b.Min.Y; y < b.Max.Y; y++ { 165 | for x := b.Min.X; x < b.Max.X; x++ { 166 | a := m.At(x, y).(color.NRGBA).A 167 | // check against zero guards against bad input 168 | // to consistently give the desired 1px border. 169 | if (ma == 0 && a != 0) || (ma != 0 && a == 0) { // implicitly prevents check against itself 170 | dt := dist(mx, my, x, y) 171 | if min > dt { 172 | min = dt 173 | } 174 | if min == 1 { // minimum-bound reached, return early 175 | return min 176 | } 177 | } 178 | } 179 | } 180 | return min 181 | } 182 | 183 | // dist returns distance between two points. 184 | func dist(x0, y0, x1, y1 int) float64 { 185 | x, y := x1-x0, y1-y0 186 | return math.Sqrt(float64(x*x + y*y)) 187 | } 188 | 189 | type glyph struct { 190 | r rune 191 | b fixed.Rectangle26_6 192 | a fixed.Int26_6 193 | tc [4]float32 194 | } 195 | 196 | func (g *glyph) width() int { return (g.b.Max.X - g.b.Min.X).Ceil() } 197 | func (g *glyph) height() int { return (g.b.Max.Y - g.b.Min.Y).Ceil() } 198 | 199 | // area total all glyphs occupy. 200 | func area(a []*glyph, pad int) int { 201 | var n int 202 | for _, g := range a { 203 | n += (g.width() + pad*2) * (g.height() + pad*2) 204 | } 205 | return n 206 | } 207 | 208 | // enumerate returns all glyphs with a valid index in font. 209 | func enumerate(f *truetype.Font, fc font.Face) []*glyph { 210 | var gs []*glyph 211 | for r := rune(1); r < (1<<16)-1; r++ { 212 | if r == '\uFEFF' { 213 | continue // ignore BOM 214 | } 215 | if f.Index(r) != 0 { 216 | b, a, _ := fc.GlyphBounds(r) 217 | gs = append(gs, &glyph{r: r, b: b, a: a}) 218 | } 219 | } 220 | return gs 221 | } 222 | 223 | func enumerateString(s string, fc font.Face) []*glyph { 224 | var gs []*glyph 225 | for _, r := range s { 226 | if b, a, ok := fc.GlyphBounds(r); ok { 227 | gs = append(gs, &glyph{r: r, b: b, a: a}) 228 | } 229 | } 230 | return gs 231 | } 232 | 233 | // byHeight sorts glyphs tallest to shortest. 234 | type byHeight []*glyph 235 | 236 | func (a byHeight) Len() int { return len(a) } 237 | func (a byHeight) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 238 | func (a byHeight) Less(i, j int) bool { return a[j].height() < a[i].height() } 239 | 240 | func main() { 241 | flag.Parse() 242 | 243 | if *flagFontfile == "" || *flagOutdir == "" || *flagPkgname == "" { 244 | flag.Usage() 245 | return 246 | } 247 | 248 | if *flagScale < 1 { 249 | log.Println("scale must be >= 1") 250 | return 251 | } 252 | 253 | bin, err := ioutil.ReadFile(*flagFontfile) 254 | if err != nil { 255 | log.Println(err) 256 | return 257 | } 258 | f, err := truetype.Parse(bin) 259 | if err != nil { 260 | log.Println(err) 261 | return 262 | } 263 | 264 | sdf := NewSDF(*flagTSize, *flagFSize, *flagPad, *flagScale, *flagBorder) 265 | d := &font.Drawer{ 266 | Dst: sdf.src, 267 | Src: image.Black, 268 | Face: truetype.NewFace(f, &truetype.Options{ 269 | Size: sdf.fsize, 270 | Hinting: font.HintingFull, 271 | }), 272 | } 273 | 274 | var glyphs []*glyph 275 | if *flagAscii { 276 | glyphs = enumerateString(ascii, d.Face) 277 | } else { 278 | glyphs = enumerate(f, d.Face) 279 | } 280 | if len(glyphs) == 0 { 281 | log.Fatalf("sdf: failed to enumerate glyphs from %s\n", *flagFontfile) 282 | } 283 | if a := area(glyphs, sdf.pad); a > sdf.tsize*sdf.tsize { 284 | asq := math.Sqrt(float64(a)) 285 | log.Fatalf("sdf: glyphs area %[1]v ~= %.2[2]fx%.2[2]f greater than texture area %[3]vx%[3]v\n", a, asq, sdf.tsize) 286 | } 287 | sort.Sort(byHeight(glyphs)) 288 | 289 | x, y, dy := 0, 0, glyphs[0].height()+sdf.pad*2+sdf.border*2 290 | var wg sync.WaitGroup 291 | for _, g := range glyphs { 292 | adx, ady := g.width()+sdf.pad*2+sdf.border*2, g.height()+sdf.pad*2+sdf.border*2 293 | if x+adx > sdf.tsize { 294 | x = 0 295 | y += dy 296 | dy = ady 297 | } 298 | 299 | g.tc = [4]float32{ 300 | // float32(x+sdf.pad+sdf.border) / float32(sdf.tsize), 301 | // float32(y+sdf.pad+sdf.border) / float32(sdf.tsize), 302 | float32(x) / float32(sdf.tsize), 303 | float32(y) / float32(sdf.tsize), 304 | float32(g.width()+sdf.pad*2+sdf.border*2) / float32(sdf.tsize), 305 | float32(g.height()+sdf.pad*2+sdf.border*2) / float32(sdf.tsize), 306 | } 307 | 308 | d.Dot = fixed.P(x+sdf.pad+sdf.border-int(g.b.Min.X>>6), y+sdf.pad+sdf.border-g.b.Min.Y.Ceil()) 309 | d.DrawString(string(g.r)) 310 | 311 | wg.Add(1) 312 | go func(m image.Image) { 313 | sdf.calc(m) 314 | wg.Done() 315 | }(sdf.src.SubImage(image.Rect(x, y, x+adx, y+ady))) // TODO workout border issues; then stop passing in dead space to sdf calc 316 | 317 | x += adx 318 | } 319 | wg.Wait() 320 | 321 | sdf.writeSrc() 322 | sdf.writeDst() 323 | sdf.writeOut() 324 | 325 | // generate source file to accompany out.png 326 | buf := new(bytes.Buffer) 327 | buf.WriteString("// generated by gen.go; DO NOT EDIT\n") 328 | fmt.Fprintf(buf, "package %s\n\n", *flagPkgname) 329 | 330 | ascent := float32(d.Face.Metrics().Ascent.Ceil()) 331 | descent := float32(d.Face.Metrics().Descent.Floor()) 332 | scale := float32(sdf.fsize) / (ascent + descent) 333 | fmt.Fprintf(buf, "const AscentUnit = %v\n", (ascent*scale)/float32(sdf.fsize)) 334 | fmt.Fprintf(buf, "const DescentUnit = %v\n", (descent*scale)/float32(sdf.fsize)) 335 | fmt.Fprintf(buf, "const TextureSize = %v\n", *flagTSize) 336 | fmt.Fprintf(buf, "const FontSize = %v\n", *flagFSize) 337 | fmt.Fprintf(buf, "const Pad = %v\n\n", *flagPad) 338 | 339 | buf.WriteString("var Texcoords = map[rune][4]float32{\n") 340 | for _, g := range glyphs { 341 | s := string(g.r) 342 | if s == "'" { 343 | s = `\'` 344 | } else if s == "\\" { 345 | s = `\\` 346 | } 347 | fmt.Fprintf(buf, "\t'%s': {%v, %v, %v, %v},\n", s, g.tc[0], g.tc[1], g.tc[2], g.tc[3]) 348 | } 349 | buf.WriteString("}\n\n") 350 | 351 | buf.WriteString("var Bounds = map[rune][5]float32{\n") 352 | for _, g := range glyphs { 353 | s := string(g.r) 354 | if s == "'" { 355 | s = `\'` 356 | } else if s == "\\" { 357 | s = `\\` 358 | } 359 | nx := float32(g.b.Min.X>>6) / float32(sdf.fsize) 360 | ny := float32(g.b.Max.Y>>6) / float32(sdf.fsize) 361 | rect := g.b.Max.Sub(g.b.Min) 362 | w, h := float32(rect.X>>6), float32(rect.Y>>6) 363 | nw := float32(w) / float32(sdf.fsize) 364 | nh := float32(h) / float32(sdf.fsize) 365 | na := float32(g.a>>6) / float32(sdf.fsize) 366 | fmt.Fprintf(buf, "\t'%s': {%v, %v, %v, %v, %v},\n", s, nx, ny, nw, nh, na) 367 | } 368 | buf.WriteString("}\n\n") 369 | 370 | if err := ioutil.WriteFile(filepath.Join(*flagOutdir, "texcoords.go"), buf.Bytes(), 0644); err != nil { 371 | log.Fatal(err) 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /environment.go: -------------------------------------------------------------------------------- 1 | package material 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "image" 7 | "image/draw" 8 | "log" 9 | "sort" 10 | "time" 11 | "unicode" 12 | 13 | _ "image/gif" 14 | _ "image/jpeg" 15 | _ "image/png" 16 | 17 | _ "golang.org/x/image/bmp" 18 | _ "golang.org/x/image/tiff" 19 | _ "golang.org/x/image/webp" 20 | 21 | "dasa.cc/material/assets" 22 | "dasa.cc/material/glutil" 23 | "dasa.cc/material/icon" 24 | "dasa.cc/material/text" 25 | "dasa.cc/simplex" 26 | 27 | "golang.org/x/mobile/event/size" 28 | "golang.org/x/mobile/event/touch" 29 | "golang.org/x/mobile/exp/f32" 30 | "golang.org/x/mobile/gl" 31 | ) 32 | 33 | type Dp float32 34 | 35 | func (dp Dp) Px() float32 { 36 | if windowSize.PixelsPerPt == 1 { 37 | return float32(dp) 38 | } 39 | density := windowSize.PixelsPerPt * 72 40 | return float32(dp) * density / 160 41 | } 42 | 43 | type Sheet interface { 44 | // Draw(ctx gl.Context, view, proj f32.Mat4) 45 | Bind(*simplex.Program) 46 | UpdateWorld(*simplex.Program) 47 | Contains(x, y float32) bool 48 | M() *Material 49 | Constraints(*Environment) []simplex.Constraint 50 | Hidden() bool 51 | } 52 | 53 | type byZ []Sheet 54 | 55 | func (a byZ) Len() int { return len(a) } 56 | func (a byZ) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 57 | func (a byZ) Less(i, j int) bool { return a[i].M().world[2][3] < a[j].M().world[2][3] } 58 | 59 | type Environment struct { 60 | View f32.Mat4 61 | 62 | proj f32.Mat4 63 | plt Palette 64 | sheets []Sheet 65 | 66 | Box Box 67 | Grid *Grid 68 | 69 | lprg *simplex.Program 70 | 71 | icons glutil.Texture 72 | glyphs glutil.Texture 73 | 74 | image glutil.Texture 75 | 76 | prg glutil.Program 77 | 78 | uniforms struct { 79 | view, proj, shadowColor gl.Uniform 80 | glyphs, icons, image gl.Uniform 81 | glyphconf gl.Uniform 82 | } 83 | 84 | attribs struct { 85 | vertex, color, dist, texcoord gl.Attrib 86 | touch gl.Attrib 87 | } 88 | 89 | buffers struct { 90 | verts, colors, dists, texcoords glutil.FloatBuffer 91 | indices glutil.UintBuffer 92 | touches glutil.FloatBuffer 93 | } 94 | 95 | verts, colors, dists, texcoords []float32 96 | indices []uint32 97 | touches []float32 98 | 99 | watchEvent chan string 100 | watchQuit chan bool 101 | } 102 | 103 | func (env *Environment) Proj() f32.Mat4 { return env.proj } 104 | 105 | func (env *Environment) Size() size.Event { 106 | return windowSize 107 | } 108 | 109 | func (env *Environment) WatchShaders() { 110 | env.watchEvent, env.watchQuit = watchShaders() 111 | } 112 | 113 | func (env *Environment) LoadIcons(ctx gl.Context) { 114 | src, _, err := image.Decode(glutil.MustOpen("material/material-icons-black-mdpi.png")) 115 | if err != nil { 116 | log.Fatal(err) 117 | } 118 | 119 | r := image.Rect(0, 0, 2048, 2048) 120 | dst := image.NewNRGBA(r) 121 | // pt := image.Point{0, -(2048 - src.Bounds().Size().Y)} 122 | draw.Draw(dst, r, src, image.ZP, draw.Src) 123 | 124 | // f, _ := os.Create("debug-icons.png") 125 | // png.Encode(f, dst) 126 | 127 | env.icons.Create(ctx) 128 | env.icons.Bind(ctx, nearestFilter, DefaultWrap) 129 | env.icons.Update(ctx, 0, 2048, 2048, dst.Pix) 130 | } 131 | 132 | var ( 133 | imageSize image.Point 134 | imageTextureSize = 2048 135 | ) 136 | 137 | func (env *Environment) LoadImage(ctx gl.Context, name string) image.Point { 138 | src, _, err := image.Decode(glutil.MustOpen(name)) 139 | if err != nil { 140 | log.Fatal(err) 141 | } 142 | 143 | imageSize = src.Bounds().Size() 144 | r := image.Rect(0, 0, imageTextureSize, imageTextureSize) 145 | dst := image.NewNRGBA(r) 146 | draw.Draw(dst, r, src, image.ZP, draw.Src) 147 | 148 | env.image.Create(ctx) 149 | env.image.Bind(ctx, nearestFilter, DefaultWrap) 150 | env.image.Update(ctx, 0, 2048, 2048, dst.Pix) 151 | 152 | return imageSize 153 | } 154 | 155 | func (env *Environment) LoadGlyphs(ctx gl.Context) { 156 | src, _, err := image.Decode(bytes.NewReader(text.Texture)) // image.Decode(glutil.MustOpen("material/glyphs.png")) 157 | if err != nil { 158 | log.Fatal(err) 159 | } 160 | env.glyphs.Create(ctx) 161 | env.glyphs.Bind(ctx, nearestFilter, DefaultWrap) 162 | switch src.(type) { 163 | case *image.RGBA: 164 | env.glyphs.Update(ctx, 0, text.TextureSize, text.TextureSize, src.(*image.RGBA).Pix) 165 | case *image.NRGBA: 166 | env.glyphs.Update(ctx, 0, text.TextureSize, text.TextureSize, src.(*image.NRGBA).Pix) 167 | default: 168 | panic(fmt.Errorf("Unhandled image type %T", src)) 169 | } 170 | } 171 | 172 | func (env *Environment) Load(ctx gl.Context) { 173 | env.prg.CreateAndLink(ctx, 174 | glutil.ShaderCompile(gl.VERTEX_SHADER, "env-vert.glsl", assets.VertexShader), 175 | glutil.ShaderCompile(gl.FRAGMENT_SHADER, "env-frag.glsl", assets.FragmentShader)) 176 | 177 | env.uniforms.view = env.prg.Uniform(ctx, "view") 178 | env.uniforms.proj = env.prg.Uniform(ctx, "proj") 179 | env.uniforms.shadowColor = env.prg.Uniform(ctx, "shadowColor") 180 | env.uniforms.glyphs = env.prg.Uniform(ctx, "texglyph") 181 | env.uniforms.icons = env.prg.Uniform(ctx, "texicon") 182 | env.uniforms.glyphconf = env.prg.Uniform(ctx, "glyphconf") 183 | env.uniforms.image = env.prg.Uniform(ctx, "image") 184 | 185 | env.attribs.vertex = env.prg.Attrib(ctx, "vertex") 186 | env.attribs.color = env.prg.Attrib(ctx, "color") 187 | env.attribs.dist = env.prg.Attrib(ctx, "dist") 188 | env.attribs.texcoord = env.prg.Attrib(ctx, "texcoord") 189 | env.attribs.touch = env.prg.Attrib(ctx, "touch") 190 | 191 | env.buffers.indices = glutil.NewUintBuffer(ctx, []uint32{}, gl.STREAM_DRAW) 192 | env.buffers.verts = glutil.NewFloatBuffer(ctx, []float32{}, gl.STREAM_DRAW) 193 | env.buffers.colors = glutil.NewFloatBuffer(ctx, []float32{}, gl.STREAM_DRAW) 194 | env.buffers.dists = glutil.NewFloatBuffer(ctx, []float32{}, gl.STREAM_DRAW) 195 | env.buffers.texcoords = glutil.NewFloatBuffer(ctx, []float32{}, gl.STREAM_DRAW) 196 | env.buffers.touches = glutil.NewFloatBuffer(ctx, []float32{}, gl.STREAM_DRAW) 197 | } 198 | 199 | func (env *Environment) Unload(ctx gl.Context) { 200 | env.prg.Delete(ctx) 201 | env.buffers.indices.Delete(ctx) 202 | env.buffers.verts.Delete(ctx) 203 | env.buffers.colors.Delete(ctx) 204 | env.buffers.dists.Delete(ctx) 205 | env.buffers.texcoords.Delete(ctx) 206 | env.buffers.touches.Delete(ctx) 207 | if env.glyphs.Value != 0 { 208 | env.glyphs.Delete(ctx) 209 | } 210 | if env.icons.Value != 0 { 211 | env.icons.Delete(ctx) 212 | } 213 | env.sheets = env.sheets[:0] 214 | } 215 | 216 | func (env *Environment) SetPerspective(sz size.Event) { 217 | windowSize = sz 218 | env.Grid = NewGrid() 219 | env.View.Identity() // TODO not here, only on creation 220 | env.proj.Identity() 221 | glutil.Perspective(&env.proj, 0, float32(sz.WidthPx), 0, float32(sz.HeightPx)) 222 | } 223 | 224 | func (env *Environment) SetOrtho(sz size.Event) { 225 | windowSize = sz 226 | env.Grid = NewGrid() 227 | env.View.Identity() // TODO not here, only on creation 228 | env.proj.Identity() 229 | glutil.Ortho(&env.proj, 0, float32(sz.WidthPx), 0, float32(sz.HeightPx), 1, 10000) 230 | env.View.Translate(&env.View, 0, 0, -5000) 231 | } 232 | 233 | func (env *Environment) Palette() Palette { return env.plt } 234 | 235 | func (env *Environment) SetPalette(plt Palette) { 236 | env.plt = plt 237 | for _, sheet := range env.sheets { 238 | switch sheet := sheet.(type) { 239 | case *Button: 240 | sheet.SetColor(env.plt.Primary) 241 | case *Toolbar: 242 | sheet.SetColor(env.plt.Light) 243 | } 244 | } 245 | } 246 | 247 | func (env *Environment) StartLayout() { 248 | env.lprg = new(simplex.Program) 249 | env.Box = NewBox(env.lprg) 250 | for _, sheet := range env.sheets { 251 | sheet.Bind(env.lprg) 252 | } 253 | env.AddConstraints( 254 | env.Box.Width(float32(windowSize.WidthPx)), 255 | env.Box.Height(float32(windowSize.HeightPx)), 256 | env.Box.Z(0), 257 | env.Box.Start(0), env.Box.Top(float32(windowSize.HeightPx)), 258 | ) 259 | for _, sheet := range env.sheets { 260 | env.AddConstraints(sheet.Constraints(env)...) 261 | } 262 | } 263 | 264 | func (env *Environment) AddConstraints(cns ...simplex.Constraint) { 265 | env.lprg.AddConstraints(cns...) 266 | } 267 | 268 | func (env *Environment) FinishLayout() { 269 | if err := env.lprg.Minimize(); err != nil { 270 | log.Println(err) 271 | } 272 | for _, sheet := range env.sheets { 273 | sheet.UpdateWorld(env.lprg) 274 | } 275 | } 276 | 277 | func (env *Environment) Draw(ctx gl.Context) { 278 | select { 279 | case <-env.watchEvent: 280 | env.Load(ctx) 281 | default: 282 | } 283 | 284 | sort.Sort(byZ(env.sheets)) 285 | 286 | env.indices = env.indices[:0] 287 | env.verts = env.verts[:0] 288 | env.colors = env.colors[:0] 289 | env.dists = env.dists[:0] 290 | env.texcoords = env.texcoords[:0] 291 | env.touches = env.touches[:0] 292 | 293 | for i, sheet := range env.sheets { 294 | m := sheet.M() 295 | x, y, z := m.world[0][3], m.world[1][3], m.world[2][3] 296 | w, h := m.world[0][0], m.world[1][1] 297 | r := m.Roundness 298 | 299 | n := uint32(len(env.verts)) / 4 300 | 301 | if i != 0 { // degenerate triangles 302 | // TODO make sure last v2 matches based on how shadows are being added 303 | env.indices = append(env.indices, 304 | env.indices[len(env.indices)-2], env.indices[len(env.indices)-1], env.indices[len(env.indices)-1], 305 | env.indices[len(env.indices)-1], env.indices[len(env.indices)-1], n, 306 | ) 307 | } 308 | 309 | // *** shadow layer 310 | if m.BehaviorFlags&DescriptorRaised == DescriptorRaised { 311 | // (r/w) should be a value in range [0.0..0.5] given that a value of 0.5 312 | // is an ellipse/circle. mapping this range to [1..3]*z provides a decent 313 | // default for resizing ellipses shadows for visibility given current 314 | // algorithm in shader. 315 | // s := (1 + 6*(r/w)) + z // TODO this needs harder limits based on size of material 316 | s := 4 + z 317 | 318 | // min := h 319 | // if w < h { 320 | // min = w 321 | // } 322 | // _ = min 323 | 324 | // s += min / 32 325 | 326 | ss := 2 * s 327 | // TODO how should roundness scale 328 | rr := r * ((w + ss) / w) 329 | // rr += s 330 | 331 | // rr += min / 8 332 | 333 | // clamp rr for circular shadows 334 | if rr > (w+ss)/2 { 335 | rr = (w + ss) / 2 336 | } 337 | // if rr > (h+ss)/2 { 338 | // rr = (h + ss) / 2 339 | // } 340 | 341 | x -= s 342 | w += ss 343 | 344 | y -= s * 1.5 // offset shadow from material 345 | h += ss 346 | 347 | env.indices = append(env.indices, 348 | n, n+2, n+1, 349 | n, n+3, n+2, 350 | ) 351 | env.verts = append(env.verts, 352 | x, y, -z, rr, // v0 353 | x, y+h, -z, rr, // v1 354 | x+w, y+h, -z, rr, // v2 355 | x+w, y, -z, rr, // v3 356 | ) 357 | env.colors = append(env.colors, 358 | m.cr, m.cg, m.cb, m.ca, 359 | m.cr, m.cg, m.cb, m.ca, 360 | m.cr, m.cg, m.cb, m.ca, 361 | m.cr, m.cg, m.cb, m.ca, 362 | ) 363 | env.dists = append(env.dists, 364 | 0.0, 0.0, w, h, // v0 left, bottom 365 | 0.0, 1.0, w, h, // v1 left, top 366 | 1.0, 1.0, w, h, // v2 right, top 367 | 1.0, 0.0, w, h, // v3 right, bottom 368 | ) 369 | env.texcoords = append(env.texcoords, 370 | -1, -1, -1, -1, 371 | -1, -1, -1, -1, 372 | -1, -1, -1, -1, 373 | -1, -1, -1, -1, 374 | ) 375 | env.touches = append(env.touches, 376 | 0, 0, 2, 0, 377 | 0, 0, 2, 0, 378 | 0, 0, 2, 0, 379 | 0, 0, 2, 0, 380 | ) 381 | } 382 | // *** end shadow layer 383 | 384 | x, y, z = m.world[0][3], m.world[1][3], m.world[2][3] 385 | w, h = m.world[0][0], m.world[1][1] 386 | n = uint32(len(env.verts)) / 4 387 | 388 | // sin, cos := f32.Sin(m.Rotate), f32.Cos(m.Rotate) 389 | // fx, fy := m.world[0][0], m.world[1][1] 390 | // w = fx*cos - fy*sin 391 | // h = fx*sin + fy*cos 392 | 393 | env.indices = append(env.indices, 394 | n, n+2, n+1, n, n+3, n+2, 395 | n+2, n+7, n+6, n+2, n+3, n+7, 396 | n+7, n+3, n, n+7, n, n+4, 397 | n+4, n+6, n+7, n+4, n+5, n+6, 398 | n+6, n+1, n+2, n+6, n+5, n+1, 399 | n+1, n+5, n+4, n+1, n+4, n, 400 | ) 401 | env.verts = append(env.verts, 402 | x, y, z, r, 403 | x, y+h, z, r, 404 | x+w, y+h, z, r, 405 | x+w, y, z, r, 406 | x, y, z-1, r, 407 | x, y+h, z-1, r, 408 | x+w, y+h, z-1, r, 409 | x+w, y, z-1, r, 410 | ) 411 | 412 | alpha := float32(0) 413 | if m.BehaviorFlags&DescriptorRaised == DescriptorRaised { 414 | alpha = m.ca 415 | } 416 | env.colors = append(env.colors, 417 | m.cr, m.cg, m.cb, alpha, 418 | m.cr, m.cg, m.cb, alpha, 419 | m.cr, m.cg, m.cb, alpha, 420 | m.cr, m.cg, m.cb, alpha, 421 | 1, 1, 1, alpha, 422 | 1, 1, 1, alpha, 423 | 1, 1, 1, alpha, 424 | 1, 1, 1, alpha, 425 | ) 426 | env.dists = append(env.dists, 427 | 0.0, 0.0, w, h, // v0 left, bottom 428 | 0.0, 1.0, w, h, // v1 left, top 429 | 1.0, 1.0, w, h, // v2 right, top 430 | 1.0, 0.0, w, h, // v3 right, bottom 431 | 0.0, 0.0, w, h, // v0 left, bottom 432 | 0.0, 1.0, w, h, // v1 left, top 433 | 1.0, 1.0, w, h, // v2 right, top 434 | 1.0, 0.0, w, h, // v3 right, bottom 435 | ) 436 | env.texcoords = append(env.texcoords, 437 | -1, -1, -1, -1, 438 | -1, -1, -1, -1, 439 | -1, -1, -1, -1, 440 | -1, -1, -1, -1, 441 | -1, -1, -1, -1, 442 | -1, -1, -1, -1, 443 | -1, -1, -1, -1, 444 | -1, -1, -1, -1, 445 | ) 446 | 447 | ex, ey := m.touch.x, m.touch.y 448 | es := float32(m.touch.state) 449 | ed := float32(time.Since(m.touch.start) / time.Millisecond) 450 | env.touches = append(env.touches, 451 | ex, ey, es, ed, 452 | ex, ey, es, ed, 453 | ex, ey, es, ed, 454 | ex, ey, es, ed, 455 | ex, ey, es, ed, 456 | ex, ey, es, ed, 457 | ex, ey, es, ed, 458 | ex, ey, es, ed, 459 | ) 460 | 461 | if m.icon.x != -1 { 462 | n = uint32(len(env.verts)) / 4 463 | env.indices = append(env.indices, 464 | n, n+2, n+1, n, n+3, n+2, 465 | ) 466 | env.verts = append(env.verts, 467 | x, y, z, 0, 468 | x, y+h, z, 0, 469 | x+w, y+h, z, 0, 470 | x+w, y, z, 0, 471 | ) 472 | env.colors = append(env.colors, 473 | m.icon.r, m.icon.g, m.icon.b, m.icon.a, 474 | m.icon.r, m.icon.g, m.icon.b, m.icon.a, 475 | m.icon.r, m.icon.g, m.icon.b, m.icon.a, 476 | m.icon.r, m.icon.g, m.icon.b, m.icon.a, 477 | ) 478 | env.dists = append(env.dists, 479 | 0.0, 0.0, w, h, // v0 left, bottom 480 | 0.0, 1.0, w, h, // v1 left, top 481 | 1.0, 1.0, w, h, // v2 right, top 482 | 1.0, 0.0, w, h, // v3 right, bottom 483 | ) 484 | s := float32(0.0234375) 485 | ix, iy := m.icon.x, m.icon.y 486 | env.texcoords = append(env.texcoords, 487 | ix, iy+s, 1, 0, 488 | ix, iy, 1, 0, 489 | ix+s, iy, 1, 0, 490 | ix+s, iy+s, 1, 0, 491 | ) 492 | env.touches = append(env.touches, 493 | 0, 0, 2, 0, 494 | 0, 0, 2, 0, 495 | 0, 0, 2, 0, 496 | 0, 0, 2, 0, 497 | ) 498 | } 499 | 500 | if m.ShowImage { 501 | n = uint32(len(env.verts)) / 4 502 | env.indices = append(env.indices, 503 | n, n+2, n+1, n, n+3, n+2, 504 | ) 505 | env.verts = append(env.verts, 506 | x, y, z, 0, 507 | x, y+h, z, 0, 508 | x+w, y+h, z, 0, 509 | x+w, y, z, 0, 510 | ) 511 | env.colors = append(env.colors, 512 | 1, 1, 1, alpha, 513 | 1, 1, 1, alpha, 514 | 1, 1, 1, alpha, 515 | 1, 1, 1, alpha, 516 | ) 517 | env.dists = append(env.dists, 518 | 0.0, 0.0, w, h, // v0 left, bottom 519 | 0.0, 1.0, w, h, // v1 left, top 520 | 1.0, 1.0, w, h, // v2 right, top 521 | 1.0, 0.0, w, h, // v3 right, bottom 522 | ) 523 | 524 | // imgX, imgY = 1, 1 525 | // proportion that image occupies of actual texture 526 | // texture has to be like 2048x2048 527 | // so if image is 800x600 528 | // then max X value would be 800/2048 529 | // and max Y value would be 600/2048 530 | mX := float32(imageSize.X) / float32(imageTextureSize) 531 | mY := float32(imageSize.Y) / float32(imageTextureSize) 532 | env.texcoords = append(env.texcoords, 533 | 0, mY, 3, 0, 534 | 0, 0, 3, 0, 535 | mX, 0, 3, 0, 536 | mX, mY, 3, 0, 537 | ) 538 | env.touches = append(env.touches, 539 | 0, 0, 3, 0, 540 | 0, 0, 3, 0, 541 | 0, 0, 3, 0, 542 | 0, 0, 3, 0, 543 | ) 544 | } 545 | 546 | // draw text 547 | tx, ty := m.world[0][3], m.world[1][3] 548 | th := m.text.height 549 | if th == 0 { 550 | th = m.world[1][1] 551 | } 552 | 553 | pad := float32(text.Pad) * (th / text.FontSize) 554 | ty = ty + m.world[1][1] - (text.AscentUnit * th) 555 | 556 | for _, r := range m.text.value { 557 | a := text.Bounds[r] 558 | ax, ay, aw, ah, aa := a[0], a[1], a[2], a[3], a[4] 559 | ax *= th 560 | ay *= th 561 | aw *= th 562 | ah *= th 563 | aa *= th 564 | 565 | if unicode.IsSpace(r) { 566 | if r == '\n' { 567 | tx = m.world[0][3] 568 | ty -= (text.AscentUnit * th) 569 | } 570 | } else { 571 | n = uint32(len(env.verts)) / 4 572 | env.indices = append(env.indices, 573 | n, n+2, n+1, n, n+3, n+2, 574 | ) 575 | env.verts = append(env.verts, 576 | tx+ax-pad, ty-ay-pad, z, 0, // v0 577 | tx+ax-pad, ty-ay+ah+pad, z, 0, // v1 578 | tx+ax+aw+pad, ty-ay+ah+pad, z, 0, // v2 579 | tx+ax+aw+pad, ty-ay-pad, z, 0, // v3 580 | ) 581 | env.colors = append(env.colors, 582 | m.text.r, m.text.g, m.text.b, m.text.a, 583 | m.text.r, m.text.g, m.text.b, m.text.a, 584 | m.text.r, m.text.g, m.text.b, m.text.a, 585 | m.text.r, m.text.g, m.text.b, m.text.a, 586 | ) 587 | env.dists = append(env.dists, 588 | 0.0, 0.0, aw, th, 589 | 0.0, 1.0, aw, th, 590 | 1.0, 1.0, aw, th, 591 | 1.0, 0.0, aw, th, 592 | ) 593 | env.touches = append(env.touches, 594 | 0, 0, 2, 0, 595 | 0, 0, 2, 0, 596 | 0, 0, 2, 0, 597 | 0, 0, 2, 0, 598 | ) 599 | g := text.Texcoords[r] 600 | gx, gy, gw, gh := g[0], g[1], g[2], g[3] 601 | env.texcoords = append(env.texcoords, 602 | gx, gy+gh, 0, 0, 603 | gx, gy, 0, 0, 604 | gx+gw, gy, 0, 0, 605 | gx+gw, gy+gh, 0, 0, 606 | ) 607 | } 608 | 609 | tx += aa 610 | } 611 | } 612 | 613 | env.prg.Use(ctx) 614 | env.prg.Mat4(ctx, env.uniforms.view, env.View) 615 | env.prg.Mat4(ctx, env.uniforms.proj, env.proj) 616 | env.prg.U4f(ctx, env.uniforms.shadowColor, shdr, shdg, shdb, shda) 617 | env.prg.U4f(ctx, env.uniforms.glyphconf, text.FontSize, text.Pad, 0.5, 1) 618 | 619 | env.buffers.texcoords.Bind(ctx) 620 | env.buffers.texcoords.Update(ctx, env.texcoords) 621 | env.prg.Pointer(ctx, env.attribs.texcoord, 4) 622 | 623 | env.buffers.touches.Bind(ctx) 624 | env.buffers.touches.Update(ctx, env.touches) 625 | env.prg.Pointer(ctx, env.attribs.touch, 4) 626 | 627 | env.buffers.dists.Bind(ctx) 628 | env.buffers.dists.Update(ctx, env.dists) 629 | env.prg.Pointer(ctx, env.attribs.dist, 4) 630 | 631 | env.buffers.colors.Bind(ctx) 632 | env.buffers.colors.Update(ctx, env.colors) 633 | env.prg.Pointer(ctx, env.attribs.color, 4) 634 | 635 | env.buffers.verts.Bind(ctx) 636 | env.buffers.verts.Update(ctx, env.verts) 637 | env.buffers.indices.Bind(ctx) 638 | env.buffers.indices.Update(ctx, env.indices) 639 | env.prg.Pointer(ctx, env.attribs.vertex, 4) 640 | 641 | if env.glyphs.Value != 0 { 642 | env.glyphs.Bind(ctx, linearFilter, DefaultWrap) 643 | env.prg.U1i(ctx, env.uniforms.glyphs, int(env.glyphs.Value-1)) 644 | } 645 | 646 | if env.icons.Value != 0 { 647 | env.icons.Bind(ctx, DefaultFilter, DefaultWrap) 648 | env.prg.U1i(ctx, env.uniforms.icons, int(env.icons.Value-1)) 649 | } 650 | 651 | if env.image.Value != 0 { 652 | env.image.Bind(ctx, DefaultFilter, DefaultWrap) 653 | env.prg.U1i(ctx, env.uniforms.image, int(env.image.Value-1)) 654 | } 655 | 656 | env.buffers.indices.Draw(ctx, env.prg, gl.TRIANGLES) 657 | } 658 | 659 | func (env *Environment) DrawGridDebug(ctx gl.Context) { 660 | env.Grid.draw(ctx, env.View, env.proj) 661 | } 662 | 663 | func (env *Environment) Touch(ev touch.Event) bool { 664 | ex, ey := ev.X, float32(windowSize.HeightPx)-ev.Y 665 | for i := len(env.sheets) - 1; i >= 0; i-- { 666 | sheet := env.sheets[i] 667 | if !sheet.Hidden() && sheet.Contains(ex, ey) { 668 | mtrl := sheet.M() 669 | mtrl.touch.state = ev.Type 670 | mtrl.touch.x, mtrl.touch.y = mtrl.RelativeCoords(ex, ey) 671 | if ev.Type == touch.TypeBegin { 672 | mtrl.touch.start = time.Now() 673 | } 674 | 675 | switch sheet := sheet.(type) { 676 | case *Button: 677 | if ev.Type == touch.TypeBegin && sheet.OnPress != nil { 678 | sheet.OnPress() 679 | } 680 | if sheet.OnTouch != nil { 681 | sheet.OnTouch(ev) 682 | } 683 | case *FloatingActionButton: 684 | if ev.Type == touch.TypeBegin && sheet.OnPress != nil { 685 | sheet.OnPress() 686 | } 687 | if sheet.OnTouch != nil { 688 | sheet.OnTouch(ev) 689 | } 690 | default: 691 | log.Printf("Unhandled type %T\n", sheet) 692 | continue 693 | } 694 | return true 695 | } 696 | } 697 | return false 698 | } 699 | 700 | func (env *Environment) NewMaterial(ctx gl.Context) *Material { 701 | m := New(ctx, Black) 702 | m.SetColor(env.plt.Light) 703 | env.sheets = append(env.sheets, m) 704 | return m 705 | } 706 | 707 | func (env *Environment) NewButton(ctx gl.Context) *Button { 708 | btn := &Button{Material: New(ctx, Black)} // TODO update constructor to remove color arg 709 | btn.SetColor(env.plt.Primary) 710 | btn.SetIconColor(White) 711 | env.sheets = append(env.sheets, btn) 712 | return btn 713 | } 714 | 715 | func (env *Environment) NewFloatingActionButton(ctx gl.Context) *FloatingActionButton { 716 | fab := &FloatingActionButton{Material: New(ctx, Black)} // TODO update constructor to remove color arg 717 | fab.SetColor(env.plt.Primary) 718 | fab.SetIconColor(White) 719 | fab.IsCircle = true 720 | env.sheets = append(env.sheets, fab) 721 | return fab 722 | } 723 | 724 | func (env *Environment) NewToolbar(ctx gl.Context) *Toolbar { 725 | bar := &Toolbar{ 726 | Material: New(ctx, Black), 727 | Nav: env.NewButton(ctx), 728 | Title: env.NewMaterial(ctx), 729 | } 730 | bar.SetColor(env.plt.Light) // create specific ColorFromPalette on each type to localize selection 731 | bar.Nav.BehaviorFlags = DescriptorFlat 732 | bar.Nav.SetIcon(icon.NavigationMenu) 733 | bar.Nav.SetIconColor(Black) 734 | bar.Title.BehaviorFlags = DescriptorFlat 735 | env.sheets = append(env.sheets, bar) 736 | return bar 737 | } 738 | 739 | func (env *Environment) NewMenu(ctx gl.Context) *Menu { 740 | mu := &Menu{Material: New(ctx, Black)} 741 | mu.SetColor(env.plt.Light) 742 | mu.BehaviorFlags |= VisibilityTemporary 743 | mu.hidden = true 744 | env.sheets = append(env.sheets, mu) 745 | return mu 746 | } 747 | -------------------------------------------------------------------------------- /text/texcoords.go: -------------------------------------------------------------------------------- 1 | // generated by gen.go; DO NOT EDIT 2 | package text 3 | 4 | const AscentUnit = 0.79671454 5 | const DescentUnit = 0.20328543 6 | const TextureSize = 512 7 | const FontSize = 52 8 | const Pad = 3 9 | 10 | var Texcoords = map[rune][4]float32{ 11 | 'ý': {0, 0, 0.060791016, 0.11401367}, 12 | 'þ': {0.060791016, 0, 0.057128906, 0.11328125}, 13 | '|': {0.11791992, 0, 0.01928711, 0.11328125}, 14 | '¦': {0.13720703, 0, 0.01928711, 0.11328125}, 15 | 'j': {0.15649414, 0, 0.033691406, 0.111083984}, 16 | 'ÿ': {0.19018555, 0, 0.060791016, 0.11035156}, 17 | 'Ç': {0.25097656, 0, 0.063964844, 0.10961914}, 18 | 'Ô': {0.3149414, 0, 0.07470703, 0.107421875}, 19 | 'Ó': {0.38964844, 0, 0.07470703, 0.107421875}, 20 | 'Û': {0.46435547, 0, 0.064208984, 0.107421875}, 21 | 'Ò': {0.52856445, 0, 0.07470703, 0.107421875}, 22 | 'Ú': {0.6032715, 0, 0.064208984, 0.107421875}, 23 | 'Ù': {0.66748047, 0, 0.064208984, 0.107421875}, 24 | 'Î': {0.73168945, 0, 0.044677734, 0.10644531}, 25 | 'Á': {0.7763672, 0, 0.07397461, 0.10644531}, 26 | 'È': {0.8503418, 0, 0.049560547, 0.10644531}, 27 | 'É': {0.89990234, 0, 0.049560547, 0.10644531}, 28 | 'Ý': {0, 0.11401367, 0.06567383, 0.10644531}, 29 | 'Â': {0.06567383, 0.11401367, 0.07397461, 0.10644531}, 30 | 'Ì': {0.13964844, 0.11401367, 0.0390625, 0.10644531}, 31 | 'Ê': {0.17871094, 0.11401367, 0.049560547, 0.10644531}, 32 | 'Í': {0.22827148, 0.11401367, 0.040039062, 0.10644531}, 33 | 'À': {0.26831055, 0.11401367, 0.07397461, 0.10644531}, 34 | 'Q': {0.34228516, 0.11401367, 0.07470703, 0.10595703}, 35 | 'Õ': {0.4169922, 0.11401367, 0.07470703, 0.10424805}, 36 | 'Ö': {0.49169922, 0.11401367, 0.07470703, 0.103759766}, 37 | 'Ü': {0.56640625, 0.11401367, 0.064208984, 0.103759766}, 38 | 'J': {0.63061523, 0.11401367, 0.03930664, 0.103515625}, 39 | 'Ñ': {0.6699219, 0.11401367, 0.06616211, 0.103271484}, 40 | 'Ã': {0.736084, 0.11401367, 0.07397461, 0.103271484}, 41 | 'Ï': {0.8100586, 0.11401367, 0.039794922, 0.1027832}, 42 | 'Ä': {0.8498535, 0.11401367, 0.07397461, 0.1027832}, 43 | 'Ë': {0.9238281, 0.11401367, 0.049560547, 0.1027832}, 44 | '¶': {0, 0.22045898, 0.061767578, 0.10180664}, 45 | 'Å': {0.061767578, 0.22045898, 0.07397461, 0.10058594}, 46 | ']': {0.13574219, 0.22045898, 0.03173828, 0.1003418}, 47 | '[': {0.16748047, 0.22045898, 0.03173828, 0.1003418}, 48 | '}': {0.19921875, 0.22045898, 0.041992188, 0.1003418}, 49 | '(': {0.24121094, 0.22045898, 0.03540039, 0.1003418}, 50 | '{': {0.27661133, 0.22045898, 0.041992188, 0.1003418}, 51 | ')': {0.31860352, 0.22045898, 0.03515625, 0.1003418}, 52 | 'å': {0.35375977, 0.22045898, 0.052978516, 0.095458984}, 53 | '$': {0.40673828, 0.22045898, 0.05444336, 0.09472656}, 54 | '@': {0.46118164, 0.22045898, 0.08886719, 0.09326172}, 55 | 'Ø': {0.5500488, 0.22045898, 0.07470703, 0.091796875}, 56 | 'ç': {0.62475586, 0.22045898, 0.049804688, 0.091552734}, 57 | 'q': {0.67456055, 0.22045898, 0.057128906, 0.091552734}, 58 | 'p': {0.73168945, 0.22045898, 0.057128906, 0.091552734}, 59 | 'g': {0.78881836, 0.22045898, 0.060546875, 0.091552734}, 60 | 'ð': {0.84936523, 0.22045898, 0.05908203, 0.09057617}, 61 | 'ò': {0.90844727, 0.22045898, 0.05908203, 0.09057617}, 62 | 'ù': {0, 0.32226562, 0.05493164, 0.09057617}, 63 | 'ú': {0.05493164, 0.32226562, 0.05493164, 0.09057617}, 64 | 'à': {0.10986328, 0.32226562, 0.052978516, 0.09057617}, 65 | 'y': {0.1628418, 0.32226562, 0.060791016, 0.09057617}, 66 | 'á': {0.22363281, 0.32226562, 0.052978516, 0.09057617}, 67 | 'è': {0.27661133, 0.32226562, 0.055419922, 0.09057617}, 68 | 'ê': {0.33203125, 0.32226562, 0.055419922, 0.09057617}, 69 | 'ô': {0.38745117, 0.32226562, 0.05908203, 0.09057617}, 70 | 'é': {0.4465332, 0.32226562, 0.055419922, 0.09057617}, 71 | 'û': {0.5019531, 0.32226562, 0.05493164, 0.09057617}, 72 | 'µ': {0.55688477, 0.32226562, 0.05493164, 0.09057617}, 73 | 'â': {0.6118164, 0.32226562, 0.052978516, 0.09057617}, 74 | 'ó': {0.6647949, 0.32226562, 0.05908203, 0.09057617}, 75 | 'ß': {0.72387695, 0.32226562, 0.059570312, 0.09033203}, 76 | '§': {0.78344727, 0.32226562, 0.048583984, 0.08984375}, 77 | 'b': {0.83203125, 0.32226562, 0.057128906, 0.08984375}, 78 | 'd': {0.88916016, 0.32226562, 0.057128906, 0.08984375}, 79 | 'î': {0.94628906, 0.32226562, 0.044677734, 0.08959961}, 80 | 'ì': {0, 0.4128418, 0.03125, 0.08959961}, 81 | 'í': {0.03125, 0.4128418, 0.03173828, 0.08959961}, 82 | 'f': {0.06298828, 0.4128418, 0.04736328, 0.08935547}, 83 | 'h': {0.11035156, 0.4128418, 0.05493164, 0.08886719}, 84 | 'k': {0.1652832, 0.4128418, 0.053222656, 0.08886719}, 85 | 'l': {0.21850586, 0.4128418, 0.020751953, 0.08886719}, 86 | 'õ': {0.23925781, 0.4128418, 0.05908203, 0.087402344}, 87 | 'ã': {0.29833984, 0.4128418, 0.052978516, 0.087402344}, 88 | 'ö': {0.35131836, 0.4128418, 0.05908203, 0.08691406}, 89 | 'ä': {0.4104004, 0.4128418, 0.052978516, 0.08691406}, 90 | 'ü': {0.4633789, 0.4128418, 0.05493164, 0.08691406}, 91 | 'ë': {0.51831055, 0.4128418, 0.055419922, 0.08691406}, 92 | '¿': {0.57373047, 0.4128418, 0.049804688, 0.08666992}, 93 | '?': {0.62353516, 0.4128418, 0.049560547, 0.08666992}, 94 | 'i': {0.6730957, 0.4128418, 0.022460938, 0.08666992}, 95 | '8': {0.69555664, 0.4128418, 0.057128906, 0.08642578}, 96 | 'O': {0.75268555, 0.4128418, 0.07470703, 0.08642578}, 97 | '&': {0.8273926, 0.4128418, 0.075927734, 0.08642578}, 98 | '0': {0.9033203, 0.4128418, 0.057861328, 0.08642578}, 99 | 'ñ': {0, 0.5024414, 0.05493164, 0.08642578}, 100 | '¢': {0.05493164, 0.5024414, 0.049804688, 0.08618164}, 101 | '3': {0.10473633, 0.5024414, 0.057373047, 0.08618164}, 102 | 'S': {0.16210938, 0.5024414, 0.0546875, 0.08618164}, 103 | '©': {0.21679688, 0.5024414, 0.08642578, 0.08618164}, 104 | '%': {0.30322266, 0.5024414, 0.08520508, 0.08618164}, 105 | '9': {0.38842773, 0.5024414, 0.057373047, 0.08618164}, 106 | 'C': {0.44580078, 0.5024414, 0.063964844, 0.08618164}, 107 | '6': {0.5097656, 0.5024414, 0.057373047, 0.08618164}, 108 | '®': {0.5671387, 0.5024414, 0.08642578, 0.08618164}, 109 | 'G': {0.65356445, 0.5024414, 0.068359375, 0.08618164}, 110 | 'ï': {0.7219238, 0.5024414, 0.039794922, 0.0859375}, 111 | '!': {0.76171875, 0.5024414, 0.024414062, 0.08569336}, 112 | '¡': {0.7861328, 0.5024414, 0.024414062, 0.08569336}, 113 | '£': {0.8105469, 0.5024414, 0.060791016, 0.08520508}, 114 | '5': {0.8713379, 0.5024414, 0.055419922, 0.08520508}, 115 | 'U': {0.9267578, 0.5024414, 0.064208984, 0.08520508}, 116 | '2': {0, 0.5888672, 0.056884766, 0.08520508}, 117 | '¾': {0.056884766, 0.5888672, 0.084228516, 0.08520508}, 118 | '4': {0.14111328, 0.5888672, 0.064453125, 0.0847168}, 119 | 'A': {0.2055664, 0.5888672, 0.07397461, 0.084472656}, 120 | 'W': {0.27954102, 0.5888672, 0.100097656, 0.084228516}, 121 | 'F': {0.37963867, 0.5888672, 0.049316406, 0.084228516}, 122 | 'E': {0.42895508, 0.5888672, 0.049560547, 0.084228516}, 123 | 'D': {0.47851562, 0.5888672, 0.06542969, 0.084228516}, 124 | 'H': {0.5439453, 0.5888672, 0.06347656, 0.084228516}, 125 | 'B': {0.6074219, 0.5888672, 0.059326172, 0.084228516}, 126 | 'I': {0.66674805, 0.5888672, 0.038085938, 0.084228516}, 127 | 'M': {0.704834, 0.5888672, 0.080566406, 0.084228516}, 128 | 'K': {0.7854004, 0.5888672, 0.061035156, 0.084228516}, 129 | 'L': {0.84643555, 0.5888672, 0.049560547, 0.084228516}, 130 | 'Ð': {0.8959961, 0.5888672, 0.07299805, 0.084228516}, 131 | '7': {0, 0.67407227, 0.05834961, 0.084228516}, 132 | 'N': {0.05834961, 0.67407227, 0.06616211, 0.084228516}, 133 | '¥': {0.12451172, 0.67407227, 0.06542969, 0.084228516}, 134 | '¼': {0.1899414, 0.67407227, 0.0793457, 0.084228516}, 135 | '½': {0.2692871, 0.67407227, 0.08129883, 0.084228516}, 136 | '⁄': {0.35058594, 0.67407227, 0.060302734, 0.084228516}, 137 | 'Þ': {0.41088867, 0.67407227, 0.055419922, 0.084228516}, 138 | 'P': {0.4663086, 0.67407227, 0.055419922, 0.084228516}, 139 | 'V': {0.5217285, 0.67407227, 0.06982422, 0.084228516}, 140 | 'X': {0.59155273, 0.67407227, 0.067871094, 0.084228516}, 141 | 'T': {0.6594238, 0.67407227, 0.06225586, 0.084228516}, 142 | '/': {0.7216797, 0.67407227, 0.047851562, 0.084228516}, 143 | 'Y': {0.76953125, 0.67407227, 0.06567383, 0.084228516}, 144 | 'Æ': {0.8352051, 0.67407227, 0.092285156, 0.084228516}, 145 | 'Z': {0.92749023, 0.67407227, 0.05810547, 0.084228516}, 146 | '#': {0, 0.7583008, 0.072265625, 0.084228516}, 147 | '\\': {0.072265625, 0.7583008, 0.047851562, 0.084228516}, 148 | '1': {0.12011719, 0.7583008, 0.038330078, 0.084228516}, 149 | 'R': {0.15844727, 0.7583008, 0.060791016, 0.084228516}, 150 | ';': {0.21923828, 0.7583008, 0.028564453, 0.08081055}, 151 | 't': {0.24780273, 0.7583008, 0.04248047, 0.079833984}, 152 | 'ø': {0.2902832, 0.7583008, 0.05908203, 0.07324219}, 153 | '±': {0.34936523, 0.7583008, 0.057861328, 0.07080078}, 154 | ':': {0.40722656, 0.7583008, 0.024414062, 0.0690918}, 155 | 'o': {0.43164062, 0.7583008, 0.05908203, 0.068115234}, 156 | 'e': {0.49072266, 0.7583008, 0.055419922, 0.068115234}, 157 | 'c': {0.5461426, 0.7583008, 0.049804688, 0.068115234}, 158 | 's': {0.59594727, 0.7583008, 0.048583984, 0.068115234}, 159 | 'a': {0.64453125, 0.7583008, 0.052978516, 0.068115234}, 160 | 'æ': {0.69750977, 0.7583008, 0.08666992, 0.068115234}, 161 | 'r': {0.7841797, 0.7583008, 0.041748047, 0.06713867}, 162 | 'n': {0.82592773, 0.7583008, 0.05493164, 0.06713867}, 163 | 'm': {0.8808594, 0.7583008, 0.08618164, 0.06713867}, 164 | 'u': {0, 0.8425293, 0.05493164, 0.06713867}, 165 | 'x': {0.05493164, 0.8425293, 0.05883789, 0.06616211}, 166 | 'v': {0.11376953, 0.8425293, 0.060791016, 0.06616211}, 167 | 'w': {0.17456055, 0.8425293, 0.0859375, 0.06616211}, 168 | 'ı': {0.26049805, 0.8425293, 0.020751953, 0.06616211}, 169 | 'z': {0.28125, 0.8425293, 0.048339844, 0.06616211}, 170 | '<': {0.32958984, 0.8425293, 0.057373047, 0.061767578}, 171 | '>': {0.3869629, 0.8425293, 0.057373047, 0.061767578}, 172 | '÷': {0.44433594, 0.8425293, 0.057861328, 0.05883789}, 173 | '+': {0.50219727, 0.8425293, 0.057861328, 0.057861328}, 174 | '*': {0.5600586, 0.8425293, 0.059814453, 0.057617188}, 175 | '^': {0.61987305, 0.8425293, 0.061767578, 0.057617188}, 176 | '³': {0.6816406, 0.8425293, 0.040771484, 0.056884766}, 177 | '²': {0.7224121, 0.8425293, 0.040283203, 0.056152344}, 178 | '¤': {0.7626953, 0.8425293, 0.055419922, 0.055419922}, 179 | '¹': {0.81811523, 0.8425293, 0.030761719, 0.05517578}, 180 | '×': {0.84887695, 0.8425293, 0.05419922, 0.053955078}, 181 | '‹': {0.9030762, 0.8425293, 0.032958984, 0.053955078}, 182 | '›': {0.93603516, 0.8425293, 0.032958984, 0.053955078}, 183 | '»': {0, 0.90966797, 0.053222656, 0.053955078}, 184 | '«': {0.053222656, 0.90966797, 0.053222656, 0.053955078}, 185 | 'º': {0.10644531, 0.90966797, 0.041015625, 0.046142578}, 186 | 'ª': {0.14746094, 0.90966797, 0.037109375, 0.046142578}, 187 | '°': {0.18457031, 0.90966797, 0.04296875, 0.04296875}, 188 | '=': {0.22753906, 0.90966797, 0.057373047, 0.03955078}, 189 | '¬': {0.2849121, 0.90966797, 0.057373047, 0.038330078}, 190 | '•': {0.34228516, 0.90966797, 0.03515625, 0.037841797}, 191 | '\'': {0.3774414, 0.90966797, 0.021728516, 0.037841797}, 192 | '"': {0.39916992, 0.90966797, 0.03955078, 0.037841797}, 193 | '„': {0.4387207, 0.90966797, 0.04638672, 0.036621094}, 194 | '‚': {0.48510742, 0.90966797, 0.02734375, 0.036621094}, 195 | '“': {0.5124512, 0.90966797, 0.046142578, 0.036621094}, 196 | '’': {0.55859375, 0.90966797, 0.02709961, 0.036621094}, 197 | ',': {0.58569336, 0.90966797, 0.02734375, 0.036621094}, 198 | '”': {0.6130371, 0.90966797, 0.046142578, 0.036621094}, 199 | '‘': {0.6591797, 0.90966797, 0.02709961, 0.036621094}, 200 | '¸': {0.6862793, 0.90966797, 0.030273438, 0.036132812}, 201 | '˚': {0.71655273, 0.90966797, 0.034179688, 0.032958984}, 202 | '´': {0.7507324, 0.90966797, 0.03125, 0.028076172}, 203 | 'ˆ': {0.7819824, 0.90966797, 0.044677734, 0.028076172}, 204 | '`': {0.82666016, 0.90966797, 0.03125, 0.028076172}, 205 | '~': {0.85791016, 0.90966797, 0.057373047, 0.025634766}, 206 | '·': {0.9152832, 0.90966797, 0.024414062, 0.025390625}, 207 | '.': {0.93969727, 0.90966797, 0.024414062, 0.025390625}, 208 | '˜': {0, 0.96362305, 0.04736328, 0.025146484}, 209 | '¨': {0.04736328, 0.96362305, 0.039794922, 0.021972656}, 210 | '—': {0.0871582, 0.96362305, 0.10498047, 0.020263672}, 211 | '–': {0.19213867, 0.96362305, 0.05419922, 0.020263672}, 212 | '­': {0.24633789, 0.96362305, 0.036376953, 0.020263672}, 213 | '-': {0.28271484, 0.96362305, 0.036376953, 0.020263672}, 214 | '¯': {0.3190918, 0.96362305, 0.06298828, 0.018798828}, 215 | '_': {0.38208008, 0.96362305, 0.053955078, 0.018798828}, 216 | ' ': {0.43603516, 0.96362305, 0.01171875, 0.01171875}, 217 | ' ': {0.4477539, 0.96362305, 0.01171875, 0.01171875}, 218 | } 219 | 220 | var Bounds = map[rune][5]float32{ 221 | 'ý': {0.0024038462, 0.24038461, 0.48317307, 1.0072116, 0.48798078}, 222 | 'þ': {0.084134616, 0.24038461, 0.4471154, 1, 0.58653843}, 223 | '|': {0.23798077, 0.24038461, 0.07451923, 1, 0.5504808}, 224 | '¦': {0.23798077, 0.24038461, 0.07451923, 1, 0.5504808}, 225 | 'j': {-0.033653848, 0.24038461, 0.21634616, 0.97836536, 0.2596154}, 226 | 'ÿ': {0.0024038462, 0.24038461, 0.48317307, 0.97115386, 0.48798078}, 227 | 'Ç': {0.060096152, 0.24038461, 0.5144231, 0.9639423, 0.60336536}, 228 | 'Ô': {0.060096152, 0.009615385, 0.6201923, 0.9423077, 0.74278843}, 229 | 'Ó': {0.060096152, 0.009615385, 0.6201923, 0.9423077, 0.74278843}, 230 | 'Û': {0.088942304, 0.009615385, 0.5168269, 0.9423077, 0.69711536}, 231 | 'Ò': {0.060096152, 0.009615385, 0.6201923, 0.9423077, 0.74278843}, 232 | 'Ú': {0.088942304, 0.009615385, 0.5168269, 0.9423077, 0.69711536}, 233 | 'Ù': {0.088942304, 0.009615385, 0.5168269, 0.9423077, 0.69711536}, 234 | 'Î': {0.009615385, 0, 0.32451922, 0.9326923, 0.33894232}, 235 | 'Á': {-0.0024038462, 0, 0.6129808, 0.9326923, 0.6081731}, 236 | 'È': {0.09615385, 0, 0.37259614, 0.9326923, 0.52884614}, 237 | 'É': {0.09615385, 0, 0.37259614, 0.9326923, 0.52884614}, 238 | 'Ý': {-0.0024038462, 0, 0.53125, 0.9326923, 0.5264423}, 239 | 'Â': {-0.0024038462, 0, 0.6129808, 0.9326923, 0.6081731}, 240 | 'Ì': {0.03125, 0, 0.26923078, 0.9326923, 0.33894232}, 241 | 'Ê': {0.09615385, 0, 0.37259614, 0.9326923, 0.52884614}, 242 | 'Í': {0.040865384, 0, 0.27884614, 0.9326923, 0.33894232}, 243 | 'À': {-0.0024038462, 0, 0.6129808, 0.9326923, 0.6081731}, 244 | 'Q': {0.060096152, 0.20192307, 0.6201923, 0.92788464, 0.74038464}, 245 | 'Õ': {0.060096152, 0.009615385, 0.6201923, 0.9110577, 0.74278843}, 246 | 'Ö': {0.060096152, 0.009615385, 0.6201923, 0.90625, 0.74278843}, 247 | 'Ü': {0.088942304, 0.009615385, 0.5168269, 0.90625, 0.69711536}, 248 | 'J': {-0.09134615, 0.18990384, 0.2716346, 0.90384614, 0.2716346}, 249 | 'Ñ': {0.09615385, 0, 0.5360577, 0.9014423, 0.72836536}, 250 | 'Ã': {-0.0024038462, 0, 0.6129808, 0.9014423, 0.6081731}, 251 | 'Ï': {0.033653848, 0, 0.27644232, 0.89663464, 0.33894232}, 252 | 'Ä': {-0.0024038462, 0, 0.6129808, 0.89663464, 0.6081731}, 253 | 'Ë': {0.09615385, 0, 0.37259614, 0.89663464, 0.52884614}, 254 | '¶': {0.05528846, 0.12740384, 0.49278846, 0.8870192, 0.65384614}, 255 | 'Å': {-0.0024038462, 0, 0.6129808, 0.875, 0.6081731}, 256 | ']': {0.026442308, 0.15865384, 0.19711539, 0.87259614, 0.3028846}, 257 | '[': {0.07932692, 0.15865384, 0.19711539, 0.87259614, 0.3028846}, 258 | '}': {0.026442308, 0.15865384, 0.29807693, 0.87259614, 0.3533654}, 259 | '(': {0.040865384, 0.15865384, 0.23317307, 0.87259614, 0.30048078}, 260 | '{': {0.028846154, 0.15865384, 0.29807693, 0.87259614, 0.3533654}, 261 | ')': {0.03125, 0.15865384, 0.23076923, 0.87259614, 0.30048078}, 262 | 'å': {0.045673076, 0.009615385, 0.40625, 0.8245192, 0.53125}, 263 | '$': {0.060096152, 0.057692308, 0.42067307, 0.8173077, 0.5504808}, 264 | '@': {0.052884616, 0.088942304, 0.75961536, 0.80288464, 0.86538464}, 265 | 'Ø': {0.060096152, 0.03846154, 0.6201923, 0.78846157, 0.74278843}, 266 | 'ç': {0.05528846, 0.24038461, 0.375, 0.7860577, 0.46394232}, 267 | 'q': {0.05528846, 0.24038461, 0.4471154, 0.7860577, 0.58653843}, 268 | 'p': {0.084134616, 0.24038461, 0.4471154, 0.7860577, 0.58653843}, 269 | 'g': {0.01923077, 0.24038461, 0.48076922, 0.7860577, 0.5192308}, 270 | 'ð': {0.05528846, 0.009615385, 0.46634614, 0.7764423, 0.5769231}, 271 | 'ò': {0.05528846, 0.009615385, 0.46634614, 0.7764423, 0.5769231}, 272 | 'ù': {0.07932692, 0.009615385, 0.42548078, 0.7764423, 0.5889423}, 273 | 'ú': {0.07932692, 0.009615385, 0.42548078, 0.7764423, 0.5889423}, 274 | 'à': {0.045673076, 0.009615385, 0.40625, 0.7764423, 0.53125}, 275 | 'y': {0.0024038462, 0.24038461, 0.48317307, 0.7764423, 0.48798078}, 276 | 'á': {0.045673076, 0.009615385, 0.40625, 0.7764423, 0.53125}, 277 | 'è': {0.05528846, 0.009615385, 0.43028846, 0.7764423, 0.5360577}, 278 | 'ê': {0.05528846, 0.009615385, 0.43028846, 0.7764423, 0.5360577}, 279 | 'ô': {0.05528846, 0.009615385, 0.46634614, 0.7764423, 0.5769231}, 280 | 'é': {0.05528846, 0.009615385, 0.43028846, 0.7764423, 0.5360577}, 281 | 'û': {0.07932692, 0.009615385, 0.42548078, 0.7764423, 0.5889423}, 282 | 'µ': {0.084134616, 0.24038461, 0.42548078, 0.7764423, 0.59375}, 283 | 'â': {0.045673076, 0.009615385, 0.40625, 0.7764423, 0.53125}, 284 | 'ó': {0.05528846, 0.009615385, 0.46634614, 0.7764423, 0.5769231}, 285 | 'ß': {0.084134616, 0.009615385, 0.47115386, 0.77403843, 0.60096157}, 286 | '§': {0.057692308, 0.0048076925, 0.36298078, 0.7692308, 0.48557693}, 287 | 'b': {0.084134616, 0.009615385, 0.4471154, 0.7692308, 0.58653843}, 288 | 'd': {0.05528846, 0.009615385, 0.4471154, 0.7692308, 0.58653843}, 289 | 'î': {-0.033653848, 0, 0.32451922, 0.7668269, 0.2596154}, 290 | 'ì': {-0.016826924, 0, 0.1923077, 0.7668269, 0.2596154}, 291 | 'í': {0.084134616, 0, 0.19711539, 0.7668269, 0.2596154}, 292 | 'f': {0.014423077, 0, 0.35096154, 0.7644231, 0.32932693}, 293 | 'h': {0.084134616, 0, 0.42548078, 0.75961536, 0.5889423}, 294 | 'k': {0.084134616, 0, 0.40865386, 0.75961536, 0.49519232}, 295 | 'l': {0.084134616, 0, 0.088942304, 0.75961536, 0.2596154}, 296 | 'õ': {0.05528846, 0.009615385, 0.46634614, 0.7451923, 0.5769231}, 297 | 'ã': {0.045673076, 0.009615385, 0.40625, 0.7451923, 0.53125}, 298 | 'ö': {0.05528846, 0.009615385, 0.46634614, 0.74038464, 0.5769231}, 299 | 'ä': {0.045673076, 0.009615385, 0.40625, 0.74038464, 0.53125}, 300 | 'ü': {0.07932692, 0.009615385, 0.42548078, 0.74038464, 0.5889423}, 301 | 'ë': {0.05528846, 0.009615385, 0.43028846, 0.74038464, 0.5360577}, 302 | '¿': {0.033653848, 0.1923077, 0.375, 0.7379808, 0.42548078}, 303 | '?': {0.01923077, 0.014423077, 0.37259614, 0.7379808, 0.42548078}, 304 | 'i': {0.07692308, 0, 0.10576923, 0.7379808, 0.2596154}, 305 | '8': {0.052884616, 0.009615385, 0.4471154, 0.7355769, 0.5504808}, 306 | 'O': {0.060096152, 0.009615385, 0.6201923, 0.7355769, 0.74278843}, 307 | '&': {0.052884616, 0.009615385, 0.63221157, 0.7355769, 0.7019231}, 308 | '0': {0.048076924, 0.009615385, 0.45432693, 0.7355769, 0.5504808}, 309 | 'ñ': {0.084134616, 0, 0.42548078, 0.7355769, 0.5889423}, 310 | '¢': {0.09134615, 0.009615385, 0.375, 0.7331731, 0.5504808}, 311 | '3': {0.040865384, 0.009615385, 0.44951922, 0.7331731, 0.5504808}, 312 | 'S': {0.050480768, 0.009615385, 0.42307693, 0.7331731, 0.5192308}, 313 | '©': {0.048076924, 0.009615385, 0.7355769, 0.7331731, 0.8317308}, 314 | '%': {0.050480768, 0.009615385, 0.7235577, 0.7331731, 0.8245192}, 315 | '9': {0.052884616, 0.009615385, 0.44951922, 0.7331731, 0.5504808}, 316 | 'C': {0.060096152, 0.009615385, 0.5144231, 0.7331731, 0.60336536}, 317 | '6': {0.05528846, 0.009615385, 0.44951922, 0.7331731, 0.5504808}, 318 | '®': {0.048076924, 0.009615385, 0.7355769, 0.7331731, 0.8317308}, 319 | 'G': {0.060096152, 0.009615385, 0.5576923, 0.7331731, 0.68990386}, 320 | 'ï': {-0.009615385, 0, 0.27644232, 0.7307692, 0.2596154}, 321 | '!': {0.072115384, 0.014423077, 0.125, 0.72836536, 0.26923078}, 322 | '¡': {0.072115384, 0.1826923, 0.125, 0.72836536, 0.26923078}, 323 | '£': {0.033653848, 0, 0.48317307, 0.7235577, 0.5504808}, 324 | '5': {0.06490385, 0.009615385, 0.43028846, 0.7235577, 0.5504808}, 325 | 'U': {0.088942304, 0.009615385, 0.5168269, 0.7235577, 0.69711536}, 326 | '2': {0.048076924, 0, 0.44471154, 0.7235577, 0.5504808}, 327 | '¾': {0.014423077, 0, 0.7139423, 0.7235577, 0.7379808}, 328 | '4': {0.012019231, 0, 0.5192308, 0.71875, 0.5504808}, 329 | 'A': {-0.0024038462, 0, 0.6129808, 0.71634614, 0.6081731}, 330 | 'W': {0.0072115385, 0, 0.8701923, 0.7139423, 0.88461536}, 331 | 'F': {0.09615385, 0, 0.37019232, 0.7139423, 0.4903846}, 332 | 'E': {0.09615385, 0, 0.37259614, 0.7139423, 0.52884614}, 333 | 'D': {0.09615385, 0, 0.52884614, 0.7139423, 0.68509614}, 334 | 'H': {0.09615385, 0, 0.50961536, 0.7139423, 0.7019231}, 335 | 'B': {0.09615385, 0, 0.46875, 0.7139423, 0.6201923}, 336 | 'I': {0.040865384, 0, 0.2596154, 0.7139423, 0.33894232}, 337 | 'M': {0.09615385, 0, 0.67788464, 0.7139423, 0.8701923}, 338 | 'K': {0.09615385, 0, 0.48557693, 0.7139423, 0.5793269}, 339 | 'L': {0.09615385, 0, 0.37259614, 0.7139423, 0.4903846}, 340 | 'Ð': {0.021634616, 0, 0.60336536, 0.7139423, 0.68509614}, 341 | '7': {0.043269232, 0, 0.4591346, 0.7139423, 0.5504808}, 342 | 'N': {0.09615385, 0, 0.5360577, 0.7139423, 0.72836536}, 343 | '¥': {0.012019231, 0, 0.52884614, 0.7139423, 0.5504808}, 344 | '¼': {0.028846154, 0, 0.66586536, 0.7139423, 0.7379808}, 345 | '½': {0.01923077, 0, 0.68509614, 0.7139423, 0.7379808}, 346 | '⁄': {-0.17548077, 0, 0.4783654, 0.7139423, 0.1298077}, 347 | 'Þ': {0.09615385, 0, 0.43028846, 0.7139423, 0.5769231}, 348 | 'P': {0.09615385, 0, 0.43028846, 0.7139423, 0.5769231}, 349 | 'V': {-0.0024038462, 0, 0.57211536, 0.7139423, 0.5673077}, 350 | 'X': {-0.0024038462, 0, 0.55288464, 0.7139423, 0.5480769}, 351 | 'T': {0.009615385, 0, 0.49759614, 0.7139423, 0.5192308}, 352 | '/': {0.0072115385, 0, 0.35576922, 0.7139423, 0.37259614}, 353 | 'Y': {-0.0024038462, 0, 0.53125, 0.7139423, 0.5264423}, 354 | 'Æ': {-0.0024038462, 0, 0.7932692, 0.7139423, 0.85096157}, 355 | 'Z': {0.040865384, 0, 0.45673078, 0.7139423, 0.53846157}, 356 | '#': {0.024038462, 0, 0.59615386, 0.7139423, 0.64663464}, 357 | '\\': {0.009615385, 0, 0.35576922, 0.7139423, 0.37259614}, 358 | '1': {0.086538464, 0, 0.26201922, 0.7139423, 0.5504808}, 359 | 'R': {0.09615385, 0, 0.48317307, 0.7139423, 0.5889423}, 360 | ';': {0.03125, 0.1298077, 0.16586539, 0.68028843, 0.26923078}, 361 | 't': {0.016826924, 0.009615385, 0.3028846, 0.6706731, 0.33894232}, 362 | 'ø': {0.05528846, 0.036057692, 0.46634614, 0.6057692, 0.5769231}, 363 | '±': {0.048076924, 0, 0.45432693, 0.5817308, 0.5504808}, 364 | ':': {0.072115384, 0.014423077, 0.125, 0.56490386, 0.26923078}, 365 | 'o': {0.05528846, 0.009615385, 0.46634614, 0.55528843, 0.5769231}, 366 | 'e': {0.05528846, 0.009615385, 0.43028846, 0.55528843, 0.5360577}, 367 | 'c': {0.05528846, 0.009615385, 0.375, 0.55528843, 0.46394232}, 368 | 's': {0.043269232, 0.009615385, 0.36298078, 0.55528843, 0.45192307}, 369 | 'a': {0.045673076, 0.009615385, 0.40625, 0.55528843, 0.53125}, 370 | 'æ': {0.045673076, 0.009615385, 0.7379808, 0.55528843, 0.83413464}, 371 | 'r': {0.084134616, 0, 0.29567307, 0.5456731, 0.39903846}, 372 | 'n': {0.084134616, 0, 0.42548078, 0.5456731, 0.5889423}, 373 | 'm': {0.084134616, 0, 0.7331731, 0.5456731, 0.89663464}, 374 | 'u': {0.07932692, 0.009615385, 0.42548078, 0.5456731, 0.5889423}, 375 | 'x': {0.016826924, 0, 0.46394232, 0.5360577, 0.5}, 376 | 'v': {-0.0024038462, 0, 0.48317307, 0.5360577, 0.4783654}, 377 | 'w': {0.0072115385, 0, 0.7307692, 0.5360577, 0.7451923}, 378 | 'ı': {0.084134616, 0, 0.088942304, 0.5360577, 0.2596154}, 379 | 'z': {0.040865384, 0, 0.36057693, 0.5360577, 0.43990386}, 380 | '<': {0.050480768, -0.115384616, 0.44951922, 0.49278846, 0.5504808}, 381 | '>': {0.050480768, -0.115384616, 0.44951922, 0.49278846, 0.5504808}, 382 | '÷': {0.048076924, -0.120192304, 0.45432693, 0.46394232, 0.5504808}, 383 | '+': {0.048076924, -0.12740384, 0.45432693, 0.45432693, 0.5504808}, 384 | '*': {0.03846154, -0.30769232, 0.47355768, 0.45192307, 0.5504808}, 385 | '^': {0.01923077, -0.26682693, 0.49278846, 0.45192307, 0.53125}, 386 | '³': {0.014423077, -0.27884614, 0.28605768, 0.44471154, 0.33173078}, 387 | '²': {0.024038462, -0.28605768, 0.28125, 0.4375, 0.33173078}, 388 | '¤': {0.060096152, -0.13701923, 0.43028846, 0.43028846, 0.5504808}, 389 | '¹': {0.028846154, -0.28605768, 0.1875, 0.4278846, 0.33173078}, 390 | '×': {0.067307696, -0.14423077, 0.41826922, 0.4158654, 0.5504808}, 391 | '‹': {0.03846154, -0.05528846, 0.20913461, 0.4158654, 0.28846154}, 392 | '›': {0.040865384, -0.05528846, 0.20913461, 0.4158654, 0.28846154}, 393 | '»': {0.040865384, -0.05528846, 0.40865386, 0.4158654, 0.48798078}, 394 | '«': {0.03846154, -0.05528846, 0.40865386, 0.4158654, 0.48798078}, 395 | 'º': {0.03125, -0.38221154, 0.28846154, 0.33894232, 0.35096154}, 396 | 'ª': {0.033653848, -0.38221154, 0.25, 0.33894232, 0.33173078}, 397 | '°': {0.060096152, -0.4158654, 0.30769232, 0.30769232, 0.4278846}, 398 | '=': {0.050480768, -0.2139423, 0.44951922, 0.27403846, 0.5504808}, 399 | '¬': {0.050480768, -0.12740384, 0.44951922, 0.26201922, 0.5504808}, 400 | '•': {0.072115384, -0.23557693, 0.23076923, 0.25721154, 0.375}, 401 | '\'': {0.06490385, -0.45673078, 0.098557696, 0.25721154, 0.22596154}, 402 | '"': {0.06490385, -0.45673078, 0.27403846, 0.25721154, 0.40144232}, 403 | '„': {0.03125, 0.1298077, 0.34134614, 0.2451923, 0.4375}, 404 | '‚': {0.03125, 0.1298077, 0.15384616, 0.2451923, 0.25}, 405 | '“': {0.012019231, -0.46875, 0.33894232, 0.2451923, 0.36298078}, 406 | '’': {0.012019231, -0.46875, 0.1514423, 0.2451923, 0.17548077}, 407 | ',': {0.03125, 0.1298077, 0.15384616, 0.2451923, 0.25}, 408 | '”': {0.012019231, -0.46875, 0.33894232, 0.2451923, 0.36298078}, 409 | '‘': {0.012019231, -0.46875, 0.1514423, 0.2451923, 0.17548077}, 410 | '¸': {0.016826924, 0.24038461, 0.1826923, 0.24038461, 0.20432693}, 411 | '˚': {0.17788461, -0.6057692, 0.22115384, 0.20913461, 0.5769231}, 412 | '´': {0.1923077, -0.6057692, 0.1923077, 0.1610577, 0.5769231}, 413 | 'ˆ': {0.125, -0.6057692, 0.32451922, 0.1610577, 0.5769231}, 414 | '`': {0.1923077, -0.6057692, 0.1923077, 0.1610577, 0.5769231}, 415 | '~': {0.050480768, -0.28365386, 0.44951922, 0.13701923, 0.5504808}, 416 | '·': {0.072115384, -0.28605768, 0.125, 0.13461539, 0.26923078}, 417 | '.': {0.072115384, 0.014423077, 0.125, 0.13461539, 0.26923078}, 418 | '˜': {0.125, -0.60336536, 0.35096154, 0.13221154, 0.5769231}, 419 | '¨': {0.14903846, -0.6298077, 0.27644232, 0.100961536, 0.5769231}, 420 | '—': {0.040865384, -0.22596154, 0.9182692, 0.084134616, 1}, 421 | '–': {0.040865384, -0.22596154, 0.41826922, 0.084134616, 0.5}, 422 | '­': {0.040865384, -0.22596154, 0.24278846, 0.084134616, 0.3221154}, 423 | '-': {0.040865384, -0.22596154, 0.24278846, 0.084134616, 0.3221154}, 424 | '¯': {-0.0024038462, -0.75961536, 0.5048077, 0.069711536, 0.5}, 425 | '_': {-0.0024038462, 0.15865384, 0.4158654, 0.069711536, 0.41105768}, 426 | ' ': {0, 0, 0, 0, 0.2596154}, 427 | ' ': {0, 0, 0, 0, 0.2596154}, 428 | } 429 | 430 | -------------------------------------------------------------------------------- /icon/icon_mdpi.go: -------------------------------------------------------------------------------- 1 | // generated by makeicons.go; DO NOT EDIT 2 | package icon 3 | 4 | type Icon int 5 | 6 | func (ic Icon) Texcoords() (float32, float32) { 7 | tc := texcoords[int(ic)] 8 | return tc[0], tc[1] 9 | } 10 | 11 | const ( 12 | Action3dRotation Icon = iota 13 | ActionAccessibility 14 | ActionAccessible 15 | ActionAccountBalance 16 | ActionAccountBalanceWallet 17 | ActionAccountBox 18 | ActionAccountCircle 19 | ActionAddShoppingCart 20 | ActionAlarm 21 | ActionAlarmAdd 22 | ActionAlarmOff 23 | ActionAlarmOn 24 | ActionAllOut 25 | ActionAndroid 26 | ActionAnnouncement 27 | ActionAspectRatio 28 | ActionAssessment 29 | ActionAssignment 30 | ActionAssignmentInd 31 | ActionAssignmentLate 32 | ActionAssignmentReturn 33 | ActionAssignmentReturned 34 | ActionAssignmentTurnedIn 35 | ActionAutorenew 36 | ActionBackup 37 | ActionBook 38 | ActionBookmark 39 | ActionBookmarkBorder 40 | ActionBugReport 41 | ActionBuild 42 | ActionCached 43 | ActionCameraEnhance 44 | ActionCardGiftcard 45 | ActionCardMembership 46 | ActionCardTravel 47 | ActionChangeHistory 48 | ActionCheckCircle 49 | ActionChromeReaderMode 50 | ActionClass 51 | ActionCode 52 | ActionCompareArrows 53 | ActionCopyright 54 | ActionCreditCard 55 | ActionDashboard 56 | ActionDateRange 57 | ActionDelete 58 | ActionDescription 59 | ActionDns 60 | ActionDone 61 | ActionDoneAll 62 | ActionDonutLarge 63 | ActionDonutSmall 64 | ActionEject 65 | ActionEvent 66 | ActionEventSeat 67 | ActionExitToApp 68 | ActionExplore 69 | ActionExtension 70 | ActionFace 71 | ActionFavorite 72 | ActionFavoriteBorder 73 | ActionFeedback 74 | ActionFindInPage 75 | ActionFindReplace 76 | ActionFingerprint 77 | ActionFlightLand 78 | ActionFlightTakeoff 79 | ActionFlipToBack 80 | ActionFlipToFront 81 | ActionGavel 82 | ActionGetApp 83 | ActionGif 84 | ActionGrade 85 | ActionGroupWork 86 | ActionHelp 87 | ActionHelpOutline 88 | ActionHighlightOff 89 | ActionHistory 90 | ActionHome 91 | ActionHourglassEmpty 92 | ActionHourglassFull 93 | ActionHttp 94 | ActionHttps 95 | ActionImportantDevices 96 | ActionInfo 97 | ActionInfoOutline 98 | ActionInput 99 | ActionInvertColors 100 | ActionLabel 101 | ActionLabelOutline 102 | ActionLanguage 103 | ActionLaunch 104 | ActionLightbulbOutline 105 | ActionLineStyle 106 | ActionLineWeight 107 | ActionList 108 | ActionLock 109 | ActionLockOpen 110 | ActionLockOutline 111 | ActionLoyalty 112 | ActionMarkunreadMailbox 113 | ActionMotorcycle 114 | ActionNoteAdd 115 | ActionOfflinePin 116 | ActionOpacity 117 | ActionOpenInBrowser 118 | ActionOpenInNew 119 | ActionOpenWith 120 | ActionPageview 121 | ActionPanTool 122 | ActionPayment 123 | ActionPermCameraMic 124 | ActionPermContactCalendar 125 | ActionPermDataSetting 126 | ActionPermDeviceInformation 127 | ActionPermIdentity 128 | ActionPermMedia 129 | ActionPermPhoneMsg 130 | ActionPermScanWifi 131 | ActionPets 132 | ActionPictureInPicture 133 | ActionPictureInPictureAlt 134 | ActionPlayForWork 135 | ActionPolymer 136 | ActionPowerSettingsNew 137 | ActionPregnantWoman 138 | ActionPrint 139 | ActionQueryBuilder 140 | ActionQuestionAnswer 141 | ActionReceipt 142 | ActionRecordVoiceOver 143 | ActionRedeem 144 | ActionReorder 145 | ActionReportProblem 146 | ActionRestore 147 | ActionRoom 148 | ActionRoundedCorner 149 | ActionRowing 150 | ActionSchedule 151 | ActionSearch 152 | ActionSettings 153 | ActionSettingsApplications 154 | ActionSettingsBackupRestore 155 | ActionSettingsBluetooth 156 | ActionSettingsBrightness 157 | ActionSettingsCell 158 | ActionSettingsEthernet 159 | ActionSettingsInputAntenna 160 | ActionSettingsInputComponent 161 | ActionSettingsInputComposite 162 | ActionSettingsInputHdmi 163 | ActionSettingsInputSvideo 164 | ActionSettingsOverscan 165 | ActionSettingsPhone 166 | ActionSettingsPower 167 | ActionSettingsRemote 168 | ActionSettingsVoice 169 | ActionShop 170 | ActionShopTwo 171 | ActionShoppingBasket 172 | ActionShoppingCart 173 | ActionSpeakerNotes 174 | ActionSpellcheck 175 | ActionStars 176 | ActionStore 177 | ActionSubject 178 | ActionSupervisorAccount 179 | ActionSwapHoriz 180 | ActionSwapVert 181 | ActionSwapVerticalCircle 182 | ActionSystemUpdateAlt 183 | ActionTab 184 | ActionTabUnselected 185 | ActionTheaters 186 | ActionThumbDown 187 | ActionThumbUp 188 | ActionThumbsUpDown 189 | ActionTimeline 190 | ActionToc 191 | ActionToday 192 | ActionToll 193 | ActionTouchApp 194 | ActionTrackChanges 195 | ActionTranslate 196 | ActionTrendingDown 197 | ActionTrendingFlat 198 | ActionTrendingUp 199 | ActionTurnedIn 200 | ActionTurnedInNot 201 | ActionUpdate 202 | ActionVerifiedUser 203 | ActionViewAgenda 204 | ActionViewArray 205 | ActionViewCarousel 206 | ActionViewColumn 207 | ActionViewDay 208 | ActionViewHeadline 209 | ActionViewList 210 | ActionViewModule 211 | ActionViewQuilt 212 | ActionViewStream 213 | ActionViewWeek 214 | ActionVisibility 215 | ActionVisibilityOff 216 | ActionWatchLater 217 | ActionWork 218 | ActionYoutubeSearchedFor 219 | ActionZoomIn 220 | ActionZoomOut 221 | AlertAddAlert 222 | AlertError 223 | AlertErrorOutline 224 | AlertWarning 225 | AvAddToQueue 226 | AvAirplay 227 | AvAlbum 228 | AvArtTrack 229 | AvAvTimer 230 | AvClosedCaption 231 | AvEqualizer 232 | AvExplicit 233 | AvFastForward 234 | AvFastRewind 235 | AvFiberDvr 236 | AvFiberManualRecord 237 | AvFiberNew 238 | AvFiberPin 239 | AvFiberSmartRecord 240 | AvForward10 241 | AvForward30 242 | AvForward5 243 | AvGames 244 | AvHd 245 | AvHearing 246 | AvHighQuality 247 | AvLibraryAdd 248 | AvLibraryBooks 249 | AvLibraryMusic 250 | AvLoop 251 | AvMic 252 | AvMicNone 253 | AvMicOff 254 | AvMovie 255 | AvMusicVideo 256 | AvNewReleases 257 | AvNotInterested 258 | AvPause 259 | AvPauseCircleFilled 260 | AvPauseCircleOutline 261 | AvPlayArrow 262 | AvPlayCircleFilled 263 | AvPlayCircleOutline 264 | AvPlaylistAdd 265 | AvPlaylistAddCheck 266 | AvPlaylistPlay 267 | AvQueue 268 | AvQueueMusic 269 | AvQueuePlayNext 270 | AvRadio 271 | AvRecentActors 272 | AvRemoveFromQueue 273 | AvRepeat 274 | AvRepeatOne 275 | AvReplay 276 | AvReplay10 277 | AvReplay30 278 | AvReplay5 279 | AvShuffle 280 | AvSkipNext 281 | AvSkipPrevious 282 | AvSlowMotionVideo 283 | AvSnooze 284 | AvSortByAlpha 285 | AvStop 286 | AvSubscriptions 287 | AvSubtitles 288 | AvSurroundSound 289 | AvVideoLibrary 290 | AvVideocam 291 | AvVideocamOff 292 | AvVolumeDown 293 | AvVolumeMute 294 | AvVolumeOff 295 | AvVolumeUp 296 | AvWeb 297 | AvWebAsset 298 | CommunicationBusiness 299 | CommunicationCall 300 | CommunicationCallEnd 301 | CommunicationCallMade 302 | CommunicationCallMerge 303 | CommunicationCallMissed 304 | CommunicationCallMissedOutgoing 305 | CommunicationCallReceived 306 | CommunicationCallSplit 307 | CommunicationChat 308 | CommunicationChatBubble 309 | CommunicationChatBubbleOutline 310 | CommunicationClearAll 311 | CommunicationComment 312 | CommunicationContactMail 313 | CommunicationContactPhone 314 | CommunicationContacts 315 | CommunicationDialerSip 316 | CommunicationDialpad 317 | CommunicationEmail 318 | CommunicationForum 319 | CommunicationImportContacts 320 | CommunicationImportExport 321 | CommunicationInvertColorsOff 322 | CommunicationLiveHelp 323 | CommunicationLocationOff 324 | CommunicationLocationOn 325 | CommunicationMailOutline 326 | CommunicationMessage 327 | CommunicationNoSim 328 | CommunicationPhone 329 | CommunicationPhonelinkErase 330 | CommunicationPhonelinkLock 331 | CommunicationPhonelinkRing 332 | CommunicationPhonelinkSetup 333 | CommunicationPortableWifiOff 334 | CommunicationPresentToAll 335 | CommunicationRingVolume 336 | CommunicationScreenShare 337 | CommunicationSpeakerPhone 338 | CommunicationStayCurrentLandscape 339 | CommunicationStayCurrentPortrait 340 | CommunicationStayPrimaryLandscape 341 | CommunicationStayPrimaryPortrait 342 | CommunicationStopScreenShare 343 | CommunicationSwapCalls 344 | CommunicationTextsms 345 | CommunicationVoicemail 346 | CommunicationVpnKey 347 | ContentAdd 348 | ContentAddBox 349 | ContentAddCircle 350 | ContentAddCircleOutline 351 | ContentArchive 352 | ContentBackspace 353 | ContentBlock 354 | ContentClear 355 | ContentContentCopy 356 | ContentContentCut 357 | ContentContentPaste 358 | ContentCreate 359 | ContentDrafts 360 | ContentFilterList 361 | ContentFlag 362 | ContentFontDownload 363 | ContentForward 364 | ContentGesture 365 | ContentInbox 366 | ContentLink 367 | ContentMail 368 | ContentMarkunread 369 | ContentMoveToInbox 370 | ContentNextWeek 371 | ContentRedo 372 | ContentRemove 373 | ContentRemoveCircle 374 | ContentRemoveCircleOutline 375 | ContentReply 376 | ContentReplyAll 377 | ContentReport 378 | ContentSave 379 | ContentSelectAll 380 | ContentSend 381 | ContentSort 382 | ContentTextFormat 383 | ContentUnarchive 384 | ContentUndo 385 | ContentWeekend 386 | DeviceAccessAlarm 387 | DeviceAccessAlarms 388 | DeviceAccessTime 389 | DeviceAddAlarm 390 | DeviceAirplanemodeActive 391 | DeviceAirplanemodeInactive 392 | DeviceBattery20 393 | DeviceBattery30 394 | DeviceBattery50 395 | DeviceBattery60 396 | DeviceBattery80 397 | DeviceBattery90 398 | DeviceBatteryAlert 399 | DeviceBatteryCharging20 400 | DeviceBatteryCharging30 401 | DeviceBatteryCharging50 402 | DeviceBatteryCharging60 403 | DeviceBatteryCharging80 404 | DeviceBatteryCharging90 405 | DeviceBatteryChargingFull 406 | DeviceBatteryFull 407 | DeviceBatteryStd 408 | DeviceBatteryUnknown 409 | DeviceBluetooth 410 | DeviceBluetoothConnected 411 | DeviceBluetoothDisabled 412 | DeviceBluetoothSearching 413 | DeviceBrightnessAuto 414 | DeviceBrightnessHigh 415 | DeviceBrightnessLow 416 | DeviceBrightnessMedium 417 | DeviceDataUsage 418 | DeviceDeveloperMode 419 | DeviceDevices 420 | DeviceDvr 421 | DeviceGpsFixed 422 | DeviceGpsNotFixed 423 | DeviceGpsOff 424 | DeviceGraphicEq 425 | DeviceLocationDisabled 426 | DeviceLocationSearching 427 | DeviceNetworkCell 428 | DeviceNetworkWifi 429 | DeviceNfc 430 | DeviceScreenLockLandscape 431 | DeviceScreenLockPortrait 432 | DeviceScreenLockRotation 433 | DeviceScreenRotation 434 | DeviceSdStorage 435 | DeviceSettingsSystemDaydream 436 | DeviceSignalCellular0Bar 437 | DeviceSignalCellular1Bar 438 | DeviceSignalCellular2Bar 439 | DeviceSignalCellular3Bar 440 | DeviceSignalCellular4Bar 441 | DeviceSignalCellularConnectedNoInternet0Bar 442 | DeviceSignalCellularConnectedNoInternet1Bar 443 | DeviceSignalCellularConnectedNoInternet2Bar 444 | DeviceSignalCellularConnectedNoInternet3Bar 445 | DeviceSignalCellularConnectedNoInternet4Bar 446 | DeviceSignalCellularNoSim 447 | DeviceSignalCellularNull 448 | DeviceSignalCellularOff 449 | DeviceSignalWifi0Bar 450 | DeviceSignalWifi1Bar 451 | DeviceSignalWifi1BarLock 452 | DeviceSignalWifi2Bar 453 | DeviceSignalWifi2BarLock 454 | DeviceSignalWifi3Bar 455 | DeviceSignalWifi3BarLock 456 | DeviceSignalWifi4Bar 457 | DeviceSignalWifi4BarLock 458 | DeviceSignalWifiOff 459 | DeviceStorage 460 | DeviceUsb 461 | DeviceWallpaper 462 | DeviceWidgets 463 | DeviceWifiLock 464 | DeviceWifiTethering 465 | EditorAttachFile 466 | EditorAttachMoney 467 | EditorBorderAll 468 | EditorBorderBottom 469 | EditorBorderClear 470 | EditorBorderColor 471 | EditorBorderHorizontal 472 | EditorBorderInner 473 | EditorBorderLeft 474 | EditorBorderOuter 475 | EditorBorderRight 476 | EditorBorderStyle 477 | EditorBorderTop 478 | EditorBorderVertical 479 | EditorDragHandle 480 | EditorFormatAlignCenter 481 | EditorFormatAlignJustify 482 | EditorFormatAlignLeft 483 | EditorFormatAlignRight 484 | EditorFormatBold 485 | EditorFormatClear 486 | EditorFormatColorFill 487 | EditorFormatColorReset 488 | EditorFormatColorText 489 | EditorFormatIndentDecrease 490 | EditorFormatIndentIncrease 491 | EditorFormatItalic 492 | EditorFormatLineSpacing 493 | EditorFormatListBulleted 494 | EditorFormatListNumbered 495 | EditorFormatPaint 496 | EditorFormatQuote 497 | EditorFormatShapes 498 | EditorFormatSize 499 | EditorFormatStrikethrough 500 | EditorFormatTextdirectionLToR 501 | EditorFormatTextdirectionRToL 502 | EditorFormatUnderlined 503 | EditorFunctions 504 | EditorHighlight 505 | EditorInsertChart 506 | EditorInsertComment 507 | EditorInsertDriveFile 508 | EditorInsertEmoticon 509 | EditorInsertInvitation 510 | EditorInsertLink 511 | EditorInsertPhoto 512 | EditorLinearScale 513 | EditorMergeType 514 | EditorModeComment 515 | EditorModeEdit 516 | EditorMoneyOff 517 | EditorPublish 518 | EditorShortText 519 | EditorSpaceBar 520 | EditorStrikethroughS 521 | EditorTextFields 522 | EditorVerticalAlignBottom 523 | EditorVerticalAlignCenter 524 | EditorVerticalAlignTop 525 | EditorWrapText 526 | FileAttachment 527 | FileCloud 528 | FileCloudCircle 529 | FileCloudDone 530 | FileCloudDownload 531 | FileCloudOff 532 | FileCloudQueue 533 | FileCloudUpload 534 | FileCreateNewFolder 535 | FileFileDownload 536 | FileFileUpload 537 | FileFolder 538 | FileFolderOpen 539 | FileFolderShared 540 | HardwareCast 541 | HardwareCastConnected 542 | HardwareComputer 543 | HardwareDesktopMac 544 | HardwareDesktopWindows 545 | HardwareDeveloperBoard 546 | HardwareDeviceHub 547 | HardwareDevicesOther 548 | HardwareDock 549 | HardwareGamepad 550 | HardwareHeadset 551 | HardwareHeadsetMic 552 | HardwareKeyboard 553 | HardwareKeyboardArrowDown 554 | HardwareKeyboardArrowLeft 555 | HardwareKeyboardArrowRight 556 | HardwareKeyboardArrowUp 557 | HardwareKeyboardBackspace 558 | HardwareKeyboardCapslock 559 | HardwareKeyboardHide 560 | HardwareKeyboardReturn 561 | HardwareKeyboardTab 562 | HardwareKeyboardVoice 563 | HardwareLaptop 564 | HardwareLaptopChromebook 565 | HardwareLaptopMac 566 | HardwareLaptopWindows 567 | HardwareMemory 568 | HardwareMouse 569 | HardwarePhoneAndroid 570 | HardwarePhoneIphone 571 | HardwarePhonelink 572 | HardwarePhonelinkOff 573 | HardwarePowerInput 574 | HardwareRouter 575 | HardwareScanner 576 | HardwareSecurity 577 | HardwareSimCard 578 | HardwareSmartphone 579 | HardwareSpeaker 580 | HardwareSpeakerGroup 581 | HardwareTablet 582 | HardwareTabletAndroid 583 | HardwareTabletMac 584 | HardwareToys 585 | HardwareTv 586 | HardwareVideogameAsset 587 | HardwareWatch 588 | ImageAddAPhoto 589 | ImageAddToPhotos 590 | ImageAdjust 591 | ImageAssistant 592 | ImageAssistantPhoto 593 | ImageAudiotrack 594 | ImageBlurCircular 595 | ImageBlurLinear 596 | ImageBlurOff 597 | ImageBlurOn 598 | ImageBrightness1 599 | ImageBrightness2 600 | ImageBrightness3 601 | ImageBrightness4 602 | ImageBrightness5 603 | ImageBrightness6 604 | ImageBrightness7 605 | ImageBrokenImage 606 | ImageBrush 607 | ImageCamera 608 | ImageCameraAlt 609 | ImageCameraFront 610 | ImageCameraRear 611 | ImageCameraRoll 612 | ImageCenterFocusStrong 613 | ImageCenterFocusWeak 614 | ImageCollections 615 | ImageCollectionsBookmark 616 | ImageColorLens 617 | ImageColorize 618 | ImageCompare 619 | ImageControlPoint 620 | ImageControlPointDuplicate 621 | ImageCrop 622 | ImageCrop169 623 | ImageCrop32 624 | ImageCrop54 625 | ImageCrop75 626 | ImageCropDin 627 | ImageCropFree 628 | ImageCropLandscape 629 | ImageCropOriginal 630 | ImageCropPortrait 631 | ImageCropRotate 632 | ImageCropSquare 633 | ImageDehaze 634 | ImageDetails 635 | ImageEdit 636 | ImageExposure 637 | ImageExposureNeg1 638 | ImageExposureNeg2 639 | ImageExposurePlus1 640 | ImageExposurePlus2 641 | ImageExposureZero 642 | ImageFilter 643 | ImageFilter1 644 | ImageFilter2 645 | ImageFilter3 646 | ImageFilter4 647 | ImageFilter5 648 | ImageFilter6 649 | ImageFilter7 650 | ImageFilter8 651 | ImageFilter9 652 | ImageFilter9Plus 653 | ImageFilterBAndW 654 | ImageFilterCenterFocus 655 | ImageFilterDrama 656 | ImageFilterFrames 657 | ImageFilterHdr 658 | ImageFilterNone 659 | ImageFilterTiltShift 660 | ImageFilterVintage 661 | ImageFlare 662 | ImageFlashAuto 663 | ImageFlashOff 664 | ImageFlashOn 665 | ImageFlip 666 | ImageGradient 667 | ImageGrain 668 | ImageGridOff 669 | ImageGridOn 670 | ImageHdrOff 671 | ImageHdrOn 672 | ImageHdrStrong 673 | ImageHdrWeak 674 | ImageHealing 675 | ImageImage 676 | ImageImageAspectRatio 677 | ImageIso 678 | ImageLandscape 679 | ImageLeakAdd 680 | ImageLeakRemove 681 | ImageLens 682 | ImageLinkedCamera 683 | ImageLooks 684 | ImageLooks3 685 | ImageLooks4 686 | ImageLooks5 687 | ImageLooks6 688 | ImageLooksOne 689 | ImageLooksTwo 690 | ImageLoupe 691 | ImageMonochromePhotos 692 | ImageMovieCreation 693 | ImageMovieFilter 694 | ImageMusicNote 695 | ImageNature 696 | ImageNaturePeople 697 | ImageNavigateBefore 698 | ImageNavigateNext 699 | ImagePalette 700 | ImagePanorama 701 | ImagePanoramaFishEye 702 | ImagePanoramaHorizontal 703 | ImagePanoramaVertical 704 | ImagePanoramaWideAngle 705 | ImagePhoto 706 | ImagePhotoAlbum 707 | ImagePhotoCamera 708 | ImagePhotoFilter 709 | ImagePhotoLibrary 710 | ImagePhotoSizeSelectActual 711 | ImagePhotoSizeSelectLarge 712 | ImagePhotoSizeSelectSmall 713 | ImagePictureAsPdf 714 | ImagePortrait 715 | ImageRemoveRedEye 716 | ImageRotate90DegreesCcw 717 | ImageRotateLeft 718 | ImageRotateRight 719 | ImageSlideshow 720 | ImageStraighten 721 | ImageStyle 722 | ImageSwitchCamera 723 | ImageSwitchVideo 724 | ImageTagFaces 725 | ImageTexture 726 | ImageTimelapse 727 | ImageTimer 728 | ImageTimer10 729 | ImageTimer3 730 | ImageTimerOff 731 | ImageTonality 732 | ImageTransform 733 | ImageTune 734 | ImageViewComfy 735 | ImageViewCompact 736 | ImageVignette 737 | ImageWbAuto 738 | ImageWbCloudy 739 | ImageWbIncandescent 740 | ImageWbIridescent 741 | ImageWbSunny 742 | MapsAddLocation 743 | MapsBeenhere 744 | MapsDirections 745 | MapsDirectionsBike 746 | MapsDirectionsBoat 747 | MapsDirectionsBus 748 | MapsDirectionsCar 749 | MapsDirectionsRailway 750 | MapsDirectionsRun 751 | MapsDirectionsSubway 752 | MapsDirectionsTransit 753 | MapsDirectionsWalk 754 | MapsEditLocation 755 | MapsFlight 756 | MapsHotel 757 | MapsLayers 758 | MapsLayersClear 759 | MapsLocalActivity 760 | MapsLocalAirport 761 | MapsLocalAtm 762 | MapsLocalBar 763 | MapsLocalCafe 764 | MapsLocalCarWash 765 | MapsLocalConvenienceStore 766 | MapsLocalDining 767 | MapsLocalDrink 768 | MapsLocalFlorist 769 | MapsLocalGasStation 770 | MapsLocalGroceryStore 771 | MapsLocalHospital 772 | MapsLocalHotel 773 | MapsLocalLaundryService 774 | MapsLocalLibrary 775 | MapsLocalMall 776 | MapsLocalMovies 777 | MapsLocalOffer 778 | MapsLocalParking 779 | MapsLocalPharmacy 780 | MapsLocalPhone 781 | MapsLocalPizza 782 | MapsLocalPlay 783 | MapsLocalPostOffice 784 | MapsLocalPrintshop 785 | MapsLocalSee 786 | MapsLocalShipping 787 | MapsLocalTaxi 788 | MapsMap 789 | MapsMyLocation 790 | MapsNavigation 791 | MapsNearMe 792 | MapsPersonPin 793 | MapsPersonPinCircle 794 | MapsPinDrop 795 | MapsPlace 796 | MapsRateReview 797 | MapsRestaurantMenu 798 | MapsSatellite 799 | MapsStoreMallDirectory 800 | MapsTerrain 801 | MapsTraffic 802 | MapsZoomOutMap 803 | NavigationApps 804 | NavigationArrowBack 805 | NavigationArrowDownward 806 | NavigationArrowDropDown 807 | NavigationArrowDropDownCircle 808 | NavigationArrowDropUp 809 | NavigationArrowForward 810 | NavigationArrowUpward 811 | NavigationCancel 812 | NavigationCheck 813 | NavigationChevronLeft 814 | NavigationChevronRight 815 | NavigationClose 816 | NavigationExpandLess 817 | NavigationExpandMore 818 | NavigationFullscreen 819 | NavigationFullscreenExit 820 | NavigationMenu 821 | NavigationMoreHoriz 822 | NavigationMoreVert 823 | NavigationRefresh 824 | NavigationSubdirectoryArrowLeft 825 | NavigationSubdirectoryArrowRight 826 | NavigationUnfoldLess 827 | NavigationUnfoldMore 828 | NotificationAdb 829 | NotificationAirlineSeatFlat 830 | NotificationAirlineSeatFlatAngled 831 | NotificationAirlineSeatIndividualSuite 832 | NotificationAirlineSeatLegroomExtra 833 | NotificationAirlineSeatLegroomNormal 834 | NotificationAirlineSeatLegroomReduced 835 | NotificationAirlineSeatReclineExtra 836 | NotificationAirlineSeatReclineNormal 837 | NotificationBluetoothAudio 838 | NotificationConfirmationNumber 839 | NotificationDiscFull 840 | NotificationDoNotDisturb 841 | NotificationDoNotDisturbAlt 842 | NotificationDriveEta 843 | NotificationEnhancedEncryption 844 | NotificationEventAvailable 845 | NotificationEventBusy 846 | NotificationEventNote 847 | NotificationFolderSpecial 848 | NotificationLiveTv 849 | NotificationMms 850 | NotificationMore 851 | NotificationNetworkCheck 852 | NotificationNetworkLocked 853 | NotificationNoEncryption 854 | NotificationOndemandVideo 855 | NotificationPersonalVideo 856 | NotificationPhoneBluetoothSpeaker 857 | NotificationPhoneForwarded 858 | NotificationPhoneInTalk 859 | NotificationPhoneLocked 860 | NotificationPhoneMissed 861 | NotificationPhonePaused 862 | NotificationPower 863 | NotificationRvHookup 864 | NotificationSdCard 865 | NotificationSimCardAlert 866 | NotificationSms 867 | NotificationSmsFailed 868 | NotificationSync 869 | NotificationSyncDisabled 870 | NotificationSyncProblem 871 | NotificationSystemUpdate 872 | NotificationTapAndPlay 873 | NotificationTimeToLeave 874 | NotificationVibration 875 | NotificationVoiceChat 876 | NotificationVpnLock 877 | NotificationWc 878 | NotificationWifi 879 | PlacesAcUnit 880 | PlacesAirportShuttle 881 | PlacesAllInclusive 882 | PlacesBeachAccess 883 | PlacesBusinessCenter 884 | PlacesCasino 885 | PlacesChildCare 886 | PlacesChildFriendly 887 | PlacesFitnessCenter 888 | PlacesFreeBreakfast 889 | PlacesGolfCourse 890 | PlacesHotTub 891 | PlacesKitchen 892 | PlacesPool 893 | PlacesRoomService 894 | PlacesSmokeFree 895 | PlacesSmokingRooms 896 | PlacesSpa 897 | SocialCake 898 | SocialDomain 899 | SocialGroup 900 | SocialGroupAdd 901 | SocialLocationCity 902 | SocialMood 903 | SocialMoodBad 904 | SocialNotifications 905 | SocialNotificationsActive 906 | SocialNotificationsNone 907 | SocialNotificationsOff 908 | SocialNotificationsPaused 909 | SocialPages 910 | SocialPartyMode 911 | SocialPeople 912 | SocialPeopleOutline 913 | SocialPerson 914 | SocialPersonAdd 915 | SocialPersonOutline 916 | SocialPlusOne 917 | SocialPoll 918 | SocialPublic 919 | SocialSchool 920 | SocialShare 921 | SocialWhatshot 922 | ToggleStar 923 | ToggleStarBorder 924 | ToggleStarHalf 925 | ) 926 | 927 | var texcoords = [][2]float32{ 928 | {0.2109375, 0.28125},{0.234375, 0.28125},{0.2578125, 0.28125},{0.28125, 0.28125},{0.3046875, 0},{0.3046875, 0.0234375},{0.3046875, 0.046875},{0.3046875, 0.0703125},{0.3046875, 0.1171875},{0.3046875, 0.09375},{0.3046875, 0.140625},{0.3046875, 0.1640625},{0.3046875, 0.1875},{0.3046875, 0.2109375},{0.3046875, 0.234375},{0.3046875, 0.2578125},{0.3046875, 0.28125},{0, 0.3046875},{0.0234375, 0.3046875},{0.046875, 0.3046875},{0.0703125, 0.3046875},{0.09375, 0.3046875},{0.1171875, 0.3046875},{0.140625, 0.3046875},{0.1640625, 0.3046875},{0.1875, 0.3046875},{0.2109375, 0.3046875},{0.234375, 0.3046875},{0.2578125, 0.3046875},{0.28125, 0.3046875},{0.3046875, 0.3046875},{0.328125, 0},{0.328125, 0.0234375},{0.328125, 0.046875},{0.328125, 0.0703125},{0.328125, 0.09375},{0.328125, 0.1171875},{0.328125, 0.140625},{0.328125, 0.1640625},{0.328125, 0.1875},{0.328125, 0.2109375},{0.328125, 0.234375},{0.328125, 0.2578125},{0.328125, 0.28125},{0.328125, 0.3046875},{0, 0.328125},{0.0234375, 0.328125},{0.046875, 0.328125},{0.09375, 0.328125},{0.0703125, 0.328125},{0.1171875, 0.328125},{0.140625, 0.328125},{0.1640625, 0.328125},{0.1875, 0.328125},{0.2109375, 0.328125},{0.234375, 0.328125},{0.2578125, 0.328125},{0.28125, 0.328125},{0.3046875, 0.328125},{0.328125, 0.328125},{0.3515625, 0},{0.3515625, 0.0234375},{0.3515625, 0.046875},{0.3515625, 0.0703125},{0.3515625, 0.09375},{0.3515625, 0.1171875},{0.3515625, 0.140625},{0.3515625, 0.1640625},{0.3515625, 0.1875},{0.3515625, 0.2109375},{0.3515625, 0.234375},{0.3515625, 0.2578125},{0.3515625, 0.28125},{0.3515625, 0.3046875},{0.3515625, 0.328125},{0, 0.3515625},{0.0234375, 0.3515625},{0.046875, 0.3515625},{0.0703125, 0.3515625},{0.09375, 0.3515625},{0.1171875, 0.3515625},{0.140625, 0.3515625},{0.1640625, 0.3515625},{0.1875, 0.3515625},{0.2109375, 0.3515625},{0.234375, 0.3515625},{0.2578125, 0.3515625},{0.28125, 0.3515625},{0.3046875, 0.3515625},{0.328125, 0.3515625},{0.3515625, 0.3515625},{0.375, 0},{0.375, 0.0234375},{0.375, 0.046875},{0.375, 0.0703125},{0.375, 0.09375},{0.375, 0.1171875},{0.375, 0.140625},{0.375, 0.1640625},{0.375, 0.1875},{0.375, 0.2109375},{0.375, 0.234375},{0.375, 0.2578125},{0.375, 0.28125},{0.375, 0.3046875},{0.375, 0.328125},{0.375, 0.3515625},{0, 0.375},{0.0234375, 0.375},{0.046875, 0.375},{0.0703125, 0.375},{0.09375, 0.375},{0.1171875, 0.375},{0.140625, 0.375},{0.1640625, 0.375},{0.1875, 0.375},{0.2109375, 0.375},{0.234375, 0.375},{0.2578125, 0.375},{0.28125, 0.375},{0.328125, 0.375},{0.3046875, 0.375},{0.3515625, 0.375},{0.375, 0.375},{0.3984375, 0},{0.3984375, 0.0234375},{0.3984375, 0.046875},{0.3984375, 0.0703125},{0.3984375, 0.09375},{0.3984375, 0.1171875},{0.3984375, 0.140625},{0.3984375, 0.1640625},{0.3984375, 0.1875},{0.3984375, 0.2109375},{0.3984375, 0.234375},{0.3984375, 0.2578125},{0.3984375, 0.28125},{0.3984375, 0.3046875},{0.3984375, 0.328125},{0.3984375, 0.3515625},{0.0234375, 0.3984375},{0.3984375, 0.375},{0, 0.3984375},{0.046875, 0.3984375},{0.0703125, 0.3984375},{0.09375, 0.3984375},{0.1171875, 0.3984375},{0.140625, 0.3984375},{0.1640625, 0.3984375},{0.1875, 0.3984375},{0.2109375, 0.3984375},{0.234375, 0.3984375},{0.2578125, 0.3984375},{0.28125, 0.3984375},{0.3046875, 0.3984375},{0.328125, 0.3984375},{0.3515625, 0.3984375},{0.375, 0.3984375},{0.3984375, 0.3984375},{0.421875, 0},{0.421875, 0.0234375},{0.421875, 0.046875},{0.421875, 0.0703125},{0.421875, 0.09375},{0.421875, 0.1171875},{0.421875, 0.140625},{0.421875, 0.1640625},{0.421875, 0.1875},{0.421875, 0.2109375},{0.421875, 0.234375},{0.421875, 0.2578125},{0.421875, 0.28125},{0.421875, 0.3046875},{0.421875, 0.328125},{0.421875, 0.3515625},{0.421875, 0.375},{0.421875, 0.3984375},{0, 0.421875},{0.0234375, 0.421875},{0.046875, 0.421875},{0.0703125, 0.421875},{0.09375, 0.421875},{0.1171875, 0.421875},{0.140625, 0.421875},{0.1640625, 0.421875},{0.1875, 0.421875},{0.2109375, 0.421875},{0.234375, 0.421875},{0.2578125, 0.421875},{0.28125, 0.421875},{0.3046875, 0.421875},{0.328125, 0.421875},{0.3515625, 0.421875},{0.375, 0.421875},{0.3984375, 0.421875},{0.421875, 0.421875},{0.4453125, 0},{0.4453125, 0.0234375},{0.4453125, 0.046875},{0.4453125, 0.0703125},{0.4453125, 0.09375},{0.4453125, 0.1171875},{0.4453125, 0.140625},{0.4453125, 0.1640625},{0.4453125, 0.1875},{0.4453125, 0.2109375},{0.4453125, 0.234375},{0.4453125, 0.2578125},{0.4453125, 0.28125},{0.0234375, 0},{0.4921875, 0.3515625},{0, 0.0234375},{0.0234375, 0.0234375},{0.046875, 0},{0.046875, 0.0234375},{0, 0.046875},{0.0234375, 0.046875},{0.046875, 0.046875},{0.0703125, 0},{0.0703125, 0.0234375},{0.0703125, 0.046875},{0, 0.0703125},{0.0234375, 0.0703125},{0.046875, 0.0703125},{0.0703125, 0.0703125},{0.09375, 0},{0.09375, 0.0234375},{0.09375, 0.046875},{0.09375, 0.0703125},{0, 0.09375},{0.0234375, 0.09375},{0.046875, 0.09375},{0.0703125, 0.09375},{0.09375, 0.09375},{0.1171875, 0},{0.1171875, 0.0234375},{0.1171875, 0.046875},{0.1171875, 0.0703125},{0.1171875, 0.09375},{0, 0.1171875},{0.0234375, 0.1171875},{0.046875, 0.1171875},{0.0703125, 0.1171875},{0.09375, 0.1171875},{0.1171875, 0.1171875},{0.140625, 0},{0.140625, 0.0234375},{0.140625, 0.046875},{0.140625, 0.0703125},{0.140625, 0.09375},{0.140625, 0.1171875},{0, 0.140625},{0.0234375, 0.140625},{0.046875, 0.140625},{0.0703125, 0.140625},{0.09375, 0.140625},{0.1171875, 0.140625},{0.140625, 0.140625},{0.1640625, 0},{0.1640625, 0.0234375},{0.1640625, 0.046875},{0.1640625, 0.0703125},{0.1640625, 0.09375},{0.0234375, 0.1640625},{0.1640625, 0.1171875},{0.1640625, 0.140625},{0, 0.1640625},{0.046875, 0.1640625},{0.0703125, 0.1640625},{0.09375, 0.1640625},{0.1171875, 0.1640625},{0.140625, 0.1640625},{0.1640625, 0.1640625},{0.1875, 0},{0.1875, 0.0234375},{0.1875, 0.046875},{0.1875, 0.0703125},{0.1875, 0.09375},{0.1875, 0.1171875},{0.1875, 0.140625},{0.1875, 0.1640625},{0, 0.1875},{0.0234375, 0.1875},{0.046875, 0.1875},{0.09375, 0.1875},{0.0703125, 0.1875},{0.1171875, 0.1875},{0.140625, 0.1875},{0.1640625, 0.1875},{0.1875, 0.1875},{0.2109375, 0},{0.2109375, 0.0234375},{0.2109375, 0.046875},{0.2109375, 0.0703125},{0.2109375, 0.09375},{0.2109375, 0.1171875},{0.2109375, 0.140625},{0.2109375, 0.1640625},{0.2109375, 0.1875},{0, 0.2109375},{0.0234375, 0.2109375},{0.046875, 0.2109375},{0.0703125, 0.2109375},{0.09375, 0.2109375},{0.1171875, 0.2109375},{0.140625, 0.2109375},{0.1640625, 0.2109375},{0.1875, 0.2109375},{0.2109375, 0.2109375},{0.234375, 0},{0.234375, 0.0234375},{0.234375, 0.046875},{0.234375, 0.0703125},{0.234375, 0.09375},{0.234375, 0.1171875},{0.234375, 0.140625},{0.234375, 0.1640625},{0.234375, 0.1875},{0.234375, 0.2109375},{0, 0.234375},{0.0234375, 0.234375},{0.046875, 0.234375},{0.0703125, 0.234375},{0.09375, 0.234375},{0.1171875, 0.234375},{0.140625, 0.234375},{0.1640625, 0.234375},{0.1875, 0.234375},{0.2109375, 0.234375},{0.234375, 0.234375},{0.2578125, 0},{0.2578125, 0.0234375},{0.2578125, 0.046875},{0.2578125, 0.0703125},{0.2578125, 0.09375},{0.2578125, 0.1171875},{0.2578125, 0.140625},{0.2578125, 0.1640625},{0.2578125, 0.1875},{0.2578125, 0.2109375},{0.2578125, 0.234375},{0, 0.2578125},{0.0234375, 0.2578125},{0.046875, 0.2578125},{0.0703125, 0.2578125},{0.09375, 0.2578125},{0.1171875, 0.2578125},{0.140625, 0.2578125},{0.1640625, 0.2578125},{0.1875, 0.2578125},{0.2109375, 0.2578125},{0.234375, 0.2578125},{0.2578125, 0.2578125},{0.28125, 0},{0.28125, 0.0234375},{0.28125, 0.046875},{0.28125, 0.0703125},{0.28125, 0.09375},{0.28125, 0.1171875},{0.28125, 0.140625},{0.28125, 0.1640625},{0.28125, 0.1875},{0.28125, 0.2109375},{0.28125, 0.2578125},{0.28125, 0.234375},{0, 0.28125},{0.0234375, 0.28125},{0.046875, 0.28125},{0.0703125, 0.28125},{0.09375, 0.28125},{0.1171875, 0.28125},{0.140625, 0.28125},{0.1640625, 0.28125},{0.1875, 0.28125},{0.4453125, 0.3046875},{0.4453125, 0.328125},{0.4453125, 0.3515625},{0.4453125, 0.375},{0.4453125, 0.3984375},{0.4453125, 0.421875},{0, 0.4453125},{0.0234375, 0.4453125},{0.046875, 0.4453125},{0.0703125, 0.4453125},{0.09375, 0.4453125},{0.1171875, 0.4453125},{0.140625, 0.4453125},{0.1640625, 0.4453125},{0.1875, 0.4453125},{0.2109375, 0.4453125},{0.234375, 0.4453125},{0.2578125, 0.4453125},{0.28125, 0.4453125},{0.3046875, 0.4453125},{0.328125, 0.4453125},{0.3515625, 0.4453125},{0.375, 0.4453125},{0.3984375, 0.4453125},{0.421875, 0.4453125},{0.4453125, 0.4453125},{0.46875, 0},{0.46875, 0.0234375},{0.46875, 0.046875},{0.46875, 0.0703125},{0.46875, 0.09375},{0.46875, 0.1171875},{0.46875, 0.140625},{0.46875, 0.1640625},{0.46875, 0.1875},{0.46875, 0.2109375},{0.46875, 0.234375},{0.46875, 0.2578125},{0.46875, 0.28125},{0.46875, 0.3046875},{0.46875, 0.328125},{0.46875, 0.3515625},{0.46875, 0.375},{0.46875, 0.3984375},{0.46875, 0.421875},{0.46875, 0.4453125},{0, 0.46875},{0.0234375, 0.46875},{0.046875, 0.46875},{0.0703125, 0.46875},{0.09375, 0.46875},{0.1171875, 0.46875},{0.140625, 0.46875},{0.1640625, 0.46875},{0.1875, 0.46875},{0.2109375, 0.46875},{0.234375, 0.46875},{0.2578125, 0.46875},{0.28125, 0.46875},{0.3046875, 0.46875},{0.328125, 0.46875},{0.3515625, 0.46875},{0.375, 0.46875},{0.3984375, 0.46875},{0.421875, 0.46875},{0.4453125, 0.46875},{0.46875, 0.46875},{0.4921875, 0},{0.4921875, 0.0234375},{0.4921875, 0.046875},{0.4921875, 0.0703125},{0.4921875, 0.09375},{0.4921875, 0.1171875},{0.4921875, 0.140625},{0.4921875, 0.1640625},{0.4921875, 0.1875},{0.4921875, 0.2109375},{0.4921875, 0.234375},{0.4921875, 0.2578125},{0.4921875, 0.28125},{0.4921875, 0.3046875},{0.4921875, 0.328125},{0, 0},{0.4921875, 0.375},{0.4921875, 0.3984375},{0.4921875, 0.421875},{0.4921875, 0.4453125},{0.4921875, 0.46875},{0, 0.4921875},{0.0234375, 0.4921875},{0.046875, 0.4921875},{0.0703125, 0.4921875},{0.09375, 0.4921875},{0.1171875, 0.4921875},{0.140625, 0.4921875},{0.1640625, 0.4921875},{0.1875, 0.4921875},{0.2109375, 0.4921875},{0.234375, 0.4921875},{0.2578125, 0.4921875},{0.28125, 0.4921875},{0.3046875, 0.4921875},{0.328125, 0.4921875},{0.3515625, 0.4921875},{0.375, 0.4921875},{0.3984375, 0.4921875},{0.421875, 0.4921875},{0.4453125, 0.4921875},{0.46875, 0.4921875},{0.4921875, 0.4921875},{0.515625, 0},{0.515625, 0.0234375},{0.515625, 0.046875},{0.515625, 0.0703125},{0.515625, 0.09375},{0.515625, 0.1171875},{0.515625, 0.140625},{0.515625, 0.1640625},{0.515625, 0.1875},{0.515625, 0.2109375},{0.515625, 0.234375},{0.515625, 0.2578125},{0.515625, 0.28125},{0.515625, 0.3046875},{0.515625, 0.328125},{0.515625, 0.3515625},{0.515625, 0.375},{0.515625, 0.3984375},{0.515625, 0.421875},{0.515625, 0.4453125},{0.515625, 0.46875},{0.515625, 0.4921875},{0, 0.515625},{0.0234375, 0.515625},{0.046875, 0.515625},{0.0703125, 0.515625},{0.09375, 0.515625},{0.1171875, 0.515625},{0.140625, 0.515625},{0.1640625, 0.515625},{0.1875, 0.515625},{0.2109375, 0.515625},{0.234375, 0.515625},{0.2578125, 0.515625},{0.28125, 0.515625},{0.3046875, 0.515625},{0.328125, 0.515625},{0.3515625, 0.515625},{0.375, 0.515625},{0.3984375, 0.515625},{0.421875, 0.515625},{0.4453125, 0.515625},{0.46875, 0.515625},{0.4921875, 0.515625},{0.515625, 0.515625},{0.5390625, 0},{0.5390625, 0.0234375},{0.5390625, 0.046875},{0.5390625, 0.0703125},{0.5390625, 0.09375},{0.5390625, 0.1171875},{0.5390625, 0.140625},{0.5390625, 0.1640625},{0.5390625, 0.1875},{0.5390625, 0.2109375},{0.5390625, 0.234375},{0.5390625, 0.375},{0.5390625, 0.2578125},{0.5390625, 0.28125},{0.5390625, 0.3046875},{0.5390625, 0.328125},{0.5390625, 0.3515625},{0.5390625, 0.3984375},{0.5390625, 0.421875},{0.5390625, 0.4453125},{0.5390625, 0.46875},{0.5390625, 0.4921875},{0.5390625, 0.515625},{0, 0.5390625},{0.0234375, 0.5390625},{0.046875, 0.5390625},{0.0703125, 0.5390625},{0.09375, 0.5390625},{0.1171875, 0.5390625},{0.140625, 0.5390625},{0.1640625, 0.5390625},{0.1875, 0.5390625},{0.2109375, 0.5390625},{0.234375, 0.5390625},{0.2578125, 0.5390625},{0.28125, 0.5390625},{0.3046875, 0.5390625},{0.328125, 0.5390625},{0.3515625, 0.5390625},{0.375, 0.5390625},{0.421875, 0.5390625},{0.3984375, 0.5390625},{0.4453125, 0.5390625},{0.46875, 0.5390625},{0.4921875, 0.5390625},{0.515625, 0.5390625},{0.5390625, 0.5390625},{0.28125, 0.5859375},{0.3046875, 0.5859375},{0.328125, 0.5859375},{0.3515625, 0.5859375},{0.375, 0.5859375},{0.3984375, 0.5859375},{0.421875, 0.5859375},{0.4453125, 0.5859375},{0.46875, 0.5859375},{0.4921875, 0.5859375},{0.515625, 0.5859375},{0.5390625, 0.5859375},{0.5625, 0.5859375},{0.5859375, 0.5859375},{0.609375, 0},{0.609375, 0.0234375},{0.609375, 0.046875},{0.609375, 0.0703125},{0.609375, 0.09375},{0.609375, 0.140625},{0.609375, 0.1171875},{0.609375, 0.1640625},{0.609375, 0.1875},{0.609375, 0.2109375},{0.609375, 0.234375},{0.609375, 0.2578125},{0.609375, 0.28125},{0.609375, 0.3046875},{0.609375, 0.328125},{0.609375, 0.3515625},{0.609375, 0.375},{0.609375, 0.3984375},{0.609375, 0.421875},{0.609375, 0.5390625},{0.609375, 0.4453125},{0.609375, 0.46875},{0.609375, 0.4921875},{0.609375, 0.515625},{0.609375, 0.5625},{0.609375, 0.5859375},{0, 0.609375},{0.0234375, 0.609375},{0.046875, 0.609375},{0.0703125, 0.609375},{0.09375, 0.609375},{0.1171875, 0.609375},{0.140625, 0.609375},{0.1640625, 0.609375},{0.1875, 0.609375},{0.2109375, 0.609375},{0.234375, 0.609375},{0.2578125, 0.609375},{0.28125, 0.609375},{0.3046875, 0.609375},{0.5859375, 0.609375},{0.328125, 0.609375},{0.3515625, 0.609375},{0.375, 0.609375},{0.3984375, 0.609375},{0.421875, 0.609375},{0.4453125, 0.609375},{0.46875, 0.609375},{0.4921875, 0.609375},{0.515625, 0.609375},{0.5390625, 0.609375},{0.5625, 0.609375},{0.609375, 0.609375},{0.6328125, 0},{0.6328125, 0.0234375},{0.6328125, 0.046875},{0.6328125, 0.0703125},{0.6328125, 0.09375},{0.6328125, 0.1171875},{0.6328125, 0.140625},{0.6328125, 0.1640625},{0.6328125, 0.1875},{0.6328125, 0.2109375},{0.6328125, 0.234375},{0.6328125, 0.2578125},{0.6328125, 0.28125},{0.6328125, 0.3046875},{0.6328125, 0.328125},{0.6328125, 0.3515625},{0.6328125, 0.375},{0.6328125, 0.3984375},{0.6328125, 0.421875},{0.6328125, 0.4453125},{0.6328125, 0.4921875},{0.6328125, 0.46875},{0.6328125, 0.515625},{0.6328125, 0.5390625},{0.6328125, 0.5625},{0.6328125, 0.5859375},{0.6328125, 0.609375},{0, 0.6328125},{0.1171875, 0.6328125},{0.0234375, 0.6328125},{0.046875, 0.6328125},{0.0703125, 0.6328125},{0.09375, 0.6328125},{0.140625, 0.6328125},{0.1640625, 0.6328125},{0.1875, 0.6328125},{0.2109375, 0.6328125},{0.234375, 0.6328125},{0.2578125, 0.6328125},{0.28125, 0.6328125},{0.3046875, 0.6328125},{0.328125, 0.6328125},{0.3515625, 0.6328125},{0.375, 0.6328125},{0.3984375, 0.6328125},{0.421875, 0.6328125},{0.4453125, 0.6328125},{0.46875, 0.6328125},{0.4921875, 0.6328125},{0.515625, 0.6328125},{0.5625, 0.6328125},{0.5390625, 0.6328125},{0.5859375, 0.6328125},{0.609375, 0.6328125},{0.6328125, 0.6328125},{0.65625, 0},{0.65625, 0.0234375},{0.65625, 0.046875},{0.65625, 0.0703125},{0.65625, 0.09375},{0.65625, 0.1171875},{0.65625, 0.140625},{0.65625, 0.1640625},{0.65625, 0.1875},{0.65625, 0.2109375},{0.65625, 0.234375},{0.65625, 0.2578125},{0.65625, 0.28125},{0.65625, 0.3046875},{0.65625, 0.328125},{0.65625, 0.3515625},{0.65625, 0.375},{0.65625, 0.4453125},{0.65625, 0.3984375},{0.65625, 0.421875},{0.65625, 0.46875},{0.65625, 0.4921875},{0.65625, 0.515625},{0.65625, 0.5390625},{0.65625, 0.5625},{0.65625, 0.5859375},{0.65625, 0.609375},{0.65625, 0.6328125},{0, 0.65625},{0.0234375, 0.65625},{0.046875, 0.65625},{0.0703125, 0.65625},{0.5625, 0},{0.5625, 0.0234375},{0.5625, 0.0703125},{0.5625, 0.046875},{0.5625, 0.09375},{0.5625, 0.1171875},{0.5625, 0.140625},{0.5625, 0.1640625},{0.5625, 0.1875},{0.5625, 0.2109375},{0.5625, 0.234375},{0.5625, 0.2578125},{0.5625, 0.28125},{0.5625, 0.3046875},{0.5625, 0.328125},{0.5625, 0.3515625},{0.5625, 0.375},{0.5625, 0.3984375},{0.5625, 0.421875},{0.5625, 0.4453125},{0.5625, 0.46875},{0.5625, 0.4921875},{0.5625, 0.515625},{0.5625, 0.5390625},{0, 0.5625},{0.0234375, 0.5625},{0.046875, 0.5625},{0.0703125, 0.5625},{0.09375, 0.5625},{0.1171875, 0.5625},{0.140625, 0.5625},{0.1640625, 0.5625},{0.1875, 0.5625},{0.2109375, 0.5625},{0.234375, 0.5625},{0.2578125, 0.5625},{0.28125, 0.5625},{0.3046875, 0.5625},{0.328125, 0.5625},{0.3515625, 0.5625},{0.375, 0.5625},{0.3984375, 0.5625},{0.421875, 0.5625},{0.4453125, 0.5625},{0.46875, 0.5625},{0.4921875, 0.5625},{0.515625, 0.5625},{0.5390625, 0.5625},{0.5625, 0.5625},{0.5859375, 0},{0.5859375, 0.0234375},{0.5859375, 0.046875},{0.5859375, 0.0703125},{0.5859375, 0.09375},{0.5859375, 0.1171875},{0.5859375, 0.140625},{0.5859375, 0.1640625},{0.5859375, 0.1875},{0.5859375, 0.2109375},{0.5859375, 0.234375},{0.5859375, 0.2578125},{0.5859375, 0.28125},{0.5859375, 0.3046875},{0.5859375, 0.328125},{0.5859375, 0.3515625},{0.5859375, 0.375},{0.5859375, 0.3984375},{0.5859375, 0.421875},{0.5859375, 0.4453125},{0.5859375, 0.46875},{0.5859375, 0.4921875},{0.5859375, 0.515625},{0.5859375, 0.5390625},{0.5859375, 0.5625},{0, 0.5859375},{0.0234375, 0.5859375},{0.046875, 0.5859375},{0.0703125, 0.5859375},{0.09375, 0.5859375},{0.1171875, 0.5859375},{0.140625, 0.5859375},{0.1640625, 0.5859375},{0.1875, 0.5859375},{0.2109375, 0.5859375},{0.234375, 0.5859375},{0.2578125, 0.5859375},{0.5859375, 0.65625},{0.6328125, 0.65625},{0.609375, 0.65625},{0.65625, 0.65625},{0.6796875, 0},{0.6796875, 0.0234375},{0.6796875, 0.046875},{0.6796875, 0.0703125},{0.6796875, 0.09375},{0.6796875, 0.1171875},{0.6796875, 0.140625},{0.6796875, 0.1640625},{0.6796875, 0.2109375},{0.6796875, 0.1875},{0.6796875, 0.234375},{0.6796875, 0.2578125},{0.6796875, 0.28125},{0.6796875, 0.3046875},{0.6796875, 0.328125},{0.6796875, 0.3515625},{0.6796875, 0.375},{0.6796875, 0.3984375},{0.6796875, 0.421875},{0.6796875, 0.4453125},{0.6796875, 0.46875},{0.6796875, 0.4921875},{0.6796875, 0.515625},{0.6796875, 0.5390625},{0.6796875, 0.5625},{0.6796875, 0.5859375},{0.6796875, 0.609375},{0.6796875, 0.6328125},{0.6796875, 0.65625},{0, 0.6796875},{0.0234375, 0.6796875},{0.046875, 0.6796875},{0.0703125, 0.6796875},{0.09375, 0.6796875},{0.1171875, 0.6796875},{0.140625, 0.6796875},{0.1640625, 0.6796875},{0.1875, 0.6796875},{0.2109375, 0.6796875},{0.234375, 0.6796875},{0.2578125, 0.6796875},{0.28125, 0.6796875},{0.3046875, 0.6796875},{0.328125, 0.6796875},{0.3515625, 0.6796875},{0.375, 0.6796875},{0.3984375, 0.6796875},{0.09375, 0.65625},{0.1171875, 0.65625},{0.140625, 0.65625},{0.1640625, 0.65625},{0.1875, 0.65625},{0.2109375, 0.65625},{0.234375, 0.65625},{0.2578125, 0.65625},{0.28125, 0.65625},{0.3046875, 0.65625},{0.328125, 0.65625},{0.3515625, 0.65625},{0.375, 0.65625},{0.3984375, 0.65625},{0.421875, 0.65625},{0.4453125, 0.65625},{0.46875, 0.65625},{0.4921875, 0.65625},{0.421875, 0.6796875},{0.4453125, 0.6796875},{0.4921875, 0.6796875},{0.46875, 0.6796875},{0.515625, 0.6796875},{0.5625, 0.6796875},{0.5390625, 0.6796875},{0.609375, 0.6796875},{0.5859375, 0.6796875},{0.6328125, 0.6796875},{0.65625, 0.6796875},{0.6796875, 0.6796875},{0.703125, 0},{0.703125, 0.0234375},{0.703125, 0.046875},{0.703125, 0.0703125},{0.703125, 0.1171875},{0.703125, 0.09375},{0.703125, 0.140625},{0.703125, 0.1640625},{0.703125, 0.1875},{0.703125, 0.2109375},{0.703125, 0.234375},{0.703125, 0.2578125},{0.703125, 0.28125},{0.515625, 0.65625},{0.5390625, 0.65625},{0.5625, 0.65625}, 929 | } 930 | --------------------------------------------------------------------------------