Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Examples/ReactiveExample/LoginView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ public LoginView (LoginViewModel viewModel)
.BindTo (unInput, x => x.Text)
.DisposeWith (_disposable);

unInput
.Events ()
.TextChanged
Observable.FromEventPattern (
h => unInput.TextChanged += h,
h => unInput.TextChanged -= h)
.Select (_ => unInput.Text)
.DistinctUntilChanged ()
.BindTo (ViewModel, x => x.Username)
Expand Down Expand Up @@ -79,9 +79,9 @@ public LoginView (LoginViewModel viewModel)
.BindTo (pwInput, x => x.Text)
.DisposeWith (_disposable);

pwInput
.Events ()
.TextChanged
Observable.FromEventPattern (
h => pwInput.TextChanged += h,
h => pwInput.TextChanged -= h)
.Select (_ => pwInput.Text)
.DistinctUntilChanged ()
.BindTo (ViewModel, x => x.Password)
Expand Down
23 changes: 10 additions & 13 deletions Terminal.Gui/ViewBase/Adornment/ArrangerButton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public ArrangeButtons ButtonType
}

_buttonType = value;
SetTextDirect (GetButtonGlyph ());
ApplyOrientationAndDirection ();
SetupKeyBindings ();
}
Expand All @@ -123,19 +124,15 @@ public ArrangeButtons ButtonType
public NavigationDirection Direction { get; set; }

/// <inheritdoc/>
public override string Text
{
get =>
ButtonType switch
{
ArrangeButtons.Move => $"{Glyphs.Move}",
ArrangeButtons.AllSize => $"{Glyphs.SizeBottomRight}",
ArrangeButtons.LeftSize or ArrangeButtons.RightSize => $"{Glyphs.SizeHorizontal}",
ArrangeButtons.TopSize or ArrangeButtons.BottomSize => $"{Glyphs.SizeVertical}",
_ => base.Text
};
set => base.Text = value;
}
private string GetButtonGlyph () =>
ButtonType switch
{
ArrangeButtons.Move => $"{Glyphs.Move}",
ArrangeButtons.AllSize => $"{Glyphs.SizeBottomRight}",
ArrangeButtons.LeftSize or ArrangeButtons.RightSize => $"{Glyphs.SizeHorizontal}",
ArrangeButtons.TopSize or ArrangeButtons.BottomSize => $"{Glyphs.SizeVertical}",
_ => Text
};

/// <summary>
/// Sets <see cref="Orientation"/> and <see cref="Direction"/> based on <see cref="ButtonType"/>.
Expand Down
7 changes: 6 additions & 1 deletion Terminal.Gui/ViewBase/Adornment/TitleView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,12 @@ protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attri
public NavigationDirection Direction { get; set; }

/// <inheritdoc/>
public override string Text { get => base.Text; set => base.Text = Title = value; }
protected override void OnTextChanged ()
{
Title = Text;

base.OnTextChanged ();
}

/// <summary>
/// Binds the appropriate arrow keys to their directional <see cref="Command"/> based on
Expand Down
127 changes: 111 additions & 16 deletions Terminal.Gui/ViewBase/View.Text.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using System.ComponentModel;

namespace Terminal.Gui.ViewBase;

public partial class View // Text Property APIs
{
private string _text = string.Empty;

/// <summary>
/// Called when the <see cref="Text"/> has changed. Fires the <see cref="TextChanged"/> event.
/// </summary>
public void OnTextChanged () => TextChanged?.Invoke (this, EventArgs.Empty);

/// <summary>
/// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
/// or not when <see cref="Text.TextFormatter.WordWrap"/> is enabled.
Expand Down Expand Up @@ -50,9 +47,19 @@ public bool PreserveTrailingSpaces
/// If <see cref="View.Width"/> or <see cref="View.Height"/> are using <see cref="DimAutoStyle.Text"/>,
/// the <see cref="GetContentSize ()"/> will be adjusted to fit the text.
/// </para>
/// <para>When the text changes, the <see cref="TextChanged"/> is fired.</para>
/// <para>
/// Setting <see cref="Text"/> to the same value as the current value is a no-op; neither
/// <see cref="TextChanging"/> nor <see cref="TextChanged"/> will be raised.
/// </para>
/// <para>
/// Before the text is changed, the <see cref="TextChanging"/> CWP hook is invoked. If cancelled,
/// the text remains unchanged and <see cref="TextChanged"/> is not raised.
/// </para>
/// <para>
/// After the text is changed, the <see cref="TextChanged"/> event is raised.
/// </para>
/// </remarks>
public virtual string Text
public string Text
{
get => _text;
set
Expand All @@ -62,14 +69,107 @@ public virtual string Text
return;
}

_text = value;
if (OnTextChanging (value))
{
return;
}

UpdateTextFormatterText ();
SetNeedsLayout ();
OnTextChanged ();
SetTextDirect (value);

RaiseTextChanged ();
}
}

