FLTK

Fast Light Tool Kitの略。非常に軽量で高速なクロスプラットフォームGUIライブラリ。最小限の依存関係で小さなバイナリサイズを実現。組み込みシステムやリソース制約のある環境に最適。

デスクトップC++軽量クロスプラットフォームGUIOpenGL

GitHub概要

fltk/fltk

FLTK - Fast Light Tool Kit - https://github.com/fltk/fltk - cross platform GUI development

ホームページ:https://www.fltk.org
スター1,935
ウォッチ52
フォーク310
作成日:2018年9月18日
言語:C++
ライセンス:Other

トピックス

なし

スター履歴

fltk/fltk Star History
データ取得日時: 2025/7/15 22:55

デスクトップフレームワーク

FLTK (Fast Light Toolkit)

概要

FLTKは軽量で高速なクロスプラットフォームC++ GUIツールキットです。Unix/Linux (X11およびWayland)、Microsoft Windows、macOSで動作し、非常に小さなメモリフットプリント(Hello Worldプログラムが約100KB)で3D グラフィックス(OpenGL)もサポートします。

詳細

FLTKは「最小限」の設計思想を採用し、GUIの機能に焦点を絞った軽量ライブラリです。GTK、Qt、wxWidgetsなどの他のUIライブラリとは対照的に、ユーザーインターフェース機能のみに特化しており、その結果として非常に小さなファイルサイズを実現しています。

特徴

  • 軽量設計: 最小限のメモリフットプリント(Hello Worldで約100KB)
  • クロスプラットフォーム: Unix/Linux (X11、Wayland)、Windows、macOS対応
  • OpenGL統合: 内蔵GLUTエミュレーションによる3Dグラフィックス
  • FLUID: グラフィカルGUIデザイナーツールが付属
  • 静的リンク: 通常は静的リンクで単体実行ファイルを生成
  • 多言語バインディング: Lua、Perl、Python、Ruby、Rust、Tclに対応

アーキテクチャ

  • Widget階層: すべてのUI要素はFlクラスから派生
  • イベント駆動: コールバック機能とイベント処理
  • 描画システム: 高速な2D描画とOpenGL統合
  • レイアウト管理: Group、Pack、Tile等の自動レイアウト
  • 現代的対応: Wayland バックエンド(1.4.0以降)

最新バージョン

  • 安定版: 1.4.3 (メンテナンスモード)
  • 開発版: 1.5.x-20250620-acd77fa8dc41 (週間スナップショット)
  • Wayland対応: 1.4.0以降で実装

メリット・デメリット

メリット

  • 軽量性: 非常に小さなライブラリサイズとメモリフットプリント
  • 高速性: 優れたレスポンス性能と描画速度
  • OpenGLサポート: 標準で3Dグラフィックス描画に対応
  • 簡単な学習: シンプルで理解しやすいAPI設計
  • 組み込み対応: リソース制約のある環境でも動作
  • GUIデザイナー: FLUID(GUIビルダー)によるビジュアル開発

デメリット

  • 古いデザイン: モダンなルックアンドフィールの実現が困難
  • 限定的なウィジェット: 他の大規模フレームワークと比べて機能が少ない
  • ドキュメント不足: 情報が限定的で学習リソースが少ない
  • コミュニティの小ささ: サポートやプラグインが限定的
  • テーマサポート: カスタマイゼーション性が低い
  • 高DPI対応: 高解像度ディスプレイサポートが限定的

参考ページ

書き方の例

Hello World

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

int main(int argc, char **argv) {
    // ウィンドウの作成
    Fl_Window *window = new Fl_Window(400, 300, "Hello World");
    
    // テキストボックスの作成
    Fl_Box *box = new Fl_Box(20, 40, 360, 100, "Hello, FLTK World!");
    box->box(FL_UP_BOX);
    box->labelfont(FL_BOLD + FL_ITALIC);
    box->labelsize(36);
    box->labeltype(FL_SHADOW_LABEL);
    
    // ウィンドウの表示
    window->end();
    window->show(argc, argv);
    
    // イベントループ
    return Fl::run();
}

ボタンとコールバック

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

// グローバル変数
Fl_Box* output_box;
int click_count = 0;

