File indexing completed on 2025-02-02 04:11:33

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "timeline_items.hpp"
0008 
0009 #include "command/undo_macro_guard.hpp"
0010 #include "keyframe_transition_data.hpp"
0011 
0012 using namespace glaxnimate::gui;
0013 using namespace glaxnimate;
0014 
0015 bool timeline::enable_debug = false;
0016 
0017     timeline::KeyframeSplitItem::KeyframeSplitItem(AnimatableItem* parent)
0018     : QGraphicsObject(parent),
0019     visual_node(parent->object()->cast<model::VisualNode>())
0020 {
0021     setFlags(
0022         QGraphicsItem::ItemIsSelectable|
0023         QGraphicsItem::ItemIgnoresTransformations
0024     );
0025 }
0026 
0027 void timeline::KeyframeSplitItem::set_enter(model::KeyframeTransition::Descriptive enter)
0028 {
0029     icon_enter = KeyframeTransitionData::data(enter, KeyframeTransitionData::Finish).icon();
0030     pix_enter = icon_enter.pixmap(icon_size);
0031     update();
0032 }
0033 
0034 void timeline::KeyframeSplitItem::set_exit(model::KeyframeTransition::Descriptive exit)
0035 {
0036     icon_exit = KeyframeTransitionData::data(exit, KeyframeTransitionData::Start).icon();
0037     pix_exit = icon_exit.pixmap(icon_size);
0038     update();
0039 }
0040 
0041 void timeline::KeyframeSplitItem::paint(QPainter * painter, const QStyleOptionGraphicsItem *, QWidget * widget)
0042 {
0043     if ( isSelected() )
0044     {
0045         QColor sel_border = widget->palette().color(QPalette::Highlight);
0046         if ( parentItem()->isSelected() )
0047             sel_border = widget->palette().color(QPalette::HighlightedText);
0048         QColor sel_fill = sel_border;
0049         sel_fill.setAlpha(128);
0050         painter->setPen(QPen(sel_border, pen));
0051         painter->setBrush(sel_fill);
0052         painter->drawRect(boundingRect());
0053     }
0054 
0055     painter->drawPixmap(-icon_size/2, -icon_size/2, half_icon_size.width(), half_icon_size.height(), pix_enter);
0056     painter->drawPixmap(0, -icon_size/2, half_icon_size.width(), half_icon_size.height(), pix_exit);
0057 }
0058 
0059 void timeline::KeyframeSplitItem::mousePressEvent(QGraphicsSceneMouseEvent * event)
0060 {
0061     if ( event->button() == Qt::LeftButton )
0062     {
0063 
0064         if ( event->modifiers() & Qt::AltModifier )
0065         {
0066             line()->cycle_keyframe_transition(time());
0067             return;
0068         }
0069 
0070         event->accept();
0071         bool multi_select = (event->modifiers() & (Qt::ControlModifier|Qt::ShiftModifier)) != 0;
0072 
0073         if ( multi_select && isSelected() )
0074         {
0075             setSelected(false);
0076             return;
0077         }
0078 
0079         if ( !multi_select && !isSelected() )
0080             scene()->clearSelection();
0081 
0082         setSelected(true);
0083         for ( auto item : scene()->selectedItems() )
0084         {
0085             if ( auto kf = dynamic_cast<KeyframeSplitItem*>(item) )
0086                 kf->drag_init();
0087         }
0088     }
0089     else
0090     {
0091         QGraphicsObject::mousePressEvent(event);
0092     }
0093 }
0094 
0095 void timeline::KeyframeSplitItem::mouseMoveEvent(QGraphicsSceneMouseEvent * event)
0096 {
0097     if ( (event->buttons() & Qt::LeftButton) && isSelected() && !(event->modifiers() & Qt::AltModifier) )
0098     {
0099         event->accept();
0100         qreal delta = qRound(event->scenePos().x()) - drag_start;
0101         for ( auto item : scene()->selectedItems() )
0102         {
0103             if ( auto kf = dynamic_cast<KeyframeSplitItem*>(item) )
0104                 kf->drag_move(delta);
0105         }
0106     }
0107     else
0108     {
0109         QGraphicsObject::mouseMoveEvent(event);
0110     }
0111 }
0112 
0113 timeline::AnimatableItem* timeline::KeyframeSplitItem::line() const
0114 {
0115     return static_cast<timeline::AnimatableItem*>(parentItem());
0116 }
0117 
0118 void timeline::KeyframeSplitItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
0119 {
0120     if ( event->button() == Qt::LeftButton && isSelected() && !(event->modifiers() & Qt::AltModifier) )
0121     {
0122         event->accept();
0123 
0124         if ( drag_start == x() )
0125             return;
0126 
0127         std::map<AnimatableItem*, std::vector<AnimatableItem::DragData>> items;
0128         for ( auto item : scene()->selectedItems() )
0129         {
0130             if ( auto kf = dynamic_cast<KeyframeSplitItem*>(item) )
0131             {
0132 
0133                 if ( kf->drag_allowed() )
0134                     items[kf->line()].push_back({kf, kf->drag_start, kf->time()});
0135 
0136                 kf->drag_end();
0137             }
0138         }
0139 
0140         if ( !items.empty() )
0141         {
0142             command::UndoMacroGuard guard(i18n("Drag Keyframes"), line()->object()->document());
0143 
0144             for ( const auto& p : items )
0145                 p.first->keyframes_dragged(p.second);
0146         }
0147     }
0148     else
0149     {
0150         QGraphicsObject::mouseReleaseEvent(event);
0151     }
0152 }
0153 
0154 timeline::LineItem::LineItem(quintptr id, model::Object* obj, int time_start, int time_end, int height):
0155     time_start(time_start),
0156     time_end(time_end),
0157     height_(height),
0158     object_(obj),
0159     id_(id)
0160 {
0161     setFlags(QGraphicsItem::ItemIsSelectable);
0162 }
0163 
0164 void timeline::LineItem::click_selected(bool selected, bool replace_selection)
0165 {
0166     Q_EMIT clicked(id_, selected, replace_selection);
0167 }
0168 
0169 
0170 model::Object* timeline::LineItem::object() const
0171 {
0172     return object_;
0173 }
0174 
0175 void timeline::LineItem::set_time_start(int time)
0176 {
0177     time_start = time;
0178     for ( auto row : rows_ )
0179         row->set_time_start(time);
0180     on_set_time_start(time);
0181     prepareGeometryChange();
0182 }
0183 
0184 void timeline::LineItem::set_time_end(int time)
0185 {
0186     time_end = time;
0187     for ( auto row : rows_ )
0188         row->set_time_end(time);
0189     on_set_time_end(time);
0190     prepareGeometryChange();
0191 }
0192 
0193 QRectF timeline::LineItem::boundingRect() const
0194 {
0195     return QRectF(time_start, 0, time_end, height_);
0196 }
0197 
0198 int timeline::LineItem::row_height() const
0199 {
0200     return height_;
0201 }
0202 
0203 int timeline::LineItem::row_count() const
0204 {
0205     return rows_.size();
0206 }
0207 
0208 int timeline::LineItem::visible_height() const
0209 {
0210     return visible_rows_ * row_height();
0211 }
0212 
0213 void timeline::LineItem::add_row(LineItem* row, int index)
0214 {
0215     row->setParentItem(this);
0216     row->setPos(0, height_ * (index+1));
0217     rows_.insert(rows_.begin() + index, row);
0218     if ( !expanded_ )
0219         row->setVisible(false);
0220     adjust_row_vis(row->visible_rows());
0221 }
0222 
0223 void timeline::LineItem::remove_rows(int first, int last)
0224 {
0225     /// \todo Figure out why these can occur
0226     if ( first >= int(rows_.size()) )
0227         return;
0228     if ( last >= int(rows_.size()) )
0229         last = rows_.size() - 1;
0230 
0231     int delta = 0;
0232     for ( int i = first; i <= last && i < int(rows_.size()); i++ )
0233     {
0234         LineItem* row = rows_[i];
0235         row->emit_removed();
0236         delta -= row->visible_rows();
0237         delete row;
0238     }
0239     rows_.erase(rows_.begin() + first, rows_.begin() + last + 1);
0240     adjust_row_vis(delta);
0241 }
0242 
0243 void timeline::LineItem::move_row(int from, int to)
0244 {
0245     LineItem* row = rows_[from];
0246     int delta = row->visible_rows();
0247 
0248     rows_.erase(rows_.begin() + from);
0249     rows_.insert(rows_.begin() + to, row);
0250 
0251     adjust_row_vis(-delta, false);
0252 }
0253 
0254 void timeline::LineItem::expand()
0255 {
0256     if ( expanded_ )
0257         return;
0258 
0259     expanded_ = true;
0260 
0261     int old_vis = visible_rows_;
0262     visible_rows_ = 1;
0263 
0264     for ( auto item : rows_ )
0265     {
0266         item->setVisible(true);
0267         visible_rows_ += item->visible_rows();
0268     }
0269 
0270     propagate_row_vis(visible_rows_ - old_vis);
0271 }
0272 
0273 void timeline::LineItem::collapse()
0274 {
0275     if ( !expanded_ )
0276         return;
0277 
0278     int old_vis = visible_rows_;
0279     for ( auto item : rows_ )
0280         item->setVisible(false);
0281 
0282     visible_rows_ = 1;
0283     propagate_row_vis(visible_rows_ - old_vis);
0284 
0285     expanded_ = false;
0286 }
0287 
0288 void timeline::LineItem::set_expanded(bool expanded)
0289 {
0290     if ( expanded != expanded_ )
0291     {
0292         if ( expanded )
0293             expand();
0294         else
0295             collapse();
0296     }
0297 }
0298 
0299 
0300 bool timeline::LineItem::is_expanded()
0301 {
0302     return expanded_;
0303 }
0304 
0305 timeline::LineItem* timeline::LineItem::parent_line() const
0306 {
0307     return static_cast<LineItem*>(parentItem());
0308 }
0309 
0310 int timeline::LineItem::visible_rows() const
0311 {
0312     return visible_rows_;
0313 }
0314 
0315 void timeline::LineItem::raw_clear()
0316 {
0317     for ( auto row : rows_ )
0318         delete row;
0319     rows_.clear();
0320     visible_rows_ = 1;
0321 }
0322 
0323 void timeline::LineItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
0324 {
0325     event->accept();
0326 }
0327 
0328 void timeline::LineItem::mousePressEvent(QGraphicsSceneMouseEvent * event)
0329 {
0330     if ( event->button() == Qt::LeftButton )
0331     {
0332         bool selected = true;
0333         bool multiple = event->modifiers() & Qt::ControlModifier;
0334         if ( !multiple )
0335             scene()->clearSelection();
0336         else
0337             selected = !isSelected();
0338 
0339         setSelected(selected);
0340         click_selected(selected, !multiple);
0341 
0342         event->accept();
0343     }
0344 }
0345 
0346 void timeline::LineItem::propagate_row_vis(int delta)
0347 {
0348     if ( isVisible() && parent_line() && delta && expanded_ )
0349         parent_line()->adjust_row_vis(delta);
0350 }
0351 
0352 void timeline::LineItem::adjust_row_vis(int delta, bool propagate)
0353 {
0354     if ( expanded_ )
0355         visible_rows_ += delta;
0356 
0357     int y = 1;
0358     for ( auto row : rows_ )
0359     {
0360         row->setPos(0, height_ * y);
0361         y += row->visible_rows_;
0362     }
0363 
0364     if ( propagate )
0365         propagate_row_vis(delta);
0366 }
0367 
0368 void timeline::LineItem::emit_removed()
0369 {
0370     for ( auto row : rows_ )
0371         row->emit_removed();
0372     Q_EMIT removed(id_);
0373 }
0374 
0375 void timeline::LineItem::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
0376 {
0377     if ( isSelected() )
0378         painter->fillRect(option->rect, widget->palette().highlight());
0379 
0380     // Debugging print, it shows some meta info on the line, useful when tweaking the layout
0381     if ( enable_debug )
0382     {
0383         painter->save();
0384         painter->setBrush(Qt::black);
0385         painter->scale(0.5, 1);
0386         QFont f;
0387         painter->setFont(f);
0388 
0389         QString debug_string = metaObject()->className();
0390         debug_string = debug_string.mid(debug_string.indexOf("::")+2);
0391         painter->drawText(0, row_height(), debug_string);
0392 
0393         painter->drawText(time_end * 3/4, row_height(), QString::number(id_));
0394 
0395         auto item = property_item();
0396         if ( item.property )
0397             debug_string = item.property->name();
0398         else if ( item.object )
0399             debug_string = item.object->object_name();
0400         else
0401             debug_string = "NULL";
0402         painter->drawText(time_end, row_height(), debug_string);
0403 
0404         painter->restore();
0405     }
0406 }
0407 
0408 timeline::AnimatableItem::AnimatableItem(quintptr id, model::Object* obj, model::AnimatableBase* animatable, int time_start, int time_end, int height)
0409     : LineItem(id, obj, time_start, time_end, height),
0410     animatable(animatable)
0411 {
0412     for ( int i = 0; i < animatable->keyframe_count(); i++ )
0413         add_keyframe(i);
0414 
0415     connect(animatable, &model::AnimatableBase::keyframe_added, this, &AnimatableItem::add_keyframe);
0416     connect(animatable, &model::AnimatableBase::keyframe_removed, this, &AnimatableItem::remove_keyframe);
0417     connect(animatable, &model::AnimatableBase::keyframe_updated, this, &AnimatableItem::update_keyframe);
0418 }
0419 
0420 std::pair<model::KeyframeBase*, model::KeyframeBase*> timeline::AnimatableItem::keyframes(KeyframeSplitItem* item)
0421 {
0422     for ( int i = 0; i < int(kf_split_items.size()); i++ )
0423     {
0424         if ( kf_split_items[i] == item )
0425         {
0426             if ( i == 0 )
0427                 return {nullptr, animatable->keyframe(i)};
0428             return {animatable->keyframe(i-1), animatable->keyframe(i)};
0429         }
0430     }
0431 
0432     return {nullptr, nullptr};
0433 }
0434 
0435 int timeline::AnimatableItem::type() const
0436 {
0437     return int(ItemTypes::AnimatableItem);
0438 }
0439 
0440 item_models::PropertyModelFull::Item timeline::AnimatableItem::property_item() const
0441 {
0442     return {object(), animatable};
0443 }
0444 
0445 void timeline::AnimatableItem::add_keyframe(int index)
0446 {
0447     model::KeyframeBase* kf = animatable->keyframe(index);
0448     if ( index == 0 && !kf_split_items.empty() )
0449         kf_split_items[0]->set_enter(kf->transition().after_descriptive());
0450 
0451     model::KeyframeBase* prev = index > 0 ? animatable->keyframe(index-1) : nullptr;
0452     auto item = new KeyframeSplitItem(this);
0453     item->setPos(kf->time(), row_height() / 2.0);
0454     item->set_exit(kf->transition().before_descriptive());
0455     item->set_enter(prev ? prev->transition().after_descriptive() : model::KeyframeTransition::Hold);
0456     kf_split_items.insert(kf_split_items.begin() + index, item);
0457 
0458     connect(kf, &model::KeyframeBase::transition_changed, this, &AnimatableItem::transition_changed);
0459 }
0460 
0461 void timeline::AnimatableItem::remove_keyframe(int index)
0462 {
0463     delete kf_split_items[index];
0464     kf_split_items.erase(kf_split_items.begin() + index);
0465     if ( index < int(kf_split_items.size()) && index > 0 )
0466     {
0467         kf_split_items[index]->set_enter(animatable->keyframe(index-1)->transition().after_descriptive());
0468     }
0469 }
0470 
0471 void timeline::AnimatableItem::transition_changed(model::KeyframeTransition::Descriptive before, model::KeyframeTransition::Descriptive after)
0472 {
0473     int index = animatable->keyframe_index(static_cast<model::KeyframeBase*>(sender()));
0474     if ( index == -1 )
0475         return;
0476 
0477     kf_split_items[index]->set_exit(before);
0478 
0479 
0480     index += 1;
0481     if ( index >= int(kf_split_items.size()) )
0482         return;
0483 
0484     kf_split_items[index]->set_enter(after);
0485 }
0486 
0487 void timeline::AnimatableItem::keyframes_dragged(const std::vector<DragData>& keyframe_items)
0488 {
0489     for ( auto kf : keyframe_items )
0490     {
0491         int index = animatable->keyframe_index(kf.from);
0492         auto cmd = new command::MoveKeyframe(animatable, index, kf.to);
0493         kf.item->setSelected(false);
0494         animatable->object()->push_command(cmd);
0495     }
0496 
0497 
0498     for ( auto kf : keyframe_items )
0499     {
0500         int index = animatable->keyframe_index(kf.to);
0501         kf_split_items[index]->setSelected(true);
0502     }
0503 }
0504 
0505 void glaxnimate::gui::timeline::AnimatableItem::cycle_keyframe_transition(model::FrameTime time)
0506 {
0507     int index = animatable->keyframe_index(time);
0508     auto kf = animatable->keyframe(index);
0509     if ( !kf )
0510         return;
0511 
0512     auto desc = index == 0 ? kf->transition().after_descriptive() : kf->transition().before_descriptive();
0513 
0514     switch ( desc )
0515     {
0516         case model::KeyframeTransition::Hold:
0517             desc = model::KeyframeTransition::Linear;
0518             break;
0519         case model::KeyframeTransition::Linear:
0520             desc = model::KeyframeTransition::Ease;
0521             break;
0522         case model::KeyframeTransition::Ease:
0523             desc = model::KeyframeTransition::Fast;
0524             break;
0525         case model::KeyframeTransition::Fast:
0526             desc = model::KeyframeTransition::Overshoot;
0527             break;
0528         case model::KeyframeTransition::Overshoot:
0529             desc = model::KeyframeTransition::Hold;
0530             break;
0531         case model::KeyframeTransition::Custom:
0532             desc = model::KeyframeTransition::Hold;
0533             break;
0534     }
0535 
0536     {
0537         command::UndoMacroGuard guard(i18n("Update keyframe transition"), animatable->object()->document());
0538         if ( index > 0 )
0539         {
0540             auto kf_before = animatable->keyframe(index - 1);
0541             auto left_trans = kf_before->transition();
0542             left_trans.set_after_descriptive(desc);
0543             animatable->object()->push_command(new command::SetKeyframeTransition(animatable, index-1, left_trans));
0544         }
0545 
0546         auto right_trans = kf->transition();
0547         right_trans.set_before_descriptive(desc);
0548         animatable->object()->push_command(new command::SetKeyframeTransition(animatable, index, right_trans));
0549     }
0550 }
0551 
0552 
0553 void timeline::AnimatableItem::update_keyframe(int index, model::KeyframeBase* kf)
0554 {
0555     auto item_start = kf_split_items[index];
0556     item_start->setPos(kf->time(), row_height() / 2.0);
0557     item_start->set_exit(kf->transition().before_descriptive());
0558 
0559     if ( index == 0 )
0560         item_start->set_enter(model::KeyframeTransition::Hold);
0561 
0562     if ( index < int(kf_split_items.size()) - 1 )
0563     {
0564         auto item_end = kf_split_items[index+1];
0565         item_end->set_enter(kf->transition().after_descriptive());
0566     }
0567 }