FLTK 由 Bill Spitzak 個人開始開發並且目前仍然是專案的領導者, 是一個跨平台的 C++ GUI toolkit,用在 UNIX/Linux、微軟 Windows 和 Mac OS X 上, 使用 OpenGL 相關的功能提供 3D 繪圖的支援,並且有一個簡單的界面設計工具 FLUID。 FLTK 被設計足夠小和模組化以被靜態連結,但作為共享庫也工作良好。FLTK 的授權使用 LGPL v2,不過允許使用靜態連結。
一般而言,跨平台 GUI toolkit 有以下二個主要的策略:
- 使用一組共同的 API 包裝各平台提供的底層函式庫或者是工具箱,如果該平台沒有提供相關的視窗元件 (widget) 再自行撰寫。 使用此策略著名的 GUI toolkit 為 wxWidgets。
- 使用各個平台的繪圖功能繪製出最基本的視窗,再由最基本的視窗建構各種視窗元件。 使用此策略著名的 GUI toolkit 為 GTK 與 Qt。
FLTK 的跨平台策略為使用各個平台的繪圖功能繪製出最基本的視窗,再由最基本的視窗建構各種視窗元件。 這個策略的優點是移植到不同的平台較為容易,缺點是缺少原生的外觀和感覺 (native look and feel),\ 需要使用佈景主題 (theme) 之類的方法改進視覺效果。
FLTK 是一個輕量化的函式庫,適合撰寫一些小工具或者是提供前端界面,不過雖然提供了大多數常見的視窗元件, 但是如果是很複雜的視窗元件則未必有提供(有可能需要自行撰寫),如果要撰寫極為複雜的圖形使用者介面應用程式,那麼建議使用其它的 GUI toolkit。
從 FLTK 1.4 開始,編譯 FLTK 建議使用的 build system 為 CMake。
所有 FLTK 應用程式都基於事件處理模型。使用者滑鼠移動、按鈕點擊和鍵盤活動等操作都會產生事件, 並發送給應用程式。然後,應用程式可能會忽略事件或回應用戶。
下面就是 FLTK 的 Hello 例子:
#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(340, 180);
Fl_Box *box = new Fl_Box(20, 40, 300, 100, "Hello, 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();
}
在 FLTK 1.3.x,需要宣告 <FL/Fl.H> 才行,FLTK 1.4 則放鬆了這個限制,不過宣告了也不會有問題。
FLTK 提供了 fltk-config 可以用來查詢編譯與連結時需要的參數,下面是在 UNIX/Linux 環境下編譯的指令:
g++ hello.cxx -o hello `fltk-config --cxxflags --ldflags`
如果要使用 CMake 作為 build system,那麼建立一個 CMakeLists.txt,內容如下:
cmake_minimum_required(VERSION 3.15)
project(hello)
set(FLTK_DIR "/usr/local"
CACHE FILEPATH "FLTK installation or build directory")
find_package(FLTK 1.4 CONFIG REQUIRED)
MESSAGE ( STATUS " FLTK_FOUND : " ${FLTK_FOUND} )
add_executable (hello WIN32 MACOSX_BUNDLE hello.cxx)
target_link_libraries(hello PRIVATE fltk::fltk)
注意:如果自行編譯 FLTK,其預設安裝位置為 /usr/local,但是使用者可以設定其安裝位置。 下面是安裝到自己家目錄下子目錄的例子。
cmake .. -DCMAKE_INSTALL_PREFIX=/home/danilo/Programs/fltk
如果改變預設安裝位置,結果 CMake 無法找到 FLTK,可以嘗試設定 CMAKE_PREFIX_PATH。
cmake .. -DCMAKE_PREFIX_PATH=/home/danilo/Programs/fltk
也可以在 CMakeLists.txt 設定:
cmake_minimum_required(VERSION 3.15)
project(hello)
set(CMAKE_PREFIX_PATH "/home/danilo/Programs/fltk")
set(FLTK_DIR "/home/danilo/Programs/fltk"
CACHE FILEPATH "FLTK installation or build directory")
find_package(FLTK 1.4 CONFIG REQUIRED)
MESSAGE ( STATUS " FLTK_FOUND : " ${FLTK_FOUND} )
add_executable (hello WIN32 MACOSX_BUNDLE hello.cxx)
target_link_libraries(hello PRIVATE fltk::fltk)
下面更新的 Hello 例子,加入了設定整體的前景顏色與背景顏色,並且使用 Fl::scheme 設定視窗元件的顯示方案。
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Window.H>
int main(int argc, char **argv) {
uchar r, g, b;
Fl::get_color(FL_BLACK, r, g, b);
Fl::foreground(r, g, b);
Fl::get_color(FL_WHITE, r, g, b);
Fl::background(r, g, b);
Fl::get_color(FL_WHITE, r, g, b);
Fl::background2(r, g, b);
Fl::scheme("gtk+");
Fl_Window *window = new Fl_Window(640, 480);
Fl_Box *box = new Fl_Box(20, 40, 450, 100, "Hello, World!");
box->box(FL_UP_BOX);
box->color(FL_BLACK);
box->labelfont(FL_BOLD + FL_ITALIC);
box->labelsize(36);
box->labeltype(FL_SHADOW_LABEL);
box->labelcolor(FL_RED);
Fl_Box *box2 = new Fl_Box(20, 180, 450, 100, "Fast Light Toolkit");
box2->box(FL_NO_BOX);
box2->labelsize(36);
box2->labeltype(FL_SHADOW_LABEL);
window->end();
window->show(argc, argv);
return Fl::run();
}
FLTK 提供了一些顯示文字的視窗元件,下面是使用 Fl_Text_Display 的例子。
// Fl_Text_Display example. -erco
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Text_Display.H>
Fl_Double_Window *app_win = nullptr;
Fl_Text_Buffer *buff = nullptr;
Fl_Text_Display *disp = nullptr;
void my_build_app_window() {
app_win = new Fl_Double_Window (640, 480, "Display");
buff = new Fl_Text_Buffer();
disp = new Fl_Text_Display(20, 20, 640 - 40, 480 - 40, "Display Text");
disp->buffer(buff);
app_win->resizable(*disp);
buff->text("line 0\nline 1\nline 2\n"
"line 3\nline 4\nline 5\n"
"line 6\nline 7\nline 8\n"
"line 9\nline 10\nline 11\n"
"line 12\nline 13\nline 14\n"
"line 15\nline 16\nline 17\n"
"line 18\nline 19\nline 20\n"
"line 21\nline 22\nline 23\n");
}
int main (int argc, char **argv) {
my_build_app_window();
app_win->show(argc, argv);
return (Fl::run());
}
Callbacks
Callbacks 是當視窗元件的值改變時所呼叫的函數。 下面是使用 Fl_Button 與回呼函數 (callback function) 的程式,還示範了應該如何設置 shortcut。
#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Window.H>
#include <cstdlib>
void close_callback(Fl_Widget *, void *) {
if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape)
return;
exit(0);
}
int main(int argc, char **argv) {
Fl_Window *window = new Fl_Window(340, 180);
Fl_Button *button = new Fl_Button(20, 40, 300, 100, "Hello, World!");
button->type(FL_NORMAL_BUTTON);
button->color(FL_WHITE);
button->labelcolor(FL_BLACK);
button->shortcut(FL_ALT + 'q');
button->callback(close_callback);
window->end();
window->callback(close_callback);
window->show(argc, argv);
return Fl::run();
}
通常只有當視窗元件的值發生變化時才會執行回呼函數,使用者可以使用 Fl_Widget::when() 方法進行相關的設定。
FLTK 也可以使用 Lambda Expression 作為回呼函數,下面是改寫的版本:
#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Window.H>
#include <cstdlib>
int main(int argc, char **argv) {
auto lambda = [](Fl_Widget *, void *) -> void {
if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape)
return;
exit(0);
};
Fl_Window *window = new Fl_Window(340, 180);
Fl_Button *button = new Fl_Button(20, 40, 300, 100, "Hello, World!");
button->type(FL_NORMAL_BUTTON);
button->color(FL_WHITE);
button->labelcolor(FL_BLACK);
button->shortcut(FL_ALT + 'q');
button->callback(lambda);
window->end();
window->callback(lambda);
window->show(argc, argv);
return Fl::run();
}
下面是使用了 Fl_Check_Button 與 Fl_Output 的程式:
#include <FL/Fl.H>
#include <FL/Fl_Check_Button.H>
#include <FL/Fl_Output.H>
#include <FL/Fl_Window.H>
int main(int argc, char **argv) {
Fl::scheme("gtk+");
auto lambda = [](Fl_Widget *w, void *data) -> void {
Fl_Check_Button *button = static_cast<Fl_Check_Button *>(w);
Fl_Output *output = static_cast<Fl_Output *>(data);
if (button->value()) {
output->value("Checkbox is ON");
output->redraw();
} else {
output->value("Checkbox is OFF");
output->redraw();
}
};
Fl_Window *window = new Fl_Window(350, 250, "FLTK Example");
Fl_Check_Button *check_button =
new Fl_Check_Button(50, 50, 150, 30, "Enable Feature");
Fl_Output *output = new Fl_Output(50, 100, 200, 30);
output->align(FL_ALIGN_BOTTOM);
output->tooltip("Display checkbox status.");
output->value("Checkbox is OFF");
check_button->callback(lambda, output);
window->end();
window->show(argc, argv);
return Fl::run();
}
下面是使用了 Fl_Round_Button 的程式:
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Round_Button.H>
#include <FL/Fl_Window.H>
void button_cb(Fl_Widget *w, void *data) {
Fl_Round_Button *button = static_cast<Fl_Round_Button *>(w);
const char *label = button->label();
Fl_Box *output_box = static_cast<Fl_Box *>(data);
if (button->value()) {
output_box->copy_label(label);
}
output_box->redraw();
}
int main(int argc, char **argv) {
Fl::scheme("gtk+");
Fl_Window *window = new Fl_Window(300, 200, "Example");
Fl_Box *output_box = new Fl_Box(20, 150, 260, 30, "Selected: None");
output_box->box(FL_FLAT_BOX);
output_box->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
output_box->labelcolor(FL_BLUE);
Fl_Round_Button *button1 = new Fl_Round_Button(20, 20, 100, 30, "Option A");
button1->type(FL_RADIO_BUTTON);
button1->callback(button_cb, output_box);
Fl_Round_Button *button2 = new Fl_Round_Button(20, 60, 100, 30, "Option B");
button2->type(FL_RADIO_BUTTON);
button2->callback(button_cb, output_box);
Fl_Round_Button *button3 =
new Fl_Round_Button(20, 100, 100, 30, "Option C");
button3->type(FL_RADIO_BUTTON);
button3->callback(button_cb, output_box);
window->end();
window->show(argc, argv);
return Fl::run();
}
下面是使用了 Fl_Slider 的程式:
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Slider.H>
#include <FL/Fl_Window.H>
#include <iomanip>
#include <sstream>
#include <string>
void slider_callback(Fl_Widget *w, void *data) {
Fl_Slider *slider = static_cast<Fl_Slider *> (w);
double value = slider->value();
std::stringstream stream;
stream << std::fixed << std::setprecision(2) << value;
std::string myvalue = stream.str();
Fl_Box *box = static_cast<Fl_Box *> (data);
box->copy_label(myvalue.c_str()); // Update label
box->redraw(); // Update UI
}
int main() {
Fl::scheme("gtk+");
Fl_Window *window = new Fl_Window(400, 250, "Example");
Fl_Slider *slider = new Fl_Slider(50, 50, 300, 40);
slider->type(FL_HORIZONTAL);
slider->value(50.0);
slider->range(0.0, 100.0);
slider->step(0.5);
std::stringstream stream;
stream << std::fixed << std::setprecision(2) << slider->value();
std::string myvalue = stream.str();
Fl_Box *value_box = new Fl_Box(50, 150, 300, 30);
value_box->box(FL_DOWN_BOX);
value_box->label(myvalue.c_str());
slider->callback(slider_callback, value_box);
window->end();
window->show();
return Fl::run();
}
下面是使用了 Fl_Choice 的程式:
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Choice.H>
#include <FL/Fl_Window.H>
#include <FL/fl_ask.H>
void choice_callback(Fl_Widget *w, void *data) {
Fl_Choice *choice = static_cast<Fl_Choice *>(w);
Fl_Box *box = static_cast<Fl_Box *>(data);
int selected_index = choice->value();
const char *selected_text = choice->text(selected_index);
box->copy_label(selected_text);
}
void button_callback(Fl_Widget *w, void *data) {
Fl_Choice *choice = static_cast<Fl_Choice *> (data);
int selected_index = choice->value();
const char *selected_text = choice->text(selected_index);
// Display the selection in an alert box
fl_message("You selected: %s (Index: %d)", selected_text, selected_index);
}
int main(int argc, char **argv) {
Fl::scheme("plastic");
Fl_Window *win = new Fl_Window(400, 300, "Fl_Choice Example");
win->begin();
Fl_Choice *choice = new Fl_Choice(100, 50, 150, 30, "Option:");
choice->add("Ada");
choice->add("C#");
choice->add("C++");
choice->add("Erlang");
choice->add("Fortran");
choice->add("Lua");
choice->add("Java");
choice->add("JavaScript");
choice->add("Object Pascal");
choice->add("Perl");
choice->add("PHP");
choice->add("Python");
choice->add("Rexx");
choice->add("Tcl");
Fl_Box *box = new Fl_Box(100, 100, 150, 30);
box->box(FL_UP_BOX);
choice->callback(choice_callback, box);
Fl_Button *button = new Fl_Button(100, 150, 150, 30, "Show Selection");
button->callback(button_callback, choice);
win->end();
win->show(argc, argv);
return Fl::run();
}
Fl_Browser 視窗元件顯示一個可捲動的文字行列表,並管理所有文字的儲存。下面是修改自 FLTK 範例程式的練習。
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Hold_Browser.H>
#include <FL/Fl_Multi_Browser.H>
// Hold Browser's callback
void HoldBrowserCallback(Fl_Widget *w, void *data) {
Fl_Hold_Browser *brow = static_cast<Fl_Hold_Browser *>(w);
Fl_Box *box = static_cast<Fl_Box *>(data);
int line = brow->value();
box->copy_label(brow->text(line));
}
int main(int argc, char *argv[]) {
Fl::scheme("gtk+");
Fl_Double_Window *win = new Fl_Double_Window(360, 270, "Simple Browser");
win->begin();
Fl_Hold_Browser *brow = new Fl_Hold_Browser(10, 10, win->w() - 20, 100);
brow->callback(HoldBrowserCallback); // callback for hold browser
brow->add("One");
brow->add("Two");
brow->add("Three");
brow->add("Four");
brow->select(1);
Fl_Box *box = new Fl_Box(10, 120, win->w() - 20, 100, "One");
box->box(FL_UP_BOX);
box->labelfont(FL_BOLD + FL_ITALIC);
box->labelsize(36);
box->labelcolor(FL_BLUE);
brow->callback(HoldBrowserCallback, box); // callback for hold browser
win->end();
win->resizable(win);
win->show(argc, argv);
return (Fl::run());
}
下面是一個簡單的 right-click context menu 例子。
#include <FL/Fl.H>
#include <FL/Fl_Menu_Button.H>
#include <FL/Fl_Multiline_Input.H>
#include <FL/Fl_Window.H>
#include <cstdlib>
void Menu_CB(Fl_Widget *w, void *d) {
exit(0);
}
int main(int argc, char **argv) {
Fl_Window *window = new Fl_Window(640, 480);
window->tooltip("Use right-click for popup menu...");
Fl_Menu_Button *menu = new Fl_Menu_Button(0, 0, 640, 480, "Popup Menu");
menu->type(Fl_Menu_Button::POPUP3); // pops menu on right click
menu->add("Quit", "^q", Menu_CB, 0); // ctrl-q hotkey
menu->menu_end();
Fl_Multiline_Input *input =
new Fl_Multiline_Input(100, 200, 350, 50, "Input");
input->value("Right-click anywhere on gray window area\nfor popup menu");
window->end();
window->show(argc, argv);
return Fl::run();
}
下面是一個簡單的 Fl_Menu_Bar 例子。
#include <FL/Fl.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/Fl_Window.H>
#include <cstdio>
#include <cstdlib>
#include <cstring>
static void MyMenuCallback(Fl_Widget *w, void *) {
Fl_Menu_Bar *bar = static_cast<Fl_Menu_Bar *> (w);
const Fl_Menu_Item *item = bar->mvalue();
char ipath[256];
bar->item_pathname(ipath, sizeof(ipath));
fprintf(stderr, "callback: You picked '%s'",
item->label()); // Print item picked
fprintf(stderr, ", item_pathname() is '%s'", ipath); // ..and full pathname
fprintf(stderr, "\n");
if (strcmp(item->label(), "&Quit") == 0) {
exit(0);
}
}
int main() {
Fl::scheme("gleam");
Fl_Window *win = new Fl_Window(640, 480, "menubar");
Fl_Menu_Bar *menu = new Fl_Menu_Bar(0, 0, 640, 25);
menu->add("&File/&Quit", "^q", MyMenuCallback);
menu->add("&Help/&About", 0, MyMenuCallback);
win->end();
win->show();
return (Fl::run());
}
下面是修改自 Fl_Chart 範例,以及使用 FL_MENU_RADIO flag 的 Fl_Menu_Bar 的測試程式。
#include <FL/Fl.H>
#include <FL/Fl_Chart.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Menu_Bar.H>
#include <cmath>
#include <cstdio>
Fl_Double_Window *g_win = nullptr;
Fl_Menu_Bar *g_menubar = nullptr;
Fl_Chart *g_chart = nullptr;
static void MyMenuCallback(Fl_Widget *w, void *data) {
Fl_Menu_Bar *bar = static_cast<Fl_Menu_Bar *>(w);
const Fl_Menu_Item *item = bar->mvalue();
if (item->flags & FL_MENU_RADIO) {
g_chart->type((uchar)item->argument()); // apply change
g_chart->redraw();
}
}
int main(int argc, char **argv) {
g_win = new Fl_Double_Window(720, 486);
g_menubar = new Fl_Menu_Bar(0, 0, g_win->w(), 25);
g_menubar->add("Options/FL_BAR_CHART", 0, MyMenuCallback,
(void *)FL_BAR_CHART, FL_MENU_RADIO);
g_menubar->add("Options/FL_HORBAR_CHART", 0, MyMenuCallback,
(void *)FL_HORBAR_CHART, FL_MENU_RADIO);
g_menubar->add("Options/FL_LINE_CHART", 0, MyMenuCallback,
(void *)FL_LINE_CHART, FL_MENU_RADIO);
g_menubar->add("Options/FL_FILL_CHART", 0, MyMenuCallback,
(void *)FL_FILL_CHART, FL_MENU_RADIO);
g_menubar->add("Options/FL_SPIKE_CHART", 0, MyMenuCallback,
(void *)FL_SPIKE_CHART, FL_MENU_RADIO);
g_menubar->add("Options/FL_PIE_CHART", 0, MyMenuCallback,
(void *)FL_PIE_CHART, FL_MENU_RADIO);
g_menubar->add("Options/FL_SPECIALPIE_CHART", 0, MyMenuCallback,
(void *)FL_SPECIALPIE_CHART, FL_MENU_RADIO);
{
Fl_Menu_Item *item =
(Fl_Menu_Item *)g_menubar->find_item("Options/FL_BAR_CHART");
item->value(1);
}
g_chart = new Fl_Chart(20, 40, g_win->w() - 40, g_win->h() - 80, "Chart");
g_chart->bounds(-125.0, 125.0);
const double start = 1.5;
const double end = start + 15.1;
for (double t = start; t < end; t += 0.5) {
double val = sin(t) * 125.0;
static char val_str[20];
sprintf(val_str, "%.0lf", val);
g_chart->add(val, val_str, (val < 0) ? FL_RED : FL_GREEN);
}
g_win->end();
g_win->resizable(g_win);
g_win->show();
return (Fl::run());
}
Fl_Terminal 是 FLTK 1.4 系列新增的視窗元件,用來提供一個可滾動的顯示區域來模擬終端 (Terminal)。 下面是我修改 Fl_Terminal 範例程式的練習。
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Terminal.H>
#include <time.h>
#define TERMINAL_HEIGHT 550
// Globals
Fl_Double_Window *g_win = nullptr;
Fl_Box *g_msg = nullptr;
Fl_Button *g_button = nullptr;
Fl_Terminal *g_tty = nullptr;
Fl_Box *status_bar = nullptr;
// Append a date/time message to the terminal every 1 seconds
void tick_cb(void *data) {
time_t lt = time(NULL);
g_tty->printf("Timer tick: \033[32m%s\033[0m\n", ctime(<));
Fl::repeat_timeout(1.0, tick_cb, data);
}
void button_cb(Fl_Widget *w, void *data) {
Fl_Button *button = static_cast<Fl_Button *>(w);
int status = Fl::has_timeout(tick_cb);
if (status == 0) {
Fl::add_timeout(0.5, tick_cb);
status_bar->label("Running.");
}
}
int main(int argc, char **argv) {
Fl::scheme("gtk+");
g_win = new Fl_Double_Window(840, 630, "Example");
g_win->begin();
g_msg = new Fl_Box(50, 10, g_win->w() - 150, 30, "Fl_Terminal example");
g_msg->labelcolor(FL_BLUE);
g_button = new Fl_Button(g_win->w() - 80, 10, 60, 30, "Run");
g_button->callback(button_cb);
g_tty = new Fl_Terminal(0, 50, g_win->w(), TERMINAL_HEIGHT);
g_tty->ansi(true);
// Creating a status bar in FLTK by using Fl_Box
status_bar = new Fl_Box(0, g_win->h() - 30, g_win->w(), 30);
status_bar->align(FL_ALIGN_LEFT | FL_ALIGN_INSIDE);
status_bar->box(FL_DOWN_BOX);
status_bar->label("Ready.");
g_win->end();
g_win->resizable(g_win);
g_win->show(argc, argv);
return Fl::run();
}
下面是我的另外一個 Fl_Terminal 練習程式,使用 Fl_Input 取得使用者的輸入,而後使用 popen() 嘗試執行。
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/Fl_Terminal.H>
#include <FL/fl_ask.H>
#include <cstdio>
#include <cstdlib>
#ifdef _WIN32
#define popen _popen
#define pclose _pclose
#endif
#define TERMINAL_HEIGHT 530
// Globals
Fl_Double_Window *g_win = nullptr;
Fl_Menu_Bar *g_menu = nullptr;
Fl_Input *g_input = nullptr;
Fl_Button *g_button = nullptr;
Fl_Terminal *g_tty = nullptr;
void MyMenuCallback(Fl_Widget *w, void *) {
Fl_Menu_Bar *bar = static_cast<Fl_Menu_Bar *>(w);
const Fl_Menu_Item *item = bar->mvalue();
if (strcmp(item->label(), "&Quit") == 0) {
exit(0);
} else if (strcmp(item->label(), "&About") == 0) {
fl_message_title("About");
fl_message("Welcome.\n"
"It is a FLTK program to test Fl_Terminal widget.");
}
}
void button_cb(Fl_Widget *w, void *data) {
Fl_Button *button = static_cast<Fl_Button *>(w);
char text[1024];
sprintf(text, "%s 2>&1", g_input->value()); // stderr + stdout
g_tty->printf("\nEXECUTING: %s\n", text);
// The popen() function opens a process by creating a pipe, forking,
// and invoking the shell.
FILE *fp = popen(text, "r");
if (fp == 0) {
g_tty->printf("Failed to execute: '%s'\n", text);
} else {
char s[1024];
while (fgets(s, sizeof(s) - 1, fp)) {
g_tty->printf("%s", s);
}
pclose(fp);
}
}
int main(int argc, char **argv) {
Fl::scheme("gleam");
g_win = new Fl_Double_Window(840, 630, "Example");
g_win->begin();
g_menu = new Fl_Menu_Bar(0, 0, g_win->w(), 30);
g_menu->add("&File/&Quit", "^q", MyMenuCallback);
g_menu->add("&Help/&About", 0, MyMenuCallback);
g_input = new Fl_Input(50, 40, g_win->w() - 150, 30, "Input: ");
g_input->maximum_size(1000);
g_button = new Fl_Button(g_win->w() - 80, 40, 60, 30, "Run");
g_button->callback(button_cb);
g_tty = new Fl_Terminal(0, 90, g_win->w(), TERMINAL_HEIGHT);
g_tty->ansi(true);
g_win->end();
g_win->resizable(g_win);
g_win->show(argc, argv);
return Fl::run();
}
Layout and containers
Fl_Scroll, Fl_Tabs 與 Fl_Wizard 是所謂的容器 (containers),基本上是直接固定視窗元件的的佈局功能, 繼承 Fl_Group 的功能加上一些加強的能力。
下面是使用 Fl_Tabs 與 Fl_Widget::when() 方法的例子。 Fl_Widget::when() 設定用於決定何時呼叫 callback function 的標記。
#include <FL/Enumerations.H>
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Group.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Tabs.H>
#include <FL/Fl_Window.H>
void tab_closed_cb(Fl_Widget *w, void *data) {
auto parent = w->parent();
parent->remove(w);
}
int main(int argc, char **argv) {
Fl::scheme("gleam");
Fl_Window window(Fl::w() / 2, Fl::h() / 2, "test");
Fl_Box windowBox(0, 32, window.w(), window.h() - 32);
window.resizable(&windowBox);
Fl_Tabs mainTabs(0, 32, window.w(), window.h() - 64);
// First tab
Fl_Group tab1(0, 64, window.w(), window.h() - 64, "Tab 1");
Fl_Button *button1 = new Fl_Button(20, 100, 100, 25, "Button 1");
Fl_Box *box1 = new Fl_Box(20, 145, 100, 50, "Text display");
tab1.when(FL_WHEN_CLOSED); // Let user can remove this tab
tab1.callback(tab_closed_cb);
tab1.end();
// Second tab
Fl_Group tab2(0, 64, window.w(), window.h() - 64, "Tab 2");
Fl_Button *button2 = new Fl_Button(20, 100, 100, 25, "Button 2");
Fl_Input *input = new Fl_Input(65, 145, 250, 25, "Input:");
input->align(FL_ALIGN_LEFT);
tab2.when(FL_WHEN_CLOSED);
tab2.callback(tab_closed_cb);
tab2.end();
// Third tab
Fl_Group tab3(0, 64, window.w(), window.h() - 64, "Tab 3");
Fl_Button *button3 = new Fl_Button(20, 100, 100, 25, "Button 3");
tab3.when(FL_WHEN_CLOSED);
tab3.callback(tab_closed_cb);
tab3.end();
mainTabs.end();
window.end();
window.show(argc, argv);
return Fl::run();
}
除了直接固定視窗元件的位置,FLTK 提供了一些佈局用的視窗元件可以用來作為自動佈局視窗元件的位置。
下面是 Fl_Flex 的例子。
#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Flex.H>
#include <FL/fl_ask.H>
void exit_cb(Fl_Widget *w, void *) {
fl_message("The '%s' button closes the window\nand terminates the program.",
w->label());
w->window()->hide();
}
void button_cb(Fl_Widget *w, void *) {
fl_message("The '%s' button does nothing.", w->label());
}
int main(int argc, char **argv) {
Fl_Double_Window window(640, 60, "Simple Fl_Flex Demo");
Fl_Flex flex(5, 5, window.w() - 10, window.h() - 10, Fl_Flex::HORIZONTAL);
Fl_Button b1(0, 0, 0, 0, "File");
Fl_Button b2(0, 0, 0, 0, "New");
Fl_Button b3(0, 0, 0, 0, "Save");
Fl_Box bx(0, 0, 0, 0); // empty space
Fl_Button eb(0, 0, 0, 0, "Exit");
// assign callbacks to buttons
b1.callback(button_cb);
b2.callback(button_cb);
b3.callback(button_cb);
eb.callback(exit_cb);
// set gap between adjacent buttons and extra spacing (invisible box size)
flex.gap(10);
flex.fixed(bx, 30);
// end() groups
flex.end();
window.end();
// set resizable, minimal window size, show() window, and execute event loop
window.resizable(flex);
window.size_range(300, 30);
window.show(argc, argv);
return Fl::run();
}
Fl_Tile 類別可以讓使用者透過拖曳子元件之間的邊框來調整其子元件的大小。下面是使用的例子:
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Tile.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Pack.H>
#include <FL/fl_draw.H>
#include <cstdlib>
void close_callback(Fl_Widget *, void *) {
if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape)
return;
exit(0);
}
// A custom box with a label and color
class ColorBox : public Fl_Box {
public:
ColorBox(int x, int y, int w, int h, const char* label, Fl_Color c)
: Fl_Box(x, y, w, h, label) {
box(FL_FLAT_BOX);
labelcolor(FL_BLACK);
labelsize(24);
labelfont(FL_BOLD);
color(c);
}
};
int main(int argc, char **argv) {
Fl_Double_Window* window = new Fl_Double_Window(600, 400, "Fl_Tile Example");
window->begin();
Fl_Tile* tile = new Fl_Tile(0, 0, 600, 400);
tile->begin();
ColorBox* box1 = new ColorBox(0, 0, 200, 400, "Left", FL_YELLOW);
ColorBox* box2 = new ColorBox(200, 0, 400, 200, "Top Right", FL_GREEN);
ColorBox* box3 = new ColorBox(200, 200, 400, 200, "Bottom Right", FL_CYAN);
tile->end();
window->resizable(tile);
window->callback(close_callback);
window->show(argc, argv);
return Fl::run();
}
Drawing
只有在某些特定的地方才可以執行繪圖到螢幕顯示器的 FLTK 程式碼。在其他地方呼叫這些函數將會導致未定義的行為!
- 最常見的地方是在 Fl_Widget::draw() 方法內部。要在此處編寫程式碼, 必須將現有 Fl_Widget 類別之一子類化並實作自己的 draw() 版本。
- 也可以建立自訂 boxtypes 與 labeltypes。這些涉及編寫可由現有 Fl_Widget::draw() 方法呼叫的小程序。 這些「類型」由儲存在 widget 的 box(), labeltype() 和可能的其他屬性中的 8 位元索引標識。
- 可以呼叫 Fl_Window::make_current() 來對視窗元件進行增量更新。使用 Fl_Widget::window() 來尋找視窗。
下面的程式是畫一個 X 的示範程式。
#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/fl_draw.H>
class DrawX : public Fl_Widget {
public:
DrawX(int X, int Y, int W, int H, const char *L = 0)
: Fl_Widget(X, Y, W, H, L) {}
virtual void draw() FL_OVERRIDE {
fl_color(FL_YELLOW);
fl_rectf(x(), y(), w(), h());
fl_color(FL_BLUE);
int x1 = x(), y1 = y();
int x2 = x() + w() - 1, y2 = y() + h() - 1;
fl_line(x1, y1, x2, y2);
fl_line(x1, y2, x2, y1);
}
};
int main() {
Fl_Double_Window win(400, 400, "Draw X");
DrawX draw_x(10, 10, win.w() - 20, win.h() - 20);
draw_x.color(FL_YELLOW);
win.resizable(draw_x);
win.show();
return (Fl::run());
}
Event
每次使用者移動滑鼠指標、點擊按鈕或按下按鍵時,都會產生一個事件 (event) 並發送到使用者的應用程式。 事件也可以來自其他程序,例如視窗管理器 (window manager)。
在 FLTK 中事件由傳遞給 handle() 方法的整數參數標識,該方法覆蓋 Fl_Widget::handle() virtual method。 有關最近事件的其他資訊儲存在靜態位置,並透過呼叫獲取 Fl::event_∗() 方法。 此靜態資訊一直有效,直到從視窗系統讀取下一個事件為止,因此可以在 handle() 方法之外查看它,例如在 callbacks 中。
下面是一個繼承 Fl_Window,並且在 handle() 方法印出事件名稱的例子。
#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Window.H>
#include <FL/names.h> // FLTK 1.1.8 and up
#include <cstdlib>
class PrintEevent_Window : public Fl_Window {
int handle(int e) {
fprintf(stderr, "EVENT: %s(%d)\n", fl_eventnames[e], e);
return (Fl_Window::handle(e));
}
public:
PrintEevent_Window(int W, int H, const char *L = 0) : Fl_Window(W, H, L) {
}
};
void close_callback(Fl_Widget *, void *) {
if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape)
return;
exit(0);
}
int main(int argc, char **argv) {
PrintEevent_Window *window = new PrintEevent_Window(340, 180);
Fl_Button *button = new Fl_Button(20, 40, 300, 100, "Exit");
button->type(FL_NORMAL_BUTTON);
button->color(FL_WHITE);
button->labelcolor(FL_BLACK);
button->shortcut(FL_ALT + 'q');
button->callback(close_callback);
window->end();
window->callback(close_callback);
window->show(argc, argv);
return Fl::run();
}
再來改寫上面的程式,使用 FL_MOVE 事件取得目前滑鼠指標的位置,並且顯示在標題列。
#include <FL/Fl.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Window.H>
#include <FL/names.h> // FLTK 1.1.8 and up
#include <cstdlib>
#include <sstream>
#include <string>
class MouseEvent_Window : public Fl_Window {
int handle(int e) {
std::stringstream stream;
std::string myvalue;
switch (e) {
case FL_MOVE:
stream << "(" << Fl::event_x() << ", " << Fl::event_y() << ")";
myvalue = stream.str();
copy_label(myvalue.c_str());
break;
default:
break;
}
return (Fl_Window::handle(e));
}
public:
MouseEvent_Window(int W, int H, const char *L = 0) : Fl_Window(W, H, L) {}
};
void close_callback(Fl_Widget *, void *) {
if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape)
return;
exit(0);
}
int main(int argc, char **argv) {
MouseEvent_Window *window = new MouseEvent_Window(340, 180);
Fl_Button *button = new Fl_Button(20, 40, 300, 100, "Exit");
button->type(FL_NORMAL_BUTTON);
button->color(FL_WHITE);
button->labelcolor(FL_BLACK);
button->shortcut(FL_ALT + 'q');
button->callback(close_callback);
window->end();
window->callback(close_callback);
window->show(argc, argv);
return Fl::run();
}
OpenGL
FLTK 提供了 Fl_Gl_Window,可以繼承此類別並且在 draw() 函式繪制自己需要的內容。
#include <FL/Fl.H>
#include <FL/Fl_Gl_Window.H>
#include <FL/gl.h>
#include <cstdlib>
class MyGlWindow : public Fl_Gl_Window {
void draw() {
if (!valid()) {
glLoadIdentity();
glViewport(0, 0, w(), h());
glOrtho(-w(), w(), -h(), h(), -1, 1);
}
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(0.0, 0.0, 0.0);
glBegin(GL_LINE_STRIP);
glVertex2f(w(), h());
glVertex2f(-w(), -h());
glEnd();
glBegin(GL_LINE_STRIP);
glVertex2f(w(), -h());
glVertex2f(-w(), h());
glEnd();
}
public:
MyGlWindow(int X, int Y, int W, int H, const char *L = 0)
: Fl_Gl_Window(X, Y, W, H, L) {}
};
int main(int argc, char **argv) {
Fl_Window win(640, 480, "OpenGL sample");
MyGlWindow mygl(10, 10, win.w() - 20, win.h() - 20);
win.end();
win.callback([](Fl_Widget *, void *) {
if (Fl::event() == FL_SHORTCUT && Fl::event_key() == FL_Escape)
return;
exit(0);
});
win.resizable(mygl);
win.show(argc, argv);
return (Fl::run());
}
CMakeLists.txt 需要增加 FLTK OpenGL 的部份:
target_link_libraries(sample PRIVATE fltk::fltk fltk::gl)
我在測試的時候不需要加入尋找 OpenGL 的部份,如果發現有相關的錯誤而需要加入,首先在 CMakeLists.txt 加入 find_package:
find_package(OpenGL REQUIRED)
MESSAGE ( STATUS " OPENGL_FOUND : " ${OPENGL_FOUND} )
MESSAGE ( STATUS " OPENGL_INCLUDE_PATH: " ${OPENGL_INCLUDE_PATH} )
更新 target_include_directories,然後在連結的部份加入:
target_link_libraries(hello PRIVATE fltk::fltk fltk::gl OpenGL::GL)
相關連結
- FLTK - Wikipedia
- Fast Light Toolkit (FLTK)
- Fast Light Toolkit - Github
- Erco's FLTK Cheat Page)
沒有留言:
張貼留言
注意:只有此網誌的成員可以留言。