// ボタンコールバック関数
void button_cb(Fl_Widget* w, void* data) {
    click_count++;
    
    // メッセージの更新
    static char message[100];
    sprintf(message, "ボタンが %d 回クリックされました", click_count);
    output_box->label(message);
    output_box->redraw();
    
    // 10回クリックでメッセージボックス表示
    if (click_count % 10 == 0) {
        fl_message("10回の倍数でクリックされました!");
    }
}

void reset_cb(Fl_Widget* w, void* data) {
    click_count = 0;
    output_box->label("リセットされました");
    output_box->redraw();
}

void quit_cb(Fl_Widget* w, void* data) {
    if (fl_choice("本当に終了しますか?", "キャンセル", "終了", NULL)) {
        exit(0);
    }
}

int main(int argc, char **argv) {
    // メインウィンドウ
    Fl_Window* window = new Fl_Window(400, 200, "ボタンサンプル");
    
    // 出力用ボックス
    output_box = new Fl_Box(20, 20, 360, 50, "ボタンをクリックしてください");
    output_box->box(FL_DOWN_BOX);
    output_box->labelfont(FL_BOLD);
    output_box->labelsize(14);
    
    // クリックボタン
    Fl_Button* click_button = new Fl_Button(50, 90, 100, 35, "クリック");
    click_button->callback(button_cb);
    
    // リセットボタン
    Fl_Button* reset_button = new Fl_Button(160, 90, 100, 35, "リセット");
    reset_button->callback(reset_cb);
    
    // 終了ボタン
    Fl_Button* quit_button = new Fl_Button(270, 90, 100, 35, "終了");
    quit_button->callback(quit_cb);
    
    // ウィンドウ設定
    window->end();
    window->show(argc, argv);
    
    return Fl::run();
}

入力フォームとバリデーション

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Choice.H>
#include <FL/Fl_Check_Button.H>
#include <FL/Fl_Text_Display.H>
#include <FL/fl_ask.H>
#include <string>
#include <sstream>

class FormWindow {
public:
    Fl_Window* window;
    Fl_Input* name_input;
    Fl_Input* age_input;
    Fl_Choice* gender_choice;
    Fl_Check_Button* newsletter_check;
    Fl_Text_Display* result_display;
    Fl_Text_Buffer* text_buffer;
    
    FormWindow() {
        // メインウィンドウ
        window = new Fl_Window(500, 400, "入力フォーム");
        
        // 名前入力
        new Fl_Box(20, 20, 80, 25, "名前:");
        name_input = new Fl_Input(110, 20, 200, 25);
        name_input->value("田中太郎");
        
        // 年齢入力
        new Fl_Box(20, 55, 80, 25, "年齢:");
        age_input = new Fl_Input(110, 55, 100, 25);
        age_input->value("30");
        
        // 性別選択
        new Fl_Box(20, 90, 80, 25, "性別:");
        gender_choice = new Fl_Choice(110, 90, 120, 25);
        gender_choice->add("男性");
        gender_choice->add("女性");
        gender_choice->add("その他");
        gender_choice->value(0);
        
        // ニュースレター購読
        newsletter_check = new Fl_Check_Button(20, 125, 200, 25, "ニュースレターを購読する");
        newsletter_check->value(1);
        
        // ボタン
        Fl_Button* submit_button = new Fl_Button(20, 160, 100, 30, "送信");
        submit_button->callback(submit_cb, this);
        
        Fl_Button* clear_button = new Fl_Button(130, 160, 100, 30, "クリア");
        clear_button->callback(clear_cb, this);
        
        // 結果表示エリア
        new Fl_Box(20, 200, 100, 25, "結果:");
        text_buffer = new Fl_Text_Buffer();
        result_display = new Fl_Text_Display(20, 225, 460, 150);
        result_display->buffer(text_buffer);
        result_display->wrap_mode(1, 80);
        
        window->end();
    }
    
    // 送信ボタンコールバック
    static void submit_cb(Fl_Widget* w, void* data) {
        FormWindow* form = (FormWindow*)data;
        form->submit();
    }
    
    // クリアボタンコールバック
    static void clear_cb(Fl_Widget* w, void* data) {
        FormWindow* form = (FormWindow*)data;
        form->clear();
    }
    
