packages feed

tinytools-0.1.0.4: src/Potato/Flow/Attachments.hs

{-# LANGUAGE RecordWildCards #-}

module Potato.Flow.Attachments (
  AvailableAttachment
  , BoxWithAttachmentLocation
  , attachLocationFromLBox_conjugateCartRotationReflection
  , attachLocationFromLBox
  , availableAttachLocationsFromLBox
  , owlItem_availableAttachments
  , owlItem_availableAttachmentsAtDefaultLocation
  , isOverAttachment
  , projectAttachment
  , attachmentRenderChar

) where

import           Relude

import           Potato.Flow.Math
import           Potato.Flow.OwlItem
import Potato.Flow.SElts
import Potato.Flow.Methods.LineTypes

import Data.List (minimumBy)
import Data.Ratio
import Control.Exception (assert)


data CartSegment = CartSegment {
    _cartSegment_isVertical :: Bool
    , _cartSegment_common :: Int
    , _cartSegment_leftOrTop :: Int
    , _cartSegment_rightOrBot :: Int
  } deriving (Eq, Show)
-- represents possible place to attach
data AvailableAttachment = AvailableAttachment_CartSegment CartSegment AttachmentLocation deriving (Show, Eq)

type BoxWithAttachmentLocation = (LBox, AttachmentLocation, AttachmentOffsetRatio)

-- TODO there is a bug in cartRotationReflection_apply/cartRotationReflection_invert_apply where we don't actually apply the rotation but somehow this only works with that bug... Maybe the rotations cancel out?
-- uh not sure if this is actually conjugation...
attachLocationFromLBox_conjugateCartRotationReflection :: CartRotationReflection -> Bool -> BoxWithAttachmentLocation -> XY
attachLocationFromLBox_conjugateCartRotationReflection crr offsetBorder (box, al, af) = r where
  r' = attachLocationFromLBox offsetBorder (cartRotationReflection_invert_apply crr box, cartRotationReflection_invert_apply crr al, cartRotationReflection_invert_apply crr af)
  r = cartRotationReflection_apply crr r'

-- NOTE assumes LBox is canonical
attachLocationFromLBox :: Bool -> BoxWithAttachmentLocation -> XY
attachLocationFromLBox True (lbx, al, af) = attachLocationFromLBox False (lBox_expand lbx (1,1,1,1), al, af)
attachLocationFromLBox False (LBox (V2 x y) (V2 w h), al, af) = case al of
  AL_Top -> V2 (x+w * n `div` d) y
  AL_Bot -> V2 (x+(w-1) * dn `div` d) (y+h-1)
  AL_Left -> V2 x (y+(h-1) * dn `div` d )
  AL_Right -> V2 (x+w-1) (y+h * n `div` d )
  -- or maybe in the middle is better?
  AL_Any -> V2 x y
  where
    n = numerator af
    d = denominator af
    dn = d-n


defaultAttachLocationsFromLBox :: Bool -> LBox -> [(AttachmentLocation, XY)]
defaultAttachLocationsFromLBox offsetBorder lbx = fmap (\a -> (a, attachLocationFromLBox offsetBorder (lbx, a, attachment_offset_rel_default))) [AL_Top, AL_Bot, AL_Left, AL_Right]

-- NOTE assumes LBox is canonical
availableAttachLocationFromLBox :: Bool -> (LBox, AttachmentLocation) -> AvailableAttachment
availableAttachLocationFromLBox offset (LBox (V2 x y) (V2 w h), al)
  | offset = flip AvailableAttachment_CartSegment al $ case al of
    AL_Top -> CartSegment False (y-1) x (x+w)
    AL_Bot -> CartSegment False (y+h) x (x+w)
    AL_Left -> CartSegment True (x-1) y (y+h)
    AL_Right -> CartSegment True (x+w) y (y+h)
    AL_Any -> assert False $ CartSegment False x y y
  | otherwise = flip AvailableAttachment_CartSegment al $ case al of
    AL_Top -> CartSegment False y x (x+w)
    AL_Bot -> CartSegment False (y+h-1) x (x+w)
    AL_Left -> CartSegment True x y (y+h)
    AL_Right -> CartSegment True (x+w-1) y (y+h)
    AL_Any -> assert False $ CartSegment False x y y

availableAttachLocationsFromLBox :: Bool -> LBox -> [AvailableAttachment]
availableAttachLocationsFromLBox offsetBorder lbx = fmap (\a -> (availableAttachLocationFromLBox offsetBorder (lbx, a))) [AL_Top, AL_Bot, AL_Left, AL_Right]

owlItem_availableAttachmentsAtDefaultLocation :: Bool -> Bool -> OwlItem -> [(AttachmentLocation, XY)]
owlItem_availableAttachmentsAtDefaultLocation includeNoBorder offsetBorder o = case _owlItem_subItem o of
  OwlSubItemBox sbox | not includeNoBorder && not (sBoxType_hasBorder (_sBox_boxType sbox)) -> []
  OwlSubItemBox sbox -> defaultAttachLocationsFromLBox offsetBorder (_sBox_box sbox)
  _ -> []

owlItem_availableAttachments :: Bool -> Bool -> OwlItem -> [AvailableAttachment]
owlItem_availableAttachments includeNoBorder offsetBorder o = case _owlItem_subItem o of
  OwlSubItemBox sbox | not includeNoBorder && not (sBoxType_hasBorder (_sBox_boxType sbox)) -> []
  OwlSubItemBox sbox -> availableAttachLocationsFromLBox offsetBorder (_sBox_box sbox)
  _ -> []

isOverAttachment :: XY -> [(Attachment, XY)] -> Maybe (Attachment, XY)
isOverAttachment pos attachments = find (\(_,x) -> x == pos) attachments


projectAttachment :: AttachmentLocation -> XY -> REltId -> LBox -> Maybe (Attachment, XY)
projectAttachment preval (V2 x y) rid lbox = r where
  als = availableAttachLocationsFromLBox False lbox

  -- returns (projection distance, (projection ratio, projection position)
  projdfn :: AvailableAttachment -> (Int, (AttachmentOffsetRatio, XY), AvailableAttachment)
  projdfn aa@(AvailableAttachment_CartSegment (CartSegment {..}) al) = r2 where
    projcomp = if _cartSegment_isVertical then x else y
    (orthd, orthcomp) = (abs (projcomp - _cartSegment_common), _cartSegment_common)
    slidecomp = if _cartSegment_isVertical then y else x
    (parad, paracomp) = if slidecomp < _cartSegment_leftOrTop
      then (_cartSegment_leftOrTop - slidecomp, _cartSegment_leftOrTop)
      else if slidecomp > _cartSegment_rightOrBot
        then (slidecomp - _cartSegment_rightOrBot, _cartSegment_rightOrBot)
        else (0, slidecomp)

    pos2@(V2 px py) = if _cartSegment_isVertical then V2 orthcomp paracomp else V2 paracomp orthcomp
    segl = _cartSegment_rightOrBot - _cartSegment_leftOrTop
    ratio2 = case al of
      AL_Top -> (px - _cartSegment_leftOrTop) % segl
      AL_Bot -> (_cartSegment_rightOrBot - px) % segl
      AL_Left -> (_cartSegment_rightOrBot - py) % segl
      AL_Right -> (py - _cartSegment_leftOrTop) % segl
      AL_Any -> error "unexpected"

    r2 = (parad+orthd, (ratio2, pos2), aa)

  rslts = fmap projdfn als
  cmpfn (d1, _, AvailableAttachment_CartSegment _ al1) (d2, _, AvailableAttachment_CartSegment _ al2) = compare d1 d2 <> compare (al2 == preval) (al1 == preval)
  (d, (ratio1, pos1), AvailableAttachment_CartSegment _ alfinal) = minimumBy cmpfn rslts

  attachment = Attachment {
      _attachment_target = rid
      , _attachment_location = alfinal
      , _attachment_offset_rel = ratio1
    }

  r = if d > 2
    then Nothing
    else Just $ (attachment, pos1)



attachmentRenderChar :: Attachment -> PChar
attachmentRenderChar att = case _attachment_location att of
  AL_Top -> '⇈'
  AL_Bot -> '⇊'
  AL_Left -> '⇇'
  AL_Right -> '⇉'
  AL_Any -> ' ' -- should never be rendered