reflexion 0.6.0 → 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 (275) hide show
  1. checksums.yaml +4 -4
  2. data/.doc/ext/reflex/application.cpp +37 -6
  3. data/.doc/ext/reflex/capture_event.cpp +1 -1
  4. data/.doc/ext/reflex/chase_constraint.cpp +1 -1
  5. data/.doc/ext/reflex/clipboard.cpp +1 -1
  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 +1 -1
  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 +1 -1
  26. data/.doc/ext/reflex/menu.cpp +6 -5
  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 +18 -6
  31. data/.doc/ext/reflex/note_event.cpp +1 -1
  32. data/.doc/ext/reflex/pin.cpp +1 -1
  33. data/.doc/ext/reflex/pointer.cpp +1 -1
  34. data/.doc/ext/reflex/pointer_event.cpp +2 -2
  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 +566 -232
  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 +1 -1
  41. data/.doc/ext/reflex/shape.cpp +1 -1
  42. data/.doc/ext/reflex/snap_constraint.cpp +1 -1
  43. data/.doc/ext/reflex/style.cpp +1 -1
  44. data/.doc/ext/reflex/style_length.cpp +1 -1
  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 +110 -56
  50. data/.doc/ext/reflex/wheel_constraint.cpp +1 -1
  51. data/.doc/ext/reflex/wheel_event.cpp +1 -1
  52. data/.doc/ext/reflex/window.cpp +226 -17
  53. data/.github/workflows/release-gem.yml +2 -49
  54. data/.github/workflows/tag.yml +5 -28
  55. data/.github/workflows/test.yml +1 -37
  56. data/ChangeLog.md +54 -0
  57. data/Gemfile.lock +13 -8
  58. data/Rakefile +13 -2
  59. data/Reflex.podspec +2 -2
  60. data/VERSION +1 -1
  61. data/ext/reflex/application.cpp +41 -6
  62. data/ext/reflex/capture_event.cpp +1 -1
  63. data/ext/reflex/chase_constraint.cpp +1 -1
  64. data/ext/reflex/clipboard.cpp +1 -1
  65. data/ext/reflex/color_event.cpp +80 -0
  66. data/ext/reflex/color_picker.cpp +121 -0
  67. data/ext/reflex/constraint.cpp +1 -1
  68. data/ext/reflex/contact_event.cpp +1 -1
  69. data/ext/reflex/control_change_event.cpp +1 -1
  70. data/ext/reflex/device.cpp +1 -1
  71. data/ext/reflex/device_event.cpp +1 -1
  72. data/ext/reflex/draw_event.cpp +1 -1
  73. data/ext/reflex/ellipse_shape.cpp +1 -1
  74. data/ext/reflex/event.cpp +1 -1
  75. data/ext/reflex/extconf.rb +4 -2
  76. data/ext/reflex/file_dialog.cpp +194 -0
  77. data/ext/reflex/file_event.cpp +108 -0
  78. data/ext/reflex/filter.cpp +1 -1
  79. data/ext/reflex/focus_event.cpp +1 -1
  80. data/ext/reflex/frame_event.cpp +1 -1
  81. data/ext/reflex/gamepad.cpp +50 -0
  82. data/ext/reflex/image_view.cpp +1 -1
  83. data/ext/reflex/key_event.cpp +1 -275
  84. data/ext/reflex/line_shape.cpp +1 -1
  85. data/ext/reflex/link_constraint.cpp +1 -1
  86. data/ext/reflex/menu.cpp +8 -7
  87. data/ext/reflex/midi.cpp +11 -3
  88. data/ext/reflex/midi_event.cpp +1 -1
  89. data/ext/reflex/motion_event.cpp +1 -1
  90. data/ext/reflex/native.cpp +18 -6
  91. data/ext/reflex/note_event.cpp +1 -1
  92. data/ext/reflex/pin.cpp +1 -1
  93. data/ext/reflex/pointer.cpp +1 -1
  94. data/ext/reflex/pointer_event.cpp +2 -2
  95. data/ext/reflex/polygon_shape.cpp +1 -1
  96. data/ext/reflex/rect_shape.cpp +1 -1
  97. data/ext/reflex/reflex.cpp +568 -232
  98. data/ext/reflex/screen.cpp +31 -2
  99. data/ext/reflex/scroll_event.cpp +1 -1
  100. data/ext/reflex/selector.cpp +1 -1
  101. data/ext/reflex/selector.h +9 -9
  102. data/ext/reflex/shape.cpp +1 -1
  103. data/ext/reflex/snap_constraint.cpp +1 -1
  104. data/ext/reflex/style.cpp +1 -1
  105. data/ext/reflex/style_length.cpp +1 -1
  106. data/ext/reflex/text_event.cpp +111 -0
  107. data/ext/reflex/timer.cpp +1 -1
  108. data/ext/reflex/timer_event.cpp +1 -1
  109. data/ext/reflex/update_event.cpp +1 -1
  110. data/ext/reflex/view.cpp +121 -61
  111. data/ext/reflex/wheel_constraint.cpp +1 -1
  112. data/ext/reflex/wheel_event.cpp +1 -1
  113. data/ext/reflex/window.cpp +256 -25
  114. data/include/reflex/application.h +10 -0
  115. data/include/reflex/color_picker.h +65 -0
  116. data/include/reflex/event.h +178 -88
  117. data/include/reflex/file_dialog.h +73 -0
  118. data/include/reflex/gamepad.h +3 -0
  119. data/include/reflex/menu.h +3 -3
  120. data/include/reflex/midi.h +3 -0
  121. data/include/reflex/reflex.h +2 -0
  122. data/include/reflex/ruby/application.h +6 -1
  123. data/include/reflex/ruby/color_picker.h +89 -0
  124. data/include/reflex/ruby/event.h +62 -29
  125. data/include/reflex/ruby/file_dialog.h +89 -0
  126. data/include/reflex/ruby/gamepad.h +46 -0
  127. data/include/reflex/ruby/reflex.h +3 -0
  128. data/include/reflex/ruby/view.h +92 -44
  129. data/include/reflex/ruby/window.h +60 -1
  130. data/include/reflex/ruby.h +3 -0
  131. data/include/reflex/screen.h +11 -2
  132. data/include/reflex/view.h +28 -16
  133. data/include/reflex/window.h +40 -8
  134. data/include/reflex.h +3 -0
  135. data/lib/reflex/application.rb +5 -5
  136. data/lib/reflex/autoinit.rb +3 -1
  137. data/lib/reflex/button.rb +3 -3
  138. data/lib/reflex/color_event.rb +16 -0
  139. data/lib/reflex/color_picker.rb +45 -0
  140. data/lib/reflex/control_change_event.rb +17 -0
  141. data/lib/reflex/file_dialog.rb +70 -0
  142. data/lib/reflex/file_event.rb +37 -0
  143. data/lib/reflex/helper.rb +58 -7
  144. data/lib/reflex/key_event.rb +9 -4
  145. data/lib/reflex/menu.rb +17 -1
  146. data/lib/reflex/model.rb +19 -19
  147. data/lib/reflex/note_event.rb +1 -1
  148. data/lib/reflex/pointer.rb +14 -3
  149. data/lib/reflex/pointer_event.rb +12 -1
  150. data/lib/reflex/reflex.rb +5 -0
  151. data/lib/reflex/shape.rb +14 -14
  152. data/lib/reflex/style.rb +16 -16
  153. data/lib/reflex/text_event.rb +40 -0
  154. data/lib/reflex/timer.rb +3 -3
  155. data/lib/reflex/view.rb +29 -14
  156. data/lib/reflex/wheel_event.rb +12 -3
  157. data/lib/reflex/window.rb +25 -13
  158. data/lib/reflex.rb +8 -2
  159. data/reflex.gemspec +4 -4
  160. data/samples/ime.rb +100 -0
  161. data/samples/ios/hello/hello.xcodeproj/project.pbxproj +236 -224
  162. data/samples/menu.rb +4 -1
  163. data/samples/model.rb +3 -3
  164. data/samples/test/alert.rb +93 -0
  165. data/samples/test/always_on.rb +76 -0
  166. data/samples/test/app_background.rb +51 -0
  167. data/samples/test/boundary.rb +75 -0
  168. data/samples/test/callback_exception.rb +101 -0
  169. data/samples/test/cancel.rb +53 -0
  170. data/samples/test/color_picker.rb +63 -0
  171. data/samples/test/file_dialog.rb +102 -0
  172. data/samples/window_style.rb +156 -0
  173. data/src/application.cpp +159 -18
  174. data/src/application.h +92 -7
  175. data/src/color_picker.cpp +113 -0
  176. data/src/color_picker.h +36 -0
  177. data/src/event.cpp +410 -252
  178. data/src/event.h +12 -0
  179. data/src/file_dialog.cpp +163 -0
  180. data/src/file_dialog.h +52 -0
  181. data/src/gamepad.cpp +42 -5
  182. data/src/gamepad.h +4 -0
  183. data/src/ios/app_delegate.mm +5 -3
  184. data/src/ios/application.mm +24 -0
  185. data/src/ios/color_picker.mm +19 -0
  186. data/src/ios/file_dialog.mm +21 -0
  187. data/src/ios/gamepad.mm +4 -4
  188. data/src/ios/menu.mm +5 -0
  189. data/src/ios/reflex.mm +8 -0
  190. data/src/ios/screen.mm +26 -0
  191. data/src/ios/view_controller.mm +2 -30
  192. data/src/ios/window.mm +38 -9
  193. data/src/menu.cpp +66 -4
  194. data/src/menu.h +17 -1
  195. data/src/midi.cpp +61 -31
  196. data/src/osx/app_delegate.h +4 -0
  197. data/src/osx/app_delegate.mm +102 -22
  198. data/src/osx/application.mm +88 -6
  199. data/src/osx/color_picker.mm +112 -0
  200. data/src/osx/event.h +10 -0
  201. data/src/osx/event.mm +84 -30
  202. data/src/osx/file_dialog.mm +168 -0
  203. data/src/osx/gamepad.mm +2 -2
  204. data/src/osx/gamepad_hid.mm +1 -1
  205. data/src/osx/menu.mm +177 -33
  206. data/src/osx/native_window.h +16 -2
  207. data/src/osx/native_window.mm +114 -57
  208. data/src/osx/opengl_view.h +2 -1
  209. data/src/osx/opengl_view.mm +184 -43
  210. data/src/osx/reflex.mm +38 -0
  211. data/src/osx/screen.mm +36 -0
  212. data/src/osx/window.mm +106 -24
  213. data/src/pointer.cpp +13 -4
  214. data/src/pointer.h +4 -0
  215. data/src/rays.h +46 -0
  216. data/src/sdl/application.cpp +68 -8
  217. data/src/sdl/color_picker.cpp +18 -0
  218. data/src/sdl/event.cpp +24 -3
  219. data/src/sdl/event.h +12 -0
  220. data/src/sdl/file_dialog.cpp +20 -0
  221. data/src/sdl/gamepad.cpp +3 -7
  222. data/src/sdl/menu.cpp +5 -0
  223. data/src/sdl/reflex.cpp +20 -0
  224. data/src/sdl/screen.cpp +36 -0
  225. data/src/sdl/window.cpp +142 -29
  226. data/src/shape.cpp +15 -10
  227. data/src/view.cpp +287 -198
  228. data/src/view.h +4 -2
  229. data/src/vk.h +416 -0
  230. data/src/win32/application.cpp +162 -12
  231. data/src/win32/application.h +20 -0
  232. data/src/win32/color_picker.cpp +82 -0
  233. data/src/win32/event.cpp +114 -12
  234. data/src/win32/event.h +21 -4
  235. data/src/win32/file_dialog.cpp +226 -0
  236. data/src/win32/gamepad.cpp +9 -2
  237. data/src/win32/menu.cpp +191 -53
  238. data/src/win32/menu.h +9 -1
  239. data/src/win32/opengl.cpp +2 -9
  240. data/src/win32/reflex.cpp +44 -3
  241. data/src/win32/screen.cpp +164 -3
  242. data/src/win32/screen.h +8 -0
  243. data/src/win32/tray.cpp +191 -0
  244. data/src/win32/tray.h +22 -0
  245. data/src/win32/vk.cpp +11 -0
  246. data/src/win32/window.cpp +809 -148
  247. data/src/win32/window.h +6 -0
  248. data/src/window.cpp +718 -185
  249. data/src/window.h +66 -33
  250. data/src/world.cpp +9 -1
  251. data/test/helper.rb +17 -0
  252. data/test/test_application.rb +90 -1
  253. data/test/test_chase_constraint.rb +1 -3
  254. data/test/test_color_event.rb +36 -0
  255. data/test/test_color_picker.rb +62 -0
  256. data/test/test_constraint.rb +2 -4
  257. data/test/test_event.rb +1 -1
  258. data/test/test_file_dialog.rb +48 -0
  259. data/test/test_file_event.rb +43 -0
  260. data/test/test_has_text_preedit.rb +54 -0
  261. data/test/test_key_event.rb +20 -5
  262. data/test/test_link_constraint.rb +1 -3
  263. data/test/test_menu.rb +40 -10
  264. data/test/test_pointer.rb +33 -23
  265. data/test/test_pointer_event.rb +24 -9
  266. data/test/test_reflex.rb +36 -0
  267. data/test/test_screen.rb +24 -2
  268. data/test/test_snap_constraint.rb +1 -3
  269. data/test/test_text_event.rb +54 -0
  270. data/test/test_view.rb +41 -4
  271. data/test/test_wheel_constraint.rb +1 -3
  272. data/test/test_wheel_event.rb +13 -5
  273. data/test/test_window.rb +196 -44
  274. metadata +80 -10
  275. data/.github/workflows/utils.rb +0 -127
