33// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
44// All Rights Reserved.
55
6+ using Wpf . Ui . Markup ;
7+ using UiTextBlock = Wpf . Ui . Controls . TextBlock ;
8+ using WinTextBlock = System . Windows . Controls . TextBlock ;
9+
610// ReSharper disable once CheckNamespace
711namespace Wpf . Ui . Controls ;
812
913/// <summary>
10- /// Extended <see cref="System.Windows.Controls.TextBlock"/> with additional parameters like <see cref="FontTypography"/>.
14+ /// Extended <see cref="System.Windows.Controls.TextBlock"/> that integrates with the library's
15+ /// typography and appearance resources.
1116/// </summary>
12- public class TextBlock : System . Windows . Controls . TextBlock
17+ /// <remarks>
18+ /// This control supports two mechanisms for applying design-system typography:
19+ /// <para>
20+ /// - The <see cref="FontTypography"/> enum which is mapped to a resource key and resolved
21+ /// at runtime to a <see cref="FontTypographyPreset"/>.
22+ /// </para>
23+ /// <para>
24+ /// - An internal resource-backed preset is resolved via a private puppet dependency property
25+ /// so the framework's resource system (DynamicResource) performs resolution and updates.
26+ /// </para>
27+ /// <para>
28+ /// The resolved preset is used to coerce <see cref="WinTextBlock.FontSizeProperty"/>
29+ /// and <see cref="WinTextBlock.FontWeight"/>.
30+ /// </para>
31+ /// <para>
32+ /// The <see cref="Appearance"/> property maps to brush resources and is applied via a resource reference.
33+ /// </para>
34+ /// </remarks>
35+ /// <example>
36+ /// <code lang="xml">
37+ /// <ui:TextBlock FontTypography="Body" Appearance="Primary" Text="Hello, world!" />
38+ /// </code>
39+ /// </example>
40+ public class TextBlock : WinTextBlock
1341{
42+ static TextBlock ( )
43+ {
44+ // Coerce FontSize based on FontTypography when set.
45+ // Using AddOwner allows the opportunity to OverrideMetadata to be reserved for later.
46+ FontSizeProperty . AddOwner (
47+ typeof ( UiTextBlock ) ,
48+ new FrameworkPropertyMetadata (
49+ 14d , // Fluent theme default font size - FontTypography Body font size
50+ FrameworkPropertyMetadataOptions . AffectsMeasure
51+ | FrameworkPropertyMetadataOptions . AffectsRender
52+ | FrameworkPropertyMetadataOptions . Inherits ,
53+ null ,
54+ static ( d , baseValue ) => ( ( UiTextBlock ) d ) . CoerceFontSize ( baseValue )
55+ )
56+ ) ;
57+
58+ // Coerce FontWeight based on FontTypography when set.
59+ FontWeightProperty . AddOwner (
60+ typeof ( UiTextBlock ) ,
61+ new FrameworkPropertyMetadata (
62+ FontWeights . Regular , // FontTypography Body font weight
63+ FrameworkPropertyMetadataOptions . AffectsMeasure
64+ | FrameworkPropertyMetadataOptions . AffectsRender
65+ | FrameworkPropertyMetadataOptions . Inherits ,
66+ null ,
67+ static ( d , baseValue ) => ( ( UiTextBlock ) d ) . CoerceFontWeight ( baseValue )
68+ )
69+ ) ;
70+
71+ // Coerce Foreground based on Appearance when set.
72+ ForegroundProperty . AddOwner (
73+ typeof ( UiTextBlock ) ,
74+ new FrameworkPropertyMetadata (
75+ UnsetMarkerBrush . Instance , // marker for an "unset" Foreground
76+ FrameworkPropertyMetadataOptions . AffectsRender
77+ | FrameworkPropertyMetadataOptions . SubPropertiesDoNotAffectRender
78+ | FrameworkPropertyMetadataOptions . Inherits ,
79+ null ,
80+ static ( d , baseValue ) => ( ( UiTextBlock ) d ) . CoerceForeground ( baseValue )
81+ )
82+ ) ;
83+ }
84+
1485 /// <summary>Identifies the <see cref="FontTypography"/> dependency property.</summary>
1586 public static readonly DependencyProperty FontTypographyProperty = DependencyProperty . Register (
1687 nameof ( FontTypography ) ,
17- typeof ( FontTypography ) ,
18- typeof ( TextBlock ) ,
88+ typeof ( FontTypography ? ) ,
89+ typeof ( UiTextBlock ) ,
1990 new PropertyMetadata (
20- FontTypography . Body ,
91+ null ,
2192 static ( o , args ) =>
2293 {
23- ( ( TextBlock ) o ) . OnFontTypographyChanged ( ( FontTypography ) args . NewValue ) ;
94+ ( ( UiTextBlock ) o ) . OnFontTypographyChanged ( ( FontTypography ? ) args . NewValue ) ;
2495 }
2596 )
2697 ) ;
2798
2899 /// <summary>Identifies the <see cref="Appearance"/> dependency property.</summary>
29100 public static readonly DependencyProperty AppearanceProperty = DependencyProperty . Register (
30101 nameof ( Appearance ) ,
31- typeof ( TextColor ) ,
32- typeof ( TextBlock ) ,
102+ typeof ( TextColor ? ) ,
103+ typeof ( UiTextBlock ) ,
33104 new PropertyMetadata (
34- TextColor . Primary ,
105+ null ,
35106 static ( o , args ) =>
36107 {
37- ( ( TextBlock ) o ) . OnAppearanceChanged ( ( TextColor ) args . NewValue ) ;
108+ ( ( UiTextBlock ) o ) . OnAppearanceChanged ( ( TextColor ? ) args . NewValue ) ;
109+ }
110+ )
111+ ) ;
112+
113+ /// <summary>
114+ /// Private Puppet dependency property used to hold a ResourceReference to a FontTypographyPreset.
115+ /// </summary>
116+ private static readonly DependencyProperty FontTypographyPresetResourceRefProperty = DependencyProperty . Register (
117+ "FontTypographyPresetResourceRef" ,
118+ typeof ( FontTypographyPreset ) ,
119+ typeof ( UiTextBlock ) ,
120+ new PropertyMetadata (
121+ null ,
122+ static ( d , e ) =>
123+ {
124+ d . CoerceValue ( FontSizeProperty ) ;
125+ d . CoerceValue ( FontWeightProperty ) ;
126+ }
127+ )
128+ ) ;
129+
130+ /// <summary>
131+ /// Private Puppet dependency property used to hold the Foreground resource resolved from the Appearance property.
132+ /// </summary>
133+ private static readonly DependencyProperty ForegroundResourceRefProperty = DependencyProperty . Register (
134+ "ForegroundResourceRef" ,
135+ typeof ( Brush ) ,
136+ typeof ( UiTextBlock ) ,
137+ new PropertyMetadata (
138+ null ,
139+ static ( d , e ) =>
140+ {
141+ d . CoerceValue ( ForegroundProperty ) ;
142+ }
143+ )
144+ ) ;
145+
146+ /// <summary>
147+ /// Private Puppet dependency property used to hold the default foreground resource.
148+ /// This property can hold a DynamicResource reference so controls without an explicit Foreground
149+ /// will use the library's themed foreground brush.
150+ /// </summary>
151+ private static readonly DependencyProperty DefaultForegroundResourceRefProperty = DependencyProperty . Register (
152+ "DefaultForegroundResourceRef" ,
153+ typeof ( Brush ) ,
154+ typeof ( UiTextBlock ) ,
155+ new PropertyMetadata (
156+ null ,
157+ static ( d , e ) =>
158+ {
159+ d . CoerceValue ( ForegroundProperty ) ;
38160 }
39161 )
40162 ) ;
41163
42164 public TextBlock ( )
43165 {
44- var defaultFontTypography = ( FontTypography ) FontTypographyProperty . DefaultMetadata . DefaultValue ;
45- SetResourceReference ( StyleProperty , defaultFontTypography . ToResourceValue ( ) ) ;
166+ SetResourceReference ( DefaultForegroundResourceRefProperty , TextColor . Primary . ToResourceKey ( ) ) ;
46167 }
47168
48169 /// <summary>
49170 /// Gets or sets the <see cref="FontTypography"/> of the text.
50171 /// </summary>
51- public FontTypography FontTypography
172+ public FontTypography ? FontTypography
52173 {
53- get => ( FontTypography ) GetValue ( FontTypographyProperty ) ;
174+ get => ( FontTypography ? ) GetValue ( FontTypographyProperty ) ;
54175 set => SetValue ( FontTypographyProperty , value ) ;
55176 }
56177
57178 /// <summary>
58179 /// Gets or sets the color of the text.
59180 /// </summary>
60- public TextColor Appearance
181+ public TextColor ? Appearance
61182 {
62- get => ( TextColor ) GetValue ( AppearanceProperty ) ;
183+ get => ( TextColor ? ) GetValue ( AppearanceProperty ) ;
63184 set => SetValue ( AppearanceProperty , value ) ;
64185 }
65186
66- private void OnFontTypographyChanged ( FontTypography newTypography )
187+ private void OnFontTypographyChanged ( FontTypography ? newTypography )
67188 {
68- SetResourceReference ( StyleProperty , newTypography . ToResourceValue ( ) ) ;
189+ if ( newTypography . HasValue )
190+ {
191+ var resourceKey = newTypography . Value . ToResourceKey ( ) ;
192+
193+ // Use WPF resource reference mechanism to resolve and cache the preset.
194+ // This avoids manual TryFindResource tree traversal.
195+ SetResourceReference ( FontTypographyPresetResourceRefProperty , resourceKey ) ;
196+ }
197+ else
198+ {
199+ // Clear any puppet resource reference
200+ ClearValue ( FontTypographyPresetResourceRefProperty ) ;
201+ }
202+
203+ // Re-evaluate coerced values so when FontTypography is set, the
204+ // CoerceValueCallbacks installed on FontSize/FontWeight will take effect
205+ // and prevent local changes from overriding typography-defined values.
206+ CoerceValue ( FontSizeProperty ) ;
207+ CoerceValue ( FontWeightProperty ) ;
69208 }
70209
71- private void OnAppearanceChanged ( TextColor textColor )
210+ private void OnAppearanceChanged ( TextColor ? textColor )
211+ {
212+ if ( textColor . HasValue )
213+ {
214+ var resourceKey = textColor . Value . ToResourceKey ( ) ;
215+
216+ // Similar to OnFontTypographyChanged, attach themed color resource reference to a proxy property,
217+ // allowing the WPF resource system to handle updates automatically.
218+ SetResourceReference ( ForegroundResourceRefProperty , resourceKey ) ;
219+ }
220+ else
221+ {
222+ ClearValue ( ForegroundResourceRefProperty ) ;
223+ }
224+
225+ CoerceValue ( ForegroundProperty ) ;
226+ }
227+
228+ /*
229+ * The following CoerceValueCallback methods handle value precedence.
230+ * When no explicit value is set (via style, local value, or inheritance),
231+ * they apply WPFUI Fluent theme defaults.
232+ */
233+
234+ private object CoerceFontSize ( object baseValue )
72235 {
73- SetResourceReference ( ForegroundProperty , textColor . ToResourceValue ( ) ) ;
236+ var preset = GetValue ( FontTypographyPresetResourceRefProperty ) as FontTypographyPreset ;
237+ if ( preset ? . FontSize is double size )
238+ {
239+ return size ;
240+ }
241+
242+ return baseValue ;
243+ }
244+
245+ private object CoerceFontWeight ( object baseValue )
246+ {
247+ var preset = GetValue ( FontTypographyPresetResourceRefProperty ) as FontTypographyPreset ;
248+ if ( preset ? . FontWeight is FontWeight weight )
249+ {
250+ return weight ;
251+ }
252+
253+ return baseValue ;
254+ }
255+
256+ private object CoerceForeground ( object baseValue )
257+ {
258+ if ( GetValue ( ForegroundResourceRefProperty ) is Brush appearance )
259+ {
260+ return appearance ;
261+ }
262+
263+ if ( ReferenceEquals ( baseValue , UnsetMarkerBrush . Instance ) )
264+ {
265+ if ( GetValue ( DefaultForegroundResourceRefProperty ) is Brush defaultForeground )
266+ {
267+ return defaultForeground ;
268+ }
269+ }
270+
271+ return baseValue ;
74272 }
75- }
273+ }
0 commit comments