-
Notifications
You must be signed in to change notification settings - Fork 774
Fixes #5202 - BREAKING CHANGE- Refactor LinearRange to implement IValue<T> #5204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
777c5e2
Add design plan for LinearRange IValue<T> refactor and clet
claude acb2a93
Refactor LinearRange to implement IValue<T> with three subclasses
Copilot 2a4fb72
Remove redundant with {} expression in LinearRange.SpanFromIndices
Copilot 03fa634
Address PR review: split tests, add visual tests, fix mouse drag, rem…
Copilot b641a89
Fix CI: update HardCoded cache test to new LinearRangeDefaults config…
Copilot c2004de
Merge branch 'develop' into claude/design-linearrange-clet-Zn29d
tig 534cb68
Add IDesignable.EnableForDesign() to each LinearRange subclass with m…
Copilot 18c1c55
Fix mouse drag for LinearRange: anchor-based drag for Closed; set sem…
Copilot bf448fd
Add non-generic LinearSelector / LinearMultiSelector / LinearRange so…
Copilot fea631a
Default DragChar to ContinuousMeterSegment so it matches SetChar
Copilot 970f5b3
Remove redundant ClearViewport in LinearRange draw to fix drag flicker
Copilot 6285cce
Potential fix for pull request finding
tig ce7ae27
Potential fix for pull request finding
tig 316079c
Add SelectedIndex to LinearSelector for unambiguous "no selection" wi…
Copilot 4b63655
Use HashSet to dedupe indices in LinearMultiSelector.Value setter (O(…
Copilot 17f0a48
Merge branch 'develop' into claude/design-linearrange-clet-Zn29d
tig 9e60694
Merge branch 'develop' into claude/design-linearrange-clet-Zn29d
tig File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| namespace Terminal.Gui.Views; | ||
|
|
||
| /// <summary> | ||
| /// Convenience non-generic <see cref="LinearMultiSelector{T}"/> closed over <see cref="string"/>. | ||
| /// Allows designer scenarios (e.g. <c>AllViewsTester</c>) and reflection-based instantiation to | ||
| /// discover and create the view without supplying a type argument. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// To work with non-string option types, use <see cref="LinearMultiSelector{T}"/> directly. | ||
| /// </para> | ||
| /// </remarks> | ||
| public class LinearMultiSelector : LinearMultiSelector<string> | ||
| { | ||
| /// <summary>Initializes a new instance of <see cref="LinearMultiSelector"/>.</summary> | ||
| public LinearMultiSelector () { } | ||
|
|
||
| /// <summary>Initializes a new instance of <see cref="LinearMultiSelector"/>.</summary> | ||
| /// <param name="options">Initial options.</param> | ||
| /// <param name="orientation">Initial orientation.</param> | ||
| public LinearMultiSelector (List<string>? options, Orientation orientation = Orientation.Horizontal) | ||
| : base (options, orientation) | ||
| { } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| namespace Terminal.Gui.Views; | ||
|
|
||
| /// <summary> | ||
| /// A linear range view that allows selection of zero or more options from a typed list. | ||
| /// </summary> | ||
| /// <typeparam name="T">The data type of the options.</typeparam> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// Exposes the current selection through <see cref="Value"/> as | ||
| /// <see cref="IReadOnlyList{T}"/>; the list is empty when no options are selected. | ||
| /// A defensive copy is taken when <see cref="Value"/> is set, so the caller may mutate | ||
| /// the list passed in without affecting subsequent reads. | ||
| /// </para> | ||
| /// <para> | ||
| /// Equality between the current value and a new value uses | ||
| /// <see cref="System.Linq.Enumerable.SequenceEqual{T}(IEnumerable{T},IEnumerable{T})"/>, | ||
| /// so two distinct list instances with the same elements in the same order are considered equal. | ||
| /// </para> | ||
| /// </remarks> | ||
| public class LinearMultiSelector<T> : LinearRangeViewBase<T, IReadOnlyList<T>>, IDesignable | ||
| { | ||
| private static readonly IReadOnlyList<T> _emptyList = new List<T> (0).AsReadOnly (); | ||
|
|
||
| private IReadOnlyList<T> _value = _emptyList; | ||
|
|
||
| /// <summary>Initializes a new instance of <see cref="LinearMultiSelector{T}"/>.</summary> | ||
| public LinearMultiSelector () : base (LinearRangeRenderMode.Multiple) { } | ||
|
|
||
| /// <summary>Initializes a new instance of <see cref="LinearMultiSelector{T}"/>.</summary> | ||
| /// <param name="options">Initial options.</param> | ||
| /// <param name="orientation">Initial orientation.</param> | ||
| public LinearMultiSelector (List<T>? options, Orientation orientation = Orientation.Horizontal) | ||
| : base (options, orientation, LinearRangeRenderMode.Multiple) { } | ||
|
|
||
| /// <inheritdoc/> | ||
| /// <remarks> | ||
| /// The setter accepts <see langword="null"/> as a synonym for an empty list. The getter never | ||
| /// returns <see langword="null"/>. | ||
| /// </remarks> | ||
| public override IReadOnlyList<T>? Value | ||
| { | ||
| get => _value; | ||
| set | ||
| { | ||
| IReadOnlyList<T> incoming = value is null ? _emptyList : new List<T> (value).AsReadOnly (); | ||
| IReadOnlyList<T> current = _value; | ||
|
|
||
| if (SequenceEqualByDefault (current, incoming)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if (RaiseValueChanging (current, incoming)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| _value = incoming; | ||
|
|
||
| // Sync indices: find the option index for each element of incoming. | ||
| // Use a HashSet to dedupe in O(1) per item rather than O(n) List.Contains scans. | ||
| List<int> indices = new (incoming.Count); | ||
| HashSet<int> seen = new (incoming.Count); | ||
|
|
||
| foreach (T item in incoming) | ||
| { | ||
| int idx = IndexOfData (item); | ||
|
|
||
| if (idx >= 0 && seen.Add (idx)) | ||
| { | ||
| indices.Add (idx); | ||
| } | ||
| } | ||
|
|
||
| ApplySelectedIndices (indices); | ||
|
|
||
| RaiseValueChanged (current, _value); | ||
| } | ||
| } | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override void OnSelectionChanged () | ||
| { | ||
| IReadOnlyList<T> previous = _value; | ||
|
|
||
| // Build the new value from current indices in the order they appear in Options | ||
| // (rather than the order they were selected) for stable, predictable output. | ||
| IReadOnlyList<int> indices = SelectedIndices; | ||
| List<T> next = new (indices.Count); | ||
| List<int> ordered = new (indices); | ||
| ordered.Sort (); | ||
|
|
||
| foreach (int i in ordered) | ||
| { | ||
| if (i >= 0 && i < Options.Count) | ||
| { | ||
| next.Add (Options [i].Data!); | ||
| } | ||
| } | ||
|
|
||
| IReadOnlyList<T> newValue = next.AsReadOnly (); | ||
|
|
||
| if (SequenceEqualByDefault (previous, newValue)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| _value = newValue; | ||
| RaiseValueChanged (previous, newValue); | ||
| } | ||
|
|
||
| private static bool SequenceEqualByDefault (IReadOnlyList<T> a, IReadOnlyList<T> b) | ||
| { | ||
| if (ReferenceEquals (a, b)) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| if (a.Count != b.Count) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| EqualityComparer<T> cmp = EqualityComparer<T>.Default; | ||
|
|
||
| for (var i = 0; i < a.Count; i++) | ||
| { | ||
| if (!cmp.Equals (a [i], b [i])) | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Loads demo data suitable for a designer preview: a multi-select | ||
| /// <see cref="LinearMultiSelector{T}"/> of the seven days of the week, with the five weekdays | ||
| /// (Mon–Fri) preselected. Only populated when <typeparamref name="T"/> is <see cref="string"/>; | ||
| /// for any other type, the view is left untouched and <see langword="false"/> is returned. | ||
| /// </summary> | ||
| /// <returns><see langword="true"/> if demo data was loaded.</returns> | ||
| public virtual bool EnableForDesign () | ||
| { | ||
| if (typeof (T) != typeof (string)) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| Title = "Active Days"; | ||
| AssignHotKeys = true; | ||
| ShowLegends = true; | ||
|
|
||
| string [] days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; | ||
|
|
||
| Options = days.Select ( | ||
| d => new LinearRangeOption<T> (d, (Rune)d [0], (T)(object)d)) | ||
| .ToList (); | ||
|
|
||
| Value = days.Take (5).Select (d => (T)(object)d).ToList (); | ||
|
|
||
| return true; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.