Skip to content

feat(DrawUtils): add drawInsetShadow for inner shadow rendering - #786

Merged
18202781743 merged 2 commits into
linuxdeepin:v25-flowstylefrom
zqq-dora:feat/draw-inset-shadow-v25
Sep 20, 2026
Merged

18202781743 merged 2 commits into
linuxdeepin:v25-flowstylefrom
zqq-dora:feat/draw-inset-shadow-v25

Conversation

@zqq-dora

@zqq-dora zqq-dora commented Sep 20, 2026 •

Copy link
Copy Markdown

Summary

Add DDrawUtils::drawInsetShadow() to dtkwidget, parallel to the existing drawShadow(), for rendering inset (inner) shadows on QWidget controls. This mirrors the QML BoxInsetShadow component in dtkdeclarative, enabling consistent inset shadow effects across both QWidget and QML frameworks.

The implementation uses qt_blurImage (same as drawShadow) to blur a hole-punched alpha mask, then composites the shadow color and clips to the rounded-rect shape.

Implementation

  1. Create an opaque black image matching the target rect size.
  2. Cut out a rounded-rect hole shifted by offset using CompositionMode_Clear.
  3. Blur the alpha channel with qt_blurImage (same function as drawShadow).
  4. Replace the placeholder black with the actual shadow color via CompositionMode_SourceIn.
  5. Clip to the rounded-rect path before drawing, hiding the opaque outer area so only the inward-fading shadow is visible.

Mapping to QML BoxInsetShadow

QML BoxInsetShadow DDrawUtils::drawInsetShadow
cornerRadius xRadius / yRadius
shadowColor sc
shadowOffsetY: 1 (top highlight) offset = (0, 1)
shadowOffsetY: -1 (bottom dark edge) offset = (0, -1)
shadowBlur radius

Files

File Change
include/widgets/dstyle.h Add drawInsetShadow declaration
src/widgets/dstyle.cpp Add drawInsetShadow implementation

概述(中文)

在 dtkwidget 的 DDrawUtils 命名空间中新增 drawInsetShadow() 函数,与现有的 drawShadow() 接口风格平行,用于在 QWidget 控件上绘制内阴影。与 dtkdeclarative 的 QML BoxInsetShadow 组件对应,使 QWidget 和 QML 两端视觉一致。

实现使用 qt_blurImage(与 drawShadow 同一函数)对挖孔后的 alpha 蒙版做高斯模糊,再合成阴影颜色并裁剪到圆角矩形形状。

实现原理

  1. 创建与目标区域同大小的不透明黑色图片。
  2. 用 CompositionMode_Clear 挖出按 offset 偏移的圆角矩形孔。
  3. 调用 qt_blurImage 对 alpha 通道做高斯模糊(与 drawShadow 使用同一函数)。
  4. 用 CompositionMode_SourceIn 将占位黑色替换为实际阴影颜色。
  5. 用圆角矩形路径裁剪后绘制,隐藏外圈不透明区域,只保留内阴影。

与 QML BoxInsetShadow 的对应

QML BoxInsetShadow DDrawUtils::drawInsetShadow
cornerRadius xRadius / yRadius
shadowColor sc
shadowOffsetY: 1(顶部高光) offset = (0, 1)
shadowOffsetY: -1(底部暗角) offset = (0, -1)
shadowBlur radius

修改文件

文件 改动
include/widgets/dstyle.h 新增 drawInsetShadow 声明
src/widgets/dstyle.cpp 新增 drawInsetShadow 实现

Summary by Sourcery

Add a reusable inset-shadow drawing API for consistent inner shadow effects across QWidget controls.

New Features:

  • Add DDrawUtils::drawInsetShadow() for rendering cached, rounded-rectangle inner shadows in QWidget controls.

Enhancements:

  • Align QWidget inset-shadow rendering capabilities with the existing QML inset-shadow behavior.

Chores:

  • Update copyright years in the affected files.