    void submit() {
        // バリデーション
        std::string name = name_input->value();
        std::string age_str = age_input->value();
        
        if (name.empty()) {
            fl_alert("名前を入力してください");
            return;
        }
        
        int age;
        try {
            age = std::stoi(age_str);
            if (age < 0 || age > 150) {
                fl_alert("年齢は0歳から150歳の間で入力してください");
                return;
            }
        } catch (const std::exception& e) {
            fl_alert("有効な年齢を入力してください");
            return;
        }
        
        // 結果の生成
        std::stringstream result;
        result << "=== 入力内容 ===\n";
        result << "名前: " << name << "\n";
        result << "年齢: " << age << "歳\n";
        result << "性別: " << gender_choice->text() << "\n";
        result << "ニュースレター: " << (newsletter_check->value() ? "購読する" : "購読しない") << "\n";
        result << "\n登録が完了しました!";
        
        text_buffer->text(result.str().c_str());
    }
    
    void clear() {
        name_input->value("");
        age_input->value("");
        gender_choice->value(0);
        newsletter_check->value(0);
        text_buffer->text("");
    }
    
    void show() {
        window->show();
    }
};

int main(int argc, char **argv) {
    FormWindow form;
    form.show();
    return Fl::run();
}

OpenGLを使った3Dグラフィックス

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Gl_Window.H>
#include <FL/Fl_Button.H>
#include <FL/gl.h>
#include <GL/glu.h>
#include <cmath>

class MyGLWindow : public Fl_Gl_Window {
private:
    float rotation_x, rotation_y;
    float zoom;
    
public:
    MyGLWindow(int x, int y, int w, int h, const char* l = 0)
        : Fl_Gl_Window(x, y, w, h, l), rotation_x(0), rotation_y(0), zoom(1.0f) {
        mode(FL_RGB | FL_DOUBLE | FL_DEPTH);
    }
    
    void draw() override {
        if (!valid()) {
            // OpenGLの初期設定
            glViewport(0, 0, w(), h());
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();
            gluPerspective(45.0, (float)w()/(float)h(), 0.1, 100.0);
            glMatrixMode(GL_MODELVIEW);
            
            glEnable(GL_DEPTH_TEST);
            glEnable(GL_LIGHTING);
            glEnable(GL_LIGHT0);
            
            // ライトの設定
            GLfloat light_position[] = {1.0, 1.0, 1.0, 0.0};
            GLfloat light_ambient[] = {0.2f, 0.2f, 0.2f, 1.0f};
            GLfloat light_diffuse[] = {0.8f, 0.8f, 0.8f, 1.0f};
            
            glLightfv(GL_LIGHT0, GL_POSITION, light_position);
            glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
            glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
        }
        
        // 画面クリア
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glLoadIdentity();
        
        // カメラ位置
        glTranslatef(0.0f, 0.0f, -5.0f * zoom);
        glRotatef(rotation_x, 1.0f, 0.0f, 0.0f);
        glRotatef(rotation_y, 0.0f, 1.0f, 0.0f);
        
        // 立方体の描画
        drawCube();
        
        // 球体の描画
        glPushMatrix();
        glTranslatef(2.0f, 0.0f, 0.0f);
        GLfloat red_material[] = {1.0f, 0.0f, 0.0f, 1.0f};
        glMaterialfv(GL_FRONT, GL_DIFFUSE, red_material);
        
        GLUquadric* quad = gluNewQuadric();
        gluSphere(quad, 0.5, 20, 20);
        gluDeleteQuadric(quad);
        glPopMatrix();
    }
    
