4
4
#
5
5
6
6
defmodule Scenic.Graph do
7
- @ moduledoc false
7
+
8
+ @ moduledoc """
9
+ Please see [`Graph Overview`](overview_graph.html) for a high-level description.
10
+
11
+ ## What is a Graph
12
+
13
+ There are many types of graphs in the world of computer science. There are graphs that
14
+ show data to a user. There are graphs that give access to databases. Graphs that link
15
+ people to a social network.
16
+
17
+ In Scenic, a Graph is a graph in same way that the DOM in HTML is a graph. It is a
18
+ hierarchical tree of data that describes a set of things to draw on the screen.
19
+
20
+ You build a graph by appending primitives (individual things to draw) to the current
21
+ node in the tree. Nodes are represented by the Group primitive in Scenic.
22
+
23
+ The following example builds a simple graph that displays some text, creates a group,
24
+ then adds more text and a rounded rectangle to it.
25
+
26
+ @graph Scenic.Graph.build()
27
+ |> text( "This is some text", translate: {20, 20} )
28
+ |> group( fn(graph) ->
29
+ graph
30
+ |> text( "This text is in a group", translate: {200, 24} )
31
+ |> rounded_rectangle( {400, 30}, stroke: {2, :blue})
32
+ end, translate: {20, 100}, text_align: :center)
33
+
34
+ There is a fair amount going on in the example above, we we will break it down.
35
+ The first line
36
+
37
+ @graph Scenic.Graph.build()
38
+
39
+ builds an empty graph with only one group as the root node. By assigning it to the
40
+ compile directive @group, we know that this group will be built once at compile
41
+ time and will be very fast to access later during runtime.
42
+
43
+ The empty graph that is returned from `build()` is then passed to `text(...)`, which
44
+ adds a text primitive to the root group. The resulting graph from that call is then
45
+ passed again into the `group(...)` call. This creates a new group and has calls an
46
+ anonymous function that you can use to add primitives to the newly created group.
47
+
48
+ Notice that the anonymous "builder" function receives a graph as its only parameter.
49
+ This is the same graph that we are building, except that it has a marker indicating
50
+ that new primitives added to it are inserted into the new group instead of the
51
+ root of the graph.
52
+
53
+ Finally, when the group is finished, a translation matrix and a `:text_align` style
54
+ are added to it. These properties are _inherited_ by the primitives in the group.
55
+
56
+ ## Inheritance
57
+
58
+ An important concept to understand is that both [styles](overview_styles.html) and
59
+ [transforms](overview_styles.html) are inherited down the graph. This means that if
60
+ you apply a style or transform to any group (including the root), then all primitives
61
+ contained by that group will have those properties applied to them too. This is true
62
+ even if the primitive is nested in several groups at the same time.
63
+
64
+ @graph Scenic.Graph.build(font: :roboto_mono)
65
+ |> text( "This text inherits the font", translate: {20, 20} )
66
+ |> group( fn(graph) ->
67
+ graph
68
+ |> text( "This text also inherits the font", translate: {200, 24} )
69
+ |> text( "This text overrides the font", font: :roboto )
70
+ end, translate: {20, 100}, text_align: :center)
71
+
72
+ Transforms, such as translate, rotate, scale, also inherit down the graph, but do
73
+ so slightly differently than the styles. With a style, when you set a specific value
74
+ on a primitive, that overrides the inherited value of the same type.
75
+
76
+ With a transform, the values multiply together. This allows you to position items
77
+ within a group relative to the origin {0,0}, then move the group as a whole, keeping
78
+ the interior positions unchanged.
79
+
80
+ ## Modifying a Graph
81
+
82
+ Scenic was written specifically for Erlang/Elixir, which is a functional programming
83
+ model with immutable data.
84
+
85
+ As such, once you make a graph, it stays in memory unchanged - until you transform it
86
+ via `Graph.modify/3`. Technically you never change it (that's the immutable part),
87
+ instead Graph.modify returns a new graph with different data in it.
88
+
89
+ [Graph.modify/3](Scenic.Graph.html#modify/3) is the single Graph function that you
90
+ will use the most.
91
+
92
+ For example, lets go back to our graph with the two text items in it.
93
+
94
+ @graph Graph.build(font: :roboto, font_size: 24, rotate: 0.4)
95
+ |> text("Hello World", translate: {300, 300}, id: :small_text)
96
+ |> text("Bigger Hello", font_size: 40, scale: 1.5, id: :big_text)
97
+
98
+ This time, we've assigned ids to both of the text primitives. This makes it easy to
99
+ find and modify that primitive in the graph.
100
+
101
+ graph =
102
+ @graph
103
+ |> Graph.modify( :small_text, &text(&1, "Smaller Hello", font_size: 16))
104
+ |> Graph.modify( :big_text, &text(&1, "Bigger Hello", font_size: 60))
105
+ |> push_graph()
106
+
107
+ Notice that the graph is modified multiple times in the pipeline. The `push_graph/1`
108
+ function is relatively heavy when the graph references other scenes. The recommended
109
+ pattern is to make multiple changes to the graph and then push once at the end.
110
+
111
+ ## Accessing Primitives
112
+
113
+ When using a Graph, it is extremely common to access and modify primitives. They way
114
+ you do this is by putting an id on the primitives you care about in a graph.
115
+
116
+ @graph Graph.build()
117
+ |> text("small text", id: :small_text)
118
+ |> text("bit text", id: :big_text)
119
+
120
+ When you get primitives, or modify a graph, you specify them by id. This happens
121
+ quickly, but at a cost of using a little memory. If you aren't going to access
122
+ a primitive, then don't assign an id to them.
123
+
124
+ One interesting note: There is nothing saying you have to use an atom as the id.
125
+ In fact you can use any Erlang term you want. This can be very powerful, especially
126
+ when used to identify components...
127
+ """
128
+
8
129
9
130
alias Scenic.Primitive
10
131
alias Scenic.Primitive.Group
@@ -46,17 +167,26 @@ defmodule Scenic.Graph do
46
167
@ err_msg_put "Graph.put can only update existing items."
47
168
@ err_msg_get_id_one "Graph.get! expected to find one and only one element"
48
169
49
- # ============================================================================
50
- # access to the raw graph fields
51
- # concentrate all knowledge of the internal structure of the graph tuple here
52
-
170
+ # ============================================================================
171
+ @ doc """
172
+ Returns the root group of a graph as a primitive.
173
+ Deprecated. Use `Graph.get!(graph, :_root_)` instead.
174
+ """
175
+ @ deprecated "Use Graph.get!(graph, :_root_) instead"
53
176
@ spec get_root ( graph :: t ( ) ) :: Primitive . t ( )
54
177
def get_root ( % __MODULE__ { } = graph ) do
55
- Map . delete ( graph . primitives [ @ root_uid ] , :styles )
178
+ get! ( graph , :_root_ )
179
+ # Map.delete(graph.primitives[@root_uid], :styles)
56
180
end
57
181
58
182
# ============================================================================
59
183
# build a new graph, starting with the given element
184
+ @ doc """
185
+ Builds and returns an empty graph.
186
+
187
+ Just like any primitive, you can pass in an option list of styles and transforms.
188
+ These will be applied to the otherwise empty root group in the new graph.
189
+ """
60
190
@ spec build ( opts :: keyword ) :: t ( )
61
191
def build ( opts \\ [ ] ) do
62
192
opts = handle_options ( opts )
@@ -70,6 +200,16 @@ defmodule Scenic.Graph do
70
200
71
201
# ============================================================================
72
202
# add a pre-built primitive
203
+ @ doc """
204
+ Add a pre-built primitive to the current group in the graph.
205
+
206
+ This is usually called during graph construction. When add a new Group primitive
207
+ to a Graph, it marks the new group as the current one before calling the group's
208
+ builder function. This is what allows you to add primitives to the right place
209
+ in the new Group.
210
+
211
+ Note that all primitives added to a group are appended to the draw order.
212
+ """
73
213
@ spec add ( graph :: t ( ) , primitive :: Primitive . t ( ) ) :: t ( )
74
214
def add ( graph , primitive )
75
215
@@ -79,6 +219,16 @@ defmodule Scenic.Graph do
79
219
end
80
220
81
221
# build and add new primitives
222
+ @ doc """
223
+ Build and add a primitive to the current group in the graph.
224
+
225
+ This is usually called during graph construction. When add a new Group primitive
226
+ to a Graph, it marks the new group as the current one before calling the group's
227
+ builder function. This is what allows you to add primitives to the right place
228
+ in the new Group.
229
+
230
+ Note that all primitives added to a group are appended to the draw order.
231
+ """
82
232
@ spec add ( graph :: t ( ) , module :: atom , data :: any , opts :: keyword ) :: t ( )
83
233
def add ( graph , primitive_module , primitive_data , opts \\ [ ] )
84
234
@@ -103,6 +253,15 @@ defmodule Scenic.Graph do
103
253
104
254
# ============================================================================
105
255
# delete a primitive/s from a graph
256
+ @ doc """
257
+ Permanently delete a primitive from a group by id.
258
+
259
+ This will remove a primitive (or many if they have the same id) from a graph. It
260
+ then returns the modified graph.
261
+
262
+ If you delete a group from a graph, then all primitives contained by that
263
+ group are deleted as well.
264
+ """
106
265
@ spec delete ( graph :: t ( ) , id :: any ) :: t ( )
107
266
def delete ( % __MODULE__ { primitives: primitives , ids: ids } = graph , id ) do
108
267
# resolve the id into a list of uids
@@ -141,6 +300,8 @@ defmodule Scenic.Graph do
141
300
end
142
301
143
302
# ============================================================================
303
+ # KEEP THIS AROUND FOR NOW
304
+ # might want to put it back in...
144
305
# add a pre-built primitive to an existing group in a graph
145
306
# def add_to( graph, id, primitive )
146
307
@@ -242,24 +403,6 @@ defmodule Scenic.Graph do
242
403
Map . put ( ids , id , uid_list )
243
404
end
244
405
245
- # ============================================================================
246
- # remove an entry in the ids
247
- # defp unmap_id_to_uid(graph, id, uid)
248
- # defp unmap_id_to_uid(graph, nil, _uid), do: graph
249
- # defp unmap_id_to_uid(graph, _id, nil), do: graph
250
- # defp unmap_id_to_uid(%Graph{ids: ids} = graph, id, uid) when is_integer(uid) do
251
-
252
- # uid_list = Map.get(ids, id, [])
253
- # |> Enum.reject(fn(mapped_uid)-> mapped_uid == uid end)
254
-
255
- # ids = case uid_list do
256
- # [] -> Map.delete(ids, id)
257
- # uids -> Map.put(ids, id, uids)
258
- # end
259
-
260
- # %{graph | ids: ids}
261
- # end
262
-
263
406
# ============================================================================
264
407
defp resolve_id ( graph , id )
265
408
@@ -270,6 +413,12 @@ defmodule Scenic.Graph do
270
413
# ============================================================================
271
414
# --------------------------------------------------------
272
415
# count all the nodes in a graph.
416
+ @ doc """
417
+ Returns a count of all the primitives in a graph.
418
+
419
+ The root Group counts as a primitive, so an empty graph should have a count
420
+ of 1.
421
+ """
273
422
@ spec count ( graph :: t ( ) ) :: integer
274
423
def count ( graph )
275
424
@@ -279,6 +428,9 @@ defmodule Scenic.Graph do
279
428
280
429
# --------------------------------------------------------
281
430
# count the nodes associated with an id.
431
+ @ doc """
432
+ Returns a count of all the primitives in a graph with a specific id.
433
+ """
282
434
@ spec count ( graph :: t ( ) , id :: any ) :: integer
283
435
def count ( graph , id )
284
436
@@ -306,6 +458,9 @@ defmodule Scenic.Graph do
306
458
307
459
# ============================================================================
308
460
# get a list of primitives by id
461
+ @ doc """
462
+ Returns a list of primitives from a graph with a specific id.
463
+ """
309
464
@ spec get ( graph :: t ( ) , id :: any ) :: list ( Primitive . t ( ) )
310
465
def get ( % __MODULE__ { } = graph , id ) do
311
466
graph
@@ -316,6 +471,12 @@ defmodule Scenic.Graph do
316
471
317
472
# --------------------------------------------------------
318
473
# get a single primitive by id. Raise error if it finds any count other than one
474
+ @ doc """
475
+ Returns a single primitive from a graph with a specific id.
476
+
477
+ This will raise an error if either none or multiple primitives are found with
478
+ the specified id.
479
+ """
319
480
@ spec get! ( graph :: t ( ) , id :: any ) :: Primitive . t ( )
320
481
def get! ( % __MODULE__ { } = graph , id ) do
321
482
case resolve_id ( graph , id ) do
@@ -519,6 +680,19 @@ defmodule Scenic.Graph do
519
680
end
520
681
end
521
682
683
+
684
+
685
+ @ doc """
686
+ Modify one or more primitives in a graph.
687
+
688
+ Retrieves the primitive (or primitives) specified by an id and passes them to
689
+ a callback function. The result of the callback function is stored as the new
690
+ version of that primitive in the graph.
691
+
692
+ If multiple primitives match the specified id, then each is passed, in turn,
693
+ to the callback function.
694
+ """
695
+
522
696
@ spec modify ( graph :: t ( ) , id :: any , action :: ( ... -> Primitive . t ( ) ) ) :: t ( )
523
697
def modify ( graph , id , action )
524
698
@@ -531,13 +705,30 @@ defmodule Scenic.Graph do
531
705
532
706
# ============================================================================
533
707
# map a graph via traversal from the root node
708
+ @ doc """
709
+ Map all primitives in a graph into a new graph.
710
+
711
+ Crawls through the entire graph, passing each primitive to the callback function.
712
+ The result of the callback replaces that primitive in the graph. The updated
713
+ graph is returned.
714
+ """
715
+
534
716
@ spec map ( graph :: t ( ) , action :: function ) :: t ( )
535
717
def map ( % __MODULE__ { } = graph , action ) when is_function ( action , 1 ) do
536
718
do_map ( graph , @ root_uid , action )
537
719
end
538
720
539
721
# ============================================================================
540
- # map a graph, but only those elements mapped to the id
722
+ @ doc """
723
+ Map all primitives in a graph that match a specified id into a new graph.
724
+
725
+ Crawls through the entire graph, passing each primitive to the callback function.
726
+ The result of the callback replaces that primitive in the graph. The updated
727
+ graph is returned.
728
+
729
+ This is so similar to the modify function that it may be deprecated in the future.
730
+ For now I recommend you use `Graph.modify/3` instead of this.
731
+ """
541
732
@ spec map ( graph :: t ( ) , id :: any , action :: function ) :: t ( )
542
733
def map ( % __MODULE__ { } = graph , id , action ) when is_function ( action , 1 ) do
543
734
# resolve the id into a list of uids
@@ -589,14 +780,32 @@ defmodule Scenic.Graph do
589
780
end
590
781
591
782
# ============================================================================
783
+ @ doc """
784
+ Invokes action for each primitive in the graph with the accumulator.
785
+
786
+ Iterates over all primitives in a graph, passing each into the callback function
787
+ with an accumulator. The return value of the callback is the new accumulator.
788
+
789
+ This is extremely similar in behaviour to Elixir's Enum.reduce function, except
790
+ that it understands now to navigate the tree structure of a Graph.
791
+ """
792
+
592
793
# reduce a graph via traversal from the root node
593
794
@ spec reduce ( graph :: t ( ) , acc :: any , action :: function ) :: any
594
795
def reduce ( % __MODULE__ { } = graph , acc , action ) when is_function ( action , 2 ) do
595
796
do_reduce ( graph , @ root_uid , acc , action )
596
797
end
597
798
598
799
# ============================================================================
599
- # reduce a graph, but only for nodes mapped to the given id
800
+ @ doc """
801
+ Invokes action for each primitive that matches an id in the graph with the accumulator.
802
+
803
+ Iterates over all primitives that match a specified id, passing each into the callback
804
+ function with an accumulator.
805
+
806
+ This is extremely similar in behaviour to Elixir's Enum.reduce function, except
807
+ that it understands now to navigate the tree structure of a Graph.
808
+ """
600
809
@ spec reduce ( graph :: t ( ) , id :: any , acc :: any , action :: function ) :: any
601
810
def reduce ( % __MODULE__ { } = graph , id , acc , action ) when is_function ( action , 2 ) do
602
811
# resolve the id into a list of uids
0 commit comments