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