Wiki

Не ваше стандартное круговое меню

Andreas Aardal Hanssen
Большая забава работать с круговым меню. Они имеют различные формы, размеры, и цвета, часто специализированные, чтобы соответствовать определенному приложению. Они особенно часто встречаются в играх и специализированных приложениях. Поскольку все элементы доступны на равном расстоянии от начала координат указателя, то круговые меню намного более эффективны при использовании, чем стандартные меню.

[Загрузить исходный текст]

Нормальные круговые меню представлены в виде окружности с действиями, представленными в виде секторов. Когда такое меню отображается, указатель автоматически устанавливается в центр круга.

Standard-Pie     Hexagon  Pie-Example

Компонент QtPieMenu, доступный в Qt Solution, облегчит добавление кругового меню в ваши приложения. Вид по умолчанию показан сверху слева. Другие виды можно получить наследованием от QtPieMenu и переопределением нескольких виртуальных функций.

В этой статье, мы показываем, как создать меню типа "сектор", которое показано выше справа. Когда сектор круга выбран, он расширяется вместе с подэлементами. Главная выгода этой новой формы - любое движение мышки состоит из прямого движения курсора от центра круга к элементу-"листу".

Наше меню-сектор требует, чтобы мы переопределили четыре функции: indexAt (), generateMask (), reposition() и paintEvent (). Все остальное обрабатывается базовым классом QtPieMenu. Давайте начнем с определения класса:

class SectorMenu : public QtPieMenu
{
    Q_OBJECT
public:
    SectorMenu(const QString &title, QWidget *parent);
 
    int indexAt(const QPoint &pos);
 
protected:
    void generateMask(QBitmap *mask);
    void reposition();
    void paintEvent(QPaintEvent *event);
    int menuDepth() const;
 
private:
    int startAngle;
    int arcLength;
};
 

Мы объявляем четыре функции, которые мы должны переопределить, вспомогательную функцию, названную menuDepth (), и два поля. Переменные будут использоваться для того, чтобы расположить и растянуть сектора круга.

SectorMenu::SectorMenu(const QString &title,
                       QWidget *parent, const char *name)
    : QtPieMenu(title, parent, name)
{
    int depth = menuDepth();
    setInnerRadius(depth * 50);
    setOuterRadius((depth + 1) * 50);
    setFixedSize(2 * outerRadius() + 1,
                 2 * outerRadius() + 1);
}

В конструкторе, мы определяем внутренние и внешние радиусы меню. Главное меню имеет установленный внутренний радиус 0 и внешний радиус 50 пикселей; подменю первого уровня имеют внутренний радиус 50 пикселей и внешний радиус 100; подменю второго уровня имеют внутренний радиус 100 пикселей и внешний радиус 150; и так далее.

int SectorMenu::menuDepth() const
{
    const QObject *pie = this;
    int depth = 0;
 
    while (pie->parent()
           && pie->parent()->inherits("SectorMenu")) {
        pie = pie->parent();
        ++depth;
    }
    return depth;
}

функция menuDepth () возвращает глубину меню. Главное меню имеет глубину 0, подменю имеют глубину 1, подподменю имеют глубину 2, и так далее.

void SectorMenu::generateMask(QBitmap *mask)
{
    if (menuDepth() == 0) {
        startAngle = 45 * 16;
        arcLength = 360 * 16;    
    } else {
        SectorMenu *parentPie = (SectorMenu *)parent();
        for (int i = 0; i < parentPie->count(); ++i) {
            if (parentPie->subMenuAt(i) == this) {
                arcLength = parentPie->arcLength
                            / parentPie->count();
                startAngle = (45 * 16) + (arcLength * i);
                break;
            }
        }
    }
    // more follows

QtPieMenu вызывает generateMask () непосредственно перед тем, как меню показывается в первый раз. Маска определяет, какие пиксели принадлежат меню; другие пиксели сделаны прозрачными.

Если меню является главным меню, поля startAngle и arcLength установлены на 45 ° и 360 ° градусов соответственно. Значения умножены на 16, потому что QPainter выражает угол как одна шестнадцатая от градуса.

Если меню является подменю, то мы выполняем итерации через подменю родительского меню, определяем угол начала и длину дуги подменю.

    QPainter painter(mask);
    painter.setPen(color1);
    painter.setBrush(color1);
    painter.drawPie(0, 0,
                    outerRadius() * 2, outerRadius() * 2,
                    startAngle, arcLength);
 
    if (innerRadius() > 0) {
        QPoint center = rect().center();
        painter.setPen(color0);
        painter.setBrush(color0);
        painter.drawPie(center.x() - innerRadius(),
                        center.y() - innerRadius(),
                        innerRadius() * 2,
                        innerRadius() * 2,
                        0, 360 * 16);
    }
}

