Browse Source

commit: remove compact mode, change layout, add YUR support

HOME 3 years ago
parent
commit
20dbd1857c

+ 13 - 0
BeatWidget.sln

@@ -5,7 +5,16 @@ VisualStudioVersion = 16.0.30309.148
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BsWidget", "BsWidget\BsWidget.csproj", "{2D981A37-94D5-442D-A945-0D0F777345BC}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BsYurHttpStatus", "BsYurHttpStatus\BsYurHttpStatus.csproj", "{5D45C16B-57DE-4514-A567-8E37425E1CAE}"
+EndProject
+Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "BsWidgetShareCodes", "BsWidgetShareCodes\BsWidgetShareCodes.shproj", "{D576A7AC-F155-4438-A04C-D7F272D6A09C}"
+EndProject
 Global
+	GlobalSection(SharedMSBuildProjectFiles) = preSolution
+		BsWidgetShareCodes\BsWidgetShareCodes.projitems*{2d981a37-94d5-442d-a945-0d0f777345bc}*SharedItemsImports = 4
+		BsWidgetShareCodes\BsWidgetShareCodes.projitems*{5d45c16b-57de-4514-a567-8e37425e1cae}*SharedItemsImports = 5
+		BsWidgetShareCodes\BsWidgetShareCodes.projitems*{d576a7ac-f155-4438-a04c-d7f272d6a09c}*SharedItemsImports = 13
+	EndGlobalSection
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Release|Any CPU = Release|Any CPU
@@ -15,6 +24,10 @@ Global
 		{2D981A37-94D5-442D-A945-0D0F777345BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{2D981A37-94D5-442D-A945-0D0F777345BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{2D981A37-94D5-442D-A945-0D0F777345BC}.Release|Any CPU.Build.0 = Release|Any CPU
+		{5D45C16B-57DE-4514-A567-8E37425E1CAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5D45C16B-57DE-4514-A567-8E37425E1CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5D45C16B-57DE-4514-A567-8E37425E1CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5D45C16B-57DE-4514-A567-8E37425E1CAE}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 3 - 0
BsWidget/BsWidget.csproj

@@ -68,6 +68,7 @@
     <Compile Include="BaseForm.Designer.cs">
       <DependentUpon>BaseForm.cs</DependentUpon>
     </Compile>
+    <Compile Include="BsYurHttpStatus\BsYurHttpStatusClient.cs" />
     <Compile Include="MainForm.cs">
       <SubType>Form</SubType>
     </Compile>
@@ -90,6 +91,8 @@
   <ItemGroup>
     <None Include="Resources\sample-cover.png" />
   </ItemGroup>
+  <ItemGroup />
+  <Import Project="..\BsWidgetShareCodes\BsWidgetShareCodes.projitems" Label="Shared" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
     <PostBuildEvent>if $(ConfigurationName) == Release setlocal enabledelayedexpansion enableextensions

+ 50 - 0
BsWidget/BsYurHttpStatus/BsYurHttpStatusClient.cs

@@ -0,0 +1,50 @@
+using BsWidgetShareCodes;
+using Newtonsoft.Json;
+using System;
+using WebSocketSharp;
+
+namespace BsWidget.BsYurHttpStatus
+{
+    internal class BsYurHttpStatusClient
+    {
+        private bool _isRunning;
+        private WebSocket _webSocket;
+
+        public event EventHandler<YurStatus> Event;
+
+        public void Start()
+        {
+            _isRunning = true;
+            _webSocket = new WebSocket("ws://localhost:6558/socket");
+            _webSocket.OnMessage += WebSocket_OnMessage;
+            _webSocket.OnClose += WebSocket_OnClose;
+            _webSocket.OnError += WebSocket_OnError;
+            _webSocket.ConnectAsync();
+        }
+
+        public void Stop()
+        {
+            _isRunning = false;
+            _webSocket.Close();
+        }
+
+        private void WebSocket_OnError(object sender, ErrorEventArgs e)
+        {
+            if (_isRunning) _webSocket.Connect();
+        }
+
+        private void WebSocket_OnClose(object sender, CloseEventArgs e)
+        {
+            if (_isRunning) _webSocket.Connect();
+        }
+
+        private void WebSocket_OnMessage(object sender, MessageEventArgs e)
+        {
+            //parse json
+            var evt = JsonConvert.DeserializeObject<YurStatus>(e.Data);
+            OnEvent(evt);
+        }
+
+        protected virtual void OnEvent(YurStatus e) => Event?.Invoke(this, e);
+    }
+}

+ 251 - 114
BsWidget/MainForm.cs

@@ -1,4 +1,6 @@
 using BsWidget.BeatSaberHttpStatus;
+using BsWidget.BsYurHttpStatus;
+using BsWidgetShareCodes;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
@@ -17,7 +19,10 @@ namespace BsWidget
         private Timer _updateTimer;
         private BlockingCollection<UpdateFlags> _queue;
         private BeatSaberHttpStatusClient _client;
+        private BsYurHttpStatusClient _yurClient;
+
         private Font _smallFont = new Font("", 12, FontStyle.Regular, GraphicsUnit.Pixel);
+        private Font _mediumFont = new Font("", 20, FontStyle.Regular, GraphicsUnit.Pixel);
 
         [Flags]
         private enum UpdateFlags
@@ -43,6 +48,8 @@ namespace BsWidget
         public string CurrentRank { get; set; } = "XX";
         public int CurrentMaxScore { get; set; }
 
+        public YurStatus YurStatus { get; set; } = new YurStatus { HeartRate = 0, KcalPerMin = 0 };
+
         /// <summary> FinalScore,CurrentCombo </summary>
         public List<HistoryModel> CutHistory { get; set; }
 
@@ -51,16 +58,21 @@ namespace BsWidget
             public int CutScore { get; set; }
             public int CurrentCombo { get; set; }
             public float Percent { get; set; }
-            //TODO: Heart rate
+            public float HeartRate { get; set; }
+            public float KcalPerMin { get; set; }
         }
 
         public MainForm()
         {
             KeyPreview = true;
+            FormBorderStyle = FormBorderStyle.None;
 
             _client = new BeatSaberHttpStatusClient();
             _client.Event += BeatSaber_Event;
 
+            _yurClient = new BsYurHttpStatusClient();
+            _yurClient.Event += Yur_Event;
+
             Text = "Beat Saber Status Widget";
             _queue = new BlockingCollection<UpdateFlags>();
             CutHistory = new List<HistoryModel>();
@@ -77,6 +89,7 @@ namespace BsWidget
             WindowState = FormWindowState.Normal;
             _updateTimer.Start();
             _client.Start();
+            _yurClient.Start();
         }
 
         protected override void OnShown(EventArgs e)
@@ -163,7 +176,9 @@ namespace BsWidget
                     {
                         CutScore = e.NoteCut.FinalScore.Value,
                         CurrentCombo = CurrentCombo,
-                        Percent = (float)CurrentScore / CurrentMaxScore
+                        Percent = (float)CurrentScore / CurrentMaxScore,
+                        HeartRate = YurStatus.HeartRate,
+                        KcalPerMin = YurStatus.KcalPerMin,
                     });
                 }
                 flags |= UpdateFlags.NoteFullyCut;
