Phone integration in native code

CHAPTER 26 Phone integration in native code M ost of the functionality in the Windows Phone platform is exposed through Microsoft .NET APIs. This is...
10 downloads 2 Views 8MB Size
CHAPTER 26

Phone integration in native code M

ost of the functionality in the Windows Phone platform is exposed through Microsoft .NET APIs. This is because the first two releases of the operating system allowed developers to submit only .NET-based apps to the Windows Phone Store. Throughout this book, we have looked at Windows Runtime classes that provide some of the same functionality as their .NET counterparts and are available for use in native code. And, of course, XAML apps that incorporate native Windows Runtime components have the option to integrate with .NET-only functionality from managed code. Because that option is not available to Direct3D games, however, Windows Phone provides pure native apps with a subset of the phone integration functionality available to XAML apps, most of which we looked at in Chapter 5, “Phone and media services.” We review some of those integration points in this chapter.

Tip  Unless you have specific performance requirements that can only be met with a pure Direct3D game, you should consider building a XAML app that uses a DrawingSurface BackgroundGrid control to render Direct3D graphics. This approach is discussed in Chapter 25, “Games and Direct3D.”

Sharing text and links Managed Windows Phone apps can share status messages and links through ShareStatusTask and the ShareLinkTask, respectively. For pure Direct3D games, these managed tasks are not available. Instead, Windows Phone 8 provides a subset of the API surface of the Windows 8 share contract. The Windows share contract makes it possible for apps to create a set of content to share, known as a data package, and pass it to the system, which uses it to determine the set of apps that could potentially handle it. Windows Phone uses the same general mechanism by which Direct3D games can share content with the system’s built-in sharing channels, such as messaging, email, and social networks. Unlike in Windows, however, apps on the phone are not able to register as the targets of sharing operations, a limitation that also applies to the equivalent managed tasks. In fact, both the managed tasks and the native share contract API ultimately launch the built-in functionality in exactly the same way. The other limitation of the share contract implementation on Windows Phone is that you can only share text and URI content. Other data types supported for the share contract in Windows 8, including custom data types, are not supported in Windows Phone 8.

I

Before going into the details of the share contract API, it is useful to understand how it works in the context of Windows 8. Without that background, some parts of the sharing workflow might seem awkward. In Windows, most sharing operations are initiated by the user from the system-provided charm, which can be invoked by swiping in from the right of the screen and clicking Share or by pressing Windows key+H on the keyboard. When the user taps the Share charm, Windows checks whether the current foreground app has registered to be notified of that event. If so, it invokes the app’s registered event handler and requests the data to be shared. In most cases, apps are discouraged from including an explicit share button in their UI, but Windows does provide an API to explicitly initiate the sharing workflow for scenarios in which it might not be obvious to the user that sharing might be possible or in which there are multiple distinct pieces of sharable content on the screen at once. Windows Phone does not have any notion of system charms, so all sharing operations must be initiated via the app by using this API. The NativeSharing solution in the sample code shows how a pure Direct3D Windows Phone app can share text and URI content by using the built-in sharing task. In the NativeSharing.h header file, the app declares a new event handler, OnDataRequested, to handle the callback request from the system to provide the data for sharing. void OnDataRequested(Windows::ApplicationModel::DataTransfer::DataTransferManager^ sender, Windows::ApplicationModel::DataTransfer::DataRequestedEventArgs^ args);

In the implementation file, we modify the OnActivated event handler to register for the Data Requested event from the DataTransferManager class. The DataTransferManager acts as the broker between the app and the sharing task. The final step is to call the ShowShareUI method to start the sharing process. To simplify the sample, we add the code to the OnPointerPressed event handler so that the app initiates the sharing workflow when the user taps anywhere on the screen. void NativeSharing::OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args) { CoreWindow::GetForCurrentThread()->Activate(); DataTransferManager::GetForCurrentView()->DataRequested += ref new TypedEventHandler (this, &NativeSharing::OnDataRequested); } void NativeSharing::OnPointerPressed(CoreWindow^ sender, PointerEventArgs^ args) { DataTransferManager::GetForCurrentView()->ShowShareUI(); }

The event arguments for the DataRequested event include an instance of the DataPackage type, which acts as the container for the data that you want to share. The DataPackage includes a set of methods specific to the types of data that can be shared through the share contract, but only SetText and SetUri are supported on Windows Phone. The DataPackage also includes a Title property, which is used by several of the built-in sharing endpoints.

II  Windows Phone 8 Development Internals

void NativeSharing::OnDataRequested(DataTransferManager^ sender, DataRequestedEventArgs^ args) { DataPackage^ dataPackage = args->Request->Data; dataPackage->Properties->Title = L"Windows Phone on the Web"; dataPackage->SetUri(ref new Uri(L"http://www.windowsphone.com")); }

When the OnDataRequested event handler returns, the system examines the contents of the DataPackage to determine which of the available sharing options to display for the content that the app has shared. Table 26-1 shows how each of the built-in apps that support sharing use the different pieces of data that you can share in the DataPackage. TABLE 26-1  The built-in sharing apps handle the elements of DataPackage differently App

DataPackage interpretation

Messaging

The title is concatenated with either the text or the URI. If both a URI and text are provided, the URI is chosen.

Email

The title is used as the email subject. Either the URI or the text is used as the message body. If both a URI and text are provided, the URI is chosen.

Tap+Send

