Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
frenzibyte committed Nov 21, 2023
1 parent c4dcee0 commit 5765a0e
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 36 deletions.
19 changes: 19 additions & 0 deletions osu.Framework/Graphics/Containers/ScrollContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,25 @@ public void ScrollIntoView(Drawable d, bool animated = true)
ScrollTo(maxPos - DisplayableContent, animated);
}

/// <summary>
/// Scrolls a <see cref="Drawable"/> to the center of the container.
/// </summary>
/// <param name="d">The <see cref="Drawable"/> to scroll to.</param>
/// <param name="animated">Whether to animate the movement.</param>
public void ScrollToCenter(Drawable d, bool animated = true)
{
float childPos0 = Math.Clamp(DisplayableContent / 2 + GetChildPosInContent(d, offset: -d.DrawSize / 2), 0, ScrollableExtent);
float childPos1 = Math.Clamp(DisplayableContent / 2 + GetChildPosInContent(d, offset: d.DrawSize / 2), 0, ScrollableExtent);

float minPos = Math.Min(childPos0, childPos1);
float maxPos = Math.Max(childPos0, childPos1);

if (minPos < Current || (minPos > Current && d.DrawSize[ScrollDim] > DisplayableContent))
ScrollTo(minPos, animated);
else if (maxPos > Current + DisplayableContent)
ScrollTo(maxPos - DisplayableContent, animated);
}

/// <summary>
/// Determines the position of a child in the content.
/// </summary>
Expand Down
127 changes: 91 additions & 36 deletions osu.Framework/Graphics/UserInterface/Dropdown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
Expand Down Expand Up @@ -53,25 +55,10 @@ public IEnumerable<T> Items
if (boundItemSource != null)
throw new InvalidOperationException($"Cannot manually set {nameof(Items)} when an {nameof(ItemSource)} is bound.");

setItems(value);
resetItems(value);
}
}

private void setItems(IEnumerable<T> items)
{
clearItems();
if (items == null)
return;

foreach (var entry in items)
addDropdownItem(entry);

if (Current.Value == null || !itemMap.Keys.Contains(Current.Value, EqualityComparer<T>.Default))
Current.Value = itemMap.Keys.FirstOrDefault();
else
Current.TriggerChange();
}

private readonly IBindableList<T> itemSource = new BindableList<T>();
private IBindableList<T> boundItemSource;

Expand All @@ -89,9 +76,22 @@ public IBindableList<T> ItemSource

if (boundItemSource != null) itemSource.UnbindFrom(boundItemSource);
itemSource.BindTo(boundItemSource = value);
resetItems(value);
}
}

private void resetItems(IEnumerable<T> value)
{
clearItems();
if (value == null)
return;

foreach (var entry in value)
addDropdownItem(entry);

ensureCurrentValid();
}

/// <summary>
/// Add a menu item directly while automatically generating a label.
/// </summary>
Expand Down Expand Up @@ -147,7 +147,6 @@ private bool removeDropdownItem(T value)

Menu.Remove(item);
itemMap.Remove(value);

return true;
}

Expand Down Expand Up @@ -221,15 +220,15 @@ protected Dropdown()
Header.Action = Menu.Toggle;
Header.ChangeSelection += selectionKeyPressed;
Menu.PreselectionConfirmed += preselectionConfirmed;
Current.ValueChanged += val => Scheduler.AddOnce(selectionChanged, val);
Current.ValueChanged += _ => Scheduler.Add(ensureItemSelectedFromCurrent);
Current.DisabledChanged += disabled =>
{
Header.Enabled.Value = !disabled;
if (disabled && Menu.State == MenuState.Open)
Menu.State = MenuState.Closed;
};

ItemSource.CollectionChanged += (_, _) => setItems(itemSource);
ItemSource.CollectionChanged += collectionChanged;
}

private void preselectionConfirmed(int selectedIndex)
Expand Down Expand Up @@ -286,20 +285,64 @@ protected override void LoadComplete()
Header.Label = SelectedItem?.Text.Value ?? default;
}

