module Graphics.UI.Gtk.Board.TiledBoard where import Control.Monad (when, void) import Control.Monad.Trans (liftIO) import Data.Array.IO import Data.IORef import Data.Maybe (isJust, fromJust) import Graphics.UI.Gtk import Graphics.UI.Gtk.Gdk.GC (gcNew) import System.Glib.Types -- Local imports import Data.Board.GameBoardIO data Board index tile piece = Board { boardDrawingArea :: DrawingArea , boardTiles :: GameBoard index tile , boardPieces :: GameBoard index piece , tilePixmaps :: PixmapsFor tile , piecePixmaps :: PixmapsFor piece , tileSize :: (Int, Int) , background :: IORef (Maybe (Pixbuf, SizeAdjustment)) , overlay :: IORef (Maybe (Pixbuf, SizeAdjustment)) , dragEnabled :: IORef Bool , draggingFrom :: IORef (Maybe (index, index)) , draggingTo :: IORef (Maybe (index, index)) , draggingMouseOrig :: IORef (Maybe (Int, Int)) , draggingMousePos :: IORef (Maybe (Int, Int)) , movingStatus :: IORef (Maybe (MovingStatus index)) } data MovingStatus index = MovingStatus { movingFrom :: (index, index) , movingTo :: (index, index) , stepsPerUnit :: Double , timePerUnit :: Double , movingStep :: Double } instance WidgetClass (Board index tile piece) instance ObjectClass (Board index tile piece) instance GObjectClass (Board index tile piece) where toGObject = toGObject . boardDrawingArea unsafeCastGObject x = Board (unsafeCastGObject x) undefined undefined undefined undefined undefined undefined undefined undefined undefined undefined undefined undefined undefined type PixmapsFor a = a -> Pixbuf data SizeAdjustment = SizeAdjustment boardNew :: Ix index => [(index,index,tile)] -> PixmapsFor tile -> PixmapsFor piece -> IO (Board index tile piece) boardNew tileList tilePixs piecePixs = do da <- drawingAreaNew tb <- gameBoardNew tileList pb <- gameBoardNewEmpty (map (\(x,y,_) -> (x,y)) tileList) ts <- getTileSize tileList tilePixs bg <- newIORef Nothing ov <- newIORef Nothing dragging <- newIORef False draggingF <- newIORef Nothing draggingT <- newIORef Nothing draggingO <- newIORef Nothing draggingP <- newIORef Nothing movingSt <- newIORef Nothing let board = Board da tb pb tilePixs piecePixs ts bg ov dragging draggingF draggingT draggingO draggingP movingSt (pixW, pixH) <- boardGetPixelSize board da `on` realize $ widgetSetSizeRequest da pixW pixH da `on` exposeEvent $ liftIO (boardRefresh board) >> return False return board getTileSize :: [(index, index, tile)] -> PixmapsFor tile -> IO (Int, Int) getTileSize [] _ = return (0,0) getTileSize ((_,_,t):_) pixs = do let pb = pixs t w <- pixbufGetWidth pb h <- pixbufGetHeight pb return (w,h) boardGetPiece :: Ix index => (index, index) -> Board index tile piece -> IO (Maybe piece) boardGetPiece pos board = gameBoardGetPiece pos (boardPieces board) boardSetPiece :: Ix index => (index, index) -> piece -> Board index tile piece -> IO () boardSetPiece pos piece board = do boardSetPieceNoRefresh pos piece board boardInvalidate board -- boardRefresh board boardSetPieceNoRefresh :: Ix index => (index, index) -> piece -> Board index tile piece -> IO () boardSetPieceNoRefresh pos piece board = do -- check that there's a tile there posOk <- fmap isJust $ gameBoardGetPiece pos $ boardTiles board when posOk $ do -- if there is, place the piece on the pieces board gameBoardSetPiece pos piece (boardPieces board) boardRemovePiece :: Ix index => (index, index) -> Board index tile piece -> IO () boardRemovePiece pos board = do -- check that there's a tile there posOk <- fmap isJust $ gameBoardGetPiece pos $ boardTiles board when posOk $ do -- if there is, remove the piece from the pieces board gameBoardRemovePiece pos (boardPieces board) -- refresh the image boardInvalidate board -- boardRefresh board boardMovePiece :: Ix index => (index, index) -> (index, index) -> Board index tile piece -> IO () boardMovePiece posO posD board = do -- check that there's a tile there posOrigOk <- fmap isJust $ gameBoardGetPiece posO $ boardTiles board posDestOk <- fmap isJust $ gameBoardGetPiece posD $ boardTiles board when (posOrigOk && posDestOk) $ do -- Move the piece gameBoardMovePiece posO posD $ boardPieces board -- Refresh the UI boardInvalidate board -- boardRefresh board boardInvalidate :: Ix index => Board index tile piece -> IO () boardInvalidate = widgetQueueDraw boardRefresh :: Ix index => Board index tile piece -> IO () boardRefresh board = do realized <- widgetGetRealized board when realized $ do dw <- widgetGetDrawWindow (boardDrawingArea board) (w,h) <- widgetGetSize (boardDrawingArea board) bg <- readIORef (background board) ov <- readIORef (overlay board) drawWindowBeginPaintRect dw (Rectangle 0 0 w h) -- Clear Drawing area drawWindowClear dw gc <- gcNew dw when (isJust bg) $ do ((posBgX, posBgY), bg') <- uncurry (adjustPixbuf (w,h)) (fromJust bg) drawPixbuf dw gc bg' 0 0 posBgX posBgY (-1) (-1) RgbDitherNone (-1) (-1) -- Dragging status (used to determine what to draw) posM <- readIORef (draggingFrom board) mpOrig <- readIORef (draggingMouseOrig board) mpPos <- readIORef (draggingMousePos board) -- Draw tiles drawPixmaps dw (tileSize board) (boardTiles board) (tilePixmaps board) -- Draw Pieces piecesBoard <- if isJust posM && isJust mpOrig && isJust mpPos then do pieces' <- gameBoardClone $ boardPieces board gameBoardRemovePiece (fromJust posM) pieces' return pieces' else return $ boardPieces board -- drawPixmaps dw (tileSize board) (boardPieces board) (piecePixmaps board) drawPixmaps dw (tileSize board) piecesBoard (piecePixmaps board) -- Draw moving piece when (isJust posM && isJust mpOrig && isJust mpPos) $ do pieceM <- boardGetPiece (fromJust posM) board when (isJust pieceM) $ do let pb = piecePixmaps board (fromJust pieceM) let (mpPosX, mpPosY) = fromJust mpPos (mpOrigX, mpOrigY) = fromJust mpOrig (x,y) = (mpPosX - mpOrigX, mpPosY - mpOrigY) drawPixbuf dw gc pb 0 0 x y (-1) (-1) RgbDitherNone (-1) (-1) when (isJust ov) $ do ((posOvX, posOvY), ov') <- uncurry (adjustPixbuf (w,h)) (fromJust ov) drawPixbuf dw gc ov' 0 0 posOvX posOvY (-1) (-1) RgbDitherNone (-1) (-1) drawWindowEndPaint dw -- FIXME: To be completed adjustPixbuf :: (Int, Int) -> Pixbuf -> SizeAdjustment -> IO ((Int, Int), Pixbuf) adjustPixbuf _ pb _ = return ((0,0), pb) mouseMotionHandler :: Ix index => Board index tile piece -> ((index, index) -> EventM EMotion Bool) -> EventM EMotion Bool mouseMotionHandler board p = do coords <- eventCoordinates pos <- liftIO $ getMouseCoordinates board coords maybe (return False) p pos mouseButtonHandler :: Ix index => Board index tile piece -> ((index, index) -> EventM EButton Bool) -> EventM EButton Bool mouseButtonHandler board p = do coords <- eventCoordinates pos <- liftIO $ getMouseCoordinates board coords maybe (return False) p pos getMouseCoordinates :: Ix index => Board index tile piece -> (Double, Double) -> IO (Maybe (index, index)) getMouseCoordinates board (x,y) = do let (tileW, tileH) = tileSize board tileCol = round x `div` tileW tileRow = round y `div` tileH let (GameBoard array) = boardTiles board ((xm, ym), (xM, yM)) <- getBounds $ array let xs = range (xm, xM) ys = range (ym, yM) if (inRange (0, length xs - 1) tileCol && inRange (0, length ys - 1) tileRow) then return $ Just ((head (drop tileCol xs)), (head (drop tileRow ys))) else return Nothing clickHandler :: Ix index => Board index tile piece -> ((index, index) -> IO ()) -> EventM EButton Bool clickHandler board p = do (x,y) <- eventCoordinates liftIO $ do let (tileW, tileH) = tileSize board tileCol = round x `div` tileW tileRow = round y `div` tileH let (GameBoard array) = boardTiles board ((xm, ym), (xM, yM)) <- getBounds $ array let xs = range (xm, xM) ys = range (ym, yM) when (inRange (0, length xs - 1) tileCol && inRange (0, length ys - 1) tileRow) $ p ((head (drop tileCol xs)), (head (drop tileRow ys))) return False boardGetPixelSize :: Ix index => Board index tile piece -> IO (Int, Int) boardGetPixelSize board = do let (GameBoard array) = boardTiles board ((xm, ym), (xM, yM)) <- getBounds $ array let htiles = rangeSize (xm, xM) vtiles = rangeSize (ym, yM) (tileW, tileH) = tileSize board return (htiles * tileW, vtiles * tileH) drawPixmaps :: (Ix index, DrawableClass d) => d -> (Int, Int) -> GameBoard index e -> PixmapsFor e -> IO() drawPixmaps d tileSize@(tw, th) gameBoard@(GameBoard array) pixmaps = do gc <- gcNew d ((xm, ym), (xM, yM)) <- gameBoardGetBoundaries gameBoard let paintPixmap (x,y) elem = do let pixmap = pixmaps elem ix = index (xm, xM) x iy = index (ym, yM) y posX = ix * tw posY = iy * th drawPixbuf d gc pixmap 0 0 posX posY (-1) (-1) RgbDitherNone (-1) (-1) gameBoardMapM_ gameBoard paintPixmap boardFoldM :: (Ix index) => Board index tile piece -> (b -> ((index,index), piece) -> IO b) -> b -> IO b boardFoldM board f def = gameBoardFoldM (boardPieces board) f def boardClear :: Ix index => Board index tile piece -> IO () boardClear board = do gameBoardClear (boardPieces board) boardInvalidate board boardLoad :: Ix index => Board index tile piece -> [((index, index), piece)] -> IO() boardLoad board pieces = do gameBoardClear (boardPieces board) mapM_ (\(pos, piece) -> boardSetPieceNoRefresh pos piece board) pieces boardRefresh board boardOnClick :: Ix index => Board index tile piece -> ((index, index) -> IO ()) -> IO() boardOnClick board p = boardOnPress board (\c -> liftIO (p c) >> return False) boardOnPress :: Ix index => Board index tile piece -> ((index, index) -> EventM EButton Bool) -> IO() boardOnPress board f = void $ do widgetAddEvents (boardDrawingArea board) [ButtonPressMask] (boardDrawingArea board) `on` buttonPressEvent $ mouseButtonHandler board f boardOnRelease :: Ix index => Board index tile piece -> ((index, index) -> EventM EButton Bool) -> IO() boardOnRelease board f = void $ do widgetAddEvents (boardDrawingArea board) [ButtonPressMask, ButtonReleaseMask] (boardDrawingArea board) `on` buttonReleaseEvent $ mouseButtonHandler board f boardOnMotion :: Ix index => Board index tile piece -> ((index, index) -> EventM EMotion Bool) -> IO() boardOnMotion board f = void $ do widgetAddEvents board [PointerMotionMask] (boardDrawingArea board) `on` motionNotifyEvent $ mouseMotionHandler board f boardSetBackground :: Ix index => Board index tile piece -> Maybe (Pixbuf, SizeAdjustment) -> IO() boardSetBackground board bg = do writeIORef (background board) bg boardInvalidate board boardSetOverlay :: Ix index => Board index tile piece -> Maybe (Pixbuf, SizeAdjustment) -> IO() boardSetOverlay board bg = do writeIORef (overlay board) bg boardInvalidate board boardEnableDrag :: Ix index => Board index tile piece -> IO() boardEnableDrag board = writeIORef (dragEnabled board) True boardDisableDrag :: Ix index => Board index tile piece -> IO() boardDisableDrag board = do writeIORef (dragEnabled board) False boardInvalidate board boardStartDrag :: Ix index => Board index tile piece -> (index, index) -> IO() boardStartDrag board ix@(i,j) = do writeIORef (draggingFrom board) (Just ix) ((xm, ym), (xM, yM)) <- gameBoardGetBoundaries $ boardPieces board let (w,h) = tileSize board x = round ((0.5 + fromIntegral (rangeSize (xm, i))) * fromIntegral w) y = round ((0.5 + fromIntegral (rangeSize (ym, j))) * fromIntegral h) writeIORef (draggingMouseOrig board) (Just (x, y)) boardStopDrag :: Ix index => Board index tile piece -> IO() boardStopDrag board = do writeIORef (draggingFrom board) Nothing writeIORef (draggingTo board) Nothing boardInvalidate board boardOnPieceDragStart :: Ix index => Board index tile piece -> ((index, index) -> IO Bool) -> IO() boardOnPieceDragStart board f = boardOnPress board $ \ix -> do (x,y) <- eventCoordinates returning False $ liftIO $ do drag <- readIORef (dragEnabled board) when drag $ do canDragThis <- f ix let from = if canDragThis then Just ix else Nothing orig = if canDragThis then Just (relativePos board ix (round x, round y)) else Nothing writeIORef (draggingFrom board) from writeIORef (draggingMouseOrig board) orig boardInvalidate board boardOnPieceDragOver :: Ix index => Board index tile piece -> ((index, index) -> (index, index) -> IO Bool) -> IO() boardOnPieceDragOver board f = boardOnMotion board $ \ix -> do (x,y) <- eventCoordinates returning False $ liftIO $ do drag <- readIORef (dragEnabled board) origM <- readIORef (draggingFrom board) when (drag && isJust origM) $ do canDropHere <- f (fromJust origM) ix let newDest = if canDropHere then Just ix else Nothing writeIORef (draggingTo board) newDest writeIORef (draggingMousePos board) (Just (round x, round y)) boardInvalidate board boardOnPieceDragDrop :: Ix index => Board index tile piece -> ((index, index) -> (index, index) -> IO ()) -> IO() boardOnPieceDragDrop board f = void $ do widgetAddEvents (boardDrawingArea board) [ButtonPressMask, ButtonReleaseMask] (boardDrawingArea board) `on` buttonReleaseEvent $ returning False $ liftIO $ do drag <- readIORef (dragEnabled board) origM <- readIORef (draggingFrom board) destM <- readIORef (draggingTo board) let notSame = origM /= destM when (drag && isJust origM) $ do -- No longer dragging writeIORef (draggingFrom board) Nothing writeIORef (draggingTo board) Nothing writeIORef (draggingMouseOrig board) Nothing writeIORef (draggingMousePos board) Nothing -- When possible, call the handler when (isJust destM && notSame) $ f (fromJust origM) (fromJust destM) -- In any case, the board must be repainted boardInvalidate board boardIsDragging :: Ix index => Board index tile piece -> IO Bool boardIsDragging = fmap isJust . readIORef . draggingFrom relativePos :: Ix index => Board index tile piece -> (index, index) -> (Int, Int) -> (Int, Int) relativePos board (ix,iy) (x,y) = (x', y') where (w,h) = tileSize board x' = x `mod` w y' = y `mod` h returning :: Monad m => a -> m b -> m a returning v f = f >> return v -- boardLiveMove :: Ix index => (index, index) -> (index, index) -> Board index tile piece -> IO () -- boardLiveMove posO posD board = do -- evs <- widgetGetEvents board -- -- let evs' = undefined -- -- movingParams <- undefined -- -- let timeDelay = undefined -- -- writeIORef (movingStatus board) (Just movingParams) -- -- -- Set delay and time handler -- timeoutAdd (do undefined -- advance moving one step -- boardInvalidate board -- -- return True if not the end, otherwise return False -- ) -- timeDelay -- -- -- Time Handler includes resetting the event handers -- return ()