summaryrefslogtreecommitdiff
path: root/osframework/source/demos/V12Demo/.#Board.cpp.1.7
blob: c176a748c06218dad59da94ed954d59b7539baf0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
#include "Board.h"
#include "DemoWidget.h"
#include "Res.h"
#include "V12DemoApp.h"
#include "..\SexyAppFramework\Font.h"
#include "..\SexyAppFramework\Graphics.h"
#include "..\SexyAppFramework\ButtonWidget.h"
#include "..\SexyAppFramework\WidgetManager.h"
#include "..\SexyAppFramework\Dialog.h"
#include "..\SexyAppFramework\Flags.h"
#include "..\SexyAppFramework\SexyMatrix.h"
#include "..\SexyAppFramework\trivertex.h"

using namespace Sexy;

Board::Board(V12DemoApp* theApp)
{
	mApp = theApp;


	// Remeber the flag explanation in the 1.2 doc and V12Demo.cpp? Sure you do.
	// This is basically another use of it. All widgets by default have the following
	// flags set:
	//	WIDGETFLAGS_UPDATE		- The widget gets its Update and UpdateF methods called
	//	WIDGETFLAGS_DRAW		- The widget is allowed to draw
	//	WIDGETFLAGS_CLIP		- Set to clip Graphics passed into Draw to the widget's bounds
	//	WIDGETFLAGS_ALLOW_MOUSE	- Set to allow mouse interaction
	//	WIDGETFLAGS_ALLOW_FOCUS	- Set to allow focus to be maintained 
	//
	//	Previously, if you wanted to mark the widget dirty every frame (and thus draw every
	//	frame), you had to call MarkDirty() all the time. Instead of doing that, we can just
	//	use the flag system. By ORing WIDGETFLAGS_MARK_DIRTY with mWidgetFlagsMod.mAddFlags,
	//	when we are added to the widget manager, the manager will then know that we want to
	//	be marked dirty every frame automatically, instead of manually calling MarkDirty().
	//	Yes, I know, that's not a huge deal, but as you can see from the other flags,
	//	it's now easy to set/unset various flags for various widgets and..various reasons.
	//	If you wanted to remove flags set by default for this widget, you'd just OR the
	//	flags to remove with mWidgetFlagsMod.mRemoveFlags. You can affect ALL widgets if you
	//	want to set up widget defaults, by modifying mApp->mWidgetManager's mWidgetFlags
	//	structure in a similar way.

	mWidgetFlagsMod.mAddFlags |= WIDGETFLAGS_MARK_DIRTY;

	// Previously, it was annoying trying to place widgets on some sort of parent widget, 
	// since there was no notion of parent/child relationship. What you had to do was
	// override the AddedToManager and RemovedFromManager functions, create and add your
	// widgets or remove and nuke them, and in the case of AddedToManager, you also had
	// to then place the widget using global coordinates that had no relation to the coordinates
	// you set the parent widget at. Not anymore. What we can do now is to dispense with
	// overriding those methods altogether! So now, in the parent's constructor, we can
	// create our child widgets, resize them, and place them using relative coordinates.
	// What this also means is that moving the parent widget around results in moving
	// ALL the child widgets, which means you no longer have to manually move everything
	// just because the parent moved. In addition, another nice thing is that you no longer
	// have to remove your child widgets before deleting them: this is handled automatically
	// for you.
	mDemoButton = new ButtonWidget(0, this);
	mDemoButton->mLabel = "Demo Widget";
	mDemoButton->SetFont(FONT_DEFAULT);
	mDemoButton->Resize(10, 10, 10 + FONT_DEFAULT->StringWidth(mDemoButton->mLabel), 50);

	// VERY IMPORTANT: Notice that we're calling THIS CLASS' (or really, it's parent, WidgetContainer's)
	// AddWidget method instead of the WidgetManager's method. In order to designate a widget as a child
	// widget, you have to call the AddWidget method of the class that will be its parent.
	AddWidget(mDemoButton);

	mDialogButton = new ButtonWidget(1, this);
	mDialogButton->mLabel = "Do Dialog";
	mDialogButton->SetFont(FONT_DEFAULT);
	int w = FONT_DEFAULT->StringWidth(mDialogButton->mLabel);
	mDialogButton->Resize(mApp->mWidth - 20 - w, 10, w + 10, 50);
	AddWidget(mDialogButton);

	mCurtainButton = new ButtonWidget(2, this);
	mCurtainButton->mLabel = "Do Transition";
	mCurtainButton->SetFont(FONT_DEFAULT);
	w = FONT_DEFAULT->StringWidth(mCurtainButton->mLabel);
	mCurtainButton->Resize(mDemoButton->mX + 20 + mDemoButton->mWidth, 10, w + 10, 50);
	AddWidget(mCurtainButton);

	mDemoWidget = NULL;

	// Position and set up our pulsing rectangle, as well as the location of the lost focus text.
	mRect = Rect(mApp->mWidth / 2 - 1, mApp->mHeight / 2 - 1, 2, 2);
	mExpanding = true;
	mMsgX = Rand() % (mApp->mWidth - 100);
	mMsgY = Rand() % (mApp->mHeight - 100);
	mLostFocus = false;

	mCurtainWidth = 0;
	mCurtainMode = CURTAIN_INACTIVE;

	// We'll toggle this between 0 and 2 every second when we are unfocussed
	mDeferPriority = 0;
}

