298 lines
10 KiB
C#
298 lines
10 KiB
C#
using System;
|
|
using Content.Shared.Atmos;
|
|
using Content.Shared.Atmos.Monitor;
|
|
using Content.Shared.Atmos.Monitor.Components;
|
|
using Robust.Client.AutoGenerated;
|
|
using Robust.Client.UserInterface.Controls;
|
|
using Robust.Client.UserInterface.CustomControls;
|
|
using Robust.Client.UserInterface.XAML;
|
|
using Robust.Shared.Localization;
|
|
|
|
// holy FUCK
|
|
// this technically works because some of this you can *not* do in XAML but holy FUCK
|
|
|
|
namespace Content.Client.Atmos.Monitor.UI.Widgets;
|
|
|
|
[GenerateTypedNameReferences]
|
|
public sealed partial class ThresholdControl : BoxContainer
|
|
{
|
|
private AtmosAlarmThreshold _threshold;
|
|
private AtmosMonitorThresholdType _type;
|
|
private Gas? _gas;
|
|
|
|
public event Action<AtmosMonitorThresholdType, AtmosAlarmThreshold, Gas?>? ThresholdDataChanged;
|
|
|
|
private CollapsibleHeading _name => CName;
|
|
private CheckBox _ignore => CIgnore;
|
|
private BoxContainer _dangerBounds => CDangerBounds;
|
|
private BoxContainer _warningBounds => CWarningBounds;
|
|
private ThresholdBoundControl _upperBoundControl;
|
|
private ThresholdBoundControl _lowerBoundControl;
|
|
private ThresholdBoundControl _upperWarningBoundControl;
|
|
private ThresholdBoundControl _lowerWarningBoundControl;
|
|
|
|
// i have played myself by making threshold values nullable to
|
|
// indicate validity/disabled status, with several layers of side effect
|
|
// dependent on the other three values when you change one :HECK:
|
|
public ThresholdControl(string name, AtmosAlarmThreshold threshold, AtmosMonitorThresholdType type, Gas? gas = null, float modifier = 1)
|
|
{
|
|
RobustXamlLoader.Load(this);
|
|
|
|
_threshold = threshold;
|
|
_type = type;
|
|
_gas = gas;
|
|
|
|
_name.Title = name;
|
|
|
|
// i miss rust macros
|
|
|
|
_upperBoundControl = new ThresholdBoundControl("upper-bound", _threshold.UpperBound, 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;
|
|
};
|
|
_upperBoundControl.OnBoundEnabled += () =>
|
|
{
|
|
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;
|
|
};
|
|
_upperBoundControl.OnValidBoundChanged += () =>
|
|
{
|
|
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
|
|
};
|
|
_dangerBounds.AddChild(_upperBoundControl);
|
|
|
|
_lowerBoundControl = new ThresholdBoundControl("lower-bound", _threshold.LowerBound, modifier);
|
|
_lowerBoundControl.OnBoundChanged += value =>
|
|
{
|
|
_threshold.TrySetPrimaryBound(AtmosMonitorThresholdBound.Lower, value);
|
|
return _threshold.LowerBound;
|
|
};
|
|
_lowerBoundControl.OnBoundEnabled += () =>
|
|
{
|
|
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;
|
|
};
|
|
_lowerBoundControl.OnValidBoundChanged += () =>
|
|
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
|
|
_dangerBounds.AddChild(_lowerBoundControl);
|
|
|
|
_upperWarningBoundControl = new ThresholdBoundControl("upper-warning-bound", _threshold.UpperWarningBound, modifier);
|
|
_upperWarningBoundControl.OnBoundChanged += value =>
|
|
{
|
|
_threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Upper, value);
|
|
return _threshold.UpperWarningBound;
|
|
};
|
|
_upperWarningBoundControl.OnBoundEnabled += () =>
|
|
{
|
|
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;
|
|
};
|
|
_upperWarningBoundControl.OnValidBoundChanged += () =>
|
|
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
|
|
_warningBounds.AddChild(_upperWarningBoundControl);
|
|
|
|
_lowerWarningBoundControl = new ThresholdBoundControl("lower-warning-bound", _threshold.LowerWarningBound, modifier);
|
|
_lowerWarningBoundControl.OnBoundChanged += value =>
|
|
{
|
|
_threshold.TrySetWarningBound(AtmosMonitorThresholdBound.Lower, value);
|
|
return _threshold.LowerWarningBound;
|
|
};
|
|
_lowerWarningBoundControl.OnBoundEnabled += () =>
|
|
{
|
|
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;
|
|
};
|
|
_lowerWarningBoundControl.OnValidBoundChanged += () =>
|
|
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
|
|
|
|
_warningBounds.AddChild(_lowerWarningBoundControl);
|
|
|
|
_ignore.OnToggled += args =>
|
|
{
|
|
_threshold.Ignore = args.Pressed;
|
|
ThresholdDataChanged!.Invoke(_type, _threshold, _gas);
|
|
};
|
|
_ignore.Pressed = _threshold.Ignore;
|
|
}
|
|
|
|
public void UpdateThresholdData(AtmosAlarmThreshold threshold)
|
|
{
|
|
_upperBoundControl.SetValue(threshold.UpperBound);
|
|
_lowerBoundControl.SetValue(threshold.LowerBound);
|
|
_upperWarningBoundControl.SetValue(threshold.UpperWarningBound);
|
|
_lowerWarningBoundControl.SetValue(threshold.LowerWarningBound);
|
|
_ignore.Pressed = threshold.Ignore;
|
|
}
|
|
|
|
|
|
private sealed 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;
|
|
private float _lastValue;
|
|
|
|
// convenience thing for getting multiplied values
|
|
// and also setting value to a usable value
|
|
private float? ModifiedValue
|
|
{
|
|
get => _value * _modifier;
|
|
set => _value = value / _modifier;
|
|
}
|
|
|
|
private float ModifiedLastValue
|
|
{
|
|
get => _lastValue * _modifier;
|
|
set => _lastValue = value / _modifier;
|
|
}
|
|
|
|
private float _modifier;
|
|
|
|
private FloatSpinBox _bound;
|
|
private CheckBox _boundEnabled;
|
|
|
|
public event Action? OnValidBoundChanged;
|
|
public Func<float?, float?>? OnBoundChanged;
|
|
public Func<float>? OnBoundEnabled;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|