Previous | Home | Up | Next |
In this section, we will write a simple drawing program called scribble
.
Scribble works by creating a set of polylines in response to the user's input. When the
user clicks the left mouse button, we will create a new polyline at that point in the window.
Then, as the user moves the mouse, the windowing system will report the new position to
us, which we then add as a new point in the current polyline.
Sounds simple, but in order for this to work, need to be able to receive messages about
the mouse from the windowing system. We do this by subclassing the Wnd
class and overriding the appropriate message handlers.
Message handlers are protected member functions of the Wnd
class that are called in response to a specific message sent by the windowing system.
Take a look at the definition of our Wnd
-derived class:
#include <lvt/lvt.h> using namespace LVT; class ScribblerWnd : public Wnd { public: ScribblerWnd(); protected: virtual void OnMenuCommand(int id); virtual void OnPaint(); virtual void OnLButtonDown(int x, int y); virtual void OnLButtonUp(int x, int y); virtual void OnMouseMove(int x, int y); private: std::vector<Polyline> m_lines; bool m_mouseDown; };
The message handlers are the protected member functions that begin with On. From looking at the
class definition, you can see that we are interested in messages regarding whether the left mouse
button has been clicked (OnLButtonDown
), released (OnLButtonUp
), or if the mouse has been moved
(OnMouseMove
). (We'll cover the other two functions -- OnMenuCommand
and OnPaint
-- a little later).
All three messages take as parameters the coordinates of the mouse at the time the message was sent.
You can also see that we have a vector
of
Polyline
s. A Polyline
is an
LVT class that encapsulates a set of points (or Vec
s) that are
sequentially connected with lines (think connect-the-dots). We also have a boolean variable that
records whether or not the left mouse button is currently pressed.
Lets look at the implementation of the three mouse message handlers:
void ScribblerWnd::OnLButtonDown(int x, int y) { // Start tracking the mouse. m_mouseDown = true; // Create a new polyline Polyline newLine; newLine.AddPoint((scalar) x, (scalar) y); m_lines.push_back(newLine); }
We start by recording that the mouse button is currently pressed. We then create a new
Polyline
and add the first point to it at the current mouse position.
A Polyline
, like all drawable objects in LVT, expects the coordinates of its points to be
in floating-point format, so we cast them to scalar
s before
passing them in. Once we're done, we push the new Polyline
onto our vector of Polyline
s.
void ScribblerWnd::OnLButtonUp(int x, int y) { // Stop tracking the mouse m_mouseDown = false; }
Not much exciting going on here. When we're notified that the left mouse button has been released, we simply mark it as such.
void ScribblerWnd::OnMouseMove(int x, int y) { if (!m_mouseDown) return; //Add the current mouse position as a new point in the current line m_lines.back().AddPointAndDraw((scalar) x, (scalar) y); glFlush(); }
Here we finally make use of that m_mouseDown
variable we were concerned with in the previous two
message handlers. If the mouse button is not currently pressed, we don't care about the mouse being
moved, so we return. Otherwise, we add the current mouse position to the Polyline
that's at the
end of our vector
of Polyline
s,
again casting the coordinates to scalar
s. Notice that we're using
AddPointAndDraw
, as opposed to AddPoint
, which, as you've probably guessed, adds the point to the
Polyline
and then draws the new portion of it.
One last detail is to call glFlush
, which informs OpenGL that we want the drawing to take place immediately.
We have one last detail we need to take care of in order for our drawing program to be functional.
We need to handle the case when our window needs to be redrawn. This occurs after a portion of the
window that was obscured becomes uncovered. We take care of this case in the OnPaint
message handler:
void ScribblerWnd::OnPaint() { Wnd::OnPaint(); std::for_each(m_lines.begin(), m_lines.end(), std::mem_fun_ref(&Polyline::Render)); }
Right away, you'll notice that this message is handled slightly differently than the others in that
we call the base class's handler before we doing our own handling. The default Paint handler
clears the frame buffer and initializes modelview matrix to the identity, it then renders a scene graph
attached with Wnd::ShowScene
. (We will cover scene graphs in part III.)
Unless you have a good reason to override this behavior (and such reasons do exist, which is why it's allowed),
it is recommended that you call the base OnPaint function in your own handler.
Wnd::OnPaint
will be moved to a separate function,
removing the need to call it in a derived OnPaint
method.Once the base class takes care of its portion of the message, we use a little STL magic to call
the Render
member function on each Polyline
in our
vector
. Every drawable LVT object has a Render
member function which
draws the object. Having each Polyline
in our list draw itself
ensures that the contents of our window are up to date.
You might be wondering what the difference between calling AddPointAndDraw
vs calling AddPoint
and then Render
is.
The difference is that AddPointAndDraw
draws only the portion of the object that has changed. In the case
of Polyline
, this is the line segment from the last point to the point
we just added. Render
, on the other hand, draws the entire object, from start to finish. Calling Render
on a Polyline
for each new point added would cause the drawing progressively
get slower and slower as the Polyline
gets longer. AddPointAndDraw
, on the
other hand, will always take the same amount of time.
This is all we need to complete the functionality of our drawing program. All we need to do to complete
it is to take the simple
program we created in the last section and modify it to use a
ScribblerWnd
instead of a Wnd
:
int main(int argc, char *argv[]) { LVT::App myApp; ScribblerWnd myWnd; myApp.Init(&argc, &argv); myWnd.Create("Scribble"); myApp.Run(myWnd); return 0; }
One issue we have yet to take up is the possibility that the user might make a mistake in his masterpiece, requiring him to start over. One way to allow him to do this is with a Menu, which is the subject of the next section.
Previous | Home | Up | Next |