This repository has been archived by the owner on Nov 16, 2022. It is now read-only.
/
element.go
295 lines (273 loc) 路 8.09 KB
/
element.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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
package r
import (
"reflect"
)
type (
// Element is the smallest building block of retort.
// You create them with r.CreateElement or r.CreateFragment
//
// Internally they are a pointer to a fiber, which is used
// to keep track of the render tree.
Element *fiber
// Component is main thing you will be making to create a
// retort app. Your component must match this function
// signature.
Component func(props Properties) Element
// Children is a slice of Elements (or pointers to a fiber)
// It's used in r.CreateElement or r.CreateFragment to
// specify the child nodes of the Element.
// It can also be extracted from props with GetProperty or
// GetOptionalProperty, if you want to pass children on.
//
// In general, unless you're creating a ScreenElement,
// you should pass any children passed into props
// on to the return Element.
Children []*fiber
// Properties are immutable state that is passed into a component, and pass
// down to components to share data.
//
// Properties is ultimately a slice of interfaces, which lets you and retort
// and any components your using add any concrete structs to it. Because of
// this, there are some helper methods to retrieve props. These are
// GetProperty and GetOptionalProperty.
//
// Properties can only contain one struct of a given type. In this sense the
// type of the struct is a key.
//
// Sometimes called props.
Properties []interface{}
// State is local to a component.
// It is mutable via the setState function from UseState. Don't edit State
// directly, as retort will not know that you have, and will not trigger an
// update and re-render.
// It can be used to create new props to pass down to other components.
State []interface{}
)
// CreateElement is used to create the building blocks of a retort application,
// and the thing that Components are ultimately made up of, Elements.
//
// import (
// "github.com/gdamore/tcell"
// "retort.dev/r"
// "retort.dev/r/component"
// )
//
// // Wrapper is a simple component that wraps the
// // children Components in a box with a white border.
// func Wrapper(p r.Properties) r.Element {
// children := r.GetProperty(
// p,
// r.Children{},
// "Container requires r.Children",
// ).(r.Children)
//
// return r.CreateElement(
// component.Box,
// r.Properties{
// component.BoxProps{
// Border: component.Border{
// Style: component.BorderStyleSingle,
// Foreground: tcell.ColorWhite,
// },
// },
// },
// children,
// )
// }
//
// By creating an Element and passing Properties and Children seperately, retort
// can keep track of the entire tree of Components, and decide when to compute
// which parts, and in turn when to render those to the screen.
func CreateElement(
component Component,
props Properties,
children Children,
) *fiber {
// debug.Log("CreateElement", component, props, children)
if !checkPropTypesAreUnique(props) {
panic("props are not unique")
}
return &fiber{
componentType: elementComponent,
component: component,
Properties: append(
props,
children,
),
}
}
// CreateFragment is like CreateElement except you do not need a Component
// or Properties. This is useful when you need to make Higher Order Components,
// or other Components that wrap or compose yet more Components.
func CreateFragment(children Children) *fiber {
return &fiber{
componentType: fragmentComponent,
component: nil,
Properties: Properties{
children,
},
}
}
// CreateScreenElement is like a normal Element except it has the
// ability to render output to the screen.
//
// Once retort has finished calculating which components have changed
// all those with changes are passed to a render function.
// This walks the tree and finds ScreenElements and calls their
// RenderToScreen function, passing in the current Screen.
//
// RenderToScreen needs to return a BlockLayout, which is used among
// other things to direct Mouse Events to the right Component.
//
// func Box(p r.Properties) r.Element {
// return r.CreateScreenElement(
// func(s tcell.Screen) r.BlockLayout {
// return BlockLayout
// },
// nil,
// )
// }
func CreateScreenElement(
calculateLayout CalculateLayout,
render RenderToScreen,
props Properties,
children Children,
) *fiber {
return &fiber{
componentType: screenComponent,
calculateLayout: &calculateLayout,
renderToScreen: &render,
Properties: append(
props,
children,
),
component: nil,
}
}
func checkPropTypesAreUnique(props Properties) bool {
seenPropTypes := make(map[reflect.Type]bool)
for _, p := range props {
if seen := seenPropTypes[reflect.TypeOf(p)]; seen {
return false
}
seenPropTypes[reflect.TypeOf(p)] = true
}
return true
}
// GetProperty will search props for the Property matching the type of the
// struct you passed in, and will throw an Error with the message provided
// if not found.
//
// This is useful when your component will not work without the provided
// Property. However it is very unforgiving, and generally you will want to use
// GetOptionalProperty which allows you to provide a default Property to use.
//
// Because this uses reflection, you must pass in a concrete struct not just the
// type. For example r.Children is the type but r.Children{} is a struct of that
// type. Only the latter will work.
//
// func Wrapper(p r.Properties) r.Element {
// children := p.GetProperty(
// r.Children{},
// "Container requires r.Children",
// ).(r.Children)
//
// return r.CreateElement(
// component.Box,
// r.Properties{
// component.BoxProps{
// Border: component.Border{
// Style: component.BorderStyleSingle,
// Foreground: tcell.ColorWhite,
// },
// },
// },
// children,
// )
// }
func (props Properties) GetProperty(
propType interface{},
errorMessage string,
) interface{} {
for _, p := range props {
if reflect.TypeOf(p) == reflect.TypeOf(propType) {
return p
}
}
// debug.Spew(props)
panic(errorMessage)
}
// GetOptionalProperty will search props for the Property matching the type of
// struct you passed in. If it was not in props, the struct passed into propType
// will be returned.
//
// You need to cast the return type of the function exactly the same as the
// struct you pass in.
//
// This allows you to specify a defaults for a property.
//
// In the following example if Wrapper is not passed a Property of the type
// component.BoxProps, the default values provided will be used.
//
// func Wrapper(p r.Properties) r.Element {
// boxProps := p.GetOptionalProperty(
// component.BoxProps{
// Border: component.Border{
// Style: component.BorderStyleSingle,
// Foreground: tcell.ColorWhite,
// },
// },
// ).(component.BoxProps)
//
// return r.CreateElement(
// component.Box,
// r.Properties{
// boxProps
// },
// children,
// )
// }
func (props Properties) GetOptionalProperty(
propType interface{},
) interface{} {
for _, p := range props {
if reflect.TypeOf(p) == reflect.TypeOf(propType) {
return p
}
}
return propType
}
// filterProps returns all props except the type you pass.
func filterProps(props Properties, prop interface{}) Properties {
newProps := Properties{}
for _, p := range props {
if reflect.TypeOf(p) != reflect.TypeOf(prop) {
newProps = append(newProps, p)
}
}
return newProps
}
// ReplaceProps by replacing with the same type you passed.
func ReplaceProps(props Properties, prop interface{}) Properties {
newProps := Properties{prop}
for _, p := range props {
if reflect.TypeOf(p) != reflect.TypeOf(prop) {
newProps = append(newProps, p)
}
}
return newProps
}
// AddPropsIfNone will add the prop to props is no existing prop of that type
// is found.
func AddPropsIfNone(props Properties, prop interface{}) Properties {
foundProp := false
for _, p := range props {
if reflect.TypeOf(p) == reflect.TypeOf(prop) {
foundProp = true
}
}
if !foundProp {
return append(props, prop)
}
return props
}