Merge v3.4 branch
3
Addons/InGameToolbar/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
_Temp/
|
||||
Packages/
|
||||
PackagesMetadata/
|
BIN
Addons/InGameToolbar/MSFSLayoutGenerator.exe
Normal file
30
Addons/InGameToolbar/PackageDefinitions/pop-out-manager.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
<AssetPackage Name="pop-out-manager" Version="1.0.0">
|
||||
<ItemSettings>
|
||||
<ContentType>SPB</ContentType>
|
||||
<Title>pop-out-manager</Title>
|
||||
<Manufacturer>Stanley Kwok</Manufacturer>
|
||||
<Creator>Stanley Kwok</Creator>
|
||||
</ItemSettings>
|
||||
<Flags>
|
||||
<VisibleInStore>false</VisibleInStore>
|
||||
<CanBeReferenced>false</CanBeReferenced>
|
||||
</Flags>
|
||||
<AssetGroups>
|
||||
<AssetGroup Name="pop-out-manager">
|
||||
<Type>SPB</Type>
|
||||
<Flags>
|
||||
<FSXCompatibility>false</FSXCompatibility>
|
||||
</Flags>
|
||||
<AssetDir>PackageSources\pop-out-manager\</AssetDir>
|
||||
<OutputDir>InGamePanels\</OutputDir>
|
||||
</AssetGroup>
|
||||
<AssetGroup Name="html-ui">
|
||||
<Type>Copy</Type>
|
||||
<Flags>
|
||||
<FSXCompatibility>false</FSXCompatibility>
|
||||
</Flags>
|
||||
<AssetDir>PackageSources\html_ui\</AssetDir>
|
||||
<OutputDir>html_ui\</OutputDir>
|
||||
</AssetGroup>
|
||||
</AssetGroups>
|
||||
</AssetPackage>
|
|
@ -0,0 +1,138 @@
|
|||
body.modal-open {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body.modal-open .stepDialog {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
pop-out-manager #popOutManager .ingameUiWrapper .horizontalLayout {
|
||||
background-color: var(--backgroundColorPanel);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1em 1em;
|
||||
}
|
||||
|
||||
pop-out-manager #popOutManager .ingameUiWrapper .horizontalLayout #profileInfo {
|
||||
flex: 1 1 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
pop-out-manager #popOutManager .ingameUiWrapper .horizontalLayout #panelConfigTable {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: solid 1px white;
|
||||
flex: 20 1 0;
|
||||
}
|
||||
|
||||
pop-out-manager #popOutManager .ingameUiWrapper .horizontalLayout #panelConfigButtons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
margin: 2em 1em 1em 1em;
|
||||
flex: 2 1 0;
|
||||
}
|
||||
|
||||
pop-out-manager #popOutManager .ingameUiWrapper .panelConfigButton {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 6em;
|
||||
height: 3em;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
pop-out-manager #popOutManager .ingameUiWrapper .lockPanelsButton {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 10em;
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
.panelRow {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.panelRow .column1 {
|
||||
width: 34%;
|
||||
}
|
||||
|
||||
.panelRow .column2 {
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
.panelRow .column3 {
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
.panelRow .column4 {
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
.panelRow .column5 {
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
.panelRow .column6 {
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
.panelRow .column7 {
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
.panelRow .column8 {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.panelRow .column9 {
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
.panelCell {
|
||||
display: flex;
|
||||
padding: 0.7em 1em;
|
||||
margin: 0;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
border-bottom: solid 1px white;
|
||||
border-right: solid 1px white;
|
||||
}
|
||||
|
||||
.panelCell:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.panelCell .alignCenter {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.panelCell ui-input .default-input input {
|
||||
padding-left: 0.25em;
|
||||
padding-right: 0.25em;
|
||||
}
|
||||
|
||||
.panelCell checkbox-element .checkbox .choices .iconWrapper .icon {
|
||||
width: 1.6em;
|
||||
height: 1.6em;
|
||||
}
|
||||
|
||||
|
||||
.stepDialog {
|
||||
display: none; /* Hidden by default */
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 1; /* Sit on top */
|
||||
left: 20vw;
|
||||
top: 20vh;
|
||||
width: 60vw; /* Full width */
|
||||
height: 60vh; /* Full height */
|
||||
overflow: auto; /* Enable scroll if needed */
|
||||
background-color: rgba(161, 153, 153, 1);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="/SCSS/common.css" />
|
||||
<link rel="stylesheet" href="PopOutManager.css" />
|
||||
|
||||
<script type="text/javascript" src="/JS/coherent.js"></script>
|
||||
<script type="text/javascript" src="/JS/common.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/JS/buttons.js"></script>
|
||||
<script type="text/javascript" src="/JS/Services/ToolBarPanels.js"></script>
|
||||
<script type="text/javascript" src="/JS/Services/Notifications.js"></script>
|
||||
|
||||
<link rel="import" href="/templates/virtualScroll/virtualScroll.html" />
|
||||
<link rel="import" href="/templates/NewPushButton/NewPushButton.html" />
|
||||
<link rel="import" href="/templates/DropDown/DropDown.html" />
|
||||
<link rel="import" href="/templates/tabMenu/tabMenu.html" />
|
||||
<link rel="import" href="/templates/ingameUi/ingameUi.html" />
|
||||
<link rel="import" href="/templates/ingameUiHeader/ingameUiHeader.html" />
|
||||
<link rel="import" href="/templates/checkbox/checkbox.html" />
|
||||
<link rel="import" href="/templates/uiInput/uiInput.html" />
|
||||
|
||||
|
||||
<script type="text/javascript" src="PopOutManager.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="border-box">
|
||||
<pop-out-manager>
|
||||
<ingame-ui id="popOutManager" panel-id="PANEL_POP_OUT_MANAGER" title=""
|
||||
class="ingameUiFrame panelInvisible condensedPanel" resize="both" min-width="160" min-height="90"
|
||||
content-fit="true" auto-inside>
|
||||
|
||||
|
||||
<div id="panelSelection" class="horizontalLayout" style="display:block">
|
||||
<div id="profileInfo">Panel Locations and Settings - <span id="planeProfileName"></span></div>
|
||||
<div id="panelSelection">
|
||||
Panel Selection
|
||||
<drop-down id="dropdownProfile" style="width:50vw"></drop-down>
|
||||
<icon-button id="addProfile" data-url="/icons/open.svg"></icon-button>
|
||||
<icon-button id="deleteProfile" data-url="/icons/reduce.svg"></icon-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="panelConfiguration" class="horizontalLayout" style="display:none">
|
||||
<div id="profileInfo">Panel Locations and Settings - <span id="planeProfileName"></span></div>
|
||||
<virtual-scroll id="panelConfigTable" direction="y">
|
||||
</virtual-scroll>
|
||||
<div id="panelConfigButtons">
|
||||
<new-push-button id="btnMinusTen" title="-10 px" class="panelConfigButton"></new-push-button>
|
||||
<new-push-button id="btnMinusOne" title="-1 px" class="panelConfigButton"></new-push-button>
|
||||
<new-push-button id="btnPlusOne" title="+1 px" class="panelConfigButton"></new-push-button>
|
||||
<new-push-button id="btnPlusTen" title="+10 px" class="panelConfigButton" toolTip="test"></new-push-button>
|
||||
<div>
|
||||
<new-push-button id="btnLockPanels" title="Lock Panels" class="lockPanelsButton"></new-push-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ingame-ui>
|
||||
</pop-out-manager>
|
||||
|
||||
|
||||
<div id="dialogBegin" class="stepDialog">
|
||||
<new-push-button id="btnStartPopOut" parentStep="stepBegin" title="Start Pop Out" class="stepButton"></new-push-button>
|
||||
<new-push-button id="btnCreateNewProfile" parentStep="stepBegin" title="Create New Profile" class="stepButton"></new-push-button>
|
||||
<new-push-button id="btnAdjustProfile" parentStep="stepBegin" title="Adjust Profile" class="stepButton"></new-push-button>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,388 @@
|
|||
class PopOutManagerPanelElement extends UIElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.ingameUi = null;
|
||||
this.isInitialized = false;
|
||||
this.panelActive = false;
|
||||
this.webSocket = null;
|
||||
this.webSocketConnected = false;
|
||||
this.webSocketInterval = null;
|
||||
|
||||
this.tryConnectWebSocket = () => {
|
||||
this.lockPanel(true);
|
||||
|
||||
this.webSocket = new WebSocket("ws://localhost:27011/ws");
|
||||
|
||||
this.webSocket.onopen = () => {
|
||||
clearInterval(this.webSocketInterval);
|
||||
this.webSocketConnected = true;
|
||||
};
|
||||
|
||||
this.webSocket.onclose = () => {
|
||||
this.webSocketConnected = false;
|
||||
|
||||
// Clear panel table
|
||||
this.createPanelConfigTableHeader(this.panelConfigTable);
|
||||
this.lockPanel(true);
|
||||
|
||||
this.webSocketInterval = setInterval(() => {
|
||||
if (!this.webSocketConnected)
|
||||
this.tryConnectWebSocket();
|
||||
}, 2000)
|
||||
};
|
||||
|
||||
this.webSocket.onerror = () => {
|
||||
this.webSocket.close();
|
||||
};
|
||||
|
||||
this.webSocket.onmessage = (event) => {
|
||||
if (event.data !== undefined) {
|
||||
|
||||
var panelData = JSON.parse(event.data);
|
||||
|
||||
// only recreate panel rows if panel is refreshed (minimize/maximize, pop out)
|
||||
if (this.panelActive && document.getElementsByClassName("panelRow").length == 1)
|
||||
this.createPanelConfigTableBody(this.panelConfigTable, panelData);
|
||||
|
||||
if (panelData !== undefined && panelData !== null && panelData.length !== 0) {
|
||||
this.lockPanel(false);
|
||||
this.bindPanelData(panelData);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.webSocketInterval = setInterval(() => {
|
||||
if (!this.webSocketConnected)
|
||||
this.tryConnectWebSocket();
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this.ingameUi = this.querySelector('ingame-ui');
|
||||
this.panelSelection = document.getElementById("panelSelection");
|
||||
this.panelConfiguration = document.getElementById("panelConfiguration");
|
||||
this.planeProfileName = document.getElementById("planeProfileName");
|
||||
this.panelConfigTable = document.getElementById("panelConfigTable");
|
||||
this.btnLockPanels = this.querySelector('#btnLockPanels');
|
||||
this.btnPlusTen = this.querySelector('#btnPlusTen');
|
||||
this.btnPlusOne = this.querySelector('#btnPlusOne');
|
||||
this.btnMinusTen = this.querySelector('#btnMinusTen');
|
||||
this.btnMinusOne = this.querySelector('#btnMinusOne');
|
||||
|
||||
this.dropdownProfile = this.querySelector("#dropdownProfile");
|
||||
this.addProfile = document.getElementById("addProfile");
|
||||
this.deleteProfile = document.getElementById("deleteProfile");
|
||||
|
||||
this.stepBeginDialog = document.getElementById("stepBeginDialog");
|
||||
|
||||
|
||||
this.closeDialog = document.getElementById("closeDialog");
|
||||
|
||||
//this.deleteProfile.disable(!this.deleteProfile.disabled);
|
||||
|
||||
//this.dropdownProfile.addEventListener("select", (event) = {});
|
||||
|
||||
let workflow = new Workflow(this);
|
||||
workflow.bindAllButtonEvents();
|
||||
workflow.stepBegin();
|
||||
|
||||
|
||||
this.addProfile.addEventListener("click", (event) => {
|
||||
addProfileDialog.style.display = "block";
|
||||
//this.ingameUi.toggleExternPanel(true);
|
||||
document.body.classList.toggle('modal-open');
|
||||
|
||||
// let value = new DataValue;
|
||||
// value.name = "New Profile " + dropDownValues.length;
|
||||
// value.ID = dropDownValues.length;
|
||||
// dropDownValues.push(value);
|
||||
// this.dropdownProfile.setData(dropDownValues, dropDownValues.length - 1);
|
||||
});
|
||||
|
||||
this.deleteProfile.addEventListener("click", (event) => {
|
||||
// dropDownValues.splice(-1,1);
|
||||
// this.dropdownProfile.setData(dropDownValues, dropDownValues.length - 1);
|
||||
});
|
||||
|
||||
|
||||
this.isLocked = false;
|
||||
|
||||
let profiles = ["Kodiak", "172", "737"];
|
||||
let dropDownValues = [];
|
||||
profiles.forEach((profile, index) => {
|
||||
let value = new DataValue;
|
||||
value.name = profile;
|
||||
value.ID = index;
|
||||
dropDownValues.push(value);
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.dropdownProfile.setData(dropDownValues, 2)
|
||||
|
||||
|
||||
}, 1000);
|
||||
|
||||
|
||||
|
||||
this.btnLockPanels.addEventListener("click", () => {
|
||||
if (this.btnLockPanels.disabled)
|
||||
return;
|
||||
|
||||
this.isLocked = !this.isLocked;
|
||||
|
||||
this.lockPanel(this.isLocked);
|
||||
|
||||
if (this.isLocked) {
|
||||
this.btnLockPanels.disabled = false;
|
||||
this.btnLockPanels.title = "Unlock Panels";
|
||||
this.btnLockPanels.style.backgroundColor = "red";
|
||||
}
|
||||
else {
|
||||
this.btnLockPanels.disabled = false;
|
||||
this.btnLockPanels.title = "Lock Panels";
|
||||
this.btnLockPanels.style.backgroundColor = null;
|
||||
}
|
||||
|
||||
this.panelSelection.style.display = 'block';
|
||||
this.panelConfiguration.style.display = 'none';
|
||||
});
|
||||
|
||||
|
||||
if (this.ingameUi) {
|
||||
this.ingameUi.addEventListener("panelActive", (e) => {
|
||||
//document.getElementsByClassName("Extern")[0].style.display = "none"; // disable extern button
|
||||
this.createPanelConfigTableHeader(this.panelConfigTable);
|
||||
|
||||
this.panelActive = true;
|
||||
this.lockPanel(true);
|
||||
|
||||
if (this.webSocketConnected)
|
||||
this.webSocket.send("RequestPanelData");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
createPanelConfigTableHeader(panelConfigTable) {
|
||||
let panelRow;
|
||||
|
||||
// remove all child of panelConfigTable
|
||||
while (panelConfigTable.firstChild) {
|
||||
panelConfigTable.removeChild(panelConfigTable.firstChild);
|
||||
}
|
||||
|
||||
// create header
|
||||
panelRow = this.createPanelRow(true);
|
||||
panelRow.appendChild(this.createPanelCell("div", "column1", "Panel Name"));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column2", "X-Pos"));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column3", "Y-Pos"));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column4", "Width"));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column5", "Height"));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column6", "Always on Top"));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column7", "Hide Title Bar"));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column8", "Full Screen Mode"));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column9", "Touch Enabled"));
|
||||
panelConfigTable.appendChild(panelRow);
|
||||
}
|
||||
|
||||
createPanelConfigTableBody(panelConfigTable, panelData) {
|
||||
let panelRow;
|
||||
|
||||
if (panelConfigTable !== undefined) {
|
||||
for (let index = 0; index < panelData.length; index++) {
|
||||
panelRow = this.createPanelRow(false);
|
||||
panelRow.appendChild(this.createPanelCell("div", "column1", this.createUiInput("PanelName_" + index)));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column2", this.createUiInput("XPos_" + index)));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column3", this.createUiInput("YPos_" + index)));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column4", this.createUiInput("Width_" + index)));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column5", this.createUiInput("Height_" + index)));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column6", this.createCheckbox("AlwaysOnTop_" + index)));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column7", this.createCheckbox("HideTitleBar_" + index)));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column8", this.createCheckbox("FullScreenMode_" + index)));
|
||||
panelRow.appendChild(this.createPanelCell("div", "column9", this.createCheckbox("TouchEnabled_" + index)));
|
||||
panelConfigTable.appendChild(panelRow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createPanelRow(isHeaderRow) {
|
||||
let panelRow = document.createElement("div");
|
||||
|
||||
if (isHeaderRow)
|
||||
panelRow.classList.add("panelHeaderRow");
|
||||
|
||||
panelRow.classList.add("panelRow");
|
||||
|
||||
return panelRow;
|
||||
}
|
||||
|
||||
createPanelCell(cellType, classes = null, childElement) {
|
||||
let cell = document.createElement(cellType);
|
||||
cell.classList.add("panelCell");
|
||||
|
||||
if (classes !== undefined || classes !== null) {
|
||||
if (typeof (classes) === "string") {
|
||||
cell.classList.add(classes);
|
||||
}
|
||||
else {
|
||||
for (let index = 0; index < classes.length; index++)
|
||||
cell.classList.add(classes[index]);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof (childElement) === "string")
|
||||
cell.innerHTML = `<div class='alignCenter'>${childElement}</div>`;
|
||||
else
|
||||
cell.appendChild(childElement);
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
createUiInput(id) {
|
||||
let input = document.createElement("ui-input");
|
||||
input.type = "text";
|
||||
input.id = id;
|
||||
return input;
|
||||
}
|
||||
|
||||
createCheckbox(id) {
|
||||
let checkbox = document.createElement("checkbox-element");
|
||||
checkbox.id = id;
|
||||
checkbox.style.width = "1em";
|
||||
return checkbox;
|
||||
}
|
||||
|
||||
lockPanel(isLocked) {
|
||||
if (this.panelActive) {
|
||||
let uiInputs = document.getElementsByTagName("ui-input");
|
||||
for (let index = 0; index < uiInputs.length; index++) {
|
||||
uiInputs[index].disabled = isLocked;
|
||||
}
|
||||
|
||||
let checkboxes = document.getElementsByTagName("checkbox-element");
|
||||
for (let index = 0; index < checkboxes.length; index++) {
|
||||
checkboxes[index].SetData({ sTitle: "", bToggled: checkboxes.toggled, bDisabled: isLocked });
|
||||
checkboxes[index].RefreshValue();
|
||||
}
|
||||
|
||||
this.btnLockPanels.disabled = isLocked;
|
||||
if(isLocked)
|
||||
{
|
||||
this.btnLockPanels.title = "Lock Panels";
|
||||
this.btnLockPanels.style.backgroundColor = null;
|
||||
}
|
||||
|
||||
this.btnPlusTen.disabled = isLocked;
|
||||
this.btnPlusOne.disabled = isLocked;
|
||||
this.btnMinusTen.disabled = isLocked;
|
||||
this.btnMinusOne.disabled = isLocked;
|
||||
}
|
||||
}
|
||||
|
||||
bindPanelData(panelData) {
|
||||
if (this.panelActive) {
|
||||
panelData.forEach((panel, index) => {
|
||||
this.bindUiInput("PanelName_" + index, panel.panelName, "text");
|
||||
this.bindUiInput("XPos_" + index, panel.xPos);
|
||||
this.bindUiInput("YPos_" + index, panel.yPos);
|
||||
this.bindUiInput("Width_" + index, panel.width);
|
||||
this.bindUiInput("Height_" + index, panel.height);
|
||||
this.bindCheckbox("AlwaysOnTop_" + index, panel.alwaysOnTop);
|
||||
this.bindCheckbox("HideTitleBar_" + index, panel.hideTitleBar);
|
||||
this.bindCheckbox("FullScreenMode_" + index, panel.fullScreenMode);
|
||||
this.bindCheckbox("TouchEnabled_" + index, panel.touchEnabled);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
bindUiInput(id, value, type) {
|
||||
let input = document.getElementById(id);
|
||||
input.style.width = "100%";
|
||||
input.setValue(value);
|
||||
|
||||
if (type === "text")
|
||||
input.children[0].children[0].style.textAlign = "left";
|
||||
}
|
||||
|
||||
bindCheckbox(id, value) {
|
||||
let checkbox = document.getElementById(id);
|
||||
checkbox.SetData({ sTitle: "", bToggled: value, bDisabled: false });
|
||||
checkbox.RefreshValue();
|
||||
}
|
||||
}
|
||||
window.customElements.define("pop-out-manager", PopOutManagerPanelElement);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Workflow {
|
||||
constructor(owner) {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
get workflowSteps() {
|
||||
return [
|
||||
{
|
||||
name: 'stepBegin',
|
||||
results:
|
||||
[
|
||||
{ value: 'stepCreateNewProfile' },
|
||||
{ value: 'stepStartPopOut' },
|
||||
{ value: 'stepAdjustProfile' }
|
||||
]
|
||||
}]
|
||||
}
|
||||
|
||||
getFuncName() {
|
||||
return this.getFuncName.caller.name;
|
||||
}
|
||||
|
||||
bindAllButtonEvents(){
|
||||
let stepButtons = document.getElementsByClassName("stepButton");
|
||||
Array.from(stepButtons).forEach(btn => {
|
||||
btn.addEventListener("click", (e) => {
|
||||
let parentStep = e.target.getAttribute("parentStep");
|
||||
let resultValue = e.target.id.replace("btn", "step");
|
||||
this.handleButtonClick(parentStep, resultValue);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
handleButtonClick(currentStep, resultValue) {
|
||||
let step = this.workflowSteps.find(c => c.name == currentStep);
|
||||
let result = step.results.find(f => f.value == resultValue);
|
||||
|
||||
if(result != null)
|
||||
{
|
||||
let func = this[result.value];
|
||||
func();
|
||||
}
|
||||
}
|
||||
|
||||
openDialog(step)
|
||||
{
|
||||
this.owner.stepBeginDialog = document.getElementById(step.replace("step", "dialog"));
|
||||
this.owner.stepBeginDialog.style.display = "block";
|
||||
document.body.classList.toggle("modal-open");
|
||||
}
|
||||
|
||||
stepBegin() {
|
||||
this.openDialog("stepBegin");
|
||||
}
|
||||
|
||||
stepStartPopOut() {
|
||||
var a = ""
|
||||
}
|
||||
|
||||
stepCreateNewProfile() {
|
||||
var a = ""
|
||||
}
|
||||
|
||||
stepAdjustProfile() {
|
||||
var a = ""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="64.000000pt" height="64.000000pt" viewBox="0 0 64.000000 64.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
|
||||
<g transform="translate(0.000000,64.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M160 315 l0 -165 160 0 160 0 0 50 c0 43 -3 50 -20 50 -16 0 -20 -7
|
||||
-20 -30 l0 -30 -120 0 -120 0 0 125 0 125 30 0 c23 0 30 4 30 20 0 17 -7 20
|
||||
-50 20 l-50 0 0 -165z"/>
|
||||
<path d="M358 447 l32 -33 -50 -49 c-47 -46 -49 -50 -33 -67 16 -18 19 -17 68
|
||||
32 l51 50 27 -27 27 -28 0 78 0 77 -77 0 -77 0 32 -33z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 740 B |
29
Addons/InGameToolbar/PackageSources/layout.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"content": [
|
||||
{
|
||||
"path": "html_ui/icons/toolbar/POP_OUT_MANAGER.svg",
|
||||
"size": 740,
|
||||
"date": 133013771715368552
|
||||
},
|
||||
{
|
||||
"path": "html_ui/InGamePanels/PopOutManagerPanel/PopOutManager.css",
|
||||
"size": 1921,
|
||||
"date": 133015501657300878
|
||||
},
|
||||
{
|
||||
"path": "html_ui/InGamePanels/PopOutManagerPanel/PopOutManager.html",
|
||||
"size": 3194,
|
||||
"date": 133023923647110813
|
||||
},
|
||||
{
|
||||
"path": "html_ui/InGamePanels/PopOutManagerPanel/PopOutManager.js",
|
||||
"size": 10854,
|
||||
"date": 133023924937932717
|
||||
},
|
||||
{
|
||||
"path": "InGamePanels/pop-out-manager.spb",
|
||||
"size": 684,
|
||||
"date": 133023927549810418
|
||||
}
|
||||
]
|
||||
}
|
15
Addons/InGameToolbar/PackageSources/manifest.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"dependencies": [],
|
||||
"content_type": "UNKNOWN",
|
||||
"title": "pop-out-manager",
|
||||
"manufacturer": "Stanley Kwok",
|
||||
"creator": "Stanley Kwok",
|
||||
"package_version": "1.0.0",
|
||||
"minimum_game_version": "1.27.9",
|
||||
"release_notes": {
|
||||
"neutral": {
|
||||
"LastUpdate": "",
|
||||
"OlderHistory": ""
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="Windows-1252"?>
|
||||
<SimBase.Document Type="InGamePanels" version="1,0">
|
||||
<Filename>InGamePanel_PopOutManager.spb</Filename>
|
||||
<InGamePanels.InGamePanelDefinition id="PANEL_POP_OUT_MANAGER" Name="Pop Out Manager" url="html_UI/ingamePanels/PopOutManagerPanel/PopOutManager.html" resizeDirections="Both" minWidth="80" minHeight="40" defaultWidth="120" defaultHeight="50" defaultTop="30" defaultLeft="5" icon="POP_OUT_MANAGER" buttonVisible="true">
|
||||
</InGamePanels.InGamePanelDefinition>
|
||||
</SimBase.Document>
|
1
Addons/InGameToolbar/build.bat
Normal file
|
@ -0,0 +1 @@
|
|||
FOR %%i IN (*.xml) DO "%MSFS_SDK%\Tools\bin\fspackagetool.exe" %%i
|
BIN
Addons/InGameToolbar/icon.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
7
Addons/InGameToolbar/pop-out-manager.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<Project Version="2" Name="pop-out-manager" FolderName="Packages">
|
||||
<OutputDirectory>.</OutputDirectory>
|
||||
<TemporaryOutputDirectory>_Temp</TemporaryOutputDirectory>
|
||||
<Packages>
|
||||
<Package>PackageDefinitions\pop-out-manager.xml</Package>
|
||||
</Packages>
|
||||
</Project>
|
160
ArduinoAgent/Arduino/Arduino.ino
Normal file
|
@ -0,0 +1,160 @@
|
|||
#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);
|
||||
}
|
BIN
ArduinoAgent/Arduino/fritzing/Fritzing Sketch.fzz
Normal file
BIN
ArduinoAgent/Arduino/fritzing/KY-040_AZ_Rotary_encoder_v0.fzpz
Normal file
BIN
ArduinoAgent/Arduino/libraries/NewEncoder-master.zip
Normal file
BIN
ArduinoAgent/Arduino/libraries/keypad.zip
Normal file
197
ArduinoAgent/Arduino/with_keypad/Arduino-with-keypad.ino
Normal file
|
@ -0,0 +1,197 @@
|
|||
#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);
|
||||
}
|
37
ArduinoAgent/ArduinoAgent.csproj
Normal file
|
@ -0,0 +1,37 @@
|
|||
<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.0.0</Version>
|
||||
<AssemblyVersion>3.4.0.0</AssemblyVersion>
|
||||
<FileVersion>3.4.0.0</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>
|
62
ArduinoAgent/ArduinoInputData.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
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
|
||||
}
|
||||
}
|
143
ArduinoAgent/ArduinoProvider.cs
Normal file
|
@ -0,0 +1,143 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using Microsoft.FlightSimulator.SimConnect;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MSFSPopoutPanelManager.FsConnector
|
||||
{
|
||||
public class DataDefinition
|
||||
{
|
||||
public static List<(string PropName, string SimConnectName, string SimConnectUnit, SIMCONNECT_DATATYPE SimConnectDataType, Type ObjectType)> GetDefinition()
|
||||
{
|
||||
var def = new List<(string, string, string, SIMCONNECT_DATATYPE, Type)>
|
||||
{
|
||||
("Title", "Title", null, SIMCONNECT_DATATYPE.STRING256, typeof(string)),
|
||||
("ElectricalMasterBattery", "ELECTRICAL MASTER BATTERY", "Bool", SIMCONNECT_DATATYPE.FLOAT64, typeof(bool)),
|
||||
("TrackIREnable", "TRACK IR ENABLE", "Bool", SIMCONNECT_DATATYPE.FLOAT64, typeof(bool)),
|
||||
("AtcOnParkingSpot", "ATC ON PARKING SPOT", "Bool", SIMCONNECT_DATATYPE.FLOAT64, typeof(bool))
|
||||
};
|
||||
|
||||
return def;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
namespace MSFSPopoutPanelManager.FsConnector
|
||||
{
|
||||
public enum SimConnectDefinition
|
||||
{
|
||||
SimConnectDataStruct
|
||||
}
|
||||
|
||||
public enum NotificationGroup
|
||||
{
|
||||
GROUP0
|
||||
}
|
||||
|
||||
public enum DataRequest
|
||||
{
|
||||
REQUEST_1
|
||||
}
|
||||
|
||||
public enum SimConnectSystemEvent
|
||||
{
|
||||
FOURSECS,
|
||||
SIMSTART,
|
||||
SIMSTOP,
|
||||
FLIGHTLOADED,
|
||||
VIEW,
|
||||
PAUSED,
|
||||
NONE
|
||||
};
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<AssemblyName>FsConnector</AssemblyName>
|
||||
<PackageId>MSFS 2020 Popout Panel Manager FsConnector</PackageId>
|
||||
<Product>MSFS 2020 Popout Panel Manager FsConnector</Product>
|
||||
<Version>3.3.7.0</Version>
|
||||
<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.FsConnector</RootNamespace>
|
||||
<Platforms>x64</Platforms>
|
||||
<AssemblyVersion>3.3.7.0</AssemblyVersion>
|
||||
<FileVersion>3.3.7.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Shared\Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.FlightSimulator.SimConnect">
|
||||
<HintPath>Resources\Managed\Microsoft.FlightSimulator.SimConnect.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="resources\SimConnect.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>SimConnect.dll</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,15 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MSFSPopoutPanelManager.FsConnector
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public class SimConnectStruct
|
||||
{
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x100)]
|
||||
public string Prop01;
|
||||
|
||||
public double Prop02;
|
||||
public double Prop03;
|
||||
public double Prop04;
|
||||
}
|
||||
}
|
|
@ -1,209 +0,0 @@
|
|||
using Microsoft.FlightSimulator.SimConnect;
|
||||
using MSFSPopoutPanelManager.Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Dynamic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MSFSPopoutPanelManager.FsConnector
|
||||
{
|
||||
public class SimConnector
|
||||
{
|
||||
private const int MSFS_CONNECTION_RETRY_TIMEOUT = 1000; // timeout to retry connection to MSFS via Simconnect in milliseconds
|
||||
private const int WM_USER_SIMCONNECT = 0x402;
|
||||
private SimConnect _simConnect;
|
||||
private System.Timers.Timer _timer;
|
||||
|
||||
public event EventHandler<EventArgs<dynamic>> OnReceivedData;
|
||||
public event EventHandler OnConnected;
|
||||
public event EventHandler OnDisconnected;
|
||||
public event EventHandler<EventArgs<SimConnectSystemEvent>> OnReceiveSystemEvent;
|
||||
|
||||
public dynamic SimData { get; set; }
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_timer = new System.Timers.Timer();
|
||||
_timer.Interval = MSFS_CONNECTION_RETRY_TIMEOUT;
|
||||
_timer.Enabled = true;
|
||||
_timer.Elapsed += (source, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_simConnect == null)
|
||||
{
|
||||
_simConnect = new SimConnect("MSFS Pop Out Panel Manager", Process.GetCurrentProcess().MainWindowHandle, WM_USER_SIMCONNECT, null, 0);
|
||||
|
||||
_simConnect.OnRecvQuit += HandleOnRecvQuit;
|
||||
_simConnect.OnRecvException += HandleOnRecvException;
|
||||
_simConnect.OnRecvSimobjectDataBytype += HandleOnRecvSimobjectDataBytype;
|
||||
_simConnect.OnRecvEvent += HandleOnReceiveEvent;
|
||||
|
||||
_simConnect.SubscribeToSystemEvent(SimConnectSystemEvent.SIMSTART, "SimStart");
|
||||
_simConnect.SubscribeToSystemEvent(SimConnectSystemEvent.SIMSTOP, "SimStop");
|
||||
_simConnect.SubscribeToSystemEvent(SimConnectSystemEvent.VIEW, "View");
|
||||
|
||||
// Setup SimConnect data structure definition using SimConnectStruct and SimConnect data definitions
|
||||
var definitions = DataDefinition.GetDefinition();
|
||||
foreach (var (PropName, SimConnectName, SimConnectUnit, SimConnectDataType, ObjectType) in definitions)
|
||||
_simConnect.AddToDataDefinition(SimConnectDefinition.SimConnectDataStruct, SimConnectName, SimConnectUnit, SimConnectDataType, 0.0f, SimConnect.SIMCONNECT_UNUSED);
|
||||
_simConnect.RegisterDataDefineStruct<SimConnectStruct>(SimConnectDefinition.SimConnectDataStruct);
|
||||
|
||||
// Setup SimEvent mapping
|
||||
foreach (var item in Enum.GetValues(typeof(ActionEvent)))
|
||||
{
|
||||
if (item.ToString().StartsWith("KEY_"))
|
||||
_simConnect.MapClientEventToSimEvent((ActionEvent)item, item.ToString()[4..]);
|
||||
}
|
||||
|
||||
_timer.Enabled = false;
|
||||
|
||||
System.Threading.Thread.Sleep(2000);
|
||||
|
||||
Debug.WriteLine("SimConnect is connected");
|
||||
|
||||
OnConnected?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// handle SimConnect instantiation error when MSFS is not connected
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_timer.Enabled = false;
|
||||
_simConnect = null;
|
||||
}
|
||||
|
||||
public void StopAndReconnect()
|
||||
{
|
||||
_simConnect = null;
|
||||
_timer.Enabled = true;
|
||||
}
|
||||
|
||||
public void RequestData()
|
||||
{
|
||||
if (_simConnect != null)
|
||||
try
|
||||
{
|
||||
_simConnect.RequestDataOnSimObjectType(DataRequest.REQUEST_1, SimConnectDefinition.SimConnectDataStruct, 0, SIMCONNECT_SIMOBJECT_TYPE.USER);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex.Message != "0xC00000B0")
|
||||
{
|
||||
Debug.WriteLine($"SimConnect request data exception: {ex.Message}");
|
||||
StopAndReconnect();
|
||||
OnDisconnected?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveMessage()
|
||||
{
|
||||
if (_simConnect != null)
|
||||
try
|
||||
{
|
||||
_simConnect.ReceiveMessage();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex.Message != "0xC00000B0")
|
||||
Debug.WriteLine($"SimConnect receive message exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void TransmitActionEvent(ActionEvent eventID, uint data)
|
||||
{
|
||||
if (_simConnect != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_simConnect.TransmitClientEvent(0U, eventID, data, NotificationGroup.GROUP0, SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var eventName = eventID.ToString()[4..]; // trim out KEY_ prefix
|
||||
Debug.WriteLine($"SimConnect transmit event exception: EventName: {eventName} - {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDataObject(SimConnectStruct simConnectStruct)
|
||||
{
|
||||
if (_simConnect != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_simConnect.SetDataOnSimObject(SimConnectDefinition.SimConnectDataStruct, SimConnect.SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_DATA_SET_FLAG.DEFAULT, simConnectStruct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"Unable to set SimConnect variable: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOnRecvQuit(SimConnect sender, SIMCONNECT_RECV data)
|
||||
{
|
||||
OnDisconnected?.Invoke(this, null);
|
||||
|
||||
StopAndReconnect();
|
||||
// Try to reconnect again
|
||||
_timer.Enabled = true;
|
||||
}
|
||||
|
||||
private void HandleOnRecvException(SimConnect sender, SIMCONNECT_RECV_EXCEPTION data)
|
||||
{
|
||||
var exception = (SIMCONNECT_EXCEPTION)data.dwException;
|
||||
|
||||
switch (exception)
|
||||
{
|
||||
case SIMCONNECT_EXCEPTION.DATA_ERROR:
|
||||
case SIMCONNECT_EXCEPTION.NAME_UNRECOGNIZED:
|
||||
case SIMCONNECT_EXCEPTION.EVENT_ID_DUPLICATE:
|
||||
return;
|
||||
default:
|
||||
Debug.WriteLine($"MSFS Error - {exception}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOnRecvSimobjectDataBytype(SimConnect sender, SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE data)
|
||||
{
|
||||
if (data.dwRequestID == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var simConnectStruct = (SimConnectStruct)data.dwData[0];
|
||||
var simConnectStructFields = typeof(SimConnectStruct).GetFields();
|
||||
var simData = new ExpandoObject();
|
||||
|
||||
var definition = DataDefinition.GetDefinition();
|
||||
int i = 0;
|
||||
foreach (var item in definition)
|
||||
{
|
||||
simData.TryAdd(item.PropName, Convert.ChangeType(simConnectStructFields[i++].GetValue(simConnectStruct), item.ObjectType));
|
||||
}
|
||||
SimData = simData;
|
||||
|
||||
OnReceivedData?.Invoke(this, new EventArgs<dynamic>(simData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"SimConnect receive data exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOnReceiveEvent(SimConnect sender, SIMCONNECT_RECV_EVENT data)
|
||||
{
|
||||
var systemEvent = ((SimConnectSystemEvent)data.uEventID);
|
||||
OnReceiveSystemEvent?.Invoke(this, new EventArgs<SimConnectSystemEvent>(systemEvent));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,14 +5,8 @@ VisualStudioVersion = 17.2.32602.215
|
|||
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}") = "Model", "Model\Model.csproj", "{4A778C1A-3782-4312-842D-33AA58A9D6D4}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "Shared\Shared.csproj", "{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Provider", "Provider\Provider.csproj", "{933E7D03-883D-4970-9AD7-7A2B5D6C3671}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FsConnector", "FsConnector\FsConnector.csproj", "{023426F4-9FD2-4198-B9F4-83F0B55B88FC}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{15FC98CD-0A69-437B-A5E5-67D025DB5CDC}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitignore = .gitignore
|
||||
|
@ -23,42 +17,176 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
VERSION.md = VERSION.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
|
||||
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsAgent", "WindowsAgent\WindowsAgent.csproj", "{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}"
|
||||
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}"
|
||||
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}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TouchPanelAgent", "TouchPanelAgent\TouchPanelAgent.csproj", "{2F36A227-C92C-4B42-B1E4-480015492357}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
DebugTouchPanel|Any CPU = DebugTouchPanel|Any CPU
|
||||
DebugTouchPanel|x64 = DebugTouchPanel|x64
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
ReleaseTouchPanel|Any CPU = ReleaseTouchPanel|Any CPU
|
||||
ReleaseTouchPanel|x64 = ReleaseTouchPanel|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Debug|x64.Build.0 = Debug|x64
|
||||
{54712A0A-B344-45E4-85C4-0A913305A0E6}.DebugTouchPanel|Any CPU.ActiveCfg = DebugTouchPanel|x64
|
||||
{54712A0A-B344-45E4-85C4-0A913305A0E6}.DebugTouchPanel|Any CPU.Build.0 = DebugTouchPanel|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|Any CPU.ActiveCfg = Release|x64
|
||||
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Release|Any CPU.Build.0 = Release|x64
|
||||
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Release|x64.ActiveCfg = Release|x64
|
||||
{54712A0A-B344-45E4-85C4-0A913305A0E6}.Release|x64.Build.0 = Release|x64
|
||||
{4A778C1A-3782-4312-842D-33AA58A9D6D4}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4A778C1A-3782-4312-842D-33AA58A9D6D4}.Debug|x64.Build.0 = Debug|x64
|
||||
{4A778C1A-3782-4312-842D-33AA58A9D6D4}.Release|x64.ActiveCfg = Release|x64
|
||||
{4A778C1A-3782-4312-842D-33AA58A9D6D4}.Release|x64.Build.0 = Release|x64
|
||||
{54712A0A-B344-45E4-85C4-0A913305A0E6}.ReleaseTouchPanel|Any CPU.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{54712A0A-B344-45E4-85C4-0A913305A0E6}.ReleaseTouchPanel|Any CPU.Build.0 = ReleaseTouchPanel|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|Any CPU.ActiveCfg = Debug|x64
|
||||
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.Debug|Any CPU.Build.0 = 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}.DebugTouchPanel|Any CPU.ActiveCfg = DebugTouchPanel|x64
|
||||
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.DebugTouchPanel|Any CPU.Build.0 = DebugTouchPanel|x64
|
||||
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64
|
||||
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|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.Build.0 = Release|x64
|
||||
{933E7D03-883D-4970-9AD7-7A2B5D6C3671}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{933E7D03-883D-4970-9AD7-7A2B5D6C3671}.Debug|x64.Build.0 = Debug|x64
|
||||
{933E7D03-883D-4970-9AD7-7A2B5D6C3671}.Release|x64.ActiveCfg = Release|x64
|
||||
{933E7D03-883D-4970-9AD7-7A2B5D6C3671}.Release|x64.Build.0 = Release|x64
|
||||
{023426F4-9FD2-4198-B9F4-83F0B55B88FC}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{023426F4-9FD2-4198-B9F4-83F0B55B88FC}.Debug|x64.Build.0 = Debug|x64
|
||||
{023426F4-9FD2-4198-B9F4-83F0B55B88FC}.Release|x64.ActiveCfg = Release|x64
|
||||
{023426F4-9FD2-4198-B9F4-83F0B55B88FC}.Release|x64.Build.0 = Release|x64
|
||||
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.ReleaseTouchPanel|Any CPU.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.ReleaseTouchPanel|Any CPU.Build.0 = ReleaseTouchPanel|x64
|
||||
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{4BDDE1F9-FBDD-479A-B88E-B27D0513C046}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64
|
||||
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Debug|Any CPU.ActiveCfg = Debug|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.Build.0 = Debug|x64
|
||||
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.DebugTouchPanel|Any CPU.ActiveCfg = DebugTouchPanel|x64
|
||||
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.DebugTouchPanel|Any CPU.Build.0 = DebugTouchPanel|x64
|
||||
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64
|
||||
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|x64
|
||||
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Release|Any CPU.Build.0 = Release|x64
|
||||
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Release|x64.ActiveCfg = Release|x64
|
||||
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.Release|x64.Build.0 = Release|x64
|
||||
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.ReleaseTouchPanel|Any CPU.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.ReleaseTouchPanel|Any CPU.Build.0 = ReleaseTouchPanel|x64
|
||||
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{BDD878A3-EF5B-43C7-94B7-CEFA04C67A9E}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64
|
||||
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.DebugTouchPanel|Any CPU.ActiveCfg = DebugTouchPanel|x64
|
||||
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.DebugTouchPanel|Any CPU.Build.0 = DebugTouchPanel|x64
|
||||
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64
|
||||
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|x64
|
||||
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.Release|Any CPU.Build.0 = Release|x64
|
||||
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.Release|x64.ActiveCfg = Release|x64
|
||||
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.ReleaseTouchPanel|Any CPU.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{E72F813F-EE30-4384-B02F-EB5D4BCFEC49}.ReleaseTouchPanel|Any CPU.Build.0 = ReleaseTouchPanel|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|Any CPU.ActiveCfg = Debug|x64
|
||||
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.Debug|Any CPU.Build.0 = 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}.DebugTouchPanel|Any CPU.ActiveCfg = DebugTouchPanel|x64
|
||||
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.DebugTouchPanel|Any CPU.Build.0 = DebugTouchPanel|x64
|
||||
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64
|
||||
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|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.Build.0 = Release|x64
|
||||
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.ReleaseTouchPanel|Any CPU.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.ReleaseTouchPanel|Any CPU.Build.0 = ReleaseTouchPanel|x64
|
||||
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{1BBF7803-BA8D-4AEF-8319-061E93AE5F3D}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64
|
||||
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.DebugTouchPanel|Any CPU.ActiveCfg = DebugTouchPanel|x64
|
||||
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.DebugTouchPanel|Any CPU.Build.0 = DebugTouchPanel|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|Any CPU.ActiveCfg = Release|x64
|
||||
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.Release|Any CPU.Build.0 = Release|x64
|
||||
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.Release|x64.ActiveCfg = Release|x64
|
||||
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.ReleaseTouchPanel|Any CPU.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{D20EA590-22C2-4F93-AC25-AF6960F97E03}.ReleaseTouchPanel|Any CPU.Build.0 = ReleaseTouchPanel|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|Any CPU.ActiveCfg = Debug|x64
|
||||
{677B6DF0-8E19-4B08-8480-F7C365B6BB89}.Debug|Any CPU.Build.0 = Debug|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|Any CPU.ActiveCfg = DebugTouchPanel|x64
|
||||
{677B6DF0-8E19-4B08-8480-F7C365B6BB89}.DebugTouchPanel|Any CPU.Build.0 = DebugTouchPanel|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|Any CPU.ActiveCfg = Release|x64
|
||||
{677B6DF0-8E19-4B08-8480-F7C365B6BB89}.Release|Any CPU.Build.0 = Release|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|Any CPU.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{677B6DF0-8E19-4B08-8480-F7C365B6BB89}.ReleaseTouchPanel|Any CPU.Build.0 = ReleaseTouchPanel|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|Any CPU.ActiveCfg = Debug|x64
|
||||
{9CD4014F-B3EA-4E67-9329-9679931E2688}.Debug|Any CPU.Build.0 = 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}.DebugTouchPanel|Any CPU.ActiveCfg = DebugTouchPanel|x64
|
||||
{9CD4014F-B3EA-4E67-9329-9679931E2688}.DebugTouchPanel|Any CPU.Build.0 = DebugTouchPanel|x64
|
||||
{9CD4014F-B3EA-4E67-9329-9679931E2688}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64
|
||||
{9CD4014F-B3EA-4E67-9329-9679931E2688}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|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.Build.0 = Release|x64
|
||||
{9CD4014F-B3EA-4E67-9329-9679931E2688}.ReleaseTouchPanel|Any CPU.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{9CD4014F-B3EA-4E67-9329-9679931E2688}.ReleaseTouchPanel|Any CPU.Build.0 = ReleaseTouchPanel|x64
|
||||
{9CD4014F-B3EA-4E67-9329-9679931E2688}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{9CD4014F-B3EA-4E67-9329-9679931E2688}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.Debug|Any CPU.ActiveCfg = Debug|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.Debug|Any CPU.Build.0 = Debug|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.Debug|x64.Build.0 = Debug|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.DebugTouchPanel|Any CPU.ActiveCfg = DebugTouchPanel|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.DebugTouchPanel|Any CPU.Build.0 = DebugTouchPanel|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.DebugTouchPanel|x64.ActiveCfg = DebugTouchPanel|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.DebugTouchPanel|x64.Build.0 = DebugTouchPanel|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.Release|Any CPU.ActiveCfg = Release|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.Release|Any CPU.Build.0 = Release|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.Release|x64.ActiveCfg = Release|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.Release|x64.Build.0 = Release|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.ReleaseTouchPanel|Any CPU.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.ReleaseTouchPanel|Any CPU.Build.0 = ReleaseTouchPanel|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.ReleaseTouchPanel|x64.ActiveCfg = ReleaseTouchPanel|x64
|
||||
{2F36A227-C92C-4B42-B1E4-480015492357}.ReleaseTouchPanel|x64.Build.0 = ReleaseTouchPanel|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{D8938324-934B-4602-B30E-66B60365B426} = {15FC98CD-0A69-437B-A5E5-67D025DB5CDC}
|
||||
{E2AF3E4D-F5B6-44C0-8E15-528206E9F3C7} = {D8938324-934B-4602-B30E-66B60365B426}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D1607553-61E8-4ED6-B382-ABA6F6944586}
|
||||
EndGlobalSection
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using MSFSPopoutPanelManager.Shared;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Model
|
||||
{
|
||||
public class AppSetting : ObservableObject
|
||||
{
|
||||
private const string APP_SETTING_DATA_FILENAME = "appsettingdata.json";
|
||||
private bool _saveEnabled;
|
||||
|
||||
public event EventHandler<EventArgs<bool>> AlwaysOnTopChanged;
|
||||
public event EventHandler<EventArgs<bool>> AutoPopOutPanelsChanged;
|
||||
|
||||
public AppSetting()
|
||||
{
|
||||
// Set defaults
|
||||
AutoUpdaterUrl = "https://raw.githubusercontent.com/hawkeye-stan/msfs-popout-panel-manager/master/autoupdate.xml";
|
||||
//AutoUpdaterUrl = "https://raw.githubusercontent.com/hawkeye-stan/AutoUpdateTest/main/autoupdate.xml"; // Test URL against test repo
|
||||
LastUsedProfileId = -1;
|
||||
MinimizeToTray = false;
|
||||
AlwaysOnTop = true;
|
||||
UseAutoPanning = true;
|
||||
AutoPanningKeyBinding = "0";
|
||||
StartMinimized = false;
|
||||
IncludeBuiltInPanel = false;
|
||||
AutoDisableTrackIR = true;
|
||||
AutoPopOutPanels = true;
|
||||
AutoPopOutPanelsWaitDelay = new AutoPopOutPanelsWaitDelay();
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
var appSetting = ReadAppSetting();
|
||||
this.AutoUpdaterUrl = appSetting.AutoUpdaterUrl;
|
||||
this.LastUsedProfileId = appSetting.LastUsedProfileId;
|
||||
this.MinimizeToTray = appSetting.MinimizeToTray;
|
||||
this.AlwaysOnTop = appSetting.AlwaysOnTop;
|
||||
this.UseAutoPanning = appSetting.UseAutoPanning;
|
||||
this.AutoPanningKeyBinding = appSetting.AutoPanningKeyBinding;
|
||||
this.StartMinimized = appSetting.StartMinimized;
|
||||
this.IncludeBuiltInPanel = appSetting.IncludeBuiltInPanel;
|
||||
this.AutoDisableTrackIR = appSetting.AutoDisableTrackIR;
|
||||
this.AutoPopOutPanels = appSetting.AutoPopOutPanels;
|
||||
this.AutoPopOutPanelsWaitDelay = appSetting.AutoPopOutPanelsWaitDelay;
|
||||
AutoPopOutPanelsWaitDelay.DataChanged += (e, source) => WriteAppSetting(this);
|
||||
|
||||
_saveEnabled = true;
|
||||
}
|
||||
|
||||
public void OnPropertyChanged(string propertyName, object before, object after)
|
||||
{
|
||||
// Automatic save data
|
||||
if (_saveEnabled && propertyName != "AutoStart" && before != after)
|
||||
WriteAppSetting(this);
|
||||
|
||||
switch (propertyName)
|
||||
{
|
||||
case "AlwaysOnTop":
|
||||
AlwaysOnTopChanged?.Invoke(this, new EventArgs<bool>((bool)after));
|
||||
break;
|
||||
case "AutoPopOutPanels":
|
||||
AutoPopOutPanelsChanged?.Invoke(this, new EventArgs<bool>((bool)after));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public string AutoUpdaterUrl { get; set; }
|
||||
|
||||
public int LastUsedProfileId { get; set; }
|
||||
|
||||
public bool MinimizeToTray { get; set; }
|
||||
|
||||
public bool AlwaysOnTop { get; set; }
|
||||
|
||||
public bool UseAutoPanning { get; set; }
|
||||
|
||||
public string AutoPanningKeyBinding { get; set; }
|
||||
|
||||
public bool StartMinimized { get; set; }
|
||||
|
||||
public bool IncludeBuiltInPanel { get; set; }
|
||||
|
||||
public bool AutoPopOutPanels { get; set; }
|
||||
|
||||
public bool AutoDisableTrackIR { get; set; }
|
||||
|
||||
public AutoPopOutPanelsWaitDelay AutoPopOutPanelsWaitDelay { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool AutoStart
|
||||
{
|
||||
get
|
||||
{
|
||||
return AppAutoStart.CheckIsAutoStart();
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
AppAutoStart.Activate();
|
||||
else
|
||||
AppAutoStart.Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
public AppSetting ReadAppSetting()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(Path.Combine(FileIo.GetUserDataFilePath(), APP_SETTING_DATA_FILENAME)))
|
||||
{
|
||||
return JsonConvert.DeserializeObject<AppSetting>(reader.ReadToEnd());
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// if file does not exist, write default data
|
||||
var appSetting = new AppSetting();
|
||||
WriteAppSetting(appSetting);
|
||||
return appSetting;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteAppSetting(AppSetting appSetting)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userProfilePath = FileIo.GetUserDataFilePath();
|
||||
if (!Directory.Exists(userProfilePath))
|
||||
Directory.CreateDirectory(userProfilePath);
|
||||
|
||||
using (StreamWriter file = File.CreateText(Path.Combine(userProfilePath, APP_SETTING_DATA_FILENAME)))
|
||||
{
|
||||
JsonSerializer serializer = new JsonSerializer();
|
||||
serializer.Serialize(file, appSetting);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.LogStatus($"Unable to write app setting data file: {APP_SETTING_DATA_FILENAME}", StatusMessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AutoPopOutPanelsWaitDelay : ObservableObject
|
||||
{
|
||||
public event EventHandler<EventArgs<string>> DataChanged;
|
||||
|
||||
public AutoPopOutPanelsWaitDelay()
|
||||
{
|
||||
ReadyToFlyButton = 4;
|
||||
InitialCockpitView = 1;
|
||||
InstrumentationPowerOn = 1;
|
||||
}
|
||||
|
||||
public int ReadyToFlyButton { get; set; }
|
||||
|
||||
public int InitialCockpitView { get; set; }
|
||||
|
||||
public int InstrumentationPowerOn { get; set; }
|
||||
|
||||
public void OnPropertyChanged(string propertyName, object before, object after)
|
||||
{
|
||||
DataChanged?.Invoke(this, new EventArgs<string>(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<RootNamespace>MSFSPopoutPanelManager.Model</RootNamespace>
|
||||
<AssemblyName>Model</AssemblyName>
|
||||
<PackageId>MSFS 2020 Popout Panel Manager Model</PackageId>
|
||||
<Version>3.3.7.0</Version>
|
||||
<Authors>Stanley Kwok</Authors>
|
||||
<Company>Stanley Kwok</Company>
|
||||
<Copyright>Stanley Kwok 2021</Copyright>
|
||||
<Product>MSFS 2020 Popout Panel Manager Model</Product>
|
||||
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
|
||||
<Platforms>x64</Platforms>
|
||||
<AssemblyVersion>3.3.7.0</AssemblyVersion>
|
||||
<FileVersion>3.3.7.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="7.1.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Shared\Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,70 +0,0 @@
|
|||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Model
|
||||
{
|
||||
public class PanelConfig : ObservableObject
|
||||
{
|
||||
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; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsCustomPopout { get { return PanelType == PanelType.CustomPopout; } }
|
||||
|
||||
[JsonIgnore]
|
||||
public IntPtr PanelHandle { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsLockable
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (PanelType)
|
||||
{
|
||||
case PanelType.CustomPopout:
|
||||
case PanelType.BuiltInPopout:
|
||||
case PanelType.MSFSTouchPanel:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool HasTouchableEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (PanelType)
|
||||
{
|
||||
case PanelType.CustomPopout:
|
||||
case PanelType.BuiltInPopout:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Model
|
||||
{
|
||||
public class PanelSourceCoordinate : ObservableObject
|
||||
{
|
||||
public int PanelIndex { get; set; }
|
||||
|
||||
public int X { get; set; }
|
||||
|
||||
public int Y { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public IntPtr PanelHandle { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Model
|
||||
{
|
||||
public class UserProfile : ObservableObject
|
||||
{
|
||||
public UserProfile()
|
||||
{
|
||||
PanelSourceCoordinates = new ObservableCollection<PanelSourceCoordinate>();
|
||||
PanelConfigs = new ObservableCollection<PanelConfig>();
|
||||
BindingAircraftLiveries = new ObservableCollection<string>();
|
||||
IsLocked = false;
|
||||
}
|
||||
|
||||
public int ProfileId { get; set; }
|
||||
|
||||
public string ProfileName { get; set; }
|
||||
|
||||
[JsonConverter(typeof(SingleValueArrayConvertor<string>))]
|
||||
public ObservableCollection<string> BindingAircraftLiveries { get; set; }
|
||||
|
||||
public bool IsLocked { get; set; }
|
||||
|
||||
public ObservableCollection<PanelSourceCoordinate> PanelSourceCoordinates { get; set; }
|
||||
|
||||
public ObservableCollection<PanelConfig> PanelConfigs { get; set; }
|
||||
|
||||
public bool PowerOnRequiredForColdStart { get; set; }
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
PanelSourceCoordinates.Clear();
|
||||
PanelConfigs.Clear();
|
||||
IsLocked = false;
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool HasBindingAircraftLiveries
|
||||
{
|
||||
get { return BindingAircraftLiveries.Count > 0; }
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public bool HasPanelSourceCoordinates
|
||||
{
|
||||
get { return PanelSourceCoordinates.Count > 0; }
|
||||
}
|
||||
|
||||
#region Legacy Properties
|
||||
|
||||
// Support pre-Version 3.3 tag for one time conversion
|
||||
[JsonConverter(typeof(SingleValueArrayConvertor<string>))]
|
||||
public ObservableCollection<string> BindingPlaneTitle
|
||||
{
|
||||
set { BindingAircraftLiveries = value; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class SingleValueArrayConvertor<T> : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
object val = new object();
|
||||
|
||||
if (reader.TokenType == JsonToken.String)
|
||||
{
|
||||
var instance = (string)serializer.Deserialize(reader, typeof(string));
|
||||
val = new ObservableCollection<string>() { instance };
|
||||
}
|
||||
else if (reader.TokenType == JsonToken.StartArray)
|
||||
{
|
||||
val = serializer.Deserialize(reader, objectType);
|
||||
}
|
||||
else if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
val = new ObservableCollection<string>();
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
serializer.Serialize(writer, value);
|
||||
}
|
||||
}
|
||||
}
|
53
Orchestration/AppSettingData.cs
Normal file
|
@ -0,0 +1,53 @@
|
|||
using MSFSPopoutPanelManager.Shared;
|
||||
using MSFSPopoutPanelManager.UserDataAgent;
|
||||
using System;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Orchestration
|
||||
{
|
||||
public class AppSettingData : ObservableObject
|
||||
{
|
||||
public event EventHandler AppSettingUpdated;
|
||||
public event EventHandler<bool> AlwaysOnTopChanged;
|
||||
public event EventHandler<bool> AutoPopOutPanelsChanged;
|
||||
public event EventHandler<bool> TouchPanelIntegrationChanged;
|
||||
|
||||
public AppSetting AppSetting { get; private set; }
|
||||
|
||||
public void ReadSettings()
|
||||
{
|
||||
AppSetting = AppSettingManager.ReadAppSetting();
|
||||
|
||||
if (AppSetting == null)
|
||||
{
|
||||
AppSetting = new AppSetting();
|
||||
AppSettingManager.WriteAppSetting(AppSetting);
|
||||
}
|
||||
|
||||
// Autosave data
|
||||
AppSetting.PropertyChanged += (sender, e) =>
|
||||
{
|
||||
var arg = e as PropertyChangedExtendedEventArgs;
|
||||
|
||||
if (arg.OldValue != arg.NewValue)
|
||||
{
|
||||
AppSettingManager.WriteAppSetting(AppSetting);
|
||||
|
||||
switch (arg.PropertyName)
|
||||
{
|
||||
case "AlwaysOnTop":
|
||||
AlwaysOnTopChanged?.Invoke(this, (bool)arg.NewValue);
|
||||
break;
|
||||
case "AutoPopOutPanels":
|
||||
AutoPopOutPanelsChanged?.Invoke(this, (bool)arg.NewValue);
|
||||
break;
|
||||
case "EnableTouchPanelIntegration":
|
||||
TouchPanelIntegrationChanged?.Invoke(this, (bool)arg.NewValue);
|
||||
break;
|
||||
}
|
||||
|
||||
AppSettingUpdated?.Invoke(this, null);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
42
Orchestration/FlightSimData.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using MSFSPopoutPanelManager.Shared;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Orchestration
|
||||
{
|
||||
public class FlightSimData : ObservableObject
|
||||
{
|
||||
public event PropertyChangedEventHandler CurrentMsfsPlaneTitleChanged;
|
||||
|
||||
public string CurrentMsfsPlaneTitle { get; set; }
|
||||
|
||||
public bool HasCurrentMsfsPlaneTitle
|
||||
{
|
||||
get { return !String.IsNullOrEmpty(CurrentMsfsPlaneTitle); }
|
||||
}
|
||||
|
||||
public bool ElectricalMasterBatteryStatus { get; set; }
|
||||
|
||||
public bool IsSimulatorStarted { get; set; }
|
||||
|
||||
public bool IsEnteredFlight { get; set; }
|
||||
|
||||
public new void OnPropertyChanged(string propertyName, object oldValue, object newValue)
|
||||
{
|
||||
if (oldValue != newValue)
|
||||
{
|
||||
if (propertyName == "CurrentMsfsPlaneTitle")
|
||||
CurrentMsfsPlaneTitleChanged?.Invoke(this, null);
|
||||
|
||||
base.OnPropertyChanged(propertyName, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearData()
|
||||
{
|
||||
CurrentMsfsPlaneTitle = null;
|
||||
ElectricalMasterBatteryStatus = false;
|
||||
IsEnteredFlight = false;
|
||||
}
|
||||
}
|
||||
}
|
124
Orchestration/FlightSimOrchestrator.cs
Normal file
|
@ -0,0 +1,124 @@
|
|||
using MSFSPopoutPanelManager.Shared;
|
||||
using MSFSPopoutPanelManager.SimConnectAgent;
|
||||
using System;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Orchestration
|
||||
{
|
||||
public class FlightSimOrchestrator : ObservableObject
|
||||
{
|
||||
private SimConnectProvider _simConnectProvider;
|
||||
|
||||
public FlightSimOrchestrator()
|
||||
{
|
||||
_simConnectProvider = new SimConnectProvider();
|
||||
}
|
||||
|
||||
internal ProfileData ProfileData { get; set; }
|
||||
|
||||
internal AppSettingData AppSettingData { get; set; }
|
||||
|
||||
internal FlightSimData FlightSimData { get; set; }
|
||||
|
||||
internal SimConnectProvider SimConnectProvider { get { return _simConnectProvider; } }
|
||||
|
||||
public event EventHandler OnSimulatorStarted;
|
||||
public event EventHandler OnSimulatorStopped;
|
||||
public event EventHandler OnFlightStarted;
|
||||
public event EventHandler OnFlightStopped;
|
||||
public event EventHandler OnFlightStartedForAutoPopOut;
|
||||
|
||||
public void StartSimConnectServer()
|
||||
{
|
||||
_simConnectProvider.OnConnected += (sender, e) =>
|
||||
{
|
||||
FlightSimData.IsSimulatorStarted = true;
|
||||
OnSimulatorStarted?.Invoke(this, null);
|
||||
};
|
||||
_simConnectProvider.OnDisconnected += (sender, e) =>
|
||||
{
|
||||
FlightSimData.IsSimulatorStarted = false;
|
||||
FlightSimData.ClearData();
|
||||
|
||||
OnSimulatorStopped?.Invoke(this, null);
|
||||
};
|
||||
_simConnectProvider.OnSimConnectDataRefreshed += (sender, e) =>
|
||||
{
|
||||
var title = Convert.ToString(e.Find(d => d.PropName == "Title").Value);
|
||||
var electricalMasterBattery = Convert.ToBoolean(e.Find(d => d.PropName == "ElectricalMasterBattery").Value);
|
||||
|
||||
// Automatic switching of active profile when SimConnect active aircraft livery changes
|
||||
if (FlightSimData.CurrentMsfsPlaneTitle != title)
|
||||
{
|
||||
FlightSimData.CurrentMsfsPlaneTitle = title;
|
||||
ProfileData.AutoSwitchProfile(title);
|
||||
}
|
||||
|
||||
if (electricalMasterBattery != FlightSimData.ElectricalMasterBatteryStatus)
|
||||
FlightSimData.ElectricalMasterBatteryStatus = electricalMasterBattery;
|
||||
};
|
||||
_simConnectProvider.OnFlightStarted += (sender, e) => OnFlightStarted?.Invoke(this, null);
|
||||
_simConnectProvider.OnFlightStopped += HandleOnFlightStopped;
|
||||
_simConnectProvider.Start();
|
||||
}
|
||||
|
||||
public void EndSimConnectServer(bool appExit)
|
||||
{
|
||||
_simConnectProvider.Stop(appExit);
|
||||
}
|
||||
|
||||
public void TurnOnTrackIR()
|
||||
{
|
||||
if (AppSettingData.AppSetting.AutoDisableTrackIR)
|
||||
_simConnectProvider.TurnOnTrackIR();
|
||||
}
|
||||
|
||||
public void TurnOffTrackIR()
|
||||
{
|
||||
if (AppSettingData.AppSetting.AutoDisableTrackIR)
|
||||
_simConnectProvider.TurnOffTrackIR();
|
||||
}
|
||||
|
||||
public void TurnOnPower()
|
||||
{
|
||||
if (ProfileData.ActiveProfile != null)
|
||||
_simConnectProvider.TurnOnPower(ProfileData.ActiveProfile.PowerOnRequiredForColdStart);
|
||||
}
|
||||
|
||||
public void TurnOnAvionics()
|
||||
{
|
||||
if (ProfileData.ActiveProfile != null)
|
||||
_simConnectProvider.TurnOnAvionics(ProfileData.ActiveProfile.PowerOnRequiredForColdStart);
|
||||
}
|
||||
|
||||
public void TurnOffPower()
|
||||
{
|
||||
if (ProfileData.ActiveProfile != null)
|
||||
_simConnectProvider.TurnOffpower(ProfileData.ActiveProfile.PowerOnRequiredForColdStart);
|
||||
}
|
||||
|
||||
public void TurnOffAvionics()
|
||||
{
|
||||
if (ProfileData.ActiveProfile != null)
|
||||
_simConnectProvider.TurnOffAvionics(ProfileData.ActiveProfile.PowerOnRequiredForColdStart);
|
||||
}
|
||||
|
||||
public void AutoPanelPopOutActivation(bool activate)
|
||||
{
|
||||
if (activate)
|
||||
_simConnectProvider.OnFlightStarted += HandleOnFlightStartedForAutoPopOut;
|
||||
else
|
||||
_simConnectProvider.OnFlightStarted -= HandleOnFlightStartedForAutoPopOut;
|
||||
}
|
||||
|
||||
private void HandleOnFlightStartedForAutoPopOut(object sender, EventArgs e)
|
||||
{
|
||||
OnFlightStartedForAutoPopOut?.Invoke(this, null);
|
||||
}
|
||||
|
||||
private void HandleOnFlightStopped(object sender, EventArgs e)
|
||||
{
|
||||
FlightSimData.IsEnteredFlight = false;
|
||||
OnFlightStopped?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
}
|
137
Orchestration/MainOrchestrator.cs
Normal file
|
@ -0,0 +1,137 @@
|
|||
using AutoUpdaterDotNET;
|
||||
using MSFSPopoutPanelManager.Shared;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Orchestration
|
||||
{
|
||||
public class MainOrchestrator : ObservableObject
|
||||
{
|
||||
public MainOrchestrator()
|
||||
{
|
||||
Profile = new ProfileOrchestrator();
|
||||
PanelSource = new PanelSourceOrchestrator();
|
||||
PanelPopOut = new PanelPopOutOrchestrator();
|
||||
PanelConfiguration = new PanelConfigurationOrchestrator();
|
||||
FlightSim = new FlightSimOrchestrator();
|
||||
OnlineFeature = new OnlineFeatureOrchestrator();
|
||||
TouchPanel = new TouchPanelOrchestrator();
|
||||
|
||||
FlightSimData = new FlightSimData();
|
||||
FlightSimData.CurrentMsfsPlaneTitleChanged += (sernder, e) => ProfileData.RefreshProfile();
|
||||
|
||||
AppSettingData = new AppSettingData();
|
||||
AppSettingData.AutoPopOutPanelsChanged += (sender, e) => FlightSim.AutoPanelPopOutActivation(e);
|
||||
AppSettingData.TouchPanelIntegrationChanged += async (sender, e) =>
|
||||
{
|
||||
await TouchPanel.TouchPanelIntegrationUpdated(e);
|
||||
};
|
||||
|
||||
ProfileData = new ProfileData();
|
||||
ProfileData.FlightSimData = FlightSimData;
|
||||
ProfileData.AppSettingData = AppSettingData;
|
||||
ProfileData.ActiveProfileChanged += (sender, e) =>
|
||||
{
|
||||
PanelSource.CloseAllPanelSource();
|
||||
};
|
||||
}
|
||||
|
||||
public ProfileOrchestrator Profile { get; set; }
|
||||
|
||||
public PanelSourceOrchestrator PanelSource { get; set; }
|
||||
|
||||
public PanelPopOutOrchestrator PanelPopOut { get; set; }
|
||||
|
||||
public PanelConfigurationOrchestrator PanelConfiguration { get; set; }
|
||||
|
||||
public TouchPanelOrchestrator TouchPanel { get; set; }
|
||||
|
||||
public ProfileData ProfileData { get; set; }
|
||||
|
||||
public AppSettingData AppSettingData { get; private set; }
|
||||
|
||||
public FlightSimData FlightSimData { get; private set; }
|
||||
|
||||
public FlightSimOrchestrator FlightSim { get; set; }
|
||||
|
||||
public OnlineFeatureOrchestrator OnlineFeature { get; set; }
|
||||
|
||||
public IntPtr ApplicationHandle { get; set; }
|
||||
|
||||
public async void Initialize()
|
||||
{
|
||||
MigrateData.MigrateUserDataFiles();
|
||||
|
||||
AppSettingData.ReadSettings();
|
||||
ProfileData.ReadProfiles();
|
||||
|
||||
Profile.ProfileData = ProfileData;
|
||||
|
||||
PanelSource.ProfileData = ProfileData;
|
||||
PanelSource.AppSettingData = AppSettingData;
|
||||
PanelSource.FlightSimData = FlightSimData;
|
||||
PanelSource.FlightSimOrchestrator = FlightSim;
|
||||
|
||||
PanelPopOut.ProfileData = ProfileData;
|
||||
PanelPopOut.AppSettingData = AppSettingData;
|
||||
PanelPopOut.FlightSimData = FlightSimData;
|
||||
PanelPopOut.FlightSimOrchestrator = FlightSim;
|
||||
PanelPopOut.PanelSourceOrchestrator = PanelSource;
|
||||
PanelPopOut.TouchPanelOrchestrator = TouchPanel;
|
||||
PanelPopOut.OnPopOutCompleted += (sender, e) => TouchPanel.ReloadTouchPanelSimConnectDataDefinition();
|
||||
|
||||
PanelConfiguration.ProfileData = ProfileData;
|
||||
PanelConfiguration.AppSettingData = AppSettingData;
|
||||
|
||||
FlightSim.ProfileData = ProfileData;
|
||||
FlightSim.AppSettingData = AppSettingData;
|
||||
FlightSim.FlightSimData = FlightSimData;
|
||||
FlightSim.OnFlightStartedForAutoPopOut += (sender, e) => PanelPopOut.AutoPopOut();
|
||||
|
||||
TouchPanel.ProfileData = ProfileData;
|
||||
TouchPanel.AppSettingData = AppSettingData;
|
||||
TouchPanel.ApplicationHandle = ApplicationHandle;
|
||||
|
||||
CheckForAutoUpdate();
|
||||
|
||||
ProfileData.UpdateActiveProfile(AppSettingData.AppSetting.LastUsedProfileId); // Load last used profile
|
||||
FlightSim.AutoPanelPopOutActivation(AppSettingData.AppSetting.AutoPopOutPanels); // Activate auto pop out panel if defined in preferences
|
||||
FlightSim.StartSimConnectServer(); // Start the SimConnect server
|
||||
|
||||
// Enable/Disable touch panel feature (Personal use only feature)
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.LoadFrom(Path.Combine(AppContext.BaseDirectory, "WebServer.dll"));
|
||||
if (assembly != null)
|
||||
AppSettingData.AppSetting.IsEnabledTouchPanelServer = true;
|
||||
|
||||
if (AppSettingData.AppSetting.TouchPanelSettings.EnableTouchPanelIntegration)
|
||||
await TouchPanel.StartServer();
|
||||
|
||||
assembly = null;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public async Task ApplicationClose()
|
||||
{
|
||||
// Force unhook all win events
|
||||
PanelConfiguration.EndConfiguration();
|
||||
FlightSim.EndSimConnectServer(true);
|
||||
await TouchPanel.StopServer();
|
||||
}
|
||||
|
||||
private void CheckForAutoUpdate()
|
||||
{
|
||||
string jsonPath = Path.Combine(Path.Combine(FileIo.GetUserDataFilePath(), "autoupdate.json"));
|
||||
AutoUpdater.PersistenceProvider = new JsonFilePersistenceProvider(jsonPath);
|
||||
AutoUpdater.Synchronous = true;
|
||||
AutoUpdater.AppTitle = "MSFS Pop Out Panel Manager";
|
||||
AutoUpdater.RunUpdateAsAdmin = false;
|
||||
AutoUpdater.UpdateFormSize = new System.Drawing.Size(930, 675);
|
||||
AutoUpdater.Start(AppSettingData.AppSetting.AutoUpdaterUrl);
|
||||
}
|
||||
}
|
||||
}
|
133
Orchestration/MigrateData.cs
Normal file
|
@ -0,0 +1,133 @@
|
|||
using MSFSPopoutPanelManager.Shared;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Orchestration
|
||||
{
|
||||
internal class MigrateData
|
||||
{
|
||||
public static void MigrateUserDataFiles()
|
||||
{
|
||||
var packageFile = new FileInfo("MSFSPopoutPanelManager.exe");
|
||||
|
||||
var newDataPath = Path.Combine(FileIo.GetUserDataFilePath());
|
||||
var oldDataPath = Path.Combine(packageFile.DirectoryName, "userdata");
|
||||
|
||||
try
|
||||
{
|
||||
// Check if migration is necessary
|
||||
if (new DirectoryInfo(newDataPath).Exists)
|
||||
return;
|
||||
|
||||
// Check if upgrading from older version of app
|
||||
if (!new DirectoryInfo(oldDataPath).Exists)
|
||||
return;
|
||||
|
||||
const string APPSETTING_DATA_JSON = "appsettingdata.json";
|
||||
const string USERPROFILE_DATA_JSON = "userprofiledata.json";
|
||||
|
||||
var installationPathDirInfo = packageFile.Directory;
|
||||
|
||||
var oldAppSettingDataJsonPath = Path.Combine(oldDataPath, APPSETTING_DATA_JSON);
|
||||
var newAppSettingDataJsonPath = Path.Combine(newDataPath, APPSETTING_DATA_JSON);
|
||||
var oldUserProfileDataJsonPath = Path.Combine(oldDataPath, USERPROFILE_DATA_JSON);
|
||||
var newUserProfileDataJsonPath = Path.Combine(newDataPath, USERPROFILE_DATA_JSON);
|
||||
|
||||
StatusMessageWriter.WriteMessage($"Performing user data migration to your Windows 'Documents' folder. Please wait.....", StatusMessageType.Info, true, 3, true);
|
||||
|
||||
// Try to create user folder
|
||||
if (!new DirectoryInfo(newDataPath).Exists)
|
||||
{
|
||||
var userDocumentFolder = Directory.CreateDirectory(newDataPath);
|
||||
|
||||
if (!new DirectoryInfo(newDataPath).Exists)
|
||||
{
|
||||
StatusMessageWriter.WriteMessage($"Unable to create folder '{userDocumentFolder}'. Application will close.", StatusMessageType.Error, true, 5, true);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Try move appsettingdata.json
|
||||
if (new FileInfo(oldAppSettingDataJsonPath).Exists)
|
||||
{
|
||||
File.Copy(oldAppSettingDataJsonPath, newAppSettingDataJsonPath);
|
||||
|
||||
// Verify file has been copied
|
||||
if (!new FileInfo(newAppSettingDataJsonPath).Exists)
|
||||
{
|
||||
StatusMessageWriter.WriteMessage("An error has occured when moving 'appsettingdata.json' to new folder location. Please try manually move this file and restart the app again.", StatusMessageType.Error, true, 5, true);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
File.Delete(oldAppSettingDataJsonPath);
|
||||
}
|
||||
|
||||
// Try move userprofiledata.json
|
||||
if (new FileInfo(oldUserProfileDataJsonPath).Exists)
|
||||
{
|
||||
File.Copy(oldUserProfileDataJsonPath, newUserProfileDataJsonPath);
|
||||
|
||||
// Verify file has been copied
|
||||
if (!new FileInfo(newUserProfileDataJsonPath).Exists)
|
||||
{
|
||||
StatusMessageWriter.WriteMessage("An error has occured when moving 'userprofiledata.json' to new folder location. Please try manually move this file and restart the app again.", StatusMessageType.Error, true, 5, true);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
File.Delete(oldUserProfileDataJsonPath);
|
||||
}
|
||||
|
||||
// Now remove all orphan files and folder
|
||||
CleanFolderRecursive(installationPathDirInfo);
|
||||
|
||||
// Force an update of AppSetting file
|
||||
var appSettingData = new AppSettingData();
|
||||
appSettingData.ReadSettings();
|
||||
appSettingData.AppSetting.AlwaysOnTop = false;
|
||||
System.Threading.Thread.Sleep(500);
|
||||
appSettingData.AppSetting.AlwaysOnTop = true;
|
||||
|
||||
StatusMessageWriter.WriteMessage(String.Empty, StatusMessageType.Info, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = "An unknown user data migration error has occured. Application will close";
|
||||
FileLogger.WriteException(msg, ex);
|
||||
StatusMessageWriter.WriteMessage(msg, StatusMessageType.Error, true, 5, true);
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CleanFolderRecursive(DirectoryInfo directory)
|
||||
{
|
||||
foreach (FileInfo filePath in directory.GetFiles())
|
||||
{
|
||||
var name = filePath.Name.ToLower();
|
||||
if (name != "msfspopoutpanelmanager.exe")
|
||||
{
|
||||
try
|
||||
{
|
||||
filePath.Delete();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo dir in directory.GetDirectories())
|
||||
{
|
||||
CleanFolderRecursive(new DirectoryInfo(dir.FullName));
|
||||
|
||||
var name = dir.Name.ToLower();
|
||||
if (name == "resources" || name == "userdata")
|
||||
{
|
||||
try
|
||||
{
|
||||
dir.Delete();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
Orchestration/OnlineFeatureOrchestrator.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using MSFSPopoutPanelManager.Shared;
|
||||
using MSFSPopoutPanelManager.WindowsAgent;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Orchestration
|
||||
{
|
||||
public class OnlineFeatureOrchestrator : ObservableObject
|
||||
{
|
||||
public void OpenUserGuide()
|
||||
{
|
||||
WindowProcessManager.OpenOnlineUserGuide();
|
||||
}
|
||||
|
||||
public void OpenLatestDownload()
|
||||
{
|
||||
WindowProcessManager.OpenOnlineLatestDownload();
|
||||
}
|
||||
}
|
||||
}
|
49
Orchestration/Orchestration.csproj
Normal file
|
@ -0,0 +1,49 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0</TargetFrameworks>
|
||||
<AssemblyName>Orchestration</AssemblyName>
|
||||
<PackageId>MSFS 2020 Popout Panel Manager Orchestration</PackageId>
|
||||
<Product>MSFS 2020 Popout Panel Manager Orchestration</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.Orchestration</RootNamespace>
|
||||
<Platforms>x64</Platforms>
|
||||
<Version>3.4.0.0</Version>
|
||||
<AssemblyVersion>3.4.0.0</AssemblyVersion>
|
||||
<FileVersion>3.4.0.0</FileVersion>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<DebugType>Embedded</DebugType>
|
||||
<Configurations>Debug;Release;DebugTouchPanel;ReleaseTouchPanel</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>
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.0" />
|
||||
<PackageReference Include="Fody" Version="6.6.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.1" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SimconnectAgent\SimconnectAgent.csproj" />
|
||||
<ProjectReference Include="..\TouchPanelAgent\TouchPanelAgent.csproj" />
|
||||
<ProjectReference Include="..\UserDataAgent\UserDataAgent.csproj" />
|
||||
<ProjectReference Include="..\WebServer\WebServer.csproj" Condition=" '$(Configuration)' == 'ReleaseTouchPanel' Or '$(Configuration)' == 'DebugTouchPanel' " />
|
||||
<ProjectReference Include="..\WindowsAgent\WindowsAgent.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
202
Orchestration/PanelAnalyzer.cs
Normal file
|
@ -0,0 +1,202 @@
|
|||
using MSFSPopoutPanelManager.WindowsAgent;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Threading;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Orchestration
|
||||
{
|
||||
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
|
||||
public class PanelAnalyzer
|
||||
{
|
||||
public static Point GetMagnifyingGlassIconCoordinate(IntPtr hwnd)
|
||||
{
|
||||
var sourceImage = TakeScreenShot(hwnd);
|
||||
|
||||
if (sourceImage == null)
|
||||
return new Point(0, 0);
|
||||
|
||||
Rectangle rectangle = WindowActionManager.GetClientRect(hwnd);
|
||||
|
||||
var panelMenubarTop = GetPanelMenubarTop(sourceImage, rectangle);
|
||||
if (panelMenubarTop > sourceImage.Height)
|
||||
return Point.Empty;
|
||||
|
||||
var panelMenubarBottom = GetPanelMenubarBottom(sourceImage, rectangle);
|
||||
if (panelMenubarBottom > sourceImage.Height)
|
||||
return Point.Empty;
|
||||
|
||||
var panelsStartingLeft = GetPanelMenubarStartingLeft(sourceImage, rectangle, panelMenubarTop + 5);
|
||||
|
||||
// The center of magnifying glass icon is around (3.2 x height of menubar) to the right of the panel menubar starting left
|
||||
// But need to use higher number here to click the left side of magnifying glass icon because on some panel, the ratio is smaller
|
||||
var menubarHeight = panelMenubarBottom - panelMenubarTop;
|
||||
var magnifyingIconXCoor = panelsStartingLeft - Convert.ToInt32(menubarHeight * 3.2); // ToDo: play around with this multiplier to find the best for all resolutions
|
||||
var magnifyingIconYCoor = panelMenubarTop + Convert.ToInt32(menubarHeight / 2);
|
||||
|
||||
return new Point(magnifyingIconXCoor, magnifyingIconYCoor);
|
||||
}
|
||||
|
||||
private static Bitmap TakeScreenShot(IntPtr windowHandle)
|
||||
{
|
||||
if (!WindowActionManager.IsWindow(windowHandle))
|
||||
return null;
|
||||
|
||||
// Set window to foreground so nothing can hide the window
|
||||
PInvoke.SetForegroundWindow(windowHandle);
|
||||
Thread.Sleep(300);
|
||||
|
||||
Rectangle rectangle = WindowActionManager.GetWindowRect(windowHandle);
|
||||
|
||||
Rectangle clientRectangle = WindowActionManager.GetClientRect(windowHandle);
|
||||
|
||||
// Take a screen shot by removing the titlebar of the window
|
||||
var left = rectangle.Left;
|
||||
var top = rectangle.Top + (rectangle.Height - clientRectangle.Height) - 8; // 8 pixels adjustments
|
||||
|
||||
var bmp = new Bitmap(clientRectangle.Width, clientRectangle.Height, PixelFormat.Format24bppRgb);
|
||||
|
||||
using (Graphics g = Graphics.FromImage(bmp))
|
||||
{
|
||||
g.CopyFromScreen(new Point(left, top), Point.Empty, rectangle.Size);
|
||||
}
|
||||
|
||||
// Place the above image in the same canvas size as before
|
||||
Bitmap backingImage = new Bitmap(rectangle.Width, rectangle.Height);
|
||||
using (Graphics gfx = Graphics.FromImage(backingImage))
|
||||
{
|
||||
using (SolidBrush brush = new SolidBrush(Color.FromArgb(255, 0, 0)))
|
||||
{
|
||||
gfx.FillRectangle(brush, 0, 0, rectangle.Width, rectangle.Height);
|
||||
gfx.DrawImage(bmp, new Point(0, top));
|
||||
}
|
||||
}
|
||||
|
||||
return backingImage;
|
||||
}
|
||||
|
||||
private static int GetPanelMenubarTop(Bitmap sourceImage, Rectangle rectangle)
|
||||
{
|
||||
// Get a snippet of 1 pixel wide vertical strip of windows. We will choose the strip left of center.
|
||||
// This is to determine when the actual panel's vertical pixel starts in the window. This will allow accurate sizing of the template image
|
||||
var left = Convert.ToInt32((rectangle.Width) * 0.70); // look at around 70% from the left
|
||||
var top = sourceImage.Height - rectangle.Height;
|
||||
|
||||
if (top < 0 || left < 0)
|
||||
return -1;
|
||||
|
||||
unsafe
|
||||
{
|
||||
var stripData = sourceImage.LockBits(new Rectangle(left, top, 1, rectangle.Height), ImageLockMode.ReadWrite, sourceImage.PixelFormat);
|
||||
|
||||
int bytesPerPixel = Bitmap.GetPixelFormatSize(stripData.PixelFormat) / 8;
|
||||
int heightInPixels = stripData.Height;
|
||||
int widthInBytes = stripData.Width * bytesPerPixel;
|
||||
byte* ptrFirstPixel = (byte*)stripData.Scan0;
|
||||
|
||||
for (int y = 0; y < heightInPixels; y++)
|
||||
{
|
||||
byte* currentLine = ptrFirstPixel + (y * stripData.Stride);
|
||||
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
|
||||
{
|
||||
int red = currentLine[x + 2];
|
||||
int green = currentLine[x + 1];
|
||||
int blue = currentLine[x];
|
||||
|
||||
if (red == 255 && green == 255 && blue == 255)
|
||||
{
|
||||
sourceImage.UnlockBits(stripData);
|
||||
return y + top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceImage.UnlockBits(stripData);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int GetPanelMenubarBottom(Bitmap sourceImage, Rectangle rectangle)
|
||||
{
|
||||
// Get a snippet of 1 pixel wide vertical strip of windows. We will choose the strip about 70% from the left of the window
|
||||
var left = Convert.ToInt32((rectangle.Width) * 0.7); // look at around 70% from the left
|
||||
var top = sourceImage.Height - rectangle.Height;
|
||||
|
||||
if (top < 0 || left < 0)
|
||||
return -1;
|
||||
|
||||
unsafe
|
||||
{
|
||||
var stripData = sourceImage.LockBits(new Rectangle(left, top, 1, rectangle.Height), ImageLockMode.ReadWrite, sourceImage.PixelFormat);
|
||||
|
||||
int bytesPerPixel = Bitmap.GetPixelFormatSize(stripData.PixelFormat) / 8;
|
||||
int heightInPixels = stripData.Height;
|
||||
int widthInBytes = stripData.Width * bytesPerPixel;
|
||||
byte* ptrFirstPixel = (byte*)stripData.Scan0;
|
||||
|
||||
int menubarBottom = -1;
|
||||
|
||||
for (int y = 0; y < heightInPixels; y++)
|
||||
{
|
||||
byte* currentLine = ptrFirstPixel + (y * stripData.Stride);
|
||||
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
|
||||
{
|
||||
int red = currentLine[x + 2];
|
||||
int green = currentLine[x + 1];
|
||||
int blue = currentLine[x];
|
||||
|
||||
if (red > 250 && green > 250 && blue > 250) // allows the color to be a little off white (ie. Fenix A30 EFB)
|
||||
{
|
||||
// found the top of menu bar
|
||||
menubarBottom = y + top;
|
||||
}
|
||||
else if (menubarBottom > -1) /// it is no longer white in color, we hit menubar bottom
|
||||
{
|
||||
sourceImage.UnlockBits(stripData);
|
||||
return menubarBottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceImage.UnlockBits(stripData);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int GetPanelMenubarStartingLeft(Bitmap sourceImage, Rectangle rectangle, int top)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var stripData = sourceImage.LockBits(new Rectangle(0, top, rectangle.Width, 1), ImageLockMode.ReadWrite, sourceImage.PixelFormat);
|
||||
|
||||
int bytesPerPixel = Bitmap.GetPixelFormatSize(stripData.PixelFormat) / 8;
|
||||
int widthInPixels = stripData.Width;
|
||||
int heightInBytes = stripData.Height * bytesPerPixel;
|
||||
byte* ptrFirstPixel = (byte*)stripData.Scan0;
|
||||
|
||||
for (int x = 0; x < widthInPixels; x++)
|
||||
{
|
||||
byte* currentLine = ptrFirstPixel - (x * bytesPerPixel);
|
||||
for (int y = 0; y < heightInBytes; y = y + bytesPerPixel)
|
||||
{
|
||||
int red = currentLine[y + 2];
|
||||
int green = currentLine[y + 1];
|
||||
int blue = currentLine[y];
|
||||
|
||||
if (red > 250 && green > 250 && blue > 250) // allows the color to be a little off white (ie. Fenix A30 EFB)
|
||||
{
|
||||
sourceImage.UnlockBits(stripData);
|
||||
return sourceImage.Width - x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceImage.UnlockBits(stripData);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,164 +1,181 @@
|
|||
using MSFSPopoutPanelManager.Model;
|
||||
using MSFSPopoutPanelManager.Shared;
|
||||
using MSFSPopoutPanelManager.UserDataAgent;
|
||||
using MSFSPopoutPanelManager.WindowsAgent;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Provider
|
||||
namespace MSFSPopoutPanelManager.Orchestration
|
||||
{
|
||||
public class PanelConfigurationManager
|
||||
public class PanelConfigurationOrchestrator : ObservableObject
|
||||
{
|
||||
private UserProfileManager _userProfileManager;
|
||||
private IntPtr _winEventHook;
|
||||
private static PInvoke.WinEventProc _winEvent; // keep this as static to prevent garbage collect or the app will crash
|
||||
private Rectangle _lastWindowRectangle;
|
||||
private static IntPtr _winEventHook;
|
||||
private uint _prevWinEvent = PInvokeConstant.EVENT_SYSTEM_CAPTUREEND;
|
||||
private int _winEventClickLock = 0;
|
||||
private Rectangle _lastWindowRectangle;
|
||||
private object _hookLock = new object();
|
||||
private bool _isHookMouseDown = false;
|
||||
|
||||
public UserProfile UserProfile { get; set; }
|
||||
|
||||
public bool AllowEdit { get; set; }
|
||||
|
||||
public PanelConfigurationManager(UserProfileManager userProfileManager)
|
||||
public PanelConfigurationOrchestrator()
|
||||
{
|
||||
_userProfileManager = userProfileManager;
|
||||
_winEvent = new PInvoke.WinEventProc(EventCallback);
|
||||
AllowEdit = true;
|
||||
}
|
||||
|
||||
public void HookWinEvent()
|
||||
internal ProfileData ProfileData { get; set; }
|
||||
|
||||
internal AppSettingData AppSettingData { get; set; }
|
||||
|
||||
private Profile ActiveProfile { get { return ProfileData == null ? null : ProfileData.ActiveProfile; } }
|
||||
|
||||
public bool AllowEdit { get; set; }
|
||||
|
||||
public void StartConfiguration()
|
||||
{
|
||||
// Setup panel config event hooks
|
||||
_winEventHook = PInvoke.SetWinEventHook(PInvokeConstant.EVENT_SYSTEM_CAPTURESTART, PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE, DiagnosticManager.GetApplicationProcess().Handle, _winEvent, 0, 0, PInvokeConstant.WINEVENT_OUTOFCONTEXT);
|
||||
HookWinEvent();
|
||||
}
|
||||
|
||||
public void UnhookWinEvent()
|
||||
public void EndConfiguration()
|
||||
{
|
||||
// Unhook all Win API events
|
||||
PInvoke.UnhookWinEvent(_winEventHook);
|
||||
UnhookWinEvent();
|
||||
}
|
||||
|
||||
public void LockPanelsUpdated()
|
||||
public void LockStatusUpdated()
|
||||
{
|
||||
UserProfile.IsLocked = !UserProfile.IsLocked;
|
||||
_userProfileManager.WriteUserProfiles();
|
||||
ActiveProfile.IsLocked = !ActiveProfile.IsLocked;
|
||||
ProfileData.WriteProfiles();
|
||||
}
|
||||
|
||||
public void PanelConfigPropertyUpdated(PanelConfigItem panelConfigItem)
|
||||
public void PanelConfigPropertyUpdated(int panelIndex, PanelConfigPropertyName configPropertyName)
|
||||
{
|
||||
if (panelConfigItem == null || !AllowEdit || UserProfile.IsLocked)
|
||||
if (panelIndex == -1 || !AllowEdit || ActiveProfile.IsLocked)
|
||||
return;
|
||||
|
||||
var panelConfig = UserProfile.PanelConfigs.ToList().Find(p => p.PanelIndex == panelConfigItem.PanelIndex);
|
||||
var panelConfig = ActiveProfile.PanelConfigs.FirstOrDefault(p => p.PanelIndex == panelIndex);
|
||||
|
||||
if (panelConfig != null)
|
||||
{
|
||||
if (panelConfigItem.PanelConfigProperty == PanelConfigPropertyName.FullScreen)
|
||||
if (configPropertyName == PanelConfigPropertyName.FullScreen)
|
||||
{
|
||||
InputEmulationManager.ToggleFullScreenPanel(panelConfig.PanelHandle);
|
||||
panelConfig.HideTitlebar = false;
|
||||
panelConfig.AlwaysOnTop = false;
|
||||
}
|
||||
else if (panelConfigItem.PanelConfigProperty == PanelConfigPropertyName.PanelName)
|
||||
else if (configPropertyName == PanelConfigPropertyName.PanelName)
|
||||
{
|
||||
var name = panelConfig.PanelName;
|
||||
if (name.IndexOf("(Custom)") == -1)
|
||||
|
||||
if (panelConfig.PanelType == PanelType.CustomPopout && name.IndexOf("(Custom)") == -1)
|
||||
{
|
||||
name = name + " (Custom)";
|
||||
PInvoke.SetWindowText(panelConfig.PanelHandle, name);
|
||||
PInvoke.SetWindowText(panelConfig.PanelHandle, name);
|
||||
}
|
||||
}
|
||||
else if (!panelConfig.FullScreen)
|
||||
{
|
||||
switch (panelConfigItem.PanelConfigProperty)
|
||||
switch (configPropertyName)
|
||||
{
|
||||
case PanelConfigPropertyName.Left:
|
||||
case PanelConfigPropertyName.Top:
|
||||
WindowManager.MoveWindow(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
|
||||
WindowActionManager.MoveWindow(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
|
||||
break;
|
||||
case PanelConfigPropertyName.Width:
|
||||
case PanelConfigPropertyName.Height:
|
||||
if (panelConfig.HideTitlebar)
|
||||
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, false);
|
||||
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, false);
|
||||
|
||||
WindowManager.MoveWindowWithMsfsBugOverrirde(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
|
||||
WindowActionManager.MoveWindowWithMsfsBugOverrirde(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
|
||||
|
||||
if (panelConfig.HideTitlebar)
|
||||
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, true);
|
||||
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, true);
|
||||
|
||||
break;
|
||||
case PanelConfigPropertyName.AlwaysOnTop:
|
||||
WindowManager.ApplyAlwaysOnTop(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.AlwaysOnTop, new Rectangle(panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height));
|
||||
WindowActionManager.ApplyAlwaysOnTop(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.AlwaysOnTop, new Rectangle(panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height));
|
||||
break;
|
||||
case PanelConfigPropertyName.HideTitlebar:
|
||||
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, panelConfig.HideTitlebar);
|
||||
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, panelConfig.HideTitlebar);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_userProfileManager.WriteUserProfiles();
|
||||
ProfileData.WriteProfiles();
|
||||
}
|
||||
}
|
||||
|
||||
public void PanelConfigIncreaseDecrease(PanelConfigItem panelConfigItem, int changeAmount)
|
||||
public void PanelConfigIncreaseDecrease(int panelIndex, PanelConfigPropertyName configPropertyName, int changeAmount)
|
||||
{
|
||||
if (panelConfigItem == null || !AllowEdit || UserProfile.IsLocked || UserProfile.PanelConfigs == null || UserProfile.PanelConfigs.Count == 0)
|
||||
if (panelIndex == -1 || !AllowEdit || ActiveProfile.IsLocked || ActiveProfile.PanelConfigs == null || ActiveProfile.PanelConfigs.Count == 0)
|
||||
return;
|
||||
|
||||
var index = UserProfile.PanelConfigs.ToList().FindIndex(p => p.PanelIndex == panelConfigItem.PanelIndex);
|
||||
var panelConfig = ActiveProfile.PanelConfigs.FirstOrDefault(p => p.PanelIndex == panelIndex);
|
||||
|
||||
if (index > -1)
|
||||
if (panelConfig != null)
|
||||
{
|
||||
var panelConfig = UserProfile.PanelConfigs[index];
|
||||
|
||||
// Cannot apply any other settings if panel is full screen mode
|
||||
// Should not apply any other settings if panel is full screen mode
|
||||
if (panelConfig.FullScreen)
|
||||
return;
|
||||
|
||||
int orignalLeft = panelConfig.Left;
|
||||
|
||||
switch (panelConfigItem.PanelConfigProperty)
|
||||
switch (configPropertyName)
|
||||
{
|
||||
case PanelConfigPropertyName.Left:
|
||||
panelConfig.Left += changeAmount;
|
||||
WindowManager.MoveWindow(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
|
||||
WindowActionManager.MoveWindow(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
|
||||
break;
|
||||
case PanelConfigPropertyName.Top:
|
||||
panelConfig.Top += changeAmount;
|
||||
WindowManager.MoveWindow(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
|
||||
WindowActionManager.MoveWindow(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
|
||||
break;
|
||||
case PanelConfigPropertyName.Width:
|
||||
panelConfig.Width += changeAmount;
|
||||
|
||||
if (panelConfig.HideTitlebar)
|
||||
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, false);
|
||||
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, false);
|
||||
|
||||
WindowManager.MoveWindowWithMsfsBugOverrirde(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
|
||||
WindowActionManager.MoveWindowWithMsfsBugOverrirde(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
|
||||
|
||||
if (panelConfig.HideTitlebar)
|
||||
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, true);
|
||||
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, true);
|
||||
|
||||
break;
|
||||
case PanelConfigPropertyName.Height:
|
||||
panelConfig.Height += changeAmount;
|
||||
|
||||
if (panelConfig.HideTitlebar)
|
||||
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, false);
|
||||
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, false);
|
||||
|
||||
WindowManager.MoveWindowWithMsfsBugOverrirde(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
|
||||
WindowActionManager.MoveWindowWithMsfsBugOverrirde(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
|
||||
|
||||
if (panelConfig.HideTitlebar)
|
||||
WindowManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, true);
|
||||
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, true);
|
||||
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
_userProfileManager.WriteUserProfiles();
|
||||
ProfileData.WriteProfiles();
|
||||
}
|
||||
}
|
||||
|
||||
private void EventCallback(IntPtr hWinEventHook, uint iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime)
|
||||
private void HookWinEvent()
|
||||
{
|
||||
// Setup panel config event hooks
|
||||
_winEventHook = PInvoke.SetWinEventHook(PInvokeConstant.EVENT_SYSTEM_CAPTURESTART, PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE, IntPtr.Zero, _winEvent, 0, 0, PInvokeConstant.WINEVENT_OUTOFCONTEXT);
|
||||
}
|
||||
|
||||
private void UnhookWinEvent()
|
||||
{
|
||||
// Unhook all Win API events
|
||||
PInvoke.UnhookWinEvent(_winEventHook);
|
||||
}
|
||||
|
||||
private void EventCallback(IntPtr hWinEventHook, uint iEvent, IntPtr hwnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime)
|
||||
{
|
||||
switch (iEvent)
|
||||
{
|
||||
|
@ -167,31 +184,36 @@ namespace MSFSPopoutPanelManager.Provider
|
|||
case PInvokeConstant.EVENT_SYSTEM_CAPTURESTART:
|
||||
case PInvokeConstant.EVENT_SYSTEM_CAPTUREEND:
|
||||
// check by priority to speed up comparing of escaping constraints
|
||||
if (hWnd == IntPtr.Zero
|
||||
if (hwnd == IntPtr.Zero
|
||||
|| idObject != 0
|
||||
|| hWinEventHook != _winEventHook
|
||||
|| !AllowEdit
|
||||
|| UserProfile.PanelConfigs == null
|
||||
|| UserProfile.PanelConfigs.Count == 0)
|
||||
|| ActiveProfile == null
|
||||
|| ActiveProfile.PanelConfigs == null
|
||||
|| ActiveProfile.PanelConfigs.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HandleEventCallback(hWnd, iEvent);
|
||||
HandleEventCallback(hwnd, iEvent);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEventCallback(IntPtr hWnd, uint iEvent)
|
||||
private void HandleEventCallback(IntPtr hwnd, uint iEvent)
|
||||
{
|
||||
var panelConfig = UserProfile.PanelConfigs.FirstOrDefault(panel => panel.PanelHandle == hWnd);
|
||||
var panelConfig = ActiveProfile.PanelConfigs.FirstOrDefault(panel => panel.PanelHandle == hwnd);
|
||||
|
||||
if (panelConfig == null)
|
||||
return;
|
||||
|
||||
if (panelConfig.IsLockable && UserProfile.IsLocked)
|
||||
// Should not apply any other settings if panel is full screen mode
|
||||
if (panelConfig.FullScreen)
|
||||
return;
|
||||
|
||||
if (panelConfig.IsLockable && ActiveProfile.IsLocked)
|
||||
{
|
||||
switch (iEvent)
|
||||
{
|
||||
|
@ -203,12 +225,18 @@ namespace MSFSPopoutPanelManager.Provider
|
|||
// Detect if window is maximized, if so, save settings
|
||||
WINDOWPLACEMENT wp = new WINDOWPLACEMENT();
|
||||
wp.length = System.Runtime.InteropServices.Marshal.SizeOf(wp);
|
||||
PInvoke.GetWindowPlacement(hWnd, ref wp);
|
||||
PInvoke.GetWindowPlacement(hwnd, ref wp);
|
||||
if (wp.showCmd == PInvokeConstant.SW_SHOWMAXIMIZED || wp.showCmd == PInvokeConstant.SW_SHOWMINIMIZED || wp.showCmd == PInvokeConstant.SW_SHOWNORMAL)
|
||||
{
|
||||
PInvoke.ShowWindow(hWnd, PInvokeConstant.SW_RESTORE);
|
||||
PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_RESTORE);
|
||||
}
|
||||
break;
|
||||
case PInvokeConstant.EVENT_SYSTEM_CAPTURESTART:
|
||||
if (!panelConfig.HasTouchableEvent || _prevWinEvent == PInvokeConstant.EVENT_SYSTEM_CAPTURESTART)
|
||||
break;
|
||||
|
||||
HandleTouchDownEvent(panelConfig);
|
||||
break;
|
||||
case PInvokeConstant.EVENT_SYSTEM_CAPTUREEND:
|
||||
if (!panelConfig.TouchEnabled || _prevWinEvent == PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE)
|
||||
break;
|
||||
|
@ -216,7 +244,7 @@ namespace MSFSPopoutPanelManager.Provider
|
|||
if (!panelConfig.HasTouchableEvent || _prevWinEvent == PInvokeConstant.EVENT_SYSTEM_CAPTUREEND)
|
||||
break;
|
||||
|
||||
HandleTouchEvent(panelConfig);
|
||||
HandleTouchUpEvent(panelConfig);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -252,15 +280,21 @@ namespace MSFSPopoutPanelManager.Provider
|
|||
// Detect if window is maximized, if so, save settings
|
||||
WINDOWPLACEMENT wp = new WINDOWPLACEMENT();
|
||||
wp.length = System.Runtime.InteropServices.Marshal.SizeOf(wp);
|
||||
PInvoke.GetWindowPlacement(hWnd, ref wp);
|
||||
PInvoke.GetWindowPlacement(hwnd, ref wp);
|
||||
if (wp.showCmd == PInvokeConstant.SW_SHOWMAXIMIZED || wp.showCmd == PInvokeConstant.SW_SHOWMINIMIZED)
|
||||
{
|
||||
_userProfileManager.WriteUserProfiles();
|
||||
ProfileData.WriteProfiles();
|
||||
}
|
||||
|
||||
break;
|
||||
case PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND:
|
||||
_userProfileManager.WriteUserProfiles();
|
||||
ProfileData.WriteProfiles();
|
||||
break;
|
||||
case PInvokeConstant.EVENT_SYSTEM_CAPTURESTART:
|
||||
if (!panelConfig.HasTouchableEvent || _prevWinEvent == PInvokeConstant.EVENT_SYSTEM_CAPTURESTART)
|
||||
break;
|
||||
|
||||
HandleTouchDownEvent(panelConfig);
|
||||
break;
|
||||
case PInvokeConstant.EVENT_SYSTEM_CAPTUREEND:
|
||||
if (!panelConfig.TouchEnabled || _prevWinEvent == PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE)
|
||||
|
@ -269,7 +303,7 @@ namespace MSFSPopoutPanelManager.Provider
|
|||
if (!panelConfig.HasTouchableEvent || _prevWinEvent == PInvokeConstant.EVENT_SYSTEM_CAPTUREEND)
|
||||
break;
|
||||
|
||||
HandleTouchEvent(panelConfig);
|
||||
HandleTouchUpEvent(panelConfig);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -277,7 +311,27 @@ namespace MSFSPopoutPanelManager.Provider
|
|||
_prevWinEvent = iEvent;
|
||||
}
|
||||
|
||||
private void HandleTouchEvent(PanelConfig panelConfig)
|
||||
private void HandleTouchDownEvent(PanelConfig panelConfig)
|
||||
{
|
||||
Point point;
|
||||
PInvoke.GetCursorPos(out point);
|
||||
|
||||
// Disable left clicking if user is touching the title bar area
|
||||
if (point.Y - panelConfig.Top > (panelConfig.HideTitlebar ? 5 : 31))
|
||||
{
|
||||
if (!_isHookMouseDown)
|
||||
{
|
||||
lock (_hookLock)
|
||||
{
|
||||
_isHookMouseDown = true;
|
||||
InputEmulationManager.LeftClickMouseDown(point.X, point.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void HandleTouchUpEvent(PanelConfig panelConfig)
|
||||
{
|
||||
Point point;
|
||||
PInvoke.GetCursorPos(out point);
|
||||
|
@ -287,11 +341,20 @@ namespace MSFSPopoutPanelManager.Provider
|
|||
{
|
||||
var prevWinEventClickLock = ++_winEventClickLock;
|
||||
|
||||
UnhookWinEvent();
|
||||
InputEmulationManager.LeftClickFast(point.X, point.Y);
|
||||
HookWinEvent();
|
||||
if (_isHookMouseDown)
|
||||
{
|
||||
Thread.Sleep(AppSettingData.AppSetting.TouchScreenSettings.MouseUpDownDelay);
|
||||
|
||||
if (prevWinEventClickLock == _winEventClickLock)
|
||||
lock (_hookLock)
|
||||
{
|
||||
_isHookMouseDown = false;
|
||||
UnhookWinEvent();
|
||||
InputEmulationManager.LeftClickMouseUp(0, 0);
|
||||
HookWinEvent();
|
||||
}
|
||||
}
|
||||
|
||||
if (prevWinEventClickLock == _winEventClickLock && AppSettingData.AppSetting.TouchScreenSettings.RefocusGameWindow)
|
||||
{
|
||||
Task.Run(() => RefocusMsfs(prevWinEventClickLock));
|
||||
}
|
||||
|
@ -304,11 +367,16 @@ namespace MSFSPopoutPanelManager.Provider
|
|||
|
||||
if (prevWinEventClickLock == _winEventClickLock)
|
||||
{
|
||||
var simulatorProcess = DiagnosticManager.GetSimulatorProcess();
|
||||
var simulatorProcess = WindowProcessManager.GetSimulatorProcess();
|
||||
|
||||
Rectangle rectangle;
|
||||
PInvoke.GetWindowRect(simulatorProcess.Handle, out rectangle);
|
||||
PInvoke.SetCursorPos(rectangle.X + 18, rectangle.Y + 80);
|
||||
|
||||
if (!_isHookMouseDown)
|
||||
{
|
||||
PInvoke.SetCursorPos(rectangle.X + 18, rectangle.Y + 80);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
452
Orchestration/PanelPopOutOrchestrator.cs
Normal file
|
@ -0,0 +1,452 @@
|
|||
using MSFSPopoutPanelManager.Shared;
|
||||
using MSFSPopoutPanelManager.UserDataAgent;
|
||||
using MSFSPopoutPanelManager.WindowsAgent;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Orchestration
|
||||
{
|
||||
public class PanelPopOutOrchestrator : ObservableObject
|
||||
{
|
||||
// This will be replaced by a signal from Ready to Fly Skipper into webserver in version 4.0
|
||||
private const int READY_TO_FLY_BUTTON_APPEARANCE_DELAY = 2000;
|
||||
|
||||
internal ProfileData ProfileData { get; set; }
|
||||
|
||||
internal AppSettingData AppSettingData { get; set; }
|
||||
|
||||
internal FlightSimData FlightSimData { get; set; }
|
||||
|
||||
internal FlightSimOrchestrator FlightSimOrchestrator { private get; set; }
|
||||
|
||||
internal PanelSourceOrchestrator PanelSourceOrchestrator { private get; set; }
|
||||
|
||||
internal TouchPanelOrchestrator TouchPanelOrchestrator { private get; set; }
|
||||
|
||||
private Profile ActiveProfile { get { return ProfileData == null ? null : ProfileData.ActiveProfile; } }
|
||||
|
||||
private AppSetting AppSetting { get { return AppSettingData == null ? null : AppSettingData.AppSetting; } }
|
||||
|
||||
public event EventHandler OnPopOutStarted;
|
||||
public event EventHandler OnPopOutCompleted;
|
||||
public event EventHandler<TouchPanelOpenEventArg> OnTouchPanelOpened;
|
||||
|
||||
public void ManualPopOut()
|
||||
{
|
||||
if (ActiveProfile == null)
|
||||
return;
|
||||
|
||||
InputHookManager.EndHook();
|
||||
|
||||
if (ActiveProfile.PanelSourceCoordinates.Count > 0 || ActiveProfile.TouchPanelBindings.Count > 0)
|
||||
{
|
||||
StatusMessageWriter.WriteMessage($"Panels pop out in progress for profile:\n{ActiveProfile.ProfileName}", StatusMessageType.Info, true);
|
||||
CorePopOutSteps();
|
||||
}
|
||||
}
|
||||
|
||||
public void AutoPopOut()
|
||||
{
|
||||
if (ActiveProfile == null)
|
||||
return;
|
||||
|
||||
ProfileData.AutoSwitchProfile(FlightSimData.CurrentMsfsPlaneTitle);
|
||||
|
||||
FlightSimData.IsEnteredFlight = true;
|
||||
|
||||
// find the profile with the matching binding plane title
|
||||
var profile = ProfileData.Profiles.FirstOrDefault(p => p.BindingAircraftLiveries.Any(p => p == FlightSimData.CurrentMsfsPlaneTitle));
|
||||
|
||||
// Do not do auto pop out if no profile matches the current livery
|
||||
if (profile == null)
|
||||
return;
|
||||
|
||||
// Match the delay for Ready to Fly button to disappear
|
||||
Thread.Sleep(READY_TO_FLY_BUTTON_APPEARANCE_DELAY);
|
||||
|
||||
if (ActiveProfile.PanelSourceCoordinates.Count > 0 || ActiveProfile.TouchPanelBindings.Count > 0)
|
||||
{
|
||||
StatusMessageWriter.WriteMessage($"Automatic pop out is starting for profile:\n{profile.ProfileName}", StatusMessageType.Info, true);
|
||||
|
||||
// Extra wait for cockpit view to align
|
||||
Thread.Sleep(1000);
|
||||
|
||||
CorePopOutSteps();
|
||||
}
|
||||
}
|
||||
|
||||
private void CorePopOutSteps()
|
||||
{
|
||||
// Has custom pop out panels
|
||||
if (ActiveProfile.PanelSourceCoordinates.Count > 0)
|
||||
{
|
||||
// Turn off TrackIR if TrackIR is started
|
||||
FlightSimOrchestrator.TurnOffTrackIR();
|
||||
|
||||
// Turn on power if required to pop out panels at least one (fix Cessna 208b grand caravan mod bug where battery is reported as on)
|
||||
if (ActiveProfile.PowerOnRequiredForColdStart)
|
||||
{
|
||||
int count = 0;
|
||||
do
|
||||
{
|
||||
FlightSimOrchestrator.TurnOnPower();
|
||||
Thread.Sleep(500);
|
||||
count++;
|
||||
}
|
||||
while (!FlightSimData.ElectricalMasterBatteryStatus && count < 10);
|
||||
}
|
||||
|
||||
// Turn on avionics if required to pop out panels
|
||||
if (ActiveProfile.PowerOnRequiredForColdStart)
|
||||
FlightSimOrchestrator.TurnOnAvionics();
|
||||
}
|
||||
|
||||
StartPopout();
|
||||
|
||||
// Has custom pop out panels
|
||||
if (ActiveProfile.PanelSourceCoordinates.Count > 0)
|
||||
{
|
||||
// Turn off avionics if needed after pop out
|
||||
FlightSimOrchestrator.TurnOffAvionics();
|
||||
|
||||
// Turn off power if needed after pop out
|
||||
FlightSimOrchestrator.TurnOffPower();
|
||||
|
||||
// Return to custom camera view
|
||||
ReturnToAfterPopOutCameraView();
|
||||
|
||||
// Turn TrackIR back on
|
||||
FlightSimOrchestrator.TurnOnTrackIR();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartPopout()
|
||||
{
|
||||
List<PanelConfig> panelConfigs = new List<PanelConfig>();
|
||||
|
||||
var simulatorProcess = WindowProcessManager.GetSimulatorProcess();
|
||||
|
||||
if (simulatorProcess == null || simulatorProcess.Handle == IntPtr.Zero)
|
||||
{
|
||||
StatusMessageWriter.WriteMessage("MSFS/SimConnect has not been started. Please try again at a later time.", StatusMessageType.Error, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ActiveProfile == null)
|
||||
{
|
||||
StatusMessageWriter.WriteMessage("No profile has been selected. Please select a profile to continue.", StatusMessageType.Error, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ActiveProfile.PanelSourceCoordinates.Count == 0 && ActiveProfile.TouchPanelBindings.Count == 0)
|
||||
{
|
||||
StatusMessageWriter.WriteMessage("No panel has been selected for the profile. Please select at least one panel to continue.", StatusMessageType.Error, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Close all existing custom pop out panels
|
||||
WindowActionManager.CloseAllPopOuts();
|
||||
|
||||
// Close all panel source overlays
|
||||
PanelSourceOrchestrator.CloseAllPanelSource();
|
||||
|
||||
OnPopOutStarted?.Invoke(this, null);
|
||||
|
||||
// Must close out all existing custom pop out panels
|
||||
if (WindowActionManager.GetWindowsCountByPanelType(new List<PanelType>() { PanelType.CustomPopout, PanelType.MSFSTouchPanel }) > 0)
|
||||
{
|
||||
StatusMessageWriter.WriteMessage("Please close all existing panel pop outs to continue.", StatusMessageType.Error, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to pop out and separate custom panels
|
||||
if (ActiveProfile.PanelSourceCoordinates.Count > 0)
|
||||
{
|
||||
if (AppSetting.UseAutoPanning)
|
||||
InputEmulationManager.LoadCustomView(AppSetting.AutoPanningKeyBinding);
|
||||
|
||||
var panelResults = ExecutePopoutAndSeparation();
|
||||
|
||||
if (panelResults == null)
|
||||
return;
|
||||
|
||||
panelConfigs.AddRange(panelResults);
|
||||
}
|
||||
|
||||
// Add the MSFS Touch Panel (My other github project) windows to the panel list
|
||||
if (AppSetting.TouchPanelSettings.EnableTouchPanelIntegration)
|
||||
{
|
||||
var panelResults = AddMsfsTouchPanels(panelConfigs.Count + 1);
|
||||
if (panelResults == null)
|
||||
return;
|
||||
|
||||
panelConfigs.AddRange(panelResults);
|
||||
}
|
||||
|
||||
if (panelConfigs.Count == 0)
|
||||
{
|
||||
OnPopOutCompleted?.Invoke(this, null);
|
||||
StatusMessageWriter.WriteMessage("No panels have been found. Please select at least one in-game panel.", StatusMessageType.Error, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Line up all the panels
|
||||
for (var i = panelConfigs.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (panelConfigs[i].PanelType == PanelType.CustomPopout)
|
||||
{
|
||||
WindowActionManager.MoveWindow(panelConfigs[i].PanelHandle, panelConfigs[i].PanelType, panelConfigs[i].Top, panelConfigs[i].Left, panelConfigs[i].Width, panelConfigs[i].Height);
|
||||
PInvoke.SetForegroundWindow(panelConfigs[i].PanelHandle);
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
}
|
||||
|
||||
if (panelConfigs.Count > 0 || ActiveProfile.PanelConfigs.Count > 0)
|
||||
{
|
||||
|
||||
LoadAndApplyPanelConfigs(panelConfigs);
|
||||
ActiveProfile.PanelConfigs = new ObservableCollection<PanelConfig>(panelConfigs);
|
||||
StatusMessageWriter.WriteMessage("Panels have been popped out succesfully and saved panel settings have been applied.", StatusMessageType.Info, true);
|
||||
OnPopOutCompleted?.Invoke(this, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
ActiveProfile.PanelConfigs = new ObservableCollection<PanelConfig>(panelConfigs);
|
||||
StatusMessageWriter.WriteMessage("Panels have been popped out succesfully.", StatusMessageType.Info, true);
|
||||
OnPopOutCompleted?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
private List<PanelConfig> ExecutePopoutAndSeparation()
|
||||
{
|
||||
List<PanelConfig> panels = new List<PanelConfig>();
|
||||
|
||||
// PanelIndex starts at 1
|
||||
for (var i = 1; i <= ActiveProfile.PanelSourceCoordinates.Count; i++)
|
||||
{
|
||||
var x = ActiveProfile.PanelSourceCoordinates[i - 1].X;
|
||||
var y = ActiveProfile.PanelSourceCoordinates[i - 1].Y;
|
||||
InputEmulationManager.PopOutPanel(x, y, AppSetting.UseLeftRightControlToPopOut);
|
||||
|
||||
var handle = PInvoke.FindWindow("AceApp", String.Empty); // Get an AceApp window with empty title
|
||||
|
||||
// Need to move the window to upper left corner first. There is a possible bug in the game that panel pop out to full screen that prevents further clicking.
|
||||
if (handle != IntPtr.Zero)
|
||||
WindowActionManager.MoveWindow(handle, PanelType.CustomPopout, 0, 0, 800, 600);
|
||||
|
||||
if (i > 1)
|
||||
SeparatePanel(panels[0].PanelHandle); // The joined panel is always the first panel that got popped out
|
||||
|
||||
handle = PInvoke.FindWindow("AceApp", String.Empty); // Get an AceApp window with empty title
|
||||
|
||||
if (handle == IntPtr.Zero && i == 1)
|
||||
{
|
||||
StatusMessageWriter.WriteMessage("Unable to pop out the first panel. Please check the first panel's number circle is positioned inside the panel, check for panel obstruction, and check if panel can be popped out. Pop out process stopped.", StatusMessageType.Error, true);
|
||||
return null;
|
||||
}
|
||||
else if (handle == IntPtr.Zero)
|
||||
{
|
||||
StatusMessageWriter.WriteMessage($"Unable to pop out panel number {i}. Please check panel's number circle is positioned inside the panel, check for panel obstruction, and check if panel can be popped out. Pop out process stopped.", StatusMessageType.Error, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
var panel = new PanelConfig();
|
||||
panel.PanelHandle = handle;
|
||||
panel.PanelType = PanelType.CustomPopout;
|
||||
panel.PanelIndex = i;
|
||||
panel.PanelName = $"Panel{i}";
|
||||
panel.Top = (i - 1) * 30;
|
||||
panel.Left = (i - 1) * 30;
|
||||
panel.Width = 800;
|
||||
panel.Height = 600;
|
||||
panels.Add(panel);
|
||||
|
||||
PInvoke.SetWindowText(panel.PanelHandle, panel.PanelName + " (Custom)");
|
||||
}
|
||||
|
||||
//Performance validation, make sure the number of pop out panels is equal to the number of selected panel
|
||||
if (WindowActionManager.GetWindowsCountByPanelType(new List<PanelType>() { PanelType.CustomPopout }) != ActiveProfile.PanelSourceCoordinates.Count)
|
||||
{
|
||||
StatusMessageWriter.WriteMessage("Unable to pop out all panels. Please align all panel number circles with in-game panel locations.", StatusMessageType.Error, false);
|
||||
return null;
|
||||
}
|
||||
|
||||
return panels;
|
||||
}
|
||||
|
||||
private void SeparatePanel(IntPtr hwnd)
|
||||
{
|
||||
// Resize all windows to 800x600 when separating and shimmy the panel
|
||||
// MSFS draws popout panel differently at different time for same panel
|
||||
|
||||
// ToDo: Need to figure mouse click code to separate window
|
||||
WindowActionManager.MoveWindow(hwnd, PanelType.CustomPopout, -8, 0, 800, 600);
|
||||
PInvoke.SetForegroundWindow(hwnd);
|
||||
Thread.Sleep(500);
|
||||
|
||||
// Find the magnifying glass coordinate
|
||||
var point = PanelAnalyzer.GetMagnifyingGlassIconCoordinate(hwnd);
|
||||
|
||||
InputEmulationManager.LeftClick(point.X, point.Y);
|
||||
}
|
||||
|
||||
private List<PanelConfig> AddMsfsTouchPanels(int panelIndex)
|
||||
{
|
||||
List<PanelConfig> touchPanels = new List<PanelConfig>();
|
||||
|
||||
if (AppSetting.TouchPanelSettings.EnableTouchPanelIntegration)
|
||||
{
|
||||
TouchPanelOrchestrator.LoadPlaneProfiles();
|
||||
|
||||
if (TouchPanelOrchestrator.PlaneProfiles == null)
|
||||
return null;
|
||||
|
||||
// Find all selected panels
|
||||
var panelConfigs = TouchPanelOrchestrator.PlaneProfiles.SelectMany(p => p.Panels.Where(c => c.IsSelected));
|
||||
|
||||
foreach (var panelConfig in panelConfigs)
|
||||
{
|
||||
var caption = $"{panelConfig.Name} (Touch Panel)";
|
||||
|
||||
// Change width and height to 1080p aspect ratio
|
||||
double aspectRatio = 1;
|
||||
if (panelConfig.Width > 1920)
|
||||
{
|
||||
aspectRatio = Convert.ToDouble(1920) / panelConfig.Width;
|
||||
panelConfig.Width = 1920; // there are hidden padding to make it to 1920
|
||||
panelConfig.Height = Convert.ToInt32(panelConfig.Height * aspectRatio);
|
||||
}
|
||||
|
||||
OnTouchPanelOpened?.Invoke(this, new TouchPanelOpenEventArg() { PlaneId = panelConfig.PlaneId, PanelId = panelConfig.PanelId, Caption = caption, Width = panelConfig.Width, Height = panelConfig.Height });
|
||||
|
||||
// detect for a max of 5 seconds
|
||||
int tryCount = 0;
|
||||
while (tryCount < 10)
|
||||
{
|
||||
var touchPanelHandle = WindowActionManager.FindWindowByCaption(caption);
|
||||
if (touchPanelHandle != IntPtr.Zero)
|
||||
{
|
||||
var dimension = WindowActionManager.GetWindowRect(touchPanelHandle);
|
||||
var panelInfo = new PanelConfig
|
||||
{
|
||||
PanelIndex = panelIndex,
|
||||
PanelHandle = touchPanelHandle,
|
||||
PanelName = caption,
|
||||
PanelType = PanelType.MSFSTouchPanel,
|
||||
Top = dimension.Top,
|
||||
Left = dimension.Left,
|
||||
Width = panelConfig.Width,
|
||||
Height = panelConfig.Height,
|
||||
AlwaysOnTop = true // default to always on top
|
||||
};
|
||||
|
||||
touchPanels.Add(panelInfo);
|
||||
break;
|
||||
}
|
||||
Thread.Sleep(500);
|
||||
tryCount++;
|
||||
}
|
||||
|
||||
if (tryCount == 10)
|
||||
return null;
|
||||
|
||||
panelIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
return touchPanels;
|
||||
}
|
||||
|
||||
private void LoadAndApplyPanelConfigs(List<PanelConfig> panelResults)
|
||||
{
|
||||
Parallel.ForEach(panelResults, panel =>
|
||||
{
|
||||
// Something is wrong here where panel has no window handle
|
||||
if (panel.PanelHandle == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
var savedPanelConfig = ActiveProfile.PanelConfigs.FirstOrDefault(s => s.PanelIndex == panel.PanelIndex);
|
||||
|
||||
// Assign previous saved values
|
||||
if (savedPanelConfig != null)
|
||||
{
|
||||
panel.PanelName = savedPanelConfig.PanelName;
|
||||
panel.Top = savedPanelConfig.Top;
|
||||
panel.Left = savedPanelConfig.Left;
|
||||
panel.Width = savedPanelConfig.Width;
|
||||
panel.Height = savedPanelConfig.Height;
|
||||
panel.FullScreen = savedPanelConfig.FullScreen;
|
||||
panel.AlwaysOnTop = savedPanelConfig.AlwaysOnTop;
|
||||
panel.HideTitlebar = savedPanelConfig.HideTitlebar;
|
||||
panel.TouchEnabled = savedPanelConfig.TouchEnabled;
|
||||
}
|
||||
|
||||
// Apply panel name
|
||||
if (panel.PanelType == PanelType.CustomPopout)
|
||||
{
|
||||
var caption = panel.PanelName + " (Custom)";
|
||||
PInvoke.SetWindowText(panel.PanelHandle, caption);
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
|
||||
// Apply locations
|
||||
if (panel.Width != 0 && panel.Height != 0)
|
||||
{
|
||||
PInvoke.ShowWindow(panel.PanelHandle, PInvokeConstant.SW_RESTORE);
|
||||
Thread.Sleep(250);
|
||||
WindowActionManager.MoveWindow(panel.PanelHandle, panel.PanelType, panel.Left, panel.Top, panel.Width, panel.Height);
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
if (!panel.FullScreen)
|
||||
{
|
||||
// Apply always on top
|
||||
if (panel.AlwaysOnTop)
|
||||
{
|
||||
WindowActionManager.ApplyAlwaysOnTop(panel.PanelHandle, panel.PanelType, true, new Rectangle(panel.Left, panel.Top, panel.Width, panel.Height));
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
// Apply hide title bar
|
||||
if (panel.HideTitlebar)
|
||||
WindowActionManager.ApplyHidePanelTitleBar(panel.PanelHandle, true);
|
||||
}
|
||||
|
||||
PInvoke.ShowWindow(panel.PanelHandle, PInvokeConstant.SW_RESTORE);
|
||||
});
|
||||
|
||||
// Apply full screen (cannot combine with always on top or hide title bar)
|
||||
// Cannot run in parallel process
|
||||
panelResults.ForEach(panel =>
|
||||
{
|
||||
if (panel.FullScreen && (!panel.AlwaysOnTop && !panel.HideTitlebar))
|
||||
{
|
||||
InputEmulationManager.ToggleFullScreenPanel(panel.PanelHandle);
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ReturnToAfterPopOutCameraView()
|
||||
{
|
||||
if (!AppSetting.AfterPopOutCameraView.EnableReturnToCameraView)
|
||||
return;
|
||||
|
||||
switch (AppSetting.AfterPopOutCameraView.CameraView)
|
||||
{
|
||||
case AfterPopOutCameraViewType.CockpitCenterView:
|
||||
InputEmulationManager.CenterView();
|
||||
break;
|
||||
case AfterPopOutCameraViewType.CustomCameraView:
|
||||
InputEmulationManager.LoadCustomView(AppSetting.AfterPopOutCameraView.CustomCameraKeyBinding);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
173
Orchestration/PanelSourceOrchestrator.cs
Normal file
|
@ -0,0 +1,173 @@
|
|||
using MSFSPopoutPanelManager.Shared;
|
||||
using MSFSPopoutPanelManager.UserDataAgent;
|
||||
using MSFSPopoutPanelManager.WindowsAgent;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Drawing;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Orchestration
|
||||
{
|
||||
public class PanelSourceOrchestrator : ObservableObject
|
||||
{
|
||||
private int _panelIndex;
|
||||
|
||||
internal ProfileData ProfileData { get; set; }
|
||||
|
||||
internal AppSettingData AppSettingData { get; set; }
|
||||
|
||||
internal FlightSimData FlightSimData { get; set; }
|
||||
|
||||
internal FlightSimOrchestrator FlightSimOrchestrator { get; set; }
|
||||
|
||||
private Profile ActiveProfile { get { return ProfileData == null ? null : ProfileData.ActiveProfile; } }
|
||||
|
||||
private AppSetting AppSetting { get { return AppSettingData == null ? null : AppSettingData.AppSetting; } }
|
||||
|
||||
public event EventHandler<PanelSourceCoordinate> onOverlayShowed;
|
||||
public event EventHandler OnLastOverlayRemoved;
|
||||
public event EventHandler OnAllOverlaysRemoved;
|
||||
public event EventHandler OnSelectionStarted;
|
||||
public event EventHandler OnSelectionCompleted;
|
||||
|
||||
public bool IsEditingPanelSource { get; set; }
|
||||
|
||||
public void StartPanelSelection()
|
||||
{
|
||||
if (ActiveProfile == null)
|
||||
return;
|
||||
|
||||
_panelIndex = 1;
|
||||
|
||||
// remove all existing panel overlay display
|
||||
for (var i = 0; i < ActiveProfile.PanelSourceCoordinates.Count; i++)
|
||||
OnLastOverlayRemoved?.Invoke(this, null);
|
||||
|
||||
ActiveProfile.PanelSourceCoordinates = new ObservableCollection<PanelSourceCoordinate>();
|
||||
ActiveProfile.PanelConfigs.Clear();
|
||||
ActiveProfile.IsLocked = false;
|
||||
|
||||
InputHookManager.OnLeftClick -= HandleOnPanelSelectionAdded;
|
||||
InputHookManager.OnLeftClick += HandleOnPanelSelectionAdded;
|
||||
InputHookManager.OnShiftLeftClick -= HandleOnLastPanelSelectionRemoved;
|
||||
InputHookManager.OnShiftLeftClick += HandleOnLastPanelSelectionRemoved;
|
||||
InputHookManager.OnCtrlLeftClick -= HandleOnPanelSelectionCompleted;
|
||||
InputHookManager.OnCtrlLeftClick += HandleOnPanelSelectionCompleted;
|
||||
|
||||
// Turn off TrackIR if TrackIR is started
|
||||
FlightSimOrchestrator.TurnOffTrackIR();
|
||||
|
||||
OnSelectionStarted?.Invoke(this, null);
|
||||
|
||||
InputHookManager.StartHook();
|
||||
}
|
||||
|
||||
public void SaveAutoPanningCamera()
|
||||
{
|
||||
var simualatorProcess = WindowProcessManager.GetSimulatorProcess();
|
||||
if (simualatorProcess == null)
|
||||
{
|
||||
StatusMessageWriter.WriteMessage("MSFS/SimConnect has not been started. Please try again at a later time.", StatusMessageType.Error, false);
|
||||
return;
|
||||
}
|
||||
|
||||
InputEmulationManager.SaveCustomView(AppSettingData.AppSetting.AutoPanningKeyBinding);
|
||||
StatusMessageWriter.WriteMessage("Auto Panning Camera has been saved succesfully.", StatusMessageType.Info, false);
|
||||
}
|
||||
|
||||
public void EditPanelSource()
|
||||
{
|
||||
IsEditingPanelSource = !IsEditingPanelSource;
|
||||
|
||||
if (IsEditingPanelSource)
|
||||
StartEditPanelSource();
|
||||
else
|
||||
EndEditPanelSource();
|
||||
}
|
||||
|
||||
public void CloseAllPanelSource()
|
||||
{
|
||||
IsEditingPanelSource = false;
|
||||
OnAllOverlaysRemoved?.Invoke(this, null);
|
||||
}
|
||||
|
||||
public void HandleOnPanelSelectionAdded(object sender, Point e)
|
||||
{
|
||||
if (ActiveProfile == null)
|
||||
return;
|
||||
|
||||
var newCoor = new PanelSourceCoordinate() { PanelIndex = _panelIndex, X = e.X, Y = e.Y };
|
||||
|
||||
ActiveProfile.PanelSourceCoordinates.Add(newCoor);
|
||||
_panelIndex++;
|
||||
|
||||
onOverlayShowed?.Invoke(this, newCoor);
|
||||
}
|
||||
|
||||
public void HandleOnLastPanelSelectionRemoved(object sender, Point e)
|
||||
{
|
||||
if (ActiveProfile == null)
|
||||
return;
|
||||
|
||||
if (ActiveProfile.PanelSourceCoordinates.Count > 0)
|
||||
{
|
||||
ActiveProfile.PanelSourceCoordinates.RemoveAt(ActiveProfile.PanelSourceCoordinates.Count - 1);
|
||||
_panelIndex--;
|
||||
|
||||
OnLastOverlayRemoved?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartEditPanelSource()
|
||||
{
|
||||
if (ActiveProfile == null)
|
||||
return;
|
||||
|
||||
// remove all existing panel overlay display
|
||||
for (var i = 0; i < ActiveProfile.PanelSourceCoordinates.Count; i++)
|
||||
OnAllOverlaysRemoved?.Invoke(this, null);
|
||||
|
||||
foreach (var coor in ActiveProfile.PanelSourceCoordinates)
|
||||
onOverlayShowed?.Invoke(this, new PanelSourceCoordinate() { PanelIndex = coor.PanelIndex, X = coor.X, Y = coor.Y });
|
||||
|
||||
// Turn off TrackIR if TrackIR is started
|
||||
FlightSimOrchestrator.TurnOffTrackIR();
|
||||
|
||||
if (AppSetting.UseAutoPanning && FlightSimData.IsEnteredFlight)
|
||||
InputEmulationManager.LoadCustomView(AppSetting.AutoPanningKeyBinding);
|
||||
}
|
||||
|
||||
private void EndEditPanelSource()
|
||||
{
|
||||
if (ActiveProfile == null)
|
||||
return;
|
||||
|
||||
// remove all existing panel overlay display
|
||||
for (var i = 0; i < ActiveProfile.PanelSourceCoordinates.Count; i++)
|
||||
OnAllOverlaysRemoved?.Invoke(this, null);
|
||||
|
||||
// Turn TrackIR back on
|
||||
FlightSimOrchestrator.TurnOnTrackIR();
|
||||
}
|
||||
|
||||
private void HandleOnPanelSelectionCompleted(object sender, Point e)
|
||||
{
|
||||
if (ActiveProfile == null)
|
||||
return;
|
||||
|
||||
//If enable, save the current viewport into custom view by Ctrl-Alt-0
|
||||
if (AppSetting.UseAutoPanning)
|
||||
InputEmulationManager.SaveCustomView(AppSetting.AutoPanningKeyBinding);
|
||||
|
||||
ProfileData.WriteProfiles();
|
||||
|
||||
InputHookManager.EndHook();
|
||||
|
||||
// Turn TrackIR back on
|
||||
FlightSimOrchestrator.TurnOnTrackIR();
|
||||
|
||||
IsEditingPanelSource = false;
|
||||
|
||||
OnSelectionCompleted?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
}
|
169
Orchestration/ProfileData.cs
Normal file
|
@ -0,0 +1,169 @@
|
|||
using MSFSPopoutPanelManager.Shared;
|
||||
using MSFSPopoutPanelManager.UserDataAgent;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Orchestration
|
||||
{
|
||||
public class ProfileData : ObservableObject
|
||||
{
|
||||
public event PropertyChangedEventHandler ActiveProfileChanged;
|
||||
|
||||
public ProfileData()
|
||||
{
|
||||
Profiles = new ObservableCollection<Profile>();
|
||||
}
|
||||
|
||||
public ObservableCollection<Profile> Profiles { get; private set; }
|
||||
|
||||
public FlightSimData FlightSimData { private get; set; }
|
||||
|
||||
public AppSettingData AppSettingData { private get; set; }
|
||||
|
||||
public int AddProfile(string profileName)
|
||||
{
|
||||
var newProfileId = ProfileManager.AddProfile(profileName, Profiles);
|
||||
UpdateActiveProfile(newProfileId);
|
||||
AppSettingData.AppSetting.LastUsedProfileId = newProfileId;
|
||||
return newProfileId;
|
||||
}
|
||||
|
||||
public int AddProfile(string profileName, int copyFromProfileId)
|
||||
{
|
||||
var newProfileId = ProfileManager.AddProfile(profileName, copyFromProfileId, Profiles);
|
||||
UpdateActiveProfile(newProfileId);
|
||||
AppSettingData.AppSetting.LastUsedProfileId = newProfileId;
|
||||
return newProfileId;
|
||||
}
|
||||
|
||||
public bool DeleteProfile(int profileId)
|
||||
{
|
||||
if (ActiveProfile == null)
|
||||
return false;
|
||||
|
||||
return ProfileManager.DeleteProfile(profileId, Profiles);
|
||||
}
|
||||
|
||||
public void AddProfileBinding(string planeTitle, int activeProfileId)
|
||||
{
|
||||
if (ActiveProfile == null)
|
||||
return;
|
||||
|
||||
ProfileManager.AddProfileBinding(planeTitle, activeProfileId, Profiles);
|
||||
RefreshProfile();
|
||||
}
|
||||
|
||||
public void DeleteProfileBinding(string planeTitle, int activeProfileId)
|
||||
{
|
||||
if (ActiveProfile == null)
|
||||
return;
|
||||
|
||||
ProfileManager.DeleteProfileBinding(planeTitle, activeProfileId, Profiles);
|
||||
RefreshProfile();
|
||||
}
|
||||
|
||||
public void ReadProfiles()
|
||||
{
|
||||
Profiles = new ObservableCollection<Profile>(ProfileManager.ReadProfiles());
|
||||
}
|
||||
|
||||
public void WriteProfiles()
|
||||
{
|
||||
ProfileManager.WriteProfiles(Profiles);
|
||||
}
|
||||
|
||||
public void UpdateActiveProfile(int profileId)
|
||||
{
|
||||
if (profileId == -1 && Profiles.Count > 0)
|
||||
ActiveProfile = Profiles.FirstOrDefault(p => p.ProfileId == Profiles[0].ProfileId);
|
||||
else if (profileId == -1 || Profiles.Count == 0)
|
||||
ActiveProfile = null;
|
||||
else
|
||||
ActiveProfile = Profiles.FirstOrDefault(p => p.ProfileId == profileId);
|
||||
|
||||
// Set active profile flag, this is used only for MVVM binding
|
||||
Profiles.ToList().ForEach(p => p.IsActive = false);
|
||||
|
||||
if (ActiveProfile != null)
|
||||
{
|
||||
ActiveProfile.IsActive = true;
|
||||
AppSettingData.AppSetting.LastUsedProfileId = ActiveProfile.ProfileId;
|
||||
}
|
||||
else
|
||||
{
|
||||
AppSettingData.AppSetting.LastUsedProfileId = -1;
|
||||
}
|
||||
|
||||
ActiveProfileChanged?.Invoke(this, null);
|
||||
}
|
||||
|
||||
public Profile ActiveProfile { get; private set; }
|
||||
|
||||
public bool HasActiveProfile { get { return ActiveProfile != null; } }
|
||||
|
||||
public bool IsAircraftBoundToProfile
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ActiveProfile == null)
|
||||
return false;
|
||||
|
||||
return ActiveProfile.BindingAircraftLiveries.Any(p => p == FlightSimData.CurrentMsfsPlaneTitle);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAllowedDeleteAircraftBinding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ActiveProfile == null || !FlightSimData.HasCurrentMsfsPlaneTitle)
|
||||
return false;
|
||||
|
||||
var uProfile = Profiles.FirstOrDefault(u => u.BindingAircraftLiveries.Any(p => p == FlightSimData.CurrentMsfsPlaneTitle));
|
||||
if (uProfile != null && uProfile.ProfileId != ActiveProfile.ProfileId)
|
||||
return false;
|
||||
|
||||
return ActiveProfile.BindingAircraftLiveries.Any(p => p == FlightSimData.CurrentMsfsPlaneTitle);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAllowedAddAircraftBinding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ActiveProfile == null || !FlightSimData.HasCurrentMsfsPlaneTitle)
|
||||
return false;
|
||||
|
||||
var uProfile = Profiles.FirstOrDefault(u => u.BindingAircraftLiveries.Any(p => p == FlightSimData.CurrentMsfsPlaneTitle));
|
||||
if (uProfile != null && uProfile.ProfileId != ActiveProfile.ProfileId)
|
||||
return false;
|
||||
|
||||
return !ActiveProfile.BindingAircraftLiveries.Any(p => p == FlightSimData.CurrentMsfsPlaneTitle);
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshProfile()
|
||||
{
|
||||
int currentProfileId;
|
||||
if (ActiveProfile == null)
|
||||
currentProfileId = -1;
|
||||
else
|
||||
currentProfileId = ActiveProfile.ProfileId;
|
||||
|
||||
ActiveProfile = null;
|
||||
UpdateActiveProfile(currentProfileId);
|
||||
}
|
||||
|
||||
public void AutoSwitchProfile(string activeAircraftTitle)
|
||||
{
|
||||
// Automatic switching of active profile when SimConnect active aircraft livery changes
|
||||
if (Profiles != null)
|
||||
{
|
||||
var matchedProfile = Profiles.FirstOrDefault(p => p.BindingAircraftLiveries.Any(t => t == activeAircraftTitle));
|
||||
if (matchedProfile != null)
|
||||
UpdateActiveProfile(matchedProfile.ProfileId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
Orchestration/ProfileOrchestrator.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using MSFSPopoutPanelManager.Shared;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Orchestration
|
||||
{
|
||||
public class ProfileOrchestrator : ObservableObject
|
||||
{
|
||||
internal ProfileData ProfileData { get; set; }
|
||||
|
||||
public void AddProfile(string profileName, int copyProfileId)
|
||||
{
|
||||
if (copyProfileId == -1)
|
||||
ProfileData.AddProfile(profileName);
|
||||
else
|
||||
ProfileData.AddProfile(profileName, copyProfileId);
|
||||
}
|
||||
|
||||
public void DeleteActiveProfile()
|
||||
{
|
||||
if (ProfileData.ActiveProfile != null)
|
||||
ProfileData.DeleteProfile(ProfileData.ActiveProfile.ProfileId);
|
||||
}
|
||||
|
||||
public void ChangeProfile(int profileId)
|
||||
{
|
||||
if (ProfileData != null)
|
||||
ProfileData.UpdateActiveProfile(profileId);
|
||||
}
|
||||
|
||||
public void AddProfileBinding(string bindingLiveryName)
|
||||
{
|
||||
if (ProfileData.ActiveProfile != null)
|
||||
ProfileData.AddProfileBinding(bindingLiveryName, ProfileData.ActiveProfile.ProfileId);
|
||||
}
|
||||
|
||||
public void DeleteProfileBinding(string bindingLiveryName)
|
||||
{
|
||||
if (ProfileData.ActiveProfile != null)
|
||||
ProfileData.DeleteProfileBinding(bindingLiveryName, ProfileData.ActiveProfile.ProfileId);
|
||||
}
|
||||
}
|
||||
}
|
187
Orchestration/TouchPanelOrchestrator.cs
Normal file
|
@ -0,0 +1,187 @@
|
|||
using MSFSPopoutPanelManager.Shared;
|
||||
using MSFSPopoutPanelManager.TouchPanelAgent;
|
||||
using MSFSPopoutPanelManager.UserDataAgent;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Dynamic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Orchestration
|
||||
{
|
||||
public class TouchPanelOrchestrator : ObservableObject
|
||||
{
|
||||
private ITouchPanelServer _touchPanelServer;
|
||||
|
||||
internal ProfileData ProfileData { get; set; }
|
||||
|
||||
internal AppSettingData AppSettingData { get; set; }
|
||||
|
||||
public ObservableCollection<PlaneProfile> PlaneProfiles { get; private set; }
|
||||
|
||||
public IntPtr ApplicationHandle { get; set; }
|
||||
|
||||
public void LoadPlaneProfiles()
|
||||
{
|
||||
if (_touchPanelServer == null || ProfileData.ActiveProfile == null)
|
||||
return;
|
||||
|
||||
var dataItems = TouchPanelManager.GetPlaneProfiles();
|
||||
|
||||
// Map plane profiles to bindable objects
|
||||
PlaneProfiles = new ObservableCollection<PlaneProfile>();
|
||||
|
||||
foreach (var dataItem in dataItems)
|
||||
{
|
||||
var planeProfile = new PlaneProfile();
|
||||
planeProfile.PlaneId = dataItem.PlaneId;
|
||||
planeProfile.Name = dataItem.Name;
|
||||
|
||||
if (dataItem.Panels != null && dataItem.Panels.Count > 0)
|
||||
{
|
||||
planeProfile.Panels = new ObservableCollection<PanelProfile>();
|
||||
|
||||
foreach (var dataItemPanel in dataItem.Panels)
|
||||
{
|
||||
planeProfile.Panels.Add(new PanelProfile()
|
||||
{
|
||||
PlaneId = dataItem.PlaneId,
|
||||
PanelId = dataItemPanel.PanelId,
|
||||
Name = dataItemPanel.Name,
|
||||
Width = dataItemPanel.PanelSize.Width,
|
||||
Height = dataItemPanel.PanelSize.Height,
|
||||
IsSelected = ProfileData.ActiveProfile.TouchPanelBindings == null ? false : ProfileData.ActiveProfile.TouchPanelBindings.Any(b => b.PlaneId == dataItem.PlaneId && b.PanelId == dataItemPanel.PanelId)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
PlaneProfiles.Add(planeProfile);
|
||||
}
|
||||
}
|
||||
|
||||
public void PanelSelected(IList<PlaneProfile> PlaneProfiles)
|
||||
{
|
||||
ProfileData.ActiveProfile.PanelConfigs.RemoveAll(panelConfig => panelConfig.PanelType == PanelType.MSFSTouchPanel);
|
||||
ProfileData.ActiveProfile.TouchPanelBindings.Clear();
|
||||
ProfileData.ActiveProfile.IsLocked = false;
|
||||
|
||||
foreach (var planeProfile in PlaneProfiles)
|
||||
{
|
||||
foreach (var panel in planeProfile.Panels)
|
||||
{
|
||||
if (panel.IsSelected)
|
||||
ProfileData.ActiveProfile.TouchPanelBindings.Add(new TouchPanelBinding() { PlaneId = planeProfile.PlaneId, PanelId = panel.PanelId });
|
||||
}
|
||||
}
|
||||
|
||||
ProfileData.WriteProfiles();
|
||||
}
|
||||
|
||||
internal async Task StartServer()
|
||||
{
|
||||
if (_touchPanelServer == null)
|
||||
{
|
||||
// This is use to remove all references and dependencies so single file package will not include any ASP.NET core dlls
|
||||
#if DEBUGTOUCHPANEL || RELEASETOUCHPANEL
|
||||
_touchPanelServer = new MSFSPopoutPanelManager.WebServer.WebHost();
|
||||
#endif
|
||||
if (_touchPanelServer != null)
|
||||
{
|
||||
_touchPanelServer.WindowHandle = ApplicationHandle;
|
||||
_touchPanelServer.DataRefreshInterval = AppSettingData.AppSetting.TouchPanelSettings.DataRefreshInterval;
|
||||
_touchPanelServer.MapRefreshInterval = AppSettingData.AppSetting.TouchPanelSettings.MapRefreshInterval;
|
||||
_touchPanelServer.IsUsedArduino = AppSettingData.AppSetting.TouchPanelSettings.UseArduino;
|
||||
_touchPanelServer.IsEnabledSound = AppSettingData.AppSetting.TouchPanelSettings.EnableSound;
|
||||
await _touchPanelServer.StartAsync(default(CancellationToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task StopServer()
|
||||
{
|
||||
if (_touchPanelServer != null && _touchPanelServer.ServerStarted)
|
||||
{
|
||||
await _touchPanelServer.StopAsync(default(CancellationToken));
|
||||
_touchPanelServer = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task TouchPanelIntegrationUpdated(bool enabled)
|
||||
{
|
||||
if ((_touchPanelServer == null || !_touchPanelServer.ServerStarted) && enabled)
|
||||
await StartServer();
|
||||
else if (_touchPanelServer.ServerStarted && !enabled)
|
||||
await StopServer();
|
||||
}
|
||||
|
||||
internal async void ReloadTouchPanelSimConnectDataDefinition()
|
||||
{
|
||||
if (_touchPanelServer == null)
|
||||
return;
|
||||
|
||||
// Communicate with Touch Panel API server to reload SimConnect data definitions
|
||||
if (ProfileData.ActiveProfile != null && ProfileData.ActiveProfile.TouchPanelBindings.Count > 0 && _touchPanelServer.ServerStarted)
|
||||
{
|
||||
var planeId = ProfileData.ActiveProfile.TouchPanelBindings.Count > 0 ?
|
||||
ProfileData.ActiveProfile.TouchPanelBindings[0].PlaneId :
|
||||
null;
|
||||
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
dynamic data = new ExpandoObject();
|
||||
data.PlaneId = planeId;
|
||||
var payload = JsonConvert.SerializeObject(data);
|
||||
|
||||
var url = "http://localhost:27012/posttouchpanelloaded";
|
||||
|
||||
try
|
||||
{
|
||||
var response = await client.PostAsync(url, new StringContent(payload, Encoding.UTF8, "application/json"));
|
||||
var token = response.Content.ReadAsStringAsync().Result;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PlaneProfile : ObservableObject
|
||||
{
|
||||
public string PlaneId { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public ObservableCollection<PanelProfile> Panels { get; set; }
|
||||
|
||||
public bool HasSelected
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Panels == null)
|
||||
return false;
|
||||
|
||||
return Panels.Any(p => p.IsSelected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class PanelProfile : ObservableObject
|
||||
{
|
||||
public string PlaneId { get; set; }
|
||||
|
||||
public string PanelId { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
public int Height { get; set; }
|
||||
|
||||
public bool IsSelected { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
using MSFSPopoutPanelManager.Shared;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Threading;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Provider
|
||||
{
|
||||
public class ImageOperation
|
||||
{
|
||||
public static Bitmap TakeScreenShot(IntPtr windowHandle)
|
||||
{
|
||||
if (!PInvoke.IsWindow(windowHandle))
|
||||
throw new PopoutManagerException("Pop out windows were closed unexpectedly.");
|
||||
|
||||
// Set window to foreground so nothing can hide the window
|
||||
PInvoke.SetForegroundWindow(windowHandle);
|
||||
Thread.Sleep(300);
|
||||
|
||||
Rectangle rectangle;
|
||||
PInvoke.GetWindowRect(windowHandle, out rectangle);
|
||||
|
||||
Rectangle clientRectangle;
|
||||
PInvoke.GetClientRect(windowHandle, out clientRectangle);
|
||||
|
||||
// Take a screen shot by removing the titlebar of the window
|
||||
var left = rectangle.Left;
|
||||
var top = rectangle.Top + (rectangle.Height - clientRectangle.Height) - 8; // 8 pixels adjustments
|
||||
|
||||
var bmp = new Bitmap(clientRectangle.Width, clientRectangle.Height, PixelFormat.Format24bppRgb);
|
||||
|
||||
using (Graphics g = Graphics.FromImage(bmp))
|
||||
{
|
||||
g.CopyFromScreen(new Point(left, top), Point.Empty, rectangle.Size);
|
||||
}
|
||||
|
||||
// Place the above image in the same canvas size as before
|
||||
Bitmap backingImage = new Bitmap(rectangle.Width, rectangle.Height);
|
||||
using (Graphics gfx = Graphics.FromImage(backingImage))
|
||||
{
|
||||
using (SolidBrush brush = new SolidBrush(Color.FromArgb(255, 0, 0)))
|
||||
{
|
||||
gfx.FillRectangle(brush, 0, 0, rectangle.Width, rectangle.Height);
|
||||
gfx.DrawImage(bmp, new Point(0, top));
|
||||
}
|
||||
}
|
||||
|
||||
return backingImage;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
using Gma.System.MouseKeyHook;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Provider
|
||||
{
|
||||
public class InputHookManager
|
||||
{
|
||||
private const string CTRL_KEY = "Control";
|
||||
private const string SHIFT_KEY = "Shift";
|
||||
|
||||
private static IKeyboardMouseEvents _mouseHook;
|
||||
|
||||
public static bool SubscribeToPanelSelectionEvent { get; set; }
|
||||
public static bool SubscribeToStartPopOutEvent { get; set; }
|
||||
|
||||
public static event EventHandler OnPanelSelectionCompleted;
|
||||
public static event EventHandler<Point> OnPanelSelectionAdded;
|
||||
public static event EventHandler OnPanelSelectionRemoved;
|
||||
|
||||
public static void StartHook()
|
||||
{
|
||||
if (_mouseHook == null)
|
||||
{
|
||||
_mouseHook = Hook.GlobalEvents();
|
||||
_mouseHook.MouseDownExt += HandleMouseHookMouseDownExt;
|
||||
}
|
||||
}
|
||||
|
||||
public static void EndHook()
|
||||
{
|
||||
if (_mouseHook != null)
|
||||
{
|
||||
_mouseHook.MouseDownExt -= HandleMouseHookMouseDownExt;
|
||||
_mouseHook.Dispose();
|
||||
_mouseHook = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleMouseHookMouseDownExt(object sender, MouseEventExtArgs e)
|
||||
{
|
||||
if (_mouseHook == null || !SubscribeToPanelSelectionEvent)
|
||||
return;
|
||||
|
||||
if (e.Button == MouseButtons.Left)
|
||||
{
|
||||
var ctrlPressed = Control.ModifierKeys.ToString() == CTRL_KEY;
|
||||
var shiftPressed = Control.ModifierKeys.ToString() == SHIFT_KEY;
|
||||
|
||||
if (ctrlPressed)
|
||||
{
|
||||
OnPanelSelectionCompleted?.Invoke(null, null);
|
||||
}
|
||||
else if (shiftPressed)
|
||||
{
|
||||
OnPanelSelectionRemoved?.Invoke(null, null);
|
||||
}
|
||||
else if (!shiftPressed)
|
||||
{
|
||||
OnPanelSelectionAdded?.Invoke(null, new Point(e.X, e.Y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,563 +0,0 @@
|
|||
using MSFSPopoutPanelManager.Model;
|
||||
using MSFSPopoutPanelManager.Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Provider
|
||||
{
|
||||
public class PanelPopOutManager
|
||||
{
|
||||
private const int RETRY_COUNT = 5;
|
||||
|
||||
private UserProfileManager _userProfileManager;
|
||||
private SimConnectManager _simConnectManager;
|
||||
private IntPtr _simulatorHandle;
|
||||
private List<PanelConfig> _panels;
|
||||
private int _currentPanelIndex;
|
||||
|
||||
public event EventHandler OnPopOutStarted;
|
||||
public event EventHandler<EventArgs<bool>> OnPopOutCompleted;
|
||||
|
||||
public UserProfile UserProfile { get; set; }
|
||||
|
||||
public AppSetting AppSetting { get; set; }
|
||||
|
||||
public PanelPopOutManager(UserProfileManager userProfileManager, SimConnectManager simConnectManager)
|
||||
{
|
||||
_userProfileManager = userProfileManager;
|
||||
_simConnectManager = simConnectManager;
|
||||
}
|
||||
|
||||
public void StartPopout()
|
||||
{
|
||||
var simulatorProcess = DiagnosticManager.GetSimulatorProcess();
|
||||
|
||||
if (simulatorProcess != null)
|
||||
_simulatorHandle = simulatorProcess.Handle;
|
||||
|
||||
_panels = new List<PanelConfig>();
|
||||
|
||||
OnPopOutStarted?.Invoke(this, null);
|
||||
|
||||
if (AppSetting.UseAutoPanning)
|
||||
InputEmulationManager.LoadCustomView(AppSetting.AutoPanningKeyBinding);
|
||||
|
||||
Task<List<PanelConfig>> popoutPanelTask = Task<List<PanelConfig>>.Factory.StartNew(() =>
|
||||
{
|
||||
return ExecutePopoutSeparation();
|
||||
});
|
||||
|
||||
popoutPanelTask.Wait();
|
||||
var popoutResults = popoutPanelTask.Result;
|
||||
|
||||
if (popoutResults != null)
|
||||
{
|
||||
if (UserProfile.PanelConfigs.Count > 0)
|
||||
{
|
||||
LoadAndApplyPanelConfigs(popoutResults);
|
||||
Logger.LogStatus("Panels have been popped out succesfully and saved panel settings have been applied.", StatusMessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
UserProfile.PanelConfigs = new ObservableCollection<PanelConfig>(popoutResults);
|
||||
Logger.LogStatus("Panels have been popped out succesfully.", StatusMessageType.Info);
|
||||
}
|
||||
|
||||
// Recenter the view port by Ctrl-Space, needs to click on game window
|
||||
var simualatorProcess = DiagnosticManager.GetSimulatorProcess();
|
||||
if (simualatorProcess != null)
|
||||
{
|
||||
InputEmulationManager.CenterView(simualatorProcess.Handle);
|
||||
}
|
||||
|
||||
_userProfileManager.WriteUserProfiles();
|
||||
|
||||
OnPopOutCompleted?.Invoke(this, new EventArgs<bool>(true));
|
||||
}
|
||||
else
|
||||
{
|
||||
OnPopOutCompleted?.Invoke(this, new EventArgs<bool>(false));
|
||||
}
|
||||
}
|
||||
|
||||
public List<PanelConfig> ExecutePopoutSeparation()
|
||||
{
|
||||
_currentPanelIndex = 0;
|
||||
|
||||
_panels.Clear();
|
||||
|
||||
// Must close out all existing custom pop out panels
|
||||
PInvoke.EnumWindows(new PInvoke.CallBack(EnumCustomPopoutCallBack), 0);
|
||||
if (_panels.Count > 0)
|
||||
{
|
||||
Logger.LogStatus("Please close all existing panel pop outs before continuing.", StatusMessageType.Error);
|
||||
return null;
|
||||
}
|
||||
|
||||
_panels.Clear();
|
||||
|
||||
if (_simulatorHandle != IntPtr.Zero)
|
||||
PInvoke.SetForegroundWindow(_simulatorHandle);
|
||||
|
||||
try
|
||||
{
|
||||
// PanelIndex starts at 1
|
||||
for (var i = 1; i <= UserProfile.PanelSourceCoordinates.Count; i++)
|
||||
{
|
||||
PopoutPanel(UserProfile.PanelSourceCoordinates[i - 1].X, UserProfile.PanelSourceCoordinates[i - 1].Y);
|
||||
|
||||
if (i > 1)
|
||||
{
|
||||
SeparatePanel(i - 1, _panels[0].PanelHandle); // The joined panel is always the first panel that got popped out
|
||||
}
|
||||
|
||||
var handle = PInvoke.FindWindow("AceApp", String.Empty);
|
||||
|
||||
if (handle == IntPtr.Zero && i == 1)
|
||||
throw new PopoutManagerException("Unable to pop out the first panel. Please check the first panel's number circle is positioned inside the panel, check for panel obstruction, and check if panel can be popped out. Pop out process stopped.");
|
||||
else if (handle == IntPtr.Zero)
|
||||
throw new PopoutManagerException($"Unable to pop out panel number {i}. Please check panel's number circle is positioned inside the panel, check for panel obstruction, and check if panel can be popped out. Pop out process stopped.");
|
||||
|
||||
var panelInfo = GetPanelWindowInfo(handle);
|
||||
panelInfo.PanelIndex = i;
|
||||
panelInfo.PanelName = $"Panel{i}";
|
||||
_panels.Add(panelInfo);
|
||||
|
||||
PInvoke.SetWindowText(panelInfo.PanelHandle, panelInfo.PanelName + " (Custom)");
|
||||
|
||||
if (i > 1)
|
||||
WindowManager.MoveWindow(panelInfo.PanelHandle, panelInfo.PanelType, 0, 0, 800, 600);
|
||||
}
|
||||
|
||||
_currentPanelIndex = _panels.Count;
|
||||
|
||||
// Performance validation, make sure the number of pop out panels is equal to the number of selected panel
|
||||
if (GetPopoutPanelCountByType(PanelType.CustomPopout) != UserProfile.PanelSourceCoordinates.Count)
|
||||
throw new PopoutManagerException("Unable to pop out all panels. Please align all panel number circles with in-game panel locations.");
|
||||
|
||||
// Add the built-in pop outs (ie. ATC, VFR Map) to the panel list
|
||||
if (AppSetting.IncludeBuiltInPanel)
|
||||
PInvoke.EnumWindows(new PInvoke.CallBack(EnumBuiltinPopoutCallBack), 0);
|
||||
|
||||
// Add the MSFS Touch Panel (My other github project) windows to the panel list
|
||||
PInvoke.EnumWindows(new PInvoke.CallBack(EnumMSFSTouchPanelPopoutCallBack), 0);
|
||||
|
||||
if (_panels.Count == 0)
|
||||
throw new PopoutManagerException("No panels have been found. Please select at least one in-game panel.");
|
||||
|
||||
// Line up all the panels and fill in meta data
|
||||
for (var i = _panels.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_panels[i].PanelType == PanelType.CustomPopout)
|
||||
{
|
||||
var shift = _panels.Count - i - 1;
|
||||
_panels[i].Top = shift * 30;
|
||||
_panels[i].Left = shift * 30;
|
||||
_panels[i].Width = 800;
|
||||
_panels[i].Height = 600;
|
||||
|
||||
WindowManager.MoveWindow(_panels[i].PanelHandle, _panels[i].PanelType, _panels[i].Top, _panels[i].Left, _panels[i].Width, _panels[i].Height);
|
||||
PInvoke.SetForegroundWindow(_panels[i].PanelHandle);
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
}
|
||||
|
||||
return _panels;
|
||||
}
|
||||
catch (PopoutManagerException ex)
|
||||
{
|
||||
Logger.LogStatus(ex.Message, StatusMessageType.Error);
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadAndApplyPanelConfigs(List<PanelConfig> popoutResults)
|
||||
{
|
||||
int index;
|
||||
popoutResults.ForEach(resultPanel =>
|
||||
{
|
||||
if (resultPanel.PanelType == PanelType.CustomPopout)
|
||||
{
|
||||
index = UserProfile.PanelConfigs.ToList().FindIndex(x => x.PanelIndex == resultPanel.PanelIndex);
|
||||
if (index > -1)
|
||||
UserProfile.PanelConfigs[index].PanelHandle = resultPanel.PanelHandle;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = UserProfile.PanelConfigs.ToList().FindIndex(x => x.PanelName == resultPanel.PanelName);
|
||||
if (index > -1)
|
||||
UserProfile.PanelConfigs[index].PanelHandle = resultPanel.PanelHandle;
|
||||
else
|
||||
UserProfile.PanelConfigs.Add(resultPanel);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Remove pop out that do not exist for this pop out iteration
|
||||
//foreach(var panelConfig in UserProfile.PanelConfigs.ToList())
|
||||
//{
|
||||
// if(panelConfig.PanelHandle == IntPtr.Zero)
|
||||
// {
|
||||
// UserProfile.PanelConfigs.Remove(panelConfig);
|
||||
// }
|
||||
//}
|
||||
|
||||
Parallel.ForEach(UserProfile.PanelConfigs, panel =>
|
||||
{
|
||||
if (panel != null && panel.PanelHandle != IntPtr.Zero && panel.Width != 0 && panel.Height != 0)
|
||||
{
|
||||
// Apply panel name
|
||||
if (panel.PanelType == PanelType.CustomPopout)
|
||||
{
|
||||
var name = panel.PanelName;
|
||||
if (name.IndexOf("(Custom)") == -1)
|
||||
name = name + " (Custom)";
|
||||
|
||||
PInvoke.SetWindowText(panel.PanelHandle, name);
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
|
||||
if (!panel.FullScreen)
|
||||
{
|
||||
// Apply locations
|
||||
PInvoke.ShowWindow(panel.PanelHandle, PInvokeConstant.SW_RESTORE);
|
||||
Thread.Sleep(250);
|
||||
WindowManager.MoveWindow(panel.PanelHandle, panel.PanelType, panel.Left, panel.Top, panel.Width, panel.Height);
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// Built-in panels (ie. Checklist, ATC) needs another window resize since there is a bug where when move between
|
||||
// monitors, it changes its size
|
||||
if (panel.PanelType == PanelType.BuiltInPopout)
|
||||
{
|
||||
PInvoke.MoveWindow(panel.PanelHandle, panel.Left, panel.Top, panel.Width, panel.Height, false);
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
// Apply always on top
|
||||
if (panel.AlwaysOnTop)
|
||||
{
|
||||
WindowManager.ApplyAlwaysOnTop(panel.PanelHandle, panel.PanelType, true, new Rectangle(panel.Left, panel.Top, panel.Width, panel.Height));
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
|
||||
// Apply hide title bar
|
||||
if (panel.HideTitlebar)
|
||||
{
|
||||
WindowManager.ApplyHidePanelTitleBar(panel.PanelHandle, true);
|
||||
}
|
||||
}
|
||||
|
||||
PInvoke.ShowWindow(panel.PanelHandle, PInvokeConstant.SW_RESTORE);
|
||||
}
|
||||
});
|
||||
|
||||
// Apply full screen (cannot combine with always on top or hide title bar)
|
||||
// Cannot run in parallel process
|
||||
UserProfile.PanelConfigs.ToList().ForEach(panel =>
|
||||
{
|
||||
if (panel.FullScreen && (!panel.AlwaysOnTop && !panel.HideTitlebar))
|
||||
{
|
||||
WindowManager.MoveWindow(panel.PanelHandle, panel.Left, panel.Top);
|
||||
Thread.Sleep(500);
|
||||
InputEmulationManager.ToggleFullScreenPanel(panel.PanelHandle);
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int GetPopoutPanelCountByType(PanelType panelType)
|
||||
{
|
||||
return _panels.FindAll(x => x.PanelType == panelType).Count;
|
||||
}
|
||||
|
||||
private PanelConfig GetCustomPopoutPanelByIndex(int index)
|
||||
{
|
||||
return _panels.Find(x => x.PanelType == PanelType.CustomPopout && x.PanelIndex == index + 1);
|
||||
}
|
||||
|
||||
private void PopoutPanel(int x, int y)
|
||||
{
|
||||
InputEmulationManager.PopOutPanel(x, y);
|
||||
}
|
||||
|
||||
private void SeparatePanel(int index, IntPtr hwnd)
|
||||
{
|
||||
// Resize all windows to 800x600 when separating and shimmy the panel
|
||||
// MSFS draws popout panel differently at different time for same panel
|
||||
WindowManager.MoveWindow(hwnd, PanelType.CustomPopout, -8, 0, 800, 600);
|
||||
PInvoke.SetForegroundWindow(hwnd);
|
||||
Thread.Sleep(500);
|
||||
InputEmulationManager.LeftClick(0, 0);
|
||||
|
||||
// Find the magnifying glass coordinate
|
||||
var point = AnalyzeMergedWindows(hwnd);
|
||||
|
||||
InputEmulationManager.LeftClick(point.X, point.Y);
|
||||
}
|
||||
|
||||
public bool EnumCustomPopoutCallBack(IntPtr hwnd, int lParam)
|
||||
{
|
||||
var panelInfo = GetPanelWindowInfo(hwnd);
|
||||
|
||||
if (panelInfo != null && panelInfo.PanelType == PanelType.CustomPopout)
|
||||
{
|
||||
if (!_panels.Exists(x => x.PanelHandle == hwnd))
|
||||
{
|
||||
Interlocked.Increment(ref _currentPanelIndex);
|
||||
panelInfo.PanelIndex = _currentPanelIndex;
|
||||
_panels.Add(panelInfo);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool EnumBuiltinPopoutCallBack(IntPtr hwnd, int lParam)
|
||||
{
|
||||
var panelInfo = GetPanelWindowInfo(hwnd);
|
||||
|
||||
if (panelInfo != null && panelInfo.PanelType == PanelType.BuiltInPopout)
|
||||
{
|
||||
if (!_panels.Exists(x => x.PanelHandle == hwnd))
|
||||
{
|
||||
Interlocked.Increment(ref _currentPanelIndex);
|
||||
panelInfo.PanelIndex = _currentPanelIndex;
|
||||
_panels.Add(panelInfo);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool EnumMSFSTouchPanelPopoutCallBack(IntPtr hwnd, int index)
|
||||
{
|
||||
var panelInfo = GetPanelWindowInfo(hwnd);
|
||||
|
||||
if (panelInfo != null && panelInfo.PanelType == PanelType.MSFSTouchPanel)
|
||||
{
|
||||
if (!_panels.Exists(x => x.PanelHandle == hwnd))
|
||||
{
|
||||
Interlocked.Increment(ref _currentPanelIndex);
|
||||
panelInfo.PanelIndex = _currentPanelIndex;
|
||||
_panels.Add(panelInfo);
|
||||
|
||||
// Apply always on top to these panels
|
||||
//WindowManager.ApplyAlwaysOnTop(panelInfo.PanelHandle, true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private PanelConfig GetPanelWindowInfo(IntPtr hwnd)
|
||||
{
|
||||
var className = PInvoke.GetClassName(hwnd);
|
||||
|
||||
if (className == "AceApp") // MSFS windows designation
|
||||
{
|
||||
var caption = PInvoke.GetWindowText(hwnd);
|
||||
|
||||
Rectangle rectangle;
|
||||
PInvoke.GetWindowRect(hwnd, out rectangle);
|
||||
|
||||
var panelInfo = new PanelConfig();
|
||||
panelInfo.PanelHandle = hwnd;
|
||||
panelInfo.PanelName = caption;
|
||||
panelInfo.Top = rectangle.Top;
|
||||
panelInfo.Left = rectangle.Left;
|
||||
panelInfo.Width = rectangle.Width;
|
||||
panelInfo.Height = rectangle.Height;
|
||||
|
||||
if (String.IsNullOrEmpty(caption) || caption.IndexOf("Custom") > -1)
|
||||
panelInfo.PanelType = PanelType.CustomPopout;
|
||||
else if (caption.IndexOf("Microsoft Flight Simulator") > -1) // MSFS main game window
|
||||
return null;
|
||||
else if (caption.IndexOf("WINDOW") > -1)
|
||||
panelInfo.PanelType = PanelType.MultiMonitorWindow;
|
||||
else
|
||||
panelInfo.PanelType = PanelType.BuiltInPopout;
|
||||
|
||||
return panelInfo;
|
||||
}
|
||||
else // For MSFS Touch Panel window
|
||||
{
|
||||
var caption = PInvoke.GetWindowText(hwnd);
|
||||
|
||||
var panelInfo = new PanelConfig();
|
||||
panelInfo.PanelHandle = hwnd;
|
||||
panelInfo.PanelName = caption;
|
||||
|
||||
if (caption.IndexOf("Touch Panel |") > -1)
|
||||
{
|
||||
panelInfo.PanelType = PanelType.MSFSTouchPanel;
|
||||
return panelInfo;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Point AnalyzeMergedWindows(IntPtr hwnd)
|
||||
{
|
||||
var sourceImage = ImageOperation.TakeScreenShot(hwnd);
|
||||
|
||||
if (sourceImage == null)
|
||||
return new Point(0, 0);
|
||||
|
||||
Rectangle rectangle;
|
||||
PInvoke.GetClientRect(hwnd, out rectangle);
|
||||
|
||||
var panelMenubarTop = GetPanelMenubarTop(sourceImage, rectangle);
|
||||
if (panelMenubarTop > sourceImage.Height)
|
||||
return Point.Empty;
|
||||
|
||||
var panelMenubarBottom = GetPanelMenubarBottom(sourceImage, rectangle);
|
||||
if (panelMenubarBottom > sourceImage.Height)
|
||||
return Point.Empty;
|
||||
|
||||
var panelsStartingLeft = GetPanelMenubarStartingLeft(sourceImage, rectangle, panelMenubarTop + 5);
|
||||
|
||||
// The center of magnifying glass icon is around (3.2 x height of menubar) to the right of the panel menubar starting left
|
||||
// But need to use higher number here to click the left side of magnifying glass icon because on some panel, the ratio is smaller
|
||||
var menubarHeight = panelMenubarBottom - panelMenubarTop;
|
||||
var magnifyingIconXCoor = panelsStartingLeft - Convert.ToInt32(menubarHeight * 3.2); // ToDo: play around with this multiplier to find the best for all resolutions
|
||||
var magnifyingIconYCoor = panelMenubarTop + Convert.ToInt32(menubarHeight / 2);
|
||||
|
||||
return new Point(magnifyingIconXCoor, magnifyingIconYCoor);
|
||||
}
|
||||
|
||||
private int GetPanelMenubarTop(Bitmap sourceImage, Rectangle rectangle)
|
||||
{
|
||||
// Get a snippet of 1 pixel wide vertical strip of windows. We will choose the strip left of center.
|
||||
// This is to determine when the actual panel's vertical pixel starts in the window. This will allow accurate sizing of the template image
|
||||
var left = Convert.ToInt32((rectangle.Width) * 0.70); // look at around 70% from the left
|
||||
var top = sourceImage.Height - rectangle.Height;
|
||||
|
||||
if (top < 0 || left < 0)
|
||||
return -1;
|
||||
|
||||
unsafe
|
||||
{
|
||||
var stripData = sourceImage.LockBits(new Rectangle(left, top, 1, rectangle.Height), ImageLockMode.ReadWrite, sourceImage.PixelFormat);
|
||||
|
||||
int bytesPerPixel = Bitmap.GetPixelFormatSize(stripData.PixelFormat) / 8;
|
||||
int heightInPixels = stripData.Height;
|
||||
int widthInBytes = stripData.Width * bytesPerPixel;
|
||||
byte* ptrFirstPixel = (byte*)stripData.Scan0;
|
||||
|
||||
for (int y = 0; y < heightInPixels; y++)
|
||||
{
|
||||
byte* currentLine = ptrFirstPixel + (y * stripData.Stride);
|
||||
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
|
||||
{
|
||||
int red = currentLine[x + 2];
|
||||
int green = currentLine[x + 1];
|
||||
int blue = currentLine[x];
|
||||
|
||||
if (red == 255 && green == 255 && blue == 255)
|
||||
{
|
||||
sourceImage.UnlockBits(stripData);
|
||||
return y + top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceImage.UnlockBits(stripData);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int GetPanelMenubarBottom(Bitmap sourceImage, Rectangle rectangle)
|
||||
{
|
||||
// Get a snippet of 1 pixel wide vertical strip of windows. We will choose the strip about 70% from the left of the window
|
||||
var left = Convert.ToInt32((rectangle.Width) * 0.7); // look at around 70% from the left
|
||||
var top = sourceImage.Height - rectangle.Height;
|
||||
|
||||
if (top < 0 || left < 0)
|
||||
return -1;
|
||||
|
||||
unsafe
|
||||
{
|
||||
var stripData = sourceImage.LockBits(new Rectangle(left, top, 1, rectangle.Height), ImageLockMode.ReadWrite, sourceImage.PixelFormat);
|
||||
|
||||
int bytesPerPixel = Bitmap.GetPixelFormatSize(stripData.PixelFormat) / 8;
|
||||
int heightInPixels = stripData.Height;
|
||||
int widthInBytes = stripData.Width * bytesPerPixel;
|
||||
byte* ptrFirstPixel = (byte*)stripData.Scan0;
|
||||
|
||||
int menubarBottom = -1;
|
||||
|
||||
for (int y = 0; y < heightInPixels; y++)
|
||||
{
|
||||
byte* currentLine = ptrFirstPixel + (y * stripData.Stride);
|
||||
for (int x = 0; x < widthInBytes; x = x + bytesPerPixel)
|
||||
{
|
||||
int red = currentLine[x + 2];
|
||||
int green = currentLine[x + 1];
|
||||
int blue = currentLine[x];
|
||||
|
||||
if (red > 250 && green > 250 && blue > 250) // allows the color to be a little off white (ie. Fenix A30 EFB)
|
||||
{
|
||||
// found the top of menu bar
|
||||
menubarBottom = y + top;
|
||||
}
|
||||
else if (menubarBottom > -1) /// it is no longer white in color, we hit menubar bottom
|
||||
{
|
||||
sourceImage.UnlockBits(stripData);
|
||||
return menubarBottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceImage.UnlockBits(stripData);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int GetPanelMenubarStartingLeft(Bitmap sourceImage, Rectangle rectangle, int top)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var stripData = sourceImage.LockBits(new Rectangle(0, top, rectangle.Width, 1), ImageLockMode.ReadWrite, sourceImage.PixelFormat);
|
||||
|
||||
int bytesPerPixel = Bitmap.GetPixelFormatSize(stripData.PixelFormat) / 8;
|
||||
int widthInPixels = stripData.Width;
|
||||
int heightInBytes = stripData.Height * bytesPerPixel;
|
||||
byte* ptrFirstPixel = (byte*)stripData.Scan0;
|
||||
|
||||
for (int x = 0; x < widthInPixels; x++)
|
||||
{
|
||||
byte* currentLine = ptrFirstPixel - (x * bytesPerPixel);
|
||||
for (int y = 0; y < heightInBytes; y = y + bytesPerPixel)
|
||||
{
|
||||
int red = currentLine[y + 2];
|
||||
int green = currentLine[y + 1];
|
||||
int blue = currentLine[y];
|
||||
|
||||
if (red > 250 && green > 250 && blue > 250) // allows the color to be a little off white (ie. Fenix A30 EFB)
|
||||
{
|
||||
sourceImage.UnlockBits(stripData);
|
||||
return sourceImage.Width - x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceImage.UnlockBits(stripData);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
using MSFSPopoutPanelManager.Model;
|
||||
using MSFSPopoutPanelManager.Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Provider
|
||||
{
|
||||
public class PanelSelectionManager
|
||||
{
|
||||
private UserProfileManager _userProfileManager;
|
||||
private int _panelIndex;
|
||||
private List<PanelSourceCoordinate> _panelCoordinates;
|
||||
|
||||
public event EventHandler OnPanelSelectionCompleted;
|
||||
public event EventHandler<EventArgs<PanelSourceCoordinate>> OnPanelLocationAdded;
|
||||
public event EventHandler OnPanelLocationRemoved;
|
||||
public event EventHandler OnAllPanelLocationsRemoved;
|
||||
|
||||
public UserProfile UserProfile { get; set; }
|
||||
|
||||
public AppSetting AppSetting { get; set; }
|
||||
|
||||
public PanelSelectionManager(UserProfileManager userProfileManager)
|
||||
{
|
||||
_userProfileManager = userProfileManager;
|
||||
|
||||
InputHookManager.OnPanelSelectionAdded += HandleOnPanelSelectionAdded;
|
||||
InputHookManager.OnPanelSelectionRemoved += HandleOnPanelSelectionRemoved;
|
||||
InputHookManager.OnPanelSelectionCompleted += HandleOnPanelSelectionCompleted;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_panelIndex = 1;
|
||||
_panelCoordinates = new List<PanelSourceCoordinate>();
|
||||
//ShowPanelLocationOverlay(_panelCoordinates, true);
|
||||
InputHookManager.SubscribeToPanelSelectionEvent = true;
|
||||
InputHookManager.StartHook();
|
||||
}
|
||||
|
||||
public void ShowPanelLocationOverlay(List<PanelSourceCoordinate> panelCoordinates, bool show)
|
||||
{
|
||||
// close all overlays
|
||||
OnAllPanelLocationsRemoved?.Invoke(this, null);
|
||||
|
||||
if (show && panelCoordinates.Count > 0)
|
||||
{
|
||||
foreach (var coor in panelCoordinates)
|
||||
{
|
||||
var panelSourceCoordinate = new PanelSourceCoordinate() { PanelIndex = coor.PanelIndex, X = coor.X, Y = coor.Y };
|
||||
OnPanelLocationAdded?.Invoke(this, new EventArgs<PanelSourceCoordinate>(panelSourceCoordinate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOnPanelSelectionAdded(object sender, System.Drawing.Point e)
|
||||
{
|
||||
var newPanelCoordinates = new PanelSourceCoordinate() { PanelIndex = _panelIndex, X = e.X, Y = e.Y };
|
||||
_panelCoordinates.Add(newPanelCoordinates);
|
||||
_panelIndex++;
|
||||
|
||||
OnPanelLocationAdded?.Invoke(this, new EventArgs<PanelSourceCoordinate>(newPanelCoordinates));
|
||||
}
|
||||
|
||||
private void HandleOnPanelSelectionRemoved(object sender, EventArgs e)
|
||||
{
|
||||
if (_panelCoordinates.Count > 0)
|
||||
{
|
||||
_panelCoordinates.RemoveAt(_panelCoordinates.Count - 1);
|
||||
_panelIndex--;
|
||||
|
||||
OnPanelLocationRemoved?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOnPanelSelectionCompleted(object sender, EventArgs e)
|
||||
{
|
||||
// If enable, save the current viewport into custom view by Ctrl-Alt-0
|
||||
if (AppSetting.UseAutoPanning)
|
||||
{
|
||||
var simualatorProcess = DiagnosticManager.GetSimulatorProcess();
|
||||
if (simualatorProcess != null)
|
||||
{
|
||||
InputEmulationManager.SaveCustomView(simualatorProcess.Handle, AppSetting.AutoPanningKeyBinding);
|
||||
}
|
||||
}
|
||||
|
||||
// Assign and save panel coordinates to active profile
|
||||
UserProfile.PanelSourceCoordinates.Clear();
|
||||
_panelCoordinates.ForEach(c => UserProfile.PanelSourceCoordinates.Add(c));
|
||||
UserProfile.PanelConfigs.Clear();
|
||||
UserProfile.IsLocked = false;
|
||||
|
||||
_userProfileManager.WriteUserProfiles();
|
||||
|
||||
InputHookManager.SubscribeToPanelSelectionEvent = false;
|
||||
OnPanelSelectionCompleted?.Invoke(this, null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
<TargetFramework>net5.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<RootNamespace>MSFSPopoutPanelManager.Provider</RootNamespace>
|
||||
<PackageId>MSFS 2020 Popout Panel Manager Provider</PackageId>
|
||||
<Product>MSFS 2020 Popout Panel Manager Provider</Product>
|
||||
<Version>3.3.7.0</Version>
|
||||
<Authors>Stanley Kwok</Authors>
|
||||
<Copyright>Stanley Kwok 2021</Copyright>
|
||||
<PackageProjectUrl>https://github.com/hawkeye-stan/msfs-popout-panel-manager</PackageProjectUrl>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<Platforms>x64</Platforms>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MouseKeyHook" Version="5.6.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="WindowsInput" Version="6.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FsConnector\FsConnector.csproj" />
|
||||
<ProjectReference Include="..\Model\Model.csproj" />
|
||||
<ProjectReference Include="..\Shared\Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -1,189 +0,0 @@
|
|||
using MSFSPopoutPanelManager.FsConnector;
|
||||
using MSFSPopoutPanelManager.Shared;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Timers;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Provider
|
||||
{
|
||||
public class SimConnectManager
|
||||
{
|
||||
private const int MSFS_DATA_REFRESH_TIMEOUT = 1000;
|
||||
|
||||
private SimConnector _simConnector;
|
||||
private dynamic _simData;
|
||||
|
||||
private System.Timers.Timer _requestDataTimer;
|
||||
private bool _isPowerOnForPopOut;
|
||||
private bool _isTrackIRManaged;
|
||||
|
||||
public event EventHandler OnConnected;
|
||||
public event EventHandler OnDisconnected;
|
||||
public event EventHandler<EventArgs<dynamic>> OnSimConnectDataRefreshed;
|
||||
public event EventHandler OnFlightStarted;
|
||||
public event EventHandler OnFlightStopped;
|
||||
|
||||
public bool IsSimConnectStarted { get; set; }
|
||||
|
||||
public SimConnectManager()
|
||||
{
|
||||
_simConnector = new SimConnector();
|
||||
_simConnector.OnConnected += (sender, e) => { OnConnected?.Invoke(this, null); };
|
||||
_simConnector.OnDisconnected += (sender, e) => { OnDisconnected?.Invoke(this, null); };
|
||||
_simConnector.OnReceivedData += HandleDataReceived;
|
||||
_simConnector.OnReceiveSystemEvent += HandleReceiveSystemEvent;
|
||||
_simConnector.OnConnected += (sender, e) =>
|
||||
{
|
||||
_requestDataTimer = new System.Timers.Timer();
|
||||
_requestDataTimer.Interval = MSFS_DATA_REFRESH_TIMEOUT;
|
||||
_requestDataTimer.Enabled = true;
|
||||
_requestDataTimer.Elapsed += HandleDataRequested;
|
||||
_requestDataTimer.Elapsed += HandleMessageReceived;
|
||||
};
|
||||
|
||||
_simConnector.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_simConnector.Stop();
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
_simConnector.StopAndReconnect();
|
||||
}
|
||||
|
||||
public void TurnOnPower(bool isRequiredForColdStart)
|
||||
{
|
||||
// Wait for _simData.AtcOnParkingSpot to refresh
|
||||
Thread.Sleep(MSFS_DATA_REFRESH_TIMEOUT + 250);
|
||||
|
||||
if (isRequiredForColdStart && _simData != null && (_simData.AtcOnParkingSpot || !_simData.ElectricalMasterBattery))
|
||||
{
|
||||
_isPowerOnForPopOut = true;
|
||||
_simConnector.TransmitActionEvent(ActionEvent.KEY_MASTER_BATTERY_SET, 1);
|
||||
Thread.Sleep(100);
|
||||
_simConnector.TransmitActionEvent(ActionEvent.KEY_TOGGLE_AVIONICS_MASTER, 1);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
public void TurnOffpower()
|
||||
{
|
||||
if (_isPowerOnForPopOut)
|
||||
{
|
||||
_simConnector.TransmitActionEvent(ActionEvent.KEY_TOGGLE_AVIONICS_MASTER, 1);
|
||||
Thread.Sleep(100);
|
||||
_simConnector.TransmitActionEvent(ActionEvent.KEY_MASTER_BATTERY_SET, 0);
|
||||
Thread.Sleep(100);
|
||||
|
||||
_isPowerOnForPopOut = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void TurnOffTrackIR()
|
||||
{
|
||||
Debug.Write("TrackIR OFF............" + Environment.NewLine);
|
||||
if (_simData != null && _simData.TrackIREnable)
|
||||
{
|
||||
SetTrackIREnable(false);
|
||||
_isTrackIRManaged = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void TurnOnTrackIR()
|
||||
{
|
||||
Debug.Write("TrackIR ON............" + Environment.NewLine);
|
||||
if (_isTrackIRManaged && _simData != null && !_simData.TrackIREnable)
|
||||
{
|
||||
SetTrackIREnable(true);
|
||||
_isTrackIRManaged = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetTrackIREnable(bool enable)
|
||||
{
|
||||
// Wait for _simData.ElectricalMasterBattery to refresh
|
||||
Thread.Sleep(MSFS_DATA_REFRESH_TIMEOUT + 250);
|
||||
|
||||
// It is prop3 in SimConnectStruct (by DataDefinitions.cs)
|
||||
SimConnectStruct simConnectStruct = new SimConnectStruct();
|
||||
|
||||
simConnectStruct.Prop01 = _simData.Title; // must set "Title" for TrackIR variable to write correctly
|
||||
simConnectStruct.Prop02 = _simData.ElectricalMasterBattery ? Convert.ToDouble(1) : Convert.ToDouble(0); // must set "ElectricalMasterBattery" for TrackIR variable to write correctly
|
||||
simConnectStruct.Prop03 = enable ? Convert.ToDouble(1) : Convert.ToDouble(0); // this is the TrackIR variable
|
||||
simConnectStruct.Prop04 = _simData.AtcOnParkingSpot ? Convert.ToDouble(1) : Convert.ToDouble(0); // must set "AtcOnParkingSpot" for TrackIR variable to write correctly
|
||||
_simConnector.SetDataObject(simConnectStruct);
|
||||
}
|
||||
|
||||
private void HandleDataRequested(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_simConnector.RequestData();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void HandleMessageReceived(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_simConnector.ReceiveMessage();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public void HandleDataReceived(object sender, EventArgs<dynamic> e)
|
||||
{
|
||||
_simData = e.Value;
|
||||
OnSimConnectDataRefreshed?.Invoke(this, new EventArgs<dynamic>(e.Value));
|
||||
}
|
||||
|
||||
private List<SimConnectSystemEvent> _systemEventBuffer;
|
||||
private List<SimConnectSystemEvent> FlightRestartBeginBufferDef = new List<SimConnectSystemEvent>() { SimConnectSystemEvent.SIMSTOP, SimConnectSystemEvent.VIEW };
|
||||
private List<SimConnectSystemEvent> FlightRestartEndBufferDef = new List<SimConnectSystemEvent>() { SimConnectSystemEvent.SIMSTART, SimConnectSystemEvent.VIEW };
|
||||
private List<SimConnectSystemEvent> FlightStartBufferDef = new List<SimConnectSystemEvent>() { SimConnectSystemEvent.SIMSTOP, SimConnectSystemEvent.SIMSTART, SimConnectSystemEvent.SIMSTOP, SimConnectSystemEvent.SIMSTART, SimConnectSystemEvent.VIEW };
|
||||
private List<SimConnectSystemEvent> FlightEndBufferDef = new List<SimConnectSystemEvent>() { SimConnectSystemEvent.SIMSTOP, SimConnectSystemEvent.SIMSTART, SimConnectSystemEvent.VIEW, SimConnectSystemEvent.SIMSTOP, SimConnectSystemEvent.SIMSTART };
|
||||
private bool _systemEventInFlightRestartSequence;
|
||||
|
||||
private void HandleReceiveSystemEvent(object sender, EventArgs<SimConnectSystemEvent> e)
|
||||
{
|
||||
if (_systemEventBuffer == null)
|
||||
_systemEventBuffer = new List<SimConnectSystemEvent>();
|
||||
|
||||
var systemEvent = e.Value;
|
||||
|
||||
_systemEventBuffer.Add(systemEvent);
|
||||
|
||||
Debug.WriteLine($"SimConnectSystemEvent Received: {systemEvent}");
|
||||
|
||||
if (_systemEventBuffer.TakeLast(2).SequenceEqual(FlightRestartBeginBufferDef))
|
||||
{
|
||||
OnFlightStopped?.Invoke(this, null);
|
||||
_systemEventBuffer = null;
|
||||
_systemEventInFlightRestartSequence = true;
|
||||
}
|
||||
else if (_systemEventInFlightRestartSequence && _systemEventBuffer.TakeLast(2).SequenceEqual(FlightRestartEndBufferDef))
|
||||
{
|
||||
OnFlightStarted?.Invoke(this, null);
|
||||
_systemEventBuffer = null;
|
||||
_systemEventInFlightRestartSequence = false;
|
||||
}
|
||||
else if (_systemEventBuffer.TakeLast(5).SequenceEqual(FlightStartBufferDef))
|
||||
{
|
||||
OnFlightStarted?.Invoke(this, null);
|
||||
_systemEventBuffer = null;
|
||||
}
|
||||
else if (_systemEventBuffer.TakeLast(5).SequenceEqual(FlightEndBufferDef))
|
||||
{
|
||||
OnFlightStopped?.Invoke(this, null);
|
||||
_systemEventBuffer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
using MSFSPopoutPanelManager.Model;
|
||||
using MSFSPopoutPanelManager.Shared;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Provider
|
||||
{
|
||||
public class UserProfileManager
|
||||
{
|
||||
private const string USER_PROFILE_DATA_FILENAME = "userprofiledata.json";
|
||||
|
||||
public ObservableCollection<UserProfile> UserProfiles { get; set; }
|
||||
|
||||
public int AddUserProfile(string newProfileName)
|
||||
{
|
||||
return AddProfile(new UserProfile(), newProfileName);
|
||||
}
|
||||
|
||||
public int AddUserProfileByCopyingProfile(string newProfileName, int copyProfileId)
|
||||
{
|
||||
if (UserProfiles == null)
|
||||
throw new Exception("User Profiles is null.");
|
||||
|
||||
var matchedProfile = UserProfiles.FirstOrDefault(p => p.ProfileId == copyProfileId);
|
||||
|
||||
var copiedProfile = matchedProfile.Copy<UserProfile>(); // Using Shared/ObjectExtensions.cs extension method
|
||||
copiedProfile.BindingAircraftLiveries = new ObservableCollection<string>();
|
||||
|
||||
return AddProfile(copiedProfile, newProfileName);
|
||||
}
|
||||
|
||||
public bool DeleteUserProfile(int profileId)
|
||||
{
|
||||
if (UserProfiles == null)
|
||||
throw new Exception("User Profiles is null.");
|
||||
|
||||
if (profileId == -1)
|
||||
return false;
|
||||
|
||||
var profileToRemove = UserProfiles.First(x => x.ProfileId == profileId);
|
||||
UserProfiles.Remove(profileToRemove);
|
||||
WriteUserProfiles();
|
||||
|
||||
Logger.LogStatus($"Profile '{profileToRemove.ProfileName}' has been deleted successfully.", StatusMessageType.Info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddProfileBinding(string planeTitle, int activeProfileId)
|
||||
{
|
||||
var boundProfile = UserProfiles.FirstOrDefault(p => p.BindingAircraftLiveries.ToList().Exists(p => p == planeTitle));
|
||||
if (boundProfile != null)
|
||||
{
|
||||
Logger.LogStatus($"Unable to add binding to the profile because '{planeTitle}' was already bound to profile '{boundProfile.ProfileName}'.", StatusMessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
UserProfiles.First(p => p.ProfileId == activeProfileId).BindingAircraftLiveries.Add(planeTitle);
|
||||
WriteUserProfiles();
|
||||
|
||||
Logger.LogStatus($"Binding for the profile has been added successfully.", StatusMessageType.Info);
|
||||
}
|
||||
|
||||
public void DeleteProfileBinding(string planeTitle, int activeProfileId)
|
||||
{
|
||||
UserProfiles.First(p => p.ProfileId == activeProfileId).BindingAircraftLiveries.Remove(planeTitle);
|
||||
WriteUserProfiles();
|
||||
Logger.LogStatus($"Binding for the profile has been deleted successfully.", StatusMessageType.Info);
|
||||
}
|
||||
|
||||
public void ReadUserProfiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
using (StreamReader reader = new StreamReader(Path.Combine(FileIo.GetUserDataFilePath(), USER_PROFILE_DATA_FILENAME)))
|
||||
{
|
||||
UserProfiles = new ObservableCollection<UserProfile>(JsonConvert.DeserializeObject<List<UserProfile>>(reader.ReadToEnd()));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
UserProfiles = new ObservableCollection<UserProfile>(new List<UserProfile>());
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteUserProfiles()
|
||||
{
|
||||
if (UserProfiles == null)
|
||||
throw new Exception("User Profiles is null.");
|
||||
|
||||
try
|
||||
{
|
||||
var userProfilePath = FileIo.GetUserDataFilePath();
|
||||
|
||||
if (!Directory.Exists(userProfilePath))
|
||||
Directory.CreateDirectory(userProfilePath);
|
||||
|
||||
using (StreamWriter file = File.CreateText(Path.Combine(userProfilePath, USER_PROFILE_DATA_FILENAME)))
|
||||
{
|
||||
JsonSerializer serializer = new JsonSerializer();
|
||||
serializer.Serialize(file, UserProfiles);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.LogStatus($"Unable to write user data file: {USER_PROFILE_DATA_FILENAME}", StatusMessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private int AddProfile(UserProfile userProfile, string newProfileName)
|
||||
{
|
||||
if (UserProfiles == null)
|
||||
throw new Exception("User Profiles is null.");
|
||||
|
||||
var newPlaneProfile = userProfile;
|
||||
var newProfileId = UserProfiles.Count > 0 ? UserProfiles.Max(x => x.ProfileId) + 1 : 1;
|
||||
|
||||
newPlaneProfile.ProfileName = newProfileName;
|
||||
newPlaneProfile.ProfileId = newProfileId;
|
||||
|
||||
var tmpList = UserProfiles.ToList();
|
||||
tmpList.Add(newPlaneProfile);
|
||||
var index = tmpList.OrderBy(x => x.ProfileName).ToList().FindIndex(x => x.ProfileId == newProfileId);
|
||||
UserProfiles.Insert(index, newPlaneProfile);
|
||||
WriteUserProfiles();
|
||||
|
||||
Logger.LogStatus($"Profile '{newPlaneProfile.ProfileName}' has been added successfully.", StatusMessageType.Info);
|
||||
|
||||
return newProfileId;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
using MSFSPopoutPanelManager.Model;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Provider
|
||||
{
|
||||
public class WindowManager
|
||||
{
|
||||
public static void ApplyHidePanelTitleBar(IntPtr handle, bool hideTitleBar)
|
||||
{
|
||||
var currentStyle = PInvoke.GetWindowLong(handle, PInvokeConstant.GWL_STYLE).ToInt64();
|
||||
|
||||
if (hideTitleBar)
|
||||
PInvoke.SetWindowLong(handle, PInvokeConstant.GWL_STYLE, (uint)(currentStyle & ~(PInvokeConstant.WS_CAPTION | PInvokeConstant.WS_SIZEBOX)));
|
||||
else
|
||||
PInvoke.SetWindowLong(handle, PInvokeConstant.GWL_STYLE, (uint)(currentStyle | (PInvokeConstant.WS_CAPTION | PInvokeConstant.WS_SIZEBOX)));
|
||||
}
|
||||
|
||||
public static void ApplyAlwaysOnTop(IntPtr handle, PanelType panelType, bool alwaysOnTop, Rectangle panelRectangle)
|
||||
{
|
||||
// Override weird size adjustment for Touch Panel WPF window
|
||||
int newWidth = panelType == PanelType.MSFSTouchPanel ? panelRectangle.Width - 16 : panelRectangle.Width;
|
||||
int newHeight = panelType == PanelType.MSFSTouchPanel ? panelRectangle.Height - 39 : panelRectangle.Height;
|
||||
|
||||
if (alwaysOnTop)
|
||||
PInvoke.SetWindowPos(handle, new IntPtr(PInvokeConstant.HWND_TOPMOST), panelRectangle.Left, panelRectangle.Top, newWidth, newHeight, PInvokeConstant.SWP_ALWAYS_ON_TOP);
|
||||
else
|
||||
PInvoke.SetWindowPos(handle, new IntPtr(PInvokeConstant.HWND_NOTOPMOST), panelRectangle.Left, panelRectangle.Top, newWidth, newHeight, 0);
|
||||
}
|
||||
|
||||
public static void ApplyAlwaysOnTop(IntPtr handle, bool alwaysOnTop)
|
||||
{
|
||||
Rectangle rect;
|
||||
PInvoke.GetWindowRect(handle, out rect);
|
||||
|
||||
Rectangle clientRectangle;
|
||||
PInvoke.GetClientRect(handle, out clientRectangle);
|
||||
|
||||
ApplyAlwaysOnTop(handle, PanelType.WPFWindow, alwaysOnTop, new Rectangle(rect.X, rect.Y, clientRectangle.Width, clientRectangle.Height));
|
||||
}
|
||||
|
||||
public static void CloseWindow(IntPtr handle)
|
||||
{
|
||||
PInvoke.SendMessage(handle, PInvokeConstant.WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
public static void MoveWindow(IntPtr handle, int x, int y)
|
||||
{
|
||||
Rectangle rectangle;
|
||||
PInvoke.GetClientRect(handle, out rectangle);
|
||||
PInvoke.MoveWindow(handle, x, y, rectangle.Width, rectangle.Height, true);
|
||||
}
|
||||
|
||||
public static void MoveWindow(IntPtr handle, PanelType panelType, int x, int y, int width, int height)
|
||||
{
|
||||
// Override weird size adjustment for Touch Panel WPF window
|
||||
int newWidth = panelType == PanelType.MSFSTouchPanel ? width - 16 : width;
|
||||
int newHeight = panelType == PanelType.MSFSTouchPanel ? height - 39 : height;
|
||||
|
||||
PInvoke.MoveWindow(handle, x, y, newWidth, newHeight, true);
|
||||
}
|
||||
|
||||
public static void MoveWindowWithMsfsBugOverrirde(IntPtr handle, PanelType panelType, int x, int y, int width, int height)
|
||||
{
|
||||
int originalX = x;
|
||||
|
||||
// Override weird size adjustment for Touch Panel WPF window
|
||||
int newWidth = panelType == PanelType.MSFSTouchPanel ? width - 16 : width;
|
||||
int newHeight = panelType == PanelType.MSFSTouchPanel ? height - 39 : height;
|
||||
|
||||
PInvoke.MoveWindow(handle, x, y, newWidth, newHeight, true);
|
||||
|
||||
// Fixed MSFS bug, create workaround where on 2nd or later instance of width adjustment, the panel shift to the left by itself
|
||||
// Wait for system to catch up on panel coordinate that were just applied
|
||||
System.Threading.Thread.Sleep(200);
|
||||
|
||||
Rectangle rectangle;
|
||||
PInvoke.GetWindowRect(handle, out rectangle);
|
||||
|
||||
if (rectangle.Left != originalX)
|
||||
PInvoke.MoveWindow(handle, originalX, y, newWidth, newHeight, false);
|
||||
}
|
||||
|
||||
public static void MinimizeWindow(IntPtr handle)
|
||||
{
|
||||
PInvoke.ShowWindow(handle, PInvokeConstant.SW_MINIMIZE);
|
||||
}
|
||||
|
||||
public static void BringWindowToForeground(IntPtr handle)
|
||||
{
|
||||
PInvoke.ShowWindowAsync(new HandleRef(null, handle), PInvokeConstant.SW_RESTORE);
|
||||
PInvoke.SetForegroundWindow(handle);
|
||||
}
|
||||
|
||||
public static void CloseAllCustomPopoutPanels()
|
||||
{
|
||||
PInvoke.EnumWindows(new PInvoke.CallBack(EnumAllCustomPopoutPanels), 1);
|
||||
}
|
||||
|
||||
public static void MinimizeAllPopoutPanels(bool active)
|
||||
{
|
||||
if (active)
|
||||
{
|
||||
PInvoke.EnumWindows(new PInvoke.CallBack(EnumToMinimizePopoutPanels), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
PInvoke.EnumWindows(new PInvoke.CallBack(EnumToMinimizePopoutPanels), 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static IntPtr FindWindowByCaption(string caption)
|
||||
{
|
||||
return PInvoke.FindWindowByCaption(IntPtr.Zero, caption);
|
||||
}
|
||||
|
||||
private static bool EnumToMinimizePopoutPanels(IntPtr hwnd, int index)
|
||||
{
|
||||
var className = PInvoke.GetClassName(hwnd);
|
||||
var caption = PInvoke.GetWindowText(hwnd);
|
||||
|
||||
if (className == "AceApp" && caption.IndexOf("Microsoft Flight Simulator") == -1) // MSFS windows designation
|
||||
{
|
||||
if (index == 0)
|
||||
PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_MINIMIZE);
|
||||
else
|
||||
PInvoke.ShowWindow(hwnd, PInvokeConstant.SW_RESTORE);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool EnumAllCustomPopoutPanels(IntPtr hwnd, int index)
|
||||
{
|
||||
var className = PInvoke.GetClassName(hwnd);
|
||||
var caption = PInvoke.GetWindowText(hwnd);
|
||||
|
||||
if (className == "AceApp" && caption.IndexOf("WINDOW") > -1) // For multi monitor window, do nothing
|
||||
return true;
|
||||
|
||||
if (className == "AceApp" && (caption.IndexOf("(Custom)") > -1 || caption == String.Empty)) // Only close non-builtin pop out panels
|
||||
{
|
||||
WindowManager.CloseWindow(hwnd);
|
||||
}
|
||||
else if (className == "AceApp" && caption.IndexOf("Microsoft Flight Simulator") == -1) // For builtin pop out (ATC, VFR Map, ect)
|
||||
{
|
||||
WindowManager.MoveWindow(hwnd, 0, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace MSFSPopoutPanelManager.Provider
|
||||
{
|
||||
public class WindowProcess
|
||||
{
|
||||
public int ProcessId { get; set; }
|
||||
|
||||
public string ProcessName { get; set; }
|
||||
|
||||
public IntPtr Handle { get; set; }
|
||||
}
|
||||
}
|
20
README.md
|
@ -1,7 +1,7 @@
|
|||
# MSFS Pop Out Panel Manager
|
||||
|
||||
<p align="center">
|
||||
<img src="images/doc/panel_selection.png" width="600" hspace="10"/>
|
||||
<img src="assets/readme/images/panel_selection.png" width="600" hspace="10"/>
|
||||
</p>
|
||||
|
||||
MSFS Pop Out Panel Manager is an application for MSFS 2020 which helps pop out, save and position pop out panels to be used by utilities such as Sim Innovations Air Manager or to place pop out panels onto your screen or another monitor at predetermined locations automatically.
|
||||
|
@ -45,7 +45,7 @@ What if you can do the setup once by defining on screen where the pop out panels
|
|||
2. Start the application **MSFSPopoutPanelManager.exe** and it will automatically connect when MSFS/SimConnect starts. You maybe prompt to download .NET framework 5.0 x64 desktop runtime. Please see the screenshot below to download and install x64 desktop version of the framework.
|
||||
|
||||
<p align="center">
|
||||
<img src="images/doc/framework_download.png" width="1000" hspace="10"/>
|
||||
<img src="assets/readme/images/framework_download.png" width="1000" hspace="10"/>
|
||||
</p>
|
||||
|
||||
## How to Update
|
||||
|
@ -67,19 +67,19 @@ What if you can do the setup once by defining on screen where the pop out panels
|
|||
1. First start the application and the game. Once you're in the main menu of the game, you can create a new profile by clicking the "plus" button in step 1 of the app.
|
||||
|
||||
<p align="center">
|
||||
<img src="images/doc/add_profile.png" width="900" hspace="10"/>
|
||||
<img src="assets/readme/images/add_profile.png" width="900" hspace="10"/>
|
||||
</p>
|
||||
|
||||
2. For step 2, if you want to associate the profile to the current aircraft livery to use in [Auto Pop Out](#auto-pop-out-feature) feature or for automatic profile switching when selecting a different aircraft, click the "plus" button next to the aircraft livery name. The aircraft livery title will become green once the livery is bound to the profile. Your chosen livery may not be available to select in the application for your newly selected plane until a flight is started.
|
||||
|
||||
<p align="center">
|
||||
<img src="images/doc/bind_profile_to_livery.png" width="900" hspace="10"/>
|
||||
<img src="assets/readme/images/bind_profile_to_livery.png" width="900" hspace="10"/>
|
||||
</p>
|
||||
|
||||
3. Now start a flight with your chosen aircraft. Once your flight is started, you're ready to select the panels you want to pop out. Please click "Start Panel Selection" to define where the pop out panels will be using **LEFT CLICK**. Use **CTRL-LEFT CLICK** when selection is completed. You can also move the number circles at this point to do final adjustment.
|
||||
|
||||
<p align="center">
|
||||
<img src="images/doc/after_panel_selection.png" width="900" hspace="10"/>
|
||||
<img src="assets/readme/images/after_panel_selection.png" width="900" hspace="10"/>
|
||||
</p>
|
||||
|
||||
4. Next, click "Start Pop Out". At this point, please be patient. The application will start popping out and separating panels one by one and you will see a lot of movements on screen. If something goes wrong, just follow the instruction in the status message and try again. Once the process is done, you will see a list of panels line up in the upper left corner of the screen. All the panels are given a default name. You can name them anything you want as needed.
|
||||
|
@ -87,7 +87,7 @@ What if you can do the setup once by defining on screen where the pop out panels
|
|||
5. You can now start panel configuration by dragging the pop out panels into their final position (to your main monitor or another monitor). You can also type value directly into the data grid to move and resize a panel. The +/- pixel buttons by the lower left corner of the grid allow you to change panel position at the chosen increment/decrement by selecting the data grid cell first (X-Pos, Y-Pos, Width, Height). You can also check "Always on Top", "Hide Title Bar", or "Full Screen Mode" if desire. If the panel is touch capable, you can check "Touch Enabled". Please see [Touch Enable Pop Out Feature](#touch-enable-pop-out-feature) regarding this experimental feature. Once all the panels are at their final position, just click "Lock Panel" to prevent further panel changes.
|
||||
|
||||
<p align="center">
|
||||
<img src="images/doc/panel_configuration.png" width="900" hspace="10"/>
|
||||
<img src="assets/readme/images/panel_configuration.png" width="900" hspace="10"/>
|
||||
</p>
|
||||
|
||||
6. To test if everything is working, please click "Restart" in the File menu. This will close all pop outs. Now click "Start Pop Out" and see the magic happens!
|
||||
|
@ -95,11 +95,11 @@ What if you can do the setup once by defining on screen where the pop out panels
|
|||
7. With auto panning feature enabled in preferences setting, you do not have to line up the circles that identified the panels in order for the panels to be popped out. But if you would like to do it manually without auto-panning, on next start of the flight, just line up the panels before clicking "Start Pop Out" if needed.
|
||||
|
||||
<p align="center">
|
||||
<img src="images/doc/autopanning_1.png" width="900" hspace="10"/>
|
||||
<img src="assets/readme/images/autopanning_1.png" width="900" hspace="10"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="images/doc/autopanning_2.png" width="900" hspace="10"/>
|
||||
<img src="assets/readme/images/autopanning_2.png" width="900" hspace="10"/>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
@ -148,13 +148,13 @@ In MSFS, when operating the above panels with pop outs, there are currently 2 li
|
|||
- Bug - If the pop out panel is also display on the main screen, a click through at incorrect coordinate will occur at the relative position where the pop out panel is located. If you click at this particular location in the pop out panel, the click event will register at the wrong coordinate. I haven’t been able to figure out how to work around this issue yet since the bug is deep in the closed source CoherentGT code in how MSFS implements the internal browser control to display to pop out panel. So touch will not work in the relative position of the pop out panel where panel appears on the main screen. This only affects instrumentation pop outs. The built-in ones such as ATC and checklist are fine since once they’re popped out, they no longer appear on the main screen. Below is the screenshot where click through at incorrect coordinate occurs. See the relative position (red box) in the pop out where the same instrumentation appears on the main screen.
|
||||
|
||||
<p align="center">
|
||||
<img src="images/doc/touch_support_bug.png" width="900" hspace="10"/>
|
||||
<img src="assets/readme/images/touch_support_bug.png" width="900" hspace="10"/>
|
||||
</p>
|
||||
|
||||
If you're a home cockpit builder and your main screen has a view that looks like something below, than touch enable feature will work almost 100% of the time since there is no click through target on your main screen.
|
||||
|
||||
<p align="center">
|
||||
<img src="images/doc/touch_support_bug_2.png" width="900" hspace="10"/>
|
||||
<img src="assets/readme/images/touch_support_bug_2.png" width="900" hspace="10"/>
|
||||
</p>
|
||||
|
||||
#### How to enable touch support
|
||||
|
|
2
ReactClient/.env
Normal file
|
@ -0,0 +1,2 @@
|
|||
BROWSER=none
|
||||
PORT=30101
|
23
ReactClient/.gitignore
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
34128
ReactClient/package-lock.json
generated
Normal file
51
ReactClient/package.json
Normal file
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"name": "pwa",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@elfalem/leaflet-curve": "^0.8.2",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@mui/icons-material": "^5.0.1",
|
||||
"@mui/material": "^5.0.1",
|
||||
"@mui/styles": "^5.0.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^11.2.7",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"console-feed": "^3.2.2",
|
||||
"geolib": "^3.3.1",
|
||||
"json-ref-lite": "^1.1.0",
|
||||
"leaflet": "^1.7.1",
|
||||
"leaflet-easybutton": "^2.4.0",
|
||||
"leaflet-marker-rotation": "^0.4.0",
|
||||
"leaflet.marker.slideto": "^0.2.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dial-knob": "^1.3.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-leaflet": "^3.2.1",
|
||||
"react-leaflet-bing-v2": "^5.2.3",
|
||||
"react-lineto": "^3.2.1",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-scripts": "4.0.3",
|
||||
"rimraf": "^2.7.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "rimraf ./build && react-scripts start",
|
||||
"build": "rimraf ./build && react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"serve": "http-server build -p 27010",
|
||||
"buildserve": "rimraf ./build && react-scripts build && http-server build -p 27010"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
342
ReactClient/public/config/PlanePanelProfile.json
Normal file
|
@ -0,0 +1,342 @@
|
|||
[
|
||||
{
|
||||
"planeId": "kodiak",
|
||||
"name": "Kodiak 100",
|
||||
"panels": [
|
||||
{
|
||||
"panelId": "kodiak",
|
||||
"name": "Kodiak Full Instrumentation",
|
||||
"rootPath": "kodiak",
|
||||
"panelSize": {
|
||||
"width": 2458,
|
||||
"height": 1310
|
||||
},
|
||||
"showMenuBar": true,
|
||||
"enableMap": true,
|
||||
"subPanels": [
|
||||
{
|
||||
"panelId": "pfd-mfd",
|
||||
"name": "PFD MFD",
|
||||
"definitions": "KODIAK_PFD_MFD_DEF",
|
||||
"rootPath": "pfdmfd",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"scale": 1
|
||||
},
|
||||
{
|
||||
"panelId": "electrical",
|
||||
"name": "Electrical",
|
||||
"definitions": "KODIAK_ELECTRICAL_DEF",
|
||||
"rootPath": "electrical",
|
||||
"left": 0,
|
||||
"top": 915,
|
||||
"scale": 1
|
||||
},
|
||||
{
|
||||
"panelId": "lights",
|
||||
"name": "Lights",
|
||||
"definitions": "KODIAK_LIGHTS_DEF",
|
||||
"rootPath": "lights",
|
||||
"left": 830,
|
||||
"top": 915,
|
||||
"scale": 1
|
||||
},
|
||||
{
|
||||
"panelId": "fuel",
|
||||
"name": "Fuel",
|
||||
"definitions": "KODIAK_FUEL_DEF",
|
||||
"rootPath": "fuel",
|
||||
"left": 1770,
|
||||
"top": 980,
|
||||
"scale": 1
|
||||
},
|
||||
{
|
||||
"panelId": "oxygen",
|
||||
"name": "Oxygen",
|
||||
"definitions": "KODIAK_OXYGEN_DEF",
|
||||
"rootPath": "oxygen",
|
||||
"left": 2230,
|
||||
"top": 985,
|
||||
"scale": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"panelId": "kodiak-pfd",
|
||||
"name": "Kodiak PFD",
|
||||
"rootPath": "kodiak",
|
||||
"panelSize": {
|
||||
"width": 1408,
|
||||
"height": 914
|
||||
},
|
||||
"showMenuBar": false,
|
||||
"enableMap": false,
|
||||
"subPanels": [
|
||||
{
|
||||
"panelId": "pfd",
|
||||
"name": "PFD",
|
||||
"definitions": "KODIAK_PFD_ONLY_DEF",
|
||||
"rootPath": "pfdmfd",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"scale": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"panelId": "kodiak-mfd",
|
||||
"name": "Kodiak MFD",
|
||||
"rootPath": "kodiak",
|
||||
"panelSize": {
|
||||
"width": 1408,
|
||||
"height": 914
|
||||
},
|
||||
"showMenuBar": false,
|
||||
"enableMap": false,
|
||||
"subPanels": [
|
||||
{
|
||||
"panelId": "mfd",
|
||||
"name": "MFD",
|
||||
"definitions": "KODIAK_MFD_ONLY_DEF",
|
||||
"rootPath": "pfdmfd",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"scale": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"panelId": "kodiak-electrical",
|
||||
"name": "Kodiak Electrical",
|
||||
"rootPath": "kodiak",
|
||||
"panelSize": {
|
||||
"width": 812,
|
||||
"height": 377
|
||||
},
|
||||
"showMenuBar": false,
|
||||
"enableMap": false,
|
||||
"subPanels": [
|
||||
{
|
||||
"panelId": "electrical",
|
||||
"name": "Electrical",
|
||||
"definitions": "KODIAK_ELECTRICAL_DEF",
|
||||
"rootPath": "electrical",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"scale": 1
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"panelId": "kodiak-lights",
|
||||
"name": "Kodiak Lights",
|
||||
"rootPath": "kodiak",
|
||||
"panelSize": {
|
||||
"width": 929,
|
||||
"height": 380
|
||||
},
|
||||
"showMenuBar": false,
|
||||
"enableMap": false,
|
||||
"subPanels": [
|
||||
{
|
||||
"panelId": "lights",
|
||||
"name": "Lights",
|
||||
"definitions": "KODIAK_LIGHTS_DEF",
|
||||
"rootPath": "lights",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"scale": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"panelId": "kodiak-fuel",
|
||||
"name": "Kodiak Fuel Switch",
|
||||
"rootPath": "kodiak",
|
||||
"panelSize": {
|
||||
"width": 440,
|
||||
"height": 226
|
||||
},
|
||||
"showMenuBar": false,
|
||||
"enableMap": false,
|
||||
"subPanels": [
|
||||
{
|
||||
"panelId": "fuel",
|
||||
"name": "Fuel",
|
||||
"definitions": "KODIAK_FUEL_DEF",
|
||||
"rootPath": "fuel",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"scale": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"panelId": "kodiak-oxygen",
|
||||
"name": "Kodiak Oxygen Switch",
|
||||
"rootPath": "kodiak",
|
||||
"panelSize": {
|
||||
"width": 215,
|
||||
"height": 215
|
||||
},
|
||||
"showMenuBar": false,
|
||||
"enableMap": false,
|
||||
"subPanels": [
|
||||
{
|
||||
"panelId": "oxygen",
|
||||
"name": "Oxygen",
|
||||
"definitions": "KODIAK_OXYGEN_DEF",
|
||||
"rootPath": "oxygen",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"scale": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"planeId": "pmdg-737-700",
|
||||
"name": "PMDG 737-700",
|
||||
"panels": [
|
||||
{
|
||||
"panelId": "pmdg-737-700-full-instrumentation",
|
||||
"name": "PMDG 737 Full Instrumentation",
|
||||
"rootPath": "pmdg-737-700",
|
||||
"panelSize": {
|
||||
"width": 1920,
|
||||
"height": 1010
|
||||
},
|
||||
"showMenuBar": true,
|
||||
"enableMap": true,
|
||||
"subPanels": [
|
||||
{
|
||||
"panelId": "mcp",
|
||||
"name": "MCP",
|
||||
"rootPath": "mcp",
|
||||
"definitions": "PMDG_737_700_MCP_DEF",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"scale": 1
|
||||
},
|
||||
{
|
||||
"panelId": "efis-cpt",
|
||||
"name": "EFIS-CPT",
|
||||
"rootPath": "efis-cpt",
|
||||
"definitions": "PMDG_737_700_EFIS_CPT_DEF",
|
||||
"left": 90,
|
||||
"top": 335,
|
||||
"scale": 1
|
||||
},
|
||||
{
|
||||
"panelId": "xpndr",
|
||||
"name": "XPNDR",
|
||||
"rootPath": "xpndr",
|
||||
"definitions": "PMDG_737_700_XPNDR_DEF",
|
||||
"left": 210,
|
||||
"top": 735,
|
||||
"scale": 1
|
||||
},
|
||||
{
|
||||
"panelId": "radio",
|
||||
"name": "Radio",
|
||||
"rootPath": "radio",
|
||||
"definitions": "PMDG_737_700_RADIO_DEF",
|
||||
"left": 850,
|
||||
"top": 335,
|
||||
"scale": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"panelId": "pmdg-737-700-mcp",
|
||||
"name": "PMDG 737 MCP",
|
||||
"rootPath": "pmdg-737-700",
|
||||
"panelSize": {
|
||||
"width": 1920,
|
||||
"height": 329
|
||||
},
|
||||
"showMenuBar": true,
|
||||
"enableMap": false,
|
||||
"subPanels": [
|
||||
{
|
||||
"panelId": "mcp",
|
||||
"name": "MCP",
|
||||
"rootPath": "mcp",
|
||||
"definitions": "PMDG_737_700_MCP_DEF",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"scale": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"panelId": "pmdg-737-700-radio",
|
||||
"name": "PMDG 737 Radio",
|
||||
"rootPath": "pmdg-737-700",
|
||||
"panelSize": {
|
||||
"width": 995,
|
||||
"height": 485
|
||||
},
|
||||
"showMenuBar": false,
|
||||
"enableMap": false,
|
||||
"subPanels": [
|
||||
{
|
||||
"panelId": "radio",
|
||||
"name": "Radio",
|
||||
"rootPath": "radio",
|
||||
"definitions": "PMDG_737_700_RADIO_DEF",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"scale": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"panelId": "pmdg-737-700-xpndr",
|
||||
"name": "PMDG 737 Transponder",
|
||||
"rootPath": "pmdg-737-700",
|
||||
"panelSize": {
|
||||
"width": 498,
|
||||
"height": 259
|
||||
},
|
||||
"showMenuBar": false,
|
||||
"enableMap": false,
|
||||
"subPanels": [
|
||||
{
|
||||
"panelId": "xpndr",
|
||||
"name": "XPNDR",
|
||||
"rootPath": "xpndr",
|
||||
"definitions": "PMDG_737_700_XPNDR_DEF",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"scale": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"panelId": "pmdg-737-700-efis-cpt",
|
||||
"name": "PMDG 737 EFIS (Captain)",
|
||||
"rootPath": "pmdg-737-700",
|
||||
"panelSize": {
|
||||
"width": 724,
|
||||
"height": 394
|
||||
},
|
||||
"showMenuBar": false,
|
||||
"enableMap": false,
|
||||
"subPanels": [
|
||||
{
|
||||
"panelId": "efis-cpt",
|
||||
"name": "EFIS-CPT",
|
||||
"rootPath": "efis-cpt",
|
||||
"definitions": "PMDG_737_700_EFIS_CPT_DEF",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"scale": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
4666
ReactClient/public/config/mobiFlightPresets/msfs2020_eventids.cip
Normal file
2033
ReactClient/public/config/mobiFlightPresets/msfs2020_simvars.cip
Normal file
|
@ -0,0 +1,380 @@
|
|||
[
|
||||
{
|
||||
"propName": "SIM_RATE",
|
||||
"variableName": "SIMULATION RATE",
|
||||
"simConnectUnit": "number",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": "toFixed2"
|
||||
},
|
||||
{
|
||||
"propName": "GPS_LAT",
|
||||
"variableName": "GPS POSITION LAT",
|
||||
"simConnectUnit": "degrees",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "GPS_LON",
|
||||
"variableName": "GPS POSITION LON",
|
||||
"simConnectUnit": "degrees",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "PLANE_HEADING_TRUE",
|
||||
"variableName": "PLANE HEADING DEGREES TRUE",
|
||||
"simConnectUnit": "degrees",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": "toFixed0"
|
||||
},
|
||||
{
|
||||
"propName": "PLANE_AIRSPEED",
|
||||
"variableName": "AIRSPEED INDICATED",
|
||||
"simConnectUnit": "knots",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": "toFixed0"
|
||||
},
|
||||
{
|
||||
"propName": "FLAPS",
|
||||
"variableName": "TRAILING EDGE FLAPS LEFT ANGLE",
|
||||
"simConnectUnit": "degrees",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": "toFixed0"
|
||||
},
|
||||
{
|
||||
"propName": "ELEVATOR_TRIM",
|
||||
"variableName": "ELEVATOR TRIM PCT",
|
||||
"simConnectUnit": "percent",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": "toFixed1"
|
||||
},
|
||||
{
|
||||
"propName": "RUDDER_TRIM",
|
||||
"variableName": "RUDDER TRIM",
|
||||
"simConnectUnit": "radians",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": "toFixed2"
|
||||
},
|
||||
{
|
||||
"propName": "AILERON_TRIM",
|
||||
"variableName": "AILERON TRIM",
|
||||
"simConnectUnit": "radians",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": "toFixed3"
|
||||
},
|
||||
{
|
||||
"propName": "BRAKE_PARKING_INDICATOR",
|
||||
"variableName": "BRAKE PARKING INDICATOR",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": "toFixed0"
|
||||
},
|
||||
{
|
||||
"propName": "GEAR_POSITION",
|
||||
"variableName": "GEAR POSITION",
|
||||
"simConnectUnit": "enum",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "GEAR_CENTER_POSITION",
|
||||
"variableName": "GEAR CENTER POSITION",
|
||||
"simConnectUnit": "percent",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "GEAR_LEFT_POSITION",
|
||||
"variableName": "GEAR LEFT POSITION",
|
||||
"simConnectUnit": "percent",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "GEAR_RIGHT_POSITION",
|
||||
"variableName": "GEAR RIGHT POSITION",
|
||||
"simConnectUnit": "percent",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "AP_YAW_DAMPER_ON",
|
||||
"variableName": "AUTOPILOT YAW DAMPER",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "BATTERY_MASTER_ON",
|
||||
"variableName": "ELECTRICAL MASTER BATTERY",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "AVIONICS_MASTER_ON",
|
||||
"variableName": "AVIONICS MASTER SWITCH",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "AP_MASTER_ON",
|
||||
"variableName": "AUTOPILOT MASTER",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "LIGHT_TAXI_ON",
|
||||
"variableName": "LIGHT TAXI",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "LIGHT_NAV_ON",
|
||||
"variableName": "LIGHT NAV",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "LIGHT_BEACON_ON",
|
||||
"variableName": "LIGHT BEACON",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "LIGHT_STROBE_ON",
|
||||
"variableName": "LIGHT STROBE",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "LIGHT_WING_ON",
|
||||
"variableName": "LIGHT WING",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_LANDING_LIGHT",
|
||||
"variableName": "SWS_LIGHTING_Switch_Light_Landing",
|
||||
"simConnectUnit": null,
|
||||
"dataType": "Default",
|
||||
"dataDefinitionType": "LVar",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_CABIN_LIGHT",
|
||||
"variableName": "SWS_LIGHTING_Switch_Light_CABIN_12",
|
||||
"simConnectUnit": null,
|
||||
"dataType": "Default",
|
||||
"dataDefinitionType": "LVar",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_PITOT_HEAT_1",
|
||||
"variableName": "DEICE_Pitot_1",
|
||||
"simConnectUnit": null,
|
||||
"dataType": "Default",
|
||||
"dataDefinitionType": "LVar",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_PITOT_HEAT_2",
|
||||
"variableName": "DEICE_Pitot_2",
|
||||
"simConnectUnit": null,
|
||||
"dataType": "Default",
|
||||
"dataDefinitionType": "LVar",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_AUX_BUS",
|
||||
"variableName": "XMLVAR_AUX_Bus_ON",
|
||||
"simConnectUnit": null,
|
||||
"dataType": "Default",
|
||||
"dataDefinitionType": "LVar",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_FUEL_PUMP",
|
||||
"variableName": "SWS_FUEL_Switch_Pump_1",
|
||||
"simConnectUnit": null,
|
||||
"dataType": "Default",
|
||||
"dataDefinitionType": "LVar",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_IGNITION",
|
||||
"variableName": "SWS_ENGINE_Switch_Ignition_1",
|
||||
"simConnectUnit": null,
|
||||
"dataType": "Default",
|
||||
"dataDefinitionType": "LVar",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_STARTER",
|
||||
"variableName": "SWS_ENGINE_Switch_Starter_ThreeState_1",
|
||||
"simConnectUnit": null,
|
||||
"dataType": "Default",
|
||||
"dataDefinitionType": "LVar",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_FUEL_SHUTOFF_LEFT",
|
||||
"variableName": "SWS_Kodiak_TankSelector_1",
|
||||
"simConnectUnit": null,
|
||||
"dataType": "Default",
|
||||
"dataDefinitionType": "LVar",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_FUEL_SHUTOFF_RIGHT",
|
||||
"variableName": "SWS_Kodiak_TankSelector_2",
|
||||
"simConnectUnit": null,
|
||||
"dataType": "Default",
|
||||
"dataDefinitionType": "LVar",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_OXYGEN",
|
||||
"variableName": "XMLVAR_Oxygen",
|
||||
"simConnectUnit": null,
|
||||
"dataType": "Default",
|
||||
"dataDefinitionType": "LVar",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "ENG_ANTI_ICE_1",
|
||||
"variableName": "ENG ANTI ICE:1",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "PROP_DEICE_SWITCH",
|
||||
"variableName": "PROP DEICE SWITCH:1",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "WINDSHIELD_DEICE_SWITCH",
|
||||
"variableName": "WINDSHIELD DEICE SWITCH",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "ALTERNATOR_1_ON",
|
||||
"variableName": "GENERAL ENG MASTER ALTERNATOR:1",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "ALTERNATOR_2_ON",
|
||||
"variableName": "GENERAL ENG MASTER ALTERNATOR:2",
|
||||
"simConnectUnit": "bool",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_GLARESHIELD_SETTING",
|
||||
"variableName": "LIGHT POTENTIOMETER:3",
|
||||
"simConnectUnit": "number",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_INSTRUMENTATION_LIGHT_SETTING",
|
||||
"variableName": "LIGHT POTENTIOMETER:2",
|
||||
"simConnectUnit": "number",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
},
|
||||
{
|
||||
"propName": "KODIAK_PANEL_LIGHT_SETTING",
|
||||
"variableName": "LIGHT POTENTIOMETER:21",
|
||||
"simConnectUnit": "number",
|
||||
"dataType": "Float64",
|
||||
"dataDefinitionType": "SimConnect",
|
||||
"defaultValue": 0,
|
||||
"JavaScriptFormatting": null
|
||||
}
|
||||
]
|
|
@ -0,0 +1,389 @@
|
|||
{
|
||||
"id": "KODIAK_ELECTRICAL_DEF",
|
||||
"value": {
|
||||
"panelSize": {
|
||||
"width": 812,
|
||||
"height": 377
|
||||
},
|
||||
"backgroundImage": "background_electrical.png",
|
||||
"controlSize": {
|
||||
"buttonBase": {
|
||||
"width": 57,
|
||||
"height": 36
|
||||
},
|
||||
"toggleButtonBase": {
|
||||
"width": 50,
|
||||
"height": 98
|
||||
},
|
||||
"masterBase": {
|
||||
"width": 80,
|
||||
"height": 141
|
||||
},
|
||||
"rockerBase": {
|
||||
"width": 55,
|
||||
"height": 115
|
||||
},
|
||||
"buttonStatusIndicator": {
|
||||
"width": 4,
|
||||
"height": 32
|
||||
}
|
||||
},
|
||||
"panelControlDefinitions": [
|
||||
{
|
||||
"id": "btn_yd",
|
||||
"type": "imageButton",
|
||||
"image": "button_yd.png",
|
||||
"left": 46,
|
||||
"top": 74,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.buttonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "YAW_DAMPER_TOGGLE",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_yd_indicator",
|
||||
"type": "bindableImageButton",
|
||||
"left": 105,
|
||||
"top": 73,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.buttonStatusIndicator"
|
||||
},
|
||||
"highlight": false,
|
||||
"binding": {
|
||||
"variable": "AP_YAW_DAMPER_ON",
|
||||
"images": [
|
||||
{
|
||||
"url": "button_status_on.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "button_status_on.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_lvl",
|
||||
"type": "imageButton",
|
||||
"image": "button_lvl.png",
|
||||
"left": 46,
|
||||
"top": 134,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.buttonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "NO_ACTION",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_bank",
|
||||
"type": "imageButton",
|
||||
"image": "button_bank.png",
|
||||
"left": 46,
|
||||
"top": 196,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.buttonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "NO_ACTION",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_spd",
|
||||
"type": "imageButton",
|
||||
"image": "button_spd.png",
|
||||
"left": 46,
|
||||
"top": 258,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.buttonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "AP_SPD_VAR_SET",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_master",
|
||||
"type": "bindableImageButton",
|
||||
"left": 155,
|
||||
"top": 98,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.masterBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "TOGGLE_MASTER_BATTERY",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "BATTERY_MASTER_ON",
|
||||
"images": [
|
||||
{
|
||||
"url": "master_off.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "master_on.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_avn_bus",
|
||||
"type": "bindableImageButton",
|
||||
"left": 274,
|
||||
"top": 106,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.rockerBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "TOGGLE_AVIONICS_MASTER",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "AVIONICS_MASTER_ON",
|
||||
"images": [
|
||||
{
|
||||
"url": "rocker_off.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "rocker_on.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_aux_bus",
|
||||
"type": "bindableImageButton",
|
||||
"left": 363,
|
||||
"top": 106,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.rockerBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "(L:XMLVAR_AUX_Bus_ON) ++ 2 % (>L:XMLVAR_AUX_Bus_ON)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "KODIAK_AUX_BUS",
|
||||
"images": [
|
||||
{
|
||||
"url": "rocker_off.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "rocker_on.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_aux_fuel_pump",
|
||||
"type": "bindableImageButton",
|
||||
"left": 472,
|
||||
"top": 47,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "(L:SWS_FUEL_Switch_Pump_1) ++ 3 % (>L:SWS_FUEL_Switch_Pump_1)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "KODIAK_FUEL_PUMP",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_mid.png",
|
||||
"val": 1
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_ignition",
|
||||
"type": "bindableImageButton",
|
||||
"left": 569,
|
||||
"top": 47,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "(L:SWS_ENGINE_Switch_Ignition_1) ++ 2 % (>L:SWS_ENGINE_Switch_Ignition_1)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "KODIAK_IGNITION",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_starter",
|
||||
"type": "bindableImageButton",
|
||||
"left": 666,
|
||||
"top": 47,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "(L:SWS_ENGINE_Switch_Starter_ThreeState_1) ++ 3 % (>L:SWS_ENGINE_Switch_Starter_ThreeState_1)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "KODIAK_STARTER",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_mid.png",
|
||||
"val": 1
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_generator",
|
||||
"type": "bindableImageButton",
|
||||
"left": 569,
|
||||
"top": 225,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "TOGGLE_ALTERNATOR1",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "ALTERNATOR_1_ON",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_alternator",
|
||||
"type": "bindableImageButton",
|
||||
"left": 666,
|
||||
"top": 225,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "TOGGLE_ALTERNATOR2",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "ALTERNATOR_2_ON",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 76 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1,80 @@
|
|||
{
|
||||
"id": "KODIAK_FUEL_DEF",
|
||||
"value": {
|
||||
"panelSize": {
|
||||
"width": 440,
|
||||
"height": 226
|
||||
},
|
||||
"backgroundImage": "background_fuel.png",
|
||||
"controlSize": {
|
||||
"fuelLever": {
|
||||
"width": 158,
|
||||
"height": 158
|
||||
}
|
||||
},
|
||||
"panelControlDefinitions": [
|
||||
{
|
||||
"id": "btn_fuel_left",
|
||||
"type": "bindableImageButton",
|
||||
"left": 50,
|
||||
"top": 45,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.fuelLever"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "(L:SWS_Kodiak_TankSelector_1) ++ 2 % (>L:SWS_Kodiak_TankSelector_1)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "KODIAK_FUEL_SHUTOFF_LEFT",
|
||||
"images": [
|
||||
{
|
||||
"url": "fuel_lever_left_off.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "fuel_lever_left_on.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_fuel_right",
|
||||
"type": "bindableImageButton",
|
||||
"left": 232,
|
||||
"top": 45,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.fuelLever"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "(L:SWS_Kodiak_TankSelector_2) ++ 2 % (>L:SWS_Kodiak_TankSelector_2)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "KODIAK_FUEL_SHUTOFF_RIGHT",
|
||||
"images": [
|
||||
{
|
||||
"url": "fuel_lever_right_off.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "fuel_lever_right_on.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,488 @@
|
|||
{
|
||||
"id": "KODIAK_LIGHTS_DEF",
|
||||
"value": {
|
||||
"panelSize": {
|
||||
"width": 929,
|
||||
"height": 380
|
||||
},
|
||||
"backgroundImage": "background_lights.png",
|
||||
"controlSize": {
|
||||
"toggleButtonBase": {
|
||||
"width": 50,
|
||||
"height": 98
|
||||
},
|
||||
"lightKnob": {
|
||||
"width": 70,
|
||||
"height": 70
|
||||
},
|
||||
"iceKnob": {
|
||||
"width": 65,
|
||||
"height": 65
|
||||
},
|
||||
"rockerBase": {
|
||||
"width": 55,
|
||||
"height": 115
|
||||
}
|
||||
},
|
||||
"panelControlDefinitions": [
|
||||
{
|
||||
"id": "btn_beacon_light",
|
||||
"type": "bindableImageButton",
|
||||
"left": 82,
|
||||
"top": 49,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "TOGGLE_BEACON_LIGHTS",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "LIGHT_BEACON_ON",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_strobe_light",
|
||||
"type": "bindableImageButton",
|
||||
"left": 178,
|
||||
"top": 49,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "STROBES_TOGGLE",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "LIGHT_STROBE_ON",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_nav_light",
|
||||
"type": "bindableImageButton",
|
||||
"left": 272,
|
||||
"top": 49,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "TOGGLE_NAV_LIGHTS",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "LIGHT_NAV_ON",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_taxi_light",
|
||||
"type": "bindableImageButton",
|
||||
"left": 367,
|
||||
"top": 49,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "TOGGLE_TAXI_LIGHTS",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "LIGHT_TAXI_ON",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_landing_light",
|
||||
"type": "bindableImageButton",
|
||||
"left": 485,
|
||||
"top": 49,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "(L:SWS_LIGHTING_Switch_Light_Landing) ++ 3 % (>L:SWS_LIGHTING_Switch_Light_Landing)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "KODIAK_LANDING_LIGHT",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_mid.png",
|
||||
"val": 1
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_cabin_light",
|
||||
"type": "bindableImageButton",
|
||||
"left": 603,
|
||||
"top": 49,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "(L:SWS_LIGHTING_Switch_Light_CABIN_12) ++ 3 % (>L:SWS_LIGHTING_Switch_Light_CABIN_12)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "KODIAK_CABIN_LIGHT",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_mid.png",
|
||||
"val": 1
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_instrument_light_knob",
|
||||
"type": "imageButton",
|
||||
"image": "knob_light_dual.png",
|
||||
"left": 685,
|
||||
"top": 69,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.lightKnob"
|
||||
},
|
||||
"highlight": true,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "KODIAK_INSTRUMENTS_LIGHT_KNOB_SELECT",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
],
|
||||
"useDualEncoder": true,
|
||||
"encoderAction": {
|
||||
"encoderLowerCW": "[/{KODIAK_GLARESHIELD_SETTING/} * 100 + 5] 100 min (>K:LIGHT_POTENTIOMETER_3_SET) 1 (>K:GLARESHIELD_LIGHTS_ON)",
|
||||
"encoderLowerCCW": "[/{KODIAK_GLARESHIELD_SETTING/} * 100 - 5] 0 max (>K:LIGHT_POTENTIOMETER_3_SET)",
|
||||
"encoderUpperCW": "[/{KODIAK_INSTRUMENTATION_LIGHT_SETTING/} * 100 + 5] 100 min (>K:LIGHT_POTENTIOMETER_2_SET) 1 (>K:PANEL_LIGHTS_ON)",
|
||||
"encoderUpperCCW": "[/{KODIAK_INSTRUMENTATION_LIGHT_SETTING/} * 100 - 5] 0 max (>K:LIGHT_POTENTIOMETER_2_SET)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_switch_panel_light_knob",
|
||||
"type": "imageButton",
|
||||
"image": "knob_light.png",
|
||||
"left": 778,
|
||||
"top": 69,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.lightKnob"
|
||||
},
|
||||
"highlight": true,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "KODIAK_PANEL_LIGHT_KNOB_SELECT",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
],
|
||||
"useEncoder": true,
|
||||
"encoderAction": {
|
||||
"encoderLowerCW": "[/{KODIAK_PANEL_LIGHT_SETTING/} * 100 + 5] 100 min (>K:LIGHT_POTENTIOMETER_21_SET) 1 (>K:PEDESTRAL_LIGHTS_ON)",
|
||||
"encoderLowerCCW": "[/{KODIAK_PANEL_LIGHT_SETTING/} * 100 - 5] 0 max (>K:LIGHT_POTENTIOMETER_21_SET)",
|
||||
"encoderUpperCW": "[/{KODIAK_PANEL_LIGHT_SETTING/} * 100 + 5] 100 min (>K:LIGHT_POTENTIOMETER_21_SET) 1 (>K:PEDESTRAL_LIGHTS_ON)",
|
||||
"encoderUpperCCW": "[/{KODIAK_PANEL_LIGHT_SETTING/} * 100 - 5] 0 max (>K:LIGHT_POTENTIOMETER_21_SET)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_engine_inlet_bypass_override",
|
||||
"type": "image",
|
||||
"image": "toggle_down.png",
|
||||
"left": 82,
|
||||
"top": 225,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false
|
||||
},
|
||||
{
|
||||
"id": "btn_engine_inlet_bypass",
|
||||
"type": "bindableImageButton",
|
||||
"left": 178,
|
||||
"top": 225,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "1 (>K:ANTI_ICE_TOGGLE_ENG1)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "ENG_ANTI_ICE_1",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_pitot_heat_left",
|
||||
"type": "bindableImageButton",
|
||||
"left": 272,
|
||||
"top": 225,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "1 (>K:PITOT_HEAT_TOGGLE)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "KODIAK_PITOT_HEAT_1",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_pitot_heat_right",
|
||||
"type": "bindableImageButton",
|
||||
"left": 367,
|
||||
"top": 225,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "2 (>K:PITOT_HEAT_TOGGLE)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "KODIAK_PITOT_HEAT_2",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_ice_knob",
|
||||
"type": "bindableImageButton",
|
||||
"left": 480,
|
||||
"top": 250,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.iceKnob"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "TOGGLE_PROPELLER_DEICE",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "PROP_DEICE_SWITCH",
|
||||
"images": [
|
||||
{
|
||||
"url": "knob_ice.png",
|
||||
"rotate": 0,
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "knob_ice.png",
|
||||
"rotate": 95,
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_wind_shield_heat",
|
||||
"type": "bindableImageButton",
|
||||
"left": 603,
|
||||
"top": 225,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "KODIAK_100_DEICE_WINDSHIELD_TOGGLE",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "WINDSHIELD_DEICE_SWITCH",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_backup_pump_heat",
|
||||
"type": "image",
|
||||
"image": "toggle_down.png",
|
||||
"left": 696,
|
||||
"top": 225,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false
|
||||
},
|
||||
{
|
||||
"id": "btn_ice_light",
|
||||
"type": "bindableImageButton",
|
||||
"left": 790,
|
||||
"top": 225,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.toggleButtonBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "TOGGLE_WING_LIGHTS",
|
||||
"actionType": "SimEventId"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "LIGHT_WING_ON",
|
||||
"images": [
|
||||
{
|
||||
"url": "toggle_down.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "toggle_up.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 206 KiB |
After Width: | Height: | Size: 9 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 9.5 KiB |
After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"id": "KODIAK_OXYGEN_DEF",
|
||||
"value": {
|
||||
"panelSize": {
|
||||
"width": 215,
|
||||
"height": 215
|
||||
},
|
||||
"backgroundImage": "background_oxygen.png",
|
||||
"controlSize": {
|
||||
"oxygenBase": {
|
||||
"width": 49,
|
||||
"height": 69
|
||||
},
|
||||
"oxygenIndicatorBase": {
|
||||
"width": 10,
|
||||
"height": 107
|
||||
}
|
||||
},
|
||||
"panelControlDefinitions": [
|
||||
{
|
||||
"id": "btn_oxygen",
|
||||
"type": "bindableImageButton",
|
||||
"left": 92,
|
||||
"top": 82,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.oxygenBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"action": {
|
||||
"touchActions": [
|
||||
{
|
||||
"action": "(L:XMLVAR_Oxygen) ++ 2 % (>L:XMLVAR_Oxygen)",
|
||||
"actionType": "SimVarCode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"binding": {
|
||||
"variable": "KODIAK_OXYGEN",
|
||||
"images": [
|
||||
{
|
||||
"url": "oxygen_off.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "oxygen_on.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "btn_oxygen_indicator",
|
||||
"type": "bindableImageButton",
|
||||
"left": 66,
|
||||
"top": 60,
|
||||
"controlSize": {
|
||||
"$ref": "#value.controlSize.oxygenIndicatorBase"
|
||||
},
|
||||
"highlight": false,
|
||||
"binding": {
|
||||
"variable": "KODIAK_OXYGEN",
|
||||
"images": [
|
||||
{
|
||||
"url": "oxygen_indicator_off.png",
|
||||
"val": 0
|
||||
},
|
||||
{
|
||||
"url": "oxygen_indicator_on.png",
|
||||
"val": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|