@@ -179,6 +194,8 @@ namespace BsWidget
             }
         }
 
+        private void Yur_Event(object sender, YurStatus e) => YurStatus = e;
+
         private void UpdateTimer_Tick(object sender, EventArgs e)
         {
             if (_queue.Count == 0) return;
@@ -188,9 +205,7 @@ namespace BsWidget
         protected override void RenderGraphic(Graphics g)
         {
             const int margin = 10;
-            const int fontHeight = 50;
-            const int coverSize = 200;
-            var ChWidth = 384;
+            var ChWidth = ViewSize.Width - margin * 2;
             var ChHeight = 64;
 
             if (_queue.Count == 0) return;
@@ -201,90 +216,12 @@ namespace BsWidget
 
             g.SetHighQuality();
 
-            var nextY = 0;
-
-            if (!Program.CompactMode)
-            {
-                if (flags.HasFlag(UpdateFlags.BeatMap) || flags.HasFlag(UpdateFlags.RefreshAll))
-                {
-                    //clear region
-                    g.ClearRect(Color.Transparent, 0, 0, ViewSize.Width, margin + coverSize + margin);
-
-                    //draw cover and text
-                    g.DrawImage(SongIcon, margin, margin, coverSize, coverSize);
-                    g.DrawRectangle(Pens.White, 10, 10, coverSize, coverSize);
-
-                    float x = coverSize + margin;
-                    var y = margin;
-
-                    var sz = g.DrawStringWithOutline(SongName, Font, x, y, Color.Black, Color.White);
-                    x += sz.Width + margin * 2;
-
-                    sz = g.DrawStringWithRoundedRect(Difficulty, Font, x, y + 5, Brushes.Black, Brushes.White);
-                    g.DrawRoundedRectangle(Pens.Black, new RectangleF(x + 2, y + 7, sz.Width - 4, sz.Height - 4), 8);
-                    x += sz.Width + margin;
-
-                    sz = g.DrawStringWithOutline($"{SongBpm}", Font, x, y, Color.Black, Color.White);
-                    x += sz.Width + margin * 2;
-
-                    sz = g.DrawStringWithRoundedRect("BPM", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White, 2.5f);
-                    x += sz.Width + margin * 2;
-
-                    sz = g.DrawStringWithOutline($"{SongNjs}", Font, x, y, Color.Black, Color.White);
-                    x += sz.Width + margin * 2;
-
-                    sz = g.DrawStringWithRoundedRect("NJS", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White, 2.5f);
-                    x += sz.Width + margin;
-
-                    x = coverSize + margin;
-                    y = margin;
-
-                    g.DrawStringWithOutline(SongSubName, Font, x, y + fontHeight, Color.Black, Color.White);
-                    g.DrawStringWithOutline(SongArtist, Font, x, y + fontHeight * 2, Color.Black, Color.White);
-                    g.DrawStringWithOutline(BeatMapper, Font, x, y + fontHeight * 3, Color.Black, Color.White);
-                }
-
-                nextY = margin + coverSize + margin;
-
-                if (flags.HasFlag(UpdateFlags.Performance) || flags.HasFlag(UpdateFlags.RefreshAll))
-                {
-                    //clear region 0,180 Wx180
-                    g.ClearRect(Color.Transparent, 0, nextY + margin, ViewSize.Width, fontHeight);
-
-                    //draw text
-                    float x = margin;
-                    float y = nextY + margin;
-
-                    var sz = g.DrawStringWithOutline($"{CurrentScore:N0}", Font, x, y, Color.Black, Color.White);
-                    x += sz.Width + margin * 2;
-
-                    sz = g.DrawStringWithRoundedRect("POINT", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White, 2.5f);
-                    x += sz.Width + margin * 2;
-
-                    sz = g.DrawStringWithOutline($"{CurrentCombo}", Font, x, y, Color.Black, Color.White);
-                    x += sz.Width + margin * 2;
-
-                    sz = g.DrawStringWithRoundedRect("COMBO", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White, 2.5f);
-                    x += sz.Width + margin * 2;
-
-                    sz = g.DrawStringWithOutline($"{CurrentRank}", Font, x, y, Color.Black, Color.White);
-                    x += sz.Width + margin * 2;
-
-                    sz = g.DrawStringWithRoundedRect("RANK", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White, 2.5f);
-                    x += sz.Width + margin * 2;
-                }
-
-                nextY += margin + fontHeight;
-            }
-            else // compact mode
-            {
-                ChWidth = ViewSize.Width - margin * 4;
-            }
+            float nextY = 0;
 
             if (flags.HasFlag(UpdateFlags.NoteFullyCut) || flags.HasFlag(UpdateFlags.RefreshAll))
             {
                 var ChLeft = margin;
-                var ChTop = nextY + margin;
+                var ChTop = margin;
 
                 var ChBottom = ChTop + ChHeight;
 
@@ -296,6 +233,8 @@ namespace BsWidget
                 using var scorePen = new Pen(Color.FromArgb(200, 255, 0, 0), LineWidth) { LineJoin = LineJoin.Round };
                 using var comboPen = new Pen(Color.FromArgb(200, 0, 0, 255), LineWidth) { LineJoin = LineJoin.Round };
                 using var percentPen = new Pen(Color.FromArgb(200, Color.Green), LineWidth) { LineJoin = LineJoin.Round };
+                using var heartPen = new Pen(Color.FromArgb(200, Color.White), LineWidth) { LineJoin = LineJoin.Round };
+                using var calPen = new Pen(Color.FromArgb(200, Color.Lime), LineWidth) { LineJoin = LineJoin.Round };
 
                 //clear region 0,210 Wx200
                 g.ClearRect(Color.Transparent, 0, ChTop - 3, ViewSize.Width, ChHeight + 6 + 20);
@@ -305,16 +244,6 @@ namespace BsWidget
                 g.FillRectangle(bgBrush, ChLeft, ChTop, ChWidth, ChHeight);
                 g.DrawRectangle(Pens.White, ChLeft - 1, ChTop - 1, ChWidth + 2, ChHeight + 2);
 
-                float x = margin;
-                if (!Program.CompactMode)
-                {
-                    var sz = g.DrawStringWithRoundedRect("POINT", _smallFont, x, ChBottom + 5, Brushes.White, Brushes.Red, 5);
-                    x += sz.Width + margin;
-                    sz = g.DrawStringWithRoundedRect("COMBO", _smallFont, x, ChBottom + 5, Brushes.White, Brushes.Blue, 5);
-                    x += sz.Width + margin;
-                    g.DrawStringWithRoundedRect("PERCENT", _smallFont, x, ChBottom + 5, Brushes.White, Brushes.Green, 5);
-                }
-
                 if (CutHistory.Count > 1)
                 {
                     HistoryModel[] items;
@@ -337,11 +266,23 @@ namespace BsWidget
                         var rngCombo = maxCombo - minCombo;
                         if (rngCombo < 1) rngCombo = 1;
 
-                        x = ChWidth - items.Length * RecordPixel + ChLeft + 1;
+                        var minHea = items.Select(p => p.HeartRate).Min();
+                        var maxHea = items.Select(p => p.HeartRate).Max();
+                        var rngHea = maxHea - minHea;
+                        if (rngHea < 1) rngHea = 1;
+
+                        var minCal = items.Select(p => p.KcalPerMin).Min();
+                        var maxCal = items.Select(p => p.KcalPerMin).Max();
+                        var rngCal = maxCal - minCal;
+                        if (rngCal < 1) rngCal = 1;
+
+                        var x = ChWidth - items.Length * RecordPixel + ChLeft + 1;
 
                         var scorePts = new List<PointF>();
                         var comboPts = new List<PointF>();
                         var percentPts = new List<PointF>();
+                        var heaPts = new List<PointF>();
+                        var calPts = new List<PointF>();
 
                         for (var i = 0; i < items.Length; i++)
                         {
@@ -349,46 +290,242 @@ namespace BsWidget
                             scorePts.Add(new PointF(x + RecordPixel * i, (ChBottom - 1) - (ChHeight - 2) * ((float)(item.CutScore - minScore) / rngScore)));
                             comboPts.Add(new PointF(x + RecordPixel * i, (ChBottom - 1) - (ChHeight - 2) * ((float)(item.CurrentCombo - minCombo) / rngCombo)));
                             percentPts.Add(new PointF(x + RecordPixel * i, (ChBottom - 1) - (ChHeight - 2) * item.Percent));
+                            heaPts.Add(new PointF(x + RecordPixel * i, (ChBottom - 1) - (ChHeight - 2) * ((item.HeartRate - minHea) / rngHea)));
+                            calPts.Add(new PointF(x + RecordPixel * i, (ChBottom - 1) - (ChHeight - 2) * ((item.KcalPerMin - minCal) / rngCal)));
                         }
 
                         g.DrawLines(scorePen, scorePts.ToArray());
                         g.DrawLines(comboPen, comboPts.ToArray());
                         g.DrawLines(percentPen, percentPts.ToArray());
+                        g.DrawLines(heartPen, heaPts.ToArray());
+                        g.DrawLines(calPen, calPts.ToArray());
                     }
                 }
 
-                nextY += ChHeight + margin;
+                nextY = ChHeight + margin * 2;
             }
 
-            if (Program.CompactMode)
+            if (flags.HasFlag(UpdateFlags.Performance) || flags.HasFlag(UpdateFlags.RefreshAll))
             {
-                if (flags.HasFlag(UpdateFlags.Performance) || flags.HasFlag(UpdateFlags.RefreshAll))
-                {
-                    float x = margin;
-                    float y = ChHeight + margin * 2;
+                float x = margin;
+                float y = ChHeight + margin * 2;
 
-                    //clear region 0,180 Wx180
-                    g.ClearRect(Color.Transparent, 0, y, ViewSize.Width, fontHeight);
+                //clear region 0,180 Wx180
+                g.ClearRect(Color.Transparent, 0, y, ViewSize.Width, 50);
 
-                    //draw text
-                    var sz = g.DrawStringWithOutline($"{CurrentScore:N0}", Font, x, y, Color.Black, Color.White);
-                    x += sz.Width + margin * 2;
+                //draw text
+                var sz = g.DrawStringWithOutline($"{CurrentScore:N0}", Font, x, y, Color.Black, Color.White);
+                x += sz.Width + margin * 2;
 
-                    sz = g.DrawStringWithRoundedRect("POINT", _smallFont, x, y + margin * 1.5f, Brushes.White, Brushes.Red, 2.5f);
-                    x += sz.Width + margin * 2;
+                sz = g.DrawStringWithRoundedRect("POINT", _smallFont, x, y + margin * 1.5f, Brushes.White, Brushes.Red,
+                    2.5f);
+                x += sz.Width + margin * 2;
 
-                    sz = g.DrawStringWithOutline($"{CurrentCombo}", Font, x, y, Color.Black, Color.White);
-                    x += sz.Width + margin * 2;
+                sz = g.DrawStringWithOutline($"{CurrentCombo}", Font, x, y, Color.Black, Color.White);
+                x += sz.Width + margin * 2;
+
+                sz = g.DrawStringWithRoundedRect("COMBO", _smallFont, x, y + margin * 1.5f, Brushes.White, Brushes.Blue,
+                    2.5f);
+                x += sz.Width + margin * 2;
+
+                sz = g.DrawStringWithOutline($"{CurrentRank}", Font, x, y, Color.Black, Color.White);
+                x += sz.Width + margin * 2;
+
+                sz = g.DrawStringWithRoundedRect("RANK", _smallFont, x, y + margin * 1.5f, Brushes.White,
+                    Brushes.Green, 2.5f);
+                x += sz.Width + margin * 2;
+
+                sz = g.DrawStringWithOutline($"{YurStatus.HeartRate:N1}", Font, x, y, Color.Black, Color.White);
+                x += sz.Width + margin * 2;
+
+                sz = g.DrawStringWithRoundedRect("HEART", _smallFont, x, y + margin * 1.5f, Brushes.Black,
+                    Brushes.White, 2.5f);
+                x += sz.Width + margin * 2;
+
+                sz = g.DrawStringWithOutline($"{YurStatus.KcalPerMin:N1}", Font, x, y, Color.Black, Color.White);
+                x += sz.Width + margin * 2;
 
-                    sz = g.DrawStringWithRoundedRect("COMBO", _smallFont, x, y + margin * 1.5f, Brushes.White, Brushes.Blue, 2.5f);
+                sz = g.DrawStringWithRoundedRect("KCAL / MIN", _smallFont, x, y + margin * 1.5f, Brushes.Black,
+                    Brushes.Lime, 2.5f);
+            }
+
+            if (flags.HasFlag(UpdateFlags.BeatMap) || flags.HasFlag(UpdateFlags.RefreshAll))
+            {
+                const int coverSize = 130;
+
+                g.ClearRect(Color.Transparent, 0, ViewSize.Height - (coverSize + 100), ViewSize.Width, coverSize + 100);
+
+                var coverLeft = ViewSize.Width - coverSize - margin * 2;
+                var coverTop = ViewSize.Height - coverSize - margin * 2;
+
+                g.DrawImage(SongIcon, coverLeft, coverTop, coverSize, coverSize);
+                g.DrawRectangle(Pens.White, coverLeft, coverTop, coverSize, coverSize);
+
+                g.DrawStringWithRoundedRect(Difficulty, Font, coverLeft - 20, coverTop - 100, Brushes.Black, Brushes.White);
+
+                {
+                    float x = coverLeft - 80;
+                    var y = coverTop - 50;
+                    var sz = g.DrawStringWithOutline($"{SongBpm}", Font, x, y, Color.Black, Color.White);
                     x += sz.Width + margin * 2;
 
-                    sz = g.DrawStringWithOutline($"{CurrentRank}", Font, x, y, Color.Black, Color.White);
+                    sz = g.DrawStringWithRoundedRect("BPM", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White, 2.5f);
                     x += sz.Width + margin * 2;
 
-                    sz = g.DrawStringWithRoundedRect("PERCENT", _smallFont, x, y + margin * 1.5f, Brushes.White, Brushes.Green, 2.5f);
+                    sz = g.DrawStringWithOutline($"{SongNjs}", Font, x, y, Color.Black, Color.White);
                     x += sz.Width + margin * 2;
+
+                    sz = g.DrawStringWithRoundedRect("NJS", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White, 2.5f);
                 }
+
+                {
+                    var bootom = ViewSize.Height - margin * 1.5f;
+                    var right = coverLeft - margin;
+
+                    var fontDeltaW = 1.3f;
+                    var fontDeltaH = 1.4f;
+
+                    var sz = g.MeasureString(SongName, _mediumFont);
+                    g.DrawStringWithOutline(SongName, _mediumFont, right - sz.Width * fontDeltaW, bootom - sz.Height * fontDeltaH, Color.Black, Color.White);
+
+                    sz = g.MeasureString(SongSubName, _mediumFont);
+                    g.DrawStringWithOutline(SongSubName, _mediumFont, right - sz.Width * fontDeltaW, bootom - (sz.Height * fontDeltaH) * 2, Color.Black, Color.White);
+
+                    sz = g.MeasureString(SongArtist, _mediumFont);
+                    g.DrawStringWithOutline(SongArtist, _mediumFont, right - sz.Width * fontDeltaW, bootom - (sz.Height * fontDeltaH) * 3, Color.Black, Color.White);
+
+                    sz = g.MeasureString(BeatMapper, _mediumFont);
+                    g.DrawStringWithOutline(BeatMapper, _mediumFont, right - sz.Width * fontDeltaW, bootom - (sz.Height * fontDeltaH) * 4, Color.Black, Color.White);
+                }
+            }
+
+            if (flags.HasFlag(UpdateFlags.Performance) || flags.HasFlag(UpdateFlags.RefreshAll))
+            {
+                //    //clear region 0,180 Wx180
+                //    g.ClearRect(Color.Transparent, 0, nextY + margin, ViewSize.Width, fontHeight);
+
+                //    //draw text
+                //    float x = margin;
+                //    float y = nextY + margin;
+
+                //    var sz = g.DrawStringWithOutline($"{CurrentScore:N0}", Font, x, y, Color.Black, Color.White);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithRoundedRect("POINT", _smallFont, x, y + margin * 1.5f, Brushes.Black,
+                //        Brushes.White, 2.5f);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithOutline($"{CurrentCombo}", Font, x, y, Color.Black, Color.White);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithRoundedRect("COMBO", _smallFont, x, y + margin * 1.5f, Brushes.Black,
+                //        Brushes.White, 2.5f);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithOutline($"{CurrentRank}", Font, x, y, Color.Black, Color.White);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithRoundedRect("RANK", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White,
+                //        2.5f);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithOutline($"{YurStatus.HeartRate:N1}", Font, x, y, Color.Black, Color.White);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithRoundedRect("HEART", _smallFont, x, y + margin * 1.5f, Brushes.Black,
+                //        Brushes.White, 2.5f);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithOutline($"{YurStatus.KcalPerMin:N1}", Font, x, y, Color.Black, Color.White);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithRoundedRect("KCAL/MIN", _smallFont, x, y + margin * 1.5f, Brushes.Black,
+                //        Brushes.White, 2.5f);
+            }
+
+            if (flags.HasFlag(UpdateFlags.BeatMap) || flags.HasFlag(UpdateFlags.RefreshAll))
+            {
+                //    //clear region
+                //    g.ClearRect(Color.Transparent, 0, 0, ViewSize.Width, margin + coverSize + margin);
+
+                //    //draw cover and text
+                //    g.DrawImage(SongIcon, margin, margin, coverSize, coverSize);
+                //    g.DrawRectangle(Pens.White, 10, 10, coverSize, coverSize);
+
+                //    float x = coverSize + margin;
+                //    var y = margin;
+
+                //    var sz = g.DrawStringWithOutline(SongName, Font, x, y, Color.Black, Color.White);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithRoundedRect(Difficulty, Font, x, y + 5, Brushes.Black, Brushes.White);
+                //    g.DrawRoundedRectangle(Pens.Black, new RectangleF(x + 2, y + 7, sz.Width - 4, sz.Height - 4), 8);
+                //    x += sz.Width + margin;
+
+                //    sz = g.DrawStringWithOutline($"{SongBpm}", Font, x, y, Color.Black, Color.White);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithRoundedRect("BPM", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White,
+                //        2.5f);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithOutline($"{SongNjs}", Font, x, y, Color.Black, Color.White);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithRoundedRect("NJS", _smallFont, x, y + margin * 1.5f, Brushes.Black, Brushes.White,
+                //        2.5f);
+                //    x += sz.Width + margin;
+
+                //    x = coverSize + margin;
+                //    y = margin;
+
+                //    g.DrawStringWithOutline(SongSubName, Font, x, y + fontHeight, Color.Black, Color.White);
+                //    g.DrawStringWithOutline(SongArtist, Font, x, y + fontHeight * 2, Color.Black, Color.White);
+                //    g.DrawStringWithOutline(BeatMapper, Font, x, y + fontHeight * 3, Color.Black, Color.White);
+            }
+
+            if (flags.HasFlag(UpdateFlags.Performance) || flags.HasFlag(UpdateFlags.RefreshAll))
+            {
+                //    float x = margin;
+                //    float y = ChHeight + margin * 2;
+
+                //    //clear region 0,180 Wx180
+                //    g.ClearRect(Color.Transparent, 0, y, ViewSize.Width, fontHeight);
+
+                //    //draw text
+                //    var sz = g.DrawStringWithOutline($"{CurrentScore:N0}", Font, x, y, Color.Black, Color.White);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithRoundedRect("POINT", _smallFont, x, y + margin * 1.5f, Brushes.White, Brushes.Red,
+                //        2.5f);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithOutline($"{CurrentCombo}", Font, x, y, Color.Black, Color.White);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithRoundedRect("COMBO", _smallFont, x, y + margin * 1.5f, Brushes.White, Brushes.Blue,
+                //        2.5f);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithOutline($"{CurrentRank}", Font, x, y, Color.Black, Color.White);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithRoundedRect("PERCENT", _smallFont, x, y + margin * 1.5f, Brushes.White,
+                //        Brushes.Green, 2.5f);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithOutline($"{YurStatus.HeartRate:N1}", Font, x, y, Color.Black, Color.White);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithRoundedRect("HEART", _smallFont, x, y + margin * 1.5f, Brushes.Black,
+                //        Brushes.White, 2.5f);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithOutline($"{YurStatus.KcalPerMin:N1}", Font, x, y, Color.Black, Color.White);
+                //    x += sz.Width + margin * 2;
+
+                //    sz = g.DrawStringWithRoundedRect("KCAL/MIN", _smallFont, x, y + margin * 1.5f, Brushes.Black,
+                //        Brushes.Lime, 2.5f);
             }
         }
     }

