Skip to content

Add View.shader and move decoration Container behind the main content#6123

Open
bl1nch wants to merge 3 commits intoflet-dev:mainfrom
bl1nch:view-decoration-and-shader
Open

Add View.shader and move decoration Container behind the main content#6123
bl1nch wants to merge 3 commits intoflet-dev:mainfrom
bl1nch:view-decoration-and-shader

Conversation

@bl1nch
Copy link
Contributor

@bl1nch bl1nch commented Feb 4, 2026

Description

The View rendering order has been updated so the decoration container is moved to the background and is now rendered fully behind the main content, including foreground_decoration. This fixes issues where decorations could overlap or interfere with content rendering. In addition, a new shader property (with configurable blend_mode) is introduced to preserve the previous behavior where foreground_decoration was visually applied on top of all other content.

Fixes #6094

Test Code

# Test code for the review of this PR

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Checklist

  • I signed the CLA.
  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • New and existing tests pass locally with my changes
  • I have made corresponding changes to the documentation (if applicable)

Summary by Sourcery

Update view rendering to place background decorations behind main content and introduce configurable shader-based foreground effects.

New Features:

  • Add a shader gradient and blend mode properties to views to apply customizable shader effects over the content.

Enhancements:

  • Change view decoration composition so the decoration container is rendered behind the main content while retaining an overlay effect via shaders.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

We've reviewed this pull request using the Sourcery rules engine

@ndonkoHenri
Copy link
Contributor

Can you please share code example to best see the effect of your addition/usecase?

@bl1nch
Copy link
Contributor Author

bl1nch commented Feb 5, 2026

# Base app
import flet as ft


async def before_main(page: ft.Page):
    container = ft.Container(
        width=500,
        height=300,
        bgcolor=ft.Colors.SURFACE_CONTAINER,
        alignment=ft.Alignment.CENTER,
        border_radius=15,
        content=ft.Text(
            value="Hello World!",
            size=26,
            weight=ft.FontWeight.W_600,
        ),
    )

    view = ft.View(
        route="/",
        bgcolor=ft.Colors.TRANSPARENT,
        vertical_alignment=ft.MainAxisAlignment.CENTER,
        horizontal_alignment=ft.CrossAxisAlignment.CENTER,
        controls=[container],
    )

    view.decoration = ft.BoxDecoration(
        image=ft.DecorationImage(
            src="https://fedoraproject.org/w/uploads/d/de/F34_default_wallpaper_night.jpg",
            fit=ft.BoxFit.COVER,
        ),
    )

    page.views.clear()
    page.views.append(view)


async def main(page: ft.Page):
    pass


ft.run(main, before_main=before_main)



# Image is rendering on top of gradient
import flet as ft


async def before_main(page: ft.Page):
    container = ft.Container(
        width=500,
        height=300,
        bgcolor=ft.Colors.SURFACE_CONTAINER,
        alignment=ft.Alignment.CENTER,
        border_radius=15,
        content=ft.Text(
            value="Hello World!",
            size=26,
            weight=ft.FontWeight.W_600,
        ),
    )

    view = ft.View(
        route="/",
        bgcolor=ft.Colors.TRANSPARENT,
        vertical_alignment=ft.MainAxisAlignment.CENTER,
        horizontal_alignment=ft.CrossAxisAlignment.CENTER,
        controls=[container],
    )

    view.decoration = ft.BoxDecoration(
        image=ft.DecorationImage(
            src="https://fedoraproject.org/w/uploads/d/de/F34_default_wallpaper_night.jpg",
            fit=ft.BoxFit.COVER,
        ),
        gradient=ft.RadialGradient(
            colors=[
                ft.Colors.TRANSPARENT,
                ft.Colors.with_opacity(0.9, ft.Colors.RED)
            ],
            stops=[0.1, 1.0],
            radius=1,
        ),
    )

    page.views.clear()
    page.views.append(view)


async def main(page: ft.Page):
    pass


ft.run(main, before_main=before_main)



# Gradient is rendering on top of main content (in current flet version)
# Main content is rendering on top of gradient (with PR)
import flet as ft


async def before_main(page: ft.Page):
    page.theme_mode = ft.ThemeMode.LIGHT

    container = ft.Container(
        width=500,
        height=300,
        bgcolor=ft.Colors.SURFACE_CONTAINER,
        alignment=ft.Alignment.CENTER,
        border_radius=15,
        content=ft.Text(
            value="Hello World!",
            size=26,
            weight=ft.FontWeight.W_600,
        ),
    )

    view = ft.View(
        route="/",
        bgcolor=ft.Colors.TRANSPARENT,
        vertical_alignment=ft.MainAxisAlignment.CENTER,
        horizontal_alignment=ft.CrossAxisAlignment.CENTER,
        controls=[container],
    )

    view.decoration = ft.BoxDecoration(
        image=ft.DecorationImage(
            src="https://fedoraproject.org/w/uploads/d/de/F34_default_wallpaper_night.jpg",
            fit=ft.BoxFit.COVER,
        ),
    )
    view.foreground_decoration = ft.BoxDecoration(
        gradient=ft.RadialGradient(
            colors=[
                ft.Colors.TRANSPARENT,
                ft.Colors.with_opacity(0.9, ft.Colors.RED)
            ],
            stops=[0.1, 1.0],
            radius=1,
        ),
    )

    page.views.clear()
    page.views.append(view)


async def main(page: ft.Page):
    pass


ft.run(main, before_main=before_main)