@sourcery-ai

sourcery-ai Bot commented Sep 20, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds DDrawUtils::drawInsetShadow to provide QWidget inner-shadow rendering compatible with QML BoxInsetShadow, using scaled offscreen image compositing, alpha blur, offset handling, and rounded-rectangle clipping.

Sequence diagram for inset shadow rendering

sequenceDiagram
    participant Caller
    participant DDrawUtils
    participant QImage
    participant QPainter
    participant qt_blurImage

    Caller->>DDrawUtils: drawInsetShadow(pa, rect, xRadius, yRadius, sc, radius, offset)
    DDrawUtils->>QImage: Create opaque shadow_base
    DDrawUtils->>QPainter: CompositionMode_Clear
    QPainter->>QImage: Cut offset rounded-rect hole
    DDrawUtils->>qt_blurImage: Blur shadow_base alpha
    qt_blurImage-->>DDrawUtils: Return blurred image
    DDrawUtils->>QPainter: CompositionMode_SourceIn
    QPainter->>QImage: Apply shadow color sc
    DDrawUtils->>QPainter: Clip rounded rect
    QPainter->>Caller: Draw inset shadow image
Loading

Flow diagram for inset shadow compositing

flowchart LR
    A[Create opaque black image] --> B[Clear offset rounded-rect hole]
    B --> C[Blur alpha with qt_blurImage]
    C --> D[Replace black with shadow color]
    D --> E[Clip to rounded rectangle]
    E --> F[Draw image into target rectangle]
Loading

File-Level Changes

Change Details Files
Expose and implement a QWidget-side inset shadow renderer using an offscreen alpha mask, Gaussian blur, color compositing, and rounded-rectangle clipping.
  • Add the public drawInsetShadow API alongside existing shadow helpers.
  • Create a device-pixel-ratio-scaled opaque mask and offset rounded-rectangle cutout.
  • Blur the mask with qt_blurImage, apply the requested color, and clip output to the target rounded rectangle.
  • Handle non-positive blur radius or null rectangles as no-ops.
include/widgets/dstyle.h
src/widgets/dstyle.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@deepin-ci-robot

Copy link
Copy Markdown
Contributor

Hi @zqq-dora. Thanks for your PR.

I'm waiting for a linuxdeepin member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/widgets/dstyle.cpp" line_range="411-446" />
<code_context>
+    QImage shadow_base(size, QImage::Format_ARGB32_Premultiplied);
</code_context>
<issue_to_address>
**issue (performance):** `drawInsetShadow` allocates two full-size images and runs `qt_blurImage` on every invocation, with no cache equivalent to the existing `drawShadow` implementation. Repainting a widget repeatedly, such as during hover or animation, therefore performs a full image allocation and Gaussian blur on every paint event.

**Triggers:** When a control using the new helper is repainted frequently or has a large rectangle.

**Suggested fix:** Cache the generated shadow by size, radii, color, blur radius, offset, and device-pixel ratio, or reuse a precomputed mask while only applying the current color.
</issue_to_address>

### Comment 2
<location path="src/widgets/dstyle.cpp" line_range="405" />
<code_context>
+    QSize size = rect.size() * scale;
+    xRadius *= scale;
+    yRadius *= scale;
+    radius *= scale;
+    QPoint scaledOffset(offset.x() * scale, offset.y() * scale);
+
+    // Build an opaque image, then cut out a rounded-rect hole shifted by
</code_context>
<issue_to_address>
**issue (bug_risk):** `scaledOffset` converts the scaled floating-point offset directly to `QPoint`, truncating fractional device-pixel values. On fractional device-pixel-ratio displays, an offset such as one logical pixel at 1.5x is rendered as one physical pixel instead of 1.5 physical pixels, producing a systematically incorrect inset-shadow offset.

**Triggers:** When the painter's device pixel ratio is fractional and `offset` is nonzero.

