on master help sign in
Move all field related stuff to Graphics.Input.Field and add the ability to style fields
093d7afEvan Czaplicki
libraries/Graphics/Input.elm+4−57
@@ -19,14 +19,15 @@ examples in this library, so just read on to get a better idea of how it works!
19@docs Input, input
19@docs Input, input
20
20
21# Basic Input Elements
21# Basic Input Elements
22−Text fields come later.
22+
23+To learn about text fields, see the
24+[`Graphics.Input.Field`](Graphics-Input-Field) library.
25+
23@docs button, customButton, checkbox, dropDown
26@docs button, customButton, checkbox, dropDown
24
27
25# Clicks and Hovers
28# Clicks and Hovers
26@docs clickable, hoverable
29@docs clickable, hoverable
27
30
28−# Text Fields
29−@docs field, password, email, noContent, FieldContent, Selection, Direction
30-}
31-}
31
32
32import Signal (Signal)
33import Signal (Signal)
@@ -175,57 +176,3 @@ distinguished with IDs or more complex data structures.
175-}
176-}
176clickable : Handle a -> a -> Element -> Element
177clickable : Handle a -> a -> Element -> Element
177clickable = Native.Graphics.Input.clickable
178clickable = Native.Graphics.Input.clickable
178−
179−{-| Represents the current content of a text field. For example:
180−
181− FieldContent "She sells sea shells" (Selection 0 3 Backward)
182−
183−This means the user highlighted the substring `"She"` backwards.
184−-}
185−type FieldContent = { string:String, selection:Selection }
186−
187−{-| The selection within a text field. `start` is never greater than `end`:
188−
189− Selection 0 0 Forward -- cursor precedes all characters
190−
191− Selection 5 9 Backward -- highlighting characters starting after
192− -- the 5th and ending after the 9th
193−-}
194−type Selection = { start:Int, end:Int, direction:Direction }
195−
196−{-| The direction of selection.-}
197−data Direction = Forward | Backward
198−
199−{-| A field with no content:
200−
201− FieldContent "" (Selection 0 0 Forward)
202−-}
203−noContent : FieldContent
204−noContent = FieldContent "" (Selection 0 0 Forward)
205−
206−{-| Create a text field. The following example creates a time-varying element
207−called `nameField`. As the user types their name, the field will be updated
208−to match what they have entered.
209−
210− name : Input FieldContent
211− name = input noContent
212−
213− nameField : Signal Element
214− nameField = field name.handle id "Name" <~ name.signal
215−-}
216−field : Handle a -> (FieldContent -> a) -> String -> FieldContent -> Element
217−field = Native.Graphics.Input.field
218−
219−{-| Same as `field` but the UI element blocks out each characters. -}
220−password : Handle a -> (FieldContent -> a) -> String -> FieldContent -> Element
221−password = Native.Graphics.Input.password
222−
223−{-| Same as `field` but it adds an annotation that this field is for email
224−addresses. This is helpful for auto-complete and for mobile users who may
225−get a custom keyboard with an `@` and `.com` button.
226−-}
227−email : Handle a -> (FieldContent -> a) -> String -> FieldContent -> Element
228−email = Native.Graphics.Input.email
229−
230−-- area : Handle a -> (FieldContent -> a) -> Handle b -> ((Int,Int) -> b) -> (Int,Int) -> String -> FieldContent -> Element
231−-- area = Native.Graphics.Input.area
libraries/Graphics/Input/Field.elmnew file+130
@@ -0,0 +1,130 @@
1+module Graphics.Input.Field where
2+{-| This library specifically addresses text fields. It uses the same genral
3+approach as the [`Graphics.Input`](Graphics-Input) module for describing an
4+`Input`, so this library focuses on creating and styling text fields.
5+
6+# Create Fields
7+@docs field, password, email
8+
9+# Field Content
10+@docs Content, Selection, Direction, noContent
11+
12+# Field Style
13+@docs Style, Outline, noOutline, Highlight, noHighlight, Dimensions, uniformly
14+-}
15+
16+import Color (Color)
17+import Color
18+import Graphics.Element (Element)
19+import Graphics.Input (Input, Handle)
20+import Native.Graphics.Input
21+import Text
22+
23+{-| Easily create uniform dimensions:
24+
25+ uniformly 4 == { left=4, right=4, top=4, bottom=4 }
26+-}
27+uniformly : Int -> Dimensions
28+uniformly n = Dimensions n n n n
29+
30+{-| For setting dimensions of a fields padding or border. The left, right, top,
31+and bottom may all have different sizes.
32+-}
33+type Dimensions = { left:Int, right:Int, top:Int, bottom:Int }
34+
35+{-| A field can have a outline around it. This lets you set its color, width,
36+and radius. The radius allows you to round the corners of your field. Set the
37+width to zero to make it invisible.
38+-}
39+type Outline = { color:Color, width:Dimensions, radius:Int }
40+
41+{-| An outline with zero width, so you cannot see it. -}
42+noOutline : Outline
43+noOutline = Outline Color.grey (uniformly 0) 0
44+
45+{-| When a field is selected, it has an highlight around it by default. Set the
46+width of the `Highlight` to zero to make it go away.
47+-}
48+type Highlight = { color:Color, width:Int }
49+
50+{-| An highlight with zero width, so you cannot see it. -}
51+noHighlight : Highlight
52+noHighlight = Highlight Color.blue 0
53+
54+{-| Describes the style of a text box. The `style` field describes the style
55+of the text itself. The `outline` field describes the glowing blue outline that
56+shows up when the field has focus. Turn off `outline` by setting its width to
57+zero. The
58+-}
59+type Style =
60+ { padding : Dimensions
61+ , outline : Outline
62+ , highlight : Highlight
63+ , style : Text.Style
64+ }
65+
66+{-| The default style for a text field. The outline is `Color.grey` with width
67+1 and radius 2. The highlight is `Color.blue` with width 1, and the default
68+text color is black.
69+-}
70+defaultStyle : Style
71+defaultStyle =
72+ { padding = uniformly 4
73+ , outline = Outline Color.grey (uniformly 1) 2
74+ , highlight = Highlight Color.blue 1
75+ , style = Text.defaultStyle
76+ }
77+
78+{-| Represents the current content of a text field. For example:
79+
80+ Content "She sells sea shells" (Selection 0 3 Backward)
81+
82+This means the user highlighted the substring `"She"` backwards.
83+-}
84+type Content = { string:String, selection:Selection }
85+
86+{-| The selection within a text field. `start` is never greater than `end`:
87+
88+ Selection 0 0 Forward -- cursor precedes all characters
89+
90+ Selection 5 9 Backward -- highlighting characters starting after
91+ -- the 5th and ending after the 9th
92+-}
93+type Selection = { start:Int, end:Int, direction:Direction }
94+
95+{-| The direction of selection.-}
96+data Direction = Forward | Backward
97+
98+{-| A field with no content:
99+
100+ Content "" (Selection 0 0 Forward)
101+-}
102+noContent : Content
103+noContent = Content "" (Selection 0 0 Forward)
104+
105+{-| Create a text field. The following example creates a time-varying element
106+called `nameField`. As the user types their name, the field will be updated
107+to match what they have entered.
108+
109+ name : Input Content
110+ name = input noContent
111+
112+ nameField : Signal Element
113+ nameField = field name.handle id "Name" <~ name.signal
114+-}
115+field : Handle a -> (Content -> a) -> Style -> String -> Content -> Element
116+field = Native.Graphics.Input.field
117+
118+{-| Same as `field` but the UI element blocks out each characters. -}
119+password : Handle a -> (Content -> a) -> Style -> String -> Content -> Element
120+password = Native.Graphics.Input.password
121+
122+{-| Same as `field` but it adds an annotation that this field is for email
123+addresses. This is helpful for auto-complete and for mobile users who may
124+get a custom keyboard with an `@` and `.com` button.
125+-}
126+email : Handle a -> (Content -> a) -> Style -> String -> Content -> Element
127+email = Native.Graphics.Input.email
128+
129+-- area : Handle a -> (Content -> a) -> Handle b -> ((Int,Int) -> b) -> (Int,Int) -> String -> Content -> Element
130+-- area = Native.Graphics.Input.area
libraries/Native/Graphics/Input.js+59−7
@@ -8,6 +8,8 @@ Elm.Native.Graphics.Input.make = function(elm) {
8 var Render = ElmRuntime.use(ElmRuntime.Render.Element);
8 var Render = ElmRuntime.use(ElmRuntime.Render.Element);
9 var newNode = ElmRuntime.use(ElmRuntime.Render.Utils).newElement;
9 var newNode = ElmRuntime.use(ElmRuntime.Render.Utils).newElement;
10
10
11+ var toCss = Elm.Native.Color.make(elm).toCss;
12+ var Text = Elm.Native.Text.make(elm);
11 var Signal = Elm.Signal.make(elm);
13 var Signal = Elm.Signal.make(elm);
12 var newElement = Elm.Graphics.Element.make(elm).newElement;
14 var newElement = Elm.Graphics.Element.make(elm).newElement;
13 var JS = Elm.Native.JavaScript.make(elm);
15 var JS = Elm.Native.JavaScript.make(elm);
@@ -215,11 +217,52 @@ Elm.Native.Graphics.Input.make = function(elm) {
215 }
217 }
216 }
218 }
217
219
220+ function updateIfNeeded(css, attribute, latestAttribute) {
221+ if (css[attribute] !== latestAttribute) {
222+ css[attribute] = latestAttribute;
223+ }
224+ }
225+ function cssDimensions(dimensions) {
226+ return dimensions.top + 'px ' +
227+ dimensions.right + 'px ' +
228+ dimensions.bottom + 'px ' +
229+ dimensions.left + 'px';
230+ }
231+ function updateFieldStyle(css, style) {
232+ updateIfNeeded(css, 'padding', cssDimensions(style.padding));
233+
234+ var outline = style.outline;
235+ updateIfNeeded(css, 'border-width', cssDimensions(outline.width));
236+ updateIfNeeded(css, 'border-color', toCss(outline.color));
237+ updateIfNeeded(css, 'border-radius', outline.radius + 'px');
238+
239+ var highlight = style.highlight;
240+ if (highlight.width === 0) {
241+ css.outline = 'none';
242+ } else {
243+ updateIfNeeded(css, 'outline-width', highlight.width + 'px');
244+ updateIfNeeded(css, 'outline-color', toCss(highlight.color));
245+ }
246+
247+ var textStyle = style.style;
248+ updateIfNeeded(css, 'color', toCss(textStyle.color));
249+ if (textStyle.typeface.ctor !== '[]') {
250+ updateIfNeeded(css, 'font-family', Text.toTypefaces(textStyle.typeface));
251+ }
252+ if (textStyle.height.ctor !== "Nothing") {
253+ updateIfNeeded(css, 'font-size', textStyle.height._0 + 'px');
254+ }
255+ updateIfNeeded(css, 'font-weight', textStyle.bold ? 'bold' : 'normal');
256+ updateIfNeeded(css, 'font-style', textStyle.italic ? 'italic' : 'normal');
257+ if (textStyle.line.ctor !== 'Nothing') {
258+ updateIfNeeded(css, 'text-decoration', Text.toLine(textStyle.line._0));
259+ }
260+ }
261+
218 function renderField(model) {
262 function renderField(model) {
219 var field = newNode('input');
263 var field = newNode('input');
220− field.style.border = 'none';
264+ updateFieldStyle(field.style, model.style);
221− field.style.outline = 'none';
265+ field.style.borderStyle = 'solid';
222− field.style.backgroundColor = 'transparent';
223 field.style.pointerEvents = 'auto';
266 field.style.pointerEvents = 'auto';
224
267
225 field.type = model.type;
268 field.type = model.type;
@@ -302,6 +345,9 @@ Elm.Native.Graphics.Input.make = function(elm) {
302 }
345 }
303
346
304 function updateField(field, oldModel, newModel) {
347 function updateField(field, oldModel, newModel) {
348+ if (oldModel.style !== newModel.style) {
349+ updateFieldStyle(field.style, newModel.style);
350+ }
305 field.elm_signal = newModel.signal;
351 field.elm_signal = newModel.signal;
306 field.elm_handler = newModel.handler;
352 field.elm_handler = newModel.handler;
307
353
@@ -316,10 +362,16 @@ Elm.Native.Graphics.Input.make = function(elm) {
316 }
362 }
317
363
318 function mkField(type) {
364 function mkField(type) {
319− function field(signal, handler, placeHolder, content) {
365+ function field(signal, handler, style, placeHolder, content) {
366+ var padding = style.padding;
367+ var outline = style.outline.width;
368+ var adjustWidth = padding.left + padding.right + outline.left + outline.right;
369+ var adjustHeight = padding.top + padding.bottom + outline.top + outline.bottom;
320 return A3(newElement, 200, 30, {
370 return A3(newElement, 200, 30, {
321 ctor: 'Custom',
371 ctor: 'Custom',
322− type: type + 'Input',
372+ type: type + 'Field',
373+ adjustWidth: adjustWidth,
374+ adjustHeight: adjustHeight,
323 render: renderField,
375 render: renderField,
324 update: updateField,
376 update: updateField,
325 model: {
377 model: {
@@ -327,14 +379,14 @@ Elm.Native.Graphics.Input.make = function(elm) {
327 handler:handler,
379 handler:handler,
328 placeHolder:placeHolder,
380 placeHolder:placeHolder,
329 content:content,
381 content:content,
382+ style:style,
330 type:type
383 type:type
331 }
384 }
332 });
385 });
333 }
386 }
334− return F4(field);
387+ return F5(field);
335 }
388 }
336
389
337−
338 function hoverable(signal, handler, elem) {
390 function hoverable(signal, handler, elem) {
339 function onHover(bool) {
391 function onHover(bool) {
340 elm.notify(signal.id, handler(bool));
392 elm.notify(signal.id, handler(bool));
runtime/Render/Element.js+20−3
@@ -7,7 +7,15 @@ var newElement = Utils.newElement, extract = Utils.extract,
7 addTransform = Utils.addTransform, removeTransform = Utils.removeTransform,
7 addTransform = Utils.addTransform, removeTransform = Utils.removeTransform,
8 fromList = Utils.fromList, eq = Utils.eq;
8 fromList = Utils.fromList, eq = Utils.eq;
9
9
10−function setProps(props, e) {
10+function setProps(elem, e) {
11+ var props = elem.props;
12+ var element = elem.element;
13+ if (element.adjustWidth) {
14+ props.width -= element.adjustWidth;
15+ }
16+ if (element.adjustHeight) {
17+ props.height -= element.adjustHeight;
18+ }
11 e.style.width = (props.width |0) + 'px';
19 e.style.width = (props.width |0) + 'px';
12 e.style.height = (props.height|0) + 'px';
20 e.style.height = (props.height|0) + 'px';
13 if (props.opacity !== 1) { e.style.opacity = props.opacity; }
21 if (props.opacity !== 1) { e.style.opacity = props.opacity; }
@@ -200,7 +208,7 @@ function rawHtml(elem) {
200 return div;
208 return div;
201}
209}
202
210
203−function render(elem) { return setProps(elem.props, makeElement(elem)); }
211+function render(elem) { return setProps(elem, makeElement(elem)); }
204function makeElement(e) {
212function makeElement(e) {
205 var elem = e.element;
213 var elem = e.element;
206 switch(elem.ctor) {
214 switch(elem.ctor) {
@@ -308,7 +316,16 @@ function update(node, curr, next) {
308}
316}
309
317
310function updateProps(node, curr, next) {
318function updateProps(node, curr, next) {
311− var props = next.props, currP = curr.props, e = node;
319+ var props = next.props;
320+ var currP = curr.props;
321+ var e = node;
322+ var element = next.element;
323+ if (element.adjustWidth) {
324+ props.width -= element.adjustWidth;
325+ }
326+ if (element.adjustHeight) {
327+ props.height -= element.adjustHeight;
328+ }
312 if (props.width !== currP.width) e.style.width = (props.width |0) + 'px';
329 if (props.width !== currP.width) e.style.width = (props.width |0) + 'px';
313 if (props.height !== currP.height) e.style.height = (props.height|0) + 'px';
330 if (props.height !== currP.height) e.style.height = (props.height|0) + 'px';
314 if (props.opacity !== 1 && props.opacity !== currP.opacity) {
331 if (props.opacity !== 1 && props.opacity !== currP.opacity) {