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

Icon controls only become painful to implement if you’re not redrawing your entire GUI system every frame. In this case, you’ve somehow got to deal with clipping the icon control, so that even though it’s being drawn every frame, it doesn’t accidentally overwrite pixels belonging to a window that’s sitting on top of it (but wasn’t changed, so therefore wasn’t drawn). I didn’t implement this - my GUI gets redrawn every frame - but if you’re faced with this problem, you might want to try setting up a clip list for each icon, using it to draw the icon, and re-evaluating it when any window is moved, closed, or opened. This may or may not be a viable solution - I just dreamt it up while writing this - but it seems to be at least a good jumping off point.

Frame controls are also pretty straightforward. I implemented my frame control by drawing a border around m_position, then drawing the window caption at about position (5,5), in client coordinates (that is, about five pixels right and five pixels down from the upper-left of the frame control), but you may decide you want something a little fancier.

The one complex thing you might want to do for your static controls is to change the behavior of the findwindow function slightly so that it “skips” all windows that are static controls. That way, if a static text control is sitting on top of a pushbutton, the user will be able to push the button “through” the static control.

Speaking of, let’s now take a look at how to implement that button.

Pushbutton Controls

Pushbuttons are only slightly more difficult than static controls. Your pushbutton control needs to keep track of whether it’s “pressed” (pushed down) or “unpressed.” It does this by implementing two virtual functions, wm_mousedown() and wm_mouseup(), which your main calcall() function needs to call when appropriate.

Basically, in wm_mousedown(), you set a boolean variable, which I call the “depressed flag,” to true, and in wm_mouseup(), you set it back to false. Then, in your drawing code, if the depressed flag is set, you draw the button “pressed,” otherwise, you draw it “unpressed.”

Next, add an additional condition - say, “only draw the button depressed if the depressed flag is set, AND, the mouse cursor is within my client coordinates, otherwise, set the depressed flag back to false.” This will give you buttons that “pop out” if you move your mouse cursor off of them, and is very important for accurately determining when a button is clicked.

In normal GUIs, when a button is clicked, it fires off an event to its parent window, which then does whatever the button represents - i.e., clicking the close button will close the window, clicking the save button will save the file, whatever. My GUI considers a button clicked if and only if, inside wm_mouseup(), the depressed flag is set. The only way the depressed flag can still be set inside mouseup() is if the user both pressed and released the mouse button while the pointer was inside the button. This allows users to “bail out” at the last minute by holding the button down and dragging the mouse pointer somewhere outside of the button to cancel the button click, just like in any other GUI system.

That’s pushbuttons. Now, let’s take a peek at text boxes.

Carets and The Textbox Control

I chose to implement a very simple textbox control. It just captures keys, and does no scrolling - but you might want something more complex, say, a control that accurately handles the home, end, insert, and delete keys, or maybe even one with support for cutting, copying, and pasting, via the windows clipboard.

But before we can have a textbox, we need a caret. In case you’re not familiar with the terminology, a caret is another word for a cursor - that’s right, that little blinking vertical bar. Carets tell the user where the text they type is going to go.

For the purposes of my GUI, I’ve made things simple - I’ve ordained that the active window is the window that has the caret, period. This is how most GUIs behave, and seems to be the best solution. Also, my GUI, like Windows, considers the “caption” of the textbox to be the text that’s actually in the box.

So how do you implement the caret? Well, I decided that since it’s a given that the caret always going to be drawn inside the active window, and that the caret will only appear if the active window is a textbox, it makes sense to consider the caret part of the textbox and implement it inside the textbox’s draw function. This makes it really easy to implement - simply use an integer to represent an index into the character array of the window caption, and your textbox has all the information it needs to properly draw the caret.

Which means, basically, that if you’re a textbox, all you have to do to render yourself is draw a border around your client area, draw your window caption inside this border, and then, if you’re the active window, draw your caret at the correct position. In my GUI, the maximum length of a string inside a textbox is governed by the size of the textbox window, meaning that I don’t have to deal with scrolling the text within the box. You, however, might want to some way for the user to scroll through the contents of a textbox, allowing them to enter a very long string in a very small box.

By far the most difficult thing about a textbox is the keyboard processing that comes with it. Once we have a key, it’s easy to create a wm_keypressed() virtual function, and call it, and it’s easy to implement the textbox handler for wm_keypressed() so that it processes the key, and either tacks it onto the end of the window caption, or processes special keys (backspace, etc. - this is where your heavyweight string class pays for itself), and moves the caret.

The hard part is getting the key in the first place. Windows provides no less than three completely different ways to query the keyboard - the WM_KEYDOWN event, the GetKeyboardState() and GetAsyncKeyState() functions, and of course, DirectInput. I used the DirectInput method, simply because I already had done most of the heavy-lifting associated with DirectInput back when I implemented the mouse cursor, and because getting the keyboard state through DirectInput seemed to me the cleanest and most elegant solution.

To use DirectInput’s keyboard functionality, the first thing you need to do is set up a keyboard device. This is incredibly similar to how we set up the DirectInput mouse device way back in Part I of this article. Basically, the only difference here is that instead of telling DirectInput to treat our new device as a mouse, we’re telling it to treat it as a keyboard (duh). If you’ve gone through DirectInput for the mouse, doing the same stuff again for the keyboard should be easy.

Once we’ve got our keyboard device we can query it.

To actual determine if a key was “hit” requires slightly more work. Basically, to determine which keys are pressed, you need two snapshots of all 101 key states - you need a snapshot from the last frame, and a snapshot from this frame. The keys that are down in this frame but not down last frame are the keys that have been “pressed,” and they’re the keys you should send out wm_keypressed() “messages” for.

Now, onto progress bars…

Progress Bars

Progess bars are just about as easy as static controls to implement, since they only take a few messages.

Basically, you need to do two things with a progress bar - you need to tell it a min/max range, and you need to tell it to “step” some number of units. For example, say I wanted to put up a “Loading…” progress bar, because I had to load 100 different game resources. I would create a progress bar with a range of 0 to 100. I would initally set the progress bar to zero, then, whenever I loaded a game resource, I would “step” the progress bar by one unit. Whenever the progress bar was stepped, it would redraw itself, showing graphically how far along it was by displaying a bar whose length was proportionate to its client area.