diff --git a/CHANGELOG.md b/CHANGELOG.md index b1e318aa..0c954b2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Alt + click on bezier points cycles between tangent symmetry modes (Ctrl+click still works) * Changing a bezier point from corner to smooth will add tangents if they are missing * The import image dialog now allows importing multiple images at once + * There is an icon on the timeline to quickly toggle keyframes * UI: * Middle mouse drag now pans the timeline * Misc: @@ -15,6 +16,7 @@ * When drawing bezier points that don't have tangents are correctly marked as corner * The play button now resumes from the current frame rather than resetting to the start * Fixed saving custom templates + * Toggling visibility / lock of a layer by clicking on its icon now adds an undo/redo action ## 0.5.4 diff --git a/src/gui/item_models/property_model_base.cpp b/src/gui/item_models/property_model_base.cpp index 43a79305..79704405 100644 --- a/src/gui/item_models/property_model_base.cpp +++ b/src/gui/item_models/property_model_base.cpp @@ -760,6 +760,23 @@ model::DocumentNode * item_models::PropertyModelBase::node(const QModelIndex& in return qobject_cast(tree->object); } +model::AnimatableBase* item_models::PropertyModelBase::Private::animatable(Subtree* tree) +{ + if ( !tree || !tree->prop ) + return nullptr; + + model::PropertyTraits traits = tree->prop->traits(); + if ( traits.flags & model::PropertyTraits::Animated ) + return static_cast(tree->prop); + + return nullptr; +} + +model::AnimatableBase* item_models::PropertyModelBase::animatable(const QModelIndex& index) const +{ + return d->animatable(d->node_from_index(index)); +} + item_models::PropertyModelBase::Private::Subtree* item_models::PropertyModelBase::Private::add_property( diff --git a/src/gui/item_models/property_model_base.hpp b/src/gui/item_models/property_model_base.hpp index 98233ef0..64ea3273 100644 --- a/src/gui/item_models/property_model_base.hpp +++ b/src/gui/item_models/property_model_base.hpp @@ -63,6 +63,7 @@ class PropertyModelBase : public DocumentModelBase model::DocumentNode* node(const QModelIndex& index) const override; QModelIndex node_index(model::DocumentNode* node) const override; model::Document* document() const override; + model::AnimatableBase* animatable(const QModelIndex& index) const; private slots: void property_changed(const model::BaseProperty* prop, const QVariant& value); diff --git a/src/gui/item_models/property_model_full.cpp b/src/gui/item_models/property_model_full.cpp index 83473c10..a18f3296 100644 --- a/src/gui/item_models/property_model_full.cpp +++ b/src/gui/item_models/property_model_full.cpp @@ -7,6 +7,8 @@ #include "property_model_full.hpp" #include "property_model_private.hpp" +#include + #include "model/stretchable_time.hpp" #include "model/assets/assets.hpp" @@ -50,13 +52,13 @@ class item_models::PropertyModelFull::Private : public PropertyModelBase::Privat QModelIndex ind = node_index(visual); QModelIndex par = node_index(visual->docnode_parent()); QModelIndex changed = model->index(ind.row(), ColumnVisible, par); - model->dataChanged(changed, changed, {Qt::DecorationRole}); + model->dataChanged(changed, changed, {Qt::DecorationRole, Qt::ToolTipRole}); }); connect(visual, &model::VisualNode::docnode_locked_changed, model, [this, visual]() { QModelIndex ind = node_index(visual); QModelIndex par = node_index(visual->docnode_parent()); QModelIndex changed = model->index(ind.row(), ColumnLocked, par); - model->dataChanged(changed, changed, {Qt::DecorationRole}); + model->dataChanged(changed, changed, {Qt::DecorationRole, Qt::ToolTipRole}); }); connect(visual, &model::VisualNode::docnode_group_color_changed, model, [this, visual]() { QModelIndex ind = node_index(visual); @@ -198,19 +200,63 @@ class item_models::PropertyModelFull::Private : public PropertyModelBase::Privat QVariant data_color(Subtree* tree, int role) { - if ( tree->visual_node && ( role == Qt::DisplayRole || role == Qt::EditRole ) ) - return tree->visual_node->docnode_group_color(); + if ( tree->visual_node ) + { + if ( role == Qt::DisplayRole || role == Qt::EditRole ) + return tree->visual_node->docnode_group_color(); + if ( role == Qt::ToolTipRole ) + return tr("Group Color"); + } return {}; } + QIcon transparent_icon(const QIcon& icon, qreal alpha = 0.3) + { + QIcon out; + + for ( const auto& size : icon.availableSizes() ) + { + QPixmap pixmap = icon.pixmap(size); + QPixmap outpix(pixmap.size()); + outpix.fill(Qt::transparent); + + QPainter painter(&outpix); + painter.setOpacity(alpha); + painter.drawPixmap(0, 0, pixmap); + + out.addPixmap(outpix); + } + + return out; + } + QVariant data_visible(Subtree* tree, int role) { - if ( tree->visual_node && ( role == Qt::DecorationRole ) ) + if ( tree->visual_node ) + { + if ( role == Qt::DecorationRole ) + { + if ( tree->visual_node->visible.get() ) + return QIcon::fromTheme("view-visible"); + return QIcon::fromTheme("view-hidden"); + } + else if ( role == Qt::ToolTipRole ) + { + if ( tree->visual_node->visible.get() ) + return tr("Visible"); + return tr("Hidden"); + } + } + else if ( model::AnimatableBase* anprop = animatable(tree) ) { - if ( tree->visual_node->visible.get() ) - return QIcon::fromTheme("view-visible"); - return QIcon::fromTheme("view-hidden"); + if ( role == Qt::DecorationRole ) + { + auto frame_status = anprop->keyframe_status(document->current_time()); + if ( frame_status == model::AnimatableBase::IsKeyframe ) + return QIcon::fromTheme("keyframe"); + return transparent_icon(QIcon::fromTheme("keyframe-disable")); + } } return {}; @@ -218,11 +264,20 @@ class item_models::PropertyModelFull::Private : public PropertyModelBase::Privat QVariant data_locked(Subtree* tree, int role) { - if ( tree->visual_node && ( role == Qt::DecorationRole ) ) + if ( tree->visual_node ) { - if ( tree->visual_node->locked.get() ) - return QIcon::fromTheme("object-locked"); - return QIcon::fromTheme("object-unlocked"); + if ( role == Qt::DecorationRole ) + { + if ( tree->visual_node->locked.get() ) + return QIcon::fromTheme("object-locked"); + return QIcon::fromTheme("object-unlocked"); + } + else if ( role == Qt::ToolTipRole ) + { + if ( tree->visual_node->locked.get() ) + return tr("Locked"); + return tr("Unlocked"); + } } return {}; @@ -380,16 +435,8 @@ QVariant item_models::PropertyModelFull::headerData(int section, Qt::Orientation return tr("Value"); break; case ColumnColor: - if ( role == Qt::ToolTipRole ) - return tr("Group Color"); - break; case ColumnLocked: - if ( role == Qt::ToolTipRole ) - return tr("Locked"); - break; case ColumnVisible: - if ( role == Qt::ToolTipRole ) - return tr("Visible"); break; } } diff --git a/src/gui/item_models/property_model_private.hpp b/src/gui/item_models/property_model_private.hpp index 011a8afe..de2db4a9 100644 --- a/src/gui/item_models/property_model_private.hpp +++ b/src/gui/item_models/property_model_private.hpp @@ -113,6 +113,8 @@ class glaxnimate::gui::item_models::PropertyModelBase::Private void clean_subtree(Subtree* node); + model::AnimatableBase* animatable(Subtree* node); + model::Document* document = nullptr; std::vector roots; id_type next_id = 1; diff --git a/src/gui/widgets/timeline/compound_timeline_widget.cpp b/src/gui/widgets/timeline/compound_timeline_widget.cpp index 825a1857..e10734f6 100644 --- a/src/gui/widgets/timeline/compound_timeline_widget.cpp +++ b/src/gui/widgets/timeline/compound_timeline_widget.cpp @@ -524,14 +524,30 @@ void CompoundTimelineWidget::expand_index(const QModelIndex& index) void CompoundTimelineWidget::click_index ( const QModelIndex& index ) { - auto node = d->property_model.visual_node(d->comp_model.mapToSource(index)); - if ( !node ) - return; - - if ( index.column() == item_models::PropertyModelFull::ColumnVisible ) - node->visible.set(!node->visible.get()); - else if ( index.column() == item_models::PropertyModelFull::ColumnLocked ) - node->locked.set(!node->locked.get()); + auto source_index = d->comp_model.mapToSource(index); + if ( auto node = d->property_model.visual_node(source_index) ) + { + if ( index.column() == item_models::PropertyModelFull::ColumnVisible ) + node->visible.set_undoable(!node->visible.get()); + else if ( index.column() == item_models::PropertyModelFull::ColumnLocked ) + node->locked.set_undoable(!node->locked.get()); + } + else if ( auto anprop = d->property_model.animatable(source_index) ) + { + if ( index.column() == item_models::PropertyModelFull::ColumnVisible ) + { + auto time = d->property_model.document()->current_time(); + auto frame_status = anprop->keyframe_status(time); + if ( frame_status == model::AnimatableBase::IsKeyframe ) + { + d->property_model.document()->push_command(new command::RemoveKeyframeTime(anprop, time)); + } + else + { + d->property_model.document()->push_command(new command::SetKeyframe(anprop, time, anprop->value(), true)); + } + } + } } void CompoundTimelineWidget::_on_selection_changed(const QItemSelection &selected, const QItemSelection &deselected)