Manual Completo de Programación con FLTK

1. Introducción a FLTK

FLTK (Fast Light Toolkit) es un toolkit gráfico multiplataforma escrito en C++ que permite desarrollar interfaces gráficas de usuario (GUI) ligeras y eficientes.

1.1 Características principales

1.2 Instalación

Linux (Debian/Ubuntu):

sudo apt-get install libfltk1.3-dev

Linux (Fedora):

sudo dnf install fltk-devel

macOS (Homebrew):

brew install fltk

Windows:

Descargar desde fltk.org y configurar las rutas en tu IDE.

1.3 Compilando un programa FLTK

// Compilar en Linux/macOS
g++ programa.cpp -o programa `fltk-config --cxxflags --ldflags`

// Compilar en Windows (ruta de ejemplo)
g++ programa.cpp -o programa.exe -I"C:\FLTK\include" -L"C:\FLTK\lib" -lfltk -lfltk_images -lfltk_jpeg -lfltk_png -lfltk_z -lole32 -luuid -lcomctl32

2. Conceptos Básicos

2.1 Estructura básica de un programa FLTK

#include <FL/Fl.H>      // Manejo de eventos
#include <FL/Fl_Window.H> // Ventana
#include <FL/Fl_Button.H> // Botón

int main() {
    // Crear ventana (ancho, alto, título)
    Fl_Window *window = new Fl_Window(400, 300, "Mi Aplicación");
    
    // Crear botón (x, y, ancho, alto, etiqueta)
    Fl_Button *button = new Fl_Button(150, 120, 100, 40, "Hola FLTK");
    
    // Mostrar ventana
    window->end();
    window->show();
    
    // Bucle principal de eventos
    return Fl::run();
}

2.2 Jerarquía de widgets

FLTK sigue una jerarquía de objetos donde todos los widgets heredan de Fl_Widget:

Fl_Widget (base)
├── Fl_Group (contenedores)
│   ├── Fl_Window
│   ├── Fl_Pack
│   └── Fl_Tabs
├── Fl_Button
│   ├── Fl_Return_Button
│   └── Fl_Repeat_Button
├── Fl_Input
│   ├── Fl_Int_Input
│   └── Fl_Secret_Input
└── ...

2.3 Sistema de coordenadas

3. Widgets Básicos

3.1 Fl_Window (Ventana)

#include <FL/Fl_Window.H>

int main() {
    // Crear ventana (ancho, alto, título)
    Fl_Window *win = new Fl_Window(640, 480, "Ventana Principal");
    
    // Configurar color de fondo (R,G,B)
    win->color(FL_WHITE);
    
    // Hacer la ventana redimensionable
    win->resizable(win);
    
    // Mostrar ventana y entrar en bucle
    win->show();
    return Fl::run();
}

3.2 Fl_Button (Botón)

#include <FL/Fl_Button.H>

// Función callback para el botón
void button_cb(Fl_Widget *w, void *data) {
    printf("Botón presionado!\n");
}

int main() {
    Fl_Window *win = new Fl_Window(300, 200);
    
    Fl_Button *btn = new Fl_Button(100, 80, 100, 40, "Presionar");
    btn->callback(button_cb); // Asignar callback
    
    win->end();
    win->show();
    return Fl::run();
}

3.3 Fl_Box (Caja de texto/etiqueta)

#include <FL/Fl_Box.H>

int main() {
    Fl_Window *win = new Fl_Window(400, 300);
    
    // Crear etiqueta (x, y, ancho, alto, texto)
    Fl_Box *box = new Fl_Box(50, 50, 300, 30, "Hola Mundo FLTK");
    
    // Configurar alineación y fuente
    box->align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE);
    box->labelfont(FL_BOLD);
    box->labelsize(16);
    
    win->end();
    win->show();
    return Fl::run();
}

3.4 Fl_Input y Fl_Output (Entrada/Salida de texto)

#include <FL/Fl_Input.H>
#include <FL/Fl_Output.H>
#include <FL/Fl_Button.H>

