/**** * * This example illustrates what it takes to use the canvas of a plain * interactor, say a MonoScene, as a drawing area via the interactor's painter. * Since a painter supplies all of the normal drawing operations, it seems * reasonable to use them to draw graphics. * * The catch is that the canvas of an interactor really isn't designed to be a * general-purpose drawing canvas. Rather, the generally intended way to do * vector graphics is on a Graphic, which can be displayed in a GraphicBlock. * The example in ../graphics shows how to do this. * * It is admittedly misleading to have all of the nice-looking graphics * operations in a Painter if it's not intended to be a general-purpose drawing * class. What the InterViews designers evidently had in mind was to provide a * convenient means to draw any kind of shape for an interactor. The way they * chose to do this is with the full-featured Painter class. But despite its * featurefulness, the Painter class is tailored for the specific purpose of * drawing the graphic shape of an interactor, not for drawing general * graphics. * * If one does want to use an interactor's canvas for general drawing (in spite * of the preceding observations), there are some "gotchas" that must be dealt * with. First, no drawing can take place until the interactor's canvas has * been allocated, and this does not happen until the interactor has been * mapped onto the screen, which in turn does not happen until after * InsertApplication (or other top-level World::Insert... function) has been * called. So, one must wait until *after* InsertApplication () has been * called to do any drawing on the canvas. * * Second, if the drawing is going to be resizeable, which is the default for * basic interactors, then a Redraw function must be provided in order for the * redrawing to look sensible. This is because Redraw is called whenever the * resizing takes place, and it should redraw the contents of the canvas * sensibly. E.g., in the example of this file, a single rectangle is drawn * near the lower left corner of the canvas. The redraw function provided * redraws so that the rectangle stays in the same relative place after the * window is resized. * * If desired, the Redraw function could do something arbitrarily * sophisticated, such as scale the drawing proportional to the resize. The * bottom line is that without a Redraw function that effectively redraws the * entire drawing in some sensible way, the canvas won't look right after * resize. To be very specific in this regard, if a MonoScene is used as the * canvas, without a redraw the drawing will go away entirely, since the * default behavior for MonoScene::Resize is to set the background, which will * clear out any painter-drawn shapes. * * What this adds up to is that the Redraw function must essentially do all of * the drawing over again, where "all of the drawing" is whatever set of * graphic shapes have previously been drawn. Hence, it could make sense just * to have the Redraw function do whatever drawing is to be done initially, and * then call it manually to do the drawing once, just after InsertApplication. * This is NOT the way it's done in this example, since here we provide both a * DrawRect function that is called to do the initial drawing, and a separate * redraw function that does drawing over again, with the saved rectangle * coordinates from the initial call to DrawRect. * * Given this requirement for Redraw, one can see that incremental shape * addition will require that the programmer keep track somehow of the * parameters for all of the shapes that have been drawn, so that Redraw can * put them all on the canvas. This is way more painful than using the Graphic * solution mentioned above (found in ../graphics). In that example, a Picture * class is used, which allows new graphics to be incrementally drawn simply by * calling Picture::Append. Hence, using an interactor canvas for general * drawing is at best useful for a drawing that is composed all at once in a * single function, not for a drawing that will have shapes incrementally * added, say in response to some user intput. * * The third gotcha is that only a very plain interactor should be used as a * drawing canvas, such as a MonoScene or just a plain interactor. E.g., in * this example all occurrences of "MonoScene" could be replaced with * "Interactor" with no effect. This is because both of these plain * interactors have no drawn shape of their own. Using some non-empty * interactor, such as say button, as a drawing canvas would be obviously * silly, since we want neither the shape of the button, nor any of the button * behavior on a plain drawing canvas. * * Fourth, in order to set the initial shape of the interactor, and hence its * canvas, the shape must be extracted from the interactor after it is * constructed, and the shape must then be modified as desired. E.g., in this * example, Shape::Rect(50,50) is called to set the initial size of the canvas * to 50x50 pixels. Without this modification, the default size of the canvas * will be 100x100 pixels. After calling Shape::Rect, Shape::Rigid is called * to set the shrinkablility and stretchability of the interactor. In this * example, the call Shape::Rigid(hfil, hfil, vfil, vfil) makes the interactor * maximally shrinkable and stretchable. (Note that the function name "Rigid" * is rather misleading. It does not make a shape purely rigid, but supplies * four parameters that define the rigidity (or non-rigidity) of the * interactor. Hence the name "SetRigidity" or even "SetFlexiblity" would be * better that "Rigid". * * Finally, the implementation of Redraw should probably start with a call to * output->ClearRect(0, 0, xmax, ymax). This draws the canvas background in * whatever color has been chosen for the painter. The four coordinates sent * to ClearRect are the full size of the canvas, relative to 0,0 in the lower * left corner. The xmax and ymax values are protected data members inherited * from Interactor. Without this call to ClearRect in Redraw, if the * interactor is the outermost in the window (i.e., it is the argument to * InsertApplication), then the first time the interactor is drawn on the * screen, it will have a transparent background, and hence "absorb' whatever * background in comes up on. This is rarely if ever desirable behavior. If * the drawing interactor is not the outermost, and it's put in some other * interactor such as a Tray, then the call to ClearRect may not be necessary. * This would be the case if we wanted the drawing canvas to have the same * initial background as the tray. This is most likely an unusual * circumstance, so the bottom line is that the call to ClearRect in Redraw is * probably a good idea in almost all cases. * */ #include #include #include #include #include #include #include class DrawingArea : public MonoScene { public: DrawingArea(); void DrawRect(Coord l, Coord b, Coord r, Coord t); void Redraw(Coord l, Coord b, Coord r, Coord t); Painter* GetOutput() {return output;} protected: Coord l; Coord b; Coord r; Coord t; }; DrawingArea::DrawingArea() : MonoScene(new Sensor(), new Painter()) { output->SetColors(new Color("black"), new Color("white")); } void DrawingArea::DrawRect(Coord l, Coord b, Coord r, Coord t) { this->l = l; this->b = b; this->r = r; this->t = t; output->SetColors(new Color("black"), new Color("white")); output->Rect(canvas, l, b, r, t); } void DrawingArea::Redraw(Coord l, Coord b, Coord r, Coord t) { output->ClearRect(canvas, 0, 0, xmax, ymax); output->Rect(canvas, this->l, this->b, this->r, this->t); } main() { World* world = new World(); DrawingArea* drawing_area = new DrawingArea(); Shape* shape = drawing_area->GetShape(); shape->Rect(50, 50); shape->Rigid(hfil, hfil, vfil, vfil); drawing_area->Reshape(*shape); world->InsertApplication(drawing_area); drawing_area->DrawRect(10,10,20,20); world->Run(); }