Repository: napari/napari

Issue:
Remove toggle theme option
## 🐛 Bug

Since there can be more than 2 themes, this option should be removed.


<!-- A clear and concise description of what the bug is. -->
<img width="255" alt="Screen Shot 2021-03-22 at 10 54 03 AM" src="https://user-images.githubusercontent.com/3627835/112018841-eb5c0480-8afc-11eb-8fbc-4f392d431ecb.png">


## To Reproduce

Steps to reproduce the behavior:

1.
2.
3.

<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->

## Expected behavior

<!-- A clear and concise description of what you expected to happen. -->

## Environment

 - Please copy and paste the information at napari info option in help menubar here:

 - Any other relevant information:

## Additional context

<!-- Add any other context about the problem here. -->


@liaprins-czi your advice is greatly appreciated :) 


Discussion:
fwiw, the behavior there is currently more of a "cycle" theme, and it does work with multiple themes:

https://github.com/napari/napari/blob/80f8ef18d9645c52601a8c1fd1125601f4b3dac0/napari/components/viewer_model.py#L255-L259

maybe we could just change the misleading wording of the menu?
>fwiw, the behavior there is currently more of a "cycle" theme, and it does work with multiple themes:

For anything more than 2 themes, cycling might not be the best UI. In that case, if we want to expose that, we should probably use a submenu.
that's fine too (but I do also kinda like the hot key that lets me cycle).
@goanpeca can the hot key still cycle through them even if we add a new submenu for the themes? 
>can the hot key still cycle through them even if we add a new submenu for the themes?

It could but it would be very hard to have discoverability of such a feature.

Now the real UX question is, even if toggling and cycling is convenient with a shortcut, a theme is not something you are toggling every minute, it is something you select and then move on, so the quick access feature and ability to toggle it quickly with a shortcut does not seems to sync with the actual usage.
At a bare minimum it should be renamed `cycle theme` instead of toggle theme. I could see reasons for having a submenu, but probably wouldn't as it is now possible to set the theme in the preferences, and having the submenu then seems highly redundant.

> Now the real UX question is, even if toggling and cycling is convenient with a shortcut, a theme is not something you are toggling every minute, it is something you select and then move on, so the quick access feature and ability to toggle it quickly with a shortcut does not seems to sync with the actual usage.

I agree that most users are unlikely to do this, but I can see how it is useful for a GUI developer :-) Maybe we shouldn't be adding menu items/ keybindings like this just for developers though.

One question is would our custom keybindings dialog and keybindings preferences ever become so expressive that we could add a custom keybinding for this and store it in the preferences?

My current vote would probably be to drop the menu item (as redundant with preferences), but keep the keybinding, at least for now (as convenient). We do have a number of other keybindings that don't have menu items, but maybe it's not the best practice ...

Curious what others think

Resolving patch:
diff --git a/napari/_qt/qt_main_window.py b/napari/_qt/qt_main_window.py
--- a/napari/_qt/qt_main_window.py
+++ b/napari/_qt/qt_main_window.py
@@ -472,10 +472,6 @@ def _add_view_menu(self):
         toggle_visible.setShortcut('Ctrl+M')
         toggle_visible.setStatusTip(trans._('Hide Menubar'))
         toggle_visible.triggered.connect(self._toggle_menubar_visible)
-        toggle_theme = QAction(trans._('Toggle Theme'), self._qt_window)
-        toggle_theme.setShortcut('Ctrl+Shift+T')
-        toggle_theme.setStatusTip(trans._('Toggle theme'))
-        toggle_theme.triggered.connect(self.qt_viewer.viewer._toggle_theme)
         toggle_fullscreen = QAction(
             trans._('Toggle Full Screen'), self._qt_window
         )
@@ -490,7 +486,6 @@ def _add_view_menu(self):
         self.view_menu = self.main_menu.addMenu(trans._('&View'))
         self.view_menu.addAction(toggle_fullscreen)
         self.view_menu.addAction(toggle_visible)
-        self.view_menu.addAction(toggle_theme)
         self.view_menu.addAction(toggle_play)
         self.view_menu.addSeparator()
 
diff --git a/napari/components/_viewer_key_bindings.py b/napari/components/_viewer_key_bindings.py
--- a/napari/components/_viewer_key_bindings.py
+++ b/napari/components/_viewer_key_bindings.py
@@ -1,3 +1,5 @@
+from ..utils.settings import SETTINGS
+from ..utils.theme import available_themes
 from ..utils.translations import trans
 from .viewer_model import ViewerModel
 
@@ -28,6 +30,19 @@ def toggle_ndisplay(viewer):
 toggle_ndisplay.__doc__ = trans._("Toggle ndisplay.")
 
 
+@ViewerModel.bind_key('Control-Shift-T')
+def toggle_theme(viewer):
+    """Toggle theme for viewer"""
+    themes = available_themes()
+    current_theme = SETTINGS.appearance.theme
+    idx = themes.index(current_theme)
+    idx += 1
+    if idx == len(themes):
+        idx = 0
+
+    SETTINGS.appearance.theme = themes[idx]
+
+
 @ViewerModel.bind_key('Left')
 def increment_dims_left(viewer):
     """Increment dimensions slider to the left."""
diff --git a/napari/components/viewer_model.py b/napari/components/viewer_model.py
--- a/napari/components/viewer_model.py
+++ b/napari/components/viewer_model.py
@@ -253,12 +253,6 @@ def _update_layers(self, event=None, layers=None):
                 self.dims.point, self.dims.ndisplay, self.dims.order
             )
 
-    def _toggle_theme(self):
-        """Switch to next theme in list of themes"""
-        theme_names = available_themes()
-        cur_theme = theme_names.index(self.theme)
-        self.theme = theme_names[(cur_theme + 1) % len(theme_names)]
-
     def _update_active_layer(self, event):
         """Set the active layer by iterating over the layers list and
         finding the first selected layer. If multiple layers are selected the


Test patch:
diff --git a/napari/_tests/test_viewer.py b/napari/_tests/test_viewer.py
--- a/napari/_tests/test_viewer.py
+++ b/napari/_tests/test_viewer.py
@@ -20,7 +20,7 @@ def _get_all_keybinding_methods(type_):
 
 
 viewer_methods = _get_all_keybinding_methods(Viewer)
-EXPECTED_NUMBER_OF_VIEWER_METHODS = 18
+EXPECTED_NUMBER_OF_VIEWER_METHODS = 19
 
 
 def test_len_methods_viewer():