data/src/win32/window.cpp CHANGED
@@ -1,21 +1,29 @@
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
- #include "menu.h"
19
27
 
20
28
 
21
29
  namespace Reflex
@@ -25,19 +33,26 @@ namespace Reflex
25
33
  typedef std::map<int, String> PressingKeyMap;
26
34
 
27
35
 
28
- static const char* WINDOWCLASS = "Reflex:WindowClass";
36
+ static const wchar_t* WINDOWCLASS = L"Reflex:WindowClass";
29
37
 
30
- static const char* USERDATA_PROP = "Reflex:Window:HWND";
38
+ static const wchar_t* USERDATA_PROP = L"Reflex:Window:HWND";
31
39
 
32
40
 
33
41
  struct WindowData : public Window::Data
34
42
  {
35
43
 
36
- HWND hwnd = NULL;
44
+ HWND hwnd = NULL;
45
+
46
+ bool need_rebind = false;
47
+
48
+ bool transparent = false;
37
49
 
38
- bool need_rebind = false;
50
+ bool tracking_mouse = false;
39
51
 
40
- bool tracking_mouse = false;
52
+ // the first half of a surrogate pair, waiting for the second half
53
+ wchar_t pending_surrogate = 0;
54
+
55
+ HACCEL haccel = NULL;
41
56
 
42
57
  OpenGLContext context;
43
58
 
@@ -45,6 +60,17 @@ namespace Reflex
45
60
 
46
61
  mutable String title_tmp;
47
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
+
48
74
  bool is_valid () const
49
75
  {
50
76
  return hwnd && IsWindow(hwnd);
@@ -67,12 +93,20 @@ namespace Reflex
67
93
  return get_data(const_cast<Window*>(window));
68
94
  }
69
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
+
70
104
  static bool
71
105
  window_has_wndproc (HWND hwnd)
72
106
  {
73
107
  if (!hwnd) return false;
74
108
 
75
- WNDPROC proc = (WNDPROC) GetWindowLongPtr(hwnd, GWLP_WNDPROC);
109
+ WNDPROC proc = (WNDPROC) GetWindowLongPtrW(hwnd, GWLP_WNDPROC);
76
110
  if (proc == wndproc)
77
111
  return true;
78
112
 
@@ -80,24 +114,42 @@ namespace Reflex
80
114
  // it can not determine the hwnd is mine or not.
81
115
 
82
116
  enum {BUFSIZE = 256};
83
- char buf[BUFSIZE + 1];
117
+ wchar_t buf[BUFSIZE + 1];
84
118
  if (
85
- GetClassName(hwnd, &buf[0], BUFSIZE) == 0 &&
119
+ GetClassNameW(hwnd, &buf[0], BUFSIZE) == 0 &&
86
120
  GetLastError() != 0)
87
121
  {
88
122
  return false;
89
123
  }
90
124
 
91
- return stricmp(buf, WINDOWCLASS) == 0;
125
+ return _wcsicmp(buf, WINDOWCLASS) == 0;
92
126
  }
93
127
 
94
128
  static Window*
95
129
  get_window_from_hwnd (HWND hwnd)
96
130
  {
97
131
  if (window_has_wndproc(hwnd))
98
- return (Window*) GetWindowLongPtr(hwnd, GWLP_USERDATA);
132
+ return (Window*) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
99
133
  else
100
- 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;
101
153
  }
102
154
 
103
155
  static void
@@ -111,13 +163,13 @@ namespace Reflex
111
163
  if (window_has_wndproc(hwnd))
112
164
  {
113
165
  SetLastError(0);
114
- SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) win);
166
+ SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) win);
115
167
  if (GetLastError() != 0)
