1
0
Fork 0
mirror of https://github.com/hawkeye-stan/msfs-popout-panel-manager.git synced 2024-12-28 07:11:59 +00:00

Started development v3.4.2

This commit is contained in:
Stanley 2022-08-13 02:14:49 -04:00
parent bfb2a72a20
commit deea4bc739
35 changed files with 505 additions and 171 deletions

View file

@ -7,6 +7,7 @@ namespace MSFSPopoutPanelManager.Orchestration
public class FlightSimData : ObservableObject
{
public event PropertyChangedEventHandler CurrentMsfsAircraftChanged;
public event PropertyChangedEventHandler CurrentMsfsLiveryTitleChanged;
public string CurrentMsfsAircraft { get; set; }
@ -27,10 +28,13 @@ namespace MSFSPopoutPanelManager.Orchestration
{
if (oldValue != newValue)
{
base.OnPropertyChanged(propertyName, oldValue, newValue);
if (propertyName == "CurrentMsfsAircraft")
CurrentMsfsAircraftChanged?.Invoke(this, null);
base.OnPropertyChanged(propertyName, oldValue, newValue);
if (propertyName == "CurrentMsfsLiveryTitle")
CurrentMsfsLiveryTitleChanged?.Invoke(this, null);
}
}

View file

@ -61,7 +61,7 @@ namespace MSFSPopoutPanelManager.Orchestration
if (FlightSimData.CurrentMsfsAircraft != aircraftName)
{
FlightSimData.CurrentMsfsAircraft = aircraftName;
ProfileData.AutoSwitchProfile(aircraftName);
ProfileData.AutoSwitchProfile();
}
};
_simConnectProvider.OnFlightStarted += (sender, e) => OnFlightStarted?.Invoke(this, null);

View file

@ -20,7 +20,8 @@ namespace MSFSPopoutPanelManager.Orchestration
TouchPanel = new TouchPanelOrchestrator();
FlightSimData = new FlightSimData();
FlightSimData.CurrentMsfsAircraftChanged += (sernder, e) => ProfileData.RefreshProfile();
FlightSimData.CurrentMsfsAircraftChanged += (sernder, e) => { ProfileData.RefreshProfile(); ProfileData.AutoSwitchProfile(); };
FlightSimData.CurrentMsfsLiveryTitleChanged += (sernder, e) => { ProfileData.MigrateLiveryToAircraftBinding(); ProfileData.AutoSwitchProfile(); };
AppSettingData = new AppSettingData();
AppSettingData.AutoPopOutPanelsChanged += (sender, e) => FlightSim.AutoPanelPopOutActivation(e);

View file

