Создание экранных средств управления

В дополнение к традиционным средствам управления параметром в Инспекторе сменные разработчики могут создать средства управления для своих плагинов непосредственно на холсте по видеозаписи пользователя и другим объектам. Посмотрите рисунок 6-1 для примера.

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

Рисунок 6-1  экранное управление фильтра Кольцевой Линзы

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

Создание экранных средств управления

Экранные средства управления являются типом плагина, точно так же, как фильтры и генераторы. Для реализации один Вы создаете NSObject подкласс, реализующий FxOnScreenControl протокол и добавляет запись для него к Вашему плагину Info.plist файл. Протокол включает несколько методов для рисования экранных средств управления и обработки входных событий от пользователя.

FxPlug 2.0 представил новый протокол, FxOnScreenControl_v2 протокол. Этот протокол добавляет дополнительные дополнительные методы для обработки перемещенных в мышь событий.

Протокол FxOnScreenControl

Плагины используют FxOnScreenControl протокол для рисования средств управления и других элементов UI на экране. Плагины также используют этот протокол для обработки событий от нажатия мыши и событий клавиатуры.

Инициализация

Несмотря на то, что не формально часть протокола, Ваш экранный плагин управления первоначально отправляется initWithAPIManager: сообщение, так же, как FxFilter или FxGenerator объект был бы.

Ваш плагин сохраняет указатель на менеджера по API объект так, чтобы это могло получить и установить значения параметров фильтра или генератора, с которым это связано. Это - единственный способ, которым плагин может связаться с плагином эффекта, для которого это продает пользовательский интерфейс.

Рисование пробелов

Первая вещь, которую должен сделать экранный плагин управления, говорят хост-приложение, какое рисование пространства это будет использовать. Плагин может нарисовать в пространстве холста, объектном пространстве или пространстве документа. Большинство плагинов должно сделать свое получение в пространстве холста так, чтобы дескрипторы и подобные элементы пользовательского интерфейса были нарисованы в непротиворечивом размере, всегда будучи обращенным к пользователю.

Холст является областью хост-приложения, куда пользователь помещает их видеозапись, взаимодействует с ним, и часы, которые это воспроизводит. Это может быть больше или меньшим, чем просто область, представляющаяся, когда пользователь экспортирует их проект как фильм, и его размер варьируется на основе того, как пользователь располагает окна и области приложения. Пользователь может увеличить холст для наблюдения большего количества подробности в их проекте или уменьшения для получения высокоуровневого обзора. Его пиксели адресуются, запускаясь в 0 от крайних левых и самых нижних пикселей до того, что ширина и высота, оказывается. Это может заставить его казаться несколько пугающим для работы с, но как Вы будете видеть, Вы почти всегда хотите использовать это пространство для своего получения, потому что это дает пользователям лучший опыт.

Объектное пространство является пространством, в котором параметры точки Вашего плагина уже находятся. Это - нормализованное пространство объекта, Вашему плагину применяются к (или самого объекта в случае генератора). Параметры точки имеют значения в диапазоне от 0 до 1 в обоих направлениях для представления области объекта — (0,0), представляет нижний левый угол объекта, и (1,1) представляет верхний правый угол. Также, работа с абсолютными пиксельными значениями является очень громоздкой в объектном пространстве. Но работа с точками очень удобна, потому что можно легко обработать разрешение прокси и попиксельную пропорцию путем простого умножения координат точки на ширину и высоту изображения. (Обратите внимание на то, что в предыдущих версиях плагинов FxPlug, было пространство, названное центрируемым объектом пространством. Это было, фактически, просто объектное пространство. Поскольку это не сделало ничего полезного вне объектного пространства, это было осуждено. Это не должно использоваться, продвигаясь.)

Пространство документа центрируется в источнике сцены и всегда находится в пикселях проекта. Таким образом, если у Вас есть проект HD на 1 080 пунктов, пространство документа первоначально простирается от (–960, –540) к (960,540) вдоль осей x и y. Это находится в тех же координатах как сцена. Поскольку Вы перемещаете камеру, координаты остаются привязанными в сцене, а не на объекте или к холсту.

Подводить итог:

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

  • Объектное пространство позволяет Вам легко обрабатывать параметры точки, не имея необходимость волноваться о неквадратных пикселях, разрешении прокси или полях.

  • Пространство документа позволяет Вам рисовать в том же пространстве как сцена.

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

Рисование экранного управления

Ваш плагин должен реализовать drawOSC:height:activePart:time: метод. Этот метод говорит Вам ширину и высоту ввода к плагину, с каким управлением Вашего экранного управления мышь в настоящее время взаимодействует и в какое время во временной шкале необходимо запрашивать параметры плагина эффекта.

