]> git.saurik.com Git - wxWidgets.git/blame - demos/life/life.cpp
Generic path instead of hardcoded
[wxWidgets.git] / demos / life / life.cpp
CommitLineData
5a1dca12
GRG
1/////////////////////////////////////////////////////////////////////////////
2// Name: life.cpp
3// Purpose: The game of life, created by J. H. Conway
4// Author: Guillermo Rodriguez Garcia, <guille@iies.es>
5// Modified by:
6// Created: Jan/2000
7// RCS-ID: $Id$
8// Copyright: (c) 2000, Guillermo Rodriguez Garcia
9// Licence: wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12// ==========================================================================
e0a40292 13// headers, declarations, constants
5a1dca12
GRG
14// ==========================================================================
15
5a1dca12 16#ifdef __GNUG__
2480be69 17 #pragma implementation "life.h"
5a1dca12
GRG
18#endif
19
5a1dca12 20#include "wx/statline.h"
2480be69
GRG
21
22#include "life.h"
23#include "game.h"
24#include "dialogs.h"
5a1dca12
GRG
25
26// --------------------------------------------------------------------------
27// resources
28// --------------------------------------------------------------------------
29
30#if defined(__WXGTK__) || defined(__WXMOTIF__)
31 // the application icon
32 #include "mondrian.xpm"
33
34 // bitmap buttons for the toolbar
35 #include "bitmaps/reset.xpm"
36 #include "bitmaps/play.xpm"
37 #include "bitmaps/stop.xpm"
e0a40292
GRG
38 #include "bitmaps/zoomin.xpm"
39 #include "bitmaps/zoomout.xpm"
5a1dca12
GRG
40#endif
41
5a1dca12
GRG
42// --------------------------------------------------------------------------
43// constants
44// --------------------------------------------------------------------------
45
46// IDs for the controls and the menu commands
47enum
48{
49 // menu items and toolbar buttons
e0a40292 50 ID_RESET = 1001,
087e4f4a
GRG
51 ID_SAMPLES,
52 ID_ABOUT,
53 ID_EXIT,
e0a40292 54 ID_CENTER,
5a1dca12 55 ID_START,
087e4f4a 56 ID_STEP,
5a1dca12 57 ID_STOP,
e0a40292
GRG
58 ID_ZOOMIN,
59 ID_ZOOMOUT,
60 ID_TOPSPEED,
5a1dca12 61
087e4f4a 62 // speed selection slider
2480be69 63 ID_SLIDER
5a1dca12
GRG
64};
65
66// --------------------------------------------------------------------------
67// event tables and other macros for wxWindows
68// --------------------------------------------------------------------------
69
70// Event tables
5a1dca12 71BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
e0a40292
GRG
72 EVT_MENU (ID_SAMPLES, LifeFrame::OnSamples)
73 EVT_MENU (ID_RESET, LifeFrame::OnMenu)
74 EVT_MENU (ID_ABOUT, LifeFrame::OnMenu)
75 EVT_MENU (ID_EXIT, LifeFrame::OnMenu)
76 EVT_MENU (ID_CENTER, LifeFrame::OnMenu)
77 EVT_MENU (ID_START, LifeFrame::OnMenu)
78 EVT_MENU (ID_STEP, LifeFrame::OnMenu)
79 EVT_MENU (ID_STOP, LifeFrame::OnMenu)
80 EVT_MENU (ID_ZOOMIN, LifeFrame::OnMenu)
81 EVT_MENU (ID_ZOOMOUT, LifeFrame::OnMenu)
82 EVT_MENU (ID_TOPSPEED, LifeFrame::OnMenu)
83 EVT_COMMAND_SCROLL (ID_SLIDER, LifeFrame::OnSlider)
84 EVT_CLOSE ( LifeFrame::OnClose)
ecbdd409 85END_EVENT_TABLE()
5a1dca12 86
e0a40292
GRG
87BEGIN_EVENT_TABLE(LifeCanvas, wxWindow)
88 EVT_PAINT ( LifeCanvas::OnPaint)
89 EVT_SCROLLWIN ( LifeCanvas::OnScroll)
90 EVT_SIZE ( LifeCanvas::OnSize)
91 EVT_MOUSE_EVENTS ( LifeCanvas::OnMouse)
92 EVT_ERASE_BACKGROUND( LifeCanvas::OnEraseBackground)
ecbdd409 93END_EVENT_TABLE()
5a1dca12 94
5a1dca12
GRG
95
96// Create a new application object
97IMPLEMENT_APP(LifeApp)
98
e0a40292 99
5a1dca12
GRG
100// ==========================================================================
101// implementation
102// ==========================================================================
103
2480be69 104// some shortcuts
e0a40292
GRG
105#define ADD_TOOL(id, bmp, tooltip, help) \
106 toolBar->AddTool(id, bmp, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, tooltip, help)
2480be69 107
e0a40292 108#define GET_FRAME() ((LifeFrame *) wxGetApp().GetTopWindow())
2480be69 109
5a1dca12
GRG
110// --------------------------------------------------------------------------
111// LifeApp
112// --------------------------------------------------------------------------
113
e0a40292 114// 'Main program' equivalent: the program execution "starts" here
5a1dca12
GRG
115bool LifeApp::OnInit()
116{
117 // create the main application window
118 LifeFrame *frame = new LifeFrame();
119
120 // show it and tell the application that it's our main window
121 frame->Show(TRUE);
122 SetTopWindow(frame);
123
124 // enter the main message loop and run the app
125 return TRUE;
126}
127
128// --------------------------------------------------------------------------
129// LifeFrame
130// --------------------------------------------------------------------------
131
132// frame constructor
133LifeFrame::LifeFrame() : wxFrame((wxFrame *)0, -1, _("Life!"), wxPoint(50, 50))
134{
135 // frame icon
136 SetIcon(wxICON(mondrian));
137
138 // menu bar
139 wxMenu *menuFile = new wxMenu("", wxMENU_TEAROFF);
087e4f4a 140 wxMenu *menuGame = new wxMenu("", wxMENU_TEAROFF);
5a1dca12 141
e0a40292 142 menuFile->Append(ID_RESET, _("Reset"), _("Start a new game"));
087e4f4a 143 menuFile->Append(ID_SAMPLES, _("Sample game..."), _("Select a sample configuration"));
5a1dca12
GRG
144 menuFile->AppendSeparator();
145 menuFile->Append(ID_ABOUT, _("&About...\tCtrl-A"), _("Show about dialog"));
146 menuFile->AppendSeparator();
147 menuFile->Append(ID_EXIT, _("E&xit\tAlt-X"), _("Quit this program"));
087e4f4a 148
e0a40292 149 menuGame->Append(ID_CENTER, _("Re&center\tCtrl-C"), _("Go to (0, 0)"));
087e4f4a
GRG
150 menuGame->Append(ID_START, _("&Start\tCtrl-S"), _("Start"));
151 menuGame->Append(ID_STEP, _("&Next\tCtrl-N"), _("Single step"));
152 menuGame->Append(ID_STOP, _("S&top\tCtrl-T"), _("Stop"));
153 menuGame->Enable(ID_STOP, FALSE);
154 menuGame->AppendSeparator();
e0a40292
GRG
155 menuGame->Append(ID_TOPSPEED, _("Top speed!"), _("Go as fast as possible"));
156 menuGame->AppendSeparator();
157 menuGame->Append(ID_ZOOMIN, _("Zoom &in\tCtrl-I"));
158 menuGame->Append(ID_ZOOMOUT, _("Zoom &out\tCtrl-O"));
087e4f4a 159
5a1dca12
GRG
160 wxMenuBar *menuBar = new wxMenuBar();
161 menuBar->Append(menuFile, _("&File"));
087e4f4a 162 menuBar->Append(menuGame, _("&Game"));
5a1dca12
GRG
163 SetMenuBar(menuBar);
164
165 // tool bar
e0a40292 166 wxBitmap tbBitmaps[5];
471ed537 167
5a1dca12
GRG
168 tbBitmaps[0] = wxBITMAP(reset);
169 tbBitmaps[1] = wxBITMAP(play);
170 tbBitmaps[2] = wxBITMAP(stop);
e0a40292
GRG
171 tbBitmaps[3] = wxBITMAP(zoomin);
172 tbBitmaps[4] = wxBITMAP(zoomout);
5a1dca12
GRG
173
174 wxToolBar *toolBar = CreateToolBar();
175 toolBar->SetMargins(5, 5);
176 toolBar->SetToolBitmapSize(wxSize(16, 16));
33e39147
GRG
177 ADD_TOOL(ID_RESET, tbBitmaps[0], _("Reset"), _("Start a new game"));
178 ADD_TOOL(ID_START, tbBitmaps[1], _("Start"), _("Start"));
179 ADD_TOOL(ID_STOP, tbBitmaps[2], _("Stop"), _("Stop"));
e0a40292 180 toolBar->AddSeparator();
33e39147
GRG
181 ADD_TOOL(ID_ZOOMIN, tbBitmaps[3], _("Zoom in"), _("Zoom in"));
182 ADD_TOOL(ID_ZOOMOUT, tbBitmaps[4], _("Zoom out"), _("Zoom out"));
5a1dca12 183 toolBar->Realize();
e0a40292 184 toolBar->EnableTool(ID_STOP, FALSE); // must be after Realize() !
5a1dca12
GRG
185
186 // status bar
187 CreateStatusBar(2);
188 SetStatusText(_("Welcome to Life!"));
189
e0a40292 190 // game and canvas
2480be69 191 wxPanel *panel = new wxPanel(this, -1);
e0a40292 192 m_life = new Life();
2480be69
GRG
193 m_canvas = new LifeCanvas(panel, m_life);
194 m_timer = new LifeTimer();
e0a40292
GRG
195 m_running = FALSE;
196 m_topspeed = FALSE;
2480be69
GRG
197 m_interval = 500;
198 m_tics = 0;
199 m_text = new wxStaticText(panel, -1, "");
5a1dca12
GRG
200 UpdateInfoText();
201
e0a40292 202 // speed selection slider
2480be69
GRG
203 wxSlider *slider = new wxSlider(panel, ID_SLIDER,
204 5, 1, 10,
205 wxDefaultPosition,
206 wxSize(200, -1),
207 wxSL_HORIZONTAL | wxSL_AUTOTICKS);
5a1dca12
GRG
208
209 // component layout
210 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
087e4f4a 211 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
33e39147 212 sizer->Add(m_canvas, 1, wxGROW | wxCENTRE | wxALL, 2);
5a1dca12 213 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
33e39147
GRG
214 sizer->Add(m_text, 0, wxCENTRE | wxTOP, 4);
215 sizer->Add(slider, 0, wxCENTRE | wxALL, 4);
5a1dca12
GRG
216 panel->SetSizer(sizer);
217 panel->SetAutoLayout(TRUE);
218 sizer->Fit(this);
219 sizer->SetSizeHints(this);
220}
221
222LifeFrame::~LifeFrame()
223{
224 delete m_timer;
5a1dca12
GRG
225}
226
227void LifeFrame::UpdateInfoText()
228{
229 wxString msg;
230
e0a40292
GRG
231 msg.Printf(_(" Generation: %u (T: %u ms), Population: %u "),
232 m_tics,
233 m_topspeed? 0 : m_interval,
234 m_life->GetNumCells());
5a1dca12
GRG
235 m_text->SetLabel(msg);
236}
237
e0a40292
GRG
238// Enable or disable tools and menu entries according to the current
239// state. See also wxEVT_UPDATE_UI events for a slightly different
240// way to do this.
241void LifeFrame::UpdateUI()
242{
a9cf4097 243 // start / stop
e0a40292
GRG
244 GetToolBar()->EnableTool(ID_START, !m_running);
245 GetToolBar()->EnableTool(ID_STOP, m_running);
246 GetMenuBar()->GetMenu(1)->Enable(ID_START, !m_running);
247 GetMenuBar()->GetMenu(1)->Enable(ID_STEP, !m_running);
248 GetMenuBar()->GetMenu(1)->Enable(ID_STOP, m_running);
a9cf4097
GRG
249
250 // zooming
251 int cellsize = m_canvas->GetCellSize();
252 GetToolBar()->EnableTool(ID_ZOOMIN, cellsize < 32);
253 GetToolBar()->EnableTool(ID_ZOOMOUT, cellsize > 1);
254 GetMenuBar()->GetMenu(1)->Enable(ID_ZOOMIN, cellsize < 32);
255 GetMenuBar()->GetMenu(1)->Enable(ID_ZOOMOUT, cellsize > 1);
e0a40292
GRG
256}
257
5a1dca12
GRG
258// event handlers
259void LifeFrame::OnMenu(wxCommandEvent& event)
260{
261 switch (event.GetId())
262 {
e0a40292 263 case ID_CENTER : m_canvas->Recenter(0, 0); break;
5a1dca12 264 case ID_START : OnStart(); break;
087e4f4a 265 case ID_STEP : OnTimer(); break;
5a1dca12 266 case ID_STOP : OnStop(); break;
e0a40292 267 case ID_ZOOMIN :
087e4f4a 268 {
e0a40292
GRG
269 int cellsize = m_canvas->GetCellSize();
270 if (cellsize < 32)
a9cf4097 271 {
e0a40292 272 m_canvas->SetCellSize(cellsize * 2);
a9cf4097
GRG
273 UpdateUI();
274 }
087e4f4a
GRG
275 break;
276 }
e0a40292 277 case ID_ZOOMOUT :
5a1dca12 278 {
e0a40292
GRG
279 int cellsize = m_canvas->GetCellSize();
280 if (cellsize > 1)
a9cf4097 281 {
e0a40292 282 m_canvas->SetCellSize(cellsize / 2);
a9cf4097
GRG
283 UpdateUI();
284 }
e0a40292
GRG
285 break;
286 }
287 case ID_TOPSPEED:
288 {
289 m_running = TRUE;
290 m_topspeed = TRUE;
291 UpdateUI();
292 while (m_running && m_topspeed)
293 {
294 OnTimer();
295 wxYield();
296 }
297 break;
298 }
299 case ID_RESET:
300 {
301 // stop if it was running
5a1dca12
GRG
302 OnStop();
303 m_life->Clear();
e0a40292 304 m_canvas->Recenter(0, 0);
5a1dca12
GRG
305 m_tics = 0;
306 UpdateInfoText();
307 break;
308 }
e0a40292 309 case ID_ABOUT:
5a1dca12 310 {
e0a40292
GRG
311 LifeAboutDialog dialog(this);
312 dialog.ShowModal();
5a1dca12
GRG
313 break;
314 }
315 case ID_EXIT :
316 {
317 // TRUE is to force the frame to close
318 Close(TRUE);
319 break;
320 }
321 }
322}
323
e0a40292 324void LifeFrame::OnClose(wxCloseEvent& WXUNUSED(event))
5a1dca12 325{
e0a40292
GRG
326 // Stop if it was running; this is absolutely needed because
327 // the frame won't be actually destroyed until there are no
328 // more pending events, and this in turn won't ever happen
329 // if the timer is running faster than the window can redraw.
5a1dca12 330 OnStop();
e0a40292 331 Destroy();
5a1dca12
GRG
332}
333
087e4f4a
GRG
334void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
335{
336 // stop if it was running
337 OnStop();
338
471ed537 339 // dialog box
087e4f4a
GRG
340 LifeSamplesDialog dialog(this);
341
342 // new game?
343 if (dialog.ShowModal() == wxID_OK)
344 {
e0a40292 345 const LifeShape shape = dialog.GetShape();
087e4f4a
GRG
346
347 // put the shape
e0a40292
GRG
348 m_life->Clear();
349 m_life->SetShape(shape);
087e4f4a 350
e0a40292
GRG
351 // recenter canvas
352 m_canvas->Recenter(0, 0);
087e4f4a
GRG
353 m_tics = 0;
354 UpdateInfoText();
355 }
356}
357
5a1dca12
GRG
358void LifeFrame::OnStart()
359{
087e4f4a
GRG
360 if (!m_running)
361 {
087e4f4a
GRG
362 m_timer->Start(m_interval);
363 m_running = TRUE;
e0a40292 364 UpdateUI();
087e4f4a 365 }
5a1dca12
GRG
366}
367
368void LifeFrame::OnStop()
369{
087e4f4a
GRG
370 if (m_running)
371 {
087e4f4a
GRG
372 m_timer->Stop();
373 m_running = FALSE;
e0a40292
GRG
374 m_topspeed = FALSE;
375 UpdateUI();
087e4f4a 376 }
5a1dca12
GRG
377}
378
379void LifeFrame::OnTimer()
380{
a36f0f83
GRG
381 if (m_life->NextTic())
382 m_tics++;
383 else
384 OnStop();
5a1dca12 385
e0a40292 386 m_canvas->DrawChanged();
a36f0f83 387 UpdateInfoText();
5a1dca12
GRG
388}
389
a36f0f83
GRG
390void LifeFrame::OnSlider(wxScrollEvent& event)
391{
392 m_interval = event.GetPosition() * 100;
393
a36f0f83
GRG
394 if (m_running)
395 {
e0a40292
GRG
396 OnStop();
397 OnStart();
a36f0f83 398 }
e0a40292 399
a36f0f83
GRG
400 UpdateInfoText();
401}
402
5a1dca12
GRG
403// --------------------------------------------------------------------------
404// LifeTimer
405// --------------------------------------------------------------------------
406
5a1dca12
GRG
407void LifeTimer::Notify()
408{
a36f0f83 409 GET_FRAME()->OnTimer();
087e4f4a 410};
5a1dca12
GRG
411
412// --------------------------------------------------------------------------
087e4f4a 413// LifeCanvas
5a1dca12
GRG
414// --------------------------------------------------------------------------
415
416// canvas constructor
087e4f4a 417LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive)
e0a40292
GRG
418 : wxWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100),
419 wxSUNKEN_BORDER)
5a1dca12 420{
087e4f4a
GRG
421 m_life = life;
422 m_interactive = interactive;
423 m_cellsize = 8;
e0a40292
GRG
424 m_status = MOUSE_NOACTION;
425 m_viewportX = 0;
426 m_viewportY = 0;
427 m_viewportH = 0;
428 m_viewportW = 0;
429
430 if (m_interactive)
431 SetCursor(*wxCROSS_CURSOR);
432
433 // reduce flicker if wxEVT_ERASE_BACKGROUND is not available
434 SetBackgroundColour(*wxWHITE);
5a1dca12
GRG
435}
436
437LifeCanvas::~LifeCanvas()
438{
e0a40292 439 delete m_life;
5a1dca12
GRG
440}
441
e0a40292
GRG
442// recenter at the given position
443void LifeCanvas::Recenter(wxInt32 i, wxInt32 j)
5a1dca12 444{
e0a40292
GRG
445 m_viewportX = i - m_viewportW / 2;
446 m_viewportY = j - m_viewportH / 2;
5a1dca12 447
087e4f4a 448 // redraw everything
e0a40292 449 Refresh(FALSE);
5a1dca12
GRG
450}
451
e0a40292
GRG
452// set the cell size and refresh display
453void LifeCanvas::SetCellSize(int cellsize)
5a1dca12 454{
e0a40292
GRG
455 m_cellsize = cellsize;
456
457 // find current center
458 wxInt32 cx = m_viewportX + m_viewportW / 2;
459 wxInt32 cy = m_viewportY + m_viewportH / 2;
460
461 // get current canvas size and adjust viewport accordingly
462 wxCoord w, h;
463 GetClientSize(&w, &h);
464 m_viewportW = (w + m_cellsize - 1) / m_cellsize;
465 m_viewportH = (h + m_cellsize - 1) / m_cellsize;
466
467 // recenter
468 m_viewportX = cx - m_viewportW / 2;
469 m_viewportY = cy - m_viewportH / 2;
470
471 // adjust scrollbars
472 if (m_interactive)
473 {
474 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
475 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
476 m_thumbX = m_viewportW;
477 m_thumbY = m_viewportH;
478 }
479
480 Refresh(FALSE);
481}
2480be69 482
e0a40292
GRG
483// draw a cell
484void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, bool alive)
485{
486 wxClientDC dc(this);
5a1dca12 487
e0a40292
GRG
488 dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
489 dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
5a1dca12 490
e0a40292
GRG
491 dc.BeginDrawing();
492 DrawCell(i, j, dc);
5a1dca12 493 dc.EndDrawing();
5a1dca12
GRG
494}
495
e0a40292 496void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, wxDC &dc)
5a1dca12 497{
e0a40292
GRG
498 wxCoord x = CellToX(i);
499 wxCoord y = CellToY(j);
5a1dca12 500
e0a40292
GRG
501 // if cellsize is 1 or 2, there will be no grid
502 switch (m_cellsize)
503 {
504 case 1:
505 dc.DrawPoint(x, y);
506 break;
507 case 2:
508 dc.DrawRectangle(x, y, 2, 2);
509 break;
510 default:
511 dc.DrawRectangle(x + 1, y + 1, m_cellsize - 1, m_cellsize - 1);
512 }
5a1dca12
GRG
513}
514
e0a40292
GRG
515// draw all changed cells
516void LifeCanvas::DrawChanged()
5a1dca12 517{
e0a40292
GRG
518 wxClientDC dc(this);
519
520 size_t ncells;
521 Cell *cells;
522 bool done = FALSE;
523
524 m_life->BeginFind(m_viewportX,
525 m_viewportY,
526 m_viewportX + m_viewportW,
527 m_viewportY + m_viewportH,
528 TRUE);
529
530 dc.BeginDrawing();
531 dc.SetLogicalFunction(wxINVERT);
532
533 if (m_cellsize == 1)
5a1dca12 534 {
e0a40292 535 // drawn using DrawPoint
5a1dca12 536 dc.SetPen(*wxBLACK_PEN);
5a1dca12
GRG
537 }
538 else
539 {
e0a40292
GRG
540 // drawn using DrawRectangle
541 dc.SetPen(*wxTRANSPARENT_PEN);
542 dc.SetBrush(*wxBLACK_BRUSH);
543 }
544
545 while (!done)
546 {
547 done = m_life->FindMore(&cells, &ncells);
548
549 for (size_t m = 0; m < ncells; m++)
550 DrawCell(cells[m].i, cells[m].j, dc);
5a1dca12 551 }
e0a40292 552 dc.EndDrawing();
5a1dca12 553}
ecbdd409 554
5a1dca12
GRG
555// event handlers
556void LifeCanvas::OnPaint(wxPaintEvent& event)
557{
558 wxPaintDC dc(this);
e0a40292
GRG
559 wxRect rect = GetUpdateRegion().GetBox();
560 wxCoord x, y, w, h;
561 wxInt32 i0, j0, i1, j1;
562
563 // find damaged area
564 x = rect.GetX();
565 y = rect.GetY();
566 w = rect.GetWidth();
567 h = rect.GetHeight();
568
569 i0 = XToCell(x);
570 j0 = YToCell(y);
571 i1 = XToCell(x + w - 1);
572 j1 = YToCell(y + h - 1);
573
574 size_t ncells;
575 Cell *cells;
576 bool done = FALSE;
5a1dca12 577
e0a40292
GRG
578 m_life->BeginFind(i0, j0, i1, j1, FALSE);
579 done = m_life->FindMore(&cells, &ncells);
5a1dca12 580
e0a40292 581 // erase all damaged cells and draw the grid
5a1dca12 582 dc.BeginDrawing();
e0a40292 583 dc.SetBrush(*wxWHITE_BRUSH);
5a1dca12 584
e0a40292 585 if (m_cellsize <= 2)
5a1dca12 586 {
e0a40292
GRG
587 // no grid
588 dc.SetPen(*wxWHITE_PEN);
589 dc.DrawRectangle(x, y, w, h);
5a1dca12 590 }
e0a40292
GRG
591 else
592 {
593 x = CellToX(i0);
594 y = CellToY(j0);
595 w = CellToX(i1 + 1) - x + 1;
596 h = CellToY(j1 + 1) - y + 1;
597
598 dc.SetPen(*wxLIGHT_GREY_PEN);
599 for (wxInt32 yy = y; yy <= (y + h - m_cellsize); yy += m_cellsize)
600 dc.DrawRectangle(x, yy, w, m_cellsize + 1);
601 for (wxInt32 xx = x; xx <= (x + w - m_cellsize); xx += m_cellsize)
602 dc.DrawLine(xx, y, xx, y + h);
603 }
604
605 // draw all alive cells
606 dc.SetPen(*wxBLACK_PEN);
607 dc.SetBrush(*wxBLACK_BRUSH);
608
609 while (!done)
610 {
611 for (size_t m = 0; m < ncells; m++)
612 DrawCell(cells[m].i, cells[m].j, dc);
613
614 done = m_life->FindMore(&cells, &ncells);
615 }
616
617 // last set
618 for (size_t m = 0; m < ncells; m++)
619 DrawCell(cells[m].i, cells[m].j, dc);
5a1dca12 620
5a1dca12
GRG
621 dc.EndDrawing();
622}
623
624void LifeCanvas::OnMouse(wxMouseEvent& event)
625{
087e4f4a
GRG
626 if (!m_interactive)
627 return;
628
5a1dca12 629 // which cell are we pointing at?
e0a40292
GRG
630 wxInt32 i = XToCell( event.GetX() );
631 wxInt32 j = YToCell( event.GetY() );
632
633 // set statusbar text
634 wxString msg;
635 msg.Printf(_("Cell: (%d, %d)"), i, j);
636 GET_FRAME()->SetStatusText(msg, 1);
5a1dca12
GRG
637
638 // button pressed?
639 if (!event.LeftIsDown())
640 {
641 m_status = MOUSE_NOACTION;
7989fb37 642 return;
5a1dca12 643 }
5a1dca12 644
7989fb37
GRG
645 // button just pressed?
646 if (m_status == MOUSE_NOACTION)
647 {
648 // yes, update status and toggle this cell
649 m_status = (m_life->IsAlive(i, j)? MOUSE_ERASING : MOUSE_DRAWING);
650
651 m_mi = i;
652 m_mj = j;
653 m_life->SetCell(i, j, m_status == MOUSE_DRAWING);
654 DrawCell(i, j, m_status == MOUSE_DRAWING);
655 }
656 else if ((m_mi != i) || (m_mj != j))
657 {
658 // draw a line of cells using Bresenham's algorithm
659 wxInt32 d, ii, jj, di, ai, si, dj, aj, sj;
660 di = i - m_mi;
661 ai = abs(di) << 1;
662 si = (di < 0)? -1 : 1;
663 dj = j - m_mj;
664 aj = abs(dj) << 1;
665 sj = (dj < 0)? -1 : 1;
666
667 ii = m_mi;
668 jj = m_mj;
669
670 if (ai > aj)
671 {
672 // iterate over i
673 d = aj - (ai >> 1);
674
675 while (ii != i)
676 {
677 m_life->SetCell(ii, jj, m_status == MOUSE_DRAWING);
678 DrawCell(ii, jj, m_status == MOUSE_DRAWING);
679 if (d >= 0)
680 {
681 jj += sj;
682 d -= ai;
683 }
684 ii += si;
685 d += aj;
686 }
687 }
688 else
5a1dca12 689 {
7989fb37
GRG
690 // iterate over j
691 d = ai - (aj >> 1);
692
693 while (jj != j)
694 {
695 m_life->SetCell(ii, jj, m_status == MOUSE_DRAWING);
696 DrawCell(ii, jj, m_status == MOUSE_DRAWING);
697 if (d >= 0)
698 {
699 ii += si;
700 d -= aj;
701 }
702 jj += sj;
703 d += ai;
704 }
5a1dca12 705 }
7989fb37
GRG
706
707 // last cell
708 m_life->SetCell(ii, jj, m_status == MOUSE_DRAWING);
709 DrawCell(ii, jj, m_status == MOUSE_DRAWING);
710 m_mi = ii;
711 m_mj = jj;
5a1dca12 712 }
7989fb37
GRG
713
714 GET_FRAME()->UpdateInfoText();
5a1dca12
GRG
715}
716
717void LifeCanvas::OnSize(wxSizeEvent& event)
718{
e0a40292
GRG
719 // find center
720 wxInt32 cx = m_viewportX + m_viewportW / 2;
721 wxInt32 cy = m_viewportY + m_viewportH / 2;
722
723 // get new size
5a1dca12
GRG
724 wxCoord w = event.GetSize().GetX();
725 wxCoord h = event.GetSize().GetY();
e0a40292
GRG
726 m_viewportW = (w + m_cellsize - 1) / m_cellsize;
727 m_viewportH = (h + m_cellsize - 1) / m_cellsize;
728
729 // recenter
730 m_viewportX = cx - m_viewportW / 2;
731 m_viewportY = cy - m_viewportH / 2;
732
733 // scrollbars
734 if (m_interactive)
735 {
736 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
737 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
738 m_thumbX = m_viewportW;
739 m_thumbY = m_viewportH;
740 }
5a1dca12
GRG
741
742 // allow default processing
743 event.Skip();
744}
e0a40292
GRG
745
746void LifeCanvas::OnScroll(wxScrollWinEvent& event)
747{
748 WXTYPE type = event.GetEventType();
749 int pos = event.GetPosition();
750 int orient = event.GetOrientation();
e0a40292
GRG
751
752 // calculate scroll increment
33e39147 753 int scrollinc = 0;
e0a40292
GRG
754 switch (type)
755 {
756 case wxEVT_SCROLLWIN_TOP:
757 {
758 if (orient == wxHORIZONTAL)
759 scrollinc = -m_viewportW;
760 else
761 scrollinc = -m_viewportH;
762 break;
763 }
764 case wxEVT_SCROLLWIN_BOTTOM:
765 {
766 if (orient == wxHORIZONTAL)
767 scrollinc = m_viewportW;
768 else
769 scrollinc = m_viewportH;
770 break;
771 }
772 case wxEVT_SCROLLWIN_LINEUP: scrollinc = -1; break;
773 case wxEVT_SCROLLWIN_LINEDOWN: scrollinc = +1; break;
774 case wxEVT_SCROLLWIN_PAGEUP: scrollinc = -10; break;
775 case wxEVT_SCROLLWIN_PAGEDOWN: scrollinc = +10; break;
776 case wxEVT_SCROLLWIN_THUMBTRACK:
777 {
33e39147 778 if (orient == wxHORIZONTAL)
e0a40292 779 {
33e39147
GRG
780 scrollinc = pos - m_thumbX;
781 m_thumbX = pos;
e0a40292
GRG
782 }
783 else
784 {
33e39147
GRG
785 scrollinc = pos - m_thumbY;
786 m_thumbY = pos;
e0a40292
GRG
787 }
788 break;
789 }
33e39147
GRG
790 case wxEVT_SCROLLWIN_THUMBRELEASE:
791 {
792 m_thumbX = m_viewportW;
793 m_thumbY = m_viewportH;
794 }
e0a40292
GRG
795 }
796
797#ifdef __WXGTK__ // what about Motif?
798 // wxGTK updates the thumb automatically (wxMSW doesn't); reset it back
33e39147 799 if (type != wxEVT_SCROLLWIN_THUMBTRACK)
e0a40292
GRG
800 {
801 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
802 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
803 }
804#endif
805
806 if (scrollinc == 0) return;
807
808 // scroll the window and adjust the viewport
809 if (orient == wxHORIZONTAL)
810 {
811 m_viewportX += scrollinc;
812 ScrollWindow( -m_cellsize * scrollinc, 0, (const wxRect *) NULL);
813 }
814 else
815 {
816 m_viewportY += scrollinc;
817 ScrollWindow( 0, -m_cellsize * scrollinc, (const wxRect *) NULL);
818 }
819}
820
821void LifeCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
822{
823 // do nothing. I just don't want the background to be erased, you know.
824}