SDK Multiplataforma en C logo
EspañolEnglish
SDK Multiplataforma en C

Panel

❮ Anterior
Siguiente ❯

#Cabecera

#include <gui/panel.h>


#Funciones

Panel*panel_create (void)
Panel*panel_scroll (...)
Panel*panel_custom (...)
voidpanel_data (...)
type*panel_get_data (...)
voidpanel_size (...)
voidpanel_scroll_mode (...)
uint32_tpanel_layout (...)
Layout*panel_get_layout (...)
voidpanel_visible_layout (...)
voidpanel_update (...)
voidpanel_viewport (...)

Un Panel es un control dentro de una ventana que agrupa otros controles. Define su propio sistema de referencia, es decir, si desplazamos un panel todos sus descendientes se moverán al unísono ya que sus ubicaciones serán relativas al origen del mismo. Admitirá otros (sub)-paneles como descendientes, lo que permite conformar una Jerarquía de ventana (Figura 1). En pro de la portabilidad, esta librería Gui no permite indicar directamente coordenadas ni tamaños concretos para los elementos dentro de un panel, sino que la asociación se lleva a cabo mediante un objeto Layout que se encarga de calcular en tiempo de ejecución las ubicaciones finales de los controles en función de la plataforma y gestor de ventanas. En ¡Hola Subpanel! tienes un ejemplo elemental del uso de paneles.

  • Utiliza panel_create para crear un nuevo panel.
  • Utiliza panel_layout para añadir controles al panel.
  • Figura 1: Jerarquía de ventana.

#1. Panel multi-layout

Cada panel admite varios layouts y permite alternar entre ellos en tiempo de ejecución (Figura 2). Esto permite crear interfaces dinámicas responsivas con muy poco esfuerzo, ya que el propio panel se encarga de vincular y dimensionar los controles en función del layout activo en cada caso. En ¡Hola Multi-layout! tienes un ejemplo.

Figura 2: Panel con dos organizaciones diferentes para los mismos controles.

Debido a que los layout son estructuras lógicas fuera de la jerarquía de ventana, pueden compartir controles al estar vinculados con el mismo panel (Figura 3). Lo que no está permitido es utilizar los mismos objetos en diferentes paneles, por el propio concepto de jerarquía.

Al contrario que los paneles, los layouts no crean ningún control nativo (GtkWidget, HWND, NSView, etc).
Figura 3: Es posible reutilizar los mismos componentes entre layouts del mismo panel.

#2. Entendiendo el dimensionado de paneles

  • Utiliza panel_size para establecer el tamaño por defecto del panel.

Vamos a mostrar, por medio de un ejemplo, la lógica que se esconde detrás de la composición y dimensionado de paneles. Empezamos con (Listado 1) donde creamos un panel relativamente extenso en altura.

Listado 1: Composición de un panel con varias filas de edición.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static Window *i_window(void)
{
    uint32_t i, n = 20;
    Window *window = window_create(ekWINDOW_STDRES);
    Panel *panel = panel_create();
    Layout *layout = layout_create(2, n);

    for (i = 0; i < n; ++i)
    {
        char_t text[64];
        Label *label = label_create();
        Edit *edit = edit_create();
        bstd_sprintf(text, sizeof(text), "Value %02d", i);
        label_text(label, text);
        bstd_sprintf(text, sizeof(text), "Edit here value %02d", i);
        edit_text(edit, text);
        layout_label(layout, label, 0, i);
        layout_edit(layout, edit, 1, i);
    }

    for (i = 0; i < n - 1; ++i)
        layout_vmargin(layout, i, 3);

    layout_hmargin(layout, 0, 5);
    layout_margin4(layout, 10, 10, 10, 10);
    panel_layout(panel, layout);
    window_panel(window, panel);
    return window;
}
  • La línea 3 declara las variables de control y las líneas 4-6 crean la ventana, el panel y el layout.
  • El bucle 8-19 añade varias etiquetas y cajas de edición al layout.
  • El bucle 21-22 establece una pequeña separación entre filas.
  • Las líneas 24-25 establecen una separación entre columnas y un margen en el borde.
  • Las líneas 26-27 vinculan el layout con el panel y este con la ventana.

El resultado de este código es el Dimensionado natural del panel (Figura 4), que establece por defecto una anchura de 100 puntos para los controles de edición. Las etiquetas se ajustan al texto que contienen. También se han aplicado las separaciones y márgenes.

Panel con una lista de controles de edición.
Figura 4: Dimensionado natural del panel definido en (Listado 1).

En este caso es posible redimensionar la ventana, ya que hemos utilizado el flag ekWINDOW_STDRES al crearla (Figura 5).

Ventana con controles de edición de tamaño extendido.
Figura 5: Comportamiento del panel cuando crece la ventana.

Este comportamiento puede que no sea el más apropiado para el caso que nos ocupa. Por defecto, el layout realiza la Expansión de celdas de manera proporcional. Pero lo que realmente buscamos es "estirar" los controles de edición y que las filas mantengan su altura por defecto (Listado 2).