A shared URI is sent via proximity to a nearby device. If only text is shared, Tap+Send will not appear in the sharing menu.

Social networks

A shared URI can be uploaded to a user-chosen social network, with the title as an optional heading. If text is provided, it will be prepopulated as the status message in the social network sharing task. If both a URI and text are provided, the URI is chosen.

Xbox

A shared URI can be sent to Xbox via Xbox SmartGlass. The title and text are ignored.

Note  Most sharing options are not fully enabled in the Windows Phone emulator, either because they require a user account (social networks) or the ability to talk to external hardware (Tap+Send or Xbox). You can use the Messaging app to test your basic sharing flow and then move to a real device to validate additional scenarios. If the user chooses Social Networks from the list of available sharing options, the rest of the user experience (UX) is the same as it is when a XAML app shares content by using the ShareLinkTask or ShareStatusTask. Figure 26-1 shows the built-in social network sharing UI, prepopulated with the content added to the DataPackage in the NativeSharing solution.



     III

FIGURE 26-1  The built-in sharing tasks work the same way whether you share from a XAML app or a

Direct3D game.

Launching phone experiences Windows Phone provides a number of managed “launcher” tasks, which make it possible for Windows Phone Store apps to invoke key built-in apps. For instance, an app can start the browser by using the WebBrowserTask or send the user to its details page in the Windows Phone Store by using the MarketplaceDetailTask. In general, the implementation of these tasks is very lightweight. Recall from Chapter 2, “App model and navigation,” that the Windows Phone navigation model is URI-based, meaning that all transitions between apps (and between pages in XAML apps) are triggered by navigation to a URI, similar to the model on the web. The managed launcher tasks, then, are primarily

IV  Windows Phone 8 Development Internals

responsible for taking the properties set on the task, such as the ContentIdentifier on Marketplace DetailTask, and mapping them to query string parameters for the navigation URI that will be used to launch the corresponding app. The only difference for pure Direct3D games is that you must craft the URI yourself rather than relying on the task to do it for you. Otherwise, the launcher functionality is exactly the same.

The “app” scheme Even though the managed launcher tasks do indeed translate the properties of strongly typed objects into the query string parameters of a navigation URI that triggers the launch of a built-in app, the URIs that they create are somewhat different than the ones used by native Direct3D games to start those same experiences. The URIs generated by the launcher tasks use a system URI scheme called app, which supports the startup of any app on the phone based on its ProductId. Launching based on the app scheme is not supported for Windows Phone Store apps for two reasons: 1. It is based on a specific internal implementation of the phone’s navigation model,

which is subject to change in future releases. 2. It would be possible for one app to launch another in a way that it might not be

expecting, creating security vulnerabilities and an inconsistent UX.

As an example, let’s look at how to start the email app and prepopulate some of the key fields, as you would do with the EmailComposeTask in managed code. Starting other tasks is simply a matter of crafting the appropriate URIs, the formats of which we will review later in this section. The Native EmailCompose solution in the sample code illustrates how to start the email app from a Direct3D game by using the “mailto” URI scheme, an Internet standard for creating a new email message that was codified in the late 1990s. As with the sharing tasks in the previous section, the code is invoked when the user taps anywhere on the screen, raising the OnPointerPressed event. void NativeEmailCompose::OnPointerPressed(CoreWindow^ sender, PointerEventArgs^ args) { Platform::String^ mailtoString = ref new Platform::String (L"mailto:[email protected]?subject=Windows Phone 8&body=Hello world!"); Windows::System::Launcher::LaunchUriAsync(ref new Uri(mailtoString)); }

The Launcher class is used to navigate to the “mailto” URI containing the prepopulated fields for the email message. The system performs a lookup to determine which app should respond to this URI. As soon as the system determines that the handler app should be email, it starts the email app, which shows the user a list of registered accounts. When the user chooses an account, the email app displays the message with the app-provided fields filled in, as shown in Figure 26-2.



     V

FIGURE 26-2  With the “mailto” URI scheme, you can prepopulate an email message from a Direct3D game.

You can use this simple technique to launch a number of built-in experiences. Table 26-2 shows the set of built-in URI schemes supported for use with LaunchUriAsync. Be aware that although some of the tasks listed are available as managed launchers, many are not. You can launch those tasks from managed apps in exactly the same way, by using the Launcher class.

VI  Windows Phone 8 Development Internals

TABLE 26-2  Built-in URI schemes supported for LaunchUriAsync Task

URI format

Create new email message

mailto:?cc=&subject=&body=

Launch web browser

http:

App details page

zune:navigate?appid=

Current app review page

zune:reviewapp

App review page

zune:reviewapp?appid=

Store search

zune:search?keyword=&publisher=&contenttype=app

Maps

maps:,

Driving directions

ms-driveto:?destination.latitude=&destination.longitude= &destination.name=

Walking directions

ms-walkto:?destination.latitude=&destination.longitude= &destination.name=

Airplane mode settings

ms-settings-airplane-mode:

Bluetooth settings

ms-settings-bluetooth:pairing?autodismiss=

Cellular settings

ms-settings-cellular:

Email and account settings

ms-settings-emailandaccounts:

Location settings

ms-settings-location:

Lock settings

ms-settings-lock:

Wi-Fi settings

ms-settings-wifi:

Note  Although the format of the URIs for driving and walking directions is defined by Windows Phone, those tasks are not provided by built-in experiences. Instead, they are provided by Windows Phone Store apps by using the custom URI association functionality discussed in Chapter 2.