void copy_cb(Fl_Widget *w, void *data) {
    Fl_Input *input = (Fl_Input*)data;
    Fl_Output *output = (Fl_Output*)w->parent()->child(1);
    output->value(input->value());
}

int main() {
    Fl_Window *win = new Fl_Window(400, 200);
    
    Fl_Input *input = new Fl_Input(50, 30, 300, 30, "Entrada:");
    Fl_Output *output = new Fl_Output(50, 80, 300, 30, "Salida:");
    Fl_Button *btn = new Fl_Button(150, 130, 100, 40, "Copiar");
    
    btn->callback(copy_cb, input);
    
    win->end();
    win->show();
    return Fl::run();
}

4. Manejo de Eventos

4.1 Callbacks (Funciones de retorno)

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Button.H>

void button_callback(Fl_Widget *widget, void *data) {
    printf("Botón presionado. Datos: %s\n", (char*)data);
}

int main() {
    Fl_Window *win = new Fl_Window(300, 200);
    
    Fl_Button *btn = new Fl_Button(100, 80, 100, 40, "Presionar");
    btn->callback(button_callback, (void*)"Datos personalizados");
    
    win->end();
    win->show();
    return Fl::run();
}

4.2 Manejo de eventos personalizados

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>

class MyBox : public Fl_Box {
public:
    MyBox(int x, int y, int w, int h, const char *l=0) : Fl_Box(x,y,w,h,l) {
        box(FL_UP_BOX);
        color(FL_WHITE);
    }
    
    int handle(int event) override {
        switch(event) {
            case FL_PUSH:
                color(FL_RED);
                redraw();
                return 1;
            case FL_RELEASE:
                color(FL_WHITE);
                redraw();
                return 1;
            case FL_ENTER:
                color(FL_GREEN);
                redraw();
                return 1;
            case FL_LEAVE:
                color(FL_WHITE);
                redraw();
                return 1;
        }
        return Fl_Box::handle(event);
    }
};

int main() {
    Fl_Window *win = new Fl_Window(400, 300);
    MyBox *box = new MyBox(100, 100, 200, 100, "Prueba eventos");
    box->align(FL_ALIGN_CENTER);
    
    win->end();
    win->show();
    return Fl::run();
}

4.3 Tipos de eventos comunes

Evento Descripción
FL_PUSH Botón del ratón presionado
FL_RELEASE Botón del ratón liberado
FL_DRAG Ratón movido con botón presionado
FL_MOVE Ratón movido
FL_ENTER Ratón entra en el widget
FL_LEAVE Ratón sale del widget
FL_KEYDOWN Tecla presionada
FL_KEYUP Tecla liberada

5. Contenedores y Layouts

5.1 Fl_Group (Agrupación básica)

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Group.H>
#include <FL/Fl_Button.H>

int main() {
    Fl_Window *win = new Fl_Window(400, 300);
    
    // Crear grupo (x, y, ancho, alto)
    Fl_Group *group = new Fl_Group(50, 50, 300, 200);
    group->box(FL_ENGRAVED_BOX);
    group->align(FL_ALIGN_TOP | FL_ALIGN_INSIDE, "Grupo de Controles");
    
    // Añadir widgets al grupo
    Fl_Button *btn1 = new Fl_Button(70, 80, 100, 30, "Botón 1");
    Fl_Button *btn2 = new Fl_Button(70, 120, 100, 30, "Botón 2");
    
    // Finalizar grupo (deja de añadir widgets)
    group->end();
    
    win->end();
    win->show();
    return Fl::run();
}

5.2 Fl_Pack (Organización automática)

#include <FL/Fl_Pack.H>

int main() {
    Fl_Window *win = new Fl_Window(300, 400);
    
    // Pack vertical (también puede ser horizontal con FL_HORIZONTAL)
    Fl_Pack *pack = new Fl_Pack(50, 50, 200, 300);
    pack->type(FL_VERTICAL);
    pack->spacing(10); // Espacio entre widgets
    
    // Añadir widgets (se organizan automáticamente)
    new Fl_Button(0, 0, 0, 40, "Botón 1"); // x,y se ignoran
    new Fl_Button(0, 0, 0, 40, "Botón 2");
    new Fl_Button(0, 0, 0, 40, "Botón 3");
    
    pack->end();
    win->end();
    win->show();
    return Fl::run();
}

