Utility methods for window decoration.
Utility methods for window decoration.

--- a/API/VOID_Module.cs
+++ b/API/VOID_Module.cs
@@ -142,15 +142,6 @@
 		{
 			get
 			{
-
-				Tools.PostDebugMessage(
-					this,
-					"Checking if scene is valid: LoadedScene={0}, ValidScenes={1}, inValidScene={2}",
-					Enum.GetName(typeof(GameScenes), HighLogic.LoadedScene),
-					string.Join(", ", this.ValidScenes.Select(s => Enum.GetName(typeof(GameScenes), s)).ToArray()),
-					this.ValidScenes.Contains(HighLogic.LoadedScene)
-				);
-
 				return this.ValidScenes.Contains(HighLogic.LoadedScene);
 			}
 		}
@@ -199,15 +190,6 @@
 		{
 			get
 			{
-
-				Tools.PostDebugMessage(
-					this,
-					"Checking if mode is valid: CurrentGame.Mode={0}, ValidModes={1}, inValidGame={2}",
-					Enum.GetName(typeof(Game.Modes), HighLogic.CurrentGame.Mode),
-					string.Join(", ", this.ValidModes.Select(m => Enum.GetName(typeof(Game.Modes), m)).ToArray()),
-					this.ValidModes.Contains(HighLogic.CurrentGame.Mode)
-				);
-
 				return this.ValidModes.Contains(HighLogic.CurrentGame.Mode);
 			}
 		}
@@ -425,6 +407,10 @@
 
 	public abstract class VOID_WindowModule : VOID_Module
 	{
+		protected static GUIContent closeButton;
+
+		protected static Dictionary<int, Action<int>> DecoratedWindows;
+
 		[AVOID_SaveValue("WindowPos")]
 		protected Rect WindowPos;
 		protected float defWidth;
@@ -432,6 +418,18 @@
 
 		protected string inputLockName;
 
+		protected virtual Action<int> DecoratedWindow
+		{
+			get
+			{
+				return VOID_WindowModule.DecorateWindow(
+					this.ModuleWindow,
+					this.WindowPos,
+					(bool active) => { this.toggleActive = active; }
+				);
+			}
+		}
+
 		public VOID_WindowModule() : base()
 		{
 			this.defWidth = 250f;
@@ -453,7 +451,7 @@
 			_Pos = GUILayout.Window(
 				this.core.windowID,
 				_Pos,
-				VOID_Tools.GetWindowHandler(this.ModuleWindow),
+				VOID_Tools.GetWindowHandler(this.DecoratedWindow),
 				this.Name,
 				GUILayout.Width(this.defWidth),
 				GUILayout.Height(this.defHeight),
@@ -520,6 +518,65 @@
 				this.core.configDirty = true;
 			}
 		}
+
+		public static Action<int> DecorateWindow(Action<int> func, Rect windowRect, Callback<bool> callBack)
+		{
+			if (DecoratedWindows == null)
+			{
+				DecoratedWindows = new Dictionary<int, Action<int>>();
+			}
+
+			int hashCode = func.GetHashCode();
+
+			if (!DecoratedWindows.ContainsKey(hashCode))
+			{
+				DecoratedWindows[hashCode] = delegate(int id)
+				{
+					func(id);
+
+					if (closeButton == null)
+					{
+						closeButton = new GUIContent("X");
+					}
+
+					Rect closeRect = GUILayoutUtility.GetRect(
+						closeButton,
+						VOID_Data.Core.Skin.button,
+						GUILayout.ExpandWidth(false)
+					);
+
+					closeRect.x = windowRect.width - closeRect.width - VOID_Data.Core.Skin.button.margin.right;
+					closeRect.y = VOID_Data.Core.Skin.button.margin.top;
+
+					GUI.Button(closeRect, closeButton, VOID_Data.Core.Skin.button);
+
+					if (Event.current.type == EventType.repaint && Input.GetMouseButtonUp(0))
+					{
+						if (closeRect.Contains(Event.current.mousePosition))
+						{
+							callBack(false);
+						}
+					}
+				};
+			}
+
+			return DecoratedWindows[hashCode];
+		}
+
+		public static void UncacheWindow(Action<int> func)
+		{
+			if (DecoratedWindows != null)
+			{
+				int hashCode = func.GetHashCode();
+
+				if (DecoratedWindows.ContainsKey(hashCode))
+				{
+					VOID_Tools.UncacheWindow(DecoratedWindows[hashCode]);
+
+					DecoratedWindows.Remove(hashCode);
+				}
+			}
+		}
 	}
 }
 

