Repository: napari/napari

Issue:
Shapes Layer: Unable to draw horizontal/verticle line
## 🐛 Bug

It seems impossible to draw a horizontal or vertical line.
Perhaps related to:
https://github.com/napari/napari/pull/3259

## To Reproduce

Steps to reproduce the behavior:

1. Open napari
2. Add shapes layer
3. Try to draw a horizontal or tercile line

https://user-images.githubusercontent.com/76622105/168588334-68040e6d-2943-427e-9f92-7c66f644a627.mp4

You can see the line skipping from almost horizontal to the "other side" of horizontal. Same with vertical. I can never just make a flat or vertical line.

I thought maybe using shift would help, but that's actually...worse!
Holding shift when starting to draw yields lines fixed at the SE direction. 
(Pressing shift while drawing locks the current orientation).

https://user-images.githubusercontent.com/76622105/168588558-fee849f8-ce50-4221-bb12-e2daa8771df1.mp4


## Expected behavior

It should be possible to draw lines oriented at the 0°/180° or 90°/270° orientations free-hand.
Also, shift before drawing should "skip" through 45° steps all the way from 0° to 360°.

## Environment


```
napari: 0.4.16rc2.dev17+g0c148bb6
Platform: macOS-12.3.1-arm64-arm-64bit
System: MacOS 12.3.1
Python: 3.9.12 | packaged by conda-forge | (main, Mar 24 2022, 23:25:14) [Clang 12.0.1 ]
Qt: 5.15.2
PyQt5: 5.15.4
NumPy: 1.21.5
SciPy: 1.8.0
Dask: 2022.02.0
VisPy: 0.10.0

OpenGL:
- GL version: 2.1 Metal - 76.3
- MAX_TEXTURE_SIZE: 16384

Screens:
- screen 1: resolution 1680x1050, scale 2.0

Plugins:
- aicsimageio-in-memory: 0.6.1
- aicsimageio-out-of-memory: 0.6.1
- clEsperanto: 0.16.0
- console: 0.0.4
- napari-accelerated-pixel-and-object-classification: 0.6.8
- napari-assistant: 0.1.7
- napari-math: 0.0.1b0
- napari-ome-zarr: 0.4.0
- napari-sklearn-decomposition: 0.0.1
- napari-svg: 0.1.6
- napari-time-slicer: 0.4.3
- napari-tools-menu: 0.1.10
- napari_skimage_regionprops1: 0.4.0
- napari_skimage_regionprops2: 0.4.0
- natari: 0.2.7
- ome-types: 0.2.9
- scikit-image: 0.4.16rc2.dev17+g0c148bb6
- stardist-napari: 2022.4.8
```



Discussion:
Bizarre... I tried diving down, and all I could figure out is that `Shapes._selected_box` at some point becomes "blocked", but I could not find the point where it happens :/
Hi, not sure but maybe the code related to a threshold definition could be causing the blocked behavior? Seems like that is being defined in the logic that is handling shapes movement/resize at:  https://github.com/napari/napari/blob/a79537cac7c2a595c4fca41b85f2c7b4a44d0a61/napari/layers/shapes/_shapes_mouse_bindings.py#L453-L459



Resolving patch:
diff --git a/napari/layers/shapes/_shapes_mouse_bindings.py b/napari/layers/shapes/_shapes_mouse_bindings.py
--- a/napari/layers/shapes/_shapes_mouse_bindings.py
+++ b/napari/layers/shapes/_shapes_mouse_bindings.py
@@ -678,21 +678,21 @@ def _move_active_element_under_cursor(
             fixed = layer._fixed_vertex
             new = list(coord)
 
-            c = box[Box.CENTER]
+            box_center = box[Box.CENTER]
             if layer._fixed_aspect and layer._fixed_index % 2 == 0:
                 # corner
-                new = (box[vertex] - c) / np.linalg.norm(
-                    box[vertex] - c
-                ) * np.linalg.norm(new - c) + c
+                new = (box[vertex] - box_center) / np.linalg.norm(
+                    box[vertex] - box_center
+                ) * np.linalg.norm(new - box_center) + box_center
 
             if layer._fixed_index % 2 == 0:
                 # corner selected
-                scale = (inv_rot @ (new - fixed)) / (
+                drag_scale = (inv_rot @ (new - fixed)) / (
                     inv_rot @ (box[vertex] - fixed)
                 )
             elif layer._fixed_index % 4 == 3:
                 # top or bottom selected
-                scale = np.array(
+                drag_scale = np.array(
                     [
                         (inv_rot @ (new - fixed))[0]
                         / (inv_rot @ (box[vertex] - fixed))[0],
@@ -701,7 +701,7 @@ def _move_active_element_under_cursor(
                 )
             else:
                 # left or right selected
-                scale = np.array(
+                drag_scale = np.array(
                     [
                         1,
                         (inv_rot @ (new - fixed))[1]
@@ -710,22 +710,25 @@ def _move_active_element_under_cursor(
                 )
 
             # prevent box from shrinking below a threshold size
-            size = [
-                np.linalg.norm(box[Box.TOP_CENTER] - c),
-                np.linalg.norm(box[Box.LEFT_CENTER] - c),
-            ]
-            threshold = layer._vertex_size * layer.scale_factor / 2
-            scale[abs(scale * size) < threshold] = 1
+            size = (np.linalg.norm(box[Box.TOP_LEFT] - box_center),)
+            threshold = (
+                layer._vertex_size * layer.scale_factor / layer.scale[-1] / 2
+            )
+            if np.linalg.norm(size * drag_scale) < threshold:
+                drag_scale[:] = 1
+            # on vertical/horizontal drags we get scale of 0
+            # when we actually simply don't want to scale
+            drag_scale[drag_scale == 0] = 1
 
             # check orientation of box
             if abs(handle_offset_norm[0]) == 1:
                 for index in layer.selected_data:
                     layer._data_view.scale(
-                        index, scale, center=layer._fixed_vertex
+                        index, drag_scale, center=layer._fixed_vertex
                     )
-                layer._scale_box(scale, center=layer._fixed_vertex)
+                layer._scale_box(drag_scale, center=layer._fixed_vertex)
             else:
-                scale_mat = np.array([[scale[0], 0], [0, scale[1]]])
+                scale_mat = np.array([[drag_scale[0], 0], [0, drag_scale[1]]])
                 transform = rot @ scale_mat @ inv_rot
                 for index in layer.selected_data:
                     layer._data_view.shift(index, -layer._fixed_vertex)
@@ -747,9 +750,7 @@ def _move_active_element_under_cursor(
                 np.arctan2(fixed_offset[0], -fixed_offset[1])
             )
 
-            if np.linalg.norm(new_offset) < 1:
-                angle = 0
-            elif layer._fixed_aspect:
+            if layer._fixed_aspect:
                 angle = np.round(new_angle / 45) * 45 - fixed_angle
             else:
                 angle = new_angle - fixed_angle


Test patch: