mirror of
https://github.com/space-wizards/space-station-14.git
synced 2026-02-14 19:29:53 +01:00
Camera map (#39684)
* Camera map * I hope this helps * Review 1 * Review 2 * Review 3 * Review 4 * Review 5 * Colorblind mode support * Review 6 * Change design * Map wire * Logic fix * Fix a terrible mistake * Fix * Fix 2 * Small rename * More fix * Better removal * And another fix * Will it work? * It is literally pointless * some small things
This commit is contained in:
@@ -34,11 +34,17 @@ public sealed class SurveillanceCameraMonitorBoundUserInterface : BoundUserInter
|
||||
_window.SubnetRefresh += OnSubnetRefresh;
|
||||
_window.CameraSwitchTimer += OnCameraSwitchTimer;
|
||||
_window.CameraDisconnect += OnCameraDisconnect;
|
||||
|
||||
var xform = EntMan.GetComponent<TransformComponent>(Owner);
|
||||
var gridUid = xform.GridUid ?? xform.MapUid;
|
||||
|
||||
if (gridUid is not null)
|
||||
_window?.SetMap(gridUid.Value);
|
||||
}
|
||||
|
||||
private void OnCameraSelected(string address)
|
||||
private void OnCameraSelected(string address, string? subnet)
|
||||
{
|
||||
SendMessage(new SurveillanceCameraMonitorSwitchMessage(address));
|
||||
SendMessage(new SurveillanceCameraMonitorSwitchMessage(address, subnet));
|
||||
}
|
||||
|
||||
private void OnSubnetRequest(string subnet)
|
||||
|
||||
@@ -1,25 +1,71 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:viewport="clr-namespace:Content.Client.Viewport"
|
||||
xmlns:local="clr-namespace:Content.Client.SurveillanceCamera.UI"
|
||||
Title="{Loc 'surveillance-camera-monitor-ui-window'}">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<BoxContainer Orientation="Vertical" MinWidth="350" VerticalExpand="True">
|
||||
<!-- lazy -->
|
||||
<OptionButton Name="SubnetSelector" />
|
||||
<Button Name="SubnetRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-subnets'}" />
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<ItemList Name="SubnetList" />
|
||||
</ScrollContainer>
|
||||
<Button Name="CameraRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-cameras'}" />
|
||||
<Button Name="CameraDisconnectButton" Text="{Loc 'surveillance-camera-monitor-ui-disconnect'}" />
|
||||
<Label Name="CameraStatus" />
|
||||
<BoxContainer>
|
||||
<!-- Panel with tabs -->
|
||||
<BoxContainer Orientation="Vertical" MinWidth="350">
|
||||
<TabContainer Name="ViewModeTabs" VerticalExpand="True">
|
||||
<!-- Camera list tab -->
|
||||
<BoxContainer Name="{Loc 'surveillance-camera-monitor-ui-tab-list'}" Orientation="Vertical">
|
||||
<OptionButton Name="SubnetSelector"/>
|
||||
<Button Name="SubnetRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-subnets'}"/>
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<ItemList Name="SubnetList"/>
|
||||
</ScrollContainer>
|
||||
<Button Name="CameraRefreshButton" Text="{Loc 'surveillance-camera-monitor-ui-refresh-cameras'}"/>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Map view tab -->
|
||||
<BoxContainer Name="{Loc 'surveillance-camera-monitor-ui-tab-map'}" Orientation="Vertical" VerticalExpand="True">
|
||||
<local:SurveillanceCameraNavMapControl Name="CameraMap"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
MinSize="350 350"/>
|
||||
|
||||
<!-- Map legend -->
|
||||
<BoxContainer Name="LegendContainer" Margin="0 10">
|
||||
<TextureRect Stretch="KeepAspectCentered"
|
||||
TexturePath="/Textures/Interface/NavMap/beveled_triangle.png"
|
||||
Modulate="#FF00FF"
|
||||
SetSize="20 20"
|
||||
Margin="10 0 5 0"/>
|
||||
<Label Text="{Loc 'surveillance-camera-monitor-ui-legend-active'}"/>
|
||||
|
||||
<TextureRect Stretch="KeepAspectCentered"
|
||||
TexturePath="/Textures/Interface/NavMap/beveled_triangle.png"
|
||||
SetSize="20 20"
|
||||
Modulate="#fbff19ff"
|
||||
Margin="10 0 5 0"/>
|
||||
<Label Text="{Loc 'surveillance-camera-monitor-ui-legend-selected'}"/>
|
||||
|
||||
<TextureRect Stretch="KeepAspectCentered"
|
||||
TexturePath="/Textures/Interface/NavMap/beveled_circle.png"
|
||||
SetSize="20 20"
|
||||
Modulate="#a09f9fff"
|
||||
Margin="10 0 5 0"/>
|
||||
<Label Text="{Loc 'surveillance-camera-monitor-ui-legend-inactive'}"/>
|
||||
|
||||
<TextureRect Stretch="KeepAspectCentered"
|
||||
TexturePath="/Textures/Interface/NavMap/beveled_square.png"
|
||||
SetSize="20 20"
|
||||
Modulate="#fa1f1fff"
|
||||
Margin="10 0 5 0"/>
|
||||
<Label Text="{Loc 'surveillance-camera-monitor-ui-legend-invalid'}"/>
|
||||
</BoxContainer>
|
||||
<Button Name="SubnetRefreshButtonMap" Text="{Loc 'surveillance-camera-monitor-ui-refresh-subnets'}"/>
|
||||
</BoxContainer>
|
||||
</TabContainer>
|
||||
<Button Name="CameraDisconnectButton" Text="{Loc 'surveillance-camera-monitor-ui-disconnect'}"/>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Right panel with camera view -->
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Name="CameraStatus"/>
|
||||
<Control VerticalExpand="True" Margin="5" Name="CameraViewBox">
|
||||
<viewport:ScalingViewport Name="CameraView" MinSize="500 500" MouseFilter="Ignore"/>
|
||||
<TextureRect MinSize="500 500" Name="CameraViewBackground" />
|
||||
</Control>
|
||||
</BoxContainer>
|
||||
<Control VerticalExpand="True" HorizontalExpand="True" Margin="5 5 5 5" Name="CameraViewBox">
|
||||
<viewport:ScalingViewport Name="CameraView"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
MinSize="500 500"
|
||||
MouseFilter="Ignore" />
|
||||
<TextureRect VerticalExpand="True" HorizontalExpand="True" MinSize="500 500" Name="CameraViewBackground" />
|
||||
</Control>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Client.Resources;
|
||||
using Content.Client.Viewport;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.SurveillanceCamera;
|
||||
using Content.Shared.SurveillanceCamera.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
@@ -21,8 +22,15 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
|
||||
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when a camera is selected.
|
||||
/// First parameter contains the camera's address.
|
||||
/// Second optional parameter contains a subnet - if possible, the monitor will switch to this subnet.
|
||||
/// </summary>
|
||||
public event Action<string, string?>? CameraSelected;
|
||||
|
||||
public event Action<string>? CameraSelected;
|
||||
public event Action<string>? SubnetOpened;
|
||||
public event Action? CameraRefresh;
|
||||
public event Action? SubnetRefresh;
|
||||
@@ -33,6 +41,7 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
|
||||
private bool _isSwitching;
|
||||
private readonly FixedEye _defaultEye = new();
|
||||
private readonly Dictionary<string, int> _subnetMap = new();
|
||||
private EntityUid? _mapUid;
|
||||
|
||||
private string? SelectedSubnet
|
||||
{
|
||||
@@ -68,11 +77,15 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
|
||||
SubnetSelector.OnItemSelected += args =>
|
||||
{
|
||||
// piss
|
||||
SubnetOpened!((string) args.Button.GetItemMetadata(args.Id)!);
|
||||
SubnetOpened?.Invoke((string) args.Button.GetItemMetadata(args.Id)!);
|
||||
};
|
||||
SubnetRefreshButton.OnPressed += _ => SubnetRefresh!();
|
||||
CameraRefreshButton.OnPressed += _ => CameraRefresh!();
|
||||
CameraDisconnectButton.OnPressed += _ => CameraDisconnect!();
|
||||
SubnetRefreshButton.OnPressed += _ => SubnetRefresh?.Invoke();
|
||||
SubnetRefreshButtonMap.OnPressed += _ => SubnetRefresh?.Invoke();
|
||||
CameraRefreshButton.OnPressed += _ => CameraRefresh?.Invoke();
|
||||
CameraDisconnectButton.OnPressed += _ => CameraDisconnect?.Invoke();
|
||||
|
||||
CameraMap.EnableCameraSelection = true;
|
||||
CameraMap.CameraSelected += OnCameraMapSelected;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +93,9 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
|
||||
// pass it here so that the UI can change its view.
|
||||
public void UpdateState(IEye? eye, HashSet<string> subnets, string activeAddress, string activeSubnet, Dictionary<string, string> cameras)
|
||||
{
|
||||
CameraMap.SetActiveCameraAddress(activeAddress);
|
||||
CameraMap.SetAvailableSubnets(subnets);
|
||||
|
||||
_currentAddress = activeAddress;
|
||||
SetCameraView(eye);
|
||||
|
||||
@@ -189,6 +205,25 @@ public sealed partial class SurveillanceCameraMonitorWindow : DefaultWindow
|
||||
|
||||
private void OnSubnetListSelect(ItemList.ItemListSelectedEventArgs args)
|
||||
{
|
||||
CameraSelected!((string) SubnetList[args.ItemIndex].Metadata!);
|
||||
CameraSelected!((string) SubnetList[args.ItemIndex].Metadata!, null);
|
||||
}
|
||||
|
||||
public void SetMap(EntityUid mapUid)
|
||||
{
|
||||
CameraMap.MapUid = _mapUid = mapUid;
|
||||
}
|
||||
|
||||
private void OnCameraMapSelected(NetEntity netEntity)
|
||||
{
|
||||
if (_mapUid is null || !_entityManager.TryGetComponent<SurveillanceCameraMapComponent>(_mapUid.Value, out var mapComp))
|
||||
return;
|
||||
|
||||
if (!mapComp.Cameras.TryGetValue(netEntity, out var marker) || !marker.Active)
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrEmpty(marker.Address))
|
||||
CameraSelected?.Invoke(marker.Address, marker.Subnet);
|
||||
else
|
||||
_entityManager.RaisePredictiveEvent(new RequestCameraMarkerUpdateMessage(netEntity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Map;
|
||||
using Content.Client.Pinpointer.UI;
|
||||
using Content.Client.Resources;
|
||||
using Content.Shared.SurveillanceCamera.Components;
|
||||
|
||||
namespace Content.Client.SurveillanceCamera.UI;
|
||||
|
||||
public sealed class SurveillanceCameraNavMapControl : NavMapControl
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
private static readonly Color CameraActiveColor = Color.FromHex("#FF00FF");
|
||||
private static readonly Color CameraInactiveColor = Color.FromHex("#a09f9fff");
|
||||
private static readonly Color CameraSelectedColor = Color.FromHex("#fbff19ff");
|
||||
private static readonly Color CameraInvalidColor = Color.FromHex("#fa1f1fff");
|
||||
|
||||
private readonly Texture _activeTexture;
|
||||
private readonly Texture _inactiveTexture;
|
||||
private readonly Texture _selectedTexture;
|
||||
private readonly Texture _invalidTexture;
|
||||
|
||||
private string _activeCameraAddress = string.Empty;
|
||||
private HashSet<string> _availableSubnets = new();
|
||||
private (Dictionary<NetEntity, CameraMarker> Cameras, string ActiveAddress, HashSet<string> AvailableSubnets) _lastState;
|
||||
|
||||
public bool EnableCameraSelection { get; set; }
|
||||
|
||||
public event Action<NetEntity>? CameraSelected;
|
||||
|
||||
|
||||
public SurveillanceCameraNavMapControl()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_activeTexture = _resourceCache.GetTexture("/Textures/Interface/NavMap/beveled_triangle.png");
|
||||
_selectedTexture = _activeTexture;
|
||||
_inactiveTexture = _resourceCache.GetTexture("/Textures/Interface/NavMap/beveled_circle.png");
|
||||
_invalidTexture = _resourceCache.GetTexture("/Textures/Interface/NavMap/beveled_square.png");
|
||||
|
||||
TrackedEntitySelectedAction += entity =>
|
||||
{
|
||||
if (entity.HasValue)
|
||||
CameraSelected?.Invoke(entity.Value);
|
||||
};
|
||||
}
|
||||
|
||||
public void SetActiveCameraAddress(string address)
|
||||
{
|
||||
if (_activeCameraAddress == address)
|
||||
return;
|
||||
|
||||
_activeCameraAddress = address;
|
||||
ForceNavMapUpdate();
|
||||
}
|
||||
|
||||
public void SetAvailableSubnets(HashSet<string> subnets)
|
||||
{
|
||||
if (_availableSubnets.SetEquals(subnets))
|
||||
return;
|
||||
|
||||
_availableSubnets = subnets;
|
||||
ForceNavMapUpdate();
|
||||
}
|
||||
|
||||
protected override void UpdateNavMap()
|
||||
{
|
||||
base.UpdateNavMap();
|
||||
|
||||
if (MapUid is null || !_entityManager.TryGetComponent<SurveillanceCameraMapComponent>(MapUid, out var mapComp))
|
||||
return;
|
||||
|
||||
var currentState = (mapComp.Cameras, _activeCameraAddress, _availableSubnets);
|
||||
if (_lastState.Equals(currentState))
|
||||
return;
|
||||
|
||||
_lastState = currentState;
|
||||
UpdateCameraMarkers(mapComp);
|
||||
}
|
||||
|
||||
private void UpdateCameraMarkers(SurveillanceCameraMapComponent mapComp)
|
||||
{
|
||||
TrackedEntities.Clear();
|
||||
|
||||
if (MapUid is null)
|
||||
return;
|
||||
|
||||
foreach (var (netEntity, marker) in mapComp.Cameras)
|
||||
{
|
||||
if (!marker.Visible || !_availableSubnets.Contains(marker.Subnet))
|
||||
continue;
|
||||
|
||||
var coords = new EntityCoordinates(MapUid.Value, marker.Position);
|
||||
|
||||
Texture texture;
|
||||
Color color;
|
||||
|
||||
if (string.IsNullOrEmpty(marker.Address))
|
||||
{
|
||||
color = CameraInvalidColor;
|
||||
texture = _invalidTexture;
|
||||
}
|
||||
else if (marker.Address == _activeCameraAddress)
|
||||
{
|
||||
color = CameraSelectedColor;
|
||||
texture = _selectedTexture;
|
||||
}
|
||||
else if (marker.Active)
|
||||
{
|
||||
color = CameraActiveColor;
|
||||
texture = _activeTexture;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = CameraInactiveColor;
|
||||
texture = _inactiveTexture;
|
||||
}
|
||||
|
||||
TrackedEntities[netEntity] = new NavMapBlip(
|
||||
coords,
|
||||
texture,
|
||||
color,
|
||||
false,
|
||||
EnableCameraSelection
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user