OSDN Git Service

125%ズーム時に明石タイマーの右端が切れるのを直す
[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 AdjustAkashiTimers()\r
219         {\r
220             var scale = ShipLabel.ScaleFactor;\r
221             if (scale.Height < 1.2 || scale.Height > 1.3)\r
222                 return;\r
223             for (var i = 0; i < _akashiTimers.Length; i++)\r
224             {\r
225                 const int x = 55;\r
226                 var y = 3 + 16 * (i + 1);\r
227                 _akashiTimers[i].Location = new Point((int)Round(x * scale.Width) - 3, (int)Round(y * scale.Height));\r
228                 _akashiTimers[i].Size = new Size((int)Round(31 * scale.Width) + 1, (int)Round(12 * scale.Height));\r
229             }\r
230         }\r
231 \r
232         public void SetAkashiTimer(ShipStatus[] statuses, AkashiTimer.RepairSpan[] timers)\r
233         {\r
234             var shortest = -1;\r
235             for (var i = 0; i < timers.Length; i++)\r
236             {\r
237                 if (timers[i].Span <= TimeSpan.Zero)\r
238                     continue;\r
239                 if (shortest == -1 || timers[i].Span < timers[shortest].Span)\r
240                     shortest = i;\r
241             }\r
242             for (var i = 0; i < _akashiTimers.Length; i++)\r
243             {\r
244                 var label = _akashiTimers[i];\r
245                 var labelHp = _labels[i][0];\r
246                 var labelName = _labels[i][4];\r
247                 if (i >= timers.Length || timers[i].Span == TimeSpan.MinValue)\r
248                 {\r
249                     label.Visible = false;\r
250                     labelHp.ForeColor = Control.DefaultForeColor;\r
251                     continue;\r
252                 }\r
253                 var timer = timers[i];\r
254                 var stat = statuses[i];\r
255                 label.Visible = true;\r
256                 label.Text = timer.Span.ToString(@"mm\:ss");\r
257                 label.ForeColor = Control.DefaultForeColor;\r
258                 labelName.SetName(stat, ShipNameWidth.AkashiTimer);\r
259                 if (timer.Diff == 0)\r
260                 {\r
261                     labelHp.ForeColor = Control.DefaultForeColor;\r
262                     continue;\r
263                 }\r
264                 if (i == shortest)\r
265                     label.ForeColor = Color.Red;\r
266                 labelHp.ForeColor = Color.DimGray;\r
267                 labelHp.SetHp(stat.NowHp + timer.Diff, stat.MaxHp);\r
268             }\r
269         }\r
270 \r
271         public void CreateRepairList(Control parent, EventHandler onClick)\r
272         {\r
273             parent.SuspendLayout();\r
274             for (var i = 0; i < _repairList.Length; i++)\r
275             {\r
276                 var y = 3 + i * 16;\r
277                 const int height = 12;\r
278                 parent.Controls.AddRange(_repairList[i] = new[]\r
279                 {\r
280                     new ShipLabel {Location = new Point(0, y), Size = new Size(11, height)},\r
281                     new ShipLabel {Location = new Point(119, y), Size = new Size(5, height - 1)},\r
282                     new ShipLabel {Location = new Point(75, y), AutoSize = true},\r
283                     new ShipLabel {Location = new Point(9, y), AutoSize = true},\r
284                     new ShipLabel {Location = new Point(0, y - 2), Size = new Size(parent.Width, height + 3)}\r
285                 });\r
286                 foreach (var label in _repairList[i])\r
287                 {\r
288                     label.Scale();\r
289                     label.PresetColor = label.BackColor = ColumnColors[(i + 1) % 2];\r
290                     label.Click += onClick;\r
291                 }\r
292             }\r
293             _panelRepairList = parent;\r
294             parent.ResumeLayout();\r
295         }\r
296 \r
297         public void SetRepairList(ShipStatus[] list)\r
298         {\r
299             const int fleet = 0, name = 3, time = 2, damage = 1;\r
300             var parent = _panelRepairList;\r
301             var num = Min(list.Length, _repairList.Length);\r
302             if (num == 0)\r
303             {\r
304                 parent.Size = new Size(parent.Width, (int)Round(ShipLabel.ScaleFactor.Height * 19));\r
305                 var labels = _repairList[0];\r
306                 labels[fleet].Text = "";\r
307                 labels[name].SetName("なし");\r
308                 labels[time].Text = "";\r
309                 labels[damage].BackColor = labels[damage].PresetColor;\r
310                 return;\r
311             }\r
312             parent.Size = new Size(parent.Width, (int)Round(ShipLabel.ScaleFactor.Height * (num * 16 + 3)));\r
313             for (var i = 0; i < num; i++)\r
314             {\r
315                 var s = list[i];\r
316                 var labels = _repairList[i];\r
317                 labels[fleet].SetFleet(s);\r
318                 labels[name].SetName(s, ShipNameWidth.RepairList);\r
319                 labels[time].SetRepairTime(s);\r
320                 labels[damage].BackColor = ShipLabel.DamageColor(s, labels[damage].PresetColor);\r
321             }\r
322         }\r
323 \r
324         public void CreateNDockLabels(Control parent, EventHandler onClick)\r
325         {\r
326             for (var i = 0; i < _ndockLabels.Length; i++)\r
327             {\r
328                 var y = 3 + i * 15;\r
329                 parent.Controls.AddRange(\r
330                     _ndockLabels[i] = new[]\r
331                     {\r
332                         new ShipLabel\r
333                         {\r
334                             Location = new Point(138, y),\r
335                             AutoSize = true,\r
336                             AnchorRight = true\r
337                         },\r
338                         new ShipLabel {Location = new Point(29, y), AutoSize = true} // 名前のZ-orderを下に\r
339                     });\r
340                 foreach (var label in _ndockLabels[i])\r
341                 {\r
342                     label.Scale();\r
343                     label.Click += onClick;\r
344                 }\r
345             }\r
346         }\r
347 \r
348         public void SetNDockLabels(NameAndTimer[] ndock)\r
349         {\r
350             for (var i = 0; i < _ndockLabels.Length; i++)\r
351                 _ndockLabels[i][1].SetName(ndock[i].Name, ShipNameWidth.NDock);\r
352         }\r
353 \r
354         public void SetNDockTimer(int dock, RingTimer timer, bool finishTime)\r
355         {\r
356             var label = _ndockLabels[dock][0];\r
357             label.ForeColor = timer.IsFinished ? Color.Red : Color.Black;\r
358             label.Text = timer.ToString(finishTime);\r
359         }\r
360     }\r
361 \r
362     [System.ComponentModel.DesignerCategory("Code")]\r
363     public class ShipLabel : Label\r
364     {\r
365         public static SizeF ScaleFactor { get; set; }\r
366         public static Font LatinFont { get; set; } = new Font("Tahoma", 8f);\r
367         public Color PresetColor { get; set; }\r
368         public bool AnchorRight { get; set; }\r
369         private int _right = int.MinValue;\r
370         private int _left;\r
371         private SlotStatus _slotStatus;\r
372 \r
373         [Flags]\r
374         private enum SlotStatus\r
375         {\r
376             Equipped = 0,\r
377             NormalEmpty = 1,\r
378             ExtraEmpty = 2\r
379         }\r
380 \r
381         public ShipLabel()\r
382         {\r
383             UseMnemonic = false;\r
384         }\r
385 \r
386         public void SetName(ShipStatus status, ShipNameWidth width = ShipNameWidth.Max)\r
387         {\r
388             SlotStatus empty = SlotStatus.Equipped;\r
389             if (status.Id != -1)\r
390             {\r
391                 if (status.Slot.All(item => item.Id == -1))\r
392                     empty |= SlotStatus.NormalEmpty;\r
393                 if (status.SlotEx.Id == -1)\r
394                     empty |= SlotStatus.ExtraEmpty;\r
395             }\r
396             var dc = status.PreparedDamageControl;\r
397             var dcname = dc == 42 ? "[ダ]" : dc == 43 ? "[メ]" : "";\r
398             SetName((status.Escaped ? "[避]" : dcname), status.Name, empty, width);\r
399         }\r
400 \r
401         public void SetName(string name)\r
402         {\r
403             SetName("", name, SlotStatus.Equipped);\r
404         }\r
405 \r
406         public void SetName(string name, ShipNameWidth width)\r
407         {\r
408             SetName("", name, SlotStatus.Equipped, width);\r
409         }\r
410 \r
411         private void SetName(string prefix, string name, SlotStatus slotStatus, ShipNameWidth width = ShipNameWidth.Max)\r
412         {\r
413             if (name == null)\r
414                 return;\r
415             _slotStatus = slotStatus;\r
416             var lu = new Regex(@"^\p{Lu}").IsMatch(name);\r
417             var shift = (int)Round(ScaleFactor.Height);\r
418             if (lu && Font.Equals(Parent.Font))\r
419             {\r
420                 Location += new Size(0, -shift);\r
421                 Font = LatinFont;\r
422             }\r
423             else if (!lu && !Font.Equals(Parent.Font))\r
424             {\r
425                 Location += new Size(0, shift);\r
426                 Font = Parent.Font;\r
427             }\r
428             var result = prefix + name;\r
429             var measured = TextRenderer.MeasureText(result, Font).Width;\r
430             if (measured <= (int)width)\r
431             {\r
432                 Text = result;\r
433                 Invalidate(); // 必ずOnPaintを実行させるため\r
434                 return;\r
435             }\r
436             var truncated = "";\r
437             foreach (var ch in name)\r
438             {\r
439                 var tmp = truncated + ch;\r
440                 if (TextRenderer.MeasureText(tmp, Font).Width > (int)width * ScaleFactor.Width)\r
441                     break;\r
442                 truncated = tmp;\r
443             }\r
444             Text = prefix + truncated.TrimEnd(' ');\r
445             Invalidate();\r
446         }\r
447 \r
448         public void SetHp(ShipStatus status)\r
449         {\r
450             Text = $"{status.NowHp:D}/{status.MaxHp:D}";\r
451             BackColor = DamageColor(status, PresetColor);\r
452         }\r
453 \r
454         public void SetHp(int now, int max)\r
455         {\r
456             SetHp(new ShipStatus {NowHp = now, MaxHp = max});\r
457         }\r
458 \r
459         public static Color DamageColor(ShipStatus status, Color backcolor)\r
460         {\r
461             switch (status.DamageLevel)\r
462             {\r
463                 case ShipStatus.Damage.Sunk:\r
464                     return Color.CornflowerBlue;\r
465                 case ShipStatus.Damage.Badly:\r
466                     return Color.Red;\r
467                 case ShipStatus.Damage.Half:\r
468                     return Color.Orange;\r
469                 case ShipStatus.Damage.Small:\r
470                     return Color.FromArgb(240, 240, 0);\r
471                 default:\r
472                     return backcolor;\r
473             }\r
474         }\r
475 \r
476         public void SetCond(ShipStatus status)\r
477         {\r
478             var cond = status.Cond;\r
479             Text = cond.ToString("D");\r
480             BackColor = cond >= 50\r
481                 ? Color.Yellow\r
482                 : cond >= 30\r
483                     ? PresetColor\r
484                     : cond >= 20 ? Color.Orange : Color.Red;\r
485         }\r
486 \r
487         public void SetLevel(ShipStatus status)\r
488         {\r
489             Text = status.Level.ToString("D");\r
490         }\r
491 \r
492         public void SetExpToNext(ShipStatus status)\r
493         {\r
494             Text = status.ExpToNext.ToString("D");\r
495         }\r
496 \r
497         public void SetRepairTime(ShipStatus status)\r
498         {\r
499             SetRepairTime(status.RepairTime);\r
500         }\r
501 \r
502         public void SetRepairTime(TimeSpan span)\r
503         {\r
504             Text = $@"{(int)span.TotalHours:d2}:{span:mm\:ss}";\r
505         }\r
506 \r
507         public void SetFleet(ShipStatus status)\r
508         {\r
509             Text = new[] {"", "1", "2", "3", "4"}[status.Fleet + 1];\r
510         }\r
511 \r
512         protected override void OnLayout(LayoutEventArgs levent)\r
513         {\r
514             base.OnLayout(levent);\r
515             if (!AnchorRight)\r
516                 return;\r
517             if (_right == int.MinValue || _left != Left)\r
518             {\r
519                 _right = Right;\r
520                 _left = Left;\r
521                 return;\r
522             }\r
523             if (_right == Right)\r
524                 return;\r
525             _left -= Right - _right;\r
526             Location = new Point(_left, Top);\r
527         }\r
528 \r
529         protected override void OnPaint(PaintEventArgs e)\r
530         {\r
531             base.OnPaint(e);\r
532             if ((_slotStatus & SlotStatus.NormalEmpty) != 0)\r
533             {\r
534                 e.Graphics.DrawRectangle(\r
535                     Pens.Black,\r
536                     ClientSize.Width - 3 * ScaleFactor.Width, 1 * ScaleFactor.Height,\r
537                     2 * ScaleFactor.Width, 4 * ScaleFactor.Height);\r
538             }\r
539             if ((_slotStatus & SlotStatus.ExtraEmpty) != 0)\r
540             {\r
541                 e.Graphics.DrawRectangle(\r
542                     Pens.Black,\r
543                     ClientSize.Width - 3 * ScaleFactor.Width, 7 * ScaleFactor.Height,\r
544                     2 * ScaleFactor.Width, 3 * ScaleFactor.Height);\r
545             }\r
546         }\r
547 \r
548         public void Scale()\r
549         {\r
550             Scale(ScaleFactor);\r
551         }\r
552     }\r
553 }