Este es uno de los capítulos del tutorial Aplicaciones nativas en JavaScript con GJS. Encontrarás los enlaces a todos los de capítulos, al final de este artículo.
Una vez has visto en el capítulo anterior del tutorial sobre aplicaciones nativas en JavaScript que son los [widgets](), las unidades básicas con las que puede interactuar un usuario, en este capítulo descubrirás como disponerlas en una ventana gráfica. Así, si en el capítulo anterior descubriste los widgets, en este capítulo te voy a presentar los layouts. Los layouts en Gtk con JavaScript.
Esto de los layouts en Gtk con JavaScript tiene miga de la que uno pueda pensar. Es posible, que te plantees, o al menos así lo hacía yo hace algún tiempo, que la mejor manera de colocar un widget en una ventana, es mediante unas coordenadas específicas, es decir, fijando su posición. Pero, nada mas lejos de la realidad. Una posición fija no es ni mucho menos la mejor solución, en tanto en cuanto, el usuario, puede redimensionar la ventana, y los elementos quedaran dispuestos de una manera que tu no tenías prevista.
Layouts en Gtk con JavaScript
¿Que es un layout en Gtk?
Un layout en Gtk no es, ni mas ni menos, que la manera en la que se disponen los widgets. Si partes de que los widgets no se colocan en una posición fija, es necesario, indicar como se van a disponer, como se van a colocar en una ventana o en otro widget.
Un widget ocupa el espacio necesario para mostrar su contenido, ni mas ni menos, ni menos ni mas*. Así, por ejemplo, si utilizas un *widget*, para mostrar un texto, este widget *medirá* tanto como su contenido. Si, el contenido es palabra
ocupará el espacio de palabra
mas el correspondiente para pintar los bordes, márgenes, etc. Pero, si tiene que mostrar esto es una palabra
, ocupará bastante mas. Pero, siempre, el tamaño necesario para mostrar su contenido.
Este comportamiento, se puede modificar, utilizando contenedores y configuraciones específicas a cada widget. Es posible forzar el tamaño de un widget, indicando que tenga un alto o ancho determinados. O también, si dispones varios widgets en un contenedor, es posible configurarlos para que el ancho sea el mismo para todos los widgets. De nuevo, estás modificando el comportamiento por defecto de los widgets.
Tipos de Layouts en Gkt con JavaScript
Existen básicamente cuatro tipo de layout en Gtk. Según tus necesidades, o las necesidades de la aplicación que estés creando, puedes utilizar un layout u otro.
Gtk.Box
Gtk.Grid
Gtk.FlowBox
Gtk.ListBox
A continuación, encontrarás cada uno de los diferentes layout, con un ejemplo, para que sea mas fácil entender el comportamiento del mismo. Cada uno de los ejemplos, simplemente lo tienes que copiar en un archivo, darle permisos de ejecución y con un doble clic ejecutar. O bien, si quieres puedes hacerlo directamente desde el terminal utilizando gjs
.
Gtk.Box
Este es el mas sencillo de los layouts en Gtk. Simplemente te permite disponer los widgets que se encuentran en su interior o bien de forma vertical o de forma horizontal, según le indiques. En pocas palabras, o bien se colocan uno encima de otro o bien uno al lado de otro.
El tamaño de los widgets dentro de un layout de tipo Gtk.Box
puede ser homogéneo, de forma que todos ocupan lo que ocupa el mayor de todos. O bien, pueden tener el tamaño calculado, es decir, el tamaño en función de su contenido.
Indicar que un layout dentro de otro layout se comporta de la misma forma, es decir, puede tener un tamaño calculado, o bien modificado según determinadas propiedades del mismo.
Este layout tiene algunas propiedades específicas de este objeto, y otras heredadas, como puede ser el caso de la orientación. Así, puede definir un objeto de este tipo de la siguiente manera,
let layout = new Gtk.Box({
margin: 5,
orientation: Gtk.Orientation.VERTICAL,
"baseline-position": Gtk.BaselinePosition.BOTTOM,
homogeneous: true,
spacing: 20
});
En este caso, para añadir widgets en el interior de un Gtk.Box, tienes dos métodos específicos, pack_start
y pack_end
. El uso en ambos casos es el mismo, la diferencia es que mientras que el primero llena el objeto de arriba hacia abajo o de derecha a izquierda, el segundo lo hace justo al revés. Así, para añadir una etiqueta se podría hacer de la siguiente forma,
let etiqueta = new Gtk.Label({text: "etiqueta"});
layout.pack_start(etiqueta, false, false, 5);
Los parámetros que has definido para añadir la etiqueta son los siguientes,
expand
el espacio adicional se reparte entre todos los widget que tengan esta opción.fill
implica que el objeto rellenará todo el espacio que se le haya designado al objeto.padding
es el espacio adicional en pixel entre el widget y sus vecinos.
Para entendernos, supongamos que defines una ventana. Si a esta ventana no le das ninguna dimensión definida, se calculará el tamaño de la misma en función de los objetos que contenga.
En cambio, si decides definir unas dimensiones a la ventana, como en el ejemplo siguiente, la cosa cambia radicalmente,
super._init({
defaultWidth:200,
defaultHeight: 200
});
En este caso, puede suceder que, al poner los objetos en la ventana, sobre espacio. Este espacio, se repartirá entre los objetos, o no, dependiendo de las opciones que hayas marcado. Así, si marcaste la opción expand
el espacio se repartirá entre estos objetos. Y si además marcaste la opción fill
, el objeto rellenará ese espacio.
Gtk.Grid
Este segundo layout, es sin lugar a dudas, el que mas utilizo actualmente de entre todos los layouts en Gtk, por las posibilidades que me ofrece a la hora de crear mis interfaces gráficos. Así, para definir un layout de tipo Grid, lo puedes hacer como en el siguiente ejemplo,
let layout = new Gtk.Grid({
margin: 10,
"baseline-row": Gtk.BaselinePosition.CENTER,
"column-homogeneous": true,
"column-spacing": 10,
"row-homogeneous": false,
"row-spacing": 10
});
En este caso las opciones que tenemos para caracterizar nuestro layout, son las siguientes,
column-homogeneous
todas las columnas tendrán el mismo anchocolumn-spacing
define el espacio entre columnasrow-homogeneous
todas las filas tendrán el mismo altorow-spacing
el espacio entre filas
En el caso de que hayas elegido la opción de que todas las filas o columnas tengan el mismo alto o ancho, respectivamente, lo que se hará es repartir el espacio restante total, al igual que has visto en el layout Gtk.Box
.
Para añadir un widget a este layout, tienes dos métodos,
attach
donde indicas la posición del widget en coordenadasattach_next_to
donde indicas la posición relativa del widget respecto a otros elementos.
En el primer caso, lo puedes hacer de la siguiente forma,
let button = new Gtk.Button({label: 'Dialogo'});
layout.attach(button, 0, 0, 1, 1);
Los valores definidos son, la posición en horizontal y vertical, seguidos por el espacio ocupado en horizontal y vertical. Así, en este primer ejemplo, el widget está situado en la primera celda y ocupa exactamente una celda en vertical y una en horizontal.
En el segundo, caso, podrías añadir un segundo objeto de la siguiente forma,
let button2 = new Gtk.Button({label: 'Para salir'});
layout.attach_next_to(button2, button, Gtk.PositionType.LEFT, 1, 1);
Si te fijas, en este segundo caso, indicas la posición relativa con respecto al primero, button
, indicando que irá ubicado a su izquierda. Además indicas el espacio que ocupa de la misma forma que lo indicaste en el caso anterior.
Gtk.FlowBox
Este layout modifica la disposición de los objetos que contiene en función del tamaño del mismo. Por ejemplo, si tienes una ventana con un Gtk.FlowBox
, si redimensionas la ventana, dependiendo de como lo hayas definido, puede suceder que los objetos pasen de ser una columna a una rejilla, por ejemplo.
Evidentemente, para definir correctamente el comportamiento de los objetos conforme los vas añadiendo al Gtk.FlowBox
, necesitas establecer una serie de propiedades, que marcarán la disposición de los mismos. Por ejemplo,
let layout = new Gtk.FlowBox({
margin: 10,
homogeneous: false,
"column-spacing": 10,
"row-spacing": 10,
"min-children-per-line": 2,
"max-children-per-line": 4,
"selection-mode": Gtk.SelectionMode.NONE,
orientation: Gtk.Orientation.HORIZONTAL
});
Aquí ya encuentras algunas opciones que son viejas conocidas, como es el margin
, que marca la distancia respecto a otros widget o layout. Igualmente, tienes column-spacing
y row-spacing
, que definen el espacio entre filas y columnas.
Pero, además tienes dos opciones nuevas como son,
min-children-per-line
que indica el número mínimo de objetos por línea, ya sea vertical u horizontalmax-children-per-line
, exactamente igual que el anterior, pero en lugar de definir el mínimo, define el máximo.orientation
, establece la orientación del layout.selection-mode
, indica si se seleccionan las celdas. En el ejemplo anterior, he marcado que no se seleccionen, porque desde mi punto de vista me da la impresión que despista.
Añadir un objeto a este layout es mucho mas sencillo que en los otros layouts en Gtk. Por ejemplo,
layout.add(new Gtk.Label({
label: 'Etiqueta 1',
halign: Gtk.Align.CENTER,
valign: Gtk.Align.CENTER
}));
Gtk.ListBox
Este layout es un contenedor vertical donde ubicarás elementos del tipo Gtk.ListBoxRow
. Estas filas se pueden ordenar de forma dinámica, y las puedes filtrar en función del contenido de cada una.
Un problema con el que te puedes encontrar es que ListBoxRow
solo te permite añadir un objeto o widget en su interior. Para soslayar este problema puedes utilizar algunos de los layout que has visto anteriormente.
Así, para definir un Gtk.ListBox
solo tienes que utilizar el siguiente código,
let layout = new Gtk.ListBox({
margin: 10,
"selection-mode": Gtk.SelectionMode.SINGLE
});
De nuevo, utilizo la propiedad margin
para definir la distancia a otros objetos circundantes. Además utilizo la propiedad selection-mode
, que es específica de Gtk.ListBox
para definir como se seleccionan las Gtk.ListBoxRow
.
El siguiente paso es definir una fila y añadirla a tu layout. Para esto tienes el siguiente código,
let row1 = new Gtk.ListBoxRow();
layout.add(row1);
Si quieres añadir mas de un objeto en el interior de esa fila, tienes que utilizar un layout de apoyo, como puede ser un Gtk.Box
que has visto anteriormente. Por ejemplo,
let container1 = new Gtk.Box({
margin: 5,
orientation: Gtk.Orientation.HORIZONTAL,
homogeneous: false,
spacing: 10
});
row1.add(container1);
Y a partir de aquí, no te queda mas que añadir tantos objetos como necesites para completar la fila. Continuando con el ejemplo,
let label1 = new Gtk.Label({label: 'Etiqueta 1'});
container1.pack_start(label1, false, false, 5);
let button1 = new Gtk.Button({label: 'Botón 1'});
container1.pack_start(button1, false, false, 5);
Conclusiones
Estos son básicamente los widget que puedes utilizar para colocar otros widgets de una forma, relativamente, sencilla en tu ventana, cuadro de diálogo o donde necesites.
Como has podido ver, cada uno de estos layout tiene una gran cantidad de propiedades, y los widget que añades se comportan de una forma u otra en función del layout que utilices. Aquí, solo te queda jugar tanto con layout como con widget para conseguir sacarle el máximo beneficio.
Para facilitarte el seguimiento del tutorial, he creado un repositorio en GitHub, donde puedes encontrar todos los ejemplos que voy mostrando aquí.
Recuerda, que para lanzar uno de los ejemplos tan solo tienes que ejecutar la siguiente instrucción en un terminal,
gjs ejemplo.js
Donde tendrás que sustituir ejemplo.js
por el nombre del ejemplo en cuestión.