OSDN Git Service

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