# Picker (clase base)

> Base de todos los pickers — el patrón getAnswer() + show(onAnswer).

Source: https://crossfly.dev/es/docs/ui/widgets/picker — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# Picker (clase base)

`Picker` es la base común de todas las ventanas de selección de CrossFly (fecha, hora, archivo, foto, color, código de barras, coordenada). Cada picker se abre, deja que el usuario interactúe y, al **confirmar**, se cierra solo entregando el resultado de forma asíncrona, mediante callback, al código que lo abrió.

Entender el patrón de esta página es entender **todos** los pickers de una vez.

> 🟡 · **Paquete:** `dev.crossfly.ui.picker` · **Extiende:** `Window` · **Transición:** `FADE` por defecto

## El patrón central: `getAnswer()` + `show(onAnswer)`

- **`Picker.getAnswer()`** produce el valor seleccionado (cada subclase lo sobrescribe para devolver el tipo concreto: `String`, `Integer`, `LatLon`…). En la base, retorna `null`.
- **`Window.show(RunnableWith<Object> onAnswer)`** consume ese valor **cuando la ventana se cierra**.

El vínculo entre ambos está en `Window.hide()`:

```java
public void hide() {
   ...
   Object answer = this instanceof Picker ? ((Picker) this).getAnswer() : null;
   if (onAnswer != null && answer != null)
      onAnswer.run(answer);
   ...
}
```

Consecuencias prácticas (todas confirmadas en el código):

- **El callback `onAnswer` solo se dispara en `hide()` y únicamente si `getAnswer() != null`.** Al **cancelar**, las subclases hacen que `getAnswer()` retorne `null` — así que `onAnswer` **no** se llama. Quien necesite reaccionar a la cancelación debe sobrescribir `onHide()`/`onRemove()` o usar el callback específico de la subclase.
- `show(onAnswer)` es **thread-safe**: si se llama fuera de la paint thread, se reprograma y bloquea hasta concluir.
- El valor entregado es `Object`; el consumidor hace **cast** al tipo concreto del picker.

> Esto contrasta con [`Alert`](/docs/ui/widgets/alert)/[`ActionSheet`](/docs/ui/widgets/actionsheet), que extienden `Window` pero **no** `Picker` — en ellos `show(onAnswer)` no se dispara y la respuesta llega por callbacks de botón.

## Ejemplo completo (copiar, pegar y ejecutar)
```java
package com.exemplo;

import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.picker.*;

/** Pantalla principal ejecutable. Apunta el launcher (classname) y corre: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      Button abrir = new Button("Abrir picker");
      abrir.onAction(c ->
      {
         // Cualquier subclase de Picker sigue el mismo patrón show(onAnswer)
         DatePicker dp = new DatePicker();
         dp.show(answer ->
         {
            // 'answer' es el Object devuelto por getAnswer(); solo llega aquí si != null
            String data = (String) answer;
            Hint.show("Selecionado: " + data, 2000);
         });
      });
      add(LAY_AUTO, abrir);
   }
}
```

> Los fragmentos de abajo muestran **solo el núcleo** (lo que va dentro de `onShow()`). Para ejecutarlos, colócalos en el esqueleto `MainWindow` de arriba.

## Uso básico (forma genérica de cualquier picker)
```java
DatePicker dp = new DatePicker();
dp.show(answer ->
{
   // 'answer' es el Object devuelto por getAnswer(); solo llega aquí si != null
   String data = (String) answer;
   Debug.log("Selecionado: " + data);
});
```

## Métodos públicos
| Método | Retorno | Descripción |
|---|---|---|
| `getAnswer()` | `Object` | Resultado del picker, o `null` si se canceló. Sobrescrito por cada subclase. |
| `show(RunnableWith<Object> onAnswer)` | `void` | Abre el picker; entrega el valor mediante callback al cerrarse (heredado de `Window`). |

## Tipos devueltos por subclase
| Picker | `getAnswer()` devuelve |
|---|---|
| [`DatePicker`](/docs/ui/widgets/datepicker) | `String` (fecha según `LocaleFormats.dateFormat`/`dateSeparator`) |
| [`TimePicker`](/docs/ui/widgets/timepicker) | `String` (`HH:MM`, 24h) |
| [`FilePicker`](/docs/ui/widgets/filepicker) | — (esqueleto vacío; hereda `null` de la base) |
| [`PhotoPicker`](/docs/ui/widgets/photopicker) | `String` (ruta **o** `"ERROR: ..."`) |
| [`ColorPicker`](/docs/ui/widgets/colorpicker) | `Integer` (RGB sin alpha) |
| [`BarcodePicker`](/docs/ui/widgets/barcodepicker) | `String` (payload leído) |
| [`LocationPicker`](/docs/ui/widgets/locationpicker) | `LatLon` |

## Buenas prácticas
- Haz siempre el **cast** del `Object` al tipo concreto de la tabla anterior.
- Para reaccionar a la cancelación, no cuentes con `onAnswer` — usa el callback específico (`onSelect`/`onCapture`) o sobrescribe `onHide()`.
- Los pickers de hardware (`PhotoPicker`, `BarcodePicker`, `LocationPicker`) liberan recursos al cerrarse; no reutilices la misma instancia tras `hide()`.

## Errores comunes
- **Esperar `onAnswer` en la cancelación** — no se dispara, porque `getAnswer()` devuelve `null`.
- **Olvidar el cast** — el callback recibe `Object`.

## Véase también
- Guía del área: [`ui/`](/docs/ui) · Recetas: [`cookbook/`](/docs/cookbook)
- Subclases: [`DatePicker`](/docs/ui/widgets/datepicker) · [`TimePicker`](/docs/ui/widgets/timepicker) · [`FilePicker`](/docs/ui/widgets/filepicker) · [`PhotoPicker`](/docs/ui/widgets/photopicker) · [`ColorPicker`](/docs/ui/widgets/colorpicker) · [`BarcodePicker`](/docs/ui/widgets/barcodepicker) · [`LocationPicker`](/docs/ui/widgets/locationpicker)