Listado 2: Cambio en la expansión horizontal y vertical.
Layout *layout = layout_create(2, n + 1);
...
layout_hexpand(layout, 1);
layout_vexpand(layout, n);

Las líneas anteriores provocan que la expansión horizontal recaiga exclusivamente sobre la columna 1 (la de los EditBox). Por otro lado, se ha creado una fila extra vacía, volcando en ella toda la expansión vertical (Figura 6).

Ventana con controles de edición de tamaño extendido, mejorando la disposición.
Figura 6: Comportamiento deseado, ante la expansión de la ventana.

Si bien el panel se comporta ahora correctamente ante el crecimiento de la ventana, tenemos dificultades cuando queremos "encogerla" por debajo de cierto límite (Figura 7). Esto es debido a que el dimensionado natural impone un tamaño mínimo, ya que llega un momento que es imposible reducir los controles asociados al layout.

Ventana con controles de edición y tamaño reducido.
Figura 7: Tamaño mínimo del panel.

Esto puede suponer un problema, ya que es posible que tengamos paneles suficientemente grandes que excedan incluso el tamaño del monitor y no puedan mostrarse en su totalidad. Para solucionarlo podemos establecer un tamaño por defecto para todo el panel (Listado 3), que será el que muestre al iniciar la ventana (Figura 8).

Listado 3: Tamaño por defecto del panel.
...
panel_size(panel, s2df(400, 300));
...
Ventana con controles de edición y tamaño predeterminado.
Figura 8: Dimensionado natural, forzado a 400x300.

Este comando desvincula, en cierta manera, el tamaño del panel del tamaño de su contenido. De esta forma, el Layout tiene libertad para reducir el tamaño de la vista, independientemente de si puede o no mostrar la totalidad del contenido (Figura 9).

Ventana con controles de edición y tamaño muy reducido.
Figura 9: Reducción de los límites del panel.

Y ya por último, si queremos, podemos crear el panel con barras de scroll (Listado 4) y desplazarnos por el contenido no visible (Figura 10).

Listado 4: Panel con barras de scroll.
...
Panel *panel = panel_scroll(TRUE, TRUE);
...
Ventana con controles de edición y barras de scroll.
Figura 10: Panel con barras de scroll.

Y, por supuesto, todo lo dicho funcionará de la misma forma en cualquier plataforma (Figura 11).

Ventana con controles de edición y barras de scroll, versión macOS. Ventana con controles de edición y barras de scroll, versión Linux.
Figura 11: Nuestro panel funcionando en macOS y Linux.

#3. Barras de scroll en paneles

  • Utiliza panel_scroll para crear un nuevo panel con barras de scroll.
  • Utiliza panel_custom si además quieres resaltar el borde del panel.
  • Utiliza panel_scroll_mode para definir el comportamiento de las barras de scroll.

Como hemos visto en Entendiendo el dimensionado de paneles, panel_size desvincula el tamaño de un panel del de su contenido, y panel_scroll añade barras para desplazarnos por la parte no visible. Pero en cuanto aparece una barra de scroll surge una pregunta: ¿quién se hace cargo del espacio físico que ocupa esa barra? La respuesta depende de si el sistema la dibuja en estilo legacy o overlay.

  • Las barras legacy (o "sólidas") ocupan espacio real de forma permanente — el área de contenido se reduce para dejarles sitio, estén o no siendo usadas en ese momento. Es el único estilo disponible en Windows, y el que usan macOS y Linux cuando el usuario fija esa preferencia en el sistema.
  • Las barras overlay flotan sobre el contenido sin reservar ningún espacio — permanecen ocultas o casi transparentes hasta que el usuario interactúa con ellas (mover el ratón sobre el área de dibujo, hacer scroll...), momento en el que aparecen brevemente encima del contenido. Es el estilo por defecto en macOS y en la mayoría de distribuciones Linux modernas (GNOME/GTK3) con trackpad.
  • La elección entre uno u otro estilo no la hace la aplicación, sino el propio sistema operativo — en macOS, Ajustes del Sistema > Trackpad > Desplazamiento y zoom > Mostrar barras de desplazamiento; en Linux/GNOME, el ajuste equivalente de accesibilidad del entorno de escritorio activo. NAppGUI detecta el cambio de estilo en caliente, sin necesidad de reiniciar la aplicación ni recrear la ventana.

La diferencia entre ambos estilos no es solo visual — también determina si el espacio de la barra se resta o no del área disponible para el contenido (Figura 12).

Animación mostrando el cambio de estilo de scrollbar al expandir el panel.
Figura 12: Cambio de barra overlay a legacy, agrandando la ventana para dejar sitio a la barra sin invadir el contenido.

Si no queremos (o no podemos) agrandar la ventana, panel_scroll_mode permite decidir qué hacer con ese hueco cuando la barra es legacy: reservar siempre el mismo espacio fijo (modo estable), o dejar que el contenido se estreche dinámicamente para cederle sitio a la barra sin que el tamaño exterior del panel cambie (Figura 13).