116
168
  system_error(__FILE__, __LINE__);
117
169
  }
118
170
  else
119
171
  {
120
- if (!SetProp(hwnd, USERDATA_PROP, (HANDLE) win))
172
+ if (!SetPropW(hwnd, USERDATA_PROP, (HANDLE) win))
121
173
  system_error(__FILE__, __LINE__);
122
174
  }
123
175
 
@@ -158,13 +210,13 @@ namespace Reflex
158
210
  if (window_has_wndproc(self->hwnd))
159
211
  {
160
212
  SetLastError(0);
161
- SetWindowLongPtr(self->hwnd, GWLP_USERDATA, 0);
213
+ SetWindowLongPtrW(self->hwnd, GWLP_USERDATA, 0);
162
214
  if (GetLastError() != 0)
163
215
  system_error(__FILE__, __LINE__);
164
216
  }
165
217
  else
166
218
  {
167
- if (!RemoveProp(self->hwnd, USERDATA_PROP))
219
+ if (!RemovePropW(self->hwnd, USERDATA_PROP))
168
220
  system_error(__FILE__, __LINE__);
169
221
  }
170
222
 
@@ -173,8 +225,162 @@ namespace Reflex
173
225
 
174
226
  win->release();
175
227
 
176
- if (Window_all().empty())
177
- 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));
178
384
  }
