OSDN Git Service

装備一覧の更新時の書き換えを最小限にする
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / View / ShipListPanel / ShipListPanel.cs
1 // Copyright (C) 2016 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.Collections.Generic;\r
17 using System.Linq;\r
18 using System.Runtime.InteropServices;\r
19 using System.Windows.Forms;\r
20 using KancolleSniffer.Model;\r
21 using static System.Math;\r
22 \r
23 namespace KancolleSniffer.View.ShipListPanel\r
24 {\r
25     public class ShipListPanel : Panel\r
26     {\r
27         public const int LabelHeight = 12;\r
28         public const int LineHeight = 16;\r
29         private ShipStatus[] _shipList;\r
30         private readonly List<ShipLabel.Hp> _hpLabels = new List<ShipLabel.Hp>();\r
31         private readonly ShipListLabels _shipListLabels;\r
32         private readonly GroupConfigLabels _groupConfigLabels;\r
33         private readonly RepairListLabels _repairListLabels;\r
34         private int _labelCount;\r
35         private string _mode;\r
36         private bool _hpPercent;\r
37 \r
38         public HashSet<int>[] GroupSettings => _groupConfigLabels.GroupSettings;\r
39 \r
40         public bool GroupUpdated\r
41         {\r
42             get => _groupConfigLabels.GroupUpdated;\r
43             set => _groupConfigLabels.GroupUpdated = value;\r
44         }\r
45 \r
46         public ScrollBar ScrollBar { get; }\r
47 \r
48         public ShipStatus GetShip(int i)\r
49         {\r
50             return _shipList[i + ScrollBar.Value];\r
51         }\r
52 \r
53         public ShipListPanel()\r
54         {\r
55             ScrollBar = new VScrollBar {Dock = DockStyle.Right, Visible = false};\r
56             ScrollBar.ValueChanged += ScrollBarOnValueChanged;\r
57             Controls.Add(ScrollBar);\r
58             _shipListLabels = new ShipListLabels(this);\r
59             _groupConfigLabels = new GroupConfigLabels(this);\r
60             _repairListLabels = new RepairListLabels(this);\r
61         }\r
62 \r
63         private void ScrollBarOnValueChanged(object sender, EventArgs eventArgs)\r
64         {\r
65             SuspendDrawing();\r
66             SetShipLabels();\r
67             ResumeDrawing();\r
68         }\r
69 \r
70         protected override void OnResize(EventArgs ev)\r
71         {\r
72             base.OnResize(ev);\r
73             if (_shipList == null || _shipList.Length == 0 || !Visible)\r
74                 return;\r
75             SuspendDrawing();\r
76             SetupLabels();\r
77             SetShipLabels();\r
78             ResumeDrawing();\r
79         }\r
80 \r
81         protected override void OnMouseWheel(MouseEventArgs e)\r
82         {\r
83             if (!ScrollBar.Visible)\r
84                 return;\r
85             ScrollBar.Value = Max(ScrollBar.Minimum, Min(ScrollBar.Maximum - ScrollBar.LargeChange + 1,\r
86                 ScrollBar.Value - e.Delta * SystemInformation.MouseWheelScrollLines / 120));\r
87         }\r
88 \r
89         public void Update(Sniffer sniffer, string mode, ShipListConfig config)\r
90         {\r
91             _mode = mode;\r
92             CreateShipList(sniffer, config);\r
93             SuspendDrawing();\r
94             SetupLabels();\r
95             SetShipLabels();\r
96             ResumeDrawing();\r
97         }\r
98 \r
99         [DllImport("user32.dll")]\r
100         public static extern int SendMessage(IntPtr hWnd, int wMsg, bool wParam, IntPtr lParam);\r
101 \r
102         private void SuspendDrawing()\r
103         {\r
104             SendMessage(Handle, 11, false, IntPtr.Zero); // WM_SETREDRAW = 11\r
105             SuspendLayout();\r
106         }\r
107 \r
108         public void ResumeDrawing()\r
109         {\r
110             ResumeLayout();\r
111             SendMessage(Handle, 11, true, IntPtr.Zero);\r
112             Refresh();\r
113         }\r
114 \r
115         private void CreateShipList(Sniffer sniffer, ShipListConfig config)\r
116         {\r
117             var ships = FilterByShipTypes(\r
118                 _mode == "修復" ? sniffer.RepairList : _groupConfigLabels.FilterByGroup(sniffer.ShipList, _mode),\r
119                 config.ShipCategories).ToArray();\r
120             var order = _mode == "修復" ? ListForm.SortOrder.Repair : config.SortOrder;\r
121             if (!config.ShipType)\r
122             {\r
123                 _shipList = ships.OrderBy(s => s, new CompareShip(false, order)).ToArray();\r
124                 return;\r
125             }\r
126             _shipList = ships.Select(ship => new {Id = ship.Spec.ShipType, Name = ship.Spec.ShipTypeName})\r
127                 .Distinct().Select(type => new ShipStatus\r
128                 {\r
129                     Spec = new ShipSpec {Name = type.Name, ShipType = type.Id},\r
130                     Level = 1000\r
131                 }).Concat(ships).OrderBy(ship => ship, new CompareShip(true, order)).ToArray();\r
132         }\r
133 \r
134         private static readonly int[][] ShipTypeIds =\r
135         {\r
136             new[] // 戦艦\r
137             {\r
138                 8, // 巡洋戦艦\r
139                 9, // 戦艦\r
140                 10 // 航空戦艦\r
141             },\r
142             new[] // 空母\r
143             {\r
144                 18, // 装甲空母\r
145                 11, // 正規空母\r
146                 7 // 軽空母\r
147             },\r
148             new[] // 重巡\r
149             {\r
150                 5, // 重巡洋艦\r
151                 6 // 航空巡洋艦\r
152             },\r
153             new[] // 軽巡\r
154             {\r
155                 3, // 軽巡洋艦\r
156                 4, // 重雷装巡洋艦\r
157                 21 // 練習巡洋艦\r
158             },\r
159             new[] // 駆逐\r
160             {\r
161                 2 // 駆逐艦\r
162             },\r
163             new[] // 海防\r
164             {\r
165                 1 // 海防艦\r
166             },\r
167             new[] // 潜水\r
168             {\r
169                 13, // 潜水艦\r
170                 14 // 潜水空母\r
171             },\r
172             new[] // 補助\r
173             {\r
174                 16, // 水上機母艦\r
175                 17, // 揚陸艦\r
176                 19, // 工作艦\r
177                 20, // 潜水母艦\r
178                 22 // 補給艦\r
179             }\r
180         };\r
181 \r
182         private static readonly int[] ShipTypeSortIds = CreateShipTypeSortIds();\r
183 \r
184         private static int[] CreateShipTypeSortIds()\r
185         {\r
186             var ids = ShipTypeIds.SelectMany(x => x).ToArray();\r
187             var res = new int[ids.Max() + 1];\r
188             for (var i = 0; i < ids.Length; i++)\r
189                 res[ids[i]] = i;\r
190             return res;\r
191         }\r
192 \r
193         private IEnumerable<ShipStatus> FilterByShipTypes(IEnumerable<ShipStatus> ships, ShipCategory shipTypes)\r
194         {\r
195             var ids = Enumerable.Range(0, ShipTypeIds.Length)\r
196                 .Where(type => ((int)shipTypes & (1 << type)) != 0)\r
197                 .SelectMany(type => ShipTypeIds[type]).ToArray();\r
198             return ships.Where(ship => ids.Contains(ship.Spec.ShipType));\r
199         }\r
200 \r
201         public IEnumerable<ShipStatus> CurrentShipList => _shipList.Where(ship => ship.Level != 1000);\r
202 \r
203         private class CompareShip : IComparer<ShipStatus>\r
204         {\r
205             private readonly bool _shipType;\r
206             private readonly ListForm.SortOrder _order;\r
207 \r
208             public CompareShip(bool type, ListForm.SortOrder order)\r
209             {\r
210                 _shipType = type;\r
211                 _order = order;\r
212             }\r
213 \r
214             public int Compare(ShipStatus a, ShipStatus b)\r
215             {\r
216                 if (a == null || b == null)\r
217                     throw new ArgumentNullException();\r
218                 if (_shipType)\r
219                 {\r
220                     if (a.Spec.ShipType != b.Spec.ShipType)\r
221                         return ShipTypeSortIds[a.Spec.ShipType] - ShipTypeSortIds[b.Spec.ShipType];\r
222                     if (a.Level != b.Level)\r
223                     {\r
224                         if (a.Level == 1000)\r
225                             return -1;\r
226                         if (b.Level == 1000)\r
227                             return 1;\r
228                     }\r
229                 }\r
230                 if (_order == ListForm.SortOrder.Repair && a.RepairTime != b.RepairTime)\r
231                     return (int)(b.RepairTime - a.RepairTime).TotalSeconds;\r
232                 if (a.Cond != b.Cond)\r
233                 {\r
234                     if (_order == ListForm.SortOrder.CondAscend)\r
235                         return a.Cond - b.Cond;\r
236                     if (_order == ListForm.SortOrder.CondDescend)\r
237                         return b.Cond - a.Cond;\r
238                 }\r
239                 if (a.Level != b.Level)\r
240                 {\r
241                     if (_order == ListForm.SortOrder.ExpToNextAscend)\r
242                         return b.Level - a.Level;\r
243                     if (_order == ListForm.SortOrder.ExpToNextDescend)\r
244                         return a.Level - b.Level;\r
245                 }\r
246                 if (a.ExpToNext != b.ExpToNext)\r
247                 {\r
248                     if (_order == ListForm.SortOrder.ExpToNextAscend)\r
249                         return a.ExpToNext - b.ExpToNext;\r
250                     if (_order == ListForm.SortOrder.ExpToNextDescend)\r
251                         return b.ExpToNext - a.ExpToNext;\r
252                 }\r
253                 if (_shipType)\r
254                 {\r
255                     if (a.Spec.SortId != b.Spec.SortId)\r
256                         return a.Spec.SortId - b.Spec.SortId;\r
257                     if (a.Level != b.Level)\r
258                         return b.Level - a.Level;\r
259                 }\r
260                 else\r
261                 {\r
262                     if (a.Level != b.Level)\r
263                         return b.Level - a.Level;\r
264                     if (a.Spec.SortId != b.Spec.SortId)\r
265                         return a.Spec.SortId - b.Spec.SortId;\r
266                 }\r
267                 return a.Id - b.Id;\r
268             }\r
269         }\r
270 \r
271         private void SetupLabels()\r
272         {\r
273             for (; _labelCount * LineHeight < Height; _labelCount++)\r
274             {\r
275                 _groupConfigLabels.CreateComponents(_labelCount);\r
276                 _repairListLabels.CreateLabels(_labelCount);\r
277                 _shipListLabels.CreateShipLabels(_labelCount);\r
278             }\r
279             SetupScrollBar();\r
280         }\r
281 \r
282         private void SetupScrollBar()\r
283         {\r
284             var needBar = Scaler.ScaleHeight((float)_shipList.Length * LineHeight) > Height;\r
285             if (!needBar)\r
286             {\r
287                 ScrollBar.Visible = false;\r
288                 ScrollBar.Value = 0;\r
289                 return;\r
290             }\r
291             ScrollBar.Visible = true;\r
292             ScrollBar.Minimum = 0;\r
293             var lines = Max(1, Height / Scaler.ScaleHeight(LineHeight));\r
294             var max = _shipList.Length - lines;\r
295             var largeChange = Min(lines, max);\r
296             ScrollBar.LargeChange = largeChange;\r
297             ScrollBar.Maximum = Max(0, max + largeChange - 1); // ScrollBarを最大まで動かしてもmaxには届かない\r
298             ScrollBar.Value = Min(ScrollBar.Value, max);\r
299         }\r
300 \r
301         public void SetHpPercent(ShipLabel.Hp label)\r
302         {\r
303             if (_hpPercent)\r
304                 label.ToggleHpPercent();\r
305             _hpLabels.Add(label);\r
306             label.DoubleClick += HpLabelClickHandler;\r
307         }\r
308 \r
309         private void SetShipLabels()\r
310         {\r
311             for (var i = 0; i < (Height + LineHeight - 1) / LineHeight; i++)\r
312             {\r
313                 HidePanels(i);\r
314                 if (i + ScrollBar.Value >= _shipList.Length)\r
315                     continue;\r
316                 if (InShipStatus(_mode))\r
317                     _shipListLabels.SetShipStatus(i);\r
318                 if (_mode == "分類")\r
319                     _groupConfigLabels.SetGrouping(i);\r
320                 if (_mode == "修復")\r
321                     _repairListLabels.SetRepairList(i);\r
322             }\r
323         }\r
324 \r
325         public void SetShipType(int i)\r
326         {\r
327             _shipListLabels.SetShipType(i);\r
328         }\r
329 \r
330         private void HidePanels(int i)\r
331         {\r
332             _shipListLabels.HidePanel(i);\r
333             _repairListLabels.HidePanel(i);\r
334             _groupConfigLabels.HidePanel(i);\r
335         }\r
336 \r
337         private bool InShipStatus(string mode) => Array.Exists(new[] {"全艦", "A", "B", "C", "D"}, x => mode == x);\r
338 \r
339         public event Action HpLabelClick;\r
340 \r
341         private void HpLabelClickHandler(object sender, EventArgs ev)\r
342         {\r
343             HpLabelClick?.Invoke();\r
344         }\r
345 \r
346         public void ToggleHpPercent()\r
347         {\r
348             _hpPercent = !_hpPercent;\r
349             foreach (var label in _hpLabels)\r
350                 label.ToggleHpPercent();\r
351         }\r
352 \r
353         public void ShowShip(int id)\r
354         {\r
355             if (!ScrollBar.Visible)\r
356                 return;\r
357             var i = Array.FindIndex(_shipList, s => s.Id == id);\r
358             if (i == -1)\r
359                 return;\r
360             ScrollBar.Value = Min(i, ScrollBar.Maximum + 1 - ScrollBar.LargeChange);\r
361             SetShipLabels();\r
362         }\r
363     }\r
364 }