using System.Runtime.InteropServices; using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using System.Windows.Shapes; using WpfMaiTouchEmulator.Managers; namespace WpfMaiTouchEmulator; /// /// Interaction logic for TouchPanel.xaml /// public partial class TouchPanel : Window { internal Action? onTouch; internal Action? onRelease; internal Action? onInitialReposition; private readonly Dictionary activeTouches = []; private readonly TouchPanelPositionManager _positionManager; private List buttons = []; private bool isDebugEnabled = Properties.Settings.Default.IsDebugEnabled; private bool isRingButtonEmulationEnabled = Properties.Settings.Default.IsRingButtonEmulationEnabled; private bool hasRepositioned = false; private enum ResizeDirection { BottomRight = 8, } [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern bool ReleaseCapture(); [DllImport("user32.dll", CharSet = CharSet.Auto)] private static extern int SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); public TouchPanel() { InitializeComponent(); Topmost = true; _positionManager = new TouchPanelPositionManager(); Loaded += Window_Loaded; StateCheckLoop(); } private async void StateCheckLoop() { while (true) { if (activeTouches.Count != 0 && !TouchesOver.Any()) { await Task.Delay(100); if (activeTouches.Count != 0 && !TouchesOver.Any()) { DeselectAllItems(); } } await Task.Delay(100); } } private void Window_Loaded(object sender, RoutedEventArgs e) { buttons = VisualTreeHelperExtensions.FindVisualChildren(this); DeselectAllItems(); } public void PositionTouchPanel() { var position = _positionManager.GetSinMaiWindowPosition(); if (position != null && (Top != position.Value.Top || Left != position.Value.Left || Width != position.Value.Width || Height != position.Value.Height) ) { Logger.Info("Touch panel not over sinmai window, repositioning"); Top = position.Value.Top; Left = position.Value.Left; Width = position.Value.Width; Height = position.Value.Height; if (!hasRepositioned) { hasRepositioned = true; onInitialReposition?.Invoke(); } } } private void DragBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { // This event is for the draggable bar, it calls DragMove to move the window DragMove(); } private void ResizeGrip_MouseDown(object sender, MouseButtonEventArgs e) { if (e.LeftButton == MouseButtonState.Pressed) { ResizeWindow(ResizeDirection.BottomRight); } } private void ResizeWindow(ResizeDirection direction) { ReleaseCapture(); SendMessage(new System.Windows.Interop.WindowInteropHelper(this).Handle, 0x112, // WM_SYSCOMMAND message (IntPtr)(0xF000 + direction), IntPtr.Zero); } private void Element_TouchDown(object sender, TouchEventArgs e) { // Cast the sender to a Border to ensure it's the correct element type. if (sender is Polygon element) { // Highlight the element and add it to the active touches tracking. HighlightElement(element, true); var touchValue = (TouchValue)element.Tag; if (isRingButtonEmulationEnabled && RingButtonEmulator.HasRingButtonMapping((TouchValue)element.Tag)) { RingButtonEmulator.PressButton((TouchValue)element.Tag); } else { onTouch?.Invoke((TouchValue)element.Tag); } activeTouches[e.TouchDevice.Id] = element; } e.Handled = true; } private void Element_TouchMove(object sender, TouchEventArgs e) { // Attempt to find the element under the current touch point. var touchPoint = e.GetTouchPoint(this).Position; var hitTestResult = VisualTreeHelper.HitTest(this, touchPoint); if (hitTestResult != null && hitTestResult.VisualHit is Polygon newElement) { // If this touch point is already tracking another element, unhighlight the previous one. if (activeTouches.TryGetValue(e.TouchDevice.Id, out var previousElement) && previousElement != newElement) { Task.Delay(50) .ContinueWith(t => { HighlightElement(previousElement, false); Application.Current.Dispatcher.Invoke(() => { onRelease?.Invoke((TouchValue)previousElement.Tag); }); }); } // Highlight the new element and update the tracking. HighlightElement(newElement, true); onTouch?.Invoke((TouchValue)newElement.Tag); activeTouches[e.TouchDevice.Id] = newElement; } e.Handled = true; } private void Element_TouchUp(object sender, TouchEventArgs e) { // When touch is lifted, unhighlight the associated element and remove it from tracking. if (activeTouches.TryGetValue(e.TouchDevice.Id, out var element)) { HighlightElement(element, false); RingButtonEmulator.ReleaseButton((TouchValue)element.Tag); onRelease?.Invoke((TouchValue)element.Tag); activeTouches.Remove(e.TouchDevice.Id); } e.Handled = true; } private void DeselectAllItems() { // Logic to deselect all items or the last touched item foreach (var element in activeTouches.Values) { HighlightElement(element, false); onRelease?.Invoke((TouchValue)element.Tag); } activeTouches.Clear(); RingButtonEmulator.ReleaseAllButtons(); } public void SetDebugMode(bool enabled) { isDebugEnabled = enabled; buttons.ForEach(button => { button.Opacity = enabled ? 0.3 : 0; }); } public void SetBorderMode(BorderSetting borderSetting, string borderColour) { if (borderSetting == BorderSetting.Rainbow) { var rotateTransform = new RotateTransform { CenterX = 0.5, CenterY = 0.5 }; touchPanelBorder.BorderBrush = new ImageBrush { ImageSource = new BitmapImage(new Uri(@"pack://application:,,,/Assets/conicalGradient.png")), ViewportUnits = BrushMappingMode.RelativeToBoundingBox, Viewport = new Rect(0, 0, 1, 1), TileMode = TileMode.Tile, RelativeTransform = rotateTransform, }; var animation = new DoubleAnimation { From = 0, To = 360, Duration = new Duration(TimeSpan.FromSeconds(10)), RepeatBehavior = RepeatBehavior.Forever }; rotateTransform.BeginAnimation(RotateTransform.AngleProperty, animation); return; } else if (borderSetting == BorderSetting.Solid) { try { var colour = (Color)ColorConverter.ConvertFromString(borderColour); touchPanelBorder.BorderBrush = new SolidColorBrush { Color = colour }; return; } catch (Exception ex) { Logger.Error("Failed to parse solid colour", ex); } } touchPanelBorder.BorderBrush = null; } public void SetEmulateRingButton(bool enabled) { isRingButtonEmulationEnabled = enabled; } private void HighlightElement(Polygon element, bool highlight) { if (isDebugEnabled) { Application.Current.Dispatcher.Invoke(() => { element.Opacity = highlight ? 0.8 : 0.3; }); } } }