    void drawCube() {
        GLfloat blue_material[] = {0.0f, 0.0f, 1.0f, 1.0f};
        glMaterialfv(GL_FRONT, GL_DIFFUSE, blue_material);
        
        glBegin(GL_QUADS);
        // 前面
        glNormal3f(0.0f, 0.0f, 1.0f);
        glVertex3f(-0.5f, -0.5f,  0.5f);
        glVertex3f( 0.5f, -0.5f,  0.5f);
        glVertex3f( 0.5f,  0.5f,  0.5f);
        glVertex3f(-0.5f,  0.5f,  0.5f);
        
        // 背面
        glNormal3f(0.0f, 0.0f, -1.0f);
        glVertex3f(-0.5f, -0.5f, -0.5f);
        glVertex3f(-0.5f,  0.5f, -0.5f);
        glVertex3f( 0.5f,  0.5f, -0.5f);
        glVertex3f( 0.5f, -0.5f, -0.5f);
        
        // 上面
        glNormal3f(0.0f, 1.0f, 0.0f);
        glVertex3f(-0.5f,  0.5f, -0.5f);
        glVertex3f(-0.5f,  0.5f,  0.5f);
        glVertex3f( 0.5f,  0.5f,  0.5f);
        glVertex3f( 0.5f,  0.5f, -0.5f);
        
        // 下面
        glNormal3f(0.0f, -1.0f, 0.0f);
        glVertex3f(-0.5f, -0.5f, -0.5f);
        glVertex3f( 0.5f, -0.5f, -0.5f);
        glVertex3f( 0.5f, -0.5f,  0.5f);
        glVertex3f(-0.5f, -0.5f,  0.5f);
        
        // 右面
        glNormal3f(1.0f, 0.0f, 0.0f);
        glVertex3f( 0.5f, -0.5f, -0.5f);
        glVertex3f( 0.5f,  0.5f, -0.5f);
        glVertex3f( 0.5f,  0.5f,  0.5f);
        glVertex3f( 0.5f, -0.5f,  0.5f);
        
        // 左面
        glNormal3f(-1.0f, 0.0f, 0.0f);
        glVertex3f(-0.5f, -0.5f, -0.5f);
        glVertex3f(-0.5f, -0.5f,  0.5f);
        glVertex3f(-0.5f,  0.5f,  0.5f);
        glVertex3f(-0.5f,  0.5f, -0.5f);
        glEnd();
    }
    
    int handle(int event) override {
        static int last_x, last_y;
        
        switch(event) {
        case FL_PUSH:
            last_x = Fl::event_x();
            last_y = Fl::event_y();
            return 1;
            
        case FL_DRAG:
            rotation_y += (Fl::event_x() - last_x) * 0.5f;
            rotation_x += (Fl::event_y() - last_y) * 0.5f;
            last_x = Fl::event_x();
            last_y = Fl::event_y();
            redraw();
            return 1;
            
        case FL_MOUSEWHEEL:
            zoom += Fl::event_dy() * 0.1f;
            if (zoom < 0.1f) zoom = 0.1f;
            if (zoom > 5.0f) zoom = 5.0f;
            redraw();
            return 1;
        }
        
        return Fl_Gl_Window::handle(event);
    }
};

// リセットボタンコールバック
void reset_cb(Fl_Widget* w, void* data) {
    MyGLWindow* gl_win = (MyGLWindow*)data;
    // リセット処理(パブリックメンバーが必要)
    gl_win->redraw();
}

int main(int argc, char **argv) {
    Fl_Window* window = new Fl_Window(600, 500, "FLTK OpenGLサンプル");
    
    // OpenGLウィンドウ
    MyGLWindow* gl_window = new MyGLWindow(10, 10, 580, 440);
    
    // リセットボタン
    Fl_Button* reset_button = new Fl_Button(10, 460, 100, 30, "リセット");
    reset_button->callback(reset_cb, gl_window);
    
    window->end();
    window->resizable(gl_window);
    window->show(argc, argv);
    
    return Fl::run();
}

ファイルブラウザ

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_File_Browser.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Text_Display.H>
#include <FL/fl_ask.H>
#include <fstream>
#include <sstream>

class FileBrowserWindow {
public:
    Fl_Window* window;
    Fl_File_Browser* browser;
    Fl_Input* path_input;
    Fl_Text_Display* content_display;
    Fl_Text_Buffer* text_buffer;
    
