i live in spain but the S is silent
All checks were successful
Build and push image / Build (push) Successful in 49s

This commit is contained in:
Jack
2023-09-28 00:53:27 +01:00
parent 2c62cd03fa
commit 4d94fbf54f
279 changed files with 5474 additions and 6863 deletions

View File

@@ -0,0 +1,19 @@
<UserControl x:Class="ksBroadcastingTestClient.Broadcasting.BroadcastingEventView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ksBroadcastingTestClient.Broadcasting"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="70"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Evt.Type}" />
<TextBlock Grid.Column="1" Text="{Binding Evt.Msg}" />
<TextBlock Grid.Column="2" Text="{Binding Evt.CarData.RaceNumber}" />
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ksBroadcastingTestClient.Broadcasting
{
/// <summary>
/// Interaction logic for BroadcastingEventView.xaml
/// </summary>
public partial class BroadcastingEventView : UserControl
{
public BroadcastingEventView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,46 @@
using ksBroadcastingNetwork.Structs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ksBroadcastingTestClient.Broadcasting
{
public class BroadcastingEventViewModel : KSObservableObject
{
public BroadcastingEvent Evt { get; }
public KSRelayCommand PlayReplayCommand { get; }
public BroadcastingEventViewModel(BroadcastingEvent evt, Action<BroadcastingEvent, float, float> onHighlightReplayDelegate)
{
Evt = evt;
PlayReplayCommand = new KSRelayCommand((o) =>
{
float secondsBack = 5f;
float duration = 10f;
switch (Evt.Type)
{
case ksBroadcastingNetwork.BroadcastingCarEventType.GreenFlag:
secondsBack = 7f;
duration = 25f;
break;
case ksBroadcastingNetwork.BroadcastingCarEventType.PenaltyCommMsg:
secondsBack = 10f;
duration = 6f;
break;
case ksBroadcastingNetwork.BroadcastingCarEventType.Accident:
secondsBack = 10f;
duration = 10f;
break;
default:
break;
}
float requestedTime = Evt.TimeMs - (secondsBack * 1000);
onHighlightReplayDelegate(Evt, requestedTime, duration);
});
}
}
}

View File

@@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ksBroadcastingNetwork;
using ksBroadcastingNetwork.Structs;
namespace ksBroadcastingTestClient.Broadcasting
{
public class BroadcastingViewModel : KSObservableObject
{
public ObservableCollection<CarViewModel> Cars { get; } = new ObservableCollection<CarViewModel>();
public TrackViewModel TrackVM { get => Get<TrackViewModel>(); private set => Set(value); }
public KSRelayCommand RequestFocusedCarCommand { get; }
private List<BroadcastingNetworkProtocol> _clients = new List<BroadcastingNetworkProtocol>();
public BroadcastingViewModel()
{
RequestFocusedCarCommand = new KSRelayCommand(RequestFocusedCar);
}
private void RequestFocusedCar(object obj)
{
var car = obj as CarViewModel;
if (car != null)
{
foreach (var client in _clients)
{
// mssing readonly check, will skip this as the ACC client has to handle this as well
client.SetFocus(Convert.ToUInt16(car.CarIndex));
}
}
}
private void RequestHudPageChange(string requestedHudPage)
{
foreach (var client in _clients)
{
// mssing readonly check, will skip this as the ACC client has to handle this as well
client.RequestHUDPage(requestedHudPage);
}
}
private void RequestCameraChange(string camSet, string camera)
{
foreach (var client in _clients)
{
// mssing readonly check, will skip this as the ACC client has to handle this as well
client.SetCamera(camSet, camera);
}
}
internal void RegisterNewClient(ACCUdpRemoteClient newClient)
{
if (newClient.MsRealtimeUpdateInterval > 0)
{
// This client will send realtime updates, we should listen
newClient.MessageHandler.OnTrackDataUpdate += MessageHandler_OnTrackDataUpdate;
newClient.MessageHandler.OnEntrylistUpdate += MessageHandler_OnEntrylistUpdate;
newClient.MessageHandler.OnRealtimeUpdate += MessageHandler_OnRealtimeUpdate;
newClient.MessageHandler.OnRealtimeCarUpdate += MessageHandler_OnRealtimeCarUpdate;
}
_clients.Add(newClient.MessageHandler);
}
private void MessageHandler_OnTrackDataUpdate(string sender, TrackData trackUpdate)
{
if (TrackVM?.TrackId != trackUpdate.TrackId)
{
if (TrackVM != null)
{
TrackVM.OnRequestCameraChange -= RequestCameraChange;
TrackVM.OnRequestHudPageChange -= RequestHudPageChange;
}
TrackVM = new TrackViewModel(trackUpdate.TrackId, trackUpdate.TrackName, trackUpdate.TrackMeters);
TrackVM.OnRequestCameraChange += RequestCameraChange;
TrackVM.OnRequestHudPageChange += RequestHudPageChange;
}
// The track cams may update in between
TrackVM.Update(trackUpdate);
}
private void MessageHandler_OnEntrylistUpdate(string sender, CarInfo carUpdate)
{
CarViewModel vm = Cars.SingleOrDefault(x => x.CarIndex == carUpdate.CarIndex);
if (vm == null)
{
vm = new CarViewModel(carUpdate.CarIndex);
Cars.Add(vm);
}
vm.Update(carUpdate);
}
private void MessageHandler_OnRealtimeUpdate(string sender, RealtimeUpdate update)
{
if (TrackVM != null)
TrackVM.Update(update);
foreach (var carVM in Cars)
{
carVM.SetFocused(update.FocusedCarIndex);
}
try
{
if (TrackVM?.TrackMeters > 0)
{
var sortedCars = Cars.OrderBy(x => x.SplinePosition).ToArray();
for (int i = 1; i < sortedCars.Length; i++)
{
var carAhead = sortedCars[i - 1];
var carBehind = sortedCars[i];
var splineDistance = Math.Abs(carAhead.SplinePosition - carBehind.SplinePosition);
while (splineDistance > 1f)
splineDistance -= 1f;
carBehind.GapFrontMeters = splineDistance * TrackVM.TrackMeters;
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
private void MessageHandler_OnRealtimeCarUpdate(string sender, RealtimeCarUpdate carUpdate)
{
var vm = Cars.FirstOrDefault(x => x.CarIndex == carUpdate.CarIndex);
if (vm == null)
{
// Oh, we don't have this car yet. In this implementation, the Network protocol will take care of this
// so hopefully we will display this car in the next cycles
return;
}
vm.Update(carUpdate);
}
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
namespace ksBroadcastingTestClient.Broadcasting
{
public class CameraSetViewModel : KSObservableObject
{
public string CameraSetName { get; }
public ObservableCollection<CameraViewModel> Cameras { get; } = new ObservableCollection<CameraViewModel>();
public Brush CamsetForeground { get => Get<Brush>(); private set => Set(value); }
public Brush CamsetBackground { get => Get<Brush>(); private set => Set(value); }
public CameraSetViewModel(string camSetName)
{
CameraSetName = camSetName;
SetActive("nope", "nope");
}
public void SetActive(string camSet, string camera)
{
var isActive = CameraSetName == camSet;
if(isActive)
{
CamsetForeground = Brushes.Yellow;
CamsetBackground = Brushes.Black;
}
else
{
CamsetForeground = Brushes.Black;
CamsetBackground = null;
}
foreach (var item in Cameras)
item.SetActive(isActive, camera);
}
internal void Update(List<string> cameraNames)
{
foreach (var cameraUpdateName in cameraNames)
{
var camVM = Cameras.SingleOrDefault(x => x.CameraName == cameraUpdateName);
if (camVM == null)
{
camVM = new CameraViewModel(CameraSetName, cameraUpdateName);
Cameras.Add(camVM);
}
}
// Now we check if we have to remove one of our cameras
var toRemove = new List<CameraViewModel>();
foreach (var camVM in Cameras)
{
if (!cameraNames.Contains(camVM.CameraName))
toRemove.Add(camVM);
}
foreach (var item in toRemove)
Cameras.Remove(item);
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Windows.Media;
namespace ksBroadcastingTestClient.Broadcasting
{
public class CameraViewModel : KSObservableObject
{
public string CameraSetName { get; }
public string CameraName { get; }
public Brush CameraForeground { get => Get<Brush>(); private set => Set(value); }
public Brush CameraBackground { get => Get<Brush>(); private set => Set(value); }
public CameraViewModel(string cameraSetName, string cameraName)
{
CameraSetName = cameraSetName;
CameraName = cameraName;
SetActive(false, "nope");
}
internal void SetActive(bool camSetActive, string activeCamera)
{
if (camSetActive && CameraName == activeCamera)
{
CameraForeground = Brushes.Black;
CameraBackground = Brushes.Yellow;
}
else
{
CameraForeground = Brushes.Black;
CameraBackground = null;
}
}
}
}

View File

@@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
using ksBroadcastingNetwork;
using ksBroadcastingNetwork.Structs;
namespace ksBroadcastingTestClient.Broadcasting
{
public class CarViewModel : KSObservableObject
{
public int CarIndex { get; }
public int RaceNumber { get => Get<int>(); private set => Set(value); }
public int CarModelEnum { get => Get<int>(); private set => Set(value); }
public string TeamName { get => Get<string>(); private set => Set(value); }
public int CupCategoryEnum { get => Get<int>(); private set => Set(value); }
public DriverViewModel CurrentDriver { get => Get<DriverViewModel>(); private set => Set(value); }
public IEnumerable<DriverViewModel> InactiveDrivers { get { return Drivers.Where(x => x.DriverIndex != CurrentDriver?.DriverIndex); } }
public ObservableCollection<DriverViewModel> Drivers { get; } = new ObservableCollection<DriverViewModel>();
public CarLocationEnum CarLocation { get => Get<CarLocationEnum>(); private set => Set(value); }
public int Delta { get => Get<int>(); private set => Set(value); }
public string DeltaString { get => Get<string>(); private set => Set(value); }
public Brush DeltaColor { get => Get<Brush>(); private set => Set(value); }
public int Gear { get => Get<int>(); private set => Set(value); }
public int Kmh { get => Get<int>(); private set => Set(value); }
public int Position { get => Get<int>(); private set => Set(value); }
public int CupPosition { get => Get<int>(); private set => Set(value); }
public int TrackPosition { get => Get<int>(); private set => Set(value); }
public float SplinePosition { get => Get<float>(); private set => Set(value); }
public float WorldX { get => Get<float>(); private set => Set(value); }
public float WorldY { get => Get<float>(); private set => Set(value); }
public float Yaw { get => Get<float>(); private set => Set(value); }
public int Laps { get => Get<int>(); private set => Set(value); }
public LapViewModel BestLap { get => Get<LapViewModel>(); private set => Set(value); }
public LapViewModel LastLap { get => Get<LapViewModel>(); private set => Set(value); }
public LapViewModel CurrentLap { get => Get<LapViewModel>(); private set => Set(value); }
public string LocationHint { get => Get<string>(); private set => Set(value); }
public Brush RowForeground { get => Get<Brush>(); private set => Set(value); }
public Brush RowBackground { get => Get<Brush>(); private set => Set(value); }
public float GapFrontMeters { get => Get<float>(); set
{
if(Set(value))
{
NotifyUpdate(nameof(GapText));
NotifyUpdate(nameof(GapColor));
}
}
}
public string GapText {
get {
if (Kmh < 10)
return "Gap: ---";
return $"Gap: {GapFrontMeters / Kmh * 3.6:F1}s ⇅";
}
}
public Brush GapColor {
get {
if (Kmh < 10)
return Brushes.Gray;
var seconds = GapFrontMeters / Kmh * 3.6;
if (seconds < 0.5)
return Brushes.Red;
if (seconds < 2.0)
return Brushes.DarkOrange;
return Brushes.Black;
}
}
public CarViewModel(ushort carIndex)
{
CarIndex = carIndex;
}
internal void Update(CarInfo carUpdate)
{
RaceNumber = carUpdate.RaceNumber;
CarModelEnum = carUpdate.CarModelType;
TeamName = carUpdate.TeamName;
CupCategoryEnum = carUpdate.CupCategory;
if(carUpdate.Drivers.Count != Drivers.Count)
{
Drivers.Clear();
int driverIndex = 0;
foreach(DriverInfo driver in carUpdate.Drivers)
{
Drivers.Add(new DriverViewModel(driver, driverIndex++));
}
NotifyUpdate(nameof(InactiveDrivers));
}
}
internal void Update(RealtimeCarUpdate carUpdate)
{
if (carUpdate.CarIndex != CarIndex)
{
System.Diagnostics.Debug.WriteLine($"Wrong {nameof(RealtimeCarUpdate)}.CarIndex {carUpdate.CarIndex} for {nameof(CarViewModel)}.CarIndex {CarIndex}");
return;
}
if (CurrentDriver?.DriverIndex != carUpdate.DriverIndex)
{
// The driver has changed!
CurrentDriver = Drivers.SingleOrDefault(x => x.DriverIndex == carUpdate.DriverIndex);
NotifyUpdate(nameof(InactiveDrivers));
}
CarLocation = carUpdate.CarLocation;
Delta = carUpdate.Delta;
DeltaString = $"{TimeSpan.FromMilliseconds(Delta):ss\\.f}";
if (Delta < -100)
DeltaColor = Brushes.Green;
else if (Delta > 100)
DeltaColor = Brushes.Red;
else
DeltaColor = null;
Gear = carUpdate.Gear;
Kmh = carUpdate.Kmh;
Position = carUpdate.Position;
CupPosition = carUpdate.CupPosition;
TrackPosition = carUpdate.TrackPosition;
SplinePosition = carUpdate.SplinePosition;
WorldX = carUpdate.WorldPosX;
WorldY = carUpdate.WorldPosY;
Yaw = carUpdate.Yaw;
Laps = carUpdate.Laps;
if(BestLap == null && carUpdate.BestSessionLap != null)
BestLap = new LapViewModel();
if (carUpdate.BestSessionLap != null)
BestLap.Update(carUpdate.BestSessionLap);
if (LastLap == null && carUpdate.LastLap != null)
LastLap = new LapViewModel();
if (carUpdate.LastLap != null)
LastLap.Update(carUpdate.LastLap);
if (CurrentLap == null && carUpdate.CurrentLap != null)
CurrentLap = new LapViewModel();
if (carUpdate.CurrentLap != null)
CurrentLap.Update(carUpdate.CurrentLap);
// The location hint will combine stuff like pits, in/outlap
if (CarLocation == CarLocationEnum.PitEntry)
LocationHint = "IN";
else if (CarLocation == CarLocationEnum.Pitlane)
LocationHint = "PIT";
else if (CarLocation == CarLocationEnum.PitExit)
LocationHint = "OUT";
else
LocationHint = CurrentLap?.LapHint;
}
internal void SetFocused(int focusedCarIndex)
{
if (CarIndex == focusedCarIndex)
{
RowForeground = Brushes.Yellow;
RowBackground = Brushes.Black;
}
else
{
RowForeground = Brushes.Black;
RowBackground = null;
}
}
}
}

View File

@@ -0,0 +1,53 @@
<UserControl x:Class="ksBroadcastingTestClient.Broadcasting.EntryListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
xmlns:local="clr-namespace:ksBroadcastingTestClient.Broadcasting"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Name="EntryListControl">
<UserControl.Resources>
<CollectionViewSource x:Key="src" Source="{Binding Cars}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Position" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<!-- Header -->
<StackPanel Orientation="Horizontal">
<TextBlock Width="30" Text="P" Style="{StaticResource RowSizedText}" TextAlignment="Center" ToolTip="Session Position/Rank" />
<TextBlock Width="60" Text="Car" Style="{StaticResource RowSizedText}" TextAlignment="Center" ToolTip="Car number" />
<TextBlock Width="60" Text="Drv" Style="{StaticResource RowSizedText}" TextAlignment="Center" ToolTip="Driver short name" />
<TextBlock Width="150" Text="Team/Driver" Style="{StaticResource RowSizedText}" TextAlignment="Center" ToolTip="Team + Driver name" />
<TextBlock Width="50" Text="Laps" Style="{StaticResource RowSizedText}" TextAlignment="Center" ToolTip="Laps completed" />
<TextBlock Width="50" Text="Loc" Style="{StaticResource RowSizedText}" TextAlignment="Center" ToolTip="Location hints: PEn = PitEntry, PLa = PitLane, PEx = PitExit, OUT = Outlap, IN = Inlap" />
<TextBlock Width="40" Text="D" Style="{StaticResource RowSizedText}" TextAlignment="Center" ToolTip="Delta to best lap" />
<TextBlock Width="75" Text="Current" Style="{StaticResource RowSizedText}" TextAlignment="Center" ToolTip="Current laptime, splits, in/outlap hint" />
<TextBlock Width="75" Text="Last" Style="{StaticResource RowSizedText}" TextAlignment="Center" ToolTip="Driver's last laptime, splits, in/outlap hint" />
<TextBlock Width="75" Text="Best" Style="{StaticResource RowSizedText}" TextAlignment="Center" ToolTip="Car's best laptime, splits, in/outlap hint" />
</StackPanel>
<ItemsControl Name="listViewCars" ItemsSource="{Binding Source={StaticResource src}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:LiveTimingCarView Margin="0,3,0,0" >
<local:LiveTimingCarView.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding ElementName=EntryListControl, Path=DataContext.RequestFocusedCarCommand}" CommandParameter="{Binding}" />
</local:LiveTimingCarView.InputBindings>
</local:LiveTimingCarView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace ksBroadcastingTestClient.Broadcasting
{
/// <summary>
/// Interaction logic for EntryListView.xaml
/// </summary>
public partial class EntryListView : UserControl
{
public EntryListView()
{
InitializeComponent();
AutorefreshSorting();
}
/// <summary>
/// Just a lazy hack to have the EntryList always sorted by position
/// </summary>
private async void AutorefreshSorting()
{
var sorter = FindResource("src") as CollectionViewSource;
while (sorter != null)
{
sorter?.View?.Refresh();
await Task.Delay(1000);
}
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Windows.Media;
namespace ksBroadcastingTestClient.Broadcasting
{
public class HudPageViewModel : KSObservableObject
{
public string Caption { get; }
public Brush HudPageForeground { get => Get<Brush>(); private set => Set(value); }
public Brush HudPageBackground { get => Get<Brush>(); private set => Set(value); }
public HudPageViewModel(string hudPageCaption)
{
Caption = hudPageCaption;
}
public void SetActive(string activeHudPage)
{
if(Caption == activeHudPage)
{
HudPageForeground = Brushes.Black;
HudPageBackground = Brushes.Yellow;
}
else
{
HudPageForeground = Brushes.Black;
HudPageBackground = null;
}
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
using ksBroadcastingNetwork;
using ksBroadcastingNetwork.Structs;
namespace ksBroadcastingTestClient.Broadcasting
{
public class LapViewModel : KSObservableObject
{
public int? LaptimeMS { get => Get<int?>(); private set => Set(value); }
public string LaptimeString { get => Get<string>(); private set => Set(value); }
public int? Split1MS { get => Get<int?>(); private set => Set(value); }
public string Split1String { get => Get<string>(); private set => Set(value); }
public int? Split2MS { get => Get<int?>(); private set => Set(value); }
public string Split2String { get => Get<string>(); private set => Set(value); }
public int? Split3MS { get => Get<int?>(); private set => Set(value); }
public string Split3String { get => Get<string>(); private set => Set(value); }
public LapType Type { get => Get<LapType>(); private set => Set(value); }
public bool IsValid { get => Get<bool>(); private set => Set(value); }
public Brush LaptimeColor { get => Get<Brush>(); private set => Set(value); }
public string LapHint { get => Get<string>(); private set => Set(value); }
internal void Update(LapInfo lapUpdate)
{
var isChanged = LaptimeMS != lapUpdate.LaptimeMS;
if (isChanged)
{
LaptimeMS = lapUpdate.LaptimeMS;
if (LaptimeMS == null)
LaptimeString = "--";
else
LaptimeString = $"{TimeSpan.FromMilliseconds(LaptimeMS.Value):mm\\:ss\\.fff}";
Split1MS = lapUpdate.Splits.FirstOrDefault();
if (Split1MS != null)
Split1String = $"{TimeSpan.FromMilliseconds(Split1MS.Value):ss\\.f}";
else
Split1String = "";
Split2MS = lapUpdate.Splits.Skip(1).FirstOrDefault();
if (Split2MS != null)
Split2String = $"{TimeSpan.FromMilliseconds(Split2MS.Value):ss\\.f}";
else
Split2String = "";
Split3MS = lapUpdate.Splits.Skip(2).FirstOrDefault();
if (Split3MS != null)
Split3String = $"{TimeSpan.FromMilliseconds(Split3MS.Value):ss\\.f}";
else
Split3String = "";
Type = lapUpdate.Type;
IsValid = lapUpdate.IsValidForBest;
if (!IsValid)
LaptimeColor = Brushes.Red;
else
LaptimeColor = null; // will use default
if (Type == LapType.Outlap)
LapHint = "OUT";
else if (Type == LapType.Inlap)
LapHint = "IN";
else
LapHint = "";
}
}
}
}

View File

@@ -0,0 +1,69 @@
<UserControl x:Class="ksBroadcastingTestClient.Broadcasting.LiveTimingCarView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ksBroadcastingTestClient.Broadcasting"
mc:Ignorable="d"
d:DesignHeight="70" d:DesignWidth="800">
<!-- Data -->
<StackPanel>
<StackPanel Orientation="Horizontal" Background="{Binding RowBackground}">
<TextBlock Width="30" Text="{Binding Position}" Style="{StaticResource RowSizedText}" FontWeight="Bold" Foreground="{Binding RowForeground}" />
<TextBlock Width="60" Text="{Binding RaceNumber, StringFormat={}#{0}}" Style="{StaticResource RowSizedText}" TextAlignment="Center" Foreground="{Binding RowForeground}" />
<TextBlock Width="60" Text="{Binding CurrentDriver.ShortName}" Style="{StaticResource RowSizedText}" Foreground="{Binding RowForeground}" />
<StackPanel Width="150" Orientation="Vertical">
<TextBlock Text="{Binding TeamName}" Style="{StaticResource HalfSizeText}" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding CurrentDriver.DisplayName}" Style="{StaticResource HalfSizeText}" FontWeight="Bold" Foreground="{Binding RowForeground}" />
</StackPanel>
<TextBlock Width="50" Text="{Binding Laps}" Style="{StaticResource RowSizedText}" TextAlignment="Right" Foreground="{Binding RowForeground}" />
<TextBlock Width="50" Text="{Binding LocationHint}" TextAlignment="Center" Style="{StaticResource RowSizedText}" Foreground="{Binding RowForeground}" />
<TextBlock Width="40" Text="{Binding DeltaString}" Foreground="{Binding DeltaColor}" TextAlignment="Center" Style="{StaticResource RowSizedText}" />
<StackPanel Width="75" Orientation="Vertical">
<TextBlock Text="{Binding CurrentLap.LaptimeString}" Style="{StaticResource 2ThirdSizeText}" HorizontalAlignment="Right" Foreground="{Binding RowForeground}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding CurrentLap.Split1String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="0" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding CurrentLap.Split2String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="1" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding CurrentLap.Split2String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="2" Foreground="{Binding RowForeground}" />
</Grid>
</StackPanel>
<StackPanel Width="75" Orientation="Vertical">
<TextBlock Text="{Binding LastLap.LaptimeString}" Style="{StaticResource 2ThirdSizeText}" HorizontalAlignment="Right" Foreground="{Binding RowForeground}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding LastLap.Split1String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="0" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding LastLap.Split2String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="1" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding LastLap.Split2String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="2" Foreground="{Binding RowForeground}" />
</Grid>
</StackPanel>
<StackPanel Width="75" Orientation="Vertical">
<TextBlock Text="{Binding BestLap.LaptimeString}" Style="{StaticResource 2ThirdSizeText}" HorizontalAlignment="Right" Foreground="{Binding RowForeground}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding BestLap.Split1String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="0" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding BestLap.Split2String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="1" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding BestLap.Split2String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="2" Foreground="{Binding RowForeground}" />
</Grid>
</StackPanel>
</StackPanel>
<TextBlock Text="{Binding GapText, StringFormat=N0}" Foreground="{Binding GapColor}" />
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ksBroadcastingTestClient.Broadcasting
{
/// <summary>
/// Interaction logic for LiveTimingCarView.xaml
/// </summary>
public partial class LiveTimingCarView : UserControl
{
public LiveTimingCarView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,66 @@
<UserControl x:Class="ksBroadcastingTestClient.Broadcasting.LiveTimingCarViewModel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ksBroadcastingTestClient.Broadcasting"
mc:Ignorable="d"
d:DesignHeight="70" d:DesignWidth="800">
<!-- Data -->
<StackPanel Orientation="Horizontal" Background="{Binding RowBackground}">
<TextBlock Width="30" Text="{Binding Position}" Style="{StaticResource RowSizedText}" FontWeight="Bold" Foreground="{Binding RowForeground}" />
<TextBlock Width="60" Text="{Binding RaceNumber, StringFormat={}#{0}}" Style="{StaticResource RowSizedText}" TextAlignment="Center" Foreground="{Binding RowForeground}" />
<TextBlock Width="60" Text="{Binding CurrentDriver.ShortName}" Style="{StaticResource RowSizedText}" Foreground="{Binding RowForeground}" />
<StackPanel Width="150" Orientation="Vertical">
<TextBlock Text="{Binding TeamName}" Style="{StaticResource HalfSizeText}" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding CurrentDriver.DisplayName}" Style="{StaticResource HalfSizeText}" FontWeight="Bold" Foreground="{Binding RowForeground}" />
</StackPanel>
<TextBlock Width="50" Text="{Binding Laps}" Style="{StaticResource RowSizedText}" TextAlignment="Right" Foreground="{Binding RowForeground}" />
<TextBlock Width="50" Text="{Binding LocationHint}" TextAlignment="Center" Style="{StaticResource RowSizedText}" Foreground="{Binding RowForeground}" />
<TextBlock Width="40" Text="{Binding DeltaString}" Foreground="{Binding DeltaColor}" TextAlignment="Center" Style="{StaticResource RowSizedText}" />
<StackPanel Width="75" Orientation="Vertical">
<TextBlock Text="{Binding CurrentLap.LaptimeString}" Style="{StaticResource 2ThirdSizeText}" HorizontalAlignment="Right" Foreground="{Binding RowForeground}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding CurrentLap.Split1String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="0" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding CurrentLap.Split2String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="1" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding CurrentLap.Split2String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="2" Foreground="{Binding RowForeground}" />
</Grid>
</StackPanel>
<StackPanel Width="75" Orientation="Vertical">
<TextBlock Text="{Binding LastLap.LaptimeString}" Style="{StaticResource 2ThirdSizeText}" HorizontalAlignment="Right" Foreground="{Binding RowForeground}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding LastLap.Split1String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="0" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding LastLap.Split2String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="1" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding LastLap.Split2String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="2" Foreground="{Binding RowForeground}" />
</Grid>
</StackPanel>
<StackPanel Width="75" Orientation="Vertical">
<TextBlock Text="{Binding BestLap.LaptimeString}" Style="{StaticResource 2ThirdSizeText}" HorizontalAlignment="Right" Foreground="{Binding RowForeground}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding BestLap.Split1String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="0" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding BestLap.Split2String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="1" Foreground="{Binding RowForeground}" />
<TextBlock Text="{Binding BestLap.Split2String}" TextAlignment="Right" Style="{StaticResource 1ThirdSizeText}" Grid.Column="2" Foreground="{Binding RowForeground}" />
</Grid>
</StackPanel>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ksBroadcastingTestClient.Broadcasting
{
/// <summary>
/// Interaction logic for LiveTimingCarViewModel.xaml
/// </summary>
public partial class LiveTimingCarViewModel : UserControl
{
public LiveTimingCarViewModel()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,53 @@
<UserControl x:Class="ksBroadcastingTestClient.Broadcasting.ReplayControlView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ksBroadcastingTestClient.Broadcasting"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="400"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Control -->
<GroupBox Header="Instant Replay (10s each)" VerticalAlignment="Top" Grid.ColumnSpan="2">
<StackPanel Orientation="Horizontal">
<Button Content="-10s" Command="{Binding PlayLiveReplay}" CommandParameter="10" Width="100" Height="28" Margin="3"/>
<Button Content="-30s" Command="{Binding PlayLiveReplay}" CommandParameter="30" Width="100" Height="28" Margin="3"/>
<Button Content="-1m" Command="{Binding PlayLiveReplay}" CommandParameter="60" Width="100" Height="28" Margin="3"/>
<TextBlock Text="Custom:" Margin="3" Height="28" />
<TextBox Name="txtPlayBeforeSeconds" Text="120" Width="40" TextAlignment="Right" Height="22" Margin="3" />
<TextBlock Text="min, duration:" Margin="3" Height="28" />
<TextBox Text="120" Width="40" TextAlignment="Right" Height="22" Margin="3" />
<TextBlock Text="s" Margin="3" Height="28" />
<Button Content="Custom" Command="{Binding PlayLiveReplay}" CommandParameter="{Binding ElementName=txtPlayBeforeSeconds, Path=Text}" Width="100" Height="28" Margin="3"/>
</StackPanel>
</GroupBox>
<!-- Event List -->
<GroupBox Header="Highlights" Grid.Column="1" Grid.Row="1">
<ItemsControl ItemsSource="{Binding BroadcastingEvents}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:BroadcastingEventView Margin="0,10,0,0" >
<local:BroadcastingEventView.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding Path=PlayReplayCommand}" />
</local:BroadcastingEventView.InputBindings>
</local:BroadcastingEventView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ksBroadcastingTestClient.Broadcasting
{
/// <summary>
/// Interaction logic for ReplayControlView.xaml
/// </summary>
public partial class ReplayControlView : UserControl
{
public ReplayControlView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ksBroadcastingNetwork;
using ksBroadcastingNetwork.Structs;
namespace ksBroadcastingTestClient.Broadcasting
{
public class ReplayControlViewModel : KSObservableObject
{
public KSRelayCommand PlayLiveReplay { get; }
public int LiveReplaySecondsBack { get => Get<int>(); private set => Set(value); }
public int LiveReplaySecondsPlaytime { get => Get<int>(); private set => Set(value); }
public int CurrentSessionTime { get => Get<int>(); private set => Set(value); }
private List<ACCUdpRemoteClient> _clients = new List<ACCUdpRemoteClient>();
public ObservableCollection<BroadcastingEventViewModel> BroadcastingEvents { get; } = new ObservableCollection<BroadcastingEventViewModel>();
public ReplayControlViewModel()
{
PlayLiveReplay = new KSRelayCommand(OnLiveReplay);
LiveReplaySecondsBack = 120;
LiveReplaySecondsPlaytime = 10;
}
private void OnHighlightReplay(BroadcastingEvent evt, float requestedStartTime, float durationSeconds)
{
try
{
foreach (var client in _clients)
{
client.MessageHandler.RequestInstantReplay(requestedStartTime, durationSeconds * 1000.0f, evt.CarId);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
}
private void OnLiveReplay(object obj)
{
try
{
var secondsBack = Convert.ToInt32(obj);
var requestedStartTime = CurrentSessionTime - (secondsBack * 1000);
foreach (var client in _clients)
{
client.MessageHandler.RequestInstantReplay(requestedStartTime, secondsBack * 1000.0f);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
}
internal void RegisterNewClient(ACCUdpRemoteClient newClient)
{
newClient.MessageHandler.OnRealtimeUpdate += MessageHandler_OnRealtimeUpdate;
newClient.MessageHandler.OnBroadcastingEvent += MessageHandler_OnBroadcastingEvent;
_clients.Add(newClient);
}
private void MessageHandler_OnRealtimeUpdate(string sender, ksBroadcastingNetwork.Structs.RealtimeUpdate update)
{
CurrentSessionTime = Convert.ToInt32(update.SessionTime.TotalMilliseconds);
}
private void MessageHandler_OnBroadcastingEvent(string sender, BroadcastingEvent evt)
{
if (evt.Type == BroadcastingCarEventType.LapCompleted)
// this is a bit spammy
return;
BroadcastingEvents.Insert(0, new BroadcastingEventViewModel(evt, OnHighlightReplay));
while(BroadcastingEvents.Count > 30)
{
BroadcastingEvents.Remove(BroadcastingEvents.Last());
}
}
}
}

View File

@@ -0,0 +1,22 @@
<UserControl x:Class="ksBroadcastingTestClient.Broadcasting.SessionInfoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ksBroadcastingTestClient.Broadcasting"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="800">
<Grid>
<TextBlock Text="{Binding Phase}" HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/>
<TextBlock Text="{Binding SessionType}" HorizontalAlignment="Left" Margin="160,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/>
<TextBlock Text="{Binding RemainingTime}" FontWeight="Bold" HorizontalAlignment="Left" Margin="284,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/>
<TextBlock Text="SessionTime" HorizontalAlignment="Left" Margin="214,31,0,0" TextAlignment="Right" TextWrapping="Wrap" VerticalAlignment="Top"/>
<TextBlock Text="{Binding SessionTimeDisplay}" HorizontalAlignment="Left" Margin="284,31,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/>
<TextBlock Text="{Binding TimeOfDayInfo}" FontSize="17" HorizontalAlignment="Left" Margin="10,43,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/>
<TextBlock Text="{Binding TrackDisplayName}" FontSize="18" HorizontalAlignment="Left" Margin="10,61,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/>
<TextBlock Text="{Binding TempInfo}" TextAlignment="Right" HorizontalAlignment="Right" Margin="0,56,0,0" VerticalAlignment="Top"/>
<TextBlock Text="{Binding RainInfo}" TextAlignment="Right" HorizontalAlignment="Right" Margin="0,72,0,0" VerticalAlignment="Top"/>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ksBroadcastingTestClient.Broadcasting
{
/// <summary>
/// Interaction logic for SessionInfoView.xaml
/// </summary>
public partial class SessionInfoView : UserControl
{
public SessionInfoView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ksBroadcastingNetwork;
using ksBroadcastingNetwork.Structs;
namespace ksBroadcastingTestClient.Broadcasting
{
public class SessionInfoViewModel : KSObservableObject
{
public TimeSpan SessionTime { get => Get<TimeSpan>(); private set => Set(value); }
public string SessionTimeDisplay { get => Get<string>(); private set => Set(value); }
public string RemainingTime { get => Get<string>(); private set => Set(value); }
public string TrackDisplayName { get => Get<string>(); private set => Set(value); }
public SessionPhase Phase { get => Get<SessionPhase>(); private set => Set(value); }
public RaceSessionType SessionType { get => Get<RaceSessionType>(); private set => Set(value); }
public string TimeOfDayInfo { get => Get<string>(); private set => Set(value); }
public string TempInfo { get => Get<string>(); private set => Set(value); }
public string RainInfo { get => Get<string>(); private set => Set(value); }
public string CloudInfo { get => Get<string>(); private set => Set(value); }
internal void RegisterNewClient(ACCUdpRemoteClient newClient)
{
if (newClient.MsRealtimeUpdateInterval > 0)
{
// This client will send realtime updates, we should listen
newClient.MessageHandler.OnTrackDataUpdate += MessageHandler_OnTrackDataUpdate;
newClient.MessageHandler.OnRealtimeUpdate += MessageHandler_OnRealtimeUpdate;
}
}
private void MessageHandler_OnRealtimeUpdate(string sender, RealtimeUpdate update)
{
SessionTime = update.SessionTime;
SessionTimeDisplay = SessionTime.ToString("hh\\:mm\\:ss");
if(update.SessionEndTime.TotalMilliseconds < 0)
{
RemainingTime = "∞";
}
else
{
var remaining = update.SessionEndTime - update.SessionTime;
if (update.SessionTime.TotalMilliseconds < 0)
RemainingTime = "00:00";
else if (remaining.TotalHours > 2)
RemainingTime = $"{remaining.TotalHours:F0}h";
else if (remaining.TotalMinutes > 60)
RemainingTime = $"{remaining.TotalMinutes:F0}m";
else
RemainingTime = $"{remaining.TotalMinutes:2,F0}:{remaining.Seconds,2}";
}
Phase = update.Phase;
SessionType = update.SessionType;
TimeOfDayInfo = update.TimeOfDay.ToString("hh\\:mm");
if (update.Clouds < 0.2)
CloudInfo = "Sunny";
else if (update.Clouds < 0.40)
CloudInfo = "Light clouds";
else if(update.Clouds < 0.65)
CloudInfo = "Cloudy";
else
CloudInfo = "World's end";
TempInfo = $"{CloudInfo} {update.AmbientTemp} °C - Track {update.TrackTemp} °C";
if (update.RainLevel < 0.1 && update.Wetness < 0.1)
RainInfo = $"Clear";
else
RainInfo = $"{update.RainLevel:P0} rain, {update.Wetness:P0} wet";
}
private void MessageHandler_OnTrackDataUpdate(string sender, TrackData trackUpdate)
{
TrackDisplayName = trackUpdate.TrackName;
}
}
}

View File

@@ -0,0 +1,85 @@
<UserControl x:Class="ksBroadcastingTestClient.Broadcasting.TrackCameraView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ksBroadcastingTestClient.Broadcasting"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Name="TrackCamControl">
<Grid>
<ItemsControl ItemsSource="{Binding CameraSets}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Each TV set has a head (=set) followed by a list of their cameras -->
<StackPanel Orientation="Vertical" >
<!-- HEAD -->
<Border BorderThickness="1" BorderBrush="Black" Background="{Binding CamsetBackground}">
<TextBlock Text="{Binding CameraSetName}" Foreground="{Binding CamsetForeground}" Width="70" TextAlignment="Center" FontWeight="Bold" Margin="5,2,5,2" />
</Border>
<!-- ITEMS = CAMS -->
<ItemsControl ItemsSource="{Binding Cameras}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="Black" Background="{Binding CameraBackground}">
<Border.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding ElementName=TrackCamControl, Path=DataContext.RequestCameraSwitchCommand}" CommandParameter="{Binding}" />
</Border.InputBindings>
<TextBlock Text="{Binding CameraName}" Foreground="{Binding CameraForeground}" Width="90" TextAlignment="Center" Margin="5,2,5,2" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- 2nd part: HUD pages; we will put them below the drivable and pit cams, there will be always more space -->
<!-- Each TV set has a head (=set) followed by a list of their cameras -->
<StackPanel Orientation="Vertical" Width="150" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5, 182, 5, 2">
<!-- HEAD -->
<Border BorderThickness="1" BorderBrush="Black" Background="Black">
<TextBlock Text="HudPages" Foreground="Yellow" TextAlignment="Center" FontWeight="Bold" Margin="5,2,5,2" />
</Border>
<!-- ITEMS = CAMS -->
<ItemsControl ItemsSource="{Binding HudPages}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="Black" Background="{Binding HudPageBackground}">
<Border.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding ElementName=TrackCamControl, Path=DataContext.RequestHudSwitchCommand}" CommandParameter="{Binding}" />
</Border.InputBindings>
<TextBlock Text="{Binding Caption}" Foreground="{Binding HudPageForeground}" Width="90" TextAlignment="Center" Margin="5,2,5,2" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ksBroadcastingTestClient.Broadcasting
{
/// <summary>
/// Interaction logic for TrackCameraView.xaml
/// </summary>
public partial class TrackCameraView : UserControl
{
public TrackCameraView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ksBroadcastingNetwork.Structs;
namespace ksBroadcastingTestClient.Broadcasting
{
public class TrackViewModel : KSObservableObject
{
public int TrackId { get; }
public float TrackMeters { get; }
public string TrackName { get; }
public ObservableCollection<CameraSetViewModel> CameraSets { get; } = new ObservableCollection<CameraSetViewModel>();
public KSRelayCommand RequestCameraSwitchCommand { get; }
public KSRelayCommand RequestHudSwitchCommand { get;}
public ObservableCollection<HudPageViewModel> HudPages { get; } = new ObservableCollection<HudPageViewModel>();
public delegate void RequestCameraChangeDelegate(string camSet, string camera);
public event RequestCameraChangeDelegate OnRequestCameraChange;
public delegate void RequestHudChangeDelegate(string requestedHudPage);
public event RequestHudChangeDelegate OnRequestHudPageChange;
public TrackViewModel(int trackId, string trackName, float trackMeters)
{
TrackId = trackId;
TrackName = trackName;
TrackMeters = trackMeters;
RequestCameraSwitchCommand = new KSRelayCommand(RequestCameraSwitch);
RequestHudSwitchCommand = new KSRelayCommand(RequestHudPageSwitch);
}
private void RequestHudPageSwitch(object obj)
{
var hudPageVM = obj as HudPageViewModel;
if (hudPageVM != null)
OnRequestHudPageChange?.Invoke(hudPageVM.Caption);
}
private void RequestCameraSwitch(object obj)
{
var cameraVM = obj as CameraViewModel;
if(cameraVM != null)
OnRequestCameraChange?.Invoke(cameraVM.CameraSetName, cameraVM.CameraName);
}
internal void Update(TrackData trackUpdate)
{
foreach (var camSetUpdate in trackUpdate.CameraSets)
{
var camSetVM = CameraSets.SingleOrDefault(x => x.CameraSetName == camSetUpdate.Key);
if(camSetVM == null)
{
camSetVM = new CameraSetViewModel(camSetUpdate.Key);
CameraSets.Add(camSetVM);
}
camSetVM.Update(camSetUpdate.Value);
}
// Now we check if we have to remove one of our cam sets
var toRemove = new List<CameraSetViewModel>();
foreach (var camSetVM in CameraSets)
{
if (!trackUpdate.CameraSets.ContainsKey(camSetVM.CameraSetName))
toRemove.Add(camSetVM);
}
foreach (var item in toRemove)
CameraSets.Remove(item);
// The same (but 1d) for the HUD pages
foreach (var hudPageCaption in trackUpdate.HUDPages)
{
var hudPageVM = HudPages.SingleOrDefault(x => x.Caption == hudPageCaption);
if(hudPageVM == null)
{
hudPageVM = new HudPageViewModel(hudPageCaption);
HudPages.Add(hudPageVM);
}
}
}
internal void Update(RealtimeUpdate update)
{
foreach (var camSet in CameraSets)
camSet.SetActive(update.ActiveCameraSet, update.ActiveCamera);
foreach (var hudPage in HudPages)
{
hudPage.SetActive(update.CurrentHudPage);
}
}
}
}