using Hi.Disp; using Hi.Native; using Hi.PanelModels; using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Collections.Generic; using Hi.Geom; namespace Hi.Wpf.Disp { #region WPF Rendering Canvas /// /// Provides a WPF rendering canvas for 3D visualization of HiAPI components. /// Handles user interactions, rendering, and integration with the DispEngine system. /// /// /// This canvas provides the core rendering capabilities for WPF applications using HiAPI. /// It manages mouse, keyboard, and touch events, and transforms them into appropriate /// actions in the 3D environment. /// public class RenderingCanvas : UserControl, IDisposable { #region Core_Properties /// /// The DispEngine instance that handles rendering and user interactions /// public DispEngine DispEngine { get; } = new DispEngine(); /// /// Internal container for rendering content /// private UserControl DisplayerPane { get; } /// /// Dictionary to store touch point information /// private Dictionary TouchingPointsMap { get; } = new Dictionary(); /// /// Dictionary to store previous positions of touch points /// private Dictionary PreviousTouchingPointsMap { get; } = new Dictionary(); #endregion #region Initialization /// /// Initializes a new instance of the RenderingCanvas /// public RenderingCanvas() { DispEngine.BackgroundColor = new Vec3d(0.1, 0.1, 0.5); DispEngine.BackgroundOpacity = 0.1; // Configure the main control properties HorizontalAlignment = HorizontalAlignment.Stretch; VerticalAlignment = VerticalAlignment.Stretch; Focusable = true; KeyboardNavigation.SetDirectionalNavigation(this, KeyboardNavigationMode.Cycle); DataContextChanged += CanvasDataContextChanged; // Create and configure the display pane DisplayerPane = new UserControl(); DisplayerPane.HorizontalAlignment = HorizontalAlignment.Stretch; DisplayerPane.VerticalAlignment = VerticalAlignment.Stretch; DisplayerPane.Focusable = true; DisplayerPane.IsTabStop = true; // Connect event handlers for user input and window events DisplayerPane.SizeChanged += RenderingCanvas_SizeChanged; DisplayerPane.MouseMove += RenderingCanvas_MouseMove; DisplayerPane.MouseDown += RenderingCanvas_MouseDown; DisplayerPane.MouseUp += RenderingCanvas_MouseUp; DisplayerPane.MouseWheel += RenderingCanvas_MouseWheel; DisplayerPane.KeyDown += RenderingCanvas_KeyDown; DisplayerPane.KeyUp += RenderingCanvas_KeyUp; DisplayerPane.Loaded += RenderingCanvas_Loaded; DisplayerPane.Unloaded += RenderingCanvas_Unloaded; DisplayerPane.IsVisibleChanged += DisplayerPane_IsVisibleChanged; // Add touch event handlers DisplayerPane.TouchDown += RenderingCanvas_TouchDown; DisplayerPane.TouchMove += RenderingCanvas_TouchMove; DisplayerPane.TouchUp += RenderingCanvas_TouchUp; // Enable touch support this.IsManipulationEnabled = true; // Add the display pane to this control's content Content = DisplayerPane; } #endregion #region Touch_Events /// /// Handles the touch down event /// private void RenderingCanvas_TouchDown(object sender, TouchEventArgs e) { // Add touch point to dictionary Point touchPoint = e.GetTouchPoint(DisplayerPane).Position; DispEngine.TouchDown(e.TouchDevice.Id, (int)touchPoint.X, (int)touchPoint.Y); // Ensure control gets focus DisplayerPane.Focus(); e.TouchDevice.Capture(DisplayerPane); e.Handled = true; } /// /// Handles the touch move event /// private void RenderingCanvas_TouchMove(object sender, TouchEventArgs e) { Point touchPoint = e.GetTouchPoint(DisplayerPane).Position; DispEngine.TouchMove(e.TouchDevice.Id, (int)touchPoint.X, (int)touchPoint.Y); e.Handled = true; } /// /// Handles the touch up event /// private void RenderingCanvas_TouchUp(object sender, TouchEventArgs e) { DispEngine.TouchUp(e.TouchDevice.Id); e.Handled = true; } #endregion #region Window_Events /// /// Handles window state changes (maximize, minimize, etc.) /// private unsafe void RenderingCanvas_StateChanged(object sender, EventArgs e) { switch ((sender as Window).WindowState) { case WindowState.Maximized: DispEngine.IsVisible = true; break; case WindowState.Minimized: DispEngine.IsVisible = false; break; case WindowState.Normal: DispEngine.IsVisible = true; break; } } /// /// Handles data context changes /// private unsafe void CanvasDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) { DispEngine pre = e.OldValue as DispEngine; DispEngine cur = e.NewValue as DispEngine; //child's binding event is triggered after IsVisible event and Load event. if (pre != null) //this section will never occur if the datacontext not set twice. { pre.Terminate(); pre.ImageRequestAfterBufferSwapped -= RenderingCanvas_BufferSwapped; } if (cur != null) { cur.ImageRequestAfterBufferSwapped += RenderingCanvas_BufferSwapped; cur.Start((int)DisplayerPane.RenderSize.Width, (int)DisplayerPane.RenderSize.Height); cur.IsVisible = IsVisible; } } /// /// Reference to the current window containing this control /// private Window currentWindow; /// /// Gets or sets the current window, connecting or disconnecting state change events /// Window CurrentWindow { get => currentWindow; set { if (currentWindow != null) currentWindow.StateChanged -= RenderingCanvas_StateChanged; currentWindow = value; if (currentWindow != null) currentWindow.StateChanged += RenderingCanvas_StateChanged; } } /// /// Handles the loaded event /// private unsafe void RenderingCanvas_Loaded(object sender, RoutedEventArgs e) { // Get the window containing this control CurrentWindow = Window.GetWindow(this); // Set up DispEngine rendering DispEngine.ImageRequestAfterBufferSwapped -= RenderingCanvas_BufferSwapped; DispEngine.ImageRequestAfterBufferSwapped += RenderingCanvas_BufferSwapped; DispEngine.Start((int)DisplayerPane.RenderSize.Width, (int)DisplayerPane.RenderSize.Height); DispEngine.IsVisible = IsVisible; } /// /// Handles the unloaded event /// private unsafe void RenderingCanvas_Unloaded(object sender, RoutedEventArgs e) { DispEngine.IsVisible = IsVisible; DispEngine.ImageRequestAfterBufferSwapped -= RenderingCanvas_BufferSwapped; CurrentWindow = null; } #endregion #region DispEngine_Rendering /// /// Handles the buffer swapped event from DispEngine /// private unsafe void RenderingCanvas_BufferSwapped(byte* data, int w, int h) { if (data == null) return; // Copy pixel data from DispEngine int n = w * h * 4; byte[] arr = new byte[n]; for (int i = 0; i < n; i++) arr[i] = data[i]; // Update UI on the UI thread DisplayerPane.Dispatcher.InvokeAsync(() => { BitmapSource bitmap = BitmapSource.Create(w, h, 1, 1, PixelFormats.Bgra32, null, arr, w * 4); DisplayerPane.Background = new ImageBrush(bitmap); }); } /// /// Handles the size changed event /// private void RenderingCanvas_SizeChanged(object sender, SizeChangedEventArgs e) { // Notify DispEngine of size changes DispEngine.Resize((int)DisplayerPane.RenderSize.Width, (int)DisplayerPane.RenderSize.Height); } /// /// Handles visibility changes /// private unsafe void DisplayerPane_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) { // Update visibility state in DispEngine DispEngine.IsVisible = IsVisible; } #endregion #region Keyboard_Events /// /// Handles the key up event /// private void RenderingCanvas_KeyUp(object sender, KeyEventArgs e) { DispEngine.KeyUp((long)e.Key); } /// /// Handles the key down event /// private void RenderingCanvas_KeyDown(object sender, KeyEventArgs e) { DispEngine.KeyDown((long)e.Key); // Map specific keys for view transformation long key = (long)e.Key; if (key == (long)Key.RightShift) key = (long)Key.LeftShift; DispEngine.KeyDownTransform(key, new key_table__transform_view_by_key_pressing_t() { HOME = (long)Key.Home, PAGE_UP = (long)Key.PageUp, PAGE_DOWN = (long)Key.PageDown, F1 = (long)Key.F1, F2 = (long)Key.F2, F3 = (long)Key.F3, F4 = (long)Key.F4, SHIFT = (long)Key.LeftShift, ARROW_LEFT = (long)Key.Left, ARROW_RIGHT = (long)Key.Right, ARROW_DOWN = (long)Key.Down, ARROW_UP = (long)Key.Up }); } #endregion #region Mouse_Events /// /// Helper method to get mouse button mask /// internal static HiMouseButtonMask GetMouseButtonMask(MouseDevice device) { HiMouseButtonMask mouseButtonMask = 0; mouseButtonMask.SetLeftPressed(device.LeftButton == MouseButtonState.Pressed); mouseButtonMask.SetMiddlePressed(device.MiddleButton == MouseButtonState.Pressed); mouseButtonMask.SetRightPressed(device.RightButton == MouseButtonState.Pressed); mouseButtonMask.SetXButton1Pressed(device.XButton1 == MouseButtonState.Pressed); mouseButtonMask.SetXButton2Pressed(device.XButton2 == MouseButtonState.Pressed); return mouseButtonMask; } /// /// Handles the mouse wheel event /// private void RenderingCanvas_MouseWheel(object sender, MouseWheelEventArgs e) { // Handle mouse wheel for zoom operations DispEngine.MouseWheel(0, e.Delta / 120); DispEngine.MouseWheelTransform(0, e.Delta / 120); } /// /// Handles the mouse up event /// private void RenderingCanvas_MouseUp(object sender, MouseButtonEventArgs e) { // Handle mouse button release DispEngine.MouseButtonUp((long)e.ChangedButton); (sender as UIElement)?.ReleaseMouseCapture(); } /// /// Handles the mouse down event /// private void RenderingCanvas_MouseDown(object sender, MouseButtonEventArgs e) { // Handle mouse button press DispEngine.MouseButtonDown((long)e.ChangedButton); DisplayerPane.Focus(); (sender as UIElement)?.CaptureMouse(); } /// /// Handles the mouse move event /// private void RenderingCanvas_MouseMove(object sender, MouseEventArgs e) { // Update mouse position and handle drag transforms Point p = e.GetPosition(DisplayerPane); DispEngine.MouseMove((int)p.X, (int)p.Y); DispEngine.MouseDragTransform((int)p.X, (int)p.Y, new mouse_button_table__transform_view_by_mouse_drag_t() { LEFT_BUTTON = (long)MouseButton.Left, RIGHT_BUTTON = (long)MouseButton.Right }); } #endregion #region Cleanup /// /// Flag to track disposed state /// private bool disposedValue; /// /// Disposes managed resources /// protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // Dispose the DispEngine to free resources DispEngine.Dispose(); } disposedValue = true; } } /// /// Public dispose method to free resources /// public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } #endregion } #endregion }