2020-08-05 20:23:16 +00:00
|
|
|
{-# LANGUAGE BlockArguments #-}
|
|
|
|
{-# LANGUAGE DataKinds #-}
|
|
|
|
{-# LANGUAGE DeriveGeneric #-}
|
|
|
|
{-# LANGUAGE DerivingStrategies #-}
|
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
|
|
|
{-# LANGUAGE FunctionalDependencies #-}
|
2020-08-10 22:07:09 +00:00
|
|
|
{-# LANGUAGE LambdaCase #-}
|
2020-08-05 20:23:16 +00:00
|
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
{-# LANGUAGE PolyKinds #-}
|
|
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
|
|
{-# LANGUAGE TypeApplications #-}
|
|
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
|
|
|
|
|
|
module MetaBrush.UI.Menu
|
|
|
|
( newMenuBar
|
|
|
|
, Menu(..)
|
|
|
|
, FileMenu(..), EditMenu(..), ViewMenu(..), HelpMenu(..)
|
|
|
|
, ResourceType(..)
|
|
|
|
, MenuItem(..)
|
2020-08-10 22:07:09 +00:00
|
|
|
, createMenuBar
|
2020-08-05 20:23:16 +00:00
|
|
|
)
|
|
|
|
where
|
|
|
|
|
|
|
|
-- base
|
|
|
|
import Control.Monad
|
2020-08-10 22:07:09 +00:00
|
|
|
( void, unless )
|
2020-08-05 20:23:16 +00:00
|
|
|
import Data.Foldable
|
|
|
|
( for_ )
|
|
|
|
import Data.Kind
|
|
|
|
( Type )
|
|
|
|
import GHC.Generics
|
|
|
|
( Generic )
|
|
|
|
|
|
|
|
-- generic-lens
|
|
|
|
import Data.Generics.Product.Constraints
|
|
|
|
( HasConstraints(constraints) )
|
|
|
|
|
2020-08-10 22:07:09 +00:00
|
|
|
-- gi-cairo-connector
|
|
|
|
import qualified GI.Cairo.Render.Connector as Cairo
|
|
|
|
( renderWithContext )
|
|
|
|
|
|
|
|
-- gi-gdk
|
|
|
|
import qualified GI.Gdk as GDK
|
|
|
|
|
2020-08-05 20:23:16 +00:00
|
|
|
-- gi-gtk
|
|
|
|
import qualified GI.Gtk as GTK
|
|
|
|
|
|
|
|
-- text
|
|
|
|
import Data.Text
|
|
|
|
( Text )
|
|
|
|
|
|
|
|
-- transformers
|
|
|
|
import Control.Monad.IO.Class
|
|
|
|
( MonadIO(liftIO) )
|
|
|
|
|
|
|
|
-- MetaBrush
|
2020-08-10 22:07:09 +00:00
|
|
|
import MetaBrush.Asset.Colours
|
|
|
|
( Colours )
|
|
|
|
import MetaBrush.Asset.WindowIcons
|
|
|
|
( drawMinimise, drawRestoreDown, drawMaximise, drawClose )
|
2020-08-05 20:23:16 +00:00
|
|
|
import MetaBrush.Render.Util
|
2020-08-10 22:07:09 +00:00
|
|
|
( widgetAddClass, widgetAddClasses )
|
2020-08-05 20:23:16 +00:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Types for describing menu items.
|
|
|
|
|
|
|
|
data ResourceType
|
|
|
|
= Description
|
|
|
|
| Object
|
|
|
|
|
|
|
|
data family MenuItem ( submenu :: ResourceType -> Type ) ( r :: ResourceType )
|
|
|
|
data instance MenuItem submenu Description
|
|
|
|
= MenuItemDescription
|
|
|
|
{ menuItemLabel :: !Text
|
|
|
|
, menuItemClasses :: ![ Text ]
|
|
|
|
, menuItemAction :: !( Maybe Text )
|
|
|
|
, submenuDescription :: !( submenu Description )
|
|
|
|
}
|
|
|
|
data instance MenuItem submenu Object
|
|
|
|
= MenuItem
|
|
|
|
{ menuItem :: !GTK.MenuItem
|
|
|
|
, menuItemSubmenu :: !( submenu Object )
|
|
|
|
}
|
|
|
|
|
|
|
|
data family Separator ( r :: ResourceType )
|
|
|
|
data instance Separator Description
|
|
|
|
= SeparatorDescription
|
|
|
|
{ separatorClasses :: ![ Text ] }
|
|
|
|
data instance Separator Object
|
|
|
|
= Separator
|
|
|
|
{ separatorItem :: !GTK.MenuItem }
|
|
|
|
|
|
|
|
data NoSubresource ( k :: ResourceType ) = NoSubresource
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Menu used in MetaBrush.
|
|
|
|
|
|
|
|
-- Types.
|
|
|
|
|
|
|
|
data Menu ( rt :: ResourceType )
|
|
|
|
= Menu
|
|
|
|
{ file :: !( MenuItem FileMenu rt )
|
|
|
|
, edit :: !( MenuItem EditMenu rt )
|
|
|
|
, view :: !( MenuItem ViewMenu rt )
|
|
|
|
, help :: !( MenuItem HelpMenu rt )
|
|
|
|
}
|
|
|
|
deriving stock Generic
|
|
|
|
|
|
|
|
data FileMenu ( rt :: ResourceType )
|
|
|
|
= FileMenu
|
2020-08-23 22:40:16 +00:00
|
|
|
{ new, open, save, saveAs, close, quit :: !( MenuItem NoSubresource rt ) }
|
2020-08-05 20:23:16 +00:00
|
|
|
deriving stock Generic
|
|
|
|
|
|
|
|
data EditMenu ( rt :: ResourceType )
|
|
|
|
= EditMenu
|
2020-08-23 22:40:16 +00:00
|
|
|
{ undo, redo, history :: !( MenuItem NoSubresource rt )
|
|
|
|
, editSep1 :: !( Separator rt )
|
|
|
|
, cut, copy, paste, duplicate, delete :: !( MenuItem NoSubresource rt )
|
|
|
|
, editSep2 :: !( Separator rt )
|
2020-08-05 20:23:16 +00:00
|
|
|
, preferences :: !( MenuItem NoSubresource rt )
|
|
|
|
}
|
|
|
|
deriving stock Generic
|
|
|
|
|
|
|
|
data ViewMenu ( rt :: ResourceType )
|
|
|
|
= ViewMenu
|
2020-08-23 22:40:16 +00:00
|
|
|
{ navigator :: !( MenuItem NoSubresource rt )
|
|
|
|
, viewSep1 :: !( Separator rt )
|
|
|
|
, strokes, brushes, metaparameters :: !( MenuItem NoSubresource rt )
|
|
|
|
, viewSep2 :: !( Separator rt )
|
|
|
|
, transform :: !( MenuItem NoSubresource rt )
|
2020-08-05 20:23:16 +00:00
|
|
|
}
|
|
|
|
deriving stock Generic
|
|
|
|
|
|
|
|
data HelpMenu ( rt :: ResourceType )
|
|
|
|
= HelpMenu
|
|
|
|
{ about :: !( MenuItem NoSubresource rt ) }
|
|
|
|
deriving stock Generic
|
|
|
|
|
|
|
|
-- Descriptions.
|
|
|
|
|
|
|
|
menuDescription :: Menu Description
|
|
|
|
menuDescription
|
|
|
|
= Menu
|
|
|
|
{ file = MenuItemDescription "File" [ "menuItem", "file" ] Nothing fileMenuDescription
|
|
|
|
, edit = MenuItemDescription "Edit" [ "menuItem", "edit" ] Nothing editMenuDescription
|
|
|
|
, view = MenuItemDescription "View" [ "menuItem", "view" ] Nothing viewMenuDescription
|
|
|
|
, help = MenuItemDescription "Help" [ "menuItem", "help" ] Nothing helpMenuDescription
|
|
|
|
}
|
|
|
|
|
|
|
|
fileMenuDescription :: FileMenu Description
|
|
|
|
fileMenuDescription
|
|
|
|
= FileMenu
|
|
|
|
{ new = MenuItemDescription "New" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, open = MenuItemDescription "Open" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, save = MenuItemDescription "Save" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, saveAs = MenuItemDescription "Save as" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, close = MenuItemDescription "Close" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, quit = MenuItemDescription "Quit" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
}
|
|
|
|
|
|
|
|
editMenuDescription :: EditMenu Description
|
|
|
|
editMenuDescription
|
|
|
|
= EditMenu
|
|
|
|
{ undo = MenuItemDescription "Undo" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, redo = MenuItemDescription "Redo" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, history = MenuItemDescription "History" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, editSep1 = SeparatorDescription [ "submenuSeparator" ]
|
|
|
|
, cut = MenuItemDescription "Cut" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, copy = MenuItemDescription "Copy" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, paste = MenuItemDescription "Paste" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, duplicate = MenuItemDescription "Duplicate" [ "submenuItem" ] Nothing NoSubresource
|
2020-08-06 03:06:18 +00:00
|
|
|
, delete = MenuItemDescription "Delete" [ "submenuItem" ] Nothing NoSubresource
|
2020-08-05 20:23:16 +00:00
|
|
|
, editSep2 = SeparatorDescription [ "submenuSeparator" ]
|
|
|
|
, preferences = MenuItemDescription "Preferences" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
}
|
|
|
|
|
|
|
|
viewMenuDescription :: ViewMenu Description
|
|
|
|
viewMenuDescription
|
|
|
|
= ViewMenu
|
|
|
|
{ navigator = MenuItemDescription "Navigator" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, viewSep1 = SeparatorDescription [ "submenuSeparator" ]
|
|
|
|
, strokes = MenuItemDescription "Strokes" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, brushes = MenuItemDescription "Brushes" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, metaparameters = MenuItemDescription "Metaparameters" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
, viewSep2 = SeparatorDescription [ "submenuSeparator" ]
|
|
|
|
, transform = MenuItemDescription "Transform" [ "submenuItem" ] Nothing NoSubresource
|
|
|
|
}
|
|
|
|
|
|
|
|
helpMenuDescription :: HelpMenu Description
|
|
|
|
helpMenuDescription
|
|
|
|
= HelpMenu
|
|
|
|
{ about = MenuItemDescription "About MetaBrush" [ "submenuItem" ] Nothing NoSubresource }
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Creating a GTK menu bar from a menu description.
|
|
|
|
|
|
|
|
newMenuItem :: MonadIO m => MenuItem submenu Description -> m GTK.MenuItem
|
|
|
|
newMenuItem ( MenuItemDescription { .. } ) = do
|
|
|
|
menuItem <- GTK.menuItemNewWithLabel menuItemLabel
|
|
|
|
-- Could set accelerator labels...
|
|
|
|
unless ( null menuItemClasses ) do
|
|
|
|
widgetAddClasses menuItem menuItemClasses
|
|
|
|
for_ menuItemAction \ actionName -> do
|
|
|
|
GTK.actionableSetActionName menuItem ( Just actionName )
|
|
|
|
pure menuItem
|
|
|
|
|
|
|
|
class CreateMenuItem desc res | desc -> res, res -> desc where
|
|
|
|
createMenuItem :: MonadIO m => ( GTK.MenuItem -> m () ) -> desc -> m res
|
|
|
|
instance {-# OVERLAPPING #-} CreateMenuItem ( MenuItem NoSubresource Description ) ( MenuItem NoSubresource Object ) where
|
|
|
|
createMenuItem attachToParent menuItemDescription = do
|
|
|
|
menuItem <- newMenuItem menuItemDescription
|
|
|
|
attachToParent menuItem
|
|
|
|
pure
|
|
|
|
MenuItem
|
|
|
|
{ menuItem = menuItem
|
|
|
|
, menuItemSubmenu = NoSubresource
|
|
|
|
}
|
|
|
|
instance ( HasConstraints CreateMenuItem ( submenu Description ) ( submenu Object ) )
|
|
|
|
=> CreateMenuItem ( MenuItem submenu Description ) ( MenuItem submenu Object )
|
|
|
|
where
|
|
|
|
createMenuItem attachToParent menuItemDescription@( MenuItemDescription { submenuDescription } ) = do
|
|
|
|
menuItem <- newMenuItem menuItemDescription
|
|
|
|
submenu <- GTK.menuNew
|
|
|
|
submenuItems <-
|
|
|
|
constraints @CreateMenuItem
|
|
|
|
( createMenuItem ( GTK.menuShellAppend submenu ) )
|
|
|
|
submenuDescription
|
|
|
|
GTK.menuItemSetSubmenu menuItem ( Just submenu )
|
|
|
|
attachToParent menuItem
|
|
|
|
pure
|
|
|
|
MenuItem
|
|
|
|
{ menuItem = menuItem
|
|
|
|
, menuItemSubmenu = submenuItems
|
|
|
|
}
|
|
|
|
instance CreateMenuItem ( Separator Description ) ( Separator Object ) where
|
|
|
|
createMenuItem attachToParent ( SeparatorDescription { .. } ) = do
|
|
|
|
separator <- GTK.separatorMenuItemNew
|
|
|
|
unless ( null separatorClasses ) do
|
|
|
|
widgetAddClasses separator separatorClasses
|
|
|
|
sep <- liftIO ( GTK.unsafeCastTo GTK.MenuItem separator )
|
|
|
|
attachToParent sep
|
|
|
|
pure ( Separator { separatorItem = sep } )
|
|
|
|
|
|
|
|
newMenuBar :: MonadIO m => m ( GTK.MenuBar, Menu Object )
|
|
|
|
newMenuBar = do
|
|
|
|
menuBar <- GTK.menuBarNew
|
|
|
|
menu <-
|
|
|
|
constraints @CreateMenuItem
|
|
|
|
( createMenuItem ( GTK.menuShellAppend menuBar ) )
|
|
|
|
menuDescription
|
|
|
|
pure ( menuBar, menu )
|
2020-08-10 22:07:09 +00:00
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- Creating the menu bar from its declarative specification.
|
|
|
|
|
|
|
|
-- | Add the menu bar to the given box (title bar box).
|
|
|
|
createMenuBar :: Colours -> GTK.Window -> GTK.Box -> IO ( Menu Object )
|
|
|
|
createMenuBar colours window titleBar = do
|
|
|
|
( 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
|
|
|
|
|
|
|
|
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"
|
|
|
|
|
|
|
|
---------------------------------------------------------
|
|
|
|
-- Actions
|
|
|
|
|
|
|
|
_ <- 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
|
|
|
|
|
|
|
|
---------------------------------------------------------
|
|
|
|
|
|
|
|
pure menu
|