1
0
Fork 0
mirror of https://github.com/hawkeye-stan/msfs-popout-panel-manager.git synced 2024-10-16 06:00:45 +00:00

Version v4.0.0 Release

This commit is contained in:
hawkeye 2023-07-12 18:41:31 -04:00
parent bff0412386
commit 5d4d5dcac3
552 changed files with 10794 additions and 74126 deletions

4
.editorconfig Normal file
View file

@ -0,0 +1,4 @@
[*.cs]
# CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
dotnet_diagnostic.CS8618.severity = none

1
.gitignore vendored
View file

@ -8,6 +8,7 @@
*.userosscache *.userosscache
*.sln.docstates *.sln.docstates
*.backup *.backup
.nuget/
# Build results # Build results
[Dd]ebug/ [Dd]ebug/

View file

@ -1,160 +0,0 @@
#include <Joystick.h>
#include <NewEncoder.h>
// Rotary Encoder Inputs
#define CLK1 19
#define DT1 18
#define SW1 17
#define CLK2 2
#define DT2 3
#define SW2 4
#define VRx A0
#define VRy A1
#define JSW 8
// Rotatry encoder variables
int currentStateCLK1;
int lastStateCLK1;
int currentStateCLK2;
int lastStateCLK2;
String currentEvent = "";
String currentDir1 = "";
String currentDir2 = "";
boolean rotaryEncoderRotating1 = false;
boolean rotaryEncoderRotating2 = false;
unsigned long lastButton1Press = 0;
unsigned long lastButton2Press = 0;
Joystick joystick(VRx, VRy, JSW);
NewEncoder encoderLower(DT1, CLK1, -32768, 32767, 0, FULL_PULSE);
NewEncoder encoderUpper(DT2, CLK2, -32768, 32767, 0, FULL_PULSE);
int16_t prevEncoderValueLower;
int16_t prevEncoderValueUpper;
void setup() {
// Set encoder pins as inputs
pinMode(SW1, INPUT_PULLUP);
pinMode(SW2, INPUT_PULLUP);
// Setup joystick
joystick.initialize();
joystick.calibrate();
joystick.setSensivity(3);
// Setup Serial Monitor
Serial.begin(9600);
NewEncoder::EncoderState encoderState1;
NewEncoder::EncoderState encoderState2;
encoderLower.begin();
encoderLower.getState(encoderState1);
prevEncoderValueLower = encoderState1.currentValue;
encoderUpper.begin();
encoderUpper.getState(encoderState2);
prevEncoderValueUpper = encoderState2.currentValue;
}
void loop() {
int16_t currentEncoderValueLower;
int16_t currentEncoderValueUpper;
NewEncoder::EncoderState currentEncoderStateLower;
NewEncoder::EncoderState currentEncoderStateUpper;
// Read rotary encoder lower
if (encoderLower.getState(currentEncoderStateLower)) {
currentEncoderValueLower = currentEncoderStateLower.currentValue;
if (currentEncoderValueLower != prevEncoderValueLower) {
if(currentEncoderValueLower > prevEncoderValueLower){
Serial.println("EncoderLower:CW:" + String(currentEncoderValueLower - prevEncoderValueLower));
}
else{
Serial.println("EncoderLower:CCW:" + String(prevEncoderValueLower - currentEncoderValueLower));
}
prevEncoderValueLower = currentEncoderValueLower;
}
}
// Read rotary encoder upper
if (encoderUpper.getState(currentEncoderStateUpper)) {
currentEncoderValueUpper = currentEncoderStateUpper.currentValue;
if (currentEncoderValueUpper != prevEncoderValueUpper) {
if(currentEncoderValueUpper > prevEncoderValueUpper){
Serial.println("EncoderUpper:CW:" + String(currentEncoderValueUpper - prevEncoderValueUpper));
}
else{
Serial.println("EncoderUpper:CCW:" + String(prevEncoderValueUpper - currentEncoderValueUpper));
}
prevEncoderValueUpper = currentEncoderValueUpper;
}
}
// Read the rotary encoder button state
int btnState1 = digitalRead(SW1);
int btnState2 = digitalRead(SW2);
//If we detect LOW signal, button is pressed
if (btnState1 == LOW) {
//if 500ms have passed since last LOW pulse, it means that the
//button has been pressed, released and pressed again
if (millis() - lastButton1Press > 500) {
Serial.println("EncoderLower:SW");
}
// Remember last button press event
lastButton1Press = millis();
}
if (btnState2 == LOW) {
//if 500ms have passed since last LOW pulse, it means that the
//button has been pressed, released and pressed again
if (millis() - lastButton2Press > 500) {
Serial.println("EncoderUpper:SW");
}
// Remember last button press event
lastButton2Press = millis();
}
// Read joystick
if(joystick.isPressed())
{
Serial.println("Joystick:SW");
}
if(joystick.isReleased())
{
// left
if(joystick.isLeft())
{
Serial.println("Joystick:UP");
}
// right
if(joystick.isRight())
{
Serial.println("Joystick:DOWN");
}
// up
if(joystick.isUp())
{
Serial.println("Joystick:RIGHT");
}
// down
if(joystick.isDown())
{
Serial.println("Joystick:LEFT");
}
}
// slow down a bit
delay(200);
}

View file

@ -1,160 +0,0 @@
#include <Joystick.h>
#include <NewEncoder.h>
// Rotary Encoder Inputs
#define CLK1 19
#define DT1 18
#define SW1 17
#define CLK2 2
#define DT2 3
#define SW2 4
#define VRx A0
#define VRy A1
#define JSW 8
// Rotatry encoder variables
int currentStateCLK1;
int lastStateCLK1;
int currentStateCLK2;
int lastStateCLK2;
String currentEvent = "";
String currentDir1 = "";
String currentDir2 = "";
boolean rotaryEncoderRotating1 = false;
boolean rotaryEncoderRotating2 = false;
unsigned long lastButton1Press = 0;
unsigned long lastButton2Press = 0;
Joystick joystick(VRx, VRy, JSW);
NewEncoder encoderLower(DT1, CLK1, -32768, 32767, 0, FULL_PULSE);
NewEncoder encoderUpper(DT2, CLK2, -32768, 32767, 0, FULL_PULSE);
int16_t prevEncoderValueLower;
int16_t prevEncoderValueUpper;
void setup() {
// Set encoder pins as inputs
pinMode(SW1, INPUT_PULLUP);
pinMode(SW2, INPUT_PULLUP);
// Setup joystick
joystick.initialize();
joystick.calibrate();
joystick.setSensivity(3);
// Setup Serial Monitor
Serial.begin(9600);
NewEncoder::EncoderState encoderState1;
NewEncoder::EncoderState encoderState2;
encoderLower.begin();
encoderLower.getState(encoderState1);
prevEncoderValueLower = encoderState1.currentValue;
encoderUpper.begin();
encoderUpper.getState(encoderState2);
prevEncoderValueUpper = encoderState2.currentValue;
}
void loop() {
int16_t currentEncoderValueLower;
int16_t currentEncoderValueUpper;
NewEncoder::EncoderState currentEncoderStateLower;
NewEncoder::EncoderState currentEncoderStateUpper;
// Read rotary encoder lower
if (encoderLower.getState(currentEncoderStateLower)) {
currentEncoderValueLower = currentEncoderStateLower.currentValue;
if (currentEncoderValueLower != prevEncoderValueLower) {
if(currentEncoderValueLower > prevEncoderValueLower){
Serial.println("EncoderLower:CW:" + String(currentEncoderValueLower - prevEncoderValueLower));
}
else{
Serial.println("EncoderLower:CCW:" + String(prevEncoderValueLower - currentEncoderValueLower));
}
prevEncoderValueLower = currentEncoderValueLower;
}
}
// Read rotary encoder upper
if (encoderUpper.getState(currentEncoderStateUpper)) {
currentEncoderValueUpper = currentEncoderStateUpper.currentValue;
if (currentEncoderValueUpper != prevEncoderValueUpper) {
if(currentEncoderValueUpper > prevEncoderValueUpper){
Serial.println("EncoderUpper:CW:" + String(currentEncoderValueUpper - prevEncoderValueUpper));
}
else{
Serial.println("EncoderUpper:CCW:" + String(prevEncoderValueUpper - currentEncoderValueUpper));
}
prevEncoderValueUpper = currentEncoderValueUpper;
}
}
// Read the rotary encoder button state
int btnState1 = digitalRead(SW1);
int btnState2 = digitalRead(SW2);
//If we detect LOW signal, button is pressed
if (btnState1 == LOW) {
//if 500ms have passed since last LOW pulse, it means that the
//button has been pressed, released and pressed again
if (millis() - lastButton1Press > 500) {
Serial.println("EncoderLower:SW");
}
// Remember last button press event
lastButton1Press = millis();
}
if (btnState2 == LOW) {
//if 500ms have passed since last LOW pulse, it means that the
//button has been pressed, released and pressed again
if (millis() - lastButton2Press > 500) {
Serial.println("EncoderUpper:SW");
}
// Remember last button press event
lastButton2Press = millis();
}
// Read joystick
if(joystick.isPressed())
{
Serial.println("Joystick:SW");
}
if(joystick.isReleased())
{
// left
if(joystick.isLeft())
{
Serial.println("Joystick:LEFT");
}
// right
if(joystick.isRight())
{
Serial.println("Joystick:RIGHT");
}
// up
if(joystick.isUp())
{
Serial.println("Joystick:UP");
}
// down
if(joystick.isDown())
{
Serial.println("Joystick:DOWN");
}
}
// slow down a bit
delay(200);
}

View file

@ -1,197 +0,0 @@
#include <Joystick.h>
#include <NewEncoder.h>
#include <Keypad.h>
// Rotary Encoder Inputs
#define CLK1 19
#define DT1 18
#define SW1 17
#define CLK2 2
#define DT2 3
#define SW2 4
#define VRx A0
#define VRy A1
const byte ROWS = 4;
const byte COLS = 4;
char hexaKeys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {31, 33, 35, 37};
byte colPins[COLS] = {39, 41, 43, 45};
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
// Rotatry encoder variables
int currentStateCLK1;
int lastStateCLK1;
int currentStateCLK2;
int lastStateCLK2;
String currentEvent = "";
String currentDir1 = "";
String currentDir2 = "";
boolean rotaryEncoderRotating1 = false;
boolean rotaryEncoderRotating2 = false;
unsigned long lastButton1Press = 0;
unsigned long lastButton2Press = 0;
Joystick joystick(VRx, VRy, 8);
NewEncoder encoderLower(DT1, CLK1, -32768, 32767, 0, FULL_PULSE);
NewEncoder encoderUpper(DT2, CLK2, -32768, 32767, 0, FULL_PULSE);
int16_t prevEncoderValueLower;
int16_t prevEncoderValueUpper;
void setup() {
// Set encoder pins as inputs
pinMode(SW1, INPUT_PULLUP);
pinMode(SW2, INPUT_PULLUP);
// Setup joystick
joystick.initialize();
joystick.calibrate();
joystick.setSensivity(3);
// Setup Serial Monitor
Serial.begin(9600);
NewEncoder::EncoderState encoderState1;
NewEncoder::EncoderState encoderState2;
encoderLower.begin();
encoderLower.getState(encoderState1);
prevEncoderValueLower = encoderState1.currentValue;
encoderUpper.begin();
encoderUpper.getState(encoderState2);
prevEncoderValueUpper = encoderState2.currentValue;
}
void loop() {
int16_t currentEncoderValueLower;
int16_t currentEncoderValueUpper;
NewEncoder::EncoderState currentEncoderStateLower;
NewEncoder::EncoderState currentEncoderStateUpper;
// Read rotary encoder lower
if (encoderLower.getState(currentEncoderStateLower)) {
currentEncoderValueLower = currentEncoderStateLower.currentValue;
if (currentEncoderValueLower != prevEncoderValueLower) {
if(currentEncoderValueLower > prevEncoderValueLower){
Serial.println("EncoderLower:CW:" + String(currentEncoderValueLower - prevEncoderValueLower));
}
else{
Serial.println("EncoderLower:CCW:" + String(prevEncoderValueLower - currentEncoderValueLower));
}
prevEncoderValueLower = currentEncoderValueLower;
}
}
// Read rotary encoder upper
if (encoderUpper.getState(currentEncoderStateUpper)) {
currentEncoderValueUpper = currentEncoderStateUpper.currentValue;
if (currentEncoderValueUpper != prevEncoderValueUpper) {
if(currentEncoderValueUpper > prevEncoderValueUpper){
Serial.println("EncoderUpper:CW:" + String(currentEncoderValueUpper - prevEncoderValueUpper));
}
else{
Serial.println("EncoderUpper:CCW:" + String(prevEncoderValueUpper - currentEncoderValueUpper));
}
prevEncoderValueUpper = currentEncoderValueUpper;
}
}
// Read the rotary encoder button state
int btnState1 = digitalRead(SW1);
int btnState2 = digitalRead(SW2);
//If we detect LOW signal, button is pressed
if (btnState1 == LOW) {
//if 500ms have passed since last LOW pulse, it means that the
//button has been pressed, released and pressed again
if (millis() - lastButton1Press > 500) {
Serial.println("EncoderLower:SW");
}
// Remember last button press event
lastButton1Press = millis();
}
if (btnState2 == LOW) {
//if 500ms have passed since last LOW pulse, it means that the
//button has been pressed, released and pressed again
if (millis() - lastButton2Press > 500) {
Serial.println("EncoderUpper:SW");
}
// Remember last button press event
lastButton2Press = millis();
}
// Read joystick
if(joystick.isPressed())
{
Serial.println("Joystick:SW");
}
// Read joystick (updated for joystick mounted at 90 degree)
if(joystick.isReleased())
{
// left
if(joystick.isLeft())
{
//Serial.println("Joystick:LEFT");
Serial.println("Joystick:UP");
}
// right
if(joystick.isRight())
{
//Serial.println("Joystick:RIGHT");
Serial.println("Joystick:DOWN");
}
// up
if(joystick.isUp())
{
//Serial.println("Joystick:UP");
Serial.println("Joystick:RIGHT");
}
// down
if(joystick.isDown())
{
//Serial.println("Joystick:DOWN");
Serial.println("Joystick:LEFT");
}
}
// Read keypad
char customKey = customKeypad.getKey();
if (customKey){
if(customKey == '#')
{
Serial.println("Keypad:KeyPound");
}
else if(customKey == '*')
{
Serial.println("Keypad:KeyAsterisk");
}
else
{
Serial.println("Keypad:Key" + String(customKey));
}
}
// slow down a bit
delay(100);
}

View file

@ -1,37 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<AssemblyName>ArduinoAgent</AssemblyName>
<PackageId>MSFS 2020 Popout Panel Manager ArduinoAgent</PackageId>
<Product>MSFS 2020 Popout Panel Manager ArduinoAgent</Product>
<Authors>Stanley Kwok</Authors>
<Company>Stanley Kwok</Company>
<Copyright>Stanley Kwok 2021</Copyright>
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
<RootNamespace>MSFSPopoutPanelManager.ArduinoAgent</RootNamespace>
<Platforms>x64</Platforms>
<Version>3.4.6.0321</Version>
<AssemblyVersion>3.4.6.0321</AssemblyVersion>
<FileVersion>3.4.6.0321</FileVersion>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<DebugType>Embedded</DebugType>
<Configurations>Debug;Release;DebugTouchPanel;ReleaseTouchPanel</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugTouchPanel|x64'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.IO.Ports" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

View file

@ -1,62 +0,0 @@
using System;
namespace MSFSPopoutPanelManager.ArduinoAgent
{
public class ArduinoInputData
{
public ArduinoInputData(string inputName, string inputAction, int acceleration)
{
InputName = (InputName)Enum.Parse(typeof(InputName), inputName);
InputAction = (InputAction)Enum.Parse(typeof(InputAction), inputAction);
Acceleration = acceleration;
}
public InputName InputName { get; set; }
public InputAction InputAction { get; set; }
public int Acceleration { get; set; }
}
public enum InputAction
{
NONE,
// Rotary Encoder
CW,
CCW,
SW,
// Joystick
UP,
DOWN,
LEFT,
RIGHT,
// Keypad
Key1,
Key2,
Key3,
Key4,
Key5,
Key6,
Key7,
Key8,
Key9,
Key0,
KeyA,
KeyB,
KeyC,
KeyD,
KeyAsterisk,
KeyPound
}
public enum InputName
{
EncoderLower,
EncoderUpper,
Joystick,
Keypad
}
}

View file

@ -1,143 +0,0 @@
using MSFSPopoutPanelManager.Shared;
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Threading;
namespace MSFSPopoutPanelManager.ArduinoAgent
{
public class ArduinoProvider
{
private const string ARDUINO_COM_PORT = "COM3";
private const int ARDUINO_BAUD_RATE = 9600;
private SerialPort _serialPort;
public event EventHandler<bool> OnConnectionChanged;
public event EventHandler<ArduinoInputData> OnDataReceived;
public ArduinoProvider()
{
try
{
_serialPort = new SerialPort
{
PortName = ARDUINO_COM_PORT,
BaudRate = ARDUINO_BAUD_RATE
};
_serialPort.DataReceived += DataReceived;
}
catch (Exception ex)
{
FileLogger.WriteException($"Arduino Error: {ex.Message}", null);
}
}
public void Start()
{
if (!IsConnected)
{
try
{
_serialPort.Open();
OnConnectionChanged?.Invoke(this, true);
FileLogger.WriteLog($"Arduino connected.", StatusMessageType.Info);
}
catch (Exception ex)
{
FileLogger.WriteException($"Arduino Connection Error - {ex.Message}", ex);
}
}
}
public void Stop()
{
if (_serialPort.IsOpen)
{
try
{
_serialPort.Close();
FileLogger.WriteLog($"Arduino disconnected.", StatusMessageType.Info);
}
catch (Exception ex)
{
FileLogger.WriteException($"Arduino Connection Error - {ex.Message}", ex);
}
}
OnConnectionChanged?.Invoke(this, false);
}
public void SendToArduino(string data)
{
try
{
if (IsConnected)
_serialPort.WriteLine(data);
}
catch (Exception ex)
{
FileLogger.WriteException($"Arduino Connection Error - {ex.Message}", ex);
}
}
private void DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (_serialPort.IsOpen)
{
try
{
var dataEvents = new List<ArduinoInputData>();
int byteToRead = _serialPort.BytesToRead;
while (byteToRead > 0)
{
var message = _serialPort.ReadTo("\r\n");
var data = message.Split(":");
ArduinoInputData dataEvent;
// Calculate acceleration
if (data.Length == 3)
{
var accelerationValue = Convert.ToInt32(data[2]);
//dataEvent = new ArduinoInputData(data[0], data[1], accelerationValue == 1 ? 1 : accelerationValue / 2);
dataEvent = new ArduinoInputData(data[0], data[1], accelerationValue <= 2 ? 1 : accelerationValue);
}
else
{
dataEvent = new ArduinoInputData(data[0], data[1], 1);
}
dataEvents.Add(dataEvent);
byteToRead = _serialPort.BytesToRead;
}
foreach (var evt in dataEvents)
{
OnDataReceived?.Invoke(this, evt);
Thread.Sleep(10);
}
}
catch
{
_serialPort.DiscardInBuffer();
}
}
}
private bool IsConnected
{
get
{
if (_serialPort == null)
return false;
return _serialPort.IsOpen;
}
}
}
}

View file

@ -0,0 +1,19 @@
using MSFSPopoutPanelManager.DomainModel.Setting;
namespace MSFSPopoutPanelManager.DomainModel.DataFile
{
public class AppSetttingFile
{
public AppSetttingFile()
{
FileVersion = "4.0";
ApplicationSetting = new ApplicationSetting();
}
public string FileVersion { get; set; }
public ApplicationSetting ApplicationSetting { get; set; }
}
}

View file

@ -0,0 +1,18 @@
using MSFSPopoutPanelManager.DomainModel.Profile;
using System.Collections.Generic;
namespace MSFSPopoutPanelManager.DomainModel.DataFile
{
public class UserProfileFile
{
public UserProfileFile()
{
FileVersion = "4.0";
Profiles = new List<UserProfile>();
}
public string FileVersion { get; set; }
public IList<UserProfile> Profiles { get; set; }
}
}

View file

@ -0,0 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<AssemblyName>DomainModel</AssemblyName>
<PackageId>MSFS 2020 Popout Panel Manager Domain Model</PackageId>
<Product>MSFS 2020 Popout Panel Manager Domain Model</Product>
<Authors>Stanley Kwok</Authors>
<Company>Stanley Kwok</Company>
<Copyright>Stanley Kwok 2021</Copyright>
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
<RootNamespace>MSFSPopoutPanelManager.DomainModel</RootNamespace>
<Platforms>x64</Platforms>
<Version>4.0.0.0</Version>
<AssemblyVersion>4.0.0.0</AssemblyVersion>
<FileVersion>4.0.0.0</FileVersion>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<DebugType>Embedded</DebugType>
<Configurations>Debug;Release;Local</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseTouchPanel|x64'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugTouchPanel|x64'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\Shared.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fody" Version="6.7.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0" PrivateAssets="All" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,78 @@
using MSFSPopoutPanelManager.DomainModel.Setting;
namespace MSFSPopoutPanelManager.DomainModel.Legacy
{
public class LegacyAppSetting
{
public string AutoUpdaterUrl { get; set; }
public int LastUsedProfileId { get; set; }
public bool MinimizeToTray { get; set; }
public bool AlwaysOnTop { get; set; }
public bool AutoClose { get; set; }
public bool UseAutoPanning { get; set; }
public bool MinimizeAfterPopOut { get; set; }
public string AutoPanningKeyBinding { get; set; }
public bool StartMinimized { get; set; }
public bool AutoPopOutPanels { get; set; }
public bool AutoDisableTrackIR { get; set; }
public int OnScreenMessageDuration { get; set; }
public bool UseLeftRightControlToPopOut { get; set; }
public bool IsEnabledTouchPanelServer { get; set; }
public bool AutoResizeMsfsGameWindow { get; set; }
public LegacyAfterPopOutCameraView AfterPopOutCameraView { get; set; }
public LegacyTouchScreenSettings TouchScreenSettings { get; set; }
public LegacyTouchPanelSettings TouchPanelSettings { get; set; }
}
public class LegacyAfterPopOutCameraView
{
public bool EnableReturnToCameraView { get; set; }
public AfterPopOutCameraViewType CameraView { get; set; }
public string CustomCameraKeyBinding { get; set; }
}
public class LegacyTouchScreenSettings
{
public int TouchDownUpDelay { get; set; }
public bool RefocusGameWindow { get; set; }
public int RefocusGameWindowDelay { get; set; }
public bool RealSimGearGTN750Gen1Override { get; set; }
}
public class LegacyTouchPanelSettings
{
public bool EnableTouchPanelIntegration { get; set; }
public bool AutoStart { get; set; }
public int DataRefreshInterval { get; set; }
public int MapRefreshInterval { get; set; }
public bool UseArduino { get; set; }
public bool EnableSound { get; set; }
}
}

View file

