diff --git a/Pinta.Gui.Widgets/Widgets/Canvas/CanvasWindow.cs b/Pinta.Gui.Widgets/Widgets/Canvas/CanvasWindow.cs index a8cc22ac5..9b593ae77 100644 --- a/Pinta.Gui.Widgets/Widgets/Canvas/CanvasWindow.cs +++ b/Pinta.Gui.Widgets/Widgets/Canvas/CanvasWindow.cs @@ -33,14 +33,16 @@ namespace Pinta; public sealed class CanvasWindow : Gtk.Grid { private readonly Document document; + private readonly ChromeManager chrome; + private readonly ToolManager tools; private readonly Ruler horizontal_ruler; private readonly Ruler vertical_ruler; private readonly Gtk.ScrolledWindow scrolled_window; private readonly Gtk.EventControllerMotion motion_controller; + private readonly Gtk.GestureDrag drag_controller; private readonly Gtk.GestureZoom gesture_zoom; - private PointD current_window_pos = PointD.Zero; private PointD current_canvas_pos = PointD.Zero; private double cumulative_zoom_amount; private double last_scale_delta; @@ -67,9 +69,7 @@ public CanvasWindow ( scrollController.OnDecelerate += (_, _) => gestureZoom.IsActive (); // Cancel scroll deceleration when zooming PintaCanvas canvas = new ( - chrome, tools, - this, document, canvasGrid ) { @@ -82,6 +82,15 @@ public CanvasWindow ( viewPort.AddController (scrollController); viewPort.Child = canvas; + // Use the drag gesture to forward a sequence of mouse press -> move -> release events to the current tool. + // This is more reliable than using just a click gesture in combination with the move controller (see bug #1456) + // Note that we attach this to the root canvas widget, not the canvas, so that it can receive drags that start outside the canvas. + Gtk.GestureDrag dragController = Gtk.GestureDrag.New (); + dragController.SetButton (0); // Listen for all mouse buttons. + dragController.OnDragBegin += OnDragBegin; + dragController.OnDragUpdate += OnDragUpdate; + dragController.OnDragEnd += OnDragEnd; + Gtk.ScrolledWindow scrolledWindow = new () { Hexpand = true, Vexpand = true, @@ -108,6 +117,7 @@ public CanvasWindow ( Focusable = true; AddController (gestureZoom); + AddController (dragController); AddController (motionController); // --- Initialization (Gtk.Grid) @@ -123,6 +133,8 @@ public CanvasWindow ( Canvas = canvas; + this.chrome = chrome; + this.tools = tools; this.document = document; scrolled_window = scrolledWindow; @@ -130,6 +142,7 @@ public CanvasWindow ( horizontal_ruler = horizontalRuler; vertical_ruler = verticalRuler; motion_controller = motionController; + drag_controller = dragController; // --- Further initialization @@ -165,19 +178,35 @@ private void UpdateRulerSelection (object? sender, EventArgs e) } private void HandleMotion ( - Gtk.EventControllerMotion _, + Gtk.EventControllerMotion controller, Gtk.EventControllerMotion.MotionSignalArgs args) { - PointD newPosition = new (args.X, args.Y); + PointD rootPoint = new (args.X, args.Y); // These coordinates are relative to our grid widget, so transform into the child image // view's coordinates, and then to the canvas coordinates. - this.TranslateCoordinates (Canvas, newPosition, out PointD viewPos); + this.TranslateCoordinates (Canvas, rootPoint, out PointD viewPos); - current_window_pos = newPosition; current_canvas_pos = document.Workspace.ViewPointToCanvas (viewPos); horizontal_ruler.Position = current_canvas_pos.X; vertical_ruler.Position = current_canvas_pos.Y; + + // Forward mouse move events to the current tool when not dragging. + if (drag_controller.GetStartPoint (out _, out _)) + return; + + if (document.Workspace.PointInCanvas (current_canvas_pos)) + chrome.LastCanvasCursorPoint = current_canvas_pos.ToInt (); + + ToolMouseEventArgs tool_args = new () { + State = controller.GetCurrentEventState (), + MouseButton = MouseButton.None, + PointDouble = current_canvas_pos, + WindowPoint = viewPos, + RootPoint = rootPoint, + }; + + tools.DoMouseMove (document, tool_args); } private void HandleGestureZoomScaleChanged (object? sender, EventArgs e) @@ -206,9 +235,6 @@ private void HandleGestureZoomScaleChanged (object? sender, EventArgs e) last_scale_delta = gesture_zoom.GetScaleDelta () - 1; } - public PointD WindowMousePosition - => current_window_pos; - public bool IsMouseOnCanvas => motion_controller.ContainsPointer; @@ -314,4 +340,104 @@ private bool HandleScrollEvent ( return true; } + + private void OnDragBegin (Gtk.GestureDrag gesture, Gtk.GestureDrag.DragBeginSignalArgs args) + { + // A mouse click on the canvas should grab focus away from any toolbar widgets, etc + // Using the root canvas widget works best - if the drawing area is given focus, the scroll + // widget jumps back to the origin. + GrabFocus (); + + // Note: if we ever regain support for docking multiple canvas + // widgets side by side (like Pinta 1.7 could), a mouse click should switch + // the active document to this document. + + // Send the mouse press event to the current tool. + // Translate coordinates to the canvas widget. + PointD rootPoint = new (args.StartX, args.StartY); + this.TranslateCoordinates (Canvas, rootPoint, out PointD viewPoint); + PointD canvasPoint = document.Workspace.ViewPointToCanvas (viewPoint); + + ToolMouseEventArgs tool_args = new () { + State = gesture.GetCurrentEventState (), + MouseButton = gesture.GetCurrentMouseButton (), + PointDouble = canvasPoint, + WindowPoint = viewPoint, + RootPoint = rootPoint, + }; + + tools.DoMouseDown (document, tool_args); + } + + private void OnDragUpdate (Gtk.GestureDrag gesture, Gtk.GestureDrag.DragUpdateSignalArgs args) + { + gesture.GetStartPoint (out double startX, out double startY); + PointD rootPoint = new (startX + args.OffsetX, startY + args.OffsetY); + + // Translate coordinates to the canvas widget. + this.TranslateCoordinates (Canvas, rootPoint, out PointD viewPoint); + + current_canvas_pos = document.Workspace.ViewPointToCanvas (viewPoint); + if (document.Workspace.PointInCanvas (current_canvas_pos)) + chrome.LastCanvasCursorPoint = current_canvas_pos.ToInt (); + + // Send the mouse move event to the current tool. + ToolMouseEventArgs tool_args = new () { + State = gesture.GetCurrentEventState (), + MouseButton = gesture.GetCurrentMouseButton (), + PointDouble = current_canvas_pos, + WindowPoint = viewPoint, + RootPoint = rootPoint, + }; + + tools.DoMouseMove (document, tool_args); + } + + private void OnDragEnd (Gtk.GestureDrag gesture, Gtk.GestureDrag.DragEndSignalArgs args) + { + gesture.GetStartPoint (out double startX, out double startY); + PointD rootPoint = new (startX + args.OffsetX, startY + args.OffsetY); + + // Translate coordinates to the canvas widget. + this.TranslateCoordinates (Canvas, rootPoint, out PointD viewPoint); + PointD canvasPoint = document.Workspace.ViewPointToCanvas (viewPoint); + + // Send the mouse release event to the current tool. + ToolMouseEventArgs tool_args = new () { + State = gesture.GetCurrentEventState (), + MouseButton = gesture.GetCurrentMouseButton (), + PointDouble = canvasPoint, + WindowPoint = viewPoint, + RootPoint = rootPoint, + }; + + tools.DoMouseUp (document, tool_args); + } + + public bool DoKeyPressEvent ( + Gtk.EventControllerKey controller, + Gtk.EventControllerKey.KeyPressedSignalArgs args) + { + // Give the current tool a chance to handle the key press + ToolKeyEventArgs tool_args = new () { + Event = controller.GetCurrentEvent (), + Key = args.GetKey (), + State = args.State, + }; + + return tools.DoKeyDown (document, tool_args); + } + + public bool DoKeyReleaseEvent ( + Gtk.EventControllerKey controller, + Gtk.EventControllerKey.KeyReleasedSignalArgs args) + { + ToolKeyEventArgs tool_args = new () { + Event = controller.GetCurrentEvent (), + Key = args.GetKey (), + State = args.State, + }; + + return tools.DoKeyUp (document, tool_args); + } } diff --git a/Pinta.Gui.Widgets/Widgets/Canvas/PintaCanvas.cs b/Pinta.Gui.Widgets/Widgets/Canvas/PintaCanvas.cs index e7dc4a16a..c05ae0cf5 100644 --- a/Pinta.Gui.Widgets/Widgets/Canvas/PintaCanvas.cs +++ b/Pinta.Gui.Widgets/Widgets/Canvas/PintaCanvas.cs @@ -35,9 +35,7 @@ public sealed class PintaCanvas : Gtk.Picture { private readonly CanvasRenderer cr; private readonly Document document; - private readonly CanvasWindow canvas_window; private readonly ICanvasGridService canvas_grid; - private readonly Gtk.GestureDrag drag_controller; private uint queued_update_id = 0; @@ -51,19 +49,14 @@ public sealed class PintaCanvas : Gtk.Picture private readonly uint selection_animation_timer_id; private float selection_animation_dash_offset; - private readonly ChromeManager chrome; private readonly ToolManager tools; public PintaCanvas ( - ChromeManager chrome, ToolManager tools, - CanvasWindow window, Document document, ICanvasGridService canvasGrid) { - this.chrome = chrome; this.tools = tools; - canvas_window = window; canvas_grid = canvasGrid; this.document = document; @@ -80,20 +73,6 @@ public PintaCanvas ( // Timer for selection outline animation selection_animation_timer_id = GLib.Functions.TimeoutAdd (GLib.Constants.PRIORITY_DEFAULT, 80, SelectionAnimationTick); - // Use the drag gesture to forward a sequence of mouse press -> move -> release events to the current tool. - // This is more reliable than using just a click gesture in combination with the move controller (see bug #1456) - drag_controller = Gtk.GestureDrag.New (); - drag_controller.SetButton (0); // Listen for all mouse buttons. - drag_controller.OnDragBegin += OnDragBegin; - drag_controller.OnDragUpdate += OnDragUpdate; - drag_controller.OnDragEnd += OnDragEnd; - AddController (drag_controller); - - // Forward mouse move events to the current tool when not dragging. - Gtk.EventControllerMotion motion_controller = Gtk.EventControllerMotion.New (); - motion_controller.OnMotion += OnMouseMove; - AddController (motion_controller); - // If there is additional space available, keep the image centered and prevent stretching. Hexpand = false; Halign = Gtk.Align.Center; @@ -468,122 +447,6 @@ private void OnViewSizeChanged (object? o, System.EventArgs args) SetSizeRequest (viewSize.Width, viewSize.Height); } - private void OnDragBegin (Gtk.GestureDrag gesture, Gtk.GestureDrag.DragBeginSignalArgs args) - { - // Note we don't call gesture.SetState (Gtk.EventSequenceState.Claimed) here, so - // that the CanvasWindow can also receive motion events to update the root window mouse position. - - // A mouse click on the canvas should grab focus away from any toolbar widgets, etc - // Using the root canvas widget works best - if the drawing area is given focus, the scroll - // widget jumps back to the origin. - canvas_window.GrabFocus (); - - // Note: if we ever regain support for docking multiple canvas - // widgets side by side (like Pinta 1.7 could), a mouse click should switch - // the active document to this document. - - // Send the mouse press event to the current tool. - PointD window_point = new (args.StartX, args.StartY); - PointD canvas_point = document.Workspace.ViewPointToCanvas (window_point); - - ToolMouseEventArgs tool_args = new () { - State = gesture.GetCurrentEventState (), - MouseButton = gesture.GetCurrentMouseButton (), - PointDouble = canvas_point, - WindowPoint = window_point, - RootPoint = canvas_window.WindowMousePosition, - }; - - tools.DoMouseDown (document, tool_args); - } - - private void OnDragUpdate (Gtk.GestureDrag gesture, Gtk.GestureDrag.DragUpdateSignalArgs args) - { - // Send the mouse move event to the current tool. - gesture.GetStartPoint (out double startX, out double startY); - PointD window_point = new (startX + args.OffsetX, startY + args.OffsetY); - PointD canvas_point = document.Workspace.ViewPointToCanvas (window_point); - - ToolMouseEventArgs tool_args = new () { - State = gesture.GetCurrentEventState (), - MouseButton = gesture.GetCurrentMouseButton (), - PointDouble = canvas_point, - WindowPoint = window_point, - RootPoint = canvas_window.WindowMousePosition, - }; - - tools.DoMouseMove (document, tool_args); - } - - private void OnDragEnd (Gtk.GestureDrag gesture, Gtk.GestureDrag.DragEndSignalArgs args) - { - // Send the mouse release event to the current tool. - gesture.GetStartPoint (out double startX, out double startY); - PointD window_point = new (startX + args.OffsetX, startY + args.OffsetY); - PointD canvas_point = document.Workspace.ViewPointToCanvas (window_point); - - ToolMouseEventArgs tool_args = new () { - State = gesture.GetCurrentEventState (), - MouseButton = gesture.GetCurrentMouseButton (), - PointDouble = canvas_point, - WindowPoint = window_point, - RootPoint = canvas_window.WindowMousePosition, - }; - - tools.DoMouseUp (document, tool_args); - } - - private void OnMouseMove (Gtk.EventControllerMotion controller, Gtk.EventControllerMotion.MotionSignalArgs args) - { - // Don't send duplicate mouse move events while a drag is active. - if (drag_controller.GetStartPoint (out _, out _)) - return; - - PointD window_point = new (args.X, args.Y); - PointD canvas_point = document.Workspace.ViewPointToCanvas (window_point); - - if (document.Workspace.PointInCanvas (canvas_point)) - chrome.LastCanvasCursorPoint = canvas_point.ToInt (); - - ToolMouseEventArgs tool_args = new () { - State = controller.GetCurrentEventState (), - MouseButton = MouseButton.None, - PointDouble = canvas_point, - WindowPoint = window_point, - RootPoint = canvas_window.WindowMousePosition, - }; - - tools.DoMouseMove (document, tool_args); - } - - public bool DoKeyPressEvent ( - Gtk.EventControllerKey controller, - Gtk.EventControllerKey.KeyPressedSignalArgs args) - { - // Give the current tool a chance to handle the key press - ToolKeyEventArgs tool_args = new () { - Event = controller.GetCurrentEvent (), - Key = args.GetKey (), - State = args.State, - }; - - return tools.DoKeyDown (document, tool_args); - } - - public bool DoKeyReleaseEvent ( - Gtk.EventControllerKey controller, - Gtk.EventControllerKey.KeyReleasedSignalArgs args) - { - ToolKeyEventArgs tool_args = new () { - Event = controller.GetCurrentEvent (), - Key = args.GetKey (), - State = args.State, - }; - - return tools.DoKeyUp (document, tool_args); - } - - #region Selection outline animation private bool SelectionAnimationTick () { diff --git a/Pinta/MainWindow.cs b/Pinta/MainWindow.cs index bbbc6c52a..6d4bc9027 100644 --- a/Pinta/MainWindow.cs +++ b/Pinta/MainWindow.cs @@ -248,7 +248,7 @@ private bool HandleGlobalKeyPress ( var canvas_window = (CanvasWindow) PintaCore.Workspace.ActiveWorkspace.CanvasWindow; if ((canvas_window.HasFocus || canvas_window.IsMouseOnCanvas) && - canvas_window.Canvas.DoKeyPressEvent (controller, args)) { + canvas_window.DoKeyPressEvent (controller, args)) { return true; } } @@ -282,7 +282,7 @@ private void HandleGlobalKeyRelease ( var canvas_window = (CanvasWindow) PintaCore.Workspace.ActiveWorkspace.CanvasWindow; if (canvas_window.HasFocus || canvas_window.IsMouseOnCanvas) - canvas_window.Canvas.DoKeyReleaseEvent (controller, args); + canvas_window.DoKeyReleaseEvent (controller, args); } private bool SendToFocusWidget (Gtk.EventControllerKey key_controller)