535 lines
18 KiB
C#
535 lines
18 KiB
C#
|
|
// Licensed to the .NET Foundation under one or more agreements.
|
||
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||
|
|
|
||
|
|
using System;
|
||
|
|
using Microsoft.Maui.Controls;
|
||
|
|
using Microsoft.Maui.Platform.Linux.Rendering;
|
||
|
|
using Microsoft.Maui.Platform.Linux.Services;
|
||
|
|
using Microsoft.Maui.Platform.Linux.Window;
|
||
|
|
using Microsoft.Maui.Platform;
|
||
|
|
using SkiaSharp;
|
||
|
|
|
||
|
|
namespace Microsoft.Maui.Platform.Linux;
|
||
|
|
|
||
|
|
public partial class LinuxApplication
|
||
|
|
{
|
||
|
|
private void UpdateAnimations()
|
||
|
|
{
|
||
|
|
// Update cursor blink for text input controls
|
||
|
|
if (_focusedView is SkiaEntry entry)
|
||
|
|
{
|
||
|
|
entry.UpdateCursorBlink();
|
||
|
|
}
|
||
|
|
else if (_focusedView is SkiaEditor editor)
|
||
|
|
{
|
||
|
|
editor.UpdateCursorBlink();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnWindowResized(object? sender, (int Width, int Height) size)
|
||
|
|
{
|
||
|
|
if (_rootView != null)
|
||
|
|
{
|
||
|
|
// Re-measure with new available size, then arrange
|
||
|
|
var availableSize = new Microsoft.Maui.Graphics.Size(size.Width, size.Height);
|
||
|
|
_rootView.Measure(availableSize);
|
||
|
|
_rootView.Arrange(new Microsoft.Maui.Graphics.Rect(0, 0, size.Width, size.Height));
|
||
|
|
}
|
||
|
|
_renderingEngine?.InvalidateAll();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnWindowExposed(object? sender, EventArgs e)
|
||
|
|
{
|
||
|
|
Render();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnKeyDown(object? sender, KeyEventArgs e)
|
||
|
|
{
|
||
|
|
// Route to dialog if one is active
|
||
|
|
if (LinuxDialogService.HasActiveDialog)
|
||
|
|
{
|
||
|
|
LinuxDialogService.TopDialog?.OnKeyDown(e);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_focusedView != null)
|
||
|
|
{
|
||
|
|
_focusedView.OnKeyDown(e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnKeyUp(object? sender, KeyEventArgs e)
|
||
|
|
{
|
||
|
|
// Route to dialog if one is active
|
||
|
|
if (LinuxDialogService.HasActiveDialog)
|
||
|
|
{
|
||
|
|
LinuxDialogService.TopDialog?.OnKeyUp(e);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_focusedView != null)
|
||
|
|
{
|
||
|
|
_focusedView.OnKeyUp(e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnTextInput(object? sender, TextInputEventArgs e)
|
||
|
|
{
|
||
|
|
if (_focusedView != null)
|
||
|
|
{
|
||
|
|
_focusedView.OnTextInput(e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnPointerMoved(object? sender, PointerEventArgs e)
|
||
|
|
{
|
||
|
|
// Route to context menu if one is active
|
||
|
|
if (LinuxDialogService.HasContextMenu)
|
||
|
|
{
|
||
|
|
LinuxDialogService.ActiveContextMenu?.OnPointerMoved(e);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Route to dialog if one is active
|
||
|
|
if (LinuxDialogService.HasActiveDialog)
|
||
|
|
{
|
||
|
|
LinuxDialogService.TopDialog?.OnPointerMoved(e);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_rootView != null)
|
||
|
|
{
|
||
|
|
// If a view has captured the pointer, send all events to it
|
||
|
|
if (_capturedView != null)
|
||
|
|
{
|
||
|
|
_capturedView.OnPointerMoved(e);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check for popup overlay first
|
||
|
|
var popupOwner = SkiaView.GetPopupOwnerAt(e.X, e.Y);
|
||
|
|
var hitView = popupOwner ?? _rootView.HitTest(e.X, e.Y);
|
||
|
|
|
||
|
|
// Track hover state changes
|
||
|
|
if (hitView != _hoveredView)
|
||
|
|
{
|
||
|
|
_hoveredView?.OnPointerExited(e);
|
||
|
|
_hoveredView = hitView;
|
||
|
|
_hoveredView?.OnPointerEntered(e);
|
||
|
|
|
||
|
|
// Update cursor based on view's cursor type
|
||
|
|
CursorType cursor = hitView?.CursorType ?? CursorType.Arrow;
|
||
|
|
_mainWindow?.SetCursor(cursor);
|
||
|
|
}
|
||
|
|
|
||
|
|
hitView?.OnPointerMoved(e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnPointerPressed(object? sender, PointerEventArgs e)
|
||
|
|
{
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", $"OnPointerPressed at ({e.X}, {e.Y}), Button={e.Button}");
|
||
|
|
|
||
|
|
// Route to context menu if one is active
|
||
|
|
if (LinuxDialogService.HasContextMenu)
|
||
|
|
{
|
||
|
|
LinuxDialogService.ActiveContextMenu?.OnPointerPressed(e);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Route to dialog if one is active
|
||
|
|
if (LinuxDialogService.HasActiveDialog)
|
||
|
|
{
|
||
|
|
LinuxDialogService.TopDialog?.OnPointerPressed(e);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_rootView != null)
|
||
|
|
{
|
||
|
|
// Check for popup overlay first
|
||
|
|
var popupOwner = SkiaView.GetPopupOwnerAt(e.X, e.Y);
|
||
|
|
var hitView = popupOwner ?? _rootView.HitTest(e.X, e.Y);
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", $"HitView: {hitView?.GetType().Name ?? "null"}, rootView: {_rootView.GetType().Name}");
|
||
|
|
|
||
|
|
if (hitView != null)
|
||
|
|
{
|
||
|
|
// Capture pointer to this view for drag operations
|
||
|
|
_capturedView = hitView;
|
||
|
|
|
||
|
|
// Update focus
|
||
|
|
if (hitView.IsFocusable)
|
||
|
|
{
|
||
|
|
FocusedView = hitView;
|
||
|
|
}
|
||
|
|
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", $"Calling OnPointerPressed on {hitView.GetType().Name}");
|
||
|
|
hitView.OnPointerPressed(e);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
// Close any open popups when clicking outside
|
||
|
|
if (SkiaView.HasActivePopup && _focusedView != null)
|
||
|
|
{
|
||
|
|
_focusedView.OnFocusLost();
|
||
|
|
}
|
||
|
|
FocusedView = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnPointerReleased(object? sender, PointerEventArgs e)
|
||
|
|
{
|
||
|
|
// Route to dialog if one is active
|
||
|
|
if (LinuxDialogService.HasActiveDialog)
|
||
|
|
{
|
||
|
|
LinuxDialogService.TopDialog?.OnPointerReleased(e);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_rootView != null)
|
||
|
|
{
|
||
|
|
// If a view has captured the pointer, send release to it
|
||
|
|
if (_capturedView != null)
|
||
|
|
{
|
||
|
|
_capturedView.OnPointerReleased(e);
|
||
|
|
_capturedView = null; // Release capture
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check for popup overlay first
|
||
|
|
var popupOwner = SkiaView.GetPopupOwnerAt(e.X, e.Y);
|
||
|
|
var hitView = popupOwner ?? _rootView.HitTest(e.X, e.Y);
|
||
|
|
hitView?.OnPointerReleased(e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnScroll(object? sender, ScrollEventArgs e)
|
||
|
|
{
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", $"OnScroll - X={e.X}, Y={e.Y}, DeltaX={e.DeltaX}, DeltaY={e.DeltaY}");
|
||
|
|
if (_rootView != null)
|
||
|
|
{
|
||
|
|
var hitView = _rootView.HitTest(e.X, e.Y);
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", $"HitView: {hitView?.GetType().Name ?? "null"}");
|
||
|
|
// Bubble scroll events up to find a ScrollView
|
||
|
|
var view = hitView;
|
||
|
|
while (view != null)
|
||
|
|
{
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", $"Bubbling to: {view.GetType().Name}");
|
||
|
|
if (view is SkiaScrollView scrollView)
|
||
|
|
{
|
||
|
|
scrollView.OnScroll(e);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
view.OnScroll(e);
|
||
|
|
if (e.Handled) return;
|
||
|
|
view = view.Parent;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnCloseRequested(object? sender, EventArgs e)
|
||
|
|
{
|
||
|
|
_mainWindow?.Stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
// GTK Event Handlers
|
||
|
|
private void OnGtkDrawRequested(object? sender, EventArgs e)
|
||
|
|
{
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", ">>> OnGtkDrawRequested ENTER");
|
||
|
|
LogDraw();
|
||
|
|
var surface = _gtkWindow?.SkiaSurface;
|
||
|
|
if (surface?.Canvas != null && _rootView != null)
|
||
|
|
{
|
||
|
|
var bgColor = Application.Current?.UserAppTheme == AppTheme.Dark
|
||
|
|
? new SKColor(32, 33, 36)
|
||
|
|
: SKColors.White;
|
||
|
|
surface.Canvas.Clear(bgColor);
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", "Drawing rootView...");
|
||
|
|
_rootView.Draw(surface.Canvas);
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", "Drawing dialogs...");
|
||
|
|
var bounds = new SKRect(0, 0, surface.Width, surface.Height);
|
||
|
|
LinuxDialogService.DrawDialogs(surface.Canvas, bounds);
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", "<<< OnGtkDrawRequested EXIT");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnGtkResized(object? sender, (int Width, int Height) size)
|
||
|
|
{
|
||
|
|
PerformGtkLayout(size.Width, size.Height);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnGtkPointerPressed(object? sender, (double X, double Y, int Button) e)
|
||
|
|
{
|
||
|
|
string buttonName = e.Button == 1 ? "Left" : e.Button == 2 ? "Middle" : e.Button == 3 ? "Right" : $"Unknown({e.Button})";
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", $"GTK PointerPressed at ({e.X:F1}, {e.Y:F1}), Button={e.Button} ({buttonName})");
|
||
|
|
|
||
|
|
// Route to dialog if one is active
|
||
|
|
if (LinuxDialogService.HasActiveDialog)
|
||
|
|
{
|
||
|
|
var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
|
||
|
|
var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
|
||
|
|
LinuxDialogService.TopDialog?.OnPointerPressed(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (LinuxDialogService.HasContextMenu)
|
||
|
|
{
|
||
|
|
var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
|
||
|
|
var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
|
||
|
|
LinuxDialogService.ActiveContextMenu?.OnPointerPressed(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_rootView == null)
|
||
|
|
{
|
||
|
|
DiagnosticLog.Warn("LinuxApplication", "GTK _rootView is null!");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var hitView = _rootView.HitTest((float)e.X, (float)e.Y);
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", $"GTK HitView: {hitView?.GetType().Name ?? "null"}");
|
||
|
|
|
||
|
|
if (hitView != null)
|
||
|
|
{
|
||
|
|
if (hitView.IsFocusable && _focusedView != hitView)
|
||
|
|
{
|
||
|
|
_focusedView?.OnFocusLost();
|
||
|
|
_focusedView = hitView;
|
||
|
|
_focusedView.OnFocusGained();
|
||
|
|
}
|
||
|
|
_capturedView = hitView;
|
||
|
|
var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
|
||
|
|
var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", ">>> Before OnPointerPressed");
|
||
|
|
hitView.OnPointerPressed(args);
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", "<<< After OnPointerPressed, calling RequestRedraw");
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", "<<< After RequestRedraw, returning from handler");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnGtkPointerReleased(object? sender, (double X, double Y, int Button) e)
|
||
|
|
{
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", ">>> OnGtkPointerReleased ENTER");
|
||
|
|
|
||
|
|
// Route to dialog if one is active
|
||
|
|
if (LinuxDialogService.HasActiveDialog)
|
||
|
|
{
|
||
|
|
var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
|
||
|
|
var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
|
||
|
|
LinuxDialogService.TopDialog?.OnPointerReleased(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_rootView == null) return;
|
||
|
|
|
||
|
|
if (_capturedView != null)
|
||
|
|
{
|
||
|
|
var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
|
||
|
|
var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", $"Calling OnPointerReleased on {_capturedView.GetType().Name}");
|
||
|
|
_capturedView.OnPointerReleased(args);
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", "OnPointerReleased returned");
|
||
|
|
_capturedView = null;
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
DiagnosticLog.Debug("LinuxApplication", "<<< OnGtkPointerReleased EXIT (captured path)");
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
var hitView = _rootView.HitTest((float)e.X, (float)e.Y);
|
||
|
|
if (hitView != null)
|
||
|
|
{
|
||
|
|
var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
|
||
|
|
var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
|
||
|
|
hitView.OnPointerReleased(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnGtkPointerMoved(object? sender, (double X, double Y) e)
|
||
|
|
{
|
||
|
|
// Route to dialog if one is active
|
||
|
|
if (LinuxDialogService.HasActiveDialog)
|
||
|
|
{
|
||
|
|
var args = new PointerEventArgs((float)e.X, (float)e.Y);
|
||
|
|
LinuxDialogService.TopDialog?.OnPointerMoved(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (LinuxDialogService.HasContextMenu)
|
||
|
|
{
|
||
|
|
var args = new PointerEventArgs((float)e.X, (float)e.Y);
|
||
|
|
LinuxDialogService.ActiveContextMenu?.OnPointerMoved(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_rootView == null) return;
|
||
|
|
|
||
|
|
if (_capturedView != null)
|
||
|
|
{
|
||
|
|
var args = new PointerEventArgs((float)e.X, (float)e.Y);
|
||
|
|
_capturedView.OnPointerMoved(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
var hitView = _rootView.HitTest((float)e.X, (float)e.Y);
|
||
|
|
if (hitView != _hoveredView)
|
||
|
|
{
|
||
|
|
var args = new PointerEventArgs((float)e.X, (float)e.Y);
|
||
|
|
_hoveredView?.OnPointerExited(args);
|
||
|
|
_hoveredView = hitView;
|
||
|
|
_hoveredView?.OnPointerEntered(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (hitView != null)
|
||
|
|
{
|
||
|
|
var args = new PointerEventArgs((float)e.X, (float)e.Y);
|
||
|
|
hitView.OnPointerMoved(args);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnGtkKeyPressed(object? sender, (uint KeyVal, uint KeyCode, uint State) e)
|
||
|
|
{
|
||
|
|
var key = ConvertGdkKey(e.KeyVal);
|
||
|
|
var modifiers = ConvertGdkModifiers(e.State);
|
||
|
|
var args = new KeyEventArgs(key, modifiers);
|
||
|
|
|
||
|
|
// Route to dialog if one is active
|
||
|
|
if (LinuxDialogService.HasActiveDialog)
|
||
|
|
{
|
||
|
|
LinuxDialogService.TopDialog?.OnKeyDown(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_focusedView != null)
|
||
|
|
{
|
||
|
|
_focusedView.OnKeyDown(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnGtkKeyReleased(object? sender, (uint KeyVal, uint KeyCode, uint State) e)
|
||
|
|
{
|
||
|
|
var key = ConvertGdkKey(e.KeyVal);
|
||
|
|
var modifiers = ConvertGdkModifiers(e.State);
|
||
|
|
var args = new KeyEventArgs(key, modifiers);
|
||
|
|
|
||
|
|
// Route to dialog if one is active
|
||
|
|
if (LinuxDialogService.HasActiveDialog)
|
||
|
|
{
|
||
|
|
LinuxDialogService.TopDialog?.OnKeyUp(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_focusedView != null)
|
||
|
|
{
|
||
|
|
_focusedView.OnKeyUp(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnGtkScrolled(object? sender, (double X, double Y, double DeltaX, double DeltaY, uint State) e)
|
||
|
|
{
|
||
|
|
if (_rootView == null) return;
|
||
|
|
|
||
|
|
// Convert GDK state to KeyModifiers
|
||
|
|
var modifiers = ConvertGdkStateToModifiers(e.State);
|
||
|
|
bool isCtrlPressed = (modifiers & KeyModifiers.Control) != 0;
|
||
|
|
|
||
|
|
var hitView = _rootView.HitTest((float)e.X, (float)e.Y);
|
||
|
|
|
||
|
|
// Check for pinch gesture (Ctrl+Scroll) first
|
||
|
|
if (isCtrlPressed && hitView?.MauiView != null)
|
||
|
|
{
|
||
|
|
if (Handlers.GestureManager.ProcessScrollAsPinch(hitView.MauiView, e.X, e.Y, e.DeltaY, true))
|
||
|
|
{
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
while (hitView != null)
|
||
|
|
{
|
||
|
|
if (hitView is SkiaScrollView scrollView)
|
||
|
|
{
|
||
|
|
var args = new ScrollEventArgs((float)e.X, (float)e.Y, (float)e.DeltaX, (float)e.DeltaY, modifiers);
|
||
|
|
scrollView.OnScroll(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
hitView = hitView.Parent;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static KeyModifiers ConvertGdkStateToModifiers(uint state)
|
||
|
|
{
|
||
|
|
var modifiers = KeyModifiers.None;
|
||
|
|
// GDK modifier masks
|
||
|
|
const uint GDK_SHIFT_MASK = 1 << 0;
|
||
|
|
const uint GDK_CONTROL_MASK = 1 << 2;
|
||
|
|
const uint GDK_MOD1_MASK = 1 << 3; // Alt
|
||
|
|
const uint GDK_SUPER_MASK = 1 << 26;
|
||
|
|
const uint GDK_LOCK_MASK = 1 << 1; // Caps Lock
|
||
|
|
|
||
|
|
if ((state & GDK_SHIFT_MASK) != 0) modifiers |= KeyModifiers.Shift;
|
||
|
|
if ((state & GDK_CONTROL_MASK) != 0) modifiers |= KeyModifiers.Control;
|
||
|
|
if ((state & GDK_MOD1_MASK) != 0) modifiers |= KeyModifiers.Alt;
|
||
|
|
if ((state & GDK_SUPER_MASK) != 0) modifiers |= KeyModifiers.Super;
|
||
|
|
if ((state & GDK_LOCK_MASK) != 0) modifiers |= KeyModifiers.CapsLock;
|
||
|
|
|
||
|
|
return modifiers;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void OnGtkTextInput(object? sender, string text)
|
||
|
|
{
|
||
|
|
if (_focusedView != null)
|
||
|
|
{
|
||
|
|
var args = new TextInputEventArgs(text);
|
||
|
|
_focusedView.OnTextInput(args);
|
||
|
|
_gtkWindow?.RequestRedraw();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static Key ConvertGdkKey(uint keyval)
|
||
|
|
{
|
||
|
|
return keyval switch
|
||
|
|
{
|
||
|
|
65288 => Key.Backspace,
|
||
|
|
65289 => Key.Tab,
|
||
|
|
65293 => Key.Enter,
|
||
|
|
65307 => Key.Escape,
|
||
|
|
65360 => Key.Home,
|
||
|
|
65361 => Key.Left,
|
||
|
|
65362 => Key.Up,
|
||
|
|
65363 => Key.Right,
|
||
|
|
65364 => Key.Down,
|
||
|
|
65365 => Key.PageUp,
|
||
|
|
65366 => Key.PageDown,
|
||
|
|
65367 => Key.End,
|
||
|
|
65535 => Key.Delete,
|
||
|
|
>= 32 and <= 126 => (Key)keyval,
|
||
|
|
_ => Key.Unknown
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
private static KeyModifiers ConvertGdkModifiers(uint state)
|
||
|
|
{
|
||
|
|
var modifiers = KeyModifiers.None;
|
||
|
|
if ((state & 1) != 0) modifiers |= KeyModifiers.Shift;
|
||
|
|
if ((state & 4) != 0) modifiers |= KeyModifiers.Control;
|
||
|
|
if ((state & 8) != 0) modifiers |= KeyModifiers.Alt;
|
||
|
|
return modifiers;
|
||
|
|
}
|
||
|
|
}
|