**Suggested fix:** Use a floating-point offset for the geometry or explicitly round the scaled values with the same policy used for other device-pixel geometry.

```suggestion
    QPointF scaledOffset(offset.x() * scale, offset.y() * scale);
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/widgets/dstyle.cpp Outdated
Comment thread src/widgets/dstyle.cpp Outdated
Log: 在 DDrawUtils 中新增 drawInsetShadow(),用于在 QWidget 控件上绘制内阴影,与 dtkdeclarative 的 BoxInsetShadow 视觉效果保持一致
@zqq-dora
zqq-dora force-pushed the feat/draw-inset-shadow-v25 branch from cc7d18b to fa86ca1 Compare September 20, 2026 02:47
Comment thread src/widgets/dstyle.cpp Outdated
pa->drawPixmap(shadow_rect, shadow);
}

void drawInsetShadow(QPainter *pa, const QRect &rect, qreal xRadius, qreal yRadius, const QColor &sc, qreal radius, const QPoint &offset)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可以仿照drawShadow的方式去实现,分开具体的insetShaodw的实现与导出的接口,添加缓存

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

收到,按建议重构,计划如下:

  1. 拆分核心实现与导出接口:抽一个内部 static 函数负责生成内阴影图
    (挖孔蒙版 + qt_blurImage + 颜色合成),drawInsetShadow 作为公开接口
    只负责取图、按圆角裁剪、绘制。
  2. 加缓存:用 QPixmapCache 缓存生成的内阴影图,key 含尺寸/圆角/模糊半径/
    offset/DPR。需要说明一点——内阴影与外阴影几何不同:外阴影圆角在 base
    外四角,可用 borderImage 九宫格拉伸、缓存不含尺寸的小图;内阴影圆角在
    "孔"的内四角、渐变也在孔边缘,九宫格拉伸会让圆角和渐变变形,故按整图
    (带尺寸)缓存,hover/动画同尺寸即可命中。形式与 drawShadow 略有差异,
    但同样满足"分层 + 缓存"。
  3. 顺手把 scaledOffset 由 QPoint 改为 QPointF,修正小数 DPR 下的偏移截断。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已按建议重构完成(新提交 a4ebf02):

  1. 拆分实现与接口:抽出 static createInsetShadow() 负责生成内阴影图(挖孔蒙版 + qt_blurImage + 颜色合成),drawInsetShadow() 作为公开接口只做缓存查找、裁剪、绘制,与 drawShadow / dropShadow / borderImage 的分层方式一致。

  2. 添加缓存:用 QPixmapCache 缓存生成的内阴影图,key 含 尺寸/圆角/模糊半径/颜色+alpha/offset/DPR,hover、动画同尺寸重绘时命中缓存,不再每次 paint 重新分配和模糊。

补充一点:内阴影的圆角和内凹渐变都在"孔"的内边缘,无法像外阴影那样用 borderImage 九宫格拉伸复用小图(中间 tile 拉伸会变形),所以缓存按整图、key 带尺寸,与 drawShadow 缓存小 base 图的形式略有差异,但同样满足"分层 + 缓存"的思路。

  1. 顺带把 scaledOffset 从 QPoint 改为 QPointF,修正小数 DPR 下的偏移截断。

Separate the core image generation into static createInsetShadow(),
keep drawInsetShadow() as the public interface doing cache lookup,
clip, and draw - mirroring the drawShadow / dropShadow / borderImage
layering.

Cache the generated shadow with QPixmapCache keyed by size, radii,
blur radius, color+alpha, offset and DPR, so hover/animation repaints
of the same shape reuse the precomputed image instead of reallocating
and re-blurring every paint.

Also fix scaledOffset type from QPoint to QPointF to avoid truncating
fractional device-pixel values on fractional-DPR displays.
@deepin-ci-robot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743, zqq-dora

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@18202781743
18202781743 merged commit 56f1571 into linuxdeepin:v25-flowstyle Sep 20, 2026
21 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants