1 /////////////////////////////////////////////////////////////////////////////
3 // Purpose: The game of Life, created by J. H. Conway
4 // Author: Guillermo Rodriguez Garcia, <guille@iies.es>
8 // Copyright: (c) 2000, Guillermo Rodriguez Garcia
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ==========================================================================
13 // headers, declarations, constants
14 // ==========================================================================
17 #pragma implementation "life.h"
20 // For compilers that support precompilation, includes "wx/wx.h".
21 #include "wx/wxprec.h"
31 #include "wx/statline.h"
37 // --------------------------------------------------------------------------
39 // --------------------------------------------------------------------------
41 #if defined(__WXGTK__) || defined(__WXMOTIF__)
43 #include "mondrian.xpm"
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"
53 #include "bitmaps/north.xpm"
54 #include "bitmaps/south.xpm"
55 #include "bitmaps/east.xpm"
56 #include "bitmaps/west.xpm"
57 #include "bitmaps/center.xpm"
60 // --------------------------------------------------------------------------
62 // --------------------------------------------------------------------------
64 // IDs for the controls and the menu commands
70 // menu items and toolbar buttons
82 // speed selection slider
95 // --------------------------------------------------------------------------
96 // event tables and other macros for wxWindows
97 // --------------------------------------------------------------------------
100 BEGIN_EVENT_TABLE(LifeFrame
, wxFrame
)
101 EVT_MENU (ID_SAMPLES
, LifeFrame
::OnSamples
)
102 EVT_MENU (ID_RESET
, LifeFrame
::OnMenu
)
103 EVT_MENU (ID_ABOUT
, LifeFrame
::OnMenu
)
104 EVT_MENU (ID_EXIT
, LifeFrame
::OnMenu
)
105 EVT_MENU (ID_START
, LifeFrame
::OnMenu
)
106 EVT_MENU (ID_STEP
, LifeFrame
::OnMenu
)
107 EVT_MENU (ID_STOP
, LifeFrame
::OnMenu
)
108 EVT_MENU (ID_TOPSPEED
, LifeFrame
::OnMenu
)
109 EVT_MENU (ID_SHOWNAV
, LifeFrame
::OnMenu
)
110 EVT_MENU (ID_ORIGIN
, LifeFrame
::OnNavigate
)
111 EVT_BUTTON (ID_CENTER
, LifeFrame
::OnNavigate
)
112 EVT_BUTTON (ID_NORTH
, LifeFrame
::OnNavigate
)
113 EVT_BUTTON (ID_SOUTH
, LifeFrame
::OnNavigate
)
114 EVT_BUTTON (ID_EAST
, LifeFrame
::OnNavigate
)
115 EVT_BUTTON (ID_WEST
, LifeFrame
::OnNavigate
)
116 EVT_MENU (ID_ZOOMIN
, LifeFrame
::OnZoom
)
117 EVT_MENU (ID_ZOOMOUT
, LifeFrame
::OnZoom
)
118 EVT_COMMAND_SCROLL (ID_SLIDER
, LifeFrame
::OnSlider
)
119 EVT_TIMER (ID_TIMER
, LifeFrame
::OnTimer
)
120 EVT_CLOSE ( LifeFrame
::OnClose
)
123 BEGIN_EVENT_TABLE(LifeNavigator
, wxMiniFrame
)
124 EVT_CLOSE ( LifeNavigator
::OnClose
)
127 BEGIN_EVENT_TABLE(LifeCanvas
, wxWindow
)
128 EVT_PAINT ( LifeCanvas
::OnPaint
)
129 EVT_SCROLLWIN ( LifeCanvas
::OnScroll
)
130 EVT_SIZE ( LifeCanvas
::OnSize
)
131 EVT_MOUSE_EVENTS ( LifeCanvas
::OnMouse
)
132 EVT_ERASE_BACKGROUND( LifeCanvas
::OnEraseBackground
)
136 // Create a new application object
137 IMPLEMENT_APP(LifeApp
)
140 // ==========================================================================
142 // ==========================================================================
145 #define ADD_TOOL(id, bmp, tooltip, help) \
146 toolBar->AddTool(id, bmp, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, tooltip, help)
149 // --------------------------------------------------------------------------
151 // --------------------------------------------------------------------------
153 // 'Main program' equivalent: the program execution "starts" here
154 bool LifeApp
::OnInit()
156 // create the main application window
157 LifeFrame
*frame
= new LifeFrame();
159 // show it and tell the application that it's our main window
165 frame
->UpdateInfoText();
168 // enter the main message loop and run the app
172 // --------------------------------------------------------------------------
174 // --------------------------------------------------------------------------
177 LifeFrame
::LifeFrame() : wxFrame((wxFrame
*)0, -1, _("Life!"), wxPoint(200, 200))
180 SetIcon(wxICON(mondrian
));
183 wxMenu
*menuFile
= new wxMenu("", wxMENU_TEAROFF
);
184 wxMenu
*menuView
= new wxMenu("", wxMENU_TEAROFF
);
185 wxMenu
*menuGame
= new wxMenu("", wxMENU_TEAROFF
);
187 menuFile
->Append(ID_RESET
, _("Reset"), _("Start a new game"));
188 menuFile
->Append(ID_SAMPLES
, _("Sample game..."), _("Select a sample configuration"));
189 menuFile
->AppendSeparator();
190 menuFile
->Append(ID_ABOUT
, _("&About...\tCtrl-A"), _("Show about dialog"));
191 menuFile
->AppendSeparator();
192 menuFile
->Append(ID_EXIT
, _("E&xit\tAlt-X"), _("Quit this program"));
194 menuView
->Append(ID_SHOWNAV
, _("Navigation toolbox"), _("Show or hide toolbox"), TRUE
);
195 menuView
->Check (ID_SHOWNAV
, TRUE
);
196 menuView
->AppendSeparator();
197 menuView
->Append(ID_ORIGIN
, _("Absolute origin"), _("Go to (0, 0)"));
198 menuView
->Append(ID_CENTER
, _("Center of mass"), _("Find center of mass"));
199 menuView
->Append(ID_NORTH
, _("North"), _("Find northernmost cell"));
200 menuView
->Append(ID_SOUTH
, _("South"), _("Find southernmost cell"));
201 menuView
->Append(ID_EAST
, _("East"), _("Find easternmost cell"));
202 menuView
->Append(ID_WEST
, _("West"), _("Find westernmost cell"));
203 menuView
->AppendSeparator();
204 menuView
->Append(ID_ZOOMIN
, _("Zoom &in\tCtrl-I"));
205 menuView
->Append(ID_ZOOMOUT
, _("Zoom &out\tCtrl-O"));
207 menuGame
->Append(ID_START
, _("&Start\tCtrl-S"), _("Start"));
208 menuGame
->Append(ID_STEP
, _("&Next\tCtrl-N"), _("Single step"));
209 menuGame
->Append(ID_STOP
, _("S&top\tCtrl-T"), _("Stop"));
210 menuGame
->Enable(ID_STOP
, FALSE
);
211 menuGame
->AppendSeparator();
212 menuGame
->Append(ID_TOPSPEED
, _("Top speed!"), _("Go as fast as possible"));
214 wxMenuBar
*menuBar
= new wxMenuBar();
215 menuBar
->Append(menuFile
, _("&File"));
216 menuBar
->Append(menuView
, _("&View"));
217 menuBar
->Append(menuGame
, _("&Game"));
221 wxBitmap tbBitmaps
[5];
223 tbBitmaps
[0] = wxBITMAP(reset
);
224 tbBitmaps
[1] = wxBITMAP(play
);
225 tbBitmaps
[2] = wxBITMAP(stop
);
226 tbBitmaps
[3] = wxBITMAP(zoomin
);
227 tbBitmaps
[4] = wxBITMAP(zoomout
);
229 wxToolBar
*toolBar
= CreateToolBar();
230 toolBar
->SetMargins(5, 5);
231 toolBar
->SetToolBitmapSize(wxSize(16, 16));
232 ADD_TOOL(ID_RESET
, tbBitmaps
[0], _("Reset"), _("Start a new game"));
233 ADD_TOOL(ID_START
, tbBitmaps
[1], _("Start"), _("Start"));
234 ADD_TOOL(ID_STOP
, tbBitmaps
[2], _("Stop"), _("Stop"));
235 toolBar
->AddSeparator();
236 ADD_TOOL(ID_ZOOMIN
, tbBitmaps
[3], _("Zoom in"), _("Zoom in"));
237 ADD_TOOL(ID_ZOOMOUT
, tbBitmaps
[4], _("Zoom out"), _("Zoom out"));
239 toolBar
->EnableTool(ID_STOP
, FALSE
); // must be after Realize() !
243 SetStatusText(_("Welcome to Life!"));
247 m_timer
= new wxTimer(this, ID_TIMER
);
253 // We use two different panels to reduce flicker in wxGTK, because
254 // some widgets (like wxStaticText) don't have their own X11 window,
255 // and thus updating the text would result in a refresh of the canvas
256 // if they belong to the same parent.
258 wxPanel
*panel1
= new wxPanel(this, -1);
259 wxPanel
*panel2
= new wxPanel(this, -1);
262 m_canvas
= new LifeCanvas(panel1
, m_life
);
265 m_text
= new wxStaticText(panel2
, -1,
269 wxALIGN_CENTER
| wxST_NO_AUTORESIZE
);
271 wxSlider
*slider
= new wxSlider(panel2
, ID_SLIDER
,
275 wxSL_HORIZONTAL
| wxSL_AUTOTICKS
);
280 wxBoxSizer
*sizer1
= new wxBoxSizer(wxVERTICAL
);
281 wxBoxSizer
*sizer2
= new wxBoxSizer(wxVERTICAL
);
282 wxBoxSizer
*sizer3
= new wxBoxSizer(wxVERTICAL
);
284 sizer1
->Add( new wxStaticLine(panel1
, -1), 0, wxGROW
);
285 sizer1
->Add( m_canvas
, 1, wxGROW
| wxALL
, 2 );
286 sizer1
->Add( new wxStaticLine(panel1
, -1), 0, wxGROW
);
287 panel1
->SetSizer( sizer1
);
288 panel1
->SetAutoLayout( TRUE
);
289 sizer1
->Fit( panel1
);
291 sizer2
->Add( m_text
, 0, wxGROW
| wxTOP
, 4 );
292 sizer2
->Add( slider
, 0, wxCENTRE
| wxALL
, 4 );
294 panel2
->SetSizer( sizer2
);
295 panel2
->SetAutoLayout( TRUE
);
296 sizer2
->Fit( panel2
);
298 sizer3
->Add( panel1
, 1, wxGROW
);
299 sizer3
->Add( panel2
, 0, wxGROW
);
301 SetAutoLayout( TRUE
);
304 // set minimum frame size
305 sizer3
->SetSizeHints( this );
308 m_navigator
= new LifeNavigator(this);
311 LifeFrame
::~LifeFrame()
316 void LifeFrame
::UpdateInfoText()
320 msg
.Printf(_(" Generation: %u (T: %u ms), Population: %u "),
322 m_topspeed?
0 : m_interval
,
323 m_life
->GetNumCells());
324 m_text
->SetLabel(msg
);
327 // Enable or disable tools and menu entries according to the current
328 // state. See also wxEVT_UPDATE_UI events for a slightly different
330 void LifeFrame
::UpdateUI()
333 GetToolBar()->EnableTool(ID_START
, !m_running
);
334 GetToolBar()->EnableTool(ID_STOP
, m_running
);
335 GetMenuBar()->GetMenu(2)->Enable(ID_START
, !m_running
);
336 GetMenuBar()->GetMenu(2)->Enable(ID_STEP
, !m_running
);
337 GetMenuBar()->GetMenu(2)->Enable(ID_STOP
, m_running
);
340 int cellsize
= m_canvas
->GetCellSize();
341 GetToolBar()->EnableTool(ID_ZOOMIN
, cellsize
< 32);
342 GetToolBar()->EnableTool(ID_ZOOMOUT
, cellsize
> 1);
343 GetMenuBar()->GetMenu(1)->Enable(ID_ZOOMIN
, cellsize
< 32);
344 GetMenuBar()->GetMenu(1)->Enable(ID_ZOOMOUT
, cellsize
> 1);
348 void LifeFrame
::OnMenu(wxCommandEvent
& event
)
350 switch (event
.GetId())
352 case ID_START
: OnStart(); break;
353 case ID_STEP
: OnStep(); break;
354 case ID_STOP
: OnStop(); break;
357 bool checked
= GetMenuBar()->GetMenu(1)->IsChecked(ID_SHOWNAV
);
358 m_navigator
->Show(checked
);
366 while (m_running
&& m_topspeed
)
375 // stop if it was running
378 m_canvas
->Recenter(0, 0);
385 LifeAboutDialog
dialog(this);
391 // TRUE is to force the frame to close
398 void LifeFrame
::OnClose(wxCloseEvent
& WXUNUSED(event
))
400 // Stop if it was running; this is absolutely needed because
401 // the frame won't be actually destroyed until there are no
402 // more pending events, and this in turn won't ever happen
403 // if the timer is running faster than the window can redraw.
408 void LifeFrame
::OnSamples(wxCommandEvent
& WXUNUSED(event
))
410 // stop if it was running
414 LifeSamplesDialog
dialog(this);
416 if (dialog
.ShowModal() == wxID_OK
)
418 const LifeShape shape
= dialog
.GetShape();
422 m_life
->SetShape(shape
);
425 m_canvas
->Recenter(0, 0);
431 void LifeFrame
::OnZoom(wxCommandEvent
& event
)
433 int cellsize
= m_canvas
->GetCellSize();
435 if ((event
.GetId() == ID_ZOOMIN
) && cellsize
< 32)
437 m_canvas
->SetCellSize(cellsize
* 2);
440 else if ((event
.GetId() == ID_ZOOMOUT
) && cellsize
> 1)
442 m_canvas
->SetCellSize(cellsize
/ 2);
447 void LifeFrame
::OnNavigate(wxCommandEvent
& event
)
451 switch (event
.GetId())
453 case ID_NORTH
: c
= m_life
->FindNorth(); break;
454 case ID_SOUTH
: c
= m_life
->FindSouth(); break;
455 case ID_WEST
: c
= m_life
->FindWest(); break;
456 case ID_EAST
: c
= m_life
->FindEast(); break;
457 case ID_CENTER
: c
= m_life
->FindCenter(); break;
458 case ID_ORIGIN
: c
.i
= c
.j
= 0; break;
461 m_canvas
->Recenter(c
.i
, c
.j
);
464 void LifeFrame
::OnSlider(wxScrollEvent
& event
)
466 m_interval
= event
.GetPosition() * 100;
477 void LifeFrame
::OnTimer(wxTimerEvent
& WXUNUSED(event
))
482 void LifeFrame
::OnStart()
486 m_timer
->Start(m_interval
);
492 void LifeFrame
::OnStop()
503 void LifeFrame
::OnStep()
505 if (m_life
->NextTic())
510 m_canvas
->DrawChanged();
515 // --------------------------------------------------------------------------
516 // LifeNavigator miniframe
517 // --------------------------------------------------------------------------
519 LifeNavigator
::LifeNavigator(wxWindow
*parent
)
520 : wxMiniFrame(parent
, -1,
524 wxCAPTION
| wxSIMPLE_BORDER
)
526 wxPanel
*panel
= new wxPanel(this, -1);
527 wxBoxSizer
*sizer1
= new wxBoxSizer(wxVERTICAL
);
528 wxBoxSizer
*sizer2
= new wxBoxSizer(wxHORIZONTAL
);
530 // create bitmaps and masks for the buttons
532 bmpn
= wxBITMAP(north
),
533 bmpw
= wxBITMAP(west
),
534 bmpc
= wxBITMAP(center
),
535 bmpe
= wxBITMAP(east
),
536 bmps
= wxBITMAP(south
);
538 #if !defined(__WXGTK__) && !defined(__WXMOTIF__)
539 bmpn
.SetMask(new wxMask(bmpn
, *wxLIGHT_GREY
));
540 bmpw
.SetMask(new wxMask(bmpw
, *wxLIGHT_GREY
));
541 bmpc
.SetMask(new wxMask(bmpc
, *wxLIGHT_GREY
));
542 bmpe
.SetMask(new wxMask(bmpe
, *wxLIGHT_GREY
));
543 bmps
.SetMask(new wxMask(bmps
, *wxLIGHT_GREY
));
546 // create the buttons and attach tooltips to them
548 *bn
= new wxBitmapButton(panel
, ID_NORTH
, bmpn
),
549 *bw
= new wxBitmapButton(panel
, ID_WEST
, bmpw
),
550 *bc
= new wxBitmapButton(panel
, ID_CENTER
, bmpc
),
551 *be
= new wxBitmapButton(panel
, ID_EAST
, bmpe
),
552 *bs
= new wxBitmapButton(panel
, ID_SOUTH
, bmps
);
555 bn
->SetToolTip(_("Find northernmost cell"));
556 bw
->SetToolTip(_("Find westernmost cell"));
557 bc
->SetToolTip(_("Find center of mass"));
558 be
->SetToolTip(_("Find easternmost cell"));
559 bs
->SetToolTip(_("Find southernmost cell"));
562 // add buttons to sizers
563 sizer2
->Add( bw
, 0, wxCENTRE
| wxWEST
, 4 );
564 sizer2
->Add( bc
, 0, wxCENTRE
);
565 sizer2
->Add( be
, 0, wxCENTRE
| wxEAST
, 4 );
566 sizer1
->Add( bn
, 0, wxCENTRE
| wxNORTH
, 4 );
567 sizer1
->Add( sizer2
);
568 sizer1
->Add( bs
, 0, wxCENTRE
| wxSOUTH
, 4 );
570 // set the miniframe size
571 panel
->SetSizer(sizer1
);
572 panel
->SetAutoLayout(TRUE
);
574 sizer1
->SetSizeHints(this);
576 // move it to a sensible position
577 wxRect parentRect
= parent
->GetRect();
578 wxSize childSize
= GetSize();
579 int x
= parentRect
.GetX() +
580 parentRect
.GetWidth();
581 int y
= parentRect
.GetY() +
582 (parentRect
.GetHeight() - childSize
.GetHeight()) / 4;
589 void LifeNavigator
::OnClose(wxCloseEvent
& event
)
599 // --------------------------------------------------------------------------
601 // --------------------------------------------------------------------------
603 // canvas constructor
604 LifeCanvas
::LifeCanvas(wxWindow
*parent
, Life
*life
, bool interactive
)
605 : wxWindow(parent
, -1, wxPoint(0, 0), wxSize(100, 100),
609 m_interactive
= interactive
;
611 m_status
= MOUSE_NOACTION
;
618 SetCursor(*wxCROSS_CURSOR
);
620 // reduce flicker if wxEVT_ERASE_BACKGROUND is not available
621 SetBackgroundColour(*wxWHITE
);
624 LifeCanvas
::~LifeCanvas()
629 // recenter at the given position
630 void LifeCanvas
::Recenter(wxInt32 i
, wxInt32 j
)
632 m_viewportX
= i
- m_viewportW
/ 2;
633 m_viewportY
= j
- m_viewportH
/ 2;
639 // set the cell size and refresh display
640 void LifeCanvas
::SetCellSize(int cellsize
)
642 m_cellsize
= cellsize
;
644 // find current center
645 wxInt32 cx
= m_viewportX
+ m_viewportW
/ 2;
646 wxInt32 cy
= m_viewportY
+ m_viewportH
/ 2;
648 // get current canvas size and adjust viewport accordingly
650 GetClientSize(&w
, &h
);
651 m_viewportW
= (w
+ m_cellsize
- 1) / m_cellsize
;
652 m_viewportH
= (h
+ m_cellsize
- 1) / m_cellsize
;
655 m_viewportX
= cx
- m_viewportW
/ 2;
656 m_viewportY
= cy
- m_viewportH
/ 2;
661 SetScrollbar(wxHORIZONTAL
, m_viewportW
, m_viewportW
, 3 * m_viewportW
);
662 SetScrollbar(wxVERTICAL
, m_viewportH
, m_viewportH
, 3 * m_viewportH
);
663 m_thumbX
= m_viewportW
;
664 m_thumbY
= m_viewportH
;
671 void LifeCanvas
::DrawCell(wxInt32 i
, wxInt32 j
, bool alive
)
675 dc
.SetPen(alive?
*wxBLACK_PEN
: *wxWHITE_PEN
);
676 dc
.SetBrush(alive?
*wxBLACK_BRUSH
: *wxWHITE_BRUSH
);
683 void LifeCanvas
::DrawCell(wxInt32 i
, wxInt32 j
, wxDC
&dc
)
685 wxCoord x
= CellToX(i
);
686 wxCoord y
= CellToY(j
);
688 // if cellsize is 1 or 2, there will be no grid
695 dc
.DrawRectangle(x
, y
, 2, 2);
698 dc
.DrawRectangle(x
+ 1, y
+ 1, m_cellsize
- 1, m_cellsize
- 1);
702 // draw all changed cells
703 void LifeCanvas
::DrawChanged()
711 m_life
->BeginFind(m_viewportX
,
713 m_viewportX
+ m_viewportW
,
714 m_viewportY
+ m_viewportH
,
721 dc
.SetPen(*wxBLACK_PEN
);
725 dc
.SetPen(*wxTRANSPARENT_PEN
);
726 dc
.SetBrush(*wxBLACK_BRUSH
);
728 dc
.SetLogicalFunction(wxINVERT
);
732 done
= m_life
->FindMore(&cells
, &ncells
);
734 for (size_t m
= 0; m
< ncells
; m
++)
735 DrawCell(cells
[m
].i
, cells
[m
].j
, dc
);
741 void LifeCanvas
::OnPaint(wxPaintEvent
& event
)
744 wxRect rect
= GetUpdateRegion().GetBox();
746 wxInt32 i0
, j0
, i1
, j1
;
752 h
= rect
.GetHeight();
756 i1
= XToCell(x
+ w
- 1);
757 j1
= YToCell(y
+ h
- 1);
763 m_life
->BeginFind(i0
, j0
, i1
, j1
, FALSE
);
764 done
= m_life
->FindMore(&cells
, &ncells
);
766 // erase all damaged cells and draw the grid
768 dc
.SetBrush(*wxWHITE_BRUSH
);
773 dc
.SetPen(*wxWHITE_PEN
);
774 dc
.DrawRectangle(x
, y
, w
, h
);
780 w
= CellToX(i1
+ 1) - x
+ 1;
781 h
= CellToY(j1
+ 1) - y
+ 1;
783 dc
.SetPen(*wxLIGHT_GREY_PEN
);
784 for (wxInt32 yy
= y
; yy
<= (y
+ h
- m_cellsize
); yy
+= m_cellsize
)
785 dc
.DrawRectangle(x
, yy
, w
, m_cellsize
+ 1);
786 for (wxInt32 xx
= x
; xx
<= (x
+ w
- m_cellsize
); xx
+= m_cellsize
)
787 dc
.DrawLine(xx
, y
, xx
, y
+ h
);
790 // draw all alive cells
791 dc
.SetPen(*wxBLACK_PEN
);
792 dc
.SetBrush(*wxBLACK_BRUSH
);
796 for (size_t m
= 0; m
< ncells
; m
++)
797 DrawCell(cells
[m
].i
, cells
[m
].j
, dc
);
799 done
= m_life
->FindMore(&cells
, &ncells
);
803 for (size_t m
= 0; m
< ncells
; m
++)
804 DrawCell(cells
[m
].i
, cells
[m
].j
, dc
);
809 void LifeCanvas
::OnMouse(wxMouseEvent
& event
)
814 // which cell are we pointing at?
815 wxInt32 i
= XToCell( event
.GetX() );
816 wxInt32 j
= YToCell( event
.GetY() );
818 // set statusbar text
820 msg
.Printf(_("Cell: (%d, %d)"), i
, j
);
821 ((LifeFrame
*) wxGetApp().GetTopWindow())->SetStatusText(msg
, 1);
824 if (!event
.LeftIsDown())
826 m_status
= MOUSE_NOACTION
;
830 // button just pressed?
831 if (m_status
== MOUSE_NOACTION
)
833 // yes, update status and toggle this cell
834 m_status
= (m_life
->IsAlive(i
, j
)? MOUSE_ERASING
: MOUSE_DRAWING
);
838 m_life
->SetCell(i
, j
, m_status
== MOUSE_DRAWING
);
839 DrawCell(i
, j
, m_status
== MOUSE_DRAWING
);
841 else if ((m_mi
!= i
) || (m_mj
!= j
))
843 bool alive
= (m_status
== MOUSE_DRAWING
);
845 // prepare DC and pen + brush to optimize drawing
847 dc
.SetPen(alive?
*wxBLACK_PEN
: *wxWHITE_PEN
);
848 dc
.SetBrush(alive?
*wxBLACK_BRUSH
: *wxWHITE_BRUSH
);
851 // draw a line of cells using Bresenham's algorithm
852 wxInt32 d
, ii
, jj
, di
, ai
, si
, dj
, aj
, sj
;
855 si
= (di
< 0)?
-1 : 1;
858 sj
= (dj
< 0)?
-1 : 1;
870 m_life
->SetCell(ii
, jj
, alive
);
871 DrawCell(ii
, jj
, dc
);
888 m_life
->SetCell(ii
, jj
, alive
);
889 DrawCell(ii
, jj
, dc
);
901 m_life
->SetCell(ii
, jj
, alive
);
902 DrawCell(ii
, jj
, dc
);
909 ((LifeFrame
*) wxGetApp().GetTopWindow())->UpdateInfoText();
912 void LifeCanvas
::OnSize(wxSizeEvent
& event
)
915 wxInt32 cx
= m_viewportX
+ m_viewportW
/ 2;
916 wxInt32 cy
= m_viewportY
+ m_viewportH
/ 2;
919 wxCoord w
= event
.GetSize().GetX();
920 wxCoord h
= event
.GetSize().GetY();
921 m_viewportW
= (w
+ m_cellsize
- 1) / m_cellsize
;
922 m_viewportH
= (h
+ m_cellsize
- 1) / m_cellsize
;
925 m_viewportX
= cx
- m_viewportW
/ 2;
926 m_viewportY
= cy
- m_viewportH
/ 2;
931 SetScrollbar(wxHORIZONTAL
, m_viewportW
, m_viewportW
, 3 * m_viewportW
);
932 SetScrollbar(wxVERTICAL
, m_viewportH
, m_viewportH
, 3 * m_viewportH
);
933 m_thumbX
= m_viewportW
;
934 m_thumbY
= m_viewportH
;
937 // allow default processing
941 void LifeCanvas
::OnScroll(wxScrollWinEvent
& event
)
943 WXTYPE type
= event
.GetEventType();
944 int pos
= event
.GetPosition();
945 int orient
= event
.GetOrientation();
947 // calculate scroll increment
951 case wxEVT_SCROLLWIN_TOP
:
953 if (orient
== wxHORIZONTAL
)
954 scrollinc
= -m_viewportW
;
956 scrollinc
= -m_viewportH
;
959 case wxEVT_SCROLLWIN_BOTTOM
:
961 if (orient
== wxHORIZONTAL
)
962 scrollinc
= m_viewportW
;
964 scrollinc
= m_viewportH
;
967 case wxEVT_SCROLLWIN_LINEUP
: scrollinc
= -1; break;
968 case wxEVT_SCROLLWIN_LINEDOWN
: scrollinc
= +1; break;
969 case wxEVT_SCROLLWIN_PAGEUP
: scrollinc
= -10; break;
970 case wxEVT_SCROLLWIN_PAGEDOWN
: scrollinc
= +10; break;
971 case wxEVT_SCROLLWIN_THUMBTRACK
:
973 if (orient
== wxHORIZONTAL
)
975 scrollinc
= pos
- m_thumbX
;
980 scrollinc
= pos
- m_thumbY
;
985 case wxEVT_SCROLLWIN_THUMBRELEASE
:
987 m_thumbX
= m_viewportW
;
988 m_thumbY
= m_viewportH
;
992 #if defined(__WXGTK__) || defined(__WXMOTIF__)
993 // wxGTK and wxMotif update the thumb automatically (wxMSW doesn't);
994 // so reset it back as we always want it to be in the same position.
995 if (type
!= wxEVT_SCROLLWIN_THUMBTRACK
)
997 SetScrollbar(wxHORIZONTAL
, m_viewportW
, m_viewportW
, 3 * m_viewportW
);
998 SetScrollbar(wxVERTICAL
, m_viewportH
, m_viewportH
, 3 * m_viewportH
);
1002 if (scrollinc
== 0) return;
1004 // scroll the window and adjust the viewport
1005 if (orient
== wxHORIZONTAL
)
1007 m_viewportX
+= scrollinc
;
1008 ScrollWindow( -m_cellsize
* scrollinc
, 0, (const wxRect
*) NULL
);
1012 m_viewportY
+= scrollinc
;
1013 ScrollWindow( 0, -m_cellsize
* scrollinc
, (const wxRect
*) NULL
);
1017 void LifeCanvas
::OnEraseBackground(wxEraseEvent
& WXUNUSED(event
))
1019 // do nothing. I just don't want the background to be erased, you know.