Ваш плагин просят нарисовать его экранное управление в двух различных режимах: для рендеринга и для выбора. Получение для рендеринга означает рисовать средства управления для пользователя, чтобы видеть и взаимодействовать с. Получение для выбора означает рисовать области, пользователь может взаимодействовать с тем, так, чтобы система могла сделать надлежащее тестирование хита. Можно определить, который Вы, как предполагается, рисуете путем вызова glGetIntegerv (GL_RENDER_MODE, &renderMode).

Например, у Вас есть прямоугольная область, которой может управлять пользователь, и Вы хотите, чтобы они были в состоянии перетащить любой угол прямоугольника, любую из сторон, и быть в состоянии перетащить весь элемент. Прямоугольник определяется двумя параметрами точки в Вашем плагине эффекта. Это могло бы посмотреть что-то как пример на рисунке 6-2.

  Пример рисунка 6-2 на экране управляет

Когда спросили нарисовать для рендеринга, Вы рисуете схему прямоугольника и восьми дескрипторов в углах и на сторонах. Когда спросили нарисовать для выбора, тем не менее, Вы также рисуете область в прямоугольнике, потому что пользователь может щелкнуть в нем и перетащить его вокруг как показано на рисунке 6-3.

  Пример рисунка 6-3 на экране управляет перетаскиванием

При рисовании для выбора Вы говорите OpenGL, где дескрипторы или другие интересные части Вашего экранного управления расположены первым вызовом glLoadName() с постоянным представлением части управления, и затем используют вызовы OpenGL для заполнения области части управления. Это получение не выведено на экран пользователю, таким образом, оно не должно быть сглажено.

Код в Перечислении 6-1 демонстрирует, как нарисовать квадратный маркер, которым может управлять пользователь.

Перечисление 6-1  , Получающее manipulatable экранные дескрипторы управления

- (void)drawHandleAt:(FxPoint2D)location
            selected:(BOOL)isSelected
{
    const double    kSideLen    = 5.0;
 
    if (isSelected)
    {
        glColor4f (1.0, 0.0, 0.0, 0.5);
    }
    else
    {
        glColor4f (0.25, 0.25, 1.0, 0.5);
    }
    glBegin (GL_QUADS);
    {
        glVertex2f (location.x - kSideLen, location.y - kSideLen);
        glVertex2f (location.x + kSideLen, location.y - kSideLen);
        glVertex2f (location.x + kSideLen, location.y + kSideLen);
        glVertex2f (location.x - kSideLen, location.y + kSideLen);
    }
    glEnd ();
}
 
// Our drawOSC:height:activePart:time: method will call this method when
// it’s time to draw for selection. We simply call glLoadName () with the
// name of each part as we draw it. We’ll draw handles at the 4 corners
// and in the centers of each side.
- (void)drawForSelectionWithWidth:(int)width
                           height:(int)height
                       activePart:(int)activePart
                          andTime:(double)time
{
    id<FxOnScreenControlAPI_v2> oscAPI  = [_apiManager apiForProtocol:@protocol(FxOnScreenControlAPI_v2)];
 
    // Retrieve the positions of the handles on the corners and sides of
    // the box the user drew
    FxPoint2D   lowerLeft, lowerRight, upperRight, upperLeft;
    FxPoint2D   left, right, top, bottom;
    [self getQuadUpperLeft:&upperLeft
                      left:&left
                 lowerLeft:&lowerLeft
                    bottom:&bottom
                lowerRight:&lowerRight
                     right:&right
                upperRight:&upperRight
                       top:&top
                    atTime:time];
 
    // Draw the main quad
    glLoadName (kShapeOSC_Quad);
    glBegin (GL_QUADS);
    {
        glVertex2f (lowerLeft.x, lowerLeft.y);
        glVertex2f (lowerRight.x, lowerRight.y);
        glVertex2f (upperRight.x, upperRight.y);
        glVertex2f (upperLeft.x, upperLeft.y);
    }
    glEnd ();
 
    // Draw handles at the corners and on the sides
    glLoadName (kShapeOSC_LowerLeft);
    [self drawHandleAt:lowerLeft
              selected:NO];
 
    glLoadName (kShapeOSC_Bottom);
    [self drawHandleAt:bottom
              selected:NO];
 
    glLoadName (kShapeOSC_LowerRight);
    [self drawHandleAt:lowerRight
              selected:NO];
 
    glLoadName (kShapeOSC_Right);
    [self drawHandleAt:right
              selected:NO];
 
    glLoadName (kShapeOSC_UpperRight);
    [self drawHandleAt:upperRight
              selected:NO];
 
    glLoadName (kShapeOSC_Top);
    [self drawHandleAt:top
              selected:NO];
 
    glLoadName (kShapeOSC_UpperLeft);
    [self drawHandleAt:upperLeft
              selected:NO];
 
    glLoadName (kShapeOSC_Left);
    [self drawHandleAt:left
              selected:NO];
}

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