179
385
 
180
386
  void
@@ -182,11 +388,12 @@ namespace Reflex
182
388
  {
183
389
  WindowData* self = get_data(win);
184
390
 
391
+ update_accelerator_table(win);
185
392
  Gamepad_poll();
186
393
 
187
394
  Window_call_update_event(win);
188
395
 
189
- if (self->redraw)
396
+ if (Xot::check_and_remove_flag(&self->flags, Window::Data::REDRAW))
190
397
  {
191
398
  #if 1
192
399
  InvalidateRect(self->hwnd, NULL, FALSE);
@@ -194,8 +401,6 @@ namespace Reflex
194
401
  RedrawWindow(
195
402
  self->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | EDW_ALLCHILDREN);
196
403
  #endif
197
-
198
- self->redraw = false;
199
404
  }
200
405
  }
201
406
 
@@ -222,32 +427,51 @@ namespace Reflex
222
427
  Window_call_draw_event(win, &e);
223
428
  }
224
429
 
225
- static void
226
- frame_changed (Window* win)
430
+ static String
431
+ get_chars (WindowData* self, UINT msg)
227
432
  {
228
- Rays::Bounds b = win->frame();
229
- Rays::Point dpos = b.position() - win->self->prev_position;
230
- Rays::Point dsize = b.size() - win->self->prev_size;
231
- win->self->prev_position = b.position();
232
- win->self->prev_size = b.size();
233
-
234
- if (dpos == 0 && dsize == 0) return;
235
-
236
- Reflex::FrameEvent e(b, dpos.x, dpos.y, 0, dsize.x, dsize.y, 0);
237
- if (dpos != 0) win->on_move(&e);
238
- 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)
239
437
  {
240
- Rays::Bounds b = win->frame();
241
- b.move_to(0, 0);
242
-
243
- if (win->painter())
244
- 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
+ }
245
461
 
