Neptune 3 UI - App Architecture

Neptune 3 UI uses a common architecture and principles for all of its apps: the Core UI Architecture.

Core UI Architecture

The Core UI Architecture adapts the component-based architecture, by breaking the UI down into individual UI components, to ensure component reusability. A component encapsulates both the functionality and behavior of a software element into a reusable unit.

Component-based software design has many advantages over the traditional object-oriented software design, such as:

  • Reduced time-to-market and development costs by reusing existing components.
  • Increased reliability by reusing existing components.

In general, Neptune 3 differentiates between UI primitives, like rectangles and images, and controls, like buttons. To combine several UI primitives and controls, you use panels or views, which are specific container types used to layout other containers as child UI types or controls. UI primitives are only used inside controls, because the controls can style the UI. The difference between a panel and a view is that the view interfaces with the stores, the app's business layer.

Apps

An app is usually designed around a specific context, often using a particular area of the service API, and can also depend on common services to be aware of the overall system's state. The aim of the Core UI Architecture is to avoid situations where the UI has dependencies on these common services; instead, they should be wrapped as a store entity.

The store is the only entity that is allowed to talk to service instances. Besides being the adapter to the service interfaces, the store provides the necessary business logic to achieve a clean UI. Consequently, the UI should only contain visual logic and exclude any business logic.

In turn, the UI itself is divided into several UI elements, some of which are allowed to have a reference to the store; but not others. Managing these dependencies in such a strict way allows these components to stay testable in a later stage of the project. This type of architecture also allows the developer to isolate a part of the UI and work solely in that part, independently, without being tied to service dependencies.

Example architecture:

apps/music/
    stores/
        MusicStore.qml
    views/
        TopView.qml
        BottomView.qml
    panels/
        AlbumArtPanel.qml
        MusicBrowseList.qml
    controls/
        MusicControls.qml
        MusicProgressBar.qml
    Main.qml

Based on the diagram above:

  • Stores: Encapsulate the access to the service API and contain required business logic
  • Views: Have a reference to a store which provides the necessary information to others
  • Panels: Container for other controls and panels. A panel should not have any dependency to a store or a view
  • Controls: Re-usable visual element which has no external data dependencies, besides primitives
  • Helpers: Collection of some operations.

Stores

A store is a data-driven object, that encapsulates the business logic in an app; it is the only portion of the UI that uses the service layer. A store can have child stores which can be further forwarded to sub-trees in the UI. Ideally, a store has an interface that defines the API for the store to be tested. Views would only see this interface so that they do not depend on a concrete store. The store that inherits the abstract interface then fills it with values from the required service and feeds the UI. Alternatively, developers can also use the store interface and feed the UI via static simulation data or an automated simulation backend that runs the states required to provide the desired data.

Views

A view is a container for UI panels; this is the only container that depends on a store inside the app. Other UI parts need to be clear that they do not have any dependencies to any stores like views do, to make sure these components are easy to test.

The image above is an example of a simple widget view in a Music App. It is a container that holds the music control panel and an album art panel. This view takes the information from the music store that is interfaced with the music service, which provides a collection of songs to the app.

Panels

A panel is a container for controls and other panels. Normally, a panel is a layout of controls with a set of functionalities to support the app, such as the Music Control Panel shown below:

Controls

A control in this context is an app-specific control that is used only by the app itself. For example, the play, previous, and next button in the image above.

Helpers

A helper is an object that contains computing functions, but no properties. A typical helper is a set of JavaScript functions, which (if required) could later be moved into C++ code depending on the app's requirements.

UI Harnesses

The architecture described above gives the developer the capability to work independently without depending on some services. Neptune 3 UI's harnesses are located in the tests folder where they are also used by the unit tests.

In many large-scale UI projects, it is very common that UI developers have to run the whole UI just to see changes on a small UI component. In comparison, the UI Harness enables developers to do UI live reloading, such as via QmlLive, during the development process, which can significantly boost their productivity.

Below is an example of the UI harness for the instrument cluster that uses some static data to simulate a particular state and can be run independently using qmlscene or qmllive without the need to run the whole UI.

// tests/ClusterHarness.qml

import QtQuick 2.8
import QtQuick.Window 2.2

import views 1.0
import stores 1.0

import shared.Style 1.0
import shared.Sizes 1.0

Item {
    id: root
    width: Sizes.dp(1920)
    height: Sizes.dp(720)

    Image {
        anchors.fill: parent
        source: Style.image("instrument-cluster-bg")
        fillMode: Image.Stretch
    }

    // The Cluster View that shows large parts of the cluster
    ClusterView {
        anchors.fill: parent
        rtlMode: root.LayoutMirroring.enabled

        // A mocked cluster store to test the cluster view
        // independently from any services it normally would
        // depend on
        store: ClusterStoreInterface {
            id: dummystore
            navigationMode: false
            speed: 0.0
            speedLimit: 120
            speedCruise: 40.0
            driveTrainState: 2
            ePower: 50

            lowBeamHeadlight: true
            highBeamHeadlight: true
            fogLight: true
            stabilityControl: true
            seatBeltFasten: true
            leftTurn: true

            rightTurn: true
            absFailure: true
            parkBrake: true
            tyrePressureLow: true
            brakeFailure: true
            airbagFailure: true
        }
    }
}

© 2018 Pelagicore AG. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.