@ -0,0 +1,84 @@
using MSFSPopoutPanelManager.DomainModel.Profile;
using System.Collections.ObjectModel;
namespace MSFSPopoutPanelManager.DomainModel.Legacy
{
public class LegacyProfile
{
public int ProfileId { get; set; }
public string ProfileName { get; set; }
public ObservableCollection<string> BindingAircrafts { get; set; }
public ObservableCollection<LegacyPanelSourceCoordinate> PanelSourceCoordinates { get; set; }
public ObservableCollection<LegacyPanelConfig> PanelConfigs { get; set; }
public ObservableCollection<LegacyTouchPanelBinding> TouchPanelBindings { get; set; }
public bool IsLocked { get; set; }
public bool PowerOnRequiredForColdStart { get; set; }
public bool IncludeInGamePanels { get; set; }
public bool RealSimGearGTN750Gen1Override { get; set; }
public LegacyMsfsGameWindowConfig MsfsGameWindowConfig { get; set; }
}
public class LegacyPanelSourceCoordinate
{
public int PanelIndex { get; set; }
public int X { get; set; }
public int Y { get; set; }
}
public class LegacyPanelConfig
{
public int PanelIndex { get; set; }
public string PanelName { get; set; }
public PanelType PanelType { get; set; }
public int Top { get; set; }
public int Left { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public bool AlwaysOnTop { get; set; }
public bool HideTitlebar { get; set; }
public bool FullScreen { get; set; }
public bool TouchEnabled { get; set; }
public bool DisableGameRefocus { get; set; }
}
public class LegacyTouchPanelBinding
{
public string PlaneId { get; set; }
public string PanelId { get; set; }
}
public class LegacyMsfsGameWindowConfig
{
public int Top { get; set; }
public int Left { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
}

View file

@ -0,0 +1,24 @@
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.Profile
{
public class HudBarConfig : ObservableObject
{
public HudBarConfig()
{
IsEnabled = false;
HudBarType = HudBarType.Generic_Aircraft;
}
public bool IsEnabled { get; set; }
public HudBarType HudBarType { get; set; }
}
public enum HudBarType
{
None = 0, // not selectable
Generic_Aircraft = 1,
PMDG_737 = 2
}
}

View file

@ -0,0 +1,35 @@
using MSFSPopoutPanelManager.Shared;
using Newtonsoft.Json;
namespace MSFSPopoutPanelManager.DomainModel.Profile
{
public class MsfsGameWindowConfig : ObservableObject
{
public MsfsGameWindowConfig()
{
Top = 0;
Left = 0;
Width = 0;
Height = 0;
}
public MsfsGameWindowConfig(int left, int top, int width, int height)
{
Top = top;
Left = left;
Width = width;
Height = height;
}
public int Top { get; set; }
public int Left { get; set; }
public int Width { get; set; }
public int Height { get; set; }
[JsonIgnore]
public bool IsValid => Width != 0 && Height != 0;
}
}

View file

@ -0,0 +1,99 @@
using MSFSPopoutPanelManager.Shared;
using Newtonsoft.Json;
using System;
namespace MSFSPopoutPanelManager.DomainModel.Profile
{
public class PanelConfig : ObservableObject
{
public PanelConfig()
{
if (Id == Guid.Empty)
Id = Guid.NewGuid();
PanelName = "Default Panel Name";
PanelHandle = IntPtr.MaxValue;
AutoGameRefocus = true;
PanelSource = new PanelSource();
InitializeChildPropertyChangeBinding();
PropertyChanged += PanelConfig_PropertyChanged;
}
private void PanelConfig_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "FullScreen" && FullScreen)
{
AlwaysOnTop = false;
HideTitlebar = false;
}
else if (e.PropertyName == "TouchEnabled" && TouchEnabled)
{
AutoGameRefocus = true;
}
}
public Guid Id { get; set; }
public PanelType PanelType { get; set; }
public string PanelName { get; set; }
public int Top { get; set; }
public int Left { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public bool AlwaysOnTop { get; set; }
public bool HideTitlebar { get; set; }
public bool FullScreen { get; set; }
public bool TouchEnabled { get; set; }
public bool AutoGameRefocus { get; set; }
public PanelSource PanelSource { get; set; }
[JsonIgnore]
public IntPtr PanelHandle { get; set; }
[JsonIgnore]
public bool IsEditingPanel { get; set; }
[JsonIgnore]
public bool IsCustomPopOut => PanelType == PanelType.CustomPopout;
[JsonIgnore]
public bool IsBuiltInPopOut => PanelType == PanelType.BuiltInPopout;
[JsonIgnore]
public bool HasPanelSource => PanelType == PanelType.BuiltInPopout || (PanelType == PanelType.CustomPopout && PanelSource != null && PanelSource.X != null);
[JsonIgnore]
public bool? IsPopOutSuccess
{
get
{
if (PanelHandle == IntPtr.MaxValue)
return null;
if (PanelHandle == IntPtr.Zero)
return false;
return true;
}
}
[JsonIgnore]
public bool IsSelectedPanelSource { get; set; }
[JsonIgnore]
public bool IsShownPanelSource { get; set; }
}
}

View file

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MSFSPopoutPanelManager.DomainModel.Profile
{
public class PanelConfigColors
{
public static string GetNextAvailableColor(List<PanelConfig> panelConfigs)
{
foreach (string colorName in Enum.GetNames<Colors>())
{
if (panelConfigs.Any(p => p.PanelSource.Color == colorName))
continue;
return colorName;
}
return "White";
}
}
public enum Colors
{
LimeGreen,
Red,
Blue,
Fuchsia,
Orange,
Yellow,
Cyan,
Ivory,
Pink,
Indigo,
Purple,
Crimson
}
}

View file

@ -1,6 +1,6 @@
using System; using System;
namespace MSFSPopoutPanelManager.Shared namespace MSFSPopoutPanelManager.DomainModel.Profile
{ {
public class PanelConfigItem public class PanelConfigItem
{ {
@ -25,9 +25,7 @@ namespace MSFSPopoutPanelManager.Shared
HideTitlebar, HideTitlebar,
FullScreen, FullScreen,
TouchEnabled, TouchEnabled,
DisableGameRefocus, AutoGameRefocus,
RowHeader, None
None,
Invalid,
} }
} }

View file

@ -0,0 +1,18 @@
using MSFSPopoutPanelManager.Shared;
using Newtonsoft.Json;
using System;
namespace MSFSPopoutPanelManager.DomainModel.Profile
{
public class PanelSource : ObservableObject
{
public int? X { get; set; }
public int? Y { get; set; }
public string Color { get; set; }
[JsonIgnore]
public IntPtr PanelHandle { get; set; }
}
}

View file

@ -0,0 +1,15 @@
namespace MSFSPopoutPanelManager.DomainModel.Profile
{
public enum PanelType
{
PopOutManager = 0,
BuiltInPopout = 1,
CustomPopout = 2,
FlightSimMainWindow = 3,
HudBarWindow = 4,
MultiMonitorWindow = 5,
PanelSourceWindow = 6,
StatusMessageWindow = 7,
Unknown = 100
}
}

View file

@ -0,0 +1,20 @@
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.Profile
{
public class ProfileSetting : ObservableObject
{
public ProfileSetting()
{
HudBarConfig = new HudBarConfig();
InitializeChildPropertyChangeBinding();
}
public bool PowerOnRequiredForColdStart { get; set; }
public bool IncludeInGamePanels { get; set; }
public HudBarConfig HudBarConfig { get; set; }
}
}

View file

@ -0,0 +1,125 @@
using MSFSPopoutPanelManager.Shared;
using Newtonsoft.Json;
using PropertyChanged;
using System;
using System.Collections.ObjectModel;
using System.Linq;
namespace MSFSPopoutPanelManager.DomainModel.Profile
{
[SuppressPropertyChangedWarnings]
public class UserProfile : ObservableObject, IComparable<UserProfile>
{
public UserProfile()
{
Id = Guid.NewGuid();
IsLocked = false;
AircraftBindings = new ObservableCollection<string>();
PanelConfigs = new ObservableCollection<PanelConfig>();
ProfileSetting = new ProfileSetting();
MsfsGameWindowConfig = new MsfsGameWindowConfig();
this.PropertyChanged += (sender, e) =>
{
var evtArg = e as PropertyChangedExtendedEventArgs;
if (!evtArg.DisableSave)
ProfileChanged?.Invoke(this, null);
};
PanelConfigs.CollectionChanged += (sender, e) =>
{
switch (e.Action)
{
case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
if (e.NewItems[0] == null)
return;
((PanelConfig)e.NewItems[0]).PropertyChanged += (sender, e) =>
{
var evtArg = e as PropertyChangedExtendedEventArgs;
if (!evtArg.DisableSave)
ProfileChanged?.Invoke(this, null);
OnPanelConfigChanged(sender, e);
};
ProfileChanged?.Invoke(this, null);
OnPanelConfigChanged(sender, e);
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
ProfileChanged?.Invoke(this, null);
OnPanelConfigChanged(sender, e);
break;
}
};
InitializeChildPropertyChangeBinding();
}
public event EventHandler ProfileChanged;
public Guid Id { get; set; }
public string Name { get; set; }
public bool IsLocked { get; set; }
public ObservableCollection<string> AircraftBindings { get; set; }
public ObservableCollection<PanelConfig> PanelConfigs { get; set; }
public ProfileSetting ProfileSetting { get; set; }
public MsfsGameWindowConfig MsfsGameWindowConfig { get; set; }
public int CompareTo(UserProfile other)
{
int result = this.Name.ToLower().CompareTo(other.Name.ToLower());
if (result == 0)
result = this.Name.ToLower().CompareTo(other.Name.ToLower());
return result;
}
[JsonIgnore]
public bool IsActive { get; set; }
[JsonIgnore]
public bool IsEditingPanelSource { get; set; }
private bool _isPoppedOut;
[JsonIgnore]
public bool IsPoppedOut
{
get { return _isPoppedOut; }
set
{
_isPoppedOut = value;
if (!value)
{
foreach (var panelConfig in PanelConfigs)
panelConfig.PanelHandle = IntPtr.MaxValue; // reset panel is popped out status
}
}
}
[JsonIgnore]
public Guid CurrentMoveResizePanelId { get; set; }
[JsonIgnore]
public bool HasUnidentifiedPanelSource { get; private set; }
[JsonIgnore]
public bool HasAircraftBindings => AircraftBindings != null && AircraftBindings.Count > 0;
[JsonIgnore]
public bool HasCustomPanels => PanelConfigs.Count(p => p.PanelType == PanelType.CustomPopout) > 0;
private void OnPanelConfigChanged(object sender, EventArgs e)
{
HasUnidentifiedPanelSource = PanelConfigs.Any(p => p.PanelType == PanelType.CustomPopout && p.PanelSource.X == null);
}
}
}

View file

@ -0,0 +1,32 @@
using MSFSPopoutPanelManager.Shared;
using Newtonsoft.Json;
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
public class AfterPopOutCameraView : ObservableObject
{
public AfterPopOutCameraView()
{
// Default values
IsEnabled = true;
CameraView = AfterPopOutCameraViewType.CockpitCenterView;
KeyBinding = "1";
}
public bool IsEnabled { get; set; }
public AfterPopOutCameraViewType CameraView { get; set; }
public string KeyBinding { get; set; }
// Use for MVVM binding only
[JsonIgnore]
public bool IsEnabledCustomCameraKeyBinding { get { return IsEnabled && CameraView == AfterPopOutCameraViewType.CustomCameraView; } }
}
public enum AfterPopOutCameraViewType
{
CockpitCenterView,
CustomCameraView
}
}

View file

@ -5,7 +5,7 @@ using System.Text;
using System.Xml; using System.Xml;
using System.Xml.Serialization; using System.Xml.Serialization;
namespace MSFSPopoutPanelManager.UserDataAgent namespace MSFSPopoutPanelManager.DomainModel.Setting
{ {
internal class AppAutoStart internal class AppAutoStart
{ {

View file

@ -0,0 +1,37 @@
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
public class ApplicationSetting : ObservableObject
{
public ApplicationSetting()
{
GeneralSetting = new GeneralSetting();
AutoPopOutSetting = new AutoPopOutSetting();
PopOutSetting = new PopOutSetting();
RefocusSetting = new RefocusSetting();
TouchSetting = new TouchSetting();
TrackIRSetting = new TrackIRSetting();
WindowedModeSetting = new WindowedModeSetting();
SystemSetting = new SystemSetting();
InitializeChildPropertyChangeBinding();
}
public GeneralSetting GeneralSetting { get; set; }
public AutoPopOutSetting AutoPopOutSetting { get; set; }
public PopOutSetting PopOutSetting { get; set; }
public RefocusSetting RefocusSetting { get; set; }
public TouchSetting TouchSetting { get; set; }
public TrackIRSetting TrackIRSetting { get; set; }
public WindowedModeSetting WindowedModeSetting { get; set; }
public SystemSetting SystemSetting { get; set; }
}
}

View file

@ -0,0 +1,17 @@
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
public class AutoPanning : ObservableObject
{
public AutoPanning()
{
IsEnabled = true;
KeyBinding = "0";
}
public bool IsEnabled { get; set; }
public string KeyBinding { get; set; }
}
}

View file

@ -0,0 +1,17 @@
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
public class AutoPopOutSetting : ObservableObject
{
public AutoPopOutSetting()
{
IsEnabled = true;
ReadyToFlyDelay = 3;
}
public bool IsEnabled { get; set; }
public int ReadyToFlyDelay { get; set; }
}
}

View file

@ -0,0 +1,45 @@
using MSFSPopoutPanelManager.Shared;
using Newtonsoft.Json;
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
public class GeneralSetting : ObservableObject
{
public GeneralSetting()
{
AlwaysOnTop = true;
MinimizeToTray = false;
StartMinimized = false;
AutoClose = true;
CheckForUpdate = true;
InitializeChildPropertyChangeBinding();
}
public bool AlwaysOnTop { get; set; }
public bool AutoClose { get; set; }
public bool MinimizeToTray { get; set; }
public bool StartMinimized { get; set; }
public bool CheckForUpdate { get; set; }
[JsonIgnore, IgnorePropertyChanged]
public bool AutoStart
{
get
{
return AppAutoStart.CheckIsAutoStart();
}
set
{
if (value)
AppAutoStart.Activate();
else
AppAutoStart.Deactivate();
}
}
}
}

View file

@ -0,0 +1,38 @@
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
public class PopOutSetting : ObservableObject
{
public PopOutSetting()
{
MinimizeDuringPopOut = true;
MinimizeAfterPopOut = false;
UseLeftRightControlToPopOut = false;
AutoActivePause = false;
AfterPopOutCameraView = new AfterPopOutCameraView();
AutoPanning = new AutoPanning();
PopOutTitleBarCustomization = new PopOutTitleBarCustomization();
EnablePanelResetWhenLocked = true;
InitializeChildPropertyChangeBinding();
}
public AutoPanning AutoPanning { get; set; }
public AfterPopOutCameraView AfterPopOutCameraView { get; set; }
public bool MinimizeDuringPopOut { get; set; }
public bool MinimizeAfterPopOut { get; set; }
public bool UseLeftRightControlToPopOut { get; set; }
public bool EnablePanelResetWhenLocked { get; set; }
public bool AutoActivePause { get; set; }
public PopOutTitleBarCustomization PopOutTitleBarCustomization { get; set; }
};
}

View file

@ -0,0 +1,15 @@
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
public class PopOutTitleBarCustomization
{
public PopOutTitleBarCustomization()
{
IsEnabled = false;
HexColor = "000000";
}
public bool IsEnabled { get; set; }
public string HexColor { get; set; }
}
}

View file

@ -0,0 +1,17 @@
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
public class RefocusGameWindow : ObservableObject
{
public RefocusGameWindow()
{
IsEnabled = true;
Delay = 1;
}
public bool IsEnabled { get; set; }
public double Delay { get; set; }
}
}

View file

@ -0,0 +1,16 @@
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
public class RefocusSetting : ObservableObject
{
public RefocusSetting()
{
RefocusGameWindow = new RefocusGameWindow();
InitializeChildPropertyChangeBinding();
}
public RefocusGameWindow RefocusGameWindow { get; set; }
}
}

View file

@ -0,0 +1,18 @@
using MSFSPopoutPanelManager.Shared;
using System;
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
public class SystemSetting : ObservableObject
{
public SystemSetting()
{
LastUsedProfileId = Guid.Empty;
AutoUpdaterUrl = "https://raw.githubusercontent.com/hawkeye-stan/msfs-popout-panel-manager/master/autoupdate.xml";
}
public string AutoUpdaterUrl { get; set; }
public Guid LastUsedProfileId { get; set; }
}
}

View file

@ -0,0 +1,14 @@
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
public class TouchSetting : ObservableObject
{
public TouchSetting()
{
TouchDownUpDelay = 0;
}
public int TouchDownUpDelay { get; set; }
}
}

View file

@ -0,0 +1,14 @@
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
public class TrackIRSetting : ObservableObject
{
public TrackIRSetting()
{
AutoDisableTrackIR = true;
}
public bool AutoDisableTrackIR { get; set; }
}
}

View file

@ -0,0 +1,14 @@
using MSFSPopoutPanelManager.Shared;
namespace MSFSPopoutPanelManager.DomainModel.Setting
{
public class WindowedModeSetting : ObservableObject
{
public WindowedModeSetting()
{
AutoResizeMsfsGameWindow = true;
}
public bool AutoResizeMsfsGameWindow { get; set; }
}
}

View file

@ -0,0 +1,94 @@
using MSFSPopoutPanelManager.DomainModel.Profile;
using MSFSPopoutPanelManager.Shared;
using System;
using System.Reflection;
namespace MSFSPopoutPanelManager.DomainModel.SimConnect
{
public class HudBarData737 : ObservableObject, IHudBarData
{
public HudBarType HudBarType => HudBarType.PMDG_737;
public double ElevatorTrim { get; set; }
public double AileronTrim { get; set; }
public double RudderTrim { get; set; }
public double Flap { get; set; }
public double ParkingBrake { get; set; }
public double GearLeft { get; set; }
public double GearCenter { get; set; }
public double GearRight { get; set; }
public double SimRate { get; set; }
public string ElevatorTrimFormatted => Math.Round(ElevatorTrim / 10, 2).ToString("F2");
public string AileronTrimFormatted => Math.Round(AileronTrim / 16384 * 57, 2).ToString("F2");
public string RudderTrimFormatted => Math.Round(RudderTrim * 2 * 10, 2).ToString("F2");
public string FlapFormatted => MapFlap(Flap);
public string ParkingBrakeFormatted => ParkingBrake > 0 ? "Engaged" : "Disengaged";
public string GearLeftFormatted => MapGear(GearLeft);
public string GearCenterFormatted => MapGear(GearCenter);
public string GearRightFormatted => MapGear(GearRight);
private string MapFlap(double flap)
{
switch (Convert.ToInt32(flap))
{
case 0:
return "0";
case 1:
return "1";
case 2:
return "2";
case 3:
return "5";
case 4:
return "10";
case 5:
return "15";
case 6:
return "25";
case 7:
return "30";
case 8:
return "40";
default:
return "5";
}
}
private string MapGear(double gear)
{
if (gear == 100)
return "DOWN";
if (gear == 0)
return "UP";
return "MOVING";
}
public void Clear()
{
Type type = this.GetType();
PropertyInfo[] properties = type.GetProperties();
for (int i = 0; i < properties.Length; ++i)
{
if (properties[i].SetMethod != null)
properties[i].SetValue(this, 0);
}
}
}
}

View file

@ -0,0 +1,67 @@
using MSFSPopoutPanelManager.DomainModel.Profile;
using MSFSPopoutPanelManager.Shared;
using System;
using System.Reflection;
namespace MSFSPopoutPanelManager.DomainModel.SimConnect
{
public class HudBarDataGeneric : ObservableObject, IHudBarData
{
public HudBarType HudBarType => HudBarType.Generic_Aircraft;
public double ElevatorTrim { get; set; }
public double AileronTrim { get; set; }
public double RudderTrim { get; set; }
public double Flap { get; set; }
public double ParkingBrake { get; set; }
public double GearLeft { get; set; }
public double GearCenter { get; set; }
public double GearRight { get; set; }
public double SimRate { get; set; }
public string ElevatorTrimFormatted => Math.Round(ElevatorTrim / 10, 2).ToString("F2");
public string AileronTrimFormatted => Math.Round(AileronTrim / 16384 * 57, 2).ToString("F2");
public string RudderTrimFormatted => Math.Round(RudderTrim * 2 * 10, 2).ToString("F2");
public string FlapFormatted => Flap.ToString("F0");
public string ParkingBrakeFormatted => ParkingBrake > 0 ? "Engaged" : "Disengaged";
public string GearLeftFormatted => MapGear(GearLeft);
public string GearCenterFormatted => MapGear(GearCenter);
public string GearRightFormatted => MapGear(GearRight);
private string MapGear(double gear)
{
if (gear == 100)
return "DOWN";
if (gear == 0)
return "UP";
return "MOVING";
}
public void Clear()
{
Type type = this.GetType();
PropertyInfo[] properties = type.GetProperties();
for (int i = 0; i < properties.Length; ++i)
{
if (properties[i].SetMethod != null)
properties[i].SetValue(this, 0);
}
}
}
}

View file

@ -0,0 +1,45 @@
using MSFSPopoutPanelManager.DomainModel.Profile;
namespace MSFSPopoutPanelManager.DomainModel.SimConnect
{
public interface IHudBarData
{
public HudBarType HudBarType { get; }
public double ElevatorTrim { get; set; }
public double AileronTrim { get; set; }
public double RudderTrim { get; set; }
public double Flap { get; set; }
public double ParkingBrake { get; set; }
public double GearLeft { get; set; }
public double GearCenter { get; set; }
public double GearRight { get; set; }
public double SimRate { get; set; }
public string ParkingBrakeFormatted { get; }
public string GearLeftFormatted { get; }
public string GearCenterFormatted { get; }
public string GearRightFormatted { get; }
public string ElevatorTrimFormatted { get; }
public string AileronTrimFormatted { get; }
public string RudderTrimFormatted { get; }
public string FlapFormatted { get; }
public void Clear();
}
}

117
GETTING_STARTED.md Normal file
View file

@ -0,0 +1,117 @@
# GETTING STARTED
In this tutorial, you'll be adding 3 pop out panels for the aircraft TBM 930. Two of the panels is display only (PFD and MFD) and the third is a touch screen pop out (GTC 580).
To get started, launch MSFS Pop Out Panel Manager and then start MSFS. When you are at the main menu select TBM 930 as your aircraft. You do not have to start a flight session yet. Back to Pop Out Panel Manager, you should see the following screen and you can click the Add Profile button to add your first aircraft profile.
<br />
<p align="center">
<img src="assets/readme/images/gettingstarted1.png" width="900" hspace="10"/>
</p>
<br />
<hr>
<br />
Type the name for the new aircraft profile "TBM 930" and click Accept.
<br />
<p align="center">
<img src="assets/readme/images/gettingstarted2.png" width="900" hspace="10"/>
</p>
<br />
<hr>
<br />
Since your current in game aircraft is TBM 930, once the new profile is added, your selected active in-game aircraft will automatically bind this profile (in green color). This binding will allow Pop Out Panel Manager to automatically switch to this profile when you switch to TBM 930 aicraft in the game. Only one profile can be bound to an in-game aircraft at a time and you can unbind aircraft at any time. Next click the Add Pop Out Panel button 3 times to add 3 panels to this profile.
<br />
<p align="center">
<img src="assets/readme/images/gettingstarted3.png" width="900" hspace="10"/>
</p>
<br />
<hr>
<br />
Once the 3 panels are added, enter a descriptive name for each of the 3 panels. When you're still in the game's main menu, Identify Source Panel Location button for the 3 panels will not be available. You will now need to start a flight with the TBM 930 aircraft. Please go to world map, pick an airport and start a flight. Once you're in a flight, the Identify Source Panel Location button will be enable.
<br />
<p align="center">
<img src="assets/readme/images/gettingstarted4.png" width="900" hspace="10"/>
</p>
<br />
<hr>
<br />
Next, click the Identify Source Panel Location button one at a time. After each click of the button, left click on the game screen at the corresponding instrumentation panel in game to each of the pop out panel definition. A circle will appear to match the pop out panel circle color. If you need to adjust the color circle placement, just left click and drag the circle to new location. You can also zoom in and out of the cockpit view using your mouse wheel so you can see more in-game panels at a time.
<br />
<p align="center">
<img src="assets/readme/images/gettingstarted5.png" width="900" hspace="10"/>
</p>
<br />
<hr>
<br />
Once you're satified with the color circles placement, click the Toggle Edit Source Panel Location button (the green target icon). This will end the source panel configuration and will save the current camera angle and your game window size (if using Windowed mode) to be recalled later to be used when popping out panels. The Start Pop Out button should now be enabled. Click this button and start the pop out process. If Pop Out Panel Manager app window is overlapping your selected instrumentation panels, don't worry, the app will relocate itself when simulating the in-game pop out keystrokes. You should also see a progress window indicating Pop Out Manager is in process of configuring and popping out panels.
<br />
<p align="center">
<img src="assets/readme/images/gettingstarted6.png" width="900" hspace="10"/>
</p>
<br />
<hr>
<br />
Once the process has been completed successfully, you should see all 3 panels appear at the upper left corner of the game screen. All 3 pop out panels definition will now have green border showing everything is working. You can then configure the panel by:
* Changing the top, left, width, and height by entering a number
* Use the up/down arrow next to each property and a pop out box will allow you to change the property by 1 or 10 pixels at a time.
* For the fastest way, click the Move and Resize Panel icon, this will allow you to use keyboard shortcuts to adjust the panel. Please see help section of the app for keyboard commands (click the question mark icon at the upper right corner of the app).
In this tutorial, since the 3rd panel is a touch enable panel (GTC 580). You can click the hand icon for this panel to activate touch support.
Once you've completed all panel configurations, please click the Lock icon to lock this profile from accidental changes.
<br />
<br />
<p align="center">
<img src="assets/readme/images/gettingstarted7.png" width="900" hspace="10"/>
</p>
<br />
<hr>
<br />
If you encounter any error during pop out, the panel with error will have a red border. You can click the Red Exclaimation mark icon for help to resolve the error. In this tutorial, the problem is the source panel blue circle for this particular pop out is not within the in-game instrumentation panel area. To fix this, click the Toggle Edit Source Panel button, and then use your mouse to drag and move the blue circle to a location within the GTC 580 panel. Finally, click the Toggle Edit Source Panel button again to save the changes. Now click Start Pop Out and the error should be resolved.
Tip #1: If you have trouble finding the number circle on screen or figuring out the color circle you're dragging corresponding to which pop out panel, you can left click and hold either the pop out panel source target icon or the color circle itself and find the corresponding pop out panel or color circle.
Tip #2: Hovering your mouse on any input element may provide tool tip to further explain the function of each icon or action.
Tip #3: Sometimes, you may encounter issue in MSFS that certain panel does not pop out correctly. You can try changing the order in how Pop Out Panel Manager launching the pop out by reording the pop out panel definition using mosue drag and drop. You can click and drag to arrange pop out panel definitions to new sequence.
<br />
<p align="center">
<img src="assets/readme/images/gettingstarted8.png" width="900" hspace="10"/>
</p>
<br />
<hr>
<br />
An additional feature for Pop Out Panel Manager is you can add and manage built-in panels from the toolbar such as Checklist, ATC, Weather panel. One use case is you can place a panel such as ATC on a touch screen, configure the panel to be touch enabled, and operate this panel using touch.
To do this, first check the Include in-game menu bar panels option for the profile. The built-in panels themselves have to be opened manually and popped out first. Currently, Pop Out Panel Manager do not have a way to control built-in panel. Luckily, MSFS will remember these built-in panels opened and popped out state. As long as you don't close them during your flight session, they will be reopened and popped out the next time when you start a flight.
In this tutorial, when you have ATC built-in panel opened and popped out, click Start Pop Out button. A new entry for ATC is then added to the profile. You can configure its size and location and touch capability like any other pop out panel.
<br />
<p align="center">
<img src="assets/readme/images/gettingstarted9.png" width="900" hspace="10"/>
</p>

View file

