File: parent\Shared\Microsoft\Windows\Themes\SystemDropShadowChrome.cs
Project: wpf\src\Themes\Luna\PresentationFramework.Luna.csproj (PresentationFramework.Luna)
//---------------------------------------------------------------------------
// File: SystemDropShadowChrome.cs
//
// Description:
// Implementation of system drop shadow effect.
//
// Copyright (C) 2006 by Microsoft Corporation.  All rights reserved.
//
//---------------------------------------------------------------------------
using System.Windows.Shapes;
using System.Windows.Controls;
using System.Diagnostics;
using System.Threading;
 
using System.Windows;
using System.Windows.Media;
using MS.Internal;
 
using System;
 
namespace Microsoft.Windows.Themes
{
    public sealed class SystemDropShadowChrome : Decorator
    {
        #region Constructors
 
        /// <summary>
        /// Instantiates a new instance of a SystemDropShadowChrome
        /// </summary>
        public SystemDropShadowChrome()
        {
        }
 
        #endregion Constructors
 
        #region Dynamic Properties
 
        /// <summary>
        /// DependencyProperty for <see cref="Color" /> property.
        /// </summary>
        public static readonly DependencyProperty ColorProperty =
                DependencyProperty.Register(
                        "Color",
                        typeof(Color),
                        typeof(SystemDropShadowChrome),
                        new FrameworkPropertyMetadata(
                                Color.FromArgb(0x71, 0x00, 0x00, 0x00),
                                FrameworkPropertyMetadataOptions.AffectsRender,
                                new PropertyChangedCallback(ClearBrushes)));
 
        /// <summary>
        /// The Color property defines the Color used to fill the shadow region.
        /// </summary>
        public Color Color
        {
            get { return (Color)GetValue(ColorProperty); }
            set { SetValue(ColorProperty, value); }
        }
 
        /// <summary>
        /// DependencyProperty for <see cref="CornerRadius" /> property.
        /// </summary>
        public static readonly DependencyProperty CornerRadiusProperty = 
                DependencyProperty.Register(
                        "CornerRadius",
                        typeof(CornerRadius),
                        typeof(SystemDropShadowChrome),
                        new FrameworkPropertyMetadata(
                                new CornerRadius(),
                                FrameworkPropertyMetadataOptions.AffectsRender,
                                new PropertyChangedCallback(ClearBrushes)),
                        new ValidateValueCallback(IsCornerRadiusValid));
 
        private static bool IsCornerRadiusValid(object value)
        {
            CornerRadius cr = (CornerRadius)value;
            return !(cr.TopLeft < 0.0 || cr.TopRight < 0.0 || cr.BottomLeft < 0.0 || cr.BottomRight < 0.0 ||
                     double.IsNaN(cr.TopLeft) || double.IsNaN(cr.TopRight) || double.IsNaN(cr.BottomLeft) || double.IsNaN(cr.BottomRight) ||
                     double.IsInfinity(cr.TopLeft) || double.IsInfinity(cr.TopRight) || double.IsInfinity(cr.BottomLeft) || double.IsInfinity(cr.BottomRight));
        }
 
        /// <summary>
        /// The CornerRadius property defines the CornerRadius of the object casting the shadow.
        /// </summary>
        public CornerRadius CornerRadius
        {
            get { return (CornerRadius)GetValue(CornerRadiusProperty); }
            set { SetValue(CornerRadiusProperty, value); }
        }
 
        private static void ClearBrushes(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            ((SystemDropShadowChrome)o)._brushes = null;
        }
 
        #endregion Dynamic Properties
 
        #region Protected Methods
 
        private const double ShadowDepth = 5;
 
