FLTK
Fast Light Tool Kitの略。非常に軽量で高速なクロスプラットフォームGUIライブラリ。最小限の依存関係で小さなバイナリサイズを実現。組み込みシステムやリソース制約のある環境に最適。
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
トピックス
なし
スター履歴
データ取得日時: 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();
}