Listado 5: Activar el modo de estrechamiento en los dos ejes.
Animación mostrando el modo de estrechamiento de .
Figura 13: Cambio de barra overlay a legacy, estrechando el contenido para que el tamaño del panel no cambie.
  • Con hshrink/vshrink a FALSE (comportamiento estable), el panel reserva siempre el margen de la barra legacy, incluso cuando esta no está visible en ese momento — evitando el "salto" de contenido cada vez que la barra aparece o desaparece.
  • Con hshrink/vshrink a TRUE (valor por defecto), el contenido se redimensiona para aprovechar el espacio completo cuando no hace falta la barra, y se encoge justo lo necesario cuando sí hace falta — sin dejar nunca un hueco en blanco permanente.
  • En overlay, ninguno de los dos modos reserva espacio — la barra flota sobre el contenido sin afectar al layout, así que panel_scroll_mode no tiene ningún efecto visible en ese estilo.

Por último, la navegación no depende únicamente de arrastrar la propia barra. El contenido se puede desplazar con la rueda del ratón (vertical) y, en la mayoría de sistemas, con Mayús + rueda para desplazamiento horizontal — el mismo gesto estándar que usan aplicaciones como VS Code o Microsoft 365. En trackpads y ratones táctiles (como el Magic Mouse de Apple), el deslizamiento lateral de dos dedos ya produce scroll horizontal de forma nativa, sin necesidad de ninguna tecla modificadora.

❮ Anterior
Siguiente ❯

#panel_create ()

Crea un panel.

Panel*
panel_create(void);

Retorna

El nuevo panel.


#panel_scroll ()

Crea un panel con barras de scroll.

Panel*
panel_scroll(const bool_t hscroll,
             const bool_t vscroll);
hscroll

TRUE si queremos barra de scroll horizontal.

vscroll

TRUE si queremos barra de scroll vertical.

Retorna

El nuevo panel.

Observaciones

Ver Entendiendo el dimensionado de paneles y Barras de scroll en paneles.


#panel_custom ()

Crea un panel totalmente configurable.

Panel*
panel_custom(const bool_t hscroll,
             const bool_t vscroll,
             const bool_t border);
hscroll

TRUE si queremos barra de scroll horizontal.

vscroll

TRUE si queremos barra de scroll vertical.

border

TRUE si queremos resaltar el borde.

Retorna

El nuevo panel.

Observaciones

Ver Entendiendo el dimensionado de paneles y Barras de scroll en paneles.


#panel_data ()

Asocia datos de usuario con el panel.

void
panel_data(Panel *panel,
           type **data,
           FPtr_destroy func_destroy_data,
           type);
panel

El panel.

data

Datos de usuario.

func_destroy_data

Destructor de los datos de usuario. Será llamado al destruir el panel.

type

Tipo de datos de usuario.


#panel_get_data ()

Obtiene los datos de usuario asociados con el panel.

type*
panel_get_data(const Panel *panel,
               type);
panel

El panel.

type

Tipo de datos de usuario.

Retorna

Los datos de usuario.


#panel_size ()

Establece el tamaño por defecto del área visible de un panel.

void
panel_size(Panel *panel,
           const S2Df size);
panel

El panel.

size

El tamaño por defecto.

Observaciones

Ver Entendiendo el dimensionado de paneles.


#panel_scroll_mode ()

Establece cómo reacciona el contenido de un panel al espacio ocupado por sus barras de scroll legacy.

void
panel_scroll_mode(Panel *panel,
                  const bool_t hshrink,
                  const bool_t vshrink);
panel

El panel.

hshrink

TRUE para que el contenido se estreche en altura cuando aparezca la barra horizontal, en vez de reservarle siempre un margen fijo.

vshrink

TRUE para que el contenido se estreche en anchura cuando aparezca la barra vertical, en vez de reservarle siempre un margen fijo.

Observaciones

Solo tiene efecto con barras legacy — en overlay no hay espacio que reservar ni del que estrecharse. Ver Barras de scroll en paneles.


#panel_layout ()

Añade un layout a un panel.

uint32_t
panel_layout(Panel *panel,
             Layout *layout);
panel

El panel.

layout

El layout.

Retorna

El índice del layout recién añadido.


#panel_get_layout ()

Obtiene un layout de un panel.

Layout*
panel_get_layout(Panel *panel,
                 const uint32_t index);
panel

El panel.

index

El índice del layout.

Retorna

El layout.


#panel_visible_layout ()

Establece el layout activo dentro del panel.

void
panel_visible_layout(Panel *panel,
                     const uint32_t index);
panel

El panel.

index

El índice del layout.

Observaciones

Para el cambio se haga efectivo, hay que llamar a panel_update.


#panel_update ()

Refresca el contenido de la ventana que contiene el panel.

void
panel_update(Panel *panel);
panel

El panel.

Observaciones

Es equivalente a llamar a window_update.


#panel_viewport ()

Obtiene las dimensiones del área visible del panel.

void
panel_viewport(const Panel *panel,
               V2Df *pos,
               S2Df *size);
panel

El panel.

pos

La posición del viewport. Puede ser NULL.

size

El tamaño del viewport. Puede ser NULL.

Observaciones

Si el panel no tiene barras de scroll, pos será (0,0). Ver Barras de scroll en paneles.

❮ Anterior
Siguiente ❯