        /// <summary>
        /// Render callback.  
        /// </summary>
        protected override void OnRender(DrawingContext drawingContext)
        {
            CornerRadius cornerRadius = CornerRadius;
 
            Rect shadowBounds = new Rect(new Point(ShadowDepth, ShadowDepth),
                             new Size(RenderSize.Width, RenderSize.Height));
            Color color = Color;
 
            if (shadowBounds.Width > 0 && shadowBounds.Height > 0 && color.A > 0)
            {
                // The shadow is drawn with a dark center the size of the shadow bounds
                // deflated by shadow depth on each side.
                double centerWidth = shadowBounds.Right - shadowBounds.Left - 2 * ShadowDepth;
                double centerHeight = shadowBounds.Bottom - shadowBounds.Top - 2 * ShadowDepth;
 
                // Clamp corner radii to be less than 1/2 the side of the inner shadow bounds 
                double maxRadius = Math.Min(centerWidth * 0.5, centerHeight * 0.5);
                cornerRadius.TopLeft = Math.Min(cornerRadius.TopLeft, maxRadius);
                cornerRadius.TopRight = Math.Min(cornerRadius.TopRight, maxRadius);
                cornerRadius.BottomLeft = Math.Min(cornerRadius.BottomLeft, maxRadius);
                cornerRadius.BottomRight = Math.Min(cornerRadius.BottomRight, maxRadius);
 
                // Get the brushes for the 9 regions
                Brush[] brushes = GetBrushes(color, cornerRadius);
 
                // Snap grid to device pixels
                double centerTop = shadowBounds.Top + ShadowDepth;
                double centerLeft = shadowBounds.Left + ShadowDepth;
                double centerRight = shadowBounds.Right - ShadowDepth;
                double centerBottom = shadowBounds.Bottom - ShadowDepth;
 
                // Because of different corner radii there are 6 potential x (or y) lines to snap to
                double[] guidelineSetX = new double[] { centerLeft,
                                                        centerLeft + cornerRadius.TopLeft,
                                                        centerRight - cornerRadius.TopRight,
                                                        centerLeft + cornerRadius.BottomLeft,
                                                        centerRight - cornerRadius.BottomRight,
                                                        centerRight};
 
                double[] guidelineSetY = new double[] { centerTop,
                                                        centerTop + cornerRadius.TopLeft,
                                                        centerTop + cornerRadius.TopRight,
                                                        centerBottom - cornerRadius.BottomLeft,
                                                        centerBottom - cornerRadius.BottomRight,
                                                        centerBottom};
                
                drawingContext.PushGuidelineSet(new GuidelineSet(guidelineSetX, guidelineSetY));
 
                // The corner rectangles are drawn drawn ShadowDepth pixels bigger to 
                // account for the blur
                cornerRadius.TopLeft = cornerRadius.TopLeft + ShadowDepth;
                cornerRadius.TopRight = cornerRadius.TopRight + ShadowDepth;
                cornerRadius.BottomLeft = cornerRadius.BottomLeft + ShadowDepth;
                cornerRadius.BottomRight = cornerRadius.BottomRight + ShadowDepth;
 
 
                // Draw Top row
                Rect topLeft = new Rect(shadowBounds.Left, shadowBounds.Top, cornerRadius.TopLeft, cornerRadius.TopLeft);
                drawingContext.DrawRectangle(brushes[TopLeft], null, topLeft);
 
                double topWidth = guidelineSetX[2] - guidelineSetX[1];
                if (topWidth > 0)
                {
                    Rect top = new Rect(guidelineSetX[1], shadowBounds.Top, topWidth, ShadowDepth);
                    drawingContext.DrawRectangle(brushes[Top], null, top);
                }
 
                Rect topRight = new Rect(guidelineSetX[2], shadowBounds.Top, cornerRadius.TopRight, cornerRadius.TopRight);
                drawingContext.DrawRectangle(brushes[TopRight], null, topRight);
 
                // Middle row
                double leftHeight = guidelineSetY[3] - guidelineSetY[1];
                if (leftHeight > 0)
                {
                    Rect left = new Rect(shadowBounds.Left, guidelineSetY[1], ShadowDepth, leftHeight);
                    drawingContext.DrawRectangle(brushes[Left], null, left);
                }
 
                double rightHeight = guidelineSetY[4] - guidelineSetY[2];
                if (rightHeight > 0)
                {
                    Rect right = new Rect(guidelineSetX[5], guidelineSetY[2], ShadowDepth, rightHeight);
                    drawingContext.DrawRectangle(brushes[Right], null, right);
                }
 
                // Bottom row
                Rect bottomLeft = new Rect(shadowBounds.Left, guidelineSetY[3], cornerRadius.BottomLeft, cornerRadius.BottomLeft);
                drawingContext.DrawRectangle(brushes[BottomLeft], null, bottomLeft);
 
                double bottomWidth = guidelineSetX[4] - guidelineSetX[3];
                if (bottomWidth > 0)
                {
                    Rect bottom = new Rect(guidelineSetX[3], guidelineSetY[5], bottomWidth, ShadowDepth);
                    drawingContext.DrawRectangle(brushes[Bottom], null, bottom);
                }
 
                Rect bottomRight = new Rect(guidelineSetX[4], guidelineSetY[4], cornerRadius.BottomRight, cornerRadius.BottomRight);
                drawingContext.DrawRectangle(brushes[BottomRight], null, bottomRight);
 
 
                // Fill Center
 
                // Because the heights of the top/bottom rects and widths of the left/right rects are fixed
                // and the corner rects are drawn with the size of the corner, the center 
                // may not be a square.  In this case, create a path to fill the area
 
                // When the target object's corner radius is 0, only need to draw one rect
                if (cornerRadius.TopLeft == ShadowDepth &&
                    cornerRadius.TopLeft == cornerRadius.TopRight &&
                    cornerRadius.TopLeft == cornerRadius.BottomLeft &&
                    cornerRadius.TopLeft == cornerRadius.BottomRight)
                {
                    // All corners of target are 0, render one large rectangle
                    Rect center = new Rect(guidelineSetX[0], guidelineSetY[0], centerWidth, centerHeight);
                    drawingContext.DrawRectangle(brushes[Center], null, center);
                }
                else
                {
                    // If the corner radius is TL=2, TR=1, BL=0, BR=2 the following shows the shape that needs to be created.
                    //             _________________
                    //            |                 |_
                    //         _ _|                   |
                    //        |                       |
                    //        |                    _ _|
                    //        |                   |   
                    //        |___________________| 
                    // The missing corners of the shape are filled with the radial gradients drawn above
 
                    // Define shape counter clockwise
                    PathFigure figure = new PathFigure();
 
                    if (cornerRadius.TopLeft > ShadowDepth)
                    {
                        figure.StartPoint = new Point(guidelineSetX[1], guidelineSetY[0]);
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[1], guidelineSetY[1]), true));
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[0], guidelineSetY[1]), true));
                    }
                    else
                    {
                        figure.StartPoint = new Point(guidelineSetX[0], guidelineSetY[0]);
                    }
 
                    if (cornerRadius.BottomLeft > ShadowDepth)
                    {
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[0], guidelineSetY[3]), true));
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[3], guidelineSetY[3]), true));
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[3], guidelineSetY[5]), true));
                    }
                    else
                    {
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[0], guidelineSetY[5]), true));
                    }
 
                    if (cornerRadius.BottomRight > ShadowDepth)
                    {
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[4], guidelineSetY[5]), true));
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[4], guidelineSetY[4]), true));
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[5], guidelineSetY[4]), true));
                    }
                    else
                    {
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[5], guidelineSetY[5]), true));
                    }
 
 
                    if (cornerRadius.TopRight > ShadowDepth)
                    {
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[5], guidelineSetY[2]), true));
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[2], guidelineSetY[2]), true));
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[2], guidelineSetY[0]), true));
                    }
                    else
                    {
                        figure.Segments.Add(new LineSegment(new Point(guidelineSetX[5], guidelineSetY[0]), true));
                    }
                    
                    figure.IsClosed = true;
                    figure.Freeze();
 
                    PathGeometry geometry = new PathGeometry();
                    geometry.Figures.Add(figure);
                    geometry.Freeze();
 
                    drawingContext.DrawGeometry(brushes[Center], null, geometry);
                }
 
                drawingContext.Pop();
            }
        }
 
        #endregion
 
        #region Private Properties
 
        // Create common gradient stop collection for gradient brushes
        private static GradientStopCollection CreateStops(Color c, double cornerRadius)
        {
            // Scale stops to lie within 0 and 1
            double gradientScale = 1 / (cornerRadius + ShadowDepth);
 
            GradientStopCollection gsc = new GradientStopCollection();
            gsc.Add(new GradientStop(c, (0.5 + cornerRadius) * gradientScale));
 
            // Create gradient stops based on the Win32 dropshadow fall off
            Color stopColor = c;
            stopColor.A = (byte)(.74336 * c.A);
            gsc.Add(new GradientStop(stopColor, (1.5 + cornerRadius) * gradientScale));
 
            stopColor.A = (byte)(.38053 * c.A);
            gsc.Add(new GradientStop(stopColor, (2.5 + cornerRadius)* gradientScale));
 
            stopColor.A = (byte)(.12389 * c.A);
            gsc.Add(new GradientStop(stopColor, (3.5 + cornerRadius) * gradientScale));
 
            stopColor.A = (byte)(.02654 * c.A);
            gsc.Add(new GradientStop(stopColor, (4.5 + cornerRadius) * gradientScale));
 
            stopColor.A = 0;
            gsc.Add(new GradientStop(stopColor, (5 + cornerRadius) * gradientScale));
 
            gsc.Freeze();
 
            return gsc;
        }
 
        // Creates an array of brushes needed to render this 
        private static Brush[] CreateBrushes(Color c, CornerRadius cornerRadius)
        {
            Brush[] brushes = new Brush[9];
 
            // Create center brush
            brushes[Center] = new SolidColorBrush(c);
            brushes[Center].Freeze();
 
 
 
            // Sides
            GradientStopCollection sideStops = CreateStops(c, 0);
            LinearGradientBrush top = new LinearGradientBrush(sideStops, new Point(0, 1), new Point(0, 0));
            top.Freeze();
            brushes[Top] = top;
 
            LinearGradientBrush left = new LinearGradientBrush(sideStops, new Point(1, 0), new Point(0, 0));
            left.Freeze();
            brushes[Left] = left;
 
            LinearGradientBrush right = new LinearGradientBrush(sideStops, new Point(0, 0), new Point(1, 0));
            right.Freeze();
            brushes[Right] = right;
 
            LinearGradientBrush bottom = new LinearGradientBrush(sideStops, new Point(0, 0), new Point(0, 1));
            bottom.Freeze();
            brushes[Bottom] = bottom;
 
            // Corners
 
            // Use side stops if the corner radius is 0
            GradientStopCollection topLeftStops;
            if (cornerRadius.TopLeft == 0)
                topLeftStops = sideStops;
            else
                topLeftStops = CreateStops(c, cornerRadius.TopLeft);
 
            RadialGradientBrush topLeft = new RadialGradientBrush(topLeftStops);
            topLeft.RadiusX = 1;
            topLeft.RadiusY = 1;
            topLeft.Center = new Point(1, 1);
            topLeft.GradientOrigin = new Point(1, 1);
            topLeft.Freeze();
            brushes[TopLeft] = topLeft;
 
            // Reuse previous stops if corner radius is the same as side or top left
            GradientStopCollection topRightStops;
            if (cornerRadius.TopRight == 0)
                topRightStops = sideStops;
            else if (cornerRadius.TopRight == cornerRadius.TopLeft)
                topRightStops = topLeftStops;
            else
                topRightStops = CreateStops(c, cornerRadius.TopRight);
 
            RadialGradientBrush topRight = new RadialGradientBrush(topRightStops);
            topRight.RadiusX = 1;
            topRight.RadiusY = 1;
            topRight.Center = new Point(0, 1);
            topRight.GradientOrigin = new Point(0, 1);
            topRight.Freeze();
            brushes[TopRight] = topRight;
 
            // Reuse previous stops if corner radius is the same as any of the previous radii
            GradientStopCollection bottomLeftStops;
            if (cornerRadius.BottomLeft == 0)
                bottomLeftStops = sideStops;
            else if (cornerRadius.BottomLeft == cornerRadius.TopLeft)
                bottomLeftStops = topLeftStops;
            else if (cornerRadius.BottomLeft == cornerRadius.TopRight)
                bottomLeftStops = topRightStops;
            else
                bottomLeftStops = CreateStops(c, cornerRadius.BottomLeft);
 
            RadialGradientBrush bottomLeft = new RadialGradientBrush(bottomLeftStops);
            bottomLeft.RadiusX = 1;
            bottomLeft.RadiusY = 1;
            bottomLeft.Center = new Point(1, 0);
            bottomLeft.GradientOrigin = new Point(1, 0);
            bottomLeft.Freeze();
            brushes[BottomLeft] = bottomLeft;
 
            // Reuse previous stops if corner radius is the same as any of the previous radii
            GradientStopCollection bottomRightStops;
            if (cornerRadius.BottomRight == 0)
                bottomRightStops = sideStops;
            else if (cornerRadius.BottomRight == cornerRadius.TopLeft)
                bottomRightStops = topLeftStops;
            else if (cornerRadius.BottomRight == cornerRadius.TopRight)
                bottomRightStops = topRightStops;
            else if (cornerRadius.BottomRight == cornerRadius.BottomLeft)
                bottomRightStops = bottomLeftStops;
            else
                bottomRightStops = CreateStops(c, cornerRadius.BottomRight);
 
            RadialGradientBrush bottomRight = new RadialGradientBrush(bottomRightStops);
            bottomRight.RadiusX = 1;
            bottomRight.RadiusY = 1;
            bottomRight.Center = new Point(0, 0);
            bottomRight.GradientOrigin = new Point(0, 0);
            bottomRight.Freeze();
            brushes[BottomRight] = bottomRight;
 
            return brushes;
        }
 
        private Brush[] GetBrushes(Color c, CornerRadius cornerRadius)
        {
            if (_commonBrushes == null)
            {
                lock (_resourceAccess)
                {
                    if (_commonBrushes == null)
                    {
                        // Assume that the first render of DropShadow uses the most common color for the app.
                        // This breaks down if (a) the first Shadow is customized, or 
                        // (b) ButtonChrome becomes more broadly used than just on system controls.
                        _commonBrushes = CreateBrushes(c, cornerRadius);
                        _commonCornerRadius = cornerRadius;
                    }
                }
            }
            
            if (c == ((SolidColorBrush)_commonBrushes[Center]).Color &&
                cornerRadius == _commonCornerRadius)
            {
                _brushes = null;  // clear local brushes - use common
                return _commonBrushes;
            }
            else if (_brushes == null)
            {
                // need to create local brushes
                _brushes = CreateBrushes(c, cornerRadius); 
            }
            return _brushes;
        }
 
 
        private const int TopLeft = 0;
        private const int Top = 1;
        private const int TopRight = 2;
        private const int Left = 3;
        private const int Center = 4;
        private const int Right = 5;
        private const int BottomLeft = 6;
        private const int Bottom = 7;
        private const int BottomRight = 8;
 
        // 9 brushes:
        //  0 TopLeft       1 Top       2 TopRight  
        //  3 Left          4 Center    5 Right
        //  6 BottomLeft    7 Bottom    8 BottomRight
        private static Brush[] _commonBrushes;
        private static CornerRadius _commonCornerRadius;
        private static object _resourceAccess = new object();
 
        // Local brushes if our color is not the common color
        private Brush[] _brushes;
 
        #endregion
    }
}