5.3 Fl_Tabs (Pestañas)

#include <FL/Fl_Tabs.H>
#include <FL/Fl_Group.H>
#include <FL/Fl_Input.H>

int main() {
    Fl_Window *win = new Fl_Window(400, 300);
    
    Fl_Tabs *tabs = new Fl_Tabs(10, 10, 380, 280);
    
    // Pestaña 1
    Fl_Group *tab1 = new Fl_Group(10, 40, 380, 250, "Configuración");
    Fl_Input *input1 = new Fl_Input(50, 70, 200, 30, "Nombre:");
    Fl_Input *input2 = new Fl_Input(50, 110, 200, 30, "Email:");
    tab1->end();
    
    // Pestaña 2
    Fl_Group *tab2 = new Fl_Group(10, 40, 380, 250, "Avanzado");
    // ... widgets para pestaña 2 ...
    tab2->end();
    
    tabs->end();
    win->end();
    win->show();
    return Fl::run();
}

5.4 Fl_Scroll (Área desplazable)

#include <FL/Fl_Scroll.H>

int main() {
    Fl_Window *win = new Fl_Window(400, 300);
    
    Fl_Scroll *scroll = new Fl_Scroll(10, 10, 380, 280);
    scroll->type(Fl_Scroll::VERTICAL_ALWAYS);
    
    // Añadir widgets grandes (más grandes que el área visible)
    for (int i = 0; i < 20; i++) {
        new Fl_Button(20, 20 + i*40, 200, 30, 
                     ("Botón " + std::to_string(i+1)).c_str());
    }
    
    scroll->end();
    win->end();
    win->show();
    return Fl::run();
}

6. Widgets Avanzados

6.1 Fl_Text_Editor (Editor de texto)

#include <FL/Fl_Text_Editor.H>
#include <FL/Fl_Text_Buffer.H>

int main() {
    Fl_Window *win = new Fl_Window(600, 400, "Editor de Texto");
    
    // Crear buffer de texto
    Fl_Text_Buffer *buff = new Fl_Text_Buffer();
    
    // Crear editor
    Fl_Text_Editor *editor = new Fl_Text_Editor(10, 10, 580, 380);
    editor->buffer(buff); // Asignar buffer
    
    // Configurar estilo
    editor->textfont(FL_COURIER);
    editor->textsize(14);
    
    // Insertar texto inicial
    buff->text("Escribe aquí tu texto...\n");
    
    win->end();
    win->show();
    return Fl::run();
}

6.2 Fl_Browser (Lista de elementos)

#include <FL/Fl_Browser.H>

void browser_cb(Fl_Widget *w, void *data) {
    Fl_Browser *b = (Fl_Browser*)w;
    printf("Elemento seleccionado: %s\n", b->text(b->value()));
}

int main() {
    Fl_Window *win = new Fl_Window(300, 400);
    
    Fl_Browser *browser = new Fl_Browser(10, 10, 280, 380);
    browser->type(FL_HOLD_BROWSER); // Selección simple
    browser->callback(browser_cb);
    
    // Añadir elementos
    browser->add("Elemento 1");
    browser->add("Elemento 2");
    browser->add("Elemento 3");
    // ... más elementos ...
    
    win->end();
    win->show();
    return Fl::run();
}

6.3 Fl_Table (Tabla)

#include <FL/Fl_Table.H>
#include <FL/fl_draw.H>

class MyTable : public Fl_Table {
    std::vector<std::vector<std::string>> data;
    
