module Main (main) where import Prelude hiding (catch, log) import Control.Concurrent (forkIO) import Control.Monad (forM_) import Data.IORef (IORef, newIORef, readIORef, atomicModifyIORef, writeIORef) import Data.Maybe (mapMaybe) import Graphics.UI.Gtk hiding (get, Region, Size, Window, Viewport) import qualified Graphics.UI.Gtk as GTK import Graphics.UI.Gtk.OpenGL (initGL) import System.Directory (getAppUserDataDirectory, createDirectoryIfMissing) import System.FilePath (()) import Fractal.RUFF.Types.Complex (Complex((:+)), realPart, imagPart) import Paths_gruff (getDataFileName) import Number (R) import Browser (Browser(..), browserNew, browserRenders) import Progress (progressNew) import Interact (mouseCallbacks) import View ( Image(..), Location(..), Viewport(..), Window(..) , Colours(..), Colour(..), defImage, defLocation ) import GLUTGtk (glut, Size(Size), postRedisplay) import Logger (logger, LogLevel(Debug)) import qualified Logger as Log import Utils (safeRead, catchIO) exit :: (LogLevel -> String -> IO ()) -> FilePath -> Gruff -> IO () exit lg stateFile g = do lg Debug "exitCallback" aDoSave stateFile g mainQuit data GruffGUI = GruffGUI -- buttons { bHome , bLoad , bSave , bStop , bClear :: Button -- entries , eRealM, eRealE , eImagM, eImagE , eSizeM, eSizeE , eRota , eWidth , eHeight , eSamples :: Entry -- colour pickers , cInterior , cBorder , cExterior :: ColorButton -- windows , wMain , wImage :: GTK.Window } main :: IO () main = do -- contexts _ <- initGUI _ <- initGL gl' <- glut minSize -- directories appDir <- getAppUserDataDirectory "gruff" let cacheDir' = appDir "cache" logDir = appDir "log" stateFile = appDir "state.gruff" createDirectoryIfMissing False appDir createDirectoryIfMissing False logDir lg <- logger logDir icon <- pixbufNewFromFile =<< getDataFileName "icon.png" browser <- browserNew gl' icon lg cacheDir' let iw = browserWindow browser sg <- sizeGroupNew SizeGroupHorizontal let spacing = 2 entryNewWithMnemonic m = entryNewWithMnemonic' m 30 entryNewWithMnemonic' m wc = do e <- entryNew entrySetWidthChars e wc l <- labelNewWithMnemonic m labelSetMnemonicWidget l e sizeGroupAddWidget sg l h <- hBoxNew False spacing boxPackStart h l PackNatural 0 boxPackStartDefaults h e return (e, h) entryNewExponent = do e <- entryNew entrySetWidthChars e 4 l <- labelNew (Just "e") h <- hBoxNew False spacing boxPackStart h l PackNatural 0 boxPackStart h e PackNatural 0 return (e, h) frameNewWithContents box t r ws = do f <- frameNew frameSetLabel f t frameSetLabelAlign f (if r then 1 else 0) 0.5 v <- box False spacing forM_ ws $ boxPackStartDefaults v set f [ containerChild := v ] return f b01@bHome' <- buttonNewWithLabel "Home" b02@bLoad' <- buttonNewWithLabel "Load" b03@bSave' <- buttonNewWithLabel "Save" b04@bStop' <- buttonNewWithLabel "Stop" b05@bClear' <- buttonNewWithLabel "Clear" (eRealM', fc1m) <- entryNewWithMnemonic "_Real" (eRealE', fc1e) <- entryNewExponent (eImagM', fc2m) <- entryNewWithMnemonic "_Imag" (eImagE', fc2e) <- entryNewExponent (eSizeM', fc3m) <- entryNewWithMnemonic "Si_ze" (eSizeE', fc3e) <- entryNewExponent (eRota', fvR) <- entryNewWithMnemonic' "R_otation" 15 (eWidth', fvW) <- entryNewWithMnemonic' "_Width" 5 (eHeight', fvH) <- entryNewWithMnemonic' "_Height" 5 (eSamples', fvS) <- entryNewWithMnemonic' "_Samples" 3 cInterior' <- colorButtonNewWithColor red cBorder' <- colorButtonNewWithColor black cExterior' <- colorButtonNewWithColor white labelColourR <- newIORef (fromColor blue) cLabels' <- colorButtonNewWithColor blue _ <- cLabels' `onColorSet` (colorButtonGetColor cLabels' >>= writeIORef labelColourR . fromColor) fb <- frameNewWithContents vBoxNew "Actions" False [b01, b02, b03, b04, b05] fh <- frameNewWithContents hBoxNew "Colours" True [cInterior', cBorder', cExterior', cLabels'] fv <- frameNewWithContents hBoxNew "Viewport" True [fvR, fvW, fvH, fvS] let packMantissaExponent m e = do h <- hBoxNew False spacing boxPackStartDefaults h m boxPackStart h e PackNatural 0 return h fc1 <- packMantissaExponent fc1m fc1e fc2 <- packMantissaExponent fc2m fc2e fc3 <- packMantissaExponent fc3m fc3e fc <- frameNewWithContents vBoxNew "Coordinates" True [fc1, fc2, fc3] v <- vBoxNew False spacing mapM_ (\w -> boxPackStart v w PackNatural 0) [fc, fv, {- fa, -} fh] h <- hBoxNew False spacing boxPackStart h fb PackNatural 0 boxPackStartDefaults h v ww <- windowNew set ww [ windowIcon := Just icon, windowTitle := "gruff control" ] containerAdd ww h mg <- aDoLoad stateFile gR <- newIORef $ case mg of Nothing -> initialGruff Just g -> g let g0 = GruffGUI { bHome = bHome' , bLoad = bLoad' , bSave = bSave' , bStop = bStop' , bClear = bClear' , eRealM = eRealM' , eRealE = eRealE' , eImagM = eImagM' , eImagE = eImagE' , eSizeM = eSizeM' , eSizeE = eSizeE' , eRota = eRota' , eWidth = eWidth' , eHeight = eHeight' , eSamples = eSamples' , cInterior = cInterior' , cBorder = cBorder' , cExterior = cExterior' , wMain = ww , wImage = iw } but b a = do _ <- b g0 `onClicked` wrapA g0 gR a return () butI b a = do _ <- b g0 `onClicked` (wrapA g0 gR a >> upI) return () butO b a = do _ <- b g0 `onClicked` a return () entI e a = do _ <- e g0 `onEntryActivate` (entryGetText (e g0) >>= wrapE g0 gR a >> upI) return () colI cbi cbb cbe a = forM_ [cbi, cbb, cbe] $ \c -> do _ <- c g0 `onColorSet` (do ci <- colorButtonGetColor (cbi g0) cb <- colorButtonGetColor (cbb g0) ce <- colorButtonGetColor (cbe g0) _ <- a ci cb ce gR upI) return () entME m e a = do let a' = do ms <- entryGetText (m g0) es <- entryGetText (e g0) let s = ms ++ if null es then "" else "e" ++ es wrapE g0 gR a s upI _ <- m g0 `onEntryActivate` a' _ <- e g0 `onEntryActivate` a' return () upI :: IO () upI = do g <- readIORef gR browserRender browser g (return ()) (return ()) postRedisplay gl' aUpdate :: Maybe View.Image -> IO () aUpdate Nothing = return () aUpdate (Just i) = do writeIORef gR i uReal g0 i uImag g0 i uSize g0 i uRota g0 i upI aReshape :: Int -> Int -> IO () aReshape w' h' = do atomicModifyIORef gR $ \g -> ( g { imageWindow = (imageWindow g){ width = w', height = h' } , imageViewport = (imageViewport g){ aspect = fromIntegral w' / fromIntegral h' } } , () ) g <- readIORef gR uEverything g0 g upI butI bHome aHome butI bLoad aLoad but bSave aSave butO bStop (browserAbort browser) butI bClear aClear entME eRealM eRealE aReal entME eImagM eImagE aImag entME eSizeM eSizeE aSize entI eRota aRota entI eWidth aWidth entI eHeight aHeight entI eSamples aSamples colI cInterior cBorder cExterior aColours let aExit' = (exit (Log.log lg) stateFile =<< readIORef gR) browserSetExitCallback browser aExit' progress <- progressNew icon let mcbs = mouseCallbacks (readIORef labelColourR) progress aUpdate browserSetMouseCallback browser mcbs browserSetReshapeCallback browser aReshape _ <- ww `onDestroy` aExit' g <- readIORef gR uEverything g0 g upI widgetShowAll iw widgetShowAll ww _ <- forkIO $ script browser mainGUI type Gruff = View.Image initialGruff :: Gruff initialGruff = defImage -- button actions type A = GruffGUI -> Gruff -> IO Gruff wrapA :: GruffGUI -> IORef Gruff -> A -> IO () wrapA g0 gR a = do g <- readIORef gR g' <- a g0 g writeIORef gR $! g' aHome :: A aHome g0 g = do let g' = g{ imageLocation = defLocation, imageViewport = (imageViewport g){ orient = 0 } } uEverything g0 g' return g' aClear :: A aClear g0 g = do let g' = g{ imageLabels = [], imageLines = [] } uEverything g0 g' return g' aDoLoad :: FilePath -> IO (Maybe Gruff) aDoLoad ff = (do gr <- safeRead `fmap` readFile ff case gr of g@(Just _) -> return g _ -> putStrLn "file format not supported, sorry" >> return Nothing ) `catchIO` const (return Nothing) aLoad :: A aLoad g0 g = do fc <- fileChooserDialogNew (Just "gruff load") (Just $ wMain g0) FileChooserActionOpen [("Load", ResponseAccept), ("Cancel", ResponseCancel)] widgetShow fc r <- dialogRun fc g' <- case r of ResponseAccept -> do mf <- fileChooserGetFilename fc case mf of Nothing -> return g Just f -> do mg <- aDoLoad f case mg of Nothing -> return g Just g' -> uEverything g0 g' >> return g' _ -> return g widgetDestroy fc return g' aDoSave :: FilePath -> Gruff -> IO () aDoSave f g = writeFile f (show g) `catchIO` const (return ()) aSave :: A aSave g0 g = do fc <- fileChooserDialogNew (Just "gruff save") (Just $ wMain g0) FileChooserActionSave [("Save", ResponseAccept), ("Cancel", ResponseCancel)] widgetShow fc r <- dialogRun fc case r of ResponseAccept -> do mf <- fileChooserGetFilename fc case mf of Nothing -> return () Just f -> aDoSave f g _ -> return () widgetDestroy fc return g -- entry update type U = GruffGUI -> Gruff -> IO () uEverything, uReal, uImag, uSize, uRota, uColours, uWidth, uHeight, uSamples :: U uEverything g0 g = forM_ [uReal, uImag, uSize, uRota, uColours, uWidth, uHeight, uSamples] $ \u -> u g0 g uReal g0 g = uMantissaExponent (eRealM g0) (eRealE g0) (show . (fromRational :: Rational -> R) . realPart . center . imageLocation $ g) uImag g0 g = uMantissaExponent (eImagM g0) (eImagE g0) (show . (fromRational :: Rational -> R) . imagPart . center . imageLocation $ g) uSize g0 g = uMantissaExponent (eSizeM g0) (eSizeE g0) (show . radius . imageLocation $ g) uRota g0 g = entrySetText (eRota g0) (show . orient . imageViewport $ g) uColours g0 g = do colorButtonSetColor (cInterior g0) (fromColour . colourInterior . imageColours $ g) colorButtonSetColor (cBorder g0) (fromColour . colourBoundary . imageColours $ g) colorButtonSetColor (cExterior g0) (fromColour . colourExterior . imageColours $ g) uWidth g0 g = entrySetText (eWidth g0) (show . width . imageWindow $ g) uHeight g0 g = entrySetText (eHeight g0) (show . height . imageWindow $ g) uSamples g0 g = entrySetText (eSamples g0) (show . supersamples . imageWindow $ g) uMantissaExponent :: (EntryClass a, EntryClass b) => a -> b -> String -> IO () uMantissaExponent m e s = do let (ms, me) = break (== 'e') s entrySetText m ms entrySetText e (drop 1 me) -- entry actions type E = Gruff -> String -> Gruff wrapE :: GruffGUI -> IORef Gruff -> E -> String -> IO () wrapE _g0 gR e s = do g <- readIORef gR let g' = e g s writeIORef gR $! g' aReal, aImag, aSize, aRota, aWidth, aHeight, aSamples :: E aReal g s = let _ :+ i = center (imageLocation g) in case safeRead s :: Maybe R of Nothing -> g Just r -> g{ imageLocation = (imageLocation g){ center = toRational r :+ i } } aImag g s = let r :+ _ = center (imageLocation g) in case safeRead s :: Maybe R of Nothing -> g Just i -> g{ imageLocation = (imageLocation g){ center = r :+ toRational i } } aSize g s = case safeRead s :: Maybe Double of Nothing -> g Just r -> g{ imageLocation = (imageLocation g){ radius = r } } aRota g s = case safeRead s :: Maybe Double of Nothing -> g Just a -> g{ imageViewport = (imageViewport g){ orient = a } } aWidth g s = case safeRead s of Nothing -> g Just r -> (\g' -> g'{ imageViewport = (imageViewport g'){ aspect = (fromIntegral . width . imageWindow) g' / (fromIntegral . height . imageWindow) g' } }) (g{ imageWindow = (imageWindow g){ width = r } }) aHeight g s = case safeRead s of Nothing -> g Just r -> (\g' -> g'{ imageViewport = (imageViewport g'){ aspect = (fromIntegral . width . imageWindow) g' / (fromIntegral . height . imageWindow) g' } }) (g{ imageWindow = (imageWindow g){ height = r } }) aSamples g s = case safeRead s of Nothing -> g Just r -> g{ imageWindow = (imageWindow g){ supersamples = r } } aColours :: Color -> Color -> Color -> IORef Gruff -> IO () aColours i b e gR = atomicModifyIORef gR $ \g -> (g{ imageColours = Colours{ colourInterior = fromColor i, colourBoundary = fromColor b, colourExterior = fromColor e} }, ()) minSize :: Size minSize = Size 160 100 red, black, white, blue :: Color red = Color 65535 0 0 black = Color 0 0 0 white = Color 65535 65535 65535 blue = Color 0 0 65535 fromColor :: Color -> Colour fromColor (Color r g b) = Colour (fromIntegral r / 65535) (fromIntegral g / 65535) (fromIntegral b / 65535) fromColour :: Colour -> Color fromColour (Colour r g b) = Color (round $ r * 65535) (round $ g * 65535) (round $ b * 65535) script :: Browser -> IO () script b = do c <- getContents case mapMaybe readMay (lines c) of [] -> return () images@(_:_) -> browserRenders b images readMay :: Read a => String -> Maybe a readMay s = case reads s of [(a, "")] -> Just a _ -> Nothing