OSDN Git Service

終了時に一部の環境で例外が出ることがあるのを直す
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / View / SwipeScrollify.cs
1 // Copyright (C) 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 //\r
3 // Licensed under the Apache License, Version 2.0 (the "License");\r
4 // you may not use this file except in compliance with the License.\r
5 // You may obtain a copy of the License at\r
6 //\r
7 //    http://www.apache.org/licenses/LICENSE-2.0\r
8 //\r
9 // Unless required by applicable law or agreed to in writing, software\r
10 // distributed under the License is distributed on an "AS IS" BASIS,\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
12 // See the License for the specific language governing permissions and\r
13 // limitations under the License.\r
14 \r
15 using System;\r
16 using System.Drawing;\r
17 using System.Runtime.InteropServices;\r
18 using System.Windows.Forms;\r
19 using static System.Math;\r
20 \r
21 namespace KancolleSniffer.View\r
22 {\r
23     public class SwipeScrollify\r
24     {\r
25         private readonly MouseFilter _filter;\r
26 \r
27         public SwipeScrollify()\r
28         {\r
29             _filter = new MouseFilter();\r
30             Application.AddMessageFilter(_filter);\r
31         }\r
32 \r
33         public void AddPanel(Panel panel)\r
34         {\r
35             var handler = new PanelHandler(panel);\r
36             _filter.MouseDown += handler.MouseDown;\r
37             _filter.MouseMove += handler.MouseMove;\r
38             _filter.MouseUp += handler.MouseUp;\r
39         }\r
40 \r
41         public void AddShipListPanel(ShipListPanel.ShipListPanel panel)\r
42         {\r
43             var handler = new ShipListPanelHandler(panel);\r
44             _filter.MouseDown += handler.MouseDown;\r
45             _filter.MouseMove += handler.MouseMove;\r
46             _filter.MouseUp += handler.MouseUp;\r
47         }\r
48 \r
49         public void AddTreeView(TreeView treeView)\r
50         {\r
51             var handler = new TreeViewHandler(treeView);\r
52             _filter.MouseDown += handler.MouseDown;\r
53             _filter.MouseMove += handler.MouseMove;\r
54             _filter.MouseUp += handler.MouseUp;\r
55         }\r
56 \r
57         private class MouseFilter : IMessageFilter\r
58         {\r
59             public delegate void MouseHandler(IntPtr handle, ref bool handled);\r
60 \r
61             public event MouseHandler MouseMove, MouseDown, MouseUp;\r
62 \r
63             // ReSharper disable InconsistentNaming\r
64             // ReSharper disable IdentifierTypo\r
65             private const int WM_MOUSEMOVE = 0x0200;\r
66             private const int WM_LBUTTONDOWN = 0x0201;\r
67 \r
68             private const int WM_LBUTTONUP = 0x0202;\r
69             // ReSharper restore IdentifierTypo\r
70             // ReSharper restore InconsistentNaming\r
71 \r
72             public bool PreFilterMessage(ref Message m)\r
73             {\r
74                 var handled = false;\r
75                 switch (m.Msg)\r
76                 {\r
77                     case WM_LBUTTONDOWN:\r
78                         MouseDown?.Invoke(m.HWnd, ref handled);\r
79                         break;\r
80                     case WM_MOUSEMOVE:\r
81                         MouseMove?.Invoke(m.HWnd, ref handled);\r
82                         break;\r
83                     case WM_LBUTTONUP:\r
84                         MouseUp?.Invoke(m.HWnd, ref handled);\r
85                         break;\r
86                 }\r
87                 return handled;\r
88             }\r
89         }\r
90 \r
91         private class PanelHandler\r
92         {\r
93             private readonly Panel _panel;\r
94             private bool _touch;\r
95             private Point _mouseStart;\r
96             private Point _panelStart;\r
97             private Point _scrollStart;\r
98             private const int ScrollCount = 6;\r
99 \r
100             public PanelHandler(Panel panel)\r
101             {\r
102                 _panel = panel;\r
103             }\r
104 \r
105             public void MouseDown(IntPtr handle, ref bool handled)\r
106             {\r
107                 if (!_mouseStart.IsEmpty)\r
108                     return;\r
109                 if (!_panel.RectangleToScreen(_panel.ClientRectangle).Contains(Control.MousePosition))\r
110                     return;\r
111                 var found = false;\r
112                 for (var control = Control.FromHandle(handle); control != null; control = control.Parent)\r
113                 {\r
114                     if (control != _panel)\r
115                         continue;\r
116                     found = true;\r
117                     break;\r
118                 }\r
119                 if (!found)\r
120                     return;\r
121                 _mouseStart = _scrollStart = Control.MousePosition;\r
122                 _panelStart = _panel.AutoScrollPosition;\r
123             }\r
124 \r
125             public void MouseMove(IntPtr handle, ref bool handled)\r
126             {\r
127                 if (_mouseStart.IsEmpty)\r
128                     return;\r
129                 var cur = Control.MousePosition;\r
130                 var dx = cur.X - _mouseStart.X;\r
131                 var dy = cur.Y - _mouseStart.Y;\r
132                 if (!_touch)\r
133                 {\r
134                     if (!(Abs(dx) > ScrollCount || Abs(dy) > ScrollCount))\r
135                         return;\r
136                     _touch = true;\r
137                 }\r
138                 if (Abs(_scrollStart.X - cur.X) > ScrollCount || Abs(_scrollStart.Y - cur.Y) > ScrollCount)\r
139                 {\r
140                     _panel.AutoScrollPosition = new Point(-_panelStart.X - dx, -_panelStart.Y - dy);\r
141                     _scrollStart = cur;\r
142                 }\r
143             }\r
144 \r
145             public void MouseUp(IntPtr handle, ref bool handled)\r
146             {\r
147                 _touch = false;\r
148                 _mouseStart = _panelStart = Point.Empty;\r
149             }\r
150         }\r
151 \r
152         private class ShipListPanelHandler\r
153         {\r
154             private readonly ShipListPanel.ShipListPanel _panel;\r
155             private bool _touch;\r
156             private Point _mouseStart;\r
157             private int _barStart = -1;\r
158             private Point _scrollStart;\r
159             private const int ScrollCount = ShipListPanel.ShipListPanel.LineHeight;\r
160 \r
161             public ShipListPanelHandler(ShipListPanel.ShipListPanel panel)\r
162             {\r
163                 _panel = panel;\r
164             }\r
165 \r
166             public void MouseDown(IntPtr handle, ref bool handled)\r
167             {\r
168                 if (!_panel.ScrollBar.Visible || _panel.IsDisposed)\r
169                     return;\r
170                 if (!_mouseStart.IsEmpty)\r
171                     return;\r
172                 if (!_panel.RectangleToScreen(_panel.ClientRectangle).Contains(Control.MousePosition) ||\r
173                     _panel.ScrollBar.RectangleToScreen(_panel.ScrollBar.ClientRectangle)\r
174                         .Contains(Control.MousePosition))\r
175                     return;\r
176                 var found = false;\r
177                 for (var control = Control.FromHandle(handle); control != null; control = control.Parent)\r
178                 {\r
179                     if (control != _panel)\r
180                         continue;\r
181                     found = true;\r
182                     break;\r
183                 }\r
184                 if (!found)\r
185                     return;\r
186                 _mouseStart = _scrollStart = Control.MousePosition;\r
187                 _barStart = _panel.ScrollBar.Value;\r
188             }\r
189 \r
190             public void MouseMove(IntPtr handle, ref bool handled)\r
191             {\r
192                 if (_mouseStart.IsEmpty)\r
193                     return;\r
194                 var cur = Control.MousePosition;\r
195                 var dy = cur.Y - _mouseStart.Y;\r
196                 if (!_touch)\r
197                 {\r
198                     if (Abs(dy) <= ScrollCount)\r
199                         return;\r
200                     _touch = true;\r
201                 }\r
202                 if (Abs(_scrollStart.Y - cur.Y) > ScrollCount)\r
203                 {\r
204                     var bar = _panel.ScrollBar;\r
205                     bar.Value = Max(0, Min(bar.Maximum - bar.LargeChange + 1, _barStart - dy / ScrollCount));\r
206                     _scrollStart = cur;\r
207                 }\r
208             }\r
209 \r
210             public void MouseUp(IntPtr handle, ref bool handled)\r
211             {\r
212                 _touch = false;\r
213                 _barStart = -1;\r
214                 _mouseStart = Point.Empty;\r
215             }\r
216         }\r
217 \r
218         private class TreeViewHandler\r
219         {\r
220             private readonly TreeView _treeView;\r
221             private bool _touch;\r
222             private Point _mouseStart;\r
223             private Point _panelStart;\r
224 \r
225             [DllImport("user32.dll")]\r
226             private static extern int GetScrollPos(IntPtr hWnd, int nBar);\r
227 \r
228             [DllImport("user32.dll")]\r
229             private static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool bRedraw);\r
230 \r
231             // ReSharper disable InconsistentNaming\r
232             // ReSharper disable IdentifierTypo\r
233             private const int GWL_STYLE = -16;\r
234             private const int WS_HSCROLL = 0x00100000;\r
235 \r
236             private const int WS_VSCROLL = 0x00200000;\r
237             // ReSharper restore IdentifierTypo\r
238             // ReSharper restore InconsistentNaming\r
239 \r
240             [DllImport("user32.dll")]\r
241             private static extern int GetWindowLong(IntPtr hWnd, int nIndex);\r
242 \r
243             public TreeViewHandler(TreeView treeView)\r
244             {\r
245                 _treeView = treeView;\r
246             }\r
247 \r
248             public void MouseDown(IntPtr handle, ref bool handled)\r
249             {\r
250                 if (!_mouseStart.IsEmpty)\r
251                     return;\r
252                 var control = Control.FromHandle(handle);\r
253                 if (control == null || control != _treeView)\r
254                     return;\r
255                 if (!_treeView.RectangleToScreen(_treeView.ClientRectangle).Contains(Control.MousePosition))\r
256                     return;\r
257                 var loc = _treeView.HitTest(_treeView.PointToClient(Control.MousePosition)).Location;\r
258                 // アイテムをクリックするとWM_LBUTTONUPが来ないので避ける\r
259                 if (loc == TreeViewHitTestLocations.Label || loc == TreeViewHitTestLocations.Image)\r
260                     return;\r
261                 _mouseStart = Control.MousePosition;\r
262                 _panelStart = ScrollPosition();\r
263             }\r
264 \r
265             public void MouseMove(IntPtr handle, ref bool handled)\r
266             {\r
267                 if (_mouseStart.IsEmpty)\r
268                     return;\r
269                 if (!_treeView.RectangleToScreen(_treeView.ClientRectangle).Contains(Control.MousePosition))\r
270                 {\r
271                     // TreeViewではうまく動かないので外に出たら止める\r
272                     _touch = false;\r
273                     _mouseStart = _panelStart = Point.Empty;\r
274                     return;\r
275                 }\r
276                 var cur = Control.MousePosition;\r
277                 var dx = cur.X - _mouseStart.X;\r
278                 var dy = cur.Y - _mouseStart.Y;\r
279                 var style = GetWindowLong(_treeView.Handle, GWL_STYLE);\r
280                 if (_touch)\r
281                 {\r
282                     _treeView.BeginUpdate();\r
283                     if ((style & WS_HSCROLL) != 0)\r
284                         SetScrollPos(_treeView.Handle, 0, _panelStart.X - dx, true);\r
285                     if ((style & WS_VSCROLL) != 0)\r
286                         SetScrollPos(_treeView.Handle, 1, _panelStart.Y - dy / _treeView.ItemHeight, true);\r
287                     _treeView.EndUpdate();\r
288                     handled = true;\r
289                 }\r
290                 else if (Abs(dx) > 5 || Abs(dy) > 5)\r
291                     _touch = true;\r
292             }\r
293 \r
294             public void MouseUp(IntPtr handle, ref bool handled)\r
295             {\r
296                 if (_touch && !_panelStart.IsEmpty && _panelStart != ScrollPosition())\r
297                     handled = true;\r
298                 _touch = false;\r
299                 _mouseStart = _panelStart = Point.Empty;\r
300             }\r
301 \r
302             private Point ScrollPosition()\r
303             {\r
304                 return new Point(GetScrollPos(_treeView.Handle, 0), GetScrollPos(_treeView.Handle, 1));\r
305             }\r
306         }\r
307     }\r
308 }