+ 2 - 9
BsWidget/Program.cs

@@ -1,23 +1,16 @@
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
 using System.Windows.Forms;
 
 namespace BsWidget
 {
-    static class Program
+    internal static class Program
     {
-        internal static bool CompactMode { get; private set; }
-
         /// <summary>
         /// 应用程序的主入口点。
         /// </summary>
         [STAThread]
-        static void Main(string[] args)
+        private static void Main()
         {
-            CompactMode = args.Length != 0;
-
             Application.EnableVisualStyles();
             Application.SetCompatibleTextRenderingDefault(false);
             Application.Run(new MainForm());

+ 14 - 0
BsWidgetShareCodes/BsWidgetShareCodes.projitems

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
+    <HasSharedItems>true</HasSharedItems>
+    <SharedGUID>d576a7ac-f155-4438-a04c-d7f272d6a09c</SharedGUID>
+  </PropertyGroup>
+  <PropertyGroup Label="Configuration">
+    <Import_RootNamespace>BsWidgetShareCodes</Import_RootNamespace>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildThisFileDirectory)YurStatus.cs" />
+  </ItemGroup>
+</Project>

+ 13 - 0
BsWidgetShareCodes/BsWidgetShareCodes.shproj

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>d576a7ac-f155-4438-a04c-d7f272d6a09c</ProjectGuid>
+    <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
+  </PropertyGroup>
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
+  <PropertyGroup />
+  <Import Project="BsWidgetShareCodes.projitems" Label="Shared" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
+</Project>

+ 8 - 0
BsWidgetShareCodes/YurStatus.cs

@@ -0,0 +1,8 @@
+namespace BsWidgetShareCodes
+{
+    internal class YurStatus
+    {
+        public float HeartRate { get; set; }
+        public float KcalPerMin { get; set; }
+    }
+}

+ 31 - 0
BsYurHttpStatus/BsYurHttpStatus.csproj

@@ -0,0 +1,31 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net472</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="IllusionPlugin">
+      <HintPath>$(BeatSaberDir)\Beat Saber_Data\Managed\IllusionPlugin.dll</HintPath>
+    </Reference>
+    <Reference Include="websocket-sharp">
+      <HintPath>$(BeatSaberDir)\Beat Saber_Data\Managed\websocket-sharp.dll</HintPath>
+    </Reference>
+    <Reference Include="0Harmony">
+      <HintPath>$(BeatSaberDir)\Libs\0Harmony.dll</HintPath>
+    </Reference>
+    <Reference Include="Newtonsoft.Json">
+      <HintPath>$(BeatSaberDir)\Libs\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="YUR.fit-BeatSaber-Mod">
+      <HintPath>$(BeatSaberDir)\Plugins\YUR.fit-BeatSaber-Mod.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+
+  <Target Name="PostBuild" AfterTargets="PostBuildEvent">
+    <Copy SourceFiles="$(TargetDir)$(TargetName).dll" DestinationFolder="$(BeatSaberDir)\Plugins" />
+  </Target>
+
+  <Import Project="..\BsWidgetShareCodes\BsWidgetShareCodes.projitems" Label="Shared" />
+
+</Project>

+ 87 - 0
BsYurHttpStatus/HttpStatusServer.cs

@@ -0,0 +1,87 @@
+using System;
+using System.Text;
+using BsWidgetShareCodes;
+using Newtonsoft.Json;
+using WebSocketSharp;
+using WebSocketSharp.Server;
+using YUR.Fit.Core.Models;
+
+namespace BsYurHttpStatus
+{
+    internal class HttpStatusServer
+    {
+        private int ServerPort = 6558;
+
+        internal YurStatus Status { get; } = new YurStatus();
+
+        internal event EventHandler StatusChanged;
+
+        private HttpServer server;
+
+        public void Start()
+        {
+            server = new HttpServer(ServerPort);
+
+            server.OnGet += (sender, e) =>
+            {
+                OnHTTPGet(e);
+            };
+
+            server.AddWebSocketService<StatusBroadcastBehavior>("/socket", behavior => behavior.SetParent(this));
+
+            Plugin.Log("Starting HTTP server on port " + ServerPort);
+            server.Start();
+        }
+
+        public void Stop() => server.Stop();
+
+        public void Update(OverlayStatusUpdate input)
+        {
+            Status.HeartRate = input.HeartRate ?? input.CalculationMetrics.EstHeartRate;
+            Status.KcalPerMin = input.CalculationMetrics.BurnRate;
+
+            StatusChanged?.Invoke(this, EventArgs.Empty);
+        }
+
+        private void OnHTTPGet(HttpRequestEventArgs e)
+        {
+            var res = e.Response;
+            res.StatusCode = 200;
+            res.ContentType = "application/json";
+            res.ContentEncoding = Encoding.UTF8;
+
+            var json = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(Status));
+
+            res.ContentLength64 = json.Length;
+            res.Close(json, false);
+        }
+
+        private class StatusBroadcastBehavior : WebSocketBehavior
+        {
+            private HttpStatusServer _parent;
+
+            public void SetParent(HttpStatusServer httpStatusServer)
+            {
+                _parent = httpStatusServer;
+                _parent.StatusChanged += StatusChanged;
+            }
+
+            protected override void OnOpen()
+            {
+                base.OnOpen();
+                Send(JsonConvert.SerializeObject(_parent.Status));
+            }
+
+            protected override void OnClose(CloseEventArgs e)
+            {
+                base.OnClose(e);
+                _parent.StatusChanged -= StatusChanged;
+            }
+
+            private void StatusChanged(object sender, EventArgs e)
+            {
+                Send(JsonConvert.SerializeObject(_parent.Status));
+            }
+        }
+    }
+}

+ 119 - 0
BsYurHttpStatus/Plugin.cs

@@ -0,0 +1,119 @@
+using HarmonyLib;
+using IllusionPlugin;
+using System;
+using System.Diagnostics;
+using System.Reflection;
+using YUR.Fit.Core.Models;
+using YUR.ViewControllers;
+
+namespace BsYurHttpStatus
+{
+    public sealed class Plugin : IPlugin
+    {
+        [Conditional("DEBUG")]
+        internal static void Log(string text) => Debug.Print($"BsYurHttpStatus {text}");
+
+        private Harmony _harmony;
+        private bool _isPatched;
+
+        private static HttpStatusServer _statusServer;
+        private bool _isServerStarted;
+
+        public string Name => "YUR Http Status";
+        public string Version => "0.0.0.1";
+
+        public void OnApplicationStart()
+        {
+            Log("OnApplicationStart");
+
+            Log("Patching");
+            try
+            {
+                var original = AccessTools.Method(typeof(ActivityViewController), "OverlayUpdateAction");
+                var method = GetType().GetMethod(nameof(PatchMethod), BindingFlags.Static | BindingFlags.NonPublic);
+                _harmony = new Harmony(Name);
+                _harmony.Patch(original, postfix:new HarmonyMethod(method));
+                _isPatched = true;
+                Log("Patch success");
+            }
+            catch (Exception e)
+            {
+                Log("ERROR " + e);
+            }
+
+            if (_isPatched)
+            {
+                Log("Starting HTTP Server");
+                try
+                {
+                    _statusServer = new HttpStatusServer();
+                    _statusServer.Start();
+                    _isServerStarted = true;
+                    Log("HTTP Server started");
+                }
+                catch (Exception e)
+                {
+                    Log("ERROR " + e);
+                }
+            }
+        }
+
+        public void OnApplicationQuit()
+        {
+            Log("OnApplicationQuit");
+
+            if (_isPatched)
+            {
+                try
+                {
+                    Log("Unpatching");
+                    _harmony?.UnpatchAll(Name);
+                    _isPatched = false;
+                    Log("Unpatch success");
+                }
+                catch (Exception e)
+                {
+                    Log("ERROR " + e);
+                }
+            }
+
+            if (_isServerStarted)
+            {
+                Log("Stopping HTTP Server");
+                try
+                {
+                    _statusServer.Stop();
+                    _isServerStarted = false;
+                    Log("HTTP Server Stopped");
+                }
+                catch (Exception e)
+                {
+                    Log("ERROR " + e);
+                }
+            }
+        }
+
+        private static void PatchMethod(ref OverlayStatusUpdate status)
+        {
+            _statusServer?.Update(status);
+
+            Log("YUR.(Est)HeartRate:" + (status.HeartRate ?? status.CalculationMetrics.EstHeartRate));
+        }
+
+        public void OnLevelWasLoaded(int level)
+        {
+        }
+
+        public void OnLevelWasInitialized(int level)
+        {
+        }
+
+        public void OnUpdate()
+        {
+        }
+
+        public void OnFixedUpdate()
+        {
+        }
+    }
+}