Skip to content

TextComponent shadows are not affected by OpacityEffect #4013

Description

@spydon

Description

When an OpacityEffect runs on a TextComponent whose TextPaint style defines shadows, the glyph color fades but the shadows keep their original alpha. The shadows stay at full strength for the whole animation, so a fading text leaves a non-fading shadow or glow behind, which reads as a visual glitch.

There is also a related gotcha in the same code path: the first time the effect updates the paint, the style's fill color is replaced entirely by the component's HasPaint paint (white by default), so a colored text visibly snaps to white when the fade begins unless the user remembers to seed paint.color with the same color as the style.

Cause

TextComponent.onChanged applies opacity through TextRenderer.copyWithPaint, and TextPaint.copyWithPaint only swaps the style's foreground:

@override
TextRenderer copyWithPaint(Paint paint) {
  return copyWith(
    (style) {
      return style.copyWith(
        foreground: paint,
      );
    },
  );
}

style.shadows is left untouched, so shadow alpha never follows the component's opacity.

Steps to reproduce

final text = TextComponent(
  text: '+1 kr',
  textRenderer: TextPaint(
    style: const TextStyle(
      color: Color(0xFF2E9940),
      shadows: [Shadow(color: Color(0x99FFFFFF), blurRadius: 4)],
    ),
  ),
)..add(OpacityEffect.fadeOut(EffectController(duration: 1)));

Expected behavior

The text and its shadows fade out together, in the text's own color.

Actual behavior

The text snaps to white when the effect starts (foreground paint replaces the style color) and fades, while the white shadow stays at constant alpha until the component is removed.

Suggested fix

In TextPaint.copyWithPaint, scale each shadow's color alpha by the incoming paint's alpha (and consider multiplying the paint color into the existing style color instead of replacing it, so OpacityEffect composes with a styled color the way it does for sprites).

Observed on the perf/component-set-backing branch, but the copyWithPaint implementation is the same on main.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions