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

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 "shape.hpp"
0008 #include "utils/range.hpp"
0009 #include "styler.hpp"
0010 #include "path.hpp"
0011 #include "model/animation/join_animatables.hpp"
0012 
0013 using namespace glaxnimate;
0014 
0015 class glaxnimate::model::ShapeElement::Private
0016 {
0017 public:
0018     ShapeListProperty* property = nullptr;
0019     int position = -1;
0020     glaxnimate::model::Composition* owner_composition = nullptr;
0021     PathCache<QPainterPath> cached_path;
0022 
0023     void update_comp(glaxnimate::model::Composition* comp, ShapeElement* parent)
0024     {
0025         if ( comp != owner_composition )
0026         {
0027             auto old = owner_composition;
0028             owner_composition = comp;
0029             parent->on_composition_changed(old, comp);
0030         }
0031     }
0032 };
0033 
0034 glaxnimate::model::ShapeElement::ShapeElement(glaxnimate::model::Document* document)
0035     : VisualNode(document), d(std::make_unique<Private>())
0036 {
0037 }
0038 
0039 glaxnimate::model::ShapeElement::~ShapeElement() = default;
0040 
0041 
0042 glaxnimate::model::ShapeListProperty * glaxnimate::model::ShapeElement::owner() const
0043 {
0044     return d->property;
0045 }
0046 
0047 void glaxnimate::model::ShapeElement::clear_owner()
0048 {
0049     d->property = nullptr;
0050     d->position = -1;
0051     d->owner_composition = nullptr;
0052 }
0053 
0054 glaxnimate::model::Composition * glaxnimate::model::ShapeElement::owner_composition() const
0055 {
0056     return d->owner_composition;
0057 }
0058 
0059 int glaxnimate::model::ShapeElement::position() const
0060 {
0061     return d->position;
0062 }
0063 
0064 const glaxnimate::model::ShapeListProperty& glaxnimate::model::ShapeElement::siblings() const
0065 {
0066     return *d->property;
0067 }
0068 
0069 glaxnimate::model::ObjectListProperty<glaxnimate::model::ShapeElement>::iterator glaxnimate::model::ShapeListProperty::past_first_modifier() const
0070 {
0071     auto it = std::find_if(begin(), end(), [](const pointer& p){
0072         return qobject_cast<Modifier*>(p.get());
0073     });
0074     if ( it != end() )
0075         ++it;
0076     return it;
0077 }
0078 
0079 void glaxnimate::model::ShapeElement::refresh_owner_composition(glaxnimate::model::Composition* comp)
0080 {
0081     d->update_comp(comp, this);
0082 }
0083 
0084 void glaxnimate::model::ShapeElement::set_position(ShapeListProperty* property, int pos)
0085 {
0086     d->property = property;
0087     d->position = pos;
0088     position_updated();
0089 
0090     if ( property )
0091     {
0092         auto parent = d->property->object();
0093         if ( !parent )
0094             d->update_comp(nullptr, this);
0095         else if ( auto comp = parent->cast<glaxnimate::model::Composition>() )
0096             d->update_comp(comp, this);
0097         else if ( auto sh = parent->cast<glaxnimate::model::ShapeElement>() )
0098             d->update_comp(sh->d->owner_composition, this);
0099     }
0100 }
0101 
0102 void glaxnimate::model::ShapeElement::on_parent_changed(model::DocumentNode* old_parent, model::DocumentNode* new_parent)
0103 {
0104     if ( auto old_visual = qobject_cast<model::VisualNode*>(old_parent) )
0105         disconnect(this, &VisualNode::bounding_rect_changed, old_visual, &VisualNode::bounding_rect_changed);
0106 
0107     if ( auto new_visual = qobject_cast<model::VisualNode*>(new_parent) )
0108         connect(this, &VisualNode::bounding_rect_changed, new_visual, &VisualNode::bounding_rect_changed);
0109 
0110     if ( !new_parent )
0111         d->update_comp(nullptr, this);
0112 }
0113 
0114 void glaxnimate::model::ShapeElement::on_property_changed(const glaxnimate::model::BaseProperty* prop, const QVariant&)
0115 {
0116     if ( prop->traits().flags & PropertyTraits::Visual )
0117         propagate_bounding_rect_changed();
0118 }
0119 
0120 math::bezier::MultiBezier glaxnimate::model::ShapeElement::shapes(glaxnimate::model::FrameTime t) const
0121 {
0122     math::bezier::MultiBezier bez;
0123     add_shapes(t, bez, {});
0124     return bez;
0125 }
0126 
0127 QPainterPath glaxnimate::model::ShapeElement::to_clip(FrameTime t) const
0128 {
0129     return to_painter_path(t);
0130 }
0131 
0132 QPainterPath glaxnimate::model::ShapeElement::to_painter_path(FrameTime t) const
0133 {
0134     if ( d->cached_path.is_dirty(t) )
0135         d->cached_path.set_path(t, to_painter_path_impl(t));
0136     return d->cached_path.path();
0137 }
0138 
0139 void glaxnimate::model::ShapeElement::on_graphics_changed()
0140 {
0141     d->cached_path.mark_dirty();
0142 }
0143 
0144 std::unique_ptr<glaxnimate::model::ShapeElement> glaxnimate::model::ShapeElement::to_path() const
0145 {
0146     return std::unique_ptr<glaxnimate::model::ShapeElement>(static_cast<glaxnimate::model::ShapeElement*>(clone().release()));
0147 }
0148 
0149 QRectF glaxnimate::model::ShapeListProperty::bounding_rect(FrameTime t) const
0150 {
0151     QRectF rect;
0152     for ( const auto& ch : utils::Range(begin(), past_first_modifier()) )
0153     {
0154         QRectF local_rect = ch->local_bounding_rect(t);
0155         if ( local_rect.isNull() )
0156             continue;
0157 
0158         QRectF child_rect = ch->local_transform_matrix(t).map(local_rect).boundingRect();
0159 
0160         if ( rect.isNull() )
0161             rect = child_rect;
0162         else
0163             rect |= child_rect;
0164     }
0165 
0166     return rect;
0167 }
0168 
0169 std::unique_ptr<glaxnimate::model::ShapeElement> glaxnimate::model::Shape::to_path() const
0170 {
0171     std::vector<const AnimatableBase*> properties;
0172     auto flags = PropertyTraits::Visual|PropertyTraits::Animated;
0173     for ( auto prop : this->properties() )
0174     {
0175         if ( (prop->traits().flags & flags) == flags )
0176             properties.push_back(static_cast<AnimatableBase*>(prop));
0177     }
0178 
0179     auto path = std::make_unique<glaxnimate::model::Path>(document());
0180     path->name.set(name.get());
0181     path->group_color.set(group_color.get());
0182     path->visible.set(visible.get());
0183 
0184     if ( !properties.empty() )
0185     {
0186 
0187         JoinAnimatables ja(std::move(properties));
0188         FrameTime cur_time = ja.properties()[0]->time();
0189         path->set_time(cur_time);
0190 
0191         if ( ja.animated() )
0192         {
0193             for ( const auto & kf : ja )
0194             {
0195                 auto path_kf = path->shape.set_keyframe(kf.time, to_bezier(kf.time));
0196                 path_kf->set_transition(kf.transition());
0197             }
0198         }
0199 
0200         path->shape.set(to_bezier(cur_time));
0201         path->closed.set(path->shape.get().closed());
0202     }
0203 
0204     return path;
0205 }
0206 
0207 QPainterPath glaxnimate::model::Shape::to_painter_path_impl(FrameTime t) const
0208 {
0209     QPainterPath p;
0210     to_bezier(t).add_to_painter_path(p);
0211     return p;
0212 }
0213 
0214 
0215 void glaxnimate::model::Shape::add_shapes(FrameTime t, math::bezier::MultiBezier & bez, const QTransform& transform) const
0216 {
0217     auto shape = to_bezier(t);
0218     if ( !transform.isIdentity() )
0219         shape.transform(transform);
0220     bez.beziers().emplace_back(std::move(shape));
0221 }
0222 
0223 
0224 glaxnimate::model::ShapeOperator::ShapeOperator(glaxnimate::model::Document* doc)
0225     : ShapeElement(doc)
0226 {
0227     connect(this, &ShapeElement::position_updated, this, &ShapeOperator::update_affected);
0228     connect(this, &ShapeElement::siblings_changed, this, &ShapeOperator::update_affected);
0229 }
0230 
0231 
0232 void glaxnimate::model::ShapeOperator::do_collect_shapes(const std::vector<ShapeElement*>& shapes, glaxnimate::model::FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const
0233 {
0234     for ( auto sib : shapes )
0235     {
0236         if ( sib->visible.get() )
0237             sib->add_shapes(t, bez, transform);
0238     }
0239 }
0240 
0241 math::bezier::MultiBezier glaxnimate::model::ShapeOperator::collect_shapes_from(const std::vector<ShapeElement *>& shapes, glaxnimate::model::FrameTime t, const QTransform& transform) const
0242 {
0243     math::bezier::MultiBezier bez;
0244     if ( visible.get() )
0245         do_collect_shapes(shapes, t, bez, transform);
0246     return bez;
0247 }
0248 
0249 
0250 math::bezier::MultiBezier glaxnimate::model::ShapeOperator::collect_shapes(FrameTime t, const QTransform& transform) const
0251 {
0252     if ( bezier_cache.is_dirty(t) )
0253         bezier_cache.set_path(t, collect_shapes_from(affected_elements, t, transform));
0254     return bezier_cache.path();
0255 }
0256 
0257 void glaxnimate::model::ShapeOperator::update_affected()
0258 {
0259     if ( !owner() )
0260         return;
0261 
0262     std::vector<ShapeElement*> curr_siblings;
0263     curr_siblings.reserve(owner()->size() - position());
0264     bool skip = skip_stylers();
0265     for ( auto it = owner()->begin() + position() + 1; it < owner()->end(); ++it )
0266     {
0267         if ( skip && qobject_cast<Styler*>(it->get()) )
0268             continue;
0269 
0270         curr_siblings.push_back(it->get());
0271         if ( qobject_cast<Modifier*>(it->get()) )
0272             break;
0273     }
0274 
0275     affected_elements = curr_siblings;
0276     std::reverse(affected_elements.begin(), affected_elements.end());
0277 }
0278 
0279 void glaxnimate::model::ShapeOperator::on_graphics_changed()
0280 {
0281     ShapeElement::on_graphics_changed();
0282     bezier_cache.mark_dirty();
0283     Q_EMIT shape_changed();
0284 }
0285 
0286 void glaxnimate::model::Modifier::add_shapes(FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const
0287 {
0288     bez.append(collect_shapes(t, transform));
0289 }
0290 
0291 void glaxnimate::model::Modifier::do_collect_shapes(const std::vector<ShapeElement*>& shapes, glaxnimate::model::FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const
0292 {
0293     bool post = process_collected();
0294 
0295     if ( post )
0296     {
0297         math::bezier::MultiBezier temp;
0298         for ( auto sib : shapes )
0299         {
0300             if ( sib->visible.get() )
0301                 sib->add_shapes(t, temp, transform);
0302         }
0303 
0304         bez.append(process(t, temp));
0305     }
0306     else
0307     {
0308         for ( auto sib : shapes )
0309         {
0310             if ( sib->visible.get() )
0311             {
0312                 math::bezier::MultiBezier temp;
0313                 sib->add_shapes(t, temp, transform);
0314                 bez.append(process(t, temp));
0315             }
0316         }
0317     }
0318 }
0319 
0320 QPainterPath glaxnimate::model::Modifier::to_painter_path_impl(glaxnimate::model::FrameTime t) const
0321 {
0322     math::bezier::MultiBezier bez;
0323     add_shapes(t, bez, {});
0324     return bez.painter_path();
0325 }
0326 
0327 QRectF glaxnimate::model::Modifier::local_bounding_rect(glaxnimate::model::FrameTime t) const
0328 {
0329     return to_painter_path(t).boundingRect();
0330 }
0331 
0332