start implementing brush mode

This commit is contained in:
sheaf 2020-08-20 03:57:26 +02:00
parent e8c77befd6
commit 86a1426136
10 changed files with 323 additions and 145 deletions

View file

@ -32,6 +32,8 @@ common common
^>= 0.3.1.0 ^>= 0.3.1.0
, containers , containers
>= 0.6.0.1 && < 0.6.4 >= 0.6.0.1 && < 0.6.4
, generic-data
>= 0.8.0.0 && < 0.8.4.0
, groups , groups
^>= 0.4.1.0 ^>= 0.4.1.0
@ -69,9 +71,7 @@ library
, Math.Vector2D , Math.Vector2D
build-depends: build-depends:
generic-data groups-generic
>= 0.8.0.0 && < 0.8.4.0
, groups-generic
^>= 0.1.0.0 ^>= 0.1.0.0
executable MetaBrush executable MetaBrush

View file

@ -1,6 +1,8 @@
{-# LANGUAGE BlockArguments #-} {-# LANGUAGE BlockArguments #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NegativeLiterals #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
module Main module Main
@ -62,7 +64,8 @@ import MetaBrush.Asset.Logo
( drawLogo ) ( drawLogo )
import MetaBrush.Document import MetaBrush.Document
( Document(..), AABB(..), Stroke(..) ( Document(..), AABB(..), Stroke(..)
, PointData(..), FocusState(..) , FocusState(..)
, PointData(..), BrushPointData(..)
, currentDocument , currentDocument
) )
import MetaBrush.Event import MetaBrush.Event
@ -99,7 +102,8 @@ testDocuments = IntMap.fromList
{ displayName = "Document 1" { displayName = "Document 1"
, filePath = Nothing , filePath = Nothing
, unsavedChanges = False , unsavedChanges = False
, strokes = [ Stroke circle "Circle" True ( unsafeUnique 0 ) ] , strokes = [ Stroke ( circle ( PointData Normal ( razor $ BrushPointData Normal ) ) ) "Circle" True ( unsafeUnique 0 )
]
, bounds = AABB ( Point2D 0 0 ) ( Point2D 100 100 ) , bounds = AABB ( Point2D 0 0 ) ( Point2D 100 100 )
, viewportCenter = Point2D 50 50 , viewportCenter = Point2D 50 50
, zoomFactor = 1 , zoomFactor = 1
@ -115,8 +119,8 @@ testDocuments = IntMap.fromList
} }
] ]
circle :: Seq ( StrokePoint PointData ) circle :: forall a. a -> Seq ( StrokePoint a)
circle = Seq.fromList circle d = Seq.fromList
[ pp ( Point2D 0 1 ) [ pp ( Point2D 0 1 )
, cp ( Point2D a 1 ) , cp ( Point2D a 1 )
, cp ( Point2D 1 a ) , cp ( Point2D 1 a )
@ -134,9 +138,24 @@ circle = Seq.fromList
where where
a :: Double a :: Double
a = 0.551915024494 a = 0.551915024494
pp, cp :: Point2D Double -> StrokePoint PointData pp, cp :: Point2D Double -> StrokePoint a
pp p = PathPoint ( fmap ( * 100 ) p ) ( PointData Normal Empty ) pp p = PathPoint ( fmap ( * 100 ) p ) d
cp p = ControlPoint ( fmap ( * 100 ) p ) ( PointData Normal Empty ) cp p = ControlPoint ( fmap ( * 100 ) p ) d
razor :: forall a. a -> Seq ( StrokePoint a )
razor d = Seq.fromList
[ pp ( Point2D 10 0 )
, cp ( Point2D 10 -3 )
, cp ( Point2D -10 -3 )
, pp ( Point2D -10 0 )
, cp ( Point2D -10 3 )
, cp ( Point2D 10 3 )
, pp ( Point2D 10 0 )
]
where
pp, cp :: Point2D Double -> StrokePoint a
pp p = PathPoint p d
cp p = ControlPoint p d
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -260,11 +279,6 @@ main = do
widgetAddClasses title [ "text", "title", "plain" ] widgetAddClasses title [ "text", "title", "plain" ]
GTK.boxSetCenterWidget titleBar ( Just title ) GTK.boxSetCenterWidget titleBar ( Just title )
---------------------------------------------------------
-- Tool bar
_ <- createToolBar toolTVar modeTVar colours toolBar
--------------------------------------------------------- ---------------------------------------------------------
-- Main viewport -- Main viewport
@ -280,15 +294,21 @@ main = do
mbMousePos <- STM.readTVarIO mousePosTVar mbMousePos <- STM.readTVarIO mousePosTVar
mbHoldEvent <- STM.readTVarIO mouseHoldTVar mbHoldEvent <- STM.readTVarIO mouseHoldTVar
mbPartialPath <- STM.readTVarIO partialPathTVar mbPartialPath <- STM.readTVarIO partialPathTVar
mode <- STM.readTVarIO modeTVar
viewportWidth <- GTK.widgetGetAllocatedWidth viewportDrawingArea viewportWidth <- GTK.widgetGetAllocatedWidth viewportDrawingArea
viewportHeight <- GTK.widgetGetAllocatedHeight viewportDrawingArea viewportHeight <- GTK.widgetGetAllocatedHeight viewportDrawingArea
( `Cairo.renderWithContext` ctx ) $ ( `Cairo.renderWithContext` ctx ) $
renderDocument renderDocument
colours ( viewportWidth, viewportHeight ) colours mode ( viewportWidth, viewportHeight )
mbMousePos mbHoldEvent mbPartialPath mbMousePos mbHoldEvent mbPartialPath
doc doc
pure True pure True
---------------------------------------------------------
-- Tool bar
_ <- createToolBar toolTVar modeTVar colours viewportDrawingArea toolBar
--------------------------------------------------------- ---------------------------------------------------------
-- Info bar -- Info bar

View file

@ -42,9 +42,15 @@
.path { .path {
color: rgb(184,80,80); color: rgb(184,80,80);
} }
.brushStroke { .brush {
color: rgb(235,118,219); color: rgb(235,118,219);
} }
.brushStroke {
color: rgba(235,118,219,0.66);
}
.brushCenter {
color: rgb(0,0,0);
}
.pointHover { .pointHover {
color: rgb(225,225,225); color: rgb(225,225,225);
} }

View file

@ -43,7 +43,9 @@ data ColourRecord a
, controlPoint :: !a , controlPoint :: !a
, controlPointOutline :: !a , controlPointOutline :: !a
, path :: !a , path :: !a
, brush :: !a
, brushStroke :: !a , brushStroke :: !a
, brushCenter :: !a
, pointHover :: !a , pointHover :: !a
, pointSelected :: !a , pointSelected :: !a
, viewport :: !a , viewport :: !a
@ -84,7 +86,9 @@ colourNames = Colours
, controlPoint = ColourName "controlPoint" Colour [ GTK.StateFlagsNormal ] , controlPoint = ColourName "controlPoint" Colour [ GTK.StateFlagsNormal ]
, controlPointOutline = ColourName "controlPointStroke" Colour [ GTK.StateFlagsNormal ] , controlPointOutline = ColourName "controlPointStroke" Colour [ GTK.StateFlagsNormal ]
, path = ColourName "path" Colour [ GTK.StateFlagsNormal ] , path = ColourName "path" Colour [ GTK.StateFlagsNormal ]
, brush = ColourName "brush" Colour [ GTK.StateFlagsNormal ]
, brushStroke = ColourName "brushStroke" Colour [ GTK.StateFlagsNormal ] , brushStroke = ColourName "brushStroke" Colour [ GTK.StateFlagsNormal ]
, brushCenter = ColourName "brushCenter" Colour [ GTK.StateFlagsNormal ]
, pointHover = ColourName "pointHover" Colour [ GTK.StateFlagsNormal ] , pointHover = ColourName "pointHover" Colour [ GTK.StateFlagsNormal ]
, pointSelected = ColourName "pointSelected" Colour [ GTK.StateFlagsNormal ] , pointSelected = ColourName "pointSelected" Colour [ GTK.StateFlagsNormal ]
, viewport = ColourName "viewport" BackgroundColour [ GTK.StateFlagsNormal ] , viewport = ColourName "viewport" BackgroundColour [ GTK.StateFlagsNormal ]

View file

@ -2,6 +2,7 @@
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
@ -9,8 +10,9 @@ module MetaBrush.Document
( AABB(..) ( AABB(..)
, Document(..), currentDocument , Document(..), currentDocument
, Stroke(..) , Stroke(..)
, PointData(..), FocusState(..) , PointData(..), BrushPointData(..)
, selection , FocusState(..)
, _selection, _brush
) )
where where
@ -27,6 +29,8 @@ import GHC.Generics
-- generic-lens -- generic-lens
import Data.Generics.Product.Fields import Data.Generics.Product.Fields
( field' ) ( field' )
import Data.Generics.Product.Typed
( HasType(typed) )
-- lens -- lens
import Control.Lens import Control.Lens
@ -83,18 +87,26 @@ data Stroke
data PointData data PointData
= PointData = PointData
{ pointState :: FocusState { pointState :: FocusState
, brush :: Seq ( StrokePoint () ) , brushShape :: Seq ( StrokePoint BrushPointData )
} }
deriving stock ( Show, Generic ) deriving stock ( Show, Generic )
data BrushPointData
= BrushPointData
{ brushPointState :: FocusState }
deriving stock ( Show, Generic )
data FocusState data FocusState
= Normal = Normal
| Hover | Hover
| Selected | Selected
deriving stock ( Show, Eq ) deriving stock ( Show, Eq )
selection :: Lens' ( StrokePoint PointData ) FocusState _selection :: HasType FocusState pt => Lens' ( StrokePoint pt ) FocusState
selection = field' @"pointData" . field' @"pointState" _selection = field' @"pointData" . typed @FocusState
_brush :: Lens' ( StrokePoint PointData ) ( Seq ( StrokePoint BrushPointData ) )
_brush = field' @"pointData" . field' @"brushShape"
currentDocument :: STM.TVar ( Maybe Int ) -> STM.TVar ( IntMap Document ) -> IO ( Maybe Document ) currentDocument :: STM.TVar ( Maybe Int ) -> STM.TVar ( IntMap Document ) -> IO ( Maybe Document )
currentDocument activeDocumentTVar openDocumentsTVar = STM.atomically do currentDocument activeDocumentTVar openDocumentsTVar = STM.atomically do

View file

@ -49,7 +49,7 @@ import Math.Vector2D
( Point2D(..), Vector2D(..) ) ( Point2D(..), Vector2D(..) )
import MetaBrush.Document import MetaBrush.Document
( Document(..), Stroke(..), FocusState(..), PointData(..) ( Document(..), Stroke(..), FocusState(..), PointData(..)
, selection , _selection
) )
import MetaBrush.Unique import MetaBrush.Unique
( Unique, UniqueSupply, freshUnique, uniqueText ) ( Unique, UniqueSupply, freshUnique, uniqueText )
@ -106,9 +106,9 @@ getDrawAnchor uniqueSupply c doc@( Document { zoomFactor } ) =
| strokeVisible | strokeVisible
, Just anchor <- endpointAnchor strokeUnique strokePoints , Just anchor <- endpointAnchor strokeUnique strokePoints
-> put ( Just anchor ) -> put ( Just anchor )
$> set ( field' @"strokePoints" . mapped . selection ) Normal stroke $> set ( field' @"strokePoints" . mapped . _selection ) Normal stroke
-- Otherwise, just deselect. -- Otherwise, just deselect.
_ -> pure $ set ( field' @"strokePoints" . mapped . selection ) Normal stroke _ -> pure $ set ( field' @"strokePoints" . mapped . _selection ) Normal stroke
-- See if we can anchor a drawing operation on a given (visible) stroke. -- See if we can anchor a drawing operation on a given (visible) stroke.
endpointAnchor :: Unique -> Seq ( StrokePoint PointData ) -> Maybe ( DrawAnchor, Point2D Double ) endpointAnchor :: Unique -> Seq ( StrokePoint PointData ) -> Maybe ( DrawAnchor, Point2D Double )

View file

@ -17,6 +17,8 @@ module MetaBrush.Document.Selection
where where
-- base -- base
import Control.Category
( (>>>) )
import Data.Functor import Data.Functor
( ($>) ) ( ($>) )
import Data.Functor.Identity import Data.Functor.Identity
@ -62,7 +64,7 @@ import Math.Vector2D
import MetaBrush.Document import MetaBrush.Document
( Document(..), Stroke(..) ( Document(..), Stroke(..)
, PointData(..), FocusState(..) , PointData(..), FocusState(..)
, selection , _selection
) )
import MetaBrush.Event.KeyCodes import MetaBrush.Event.KeyCodes
( pattern Alt_L , pattern Alt_R ( pattern Alt_L , pattern Alt_R
@ -102,19 +104,20 @@ selectAt mode c doc@( Document { zoomFactor } ) =
where where
updateStroke :: Stroke -> State Bool Stroke updateStroke :: Stroke -> State Bool Stroke
updateStroke stroke@( Stroke { strokeVisible } ) = updateStroke stroke@( Stroke { strokeVisible } ) =
over ( field' @"strokePoints" ) matchEndpoints <$> ( field' @"strokePoints" )
( field' @"strokePoints" . traverse ) ( traverse ( updatePoint strokeVisible )
( updatePoint strokeVisible ) >>> fmap matchEndpoints
)
stroke stroke
updatePoint :: Bool -> StrokePoint PointData -> State Bool ( StrokePoint PointData ) updatePoint :: Bool -> StrokePoint PointData -> State Bool ( StrokePoint PointData )
updatePoint isVisible pt = do updatePoint isVisible pt = do
anotherPointHasAlreadyBeenSelected <- get anotherPointHasAlreadyBeenSelected <- get
if selected && not anotherPointHasAlreadyBeenSelected if selected && not anotherPointHasAlreadyBeenSelected
then put True $> case mode of then put True $> case mode of
Subtract -> set selection Normal pt Subtract -> set _selection Normal pt
_ -> set selection Selected pt _ -> set _selection Selected pt
else pure $ case mode of else pure $ case mode of
New -> set selection Normal pt New -> set _selection Normal pt
_ -> pt _ -> pt
where where
selected :: Bool selected :: Bool
@ -125,7 +128,7 @@ selectAt mode c doc@( Document { zoomFactor } ) =
matchEndpoints :: Seq ( StrokePoint PointData ) -> Seq ( StrokePoint PointData ) matchEndpoints :: Seq ( StrokePoint PointData ) -> Seq ( StrokePoint PointData )
matchEndpoints ( p0 :<| ( ps :|> pn ) ) matchEndpoints ( p0 :<| ( ps :|> pn ) )
| coords p0 == coords pn | coords p0 == coords pn
= p0 :<| ( ps :|> set selection ( view selection p0 ) pn ) = p0 :<| ( ps :|> set _selection ( view _selection p0 ) pn )
matchEndpoints ps = ps matchEndpoints ps = ps
-- | Type of a drag move selection: -- | Type of a drag move selection:
@ -154,9 +157,10 @@ dragMoveSelect c doc@( Document { zoomFactor } ) =
where where
updateStroke :: Stroke -> Tardis ( Maybe DragMoveSelect ) ( Maybe DragMoveSelect ) Stroke updateStroke :: Stroke -> Tardis ( Maybe DragMoveSelect ) ( Maybe DragMoveSelect ) Stroke
updateStroke stroke@( Stroke { strokeVisible } ) = updateStroke stroke@( Stroke { strokeVisible } ) =
over ( field' @"strokePoints" ) matchEndpoints <$> ( field' @"strokePoints" )
( field' @"strokePoints" . traverse ) ( traverse ( updatePoint strokeVisible )
( updatePoint strokeVisible ) >>> fmap matchEndpoints
)
stroke stroke
updatePoint :: Bool -> StrokePoint PointData -> Tardis ( Maybe DragMoveSelect ) ( Maybe DragMoveSelect ) ( StrokePoint PointData ) updatePoint :: Bool -> StrokePoint PointData -> Tardis ( Maybe DragMoveSelect ) ( Maybe DragMoveSelect ) ( StrokePoint PointData )
updatePoint isVisible pt updatePoint isVisible pt
@ -172,7 +176,7 @@ dragMoveSelect c doc@( Document { zoomFactor } ) =
Selected -> Tardis.sendFuture ( Just ClickedOnSelected ) Selected -> Tardis.sendFuture ( Just ClickedOnSelected )
_ -> Tardis.sendFuture ( Just ClickedOnUnselected ) _ -> Tardis.sendFuture ( Just ClickedOnUnselected )
-- Select this point (whether it was previously selected or not). -- Select this point (whether it was previously selected or not).
pure $ set selection Selected pt pure $ set _selection Selected pt
| otherwise | otherwise
= do = do
mbDragClick <- Tardis.getFuture mbDragClick <- Tardis.getFuture
@ -180,13 +184,13 @@ dragMoveSelect c doc@( Document { zoomFactor } ) =
-- needs to be lazy -- needs to be lazy
newPointState :: FocusState newPointState :: FocusState
newPointState newPointState
-- User clicked on a selected point: preserve selection. -- User clicked on a selected point: preserve _selection.
| Just ClickedOnSelected <- mbDragClick | Just ClickedOnSelected <- mbDragClick
= view selection pt = view _selection pt
-- User clicked on an unselected point, or not on a point at all: discard selection. -- User clicked on an unselected point, or not on a point at all: discard selection.
| otherwise | otherwise
= Normal = Normal
pure ( set selection newPointState pt ) pure ( set _selection newPointState pt )
where where
selected :: Bool selected :: Bool
selected selected
@ -196,7 +200,7 @@ dragMoveSelect c doc@( Document { zoomFactor } ) =
matchEndpoints :: Seq ( StrokePoint PointData ) -> Seq ( StrokePoint PointData ) matchEndpoints :: Seq ( StrokePoint PointData ) -> Seq ( StrokePoint PointData )
matchEndpoints ( p0 :<| ( ps :|> pn ) ) matchEndpoints ( p0 :<| ( ps :|> pn ) )
| coords p0 == coords pn | coords p0 == coords pn
= p0 :<| ( ps :|> set selection ( view selection p0 ) pn ) = p0 :<| ( ps :|> set _selection ( view _selection p0 ) pn )
matchEndpoints ps = ps matchEndpoints ps = ps
-- | Updates the selected objects on a rectangular selection event. -- | Updates the selected objects on a rectangular selection event.
@ -214,10 +218,10 @@ selectRectangle mode ( Point2D x0 y0 ) ( Point2D x1 y1 ) = over ( field' @"strok
updatePoint :: Bool -> StrokePoint PointData -> StrokePoint PointData updatePoint :: Bool -> StrokePoint PointData -> StrokePoint PointData
updatePoint isVisible pt updatePoint isVisible pt
| selected = case mode of | selected = case mode of
Subtract -> set selection Normal pt Subtract -> set _selection Normal pt
_ -> set selection Selected pt _ -> set _selection Selected pt
| otherwise = case mode of | otherwise = case mode of
New -> set selection Normal pt New -> set _selection Normal pt
_ -> pt _ -> pt
where where
x, y :: Double x, y :: Double
@ -233,7 +237,7 @@ translateSelection t = over ( field' @"strokes" . mapped . field' @"strokePoints
where where
updateStrokePoint :: StrokePoint PointData -> StrokePoint PointData updateStrokePoint :: StrokePoint PointData -> StrokePoint PointData
updateStrokePoint pt updateStrokePoint pt
| Selected <- view selection pt | Selected <- view _selection pt
= pt { coords = t coords pt } = pt { coords = t coords pt }
| otherwise | otherwise
= pt = pt
@ -277,4 +281,4 @@ deleteSelected
pure res pure res
where where
selectionState :: FocusState selectionState :: FocusState
selectionState = view selection p selectionState = view _selection p

View file

@ -1,9 +1,13 @@
{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NegativeLiterals #-} {-# LANGUAGE NegativeLiterals #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module MetaBrush.Render.Document module MetaBrush.Render.Document
( renderDocument ) ( renderDocument )
@ -13,13 +17,15 @@ module MetaBrush.Render.Document
import Control.Monad import Control.Monad
( guard ) ( guard )
import Data.Foldable import Data.Foldable
( traverse_ ) ( for_, sequenceA_ )
import Data.Functor.Compose import Data.Functor.Compose
( Compose(..) ) ( Compose(..) )
import Data.Int import Data.Int
( Int32 ) ( Int32 )
import Data.Maybe import Data.Maybe
( catMaybes ) ( catMaybes )
import GHC.Generics
( Generic, Generic1 )
-- acts -- acts
import Data.Act import Data.Act
@ -35,9 +41,20 @@ import Data.Sequence
import qualified Data.Sequence as Seq import qualified Data.Sequence as Seq
( fromList ) ( fromList )
-- generic-data
import Generic.Data
( Generically1(..) )
-- generic-lens
import Data.Generics.Product.Typed
( HasType )
-- gi-cairo-render -- gi-cairo-render
import qualified GI.Cairo.Render as Cairo import qualified GI.Cairo.Render as Cairo
-- gi-gdk
import qualified GI.Gdk as GDK
-- lens -- lens
import Control.Lens import Control.Lens
( view ) ( view )
@ -55,8 +72,9 @@ import MetaBrush.Asset.Colours
( Colours, ColourRecord(..) ) ( Colours, ColourRecord(..) )
import MetaBrush.Document import MetaBrush.Document
( Document(..) ( Document(..)
, Stroke(..), PointData(..), FocusState(..) , Stroke(..), FocusState(..)
, selection , PointData(..), BrushPointData(..)
, _selection
) )
import MetaBrush.Document.Selection import MetaBrush.Document.Selection
( translateSelection ) ( translateSelection )
@ -64,33 +82,50 @@ import MetaBrush.Event
( HoldEvent(..), PartialPath(..) ) ( HoldEvent(..), PartialPath(..) )
import MetaBrush.Render.Util import MetaBrush.Render.Util
( withRGBA ) ( withRGBA )
import MetaBrush.UI.ToolBar
( Mode(..) )
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
data Renders a data Renders a
= MkRenders = Renders
{ mkRenderPoints :: a { renderPath :: a
, mkRenderPaths :: a , renderStrokes :: a
, renderBrushes :: a
, renderCPts :: a
, renderCLines :: a
, renderPPts :: a
} }
deriving stock ( Show, Functor ) deriving stock ( Show, Functor, Foldable, Traversable, Generic, Generic1 )
deriving Applicative
via Generically1 Renders
instance Applicative Renders where blank :: Renders ( Cairo.Render () )
pure a = MkRenders a a blank = pure ( pure () )
MkRenders f1 f2 <*> MkRenders a1 a2 = MkRenders ( f1 a1 ) ( f2 a2 )
{-# COMPLETE Renders #-} compositeRenders :: Renders ( Cairo.Render () ) -> Cairo.Render ()
pattern Renders :: Cairo.Render a -> Cairo.Render a -> Compose Renders Cairo.Render a compositeRenders = sequenceA_
pattern Renders { renderPoints, renderPaths } = Compose ( MkRenders renderPoints renderPaths )
toAll :: Cairo.Render () -> Compose Renders Cairo.Render ()
toAll action = Compose ( pure action )
whenPath :: Applicative m => Mode -> m () -> m ()
whenPath Path = id
whenPath _ = const ( pure () )
whenBrush :: Applicative m => Mode -> m () -> m ()
whenBrush Brush = id
whenBrush _ = const ( pure () )
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
renderDocument renderDocument
:: Colours -> ( Int32, Int32 ) :: Colours -> Mode -> ( Int32, Int32 )
-> Maybe ( Point2D Double ) -> Maybe HoldEvent -> Maybe PartialPath -> Maybe ( Point2D Double ) -> Maybe HoldEvent -> Maybe PartialPath
-> Document -> Document
-> Cairo.Render () -> Cairo.Render ()
renderDocument renderDocument
cols ( viewportWidth, viewportHeight ) cols mode ( viewportWidth, viewportHeight )
mbMousePos mbHoldEvent mbPartialPath mbMousePos mbHoldEvent mbPartialPath
doc@( Document { viewportCenter = Point2D cx cy, zoomFactor } ) doc@( Document { viewportCenter = Point2D cx cy, zoomFactor } )
= do = do
@ -143,69 +178,120 @@ renderDocument
= ( Stroke previewPts undefined True undefined ) : strokes doc = ( Stroke previewPts undefined True undefined ) : strokes doc
| otherwise | otherwise
= strokes doc = strokes doc
Renders rdrPoints rdrPaths = traverse_ ( renderStroke cols zoomFactor ) modifiedStrokes
rdrPaths for_ modifiedStrokes ( compositeRenders . getCompose . renderStroke cols mode zoomFactor )
rdrPoints
renderSelectionRect renderSelectionRect
Cairo.restore Cairo.restore
pure () pure ()
renderStroke :: Colours -> Double -> Stroke -> Compose Renders Cairo.Render () renderStroke :: Colours -> Mode -> Double -> Stroke -> Compose Renders Cairo.Render ()
renderStroke cols zoom ( Stroke { strokePoints = pt0 :<| pts, strokeVisible = True } ) renderStroke cols mode zoom ( Stroke { strokePoints = pts, strokeVisible } )
= go pt0 pts *> Renders { renderPoints = drawPoint cols zoom pt0, renderPaths = pure () } | strokeVisible
= renderContent Path cols mode zoom pts
| otherwise
= pure ()
class RenderableContent d where
renderContent :: Mode -> Colours -> Mode -> Double -> d -> Compose Renders Cairo.Render ()
instance RenderableContent ( StrokePoint PointData ) where
renderContent _ cols _ zoom pt =
let
x, y :: Double
Point2D x y = coords pt
brushPts :: Seq ( StrokePoint BrushPointData )
brushPts = brushShape ( pointData pt )
in
toAll do
Cairo.save
Cairo.translate x y
*> renderContent Brush cols Path zoom brushPts
*> Compose blank { renderPPts = drawCross cols zoom }
*> toAll Cairo.restore
instance RenderableContent ( StrokePoint BrushPointData ) where
renderContent _ _ _ _ _ = pure ()
instance ( Show d, RenderableContent ( StrokePoint d ), HasType FocusState d ) => RenderableContent ( Seq ( StrokePoint d ) ) where
renderContent _ _ _ _ Empty = pure ()
renderContent tp cols mode zoom ( pt0 :<| pts ) =
Compose blank
{ renderPPts = whenPath mode $ drawPoint cols zoom pt0 }
*> whenBrush mode ( renderContent Brush cols mode zoom pt0 )
*> go pt0 pts
where where
go :: StrokePoint PointData -> Seq ( StrokePoint PointData ) -> Compose Renders Cairo.Render () go :: StrokePoint d -> Seq ( StrokePoint d ) -> Compose Renders Cairo.Render ()
go _ Empty = pure () go _ Empty = pure ()
go ( ControlPoint {} ) _ = error "closestPoint: path starts with a control point" go ( ControlPoint {} ) _ = error "renderContent: path starts with a control point"
-- Line. -- Line.
go p0 ( p1 :<| ps ) go p0 ( p1 :<| ps )
| PathPoint {} <- p1 | PathPoint {} <- p1
= Renders = Compose blank
{ renderPoints = drawPoint cols zoom p1 { renderPPts
, renderPaths = drawLine cols zoom p0 p1 = whenPath mode $ drawPoint cols zoom p1
, renderPath
= drawLine cols tp zoom p0 p1
} }
*> ( whenBrush mode $ renderContent Brush cols mode zoom p1 )
*> go p1 ps *> go p1 ps
-- Quadratic Bézier curve. -- Quadratic Bézier curve.
go p0 ( p1 :<| p2 :<| ps ) go p0 ( p1 :<| p2 :<| ps )
| ControlPoint {} <- p1 | ControlPoint {} <- p1
, PathPoint {} <- p2 , PathPoint {} <- p2
= Renders = Compose blank
{ renderPoints { renderCLines
= drawLine cols zoom p0 p1 = whenPath mode do
*> drawLine cols zoom p1 p2 drawLine cols tp zoom p0 p1
*> drawPoint cols zoom p1 drawLine cols tp zoom p1 p2
*> drawPoint cols zoom p2 , renderCPts
, renderPaths = drawQuadraticBezier cols zoom ( coords <$> Quadratic.Bezier { p0, p1, p2 } ) = whenPath mode $ drawPoint cols zoom p1
, renderPPts
= whenPath mode $ drawPoint cols zoom p2
, renderPath
= drawQuadraticBezier cols tp zoom ( coords <$> Quadratic.Bezier { p0, p1, p2 } )
} }
*> whenBrush mode
( renderContent Brush cols mode zoom p1
*> renderContent Brush cols mode zoom p2
)
*> go p2 ps *> go p2 ps
-- Cubic Bézier curve. -- Cubic Bézier curve.
go p0 ( p1 :<| p2 :<| p3 :<| ps ) go p0 ( p1 :<| p2 :<| p3 :<| ps )
| ControlPoint {} <- p1 | ControlPoint {} <- p1
, ControlPoint {} <- p2 , ControlPoint {} <- p2
, PathPoint {} <- p3 , PathPoint {} <- p3
= Renders = Compose blank
{ renderPoints { renderCLines
= drawLine cols zoom p0 p1 = whenPath mode do
*> drawLine cols zoom p2 p3 drawLine cols tp zoom p0 p1
*> drawPoint cols zoom p1 drawLine cols tp zoom p2 p3
*> drawPoint cols zoom p2 , renderCPts
*> drawPoint cols zoom p3 = whenPath mode do
, renderPaths = drawCubicBezier cols zoom ( coords <$> Cubic.Bezier { p0, p1, p2, p3 } ) drawPoint cols zoom p1
drawPoint cols zoom p2
, renderPPts
= whenPath mode $ drawPoint cols zoom p3
, renderPath
= drawCubicBezier cols tp zoom ( coords <$> Cubic.Bezier { p0, p1, p2, p3 } )
} }
*> whenBrush mode
( renderContent Brush cols mode zoom p1
*> renderContent Brush cols mode zoom p2
*> renderContent Brush cols mode zoom p3
)
*> 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 )
renderStroke _ _ _ = pure ()
drawPoint :: Colours -> Double -> StrokePoint PointData -> Cairo.Render () drawPoint :: HasType FocusState d => Colours -> Double -> StrokePoint d -> Cairo.Render ()
drawPoint ( Colours { .. } ) zoom pt@( PathPoint { coords = Point2D x y } ) drawPoint ( Colours { .. } ) 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
Cairo.save Cairo.save
Cairo.translate x y Cairo.translate x y
@ -237,7 +323,7 @@ drawPoint ( Colours { .. } ) zoom pt@( ControlPoint { coords = Point2D x y } )
= do = do
let let
selectionState :: FocusState selectionState :: FocusState
selectionState = view selection pt selectionState = view _selection pt
Cairo.save Cairo.save
Cairo.translate x y Cairo.translate x y
@ -262,12 +348,19 @@ drawPoint ( Colours { .. } ) zoom pt@( ControlPoint { coords = Point2D x y } )
Cairo.restore Cairo.restore
drawLine :: Colours -> Double -> StrokePoint PointData -> StrokePoint PointData -> Cairo.Render () drawLine :: Colours -> Mode -> Double -> StrokePoint d -> StrokePoint d -> Cairo.Render ()
drawLine ( Colours { path, controlPoint } ) zoom p1 p2 = do drawLine ( Colours { path, brush, controlPoint } ) mode zoom p1 p2 = do
let let
x1, y1, x2, y2 :: Double x1, y1, x2, y2 :: Double
Point2D x1 y1 = coords p1 Point2D x1 y1 = coords p1
Point2D x2 y2 = coords p2 Point2D x2 y2 = coords p2
col :: GDK.RGBA
sz :: Double
( col, sz )
| Brush <- mode
= ( brush, 3 )
| otherwise
= ( path, 6 )
Cairo.save Cairo.save
Cairo.moveTo x1 y1 Cairo.moveTo x1 y1
@ -275,8 +368,8 @@ drawLine ( Colours { path, controlPoint } ) zoom p1 p2 = do
case ( p1, p2 ) of case ( p1, p2 ) of
( PathPoint {}, PathPoint {} ) -> do ( PathPoint {}, PathPoint {} ) -> do
Cairo.setLineWidth ( 6 / zoom ) Cairo.setLineWidth ( sz / zoom )
withRGBA path Cairo.setSourceRGBA withRGBA col Cairo.setSourceRGBA
_ -> do _ -> do
Cairo.setLineWidth ( 3 / zoom ) Cairo.setLineWidth ( 3 / zoom )
withRGBA controlPoint Cairo.setSourceRGBA withRGBA controlPoint Cairo.setSourceRGBA
@ -284,8 +377,8 @@ drawLine ( Colours { path, controlPoint } ) zoom p1 p2 = do
Cairo.restore Cairo.restore
drawQuadraticBezier :: Colours -> Double -> Quadratic.Bezier ( Point2D Double ) -> Cairo.Render () drawQuadraticBezier :: Colours -> Mode -> Double -> Quadratic.Bezier ( Point2D Double ) -> Cairo.Render ()
drawQuadraticBezier ( Colours { path } ) zoom drawQuadraticBezier ( Colours { path, brush } ) mode zoom
( Quadratic.Bezier ( Quadratic.Bezier
{ p0 = Point2D x0 y0 { p0 = Point2D x0 y0
, p1 = Point2D x1 y1 , p1 = Point2D x1 y1
@ -293,6 +386,14 @@ drawQuadraticBezier ( Colours { path } ) zoom
} }
) )
= do = do
let
col :: GDK.RGBA
sz :: Double
( col, sz )
| Brush <- mode
= ( brush, 3 )
| otherwise
= ( path, 6 )
Cairo.save Cairo.save
@ -302,14 +403,14 @@ drawQuadraticBezier ( Colours { path } ) zoom
( ( 2 * x1 + x2 ) / 3 ) ( ( 2 * y1 + y2 ) / 3 ) ( ( 2 * x1 + x2 ) / 3 ) ( ( 2 * y1 + y2 ) / 3 )
x2 y2 x2 y2
Cairo.setLineWidth ( 6 / zoom ) Cairo.setLineWidth ( sz / zoom )
withRGBA path Cairo.setSourceRGBA withRGBA col Cairo.setSourceRGBA
Cairo.stroke Cairo.stroke
Cairo.restore Cairo.restore
drawCubicBezier :: Colours -> Double -> Cubic.Bezier ( Point2D Double ) -> Cairo.Render () drawCubicBezier :: Colours -> Mode -> Double -> Cubic.Bezier ( Point2D Double ) -> Cairo.Render ()
drawCubicBezier ( Colours { path } ) zoom drawCubicBezier ( Colours { path, brush } ) mode zoom
( Cubic.Bezier ( Cubic.Bezier
{ p0 = Point2D x0 y0 { p0 = Point2D x0 y0
, p1 = Point2D x1 y1 , p1 = Point2D x1 y1
@ -318,14 +419,22 @@ drawCubicBezier ( Colours { path } ) zoom
} }
) )
= do = do
let
col :: GDK.RGBA
sz :: Double
( col, sz )
| Brush <- mode
= ( brush, 3 )
| otherwise
= ( path, 6 )
Cairo.save Cairo.save
Cairo.moveTo x0 y0 Cairo.moveTo x0 y0
Cairo.curveTo x1 y1 x2 y2 x3 y3 Cairo.curveTo x1 y1 x2 y2 x3 y3
Cairo.setLineWidth ( 6 / zoom ) Cairo.setLineWidth ( sz / zoom )
withRGBA path Cairo.setSourceRGBA withRGBA col Cairo.setSourceRGBA
Cairo.stroke Cairo.stroke
Cairo.restore Cairo.restore
@ -350,3 +459,22 @@ renderSelectionRectangle ( Colours { .. } ) zoom ( Point2D x0 y0 ) ( Point2D x1
Cairo.stroke Cairo.stroke
Cairo.restore Cairo.restore
drawCross :: Colours -> Double -> Cairo.Render ()
drawCross ( Colours { .. } ) zoom = do
Cairo.save
Cairo.setLineWidth 2
withRGBA brushCenter Cairo.setSourceRGBA
Cairo.scale ( 1 / zoom ) ( 1 / zoom )
Cairo.moveTo -3 -3
Cairo.lineTo 3 3
Cairo.stroke
Cairo.moveTo -3 3
Cairo.lineTo 3 -3
Cairo.stroke
Cairo.restore

View file

@ -50,7 +50,7 @@ closestPoint c ( Stroke { strokePoints = pt0 :<| pts, strokeVisible = True } ) =
res :: Point2D Double -> ArgMin Double ( Maybe ( Point2D Double ) ) res :: Point2D Double -> ArgMin Double ( Maybe ( Point2D Double ) )
res p = Min $ Arg ( squaredNorm ( c --> p :: Vector2D Double ) ) ( Just p ) res p = Min $ Arg ( squaredNorm ( c --> p :: Vector2D Double ) ) ( Just p )
go :: StrokePoint PointData -> Seq ( StrokePoint PointData ) -> ArgMin Double ( Maybe ( Point2D Double ) ) go :: StrokePoint PointData -> Seq ( StrokePoint PointData ) -> ArgMin Double ( Maybe ( Point2D Double ) )
go ( ControlPoint {} ) _ = error "closedPoint: path starts with a control point" go ( ControlPoint {} ) _ = error "closestPoint: path starts with a control point"
go p0 Empty = res ( coords p0 ) go p0 Empty = res ( coords p0 )
-- Line. -- Line.
go ( PathPoint { coords = p0 } ) go ( PathPoint { coords = p0 } )

View file

@ -61,8 +61,8 @@ data ToolBar
, metaTool :: !GTK.RadioButton , metaTool :: !GTK.RadioButton
} }
createToolBar :: STM.TVar Tool -> STM.TVar Mode -> Colours -> GTK.Box -> IO ToolBar createToolBar :: STM.TVar Tool -> STM.TVar Mode -> Colours -> GTK.DrawingArea -> GTK.Box -> IO ToolBar
createToolBar toolTVar modeTVar colours toolBar = do createToolBar toolTVar modeTVar colours drawingArea toolBar = do
widgetAddClass toolBar "toolBar" widgetAddClass toolBar "toolBar"
@ -83,12 +83,16 @@ createToolBar toolTVar modeTVar colours toolBar = do
brushTool <- GTK.radioButtonNewFromWidget ( Just pathTool ) brushTool <- GTK.radioButtonNewFromWidget ( Just pathTool )
metaTool <- GTK.radioButtonNewFromWidget ( Just pathTool ) metaTool <- GTK.radioButtonNewFromWidget ( Just pathTool )
_ <- GTK.onButtonClicked pathTool _ <- GTK.onButtonClicked pathTool do
( STM.atomically $ STM.writeTVar modeTVar Path ) STM.atomically $ STM.writeTVar modeTVar Path
_ <- GTK.onButtonClicked brushTool GTK.widgetQueueDraw drawingArea
( STM.atomically $ STM.writeTVar modeTVar Brush ) _ <- GTK.onButtonClicked brushTool do
_ <- GTK.onButtonClicked metaTool STM.atomically $ STM.writeTVar modeTVar Brush
( STM.atomically $ STM.writeTVar modeTVar Meta ) GTK.widgetQueueDraw drawingArea
_ <- GTK.onButtonClicked metaTool do
STM.atomically $ STM.writeTVar modeTVar Meta
GTK.widgetQueueDraw drawingArea
GTK.boxPackStart toolBar selectionTool True True 0 GTK.boxPackStart toolBar selectionTool True True 0
GTK.boxPackStart toolBar penTool True True 0 GTK.boxPackStart toolBar penTool True True 0