packages feed

brush-stroking-0.1.0.0: src/BrushStroking/Hover.hs

module BrushStroking.Hover where

-- base
import GHC.Generics
  ( Generic )

-- acts
import Data.Act
  ( Act(..) )


-- deepseq
import Control.DeepSeq
  ( NFData(..) )

-- brush-strokes
import Math.Module
  ( quadrance
  , closestPointOnSegment
  )
import Math.Linear
  ( ℝ(..), T(..), Segment(..) )

-- MetaBrush
import BrushStroking.Document

--------------------------------------------------------------------------------

-- | An axis-aligned bounding box.
data AABB
  = AABB
  { topLeft, botRight :: !( ℝ 2 ) }
  deriving stock    ( Show, Generic )
  deriving anyclass NFData

-- | Create an 'AABB'.
mkAABB :: ℝ 2 -> ℝ 2 -> AABB
mkAABB ( ℝ2 x1 y1 ) ( ℝ2 x2 y2 ) = AABB ( ℝ2 xmin ymin ) ( ℝ2 xmax ymax )
  where
    ( xmin, xmax )
      | x1 > x2   = ( x2, x1 )
      | otherwise = ( x1, x2 )
    ( ymin, ymax )
      | y1 > y2   = ( y2, y1 )
      | otherwise = ( y1, y2 )

-- | A hover (mouse cursor or entire rectangle).
data HoverContext
  = MouseHover     !( ℝ 2 )
  | RectangleHover !AABB
  deriving stock    ( Show, Generic )
  deriving anyclass NFData

instance Act ( T ( ℝ 2 ) ) HoverContext where
  v • MouseHover p = MouseHover ( v • p )
  v • RectangleHover ( AABB p1 p2 ) = RectangleHover ( AABB ( v • p1 ) ( v • p2 ) )

instance Act ( T ( ℝ 2 ) ) ( Maybe HoverContext ) where
  (•) v = fmap ( v • )

class Hoverable a where
  hovered :: HoverContext -> Zoom -> a -> Bool

instance Hoverable ( ℝ 2 ) where
  hovered ( MouseHover p ) zoom q
    = inLargePointClickRange zoom p q
  hovered ( RectangleHover ( AABB ( ℝ2 x1 y1 ) ( ℝ2 x2 y2 ) ) ) _ ( ℝ2 x y )
    = x >= x1 && x <= x2 && y >= y1 && y <= y2

instance Hoverable ( Segment ( ℝ 2 ) ) where
  hovered ( MouseHover p ) zoom seg
    = hovered ( MouseHover p ) zoom p'
    where
      ( _, p' ) = closestPointOnSegment @( T ( ℝ 2 ) ) p seg
  hovered hov@(RectangleHover {} ) zoom ( Segment p0 p1 )
    -- Only consider a segment to be "hovered" if it lies entirely within the
    -- hover rectangle, not just if the hover rectangle intersects it.
    = hovered hov zoom p0 && hovered hov zoom p1

inLargePointClickRange :: Zoom -> ℝ 2 -> ℝ 2 -> Bool
inLargePointClickRange ( Zoom { zoomFactor } ) c p =
  quadrance @( T ( ℝ 2 ) ) c p < 16 / ( zoomFactor * zoomFactor )

inSmallPointClickRange :: Zoom -> ℝ 2 -> ℝ 2 -> Bool
inSmallPointClickRange ( Zoom { zoomFactor } ) c p =
  quadrance @( T ( ℝ 2 ) ) c p < 6 / ( zoomFactor * zoomFactor )