using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media; namespace HC_APTBS.Views.UserControls { /// /// Pump command sliders. Code-behind only carries the click-anywhere-and-drag /// gesture for the custom FluentThickVerticalSlider template — clicking /// outside the thumb captures the mouse on the Slider and tracks the value /// against the Track's geometry until release. /// public partial class PumpCommandsCard : UserControl { public PumpCommandsCard() => InitializeComponent(); /// /// Wires the click-anywhere-and-drag handlers using /// handledEventsToo: true so they fire even if a class-level /// handler (e.g. Slider.OnPreviewMouseLeftButtonDown when /// IsMoveToPointEnabled is on) marks the event handled. /// private void Slider_Loaded(object sender, RoutedEventArgs e) { if (sender is not Slider slider) return; slider.AddHandler(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(Slider_PreviewMouseLeftButtonDown), true); slider.AddHandler(PreviewMouseLeftButtonUpEvent, new MouseButtonEventHandler(Slider_PreviewMouseLeftButtonUp), true); slider.AddHandler(MouseMoveEvent, new MouseEventHandler(Slider_MouseMove), true); slider.AddHandler(PreviewMouseWheelEvent, new MouseWheelEventHandler(Slider_PreviewMouseWheel), true); } /// /// Adjusts the Slider's value by one step per wheel notch while the cursor /// is over the slider. The step is taken from /// when available, falling back to , then /// 1% of the slider's range. The event is marked handled so the wheel doesn't /// also scroll a parent ScrollViewer. /// private void Slider_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { if (sender is not Slider slider) return; double step = slider.TickFrequency; if (step <= 0) step = slider.SmallChange; if (step <= 0) step = (slider.Maximum - slider.Minimum) * 0.01; if (step <= 0) return; double notches = e.Delta / 120.0; double newValue = slider.Value + notches * step; if (newValue < slider.Minimum) newValue = slider.Minimum; else if (newValue > slider.Maximum) newValue = slider.Maximum; slider.Value = newValue; e.Handled = true; } /// /// On press outside the Thumb, captures the mouse on the Slider so the /// user can drag from any point on the track in one motion. /// private void Slider_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (sender is not Slider slider) return; if (slider.Template?.FindName("PART_Track", slider) is not Track track) return; // Direct thumb press — let the Thumb's own drag handle it. if (IsClickInsideThumb(e.OriginalSource as DependencyObject, track.Thumb)) return; UpdateValueFromPoint(slider, e.GetPosition(slider)); slider.CaptureMouse(); e.Handled = true; } /// /// While the Slider has mouse capture, continuously map the cursor /// position back to a Slider value. /// private void Slider_MouseMove(object sender, MouseEventArgs e) { if (sender is not Slider slider || !slider.IsMouseCaptured) return; UpdateValueFromPoint(slider, e.GetPosition(slider)); } /// /// Releases the Slider's mouse capture on button-up. /// private void Slider_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { if (sender is Slider slider && slider.IsMouseCaptured) { slider.ReleaseMouseCapture(); e.Handled = true; } } /// /// Maps a cursor position (in Slider coordinates) to a Slider value using /// the Slider's own bounds. Bypasses Track.ValueFromPoint, which /// can return scaled values for custom templates whose RepeatButton sizes /// don't match the Track's expected layout. /// private static void UpdateValueFromPoint(Slider slider, Point pointInSlider) { bool vertical = slider.Orientation == Orientation.Vertical; double length = vertical ? slider.ActualHeight : slider.ActualWidth; if (length <= 0) return; double pos = vertical ? pointInSlider.Y : pointInSlider.X; double fraction = pos / length; if (fraction < 0) fraction = 0; else if (fraction > 1) fraction = 1; // Vertical (default): top = Maximum. Horizontal (default): left = Minimum. // IsDirectionReversed flips that on each axis. bool flip = vertical ? !slider.IsDirectionReversed : slider.IsDirectionReversed; if (flip) fraction = 1.0 - fraction; double value = slider.Minimum + fraction * (slider.Maximum - slider.Minimum); if (value < slider.Minimum) value = slider.Minimum; else if (value > slider.Maximum) value = slider.Maximum; slider.Value = value; } private static bool IsClickInsideThumb(DependencyObject? src, Thumb? thumb) { if (thumb is null || src is null) return false; for (var node = src; node is not null; node = VisualTreeHelper.GetParent(node)) { if (node == thumb) return true; } return false; } } }