# `RasterExRatatui.Input.Touch`
[🔗](https://github.com/mcass19/raster_ex_ratatui/blob/v0.4.0/lib/raster_ex_ratatui/input/touch.ex#L1)

Turns evdev touch events into the `ExRatatui.Event.Mouse` events an app expects: a tap is a `"down"` and an `"up"` on the cell under the finger, a finger moving into another cell is a `"drag"`.

Pure and table-free: keep the struct between calls, since it holds the contact in progress. A panel reports its finger in its own axis units and its own orientation; the translator scales the axes to the panel's pixels, applies `swap_xy:` and `invert_x:`/`invert_y:` when the controller does not follow the panel, and asks `cell_at:` (normally `RasterExRatatui.Raster.cell_at/2` of the surface's raster, which knows the rotation) which cell that pixel is. A finger outside the grid, in the margins, produces nothing until it enters a cell.

Only the first contact counts: multitouch slots other than `0` are ignored, so a second finger never moves the cursor. Both multitouch type B (`abs_mt_slot`, `abs_mt_tracking_id`, `abs_mt_position_x/y`, what the Goodix controller of the Raspberry Pi Touch Display 2 sends) and single-touch panels (`abs_x/y` with `btn_touch`) are understood.

Mouse events are emitted at the end of each evdev frame. [`input_event`](https://hex.pm/packages/input_event) delivers one frame per message and drops the `syn_report` that ends it, so `translate_all/2` takes the end of its list as the end of a frame; an explicit `syn_report` ends one too, for sources that keep it.

## Options

`new/1` takes:

  * `:size` (required) — the physical panel in pixels, `{width, height}`
  * `:cell_at` (required) — `({px, py} -> {col, row} | :outside)`
  * `:axes` — `%{x: {min, max}, y: {min, max}}` as the controller reports them (`RasterExRatatui.Input.Devices.touch_axes/1`); default the pixel ranges, `{0, width - 1}` and `{0, height - 1}`
  * `:swap_xy` — the controller's x runs along the panel's height (default `false`)
  * `:invert_x`, `:invert_y` — an axis runs the other way (default `false`)

## Examples

A 24×16 panel of 6×8 cells; a tap on pixel (13, 9) is cell (2, 1).

    iex> alias RasterExRatatui.Input.Touch
    iex> touch = Touch.new(size: {24, 16}, cell_at: fn {x, y} -> {div(x, 6), div(y, 8)} end)
    iex> # The two messages input_event sends for a tap on a Goodix panel.
    iex> {touch, [down]} = Touch.translate_all(touch, [
    ...>   {:ev_abs, :abs_mt_tracking_id, 33}, {:ev_abs, :abs_mt_position_x, 13},
    ...>   {:ev_abs, :abs_mt_position_y, 9}, {:ev_abs, :abs_mt_touch_major, 46},
    ...>   {:ev_key, :btn_touch, 1}, {:ev_abs, :abs_x, 13}, {:ev_abs, :abs_y, 9}
    ...> ])
    iex> {_touch, [up]} = Touch.translate_all(touch, [{:ev_abs, :abs_mt_tracking_id, -1}, {:ev_key, :btn_touch, 0}])
    iex> [{down.kind, down.x, down.y}, {up.kind, up.x, up.y}]
    [{"down", 2, 1}, {"up", 2, 1}]

# `axes`

```elixir
@type axes() :: %{x: {integer(), integer()}, y: {integer(), integer()}}
```

# `t`

```elixir
@type t() :: %RasterExRatatui.Input.Touch{
  axes: axes(),
  cell: {integer(), integer()} | nil,
  cell_at: ({non_neg_integer(), non_neg_integer()} -&gt;
              {integer(), integer()} | :outside),
  invert_x: boolean(),
  invert_y: boolean(),
  position: {integer() | nil, integer() | nil},
  releasing?: boolean(),
  size: {pos_integer(), pos_integer()},
  slot: integer(),
  swap_xy: boolean(),
  touching?: boolean()
}
```

# `new`

```elixir
@spec new(keyword()) :: t()
```

Builds the translator (see the moduledoc for the options). Raises `ArgumentError` without `:size` or `:cell_at`.

# `translate`

```elixir
@spec translate(t(), {atom(), atom(), integer()}) ::
  {t(), [ExRatatui.Event.Mouse.t()]}
```

Folds one evdev event, `{type, code, value}`, and returns the mouse events it completes: only a `syn_report` ends a frame here. For `input_event`'s messages, which carry no `syn_report`, use `translate_all/2`.

## Examples

    iex> alias RasterExRatatui.Input.Touch
    iex> touch = Touch.new(size: {24, 16}, cell_at: fn {x, y} -> {div(x, 6), div(y, 8)} end)
    iex> {touch, []} = Touch.translate(touch, {:ev_abs, :abs_x, 20})
    iex> {touch, []} = Touch.translate(touch, {:ev_abs, :abs_y, 3})
    iex> {touch, []} = Touch.translate(touch, {:ev_key, :btn_touch, 1})
    iex> {_touch, [down]} = Touch.translate(touch, {:ev_syn, :syn_report, 0})
    iex> {down.kind, down.button, down.x, down.y}
    {"down", "left", 3, 0}

# `translate_all`

```elixir
@spec translate_all(t(), [{atom(), atom(), integer()}] | :disconnect) ::
  {t(), [ExRatatui.Event.Mouse.t()]}
```

Folds one frame of events, as `input_event` delivers it, or the `:disconnect` it sends when the panel goes away, which ends a contact in progress with an `"up"`.

The end of the list ends the frame. A list that carries its own `syn_report`s is split at them instead, and nothing more is emitted after the last one.

## Examples

A finger landing on one cell and sliding to the next; the second finger (slot 1) changes nothing.

    iex> alias RasterExRatatui.Input.Touch
    iex> touch = Touch.new(size: {24, 16}, cell_at: fn {x, y} -> {div(x, 6), div(y, 8)} end)
    iex> {touch, [down]} = Touch.translate_all(touch, [{:ev_abs, :abs_mt_tracking_id, 1}, {:ev_abs, :abs_mt_position_x, 2}, {:ev_abs, :abs_mt_position_y, 2}])
    iex> {touch, []} = Touch.translate_all(touch, [{:ev_abs, :abs_mt_position_x, 4}])
    iex> {touch, []} = Touch.translate_all(touch, [{:ev_abs, :abs_mt_slot, 1}, {:ev_abs, :abs_mt_tracking_id, 2}, {:ev_abs, :abs_mt_position_x, 20}, {:ev_abs, :abs_mt_position_y, 12}])
    iex> {touch, [drag]} = Touch.translate_all(touch, [{:ev_abs, :abs_mt_slot, 0}, {:ev_abs, :abs_mt_position_x, 9}])
    iex> {_touch, [up]} = Touch.translate_all(touch, :disconnect)
    iex> [{down.kind, down.x, down.y}, {drag.kind, drag.x, drag.y}, {up.kind, up.x, up.y}]
    [{"down", 0, 0}, {"drag", 1, 0}, {"up", 1, 0}]

A finger in the margin is ignored until it enters a cell.

    iex> alias RasterExRatatui.Input.Touch
    iex> outside = fn {x, y} -> if x < 12, do: {div(x, 6), div(y, 8)}, else: :outside end
    iex> touch = Touch.new(size: {24, 16}, cell_at: outside)
    iex> {touch, []} = Touch.translate_all(touch, [{:ev_key, :btn_touch, 1}, {:ev_abs, :abs_x, 20}, {:ev_abs, :abs_y, 0}])
    iex> {_touch, [down]} = Touch.translate_all(touch, [{:ev_abs, :abs_x, 7}])
    iex> {down.kind, down.x, down.y}
    {"down", 1, 0}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