--- a/Tools/VOID_DataValue.cs
+++ b/Tools/VOID_DataValue.cs
@@ -145,9 +145,11 @@
 		}
 	}
 
-	public abstract class VOID_NumValue<T> : VOID_DataValue<T>
+	public abstract class VOID_NumValue<T> : VOID_DataValue<T>, IFormattable
 		where T : IFormattable, IConvertible, IComparable
 	{
+		public static IFormatProvider formatProvider = Tools.SIFormatter;
+
 		public static implicit operator Double(VOID_NumValue<T> v)
 		{
 			return v.ToDouble();
@@ -158,17 +160,14 @@
 			return v.ToInt32();
 		}
 
-
 		public static implicit operator Single(VOID_NumValue<T> v)
 		{
 			return v.ToSingle();
 		}
 
-		protected IFormatProvider formatProvider;
-
 		public VOID_NumValue(string Label, Func<T> ValueFunc, string Units = "") : base(Label, ValueFunc, Units)
 		{
-			this.formatProvider = System.Globalization.CultureInfo.CurrentUICulture;
+
 		}
 
 		public virtual double ToDouble(IFormatProvider provider)
@@ -178,7 +177,7 @@
 
 		public virtual double ToDouble()
 		{
-			return this.ToDouble(this.formatProvider);
+			return this.ToDouble(formatProvider);
 		}
 
 		public virtual int ToInt32(IFormatProvider provider)
@@ -188,7 +187,7 @@
 
 		public virtual int ToInt32()
 		{
-			return this.ToInt32(this.formatProvider);
+			return this.ToInt32(formatProvider);
 		}
 
 		public virtual float ToSingle(IFormatProvider provider)
@@ -198,15 +197,19 @@
 
 		public virtual float ToSingle()
 		{
-			return this.ToSingle(this.formatProvider);
-		}
-
-		public virtual string ToString(string Format)
+			return this.ToSingle(formatProvider);
+		}
+
+		public virtual string ToString(string format)
+		{
+			return this.ToString(format, formatProvider);
+		}
+
+		public virtual string ToString(string format, IFormatProvider provider)
 		{
 			return string.Format (
-				"{0}: {1}{2}",
-				this.Label,
-				this.Value.ToString(Format, this.formatProvider),
+				"{0}{1}",
+				this.Value.ToString(format, provider),
 				this.Units
 			);
 		}
@@ -222,7 +225,7 @@
 
 		public virtual string ValueUnitString(string format)
 		{
-			return this.Value.ToString(format, this.formatProvider) + this.Units;
+			return this.Value.ToString(format, formatProvider) + this.Units;
 		}
 		
 		public virtual string ValueUnitString(int digits) {

--- a/Tools/VOID_Tools.cs
+++ b/Tools/VOID_Tools.cs
@@ -363,6 +363,19 @@
 			}
 
 			return functionCache[hashCode];
+		}
+
+		public static void UncacheWindow(Action<int> func)
+		{
+			if (functionCache != null)
+			{
+				int hashCode = func.GetHashCode();
+
+				if (functionCache.ContainsKey(hashCode))
+				{
+					functionCache.Remove(hashCode);
+				}
+			}
 		}
 
 		/// <summary>

--- a/VOIDCore_Generic.cs
+++ b/VOIDCore_Generic.cs
@@ -118,6 +118,7 @@
 				"SSUITextAreaDefault",
 				"ExperimentsDialogSkin",
 				"ExpRecoveryDialogSkin",
+				"KSP window 1",
 				"KSP window 5",
 				"KSP window 6",
 				"PartTooltipSkin",
