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.
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)