Skip to content

Conversation

@Vinci10
Copy link
Contributor

@Vinci10 Vinci10 commented Jul 2, 2025

Draft: Add Per-Corner Radius and Shadow Support for Rectangle, Square, and Circle

Description:

Fixes:

  • #5352
  • #4129 (shadows only)
  • #4411 (shadows on the right and bottom can be added using proper offset)

This pull request introduces enhancements to the canvas objects, focusing on improved visual flexibility and shadow rendering:

  • Per-Corner Radius Support

    • Rectangle and Square now support specifying a different corner radius for each corner (TopRightCornerRadius, TopLeftCornerRadius, BottomRightCornerRadius, BottomLeftCornerRadius).
    • The CornerRadius field remains as the default; per-corner fields override it if set with higher value.
    • Added a new constant FullyRoundedCornerRadius for pill/circular shapes.
  • Shadow Support for Rectangle, Square, and Circle

    • Introduced a baseShadow struct and Shadowable interface to provide reusable shadow logic.
    • Added fields for shadow color, softness, offset, and type (drop/box shadow).
    • The ExpandForShadow field allows the object to keep its requested/original size, expanding the total bounds to include the shadow.
    • Updated shader and painter logic to render shadows with configurable softness and offset, and to blend shadows correctly.
    • If a canvas object has a shadow added, the canvas size is reduced by the shadow paddings on each side. This ensures that the entire object—including its shadow—fits within the originally requested frame size. The content area (without shadow) will be smaller to accommodate the shadow within the allocated space.
  • Shader and Painter Updates

    • Rectangle and round-rectangle shaders updated to support shadow rendering and per-corner radii.
    • Painter logic updated to pass new parameters and handle shadow bounds.
  • Rounded Edge Softness Fix

    • Fixed an issue where rounded edge was not rendered correctly.
  • API Additions

    • New methods and fields are documented in code comments.
    • Backwards compatibility is maintained for existing code.

Status

  • This is a draft PR.
  • I am looking for feedback and suggestions from the Fyne team regarding:
    • API design and naming
    • Performance considerations
  • Tests are not yet included.
  • Performance has not been fully evaluated.

Additional Notes

This work is a prerequisite for adding shadows and rounded corners to popups, dialogs, and menus in Fyne. I have already applied these changes to those components and have working code locally. Rectangle shadow provides the necessary foundation for theme-level enhancements.

Examples

shadow_examples

Edge smoothness

Before

edge_smoothness_before

After

edge_smoothness_after

Dialog, menu shadow (preview)

dialog_shadow
menu_shadow


Please let me know if you have any suggestions, concerns, or requests for changes.

Checklist:

  • Tests included.
  • Lint and formatter run with no errors.
  • Tests all pass.

Where applicable:

  • Public APIs match existing style and have Since: line.
  • Any breaking changes have a deprecation path or have been discussed.
  • Check for binary size increases when importing new modules.

@Jacalz
Copy link
Member

Jacalz commented Jul 2, 2025

Thanks. This is really cool. I think it might be easier to review if the separate work is split up into multiple PRs. One for the rounded edge softness fix for example and then maybe more if it makes sense.

@andydotxyz
Copy link
Member

This is completely amazing thanks so much.

I'm not certain about the geometry changes - would it not be normal for the shadows to be drawn around the object and /not/ impact its geometry? I will need to look into the PR and understand the API changes to give a more educated comment, but that feels a little surprising on the surface.

Excited to see such a big graphics addition though 😎

@Vinci10
Copy link
Contributor Author

Vinci10 commented Jul 3, 2025

@Jacalz Yes, I can split the PR into four separate PRs for easier review:

  1. Edge smoothness fix
  2. Fully rounded corner radius
  3. Per-corner radius
  4. Shadow support

Please let me know if this works for you, or if you'd prefer a different grouping.

