Page 1 of 1

Multiple Inputs (Splitscreen)

Posted: 10 Sep 2019, 16:23
by ScottFromDerby
Hi!

We are looking to allow multiple users to interact with a subsection of the screen in our Windows app. I.e. a splitscreen pause menu, where 2 or more players can interact with their own menu independently and simultaneously. We have one xaml and multiple viewmodels:
<Window
	x:Class="OurProject.TestWindow"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
	xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
	xmlns:local="clr-namespace:OurProject"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	xmlns:noesis="clr-namespace:NoesisGUIExtensions"
	mc:Ignorable="d"
	Title="TestWindow"
	Background="{x:Null}">
	<Viewbox
		Margin="0">
		<Grid x:Name="mainCanvas" Width="1920">
			<Grid.ColumnDefinitions>
				<ColumnDefinition Width="*"/>
				<ColumnDefinition Width="*"/>
			</Grid.ColumnDefinitions>

			<Viewbox Name="StackPanelContainer1" HorizontalAlignment="Left" Grid.Column="0">
				<StackPanel Name="StackPanel1" Margin="10" Width="80">
					<ToggleButton Name="Btn11" Content="Left 1"/>
					<ToggleButton Name="Btn12" Content="Left 2"/>
					<ToggleButton Name="Btn13" Content="Left 3"/>
					<ToggleButton Name="Btn14" Content="Left 4"/>
					<ToggleButton Name="Btn15" Content="Left 5"/>
				</StackPanel>
			</Viewbox>
			<Viewbox Name="StackPanelContainer2" HorizontalAlignment="Right" Grid.Column="1">
				<StackPanel Name="StackPanel2" Margin="10" Width="50">
					<ToggleButton Name="Btn21" Content="Right 1"/>
					<ToggleButton Name="Btn22" Content="Right 2"/>
					<ToggleButton Name="Btn23" Content="Right 3"/>
				</StackPanel>
			</Viewbox>
		</Grid>
	</Viewbox>
</Window>
This produces a window with two stack panels. I want to forward inputs from one gamepad to one window, and the other to the other.

Here is how I am attempting to do this. I parse the xaml, create one view for the whole screen using Noesis::GUI::CreateView(), then create 2 more views, passing in StackPanelContainer1 and StackPanelContainer2 as root.

			
			OurProject* pTheMainWindow = [...]; // : public Noesis::ContentControl
			
			Noesis::StackPanel* pPanel1 = pTheMainWindow->FindName<Noesis::StackPanel>("StackPanel1");
			Noesis::StackPanel* pPanel2 = pTheMainWindow->FindName<Noesis::StackPanel>("StackPanel2");

			Noesis::Ptr<Noesis::IView> pNewView1 = Noesis::GUI::CreateView(pPanel1);
			Noesis::Ptr<Noesis::IView> pNewView2 = Noesis::GUI::CreateView(pPanel2);
			
			//	All Noesis::Ptrs stored off, etc
			
			pPanel1->DisconnectFromView();
			pPanel1->ConnectToView(pNewView1);
			pPanel2->DisconnectFromView();
			pPanel2->ConnectToView(pNewView2);
			
			pPanel1->GetChildren()->Get(0)->Focus();
			
We can isolate inputs from the gamepads and push them to the appropriate IView. The Focus on the last line appears to succeed, but interaction doesn't work, and causes an assert during GetNextDirection, at element->TransformToAncestor(root). root is the Noesis::View::RootVisual, but doesn't appear to be the one in the main scene. Calling GetView() on the appropriate elements seems to return what I would expect (the set View appears to propagate properly), but I feel I must be missing something.

Any advice greatly appreciated!

Cheers,
Scott

Edit: Clarification

Re: Multiple Inputs (Splitscreen)

Posted: 12 Sep 2019, 09:26
by sfernandez
Hello,

The problem is you cannot have the same Visual used in different Views, they can only be owned by a single View. The StackPanels cannot be defined at root View and used by the inner Views at the same time. Although I see you are trying to Disconnect and Connect to the newly created views, that was not the expected use. You are probably getting some errors saying that pPanel1/2 already have a Visual parent when the inner Views are created.

You should use 2 independent Views with their own xamls, and render them to the corresponding part in the screen.

If you need to have a common UI then you'll probably need a third View (unless you can render that as part of one of the players UI).

Could you try that and let us know?

Re: Multiple Inputs (Splitscreen)

Posted: 12 Sep 2019, 12:09
by ScottFromDerby
Thank-you so much for your help! Trying this approach now, will get back to you asap

Re: Multiple Inputs (Splitscreen)

Posted: 12 Sep 2019, 15:42
by ScottFromDerby
Got it now functional with your advice! Thanks for your help!

After calling Noesis::GUI::LoadXaml for our core Uri, we now additionally facilitate extra Uris to new Windows to be allocated (also via Noesis::GUI::LoadXaml), which themselves have a single associated view set up each. We then additionally update and render these windows alongside the main window, after having interrogated the resultant windows and removing some of the unwanted (duplicate) elements.

It seems important that these Xaml do not know about each-other, as attaching them to each-other quickly causes asserts within Noesis. This is not a problem, we can happily manage these bespoke Xaml alongside the main window safely.

Thanks again for your help!

Re: Multiple Inputs (Splitscreen)

