2020-08-10 22:07:09 +00:00
|
|
|
{-# LANGUAGE BlockArguments #-}
|
2020-08-19 21:34:43 +00:00
|
|
|
{-# LANGUAGE DataKinds #-}
|
2020-08-10 22:07:09 +00:00
|
|
|
{-# LANGUAGE DerivingStrategies #-}
|
2020-08-15 17:11:52 +00:00
|
|
|
{-# LANGUAGE DeriveGeneric #-}
|
2020-08-20 01:57:26 +00:00
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
2020-09-08 23:23:25 +00:00
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
|
|
{-# LANGUAGE LambdaCase #-}
|
2020-08-10 14:38:27 +00:00
|
|
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
2020-09-08 23:23:25 +00:00
|
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
2020-08-19 21:34:43 +00:00
|
|
|
{-# LANGUAGE TypeApplications #-}
|
2020-08-10 14:38:27 +00:00
|
|
|
|
2020-08-15 17:11:52 +00:00
|
|
|
module MetaBrush.Document
|
2020-09-08 23:23:25 +00:00
|
|
|
( AABB(..), mkAABB
|
2020-09-02 02:52:08 +00:00
|
|
|
, Document(..), emptyDocument
|
2020-09-08 23:23:25 +00:00
|
|
|
, Stroke(..)
|
2020-08-20 01:57:26 +00:00
|
|
|
, PointData(..), BrushPointData(..)
|
2020-09-08 23:23:25 +00:00
|
|
|
, FocusState(..), Hoverable(..), HoverContext(..)
|
2020-08-20 01:57:26 +00:00
|
|
|
, _selection, _brush
|
2020-09-08 23:23:25 +00:00
|
|
|
, Guide(..)
|
|
|
|
, addGuide, selectedGuide
|
2020-08-15 17:11:52 +00:00
|
|
|
)
|
|
|
|
where
|
2020-08-04 06:15:06 +00:00
|
|
|
|
2020-09-01 19:56:59 +00:00
|
|
|
-- base
|
2020-09-08 23:23:25 +00:00
|
|
|
import Data.Semigroup
|
|
|
|
( Arg(..), Min(..), ArgMin )
|
2020-09-01 19:56:59 +00:00
|
|
|
import GHC.Generics
|
|
|
|
( Generic )
|
|
|
|
|
2020-09-08 23:23:25 +00:00
|
|
|
-- acts
|
|
|
|
import Data.Act
|
|
|
|
( Act
|
|
|
|
( (•) )
|
|
|
|
, Torsor
|
|
|
|
( (-->) )
|
|
|
|
)
|
|
|
|
|
2020-08-10 22:07:09 +00:00
|
|
|
-- containers
|
2020-09-05 22:35:00 +00:00
|
|
|
import Data.Map.Strict
|
|
|
|
( Map )
|
2020-09-08 23:23:25 +00:00
|
|
|
import qualified Data.Map.Strict as Map
|
|
|
|
( insert )
|
2020-09-05 22:35:00 +00:00
|
|
|
import qualified Data.Map.Strict as Map
|
|
|
|
( empty )
|
2020-08-16 22:09:16 +00:00
|
|
|
import Data.Sequence
|
|
|
|
( Seq )
|
2020-08-10 22:07:09 +00:00
|
|
|
|
2020-08-19 21:34:43 +00:00
|
|
|
-- generic-lens
|
|
|
|
import Data.Generics.Product.Fields
|
|
|
|
( field' )
|
2020-08-20 01:57:26 +00:00
|
|
|
import Data.Generics.Product.Typed
|
|
|
|
( HasType(typed) )
|
2020-08-19 21:34:43 +00:00
|
|
|
|
|
|
|
-- lens
|
|
|
|
import Control.Lens
|
|
|
|
( Lens' )
|
|
|
|
|
2020-09-08 23:23:25 +00:00
|
|
|
-- stm
|
|
|
|
import Control.Concurrent.STM
|
|
|
|
( STM )
|
|
|
|
|
2020-08-10 14:38:27 +00:00
|
|
|
-- text
|
|
|
|
import Data.Text
|
|
|
|
( Text )
|
|
|
|
|
|
|
|
-- MetaBrush
|
2020-08-19 21:34:43 +00:00
|
|
|
import Math.Bezier.Stroke
|
|
|
|
( StrokePoint(..) )
|
2020-09-08 23:23:25 +00:00
|
|
|
import Math.Module
|
|
|
|
( Inner((^.^)), squaredNorm, quadrance )
|
2020-08-10 14:38:27 +00:00
|
|
|
import Math.Vector2D
|
2020-09-05 22:35:00 +00:00
|
|
|
( Point2D(..), Vector2D(..) )
|
2020-09-08 23:23:25 +00:00
|
|
|
import MetaBrush.UI.Viewport
|
|
|
|
( Ruler(..) )
|
2020-08-16 22:09:16 +00:00
|
|
|
import MetaBrush.Unique
|
2020-09-08 23:23:25 +00:00
|
|
|
( UniqueSupply, Unique, freshUnique )
|
2020-08-10 14:38:27 +00:00
|
|
|
|
2020-08-04 06:15:06 +00:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
2020-08-10 14:38:27 +00:00
|
|
|
data AABB
|
|
|
|
= AABB
|
2020-08-23 22:40:16 +00:00
|
|
|
{ topLeft, botRight :: !( Point2D Double ) }
|
2020-08-10 14:38:27 +00:00
|
|
|
deriving stock Show
|
|
|
|
|
2020-09-08 23:23:25 +00:00
|
|
|
mkAABB :: Point2D Double -> Point2D Double -> AABB
|
|
|
|
mkAABB ( Point2D x1 y1 ) ( Point2D x2 y2 ) = AABB ( Point2D xmin ymin ) ( Point2D xmax ymax )
|
|
|
|
where
|
|
|
|
( xmin, xmax )
|
|
|
|
| x1 > x2 = ( x2, x1 )
|
|
|
|
| otherwise = ( x1, x2 )
|
|
|
|
( ymin, ymax )
|
|
|
|
| y1 > y2 = ( y2, y1 )
|
|
|
|
| otherwise = ( y1, y2 )
|
|
|
|
|
2020-08-04 06:15:06 +00:00
|
|
|
data Document
|
|
|
|
= Document
|
|
|
|
{ displayName :: !Text
|
2020-08-31 20:28:30 +00:00
|
|
|
, mbFilePath :: !( Maybe FilePath )
|
2020-08-04 06:15:06 +00:00
|
|
|
, unsavedChanges :: !Bool
|
2020-08-10 14:38:27 +00:00
|
|
|
, viewportCenter :: !( Point2D Double )
|
|
|
|
, zoomFactor :: !Double
|
2020-08-31 20:28:30 +00:00
|
|
|
, documentUnique :: Unique
|
|
|
|
, strokes :: ![ Stroke ]
|
2020-09-05 22:35:00 +00:00
|
|
|
, guides :: !( Map Unique Guide )
|
2020-08-04 06:15:06 +00:00
|
|
|
}
|
2020-08-15 17:11:52 +00:00
|
|
|
deriving stock ( Show, Generic )
|
2020-08-10 14:38:27 +00:00
|
|
|
|
|
|
|
data Stroke
|
|
|
|
= Stroke
|
2020-08-31 20:28:30 +00:00
|
|
|
{ strokeName :: Text
|
2020-08-13 17:05:19 +00:00
|
|
|
, strokeVisible :: !Bool
|
2020-08-16 22:09:16 +00:00
|
|
|
, strokeUnique :: Unique
|
2020-08-31 20:28:30 +00:00
|
|
|
, strokePoints :: !( Seq ( StrokePoint PointData ) )
|
2020-08-13 17:05:19 +00:00
|
|
|
}
|
2020-08-15 17:11:52 +00:00
|
|
|
deriving stock ( Show, Generic )
|
2020-08-13 17:05:19 +00:00
|
|
|
|
2020-08-19 21:34:43 +00:00
|
|
|
data PointData
|
|
|
|
= PointData
|
|
|
|
{ pointState :: FocusState
|
2020-08-20 01:57:26 +00:00
|
|
|
, brushShape :: Seq ( StrokePoint BrushPointData )
|
2020-08-13 17:05:19 +00:00
|
|
|
}
|
2020-08-15 17:11:52 +00:00
|
|
|
deriving stock ( Show, Generic )
|
2020-08-10 22:07:09 +00:00
|
|
|
|
2020-08-20 01:57:26 +00:00
|
|
|
data BrushPointData
|
|
|
|
= BrushPointData
|
|
|
|
{ brushPointState :: FocusState }
|
|
|
|
deriving stock ( Show, Generic )
|
|
|
|
|
2020-08-13 17:05:19 +00:00
|
|
|
data FocusState
|
|
|
|
= Normal
|
|
|
|
| Hover
|
|
|
|
| Selected
|
2020-08-19 03:28:47 +00:00
|
|
|
deriving stock ( Show, Eq )
|
2020-08-10 22:07:09 +00:00
|
|
|
|
2020-09-08 23:23:25 +00:00
|
|
|
instance Semigroup FocusState where
|
|
|
|
Selected <> _ = Selected
|
|
|
|
Normal <> s = s
|
|
|
|
_ <> Selected = Selected
|
|
|
|
s <> Normal = s
|
|
|
|
_ <> _ = Hover
|
|
|
|
instance Monoid FocusState where
|
|
|
|
mempty = Normal
|
|
|
|
|
2020-08-20 01:57:26 +00:00
|
|
|
_selection :: HasType FocusState pt => Lens' ( StrokePoint pt ) FocusState
|
|
|
|
_selection = field' @"pointData" . typed @FocusState
|
|
|
|
|
|
|
|
_brush :: Lens' ( StrokePoint PointData ) ( Seq ( StrokePoint BrushPointData ) )
|
|
|
|
_brush = field' @"pointData" . field' @"brushShape"
|
2020-08-19 21:34:43 +00:00
|
|
|
|
2020-09-01 19:56:59 +00:00
|
|
|
emptyDocument :: Text -> Unique -> Document
|
|
|
|
emptyDocument docName unique =
|
|
|
|
Document
|
|
|
|
{ displayName = docName
|
|
|
|
, mbFilePath = Nothing
|
|
|
|
, unsavedChanges = False
|
|
|
|
, viewportCenter = Point2D 0 0
|
|
|
|
, zoomFactor = 1
|
|
|
|
, documentUnique = unique
|
|
|
|
, strokes = []
|
2020-09-05 22:35:00 +00:00
|
|
|
, guides = Map.empty
|
2020-09-01 19:56:59 +00:00
|
|
|
}
|
2020-09-08 23:23:25 +00:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
data HoverContext
|
|
|
|
= MouseHover !( Point2D Double )
|
|
|
|
| RectangleHover !AABB
|
|
|
|
deriving stock Show
|
|
|
|
|
|
|
|
instance Act ( Vector2D Double ) HoverContext where
|
|
|
|
v • MouseHover p = MouseHover ( v • p )
|
|
|
|
v • RectangleHover ( AABB p1 p2 ) = RectangleHover ( AABB ( v • p1 ) ( v • p2 ) )
|
|
|
|
|
|
|
|
instance Act ( Vector2D Double ) ( Maybe HoverContext ) where
|
|
|
|
(•) v = fmap ( v • )
|
|
|
|
|
|
|
|
class Hoverable a where
|
|
|
|
hovered :: Maybe HoverContext -> Double -> a -> FocusState
|
|
|
|
|
|
|
|
instance Hoverable ( Point2D Double ) where
|
|
|
|
hovered Nothing _ _ = Normal
|
|
|
|
hovered ( Just ( MouseHover p ) ) zoom q
|
|
|
|
| quadrance @( Vector2D Double ) p q * zoom ^ ( 2 :: Int ) < 16
|
|
|
|
= Hover
|
|
|
|
| otherwise
|
|
|
|
= Normal
|
|
|
|
hovered ( Just ( RectangleHover ( AABB ( Point2D x1 y1 ) ( Point2D x2 y2 ) ) ) ) _ ( Point2D x y )
|
|
|
|
| x >= x1 && x <= x2 && y >= y1 && y <= y2
|
|
|
|
= Hover
|
|
|
|
| otherwise
|
|
|
|
= Normal
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Guides.
|
|
|
|
|
|
|
|
data Guide
|
|
|
|
= Guide
|
|
|
|
{ guidePoint :: !( Point2D Double ) -- ^ point on the guide line
|
|
|
|
, guideNormal :: !( Vector2D Double ) -- ^ /normalised/ normal vector of the guide
|
|
|
|
, guideFocus :: !FocusState
|
|
|
|
, guideUnique :: Unique
|
|
|
|
}
|
|
|
|
deriving stock ( Show, Generic )
|
|
|
|
|
|
|
|
-- | Try to select a guide at the given document coordinates.
|
|
|
|
selectedGuide :: Point2D Double -> Document -> Maybe Guide
|
|
|
|
selectedGuide c ( Document { zoomFactor, guides } ) =
|
|
|
|
\case { Min ( Arg _ g ) -> g } <$> foldMap ( selectGuide_maybe c zoomFactor ) guides
|
|
|
|
|
|
|
|
selectGuide_maybe :: Point2D Double -> Double -> Guide -> Maybe ( ArgMin Double Guide )
|
|
|
|
selectGuide_maybe c zoom guide@( Guide { guidePoint = p, guideNormal = n } )
|
|
|
|
| sqDist * zoom ^ ( 2 :: Int ) < 4
|
|
|
|
= Just ( Min ( Arg sqDist guide ) )
|
|
|
|
| otherwise
|
|
|
|
= Nothing
|
|
|
|
where
|
|
|
|
t :: Double
|
|
|
|
t = ( c --> p ) ^.^ n
|
|
|
|
sqDist :: Double
|
|
|
|
sqDist = t ^ ( 2 :: Int ) / squaredNorm n
|
|
|
|
|
|
|
|
-- | Add new guide after a mouse drag from a ruler area.
|
|
|
|
addGuide :: UniqueSupply -> Ruler -> Point2D Double -> Document -> STM Document
|
|
|
|
addGuide uniqueSupply ruler p = ( field' @"guides" ) insertNewGuides
|
|
|
|
where
|
|
|
|
insertNewGuides :: Map Unique Guide -> STM ( Map Unique Guide )
|
|
|
|
insertNewGuides gs = case ruler of
|
|
|
|
RulerCorner
|
|
|
|
-> do
|
|
|
|
uniq1 <- freshUnique uniqueSupply
|
|
|
|
uniq2 <- freshUnique uniqueSupply
|
|
|
|
let
|
|
|
|
guide1, guide2 :: Guide
|
|
|
|
guide1 = Guide { guidePoint = p, guideNormal = Vector2D 0 1, guideFocus = Normal, guideUnique = uniq1 }
|
|
|
|
guide2 = Guide { guidePoint = p, guideNormal = Vector2D 1 0, guideFocus = Normal, guideUnique = uniq2 }
|
|
|
|
pure ( Map.insert uniq2 guide2 . Map.insert uniq1 guide1 $ gs )
|
|
|
|
TopRuler
|
|
|
|
-> do
|
|
|
|
uniq1 <- freshUnique uniqueSupply
|
|
|
|
let
|
|
|
|
guide1 :: Guide
|
|
|
|
guide1 = Guide { guidePoint = p, guideNormal = Vector2D 0 1, guideFocus = Normal, guideUnique = uniq1 }
|
|
|
|
pure ( Map.insert uniq1 guide1 gs )
|
|
|
|
LeftRuler
|
|
|
|
-> do
|
|
|
|
uniq2 <- freshUnique uniqueSupply
|
|
|
|
let
|
|
|
|
guide2 :: Guide
|
|
|
|
guide2 = Guide { guidePoint = p, guideNormal = Vector2D 1 0, guideFocus = Normal, guideUnique = uniq2 }
|
|
|
|
pure ( Map.insert uniq2 guide2 gs )
|
|
|
|
|
|
|
|
instance Hoverable Guide where
|
|
|
|
hovered ( Just ( MouseHover c ) ) zoom guide
|
|
|
|
| Just _ <- selectGuide_maybe c zoom guide
|
|
|
|
= Hover
|
|
|
|
| otherwise
|
|
|
|
= Normal
|
|
|
|
hovered _ _ _ = Normal
|