reflexion 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/ChangeLog +8 -0
  2. data/README +4 -0
  3. data/Rakefile +70 -0
  4. data/VERSION +1 -0
  5. data/examples/hello/Rakefile +41 -0
  6. data/examples/hello/main.cpp +18 -0
  7. data/examples/ruby/app.rb +13 -0
  8. data/examples/ruby/checker.rb +41 -0
  9. data/examples/ruby/fps.rb +49 -0
  10. data/examples/ruby/hello.rb +38 -0
  11. data/examples/ruby/key.rb +44 -0
  12. data/examples/ruby/shapes.rb +124 -0
  13. data/examples/ruby/text.rb +35 -0
  14. data/ext/reflex/application.cpp +151 -0
  15. data/ext/reflex/extconf.rb +57 -0
  16. data/ext/reflex/key.cpp +128 -0
  17. data/ext/reflex/native.cpp +22 -0
  18. data/ext/reflex/points.cpp +160 -0
  19. data/ext/reflex/reflex.cpp +83 -0
  20. data/ext/reflex/reflex.h +39 -0
  21. data/ext/reflex/window.cpp +357 -0
  22. data/include/reflex/application.h +50 -0
  23. data/include/reflex/defs.h +258 -0
  24. data/include/reflex/helpers.h +32 -0
  25. data/include/reflex/reflex.h +26 -0
  26. data/include/reflex/ruby/application.h +39 -0
  27. data/include/reflex/ruby/key.h +39 -0
  28. data/include/reflex/ruby/points.h +39 -0
  29. data/include/reflex/ruby/reflex.h +21 -0
  30. data/include/reflex/ruby/window.h +39 -0
  31. data/include/reflex/ruby.h +14 -0
  32. data/include/reflex/window.h +79 -0
  33. data/include/reflex.h +13 -0
  34. data/lib/reflex/application.rb +25 -0
  35. data/lib/reflex/autoinit.rb +11 -0
  36. data/lib/reflex/bounds.rb +133 -0
  37. data/lib/reflex/helpers.rb +52 -0
  38. data/lib/reflex/module.rb +30 -0
  39. data/lib/reflex/point.rb +69 -0
  40. data/lib/reflex/reflex.rb +21 -0
  41. data/lib/reflex/window.rb +65 -0
  42. data/lib/reflex.rb +11 -0
  43. data/reflex.gemspec +57 -0
  44. data/src/cocoa/application.mm +90 -0
  45. data/src/cocoa/applicationdata.h +51 -0
  46. data/src/cocoa/cocoaapplication.h +19 -0
  47. data/src/cocoa/cocoaapplication.mm +180 -0
  48. data/src/cocoa/cocoawindow.h +44 -0
  49. data/src/cocoa/cocoawindow.mm +171 -0
  50. data/src/cocoa/defs.h +34 -0
  51. data/src/cocoa/defs.mm +84 -0
  52. data/src/cocoa/openglview.h +17 -0
  53. data/src/cocoa/openglview.mm +186 -0
  54. data/src/cocoa/reflex.mm +68 -0
  55. data/src/cocoa/window.mm +118 -0
  56. data/src/cocoa/windowdata.h +51 -0
  57. data/src/defs.cpp +47 -0
  58. data/src/win32/defs.cpp +150 -0
  59. data/src/win32/opengl.cpp +95 -0
  60. data/src/win32/opengl.h +50 -0
  61. data/src/win32/reflex.cpp +65 -0
  62. data/src/win32/window.cpp +480 -0
  63. data/src/window.cpp +60 -0
  64. data/support.rb +56 -0
  65. data/task/ext.rake +42 -0
  66. data/task/gem.rake +33 -0
  67. data/task/git.rake +22 -0
  68. data/task/lib.rake +54 -0
  69. data/test/helpers.rb +15 -0
  70. data/test/test_bounds.rb +163 -0
  71. data/test/test_point.rb +81 -0
  72. data/test/test_reflex.rb +17 -0
  73. data/test/test_window.rb +39 -0
  74. metadata +173 -0
