reflexion 0.5.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. checksums.yaml +4 -4
  2. data/.doc/ext/reflex/application.cpp +56 -7
  3. data/.doc/ext/reflex/capture_event.cpp +1 -1
  4. data/.doc/ext/reflex/chase_constraint.cpp +84 -0
  5. data/.doc/ext/reflex/clipboard.cpp +65 -0
  6. data/.doc/ext/reflex/color_event.cpp +76 -0
  7. data/.doc/ext/reflex/color_picker.cpp +113 -0
  8. data/.doc/ext/reflex/constraint.cpp +135 -0
  9. data/.doc/ext/reflex/contact_event.cpp +1 -1
  10. data/.doc/ext/reflex/control_change_event.cpp +1 -1
  11. data/.doc/ext/reflex/device.cpp +1 -1
  12. data/.doc/ext/reflex/device_event.cpp +1 -1
  13. data/.doc/ext/reflex/draw_event.cpp +1 -1
  14. data/.doc/ext/reflex/ellipse_shape.cpp +1 -1
  15. data/.doc/ext/reflex/event.cpp +1 -1
  16. data/.doc/ext/reflex/file_dialog.cpp +181 -0
  17. data/.doc/ext/reflex/file_event.cpp +103 -0
  18. data/.doc/ext/reflex/filter.cpp +1 -1
  19. data/.doc/ext/reflex/focus_event.cpp +1 -1
  20. data/.doc/ext/reflex/frame_event.cpp +1 -1
  21. data/.doc/ext/reflex/gamepad.cpp +48 -0
  22. data/.doc/ext/reflex/image_view.cpp +1 -1
  23. data/.doc/ext/reflex/key_event.cpp +1 -274
  24. data/.doc/ext/reflex/line_shape.cpp +1 -1
  25. data/.doc/ext/reflex/link_constraint.cpp +178 -0
  26. data/.doc/ext/reflex/menu.cpp +303 -0
  27. data/.doc/ext/reflex/midi.cpp +9 -2
  28. data/.doc/ext/reflex/midi_event.cpp +1 -1
  29. data/.doc/ext/reflex/motion_event.cpp +1 -1
  30. data/.doc/ext/reflex/native.cpp +40 -10
  31. data/.doc/ext/reflex/note_event.cpp +1 -1
  32. data/.doc/ext/reflex/pin.cpp +156 -0
  33. data/.doc/ext/reflex/pointer.cpp +3 -1
  34. data/.doc/ext/reflex/pointer_event.cpp +3 -3
  35. data/.doc/ext/reflex/polygon_shape.cpp +1 -1
  36. data/.doc/ext/reflex/rect_shape.cpp +1 -1
  37. data/.doc/ext/reflex/reflex.cpp +606 -237
  38. data/.doc/ext/reflex/screen.cpp +27 -1
  39. data/.doc/ext/reflex/scroll_event.cpp +1 -1
  40. data/.doc/ext/reflex/selector.cpp +5 -5
  41. data/.doc/ext/reflex/shape.cpp +1 -1
  42. data/.doc/ext/reflex/snap_constraint.cpp +125 -0
  43. data/.doc/ext/reflex/style.cpp +1 -1
  44. data/.doc/ext/reflex/style_length.cpp +4 -3
  45. data/.doc/ext/reflex/text_event.cpp +104 -0
  46. data/.doc/ext/reflex/timer.cpp +1 -1
  47. data/.doc/ext/reflex/timer_event.cpp +1 -1
  48. data/.doc/ext/reflex/update_event.cpp +1 -1
  49. data/.doc/ext/reflex/view.cpp +206 -79
  50. data/.doc/ext/reflex/wheel_constraint.cpp +142 -0
  51. data/.doc/ext/reflex/wheel_event.cpp +1 -1
  52. data/.doc/ext/reflex/window.cpp +282 -31
  53. data/.github/workflows/release-gem.yml +2 -40
  54. data/.github/workflows/tag.yml +5 -28
  55. data/.github/workflows/test.yml +1 -28
  56. data/ChangeLog.md +79 -0
  57. data/Gemfile.lock +13 -8
  58. data/README.md +1 -0
  59. data/Rakefile +13 -2
  60. data/Reflex.podspec +2 -2
  61. data/VERSION +1 -1
  62. data/ext/reflex/application.cpp +62 -7
  63. data/ext/reflex/capture_event.cpp +1 -1
  64. data/ext/reflex/chase_constraint.cpp +89 -0
  65. data/ext/reflex/clipboard.cpp +69 -0
  66. data/ext/reflex/color_event.cpp +80 -0
  67. data/ext/reflex/color_picker.cpp +121 -0
  68. data/ext/reflex/constraint.cpp +146 -0
  69. data/ext/reflex/contact_event.cpp +1 -1
  70. data/ext/reflex/control_change_event.cpp +1 -1
  71. data/ext/reflex/device.cpp +1 -1
  72. data/ext/reflex/device_event.cpp +1 -1
  73. data/ext/reflex/draw_event.cpp +1 -1
  74. data/ext/reflex/ellipse_shape.cpp +1 -1
  75. data/ext/reflex/event.cpp +1 -1
  76. data/ext/reflex/extconf.rb +4 -2
  77. data/ext/reflex/file_dialog.cpp +194 -0
  78. data/ext/reflex/file_event.cpp +108 -0
  79. data/ext/reflex/filter.cpp +1 -1
  80. data/ext/reflex/focus_event.cpp +1 -1
  81. data/ext/reflex/frame_event.cpp +1 -1
  82. data/ext/reflex/gamepad.cpp +50 -0
  83. data/ext/reflex/image_view.cpp +1 -1
  84. data/ext/reflex/key_event.cpp +1 -275
  85. data/ext/reflex/line_shape.cpp +1 -1
  86. data/ext/reflex/link_constraint.cpp +194 -0
  87. data/ext/reflex/menu.cpp +331 -0
  88. data/ext/reflex/midi.cpp +11 -3
  89. data/ext/reflex/midi_event.cpp +1 -1
  90. data/ext/reflex/motion_event.cpp +1 -1
  91. data/ext/reflex/native.cpp +40 -10
  92. data/ext/reflex/note_event.cpp +1 -1
  93. data/ext/reflex/pin.cpp +165 -0
  94. data/ext/reflex/pointer.cpp +3 -1
  95. data/ext/reflex/pointer_event.cpp +3 -3
  96. data/ext/reflex/polygon_shape.cpp +1 -1
  97. data/ext/reflex/rect_shape.cpp +1 -1
  98. data/ext/reflex/reflex.cpp +608 -237
  99. data/ext/reflex/screen.cpp +31 -2
  100. data/ext/reflex/scroll_event.cpp +1 -1
  101. data/ext/reflex/selector.cpp +5 -5
  102. data/ext/reflex/selector.h +10 -10
  103. data/ext/reflex/shape.cpp +1 -1
  104. data/ext/reflex/snap_constraint.cpp +135 -0
  105. data/ext/reflex/style.cpp +1 -1
  106. data/ext/reflex/style_length.cpp +4 -3
  107. data/ext/reflex/text_event.cpp +111 -0
  108. data/ext/reflex/timer.cpp +1 -1
  109. data/ext/reflex/timer_event.cpp +1 -1
  110. data/ext/reflex/update_event.cpp +1 -1
  111. data/ext/reflex/view.cpp +236 -96
  112. data/ext/reflex/wheel_constraint.cpp +154 -0
  113. data/ext/reflex/wheel_event.cpp +1 -1
  114. data/ext/reflex/window.cpp +318 -40
  115. data/include/reflex/application.h +19 -0
  116. data/include/reflex/clipboard.h +53 -0
  117. data/include/reflex/color_picker.h +65 -0
  118. data/include/reflex/constraint.h +275 -0
  119. data/include/reflex/defs.h +204 -195
  120. data/include/reflex/event.h +180 -88
  121. data/include/reflex/file_dialog.h +73 -0
  122. data/include/reflex/gamepad.h +3 -0
  123. data/include/reflex/menu.h +120 -0
  124. data/include/reflex/midi.h +3 -0
  125. data/include/reflex/pin.h +68 -0
  126. data/include/reflex/pointer.h +4 -0
  127. data/include/reflex/reflex.h +2 -0
  128. data/include/reflex/ruby/application.h +6 -1
  129. data/include/reflex/ruby/clipboard.h +23 -0
  130. data/include/reflex/ruby/color_picker.h +89 -0
  131. data/include/reflex/ruby/constraint.h +94 -0
  132. data/include/reflex/ruby/event.h +62 -29
  133. data/include/reflex/ruby/file_dialog.h +89 -0
  134. data/include/reflex/ruby/gamepad.h +46 -0
  135. data/include/reflex/ruby/menu.h +103 -0
  136. data/include/reflex/ruby/pin.h +40 -0
  137. data/include/reflex/ruby/reflex.h +3 -0
  138. data/include/reflex/ruby/view.h +123 -39
  139. data/include/reflex/ruby/window.h +96 -19
  140. data/include/reflex/ruby.h +3 -0
  141. data/include/reflex/screen.h +11 -2
  142. data/include/reflex/view.h +66 -27
  143. data/include/reflex/window.h +57 -12
  144. data/include/reflex.h +8 -0
  145. data/lib/reflex/application.rb +5 -5
  146. data/lib/reflex/autoinit.rb +3 -1
  147. data/lib/reflex/button.rb +3 -3
  148. data/lib/reflex/chase_constraint.rb +15 -0
  149. data/lib/reflex/clipboard.rb +19 -0
  150. data/lib/reflex/color_event.rb +16 -0
  151. data/lib/reflex/color_picker.rb +45 -0
  152. data/lib/reflex/constraint.rb +29 -0
  153. data/lib/reflex/control_change_event.rb +17 -0
  154. data/lib/reflex/extension.rb +1 -1
  155. data/lib/reflex/file_dialog.rb +70 -0
  156. data/lib/reflex/file_event.rb +37 -0
  157. data/lib/reflex/helper.rb +79 -0
  158. data/lib/reflex/key_event.rb +14 -14
  159. data/lib/reflex/link_constraint.rb +31 -0
  160. data/lib/reflex/menu.rb +92 -0
  161. data/lib/reflex/model.rb +19 -19
  162. data/lib/reflex/note_event.rb +1 -1
  163. data/lib/reflex/pin.rb +39 -0
  164. data/lib/reflex/pointer.rb +30 -1
  165. data/lib/reflex/pointer_event.rb +13 -2
  166. data/lib/reflex/reflex.rb +5 -0
  167. data/lib/reflex/shape.rb +14 -14
  168. data/lib/reflex/snap_constraint.rb +28 -0
  169. data/lib/reflex/style.rb +16 -16
  170. data/lib/reflex/text_event.rb +40 -0
  171. data/lib/reflex/timer.rb +3 -3
  172. data/lib/reflex/view.rb +85 -18
  173. data/lib/reflex/wheel_constraint.rb +28 -0
  174. data/lib/reflex/wheel_event.rb +19 -1
  175. data/lib/reflex/window.rb +25 -13
  176. data/lib/reflex.rb +23 -8
  177. data/pod.rake +1 -1
  178. data/reflex.gemspec +4 -4
  179. data/samples/constraint.rb +152 -0
  180. data/samples/ime.rb +100 -0
  181. data/samples/ios/hello/hello.xcodeproj/project.pbxproj +236 -224
  182. data/samples/menu.rb +32 -0
  183. data/samples/model.rb +3 -3
  184. data/samples/test/alert.rb +93 -0
  185. data/samples/test/always_on.rb +76 -0
  186. data/samples/test/app_background.rb +51 -0
  187. data/samples/test/boundary.rb +75 -0
  188. data/samples/test/callback_exception.rb +101 -0
  189. data/samples/test/cancel.rb +53 -0
  190. data/samples/test/color_picker.rb +63 -0
  191. data/samples/test/file_dialog.rb +102 -0
  192. data/samples/window_style.rb +156 -0
  193. data/src/application.cpp +180 -20
  194. data/src/application.h +95 -5
  195. data/src/clipboard.cpp +81 -0
  196. data/src/color_picker.cpp +113 -0
  197. data/src/color_picker.h +36 -0
  198. data/src/constraint.cpp +1662 -0
  199. data/src/constraint.h +64 -0
  200. data/src/event.cpp +416 -252
  201. data/src/event.h +13 -2
  202. data/src/file_dialog.cpp +163 -0
  203. data/src/file_dialog.h +52 -0
  204. data/src/gamepad.cpp +42 -5
  205. data/src/gamepad.h +4 -0
  206. data/src/ios/app_delegate.mm +5 -3
  207. data/src/ios/application.mm +29 -0
  208. data/src/ios/clipboard.mm +44 -0
  209. data/src/ios/color_picker.mm +19 -0
  210. data/src/ios/event.h +6 -2
  211. data/src/ios/event.mm +31 -12
  212. data/src/ios/file_dialog.mm +21 -0
  213. data/src/ios/gamepad.mm +4 -4
  214. data/src/ios/menu.mm +41 -0
  215. data/src/ios/reflex.mm +8 -0
  216. data/src/ios/screen.mm +26 -0
  217. data/src/ios/view_controller.mm +47 -42
  218. data/src/ios/window.mm +44 -9
  219. data/src/menu.cpp +387 -0
  220. data/src/menu.h +81 -0
  221. data/src/midi.cpp +61 -31
  222. data/src/osx/app_delegate.h +4 -0
  223. data/src/osx/app_delegate.mm +168 -139
  224. data/src/osx/application.mm +103 -5
  225. data/src/osx/clipboard.mm +45 -0
  226. data/src/osx/color_picker.mm +112 -0
  227. data/src/osx/event.h +10 -0
  228. data/src/osx/event.mm +114 -30
  229. data/src/osx/file_dialog.mm +168 -0
  230. data/src/osx/gamepad.mm +2 -2
  231. data/src/osx/gamepad_hid.mm +1 -1
  232. data/src/osx/menu.h +25 -0
  233. data/src/osx/menu.mm +516 -0
  234. data/src/osx/native_window.h +16 -2
  235. data/src/osx/native_window.mm +141 -57
  236. data/src/osx/opengl_view.h +2 -1
  237. data/src/osx/opengl_view.mm +229 -37
  238. data/src/osx/reflex.mm +38 -0
  239. data/src/osx/screen.mm +36 -0
  240. data/src/osx/window.mm +117 -24
  241. data/src/pin.cpp +137 -0
  242. data/src/pointer.cpp +36 -16
  243. data/src/pointer.h +10 -0
  244. data/src/rays.h +46 -0
  245. data/src/sdl/application.cpp +73 -8
  246. data/src/sdl/clipboard.cpp +39 -0
  247. data/src/sdl/color_picker.cpp +18 -0
  248. data/src/sdl/event.cpp +44 -6
  249. data/src/sdl/event.h +18 -3
  250. data/src/sdl/file_dialog.cpp +20 -0
  251. data/src/sdl/gamepad.cpp +3 -7
  252. data/src/sdl/menu.cpp +40 -0
  253. data/src/sdl/reflex.cpp +20 -0
  254. data/src/sdl/screen.cpp +36 -0
  255. data/src/sdl/window.cpp +168 -37
  256. data/src/shape.cpp +15 -10
  257. data/src/view.cpp +583 -233
  258. data/src/view.h +22 -4
  259. data/src/vk.h +416 -0
  260. data/src/win32/application.cpp +167 -12
  261. data/src/win32/application.h +20 -0
  262. data/src/win32/clipboard.cpp +210 -0
  263. data/src/win32/color_picker.cpp +82 -0
  264. data/src/win32/event.cpp +123 -11
  265. data/src/win32/event.h +22 -3
  266. data/src/win32/file_dialog.cpp +226 -0
  267. data/src/win32/gamepad.cpp +9 -2
  268. data/src/win32/menu.cpp +490 -0
  269. data/src/win32/menu.h +35 -0
  270. data/src/win32/opengl.cpp +2 -9
  271. data/src/win32/reflex.cpp +44 -3
  272. data/src/win32/screen.cpp +164 -3
  273. data/src/win32/screen.h +8 -0
  274. data/src/win32/tray.cpp +191 -0
  275. data/src/win32/tray.h +22 -0
  276. data/src/win32/vk.cpp +11 -0
  277. data/src/win32/window.cpp +858 -135
  278. data/src/win32/window.h +9 -0
  279. data/src/window.cpp +929 -183
  280. data/src/window.h +77 -33
  281. data/src/world.cpp +86 -15
  282. data/src/world.h +9 -2
  283. data/test/helper.rb +24 -0
  284. data/test/test_application.rb +101 -1
  285. data/test/test_chase_constraint.rb +122 -0
  286. data/test/test_clipboard.rb +54 -0
  287. data/test/test_color_event.rb +36 -0
  288. data/test/test_color_picker.rb +62 -0
  289. data/test/test_constraint.rb +184 -0
  290. data/test/test_event.rb +1 -1
  291. data/test/test_file_dialog.rb +48 -0
  292. data/test/test_file_event.rb +43 -0
  293. data/test/test_has_text_preedit.rb +54 -0
  294. data/test/test_key_event.rb +23 -2
  295. data/test/test_link_constraint.rb +237 -0
  296. data/test/test_menu.rb +237 -0
  297. data/test/test_pin.rb +65 -0
  298. data/test/test_pointer.rb +50 -26
  299. data/test/test_pointer_event.rb +25 -10
  300. data/test/test_reflex.rb +36 -0
  301. data/test/test_screen.rb +24 -2
  302. data/test/test_snap_constraint.rb +140 -0
  303. data/test/test_text_event.rb +54 -0
  304. data/test/test_view.rb +152 -25
  305. data/test/test_wheel_constraint.rb +125 -0
  306. data/test/test_wheel_event.rb +24 -10
  307. data/test/test_window.rb +205 -43
  308. metadata +154 -10
  309. data/.github/workflows/utils.rb +0 -127