    // Dibujar celda
    void draw_cell(TableContext context, int R=0, int C=0, 
                  int X=0, int Y=0, int W=0, int H=0) override {
        switch(context) {
            case CONTEXT_STARTPAGE:
                fl_font(FL_HELVETICA, 12);
                return;
                
            case CONTEXT_COL_HEADER:
                fl_push_clip(X, Y, W, H);
                fl_draw_box(FL_THIN_UP_BOX, X, Y, W, H, color());
                fl_color(FL_BLACK);
                fl_draw(("Col " + std::to_string(C+1)).c_str(), X, Y, W, H, FL_ALIGN_CENTER);
                fl_pop_clip();
                return;
                
            case CONTEXT_ROW_HEADER:
                fl_push_clip(X, Y, W, H);
                fl_draw_box(FL_THIN_UP_BOX, X, Y, W, H, color());
                fl_color(FL_BLACK);
                fl_draw(("Row " + std::to_string(R+1)).c_str(), X, Y, W, H, FL_ALIGN_CENTER);
                fl_pop_clip();
                return;
                
            case CONTEXT_CELL:
                fl_push_clip(X, Y, W, H);
                // Color de fondo
                fl_color(row_selected(R) ? selection_color() : FL_WHITE);
                fl_rectf(X, Y, W, H);
                
                // Texto
                fl_color(FL_BLACK);
                if (R < data.size() && C < data[R].size()) {
                    fl_draw(data[R][C].c_str(), X, Y, W, H, FL_ALIGN_CENTER);
                }
                
                fl_pop_clip();
                return;
        }
    }
    
public:
    MyTable(int x, int y, int w, int h, const char *l=0) : Fl_Table(x, y, w, h, l) {
        // Configurar tabla
        rows(10);
        cols(5);
        row_header(1);
        col_header(1);
        row_height_all(25);
        col_width_all(80);
        
        // Datos de ejemplo
        for (int r = 0; r < 10; r++) {
            data.push_back(std::vector<std::string>());
            for (int c = 0; c < 5; c++) {
                data[r].push_back("R" + std::to_string(r+1) + "C" + std::to_string(c+1));
            }
        }
    }
};

int main() {
    Fl_Window *win = new Fl_Window(500, 400, "Tabla FLTK");
    MyTable *table = new MyTable(10, 10, 480, 380);
    win->end();
    win->show();
    return Fl::run();
}

7. Dibujo Personalizado

7.1 Fl_Widget personalizado

#include <FL/fl_draw.H>

class CircleWidget : public Fl_Widget {
    int radius;
    Fl_Color circle_color;
    
public:
    CircleWidget(int x, int y, int r, Fl_Color c) 
        : Fl_Widget(x-r, y-r, 2*r, 2*r), radius(r), circle_color(c) {}
    
    void draw() override {
        fl_color(circle_color);
        fl_pie(x(), y(), w(), h(), 0, 360);
        fl_color(FL_BLACK);
        fl_arc(x(), y(), w(), h(), 0, 360);
    }
};

int main() {
    Fl_Window *win = new Fl_Window(400, 400);
    CircleWidget *circle = new CircleWidget(200, 200, 100, FL_RED);
    win->end();
    win->show();
    return Fl::run();
}

7.2 Funciones básicas de dibujo

Función Descripción
fl_color() Establece color de dibujo
fl_line() Dibuja una línea
fl_rect() Dibuja un rectángulo
fl_rectf() Dibuja rectángulo relleno
fl_circle() Dibuja un círculo
fl_pie() Dibuja un sector circular
fl_font() Establece fuente y tamaño
fl_draw() Dibuja texto

7.3 Doble buffer para animación

#include <FL/Fl_Double_Window.H>
#include <FL/fl_draw.H>

class AnimWidget : public Fl_Widget {
    int x_pos;
    
public:
    AnimWidget(int x, int y, int w, int h) : Fl_Widget(x, y, w, h), x_pos(0) {}
    
    void draw() override {
        // Usar doble buffer
        fl_push_clip(x(), y(), w(), h());
        
        // Limpiar área
        fl_color(FL_WHITE);
        fl_rectf(x(), y(), w(), h());
        
        // Dibujar objeto animado
        fl_color(FL_RED);
        fl_pie(x() + x_pos, y() + h()/2 - 20, 40, 40, 0, 360);
        
        fl_pop_clip();
    }
    