Why so many settings schemes? At first glance, the number of individual URI schemes defined for individual items in the settings app looks odd. You might wonder why there wouldn’t just be one scheme, perhaps ms-settings, with query string parameters used to differentiate the specific types of settings. The reason is that the settings app is, in fact, simply an entry point for a number of different settings apps that manage the configuration of individual features. You can see this clearly if you choose one of the items in the settings app and then bring up the task switcher by pressing and holding the Back button. You will see separate entries for the specific Settings item you chose, such as Theme, as demonstrated in Figure 26-3. Because a URI scheme must be associated with an app, individual schemes are required for the different settings apps.



     VII

FIGURE 26-3  Individual settings are managed by different apps.

Choosing photos Chapter 16, “Camera and photos,” looks at the PhotoChooserTask, a way for apps to ask the user to provide a single photo, either from the device’s media library or by capturing a new photo through the camera. For Direct3D games, Windows Phone follows the same model shown earlier for sharing status text and links, namely exposing a subset of the equivalent feature from Windows 8. This limits the number of new concepts that developers need to learn if they want to target both platforms. In the case of choosing photos, the Windows 8 class that enables the scenario is the FileOpenPicker. In Windows, the FileOpenPicker makes it possible for the user to choose a file from either a system location, such as the documents library, or from an app, such as SkyDrive. In Windows Phone 8, the FileOpenPicker is constrained to only allow choosing photos from the device’s media library. As with the share contract API discussed earlier in this chapter, the system task launched when a Direct3D app VIII  Windows Phone 8 Development Internals

uses the FileOpenPicker is exactly the same one launched when a managed app uses the PhotoChooser Task. The only difference is the API used to invoke the task. The NativePhotoPicker solution in the sample code illustrates the use of the FileOpenPicker in a Direct3D app. Here again, we use OnPointerPressed as a simple entry point for the new functionality. Within it, we create an instance of the FileOpenPicker class and configure it to align with the limitations imposed on the picker in Windows Phone. In Windows, the PickerViewMode, SuggestedStart Location, and FileTypeFilter properties allow for configuration of the picker based on the app’s requirements. In Windows Phone, those properties must match the experience provided by the builtin photo chooser, so you must set the view mode to thumbnail and set the starting location to the pictures library. You must also request all file types.

Note  Technically, it was not necessary to force apps to configure the FileOpenPicker with properties that match the built-in photo chooser. The constraint was put in place to ensure that Windows Phone 8 apps automatically maintain the same behavior even if the scope of the picker is expanded in future releases. After the picker is configured, we create a task to wrap the PickSingleFileAsync method and provide a lambda function to handle the result, as presented in Chapter 23, “Native development.” void NativePhotoPicker::OnPointerPressed(CoreWindow^ sender, PointerEventArgs^ args) { FileOpenPicker^ photoPicker = ref new FileOpenPicker(); photoPicker->ViewMode = PickerViewMode::Thumbnail; photoPicker->SuggestedStartLocation = PickerLocationId::PicturesLibrary; photoPicker->FileTypeFilter->Append("*"); concurrency::create_task(photoPicker->PickSingleFileAsync()).then([](StorageFile^ file) { if(file == nullptr) OutputDebugStringW(L"No file was picked"); else OutputDebugStringW(file->Name->Data()); }); }

