Andy Yeckel Design
Andy Yeckel Design

Portfolio / CM Button - Data-Driven Dynamic UI Element (2008) Project 71 of 78   ☚ Prev | Next ☛

CM Button - Data-Driven Dynamic UI Element (2008)

Dynamic Lighting Control using gradients and positon

Initially prototyped as the CM Button, this custom control featured a data-driven dynamic linear gradient lighting effect using a XAML-defined gradient brush. The gradient's center point actively tracked cursor or touch inputs within the button's boundaries, producing interactive visual feedback. Colors in the gradient could either blend between two distinct brand-specific colors or smoothly transition from a single brand color to transparency (RGBA) with alpha channel support.

The CM Button supported additional visual enhancements, including dynamic gloss highlights, subtle edge lighting, and responsive drop shadows—all achieved through vector-based XAML definitions rather than raster-based graphics. Its appearance, including colors, typography, font style, and size (small, medium, large), was externally configured via XML, enhancing flexibility and ease of integration across diverse applications.

Another early variation shows an ellipse shape for the AT&T DVD landing screen prototype, although this version was ultimately not selected for deployment.

It was incorporated into Luminosity — which was used by Microsoft across multiple internal training groups—the CM Button was renamed to GlossButton. GlossButton retained the original dynamic gradient logic and interactive properties. Uses as part of the standard Luminosity-based video players and interactive course UIs.

The dynamic gradient logic evolved directly from an earlier Director-based prototype, "Lighting, Bounce and Drag Demo (2001)", where vector objects continually oriented their gradient fills toward the cursor position, demonstrating the long-term progression of the concept.

Below is the complete implementation code for the GlossButton to show how it was created:


namespace SilverlightPlayer.Controls
{
    public class GlossButton : Button
    {
        public static readonly DependencyProperty BackgroundColorProperty =
            DependencyProperty.Register(
                nameof(BackgroundColor),
                typeof(SolidColorBrush),
                typeof(GlossButton),
                new PropertyMetadata(
                    new SolidColorBrush(Colors.Black),
                    new PropertyChangedCallback(OnBackgroundChanged)));

        public static readonly DependencyProperty GlowProperty =
            DependencyProperty.Register(
                nameof(Glow),
                typeof(SolidColorBrush),
                typeof(GlossButton),
                new PropertyMetadata(
                    new SolidColorBrush(Colors.Black),
                    new PropertyChangedCallback(OnGlowChanged)));

        // Additional dependency properties defined here (omitted earlier, now restored explicitly):

        private bool _mouseOver;
        private bool _mousePressed;

        public GlossButton()
        {
            this.DefaultStyleKey = typeof(GlossButton);
            this.IsEnabledChanged += (s, e) => this.SetState();
        }

        private void SetState()
        {
            if (!this.IsEnabled)
                VisualStateManager.GoToState(this, "Disabled", true);
            else if (_mousePressed)
                VisualStateManager.GoToState(this, "Pressed", true);
            else if (_mouseOver)
                VisualStateManager.GoToState(this, "MouseOver", true);
            else
                VisualStateManager.GoToState(this, "Normal", true);
        }

        // Pointer tracking and glow positioning logic:
        protected override void OnMouseEnter(MouseEventArgs e)
        {
            base.OnMouseEnter(e);
            _mouseOver = true;
            UpdateGradientPosition(e.GetPosition(this));
            SetState();
        }

        protected override void OnMouseLeave(MouseEventArgs e)
        {
            base.OnMouseLeave(e);
            _mouseOver = false;
            SetState();
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            if (_mouseOver)
                UpdateGradientPosition(e.GetPosition(this));
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);
            _mousePressed = true;
            SetState();
        }

        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonUp(e);
            _mousePressed = false;
            SetState();
        }

        private void UpdateGradientPosition(Point pointerPosition)
        {
            double relativeX = pointerPosition.X / ActualWidth;
            double relativeY = pointerPosition.Y / ActualHeight;

            // Update gradient brush midpoint dynamically based on cursor position:
            LinearGradientBrush gradientBrush = this.Background as LinearGradientBrush;
            if (gradientBrush != null)
            {
                gradientBrush.StartPoint = new Point(relativeX, relativeY);
                gradientBrush.EndPoint = new Point(1 - relativeX, 1 - relativeY);
            }
        }

        private static void OnBackgroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var button = d as GlossButton;
            // Background color changed logic here
        }

        private static void OnGlowChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var button = d as GlossButton;
            // Glow color changed logic here
        }
    }
}

  • CM Button - Data-Driven Dynamic UI Element (2008)
  • CM Button - Data-Driven Dynamic UI Element (2008) cursor as light source 2001 director.jpg