    void update() {
        x_pos = (x_pos + 2) % w();
        redraw();
        Fl::repeat_timeout(0.05, timer_cb, this);
    }
    
    static void timer_cb(void *data) {
        ((AnimWidget*)data)->update();
    }
};

int main() {
    Fl_Double_Window *win = new Fl_Double_Window(400, 200, "Animación");
    AnimWidget *anim = new AnimWidget(0, 0, 400, 200);
    
    win->end();
    win->show();
    
    // Iniciar animación
    Fl::add_timeout(0.05, AnimWidget::timer_cb, anim);
    
    return Fl::run();
}

8. Temas y Estilos

8.1 Esquemas de colores

#include <FL/Fl_Scheme_Choice.H>

int main() {
    Fl_Window *win = new Fl_Window(400, 300);
    
    // Cambiar esquema de colores
    Fl::scheme("gtk+");  // También: "gleam", "plastic", etc.
    
    Fl_Box *box = new Fl_Box(100, 100, 200, 100, "Esquema GTK+");
    box->box(FL_UP_BOX);
    box->color(FL_BACKGROUND2_COLOR);
    box->labelfont(FL_BOLD);
    
    win->end();
    win->show();
    return Fl::run();
}

8.2 Tipos de caja (box types)

Tipo Descripción
FL_NO_BOX Sin borde ni fondo
FL_UP_BOX Caja con relieve hacia arriba
FL_DOWN_BOX Caja con relieve hacia abajo
FL_BORDER_BOX Borde simple
FL_SHADOW_BOX Caja con sombra
FL_ROUNDED_BOX Caja con bordes redondeados

8.3 Fuentes y estilos de texto

Fl_Box *box = new Fl_Box(100, 100, 200, 30, "Texto de ejemplo");

// Establecer fuente
box->labelfont(FL_HELVETICA); // FL_COURIER, FL_TIMES, etc.

// Establecer tamaño
box->labelsize(16);

// Establecer estilo
box->labeltype(FL_SHADOW_LABEL); // FL_NORMAL_LABEL, FL_ENGRAVED_LABEL, etc.

// Color de texto
box->labelcolor(FL_RED);

9. Aplicaciones Completas

9.1 Editor de texto básico

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/Fl_Text_Editor.H>
#include <FL/Fl_Text_Buffer.H>
#include <FL/Fl_Native_File_Chooser.H>

class TextEditor {
    Fl_Window *win;
    Fl_Text_Editor *editor;
    Fl_Text_Buffer *buff;
    std::string current_file;
    
    void new_file() {
        buff->text("");
        current_file.clear();
        win->label("Editor de Texto");
    }
    
    void open_file() {
        Fl_Native_File_Chooser chooser(Fl_Native_File_Chooser::BROWSE_FILE);
        if (chooser.show() == 0) {
            current_file = chooser.filename();
            buff->loadfile(current_file.c_str());
            win->label(("Editor: " + current_file).c_str());
        }
    }
    
    void save_file() {
        if (current_file.empty()) {
            save_file_as();
        } else {
            buff->savefile(current_file.c_str());
        }
    }
    
    void save_file_as() {
        Fl_Native_File_Chooser chooser(Fl_Native_File_Chooser::BROWSE_SAVE_FILE);
        if (chooser.show() == 0) {
            current_file = chooser.filename();
            buff->savefile(current_file.c_str());
            win->label(("Editor: " + current_file).c_str());
        }
    }
    
