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

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 "follow_path_dialog.hpp"
0008 #include "ui_follow_path_dialog.h"
0009 #include <QEvent>
0010 
0011 #include "command/undo_macro_guard.hpp"
0012 #include "command/animation_commands.hpp"
0013 #include "item_models/node_type_proxy_model.hpp"
0014 #include "widgets/dialogs/select_shape_dialog.hpp"
0015 
0016 class glaxnimate::gui::FollowPathDialog::Private
0017 {
0018 public:
0019     enum Units
0020     {
0021         Frames,
0022         Seconds
0023     };
0024 
0025     Private(model::AnimatedProperty<QPointF>* property, model::Composition* comp, item_models::DocumentNodeModel* model, FollowPathDialog* parent)
0026         : select_shape_dialog(model, parent), property(property), comp(comp)
0027     {
0028         ui.setupUi(parent);
0029         set_units(Frames);
0030         auto anim = comp->animation.get();
0031         ui.spin_start->setValue(anim->first_frame.get());
0032         QSignalBlocker be(ui.spin_end);
0033         QSignalBlocker bd(ui.spin_duration);
0034         ui.spin_end->setValue(anim->last_frame.get());
0035         ui.spin_duration->setValue(anim->last_frame.get() - anim->first_frame.get());
0036     }
0037 
0038     void set_frames(QDoubleSpinBox* box)
0039     {
0040         box->setSuffix(i18n("f"));
0041         box->setDecimals(0);
0042         auto v = box->value();
0043         box->setMinimum(comp->animation->first_frame.get());
0044         box->setMaximum(comp->animation->last_frame.get());
0045         box->setValue(v * comp->fps.get());
0046     }
0047 
0048     void set_seconds(QDoubleSpinBox* box)
0049     {
0050         auto fps = comp->fps.get();
0051         box->setSuffix(i18n("\""));
0052         box->setDecimals(2);
0053         auto v = box->value();
0054         box->setMinimum(comp->animation->first_frame.get() / fps);
0055         box->setMaximum(comp->animation->last_frame.get() / fps);
0056         box->setValue(v / fps);
0057     }
0058 
0059     void set_units(int index)
0060     {
0061         QSignalBlocker be(ui.spin_end);
0062         QSignalBlocker bd(ui.spin_duration);
0063 
0064         if ( index == Frames )
0065         {
0066             set_frames(ui.spin_start);
0067             set_frames(ui.spin_end);
0068             set_frames(ui.spin_duration);
0069         }
0070         else
0071         {
0072             set_seconds(ui.spin_start);
0073             set_seconds(ui.spin_end);
0074             set_seconds(ui.spin_duration);
0075         }
0076     }
0077 
0078     model::FrameTime frame(qreal time)
0079     {
0080         if ( ui.combo_units->currentIndex() == Frames )
0081             return time;
0082         return time * comp->fps.get();
0083     }
0084 
0085     Ui::FollowPathDialog ui;
0086     SelectShapeDialog select_shape_dialog;
0087     model::AnimatedProperty<QPointF>* property;
0088     model::Shape* shape = nullptr;
0089     model::Composition* comp;
0090 };
0091 
0092 glaxnimate::gui::FollowPathDialog::FollowPathDialog(model::AnimatedProperty<QPointF>* property, model::Composition* comp, item_models::DocumentNodeModel* model, QWidget* parent)
0093     : QDialog(parent), d(std::make_unique<Private>(property, comp, model, this))
0094 {}
0095 
0096 glaxnimate::gui::FollowPathDialog::~FollowPathDialog() = default;
0097 
0098 void glaxnimate::gui::FollowPathDialog::changeEvent ( QEvent* e )
0099 {
0100     QDialog::changeEvent(e);
0101 
0102     if ( e->type() == QEvent::LanguageChange)
0103     {
0104         d->ui.retranslateUi(this);
0105     }
0106 }
0107 
0108 void glaxnimate::gui::FollowPathDialog::apply()
0109 {
0110     if ( !d->shape )
0111     {
0112         accept();
0113         return;
0114     }
0115 
0116     auto start = d->frame(d->ui.spin_start->value());
0117     auto end = d->frame(d->ui.spin_end->value());
0118     auto bezier = d->shape->to_bezier(d->shape->time());
0119     bezier.add_close_point();
0120     math::bezier::LengthData length(bezier, 20);
0121 
0122     if ( start == end || length.length() == 0 )
0123     {
0124         accept();
0125         return;
0126     }
0127 
0128     auto guard = command::UndoMacroGuard(i18n("Follow Path"), d->property->object()->document());
0129     d->property->object()->push_command(new command::RemoveAllKeyframes(d->property, d->property->value()));
0130 
0131     for ( int i = 0; i < bezier.size(); i++ )
0132     {
0133         qreal point_length = i < bezier.size() - 1 ? length.child_start(i) : length.child_end(i-1);
0134         d->property->object()->push_command(new command::SetKeyframe(
0135             d->property,
0136             math::lerp(start, end, point_length / length.length()),
0137             QVariant::fromValue(bezier[i].pos),
0138             true,
0139             true
0140         ));
0141     }
0142 
0143     d->property->object()->push_command(new command::SetPositionBezier(
0144         d->property,
0145         bezier,
0146         true
0147     ));
0148 
0149     accept();
0150 }
0151 
0152 void glaxnimate::gui::FollowPathDialog::change_duration(double dur)
0153 {
0154     QSignalBlocker be(d->ui.spin_end);
0155     d->ui.spin_end->setValue(d->ui.spin_start->value() + dur);
0156 }
0157 
0158 void glaxnimate::gui::FollowPathDialog::change_end(double end)
0159 {
0160     QSignalBlocker bd(d->ui.spin_duration);
0161     d->ui.spin_duration->setValue(end - d->ui.spin_start->value());
0162 }
0163 
0164 void glaxnimate::gui::FollowPathDialog::change_units(int index)
0165 {
0166     d->set_units(index);
0167 }
0168 
0169 void glaxnimate::gui::FollowPathDialog::select_path()
0170 {
0171     d->select_shape_dialog.set_shape(d->shape);
0172     if ( d->select_shape_dialog.exec() == QDialog::Accepted )
0173     {
0174         d->shape = d->select_shape_dialog.shape();
0175         d->ui.line_shape_name->setText(d->shape ? d->shape->object_name() : "");
0176     }
0177 }
0178 
0179 
0180