data/src/win32/window.cpp CHANGED
@@ -1,19 +1,28 @@
1
1
  #include "window.h"
2
2
 
3
3
 
4
+ #include <xot/windows.h>
5
+ #include <imm.h>
6
+ #include <dwmapi.h>
7
+ #include <shellapi.h>
8
+
4
9
  #include <assert.h>
10
+ #include <limits.h>
11
+ #include <math.h>
5
12
  #include <map>
6
13
  #include <memory>
7
14
  #include <xot/time.h>
8
- #include <xot/windows.h>
9
15
  #include <rays/rays.h>
10
16
  #include "reflex/defs.h"
11
17
  #include "reflex/exception.h"
12
18
  #include "reflex/debug.h"
19
+ #include "../application.h"
13
20
  #include "../view.h"
14
21
  #include "event.h"
15
- #include "gamepad.h"
22
+ #include "application.h"
23
+ #include "menu.h"
16
24
  #include "screen.h"
25
+ #include "gamepad.h"
17
26
  #include "opengl.h"
18
27
 
19
28
 
@@ -24,17 +33,26 @@ namespace Reflex
24
33
  typedef std::map<int, String> PressingKeyMap;
25
34
 
26
35
 
27
- static const char* WINDOWCLASS = "Reflex:WindowClass";
36
+ static const wchar_t* WINDOWCLASS = L"Reflex:WindowClass";
28
37
 
29
- static const char* USERDATA_PROP = "Reflex:Window:HWND";
38
+ static const wchar_t* USERDATA_PROP = L"Reflex:Window:HWND";
30
39
 
31
40
 
32
41
  struct WindowData : public Window::Data
33
42
  {
34
43
 
35
- HWND hwnd = NULL;
44
+ HWND hwnd = NULL;
45
+
46
+ bool need_rebind = false;
47
+
48
+ bool transparent = false;
36
49
 
37
- bool need_rebind = false;
50
+ bool tracking_mouse = false;
51
+
52
+ // the first half of a surrogate pair, waiting for the second half
53
+ wchar_t pending_surrogate = 0;
54
+
55
+ HACCEL haccel = NULL;
38
56
 
39
57
  OpenGLContext context;
40
58
 
@@ -42,6 +60,17 @@ namespace Reflex
42
60
 
43
61
  mutable String title_tmp;
44
62
 
63
+ virtual ~WindowData ()
64
+ {
65
+ set_accelerator_table(NULL);
66
+ }
67
+
68
+ void set_accelerator_table (HACCEL haccel)
69
+ {
70
+ if (this->haccel) DestroyAcceleratorTable(this->haccel);
71
+ this->haccel = haccel;
72
+ }
73
+
45
74
  bool is_valid () const
46
75
  {
47
76
  return hwnd && IsWindow(hwnd);
@@ -64,12 +93,20 @@ namespace Reflex
64
93
  return get_data(const_cast<Window*>(window));
65
94
  }
66
95
 
96
+ HWND
97
+ Window_get_hwnd (const Window* window)
98
+ {
99
+ if (!window) return NULL;
100
+
101
+ return get_data(window)->hwnd;
102
+ }
103
+
67
104
  static bool
68
105
  window_has_wndproc (HWND hwnd)
69
106
  {
70
107
  if (!hwnd) return false;
71
108
 
72
- WNDPROC proc = (WNDPROC) GetWindowLongPtr(hwnd, GWLP_WNDPROC);
109
+ WNDPROC proc = (WNDPROC) GetWindowLongPtrW(hwnd, GWLP_WNDPROC);
73
110
  if (proc == wndproc)
74
111
  return true;
75
112
 
@@ -77,24 +114,42 @@ namespace Reflex
77
114
  // it can not determine the hwnd is mine or not.
78
115
 
79
116
  enum {BUFSIZE = 256};
80
- char buf[BUFSIZE + 1];
117
+ wchar_t buf[BUFSIZE + 1];
81
118
  if (
82
- GetClassName(hwnd, &buf[0], BUFSIZE) == 0 &&
119
+ GetClassNameW(hwnd, &buf[0], BUFSIZE) == 0 &&
83
120
  GetLastError() != 0)
84
121
  {
85
122
  return false;
86
123
  }
87
124
 
88
- return stricmp(buf, WINDOWCLASS) == 0;
125
+ return _wcsicmp(buf, WINDOWCLASS) == 0;
89
126
  }
90
127
 
91
128
  static Window*
92
129
  get_window_from_hwnd (HWND hwnd)
93
130
  {
94
131
  if (window_has_wndproc(hwnd))
95
- return (Window*) GetWindowLongPtr(hwnd, GWLP_USERDATA);
132
+ return (Window*) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
96
133
  else
97
- return (Window*) GetProp(hwnd, USERDATA_PROP);
134
+ return (Window*) GetPropW(hwnd, USERDATA_PROP);
135
+ }
136
+
137
+ bool
138
+ Window_translate_accelerator (MSG* msg)
139
+ {
140
+ if (!msg || !msg->hwnd) return false;
141
+
142
+ if (msg->message != WM_KEYDOWN && msg->message != WM_SYSKEYDOWN)
143
+ return false;
144
+
145
+ HWND hwnd = GetAncestor(msg->hwnd, GA_ROOT);
146
+ Window* win = get_window_from_hwnd(hwnd);
147
+ if (!win) return false;
148
+
149
+ HACCEL haccel = get_data(win)->haccel;
150
+ if (!haccel) return false;
151
+
152
+ return TranslateAcceleratorW(hwnd, haccel, msg) != 0;
98
153
  }
99
154
 
100
155
  static void
@@ -108,13 +163,13 @@ namespace Reflex
108
163
  if (window_has_wndproc(hwnd))
109
164
  {
110
165
  SetLastError(0);
111
- SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) win);
166
+ SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) win);
112
167
  if (GetLastError() != 0)
113
168
  system_error(__FILE__, __LINE__);
114
169
  }
115
170
  else
116
171
  {
117
- if (!SetProp(hwnd, USERDATA_PROP, (HANDLE) win))
172
+ if (!SetPropW(hwnd, USERDATA_PROP, (HANDLE) win))
118
173
  system_error(__FILE__, __LINE__);
119
174
  }
120
175
 
@@ -155,13 +210,13 @@ namespace Reflex
155
210
  if (window_has_wndproc(self->hwnd))
156
211
  {
157
212
  SetLastError(0);
158
- SetWindowLongPtr(self->hwnd, GWLP_USERDATA, 0);
213
+ SetWindowLongPtrW(self->hwnd, GWLP_USERDATA, 0);
159
214
  if (GetLastError() != 0)
160
215
  system_error(__FILE__, __LINE__);
161
216
  }
162
217
  else
163
218
  {
164
- if (!RemoveProp(self->hwnd, USERDATA_PROP))
219
+ if (!RemovePropW(self->hwnd, USERDATA_PROP))
165
220
  system_error(__FILE__, __LINE__);
166
221
  }
167
222
 
@@ -170,8 +225,162 @@ namespace Reflex
170
225
 
171
226
  win->release();
172
227
 