/// <summary>
/// Sets the <see cref="Text"/> backing field directly without raising <see cref="TextChanging"/>
/// or <see cref="TextChanged"/> events and without invoking the <see cref="OnTextChanging"/>
/// or <see cref="OnTextChanged"/> virtual methods.
/// </summary>
/// <remarks>
/// <para>
/// Use this method in derived views that maintain an internal text model (e.g., an editor
/// buffer) and need to keep <see cref="Text"/> in sync after internal edits without
/// re-entering the CWP flow.
/// </para>
/// </remarks>
/// <param name="value">The new text value to store.</param>
protected void SetTextDirect (string value)
{
_text = value;
UpdateTextFormatterText ();
SetNeedsLayout ();
}

/// <summary>
/// Called before the <see cref="Text"/> changes. Invokes the <see cref="TextChanging"/> event, which can
/// be cancelled.
/// </summary>
/// <remarks>
/// <para>
/// The base implementation raises the <see cref="TextChanging"/> event. Override in derived views
/// to perform validation or fire control-specific pre-change events.
/// </para>
/// </remarks>
/// <param name="newText">The proposed new text value.</param>
/// <returns><see langword="true"/> if the text change should be cancelled; otherwise <see langword="false"/>.</returns>
protected virtual bool OnTextChanging (string newText)
{
CancelEventArgs args = new ();
TextChanging?.Invoke (this, args);

return args.Cancel;
}

/// <summary>
/// Raised when the <see cref="Text"/> is about to change. Set <see cref="CancelEventArgs.Cancel"/> to
/// <see langword="true"/> to prevent the change.
/// </summary>
/// <remarks>
/// <para>
/// This is a signal-only notification at the <see cref="View"/> level. It does not carry old or new
/// text values. Derived controls that need richer text-edit semantics may expose their own specific events.
/// </para>
/// </remarks>
public event EventHandler<CancelEventArgs>? TextChanging;

/// <summary>
/// Called after the <see cref="Text"/> has been changed.
/// </summary>
/// <remarks>
/// <para>
/// Override in derived views to react to text changes. The base implementation is empty.
/// The <see cref="TextChanged"/> event is raised by the caller after this method returns.
/// </para>
/// </remarks>
protected virtual void OnTextChanged () { }

/// <summary>
/// Invokes the CWP post-change workflow for <see cref="Text"/>: calls <see cref="OnTextChanged"/>
/// then raises <see cref="TextChanged"/>.
/// </summary>
/// <remarks>
/// <para>
/// Derived views that bypass the base <see cref="Text"/> setter (e.g., using <c>new</c>) should
/// call this method after mutating text to participate in the CWP workflow.
/// </para>
/// </remarks>
protected internal void RaiseTextChanged ()
{
OnTextChanged ();
TextChanged?.Invoke (this, EventArgs.Empty);
}
Comment thread
tig marked this conversation as resolved.

/// <summary>
/// Raised after the <see cref="Text"/> has been changed.
/// </summary>
/// <remarks>
/// <para>
/// This is a signal-only notification at the <see cref="View"/> level. It does not carry old or new
/// text values. Derived controls that need richer text-edit semantics may expose their own specific events.
/// </para>
/// </remarks>
public event EventHandler? TextChanged;

/// <summary>
/// Gets or sets how the View's <see cref="Text"/> is aligned horizontally when drawn. Changing this property will
/// redisplay the <see cref="View"/>.
Expand All @@ -92,11 +192,6 @@ public Alignment TextAlignment
}
}

/// <summary>
/// Text changed event, raised when the text has changed.
/// </summary>
public event EventHandler? TextChanged;

/// <summary>
/// Gets or sets the direction of the View's <see cref="Text"/>. Changing this property will redisplay the
/// <see cref="View"/>.
Expand Down
9 changes: 7 additions & 2 deletions Terminal.Gui/Views/Button.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,18 @@ private void SetMouseBindings (MouseFlags? mouseHoldRepeat)

