OSDN Git Service

ShipLabelのメソッドの引数にnullを渡して初期化できるようにする
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / ShipLabels.cs
1 // Copyright (C) 2014, 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.ComponentModel;\r
17 using System.Drawing;\r
18 using System.Linq;\r
19 using System.Text.RegularExpressions;\r
20 using System.Windows.Forms;\r
21 using static System.Math;\r
22 \r
23 namespace KancolleSniffer\r
24 {\r
25     public enum ShipNameWidth\r
26     {\r
27         MainPanel = 93,\r
28         AkashiTimer = 53,\r
29         NDock = 69,\r
30         RepairList = NDock,\r
31         RepairListFull = 75,\r
32         ShipList = 82,\r
33         GroupConfig = 82,\r
34         Combined = 54,\r
35         Max = int.MaxValue\r
36     }\r
37 \r
38     public class ShipLabels\r
39     {\r
40         private readonly ShipLabel[][] _labels = new ShipLabel[ShipInfo.MemberCount][];\r
41         private readonly ShipLabel[][] _combinedLabels = new ShipLabel[ShipInfo.MemberCount * 2][];\r
42         private readonly ShipLabel[] _akashiTimers = new ShipLabel[ShipInfo.MemberCount];\r
43         private readonly ShipLabel[][] _ndockLabels = new ShipLabel[DockInfo.DockCount][];\r
44         public static Color[] ColumnColors = {SystemColors.Control, Color.FromArgb(255, 250, 250, 250)};\r
45 \r
46         public void CreateShipLabels(Control parent, EventHandler onClick)\r
47         {\r
48             parent.SuspendLayout();\r
49             const int top = 3, height = 12, lh = 16;\r
50             ShipLabel[] headings;\r
51             parent.Controls.AddRange(headings = new[]\r
52             {\r
53                 new ShipLabel {Location = new Point(109, top), Text = "HP", AutoSize = true},\r
54                 new ShipLabel {Location = new Point(128, top), Text = "cond", AutoSize = true},\r
55                 new ShipLabel {Location = new Point(163, top), Text = "Lv", AutoSize = true},\r
56                 new ShipLabel {Location = new Point(194, top), Text = "Exp", AutoSize = true},\r
57                 new ShipLabel {Location = new Point(0, 1), Size = new Size(parent.Width, lh - 1)}\r
58             });\r
59             foreach (var label in headings)\r
60             {\r
61                 label.Scale();\r
62                 label.BackColor = ColumnColors[1];\r
63             }\r
64             for (var i = 0; i < _labels.Length; i++)\r
65             {\r
66                 var y = top + lh * (i + 1);\r
67                 parent.Controls.AddRange(_labels[i] = new[]\r
68                 {\r
69                     new ShipLabel {Location = new Point(129, y), AutoSize = true, AnchorRight = true},\r
70                     new ShipLabel\r
71                     {\r
72                         Location = new Point(131, y),\r
73                         Size = new Size(24, height),\r
74                         TextAlign = ContentAlignment.MiddleRight\r
75                     },\r
76                     new ShipLabel\r
77                     {\r
78                         Location = new Point(156, y),\r
79                         Size = new Size(24, height),\r
80                         TextAlign = ContentAlignment.MiddleRight\r
81                     },\r
82                     new ShipLabel\r
83                     {\r
84                         Location = new Point(176, y),\r
85                         Size = new Size(42, height),\r
86                         TextAlign = ContentAlignment.MiddleRight\r
87                     },\r
88                     new ShipLabel {Location = new Point(2, y), AutoSize = true}, // 名前のZ-orderを下に\r
89                     new ShipLabel {Location = new Point(0, y - 2), Size = new Size(parent.Width, lh - 1)}\r
90                 });\r
91                 foreach (var label in _labels[i])\r
92                 {\r
93                     label.Scale();\r
94                     label.PresetColor = label.BackColor = ColumnColors[i % 2];\r
95                     label.Tag = i;\r
96                     label.Click += onClick;\r
97                 }\r
98             }\r
99             parent.ResumeLayout();\r
100         }\r
101 \r
102         public void SetShipLabels(ShipStatus[] statuses)\r
103         {\r
104             for (var i = 0; i < _labels.Length; i++)\r
105             {\r
106                 var labels = _labels[i];\r
107                 var s = i < statuses.Length ? statuses[i] : null;\r
108                 labels[0].SetHp(s);\r
109                 labels[1].SetCond(s);\r
110                 labels[2].SetLevel(s);\r
111                 labels[3].SetExpToNext(s);\r
112                 labels[4].SetName(s, ShipNameWidth.MainPanel);\r
113             }\r
114         }\r
115 \r
116         public void CreateCombinedShipLabels(Control parent, EventHandler onClick)\r
117         {\r
118             parent.SuspendLayout();\r
119             const int top = 3, height = 12, lh = 16;\r
120             const int parentWidth = 220; // parent.Widthを使うとDPIスケーリング時に計算がくるうので\r
121             ShipLabel[] headings;\r
122             parent.Controls.AddRange(headings = new[]\r
123             {\r
124                 new ShipLabel {Location = new Point(68, top), Text = "HP", AutoSize = true},\r
125                 new ShipLabel {Location = new Point(86, top), Text = "cnd", AutoSize = true},\r
126                 new ShipLabel {Location = new Point(177, top), Text = "HP", AutoSize = true},\r
127                 new ShipLabel {Location = new Point(195, top), Text = "cnd", AutoSize = true},\r
128                 new ShipLabel {Location = new Point(0, 1), Size = new Size(parentWidth, lh - 1)}\r
129             });\r
130             foreach (var label in headings)\r
131             {\r
132                 label.Scale();\r
133                 label.BackColor = ColumnColors[1];\r
134             }\r
135             for (var i = 0; i < _combinedLabels.Length; i++)\r
136             {\r
137                 var x = parentWidth / 2 * (i / ShipInfo.MemberCount);\r
138                 var y = top + lh * (i % ShipInfo.MemberCount + 1);\r
139                 parent.Controls.AddRange(_combinedLabels[i] = new[]\r
140                 {\r
141                     new ShipLabel {Location = new Point(x + 88, y), AutoSize = true, AnchorRight = true},\r
142                     new ShipLabel\r
143                     {\r
144                         Location = new Point(x + 85, y),\r
145                         Size = new Size(24, height),\r
146                         TextAlign = ContentAlignment.MiddleRight\r
147                     },\r
148                     new ShipLabel {Location = new Point(x + 2, y), AutoSize = true}, // 名前のZ-orderを下に\r
149                     new ShipLabel {Location = new Point(x, y - 2), Size = new Size(parentWidth / 2, lh - 1)}\r
150                 });\r
151                 foreach (var label in _combinedLabels[i])\r
152                 {\r
153                     label.Scale();\r
154                     label.PresetColor = label.BackColor = ColumnColors[i % 2];\r
155                     label.Tag = i;\r
156                     label.Click += onClick;\r
157                 }\r
158             }\r
159             parent.ResumeLayout();\r
160         }\r
161 \r
162         public void SetCombinedShipLabels(ShipStatus[] first, ShipStatus[] second)\r
163         {\r
164             for (var i = 0; i < _combinedLabels.Length; i++)\r
165             {\r
166                 var idx = i % ShipInfo.MemberCount;\r
167                 var statuses = i < ShipInfo.MemberCount ? first : second;\r
168                 var labels = _combinedLabels[i];\r
169                 var s = idx < statuses.Length ? statuses[idx] : null;\r
170                 labels[0].SetHp(s);\r
171                 labels[1].SetCond(s);\r
172                 labels[2].SetName(s, ShipNameWidth.Combined);\r
173             }\r
174         }\r
175 \r
176         public void CreateAkashiTimers(Control parent)\r
177         {\r
178             parent.SuspendLayout();\r
179             for (var i = 0; i < _akashiTimers.Length; i++)\r
180             {\r
181                 const int x = 55;\r
182                 var y = 3 + 16 * (i + 1);\r
183                 ShipLabel label;\r
184                 parent.Controls.Add(\r
185                     label = _akashiTimers[i] =\r
186                         new ShipLabel\r
187                         {\r
188                             Location = new Point(x, y),\r
189                             Size = new Size(31, 12),\r
190                             TextAlign = ContentAlignment.TopRight\r
191                         });\r
192                 label.BackColor = ColumnColors[i % 2];\r
193             }\r
194             foreach (var label in _akashiTimers)\r
195                 label.Scale();\r
196             parent.ResumeLayout();\r
197         }\r
198 \r
199         public void AdjustAkashiTimers()\r
200         {\r
201             var scale = ShipLabel.ScaleFactor;\r
202             if (scale.Height < 1.2)\r
203                 return;\r
204             for (var i = 0; i < _akashiTimers.Length; i++)\r
205             {\r
206                 const int x = 55;\r
207                 var y = 3 + 16 * (i + 1);\r
208                 _akashiTimers[i].Location = new Point((int)Round(x * scale.Width) - 3, (int)Round(y * scale.Height));\r
209                 _akashiTimers[i].Size = new Size((int)Round(31 * scale.Width) + 1, (int)Round(12 * scale.Height));\r
210             }\r
211         }\r
212 \r
213         public void SetAkashiTimer(ShipStatus[] statuses, AkashiTimer.RepairSpan[] timers)\r
214         {\r
215             var shortest = -1;\r
216             for (var i = 0; i < timers.Length; i++)\r
217             {\r
218                 if (timers[i].Span <= TimeSpan.Zero)\r
219                     continue;\r
220                 if (shortest == -1 || timers[i].Span < timers[shortest].Span)\r
221                     shortest = i;\r
222             }\r
223             for (var i = 0; i < _akashiTimers.Length; i++)\r
224             {\r
225                 var label = _akashiTimers[i];\r
226                 var labelHp = _labels[i][0];\r
227                 var labelName = _labels[i][4];\r
228                 if (i >= timers.Length || timers[i].Span == TimeSpan.MinValue)\r
229                 {\r
230                     label.Visible = false;\r
231                     labelHp.ForeColor = Control.DefaultForeColor;\r
232                     continue;\r
233                 }\r
234                 var timer = timers[i];\r
235                 var stat = statuses[i];\r
236                 label.Visible = true;\r
237                 label.Text = timer.Span.ToString(@"mm\:ss");\r
238                 label.ForeColor = Control.DefaultForeColor;\r
239                 labelName.SetName(stat, ShipNameWidth.AkashiTimer);\r
240                 if (timer.Diff == 0)\r
241                 {\r
242                     labelHp.ForeColor = Control.DefaultForeColor;\r
243                     continue;\r
244                 }\r
245                 if (i == shortest)\r
246                     label.ForeColor = CUDColor.Red;\r
247                 labelHp.ForeColor = Color.DimGray;\r
248                 labelHp.SetHp(stat.NowHp + timer.Diff, stat.MaxHp);\r
249             }\r
250         }\r
251 \r
252         public void CreateNDockLabels(Control parent, EventHandler onClick)\r
253         {\r
254             for (var i = 0; i < _ndockLabels.Length; i++)\r
255             {\r
256                 var y = 3 + i * 15;\r
257                 parent.Controls.AddRange(\r
258                     _ndockLabels[i] = new[]\r
259                     {\r
260                         new ShipLabel\r
261                         {\r
262                             Location = new Point(138, y),\r
263                             AutoSize = true,\r
264                             AnchorRight = true\r
265                         },\r
266                         new ShipLabel {Location = new Point(29, y), AutoSize = true} // 名前のZ-orderを下に\r
267                     });\r
268                 foreach (var label in _ndockLabels[i])\r
269                 {\r
270                     label.Scale();\r
271                     label.Click += onClick;\r
272                 }\r
273             }\r
274         }\r
275 \r
276         public void SetNDockLabels(NameAndTimer[] ndock)\r
277         {\r
278             for (var i = 0; i < _ndockLabels.Length; i++)\r
279                 _ndockLabels[i][1].SetName(ndock[i].Name, ShipNameWidth.NDock);\r
280         }\r
281 \r
282         public void SetNDockTimer(int dock, RingTimer timer, bool finishTime)\r
283         {\r
284             var label = _ndockLabels[dock][0];\r
285             label.ForeColor = timer.IsFinished ? CUDColor.Red : Color.Black;\r
286             label.Text = timer.ToString(finishTime);\r
287         }\r
288     }\r
289 \r
290     [DesignerCategory("Code")]\r
291     public class ShipLabel : Label\r
292     {\r
293         public static SizeF ScaleFactor { get; set; }\r
294         public static Font LatinFont { get; set; } = new Font("Tahoma", 8f);\r
295         public Color PresetColor { get; set; }\r
296         public bool AnchorRight { get; set; }\r
297         private int _right = int.MinValue;\r
298         private int _left;\r
299         private SlotStatus _slotStatus;\r
300 \r
301         public override Color BackColor\r
302         {\r
303             get => base.BackColor;\r
304             set => base.BackColor = value == DefaultBackColor ? PresetColor : value;\r
305         }\r
306 \r
307         [Flags]\r
308         private enum SlotStatus\r
309         {\r
310             Equipped = 0,\r
311             NormalEmpty = 1,\r
312             ExtraEmpty = 2\r
313         }\r
314 \r
315         public ShipLabel()\r
316         {\r
317             UseMnemonic = false;\r
318         }\r
319 \r
320         public void SetName(ShipStatus status, ShipNameWidth width = ShipNameWidth.Max)\r
321         {\r
322             if (status == null)\r
323             {\r
324                 SetName("");\r
325                 return;\r
326             }\r
327             var empty = SlotStatus.Equipped;\r
328             if (status.Id != -1)\r
329             {\r
330                 if (status.Slot.All(item => item.Id == -1))\r
331                     empty |= SlotStatus.NormalEmpty;\r
332                 if (status.SlotEx.Id == -1)\r
333                     empty |= SlotStatus.ExtraEmpty;\r
334             }\r
335             var dc = status.PreparedDamageControl;\r
336             var dcname = dc == 42 ? "[ダ]" : dc == 43 ? "[メ]" : "";\r
337             SetName(status.Escaped ? "[避]" : dcname, status.Name, empty, width);\r
338         }\r
339 \r
340         public void SetName(string name)\r
341         {\r
342             SetName("", name, SlotStatus.Equipped);\r
343         }\r
344 \r
345         public void SetName(string name, ShipNameWidth width)\r
346         {\r
347             SetName("", name, SlotStatus.Equipped, width);\r
348         }\r
349 \r
350         private void SetName(string prefix, string name, SlotStatus slotStatus, ShipNameWidth width = ShipNameWidth.Max)\r
351         {\r
352             if (name == null)\r
353                 name = "";\r
354             _slotStatus = slotStatus;\r
355             var lu = new Regex(@"^\p{Lu}").IsMatch(name);\r
356             var shift = (int)Round(ScaleFactor.Height);\r
357             if (lu && Font.Equals(Parent.Font))\r
358             {\r
359                 Location += new Size(0, -shift);\r
360                 Font = LatinFont;\r
361             }\r
362             else if (!lu && !Font.Equals(Parent.Font))\r
363             {\r
364                 Location += new Size(0, shift);\r
365                 Font = Parent.Font;\r
366             }\r
367             var result = prefix + name;\r
368             var measured = TextRenderer.MeasureText(result, Font).Width;\r
369             if (measured <= (int)width)\r
370             {\r
371                 Text = result;\r
372                 Invalidate(); // 必ずOnPaintを実行させるため\r
373                 return;\r
374             }\r
375             var truncated = "";\r
376             foreach (var ch in name)\r
377             {\r
378                 var tmp = truncated + ch;\r
379                 if (TextRenderer.MeasureText(tmp, Font).Width > (int)width * ScaleFactor.Width)\r
380                     break;\r
381                 truncated = tmp;\r
382             }\r
383             Text = prefix + truncated.TrimEnd(' ');\r
384             Invalidate();\r
385         }\r
386 \r
387         public void SetHp(ShipStatus status)\r
388         {\r
389             if (status == null)\r
390             {\r
391                 Text = "";\r
392                 BackColor = PresetColor;\r
393                 return;\r
394             }\r
395             Text = $"{status.NowHp:D}/{status.MaxHp:D}";\r
396             BackColor = DamageColor(status, PresetColor);\r
397         }\r
398 \r
399         public void SetHp(int now, int max)\r
400         {\r
401             SetHp(new ShipStatus {NowHp = now, MaxHp = max});\r
402         }\r
403 \r
404         public static Color DamageColor(ShipStatus status, Color backcolor)\r
405         {\r
406             switch (status.DamageLevel)\r
407             {\r
408                 case ShipStatus.Damage.Sunk:\r
409                     return Color.CornflowerBlue;\r
410                 case ShipStatus.Damage.Badly:\r
411                     return CUDColor.Red;\r
412                 case ShipStatus.Damage.Half:\r
413                     return CUDColor.Orange;\r
414                 case ShipStatus.Damage.Small:\r
415                     return Color.FromArgb(240, 240, 0);\r
416                 default:\r
417                     return backcolor;\r
418             }\r
419         }\r
420 \r
421         public void SetCond(ShipStatus status)\r
422         {\r
423             if (status == null)\r
424             {\r
425                 Text = "";\r
426                 BackColor = PresetColor;\r
427                 return;\r
428             }\r
429             var cond = status.Cond;\r
430             Text = cond.ToString("D");\r
431             BackColor = cond >= 50\r
432                 ? CUDColor.Yellow\r
433                 : cond >= 30\r
434                     ? PresetColor\r
435                     : cond >= 20\r
436                         ? CUDColor.Orange\r
437                         : CUDColor.Red;\r
438         }\r
439 \r
440         public void SetLevel(ShipStatus status)\r
441         {\r
442             Text = status?.Level.ToString("D");\r
443         }\r
444 \r
445         public void SetExpToNext(ShipStatus status)\r
446         {\r
447             Text = status?.ExpToNext.ToString("D");\r
448         }\r
449 \r
450         public void SetRepairTime(ShipStatus status)\r
451         {\r
452             if (status == null)\r
453             {\r
454                 Text = "";\r
455                 return;\r
456             }\r
457             SetRepairTime(status.RepairTime);\r
458         }\r
459 \r
460         public void SetRepairTime(TimeSpan span)\r
461         {\r
462             Text = $@"{(int)span.TotalHours:d2}:{span:mm\:ss}";\r
463         }\r
464 \r
465         public void SetFleet(ShipStatus status)\r
466         {\r
467             Text = status == null ? "" : new[] {"", "1", "2", "3", "4"}[status.Fleet + 1];\r
468         }\r
469 \r
470         protected override void OnLayout(LayoutEventArgs levent)\r
471         {\r
472             base.OnLayout(levent);\r
473             if (!AnchorRight)\r
474                 return;\r
475             if (_right == int.MinValue || _left != Left)\r
476             {\r
477                 _right = Right;\r
478                 _left = Left;\r
479                 return;\r
480             }\r
481             if (_right == Right)\r
482                 return;\r
483             _left -= Right - _right;\r
484             Location = new Point(_left, Top);\r
485         }\r
486 \r
487         protected override void OnPaint(PaintEventArgs e)\r
488         {\r
489             base.OnPaint(e);\r
490             if ((_slotStatus & SlotStatus.NormalEmpty) != 0)\r
491             {\r
492                 e.Graphics.DrawRectangle(\r
493                     Pens.Black,\r
494                     ClientSize.Width - 3 * ScaleFactor.Width, 1 * ScaleFactor.Height,\r
495                     2 * ScaleFactor.Width, 4 * ScaleFactor.Height);\r
496             }\r
497             if ((_slotStatus & SlotStatus.ExtraEmpty) != 0)\r
498             {\r
499                 e.Graphics.DrawRectangle(\r
500                     Pens.Black,\r
501                     ClientSize.Width - 3 * ScaleFactor.Width, 7 * ScaleFactor.Height,\r
502                     2 * ScaleFactor.Width, 3 * ScaleFactor.Height);\r
503             }\r
504         }\r
505 \r
506         public void Scale()\r
507         {\r
508             Scale(ScaleFactor);\r
509         }\r
510     }\r
511 }