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