@ -3,118 +3,144 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.2.32602.215 VisualStudioVersion = 17.2.32602.215
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfApp", "WpfApp\WpfApp.csproj", "{54712A0A-B344-45E4-85C4-0A913305A0E6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{15FC98CD-0A69-437B-A5E5-67D025DB5CDC}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{15FC98CD-0A69-437B-A5E5-67D025DB5CDC}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore .gitignore = .gitignore
autoupdate.xml = autoupdate.xml autoupdate.xml = autoupdate.xml
latestreleasenotes.txt = latestreleasenotes.txt GETTING_STARTED.md = GETTING_STARTED.md
LICENSE = LICENSE LICENSE = LICENSE
README.md = README.md README.md = README.md
RELEASENOTES.md = RELEASENOTES.md
rollback.xml = rollback.xml
VERSION.md = VERSION.md VERSION.md = VERSION.md
EndProjectSection EndProjectSection
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsAgent", "WindowsAgent\WindowsAgent.csproj", "{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsAgent", "WindowsAgent\WindowsAgent.csproj", "{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArduinoAgent", "ArduinoAgent\ArduinoAgent.csproj", "{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimconnectAgent", "SimconnectAgent\SimconnectAgent.csproj", "{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimconnectAgent", "SimconnectAgent\SimconnectAgent.csproj", "{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebServer", "WebServer\WebServer.csproj", "{D20EA590-22C2-4F93-AC25-AF6960F97E03}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserDataAgent", "UserDataAgent\UserDataAgent.csproj", "{677B6DF0-8E19-4B08-8480-F7C365B6BB89}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orchestration", "Orchestration\Orchestration.csproj", "{9CD4014F-B3EA-4E67-9329-9679931E2688}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Orchestration", "Orchestration\Orchestration.csproj", "{9CD4014F-B3EA-4E67-9329-9679931E2688}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TouchPanelAgent", "TouchPanelAgent\TouchPanelAgent.csproj", "{2F36A227-C92C-4B42-B1E4-480015492357}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MainApp", "MainApp\MainApp.csproj", "{5EEA1D18-3036-4A9F-876C-45DE2AC9F247}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DomainModel", "DomainModel\DomainModel.csproj", "{602CA4F9-B6AE-4500-A52F-082D47CB1F9A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{143369CD-7A52-43A0-9364-41A46A645B05}"
ProjectSection(SolutionItems) = preProject
assets\logo.png = assets\logo.png
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "readme", "readme", "{71539661-D87F-46E3-82F2-2608B5AF74BA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "images", "images", "{FF7DD7EF-43C5-4E7C-8283-1087F51935F9}"
ProjectSection(SolutionItems) = preProject
assets\readme\images\app.png = assets\readme\images\app.png
assets\readme\images\gettingstarted1.png = assets\readme\images\gettingstarted1.png
assets\readme\images\gettingstarted2.png = assets\readme\images\gettingstarted2.png
assets\readme\images\gettingstarted3.png = assets\readme\images\gettingstarted3.png
assets\readme\images\gettingstarted4.png = assets\readme\images\gettingstarted4.png
assets\readme\images\gettingstarted5.png = assets\readme\images\gettingstarted5.png
assets\readme\images\gettingstarted6.png = assets\readme\images\gettingstarted6.png
assets\readme\images\gettingstarted7.png = assets\readme\images\gettingstarted7.png
assets\readme\images\gettingstarted8.png = assets\readme\images\gettingstarted8.png
assets\readme\images\gettingstarted9.png = assets\readme\images\gettingstarted9.png
assets\readme\images\rollback.png = assets\readme\images\rollback.png
EndProjectSection
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64 Debug|x64 = Debug|x64
DebugTouchPanel|x64 = DebugTouchPanel|x64 Local|Any CPU = Local|Any CPU
Local|x64 = Local|x64
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64 Release|x64 = Release|x64
ReleaseTouchPanel|x64 = ReleaseTouchPanel|x64
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Debug|x64.ActiveCfg = Debug|x64 {4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Debug|Any CPU.ActiveCfg = Debug|x64
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Debug|x64.Build.0 = Debug|x64 {4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Debug|Any CPU.Build.0 = Debug|x64
{54712A0A-B344-45E4-85C4-0A913305A0E6}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64
{54712A0A-B344-45E4-85C4-0A913305A0E6}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|x64
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Release|x64.ActiveCfg = Release|x64
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Release|x64.Build.0 = Release|x64
{54712A0A-B344-45E4-85C4-0A913305A0E6}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64
{54712A0A-B344-45E4-85C4-0A913305A0E6}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Debug|x64.ActiveCfg = Debug|x64 {4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Debug|x64.ActiveCfg = Debug|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Debug|x64.Build.0 = Debug|x64 {4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Debug|x64.Build.0 = Debug|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64 {4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Local|Any CPU.ActiveCfg = Local|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|x64 {4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Local|Any CPU.Build.0 = Local|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Local|x64.ActiveCfg = Local|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Local|x64.Build.0 = Local|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Release|Any CPU.ActiveCfg = Release|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Release|Any CPU.Build.0 = Release|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Release|x64.ActiveCfg = Release|x64 {4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Release|x64.ActiveCfg = Release|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Release|x64.Build.0 = Release|x64 {4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Release|x64.Build.0 = Release|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64 {BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Debug|Any CPU.ActiveCfg = Debug|x64
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64 {BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Debug|Any CPU.Build.0 = Debug|x64
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Debug|x64.ActiveCfg = Debug|x64 {BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Debug|x64.ActiveCfg = Debug|x64
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Debug|x64.Build.0 = Debug|x64 {BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Debug|x64.Build.0 = Debug|x64
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64 {BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Local|Any CPU.ActiveCfg = Local|x64
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|x64 {BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Local|Any CPU.Build.0 = Local|x64
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Release|x64.ActiveCfg = Release|x64 {BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Local|x64.ActiveCfg = Local|x64
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Release|x64.Build.0 = Release|x64 {BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Local|x64.Build.0 = Local|x64
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64 {BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Release|Any CPU.ActiveCfg = Release|x64
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64 {BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Release|Any CPU.Build.0 = Release|x64
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.Debug|x64.ActiveCfg = Debug|x64 {BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Release|x64.ActiveCfg = Debug|x64
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.Debug|x64.Build.0 = Debug|x64 {BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Release|x64.Build.0 = Debug|x64
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64 {1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Debug|Any CPU.ActiveCfg = Debug|x64
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|x64 {1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Debug|Any CPU.Build.0 = Debug|x64
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.Release|x64.ActiveCfg = Release|x64
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.Release|x64.Build.0 = Release|x64
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Debug|x64.ActiveCfg = Debug|x64 {1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Debug|x64.ActiveCfg = Debug|x64
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Debug|x64.Build.0 = Debug|x64 {1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Debug|x64.Build.0 = Debug|x64
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64 {1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Local|Any CPU.ActiveCfg = Local|x64
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|x64 {1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Local|Any CPU.Build.0 = Local|x64
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Local|x64.ActiveCfg = Local|x64
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Local|x64.Build.0 = Local|x64
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Release|Any CPU.ActiveCfg = Release|x64
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Release|Any CPU.Build.0 = Release|x64
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Release|x64.ActiveCfg = Release|x64 {1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Release|x64.ActiveCfg = Release|x64
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Release|x64.Build.0 = Release|x64 {1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Release|x64.Build.0 = Release|x64
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64 {9CD4014F-B3EA-4E67-9329-9679931E2688}.Debug|Any CPU.ActiveCfg = Debug|x64
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64 {9CD4014F-B3EA-4E67-9329-9679931E2688}.Debug|Any CPU.Build.0 = Debug|x64
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.Debug|x64.ActiveCfg = Debug|x64
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.Debug|x64.Build.0 = Debug|x64
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|x64
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.Release|x64.ActiveCfg = Release|x64
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.Release|x64.Build.0 = Release|x64
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64
{677B6DF0-8E19-4B08-8480-F7C365B6BB89}.Debug|x64.ActiveCfg = Debug|x64
{677B6DF0-8E19-4B08-8480-F7C365B6BB89}.Debug|x64.Build.0 = Debug|x64
{677B6DF0-8E19-4B08-8480-F7C365B6BB89}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64
{677B6DF0-8E19-4B08-8480-F7C365B6BB89}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|x64
{677B6DF0-8E19-4B08-8480-F7C365B6BB89}.Release|x64.ActiveCfg = Release|x64
{677B6DF0-8E19-4B08-8480-F7C365B6BB89}.Release|x64.Build.0 = Release|x64
{677B6DF0-8E19-4B08-8480-F7C365B6BB89}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64
{677B6DF0-8E19-4B08-8480-F7C365B6BB89}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64
{9CD4014F-B3EA-4E67-9329-9679931E2688}.Debug|x64.ActiveCfg = Debug|x64 {9CD4014F-B3EA-4E67-9329-9679931E2688}.Debug|x64.ActiveCfg = Debug|x64
{9CD4014F-B3EA-4E67-9329-9679931E2688}.Debug|x64.Build.0 = Debug|x64 {9CD4014F-B3EA-4E67-9329-9679931E2688}.Debug|x64.Build.0 = Debug|x64
{9CD4014F-B3EA-4E67-9329-9679931E2688}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64 {9CD4014F-B3EA-4E67-9329-9679931E2688}.Local|Any CPU.ActiveCfg = Local|x64
{9CD4014F-B3EA-4E67-9329-9679931E2688}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|x64 {9CD4014F-B3EA-4E67-9329-9679931E2688}.Local|Any CPU.Build.0 = Local|x64
{9CD4014F-B3EA-4E67-9329-9679931E2688}.Local|x64.ActiveCfg = Local|x64
{9CD4014F-B3EA-4E67-9329-9679931E2688}.Local|x64.Build.0 = Local|x64
{9CD4014F-B3EA-4E67-9329-9679931E2688}.Release|Any CPU.ActiveCfg = Release|x64
{9CD4014F-B3EA-4E67-9329-9679931E2688}.Release|Any CPU.Build.0 = Release|x64
{9CD4014F-B3EA-4E67-9329-9679931E2688}.Release|x64.ActiveCfg = Release|x64 {9CD4014F-B3EA-4E67-9329-9679931E2688}.Release|x64.ActiveCfg = Release|x64
{9CD4014F-B3EA-4E67-9329-9679931E2688}.Release|x64.Build.0 = Release|x64 {9CD4014F-B3EA-4E67-9329-9679931E2688}.Release|x64.Build.0 = Release|x64
{9CD4014F-B3EA-4E67-9329-9679931E2688}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64 {5EEA1D18-3036-4A9F-876C-45DE2AC9F247}.Debug|Any CPU.ActiveCfg = Debug|x64
{9CD4014F-B3EA-4E67-9329-9679931E2688}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64 {5EEA1D18-3036-4A9F-876C-45DE2AC9F247}.Debug|Any CPU.Build.0 = Debug|x64
{2F36A227-C92C-4B42-B1E4-480015492357}.Debug|x64.ActiveCfg = Debug|x64 {5EEA1D18-3036-4A9F-876C-45DE2AC9F247}.Debug|x64.ActiveCfg = Debug|x64
{2F36A227-C92C-4B42-B1E4-480015492357}.Debug|x64.Build.0 = Debug|x64 {5EEA1D18-3036-4A9F-876C-45DE2AC9F247}.Debug|x64.Build.0 = Debug|x64
{2F36A227-C92C-4B42-B1E4-480015492357}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64 {5EEA1D18-3036-4A9F-876C-45DE2AC9F247}.Local|Any CPU.ActiveCfg = Release|x64
{2F36A227-C92C-4B42-B1E4-480015492357}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|x64 {5EEA1D18-3036-4A9F-876C-45DE2AC9F247}.Local|Any CPU.Build.0 = Release|x64
{2F36A227-C92C-4B42-B1E4-480015492357}.Release|x64.ActiveCfg = Release|x64 {5EEA1D18-3036-4A9F-876C-45DE2AC9F247}.Local|x64.ActiveCfg = Release|x64
{2F36A227-C92C-4B42-B1E4-480015492357}.Release|x64.Build.0 = Release|x64 {5EEA1D18-3036-4A9F-876C-45DE2AC9F247}.Local|x64.Build.0 = Release|x64
{2F36A227-C92C-4B42-B1E4-480015492357}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64 {5EEA1D18-3036-4A9F-876C-45DE2AC9F247}.Release|Any CPU.ActiveCfg = Release|x64
{2F36A227-C92C-4B42-B1E4-480015492357}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64 {5EEA1D18-3036-4A9F-876C-45DE2AC9F247}.Release|Any CPU.Build.0 = Release|x64
{5EEA1D18-3036-4A9F-876C-45DE2AC9F247}.Release|x64.ActiveCfg = Release|x64
{5EEA1D18-3036-4A9F-876C-45DE2AC9F247}.Release|x64.Build.0 = Release|x64
{602CA4F9-B6AE-4500-A52F-082D47CB1F9A}.Debug|Any CPU.ActiveCfg = Debug|x64
{602CA4F9-B6AE-4500-A52F-082D47CB1F9A}.Debug|Any CPU.Build.0 = Debug|x64
{602CA4F9-B6AE-4500-A52F-082D47CB1F9A}.Debug|x64.ActiveCfg = Debug|x64
{602CA4F9-B6AE-4500-A52F-082D47CB1F9A}.Debug|x64.Build.0 = Debug|x64
{602CA4F9-B6AE-4500-A52F-082D47CB1F9A}.Local|Any CPU.ActiveCfg = Local|x64
{602CA4F9-B6AE-4500-A52F-082D47CB1F9A}.Local|Any CPU.Build.0 = Local|x64
{602CA4F9-B6AE-4500-A52F-082D47CB1F9A}.Local|x64.ActiveCfg = Local|x64
{602CA4F9-B6AE-4500-A52F-082D47CB1F9A}.Local|x64.Build.0 = Local|x64
{602CA4F9-B6AE-4500-A52F-082D47CB1F9A}.Release|Any CPU.ActiveCfg = Release|x64
{602CA4F9-B6AE-4500-A52F-082D47CB1F9A}.Release|Any CPU.Build.0 = Release|x64
{602CA4F9-B6AE-4500-A52F-082D47CB1F9A}.Release|x64.ActiveCfg = Release|x64
{602CA4F9-B6AE-4500-A52F-082D47CB1F9A}.Release|x64.Build.0 = Release|x64
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{143369CD-7A52-43A0-9364-41A46A645B05} = {15FC98CD-0A69-437B-A5E5-67D025DB5CDC}
{71539661-D87F-46E3-82F2-2608B5AF74BA} = {143369CD-7A52-43A0-9364-41A46A645B05}
{FF7DD7EF-43C5-4E7C-8283-1087F51935F9} = {71539661-D87F-46E3-82F2-2608B5AF74BA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D1607553-61E8-4ED6-B382-ABA6F6944586} SolutionGuid = {D1607553-61E8-4ED6-B382-ABA6F6944586}
EndGlobalSection EndGlobalSection

21
MainApp/App.xaml Normal file
View file

@ -0,0 +1,21 @@
<Application
x:Class="MSFSPopoutPanelManager.MainApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<materialDesign:BundledTheme
BaseTheme="Dark"
PrimaryColor="BlueGrey"
SecondaryColor="Blue" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.PopupBox.xaml" />
<ResourceDictionary Source="CustomControl/themes/NumericUpDown.xaml" />
<ResourceDictionary Source="Styles/CustomScrollViewStyle.xaml" />
<ResourceDictionary x:Name="CustomMaterialDesignExpander" Source="Styles/CustomMaterialDesignExpander.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View file

@ -1,27 +1,81 @@
using MSFSPopoutPanelManager.Shared; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MSFSPopoutPanelManager.MainApp.ViewModel;
using MSFSPopoutPanelManager.Orchestration;
using MSFSPopoutPanelManager.Shared;
using MSFSPopoutPanelManager.WindowsAgent; using MSFSPopoutPanelManager.WindowsAgent;
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Threading;
namespace MSFSPopoutPanelManager.WpfApp namespace MSFSPopoutPanelManager.MainApp
{ {
public partial class App : Application public partial class App : Application
{ {
private static Mutex _mutex = null; public static IHost AppHost { get; private set; }
protected override void OnStartup(StartupEventArgs e) public App()
{
}
protected override async void OnStartup(StartupEventArgs e)
{ {
// Override default WPF System DPI Awareness to Per Monitor Awareness
// For this to work make sure [assembly:dpiawareness]
DpiAwareness.Enable(); DpiAwareness.Enable();
// Must run this first
Initialize();
// Setup dependency injections
AppHost = Host.CreateDefaultBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<AppWindow>();
services.AddSingleton<MainOrchestrator>();
services.AddSingleton<OrchestratorUIHelper>(s => new OrchestratorUIHelper(s.GetRequiredService<MainOrchestrator>()));
services.AddSingleton<ApplicationViewModel>(s => new ApplicationViewModel(s.GetRequiredService<MainOrchestrator>()));
services.AddSingleton<HelpViewModel>(s => new HelpViewModel(s.GetRequiredService<MainOrchestrator>()));
services.AddSingleton<ProfileCardListViewModel>(s => new ProfileCardListViewModel(s.GetRequiredService<MainOrchestrator>()));
services.AddSingleton<ProfileCardViewModel>(s => new ProfileCardViewModel(s.GetRequiredService<MainOrchestrator>()));
services.AddSingleton<TrayIconViewModel>(s => new TrayIconViewModel(s.GetRequiredService<MainOrchestrator>()));
services.AddTransient<AddProfileViewModel>(s => new AddProfileViewModel(s.GetRequiredService<MainOrchestrator>()));
services.AddTransient<PopOutPanelListViewModel>(s => new PopOutPanelListViewModel(s.GetRequiredService<MainOrchestrator>()));
services.AddTransient<PopOutPanelCardViewModel>(s => new PopOutPanelCardViewModel(s.GetRequiredService<MainOrchestrator>()));
services.AddTransient<PanelConfigFieldViewModel>(s => new PanelConfigFieldViewModel(s.GetRequiredService<MainOrchestrator>()));
services.AddTransient<PanelCoorOverlayViewModel>(s => new PanelCoorOverlayViewModel(s.GetRequiredService<MainOrchestrator>()));
services.AddTransient<MessageWindowViewModel>(s => new MessageWindowViewModel(s.GetRequiredService<MainOrchestrator>()));
services.AddTransient<HudBarViewModel>(s => new HudBarViewModel(s.GetRequiredService<MainOrchestrator>()));
}).Build();
await AppHost!.StartAsync();
// Startup window (must come after DPI setup above)
MainWindow = AppHost.Services.GetRequiredService<AppWindow>();
MainWindow.Show();
base.OnStartup(e);
// Setup orchestration UI handler
var orchestrationUIHelper = App.AppHost.Services.GetRequiredService<OrchestratorUIHelper>();
// Setup message window dialog
var messageWindow = new MessageWindow();
messageWindow.Show();
}
private void Initialize()
{
const string appName = "MSFS PopOut Panel Manager"; const string appName = "MSFS PopOut Panel Manager";
bool createdNew; bool createdNew;
_mutex = new Mutex(true, appName, out createdNew); var mutex = new Mutex(true, appName, out createdNew);
if (!createdNew) if (!createdNew)
{ {
@ -35,23 +89,19 @@ namespace MSFSPopoutPanelManager.WpfApp
TaskScheduler.UnobservedTaskException += HandleTaskSchedulerUnobservedTaskException; TaskScheduler.UnobservedTaskException += HandleTaskSchedulerUnobservedTaskException;
AppDomain.CurrentDomain.UnhandledException += HandledDomainException; AppDomain.CurrentDomain.UnhandledException += HandledDomainException;
} }
base.OnStartup(e);
} }
private void HandleTaskSchedulerUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) private void HandleTaskSchedulerUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{ {
FileLogger.WriteException(e.Exception.Message, e.Exception); FileLogger.WriteException(e.Exception.Message, e.Exception);
//ShowExceptionDialog();
} }
private void HandleDispatcherException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) private void HandleDispatcherException(object sender, DispatcherUnhandledExceptionEventArgs e)
{ {
e.Handled = true; e.Handled = true;
if (e.Exception.Message != "E_INVALIDARG") // Ignore this error if (e.Exception.Message != "E_INVALIDARG") // Ignore this error
{ {
FileLogger.WriteException(e.Exception.Message, e.Exception); FileLogger.WriteException(e.Exception.Message, e.Exception);
//ShowExceptionDialog();
} }
} }
@ -59,23 +109,6 @@ namespace MSFSPopoutPanelManager.WpfApp
{ {
var exception = (Exception)e.ExceptionObject; var exception = (Exception)e.ExceptionObject;
FileLogger.WriteException(exception.Message, exception); FileLogger.WriteException(exception.Message, exception);
//ShowExceptionDialog();
}
private void ShowExceptionDialog()
{
var messageBoxTitle = "MSFS Pop Out Panel Manager - Critical Error!";
var messageBoxMessage = "Application has encountered a critical error and will be closed.\nPlease see the file 'error.log' for information.";
var messageBoxButtons = MessageBoxButton.OK;
if (MessageBox.Show(messageBoxMessage, messageBoxTitle, messageBoxButtons) == MessageBoxResult.OK)
{
try
{
Application.Current.Shutdown();
}
catch { }
}
} }
} }

162
MainApp/AppWindow.xaml Normal file
View file

@ -0,0 +1,162 @@
<Window
x:Class="MSFSPopoutPanelManager.MainApp.AppWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.MainApp"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:viewmodel="clr-namespace:MSFSPopoutPanelManager.MainApp.ViewModel"
Title="MSFS POP OUT PANEL MANAGER"
Width="1000"
Height="600"
AllowsTransparency="True"
Background="Transparent"
Icon="logo.ico"
ResizeMode="NoResize"
Style="{StaticResource MaterialDesignWindow}"
WindowState="{Binding InitialWindowState, Mode=OneWay}"
WindowStyle="None"
mc:Ignorable="d">
<Window.Resources>
<system:Double x:Key="IconSize">28</system:Double>
</Window.Resources>
<Border
Margin="0"
Background="{StaticResource MaterialDesignDarkBackground}"
CornerRadius="15">
<Grid d:DataContext="{d:DesignInstance viewmodel:ApplicationViewModel}">
<local:TrayIcon x:Name="SystemTrayIcon" />
<materialDesign:DialogHost
Background="Transparent"
DialogTheme="Inherit"
Identifier="RootDialog">
<materialDesign:DrawerHost OverlayBackground="Transparent" RightDrawerCornerRadius="15">
<materialDesign:DrawerHost.RightDrawerContent>
<DockPanel>
<Button
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Margin="8,8,12,0"
HorizontalAlignment="Right"
Command="{x:Static materialDesign:DrawerHost.CloseDrawerCommand}"
DockPanel.Dock="Top"
Style="{StaticResource MaterialDesignIconButton}">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="ArrowRight" />
</Button>
<StackPanel Name="panelPreferenceDrawer" />
</DockPanel>
</materialDesign:DrawerHost.RightDrawerContent>
<DockPanel>
<materialDesign:ColorZone
Padding="12,4,12,4"
materialDesign:ElevationAssist.Elevation="Dp4"
CornerRadius="15,15,0,0"
DockPanel.Dock="Top"
Mode="PrimaryDark">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="0,0,0,0">
<Button
Margin="0"
Padding="0"
HorizontalAlignment="Left"
IsHitTestVisible="false">
<Button.Style>
<Style BasedOn="{StaticResource MaterialDesignFlatButton}" TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding FlightSimData.IsSimulatorStarted}" Value="True">
<Setter Property="Foreground" Value="LightGreen" />
<Setter Property="Content" Value="{materialDesign:PackIcon Kind=AccessPointNetwork, Size={StaticResource IconSize}}" />
</DataTrigger>
<DataTrigger Binding="{Binding FlightSimData.IsSimulatorStarted}" Value="False">
<Setter Property="Foreground" Value="Red" />
<Setter Property="Content" Value="{materialDesign:PackIcon Kind=AccessPointNetworkOff, Size={StaticResource IconSize}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
<StackPanel Grid.Column="1" Width="Auto">
<TextBlock
Margin="0,4,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="White"
Style="{StaticResource MaterialDesignHeadline6TextBlock}"
Text="MSFS POP OUT PANEL MANAGER" />
</StackPanel>
<StackPanel
Grid.Column="2"
Margin="0,0,0,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Margin="0,0,0,0"
Click="SettingsButton_Click"
Command="{x:Static materialDesign:DrawerHost.OpenDrawerCommand}"
Foreground="White"
Style="{StaticResource MaterialDesignIconButton}"
ToolTip="Preferences">
<materialDesign:PackIcon Kind="Cog" />
</Button>
<Button
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Margin="10,0,0,0"
Click="HelpButton_Click"
Command="{x:Static materialDesign:DrawerHost.OpenDrawerCommand}"
Foreground="White"
Style="{StaticResource MaterialDesignIconButton}"
ToolTip="Help">
<materialDesign:PackIcon Kind="Help" />
</Button>
<Button
x:Name="BtnMinimize"
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Margin="10,0,0,0"
Click="BtnMinimize_Click"
Foreground="White"
Style="{StaticResource MaterialDesignIconButton}"
ToolTip="Minimize application">
<materialDesign:PackIcon Kind="WindowMinimize" />
</Button>
<Button
x:Name="BtnClose"
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Margin="10,0,0,0"
Click="BtnClose_Click"
Foreground="White"
Style="{StaticResource MaterialDesignIconButton}"
ToolTip="Close application">
<materialDesign:PackIcon Kind="Close" />
</Button>
</StackPanel>
</Grid>
</materialDesign:ColorZone>
<local:ProfileCardList
x:Name="ProfileCardList"
Width="1024"
Margin="0,8,0,8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
DockPanel.Dock="Top" />
</DockPanel>
</materialDesign:DrawerHost>
</materialDesign:DialogHost>
</Grid>
</Border>
</Window>

133
MainApp/AppWindow.xaml.cs Normal file
View file

@ -0,0 +1,133 @@
using Microsoft.Extensions.DependencyInjection;
using MSFSPopoutPanelManager.DomainModel.Profile;
using MSFSPopoutPanelManager.MainApp.ViewModel;
using MSFSPopoutPanelManager.WindowsAgent;
using Prism.Commands;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Interop;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class AppWindow : Window
{
private ApplicationViewModel _viewModel;
// This command has to be here since it doesn't work in view model, window StateChanged never gets fire
public DelegateCommand RestoreWindowCommand => new DelegateCommand(() => { this.WindowState = WindowState.Normal; }, () => { return true; });
public AppWindow()
{
_viewModel = App.AppHost.Services.GetRequiredService<ApplicationViewModel>();
InitializeComponent();
Loaded += AppWindow_Loaded;
Closing += AppWindow_Closing;
StateChanged += AppWindow_StateChanged;
WindowActionManager.OnPopOutManagerAlwaysOnTopChanged += (sender, e) => { this.Topmost = e; };
this.MouseLeftButtonDown += (sender, e) => DragMove();
}
private void AppWindow_Loaded(object sender, RoutedEventArgs e)
{
_viewModel.ApplicationHandle = new WindowInteropHelper(Window.GetWindow(this)).Handle;
_viewModel.ApplicationWindow = Application.Current.MainWindow;
_viewModel.Initialize();
this.DataContext = _viewModel;
// Try to fix always on to click through. This won't work for app's title bar since mouseEnter won't be triggered.
// This is super tricky to trick POPM process is MSFS process to avoid Windows OS focus stealing
this.MouseEnter += (sender, e) =>WindowActionManager.SetWindowFocus(WindowProcessManager.GetApplicationProcess().Handle);
}
private void AppWindow_Closing(object? sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
_viewModel.WindowClosing();
}
private void AppWindow_StateChanged(object? sender, EventArgs e)
{
switch (this.WindowState)
{
case WindowState.Maximized:
this.ShowInTaskbar = true;
break;
case WindowState.Minimized:
if (_viewModel.AppSettingData.ApplicationSetting.GeneralSetting.MinimizeToTray)
{
SystemTrayIcon.Tray.Visibility = Visibility.Visible;
this.ShowInTaskbar = false;
}
break;
case WindowState.Normal:
SystemTrayIcon.Tray.Visibility = Visibility.Hidden;
this.ShowInTaskbar = true;
// Fix always on top status once app is minimize and then restore
if (_viewModel.AppSettingData.ApplicationSetting.GeneralSetting.AlwaysOnTop)
WindowActionManager.ApplyAlwaysOnTop(_viewModel.ApplicationHandle, PanelType.PopOutManager, true);
break;
}
}
private void SettingsButton_Click(object sender, RoutedEventArgs e)
{
this.panelPreferenceDrawer.Children.Clear();
this.panelPreferenceDrawer.Children.Add(new PreferenceDrawer());
}
private void HelpButton_Click(object sender, RoutedEventArgs e)
{
this.panelPreferenceDrawer.Children.Clear();
this.panelPreferenceDrawer.Children.Add(new HelpDrawer());
}
private void BtnMinimize_Click(object sender, RoutedEventArgs e)
{
this.WindowState = WindowState.Minimized;
}
private void BtnClose_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
//private void _viewModel_OnStatusMessageUpdated(object sender, (List<Run>, int) e)
//{
// var (messages, duration) = e;
// if (messages == null)
// return;
// TextBlockMessage.Inlines.Clear();
// foreach (var run in messages)
// TextBlockMessage.Inlines.Add(run);
// var lastRun = TextBlockMessage.Inlines.LastInline;
// if (lastRun != null && ((Run)lastRun).Text == Environment.NewLine)
// TextBlockMessage.Inlines.Remove(lastRun);
// ScrollViewerMessageBar.ScrollToEnd();
// if (duration != -1)
// {
// Task.Run(() =>
// {
// Thread.Sleep(duration * 1000);
// Application.Current.Dispatcher.Invoke(() =>
// {
// TextBlockMessage.Inlines.Clear();
// });
// });
// }
//}
}
}

View file

@ -10,3 +10,4 @@ using System.Windows.Media;
//(used if a resource is not found in the page, //(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries) // app, or any theme specific resource dictionaries)
)] )]

View file

@ -0,0 +1,28 @@
using System;
using System.Windows.Data;
namespace MSFSPopoutPanelManager.MainApp
{
public class InverseBooleanOrConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values.LongLength > 0)
{
foreach (var value in values)
{
if (value is bool && (bool)value)
{
return false;
}
}
}
return true;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return new object[] { false };
}
}
}

View file

@ -0,0 +1,20 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace MSFSPopoutPanelManager.MainApp.CustomControl
{
public class FontSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double v = (double)value;
return v * 0.6;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,216 @@
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace MSFSPopoutPanelManager.MainApp.CustomControl
{
[TemplatePart(Name = "PART_TextBox", Type = typeof(TextBox))]
[TemplatePart(Name = "PART_ButtonUp", Type = typeof(ButtonBase))]
[TemplatePart(Name = "PART_ButtonDown", Type = typeof(ButtonBase))]
public class NumericUpDown : Control
{
private TextBox PART_TextBox = new TextBox();
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
TextBox? textBox = GetTemplateChild("PART_TextBox") as TextBox;
if (textBox != null)
{
PART_TextBox = textBox;
PART_TextBox.PreviewKeyDown += textBox_PreviewKeyDown;
PART_TextBox.TextChanged += textBox_TextChanged;
PART_TextBox.Text = Value.ToString();
}
ButtonBase? PART_ButtonUp = GetTemplateChild("PART_ButtonUp") as ButtonBase;
if (PART_ButtonUp != null)
{
PART_ButtonUp.Click += buttonUp_Click;
}
ButtonBase? PART_ButtonDown = GetTemplateChild("PART_ButtonDown") as ButtonBase;
if (PART_ButtonDown != null)
{
PART_ButtonDown.Click += buttonDown_Click;
}
}
static NumericUpDown()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
"ValueChanged", RoutingStrategy.Direct,
typeof(ValueChangedEventHandler), typeof(NumericUpDown));
public event ValueChangedEventHandler ValueChanged
{
add
{
base.AddHandler(NumericUpDown.ValueChangedEvent, value);
}
remove
{
base.RemoveHandler(NumericUpDown.ValueChangedEvent, value);
}
}
public int Places
{
get { return (int)GetValue(PlacesProperty); }
set { SetValue(PlacesProperty, value); }
}
public static readonly DependencyProperty PlacesProperty = DependencyProperty.Register("Places", typeof(int), typeof(NumericUpDown));
public double MaxValue
{
get { return (double)GetValue(MaxValueProperty); }
set { SetValue(MaxValueProperty, value); }
}
public static readonly DependencyProperty MaxValueProperty =
DependencyProperty.Register("MaxValue", typeof(double), typeof(NumericUpDown), new FrameworkPropertyMetadata(100D, maxValueChangedCallback, coerceMaxValueCallback));
private static object coerceMaxValueCallback(DependencyObject d, object value)
{
double minValue = ((NumericUpDown)d).MinValue;
if ((double)value < minValue)
return minValue;
return value;
}
private static void maxValueChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
NumericUpDown numericUpDown = ((NumericUpDown)d);
numericUpDown.CoerceValue(MinValueProperty);
numericUpDown.CoerceValue(ValueProperty);
}
public double MinValue
{
get { return (double)GetValue(MinValueProperty); }
set { SetValue(MinValueProperty, value); }
}
public static readonly DependencyProperty MinValueProperty =
DependencyProperty.Register("MinValue", typeof(double), typeof(NumericUpDown), new FrameworkPropertyMetadata(0D, minValueChangedCallback, coerceMinValueCallback));
private static object coerceMinValueCallback(DependencyObject d, object value)
{
double maxValue = ((NumericUpDown)d).MaxValue;
if ((double)value > maxValue)
return maxValue;
return value;
}
private static void minValueChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
NumericUpDown numericUpDown = ((NumericUpDown)d);
numericUpDown.CoerceValue(NumericUpDown.MaxValueProperty);
numericUpDown.CoerceValue(NumericUpDown.ValueProperty);
}
public double Increment
{
get { return (double)GetValue(IncrementProperty); }
set { SetValue(IncrementProperty, value); }
}
public static readonly DependencyProperty IncrementProperty =
DependencyProperty.Register("Increment", typeof(double), typeof(NumericUpDown), new FrameworkPropertyMetadata(1D, null, coerceIncrementCallback));
private static object coerceIncrementCallback(DependencyObject d, object value)
{
NumericUpDown numericUpDown = ((NumericUpDown)d);
double i = numericUpDown.MaxValue - numericUpDown.MinValue;
if ((double)value > i)
return i;
return value;
}
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(double), typeof(NumericUpDown), new FrameworkPropertyMetadata(0D, valueChangedCallback, coerceValueCallback), validateValueCallback);
private static void valueChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
NumericUpDown numericUpDown = (NumericUpDown)d;
ValueChangedEventArgs ea =
new ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, d, (double)e.OldValue, (double)e.NewValue);
numericUpDown.RaiseEvent(ea);
numericUpDown.PART_TextBox.Text = e.NewValue.ToString();
}
private static bool validateValueCallback(object value)
{
double val = (double)value;
if (val > double.MinValue && val < double.MaxValue)
return true;
else
return false;
}
private static object coerceValueCallback(DependencyObject d, object value)
{
double val = (double)value;
double minValue = ((NumericUpDown)d).MinValue;
double maxValue = ((NumericUpDown)d).MaxValue;
double result;
if (val < minValue)
result = minValue;
else if (val > maxValue)
result = maxValue;
else
result = (double)value;
return result;
}
private void buttonUp_Click(object sender, RoutedEventArgs e)
{
if (Value < MaxValue)
Value += Increment;
Value = Math.Round(Value, Places);
}
private void buttonDown_Click(object sender, RoutedEventArgs e)
{
if (Value > MinValue)
Value -= Increment;
Value = Math.Round(Value, Places);
}
private void textBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Space)
e.Handled = true;
}
private void textBox_TextChanged(object sender, TextChangedEventArgs e)
{
int index = PART_TextBox.CaretIndex;
double result;
if (!double.TryParse(PART_TextBox.Text, out result))
{
var changes = e.Changes.FirstOrDefault();
if (changes != null)
{
PART_TextBox.Text = PART_TextBox.Text.Remove(changes.Offset, changes.AddedLength);
PART_TextBox.CaretIndex = index > 0 ? index - changes.AddedLength : 0;
}
}
else if (result < MaxValue && result > MinValue)
Value = result;
else
{
PART_TextBox.Text = Value.ToString();
PART_TextBox.CaretIndex = index > 0 ? index - 1 : 0;
}
}
}
}

View file

@ -0,0 +1,52 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.MainApp.CustomControl">
<local:FontSizeConverter x:Key="FontSizeConverter" />
<Style TargetType="{x:Type local:NumericUpDown}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NumericUpDown}">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox
x:Name="PART_TextBox"
Margin="-20,0,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
BorderThickness="0" />
<Grid Grid.Column="1">
<RepeatButton
x:Name="PART_ButtonUp"
Width="20"
Height="20"
Margin="-40,0,0,0"
Padding="2,0"
Content="▲"
FontSize="{Binding FontSize, Converter={StaticResource FontSizeConverter}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:NumericUpDown}}}" />
<RepeatButton
x:Name="PART_ButtonDown"
Width="20"
Height="20"
Margin="1,0,0,0"
Padding="2,0"
Content="▼"
FontSize="{Binding FontSize, Converter={StaticResource FontSizeConverter}, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:NumericUpDown}}}" />
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush" Value="Gray" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Width" Value="100" />
</Style>
</ResourceDictionary>

View file

@ -0,0 +1,18 @@
using System.Windows;
namespace MSFSPopoutPanelManager.MainApp.CustomControl
{
public delegate void ValueChangedEventHandler(object sender, ValueChangedEventArgs e);
public class ValueChangedEventArgs : RoutedEventArgs
{
public ValueChangedEventArgs(RoutedEvent routedEvent, object source, double oldValue, double newValue)
: base(routedEvent, source)
{
OldValue = oldValue;
NewValue = newValue;
}
public double OldValue { get; private set; }
public double NewValue { get; private set; }
}
}

272
MainApp/HudBar.xaml Normal file
View file

@ -0,0 +1,272 @@
<Window
x:Class="MSFSPopoutPanelManager.MainApp.HudBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:viewmodel="clr-namespace:MSFSPopoutPanelManager.MainApp.ViewModel"
Title="HudBar"
Width="1920"
MinWidth="1920"
MinHeight="40"
MaxHeight="40"
ResizeMode="NoResize"
WindowStyle="None"
mc:Ignorable="d">
<Window.Resources>
<system:Double x:Key="IconSize">22</system:Double>
<system:Double x:Key="ButtonSize">28</system:Double>
<Style
x:Key="TxtBoxLabel"
BasedOn="{StaticResource MaterialDesignBody1TextBlock}"
TargetType="TextBlock">
<Setter Property="Margin" Value="10,5,10,5" />
<Setter Property="Foreground" Value="White" />
</Style>
<Style
x:Key="TxtBoxData"
BasedOn="{StaticResource MaterialDesignBody1TextBlock}"
TargetType="TextBlock">
<Setter Property="Margin" Value="0,5,10,5" />
<Setter Property="Foreground" Value="Lime" />
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
<Style
x:Key="VerticalSeparator"
BasedOn="{StaticResource {x:Type Separator}}"
TargetType="Separator">
<Setter Property="Foreground" Value="White" />
<Setter Property="Margin" Value="6,0,6,0" />
<Setter Property="LayoutTransform">
<Setter.Value>
<TransformGroup>
<TransformGroup.Children>
<TransformCollection>
<RotateTransform Angle="90" />
</TransformCollection>
</TransformGroup.Children>
</TransformGroup>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<materialDesign:ColorZone Height="40" Mode="Dark">
<Grid d:DataContext="{d:DesignInstance viewmodel:HudBarViewModel}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="130" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="6*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="4*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="10,5,0,0"
Style="{StaticResource TxtBoxLabel}"
Text="{Binding HudBarTypeText}" />
<DockPanel Grid.Column="1">
<Separator Style="{StaticResource VerticalSeparator}" />
</DockPanel>
<DockPanel Grid.Column="2">
<TextBlock Style="{StaticResource TxtBoxLabel}">Elevator Trim:</TextBlock>
<TextBlock Style="{StaticResource TxtBoxData}" Text="{Binding FlightSimData.HudBarData.ElevatorTrimFormatted}" />
</DockPanel>
<DockPanel Grid.Column="3">
<Separator Style="{StaticResource VerticalSeparator}" />
</DockPanel>
<DockPanel Grid.Column="4">
<TextBlock Style="{StaticResource TxtBoxLabel}">Aileron Trim:</TextBlock>
<TextBlock Style="{StaticResource TxtBoxData}" Text="{Binding FlightSimData.HudBarData.AileronTrimFormatted}" />
</DockPanel>
<DockPanel Grid.Column="5">
<Separator Style="{StaticResource VerticalSeparator}" />
</DockPanel>
<DockPanel Grid.Column="6">
<TextBlock Style="{StaticResource TxtBoxLabel}">Rudder Trim:</TextBlock>
<TextBlock Style="{StaticResource TxtBoxData}" Text="{Binding FlightSimData.HudBarData.RudderTrimFormatted}" />
</DockPanel>
<DockPanel Grid.Column="7">
<Separator Style="{StaticResource VerticalSeparator}" />
</DockPanel>
<DockPanel Grid.Column="8">
<TextBlock Style="{StaticResource TxtBoxLabel}">Flaps:</TextBlock>
<TextBlock Style="{StaticResource TxtBoxData}" Text="{Binding FlightSimData.HudBarData.FlapFormatted}" />
</DockPanel>
<DockPanel Grid.Column="9">
<Separator Style="{StaticResource VerticalSeparator}" />
</DockPanel>
<DockPanel Grid.Column="10">
<TextBlock Style="{StaticResource TxtBoxLabel}">Brake:</TextBlock>
<TextBlock Text="{Binding FlightSimData.HudBarData.ParkingBrakeFormatted}">
<TextBlock.Style>
<Style BasedOn="{StaticResource TxtBoxData}" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding FlightSimData.HudBarData.ParkingBrakeFormatted}" Value="Disengaged">
<Setter Property="TextBlock.Foreground" Value="DimGray" />
</DataTrigger>
<DataTrigger Binding="{Binding FlightSimData.HudBarData.ParkingBrakeFormatted}" Value="Engaged">
<Setter Property="TextBlock.Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DockPanel>
<DockPanel Grid.Column="11">
<Separator Style="{StaticResource VerticalSeparator}" />
</DockPanel>
<DockPanel Grid.Column="12">
<TextBlock Style="{StaticResource TxtBoxLabel}">Gear:</TextBlock>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<TextBlock Text="L">
<TextBlock.Style>
<Style BasedOn="{StaticResource TxtBoxData}" TargetType="TextBlock">
<Setter Property="Margin" Value="10,5,10,0" />
<Style.Triggers>
<DataTrigger Binding="{Binding FlightSimData.HudBarData.GearLeftFormatted}" Value="DOWN">
<Setter Property="TextBlock.Foreground" Value="Lime" />
</DataTrigger>
<DataTrigger Binding="{Binding FlightSimData.HudBarData.GearLeftFormatted}" Value="UP">
<Setter Property="TextBlock.Foreground" Value="DimGray" />
</DataTrigger>
<DataTrigger Binding="{Binding FlightSimData.HudBarData.GearLeftFormatted}" Value="MOVING">
<Setter Property="TextBlock.Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="C">
<TextBlock.Style>
<Style BasedOn="{StaticResource TxtBoxData}" TargetType="TextBlock">
<Setter Property="Margin" Value="10,5,10,0" />
<Style.Triggers>
<DataTrigger Binding="{Binding FlightSimData.HudBarData.GearCenterFormatted}" Value="DOWN">
<Setter Property="TextBlock.Foreground" Value="Lime" />
</DataTrigger>
<DataTrigger Binding="{Binding FlightSimData.HudBarData.GearCenterFormatted}" Value="UP">
<Setter Property="TextBlock.Foreground" Value="DimGray" />
</DataTrigger>
<DataTrigger Binding="{Binding FlightSimData.HudBarData.GearCenterFormatted}" Value="MOVING">
<Setter Property="TextBlock.Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="R">
<TextBlock.Style>
<Style BasedOn="{StaticResource TxtBoxData}" TargetType="TextBlock">
<Setter Property="Margin" Value="10,5,10,0" />
<Style.Triggers>
<DataTrigger Binding="{Binding FlightSimData.HudBarData.GearRightFormatted}" Value="DOWN">
<Setter Property="TextBlock.Foreground" Value="Lime" />
</DataTrigger>
<DataTrigger Binding="{Binding FlightSimData.HudBarData.GearRightFormatted}" Value="UP">
<Setter Property="TextBlock.Foreground" Value="DimGray" />
</DataTrigger>
<DataTrigger Binding="{Binding FlightSimData.HudBarData.GearRightFormatted}" Value="MOVING">
<Setter Property="TextBlock.Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DockPanel>
<DockPanel Grid.Column="13">
<Separator Style="{StaticResource VerticalSeparator}" />
</DockPanel>
<DockPanel Grid.Column="14">
<TextBlock Style="{StaticResource TxtBoxLabel}">Timer:</TextBlock>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<TextBlock Style="{StaticResource TxtBoxData}" Text="{Binding Timer, StringFormat={}{0:HH:mm:ss}}" />
<Button
Width="80"
Height="28"
Margin="10,0,0,0"
Command="{Binding StartStopTimerCommand}"
Style="{StaticResource MaterialDesignOutlinedButton}">
<TextBlock Foreground="White" Text="{c:Binding 'IsTimerStarted ? &quot;STOP&quot; : &quot;START&quot;'}" />
</Button>
<Button
Width="80"
Height="28"
Margin="15,0,10,0"
Command="{Binding ResetTimerCommand}"
Style="{StaticResource MaterialDesignOutlinedButton}">
<TextBlock Foreground="White" Text="RESET" />
</Button>
</StackPanel>
</DockPanel>
<DockPanel Grid.Column="15">
<Separator Style="{StaticResource VerticalSeparator}" />
</DockPanel>
<DockPanel Grid.Column="16">
<TextBlock Style="{StaticResource TxtBoxLabel}">Sim Rate:</TextBlock>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<TextBlock
Margin="0,5,5,0"
Style="{StaticResource TxtBoxData}"
Text="{Binding FlightSimData.HudBarData.SimRate}" />
<TextBlock
Margin="0,5,20,0"
Style="{StaticResource TxtBoxData}"
Text="X" />
<Button
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
Margin="0,0,15,0"
Command="{Binding IncreaseSimRateCommand}"
Style="{StaticResource MaterialDesignFloatingActionButton}">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="ArrowUp" />
</Button>
<Button
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
Margin="0,0,10,0"
Command="{Binding DecreaseSimRateCommand}"
Style="{StaticResource MaterialDesignFloatingActionButton}">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="ArrowDown" />
</Button>
</StackPanel>
</DockPanel>
<DockPanel Grid.Column="17">
<Separator Style="{StaticResource VerticalSeparator}" />
</DockPanel>
<DockPanel Grid.Column="18">
<Button
x:Name="BtnClose"
Width="80"
Height="28"
Click="BtnClose_Click"
Style="{StaticResource MaterialDesignOutlinedButton}">
<TextBlock Foreground="White" Text="Close" />
</Button>
</DockPanel>
</Grid>
</materialDesign:ColorZone>
</Window>

49
MainApp/HudBar.xaml.cs Normal file
View file

@ -0,0 +1,49 @@
using Microsoft.Extensions.DependencyInjection;
using MSFSPopoutPanelManager.MainApp.ViewModel;
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class HudBar : Window
{
private HudBarViewModel _viewModel;
public HudBar(Guid panelId)
{
_viewModel = App.AppHost.Services.GetRequiredService<HudBarViewModel>();
_viewModel.PanelId = panelId;
InitializeComponent();
Loaded += (sender, e) =>
{
DataContext = _viewModel;
_viewModel.PanelConfig.PanelHandle = new WindowInteropHelper(Window.GetWindow(this)).Handle;
_viewModel.PanelConfig.Width = Convert.ToInt32(Width);
_viewModel.PanelConfig.Height = Convert.ToInt32(Height);
};
this.MouseLeftButtonDown += HudBar_MouseLeftButtonDown;
this.Topmost = true;
Closing += HudBar_Closing;
}
private void HudBar_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
_viewModel.CloseCommand.Execute(null);
}
private void HudBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
private void BtnClose_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
}

102
MainApp/MainApp.csproj Normal file
View file

@ -0,0 +1,102 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<AssemblyName>MSFSPopoutPanelManager</AssemblyName>
<PackageId>MSFS 2020 Popout Panel Manager</PackageId>
<Product>MSFS 2020 Popout Panel Manager</Product>
<Authors>Stanley Kwok</Authors>
<Company>Stanley Kwok</Company>
<Copyright>Stanley Kwok 2021</Copyright>
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
<RootNamespace>MSFSPopoutPanelManager.MainApp</RootNamespace>
<ApplicationIcon>logo.ico</ApplicationIcon>
<Platforms>x64</Platforms>
<Version>4.0.0.0</Version>
<AssemblyVersion>4.0.0.0</AssemblyVersion>
<FileVersion>4.0.0.0</FileVersion>
<DebugType>embedded</DebugType>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<!-- Publishing options -->
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
<Configurations>Debug;Release;Local</Configurations>
<PublishReadyToRunComposite>false</PublishReadyToRunComposite>
</PropertyGroup>
<ItemGroup>
<Resource Include="logo.ico" />
</ItemGroup>
<ItemGroup>
<Content Include="log4net.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autoupdater.NET.Official" Version="1.8.3" />
<PackageReference Include="DotNetKit.Wpf.AutoCompleteComboBox" Version="1.6.0" />
<PackageReference Include="Fody" Version="6.7.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="gong-wpf-dragdrop" Version="3.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0" PrivateAssets="All" />
<PackageReference Include="CalcBinding" Version="2.5.2" />
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="1.1.0" />
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageReference Include="Prism.Core" Version="8.1.97" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="InputSimulatorCore" Version="1.0.5" />
<PackageReference Include="WindowsHook" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<Compile Update="AppWindow.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="CustomControl\Themes\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DomainModel\DomainModel.csproj" />
<ProjectReference Include="..\Orchestration\Orchestration.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="logo.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<Target Name="CopyItems" AfterTargets="ComputeFilesToPublish">
<!-- Copy all plugin-extensions items -->
<ItemGroup>
<Skipper Include="$(SolutionDir)\Assets\Community\zzz-ready-to-fly-button-skipper\**"></Skipper>
<License Include="$(SolutionDir)\LICENSE"></License>
<Version Include="$(SolutionDir)\VERSION.md"></Version>
</ItemGroup>
<Copy SourceFiles="@(License)" DestinationFolder="$(PublishDir)" SkipUnchangedFiles="false" OverwriteReadOnlyFiles="true" />
<Copy SourceFiles="@(Version)" DestinationFolder="$(PublishDir)" SkipUnchangedFiles="false" OverwriteReadOnlyFiles="true" />
<Copy SourceFiles="@(Skipper)" DestinationFolder="$(PublishDir)Community\zzz-ready-to-fly-button-skipper\%(RecursiveDir)" SkipUnchangedFiles="false" OverwriteReadOnlyFiles="true" />
</Target>
</Project>

View file

@ -0,0 +1,64 @@
<Window
x:Class="MSFSPopoutPanelManager.MainApp.MessageWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:viewmodel="clr-namespace:MSFSPopoutPanelManager.MainApp.ViewModel"
Title="MSFS Pop Out Panel Manager"
Width="Auto"
Height="Auto"
AllowsTransparency="True"
ResizeMode="NoResize"
ShowInTaskbar="False"
SizeToContent="WidthAndHeight"
Topmost="True"
WindowStyle="None"
mc:Ignorable="d">
<Window.Resources>
<system:Double x:Key="IconSize">22</system:Double>
<system:Double x:Key="ButtonSize">28</system:Double>
<Style
x:Key="Label"
BasedOn="{StaticResource MaterialDesignBody1TextBlock}"
TargetType="TextBlock">
<Setter Property="Foreground" Value="White" />
</Style>
</Window.Resources>
<materialDesign:ColorZone
Width="Auto"
Height="Auto"
Mode="Dark">
<DockPanel
Width="400"
Height="225"
d:DataContext="{d:DesignInstance viewmodel:MessageWindowViewModel}">
<WrapPanel
Height="30"
Background="SlateGray"
DockPanel.Dock="Top">
<TextBlock
Padding="10,5,10,5"
HorizontalAlignment="Stretch"
Style="{StaticResource MaterialDesignBody1TextBlock}">
MSFS POP OUT PANEL MANAGER
</TextBlock>
</WrapPanel>
<WrapPanel Margin="20,5,20,10">
<ScrollViewer
x:Name="ScrollViewerMessage"
Height="Auto"
DockPanel.Dock="Bottom"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden">
<TextBlock
x:Name="TextBlockMessage"
LineHeight="18"
LineStackingStrategy="BlockLineHeight" />
</ScrollViewer>
</WrapPanel>
</DockPanel>
</materialDesign:ColorZone>
</Window>

View file

@ -0,0 +1,72 @@
using Microsoft.Extensions.DependencyInjection;
using MSFSPopoutPanelManager.MainApp.ViewModel;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Interop;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class MessageWindow : Window
{
private MessageWindowViewModel _viewModel;
public MessageWindow()
{
_viewModel = App.AppHost.Services.GetRequiredService<MessageWindowViewModel>();
InitializeComponent();
Loaded += (sender, e) =>
{
DataContext = _viewModel;
_viewModel.Handle = new WindowInteropHelper(Window.GetWindow(this)).Handle;
// Set window binding, needs to be in code after window loaded
Binding visibleBinding = new Binding("IsVisible");
visibleBinding.Source = _viewModel;
visibleBinding.Converter = new BooleanToVisibilityConverter();
BindingOperations.SetBinding(this, Window.VisibilityProperty, visibleBinding);
// Set window click through
WindowsServices.SetWindowExTransparent(_viewModel.Handle);
_viewModel.OnMessageUpdated += _viewModel_OnMessageUpdated;
};
}
private void _viewModel_OnMessageUpdated(object sender, List<Run> e)
{
if (e == null)
return;
TextBlockMessage.Inlines.Clear();
foreach (var run in e)
TextBlockMessage.Inlines.Add(run);
ScrollViewerMessage.ScrollToEnd();
}
}
public static class WindowsServices
{
const int WS_EX_TRANSPARENT = 0x00000020;
const int GWL_EXSTYLE = (-20);
[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hwnd, int index);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);
public static void SetWindowExTransparent(IntPtr hwnd)
{
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT);
}
}
}

View file

@ -0,0 +1,70 @@
<Window
x:Class="MSFSPopoutPanelManager.MainApp.PanelCoorOverlay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="PanelCoorOverlay"
Width="30"
Height="30"
AllowsTransparency="True"
Background="#01F0F0FF"
Loaded="Window_Loaded"
MouseMove="Window_MouseMove"
ResizeMode="NoResize"
SizeToContent="WidthAndHeight"
WindowStyle="None"
mc:Ignorable="d">
<Canvas
Width="30"
Height="30"
PreviewMouseDown="Canvas_PreviewMouseDown"
ToolTip="{Binding Panel.PanelName}">
<Grid>
<Ellipse
x:Name="OverlayCircle"
Canvas.Left="1"
Canvas.Top="1"
Width="28"
Height="28"
Fill="Transparent"
StrokeThickness="6" />
<Ellipse
x:Name="OverlayBlinkingCircle"
Canvas.Left="0"
Canvas.Top="0"
Width="30"
Height="30"
Fill="Transparent"
Stroke="Black"
StrokeThickness="7"
Visibility="{c:Binding Panel.IsShownPanelSource}">
<Ellipse.Triggers>
<EventTrigger RoutedEvent="Ellipse.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
AutoReverse="True"
RepeatBehavior="Forever"
Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:0.4" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Ellipse.Triggers>
</Ellipse>
<Ellipse
x:Name="OverlayCircleBorder"
Canvas.Left="0"
Canvas.Top="0"
Width="30"
Height="30"
Fill="Transparent"
Opacity="0.5"
Stroke="White" />
</Grid>
</Canvas>
</Window>

View file

@ -0,0 +1,96 @@
using Microsoft.Extensions.DependencyInjection;
using MSFSPopoutPanelManager.DomainModel.Profile;
using MSFSPopoutPanelManager.MainApp.ViewModel;
using MSFSPopoutPanelManager.WindowsAgent;
using System;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class PanelCoorOverlay : Window
{
private PanelCoorOverlayViewModel _viewModel;
private const int WINDOW_ADJUSTMENT = 15; // half of window height with shadow adjustment
private int _xCoor;
private int _yCoor;
public bool IsEditingPanelLocation { get; set; }
public Guid PanelId { get; set; }
public IntPtr WindowHandle { get; set; }
public event EventHandler<System.Drawing.Point> WindowLocationChanged;
public PanelCoorOverlay(Guid id)
{
_viewModel = App.AppHost.Services.GetRequiredService<PanelCoorOverlayViewModel>();
_viewModel.SetPanelId(id);
PanelId = id;
InitializeComponent();
Loaded += PanelCoorOverlay_Loaded;
OverlayCircle.Stroke = new SolidColorBrush((Color)ColorConverter.ConvertFromString(_viewModel.Panel.PanelSource.Color));
if (!_viewModel.ProfileData.ActiveProfile.IsEditingPanelSource)
OverlayBlinkingCircle.Visibility = Visibility.Collapsed;
IsEditingPanelLocation = false;
this.Topmost = true;
this.Left = 0;
this.Top = 0;
this.MouseUp += PanelCoorOverlay_MouseUp; // detect location change when user release mouse button when dragging the overlay window
}
private void PanelCoorOverlay_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = _viewModel;
}
private void PanelCoorOverlay_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (this.Top is double.NaN || this.Left is double.NaN)
return;
// Fixed broken window left/top coordinate for DPI Awareness Per Monitor
var handle = new WindowInteropHelper(this).Handle;
var rect = WindowActionManager.GetWindowRectangle(handle);
WindowLocationChanged?.Invoke(this, new System.Drawing.Point(rect.X + WINDOW_ADJUSTMENT, rect.Y + WINDOW_ADJUSTMENT));
if (_viewModel.Panel != null)
_viewModel.Panel.IsSelectedPanelSource = false;
}
public void SetWindowCoor(int x, int y)
{
_xCoor = x;
_yCoor = y;
}
private void Window_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (IsEditingPanelLocation && e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
this.DragMove();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Fixed broken window left/top coordinate for DPI Awareness Per Monitor
var handle = new WindowInteropHelper(this).Handle;
WindowActionManager.MoveWindow(handle, _xCoor - WINDOW_ADJUSTMENT, _yCoor - WINDOW_ADJUSTMENT, Convert.ToInt32(this.Width), Convert.ToInt32(this.Height));
WindowActionManager.ApplyAlwaysOnTop(handle, PanelType.PanelSourceWindow, true);
}
private void Canvas_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed && _viewModel.Panel != null)
_viewModel.Panel.IsSelectedPanelSource = true;
}
}
}

View file

@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Platform>x64</Platform> <Platform>x64</Platform>
<PublishDir>..\..\..\publish\master</PublishDir> <PublishDir>..\..\..\publish\master</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol> <PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net6.0-windows</TargetFramework> <TargetFramework>net7.0-windows</TargetFramework>
<SelfContained>true</SelfContained> <SelfContained>true</SelfContained>
<DeleteExistingFiles>true</DeleteExistingFiles> <DeleteExistingFiles>true</DeleteExistingFiles>
<PublishWithAspNetCoreTargetManifest>true</PublishWithAspNetCoreTargetManifest> <PublishWithAspNetCoreTargetManifest>true</PublishWithAspNetCoreTargetManifest>

View file

@ -0,0 +1,316 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.MainApp"
xmlns:profileDomain="clr-namespace:MSFSPopoutPanelManager.DomainModel.Profile;assembly=DomainModel"
xmlns:wpf="clr-namespace:MaterialDesignThemes.Wpf;assembly=MaterialDesignThemes.Wpf">
<Duration x:Key="ExpandDuration">0:0:0.250</Duration>
<Duration x:Key="CollapseDuration">0:0:0.200</Duration>
<KeyTime x:Key="ExpandKeyTime">0:0:0.250</KeyTime>
<KeyTime x:Key="CollapseKeyTime">0:0:0.200</KeyTime>
<Style x:Key="MaterialDesignExpanderToggleButton" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="Transparent" BorderThickness="0">
<wpf:PackIcon
x:Name="ExpandPath"
Width="24"
Height="24"
Padding="0"
Foreground="{TemplateBinding Foreground}"
Kind="ChevronDown"
Opacity="0.5"
RenderTransformOrigin="0.5 0.5"
Visibility="{c:Binding 'DataItem.PanelType!=profileDomain:PanelType.HudBarWindow'}">
<wpf:PackIcon.RenderTransform>
<RotateTransform x:Name="ExpandPathRotateTransform" />
</wpf:PackIcon.RenderTransform>
</wpf:PackIcon>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ExpandPathRotateTransform" Storyboard.TargetProperty="Angle">
<EasingDoubleKeyFrame KeyTime="{StaticResource ExpandKeyTime}" Value="180" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ExpandPathRotateTransform" Storyboard.TargetProperty="Angle">
<EasingDoubleKeyFrame KeyTime="{StaticResource CollapseKeyTime}" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="MaterialDesignHorizontalHeaderStyleButton" TargetType="{x:Type ToggleButton}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="{TemplateBinding Background}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ToggleButton
x:Name="ExpanderButton"
VerticalAlignment="Center"
Foreground="{TemplateBinding Foreground}"
IsChecked="{Binding Path=IsChecked, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
RenderTransformOrigin="0.5 0.5">
<ToggleButton.Style>
<Style BasedOn="{StaticResource MaterialDesignExpanderToggleButton}" TargetType="{x:Type ToggleButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding ExpandDirection, RelativeSource={RelativeSource AncestorType=Expander, AncestorLevel=1}}" Value="Up">
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="180" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="MaterialDesignHorizontalHeaderStyleContent" TargetType="{x:Type Button}">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Padding="{Binding Path=(wpf:ExpanderAssist.HorizontalHeaderPadding), RelativeSource={RelativeSource AncestorType=Expander, AncestorLevel=1}}" Background="{TemplateBinding Background}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentPresenter
Grid.Column="0"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CustomMaterialDesignExpander" TargetType="{x:Type Expander}">
<Setter Property="Background" Value="{DynamicResource MaterialDesignPaper}" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}, Path=(TextElement.Foreground)}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<ControlTemplate.Resources>
<local:ExpanderRotateAngleConverter x:Key="ExpanderRotateAngleConverter" />
</ControlTemplate.Resources>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel Background="{TemplateBinding Background}">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button
Name="HeaderSiteContent"
Background="{TemplateBinding wpf:ExpanderAssist.HeaderBackground}"
BorderThickness="0"
Content="{TemplateBinding Header}"
ContentStringFormat="{TemplateBinding HeaderStringFormat}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
DockPanel.Dock="Top"
Focusable="False"
Foreground="{TemplateBinding Foreground}"
IsTabStop="False"
Style="{StaticResource MaterialDesignOutlinedButton}"
TextElement.FontSize="{TemplateBinding wpf:ExpanderAssist.HeaderFontSize}" />
<StackPanel
Margin="0"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<ToggleButton
Name="HeaderSiteButton"
Margin="0"
Padding="0"
Background="{TemplateBinding wpf:ExpanderAssist.HeaderBackground}"
BorderThickness="0"
Content="{TemplateBinding Header}"
ContentStringFormat="{TemplateBinding HeaderStringFormat}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
Cursor="Hand"
Focusable="False"
Foreground="{TemplateBinding Foreground}"
IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
IsTabStop="False"
TextElement.FontSize="{TemplateBinding wpf:ExpanderAssist.HeaderFontSize}" />
</StackPanel>
</StackPanel>
<Border Name="ContentSite">
<Border.LayoutTransform>
<TransformGroup>
<ScaleTransform x:Name="ContentSiteScaleTransform" />
<RotateTransform Angle="{Binding Path=ExpandDirection, RelativeSource={RelativeSource AncestorType=Expander}, Converter={StaticResource ExpanderRotateAngleConverter}}" />
</TransformGroup>
</Border.LayoutTransform>
<Grid
Name="ContentPanel"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Grid.LayoutTransform>
<RotateTransform Angle="{Binding Path=ExpandDirection, RelativeSource={RelativeSource AncestorType=Expander}, Converter={StaticResource ExpanderRotateAngleConverter}, ConverterParameter=-1}" />
</Grid.LayoutTransform>
<ContentPresenter
Name="PART_Content"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}"
Focusable="False"
Visibility="Collapsed" />
</Grid>
</Border>
</DockPanel>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ExpansionStates">
<VisualStateGroup.Transitions>
<VisualTransition To="Expanded">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="ContentPanel"
Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="{DynamicResource ExpandDuration}" />
<DoubleAnimation
Storyboard.TargetName="ContentSiteScaleTransform"
Storyboard.TargetProperty="(ScaleTransform.ScaleY)"
From="0"
To="1"
Duration="{DynamicResource ExpandDuration}">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="PART_Content"
Storyboard.TargetProperty="Visibility"
Duration="0:0:0">
<DiscreteObjectKeyFrame KeyTime="0%" Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition To="Collapsed">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="ContentPanel"
Storyboard.TargetProperty="Opacity"
From="1"
To="0"
Duration="{DynamicResource CollapseDuration}" />
<DoubleAnimation
Storyboard.TargetName="ContentSiteScaleTransform"
Storyboard.TargetProperty="(ScaleTransform.ScaleY)"
From="1"
To="0"
Duration="{DynamicResource CollapseDuration}">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseInOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="PART_Content"
Storyboard.TargetProperty="Visibility"
Duration="{DynamicResource CollapseDuration}">
<DiscreteObjectKeyFrame KeyTime="100%" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Expanded">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="ContentPanel"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0" />
<DoubleAnimation
Storyboard.TargetName="ContentSiteScaleTransform"
Storyboard.TargetProperty="(ScaleTransform.ScaleY)"
To="1"
Duration="0" />
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="PART_Content"
Storyboard.TargetProperty="Visibility"
Duration="0">
<DiscreteObjectKeyFrame KeyTime="0%" Value="{x:Static Visibility.Visible}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Collapsed">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="ContentPanel"
Storyboard.TargetProperty="Opacity"
To="0"
Duration="0" />
<DoubleAnimation
Storyboard.TargetName="ContentSiteScaleTransform"
Storyboard.TargetProperty="(ScaleTransform.ScaleY)"
To="0"
Duration="0" />
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="PART_Content"
Storyboard.TargetProperty="Visibility"
Duration="0">
<DiscreteObjectKeyFrame KeyTime="100%" Value="{x:Static Visibility.Collapsed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="ExpandDirection" Value="Up">
<Setter TargetName="HeaderSiteButton" Property="DockPanel.Dock" Value="Bottom" />
<Setter TargetName="HeaderSiteButton" Property="Style" Value="{StaticResource MaterialDesignHorizontalHeaderStyleButton}" />
<Setter TargetName="HeaderSiteContent" Property="Style" Value="{StaticResource MaterialDesignHorizontalHeaderStyleContent}" />
</Trigger>
<Trigger Property="ExpandDirection" Value="Down">
<Setter TargetName="HeaderSiteButton" Property="DockPanel.Dock" Value="Top" />
<Setter TargetName="HeaderSiteButton" Property="Style" Value="{StaticResource MaterialDesignHorizontalHeaderStyleButton}" />
<Setter TargetName="HeaderSiteContent" Property="Style" Value="{StaticResource MaterialDesignHorizontalHeaderStyleContent}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="wpf:ExpanderAssist.HeaderBackground" Value="Transparent" />
</Style>
</ResourceDictionary>

View file

@ -0,0 +1,44 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style TargetType="{x:Type ScrollViewer}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollContentPresenter Grid.Column="0" />
<ScrollBar
Name="PART_VerticalScrollBar"
Grid.Column="1"
Width="10"
MinWidth="10"
Maximum="{TemplateBinding ScrollableHeight}"
Opacity="0.5"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
Value="{TemplateBinding VerticalOffset}" />
<ScrollBar
Name="PART_HorizontalScrollBar"
Grid.Row="1"
Grid.Column="0"
Height="10"
MinHeight="10"
Maximum="{TemplateBinding ScrollableWidth}"
Opacity="0.5"
Orientation="Horizontal"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
Value="{TemplateBinding HorizontalOffset}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

View file

@ -0,0 +1,33 @@
using System;
using System.Globalization;
using System.Windows.Controls;
using System.Windows.Data;
namespace MSFSPopoutPanelManager.MainApp
{
public class ExpanderRotateAngleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double factor = 1.0;
if (parameter is { } parameterValue)
{
if (!double.TryParse(parameterValue.ToString(), out factor))
{
factor = 1.0;
}
}
return value switch
{
ExpandDirection.Left => 90 * factor,
ExpandDirection.Right => -90 * factor,
_ => 0
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,92 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.AddProfileDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.MainApp"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="clr-namespace:MSFSPopoutPanelManager.MainApp.ViewModel"
xmlns:wpf="clr-namespace:MaterialDesignThemes.Wpf;assembly=MaterialDesignThemes.Wpf"
Width="400"
Height="210"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
mc:Ignorable="d">
<UserControl.Resources>
<local:InverseBooleanOrConverter x:Key="InverseBooleanOrConverter" />
</UserControl.Resources>
<StackPanel>
<materialDesign:ColorZone
Height="30"
materialDesign:ElevationAssist.Elevation="Dp4"
Mode="PrimaryDark">
<StackPanel
Grid.Row="0"
Margin="24,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center">
<TextBlock Text="Add Profile" />
</StackPanel>
</materialDesign:ColorZone>
<StackPanel
Height="84"
Margin="24,12,24,16"
HorizontalAlignment="Stretch"
FocusManager.FocusedElement="{Binding ElementName=TxtBoxName}">
<ComboBox
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="Copy From Profile"
DisplayMemberPath="Name"
ItemsSource="{Binding ProfileData.Profiles}"
MaxDropDownHeight="280"
SelectedValue="{Binding CopiedProfile, Mode=TwoWay}"
Style="{StaticResource MaterialDesignFloatingHintComboBox}" />
<TextBox
x:Name="TxtBoxName"
Margin="0,8,0,0"
VerticalAlignment="Center"
materialDesign:HintAssist.FloatingScale="0.75"
materialDesign:HintAssist.Hint="Profile Name"
Style="{StaticResource MaterialDesignFloatingHintTextBox}">
<Binding
Mode="TwoWay"
Path="Profile.Name"
UpdateSourceTrigger="PropertyChanged"
ValidatesOnDataErrors="True">
<Binding.ValidationRules>
<viewmodel:PostiveValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox>
</StackPanel>
<StackPanel
Height="50"
Margin="24,12,24,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<Button
x:Name="BtnAccept"
Command="{x:Static wpf:DialogHost.CloseDialogCommand}"
CommandParameter="ADD"
IsDefault="True"
Style="{StaticResource MaterialDesignOutlinedButton}">
ACCEPT
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource InverseBooleanOrConverter}" Mode="TwoWay">
<Binding ElementName="TxtBoxName" Path="(Validation.HasError)" />
<Binding Path="Profile.Name.Length" />
</MultiBinding>
</Button.IsEnabled>
</Button>
<Button
x:Name="BtnCancel"
Margin="12,0,0,0"
Command="{x:Static wpf:DialogHost.CloseDialogCommand}"
CommandParameter="CANCEL"
IsCancel="True"
Style="{StaticResource MaterialDesignOutlinedButton}">
CANCEL
</Button>
</StackPanel>
</StackPanel>
</UserControl>

View file

@ -0,0 +1,26 @@
using MaterialDesignThemes.Wpf;
using Microsoft.Extensions.DependencyInjection;
using MSFSPopoutPanelManager.MainApp.ViewModel;
using System;
using System.Windows.Controls;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class AddProfileDialog : UserControl
{
public AddProfileViewModel ViewModel { get; set; }
public AddProfileDialog()
{
ViewModel = App.AppHost.Services.GetRequiredService<AddProfileViewModel>();
InitializeComponent();
Loaded += (sender, e) =>
{
DataContext = ViewModel;
BtnAccept.IsEnabled = false;
};
}
public DialogClosingEventHandler ClosingEventHandler { get { return ViewModel.ClosingEventHandler; } }
}
}

View file

@ -0,0 +1,55 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.ConfirmationDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpf="clr-namespace:MaterialDesignThemes.Wpf;assembly=MaterialDesignThemes.Wpf"
Width="400"
MinHeight="170"
mc:Ignorable="d">
<StackPanel>
<materialDesign:ColorZone
Height="30"
materialDesign:ElevationAssist.Elevation="Dp4"
Mode="PrimaryDark">
<StackPanel
Margin="24,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center">
<TextBlock Text="{Binding Title}" />
</StackPanel>
</materialDesign:ColorZone>
<StackPanel
MinHeight="40"
Margin="24,24,24,16"
Orientation="Vertical">
<TextBlock
VerticalAlignment="Top"
Text="{Binding Content}"
TextWrapping="Wrap" />
</StackPanel>
<StackPanel
Margin="24,12,24,8"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Orientation="Horizontal">
<Button
Command="{x:Static wpf:DialogHost.CloseDialogCommand}"
CommandParameter="CONFIRM"
KeyboardNavigation.AcceptsReturn="False"
Style="{StaticResource MaterialDesignOutlinedButton}">
<TextBlock Text="{Binding ConfirmButtonText}" />
</Button>
<Button
Margin="12,0,0,0"
Command="{x:Static wpf:DialogHost.CloseDialogCommand}"
CommandParameter="CANCEL"
IsCancel="True"
Style="{StaticResource MaterialDesignOutlinedButton}">
CANCEL
</Button>
</StackPanel>
</StackPanel>
</UserControl>

View file

@ -0,0 +1,14 @@
using MSFSPopoutPanelManager.MainApp.ViewModel;
using System.Windows.Controls;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class ConfirmationDialog : UserControl
{
public ConfirmationDialog(string content, string confirmButtonText)
{
InitializeComponent();
DataContext = new ConfirmationViewModel(content, confirmButtonText);
}
}
}

View file

@ -0,0 +1,343 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.HelpDrawer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="clr-namespace:MSFSPopoutPanelManager.MainApp.ViewModel"
Width="1024"
mc:Ignorable="d">
<UserControl.Resources>
<Style
x:Key="TextBlockHeading"
BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Width" Value="Auto" />
<Setter Property="Margin" Value="0,5,0,0" />
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style
x:Key="TextBlockSubheading"
BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="16" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Width" Value="Auto" />
<Setter Property="Margin" Value="0,5,0,5" />
</Style>
<Style TargetType="AccessText">
<Setter Property="FontSize" Value="14" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Width" Value="Auto" />
<Setter Property="Margin" Value="5,-2,0,0" />
</Style>
<Style TargetType="Line">
<Setter Property="Margin" Value="0,5,0,5" />
</Style>
<Style TargetType="{x:Type ScrollViewer}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollContentPresenter Grid.Column="0" />
<ScrollBar
Name="PART_VerticalScrollBar"
Grid.Column="1"
Width="10"
MinWidth="10"
Maximum="{TemplateBinding ScrollableHeight}"
Opacity="0.5"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
Value="{TemplateBinding VerticalOffset}" />
<ScrollBar
Name="PART_HorizontalScrollBar"
Grid.Row="1"
Grid.Column="0"
Height="10"
MinHeight="10"
Maximum="{TemplateBinding ScrollableWidth}"
Opacity="0.5"
Orientation="Horizontal"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
Value="{TemplateBinding HorizontalOffset}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
<DockPanel d:DataContext="{d:DesignInstance viewmodel:HelpViewModel}">
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<TreeView
Width="210"
VerticalAlignment="Stretch"
DockPanel.Dock="Left">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
<Setter Property="Foreground" Value="#666666" />
<Setter Property="Background" Value="Transparent" />
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="White" />
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="White" />
</Style.Resources>
</Style>
</TreeView.ItemContainerStyle>
<TreeViewItem
Margin="-5,0,0,10"
FontSize="14"
FontWeight="Bold"
Foreground="Gray"
Header="Help"
IsHitTestVisible="False" />
<TreeViewItem
x:Name="CategoryKeyboardCommands"
Margin="0,0,0,10"
Header="Keyboard Commands"
IsSelected="True" />
<TreeViewItem
x:Name="CategoryUserGuide"
Margin="0,0,0,10"
Header="User Guide" />
<TreeViewItem
x:Name="CategoryDownloadLatestRelease"
Margin="0,0,0,10"
Header="Download Latest Release" />
<TreeViewItem
x:Name="CategorySupport"
Margin="0,0,0,10"
Header="Support" />
<TreeViewItem
x:Name="CategoryAbout"
Margin="0,0,0,10"
Header="About" />
</TreeView>
</ScrollViewer>
<ScrollViewer
Width="780"
Height="565"
VerticalScrollBarVisibility="Auto">
<StackPanel Margin="0,-5,0,0">
<!-- Keyboard Commands -->
<WrapPanel Orientation="Vertical" Visibility="{Binding ElementName=CategoryKeyboardCommands, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel Margin="0,0,20,0" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Keyboard Commands</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<TextBlock Margin="0,0,0,20" TextWrapping="Wrap">
To configure a pop out panel, first click on the move and resize icon<InlineUIContainer>
<materialDesign:PackIcon Kind="MoveResize" />
</InlineUIContainer>
for the panel. You can then use keyboard commands below to adjust pop out panel when the icon turns green. To end panel configuration using keyboard commands, just click the icon<InlineUIContainer>
<materialDesign:PackIcon Kind="MoveResize" />
</InlineUIContainer>
again to end panel adjustment.</TextBlock>
<TextBlock>
<Run Foreground="LightSkyBlue">Up Arrow</Run>
- Move panel up by 10 pixels<LineBreak />
<Run Foreground="LightSkyBlue">Down Arrow</Run>
- Move panel down by 10 pixels<LineBreak />
<Run Foreground="LightSkyBlue">Left Arrow</Run>
- Move panel left by 10 pixels<LineBreak />
<Run Foreground="LightSkyBlue">Right Arrow</Run>
- Move panel right by 10 pixels<LineBreak /><LineBreak />
<Run Foreground="LightSkyBlue">Shift + Up Arrow</Run>
- Move panel up by 1 pixel<LineBreak />
<Run Foreground="LightSkyBlue">Shift + Down Arrow</Run>
- Move panel down by 1 pixel<LineBreak />
<Run Foreground="LightSkyBlue">Shift + Left Arrow</Run>
- Move panel left by 1 pixel<LineBreak />
<Run Foreground="LightSkyBlue">Shift + Right Arrow</Run>
- Move panel right by 1 pixel<LineBreak /><LineBreak />
<Run Foreground="LightSkyBlue">Ctrl + Up Arrow</Run>
- Decrease height by 10 pixels<LineBreak />
<Run Foreground="LightSkyBlue">Ctrl + Down Arrow</Run>
- Increase height by 10 pixels<LineBreak />
<Run Foreground="LightSkyBlue">Ctrl + Left Arrow</Run>
- Decrease width by 10 pixels<LineBreak />
<Run Foreground="LightSkyBlue">Ctrl + Right Arrow</Run>
- Increase width by 10 pixels<LineBreak /><LineBreak />
<Run Foreground="LightSkyBlue">Shift + Ctrl + Up Arrow</Run>
- Decrease height by 1 pixel<LineBreak />
<Run Foreground="LightSkyBlue">Shift + Ctrl + Down Arrow</Run>
- Increase height by 1 pixel<LineBreak />
<Run Foreground="LightSkyBlue">Shift + Ctrl + Left Arrow</Run>
- Decrease width by 1 pixel<LineBreak />
<Run Foreground="LightSkyBlue">Shift + Ctrl + Right Arrow</Run>
- Increase width by 1 pixel<LineBreak />
</TextBlock>
</WrapPanel>
</WrapPanel>
</WrapPanel>
<!-- User Guide -->
<WrapPanel Orientation="Vertical" Visibility="{Binding ElementName=CategoryUserGuide, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel Margin="0,0,20,0" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">User Guide</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
</WrapPanel>
<TextBlock Width="Auto">
<Hyperlink
NavigateUri="Getting Started"
RequestNavigate="Hyperlink_RequestNavigate"
Style="{StaticResource MaterialDesignBody2Hyperlink}">
<TextBlock Text="Getting Started" />
</Hyperlink>
</TextBlock>
<TextBlock Width="Auto" Margin="0,10,0,0">
<Hyperlink
NavigateUri="User Guide"
RequestNavigate="Hyperlink_RequestNavigate"
Style="{StaticResource MaterialDesignBody2Hyperlink}">
<TextBlock Text="User Guide" />
</Hyperlink>
</TextBlock>
</WrapPanel>
<!-- Download Latest -->
<WrapPanel Orientation="Vertical" Visibility="{Binding ElementName=CategoryDownloadLatestRelease, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel Margin="0,0,20,0" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Download Latest Release</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
</WrapPanel>
<TextBlock Width="Auto">
<Hyperlink
NavigateUri="Download Latest GitHub"
RequestNavigate="Hyperlink_RequestNavigate"
Style="{StaticResource MaterialDesignBody2Hyperlink}">
<TextBlock Text="Download latest release (GitHub)" />
</Hyperlink>
</TextBlock>
<TextBlock Width="Auto" Margin="0,10,0,0">
<Hyperlink
NavigateUri="Download Latest FlightsimTo"
RequestNavigate="Hyperlink_RequestNavigate"
Style="{StaticResource MaterialDesignBody2Hyperlink}">
<TextBlock Text="Download latest release (Flightsim.to)" />
</Hyperlink>
</TextBlock>
</WrapPanel>
<!-- Support -->
<WrapPanel Orientation="Vertical" Visibility="{Binding ElementName=CategorySupport, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel Margin="0,0,20,0" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Support</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
</WrapPanel>
<TextBlock Width="Auto" Margin="0,0,0,0">
<Hyperlink
NavigateUri="Open Data Folder"
RequestNavigate="Hyperlink_RequestNavigate"
Style="{StaticResource MaterialDesignBody2Hyperlink}">
<TextBlock Text="Open application data file folder" />
</Hyperlink>
</TextBlock>
<TextBlock Width="Auto" Margin="0,10,0,0">
<Hyperlink
NavigateUri="Download VCC Library"
RequestNavigate="Hyperlink_RequestNavigate"
Style="{StaticResource MaterialDesignBody2Hyperlink}">
<TextBlock Text="Download VC++ Library that is required by SimConnect to establish connection to MSFS" />
</Hyperlink>
</TextBlock>
<Button
Width="190"
Margin="0,10,0,0"
HorizontalAlignment="Left"
Command="{Binding DeleteAppCacheCommand}"
Content="Delete Application Cache"
IsEnabled="{Binding HasOrphanAppCache}"
KeyboardNavigation.AcceptsReturn="False"
Style="{StaticResource MaterialDesignOutlinedButton}"
ToolTip="This will delete all orphan application cache files in your Windows user account 'AppData/Local/Temp/.net/MSFSPopoutPanelManager' folder" />
<Button
Width="280"
Margin="0,10,0,0"
HorizontalAlignment="Left"
Command="{Binding RollBackCommand}"
Content="Rollback to previous version 3.4.6.0321"
KeyboardNavigation.AcceptsReturn="False"
Style="{StaticResource MaterialDesignOutlinedButton}"
ToolTip="This will rollback the application to previous version 3.4.6.0321. All changes since installing the latest v4.0.0 update will be lost."
Visibility="{Binding IsRollBackCommandVisible, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}" />
</WrapPanel>
<!-- About -->
<WrapPanel Orientation="Vertical" Visibility="{Binding ElementName=CategoryAbout, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel Margin="0,0,20,0" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">About</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
</WrapPanel>
<TextBlock Width="Auto" Margin="0,0,0,0">
<Hyperlink
NavigateUri="Version Info"
RequestNavigate="Hyperlink_RequestNavigate"
Style="{StaticResource MaterialDesignBody2Hyperlink}">
<WrapPanel>
<WrapPanel Margin="0">
<Label
Margin="0"
Padding="0"
Content="Version" />
<Label
Margin="5,0,0,0"
Padding="0"
Content="{Binding ApplicationVersion}" />
</WrapPanel>
</WrapPanel>
</Hyperlink>
</TextBlock>
<Label
Margin="0,5,0,0"
Padding="0"
Content="© 2022 Stanley Kwok. All rights reserved." />
<TextBlock Width="Auto" Margin="0,10,0,0">
<Hyperlink
NavigateUri="License"
RequestNavigate="Hyperlink_RequestNavigate"
Style="{StaticResource MaterialDesignBody2Hyperlink}">
<TextBlock Text="Public Release License" />
</Hyperlink>
</TextBlock>
</WrapPanel>
</StackPanel>
</ScrollViewer>
</DockPanel>
</UserControl>

View file

@ -0,0 +1,25 @@
using Microsoft.Extensions.DependencyInjection;
using MSFSPopoutPanelManager.MainApp.ViewModel;
using System;
using System.Windows.Controls;
using System.Windows.Navigation;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class HelpDrawer : UserControl
{
private HelpViewModel _viewModel;
public HelpDrawer()
{
_viewModel = App.AppHost.Services.GetRequiredService<HelpViewModel>();
InitializeComponent();
Loaded += (sender, e) => { DataContext = _viewModel; };
}
private void Hyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e)
{
_viewModel.HyperLinkCommand.Execute(e.Uri.ToString());
}
}
}