Board::~Board()
{
	// We need to remove child widgets before deleting them.
	RemoveAllWidgets();

	delete mDemoButton;
	delete mDialogButton;
	delete mCurtainButton;

	if (mDemoWidget != NULL)
		mApp->mWidgetManager->RemoveWidget(mDemoWidget);

	delete mDemoWidget;
}

void Board::Update()
{
	Widget::Update();

	if (mLostFocus)
	{
		if (mUpdateCnt % 100 == 0)
		{
			mMsgX = Rand() % (mApp->mWidth - 100);
			mMsgY = Rand() % (mApp->mHeight - 100);

			// Every second, we switch between drawing the overlay above or below
			// the mDemoWidget object (assuming of course you have the widget on screen
			//	when you lose focus).
			mDeferPriority = mDeferPriority == 0 ? 2 : 0;
		}
	}
	else
	{		
		// This just makes the rectangle in the middle grow/shrink. Nothing
		// too crazy.
		if (mExpanding)
		{
			mRect.mWidth += 2;
			mRect.mHeight += 2;

			if (mRect.mWidth >= mApp->mWidth)
			{
				mRect.mWidth = mApp->mWidth;
				mExpanding = false;
			}

			if (mRect.mHeight >= mApp->mHeight)
			{
				mRect.mHeight = mApp->mHeight;
				mExpanding = false;
			}
		}
		else
		{
			mRect.mWidth -= 2;
			mRect.mHeight -= 2;

			if (mRect.mHeight <= 0)
			{
				mRect.mHeight = 0;
				mExpanding = true;
			}

			if (mRect.mWidth <= 0)
			{
				mRect.mWidth = 0;
				mExpanding = true;
			}

		}

		mRect.mX = mApp->mWidth / 2 - mRect.mWidth / 2;
		mRect.mY = mApp->mHeight / 2 - mRect.mHeight / 2;
	}
}

