Modified for new THUMBRELEASE event
[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 #include "wx/statline.h"
21
22 #include "life.h"
23 #include "game.h"
24 #include "dialogs.h"
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"
38 #include "bitmaps/zoomin.xpm"
39 #include "bitmaps/zoomout.xpm"
40 #endif
41
42 // --------------------------------------------------------------------------
43 // constants
44 // --------------------------------------------------------------------------
45
46 // IDs for the controls and the menu commands
47 enum
48 {
49 // menu items and toolbar buttons
50 ID_RESET = 1001,
51 ID_SAMPLES,
52 ID_ABOUT,
53 ID_EXIT,
54 ID_CENTER,
55 ID_START,
56 ID_STEP,
57 ID_STOP,
58 ID_ZOOMIN,
59 ID_ZOOMOUT,
60 ID_TOPSPEED,
61
62 // speed selection slider
63 ID_SLIDER
64 };
65
66 // --------------------------------------------------------------------------
67 // event tables and other macros for wxWindows
68 // --------------------------------------------------------------------------
69
70 // Event tables
71 BEGIN_EVENT_TABLE(LifeFrame, wxFrame)
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)
85 END_EVENT_TABLE()
86
87 BEGIN_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)
93 END_EVENT_TABLE()
94
95
96 // Create a new application object
97 IMPLEMENT_APP(LifeApp)
98
99
100 // ==========================================================================
101 // implementation
102 // ==========================================================================
103
104 // some shortcuts
105 #define ADD_TOOL(id, bmp, tooltip, help) \
106 toolBar->AddTool(id, bmp, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, tooltip, help)
107
108 #define GET_FRAME() ((LifeFrame *) wxGetApp().GetTopWindow())
109
110 // --------------------------------------------------------------------------
111 // LifeApp
112 // --------------------------------------------------------------------------
113
114 // 'Main program' equivalent: the program execution "starts" here
115 bool 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
133 LifeFrame::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);
140 wxMenu *menuGame = new wxMenu("", wxMENU_TEAROFF);
141
142 menuFile->Append(ID_RESET, _("Reset"), _("Start a new game"));
143 menuFile->Append(ID_SAMPLES, _("Sample game..."), _("Select a sample configuration"));
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"));
148
149 menuGame->Append(ID_CENTER, _("Re&center\tCtrl-C"), _("Go to (0, 0)"));
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();
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"));
159
160 wxMenuBar *menuBar = new wxMenuBar();
161 menuBar->Append(menuFile, _("&File"));
162 menuBar->Append(menuGame, _("&Game"));
163 SetMenuBar(menuBar);
164
165 // tool bar
166 wxBitmap tbBitmaps[5];
167
168 tbBitmaps[0] = wxBITMAP(reset);
169 tbBitmaps[1] = wxBITMAP(play);
170 tbBitmaps[2] = wxBITMAP(stop);
171 tbBitmaps[3] = wxBITMAP(zoomin);
172 tbBitmaps[4] = wxBITMAP(zoomout);
173
174 wxToolBar *toolBar = CreateToolBar();
175 toolBar->SetMargins(5, 5);
176 toolBar->SetToolBitmapSize(wxSize(16, 16));
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"));
180 toolBar->AddSeparator();
181 ADD_TOOL(ID_ZOOMIN, tbBitmaps[3], _("Zoom in"), _("Zoom in"));
182 ADD_TOOL(ID_ZOOMOUT, tbBitmaps[4], _("Zoom out"), _("Zoom out"));
183 toolBar->Realize();
184 toolBar->EnableTool(ID_STOP, FALSE); // must be after Realize() !
185
186 // status bar
187 CreateStatusBar(2);
188 SetStatusText(_("Welcome to Life!"));
189
190 // game and canvas
191 wxPanel *panel = new wxPanel(this, -1);
192 m_life = new Life();
193 m_canvas = new LifeCanvas(panel, m_life);
194 m_timer = new LifeTimer();
195 m_running = FALSE;
196 m_topspeed = FALSE;
197 m_interval = 500;
198 m_tics = 0;
199 m_text = new wxStaticText(panel, -1, "");
200 UpdateInfoText();
201
202 // speed selection slider
203 wxSlider *slider = new wxSlider(panel, ID_SLIDER,
204 5, 1, 10,
205 wxDefaultPosition,
206 wxSize(200, -1),
207 wxSL_HORIZONTAL | wxSL_AUTOTICKS);
208
209 // component layout
210 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
211 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
212 sizer->Add(m_canvas, 1, wxGROW | wxCENTRE | wxALL, 2);
213 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
214 sizer->Add(m_text, 0, wxCENTRE | wxTOP, 4);
215 sizer->Add(slider, 0, wxCENTRE | wxALL, 4);
216 panel->SetSizer(sizer);
217 panel->SetAutoLayout(TRUE);
218 sizer->Fit(this);
219 sizer->SetSizeHints(this);
220 }
221
222 LifeFrame::~LifeFrame()
223 {
224 delete m_timer;
225 }
226
227 void LifeFrame::UpdateInfoText()
228 {
229 wxString msg;
230
231 msg.Printf(_(" Generation: %u (T: %u ms), Population: %u "),
232 m_tics,
233 m_topspeed? 0 : m_interval,
234 m_life->GetNumCells());
235 m_text->SetLabel(msg);
236 }
237
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.
241 void LifeFrame::UpdateUI()
242 {
243 // start / stop
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);
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);
256 }
257
258 // event handlers
259 void LifeFrame::OnMenu(wxCommandEvent& event)
260 {
261 switch (event.GetId())
262 {
263 case ID_CENTER : m_canvas->Recenter(0, 0); break;
264 case ID_START : OnStart(); break;
265 case ID_STEP : OnTimer(); break;
266 case ID_STOP : OnStop(); break;
267 case ID_ZOOMIN :
268 {
269 int cellsize = m_canvas->GetCellSize();
270 if (cellsize < 32)
271 {
272 m_canvas->SetCellSize(cellsize * 2);
273 UpdateUI();
274 }
275 break;
276 }
277 case ID_ZOOMOUT :
278 {
279 int cellsize = m_canvas->GetCellSize();
280 if (cellsize > 1)
281 {
282 m_canvas->SetCellSize(cellsize / 2);
283 UpdateUI();
284 }
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
302 OnStop();
303 m_life->Clear();
304 m_canvas->Recenter(0, 0);
305 m_tics = 0;
306 UpdateInfoText();
307 break;
308 }
309 case ID_ABOUT:
310 {
311 LifeAboutDialog dialog(this);
312 dialog.ShowModal();
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
324 void LifeFrame::OnClose(wxCloseEvent& WXUNUSED(event))
325 {
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.
330 OnStop();
331 Destroy();
332 }
333
334 void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
335 {
336 // stop if it was running
337 OnStop();
338
339 // dialog box
340 LifeSamplesDialog dialog(this);
341
342 // new game?
343 if (dialog.ShowModal() == wxID_OK)
344 {
345 const LifeShape shape = dialog.GetShape();
346
347 // put the shape
348 m_life->Clear();
349 m_life->SetShape(shape);
350
351 // recenter canvas
352 m_canvas->Recenter(0, 0);
353 m_tics = 0;
354 UpdateInfoText();
355 }
356 }
357
358 void LifeFrame::OnStart()
359 {
360 if (!m_running)
361 {
362 m_timer->Start(m_interval);
363 m_running = TRUE;
364 UpdateUI();
365 }
366 }
367
368 void LifeFrame::OnStop()
369 {
370 if (m_running)
371 {
372 m_timer->Stop();
373 m_running = FALSE;
374 m_topspeed = FALSE;
375 UpdateUI();
376 }
377 }
378
379 void LifeFrame::OnTimer()
380 {
381 if (m_life->NextTic())
382 m_tics++;
383 else
384 OnStop();
385
386 m_canvas->DrawChanged();
387 UpdateInfoText();
388 }
389
390 void LifeFrame::OnSlider(wxScrollEvent& event)
391 {
392 m_interval = event.GetPosition() * 100;
393
394 if (m_running)
395 {
396 OnStop();
397 OnStart();
398 }
399
400 UpdateInfoText();
401 }
402
403 // --------------------------------------------------------------------------
404 // LifeTimer
405 // --------------------------------------------------------------------------
406
407 void LifeTimer::Notify()
408 {
409 GET_FRAME()->OnTimer();
410 };
411
412 // --------------------------------------------------------------------------
413 // LifeCanvas
414 // --------------------------------------------------------------------------
415
416 // canvas constructor
417 LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive)
418 : wxWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100),
419 wxSUNKEN_BORDER)
420 {
421 m_life = life;
422 m_interactive = interactive;
423 m_cellsize = 8;
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);
435 }
436
437 LifeCanvas::~LifeCanvas()
438 {
439 delete m_life;
440 }
441
442 // recenter at the given position
443 void LifeCanvas::Recenter(wxInt32 i, wxInt32 j)
444 {
445 m_viewportX = i - m_viewportW / 2;
446 m_viewportY = j - m_viewportH / 2;
447
448 // redraw everything
449 Refresh(FALSE);
450 }
451
452 // set the cell size and refresh display
453 void LifeCanvas::SetCellSize(int cellsize)
454 {
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 }
482
483 // draw a cell
484 void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, bool alive)
485 {
486 wxClientDC dc(this);
487
488 dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
489 dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
490
491 dc.BeginDrawing();
492 DrawCell(i, j, dc);
493 dc.EndDrawing();
494 }
495
496 void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, wxDC &dc)
497 {
498 wxCoord x = CellToX(i);
499 wxCoord y = CellToY(j);
500
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 }
513 }
514
515 // draw all changed cells
516 void LifeCanvas::DrawChanged()
517 {
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)
534 {
535 // drawn using DrawPoint
536 dc.SetPen(*wxBLACK_PEN);
537 }
538 else
539 {
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);
551 }
552 dc.EndDrawing();
553 }
554
555 // event handlers
556 void LifeCanvas::OnPaint(wxPaintEvent& event)
557 {
558 wxPaintDC dc(this);
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;
577
578 m_life->BeginFind(i0, j0, i1, j1, FALSE);
579 done = m_life->FindMore(&cells, &ncells);
580
581 // erase all damaged cells and draw the grid
582 dc.BeginDrawing();
583 dc.SetBrush(*wxWHITE_BRUSH);
584
585 if (m_cellsize <= 2)
586 {
587 // no grid
588 dc.SetPen(*wxWHITE_PEN);
589 dc.DrawRectangle(x, y, w, h);
590 }
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);
620
621 dc.EndDrawing();
622 }
623
624 void LifeCanvas::OnMouse(wxMouseEvent& event)
625 {
626 if (!m_interactive)
627 return;
628
629 // which cell are we pointing at?
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);
637
638 // button pressed?
639 if (!event.LeftIsDown())
640 {
641 m_status = MOUSE_NOACTION;
642 return;
643 }
644
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
689 {
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 }
705 }
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;
712 }
713
714 GET_FRAME()->UpdateInfoText();
715 }
716
717 void LifeCanvas::OnSize(wxSizeEvent& event)
718 {
719 // find center
720 wxInt32 cx = m_viewportX + m_viewportW / 2;
721 wxInt32 cy = m_viewportY + m_viewportH / 2;
722
723 // get new size
724 wxCoord w = event.GetSize().GetX();
725 wxCoord h = event.GetSize().GetY();
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 }
741
742 // allow default processing
743 event.Skip();
744 }
745
746 void LifeCanvas::OnScroll(wxScrollWinEvent& event)
747 {
748 WXTYPE type = event.GetEventType();
749 int pos = event.GetPosition();
750 int orient = event.GetOrientation();
751
752 // calculate scroll increment
753 int scrollinc = 0;
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 {
778 if (orient == wxHORIZONTAL)
779 {
780 scrollinc = pos - m_thumbX;
781 m_thumbX = pos;
782 }
783 else
784 {
785 scrollinc = pos - m_thumbY;
786 m_thumbY = pos;
787 }
788 break;
789 }
790 case wxEVT_SCROLLWIN_THUMBRELEASE:
791 {
792 m_thumbX = m_viewportW;
793 m_thumbY = m_viewportH;
794 }
795 }
796
797 #ifdef __WXGTK__ // what about Motif?
798 // wxGTK updates the thumb automatically (wxMSW doesn't); reset it back
799 if (type != wxEVT_SCROLLWIN_THUMBTRACK)
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
821 void LifeCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
822 {
823 // do nothing. I just don't want the background to be erased, you know.
824 }