context: #region Copyright & License Information
  /*
   * Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
   * This file is part of OpenRA, which is free software. It is made
   * available to you under the terms of the GNU General Public License
   * as published by the Free Software Foundation, either version 3 of
   * the License, or (at your option) any later version. For more
   * information, see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using OpenRA.Graphics;
  using OpenRA.Network;
  using OpenRA.Primitives;
  using OpenRA.Support;
  
  namespace OpenRA.Widgets
  {
  	public static class Ui
  	{
  		public const int Timestep = 40;
  
  		public static Widget Root = new ContainerWidget();
  
  		public static TickTime LastTickTime = new TickTime(() => Timestep, Game.RunTime);
  
  		static readonly Stack<Widget> WindowList = new Stack<Widget>();
  
  		public static Widget MouseFocusWidget;
  		public static Widget KeyboardFocusWidget;
  		public static Widget MouseOverWidget;
  
  		internal static Translation Translation;
  
  		public static void CloseWindow()
  		{
  			if (WindowList.Count > 0)
  			{
  				var hidden = WindowList.Pop();
  				Root.RemoveChild(hidden);
  				if (hidden.LogicObjects != null)
  					foreach (var l in hidden.LogicObjects)
  						l.BecameHidden();
  			}
  
  			if (WindowList.Count > 0)
  			{
  				var restore = WindowList.Peek();
  				Root.AddChild(restore);
  
  				if (restore.LogicObjects != null)
  					foreach (var l in restore.LogicObjects)
  						l.BecameVisible();
  			}
  		}
  
  		public static Widget OpenWindow(string id)
  		{
  			return OpenWindow(id, new WidgetArgs());
  		}
  
  		public static Widget OpenWindow(string id, WidgetArgs args)
  		{
  			var window = Game.ModData.WidgetLoader.LoadWidget(args, Root, id);
  			if (WindowList.Count > 0)
  				Root.HideChild(WindowList.Peek());
  			WindowList.Push(window);
  			return window;
  		}
  
  		public static Widget CurrentWindow()
  		{
  			return WindowList.Count > 0 ? WindowList.Peek() : null;
  		}
  
  		public static T LoadWidget<T>(string id, Widget parent, WidgetArgs args) where T : Widget
  		{
  			if (LoadWidget(id, parent, args) is T widget)
  				return widget;
  
  			throw new InvalidOperationException($"Widget {id} is not of type {typeof(T).Name}");
  		}
  
  		public static Widget LoadWidget(string id, Widget parent, WidgetArgs args)
  		{
  			return Game.ModData.WidgetLoader.LoadWidget(args, parent, id);
  		}
  
  		public static void Tick() { Root.TickOuter(); }
  
  		public static void PrepareRenderables() { Root.PrepareRenderablesOuter(); }
  
  		public static void Draw() { Root.DrawOuter(); }
  
  		public static bool HandleInput(MouseInput mi)
  		{
  			var wasMouseOver = MouseOverWidget;
  
  			if (mi.Event == MouseInputEvent.Move)
  				MouseOverWidget = null;
  
  			var handled = false;
  			if (MouseFocusWidget != null && MouseFocusWidget.HandleMouseInputOuter(mi))
  				handled = true;
  
  			if (!handled && Root.HandleMouseInputOuter(mi))
  				handled = true;
  
  			if (mi.Event == MouseInputEvent.Move)
  			{
  				Viewport.LastMousePos = mi.Location;
  				Viewport.LastMoveRunTime = Game.RunTime;
  			}
  
  			if (wasMouseOver != MouseOverWidget)
  			{
  				wasMouseOver?.MouseExited();
  
  				MouseOverWidget?.MouseEntered();
  			}
  
  			return handled;
  		}
  
  		/// <summary>Possibly handle keyboard input (if this widget has keyboard focus)</summary>
  		/// <returns><c>true</c>, if keyboard input was handled, <c>false</c> if the input should bubble to the parent widget</returns>
  		/// <param name="e">Key input data</param>
  		public static bool HandleKeyPress(KeyInput e)
  		{
  			if (KeyboardFocusWidget != null)
  				return KeyboardFocusWidget.HandleKeyPressOuter(e);
  
  			return Root.HandleKeyPressOuter(e);
  		}
  
  		public static bool HandleTextInput(string text)
  		{
  			if (KeyboardFocusWidget != null)
  				return KeyboardFocusWidget.HandleTextInputOuter(text);
  
  			return Root.HandleTextInputOuter(text);
  		}
  
  		public static void ResetAll()
  		{
  			Root.RemoveChildren();
  
  			while (WindowList.Count > 0)
  				CloseWindow();
  		}
  
  		public static void ResetTooltips()
  		{
  			// Issue a no-op mouse move to force any tooltips to be recalculated
  			HandleInput(new MouseInput(MouseInputEvent.Move, MouseButton.None,
  				Viewport.LastMousePos, int2.Zero, Modifiers.None, 0));
  		}
  
  		public static void InitializeTranslation()
  		{
  			Translation = new Translation(Game.Settings.Player.Language, Game.ModData.Manifest.Translations, Game.ModData.DefaultFileSystem);
  		}
  
  		public static string Translate(string key, IDictionary<string, object> args = null, string attribute = null)
  		{
  			if (Translation == null)
  				return null;
  
  			return Translation.GetFormattedMessage(key, args, attribute);
  		}
  
  		public static string TranslationAttribute(string key, string attribute = null)
  		{
  			if (Translation == null)
  				return null;
  
  			return Translation.GetAttribute(key, attribute);
  		}
  	}
  
  	public class ChromeLogic : IDisposable
  	{
  		public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
  		public virtual void Tick() { }
  		public virtual void BecameHidden() { }
  		public virtual void BecameVisible() { }
  		protected virtual void Dispose(bool disposing) { }
  	}
  
  	public abstract class Widget
  	{
  		string defaultCursor = null;
  
  		public readonly List<Widget> Children = new List<Widget>();
  
  		// Info defined in YAML
  		public string Id = null;
  		public string X = "0";
  		public string Y = "0";
  		public string Width = "0";
  		public string Height = "0";
  		public string[] Logic = Array.Empty<string>();
  		public ChromeLogic[] LogicObjects { get; private set; }
  		public bool Visible = true;
  		public bool IgnoreMouseOver;
  		public bool IgnoreChildMouseOver;
  
  		// Calculated internally
  		public Rectangle Bounds;
  		public Widget Parent = null;
  		public Func<bool> IsVisible;
  		public Widget() { IsVisible = () => Visible; }
  
  		public Widget(Widget widget)
  		{
  			Id = widget.Id;
  			X = widget.X;
  			Y = widget.Y;
  			Width = widget.Width;
  			Height = widget.Height;
  			Logic = widget.Logic;
  			Visible = widget.Visible;
  
  			Bounds = widget.Bounds;
  			Parent = widget.Parent;
  
  			IsVisible = widget.IsVisible;
  			IgnoreChildMouseOver = widget.IgnoreChildMouseOver;
  			IgnoreMouseOver = widget.IgnoreMouseOver;
  
  			foreach (var child in widget.Children)
  				AddChild(child.Clone());
  		}
  
  		public virtual Widget Clone()
  		{
  			throw new InvalidOperationException($"Widget type `{GetType().Name}` is not cloneable.");
  		}
  
  		public virtual int2 RenderOrigin
  		{
  			get
  			{
  				var offset = (Parent == null) ? int2.Zero : Parent.ChildOrigin;
  				return new int2(Bounds.X, Bounds.Y) + offset;
  			}
  		}
  
  		public virtual int2 ChildOrigin => RenderOrigin;
  
  		public virtual Rectangle RenderBounds
  		{
  			get
  			{
  				var ro = RenderOrigin;
  				return new Rectangle(ro.X, ro.Y, Bounds.Width, Bounds.Height);
  			}
  		}
  
  		public virtual void Initialize(WidgetArgs args)
  		{
  			defaultCursor = ChromeMetrics.Get<string>("DefaultCursor");
  
  			// Parse the YAML equations to find the widget bounds
  			var parentBounds = (Parent == null)
  				? new Rectangle(0, 0, Game.Renderer.Resolution.Width, Game.Renderer.Resolution.Height)
  				: Parent.Bounds;
  
  			var substitutions = args.ContainsKey("substitutions") ?
  				new Dictionary<string, int>((Dictionary<string, int>)args["substitutions"]) :
  				new Dictionary<string, int>();
  
  			substitutions.Add("WINDOW_RIGHT", Game.Renderer.Resolution.Width);
  			substitutions.Add("WINDOW_BOTTOM", Game.Renderer.Resolution.Height);
  			substitutions.Add("PARENT_RIGHT", parentBounds.Width);
  			substitutions.Add("PARENT_LEFT", parentBounds.Left);
  			substitutions.Add("PARENT_TOP", parentBounds.Top);
  			substitutions.Add("PARENT_BOTTOM", parentBounds.Height);
  			var width = Evaluator.Evaluate(Width, substitutions);
  			var height = Evaluator.Evaluate(Height, substitutions);
  
  			substitutions.Add("WIDTH", width);
  			substitutions.Add("HEIGHT", height);
  
  			Bounds = new Rectangle(Evaluator.Evaluate(X, substitutions),
  								   Evaluator.Evaluate(Y, substitutions),
  								   width,
  								   height);
  		}
  
  		public void PostInit(WidgetArgs args)
  		{
  			if (!Logic.Any())
  				return;
  
  			args["widget"] = this;
  
  			LogicObjects = Logic.Select(l => Game.ModData.ObjectCreator.CreateObject<ChromeLogic>(l, args))
  				.ToArray();
  
  			args.Remove("widget");
  		}
  
  		public virtual Rectangle EventBounds => RenderBounds;
  
  		public virtual bool EventBoundsContains(int2 location)
  		{
  			// PERF: Avoid LINQ.
  			if (EventBounds.Contains(location))
  				return true;
  
  			foreach (var child in Children)
  				if (child.IsVisible())
  					if (child.EventBoundsContains(location))
  						return true;
  
  			return false;
  		}
  
  		public bool HasMouseFocus => Ui.MouseFocusWidget == this;
  		public bool HasKeyboardFocus => Ui.KeyboardFocusWidget == this;
  
  		public virtual bool TakeMouseFocus(MouseInput mi)
  		{
  			if (HasMouseFocus)
  				return true;
  
  			if (Ui.MouseFocusWidget != null && !Ui.MouseFocusWidget.YieldMouseFocus(mi))
  				return false;
  
  			Ui.MouseFocusWidget = this;
  			return true;
  		}
  
  		// Remove focus from this widget; return false to hint that you don't want to give it up
  		public virtual bool YieldMouseFocus(MouseInput mi)
  		{
  			if (Ui.MouseFocusWidget == this)
  				Ui.MouseFocusWidget = null;
  
  			return true;
  		}
  
  		void ForceYieldMouseFocus()
  		{
  			if (Ui.MouseFocusWidget == this && !YieldMouseFocus(default(MouseInput)))
  				Ui.MouseFocusWidget = null;
  		}
  
  		public virtual bool TakeKeyboardFocus()
  		{
  			if (HasKeyboardFocus)
  				return true;
  
  			if (Ui.KeyboardFocusWidget != null && !Ui.KeyboardFocusWidget.YieldKeyboardFocus())
  				return false;
  
  			Ui.KeyboardFocusWidget = this;
  			return true;
  		}
  
  		public virtual bool YieldKeyboardFocus()
  		{
  			if (Ui.KeyboardFocusWidget == this)
  				Ui.KeyboardFocusWidget = null;
  
  			return true;
  		}
  
  		void ForceYieldKeyboardFocus()
  		{
  			if (Ui.KeyboardFocusWidget == this && !YieldKeyboardFocus())
  				Ui.KeyboardFocusWidget = null;
  		}
  
  		public virtual string GetCursor(int2 pos) { return defaultCursor; }
  		public string GetCursorOuter(int2 pos)
  		{
  			// Is the cursor on top of us?
  			if (!(IsVisible() && EventBoundsContains(pos)))
  				return null;
  
  			// Do any of our children specify a cursor?
  			foreach (var child in Children.OfType<Widget>().Reverse())
  			{
  				var cc = child.GetCursorOuter(pos);
  				if (cc != null)
  					return cc;
  			}
  
  			return EventBounds.Contains(pos) ? GetCursor(pos) : null;
  		}
  
  		public virtual void MouseEntered() { }
  		public virtual void MouseExited() { }
  
  		/// <summary>Possibly handles mouse input (click, drag, scroll, etc).</summary>
  		/// <returns><c>true</c>, if mouse input was handled, <c>false</c> if the input should bubble to the parent widget</returns>
  		/// <param name="mi">Mouse input data</param>
  		public virtual bool HandleMouseInput(MouseInput mi) { return false; }
  
  		public bool HandleMouseInputOuter(MouseInput mi)
  		{
  			// Are we able to handle this event?
  			if (!(HasMouseFocus || (IsVisible() && EventBoundsContains(mi.Location))))
  				return false;
  
  			var oldMouseOver = Ui.MouseOverWidget;
  
  			// Send the event to the deepest children first and bubble up if unhandled
  			foreach (var child in Children.OfType<Widget>().Reverse())
  				if (child.HandleMouseInputOuter(mi))
  					return true;
  
  			if (IgnoreChildMouseOver)
  				Ui.MouseOverWidget = oldMouseOver;
  
  			if (mi.Event == MouseInputEvent.Move && Ui.MouseOverWidget == null && !IgnoreMouseOver)
  				Ui.MouseOverWidget = this;
  
  			return HandleMouseInput(mi);
  		}
  
  		public virtual bool HandleKeyPress(KeyInput e) { return false; }
  
  		public virtual bool HandleKeyPressOuter(KeyInput e)
  		{
  			if (!IsVisible())
  				return false;
  
  			// Can any of our children handle this?
  			foreach (var child in Children.OfType<Widget>().Reverse())
  				if (child.HandleKeyPressOuter(e))
  					return true;
  
  			// Do any widgety behavior
  
gt: 			var handled = HandleKeyPress(e);