gampleman/evg/1.0.0

NOTE: the latest version is 1.0.1

Evg.Events

Handle mouse, touch, and pen interactions on shapes.

Event handlers receive coordinates in your coordinate space, not raw screen pixels. There are three spaces a position can live in, and the library converts between them for you:

  • local: the numbers you used to draw this element. If the shape lives inside Evg.group [ Transform.translate 50 0 ], local coordinates already have that translation undone. The simple handlers (like onClick) give you this, so a click at the visual centre of a shape reports the centre in the shape's own numbers no matter how it's been moved, scaled, or rotated.
  • scene: the root coordinate system of your Evg.svg, meaning the same numbers you'd use without any transforms. This is the space to do drag maths in, because it doesn't move when the thing you're dragging moves.
  • client: raw browser pixels, measured from the top-left corner of the browser window. An escape hatch, and the currency of window-level drag tracking.

When you use no transforms, local and scene are identical, so you can ignore the distinction until you need it.

import Evg
import Evg.Events as Events


Evg.circle
    [ Evg.fillStr "blue"
    , Events.onClick (\point -> ClickedAt point)
    ]
    { r = 30, center = ( 50, 50 ) }

Simple handlers

These report a single ( x, y ) position in the element's local coordinate space. They work for mouse, touch, and pen alike.

(( Float, Float ) -> msg)
-> Attribute { a | events : Supported } msg

Fires when the element is clicked. Reports the click position in the element's local coordinate space.

(( Float, Float ) -> msg)
-> Attribute { a | events : Supported } msg

Fires when a pointer (mouse button, finger, or pen) is pressed on the element. This is where a drag or gesture usually begins.

For touch and pen the browser keeps sending onPointerMove/onPointerUp to this element even if the pointer leaves it (browsers call this "pointer capture": the element that received the press keeps receiving the follow-up events). So touch and pen gestures work without any extra setup. Mouse drags need whileDragging to follow the cursor off the element. Pair this with touchAction None so the browser doesn't treat a touch-drag as a scroll.

(( Float, Float ) -> msg)
-> Attribute { a | events : Supported } msg

Fires when a pressed pointer is released over the element.

(( Float, Float ) -> msg)
-> Attribute { a | events : Supported } msg

Fires continuously as a pointer moves over the element. Use sparingly, since it generates many messages.

onPointerEnter : msg -> Attribute { a | events : Supported } msg

Fires once when a pointer enters the element.

onPointerLeave : msg -> Attribute { a | events : Supported } msg

Fires once when a pointer leaves the element.

onPointerCancel : msg -> Attribute { a | events : Supported } msg

Fires when the browser takes over an in-progress gesture, for example when a touch turns into a page scroll. Treat it like an interrupted onPointerUp: end whatever drag was in progress.

Detailed handlers

Sometimes a position isn't enough: you also want the input device, which button was pressed, which keyboard modifiers were held, or the Frame for window-level drag tracking. These handlers hand you a Pointer record with all of that.

onClickWith : (Pointer -> msg) -> Attribute { a | events : Supported } msg

Like onClick, but hands you the full Pointer record.

onPointerDownWith : (Pointer -> msg) -> Attribute { a | events : Supported } msg

Like onPointerDown, but hands you the full Pointer record. This is where you'd grab .frame and .pointerId to set up a drag.

onPointerUpWith : (Pointer -> msg) -> Attribute { a | events : Supported } msg

Like onPointerUp, but hands you the full Pointer record.

onPointerMoveWith : (Pointer -> msg) -> Attribute { a | events : Supported } msg

Like onPointerMove, but hands you the full Pointer record.

type alias Pointer =
{ local : ( Float, Float )
, scene : ( Float, Float )
, client : ( Float, Float )
, frame : Frame
, pointerType : PointerType
, pointerId : Int
, isPrimary : Bool
, button : Button
, modifiers : Modifiers
}

Everything known about a pointer event.

  • local: position in this element's own coordinate space (see the module intro).
  • scene: position in the root Evg.svg coordinate space.
  • client: raw browser pixels, measured from the top-left corner of the browser window.
  • frame: a snapshot of how to convert client pixels to scene coordinates, for use with whileDragging.
  • pointerType: whether this was a mouse, touch, or pen.
  • pointerId: a number identifying this pointer, so you can track several fingers independently during a multi-touch gesture.
  • isPrimary: True for the first pointer in a multi-pointer gesture.
  • button: which button triggered the event.
  • modifiers: which keyboard modifier keys were held.