    static void menu_cb(Fl_Widget *w, void *data) {
        TextEditor *te = (TextEditor*)data;
        const Fl_Menu_Item *item = ((Fl_Menu_Bar*)w)->mvalue();
        
        if (strcmp(item->label(), "&Nuevo") == 0) te->new_file();
        else if (strcmp(item->label(), "&Abrir") == 0) te->open_file();
        else if (strcmp(item->label(), "&Guardar") == 0) te->save_file();
        else if (strcmp(item->label(), "Guardar &como") == 0) te->save_file_as();
        else if (strcmp(item->label(), "&Salir") == 0) te->win->hide();
    }
    
public:
    TextEditor(int w, int h) {
        win = new Fl_Window(w, h, "Editor de Texto");
        
        // Crear barra de menú
        Fl_Menu_Bar *menu = new Fl_Menu_Bar(0, 0, w, 25);
        menu->add("&Archivo/Nuevo", FL_COMMAND+'n', menu_cb, this);
        menu->add("&Archivo/Abrir", FL_COMMAND+'o', menu_cb, this);
        menu->add("&Archivo/Guardar", FL_COMMAND+'s', menu_cb, this);
        menu->add("&Archivo/Guardar como", FL_COMMAND+FL_SHIFT+'s', menu_cb, this);
        menu->add("&Archivo/Salir", FL_COMMAND+'q', menu_cb, this);
        
        // Crear editor
        buff = new Fl_Text_Buffer();
        editor = new Fl_Text_Editor(0, 25, w, h-25);
        editor->buffer(buff);
        editor->textfont(FL_COURIER);
        editor->textsize(14);
        
        win->end();
        win->resizable(editor);
        win->show();
    }
    
    int run() { return Fl::run(); }
};

int main() {
    TextEditor editor(800, 600);
    return editor.run();
}

9.2 Calculadora básica

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Output.H>
#include <sstream>

class Calculator {
    Fl_Window *win;
    Fl_Output *output;
    std::string current_input;
    double current_value;
    char last_operation;
    
    void update_display() {
        output->value(current_input.empty() ? "0" : current_input.c_str());
    }
    
    void button_cb(Fl_Widget *w, void *data) {
        const char *label = ((Fl_Button*)w)->label();
        
        if (isdigit(label[0]) || label[0] == '.') {
            current_input += label;
            update_display();
        }
        else if (strchr("+-*/", label[0])) {
            if (!current_input.empty()) {
                double num = std::stod(current_input);
                perform_calculation(num);
                current_input.clear();
            }
            last_operation = label[0];
        }
        else if (strcmp(label, "=") == 0) {
            if (!current_input.empty() && last_operation) {
                double num = std::stod(current_input);
                perform_calculation(num);
                current_input = std::to_string(current_value);
                update_display();
                current_input.clear();
                last_operation = 0;
            }
        }
        else if (strcmp(label, "C") == 0) {
            current_input.clear();
            current_value = 0;
            last_operation = 0;
            update_display();
        }
    }
    
    void perform_calculation(double num) {
        switch(last_operation) {
            case '+': current_value += num; break;
            case '-': current_value -= num; break;
            case '*': current_value *= num; break;
            case '/': current_value /= num; break;
            default: current_value = num;
        }
    }
    
public:
    Calculator() : current_value(0), last_operation(0) {
        win = new Fl_Window(300, 400, "Calculadora");
        
        output = new Fl_Output(10, 10, 280, 50);
        output->textsize(24);
        
        const char *buttons[16] = {
            "7", "8", "9", "/",
            "4", "5", "6", "*",
            "1", "2", "3", "-",
            "0", ".", "=", "+"
        };
        
        for (int i = 0; i < 16; i++) {
            int x = 10 + (i%4)*70;
            int y = 70 + (i/4)*70;
            Fl_Button *btn = new Fl_Button(x, y, 65, 65, buttons[i]);
            btn->callback(button_cb, this);
            btn->labelsize(18);
        }
        
        Fl_Button *clear = new Fl_Button(10, 350, 280, 40, "C");
        clear->callback(button_cb, this);
        clear->labelsize(18);
        
        win->end();
        win->show();
    }
    
    int run() { return Fl::run(); }
};

int main() {
    Calculator calc;
    return calc.run();
}

10. Recursos y Referencias

10.1 Documentación oficial

10.2 Libros recomendados

10.3 Comunidades