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 }