173
- if (Window_all().empty())
174
- Reflex::app()->quit();
228
+ Application_quit_if_should();
229
+ }
230
+
231
+ static bool
232
+ has_style (HWND hwnd, DWORD style)
233
+ {
234
+ return ((DWORD) GetWindowLongPtrW(hwnd, GWL_STYLE) & style) == style;
235
+ }
236
+
237
+ static bool
238
+ calc_size (LRESULT* result, HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
239
+ {
240
+ if (
241
+ !wp || IsZoomed(hwnd) ||
242
+ has_style(hwnd, WS_CAPTION) || !has_style(hwnd, WS_THICKFRAME))
243
+ {
244
+ return false;
245
+ }
246
+
247
+ // without a caption the sizing border is still drawn at the
248
+ // top of the window, so let the client area cover it
249
+ NCCALCSIZE_PARAMS* params = (NCCALCSIZE_PARAMS*) lp;
250
+ LONG top = params->rgrc[0].top;
251
+ *result = DefWindowProcW(hwnd, msg, wp, lp);
252
+ params->rgrc[0].top = top;
253
+ return true;
254
+ }
255
+
256
+ static int
257
+ border_size ()
258
+ {
259
+ return
260
+ GetSystemMetrics(SM_CYSIZEFRAME) +
261
+ GetSystemMetrics(SM_CXPADDEDBORDER);
262
+ }
263
+
264
+ static bool
265
+ hit_test (LRESULT* result, Window* win, HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
266
+ {
267
+ // the client area covers the sizing border, or there is no border at
268
+ // all, so an edge answers HTCLIENT and cannot resize. this gives the
269
+ // edges back.
270
+ if (has_style(hwnd, WS_CAPTION) || IsZoomed(hwnd))
271
+ return false;
272
+
273
+ LRESULT hit = DefWindowProcW(hwnd, msg, wp, lp);
274
+
275
+ // the sizing border of a window that cannot resize is only there for
276
+ // the shadow, so its edges answer as a plain border
277
+ if (!win || !win->has_flag(Window::FLAG_RESIZABLE))
278
+ {
279
+ *result = HTBORDER;
280
+ return HTSIZEFIRST <= hit && hit <= HTSIZELAST;
281
+ }
282
+
283
+ if (hit != HTCLIENT)
284
+ return false;
285
+
286
+ RECT window;
287
+ if (!GetWindowRect(hwnd, &window))
288
+ system_error(__FILE__, __LINE__);
289
+
290
+ int size = border_size();
291
+ POINT pos = {GET_X_LPARAM(lp), GET_Y_LPARAM(lp)};
292
+ bool left = pos.x < window.left + size;
293
+ bool right = pos.x >= window.right - size;
294
+ bool top = pos.y < window.top + size;
295
+ bool bottom = pos.y >= window.bottom - size;
296
+
297
+ // a sizing border draws the other edges outside the client area
298
+ if (has_style(hwnd, WS_THICKFRAME) && !top)
299
+ return false;
300
+
301
+ int corner = size * 2;
302
+ bool near_l = pos.x < window.left + corner;
303
+ bool near_r = pos.x >= window.right - corner;
304
+ bool near_t = pos.y < window.top + corner;
305
+ bool near_b = pos.y >= window.bottom - corner;
306
+
307
+ if (top && near_l) *result = HTTOPLEFT;
308
+ else if (top && near_r) *result = HTTOPRIGHT;
309
+ else if (bottom && near_l) *result = HTBOTTOMLEFT;
310
+ else if (bottom && near_r) *result = HTBOTTOMRIGHT;
311
+ else if (left && near_t) *result = HTTOPLEFT;
312
+ else if (left && near_b) *result = HTBOTTOMLEFT;
313
+ else if (right && near_t) *result = HTTOPRIGHT;
314
+ else if (right && near_b) *result = HTBOTTOMRIGHT;
315
+ else if (top) *result = HTTOP;
316
+ else if (bottom) *result = HTBOTTOM;
317
+ else if (left) *result = HTLEFT;
318
+ else if (right) *result = HTRIGHT;
319
+ else return false;
320
+
321
+ return true;
322
+ }
323
+
324
+ static void
325
+ keep_caption_on_screen (HWND hwnd, RECT* rect)
326
+ {
327
+ if (!has_style(hwnd, WS_CAPTION)) return;
328
+
329
+ HMONITOR hmonitor = MonitorFromRect(rect, MONITOR_DEFAULTTONEAREST);
330
+ if (!hmonitor) return;
331
+
332
+ MONITORINFO info = {sizeof(info)};
333
+ if (!GetMonitorInfoW(hmonitor, &info))
334
+ system_error(__FILE__, __LINE__);
335
+
336
+ LONG over = info.rcWork.top - rect->top;
337
+ if (over <= 0) return;
338
+
339
+ rect->top += over;
340
+ rect->bottom += over;
341
+ }
342
+
343
+ static bool
344
+ window_pos_changing (
345
+ LRESULT* result, Window* win, HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
346
+ {
347
+ WINDOWPOS* pos = (WINDOWPOS*) lp;
348
+
349
+ if (win && win->has_flag(Window::FLAG_ALWAYS_ON_BOTTOM))
350
+ {
351
+ pos->hwndInsertAfter = HWND_BOTTOM;
352
+ pos->flags &= ~SWP_NOZORDER;
353
+ }
354
+
355
+ if (!(pos->flags & SWP_SHOWWINDOW) && !IsWindowVisible(hwnd))
356
+ {
357
+ // a hidden window gets the size it asks for, but windows widens
358
+ // a captioned one here, so take the width back
359
+ int cx = pos->cx;
360
+ *result = DefWindowProcW(hwnd, msg, wp, lp);
361
+ pos->cx = cx;
362
+ return true;
363
+ }
364
+
365
+ if (!(pos->flags & SWP_NOMOVE) && !IsIconic(hwnd) && !IsZoomed(hwnd))
366
+ {
367
+ // once on screen the caption has to stay where it can be grabbed
368
+ RECT rect = {pos->x, pos->y, pos->x + pos->cx, pos->y + pos->cy};
369
+ keep_caption_on_screen(hwnd, &rect);
370
+ pos->y = rect.top;
371
+ }
372
+
373
+ return false;
374
+ }
375
+
376
+ static void
377
+ update_accelerator_table (Window* win)
378
+ {
379
+ Menu* menu = win->menu();
380
+ if (!menu || !Menu_is_modified(menu))
381
+ return;
382
+
383
+ get_data(win)->set_accelerator_table(Menu_create_accelerator_table(menu));
175
384
  }
176
385
 
177
386
  void
@@ -179,11 +388,12 @@ namespace Reflex
179
388
  {
180
389
  WindowData* self = get_data(win);
181
390
 
391
+ update_accelerator_table(win);
182
392
  Gamepad_poll();
183
393
 
184
394
  Window_call_update_event(win);
185
395
 
186
- if (self->redraw)
396
+ if (Xot::check_and_remove_flag(&self->flags, Window::Data::REDRAW))
187
397
  {
188
398
  #if 1
189
399
  InvalidateRect(self->hwnd, NULL, FALSE);
@@ -191,8 +401,6 @@ namespace Reflex
191
401
  RedrawWindow(
192
402
  self->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | EDW_ALLCHILDREN);
193
403
  #endif
194
-
195
- self->redraw = false;
196
404
  }
197
405
  }
198
406
 
@@ -219,32 +427,51 @@ namespace Reflex
219
427
  Window_call_draw_event(win, &e);
220
428
  }
221
429
 
222
- static void
223
- frame_changed (Window* win)
430
+ static String
431
+ get_chars (WindowData* self, UINT msg)
224
432
  {
225
- Rays::Bounds b = win->frame();
226
- Rays::Point dpos = b.position() - win->self->prev_position;
227
- Rays::Point dsize = b.size() - win->self->prev_size;
228
- win->self->prev_position = b.position();
229
- win->self->prev_size = b.size();
230
-
231
- if (dpos == 0 && dsize == 0) return;
232
-
233
- Reflex::FrameEvent e(b, dpos.x, dpos.y, 0, dsize.x, dsize.y, 0);
234
- if (dpos != 0) win->on_move(&e);
235
- if (dsize != 0)
433
+ MSG wmchar;
434
+ UINT filter = msg == WM_SYSKEYDOWN ? WM_SYSCHAR : WM_CHAR;
435
+ BOOL peeked = PeekMessageW(&wmchar, self->hwnd, filter, filter, PM_NOREMOVE);
436
+ if (peeked)
236
437
  {
237
- Rays::Bounds b = win->frame();
238
- b.move_to(0, 0);
239
-
240
- if (win->painter())
241
- win->painter()->canvas(b, win->painter()->pixel_density());
438
+ // a character outside the BMP is injected as one VK_PACKET per
439
+ // UTF-16 code unit, so the halves of the surrogate pair arrive in
440
+ // two key events and the first of them has to be held back until
441
+ // the second one completes the character.
442
+ wchar_t unit = (wchar_t) wmchar.wParam;
443
+ wchar_t pending = self->pending_surrogate;
444
+ self->pending_surrogate = 0;
445
+
446
+ if (IS_HIGH_SURROGATE(unit))
447
+ self->pending_surrogate = unit;
448
+ else if (IS_LOW_SURROGATE(unit))
449
+ {
450
+ if (pending)
451
+ {
452
+ wchar_t pair[] = {pending, unit};
453
+ return String(pair, 2);
454
+ }
455
+ }
456
+ else
457
+ return String(&unit, 1);
458
+ }
459
+ return "";
460
+ }
242
461
 
243
- if (win->root())
244
- View_set_frame(win->root(), b);
462
+ static bool
463
+ accepts_text_input (Window* win)
464
+ {
465
+ const View* focus = win->focus();
466
+ return focus && focus->accepts_text_input();
467
+ }
245
468
 
246
- win->on_resize(&e);
247
- }
469
+ // macos never commits a control character, so it is not text here either
470
+ static bool
471
+ is_committable (const String& chars)
472
+ {
473
+ uchar c = chars.empty() ? 0 : (uchar) chars[0];
474
+ return c >= 0x20 && c != 0x7f;
248
475
  }
249
476
 
250
477
  static void
@@ -252,23 +479,26 @@ namespace Reflex
252
479
  {
253
480
  assert(*win);
254
481
 
255
- WindowData* self = get_data(win);
482
+ if (wp == VK_PROCESSKEY) return;
256
483
 
257
- MSG wmchar;
258
- UINT filter = msg == WM_SYSKEYDOWN ? WM_SYSCHAR : WM_CHAR;
259
- BOOL peeked = PeekMessage(&wmchar, self->hwnd, filter, filter, PM_NOREMOVE);
484
+ WindowData* self = get_data(win);
260
485
 
261
- String chars;
262
- if (peeked) chars += (char) wmchar.wParam;
486
+ String chars = get_chars(self, msg);
263
487
 
264
488
  NativeKeyEvent e(msg, wp, lp, chars);
265
-
266
489
  self->pressing_keys.insert_or_assign(e.code(), chars);
490
+
267
491
  #if 0
268
492
  for (auto kv : self->pressing_keys)
269
493
  doutln("0x%x : %s", kv.first, (const char*) kv.second);
270
494
  #endif
271
495
 
496
+ if (accepts_text_input(win) && is_committable(chars))
497
+ {
498
+ TextEvent te(TextEvent::COMMIT, chars);
499
+ Window_call_text_event(win, &te);
500
+ }
501
+
272
502
  Window_call_key_event(win, &e);
273
503
  }
274
504
 
@@ -283,11 +513,73 @@ namespace Reflex
283
513
 
284
514
  auto it = self->pressing_keys.find(e.code());
285
515
  if (it != self->pressing_keys.end())
516
+ {
286
517
  KeyEvent_set_chars(&e, it->second);
518
+ self->pressing_keys.erase(it);
519
+ }
287
520
 
288
521
  Window_call_key_event(win, &e);
522
+ }
523
+
524
+ static void
525
+ update_composition (Window* win, LPARAM lp)
526
+ {
527
+ assert(*win);
528
+
529
+ WindowData* self = get_data(win);
530
+
531
+ HIMC himc = ImmGetContext(self->hwnd);
532
+ if (!himc) return;
533
+
534
+ if (lp & GCS_RESULTSTR)
535
+ {
536
+ NativeTextEvent e(TextEvent::COMMIT, himc, GCS_RESULTSTR);
537
+ Window_call_text_event(win, &e, true);
538
+ }
539
+
540
+ if (lp & GCS_COMPSTR)
541
+ {
542
+ NativeTextEvent e(TextEvent::PREEDIT, himc, GCS_COMPSTR);
543
+ Window_call_text_event(win, &e);
544
+ }
545
+
546
+ ImmReleaseContext(self->hwnd, himc);
547
+ }
548
+
549
+ static void
550
+ clear_composition (Window* win)
551
+ {
552
+ assert(*win);
553
+
554
+ TextEvent e(TextEvent::PREEDIT, "");
555
+ Window_call_text_event(win, &e);
556
+ }
557
+
558
+ static void
559
+ update_ime_position (Window* win)
560
+ {
561
+ assert(*win);
562
+
563
+ const View* focus = win->focus();
564
+ if (!focus) return;
565
+
566
+ WindowData* self = get_data(win);
567
+
568
+ HIMC himc = ImmGetContext(self->hwnd);
569
+ if (!himc) return;
570
+
571
+ Bounds b = focus->text_input_bounds();
572
+ POINT p1 = Window_to_native_coord(*win, focus->to_window(b.position()));
573
+ POINT p2 = Window_to_native_coord(*win, focus->to_window(b.position() + b.size()));
289
574
 
290
- if (it != self->pressing_keys.end()) self->pressing_keys.erase(it);
575
+ CANDIDATEFORM form = {0};
576
+ form.dwIndex = 0;
577
+ form.dwStyle = CFS_EXCLUDE;
578
+ form.ptCurrentPos = {(LONG) p1.x, (LONG) p2.y};
579
+ form.rcArea = {(LONG) p1.x, (LONG) p1.y, (LONG) p2.x, (LONG) p2.y};
580
+ ImmSetCandidateWindow(himc, &form);
581
+
582
+ ImmReleaseContext(self->hwnd, himc);
291
583
  }
