1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-11-26 06:28:35 +01:00

Compare commits

...

3 commits

2 changed files with 58 additions and 49 deletions

View file

@ -68,6 +68,7 @@ Improvements
- Ensure paragraphs display up-to-date versions of their text callbacks - Ensure paragraphs display up-to-date versions of their text callbacks
- Set cornflower blue as the default link color - Set cornflower blue as the default link color
- Added TextField.OnCopyPasteException to allow handling exceptions thrown by TextCopy - Added TextField.OnCopyPasteException to allow handling exceptions thrown by TextCopy
- Avoid paragraphs splitting or truncating their text unnecessarily
Fixes Fixes
- Fixed parents of elements that prevent spill not being notified properly - Fixed parents of elements that prevent spill not being notified properly

View file

@ -32,7 +32,13 @@ namespace MLEM.Ui.Elements {
/// <summary> /// <summary>
/// The tokenized version of the <see cref="Text"/> /// The tokenized version of the <see cref="Text"/>
/// </summary> /// </summary>
public TokenizedString TokenizedText { get; private set; } public TokenizedString TokenizedText {
get {
this.CheckTextChange();
this.TokenizeIfNecessary();
return this.tokenizedText;
}
}
/// <summary> /// <summary>
/// The color that the text will be rendered with /// The color that the text will be rendered with
/// </summary> /// </summary>
@ -53,14 +59,10 @@ namespace MLEM.Ui.Elements {
/// </summary> /// </summary>
public string Text { public string Text {
get { get {
var ret = this.GetTextCallback?.Invoke(this) ?? this.text; this.CheckTextChange();
this.CheckTextChange(ret); return this.displayedText;
return ret;
}
set {
this.text = value;
this.CheckTextChange(value);
} }
set => this.explicitlySetText = value;
} }
/// <summary> /// <summary>
/// If this paragraph should automatically adjust its width based on the width of the text within it /// If this paragraph should automatically adjust its width based on the width of the text within it
@ -100,10 +102,13 @@ namespace MLEM.Ui.Elements {
/// <inheritdoc /> /// <inheritdoc />
public override bool IsHidden => base.IsHidden || string.IsNullOrWhiteSpace(this.Text); public override bool IsHidden => base.IsHidden || string.IsNullOrWhiteSpace(this.Text);
private string text; private string displayedText;
private string lastText; private string explicitlySetText;
private StyleProp<TextAlignment> alignment; private StyleProp<TextAlignment> alignment;
private StyleProp<GenericFont> regularFont; private StyleProp<GenericFont> regularFont;
private TokenizedString tokenizedText;
private float? lastAlignSplitWidth;
private float? lastAlignSplitScale;
/// <summary> /// <summary>
/// Creates a new paragraph with the given settings. /// Creates a new paragraph with the given settings.
@ -127,16 +132,16 @@ namespace MLEM.Ui.Elements {
/// <inheritdoc /> /// <inheritdoc />
protected override Vector2 CalcActualSize(RectangleF parentArea) { protected override Vector2 CalcActualSize(RectangleF parentArea) {
var size = base.CalcActualSize(parentArea); var size = base.CalcActualSize(parentArea);
this.ParseText(size); this.TokenizeIfNecessary();
var textSize = this.TokenizedText.GetArea(Vector2.Zero, this.TextScale * this.TextScaleMultiplier * this.Scale).Size; this.AlignAndSplitIfNecessary(size);
var textSize = this.tokenizedText.GetArea(Vector2.Zero, this.TextScale * this.TextScaleMultiplier * this.Scale).Size;
return new Vector2(this.AutoAdjustWidth ? textSize.X + this.ScaledPadding.Width : size.X, textSize.Y + this.ScaledPadding.Height); return new Vector2(this.AutoAdjustWidth ? textSize.X + this.ScaledPadding.Width : size.X, textSize.Y + this.ScaledPadding.Height);
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Update(GameTime time) { public override void Update(GameTime time) {
base.Update(time); base.Update(time);
if (this.TokenizedText != null) this.TokenizedText?.Update(time);
this.TokenizedText.Update(time);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -157,47 +162,19 @@ namespace MLEM.Ui.Elements {
this.Alignment = this.Alignment.OrStyle(style.TextAlignment); this.Alignment = this.Alignment.OrStyle(style.TextAlignment);
} }
/// <summary> private void SetTextDirty() {
/// Parses this paragraph's <see cref="Text"/> into <see cref="TokenizedText"/>. this.tokenizedText = null;
/// Additionally, this method adds any <see cref="Link"/> elements for tokenized links in the text.
/// </summary>
/// <param name="size">The paragraph's default size</param>
protected virtual void ParseText(Vector2 size) {
if (this.TokenizedText == null) {
// tokenize the text
this.TokenizedText = this.System.TextFormatter.Tokenize(this.RegularFont, this.Text, this.Alignment);
// add links to the paragraph
this.RemoveChildren(c => c is Link);
foreach (var link in this.TokenizedText.Tokens.Where(t => t.AppliedCodes.Any(c => c is LinkCode)))
this.AddChild(new Link(Anchor.TopLeft, link, this.TextScale * this.TextScaleMultiplier));
}
var width = size.X - this.ScaledPadding.Width;
var scale = this.TextScale * this.TextScaleMultiplier * this.Scale;
if (this.TruncateIfLong) {
this.TokenizedText.Truncate(this.RegularFont, width, scale, this.Ellipsis, this.Alignment);
} else {
this.TokenizedText.Split(this.RegularFont, width, scale, this.Alignment);
}
}
/// <summary>
/// A helper method that causes the <see cref="TokenizedText"/> to be reset.
/// Additionally, <see cref="Element.SetAreaDirty"/> if this paragraph's area has changed enough to warrant it, or if it has any <see cref="Link"/> children.
/// </summary>
protected void SetTextDirty() {
this.TokenizedText = null;
// only set our area dirty if our size changed as a result of this action // only set our area dirty if our size changed as a result of this action
if (!this.AreaDirty && !this.CalcActualSize(this.ParentArea).Equals(this.DisplayArea.Size, Element.Epsilon)) if (!this.AreaDirty && !this.CalcActualSize(this.ParentArea).Equals(this.DisplayArea.Size, Element.Epsilon))
this.SetAreaDirty(); this.SetAreaDirty();
} }
private void CheckTextChange(string newText) { private void CheckTextChange() {
if (this.lastText == newText) var newText = this.GetTextCallback?.Invoke(this) ?? this.explicitlySetText;
if (this.displayedText == newText)
return; return;
var emptyChanged = string.IsNullOrWhiteSpace(this.lastText) != string.IsNullOrWhiteSpace(newText); var emptyChanged = string.IsNullOrWhiteSpace(this.displayedText) != string.IsNullOrWhiteSpace(newText);
this.lastText = newText; this.displayedText = newText;
if (emptyChanged) if (emptyChanged)
this.SetAreaDirty(); this.SetAreaDirty();
this.SetTextDirty(); this.SetTextDirty();
@ -213,6 +190,37 @@ namespace MLEM.Ui.Elements {
return 0; return 0;
} }
private void TokenizeIfNecessary() {
if (this.tokenizedText != null)
return;
// tokenize the text
this.tokenizedText = this.System.TextFormatter.Tokenize(this.RegularFont, this.Text, this.Alignment);
this.lastAlignSplitWidth = null;
this.lastAlignSplitScale = null;
// add links to the paragraph
this.RemoveChildren(c => c is Link);
foreach (var link in this.tokenizedText.Tokens.Where(t => t.AppliedCodes.Any(c => c is LinkCode)))
this.AddChild(new Link(Anchor.TopLeft, link, this.TextScale * this.TextScaleMultiplier));
}
private void AlignAndSplitIfNecessary(Vector2 size) {
var width = size.X - this.ScaledPadding.Width;
var scale = this.TextScale * this.TextScaleMultiplier * this.Scale;
if (this.lastAlignSplitWidth == width && this.lastAlignSplitScale == scale)
return;
this.lastAlignSplitWidth = width;
this.lastAlignSplitScale = scale;
if (this.TruncateIfLong) {
this.tokenizedText.Truncate(this.RegularFont, width, scale, this.Ellipsis, this.Alignment);
} else {
this.tokenizedText.Split(this.RegularFont, width, scale, this.Alignment);
}
}
/// <summary> /// <summary>
/// A delegate method used for <see cref="Paragraph.GetTextCallback"/> /// A delegate method used for <see cref="Paragraph.GetTextCallback"/>
/// </summary> /// </summary>