on c3c52cd help sign in
410 lines 14 kB history raw
1Elm.Native.Graphics.Input = {};
2Elm.Native.Graphics.Input.make = function(elm) {
3 elm.Native = elm.Native || {};
4 elm.Native.Graphics = elm.Native.Graphics || {};
5 elm.Native.Graphics.Input = elm.Native.Graphics.Input || {};
6 if (elm.Native.Graphics.Input.values) return elm.Native.Graphics.Input.values;
8 var Render = ElmRuntime.use(ElmRuntime.Render.Element);
9 var newNode = ElmRuntime.use(ElmRuntime.Render.Utils).newElement;
11 var toCss = Elm.Native.Color.make(elm).toCss;
12 var Text = Elm.Native.Text.make(elm);
13 var Signal = Elm.Signal.make(elm);
14 var newElement = Elm.Graphics.Element.make(elm).newElement;
15 var List = Elm.Native.List.make(elm);
16 var Utils = Elm.Native.Utils.make(elm);
17 var Tuple2 = Utils.Tuple2;
19 function input(initialValue) {
20 var signal = Signal.constant(initialValue);
21 return { _:{}, signal:signal, handle:signal };
22 }
24 function renderDropDown(model) {
25 var drop = newNode('select');
26 drop.style.border = '0 solid';
27 drop.style.pointerEvents = 'auto';
28 drop.style.display = 'block';
30 drop.elm_signal = model.signal;
31 drop.elm_values = List.toArray(model.values);
32 var values = drop.elm_values;
34 for (var i = 0; i < values.length; ++i) {
35 var option = newNode('option');
36 var name = values[i]._0;
37 option.value = name;
38 option.innerHTML = name;
39 drop.appendChild(option);
40 }
41 drop.addEventListener('change', function() {
42 elm.notify(drop.elm_signal.id, drop.elm_values[drop.selectedIndex]._1);
43 });
45 return drop;
46 }
48 function updateDropDown(node, oldModel, newModel) {
49 node.elm_signal = newModel.signal;
50 node.elm_values = List.toArray(newModel.values);
52 var values = node.elm_values;
53 var kids = node.childNodes;
54 var kidsLength = kids.length;
56 var i = 0;
57 for (; i < kidsLength && i < values.length; ++i) {
58 var option = kids[i];
59 var name = values[i]._0;
60 option.value = name;
61 option.innerHTML = name;
62 }
63 for (; i < kidsLength; ++i) {
64 node.removeChild(node.lastChild);
65 }
66 for (; i < values.length; ++i) {
67 var option = newNode('option');
68 var name = values[i]._0;
69 option.value = name;
70 option.innerHTML = name;
71 node.appendChild(option);
72 }
73 }
75 function dropDown(signal, values) {
76 return A3(newElement, 100, 24, {
77 ctor: 'Custom',
78 type: 'DropDown',
79 render: renderDropDown,
80 update: updateDropDown,
81 model: {
82 signal: signal,
83 values: values
84 }
85 });
86 }
88 function renderButton(model) {
89 var node = newNode('button');
90 node.style.display = 'block';
91 node.style.pointerEvents = 'auto';
92 node.elm_signal = model.signal;
93 node.elm_value = model.value;
94 function click() {
95 elm.notify(node.elm_signal.id, node.elm_value);
96 }
97 node.addEventListener('click', click);
98 node.innerHTML = model.text;
99 return node;
100 }
102 function updateButton(node, oldModel, newModel) {
103 node.elm_signal = newModel.signal;
104 node.elm_value = newModel.value;
105 var txt = newModel.text;
106 if (oldModel.text !== txt) node.innerHTML = txt;
107 }
109 function button(signal, value, text) {
110 return A3(newElement, 100, 40, {
111 ctor: 'Custom',
112 type: 'Button',
113 render: renderButton,
114 update: updateButton,
115 model: { signal:signal, value:value, text:text }
116 });
117 }
119 function renderCustomButton(model) {
120 var btn = newNode('div');
121 btn.style.pointerEvents = 'auto';
122 btn.elm_signal = model.signal;
123 btn.elm_value = model.value;
125 btn.elm_up = Render.render(model.up);
126 btn.elm_hover = Render.render(model.hover);
127 btn.elm_down = Render.render(model.down);
129 btn.elm_up.style.display = 'block';
130 btn.elm_hover.style.display = 'none';
131 btn.elm_down.style.display = 'none';
133 btn.appendChild(btn.elm_up);
134 btn.appendChild(btn.elm_hover);
135 btn.appendChild(btn.elm_down);
137 function swap(visibleNode, hiddenNode1, hiddenNode2) {
138 visibleNode.style.display = 'block';
139 hiddenNode1.style.display = 'none';
140 hiddenNode2.style.display = 'none';
141 }
143 var overCount = 0;
144 function over(e) {
145 if (overCount++ > 0) return;
146 swap(btn.elm_hover, btn.elm_down, btn.elm_up);
147 }
148 function out(e) {
149 if (btn.contains(e.toElement || e.relatedTarget)) return;
150 overCount = 0;
151 swap(btn.elm_up, btn.elm_down, btn.elm_hover);
152 }
153 function up() {
154 swap(btn.elm_hover, btn.elm_down, btn.elm_up);
155 elm.notify(btn.elm_signal.id, btn.elm_value);
156 }
157 function down() {
158 swap(btn.elm_down, btn.elm_hover, btn.elm_up);
159 }
161 btn.addEventListener('mouseover', over);
162 btn.addEventListener('mouseout' , out);
163 btn.addEventListener('mousedown', down);
164 btn.addEventListener('mouseup' , up);
166 return btn;
167 }
169 function updateCustomButton(node, oldModel, newModel) {
170 node.elm_signal = newModel.signal;
171 node.elm_value = newModel.value;
173 var kids = node.childNodes;
174 var styleUp = kids[0].style.display;
175 var styleHover = kids[1].style.display;
176 var styleDown = kids[2].style.display;
178 Render.update(kids[0], oldModel.up, newModel.up);
179 Render.update(kids[1], oldModel.hover, newModel.hover);
180 Render.update(kids[2], oldModel.down, newModel.down);
182 var kids = node.childNodes;
183 kids[0].style.display = styleUp;
184 kids[1].style.display = styleHover;
185 kids[2].style.display = styleDown;
186 }
188 function max3(a,b,c) {
189 var ab = a > b ? a : b;
190 return ab > c ? ab : c;
191 }
193 function customButton(signal, value, up, hover, down) {
194 return A3(newElement,
195 max3(up.props.width, hover.props.width, down.props.width),
196 max3(up.props.height, hover.props.height, down.props.height),
197 { ctor: 'Custom',
198 type: 'CustomButton',
199 render: renderCustomButton,
200 update: updateCustomButton,
201 model: { signal:signal, value:value, up:up, hover:hover, down:down }
202 });
203 }
205 function renderCheckbox(model) {
206 var node = newNode('input');
207 node.type = 'checkbox';
208 node.checked = model.checked;
209 node.style.display = 'block';
210 node.style.pointerEvents = 'auto';
211 node.elm_signal = model.signal;
212 node.elm_handler = model.handler;
213 function change() {
214 elm.notify(node.elm_signal.id, node.elm_handler(node.checked));
215 }
216 node.addEventListener('change', change);
217 return node;
218 }
220 function updateCheckbox(node, oldModel, newModel) {
221 node.elm_signal = newModel.signal;
222 node.elm_handler = newModel.handler;
223 node.checked = newModel.checked;
224 return true;
225 }
227 function checkbox(signal, handler, checked) {
228 return A3(newElement, 13, 13, {
229 ctor: 'Custom',
230 type: 'CheckBox',
231 render: renderCheckbox,
232 update: updateCheckbox,
233 model: { signal:signal, handler:handler, checked:checked }
234 });
235 }
237 function setRange(node, start, end, dir) {
238 if (node.parentNode) {
239 node.setSelectionRange(start, end, dir);
240 } else {
241 setTimeout(function(){node.setSelectionRange(start, end, dir);}, 0);
242 }
243 }
245 function updateIfNeeded(css, attribute, latestAttribute) {
246 if (css[attribute] !== latestAttribute) {
247 css[attribute] = latestAttribute;
248 }
249 }
250 function cssDimensions(dimensions) {
251 return dimensions.top + 'px ' +
252 dimensions.right + 'px ' +
253 dimensions.bottom + 'px ' +
254 dimensions.left + 'px';
255 }
256 function updateFieldStyle(css, style) {
257 updateIfNeeded(css, 'padding', cssDimensions(style.padding));
259 var outline = style.outline;
260 updateIfNeeded(css, 'border-width', cssDimensions(outline.width));
261 updateIfNeeded(css, 'border-color', toCss(outline.color));
262 updateIfNeeded(css, 'border-radius', outline.radius + 'px');
264 var highlight = style.highlight;
265 if (highlight.width === 0) {
266 css.outline = 'none';
267 } else {
268 updateIfNeeded(css, 'outline-width', highlight.width + 'px');
269 updateIfNeeded(css, 'outline-color', toCss(highlight.color));
270 }
272 var textStyle = style.style;
273 updateIfNeeded(css, 'color', toCss(textStyle.color));
274 if (textStyle.typeface.ctor !== '[]') {
275 updateIfNeeded(css, 'font-family', Text.toTypefaces(textStyle.typeface));
276 }
277 if (textStyle.height.ctor !== "Nothing") {
278 updateIfNeeded(css, 'font-size', textStyle.height._0 + 'px');
279 }
280 updateIfNeeded(css, 'font-weight', textStyle.bold ? 'bold' : 'normal');
281 updateIfNeeded(css, 'font-style', textStyle.italic ? 'italic' : 'normal');
282 if (textStyle.line.ctor !== 'Nothing') {
283 updateIfNeeded(css, 'text-decoration', Text.toLine(textStyle.line._0));
284 }
285 }
287 function renderField(model) {
288 var field = newNode('input');
289 updateFieldStyle(field.style, model.style);
290 field.style.borderStyle = 'solid';
291 field.style.pointerEvents = 'auto';
293 field.type = model.type;
294 field.placeholder = model.placeHolder;
295 field.value = model.content.string;
297 field.elm_signal = model.signal;
298 field.elm_handler = model.handler;
299 field.elm_old_value = field.value;
301 function inputUpdate(event) {
302 var curr = field.elm_old_value;
303 var next = field.value;
304 if (curr === next) {
305 return;
306 }
308 var direction = field.selectionDirection === 'forward' ? 'Forward' : 'Backward';
309 var start = field.selectionStart;
310 var end = field.selectionEnd;
311 field.value = field.elm_old_value;
313 elm.notify(field.elm_signal.id, field.elm_handler({
314 _:{},
315 string: next,
316 selection: {
317 _:{},
318 start: start,
319 end: end,
320 direction: { ctor: direction }
321 }
322 }));
323 }
325 field.addEventListener('input', inputUpdate);
326 field.addEventListener('focus', function() {
327 field.elm_hasFocus = true;
328 });
329 field.addEventListener('blur', function() {
330 field.elm_hasFocus = false;
331 });
333 return field;
334 }
336 function updateField(field, oldModel, newModel) {
337 if (oldModel.style !== newModel.style) {
338 updateFieldStyle(field.style, newModel.style);
339 }
340 field.elm_signal = newModel.signal;
341 field.elm_handler = newModel.handler;
343 field.type = newModel.type;
344 field.placeholder = newModel.placeHolder;
345 var value = newModel.content.string;
346 field.value = value;
347 field.elm_old_value = value;
348 if (field.elm_hasFocus) {
349 var selection = newModel.content.selection;
350 var direction = selection.direction.ctor === 'Forward' ? 'forward' : 'backward';
351 setRange(field, selection.start, selection.end, direction);
352 }
353 }
355 function mkField(type) {
356 function field(style, signal, handler, placeHolder, content) {
357 var padding = style.padding;
358 var outline = style.outline.width;
359 var adjustWidth = padding.left + padding.right + outline.left + outline.right;
360 var adjustHeight = padding.top + padding.bottom + outline.top + outline.bottom;
361 return A3(newElement, 200, 30, {
362 ctor: 'Custom',
363 type: type + 'Field',
364 adjustWidth: adjustWidth,
365 adjustHeight: adjustHeight,
366 render: renderField,
367 update: updateField,
368 model: {
369 signal:signal,
370 handler:handler,
371 placeHolder:placeHolder,
372 content:content,
373 style:style,
374 type:type
375 }
376 });
377 }
378 return F5(field);
379 }
381 function hoverable(signal, handler, elem) {
382 function onHover(bool) {
383 elm.notify(signal.id, handler(bool));
384 }
385 var props = Utils.replace([['hover',onHover]], elem.props);
386 return { props:props, element:elem.element };
387 }
389 function clickable(signal, value, elem) {
390 function onClick(bool) {
391 elm.notify(signal.id, value);
392 }
393 var props = Utils.replace([['click',onClick]], elem.props);
394 return { props:props, element:elem.element };
395 }
397 return elm.Native.Graphics.Input.values = {
398 input:input,
399 button:F3(button),
400 customButton:F5(customButton),
401 checkbox:F3(checkbox),
402 dropDown:F2(dropDown),
403 field:mkField('text'),
404 email:mkField('email'),
405 password:mkField('password'),
406 hoverable:F3(hoverable),
407 clickable:F3(clickable)
408 };
410};