246
- if (win->root())
247
- 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
+ }
248
468
 
249
- win->on_resize(&e);
250
- }
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;
251
475
  }
252
476
 
253
477
  static void
@@ -255,23 +479,26 @@ namespace Reflex
255
479
  {
256
480
  assert(*win);
257
481
 
258
- WindowData* self = get_data(win);
482
+ if (wp == VK_PROCESSKEY) return;
259
483
 
260
- MSG wmchar;
261
- UINT filter = msg == WM_SYSKEYDOWN ? WM_SYSCHAR : WM_CHAR;
262
- BOOL peeked = PeekMessage(&wmchar, self->hwnd, filter, filter, PM_NOREMOVE);
484
+ WindowData* self = get_data(win);
263
485
 
264
- String chars;
265
- if (peeked) chars += (char) wmchar.wParam;
486
+ String chars = get_chars(self, msg);
266
487
 
267
488
  NativeKeyEvent e(msg, wp, lp, chars);
268
-
269
489
  self->pressing_keys.insert_or_assign(e.code(), chars);
490
+
270
491
  #if 0
271
492
  for (auto kv : self->pressing_keys)
272
493
  doutln("0x%x : %s", kv.first, (const char*) kv.second);
273
494
  #endif
274
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
+
275
502
  Window_call_key_event(win, &e);
276
503
  }
277
504
 
@@ -286,11 +513,73 @@ namespace Reflex
286
513
 
287
514
  auto it = self->pressing_keys.find(e.code());
288
515
  if (it != self->pressing_keys.end())
516
+ {
289
517
  KeyEvent_set_chars(&e, it->second);
518
+ self->pressing_keys.erase(it);
519
+ }
290
520
 
291
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
+ }
292
545
 
293
- if (it != self->pressing_keys.end()) self->pressing_keys.erase(it);
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()));
574
+
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);
294
583
  }
295
584
 
296
585
  #ifndef MOUSEEVENTF_FROMTOUCH
@@ -303,6 +592,40 @@ namespace Reflex
303
592
  return (GetMessageExtraInfo() & 0xffffff00) == MOUSEEVENTF_FROMTOUCH;
304
593
  }
305
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
+
306
629
  static void
307
630
  mouse (Window* win, UINT msg, WPARAM wp, LPARAM lp)
308
631
  {
@@ -318,7 +641,7 @@ namespace Reflex
318
641
 
319
642
  // Win32 has no mouse-enter message; the first move after (re)arming
320
643
  // the leave tracking is when the mouse entered the window.
321
- NativePointerEvent e(msg, wp, lp, Pointer::ENTER);
644
+ NativePointerEvent e(*win, msg, wp, lp, Pointer::ENTER);
322
645
  Window_call_pointer_event(win, &e);
323
646
  }
324
647
  else if (msg == WM_MOUSELEAVE)
@@ -330,8 +653,15 @@ namespace Reflex
330
653
  lp = MAKELPARAM(pt.x, pt.y);
331
654
  }
332
655
 
333
- NativePointerEvent e(msg, wp, lp);
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);
334
662
  Window_call_pointer_event(win, &e);
663
+
664
+ release_mouse_on_button_up(self->hwnd, msg, wp);
335
665
  }
336
666
 
337
667
  static void
