/
imageview.go
127 lines (112 loc) · 3.06 KB
/
imageview.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// Package button implements a view that can display an image.
package imageview
import (
"image"
"image/color"
_ "image/jpeg"
_ "image/png"
"gomatcha.io/matcha"
"gomatcha.io/matcha/app"
"gomatcha.io/matcha/comm"
"gomatcha.io/matcha/layout"
"gomatcha.io/matcha/paint"
"gomatcha.io/matcha/pb"
"gomatcha.io/matcha/pb/view/imageview"
"gomatcha.io/matcha/view"
)
type ResizeMode int
const (
// The image is resized proportionally such that a single axis is filled.
ResizeModeFit ResizeMode = iota
// The image is resized proportionally such that the entire view is filled.
ResizeModeFill
// The image is stretched to fill the view.
ResizeModeStretch
// The image is centered in the view with its natural size.
ResizeModeCenter
)
func (m ResizeMode) MarshalProtobuf() imageview.ResizeMode {
return imageview.ResizeMode(m)
}
type layouter struct {
bounds image.Rectangle
scale float64
resizeMode ResizeMode
}
func (l *layouter) Layout(ctx *layout.Context) (layout.Guide, map[matcha.Id]layout.Guide) {
g := layout.Guide{Frame: layout.Rect{Max: ctx.MaxSize}}
switch l.resizeMode {
case ResizeModeFit:
imgRatio := float64(l.bounds.Dx()) / l.scale / float64(l.bounds.Dy()) / l.scale
maxRatio := ctx.MaxSize.X / ctx.MaxSize.Y
if imgRatio > maxRatio {
g.Frame.Max = layout.Pt(ctx.MaxSize.X, ctx.MaxSize.X/imgRatio)
} else {
g.Frame.Max = layout.Pt(ctx.MaxSize.Y/imgRatio, ctx.MaxSize.Y)
}
case ResizeModeFill:
fallthrough
case ResizeModeStretch:
g.Frame.Max = ctx.MaxSize
case ResizeModeCenter:
g.Frame.Max = layout.Pt(float64(l.bounds.Dx())/l.scale, float64(l.bounds.Dy())/l.scale)
}
return g, nil
}
func (l *layouter) Notify(f func()) comm.Id {
return 0 // no-op
}
func (l *layouter) Unnotify(id comm.Id) {
// no-op
}
type View struct {
view.Embed
Image image.Image
ResizeMode ResizeMode
Tint color.Color
PaintStyle *paint.Style
image image.Image
pbImage *pb.ImageOrResource
}
// New returns either the previous View in ctx with matching key, or a new View if none exists.
func New(ctx *view.Context, key string) *View {
if v, ok := ctx.Prev(key).(*View); ok {
return v
}
return &View{
Embed: ctx.NewEmbed(key),
}
}
// Build implements view.View.
func (v *View) Build(ctx *view.Context) view.Model {
if v.Image != v.image {
v.image = v.Image
v.pbImage = app.ImageMarshalProtobuf(v.image)
}
// Default to Center if we don't have an image
bounds := image.Rect(0, 0, 0, 0)
resizeMode := ResizeModeCenter
scale := 1.0
if v.image != nil {
bounds = v.image.Bounds()
resizeMode = v.ResizeMode
if res, ok := v.image.(*app.ImageResource); ok {
scale = res.Scale()
}
}
var painter paint.Painter
if v.PaintStyle != nil {
painter = v.PaintStyle
}
return view.Model{
Painter: painter,
Layouter: &layouter{bounds: bounds, resizeMode: resizeMode, scale: scale},
NativeViewName: "gomatcha.io/matcha/view/imageview",
NativeViewState: &imageview.View{
Image: v.pbImage,
Scale: scale,
ResizeMode: v.ResizeMode.MarshalProtobuf(),
Tint: pb.ColorEncode(v.Tint),
},
}
}