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

Commit 531f600

Browse files
authored
Support padding & margin, routing. (#70)
* Support padding & margin. Integrate router. * Add old sign in background * Adjust sign in style
1 parent b0fd4df commit 531f600

24 files changed

+918
-199
lines changed

assets.sketch

0 Bytes
Binary file not shown.

graphics/window.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,16 @@ func (e *EbitenWindow) Update() error {
5353
return nil
5454
}
5555

56-
func (e EbitenWindow) Draw(screen *ebiten.Image) {
56+
func (e *EbitenWindow) Draw(screen *ebiten.Image) {
5757
e.sp.Draw()
5858
e.ebiten.Render(screen)
5959
}
6060

61-
func (e EbitenWindow) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
61+
func (e *EbitenWindow) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
6262
return e.windowConfig.Width, e.windowConfig.Height
6363
}
6464

65-
func (e EbitenWindow) pollEvents() []input.Input {
65+
func (e *EbitenWindow) pollEvents() []input.Input {
6666
inputs := make([]input.Input, 0)
6767
if ebiten.IsKeyPressed(ebiten.KeyLeft) {
6868
inputs = append(inputs, input.Input{
@@ -141,13 +141,13 @@ func (e EbitenWindow) pollEvents() []input.Input {
141141
return inputs
142142
}
143143

144-
func NewEbitenWindow(windowConfig WindowConfig, sp Sprite, framesPerSeconds int, ebiten *Ebiten) EbitenWindow {
144+
func NewEbitenWindow(windowConfig WindowConfig, sp Sprite, framesPerSeconds int, eb *Ebiten) *EbitenWindow {
145145
nanoPerUpdate := time.Second.Nanoseconds() / int64(framesPerSeconds)
146-
return EbitenWindow{
146+
return &EbitenWindow{
147147
sp: sp,
148148
nanoPerUpdate: nanoPerUpdate,
149149
updateTime: time.Duration(nanoPerUpdate),
150150
windowConfig: windowConfig,
151-
ebiten: ebiten,
151+
ebiten: eb,
152152
}
153153
}

main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func main() {
6666
}, app, 24, &eb)
6767
g.Init()
6868

69-
if err := ebiten.RunGame(&g); err != nil {
69+
if err := ebiten.RunGame(g); err != nil {
7070
log.Fatal(err)
7171
}
7272
}

public/test/signin.png

1.7 MB
Loading

router/router.go

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package view
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"candy/observability"
8+
"candy/view"
9+
)
10+
11+
type Route struct {
12+
Path string
13+
CreateFactory view.CreateFactory
14+
}
15+
16+
type trieNode struct {
17+
endOfPath bool
18+
route *Route
19+
children map[rune]*trieNode
20+
}
21+
22+
type Router struct {
23+
logger *observability.Logger
24+
root *trieNode
25+
currView view.View
26+
}
27+
28+
func (r *Router) AddRoute(route Route) error {
29+
if !isPathValid(route.Path) {
30+
return fmt.Errorf("path is invalid: %s", route.Path)
31+
}
32+
33+
path := preparePath(route.Path)
34+
root := r.root
35+
for index, currRune := range path {
36+
if child, ok := root.children[currRune]; ok {
37+
root = child
38+
} else {
39+
child := newTrieNode()
40+
root.children[currRune] = child
41+
root = child
42+
}
43+
44+
if index < len(path)-1 {
45+
continue
46+
} else if root.endOfPath {
47+
return fmt.Errorf("path already exists: %s", route.Path)
48+
} else {
49+
root.endOfPath = true
50+
root.route = &route
51+
}
52+
}
53+
return nil
54+
}
55+
56+
func (r *Router) AddRoutes(routes []Route) error {
57+
for _, rt := range routes {
58+
err := r.AddRoute(rt)
59+
if err != nil {
60+
return err
61+
}
62+
}
63+
return nil
64+
}
65+
66+
func (r Router) CurrentView() view.View {
67+
return r.currView
68+
}
69+
70+
func (r *Router) Navigate(path string, props interface{}) error {
71+
rt, err := r.matchRoute(path)
72+
if err != nil {
73+
return err
74+
}
75+
if r.currView != nil {
76+
r.currView.Destroy()
77+
}
78+
79+
r.currView = rt.CreateFactory(props)
80+
r.currView.Init()
81+
r.logger.Infof("Navigate to %s\n", path)
82+
return nil
83+
}
84+
85+
func (r Router) matchRoute(path string) (*Route, error) {
86+
if !isPathValid(path) {
87+
return nil, fmt.Errorf("path is invalid: %s", path)
88+
}
89+
90+
path = preparePath(path)
91+
root := r.root
92+
for index, currRune := range path {
93+
child, ok := root.children[currRune]
94+
if !ok {
95+
break
96+
}
97+
root = child
98+
if index == len(path)-1 && root.endOfPath {
99+
return root.route, nil
100+
}
101+
}
102+
return nil, fmt.Errorf("path not found: %s", path)
103+
}
104+
105+
func preparePath(path string) string {
106+
for len(path) > 1 && path[len(path)-1] == '/' {
107+
path = path[:len(path)-1]
108+
}
109+
return path
110+
}
111+
112+
var blockedRunes = map[rune]struct{}{}
113+
114+
func isPathValid(path string) bool {
115+
if !strings.HasPrefix(path, "/") {
116+
return false
117+
}
118+
for _, currRune := range path {
119+
if _, ok := blockedRunes[currRune]; ok {
120+
return false
121+
}
122+
}
123+
return true
124+
}
125+
126+
func NewRouter(logger *observability.Logger) *Router {
127+
return &Router{
128+
logger: logger,
129+
root: newTrieNode(),
130+
}
131+
}
132+
133+
func newTrieNode() *trieNode {
134+
return &trieNode{children: make(map[rune]*trieNode)}
135+
}

router/router_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package view
2+
3+
import (
4+
"testing"
5+
6+
"candy/observability"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestRouter_AddRoute(t *testing.T) {
12+
testCases := []struct {
13+
name string
14+
newRoutes []Route
15+
hasErr bool
16+
}{
17+
{
18+
name: "path is empty",
19+
newRoutes: []Route{{Path: ""}},
20+
hasErr: true,
21+
},
22+
{
23+
name: "path must start with /",
24+
newRoutes: []Route{{Path: "path"}},
25+
hasErr: true,
26+
},
27+
{
28+
name: "path is valid",
29+
newRoutes: []Route{{Path: "/first/second"}},
30+
hasErr: false,
31+
},
32+
{
33+
name: "path already exists",
34+
newRoutes: []Route{{Path: "/first/second"}, {Path: "/first/second/"}},
35+
hasErr: true,
36+
},
37+
}
38+
39+
for _, testCase := range testCases {
40+
t.Run(testCase.name, func(t *testing.T) {
41+
logger := observability.NewLogger(observability.Info)
42+
router := NewRouter(&logger)
43+
44+
var err error
45+
46+
for _, newRoute := range testCase.newRoutes {
47+
err = router.AddRoute(newRoute)
48+
if err != nil {
49+
break
50+
}
51+
}
52+
53+
if testCase.hasErr {
54+
assert.NotNil(t, err)
55+
return
56+
} else {
57+
assert.Nil(t, err)
58+
}
59+
})
60+
}
61+
}

ui/alignment.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package ui
2+
3+
type AlignHorizontal int
4+
5+
func (a AlignHorizontal) Ptr() *AlignHorizontal {
6+
return &a
7+
}
8+
9+
const (
10+
AlignLeft AlignHorizontal = iota
11+
AlignHorizontalCenter
12+
AlignRight
13+
)
14+
15+
type AlignVertical int
16+
17+
const (
18+
AlignTop AlignVertical = iota
19+
AlignVerticalCenter
20+
AlignBottom
21+
)
22+
23+
type Alignment struct {
24+
Horizontal *AlignHorizontal
25+
Vertical *AlignVertical
26+
}
27+
28+
func (a Alignment) getHorizontal() AlignHorizontal {
29+
if a.Horizontal == nil {
30+
return AlignLeft
31+
}
32+
return *a.Horizontal
33+
}
34+
35+
func (a Alignment) getVertical() AlignVertical {
36+
if a.Vertical == nil {
37+
return AlignTop
38+
}
39+
return *a.Vertical
40+
}
41+
42+
func (a Alignment) AlignHorizontal(parent Component, child Component) int {
43+
padding := parent.getStyle().GetPadding()
44+
margin := child.getStyle().GetMargin()
45+
switch a.getHorizontal() {
46+
case AlignLeft:
47+
return margin.GetLeft() + padding.GetLeft()
48+
case AlignRight:
49+
return parent.getSize().width - padding.GetRight() - margin.GetRight() - child.getSize().width
50+
case AlignHorizontalCenter:
51+
return (parent.getSize().width - child.getSize().width) / 2
52+
}
53+
return margin.GetLeft() + padding.GetLeft()
54+
}

ui/background.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,15 @@ type Background struct {
1818
hasChanged bool
1919
}
2020

21-
func (b Background) Paint(painter *Painter, destLayer draw.Image) {
22-
if b.ImagePath != nil {
21+
func (b Background) Paint(painter *Painter, size Size, destLayer draw.Image) {
22+
if b.image != nil {
23+
rect := image.Rectangle{
24+
Max: image.Point{
25+
X: size.width,
26+
Y: size.height,
27+
},
28+
}
29+
painter.drawImage(b.image, rect, destLayer, image.Point{})
2330
return
2431
}
2532
if b.Color != nil {

ui/box.go

+9-46
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package ui
22

33
import (
4-
"image"
5-
"image/draw"
64
"sort"
75

86
"candy/ui/ptr"
@@ -22,52 +20,17 @@ func (b Box) GetName() string {
2220
return "Box"
2321
}
2422

25-
func (b Box) Paint(painter *Painter, destLayer draw.Image, offset Offset) {
26-
if !b.hasChanged {
27-
return
28-
}
29-
30-
contentLayer := image.NewRGBA(image.Rectangle{
31-
Max: image.Point{
32-
X: b.size.width,
33-
Y: b.size.height,
34-
},
35-
})
36-
if b.style.Background != nil {
37-
b.style.Background.Paint(painter, contentLayer)
38-
}
39-
40-
sortedChildren := Children{
41-
children: b.children,
42-
childrenOffset: b.childrenOffset,
43-
}
44-
sort.Sort(&sortedChildren)
45-
46-
for index, child := range sortedChildren.children {
47-
childOffset := sortedChildren.childrenOffset[index]
48-
child.Paint(painter, contentLayer, childOffset)
49-
}
50-
51-
painter.drawImage(contentLayer, image.Rectangle{
52-
Min: image.Point{},
53-
Max: contentLayer.Bounds().Max,
54-
}, destLayer, image.Point{
55-
X: offset.x,
56-
Y: offset.y,
57-
})
58-
}
59-
6023
func (b Box) ComputeLeafSize(_ Constraints) Size {
61-
padding := b.style.GetPadding()
24+
padding := b.Style.GetPadding()
6225

6326
width := 0
64-
if b.style.Width != nil {
65-
width = *b.style.Width
27+
if b.Style.Width != nil {
28+
width = *b.Style.Width
6629
}
6730
width += padding.GetLeft() + padding.GetRight()
6831
height := 0
69-
if b.style.Height != nil {
70-
height = *b.style.Height
32+
if b.Style.Height != nil {
33+
height = *b.Style.Height
7134
}
7235
height += padding.GetTop() + padding.GetBottom()
7336
return Size{width: width, height: height}
@@ -90,10 +53,10 @@ func NewBox(pros *BoxProps, children []Component, style *Style) *Box {
9053
}
9154
return &Box{
9255
SharedComponent: SharedComponent{
93-
name: "Box",
94-
layout: newLayout(*style.LayoutType),
95-
style: style,
96-
children: children,
56+
Name: "Box",
57+
Layout: NewLayout(*style.LayoutType),
58+
Style: style,
59+
Children: children,
9760
childrenOffset: []Offset{},
9861
events: Events{onClick: pros.OnClick},
9962
}}

0 commit comments

Comments
 (0)