dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.2k stars 1.75k forks source link

Shadow on Label disappears permanently and won't come back if Label opacity is ever tweened down to zero #17916

Open jonmdev opened 1 year ago

jonmdev commented 1 year ago

Description

This is a bizarre bug I can't explain but is easy to reproduce. If you create a Label with a Shadow, then tween that Label's opacity down to zero in Windows, the Shadow disappears and won't come back.

To illustrate, I created the following simple program which just features a few Labels on screen, and on a Timer, will tween their opacity either from 0 to 1 or 1 to 0 depending on the showHide int which is toggled between +1 (to show) and -1 (to hide):

using System.Diagnostics;

namespace Windows_Shadow_Opacity_Bug {
    public partial class App : Application {
        private double screenWidth;
        private double screenHeight;
        public event Action screenSizeChanged = null;
        double deltaTime = 0;
        int showHide = 1;

        public App() {
            InitializeComponent();

            ContentPage mainPage = new();
            MainPage = mainPage;

            AbsoluteLayout abs = new();
            mainPage.Content = abs;

            VerticalStackLayout vert = new();
            abs.Children.Add(vert);

            //create a group of labels on screen
            List<View> labelList = new();
            for (int i=0; i < 5; i++) {
                Label label = new();
                label.Text = "HELLO MAUI";
                label.FontAttributes = FontAttributes.Bold;
                label.FontSize = 60;
                label.Shadow = new() { Offset =  new Point(0, 5), Radius = 7 };
                vert.Children.Add(label);
                labelList.Add(label);
            }

            //animation timer and function
            var timer = Application.Current.Dispatcher.CreateTimer();
            timer.Interval = TimeSpan.FromSeconds( 1 / 180);
            timer.Start();
            DateTime dateTime = DateTime.Now;
            timer.Tick += delegate {
                deltaTime = (DateTime.Now - dateTime).TotalSeconds;
                dateTime = DateTime.Now;

                double animationTime = 1;
                double targetOpacity = (showHide + 1) / 2;

                for (int i = 0; i < labelList.Count; i++) {
                    if (targetOpacity != labelList[i].Opacity) {

                        //tweening the label opacity between 1 and 0 will cause the shadow to be lost:
                        labelList[i].Opacity = Math.Clamp(labelList[i].Opacity + showHide * deltaTime/ animationTime, 0, 1);

                        //alternatively if you immediately change the opacity between zero and one in 1 step back and forth each time click rather than tween, shadow is NOT lost:
                        //labelList[i].Opacity = Math.Clamp(showHide, 0, 1); 
                    }
                }

            };

            //tap function
            TapGestureRecognizer tap = new();
            tap.Tapped += delegate {
                showHide *= -1;
            };

            //border to take clicks
            Border clickBorder = new();
            abs.Children.Add(clickBorder);
            clickBorder.BackgroundColor = Colors.Blue;
            clickBorder.Opacity = 0;
            clickBorder.GestureRecognizers.Add(tap);

            //screen resize function
            mainPage.SizeChanged += delegate {
                if (mainPage.Width > 0 && mainPage.Height > 0) {
                    screenWidth = mainPage.Width;
                    screenHeight = mainPage.Height;
                    vert.WidthRequest = screenWidth;
                    vert.HeightRequest = screenHeight;
                    clickBorder.WidthRequest = screenWidth;
                    clickBorder.HeightRequest = screenHeight;
                    abs.HeightRequest = screenHeight;
                    abs.WidthRequest = screenWidth;

                }

            };

        }
    }
}

This is what it looks like if you play it to start:

windows shadow 1

If you click the screen, these labels will fade out. If you click again, they will fade back in. But now bizarrely their Shadows will be gone:

windows shadow 2

What is even more interesting is if you comment out the line labelList[i].Opacity = Math.Clamp(labelList[i].Opacity + showHide * deltaTime/ animationTime, 0, 1); and let back in the line labelList[i].Opacity = Math.Clamp(showHide, 0, 1); so this no longer tweens the opacity but rather sets it hard back and forth between 0 and 1, the same problem does not happen.

So this problem is only provoked by tweening opacity. And it only happens in Windows.

I presume there must be some intermediate opacity between 0 and 1 that is triggering a glitch and only in Windows but don't know what specific value it is or why it is happening.

Thanks for any help or solutions.

Steps to Reproduce

  1. Create a new project by File > New and name it "Windows Shadow Opacity Bug"
  2. Copy paste the code above to replace App.xaml.cs
  3. Play project and click screen to fade out labels, click again to fade them back in.
  4. Observe that when they return their Shadows are gone

Link to public reproduction project repository

https://github.com/jonmdev/Windows-Shadow-Opacity-Bug

Version with bug

7.0.92

Is this a regression from previous behavior?

No, this is something new

Last version that worked well

Unknown/Other

Affected platforms

Windows

Affected platform versions

Windows 10

Did you find any workaround?

If you never tween Label opacities but only hard set them to 0 or 1, this can be avoided. If you also keep the opacity floor above around 0.01 or 0.05 or so (I believe) it doesn't happen. So I presume there is some math or programming error that is happening once Label opacity gets close to zero but not exactly zero.

Relevant log output

No response

ghost commented 1 year ago

We've added this issue to our backlog, and we will work to address it as time and resources allow. If you have any additional information or questions about this issue, please leave a comment. For additional info about issue management, please read our Triage Process.

jonmdev commented 1 year ago

I have done a bit more experimentation and what seems to be happening is MAUI/Windows is tweening down the shadow of the object separately from the Label. So if you say clamp the expected opacity between 0.4 and 1, rather than seeing the shadow go to zero and disappear, it will go down to 0.4 opacity and stay there.

So it seems the opacity of the shadow can be tweened down but not up on MAUI/Windows. Very strange.

Is there any link to the MAUI Label/Shadow code that might be responsible? I can take a look.

I have also confirmed this does not happen with Border or BoxView, only Label.

XamlTest commented 1 year ago

Verified this on Visual Studio Enterprise 17.8.0 Preview 3.0(8.0.0-rc.2.9373). Repro on Windows 11 with below Project: Windows Shadow Opacity Bug.zip