OSDN Git Service

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