Выбрать главу

My answer was “the GUI doesn’t own the window pointers; the game itself is responsible for adding them.” This is consistent with the C++ rule of thumb that says “those who new things also delete them.”

The alternative to the method I chose was to say “the parent window is responsible for the pointers of all his child windows.” That would mean that to prevent memory leaks, each window must, in it’s (virtual) destructor (remember, there’s derived classes), loop through its m_subwindows array and delete all of the windows contained within it.

If you decide to implement a GUI-owns-pointer system, be aware of an important trade-off - all of your windows must be dynamically allocated (newed). A quick way to crash a system like that is to pass in the address of a variable on the stack, i.e. say something like “addwindow(&mywindow)”, where mywindow is declared as a local variable on the stack. Things will work until mywindow goes out of scope, or until the destructor for the parent window is called, whereupon it’ll try to delete that address and all hell will break loose. The lesson is “be extra careful with pointers.”

That’s the main reason behind why I decided that my GUI would not own the window pointer. If you’re passing a lot of complex window classes into and out of your GUI (say, for example, you’re populating a tabbed property sheet), you might prefer a system where the GUI doesn’t keep track of the pointers, and where remove simply means “the pointer is now in my control; remove it from your array, but don’t delete it.” This would also allow you to (carefully) use addresses of local variables on the stack, provided you made sure that you removewindow()’ed them before they went out of scope.

Moving on… Showing and hiding windows is accompished through a boolean variable. Showwindow() and hidewindow() simply set or clear this variable; the window drawing and message processing functions check this “is window shown” flag before they do anything. Pretty easy stuff.

Z-ordering was also fairly easy. For those unfamiliar with the term, z-ordering refers to the “stacking” of windows on top of each other. At first thought, you may decide to implement z-ordering similar to how DirectDraw does it for overlays - you might decide to give each window an integer that describes its absolute z-order position, their place on the z axis - say, maybe, 0 is the top of the screen, and negative -1000 is furthest back. I thought a bit about implementing this type of z-ordering, but decided against it - absolute z-order positions don’t concern me; I care more about relative z-order positions. That is, I don’t really need to know “how far back” one window is from another; I simply need to know whether a given window is behind another, or in front of it.

So, I decided to implement z-order like this: The window with the highest index in the array, m_subwins, would be the window “on top.” The window at [size-1] would be directly under it, followed by [size-2], etc. The window at position [0] would be on the very bottom. In this way, processing z-ordering became very easy. Also, killing two birds with one stone, I deemed that the topmost window would always be the active window, or more technically, the window with input focus. Although this restricted my GUI from making “always on top” windows (for example: Windows NT’s task manager is always on top of all other windows, regardless of who has the input focus), I felt it was worth it to keep the code as simple as possible.

Also, I paid a small price for using array indices as z-orders was the array shuffle that occurs when I tell a given window to move to the top of the z-order. Say I tell window #2 to move to the top of a 50 window list; I’ve got to shift 48 windows down a slot to accommodate window #2’s new position at the end. The good news is that moving a window to top of the z-order isn’t really a time-critical function, and even if it were, there’s dozens of good, quick ways to juggle array items like this - linked lists spring to mind.

Check out the cheap trick I used in the bringtotop() function. Since I know that the window doesn’t own the pointers, I can just clobber the window and then immediate re-add him, effectively repositioning him at the top of the array. I did this solely because my pointer class, uti_pointerarray, already had code that would delete an element and slide all higher elements backwards one slot.

So that’s window management. Now, onto the joy of coordinate systems…

Coordinate Systems

/****************************************************************************

virtual coordinate system to graphics card resolution converters

****************************************************************************/

const double GUI_SCALEX = 10000.0;

const double GUI_SCALEY = 10000.0;

int gui_window::virtxtopixels(int virtx) {

 int width = (m_parent) ? m_parent-›getpos().getwidth() : getscreendims().getwidth();

 return((int)((double)virtx*(double)width/GUI_SCALEX));

}

int gui_window::virtytopixels(int virty) {

 int height = (m_parent) ? m_parent-›getpos().getheight() : getscreendims().getheight();

 return((int)((double)virty*(double)height/GUI_SCALEY));

}

/****************************************************************************

findchildatcoord: returns the top-most child window at coord (x,y); recursive.

****************************************************************************/

gui_window *gui_window::findchildatcoord(coord x, coord y, int flags) {

 for (int q = m_subwins.getsize()-1; q ›= 0; q--) {

  gui_window *ww = (gui_window *)m_subwins.getat(q);

  if (ww) {

   gui_window *found = ww-›findchildatcoord(x-m_position.getx1(), y-m_position.gety1(), flags);

   if (found) return(found);

  }

 }

 // check to see if this window itself is at the coord - this breaks the recursion

 if (!getinvisible() && m_position.ispointin(x,y)) return(this);

 return(NULL);

}

One of the top priorities for my GUI was resolution independence, and what I call “stretchy dialog boxes.” Basically, I wanted my windows and dialog boxes to scale themselves larger or smaller, depending on the screen resolution of the system they were running on. On systems with higher resolutions, I wanted the windows, controls, etc. to expand; on 640×480, I wanted things to shrink.

What this really meant was that I needed to implement a virtual coordinate system. I based my virtual coordinate system around an arbitrary number - I effectively said, “Henceforth, I will assume that every window is 10,000×10,000 units, regardless of the actual size of that window,” and then let my GUI do the work of scaling the coordinates. For the desktop window, the coordinates are scaled to the physical resolution of the monitor.

I accomplished this through four functions: virtxtopixels(), virtytopixels(), pixelstovirtx(), and pixelstovirty(). (Note: only two are listed in the code; I figured you got the idea). These functions are responsible for converting between the virtual 10,000×10,000 unit coordinates and either the actual dimensions of the parent window, or the physical coordinates of the monitor. Obviously, the rendering functions of the windows use these functions heavily.