]>
Commit | Line | Data |
---|---|---|
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 | ||
2fa7c206 JS |
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 | ||
5a1dca12 | 31 | #include "wx/statline.h" |
2480be69 GRG |
32 | |
33 | #include "life.h" | |
34 | #include "game.h" | |
35 | #include "dialogs.h" | |
5a1dca12 GRG |
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" | |
e0a40292 GRG |
49 | #include "bitmaps/zoomin.xpm" |
50 | #include "bitmaps/zoomout.xpm" | |
5a1dca12 GRG |
51 | #endif |
52 | ||
5a1dca12 GRG |
53 | // -------------------------------------------------------------------------- |
54 | // constants | |
55 | // -------------------------------------------------------------------------- | |
56 | ||
57 | // IDs for the controls and the menu commands | |
58 | enum | |
59 | { | |
60 | // menu items and toolbar buttons | |
e0a40292 | 61 | ID_RESET = 1001, |
087e4f4a GRG |
62 | ID_SAMPLES, |
63 | ID_ABOUT, | |
64 | ID_EXIT, | |
e0a40292 | 65 | ID_CENTER, |
5a1dca12 | 66 | ID_START, |
087e4f4a | 67 | ID_STEP, |
5a1dca12 | 68 | ID_STOP, |
e0a40292 GRG |
69 | ID_ZOOMIN, |
70 | ID_ZOOMOUT, | |
71 | ID_TOPSPEED, | |
5a1dca12 | 72 | |
087e4f4a | 73 | // speed selection slider |
2480be69 | 74 | ID_SLIDER |
5a1dca12 GRG |
75 | }; |
76 | ||
77 | // -------------------------------------------------------------------------- | |
78 | // event tables and other macros for wxWindows | |
79 | // -------------------------------------------------------------------------- | |
80 | ||
81 | // Event tables | |
5a1dca12 | 82 | BEGIN_EVENT_TABLE(LifeFrame, wxFrame) |
e0a40292 GRG |
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) | |
ecbdd409 | 96 | END_EVENT_TABLE() |
5a1dca12 | 97 | |
e0a40292 GRG |
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) | |
ecbdd409 | 104 | END_EVENT_TABLE() |
5a1dca12 | 105 | |
5a1dca12 GRG |
106 | |
107 | // Create a new application object | |
108 | IMPLEMENT_APP(LifeApp) | |
109 | ||
e0a40292 | 110 | |
5a1dca12 GRG |
111 | // ========================================================================== |
112 | // implementation | |
113 | // ========================================================================== | |
114 | ||
2480be69 | 115 | // some shortcuts |
e0a40292 GRG |
116 | #define ADD_TOOL(id, bmp, tooltip, help) \ |
117 | toolBar->AddTool(id, bmp, wxNullBitmap, FALSE, -1, -1, (wxObject *)0, tooltip, help) | |
2480be69 | 118 | |
e0a40292 | 119 | #define GET_FRAME() ((LifeFrame *) wxGetApp().GetTopWindow()) |
2480be69 | 120 | |
5a1dca12 GRG |
121 | // -------------------------------------------------------------------------- |
122 | // LifeApp | |
123 | // -------------------------------------------------------------------------- | |
124 | ||
e0a40292 | 125 | // 'Main program' equivalent: the program execution "starts" here |
5a1dca12 GRG |
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); | |
087e4f4a | 151 | wxMenu *menuGame = new wxMenu("", wxMENU_TEAROFF); |
5a1dca12 | 152 | |
e0a40292 | 153 | menuFile->Append(ID_RESET, _("Reset"), _("Start a new game")); |
087e4f4a | 154 | menuFile->Append(ID_SAMPLES, _("Sample game..."), _("Select a sample configuration")); |
5a1dca12 GRG |
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")); | |
087e4f4a | 159 | |
e0a40292 | 160 | menuGame->Append(ID_CENTER, _("Re¢er\tCtrl-C"), _("Go to (0, 0)")); |
087e4f4a GRG |
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(); | |
e0a40292 GRG |
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")); | |
087e4f4a | 170 | |
5a1dca12 GRG |
171 | wxMenuBar *menuBar = new wxMenuBar(); |
172 | menuBar->Append(menuFile, _("&File")); | |
087e4f4a | 173 | menuBar->Append(menuGame, _("&Game")); |
5a1dca12 GRG |
174 | SetMenuBar(menuBar); |
175 | ||
176 | // tool bar | |
e0a40292 | 177 | wxBitmap tbBitmaps[5]; |
471ed537 | 178 | |
5a1dca12 GRG |
179 | tbBitmaps[0] = wxBITMAP(reset); |
180 | tbBitmaps[1] = wxBITMAP(play); | |
181 | tbBitmaps[2] = wxBITMAP(stop); | |
e0a40292 GRG |
182 | tbBitmaps[3] = wxBITMAP(zoomin); |
183 | tbBitmaps[4] = wxBITMAP(zoomout); | |
5a1dca12 GRG |
184 | |
185 | wxToolBar *toolBar = CreateToolBar(); | |
186 | toolBar->SetMargins(5, 5); | |
187 | toolBar->SetToolBitmapSize(wxSize(16, 16)); | |
33e39147 GRG |
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")); | |
e0a40292 | 191 | toolBar->AddSeparator(); |
33e39147 GRG |
192 | ADD_TOOL(ID_ZOOMIN, tbBitmaps[3], _("Zoom in"), _("Zoom in")); |
193 | ADD_TOOL(ID_ZOOMOUT, tbBitmaps[4], _("Zoom out"), _("Zoom out")); | |
5a1dca12 | 194 | toolBar->Realize(); |
e0a40292 | 195 | toolBar->EnableTool(ID_STOP, FALSE); // must be after Realize() ! |
5a1dca12 GRG |
196 | |
197 | // status bar | |
198 | CreateStatusBar(2); | |
199 | SetStatusText(_("Welcome to Life!")); | |
200 | ||
e0a40292 | 201 | // game and canvas |
2480be69 | 202 | wxPanel *panel = new wxPanel(this, -1); |
e0a40292 | 203 | m_life = new Life(); |
2480be69 GRG |
204 | m_canvas = new LifeCanvas(panel, m_life); |
205 | m_timer = new LifeTimer(); | |
e0a40292 GRG |
206 | m_running = FALSE; |
207 | m_topspeed = FALSE; | |
2480be69 GRG |
208 | m_interval = 500; |
209 | m_tics = 0; | |
210 | m_text = new wxStaticText(panel, -1, ""); | |
5a1dca12 GRG |
211 | UpdateInfoText(); |
212 | ||
e0a40292 | 213 | // speed selection slider |
2480be69 GRG |
214 | wxSlider *slider = new wxSlider(panel, ID_SLIDER, |
215 | 5, 1, 10, | |
216 | wxDefaultPosition, | |
217 | wxSize(200, -1), | |
218 | wxSL_HORIZONTAL | wxSL_AUTOTICKS); | |
5a1dca12 GRG |
219 | |
220 | // component layout | |
221 | wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); | |
087e4f4a | 222 | sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE); |
33e39147 | 223 | sizer->Add(m_canvas, 1, wxGROW | wxCENTRE | wxALL, 2); |
5a1dca12 | 224 | sizer->Add(new wxStaticLine(panel, -1), 0, wxGROW | wxCENTRE); |
33e39147 GRG |
225 | sizer->Add(m_text, 0, wxCENTRE | wxTOP, 4); |
226 | sizer->Add(slider, 0, wxCENTRE | wxALL, 4); | |
5a1dca12 GRG |
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; | |
5a1dca12 GRG |
236 | } |
237 | ||
238 | void LifeFrame::UpdateInfoText() | |
239 | { | |
240 | wxString msg; | |
241 | ||
e0a40292 GRG |
242 | msg.Printf(_(" Generation: %u (T: %u ms), Population: %u "), |
243 | m_tics, | |
244 | m_topspeed? 0 : m_interval, | |
245 | m_life->GetNumCells()); | |
5a1dca12 GRG |
246 | m_text->SetLabel(msg); |
247 | } | |
248 | ||
e0a40292 GRG |
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 | { | |
a9cf4097 | 254 | // start / stop |
e0a40292 GRG |
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); | |
a9cf4097 GRG |
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); | |
e0a40292 GRG |
267 | } |
268 | ||
5a1dca12 GRG |
269 | // event handlers |
270 | void LifeFrame::OnMenu(wxCommandEvent& event) | |
271 | { | |
272 | switch (event.GetId()) | |
273 | { | |
e0a40292 | 274 | case ID_CENTER : m_canvas->Recenter(0, 0); break; |
5a1dca12 | 275 | case ID_START : OnStart(); break; |
087e4f4a | 276 | case ID_STEP : OnTimer(); break; |
5a1dca12 | 277 | case ID_STOP : OnStop(); break; |
e0a40292 | 278 | case ID_ZOOMIN : |
087e4f4a | 279 | { |
e0a40292 GRG |
280 | int cellsize = m_canvas->GetCellSize(); |
281 | if (cellsize < 32) | |
a9cf4097 | 282 | { |
e0a40292 | 283 | m_canvas->SetCellSize(cellsize * 2); |
a9cf4097 GRG |
284 | UpdateUI(); |
285 | } | |
087e4f4a GRG |
286 | break; |
287 | } | |
e0a40292 | 288 | case ID_ZOOMOUT : |
5a1dca12 | 289 | { |
e0a40292 GRG |
290 | int cellsize = m_canvas->GetCellSize(); |
291 | if (cellsize > 1) | |
a9cf4097 | 292 | { |
e0a40292 | 293 | m_canvas->SetCellSize(cellsize / 2); |
a9cf4097 GRG |
294 | UpdateUI(); |
295 | } | |
e0a40292 GRG |
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 | |
5a1dca12 GRG |
313 | OnStop(); |
314 | m_life->Clear(); | |
e0a40292 | 315 | m_canvas->Recenter(0, 0); |
5a1dca12 GRG |
316 | m_tics = 0; |
317 | UpdateInfoText(); | |
318 | break; | |
319 | } | |
e0a40292 | 320 | case ID_ABOUT: |
5a1dca12 | 321 | { |
e0a40292 GRG |
322 | LifeAboutDialog dialog(this); |
323 | dialog.ShowModal(); | |
5a1dca12 GRG |
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 | ||
e0a40292 | 335 | void LifeFrame::OnClose(wxCloseEvent& WXUNUSED(event)) |
5a1dca12 | 336 | { |
e0a40292 GRG |
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. | |
5a1dca12 | 341 | OnStop(); |
e0a40292 | 342 | Destroy(); |
5a1dca12 GRG |
343 | } |
344 | ||
087e4f4a GRG |
345 | void LifeFrame::OnSamples(wxCommandEvent& WXUNUSED(event)) |
346 | { | |
347 | // stop if it was running | |
348 | OnStop(); | |
349 | ||
471ed537 | 350 | // dialog box |
087e4f4a GRG |
351 | LifeSamplesDialog dialog(this); |
352 | ||
353 | // new game? | |
354 | if (dialog.ShowModal() == wxID_OK) | |
355 | { | |
e0a40292 | 356 | const LifeShape shape = dialog.GetShape(); |
087e4f4a GRG |
357 | |
358 | // put the shape | |
e0a40292 GRG |
359 | m_life->Clear(); |
360 | m_life->SetShape(shape); | |
087e4f4a | 361 | |
e0a40292 GRG |
362 | // recenter canvas |
363 | m_canvas->Recenter(0, 0); | |
087e4f4a GRG |
364 | m_tics = 0; |
365 | UpdateInfoText(); | |
366 | } | |
367 | } | |
368 | ||
5a1dca12 GRG |
369 | void LifeFrame::OnStart() |
370 | { | |
087e4f4a GRG |
371 | if (!m_running) |
372 | { | |
087e4f4a GRG |
373 | m_timer->Start(m_interval); |
374 | m_running = TRUE; | |
e0a40292 | 375 | UpdateUI(); |
087e4f4a | 376 | } |
5a1dca12 GRG |
377 | } |
378 | ||
379 | void LifeFrame::OnStop() | |
380 | { | |
087e4f4a GRG |
381 | if (m_running) |
382 | { | |
087e4f4a GRG |
383 | m_timer->Stop(); |
384 | m_running = FALSE; | |
e0a40292 GRG |
385 | m_topspeed = FALSE; |
386 | UpdateUI(); | |
087e4f4a | 387 | } |
5a1dca12 GRG |
388 | } |
389 | ||
390 | void LifeFrame::OnTimer() | |
391 | { | |
a36f0f83 GRG |
392 | if (m_life->NextTic()) |
393 | m_tics++; | |
394 | else | |
395 | OnStop(); | |
5a1dca12 | 396 | |
e0a40292 | 397 | m_canvas->DrawChanged(); |
a36f0f83 | 398 | UpdateInfoText(); |
5a1dca12 GRG |
399 | } |
400 | ||
a36f0f83 GRG |
401 | void LifeFrame::OnSlider(wxScrollEvent& event) |
402 | { | |
403 | m_interval = event.GetPosition() * 100; | |
404 | ||
a36f0f83 GRG |
405 | if (m_running) |
406 | { | |
e0a40292 GRG |
407 | OnStop(); |
408 | OnStart(); | |
a36f0f83 | 409 | } |
e0a40292 | 410 | |
a36f0f83 GRG |
411 | UpdateInfoText(); |
412 | } | |
413 | ||
5a1dca12 GRG |
414 | // -------------------------------------------------------------------------- |
415 | // LifeTimer | |
416 | // -------------------------------------------------------------------------- | |
417 | ||
5a1dca12 GRG |
418 | void LifeTimer::Notify() |
419 | { | |
a36f0f83 | 420 | GET_FRAME()->OnTimer(); |
087e4f4a | 421 | }; |
5a1dca12 GRG |
422 | |
423 | // -------------------------------------------------------------------------- | |
087e4f4a | 424 | // LifeCanvas |
5a1dca12 GRG |
425 | // -------------------------------------------------------------------------- |
426 | ||
427 | // canvas constructor | |
087e4f4a | 428 | LifeCanvas::LifeCanvas(wxWindow *parent, Life *life, bool interactive) |
e0a40292 GRG |
429 | : wxWindow(parent, -1, wxPoint(0, 0), wxSize(100, 100), |
430 | wxSUNKEN_BORDER) | |
5a1dca12 | 431 | { |
087e4f4a GRG |
432 | m_life = life; |
433 | m_interactive = interactive; | |
434 | m_cellsize = 8; | |
e0a40292 GRG |
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); | |
5a1dca12 GRG |
446 | } |
447 | ||
448 | LifeCanvas::~LifeCanvas() | |
449 | { | |
e0a40292 | 450 | delete m_life; |
5a1dca12 GRG |
451 | } |
452 | ||
e0a40292 GRG |
453 | // recenter at the given position |
454 | void LifeCanvas::Recenter(wxInt32 i, wxInt32 j) | |
5a1dca12 | 455 | { |
e0a40292 GRG |
456 | m_viewportX = i - m_viewportW / 2; |
457 | m_viewportY = j - m_viewportH / 2; | |
5a1dca12 | 458 | |
087e4f4a | 459 | // redraw everything |
e0a40292 | 460 | Refresh(FALSE); |
5a1dca12 GRG |
461 | } |
462 | ||
e0a40292 GRG |
463 | // set the cell size and refresh display |
464 | void LifeCanvas::SetCellSize(int cellsize) | |
5a1dca12 | 465 | { |
e0a40292 GRG |
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 | |
2fa7c206 | 473 | int w, h; |
e0a40292 GRG |
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 | } | |
2480be69 | 493 | |
e0a40292 GRG |
494 | // draw a cell |
495 | void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, bool alive) | |
496 | { | |
497 | wxClientDC dc(this); | |
5a1dca12 | 498 | |
e0a40292 GRG |
499 | dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN); |
500 | dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH); | |
5a1dca12 | 501 | |
e0a40292 GRG |
502 | dc.BeginDrawing(); |
503 | DrawCell(i, j, dc); | |
5a1dca12 | 504 | dc.EndDrawing(); |
5a1dca12 GRG |
505 | } |
506 | ||
e0a40292 | 507 | void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, wxDC &dc) |
5a1dca12 | 508 | { |
e0a40292 GRG |
509 | wxCoord x = CellToX(i); |
510 | wxCoord y = CellToY(j); | |
5a1dca12 | 511 | |
e0a40292 GRG |
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 | } | |
5a1dca12 GRG |
524 | } |
525 | ||
e0a40292 GRG |
526 | // draw all changed cells |
527 | void LifeCanvas::DrawChanged() | |
5a1dca12 | 528 | { |
e0a40292 GRG |
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) | |
5a1dca12 GRG |
545 | { |
546 | dc.SetPen(*wxBLACK_PEN); | |
5a1dca12 GRG |
547 | } |
548 | else | |
549 | { | |
e0a40292 GRG |
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); | |
5a1dca12 | 560 | } |
e0a40292 | 561 | dc.EndDrawing(); |
5a1dca12 | 562 | } |
ecbdd409 | 563 | |
5a1dca12 GRG |
564 | // event handlers |
565 | void LifeCanvas::OnPaint(wxPaintEvent& event) | |
566 | { | |
567 | wxPaintDC dc(this); | |
e0a40292 GRG |
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; | |
5a1dca12 | 586 | |
e0a40292 GRG |
587 | m_life->BeginFind(i0, j0, i1, j1, FALSE); |
588 | done = m_life->FindMore(&cells, &ncells); | |
5a1dca12 | 589 | |
e0a40292 | 590 | // erase all damaged cells and draw the grid |
5a1dca12 | 591 | dc.BeginDrawing(); |
e0a40292 | 592 | dc.SetBrush(*wxWHITE_BRUSH); |
5a1dca12 | 593 | |
e0a40292 | 594 | if (m_cellsize <= 2) |
5a1dca12 | 595 | { |
e0a40292 GRG |
596 | // no grid |
597 | dc.SetPen(*wxWHITE_PEN); | |
598 | dc.DrawRectangle(x, y, w, h); | |
5a1dca12 | 599 | } |
e0a40292 GRG |
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); | |
5a1dca12 | 629 | |
5a1dca12 GRG |
630 | dc.EndDrawing(); |
631 | } | |
632 | ||
633 | void LifeCanvas::OnMouse(wxMouseEvent& event) | |
634 | { | |
087e4f4a GRG |
635 | if (!m_interactive) |
636 | return; | |
637 | ||
5a1dca12 | 638 | // which cell are we pointing at? |
e0a40292 GRG |
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); | |
5a1dca12 GRG |
646 | |
647 | // button pressed? | |
648 | if (!event.LeftIsDown()) | |
649 | { | |
650 | m_status = MOUSE_NOACTION; | |
7989fb37 | 651 | return; |
5a1dca12 | 652 | } |
5a1dca12 | 653 | |
7989fb37 GRG |
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 | { | |
5e9ff6ad GRG |
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 | ||
7989fb37 GRG |
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 | { | |
5e9ff6ad GRG |
694 | m_life->SetCell(ii, jj, alive); |
695 | DrawCell(ii, jj, dc); | |
7989fb37 GRG |
696 | if (d >= 0) |
697 | { | |
698 | jj += sj; | |
699 | d -= ai; | |
700 | } | |
701 | ii += si; | |
702 | d += aj; | |
703 | } | |
704 | } | |
705 | else | |
5a1dca12 | 706 | { |
7989fb37 GRG |
707 | // iterate over j |
708 | d = ai - (aj >> 1); | |
709 | ||
710 | while (jj != j) | |
711 | { | |
5e9ff6ad GRG |
712 | m_life->SetCell(ii, jj, alive); |
713 | DrawCell(ii, jj, dc); | |
7989fb37 GRG |
714 | if (d >= 0) |
715 | { | |
716 | ii += si; | |
717 | d -= aj; | |
718 | } | |
719 | jj += sj; | |
720 | d += ai; | |
721 | } | |
5a1dca12 | 722 | } |
7989fb37 GRG |
723 | |
724 | // last cell | |
5e9ff6ad GRG |
725 | m_life->SetCell(ii, jj, alive); |
726 | DrawCell(ii, jj, dc); | |
7989fb37 GRG |
727 | m_mi = ii; |
728 | m_mj = jj; | |
5e9ff6ad GRG |
729 | |
730 | dc.EndDrawing(); | |
5a1dca12 | 731 | } |
7989fb37 GRG |
732 | |
733 | GET_FRAME()->UpdateInfoText(); | |
5a1dca12 GRG |
734 | } |
735 | ||
736 | void LifeCanvas::OnSize(wxSizeEvent& event) | |
737 | { | |
e0a40292 GRG |
738 | // find center |
739 | wxInt32 cx = m_viewportX + m_viewportW / 2; | |
740 | wxInt32 cy = m_viewportY + m_viewportH / 2; | |
741 | ||
742 | // get new size | |
5a1dca12 GRG |
743 | wxCoord w = event.GetSize().GetX(); |
744 | wxCoord h = event.GetSize().GetY(); | |
e0a40292 GRG |
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 | } | |
5a1dca12 GRG |
760 | |
761 | // allow default processing | |
762 | event.Skip(); | |
763 | } | |
e0a40292 GRG |
764 | |
765 | void LifeCanvas::OnScroll(wxScrollWinEvent& event) | |
766 | { | |
767 | WXTYPE type = event.GetEventType(); | |
768 | int pos = event.GetPosition(); | |
769 | int orient = event.GetOrientation(); | |
e0a40292 GRG |
770 | |
771 | // calculate scroll increment | |
33e39147 | 772 | int scrollinc = 0; |
e0a40292 GRG |
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 | { | |
33e39147 | 797 | if (orient == wxHORIZONTAL) |
e0a40292 | 798 | { |
33e39147 GRG |
799 | scrollinc = pos - m_thumbX; |
800 | m_thumbX = pos; | |
e0a40292 GRG |
801 | } |
802 | else | |
803 | { | |
33e39147 GRG |
804 | scrollinc = pos - m_thumbY; |
805 | m_thumbY = pos; | |
e0a40292 GRG |
806 | } |
807 | break; | |
808 | } | |
33e39147 GRG |
809 | case wxEVT_SCROLLWIN_THUMBRELEASE: |
810 | { | |
811 | m_thumbX = m_viewportW; | |
812 | m_thumbY = m_viewportH; | |
813 | } | |
e0a40292 GRG |
814 | } |
815 | ||
816 | #ifdef __WXGTK__ // what about Motif? | |
817 | // wxGTK updates the thumb automatically (wxMSW doesn't); reset it back | |
33e39147 | 818 | if (type != wxEVT_SCROLLWIN_THUMBTRACK) |
e0a40292 GRG |
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 | } |