292
584
 
293
585
  #ifndef MOUSEEVENTF_FROMTOUCH
@@ -300,13 +592,76 @@ namespace Reflex
300
592
  return (GetMessageExtraInfo() & 0xffffff00) == MOUSEEVENTF_FROMTOUCH;
301
593
  }
302
594
 
595
+ static void
596
+ capture_mouse_on_button_down (HWND hwnd, UINT msg)
597
+ {
598
+ switch (msg)
599
+ {
600
+ case WM_LBUTTONDOWN:
601
+ case WM_RBUTTONDOWN:
602
+ case WM_MBUTTONDOWN:
603
+ case WM_LBUTTONDBLCLK:
604
+ case WM_RBUTTONDBLCLK:
605
+ case WM_MBUTTONDBLCLK:
606
+ SetCapture(hwnd);
607
+ break;
608
+ }
609
+ }
610
+
611
+ static void
612
+ release_mouse_on_button_up (HWND hwnd, UINT msg, WPARAM wp)
613
+ {
614
+ switch (msg)
615
+ {
616
+ case WM_LBUTTONUP:
617
+ case WM_RBUTTONUP:
618
+ case WM_MBUTTONUP:
619
+ if (
620
+ (wp & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)) == 0 &&
621
+ hwnd == GetCapture())
622
+ {
623
+ ReleaseCapture();
624
+ }
625
+ break;
626
+ }
627
+ }
628
+
303
629
  static void
304
630
  mouse (Window* win, UINT msg, WPARAM wp, LPARAM lp)
305
631
  {
306
632
  if (is_from_touch_event()) return;
307
633
 
308
- NativePointerEvent e(msg, wp, lp);
634
+ WindowData* self = get_data(win);
635
+
636
+ if (msg == WM_MOUSEMOVE && !self->tracking_mouse)
637
+ {
638
+ TRACKMOUSEEVENT tme = {sizeof(tme), TME_LEAVE, self->hwnd, 0};
639
+ TrackMouseEvent(&tme);
640
+ self->tracking_mouse = true;
641
+
642
+ // Win32 has no mouse-enter message; the first move after (re)arming
643
+ // the leave tracking is when the mouse entered the window.
644
+ NativePointerEvent e(*win, msg, wp, lp, Pointer::ENTER);
645
+ Window_call_pointer_event(win, &e);
646
+ }
647
+ else if (msg == WM_MOUSELEAVE)
648
+ {
649
+ self->tracking_mouse = false;
650
+
651
+ POINT pt;
652
+ if (GetCursorPos(&pt) && ScreenToClient(self->hwnd, &pt))
653
+ lp = MAKELPARAM(pt.x, pt.y);
654
+ }
655
+
656
+ // before the press is handled: a popup menu opened by it takes the
657
+ // capture away and eats the release, which would never come to let
658
+ // go of a capture taken after
659
+ capture_mouse_on_button_down(self->hwnd, msg);
660
+
661
+ NativePointerEvent e(*win, msg, wp, lp);
309
662
  Window_call_pointer_event(win, &e);
663
+
664
+ release_mouse_on_button_up(self->hwnd, msg, wp);
310
665
  }
311
666
 
312
667
  static void
@@ -321,18 +676,18 @@ namespace Reflex
321
676
  {
322
677
  wp_y = wp;
323
678
  MSG m;
324
- if (PeekMessage(&m, self->hwnd, WM_MOUSEHWHEEL, WM_MOUSEHWHEEL, PM_REMOVE))
679
+ if (PeekMessageW(&m, self->hwnd, WM_MOUSEHWHEEL, WM_MOUSEHWHEEL, PM_REMOVE))
325
680
  wp_x = m.wParam;
326
681
  }
327
682
  else
328
683
  {
329
684
  wp_x = wp;
330
685
  MSG m;
331
- if (PeekMessage(&m, self->hwnd, WM_MOUSEWHEEL, WM_MOUSEWHEEL, PM_REMOVE))
686
+ if (PeekMessageW(&m, self->hwnd, WM_MOUSEWHEEL, WM_MOUSEWHEEL, PM_REMOVE))
332
687
  wp_y = m.wParam;
333
688
  }
334
689
 
335
- NativeWheelEvent e(wp_x, wp_y, lp);
690
+ NativeWheelEvent e(*win, wp_x, wp_y, lp);
336
691
  Window_call_wheel_event(win, &e);
337
692
  }
338
693
 
@@ -350,7 +705,7 @@ namespace Reflex
350
705
  if (!GetTouchInputInfo(handle, size, &touches[0], sizeof(TOUCHINPUT)))
351
706
  return;
352
707
 
353
- NativePointerEvent e(self->hwnd, &touches[0], size);
708
+ NativePointerEvent e(*win, &touches[0], size);
354
709
  Window_call_pointer_event(win, &e);
355
710
 
356
711
  CloseTouchInputHandle(handle);
