UX improvements to Air Alarm UI (#12681)
Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
This commit is contained in:
@@ -14,35 +14,66 @@ public sealed class AtmosAlarmThreshold : IPrototype, ISerializationHooks
|
||||
[DataField("ignore")]
|
||||
public bool Ignore;
|
||||
|
||||
// zero bounds are not allowed - just
|
||||
// set the bound to null if you want
|
||||
// to disable it
|
||||
[DataField("upperBound")]
|
||||
public float? UpperBound { get; private set; }
|
||||
private AlarmThresholdSetting _UpperBound;
|
||||
|
||||
public AlarmThresholdSetting UpperBound { get { return _UpperBound; } private set
|
||||
{
|
||||
// Because the warnings are stored as percentages of the bounds,
|
||||
// Make a copy of the calculated bounds, so that the real warning amount
|
||||
// doesn't change value when user changes the bounds
|
||||
var oldWarning = UpperWarningBound;
|
||||
_UpperBound = value;
|
||||
UpperWarningBound = oldWarning;
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("lowerBound")]
|
||||
public float? LowerBound { get; private set; }
|
||||
public AlarmThresholdSetting _LowerBound;
|
||||
|
||||
public AlarmThresholdSetting LowerBound { get { return _LowerBound; } private set
|
||||
{
|
||||
// Because the warnings are stored as percentages of the bounds,
|
||||
// Make a copy of the calculated bounds, so that the real warning amount
|
||||
// doesn't change value when user changes the bounds
|
||||
var oldWarning = LowerWarningBound;
|
||||
_LowerBound = value;
|
||||
LowerWarningBound = oldWarning;
|
||||
}
|
||||
}
|
||||
|
||||
// upper warning percentage
|
||||
// must always cause UpperWarningBound
|
||||
// to be smaller
|
||||
[DataField("upperWarnAround")]
|
||||
public float? UpperWarningPercentage { get; private set; }
|
||||
public AlarmThresholdSetting UpperWarningPercentage { get; private set; }
|
||||
|
||||
// lower warning percentage
|
||||
// must always cause LowerWarningBound
|
||||
// to be larger
|
||||
[DataField("lowerWarnAround")]
|
||||
public float? LowerWarningPercentage { get; private set; }
|
||||
public AlarmThresholdSetting LowerWarningPercentage { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public float? UpperWarningBound => CalculateWarningBound(AtmosMonitorThresholdBound.Upper);
|
||||
public AlarmThresholdSetting UpperWarningBound
|
||||
{
|
||||
get { return CalculateWarningBound(AtmosMonitorThresholdBound.Upper); }
|
||||
set { UpperWarningPercentage = CalculateWarningPercentage(AtmosMonitorThresholdBound.Upper, value); }
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public float? LowerWarningBound => CalculateWarningBound(AtmosMonitorThresholdBound.Lower);
|
||||
public AlarmThresholdSetting LowerWarningBound
|
||||
{
|
||||
get { return CalculateWarningBound(AtmosMonitorThresholdBound.Lower); }
|
||||
set { LowerWarningPercentage = CalculateWarningPercentage(AtmosMonitorThresholdBound.Lower, value); }
|
||||
}
|
||||
|
||||
public AtmosAlarmThreshold()
|
||||
{
|
||||
UpperBound = new AlarmThresholdSetting();
|
||||
LowerBound = new AlarmThresholdSetting();
|
||||
UpperWarningPercentage = new AlarmThresholdSetting();
|
||||
LowerWarningPercentage = new AlarmThresholdSetting();
|
||||
}
|
||||
|
||||
public AtmosAlarmThreshold(AtmosAlarmThreshold other)
|
||||
@@ -56,186 +87,189 @@ public sealed class AtmosAlarmThreshold : IPrototype, ISerializationHooks
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
if (UpperBound <= LowerBound)
|
||||
UpperBound = null;
|
||||
|
||||
if (LowerBound >= UpperBound)
|
||||
LowerBound = null;
|
||||
|
||||
if (UpperWarningPercentage != null)
|
||||
TrySetWarningBound(AtmosMonitorThresholdBound.Upper, UpperBound * UpperWarningPercentage);
|
||||
|
||||
if (LowerWarningPercentage != null)
|
||||
TrySetWarningBound(AtmosMonitorThresholdBound.Lower, LowerBound * LowerWarningPercentage);
|
||||
UpperBound = new AlarmThresholdSetting{ Enabled = UpperBound.Value != 0, Value = UpperBound.Value };
|
||||
LowerBound = new AlarmThresholdSetting{ Enabled = LowerBound.Value != 0, Value = LowerBound.Value };
|
||||
UpperWarningPercentage = new AlarmThresholdSetting{ Enabled = UpperWarningPercentage.Value != 0, Value = UpperWarningPercentage.Value };
|
||||
LowerWarningPercentage = new AlarmThresholdSetting{ Enabled = LowerWarningPercentage.Value != 0, Value = LowerWarningPercentage.Value };
|
||||
}
|
||||
|
||||
// utility function to check a threshold against some calculated value
|
||||
public bool CheckThreshold(float value, out AtmosAlarmType state)
|
||||
{
|
||||
return CheckThreshold(value, out state, out AtmosMonitorThresholdBound _);
|
||||
}
|
||||
|
||||
// utility function to check a threshold against some calculated value. If the output state
|
||||
// is normal, whichFailed should not be used..
|
||||
public bool CheckThreshold(float value, out AtmosAlarmType state, out AtmosMonitorThresholdBound whichFailed)
|
||||
{
|
||||
state = AtmosAlarmType.Normal;
|
||||
whichFailed = AtmosMonitorThresholdBound.Upper;
|
||||
|
||||
if (Ignore)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value >= UpperBound || value <= LowerBound)
|
||||
if (value >= UpperBound)
|
||||
{
|
||||
state = AtmosAlarmType.Danger;
|
||||
whichFailed = AtmosMonitorThresholdBound.Upper;
|
||||
return true;
|
||||
}
|
||||
if (value >= UpperWarningBound || value <= LowerWarningBound)
|
||||
if(value <= LowerBound)
|
||||
{
|
||||
state = AtmosAlarmType.Danger;
|
||||
whichFailed = AtmosMonitorThresholdBound.Lower;
|
||||
return true;
|
||||
}
|
||||
if (value >= UpperWarningBound)
|
||||
{
|
||||
state = AtmosAlarmType.Warning;
|
||||
whichFailed = AtmosMonitorThresholdBound.Upper;
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// set the primary bound, takes a hard value
|
||||
public bool TrySetPrimaryBound(AtmosMonitorThresholdBound bound, float? input)
|
||||
{
|
||||
if (input == null)
|
||||
if (value <= LowerWarningBound)
|
||||
{
|
||||
switch (bound)
|
||||
{
|
||||
case AtmosMonitorThresholdBound.Upper:
|
||||
UpperBound = null;
|
||||
break;
|
||||
case AtmosMonitorThresholdBound.Lower:
|
||||
LowerBound = null;
|
||||
break;
|
||||
}
|
||||
|
||||
state = AtmosAlarmType.Warning;
|
||||
whichFailed = AtmosMonitorThresholdBound.Lower;
|
||||
return true;
|
||||
}
|
||||
|
||||
var value = (float) input;
|
||||
|
||||
if (value <= 0f || float.IsNaN(value))
|
||||
return false;
|
||||
|
||||
(float target, int compare)? targetValue = null;
|
||||
switch (bound)
|
||||
{
|
||||
case AtmosMonitorThresholdBound.Upper:
|
||||
if (float.IsPositiveInfinity(value))
|
||||
return false;
|
||||
|
||||
if (LowerBound != null)
|
||||
targetValue = ((float) LowerBound, -1);
|
||||
break;
|
||||
case AtmosMonitorThresholdBound.Lower:
|
||||
if (float.IsNegativeInfinity(value))
|
||||
return false;
|
||||
|
||||
if (UpperBound != null)
|
||||
targetValue = ((float) UpperBound, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
var isValid = true;
|
||||
if (targetValue != null)
|
||||
{
|
||||
var result = targetValue.Value.target.CompareTo(value);
|
||||
isValid = targetValue.Value.compare == result;
|
||||
}
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
switch (bound)
|
||||
{
|
||||
case AtmosMonitorThresholdBound.Upper:
|
||||
UpperBound = value;
|
||||
return true;
|
||||
case AtmosMonitorThresholdBound.Lower:
|
||||
LowerBound = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// set the warning bound, takes a hard value
|
||||
//
|
||||
// this will always set the percentage and
|
||||
// the raw value at the same time
|
||||
public bool TrySetWarningBound(AtmosMonitorThresholdBound bound, float? input)
|
||||
/// Warnings are stored in prototypes as a percentage, for ease of content
|
||||
/// maintainers. This recalculates a new "real" value of the warning
|
||||
/// threshold, for use in the actual atmosphereic checks.
|
||||
public AlarmThresholdSetting CalculateWarningBound(AtmosMonitorThresholdBound bound)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
switch (bound)
|
||||
{
|
||||
case AtmosMonitorThresholdBound.Upper:
|
||||
UpperWarningPercentage = null;
|
||||
break;
|
||||
case AtmosMonitorThresholdBound.Lower:
|
||||
LowerWarningPercentage = null;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (bound)
|
||||
{
|
||||
case AtmosMonitorThresholdBound.Upper:
|
||||
if (UpperBound == null)
|
||||
return false;
|
||||
|
||||
var upperWarning = (float) (input / UpperBound);
|
||||
var upperTestValue = upperWarning * (float) UpperBound;
|
||||
|
||||
if (upperWarning > 1f
|
||||
|| upperTestValue < LowerWarningBound
|
||||
|| upperTestValue < LowerBound)
|
||||
return false;
|
||||
|
||||
UpperWarningPercentage = upperWarning;
|
||||
|
||||
return true;
|
||||
return new AlarmThresholdSetting {
|
||||
Enabled = UpperWarningPercentage.Enabled,
|
||||
Value = UpperBound.Value * UpperWarningPercentage.Value};
|
||||
case AtmosMonitorThresholdBound.Lower:
|
||||
if (LowerBound == null)
|
||||
return false;
|
||||
|
||||
var lowerWarning = (float) (input / LowerBound);
|
||||
var testValue = lowerWarning * (float) LowerBound;
|
||||
|
||||
if (lowerWarning < 1f
|
||||
|| testValue > UpperWarningBound
|
||||
|| testValue > UpperBound)
|
||||
return false;
|
||||
|
||||
LowerWarningPercentage = lowerWarning;
|
||||
|
||||
return true;
|
||||
return new AlarmThresholdSetting {
|
||||
Enabled = LowerWarningPercentage.Enabled,
|
||||
Value = LowerBound.Value * LowerWarningPercentage.Value};
|
||||
default:
|
||||
// Unreachable.
|
||||
return new AlarmThresholdSetting();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public float? CalculateWarningBound(AtmosMonitorThresholdBound bound)
|
||||
public AlarmThresholdSetting CalculateWarningPercentage(AtmosMonitorThresholdBound bound, AlarmThresholdSetting warningBound)
|
||||
{
|
||||
float? value = null;
|
||||
|
||||
switch (bound)
|
||||
{
|
||||
case AtmosMonitorThresholdBound.Upper:
|
||||
if (UpperBound == null || UpperWarningPercentage == null)
|
||||
break;
|
||||
|
||||
value = UpperBound * UpperWarningPercentage;
|
||||
break;
|
||||
return new AlarmThresholdSetting {
|
||||
Enabled = UpperWarningPercentage.Enabled,
|
||||
Value = UpperBound.Value == 0 ? 0 : warningBound.Value / UpperBound.Value};
|
||||
case AtmosMonitorThresholdBound.Lower:
|
||||
if (LowerBound == null || LowerWarningPercentage == null)
|
||||
break;
|
||||
return new AlarmThresholdSetting {
|
||||
Enabled = LowerWarningPercentage.Enabled,
|
||||
Value = LowerBound.Value == 0 ? 0 : warningBound.Value / LowerBound.Value };
|
||||
default:
|
||||
// Unreachable.
|
||||
return new AlarmThresholdSetting();
|
||||
}
|
||||
}
|
||||
|
||||
value = LowerBound * LowerWarningPercentage;
|
||||
// Enable or disable a single threshold setting
|
||||
public void SetEnabled(AtmosMonitorLimitType whichLimit, bool isEnabled)
|
||||
{
|
||||
switch(whichLimit)
|
||||
{
|
||||
case AtmosMonitorLimitType.LowerDanger:
|
||||
LowerBound = LowerBound.WithEnabled(isEnabled);
|
||||
break;
|
||||
case AtmosMonitorLimitType.LowerWarning:
|
||||
LowerWarningPercentage = LowerWarningPercentage.WithEnabled(isEnabled);
|
||||
break;
|
||||
case AtmosMonitorLimitType.UpperWarning:
|
||||
UpperWarningPercentage = UpperWarningPercentage.WithEnabled(isEnabled);
|
||||
break;
|
||||
case AtmosMonitorLimitType.UpperDanger:
|
||||
UpperBound = UpperBound.WithEnabled(isEnabled);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
// Set the limit for a threshold. Will clamp other limits appropriately to
|
||||
// enforce that LowerBound <= LowerWarningBound <= UpperWarningBound <= UpperBound
|
||||
public void SetLimit(AtmosMonitorLimitType whichLimit, float limit)
|
||||
{
|
||||
if (limit <= 0)
|
||||
{
|
||||
// Unit tests expect that setting value of 0 or less should not change the limit.
|
||||
// Feels a bit strange, but does avoid a bug where the warning data (stored as a
|
||||
// percentage of danger bounds) is lost when setting the danger threshold to zero
|
||||
return;
|
||||
}
|
||||
|
||||
switch (whichLimit)
|
||||
{
|
||||
case AtmosMonitorLimitType.LowerDanger:
|
||||
LowerBound = LowerBound.WithThreshold(limit);
|
||||
LowerWarningBound = LowerWarningBound.WithThreshold(Math.Max(limit, LowerWarningBound.Value));
|
||||
UpperWarningBound = UpperWarningBound.WithThreshold(Math.Max(limit, UpperWarningBound.Value));
|
||||
UpperBound = UpperBound.WithThreshold(Math.Max(limit, UpperBound.Value));
|
||||
break;
|
||||
case AtmosMonitorLimitType.LowerWarning:
|
||||
LowerBound = LowerBound.WithThreshold(Math.Min(LowerBound.Value, limit));
|
||||
LowerWarningBound = LowerWarningBound.WithThreshold(limit);
|
||||
UpperWarningBound = UpperWarningBound.WithThreshold(Math.Max(limit, UpperWarningBound.Value));
|
||||
UpperBound = UpperBound.WithThreshold(Math.Max(limit, UpperBound.Value));
|
||||
break;
|
||||
case AtmosMonitorLimitType.UpperWarning:
|
||||
LowerBound = LowerBound.WithThreshold(Math.Min(LowerBound.Value, limit));
|
||||
LowerWarningBound = LowerWarningBound.WithThreshold(Math.Min(LowerWarningBound.Value, limit));
|
||||
UpperWarningBound = UpperWarningBound.WithThreshold(limit);
|
||||
UpperBound = UpperBound.WithThreshold(Math.Max(limit, UpperBound.Value));
|
||||
break;
|
||||
case AtmosMonitorLimitType.UpperDanger:
|
||||
LowerBound = LowerBound.WithThreshold(Math.Min(LowerBound.Value, limit));
|
||||
LowerWarningBound = LowerWarningBound.WithThreshold(Math.Min(LowerWarningBound.Value, limit));
|
||||
UpperWarningBound = UpperWarningBound.WithThreshold(Math.Min(UpperWarningBound.Value, limit));
|
||||
UpperBound = UpperBound.WithThreshold(limit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[DataDefinition, Serializable]
|
||||
public struct AlarmThresholdSetting
|
||||
{
|
||||
[DataField("enabled")]
|
||||
public bool Enabled { get; set; } = false;
|
||||
[DataField("threshold")]
|
||||
public float Value { get; set; } = 0;
|
||||
|
||||
public AlarmThresholdSetting()
|
||||
{
|
||||
}
|
||||
|
||||
public static bool operator <=(float a, AlarmThresholdSetting b)
|
||||
{
|
||||
return b.Enabled && a <= b.Value;
|
||||
}
|
||||
|
||||
public static bool operator >=(float a, AlarmThresholdSetting b)
|
||||
{
|
||||
return b.Enabled && a >= b.Value;
|
||||
}
|
||||
|
||||
public AlarmThresholdSetting WithThreshold(float threshold)
|
||||
{
|
||||
return new AlarmThresholdSetting{ Enabled = Enabled, Value = threshold };
|
||||
}
|
||||
|
||||
public AlarmThresholdSetting WithEnabled(bool enabled)
|
||||
{
|
||||
return new AlarmThresholdSetting{ Enabled = enabled, Value = Value };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,6 +279,14 @@ public enum AtmosMonitorThresholdBound
|
||||
Lower
|
||||
}
|
||||
|
||||
public enum AtmosMonitorLimitType //<todo.eoin Very similar to the above...
|
||||
{
|
||||
LowerDanger,
|
||||
LowerWarning,
|
||||
UpperWarning,
|
||||
UpperDanger,
|
||||
}
|
||||
|
||||
// not really used in the prototype but in code,
|
||||
// to differentiate between the different
|
||||
// fields you can find this prototype in
|
||||
|
||||
Reference in New Issue
Block a user