View file

@ -0,0 +1,352 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.PopOutPanelCard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.MainApp"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:profileDomain="clr-namespace:MSFSPopoutPanelManager.DomainModel.Profile;assembly=DomainModel"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Width="860"
mc:Ignorable="d">
<UserControl.Resources>
<system:Double x:Key="IconSize">22</system:Double>
<system:Double x:Key="ButtonSize">28</system:Double>
<system:Double x:Key="UpDownButtonSize">22</system:Double>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<DataTrigger
x:Key="TriggerIsProfileLocked"
Binding="{Binding ProfileData.ActiveProfile.IsLocked}"
Value="True">
<Setter Property="FrameworkElement.IsHitTestVisible" Value="False" />
<Setter Property="FrameworkElement.Opacity" Value="0.8" />
</DataTrigger>
<DataTrigger
x:Key="TriggerIsProfileUnlocked"
Binding="{Binding ProfileData.ActiveProfile.IsLocked}"
Value="False">
<Setter Property="FrameworkElement.IsHitTestVisible" Value="True" />
<Setter Property="Button.Foreground" Value="White" />
</DataTrigger>
<Style TargetType="{x:Type Expander}">
<Style.Triggers>
<StaticResource ResourceKey="TriggerIsProfileLocked" />
<StaticResource ResourceKey="TriggerIsProfileUnlocked" />
</Style.Triggers>
</Style>
<Style
x:Key="ToggleButton"
BasedOn="{StaticResource MaterialDesignSwitchToggleButton}"
TargetType="ToggleButton">
<Setter Property="Margin" Value="4,0,4,0" />
</Style>
<Style
x:Key="TextBlockLabel"
BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="14" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Margin" Value="5,0,40,0" />
<Setter Property="LineHeight" Value="18" />
</Style>
<Style BasedOn="{StaticResource MaterialDesignIconForegroundButton}" TargetType="Button" />
<Style
x:Key="PopOutPanelExpander"
BasedOn="{StaticResource CustomMaterialDesignExpander}"
TargetType="Expander">
<Style.Triggers>
<DataTrigger Binding="{Binding DataItem.IsPopOutSuccess}" Value="False">
<Setter Property="BorderBrush" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding DataItem.IsPopOutSuccess}" Value="True">
<Setter Property="BorderBrush" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding DataItem.IsSelectedPanelSource}" Value="True">
<Setter Property="Background" Value="SlateGray" />
<Setter Property="Opacity" Value="0.9" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="PopOutPanelExpanderVisibility" TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding DataItem.IsPopOutSuccess}" Value="True">
<Setter Property="FrameworkElement.Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding DataItem.IsPopOutSuccess}" Value="{x:Null}">
<Setter Property="FrameworkElement.Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding DataItem.IsPopOutSuccess}" Value="False">
<Setter Property="FrameworkElement.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="ErrorPanelExpanderVisibility" TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding DataItem.IsPopOutSuccess}" Value="True">
<Setter Property="FrameworkElement.Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding DataItem.IsPopOutSuccess}" Value="{x:Null}">
<Setter Property="FrameworkElement.Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding DataItem.IsPopOutSuccess}" Value="False">
<Setter Property="FrameworkElement.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="TxtBlockErrorMessage" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding DataItem.PanelType}" Value="{x:Static profileDomain:PanelType.CustomPopout}">
<Setter Property="Text" Value="Unable to pop out this panel. Please check the source panel circle defined for this panel is at the correct location and not blocked by other window. Also please check if this panel is a duplicate with another panel. Lastly, please close all instrumentation pop outs that were opened manually." />
<Setter Property="LineHeight" Value="18" />
</DataTrigger>
<DataTrigger Binding="{Binding DataItem.PanelType}" Value="{x:Static profileDomain:PanelType.BuiltInPopout}">
<Setter Property="Text" Value="Unable to configure this built-in panel. Please make sure this panel has been opened and popped out by the game." />
<Setter Property="LineHeight" Value="18" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style
x:Key="TxtBlockDisableWhenFullScreen"
BasedOn="{StaticResource TextBlockLabel}"
TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding DataItem.FullScreen}" Value="True">
<Setter Property="Foreground" Value="DimGray" />
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<DockPanel>
<Expander
x:Name="RootExpander"
Width="860"
materialDesign:ExpanderAssist.HorizontalHeaderPadding="10,0,10,0"
BorderThickness="1">
<Expander.Style>
<Style BasedOn="{StaticResource PopOutPanelExpander}" TargetType="Expander">
<Style.Triggers>
<StaticResource ResourceKey="TriggerIsProfileLocked" />
<StaticResource ResourceKey="TriggerIsProfileUnlocked" />
</Style.Triggers>
</Style>
</Expander.Style>
<Expander.Header>
<StackPanel Width="805" Orientation="Horizontal">
<StackPanel
Width="24"
Height="52"
Margin="0"
Style="{StaticResource ErrorPanelExpanderVisibility}">
<materialDesign:PopupBox
x:Name="PopupErrorMessage"
Margin="0,15,0,0"
Padding="5"
PlacementMode="RightAndAlignMiddles"
PopupHorizontalOffset="-10"
PopupUniformCornerRadius="10"
PopupVerticalOffset="15"
StaysOpen="True">
<materialDesign:PopupBox.ToggleContent>
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Foreground="Red"
Kind="AlertCircleOutline" />
</materialDesign:PopupBox.ToggleContent>
<TextBlock
Width="450"
Style="{StaticResource TxtBlockErrorMessage}"
TextWrapping="Wrap" />
</materialDesign:PopupBox>
</StackPanel>
<!-- Drag panel handle -->
<StackPanel
Width="24"
Height="52"
Margin="0"
Style="{StaticResource PopOutPanelExpanderVisibility}">
<materialDesign:PackIcon
x:Name="IconDrag"
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Margin="0,15,0,0"
Kind="Menu"
Opacity="0.5" />
</StackPanel>
<!-- Panel name text box -->
<TextBox
x:Name="TxtBoxPanelName"
Width="260"
Margin="8,0,0,0"
Padding="0"
VerticalAlignment="Center"
HorizontalContentAlignment="Left"
materialDesign:HintAssist.FloatingScale="1"
materialDesign:HintAssist.Hint="Panel Name"
GotFocus="TextBox_GotFocus"
IsEnabled="{c:Binding 'DataItem.PanelType==profileDomain:PanelType.CustomPopout'}"
KeyDown="TextBox_KeyDown"
SourceUpdated="Data_SourceUpdated"
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
Text="{Binding DataItem.PanelName, Mode=TwoWay, NotifyOnSourceUpdated=True}" />
<!-- Identify source panel button -->
<StackPanel
x:Name="StackPanelIdentifyPanelSource"
Width="440"
VerticalAlignment="Center"
Visibility="{c:Binding 'DataItem.PanelType==profileDomain:PanelType.CustomPopout and DataItem.PanelSource.X == null'}">
<Button
x:Name="BtnIdentifySourcePanel"
HorizontalAlignment="Center"
Command="{Binding AddPanelSourceLocationCommand}"
Content="Identify Source Panel Location"
Foreground="White"
KeyboardNavigation.AcceptsReturn="False"
Style="{StaticResource MaterialDesignOutlinedButton}"
ToolTip="Identify source aircraft instrumentation panel location" />
</StackPanel>
<!-- Panel configurations -->
<StackPanel
x:Name="StackPanelAdjustment"
Width="440"
Orientation="Horizontal"
Visibility="{c:Binding 'DataItem.PanelType==profileDomain:PanelType.BuiltInPopout or DataItem.PanelType==profileDomain:PanelType.HudBarWindow or DataItem.PanelSource.X != null'}">
<local:PanelConfigField
Margin="20,0,0,0"
BindingPath="Top"
DataItem="{Binding DataItem}"
SourceUpdated="Data_SourceUpdated" />
<local:PanelConfigField
Margin="20,0,0,0"
BindingPath="Left"
DataItem="{Binding DataItem}"
SourceUpdated="Data_SourceUpdated" />
<local:PanelConfigField
Margin="20,0,0,0"
BindingPath="Width"
DataItem="{Binding DataItem}"
SourceUpdated="Data_SourceUpdated" />
<local:PanelConfigField
Margin="20,0,0,0"
BindingPath="Height"
DataItem="{Binding DataItem}"
SourceUpdated="Data_SourceUpdated" />
<local:MoveAndResizePanelButton Margin="12,0,0,0" />
<local:TouchEnabledButton
Width="{StaticResource IconSize}"
Margin="12,0,0,0"
Visibility="{c:Binding 'DataItem.PanelType!=profileDomain:PanelType.HudBarWindow'}" />
</StackPanel>
<!-- Source panel icon -->
<StackPanel Margin="12,0,0,0" VerticalAlignment="Center">
<Button
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
IsHitTestVisible="{Binding ProfileData.ActiveProfile.IsEditingPanelSource}"
PreviewMouseDown="PanelSourceIcon_PreviewMouseDown"
PreviewMouseUp="PanelSourceIcon_PreviewMouseUp"
Style="{StaticResource MaterialDesignIconButton}"
ToolTip="Hold the left mouse button to show the panel source location"
Visibility="{c:Binding 'DataItem.PanelType==profileDomain:PanelType.CustomPopout'}">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="Crosshairs">
<materialDesign:PackIcon.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding ProfileData.ActiveProfile.IsEditingPanelSource}" Value="True">
<Setter Property="materialDesign:PackIcon.Foreground" Value="{Binding DataItem.PanelSource.Color}" />
</DataTrigger>
<DataTrigger Binding="{Binding ProfileData.ActiveProfile.IsEditingPanelSource}" Value="False">
<Setter Property="materialDesign:PackIcon.Opacity" Value="0.3" />
</DataTrigger>
</Style.Triggers>
</Style>
</materialDesign:PackIcon.Style>
</materialDesign:PackIcon>
</Button>
<StackPanel Width="{StaticResource IconSize}" Visibility="{c:Binding 'DataItem.PanelType!=profileDomain:PanelType.CustomPopOut'}" />
</StackPanel>
<!-- Delete panel button -->
<StackPanel Margin="12,0,0,0" VerticalAlignment="Center">
<Button
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
Margin="0"
Command="{Binding DeletePanelCommand}"
KeyboardNavigation.AcceptsReturn="False"
ToolTip="Delete panel">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="DeleteOutline" />
</Button>
</StackPanel>
</StackPanel>
</Expander.Header>
<StackPanel
Margin="42,8,24,16"
Orientation="Horizontal"
TextBlock.Foreground="{DynamicResource MaterialDesignBody}"
Visibility="{c:Binding 'DataItem.PanelType!=profileDomain:PanelType.HudBarWindow'}">
<WrapPanel>
<ToggleButton
x:Name="TglBtnAlwaysOnTop"
Margin="0,0,0,0"
IsChecked="{Binding DataItem.AlwaysOnTop, Mode=TwoWay, NotifyOnSourceUpdated=True}"
IsEnabled="{c:Binding !DataItem.FullScreen}"
SourceUpdated="Data_SourceUpdated"
Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TxtBlockDisableWhenFullScreen}" ToolTip="Set this panel to be always on top">
Always on Top
</TextBlock>
</WrapPanel>
<WrapPanel>
<ToggleButton
x:Name="TglBtnFullScreen"
Margin="0,0,0,0"
IsChecked="{Binding DataItem.FullScreen, Mode=TwoWay, NotifyOnSourceUpdated=True}"
SourceUpdated="Data_SourceUpdated"
Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}" ToolTip="Expand this panel into full screen (emulate keystroke Alt-Enter)">Full Screen Mode</TextBlock>
</WrapPanel>
<WrapPanel>
<ToggleButton
x:Name="TglBtnHideTitlebar"
Margin="0,0,0,0"
IsChecked="{Binding DataItem.HideTitlebar, Mode=TwoWay, NotifyOnSourceUpdated=True}"
IsEnabled="{c:Binding !DataItem.FullScreen}"
SourceUpdated="Data_SourceUpdated"
Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TxtBlockDisableWhenFullScreen}" ToolTip="Hide the title bar for this panel">
Hide Title Bar
</TextBlock>
</WrapPanel>
<WrapPanel>
<ToggleButton
x:Name="TglBtnAutoGameRefocus"
Margin="0,0,0,0"
IsChecked="{Binding DataItem.AutoGameRefocus, Mode=TwoWay, NotifyOnSourceUpdated=True}"
SourceUpdated="Data_SourceUpdated"
Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}" ToolTip="Automatic game refocus when clicking this panel or when using touch on this panel">Automatic Game Refocus</TextBlock>
</WrapPanel>
</StackPanel>
</Expander>
</DockPanel>
</UserControl>

