UX improvements to Air Alarm UI (#12681)

Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
This commit is contained in:
eoineoineoin
2022-12-06 23:46:07 +00:00
committed by GitHub
parent dadd34286e
commit ff9cf108b6
16 changed files with 649 additions and 495 deletions

View File

@@ -20,7 +20,7 @@ public sealed class AirAlarmBoundUserInterface : BoundUserInterface
{
base.Open();
_window = new AirAlarmWindow();
_window = new AirAlarmWindow(Owner);
if (State != null)
{

View File

@@ -1,11 +1,17 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MinSize="500 500" Title="Air Alarm">
<ui:FancyWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2007/xaml"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
MinSize="500 500" Resizable="True" Title="Air Alarm">
<BoxContainer Orientation="Vertical" Margin="5 5 5 5">
<!-- Status (pressure, temperature, alarm state, device total, address, etc) -->
<BoxContainer Orientation="Horizontal" Margin="0 0 0 2">
<!-- Left column (pressure, temperature, alarm state) -->
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<!-- Left column (view of entity) -->
<PanelContainer Margin="2 0 6 0" StyleClasses="Inset" VerticalAlignment="Center" VerticalExpand="True">
<SpriteView Name="EntityView" OverrideDirection="South" Scale="2 2" />
</PanelContainer>
<!-- Center column (pressure, temperature, alarm state) -->
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 6 0">
<BoxContainer Orientation="Vertical" Margin="0 0 2 0" HorizontalExpand="True">
<Label Text="{Loc 'air-alarm-ui-window-pressure-label'}" />
<Label Text="{Loc 'air-alarm-ui-window-temperature-label'}" />
@@ -25,8 +31,8 @@
<Label Text="{Loc 'air-alarm-ui-window-device-count-label'}" />
</BoxContainer>
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<Label Name="CDeviceAddress" HorizontalAlignment="Right" />
<Label Name="CDeviceTotal" HorizontalAlignment="Right" />
<RichTextLabel Name="CDeviceAddress" HorizontalAlignment="Right" />
<RichTextLabel Name="CDeviceTotal" HorizontalAlignment="Right" />
</BoxContainer>
</BoxContainer>
<Button Name="CResyncButton" Text="{Loc 'air-alarm-ui-window-resync-devices-label'}" HorizontalExpand="True" />
@@ -35,17 +41,35 @@
<!-- Gas/Device Data -->
<TabContainer Name="CTabContainer" VerticalExpand="True" Margin="0 0 0 2">
<!-- Vent devices -->
<ScrollContainer VerticalExpand="True">
<BoxContainer Name="CVentContainer" Orientation="Vertical"/>
</ScrollContainer>
<PanelContainer VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<ScrollContainer>
<BoxContainer Name="CVentContainer" Orientation="Vertical"/>
</ScrollContainer>
</PanelContainer>
<!-- Scrubber devices -->
<ScrollContainer VerticalExpand="True">
<BoxContainer Name="CScrubberContainer" Orientation="Vertical"/>
</ScrollContainer>
<PanelContainer VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<ScrollContainer>
<BoxContainer Name="CScrubberContainer" Orientation="Vertical"/>
</ScrollContainer>
</PanelContainer>
<!-- Sensors -->
<ScrollContainer VerticalExpand="True">
<BoxContainer Name="CSensorContainer" Orientation="Vertical"/>
</ScrollContainer>
<PanelContainer VerticalExpand="True">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<ScrollContainer>
<BoxContainer Name="CSensorContainer" Orientation="Vertical"/>
</ScrollContainer>
</PanelContainer>
</TabContainer>
<!-- Mode buttons -->
<BoxContainer Orientation="Horizontal">
@@ -53,4 +77,4 @@
<OptionButton Name="CModeButton" HorizontalExpand="True" />
</BoxContainer>
</BoxContainer>
</DefaultWindow>
</ui:FancyWindow>

View File

@@ -1,22 +1,21 @@
using System;
using System.Collections.Generic;
using Content.Client.Atmos.Monitor.UI.Widgets;
using Content.Client.Message;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Temperature;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Localization;
namespace Content.Client.Atmos.Monitor.UI;
[GenerateTypedNameReferences]
public sealed partial class AirAlarmWindow : DefaultWindow
public sealed partial class AirAlarmWindow : FancyWindow
{
public event Action<string, IAtmosDeviceData>? AtmosDeviceDataChanged;
public event Action<string, AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? AtmosAlarmThresholdChanged;
@@ -25,8 +24,8 @@ public sealed partial class AirAlarmWindow : DefaultWindow
public event Action? ResyncAllRequested;
public event Action<AirAlarmTab>? AirAlarmTabChange;
private Label _address => CDeviceAddress;
private Label _deviceTotal => CDeviceTotal;
private RichTextLabel _address => CDeviceAddress;
private RichTextLabel _deviceTotal => CDeviceTotal;
private RichTextLabel _pressure => CPressureLabel;
private RichTextLabel _temperature => CTemperatureLabel;
private RichTextLabel _alarmState => CStatusLabel;
@@ -45,7 +44,7 @@ public sealed partial class AirAlarmWindow : DefaultWindow
private OptionButton _modes => CModeButton;
public AirAlarmWindow()
public AirAlarmWindow(ClientUserInterfaceComponent component)
{
RobustXamlLoader.Load(this);
@@ -88,15 +87,19 @@ public sealed partial class AirAlarmWindow : DefaultWindow
_sensors.Clear();
ResyncAllRequested!.Invoke();
};
EntityView.Sprite = IoCManager.Resolve<IEntityManager>().GetComponent<SpriteComponent>(component.Owner);
}
public void UpdateState(AirAlarmUIState state)
{
_address.Text = state.Address;
_deviceTotal.Text = $"{state.DeviceCount}";
_address.SetMarkup(state.Address);
_deviceTotal.SetMarkup($"{state.DeviceCount}");
_pressure.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure", ("pressure", $"{state.PressureAverage:0.##}")));
_temperature.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature", ("tempC", $"{TemperatureHelpers.KelvinToCelsius(state.TemperatureAverage):0.#}"), ("temperature", $"{state.TemperatureAverage:0.##}")));
_alarmState.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state", ("state", $"{state.AlarmType}")));
_alarmState.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state",
("color", ColorForAlarm(state.AlarmType)),
("state", $"{state.AlarmType}")));
UpdateModeSelector(state.Mode);
foreach (var (addr, dev) in state.DeviceData)
{
@@ -159,4 +162,26 @@ public sealed partial class AirAlarmWindow : DefaultWindow
break;
}
}
public static Color ColorForThreshold(float amount, AtmosAlarmThreshold threshold)
{
threshold.CheckThreshold(amount, out AtmosAlarmType curAlarm);
return ColorForAlarm(curAlarm);
}
public static Color ColorForAlarm(AtmosAlarmType curAlarm)
{
if(curAlarm == AtmosAlarmType.Danger)
{
return StyleNano.DangerousRedFore;
}
else if(curAlarm == AtmosAlarmType.Warning)
{
return StyleNano.ConcerningOrangeFore;
}
return StyleNano.GoodGreenFore;
}
}

View File

@@ -4,7 +4,7 @@
<Collapsible Orientation="Vertical">
<CollapsibleHeading Name="CAddress" />
<!-- Upper row: toggle, direction, checks -->
<CollapsibleBody>
<CollapsibleBody Margin="20 0 0 0">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" Margin ="0 0 0 2">
<CheckBox Name="CEnableDevice" Text="{Loc 'air-alarm-ui-widget-enable'}" />

View File

@@ -3,7 +3,7 @@
Orientation="Vertical" Margin="2 0 2 4">
<Collapsible Orientation="Vertical">
<CollapsibleHeading Name="CAddress" />
<CollapsibleBody>
<CollapsibleBody Margin="20 0 0 0">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" Margin="0 0 0 2">
<CheckBox Name="CEnableDevice" Text="{Loc 'air-alarm-ui-widget-enable'}" />
@@ -25,7 +25,7 @@
<!-- Lower row: every single gas -->
<Collapsible Orientation="Vertical" Margin="2 2 2 2">
<CollapsibleHeading Title="Gas filters" />
<CollapsibleBody>
<CollapsibleBody Margin="20 0 0 0">
<GridContainer HorizontalExpand="True" Name="CGasContainer" Columns="3" />
</CollapsibleBody>
</Collapsible>

View File

@@ -1,41 +1,21 @@
<BoxContainer xmlns="https://spacestation14.io" HorizontalExpand="True">
<Collapsible Orientation="Vertical">
<CollapsibleHeading Name="SensorAddress" />
<CollapsibleBody Margin="2">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<BoxContainer Orientation="Vertical" Margin="0 0 2 0" HorizontalExpand="True">
<Label Text="{Loc 'air-alarm-ui-window-pressure-label'}" />
<Label Text="{Loc 'air-alarm-ui-window-temperature-label'}" />
<Label Text="{Loc 'air-alarm-ui-window-alarm-state-label'}" />
</BoxContainer>
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<RichTextLabel Name="PressureLabel" />
<RichTextLabel Name="TemperatureLabel" />
<RichTextLabel Name="AlarmStateLabel" />
</BoxContainer>
<CollapsibleBody Margin="20 2 2 2">
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<BoxContainer Orientation="Vertical" Margin="0 0 2 0" HorizontalExpand="True">
<RichTextLabel Name="AlarmStateLabel" />
<RichTextLabel Name="PressureLabel" />
<Control Name="PressureThresholdContainer" Margin="20 0 2 0" />
<RichTextLabel Name="TemperatureLabel" />
<Control Name="TemperatureThresholdContainer" Margin="20 0 2 0" />
</BoxContainer>
<Collapsible Orientation="Vertical" Margin="2">
<CollapsibleHeading Title="{Loc 'air-alarm-ui-sensor-gases'}" />
<CollapsibleBody>
<CollapsibleBody Margin="20 0 0 0">
<BoxContainer Name="GasContainer" Orientation="Vertical" Margin="2" />
</CollapsibleBody>
</Collapsible>
<Collapsible Orientation="Vertical" Margin="2">
<CollapsibleHeading Title="{Loc 'air-alarm-ui-sensor-thresholds'}" />
<CollapsibleBody>
<BoxContainer Orientation="Vertical">
<Control Name="PressureThresholdContainer" Margin="2 0 2 0" />
<Control Name="TemperatureThresholdContainer" Margin="2 0 2 0" />
<Collapsible Orientation="Vertical" Margin="2 0 2 0">
<CollapsibleHeading Title="{Loc 'air-alarm-ui-sensor-gases'}" />
<CollapsibleBody Margin="4 2 4 2">
<BoxContainer Name="GasThresholds" Orientation="Vertical" Margin="2 0 2 0" />
</CollapsibleBody>
</Collapsible>
</BoxContainer>
</CollapsibleBody>
</Collapsible>
</BoxContainer>
</CollapsibleBody>
</Collapsible>

View File

@@ -17,7 +17,7 @@ public sealed partial class SensorInfo : BoxContainer
private ThresholdControl _pressureThreshold;
private ThresholdControl _temperatureThreshold;
private Dictionary<Gas, ThresholdControl> _gasThresholds = new();
private Dictionary<Gas, Label> _gasLabels = new();
private Dictionary<Gas, RichTextLabel> _gasLabels = new();
public SensorInfo(AtmosSensorData data, string address)
{
@@ -27,22 +27,42 @@ public sealed partial class SensorInfo : BoxContainer
SensorAddress.Title = $"{address} : {data.AlarmState}";
PressureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure", ("pressure", $"{data.Pressure:0.##}")));
TemperatureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature", ("tempC", $"{TemperatureHelpers.KelvinToCelsius(data.Temperature):0.#}"), ("temperature", $"{data.Temperature:0.##}")));
AlarmStateLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state", ("state", $"{data.AlarmState}")));
AlarmStateLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state-indicator",
("color", AirAlarmWindow.ColorForAlarm(data.AlarmState)),
("state", $"{data.AlarmState}")));
PressureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure-indicator",
("color", AirAlarmWindow.ColorForThreshold(data.Pressure, data.PressureThreshold)),
("pressure", $"{data.Pressure:0.##}")));
TemperatureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature-indicator",
("color", AirAlarmWindow.ColorForThreshold(data.Temperature, data.TemperatureThreshold)),
("tempC", $"{TemperatureHelpers.KelvinToCelsius(data.Temperature):0.#}"),
("temperature", $"{data.Temperature:0.##}")));
foreach (var (gas, amount) in data.Gases)
{
var label = new Label();
label.Text = Loc.GetString("air-alarm-ui-gases", ("gas", $"{gas}"),
var label = new RichTextLabel();
var fractionGas = amount / data.TotalMoles;
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator", ("gas", $"{gas}"),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
("amount", $"{amount:0.####}"),
("percentage", $"{(100 * amount / data.TotalMoles):0.##}"));
("percentage", $"{(100 * fractionGas):0.##}")));
GasContainer.AddChild(label);
_gasLabels.Add(gas, label);
var threshold = data.GasThresholds[gas];
var gasThresholdControl = new ThresholdControl(Loc.GetString($"air-alarm-ui-thresholds-gas-title", ("gas", $"{gas}")), threshold, AtmosMonitorThresholdType.Gas, gas, 100);
gasThresholdControl.Margin = new Thickness(20, 2, 2, 2);
gasThresholdControl.ThresholdDataChanged += (type, threshold, arg3) =>
{
OnThresholdUpdate!(_address, type, threshold, arg3);
};
_gasThresholds.Add(gas, gasThresholdControl);
GasContainer.AddChild(gasThresholdControl);
}
_pressureThreshold =
new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-pressure-title"), data.PressureThreshold, AtmosMonitorThresholdType.Pressure);
_pressureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-pressure-title"), data.PressureThreshold, AtmosMonitorThresholdType.Pressure);
PressureThresholdContainer.AddChild(_pressureThreshold);
_temperatureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-temperature-title"), data.TemperatureThreshold,
AtmosMonitorThresholdType.Temperature);
@@ -60,23 +80,24 @@ public sealed partial class SensorInfo : BoxContainer
foreach (var (gas, threshold) in data.GasThresholds)
{
var gasThresholdControl = new ThresholdControl(Loc.GetString($"air-alarm-ui-thresholds-gas-title", ("gas", $"{gas}")), threshold, AtmosMonitorThresholdType.Gas, gas, 100);
gasThresholdControl.ThresholdDataChanged += (type, threshold, arg3) =>
{
OnThresholdUpdate!(_address, type, threshold, arg3);
};
_gasThresholds.Add(gas, gasThresholdControl);
GasThresholds.AddChild(gasThresholdControl);
}
}
}
public void ChangeData(AtmosSensorData data)
{
SensorAddress.Title = $"{_address} : {data.AlarmState}";
PressureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure", ("pressure", $"{data.Pressure:0.##}")));
TemperatureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature", ("tempC", $"{TemperatureHelpers.KelvinToCelsius(data.Temperature):0.#}"), ("temperature", $"{data.Temperature:0.##}")));
AlarmStateLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state", ("state", $"{data.AlarmState}")));
AlarmStateLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state-indicator",
("color", AirAlarmWindow.ColorForAlarm(data.AlarmState)),
("state", $"{data.AlarmState}")));
PressureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure-indicator",
("color", AirAlarmWindow.ColorForThreshold(data.Pressure, data.PressureThreshold)),
("pressure", $"{data.Pressure:0.##}")));
TemperatureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature-indicator",
("color", AirAlarmWindow.ColorForThreshold(data.Temperature, data.TemperatureThreshold)),
("tempC", $"{TemperatureHelpers.KelvinToCelsius(data.Temperature):0.#}"),
("temperature", $"{data.Temperature:0.##}")));
foreach (var (gas, amount) in data.Gases)
{
@@ -85,13 +106,15 @@ public sealed partial class SensorInfo : BoxContainer
continue;
}
label.Text = Loc.GetString("air-alarm-ui-gases", ("gas", $"{gas}"),
var fractionGas = amount / data.TotalMoles;
label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator", ("gas", $"{gas}"),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
("amount", $"{amount:0.####}"),
("percentage", $"{(100 * amount / data.TotalMoles):0.##}"));
("percentage", $"{(100 * fractionGas):0.##}")));
}
_pressureThreshold.UpdateThresholdData(data.PressureThreshold);
_temperatureThreshold.UpdateThresholdData(data.TemperatureThreshold);
_pressureThreshold.UpdateThresholdData(data.PressureThreshold, data.Pressure);
_temperatureThreshold.UpdateThresholdData(data.TemperatureThreshold, data.Temperature);
foreach (var (gas, control) in _gasThresholds)
{
if (!data.GasThresholds.TryGetValue(gas, out var threshold))
@@ -99,7 +122,8 @@ public sealed partial class SensorInfo : BoxContainer
continue;
}
control.UpdateThresholdData(threshold);
control.UpdateThresholdData(threshold, data.Gases[gas] / data.TotalMoles);
}
}
}
}

