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

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 "model/document_node.hpp"
0010 #include "math/bezier/bezier.hpp"
0011 #include "model/property/object_list_property.hpp"
0012 
0013 namespace glaxnimate::model {
0014 
0015 using ShapeListProperty = ObjectListProperty<class ShapeElement>;
0016 class Composition;
0017 
0018 template<class T>
0019 class PathCache
0020 {
0021 public:
0022     bool is_dirty(FrameTime time) const
0023     {
0024         return time != cached_time || dirty;
0025     }
0026 
0027     void mark_dirty() { dirty = true; }
0028 
0029     const T& path() const { return cached_path; }
0030 
0031     void set_path(FrameTime time, const T& path)
0032     {
0033         cached_time = time;
0034         dirty = false;
0035         cached_path = path;
0036     }
0037 
0038 private:
0039     bool dirty = true;
0040     T cached_path = {};
0041     FrameTime cached_time = 0;
0042 };
0043 
0044 /**
0045  * \brief Base class for all shape elements
0046  */
0047 class ShapeElement : public VisualNode
0048 {
0049     Q_OBJECT
0050 
0051 public:
0052     explicit ShapeElement(model::Document* document);
0053     ~ShapeElement();
0054 
0055     int docnode_child_count() const override { return 0; }
0056     DocumentNode* docnode_child(int) const override { return nullptr; }
0057     int docnode_child_index(DocumentNode*) const override { return -1; }
0058 
0059     /**
0060      * \brief Index within its parent
0061      */
0062     int position() const;
0063 
0064     virtual void add_shapes(FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const = 0;
0065     math::bezier::MultiBezier shapes(FrameTime t) const;
0066 
0067     ShapeListProperty* owner() const;
0068     Composition* owner_composition() const;
0069     void clear_owner();
0070 
0071     virtual QPainterPath to_clip(FrameTime t) const;
0072     QPainterPath to_painter_path(FrameTime t) const;
0073     virtual std::unique_ptr<ShapeElement> to_path() const;
0074 
0075 Q_SIGNALS:
0076     void position_updated();
0077     void siblings_changed();
0078 
0079 protected:
0080     const ShapeListProperty& siblings() const;
0081     void on_property_changed(const BaseProperty* prop, const QVariant& value) override;
0082     void on_parent_changed(model::DocumentNode* old_parent, model::DocumentNode* new_parent) override;
0083     void refresh_owner_composition(glaxnimate::model::Composition* comp);
0084     virtual void on_composition_changed(model::Composition* old_comp, model::Composition* new_comp)
0085     {
0086         Q_UNUSED(old_comp);
0087         Q_UNUSED(new_comp);
0088     }
0089 
0090     virtual QPainterPath to_painter_path_impl(FrameTime t) const = 0;
0091     void on_graphics_changed() override;
0092 
0093 private:
0094     void set_position(ShapeListProperty* property, int pos);
0095 
0096     class Private;
0097     std::unique_ptr<Private> d;
0098     friend ShapeListProperty;
0099     friend class Group;
0100 };
0101 
0102 template<>
0103 class ObjectListProperty<ShapeElement> : public detail::ObjectListProperty<ShapeElement>
0104 {
0105 public:
0106     using detail::ObjectListProperty<ShapeElement>::ObjectListProperty;
0107 
0108     /**
0109      * \brief End iterator for a range that includes a modifier then stops
0110      */
0111     iterator past_first_modifier() const;
0112 
0113     QRectF bounding_rect(FrameTime t) const;
0114 
0115 protected:
0116     void update_pos(int index)
0117     {
0118         int i;
0119         for ( i = size() - 1; i >= index; i-- )
0120             objects[i]->set_position(this, i);
0121         for ( ; i >= 0; i-- )
0122             objects[i]->siblings_changed();
0123     }
0124 
0125     void on_insert(int index) override
0126     {
0127         update_pos(index);
0128     }
0129 
0130     void on_remove(int index) override
0131     {
0132         update_pos(index);
0133     }
0134 
0135     void on_move(int index_a, int index_b) override
0136     {
0137         if ( index_b < index_a )
0138             std::swap(index_a, index_b);
0139 
0140         for ( int i = index_a; i <= index_b; i++ )
0141             objects[i]->set_position(this, i);
0142 
0143         for ( int i = 0; i <= index_b; i++ )
0144             objects[i]->siblings_changed();
0145     }
0146 };
0147 
0148 class Path;
0149 
0150 /**
0151  * \brief Classes that define shapes on their own (but not necessarily style)
0152  */
0153 class Shape : public ShapeElement
0154 {
0155     Q_OBJECT
0156 
0157     GLAXNIMATE_PROPERTY(bool, reversed, false, {}, {}, PropertyTraits::Visual|PropertyTraits::Hidden)
0158 
0159 public:
0160     using ShapeElement::ShapeElement;
0161 
0162     virtual math::bezier::Bezier to_bezier(FrameTime t) const = 0;
0163 
0164     void add_shapes(FrameTime t, math::bezier::MultiBezier & bez, const QTransform& transform) const override;
0165 
0166     std::unique_ptr<ShapeElement> to_path() const override;
0167 
0168 protected:
0169     QPainterPath to_painter_path_impl(FrameTime t) const override;
0170 };
0171 
0172 /**
0173  * \brief Base class for types that perform operations on their sibling shapes
0174  */
0175 class ShapeOperator : public ShapeElement
0176 {
0177     Q_OBJECT
0178 
0179 public:
0180     ShapeOperator(model::Document* doc);
0181 
0182     math::bezier::MultiBezier collect_shapes(FrameTime t, const QTransform& transform) const;
0183     math::bezier::MultiBezier collect_shapes_from(const std::vector<ShapeElement*>& shapes, FrameTime t, const QTransform& transform) const;
0184 
0185     const std::vector<ShapeElement*>& affected() const { return affected_elements; }
0186 
0187 protected:
0188     virtual void do_collect_shapes(const std::vector<ShapeElement*>& shapes, FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const;
0189     virtual bool skip_stylers() const { return true; }
0190     void on_graphics_changed() override;
0191 
0192 private Q_SLOTS:
0193     void update_affected();
0194 
0195 Q_SIGNALS:
0196     void shape_changed();
0197 
0198 private:
0199     std::vector<ShapeElement*> affected_elements;
0200     mutable PathCache<math::bezier::MultiBezier> bezier_cache;
0201 };
0202 
0203 /**
0204  * \brief Base class for elements that modify other shapes
0205  */
0206 class Modifier : public ShapeOperator
0207 {
0208     Q_OBJECT
0209 
0210 public:
0211     using ShapeOperator::ShapeOperator;
0212 
0213     void add_shapes(FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const override;
0214 
0215 
0216     QRectF local_bounding_rect(FrameTime t) const override;
0217 
0218     virtual math::bezier::MultiBezier process(FrameTime t, const math::bezier::MultiBezier& mbez) const = 0;
0219 
0220 protected:
0221     QPainterPath to_painter_path_impl(FrameTime t) const override;
0222 
0223     /**
0224      * \brief Whether to process on the whole thing (or individual objects)
0225      */
0226     virtual bool process_collected() const = 0;
0227 
0228     void do_collect_shapes(const std::vector<ShapeElement*>& shapes, FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const override;
0229 
0230     virtual bool skip_stylers() const override { return false; }
0231 
0232 };
0233 
0234 /**
0235  * \brief CRTP to override some methods using static functions
0236  * (so said methods can be accessed with no object)
0237  */
0238 template<class Derived, class Base>
0239 class StaticOverrides : public Base
0240 {
0241 public:
0242     using Ctor = StaticOverrides;
0243     using Base::Base;
0244 
0245     QIcon tree_icon() const override
0246     {
0247         return Derived::static_tree_icon();
0248     }
0249 
0250     QString type_name_human() const override
0251     {
0252         return Derived::static_type_name_human();
0253     }
0254 
0255     static QString static_class_name()
0256     {
0257         return detail::naked_type_name<Derived>();
0258     }
0259 };
0260 
0261 
0262 } // namespace glaxnimate::model