@@ -0,0 +1,480 @@
1
+ #include "reflex/window.h"
2
+
3
+
4
+ #include <windows.h>
5
+ #include <boost/shared_ptr.hpp>
6
+ #include <boost/scoped_array.hpp>
7
+ #include "reflex/reflex.h"
8
+ #include "opengl.h"
9
+
10
+
11
+ namespace Reflex
12
+ {
13
+
14
+
15
+ struct Window::Data
16
+ {
17
+
18
+ Window* this_;
19
+
20
+ HWND hwnd;
21
+
22
+ int hidecount;
23
+
24
+ OpenGL opengl;
25
+
26
+ Data ()
27
+ : this_(NULL), hwnd(NULL), hidecount(1)
28
+ {
29
+ }
30
+
31
+ };// Window::Data
32
+
33
+ typedef boost::shared_ptr<Window::Data> WindowData;
34
+
35
+
36
+ static const char* WINDOWCLASS = "Reflex:WindowClass";
37
+
38
+ static const char* USERDATA_PROP = "Reflex:Window:HWND";
39
+
40
+ enum {UPDATE_TIMER_ID = 99999};
41
+
42
+
43
+ static LRESULT CALLBACK wndproc (HWND, UINT, WPARAM, LPARAM);
44
+
45
+
46
+ static bool
47
+ window_has_wndproc (HWND hwnd)
48
+ {
49
+ if (!hwnd) return false;
50
+
51
+ WNDPROC proc = (WNDPROC) GetWindowLongPtr(hwnd, GWLP_WNDPROC);
52
+ if (proc == wndproc)
53
+ return true;
54
+
55
+ // OpenGL SetPixelFormat() changes GWLP_WNDPROC, so checking
56
+ // it can not determine the hwnd is mine or not.
57
+
58
+ enum {BUFSIZE = 256};
59
+ char buf[BUFSIZE + 1];
60
+ if (
61
+ GetClassName(hwnd, &buf[0], BUFSIZE) == 0 &&
62
+ GetLastError() != 0)
63
+ {
64
+ return false;
65
+ }
66
+
67
+ return stricmp(buf, WINDOWCLASS) == 0;
68
+ }
69
+
70
+ static Window*
71
+ get_window_from_hwnd (HWND hwnd)
72
+ {
73
+ WindowData* data = NULL;
74
+ if (window_has_wndproc(hwnd))
75
+ data = (WindowData*) GetWindowLongPtr(hwnd, GWLP_USERDATA);
76
+ else
77
+ data = (WindowData*) GetProp(hwnd, USERDATA_PROP);
78
+
79
+ return data ? (*data)->this_ : NULL;
80
+ }
81
+
82
+ static bool
83
+ setup_window (Window* win, HWND hwnd)
84
+ {
85
+ if (!win || *win) return false;
86
+
87
+ WindowData* data = new WindowData(win->self);
88
+
89
+ bool ret = false;
90
+ if (window_has_wndproc(hwnd))
91
+ {
92
+ SetLastError(0);
93
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) data);
94
+ ret = GetLastError() == 0;
95
+ }
96
+ else
97
+ ret = SetProp(hwnd, USERDATA_PROP, (HANDLE) data);
98
+
99
+ if (ret)
100
+ {
101
+ win->self->hwnd = hwnd;
102
+ win->self->opengl.init(hwnd);
103
+ }
104
+ else
105
+ delete data;
106
+
107
+ return ret;
108
+ }
109
+
110
+ static bool
111
+ cleanup_window (Window* win)
112
+ {
113
+ if (!win || !*win) return false;
114
+
115
+ HWND hwnd = win->self->hwnd;
116
+ WindowData* data = NULL;
117
+
118
+ bool has_wndproc = window_has_wndproc(hwnd);
119
+ if (has_wndproc)
120
+ data = (WindowData*) GetWindowLongPtr(hwnd, GWLP_USERDATA);
121
+ else
122
+ data = (WindowData*) GetProp(hwnd, USERDATA_PROP);
123
+
124
+ if (!data || data->get() != win->self.get())
125
+ return false;
126
+
127
+ bool ret = false;
128
+ if (has_wndproc)
129
+ {
130
+ SetLastError(0);
131
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
132
+ ret = GetLastError() == 0;
133
+ }
134
+ else
135
+ ret = RemoveProp(hwnd, USERDATA_PROP) != NULL;
136
+
137
+ if (ret)
138
+ {
139
+ win->self->opengl.fin();
140
+ win->self->hwnd = NULL;
141
+ delete data;
142
+ }
143
+
144
+ return ret;
145
+ }
146
+
147
+ static LRESULT CALLBACK
148
+ window_proc (Window* win, HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
149
+ {
150
+ if (!win || !*win || win->self->hwnd != hwnd)
151
+ return DefWindowProc(hwnd, msg, wp, lp);
152
+
153
+ switch (msg)
154
+ {
155
+ case WM_CLOSE:
156
+ {
157
+ win->close();
158
+ return 0;
159
+ }
160
+
161
+ case WM_PAINT:
162
+ {
163
+ PAINTSTRUCT ps;
164
+ BeginPaint(hwnd, &ps);
165
+
166
+ if (win->self->opengl.make_current())
167
+ {
168
+ win->draw();
169
+ win->self->opengl.swap_buffers();
170
+ }
171
+
172
+ EndPaint(hwnd, &ps);
173
+ return 0;
174
+ }
175
+
176
+ case WM_MOVE:
177
+ {
178
+ coord l = 0, t = 0;
179
+ win->get_bounds(&l, &t);
180
+ win->moved(l, t);
181
+ break;
182
+ }
183
+
184
+ case WM_SIZE:
185
+ {
186
+ coord l = 0, t = 0, r = 0, b = 0;
187
+ win->get_bounds(&l, &t, &r, &b);
188
+ win->resized(r - l, b - t);
189
+ break;
190
+ }
191
+
192
+ case WM_ERASEBKGND:
193
+ {
194
+ // do nothing.
195
+ return 0;
196
+ }
197
+
198
+ case WM_TIMER:
199
+ {
200
+ if (wp == UPDATE_TIMER_ID)
201
+ win->update();
202
+ return 0;
203
+ }
204
+
205
+ case WM_SYSCOMMAND:
206
+ {
207
+ #if 0
208
+ if (wp == SC_SCREENSAVE || wp == SC_MONITORPOWER)
209
+ return 0;
210
+ #endif
211
+ break;
212
+ }
213
+ }
214
+
215
+ return DefWindowProc(hwnd, msg, wp, lp);
216
+ }
217
+
218
+ static LRESULT CALLBACK
219
+ wndproc (HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
220
+ {
221
+ static int window_total = 0;
222
+
223
+ Window* win = NULL;
224
+ if (msg == WM_NCCREATE)
225
+ {
226
+ CREATESTRUCT* cs = (CREATESTRUCT*) lp;
227
+ win = (Window*) cs->lpCreateParams;
228
+ setup_window(win, hwnd);
229
+
230
+ ++window_total;
231
+ }
232
+
233
+ if (!win)
234
+ win = get_window_from_hwnd(hwnd);
235
+
236
+ LRESULT ret = window_proc(win, hwnd, msg, wp, lp);
237
+
238
+ if (msg == WM_NCDESTROY)
239
+ {
240
+ cleanup_window(win);
241
+
242
+ if (--window_total == 0)
243
+ Reflex::quit();
244
+ }
245
+
246
+ return ret;
247
+ }
248
+
249
+ static bool
250
+ register_windowclass ()
251
+ {
252
+ static bool registered = false;
253
+ if (registered) return true;
254
+
255
+ WNDCLASSEX wc;
256
+ memset(&wc, 0, sizeof(wc));
257
+
258
+ wc.cbSize = sizeof(wc);
259
+ wc.lpszClassName = WINDOWCLASS;
260
+ wc.lpfnWndProc = (WNDPROC) wndproc;
261
+ wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
262
+ wc.hInstance = GetModuleHandle(NULL);
263
+ //wc.hIcon = LoadIcon(wc.hInstance, IDI_APP_LARGE);
264
+ //wc.hIconSm = LoadIcon(wc.hInstance, IDI_APP_SMALL);
265
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
266
+ wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
267
+ wc.lpszMenuName = NULL;
268
+ wc.cbWndExtra = sizeof(Window*);
269
+
270
+ if (!RegisterClassEx(&wc))
271
+ return false;
272
+
273
+ return registered = true;
274
+ }
275
+
276
+ static bool
277
+ create_window (Window* win)
278
+ {
279
+ if (!win) return false;
280
+
281
+ if (!register_windowclass())
282
+ return false;
283
+
284
+ CreateWindowEx(
285
+ 0, WINDOWCLASS, "",
286
+ WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
287
+ 0, 0, 0, 0, NULL, NULL, GetModuleHandle(NULL), win);
288
+ return *win;
289
+ }
290
+
291
+
292
+ Window::Window ()
293
+ {
294
+ self->this_ = this;
295
+
296
+ create_window(this);
297
+ }
298
+
299
+ Window::~Window ()
300
+ {
301
+ close();
302
+ self->this_ = NULL;
303
+ }
304
+
305
+ bool
306
+ Window::close ()
307
+ {
308
+ if (!self->hwnd || !IsWindow(self->hwnd))
309
+ return false;
310
+
311
+ return DestroyWindow(self->hwnd) != FALSE;
312
+ }
313
+
314
+ static bool
315
+ start_timer (HWND hwnd, UINT id, UINT interval)
316
+ {
317
+ if (!hwnd) return false;
318
+
319
+ return SetTimer(hwnd, id, interval, NULL) != 0;
320
+ }
321
+
322
+ static bool
323
+ stop_timer (HWND hwnd, UINT id)
324
+ {
325
+ if (!hwnd || id == 0) return false;
326
+
327
+ return KillTimer(hwnd, id) != FALSE;
328
+ }
329
+
330
+ bool
331
+ Window::show ()
332
+ {
333
+ if (!*this) return false;
334
+
335
+ if (--self->hidecount != 0) return true;
336
+
337
+ SetLastError(0);
338
+ ShowWindow(self->hwnd, SW_SHOW);
339
+ if (GetLastError() != 0) return false;
340
+
341
+ if (!UpdateWindow(self->hwnd))
342
+ return false;
343
+
344
+ start_timer(self->hwnd, UPDATE_TIMER_ID, 1000 / 60);
345
+ return true;
346
+ }
347
+
348
+ bool
349
+ Window::hide ()
350
+ {
351
+ if (!*this) return false;
352
+
353
+ if (++self->hidecount != 1) return true;
354
+
355
+ SetLastError(0);
356
+ ShowWindow(self->hwnd, SW_HIDE);
357
+ if (GetLastError() != 0) return false;
358
+
359
+ stop_timer(self->hwnd, UPDATE_TIMER_ID);
360
+ return true;
361
+ }
362
+
363
+ bool
364
+ Window::hidden () const
365
+ {
366
+ if (!*this) return true;
367
+
368
+ return self->hidecount > 0;
369
+ }
370
+
371
+ void
372
+ Window::update ()
373
+ {
374
+ }
375
+
376
+ void
377
+ Window::draw ()
378
+ {
379
+ }
380
+
381
+ bool
382
+ Window::redraw ()
383
+ {
384
+ if (!*this) return false;
385
+
386
+ #if 1
387
+ InvalidateRect(self->hwnd, NULL, FALSE);
388
+ return true;
389
+ #else
390
+ return FALSE != RedrawWindow(
391
+ self->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | EDW_ALLCHILDREN);
392
+ #endif
393
+ }
394
+
395
+ bool
396
+ Window::get_title (String* title) const
397
+ {
398
+ if (!*this || !title) return false;
399
+
400
+ *title = "";
401
+
402
+ int size = GetWindowTextLength(self->hwnd);
403
+ if (size <= 0) return true;
404
+
405
+ boost::scoped_array<char> buf(new char[size + 1]);
406
+ if (GetWindowText(self->hwnd, &buf[0], size + 1) != size)
407
+ return false;
408
+
409
+ *title = &buf[0];
410
+ return true;
411
+ }
412
+
413
+ bool
414
+ Window::set_title (const char* title)
415
+ {
416
+ if (!*this || !title) return false;
417
+
418
+ return SetWindowText(self->hwnd, title) != FALSE;
419
+ }
420
+
421
+ bool
422
+ Window::get_bounds (coord* x, coord* y, coord* width, coord* height)
423
+ {
424
+ if (!*this || (!x && !y && !width && !height))
425
+ return false;
426
+
427
+ RECT rect;
428
+ if (!GetWindowRect(self->hwnd, &rect))
429
+ return false;
430
+
431
+ if (x) *x = rect.left;
432
+ if (y) *y = rect.top;
433
+ if (width) *width = rect.right - rect.left + 1;
434
+ if (height) *height = rect.bottom - rect.top + 1;
435
+ return true;
436
+ }
437
+
438
+ bool
439
+ Window::set_bounds (coord x, coord y, coord width, coord height)
440
+ {
441
+ if (!*this) return false;
442
+
443
+ coord xx, yy, ww, hh;
444
+ if (!get_bounds(&xx, &yy, &ww, &hh))
445
+ return false;
446
+
447
+ UINT flags = 0;
448
+ if (x == xx && y == yy) flags |= SWP_NOMOVE;
449
+ if (width == ww && height == hh) flags |= SWP_NOSIZE;
450
+
451
+ if (flags == (SWP_NOMOVE | SWP_NOSIZE)) return true;
452
+
453
+ return FALSE != SetWindowPos(
454
+ self->hwnd, NULL, (int) x, (int) y, (int) width, (int) height,
455
+ flags | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER);
456
+ }
457
+
458
+ void
459
+ Window::moved (coord x, coord y)
460
+ {
461
+ }
462
+
463
+ void
464
+ Window::resized (coord width, coord height)
465
+ {
466
+ }
467
+
468
+ Window::operator bool () const
469
+ {
470
+ return self && self->this_ && self->hwnd && IsWindow(self->hwnd);
471
+ }
472
+
473
+ bool
474
+ Window::operator ! () const
475
+ {
476
+ return !operator bool();
477
+ }
478
+
479
+
480
+ }// Reflex
data/src/window.cpp ADDED
@@ -0,0 +1,60 @@
1
+ #include "reflex/window.h"
2
+
3
+
4
+ namespace Reflex
5
+ {
6
+
7
+
8
+ void
9
+ Window::update ()
10
+ {
11
+ }
12
+
13
+ void
14
+ Window::draw ()
15
+ {
16
+ }
17
+
18
+ void
19
+ Window::moved (coord x, coord y)
20
+ {
21
+ }
22
+
23
+ void
24
+ Window::resized (coord width, coord height)
25
+ {
26
+ }
27
+
28
+ void
29
+ Window::key_down (const Key& key)
30
+ {
31
+ }
32
+
33
+ void
34
+ Window::key_up (const Key& key)
35
+ {
36
+ }
37
+
38
+ void
39
+ Window::points_down (const Points& points)
40
+ {
41
+ }
42
+
43
+ void
44
+ Window::points_up (const Points& points)
45
+ {
46
+ }
47
+
48
+ void
49
+ Window::points_moved (const Points& points)
50
+ {
51
+ }
52
+
53
+ bool
54
+ Window::operator ! () const
55
+ {
56
+ return !operator bool();
57
+ }
58
+
59
+
60
+ }// Reflex
data/support.rb ADDED
@@ -0,0 +1,56 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'erb'
3
+ require 'pp'
4
+
5
+
6
+ def version ()
7
+ open("VERSION") {|f| f.readline.chomp}
8
+ end
9
+
10
+ def glob (*patterns)
11
+ paths = []
12
+ patterns.each do |pattern|
13
+ paths.concat Dir.glob(pattern)
14
+ end
15
+ paths
16
+ end
17
+
18
+ def erb (str)
19
+ ERB.new(str, nil, "%").result binding
20
+ end
21
+
22
+ def compile (path, out)
23
+ open(path) do |input|
24
+ open(out, "w") do |output|
25
+ output.write erb(input.read)
26
+ end
27
+ end
28
+ #rescue
29
+ end
30
+
31
+ def convertions (paths, convs)
32
+ raise "empty conversion." if convs.empty?
33
+ paths = paths.map do |path|
34
+ convpath = path
35
+ convs.each do |from, to|
36
+ convpath = convpath.sub(/#{from.gsub('.', '\.')}$/, to)
37
+ end
38
+ [path, convpath]
39
+ end
40
+ Hash[*paths.flatten]
41
+ end
42
+
43
+ alias sh_original sh
44
+
45
+ def sh (*args)
46
+ sh_original *args
47
+ #rescue
48
+ end
49
+
50
+ def win32? ()
51
+ RUBY_PLATFORM =~ /mswin|ming|cygwin/
52
+ end
53
+
54
+ def cocoa? ()
55
+ RUBY_PLATFORM =~ /darwin/
56
+ end
data/task/ext.rake ADDED
@@ -0,0 +1,42 @@
1
+ # -*- mode: ruby; coding: utf-8 -*-
2
+
3
+
4
+ require 'rucy/module'
5
+ require 'rays/module'
6
+
7
+
8
+ namespace :ext do
9
+
10
+ dir = "#{EXTDIR}/#{NAME}"
11
+ name = "#{NAME}/native"
12
+ outname = "#{name}.#{EXTEXT}"
13
+ out = File.join EXTDIR, outname
14
+
15
+ extconf = File.join dir, "extconf.rb"
16
+ makefile = File.join dir, "Makefile"
17
+ depend = File.join dir, "depend"
18
+
19
+ cpps = Dir.glob("#{dir}/**/*.cpp")
20
+
21
+ task :build => makefile do
22
+ sh %( cd #{dir} && #{MAKE} )
23
+ end
24
+
25
+ task :clean do
26
+ sh %( cd #{dir} && #{MAKE} clean ) if File.exist? makefile
27
+ sh %( rm -f #{makefile} #{depend} )
28
+ end
29
+
30
+ file makefile => [extconf, depend] do
31
+ sh %( cd #{dir} && #{RUBY} #{File.basename extconf} )
32
+ end
33
+
34
+ file depend => ["lib:build"] + cpps do
35
+ incdirs = INCDIRS + Rucy.include_dirs + Rays.include_dirs;
36
+ incdirs = incdirs.map{|s| " -I#{s}"}.join
37
+ srcs = cpps.map{|cpp| File.basename cpp}.join ' '
38
+ dep = File.basename depend
39
+ sh %( cd #{dir} && #{CC} -M #{CFLAGS} #{incdirs} #{srcs} > #{dep} )
40
+ end
41
+
42
+ end# :ext
data/task/gem.rake ADDED
@@ -0,0 +1,33 @@
1
+ # -*- mode: ruby; coding: utf-8 -*-
2
+
3
+
4
+ namespace :gem do
5
+
6
+ name = "#{NAME}ion"
7
+
8
+ gemspec = "#{NAME}.gemspec"
9
+ gem = "#{name}-#{version}.gem"
10
+
11
+ task :build => gem
12
+
13
+ task :install => gem do
14
+ sh %( #{GEM} install #{gem} )
15
+ end
16
+
17
+ task :uninstall do
18
+ sh %( #{GEM} uninstall #{name} )
19
+ end
20
+
21
+ task :clean do
22
+ sh %( rm -f #{gem} )
23
+ end
24
+
25
+ task :upload => gem do
26
+ sh %( #{GEM} push #{gem} )
27
+ end
28
+
29
+ file gem => "lib:build" do
30
+ sh %( #{GEM} build #{gemspec} )
31
+ end
32
+
33
+ end# :gem
data/task/git.rake ADDED
@@ -0,0 +1,22 @@
1
+ # -*- mode: ruby; coding: utf-8 -*-
2
+
3
+
4
+ namespace :git do
5
+
6
+ task :status do
7
+ sh %( #{GIT} status )
8
+ end
9
+
10
+ task :diff do
11
+ sh %( #{GIT} diff | cat )
12
+ end
13
+
14
+ task :push do
15
+ sh %( #{GIT} push )
16
+ end
17
+
18
+ task :pull do
19
+ sh %( #{GIT} pull )
20
+ end
21
+
22
+ end# :git