diff --git a/StabilityMatrix.Avalonia/Controls/FADownloadableComboBox.cs b/StabilityMatrix.Avalonia/Controls/FADownloadableComboBox.cs index c9f2cb6a..a4b987d0 100644 --- a/StabilityMatrix.Avalonia/Controls/FADownloadableComboBox.cs +++ b/StabilityMatrix.Avalonia/Controls/FADownloadableComboBox.cs @@ -2,12 +2,17 @@ using System.Threading.Tasks; using AsyncAwaitBestPractices; using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.VisualTree; using CommunityToolkit.Mvvm.Input; using FluentAvalonia.UI.Controls; using Microsoft.Extensions.DependencyInjection; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Dialogs; +using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models; namespace StabilityMatrix.Avalonia.Controls; @@ -17,6 +22,66 @@ public partial class FADownloadableComboBox : FAComboBox { protected override Type StyleKeyOverride => typeof(FAComboBox); + private Popup? dropDownPopup; + private IDisposable? openSubscription; + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + CleanupSubscription(); + DropDownOpened -= OnDropDownOpenedHandler; + DropDownClosed -= OnDropDownClosedHandler; + + // Template part name is "Popup" per FAComboBox.properties.cs (s_tpPopup = "Popup") + dropDownPopup = e.NameScope.Find("Popup"); + + DropDownOpened += OnDropDownOpenedHandler; + DropDownClosed += OnDropDownClosedHandler; + } + + private void OnDropDownOpenedHandler(object? sender, EventArgs e) + { + CleanupSubscription(); + + if (dropDownPopup?.Child is not Control popupChild) + return; + + var scrollViewer = popupChild.GetVisualDescendants().OfType().FirstOrDefault(); + + if (scrollViewer == null) + return; + + // On Unix-like systems, overlay popups share the same TopLevel visual root as the main window. + // FAComboBox.OnPopupOpened adds a TopLevel tunnel handler that marks all wheel eventsas handled while the dropdown is open, + // which inadvertently blocks scroll-wheelevents in popup menus in Inference model cards. + // Resetting e.Handled on the ScrollViewer's tunnel phase counters this. + if (!Compat.IsUnix) + return; + + openSubscription = scrollViewer.AddDisposableHandler( + PointerWheelChangedEvent, + static (_, ev) => + { + if (ev.Handled) + ev.Handled = false; + }, + RoutingStrategies.Tunnel, + handledEventsToo: true + ); + } + + private void OnDropDownClosedHandler(object? sender, EventArgs e) + { + CleanupSubscription(); + } + + private void CleanupSubscription() + { + openSubscription?.Dispose(); + openSubscription = null; + } + static FADownloadableComboBox() { SelectionChangedEvent.AddClassHandler(