private void selectionChanged(ValueChangedEvent<T> args)
private void collectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// refresh if SelectedItem and SelectedValue mismatched
// null is not a valid value for Dictionary, so neither here
if (args.NewValue == null && SelectedItem != null)
switch (e.Action)
{
selectedItem = new DropdownMenuItem<T>(default(LocalisableString), default);
case NotifyCollectionChangedAction.Add:
foreach (var item in e.NewItems.AsNonNull().Cast<T>())
addDropdownItem(item);

break;

case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems.AsNonNull().Cast<T>())
removeDropdownItem(item);

break;

case NotifyCollectionChangedAction.Replace:
foreach (var item in e.OldItems.AsNonNull().Cast<T>())
removeDropdownItem(item);

foreach (var item in e.NewItems.AsNonNull().Cast<T>())
addDropdownItem(item);

break;

case NotifyCollectionChangedAction.Reset:
clearItems();
break;
}
else if (SelectedItem == null || !EqualityComparer<T>.Default.Equals(SelectedItem.Value, args.NewValue))

ensureCurrentValid();
}

private void ensureCurrentValid()
{
if (Current.Value != null && itemMap.Keys.Contains(Current.Value, EqualityComparer<T>.Default))
{
if (args.NewValue == null || !itemMap.TryGetValue(args.NewValue, out selectedItem))
{
selectedItem = new DropdownMenuItem<T>(GenerateItemText(args.NewValue), args.NewValue);
}
// current is already valid, update selection to the item existing in the map.
ensureItemSelectedFromCurrent();
}
else
Current.Value = itemMap.Keys.FirstOrDefault();
}

private void ensureItemSelectedFromCurrent()
{
if (Current.Value != null && itemMap.TryGetValue(Current.Value, out var existingItem))
{
// the item representing current exists, mark it as selected
selectedItem = existingItem;
}
else
{
// todo: this is unnecessary complexity.
if (Current.Value == null && selectedItem != null)
selectedItem = new DropdownMenuItem<T>(default(LocalisableString), default);
else
selectedItem = new DropdownMenuItem<T>(GenerateItemText(Current.Value), Current.Value);
}

Menu.SelectItem(selectedItem);
Expand Down Expand Up @@ -363,7 +406,7 @@ public abstract partial class DropdownMenu : Menu, IKeyBindingHandler<PlatformAc
protected DropdownMenu()
: base(Direction.Vertical)
{
StateChanged += clearPreselection;
StateChanged += stateChanged;
}

public override void Add(MenuItem item)
Expand All @@ -374,17 +417,31 @@ public override void Add(MenuItem item)
drawableDropdownMenuItem.PreselectionRequested += PreselectItem;
}

private void clearPreselection(MenuState obj)
private void stateChanged(MenuState state)
{
if (obj == MenuState.Closed)
PreselectItem(null);
switch (state)
{
case MenuState.Open:
if (SelectedItem != null)
// this is very unfortunate, but without it, DisplayableContent would be 0, causing ScrollToCenter to not work properly.
// todo: investigate and get rid of this
ScheduleAfterChildren(() => ScheduleAfterChildren(() => ContentContainer.ScrollToCenter(SelectedItem)));

break;

case MenuState.Closed:
PreselectItem(null);
break;
}
}

protected internal IEnumerable<DrawableDropdownMenuItem> DrawableMenuItems => Children.OfType<DrawableDropdownMenuItem>();
protected internal IEnumerable<DrawableDropdownMenuItem> VisibleMenuItems => DrawableMenuItems.Where(item => !item.IsMaskedAway);

public DrawableDropdownMenuItem SelectedItem => DrawableMenuItems.FirstOrDefault(c => c.IsSelected);

public DrawableDropdownMenuItem PreselectedItem => DrawableMenuItems.FirstOrDefault(c => c.IsPreSelected)
?? DrawableMenuItems.FirstOrDefault(c => c.IsSelected);
?? SelectedItem;

public event Action<int> PreselectionConfirmed;

Expand All @@ -397,8 +454,6 @@ public void SelectItem(DropdownMenuItem<T> item)
Children.OfType<DrawableDropdownMenuItem>().ForEach(c =>
{
c.IsSelected = compareItemEquality(item, c.Item);
if (c.IsSelected)
ContentContainer.ScrollIntoView(c);
});
}

Expand Down

0 comments on commit 5765a0e

Please sign in to comment.