Attempt to revamp the Graphics.Input library, much testing still needed
This commit is contained in:
parent
8fbb9c799d
commit
1c832af6b0
4 changed files with 438 additions and 401 deletions
|
@ -53,7 +53,8 @@ type Properties = {
|
|||
color : Maybe Color,
|
||||
href : JSString,
|
||||
tag : JSString,
|
||||
hover : ()
|
||||
hover : (),
|
||||
click : ()
|
||||
}
|
||||
|
||||
type Element = { props : Properties, element : ElementPrim }
|
||||
|
@ -128,7 +129,9 @@ link href e = let p = e.props in
|
|||
|
||||
emptyStr = JS.fromString ""
|
||||
newElement w h e =
|
||||
{ props = Properties (Native.Utils.guid ()) w h 1 Nothing emptyStr emptyStr (), element = e }
|
||||
{ props = Properties (Native.Utils.guid ()) w h 1 Nothing emptyStr emptyStr () ()
|
||||
, element = e
|
||||
}
|
||||
|
||||
data ElementPrim
|
||||
= Image ImageStyle Int Int JSString
|
||||
|
|
|
@ -1,199 +1,193 @@
|
|||
module Graphics.Input where
|
||||
|
||||
{-| This module is for creating standard input widgets such as buttons and
|
||||
text boxes. In general, functions in this library return a signal representing
|
||||
events from the user.
|
||||
text fields. All functions in this library follow this general pattern:
|
||||
|
||||
The simplest inputs are *one-way inputs*, meaning the user can update
|
||||
them, but the programmer cannot. If you need to update an input from
|
||||
within the program you want the slightly more complex *two-way inputs*.
|
||||
This document will always show the one-way inputs first, *then* the
|
||||
two-way inputs.
|
||||
```haskell
|
||||
-- (Signal (), SignalID ())
|
||||
(clicks, clickID) = input ()
|
||||
|
||||
# Buttons
|
||||
@docs button, customButton, buttons, customButtons
|
||||
clickableYogi : Element
|
||||
clickableYogi = clickable clickID () (image 40 40 "/yogi.jpg")
|
||||
```
|
||||
|
||||
# Fields
|
||||
@docs field, password, email, fields, FieldState, emptyFieldState
|
||||
Whenever the user clicks on the resulting `clickableYogi` element, it sends a
|
||||
`()` to the `clicks` signal. Typically, you would create all your input signals
|
||||
when setting up the signal graph, so then your graphics code can refer to them
|
||||
easily.
|
||||
|
||||
# Checkboxes
|
||||
@docs checkbox, checkboxes
|
||||
# Creating Inputs
|
||||
@docs input
|
||||
|
||||
# Drop Downs
|
||||
@docs stringDropDown, dropDown
|
||||
# Basic Input Elements
|
||||
Text fields come later.
|
||||
@docs button, customButton, checkbox, dropDown
|
||||
|
||||
# Mouse Hover
|
||||
@docs hoverable, hoverables
|
||||
# Clicks and Hovers
|
||||
@docs clickable, hoverable
|
||||
|
||||
# Text Fields
|
||||
@docs field, password, email, noContent, FieldContent, Selection, Direction
|
||||
-}
|
||||
|
||||
import Basics (String)
|
||||
import Signal (Signal,lift,dropRepeats)
|
||||
import Native.Graphics.Input
|
||||
import List
|
||||
import Signal (Signal)
|
||||
import Graphics.Element (Element)
|
||||
import Maybe (Maybe)
|
||||
import JavaScript (JSString)
|
||||
|
||||
id x = x
|
||||
data SignalID a = SignalID
|
||||
|
||||
{-| Create a group of buttons.
|
||||
{-| This function is the key to using this library. It creates a signal that
|
||||
input elements (buttens, checkboxes, etc.) can report to. It also create a
|
||||
unique `SignalID` that allows input elements to refer to this particular
|
||||
signal. It may be best to look at examples in the rest of the docs and return
|
||||
to this function.
|
||||
|
||||
* The first argument is the default value of the `events` signal.
|
||||
* The `events` signal represents all of the activity in this group
|
||||
of buttons.
|
||||
* The `button` function creates a button
|
||||
with the given name, like “Submit” or “Cancel”.
|
||||
The `a` value is sent to `events` whenever the button is pressed.
|
||||
Note: creating an input signal is an inherently imperative action, so this is
|
||||
one of very few impure functions in Elm. It is designed such that it can only
|
||||
be used as you build your signal graph at startup, keeping Elm pure at runtime.
|
||||
-}
|
||||
buttons : a -> { events : Signal a,
|
||||
button : a -> String -> Element }
|
||||
buttons = Native.Graphics.Input.buttons
|
||||
input : a -> (Signal a, SignalID a)
|
||||
input = Native.Graphics.Input.input
|
||||
|
||||
{-| Create a button with a given label. The result is an `Element` and
|
||||
a signal of units. This signal triggers whenever the button is pressed.
|
||||
{-| Create a standard button. The following example begins making a basic
|
||||
calculator:
|
||||
|
||||
data Input = Number Int | Plus | Minus | Clear
|
||||
|
||||
-- (Signal Input, SignalID Input)
|
||||
(calcInput, pressID) = input Clear
|
||||
|
||||
buttons : Element
|
||||
buttons =
|
||||
flow right [ button pressID (Number 1) "1"
|
||||
, button pressID (Number 2) "2"
|
||||
, button pressID Plus "+"
|
||||
]
|
||||
|
||||
If the user presses the "plus" button, the `calcInput` signal will update to
|
||||
`Plus`. If the users presses "2", `calcInput` will update to `(Number 2)`.
|
||||
-}
|
||||
button : String -> (Element, Signal ())
|
||||
button txt =
|
||||
let pool = buttons ()
|
||||
in (pool.button () txt, pool.events)
|
||||
button : SignalID a -> a -> String -> Element
|
||||
button = Native.Graphics.Input.button
|
||||
|
||||
{-| Create a group of custom buttons.
|
||||
customButton : SignalID a -> a -> Element -> Element -> Element -> Element
|
||||
customButton = Native.Graphics.Input.customButton
|
||||
|
||||
* The first argument is the default value of the `events` signal.
|
||||
* The `events` signal represents all of the activity in this group
|
||||
of custom buttons.
|
||||
* The `customButton` function creates a button with three different visual
|
||||
states, one for up, hovering, and down. The resulting button has dimensions
|
||||
large enough to fit all three possible `Elements`.
|
||||
The `a` value is sent to `events` whenever the button is pressed.
|
||||
checkbox : SignalID a -> (Bool -> a) -> Bool -> Element
|
||||
checkbox = Native.Graphics.Input.checkboxes
|
||||
|
||||
{-| Create a drop-down menu. The following drop-down lets you choose your
|
||||
favorite British sport:
|
||||
|
||||
data Sport = Football | Cricket | Snooker
|
||||
|
||||
-- (Signal (Maybe Sport), SignalID (Maybe Sport))
|
||||
(dropStatus, dropID) = input Nothing
|
||||
|
||||
sportDropDown : Element
|
||||
sportDropDown =
|
||||
dropDown dropID
|
||||
[ ("" , Nothing)
|
||||
, ("Football", Just Football)
|
||||
, ("Cricket" , Just Cricket)
|
||||
, ("Snooker" , Just Snooker)
|
||||
]
|
||||
|
||||
If the user selects "Football" from the drop down menue, the `dropStatus`
|
||||
signal will update to `Just Football`.
|
||||
-}
|
||||
customButtons : a -> { events : Signal a,
|
||||
customButton : a -> Element -> Element -> Element -> Element }
|
||||
customButtons = Native.Graphics.Input.customButtons
|
||||
dropDown : SignalID a -> [(String,a)] -> Element
|
||||
dropDown = Native.Graphics.Input.dropDown
|
||||
|
||||
{-| Create a button with custom states for up, hovering, and down
|
||||
(given in that order). The result is an `Element` and a signal of
|
||||
units. This signal triggers whenever the button is pressed.
|
||||
{-| Detect mouse hovers over a specific `Element`. In the following example,
|
||||
we will create a hoverable picture called `cat`.
|
||||
|
||||
-- (Signal Bool, SignalID Bool)
|
||||
(hover, hoverID) = input False
|
||||
|
||||
cat : Element
|
||||
cat = image 30 30 "/cat.jpg"
|
||||
|> hoverable hoverID (\hover -> hover)
|
||||
|
||||
When the mouse hovers above the `cat` element, the `hover` signal will become
|
||||
`True`. When the mouse leaves it, `hover` will become `False`.
|
||||
-}
|
||||
customButton : Element -> Element -> Element -> (Element, Signal ())
|
||||
customButton up hover down =
|
||||
let pool = customButtons ()
|
||||
in (pool.customButton () up hover down, pool.events)
|
||||
hoverable : SignalID a -> (Bool -> a) -> Element -> Element
|
||||
hoverable = Native.Graphics.Input.hoverable
|
||||
|
||||
{-| Create a group of checkboxes.
|
||||
{-| Detect mouse clicks on a specific `Element`. In the following example,
|
||||
we will create a clickable picture called `cat`.
|
||||
|
||||
* The first argument is the default value of the `events` signal.
|
||||
* The `events` signal represents all of the activity in this group
|
||||
of checkboxes.
|
||||
* The `checkbox` function creates a
|
||||
checkbox with a given state. The `(Bool -> a)` function is used
|
||||
when the checkbox is modified. It takes the new state and turns
|
||||
it into a value that can be sent to `events`. For example, this
|
||||
lets you add an ID to distinguish between checkboxes.
|
||||
data Picture = Cat | Hat
|
||||
|
||||
-- (Signal Picture, SignalID Picture)
|
||||
(click, clickID) = input Cat
|
||||
|
||||
cat : Element
|
||||
cat = image 30 30 "/cat.jpg"
|
||||
|> clickable clickID Cat
|
||||
|
||||
hat : Element
|
||||
hat = image 30 30 "/hat.jpg"
|
||||
|> clickable clickID Hat
|
||||
|
||||
When the user clicks on the `cat` element, the `click` signal receives
|
||||
an update containing the value `Cat`. When the user clicks on the `hat` element,
|
||||
the `click` signal receives an update containing the value `Hat`. This lets you
|
||||
distinguish which element was clicked. In a more complex example, they could be
|
||||
distinguished with IDs or more complex data structures.
|
||||
-}
|
||||
checkboxes : a -> { events : Signal a,
|
||||
checkbox : (Bool -> a) -> Bool -> Element }
|
||||
checkboxes = Native.Graphics.Input.checkboxes
|
||||
clickable : SignalID a -> a -> Element -> Element
|
||||
clickable = Native.Graphics.Input.clickable
|
||||
|
||||
{-| Create a checkbox with a given start state. Unlike `button`, this
|
||||
result is a *signal* of elements. That is because a checkbox has state
|
||||
that updates based on user input. The boolean signal represents the
|
||||
current state of the checkbox.
|
||||
-}
|
||||
checkbox : Bool -> (Signal Element, Signal Bool)
|
||||
checkbox b =
|
||||
let cbs = checkboxes b
|
||||
in (lift (cbs.checkbox id) cbs.events, cbs.events)
|
||||
{-| Represents the current content of a text field. For example:
|
||||
|
||||
{-| Detect when the mouse is hovering over some elements. This
|
||||
allows you to create and destroy elements dynamically and still
|
||||
detect hover information.
|
||||
-}
|
||||
hoverables : a -> { events : Signal a,
|
||||
hoverable : (Bool -> a) -> Element -> Element }
|
||||
hoverables = Native.Graphics.Input.hoverables
|
||||
|
||||
{-| Detect when the mouse is hovering over a specific `Element`. -}
|
||||
hoverable : Element -> (Element, Signal Bool)
|
||||
hoverable elem =
|
||||
let pool = hoverables False
|
||||
in (pool.hoverable id elem, pool.events)
|
||||
|
||||
{-| Represents the current state of a text field. The `string` represents the
|
||||
characters filling the text field. The `selectionStart` and `selectionEnd`
|
||||
values represent what the user has selected with their mouse or keyboard.
|
||||
For example:
|
||||
|
||||
{ string="She sells sea shells", selectionStart=3, selectionEnd=0 }
|
||||
FieldContent "She sells sea shells" (Selection 0 3 Backward)
|
||||
|
||||
This means the user highlighted the substring `"She"` backwards.
|
||||
-}
|
||||
type FieldState = { string:String, selectionStart:Int, selectionEnd:Int }
|
||||
type FieldContent = { string:String, selection:Selection }
|
||||
|
||||
{-| Create a group of text input fields.
|
||||
{-| The selection within a text field. `start` is never greater than `end`:
|
||||
|
||||
* The first argument is the default value of the `events` signal.
|
||||
* The `events` signal represents all of the activity in this group
|
||||
of text fields.
|
||||
* The `field` function creates a
|
||||
field with the given ghost text and initial field state.
|
||||
When the field is modified, the `(FieldState -> a)` function
|
||||
takes the new state and turns
|
||||
it into a value that can be sent to `events`. For example, this
|
||||
lets you add an ID to distinguish between input fields.
|
||||
Selection 0 0 Forward -- cursor precedes all characters
|
||||
|
||||
Selection 5 9 Backward -- highlighting characters starting after
|
||||
-- the 5th and ending after the 9th
|
||||
-}
|
||||
fields : a -> { events : Signal a,
|
||||
field : (FieldState -> a) -> String -> FieldState -> Element }
|
||||
fields = Native.Graphics.Input.fields
|
||||
type Selection = { start:Int, end:Int, direction:Direction }
|
||||
|
||||
{-| The empty field state:
|
||||
{-| The direction of selection.-}
|
||||
data Direction = Forward | Backward
|
||||
|
||||
{ string="", selectionStart=0, selectionEnd=0 }
|
||||
{-| A field with no content:
|
||||
|
||||
FieldContent "" (Selection 0 0 Forward)
|
||||
-}
|
||||
emptyFieldState : FieldState
|
||||
emptyFieldState = { string="", selectionStart=0, selectionEnd=0 }
|
||||
noContent : FieldContent
|
||||
noContent = FieldContent "" (Selection 0 0 Forward)
|
||||
|
||||
{-| Create a field with the given default text. The output is an element
|
||||
that updates to match the user input and a signal of strings representing
|
||||
the content of the field.
|
||||
{-| Create a text field. The following example creates a time-varying element
|
||||
called `nameField`. As the user types their name, the field will be updated
|
||||
to match what they have entered.
|
||||
|
||||
-- (Signal FieldContent, SignalID FieldContent)
|
||||
(nameContent,nameID) = input noContent
|
||||
|
||||
nameField : Signal Element
|
||||
nameField = field nameID (\content -> content) "Name" <~ nameContent
|
||||
-}
|
||||
field : String -> (Signal Element, Signal String)
|
||||
field placeHolder =
|
||||
let tfs = fields emptyFieldState
|
||||
changes = dropRepeats tfs.events
|
||||
in (lift (tfs.field id placeHolder) changes,
|
||||
dropRepeats (lift .string changes))
|
||||
field : SignalID a -> (FieldContent -> a) -> String -> FieldContent -> Element
|
||||
field = Native.Graphics.Input.field
|
||||
|
||||
{-| Same as `field` but the UI element blocks out each characters. -}
|
||||
password : String -> (Signal Element, Signal String)
|
||||
password placeHolder =
|
||||
let tfs = Native.Graphics.Input.passwords emptyFieldState
|
||||
changes = dropRepeats tfs.events
|
||||
in (lift (tfs.field id placeHolder) changes,
|
||||
dropRepeats (lift .string changes))
|
||||
password : SignalID a -> (FieldContent -> a) -> String -> FieldContent -> Element
|
||||
password = Native.Graphics.Input.password
|
||||
|
||||
{-| Same as `field` but it adds an annotation that this field is for email
|
||||
addresses. This is helpful for auto-complete and for mobile users who may
|
||||
get a custom keyboard with an `@` and `.com` button.
|
||||
-}
|
||||
email : String -> (Signal Element, Signal String)
|
||||
email placeHolder =
|
||||
let tfs = Native.Graphics.Input.emails emptyFieldState
|
||||
changes = dropRepeats tfs.events
|
||||
in (lift (tfs.field id placeHolder) changes,
|
||||
dropRepeats (lift .string changes))
|
||||
email : SignalID a -> (FieldContent -> a) -> String -> FieldContent -> Element
|
||||
email = Native.Graphics.Input.email
|
||||
|
||||
{-| Create a drop-down menu. When the user selects a string,
|
||||
the current state of the drop-down is set to the associated
|
||||
value. This lets you avoid manually mapping the string onto
|
||||
functions and values.
|
||||
-}
|
||||
dropDown : [(String,a)] -> (Signal Element, Signal a)
|
||||
dropDown = Native.Graphics.Input.dropDown
|
||||
|
||||
{-| Create a drop-down menu for selecting strings. The resulting
|
||||
signal of strings represents the string that is currently selected.
|
||||
-}
|
||||
stringDropDown : [String] -> (Signal Element, Signal String)
|
||||
stringDropDown strs =
|
||||
dropDown (List.map (\s -> (s,s)) strs)
|
||||
-- area : SignalID a -> (FieldContent -> a) -> SignalID b -> ((Int,Int) -> b) -> (Int,Int) -> String -> FieldContent -> Element
|
||||
-- area = Native.Graphics.Input.area
|
||||
|
|
|
@ -15,9 +15,14 @@ Elm.Native.Graphics.Input.make = function(elm) {
|
|||
var Utils = Elm.Native.Utils.make(elm);
|
||||
var Tuple2 = Utils.Tuple2;
|
||||
|
||||
function dropDown(values) {
|
||||
function input(initialValue) {
|
||||
var sig = Signal.constant(initialValue);
|
||||
return Tuple2(sig, sig);
|
||||
}
|
||||
|
||||
function renderDropDown(signal, values) {
|
||||
return function(_) {
|
||||
var entries = JS.fromList(values);
|
||||
var events = Signal.constant(entries[0]._1);
|
||||
|
||||
var drop = newNode('select');
|
||||
drop.style.border = '0 solid';
|
||||
|
@ -29,7 +34,7 @@ Elm.Native.Graphics.Input.make = function(elm) {
|
|||
drop.appendChild(option);
|
||||
}
|
||||
drop.addEventListener('change', function() {
|
||||
elm.notify(events.id, entries[drop.selectedIndex]._1);
|
||||
elm.notify(signal.id, entries[drop.selectedIndex]._1);
|
||||
});
|
||||
|
||||
var t = drop.cloneNode(true);
|
||||
|
@ -40,89 +45,93 @@ Elm.Native.Graphics.Input.make = function(elm) {
|
|||
var w = Math.ceil(style.getPropertyValue("width").slice(0,-2) - 0);
|
||||
var h = Math.ceil(style.getPropertyValue("height").slice(0,-2) - 0);
|
||||
elm.node.removeChild(t);
|
||||
return drop;
|
||||
};
|
||||
}
|
||||
|
||||
var element = A3(newElement, w, h, {
|
||||
function updateDropDown(node, oldModel, newModel) {
|
||||
}
|
||||
|
||||
function dropDown(signal, values) {
|
||||
return A3(newElement, w, h, {
|
||||
ctor: 'Custom',
|
||||
type: 'DropDown',
|
||||
render: function render(model) { return drop; },
|
||||
update: function update(node, oldModel, newModel) {},
|
||||
render: renderDropDown(signal,values),
|
||||
update: updateDropDown,
|
||||
model: {}
|
||||
});
|
||||
|
||||
return Tuple2(Signal.constant(element), events);
|
||||
}
|
||||
|
||||
function buttons(defaultValue) {
|
||||
var events = Signal.constant(defaultValue);
|
||||
|
||||
function render(model) {
|
||||
var b = newNode('button');
|
||||
b.style.display = 'block';
|
||||
b.elmEvent = model.event;
|
||||
function click() { elm.notify(events.id, b.elmEvent); }
|
||||
b.addEventListener('click', click);
|
||||
b.innerHTML = model.text;
|
||||
return b;
|
||||
function renderButton(model) {
|
||||
var node = newNode('button');
|
||||
node.style.display = 'block';
|
||||
node.elm_signal = model.signal;
|
||||
node.elm_value = model.value;
|
||||
function click() {
|
||||
elm.notify(node.elm_signal.id, node.elm_value);
|
||||
}
|
||||
node.addEventListener('click', click);
|
||||
node.innerHTML = model.text;
|
||||
return node;
|
||||
}
|
||||
|
||||
function update(node, oldModel, newModel) {
|
||||
node.elmEvent = newModel.event;
|
||||
function updateButton(node, oldModel, newModel) {
|
||||
node.elm_signal = newModel.signal;
|
||||
node.elm_value = nemModel.value;
|
||||
var txt = newModel.text;
|
||||
if (oldModel.text !== txt) node.innerHTML = txt;
|
||||
}
|
||||
|
||||
function button(evnt, txt) {
|
||||
function button(signal, value, text) {
|
||||
return A3(newElement, 100, 40, {
|
||||
ctor: 'Custom',
|
||||
type: 'Button',
|
||||
render: render,
|
||||
update: update,
|
||||
model: { event:evnt, text:JS.fromString(txt) }
|
||||
render: renderButton,
|
||||
update: updateButton,
|
||||
model: { signal:signal, value:value, text:JS.fromString(text) }
|
||||
});
|
||||
}
|
||||
|
||||
return { _:{}, button:F2(button), events:events };
|
||||
}
|
||||
|
||||
function customButtons(defaultValue) {
|
||||
var events = Signal.constant(defaultValue);
|
||||
|
||||
function render(model) {
|
||||
function renderCustomButton(model) {
|
||||
var btn = newNode('div');
|
||||
btn.elmEvent = model.event;
|
||||
btn.elm_value = model.value;
|
||||
|
||||
btn.elmUp = Render.render(model.up);
|
||||
btn.elmHover = Render.render(model.hover);
|
||||
btn.elmDown = Render.render(model.down);
|
||||
btn.elm_up = Render.render(model.up);
|
||||
btn.elm_hover = Render.render(model.hover);
|
||||
btn.elm_down = Render.render(model.down);
|
||||
|
||||
function replace(node) {
|
||||
if (node !== btn.firstChild) btn.replaceChild(node, btn.firstChild);
|
||||
if (node !== btn.firstChild) {
|
||||
btn.replaceChild(node, btn.firstChild);
|
||||
}
|
||||
}
|
||||
var overCount = 0;
|
||||
function over(e) {
|
||||
if (overCount++ > 0) return;
|
||||
replace(btn.elmHover);
|
||||
replace(btn.elm_hover);
|
||||
}
|
||||
function out(e) {
|
||||
if (btn.contains(e.toElement || e.relatedTarget)) return;
|
||||
overCount = 0;
|
||||
replace(btn.elmUp);
|
||||
replace(btn.elm_up);
|
||||
}
|
||||
function up() {
|
||||
replace(btn.elmHover);
|
||||
elm.notify(events.id, btn.elmEvent);
|
||||
replace(btn.elm_hover);
|
||||
elm.notify(btn.elm_signal.id, btn.elm_value);
|
||||
}
|
||||
function down() {
|
||||
replace(btn.elm_down);
|
||||
}
|
||||
function down() { replace(btn.elmDown); }
|
||||
btn.addEventListener('mouseover', over);
|
||||
btn.addEventListener('mouseout' , out);
|
||||
btn.addEventListener('mousedown', down);
|
||||
btn.addEventListener('mouseup' , up);
|
||||
|
||||
btn.appendChild(btn.elmUp);
|
||||
btn.appendChild(btn.elm_up);
|
||||
|
||||
var clicker = newNode('div');
|
||||
clicker.style.width = btn.elmUp.style.width;
|
||||
clicker.style.height = btn.elmUp.style.height;
|
||||
clicker.style.width = btn.elm_up.style.width;
|
||||
clicker.style.height = btn.elm_up.style.height;
|
||||
clicker.style.position = 'absolute';
|
||||
clicker.style.top = 0;
|
||||
btn.appendChild(clicker);
|
||||
|
@ -130,75 +139,62 @@ Elm.Native.Graphics.Input.make = function(elm) {
|
|||
return btn;
|
||||
}
|
||||
|
||||
function update(node, oldModel, newModel) {
|
||||
node.elmEvent = newModel.event;
|
||||
Render.update(node.elmUp, oldModel.up, newModel.up)
|
||||
Render.update(node.elmHover, oldModel.hover, newModel.hover)
|
||||
Render.update(node.elmDown, oldModel.down, newModel.down)
|
||||
function updateCustomButton(node, oldModel, newModel) {
|
||||
node.elm_signal = newModel.signal;
|
||||
node.elm_value = newModel.value;
|
||||
Render.update(node.elm_up, oldModel.up, newModel.up)
|
||||
Render.update(node.elm_hover, oldModel.hover, newModel.hover)
|
||||
Render.update(node.elm_down, oldModel.down, newModel.down)
|
||||
}
|
||||
|
||||
function button(evnt, up, hover, down) {
|
||||
function max3(a,b,c) {
|
||||
var ab = a > b ? a : b;
|
||||
return ab > c ? ab : c;
|
||||
}
|
||||
|
||||
function customButton(signal, value, up, hover, down) {
|
||||
return A3(newElement,
|
||||
Math.max(up.props.width, hover.props.width, down.props.width),
|
||||
Math.max(up.props.height, hover.props.height, down.props.height),
|
||||
max3(up.props.width, hover.props.width, down.props.width),
|
||||
max3(up.props.height, hover.props.height, down.props.height),
|
||||
{ ctor: 'Custom',
|
||||
type: 'CustomButton',
|
||||
render: render,
|
||||
update: update,
|
||||
model: { event:evnt, up:up, hover:hover, down:down }
|
||||
render: renderCustomButton,
|
||||
update: updateCustomButton,
|
||||
model: { signal:signal, value:value, up:up, hover:hover, down:down }
|
||||
});
|
||||
}
|
||||
|
||||
return { _:{}, customButton:F4(button), events:events };
|
||||
function renderCheckbox(model) {
|
||||
var node = newNode('input');
|
||||
node.type = 'checkbox';
|
||||
node.checked = model.checked;
|
||||
node.style.display = 'block';
|
||||
node.elm_signal = model.signal;
|
||||
node.elm_handler = model.handler;
|
||||
function change() {
|
||||
elm.notify(node.elm_signal.id, node.elm_handler(node.checked));
|
||||
}
|
||||
node.addEventListener('change', change);
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
function hoverables(defaultValue) {
|
||||
var events = Signal.constant(defaultValue);
|
||||
function hoverable(handler, elem) {
|
||||
function onHover(bool) {
|
||||
elm.notify(events.id, handler(bool));
|
||||
}
|
||||
var props = Utils.replace([['hover',onHover]], elem.props);
|
||||
return { props:props, element:elem.element };
|
||||
}
|
||||
return { _:{}, hoverable:F2(hoverable), events:events };
|
||||
}
|
||||
|
||||
|
||||
function checkboxes(defaultValue) {
|
||||
var events = Signal.constant(defaultValue);
|
||||
|
||||
function render(model) {
|
||||
var b = newNode('input');
|
||||
b.type = 'checkbox';
|
||||
b.checked = model.checked;
|
||||
b.style.display = 'block';
|
||||
b.elmHandler = model.handler;
|
||||
function change() { elm.notify(events.id, b.elmHandler(b.checked)); }
|
||||
b.addEventListener('change', change);
|
||||
return b;
|
||||
}
|
||||
|
||||
function update(node, oldModel, newModel) {
|
||||
node.elmHandler = newModel.handler;
|
||||
function updateCheckbox(node, oldModel, newModel) {
|
||||
node.elm_signal = newModel.signal;
|
||||
node.elm_handler = newModel.handler;
|
||||
node.checked = newModel.checked;
|
||||
return true;
|
||||
}
|
||||
|
||||
function box(handler, checked) {
|
||||
function checkbox(signal, handler, checked) {
|
||||
return A3(newElement, 13, 13, {
|
||||
ctor: 'Custom',
|
||||
type: 'CheckBox',
|
||||
render: render,
|
||||
update: update,
|
||||
model: { checked:checked, handler:handler }
|
||||
render: renderCheckbox,
|
||||
update: updateCheckbox,
|
||||
model: { signal:signal, handler:handler, checked:checked }
|
||||
});
|
||||
}
|
||||
|
||||
return { _:{}, checkbox:F2(box), events:events };
|
||||
}
|
||||
|
||||
function setRange(node, start, end, dir) {
|
||||
if (node.parentNode) {
|
||||
node.setSelectionRange(start, end, dir);
|
||||
|
@ -207,14 +203,10 @@ Elm.Native.Graphics.Input.make = function(elm) {
|
|||
}
|
||||
}
|
||||
|
||||
function mkTextPool(type) { return function fields(defaultValue) {
|
||||
var events = Signal.constant(defaultValue);
|
||||
|
||||
var state = null;
|
||||
|
||||
function render(model) {
|
||||
function renderField(model) {
|
||||
var field = newNode('input');
|
||||
field.elmHandler = model.handler;
|
||||
field.elm_signal = model.signal;
|
||||
field.elm_handler = model.handler;
|
||||
|
||||
field.id = 'test';
|
||||
field.type = type;
|
||||
|
@ -225,17 +217,18 @@ Elm.Native.Graphics.Input.make = function(elm) {
|
|||
state = model.state;
|
||||
|
||||
function update() {
|
||||
var start = field.selectionStart,
|
||||
end = field.selectionEnd;
|
||||
var start = field.selectionStart;
|
||||
var end = field.selectionEnd;
|
||||
if (field.selectionDirection === 'backward') {
|
||||
start = end;
|
||||
end = field.selectionStart;
|
||||
}
|
||||
state = { _:{},
|
||||
elm.notify(events.id, field.elm_handler({
|
||||
_:{},
|
||||
string:JS.toString(field.value),
|
||||
selectionStart:start,
|
||||
selectionEnd:end };
|
||||
elm.notify(events.id, field.elmHandler(state));
|
||||
selectionEnd:end
|
||||
}));
|
||||
}
|
||||
function mousedown() {
|
||||
update();
|
||||
|
@ -251,9 +244,8 @@ Elm.Native.Graphics.Input.make = function(elm) {
|
|||
return field;
|
||||
}
|
||||
|
||||
function update(node, oldModel, newModel) {
|
||||
function updateField(node, oldModel, newModel) {
|
||||
node.elmHandler = newModel.handler;
|
||||
if (state === newModel.state) return;
|
||||
var newStr = JS.fromString(newModel.state.string);
|
||||
if (node.value !== newStr) node.value = newStr;
|
||||
|
||||
|
@ -273,31 +265,52 @@ Elm.Native.Graphics.Input.make = function(elm) {
|
|||
}
|
||||
}
|
||||
|
||||
function field(handler, placeHolder, state) {
|
||||
return A3(newElement, 200, 30,
|
||||
{ ctor: 'Custom',
|
||||
function mkField(type) {
|
||||
function field(signal, handler, placeHolder, state) {
|
||||
return A3(newElement, 200, 30, {
|
||||
ctor: 'Custom',
|
||||
type: type + 'Input',
|
||||
render: render,
|
||||
update: update,
|
||||
model: { handler:handler,
|
||||
model: {
|
||||
signal:signal,
|
||||
handler:handler,
|
||||
placeHolder:placeHolder,
|
||||
state:state }
|
||||
state:state
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { _:{}, field:F3(field), events:events };
|
||||
return F4(field);
|
||||
}
|
||||
|
||||
|
||||
function hoverable(signal, handler, element) {
|
||||
function onHover(bool) {
|
||||
elm.notify(signal.id, handler(bool));
|
||||
}
|
||||
var props = Utils.replace([['hover',onHover]], elem.props);
|
||||
return { props:props, element:elem.element };
|
||||
}
|
||||
|
||||
function clickable(signal, value, element) {
|
||||
function onClick(bool) {
|
||||
elm.notify(signal.id, value);
|
||||
}
|
||||
var props = Utils.replace([['click',onClick]], elem.props);
|
||||
return { props:props, element:elem.element };
|
||||
}
|
||||
|
||||
return elm.Native.Graphics.Input.values = {
|
||||
buttons:buttons,
|
||||
customButtons:customButtons,
|
||||
hoverables:hoverables,
|
||||
checkboxes:checkboxes,
|
||||
fields:mkTextPool('text'),
|
||||
emails:mkTextPool('email'),
|
||||
passwords:mkTextPool('password'),
|
||||
dropDown:dropDown
|
||||
input:input,
|
||||
button:F3(button),
|
||||
customButton:F5(customButton),
|
||||
checkbox:F3(checkbox),
|
||||
dropDown:F2(dropDown),
|
||||
field:mkField('text'),
|
||||
email:mkField('email'),
|
||||
password:mkField('password'),
|
||||
hoverable:F3(hoverable),
|
||||
clickable:F3(clickable)
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -28,15 +28,28 @@ function setProps(props, e) {
|
|||
e.appendChild(a);
|
||||
}
|
||||
if (props.hover.ctor !== '_Tuple0') {
|
||||
var overCount = 0;
|
||||
e.elm_hover_handler = props.hover;
|
||||
e.elm_hover_count = 0;
|
||||
e.addEventListener('mouseover', function() {
|
||||
if (overCount++ > 0) return;
|
||||
props.hover(true);
|
||||
if (e.elm_hover_count++ > 0) return;
|
||||
var handler = e.elm_hover_handler;
|
||||
if (handler !== null) {
|
||||
handler(true);
|
||||
}
|
||||
});
|
||||
e.addEventListener('mouseout', function(evt) {
|
||||
if (e.contains(evt.toElement || evt.relatedTarget)) return;
|
||||
overCount = 0;
|
||||
props.hover(false);
|
||||
e.elm_hover_count = 0;
|
||||
var handler = e.elm_hover_handler;
|
||||
if (handler !== null) {
|
||||
handler(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (props.click.ctor !== '_Tuple0') {
|
||||
e.elm_click_handler = props.click;
|
||||
e.addEventListener('click', function() {
|
||||
e.elm_click_handler(Tuple0);
|
||||
});
|
||||
}
|
||||
return e;
|
||||
|
@ -319,6 +332,20 @@ function updateProps(node, curr, next) {
|
|||
node.lastNode.href = props.href;
|
||||
}
|
||||
}
|
||||
|
||||
// update hover handlers
|
||||
if (props.hover.ctor !== '_Tuple0') {
|
||||
e.elm_hover_handler = props.hover;
|
||||
} else if (e.elm_hover_handler) {
|
||||
e.elm_hover_handler = null;
|
||||
}
|
||||
|
||||
// update click handlers
|
||||
if (props.click.ctor !== '_Tuple0') {
|
||||
e.elm_click_handler = props.click;
|
||||
} else if (e.elm_click_handler) {
|
||||
e.elm_click_handler = null;
|
||||
}
|
||||
}
|
||||
|
||||
return { render:render, update:update };
|
||||
|
|
Loading…
Reference in a new issue