{-# LANGUAGE OverloadedStrings, TemplateHaskell #-} module Main where import System.Environment (getArgs) import qualified Data.Map as M import qualified Data.Set as S import Data.Maybe (fromJust, fromMaybe) import qualified Data.Text as Txt import qualified Data.ByteString as BS import System.Directory (getCurrentDirectory) import qualified System.Directory as Dir import Graphics.Layout.CSS (CSSBox(..), finalizeCSS') import Graphics.Layout.CSS.Font (placeholderFont) import Graphics.Layout (LayoutItem, boxLayout, glyphsPerFont, layoutGetBox, layoutGetChilds, layoutGetInner) import Graphics.Layout.Box (zeroBox) import qualified Graphics.Layout.Box as B import Network.URI.Fetch.XML (Page(..), fetchDocument, applyCSScharset) import Network.URI.Fetch (newSession, fetchURL) import Network.URI.Charset (charsets) import Network.URI (URI(..), nullURI, parseURIReference) import Data.FileEmbed (makeRelativeToProject, embedStringFile) import Data.HTML2CSS (el2stylist) import Text.XML as X (Document(..), Element(..), Node(..), Prologue(..)) import Stylist.Tree (StyleTree(..), preorder, treeMap) import Stylist (PropertyParser(..), cssPriorityAgent, cssPriorityUser) import qualified Data.CSS.Style as Style import qualified Data.CSS.Syntax.StyleSheet as CSS import qualified Data.CSS.Preprocessor.Text as CSSTxt import Data.CSS.Preprocessor.Conditions as CSSCond (ConditionalStyles, conditionalStyles, loadImports, Datum(..), resolve) import qualified Data.CSS.Preprocessor.PsuedoClasses as CSSPseudo import Control.Concurrent.MVar (putMVar, newEmptyMVar, tryReadMVar) import Control.Concurrent (forkIO) import Control.DeepSeq (NFData(..), ($!!)) import SDL hiding (rotate) import Foreign.C.Types (CInt) import Data.Function (fix) import Control.Monad (unless) import Control.Exception (evaluate) import qualified Graphics.Text.Font.Choose as FC initReferer :: IO (Page (CSSCond.ConditionalStyles (CSSBox Nil))) initReferer = do cwd <- getCurrentDirectory return $ Page { -- Default to URIs being relative to CWD. pageURL = URI {uriScheme = "file:", uriPath = cwd, uriAuthority = Nothing, uriQuery = "", uriFragment = ""}, -- Blank values: css = conditionalStyles nullURI "temp", domain = "temp", html = Document { documentPrologue = Prologue [] Nothing [], documentRoot = Element "temp" M.empty [], documentEpilogue = [] }, pageTitle = "", pageMIME = "", apps = [], backStack = [], forwardStack = [], visitedURLs = S.empty, initCSS = conditionalStyles, appName = "cattrap" } stylize' style = preorder inner where inner parent _ el = Style.cascade style el [] $ Style.inherit $ fromMaybe Style.temp parent resolveCSS manager page = do let agentStyle = cssPriorityAgent (css page) `CSS.parse` $(makeRelativeToProject "app/useragent.css" >>= embedStringFile) userStyle <- loadUserStyles agentStyle CSSCond.loadImports loadURL lowerVars lowerToks userStyle [] where loadURL url = do response <- fetchURL manager ["text/css"] url let charsets' = map Txt.unpack charsets return $ case response of ("text/css", Left text) -> text ("text/css", Right bytes) -> applyCSScharset charsets' $ BS.toStrict bytes (_, _) -> "" loadUserStyles styles = do dir <- Dir.getXdgDirectory Dir.XdgConfig "rhapsode" exists <- Dir.doesDirectoryExist dir loadDirectory dir exists where loadDirectory _ False = return styles loadDirectory dir True = do files <- Dir.listDirectory dir loadFiles (cssPriorityUser styles) files loadFiles style (file:files) = do source <- readFile file CSS.parse style (Txt.pack source) `loadFiles` files loadFiles style [] = return style -- FIXME: Support more media queries! resolve' = CSSCond.resolve lowerVars lowerToks lowerVars _ = CSSCond.B False lowerToks _ = CSSCond.B False main :: IO () main = do FC.init SDL.initializeAll let wcfg = defaultWindow { windowInitialSize = V2 1280 480, -- Simplify moving layout/download out-of-thread windowResizable = False } w <- createWindow "CatTrap" wcfg renderer <- createRenderer w (-1) defaultRenderer args <- getArgs let url = case args of (url:_) -> url [] -> "https://git.argonaut-constellation.org/~alcinnz/CatTrap" sess <- newSession ref <- initReferer xml <- fetchDocument sess ref $ fromMaybe nullURI $ parseURIReference url let pseudoFilter = CSSPseudo.htmlPsuedoFilter Style.queryableStyleSheet css' <- resolveCSS sess xml let css = CSSPseudo.inner $ resolve' pseudoFilter css' let styles = CSSTxt.resolve $ treeMap Style.innerParser $ stylize' css $ el2stylist $ X.documentRoot $ html xml let layout = finalizeCSS' placeholderFont styles V2 x y <- get $ windowSize w pages' <- forkCompute $ boxLayout zeroBox { B.size = B.Size (fromIntegral x) (fromIntegral y) } layout False fix $ \loop -> do events <- fmap eventPayload <$> pollEvents rendererDrawColor renderer $= V4 255 255 255 255 clear renderer pages <- tryReadMVar pages' case pages of Just (display:_) -> do evaluate $ glyphsPerFont display renderDisplay renderer display _ -> return () present renderer unless (QuitEvent `elem` events) loop SDL.quit -- FC.fini -- FIXME: Need to free all Haskell data before freeing FontConfig's data Nil = Nil deriving Eq instance PropertyParser Nil where temp = Nil inherit _ = Nil longhand _ _ _ _ = Nothing instance NFData Nil where rnf Nil = () renderDisplay :: Renderer -> LayoutItem Double Double ((Double, Double), Nil) -> IO () renderDisplay renderer display = do let ((x, y), _) = layoutGetInner display let box = layoutGetBox display rendererDrawColor renderer $= V4 255 0 0 255 drawBox renderer x y (B.width box) (B.height box) rendererDrawColor renderer $= V4 0 255 0 255 drawBox renderer (x + B.left (B.margin box)) (y + B.top (B.margin box)) (B.width box - B.left (B.margin box) - B.right (B.margin box)) (B.height box - B.top (B.margin box) - B.bottom (B.margin box)) rendererDrawColor renderer $= V4 0 0 255 255 drawBox renderer (x + B.left (B.margin box) + B.left (B.border box)) (y + B.top (B.margin box) + B.top (B.border box)) (B.inline (B.size box) + B.left (B.padding box) + B.right (B.padding box)) (B.block (B.size box) + B.top (B.padding box) + B.bottom (B.padding box)) rendererDrawColor renderer $= V4 255 255 0 255 drawBox renderer (x + B.left (B.margin box) + B.left (B.border box) + B.left (B.padding box)) (y + B.top (B.margin box) + B.top (B.border box) + B.top (B.padding box)) (B.inline $ B.size box) (B.block $ B.size box) mapM (renderDisplay renderer) $ layoutGetChilds display return () drawBox :: Renderer -> Double -> Double -> Double -> Double -> IO () drawBox renderer x y width height = do fillRect renderer $ Just $ Rectangle (P $ V2 (c x) (c y)) (V2 (c width) (c height)) c :: (Enum a, Enum b) => a -> b c = toEnum . fromEnum forkCompute dat = do ret <- newEmptyMVar forkIO $ putMVar ret $!! dat return ret