View file

@ -0,0 +1,128 @@
using Microsoft.Extensions.DependencyInjection;
using MSFSPopoutPanelManager.DomainModel.Profile;
using MSFSPopoutPanelManager.MainApp.ViewModel;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class PopOutPanelCard : UserControl
{
private PopOutPanelCardViewModel _viewModel;
public static readonly DependencyProperty DataItemProperty = DependencyProperty.Register("DataItem", typeof(PanelConfig), typeof(PopOutPanelCard));
public PopOutPanelCard()
{
_viewModel = App.AppHost.Services.GetRequiredService<PopOutPanelCardViewModel>();
Loaded += (sender, e) =>
{
_viewModel.DataItem = DataItem;
this.DataContext = _viewModel;
InitializeComponent();
};
}
public PanelConfig DataItem
{
get { return (PanelConfig)GetValue(DataItemProperty); }
set { SetValue(DataItemProperty, value); }
}
private void TextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
Keyboard.ClearFocus();
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(RootExpander), RootExpander as IInputElement);
}
}
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
var txtBox = (TextBox)sender;
txtBox.Dispatcher.BeginInvoke(new Action(() => txtBox.SelectAll()));
}
private void BtnUpDown_Click(object sender, RoutedEventArgs e)
{
var button = (Button)sender;
var buttonType = button.Name.Substring(9);
var txtBox = this.FindName($"TxtBox{buttonType}") as TextBox;
if (txtBox != null)
txtBox.Dispatcher.BeginInvoke(new Action(() => txtBox.Focus()));
}
private string _oldText;
private void TxtBox_NumbersOnly(object sender, TextCompositionEventArgs e)
{
int result;
e.Handled = !(int.TryParse(e.Text, out result) || (e.Text.Trim() == "-"));
if (!e.Handled)
_oldText = ((TextBox)sender).Text;
}
private void TxtBox_NumbersOnlyTextChanged(object sender, TextChangedEventArgs e)
{
int result;
var txtBox = (TextBox)sender;
if (String.IsNullOrEmpty(txtBox.Text))
txtBox.Text = "0";
else if (!(int.TryParse(txtBox.Text, out result) || (txtBox.Text.Trim() == "-")))
{
txtBox.Text = _oldText;
}
}
private void Data_SourceUpdated(object sender, System.Windows.Data.DataTransferEventArgs e)
{
string? param = null;
if (sender is PanelConfigField)
param = ((PanelConfigField)sender).BindingPath;
else if (sender is ToggleButton)
param = ((ToggleButton)sender).Name.Substring(6);
else if (sender is TextBox)
param = ((TextBox)sender).Name.Substring(6);
if (!String.IsNullOrEmpty(param))
_viewModel.PanelAttributeUpdatedCommand.Execute(param);
}
private void PanelSourceIcon_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed && _viewModel.DataItem != null && _viewModel.ProfileData.ActiveProfile.IsEditingPanelSource)
_viewModel.DataItem.IsShownPanelSource = true;
}
private void PanelSourceIcon_PreviewMouseUp(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Released && _viewModel.DataItem != null && _viewModel.ProfileData.ActiveProfile.IsEditingPanelSource)
_viewModel.DataItem.IsShownPanelSource = false;
}
}
public class CustomTextBox : TextBox
{
static CustomTextBox()
{
TextProperty.OverrideMetadata(typeof(CustomTextBox), new FrameworkPropertyMetadata(null, null, CoerceChanged));
}
private static object CoerceChanged(DependencyObject d, object basevalue)
{
TextBox? txtBox = d as TextBox;
if (txtBox != null && basevalue == null)
{
return txtBox.Text;
}
return basevalue;
}
}
}