@andydotxyz You are right—by default, adding a shadow currently impacts the geometry: the canvas size is reduced by the shadow paddings so that both the object and its shadow fit inside the requested frame. This means the content (e.g., a rectangle) is shrunk to ensure the shadow does not overflow the frame.

To address this, I introduced the ExpandForShadow option. When set to true, the object keeps its requested/original size, and the overall canvas size is increased to fit the shadow outside the content.

Example Calculations

Example 1

Suppose you have a rectangle with a requested frame size of 150 x 250.

  • Shadow parameters:
    • Offset: (-10, 15)
    • Softness: 8

Shadow paddings calculation:

  • Left: offsetX + softness = -10 + 8 = -2 (clamped to 0)
  • Top: -offsetY + softness = -15 + 8 = -7 (clamped to 0)
  • Right: -offsetX + softness = 10 + 8 = 18
  • Bottom: offsetY + softness = 15 + 8 = 23

After clamping negatives to zero:

  • Left: 0
  • Top: 0
  • Right: 18
  • Bottom: 23
Mode Rectangle Size Canvas Size Rectangle Position Canvas Position
No shadow 150 x 250 150 x 250 (0, 0) (0, 0)
Shadow, ExpandForShadow=false 132 x 227 150 x 250 (0, 0) (0, 0)
Shadow, ExpandForShadow=true 150 x 250 168 x 273 (0, 0) (0, 0)

Example 2

Suppose you have a rectangle with a requested frame size of 100 x 100.

  • Shadow parameters:
    • Offset: (12, -6)
    • Softness: 5

Shadow paddings calculation:

  • Left: 12 + 5 = 17
  • Top: 6 + 5 = 11
  • Right: -12 + 5 = -7 (clamped to 0)
  • Bottom: -6 + 5 = -1 (clamped to 0)

After clamping negatives to zero:

  • Left: 17
  • Top: 11
  • Right: 0
  • Bottom: 0
Mode Rectangle Size Canvas Size Rectangle Position Canvas Position
No shadow 100 x 100 100 x 100 (0, 0) (0, 0)
Shadow, ExpandForShadow=false 83 x 89 100 x 100 (17, 11) (0, 0)
Shadow, ExpandForShadow=true 100 x 100 117 x 111 (0, 0) (-17, -11)

Notes:

  • When ExpandForShadow is false, the rectangle is shrunk and shifted inside the canvas to fit the shadow, so the rectangle's position changes (by the left/top padding), but the canvas position remains the same.
  • When ExpandForShadow is true, the rectangle keeps its original position and size, but the canvas itself is shifted (by the left/top padding) to ensure the rectangle appears at the requested position, and the overall canvas size is increased to fit

This approach allows developers to choose whether the shadow should be included inside the original frame (shrinking the content) or expand the overall bounds to preserve the content size.

Please let me know if you’d like further clarification or adjustments to this behavior!

@Jacalz
Copy link
Member

Jacalz commented Jul 3, 2025

Yes, I can split the PR into four separate PRs for easier review

Thanks. Yes, that would make it a lot easier to review and let some changes land while discussions and/or reviews continue on others. It is ways good to keep PRs minimal and restricted to a single larger change.

@Vinci10
Copy link
Contributor Author

Vinci10 commented Jul 4, 2025

I created a separate PR for each change.

I am closing this one.

@Vinci10 Vinci10 closed this Jul 4, 2025
@andydotxyz
Copy link
Member

@andydotxyz You are right—by default, adding a shadow currently impacts the geometry: the canvas size is reduced by the shadow paddings so that both the object and its shadow fit inside the requested frame. This means the content (e.g., a rectangle) is shrunk to ensure the shadow does not overflow the frame.

To address this, I introduced the ExpandForShadow option. When set to true, the object keeps its requested/original size, and the overall canvas size is increased to fit the shadow outside the content.

You realise that an object is not restricted to drawing in its "frame"?

I don't think we should add more APIs to control how the geometry will be interpreted, it will just lead to confusion.
Simple and standard is the way - provide the "path of least surprise".

Will move the conversation to #5841

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants