defmodule FloUI.Scrollable.PositionCap do
alias __MODULE__
@moduledoc """
Module for applying limits to a position.
"""
@typedoc """
A vector 2 in the form of {x, y}
"""
@type v2 :: Scenic.Scrollable.v2()
@typedoc """
Data structure representing a minimum, or maximum cap which values will be compared against.
The cap can be either a `t:v2/0` or a `t:Scenic.Scrollable.Direction.t/0`.
By using a `t:Scenic.Scrollable.Direction/0` it is possible to cap a position only for either its x, or its y value.
"""
@type cap :: v2 | {:horizontal, number} | {:vertical, number}
@typedoc """
The settings with which to initialize a `t:Scenic.Scrollable.PositionCap.t`.
Both min and max caps are optional, and can be further limited to only the x, or y axes by passing in a `t:Scenic.Scrollable.Direction/0` rather than a `t:v2/0`.
"""
@type settings :: %{
optional(:max) => cap,
optional(:min) => cap
}
@typedoc """
A struct representing a position cap. Positions in the form of a `t:v2/0` can be compared against, and increased or reduced to the capped values by using the `cap/2` function.
"""
@type t :: %PositionCap{
max: cap,
min: cap
}
defstruct max: {0, 0},
min: {0, 0}
@doc """
Initializes a `t:Scenic.Scrollable.PositionCap.t/0` according to the provided `t:Scenic.Scrollable.PositionCap.settings/0`.
"""
@spec init(settings) :: t
def init(settings) do
# TODO add validation in order to prevent a max value that is smaller than the min value
# In the current code, the max value will take precedence in such case
%PositionCap{
max: settings[:max],
min: settings[:min]
}
end
@doc """
Compare the upper and lower limits set in the `t:Scenic.Scrollable.PositionCap.t/0` against the `t:v2/0` provided, and adjusts the `t:v2/0` according to those limits.
"""
@spec cap(t, v2) :: v2
def cap(%{min: min, max: max}, coordinate) do
coordinate
|> floor(min)
|> ceil(max)
end
@spec floor(v2, cap) :: v2
defp floor({x, y}, {:horizontal, min_x}), do: {max(x, min_x), y}
defp floor({x, y}, {:vertical, min_y}), do: {x, max(y, min_y)}
defp floor({x, y}, {min_x, min_y}), do: {max(x, min_x), max(y, min_y)}
@spec ceil(v2, cap) :: v2
defp ceil({x, y}, {:horizontal, max_x}), do: {min(x, max_x), y}
defp ceil({x, y}, {:vertical, max_y}), do: {x, min(y, max_y)}
defp ceil({x, y}, {max_x, max_y}), do: {min(x, max_x), min(y, max_y)}
end