From 9b86bc4b32c1787aea2b04c860f933798d67809c Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Fri, 15 Mar 2019 22:13:12 +1100 Subject: [PATCH] documented effectfield --- Doxyfile | 3 +- effects/effectfield.cpp | 4 +- effects/effectfield.h | 433 +++++++++++++++++++++++++++++++++++++++- effects/effectfields.h | 6 + 4 files changed, 434 insertions(+), 12 deletions(-) diff --git a/Doxyfile b/Doxyfile index c717922fd..2df85e372 100644 --- a/Doxyfile +++ b/Doxyfile @@ -914,7 +914,8 @@ EXCLUDE_SYMLINKS = NO # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = */.git/* \ - */docs/* + */docs/* \ + */include/* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the diff --git a/effects/effectfield.cpp b/effects/effectfield.cpp index 0321e8d76..aa0dbbef6 100644 --- a/effects/effectfield.cpp +++ b/effects/effectfield.cpp @@ -67,8 +67,6 @@ EffectField::EffectField(EffectRow* parent, const QString &i, EffectFieldType t) connect(this, SIGNAL(Changed()), parent->GetParentEffect(), SLOT(FieldChanged())); } -EffectField::~EffectField() {} - EffectRow *EffectField::GetParentRow() { return static_cast(parent()); @@ -97,7 +95,7 @@ QString EffectField::ConvertValueToString(const QVariant &v) QWidget *EffectField::CreateWidget(QWidget *existing) { - return (existing == nullptr) ? new QLabel(tr("(Invalid field)")) : existing; + Q_ASSERT(false); } void EffectField::UpdateWidgetValue(QWidget *, double) {} diff --git a/effects/effectfield.h b/effects/effectfield.h index af3e236e6..9d5dae487 100644 --- a/effects/effectfield.h +++ b/effects/effectfield.h @@ -32,69 +32,486 @@ class EffectRow; class ComboAction; +/** + * @brief The EffectField class + * + * Any user-interactive element of an Effect. Usually a parameter that modifies the effect output, but sometimes just + * a UI object that performs some other function (e.g. LabelField and ButtonField). + * + * EffectField provides a largely abstract interface for Effect classes to pull information from. The class itself + * handles keyframing between linear, bezier, and hold interpolation accessible through GetValueAt(). This class + * is abstract, and therefore never intended to be used on its own. Instead you should always use a derived class. + * + * EffectField objects are *not* UI objects on their own. Instead, they're largely a system of values that can change + * over time. For a widget that the user can use to edit/modify these values, use CreateWidget(). + * + * Derived classes are expected to override at least CreateWidget() to create a visual interactive widget corresponding + * to the field. If this + * field is a value used in the Effect (as most will be), UpdateWidgetValue() should also be overridden to display the + * correct value for this field as the user moves around the Timeline. + * + * If the field is intended to be saved and loaded from Olive project files (as most will be), ConvertStringToValue() + * and ConvertValueToString() may also have to be overridden depending on how the derived class's data works. + */ class EffectField : public QObject { Q_OBJECT public: + /** + * @brief The EffectFieldType enum + * + * Predetermined types of fields. Used throughout Olive to identify what kind of data to expect from GetValueAt(). + * + * This enum is also currently used to match an external XML effect's fields with the correct derived class (e.g. + * EFFECT_FIELD_DOUBLE matches to DoubleField). + */ enum EffectFieldType { + /** Values are doubles. Also corresponds to DoubleField. */ EFFECT_FIELD_DOUBLE, + + /** Values are colors. Also corresponds to ColorField. */ EFFECT_FIELD_COLOR, + + /** Values are strings. Also corresponds to StringField. */ EFFECT_FIELD_STRING, + + /** Values are booleans. Also corresponds to BoolField. */ EFFECT_FIELD_BOOL, + + /** Values are arbitrary data. Also corresponds to ComboField. */ EFFECT_FIELD_COMBO, + + /** Values are font family names (in string). Also corresponds to FontField. */ EFFECT_FIELD_FONT, + + /** Values are filenames (in string). Also corresponds to FileField. */ EFFECT_FIELD_FILE, + + /** Values is a UI object with no data. Corresponds to nothing. */ EFFECT_FIELD_UI }; + /** + * @brief EffectField Constructor + * + * Creates a new EffectField object. + * + * @param parent + * + * The EffectRow to add this field to. This must be a valid EffectRow. The EffectRow takes ownership of the field + * using the QObject parent/child system to automate memory management. EffectFields are never expected + * to change parent during their lifetime. + * + * @param i + * + * Field ID. Must be non-empty. Must also be unique within this Effect. Used for saving/loading values into project + * files so that if ordering of fields are changed, or fields are added/removed from Effects later in development, + * saved values in project files will still link with the correct field. Also used as the uniform variable name + * in GLSL shaders. + * + * @param t + * + * The type of data contained within this field. This is expected to be filled by a derived class. + */ EffectField(EffectRow* parent, const QString& i, EffectFieldType t); - ~EffectField(); + /** + * @brief Get the EffectRow that this field is a member of. + * + * Equivalent to `static_cast(EffectField::parent())` + * + * @return + * + * The EffectRow that this field is a member of. + */ EffectRow* GetParentRow(); + /** + * @brief Get the type of data to expect from this field + * + * @return + * + * A member of the EffectFieldType enum. + */ const EffectFieldType& type(); + + /** + * @brief Get the unique identifier of this field set in the constructor + * + * Mostly used for saving/loading or interacting with GLSL-based shader effects (see EffectField() for more details). + * + * @return + */ const QString& id(); + /** + * @brief Get the value of this field at a given timecode + * + * EffectFields are designed to be keyframable, meaning the user can make the values change over the course of the + * Sequence. This is the main function used through Olive to retrieve what value this field will be at a given time. + * + * A common use case for this function would be EffectField::GetValueAt(EffectField::Now()), which will automatically + * retrieve the timecode at the current playhead. + * + * If the parent EffectRow is NOT keyframing, this function will simply return persistent_data_. If it IS keyframing, + * this will use the values in `keyframes` to determine what value should be specifically at this time. + * (bezier or linear interpolating it between values if necessary). Therefore this function should almost always be + * used to retrieve data from this field as the value will always be correct for the given time. + * + * @param timecode + * + * The time to retrieve the value in clip/media seconds (e.g. 0.0 is the very start of the media, 1.0 is one second + * into the media). + * + * @return + * + * A QVariant representation of the value at the given timecode. + */ QVariant GetValueAt(double timecode); + + /** + * @brief Set the value of this field at a given timecode + * + * EffectFields are designed to be keyframable, meaning the user can make the values change over the course of the + * Sequence. This is the main function used through Olive to set what value this field will be at a given time. + * + * If the parent EffectRow is keyframing, this function will determine whether a keyframe exists at this time already. + * If it does, it will change the value at that keyframe to `value`. Otherwise, it'll create a new keyframe at the + * specified `time` with the specified `value`. + * + * If the parent EffectRow is not keyframing, the data is simply stored in `persistent_data_`. + * + * When constructing an Effect + * + * @param time + * + * The time to retrieve the value at in clip/media seconds (e.g. 0.0 is the very start of the media, 1.0 is one + * second into the media). + * + * @param value + * + * The QVariant value to set at this time. + */ void SetValueAt(double time, const QVariant& value); + /** + * @brief Get the current clip/media time + * + * A convenience function that can be plugged into GetValueAt() to get the value wherever the appropriate Sequence's + * playhead it. + * + * @return + * + * Current clip/media time in seconds. + */ double Now(); - long NowInFrames(); + /** + * @brief Set up keyframing on this field + * + * This should always be called if the user is enabling/disabling keyframing on the parent row. This function will + * move data between persistent_data_ and keyframes depending on whether keyframing is being enabled or disabled. + * + * If keyframing is getting ENABLED, this function will create the first keyframe automatically at the current time + * using the current value in persistent_data_. + * + * If keyframing is getting DISABLED, persistent_data_ is set to the current value at this time (GetValueAt(Now())) + * and delete all current keyframes. + * + * @param enabled + * + * TRUE if keyframing is getting enabled. + * + * @param ca + * + * A valid ComboAction object. It's expected that this function will be part of a larger action to enable/disable + * keyframing on the parent EffectRow, so this function will add commands to this ComboAction. + */ void PrepareDataForKeyframing(bool enabled, ComboAction* ca); + /** + * @brief Get field's column span + * + * Used whenever constructing the effect's UI. EffectFields are visually laid out in a table, and some widgets can + * be set to take up multiple columns for a better visual flow. + * + * @return + * + * The current column span. + */ int GetColumnSpan(); + + /** + * @brief Set field's column span + * + * Used whenever constructing the effect's UI. EffectFields are visually laid out in a table, and some widgets can + * be set to take up multiple columns for a better visual flow. + * + * @param i + * + * The column span to set on this field. + */ void SetColumnSpan(int i); - virtual QVariant ConvertStringToValue(const QString& s); + /** + * @brief Convert a value from this field to a string + * + * When saving effect data to a project file, the data needs to be converted to a string format for saving in XML. + * The needs of this string representation may differ depending on the needs of the derived class, therefore you + * derived classes may need to override it. + * + * Default behavior is a simple QVariant <-> QString conversion, which should suffice in most cases. + * + * @param v + * + * The QVariant data (retrieved from this field) to convert to string + * + * @return + * + * A string representation of the QVariant data provided. + */ virtual QString ConvertValueToString(const QVariant& v); - virtual QWidget* CreateWidget(QWidget* existing = nullptr); - virtual void UpdateWidgetValue(QWidget* widget, double); + /** + * @brief Convert a string to a value appropriate for this field + * + * This function is the inverse of ConvertValueToString(), converting a string back to field data. + * + * @param s + * + * The string to convert to data. + * + * @return + * + * QVariant data converted from the provided string. + */ + virtual QVariant ConvertStringToValue(const QString& s); + + /** + * @brief Create a widget for the user to interact with this field + * + * EffectField objects are *not* UI objects on their own. Instead, they're largely a system of values that can change + * over time. This function creates a QWidget object that can be placed somewhere in the UI so the user can + * interact with and change the data in this field. + * + * This function must be overridden by derived classes in order to create a widget that appropriate for that field's + * data. The derived class is also responsible for + * connecting signals like EnabledChanged(), Clicked(), and any other data that needs to be transferred between the + * widget and the field (setting up the signals and slots to do so). The field does NOT retain ownership (or any + * reference for that matter) to widgets it creates, + * so keeping the widget and field up to date with each other relies solely on setting up signals and slots. + * Infinite widgets can be created from a single field and used throughout Olive this way. + * + * Ownership is passed to the caller, and therefore the caller is responsible for freeing it. + * + * @param existing + * + * Olive allows multiple effects to attach to one UI layout. Pass a QWidget to this parameter (instead of nullptr) + * to attach this field additionally to the widget's signals/slots without creating a new one. The QWidget must be a + * widget previously created from the same derived class type or the result is undefined. + * + * @return + * + * A new QWidget object for this EffectField, or the same QWidget passed to `existing` if one was specified. + */ + virtual QWidget* CreateWidget(QWidget* existing = nullptr) = 0; + + /** + * @brief Update a widget created by CreateWidget() using the value at a given time + * + * Use this function to update a QWidget (obtained from CreateWidget()) with the correct value from the field at a + * given time. + * + * Since only the derived classes know what type of QWidget it created in CreateWidget() and how to work with them, + * derived classes are also expected to override this function if the field is an active value used in the Effect + * that should visually update as the user moves around the Timeline. However if the field does NOT need to update + * live (e.g. the field is just a UI wrapper like LabelField or ButtonField), this function does not need to be + * overridden as the default behavior (to do nothing) will suffice in those cases. + * + * @param widget + * + * The QWidget to set the value of (must be a QWidget obtained from CreateWidget() or the behavior is undefined). + * + * @param timecode + * + * The time in clip/media seconds to retrieve data from. + */ + virtual void UpdateWidgetValue(QWidget* widget, double timecode); + + /** + * @brief Get the correct X position/time value of a bezier keyframe's handles + * + * Retrieves the X value (time value) of a bezier keyframe's handles. Internally, the handles' X values are allowed + * to be arbitrary values. This however can lead to inadvertently creating impossible bezier curves (ones that, for + * example, mathematically loop over each other, but obviously a field can't have two values at the same time). + * + * This function returns the keyframe handles' X values adjusted to prevent this from happening. All calculations + * are consistent (i.e. the post handle of one keyframe will be adjusted the same way as the pre handle of the + * keyframe before it). It's recommended to always use this function to retrieve keyframe handle X values. + * + * @param key + * + * Index of the keyframe (in `keyframes`) to retrieve the handle position from. + * + * @param post + * + * FALSE to retrieve the "pre" handle (handle to the left of the keyframe), TRUE to retrieve the "post" handle + * (handle to the right of the keyframe). + * + * @return + * + * The adjusted X value of that keyframe handle. + */ double GetValidKeyframeHandlePosition(int key, bool post); + /** + * @brief Return whether this field is enabled or not + * + * @return + * + * TRUE if this field is enabled. + */ bool IsEnabled(); + + /** + * @brief Set the enabled state of this field + * @param e + * + * TRUE to enable this field, FALSE to disable it. + */ void SetEnabled(bool e); + /** + * @brief Persistent data object + * + * If the parent EffectRow is not keyframing, all field data is stored and retrieved here. If the row IS keyframing, + * this variable goes basically unused unless `keyframes` is empty. + * + * NOTE: It is NOT recommended to access this variable directly. Use GetValueAt() instead. + */ QVariant persistent_data_; + + /** + * @brief Keyframe array + * + * Contains all data about this field's keyframes, from the keyframe times, to their data, to their type (linear, + * bezier, or hold), to the bezier handles (if using bezier). If the row is not keyframing, this array is never used + * (`persistent_data_` is used instead). If it is, this array will always be used unless the array is empty, in which + * case `persistent_data_` will be used again. + */ QVector keyframes; signals: + /** + * @brief Changed signal + * + * Emitted whenever SetValueAt() is called in order to trigger a UI update and Viewer repaint. Note this is NOT + * triggered as the value changes from keyframing. Only when the user themselves triggers a change. + */ void Changed(); + + /** + * @brief Clicked signal + * + * Emitted when the user clicks on a QWidget attached to this field. Derived classes should connect this to the + * clicked signal of any QWidget's created (or attached) in CreateWidget(). + */ void Clicked(); + /** + * @brief Enable change state signal + * + * Emitted when the field's enabled state is changed through SetEnabled(). Derived classes should connect this to the + * setEnabled() slot of any QWidget's created (or attached) in CreateWidget(). + */ void EnabledChanged(bool); private: + /** + * @brief Internal type variable set in the constructor. Access with type(). + */ EffectFieldType type_; + + /** + * @brief Internal unique identifier for this field set in the constructor. Access with id(). + */ QString id_; + /** + * @brief Used by GetValueAt() to determine whether to use keyframe data or persistent data + * @return + * + * TRUE if this keyframe data should be retrieved, FALSE if persistent data should be retrieved + */ bool HasKeyframes(); - double frameToTimecode(long frame); - long timecodeToFrame(double timecode); - void get_keyframe_data(double timecode, int& before, int& after, double& d); + /** + * @brief Convert clip time in frames to clip time in seconds + * + * @param frame + * + * Clip time in frames + * + * @return + * + * Clip time in seconds + */ + double FrameToSeconds(long frame); + + /** + * @brief Convert clip time in seconds to clip time in frames + * + * @param seconds + * + * Clip time in seconds + * + * @return + * + * Clip time in frames + */ + long SecondsToFrame(double seconds); + + /** + * @brief Internal function for determining where we are between the available keyframes + * + * @param timecode + * + * Timecode to get keyframe data at + * + * @param before + * + * The index (in the keyframes array) in the keyframe prior to this timecode. + * + * @param after + * + * The index (in the keyframes array) in the keyframe after this timecode. + * + * @param d + * + * The progress between the `before` keyframe and `after` keyframe from 0.0 to 1.0. + */ + void GetKeyframeData(double timecode, int& before, int& after, double& d); + + /** + * @brief Retrieve the current clip as a frame number + * + * Same as Now() but retrieves the value as a frame number (in the appropriate Sequence's frame rate) instead of + * seconds. + * + * @return + * + * The current clip time in frames + */ + long NowInFrames(); + + /** + * @brief Internal enabled value + */ bool enabled_; + + /** + * @brief Internal column span value + */ int colspan_; }; diff --git a/effects/effectfields.h b/effects/effectfields.h index 1c8e687e2..1a6e1777a 100644 --- a/effects/effectfields.h +++ b/effects/effectfields.h @@ -1,6 +1,12 @@ #ifndef EFFECTFIELDS_H #define EFFECTFIELDS_H +/** + + A simple convenience header for including all the available EffectField derivations. + + */ + #include "fields/boolfield.h" #include "fields/buttonfield.h" #include "fields/colorfield.h"