mirror of
https://gitlab.com/sheaf/metabrush.git
synced 2024-11-05 14:53:37 +00:00
458 lines
16 KiB
Haskell
458 lines
16 KiB
Haskell
{-# LANGUAGE BlockArguments #-}
|
||
{-# LANGUAGE ConstraintKinds #-}
|
||
{-# LANGUAGE GADTs #-}
|
||
{-# LANGUAGE LambdaCase #-}
|
||
{-# LANGUAGE OverloadedStrings #-}
|
||
{-# LANGUAGE RecordWildCards #-}
|
||
{-# LANGUAGE TypeApplications #-}
|
||
|
||
module Main
|
||
( main )
|
||
where
|
||
|
||
-- base
|
||
import Control.Monad
|
||
( void )
|
||
import Data.Foldable
|
||
( for_ )
|
||
import Data.Int
|
||
( Int32 )
|
||
import System.Exit
|
||
( exitSuccess )
|
||
|
||
-- directory
|
||
import qualified System.Directory as Directory
|
||
( canonicalizePath )
|
||
|
||
-- 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
|
||
|
||
-- text
|
||
import qualified Data.Text as Text
|
||
( pack )
|
||
|
||
-- MetaBrush
|
||
import MetaBrush.Asset.Colours
|
||
( ColourRecord(..), getColours )
|
||
import MetaBrush.Asset.Cursor
|
||
( drawCursorIcon )
|
||
import MetaBrush.Asset.Logo
|
||
( drawLogo )
|
||
import MetaBrush.Asset.Tools
|
||
( drawBrush, drawMeta, drawPath, drawPen )
|
||
import MetaBrush.Asset.WindowIcons
|
||
( drawMinimise, drawRestoreDown, drawMaximise, drawClose )
|
||
import MetaBrush.Event
|
||
( handleKeyboardPressEvent, handleKeyboardReleaseEvent )
|
||
import MetaBrush.Render.Util
|
||
( widgetAddClass, widgetAddClasses )
|
||
import MetaBrush.UI.Menu
|
||
( newMenuBar )
|
||
import qualified Paths_MetaBrush as Cabal
|
||
( getDataFileName )
|
||
|
||
--------------------------------------------------------------------------------
|
||
|
||
main :: IO ()
|
||
main = do
|
||
|
||
---------------------------------------------------------
|
||
-- Initialise GTK
|
||
|
||
void $ GTK.init Nothing
|
||
Just screen <- GDK.screenGetDefault
|
||
|
||
themePath <- Text.pack <$> ( Directory.canonicalizePath =<< Cabal.getDataFileName "theme.css" )
|
||
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
|
||
GTK.setWindowDecorated window True
|
||
GTK.setWindowTitle window "MetaBrush"
|
||
GTK.windowSetDefaultSize window 800 600
|
||
|
||
let
|
||
baseMinWidth, baseMinHeight :: Int32
|
||
baseMinWidth = 480
|
||
baseMinHeight = 240
|
||
|
||
windowGeometry <- GDK.newZeroGeometry
|
||
GDK.setGeometryMinWidth windowGeometry baseMinWidth
|
||
GDK.setGeometryMinHeight windowGeometry baseMinHeight
|
||
GTK.windowSetGeometryHints window ( Nothing @GTK.Widget )
|
||
( Just windowGeometry )
|
||
[ GDK.WindowHintsMinSize ]
|
||
|
||
iconPath <- Directory.canonicalizePath =<< Cabal.getDataFileName "icon.png"
|
||
GTK.windowSetIconFromFile window iconPath
|
||
|
||
colours <- getColours windowWidgetPath
|
||
|
||
---------------------------------------------------------
|
||
-- Create base UI elements
|
||
|
||
baseOverlay <- GTK.overlayNew
|
||
GTK.setContainerChild window baseOverlay
|
||
|
||
uiGrid <- GTK.gridNew
|
||
GTK.setContainerChild baseOverlay uiGrid
|
||
|
||
logo <- GTK.boxNew GTK.OrientationVertical 0
|
||
titleBar <- GTK.boxNew GTK.OrientationHorizontal 0
|
||
toolBar <- GTK.boxNew GTK.OrientationVertical 0
|
||
mainPane <- GTK.panedNew GTK.OrientationHorizontal
|
||
panelGrid <- GTK.gridNew
|
||
|
||
GTK.gridAttach uiGrid logo 0 0 1 2
|
||
GTK.gridAttach uiGrid titleBar 1 0 2 1
|
||
GTK.gridAttach uiGrid toolBar 0 2 2 1
|
||
GTK.gridAttach uiGrid mainPane 2 2 1 1
|
||
|
||
mainView <- GTK.boxNew GTK.OrientationVertical 0
|
||
|
||
GTK.panedPack1 mainPane mainView True False
|
||
GTK.panedPack2 mainPane panelGrid False False
|
||
|
||
fileBar <- GTK.scrolledWindowNew ( Nothing @GTK.Adjustment ) ( Nothing @GTK.Adjustment )
|
||
viewportGrid <- GTK.gridNew
|
||
infoBar <- GTK.boxNew GTK.OrientationHorizontal 0
|
||
|
||
GTK.boxPackStart mainView fileBar False False 0
|
||
GTK.boxPackStart mainView viewportGrid True True 0
|
||
GTK.boxPackStart mainView infoBar False False 0
|
||
|
||
---------------------------------------------------------
|
||
-- Background
|
||
|
||
widgetAddClass uiGrid "bg"
|
||
|
||
---------------------------------------------------------
|
||
-- Logo
|
||
|
||
widgetAddClass logo "logo"
|
||
|
||
logoArea <- GTK.drawingAreaNew
|
||
GTK.boxPackStart logo logoArea True True 0
|
||
|
||
void $ GTK.onWidgetDraw logoArea
|
||
$ Cairo.renderWithContext ( drawLogo colours )
|
||
|
||
---------------------------------------------------------
|
||
-- Title bar
|
||
|
||
widgetAddClass titleBar "titleBar"
|
||
|
||
( menuBar, _menu ) <- newMenuBar
|
||
widgetAddClasses menuBar [ "menuBar", "text", "plain" ]
|
||
GTK.boxPackStart titleBar menuBar False False 0
|
||
|
||
-- TODO: this is a bit of a workaround to add hover highlight to top-level menu items.
|
||
-- Activating a menu somehow sets the "hover" setting,
|
||
-- so instead we use the "selected" setting for actual hover highlighting.
|
||
topLevelMenuItems <- GTK.containerGetChildren menuBar
|
||
for_ topLevelMenuItems \ topLevelMenuItem -> do
|
||
void $ GTK.onWidgetEnterNotifyEvent topLevelMenuItem \ _ -> do
|
||
flags <- GTK.widgetGetStateFlags topLevelMenuItem
|
||
GTK.widgetSetStateFlags topLevelMenuItem ( GTK.StateFlagsSelected : flags ) True
|
||
pure False
|
||
void $ GTK.onWidgetLeaveNotifyEvent topLevelMenuItem \ _ -> do
|
||
flags <- GTK.widgetGetStateFlags topLevelMenuItem
|
||
GTK.widgetSetStateFlags topLevelMenuItem ( filter ( /= GTK.StateFlagsSelected ) flags ) True
|
||
pure False
|
||
|
||
windowIcons <- GTK.boxNew GTK.OrientationHorizontal 0
|
||
widgetAddClasses windowIcons [ "windowIcon" ]
|
||
GTK.boxPackEnd titleBar windowIcons False False 0
|
||
|
||
title <- GTK.labelNew ( Just "● New Document (1) – MetaBrush" )
|
||
widgetAddClasses title [ "text", "title", "plain" ]
|
||
GTK.boxSetCenterWidget titleBar ( Just title )
|
||
|
||
minimiseButton <- GTK.buttonNew
|
||
fullscreenButton <- GTK.buttonNew
|
||
closeButton <- GTK.buttonNew
|
||
|
||
GTK.boxPackStart windowIcons minimiseButton True True 0
|
||
GTK.boxPackStart windowIcons fullscreenButton True True 0
|
||
GTK.boxPackStart windowIcons closeButton True True 0
|
||
|
||
minimiseArea <- GTK.drawingAreaNew
|
||
fullscreenArea <- GTK.drawingAreaNew
|
||
closeArea <- GTK.drawingAreaNew
|
||
|
||
GTK.containerAdd minimiseButton minimiseArea
|
||
GTK.containerAdd fullscreenButton fullscreenArea
|
||
GTK.containerAdd closeButton closeArea
|
||
|
||
void $ GTK.onWidgetDraw minimiseArea
|
||
$ Cairo.renderWithContext
|
||
( drawMinimise colours )
|
||
|
||
void $ GTK.onWidgetDraw fullscreenArea \ cairoContext -> do
|
||
Just gdkWindow <- GTK.widgetGetWindow window
|
||
windowState <- GDK.windowGetState gdkWindow
|
||
if any ( \case { GDK.WindowStateFullscreen -> True; GDK.WindowStateMaximized -> True; _ -> False } ) windowState
|
||
then Cairo.renderWithContext ( drawRestoreDown colours ) cairoContext
|
||
else Cairo.renderWithContext ( drawMaximise colours ) cairoContext
|
||
|
||
void $ GTK.onWidgetDraw closeArea
|
||
$ Cairo.renderWithContext
|
||
( drawClose colours )
|
||
|
||
|
||
for_ [ minimiseButton, fullscreenButton, closeButton ] \ button -> do
|
||
widgetAddClass button "windowIcon"
|
||
|
||
widgetAddClass closeButton "closeWindowIcon"
|
||
|
||
---------------------------------------------------------
|
||
-- Tool bar
|
||
|
||
widgetAddClass toolBar "toolBar"
|
||
|
||
GTK.widgetSetValign toolBar GTK.AlignStart
|
||
GTK.widgetSetVexpand toolBar True
|
||
|
||
selectionTool <- GTK.radioButtonNew ( [] @GTK.RadioButton )
|
||
penTool <- GTK.radioButtonNewFromWidget ( Just selectionTool )
|
||
|
||
pathTool <- GTK.radioButtonNew ( [] @GTK.RadioButton )
|
||
brushTool <- GTK.radioButtonNewFromWidget ( Just pathTool )
|
||
metaTool <- GTK.radioButtonNewFromWidget ( Just pathTool )
|
||
|
||
toolSep1 <- GTK.boxNew GTK.OrientationVertical 0
|
||
|
||
GTK.boxPackStart toolBar selectionTool True True 0
|
||
GTK.boxPackStart toolBar penTool True True 0
|
||
GTK.boxPackStart toolBar toolSep1 True True 0
|
||
GTK.boxPackStart toolBar pathTool True True 0
|
||
GTK.boxPackStart toolBar brushTool True True 0
|
||
GTK.boxPackStart toolBar metaTool True True 0
|
||
|
||
for_ [ selectionTool, penTool, pathTool, brushTool, metaTool ] \ tool -> do
|
||
GTK.toggleButtonSetMode tool False -- don't display radio indicator
|
||
widgetAddClass tool "toolItem"
|
||
|
||
widgetAddClass toolSep1 "toolBarSeparator"
|
||
|
||
GTK.widgetSetTooltipText selectionTool ( Just "Select" )
|
||
GTK.widgetSetTooltipText penTool ( Just "Draw" )
|
||
GTK.widgetSetTooltipText pathTool ( Just "Brush path" )
|
||
GTK.widgetSetTooltipText brushTool ( Just "Brushes" )
|
||
GTK.widgetSetTooltipText metaTool ( Just "Meta-parameters" )
|
||
|
||
selectionToolArea <- GTK.drawingAreaNew
|
||
penToolArea <- GTK.drawingAreaNew
|
||
pathToolArea <- GTK.drawingAreaNew
|
||
brushToolArea <- GTK.drawingAreaNew
|
||
metaToolArea <- GTK.drawingAreaNew
|
||
|
||
{-
|
||
for_ [ selectionToolArea, brushToolArea, metaToolArea ] \ toolArea -> do
|
||
GTK.widgetSetValign toolArea GTK.AlignCenter
|
||
GTK.widgetSetHalign toolArea GTK.AlignCenter
|
||
-}
|
||
|
||
GTK.containerAdd selectionTool selectionToolArea
|
||
GTK.containerAdd penTool penToolArea
|
||
GTK.containerAdd pathTool pathToolArea
|
||
GTK.containerAdd brushTool brushToolArea
|
||
GTK.containerAdd metaTool metaToolArea
|
||
|
||
void $ GTK.onWidgetDraw selectionToolArea
|
||
$ Cairo.renderWithContext
|
||
( drawCursorIcon colours )
|
||
|
||
void $ GTK.onWidgetDraw penToolArea
|
||
$ Cairo.renderWithContext
|
||
( drawPen colours )
|
||
|
||
void $ GTK.onWidgetDraw pathToolArea
|
||
$ Cairo.renderWithContext
|
||
( drawPath colours )
|
||
|
||
void $ GTK.onWidgetDraw brushToolArea
|
||
$ Cairo.renderWithContext
|
||
( drawBrush colours )
|
||
|
||
void $ GTK.onWidgetDraw metaToolArea
|
||
$ Cairo.renderWithContext
|
||
( drawMeta colours )
|
||
|
||
---------------------------------------------------------
|
||
-- File bar
|
||
|
||
widgetAddClass fileBar "fileBar"
|
||
|
||
fileTabs <- GTK.boxNew GTK.OrientationHorizontal 0
|
||
GTK.containerAdd fileBar fileTabs
|
||
widgetAddClasses fileTabs [ "fileBar", "plain", "text" ]
|
||
|
||
fileBarPhantomRadioButton <- GTK.radioButtonNew ( [] @GTK.RadioButton )
|
||
|
||
for_ [ 1 .. 12 ] \ i -> do
|
||
-- File tab elements.
|
||
pgButton <- GTK.radioButtonNewWithLabelFromWidget ( Just fileBarPhantomRadioButton ) ( "New Document (" <> Text.pack ( show i ) <> ")" )
|
||
GTK.toggleButtonSetMode pgButton False -- don't display radio indicator
|
||
closeFileButton <- GTK.buttonNewWithLabel "x"
|
||
|
||
-- Create box for file tab elements.
|
||
tab <- GTK.boxNew GTK.OrientationHorizontal 0
|
||
widgetAddClasses tab [ "fileBarTab" ]
|
||
GTK.boxPackStart fileTabs tab False False 0
|
||
GTK.boxPackStart tab pgButton True True 0
|
||
GTK.boxPackStart tab closeFileButton False False 0
|
||
|
||
widgetAddClasses pgButton [ "fileBarTabButton" ]
|
||
widgetAddClasses closeFileButton [ "fileBarCloseButton" ]
|
||
|
||
-- Make both file tab elements activate styling on the whole tab
|
||
-- (e.g. hovering over the close file button should highlight the whole tab).
|
||
void $ GTK.onButtonClicked pgButton do
|
||
isActive <- GTK.toggleButtonGetActive pgButton
|
||
flags <- GTK.widgetGetStateFlags tab
|
||
if isActive
|
||
then GTK.widgetSetStateFlags tab ( GTK.StateFlagsActive : flags ) True
|
||
else GTK.widgetSetStateFlags tab ( filter ( /= GTK.StateFlagsActive ) flags ) True
|
||
|
||
{-
|
||
void $ GTK.onButtonClicked closeFileButton do
|
||
closeFileDialog ...
|
||
-}
|
||
|
||
for_ @_ @_ @_ @() [ Exists @GTK.IsWidget pgButton, Exists @GTK.IsWidget closeFileButton ] \ ( Exists button ) -> do
|
||
void $ GTK.onWidgetEnterNotifyEvent button \ _ -> do
|
||
flags <- GTK.widgetGetStateFlags tab
|
||
GTK.widgetSetStateFlags tab ( GTK.StateFlagsPrelight : flags ) True
|
||
pure False
|
||
void $ GTK.onWidgetLeaveNotifyEvent button \ _ -> do
|
||
flags <- GTK.widgetGetStateFlags tab
|
||
GTK.widgetSetStateFlags tab ( filter ( /= GTK.StateFlagsPrelight ) flags ) True
|
||
pure False
|
||
|
||
GTK.scrolledWindowSetPolicy fileBar GTK.PolicyTypeAutomatic GTK.PolicyTypeNever
|
||
GTK.scrolledWindowSetOverlayScrolling fileBar True
|
||
|
||
---------------------------------------------------------
|
||
-- Main viewport
|
||
|
||
widgetAddClass viewportGrid "viewport"
|
||
|
||
rvRulerCorner <- GTK.revealerNew
|
||
rvLeftRuler <- GTK.revealerNew
|
||
rvTopRuler <- GTK.revealerNew
|
||
viewportOverlay <- GTK.overlayNew
|
||
|
||
GTK.gridAttach viewportGrid rvRulerCorner 0 0 1 1
|
||
GTK.gridAttach viewportGrid rvLeftRuler 0 1 1 1
|
||
GTK.gridAttach viewportGrid rvTopRuler 1 0 1 1
|
||
GTK.gridAttach viewportGrid viewportOverlay 1 1 1 1
|
||
|
||
rulerCorner <- GTK.boxNew GTK.OrientationVertical 0
|
||
leftRuler <- GTK.boxNew GTK.OrientationVertical 0
|
||
topRuler <- GTK.boxNew GTK.OrientationHorizontal 0
|
||
|
||
GTK.containerAdd rvRulerCorner rulerCorner
|
||
GTK.containerAdd rvLeftRuler leftRuler
|
||
GTK.containerAdd rvTopRuler topRuler
|
||
|
||
widgetAddClass rulerCorner "ruler"
|
||
widgetAddClass leftRuler "ruler"
|
||
widgetAddClass topRuler "ruler"
|
||
|
||
GTK.revealerSetRevealChild rvRulerCorner True
|
||
GTK.revealerSetRevealChild rvLeftRuler True
|
||
GTK.revealerSetRevealChild rvTopRuler True
|
||
|
||
GTK.revealerSetTransitionType rvRulerCorner GTK.RevealerTransitionTypeSlideLeft
|
||
GTK.revealerSetTransitionType rvLeftRuler GTK.RevealerTransitionTypeSlideLeft
|
||
GTK.revealerSetTransitionType rvTopRuler GTK.RevealerTransitionTypeSlideUp
|
||
|
||
rulerCornerArea <- GTK.drawingAreaNew
|
||
GTK.boxPackStart rulerCorner rulerCornerArea True True 0
|
||
|
||
leftRulerArea <- GTK.drawingAreaNew
|
||
GTK.boxPackStart leftRuler leftRulerArea True True 0
|
||
|
||
topRulerArea <- GTK.drawingAreaNew
|
||
GTK.boxPackStart topRuler topRulerArea True True 0
|
||
|
||
GTK.widgetSetHexpand rulerCorner False
|
||
GTK.widgetSetVexpand rulerCorner False
|
||
GTK.widgetSetHexpand leftRuler False
|
||
GTK.widgetSetVexpand leftRuler True
|
||
GTK.widgetSetHexpand topRuler True
|
||
GTK.widgetSetVexpand topRuler False
|
||
GTK.widgetSetHexpand viewportOverlay True
|
||
GTK.widgetSetVexpand viewportOverlay True
|
||
|
||
viewportArea <- GTK.drawingAreaNew
|
||
GTK.setContainerChild viewportOverlay viewportArea
|
||
|
||
viewportScrollbarGrid <- GTK.gridNew
|
||
GTK.overlayAddOverlay viewportOverlay viewportScrollbarGrid
|
||
|
||
viewportHScrollbar <- GTK.scrollbarNew GTK.OrientationHorizontal ( Nothing @GTK.Adjustment )
|
||
viewportVScrollbar <- GTK.scrollbarNew GTK.OrientationVertical ( Nothing @GTK.Adjustment )
|
||
GTK.widgetSetValign viewportHScrollbar GTK.AlignEnd
|
||
GTK.widgetSetHalign viewportVScrollbar GTK.AlignEnd
|
||
GTK.widgetSetVexpand viewportVScrollbar True
|
||
GTK.widgetSetHexpand viewportHScrollbar True
|
||
GTK.gridAttach viewportScrollbarGrid viewportHScrollbar 0 1 1 1
|
||
GTK.gridAttach viewportScrollbarGrid viewportVScrollbar 1 0 1 1
|
||
widgetAddClass viewportHScrollbar "viewportScrollbar"
|
||
widgetAddClass viewportVScrollbar "viewportScrollbar"
|
||
|
||
---------------------------------------------------------
|
||
-- Panels
|
||
|
||
widgetAddClass panelGrid "panels"
|
||
|
||
---------------------------------------------------------
|
||
-- Info bar
|
||
|
||
widgetAddClass infoBar "infoBar"
|
||
|
||
---------------------------------------------------------
|
||
-- Actions
|
||
|
||
GTK.widgetAddEvents window [GDK.EventMaskKeyPressMask, GDK.EventMaskKeyReleaseMask]
|
||
|
||
_ <- GTK.onButtonClicked closeButton GTK.mainQuit
|
||
_ <- GTK.onButtonClicked minimiseButton ( GTK.windowIconify window )
|
||
_ <- GTK.onButtonClicked fullscreenButton do
|
||
Just gdkWindow <- GTK.widgetGetWindow window
|
||
windowState <- GDK.windowGetState gdkWindow
|
||
if GDK.WindowStateFullscreen `elem` windowState
|
||
then GTK.windowUnfullscreen window
|
||
else
|
||
if GDK.WindowStateMaximized `elem` windowState
|
||
then GTK.windowUnmaximize window
|
||
else GTK.windowMaximize window
|
||
|
||
_ <- GTK.onWidgetKeyPressEvent window handleKeyboardPressEvent
|
||
_ <- GTK.onWidgetKeyReleaseEvent window handleKeyboardReleaseEvent
|
||
_ <- GTK.onWidgetDestroy window GTK.mainQuit
|
||
|
||
---------------------------------------------------------
|
||
-- GTK main loop
|
||
|
||
GTK.widgetShowAll window
|
||
GTK.main
|
||
|
||
exitSuccess
|
||
|
||
|
||
data Exists c where
|
||
Exists :: c a => a -> Exists c |