// This method will be called by drawOSC:height:activePart:time: when we
// need to actually display the OSC to the user. activePart will be one of the
// parts we named with glLoadName ().
- (void)drawForRenderingWithWidth:(int)width
                           height:(int)height
                       activePart:(int)activePart
                          andTime:(double)time
{
    // Convert the point parameters from object-relative space to a
    // quad in canvas space
    FxPoint2D   lowerLeft, lowerRight, upperRight, upperLeft;
    FxPoint2D   left, right, top, bottom;
    [self getQuadUpperLeft:&upperLeft
                      left:&left
                 lowerLeft:&lowerLeft
                    bottom:&bottom
                lowerRight:&lowerRight
                     right:&right
                upperRight:&upperRight
                       top:&top
                    atTime:time];
 
 
    // Draw the shadowed outline of the quad
    glColor4f (0.0, 0.0, 0.0, 0.5);
    glBegin (GL_LINE_LOOP);
    {
        glVertex2f (lowerLeft.x + 1.0, lowerLeft.y - 1.0);
        glVertex2f (lowerRight.x + 1.0, lowerRight.y - 1.0);
        glVertex2f (upperRight.x + 1.0, upperRight.y - 1.0);
        glVertex2f (upperLeft.x + 1.0, upperLeft.y - 1.0);
    }
    glEnd ();
 
    glColor4f (1.0, 1.0, 1.0, 1.0);
    glBegin (GL_LINE_LOOP);
    {
        glVertex2f (lowerLeft.x, lowerLeft.y);
        glVertex2f (lowerRight.x, lowerRight.y);
        glVertex2f (upperRight.x, upperRight.y);
        glVertex2f (upperLeft.x, upperLeft.y);
    }
    glEnd ();
 
    // Draw handles at the corners and on the sides
    [self drawHandleAt:lowerLeft
              selected:activePart == kShapeOSC_LowerLeft];
    [self drawHandleAt:bottom
              selected:activePart == kShapeOSC_Bottom];
    [self drawHandleAt:lowerRight
              selected:activePart == kShapeOSC_LowerRight];
    [self drawHandleAt:right
              selected:activePart == kShapeOSC_Right];
    [self drawHandleAt:upperRight
              selected:activePart == kShapeOSC_UpperRight];
    [self drawHandleAt:top
              selected:activePart == kShapeOSC_Top];
    [self drawHandleAt:upperLeft
              selected:activePart == kShapeOSC_UpperLeft];
    [self drawHandleAt:left
              selected:activePart == kShapeOSC_Left];
}

Как отмечалось ранее, это - общее, что некоторые части управления нарисованы в объектном пространстве так, чтобы они выровнялись с Вашим эффектом, но нарисовали свои дескрипторы в пространстве холста так, чтобы они всегда остались тем же размером и всегда были обращенным к пользователю. Можно сделать это при помощи методов в FxOnScreenControlAPI и FxOnScreenControlAPI_v2 протоколы. Пример нарисовал бы круговое управление, это определяется параметром, представляющим расстояние в квадратных, полноразмерных пикселях.

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

  Пример перечисления 6-2 addParameters реализация

- (BOOL)addParameters
{
    id<FxParameterCreationAPI> parmsApi;
 
    parmsApi = [_apiManager apiForProtocol:@protocol(FxParameterCreationAPI)];
 
    if ( parmsApi != NULL )
    {
        NSBundle *bundle = [NSBundle bundleForClass:[self class]];
 
         ...
        [parmsApi addPointParameterWithName:@"Shape::Circle Center"
                                     parmId:kShape_CircleCenter
                                   defaultX:0.75
                                   defaultY:0.75
                                  parmFlags:kFxParameterFlag_DEFAULT];
 
        [parmsApi addFloatSliderWithName:@"Shape::Circle Radius"
                                  parmId:kShape_CircleRadius
                            defaultValue:100.0
                            parameterMin:0.0
                            parameterMax:1000.0
                               sliderMin:0.0
                               sliderMax:500.0
                                   delta:1.0
                               parmFlags:kFxParameterFlag_DEFAULT];
        ...
        return YES;
    }
    else
    {
        return NO;
    }
}