Затем, мы рисуем маску с одним большим кругом для внешнего радиуса, и одним маленьким кругом для
внутреннего радиуса, основанного на startAngle и arcLength. Маска точно охватит секцию,
которое принадлежит этому меню.

Pie-Diagram1

Меню круга в диаграмме, показанной выше, составлено из трех виджетов, сделанных здесь видимыми, используя черные прямоугольники. Вот скриншоты каждого виджета, позволяющие увидеть идею более подробно.

Pie-Diagram2    Pie-Diagram3    Pie-Diagram4

Теперь, у нас осталось только две нерассмотренные функции: indexAt () и paintEvent ().

int SectorMenu::indexAt(const QPoint &pos)
{
    if (count() == 0)
        return -1;
 
    int sliceSize = arcLength / count();
    if (sliceSize == 0)
        return -1;
 
    int angle = (int)((angleAt(pos) * 360.0 * 16.0)
                      / (2.0 * M_PI));
    if (angle - startAngle < 0)
        angle += 16 * 360;
    angle -= startAngle;
 
    int sector = angle / sliceSize;
    if (sector < 0 || sector >= count())
        return -1;
    return sector;
}

Функция IndexAt () переопределена для возврата индекса элемента, расположенного в некоторой экранной позиции (или-1, если там нет никакого элемента). Затем в ней вычисляется угол точки, и определяется, какой сектор (если он есть) находится под этой точкой. Функция angleAt () обеспечена базовым классом QtPieMenu.

Наша функция indexAt () может без последствий игнорировать внутренние и внешние радиусы меню, так как QtPieMenu только вызывает indexAt () на участках, которые являются частью маски, которую мы сгенерировали.

void SectorMenu::reposition()
{
    if (menuDepth() > 0) {
        SectorMenu *parentPie = (SectorMenu *)parent();
        QPoint center = parentPie->geometry().center();
        move(center.x() - outerRadius(),
             center.y() - outerRadius());
    }
}

Когда открывается подменю, QtPieMenu устанавливается вне сектора, который вызвал это открытие. В нашем выполнении, виджет подменю центрирован на вершине родительского виджета.

void SectorMenu::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QFontMetrics metrics(font());
    QPoint center = rect().center();
    int sliceSize = arcLength / count();
 
    for (int i = 0; i < count(); ++i) {
        if (i == highlightedItem()) {
            painter.setPen(
                    colorGroup().highlightedText());
            painter.setBrush(colorGroup().highlight());
        } else {
            painter.setPen(colorGroup().foreground());
            painter.setBrush(colorGroup().background());
        }
        painter.drawPie(0, 0,
                        outerRadius() * 2,
                        outerRadius() * 2,
                        startAngle + sliceSize * i,
                        sliceSize);
        // more follows

Наконец, мы готовы рисовать круговое меню. Для каждого элемента, мы рисуем один сектор круга, используя QPainter:: drawPie ().

      double rad = (innerRadius() + outerRadius())
                      / 2.0;
        double slice = sliceSize * M_PI / (360.0 * 8.0);
        double angle = startAngle * M_PI / (360.0 * 8.0);
        angle += slice * (i + 0.5);
 
        int x = (int)(cos(angle) * rad);
        int y = (int)(-sin(angle) * rad);
 
        painter.drawText(center.x() + x
                         - metrics.width(itemText(i)) / 2,
                         center.y() + y, itemText(i));
    }
}
 

Затем мы отображаем текст. Мы вычисляем среднюю точку каждого сектора, и вписываем текст элемента, центрированный по данной точке.

Наше использование функции paintEvent () очень просто, и эта статья охватывает только изображение секторов и текста. Полный исходный текст содержит описание того, как отображать иконки .


Copyright © 2004 Trolltech Trademarks