View File

@@ -0,0 +1,7 @@
<BoxContainer xmlns="https://spacestation14.io"
HorizontalExpand="True" Orientation="Vertical"
Margin = "20 0 0 0" MinSize="160 0" >
<Label Name="CBoundLabel" HorizontalAlignment="Center" />
<CheckBox Name="CBoundEnabled" HorizontalAlignment="Center" Text="{Loc 'Enable'}"/>
<FloatSpinBox Name="CSpinner" />
</BoxContainer>

View File

@@ -0,0 +1,95 @@
using Content.Client.Message;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Temperature;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Atmos.Monitor.UI.Widgets;
[GenerateTypedNameReferences]
public sealed partial class ThresholdBoundControl : BoxContainer
{
// raw values to use in thresholds, prefer these
// over directly setting Modified(Value/LastValue)
// when working with the FloatSpinBox
private float _value;
// convenience thing for getting multiplied values
// and also setting value to a usable value
private float ScaledValue
{
get => _value * _uiValueScale;
set => _value = value / _uiValueScale;
}
private float _uiValueScale;
public event Action? OnValidBoundChanged;
public Action<float>? OnBoundChanged;
public Action<bool>? OnBoundEnabled;
public void SetValue(float value)
{
_value = value;
CSpinner.Value = (float) ScaledValue!;
}
public void SetEnabled(bool enabled)
{
CBoundEnabled.Pressed = enabled;
if (enabled)
{
CBoundLabel.RemoveStyleClass("Disabled");
}
else
{
CBoundLabel.SetOnlyStyleClass("Disabled");
}
}
public void SetWarningState(AtmosAlarmType alarm)
{
if(alarm == AtmosAlarmType.Normal)
{
CBoundLabel.FontColorOverride = null;
}
else
{
CBoundLabel.FontColorOverride = AirAlarmWindow.ColorForAlarm(alarm);
}
}
public ThresholdBoundControl(string controlLabel, float value, float uiValueScale = 1)
{
RobustXamlLoader.Load(this);
_uiValueScale = uiValueScale > 0 ? uiValueScale : 1;
_value = value;
CBoundLabel.Text = controlLabel;
CSpinner.Value = ScaledValue;
CBoundEnabled.Pressed = _value != null;
CSpinner.OnValueChanged += SpinnerValueChanged;
CBoundEnabled.OnToggled += CheckboxToggled;
}
private void SpinnerValueChanged(FloatSpinBox.FloatSpinBoxEventArgs args)
{
// ensure that the value in the spinbox is transformed
ScaledValue = args.Value;
// set the value in the scope above
OnBoundChanged!(_value);
OnValidBoundChanged!.Invoke();
}
private void CheckboxToggled(BaseButton.ButtonToggledEventArgs args)
{
OnBoundEnabled!(args.Pressed);
OnValidBoundChanged!.Invoke();
}
}

