| #include "rive/drawable.hpp" | |
| #include "rive/artboard.hpp" | |
| #include "rive/custom_property.hpp" | |
| #include "rive/shapes/clipping_shape.hpp" | |
| #include "rive/shapes/path_composer.hpp" | |
| #include "rive/shapes/shape.hpp" | |
| #include "rive/clip_result.hpp" | |
| #include "rive/shapes/paint/paint_outset.hpp" | |
| using namespace rive; | |
| bool Drawable::hasCustomProperties() | |
| { | |
| #ifdef WITH_RIVE_EDITOR | |
| // Children change under the editor, so nothing cached stays true. | |
| for (auto child : children()) | |
| { | |
| if (CustomProperty::tagging(child) != nullptr) | |
| { | |
| return true; | |
| } | |
| } | |
| return false; | |
| #else | |
| return runtimeFlag(RuntimeFlags::hasCustomProperties); | |
| #endif | |
| } | |
| CustomProperty* Drawable::customProperty(uint32_t nameId) | |
| { | |
| for (auto child : children()) | |
| { | |
| // Only tagging ones: the key of a missing name is what every other | |
| // property holds, a scripted drawable's inputs among them. | |
| auto property = CustomProperty::tagging(child); | |
| if (property != nullptr && property->nameId() == nameId) | |
| { | |
| return property; | |
| } | |
| } | |
| return nullptr; | |
| } | |
| StatusCode Drawable::onAddedDirty(CoreContext* context) | |
| { | |
| auto code = Super::onAddedDirty(context); | |
| if (code != StatusCode::Ok) | |
| { | |
| return code; | |
| } | |
| auto blendMode = static_cast<rive::BlendMode>(blendModeValue()); | |
| switch (blendMode) | |
| { | |
| case rive::BlendMode::srcOver: | |
| case rive::BlendMode::additive: | |
| case rive::BlendMode::screen: | |
| case rive::BlendMode::overlay: | |
| case rive::BlendMode::darken: | |
| case rive::BlendMode::lighten: | |
| case rive::BlendMode::colorDodge: | |
| case rive::BlendMode::colorBurn: | |
| case rive::BlendMode::hardLight: | |
| case rive::BlendMode::softLight: | |
| case rive::BlendMode::difference: | |
| case rive::BlendMode::exclusion: | |
| case rive::BlendMode::multiply: | |
| case rive::BlendMode::hue: | |
| case rive::BlendMode::saturation: | |
| case rive::BlendMode::color: | |
| case rive::BlendMode::luminosity: | |
| return StatusCode::Ok; | |
| } | |
| return StatusCode::InvalidObject; | |
| } | |
| void Drawable::addClippingShape(ClippingShape* shape) | |
| { | |
| m_ClippingShapes.push_back(shape); | |
| } | |
| void Drawable::addLayerMask(LayerMask* mask) { m_layerMasks.push_back(mask); } | |
| void Drawable::addSourceOfLayerMask(LayerMask* mask) | |
| { | |
| m_sourceOfLayerMasks.push_back(mask); | |
| } | |
| bool Drawable::willDraw() { return !isHidden(); } | |
| bool Drawable::isChildOfLayout(LayoutComponent* layout) | |
| { | |
| for (ContainerComponent* parent = this; parent != nullptr; | |
| parent = parent->parent()) | |
| { | |
| if (parent->is<LayoutComponent>() && | |
| parent->as<LayoutComponent>() == layout) | |
| { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| bool Drawable::hitTestPoint(const Vec2D& position, | |
| bool skipOnUnclipped, | |
| bool isPrimaryHit) | |
| { | |
| if (isHidden()) | |
| { | |
| return false; | |
| } | |
| auto hComponent = hittableComponent(); | |
| if (hComponent != this && hComponent != nullptr) | |
| { | |
| return hComponent->hitTestPoint(position, | |
| skipOnUnclipped, | |
| isPrimaryHit); | |
| } | |
| return Component::hitTestPoint(position, skipOnUnclipped, isPrimaryHit); | |
| } | |
| Drawable* DrawableProxy::hittableComponent() | |
| { | |
| return static_cast<LayoutComponent*>(proxyDrawing()); | |
| } | |
| bool DrawableProxy::isTargetOpaque() | |
| { | |
| return hittableComponent()->isTargetOpaque(); | |
| } | |
| BoundsFidelity Drawable::paintedBoundsFromLocal( | |
| const AABB& local, | |
| const Mat2D& world, | |
| const ShapePaintContainer* paints, | |
| AABB* out) | |
| { | |
| if (local.isEmptyOrNaN()) | |
| { | |
| return BoundsFidelity::none; | |
| } | |
| AABB bounds = world.mapBoundingBox(local); | |
| if (bounds.isEmptyOrNaN()) | |
| { | |
| // A degenerate transform -- scale 0, or a collapsed layout fold -- | |
| // flattens the box to nothing. That is a real answer, not a failure: | |
| // nothing is painted there. | |
| *out = AABB(); | |
| return BoundsFidelity::exact; | |
| } | |
| bool trustworthy = true; | |
| if (paints != nullptr) | |
| { | |
| const PaintReach reach = shapePaintsWorldReach(paints, world); | |
| if (reach.worldOutset > 0.0f) | |
| { | |
| bounds = bounds.outset(reach.worldOutset, reach.worldOutset); | |
| } | |
| trustworthy = reach.trustworthy; | |
| } | |
| *out = bounds; | |
| return trustworthy ? BoundsFidelity::exact : BoundsFidelity::approximate; | |
| } | |
| BoundsFidelity DrawableProxy::paintedWorldBounds(AABB* out) | |
| { | |
| Drawable* target = hittableComponent(); | |
| // hittableComponent() returns `this` on the base Drawable, and a proxy that | |
| // stands in for nothing has nothing to measure. Guarding also keeps this | |
| // from recursing forever if that ever changes. | |
| if (target == nullptr || target == this) | |
| { | |
| return BoundsFidelity::none; | |
| } | |
| return target->paintedWorldBounds(out); | |
| } | |
| const std::vector<LayerMask*>& DrawableProxy::layerMasks() const | |
| { | |
| // const_cast because hittableComponent() is non-const; it is a pure | |
| // downcast of proxyDrawing() and mutates nothing. | |
| return const_cast<DrawableProxy*>(this)->hittableComponent()->layerMasks(); | |
| } | |
| const std::vector<LayerMask*>& DrawableProxy::sourceOfLayerMasks() const | |
| { | |
| return const_cast<DrawableProxy*>(this) | |
| ->hittableComponent() | |
| ->sourceOfLayerMasks(); | |
| } |