Mobile Computing: Scale Lists for 2nd Screen (HDMI)

November 18, 2013 — Leave a comment

next weeks and months I will blog about many different use cases from ‘Mobile Computing’

2nd Screen

It’s easy to connect to a 2nd (bigger) Screen from your BlackBerry 10 device: Z10, Z30, Q10 have a HDMI Micro Connector and you can easy plug in via HDMI cable to a bigger screen. The Z30 also supports Miracast to connect wireless (HDMI).

By default the BlackBerry 10 Device – Screen is mirrored to the big screen and in many cases this is what you want, per ex. while watching a Movie.

Some Applications like Docs2Go – Powerpoint display different content: the actual slide on the big screen and actual plus next slide or notes on the device screen. If you want to do this from Cascades, you’ll find a notice in the documentation of SDK 10.2, that this is currently not possible – you have to use OpenGL ES.

I’m not familar with OpenGL ES and I don’t want to output my content this way. So I have to wait for a future Cascades relase supporting 2nd screen – Design.

But I’m already developing business apps using scenarios from Mobile Computing (per ex. SerCar10) – so I tried to make it something better with the tools available NOW. To let you know what I found out, I created a sample app and pushed to a public Github repository:

cascades_lists_on_hdmi

There’s also a Thread in the forums, if you want to discuss this topic.

Here’s an example of a ScoreList running on Z30:

IMG_00000006

There are 5 items displayed. Connecting the device to HDMI you’ll see the same 5 items, but much larger.

Would it be cool to get more on your big screen from same ListView and same QML file ?

Here it is:

IMG_00000005

The text would be too small on the device – screen, but looks good on the big screen.

Scaling the ListView

The easiest way to scale a ListView is

    var scaleFactor = 0.5
    listView.scaleX = scaleFactor
    listView.scaleY = scaleFactor

but this doesn’t really help you, because now the ListView is scaled down by 0.5 and positioned in the middle of your screen surrounded by white or dark space denpending on your Theme:

mobile_computing1

OK – seems we have to enhance the width and height to make the ListView as large as the Screen. If we scaled down to 0.5, we have to use the reciprocal value – in this case 1 / 0.5  = 2. For a screen of 1280 x 720:

    var extendFactor = 1 / scaleFactor
    listView.minWidth = 1280 * extendFactor
    listView.minHeight = 720 * extendFactor

Now we get this:

mobile_computing2

Seems there’s something outside the visible screen. You probably know, that you can ‘move’ the positioning of a UI Control using translateX and translateY.

mobile_computing3

To calculate the offset we have to subtract the origin width (or height) from the new (enlarged) width (or height) and divide by -2.

Our solution should work on all devices, where Z10 has 1280 x 768 and Z30 has 1280 x 720 and who knows about upcoming devices, so we need the actual sizes and no constants.

Also our simplified sample above ignores that there could be a Title Bar and an Action Bar and we know: Cascades is intelligent and uses different sizes for TitleBar and ActionBar on Z10 and Z30.

We have to find a way to get the size of the ListView and exactly for this Cascades gives us the LayoutUpdateHandler we can attach to UI Controls. For a ListView we have to attach the handler to the surrounding Container.

The LayoutUpdateHandler gives us:

    layoutFrame.width
    layoutFrame.height
    layoutFrame.X
    layoutFrame.Y

The values are always the origin values ignoring scaling or translations. This is great: we get the old values and know the scaleFactor to calculate the new ones.

Where the LayoutUpdateHandler is connected to the Container, we have to do the scaling on the ListView. To handle different situations, we use a function to do the scaling. To make it easy for you to test, there’s an ActionItem to switch between unscaled Device screen and scaled screen.

To persist the state we use a property on the NavigationPane. Also we only want to scale if the device is in Landscape orientation.

// properties set for NavigationPane
property variant scaleFactor: 0.666
property bool use2ndScreen: false
onUse2ndScreenChanged: {
    // current orientation is Landscape ? scale immediately
    if (OrientationSupport.orientation == UIOrientation.Landscape) {
        scaled = use2ndScreen
    }
}
onScaleFactorChanged: {
    // current orientation is Landscape AND already scaled ? re-scale immediately
    if (scaled && OrientationSupport.orientation == UIOrientation.Landscape) {
        scaleListView()
    }
}
// this is the trigger to scale the ListView for HDMI or Device
property bool scaled: false
onScaledChanged: {
    scaleListView()
}

