Repository: napari/napari

Issue:
Events are not emitted when colors are changed manually
## 🐛 Bug

When the face color or the edge color of a points layer is changed from the layer control, the corresponding events are not emitted.

## To Reproduce

Steps to reproduce the behavior:

1. Add a points layer.
2. Run `viewer.layers[0].events.face_color.connect(lambda x: print(x))`
3. Change the face color in the layer control.

<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->

## Expected behavior

Events should be emitted as other properties.

## Environment

 - Please copy and paste the information at napari info option in help menubar here:

```
napari: 0.4.17
Platform: Windows-10-10.0.22621-SP0
Python: 3.9.16 | packaged by conda-forge | (main, Feb 1 2023, 21:28:38) [MSC v.1929 64 bit (AMD64)]
Qt: 5.15.2
PyQt5: 5.15.9
NumPy: 1.23.5
SciPy: 1.9.1
Dask: 2023.3.2
VisPy: 0.11.0
magicgui: 0.7.2
superqt: unknown
in-n-out: 0.1.7
app-model: 0.1.4
npe2: 0.6.2

OpenGL:
- GL version: 4.6.0 - Build 27.20.100.9621
- MAX_TEXTURE_SIZE: 16384

Screens:
- screen 1: resolution 1368x912, scale 2.0
```

 - Any other relevant information:

## Additional context

<!-- Add any other context about the problem here. -->



Discussion:

Resolving patch:
diff --git a/napari/_qt/layer_controls/qt_points_controls.py b/napari/_qt/layer_controls/qt_points_controls.py
--- a/napari/_qt/layer_controls/qt_points_controls.py
+++ b/napari/_qt/layer_controls/qt_points_controls.py
@@ -78,8 +78,8 @@ def __init__(self, layer) -> None:
         self.layer.events.out_of_slice_display.connect(
             self._on_out_of_slice_display_change
         )
-        self.layer.events.symbol.connect(self._on_symbol_change)
-        self.layer.events.size.connect(self._on_size_change)
+        self.layer.events.symbol.connect(self._on_current_symbol_change)
+        self.layer.events.size.connect(self._on_current_size_change)
         self.layer.events.current_edge_color.connect(
             self._on_current_edge_color_change
         )
@@ -92,6 +92,9 @@ def __init__(self, layer) -> None:
         self.layer._face.events.current_color.connect(
             self._on_current_face_color_change
         )
+        self.layer.events.current_symbol.connect(
+            self._on_current_symbol_change
+        )
         self.layer.events.editable.connect(self._on_editable_or_visible_change)
         self.layer.events.visible.connect(self._on_editable_or_visible_change)
         self.layer.text.events.visible.connect(self._on_text_visibility_change)
@@ -112,7 +115,7 @@ def __init__(self, layer) -> None:
         sld.setSingleStep(1)
         value = self.layer.current_size
         sld.setValue(int(value))
-        sld.valueChanged.connect(self.changeSize)
+        sld.valueChanged.connect(self.changeCurrentSize)
         self.sizeSlider = sld
 
         self.faceColorEdit = QColorSwatchEdit(
@@ -123,8 +126,8 @@ def __init__(self, layer) -> None:
             initial_color=self.layer.current_edge_color,
             tooltip=trans._('click to set current edge color'),
         )