@@ -346,18 +676,18 @@ namespace Reflex
346
676
  {
347
677
  wp_y = wp;
348
678
  MSG m;
349
- if (PeekMessage(&m, self->hwnd, WM_MOUSEHWHEEL, WM_MOUSEHWHEEL, PM_REMOVE))
679
+ if (PeekMessageW(&m, self->hwnd, WM_MOUSEHWHEEL, WM_MOUSEHWHEEL, PM_REMOVE))
350
680
  wp_x = m.wParam;
351
681
  }
352
682
  else
353
683
  {
354
684
  wp_x = wp;
355
685
  MSG m;
356
- if (PeekMessage(&m, self->hwnd, WM_MOUSEWHEEL, WM_MOUSEWHEEL, PM_REMOVE))
686
+ if (PeekMessageW(&m, self->hwnd, WM_MOUSEWHEEL, WM_MOUSEWHEEL, PM_REMOVE))
357
687
  wp_y = m.wParam;
358
688
  }
359
689
 
360
- NativeWheelEvent e(wp_x, wp_y, lp);
690
+ NativeWheelEvent e(*win, wp_x, wp_y, lp);
361
691
  Window_call_wheel_event(win, &e);
362
692
  }
363
693
 
@@ -375,7 +705,7 @@ namespace Reflex
375
705
  if (!GetTouchInputInfo(handle, size, &touches[0], sizeof(TOUCHINPUT)))
376
706
  return;
377
707
 
378
- NativePointerEvent e(self->hwnd, &touches[0], size);
708
+ NativePointerEvent e(*win, &touches[0], size);
379
709
  Window_call_pointer_event(win, &e);
380
710
 
381
711
  CloseTouchInputHandle(handle);
@@ -385,12 +715,36 @@ namespace Reflex
385
715
  window_proc (Window* win, HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
386
716
  {
387
717
  if (!win || !*win || hwnd != get_data(win)->hwnd)
388
- return DefWindowProc(hwnd, msg, wp, lp);
718
+ return DefWindowProcW(hwnd, msg, wp, lp);
389
719
 
390
720
  WindowData* self = get_data(win);
391
721
 
392
722
  switch (msg)
393
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
+
394
748
  case WM_ACTIVATE:
395
749
  {
396
750
  if (LOWORD(wp) == WA_INACTIVE)
@@ -405,10 +759,22 @@ namespace Reflex
405
759
 
406
760
  case WM_CLOSE:
407
761
  {
408
- win->close();
762
+ Window_call_close(win);
409
763
  return 0;
410
764
  }
411
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
+
412
778
  case WM_MENUCOMMAND:
413
779
  {
414
780
  Menu_call_command_event((HMENU) lp, (uint) wp);
@@ -449,13 +815,13 @@ namespace Reflex
449
815
 
450
816
  case WM_MOVE:
451
817
  {
452
- frame_changed(win);
818
+ Window_call_frame_event(win);
453
819
  break;
454
820
  }
455
821
 
456
822
  case WM_SIZE:
457
823
  {
458
- frame_changed(win);
824
+ Window_call_frame_event(win);
459
825
  break;
460
826
  }
461
827
 
@@ -473,6 +839,31 @@ namespace Reflex
473
839
  break;
474
840
  }
475
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
+
476
867
  case WM_LBUTTONDOWN:
477
868
  case WM_RBUTTONDOWN:
478
869
  case WM_MBUTTONDOWN:
@@ -496,6 +887,12 @@ namespace Reflex
496
887
  break;
497
888
  }
498
889
 
890
+ case WM_CAPTURECHANGED:
891
+ {
892
+ Window_cancel_active_pointers(win);
893
+ break;
894
+ }
895
+
499
896
  case WM_TOUCH:
500
897
  {
501
898
  touch(win, msg, wp, lp);
@@ -512,29 +909,51 @@ namespace Reflex
512
909
  }
513
910
  }
514
911
 
515
- return DefWindowProc(hwnd, msg, wp, lp);
912
+ return DefWindowProcW(hwnd, msg, wp, lp);
516
913
  }
517
914
 
518
915
  static LRESULT CALLBACK