// ActionItem to swap scaled/unscaled
ActionItem {
    title: use2ndScreen ? "On Device" : "On 2nd Screen"
    imageSource: use2ndScreen ? "asset:///images/device.png" : "asset:///images/ic_hdmi.png"
    ActionBar.placement: ActionBarPlacement.OnBar
    onTriggered: {
        use2ndScreen = ! use2ndScreen
    }
}

// Function to scale the ListView
function scaleListView() {
    var extendFactor = 1 / scaleFactor
    if (scaled) {
        listView.scaleX = scaleFactor
        listView.scaleY = scaleFactor
        listView.minWidth = listViewLayoutHandler.layoutFrame.width * extendFactor
        listView.minHeight = listViewLayoutHandler.layoutFrame.height * extendFactor
        listView.translationX = (listView.minWidth - listViewLayoutHandler.layoutFrame.width) / -2
        listView.translationY = (listView.minHeight - listViewLayoutHandler.layoutFrame.height) / -2
    } else {
        listView.scaleX = 1
        listView.scaleY = 1
        listView.minWidth = 0
        listView.minHeight = 0
        listView.translationX = 0
        listView.translationY = 0
    }
}

As you see: to reset the scaling simply set  scaleX and scaleY to 1 and the other values to zero.

If you take a deeper look at the app you’ll see that there are some Actions with predefined scaling factors, so you can easy test it out with your hardware.

There’s even a Slider to try your own values. You know: “peeking-back” is a fundamental UI feature of BlackBerry 10, but have you ever used it to display something different while peeking back ? Here’s your chance to learn about: while changing the Slider from a Page pushed on top of the List you can peek back to see the scaled ListView. (If in Landscape and switch is set to ‘HDMI’)

onPeekStarted: {
scaleFactor = scaleSliderPage.scaling
}

From my experiences a scaling factor of 0.666 looks great and also up to 0.40 the text was readable on my HD Monitor and also using HD Beamer in my HomeCinema. BTW: 0.666 means that you’re enlarging from 1280px to 1920px, which probably fits best on your FullHD Monitor.

Scale automagically if 2nd Screen attached

To test it out, ActionItems are are great, but for a real – life Application scaling should automatically be done if a 2nd Screen was attached – doesn’t matter if via HDMI cable or wireless using Miracast.

Thanks to Signals / Slots – concept from Qt this is easy to solve.

From C++

// monitor attached 2nd screens
int secondaryDisplayId = bb::device::DisplayInfo::secondaryDisplayId();
mSecondaryDisplayInfo = new bb::device::DisplayInfo(secondaryDisplayId, this);

// to be notified when the attached state changes.
bool ok = QObject::connect(mSecondaryDisplayInfo,
		SIGNAL(attachedChanged(bool)), this,
		SLOT(secondaryDisplayAttachedChanged(bool)));

void ApplicationUI::secondaryDisplayAttachedChanged(bool connected) {
	emit secondScreenAttached(connected);
}

From QML connect to the signal:

function onSecondScreenAttached(connected) {
    if (connected) {
        use2ndScreen = true
        // support only Landscape orientation
        OrientationSupport.supportedDisplayOrientation = SupportedDisplayOrientation.DisplayLandscape;
    } else {
        use2ndScreen = false
        // support all orientations
        OrientationSupport.supportedDisplayOrientation = SupportedDisplayOrientation.All;
    }
}

onCreationCompleted: {
    // connect to signal if 2nd screen is connected
    app.secondScreenAttached.connect(onSecondScreenAttached)
}
}

Now if you connect to the 2nd Screen your APP automatically switches to Landscape and scales the ListView.

Have Fun with your HDMI Screen and scaling

The sample application is well documentated, so you should understand easy. I hope this gives you some ideas HowTo deal with external screens using 10.2.0 SDK. You can use the technic also for ScrollViews or Pages where you enter data. It’s up to you not only to scale but also to add fields, change Layouts, rearrange content and more.

For me it’s now easier to design my SerCar10 App to be used On-The-Go while out there with your smartphone or In-The-Office. But that’s another story to tewll you later …

Download from GitHub cascades_lists_on_hdmi and if you have questions, please discuss in the forum thread here.

No Comments

Be the first to start the conversation!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s