View file

@ -0,0 +1,24 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.EditPanelSourceButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d">
<UserControl.Resources>
<system:Double x:Key="IconSize">22</system:Double>
<system:Double x:Key="ButtonSize">28</system:Double>
</UserControl.Resources>
<DockPanel
Width="35"
Margin="0"
VerticalAlignment="Center">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Foreground="{Binding DataItem.PanelSource.Color}"
Kind="CrosshairsGps" />
</DockPanel>
</UserControl>

View file

@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class EditPanelSourceButton : UserControl
{
public EditPanelSourceButton()
{
InitializeComponent();
}
}
}

View file

@ -0,0 +1,38 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.MoveAndResizePanelButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d">
<UserControl.Resources>
<system:Double x:Key="IconSize">22</system:Double>
<system:Double x:Key="ButtonSize">28</system:Double>
</UserControl.Resources>
<ToggleButton
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
materialDesign:ToggleButtonAssist.OnContent="{materialDesign:PackIcon Kind=MoveResize,
Size={StaticResource IconSize}}"
Background="Transparent"
Command="{Binding MoveResizePanelCommand}"
IsChecked="{Binding DataItem.IsEditingPanel, Mode=TwoWay}"
KeyboardNavigation.AcceptsReturn="False"
ToolTip="Use keyboard commands to edit panel size &#x0a;and location. Please see help for more info.">
<ToggleButton.Style>
<Style BasedOn="{StaticResource MaterialDesignActionSecondaryToggleButton}" TargetType="ToggleButton">
<Style.Triggers>
<DataTrigger Binding="{Binding DataItem.IsEditingPanel}" Value="True">
<Setter Property="Foreground" Value="LimeGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="MoveResize" />
</ToggleButton>
</UserControl>

View file

@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class MoveAndResizePanelButton : UserControl
{
public MoveAndResizePanelButton()
{
InitializeComponent();
}
}
}

View file

@ -0,0 +1,51 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.MoveUpDownButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d">
<UserControl.Resources>
<system:Double x:Key="UpDownIconSize">14</system:Double>
<system:Double x:Key="UpDownButtonSize">22</system:Double>
</UserControl.Resources>
<StackPanel
Width="25"
Height="52"
Margin="0"
VerticalAlignment="Center">
<WrapPanel
Height="26"
Margin="0,4,0,-4"
VerticalAlignment="Center">
<Button
x:Name="BtnMovePanelUp"
Width="{StaticResource UpDownButtonSize}"
Height="{StaticResource UpDownButtonSize}"
Command="{Binding MovePanelUpCommand}"
KeyboardNavigation.AcceptsReturn="False"
ToolTip="Move panel Up">
<materialDesign:PackIcon
Width="{StaticResource UpDownButtonSize}"
Height="{StaticResource UpDownButtonSize}"
Kind="CaretUp" />
</Button>
</WrapPanel>
<WrapPanel Height="26" VerticalAlignment="Center">
<Button
x:Name="BtnMovePanelDown"
Width="{StaticResource UpDownButtonSize}"
Height="{StaticResource UpDownButtonSize}"
Command="{Binding MovePanelDownCommand}"
KeyboardNavigation.AcceptsReturn="False"
ToolTip="Move panel down">
<materialDesign:PackIcon
Width="{StaticResource UpDownButtonSize}"
Height="{StaticResource UpDownButtonSize}"
Kind="CaretDown" />
</Button>
</WrapPanel>
</StackPanel>
</UserControl>

