6.3 El contenedor Layout (distribución)

Hasta ahora el empaquetado de widgets se ha realizado por secuenciado en cajas verticales u horizontales, o en una tabla. Puedes, sin embargo, colocar los widgets en la posición que desees usando un widget Fixed (Fijo) o un Layout (algo así como plano de distribución). No se recomienda usar un contenedor Fixed, ya que no se adapta bien a los cambios de tamaño de la ventana.

El contenedor Layout es parecido al contenedor Fijo, pero se diferencia en que implementa un área de desplazamiento infinita (cuando infinito es menor que 2^32). El sistema X window (sobre el que está basado Gtk+) tiene una limitación de anchura o altura, y no pueden superar los 32767 pixels. El contenedor Layout puede esquivar este problema haciendo cosas exóticas, lo que permite tener suaves deplazamientos incluso cuando tienes muchos widgets hijos en el área de desplazamiento. De este modo las desventajas del widget Fixed se esquivan.

Para crear un contenedor Layout usamos:

layoutNew :: Maybe Adjustment -> Maybe Adjustment -> IO Layout

Como puedes ver, puedes optar por especificar los objetos Adjustment que el widget Layout usará para su desplazamiento.

Puedes añadir y mover widgets en el contenedor Layout container usando las dos funciones siguientes:

layoutPut :: (LayoutClass self, WidgetClass childWidget)
=> self -> childWidget -> Int -> Int -> IO ()
layoutMove :: (LayoutClass self, WidgetClass childWidget)
=> self -> childWidget -> Int -> Int -> IO ()   

El primer argumento es la posición x, el segundo la posición y. La posición superior izquierda es (0,0), x crece de izquierda a derecha e y de arriba abajo.

Se puede fijar el tamaño del contenedor Layout usando la siguiente función:

layoutSetSize :: LayoutClass self => self  -> Int -> Int -> IO ()

El primer argumento es la anchura de toda el área desplazable, el segundo su altura.

En el ejemplo hemos puesto una lista de etiquetas, cada una con una letra mayúscula del alfabeto, en un círculo. La etiquetas se posicionan perpendicularmente al radio, usando la función :

labelSetAngle :: labelClass self => self -> Double -> IO ()

El ángulo se mide en grados, medidos en contra de las agujas del reloj.

Alphabet

El widget layout se posiciona en una ventana desplazable con containerAdd ya que en sí es desplazable, y además no necesita un viewport, como vimos en el capítulo 6.1. Las etiquetas se posicionan usando coordenadas angulares, que son transformadas en coordenadas cartesianas con las funciones de Prelude sin (seno) y cos (coseno). Estas toman el argumento en radianes (entre 0 y (2 * pi)). En el ejemplo, la anchura y la altura están parametrizadas, como lo está la lista a mostrar. Además, en main las esquinas del Layout se marcan, por lo que es sencillo experimentar con su tamaño, si quieres. Fíjate en que el marcador ha sido reemplazado por un '+' debido a las quejas del validador.

import Graphics.UI.Gtk

main :: IO ()
main = do
     initGUI
     window <- windowNew
     set window [windowTitle := "Alphabet" , windowDefaultWidth := 350,
             windowDefaultHeight := 350 , containerBorderWidth := 10]
     sw <- scrolledWindowNew Nothing Nothing
     set sw [scrolledWindowPlacement := CornerBottomRight, 
             scrolledWindowShadowType := ShadowEtchedIn,
             scrolledWindowHscrollbarPolicy := PolicyAutomatic,
             scrolledWindowVscrollbarPolicy := PolicyAutomatic ]
     containerAdd window sw

     layt <- layoutNew Nothing Nothing
     layoutSetSize layt myLayoutWidth myLayoutHeight
     widgetModifyBg layt StateNormal (Color 65535 65535 65535)
     containerAdd sw layt     
 
     upleft  <- labelNew (Just "+(0,0)")
     layoutPut layt upleft 0 0
     upright <- labelNew (Just ("+(" ++ (show (myLayoutWidth - 50)) ++",0)"))
     layoutPut layt upright (myLayoutWidth -50)  0
     dwnright <- labelNew (Just ("+(0," ++ (show (myLayoutHeight -20)) ++ ")"))
     layoutPut layt dwnright 0 (myLayoutHeight -20)
     dwnleft <- labelNew (Just ("+(" ++ (show(myLayoutWidth -70)) ++ "," ++
                                  (show (myLayoutHeight -20)) ++ ")"))
     layoutPut layt dwnleft (myLayoutWidth -70) (myLayoutHeight - 20)
     
     labels <- sequence $ map (labelNew . Just) txtls
     sequence_ $ map (\x -> widgetModifyFg x StateNormal (Color 0 0 45000)) labels
     
     let wnums = zip labels [0..]
     sequence_ $ map (myLayoutPut layt) wnums     

     widgetShowAll window
     onDestroy window mainQuit
     mainGUI

-- parameters
myLayoutWidth :: Int
myLayoutWidth = 800

myLayoutHeight :: Int
myLayoutHeight = 800

txtls :: [String]
txtls = map (\x -> x:[]) ['A'..'Z']
-- end parameters

step :: Double
step = (2 * pi)/(fromIntegral (length txtls))

ox :: Int
ox =  myLayoutWidth `div` 2

oy :: Int
oy = myLayoutHeight `div` 2

radius :: Double
radius = 0.25 * (fromIntegral ox)

angle :: Int -> Double
angle num = 1.5 * pi + (fromIntegral num) * step

num2x :: Int -> Int
num2x n = ox + relx where 
              relx = round $ radius * (cos (angle n))

num2y :: Int -> Int
num2y n = oy + rely where
              rely = round $ radius * (sin (angle n))

myLayoutPut :: Layout -> (Label, Int) -> IO ()
myLayoutPut lt (lb, n) = do 
         layoutPut lt lb (num2x n) (num2y n) 
         labelSetAngle lb (letterAngle n)

letterAngle :: Int -> Double
letterAngle n = (270 - degree) where
                    degree = (angle n) * (180.0 /pi)