В экранном коде для прорисовки управления Вы ссылаетесь на эти параметры и текущее положение мыши для рисования управления и дескриптора в надлежащем месте.

Перечисление 6-3  Перемещенная в мышь реализация в качестве примера

- (void)mouseMovedWithPositionX:(double)mousePositionX
                      positionY:(double)mousePositionY
                     activePart:(int)activePart
                      modifiers:(FxModifierKeys)modifiers
                    forceUpdate:(BOOL *)forceUpdate
                           time:(double)time
{
    // Convert the mouse position into object relative coordinates for drawing later
    id<FxOnScreenControlAPI_v2> oscAPI      = [_apiManager apiForProtocol:@protocol(FxOnScreenControlAPI_v2)];
 
    // The _mouseMovedPos variable is an class member that we’ll reference again in
    // our mouseDown handler and drawing routines
    [oscAPI convertPointFromSpace:kFxDrawingCoordinates_CANVAS
                            fromX:mousePositionX
                            fromY:mousePositionY
                          toSpace:kFxDrawingCoordinates_OBJECT
                              toX:&_mouseMovedPos.x
                              toY:&_mouseMovedPos.y];
 
    // Redraw the OSC so we see the change in position on the circle's handle
    *forceUpdate = YES;
}
- (void)drawCircleAt:(FxPoint2D)center
          withRadius:(FxPoint2D)radius
{
    id<FxOnScreenControlAPI_v2> oscAPI  = [_apiManager apiForProtocol:@protocol(FxOnScreenControlAPI_v2)];
 
    unsigned int     objWidth;
    unsigned int    objHeight;
    double          objPixelAspectRatio;
    [oscAPI objectWidth:&objWidth
                 height:&objHeight
       pixelAspectRatio:&objPixelAspectRatio];
 
    // NOTE: If we want our circles to be the correct size and round, we need to multiply the center
    // by the object's pixel aspect ratio to get to square pixels, add in the radius, which is
    // always in square pixels, then divide by the pixel aspect ratio to get back to object
    // coordinates. Then the object to screen transform will handle transforming the result into
    // screen space.
 
    glBegin (GL_LINE_LOOP);
    {
        double delta = 0.1;
 
        const double startX = ((center.x * objPixelAspectRatio) + radius.x) / objPixelAspectRatio;
        const double startY = center.y;
 
        double previousX = startX;
        double previousY = startY;
 
        double ang;
        for (ang = delta; ang < (2.0 * M_PI); ang += delta)
        {
            const double currentX = ((center.x * objPixelAspectRatio) + radius.x * cos (ang)) / objPixelAspectRatio;
            const double currentY = (center.y + radius.y * sin (ang));
 
            glVertex2f (currentX, currentY);
 
            previousX = currentX;
            previousY = currentY;
 
        }
 
        glVertex2f (startX, startY);
    }
 
    glEnd ();
}
...
- (void)drawOSC:(int)width
         height:(int)height
     activePart:(int)activePart
           time:(double)time
{
      // Draw the circle
    id<FxOnScreenControlAPI_v2> oscAPI  = [_apiManager apiForProtocol:@protocol(FxOnScreenControlAPI_v2)];
 
    // Convert the center point from object relative to object absolute coordinates
    NSRect  inputBounds = [oscAPI inputBounds];
    FxPoint2D   objAbsCenter    = {
        inputBounds.origin.x + inputBounds.size.width * circleCenter.x,
        inputBounds.origin.y + inputBounds.size.height * circleCenter.y
    };
 
    FxPoint2D   radiusV = { radius, radius };
 
    // Get the object to screen transformation so we can tell OpenGL
    // to draw in the right space
    FxMatrix44* objectToScreen  = [oscAPI objectToScreenTransform];
 
    // Set up the OpenGL transformation matrix to draw in object space
    glMatrixMode (GL_MODELVIEW);
    glPushMatrix ();
    Matrix44Data*   mat = [objectToScreen matrix];
    glMultTransposeMatrixd ((GLdouble*)mat);
 
    glColor4f (1.0, 1.0, 1.0, 1.0);
    [self drawCircleAt:objAbsCenter
            withRadius:radiusV];
 
    // Now start drawing in canvas space to draw the handle
    glPopMatrix ();
    [self drawCircleHandleAt:objAbsCenter
                  withRadius:radiusV
                    selected:activePart == kShapeOSC_CircleHandle];
    ...
}

События от нажатия мыши и события клавиатуры

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

