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