Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ on:

env:
DOTNET_VERSION: 8.0.x
PINTA_VERSION: 3.1.1

jobs:
build-ubuntu:
Expand All @@ -34,14 +35,13 @@ jobs:
GTK_LIBS: undefined

steps:
- name: Set environment variables
run: echo "BUILD_DIR=${{runner.temp}}/pinta-${{env.PINTA_VERSION}}" >> $GITHUB_ENV
- uses: actions/checkout@v6
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{matrix.dotnet_version}}
- name: Create temporary global.json
if: matrix.dotnet_version == '9.0.x'
run: mv .github/workflows/dotnet9.global.json ./global.json
- name: Install Apt Dependencies
run: |
sudo apt update
Expand All @@ -50,25 +50,34 @@ jobs:
run: |
./autogen.sh
make dist
mkdir ${{env.BUILD_DIR}}
tar -xzf pinta-${{env.PINTA_VERSION}}.tar.gz -C ${{runner.temp}}
- name: Create temporary global.json
if: matrix.dotnet_version == '9.0.x'
run: mv .github/workflows/dotnet9.global.json ${{env.BUILD_DIR}}/global.json
- name: Build
working-directory: ${{env.BUILD_DIR}}
run: |
./autogen.sh --prefix ${{ runner.temp }}
./configure --prefix ${{runner.temp}}/pinta-install
make build
- name: Test
working-directory: ${{env.BUILD_DIR}}
run: make test
- name: Verify code formatting
if: matrix.dotnet_version == '9.0.x'
# Ignore warning CA1416 for unavailable platform-specific code, since this is unrelated to formatting.
run: dotnet format --no-restore --verify-no-changes --exclude-diagnostics CA1416
- name: Test Install
working-directory: ${{env.BUILD_DIR}}
run: make install
- name: Build Installer
working-directory: ${{env.BUILD_DIR}}
run: make releasezip
- name: Upload Installer
uses: actions/upload-artifact@v6
with:
name: Pinta-linux-dotnet-${{matrix.dotnet_version}}.zip
path: pinta-3.1.zip
path: ${{env.BUILD_DIR}}/pinta-${{env.PINTA_VERSION}}.zip
if-no-files-found: error

build-macos:
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ Thanks to the following contributors who worked on this release:

### Fixed

## [3.1.1](https://github.com/PintaProject/Pinta/release/tag/3.1.1) - 2026/01/10

### Added
- The Windows installer now supports a non-administrative install mode (#1915, #1918)

### Fixed
- Fixed packaging issue where the release tarball was missing required files (#1905, #1907)
- Fixed performance regression with the selection tools on large images after the canvas widget rewrite in version 3.1 (#1912, #1909)
- Fixed regression from Pinta 3.1 where the selection handles could become inverted in certain scenarios (#1917, #1921)
- Fixed regression from Pinta 3.1 where drag gestures starting outside the canvas were not registered (#1929, #1908)
- Fixed regression from Pinta 3.1 where drag gestures did not update the canvas position displayed in the status bar (#1929, #1908)


## [3.1](https://github.com/PintaProject/Pinta/release/tag/3.1) - 2025/12/23

Thanks to the following contributors who worked on this release:
Expand Down
6 changes: 3 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
<Nullable>enable</Nullable>

<!-- Versioning and assembly info -->
<Version>3.1.0.0</Version>
<Version>3.1.1.0</Version>
<!-- User-facing version number, used for the version command line argument -->
<InformationalVersion>3.1</InformationalVersion>
<InformationalVersion>3.1.1</InformationalVersion>
<Authors>PintaProject</Authors>
<Company />
<Product />

<!-- Add an absolute path to a local GirCore source directory to use it instead of the NuGet package -->
<GirCoreSource />
</PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ releasezip:

EXTRA_DIST = Pinta Pinta.Core Pinta.Docking Pinta.Effects Pinta.Gui.Addins Pinta.Gui.Widgets Pinta.Resources Pinta.Tools po xdg tests license-mit.txt \
license-pdn.txt Pinta.sln pinta.pc.in readme.md intltool-extract.in \
intltool-merge.in intltool-update.in installer/linux/install.proj Directory.Build.props Directory.Packages.props
intltool-merge.in intltool-update.in installer/addins installer/linux/install.proj properties Directory.Build.props Directory.Packages.props

CLEANFILES = intltool-extract \
intltool-update \
Expand Down
2 changes: 1 addition & 1 deletion Pinta.Core/PintaCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static class PintaCore
/// <summary>
/// The current version number of Pinta.
/// </summary>
public const string ApplicationVersion = "3.1";
public const string ApplicationVersion = "3.1.1";

/// <summary>
/// The oldest version of Pinta for which add-ins built against it will still
Expand Down
146 changes: 136 additions & 10 deletions Pinta.Gui.Widgets/Widgets/Canvas/CanvasWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -67,9 +69,7 @@ public CanvasWindow (
scrollController.OnDecelerate += (_, _) => gestureZoom.IsActive (); // Cancel scroll deceleration when zooming

PintaCanvas canvas = new (
chrome,
tools,
this,
document,
canvasGrid
) {
Expand All @@ -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,
Expand All @@ -108,6 +117,7 @@ public CanvasWindow (
Focusable = true;

AddController (gestureZoom);
AddController (dragController);
AddController (motionController);

// --- Initialization (Gtk.Grid)
Expand All @@ -123,13 +133,16 @@ public CanvasWindow (

Canvas = canvas;

this.chrome = chrome;
this.tools = tools;
this.document = document;

scrolled_window = scrolledWindow;
gesture_zoom = gestureZoom;
horizontal_ruler = horizontalRuler;
vertical_ruler = verticalRuler;
motion_controller = motionController;
drag_controller = dragController;

// --- Further initialization

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
}
Loading
Loading