mirror of
https://github.com/cyanfish/naps2.git
synced 2024-09-11 15:26:55 +03:00
The previous implementation involved lazy item setting. This was causing layout issues in Gtk where empty dropdowns have a different size.
143 lines
3.8 KiB
C#
143 lines
3.8 KiB
C#
using Eto.Forms;
|
|
using NAPS2.EtoForms.Layout;
|
|
|
|
namespace NAPS2.EtoForms.Widgets;
|
|
|
|
public class DropDownWidget<T> where T : notnull
|
|
{
|
|
private readonly DropDown _dropDown = new();
|
|
private T[] _items = [];
|
|
private Func<T, string> _format = x => x?.ToString() ?? "";
|
|
|
|
private bool _hasUserPreferredItem;
|
|
private T? _userPreferredItem;
|
|
private bool _changingItems;
|
|
|
|
public DropDownWidget()
|
|
{
|
|
EtoPlatform.Current.ConfigureDropDown(_dropDown);
|
|
_dropDown.SelectedIndexChanged += DropDown_SelectedIndexChanged;
|
|
if (typeof(IComparable<T>).IsAssignableFrom(typeof(T)))
|
|
{
|
|
GetClosestItem = GetClosestItemByComparing;
|
|
}
|
|
}
|
|
|
|
public Func<T, string> Format
|
|
{
|
|
get => _format;
|
|
set
|
|
{
|
|
_format = value;
|
|
// Force the dropdown items to be regenerated
|
|
Items = Items;
|
|
}
|
|
}
|
|
|
|
public Func<T[], T, T>? GetClosestItem { get; set; }
|
|
|
|
public bool Enabled
|
|
{
|
|
get => _dropDown.Enabled;
|
|
set => _dropDown.Enabled = value;
|
|
}
|
|
|
|
public event EventHandler? SelectedItemChanged;
|
|
|
|
public IEnumerable<T> Items
|
|
{
|
|
get => _items;
|
|
set
|
|
{
|
|
_items = value.ToArray();
|
|
_changingItems = true;
|
|
var previousSelection = InternalSelectedItem;
|
|
_dropDown.Items.Clear();
|
|
foreach (var item in _items)
|
|
{
|
|
_dropDown.Items.Add(new ListItem
|
|
{
|
|
Text = Format(item),
|
|
Tag = item
|
|
});
|
|
}
|
|
InternalSelectedItem = GetPreferredSelection(previousSelection);
|
|
_changingItems = false;
|
|
if (!Equals(InternalSelectedItem, previousSelection))
|
|
{
|
|
SelectedItemChanged?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
private T? GetPreferredSelection(T? previousSelection)
|
|
{
|
|
if (_hasUserPreferredItem && _items.Contains(_userPreferredItem))
|
|
{
|
|
return _userPreferredItem;
|
|
}
|
|
if (_items.Contains(previousSelection))
|
|
{
|
|
return previousSelection;
|
|
}
|
|
var compareTo = _hasUserPreferredItem ? _userPreferredItem : previousSelection;
|
|
if (_items.Length > 0 && compareTo != null && GetClosestItem != null)
|
|
{
|
|
return GetClosestItem(_items, compareTo);
|
|
}
|
|
if (_items.Length > 0)
|
|
{
|
|
return _items[0];
|
|
}
|
|
return default;
|
|
}
|
|
|
|
public T? SelectedItem
|
|
{
|
|
get => InternalSelectedItem;
|
|
set
|
|
{
|
|
_hasUserPreferredItem = true;
|
|
_userPreferredItem = value;
|
|
_changingItems = true;
|
|
InternalSelectedItem = value;
|
|
_changingItems = false;
|
|
}
|
|
}
|
|
|
|
private T? InternalSelectedItem
|
|
{
|
|
get
|
|
{
|
|
if (_dropDown.SelectedIndex == -1) return default;
|
|
return (T) ((ListItem) _dropDown.Items[_dropDown.SelectedIndex]).Tag;
|
|
}
|
|
set => _dropDown.SelectedIndex = Array.IndexOf(_items, value);
|
|
}
|
|
|
|
private void DropDown_SelectedIndexChanged(object? sender, EventArgs eventArgs)
|
|
{
|
|
if (!_changingItems)
|
|
{
|
|
_hasUserPreferredItem = true;
|
|
_userPreferredItem = InternalSelectedItem;
|
|
SelectedItemChanged?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
|
|
private T GetClosestItemByComparing(T[] items, T x)
|
|
{
|
|
foreach (var y in items)
|
|
{
|
|
if (((IComparable<T>) x).CompareTo(y) <= 0)
|
|
{
|
|
return y;
|
|
}
|
|
}
|
|
return items[^1];
|
|
}
|
|
|
|
public static implicit operator LayoutElement(DropDownWidget<T> control) => control.AsControl();
|
|
|
|
public DropDown AsControl() => _dropDown;
|
|
} |