void Board::Draw(Graphics* g)
{
	g->SetColor(Color::Black);
	g->FillRect(0, 0, mWidth, mHeight);

	// Draw our pulsing rectangle in the middle of the screen.
	g->SetColor(Color(255, 0, 0));
	g->FillRect(mRect);

	if (!mLostFocus)
	{
		// Previously, any time you changed the
		// graphic's state, you had to undo it, otherwise it affected anything
		// drawn later. Common examples are SetColorizeImages, SetColor, SetDrawMode, etc.
		// In Board::DrawOverlay, you'll see that we can use PushState and PopState to on-demand
		// save/restore the graphics state. However, if we're either lazy, or after we draw
		// our current stuff we don't have a need to reset the state and change it again, we
		// can use the GraphicsAutoState object. This nifty helper class, upon instantiation on 
		// the stack, will automatically push the graphics state of the graphics object you pass in.
		// Note that since stack variables are
		// removed (and have their con/destructors called automatically, for classes/structs) when they
		// go out of scope, the destructor for a GraphicsAutoState object takes care of popping the graphics
		// state and restoring things to their previous..uh...state.
		// NOTE: Graphics state is saved between widgets.
		GraphicsAutoState auto_state(g);
		g->DrawImage(IMAGE_HUNGARR_LOGO, 10, 100);
		g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
		g->SetColorizeImages(true);
		g->SetColor(Color(mUpdateCnt % 128, mUpdateCnt % 255, mUpdateCnt % 64));
		g->DrawImage(IMAGE_HUNGARR_LOGO, 10, 100);
	}

	if (mCurtainMode != CURTAIN_INACTIVE)
	{
		g->SetColor(Color(255, 255, 0));
		g->FillRect(0, 0, mCurtainWidth, mHeight);
		g->FillRect(mWidth - mCurtainWidth, 0, mCurtainWidth, mHeight);
	}

	// Instead of using an overlay widget to draw our stuff when focus is lost,
	// we can just use DeferOverlay. You can ONLY call this in a Draw routine. What happens
	// when you call DeferOverlay is that it schedules with the WidgetManager a call to
	// this widget's DrawOverlay method at a later time. You can control the order of the 
	// overlay layer, and place it above or below other widgets by optionally specifying
	// a defer priority in the call to DeferOverlay. The higher the priority, the more toplevel
	// and thus the more widgets above it will be drawn. You'll notice that in DemoWidget's constructor,
	// that we set its priority to 1. Thus, if we DeferOverlay with a value of 0, the overlay will be
	// drawn BELOW the demo widget, but still above all our child widgets and ourself. When mDeferPriority
	// is 2, it will be drawn above ALL widgets, including the demo widget, as its priority is only 1, which
	// last time I checked, is less than 2. Note that widgets by default have priority 0 and dialogs have priority 1.
	//if (mLostFocus)
		DeferOverlay(mDeferPriority);

}

