Saturday, January 12, 2013

Signal Slot connection with C++ object and Cascades QML

In this blog post I shown how to achieve signal slot connection with QML and C++ object using Connections QML element.

In BB10 Cascades there are two ways to achieve the same.

First using java script connect method, like blow. Following code is from my previous post of Sprite animation.

This is preferred method as we are using only Cascades API.

Here timer is exported c++ object. By using timer.timeout.connect(renderNextFrame) statement. We are connecting the timeout signal of timer C++ object to renderNextFrame function.

import bb.cascades 1.0
...
        Container {
            id: sprite;
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center
            layout: AbsoluteLayout {}  
            
            onCreationCompleted: {
                timer.timeout.connect(renderNextFrame);
                timer.start(200);
            }
                      
            function renderNextFrame() {
                ...
            }
       }
...

Second, Using Connections element like below.
import bb.cascades 1.0
import QtQuick 1.0
...
        Container {
            id: sprite;
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center
            
            onCreationCompleted: {
                timer.start(200);
            }
            
            attachedObjects: [
                Connections {
                    target:timer;
                    onTimeout:{
                        sprite.renderNextFrame();
                    }
                }
            ]
            
            function renderNextFrame() {
             ...
            }
        }
...
To use Connections element, we need to import QtQuick components, as Connections element is part of QtQuick.

Once its imported, you will need to declare Connections element as part of attachedObjects. Then you can use it as usual in QML.

And just for reference, I create Timer object like below for above example.
    QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(this);

    QTimer* timer = new QTimer(app);
    qml->setContextProperty("timer",timer);

Sprite animation from sprite sheet with BB10 Cascades QML

In past I have posted solution for how to achieve sprite animation from sprite sheet with Qt C++ widget framework and with QML framework.

I tried to do the same with BB10 Cascades QML. Final output will look like below.


While it was quite straight forward to achieve this with Qt or QML, with Cascades you will need to tweak few things, like desabling the implicit Cascades animation and enabling cliping.

Following is my code for the dragon sprite.

        Container {
            id: sprite;
            preferredHeight: 100;
            preferredWidth: 100;
            clipContentToBounds: true
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Center
            layout: AbsoluteLayout {}  
            
            onCreationCompleted: {
                timer.timeout.connect(renderNextFrame);
                timer.start(200);
            }
                        
            function renderNextFrame() {
                dragonSprite.currentFrame = 
                      (dragonSprite.currentFrame + 1) % dragonSprite.frameCount;
                dragonSprite.translationX = 
                     -(dragonSprite.currentFrame * sprite.preferredWidth);
            }
                              
            ImageView{
                id: dragonSprite
                imageSource: "dragon.png"
                scalingMethod: ScalingMethod.AspectFill
                preferredHeight: 100
                preferredWidth: 500 
                property int currentFrame: 0
                property int frameCount: 5
                
                attachedObjects: [
                    ImplicitAnimationController {
                        propertyName: "translationX"
                        enabled: false
                    }
                ]  
            }
        }
First, I am creating parent Container which will hold ImageView with sprite sheet as image source.

We need enable clipping by setting clipContentToBounds to true so only certain portion of image is visible not whole image and also need to set preferredHeight, preferredWidth to indicate clip region.

Then I am attaching the renderNextFrame function with timeout signal. renderNextFrame takes care of which frame to display by changing current frame and translating the image view accordingly.

Second, I am creating ImageView with sprite sheet image as image source. We need to set scalingMethod to ScalingMethod.AspectFill so that unnecessary scaling is not performed. Again we need to set preferredWidth, preferredHeight so that proper frame is displayed.

I am also storing max frame count and current frame number with image.

And finally we need to disable the implicit animation for translationX property. Without this you will not be able to achieve smooth sprite animation. We can use ImplicitAnimationController element to disable implicit animation. However it allows only certain property to be disabled. Here is documentation about ImplicitAnimationController.

Once this is done, you will be able to see smooth animation.

Friday, January 4, 2013

Creating custom listview with BB10 Cascades API

As I indicated in my previous post, I am trying to port my Audiobook Reader application to BB10. I needed to make few changes to use BB10 Cascades API.

Previously I posted about how to create custom dialog box using Cascades QML API.

In this post I will talk about how to create custom list view.

For most case StandardListItem should be enough for use. Its looks like below.


But I wanted to learn creating custom component and also wanted to add some dynamic behavior to my list item.

I wanted to add Delete button on my list item. By default delete button is invisible, its get visible on long press event. It you tap on button the delete event is fired, touching anywhere else in item makes it invisible again. Also touching List item in normal state fire the selection event.

I was not sure how I can achieve this behavior with StandardListItem and I create my custom list item like below.

Normal condition, it looks like below.

On long press event on list item, it shows the delete button.

Now lets see how I created list item to satisfy above behavior.

