metabrush/app/Main.hs

434 lines
13 KiB
Haskell
Raw Normal View History

2020-08-20 01:57:26 +00:00
{-# LANGUAGE BlockArguments #-}
2020-09-10 16:43:42 +00:00
{-# LANGUAGE DataKinds #-}
2020-08-20 01:57:26 +00:00
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NegativeLiterals #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RecursiveDo #-}
2020-08-20 01:57:26 +00:00
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
2020-08-05 20:23:16 +00:00
2020-08-04 06:15:06 +00:00
module Main
( main )
where
2020-08-05 20:23:16 +00:00
-- base
2020-08-06 03:06:18 +00:00
import Control.Monad
( void )
import Data.Foldable
( for_ )
2020-09-10 16:43:42 +00:00
import Data.Function
( (&) )
2020-08-05 20:23:16 +00:00
import Data.Int
( Int32 )
import System.Exit
( exitSuccess )
import GHC.Conc
( getNumProcessors, setNumCapabilities )
2020-08-05 20:23:16 +00:00
2020-08-10 14:38:27 +00:00
-- containers
2020-09-01 19:56:59 +00:00
import Data.Map.Strict
( Map )
import qualified Data.Map.Strict as Map
( empty )
import Data.Sequence
2020-08-19 21:34:43 +00:00
( Seq(..) )
import qualified Data.Sequence as Seq
( fromList )
import Data.Set
( Set )
import qualified Data.Set as Set
( empty )
2020-08-10 14:38:27 +00:00
2020-08-05 20:23:16 +00:00
-- directory
import qualified System.Directory as Directory
( canonicalizePath )
2020-09-10 16:43:42 +00:00
-- generic-lens
import Data.Generics.Product.Fields
( field' )
2020-08-04 06:15:06 +00:00
-- gi-cairo-connector
import qualified GI.Cairo.Render.Connector as Cairo
( renderWithContext )
-- gi-gdk
import qualified GI.Gdk as GDK
-- gi-gtk
import qualified GI.Gtk as GTK
2020-09-10 16:43:42 +00:00
-- lens
import Control.Lens
( (.~) )
2020-08-10 14:38:27 +00:00
-- stm
import qualified Control.Concurrent.STM as STM
( atomically )
2020-08-10 14:38:27 +00:00
import qualified Control.Concurrent.STM.TVar as STM
( newTVarIO, readTVar )
2020-08-10 14:38:27 +00:00
2020-08-05 20:23:16 +00:00
-- text
import qualified Data.Text as Text
( pack )
2020-08-04 06:15:06 +00:00
-- MetaBrush
import Math.Bezier.Cubic.Fit
( FitParameters(..) )
2020-08-19 21:34:43 +00:00
import Math.Bezier.Stroke
( StrokePoint(..) )
2020-08-10 14:38:27 +00:00
import Math.Vector2D
( Point2D(..) )
import MetaBrush.Action
( ActionOrigin(..) )
import MetaBrush.Asset.Brushes
( ellipse, rect )
2020-08-05 20:23:16 +00:00
import MetaBrush.Asset.Colours
2020-08-08 03:33:35 +00:00
( getColours )
2020-08-05 20:23:16 +00:00
import MetaBrush.Asset.Logo
( drawLogo )
import MetaBrush.Context
( UIElements(..), Variables(..)
, Modifier(..)
, HoldAction(..), PartialPath(..)
)
2020-08-10 14:38:27 +00:00
import MetaBrush.Document
( Document(..), emptyDocument
, Stroke(..), FocusState(..)
2020-08-20 01:57:26 +00:00
, PointData(..), BrushPointData(..)
2020-08-10 14:38:27 +00:00
)
2020-09-10 16:43:42 +00:00
import MetaBrush.Document.History
( DocumentHistory(..), newHistory )
import MetaBrush.Document.Update
( activeDocument, withActiveDocument )
2020-08-05 20:23:16 +00:00
import MetaBrush.Event
( handleEvents )
2020-08-15 21:49:14 +00:00
import MetaBrush.Render.Document
( renderDocument, blankRender )
import MetaBrush.Render.Rulers
( renderRuler )
import MetaBrush.UI.FileBar
( FileBar(..), createFileBar )
import MetaBrush.UI.InfoBar
( InfoBar(..), createInfoBar, updateInfoBar )
2020-08-05 20:23:16 +00:00
import MetaBrush.UI.Menu
( createMenuBar )
import MetaBrush.UI.Panels
( createPanelBar )
import MetaBrush.UI.ToolBar
2020-08-13 22:47:10 +00:00
( Tool(..), Mode(..), createToolBar )
import MetaBrush.UI.Viewport
( Viewport(..), Ruler(..), createViewport )
2020-08-16 22:09:16 +00:00
import MetaBrush.Unique
2020-09-01 19:56:59 +00:00
( newUniqueSupply
, Unique, unsafeUnique
, uniqueMapFromList
)
import MetaBrush.Util
( widgetAddClass, widgetAddClasses )
2020-08-05 20:23:16 +00:00
import qualified Paths_MetaBrush as Cabal
2020-08-04 06:15:06 +00:00
( getDataFileName )
--------------------------------------------------------------------------------
2020-09-10 16:43:42 +00:00
testDocuments :: Map Unique DocumentHistory
testDocuments = fmap newHistory $ uniqueMapFromList
[ emptyDocument "Closed" ( unsafeUnique 0 )
& ( field' @"documentContent" . field' @"strokes" ) .~
[ Stroke
{ strokeName = "Ellipse"
, strokeVisible = True
, strokeUnique = unsafeUnique 10
, strokePoints = ellipse 150 100 ( PointData Normal ( rect 30 6 $ BrushPointData Normal ) )
}
]
, emptyDocument "Line" ( unsafeUnique 1 )
& ( field' @"documentContent" . field' @"strokes" ) .~
[ Stroke
{ strokeName = "Line"
, strokeVisible = True
, strokeUnique = unsafeUnique 11
, strokePoints = linePts
}
]
, emptyDocument "Short line" ( unsafeUnique 2 )
& ( field' @"documentContent" . field' @"strokes" ) .~
[ Stroke
{ strokeName = "ShortLine"
, strokeVisible = True
, strokeUnique = unsafeUnique 12
, strokePoints = linePts2
}
]
2020-09-01 19:56:59 +00:00
]
where
linePts :: Seq ( StrokePoint PointData )
linePts = Seq.fromList
[ PathPoint ( Point2D 0 -100 ) ( PointData Normal ( ellipse 30 8 $ BrushPointData Normal ) )
, ControlPoint ( Point2D 0 -30 ) ( PointData Normal ( ellipse 25 6 $ BrushPointData Normal ) )
, ControlPoint ( Point2D 0 30 ) ( PointData Normal ( ellipse 15 6 $ BrushPointData Normal ) )
, PathPoint ( Point2D 0 100 ) ( PointData Normal ( ellipse 15 6 $ BrushPointData Normal ) )
, ControlPoint ( Point2D 0 150 ) ( PointData Normal ( ellipse 15 6 $ BrushPointData Normal ) )
, ControlPoint ( Point2D 0 200 ) ( PointData Normal ( ellipse 15 6 $ BrushPointData Normal ) )
, PathPoint ( Point2D 0 250 ) ( PointData Normal ( ellipse 10 1 $ BrushPointData Normal ) )
]
linePts2 :: Seq ( StrokePoint PointData )
linePts2 = Seq.fromList
[ PathPoint ( Point2D 0 -100 ) ( PointData Normal ( ellipse 20 8 $ BrushPointData Normal ) )
--, ControlPoint ( Point2D 0 0 ) ( PointData Normal ( ellipse 140 8 $ BrushPointData Normal ) )
, PathPoint ( Point2D 0 100 ) ( PointData Normal ( ellipse 20 8 $ BrushPointData Normal ) )
]
2020-08-10 14:38:27 +00:00
--------------------------------------------------------------------------------
2020-08-04 06:15:06 +00:00
main :: IO ()
main = do
procs <- getNumProcessors
let
caps :: Int
caps
| procs >= 6
= procs - 2
| procs >= 2
= procs - 1
| otherwise
= procs
setNumCapabilities caps
2020-08-10 14:38:27 +00:00
---------------------------------------------------------
-- Initialise state
2020-08-16 22:09:16 +00:00
uniqueSupply <- newUniqueSupply
2020-09-15 16:51:07 +00:00
activeDocumentTVar <- STM.newTVarIO @( Maybe Unique ) Nothing
2020-09-10 16:43:42 +00:00
openDocumentsTVar <- STM.newTVarIO @( Map Unique DocumentHistory ) testDocuments
mousePosTVar <- STM.newTVarIO @( Maybe ( Point2D Double ) ) Nothing
mouseHoldTVar <- STM.newTVarIO @( Maybe HoldAction ) Nothing
modifiersTVar <- STM.newTVarIO @( Set Modifier ) Set.empty
toolTVar <- STM.newTVarIO @Tool Selection
modeTVar <- STM.newTVarIO @Mode Path
debugTVar <- STM.newTVarIO @Bool False
2020-09-10 16:43:42 +00:00
partialPathTVar <- STM.newTVarIO @( Maybe PartialPath ) Nothing
fileBarTabsTVar <- STM.newTVarIO @( Map Unique ( GTK.Box, GTK.RadioButton ) ) Map.empty
showGuidesTVar <- STM.newTVarIO @Bool True
maxHistorySizeTVar <- STM.newTVarIO @Int 1000
fitParametersTVar <- STM.newTVarIO @FitParameters
( FitParameters
{ maxSubdiv = 6
2020-09-15 16:51:07 +00:00
, nbSegments = 12
, dist_tol = 5e-3
, t_tol = 1e-4
, maxIters = 100
}
)
2020-08-10 14:38:27 +00:00
-- Put all these stateful variables in a record for conciseness.
let
variables :: Variables
variables = Variables {..}
2020-08-04 06:15:06 +00:00
---------------------------------------------------------
-- Initialise GTK
2020-08-06 03:06:18 +00:00
void $ GTK.init Nothing
2020-08-04 06:15:06 +00:00
Just screen <- GDK.screenGetDefault
2020-08-05 20:23:16 +00:00
themePath <- Text.pack <$> ( Directory.canonicalizePath =<< Cabal.getDataFileName "theme.css" )
2020-08-04 06:15:06 +00:00
cssProvider <- GTK.cssProviderNew
GTK.cssProviderLoadFromPath cssProvider themePath
GTK.styleContextAddProviderForScreen screen cssProvider 1000
window <- GTK.windowNew GTK.WindowTypeToplevel
windowWidgetPath <- GTK.widgetGetPath window
widgetAddClass window "window"
GTK.setWindowResizable window True
2020-08-10 14:38:27 +00:00
GTK.setWindowDecorated window False
2020-08-04 06:15:06 +00:00
GTK.setWindowTitle window "MetaBrush"
GTK.windowSetDefaultSize window 1024 768
GTK.widgetAddEvents window
[ GDK.EventMaskKeyPressMask, GDK.EventMaskKeyReleaseMask ]
2020-08-04 06:15:06 +00:00
let
baseMinWidth, baseMinHeight :: Int32
2020-08-05 20:23:16 +00:00
baseMinWidth = 480
2020-08-04 06:15:06 +00:00
baseMinHeight = 240
windowGeometry <- GDK.newZeroGeometry
GDK.setGeometryMinWidth windowGeometry baseMinWidth
GDK.setGeometryMinHeight windowGeometry baseMinHeight
GTK.windowSetGeometryHints window ( Nothing @GTK.Widget )
( Just windowGeometry )
[ GDK.WindowHintsMinSize ]
2020-08-07 19:39:24 +00:00
iconPath <- Directory.canonicalizePath =<< Cabal.getDataFileName "icon.png"
GTK.windowSetIconFromFile window iconPath
2020-08-07 22:41:08 +00:00
colours <- getColours windowWidgetPath
2020-08-05 20:23:16 +00:00
2020-08-04 06:15:06 +00:00
---------------------------------------------------------
-- Create base UI elements
baseOverlay <- GTK.overlayNew
2020-08-05 20:23:16 +00:00
GTK.setContainerChild window baseOverlay
2020-08-04 06:15:06 +00:00
uiGrid <- GTK.gridNew
2020-08-05 20:23:16 +00:00
GTK.setContainerChild baseOverlay uiGrid
2020-08-04 06:15:06 +00:00
2020-08-08 13:53:06 +00:00
logo <- GTK.boxNew GTK.OrientationVertical 0
titleBar <- GTK.boxNew GTK.OrientationHorizontal 0
toolBar <- GTK.boxNew GTK.OrientationVertical 0
mainPane <- GTK.panedNew GTK.OrientationHorizontal
panelBox <- GTK.boxNew GTK.OrientationVertical 0
2020-08-04 06:15:06 +00:00
GTK.gridAttach uiGrid logo 0 0 1 2
2020-08-07 19:39:24 +00:00
GTK.gridAttach uiGrid titleBar 1 0 2 1
GTK.gridAttach uiGrid toolBar 0 2 2 1
GTK.gridAttach uiGrid mainPane 2 2 1 1
2020-08-05 21:30:36 +00:00
mainView <- GTK.boxNew GTK.OrientationVertical 0
2020-08-08 13:53:06 +00:00
GTK.panedPack1 mainPane mainView True False
GTK.panedPack2 mainPane panelBox False False
2020-08-05 21:30:36 +00:00
2020-08-06 00:45:20 +00:00
viewportGrid <- GTK.gridNew
2020-08-05 20:23:16 +00:00
---------------------------------------------------------
-- Background
widgetAddClass uiGrid "bg"
---------------------------------------------------------
2020-08-08 03:33:35 +00:00
-- Title bar
widgetAddClass titleBar "titleBar"
--------
2020-08-05 20:23:16 +00:00
-- Logo
widgetAddClass logo "logo"
logoArea <- GTK.drawingAreaNew
GTK.boxPackStart logo logoArea True True 0
2020-08-06 03:06:18 +00:00
void $ GTK.onWidgetDraw logoArea
2020-08-07 22:41:08 +00:00
$ Cairo.renderWithContext ( drawLogo colours )
2020-08-05 20:23:16 +00:00
------------
-- Title
2020-08-07 19:39:24 +00:00
title <- GTK.labelNew ( Just "MetaBrush" )
widgetAddClasses title [ "text", "title", "plain" ]
GTK.boxSetCenterWidget titleBar ( Just title )
2020-08-07 19:39:24 +00:00
---------------------------------------------------------
-- Main viewport
2020-08-07 19:39:24 +00:00
viewport@( Viewport { .. } ) <- createViewport viewportGrid
2020-08-15 21:49:14 +00:00
-----------------
-- Viewport rendering
void $ GTK.onWidgetDraw viewportDrawingArea \ ctx -> do
-- Get the relevant document information
viewportWidth <- GTK.widgetGetAllocatedWidth viewportDrawingArea
viewportHeight <- GTK.widgetGetAllocatedHeight viewportDrawingArea
2020-09-10 16:43:42 +00:00
mbRender <- STM.atomically $ withActiveDocument variables \ doc@( Document {..} ) -> do
2020-09-18 09:40:14 +00:00
modifiers <- STM.readTVar modifiersTVar
mbMousePos <- STM.readTVar mousePosTVar
mbHoldAction <- STM.readTVar mouseHoldTVar
mbPartialPath <- STM.readTVar partialPathTVar
mode <- STM.readTVar modeTVar
debug <- STM.readTVar debugTVar
2020-09-06 03:32:03 +00:00
showGuides <- STM.readTVar showGuidesTVar
fitParameters <- STM.readTVar fitParametersTVar
pure do
2020-08-16 22:09:16 +00:00
renderDocument
colours fitParameters mode debug ( viewportWidth, viewportHeight )
2020-09-18 09:40:14 +00:00
modifiers mbMousePos mbHoldAction mbPartialPath
2020-08-16 22:09:16 +00:00
doc
renderRuler
colours ( viewportWidth, viewportHeight ) ViewportOrigin ( viewportWidth, viewportHeight )
mbMousePos mbHoldAction showGuides
doc
case mbRender of
Just render -> Cairo.renderWithContext render ctx
Nothing -> Cairo.renderWithContext ( blankRender colours ) ctx
2020-08-15 21:49:14 +00:00
pure True
2020-08-07 19:39:24 +00:00
for_ [ ( rulerCornerDrawingArea, RulerCorner )
, ( topRulerDrawingArea, TopRuler )
, ( leftRulerDrawingArea, LeftRuler
) ] \ ( rulerDrawingArea, ruler ) -> do
void $ GTK.onWidgetDraw rulerDrawingArea \ ctx -> do
viewportWidth <- GTK.widgetGetAllocatedWidth viewportDrawingArea
viewportHeight <- GTK.widgetGetAllocatedHeight viewportDrawingArea
width <- GTK.widgetGetAllocatedWidth rulerDrawingArea
height <- GTK.widgetGetAllocatedHeight rulerDrawingArea
2020-09-10 16:43:42 +00:00
mbRender <- STM.atomically $ withActiveDocument variables \ doc@( Document {..} ) -> do
mbMousePos <- STM.readTVar mousePosTVar
mbHoldAction <- STM.readTVar mouseHoldTVar
2020-09-06 03:32:03 +00:00
showGuides <- STM.readTVar showGuidesTVar
pure do
renderRuler
colours ( viewportWidth, viewportHeight ) ( RulerOrigin ruler ) ( width, height )
mbMousePos mbHoldAction showGuides
doc
for_ mbRender \ render -> Cairo.renderWithContext render ctx
pure True
2020-08-20 01:57:26 +00:00
---------------------------------------------------------
-- Tool bar
_ <- createToolBar variables colours viewportDrawingArea toolBar
2020-08-20 01:57:26 +00:00
---------------------------------------------------------
-- Info bar
2020-08-07 19:39:24 +00:00
infoBar@( InfoBar { infoBarArea } ) <- createInfoBar colours
2020-08-06 03:06:18 +00:00
rec
2020-09-01 19:56:59 +00:00
---------------------------------------------------------
-- File bar
2020-08-10 14:38:27 +00:00
fileBar@( FileBar { fileBarBox } ) <-
createFileBar
colours
variables
window titleBar title viewport infoBar menu
------------
-- Menu bar
let
uiElements :: UIElements
uiElements = UIElements { menu, fileBar, .. }
menu <- createMenuBar uiElements variables colours
GTK.boxPackStart mainView fileBarBox False False 0
GTK.boxPackStart mainView viewportGrid True True 0
GTK.boxPackStart mainView infoBarArea False False 0
2020-08-05 20:23:16 +00:00
---------------------------------------------------------
-- Panels
createPanelBar panelBox
2020-08-05 20:23:16 +00:00
---------------------------------------------------------
2020-08-04 06:15:06 +00:00
-- Actions
handleEvents uiElements variables
2020-08-07 19:39:24 +00:00
2020-08-05 20:23:16 +00:00
---------------------------------------------------------
-- GTK main loop
2020-08-04 06:15:06 +00:00
GTK.widgetShowAll window
2020-09-10 16:43:42 +00:00
mbDoc <- fmap present <$> ( STM.atomically $ activeDocument variables )
updateInfoBar viewportDrawingArea infoBar variables mbDoc -- need to update the info bar after widgets have been realized
2020-08-04 06:15:06 +00:00
GTK.main
2020-08-05 20:23:16 +00:00
exitSuccess