519
916
  wndproc (HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
520
917
  {
521
- Window* win = NULL;
522
- if (msg == WM_NCCREATE)
918
+ if (Application_has_exception())
919
+ return DefWindowProcW(hwnd, msg, wp, lp);
920
+
921
+ return Application_guard([&]() -> LRESULT
523
922
  {
524
- CREATESTRUCT* cs = (CREATESTRUCT*) lp;
525
- win = (Window*) cs->lpCreateParams;
526
- setup_window(win, hwnd);
527
- }
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
+ }
930
+
931
+ if (!win) win = get_window_from_hwnd(hwnd);
932
+ if (win) rebind(win);
933
+
934
+ LRESULT ret = window_proc(win, hwnd, msg, wp, lp);
528
935
 
529
- if (!win) win = get_window_from_hwnd(hwnd);
530
- if (win) rebind(win);
936
+ if (msg == WM_NCDESTROY)
937
+ cleanup_window(win);
531
938
 
532
- LRESULT ret = window_proc(win, hwnd, msg, wp, lp);
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;
533
949
 
534
- if (msg == WM_NCDESTROY)
535
- cleanup_window(win);
950
+ wchar_t path[32768];
951
+ if (GetModuleFileNameW(NULL, path, sizeof(path) / sizeof(path[0])) == 0)
952
+ return;
536
953
 
537
- return ret;
954
+ UINT n = ExtractIconExW(path, 0, large, small, 1);
955
+ if (n == 0 || n == UINT_MAX)
956
+ *large = *small = NULL;
538
957
  }
539
958
 
540
959
  static void
@@ -543,22 +962,21 @@ namespace Reflex
543
962
  static bool registered = false;
544
963
  if (registered) return;
545
964
 
546
- WNDCLASSEX wc;
965
+ WNDCLASSEXW wc;
547
966
  memset(&wc, 0, sizeof(wc));
548
-
549
967
  wc.cbSize = sizeof(wc);
550
968
  wc.lpszClassName = WINDOWCLASS;
551
969
  wc.lpfnWndProc = (WNDPROC) wndproc;
552
970
  wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;
553
- wc.hInstance = GetModuleHandle(NULL);
554
- //wc.hIcon = LoadIcon(wc.hInstance, IDI_APP_LARGE);
555
- //wc.hIconSm = LoadIcon(wc.hInstance, IDI_APP_SMALL);
556
- wc.hCursor = LoadCursor(NULL, IDC_ARROW);
971
+ wc.hInstance = GetModuleHandleW(NULL);
972
+ wc.hCursor = LoadCursorW(NULL, (LPCWSTR) IDC_ARROW);
557
973
  wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
558
974
  wc.lpszMenuName = NULL;
559
975
  wc.cbWndExtra = sizeof(Window*);
560
976
 
561
- if (!RegisterClassEx(&wc))
977
+ load_app_icons(&wc.hIcon, &wc.hIconSm);
978
+
979
+ if (!RegisterClassExW(&wc))
562
980
  system_error(__FILE__, __LINE__);
563
981
 
564
982
  registered = true;
@@ -571,10 +989,10 @@ namespace Reflex
571
989
  invalid_state_error(__FILE__, __LINE__);
572
990
 
573
991
  register_windowclass();
574
- HWND hwnd = CreateWindowEx(
575
- 0, WINDOWCLASS, "",
992
+ HWND hwnd = CreateWindowExW(
993
+ 0, WINDOWCLASS, L"",
576
994
  WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
577
- 0, 0, 0, 0, NULL, NULL, GetModuleHandle(NULL), win);
995
+ 0, 0, 0, 0, NULL, NULL, GetModuleHandleW(NULL), win);
578
996
  if (!hwnd)
579
997
  system_error(__FILE__, __LINE__);
580
998
 
@@ -596,9 +1014,12 @@ namespace Reflex
596
1014
  Window_default_flags ()
597
1015
  {
598
1016
  return
599
- Window::FLAG_CLOSABLE |
600
- Window::FLAG_RESIZABLE |
601
- 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;
602
1023
  }
603
1024
 
604
1025
  void
@@ -617,9 +1038,15 @@ namespace Reflex
617
1038
 
618
1039
  WindowData* self = get_data(window);
619
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
+
620
1046
  SetWindowPos(
621
- self->hwnd, HWND_TOP, 0, 0, 0, 0,
622
- 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);
623
1050
  UpdateWindow(self->hwnd);
624
1051
  }
625
1052
 
@@ -654,7 +1081,7 @@ namespace Reflex
654
1081
 
655
1082
  if (!*window) return;
656
1083
 
657
- if (!SetWindowText(get_data(window)->hwnd, title))
1084
+ if (!SetWindowTextW(get_data(window)->hwnd, String(title).to_wstr().c_str()))
658
1085
  system_error(__FILE__, __LINE__);
659
1086
  }
660
1087
 
@@ -665,75 +1092,80 @@ namespace Reflex
665
1092
 
666
1093
  const WindowData* self = get_data(&window);
667
1094
 
668
- int size = GetWindowTextLength(self->hwnd);
1095
+ int size = GetWindowTextLengthW(self->hwnd);
669
1096
  if (size <= 0) return "";
670
1097
 
671
- std::unique_ptr<char[]> buf(new char[size + 1]);
672
- 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)
673
1100
  return "";
674
1101
 
675
- self->title_tmp = &buf[0];
1102
+ self->title_tmp = String(&buf[0], size);
676
1103
  return self->title_tmp.c_str();
677
1104
  }
678
1105
 
