mirror of
https://gitlab.com/sheaf/metabrush.git
synced 2024-11-23 15:34:06 +00:00
add hover indicator for points and guides
This commit is contained in:
parent
dc6602bb79
commit
0a978b7c59
|
@ -114,7 +114,6 @@ executable MetaBrush
|
||||||
, MetaBrush.Context
|
, MetaBrush.Context
|
||||||
, MetaBrush.Document
|
, MetaBrush.Document
|
||||||
, MetaBrush.Document.Draw
|
, MetaBrush.Document.Draw
|
||||||
, MetaBrush.Document.Guide
|
|
||||||
, MetaBrush.Document.Selection
|
, MetaBrush.Document.Selection
|
||||||
, MetaBrush.Document.Serialise
|
, MetaBrush.Document.Serialise
|
||||||
, MetaBrush.Document.SubdivideStroke
|
, MetaBrush.Document.SubdivideStroke
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
color: rgb(0,0,0);
|
color: rgb(0,0,0);
|
||||||
}
|
}
|
||||||
.pointHover {
|
.pointHover {
|
||||||
color: rgb(225,225,225);
|
color: rgb(231, 172, 83);
|
||||||
}
|
}
|
||||||
.pointSelected {
|
.pointSelected {
|
||||||
color: rgb(252,237,120);
|
color: rgb(252,237,120);
|
||||||
|
|
|
@ -95,13 +95,11 @@ import MetaBrush.Context
|
||||||
, updateTitle
|
, updateTitle
|
||||||
)
|
)
|
||||||
import MetaBrush.Document
|
import MetaBrush.Document
|
||||||
( Document(..), Guide(..)
|
( Document(..), PointData(..), FocusState(..) )
|
||||||
, PointData(..), FocusState(..)
|
|
||||||
)
|
|
||||||
import MetaBrush.Document.Draw
|
import MetaBrush.Document.Draw
|
||||||
( addToAnchor, getOrCreateDrawAnchor, anchorsAreComplementary )
|
( addToAnchor, getOrCreateDrawAnchor, anchorsAreComplementary )
|
||||||
import MetaBrush.Document.Guide
|
import MetaBrush.Document
|
||||||
( selectGuide, addGuide )
|
( Guide(..), selectedGuide, addGuide )
|
||||||
import MetaBrush.Document.Selection
|
import MetaBrush.Document.Selection
|
||||||
( SelectionMode(..), selectionMode
|
( SelectionMode(..), selectionMode
|
||||||
, selectAt, selectRectangle
|
, selectAt, selectRectangle
|
||||||
|
@ -695,7 +693,7 @@ instance HandleAction MouseClick where
|
||||||
when showGuides do
|
when showGuides do
|
||||||
let
|
let
|
||||||
mbGuide :: Maybe Guide
|
mbGuide :: Maybe Guide
|
||||||
mbGuide = selectGuide pos doc
|
mbGuide = selectedGuide pos doc
|
||||||
guideAction :: GuideAction
|
guideAction :: GuideAction
|
||||||
guideAction
|
guideAction
|
||||||
| Just guide <- mbGuide
|
| Just guide <- mbGuide
|
||||||
|
|
|
@ -3,26 +3,43 @@
|
||||||
{-# LANGUAGE DerivingStrategies #-}
|
{-# LANGUAGE DerivingStrategies #-}
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE TypeApplications #-}
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
|
||||||
module MetaBrush.Document
|
module MetaBrush.Document
|
||||||
( AABB(..)
|
( AABB(..), mkAABB
|
||||||
, Document(..), emptyDocument
|
, Document(..), emptyDocument
|
||||||
, Stroke(..), Guide(..)
|
, Stroke(..)
|
||||||
, PointData(..), BrushPointData(..)
|
, PointData(..), BrushPointData(..)
|
||||||
, FocusState(..)
|
, FocusState(..), Hoverable(..), HoverContext(..)
|
||||||
, _selection, _brush
|
, _selection, _brush
|
||||||
|
, Guide(..)
|
||||||
|
, addGuide, selectedGuide
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
-- base
|
-- base
|
||||||
|
import Data.Semigroup
|
||||||
|
( Arg(..), Min(..), ArgMin )
|
||||||
import GHC.Generics
|
import GHC.Generics
|
||||||
( Generic )
|
( Generic )
|
||||||
|
|
||||||
|
-- acts
|
||||||
|
import Data.Act
|
||||||
|
( Act
|
||||||
|
( (•) )
|
||||||
|
, Torsor
|
||||||
|
( (-->) )
|
||||||
|
)
|
||||||
|
|
||||||
-- containers
|
-- containers
|
||||||
import Data.Map.Strict
|
import Data.Map.Strict
|
||||||
( Map )
|
( Map )
|
||||||
|
import qualified Data.Map.Strict as Map
|
||||||
|
( insert )
|
||||||
import qualified Data.Map.Strict as Map
|
import qualified Data.Map.Strict as Map
|
||||||
( empty )
|
( empty )
|
||||||
import Data.Sequence
|
import Data.Sequence
|
||||||
|
@ -38,6 +55,10 @@ import Data.Generics.Product.Typed
|
||||||
import Control.Lens
|
import Control.Lens
|
||||||
( Lens' )
|
( Lens' )
|
||||||
|
|
||||||
|
-- stm
|
||||||
|
import Control.Concurrent.STM
|
||||||
|
( STM )
|
||||||
|
|
||||||
-- text
|
-- text
|
||||||
import Data.Text
|
import Data.Text
|
||||||
( Text )
|
( Text )
|
||||||
|
@ -45,10 +66,14 @@ import Data.Text
|
||||||
-- MetaBrush
|
-- MetaBrush
|
||||||
import Math.Bezier.Stroke
|
import Math.Bezier.Stroke
|
||||||
( StrokePoint(..) )
|
( StrokePoint(..) )
|
||||||
|
import Math.Module
|
||||||
|
( Inner((^.^)), squaredNorm, quadrance )
|
||||||
import Math.Vector2D
|
import Math.Vector2D
|
||||||
( Point2D(..), Vector2D(..) )
|
( Point2D(..), Vector2D(..) )
|
||||||
|
import MetaBrush.UI.Viewport
|
||||||
|
( Ruler(..) )
|
||||||
import MetaBrush.Unique
|
import MetaBrush.Unique
|
||||||
( Unique )
|
( UniqueSupply, Unique, freshUnique )
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -57,6 +82,16 @@ data AABB
|
||||||
{ topLeft, botRight :: !( Point2D Double ) }
|
{ topLeft, botRight :: !( Point2D Double ) }
|
||||||
deriving stock Show
|
deriving stock Show
|
||||||
|
|
||||||
|
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 )
|
||||||
|
|
||||||
data Document
|
data Document
|
||||||
= Document
|
= Document
|
||||||
{ displayName :: !Text
|
{ displayName :: !Text
|
||||||
|
@ -79,14 +114,6 @@ data Stroke
|
||||||
}
|
}
|
||||||
deriving stock ( Show, Generic )
|
deriving stock ( Show, Generic )
|
||||||
|
|
||||||
data Guide
|
|
||||||
= Guide
|
|
||||||
{ guidePoint :: !( Point2D Double ) -- ^ point on the guide line
|
|
||||||
, guideNormal :: !( Vector2D Double ) -- ^ /normalised/ normal vector of the guide
|
|
||||||
, guideUnique :: Unique
|
|
||||||
}
|
|
||||||
deriving stock ( Show, Generic )
|
|
||||||
|
|
||||||
data PointData
|
data PointData
|
||||||
= PointData
|
= PointData
|
||||||
{ pointState :: FocusState
|
{ pointState :: FocusState
|
||||||
|
@ -105,6 +132,15 @@ data FocusState
|
||||||
| Selected
|
| Selected
|
||||||
deriving stock ( Show, Eq )
|
deriving stock ( Show, Eq )
|
||||||
|
|
||||||
|
instance Semigroup FocusState where
|
||||||
|
Selected <> _ = Selected
|
||||||
|
Normal <> s = s
|
||||||
|
_ <> Selected = Selected
|
||||||
|
s <> Normal = s
|
||||||
|
_ <> _ = Hover
|
||||||
|
instance Monoid FocusState where
|
||||||
|
mempty = Normal
|
||||||
|
|
||||||
_selection :: HasType FocusState pt => Lens' ( StrokePoint pt ) FocusState
|
_selection :: HasType FocusState pt => Lens' ( StrokePoint pt ) FocusState
|
||||||
_selection = field' @"pointData" . typed @FocusState
|
_selection = field' @"pointData" . typed @FocusState
|
||||||
|
|
||||||
|
@ -123,3 +159,100 @@ emptyDocument docName unique =
|
||||||
, strokes = []
|
, strokes = []
|
||||||
, guides = Map.empty
|
, guides = Map.empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
{-# LANGUAGE DataKinds #-}
|
|
||||||
{-# LANGUAGE LambdaCase #-}
|
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
|
||||||
{-# LANGUAGE TypeApplications #-}
|
|
||||||
|
|
||||||
module MetaBrush.Document.Guide
|
|
||||||
( selectGuide, addGuide )
|
|
||||||
where
|
|
||||||
|
|
||||||
-- base
|
|
||||||
import Data.Semigroup
|
|
||||||
( Arg(..), Min(..), ArgMin )
|
|
||||||
|
|
||||||
-- acts
|
|
||||||
import Data.Act
|
|
||||||
( Torsor((-->)) )
|
|
||||||
|
|
||||||
-- containers
|
|
||||||
import Data.Map.Strict
|
|
||||||
( Map )
|
|
||||||
import qualified Data.Map.Strict as Map
|
|
||||||
( insert )
|
|
||||||
|
|
||||||
-- generic-lens
|
|
||||||
import Data.Generics.Product.Fields
|
|
||||||
( field' )
|
|
||||||
|
|
||||||
-- stm
|
|
||||||
import Control.Concurrent.STM
|
|
||||||
( STM )
|
|
||||||
|
|
||||||
-- MetaBrush
|
|
||||||
import Math.Module
|
|
||||||
( Inner((^.^)), squaredNorm )
|
|
||||||
import Math.Vector2D
|
|
||||||
( Point2D(..), Vector2D(..) )
|
|
||||||
import MetaBrush.Document
|
|
||||||
( Document(..), Guide(..)
|
|
||||||
)
|
|
||||||
import MetaBrush.UI.Viewport
|
|
||||||
( Ruler(..) )
|
|
||||||
import MetaBrush.Unique
|
|
||||||
( UniqueSupply, Unique, freshUnique )
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
-- | Try to select a guide at the given document coordinates.
|
|
||||||
selectGuide :: Point2D Double -> Document -> Maybe Guide
|
|
||||||
selectGuide c ( Document { zoomFactor, guides } ) = \case { Min ( Arg _ g ) -> g } <$> foldMap f guides
|
|
||||||
where
|
|
||||||
f :: Guide -> Maybe ( ArgMin Double Guide )
|
|
||||||
f guide@( Guide { guidePoint = p, guideNormal = n } )
|
|
||||||
| sqDist * zoomFactor ^ ( 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, guideUnique = uniq1 }
|
|
||||||
guide2 = Guide { guidePoint = p, guideNormal = Vector2D 1 0, 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, guideUnique = uniq1 }
|
|
||||||
pure ( Map.insert uniq1 guide1 gs )
|
|
||||||
LeftRuler
|
|
||||||
-> do
|
|
||||||
uniq2 <- freshUnique uniqueSupply
|
|
||||||
let
|
|
||||||
guide2 :: Guide
|
|
||||||
guide2 = Guide { guidePoint = p, guideNormal = Vector2D 1 0, guideUnique = uniq2 }
|
|
||||||
pure ( Map.insert uniq2 guide2 gs )
|
|
|
@ -120,8 +120,8 @@ import Math.Vector2D
|
||||||
( Point2D(..), Vector2D(..) )
|
( Point2D(..), Vector2D(..) )
|
||||||
import MetaBrush.Document
|
import MetaBrush.Document
|
||||||
( Document(..)
|
( Document(..)
|
||||||
, Stroke(..)
|
|
||||||
, Guide(..)
|
, Guide(..)
|
||||||
|
, Stroke(..)
|
||||||
, PointData(..)
|
, PointData(..)
|
||||||
, BrushPointData(..)
|
, BrushPointData(..)
|
||||||
, FocusState(..)
|
, FocusState(..)
|
||||||
|
@ -336,16 +336,18 @@ decodeStroke uniqueSupply = do
|
||||||
|
|
||||||
|
|
||||||
encodeGuide :: Applicative f => JSON.Encoder f Guide
|
encodeGuide :: Applicative f => JSON.Encoder f Guide
|
||||||
encodeGuide = JSON.Encoder.mapLikeObj \ ( Guide { guidePoint, guideNormal } ) ->
|
encodeGuide = JSON.Encoder.mapLikeObj \ ( Guide { guidePoint, guideNormal, guideFocus } ) ->
|
||||||
JSON.Encoder.atKey' "point" ( encodePoint2D encodeDouble ) guidePoint
|
JSON.Encoder.atKey' "point" ( encodePoint2D encodeDouble ) guidePoint
|
||||||
. JSON.Encoder.atKey' "normal" ( encodeVector2D encodeDouble ) guideNormal
|
. JSON.Encoder.atKey' "normal" ( encodeVector2D encodeDouble ) guideNormal
|
||||||
|
. JSON.Encoder.atKey' "focus" encodeFocusState guideFocus
|
||||||
|
|
||||||
decodeGuide :: MonadIO m => UniqueSupply -> JSON.Decoder m Guide
|
decodeGuide :: MonadIO m => UniqueSupply -> JSON.Decoder m Guide
|
||||||
decodeGuide uniqueSupply = do
|
decodeGuide uniqueSupply = do
|
||||||
guidePoint <- JSON.Decoder.atKey "point" ( decodePoint2D decodeDouble )
|
guidePoint <- JSON.Decoder.atKey "point" ( decodePoint2D decodeDouble )
|
||||||
guideNormal <- JSON.Decoder.atKey "normal" ( decodeVector2D decodeDouble )
|
guideNormal <- JSON.Decoder.atKey "normal" ( decodeVector2D decodeDouble )
|
||||||
|
guideFocus <- JSON.Decoder.atKey "focus" decodeFocusState
|
||||||
guideUnique <- lift ( liftIO . STM.atomically $ freshUnique uniqueSupply )
|
guideUnique <- lift ( liftIO . STM.atomically $ freshUnique uniqueSupply )
|
||||||
pure ( Guide { guidePoint, guideNormal, guideUnique } )
|
pure ( Guide { guidePoint, guideNormal, guideFocus, guideUnique } )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
{-# LANGUAGE BlockArguments #-}
|
{-# LANGUAGE BlockArguments #-}
|
||||||
{-# LANGUAGE DataKinds #-}
|
{-# LANGUAGE DataKinds #-}
|
||||||
{-# LANGUAGE DeriveTraversable #-}
|
{-# LANGUAGE DeriveTraversable #-}
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
{-# LANGUAGE DerivingVia #-}
|
{-# LANGUAGE DerivingVia #-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE NegativeLiterals #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE NegativeLiterals #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
{-# LANGUAGE TypeApplications #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
|
||||||
module MetaBrush.Render.Document
|
module MetaBrush.Render.Document
|
||||||
( renderDocument, blankRender )
|
( renderDocument, blankRender )
|
||||||
|
@ -74,8 +75,9 @@ import MetaBrush.Asset.Colours
|
||||||
import MetaBrush.Context
|
import MetaBrush.Context
|
||||||
( HoldAction(..), PartialPath(..) )
|
( HoldAction(..), PartialPath(..) )
|
||||||
import MetaBrush.Document
|
import MetaBrush.Document
|
||||||
( Document(..)
|
( Document(..), mkAABB
|
||||||
, Stroke(..), FocusState(..)
|
, Stroke(..), FocusState(..)
|
||||||
|
, HoverContext(..), Hoverable(..)
|
||||||
, PointData(..), BrushPointData(..)
|
, PointData(..), BrushPointData(..)
|
||||||
, _selection
|
, _selection
|
||||||
)
|
)
|
||||||
|
@ -91,7 +93,7 @@ import MetaBrush.Util
|
||||||
data Renders a
|
data Renders a
|
||||||
= Renders
|
= Renders
|
||||||
{ renderPath, renderStrokes, renderBrushes
|
{ renderPath, renderStrokes, renderBrushes
|
||||||
, renderCPts, renderCLines, renderPPts :: a
|
, renderCLines, renderCPts, renderPPts :: a
|
||||||
}
|
}
|
||||||
deriving stock ( Show, Functor, Foldable, Traversable, Generic, Generic1 )
|
deriving stock ( Show, Functor, Foldable, Traversable, Generic, Generic1 )
|
||||||
deriving Applicative
|
deriving Applicative
|
||||||
|
@ -129,12 +131,13 @@ renderDocument
|
||||||
|
|
||||||
let
|
let
|
||||||
renderSelectionRect :: Cairo.Render ()
|
renderSelectionRect :: Cairo.Render ()
|
||||||
renderSelectionRect
|
mbHoverContext :: Maybe HoverContext
|
||||||
|
( renderSelectionRect, mbHoverContext )
|
||||||
| Just ( SelectionHold p0 ) <- mbHoldEvent
|
| Just ( SelectionHold p0 ) <- mbHoldEvent
|
||||||
, Just p1 <- mbMousePos
|
, Just p1 <- mbMousePos
|
||||||
= drawSelectionRectangle cols zoomFactor p0 p1
|
= ( drawSelectionRectangle cols zoomFactor p0 p1, Just $ RectangleHover ( mkAABB p0 p1 ) )
|
||||||
| otherwise
|
| otherwise
|
||||||
= pure ()
|
= ( pure (), MouseHover <$> mbMousePos )
|
||||||
|
|
||||||
modifiedStrokes :: [ Stroke ]
|
modifiedStrokes :: [ Stroke ]
|
||||||
modifiedStrokes
|
modifiedStrokes
|
||||||
|
@ -173,18 +176,18 @@ renderDocument
|
||||||
| otherwise
|
| otherwise
|
||||||
= strokes doc
|
= strokes doc
|
||||||
|
|
||||||
for_ modifiedStrokes ( compositeRenders . getCompose . renderStroke cols params mode zoomFactor )
|
for_ modifiedStrokes ( compositeRenders . getCompose . renderStroke cols mbHoverContext params mode zoomFactor )
|
||||||
renderSelectionRect
|
renderSelectionRect
|
||||||
|
|
||||||
Cairo.restore
|
Cairo.restore
|
||||||
|
|
||||||
pure ()
|
pure ()
|
||||||
|
|
||||||
renderStroke :: Colours -> FitParameters -> Mode -> Double -> Stroke -> Compose Renders Cairo.Render ()
|
renderStroke :: Colours -> Maybe HoverContext -> FitParameters -> Mode -> Double -> Stroke -> Compose Renders Cairo.Render ()
|
||||||
renderStroke cols@( Colours { brush } ) params mode zoom ( Stroke { strokePoints = pts, strokeVisible } )
|
renderStroke cols@( Colours { brush } ) mbHoverContext params mode zoom ( Stroke { strokePoints = pts, strokeVisible } )
|
||||||
| strokeVisible
|
| strokeVisible
|
||||||
= renderStrokePoints cols mode zoom
|
= renderStrokePoints cols mode mbHoverContext zoom
|
||||||
( when ( mode == Brush ) . renderBrushShape ( cols { path = brush } ) ( 1.5 * zoom ) )
|
( when ( mode == Brush ) . renderBrushShape ( cols { path = brush } ) mbHoverContext ( 1.5 * zoom ) )
|
||||||
pts
|
pts
|
||||||
*> Compose blank { renderStrokes = drawStroke cols ( stroke params pts ) }
|
*> Compose blank { renderStrokes = drawStroke cols ( stroke params pts ) }
|
||||||
| otherwise
|
| otherwise
|
||||||
|
@ -197,13 +200,13 @@ renderStroke cols@( Colours { brush } ) params mode zoom ( Stroke { strokePoints
|
||||||
renderStrokePoints
|
renderStrokePoints
|
||||||
:: forall d
|
:: forall d
|
||||||
. ( Show d, HasType FocusState d )
|
. ( Show d, HasType FocusState d )
|
||||||
=> Colours -> Mode -> Double
|
=> Colours -> Mode -> Maybe HoverContext -> Double
|
||||||
-> ( StrokePoint d -> Compose Renders Cairo.Render () )
|
-> ( StrokePoint d -> Compose Renders Cairo.Render () )
|
||||||
-> Seq ( StrokePoint d )
|
-> Seq ( StrokePoint d )
|
||||||
-> Compose Renders Cairo.Render ()
|
-> Compose Renders Cairo.Render ()
|
||||||
renderStrokePoints _ _ _ _ Empty = pure ()
|
renderStrokePoints _ _ _ _ _ Empty = pure ()
|
||||||
renderStrokePoints cols mode zoom renderSubcontent ( pt0 :<| pts ) =
|
renderStrokePoints cols mode mbHover zoom renderSubcontent ( pt0 :<| pts ) =
|
||||||
Compose blank { renderPPts = when ( mode == Path ) $ drawPoint cols zoom pt0 }
|
Compose blank { renderPPts = when ( mode == Path ) $ drawPoint cols mbHover zoom pt0 }
|
||||||
*> renderSubcontent pt0
|
*> renderSubcontent pt0
|
||||||
*> go pt0 pts
|
*> go pt0 pts
|
||||||
where
|
where
|
||||||
|
@ -215,7 +218,7 @@ renderStrokePoints cols mode zoom renderSubcontent ( pt0 :<| pts ) =
|
||||||
| PathPoint {} <- p1
|
| PathPoint {} <- p1
|
||||||
= Compose blank
|
= Compose blank
|
||||||
{ renderPPts
|
{ renderPPts
|
||||||
= when ( mode == Path ) $ drawPoint cols zoom p1
|
= when ( mode == Path ) $ drawPoint cols mbHover zoom p1
|
||||||
, renderPath
|
, renderPath
|
||||||
= unless ( mode == Meta ) $ drawLine cols zoom p0 p1
|
= unless ( mode == Meta ) $ drawLine cols zoom p0 p1
|
||||||
}
|
}
|
||||||
|
@ -231,9 +234,9 @@ renderStrokePoints cols mode zoom renderSubcontent ( pt0 :<| pts ) =
|
||||||
drawLine cols zoom p0 p1
|
drawLine cols zoom p0 p1
|
||||||
drawLine cols zoom p1 p2
|
drawLine cols zoom p1 p2
|
||||||
, renderCPts
|
, renderCPts
|
||||||
= when ( mode == Path ) $ drawPoint cols zoom p1
|
= when ( mode == Path ) $ drawPoint cols mbHover zoom p1
|
||||||
, renderPPts
|
, renderPPts
|
||||||
= when ( mode == Path ) $ drawPoint cols zoom p2
|
= when ( mode == Path ) $ drawPoint cols mbHover zoom p2
|
||||||
, renderPath
|
, renderPath
|
||||||
= unless ( mode == Meta ) $ drawQuadraticBezier cols zoom ( coords <$> Quadratic.Bezier { p0, p1, p2 } )
|
= unless ( mode == Meta ) $ drawQuadraticBezier cols zoom ( coords <$> Quadratic.Bezier { p0, p1, p2 } )
|
||||||
}
|
}
|
||||||
|
@ -252,10 +255,10 @@ renderStrokePoints cols mode zoom renderSubcontent ( pt0 :<| pts ) =
|
||||||
drawLine cols zoom p2 p3
|
drawLine cols zoom p2 p3
|
||||||
, renderCPts
|
, renderCPts
|
||||||
= when ( mode == Path ) do
|
= when ( mode == Path ) do
|
||||||
drawPoint cols zoom p1
|
drawPoint cols mbHover zoom p1
|
||||||
drawPoint cols zoom p2
|
drawPoint cols mbHover zoom p2
|
||||||
, renderPPts
|
, renderPPts
|
||||||
= when ( mode == Path ) $ drawPoint cols zoom p3
|
= when ( mode == Path ) $ drawPoint cols mbHover zoom p3
|
||||||
, renderPath
|
, renderPath
|
||||||
= unless ( mode == Meta ) $ drawCubicBezier cols zoom ( coords <$> Cubic.Bezier { p0, p1, p2, p3 } )
|
= unless ( mode == Meta ) $ drawCubicBezier cols zoom ( coords <$> Cubic.Bezier { p0, p1, p2, p3 } )
|
||||||
}
|
}
|
||||||
|
@ -265,29 +268,31 @@ renderStrokePoints cols mode zoom renderSubcontent ( pt0 :<| pts ) =
|
||||||
*> go p3 ps
|
*> go p3 ps
|
||||||
go p0 ps = error $ "renderStroke: unrecognised stroke type\n" <> show ( p0 :<| ps )
|
go p0 ps = error $ "renderStroke: unrecognised stroke type\n" <> show ( p0 :<| ps )
|
||||||
|
|
||||||
renderBrushShape :: Colours -> Double -> StrokePoint PointData -> Compose Renders Cairo.Render ()
|
renderBrushShape :: Colours -> Maybe HoverContext -> Double -> StrokePoint PointData -> Compose Renders Cairo.Render ()
|
||||||
renderBrushShape cols zoom pt =
|
renderBrushShape cols mbHoverContext zoom pt =
|
||||||
let
|
let
|
||||||
x, y :: Double
|
x, y :: Double
|
||||||
Point2D x y = coords pt
|
Point2D x y = coords pt
|
||||||
brushPts :: Seq ( StrokePoint BrushPointData )
|
brushPts :: Seq ( StrokePoint BrushPointData )
|
||||||
brushPts = brushShape ( pointData pt )
|
brushPts = brushShape ( pointData pt )
|
||||||
|
mbHoverContext' :: Maybe HoverContext
|
||||||
|
mbHoverContext' = Vector2D (-x) (-y) • mbHoverContext
|
||||||
in
|
in
|
||||||
toAll do
|
toAll do
|
||||||
Cairo.save
|
Cairo.save
|
||||||
Cairo.translate x y
|
Cairo.translate x y
|
||||||
*> renderStrokePoints cols Path zoom ( const $ pure () ) brushPts
|
*> renderStrokePoints cols Path mbHoverContext' zoom ( const $ pure () ) brushPts
|
||||||
*> Compose blank { renderPPts = drawCross cols zoom }
|
*> Compose blank { renderPPts = drawCross cols zoom }
|
||||||
*> toAll Cairo.restore
|
*> toAll Cairo.restore
|
||||||
|
|
||||||
drawPoint :: HasType FocusState d => Colours -> Double -> StrokePoint d -> Cairo.Render ()
|
drawPoint :: HasType FocusState d => Colours -> Maybe HoverContext -> Double -> StrokePoint d -> Cairo.Render ()
|
||||||
drawPoint ( Colours {..} ) zoom pt@( PathPoint { coords = Point2D x y } )
|
drawPoint ( Colours {..} ) mbHover zoom pt@( PathPoint { coords = Point2D x y } )
|
||||||
= do
|
= do
|
||||||
let
|
let
|
||||||
hsqrt3 :: Double
|
hsqrt3 :: Double
|
||||||
hsqrt3 = sqrt 0.75
|
hsqrt3 = sqrt 0.75
|
||||||
selectionState :: FocusState
|
selectionState :: FocusState
|
||||||
selectionState = view _selection pt
|
selectionState = view _selection pt <> hovered mbHover zoom ( Point2D x y )
|
||||||
|
|
||||||
Cairo.save
|
Cairo.save
|
||||||
Cairo.translate x y
|
Cairo.translate x y
|
||||||
|
@ -303,8 +308,8 @@ drawPoint ( Colours {..} ) zoom pt@( PathPoint { coords = Point2D x y } )
|
||||||
|
|
||||||
Cairo.setLineWidth 1.0
|
Cairo.setLineWidth 1.0
|
||||||
case selectionState of
|
case selectionState of
|
||||||
Normal -> withRGBA pathPointOutline Cairo.setSourceRGBA
|
Selected -> withRGBA pathPoint Cairo.setSourceRGBA
|
||||||
_ -> withRGBA pathPoint Cairo.setSourceRGBA
|
_ -> withRGBA pathPointOutline Cairo.setSourceRGBA
|
||||||
Cairo.strokePreserve
|
Cairo.strokePreserve
|
||||||
|
|
||||||
case selectionState of
|
case selectionState of
|
||||||
|
@ -315,11 +320,11 @@ drawPoint ( Colours {..} ) zoom pt@( PathPoint { coords = Point2D x y } )
|
||||||
|
|
||||||
Cairo.restore
|
Cairo.restore
|
||||||
|
|
||||||
drawPoint ( Colours {..} ) zoom pt@( ControlPoint { coords = Point2D x y } )
|
drawPoint ( Colours {..} ) mbHover zoom pt@( ControlPoint { coords = Point2D x y } )
|
||||||
= do
|
= do
|
||||||
let
|
let
|
||||||
selectionState :: FocusState
|
selectionState :: FocusState
|
||||||
selectionState = view _selection pt
|
selectionState = view _selection pt <> hovered mbHover zoom ( Point2D x y )
|
||||||
|
|
||||||
Cairo.save
|
Cairo.save
|
||||||
Cairo.translate x y
|
Cairo.translate x y
|
||||||
|
@ -329,8 +334,8 @@ drawPoint ( Colours {..} ) zoom pt@( ControlPoint { coords = Point2D x y } )
|
||||||
|
|
||||||
Cairo.setLineWidth 1.0
|
Cairo.setLineWidth 1.0
|
||||||
case selectionState of
|
case selectionState of
|
||||||
Normal -> withRGBA controlPointOutline Cairo.setSourceRGBA
|
Selected -> withRGBA controlPoint Cairo.setSourceRGBA
|
||||||
_ -> withRGBA controlPoint Cairo.setSourceRGBA
|
_ -> withRGBA controlPointOutline Cairo.setSourceRGBA
|
||||||
Cairo.strokePreserve
|
Cairo.strokePreserve
|
||||||
|
|
||||||
case selectionState of
|
case selectionState of
|
||||||
|
|
|
@ -48,7 +48,7 @@ import qualified GI.Cairo.Render as Cairo
|
||||||
|
|
||||||
-- lens
|
-- lens
|
||||||
import Control.Lens
|
import Control.Lens
|
||||||
( over )
|
( set, over )
|
||||||
|
|
||||||
-- MetaBrush
|
-- MetaBrush
|
||||||
import Math.Vector2D
|
import Math.Vector2D
|
||||||
|
@ -60,11 +60,15 @@ import MetaBrush.Asset.Colours
|
||||||
import MetaBrush.Context
|
import MetaBrush.Context
|
||||||
( HoldAction(..), GuideAction(..) )
|
( HoldAction(..), GuideAction(..) )
|
||||||
import MetaBrush.Document
|
import MetaBrush.Document
|
||||||
( Document(..), Guide(..) )
|
( Document(..), FocusState(..), Hoverable(..), HoverContext(..)
|
||||||
|
, Guide(..)
|
||||||
|
)
|
||||||
import MetaBrush.UI.Coordinates
|
import MetaBrush.UI.Coordinates
|
||||||
( toViewportCoordinates )
|
( toViewportCoordinates )
|
||||||
import MetaBrush.UI.Viewport
|
import MetaBrush.UI.Viewport
|
||||||
( Ruler(..) )
|
( Ruler(..) )
|
||||||
|
import MetaBrush.Unique
|
||||||
|
( unsafeUnique )
|
||||||
import MetaBrush.Util
|
import MetaBrush.Util
|
||||||
( withRGBA )
|
( withRGBA )
|
||||||
|
|
||||||
|
@ -91,25 +95,37 @@ renderRuler
|
||||||
let
|
let
|
||||||
translate :: Point2D Double -> Point2D Double
|
translate :: Point2D Double -> Point2D Double
|
||||||
translate = ( ( mousePos0 --> mousePos :: Vector2D Double ) • )
|
translate = ( ( mousePos0 --> mousePos :: Vector2D Double ) • )
|
||||||
in toList ( Map.adjust ( over ( field' @"guidePoint" ) translate ) guideUnique guides )
|
in toList
|
||||||
|
$ Map.adjust
|
||||||
|
( over ( field' @"guidePoint" ) translate . set ( field' @"guideFocus" ) Selected )
|
||||||
|
guideUnique
|
||||||
|
guides
|
||||||
CreateGuide ruler
|
CreateGuide ruler
|
||||||
-> let
|
-> let
|
||||||
addNewGuides :: [ Guide ] -> [ Guide ]
|
addNewGuides :: [ Guide ] -> [ Guide ]
|
||||||
addNewGuides gs = case ruler of
|
addNewGuides gs = case ruler of
|
||||||
RulerCorner
|
RulerCorner
|
||||||
-> Guide { guidePoint = mousePos, guideNormal = Vector2D 0 1, guideUnique = undefined }
|
-> Guide { guidePoint = mousePos, guideNormal = Vector2D 0 1, guideFocus = Selected, guideUnique = unsafeUnique 0 }
|
||||||
: Guide { guidePoint = mousePos, guideNormal = Vector2D 1 0, guideUnique = undefined }
|
: Guide { guidePoint = mousePos, guideNormal = Vector2D 1 0, guideFocus = Selected, guideUnique = unsafeUnique 1 }
|
||||||
: gs
|
: gs
|
||||||
LeftRuler
|
LeftRuler
|
||||||
-> Guide { guidePoint = mousePos, guideNormal = Vector2D 1 0, guideUnique = undefined }
|
-> Guide { guidePoint = mousePos, guideNormal = Vector2D 1 0, guideFocus = Selected, guideUnique = unsafeUnique 2 }
|
||||||
: gs
|
: gs
|
||||||
TopRuler
|
TopRuler
|
||||||
-> Guide { guidePoint = mousePos, guideNormal = Vector2D 0 1, guideUnique = undefined }
|
-> Guide { guidePoint = mousePos, guideNormal = Vector2D 0 1, guideFocus = Selected, guideUnique = unsafeUnique 3 }
|
||||||
: gs
|
: gs
|
||||||
in addNewGuides ( toList guides )
|
in addNewGuides ( toList guides )
|
||||||
| otherwise
|
| otherwise
|
||||||
= toList guides
|
= toList guides
|
||||||
|
|
||||||
|
mbHoverContext :: Maybe HoverContext
|
||||||
|
mbHoverContext
|
||||||
|
| Just mp@( Point2D x y ) <- mbMousePos
|
||||||
|
, x <= left || y <= top -- only hover guides from within ruler area
|
||||||
|
= Just ( MouseHover mp )
|
||||||
|
| otherwise
|
||||||
|
= Nothing
|
||||||
|
|
||||||
Cairo.save
|
Cairo.save
|
||||||
Cairo.translate ( 0.5 + 0.5 * fromIntegral viewportWidth ) ( 0.5 + 0.5 * fromIntegral viewportHeight )
|
Cairo.translate ( 0.5 + 0.5 * fromIntegral viewportWidth ) ( 0.5 + 0.5 * fromIntegral viewportHeight )
|
||||||
additionalAdjustment
|
additionalAdjustment
|
||||||
|
@ -119,7 +135,7 @@ renderRuler
|
||||||
-- Render tick marks.
|
-- Render tick marks.
|
||||||
renderTicks
|
renderTicks
|
||||||
-- Render guides.
|
-- Render guides.
|
||||||
when showGuides ( for_ modifiedGuides ( renderGuide cols zoomFactor ) )
|
when showGuides ( for_ modifiedGuides ( renderGuide cols mbHoverContext zoomFactor ) )
|
||||||
-- Render mouse cursor indicator.
|
-- Render mouse cursor indicator.
|
||||||
for_ mbMousePos \ ( Point2D mx my ) ->
|
for_ mbMousePos \ ( Point2D mx my ) ->
|
||||||
case actionOrigin of
|
case actionOrigin of
|
||||||
|
@ -254,8 +270,8 @@ renderRuler
|
||||||
Cairo.stroke
|
Cairo.stroke
|
||||||
when tickHasLabel do
|
when tickHasLabel do
|
||||||
if tickPosition < 0
|
if tickPosition < 0
|
||||||
then Cairo.translate -14 8
|
then Cairo.translate -14.5 6.5
|
||||||
else Cairo.translate -14 12
|
else Cairo.translate -14.5 10.5
|
||||||
for_ ( show $ round @_ @Int tickPosition ) \ char -> do
|
for_ ( show $ round @_ @Int tickPosition ) \ char -> do
|
||||||
Cairo.moveTo 0 0
|
Cairo.moveTo 0 0
|
||||||
Cairo.showText [char]
|
Cairo.showText [char]
|
||||||
|
@ -270,21 +286,25 @@ data Tick
|
||||||
}
|
}
|
||||||
deriving stock Show
|
deriving stock Show
|
||||||
|
|
||||||
renderGuide :: Colours -> Double -> Guide -> Cairo.Render ()
|
renderGuide :: Colours -> Maybe HoverContext -> Double -> Guide -> Cairo.Render ()
|
||||||
renderGuide ( Colours {..} ) zoom ( Guide { guidePoint = Point2D x y, guideNormal = Vector2D nx ny } ) = do
|
renderGuide ( Colours {..} ) mbHoverContext zoom
|
||||||
|
gd@( Guide { guidePoint = Point2D x y, guideNormal = Vector2D nx ny, guideFocus } )
|
||||||
|
= do
|
||||||
|
|
||||||
Cairo.save
|
Cairo.save
|
||||||
Cairo.translate x y
|
Cairo.translate x y
|
||||||
Cairo.scale ( 1 / zoom ) ( 1 / zoom )
|
Cairo.scale ( 1 / zoom ) ( 1 / zoom )
|
||||||
|
|
||||||
Cairo.setLineWidth 1
|
Cairo.setLineWidth 1
|
||||||
withRGBA guide Cairo.setSourceRGBA
|
case guideFocus <> hovered mbHoverContext zoom gd of
|
||||||
|
Normal -> withRGBA guide Cairo.setSourceRGBA
|
||||||
|
_ -> withRGBA pointHover Cairo.setSourceRGBA
|
||||||
|
|
||||||
Cairo.moveTo ( 1e5 * ny ) ( -1e5 * nx )
|
Cairo.moveTo ( 1e5 * ny ) ( -1e5 * nx )
|
||||||
Cairo.lineTo ( -1e5 * ny ) ( 1e5 * nx )
|
Cairo.lineTo ( -1e5 * ny ) ( 1e5 * nx )
|
||||||
Cairo.stroke
|
Cairo.stroke
|
||||||
|
|
||||||
Cairo.restore
|
Cairo.restore
|
||||||
|
|
||||||
tickSpacing :: Double -> ( Double, [ Int ] )
|
tickSpacing :: Double -> ( Double, [ Int ] )
|
||||||
tickSpacing d
|
tickSpacing d
|
||||||
|
|
Loading…
Reference in a new issue