MainForm.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. using BsWidget.BeatSaberHttpStatus;
  2. using BsWidget.BsYurHttpStatus;
  3. using BsWidgetShareCodes;
  4. using System;
  5. using System.Collections.Concurrent;
  6. using System.Collections.Generic;
  7. using System.ComponentModel;
  8. using System.Drawing;
  9. using System.Drawing.Drawing2D;
  10. using System.Drawing.Text;
  11. using System.IO;
  12. using System.Linq;
  13. using System.Windows.Forms;
  14. namespace BsWidget
  15. {
  16. internal class MainForm : BaseForm
  17. {
  18. private Timer _updateTimer;
  19. private BlockingCollection<UpdateFlags> _queue;
  20. private BeatSaberHttpStatusClient _client;
  21. private BsYurHttpStatusClient _yurClient;
  22. private Font _smallFont = new Font("", 12, FontStyle.Regular, GraphicsUnit.Pixel);
  23. private Font _mediumFont = new Font("", 20, FontStyle.Regular, GraphicsUnit.Pixel);
  24. [Flags]
  25. private enum UpdateFlags
  26. {
  27. BeatMap = 1 << 0,
  28. Performance = 1 << 1,
  29. NoteFullyCut = 1 << 2,
  30. ClearAll = 1 << 3,
  31. RefreshAll = 1 << 4,
  32. }
  33. public Image SongIcon { get; set; } = Properties.Resources.sample_cover;
  34. public string SongName { get; set; } = "Song Name";
  35. public string SongSubName { get; set; } = "Song Sub Name";
  36. public string SongArtist { get; set; } = "Song Artist";
  37. public string BeatMapper { get; set; } = "Mapper";
  38. public string Difficulty { get; set; } = "Difficulty";
  39. public double SongBpm { get; set; } = 123;
  40. public double SongNjs { get; set; } = 23;
  41. public int CurrentScore { get; set; } = 12345;
  42. public int CurrentCombo { get; set; } = 120;
  43. public string CurrentRank { get; set; } = "XX";
  44. public int CurrentMaxScore { get; set; }
  45. public YurStatus YurStatus { get; set; } = new YurStatus { HeartRate = 0, KcalPerMin = 0 };
  46. /// <summary> FinalScore,CurrentCombo </summary>
  47. public List<HistoryModel> CutHistory { get; set; }
  48. public class HistoryModel
  49. {
  50. public int CutScore { get; set; }
  51. public int CurrentCombo { get; set; }
  52. public float Percent { get; set; }
  53. public float HeartRate { get; set; }
  54. public float KcalPerMin { get; set; }
  55. }
  56. public MainForm()
  57. {
  58. KeyPreview = true;
  59. FormBorderStyle = FormBorderStyle.None;
  60. _client = new BeatSaberHttpStatusClient();
  61. _client.Event += BeatSaber_Event;
  62. _yurClient = new BsYurHttpStatusClient();
  63. _yurClient.Event += Yur_Event;
  64. Text = "Beat Saber Status Widget";
  65. _queue = new BlockingCollection<UpdateFlags>();
  66. CutHistory = new List<HistoryModel>();
  67. _updateTimer = new Timer { Interval = 25 };
  68. _updateTimer.Tick += UpdateTimer_Tick;
  69. }
  70. protected override void OnLoad(EventArgs e)
  71. {
  72. base.OnLoad(e);
  73. Font = new Font("", 30, FontStyle.Bold, GraphicsUnit.Pixel);
  74. TopMost = false;
  75. WindowState = FormWindowState.Normal;
  76. _updateTimer.Start();
  77. _client.Start();
  78. _yurClient.Start();
  79. }
  80. protected override void OnShown(EventArgs e)
  81. {
  82. WindowState = FormWindowState.Normal;
  83. Location = Screen.PrimaryScreen.Bounds.Location;
  84. ClientSize = Screen.PrimaryScreen.Bounds.Size;
  85. TopMost = true;
  86. base.OnShown(e);
  87. if (_queue.Count == 0)
  88. {
  89. _queue.Add(UpdateFlags.RefreshAll);
  90. }
  91. }
  92. protected override void OnClosing(CancelEventArgs e)
  93. {
  94. _client.Stop();
  95. _updateTimer.Stop();
  96. base.OnClosing(e);
  97. }
  98. protected override void OnKeyDown(KeyEventArgs e)
  99. {
  100. base.OnKeyDown(e);
  101. if (e.KeyCode == Keys.Escape) Application.Exit();
  102. }
  103. private void BeatSaber_Event(object sender, BeatSaberStatusEventArgs e)
  104. {
  105. if (e.Event == "noteMissed")
  106. {
  107. var bp = 0;
  108. }
  109. var flags = (UpdateFlags)0;
  110. if (e.Event == "menu" && CutHistory.Count > 0)
  111. {
  112. lock (CutHistory)
  113. {
  114. CutHistory.Clear();
  115. }
  116. flags |= UpdateFlags.ClearAll;
  117. }
  118. if (null != e.Status.Beatmap)
  119. {
  120. var bytes = Convert.FromBase64String(e.Status.Beatmap.SongCover);
  121. using var stream = new MemoryStream(bytes);
  122. var newImg = Image.FromStream(stream);
  123. var old = SongIcon;
  124. SongIcon = newImg;
  125. old?.Dispose();
  126. SongName = e.Status.Beatmap.SongName;
  127. SongSubName = e.Status.Beatmap.SongSubName;
  128. SongArtist = e.Status.Beatmap.SongAuthorName;
  129. BeatMapper = e.Status.Beatmap.LevelAuthorName;
  130. if (string.IsNullOrEmpty(BeatMapper)) BeatMapper = "Unknown Mapper";
  131. Difficulty = e.Status.Beatmap.Difficulty.ToUpper();
  132. if (Difficulty == "EXPERTPLUS") Difficulty = "EXPERT+";
  133. SongBpm = e.Status.Beatmap.SongBPM;
  134. SongNjs = e.Status.Beatmap.NoteJumpSpeed;
  135. flags |= UpdateFlags.BeatMap;
  136. }
  137. if (null != e.Status.Performance)
  138. {
  139. CurrentScore = e.Status.Performance.Score;
  140. CurrentCombo = e.Status.Performance.Combo;
  141. CurrentRank = e.Status.Performance.Rank;
  142. CurrentMaxScore = e.Status.Performance.CurrentMaxScore;
  143. flags |= UpdateFlags.Performance;
  144. }
  145. if (e.Event == "noteFullyCut" && null != e.NoteCut?.FinalScore)
  146. {
  147. lock (CutHistory)
  148. {
  149. CutHistory.Add(new HistoryModel
  150. {
  151. CutScore = e.NoteCut.FinalScore.Value,
  152. CurrentCombo = CurrentCombo,
  153. Percent = (float)CurrentScore / CurrentMaxScore,
  154. HeartRate = YurStatus.HeartRate,
  155. KcalPerMin = YurStatus.KcalPerMin,
  156. });
  157. }
  158. flags |= UpdateFlags.NoteFullyCut;
  159. }
  160. if (flags == 0)
  161. {
  162. var bp = 0;
  163. }
  164. else
  165. {
  166. _queue.Add(flags);
  167. }
  168. }
  169. private void Yur_Event(object sender, YurStatus e) => YurStatus = e;
  170. private void UpdateTimer_Tick(object sender, EventArgs e)
  171. {
  172. if (_queue.Count == 0) return;
  173. UpdateGraphic();
  174. }
  175. protected override void RenderGraphic(Graphics g)
  176. {
  177. const int margin = 10;
  178. var ChWidth = ViewSize.Width - margin * 2;
  179. var ChHeight = 64;
  180. if (_queue.Count == 0) return;
  181. var flags = _queue.Take();
  182. if (flags.HasFlag(UpdateFlags.ClearAll)) g.Clear(Color.Transparent);
  183. g.SetHighQuality();
  184. float nextY = 0;
  185. if (flags.HasFlag(UpdateFlags.NoteFullyCut) || flags.HasFlag(UpdateFlags.RefreshAll))
  186. {
  187. var ChLeft = margin;
  188. var ChTop = margin;
  189. var ChBottom = ChTop + ChHeight;
  190. float RecordPixel = 5;
  191. var LineWidth = 2f;
  192. var DisplayCount = (int)(ChWidth / RecordPixel);
  193. using var scorePen = new Pen(Color.FromArgb(200, 255, 0, 0), LineWidth) { LineJoin = LineJoin.Round };
  194. using var comboPen = new Pen(Color.FromArgb(200, 0, 0, 255), LineWidth) { LineJoin = LineJoin.Round };
  195. using var percentPen = new Pen(Color.FromArgb(200, Color.Green), LineWidth) { LineJoin = LineJoin.Round };
  196. using var heartPen = new Pen(Color.FromArgb(200, Color.White), LineWidth) { LineJoin = LineJoin.Round };
  197. using var calPen = new Pen(Color.FromArgb(200, Color.Lime), LineWidth) { LineJoin = LineJoin.Round };
  198. //clear region 0,210 Wx200
  199. g.ClearRect(Color.Transparent, 0, ChTop - 3, ViewSize.Width, ChHeight + 6 + 20);
  200. //draw chart
  201. using var bgBrush = new SolidBrush(Color.FromArgb(90, 255, 255, 255));
  202. g.FillRectangle(bgBrush, ChLeft, ChTop, ChWidth, ChHeight);
  203. g.DrawRectangle(Pens.White, ChLeft - 1, ChTop - 1, ChWidth + 2, ChHeight + 2);
  204. if (CutHistory.Count > 1)
  205. {
  206. HistoryModel[] items;
  207. lock (CutHistory)
  208. {
  209. var skip = CutHistory.Count > DisplayCount ? CutHistory.Count - DisplayCount : 0;
  210. items = CutHistory.Skip(skip).ToArray();
  211. }
  212. if (items.Length > 1)
  213. {
  214. var minScore = items.Select(p => p.CutScore).Min();
  215. var maxScore = items.Select(p => p.CutScore).Max();
  216. var rngScore = maxScore - minScore;
  217. if (rngScore < 1) rngScore = 1;
  218. var minCombo = items.Select(p => p.CurrentCombo).Min();
  219. var maxCombo = items.Select(p => p.CurrentCombo).Max();
  220. var rngCombo = maxCombo - minCombo;
  221. if (rngCombo < 1) rngCombo = 1;
  222. var minHea = items.Select(p => p.HeartRate).Min();
  223. var maxHea = items.Select(p => p.HeartRate).Max();
  224. var rngHea = maxHea - minHea;
  225. if (rngHea < 1) rngHea = 1;
  226. var minCal = items.Select(p => p.KcalPerMin).Min();
  227. var maxCal = items.Select(p => p.KcalPerMin).Max();
  228. var rngCal = maxCal - minCal;
  229. if (rngCal < 1) rngCal = 1;
  230. var x = ChWidth - items.Length * RecordPixel + ChLeft + 1;
  231. var scorePts = new List<PointF>();
  232. var comboPts = new List<PointF>();
  233. var percentPts = new List<PointF>();
  234. var heaPts = new List<PointF>();
  235. var calPts = new List<PointF>();
  236. for (var i = 0; i < items.Length; i++)
  237. {
  238. var item = items[i];
  239. scorePts.Add(new PointF(x + RecordPixel * i, (ChBottom - 1) - (ChHeight - 2) * ((float)(item.CutScore - minScore) / rngScore)));
  240. comboPts.Add(new PointF(x + RecordPixel * i, (ChBottom - 1) - (ChHeight - 2) * ((float)(item.CurrentCombo - minCombo) / rngCombo)));
  241. percentPts.Add(new PointF(x + RecordPixel * i, (ChBottom - 1) - (ChHeight - 2) * item.Percent));
  242. heaPts.Add(new PointF(x + RecordPixel * i, (ChBottom - 1) - (ChHeight - 2) * ((item.HeartRate - minHea) / rngHea)));
  243. calPts.Add(new PointF(x + RecordPixel * i, (ChBottom - 1) - (ChHeight - 2) * ((item.KcalPerMin - minCal) / rngCal)));
  244. }
  245. g.DrawLines(scorePen, scorePts.ToArray());
  246. g.DrawLines(comboPen, comboPts.ToArray());
  247. g.DrawLines(percentPen, percentPts.ToArray());
  248. g.DrawLines(heartPen, heaPts.ToArray());
  249. g.DrawLines(calPen, calPts.ToArray());
  250. }
  251. }
  252. nextY = ChHeight + margin * 2;
  253. }
  254. if (flags.HasFlag(UpdateFlags.Performance) || flags.HasFlag(UpdateFlags.RefreshAll))
  255. {
  256. float x = margin;
  257. float y = ChHeight + margin * 2;
  258. //clear region 0,180 Wx180
  259. g.ClearRect(Color.Transparent, 0, y, ViewSize.Width, 50);
  260. //draw text
  261. var sz = g.DrawStringWithOutline($"{CurrentScore:N0}", Font, x, y, Color.Black, Color.White);
  262. x += sz.Width + margin * 2;
  263. sz = g.DrawStringWithRoundedRect("POINT", _smallFont, x, y + margin * 1.5f, Brushes.White, Brushes.Red,
  264. 2.5f);
  265. x += sz.Width + margin * 2;
  266. sz = g.DrawStringWithOutline($"{CurrentCombo}", Font, x, y, Color.Black, Color.White);
  267. x += sz.Width + margin * 2;
  268. sz = g.DrawStringWithRoundedRect("COMBO", _smallFont, x, y + margin * 1.5f, Brushes.White, Brushes.Blue,
  269. 2.5f);
  270. x += sz.Width + margin * 2;
  271. sz = g.DrawStringWithOutline($"{CurrentRank}", Font, x, y, Color.Black, Color.White);
  272. x += sz.Width + margin * 2;
  273. sz = g.DrawStringWithRoundedRect("RANK", _smallFont, x, y + margin * 1.5f, Brushes.White,
  274. Brushes.Green, 2.5f);
  275. x += sz.Width + margin * 2;
  276. sz = g.DrawStringWithOutline($"{YurStatus.HeartRate:N1}", Font, x, y, Color.Black, Color.White);
  277. x += sz.Width + margin * 2;
  278. sz = g.DrawStringWithRoundedRect("HEART", _smallFont, x, y + margin * 1.5f, Brushes.Black,
  279. Brushes.White, 2.5f);
  280. x += sz.Width + margin * 2;
  281. sz = g.DrawStringWithOutline($"{YurStatus.KcalPerMin:N1}", Font, x, y, Color.Black, Color.White);
  282. x += sz.Width + margin * 2;
  283. sz = g.DrawStringWithRoundedRect("KCAL / MIN", _smallFont, x, y + margin * 1.5f, Brushes.Black,
  284. Brushes.Lime, 2.5f);
  285. }
  286. if (flags.HasFlag(UpdateFlags.BeatMap) || flags.HasFlag(UpdateFlags.RefreshAll))
  287. {
  288. const int coverSize = 130;
  289. g.ClearRect(Color.Transparent, 0, ViewSize.Height - (coverSize + 100), ViewSize.Width, coverSize + 100);
  290. var coverLeft = ViewSize.Width - coverSize - margin * 2;
  291. var coverTop = ViewSize.Height - coverSize - margin * 2;
  292. g.DrawImage(SongIcon, coverLeft, coverTop, coverSize, coverSize);
  293. g.DrawRectangle(Pens.White, coverLeft, coverTop, coverSize, coverSize);
  294. g.DrawStringWithRoundedRect(Difficulty, Font, coverLeft - 20, coverTop - 100, Brushes.Black, Brushes.White);
  295. {
  296. float x = coverLeft - 80;
  297. var y = coverTop - 50;
  298. var sz = g.DrawStringWithOutline($"{SongBpm}", Font, x, y, Color.Black, Color.White);
  299. x += sz.Width + margin * 2;
  300. sz = g.DrawStringWithRoundedRect("BPM", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White, 2.5f);
  301. x += sz.Width + margin * 2;
  302. sz = g.DrawStringWithOutline($"{SongNjs}", Font, x, y, Color.Black, Color.White);
  303. x += sz.Width + margin * 2;
  304. sz = g.DrawStringWithRoundedRect("NJS", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White, 2.5f);
  305. }
  306. {
  307. var bootom = ViewSize.Height - margin * 1.5f;
  308. var right = coverLeft - margin;
  309. var fontDeltaW = 1.3f;
  310. var fontDeltaH = 1.4f;
  311. var sz = g.MeasureString(SongName, _mediumFont);
  312. g.DrawStringWithOutline(SongName, _mediumFont, right - sz.Width * fontDeltaW, bootom - sz.Height * fontDeltaH, Color.Black, Color.White);
  313. sz = g.MeasureString(SongSubName, _mediumFont);
  314. g.DrawStringWithOutline(SongSubName, _mediumFont, right - sz.Width * fontDeltaW, bootom - (sz.Height * fontDeltaH) * 2, Color.Black, Color.White);
  315. sz = g.MeasureString(SongArtist, _mediumFont);
  316. g.DrawStringWithOutline(SongArtist, _mediumFont, right - sz.Width * fontDeltaW, bootom - (sz.Height * fontDeltaH) * 3, Color.Black, Color.White);
  317. sz = g.MeasureString(BeatMapper, _mediumFont);
  318. g.DrawStringWithOutline(BeatMapper, _mediumFont, right - sz.Width * fontDeltaW, bootom - (sz.Height * fontDeltaH) * 4, Color.Black, Color.White);
  319. }
  320. }
  321. if (flags.HasFlag(UpdateFlags.Performance) || flags.HasFlag(UpdateFlags.RefreshAll))
  322. {
  323. // //clear region 0,180 Wx180
  324. // g.ClearRect(Color.Transparent, 0, nextY + margin, ViewSize.Width, fontHeight);
  325. // //draw text
  326. // float x = margin;
  327. // float y = nextY + margin;
  328. // var sz = g.DrawStringWithOutline($"{CurrentScore:N0}", Font, x, y, Color.Black, Color.White);
  329. // x += sz.Width + margin * 2;
  330. // sz = g.DrawStringWithRoundedRect("POINT", _smallFont, x, y + margin * 1.5f, Brushes.Black,
  331. // Brushes.White, 2.5f);
  332. // x += sz.Width + margin * 2;
  333. // sz = g.DrawStringWithOutline($"{CurrentCombo}", Font, x, y, Color.Black, Color.White);
  334. // x += sz.Width + margin * 2;
  335. // sz = g.DrawStringWithRoundedRect("COMBO", _smallFont, x, y + margin * 1.5f, Brushes.Black,
  336. // Brushes.White, 2.5f);
  337. // x += sz.Width + margin * 2;
  338. // sz = g.DrawStringWithOutline($"{CurrentRank}", Font, x, y, Color.Black, Color.White);
  339. // x += sz.Width + margin * 2;
  340. // sz = g.DrawStringWithRoundedRect("RANK", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White,
  341. // 2.5f);
  342. // x += sz.Width + margin * 2;
  343. // sz = g.DrawStringWithOutline($"{YurStatus.HeartRate:N1}", Font, x, y, Color.Black, Color.White);
  344. // x += sz.Width + margin * 2;
  345. // sz = g.DrawStringWithRoundedRect("HEART", _smallFont, x, y + margin * 1.5f, Brushes.Black,
  346. // Brushes.White, 2.5f);
  347. // x += sz.Width + margin * 2;
  348. // sz = g.DrawStringWithOutline($"{YurStatus.KcalPerMin:N1}", Font, x, y, Color.Black, Color.White);
  349. // x += sz.Width + margin * 2;
  350. // sz = g.DrawStringWithRoundedRect("KCAL/MIN", _smallFont, x, y + margin * 1.5f, Brushes.Black,
  351. // Brushes.White, 2.5f);
  352. }
  353. if (flags.HasFlag(UpdateFlags.BeatMap) || flags.HasFlag(UpdateFlags.RefreshAll))
  354. {
  355. // //clear region
  356. // g.ClearRect(Color.Transparent, 0, 0, ViewSize.Width, margin + coverSize + margin);
  357. // //draw cover and text
  358. // g.DrawImage(SongIcon, margin, margin, coverSize, coverSize);
  359. // g.DrawRectangle(Pens.White, 10, 10, coverSize, coverSize);
  360. // float x = coverSize + margin;
  361. // var y = margin;
  362. // var sz = g.DrawStringWithOutline(SongName, Font, x, y, Color.Black, Color.White);
  363. // x += sz.Width + margin * 2;
  364. // sz = g.DrawStringWithRoundedRect(Difficulty, Font, x, y + 5, Brushes.Black, Brushes.White);
  365. // g.DrawRoundedRectangle(Pens.Black, new RectangleF(x + 2, y + 7, sz.Width - 4, sz.Height - 4), 8);
  366. // x += sz.Width + margin;
  367. // sz = g.DrawStringWithOutline($"{SongBpm}", Font, x, y, Color.Black, Color.White);
  368. // x += sz.Width + margin * 2;
  369. // sz = g.DrawStringWithRoundedRect("BPM", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White,
  370. // 2.5f);
  371. // x += sz.Width + margin * 2;
  372. // sz = g.DrawStringWithOutline($"{SongNjs}", Font, x, y, Color.Black, Color.White);
  373. // x += sz.Width + margin * 2;
  374. // sz = g.DrawStringWithRoundedRect("NJS", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White,
  375. // 2.5f);
  376. // x += sz.Width + margin;
  377. // x = coverSize + margin;
  378. // y = margin;
  379. // g.DrawStringWithOutline(SongSubName, Font, x, y + fontHeight, Color.Black, Color.White);
  380. // g.DrawStringWithOutline(SongArtist, Font, x, y + fontHeight * 2, Color.Black, Color.White);
  381. // g.DrawStringWithOutline(BeatMapper, Font, x, y + fontHeight * 3, Color.Black, Color.White);
  382. }
  383. if (flags.HasFlag(UpdateFlags.Performance) || flags.HasFlag(UpdateFlags.RefreshAll))
  384. {
  385. // float x = margin;
  386. // float y = ChHeight + margin * 2;
  387. // //clear region 0,180 Wx180
  388. // g.ClearRect(Color.Transparent, 0, y, ViewSize.Width, fontHeight);
  389. // //draw text
  390. // var sz = g.DrawStringWithOutline($"{CurrentScore:N0}", Font, x, y, Color.Black, Color.White);
  391. // x += sz.Width + margin * 2;
  392. // sz = g.DrawStringWithRoundedRect("POINT", _smallFont, x, y + margin * 1.5f, Brushes.White, Brushes.Red,
  393. // 2.5f);
  394. // x += sz.Width + margin * 2;
  395. // sz = g.DrawStringWithOutline($"{CurrentCombo}", Font, x, y, Color.Black, Color.White);
  396. // x += sz.Width + margin * 2;
  397. // sz = g.DrawStringWithRoundedRect("COMBO", _smallFont, x, y + margin * 1.5f, Brushes.White, Brushes.Blue,
  398. // 2.5f);
  399. // x += sz.Width + margin * 2;
  400. // sz = g.DrawStringWithOutline($"{CurrentRank}", Font, x, y, Color.Black, Color.White);
  401. // x += sz.Width + margin * 2;
  402. // sz = g.DrawStringWithRoundedRect("PERCENT", _smallFont, x, y + margin * 1.5f, Brushes.White,
  403. // Brushes.Green, 2.5f);
  404. // x += sz.Width + margin * 2;
  405. // sz = g.DrawStringWithOutline($"{YurStatus.HeartRate:N1}", Font, x, y, Color.Black, Color.White);
  406. // x += sz.Width + margin * 2;
  407. // sz = g.DrawStringWithRoundedRect("HEART", _smallFont, x, y + margin * 1.5f, Brushes.Black,
  408. // Brushes.White, 2.5f);
  409. // x += sz.Width + margin * 2;
  410. // sz = g.DrawStringWithOutline($"{YurStatus.KcalPerMin:N1}", Font, x, y, Color.Black, Color.White);
  411. // x += sz.Width + margin * 2;
  412. // sz = g.DrawStringWithRoundedRect("KCAL/MIN", _smallFont, x, y + margin * 1.5f, Brushes.Black,
  413. // Brushes.Lime, 2.5f);
  414. }
  415. }
  416. }
  417. internal static class GdiPlusExt
  418. {
  419. public static void ClearRect(this Graphics g, Color color, float x, float y, float w, float h)
  420. {
  421. g.SetClip(new RectangleF(x, y, w, h));
  422. g.Clear(color);
  423. g.ResetClip();
  424. }
  425. public static void SetHighQuality(this Graphics g)
  426. {
  427. g.CompositingMode = CompositingMode.SourceOver;
  428. g.InterpolationMode = InterpolationMode.HighQualityBicubic;
  429. g.PixelOffsetMode = PixelOffsetMode.HighQuality;
  430. g.SmoothingMode = SmoothingMode.HighQuality;
  431. g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
  432. }
  433. public static SizeF DrawStringWithOutline(this Graphics g, string text, Font font, float x, float y, Color outlineColor, Color fillColor, float outlineWidth = 1)
  434. {
  435. // assuming g is the Graphics object on which you want to draw the text
  436. using var p = new GraphicsPath();
  437. p.AddString(
  438. text, // text to draw
  439. font.FontFamily, // or any other font family
  440. (int)FontStyle.Regular, // font style (bold, italic, etc.)
  441. g.DpiY * font.Size / 72, // em size
  442. new PointF(x, y), // location where to draw text
  443. new StringFormat()); // set options here (e.g. center alignment)
  444. using var outlinePen = new Pen(outlineColor, outlineWidth);
  445. using var fillBrush = new SolidBrush(fillColor);
  446. g.FillPath(fillBrush, p);
  447. g.DrawPath(outlinePen, p);
  448. return p.GetBounds().Size;
  449. }
  450. public static void DrawRoundedRectangle(this Graphics graphics, Pen pen, RectangleF bounds, int cornerRadius)
  451. {
  452. if (graphics == null)
  453. throw new ArgumentNullException("graphics");
  454. if (pen == null)
  455. throw new ArgumentNullException("pen");
  456. using var path = RoundedRect(bounds, cornerRadius);
  457. graphics.DrawPath(pen, path);
  458. }
  459. public static void FillRoundedRectangle(this Graphics graphics, Brush brush, RectangleF bounds, float cornerRadius)
  460. {
  461. if (graphics == null)
  462. throw new ArgumentNullException("graphics");
  463. if (brush == null)
  464. throw new ArgumentNullException("brush");
  465. using var path = RoundedRect(bounds, cornerRadius);
  466. graphics.FillPath(brush, path);
  467. }
  468. public static SizeF DrawStringWithRoundedRect(this Graphics g, string text, Font font, float x, float y, Brush textbBrushe, Brush bgBrush, float radus = 10)
  469. {
  470. var sz = g.MeasureString(text, font);
  471. g.FillRoundedRectangle(bgBrush, new RectangleF(x, y, sz.Width, sz.Height), radus);
  472. g.DrawString(text, font, textbBrushe, x, y);
  473. return sz;
  474. }
  475. private static GraphicsPath RoundedRect(RectangleF bounds, float radius)
  476. {
  477. var diameter = radius * 2;
  478. var size = new SizeF(diameter, diameter);
  479. var arc = new RectangleF(bounds.Location, size);
  480. var path = new GraphicsPath();
  481. if (radius == 0)
  482. {
  483. path.AddRectangle(bounds);
  484. return path;
  485. }
  486. // top left arc
  487. path.AddArc(arc, 180, 90);
  488. // top right arc
  489. arc.X = bounds.Right - diameter;
  490. path.AddArc(arc, 270, 90);
  491. // bottom right arc
  492. arc.Y = bounds.Bottom - diameter;
  493. path.AddArc(arc, 0, 90);
  494. // bottom left arc
  495. arc.X = bounds.Left;
  496. path.AddArc(arc, 90, 90);
  497. path.CloseFigure();
  498. return path;
  499. }
  500. }
  501. }