Skip to content

Freeform Panel Widget

Siddharth Utgikar edited this page Dec 22, 2020 · 1 revision

Table of Contents

The Freeform Panel widget is an abstract class of the panel widget cluster. The purpose of the freeform panel is to allow a number of arbitrary widgets to be placed into a single display panel. The freeform panel can be split multiple times to create multiple panes, enabling complex visual layouts of widgets and views.

An example of a freeform panel is the Telemetry Panel Widget. The panel is split into multiple sections for displaying multiple widgets together. Then entire panel can be positioned as needed on screen, removing the need for laying out every individual widget's location.

Freeform Panel supports a titlebar and close box. (See Panel Configuration).

Creating a Freeform Panel

The freeform panel is created with a single pane (accessed via method rootID). Each pane has a PaneID unique to the panel for identification and manipulation purposes. Panes can be split and merged together dynamically to create complex interfaces. Each pane may contain one Widget or View type object. Those widgets can be composed of other widgets or views. The content of each pane can be positioned in one of 9 ways, corner, edge, or centered.

Spliting a Pane

Panes can be split either horizontally (along the horizontal axis) or vertically (along the vertical axis). When a split is done, a list of PaneID is returned to the splitting code. The pane can be split using

fun splitPane(paneId: Int, splitType: SplitType, proportions: Array<Float>): List<Int>

The API to split the panes accepts the following three parameters:

  • PaneID - The unique ID of the pane that needs to be split.
  • SplitType - One of the two options Horizontal or Vertical
  • ProportionArray - A float array representing the proportions for the resultant panes of the split. The sum of array values should be less than or equal to 1. In case the sum is less than 1, the last row or the last column will be allocated the left over space.

This example shows splitting horizontally into 5%, 90%, and 5% sections, then dividing the 90% section in two even sections vertically.

class SimpleFreeFormPanel @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0,
        configuration: PanelWidgetConfiguration = PanelWidgetConfiguration(
                context = context,
                panelWidgetType = PanelWidgetType.FREE_FORM)
) : FreeFormPanelWidget<Any>(context, attrs, defStyleAttr, configuration) {

    init {
            // Create 3 columns by splitting the root pane horizontally
            val columnList = splitPane(rootID, SplitType.HORIZONTAL, arrayOf(0.05f, 0.9f, 0.05f))
            // Create 2 rows by splitting the center pane vertically
            val rowList = splitPane(columnList[1], SplitType.VERTICAL, arrayOf(0.5f, 0.5f))
        }
}

Merging - Reversing a Split

To recombine a split, call mergeChildren passing in the original PaneID which was split, or more conveniently, mergeSiblings, passing any of the sibling PaneIDs from the split.

Merge Children

fun mergeChildren(paneId: Int)

Merge Siblings

fun mergeSiblings(paneId: Int)

The following example merges the previously split panes back to the default state.

class SimpleFreeFormPanel @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0,
        configuration: PanelWidgetConfiguration = PanelWidgetConfiguration(
                context = context,
                panelWidgetType = PanelWidgetType.FREE_FORM)
) : FreeFormPanelWidget<Any>(context, attrs, defStyleAttr, configuration) {

    init {
            // Create 3 columns by splitting the root pane horizontally
            val columnList = splitPane(rootID, SplitType.HORIZONTAL, arrayOf(0.5f, 0.9f, 0.5f))
            // Create 2 rows by splitting the center pane vertically
            val rowList = splitPane(rowList[1], SplitType.VERTICAL, arrayOf(0.5f, 0.5f))
            // Merge 2 rows created using merge siblings API
            mergeSiblings(rowList[0])
            // Merge 3 columns using merge children API. Since parent of columns is root passing rootID
            mergeChildren(roodID)
        }
}

During a split or merge, any widgets or views which were added to a pane will be removed.

Widget/View Alignment

Widgets and views are positioned when they are added using the position parameter. There are 9 defined positions which can be used and are defined by the enum ViewAlignment.

  • CENTER - Center of the pane.
  • TOP - Top edge of the pane horizontally centered.
  • BOTTOM - Bottom edge of the pane horizontally centered.
  • LEFT - Left edge of the pane vertically centered.
  • RIGHT - Right edge of the pane vertically centered.
  • LEFT_TOP - Top left corner of the pane.
  • LEFT_BOTTOM - Bottom left corner of the pane.
  • RIGHT_TOP - Top right corner of the pane.
  • RIGHT_BOTTOM - Bottom right corner of the pane.

Positioning can also be changed dynamically by using

fun setPanePosition(paneId: Int, 
                    position: ViewAlignment, 
                    leftMargin: Int = 0, 
                    topMargin: Int = 0, 
                    rightMargin: Int = 0, 
                    bottomMargin: Int = 0)

Adding and Removing Widgets and Views

Since widgets are essentially views the same API can be used to add Widgets or Views to the pane. When adding widgets and views, you can also add margins around the widget or view.

fun addView(paneId: Int, 
            widgetView: View, 
            position: ViewAlignment = ViewAlignment.CENTER,
            leftMargin: Int = 0, 
            topMargin: Int = 0, 
            rightMargin: Int = 0, 
            bottomMargin: Int = 0)
fun removeView(paneId: Int)

Debug FreeformPanel

It can be challenging to create a fully functional FreeFormPanel without visualizing the panes created. To make this task easier, the panel provides an API to highlight the panes and display IDs of each pane.

fun enablePaneDebug(enableLabelAssist: Boolean, 
                    enableBackgroundAssist: Boolean = false,
                    @ColorInt textColor: Int = INVALID_COLOR, 
                    @ColorInt textBackgroundColor: Int = INVALID_COLOR)
  • EnableLabelAssist - Boolean when set true will enable the display of labels on the pane.
  • EnableBackgroundAssist - Boolean when set true will assign pseudo randomized background colors to each pane providing a clear distinction.
  • TextColor - The color for the text displaying the ID.
  • TextBackgroundColor - The color for the background of the text displaying the ID.

To turn off the debugging information call the API while setting both the flags to false.

Utility Methods

Several utility methods have been provided to make manipulation of freeform panels easier and more convenient.

  • fun findViewPane(view: View): Int - Takes a view or widget as an argument and returns the PaneID containing the widget or -1 if not found.
  • fun viewInPane(paneId: Int): View? - Takes a PaneID as an argument and returns the view or widget installed in the pane or null if pane contains a view or no widget.
  • fun getSiblingList(paneId: Int): List<Int> - Takes a PaneID as an argument and returns an inclusive list of all siblings.
  • fun getChildrenList(paneId: Int): List<Int> - Takes a PaneID as an argument and returns a list of all children.
  • fun getParentId(paneId: Int): Int - Takes a PaneID as an argument and returns the parent PaneID or -1 if the PaneID is the root pane.
  • fun getPanePositioning(paneId: Int): ViewAlignment? - Takes a PaneID as an argument and returns the positioning of the view or widget in it.
  • fun setPaneBackgroundColor(paneId: Int, @ColorInt color: Int) - Takes an integer color, and a PaneID and sets the background color for the pane.
  • fun setPaneVisibility(paneId: Int, isVisible: Boolean) - Takes a PaneID and a boolean value to represent the visibility of the pane.
Clone this wiki locally