Using Layouts (MAUI)
Layouts format your widgets on screen. They provide a grid into which all of your widgets can be aligned. Almost every screen you’ll create will have a Layout widget on it, and whilst they can’t do everything you can imagine, they can help you create some attractive and useful screens.
Basics of Layouts
The layout widget does display much in its own right, but it will organise your widgets on screen for you. You can specify a grid into which widgets will be placed. They will be added from top left to bottom right. Don’t confuse this too much with an HTML table. There are some similarities, but the Layout does not give you all the options an HTML table will.
When you create a layout, you specify the number of rows and columns you want.
Layout *mainLayout = new Layout(0, 0, 240, 320, NULL, 1, 2);
You cannot add more widgets than you’ve specified space for. It will not automatically grow as you add more. Use a ListBox if you want to do that.
If you have more than one column, then horizontal space is allocated evenly between them. You cannot have one column which is narrower than another.
Each widget can be given the right amount of space vertically though, but you do have to have just one column to do it. If you have a 2x2 grid, then each cell must be the same size as its neighbour on the same row.
Any specific spacing you’ve set for widgets in a layout will be ignored. You can’t offset a widget within its own cell. You have to make that widget larger if you want it to appear with a border, so the first two arguments when creating a child for a widget can be ignored.
You add widgets into the layout with the add(Widget* w) method.
/*
* This program will generate a basic menu.
*/
Layout* createMainLayout(const char *left, const char *right)
{
Layout *mainLayout = new Layout(0, 0, scrWidth, scrHeight, NULL, 1, 2);
Widget *softKeys = createSoftKeyBar(30, left, right);
ListBox* listBox = new ListBox(0, 0, scrWidth, scrHeight-softKeys->getHeight(),
mainLayout, ListBox::LBO_VERTICAL, ListBox::LBA_LINEAR, true);
listBox->setSkin(gSkin);
listBox->setPaddingLeft(5);
listBox->setPaddingRight(5);
listBox->setPaddingTop(15);
listBox->setPaddingBottom(15);
mainLayout->add(softKeys);
return mainLayout;
}
This example comes from the MAUIex project supplied with MoSync. Widgets are added into the Layout from left to right and top to bottom. If you have a Layout with a 2x2 grid, the first widget you add will be placed top left, the second top right, the third bottom left and the fourth in the bottom right. If you want to skip a cell, then you have to add a blank widget.
Advanced Layouts
I’ve created a new Widget, which is a grid-based menu where each cell contains an icon and label. You may have seen these on some phones.
To do this, the Widget has inherited from Layout instead of Widget. By doing this you can control some of the placements within your control, and make it really easy to reuse it.
#ifndef _GRID_MENU_H_
#define _GRID_MENU_H_
#include <MAUI/Layout.h>
#include <MAUI/Widget.h>
#include <MAUtil/Environment.h>
#include <MAUtil/Set.h>
using namespace MAUI;
/*
* Interface class to call back to grid menu listener.
*/
class IGridMenuListener
{
public:
virtual void GridMenuItemSelected(int item, Widget* selectedItem);
};
/*
* This class will show the list of member variables and functions
* that are required to form an advanced layout.
* This is not a complete tutorial, some of the member functions which
* can be quite helpful in creating advanced layouts, are
* declared in the following sections.
*/
class GridMenu : public Layout, public KeyListener, public PointerListener
{
public:
GridMenu(int x, int y, int width, int height, Widget* parent = NULL,
int rows = 3, int columns = 3);
virtual ~GridMenu();
void pointerPressEvent(MAPoint2d point);
void pointerMoveEvent(MAPoint2d point);
void pointerReleaseEvent(MAPoint2d point);
void keyPressEvent(int keyCode);
void keyReleaseEvent(int keyCode);
bool handleEvents;
void add(Widget* button);
void addListener(IGridMenuListener* l);
void setSelectedIndex(int index);
int getSelectedIndex();
void selectNext();
void selectPrevious();
void selectNextRow();
void selectPreviousRow();
void setEnabled(bool e);
void drawWidget();
void bindKeyLeft(int keyCode);
void bindKeyRight(int keyCode);
void bindKeyUp(int keyCode);
void bindKeyDown(int keyCode);
void bindKeySelect(int keyCode);
void setPanelActive(int panelID, bool active = true);
private:
IGridMenuListener* listener;
Widget* selectedWidget;
Widget* previouslySelectedWidget;
int _rows;
int _cols;
int itemCount;
int selectedIndex;
void selectButton(int index);
void informListener();
void locateWidget(MAPoint2d, bool inform = false);
bool setContains(Vector<int> s, int value);
Vector<int> moveUpKeys;
Vector<int> moveDownKeys;
Vector<int> moveLeftKeys;
Vector<int> moveRightKeys;
Vector<int> selectKeys;
int origW;
Vector<int> activePanels;
};
#endif
At the top, there is a class IGridMenuListener, for a Screen to be able to attach to the Widget so it can be informed of changes to the menu, such as items being selected. This will appear again in another tutorial on programming Widgets for touch screens.
This class inherits from Layout, has code to capture screen and key presses and for navigation within the menu. It also has the essential drawWidget() method which is required when you create new widgets, although because we’ve inherited an existing Widget and we're not changing how it works, it isn’t essential.
There is a lot of other code to examine in the tutorials about creating your own widgets.
Below is some of the code for managing this advanced Layout. This is not a complete code example.
/*
* Some of the member functions of GridMenu class are declared here.
*/
#include "GridMenu.h"
#include <conprint.h>
GridMenu::GridMenu(int x, int y, int width, int height, Widget *parent,
int rows, int columns): Layout(x, y, width, height, parent, rows, columns)
{
// lprintfln("Inside menu constructor");
origW = width;
itemCount = 0;
selectedIndex = -1;
selectedWidget = NULL;
listener = NULL;
enabled = false;
_rows = rows;
_cols = columns;
// set all the panels to active
for(int i = 0; i < _rows * _cols; i++)
{
activePanels.add(1);
}
this->setPaddingLeft((width - this->getWidth()) / 2);
this->setPaddingRight(5);
this->setBackgroundColor(0xFF0000);
this->setDrawBackground(true);
// lprintfln("Menu constructed");
}
/*
* The destructor for GridMenu class
*/
GridMenu::~GridMenu()
{
}
/*
* The drawWidget member function.
*/
void GridMenu::drawWidget()
{
}
/*
* The add function is used to add widgets to the layout.
*/
void GridMenu::add(Widget *button)
{
if(button != NULL && itemCount < _rows * _cols)
{
Layout::add(button);
itemCount++;
}
// lprintfln("button added");
}
/*
* This function is used to select a widget.
*/
void GridMenu::selectButton(int index)
{
// lprintfln("Selecting button %d", index);
if(index <= itemCount && index > -1)
{
// lprintfln("Testing previously selected widget");
if(selectedWidget != NULL)
{
selectedWidget->setSelected(false);
previouslySelectedWidget = selectedWidget;
}
selectedWidget = getChildren()[index];
// lprintfln("Selecting new button");
selectedWidget->setSelected(true);
// lprintfln("Updating index");
selectedIndex = index;
}
requestRepaint();
}
/*
* To navigate next item or widget in the index.
*/
void GridMenu::selectNext()
{
// lprintfln("Current index: %d", selectedIndex);
int nextItem = (selectedIndex + 1) % itemCount;
// lprintfln("First option: %d", nextItem);
while(activePanels[nextItem] == 0)
{
nextItem = (nextItem + 1) % itemCount;
// lprintfln("Hide the next one and try %d", nextItem);
}
selectButton(nextItem);
}
/*
* This will navigate to previous widget in the index.
*/
void GridMenu::selectPrevious()
{
// lprintfln("Selecting previous");
// lprintfln("selectedIndex: %d", selectedIndex);
// lprintfln("itemCount: %d", itemCount);
int nextItem = selectedIndex - 1;
// lprintfln("nextItem: %d", selectedIndex);
while(activePanels[nextItem] == 0)
{
nextItem--;
if(nextItem < 0) nextItem = itemCount - 1;
// lprintfln("Don't select nextItem: %d", nextItem);
}
selectButton(nextItem);
}
/*
* This will navigate to next row in the grid.
*/
void GridMenu::selectNextRow()
{
int nextItem = (selectedIndex + _cols) % itemCount;
while(activePanels[nextItem] == 0)
nextItem = (nextItem + 1) % itemCount;
selectButton(nextItem);
}
/*
* To navigate to previous row in the grid.
*/
void GridMenu::selectPreviousRow()
{
int nextItem = (selectedIndex + itemCount - _cols) % itemCount;
while(activePanels[nextItem] == 0)
nextItem = (nextItem + itemCount - 1) % itemCount;
selectButton(nextItem);
}
/*
* The setSelectedIndex function sets the value for selected index.
*/
void GridMenu::setSelectedIndex(int index)
{
selectButton(index);
}
/*
* Returns the selected index value.
*/
int GridMenu::getSelectedIndex()
{
return selectedIndex;
}
/*
* The setPanelActive function can set a widget to be active or inactive.
*/
void GridMenu::setPanelActive(int panelID, bool active)
{
activePanels[panelID] = active == true ? 1 : 0;
}
Unlike standard layouts, we don’t want every cell to be active. We don’t want all the cells to have a Widget in, and when you navigate you want it to skip the empty cells. To do this, there is a Vector<int> (although, looking at it now, this could be Vector<byte>), where it can mark cells as active or not.
Vector<int> activePanels;
When the menu is created, the panels can be set to active.
// set all the panels to active
for(int i = 0; i < _rows * _cols; i++)
{
activePanels.add(1);
}
When a widget is added to the menu, it is added to the base Layout and to keep a track of the number of items which have been added.
void GridMenu::add(Widget *button)
{
if(button != NULL && itemCount < _rows * _cols)
{
Layout::add(button);
activePanels.add(1);
itemCount++;
}
// lprintfln("button added");
}
When the user navigates the Layout, we can check to see if the panel is active or not, and decide whether it should be skipped.
void GridMenu::selectNext()
{
// lprintfln("Current index: %d", selectedIndex);
int nextItem = (selectedIndex + 1) % itemCount;
// lprintfln("First option: %d", nextItem);
while(activePanels[nextItem] == 0)
{
nextItem = (nextItem + 1) % itemCount;
// lprintfln("Hide the next one and try %d", nextItem);
}
selectButton(nextItem);
}
Keep moving onto the next panel (going back to the start if necessary) until we find one which is active.
When we move up or down a row, we have to do the same thing:
void GridMenu::selectNextRow()
{
int nextItem = (selectedIndex + _cols) % itemCount;
while(activePanels[nextItem] == 0)
nextItem = (nextItem + 1) % itemCount;
selectButton(nextItem);
}
So, when creating the menu Layout, you can set which panels you want to use.
// Creates an iPhone-style grid menu
Widget* createGridMenu(IGridMenuListener* listener)
{
GridMenu* gridMenu = new GridMenu(0, 0, scrWidth, scrHeight, NULL);
gridMenu->setBackgroundColor(0xFFFFFF);
gridMenu->setDrawBackground(false);
setLabelPadding(gridMenu);
gridMenu->addListener(listener);
Font* f = new Font(FONT);
gridMenu->add(createImage(BLANK));
gridMenu->add(createImage(BLANK));
gridMenu->add(createMenuImage(THEMESICON, "Themes", f, aSkin));
gridMenu->add(createImage(BLANK));
gridMenu->add(createMenuImage(MAPICON, "Map", f, aSkin));
gridMenu->add(createMenuImage(FRIENDSICON, "Friends", f, aSkin));
gridMenu->add(createMenuImage(EVENTSICON, "Events", f, aSkin));
gridMenu->add(createMenuImage(SETTINGSICON, "Settings", f, aSkin));
gridMenu->add(createMenuImage(MESSAGESICON, "Messages", f, aSkin));
gridMenu->setPanelActive(0, false);
gridMenu->setPanelActive(1, false);
gridMenu->setPanelActive(3, false);
gridMenu->setSelectedIndex(2);
return gridMenu;
}
At the end of this, we are setting three panels to be inactive, as you can see from the picture. There is a 3x3 layout, but now it acts like a pyramid.
- Printer-friendly version
- Login or register to post comments
Share on Facebook