commit b4d47cc5cca230f75538bdb9acb2a6a9c13ad2e5
Author: Oto Šťáva <oto.stava@gmail.com>
Date:   Mon May 31 22:02:33 2021 +0200

    Re #9001 - Base launcher

diff --git a/deltarobot-vr-launcher/.gitignore b/deltarobot-vr-launcher/.gitignore
new file mode 100644
index 0000000..f838697
--- /dev/null
+++ b/deltarobot-vr-launcher/.gitignore
@@ -0,0 +1,4 @@
+obj/
+bin/
+
+.idea/
diff --git a/deltarobot-vr-launcher/DeltarobotVr.Launcher/DeltarobotVr.Launcher.csproj b/deltarobot-vr-launcher/DeltarobotVr.Launcher/DeltarobotVr.Launcher.csproj
new file mode 100644
index 0000000..59d9693
--- /dev/null
+++ b/deltarobot-vr-launcher/DeltarobotVr.Launcher/DeltarobotVr.Launcher.csproj
@@ -0,0 +1,57 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+    <PropertyGroup>
+        <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+        <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+        <ProjectGuid>{3B359CF2-C5D8-4466-983F-E0467CDB1144}</ProjectGuid>
+        <OutputType>Exe</OutputType>
+        <AppDesignerFolder>Properties</AppDesignerFolder>
+        <RootNamespace>DeltarobotVr.Launcher</RootNamespace>
+        <AssemblyName>DeltarobotVr.Launcher</AssemblyName>
+        <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
+        <FileAlignment>512</FileAlignment>
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+        <PlatformTarget>AnyCPU</PlatformTarget>
+        <DebugSymbols>true</DebugSymbols>
+        <DebugType>full</DebugType>
+        <Optimize>false</Optimize>
+        <OutputPath>bin\Debug\</OutputPath>
+        <DefineConstants>DEBUG;TRACE</DefineConstants>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+    </PropertyGroup>
+    <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+        <PlatformTarget>AnyCPU</PlatformTarget>
+        <DebugType>pdbonly</DebugType>
+        <Optimize>true</Optimize>
+        <OutputPath>bin\Release\</OutputPath>
+        <DefineConstants>TRACE</DefineConstants>
+        <ErrorReport>prompt</ErrorReport>
+        <WarningLevel>4</WarningLevel>
+    </PropertyGroup>
+    <ItemGroup>
+        <Reference Include="System" />
+        <Reference Include="System.Core" />
+        <Reference Include="System.Data" />
+        <Reference Include="System.Drawing" />
+        <Reference Include="System.Windows.Forms" />
+        <Reference Include="System.Xml" />
+    </ItemGroup>
+    <ItemGroup>
+        <Compile Include="LauncherForm.cs" />
+        <Compile Include="LayoutRectangle.cs" />
+        <Compile Include="Program.cs" />
+        <Compile Include="Properties\AssemblyInfo.cs" />
+    </ItemGroup>
+    <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+    <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+         Other similar extension points exist, see Microsoft.Common.targets.
+    <Target Name="BeforeBuild">
+    </Target>
+    <Target Name="AfterBuild">
+    </Target>
+    -->
+
+</Project>
diff --git a/deltarobot-vr-launcher/DeltarobotVr.Launcher/LauncherForm.cs b/deltarobot-vr-launcher/DeltarobotVr.Launcher/LauncherForm.cs
new file mode 100644
index 0000000..def63d0
--- /dev/null
+++ b/deltarobot-vr-launcher/DeltarobotVr.Launcher/LauncherForm.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace DeltarobotVr.Launcher
+{
+    public class LauncherForm : Form
+    {
+
+        private readonly Label _serverHostLabel = new Label();
+        private readonly TextBox _serverHost = new TextBox();
+
+        private readonly Label _serverPortLabel = new Label();
+        private readonly TextBox _serverPort = new TextBox();
+        
+        private readonly Button _launchButton = new Button();
+
+
+        private const int Spacing = 5;
+        private const int ControlHeight = 24;
+        private const int LabelWidth = 120;
+        
+        public LauncherForm()
+        {
+            Text = "Deltarobot VR Launcher";
+            
+            _serverHostLabel.Text = "Server host:";
+            _serverHostLabel.TextAlign = ContentAlignment.MiddleRight;
+            Controls.Add(_serverHostLabel);
+            
+            Controls.Add(_serverHost);
+
+            _serverPortLabel.Text = "Server port:";
+            _serverPortLabel.TextAlign = ContentAlignment.MiddleRight;
+            Controls.Add(_serverPortLabel);
+            
+            Controls.Add(_serverPort);
+            
+            _launchButton.Text = "Launch";
+            Controls.Add(_launchButton);
+        }
+
+        protected override void OnLoad(EventArgs e)
+        {
+            base.OnLoad(e);
+
+            Width = 640;
+            Height = 480;
+            
+            UpdateLayout();
+            CenterToScreen();
+        }
+
+        protected override void OnResize(EventArgs e)
+        {
+            base.OnResize(e);
+            
+            UpdateLayout();
+        }
+
+        private void UpdateLayout()
+        {
+            var lr = new LayoutRectangle(ClientRectangle);
+            lr.Shrink(10);
+
+            var hostRect = lr.RemoveFromTop(ControlHeight);
+            _serverHostLabel.Bounds = hostRect.RemoveFromLeft(LabelWidth).ToRectangle();
+            hostRect.RemoveFromLeft(Spacing);
+            _serverHost.Bounds = hostRect.RemoveFromLeft(250).ToRectangle();
+
+            lr.RemoveFromTop(Spacing);
+
+            var portRect = lr.RemoveFromTop(ControlHeight);
+            _serverPortLabel.Bounds = portRect.RemoveFromLeft(LabelWidth).ToRectangle();
+            portRect.RemoveFromLeft(Spacing);
+            _serverPort.Bounds = portRect.RemoveFromLeft(64).ToRectangle();
+            
+            lr.RemoveFromTop(2 * Spacing);
+            
+            _launchButton.Bounds = lr.RemoveFromBottom(ControlHeight * 2).Shrink(10).ToRectangle();
+        }
+    }
+}
\ No newline at end of file
diff --git a/deltarobot-vr-launcher/DeltarobotVr.Launcher/LayoutRectangle.cs b/deltarobot-vr-launcher/DeltarobotVr.Launcher/LayoutRectangle.cs
new file mode 100644
index 0000000..5cf51d2
--- /dev/null
+++ b/deltarobot-vr-launcher/DeltarobotVr.Launcher/LayoutRectangle.cs
@@ -0,0 +1,86 @@
+using System.Drawing;
+
+namespace DeltarobotVr.Launcher
+{
+    public struct LayoutRectangle
+    {
+        public int Top;
+        public int Right;
+        public int Bottom;
+        public int Left;
+
+        public int Width => Right - Left;
+        public int Height => Bottom - Top;
+
+        public LayoutRectangle(int top, int right, int bottom, int left)
+        {
+            Top = top;
+            Right = right;
+            Bottom = bottom;
+            Left = left;
+        }
+
+        public LayoutRectangle(Rectangle rect) : this(rect.Top, rect.Right, rect.Bottom, rect.Left)
+        {
+        }
+
+        public Rectangle ToRectangle() => new Rectangle(Left, Top, Right - Left, Bottom - Top);
+
+        public LayoutRectangle RemoveFromTop(int value)
+        {
+            if (value > Height)
+            {
+                value = Height;
+            }
+            
+            var result = new LayoutRectangle(Top, Right, Top + value, Left);
+            Top += value;
+            return result;
+        }
+
+        public LayoutRectangle RemoveFromRight(int value)
+        {
+            if (value > Width)
+            {
+                value = Width;
+            }
+            
+            var result = new LayoutRectangle(Top, Right, Bottom, Right - value);
+            Right -= value;
+            return result;
+        }
+
+        public LayoutRectangle RemoveFromBottom(int value)
+        {
+            if (value > Height)
+            {
+                value = Height;
+            }
+            
+            var result = new LayoutRectangle(Bottom - value, Right, Bottom, Left);
+            Bottom -= value;
+            return result;
+        }
+
+        public LayoutRectangle RemoveFromLeft(int value)
+        {
+            if (value > Width)
+            {
+                value = Width;
+            }
+            
+            var result = new LayoutRectangle(Top, Left + value, Bottom, Left);
+            Left += value;
+            return result;
+        }
+
+        public LayoutRectangle Shrink(int value)
+        {
+            Top += value;
+            Right -= value;
+            Bottom -= value;
+            Left += value;
+            return this;
+        }
+    }
+}
\ No newline at end of file
diff --git a/deltarobot-vr-launcher/DeltarobotVr.Launcher/Program.cs b/deltarobot-vr-launcher/DeltarobotVr.Launcher/Program.cs
new file mode 100644
index 0000000..094d56f
--- /dev/null
+++ b/deltarobot-vr-launcher/DeltarobotVr.Launcher/Program.cs
@@ -0,0 +1,12 @@
+﻿using System.Windows.Forms;
+
+namespace DeltarobotVr.Launcher
+{
+    internal class Program
+    {
+        public static void Main(string[] args)
+        {
+            Application.Run(new LauncherForm());
+        }
+    }
+}
\ No newline at end of file
diff --git a/deltarobot-vr-launcher/DeltarobotVr.Launcher/Properties/AssemblyInfo.cs b/deltarobot-vr-launcher/DeltarobotVr.Launcher/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..a3649ff
--- /dev/null
+++ b/deltarobot-vr-launcher/DeltarobotVr.Launcher/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+﻿using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("deltarobot_vr_launcher")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("deltarobot_vr_launcher")]
+[assembly: AssemblyCopyright("Copyright ©  2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("3B359CF2-C5D8-4466-983F-E0467CDB1144")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
\ No newline at end of file
diff --git a/deltarobot-vr-launcher/deltarobot-vr-launcher.sln b/deltarobot-vr-launcher/deltarobot-vr-launcher.sln
new file mode 100644
index 0000000..84ea88e
--- /dev/null
+++ b/deltarobot-vr-launcher/deltarobot-vr-launcher.sln
@@ -0,0 +1,16 @@
+﻿
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeltarobotVr.Launcher", "DeltarobotVr.Launcher\DeltarobotVr.Launcher.csproj", "{3B359CF2-C5D8-4466-983F-E0467CDB1144}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{3B359CF2-C5D8-4466-983F-E0467CDB1144}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3B359CF2-C5D8-4466-983F-E0467CDB1144}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3B359CF2-C5D8-4466-983F-E0467CDB1144}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3B359CF2-C5D8-4466-983F-E0467CDB1144}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+EndGlobal