@@ -360,12 +715,36 @@ namespace Reflex
360
715
  window_proc (Window* win, HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
361
716
  {
362
717
  if (!win || !*win || hwnd != get_data(win)->hwnd)
363
- return DefWindowProc(hwnd, msg, wp, lp);
718
+ return DefWindowProcW(hwnd, msg, wp, lp);
364
719
 
365
720
  WindowData* self = get_data(win);
366
721
 
367
722
  switch (msg)
368
723
  {
724
+ case WM_NCCALCSIZE:
725
+ {
726
+ LRESULT result = 0;
727
+ if (calc_size(&result, hwnd, msg, wp, lp))
728
+ return result;
729
+ break;
730
+ }
731
+
732
+ case WM_NCHITTEST:
733
+ {
734
+ LRESULT result = 0;
735
+ if (hit_test(&result, win, hwnd, msg, wp, lp))
736
+ return result;
737
+ break;
738
+ }
739
+
740
+ case WM_WINDOWPOSCHANGING:
741
+ {
742
+ LRESULT result = 0;
743
+ if (window_pos_changing(&result, win, hwnd, msg, wp, lp))
744
+ return result;
745
+ break;
746
+ }
747
+
369
748
  case WM_ACTIVATE:
370
749
  {
371
750
  if (LOWORD(wp) == WA_INACTIVE)
@@ -380,10 +759,40 @@ namespace Reflex
380
759
 
381
760
  case WM_CLOSE:
382
761
  {
383
- win->close();
762
+ Window_call_close(win);
384
763
  return 0;
385
764
  }
386
765
 
766
+ case WM_COMMAND:
767
+ {
768
+ if (HIWORD(wp) == 1)
769
+ {
770
+ // menu items are reported by WM_MENUCOMMAND (MNS_NOTIFYBYPOS),
771
+ // so only accelerators (HIWORD == 1) arrive here
772
+ Menu_call_command_event(win->menu(), LOWORD(wp));
773
+ return 0;
774
+ }
775
+ break;
776
+ }
777
+
778
+ case WM_MENUCOMMAND:
779
+ {
780
+ Menu_call_command_event((HMENU) lp, (uint) wp);
781
+ return 0;
782
+ }
783
+
784
+ case WM_INITMENUPOPUP:
785
+ {
786
+ Menu_call_open_event((HMENU) wp);
787
+ break;
788
+ }
789
+
790
+ case WM_UNINITMENUPOPUP:
791
+ {
792
+ Menu_call_close_event((HMENU) wp);
793
+ break;
794
+ }
795
+
387
796
  case WM_PAINT:
388
797
  {
389
798
  self->context.make_current();
@@ -406,13 +815,13 @@ namespace Reflex
406
815
 
407
816
  case WM_MOVE:
408
817
  {
409
- frame_changed(win);
818
+ Window_call_frame_event(win);
410
819
  break;
411
820
  }
412
821
 
413
822
  case WM_SIZE:
414
823
  {
415
- frame_changed(win);
824
+ Window_call_frame_event(win);
416
825
  break;
417
826
  }
418
827
 
@@ -430,6 +839,31 @@ namespace Reflex
430
839
  break;
431
840
  }
432
841
 
842
+ case WM_IME_STARTCOMPOSITION:
843
+ {
844
+ if (!accepts_text_input(win)) break;
845
+
846
+ update_ime_position(win);
847
+ return 0;
848
+ }
849
+
850
+ case WM_IME_ENDCOMPOSITION:
851
+ {
852
+ if (!accepts_text_input(win)) break;
853
+
854
+ clear_composition(win);
855
+ return 0;
856
+ }
857
+
858
+ case WM_IME_COMPOSITION:
859
+ {
860
+ if (!accepts_text_input(win)) break;
861
+
862
+ update_composition(win, lp);
863
+ update_ime_position(win);
864
+ return 0;
865
+ }
866
+
433
867
  case WM_LBUTTONDOWN:
434
868
  case WM_RBUTTONDOWN:
435
869
  case WM_MBUTTONDOWN:
@@ -440,6 +874,7 @@ namespace Reflex
440
874
  case WM_RBUTTONUP:
441
875
  case WM_MBUTTONUP:
442
876
  case WM_MOUSEMOVE:
877
+ case WM_MOUSELEAVE:
443
878
  {
444
879
  mouse(win, msg, wp, lp);
445
880
  break;
@@ -452,6 +887,12 @@ namespace Reflex
452
887
  break;
453
888
  }
454
889
 
890
+ case WM_CAPTURECHANGED:
891
+ {
892
+ Window_cancel_active_pointers(win);
893
+ break;
894
+ }
895
+
455
896
  case WM_TOUCH:
456
897
  {
457
898
  touch(win, msg, wp, lp);
@@ -468,29 +909,51 @@ namespace Reflex
468
909
  }
469
910
  }
470
911
 
471
- return DefWindowProc(hwnd, msg, wp, lp);
912
+ return DefWindowProcW(hwnd, msg, wp, lp);
472
913
  }
473
914
 
474
915
  static LRESULT CALLBACK
475
916
  wndproc (HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
476
917
  {
477
- Window* win = NULL;
478
- if (msg == WM_NCCREATE)
918
+ if (Application_has_exception())
919
+ return DefWindowProcW(hwnd, msg, wp, lp);
920
+
921
+ return Application_guard([&]() -> LRESULT
479
922
  {
480
- CREATESTRUCT* cs = (CREATESTRUCT*) lp;
481
- win = (Window*) cs->lpCreateParams;
482
- setup_window(win, hwnd);
483
- }
923
+ Window* win = NULL;
924
+ if (msg == WM_NCCREATE)
925
+ {
926
+ CREATESTRUCTW* cs = (CREATESTRUCTW*) lp;
927
+ win = (Window*) cs->lpCreateParams;
928
+ setup_window(win, hwnd);
929
+ }
484
930
 
485
- if (!win) win = get_window_from_hwnd(hwnd);
486
- if (win) rebind(win);
931
+ if (!win) win = get_window_from_hwnd(hwnd);
932
+ if (win) rebind(win);
487
933
 
488
- LRESULT ret = window_proc(win, hwnd, msg, wp, lp);
934
+ LRESULT ret = window_proc(win, hwnd, msg, wp, lp);
489
935
 
490
- if (msg == WM_NCDESTROY)
491
- cleanup_window(win);
936
+ if (msg == WM_NCDESTROY)
937
+ cleanup_window(win);
492
938
 
493
- return ret;
939
+ return ret;
940
+ }, (LRESULT) 0);
941
+ }
942
+
943
+ static void
944
+ load_app_icons (HICON* large, HICON* small)
945
+ {
946
+ // the icons Explorer shows for the host executable, if it has any;
947
+ // NULL leaves the window with the default icon
948
+ *large = *small = NULL;
949
+
950
+ wchar_t path[32768];
951
+ if (GetModuleFileNameW(NULL, path, sizeof(path) / sizeof(path[0])) == 0)
952
+ return;
953
+
954
+ UINT n = ExtractIconExW(path, 0, large, small, 1);
955
+ if (n == 0 || n == UINT_MAX)
956
+ *large = *small = NULL;
494
957
  }
495
958
 
496
959
  static void
@@ -499,22 +962,21 @@ namespace Reflex
499
962
  static bool registered = false;
500
963
  if (registered) return;
501
964
 
502
- WNDCLASSEX wc;
965
+ WNDCLASSEXW wc;
503
966
  memset(&wc, 0, sizeof(wc));
504
-
505
967
  wc.cbSize = sizeof(wc);
506
968
  wc.lpszClassName = WINDOWCLASS;
507
969
  wc.lpfnWndProc = (WNDPROC) wndproc;
508
970
  wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;
509
- wc.hInstance = GetModuleHandle(NULL);
510
- //wc.hIcon = LoadIcon(wc.hInstance, IDI_APP_LARGE);
511
- //wc.hIconSm = LoadIcon(wc.hInstance, IDI_APP_SMALL);
512
- wc.hCursor = LoadCursor(NULL, IDC_ARROW);
971
+ wc.hInstance = GetModuleHandleW(NULL);
972
+ wc.hCursor = LoadCursorW(NULL, (LPCWSTR) IDC_ARROW);
513
973
  wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
514
974
  wc.lpszMenuName = NULL;
515
975
  wc.cbWndExtra = sizeof(Window*);
516
976
 
517
- if (!RegisterClassEx(&wc))
977
+ load_app_icons(&wc.hIcon, &wc.hIconSm);
978
+
979
+ if (!RegisterClassExW(&wc))
518
980
  system_error(__FILE__, __LINE__);
519
981
 
520
982
  registered = true;
@@ -527,10 +989,10 @@ namespace Reflex
527
989
  invalid_state_error(__FILE__, __LINE__);
528
990
 
529
991
  register_windowclass();
530
- HWND hwnd = CreateWindowEx(
531
- 0, WINDOWCLASS, "",
992
+ HWND hwnd = CreateWindowExW(
993
+ 0, WINDOWCLASS, L"",
532
994
  WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
533
- 0, 0, 0, 0, NULL, NULL, GetModuleHandle(NULL), win);
995
+ 0, 0, 0, 0, NULL, NULL, GetModuleHandleW(NULL), win);
534
996
  if (!hwnd)
535
997
  system_error(__FILE__, __LINE__);
536
998
 
@@ -552,9 +1014,12 @@ namespace Reflex
552
1014
  Window_default_flags ()
553
1015
  {
554
1016
  return
555
- Window::FLAG_CLOSABLE |
556
- Window::FLAG_RESIZABLE |
557
- Window::FLAG_MINIMIZABLE;
1017
+ Window::FLAG_CLOSABLE |
1018
+ Window::FLAG_MINIMIZABLE |
1019
+ Window::FLAG_RESIZABLE |
1020
+ Window::FLAG_TITLEBAR_BUTTONS |
1021
+ Window::FLAG_TITLEBAR_BACKGROUND |
1022
+ Window::FLAG_SHADOW;
558
1023
  }
559
1024
 
560
1025
  void
@@ -573,9 +1038,15 @@ namespace Reflex
573
1038
 
574
1039
  WindowData* self = get_data(window);
575
1040
 
1041
+ // the frame is passed so that WM_WINDOWPOSCHANGING can correct it
1042
+ RECT rect = {0};
1043
+ if (!GetWindowRect(self->hwnd, &rect))
1044
+ system_error(__FILE__, __LINE__);
1045
+
576
1046
  SetWindowPos(
577
- self->hwnd, HWND_TOP, 0, 0, 0, 0,
578
- SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);
1047
+ self->hwnd, HWND_TOP,
1048
+ rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
1049
+ SWP_SHOWWINDOW);
579
1050
  UpdateWindow(self->hwnd);
580
1051
  }