-        self.faceColorEdit.color_changed.connect(self.changeFaceColor)
-        self.edgeColorEdit.color_changed.connect(self.changeEdgeColor)
+        self.faceColorEdit.color_changed.connect(self.changeCurrentFaceColor)
+        self.edgeColorEdit.color_changed.connect(self.changeCurrentEdgeColor)
 
         sym_cb = QComboBox()
         sym_cb.setToolTip(
@@ -143,7 +146,7 @@ def __init__(self, layer) -> None:
                 current_index = index
 
         sym_cb.setCurrentIndex(current_index)
-        sym_cb.currentTextChanged.connect(self.changeSymbol)
+        sym_cb.currentTextChanged.connect(self.changeCurrentSymbol)
         self.symbolComboBox = sym_cb
 
         self.outOfSliceCheckBox = QCheckBox()
@@ -244,7 +247,7 @@ def _on_mode_change(self, event):
         elif mode != Mode.TRANSFORM:
             raise ValueError(trans._("Mode not recognized {mode}", mode=mode))
 
-    def changeSymbol(self, text):
+    def changeCurrentSymbol(self, text):
         """Change marker symbol of the points on the layer model.
 
         Parameters
@@ -252,9 +255,10 @@ def changeSymbol(self, text):
         text : int
             Index of current marker symbol of points, eg: '+', '.', etc.
         """
-        self.layer.current_symbol = SYMBOL_TRANSLATION_INVERTED[text]
+        with self.layer.events.symbol.blocker(self._on_current_symbol_change):
+            self.layer.current_symbol = SYMBOL_TRANSLATION_INVERTED[text]
 
-    def changeSize(self, value):
+    def changeCurrentSize(self, value):
         """Change size of points on the layer model.
 
         Parameters
@@ -262,7 +266,10 @@ def changeSize(self, value):
         value : float
             Size of points.
         """
-        self.layer.current_size = value
+        with self.layer.events.current_size.blocker(
+            self._on_current_size_change
+        ):
+            self.layer.current_size = value
 
     def change_out_of_slice(self, state):
         """Toggleout of slice display of points layer.
@@ -273,7 +280,10 @@ def change_out_of_slice(self, state):
             Checkbox indicating whether to render out of slice.
         """
         # needs cast to bool for Qt6
-        self.layer.out_of_slice_display = bool(state)
+        with self.layer.events.out_of_slice_display.blocker(
+            self._on_out_of_slice_display_change
+        ):
+            self.layer.out_of_slice_display = bool(state)
 
     def change_text_visibility(self, state):
         """Toggle the visibility of the text.
@@ -283,29 +293,32 @@ def change_text_visibility(self, state):
         state : QCheckBox
             Checkbox indicating if text is visible.
         """
-        # needs cast to bool for Qt6
-        self.layer.text.visible = bool(state)
+        with self.layer.text.events.visible.blocker(
+            self._on_text_visibility_change
+        ):
+            # needs cast to bool for Qt6
+            self.layer.text.visible = bool(state)
 
     def _on_text_visibility_change(self):
-        """Receive layer model text visibiltiy change change event and update checkbox."""
-        with self.layer.text.events.visible.blocker():
+        """Receive layer model text visibiltiy change event and update checkbox."""
+        with qt_signals_blocked(self.textDispCheckBox):
             self.textDispCheckBox.setChecked(self.layer.text.visible)
 
     def _on_out_of_slice_display_change(self):
         """Receive layer model out_of_slice_display change event and update checkbox."""
-        with self.layer.events.out_of_slice_display.blocker():
+        with qt_signals_blocked(self.outOfSliceCheckBox):
             self.outOfSliceCheckBox.setChecked(self.layer.out_of_slice_display)
 
-    def _on_symbol_change(self):
+    def _on_current_symbol_change(self):
         """Receive marker symbol change event and update the dropdown menu."""
-        with self.layer.events.symbol.blocker():
+        with qt_signals_blocked(self.symbolComboBox):
             self.symbolComboBox.setCurrentIndex(
                 self.symbolComboBox.findData(self.layer.current_symbol.value)
             )
 
-    def _on_size_change(self):
+    def _on_current_size_change(self):
         """Receive layer model size change event and update point size slider."""
-        with self.layer.events.size.blocker():
+        with qt_signals_blocked(self.sizeSlider):
             value = self.layer.current_size
             min_val = min(value) if isinstance(value, list) else value
             max_val = max(value) if isinstance(value, list) else value
@@ -317,15 +330,19 @@ def _on_size_change(self):
                 self.sizeSlider.setValue(int(value))
 
     @Slot(np.ndarray)
-    def changeFaceColor(self, color: np.ndarray):
+    def changeCurrentFaceColor(self, color: np.ndarray):
         """Update face color of layer model from color picker user input."""
-        with self.layer.events.current_face_color.blocker():
+        with self.layer.events.current_face_color.blocker(
+            self._on_current_face_color_change
+        ):
             self.layer.current_face_color = color
 
     @Slot(np.ndarray)
-    def changeEdgeColor(self, color: np.ndarray):
+    def changeCurrentEdgeColor(self, color: np.ndarray):
         """Update edge color of layer model from color picker user input."""
-        with self.layer.events.current_edge_color.blocker():
+        with self.layer.events.current_edge_color.blocker(
+            self._on_current_edge_color_change
+        ):
             self.layer.current_edge_color = color
 
     def _on_current_face_color_change(self):
diff --git a/napari/layers/points/points.py b/napari/layers/points/points.py
--- a/napari/layers/points/points.py
+++ b/napari/layers/points/points.py
@@ -412,7 +412,9 @@ def __init__(
 
         self.events.add(
             size=Event,
+            current_size=Event,
             edge_width=Event,
+            current_edge_width=Event,
             edge_width_is_relative=Event,
             face_color=Event,
             current_face_color=Event,
@@ -421,6 +423,7 @@ def __init__(
             properties=Event,
             current_properties=Event,
             symbol=Event,
+            current_symbol=Event,
             out_of_slice_display=Event,
             n_dimensional=Event,
             highlight=Event,
@@ -803,6 +806,7 @@ def current_symbol(self, symbol: Union[None, float]) -> None:
         if self._update_properties and len(self.selected_data) > 0:
             self.symbol[list(self.selected_data)] = symbol
             self.events.symbol()
+        self.events.current_symbol()
 
     @property
     def size(self) -> np.ndarray:
@@ -852,6 +856,7 @@ def current_size(self, size: Union[None, float]) -> None:
                 self.size[i, :] = (self.size[i, :] > 0) * size
             self.refresh()
             self.events.size()
+        self.events.current_size()
 
     @property
     def antialiasing(self) -> float:
@@ -971,6 +976,7 @@ def current_edge_width(self, edge_width: Union[None, float]) -> None:
                 self.edge_width[i] = (self.edge_width[i] > 0) * edge_width
             self.refresh()
             self.events.edge_width()
+        self.events.current_edge_width()
 
     @property
     def edge_color(self) -> np.ndarray:


Test patch: