Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions SentryReplay/CameraLayoutPanel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using System.Windows;
using System.Windows.Controls;

namespace SentryReplay;

/// <summary>
/// Arranges the four camera hosts without ever reparenting them: a 2x2 grid, or one primary view filling
/// the top with the other three as a strip of tiles along the bottom. Switching views only re-arranges,
/// so each Flyleaf surface resizes in place (no reparent flash). Identify each child with the attached
/// <see cref="CameraProperty"/>; drive the layout with <see cref="SelectedCameraView"/>.
/// </summary>
public sealed class CameraLayoutPanel : Panel
{
private const double Gap = 2;
private const double TileStripFraction = 0.22;

private static readonly string[] CameraOrder =
[
MainWindowViewModel.FrontCameraView,
MainWindowViewModel.RearCameraView,
MainWindowViewModel.LeftCameraView,
MainWindowViewModel.RightCameraView,
];

public static readonly DependencyProperty CameraProperty = DependencyProperty.RegisterAttached(
"Camera",
typeof(string),
typeof(CameraLayoutPanel),
new PropertyMetadata(null));

public static string GetCamera(DependencyObject element) => (string)element.GetValue(CameraProperty);

public static void SetCamera(DependencyObject element, string value) => element.SetValue(CameraProperty, value);

public static readonly DependencyProperty SelectedCameraViewProperty = DependencyProperty.Register(
nameof(SelectedCameraView),
typeof(string),
typeof(CameraLayoutPanel),
new FrameworkPropertyMetadata(MainWindowViewModel.FrontCameraView, FrameworkPropertyMetadataOptions.AffectsArrange));

public string SelectedCameraView
{
get => (string)GetValue(SelectedCameraViewProperty);
set => SetValue(SelectedCameraViewProperty, value);
}

protected override Size MeasureOverride(Size availableSize)
{
foreach (UIElement child in InternalChildren)
{
child.Measure(availableSize);
}

return availableSize;
}

protected override Size ArrangeOverride(Size finalSize)
{
if (SelectedCameraView == MainWindowViewModel.GridCameraView)
{
ArrangeGrid(finalSize);
}
else
{
ArrangeSingle(finalSize);
}

return finalSize;
}

private void ArrangeGrid(Size size)
{
var cellWidth = (size.Width - Gap) / 2;
var cellHeight = (size.Height - Gap) / 2;
var right = cellWidth + Gap;
var bottom = cellHeight + Gap;

ArrangeCamera(MainWindowViewModel.FrontCameraView, new Rect(0, 0, cellWidth, cellHeight));
ArrangeCamera(MainWindowViewModel.RearCameraView, new Rect(right, 0, cellWidth, cellHeight));
ArrangeCamera(MainWindowViewModel.LeftCameraView, new Rect(0, bottom, cellWidth, cellHeight));
ArrangeCamera(MainWindowViewModel.RightCameraView, new Rect(right, bottom, cellWidth, cellHeight));
}

private void ArrangeSingle(Size size)
{
var stripHeight = size.Height * TileStripFraction;
var primaryHeight = Math.Max(0, size.Height - stripHeight - Gap);

ArrangeCamera(SelectedCameraView, new Rect(0, 0, size.Width, primaryHeight));

var tiles = CameraOrder.Where(camera => camera != SelectedCameraView).ToArray();
if (tiles.Length == 0)
{
return;
}

var tileWidth = (size.Width - (Gap * (tiles.Length - 1))) / tiles.Length;
var tileTop = primaryHeight + Gap;

for (var i = 0; i < tiles.Length; i++)
{
ArrangeCamera(tiles[i], new Rect(i * (tileWidth + Gap), tileTop, tileWidth, stripHeight));
}
}

private void ArrangeCamera(string camera, Rect rect)
{
foreach (UIElement child in InternalChildren)
{
if (GetCamera(child) == camera)
{
child.Arrange(rect);
return;
}
}
}
}
145 changes: 18 additions & 127 deletions SentryReplay/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -417,14 +417,6 @@
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<Grid x:Name="FlyleafHostPool"
Visibility="Collapsed">
<fl:FlyleafHost x:Name="FrontFlyleafHost" />
<fl:FlyleafHost x:Name="BackFlyleafHost" />
<fl:FlyleafHost x:Name="LeftFlyleafHost" />
<fl:FlyleafHost x:Name="RightFlyleafHost" />
</Grid>

<!-- Video Player -->
<Grid Grid.RowSpan="2"
Visibility="{Binding ShowVideoHosts, Converter={local:BoolToVisibilityConverter}}">
Expand All @@ -439,113 +431,16 @@
BorderThickness="1"
ClipToBounds="True"
CornerRadius="6">
<Grid>
<Grid Visibility="{Binding IsGridViewSelected, Converter={local:BoolToVisibilityConverter}}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>

<Border Grid.Row="0"
Grid.Column="0"
Margin="0,0,1,1"
Background="Black"
ClipToBounds="True">
<Grid>
<ContentControl x:Name="GridFrontHostSlot" />
<Border Margin="10"
Padding="8,3"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="#A0000000"
CornerRadius="4">
<TextBlock Text="Front"
FontSize="12"
Foreground="White" />
</Border>
</Grid>
</Border>

<Border Grid.Row="0"
Grid.Column="1"
Margin="1,0,0,1"
Background="Black"
ClipToBounds="True">
<Grid>
<ContentControl x:Name="GridRearHostSlot" />
<Border Margin="10"
Padding="8,3"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="#A0000000"
CornerRadius="4">
<TextBlock Text="Rear"
FontSize="12"
Foreground="White" />
</Border>
</Grid>
</Border>

<Border Grid.Row="1"
Grid.Column="0"
Margin="0,1,1,0"
Background="Black"
ClipToBounds="True">
<Grid>
<ContentControl x:Name="GridLeftHostSlot" />
<Border Margin="10"
Padding="8,3"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="#A0000000"
CornerRadius="4">
<TextBlock Text="Left"
FontSize="12"
Foreground="White" />
</Border>
</Grid>
</Border>

<Border Grid.Row="1"
Grid.Column="1"
Margin="1,1,0,0"
Background="Black"
ClipToBounds="True">
<Grid>
<ContentControl x:Name="GridRightHostSlot" />
<Border Margin="10"
Padding="8,3"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="#A0000000"
CornerRadius="4">
<TextBlock Text="Right"
FontSize="12"
Foreground="White" />
</Border>
</Grid>
</Border>
</Grid>

<Grid Visibility="{Binding IsSingleCameraViewSelected, Converter={local:BoolToVisibilityConverter}}">
<ContentControl x:Name="PrimaryCameraHostSlot" />
<Border Margin="14"
Padding="10,4"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="#A0000000"
CornerRadius="4">
<TextBlock Text="{Binding ActiveCameraLabel}"
FontSize="13"
FontWeight="SemiBold"
Foreground="White" />
</Border>
</Grid>
</Grid>
<local:CameraLayoutPanel SelectedCameraView="{Binding SelectedCameraView}">
<fl:FlyleafHost x:Name="FrontFlyleafHost"
local:CameraLayoutPanel.Camera="front" />
<fl:FlyleafHost x:Name="BackFlyleafHost"
local:CameraLayoutPanel.Camera="rear" />
<fl:FlyleafHost x:Name="LeftFlyleafHost"
local:CameraLayoutPanel.Camera="left" />
<fl:FlyleafHost x:Name="RightFlyleafHost"
local:CameraLayoutPanel.Camera="right" />
</local:CameraLayoutPanel>
</Border>

<Border Grid.Row="1"
Expand Down Expand Up @@ -608,11 +503,10 @@
CornerRadius="4">
<Grid>
<ContentControl x:Name="FrontTileHostSlot" />
<!-- Single-screen icon: this camera is the main view (cf. the 2x2 grid icon) -->
<!-- Single-screen switch-button icon (cf. the 2x2 grid icon) -->
<Border Margin="18"
Background="{DynamicResource ControlFillColorSecondaryBrush}"
CornerRadius="3"
Visibility="{Binding IsFrontViewSelected, Converter={local:BoolToVisibilityConverter}}" />
CornerRadius="3" />
</Grid>
</Border>
<TextBlock Grid.Row="1"
Expand Down Expand Up @@ -640,11 +534,10 @@
CornerRadius="4">
<Grid>
<ContentControl x:Name="RearTileHostSlot" />
<!-- Single-screen icon: this camera is the main view (cf. the 2x2 grid icon) -->
<!-- Single-screen switch-button icon (cf. the 2x2 grid icon) -->
<Border Margin="18"
Background="{DynamicResource ControlFillColorSecondaryBrush}"
CornerRadius="3"
Visibility="{Binding IsRearViewSelected, Converter={local:BoolToVisibilityConverter}}" />
CornerRadius="3" />
</Grid>
</Border>
<TextBlock Grid.Row="1"
Expand Down Expand Up @@ -672,11 +565,10 @@
CornerRadius="4">
<Grid>
<ContentControl x:Name="LeftTileHostSlot" />
<!-- Single-screen icon: this camera is the main view (cf. the 2x2 grid icon) -->
<!-- Single-screen switch-button icon (cf. the 2x2 grid icon) -->
<Border Margin="18"
Background="{DynamicResource ControlFillColorSecondaryBrush}"
CornerRadius="3"
Visibility="{Binding IsLeftViewSelected, Converter={local:BoolToVisibilityConverter}}" />
CornerRadius="3" />
</Grid>
</Border>
<TextBlock Grid.Row="1"
Expand Down Expand Up @@ -704,11 +596,10 @@
CornerRadius="4">
<Grid>
<ContentControl x:Name="RightTileHostSlot" />
<!-- Single-screen icon: this camera is the main view (cf. the 2x2 grid icon) -->
<!-- Single-screen switch-button icon (cf. the 2x2 grid icon) -->
<Border Margin="18"
Background="{DynamicResource ControlFillColorSecondaryBrush}"
CornerRadius="3"
Visibility="{Binding IsRightViewSelected, Converter={local:BoolToVisibilityConverter}}" />
CornerRadius="3" />
</Grid>
</Border>
<TextBlock Grid.Row="1"
Expand Down
Loading