View file

@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class MoveUpDownButton : UserControl
{
public MoveUpDownButton()
{
InitializeComponent();
}
}
}

View file

@ -0,0 +1,148 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.PanelConfigField"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d">
<UserControl.Resources>
<system:Double x:Key="UpDownIconSize">14</system:Double>
<system:Double x:Key="UpDownButtonSize">22</system:Double>
<Style BasedOn="{StaticResource MaterialDesignFloatingHintTextBox}" TargetType="TextBox">
<Setter Property="materialDesign:HintAssist.FloatingScale" Value="1" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Padding" Value="0" />
<EventSetter Event="KeyDown" Handler="TextBox_KeyDown" />
<EventSetter Event="GotFocus" Handler="TextBox_GotFocus" />
</Style>
<Style
x:Key="BtnPlusMinus"
BasedOn="{StaticResource MaterialDesignIconForegroundButton}"
TargetType="Button">
<Setter Property="Width" Value="30" />
<Setter Property="Height" Value="30" />
<Setter Property="Margin" Value="0,0,5,0" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Command" Value="{Binding PlusMinusCommand}" />
</Style>
</UserControl.Resources>
<StackPanel Orientation="Horizontal">
<TextBox
x:Name="TxtBoxData"
Width="50"
Margin="0,0,0,0"
materialDesign:HintAssist.Hint="{Binding BindingPath}"
IsEnabled="{c:Binding '!DataItem.FullScreen'}"
PreviewTextInput="TxtBox_NumbersOnly"
SourceUpdated="Data_SourceUpdated"
TextChanged="TxtBox_NumbersOnlyTextChanged" />
<materialDesign:PopupBox
x:Name="PopupBoxAdjustment"
Padding="5"
IsEnabled="{c:Binding '!DataItem.FullScreen'}"
PlacementMode="RightAndAlignMiddles"
PopupHorizontalOffset="-20"
PopupUniformCornerRadius="10"
StaysOpen="True">
<materialDesign:PopupBox.ToggleContent>
<Button
Width="{StaticResource UpDownButtonSize}"
Height="{StaticResource UpDownButtonSize}"
Margin="-4,12,0,0"
Click="BtnPopupBoxOpen_Click">
<materialDesign:PackIcon
Width="{StaticResource UpDownIconSize}"
Height="{StaticResource UpDownIconSize}"
Foreground="White"
Kind="ArrowUpDown" />
</Button>
</materialDesign:PopupBox.ToggleContent>
<StackPanel
Width="155"
Margin="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<Label
Width="60"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Content="1 pixel" />
<Button
Command="{Binding PlusMinusCommand}"
CommandParameter="1"
Style="{StaticResource BtnPlusMinus}"
ToolTip="Add 1 pixel">
<materialDesign:PackIcon
Width="{StaticResource UpDownIconSize}"
Height="{StaticResource UpDownIconSize}"
Kind="Plus" />
</Button>
<Button
Command="{Binding PlusMinusCommand}"
CommandParameter="-1"
Style="{StaticResource BtnPlusMinus}"
ToolTip="Subtract 1 pixel">
<materialDesign:PackIcon
Width="{StaticResource UpDownIconSize}"
Height="{StaticResource UpDownIconSize}"
Kind="Minus" />
</Button>
</StackPanel>
<Separator
Height="2"
Margin="0"
Padding="0" />
<StackPanel Orientation="Horizontal">
<Label
Width="60"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="10 pixels" />
<Button
Command="{Binding PlusMinusCommand}"
CommandParameter="10"
Style="{StaticResource BtnPlusMinus}"
ToolTip="Add 10 pixels">
<materialDesign:PackIcon
Width="{StaticResource UpDownIconSize}"
Height="{StaticResource UpDownIconSize}"
Kind="Plus" />
</Button>
<Button
Command="{Binding PlusMinusCommand}"
CommandParameter="-10"
Style="{StaticResource BtnPlusMinus}"
ToolTip="Subtract 10 pixels">
<materialDesign:PackIcon
Width="{StaticResource UpDownIconSize}"
Height="{StaticResource UpDownIconSize}"
Kind="Minus" />
</Button>
</StackPanel>
</StackPanel>
<Button
Width="{StaticResource UpDownButtonSize}"
Height="{StaticResource UpDownButtonSize}"
Margin="5,0,0,0"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Click="BtnPopupBoxClose_Click"
Style="{StaticResource MaterialDesignIconForegroundButton}">
<materialDesign:PackIcon
Width="{StaticResource UpDownIconSize}"
Height="{StaticResource UpDownIconSize}"
Kind="CloseBoxOutline" />
</Button>
</StackPanel>
</materialDesign:PopupBox>
</StackPanel>
</UserControl>

View file

@ -0,0 +1,103 @@
using Microsoft.Extensions.DependencyInjection;
using MSFSPopoutPanelManager.DomainModel.Profile;
using MSFSPopoutPanelManager.MainApp.ViewModel;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class PanelConfigField : UserControl
{
private PanelConfigFieldViewModel _viewModel;
public static readonly DependencyProperty DataItemProperty = DependencyProperty.Register("DataItem", typeof(PanelConfig), typeof(PanelConfigField));
public static readonly DependencyProperty BindingPathProperty = DependencyProperty.Register("BindingPath", typeof(string), typeof(PanelConfigField));
public static readonly RoutedEvent SourceUpdatedEvent = EventManager.RegisterRoutedEvent("SourceUpdatedEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(PanelConfigField));
public PanelConfigField()
{
_viewModel = App.AppHost.Services.GetRequiredService<PanelConfigFieldViewModel>();
Loaded += (sender, e) =>
{
_viewModel.DataItem = DataItem;
_viewModel.BindingPath = BindingPath;
_viewModel.SourceUpdatedEvent = SourceUpdatedEvent;
this.DataContext = _viewModel;
InitializeComponent();
Binding binding = new Binding($"DataItem.{BindingPath}");
binding.Mode = BindingMode.TwoWay;
binding.NotifyOnSourceUpdated = true;
TxtBoxData.SetBinding(TextBox.TextProperty, binding);
};
}
public PanelConfig DataItem
{
get { return (PanelConfig)GetValue(DataItemProperty); }
set { SetValue(DataItemProperty, value); }
}
public string BindingPath
{
get { return (string)GetValue(BindingPathProperty); }
set { SetValue(BindingPathProperty, value); }
}
private void TextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
Keyboard.ClearFocus();
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this.Parent), this.Parent as IInputElement);
}
}
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
TxtBoxData.Dispatcher.BeginInvoke(new Action(() => TxtBoxData.SelectAll()));
}
private string _oldText;
private void TxtBox_NumbersOnly(object sender, TextCompositionEventArgs e)
{
int result;
e.Handled = !(int.TryParse(e.Text, out result) || (e.Text.Trim() == "-"));
if (!e.Handled)
_oldText = ((TextBox)sender).Text;
}
private void TxtBox_NumbersOnlyTextChanged(object sender, TextChangedEventArgs e)
{
int result;
var txtBox = (TextBox)sender;
if (String.IsNullOrEmpty(txtBox.Text))
txtBox.Text = "0";
else if (!(int.TryParse(txtBox.Text, out result) || (txtBox.Text.Trim() == "-")))
{
txtBox.Text = _oldText;
}
}
private void Data_SourceUpdated(object sender, DataTransferEventArgs e)
{
_viewModel.DataUpdatedCommand.Execute(null);
}
private void BtnPopupBoxOpen_Click(object sender, RoutedEventArgs e)
{
PopupBoxAdjustment.IsPopupOpen = true;
}
private void BtnPopupBoxClose_Click(object sender, RoutedEventArgs e)
{
PopupBoxAdjustment.IsPopupOpen = false;
}
}
}

View file

@ -0,0 +1,38 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.TouchEnabledButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d">
<UserControl.Resources>
<system:Double x:Key="IconSize">22</system:Double>
<system:Double x:Key="ButtonSize">28</system:Double>
</UserControl.Resources>
<ToggleButton
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
materialDesign:ToggleButtonAssist.OnContent="{materialDesign:PackIcon Kind=HandBackRightOutline,
Size={StaticResource IconSize}}"
Background="Transparent"
Command="{Binding TouchEnabledCommand}"
IsChecked="{Binding DataItem.TouchEnabled, Mode=TwoWay}"
KeyboardNavigation.AcceptsReturn="False"
ToolTip="Toggle panel touch capability">
<ToggleButton.Style>
<Style BasedOn="{StaticResource MaterialDesignActionSecondaryToggleButton}" TargetType="ToggleButton">
<Style.Triggers>
<DataTrigger Binding="{Binding DataItem.TouchEnabled}" Value="True">
<Setter Property="Foreground" Value="LimeGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="HandBackRightOffOutline" />
</ToggleButton>
</UserControl>

View file

@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class TouchEnabledButton : UserControl
{
public TouchEnabledButton()
{
InitializeComponent();
}
}
}

View file

@ -0,0 +1,60 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.PopOutPanelList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dd="clr-namespace:GongSolutions.Wpf.DragDrop;assembly=GongSolutions.Wpf.DragDrop"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.MainApp"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<UserControl.Resources>
<local:DummyConverter x:Key="DummyConverter" />
</UserControl.Resources>
<ScrollViewer
Height="350"
materialDesign:ScrollViewerAssist.IsAutoHideEnabled="True"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Auto">
<ListView
x:Name="PopOutPanelCardList"
Height="Auto"
Margin="16,0,0,0"
Padding="0"
HorizontalAlignment="Left"
dd:DragDrop.IsDragSource="{c:Binding '!ProfileData.ActiveProfile.IsLocked'}"
dd:DragDrop.IsDropTarget="True"
dd:DragDrop.UseDefaultEffectDataTemplate="True"
ItemsSource="{Binding ProfileData.ActiveProfile.PanelConfigs, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<local:PopOutPanelCard Height="Auto" DataItem="{Binding '', Converter={StaticResource DummyConverter}}" />
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<Border
Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter
Margin="{TemplateBinding Padding}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</ScrollViewer>
</UserControl>

View file

@ -0,0 +1,28 @@
using System.Globalization;
using System;
using System.Windows.Controls;
using System.Windows.Data;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class PopOutPanelList : UserControl
{
public PopOutPanelList()
{
InitializeComponent();
}
}
public class DummyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
}

View file

@ -0,0 +1,45 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.PopOutPanelListEmpty"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<StackPanel Height="335" Margin="10,0,10,0">
<StackPanel HorizontalAlignment="Right" Orientation="Vertical">
<materialDesign:PackIcon
Width="40"
Height="40"
Margin="0,0,45,0"
HorizontalAlignment="Right"
Foreground="white"
Kind="ArrowUpBoldOutline" />
<TextBlock
Width="80"
Margin="0,5,24,0"
FontSize="16"
Foreground="gray"
TextAlignment="Center"
TextWrapping="Wrap">
Click here to add
</TextBlock>
</StackPanel>
<TextBlock
Margin="0,35,0,0"
HorizontalAlignment="Center"
FontSize="24"
Foreground="gray">
Add pop out panels to aircraft profile
</TextBlock>
<TextBlock
Margin="0,0,0,0"
HorizontalAlignment="Center"
FontSize="16"
Foreground="gray">
(by identifying instrumetation source panel location in the game)
</TextBlock>
</StackPanel>
</UserControl>

View file

@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class PopOutPanelListEmpty : UserControl
{
public PopOutPanelListEmpty()
{
InitializeComponent();
}
}
}

View file

@ -0,0 +1,544 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.PreferenceDrawer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:localcontrol="clr-namespace:MSFSPopoutPanelManager.MainApp.CustomControl"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="clr-namespace:MSFSPopoutPanelManager.MainApp.ViewModel"
Width="1024"
mc:Ignorable="d">
<UserControl.Resources>
<Style
x:Key="TextBlockHeading"
BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Width" Value="Auto" />
<Setter Property="Margin" Value="0,5,0,0" />
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style
x:Key="TextBlockSubheading"
BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="16" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Width" Value="Auto" />
<Setter Property="Margin" Value="0,5,0,5" />
</Style>
<Style
x:Key="ToggleButton"
BasedOn="{StaticResource MaterialDesignSwitchToggleButton}"
TargetType="ToggleButton">
<Setter Property="Margin" Value="4,0,4,0" />
</Style>
<Style
x:Key="TextBlockLabel"
BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="14" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Width" Value="700" />
<Setter Property="Margin" Value="5,0,0,0" />
<Setter Property="LineHeight" Value="20" />
</Style>
<Style TargetType="Line">
<Setter Property="Margin" Value="0,5,0,5" />
</Style>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
<DockPanel d:DataContext="{d:DesignInstance viewmodel:ApplicationViewModel}">
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden">
<TreeView
Width="210"
VerticalAlignment="Stretch"
DockPanel.Dock="Left">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
<Setter Property="Foreground" Value="#666666" />
<Setter Property="Background" Value="Transparent" />
<Style.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="White" />
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="White" />
</Style.Resources>
</Style>
</TreeView.ItemContainerStyle>
<TreeViewItem
Margin="-5,0,0,10"
Padding="0"
FontSize="14"
FontWeight="Bold"
Foreground="Gray"
Header="Preferences"
IsHitTestVisible="False" />
<TreeViewItem
x:Name="CategoryGeneralSettings"
Margin="0,0,0,10"
Padding="0"
Header="General Settings"
IsSelected="True" />
<TreeViewItem
x:Name="CategoryAutoPopOutPanelSettings"
Margin="0,0,0,10"
Header="Auto Pop Out Panel Settings" />
<TreeViewItem
x:Name="CategoryPopOutSettings"
Margin="0,0,0,10"
Header="Pop Out Settings" />
<TreeViewItem
x:Name="CategoryGameRefocusSettings"
Margin="0,0,0,10"
Header="Game Refocus Settings" />
<TreeViewItem
x:Name="CategoryTouchSettings"
Margin="0,0,0,10"
Header="Touch Settings" />
<TreeViewItem
x:Name="CategoryTrackIRSettings"
Margin="0,0,0,10"
Header="Track IR Settings" />
<TreeViewItem
x:Name="CategoryWindowedModeSettings"
Margin="0,0,0,10"
Header="Windowed Mode Settings" />
</TreeView>
</ScrollViewer>
<ScrollViewer
Width="780"
Height="565"
VerticalScrollBarVisibility="Auto">
<StackPanel Margin="0,-4,0,0">
<!-- General Settings -->
<WrapPanel Orientation="Vertical" Visibility="{Binding ElementName=CategoryGeneralSettings, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel
Width="Auto"
Margin="0,0,20,20"
Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Always on Top</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.GeneralSetting.AlwaysOnTop, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Pin the application on top of all open windows.</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,0,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Auto Start</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.GeneralSetting.AutoStart, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Enable auto start application when MSFS starts. This adds a XML config entry in EXE.xml file.</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,0,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Minimize to Tray</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.GeneralSetting.MinimizeToTray, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Minimize the application to system tray.</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,0,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Start Minimized</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.GeneralSetting.StartMinimized, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Start the application in minimized mode in system tray.</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,0,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Auto Close When Exiting MSFS</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.GeneralSetting.AutoClose, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Automatically close the application when exiting MSFS.</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,0,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Check for Update</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.GeneralSetting.CheckForUpdate, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Enable checking for update of application through Github.</TextBlock>
</WrapPanel>
</WrapPanel>
</WrapPanel>
<!-- Auto Pop Out Panel Settings -->
<WrapPanel Orientation="Vertical" Visibility="{Binding ElementName=CategoryAutoPopOutPanelSettings, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel Margin="0,0,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Enable Auto Pop Out Panels</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.AutoPopOutSetting.IsEnabled, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Automatic pop out panels when an aircraft livery is bound to a profile. The following steps will be performed.</TextBlock>
</WrapPanel>
<StackPanel Margin="46,0,0,0" Orientation="Vertical">
<TextBlock Margin="0,10,0,0" Style="{StaticResource TextBlockLabel}">
1. Detect flight start signal using SimConnect.
</TextBlock>
<TextBlock Margin="0,10,0,0" Style="{StaticResource TextBlockLabel}">
2. Wait for cockpit view to appear before executing pop out panel sequence.
</TextBlock>
<TextBlock Margin="0,10,0,0" Style="{StaticResource TextBlockLabel}">
3. If configured for profile on cold start, execute and detect instrumentation power on before executing pop out panel sequence.
</TextBlock>
</StackPanel>
</WrapPanel>
<WrapPanel Margin="0,0,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Ready to Fly Button Delay</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<StackPanel Orientation="Horizontal">
<localcontrol:NumericUpDown
Width="90"
Height="24"
FontSize="12"
Increment="1"
MaxValue="10"
MinValue="0"
Value="{Binding AppSettingData.ApplicationSetting.AutoPopOutSetting.ReadyToFlyDelay, Mode=TwoWay}" />
<TextBlock
Width="640"
Margin="10,0,0,0"
Style="{StaticResource TextBlockLabel}"
TextWrapping="Wrap">
Amount of time in seconds to delay auto pop out panels from starting after ready to fly button has been pressed automatically. Extending this delay helps resolve auto pop out failure because cockpit has not been loaded completely yet depending on the speed of your PC.
</TextBlock>
</StackPanel>
</WrapPanel>
</WrapPanel>
<!-- Pop Out Settings -->
<WrapPanel Orientation="Vertical" Visibility="{Binding ElementName=CategoryPopOutSettings, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel Margin="0,0,20,0" Orientation="Vertical">
<WrapPanel Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Enable Auto Panning</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.PopOutSetting.AutoPanning.IsEnabled, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Enable automatic panning of cockpit view when popping out panels. Auto Panning remembers the custom cockpit camera angle you used when defining the locations of pop out panel.</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Orientation="Vertical" Visibility="{Binding AppSettingData.ApplicationSetting.PopOutSetting.AutoPanning.IsEnabled, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<StackPanel
Width="Auto"
Margin="40,15,0,0"
VerticalAlignment="Top"
Orientation="Horizontal">
<WrapPanel>
<Label Content="Ctrl-Alt-" FontSize="14" />
<ComboBox
Width="35"
VerticalAlignment="top"
SelectedValue="{Binding AppSettingData.ApplicationSetting.PopOutSetting.AutoPanning.KeyBinding, Mode=TwoWay}"
SelectedValuePath="Tag">
<ComboBoxItem Content="0" Tag="0" />
<ComboBoxItem Content="1" Tag="1" />
<ComboBoxItem Content="2" Tag="2" />
<ComboBoxItem Content="3" Tag="3" />
<ComboBoxItem Content="4" Tag="4" />
<ComboBoxItem Content="5" Tag="5" />
<ComboBoxItem Content="6" Tag="6" />
<ComboBoxItem Content="7" Tag="7" />
<ComboBoxItem Content="8" Tag="8" />
<ComboBoxItem Content="9" Tag="9" />
</ComboBox>
</WrapPanel>
<TextBlock
Width="620"
Margin="10,3,0,0"
Style="{StaticResource TextBlockLabel}">
Configure key binding for saving and recalling of custom MSFS cockpit camera view when defining the locations of pop out panel. Requires binding keystroke to custom camera in MSFS control setting. (Default: Ctrl-Alt-0 to save and Alt-0 to load).
</TextBlock>
</StackPanel>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,20,20,0" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Minimize Pop Out Panel Manager During Pop Out</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.PopOutSetting.MinimizeDuringPopOut, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Minimize Pop Out Panel Manager during pop out process.</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,20,20,0" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Minimize Pop Out Panel Manager After Pop Out</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.PopOutSetting.MinimizeAfterPopOut, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Minimize Pop Out Panel Manager after all panels have been popped out.</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,20,20,0" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Enable Active Pause</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.PopOutSetting.AutoActivePause, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Enable active pause when panels are being popped out.</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,20,20,0" Orientation="Vertical">
<WrapPanel Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Enable Return to Predefined Camera View After Pop Out</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.PopOutSetting.AfterPopOutCameraView.IsEnabled, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Enable return to a predefined camera view after pop out.</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Orientation="Vertical" Visibility="{Binding AppSettingData.ApplicationSetting.PopOutSetting.AfterPopOutCameraView.IsEnabled, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel Margin="46,10,0,0" Orientation="Horizontal">
<ComboBox
Width="160"
HorizontalAlignment="Left"
VerticalAlignment="Center"
SelectedValue="{Binding AppSettingData.ApplicationSetting.PopOutSetting.AfterPopOutCameraView.CameraView, Mode=TwoWay}"
SelectedValuePath="Tag">
<ComboBoxItem Content="Cockpit Center View" Tag="CockpitCenterView" />
<ComboBoxItem Content="Custom Camera View" Tag="CustomCameraView" />
</ComboBox>
</WrapPanel>
</WrapPanel>
<WrapPanel Orientation="Vertical" Visibility="{Binding AppSettingData.ApplicationSetting.PopOutSetting.AfterPopOutCameraView.IsEnabledCustomCameraKeyBinding, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<StackPanel Margin="44,10,0,0" Orientation="Horizontal">
<Label Content="Alt-" FontSize="14" />
<ComboBox
Width="35"
HorizontalAlignment="Left"
VerticalAlignment="Top"
SelectedValue="{Binding AppSettingData.ApplicationSetting.PopOutSetting.AfterPopOutCameraView.KeyBinding, Mode=TwoWay}"
SelectedValuePath="Tag">
<ComboBoxItem Content="0" Tag="0" />
<ComboBoxItem Content="1" Tag="1" />
<ComboBoxItem Content="2" Tag="2" />
<ComboBoxItem Content="3" Tag="3" />
<ComboBoxItem Content="4" Tag="4" />
<ComboBoxItem Content="5" Tag="5" />
<ComboBoxItem Content="6" Tag="6" />
<ComboBoxItem Content="7" Tag="7" />
<ComboBoxItem Content="8" Tag="8" />
<ComboBoxItem Content="9" Tag="9" />
</ComboBox>
<TextBlock
Width="620"
Margin="10,3,0,0"
Style="{StaticResource TextBlockLabel}">
Configure key binding for custom camera view to load. Requires binding keystroke to custom camera in MSFS control setting. (Default: Alt-1 to load).
</TextBlock>
</StackPanel>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,20,20,0" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Enable Panel Reset When Profile Is Locked</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.PopOutSetting.EnablePanelResetWhenLocked, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">
Enable panel to go back to its original location when move if the profile is locked. Disable this setting will allow panel to be moved when profile is locked
but the profile setting will be unchanged. With this setting disable, Pop Out Panel Manager will no longer detect pop out panel's movement when profile is locked which may save some CPU cycles.
</TextBlock>
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,20,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Pop Out Title Bar Color Customization</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton
x:Name="TglBtnPopOutColorCustomizationEnable"
IsChecked="{Binding AppSettingData.ApplicationSetting.PopOutSetting.PopOutTitleBarCustomization.IsEnabled, Mode=TwoWay}"
Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">
Enable setting the color of title bar for pop out panel. The color is set in Hexidemical format of RGB color (RRGGBB). For example, black is "#000000" and white is "#FFFFFF".
</TextBlock>
</WrapPanel>
<WrapPanel Visibility="{Binding AppSettingData.ApplicationSetting.PopOutSetting.PopOutTitleBarCustomization.IsEnabled, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<Label Margin="42,10,0,0">Color:</Label>
<Label Margin="0,10,0,0">#</Label>
<TextBox
Width="50"
Height="22"
Margin="0,10,0,0"
VerticalAlignment="Top"
materialDesign:TextFieldAssist.CharacterCounterVisibility="Hidden"
AcceptsReturn="False"
IsEnabled="{Binding Path=IsChecked, ElementName=TglBtnPopOutColorCustomizationEnable}"
MaxLength="6"
Style="{StaticResource MaterialDesignTextBox}"
Text="{Binding AppSettingData.ApplicationSetting.PopOutSetting.PopOutTitleBarCustomization.HexColor, Mode=TwoWay}" />
</WrapPanel>
</WrapPanel>
<WrapPanel Margin="0,0,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Use Left Control + Right Control to Pop Out Panel</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.PopOutSetting.UseLeftRightControlToPopOut, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">
If your keyboard does not have a Right-Alt key to perform left click to pop out panel, you can map Left Ctrl + Right Ctrl in MSFS control setting to pop out
panel instead. For this feature to work, please map (CTRL + RIGHT CTRL) in Control Options => Miscellaneous => New UI Window Mode in the game
</TextBlock>
</WrapPanel>
</WrapPanel>
</WrapPanel>
<!-- Game Refocus Settings -->
<WrapPanel Orientation="Vertical" Visibility="{Binding ElementName=CategoryGameRefocusSettings, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel Margin="0,0,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Refocus Game Window</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.RefocusSetting.RefocusGameWindow.IsEnabled, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Automactically set focus back to game window after a period of inactivity when either clicking on a panel or touching a panel when touch feature is enabled. This will give you flight control back when using pop out panel to overcome the current MSFS limitation. This setting needs to be enabled for each profile and each pop out panel's automatic refocus setting to work.</TextBlock>
</WrapPanel>
<StackPanel
Margin="46,10,0,0"
Orientation="Horizontal"
Visibility="{Binding AppSettingData.ApplicationSetting.RefocusSetting.RefocusGameWindow.IsEnabled, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<localcontrol:NumericUpDown
Width="90"
Height="24"
FontSize="12"
Increment="0.1"
MaxValue="10"
MinValue="0.5"
Places="1"
Value="{Binding AppSettingData.ApplicationSetting.RefocusSetting.RefocusGameWindow.Delay, Mode=TwoWay}" />
<TextBlock
Width="620"
Margin="10,0,0,0"
Style="{StaticResource TextBlockLabel}">
Amount of time in seconds to wait for touch inactivity before input focus goes back to game window.
</TextBlock>
</StackPanel>
</WrapPanel>
</WrapPanel>
<!-- Touch Settings -->
<WrapPanel Orientation="Vertical" Visibility="{Binding ElementName=CategoryTouchSettings, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel Margin="0,0,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Touch Down Touch Up Delay</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<localcontrol:NumericUpDown
Width="90"
Height="24"
FontSize="12"
Increment="5"
MaxValue="100"
MinValue="0"
Value="{Binding AppSettingData.ApplicationSetting.TouchSetting.TouchDownUpDelay, Mode=TwoWay}" />
<TextBlock
Width="630"
Margin="10,0,0,0"
Style="{StaticResource TextBlockLabel}">
Amount of time in milliseconds to delay touch down and then touch up event when operating touch enabled panel. If your touch is not registering consistently, increasing this value may help.
</TextBlock>
<TextBlock
Margin="0,10,0,0"
FontSize="14"
TextWrapping="Wrap">
For panel display on direct connected touch monitor, 0 milliseconds work really well.<LineBreak /><LineBreak />
For panel display on a tablet using software such as Spacedesk, since there is higher latency for touch signal, increasing this value 5ms at a time may compensate for this latency.</TextBlock>
</WrapPanel>
</WrapPanel>
</WrapPanel>
<!-- TrackIR Settings -->
<WrapPanel Orientation="Vertical" Visibility="{Binding ElementName=CategoryTrackIRSettings, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel Margin="0,0,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Auto Disable Track IR</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.TrackIRSetting.AutoDisableTrackIR, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">Automactically disable Track IR during panel selections and pop out process. Track IR will be re-enabled once these processes are completed.</TextBlock>
</WrapPanel>
</WrapPanel>
</WrapPanel>
<!-- Windowed Mode Settings -->
<WrapPanel Orientation="Vertical" Visibility="{Binding ElementName=CategoryWindowedModeSettings, Path=IsSelected, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<WrapPanel Margin="0,0,20,20" Orientation="Vertical">
<TextBlock Style="{StaticResource TextBlockHeading}">Auto Resize MSFS Game Window (Used with Windowed Display Mode only)</TextBlock>
<Line
Stretch="Fill"
Stroke="Gray"
X2="1" />
<WrapPanel>
<ToggleButton IsChecked="{Binding AppSettingData.ApplicationSetting.WindowedModeSetting.AutoResizeMsfsGameWindow, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}">
Enable automatic resize of MSFS game window when using Windowed Display Mode. When playing the game in Windowed Display Mode, this setting is used to resize game window to match original size
and location when panel profile was initially defined. When this setting is first checked, current game window size and location will also be saved automatically.
</TextBlock>
</WrapPanel>
</WrapPanel>
</WrapPanel>
</StackPanel>
</ScrollViewer>
</DockPanel>
</UserControl>

