sifflet-0.1.5: UITypes.hs
module UITypes (VPUI(..)
, WinId, VPUIWindow(..)
, vpuiUserEnvAList
-- operations on a VPUI involving its window
, vpuiInsertWindow
, vpuiTryGetWindow
, vpuiGetWindow
, vpuiUpdateWindow
, vpuiReplaceWindow
, vpuiUpdateWindowIO
, vpuiRemoveVPUIWindow
-- operations on a window involving its canvas
, vpuiWindowLookupCanvas, vpuiWindowGetCanvas
, vpuiWindowSetCanvas, vpuiWindowModCanvas
, vpuiWindowModCanvasIO
-- operation on a VPUI involving the canvas of its window
, vpuiModCanvas, vpuiModCanvasIO
-- other operations on a window
, vpuiWindowWindow
, VPToolkit(..), Toolbox(..), Tool(..), ToolContext(..)
, CanvasToolOp
, ToolOp
, toToolOpVW
, Workspace(..)
, FunctionEditor(..), fedFunctionName
, VCanvas(..), Selection(..), Dragging(..)
)
where
import Data.Map as Map
import Data.Graph.Inductive as G
import Graphics.UI.Gtk
hiding (Function, Layout, Style, Size)
import qualified Graphics.UI.Gtk as Gtk (Frame, Layout)
import Graphics.UI.Gtk.Gdk.EventM (Modifier(..))
import RPanel
import TreeLayout
import Workspace.Frame
import Workspace.WGraph
import Expr
import Geometry
-- | VPUI: Sifflet (formerly VisiProg) User Interface
-- The initialEnv is apt to contain "builtin" functions;
-- it's preserved here so that when writing to a file,
-- we can skip the functions that were in the initial env.
data VPUI = VPUI {
vpuiWindows :: Map WinId VPUIWindow,
vpuiToolkits :: Map String VPToolkit, -- collections of tools
-- A phantom button (never shown) to represent the group of all tools:
vpuiButtonGroup :: RadioToolButton,
vpuiFilePath :: Maybe FilePath, -- the file opened or to save
vpuiFileChanged :: Bool, -- has file changed since open/save?
vpuiStyle :: Style, -- for windows, canvases, editors
vpuiInitialEnv :: Env, -- initial value of global environment
vpuiGlobalEnv :: Env -- the global environment
}
-- | Extract from the environment the part defined by the user
vpuiUserEnvAList :: VPUI -> [(String, Value)]
vpuiUserEnvAList vpui =
let env' = vpuiGlobalEnv vpui -- I hope
env = vpuiInitialEnv vpui
in if length env == 1 && length env' == 1
then assocs (Map.difference (head env') (head env))
else error ("vpuiUserEnv: env lengths are not one" ++
"|env'|: " ++ show (length env') ++
"|env|: " ++ show (length env))
-- | Insert a window in the window map
vpuiInsertWindow :: VPUI -> WinId -> VPUIWindow -> VPUI
vpuiInsertWindow vpui winId vw =
vpui {vpuiWindows = Map.insert winId vw (vpuiWindows vpui)}
-- | Try to get the VPUIWindow with the given window ID,
-- return Just result or Nothing
vpuiTryGetWindow :: VPUI -> WinId -> Maybe VPUIWindow
vpuiTryGetWindow vpui winId = Map.lookup winId (vpuiWindows vpui)
-- | Get the VPUIWindow with the given window ID;
-- it is an error if this fails.
vpuiGetWindow :: VPUI -> WinId -> VPUIWindow
vpuiGetWindow vpui winId = vpuiWindows vpui ! winId
-- | Replace a VPUIWindow with given window ID;
-- it is an error if this fails.
vpuiReplaceWindow :: VPUI -> WinId -> VPUIWindow -> VPUI
vpuiReplaceWindow vpui winId vpuiWin =
let winMap = vpuiWindows vpui
winMap' = insert winId vpuiWin winMap
in vpui {vpuiWindows = winMap'}
-- | Apply an update function to a VPUIWindow with given window ID;
-- it is an error if this fails.
vpuiUpdateWindow :: VPUI -> WinId -> (VPUIWindow -> VPUIWindow) -> VPUI
vpuiUpdateWindow vpui winId updater =
let winMap = vpuiWindows vpui
winMap' = adjust updater winId winMap
in vpui {vpuiWindows = winMap'}
-- | Apply an update IO action to a VPUIWindow with given window ID;
-- it is an error if this fails.
vpuiUpdateWindowIO :: WinId -> (VPUIWindow -> IO VPUIWindow) -> VPUI -> IO VPUI
vpuiUpdateWindowIO winId updater vpui = do
{
let winMap = vpuiWindows vpui
vw = winMap ! winId
; vw' <- updater vw
; let winMap' = insert winId vw' winMap
; return $ vpui {vpuiWindows = winMap'}
}
-- | Remove a window from the windows map; it has already been destroyed
-- in the GUI
vpuiRemoveVPUIWindow :: WinId -> VPUI -> VPUI
vpuiRemoveVPUIWindow winId vpui =
let winMap = vpuiWindows vpui
winMap' = delete winId winMap
in vpui {vpuiWindows = winMap'}
data VPUIWindow = -- VPUIJustWindow Window
VPUIWorkWin Workspace Window
| FunctionPadWindow Window [(String, RPanel)]
vpuiWindowWindow :: VPUIWindow -> Window
vpuiWindowWindow vw =
case vw of
VPUIWorkWin _ w -> w
FunctionPadWindow w _ -> w
-- | Try to find canvas; fail gracefully
vpuiWindowLookupCanvas :: VPUIWindow -> Maybe VCanvas
vpuiWindowLookupCanvas vw =
case vw of
VPUIWorkWin ws _ -> Just (wsCanvas ws)
_ -> Nothing
-- | Find canvas or fail dramatically
vpuiWindowGetCanvas :: VPUIWindow -> VCanvas
vpuiWindowGetCanvas vw =
case vpuiWindowLookupCanvas vw of
Nothing -> error "vpuiWindowGetCanvas: no canvas found"
Just canvas -> canvas
vpuiWindowSetCanvas :: VPUIWindow -> VCanvas -> VPUIWindow
vpuiWindowSetCanvas vw canvas =
case vw of
VPUIWorkWin ws w -> VPUIWorkWin (ws {wsCanvas = canvas}) w
_ -> error "vpuiWindowSetCanvas: not a workspace window"
vpuiWindowModCanvas :: VPUIWindow -> (VCanvas -> VCanvas) -> VPUIWindow
vpuiWindowModCanvas vw f =
case vpuiWindowLookupCanvas vw of
Nothing -> error "vpuiWindowModCanvas: plain VPUIWindow"
Just canvas -> vpuiWindowSetCanvas vw (f canvas)
vpuiWindowModCanvasIO :: VPUIWindow -> (VCanvas -> IO VCanvas) -> IO VPUIWindow
vpuiWindowModCanvasIO vw f =
case vpuiWindowLookupCanvas vw of
Nothing -> error "vpuiWindowModCanvas: plain VPUIWindow"
Just canvas ->
do
{
canvas' <- f canvas
; return $ vpuiWindowSetCanvas vw canvas'
}
-- | Update the canvas of the specified window, without IO
vpuiModCanvas :: VPUI -> WinId -> (VCanvas -> VCanvas) -> VPUI
vpuiModCanvas vpui winId modCanvas =
let modWindow vw = vpuiWindowModCanvas vw modCanvas
in vpuiUpdateWindow vpui winId modWindow
-- | Update the canvas of the specified window, with IO
vpuiModCanvasIO :: VPUI -> WinId -> (VCanvas -> IO VCanvas) -> IO VPUI
vpuiModCanvasIO vpui winId modCanvas =
let modWindow vw = vpuiWindowModCanvasIO vw modCanvas
in vpuiUpdateWindowIO winId modWindow vpui
type WinId = String
data Workspace = Workspace {wsBox :: VBox, -- ^ container of the rest
wsCanvas :: VCanvas, -- ^ the canvas
wsButtonBar :: HBox,
wsStatusbar :: Statusbar}
data FunctionEditor = FunctionEditor {fedWindow :: Window,
fedWinTitle :: String,
fedFunction :: Function,
fedCanvas :: VCanvas
-- , fedUIRef :: IORef VPUI
}
fedFunctionName :: FunctionEditor -> String
fedFunctionName = functionName . fedFunction
-- | Toolkit functions are organized in groups (rows) for presentation
-- in a toolbox
data VPToolkit = VPToolkit {toolkitName :: String,
toolkitWidth :: Int, -- (-1) = don't care
toolkitRows :: [[Tool]]}
-- | A Toolbox is a framed VBox with a set of Toolbars attached
data Toolbox = Toolbox {toolboxFrame :: Gtk.Frame
, toolboxVBox :: VBox}
-- | ToolOp a is intended for a = VPUIWindow or VCanvas
-- type ToolOp a
-- = VPUI -> a -> ToolContext -> [Modifier] -> Double -> Double -> IO a
type ToolOp
= VPUI -> WinId -> ToolContext -> [Modifier] -> Double -> Double -> IO VPUI
type CanvasToolOp
= VCanvas -> ToolContext -> [Modifier] -> Double -> Double -> IO VCanvas
data Tool = Tool {toolName :: String, -- the tool's name
-- what to do when the tool is selected from the toolbox
toolActivated :: VCanvas -> IO VCanvas,
-- what to do to apply the tool to a point on the canvas
toolOp :: ToolOp
}
-- | A helper for making toolOps from actions on VCanvas
toToolOpVW :: CanvasToolOp -> ToolOp
toToolOpVW vcOp vpui winId toolContext mods x y = do
{
let vw = vpuiGetWindow vpui winId
canv = vpuiWindowGetCanvas vw
; canv' <- vcOp canv toolContext mods x y
; let vw' = vpuiWindowSetCanvas vw canv'
; return $ vpuiReplaceWindow vpui winId vw'
}
-- | ToolContext: The way a tool should be applied depends on
-- where it is being used
data ToolContext = TCWorkspace
| TCCallFrame CanvFrame
| TCEditFrame CanvFrame
| TCExprNode -- ???
-- | A canvas that can display multiple boxes representing
-- expressions or function definitions or calls
data VCanvas = VCanvas {
vcLayout :: Gtk.Layout,
vcStyle :: Style,
vcGraph :: WGraph,
vcFrames :: [CanvFrame],
vcSize :: Size,
-- vcLocalEnv :: Env, -- only good for function editor, I think?
vcMousePos :: (Double, Double),
vcTool :: Maybe Tool, -- current tool on this canvas
vcActive :: Maybe Node, -- active node, if any
vcSelected :: Maybe Selection, -- selected node(s), if any
vcDragging :: Maybe Dragging -- what we're dragging, if anything
}
data Selection = SelectionNode {selNode :: G.Node}
| SelectionInlet {selNode :: G.Node,
selInEdge :: WEdge} -- numbered from 0
| SelectionOutlet {selNode :: G.Node,
selOutEdge :: WEdge} -- normally just 0
deriving (Eq, Read, Show)
-- | A Dragging keeps track of the object (node) being dragged
-- and the current mouse position.
data Dragging = Dragging { draggingNode :: G.Node,
draggingPosition :: Position
}
deriving (Eq, Read, Show)