a74162e34e
- Bottom-bar resolution dropdown (1080p60/1080p30/720p60/480p30) with tooltip on finding upload bandwidth; disabled while live; no in-app speed test - FocusPreservingListBox keeps selection/focus coherent when a selected scene/source is deleted (skip hidden scenes; leave list when empty) - Themes/Controls.xaml: single dark-theme dictionary merged once in App.xaml; custom ComboBox template fixes SelectionBoxItem rendering; GoLiveWindow and ReuseImageDialog consolidated onto shared styles - AppLog file logger + AppDomain/Dispatcher exception hooks; checkpointed MainWindow/VM/dialog constructors (caught MenuItemRole.Separator XAML crash) - Memory map: schema.md conventions, per-directory index.md, updated ai.md/ TASKS.md/README.md (OAuth creds real; token persistence still pending)
73 lines
2.3 KiB
C#
73 lines
2.3 KiB
C#
using System.Collections.Specialized;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Threading;
|
|
|
|
namespace ytLive.Helpers;
|
|
|
|
/// <summary>
|
|
/// A ListBox that keeps selection and keyboard focus coherent when the
|
|
/// selected item is deleted. Focus lands on the item now at the deleted
|
|
/// position (the previous item when the last one was removed) or leaves
|
|
/// the box entirely when it becomes empty. Drag-to-reorder is untouched.
|
|
/// </summary>
|
|
public class FocusPreservingListBox : ListBox
|
|
{
|
|
/// <summary>
|
|
/// Predicate deciding which items the delete-focus fallback may land on
|
|
/// (e.g. skip hidden scenes). Null means every item is eligible.
|
|
/// </summary>
|
|
public Func<object, bool>? IsSelectable { get; set; }
|
|
|
|
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
|
|
{
|
|
base.OnItemsChanged(e);
|
|
|
|
if (e.Action != NotifyCollectionChangedAction.Remove) return;
|
|
var hadFocus = IsKeyboardFocusWithin || IsKeyboardFocused;
|
|
if (!hadFocus) return;
|
|
if (Mouse.Captured == this) return;
|
|
|
|
if (Items.Count == 0)
|
|
{
|
|
SelectedItem = null;
|
|
MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
|
|
return;
|
|
}
|
|
|
|
var targetIndex = FindSelectableIndex(Math.Clamp(e.OldStartingIndex, 0, Items.Count - 1));
|
|
if (targetIndex < 0)
|
|
{
|
|
SelectedItem = null;
|
|
return;
|
|
}
|
|
|
|
SelectedIndex = targetIndex;
|
|
var item = Items[targetIndex];
|
|
|
|
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, () =>
|
|
{
|
|
ScrollIntoView(item);
|
|
if (ItemContainerGenerator.ContainerFromItem(item) is ListBoxItem container)
|
|
container.Focus();
|
|
});
|
|
}
|
|
|
|
private int FindSelectableIndex(int start)
|
|
{
|
|
for (var i = start; i >= 0; i--)
|
|
if (IsItemSelectable(i)) return i;
|
|
for (var i = start + 1; i < Items.Count; i++)
|
|
if (IsItemSelectable(i)) return i;
|
|
return -1;
|
|
}
|
|
|
|
private bool IsItemSelectable(int index)
|
|
{
|
|
if (index < 0 || index >= Items.Count) return false;
|
|
var item = Items[index];
|
|
return item != null && (IsSelectable?.Invoke(item) ?? true);
|
|
}
|
|
}
|