|
@@ -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 };
|
|
|
+
|
|
|
|
|
|
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; }
|
|
|
-
|
|
|
+ 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))
|
|
|
- {
|
|
|
-
|
|
|
- g.ClearRect(Color.Transparent, 0, 0, ViewSize.Width, margin + coverSize + margin);
|
|
|
-
|
|
|
-
|
|
|
- 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))
|
|
|
- {
|
|
|
-
|
|
|
- g.ClearRect(Color.Transparent, 0, nextY + margin, ViewSize.Width, fontHeight);
|
|
|
-
|
|
|
-
|
|
|
- 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
|
|
|
- {
|
|
|
- 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 };
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
-
|
|
|
- g.ClearRect(Color.Transparent, 0, y, ViewSize.Width, fontHeight);
|
|
|
+
|
|
|
+ g.ClearRect(Color.Transparent, 0, y, ViewSize.Width, 50);
|
|
|
|
|
|
-
|
|
|
- var sz = g.DrawStringWithOutline($"{CurrentScore:N0}", Font, x, y, Color.Black, Color.White);
|
|
|
- x += sz.Width + margin * 2;
|
|
|
+
|
|
|
+ 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))
|
|
|
+ {
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if (flags.HasFlag(UpdateFlags.BeatMap) || flags.HasFlag(UpdateFlags.RefreshAll))
|
|
|
+ {
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ if (flags.HasFlag(UpdateFlags.Performance) || flags.HasFlag(UpdateFlags.RefreshAll))
|
|
|
+ {
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
}
|