First lets create list view, which uses the custom list item named ListDelegate and also have necessary function (selected and delete )to handle events from list item.
            ListView {
                id: listView
                dataModel: listModel.model
                listItemComponents: [
                    ListDelegate {
                    }
                ]

                //called by listdelegate manually
                function selected(index) {
                    console.log("###### Index selected:" + index);
                }

                //called by listdelegate manually
                function delete(index) {
                    console.log("###### Delete index:" + index);
                }
            }
Following is code for ListDelete component. I removed some code to keep code short and relevant.

First we need to set touchPropogationMode to Full on root container, so the all sub component get chance to handle their event. Then I added to sub container to main container to hold actual list item content and one to hold delete button and added gesture handler to both of them.

Unfortunately I was not able to find easy way to emit signal from ListItem to ListView, so I ended up calling ListView function manually to indicate select and delete event. By calling list view function
 root.ListItem.view.selected() and root.ListItem.view.delete()
.
To access current list items's index, we can use root.ListItem.indexPath property.
ListItemComponent {  
    
    Container{
        id: root
        layout: DockLayout {}
        touchPropagationMode: TouchPropagationMode.Full;
          
        Container{
            id: itemRoot
            layout: DockLayout {}
            preferredWidth: 768; preferredHeight: 120  
            
            gestureHandlers:[
                LongPressHandler {
                    onLongPressed: {
                        //make delete button visible
                        deleteBtn.opacity = 1;
                        itemRoot.opacity = 0.3 
                    }
                },
                TapHandler {
                    onTapped: {   
                        if( deleteBtn.opacity == 1 ) {   
                            //make delete button hide                
                            deleteBtn.opacity = 0;
                            itemRoot.opacity = 1; 
                        } else {
                            //fire selected event
                            root.ListItem.view.selected(root.ListItem.indexPath); 
                        }
                    }
                }
            ]  
                        
            Divider {
                verticalAlignment: VerticalAlignment.Bottom
                horizontalAlignment: HorizontalAlignment.Center  
            }
            
            Container {
                
                verticalAlignment: VerticalAlignment.Center
                layout:StackLayout {
                    orientation: LayoutOrientation.LeftToRight;                
                }
                                
                ImageView{
                    id: image
                    preferredHeight: 110; preferredWidth: 110
                    imageSource: ListItemData.image;
                    verticalAlignment: VerticalAlignment.Center
                }
                
                Container {             
                    layout:StackLayout {}
                    bottomPadding: 10
                    Label{
                        text: ListItemData.title;            
                    }
                    
                    Label{
                        text: ListItemData.subTitle;                            
                    } 
                }
            }
        
        } 
        
        Container {
            id: deleteBtn
            
            opacity: 0; preferredWidth: 150; preferredHeight: 100
            verticalAlignment: VerticalAlignment.Center
            horizontalAlignment: HorizontalAlignment.Right  
          
            ImageView{
                imageSource: "delete.png";
                verticalAlignment: VerticalAlignment.Center
                horizontalAlignment: HorizontalAlignment.Center
            } 
            
            gestureHandlers:[
                TapHandler {
                    onTapped: {                              
                        if( deleteBtn.opacity == 1 ) {                    
                            deleteBtn.opacity = 0;
                            itemRoot.opacity = 1;
                            root.ListItem.view.delete(root.ListItem.indexPath); 
                        } 
                    }
                }
            ]           
        }
    }
}
Thought it seems lot of code, its quite easy to create custom list item. Now lets see how we can provide data to ListView. For my case I created Data model in C++ and exported it to QML. You can export c++ Data model to QML using setContextProperty. listModel is instance of class derived from QObject.

  QmlDocument *qml = QmlDocument::create("asset:///main.qml").parent(&app);
  ListModel listModel;
 qml->setContextProperty("listModel", &listModel);
We are setting model to list view like dataModel: listModel.model, it means that listModel has property called model. Following is code from the same.

class ListModel : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bb::cascades::DataModel* model READ model);

public:
    void loadData();
    bb::cascades::DataModel* model(return mListModel;);
private:

    QMapListDataModel* mListModel;
};
And I am populating the ListModel like below. I am packaging my List ItemData inside QVariantMap, so that ListItem can easily access the data.

void ListModel::loadData()
{
    mListModel->clear();

    foreach( ... )
    {
  QVariantMap map;
 map["image"] = "image path";
 map["title"] = "title";
 map["subtitle"] = "subtitle";
 (*mListModel) << map;
   }
}
If you see ListItem's code, We can access the list item data like below.
                ImageView{
                    imageSource: ListItemData.image;
                }
                
                Container {             
                    Label{
                        text: ListItemData.title;
                    }            
                    Label{
                        text: ListItemData.subTitle;                            
                    } 
                }
Now finally its done. I hope it will be helpful to someone.