{-| Module : Graphics.Mosaico.Ventana Description : Ventanas interactivas con distribuciones de rectángulos Copyright : ⓒ Manuel Gómez, 2015 License : BSD3 Maintainer : targen@gmail.com Stability : experimental Portability : portable Representación orientada a objetos de una ventana interactiva donde se puede mostrar un 'Diagrama' con una parte enfocada, y obtener eventos de teclas pulsadas en la ventana. -} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE UnicodeSyntax #-} module Graphics.Mosaico.Ventana ( Ventana , cerrar , crearVentana , leerTecla , mostrar ) where import Control.Applicative (pure) import Control.Concurrent (forkIO) import Control.Concurrent.STM.TMChan (newTMChanIO, closeTMChan, readTMChan, writeTMChan) import Control.Concurrent.STM.TVar (newTVarIO, readTVarIO, writeTVar) import Control.Monad (void) import Control.Monad.STM (atomically) import Control.Monad.Unicode ((=≪)) import Control.Monad.IO.Class (liftIO) import Data.Bool (Bool(True)) import Data.Colour.Names (blue, green, red, yellow) import Data.Colour.SRGB (Colour, sRGB24) import Data.Function (($), flip) import Data.Function.Unicode ((∘)) import Data.Functor ((<$>)) import Data.List (reverse) import Data.Maybe (Maybe(Nothing, Just)) import Data.Monoid (mappend, mempty) import Data.String (String) import Diagrams.Attributes (opacity) import Diagrams.Backend.Cairo (Cairo) import Diagrams.Backend.Gtk (renderToGtk, toGtkCoords) import Diagrams.BoundingBox (boundingBox, boxExtents) import Diagrams.Core.Types (Diagram) import Diagrams.TwoD.Align (centerXY) import Diagrams.TwoD.Attributes (fillColor, lineColor, lineWidth, ultraThick) import Diagrams.TwoD.Combinators ((===), (|||)) import Diagrams.TwoD.Shapes (rect, unitSquare) import Diagrams.TwoD.Size (SizeSpec2D(Dims), sized) import Diagrams.TwoD.Transform (scaleX, scaleY) import Diagrams.TwoD.Types (R2(R2)) import Diagrams.Util (( # )) import Graphics.Mosaico.Imagen (Imagen(Imagen, altura, anchura), Color(Color, rojo, verde, azul)) import Graphics.Mosaico.Diagrama (Diagrama((:-:), (:|:), Hoja), Paso(Primero, Segundo), Rectángulo(Rectángulo, color, imagen)) import Graphics.UI.Gtk.Abstract.Container (containerChild) import Graphics.UI.Gtk.Abstract.Widget (EventMask(KeyPressMask), Requisition(Requisition), exposeEvent, keyPressEvent, onDestroy, sizeRequest, widgetAddEvents, widgetDestroy, widgetQueueDraw, widgetShowAll) import Graphics.UI.Gtk.Gdk.EventM (eventKeyName, eventWindow) import Graphics.UI.Gtk.General.General (initGUI, mainGUI, mainQuit, postGUIAsync, postGUISync) import Graphics.UI.Gtk.Misc.DrawingArea (drawingAreaNew) import Graphics.UI.Gtk.Windows.Window (windowNew) import Prelude (Double, Integer, fromInteger, fromIntegral) import System.Glib.Attributes (AttrOp((:=)), set) import System.Glib.Signals (on) import System.Glib.UTFString (glibToString) import System.IO (IO) -- | Un valor del tipo 'Ventana' es un objeto que representa a una ventana -- interactiva donde puede dibujarse un 'Diagrama'. Es posible, además, -- obtener información de qué teclas son pulsadas sobre la ventana. data Ventana = Ventana { mostrar ∷ [Paso] → Diagrama → IO () -- ^ Dada una 'Ventana', un 'Diagrama', y una lista de 'Paso's, -- representar gráficamente el 'Diagrama' dado sobre el lienzo de la -- 'Ventana', haciendo resaltar visualmente el nodo del árbol alcanzado -- si se realizan los movimientos correspondientes a la lista de -- 'Paso's desde la raíz del árbol. -- -- Los nodos se resaltan con un cuadro verde, y se colorean según el -- tipo de nodo. En el caso de nodos intermedios, se colorea en azul -- la región correspondiente al primer subárbol del nodo binario, y en -- rojo la región correspondiente al segundo subárbol. En el caso de -- nodos terminales (hojas), el rectángulo se colorea en amarillo. , leerTecla ∷ IO (Maybe String) -- ^ Dada una 'Ventana', esperar por un evento de teclado. -- -- Cuando sobre la ventana se haya pulsado alguna tecla que no haya sido -- reportada a través de este cómputo, se producirá como resultado -- @'Just' tecla@, donde @tecla@ será el nombre de la tecla. -- -- Si la ventana ya ha sido cerrada, se producirá como resultado -- 'Nothing'. -- -- El texto correspondiente a cada tecla es aproximadamente igual al -- nombre del símbolo en la biblioteca GDK sin el prefijo @GDK_KEY_@. -- La lista completa está disponible en -- . -- Sin embargo, la mejor manera de descubrir cuál simbolo corresponde -- a cada tecla es crear una 'Ventana' y hacer que se imprima el texto -- correspondiente a cada tecla pulsada sobre ella. , cerrar ∷ IO () -- ^ Dada una 'Ventana', hacer que se cierre y que no pueda producir -- más eventos de teclado. } -- | Construye un objeto del tipo 'Ventana' dadas sus dimensiones en número -- de píxeles. crearVentana ∷ Integer -- ^ Número de píxeles de anchura de la 'Ventana' a crear. → Integer -- ^ Número de píxeles de altura de la 'Ventana' a crear. → IO Ventana -- ^ La 'Ventana' nueva, ya visible, con el lienzo en blanco. crearVentana anchura' altura' = do chan ← newTMChanIO diagramaV ← newTVarIO mempty void initGUI window ← windowNew drawingArea ← drawingAreaNew set window [containerChild := drawingArea] void $ drawingArea `on` sizeRequest $ pure (Requisition (fromInteger anchura') (fromInteger altura')) void $ window `on` keyPressEvent $ do key ← glibToString <$> eventKeyName liftIO ∘ void ∘ atomically $ writeTMChan chan key pure True void $ drawingArea `on` exposeEvent $ do w ← eventWindow liftIO $ do renderToGtk w ∘ toGtkCoords ∘ sized (Dims (fromIntegral anchura') (fromIntegral altura')) =≪ readTVarIO diagramaV pure True void $ onDestroy window $ do mainQuit atomically $ closeTMChan chan widgetAddEvents window [KeyPressMask] widgetShowAll window void $ forkIO mainGUI let ventana = Ventana {..} cerrar = postGUISync $ widgetDestroy window leerTecla = atomically $ readTMChan chan mostrar pasos diagrama = postGUIAsync $ do atomically ∘ writeTVar diagramaV $ renderDiagrama pasos diagrama widgetQueueDraw drawingArea pure ventana renderDiagrama ∷ [Paso] → Diagrama → Diagram Cairo R2 renderDiagrama = go ∘ pure where go pasos = centerXY ∘ \ case d1 :-: d2 → foco blue (go pasosPrimero d1) === foco red (go pasosSegundo d2) d1 :|: d2 → foco blue (go pasosPrimero d1) ||| foco red (go pasosSegundo d2) Hoja Rectángulo { color = Color {..}, imagen = Imagen {..} } → foco yellow $ unitSquare # fillColor (sRGB24 rojo verde azul ∷ Colour Double) # scaleX (fromInteger anchura) # scaleY (fromInteger altura ) where pasosPrimero = case pasos of Just (Primero:xs) → Just xs _ → Nothing pasosSegundo = case pasos of Just (Segundo:xs) → Just xs _ → Nothing foco color diagrama = case pasos of Just [] → flip mappend (diagrama # centerXY) ∘ toRect ∘ boxExtents $ boundingBox diagrama _ → diagrama where toRect (R2 w h) = rect w h # fillColor (color ∷ Colour Double) # lineColor (green ∷ Colour Double) # lineWidth ultraThick # opacity 0.25