WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 6c9983a

Browse files
authored
Merge pull request #204 from reactivemarbles/AddNewControlsToAvaloniaUI
Add RichTextBox control with formatting support
2 parents 7a29725 + ed97b1e commit 6c9983a

File tree

7 files changed

+1348
-9
lines changed

7 files changed

+1348
-9
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved.
2+
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for full license information.
4+
5+
using Avalonia.Metadata;
6+
7+
[assembly: XmlnsDefinition("https://github.com/reactivemarbles/CrissCross.Avalonia.UI", "CrissCross.Avalonia.UI")]
8+
[assembly: XmlnsDefinition("https://github.com/reactivemarbles/CrissCross.Avalonia.UI", "CrissCross.Avalonia.UI.Controls")]
9+
[assembly: XmlnsDefinition("https://github.com/reactivemarbles/CrissCross.Avalonia.UI", "CrissCross.Avalonia.UI.Appearance")]
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved.
2+
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for full license information.
4+
5+
using Avalonia;
6+
using Avalonia.Controls;
7+
using Avalonia.Controls.Documents;
8+
using Avalonia.Media;
9+
10+
namespace CrissCross.Avalonia.UI.Controls;
11+
12+
/// <summary>
13+
/// A control that displays formatted text using Inlines.
14+
/// </summary>
15+
public class FormattedTextPresenter : SelectableTextBlock
16+
{
17+
/// <summary>
18+
/// Property for <see cref="Document"/>.
19+
/// </summary>
20+
public static readonly StyledProperty<RichTextDocument?> DocumentProperty =
21+
AvaloniaProperty.Register<FormattedTextPresenter, RichTextDocument?>(nameof(Document));
22+
23+
/// <summary>
24+
/// Property for <see cref="DefaultForeground"/>.
25+
/// </summary>
26+
public static readonly StyledProperty<IBrush?> DefaultForegroundProperty =
27+
AvaloniaProperty.Register<FormattedTextPresenter, IBrush?>(nameof(DefaultForeground));
28+
29+
/// <summary>
30+
/// Property for <see cref="DefaultFontSize"/>.
31+
/// </summary>
32+
public static readonly StyledProperty<double> DefaultFontSizeProperty =
33+
AvaloniaProperty.Register<FormattedTextPresenter, double>(nameof(DefaultFontSize), 14);
34+
35+
/// <summary>
36+
/// Initializes a new instance of the <see cref="FormattedTextPresenter"/> class.
37+
/// </summary>
38+
public FormattedTextPresenter()
39+
{
40+
// Enable text selection
41+
SelectionBrush = new SolidColorBrush(Color.FromArgb(100, 0, 120, 212));
42+
}
43+
44+
/// <summary>
45+
/// Gets or sets the document to display.
46+
/// </summary>
47+
public RichTextDocument? Document
48+
{
49+
get => GetValue(DocumentProperty);
50+
set => SetValue(DocumentProperty, value);
51+
}
52+
53+
/// <summary>
54+
/// Gets or sets the default foreground brush.
55+
/// </summary>
56+
public IBrush? DefaultForeground
57+
{
58+
get => GetValue(DefaultForegroundProperty);
59+
set => SetValue(DefaultForegroundProperty, value);
60+
}
61+
62+
/// <summary>
63+
/// Gets or sets the default font size.
64+
/// </summary>
65+
public double DefaultFontSize
66+
{
67+
get => GetValue(DefaultFontSizeProperty);
68+
set => SetValue(DefaultFontSizeProperty, value);
69+
}
70+
71+
/// <summary>
72+
/// Updates the inline collection from the document.
73+
/// </summary>
74+
public void UpdateInlines()
75+
{
76+
Inlines?.Clear();
77+
78+
if (Document is null || Document.Segments.Count == 0)
79+
{
80+
return;
81+
}
82+
83+
Inlines ??= [];
84+
85+
foreach (var segment in Document.Segments)
86+
{
87+
var run = new Run(segment.Text)
88+
{
89+
FontWeight = segment.FontWeight,
90+
FontStyle = segment.FontStyle,
91+
};
92+
93+
// Apply text decorations
94+
if (segment.TextDecorations is { } decorations)
95+
{
96+
run.TextDecorations = decorations;
97+
}
98+
99+
// Apply custom foreground
100+
if (segment.Foreground is not null)
101+
{
102+
run.Foreground = segment.Foreground;
103+
}
104+
else if (DefaultForeground is not null)
105+
{
106+
run.Foreground = DefaultForeground;
107+
}
108+
109+
// Apply custom font size
110+
if (segment.FontSize.HasValue)
111+
{
112+
run.FontSize = segment.FontSize.Value;
113+
}
114+
115+
// Apply custom font family
116+
if (segment.FontFamily is not null)
117+
{
118+
run.FontFamily = segment.FontFamily;
119+
}
120+
121+
Inlines.Add(run);
122+
}
123+
}
124+
125+
/// <inheritdoc/>
126+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
127+
{
128+
base.OnPropertyChanged(change);
129+
130+
if (change is null)
131+
{
132+
return;
133+
}
134+
135+
if (change.Property == DocumentProperty ||
136+
change.Property == DefaultForegroundProperty ||
137+
change.Property == DefaultFontSizeProperty ||
138+
change.Property == ForegroundProperty ||
139+
change.Property == FontSizeProperty ||
140+
change.Property == FontFamilyProperty)
141+
{
142+
UpdateInlines();
143+
}
144+
}
145+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved.
2+
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for full license information.
4+
5+
using Avalonia.Interactivity;
6+
7+
namespace CrissCross.Avalonia.UI.Controls;
8+
9+
/// <summary>
10+
/// Event arguments for formatting events.
11+
/// </summary>
12+
/// <remarks>
13+
/// Initializes a new instance of the <see cref="FormattingEventArgs"/> class.
14+
/// </remarks>
15+
/// <param name="routedEvent">The routed event.</param>
16+
/// <param name="source">The source.</param>
17+
/// <param name="formatType">The type of formatting applied.</param>
18+
/// <param name="affectedText">The text that was formatted.</param>
19+
public class FormattingEventArgs(RoutedEvent routedEvent, object source, TextFormatType formatType, string affectedText) : RoutedEventArgs(routedEvent, source)
20+
{
21+
/// <summary>
22+
/// Gets the type of formatting that was applied.
23+
/// </summary>
24+
public TextFormatType FormatType { get; } = formatType;
25+
26+
/// <summary>
27+
/// Gets the text that was formatted.
28+
/// </summary>
29+
public string AffectedText { get; } = affectedText;
30+
}

0 commit comments

Comments
 (0)