With a bit of care you can also use OpenGL to draw into normal FLTK windows. This allows you to use Gouraud shading for drawing your widgets. To do this you use the gl_start() and gl_finish() functions around your OpenGL code.
You must include FLTK's <FL/gl.h> header file. It will include the file <GL/gl.h>, define some extra drawing functions provided by FLTK, and include the <windows.h> header file needed by WIN32 applications.
#ifndef MESA glDrawBuffer(GL_FRONT_AND_BACK); #endif // !MESA ... draw stuff here ... #ifndef MESA glDrawBuffer(GL_BACK); #endif // !MESANote: If you are using the Mesa graphics library, the call to glDrawBuffer() is not required and will slow down drawing considerably. The preprocessor instructions shown above will optimize your code based upon the graphics library used.
class MyWindow : public Fl_Gl_Window { void draw(); int handle(int); public: MyWindow(int X, int Y, int W, int H, const char *L) : Fl_Gl_Window(X, Y, W, H, L) {} };The draw() and handle() methods are described below. Like any widget, you can include additional private and public data in your class (such as scene graph information, etc.)
void MyWindow::draw() { if (!valid()) { ... set up projection, viewport, etc ... ... window size is in w() and h(). ... valid() is turned on by FLTK after draw() returns } ... draw ... }
int MyWindow::handle(int event) { switch(event) { case FL_PUSH: ... mouse down event ... ... position in Fl::event_x() and Fl::event_y() return 1; case FL_DRAG: ... mouse moved while down event ... return 1; case FL_RELEASE: ... mouse up event ... return 1; case FL_FOCUS : case FL_UNFOCUS : ... Return 1 if you want keyboard events, 0 otherwise return 1; case FL_KEYBOARD: ... keypress, key is in Fl::event_key(), ascii in Fl::event_text() ... Return 1 if you understand/use the keyboard event, 0 otherwise... return 1; case FL_SHORTCUT: ... shortcut, key is in Fl::event_key(), ascii in Fl::event_text() ... Return 1 if you understand/use the shortcut event, 0 otherwise... return 1; default: // pass other events to the base class... return Fl_Gl_Window::handle(event); } }When handle() is called, the OpenGL context is not set up! If your display changes, you should call redraw() and let draw() do the work. Don't call any OpenGL drawing functions from inside handle()!
You can call some OpenGL stuff like hit detection and texture loading functions by doing:
case FL_PUSH: make_current(); // make OpenGL context current if (!valid()) { ... set up projection exactly the same as draw ... valid(1); // stop it from doing this next time } ... ok to call NON-DRAWING OpenGL code here, such as hit detection, loading textures, etc...Your main program can now create one of your windows by doing new MyWindow(...). You can also use FLUID by:
Most importantly, before you show any windows (including those that don't have OpenGL drawing) you must initialize FLTK so that it knows it is going to use OpenGL. You may use any of the symbols described for Fl_Gl_Window::mode() to describe how you intend to use OpenGL:
Fl::gl_visual(FL_RGB);You can then put OpenGL drawing code anywhere you can draw normally by surrounding it with:
gl_start(); ... put your OpenGL code here ... gl_finish();gl_start() and gl_finish() set up an OpenGL context with an orthographic projection so that 0,0 is the lower-left corner of the window and each pixel is one unit. The current clipping is reproduced with OpenGL glScissor() commands. These also synchronize the OpenGL graphics stream with the drawing done by other X, WIN32, or FLTK functions.
The same context is reused each time. If your code changes the projection transformation or anything else you should use glPushMatrix() and glPopMatrix() functions to put the state back before calling gl_finish().
You may want to use Fl_Window::current()->h() to get the drawable height so that you can flip the Y coordinates.
Unfortunately, there are a bunch of limitations you must adhere to for maximum portability:
class OptimizerWindow : public Fl_Gl_Window { csContext *context_; // Initialized to 0 and set by draw()... csDrawAction *draw_action_; // Draw action... csGroup *scene_; // Scene to draw... csCamara *camera_; // Viewport for scene... void draw(); public: OptimizerWindow(int X, int Y, int W, int H, const char *L) : Fl_Gl_Window(X, Y, W, H, L) { context_ = (csContext *)0; draw_action_ = (csDrawAction *)0; scene_ = (csGroup *)0; camera_ = (csCamera *)0; } void scene(csGroup *g) { scene_ = g; redraw(); } void camera(csCamera *c) { camera_ = c; if (context_) { draw_action_->setCamera(camera_); camera_->draw(draw_action_); redraw(); } } };
void OptimizerWindow::draw() { if (!context_) { // This is the first time we've been asked to draw; create the // Optimizer context for the scene... #ifdef WIN32 context_ = new csContext((HDC)fl_getHDC()); context_->ref(); context_->makeCurrent((HDC)fl_getHDC()); #else context_ = new csContext(fl_display, fl_visual); context_->ref(); context_->makeCurrent(fl_display, fl_window); #endif // WIN32 ... perform other context setup as desired ... // Then create the draw action to handle drawing things... draw_action_ = new csDrawAction; if (camera_) { draw_action_->setCamera(camera_); camera_->draw(draw_action_); } } else { #ifdef WIN32 context_->makeCurrent((HDC)fl_getHDC()); #else context_->makeCurrent(fl_display, fl_window); #endif // WIN32 } if (!valid()) { // Update the viewport for this context... context_->setViewport(0, 0, w(), h()); } // Clear the window... context_->clear(csContext::COLOR_CLEAR | csContext::DEPTH_CLEAR, 0.0f, // Red 0.0f, // Green 0.0f, // Blue 1.0f); // Alpha // Then draw the scene (if any)... if (scene_) draw_action_->apply(scene_); }