Posted: 12 Sep 2019, 16:12
by sfernandez
Great it finally worked, but there a couple of things I don't understand:
after having interrogated the resultant windows and removing some of the unwanted (duplicate) elements.
What do you mean about duplicate elements?
It seems important that these Xaml do not know about each-other, as attaching them to each-other quickly causes asserts within Noesis.
Do you mean moving elements from one view to another? What kind of asserts do you get? If you correctly remove an element from the tree, you should be able to add it to another view if they were created in the same thread.

Re: Multiple Inputs (Splitscreen)

Posted: 13 Sep 2019, 09:30
by ScottFromDerby
In our approach, we have one main window xaml, and one "SplitscreenElementsContainer" xaml which we instantiate multiple of. We only want the appropriate part of the splitscreen elements, and in our approach we want to fully define all splitscreen elements within one single container. We have code that can associate a view with a gamepad, so that the inputs uniquely filter through to exactly one of the views. Xaml pseudocode:
MainWindow.xaml

<Window/>
SplitscreenElementsContainer.xaml

<Window>
<StackPanel Tag="PLAYER_ONE_ONLY"/>
<StackPanel Tag="PLAYER_TWO_ONLY"/>
<StackPanel Tag="PLAYER_THREE_ONLY"/>
</Window>
App.cpp

Noesis::Ptr<ProjectWindow> pMainWindow = Noesis::GUI::LoadXaml("MainWindow.xaml");
Noesis::Ptr<ProjectView> pMainView = Noesis::GUI::CreateView(pMainWindow);

Noesis::Ptr<ProjectWindow> pSubWindow1 = Noesis::GUI::LoadXaml("SplitscreenElementsContainer.xaml");
pSubWindow1->RemoveExcept("PLAYER_ONE_ONLY");
Noesis::Ptr<ProjectView> pSubView1 = Noesis::GUI::CreateView(pSubWindow1);

Noesis::Ptr<ProjectWindow> pSubWindow2 = Noesis::GUI::LoadXaml("SplitscreenElementsContainer.xaml");
pSubWindow2->RemoveExcept("PLAYER_TWO_ONLY");
Noesis::Ptr<ProjectView> pSubView2 = Noesis::GUI::CreateView(pSubWindow2);

// If using the splitscreen windows:
pMainView->RestrictInput(None);
pSubView1 ->RestrictInput(Gamepad_1);
pSubView2 ->RestrictInput(Gamepad_2);
// If not
pMainView->RestrictInput(Any);
pSubView1 ->RestrictInput(None);
pSubView2 ->RestrictInput(None);
RemoveExcept() would iterate over children and remove any that do not match the given string tag. RestrictInput() would prevent later Updates from interacting with these views.

The asserts are typically to do with either KeyboardNavigation failing to find focus, or something similar. I'll provide further details asap.

Edit: on reflection, you're right it ought to be possible to do this without ending up with duplicate elements, will keep working on it

Re: Multiple Inputs (Splitscreen)

Posted: 16 Sep 2019, 16:37
by sfernandez
The way I see it which won't require searching for duplicates and removing elements:

MainWindow.xaml
<Window>
  ...
  <Grid x:Name="MenuContainer"/>
</Window>
Menu.xaml
<StackPanel>
  <Button.../>
  ...
</StackPanel>
When you only have 1 player:
Ptr<Window> window = GUI::LoadXaml<Window>("MainWindow.xaml");
Ptr<IView> mainView = GUI::CreateView(window);
Ptr<StackPanel> menu = GUI::LoadXaml<StackPanel>("Menu.xaml");
Grid* menuContainer = mainWindow->FindName<Grid>("MenuContainer");
menuContainer->GetChildren()->Add(menu);
When you have more players:
Ptr<Window> window = GUI::LoadXaml<Window>("MainWindow.xaml");
Ptr<IView> mainView = GUI::CreateView(window);
Ptr<StackPanel> menuPlayer1 = GUI::LoadXaml<StackPanel>("Menu.xaml");
Ptr<IView> menuPlayer1View = GUI::CreateView(menuPlayer1);
Ptr<StackPanel> menuPlayer2 = GUI::LoadXaml<StackPanel>("Menu.xaml");
Ptr<IView> menuPlayer2View = GUI::CreateView(menuPlayer2);
"menuPlayer1View" will be rendered on the left side of the screen, and gamepad 1 events will only be injected to that view.
"menuPlayer2View" will be rendered on the right side of the screen, and gamepad 2 events will only be injected to that view.

Re: Multiple Inputs (Splitscreen)

Posted: 17 Sep 2019, 12:46
by ScottFromDerby
Love it! Your explanation is really clear, it really made things understandable.

Our UI department had created the splitscreen menu by creating a xaml that contained four menus, one for each splitscreen player. I originally planned to keep the xaml as-is, instantiate it four times, then remove the unwanted elements.

It's much simpler (now in our current approach) to have a xaml with a single menu in, then instantiate that four times, once for each player, and manually offset the margin for each menu. It's all working perfectly with this approach.

Thank-you again so much for your well-written examples, we really appreciate it!

Edit: Forgot to mention, the assert we encountered was because we were calling
Frame: UpdateRenderTree, RenderOffscreen, Render
in our main loop, and with multiple windows we were calling
Frame: UpdateRenderTree(0), RenderOffscreen(0), UpdateRenderTree(1), RenderOffscreen(1), Render(0), Render(1)
and it should be:
Frame: UpdateRenderTree(0), UpdateRenderTree(1), RenderOffscreen(0), RenderOffscreen(1), Render(0), Render(1)