581
1052
 
@@ -610,7 +1081,7 @@ namespace Reflex
610
1081
 
611
1082
  if (!*window) return;
612
1083
 
613
- if (!SetWindowText(get_data(window)->hwnd, title))
1084
+ if (!SetWindowTextW(get_data(window)->hwnd, String(title).to_wstr().c_str()))
614
1085
  system_error(__FILE__, __LINE__);
615
1086
  }
616
1087
 
@@ -621,75 +1092,80 @@ namespace Reflex
621
1092
 
622
1093
  const WindowData* self = get_data(&window);
623
1094
 
624
- int size = GetWindowTextLength(self->hwnd);
1095
+ int size = GetWindowTextLengthW(self->hwnd);
625
1096
  if (size <= 0) return "";
626
1097
 
627
- std::unique_ptr<char[]> buf(new char[size + 1]);
628
- if (GetWindowText(self->hwnd, &buf[0], size + 1) != size)
1098
+ std::unique_ptr<wchar_t[]> buf(new wchar_t[size + 1]);
1099
+ if (GetWindowTextW(self->hwnd, &buf[0], size + 1) != size)
629
1100
  return "";
630
1101
 
631
- self->title_tmp = &buf[0];
1102
+ self->title_tmp = String(&buf[0], size);
632
1103
  return self->title_tmp.c_str();
633
1104
  }
634
1105
 
635
- static void
636
- get_window_bounds (
637
- HWND hwnd, coord* x, coord* y, coord* width, coord* height,
638
- coord* nonclient_width = NULL, coord* nonclient_height = NULL)
1106
+ static RECT
1107
+ get_client_rect (HWND hwnd)
639
1108
  {
640
- if (!x && !y && !width && !height && !nonclient_width && !nonclient_height)
641
- argument_error(__FILE__, __LINE__);
642
-
643
- RECT window;
644
- if (!GetWindowRect(hwnd, &window))
645
- system_error(__FILE__, __LINE__);
646
-
647
1109
  RECT client;
648
1110
  if (!GetClientRect(hwnd, &client))
649
1111
  system_error(__FILE__, __LINE__);
650
1112
 
651
- if (x) *x = window.left;
652
- if (y) *y = window.top;
653
- if (width) *width = client.right - client.left;
654
- if (height) *height = client.bottom - client.top;
1113
+ POINT pos = {client.left, client.top};
1114
+ if (!ClientToScreen(hwnd, &pos))
1115
+ system_error(__FILE__, __LINE__);
655
1116
 
656
- if (nonclient_width)
657
- {
658
- coord ww = window.right - window.left;
659
- coord cw = client.right - client.left;
660
- *nonclient_width = ww - cw;
661
- }
662
- if (nonclient_height)
663
- {
664
- coord wh = window.bottom - window.top;
665
- coord ch = client.bottom - client.top;
666
- *nonclient_height = wh - ch;
667
- }
1117
+ return {
1118
+ pos.x,
1119
+ pos.y,
1120
+ pos.x + (client.right - client.left),
1121
+ pos.y + (client.bottom - client.top)
1122
+ };
1123
+ }
1124
+
1125
+ static void
1126
+ client_to_window_rect (HWND hwnd, RECT* rect)
1127
+ {
1128
+ DWORD style = (DWORD) GetWindowLongPtrW(hwnd, GWL_STYLE);
1129
+ DWORD exstyle = (DWORD) GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
1130
+
1131
+ LONG top = rect->top;
1132
+ if (!AdjustWindowRectEx(rect, style, GetMenu(hwnd) != NULL, exstyle))
1133
+ system_error(__FILE__, __LINE__);
1134
+
1135
+ // WM_NCCALCSIZE gives the top of the sizing border to the client area
1136
+ // when there is no caption, so it takes no room here either
1137
+ if (!has_style(hwnd, WS_CAPTION)) rect->top = top;
668
1138
  }
669
1139
 
670
1140
  void
671
- Window_set_frame (Window* window, coord x, coord y, coord width, coord height)
1141
+ Window_set_frame (Window* window, coord x, coord y, coord w, coord h)
672
1142
  {
673
1143
  if (!*window) return;
674
1144
 
675
- WindowData* self = get_data(window);
1145
+ HWND hwnd = get_data(window)->hwnd;
676
1146
 
677
- coord xx, yy, ww, hh, nonclient_w, nonclient_h;
678
- get_window_bounds(
679
- self->hwnd, &xx, &yy, &ww, &hh, &nonclient_w, &nonclient_h);
1147
+ RECT now = get_client_rect(hwnd);
1148
+ RECT rect = Screen_to_native_coord(x, y, w, h);
1149
+ UINT flags = 0;
680
1150
 
681
- width += nonclient_w;
682
- height += nonclient_h;
1151
+ if (rect.left == now.left && rect.top == now.top)
1152
+ flags |= SWP_NOMOVE;
683
1153
 
684
- UINT flags = 0;
685
- if (x == xx && y == yy) flags |= SWP_NOMOVE;
686
- if (width == ww && height == hh) flags |= SWP_NOSIZE;
1154
+ if (
1155
+ rect.right - rect.left == now.right - now.left &&
1156
+ rect.bottom - rect.top == now.bottom - now.top)
1157
+ {
1158
+ flags |= SWP_NOSIZE;
1159
+ }
687
1160
 
688
1161
  if (flags == (SWP_NOMOVE | SWP_NOSIZE))
689
1162
  return;
690
1163
 
1164
+ client_to_window_rect(hwnd, &rect);
1165
+
691
1166
  if (!SetWindowPos(
692
- self->hwnd, NULL, (int) x, (int) y, (int) width, (int) height,
1167
+ hwnd, NULL,
1168
+ rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
693
1169
  flags | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER))
694
1170
  {
695
1171
  system_error(__FILE__, __LINE__);
@@ -702,9 +1178,20 @@ namespace Reflex
702
1178
  if (!window)
703
1179
  invalid_state_error(__FILE__, __LINE__);
704
1180
 
705
- coord x, y, w, h;
706
- get_window_bounds(get_data(&window)->hwnd, &x, &y, &w, &h);
707
- return Bounds(x, y, w, h);
1181
+ return Screen_from_native_coord(get_client_rect(get_data(&window)->hwnd));
1182
+ }
1183
+
1184
+ void
1185
+ Window_set_menu (Window* window, Menu* menu)
1186
+ {
1187
+ WindowData* self = get_data(window);
1188
+ HWND hwnd = self->hwnd;
1189
+ if (!hwnd) return;
1190
+
1191
+ SetMenu(hwnd, menu ? Menu_get_hmenu(menu, true) : NULL);
1192
+ DrawMenuBar(hwnd);
1193
+
1194
+ self->set_accelerator_table(menu ? Menu_create_accelerator_table(menu) : NULL);
708
1195
  }
709
1196
 
710
1197
  Screen
@@ -717,20 +1204,256 @@ namespace Reflex
717
1204
  return s;
718
1205
  }
719
1206
 
