@@ -4,6 +4,11 @@ defmodule Scenic.Themes do
4
4
By registering themes in this way you can safely pull in themes from external libraries,
5
5
without theme names colliding, as well as get all the validation.
6
6
7
+ You can add additional keys to be validated on your custom themes by returning a tuple with your map of themes and a list of keys to be validated
8
+ from your load function.
9
+
10
+ All themes will validate against the default schema. If you provide additional keys, the list will get merged with the list of default keys.
11
+
7
12
### Required Configuration
8
13
Setting up themes requires some initial setup.
9
14
@@ -23,13 +28,15 @@ defmodule Scenic.Themes do
23
28
text: @text
24
29
}
25
30
31
+ schema [:surface] # add additional required keys to your theme
32
+
26
33
use Scenic.Themes,
27
34
sources: [
28
35
{:scenic, Scenic.Themes"},
29
36
{:my_app, load()}
30
37
]
31
38
32
- def load(), do: @themes
39
+ def load(), do: { @themes, @schema}
33
40
end
34
41
```
35
42
@@ -42,67 +49,41 @@ defmodule Scenic.Themes do
42
49
43
50
Now themes are passed around scenic in the form of `{:library_name, :theme_name}` as opposed to just :theme_name.
44
51
"""
45
-
46
- @ theme_light % {
47
- text: :black ,
48
- background: :white ,
49
- border: :dark_grey ,
50
- active: { 215 , 215 , 215 } ,
51
- thumb: :cornflower_blue ,
52
- focus: :blue ,
53
- highlight: :saddle_brown
54
- }
55
-
56
- @ theme_dark % {
57
- text: :white ,
58
- background: :black ,
59
- border: :light_grey ,
60
- active: { 40 , 40 , 40 } ,
61
- thumb: :cornflower_blue ,
62
- focus: :cornflower_blue ,
63
- highlight: :sandy_brown
64
- }
65
-
66
- @ primary Map . merge ( @ theme_dark , % { background: { 72 , 122 , 252 } , active: { 58 , 94 , 201 } } )
67
- @ secondary Map . merge ( @ theme_dark , % { background: { 111 , 117 , 125 } , active: { 86 , 90 , 95 } } )
68
- @ success Map . merge ( @ theme_dark , % { background: { 99 , 163 , 74 } , active: { 74 , 123 , 56 } } )
69
- @ danger Map . merge ( @ theme_dark , % { background: { 191 , 72 , 71 } , active: { 164 , 54 , 51 } } )
70
- @ warning Map . merge ( @ theme_light , % { background: { 239 , 196 , 42 } , active: { 197 , 160 , 31 } } )
71
- @ info Map . merge ( @ theme_dark , % { background: { 94 , 159 , 183 } , active: { 70 , 119 , 138 } } )
72
- @ text Map . merge ( @ theme_dark , % { text: { 72 , 122 , 252 } , background: :clear , active: :clear } )
73
- @ themes % {
74
- light: @ theme_light ,
75
- dark: @ theme_dark ,
76
- primary: @ primary ,
77
- secondary: @ secondary ,
78
- success: @ success ,
79
- danger: @ danger ,
80
- warning: @ warning ,
81
- info: @ info ,
82
- text: @ text
83
- }
84
-
85
- @ callback load ( ) :: list
52
+ @ callback load ( ) :: { map , list } | map
53
+ @ optional_callbacks load: 0
86
54
87
55
defmacro __using__ ( using_opts \\ [ ] ) do
88
56
quote do
89
57
alias Scenic.Primitive.Style.Paint.Color
90
58
@ behaviour Scenic.Themes
91
59
@ sources Keyword . get ( unquote ( using_opts ) , :sources , [ ] )
60
+ @ default_schema [ :text , :background , :border , :active , :thumb , :focus ]
61
+
92
62
@ library_themes Enum . reduce ( @ sources , % { } , fn
93
63
{ lib , module } , acc when is_atom ( module ) ->
94
- themes = module . load ( )
95
- Map . put_new ( acc , lib , themes )
64
+ case module . load ( ) do
65
+ { themes , schema } ->
66
+ Map . put_new ( acc , lib , { themes , List . flatten ( [ @ default_schema | schema ] ) } )
67
+ themes ->
68
+ Map . put_new ( acc , lib , { themes , @ default_schema } )
69
+ end
70
+ { lib , { themes , schema } } , acc ->
71
+ Map . put_new ( acc , lib , { themes , List . flatten ( [ @ default_schema | schema ] ) } )
96
72
{ lib , themes } , acc ->
97
- Map . put_new ( acc , lib , themes )
73
+ Map . put_new ( acc , lib , { themes , @ default_schema } )
98
74
_ , acc -> acc
99
75
end )
100
76
101
77
def validate ( theme )
102
- def validate ( { lib , theme } = lib_theme ) when is_atom ( theme ) do
78
+ def validate ( { lib , theme_name } = lib_theme ) when is_atom ( theme_name ) do
79
+ { _ , schema } = Map . get ( @ library_themes , lib )
103
80
case normalize ( lib_theme ) do
104
- map when is_map ( map ) ->
105
- { :ok , lib_theme }
81
+ theme ->
82
+ # validate against the schema
83
+ case validate ( theme , schema ) do
84
+ { :ok , _ } -> { :ok , lib_theme }
85
+ error -> error
86
+ end
106
87
nil ->
107
88
{
108
89
:error ,
@@ -118,6 +99,23 @@ defmodule Scenic.Themes do
118
99
end
119
100
end
120
101
102
+ def validate (
103
+ theme ,
104
+ schema
105
+ ) do
106
+ # we have the schema so we can validate against it.
107
+ schema
108
+ |> Enum . reduce ( { :ok , theme } , fn
109
+ _ , { :error , msg } ->
110
+ { :error , msg }
111
+ key , { :ok , _ } = acc ->
112
+ case Map . has_key? ( theme , key ) do
113
+ true -> acc
114
+ false -> err_key ( key , theme )
115
+ end
116
+ end )
117
+ end
118
+
121
119
def validate (
122
120
% {
123
121
text: _ ,
@@ -128,6 +126,8 @@ defmodule Scenic.Themes do
128
126
focus: _
129
127
} = theme
130
128
) do
129
+ # we dont have the schema so validate against the default,
130
+ # this is not ideal, but should be fine for now.
131
131
# we know all the required colors are there.
132
132
# now make sure they are all valid colors, including any custom added ones.
133
133
theme
@@ -153,6 +153,7 @@ defmodule Scenic.Themes do
153
153
You passed in a map, but it didn't include all the required color specifications.
154
154
It must contain a valid color for each of the following entries.
155
155
:text, :background, :border, :active, :thumb, :focus
156
+ If you're using a custom theme please check the documentation for that specific theme.
156
157
#{ IO.ANSI . default_color ( ) }
157
158
"""
158
159
}
@@ -176,6 +177,37 @@ defmodule Scenic.Themes do
176
177
}
177
178
end
178
179
180
+ @ doc false
181
+ def normalize ( { lib , theme_name } ) when is_atom ( theme_name ) do
182
+ case Map . get ( @ library_themes , lib ) do
183
+ { themes , schema } -> Map . get ( themes , theme_name )
184
+ nil -> nil
185
+ end
186
+ end
187
+
188
+ def normalize ( theme ) when is_map ( theme ) , do: theme
189
+
190
+ @ doc false
191
+ def preset ( { lib , theme_name } ) do
192
+ case Map . get ( @ library_themes , lib ) do
193
+ { themes , schema } -> Map . get ( themes , theme_name )
194
+ nil -> nil
195
+ end
196
+ end
197
+
198
+ defp err_key ( key , map ) do
199
+ {
200
+ :error ,
201
+ """
202
+ #{ IO.ANSI . red ( ) } Invalid theme specification
203
+ Received: #{ inspect ( map ) }
204
+ #{ IO.ANSI . yellow ( ) }
205
+ Map entry: #{ inspect ( key ) }
206
+ #{ IO.ANSI . default_color ( ) }
207
+ """
208
+ }
209
+ end
210
+
179
211
defp err_color ( key , msg ) do
180
212
{
181
213
:error ,
@@ -186,17 +218,10 @@ defmodule Scenic.Themes do
186
218
"""
187
219
}
188
220
end
189
-
190
- @ doc false
191
- def normalize ( { lib , theme } ) when is_atom ( theme ) , do: Map . get ( Map . get ( @ library_themes , lib ) , theme )
192
- def normalize ( theme ) when is_map ( theme ) , do: theme
193
-
194
- @ doc false
195
- def preset ( { lib , theme } ) , do: Map . get ( Map . get ( @ library_themes , lib ) , theme )
196
221
end
197
222
end
198
223
199
- @ moduledoc false
224
+ @ doc false
200
225
def module ( ) do
201
226
with { :ok , config } <- Application . fetch_env ( :scenic , :themes ) ,
202
227
{ :ok , module } <- Keyword . fetch ( config , :module ) do
@@ -226,12 +251,54 @@ defmodule Scenic.Themes do
226
251
end
227
252
end
228
253
254
+ @ doc false
229
255
def validate ( theme ) , do: module ( ) . validate ( theme )
230
256
257
+ @ doc false
231
258
def normalize ( theme ) , do: module ( ) . normalize ( theme )
232
259
260
+ @ doc false
233
261
def preset ( theme ) , do: module ( ) . preset ( theme )
234
262
263
+ @ theme_light % {
264
+ text: :black ,
265
+ background: :white ,
266
+ border: :dark_grey ,
267
+ active: { 215 , 215 , 215 } ,
268
+ thumb: :cornflower_blue ,
269
+ focus: :blue ,
270
+ highlight: :saddle_brown
271
+ }
272
+
273
+ @ theme_dark % {
274
+ text: :white ,
275
+ background: :black ,
276
+ border: :light_grey ,
277
+ active: { 40 , 40 , 40 } ,
278
+ thumb: :cornflower_blue ,
279
+ focus: :cornflower_blue ,
280
+ highlight: :sandy_brown
281
+ }
282
+
283
+ @ primary Map . merge ( @ theme_dark , % { background: { 72 , 122 , 252 } , active: { 58 , 94 , 201 } } )
284
+ @ secondary Map . merge ( @ theme_dark , % { background: { 111 , 117 , 125 } , active: { 86 , 90 , 95 } } )
285
+ @ success Map . merge ( @ theme_dark , % { background: { 99 , 163 , 74 } , active: { 74 , 123 , 56 } } )
286
+ @ danger Map . merge ( @ theme_dark , % { background: { 191 , 72 , 71 } , active: { 164 , 54 , 51 } } )
287
+ @ warning Map . merge ( @ theme_light , % { background: { 239 , 196 , 42 } , active: { 197 , 160 , 31 } } )
288
+ @ info Map . merge ( @ theme_dark , % { background: { 94 , 159 , 183 } , active: { 70 , 119 , 138 } } )
289
+ @ text Map . merge ( @ theme_dark , % { text: { 72 , 122 , 252 } , background: :clear , active: :clear } )
290
+ @ themes % {
291
+ light: @ theme_light ,
292
+ dark: @ theme_dark ,
293
+ primary: @ primary ,
294
+ secondary: @ secondary ,
295
+ success: @ success ,
296
+ danger: @ danger ,
297
+ warning: @ warning ,
298
+ info: @ info ,
299
+ text: @ text
300
+ }
301
+
235
302
@ doc false
236
303
def load ( ) , do: @ themes
237
304
end
0 commit comments