Move all field related stuff to Graphics.Input.Field and add the ability to style fields
This commit is contained in:
parent
10506b5663
commit
093d7afb34
4 changed files with 213 additions and 67 deletions
|
@ -19,14 +19,15 @@ examples in this library, so just read on to get a better idea of how it works!
|
|||
@docs Input, input
|
||||
|
||||
# Basic Input Elements
|
||||
Text fields come later.
|
||||
|
||||
To learn about text fields, see the
|
||||
[`Graphics.Input.Field`](Graphics-Input-Field) library.
|
||||
|
||||
@docs button, customButton, checkbox, dropDown
|
||||
|
||||
# Clicks and Hovers
|
||||
@docs clickable, hoverable
|
||||
|
||||
# Text Fields
|
||||
@docs field, password, email, noContent, FieldContent, Selection, Direction
|
||||
-}
|
||||
|
||||
import Signal (Signal)
|
||||
|
@ -175,57 +176,3 @@ distinguished with IDs or more complex data structures.
|
|||
-}
|
||||
clickable : Handle a -> a -> Element -> Element
|
||||
clickable = Native.Graphics.Input.clickable
|
||||
|
||||
{-| Represents the current content of a text field. For example:
|
||||
|
||||
FieldContent "She sells sea shells" (Selection 0 3 Backward)
|
||||
|
||||
This means the user highlighted the substring `"She"` backwards.
|
||||
-}
|
||||
type FieldContent = { string:String, selection:Selection }
|
||||
|
||||
{-| The selection within a text field. `start` is never greater than `end`:
|
||||
|
||||
Selection 0 0 Forward -- cursor precedes all characters
|
||||
|
||||
Selection 5 9 Backward -- highlighting characters starting after
|
||||
-- the 5th and ending after the 9th
|
||||
-}
|
||||
type Selection = { start:Int, end:Int, direction:Direction }
|
||||
|
||||
{-| The direction of selection.-}
|
||||
data Direction = Forward | Backward
|
||||
|
||||
{-| A field with no content:
|
||||
|
||||
FieldContent "" (Selection 0 0 Forward)
|
||||
-}
|
||||
noContent : FieldContent
|
||||
noContent = FieldContent "" (Selection 0 0 Forward)
|
||||
|
||||
{-| 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.
|
||||
|
||||
name : Input FieldContent
|
||||
name = input noContent
|
||||
|
||||
nameField : Signal Element
|
||||
nameField = field name.handle id "Name" <~ name.signal
|
||||
-}
|
||||
field : Handle a -> (FieldContent -> a) -> String -> FieldContent -> Element
|
||||
field = Native.Graphics.Input.field
|
||||
|
||||
{-| Same as `field` but the UI element blocks out each characters. -}
|
||||
password : Handle 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 : Handle a -> (FieldContent -> a) -> String -> FieldContent -> Element
|
||||
email = Native.Graphics.Input.email
|
||||
|
||||
-- area : Handle a -> (FieldContent -> a) -> Handle b -> ((Int,Int) -> b) -> (Int,Int) -> String -> FieldContent -> Element
|
||||
-- area = Native.Graphics.Input.area
|
||||
|
|
130
libraries/Graphics/Input/Field.elm
Normal file
130
libraries/Graphics/Input/Field.elm
Normal file
|
@ -0,0 +1,130 @@
|
|||
module Graphics.Input.Field where
|
||||
{-| This library specifically addresses text fields. It uses the same genral
|
||||
approach as the [`Graphics.Input`](Graphics-Input) module for describing an
|
||||
`Input`, so this library focuses on creating and styling text fields.
|
||||
|
||||
# Create Fields
|
||||
@docs field, password, email
|
||||
|
||||
# Field Content
|
||||
@docs Content, Selection, Direction, noContent
|
||||
|
||||
# Field Style
|
||||
@docs Style, Outline, noOutline, Highlight, noHighlight, Dimensions, uniformly
|
||||
-}
|
||||
|
||||
import Color (Color)
|
||||
import Color
|
||||
import Graphics.Element (Element)
|
||||
import Graphics.Input (Input, Handle)
|
||||
import Native.Graphics.Input
|
||||
import Text
|
||||
|
||||
{-| Easily create uniform dimensions:
|
||||
|
||||
uniformly 4 == { left=4, right=4, top=4, bottom=4 }
|
||||
-}
|
||||
uniformly : Int -> Dimensions
|
||||
uniformly n = Dimensions n n n n
|
||||
|
||||
{-| For setting dimensions of a fields padding or border. The left, right, top,
|
||||
and bottom may all have different sizes.
|
||||
-}
|
||||
type Dimensions = { left:Int, right:Int, top:Int, bottom:Int }
|
||||
|
||||
{-| A field can have a outline around it. This lets you set its color, width,
|
||||
and radius. The radius allows you to round the corners of your field. Set the
|
||||
width to zero to make it invisible.
|
||||
-}
|
||||
type Outline = { color:Color, width:Dimensions, radius:Int }
|
||||
|
||||
{-| An outline with zero width, so you cannot see it. -}
|
||||
noOutline : Outline
|
||||
noOutline = Outline Color.grey (uniformly 0) 0
|
||||
|
||||
{-| When a field is selected, it has an highlight around it by default. Set the
|
||||
width of the `Highlight` to zero to make it go away.
|
||||
-}
|
||||
type Highlight = { color:Color, width:Int }
|
||||
|
||||
{-| An highlight with zero width, so you cannot see it. -}
|
||||
noHighlight : Highlight
|
||||
noHighlight = Highlight Color.blue 0
|
||||
|
||||
{-| Describes the style of a text box. The `style` field describes the style
|
||||
of the text itself. The `outline` field describes the glowing blue outline that
|
||||
shows up when the field has focus. Turn off `outline` by setting its width to
|
||||
zero. The
|
||||
-}
|
||||
type Style =
|
||||
{ padding : Dimensions
|
||||
, outline : Outline
|
||||
, highlight : Highlight
|
||||
, style : Text.Style
|
||||
}
|
||||
|
||||
{-| The default style for a text field. The outline is `Color.grey` with width
|
||||
1 and radius 2. The highlight is `Color.blue` with width 1, and the default
|
||||
text color is black.
|
||||
-}
|
||||
defaultStyle : Style
|
||||
defaultStyle =
|
||||
{ padding = uniformly 4
|
||||
, outline = Outline Color.grey (uniformly 1) 2
|
||||
, highlight = Highlight Color.blue 1
|
||||
, style = Text.defaultStyle
|
||||
}
|
||||
|
||||
{-| Represents the current content of a text field. For example:
|
||||
|
||||
Content "She sells sea shells" (Selection 0 3 Backward)
|
||||
|
||||
This means the user highlighted the substring `"She"` backwards.
|
||||
-}
|
||||
type Content = { string:String, selection:Selection }
|
||||
|
||||
{-| The selection within a text field. `start` is never greater than `end`:
|
||||
|
||||
Selection 0 0 Forward -- cursor precedes all characters
|
||||
|
||||
Selection 5 9 Backward -- highlighting characters starting after
|
||||
-- the 5th and ending after the 9th
|
||||
-}
|
||||
type Selection = { start:Int, end:Int, direction:Direction }
|
||||
|
||||
{-| The direction of selection.-}
|
||||
data Direction = Forward | Backward
|
||||
|
||||
{-| A field with no content:
|
||||
|
||||
Content "" (Selection 0 0 Forward)
|
||||
-}
|
||||
noContent : Content
|
||||
noContent = Content "" (Selection 0 0 Forward)
|
||||
|
||||
{-| 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.
|
||||
|
||||
name : Input Content
|
||||
name = input noContent
|
||||
|
||||
nameField : Signal Element
|
||||
nameField = field name.handle id "Name" <~ name.signal
|
||||
-}
|
||||
field : Handle a -> (Content -> a) -> Style -> String -> Content -> Element
|
||||
field = Native.Graphics.Input.field
|
||||
|
||||
{-| Same as `field` but the UI element blocks out each characters. -}
|
||||
password : Handle a -> (Content -> a) -> Style -> String -> Content -> 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 : Handle a -> (Content -> a) -> Style -> String -> Content -> Element
|
||||
email = Native.Graphics.Input.email
|
||||
|
||||
-- area : Handle a -> (Content -> a) -> Handle b -> ((Int,Int) -> b) -> (Int,Int) -> String -> Content -> Element
|
||||
-- area = Native.Graphics.Input.area
|
|
@ -8,6 +8,8 @@ Elm.Native.Graphics.Input.make = function(elm) {
|
|||
var Render = ElmRuntime.use(ElmRuntime.Render.Element);
|
||||
var newNode = ElmRuntime.use(ElmRuntime.Render.Utils).newElement;
|
||||
|
||||
var toCss = Elm.Native.Color.make(elm).toCss;
|
||||
var Text = Elm.Native.Text.make(elm);
|
||||
var Signal = Elm.Signal.make(elm);
|
||||
var newElement = Elm.Graphics.Element.make(elm).newElement;
|
||||
var JS = Elm.Native.JavaScript.make(elm);
|
||||
|
@ -215,11 +217,52 @@ Elm.Native.Graphics.Input.make = function(elm) {
|
|||
}
|
||||
}
|
||||
|
||||
function updateIfNeeded(css, attribute, latestAttribute) {
|
||||
if (css[attribute] !== latestAttribute) {
|
||||
css[attribute] = latestAttribute;
|
||||
}
|
||||
}
|
||||
function cssDimensions(dimensions) {
|
||||
return dimensions.top + 'px ' +
|
||||
dimensions.right + 'px ' +
|
||||
dimensions.bottom + 'px ' +
|
||||
dimensions.left + 'px';
|
||||
}
|
||||
function updateFieldStyle(css, style) {
|
||||
updateIfNeeded(css, 'padding', cssDimensions(style.padding));
|
||||
|
||||
var outline = style.outline;
|
||||
updateIfNeeded(css, 'border-width', cssDimensions(outline.width));
|
||||
updateIfNeeded(css, 'border-color', toCss(outline.color));
|
||||
updateIfNeeded(css, 'border-radius', outline.radius + 'px');
|
||||
|
||||
var highlight = style.highlight;
|
||||
if (highlight.width === 0) {
|
||||
css.outline = 'none';
|
||||
} else {
|
||||
updateIfNeeded(css, 'outline-width', highlight.width + 'px');
|
||||
updateIfNeeded(css, 'outline-color', toCss(highlight.color));
|
||||
}
|
||||
|
||||
var textStyle = style.style;
|
||||
updateIfNeeded(css, 'color', toCss(textStyle.color));
|
||||
if (textStyle.typeface.ctor !== '[]') {
|
||||
updateIfNeeded(css, 'font-family', Text.toTypefaces(textStyle.typeface));
|
||||
}
|
||||
if (textStyle.height.ctor !== "Nothing") {
|
||||
updateIfNeeded(css, 'font-size', textStyle.height._0 + 'px');
|
||||
}
|
||||
updateIfNeeded(css, 'font-weight', textStyle.bold ? 'bold' : 'normal');
|
||||
updateIfNeeded(css, 'font-style', textStyle.italic ? 'italic' : 'normal');
|
||||
if (textStyle.line.ctor !== 'Nothing') {
|
||||
updateIfNeeded(css, 'text-decoration', Text.toLine(textStyle.line._0));
|
||||
}
|
||||
}
|
||||
|
||||
function renderField(model) {
|
||||
var field = newNode('input');
|
||||
field.style.border = 'none';
|
||||
field.style.outline = 'none';
|
||||
field.style.backgroundColor = 'transparent';
|
||||
updateFieldStyle(field.style, model.style);
|
||||
field.style.borderStyle = 'solid';
|
||||
field.style.pointerEvents = 'auto';
|
||||
|
||||
field.type = model.type;
|
||||
|
@ -302,6 +345,9 @@ Elm.Native.Graphics.Input.make = function(elm) {
|
|||
}
|
||||
|
||||
function updateField(field, oldModel, newModel) {
|
||||
if (oldModel.style !== newModel.style) {
|
||||
updateFieldStyle(field.style, newModel.style);
|
||||
}
|
||||
field.elm_signal = newModel.signal;
|
||||
field.elm_handler = newModel.handler;
|
||||
|
||||
|
@ -316,10 +362,16 @@ Elm.Native.Graphics.Input.make = function(elm) {
|
|||
}
|
||||
|
||||
function mkField(type) {
|
||||
function field(signal, handler, placeHolder, content) {
|
||||
function field(signal, handler, style, placeHolder, content) {
|
||||
var padding = style.padding;
|
||||
var outline = style.outline.width;
|
||||
var adjustWidth = padding.left + padding.right + outline.left + outline.right;
|
||||
var adjustHeight = padding.top + padding.bottom + outline.top + outline.bottom;
|
||||
return A3(newElement, 200, 30, {
|
||||
ctor: 'Custom',
|
||||
type: type + 'Input',
|
||||
type: type + 'Field',
|
||||
adjustWidth: adjustWidth,
|
||||
adjustHeight: adjustHeight,
|
||||
render: renderField,
|
||||
update: updateField,
|
||||
model: {
|
||||
|
@ -327,14 +379,14 @@ Elm.Native.Graphics.Input.make = function(elm) {
|
|||
handler:handler,
|
||||
placeHolder:placeHolder,
|
||||
content:content,
|
||||
style:style,
|
||||
type:type
|
||||
}
|
||||
});
|
||||
}
|
||||
return F4(field);
|
||||
return F5(field);
|
||||
}
|
||||
|
||||
|
||||
function hoverable(signal, handler, elem) {
|
||||
function onHover(bool) {
|
||||
elm.notify(signal.id, handler(bool));
|
||||
|
|
|
@ -7,7 +7,15 @@ var newElement = Utils.newElement, extract = Utils.extract,
|
|||
addTransform = Utils.addTransform, removeTransform = Utils.removeTransform,
|
||||
fromList = Utils.fromList, eq = Utils.eq;
|
||||
|
||||
function setProps(props, e) {
|
||||
function setProps(elem, e) {
|
||||
var props = elem.props;
|
||||
var element = elem.element;
|
||||
if (element.adjustWidth) {
|
||||
props.width -= element.adjustWidth;
|
||||
}
|
||||
if (element.adjustHeight) {
|
||||
props.height -= element.adjustHeight;
|
||||
}
|
||||
e.style.width = (props.width |0) + 'px';
|
||||
e.style.height = (props.height|0) + 'px';
|
||||
if (props.opacity !== 1) { e.style.opacity = props.opacity; }
|
||||
|
@ -200,7 +208,7 @@ function rawHtml(elem) {
|
|||
return div;
|
||||
}
|
||||
|
||||
function render(elem) { return setProps(elem.props, makeElement(elem)); }
|
||||
function render(elem) { return setProps(elem, makeElement(elem)); }
|
||||
function makeElement(e) {
|
||||
var elem = e.element;
|
||||
switch(elem.ctor) {
|
||||
|
@ -308,7 +316,16 @@ function update(node, curr, next) {
|
|||
}
|
||||
|
||||
function updateProps(node, curr, next) {
|
||||
var props = next.props, currP = curr.props, e = node;
|
||||
var props = next.props;
|
||||
var currP = curr.props;
|
||||
var e = node;
|
||||
var element = next.element;
|
||||
if (element.adjustWidth) {
|
||||
props.width -= element.adjustWidth;
|
||||
}
|
||||
if (element.adjustHeight) {
|
||||
props.height -= element.adjustHeight;
|
||||
}
|
||||
if (props.width !== currP.width) e.style.width = (props.width |0) + 'px';
|
||||
if (props.height !== currP.height) e.style.height = (props.height|0) + 'px';
|
||||
if (props.opacity !== 1 && props.opacity !== currP.opacity) {
|
||||
|
|
Loading…
Reference in a new issue