Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a strokeOnTop property to the ShapeLayer class. #426

Merged
merged 1 commit into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions include/tgfx/layers/Layer.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ namespace tgfx {

class DisplayList;
class DrawArgs;
struct LayerStyleSource;

/**
* The base class for all layers that can be placed on the display list. The layer class includes
Expand Down Expand Up @@ -505,14 +506,17 @@ class Layer {
virtual std::unique_ptr<LayerContent> onUpdateContent();

/**
* Draws the layer contour on the given canvas. The layer contour is the outline of the layer
* content, used for applying layer styles that need the contour. By default, this calls the
* draw() method with the layer content and the given paint object.
* Draws the layer content and its children on the given canvas. By default, this method draws the
* layer content first, followed by the children. Subclasses can override this method to change
* the drawing order or the way the layer content is drawn.
* @param content The layer content to draw. This can be nullptr.
* @param canvas The canvas to draw the layer contour on.
* @param paint The paint object used to draw the layer contour.
* @param canvas The canvas to draw the layer content on.
* @param alpha The alpha transparency value used for drawing the layer content.
* @param forContour Whether to draw the layer content for the contour.
* @param drawChildren A callback function that draws the children of the layer.
*/
virtual void drawContour(LayerContent* content, Canvas* canvas, const Paint& paint) const;
virtual void drawContents(LayerContent* content, Canvas* canvas, float alpha, bool forContour,
const std::function<void()>& drawChildren) const;

/**
* Attachs a property to this layer.
Expand Down Expand Up @@ -544,7 +548,7 @@ class Layer {

LayerContent* getContent();

Paint getLayerPaint(float alpha, BlendMode blendMode);
Paint getLayerPaint(float alpha, BlendMode blendMode = BlendMode::SrcOver) const;

std::shared_ptr<ImageFilter> getImageFilter(float contentScale);

Expand All @@ -557,12 +561,13 @@ class Layer {

void drawOffscreen(const DrawArgs& args, Canvas* canvas, float alpha, BlendMode blendMode);

void drawContents(const DrawArgs& args, Canvas* canvas, float alpha);
void drawDirectly(const DrawArgs& args, Canvas* canvas, float alpha);

void drawChildren(const DrawArgs& args, Canvas* canvas, float alpha);

void drawLayerStyles(Canvas* canvas, std::shared_ptr<Image> content, float contentScale,
std::shared_ptr<Image> contour, const Point& contourOffset, float alpha,
std::unique_ptr<LayerStyleSource> getLayerStyleSource(const DrawArgs& args, const Matrix& matrix);

void drawLayerStyles(Canvas* canvas, float alpha, const LayerStyleSource* source,
LayerStylePosition position);

bool getLayersUnderPointInternal(float x, float y, std::vector<std::shared_ptr<Layer>>* results);
Expand Down
16 changes: 15 additions & 1 deletion include/tgfx/layers/ShapeLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,24 @@ class ShapeLayer : public Layer {
*/
void setStrokeAlign(StrokeAlign align);

/**
* Indicates whether strokes are drawn on top of child layers and layer styles. Normally, strokes
* are drawn above fills but below child layers. If true, strokes are drawn above all child layers
* and layer styles. The default value is false.
*/
bool strokeOnTop() const {
return _strokeOnTop;
}

void setStrokeOnTop(bool value);

protected:
ShapeLayer() = default;

std::unique_ptr<LayerContent> onUpdateContent() override;

void drawContour(LayerContent* content, Canvas* canvas, const Paint& paint) const override;
void drawContents(LayerContent* content, Canvas* canvas, float alpha, bool forContour,
const std::function<void()>& drawChildren) const override;

private:
std::shared_ptr<Shape> _shape = nullptr;
Expand All @@ -290,7 +302,9 @@ class ShapeLayer : public Layer {
float _strokeStart = 0.0f;
float _strokeEnd = 1.0f;
StrokeAlign _strokeAlign = StrokeAlign::Center;
bool _strokeOnTop = false;

Paint getPaint(float alpha) const;
std::shared_ptr<Shape> createStrokeShape() const;
};
} // namespace tgfx
7 changes: 3 additions & 4 deletions src/layers/DrawArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,21 @@ class DrawArgs {
DrawArgs() = default;

DrawArgs(Context* context, uint32_t renderFlags, bool cleanDirtyFlags = false,
bool excludeEffects = false, bool drawContour = false)
bool excludeEffects = false, bool forContour = false)
: context(context), renderFlags(renderFlags), cleanDirtyFlags(cleanDirtyFlags),
excludeEffects(excludeEffects), drawContour(drawContour) {
excludeEffects(excludeEffects), forContour(forContour) {
}

// The GPU context to be used during the drawing process. Note: this could be nullptr.
Context* context = nullptr;
// Render flags to be used during the drawing process.
uint32_t renderFlags = 0;

// Whether to clean the dirty flags of the associated Layer during the drawing process.
bool cleanDirtyFlags = false;
// Whether to exclude effects during the drawing process.
bool excludeEffects = false;
// Whether to draw the contour of the associated Layer during the drawing process. If true, the
// contour will be drawn instead of the content.
bool drawContour = false;
bool forContour = false;
};
} // namespace tgfx
167 changes: 79 additions & 88 deletions src/layers/Layer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,13 @@ namespace tgfx {
static std::atomic_bool AllowsEdgeAntialiasing = true;
static std::atomic_bool AllowsGroupOpacity = false;

static bool ChildrenContainEffects(const Layer* layer) {
for (auto child : layer->children()) {
if (!child->layerStyles().empty() || !child->filters().empty()) {
return true;
}
if (ChildrenContainEffects(child.get())) {
return true;
}
}
return false;
}
struct LayerStyleSource {
float contentScale = 1.0f;
std::shared_ptr<Image> content = nullptr;
Point contentOffset = Point::Zero();
std::shared_ptr<Image> contour = nullptr;
Point contourOffset = Point::Zero();
};

static std::shared_ptr<Picture> CreatePicture(
const DrawArgs& args, float contentScale,
Expand Down Expand Up @@ -604,8 +600,8 @@ LayerContent* Layer::getContent() {
return layerContent.get();
}

Paint Layer::getLayerPaint(float alpha, BlendMode blendMode) {
Paint paint;
Paint Layer::getLayerPaint(float alpha, BlendMode blendMode) const {
Paint paint = {};
paint.setAntiAlias(bitFields.allowsEdgeAntialiasing);
paint.setAlpha(alpha);
paint.setBlendMode(blendMode);
Expand Down Expand Up @@ -658,7 +654,7 @@ std::shared_ptr<Image> Layer::getRasterizedImage(const DrawArgs& args, float con
return nullptr;
}
auto picture = CreatePicture(args, contentScale, [this](const DrawArgs& args, Canvas* canvas) {
drawContents(args, canvas, 1.0f);
drawDirectly(args, canvas, 1.0f);
});
if (!picture) {
return nullptr;
Expand Down Expand Up @@ -688,7 +684,7 @@ void Layer::drawLayer(const DrawArgs& args, Canvas* canvas, float alpha, BlendMo
drawOffscreen(args, canvas, alpha, blendMode);
} else {
// draw directly
drawContents(args, canvas, alpha);
drawDirectly(args, canvas, alpha);
}
}

Expand Down Expand Up @@ -740,15 +736,12 @@ void Layer::drawOffscreen(const DrawArgs& args, Canvas* canvas, float alpha, Ble
}

auto picture = CreatePicture(args, contentScale, [this](const DrawArgs& args, Canvas* canvas) {
drawContents(args, canvas, 1.0f);
drawDirectly(args, canvas, 1.0f);
});
if (picture == nullptr) {
return;
}
Paint paint = {};
paint.setAntiAlias(bitFields.allowsEdgeAntialiasing);
paint.setAlpha(alpha);
paint.setBlendMode(blendMode);
auto paint = getLayerPaint(alpha, blendMode);
paint.setMaskFilter(getMaskFilter(args, contentScale));
if (!args.excludeEffects) {
paint.setImageFilter(getImageFilter(contentScale));
Expand All @@ -757,76 +750,25 @@ void Layer::drawOffscreen(const DrawArgs& args, Canvas* canvas, float alpha, Ble
canvas->drawPicture(std::move(picture), &matrix, &paint);
}

void Layer::drawContents(const DrawArgs& args, Canvas* canvas, float alpha) {
auto drawLayerContents = [this, alpha](const DrawArgs& args, Canvas* canvas) {
auto content = getContent();
if (args.drawContour) {
drawContour(content, canvas, getLayerPaint(alpha, BlendMode::SrcOver));
} else if (content != nullptr) {
content->draw(canvas, getLayerPaint(alpha, BlendMode::SrcOver));
}
void Layer::drawDirectly(const DrawArgs& args, Canvas* canvas, float alpha) {
auto layerStyleSource = getLayerStyleSource(args, canvas->getMatrix());
if (layerStyleSource) {
drawLayerStyles(canvas, alpha, layerStyleSource.get(), LayerStylePosition::Below);
}
drawContents(getContent(), canvas, alpha, args.forContour, [&]() {
drawChildren(args, canvas, alpha);
if (args.cleanDirtyFlags) {
bitFields.childrenDirty = false;
if (layerStyleSource) {
drawLayerStyles(canvas, alpha, layerStyleSource.get(), LayerStylePosition::Above);
}
};

if (_layerStyles.empty() || args.excludeEffects) {
drawLayerContents(args, canvas);
return;
}

auto contentScale = canvas->getMatrix().getMaxScale();
if (FloatNearlyZero(contentScale)) {
return;
}

AutoCanvasRestore autoRestore(canvas);
canvas->scale(1.f / contentScale, 1.f / contentScale);
Point offset = Point::Zero();
std::shared_ptr<Image> source = nullptr;
auto picture = CreatePicture(args, contentScale, drawLayerContents);
if (picture == nullptr) {
return;
}
if (!bitFields.excludeChildEffectsInLayerStyle || !ChildrenContainEffects(this)) {
source = CreatePictureImage(picture, &offset);
} else {
auto newArgs =
DrawArgs(args.context, args.renderFlags | RenderFlags::DisableCache, false, true);
auto contentsWithoutEffects = CreatePicture(newArgs, contentScale, drawLayerContents);
source = CreatePictureImage(contentsWithoutEffects, &offset);
}
if (source == nullptr) {
return;
}
std::shared_ptr<Image> contour = nullptr;
Point contourOffset = offset;
auto needContour =
std::any_of(_layerStyles.begin(), _layerStyles.end(),
[](const auto& layerStyle) { return layerStyle->requireLayerContour(); });
if (needContour) {
// Child effects are always excluded when drawing the layer contour.
DrawArgs newArgs =
DrawArgs(args.context, args.renderFlags | RenderFlags::DisableCache, false, true, true);
auto contourPicture = CreatePicture(newArgs, contentScale, drawLayerContents);
contour = CreatePictureImage(contourPicture, &contourOffset);
contourOffset -= offset;
}

canvas->translate(offset.x, offset.y);
drawLayerStyles(canvas, source, contentScale, contour, contourOffset, alpha,
LayerStylePosition::Below);
auto matrix = Matrix::MakeTrans(-offset.x, -offset.y);
canvas->drawPicture(std::move(picture), &matrix, nullptr);
drawLayerStyles(canvas, source, contentScale, contour, contourOffset, alpha,
LayerStylePosition::Above);
});
}

void Layer::drawContour(LayerContent* content, Canvas* canvas, const Paint& paint) const {
void Layer::drawContents(LayerContent* content, Canvas* canvas, float alpha, bool,
const std::function<void()>& drawChildren) const {
if (content != nullptr) {
content->draw(canvas, paint);
content->draw(canvas, getLayerPaint(alpha));
}
drawChildren();
}

void Layer::drawChildren(const DrawArgs& args, Canvas* canvas, float alpha) {
Expand All @@ -841,20 +783,69 @@ void Layer::drawChildren(const DrawArgs& args, Canvas* canvas, float alpha) {
}
child->drawLayer(args, canvas, child->_alpha * alpha, child->_blendMode);
}
if (args.cleanDirtyFlags) {
bitFields.childrenDirty = false;
}
}

std::unique_ptr<LayerStyleSource> Layer::getLayerStyleSource(const DrawArgs& args,
const Matrix& matrix) {
if (_layerStyles.empty() || args.excludeEffects) {
return nullptr;
}
auto contentScale = matrix.getMaxScale();
if (FloatNearlyZero(contentScale)) {
return nullptr;
}

auto drawLayerContents = [this](const DrawArgs& drawArgs, Canvas* canvas) {
drawContents(getContent(), canvas, 1.0f, drawArgs.forContour,
[&]() { drawChildren(drawArgs, canvas, 1.0f); });
};

DrawArgs drawArgs(args.context, args.renderFlags | RenderFlags::DisableCache, false,
bitFields.excludeChildEffectsInLayerStyle);
auto contentPicture = CreatePicture(drawArgs, contentScale, drawLayerContents);
auto contentOffset = Point::Zero();
auto content = CreatePictureImage(contentPicture, &contentOffset);
if (content == nullptr) {
return nullptr;
}
auto source = std::make_unique<LayerStyleSource>();
source->contentScale = contentScale;
source->content = std::move(content);
source->contentOffset = contentOffset;
auto needContour =
std::any_of(_layerStyles.begin(), _layerStyles.end(),
[](const auto& layerStyle) { return layerStyle->requireLayerContour(); });
if (needContour) {
// Child effects are always excluded when drawing the layer contour.
drawArgs.excludeEffects = true;
drawArgs.forContour = true;
auto contourPicture = CreatePicture(drawArgs, contentScale, drawLayerContents);
source->contour = CreatePictureImage(contourPicture, &source->contourOffset);
}
return source;
}

void Layer::drawLayerStyles(Canvas* canvas, std::shared_ptr<Image> content, float contentScale,
std::shared_ptr<Image> contour, const Point& contourOffset, float alpha,
void Layer::drawLayerStyles(Canvas* canvas, float alpha, const LayerStyleSource* source,
LayerStylePosition position) {
DEBUG_ASSERT(source != nullptr && !FloatNearlyZero(source->contentScale));
auto matrix = Matrix::MakeScale(1.f / source->contentScale, 1.f / source->contentScale);
matrix.preTranslate(source->contentOffset.x, source->contentOffset.y);
auto& contour = source->contour;
auto contourOffset = source->contourOffset - source->contentOffset;
for (const auto& layerStyle : _layerStyles) {
if (layerStyle->position() != position) {
continue;
}
AutoCanvasRestore autoRestore(canvas);
canvas->concat(matrix);
if (layerStyle->requireLayerContour() && contour != nullptr) {
layerStyle->drawWithContour(canvas, content, contentScale, contour, contourOffset, alpha);
layerStyle->drawWithContour(canvas, source->content, source->contentScale, contour,
contourOffset, alpha);
} else {
layerStyle->draw(canvas, content, contentScale, alpha);
layerStyle->draw(canvas, source->content, source->contentScale, alpha);
}
}
}
Expand Down
Loading
Loading