# Gradient is rendering on top of main content (with PR)
# (Use View.shader for previous behavior instead of View.foreground_decoration)
import flet as ft


async def before_main(page: ft.Page):
    page.theme_mode = ft.ThemeMode.LIGHT

    container = ft.Container(
        width=500,
        height=300,
        bgcolor=ft.Colors.SURFACE_CONTAINER,
        alignment=ft.Alignment.CENTER,
        border_radius=15,
        content=ft.Text(
            value="Hello World!",
            size=26,
            weight=ft.FontWeight.W_600,
        ),
    )

    view = ft.View(
        route="/",
        bgcolor=ft.Colors.TRANSPARENT,
        vertical_alignment=ft.MainAxisAlignment.CENTER,
        horizontal_alignment=ft.CrossAxisAlignment.CENTER,
        controls=[container],
    )

    view.decoration = ft.BoxDecoration(
        image=ft.DecorationImage(
            src="https://fedoraproject.org/w/uploads/d/de/F34_default_wallpaper_night.jpg",
            fit=ft.BoxFit.COVER,
        ),
    )
    view.shader = ft.RadialGradient(
        colors=[
            ft.Colors.TRANSPARENT,
            ft.Colors.with_opacity(0.9, ft.Colors.RED)
        ],
        stops=[0.1, 1.0],
        radius=1,
    )
    view.blend_mode = ft.BlendMode.SRC_OVER

    page.views.clear()
    page.views.append(view)


async def main(page: ft.Page):
    pass


ft.run(main, before_main=before_main)

@ndonkoHenri
Copy link
Contributor

I’m seeing a blocking runtime issue on my side:

Another exception was thrown: Multiple widgets used the same GlobalKey.

I think this comes from the new Stack logic in view.dart (around the bgContainer block).
Right now, result is used as the child of bgContainer and also added again as a separate stack child:

  • bgContainer = Container(... child: result)
  • result = Stack(children: [bgContainer, result])

That effectively mounts the same widget tree twice, which can duplicate GlobalKeys.

One more concern: ShaderMask is applied before decoration/foreground decoration, so the shader may not affect the final composed view as intended by the issue description. Can you clarify whether shader should apply to content only or to the final decorated output?

@bl1nch bl1nch marked this pull request as draft February 16, 2026 07:53
@bl1nch
Copy link
Contributor Author

bl1nch commented Feb 16, 2026

Sorry, didn’t notice that. Fixed and updated the example.

@bl1nch bl1nch marked this pull request as ready for review February 16, 2026 10:36
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

We've reviewed this pull request using the Sourcery rules engine

@ndonkoHenri
Copy link
Contributor

Thanks for the fixes.

I hope its not too much asked, but for a better understanding for everyone, can you please share the visual result/output before and after this PR? Cause to be honest, I don't still seem to fully understand. For example, with your PR, both the foregorund_decoration and background decoration are now rendered behind the main content, and hence, "foreground" looses its meaning?

Visual output can be a little more convincing to better understand your usecase and purpose of this PR.

@bl1nch
Copy link
Contributor Author

bl1nch commented Feb 17, 2026

Yes, of course, no problem.

1.png

This is how foreground_decoration with a RadialGradient works currently.
The gradient is rendered on top of all content.

2.png

After the PR, both background_decoration and foreground_decoration are rendered below the main content, while foreground_decoration is still rendered above background_decoration.
In this specific case, background_decoration uses an image. We cannot apply a RadialGradient inside the same BoxDecoration, because in Flutter the gradient would be painted before the image due to the decoration painting order.
Therefore, we use foreground_decoration with a RadialGradient.
It is rendered above the background image (from background_decoration), but below the main application content.

3.png

If we still want to render a RadialGradient over the entire content after this PR, we can use View.shader

@ndonkoHenri
Copy link
Contributor

Ah, I kind of see what you are trying to achieve.

flowchart TB
  subgraph Before["Before"]
    direction TB
    X1["foreground_decoration"]
    X2["app content"]
    X3["background_decoration"]

    X1 --> X2 --> X3
  end

  subgraph After["After"]
    direction TB
    Y1["shader (gradient) + blend_mode"]
    Y2["app content"]
    Y3["foreground_decoration"]
    Y4["background_decoration"]

    Y1 --> Y2 --> Y3 --> Y4
  end
Loading

This is kind of breaking though, as only one property (or 2 with blend_mode) of BoxDecoration are brought back for backward compatibility. Or to put it in another way, how will users who were previously making use of foreground_decoration (and all its props) migrate their app if your PR is merged? They will only be able to migrate gradient and blend_mode, but what of others?
This basically prevents users from having a proper foreground decoration displayed above app content.

Let me have your thoughts.

@bl1nch
Copy link
Contributor Author

bl1nch commented Feb 17, 2026

Yes, I understand. In fact, the only real use cases we can lose are either some kind of color with opacity overlay (otherwise the color overlay would simply cover everything), or applying a certain image, again with transparency, to achieve something like a watermark. I don’t think the other BoxDecoration props can really be used in this context — I can’t imagine real use cases for them.

Of course, if we want to preserve full backward compatibility, we could try to rework the PR. However, in that case we would need to think about how to rename the props properly (background_decoration, foreground_decoration, and possibly introduce something third).

What do you think about this?

@bl1nch
Copy link
Contributor Author

bl1nch commented Feb 18, 2026

@FeodorFitsner
Copy link
Contributor

OK, why not just add ShaderMask to page.overlay, which is already a Stack on top of page content?

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.

feature: Add shader_mask property to the View class

3 participants

Comments