With a bit of care you can also use OpenGL to draw into normal fltk windows. This is mostly useful because you can access Gourand 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>, plus it defines some extra drawing functions provided by fltk, and also gets around a horrid screwup by our friends in Seattle.
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) {} }; 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_KEYBOARD: ... keypress, key is in Fl::event_key(), ascii in Fl::event_text() return 1; default: // tell fltk that I don't understand other events return 0; } }
When handle() is called, the glx context is not set up! If your display changes, you should call redraw() and let draw() do the work. Don't call any gl functions from inside handle()!
This may mean you cannot call some OpenGl stuff like hit detection. You can fix this by doing:
case FL_PUSH: make_current(); // make glx 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 ...
Your main program can now create one of your windows by doing "new MyWindow(...)". You can also use fluid:
You must put glwindow->show() in your main code after calling show() on the window containing the gl window.
An Fl_Gl_Window sets things up so OpenGL works, and also keeps an OpenGL "context" for that window, so that changes to the lighting and projection may be reused between redraws. Fl_Gl_Window also flushes the OpenGL streams and swaps buffers after draw() returns.
Fl_Gl_Window::draw() is a pure virtual method. You must subclass Fl_Gl_Window and provide an implementation for draw(). You may also provide an implementation of draw_overlay() if you want to draw into the overlay planes. You can avoid reinitializing the viewport and lights and other things by checking valid() at the start of draw() and only doing the initialization if it is false.
The draw() method can only use OpenGL calls. Do not attempt to call X, any of the functions in <FL/fl_draw.H>, or glX directly. Do not call gl_start() or gl_finish().
Fl_Gl_Window::Fl_Gl_Window(int W, int H, const char *l=0);
Fl_Gl_Window::Fl_Gl_Window(int X, int Y, int W, int H, const char
*l=0)
FL_RGB|FL_DOUBLE|FL_DEPTH
.
const int Fl_Gl_Window::mode() const;
int Fl_Gl_Window::mode(int);
FL_RGB
- Color (not indexed)
FL_RGB8
- Color with at least 8 bits of each color
FL_INDEX
- Indexed mode
FL_SINGLE
- not double buffered
FL_DOUBLE
- double buffered
FL_ACCUM
- accumulation buffer
FL_ALPHA
- alpha channel in color
FL_DEPTH
- depth buffer
FL_STENCIL
- stencil buffer
FL_MULTISAMPLE
- multisample antialiasing
FL_RGB
and FL_SINGLE
have a
value of zero, they are "on" unless you give
FL_INDEX
or FL_DOUBLE
.
If the desired combination cannot be done, fltk will try turning off
the FL_MULTISAMPLE
. If this also fails show() will call
Fl::error() and not show the window.
You can change the mode while the window is displayed. This is most useful for turning double-buffering on and off. Under X this will cause the old X window to be destroyed and a new one created. If this is a top-level window this will unfortunately also cause the window to blink, raise to the top, and be de-iconized, and the xid() will change, possibly breaking other code. It is best to make the GL window a child of another window if you wish to do this!
int Fl_Gl_Window::mode(const int *);
This call only works on systems using glX. This value is
passed unchanged to glXChooseVisual(), superceeding the value
calculated from mode(int). See "man glXChooseVisual" if you wish to
construct your own mode. Fltk assummes that the pointer is to static
const data, and caches the pointer with the found visual.
glXChooseVisual is not called until show() or can_do()
is called. To restore the use of mode(int), call
mode((int*)0)
.
static int Fl_Gl_Window::can_do(int);
static int Fl_Gl_Window::can_do(const int *mode);
int Fl_Gl_Window::can_do() const;
char Fl_Gl_Window::valid() const;
void Fl_Gl_Window::invalidate();
void Fl_Gl_Window::valid(char i);
Fl_Gl_Window::valid()
is turned off when fltk creates a
new context for this window and by the window resizing, and is turned
on after draw() is called. You can use this inside your draw()
method to avoid unneccessarily initializing the OpenGL context. Just
do this:
void mywindow::draw() {
if (!valid()) {
glViewport(0,0,w(),h());
glFrustum(...);
glLight(...);
...other initilization...
}
... draw your geometry here ...
}
You can also turn valid() off yourself (for instance if you know
the current projection has changed). To do this call
invalidate()
.
You can turn valid() on by calling valid(1). You should only do this after fixing the transformation inside a draw() or after make_current(). This is done automatically after draw() returns.
void Fl_Gl_Window::ortho();
void Fl_Gl_Window::make_current();
void Fl_Gl_Window::make_overlay_current();
void Fl_Gl_Window::swap_buffers();
void Fl_Gl_Window::hide();
Fl_Gl_Window::~Fl_Gl_Window();
int Fl_Gl_Window::can_do_overlay();
void Fl_Gl_Window::redraw_overlay();
virtual void Fl_Gl_Window::draw_overlay();
Both this function and Fl_Gl_Window::draw() must check Fl_Gl_Window::valid(), and set the same transformation. If you don't your code may not work on other systems. Depending on the OS, and on whether overlays are real or simulated, the OpenGL context may be the same or different between the overlay and main window.
You can put OpenGL code into an Fl_Widget::draw() method or into the code for a boxtype or other places, with some care.
Most important, before you show any windows (including those that don't have OpenGL drawing) you must initialize fltk/X 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 a GL 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 scissor commands. These also synchronize the OpenGL graphics stream with the drawing done by other X or fltk functions.
The same context is reused each time. If your code changes the projection transformation or anything else you should use glPush/glPop to put the state back before calling gl_finish().
You may want to use Fl_Window::current()->h()
to get
the drawable height so you can flip the coordinate system.
Unfortunately there are a bunch of limitations you must adhere to for maximum portability:
Do not call gl_start()/gl_finish() when drawing an Fl_Gl_Window!
void gl_color(Fl_Color);
void gl_rect(int x,int y,int w,int h);
void gl_rectf(int x,int y,int w,int h);
void gl_font(Fl_Font fontid, int size);
int gl_height();
int gl_descent();
float gl_width(const char *);
float gl_width(const char *, int n);
float gl_width(uchar);
void gl_draw(const char *);
void gl_draw(const char *, int n);
void gl_draw(const char *, int x, int y);
void gl_draw(const char *, int n, int x, int y);
void gl_draw(const char *, int x, int y, int w, int h, Fl_Align);