Когда пользовательские щелчки в одних из Ваших средств управления, Ваш плагин принимает вызов к mouseDown:positionY:activePart:modifiers:forceUpdate:time: метод. Если пользователь тогда перетаскивает мышь, Ваш плагин принимает вызов к -mouseDragged:positionY:activePart:modifiers:forceUpdate:time: метод. И наконец, когда разъединения абонентом мышь, приложение узла вызывает Ваш плагин -mouseUp:positionY:activePart:modifiers:forceUpdate:time: метод. В каждом из этих методов возвратиться YES для forceUpdate параметр, если Вам нужно приложение узла для перерисовки средств управления (который Вы почти всегда будете).

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

Перечисление 6-4 демонстрирует, как изменить параметры точки, представляющие углы прямоугольника, когда перетаскивается нижний левый угловой дескриптор.

  Пример перечисления 6-4 mouseDragged:positionY:activePart:modifiers:forceUpdate:time: реализация

- (void)mouseDragged:(double)mousePositionX
           positionY:(double)mousePositionY
          activePart:(int)activePart
           modifiers:(FxModifierKeys)modifiers
         forceUpdate:(BOOL *)forceUpdate
                time:(double)time
{
    id<FxOnScreenControlAPI_v2> oscAPI  = [_apiManager apiForProtocol:@protocol(FxOnScreenControlAPI_v2)];
    id<FxParameterSettingAPI>   paramSetAPI = [_apiManager apiForProtocol:@protocol(FxParameterSettingAPI)];
    id<FxParameterRetrievalAPI> paramGetAPI = [_apiManager apiForProtocol:@protocol(FxParameterRetrievalAPI)];
    if ((oscAPI == nil) or (paramGetAPI == nil) or (paramSetAPI == nil))
    {
        NSLog (@"Unable to obtain the OSC or parameter APIs in %s:%d", __func__, __LINE__);
        return;
    }
 
    // Get some info about the object
    unsigned int    objWidth;
    unsigned int    objHeight;
    double          objPixelAspectRatio;
    [oscAPI objectWidth:&objWidth
                 height:&objHeight
       pixelAspectRatio:&objPixelAspectRatio];
 
    // Get the point parameter's values
    FxPoint2D   lowerLeft;
    FxPoint2D   upperRight;
    FxPoint2D   mousePosObjSpace;
    [paramGetAPI getXValue:&lowerLeft.x
                    YValue:&lowerLeft.y
                  fromParm:kShape_LowerLeft
                    atTime:time];
 
    [paramGetAPI getXValue:&upperRight.x
                    YValue:&upperRight.y
                  fromParm:kShape_UpperRight
                    atTime:time];
 
    // Get the mouse position in object relative space
    [oscAPI convertPointFromSpace:kFxDrawingCoordinates_CANVAS
                            fromX:mousePositionX
                            fromY:mousePositionY
                          toSpace:kFxDrawingCoordinates_OBJECT
                              toX:&mousePosObjSpace.x
                              toY:&mousePosObjSpace.y];
 
    // Find the change from the last time
    FxPoint2D   delta   = {
        mousePosObjSpace.x - _mouseDownPos.x,
        mousePosObjSpace.y - _mouseDownPos.y
    };
 
    // Save the current location for the next time around
    _mouseDownPos = mousePosObjSpace;
    _mouseMovedPos = mousePosObjSpace;
 
    // Tell the app to update
    *forceUpdate = YES;
 
    // Now respond to the part that the user clicked in
    FxPoint2D   newLowerLeft;
    switch (activePart)
    {
        ...
        case kShapeOSC_LowerLeft:
            newLowerLeft.x = mousePosObjSpace.x;
            newLowerLeft.y = mousePosObjSpace.y;
            newUpperRight = upperRight;
            break;
        ...
    }
 
    // Set the new values
    ...
    [paramSetAPI setXValue:newLowerLeft.x
                    YValue:newLowerLeft.y
                    toParm:kShape_LowerLeft
                    atTime:time];
    ...
    }
}

В дополнение к событиям от нажатия мыши можно также получить события клавиатуры в keyDown:positionY:keyPressed:modifiers:forceUpdate:didHandle:time: и keyUp:positionY:keyPressed:modifiers:forceUpdate:didHandle:time: методы.

Если Вы реализуете FxOnScreenControlAPI_v2 версия протокола, можно получить mouseMovedWithPositionX:positionY:activePart:modifiers:forceUpdate:time:, mouseEnteredWithPositionX:positionY:modifiers:forceUpdate:time:, и mouseExitedWithPositionX:positionY:modifiers:forceUpdate:time: сообщения, когда пользователь перемещает мышь в или из области, покрытой Вашими средствами управления.