View file

@ -0,0 +1,16 @@
using Microsoft.Extensions.DependencyInjection;
using MSFSPopoutPanelManager.MainApp.ViewModel;
using System;
using System.Windows;
using System.Windows.Controls;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class PreferenceDrawer : UserControl
{
public PreferenceDrawer()
{
InitializeComponent();
}
}
}

View file

@ -0,0 +1,392 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.ProfileCard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:domain="clr-namespace:MSFSPopoutPanelManager.DomainModel.Profile;assembly=DomainModel"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.MainApp"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:viewmodel="clr-namespace:MSFSPopoutPanelManager.MainApp.ViewModel"
Width="900"
Height="535"
mc:Ignorable="d">
<UserControl.Resources>
<local:StringToHudBarTypeConverter x:Key="StringToHudBarTypeConverter" />
<system:Double x:Key="IconSize">22</system:Double>
<system:Double x:Key="ButtonSize">28</system:Double>
<system:Double x:Key="UpDownIconSize">14</system:Double>
<system:Double x:Key="UpDownButtonSize">22</system:Double>
<DataTrigger
x:Key="TriggerIsDisabledAppInput"
Binding="{Binding IsDisabledAppInput}"
Value="True">
<Setter Property="FrameworkElement.Opacity" Value="0.6" />
<Setter Property="FrameworkElement.IsHitTestVisible" Value="False" />
</DataTrigger>
<Style
x:Key="ToggleButton"
BasedOn="{StaticResource MaterialDesignSwitchToggleButton}"
TargetType="ToggleButton">
<Setter Property="Margin" Value="4,0,4,0" />
</Style>
<Style
x:Key="TextBlockLabel"
BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock">
<Setter Property="FontSize" Value="14" />
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="Margin" Value="5,0,0,0" />
<Setter Property="LineHeight" Value="18" />
</Style>
<Style
x:Key="TxtBlockDisableWhenLocked"
BasedOn="{StaticResource TextBlockLabel}"
TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ProfileData.ActiveProfile.IsLocked}" Value="True">
<Setter Property="Foreground" Value="DimGray" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style
x:Key="CmbBoxDisableWhenLocked"
BasedOn="{StaticResource MaterialDesignComboBox}"
TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding ProfileData.ActiveProfile.IsLocked}" Value="True">
<Setter Property="Foreground" Value="DimGray" />
</DataTrigger>
</Style.Triggers>
</Style>
<ObjectDataProvider
x:Key="HudBarTypeEnum"
MethodName="GetValues"
ObjectType="system:Enum">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="domain:HudBarType" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</UserControl.Resources>
<Grid d:DataContext="{d:DesignInstance viewmodel:ProfileCardViewModel}">
<materialDesign:Card
x:Name="rootCard"
Width="900"
Height="535"
IsHitTestVisible="{c:Binding '!IsDisabledAppInput'}"
UniformCornerRadius="16">
<materialDesign:Card.Style>
<Style>
<Style.Triggers>
<StaticResource ResourceKey="TriggerIsDisabledAppInput" />
</Style.Triggers>
</Style>
</materialDesign:Card.Style>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="44" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="55" />
</Grid.RowDefinitions>
<materialDesign:ColorZone
Grid.Row="0"
materialDesign:ElevationAssist.Elevation="Dp2"
DockPanel.Dock="Top"
Mode="PrimaryDark">
<StackPanel Height="44" Margin="16,6,8,0">
<DockPanel>
<StackPanel Width="812" Orientation="Horizontal">
<TextBox
x:Name="TxtBoxProfileTitle"
Margin="8,0,0,0"
FontSize="22"
FontWeight="Medium"
Foreground="White"
KeyDown="TxtBoxProfileTitle_KeyDown"
Text="{Binding ProfileData.ActiveProfile.Name, Mode=TwoWay}">
<TextBox.Style>
<Style BasedOn="{StaticResource MaterialDesignTextBox}" TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ToggleButtonEditProfileTitle, Path=IsChecked}" Value="False">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="IsReadOnly" Value="true" />
<Setter Property="IsHitTestVisible" Value="false" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=ToggleButtonEditProfileTitle, Path=IsChecked}" Value="True">
<Setter Property="IsReadOnly" Value="false" />
<Setter Property="IsHitTestVisible" Value="true" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<ToggleButton
x:Name="ToggleButtonEditProfileTitle"
Width="16"
Height="16"
Margin="8,0,0,0"
materialDesign:ToggleButtonAssist.OnContent="{materialDesign:PackIcon Kind=PencilOff,
Size=14}"
Click="ToggleButtonEditProfileTitle_Click"
IsChecked="False"
Style="{StaticResource MaterialDesignActionDarkToggleButton}"
ToolTip="Edit aircraft profile name">
<materialDesign:PackIcon
Width="14"
Height="14"
Kind="Pencil" />
</ToggleButton>
</StackPanel>
<Button
x:Name="BtnDeleteProfile"
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
Command="{Binding DeleteProfileCommand}"
IsEnabled="True"
KeyboardNavigation.AcceptsReturn="False"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Delete profile">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="DeleteOutline" />
</Button>
</DockPanel>
</StackPanel>
</materialDesign:ColorZone>
<DockPanel Grid.Row="1">
<Expander Style="{DynamicResource CustomMaterialDesignExpander}">
<Expander.Header>
<StackPanel Width="800" Orientation="Horizontal">
<TextBlock
Height="20"
Margin="0,0,10,0"
Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
Active Aircraft:
</TextBlock>
<TextBlock Height="20" Text="{c:Binding 'FlightSimData.AircraftName == null ? &quot;Aircraft binding information is currently unavailable&quot; : FlightSimData.AircraftName'}">
<TextBlock.Style>
<Style BasedOn="{StaticResource MaterialDesignSubtitle1TextBlock}" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ProfileData.IsAircraftBoundToProfile}" Value="True">
<Setter Property="Foreground" Value="LightGreen" />
</DataTrigger>
<DataTrigger Binding="{Binding ProfileData.IsAircraftBoundToProfile}" Value="False">
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding ProfileData.IsAllowedAddAircraftBinding}" Value="False">
<Setter Property="Foreground" Value="White" />
<Setter Property="ToolTip" Value="Aircraft is currently bound to another profile" />
</DataTrigger>
<DataTrigger Binding="{Binding FlightSimData.HasAircraftName}" Value="False">
<Setter Property="Foreground" Value="AntiqueWhite" />
<Setter Property="ToolTip" Value="No aircraft has been loaded by the game yet" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<ToggleButton
Width="{StaticResource ButtonSize}"
Height="{StaticResource IconSize}"
Margin="10,0,0,0"
materialDesign:ToggleButtonAssist.OnContent="{materialDesign:PackIcon Kind=AirplaneMinus,
Size={StaticResource IconSize}}"
Background="Transparent"
Command="{Binding ToggleAircraftBindingCommand}"
IsChecked="{Binding ProfileData.IsAircraftBoundToProfile, Mode=OneWay}"
Style="{StaticResource MaterialDesignActionSecondaryToggleButton}"
ToolTip="Toggle aircraft binding to profile"
Visibility="{c:Binding 'FlightSimData.HasAircraftName and ProfileData.IsAllowedAddAircraftBinding'}">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="AirplanePlus" />
</ToggleButton>
</StackPanel>
</Expander.Header>
<StackPanel Margin="22,0,0,8">
<WrapPanel>
<ToggleButton IsChecked="{Binding ProfileData.ActiveProfile.ProfileSetting.PowerOnRequiredForColdStart, Mode=TwoWay}" Style="{StaticResource ToggleButton}" />
<TextBlock Style="{StaticResource TextBlockLabel}" ToolTip="During cold start, Pop Out Manager will first turn on power and avionics for the aircraft in order &#x0a;to pop out the instrumentation panels. After pop out is completed, Pop Out Manager will then &#x0a;turn off aircraft power and avionics to put the aircraft back to cold and dark state.">Power on is required to pop out panels on cold start (for G1000 based aircrafts ONLY if needed)</TextBlock>
</WrapPanel>
<WrapPanel Margin="0,8,0,0">
<ToggleButton
IsChecked="{Binding ProfileData.ActiveProfile.ProfileSetting.IncludeInGamePanels, Mode=TwoWay, NotifyOnTargetUpdated=True}"
IsEnabled="{c:Binding '!ProfileData.ActiveProfile.IsLocked',
Mode=OneWay}"
Style="{StaticResource ToggleButton}"
TargetUpdated="IncludeInGamePanel_TargetUpdated" />
<TextBlock Style="{StaticResource TxtBlockDisableWhenLocked}" ToolTip="Add in-game menu bar panels such as VFR Map, Checklist, ATC, etc. to profile to enable panel size and location management and touch support">Include in-game menu bar panels for pop out management and touch screen support</TextBlock>
</WrapPanel>
<StackPanel Margin="0,8,0,0" Orientation="Horizontal">
<WrapPanel>
<ToggleButton
IsChecked="{Binding ProfileData.ActiveProfile.ProfileSetting.HudBarConfig.IsEnabled, Mode=TwoWay, NotifyOnTargetUpdated=True}"
IsEnabled="{c:Binding '!ProfileData.ActiveProfile.IsLocked',
Mode=OneWay}"
Style="{StaticResource ToggleButton}"
TargetUpdated="AddHudBar_TargetUpdated" />
<TextBlock Style="{StaticResource TxtBlockDisableWhenLocked}" ToolTip="Add an HUD bar to the game">Add a HUD Bar</TextBlock>
<ComboBox
x:Name="ComboBoxHudBarType"
Width="130"
Margin="10,-4,0,0"
FontSize="14"
IsEnabled="{c:Binding '!ProfileData.ActiveProfile.IsLocked and ProfileData.ActiveProfile.ProfileSetting.HudBarConfig.IsEnabled',
Mode=OneWay}"
ItemsSource="{Binding HudBarTypes}"
SelectedValue="{Binding ProfileData.ActiveProfile.ProfileSetting.HudBarConfig.HudBarType, Mode=TwoWay, Converter={StaticResource StringToHudBarTypeConverter}}"
Style="{StaticResource CmbBoxDisableWhenLocked}" />
</WrapPanel>
</StackPanel>
</StackPanel>
</Expander>
</DockPanel>
<StackPanel Grid.Row="2">
<Separator Margin="0,-1,0,0" />
<WrapPanel Margin="24,8,0,4">
<TextBlock
Width="750"
Style="{StaticResource MaterialDesignBody1TextBlock}"
Text="Pop Out Panels" />
<ToggleButton
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
Margin="0,0,0,0"
materialDesign:ToggleButtonAssist.OnContent="{materialDesign:PackIcon Kind=CrosshairsGps,
Size={StaticResource IconSize}}"
Background="Transparent"
Command="{Binding ToggleEditPanelSourceCommand}"
IsChecked="{Binding ProfileData.ActiveProfile.IsEditingPanelSource, Mode=TwoWay}"
KeyboardNavigation.AcceptsReturn="False"
ToolTip="Toggle editing of panel source">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="Crosshairs" />
<ToggleButton.Style>
<Style BasedOn="{StaticResource MaterialDesignActionSecondaryToggleButton}" TargetType="ToggleButton">
<Style.Triggers>
<DataTrigger Binding="{Binding ProfileData.ActiveProfile.IsEditingPanelSource}" Value="True">
<Setter Property="Foreground" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
<Button
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
Margin="8,0,0,0"
Command="{Binding AddPanelCommand}"
KeyboardNavigation.AcceptsReturn="False"
Style="{StaticResource MaterialDesignIconForegroundButton}"
ToolTip="Add pop out panel">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="PlusThick" />
</Button>
<ToggleButton
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
Margin="8,0,0,0"
materialDesign:ToggleButtonAssist.OnContent="{materialDesign:PackIcon Kind=LockOutline,
Size={StaticResource IconSize}}"
Background="Transparent"
Command="{Binding ToggleLockProfileCommand}"
IsChecked="{Binding ProfileData.ActiveProfile.IsLocked, Mode=TwoWay}"
KeyboardNavigation.AcceptsReturn="False"
ToolTip="Lock and unlock pop out panel settings">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="UnlockedOutline" />
<ToggleButton.Style>
<Style BasedOn="{StaticResource MaterialDesignActionSecondaryToggleButton}" TargetType="ToggleButton">
<Style.Triggers>
<DataTrigger Binding="{Binding ProfileData.ActiveProfile.IsLocked}" Value="True">
<Setter Property="Foreground" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
</WrapPanel>
<local:PopOutPanelListEmpty Visibility="{c:Binding 'ProfileData.ActiveProfile.PanelConfigs.Count == 0'}" />
<local:PopOutPanelList Visibility="{c:Binding 'ProfileData.ActiveProfile.PanelConfigs.Count > 0'}" />
</StackPanel>
<StackPanel Grid.Row="3">
<Separator Margin="0,5,0,0" />
<Grid>
<Button
x:Name="BtnStartPopOut"
Width="Auto"
Margin="250,10,250,0"
Command="{Binding StartPopOutCommand}"
Content="Start Pop Out"
Foreground="White"
IsHitTestVisible="{c:Binding '!IsDisabledAppInput'}"
KeyboardNavigation.AcceptsReturn="False"
Style="{StaticResource MaterialDesignOutlinedButton}"
ToolTip="Start pop out process" />
<StackPanel>
<materialDesign:Snackbar
x:Name="SnackbarMessage"
HorizontalAlignment="Stretch"
Background="DimGray"
Foreground="White"
IsActive="{Binding ProfileData.ActiveProfile.IsEditingPanelSource}">
<materialDesign:SnackbarMessage>
<StackPanel
Width="800"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock Margin="160,0,0,0" VerticalAlignment="Center">Please complete editing of source panel locations by clicking</TextBlock>
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Margin="5,0,5,0"
Foreground="LightGreen"
Kind="CrosshairsGps" />
<TextBlock VerticalAlignment="Center">above to enable Start Pop Out</TextBlock>
</StackPanel>
</materialDesign:SnackbarMessage>
</materialDesign:Snackbar>
</StackPanel>
<StackPanel>
<materialDesign:Snackbar
x:Name="SnackbarMessage2"
HorizontalAlignment="Stretch"
Background="DimGray"
Foreground="White"
IsActive="{Binding ProfileData.ActiveProfile.HasUnidentifiedPanelSource}">
<materialDesign:SnackbarMessage>
<StackPanel
Width="800"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock Margin="250,0,0,0" VerticalAlignment="Center">Please identify all source panel locations to enable Start Pop Out</TextBlock>
</StackPanel>
</materialDesign:SnackbarMessage>
</materialDesign:Snackbar>
</StackPanel>
</Grid>
</StackPanel>
</Grid>
</materialDesign:Card>
</Grid>
</UserControl>

View file

@ -0,0 +1,72 @@
using Microsoft.Extensions.DependencyInjection;
using MSFSPopoutPanelManager.DomainModel.Profile;
using MSFSPopoutPanelManager.MainApp.ViewModel;
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class ProfileCard : UserControl
{
private ProfileCardViewModel? _viewModel;
public ProfileCard()
{
_viewModel = App.AppHost.Services.GetRequiredService<ProfileCardViewModel>();
InitializeComponent();
Loaded += (sender, e) => { DataContext = _viewModel; };
}
private void ToggleButtonEditProfileTitle_Click(object sender, RoutedEventArgs e)
{
ToggleButton? toggleButton = sender as ToggleButton;
if (toggleButton != null && toggleButton.IsChecked != null && (bool)toggleButton.IsChecked)
{
TxtBoxProfileTitle.Dispatcher.BeginInvoke(new Action(() => TxtBoxProfileTitle.Focus()));
TxtBoxProfileTitle.Dispatcher.BeginInvoke(new Action(() => TxtBoxProfileTitle.SelectAll()));
}
}
private void TxtBoxProfileTitle_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
ToggleButtonEditProfileTitle.IsChecked = false;
Keyboard.ClearFocus();
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(rootCard), rootCard as IInputElement);
}
}
private void IncludeInGamePanel_TargetUpdated(object sender, DataTransferEventArgs e)
{
_viewModel.IncludeInGamePanelUpdatedCommand.Execute(null);
}
private void AddHudBar_TargetUpdated(object sender, DataTransferEventArgs e)
{
_viewModel.AddHudBarUpdatedCommand.Execute(null);
}
}
[ValueConversion(typeof(DateTime), typeof(String))]
public class StringToHudBarTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString().Replace("_", " ");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string data = value.ToString();
return Enum.Parse<HudBarType>(data.Replace(" ", "_"));
}
}
}

View file

@ -0,0 +1,44 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.ProfileCardEmpty"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="clr-namespace:MSFSPopoutPanelManager.MainApp.ViewModel"
Width="900"
Height="535"
mc:Ignorable="d">
<Grid d:DataContext="{d:DesignInstance viewmodel:ProfileCardViewModel}">
<materialDesign:Card
Width="900"
Height="535"
UniformCornerRadius="16">
<StackPanel>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<TextBlock
Margin="0,16,5,0"
HorizontalAlignment="Center"
FontSize="16"
Foreground="gray">
Click here to start
</TextBlock>
<materialDesign:PackIcon
Width="40"
Height="40"
Margin="0,5,0,0"
HorizontalAlignment="Right"
Foreground="white"
Kind="ArrowRightBoldOutline" />
</StackPanel>
<TextBlock
Margin="0,210,0,0"
HorizontalAlignment="Center"
FontSize="24"
Foreground="gray">
Get started by adding a new aircraft profile
</TextBlock>
</StackPanel>
</materialDesign:Card>
</Grid>
</UserControl>

View file

@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace MSFSPopoutPanelManager.MainApp
{
public partial class ProfileCardEmpty : UserControl
{
public ProfileCardEmpty()
{
InitializeComponent();
}
}
}

View file

@ -0,0 +1,148 @@
<UserControl
x:Class="MSFSPopoutPanelManager.MainApp.ProfileCardList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dotNetKitControls="clr-namespace:DotNetKit.Windows.Controls;assembly=DotNetKit.Wpf.AutoCompleteComboBox"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:MSFSPopoutPanelManager.MainApp"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:viewmodel="clr-namespace:MSFSPopoutPanelManager.MainApp.ViewModel"
Width="1024"
mc:Ignorable="d">
<UserControl.Resources>
<system:Double x:Key="IconSize">22</system:Double>
<system:Double x:Key="ButtonSize">28</system:Double>
</UserControl.Resources>
<StackPanel d:DataContext="{d:DesignInstance viewmodel:ProfileCardListViewModel}" Orientation="Horizontal">
<DockPanel Width="62">
<Button
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
Margin="6,0,0,0"
Command="{Binding PreviousProfileCommand}"
IsEnabled="{c:Binding 'ProfileData.Profiles.Count > 1'}"
Style="{StaticResource MaterialDesignFloatingActionDarkButton}"
ToolTip="Previous aircraft profile">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="ChevronLeft" />
</Button>
</DockPanel>
<DockPanel Width="900">
<materialDesign:Transitioner x:Name="TransitionProfile" SelectedIndex="{Binding ProfileTransitionIndex}">
<materialDesign:TransitionerSlide x:Name="Slide1" ClipToBounds="True">
<materialDesign:TransitionerSlide.ForwardWipe>
<materialDesign:SlideWipe Duration="0:0:0.8" />
</materialDesign:TransitionerSlide.ForwardWipe>
<materialDesign:TransitionerSlide.BackwardWipe>
<materialDesign:SlideWipe Duration="0:0:0.8" />
</materialDesign:TransitionerSlide.BackwardWipe>
<materialDesign:TransitionerSlide.OpeningEffects>
<materialDesign:TransitionEffect Kind="SlideInFromRight" Duration="0:0:0.8" />
</materialDesign:TransitionerSlide.OpeningEffects>
<StackPanel>
<local:ProfileCardEmpty Visibility="{c:Binding 'ProfileData.Profiles.Count == 0'}" />
<local:ProfileCard Visibility="{c:Binding 'ProfileData.Profiles.Count > 0'}" />
</StackPanel>
</materialDesign:TransitionerSlide>
<materialDesign:TransitionerSlide x:Name="Slide2" ClipToBounds="True">
<materialDesign:TransitionerSlide.ForwardWipe>
<materialDesign:SlideWipe Duration="0:0:0.8" />
</materialDesign:TransitionerSlide.ForwardWipe>
<materialDesign:TransitionerSlide.BackwardWipe>
<materialDesign:SlideWipe Duration="0:0:0.8" />
</materialDesign:TransitionerSlide.BackwardWipe>
<materialDesign:TransitionerSlide.OpeningEffects>
<materialDesign:TransitionEffect Kind="SlideInFromRight" Duration="0:0:0.8" />
</materialDesign:TransitionerSlide.OpeningEffects>
<StackPanel>
<local:ProfileCardEmpty Visibility="{c:Binding 'ProfileData.Profiles.Count == 0'}" />
<local:ProfileCard Visibility="{c:Binding 'ProfileData.Profiles.Count > 0'}" />
</StackPanel>
</materialDesign:TransitionerSlide>
</materialDesign:Transitioner>
</DockPanel>
<DockPanel Width="62" Margin="-5,10,0,0">
<StackPanel Orientation="Vertical">
<Button
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
Command="{Binding AddProfileCommand}"
Style="{StaticResource MaterialDesignFloatingActionDarkButton}"
ToolTip="Add a new aircraft profile">
<materialDesign:PackIcon
Width="18"
Height="18"
Kind="FilePlusOutline" />
</Button>
<materialDesign:PopupBox
x:Name="PopupBoxFinder"
Margin="16,16,0,0"
materialDesign:ElevationAssist.Elevation="Dp0"
IsEnabled="{c:Binding 'ProfileData.Profiles.Count > 1'}"
PlacementMode="LeftAndAlignMiddles"
PopupHorizontalOffset="-10"
PopupUniformCornerRadius="4"
Style="{StaticResource MaterialDesignPopupBox}">
<materialDesign:PopupBox.ToggleContent>
<Button
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
Margin="0"
Padding="0"
Click="BtnPopupBoxFinder_Click"
Style="{StaticResource MaterialDesignFloatingActionDarkButton}"
ToolTip="Find an aircraft profile">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Foreground="White"
Kind="Magnify" />
</Button>
</materialDesign:PopupBox.ToggleContent>
<dotNetKitControls:AutoCompleteComboBox
x:Name="ComboBoxSearchProfile"
Width="250"
Margin="5,0,5,0"
Padding="5,0,5,0"
materialDesign:ComboBoxAssist.MaxLength="20"
materialDesign:HintAssist.Hint="Search Profile"
materialDesign:HintAssist.HintOpacity="0.3"
DisplayMemberPath="Name"
Foreground="White"
ItemsSource="{Binding ProfileData.Profiles}"
MaxDropDownHeight="400"
SelectedItem="{Binding SearchProfileSelectedItem}"
SelectedValuePath="Id"
Style="{StaticResource MaterialDesignComboBox}"
TextSearch.TextPath="Name">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SearchProfileSelectedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</dotNetKitControls:AutoCompleteComboBox>
</materialDesign:PopupBox>
<Button
Width="{StaticResource ButtonSize}"
Height="{StaticResource ButtonSize}"
Margin="0,170,0,0"
Command="{Binding NextProfileCommand}"
DockPanel.Dock="Top"
IsEnabled="{c:Binding 'ProfileData.Profiles.Count > 1'}"
Style="{StaticResource MaterialDesignFloatingActionDarkButton}"
ToolTip="Next aircraft profile">
<materialDesign:PackIcon
Width="{StaticResource IconSize}"
Height="{StaticResource IconSize}"
Kind="ChevronRight" />
</Button>
</StackPanel>
</DockPanel>
</StackPanel>
</UserControl>

Some files were not shown because too many files have changed in this diff Show more