using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using MonoGame.Extended.Collections; using MonoGame.Extended.Input; namespace MonoGame.Extended.NuclexGui.Controls.Desktop { /// How the list lets the user select items public enum ListSelectionMode { /// The user is not allowed to select an item None, /// The user can select only one item Single, /// The user can pick any number of items Multi } /// List showing a sequence of items public class GuiListControl : GuiControl, IFocusable { /// Items contained in the list private readonly ObservableCollection _items; /// /// Row locator through which the list can detect which row the mouse has /// been pressed down on /// private IListRowLocator _listRowLocator; /// Last known Y coordinate of the mouse private float _mouseY; /// Items currently selected in the list private readonly ObservableCollection _selectedItems; /// How the list lets the user select from its items private ListSelectionMode _selectionMode; /// Slider the lists uses to scroll through its items private readonly GuiVerticalSliderControl _slider; public GuiListControl() { _items = new ObservableCollection(); _items.Cleared += ItemsCleared; _items.ItemAdded += ItemAdded; _items.ItemRemoved += ItemRemoved; _selectedItems = new ObservableCollection(); _selectedItems.Cleared += SelectionCleared; _selectedItems.ItemAdded += SelectionAdded; _selectedItems.ItemRemoved += SelectionRemoved; _slider = new GuiVerticalSliderControl { Bounds = new UniRectangle( new UniScalar(1.0f, -20.0f), new UniScalar(0.0f, 0.0f), new UniScalar(0.0f, 20.0f), new UniScalar(1.0f, 0.0f) ) }; Children.Add(_slider); } /// How the user can select items in the list public ListSelectionMode SelectionMode { get { return _selectionMode; } set { _selectionMode = value; } } /// Slider the list uses to scroll through its items public GuiVerticalSliderControl Slider => _slider; /// Items being displayed in the list public IList Items => _items; /// Indices of the items current selected in the list public IList SelectedItems => _selectedItems; /// /// Can be set by renderers to enable selection of list items by mouse /// public IListRowLocator ListRowLocator { get { return _listRowLocator; } set { if (value != _listRowLocator) { _listRowLocator = value; UpdateSlider(); } } } /// Whether the control can currently obtain the input focus bool IFocusable.CanGetFocus => true; /// Triggered when the selected items in list have changed public event EventHandler SelectionChanged; /// Called when a mouse button has been pressed down /// Index of the button that has been pressed /// /// If this method states that a mouse press is processed by returning /// true, that means the control did something with it and the mouse press /// should not be acted upon by any other listener. /// protected override void OnMousePressed(MouseButton button) { if (_listRowLocator != null) { var row = _listRowLocator.GetRow(GetAbsoluteBounds(), _slider.ThumbPosition, _items.Count, _mouseY); if ((row >= 0) && (row < _items.Count)) OnRowClicked(row); } } /// Called when the user has clicked on a row in the list /// Row the user has clicked on /// /// The default behavior of the list control in multi select mode is to /// toggle items that are clicked between selected and unselected. If you /// need different behavior (for example, dragging a selected region or /// selecting sequences of items by holding the shift key), you can override /// this method and handle the selection behavior yourself. /// protected virtual void OnRowClicked(int row) { switch (_selectionMode) { // The user isn't allowed to select items in the list case ListSelectionMode.None: { break; } // Only a single item can be selected at a time case ListSelectionMode.Single: { if (_selectedItems.Count == 1) { if (_selectedItems[0] == row) break; // do not fire the SelectionChanged event _selectedItems[0] = row; } else { _selectedItems.Clear(); _selectedItems.Add(row); } OnSelectionChanged(); break; } // Any number of items can be selected case ListSelectionMode.Multi: { if (!_selectedItems.Remove(row)) _selectedItems.Add(row); OnSelectionChanged(); break; } } } /// Called when the mouse wheel has been rotated /// Number of ticks that the mouse wheel has been rotated protected override void OnMouseWheel(float ticks) { const float itemsPerTick = 1.0f; if (_listRowLocator != null) { var bounds = GetAbsoluteBounds(); float totalitems = _items.Count; var itemsInView = bounds.Height/_listRowLocator.GetRowHeight(bounds); var scrollableItems = totalitems - itemsInView; _slider.ThumbPosition -= itemsPerTick/scrollableItems*ticks; _slider.ThumbPosition = MathHelper.Clamp(_slider.ThumbPosition, 0.0f, 1.0f); } } /// Called when the mouse position is updated /// X coordinate of the mouse cursor on the control /// Y coordinate of the mouse cursor on the control protected override void OnMouseMoved(float x, float y) { _mouseY = y; } /// Called when the selected items in the list have changed protected virtual void OnSelectionChanged() { SelectionChanged?.Invoke(this, EventArgs.Empty); } /// Called when an item is removed from the items list /// List the item has been removed from /// Contains the item that has been removed private void ItemRemoved(object sender, ItemEventArgs arguments) { UpdateSlider(); } /// Called when an item is added to the items list /// List the item has been added to /// Contains the item that has been added private void ItemAdded(object sender, ItemEventArgs arguments) { UpdateSlider(); } /// Called when the items list is about to clear itself /// Items list that is about to clear itself /// Not used private void ItemsCleared(object sender, EventArgs arguments) { UpdateSlider(); } /// Called when an entry is added to the list of selected items /// List to which an item was added to /// Contains the added item private void SelectionAdded(object sender, ItemEventArgs arguments) { OnSelectionChanged(); } /// /// Called when an entry is removed from the list of selected items /// /// List from which an item was removed /// Contains the removed item private void SelectionRemoved(object sender, ItemEventArgs arguments) { OnSelectionChanged(); } /// Called when the selected items list is about to clear itself /// List that is about to clear itself /// Not Used private void SelectionCleared(object sender, EventArgs arguments) { OnSelectionChanged(); } /// Updates the size and position of the list's slider private void UpdateSlider() { if ((Screen != null) && (_listRowLocator != null)) { var bounds = GetAbsoluteBounds(); float totalitems = _items.Count; var itemsInView = bounds.Height/_listRowLocator.GetRowHeight(bounds); _slider.ThumbSize = Math.Min(1.0f, itemsInView/totalitems); } } } }