In the Completed event handler, we get the photo returned from the chooser as a StorageFile instance by calling GetResults on the IAsyncOperation. void NativePhotoPicker::OnPickSinglePhotoCompleted (Windows::Foundation::IAsyncOperation^ operation, Windows::Foundation::AsyncStatus status) { StorageFile^ photo = operation->GetResults(); // use photo ... }



     IX

Note  You might notice that the Windows Phone version of the FileOpenPicker class includes a method not available in the Windows version, namely ResumePickingSingle FileAsync. This method was added to the API surface in anticipation of the possibility that apps invoking the picker could be tombstoned while the picker was open and would need a way to resume the operation upon reactivation. However, in Windows Phone 8, the execution manager guarantees that apps will not be tombstoned while waiting for a picker to return, so this API is not necessary.

Integrating with device state Pure Direct3D games in Windows Phone can query and set several key device states to provide an experience that is well-integrated with the user’s current environment.

User default language Although Direct3D games do not support the full richness of the localization options available in .NET, such as localized resource files, you can query the user’s current language setting and load the appropriate resources for your game based on that setting. To determine the current language setting, you must call the GetUserPreferredUILanguages function twice. In the first invocation, you determine the size of the buffer to allocate in order to receive the language name string in the second invocation. When the language name is retrieved, you can compare it to the list of languages that your app supports and load the appropriate set of resources. The language name returned is in the form of an IETF language tag, such as “en-us” for American English. If you don’t intend to differentiate your language resources between variants of a language (such as American English and British English), you can simply check the first two characters of the returned string for the language code. ULONG numberOfLanguages = 0; DWORD bufferLength = 0; // first invocation - retrieve size of language buffer in bufferLength BOOL result = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages, NULL, &bufferLength); if(result) { unique_ptr languagesBuffer(new wchar_t[bufferLength]); // second invocation - load language string into languagesBuffer hr = GetUserPreferredUILanguages(MUI_LANGUAGE_NAME, &numberOfLanguages, languagesBuffer.get(), &bufferLength); if(result)

X  Windows Phone 8 Development Internals

{ if(wcsncmp(languagesBuffer.get(), L"en-us", 5) == 0) { // load US English resources } else if(wcsncmp(languagesBuffer.get(), L"fr", 2) == 0) { // load French resources } } }

Theme colors In most cases, Direct3D games create a fully immersive experience that is completely independent of the platform on which they are running. However, given the importance of personalization in Windows Phone, you might want to incorporate some elements of the device’s theme into your game where it makes sense, such as in menus or high score pages. The names of the theme resources are different for Direct3D apps from what is available to XAML apps. The supported resources are available through the Windows::UI::ViewManagement::UIElementType enumeration and are described in Table 26-3. TABLE 26-3  Theme resources available in Direct3D



UI element type

Description

AccentColor

The phone’s accent color

TextHigh

High-contrast text

TextMedium

Medium-contrast text

TextLow

Low-contrast text

TextContrastWithHigh

Text that contrasts with TextHigh

NonTextHigh

High-contrast, non-text content

NonTextMedium

Medium-contrast, non-text content

NonTextMediumLow

Medium–low-contrast, non-text content

NonTextLow

Low-contrast, non-text content

PageBackground

The background color for a standard page

PopupBackground

The background color of a pop-up message box

OverlayOutsidePopup

The medium-opacity color that obscures background content while a pop-up message box is displayed in the foreground

     XI

Reading one of the colors is simple. We create an instance of the UISettings class and call its UIElementColor method, passing in the UIElementType that we want to retrieve. The return value is a Color object, which contains properties for the color’s alpha, red, green, and blue channels; that is, its aRGB value. auto uiSettings = ref new Windows::UI::ViewManagement::UISettings(); auto accentColor = uiSettings->UIElementColor(UIElementType::AccentColor);

The ThemedBackgroundCube in the sample code shows how you can use the queried theme color to modify the background of the Direct3D sample, the spinning cube. By default, the sample app hard-codes the background to the color described by the midnightBlue array, defined in CubeRenderer::Render. We replace this with a new private member array, defined at class scope and named m_backgroundColor. Then, in the CreateDeviceResources method, we query the current theme color and transfer the value of each channel to the background color array. Observe how each value is divided by 255 to account for the fact that the Color object represents the value of each channel on an integer scale from 0 to 255, whereas the Direct3D function used to paint the background, ClearRender TargetView, expects a set of floats on a scale from 0 to 1. void CubeRenderer::CreateDeviceResources() { Direct3Dbase::CreateDeviceResources(); // unchanged code... // ... auto uiSettings = ref new Windows::UI::ViewManagement::UISettings(); auto accentColor = uiSettings->UIElementColor(UIElementType::AccentColor); m_backgroundColor[0] m_backgroundColor[1] m_backgroundColor[2] m_backgroundColor[3]

= = = =

(float)accentColor.R (float)accentColor.G (float)accentColor.B (float)accentColor.A

/ / / /

255.0f; 255.0f; 255.0f; 255.0f;

createCubeTask.then([this] () { m_loadingComplete = true; }); } void CubeRenderer::Render() { m_d3dContext->ClearRenderTargetView( m_renderTargetView.Get(), m_backgroundColor ); // unchanged code... // ... }

The end result is the spinning cube sample, personalized based on the user’s accent color, as shown in Figure 26-4.

XII  Windows Phone 8 Development Internals

FIGURE 26-4  Use theme colors to personalize the user’s experience.

Screen orientation Windows Phone supports a set of well-defined screen orientations, determined automatically based on readings from the device’s motion sensors. Direct3D games can register to receive events when the device’s orientation changes, giving you an opportunity to adjust what you are displaying on the screen to align with the way in which the user is holding the device. The NativeOrientationChanger solution in the sample code shows how you can query the current screen orientation and how you can receive events when that orientation changes. In the header file, we add one new event handler, which will respond to orientation change events, and one new field, to keep track of the current device orientation.



     XIII

protected: // Event Handlers. void OnActivated(Windows::ApplicationModel::Core::CoreApplicationView^ applicationView, Windows::ApplicationModel::Activation::IActivatedEventArgs^ args); void OnSuspending(Platform::Object^ sender, Windows::ApplicationModel::SuspendingEventArgs^ args); void OnResuming(Platform::Object^ sender, Platform::Object^ args); void OnWindowClosed(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::CoreWindowEventArgs^ args); void OnVisibilityChanged(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::VisibilityChangedEventArgs^ args); void OnPointerPressed(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::PointerEventArgs^ args); void OnPointerMoved(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::PointerEventArgs^ args); void OnPointerReleased(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::PointerEventArgs^ args); void OnOrientationChanged(Platform::Object^ sender); private: CubeRenderer^ m_renderer; bool m_windowClosed; bool m_windowVisible; Windows::Graphics::Display::DisplayOrientations m_currentScreenOrientation;

In the implementation file, we make several changes to the OnActivated event handler. First, we set the current screen orientation to portrait, which is the default orientation. It is safe to assume that you are in portrait mode initially, because if the system determines that it is in an orientation other than portrait, it will raise an event, presenting an opportunity for you to adjust. The second step is to configure the set of orientations that your app supports by setting the static AutoRotationPreferences property to the bitwise OR of the three available resolutions, portrait, landscape, and landscape flipped. Unlike in XAML apps, the system does not automatically adjust your UI if the user moves between supported orientations. Instead, telling the system which orientations your app supports simply dictates whether you will receive notifications of particular orientation changes. Finally, we register the event handler for the OrientationChanged event. void NativeOrientationChanger::OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args) { CoreWindow::GetForCurrentThread()->Activate(); m_currentScreenOrientation = DisplayOrientations::Portrait; DisplayProperties::AutoRotationPreferences = DisplayOrientations::Portrait |

XIV  Windows Phone 8 Development Internals

DisplayOrientations::Landscape | DisplayOrientations::LandscapeFlipped; DisplayProperties::OrientationChanged += ref new DisplayPropertiesEventHandler (this, &NativeOrientationChanger::OnOrientationChanged); }

In the event handler, we query the new screen orientation and then use the speech synthesis API to read it out. void NativeOrientationChanger::OnOrientationChanged(Platform::Object^ sender) { m_currentScreenOrientation = DisplayProperties::CurrentOrientation; Platform::String^ orientationStatusString; switch(m_currentScreenOrientation) { case DisplayOrientations::Portrait: orientationStatusString = L"Portrait"; break; case DisplayOrientations::Landscape: orientationStatusString = L"Landscape"; break; case DisplayOrientations::LandscapeFlipped: orientationStatusString = L"Landscape flipped"; break; } auto speechSynthesizer = ref new Windows::Phone::Speech::Synthesis::SpeechSynthesizer(); speechSynthesizer->SpeakTextAsync(orientationStatusString); }

Note  The Windows Phone orientation manager does not support the PortraitFlipped value from the DisplayOrientations enumeration, which corresponds a 180-degree rotation from portrait mode. If you want to handle this orientation, you must detect it within your app by using the device’s sensors.

Screen resolution Windows Phone 8 supports three screen resolutions: 480x800 (WVGA), 720x1280 (720p), and 768x1280 (WXGA). Because the aspect ratio is identical for WVGA and WXGA and very similar for 720p, the general guidance for app developers is to target a logical screen size of 480x800 and allow the platform to scale up based on the resolution of the device on which the app is running. For immersive games, such as those written in Direct3D, however, it is often important for the app to know the exact resolution of the screen so that it can load textures that will look good in that environment. Windows Phone does not provide an API to query the screen resolution directly, but it is simple to calculate.

     XV

You can query the logical screen resolution in device-independent pixels (DIPs) from the CoreWindow by checking the Bounds property. For WVGA and WXGA screens, which have a 15:9 aspect ratio, Bounds will show a resolution of 480x800. For 720P devices, Bounds will show 480x853, indicating that the 16:9 aspect ratio of a 720P screen is taller (relative to its width) than its WVGA and WXGA counterparts. CoreWindow^ appWindow = CoreWindow::GetForCurrentThread(); Rect resolution = appWindow->Bounds; // logical screen size is resolution.Width x resolution.Height

The Windows::Graphics::Display::DisplayProperties class includes a static property, ResolutionScale, which provides the scaling factor to apply to the logical screen resolution to determine the physical resolution. The three values returned for Windows Phone 8 devices are Scale100Percent (WVGA), Scale150Percent (720p), and Scale160Percent (WXGA). Because there is a direct mapping between the scaling factor and the screen resolution, you can simply query this value to determine which assets to load. Switch(DisplayProperties::ResolutionScale) { case ResolutionScale::Scale100Percent: // load WVGA assets; break; case ResolutionScale::Scale150Percent: // load 720p assets break; case ResolutionScale::Scale160Percent // load WXGA assets break; }

If you want to calculate the number of pixels that your app can actually target, you simply need to combine the logical screen resolution with the scaling factor, taking advantage of the fact that the integer values of the members of the ResolutionScale enumeration correspond to their scaling factor. That is, the integer value of Scale150Percent is 150. CoreWindow^ appWindow = CoreWindow::GetForCurrentThread(); Rect resolution = appWindow->Bounds; resolution.Height *= ((int) DisplayProperties::ResolutionScale) / 100.0; resolution.Width *= ((int) DisplayProperties::ResolutionScale) / 100.0;

Disabling idle timeouts Unless the user has explicitly disabled screen timeouts in Settings, Windows Phone devices transition to an idle state if no user input has been received for some period, usually a few minutes. For games that rely primarily on the accelerometer for gameplay or apps that play video, however, a lack of touch input does not necessarily indicate a lack of engagement. As a result, apps and games in this state should temporarily disable the idle timeout behavior by using an instance of the Windows::System:: Display::DisplayRequest class. Ensure that you declare the DisplayRequest instance at sufficient scope

XVI  Windows Phone 8 Development Internals

that it will still be available when you want to remove your temporary blocking of the idle behavior. One option is to declare it as a member of your IFrameworkView implementation, which is the same class that contains your handlers for all of the app lifecycle events. Windows::System::Display::DisplayRequest^ m_displayRequest;

When the user enters a portion of your app where it is reasonable to expect long periods without touch input, disable the idle timeout by calling RequestActive on the DisplayRequest. m_displayRequest = ref new Display::DisplayRequest(); m_displayRequest->RequestActive();

When the user exits that portion of the app, re-enable the idle timeout behavior by calling RequestRelease. m_displayRequest->RequestRelease();

Keep in mind that the calls to RequestActive are cumulative, so you must call RequestRelease once for each RequestActive call.

Note  You should only disable the idle timeout behavior if there is a reasonable expectation that the user could interact with your app for several minutes without touching the screen. Disabling idle timeout when the user is not interacting with the device has the potential to significantly reduce battery life if the user forgets to manually turn off the screen.

Handling user input There are two sources of user input that require different handling in Direct3D games compared to XAML apps: text input through the software keyboard, and presses of the hardware Back button.

Keyboard input Receiving text input in a XAML app is simple. The system automatically does all of the work necessary for the software keyboard to appear when the user taps a TextBox, for the keystrokes entered to appear in that TextBox, and for the app to be able to retrieve the input as a string by using the Text property. In a Direct3D game, you must perform these steps yourself to achieve the same effect. The NativeTextInput solution in the sample code demonstrates how to receive text input in a Direct3D game. We define a new Windows Runtime class, NativeTextBox, which will encapsulate the interaction with the keyboard, also known as the software input panel (SIP). The class’s constructor takes parameters describing the position and size of the text box, and the input scope to apply to the SIP. The class’s other public methods are mostly self-explanatory and will be reviewed in more detail when we look at their implementation.



     XVII

Note The NativeTextInput solution uses the DirectXTK helper library to render text on the screen, but we do not cover the use of DirectXTK in this chapter. For more details about this, see Chapter 25.

ref class NativeTextBox sealed { public: NativeTextBox(void); NativeTextBox(int xPosition, int yPosition, int width, int height, Windows::Phone::UI::Core::CoreInputScope inputScope); bool HitTest(float pointerX, float pointerY); property bool HasFocus { bool get() { return m_hasFocus; } } void SetFocus(bool hasFocus); property bool HasText { bool get() { return m_hasText; } } property Platform::String^ Text { Platform::String^ { return m_text; } } protected: void OnTextChanged(Windows::Phone::UI::Core::KeyboardInputBuffer^ sender, Windows::Phone::UI::Core::CoreTextChangedEventArgs^ args); private: bool m_hasFocus; bool m_hasText; int m_xPosition; int m_yPosition; int m_height; int m_width; Platform::String^ m_text; Windows::Phone::UI::Core::CoreInputScope m_inputScope; Windows::Phone::UI::Core::KeyboardInputBuffer^ m_keyboardInputBuffer; };

The implementation of the NativeTextBox constructor contains two lines of note: the instantiation of the KeyboardInputBuffer, and the registration of a handler for its TextChanged event. As the name suggests, the KeyboardInputBuffer provides the characters that the user has entered in the SIP as a text buffer from which apps can read and raises an event, TextChanged, when the contents of that buffer change. m_keyboardInputBuffer = ref new KeyboardInputBuffer(); m_keyboardInputBuffer->TextChanged += ref new TypedEventHandler (this, &NativeTextBox::OnTextChanged);

XVIII  Windows Phone 8 Development Internals

Because the SIP takes up about half the screen, it is generally only displayed when the user has provided focus to a control that will accept text. The NativeTextBox class uses the HitTest method to determine whether a given point on the screen is within the bounds of the text box. If so, we will call the SetFocus method to display the keyboard by using the IsKeyboardInputEnabled property on CoreWindow. bool NativeTextBox::HitTest(float pointerX, float pointerY) { if((pointerX >= m_xPosition && pointerX = m_yPosition && pointerY InputScope = m_inputScope; CoreWindow::GetForCurrentThread()->KeyboardInputBuffer = m_keyboardInputBuffer; CoreWindow::GetForCurrentThread()->IsKeyboardInputEnabled = true; } else { CoreWindow::GetForCurrentThread()->IsKeyboardInputEnabled = false; } }

The last method of interest on the NativeTextBox is the OnTextChanged event handler. It fetches the current contents of the keyboard input buffer and sets a flag indicating whether it contains text. We will eventually use this flag to determine whether to render the text on the screen. We also check if the user has pressed the return key, denoted by the control character “\r”. If so, we remove focus from the text box, which causes the keyboard to disappear. void NativeTextBox::OnTextChanged(KeyboardInputBuffer^ sender, CoreTextChangedEventArgs^ args) { m_text = m_keyboardInputBuffer->Text; if(m_text->Length() > 0) m_hasText = true; else m_hasText = false;

if(wcsstr(m_text->Data(), L"\r")!= NULL) SetFocus(false); }



     XIX

Now that the NativeTextBox is able to receive focus, display the SIP, and receive text input from the user, we only need to add an instance of it to the app to see it in action. As usual, we are using the Direct3D project template as a starting point for the NativeTextInput solution, so the CubeRenderer class is responsible for displaying all of the app’s UI and is a convenient place to add a text box. In addition to a new private field to manage the NativeTextBox instance, we add three new event handlers: OnPointerPressed, OnInputShowing, and OnInputPaneHiding. The OnPointerPressed event handler responds to the user tapping the screen by calling the text box’s HitTest method to determine if it needs to be given focus. The OnInputPaneShowing and OnInputPaneHiding methods respond to the SIP appearing and disappearing by handling the Showing and Hiding events on the Windows::UI:: ViewManagement::InputPane class. In the NativeTextInput solution, these event handlers do nothing, but in a real Direct3D app, you might want to consider rendering the screen differently when the SIP is showing. In particular, you will usually want to ensure that the SIP is not blocking the area of the screen where you intend to render the text that the user is entering. The OccludedRect property on the InputPane class provides a Rect object describing the coordinates of the area covered by the SIP. void OnPointerPressed(Windows::UI::Core::CoreWindow^ sender, Windows::UI::Core::PointerEventArgs^ args); void OnInputPaneShowing(Windows::UI::ViewManagement::InputPane^ sender, Windows::UI::ViewManagement::InputPaneVisibilityEventArgs^ args); void OnInputPaneHiding(Windows::UI::ViewManagement::InputPane^ sender, Windows::UI::ViewManagement::InputPaneVisibilityEventArgs^ args);

In CubeRenderer::CreateDeviceResources, we add the code to set up the text box in the lambda passed to the final continuation. In it, we instantiate the NativeTextBox instance; position it 24 pixels from the top, left, and right of the screen; and set it to be 60 pixels tall. We also register for the events described earlier. createCubeTask.then([this] () { m_textBox = ref new NativeTextBox(24, // top-left of the text box is positioned 24, // at x=24, y=24 432, // width is screen width - 24 * 2 60, // text box height Windows::Phone::UI::Core::CoreInputScope::Text); m_window->PointerPressed += ref new TypedEventHandler (this, &CubeRenderer::OnPointerPressed); auto inputPane = InputPane::GetForCurrentView();

XX  Windows Phone 8 Development Internals

inputPane->Hiding += ref new TypedEventHandler (this, &CubeRenderer::OnInputPaneHiding); inputPane->Showing += ref new TypedEventHandler (this, &CubeRenderer::OnInputPaneShowing); m_loadingComplete = true; });

Finally, we add code to CubeRenderer::Render to actually render the text from the text box if any exists. Note that the technique for rendering text shown here requires the DirectXTK helper library for displaying the text, which we review in detail in Chapter 25. Void CubeRenderer::Render() { // unchanged code... // ... if(m_textBox->HasText()) { m_sprites->Begin(); m_font->DrawString(m_sprites.get(), m_textBox->GetText()->Data(), XMFLOAT2( 30, 30 ), Colors::Black); m_sprites->End(); } }

With the SIP displayed and text entered in the buffer, the NativeTextInput solution looks something like Figure 24-5.



     XXI

FIGURE 24-5  Direct3D apps can bring up the Soft Input Panel (SIP) but must render the text box and entered text

themselves.

Back key presses All Windows Phone devices include a hardware Back button with which the user can navigate back to tasks that she was previously using. For XAML apps, the Back button is tightly integrated with the app’s page stack, with Back-key presses automatically initiating navigation events that are raised in the app. Direct3D games do not have a page stack, so by default, pressing the Back key results in termination. However, you might wish to handle a Back-key press as input to your app, instead. For instance, many games interpret pressing the Back button as a request to pause active game play. The NativeBackButton solution in the sample code illustrates how to integrate with the hardware Back button, using the spinning of the cube in the default project template as a representation of

XXII  Windows Phone 8 Development Internals

gameplay. If the user taps the Back button while the cube is spinning, it stops. If the user then taps the Back button again, the app closes. On the other hand, if he taps the screen, the cube resumes spinning. First, we add a Boolean property to the CubeRenderer class to support pausing and resuming the spinning of the cube. We add methods for toggling the spinning on and off, and for determining whether the cube is currently spinning, along with a private member variable to keep track of the spinning state. Ref class CubeRenderer sealed : public Direct3Dbase { public: CubeRenderer(): // unchanged code... property bool IsSpinning { bool get() { return m_isSpinning; } void set(bool value) { m_isSpinning = value; } } private: bool m_isSpinning; // unchanged code... }

Then, we update the implementation of the Update method to stop performing the matrix transformations that drive the rotation whenever spinning is turned off. void CubeRenderer::Update(float timeTotal, float timeDelta) { if(IsSpinning) return; (void) timeDelta; // Unused parameter. XMVECTOR eye = XMVectorSet(0.0f, 0.7f, 1.5f, 0.0f); XMVECTOR at = XMVectorSet(0.0f, -0.1f, 0.0f, 0.0f); XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); XMStoreFloat4x4 (&m_constantBufferData.view, XMMatrixTranspose(XMMatrixLookAtRH(eye, at, up))); XMStoreFloat4x4 (&m_constantBufferData.model, XMMatrixTranspose(XMMatrixRotationY(timeTotal * XM_PIDIV4))); }



     XXIII

Now that the CubeRenderer supports pausing the spinning of the cube, we can interpret pressing the Back button as the signal to perform that pausing. In the NativeBackButton.h header file, we add a new event handler named OnBackKeyPressed, which will be used to handle the Back-key press. void OnBackKeyPressed(Platform::Object^ sender, Windows::Phone::UI::Input::BackPressedEventArgs^ args);

In the OnActivated event handler, we register OnBackKeyPressed as the handler for the BackPressed event on the HardwareButtons class. void NativeBackButton::OnActivated(CoreApplicationView^ applicationView, IActivatedEventArgs^ args) { CoreWindow::GetForCurrentThread()->Activate(); HardwareButtons::BackPressed += ref new EventHandler (this, &NativeBackButton::OnBackKeyPressed); }

In the OnBackKeyPressed event handler, we first check whether the cube is already stopped. If so, this Back-key press is the second in a row, meaning that we should allow the app to exit, which happens automatically if the app does not set the Handled property on the BackPressedEventArgs parameter to true before returning. On the other hand, if the cube is currently spinning, we call the new ToggleSpin method to pause it and mark the Back-key press as handled. void NativeBackButton::OnBackKeyPressed(Platform::Object^ sender, BackPressedEventArgs^ args) { if(!m_renderer->IsSpinning) return; m_renderer->IsSpinning = false; args->Handled = true; }

Finally, we add code to the OnPointerPressed event handler to restart spinning if the cube is currently paused when the user taps anywhere on the screen. void NativeBackButton::OnPointerPressed(CoreWindow^ sender, PointerEventArgs^ args) { if(!m_renderer->IsSpinning) m_renderer->IsSpinning = true; }

HTTP networking Chapter 7, “Web connectivity,” looks at how to perform HTTP network requests from managed code by using System.Net.WebClient and System.Net.HttpWebRequest. Because neither of those options is available in native code, Direct3D apps must use the IXMLHTTPRequest2 (IXHR2) interface, part of

XXIV  Windows Phone 8 Development Internals

the Microsoft XML Core Services 6 (MSXML6) library. Like its managed counterparts, IXHR2 is fully asynchronous, relying on apps to register callback functions to handle data as it is received from the network. The NativeHttpNetworking solution in the sample code illustrates the use of the IXHR2 interface to perform a simple HTTP GET request on the Windows Phone home page. It includes a header file and an implementation file from the Windows 8 HTTP networking sample, HttpRequest.h and Http Request.cpp. You can find the Windows 8 HTTP networking sample at http://code.msdn.microsoft.com/ windowsapps/HttpClient-sample-55700664/sourcecode?fileId=63906&pathId=173532208. These files provide a simplified abstraction layer over the IXHR2 interface and use the task infrastructure provided by the Parallel Patterns Library (PPL) to enable the use of continuations, as discussed in Chapter 23. They also wrap an implementation of the IXMLHTTPRequest2Callback interface, which defines a set of callback methods that can be registered to respond to the various stages of the HTTP request, including completion, errors, and redirects. There are several short steps required to make the Windows sample code work in a Windows Phone app. First, you must add msxml6.lib to the list of linker inputs for the project. To do this, perform the following steps. Keep in mind that this step is required to use IXHR2, regardless of whether you use the Windows sample code. 1. Right-click the project in Microsoft Visual Studio and then, on the shortcut menu that appears,

click Properties. 2. In the Properties dialog box, in the Configuration list box, click All Configurations. This ensures

that the linker change applies to all build configurations, not just the active one. 3. In the navigation pane on the left, choose Configuration Properties. Click Linker and then click

Input. 4. Click the Additional Dependencies list box, click Edit, and then add “msxml6.lib” to the list.

Next, comment out the implementation of the static method CreateMemoryStream. The Create MemoryStream method is used to create a concrete instance of the ISequentialStream interface for use with HTTP POST requests. However, neither InMemoryRandomAccessStream type that it instantiates nor the CreateStreamOverRandomAccessStream method that it calls to create an IStream instance from that in-memory stream is supported in Windows Phone 8. void HttpRequest::CreateMemoryStream(IStream **stream) { /* auto randomAccessStream = ref new Windows::Storage::Streams::InMemoryRandomAccessStream(); CheckHResult(CreateStreamOverRandomAccessStream (randomAccessStream, IID_PPV_ARGS(stream))); */ }

Finally, remove the include statement for the shcore.h header file from HttpRequest.cpp. In Windows, shcore.h provides the declaration for the CreateStreamOverRandomAccessStream method that we just commented out, but it is not provided in the Windows Phone SDK.

     XXV

With those changes made, you should be able to build your project with the two new files included and we can move on to integrating their functionality into the app. In the NativeHttpNetworking.h header file, we add an include directive for HttpRequest.h and a private member variable for an HttpRequest instance, which we will use to perform the GET operation. In the implementation file, we again add the interesting code to the OnPointerPressed event handler, such that the HTTP request will be sent whenever the user taps anywhere on the screen. As mentioned earlier, the Windows sample code is fully integrated with the PPL, meaning that you can handle the response in a lambda function passed as a parameter to the continuation. void NativeHttpNetworking::OnPointerPressed(CoreWindow^ sender, PointerEventArgs^ args) { Uri^ httpGetUri = ref new Uri(L"http://www.windowsphone.com"); m_httpRequest.GetAsync(httpGetUri).then([this](task response) { try { auto responseData = response.get(); // handle response... } catch(Exception^ ex) { // handle errors } }); }

If you want to support the HTTP PUT or POST verbs, further changes are required. As mentioned earlier, Windows Phone does not support the InMemoryRandomAccessStream class or the Create StreamOverRandomAccessStream method required to convert it to an implementation of the IStream interface. As a result, to pass a stream to IXHR2, you must create your own implementation of ISequentialStream, which includes methods for Read and Write in addition to the base methods inherited from IUnknown.

Summary Most games written in Direct3D are immersive experiences that depend very little on the ability to interact with the rest of the device. However, there are a set of cases for which interaction is important, such as making it possible for the user to share the completion of a difficult level with all of her friends on a social network or sending the user to Bluetooth settings when a peer-to-peer game is unable to establish a socket connection. In this chapter, you learned about the critical phone integration points available to Direct3D games and how to use them. Keep in mind that if there is an important integration point not currently available to pure Direct3D games, you can also consider creating a XAML app that uses a Direct3D DrawingSurfaceBackgroundGrid for rendering graphics. For more details on DrawingSurfaceBackgroundGrid, see Chapter 25.

XXVI  Windows Phone 8 Development Internals

Suggest Documents