@ -12,6 +12,7 @@ namespace MSFSPopoutPanelManager.Orchestration
private static PInvoke.WinEventProc _winEvent; // keep this as static to prevent garbage collect or the app will crash
private static IntPtr _winEventHook;
private Rectangle _lastWindowRectangle;
private IntPtr _panelHandleDisableRefresh = IntPtr.Zero;
public PanelConfigurationOrchestrator()
{
@ -83,14 +84,17 @@ namespace MSFSPopoutPanelManager.Orchestration
{
case PanelConfigPropertyName.Left:
case PanelConfigPropertyName.Top:
WindowActionManager.MoveWindow(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
_panelHandleDisableRefresh = panelConfig.PanelHandle;
WindowActionManager.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
break;
case PanelConfigPropertyName.Width:
case PanelConfigPropertyName.Height:
_panelHandleDisableRefresh = panelConfig.PanelHandle;
if (panelConfig.HideTitlebar)
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, false);
WindowActionManager.MoveWindowWithMsfsBugOverrirde(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
WindowActionManager.MoveWindowWithMsfsBugOverrirde(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
if (panelConfig.HideTitlebar)
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, true);
@ -100,6 +104,7 @@ namespace MSFSPopoutPanelManager.Orchestration
WindowActionManager.ApplyAlwaysOnTop(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.AlwaysOnTop, new Rectangle(panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height));
break;
case PanelConfigPropertyName.HideTitlebar:
_panelHandleDisableRefresh = panelConfig.PanelHandle;
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, panelConfig.HideTitlebar);
break;
case PanelConfigPropertyName.TouchEnabled:
@ -136,32 +141,36 @@ namespace MSFSPopoutPanelManager.Orchestration
switch (configPropertyName)
{
case PanelConfigPropertyName.Left:
_panelHandleDisableRefresh = panelConfig.PanelHandle;
panelConfig.Left += changeAmount;
WindowActionManager.MoveWindow(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
WindowActionManager.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
break;
case PanelConfigPropertyName.Top:
_panelHandleDisableRefresh = panelConfig.PanelHandle;
panelConfig.Top += changeAmount;
WindowActionManager.MoveWindow(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
WindowActionManager.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
break;
case PanelConfigPropertyName.Width:
_panelHandleDisableRefresh = panelConfig.PanelHandle;
panelConfig.Width += changeAmount;
if (panelConfig.HideTitlebar)
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, false);
WindowActionManager.MoveWindowWithMsfsBugOverrirde(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
WindowActionManager.MoveWindowWithMsfsBugOverrirde(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
if (panelConfig.HideTitlebar)
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, true);
break;
case PanelConfigPropertyName.Height:
_panelHandleDisableRefresh = panelConfig.PanelHandle;
panelConfig.Height += changeAmount;
if (panelConfig.HideTitlebar)
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, false);
WindowActionManager.MoveWindowWithMsfsBugOverrirde(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
WindowActionManager.MoveWindowWithMsfsBugOverrirde(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
if (panelConfig.HideTitlebar)
WindowActionManager.ApplyHidePanelTitleBar(panelConfig.PanelHandle, true);
@ -224,7 +233,7 @@ namespace MSFSPopoutPanelManager.Orchestration
{
case PInvokeConstant.EVENT_SYSTEM_MOVESIZEEND:
// Move window back to original location
WindowActionManager.MoveWindow(panelConfig.PanelHandle, panelConfig.PanelType, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
WindowActionManager.MoveWindow(panelConfig.PanelHandle, panelConfig.Left, panelConfig.Top, panelConfig.Width, panelConfig.Height);
break;
case PInvokeConstant.EVENT_OBJECT_LOCATIONCHANGE:
WINDOWPLACEMENT wp = new WINDOWPLACEMENT();
@ -249,21 +258,25 @@ namespace MSFSPopoutPanelManager.Orchestration
return;
_lastWindowRectangle = winRectangle;
Rectangle clientRectangle;
PInvoke.GetClientRect(panelConfig.PanelHandle, out clientRectangle);
if (_panelHandleDisableRefresh != IntPtr.Zero)
{
_panelHandleDisableRefresh = IntPtr.Zero;
return;
}
panelConfig.Left = winRectangle.Left;
panelConfig.Top = winRectangle.Top;
if (panelConfig.HideTitlebar)
if (!panelConfig.HideTitlebar)
{
panelConfig.Width = clientRectangle.Width;
panelConfig.Height = clientRectangle.Height;
panelConfig.Width = winRectangle.Width - winRectangle.Left;
panelConfig.Height = winRectangle.Height - winRectangle.Top;
}
else
{
panelConfig.Width = clientRectangle.Width + 16;
panelConfig.Height = clientRectangle.Height + 39;
panelConfig.Width = winRectangle.Width - winRectangle.Left - 16;
panelConfig.Height = winRectangle.Height - winRectangle.Top - 39;
}
// Detect if window is maximized, if so, save settings

View file

@ -55,7 +55,7 @@ namespace MSFSPopoutPanelManager.Orchestration
if (ActiveProfile == null)
return;
ProfileData.AutoSwitchProfile(FlightSimData.CurrentMsfsAircraft);
ProfileData.AutoSwitchProfile();
FlightSimData.IsEnteredFlight = true;
@ -198,7 +198,7 @@ namespace MSFSPopoutPanelManager.Orchestration
{
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);
WindowActionManager.MoveWindow(panelConfigs[i].PanelHandle, panelConfigs[i].Top, panelConfigs[i].Left, panelConfigs[i].Width, panelConfigs[i].Height);
PInvoke.SetForegroundWindow(panelConfigs[i].PanelHandle);
Thread.Sleep(200);
}
@ -237,7 +237,7 @@ namespace MSFSPopoutPanelManager.Orchestration
// 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);
WindowActionManager.MoveWindow(handle, 0, 0, 800, 600);
// Make window always on top to make sure it is clickable and not obstruct by other user windows
//WindowActionManager.ApplyAlwaysOnTop(handle, PanelType.WPFWindow, true);
@ -260,7 +260,7 @@ namespace MSFSPopoutPanelManager.Orchestration
}
// Fix SU10+ bug where pop out window after separation is huge
WindowActionManager.MoveWindow(handle, PanelType.CustomPopout, -8, 0, 800, 600);
WindowActionManager.MoveWindow(handle, -8, 0, 800, 600);
var panel = new PanelConfig();
panel.PanelHandle = handle;
@ -296,7 +296,7 @@ namespace MSFSPopoutPanelManager.Orchestration
// 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);
WindowActionManager.MoveWindow(hwnd, -8, 0, 800, 600);
PInvoke.SetForegroundWindow(hwnd);
Thread.Sleep(500);
@ -410,7 +410,7 @@ namespace MSFSPopoutPanelManager.Orchestration
{
PInvoke.ShowWindow(panel.PanelHandle, PInvokeConstant.SW_RESTORE);
Thread.Sleep(250);
WindowActionManager.MoveWindow(panel.PanelHandle, panel.PanelType, panel.Left, panel.Top, panel.Width, panel.Height);
WindowActionManager.MoveWindow(panel.PanelHandle, panel.Left, panel.Top, panel.Width, panel.Height);
Thread.Sleep(1000);
}

View file

@ -160,12 +160,12 @@ namespace MSFSPopoutPanelManager.Orchestration
UpdateActiveProfile(currentProfileId);
}
public void AutoSwitchProfile(string activeAircraft)
public void AutoSwitchProfile()
{
// Automatic switching of active profile when SimConnect active aircraft changes
if (Profiles != null)
if (Profiles != null && !string.IsNullOrEmpty(FlightSimData.CurrentMsfsAircraft))
{
var matchedProfile = Profiles.FirstOrDefault(p => p.BindingAircrafts.Any(t => t == activeAircraft));
var matchedProfile = Profiles.FirstOrDefault(p => p.BindingAircrafts.Any(t => t == FlightSimData.CurrentMsfsAircraft));
if (matchedProfile != null)
UpdateActiveProfile(matchedProfile.ProfileId);
}
@ -175,15 +175,21 @@ namespace MSFSPopoutPanelManager.Orchestration
// Started in v3.4.2
public void MigrateLiveryToAircraftBinding(string liveryName, string aircraftName)
{
if (Profiles != null)
if (Profiles != null && !string.IsNullOrEmpty(liveryName) && !string.IsNullOrEmpty(aircraftName))
{
var matchedProfile = Profiles.FirstOrDefault(p => p.BindingAircraftLiveries.Any(t => t == liveryName));
if (matchedProfile != null && !matchedProfile.BindingAircrafts.Any(a => a == aircraftName))
{
matchedProfile.BindingAircrafts.Add(aircraftName);
WriteProfiles();
RefreshProfile();
}
}
}
public void MigrateLiveryToAircraftBinding()
{
MigrateLiveryToAircraftBinding(FlightSimData.CurrentMsfsLiveryTitle, FlightSimData.CurrentMsfsAircraft);
}
}
}

View file

@ -40,13 +40,13 @@ namespace MSFSPopoutPanelManager.Orchestration
public void AddProfileBinding(string bindingAircraft)
{
if (ProfileData.ActiveProfile != null)
if (ProfileData.ActiveProfile != null && bindingAircraft != null)
ProfileData.AddProfileBinding(bindingAircraft, ProfileData.ActiveProfile.ProfileId);
}
public void DeleteProfileBinding(string bindingAircraft)
{
if (ProfileData.ActiveProfile != null)
if (ProfileData.ActiveProfile != null && bindingAircraft != null)
ProfileData.DeleteProfileBinding(bindingAircraft, ProfileData.ActiveProfile.ProfileId);
}
}

View file

@ -15,7 +15,7 @@ Please follow [FlightSimulator.com](https://forums.flightsimulator.com/t/msfs-po
<hr>
## Touch Panel Feature
With SU10 Beta v1.27.11, Asobo seems to have fix a major [bug](#touch-enable-pop-out-feature) (in SU9 and before) that stops Pop Out Manager's touch panel feature from working reliably. I'm happy to announce touch enabled feature works pretty well out of the box on either direct connected touch monitor or on pop out window that is displayed on tablet using software tool such as SpaceDesk. Until Asobo actually allow touch passthrough for panels, this tool can serve as a stopgap solution.
With SU10 Beta v1.27.11, Asobo seems to have fix a major [bug](#touch-enable-pop-out-feature) (in SU9 and before) that stops Pop Out Panel Manager's touch panel feature from working reliably. I'm happy to announce touch enabled feature works pretty well out of the box on either direct connected touch monitor or on pop out window that is displayed on tablet using software tool such as SpaceDesk. Until Asobo actually allow touch passthrough for panels, this tool can serve as a stopgap solution.
I've tested touch operation on GTN750, GTN530, KingAir PFD/MFD, TMB 930 FMS, Flybywire A32NX EFB and they are operational. Please report any issues that you encounter when using touch enable feature. There is still lots of room for improvement and I'll continue my effort to make touch work better and better.
@ -28,8 +28,6 @@ Things that work out of the box:
If using SpaceDesk to host pop out panel display, since there is a latency for touch response in wireless display, your touch may not register consistently. Please go to Preferences => Touch Settings => Touch Down Touch Up Delay, and increase the value to 25ms or higher to increase touch sensitivity.
## Application Features
* Display resolution independent. Supports 1080p/1440p/4k display and ultrawide displays.
@ -55,7 +53,7 @@ If using SpaceDesk to host pop out panel display, since there is a latency for t
* Auto update feature. Application can auto-update itself when new version becomes available.
* **Experimental Feature**: Enable touch support for pop outs on touch capable display. Please see [Touch Enable Pop Out Feature](#touch-enable-pop-out-feature) for more information.
* Enable touch support for pop outs on touch capable display. Please see [Touch Enable Pop Out Feature](#touch-enable-pop-out-feature) for more information.
<hr>
@ -96,7 +94,7 @@ What if you can do the setup once by defining on screen where the pop out panels
<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 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 name. The aircraft title will become green once the it is bound to the profile. Your chosen aircraft may not be available to select in the application for your newly selected plane until a flight is started.
2. If you want to associate the profile to the current aircraft 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 name. The aircraft title will become green once it is bound to the profile. Your chosen aircraft may not be available to select in the application for your newly selected plane until a flight is started. If the current aircraft has not been bound to another profile, it will be bound to your newly created profile automatically.
<p align="center">
<img src="assets/readme/images/bind_profile_to_livery.png" width="900" hspace="10"/>
@ -138,11 +136,9 @@ The app will try to find a matching profile with the current selected aircraft.
* In File->Preferences->Auto Pop Out Panel Settings, "Enable Auto Pop Out Panels" option is turned on. You can also adjust wait delay settings if you've a fast computer.
* For existing profile to use Auto Pop Out feature, just click the plus sign in the bind active aircraft to profile section. This will bind the active aircraft being displayed to the profile. Any bound aircraft will appear in GREEN color. Unbound ones will be in WHITE, and bound aircraft in another profile will be in RED. You can bind as many liveries to a profile as you desire but an aircraft can only bind to a single profile so Auto Pop Out knows which profile to start when a flight session starts.
* For existing profile to use Auto Pop Out feature, just click the plus sign in the bind active aircraft to profile section. This will bind the active aircraft being displayed to the profile. Any bound aircraft will appear in GREEN color. Unbound ones will be in WHITE, and bound aircraft in another profile will be in RED. You can bind as many aircrafts to a profile as you desire but an aircraft can only bind to a single profile so Auto Pop Out knows which profile to start when a flight session starts.
* During my testing, instrumentations only need to be powered on for Auto Pop Out to work for G1000/G1000 NXi plane during cold start. (There seems to be a bug in the game that you can't do Alt-Right click to pop out cold start panel for this particular plane variant). For other plane instrumentations I've tested (G3000, CJ4, Aerosoft CRJ 550/700, FBW A32NX), panels can be popped out without powering on. So please make sure the checkbox "Power on required to pop out panels on cold start" is checked for G1000 related profiles.
* **TIPS:** You can go to the preference settings to configure the time delay for each steps for the Auto Pop Out process based on the speed of your computer if things do not work correctly or if you want to speed up the Auto Pop Out process.
* During my testing, instrumentations only need to be powered on for Auto Pop Out to work for G1000/G1000 NXi plane during cold start. There seems to be a bug in the game that you can't do Alt-Right click to pop out cold start panel for this particular plane variant. For other plane instrumentations I've tested (G3000, CJ4, Aerosoft CRJ 550/700, FBW A32NX), panels can be popped out without powering on. So please make sure the checkbox "Power on required to pop out panels on cold start" is checked for G1000 related profiles.
* **TIPS:** One trick to force SimConnect to update the current selected aircraft so you can do the binding immediately after selecting a new aircraft in the World Map is to click the "Go Back" button at the lower left of your screen. You should see aircraft title changes to the active ones. Or you can wait until the flight is almost fully loaded and you will see the current aircraft name gets updated.
@ -166,28 +162,22 @@ In MSFS, when operating the above panels with pop outs, there are currently 2 li
- Limitation #2 - When you click or hover your mouse over any pop out panels on your main monitor or on another monitor, the game main window will lose focus and you cant operate flight control without clicking the game window again.
**EDITED (July 31st)** - Asobo have fixed the below bug in SU10 Beta v1.27.11. And hopefuly it won't be broken again.
**EDITED (July 31st)** - Asobo have fixed the below bug in SU10 Beta v1.27.11. And hopefully it won't be broken again.
- ~~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 havent 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 theyre 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="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="assets/readme/images/touch_support_bug_2.png" width="900" hspace="10"/>
</p>
#### How to enable touch support
Perform your regular panel selection and once your touch capable panel has been popped out, in the configuration screen grid, just check "Touch Enabled" to enable touch support for the selected panel.
#### Known Issues
~~- A MSFS click through bug where pop out panel also appears on the main game screen. Touch will not register correctly in the section of the pop out panel where the relative position of the panel corresponds to where the panel is located on the main game screen.~~ **Asobo fixed in SU10 Beta 1.27.11.**
~~- A MSFS click through bug where pop out panel also appears on the main game screen. Touch will not register correctly in the section of the pop out panel where the relative position of the panel corresponds to where the panel is located on the main game screen.~~ **Fixed in SU10 Beta 1.27.11.**
~~- When a click through occurs on non-instrumentation panel items such as throttle or switches, even though the switches will not accidentally get clicked, touch response in pop out panel may not work. Just touching a little bit to the left/right in the pop out panel may register your touch event correctly and trigger your intend target.~~ **Asobo fixed in SU10 Beta 1.27.11.**
~~- When a click through occurs on non-instrumentation panel items such as throttle or switches, even though the switches will not accidentally get clicked, touch response in pop out panel may not work. Just touching a little bit to the left/right in the pop out panel may register your touch event correctly and trigger your intend target.~~ **Fixed in SU10 Beta 1.27.11.**
- If touch suddenly becomes unresponsive, please try to change the main view of the game such as looking left/right using keyboard shortcut. This will sometime reset the mouse coordinate where you touch the pop out panel.
@ -239,7 +229,7 @@ You can backup this folder and restore this folder if you want to uninstall and
ERROR MSFSPopoutPanelManager.WpfApp.App - Could not load file or assembly 'Microsoft.FlightSimulator.SimConnect, Version=11.0.62651.3, Culture=neutral, PublicKeyToken=baf445ffb3a06b5c'. An attempt was made to load a program with an incorrect format. System.BadImageFormatException:
This usually happens on clean Windows installation. Pop Out Panel Manager uses x64 version of SimConnect.dll to perform its function and a Visaul C++ redistributable is required for SimConnect to run correctly. Please download and install the following [VC++ redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe) on your PC to resolve this issue. Further information can be obtained in this [support ticket](https://github.com/hawkeye-stan/msfs-popout-panel-manager/issues/21).
This usually happens on clean Windows installation. Pop Out Panel Manager uses x64 version of SimConnect.dll to perform its function and a Visual C++ redistributable is required for SimConnect to run correctly. Please download and install the following [VC++ redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe) on your PC to resolve this issue. Further information can be obtained in this [support ticket](https://github.com/hawkeye-stan/msfs-popout-panel-manager/issues/21).
## Author
Stanley Kwok

View file

@ -343,7 +343,7 @@
"controlSize": {
"$ref": "#value.controlSize.regularKnob"
},
"highlight": false,
"highlight": true,
"action": {
"touchActions": [
{
@ -571,7 +571,7 @@
"controlSize": {
"$ref": "#value.controlSize.speedKnob"
},
"highlight": false,
"highlight": true,
"action": {
"touchActions": [
{
@ -1589,7 +1589,7 @@
"controlSize": {
"$ref": "#value.controlSize.regularKnob"
},
"highlight": false,
"highlight": true,
"action": {
"touchActions": [
{
@ -1814,7 +1814,7 @@
"controlSize": {
"$ref": "#value.controlSize.speedKnob"
},
"highlight": false,
"highlight": true,
"action": {
"touchActions": [
{
@ -2568,7 +2568,7 @@
"controlSize": {
"$ref": "#value.controlSize.regularKnob"
},
"highlight": false,
"highlight": true,
"action": {
"touchActions": [
{
@ -2793,7 +2793,7 @@
"controlSize": {
"$ref": "#value.controlSize.speedKnob"
},
"highlight": false,
"highlight": true,
"action": {
"touchActions": [
{

View file

@ -14,7 +14,7 @@ const useStyles = makeStyles(() => ({
height: '100%'
},
iconImageHighlight: {
filter: 'sepia(80%)'
filter: 'brightness(1.5) sepia(1)'
},
controlBase: {
position: 'absolute',
@ -83,7 +83,6 @@ const setupControlWidthHeightStyle = (ctrl, panelInfo) => {
const ImageControl = ({ctrl, panelInfo}) => {
const classes = useStyles();
const imagePath = getImagePath(panelInfo);
const isHighlighted = useRef(false);
const setupBackgroundImageStyle = () => {
if (ctrl.image === undefined) {
@ -95,18 +94,17 @@ const ImageControl = ({ctrl, panelInfo}) => {
}
return useMemo(() =>(
<div className={[classes.controlBase, ctrl.highlight && isHighlighted.current ? classes.iconImageHighlight : ''].join(' ')} style={{ ...setupControlLocationStyle(ctrl, panelInfo), ...setupBackgroundImageStyle(ctrl, panelInfo), ...setupControlWidthHeightStyle(ctrl, panelInfo) }}>
<div className={classes.controlBase} style={{ ...setupControlLocationStyle(ctrl, panelInfo), ...setupBackgroundImageStyle(ctrl, panelInfo), ...setupControlWidthHeightStyle(ctrl, panelInfo) }}>
<IconButton className={classes.iconButton} />
</div>
), [ctrl, isHighlighted.current])
), [ctrl])
}
const ImageButton = ({ctrl, panelInfo, showEncoder}) => {
const ImageButton = ({ctrl, panelInfo, showEncoder, highLightedControlId, highlightedControlChanged}) => {
const classes = useStyles();
const { simConnectData } = useSimConnectData();
const { isUsedArduino, isEnabledSound } = useLocalStorageData().configurationData;
const imagePath = getImagePath(panelInfo);
const isHighlighted = useRef(false);
const setupBackgroundImageStyle = () => {
if (ctrl.image === undefined) {
@ -118,10 +116,10 @@ const ImageButton = ({ctrl, panelInfo, showEncoder}) => {
}
const handleOnClick = (event) => {
if (ctrl.highlight === undefined || ctrl.highlight) {
isHighlighted.current = true;
setTimeout(() => { isHighlighted.current = false; }, 2000);
}
if (ctrl.highlight === undefined || ctrl.highlight && highlightedControlChanged !== null)
highlightedControlChanged(ctrl.id);
else
highlightedControlChanged(null);
if (ctrl.action != null)
playSound(isEnabledSound);
@ -133,18 +131,17 @@ const ImageButton = ({ctrl, panelInfo, showEncoder}) => {
}
return useMemo(() =>(
<div className={[classes.controlBase, ctrl.highlight && isHighlighted.current ? classes.iconImageHighlight : ''].join(' ')} style={{ ...setupControlLocationStyle(ctrl, panelInfo), ...setupBackgroundImageStyle(ctrl, panelInfo), ...setupControlWidthHeightStyle(ctrl, panelInfo) }}>
<div className={[classes.controlBase, ctrl.highlight && highLightedControlId === ctrl.id ? classes.iconImageHighlight : ''].join(' ')} style={{ ...setupControlLocationStyle(ctrl, panelInfo), ...setupBackgroundImageStyle(ctrl, panelInfo), ...setupControlWidthHeightStyle(ctrl, panelInfo) }}>
<IconButton className={classes.iconButton} onClick={(event) => handleOnClick(event)} />
</div>
), [ctrl, isHighlighted.current, isUsedArduino, isEnabledSound])
), [ctrl, isUsedArduino, isEnabledSound, highLightedControlId])
}
const BindableImageButton = ({ctrl, panelInfo, showEncoder}) => {
const BindableImageButton = ({ctrl, panelInfo, showEncoder, highLightedControlId, highlightedControlChanged}) => {
const classes = useStyles();
const { simConnectData } = useSimConnectData();
const { isUsedArduino, isEnabledSound } = useLocalStorageData().configurationData;
const imagePath = getImagePath(panelInfo);
const isHighlighted = useRef(false);
const dataBindingValue = simConnectData[ctrl.binding?.variable];
const setupBackgroundImageStyle = () => {
@ -171,10 +168,10 @@ const BindableImageButton = ({ctrl, panelInfo, showEncoder}) => {
}
const handleOnClick = (event) => {
if (ctrl.highlight === undefined || ctrl.highlight) {
isHighlighted.current = true;
setTimeout(() => { isHighlighted.current = false; }, 2000);
}
if (ctrl.highlight === undefined || ctrl.highlight && highlightedControlChanged !== null)
highlightedControlChanged(ctrl.id);
else
highlightedControlChanged(null);
if (ctrl.action != null)
playSound(isEnabledSound);
@ -186,10 +183,10 @@ const BindableImageButton = ({ctrl, panelInfo, showEncoder}) => {
}
return useMemo(() =>(
<div className={[classes.controlBase, ctrl.highlight && isHighlighted.current ? classes.iconImageHighlight : ''].join(' ')} style={{ ...setupControlLocationStyle(ctrl, panelInfo), ...setupBackgroundImageStyle(ctrl, panelInfo, dataBindingValue), ...setupControlWidthHeightStyle(ctrl, panelInfo) }}>
<div className={[classes.controlBase, ctrl.highlight && highLightedControlId === ctrl.id ? classes.iconImageHighlight : ''].join(' ')} style={{ ...setupControlLocationStyle(ctrl, panelInfo), ...setupBackgroundImageStyle(ctrl, panelInfo, dataBindingValue), ...setupControlWidthHeightStyle(ctrl, panelInfo) }}>
<IconButton className={classes.iconButton} onClick={(event) => handleOnClick(event)} />
</div>
), [ctrl, dataBindingValue, isHighlighted.current, isUsedArduino, isEnabledSound])
), [ctrl, dataBindingValue, isUsedArduino, isEnabledSound, highLightedControlId])
}
const DigitDisplay = ({ctrl, panelInfo}) => {
@ -248,7 +245,7 @@ const TextBlock = ({ctrl, panelInfo}) => {
}
const InteractiveControlTemplate = ({ ctrl, panelInfo, showEncoder }) => {
const InteractiveControlTemplate = ({ ctrl, panelInfo, showEncoder, highLightedControlId, highlightedControlChanged }) => {
// preload all dynamic bindable images for control
useEffect(() => {
let imagePath = getImagePath(panelInfo);
@ -263,16 +260,16 @@ const InteractiveControlTemplate = ({ ctrl, panelInfo, showEncoder }) => {
}
}, [])
return (
return useMemo(() =>(
<>
{ctrl.type === 'image' &&
<ImageControl ctrl={ctrl} panelInfo={panelInfo}></ImageControl>
}
{ctrl.type === 'imageButton' &&
<ImageButton ctrl={ctrl} panelInfo={panelInfo} showEncoder={showEncoder}></ImageButton>
<ImageButton ctrl={ctrl} panelInfo={panelInfo} showEncoder={showEncoder} highLightedControlId={highLightedControlId} highlightedControlChanged={highlightedControlChanged}></ImageButton>
}
{ctrl.type === 'bindableImageButton' &&
<BindableImageButton ctrl={ctrl} panelInfo={panelInfo} showEncoder={showEncoder}></BindableImageButton>
<BindableImageButton ctrl={ctrl} panelInfo={panelInfo} showEncoder={showEncoder} highLightedControlId={highLightedControlId} highlightedControlChanged={highlightedControlChanged}></BindableImageButton>
}
{ctrl.type === 'digitDisplay' &&
<DigitDisplay ctrl={ctrl} panelInfo={panelInfo}></DigitDisplay>
@ -284,7 +281,7 @@ const InteractiveControlTemplate = ({ ctrl, panelInfo, showEncoder }) => {
<TextBlock ctrl={ctrl} panelInfo={panelInfo}></TextBlock>
}
</>
)
), [highLightedControlId])
}
export default InteractiveControlTemplate;

View file

@ -1,4 +1,4 @@
import React, {useState, useEffect} from 'react';
import React, {useState, useEffect, useMemo} from 'react';
import makeStyles from '@mui/styles/makeStyles';
import { MapContainer } from 'react-leaflet';
import { useSimConnectData } from '../Services/SimConnectDataProvider';
@ -20,7 +20,7 @@ const MapPanel = ({refresh}) => {
const { simConnectSystemEvent } = useSimConnectData();
const classes = useStyles();
const [reload, setReload] = useState(true);
useEffect(() =>{
if(simConnectSystemEvent !== null)
{
@ -31,7 +31,7 @@ const MapPanel = ({refresh}) => {
}
}, [simConnectSystemEvent])
return (
return useMemo(() => (
<div className={classes.root}>
{ reload &&
<MapContainer zoom={15} scrollWheelZoom={true} style={{ height: '100%'}}>
@ -39,7 +39,7 @@ const MapPanel = ({refresh}) => {
</MapContainer>
}
</div>
)
), [refresh])
}
export default MapPanel;

View file

@ -28,7 +28,7 @@ const useStyles = makeStyles((theme) => ({
}
}));
const PopoutPanelContainer = ({panelInfo}) => {
const PopoutPanelContainer = ({panelInfo, highLightedControlId, highlightedControlChanged}) => {
const { simConnectSystemEvent } = useSimConnectData();
const { isUsedArduino } = useLocalStorageData().configurationData;
const classes = useStyles(panelInfo);
@ -78,9 +78,11 @@ const PopoutPanelContainer = ({panelInfo}) => {
key={ctrl.id}
ctrl={ctrl}
panelInfo={panelInfo}
showEncoder={(e, useDualEncoder) => handleShowEncoder(e, useDualEncoder)}>
showEncoder={(e, useDualEncoder) => handleShowEncoder(e, useDualEncoder)}
highLightedControlId={highLightedControlId}
highlightedControlChanged={(ctrlId) => highlightedControlChanged(ctrlId)}
>
</InteractiveControlTemplate>
)}
</div>
}
@ -94,7 +96,7 @@ const PopoutPanelContainer = ({panelInfo}) => {
</KnobPadOverlay>
}
</div>
), [panelInfo, keyPadOpen, isUsedArduino])
), [panelInfo, keyPadOpen, isUsedArduino, highLightedControlId])
}
export default PopoutPanelContainer;

View file

@ -67,6 +67,7 @@ const WebPanel = ({ planeId, panelId }) => {
const classes = useStyles(useWindowDimensions())();
const [mapOpen, setMapOpen] = useState(false);
const [panelProfile, setPanelProfile] = useState();
const [highLightedControlId, setHighLightedControlId] = useState(null);
document.body.style.backgroundColor = 'transparent';
@ -128,9 +129,13 @@ const WebPanel = ({ planeId, panelId }) => {
<div className={classes.mapPanel} style={{ ...setupMapDisplayStyle() }}>
<MapPanel refresh={mapOpen} />
</div>
{panelProfile.subPanels.map(subPanel =>
{!mapOpen && panelProfile.subPanels.map(subPanel =>
<div key={subPanel.panelId} className={setupSubPanelClasses()} style={{...setupSubPanelDisplayStyle() , ...setupSubPanelLocationStyle(subPanel), ...setupSubPanelWidthHeightStyle(subPanel)} }>
<PopoutPanelContainer panelInfo={subPanel} />
<PopoutPanelContainer
panelInfo={subPanel}
highLightedControlId={highLightedControlId}
highlightedControlChanged={(ctrlId) => setHighLightedControlId(ctrlId)}
/>
</div>
)}
</div>

View file

@ -1,13 +1,14 @@
import React, { useEffect, useState, useRef, useMemo } from 'react';
import { useSimConnectData } from '../../Services/SimConnectDataProvider';
import { useSimConnectData, simConnectGetFlightPlan } from '../../Services/SimConnectDataProvider';
import { useLocalStorageData } from '../../Services/LocalStorageProvider';
import { useInterval } from '../Util/hooks';
import { LayersControl, TileLayer, useMapEvents } from 'react-leaflet'
import { LayersControl, LayerGroup, TileLayer, useMapEvents } from 'react-leaflet'
import L from 'leaflet';
import 'leaflet.marker.slideto';
import 'leaflet-marker-rotation';
import 'leaflet-easybutton';
import '@elfalem/leaflet-curve';
import { getDistance } from 'geolib';
import {BingLayer} from 'react-leaflet-bing-v2';
const MAP_TYPE_DEFAULTS = { zoomLevel: 12, flightFollowing: true, showFlightPlan: true, uiZoomFactor: 1, planeRadiusCircleRange: 2.5};
@ -74,6 +75,82 @@ const drawPlaneCircleRadius = (map, planePosition, scaleInNm = 2.5, isCreated, m
return marker;
}
const getControlPoints = (waypoints) => {
// This is to create two control points XXXX meters away any give waypoint to give flight path
// a smooth quadratic bezier curve transition. You can adjust the bezier curve radius with the constant below
const bezierCurveRadius = 100;
let controlPoints = [];
for (var i = 0; i < waypoints.length; i++) {
let prevWp = i === 0 ? null : waypoints[i - 1].latLong;
let curWp = waypoints[i].latLong;
let nextWp = i === waypoints.length - 1 ? null : waypoints[i + 1].latLong;
var distance1 = prevWp === null ? null : getDistance({ latitude: prevWp[0], longitude: prevWp[1] }, { latitude: curWp[0], longitude: curWp[1] });
var distance2 = nextWp === null ? null : getDistance({ latitude: curWp[0], longitude: curWp[1] }, { latitude: nextWp[0], longitude: nextWp[1] });
let ratio1 = (distance1 / bezierCurveRadius) > 2 ? (distance1 / bezierCurveRadius) : 2;
let ratio2 = (distance2 / bezierCurveRadius) > 2 ? (distance2 / bezierCurveRadius) : 2;
var p1 = prevWp === null ? null : [(prevWp[0] - curWp[0]) / ratio1 + curWp[0], (prevWp[1] - curWp[1]) / ratio1 + curWp[1]];
var p2 = nextWp === null ? null : [(nextWp[0] - curWp[0]) / ratio2 + curWp[0], (nextWp[1] - curWp[1]) / ratio2 + curWp[1]];
controlPoints.push({ p1: p1, p2: p2 });
}
return controlPoints;
}
const drawFlightPath = (waypoints, layerGroup, scaleInNm) => {
let scale = 2.5 / scaleInNm;
let path, line, marker, tooltip;
let controlPoints = getControlPoints(waypoints);
waypoints.forEach((waypoint, index) => {
let controlPoint = controlPoints[index];
let nextControlPoint = controlPoints[index + 1];
let lineColor = Boolean(waypoint.isActiveLeg) ? 'magenta' : 'magenta'
// First waypoint
if (controlPoint.p1 === null) {
path = [waypoint.latLong, controlPoint.p2];
line = new L.Polyline(path, {color: lineColor});
layerGroup.addLayer(line);
}
// Last waypoint
else if (controlPoint.p2 === null) {
path = [controlPoint.p1, waypoint.latLong];
line = new L.Polyline(path, {color: lineColor});
layerGroup.addLayer(line);
}
// All other waypoints inbetween, draw bezier curve
else {
path = ['M', controlPoint.p1, 'Q', waypoint.latLong, controlPoint.p2];
line = L.curve(path, {color: 'white'});
layerGroup.addLayer(line);
}
// Waypoint marker
tooltip = getFullmapTooltip(waypoint);
marker = L.circleMarker(waypoint.latLong, {radius: 6, color: 'purple'}).bindTooltip(tooltip, { permanent: true, interactive: true, offset: [-50 * scale, -15 * scale] });
layerGroup.addLayer(marker);
// Draw inbetween control points line
if (index < waypoints.length - 1) {
path = [controlPoint.p2, nextControlPoint.p1];
line = new L.Polyline(path, {color: lineColor});
layerGroup.addLayer(line);
}
})
}
const getFullmapTooltip = (waypoint) => {
let tooltip = `<div style="padding: 3px; font-size:1em; font-weight: bold">${waypoint.id}</div>`;
return tooltip;
}
const centerPlaneToMap = (map, mapPosition, planePosition) => {
mapPosition.current = planePosition.current;
if (mapPosition.current !== null)
@ -103,7 +180,7 @@ const formatLatLong = (lat, lon) => {
let centerPlaneIcon, flightFollowingIcon, showFlightPlanIcon;
const MapDisplay = ({refresh}) => {
const { simConnectData } = useSimConnectData();
const { simConnectData, simConnectSystemEvent } = useSimConnectData();
const { PLANE_HEADING_TRUE, GPS_LAT, GPS_LON } = simConnectData;
const { mapConfig, configurationData } = useLocalStorageData();
const { mapRefreshInterval } = configurationData;
@ -111,6 +188,7 @@ const MapDisplay = ({refresh}) => {
const [ mapDefaults ] = useState(MAP_TYPE_DEFAULTS);
const [ flightFollowing, setFlightFollowing ] = useState(MAP_TYPE_DEFAULTS.flightFollowing);
const [ showFlightPlan, setShowFlightPlan ] = useState(MAP_TYPE_DEFAULTS.showFlightPlan);
const [ waypoints, setWaypoints] = useState([]);
const planePosition = useRef(formatLatLong(GPS_LAT, GPS_LON));
const mapPosition = useRef(formatLatLong(GPS_LAT, GPS_LON));
@ -236,6 +314,27 @@ const MapDisplay = ({refresh}) => {
}
}, [flightFollowing, planePosition.current])
useEffect(async() => {
let data = await simConnectGetFlightPlan();
if (data !== undefined && data !== null) {
setWaypoints(data.waypoints);
}
}, [simConnectSystemEvent, refresh])
useEffect(() => {
if(waypoints != null && waypoints.length > 1)
{
layerGroupFlightPlan.current.clearLayers();
if(showFlightPlan)
drawFlightPath(waypoints, layerGroupFlightPlan.current, mapDefaults.planeRadiusCircleRange)
else
layerGroupFlightPlan.current.clearLayers();
}
}, [showFlightPlan, waypoints])
return useMemo(() => (
<LayersControl>
<LayersControl.BaseLayer name='Open Topo' checked={activeMapLayer.current === 'Open Topo'}>
@ -271,6 +370,9 @@ const MapDisplay = ({refresh}) => {
<LayersControl.Overlay name='Aviation'>
<TileLayer url={MAP_PROVIDER.aviation} tms={true} detectRetina={true} subdomains='12' />
</LayersControl.Overlay>
<LayersControl.Overlay checked={showFlightPlan} name='Flight Plan'>
<LayerGroup id='lgFlightPlan' ref={layerGroupFlightPlan} />
</LayersControl.Overlay>
</LayersControl>
), [showFlightPlan, flightFollowing, layerGroupFlightPlan.current])
}

View file

@ -14,8 +14,6 @@ const LocalStorageProvider = ({ initialData, children }) => {
// Set default map config
setMapConfig({
flightFollowing: true,
showFlightPlan: true,
showFlightPlanLabel: false,
currentLayer: 'Bing Roads'
});

View file

@ -156,4 +156,16 @@ export const getLocalPopoutPanelDefinitions = async (panelRootPath, subPanelRoot
console.error('Unable to retrieve pop out panel definitions. There may be an error with PopoutPanelDefinition.json.')
return null;
}
}
export const simConnectGetFlightPlan = async () => {
try {
let response = await fetch(`${API_URL.url}/getflightplan`);
let result = await response.json();
return result;
}
catch {
console.error('MSFS unable to load flight plan.')
}
}

View file

@ -1,5 +1,4 @@
using MSFSPopoutPanelManager.Shared;
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
@ -176,19 +175,19 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
private void HandleSimConnected(object source, EventArgs e)
{
OnConnected?.Invoke(this, null);
// Start data request timer
_requestDataTimer = new System.Timers.Timer();
_requestDataTimer.Interval = MSFS_DATA_REFRESH_TIMEOUT;
_requestDataTimer.Enabled = true;
_requestDataTimer.Elapsed += HandleDataRequested;
_requestDataTimer.Elapsed += HandleMessageReceived;
OnConnected?.Invoke(this, null);
}
private void HandleSimDisonnected(object source, EventArgs e)
{
FileLogger.WriteLog($"MSFS is closed.", StatusMessageType.Info);
_requestDataTimer.Enabled = false;
OnDisconnected?.Invoke(this, null);
StopAndReconnect();
}

View file

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
namespace MSFSPopoutPanelManager.SimConnectAgent
@ -15,6 +16,7 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
private SimConnect _simConnect;
private Timer _connectionTimer;
private bool _isDisabledReconnect;
public event EventHandler<string> OnException;
public event EventHandler<List<SimConnectDataDefinition>> OnReceivedData;
@ -79,7 +81,8 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
foreach (var definition in SimConnectDataDefinitions)
{
_simConnect.RequestDataOnSimObjectType(definition.RequestId, definition.DefineId, 0, SIMCONNECT_SIMOBJECT_TYPE.USER);
if (definition.DataDefinitionType == DataDefinitionType.SimConnect)
_simConnect.RequestDataOnSimObjectType(definition.RequestId, definition.DefineId, 0, SIMCONNECT_SIMOBJECT_TYPE.USER);
}
}
@ -90,14 +93,20 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
try
{
_simConnect.ReceiveMessage();
if (!_isDisabledReconnect)
_simConnect.ReceiveMessage();
}
catch (Exception ex)
{
if (ex.Message != "0xC00000B0")
{
FileLogger.WriteLog($"SimConnector: SimConnect receive message exception - {ex.Message}", StatusMessageType.Error);
}
if (!_isDisabledReconnect)
{
// Prevent multiple reconnects from running
_isDisabledReconnect = true;
// Need to stop and reconnect server since the data is SimConnect connection or data is probably corrupted.
StopAndReconnect();
}
@ -167,16 +176,21 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
AddDataDefinitions();
for (var i = 0; i < 5; i++)
{
System.Threading.Thread.Sleep(1000);
ReceiveMessage();
}
_simConnect.RequestSystemState(SystemStateRequestId.AIRCRAFTPATH, "AircraftLoaded");
Connected = true;
_isDisabledReconnect = false;
Task.Run(() =>
{
for (var i = 0; i < 5; i++)
{
System.Threading.Thread.Sleep(1000);
ReceiveMessage();
}
});
OnConnected?.Invoke(this, null);
Connected = true;
StatusMessageWriter.WriteMessage("MSFS is connected", StatusMessageType.Info, false);
}
@ -312,10 +326,16 @@ namespace MSFSPopoutPanelManager.SimConnectAgent
private void SetActiveAircraftTitle(string aircraftFilePath)
{
var filePathToken = aircraftFilePath.Split(@"\");
var aircraftName = filePathToken[filePathToken.Length - 2];
aircraftName = aircraftName.Replace("_", " ").ToUpper();
SimConnectDataDefinitions.Find(s => s.PropName == "AircraftName").Value = aircraftName;
if (filePathToken.Length > 1)
{
var aircraftName = filePathToken[filePathToken.Length - 2];
aircraftName = aircraftName.Replace("_", " ").ToUpper();
SimConnectDataDefinitions.Find(s => s.PropName == "AircraftName").Value = aircraftName;
OnReceivedData?.Invoke(this, SimConnectDataDefinitions);
}
}
}
}

View file

@ -32,6 +32,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CoordinateSharp" Version="2.13.1.1" />
<PackageReference Include="ini-parser-netcore3.1" Version="3.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="StringMath" Version="3.0.2" />
</ItemGroup>

View file

@ -3,6 +3,8 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Timers;
namespace MSFSPopoutPanelManager.SimConnectAgent.TouchPanel
@ -44,6 +46,29 @@ namespace MSFSPopoutPanelManager.SimConnectAgent.TouchPanel
}
}
public string GetFlightPlan()
{
// MSFS 2020 Windows Store version: C:\Users\{username}\AppData\Local\Packages\Microsoft.FlightSimulator_8wekyb3d8bbwe\LocalState\MISSIONS\Custom\CustomFlight\CustomFlight.FLT
// MSFS 2020 Steam version: C:\Users\{username}\AppData\Roaming\Microsoft Flight Simulator\MISSIONS\Custom\CustomFlight\CustomFlight.FLT
var filePathMSStore = Environment.ExpandEnvironmentVariables("%LocalAppData%") + @"\Packages\Microsoft.FlightSimulator_8wekyb3d8bbwe\LocalState\MISSIONS\Custom\CustomFlight\CustomFlight.FLT";
var filePathSteam = Environment.ExpandEnvironmentVariables("%AppData%") + @"\Microsoft Flight Simulator\MISSIONS\Custom\CustomFlight\CustomFlight.FLT";
string filePath;
if (File.Exists(filePathMSStore))
filePath = filePathMSStore;
else if (File.Exists(filePathSteam))
filePath = filePathSteam;
else
filePath = null;
// cannot find CustomFlight.PLN, return empty set of waypoints
if (filePath == null)
return JsonConvert.SerializeObject(new List<ExpandoObject>());
return FlightPlanProvider.ParseCustomFLT(filePath);
}
private void HandleDataRequested(object sender, ElapsedEventArgs e)
{
try

View file

@ -0,0 +1,129 @@
using CoordinateSharp;
using IniParser;
using IniParser.Model;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace MSFSPopoutPanelManager.SimConnectAgent.TouchPanel
{
public class FlightPlanProvider
{
public static string ParseCustomFLT(string filePath)
{
var wayPoints = new List<ATCWaypoint>();
try
{
var parser = new FileIniDataParser();
var data = parser.ReadFile(filePath);
var isActiveFlightPlan = Convert.ToBoolean(data["ATC_Aircraft.0"]["ActiveFlightPlan"]);
var isRequestedFlightPlan = Convert.ToBoolean(data["ATC_Aircraft.0"]["RequestedFlightPlan"]);
PropertyCollection flightPlan;
// If FLT file has both requested and active flight plan set to true, requested flight plan takes precedence
if (isRequestedFlightPlan)
{
flightPlan = data["ATC_RequestedFlightPlan.0"];
}
else if (isActiveFlightPlan)
{
flightPlan = data["ATC_ActiveFlightPlan.0"];
}
else
{
return JsonConvert.SerializeObject(wayPoints);
}
if (flightPlan != null)
{
int i = 0;
Coordinate c;
while (true)
{
var waypointData = flightPlan["waypoint." + i];
if (waypointData == null)
break;
var waypointArr = waypointData.Split(",");
var waypoint = new ATCWaypoint();
waypoint.index = i + 1;
waypoint.type = waypointArr[4].Trim();
waypoint.description = waypointArr[3].Trim();
// exclude unnecessary user waypoints
if (!(waypoint.type == "V" || (waypoint.type == "U" && (waypoint.description == "TIMECLIMB" || waypoint.description == "TIMECRUIS" || waypoint.description == "TIMEDSCNT" || waypoint.description == "TIMEAPPROACH" || waypoint.description == "TIMEVERT"))))
{
waypoint.id = waypoint.type == "U" ? waypoint.description : waypointArr[1].Trim();
// parse "+003000.00" - 3000ft
var alt = Convert.ToInt32(waypointArr[17].Trim());
waypoint.altitude = alt == 0 ? null : alt;
var spd = Convert.ToInt32(waypointArr[16].Trim());
waypoint.maxSpeed = spd == 0 ? null : spd;
Coordinate.TryParse(waypointArr[5].Trim() + " " + waypointArr[6].Trim(), out c);
waypoint.latLong = new double[] { c.Latitude.DecimalDegree, c.Longitude.DecimalDegree };
//waypoint.departureProcedure = String.IsNullOrEmpty(waypointArr[9].Trim()) ? null : waypointArr[9].Trim();
//waypoint.arrivalProcedure = String.IsNullOrEmpty(waypointArr[10].Trim()) ? null : waypointArr[10].Trim();
//waypoint.approachType = String.IsNullOrEmpty(waypointArr[11].Trim()) ? null : waypointArr[11].Trim();
//waypoint.approachRunway = String.IsNullOrEmpty(waypointArr[12].Trim()) ? null : waypointArr[12].Trim();
wayPoints.Add(waypoint);
}
i++;
}
}
return JsonConvert.SerializeObject(new FlightPlan() { waypoints = wayPoints, activeLegIndex = 0 });
}
catch (Exception)
{
return JsonConvert.SerializeObject(wayPoints);
}
}
public class FlightPlan
{
public int activeLegIndex { get; set; }
public List<ATCWaypoint> waypoints { get; set; }
public int dtk { get; set; }
}
public class ATCWaypoint
{
public string id { get; set; }
public string description { get; set; }
public int index { get; set; }
[JsonProperty("type")]
public string type { get; set; }
public double[] latLong { get; set; }
public double[] startLatLong { get; set; }
public int? altitude { get; set; }
public int? maxSpeed { get; set; }
public double? distance { get; set; }
public int? course { get; set; }
public bool isActiveLeg { get; set; }
}
}
}

View file

@ -70,6 +70,12 @@ namespace MSFSPopoutPanelManager.SimConnectAgent.TouchPanel
_simConnector.ResetSimConnectDataArea(planeId);
}
public string GetFlightPlan()
{
return _dataProvider.GetFlightPlan();
}
private void InitializeProviders()
{
_dataProvider = new DataProvider(_simConnector);

View file

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace MSFSPopoutPanelManager.SimConnectAgent.TouchPanel
{
@ -147,7 +148,6 @@ namespace MSFSPopoutPanelManager.SimConnectAgent.TouchPanel
private void InitializeSimConnect()
{
// The constructor is similar to SimConnect_Open in the native API
_simConnect = new SimConnect("TouchPanel Simconnect - Touch Panel Server", Process.GetCurrentProcess().MainWindowHandle, WM_USER_SIMCONNECT, null, 0);
_connectionTimer.Enabled = false;
@ -167,11 +167,28 @@ namespace MSFSPopoutPanelManager.SimConnectAgent.TouchPanel
_simConnect.UnsubscribeFromSystemEvent(SimConnectSystemEvent.VIEW);
_simConnect.SubscribeToSystemEvent(SimConnectSystemEvent.VIEW, "View");
for (var i = 0; i < 5; i++)
Task.Run(() =>
{
System.Threading.Thread.Sleep(1000);
ReceiveMessage();
}
for (var i = 0; i < 5; i++)
{
System.Threading.Thread.Sleep(1000);
ReceiveMessage();
}
});
MobilFlightInitialize();
if (_planeId != null)
ResetSimConnectDataArea(_planeId);
// MobiFlight wasm event
_simConnect.OnRecvClientData -= HandleOnRecvClientData;
_simConnect.OnRecvClientData += HandleOnRecvClientData;
Connected = true;
OnConnected?.Invoke(this, null);
MobiFlightWasmClient.Ping(_simConnect);
}
private void AddDataDefinitions()
@ -215,19 +232,7 @@ namespace MSFSPopoutPanelManager.SimConnectAgent.TouchPanel
private void HandleOnRecvOpen(SimConnect sender, SIMCONNECT_RECV_OPEN data)
{
MobilFlightInitialize();
if (_planeId != null)
ResetSimConnectDataArea(_planeId);
// MobiFlight wasm event
_simConnect.OnRecvClientData -= HandleOnRecvClientData;
_simConnect.OnRecvClientData += HandleOnRecvClientData;
Connected = true;
OnConnected?.Invoke(this, null);
MobiFlightWasmClient.Ping(_simConnect);
ReceiveMessage();
}
private void HandleOnRecvQuit(SimConnect sender, SIMCONNECT_RECV data)

View file

@ -152,7 +152,7 @@ namespace MSFSPopoutPanelManager.UserDataAgent
// Default values
EnableTouchPanelIntegration = false;
DataRefreshInterval = 200;
MapRefreshInterval = 250;
MapRefreshInterval = 1000;
UseArduino = false;
EnableSound = true;
}

View file

@ -43,7 +43,7 @@ namespace MSFSPopoutPanelManager.WebServer.Controllers
}
catch
{
return new SimConnectData { Data = null, MsfsStatus = false, ArduinoStatus = false, SystemEvent = null, G1000NxiFlightPlan = null };
return new SimConnectData { Data = null, MsfsStatus = false, ArduinoStatus = false, SystemEvent = null };
}
}
@ -99,6 +99,12 @@ namespace MSFSPopoutPanelManager.WebServer.Controllers
return new TouchPanelConfigSetting();
}
[HttpGet("/getflightplan")]
public string GetFlightPlan()
{
return _simConnectService.GetFlightPlan();
}
}
public class SimConnectData
@ -110,8 +116,6 @@ namespace MSFSPopoutPanelManager.WebServer.Controllers
public bool ArduinoStatus { get; set; }
public string SystemEvent { get; set; }
public string G1000NxiFlightPlan { get; set; }
}
public class TouchPanelLoadedPostData

View file

@ -73,6 +73,11 @@ namespace MSFSPopoutPanelManager.WebServer
{
_simConnectorProvider.ResetSimConnectDataArea(planeId);
}
public string GetFlightPlan()
{
return _simConnectorProvider.GetFlightPlan();
}
}
public interface ISimConnectService
@ -87,6 +92,8 @@ namespace MSFSPopoutPanelManager.WebServer
public void ResetSimConnectDataArea(string planeId);
public string GetFlightPlan();
public TouchPanelConfigSetting TouchPanelConfigSetting { get; set; }
}
}

View file

@ -24,10 +24,6 @@ namespace MSFSPopoutPanelManager.WindowsAgent
public static void ApplyAlwaysOnTop(IntPtr hwnd, 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 (panelType == PanelType.PopOutManager)
{
OnPopOutManagerAlwaysOnTopChanged?.Invoke(null, alwaysOnTop);
@ -35,9 +31,9 @@ namespace MSFSPopoutPanelManager.WindowsAgent
else
{
if (alwaysOnTop)
PInvoke.SetWindowPos(hwnd, new IntPtr(PInvokeConstant.HWND_TOPMOST), panelRectangle.Left, panelRectangle.Top, newWidth, newHeight, PInvokeConstant.SWP_ALWAYS_ON_TOP);
PInvoke.SetWindowPos(hwnd, new IntPtr(PInvokeConstant.HWND_TOPMOST), panelRectangle.Left, panelRectangle.Top, panelRectangle.Width, panelRectangle.Height, PInvokeConstant.SWP_ALWAYS_ON_TOP);
else
PInvoke.SetWindowPos(hwnd, new IntPtr(PInvokeConstant.HWND_NOTOPMOST), panelRectangle.Left, panelRectangle.Top, newWidth, newHeight, 0);
PInvoke.SetWindowPos(hwnd, new IntPtr(PInvokeConstant.HWND_NOTOPMOST), panelRectangle.Left, panelRectangle.Top, panelRectangle.Width, panelRectangle.Height, 0);
}
}
@ -64,24 +60,16 @@ namespace MSFSPopoutPanelManager.WindowsAgent
PInvoke.MoveWindow(hwnd, x, y, rectangle.Width, rectangle.Height, true);
}
public static void MoveWindow(IntPtr hwnd, PanelType panelType, int x, int y, int width, int height)
public static void MoveWindow(IntPtr hwnd, 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(hwnd, x, y, newWidth, newHeight, true);
PInvoke.MoveWindow(hwnd, x, y, width, height, true);
}
public static void MoveWindowWithMsfsBugOverrirde(IntPtr hwnd, PanelType panelType, int x, int y, int width, int height)
public static void MoveWindowWithMsfsBugOverrirde(IntPtr hwnd, 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(hwnd, x, y, newWidth, newHeight, true);
PInvoke.MoveWindow(hwnd, x, y, width, height, 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
@ -91,7 +79,7 @@ namespace MSFSPopoutPanelManager.WindowsAgent
PInvoke.GetWindowRect(hwnd, out rectangle);
if (rectangle.Left != originalX)
PInvoke.MoveWindow(hwnd, originalX, y, newWidth, newHeight, false);
PInvoke.MoveWindow(hwnd, originalX, y, width, height, false);
}
public static void MinimizeWindow(IntPtr hwnd)

View file

@ -43,7 +43,7 @@ namespace MSFSPopoutPanelManager.WpfApp
var x = Convert.ToInt32(rectangle.X + clientRectangle.Width / 2 - this.Width / 2);
var y = Convert.ToInt32(rectangle.Y + clientRectangle.Height / 2 - this.Height / 2);
WindowActionManager.MoveWindow(dialogHandle, PanelType.WPFWindow, x, y, Convert.ToInt32(this.Width), Convert.ToInt32(this.Height));
WindowActionManager.MoveWindow(dialogHandle, x, y, Convert.ToInt32(this.Width), Convert.ToInt32(this.Height));
}
else
{

View file

@ -56,7 +56,7 @@ namespace MSFSPopoutPanelManager.WpfApp
{
// Fixed broken window left/top coordinate for DPI Awareness Per Monitor
var handle = new WindowInteropHelper(this).Handle;
WindowActionManager.MoveWindow(handle, PanelType.WPFWindow, _xCoor, _yCoor, Convert.ToInt32(this.Width), Convert.ToInt32(this.Height));
WindowActionManager.MoveWindow(handle, _xCoor, _yCoor, Convert.ToInt32(this.Width), Convert.ToInt32(this.Height));
WindowActionManager.ApplyAlwaysOnTop(handle, PanelType.WPFWindow, true);
}

View file

@ -260,7 +260,7 @@
<Line Stretch="Fill" Stroke="Gray" X2="1"/>
<WrapPanel>
<mah:NumericUpDown Width="100" Minimum="0" Maximum="50" Interval="25" FontSize="16" Height="32" Value="{Binding AppSettingData.AppSetting.TouchScreenSettings.TouchDownUpDelay, Mode=TwoWay}"></mah:NumericUpDown>
<AccessText Margin="10,0,0,0" Width="490">Amount of time to delay touch down and then touch up event when operating touch enabled panel. If your touch is not registering consistently, increasing this value will help.</AccessText>
<AccessText Margin="10,0,0,0" Width="490">Amount of time in milliseconds to delay touch down and then touch up event when operating touch enabled panel. If your touch is not registering consistently, increasing this value will help.</AccessText>
<TextBlock FontSize="14" TextWrapping="Wrap" Margin="0,10,0,0">
For panel display on direct connected touch monitor, 0 milliseconds work really well.<LineBreak/><LineBreak/>
@ -310,8 +310,8 @@
<TextBlock Style="{StaticResource TextBlockHeading}">Map Refresh Interval</TextBlock>
<Line Stretch="Fill" Stroke="Gray" X2="1"/>
<WrapPanel>
<mah:NumericUpDown Width="100" Minimum="200" Maximum="3000" Interval="50" FontSize="16" Height="32" Value="{Binding AppSettingData.AppSetting.TouchPanelSettings.MapRefreshInterval, Mode=TwoWay}"></mah:NumericUpDown>
<AccessText Margin="10,0,0,0" Width="490">Time interval for touch panel's map to refresh SimConnect data. (Default: 250 miliseconds)</AccessText>
<mah:NumericUpDown Width="100" Minimum="200" Maximum="3000" Interval="200" FontSize="16" Height="32" Value="{Binding AppSettingData.AppSetting.TouchPanelSettings.MapRefreshInterval, Mode=TwoWay}"></mah:NumericUpDown>
<AccessText Margin="10,0,0,0" Width="490">Time interval for touch panel's map to refresh SimConnect data. (Default: 1000 miliseconds)</AccessText>
</WrapPanel>
</WrapPanel>
</WrapPanel>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -35,12 +35,16 @@
<Grid KeyboardNavigation.DirectionalNavigation="None">
<DockPanel>
<WrapPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="19,10,0,0" HorizontalAlignment="Left">
<Label Content="Panel Locations and Settings -" Margin="0,0,0,0" HorizontalAlignment="Left"/>
<Label Content="{Binding ProfileData.ActiveProfile.ProfileName}" Margin="0,0,0,0" HorizontalAlignment="Left"/>
<controls:Tile Foreground="LightSkyBlue" Background="Transparent" HorizontalAlignment="Right" VerticalAlignment="Center" Width="30" Height="30"
controls:ControlsHelper.MouseOverBorderBrush="{DynamicResource MahApps.Brushes.Button.Border}" Margin="565,0,0,0" Click="Instruction_Click" >
<iconPacks:PackIconMaterial Width="22" Height="22" Kind="Information" VerticalAlignment="Center"></iconPacks:PackIconMaterial>
</controls:Tile>
<WrapPanel Width="850">
<Label Content="Panel Locations and Settings -" Margin="0,0,0,0" HorizontalAlignment="Left"/>
<Label Content="{Binding ProfileData.ActiveProfile.ProfileName}" Margin="0,0,0,0" HorizontalAlignment="Left"/>
</WrapPanel>
<WrapPanel>
<controls:Tile Foreground="LightSkyBlue" Background="Transparent" VerticalAlignment="Center" Width="30" Height="30"
controls:ControlsHelper.MouseOverBorderBrush="{DynamicResource MahApps.Brushes.Button.Border}" Click="Instruction_Click" >
<Image Width="22" Height="22" VerticalAlignment="Center" Source="Resources\info_icon.png"></Image>
</controls:Tile>
</WrapPanel>
<DataGrid Name="PanelConfigGrid" HorizontalAlignment="Center" Width="882" Height="430" Margin="0 10 0 0" AutoGenerateColumns="False" CanUserResizeColumns="False" HorizontalScrollBarVisibility="Disabled"
CanUserReorderColumns="False" CanUserResizeRows="False" HorizontalGridLinesBrush="#B9B9B9" VerticalGridLinesBrush="#B9B9B9" GridLinesVisibility="Horizontal" SelectionUnit="Cell"
BorderThickness="1" CanUserAddRows="False" CanUserSortColumns="False" KeyboardNavigation.TabNavigation="None" KeyboardNavigation.IsTabStop="False"

View file

@ -127,6 +127,9 @@ namespace MSFSPopoutPanelManager.WpfApp
private void PanelConfigGrid_KeyDown(object sender, KeyEventArgs e)
{
if (_panelConfigurationViewModel.SelectedPanelConfigItem == null)
return;
var isControlKeyHeld = (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control;
var isShiftKeyHeld = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;

View file

@ -40,36 +40,40 @@ namespace MSFSPopoutPanelManager.WpfApp.ViewModel
.ObservesProperty(() => ProfileData.ActiveProfile.BindingAircrafts.Count)
.ObservesProperty(() => FlightSimData.IsSimulatorStarted);
SetPowerOnRequiredCommand = new DelegateCommand(() => ProfileData.WriteProfiles(), () => ProfileData.HasActiveProfile && FlightSimData.IsSimulatorStarted)
SetPowerOnRequiredCommand = new DelegateCommand(() => ProfileData.WriteProfiles(), () => FlightSimData.HasCurrentMsfsAircraft && ProfileData.HasActiveProfile && FlightSimData.IsSimulatorStarted)
.ObservesProperty(() => FlightSimData.HasCurrentMsfsAircraft)
.ObservesProperty(() => ProfileData.ActiveProfile)
.ObservesProperty(() => FlightSimData.IsSimulatorStarted);
StartPanelSelectionCommand = new DelegateCommand(OnStartPanelSelection, () => ProfileData.HasActiveProfile && ProfileData.ActiveProfile != null && FlightSimData.IsSimulatorStarted)
StartPanelSelectionCommand = new DelegateCommand(OnStartPanelSelection, () => FlightSimData.HasCurrentMsfsAircraft && ProfileData.HasActiveProfile && ProfileData.ActiveProfile != null && FlightSimData.IsSimulatorStarted)
.ObservesProperty(() => FlightSimData.HasCurrentMsfsAircraft)
.ObservesProperty(() => ProfileData.ActiveProfile)
.ObservesProperty(() => FlightSimData.IsSimulatorStarted);
StartPopOutCommand = new DelegateCommand(OnStartPopOut, () => ProfileData.HasActiveProfile && (ProfileData.ActiveProfile.PanelSourceCoordinates.Count > 0 || ProfileData.ActiveProfile.TouchPanelBindings.Count > 0) && FlightSimData.IsSimulatorStarted)
StartPopOutCommand = new DelegateCommand(OnStartPopOut, () => FlightSimData.HasCurrentMsfsAircraft && ProfileData.HasActiveProfile && (ProfileData.ActiveProfile.PanelSourceCoordinates.Count > 0 || ProfileData.ActiveProfile.TouchPanelBindings.Count > 0) && FlightSimData.IsSimulatorStarted)
.ObservesProperty(() => FlightSimData.HasCurrentMsfsAircraft)
.ObservesProperty(() => ProfileData.ActiveProfile)
.ObservesProperty(() => ProfileData.ActiveProfile.PanelSourceCoordinates.Count)
.ObservesProperty(() => ProfileData.ActiveProfile.TouchPanelBindings.Count)
.ObservesProperty(() => FlightSimData.IsSimulatorStarted);
SaveAutoPanningCameraCommand = new DelegateCommand(OnSaveAutoPanningCamera, () => ProfileData.HasActiveProfile && ProfileData.ActiveProfile.PanelSourceCoordinates.Count > 0 && FlightSimData.IsSimulatorStarted)
SaveAutoPanningCameraCommand = new DelegateCommand(OnSaveAutoPanningCamera, () => FlightSimData.HasCurrentMsfsAircraft && ProfileData.HasActiveProfile && ProfileData.ActiveProfile.PanelSourceCoordinates.Count > 0 && FlightSimData.IsSimulatorStarted)
.ObservesProperty(() => FlightSimData.HasCurrentMsfsAircraft)
.ObservesProperty(() => ProfileData.ActiveProfile)
.ObservesProperty(() => ProfileData.ActiveProfile.PanelSourceCoordinates.Count)
.ObservesProperty(() => FlightSimData.IsSimulatorStarted);
EditPanelSourceCommand = new DelegateCommand(EditPanelSource, () => ProfileData.HasActiveProfile && ProfileData.ActiveProfile.PanelSourceCoordinates.Count > 0 && FlightSimData.IsSimulatorStarted)
EditPanelSourceCommand = new DelegateCommand(EditPanelSource, () => FlightSimData.HasCurrentMsfsAircraft && ProfileData.HasActiveProfile && ProfileData.ActiveProfile.PanelSourceCoordinates.Count > 0 && FlightSimData.IsSimulatorStarted)
.ObservesProperty(() => FlightSimData.HasCurrentMsfsAircraft)
.ObservesProperty(() => ProfileData.ActiveProfile)
.ObservesProperty(() => ProfileData.ActiveProfile.PanelSourceCoordinates.Count)
.ObservesProperty(() => FlightSimData.IsSimulatorStarted);
OpenTouchPanelBindingCommand = new DelegateCommand(OnOpenTouchPanelBinding, () => FlightSimData.HasCurrentMsfsAircraft && FlightSimData.IsSimulatorStarted && ProfileData.HasActiveProfile && AppSettingData.AppSetting.TouchPanelSettings.EnableTouchPanelIntegration)
.ObservesProperty(() => FlightSimData.HasCurrentMsfsAircraft)
.ObservesProperty(() => ProfileData.HasActiveProfile)
.ObservesProperty(() => AppSettingData.AppSetting.TouchPanelSettings.EnableTouchPanelIntegration)
.ObservesProperty(() => FlightSimData.HasCurrentMsfsAircraft)
.ObservesProperty(() => FlightSimData.IsSimulatorStarted);
;
TouchPanelBindingViewModel = new TouchPanelBindingViewModel(_orchestrator);
}

View file

@ -28,6 +28,10 @@
<Configurations>Debug;Release;DebugTouchPanel;ReleaseTouchPanel</Configurations>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\info_icon.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="logo.ico" />
</ItemGroup>
@ -45,6 +49,9 @@
<Resource Include="Resources\info.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Resources\info_icon.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="resources\logo.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
@ -62,15 +69,16 @@
<ItemGroup>
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.0" />
<PackageReference Include="CalcBinding" Version="2.5.2" />
<PackageReference Include="CoordinateSharp" Version="2.13.1.1" />
<PackageReference Include="Fody" Version="6.6.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="1.1.0" />
<PackageReference Include="ini-parser-netcore3.1" Version="3.0.0" />
<PackageReference Include="InputSimulatorCore" Version="1.0.5" />
<PackageReference Include="log4net" Version="2.0.14" />
<PackageReference Include="MahApps.Metro" Version="2.4.9" />
<PackageReference Include="MahApps.Metro.IconPacks" Version="4.11.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Prism.Core" Version="8.1.97" />