void Board::DrawOverlay(Graphics* g)
{
	// Make sure you've read through Board::Draw before this guy, as it explains
	// how and why we get here.
	g->SetColor(Color(0, 0, 255, 175));
	g->FillRect(0, 0, mWidth, mHeight);

	g->SetFont(FONT_DEFAULT);
	g->SetColor(Color::White);
	g->DrawString("LOST FOCUS", mMsgX, mMsgY);	

	// PushState is a new addition. Previously, any time you changed the
	// graphic's state, you had to undo it, otherwise it affected anything
	// drawn later. Common examples are SetColorizeImages, SetColor, SetDrawMode, etc.
	// With PushState, you save the entire previous state of things. Then, you can make
	// whatever changes you like, without needing to undo them, as a resulting call to
	// PopState restores things back to normal. Note that you can push/pop as much as you want.
	g->PushState();

	// No need to turn this off! When we PopState the previous, non-colorized state,
	// will return.
	g->SetColorizeImages(true);
	g->SetColor(Color(0, 255, 255));
	
	// MORE NEW STUFF? Yes. Think back to the previous framework version:
	// how would you draw in real-time, a rotated and scaled image at a given
	// location? Well, you could mess around with destination Rects for scaling,
	// but you would have a hard time scaling and rotating all at once. You could
	// create images in memory to hold the scaled or rotated versions, but that's annoying.
	// BEHOLD: DrawImageTransform/F. All you have to do is create a Transform object,
	// and apply various transformations to it, then call DrawImageTransform/F.
	// Let's take a look:
	Transform t;

	// Let's rotate the image between 0 and 360 degrees (note that you could also
	// use the radian version of the function). So far, not hard, right?
	t.RotateDeg((float)(mUpdateCnt % 360));
	
	float sw = 1.0f;
	float sh = 1.0f;
	
	// Don't get scared here. This is just a little trickery to expand/contract
	// the image depending on the update count. All it does is expand the image
	// to normal size for a second, and then shrink it to almost invisible for
	// another second, which makes that pulse effect happen.
	int mod = mUpdateCnt % 200;
	if (mod < 100)
	{
		sw = (float)(mod + 1) / 100.0f;
		sh = (float)(mod + 1) / 100.0f;
	}
	else
	{
		mod = 200 - mod;
		sw = (float)mod / 100.0f;
		sh = (float)mod / 100.0f;
	}

	// And now we just tell the transform object, t, that we want to scale. 
	// The value for the x and y direction should be a %, so 1.0 means no change in scale.
	// Let's also mirror and flip the image at the same time too. To mirror and flip, we'll
	// just multiply sw and sh by -1. Of course, it'll be hard to tell that it's flipped and mirrored
	// since it's rotating at the same time, so if you don't believe me you can try commenting
	// out the t.RotateDeg(...) call above and you'll see that it works.
	t.Scale(-sw, -sh);
	
	// And now we just pass in our transform object and it works in both 2D and 3D! Note that we can also
	// draw the image at a given XY as well. We'll make it move rightward depending on the update count.
	// ******************IMPORTANT NOTE:*********************
	// DrawImageTransform/F and DrawImageMatrix use the CENTER of the image rather than the top left
	// for XY drawing. For images of even size, the non-F form of the function will just truncate
	// the the center, but for the F forms, they will be offset by 0.5f since floating point is used.
	if (gSexyAppBase->Is3DAccelerated())
		g->DrawImageTransformF(IMAGE_HUNGARR_LOGO, t, (float)(mUpdateCnt % (mApp->mWidth + 340)), 200.0f);
	else
		g->DrawImageTransform(IMAGE_HUNGARR_LOGO, t, (float)(mUpdateCnt % (mApp->mWidth + 340)), 200.0f);
	
	g->PopState();

	// You can now draw using matrices. Why the heck would you want to use matrices? Besides doing some
	// crazy stuff, you can also do some cool little tricks. For example, previously it used be a pain
	// to in real-time flip and mirror an image. While I won't give a tutorial on matrix algebra
	// (that would take a loooooong time), I'll explain the essential parts:
	SexyTransform2D matrix;


	// Multiplying the X coordinate by -1 (which is the 0, 0 element of the matrix) will result in our
	// image being mirrored, while multiplying the Y coordinate by -1 (1, 1 in the matrix) will result in our
	// image being flipped.
	matrix.m[0][0] *= -1;
	matrix.m[1][1] *= -1;
	matrix.RotateDeg(45.0f);
	g->SetFastStretch(true);
	bool b = g->GetFastStretch();

	// Let's also shear it. While mirroring/flipping could easily be done with the DrawImageTransform methods,
	// doing more complex matrix manipulation (like shearing) can only be done with the DrawImageMatrix function.
	matrix.m[0][1] = 2;

	// And then we just make a call to DrawImageMatrix and give it our specified XY coordinates as well,
	// and that's it! This works in both 2D and 3D modes.
	g->DrawImageMatrix(IMAGE_HUNGARR_LOGO, matrix, 300, 400);	

	// IMPORTANT COMPARISON NOTE:
	// DrawImageTransform/F will try to use the faster drawing methods if it recognizes certain
	// common transforms, like rotating and scaling. This only works though if you are using
	// either one operation, or multiple operations of the same type (i.e. you only used scale
	// or rotate with the Transform object, or used the same one multiple times). If you mix
	// operations, like we do above, the DrawImageTransform method will actually use DrawImageMatrix.
	// With DrawImageTransform, you can't directly modify the underlying matrix, so if that's something
	// you need to do, then DrawImageMatrix is a better option.
}