    FileBrowserWindow() {
        window = new Fl_Window(800, 600, "ファイルブラウザ");
        
        // パス入力
        new Fl_Box(10, 10, 60, 25, "パス:");
        path_input = new Fl_Input(75, 10, 600, 25);
        path_input->value("./");
        path_input->callback(path_cb, this);
        
        Fl_Button* browse_button = new Fl_Button(680, 10, 80, 25, "移動");
        browse_button->callback(browse_cb, this);
        
        // ファイルブラウザ
        browser = new Fl_File_Browser(10, 45, 380, 450);
        browser->type(FL_HOLD_BROWSER);
        browser->callback(file_select_cb, this);
        
        // コンテンツ表示
        new Fl_Box(400, 45, 100, 25, "ファイル内容:");
        text_buffer = new Fl_Text_Buffer();
        content_display = new Fl_Text_Display(400, 70, 390, 425);
        content_display->buffer(text_buffer);
        content_display->wrap_mode(1, 80);
        
        // 操作ボタン
        Fl_Button* refresh_button = new Fl_Button(10, 505, 80, 30, "更新");
        refresh_button->callback(refresh_cb, this);
        
        Fl_Button* parent_button = new Fl_Button(100, 505, 80, 30, "上へ");
        parent_button->callback(parent_cb, this);
        
        Fl_Button* home_button = new Fl_Button(190, 505, 80, 30, "ホーム");
        home_button->callback(home_cb, this);
        
        // 初期表示
        loadDirectory("./");
        
        window->end();
        window->resizable(browser);
    }
    
    void loadDirectory(const char* path) {
        browser->load(path);
        path_input->value(path);
    }
    
    void loadFileContent(const char* filename) {
        std::ifstream file(filename);
        if (!file.is_open()) {
            text_buffer->text("ファイルを開けませんでした");
            return;
        }
        
        std::stringstream buffer;
        buffer << file.rdbuf();
        std::string content = buffer.str();
        
        // テキストファイルかどうか簡単にチェック
        bool is_text = true;
        for (char c : content) {
            if (c == 0 || (c < 32 && c != '\t' && c != '\n' && c != '\r')) {
                is_text = false;
                break;
            }
        }
        
        if (is_text && content.size() < 100000) {  // 100KB以下のテキストファイル
            text_buffer->text(content.c_str());
        } else {
            std::stringstream info;
            info << "ファイル情報:\n";
            info << "サイズ: " << content.size() << " bytes\n";
            info << "タイプ: " << (is_text ? "テキスト(大きすぎます)" : "バイナリ") << "\n";
            text_buffer->text(info.str().c_str());
        }
    }
    
    // コールバック関数
    static void path_cb(Fl_Widget* w, void* data) {
        FileBrowserWindow* fb = (FileBrowserWindow*)data;
        fb->loadDirectory(fb->path_input->value());
    }
    
    static void browse_cb(Fl_Widget* w, void* data) {
        FileBrowserWindow* fb = (FileBrowserWindow*)data;
        fb->loadDirectory(fb->path_input->value());
    }
    
    static void file_select_cb(Fl_Widget* w, void* data) {
        FileBrowserWindow* fb = (FileBrowserWindow*)data;
        
        int selected = fb->browser->value();
        if (selected == 0) return;
        
        const char* filename = fb->browser->text(selected);
        if (!filename) return;
        
        // フルパスの構築
        std::string full_path = std::string(fb->path_input->value()) + "/" + filename;
        
        // ディレクトリの場合は移動
        if (filename[strlen(filename)-1] == '/') {
            fb->loadDirectory(full_path.c_str());
        } else {
            // ファイルの場合は内容を表示
            fb->loadFileContent(full_path.c_str());
        }
    }
    
    static void refresh_cb(Fl_Widget* w, void* data) {
        FileBrowserWindow* fb = (FileBrowserWindow*)data;
        fb->loadDirectory(fb->path_input->value());
    }
    
    static void parent_cb(Fl_Widget* w, void* data) {
        FileBrowserWindow* fb = (FileBrowserWindow*)data;
        std::string current_path = fb->path_input->value();
        
        // 親ディレクトリのパスを構築
        size_t pos = current_path.find_last_of("/\\");
        if (pos != std::string::npos && pos > 0) {
            std::string parent_path = current_path.substr(0, pos);
            fb->loadDirectory(parent_path.c_str());
        }
    }
    
    static void home_cb(Fl_Widget* w, void* data) {
        FileBrowserWindow* fb = (FileBrowserWindow*)data;
        fb->loadDirectory(getenv("HOME") ? getenv("HOME") : "./");
    }
    
    void show() {
        window->show();
    }
};

int main(int argc, char **argv) {
    FileBrowserWindow browser;
    browser.show();
    return Fl::run();
}