2024-09-27 15:21:24 +00:00
|
|
|
|
module MetaBrush.Document where
|
2020-08-04 06:15:06 +00:00
|
|
|
|
|
2020-09-01 19:56:59 +00:00
|
|
|
|
-- base
|
|
|
|
|
import GHC.Generics
|
2024-09-27 15:21:24 +00:00
|
|
|
|
( Generic )
|
2020-09-08 23:23:25 +00:00
|
|
|
|
|
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
|
2024-09-27 15:21:24 +00:00
|
|
|
|
import Data.Set
|
|
|
|
|
( Set )
|
|
|
|
|
import qualified Data.Set as Set
|
2020-08-10 22:07:09 +00:00
|
|
|
|
|
2020-09-10 16:43:42 +00:00
|
|
|
|
-- deepseq
|
|
|
|
|
import Control.DeepSeq
|
2024-09-27 15:21:24 +00:00
|
|
|
|
( NFData(..) )
|
2020-09-08 23:23:25 +00:00
|
|
|
|
|
2020-08-10 14:38:27 +00:00
|
|
|
|
-- text
|
|
|
|
|
import Data.Text
|
|
|
|
|
( Text )
|
2020-11-12 17:34:46 +00:00
|
|
|
|
|
2024-09-27 15:21:24 +00:00
|
|
|
|
-- brush-strokes
|
|
|
|
|
import Math.Linear
|
|
|
|
|
( ℝ(..), T(..) )
|
2020-08-10 14:38:27 +00:00
|
|
|
|
|
|
|
|
|
-- MetaBrush
|
2024-09-27 15:21:24 +00:00
|
|
|
|
import MetaBrush.Layer
|
|
|
|
|
( LayerMetadata, emptyHierarchy )
|
|
|
|
|
import MetaBrush.Stroke
|
|
|
|
|
( StrokeHierarchy, PointIndex )
|
2020-08-16 22:09:16 +00:00
|
|
|
|
import MetaBrush.Unique
|
2024-09-27 15:21:24 +00:00
|
|
|
|
( Unique )
|
2020-08-10 14:38:27 +00:00
|
|
|
|
|
2020-08-04 06:15:06 +00:00
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
2020-09-10 16:43:42 +00:00
|
|
|
|
-- | Document, together with some extra metadata.
|
2020-08-04 06:15:06 +00:00
|
|
|
|
data Document
|
|
|
|
|
= Document
|
2024-09-27 15:21:24 +00:00
|
|
|
|
{ documentContent :: !DocumentContent
|
|
|
|
|
-- ^ Main document content, which we keep track throughout history.
|
|
|
|
|
, documentMetadata :: !DocumentMetadata
|
|
|
|
|
-- ^ Metadata about the document, that we don't track throughout history.
|
2020-09-10 16:43:42 +00:00
|
|
|
|
}
|
|
|
|
|
deriving stock ( Show, Generic )
|
|
|
|
|
deriving anyclass NFData
|
|
|
|
|
|
2024-09-27 15:21:24 +00:00
|
|
|
|
newtype Zoom = Zoom { zoomFactor :: Double }
|
|
|
|
|
deriving stock ( Show, Eq, Ord )
|
|
|
|
|
deriving newtype NFData
|
|
|
|
|
|
|
|
|
|
-- | A collection of points, indexed first by the stroke they belong to
|
|
|
|
|
-- and then their position in that stroke.
|
|
|
|
|
newtype StrokePoints = StrokePoints { strokePoints :: Map Unique ( Set PointIndex ) }
|
|
|
|
|
deriving newtype ( Eq, Show, NFData )
|
|
|
|
|
-- Invariant: the sets are never empty.
|
|
|
|
|
|
|
|
|
|
instance Semigroup StrokePoints where
|
|
|
|
|
( StrokePoints pts1 ) <> ( StrokePoints pts2 ) =
|
|
|
|
|
StrokePoints ( Map.unionWith Set.union pts1 pts2 )
|
|
|
|
|
instance Monoid StrokePoints where
|
|
|
|
|
mempty = StrokePoints Map.empty
|
|
|
|
|
|
|
|
|
|
-- | Remove the second set of points from the first.
|
|
|
|
|
differenceStrokePoints :: StrokePoints -> StrokePoints -> StrokePoints
|
|
|
|
|
differenceStrokePoints ( StrokePoints pts1 ) ( StrokePoints pts2 ) =
|
|
|
|
|
StrokePoints $
|
|
|
|
|
Map.differenceWith remove pts1 pts2
|
|
|
|
|
where
|
|
|
|
|
remove :: Set PointIndex -> Set PointIndex -> Maybe ( Set PointIndex )
|
|
|
|
|
remove old new =
|
|
|
|
|
let new' = old Set.\\ new
|
|
|
|
|
in if null new'
|
|
|
|
|
then Nothing
|
|
|
|
|
else Just new'
|
|
|
|
|
|
|
|
|
|
noStrokePoints :: StrokePoints -> Bool
|
|
|
|
|
noStrokePoints ( StrokePoints pts ) = null pts
|
|
|
|
|
|
|
|
|
|
elemStrokePoint :: Unique -> PointIndex -> StrokePoints -> Bool
|
|
|
|
|
elemStrokePoint u i ( StrokePoints pts ) =
|
|
|
|
|
case Map.lookup u pts of
|
|
|
|
|
Nothing -> False
|
|
|
|
|
Just is -> Set.member i is
|
|
|
|
|
|
|
|
|
|
-- | Metadata about a document and its content, that we don't track through
|
|
|
|
|
-- history.
|
|
|
|
|
data DocumentMetadata =
|
|
|
|
|
Metadata
|
|
|
|
|
{ documentName :: !Text
|
|
|
|
|
, documentFilePath :: !( Maybe FilePath )
|
|
|
|
|
, viewportCenter :: !( ℝ 2 )
|
|
|
|
|
, documentZoom :: !Zoom
|
|
|
|
|
, documentGuides :: !( Map Unique Guide )
|
|
|
|
|
, layerMetadata :: !LayerMetadata
|
|
|
|
|
, selectedPoints :: !StrokePoints
|
2021-04-26 15:18:48 +00:00
|
|
|
|
}
|
|
|
|
|
deriving stock ( Show, Generic )
|
|
|
|
|
deriving anyclass NFData
|
|
|
|
|
|
2024-09-27 15:21:24 +00:00
|
|
|
|
-- | Main content of document (data which we keep track of throughout history).
|
|
|
|
|
data DocumentContent
|
|
|
|
|
= Content
|
|
|
|
|
{ unsavedChanges :: !Bool
|
|
|
|
|
-- ^ Whether this current content is unsaved.
|
|
|
|
|
, strokeHierarchy :: !StrokeHierarchy
|
|
|
|
|
-- ^ Hierarchical structure of layers and groups.
|
2020-08-13 17:05:19 +00:00
|
|
|
|
}
|
2020-11-12 17:34:46 +00:00
|
|
|
|
deriving stock ( Show, Generic )
|
|
|
|
|
deriving anyclass NFData
|
|
|
|
|
|
2024-09-27 15:21:24 +00:00
|
|
|
|
-- | A guide, i.e. a horizontal or vertical line used for alignment.
|
2020-09-08 23:23:25 +00:00
|
|
|
|
data Guide
|
|
|
|
|
= Guide
|
2023-01-09 03:27:08 +00:00
|
|
|
|
{ guidePoint :: !( ℝ 2 ) -- ^ point on the guide line
|
|
|
|
|
, guideNormal :: !( T ( ℝ 2 ) ) -- ^ /normalised/ normal vector of the guide
|
2020-09-08 23:23:25 +00:00
|
|
|
|
}
|
2020-09-10 16:43:42 +00:00
|
|
|
|
deriving stock ( Show, Generic )
|
|
|
|
|
deriving anyclass NFData
|
2020-09-08 23:23:25 +00:00
|
|
|
|
|
2024-09-27 15:21:24 +00:00
|
|
|
|
emptyDocument :: Text -> Document
|
|
|
|
|
emptyDocument docName =
|
|
|
|
|
Document
|
|
|
|
|
{ documentContent = emptyDocumentContent
|
|
|
|
|
, documentMetadata = emptyDocumentMetadata docName
|
|
|
|
|
}
|
2020-09-08 23:23:25 +00:00
|
|
|
|
|
2024-09-27 15:21:24 +00:00
|
|
|
|
emptyDocumentContent :: DocumentContent
|
|
|
|
|
emptyDocumentContent =
|
|
|
|
|
Content
|
|
|
|
|
{ strokeHierarchy = emptyHierarchy
|
|
|
|
|
, unsavedChanges = False
|
|
|
|
|
}
|
2020-09-08 23:23:25 +00:00
|
|
|
|
|
2024-09-27 15:21:24 +00:00
|
|
|
|
emptyDocumentMetadata :: Text -> DocumentMetadata
|
|
|
|
|
emptyDocumentMetadata docName =
|
|
|
|
|
Metadata
|
|
|
|
|
{ documentName = docName
|
|
|
|
|
, documentFilePath = Nothing
|
|
|
|
|
, viewportCenter = ℝ2 0 0
|
|
|
|
|
, documentZoom = Zoom { zoomFactor = 1 }
|
|
|
|
|
, documentGuides = Map.empty
|
|
|
|
|
, layerMetadata = mempty
|
|
|
|
|
, selectedPoints = mempty
|
|
|
|
|
}
|