Создание экранных средств управления
В дополнение к традиционным средствам управления параметром в Инспекторе сменные разработчики могут создать средства управления для своих плагинов непосредственно на холсте по видеозаписи пользователя и другим объектам. Посмотрите рисунок 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-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: сообщения, когда пользователь перемещает мышь в или из области, покрытой Вашими средствами управления.