1207
+ static void
1208
+ set_background_transparent (HWND hwnd, bool transparent)
1209
+ {
1210
+ std::shared_ptr<HRGN__> region;
1211
+ if (transparent)
1212
+ {
1213
+ region.reset(CreateRectRgn(0, 0, -1, -1), DeleteObject);// no blur
1214
+ if (!region)
1215
+ system_error(__FILE__, __LINE__);
1216
+ }
1217
+
1218
+ DWM_BLURBEHIND blur = {0};
1219
+ blur.dwFlags = DWM_BB_ENABLE | (transparent ? DWM_BB_BLURREGION : 0);
1220
+ blur.fEnable = transparent ? TRUE : FALSE;
1221
+ blur.hRgnBlur = region.get();
1222
+ HRESULT result = DwmEnableBlurBehindWindow(hwnd, &blur);
1223
+ if (FAILED(result))
1224
+ {
1225
+ system_error(
1226
+ __FILE__, __LINE__,
1227
+ "DwmEnableBlurBehindWindow failed (0x%08x)", (uint) result);
1228
+ }
1229
+ }
1230
+
1231
+ static DWORD
1232
+ make_window_style (uint flags, DWORD style)
1233
+ {
1234
+ using namespace Xot;
1235
+
1236
+ bool caption = has_flag(flags, Window::FLAG_TITLEBAR_BACKGROUND);
1237
+
1238
+ // the sizing border follows resizable, but a captionless window keeps
1239
+ // it for the shadow and leaves the resizing to the hit test
1240
+ bool sizing_border = caption
1241
+ ? has_flag(flags, Window::FLAG_RESIZABLE)
1242
+ : has_flag(flags, Window::FLAG_SHADOW);
1243
+
1244
+ update_flag(&style, WS_SYSMENU, has_flag(flags, Window::FLAG_TITLEBAR_BUTTONS));
1245
+ update_flag(&style, WS_CAPTION, caption);
1246
+ update_flag(&style, WS_MINIMIZEBOX, has_flag(flags, Window::FLAG_MINIMIZABLE));
1247
+ update_flag(&style, WS_MAXIMIZEBOX, has_flag(flags, Window::FLAG_RESIZABLE));
1248
+ update_flag(&style, WS_THICKFRAME, sizing_border);
1249
+
1250
+ return style;
1251
+ }
1252
+
1253
+ static void
1254
+ set_window_style (HWND hwnd, DWORD style)
1255
+ {
1256
+ RECT client = {0};
1257
+ bool normal = !IsIconic(hwnd) && !IsZoomed(hwnd) && GetClientRect(hwnd, &client);
1258
+
1259
+ POINT pos = {0, 0};
1260
+ if (normal && !ClientToScreen(hwnd, &pos))
1261
+ system_error(__FILE__, __LINE__);
1262
+
1263
+ RECT rect = {
1264
+ pos.x, pos.y,
1265
+ pos.x + (client.right - client.left),
1266
+ pos.y + (client.bottom - client.top)
1267
+ };
1268
+
1269
+ SetLastError(0);
1270
+ SetWindowLongPtrW(hwnd, GWL_STYLE, style);
1271
+ if (GetLastError() != 0)
1272
+ system_error(__FILE__, __LINE__);
1273
+
1274
+ UINT posflags = SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE;
1275
+ if (normal)
1276
+ client_to_window_rect(hwnd, &rect);
1277
+ else
1278
+ posflags |= SWP_NOMOVE | SWP_NOSIZE;
1279
+
1280
+ if (!SetWindowPos(
1281
+ hwnd, NULL,
1282
+ rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
1283
+ posflags))
1284
+ {
1285
+ system_error(__FILE__, __LINE__);
1286
+ }
1287
+ }
1288
+
1289
+ static void
1290
+ set_closable (HWND hwnd, bool closable)
1291
+ {
1292
+ // the close button follows the state of the close menu item
1293
+ HMENU menu = GetSystemMenu(hwnd, FALSE);
1294
+ if (!menu) return;
1295
+
1296
+ EnableMenuItem(
1297
+ menu, SC_CLOSE, MF_BYCOMMAND | (closable ? MF_ENABLED : MF_GRAYED));
1298
+ }
1299
+
1300
+ static void
1301
+ set_unlisted (HWND hwnd, bool state)
1302
+ {
1303
+ DWORD current = (DWORD) GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
1304
+ DWORD exstyle = state
1305
+ ? current | WS_EX_TOOLWINDOW
1306
+ : current & ~WS_EX_TOOLWINDOW;
1307
+ if (exstyle == current) return;
1308
+
1309
+ SetLastError(0);
1310
+ SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exstyle);
1311
+ if (GetLastError() != 0)
1312
+ system_error(__FILE__, __LINE__);
1313
+ }
1314
+
720
1315
  void
721
1316
  Window_set_flags (Window* window, uint flags)
722
1317
  {
1318
+ if (
1319
+ Xot::has_flag(flags, Window::FLAG_TITLEBAR_BUTTONS) &&
1320
+ !Xot::has_flag(flags, Window::FLAG_TITLEBAR_BACKGROUND))
1321
+ {
1322
+ argument_error(
1323
+ __FILE__, __LINE__, "FLAG_TITLEBAR_BUTTONS needs FLAG_TITLEBAR_BACKGROUND");
1324
+ }
1325
+
1326
+ if (
1327
+ Xot::has_flag(flags, Window::FLAG_TITLEBAR_BACKGROUND) &&
1328
+ !Xot::has_flag(flags, Window::FLAG_SHADOW))
1329
+ {
1330
+ argument_error(
1331
+ __FILE__, __LINE__, "FLAG_TITLEBAR_BACKGROUND needs FLAG_SHADOW");
1332
+ }
1333
+
723
1334
  if (Xot::has_flag(flags, Window::FLAG_PORTRAIT))
724
1335
  argument_error(__FILE__, __LINE__, "FLAG_PORTRAIT is not supported");
725
1336
 
726
1337
  if (Xot::has_flag(flags, Window::FLAG_LANDSCAPE))
727
1338
  argument_error(__FILE__, __LINE__, "FLAG_LANDSCAPE is not supported");
1339
+
1340
+ if (!*window)
1341
+ invalid_state_error(__FILE__, __LINE__);
1342
+
1343
+ WindowData* self = get_data(window);
1344
+
1345
+ bool transparent = Xot::has_flag(flags, Window::FLAG_TRANSPARENT);
1346
+ if (transparent != self->transparent)
1347
+ {
1348
+ set_background_transparent(self->hwnd, transparent);
1349
+ self->transparent = transparent;
1350
+ }
1351
+
1352
+ DWORD current = (DWORD) GetWindowLongPtrW(self->hwnd, GWL_STYLE);
1353
+ DWORD style = make_window_style(flags, current);
1354
+ if (style != current)
1355
+ set_window_style(self->hwnd, style);
1356
+
1357
+ set_closable(self->hwnd, Xot::has_flag(flags, Window::FLAG_CLOSABLE));
1358
+
1359
+ set_unlisted(self->hwnd, Xot::has_flag(flags, Window::FLAG_UNLISTED));
1360
+
1361
+ HWND after = HWND_NOTOPMOST;
1362
+ if (Xot::has_flag(flags, Window::FLAG_ALWAYS_ON_TOP))
1363
+ after = HWND_TOPMOST;
1364
+ else if (Xot::has_flag(flags, Window::FLAG_ALWAYS_ON_BOTTOM))
1365
+ after = HWND_BOTTOM;
1366
+ if (!SetWindowPos(
1367
+ self->hwnd, after, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE))
1368
+ {
1369
+ system_error(__FILE__, __LINE__);
1370
+ }
728
1371
  }
729
1372
 
730
1373
  float
731
1374
  Window_get_pixel_density (const Window& window)
732
1375
  {
733
- return 1;
1376
+ HWND hwnd = get_data(&window)->hwnd;
1377
+ return Screen_get_pixel_density(
1378
+ hwnd ? MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST) : NULL);
1379
+ }
1380
+
1381
+ Point
1382
+ Window_from_native_coord (const Window& window, coord x, coord y)
1383
+ {
1384
+ float density = Window_get_pixel_density(window);
1385
+ return Point(x / density, y / density);
1386
+ }
1387
+
1388
+ POINT
1389
+ Window_to_native_coord (const Window& window, const Point& point)
1390
+ {
1391
+ float density = Window_get_pixel_density(window);
1392
+ return {
1393
+ (LONG) lround(point.x * density),
1394
+ (LONG) lround(point.y * density)
1395
+ };
1396
+ }
1397
+
1398
+ void
1399
+ Window_set_pointer_through (Window* window, bool through)
1400
+ {
1401
+ static const DWORD THROUGH = WS_EX_TRANSPARENT | WS_EX_LAYERED;
1402
+
1403
+ HWND hwnd = get_data(window)->hwnd;
1404
+ DWORD current = (DWORD) GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
1405
+ DWORD exstyle = through ? current | THROUGH : current & ~THROUGH;
1406
+ if (exstyle == current) return;
1407
+
1408
+ SetLastError(0);
1409
+ SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exstyle);
1410
+ if (GetLastError() != 0)
1411
+ system_error(__FILE__, __LINE__);
1412
+
1413
+ if (through && !SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA))
1414
+ system_error(__FILE__, __LINE__);
1415
+ }
1416
+
1417
+ static bool
1418
+ is_covered_by_another_window (HWND hwnd, POINT pos)
1419
+ {
1420
+ // only a window that takes mouse events counts as covering. the hit
1421
+ // test skips a window letting pointers through, so the hit can be one
1422
+ // below hwnd itself; it covers hwnd when it is among the windows above
1423
+
1424
+ HWND hit = WindowFromPoint(pos);
1425
+ if (hit) hit = GetAncestor(hit, GA_ROOT);
1426
+ if (!hit || hit == hwnd)
1427
+ return false;
1428
+
1429
+ for (HWND h = GetWindow(hwnd, GW_HWNDPREV); h; h = GetWindow(h, GW_HWNDPREV))
1430
+ {
1431
+ if (h == hit)
1432
+ return true;
1433
+ }
1434
+ return false;
1435
+ }
1436
+
1437
+ bool
1438
+ Window_is_pointer_over_and_uncovered (Point* position, const Window& window)
1439
+ {
1440
+ POINT pos;
1441
+ if (!GetCursorPos(&pos))
1442
+ return false;
1443
+
1444
+ HWND hwnd = get_data(&window)->hwnd;
1445
+ RECT rect;
1446
+ if (!GetWindowRect(hwnd, &rect))
1447
+ return false;
1448
+ if (!PtInRect(&rect, pos))
1449
+ return false;
1450
+ if (is_covered_by_another_window(hwnd, pos))
1451
+ return false;
1452
+ if (!ScreenToClient(hwnd, &pos))
1453
+ return false;
1454
+
1455
+ *position = Window_from_native_coord(window, pos.x, pos.y);
1456
+ return true;
734
1457
  }
735
1458
 
736
1459