void Board::ButtonDepress(int id)
{
	if (id == mDemoButton->mId)
	{
		delete mDemoWidget;
		mDemoWidget = new DemoWidget();
		mApp->mWidgetManager->AddWidget(mDemoWidget);
		
		// What, more flags? Yup. Since our little DemoWidget isn't a dialog, when we add it,
		// it won't change anything about the widgets drawn below it. Which means, unmodified, 
		// mouse clicks could still be passed down to the board (if the click wasn't in the DemoWidget),
		// the board still updates, still draws, etc. Let's turn off mouse clicks for all widgets below the
		// DemoWidget. But, let's still allow all widgets below it to update. Note that if we used the form
		// of the method that only takes one parameter, then it would use mDefaultBelowModalFlagsMod
		// which we modified in our app. By passing in our own flags though, they're used instead.
		// Which means the only flag we need to remove from the widgets below it is the allow mouse flag.
		// We do that by making a temp FlagsMod object, and setting
		// its mRemoveFlags variable to be ORed with WIDGETFLAGS_ALLOW_MOUSE. Upon calling
		// the WidgetManager's AddBaseModal method, we then pass in the DemoWidget, and the above flags.
		// AddBaseModal will then treat this new widget as a modal object, applying any flags we passed in.
		FlagsMod flags;
		flags.mRemoveFlags |= WIDGETFLAGS_ALLOW_MOUSE;
		mApp->mWidgetManager->AddBaseModal(mDemoWidget, flags);
	}
	else if (id == mDialogButton->mId)
	{
		// With the new 1.2 changes, you can create dialogs very easily. Previously, you had to supply
		// images in order to skin the dialog and be able to see it in the first place. You also had to
		// override the SexyAppBase::NewDialog method, otherwise calls to SexyAppBase::DoDialog wouldn't
		// do anything. Not so anymore. You can rapidly prototype with dialogs even without images.
		// A default NewDialog implementation now exists, and if a dialog doesn't have an image, it will
		// be drawn using colored rectangles. So now, to make a little dialog box appear, it's as simple as
		// the single line of code below. No messy functions to write, no images to create, that's a hassel
		// when you're starting a new app and don't care about the initial appearance of your UI elements and
		// just want to start testing gameplay immediately.
		Dialog* d = mApp->DoDialog(100, true, "Fun Dialog", "Line 1\nLine 2\nLine 3", "Close!", Dialog::BUTTONS_FOOTER);

		// Using the default font, which is a system font, can sometimes cause problems on older OS's, like
		// Windows 95 or 98, in which printing with it appears to produce blank results. Let's set the font
		// for the button on the dialog box to be FONT_DEFAULT. 
		d->SetButtonFont(FONT_DEFAULT);
	}
	else if (id == mCurtainButton->mId)
	{
		mCurtainMode = CURTAIN_CLOSING;
		mCurtainWidth = 0;

		// Here's some more new stuff. Previously, if you wanted to do something like
		// a transition after a button is pressed (for example), you had to set a bunch of
		// variables in your class, check them, and process/draw differently depending on 
		// the transition state. That can be both messy and annoying to do. Why not just
		// do the logic for the transition right at the site where it has to trigger, in this
		// case, right when the button is pressed? If you ignore the UpdateApp() call below for 
		// a minute, what the loop looks like is just a simple loop that makes a variable expand
		// to 1/2 the app width, and then contract back to 0. If you removed the call to UpdateApp,
		// obviously the program would be stuck in the loop until it completed, making it look like the
		// whole thing is frozen, and not updating the display. By putting UpdateApp in the while loop,
		// you enable ALL the other widgets to update, and thus draw, which in turn means we can see
		// our little curtain transition effect working as expected. You could even use this method
		// to block while waiting for a dialog/widget to return, but to still allow other widgets
		// to update/draw in the process. ONE VERY IMPORTANT THING TO NOTE THOUGH:
		// If you have code like this in your Update methods, be careful, as you can run into some
		// reentrancy problems. If you place this sort of code in your Update methods, make sure you
		// protect it (you could just use a simple bool that's true if doing a transition, otherwise false).
		// Why? Think about it like this: UpdateApp updates ALL widgets, including the one in which the
		// while loop exists. Thus, when going back into Update for said widget, it will once again get
		// stuck in the same while loop, which will effectively prevent any drawing from occurring.
		// Just remember that UpdateApp will update EVERY widget, and so you will want to avoid having
		// your while loop called more often than it should. Of course, an easy way to avoid all that
		// is to have your transition code happen elsewhere that isn't updated every frame, like a
		// button down method.
		//
		// What you'll notice is that if the game lost focus, the curtain call still updates, although
		// the shrinking/expanding rectangle in the middle does not. That is because when focus is lost,
		// we don't update that part of the app, but this while loop is still running, which means the
		// curtain effect is still running too.
		while ((mCurtainMode != CURTAIN_INACTIVE) && mApp->UpdateApp())
		{
			if (mCurtainMode == CURTAIN_CLOSING)
			{
				mCurtainWidth += 4;
				if (mCurtainWidth >= mWidth / 2)
				{
					mCurtainWidth = mWidth / 2;
					mCurtainMode = CURTAIN_OPENING;
				}
			}
			else
			{
				mCurtainWidth -= 4;
				if (mCurtainWidth <= 0)
				{
					mCurtainWidth = 0;
					mCurtainMode = CURTAIN_INACTIVE;
				}
			}

		}

	}
}