private void Button_TitleChanged (object? sender, EventArgs<string> e)
{
base.Text = e.Value;
SetTextDirect (e.Value);
TextFormatter.HotKeySpecifier = HotKeySpecifier;
_interiorTextFormatter.HotKeySpecifier = HotKeySpecifier;
}

/// <inheritdoc/>
public override string Text { get => Title; set => base.Text = Title = value; }
protected override void OnTextChanged ()
{
Title = Text;

base.OnTextChanged ();
}

/// <inheritdoc/>
public override Rune HotKeySpecifier
Expand Down
9 changes: 7 additions & 2 deletions Terminal.Gui/Views/CheckBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,17 @@ protected override void OnActivated (ICommandContext? commandContext)

private void Checkbox_TitleChanged (object? sender, EventArgs<string> e)
{
base.Text = e.Value;
SetTextDirect (e.Value);
TextFormatter.HotKeySpecifier = HotKeySpecifier;
}

/// <inheritdoc/>
public override string Text { get => Title; set => base.Text = Title = value; }
protected override void OnTextChanged ()
{
Title = Text;

base.OnTextChanged ();
}

/// <inheritdoc/>
public override Rune HotKeySpecifier { get => base.HotKeySpecifier; set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value; }
Expand Down
14 changes: 3 additions & 11 deletions Terminal.Gui/Views/Code.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,11 @@ public Code ()
}

/// <summary>Gets or sets the source text to render.</summary>
public override string Text
protected override void OnTextChanged ()
{
get => base.Text;
set
{
if (base.Text == value)
{
return;
}
UpdateStyledLines ();

base.Text = value;
UpdateStyledLines ();
}
base.OnTextChanged ();
}

/// <summary>Gets or sets the language hint used for syntax highlighting.</summary>
Expand Down
27 changes: 20 additions & 7 deletions Terminal.Gui/Views/Color/ColorPicker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,16 +153,26 @@ protected override bool OnDrawingContent (DrawContext? context)
protected virtual void OnValueChanged (ValueChangedEventArgs<Color?> args) { }

/// <inheritdoc/>
public override string Text
protected override bool OnTextChanging (string newText)
{
get => SelectedColor.ToString ();
set
// Reject text that cannot be parsed as a valid color
if (!_colorNameResolver.TryParseColor (newText, out _))
{
if (_colorNameResolver.TryParseColor (value, out Color newColor))
{
SelectedColor = newColor;
}
return true;
}

return base.OnTextChanging (newText);
}

/// <inheritdoc/>
protected override void OnTextChanged ()
{
if (_colorNameResolver.TryParseColor (Text, out Color newColor))
{
SelectedColor = newColor;
Comment thread
tig marked this conversation as resolved.
}

base.OnTextChanged ();
}
Comment thread
tig marked this conversation as resolved.

/// <summary>
Expand Down Expand Up @@ -288,6 +298,9 @@ private void SetSelectedColor (Color value, bool syncBars)
// Do the work
_selectedColor = value;

// Keep Text in sync with the new color
SetTextDirect (_selectedColor.ToString ());

// CWP: Fire ValueChanged
ValueChangedEventArgs<Color?> changedArgs = new (oldValue, value);
OnValueChanged (changedArgs);
Expand Down
27 changes: 20 additions & 7 deletions Terminal.Gui/Views/DatePicker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,26 @@ public CultureInfo? Culture
}

/// <inheritdoc/>
public override string Text
protected override bool OnTextChanging (string newText)
{
get => Value.ToString (Format);
set
// Reject text that cannot be parsed as a valid DateTime
if (!DateTime.TryParse (newText, out _))
{
if (DateTime.TryParse (value, out DateTime result))
{
Value = result;
}
return true;
}

return base.OnTextChanging (newText);
}

/// <inheritdoc/>
protected override void OnTextChanged ()
{
if (DateTime.TryParse (Text, out DateTime result))
{
Value = result;
Comment thread
tig marked this conversation as resolved.
}
Comment thread
tig marked this conversation as resolved.

base.OnTextChanged ();
}

#region IValue<DateTime> Implementation
Expand Down Expand Up @@ -90,6 +100,9 @@ public DateTime Value

_date = value;

// Keep Text in sync with the new date value
SetTextDirect (_date.ToString (Format));

// Propagate value to embedded editor
_dateEditor?.Value = value;

Expand Down
Loading
Loading