File indexing completed on 2025-02-02 04:11:34
0001 /* 0002 * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #pragma once 0008 0009 #include <QPainter> 0010 #include <QGraphicsScene> 0011 #include <QGraphicsObject> 0012 #include <QStyleOptionGraphicsItem> 0013 #include <QGraphicsSceneMouseEvent> 0014 0015 #include "app/application.hpp" 0016 #include "command/animation_commands.hpp" 0017 #include "model/document.hpp" 0018 #include "model/shapes/precomp_layer.hpp" 0019 0020 #include "graphics/handle.hpp" 0021 #include "item_models/property_model_full.hpp" 0022 0023 namespace glaxnimate::gui::timeline { 0024 0025 extern bool enable_debug; 0026 0027 enum class ItemTypes 0028 { 0029 LineItem = QGraphicsItem::UserType + 1, 0030 ObjectLineItem, 0031 AnimatableItem, 0032 PropertyLineItem, 0033 ObjectListLineItem, 0034 }; 0035 0036 class LineItem : public QGraphicsObject 0037 { 0038 Q_OBJECT 0039 0040 public: 0041 LineItem(quintptr id, model::Object* obj, int time_start, int time_end, int height); 0042 0043 model::Object* object() const; 0044 0045 void set_time_start(int time); 0046 0047 void set_time_end(int time); 0048 0049 QRectF boundingRect() const override; 0050 0051 void paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) override; 0052 0053 int row_height() const; 0054 0055 int row_count() const; 0056 0057 int visible_height() const; 0058 0059 void add_row(LineItem* row, int index); 0060 0061 void remove_rows(int first, int last); 0062 0063 void move_row(int from, int to); 0064 0065 void expand(); 0066 0067 void collapse(); 0068 0069 void set_expanded(bool expanded); 0070 0071 bool is_expanded(); 0072 0073 LineItem* parent_line() const; 0074 0075 int visible_rows() const; 0076 0077 void raw_clear(); 0078 0079 virtual item_models::PropertyModelFull::Item property_item() const 0080 { 0081 return {}; 0082 } 0083 0084 const std::vector<LineItem*>& rows() const 0085 { 0086 return rows_; 0087 } 0088 0089 int type() const override { return int(ItemTypes::LineItem); } 0090 0091 quintptr id() const { return id_; } 0092 0093 Q_SIGNALS: 0094 void removed(quintptr id, QPrivateSignal = QPrivateSignal()); 0095 void clicked(quintptr id, bool selected, bool replace_selection); 0096 0097 protected: 0098 void mousePressEvent(QGraphicsSceneMouseEvent * event) override; 0099 void mouseReleaseEvent(QGraphicsSceneMouseEvent * event) override; 0100 0101 virtual void on_set_time_start(int){} 0102 virtual void on_set_time_end(int){} 0103 0104 void click_selected(bool selected, bool replace_selection); 0105 0106 private: 0107 void propagate_row_vis(int delta); 0108 0109 void adjust_row_vis(int delta, bool propagate = true); 0110 0111 void emit_removed(); 0112 0113 int time_start; 0114 int time_end; 0115 int height_; 0116 model::Object* object_; 0117 std::vector<LineItem*> rows_; 0118 int visible_rows_ = 1; 0119 quintptr id_ = 0; 0120 bool expanded_ = false; 0121 }; 0122 0123 class ObjectLineItem : public LineItem 0124 { 0125 Q_OBJECT 0126 0127 public: 0128 ObjectLineItem(quintptr id, model::Object* obj, int time_start, int time_end, int height) 0129 : LineItem(id, obj, time_start, time_end, height) 0130 {} 0131 0132 int type() const override { return int(ItemTypes::ObjectLineItem); } 0133 0134 item_models::PropertyModelFull::Item property_item() const override 0135 { 0136 return {object(), nullptr}; 0137 } 0138 }; 0139 0140 class AnimatableItem; 0141 0142 class KeyframeSplitItem : public QGraphicsObject 0143 { 0144 Q_OBJECT 0145 0146 public: 0147 static constexpr const int icon_size = 16; 0148 static constexpr const int pen = 2; 0149 static constexpr const QSize half_icon_size{icon_size/2, icon_size}; 0150 0151 KeyframeSplitItem(AnimatableItem* parent); 0152 0153 QRectF boundingRect() const override 0154 { 0155 return QRectF(-icon_size/2-pen, -icon_size/2-pen, icon_size+2*pen, icon_size+2*pen); 0156 } 0157 0158 void paint(QPainter * painter, const QStyleOptionGraphicsItem *, QWidget * widget) override; 0159 0160 0161 void set_enter(model::KeyframeTransition::Descriptive enter); 0162 0163 void set_exit(model::KeyframeTransition::Descriptive exit); 0164 0165 model::FrameTime time() const { return x(); } 0166 0167 protected: 0168 void mousePressEvent(QGraphicsSceneMouseEvent * event) override; 0169 0170 void mouseMoveEvent(QGraphicsSceneMouseEvent * event) override; 0171 0172 void mouseReleaseEvent(QGraphicsSceneMouseEvent * event) override; 0173 0174 private: 0175 bool drag_allowed() const 0176 { 0177 /// \todo Handle for stuff like transform attributes :/ 0178 return !visual_node || !visual_node->docnode_locked_recursive(); 0179 } 0180 0181 void drag_init() 0182 { 0183 drag_start = x(); 0184 dragging = drag_allowed(); 0185 } 0186 0187 void drag_move(qreal delta) 0188 { 0189 if ( dragging ) 0190 setX(drag_start+delta); 0191 } 0192 0193 void drag_end() 0194 { 0195 dragging = false; 0196 } 0197 0198 AnimatableItem* line() const; 0199 0200 QPixmap pix_enter; 0201 QPixmap pix_exit; 0202 QIcon icon_enter; 0203 QIcon icon_exit; 0204 model::FrameTime drag_start; 0205 bool dragging = false; 0206 model::VisualNode* visual_node = nullptr; 0207 }; 0208 0209 class AnimatableItem : public LineItem 0210 { 0211 Q_OBJECT 0212 0213 private: 0214 struct DragData 0215 { 0216 KeyframeSplitItem* item; 0217 model::FrameTime from; 0218 model::FrameTime to; 0219 }; 0220 0221 public: 0222 AnimatableItem(quintptr id, model::Object* obj, model::AnimatableBase* animatable, int time_start, int time_end, int height); 0223 0224 std::pair<model::KeyframeBase*, model::KeyframeBase*> keyframes(KeyframeSplitItem* item); 0225 0226 int type() const override; 0227 0228 item_models::PropertyModelFull::Item property_item() const override; 0229 0230 public Q_SLOTS: 0231 void add_keyframe(int index); 0232 0233 void remove_keyframe(int index); 0234 0235 private Q_SLOTS: 0236 void transition_changed(model::KeyframeTransition::Descriptive before, model::KeyframeTransition::Descriptive after); 0237 0238 0239 void update_keyframe(int index, model::KeyframeBase* kf); 0240 0241 private: 0242 void keyframes_dragged(const std::vector<DragData>& keyframe_items); 0243 void cycle_keyframe_transition(model::FrameTime time); 0244 0245 model::AnimatableBase* animatable; 0246 std::vector<KeyframeSplitItem*> kf_split_items; 0247 friend KeyframeSplitItem; 0248 }; 0249 0250 class TimeRectItem : public QGraphicsObject 0251 { 0252 public: 0253 TimeRectItem(model::VisualNode* node, qreal height, QGraphicsItem* parent) 0254 : QGraphicsObject(parent), 0255 radius(height/2) 0256 { 0257 update_color(node->docnode_group_color()); 0258 connect(node, &model::VisualNode::docnode_group_color_changed, this, &TimeRectItem::update_color); 0259 } 0260 0261 void paint(QPainter * painter, const QStyleOptionGraphicsItem * opt, QWidget *) override 0262 { 0263 QColor fill = math::lerp(opt->palette.base().color(), color, color.alphaF()); 0264 fill.setAlpha(255); 0265 auto stroke = fill.lightnessF() < 0.4 ? Qt::white : Qt::black; 0266 QPen p(stroke, 1); 0267 p.setCosmetic(true); 0268 painter->setPen(p); 0269 painter->setBrush(fill); 0270 painter->drawRect(boundingRect()); 0271 } 0272 0273 private: 0274 void update_color(const QColor& col) 0275 { 0276 color = col; 0277 update(); 0278 } 0279 0280 protected: 0281 QColor color; 0282 qreal radius; 0283 }; 0284 0285 class AnimationContainerItem : public TimeRectItem 0286 { 0287 public: 0288 AnimationContainerItem(model::VisualNode* node, model::AnimationContainer* animation, 0289 qreal height, QGraphicsItem* parent) 0290 : TimeRectItem(node, height, parent), 0291 animation(animation) 0292 0293 { 0294 handle_ip.set_radius(radius); 0295 handle_op.set_radius(radius); 0296 handle_ip.setPos(animation->first_frame.get(), 0); 0297 handle_op.setPos(animation->last_frame.get(), 0); 0298 connect(&handle_ip, &graphics::MoveHandle::dragged_x, this, &AnimationContainerItem::drag_ip); 0299 connect(&handle_op, &graphics::MoveHandle::dragged_x, this, &AnimationContainerItem::drag_op); 0300 connect(animation, &model::AnimationContainer::first_frame_changed, this, &AnimationContainerItem::update_ip); 0301 connect(animation, &model::AnimationContainer::last_frame_changed, this, &AnimationContainerItem::update_op); 0302 connect(&handle_ip, &graphics::MoveHandle::drag_finished, this, &AnimationContainerItem::commit_ip); 0303 connect(&handle_op, &graphics::MoveHandle::drag_finished, this, &AnimationContainerItem::commit_op); 0304 } 0305 0306 QRectF boundingRect() const override 0307 { 0308 return QRectF( 0309 QPointF(handle_ip.pos().x(), -radius), 0310 QPointF(handle_op.pos().x(), radius) 0311 ); 0312 } 0313 0314 private: 0315 void drag_ip(qreal x) 0316 { 0317 x = qRound(x); 0318 if ( x >= animation->last_frame.get() ) 0319 x = animation->last_frame.get() - 1; 0320 animation->first_frame.set_undoable(x, false); 0321 } 0322 0323 void drag_op(qreal x) 0324 { 0325 x = qRound(x); 0326 if ( x <= animation->first_frame.get() ) 0327 x = animation->first_frame.get() + 1; 0328 animation->last_frame.set_undoable(x, false); 0329 } 0330 0331 void update_ip(qreal x) 0332 { 0333 handle_ip.setPos(x, 0); 0334 prepareGeometryChange(); 0335 update(); 0336 } 0337 0338 void update_op(qreal x) 0339 { 0340 handle_op.setPos(x, 0); 0341 prepareGeometryChange(); 0342 update(); 0343 } 0344 0345 void commit_ip() 0346 { 0347 animation->first_frame.set_undoable(animation->first_frame.get(), true); 0348 } 0349 0350 void commit_op() 0351 { 0352 animation->last_frame.set_undoable(animation->last_frame.get(), true); 0353 } 0354 0355 private: 0356 graphics::MoveHandle handle_ip{this, graphics::MoveHandle::Horizontal, graphics::MoveHandle::None, 1, true}; 0357 graphics::MoveHandle handle_op{this, graphics::MoveHandle::Horizontal, graphics::MoveHandle::None, 1, true}; 0358 model::AnimationContainer* animation; 0359 }; 0360 0361 0362 class PropertyLineItem : public LineItem 0363 { 0364 Q_OBJECT 0365 0366 public: 0367 PropertyLineItem(quintptr id, model::Object* obj, model::BaseProperty* prop, int time_start, int time_end, int height) 0368 : LineItem(id, obj, time_start, time_end, height), 0369 property_(prop) 0370 {} 0371 0372 int type() const override { return int(ItemTypes::PropertyLineItem); } 0373 0374 model::BaseProperty* property() const 0375 { 0376 return property_; 0377 } 0378 0379 item_models::PropertyModelFull::Item property_item() const override 0380 { 0381 return {object(), property_}; 0382 } 0383 0384 private: 0385 model::BaseProperty* property_; 0386 }; 0387 0388 0389 class StretchableTimeItem : public TimeRectItem 0390 { 0391 public: 0392 StretchableTimeItem(model::PreCompLayer* layer, 0393 qreal height, QGraphicsItem* parent) 0394 : TimeRectItem(layer, height, parent), 0395 timing(layer->timing.get()), 0396 animation(layer->owner_composition()->animation.get()) 0397 0398 { 0399 handle_ip.set_radius(radius); 0400 handle_op.set_radius(radius); 0401 update_handles(); 0402 connect(&handle_ip, &graphics::MoveHandle::dragged_x, this, &StretchableTimeItem::drag_ip); 0403 connect(&handle_op, &graphics::MoveHandle::dragged_x, this, &StretchableTimeItem::drag_op); 0404 connect(timing, &model::StretchableTime::timing_changed, this, &StretchableTimeItem::update_handles); 0405 connect(layer, &model::PreCompLayer::composition_changed, this, &StretchableTimeItem::update_handles); 0406 connect(&handle_ip, &graphics::MoveHandle::drag_finished, this, &StretchableTimeItem::commit_ip); 0407 connect(&handle_op, &graphics::MoveHandle::drag_finished, this, &StretchableTimeItem::commit_op); 0408 connect(animation, &model::AnimationContainer::last_frame_changed, this, &StretchableTimeItem::update_handles); 0409 0410 } 0411 0412 QRectF boundingRect() const override 0413 { 0414 return QRectF( 0415 QPointF(handle_ip.pos().x(), -radius), 0416 QPointF(handle_op.pos().x(), radius) 0417 ); 0418 } 0419 0420 private: 0421 void drag_ip(qreal x) 0422 { 0423 x = qRound(x); 0424 timing->start_time.set_undoable(x, false); 0425 } 0426 0427 void drag_op(qreal x) 0428 { 0429 x = qRound(x); 0430 auto duration = x - timing->start_time.get(); 0431 if ( duration < 1 ) 0432 duration = 1; 0433 0434 auto source_duration = animation->last_frame.get(); 0435 0436 if ( source_duration != 0 ) 0437 timing->stretch.set_undoable(duration / source_duration, false); 0438 } 0439 0440 void update_ip(qreal x) 0441 { 0442 handle_ip.setPos(x, 0); 0443 prepareGeometryChange(); 0444 update(); 0445 } 0446 0447 void update_handles() 0448 { 0449 handle_ip.setPos(timing->start_time.get(), 0); 0450 0451 handle_op.setPos(timing->start_time.get() + animation->last_frame.get() * timing->stretch.get(), 0); 0452 prepareGeometryChange(); 0453 update(); 0454 } 0455 0456 void commit_ip() 0457 { 0458 timing->start_time.set_undoable(timing->start_time.get(), true); 0459 } 0460 0461 void commit_op() 0462 { 0463 timing->stretch.set_undoable(timing->stretch.get(), true); 0464 } 0465 0466 private: 0467 model::StretchableTime* timing; 0468 model::AnimationContainer* animation; 0469 graphics::MoveHandle handle_ip{this, graphics::MoveHandle::Horizontal, graphics::MoveHandle::None, 1, true}; 0470 graphics::MoveHandle handle_op{this, graphics::MoveHandle::Horizontal, graphics::MoveHandle::None, 1, true}; 0471 }; 0472 0473 class ObjectListLineItem : public LineItem 0474 { 0475 Q_OBJECT 0476 0477 public: 0478 ObjectListLineItem(quintptr id, model::Object* obj, model::ObjectListPropertyBase* prop, int time_start, int time_end, int height) 0479 : LineItem(id, obj, time_start, time_end, height), 0480 property_(prop) 0481 {} 0482 0483 int type() const override { return int(ItemTypes::ObjectListLineItem); } 0484 0485 model::BaseProperty* property() const 0486 { 0487 return property_; 0488 } 0489 0490 item_models::PropertyModelFull::Item property_item() const override 0491 { 0492 return {object(), property_}; 0493 } 0494 0495 private: 0496 model::ObjectListPropertyBase* property_; 0497 }; 0498 0499 0500 } // namespace glaxnimate::gui::timeline