View File

@@ -3,15 +3,15 @@
Orientation="Vertical" Margin="0 0 0 4">
<Collapsible Orientation="Vertical">
<CollapsibleHeading Name="CName" />
<CollapsibleBody>
<CollapsibleBody Margin="20 0 0 0">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<CheckBox Name="CIgnore" Text="{Loc 'Ignore'}" />
<CheckBox Name="CEnabled" Text="{Loc 'Enabled'}" />
</BoxContainer>
<!-- Upper row: Danger bounds -->
<BoxContainer Name="CDangerBounds" Orientation="Horizontal" Margin="0 0 0 2"/>
<!-- Lower row: Warning bounds -->
<BoxContainer Name="CWarningBounds" Orientation="Horizontal" Margin="0 0 0 2"/>
<BoxContainer Name="CWarningBounds" Orientation="Horizontal" Margin="0 6 0 2"/>
</BoxContainer>
</CollapsibleBody>
</Collapsible>

View File

@@ -23,7 +23,7 @@ public sealed partial class ThresholdControl : BoxContainer
public event Action<AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? ThresholdDataChanged;
private CollapsibleHeading _name => CName;
private CheckBox _ignore => CIgnore;
private CheckBox _enabled => CEnabled;
private BoxContainer _dangerBounds => CDangerBounds;
private BoxContainer _warningBounds => CWarningBounds;
private ThresholdBoundControl _upperBoundControl;
@@ -46,24 +46,14 @@ public sealed partial class ThresholdControl : BoxContainer
// i miss rust macros
_upperBoundControl = new ThresholdBoundControl("upper-bound", _threshold.UpperBound, modifier);
_upperBoundControl.OnBoundChanged += value =>
_upperBoundControl = new ThresholdBoundControl(LabelForBound("upper-bound"), _threshold.UpperBound.Value, modifier);
_upperBoundControl.OnBoundChanged += (value) =>
{
// a lot of threshold logic is baked into the properties,
// so setting this just returns if a change occurred or not
_threshold.TrySetPrimaryBound(AtmosMonitorThresholdBound.Upper, value);
return _threshold.UpperBound;
_threshold.SetLimit(AtmosMonitorLimitType.UpperDanger, value);
};
_upperBoundControl.OnBoundEnabled += () =>
_upperBoundControl.OnBoundEnabled += (isEnabled) =>
{
var value = 0f;
if (_threshold.LowerWarningBound != null)
value = (float) _threshold.LowerWarningBound + 0.1f;
else if (_threshold.LowerBound != null)
value = (float) _threshold.LowerBound + 0.1f;
return value;
_threshold.SetEnabled(AtmosMonitorLimitType.UpperDanger, isEnabled);
};
_upperBoundControl.OnValidBoundChanged += () =>
{
@@ -71,227 +61,101 @@ public sealed partial class ThresholdControl : BoxContainer
};
_dangerBounds.AddChild(_upperBoundControl);
_lowerBoundControl = new ThresholdBoundControl("lower-bound", _threshold.LowerBound, modifier);
_lowerBoundControl = new ThresholdBoundControl(LabelForBound("lower-bound"), _threshold.LowerBound.Value, modifier);
_lowerBoundControl.OnBoundChanged += value =>
{
_threshold.TrySetPrimaryBound(AtmosMonitorThresholdBound.Lower, value);
return _threshold.LowerBound;
_threshold.SetLimit(AtmosMonitorLimitType.LowerDanger, value);
};
_lowerBoundControl.OnBoundEnabled += () =>
_lowerBoundControl.OnBoundEnabled += (isEnabled) =>
{
var value = 0f;
if (_threshold.UpperWarningBound != null)
value = (float) _threshold.UpperWarningBound - 0.1f;
else if (_threshold.UpperBound != null)
value = (float) _threshold.UpperBound - 0.1f;
return value;
_threshold.SetEnabled(AtmosMonitorLimitType.LowerDanger, isEnabled);
};
_lowerBoundControl.OnValidBoundChanged += () =>
{
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
};
_dangerBounds.AddChild(_lowerBoundControl);
_upperWarningBoundControl = new ThresholdBoundControl("upper-warning-bound", _threshold.UpperWarningBound, modifier);
_upperWarningBoundControl = new ThresholdBoundControl(LabelForBound("upper-warning-bound"), _threshold.UpperWarningBound.Value, modifier);
_upperWarningBoundControl.OnBoundChanged += value =>
{
_threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Upper, value);
return _threshold.UpperWarningBound;
_threshold.SetLimit(AtmosMonitorLimitType.UpperWarning, value);
};
_upperWarningBoundControl.OnBoundEnabled += () =>
_upperWarningBoundControl.OnBoundEnabled += (isEnabled) =>
{
var value = 0f;
if (_threshold.LowerWarningBound != null)
value = (float) _threshold.LowerWarningBound + 0.1f;
else if (_threshold.LowerBound != null)
value = (float) _threshold.LowerBound + 0.1f;
return value;
_threshold.SetEnabled(AtmosMonitorLimitType.UpperWarning, isEnabled);
};
_upperWarningBoundControl.OnValidBoundChanged += () =>
{
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
};
_warningBounds.AddChild(_upperWarningBoundControl);
_lowerWarningBoundControl = new ThresholdBoundControl("lower-warning-bound", _threshold.LowerWarningBound, modifier);
_lowerWarningBoundControl = new ThresholdBoundControl(LabelForBound("lower-warning-bound"), _threshold.LowerWarningBound.Value, modifier);
_lowerWarningBoundControl.OnBoundChanged += value =>
{
_threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Lower, value);
return _threshold.LowerWarningBound;
_threshold.SetLimit(AtmosMonitorLimitType.LowerWarning, value);
};
_lowerWarningBoundControl.OnBoundEnabled += () =>
_lowerWarningBoundControl.OnBoundEnabled += (isEnabled) =>
{
var value = 0f;
if (_threshold.UpperWarningBound != null)
value = (float) _threshold.UpperWarningBound - 0.1f;
else if (_threshold.UpperBound != null)
value = (float) _threshold.UpperBound - 0.1f;
return value;
_threshold.SetEnabled(AtmosMonitorLimitType.LowerWarning, isEnabled);
};
_lowerWarningBoundControl.OnValidBoundChanged += () =>
{
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
};
_warningBounds.AddChild(_lowerWarningBoundControl);
_ignore.OnToggled += args =>
_enabled.OnToggled += args =>
{
_threshold.Ignore = args.Pressed;
_threshold.Ignore = !args.Pressed;
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
};
_ignore.Pressed = _threshold.Ignore;
_enabled.Pressed = !_threshold.Ignore;
}
public void UpdateThresholdData(AtmosAlarmThreshold threshold)
private String LabelForBound(string boundType) //<todo.eoin Replace this with enums
{
_upperBoundControl.SetValue(threshold.UpperBound);
_lowerBoundControl.SetValue(threshold.LowerBound);
_upperWarningBoundControl.SetValue(threshold.UpperWarningBound);
_lowerWarningBoundControl.SetValue(threshold.LowerWarningBound);
_ignore.Pressed = threshold.Ignore;
return Loc.GetString($"air-alarm-ui-thresholds-{boundType}");
}
private sealed class ThresholdBoundControl : BoxContainer
public void UpdateThresholdData(AtmosAlarmThreshold threshold, float currentAmount)
{
// raw values to use in thresholds, prefer these
// over directly setting Modified(Value/LastValue)
// when working with the FloatSpinBox
private float? _value;
private float _lastValue;
threshold.CheckThreshold(currentAmount, out AtmosAlarmType alarm, out AtmosMonitorThresholdBound which);
// convenience thing for getting multiplied values
// and also setting value to a usable value
private float? ModifiedValue
var upperDangerState = AtmosAlarmType.Normal;
var lowerDangerState = AtmosAlarmType.Normal;
var upperWarningState = AtmosAlarmType.Normal;
var lowerWarningState = AtmosAlarmType.Normal;
if(alarm == AtmosAlarmType.Danger)
{
get => _value * _modifier;
set => _value = value / _modifier;
if(which == AtmosMonitorThresholdBound.Upper) upperDangerState = alarm;
else lowerDangerState = alarm;
}
else if(alarm == AtmosAlarmType.Warning)
{
if(which == AtmosMonitorThresholdBound.Upper) upperWarningState = alarm;
else lowerWarningState = alarm;
}
private float ModifiedLastValue
{
get => _lastValue * _modifier;
set => _lastValue = value / _modifier;
}
_upperBoundControl.SetValue(threshold.UpperBound.Value);
_upperBoundControl.SetEnabled(threshold.UpperBound.Enabled);
_upperBoundControl.SetWarningState(upperDangerState);
private float _modifier;
_lowerBoundControl.SetValue(threshold.LowerBound.Value);
_lowerBoundControl.SetEnabled(threshold.LowerBound.Enabled);
_lowerBoundControl.SetWarningState(lowerDangerState);
private FloatSpinBox _bound;
private CheckBox _boundEnabled;
_upperWarningBoundControl.SetValue(threshold.UpperWarningBound.Value);
_upperWarningBoundControl.SetEnabled(threshold.UpperWarningBound.Enabled);
_upperWarningBoundControl.SetWarningState(upperWarningState);
public event Action? OnValidBoundChanged;
public Func<float?, float?>? OnBoundChanged;
public Func<float>? OnBoundEnabled;
_lowerWarningBoundControl.SetValue(threshold.LowerWarningBound.Value);
_lowerWarningBoundControl.SetEnabled(threshold.LowerWarningBound.Enabled);
_lowerWarningBoundControl.SetWarningState(lowerWarningState);
public void SetValue(float? value)
{
_value = value;
if (_value == null)
{
_boundEnabled.Pressed = false;
_bound.Value = 0;
}
else
{
_boundEnabled.Pressed = true;
_bound.Value = (float) ModifiedValue!;
}
}
// Modifier indicates what factor the value should be multiplied by.
// Mostly useful to convert tiny decimals to human-readable 'percentages'
// (yes it's still a float, but floatspinbox unfucks that)
public ThresholdBoundControl(string name, float? value, float modifier = 1)
{
_modifier = modifier > 0 ? modifier : 1;
_value = value;
HorizontalExpand = true;
Orientation = LayoutOrientation.Vertical;
AddChild(new Label { Text = Loc.GetString($"air-alarm-ui-thresholds-{name}") });
_bound = new FloatSpinBox(.01f, 2);
AddChild(_bound);
_boundEnabled = new CheckBox
{
Text = Loc.GetString("Enabled")
};
AddChild(_boundEnabled);
_bound.Value = ModifiedValue ?? 0;
_lastValue = _value ?? 0;
_boundEnabled.Pressed = _value != null;
_bound.OnValueChanged += ChangeValue;
_bound.IsValid += ValidateThreshold;
_boundEnabled.OnToggled += ToggleBound;
}
private void ChangeValue(FloatSpinBox.FloatSpinBoxEventArgs args)
{
// ensure that the value in the spinbox is transformed
ModifiedValue = args.Value;
// set the value in the scope above
var value = OnBoundChanged!(_value);
// is the value not null, or has it changed?
if (value != null || value != _lastValue)
{
_value = value;
_lastValue = (float) value!;
OnValidBoundChanged!.Invoke();
}
// otherwise, just set it to the last known value
else
{
_value = _lastValue;
_bound.Value = ModifiedLastValue;
}
}
private void ToggleBound(BaseButton.ButtonToggledEventArgs args)
{
if (args.Pressed)
{
var value = OnBoundChanged!(_lastValue);
if (value != _lastValue)
{
value = OnBoundChanged!(OnBoundEnabled!());
if (value == null || value < 0)
{
// TODO: Improve UX here, this is ass
// basically this implies that the bound
// you currently have is too aggressive
// for the other set of values, so a
// default value (which is +/-0.1) can't
// be used
_boundEnabled.Pressed = false;
return;
}
}
_value = value;
_bound.Value = (float) ModifiedValue!;
_lastValue = (float) _value;
}
else
{
_value = null;
_bound.Value = 0f;
OnBoundChanged!(_value);
}
OnValidBoundChanged!.Invoke();
}
private bool ValidateThreshold(float value)
{
return _value != null && value >= 0;
}
_enabled.Pressed = !threshold.Ignore;
}
}