Bot-Man-Toe/elm/Zipper.elm

144 lines
2.7 KiB
Elm

module Zipper exposing (Zipper, current, currentPage, fromList, init, isAtEnd, isAtStart, length, next, prev, samePageAs, toEnd, toStart)
{-| The Zipper allows to dynamically paginate between items.
-}
type Zipper a
= Zipper
{ prev : List a
, current : a
, next : List a
}
{-| Gets the current item in the zipper.
-}
current : Zipper a -> a
current (Zipper data) =
data.current
{-| If counting from 1, determines the number corresponding to the currently selected item.
-}
currentPage : Zipper a -> Int
currentPage (Zipper data) =
List.length data.prev + 1
{-| Builds a zipper from a list of items.
-}
fromList : a -> List a -> Zipper a
fromList head tail =
Zipper { prev = [], current = head, next = tail }
{-| Create a new zipper from nothing.
-}
init : a -> Zipper a
init x =
Zipper { prev = [], current = x, next = [] }
{-| Determines whether the zipper is at the end.
-}
isAtEnd : Zipper a -> Bool
isAtEnd (Zipper data) =
List.isEmpty data.next
{-| Determines whether the zipper is at the start.
-}
isAtStart : Zipper a -> Bool
isAtStart (Zipper data) =
List.isEmpty data.prev
{-| Determine the total number of items in the zipper.
-}
length : Zipper a -> Int
length (Zipper data) =
List.length data.prev + List.length data.next + 1
{-| Paginates one further in the zipper.
-}
next : Zipper a -> Zipper a
next (Zipper data) =
case data.next of
[] ->
Zipper data
head :: tail ->
Zipper
{ prev = data.current :: data.prev
, current = head
, next = tail
}
{-| Paginates one back in the zipper.
-}
prev : Zipper a -> Zipper a
prev (Zipper data) =
case data.prev of
[] ->
Zipper data
head :: tail ->
Zipper
{ prev = tail
, current = head
, next = data.current :: data.next
}
{-| Synchronize a zipper to be at the same page as another, if possible.
-}
samePageAs : Zipper a -> Zipper a -> Zipper a
samePageAs goal z =
let
cp =
currentPage z
tp =
currentPage goal
in
if cp == tp then
z
else if cp < tp then
if isAtEnd z then
z
else
samePageAs goal (next z)
else if isAtStart z then
z
else
samePageAs goal (prev z)
{-| Navigate all the way to the end of the zipper.
-}
toEnd : Zipper a -> Zipper a
toEnd z =
if isAtEnd z then
z
else
toEnd (next z)
{-| Navigate all the way to the start of the zipper.
-}
toStart : Zipper a -> Zipper a
toStart z =
if isAtStart z then
z
else
toStart (prev z)