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