@@ -391,7 +392,12 @@
 				_mainWindowPos = GUILayout.Window(
 					this.windowID,
 					_mainWindowPos,
-					VOID_Tools.GetWindowHandler(this.VOIDMainWindow),
+					VOID_Tools.GetWindowHandler(
+						VOID_WindowModule.DecorateWindow(
+							this.VOIDMainWindow,
+							_mainWindowPos,
+							(bool active) => { this.mainGuiMinimized = !active; }
+						)),
 					string.Join(" ", new string[] { this.VoidName, this.VoidVersion }),
 					GUILayout.Width(250f),
 					GUILayout.Height(50f),
@@ -421,7 +427,11 @@
 				_configWindowPos = GUILayout.Window(
 					this.windowID,
 					_configWindowPos,
-					VOID_Tools.GetWindowHandler(this.VOIDConfigWindow),
+					VOID_Tools.GetWindowHandler(VOID_WindowModule.DecorateWindow(
+						this.VOIDConfigWindow,
+						_configWindowPos,
+						(bool active) => { this.configWindowMinimized = !active; }
+					)),
 					string.Join(" ", new string[] { this.VoidName, "Configuration" }),
 					GUILayout.Width(250),
 					GUILayout.Height(50)

--- a/VOID_CareerStatus.cs
+++ b/VOID_CareerStatus.cs
@@ -48,11 +48,11 @@
 		{
 			if (delta > 0)
 			{
-				return string.Format("<color='lime'>{0}↑</color>", delta.ToString(numberFormat, Tools.mySIFormatter));
+				return string.Format("<color='lime'>{0}↑</color>", delta.ToString(numberFormat, Tools.SIFormatter));
 			}
 			else if (delta < 0)
 			{
-				return string.Format("<color='red'>{0}↓</color>", delta.ToString(numberFormat, Tools.mySIFormatter));
+				return string.Format("<color='red'>{0}↓</color>", delta.ToString(numberFormat, Tools.SIFormatter));
 			}
 			else
 			{

--- a/VOID_Data.cs
+++ b/VOID_Data.cs
@@ -283,7 +283,7 @@
 						return double.NaN;
 					}
 
-					return Core.LastStage.resourceMass;
+					return Core.LastStage.totalResourceMass;
 				},
 				"tons"
 			);
@@ -1150,6 +1150,50 @@
 				"°"
 			);
 
