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? 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? OnBoundChanged; public Func? 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; } } }