679
- static void
680
- get_window_bounds (
681
- HWND hwnd, coord* x, coord* y, coord* width, coord* height,
682
- coord* nonclient_width = NULL, coord* nonclient_height = NULL)
1106
+ static RECT
1107
+ get_client_rect (HWND hwnd)
683
1108
  {
684
- if (!x && !y && !width && !height && !nonclient_width && !nonclient_height)
685
- argument_error(__FILE__, __LINE__);
686
-
687
- RECT window;
688
- if (!GetWindowRect(hwnd, &window))
689
- system_error(__FILE__, __LINE__);
690
-
691
1109
  RECT client;
692
1110
  if (!GetClientRect(hwnd, &client))
693
1111
  system_error(__FILE__, __LINE__);
694
1112
 
695
- if (x) *x = window.left;
696
- if (y) *y = window.top;
697
- if (width) *width = client.right - client.left;
698
- if (height) *height = client.bottom - client.top;
1113
+ POINT pos = {client.left, client.top};
1114
+ if (!ClientToScreen(hwnd, &pos))
1115
+ system_error(__FILE__, __LINE__);
699
1116
 
700
- if (nonclient_width)
701
- {
702
- coord ww = window.right - window.left;
703
- coord cw = client.right - client.left;
704
- *nonclient_width = ww - cw;
705
- }
706
- if (nonclient_height)
707
- {
708
- coord wh = window.bottom - window.top;
709
- coord ch = client.bottom - client.top;
710
- *nonclient_height = wh - ch;
711
- }
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;
712
1138
  }
713
1139
 
714
1140
  void
715
- 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)
716
1142
  {
717
1143
  if (!*window) return;
718
1144
 
719
- WindowData* self = get_data(window);
1145
+ HWND hwnd = get_data(window)->hwnd;
720
1146
 
721
- coord xx, yy, ww, hh, nonclient_w, nonclient_h;
722
- get_window_bounds(
723
- 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;
724
1150
 
725
- width += nonclient_w;
726
- height += nonclient_h;
1151
+ if (rect.left == now.left && rect.top == now.top)
1152
+ flags |= SWP_NOMOVE;
727
1153
 
728
- UINT flags = 0;
729
- if (x == xx && y == yy) flags |= SWP_NOMOVE;
730
- 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
+ }
731
1160
 
732
1161
  if (flags == (SWP_NOMOVE | SWP_NOSIZE))
733
1162
  return;
734
1163
 
1164
+ client_to_window_rect(hwnd, &rect);
1165
+
735
1166
  if (!SetWindowPos(
736
- 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,
737
1169
  flags | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER))
738
1170
  {
739
1171
  system_error(__FILE__, __LINE__);
@@ -746,27 +1178,20 @@ namespace Reflex
746
1178
  if (!window)
747
1179
  invalid_state_error(__FILE__, __LINE__);
748
1180
 
749
- coord x, y, w, h;
750
- get_window_bounds(get_data(&window)->hwnd, &x, &y, &w, &h);
751
- return Bounds(x, y, w, h);
752
- }
753
-
754
- HWND
755
- Window_get_hwnd (const Window* window)
756
- {
757
- if (!window) return NULL;
758
-
759
- return get_data(window)->hwnd;
1181
+ return Screen_from_native_coord(get_client_rect(get_data(&window)->hwnd));
760
1182
  }
761
1183
 
762
1184
  void
763
1185
  Window_set_menu (Window* window, Menu* menu)
764
1186
  {
765
- HWND hwnd = get_data(window)->hwnd;
1187
+ WindowData* self = get_data(window);
1188
+ HWND hwnd = self->hwnd;
766
1189
  if (!hwnd) return;
767
1190
 
768
- SetMenu(hwnd, menu ? Menu_get_hmenu(menu) : NULL);
1191
+ SetMenu(hwnd, menu ? Menu_get_hmenu(menu, true) : NULL);
769
1192
  DrawMenuBar(hwnd);
1193
+
1194
+ self->set_accelerator_table(menu ? Menu_create_accelerator_table(menu) : NULL);
770
1195
  }
771
1196
 
772
1197
  Screen
@@ -779,20 +1204,256 @@ namespace Reflex
779
1204
  return s;
780
1205
  }
781
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
+
782
1315
  void
783
1316
  Window_set_flags (Window* window, uint flags)
784
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
+
785
1334
  if (Xot::has_flag(flags, Window::FLAG_PORTRAIT))
786
1335
  argument_error(__FILE__, __LINE__, "FLAG_PORTRAIT is not supported");
787
1336
 
788
1337
  if (Xot::has_flag(flags, Window::FLAG_LANDSCAPE))
789
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
+ }
790
1371
  }
791
1372
 
792
1373
  float
793
1374
  Window_get_pixel_density (const Window& window)
794
1375
  {
795
- 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;
796
1457
  }
797
1458
 
798
1459