= Mouse
| Touch
| Pen

The kind of device that produced a pointer event.

type Button
= Main
| Middle
| Secondary
| Back
| Forward
| NoButton

Which button was pressed. For touch and pen this is normally Main.

type alias Modifiers =
{ shift : Bool
, ctrl : Bool
, alt : Bool
, meta : Bool
}

Which modifier keys were held during the event.

Scroll and zoom

onWheel : (Wheel -> msg) -> Attribute { a | events : Supported } msg

Fires when the mouse wheel or trackpad scrolls over the element. Calls preventDefault, so the page won't scroll while the pointer is over your drawing. That is handy for zoom, but don't attach it to something the user needs to be able to scroll past.

Events.onWheel (\wheel -> Zoomed wheel)
type alias Wheel =
{ local : ( Float, Float )
, scene : ( Float, Float )
, delta : ( Float, Float )
, pinch : Bool
, frame : Frame
, modifiers : Modifiers
}

A mouse-wheel or trackpad scroll event.

  • local / scene: where the pointer was, in the two coordinate spaces.
  • delta: how far it scrolled, normalised to pixels (positive is down/right).
  • pinch: True when this is a trackpad pinch-to-zoom rather than a plain scroll. Browsers report pinches as a scroll with the Ctrl key implicitly held, which is what this detects.
  • frame: as on Pointer.
  • modifiers: modifier keys held. Note ctrl reads True during a trackpad pinch even if the physical key isn't down; use pinch to tell them apart.

Dragging beyond the element

A mouse drag that leaves the element stops sending events to it, because the browser delivers them to whatever is now under the cursor. To keep following the pointer you listen on the window instead, but a window event can't tell you where your drawing is. A Frame, captured during an ordinary handler while that information is still available, bridges the gap.

type Frame

A snapshot of the conversion from client pixels to scene coordinates, captured at the moment of a pointer event. Pass it to whileDragging to convert the window-level events that follow.

You get one on every Pointer and Wheel record; store it in your model when a drag starts.

frameToScene : Frame -> ( Float, Float ) -> ( Float, Float )

Convert a client-pixel position to scene coordinates using a captured Frame.

-> { onMove : ( Float, Float ) -> msg
, onEnd : ( Float, Float ) -> msg
}
-> Sub msg

Track a mouse drag across the whole window, converting each position to scene coordinates via a Frame you captured on onPointerDown.

Subscribe to it only while a drag is in progress, and only for mouse drags. Touch and pen keep firing on the element itself, so they don't need it:

subscriptions model =
    case model.drag of
        Just { frame, pointerType } ->
            if pointerType == Events.Mouse then
                Events.whileDragging frame
                    { onMove = DragMoved
                    , onEnd = DragEnded
                    }


            else
                Sub.none


        Nothing ->
            Sub.none

The Frame is a snapshot taken when the drag began. If the page scrolls or your Evg.svg moves during the drag, the converted positions will be off by that much; capture a fresh Frame if that matters. A Frame also assumes the svg element itself carries no CSS transform (ordinary positioning is fine).

Pointer behaviour

touchAction : TouchActionValue -> Attribute { a | events : Supported } msg

Declares which touch gestures the browser may handle itself, before your handlers see them. Set this to None on anything you drag or zoom with touch, so the browser doesn't scroll or pinch the page instead.

Events.touchAction Events.None
= None
| PanX
| PanY
| Manipulation

How the browser should treat touch gestures on this element.

pointerEvents : PointerEventsValue -> Attribute { a | events : Supported } msg

Sets which parts of a shape can receive pointer events.

  • VisiblePainted: only the painted (filled/stroked) visible areas (default)
  • Visible: the entire visible area, even transparent parts
  • Painted: all painted areas, even if hidden
  • All: the entire area regardless of visibility or paint
  • PointerEventsNone: the shape ignores all pointer events
= VisiblePainted
| Visible
| Painted
| All
| PointerEventsNone

Controls which parts of a shape respond to pointer events.

cursor : CursorValue -> Attribute { a | events : Supported } msg

Sets the mouse cursor appearance when hovering over the shape.

-- hand cursor, indicating clickable:
Events.cursor Events.PointerCursor
= Default
| PointerCursor
| Move
| Crosshair
| Text
| NotAllowed
| Grab
| Grabbing

The mouse cursor shape when hovering over the element.