Life! version 2
[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, 5);
213 sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE);
214 sizer->Add(m_text, 0, wxCENTRE | wxTOP, 5);
215 sizer->Add(slider, 0, wxCENTRE | wxALL, 5);
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 GetToolBar()->EnableTool(ID_START, !m_running);
244 GetToolBar()->EnableTool(ID_STOP, m_running);
245 GetMenuBar()->GetMenu(1)->Enable(ID_START, !m_running);
246 GetMenuBar()->GetMenu(1)->Enable(ID_STEP, !m_running);
247 GetMenuBar()->GetMenu(1)->Enable(ID_STOP, m_running);
248 }
249
250 // event handlers
251 void LifeFrame::OnMenu(wxCommandEvent& event)
252 {
253 switch (event.GetId())
254 {
255 case ID_CENTER : m_canvas->Recenter(0, 0); break;
256 case ID_START : OnStart(); break;
257 case ID_STEP : OnTimer(); break;
258 case ID_STOP : OnStop(); break;
259 case ID_ZOOMIN :
260 {
261 int cellsize = m_canvas->GetCellSize();
262 if (cellsize < 32)
263 m_canvas->SetCellSize(cellsize * 2);
264 break;
265 }
266 case ID_ZOOMOUT :
267 {
268 int cellsize = m_canvas->GetCellSize();
269 if (cellsize > 1)
270 m_canvas->SetCellSize(cellsize / 2);
271 break;
272 }
273 case ID_TOPSPEED:
274 {
275 m_running = TRUE;
276 m_topspeed = TRUE;
277 UpdateUI();
278 while (m_running && m_topspeed)
279 {
280 OnTimer();
281 wxYield();
282 }
283 break;
284 }
285 case ID_RESET:
286 {
287 // stop if it was running
288 OnStop();
289 m_life->Clear();
290 m_canvas->Recenter(0, 0);
291 m_tics = 0;
292 UpdateInfoText();
293 break;
294 }
295 case ID_ABOUT:
296 {
297 LifeAboutDialog dialog(this);
298 dialog.ShowModal();
299 break;
300 }
301 case ID_EXIT :
302 {
303 // TRUE is to force the frame to close
304 Close(TRUE);
305 break;
306 }
307 }
308 }
309
310 void LifeFrame::OnClose(wxCloseEvent& WXUNUSED(event))
311 {
312 // Stop if it was running; this is absolutely needed because
313 // the frame won't be actually destroyed until there are no
314 // more pending events, and this in turn won't ever happen
315 // if the timer is running faster than the window can redraw.
316 OnStop();
317 Destroy();
318 }
319
320 void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event))
321 {
322 // stop if it was running
323 OnStop();
324
325 // dialog box
326 LifeSamplesDialog dialog(this);
327
328 // new game?
329 if (dialog.ShowModal() == wxID_OK)
330 {
331 const LifeShape shape = dialog.GetShape();
332
333 // put the shape
334 m_life->Clear();
335 m_life->SetShape(shape);
336
337 // recenter canvas
338 m_canvas->Recenter(0, 0);
339 m_tics = 0;
340 UpdateInfoText();
341 }
342 }
343
344 void LifeFrame::OnStart()
345 {
346 if (!m_running)
347 {
348 m_timer->Start(m_interval);
349 m_running = TRUE;
350 UpdateUI();
351 }
352 }
353
354 void LifeFrame::OnStop()
355 {
356 if (m_running)
357 {
358 m_timer->Stop();
359 m_running = FALSE;
360 m_topspeed = FALSE;
361 UpdateUI();
362 }
363 }
364
365 void LifeFrame::OnTimer()
366 {
367 if (m_life->NextTic())
368 m_tics++;
369 else
370 OnStop();
371
372 m_canvas->DrawChanged();
373 UpdateInfoText();
374 }
375
376 void LifeFrame::OnSlider(wxScrollEvent& event)
377 {
378 m_interval = event.GetPosition() * 100;
379
380 if (m_running)
381 {
382 OnStop();
383 OnStart();
384 }
385
386 UpdateInfoText();
387 }
388
389 // --------------------------------------------------------------------------
390 // LifeTimer
391 // --------------------------------------------------------------------------
392
393 void LifeTimer::Notify()
394 {
395 GET_FRAME()->OnTimer();
396 };
397
398 // --------------------------------------------------------------------------
399 // LifeCanvas
400 // --------------------------------------------------------------------------
401
402 // canvas constructor
403 LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive)
404 : wxWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100),
405 wxSUNKEN_BORDER)
406 {
407 m_life = life;
408 m_interactive = interactive;
409 m_cellsize = 8;
410 m_status = MOUSE_NOACTION;
411 m_viewportX = 0;
412 m_viewportY = 0;
413 m_viewportH = 0;
414 m_viewportW = 0;
415
416 if (m_interactive)
417 SetCursor(*wxCROSS_CURSOR);
418
419 // reduce flicker if wxEVT_ERASE_BACKGROUND is not available
420 SetBackgroundColour(*wxWHITE);
421 }
422
423 LifeCanvas::~LifeCanvas()
424 {
425 delete m_life;
426 }
427
428 // recenter at the given position
429 void LifeCanvas::Recenter(wxInt32 i, wxInt32 j)
430 {
431 m_viewportX = i - m_viewportW / 2;
432 m_viewportY = j - m_viewportH / 2;
433
434 // redraw everything
435 Refresh(FALSE);
436 }
437
438 // set the cell size and refresh display
439 void LifeCanvas::SetCellSize(int cellsize)
440 {
441 m_cellsize = cellsize;
442
443 // find current center
444 wxInt32 cx = m_viewportX + m_viewportW / 2;
445 wxInt32 cy = m_viewportY + m_viewportH / 2;
446
447 // get current canvas size and adjust viewport accordingly
448 wxCoord w, h;
449 GetClientSize(&w, &h);
450 m_viewportW = (w + m_cellsize - 1) / m_cellsize;
451 m_viewportH = (h + m_cellsize - 1) / m_cellsize;
452
453 // recenter
454 m_viewportX = cx - m_viewportW / 2;
455 m_viewportY = cy - m_viewportH / 2;
456
457 // adjust scrollbars
458 if (m_interactive)
459 {
460 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
461 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
462 m_thumbX = m_viewportW;
463 m_thumbY = m_viewportH;
464 }
465
466 Refresh(FALSE);
467 }
468
469 // draw a cell
470 void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, bool alive)
471 {
472 wxClientDC dc(this);
473
474 dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
475 dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
476
477 dc.BeginDrawing();
478 DrawCell(i, j, dc);
479 dc.EndDrawing();
480 }
481
482 void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, wxDC &dc)
483 {
484 wxCoord x = CellToX(i);
485 wxCoord y = CellToY(j);
486
487 // if cellsize is 1 or 2, there will be no grid
488 switch (m_cellsize)
489 {
490 case 1:
491 dc.DrawPoint(x, y);
492 break;
493 case 2:
494 dc.DrawRectangle(x, y, 2, 2);
495 break;
496 default:
497 dc.DrawRectangle(x + 1, y + 1, m_cellsize - 1, m_cellsize - 1);
498 }
499 }
500
501 // draw all changed cells
502 void LifeCanvas::DrawChanged()
503 {
504 wxClientDC dc(this);
505
506 size_t ncells;
507 Cell *cells;
508 bool done = FALSE;
509
510 m_life->BeginFind(m_viewportX,
511 m_viewportY,
512 m_viewportX + m_viewportW,
513 m_viewportY + m_viewportH,
514 TRUE);
515
516 dc.BeginDrawing();
517 dc.SetLogicalFunction(wxINVERT);
518
519 if (m_cellsize == 1)
520 {
521 // drawn using DrawPoint
522 dc.SetPen(*wxBLACK_PEN);
523 }
524 else
525 {
526 // drawn using DrawRectangle
527 dc.SetPen(*wxTRANSPARENT_PEN);
528 dc.SetBrush(*wxBLACK_BRUSH);
529 }
530
531 while (!done)
532 {
533 done = m_life->FindMore(&cells, &ncells);
534
535 for (size_t m = 0; m < ncells; m++)
536 DrawCell(cells[m].i, cells[m].j, dc);
537 }
538 dc.EndDrawing();
539 }
540
541 // event handlers
542 void LifeCanvas::OnPaint(wxPaintEvent& event)
543 {
544 wxPaintDC dc(this);
545 wxRect rect = GetUpdateRegion().GetBox();
546 wxCoord x, y, w, h;
547 wxInt32 i0, j0, i1, j1;
548
549 // find damaged area
550 x = rect.GetX();
551 y = rect.GetY();
552 w = rect.GetWidth();
553 h = rect.GetHeight();
554
555 i0 = XToCell(x);
556 j0 = YToCell(y);
557 i1 = XToCell(x + w - 1);
558 j1 = YToCell(y + h - 1);
559
560 size_t ncells;
561 Cell *cells;
562 bool done = FALSE;
563
564 m_life->BeginFind(i0, j0, i1, j1, FALSE);
565 done = m_life->FindMore(&cells, &ncells);
566
567 // erase all damaged cells and draw the grid
568 dc.BeginDrawing();
569 dc.SetBrush(*wxWHITE_BRUSH);
570
571 if (m_cellsize <= 2)
572 {
573 // no grid
574 dc.SetPen(*wxWHITE_PEN);
575 dc.DrawRectangle(x, y, w, h);
576 }
577 else
578 {
579 x = CellToX(i0);
580 y = CellToY(j0);
581 w = CellToX(i1 + 1) - x + 1;
582 h = CellToY(j1 + 1) - y + 1;
583
584 dc.SetPen(*wxLIGHT_GREY_PEN);
585 for (wxInt32 yy = y; yy <= (y + h - m_cellsize); yy += m_cellsize)
586 dc.DrawRectangle(x, yy, w, m_cellsize + 1);
587 for (wxInt32 xx = x; xx <= (x + w - m_cellsize); xx += m_cellsize)
588 dc.DrawLine(xx, y, xx, y + h);
589 }
590
591 // draw all alive cells
592 dc.SetPen(*wxBLACK_PEN);
593 dc.SetBrush(*wxBLACK_BRUSH);
594
595 while (!done)
596 {
597 for (size_t m = 0; m < ncells; m++)
598 DrawCell(cells[m].i, cells[m].j, dc);
599
600 done = m_life->FindMore(&cells, &ncells);
601 }
602
603 // last set
604 for (size_t m = 0; m < ncells; m++)
605 DrawCell(cells[m].i, cells[m].j, dc);
606
607 dc.EndDrawing();
608 }
609
610 void LifeCanvas::OnMouse(wxMouseEvent& event)
611 {
612 if (!m_interactive)
613 return;
614
615 // which cell are we pointing at?
616 wxInt32 i = XToCell( event.GetX() );
617 wxInt32 j = YToCell( event.GetY() );
618
619 // set statusbar text
620 wxString msg;
621 msg.Printf(_("Cell: (%d, %d)"), i, j);
622 GET_FRAME()->SetStatusText(msg, 1);
623
624 // button pressed?
625 if (!event.LeftIsDown())
626 {
627 m_status = MOUSE_NOACTION;
628 }
629 else
630 {
631 bool alive = m_life->IsAlive(i, j);
632
633 // if just pressed, update status
634 if (m_status == MOUSE_NOACTION)
635 m_status = (alive? MOUSE_ERASING : MOUSE_DRAWING);
636
637 // toggle cell and refresh if needed
638 if (((m_status == MOUSE_ERASING) && alive) ||
639 ((m_status == MOUSE_DRAWING) && !alive))
640 {
641 m_life->SetCell(i, j, !alive);
642 DrawCell(i, j, !alive);
643 GET_FRAME()->UpdateInfoText();
644 }
645 }
646 }
647
648 void LifeCanvas::OnSize(wxSizeEvent& event)
649 {
650 // find center
651 wxInt32 cx = m_viewportX + m_viewportW / 2;
652 wxInt32 cy = m_viewportY + m_viewportH / 2;
653
654 // get new size
655 wxCoord w = event.GetSize().GetX();
656 wxCoord h = event.GetSize().GetY();
657 m_viewportW = (w + m_cellsize - 1) / m_cellsize;
658 m_viewportH = (h + m_cellsize - 1) / m_cellsize;
659
660 // recenter
661 m_viewportX = cx - m_viewportW / 2;
662 m_viewportY = cy - m_viewportH / 2;
663
664 // scrollbars
665 if (m_interactive)
666 {
667 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
668 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
669 m_thumbX = m_viewportW;
670 m_thumbY = m_viewportH;
671 }
672
673 // allow default processing
674 event.Skip();
675 }
676
677 void LifeCanvas::OnScroll(wxScrollWinEvent& event)
678 {
679 WXTYPE type = event.GetEventType();
680 int pos = event.GetPosition();
681 int orient = event.GetOrientation();
682 bool scrolling = event.IsScrolling();
683 int scrollinc = 0;
684
685 // calculate scroll increment
686 switch (type)
687 {
688 case wxEVT_SCROLLWIN_TOP:
689 {
690 if (orient == wxHORIZONTAL)
691 scrollinc = -m_viewportW;
692 else
693 scrollinc = -m_viewportH;
694 break;
695 }
696 case wxEVT_SCROLLWIN_BOTTOM:
697 {
698 if (orient == wxHORIZONTAL)
699 scrollinc = m_viewportW;
700 else
701 scrollinc = m_viewportH;
702 break;
703 }
704 case wxEVT_SCROLLWIN_LINEUP: scrollinc = -1; break;
705 case wxEVT_SCROLLWIN_LINEDOWN: scrollinc = +1; break;
706 case wxEVT_SCROLLWIN_PAGEUP: scrollinc = -10; break;
707 case wxEVT_SCROLLWIN_PAGEDOWN: scrollinc = +10; break;
708 case wxEVT_SCROLLWIN_THUMBTRACK:
709 {
710 if (scrolling)
711 {
712 // user is dragging the thumb in the scrollbar
713 if (orient == wxHORIZONTAL)
714 {
715 scrollinc = pos - m_thumbX;
716 m_thumbX = pos;
717 }
718 else
719 {
720 scrollinc = pos - m_thumbY;
721 m_thumbY = pos;
722 }
723 }
724 else
725 {
726 // user released the thumb after dragging
727 m_thumbX = m_viewportW;
728 m_thumbY = m_viewportH;
729 }
730 break;
731 }
732 }
733
734 #ifdef __WXGTK__ // what about Motif?
735 // wxGTK updates the thumb automatically (wxMSW doesn't); reset it back
736 if ((type != wxEVT_SCROLLWIN_THUMBTRACK) || !scrolling)
737 {
738 SetScrollbar(wxHORIZONTAL, m_viewportW, m_viewportW, 3 * m_viewportW);
739 SetScrollbar(wxVERTICAL, m_viewportH, m_viewportH, 3 * m_viewportH);
740 }
741 #endif
742
743 if (scrollinc == 0) return;
744
745 // scroll the window and adjust the viewport
746 if (orient == wxHORIZONTAL)
747 {
748 m_viewportX += scrollinc;
749 ScrollWindow( -m_cellsize * scrollinc, 0, (const wxRect *) NULL);
750 }
751 else
752 {
753 m_viewportY += scrollinc;
754 ScrollWindow( 0, -m_cellsize * scrollinc, (const wxRect *) NULL);
755 }
756 }
757
758 void LifeCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
759 {
760 // do nothing. I just don't want the background to be erased, you know.
761 }