Skip to content
This repository was archived by the owner on Sep 24, 2022. It is now read-only.

Commit b0fd4df

Browse files
authored
Support change detection (#69)
1 parent d7a72f0 commit b0fd4df

File tree

11 files changed

+291
-108
lines changed

11 files changed

+291
-108
lines changed

ui/background.go

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,49 @@
11
package ui
22

33
import (
4+
"image"
45
"image/draw"
6+
7+
"candy/assets"
58
)
69

710
type Background struct {
8-
Color *Color
11+
Color *Color
12+
ImagePath *string
13+
14+
prevColor Color
15+
prevImagePath string
16+
17+
image image.Image
18+
hasChanged bool
919
}
1020

1121
func (b Background) Paint(painter *Painter, destLayer draw.Image) {
22+
if b.ImagePath != nil {
23+
return
24+
}
1225
if b.Color != nil {
1326
painter.fillColor(destLayer, *b.Color)
1427
}
1528
}
29+
30+
func (b *Background) Update(assets *assets.Assets) {
31+
if b.ImagePath != nil {
32+
imagePath := *b.ImagePath
33+
if imagePath != b.prevImagePath {
34+
b.image = assets.GetImage(imagePath)
35+
b.hasChanged = true
36+
b.prevImagePath = *b.ImagePath
37+
}
38+
}
39+
if b.Color != nil {
40+
if *b.Color == b.prevColor {
41+
b.hasChanged = true
42+
b.prevColor = *b.Color
43+
}
44+
}
45+
}
46+
47+
func (b *Background) ResetChangeDetection() {
48+
b.hasChanged = false
49+
}

ui/box.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ func (b Box) GetName() string {
2323
}
2424

2525
func (b Box) Paint(painter *Painter, destLayer draw.Image, offset Offset) {
26+
if !b.hasChanged {
27+
return
28+
}
29+
2630
contentLayer := image.NewRGBA(image.Rectangle{
2731
Max: image.Point{
2832
X: b.size.width,
@@ -88,7 +92,7 @@ func NewBox(pros *BoxProps, children []Component, style *Style) *Box {
8892
SharedComponent: SharedComponent{
8993
name: "Box",
9094
layout: newLayout(*style.LayoutType),
91-
style: *style,
95+
style: style,
9296
children: children,
9397
childrenOffset: []Offset{},
9498
events: Events{onClick: pros.OnClick},

ui/change.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package ui
2+
3+
type Changeable interface {
4+
hasChanged() bool
5+
}

ui/component.go

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,20 @@ import (
55
"image/draw"
66
"time"
77

8+
"candy/assets"
89
"candy/input"
910
)
1011

12+
type UpdateDeps struct {
13+
assets *assets.Assets
14+
fonts *Fonts
15+
}
16+
1117
type Component interface {
1218
GetName() string
1319
HandleInput(in input.Input)
1420
handleInput(in input.Input, offset Offset)
15-
Update(timeElapsed time.Duration)
21+
Update(timeElapsed time.Duration, deps *UpdateDeps)
1622
getLayout() layout
1723
getChildren() []Component
1824
ComputeLeafSize(constraints Constraints) Size
@@ -21,6 +27,8 @@ type Component interface {
2127
getStyle() Style
2228
getChildrenOffset() []Offset
2329
setChildrenOffset(childrenOffsets []Offset)
30+
HasChanged() bool
31+
ResetChangeDetection()
2432
Paint(painter *Painter, destLayer draw.Image, offset Offset)
2533
}
2634

@@ -38,11 +46,12 @@ type Offset struct {
3846
type SharedComponent struct {
3947
name string
4048
layout layout
41-
style Style
49+
style *Style
4250
size Size
4351
childrenOffset []Offset
4452
children []Component
4553
events Events
54+
hasChanged bool
4655
}
4756

4857
func (s *SharedComponent) HandleInput(in input.Input) {
@@ -74,10 +83,34 @@ func (s *SharedComponent) BoundingBox(offset Offset) image.Rectangle {
7483
return image.Rect(offset.x, offset.y, offset.x+s.size.width, offset.y+s.size.height)
7584
}
7685

77-
func (s SharedComponent) Update(timeElapsed time.Duration) {
86+
func (s *SharedComponent) Update(timeElapsed time.Duration, deps *UpdateDeps) {
87+
for _, child := range s.children {
88+
child.Update(timeElapsed, deps)
89+
if child.HasChanged() {
90+
s.hasChanged = true
91+
}
92+
}
93+
if s.style != nil {
94+
s.style.Update(deps)
95+
if s.style.hasChanged {
96+
s.hasChanged = true
97+
}
98+
}
99+
}
100+
101+
func (s SharedComponent) HasChanged() bool {
102+
return s.hasChanged
103+
}
104+
105+
func (s *SharedComponent) ResetChangeDetection() {
78106
for _, child := range s.children {
79-
child.Update(timeElapsed)
107+
child.ResetChangeDetection()
80108
}
109+
110+
if s.style != nil {
111+
s.style.ResetChangeDetection()
112+
}
113+
s.hasChanged = false
81114
}
82115

83116
func (s SharedComponent) GetName() string {
@@ -105,7 +138,10 @@ func (s *SharedComponent) setSize(size Size) {
105138
}
106139

107140
func (s SharedComponent) getStyle() Style {
108-
return s.style
141+
if s.style == nil {
142+
return Style{}
143+
}
144+
return *s.style
109145
}
110146

111147
func (s *SharedComponent) setChildrenOffset(childrenOffsets []Offset) {

ui/engine.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"image/draw"
77
"time"
88

9+
"candy/assets"
910
"candy/graphics"
1011
"candy/input"
1112
"candy/observability"
@@ -20,8 +21,8 @@ type RenderEngine struct {
2021
rootComponent Component
2122
rootConstraints Constraints
2223
compositeLayer draw.Image
23-
hasUpdate bool
2424
batch graphics.Batch
25+
updateDeps *UpdateDeps
2526
}
2627

2728
func (r *RenderEngine) Render(component Component) {
@@ -37,7 +38,7 @@ func (r *RenderEngine) Update(timeElapsed time.Duration) {
3738
if r.rootComponent == nil {
3839
return
3940
}
40-
r.rootComponent.Update(timeElapsed)
41+
r.rootComponent.Update(timeElapsed, r.updateDeps)
4142
}
4243

4344
func (r *RenderEngine) HandleInput(in input.Input) {
@@ -48,7 +49,10 @@ func (r *RenderEngine) HandleInput(in input.Input) {
4849
}
4950

5051
func (r *RenderEngine) render() {
51-
if !r.hasUpdate {
52+
if r.rootComponent == nil {
53+
return
54+
}
55+
if !r.rootComponent.HasChanged() {
5256
return
5357
}
5458

@@ -63,7 +67,8 @@ func (r *RenderEngine) render() {
6367
r.rootComponent.Paint(r.painter, r.compositeLayer, Offset{})
6468

6569
r.batch = r.graphics.StartNewBatch(r.compositeLayer)
66-
r.hasUpdate = false
70+
71+
r.rootComponent.ResetChangeDetection()
6772
}
6873

6974
func (r *RenderEngine) draw() {
@@ -82,15 +87,19 @@ func (r RenderEngine) generateLayout(component Component) {
8287
}
8388

8489
func NewRenderEngine(
90+
rootConstraints Constraints,
8591
logger *observability.Logger,
8692
g graphics.Graphics,
87-
rootConstraints Constraints,
93+
assets *assets.Assets,
8894
) *RenderEngine {
8995
return &RenderEngine{
9096
logger: logger,
9197
graphics: g,
9298
painter: &Painter{},
9399
rootConstraints: rootConstraints,
94-
hasUpdate: true,
100+
updateDeps: &UpdateDeps{
101+
assets: assets,
102+
fonts: NewFonts(),
103+
},
95104
}
96105
}

ui/example/app.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package main
33
import (
44
"fmt"
55

6-
"candy/assets"
76
"candy/ui"
87
"candy/ui/ptr"
98
)
@@ -14,7 +13,7 @@ type app struct {
1413
*ui.Box
1514
}
1615

17-
func newApp(assets *assets.Assets) *app {
16+
func newApp() *app {
1817
return &app{ui.NewBox(
1918
&ui.BoxProps{OnClick: func() {
2019
fmt.Println("Box clicked")
@@ -37,7 +36,7 @@ determination that "meant to be" is exactly as meaningless a phrase as it
3736
seems to be, and that none of us is actually meant to be doing anything at all.
3837
But that's my existential underpants underpinnings showing. It's the way the
3938
cookie crumbles. And now I want a cookie.
40-
`}, &ui.Style{FontStyle: ui.FontStyle{
39+
`}, &ui.Style{FontStyle: &ui.FontStyle{
4140
Family: ptr.String("Source Code Pro"),
4241
Weight: ptr.String("ExtraLight"),
4342
Italic: ptr.Bool(false),
@@ -49,9 +48,9 @@ cookie crumbles. And now I want a cookie.
4948
Blue: 255,
5049
Alpha: 255,
5150
}}}),
52-
ui.NewImage(assets, &ui.ImageProps{ImagePath: "test/image3.png"}, nil),
53-
ui.NewImage(assets, &ui.ImageProps{ImagePath: "test/image1.jpg"}, nil),
54-
ui.NewImage(assets, &ui.ImageProps{ImagePath: "test/image2.jpg"}, nil),
51+
ui.NewImage(&ui.ImageProps{ImagePath: "test/image3.png"}, nil),
52+
ui.NewImage(&ui.ImageProps{ImagePath: "test/image1.jpg"}, nil),
53+
ui.NewImage(&ui.ImageProps{ImagePath: "test/image2.jpg"}, nil),
5554
},
5655
nil)}
5756
}

ui/example/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ func main() {
2525
logger := observability.NewLogger(observability.Debug)
2626
rootConstraint := ui.NewScreenConstraint(screenWidth, screenHeight)
2727

28-
renderEngine := ui.NewRenderEngine(&logger, &eb, rootConstraint)
29-
renderEngine.Render(newApp(&ass))
28+
renderEngine := ui.NewRenderEngine(rootConstraint, &logger, &eb, &ass)
29+
renderEngine.Render(newApp())
3030

3131
g := graphics.NewEbitenWindow(graphics.WindowConfig{
3232
Width: screenWidth,

ui/font.go

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,37 @@ import (
99
"golang.org/x/image/font"
1010
)
1111

12+
type Fonts struct {
13+
fontFinder *sysfont.Finder
14+
families map[string]*fontFamily
15+
}
16+
17+
func (f *Fonts) getFamily(familyName string) *fontFamily {
18+
f.loadFont(familyName)
19+
matchedFont := f.fontFinder.Match(familyName)
20+
return f.families[matchedFont.Family]
21+
}
22+
23+
func (f *Fonts) loadFont(familyName string) {
24+
matchedFont := f.fontFinder.Match(familyName)
25+
if _, ok := f.families[matchedFont.Family]; ok {
26+
return
27+
}
28+
ff, err := newFontFamily(f.fontFinder, matchedFont.Family)
29+
if err != nil {
30+
return
31+
}
32+
f.families[matchedFont.Family] = &ff
33+
}
34+
35+
func NewFonts() *Fonts {
36+
finder := sysfont.NewFinder(nil)
37+
return &Fonts{
38+
fontFinder: finder,
39+
families: make(map[string]*fontFamily, 0),
40+
}
41+
}
42+
1243
type fontFamily struct {
1344
dpi int
1445
fontFinder *sysfont.Finder
@@ -21,14 +52,14 @@ type fontStyle struct {
2152
italic bool
2253
}
2354

24-
func (f fontFamily) face(style fontStyle, fontSize int) (*truetype.Font, fontFace, error) {
55+
func (f fontFamily) face(style fontStyle, fontSize int) (*truetype.Font, *fontFace, error) {
2556
ft, err := f.getFont(style)
2657
if err != nil {
27-
return nil, fontFace{}, err
58+
return nil, nil, err
2859
}
2960
options := truetype.Options{Size: float64(fontSize), DPI: float64(f.dpi)}
3061
face := truetype.NewFace(ft, &options)
31-
return ft, fontFace{fontFace: face}, nil
62+
return ft, &fontFace{fontFace: face}, nil
3263
}
3364

3465
func (f fontFamily) getFont(style fontStyle) (*truetype.Font, error) {
@@ -43,18 +74,14 @@ func (f fontFamily) getQuery(style fontStyle) string {
4374
return f.fontFamily + style.toString()
4475
}
4576

46-
func findFontFamily(fontFinder *sysfont.Finder, fontFamily string) (string, []*sysfont.Font) {
47-
matchedFont := fontFinder.Match(fontFamily)
48-
if matchedFont == nil {
49-
return "", []*sysfont.Font{}
50-
}
77+
func findFontFamily(fontFinder *sysfont.Finder, family string) (string, []*sysfont.Font) {
5178
fonts := make([]*sysfont.Font, 0)
5279
for _, fontInstalled := range fontFinder.List() {
53-
if matchedFont.Family == fontInstalled.Family {
80+
if family == fontInstalled.Family {
5481
fonts = append(fonts, fontInstalled)
5582
}
5683
}
57-
return matchedFont.Family, fonts
84+
return family, fonts
5885
}
5986

6087
func loadFonts(fonts []*sysfont.Font) (map[sysfont.Font]*truetype.Font, error) {
@@ -73,11 +100,10 @@ func loadFonts(fonts []*sysfont.Font) (map[sysfont.Font]*truetype.Font, error) {
73100
return parsedFonts, nil
74101
}
75102

76-
func newFontFamily(fontFamilyName string) (fontFamily, error) {
77-
finder := sysfont.NewFinder(nil)
78-
familyName, fontsInFamily := findFontFamily(finder, fontFamilyName)
103+
func newFontFamily(fontFinder *sysfont.Finder, family string) (fontFamily, error) {
104+
familyName, fontsInFamily := findFontFamily(fontFinder, family)
79105
if len(fontsInFamily) < 1 {
80-
return fontFamily{}, fmt.Errorf("font family not found: %s", fontFamilyName)
106+
return fontFamily{}, fmt.Errorf("font family not found: %s", family)
81107
}
82108

83109
fonts, err := loadFonts(fontsInFamily)
@@ -87,7 +113,7 @@ func newFontFamily(fontFamilyName string) (fontFamily, error) {
87113
return fontFamily{
88114
dpi: 72,
89115
fontFamily: familyName,
90-
fontFinder: finder,
116+
fontFinder: fontFinder,
91117
fonts: fonts,
92118
}, nil
93119
}

0 commit comments

Comments
 (0)