+		public static readonly VOID_StrValue timeToAscendingNode =
+			new VOID_StrValue(
+				"Time to Ascending Node",
+				delegate()
+				{
+					double trueAnomalyAscNode = 360d - argumentPeriapsis;
+					double dTAscNode = Core.vessel.orbit.GetDTforTrueAnomaly(
+						trueAnomalyAscNode * Mathf.Deg2Rad,
+						Core.vessel.orbit.period
+					);
+
+					dTAscNode %= Core.vessel.orbit.period;
+
+					if (dTAscNode < 0d)
+					{
+						dTAscNode += Core.vessel.orbit.period;
+					}
+
+					return VOID_Tools.FormatInterval(dTAscNode);
+				}
+			);
+
+		public static readonly VOID_StrValue timeToDescendingNode =
+			new VOID_StrValue(
+				"Time to Descending Node",
+				delegate()
+				{
+					double trueAnomalyAscNode = 180d - argumentPeriapsis;
+					double dTDescNode = Core.vessel.orbit.GetDTforTrueAnomaly(
+						trueAnomalyAscNode * Mathf.Deg2Rad,
+						Core.vessel.orbit.period
+					);
+
+					dTDescNode %= Core.vessel.orbit.period;
+
+					if (dTDescNode < 0d)
+					{
+						dTDescNode += Core.vessel.orbit.period;
+					}
+
+					return VOID_Tools.FormatInterval(dTDescNode);
+				}
+			);
+
 		public static readonly VOID_DoubleValue localSiderealLongitude =
 			new VOID_DoubleValue(
 				"Local Sidereal Longitude",
@@ -1171,7 +1215,17 @@
 		public static readonly VOID_StrValue currBiome =
 			new VOID_StrValue(
 				"Biome",
-				new Func<string>(() => VOID_Tools.GetBiome(Core.vessel).name)
+				delegate()
+				{
+					if (Core.vessel.landedAt == string.Empty)
+					{
+						return VOID_Tools.GetBiome(Core.vessel).name;
+					}
+					else
+					{
+						return Core.vessel.landedAt;
+					}
+				}
 			);
 
 		#endregion

--- a/VOID_DataLogger.cs
+++ b/VOID_DataLogger.cs
@@ -45,6 +45,9 @@
 
 		protected bool _loggingActive;
 		protected bool firstWrite;
+
+		[AVOID_SaveValue("waitForLaunch")]
+		protected VOID_SaveValue<bool> waitForLaunch;
 
 		[AVOID_SaveValue("logInterval")]
 		protected VOID_SaveValue<float> logInterval;
@@ -194,7 +197,7 @@
 
 			// CSV Logging
 			// from ISA MapSat
-			if (loggingActive)
+			if (loggingActive && (!waitForLaunch || this.vessel.situation != Vessel.Situations.PRELAUNCH))
 			{
 				//data logging is on
 				//increment timers
@@ -260,6 +263,11 @@
 				string.Format("Data logging: {0}", activeLabelText),
 				null,
 				activeLabelStyle
+			);
+
+			this.waitForLaunch.value = GUITools.Toggle(
+				this.waitForLaunch,
+				"Wait for launch"
 			);
 
 			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
@@ -459,6 +467,8 @@
 			this.loggingActive = false;
 			this.firstWrite = true;
 
+			this.waitForLaunch = true;
+
 			this.logInterval = 0.5f;
 			this.csvCollectTimer = 0f;
 

--- a/VOID_Orbital.cs
+++ b/VOID_Orbital.cs
@@ -98,6 +98,10 @@
 
 				VOID_Data.longitudeAscNode.DoGUIHorizontal("F3");
 
+				VOID_Data.timeToAscendingNode.DoGUIHorizontal();
+
+				VOID_Data.timeToDescendingNode.DoGUIHorizontal();
+
 				VOID_Data.argumentPeriapsis.DoGUIHorizontal("F3");
 
 				VOID_Data.localSiderealLongitude.DoGUIHorizontal("F3");

--- a/VOID_StageInfo.cs
+++ b/VOID_StageInfo.cs
@@ -26,11 +26,14 @@
 		private Table.Column<double> stageTotalMassCol;
 		private Table.Column<double> stageThrustCol;
 		private Table.Column<double> stageTWRCol;
+		private Table.Column<string> stageTimeCol;
 
 		private bool stylesApplied;
+
 		private bool showBodyList;
-
 		private Rect bodyListPos;
+
+		private bool showColumnSelection;
 
 		private CelestialBody selectedBody;
 		[AVOID_SaveValue("bodyIdx")]
@@ -45,6 +48,7 @@
 
 			this.stylesApplied = false;
 			this.showBodyList = false;
+			this.showColumnSelection = false;
 
 			this.bodyListPos = new Rect();
 
@@ -80,6 +84,9 @@
 			this.stageTWRCol = new Table.Column<double>("T/W Ratio", 20f);
 			this.stageTWRCol.Format = "#.#";
 			this.stageTable.Add(this.stageTWRCol);
+
+			this.stageTimeCol = new Table.Column<string>("Burn Time", 20f);
+			this.stageTable.Add(this.stageTimeCol);
 		}
 
 		public override void DrawGUI()
@@ -140,6 +147,8 @@
 
 				this.stageThrustCol.Add(stage.thrust * 1000f);
 				this.stageTWRCol.Add(stage.thrustToWeight / (this.selectedBody ?? core.HomeBody).GeeASL);
+
+				this.stageTimeCol.Add(VOID_Tools.FormatInterval(stage.time));
 			}
 
 			this.stageTable.Render();
@@ -204,6 +213,15 @@
 			GUI.DragWindow();
 		}
 
+		public override void DrawConfigurables()
+		{
+			this.showColumnSelection = GUILayout.Toggle(
+				this.showColumnSelection,
+				"Select StageInfo Columns",
+				GUI.skin.button
+			);
+		}
+
 		private void BodyPickerWindow(int _)
 		{
 			foreach (CelestialBody body in core.sortedBodyList)