UI: support drawing booleans with icons as check-boxes
authorCampbell Barton <ideasman42@gmail.com>
Tue, 21 May 2019 04:39:09 +0000 (14:39 +1000)
committerCampbell Barton <ideasman42@gmail.com>
Tue, 21 May 2019 05:06:44 +0000 (15:06 +1000)
Previously, if a boolean happened to use an icon there was no way
to make it display as a check-box from Python scripts.

The previous logic meant we ended up having to edit the RNA.
Since booleans with icons don't work well with the split-property layout
(now used for most of the interface).
Icons were being removed from RNA then added back using awkward Python
ternary expressions in the interface scripts.

The toggle argument now has an unset state (-1).

- toggle=True: no checkbox (emboss).
- toggle=False: always use a checkbox (no icon).
- toggle=(unset/-1): depends on the icon status, default as before.

Since toggle=False was default, this isn't used in existing UI logic.

source/blender/editors/include/UI_interface.h
source/blender/editors/interface/interface.c
source/blender/editors/interface/interface_intern.h
source/blender/editors/interface/interface_layout.c
source/blender/makesrna/intern/rna_ui_api.c

index 5912a3a59e44b92d86aad9f6df05d4b52982209c..5efe5a7e07c8b5da12e860ed58c032623acec2ad 100644 (file)
@@ -1714,15 +1714,28 @@ enum {
   UI_ITEM_O_RETURN_PROPS = 1 << 0,
   UI_ITEM_R_EXPAND = 1 << 1,
   UI_ITEM_R_SLIDER = 1 << 2,
+  /**
+   * Use for booleans, causes the button to draw with an outline (emboss),
+   * instead of text with a checkbox.
+   * This is implied when toggle buttons have an icon
+   * unless #UI_ITEM_R_ICON_NEVER flag is set.
+   */
   UI_ITEM_R_TOGGLE = 1 << 3,
-  UI_ITEM_R_ICON_ONLY = 1 << 4,
-  UI_ITEM_R_EVENT = 1 << 5,
-  UI_ITEM_R_FULL_EVENT = 1 << 6,
-  UI_ITEM_R_NO_BG = 1 << 7,
-  UI_ITEM_R_IMMEDIATE = 1 << 8,
-  UI_ITEM_O_DEPRESS = 1 << 9,
-  UI_ITEM_R_COMPACT = 1 << 10,
-  UI_ITEM_R_CHECKBOX_INVERT = 1 << 11,
+  /**
+   * Don't attempt to use an icon when the icon is set to #ICON_NONE.
+   *
+   * Use for boolean's, causes the buttons to always show as a checkbox
+   * even when there is an icon (which would normally show the button as a toggle).
+   */
+  UI_ITEM_R_ICON_NEVER = 1 << 4,
+  UI_ITEM_R_ICON_ONLY = 1 << 5,
+  UI_ITEM_R_EVENT = 1 << 6,
+  UI_ITEM_R_FULL_EVENT = 1 << 7,
+  UI_ITEM_R_NO_BG = 1 << 8,
+  UI_ITEM_R_IMMEDIATE = 1 << 9,
+  UI_ITEM_O_DEPRESS = 1 << 10,
+  UI_ITEM_R_COMPACT = 1 << 11,
+  UI_ITEM_R_CHECKBOX_INVERT = 1 << 12,
 };
 
 #define UI_HEADER_OFFSET ((void)0, 0.4f * UI_UNIT_X)
index 8ff270bb6223b593ef9ca5676c7de882c81832d7..931a4faa1c031fdcffa0b3f8bf60f579d37e1c0a 100644 (file)
@@ -3738,6 +3738,16 @@ void ui_def_but_icon(uiBut *but, const int icon, const int flag)
   }
 }
 
+/**
+ * Avoid using this where possible since it's better not to ask for an icon in the first place.
+ */
+void ui_def_but_icon_clear(uiBut *but)
+{
+  but->icon = ICON_NONE;
+  but->flag &= ~UI_HAS_ICON;
+  but->drawflag &= ~UI_BUT_ICON_LEFT;
+}
+
 static void ui_def_but_rna__disable(uiBut *but, const char *info)
 {
   but->flag |= UI_BUT_DISABLED;
index 33288df15ba5ffabe521f2b90ebe7ebcd371f9a2..8a2b28ee2d46f4c0455018c9b7b0d343803b3c38 100644 (file)
@@ -500,6 +500,7 @@ extern int ui_but_string_get_max_length(uiBut *but);
 extern uiBut *ui_but_drag_multi_edit_get(uiBut *but);
 
 void ui_def_but_icon(uiBut *but, const int icon, const int flag);
+void ui_def_but_icon_clear(uiBut *but);
 extern uiButExtraIconType ui_but_icon_extra_get(uiBut *but);
 
 extern void ui_but_default_set(struct bContext *C, const bool all, const bool use_afterfunc);
index 9632bcd35e435b8f6a5773af3d4d380abaf784e2..a3906879fd79dd3e4d66ca3ebed63268836c97fc 100644 (file)
@@ -484,7 +484,7 @@ static void ui_item_array(uiLayout *layout,
                           int UNUSED(h),
                           bool expand,
                           bool slider,
-                          bool toggle,
+                          int toggle,
                           bool icon_only,
                           bool compact,
                           bool show_text)
@@ -691,7 +691,7 @@ static void ui_item_array(uiLayout *layout,
         if (slider && but->type == UI_BTYPE_NUM) {
           but->type = UI_BTYPE_NUM_SLIDER;
         }
-        if (toggle && but->type == UI_BTYPE_CHECKBOX) {
+        if ((toggle == 1) && but->type == UI_BTYPE_CHECKBOX) {
           but->type = UI_BTYPE_TOGGLE;
         }
         if ((a == 0) && (subtype == PROP_AXISANGLE)) {
@@ -1889,6 +1889,10 @@ void uiItemFullR(uiLayout *layout,
 
   const bool icon_only = (flag & UI_ITEM_R_ICON_ONLY) != 0;
 
+  /* Boolean with -1 to signify that the value depends on the presence of an icon. */
+  const int toggle = ((flag & UI_ITEM_R_TOGGLE) ? 1 : ((flag & UI_ITEM_R_ICON_NEVER) ? 0 : -1));
+  const bool no_icon = (toggle == 0);
+
   /* set name and icon */
   if (!name) {
     if (!icon_only) {
@@ -1903,10 +1907,6 @@ void uiItemFullR(uiLayout *layout,
     flag &= ~UI_ITEM_R_CHECKBOX_INVERT;
   }
 
-  if (icon == ICON_NONE) {
-    icon = RNA_property_ui_icon(prop);
-  }
-
   if (flag & UI_ITEM_R_ICON_ONLY) {
     /* pass */
   }
@@ -1931,51 +1931,60 @@ void uiItemFullR(uiLayout *layout,
     }
   }
 
-#ifdef UI_PROP_SEP_ICON_WIDTH_EXCEPTION
-  if (use_prop_sep) {
-    if (type == PROP_BOOLEAN && (icon == ICON_NONE) && !icon_only) {
-      use_prop_sep_split_label = false;
+  if (no_icon == false) {
+    if (icon == ICON_NONE) {
+      icon = RNA_property_ui_icon(prop);
     }
-  }
-#endif
 
-  /* menus and pie-menus don't show checkbox without this */
-  if ((layout->root->type == UI_LAYOUT_MENU) ||
-      /* use checkboxes only as a fallback in pie-menu's, when no icon is defined */
-      ((layout->root->type == UI_LAYOUT_PIEMENU) && (icon == ICON_NONE))) {
-    int prop_flag = RNA_property_flag(prop);
-    if (type == PROP_BOOLEAN && ((is_array == false) || (index != RNA_NO_INDEX))) {
-      if (prop_flag & PROP_ICONS_CONSECUTIVE) {
-        icon = ICON_CHECKBOX_DEHLT; /* but->iconadd will set to correct icon */
-      }
-      else if (is_array) {
-        icon = (RNA_property_boolean_get_index(ptr, prop, index)) ? ICON_CHECKBOX_HLT :
-                                                                    ICON_CHECKBOX_DEHLT;
+    /* Menus and pie-menus don't show checkbox without this. */
+    if ((layout->root->type == UI_LAYOUT_MENU) ||
+        /* Use checkboxes only as a fallback in pie-menu's, when no icon is defined. */
+        ((layout->root->type == UI_LAYOUT_PIEMENU) && (icon == ICON_NONE))) {
+      int prop_flag = RNA_property_flag(prop);
+      if (type == PROP_BOOLEAN) {
+        if ((is_array == false) || (index != RNA_NO_INDEX)) {
+          if (prop_flag & PROP_ICONS_CONSECUTIVE) {
+            icon = ICON_CHECKBOX_DEHLT; /* but->iconadd will set to correct icon */
+          }
+          else if (is_array) {
+            icon = (RNA_property_boolean_get_index(ptr, prop, index)) ? ICON_CHECKBOX_HLT :
+                                                                        ICON_CHECKBOX_DEHLT;
+          }
+          else {
+            icon = (RNA_property_boolean_get(ptr, prop)) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT;
+          }
+        }
       }
-      else {
-        icon = (RNA_property_boolean_get(ptr, prop)) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT;
+      else if (type == PROP_ENUM) {
+        if (index == RNA_ENUM_VALUE) {
+          int enum_value = RNA_property_enum_get(ptr, prop);
+          if (prop_flag & PROP_ICONS_CONSECUTIVE) {
+            icon = ICON_CHECKBOX_DEHLT; /* but->iconadd will set to correct icon */
+          }
+          else if (prop_flag & PROP_ENUM_FLAG) {
+            icon = (enum_value & value) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT;
+          }
+          else {
+            icon = (enum_value == value) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT;
+          }
+        }
       }
     }
-    else if (type == PROP_ENUM && index == RNA_ENUM_VALUE) {
-      int enum_value = RNA_property_enum_get(ptr, prop);
-      if (prop_flag & PROP_ICONS_CONSECUTIVE) {
-        icon = ICON_CHECKBOX_DEHLT; /* but->iconadd will set to correct icon */
-      }
-      else if (prop_flag & PROP_ENUM_FLAG) {
-        icon = (enum_value & value) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT;
-      }
-      else {
-        icon = (enum_value == value) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT;
-      }
+  }
+
+#ifdef UI_PROP_SEP_ICON_WIDTH_EXCEPTION
+  if (use_prop_sep) {
+    if (type == PROP_BOOLEAN && (icon == ICON_NONE) && !icon_only) {
+      use_prop_sep_split_label = false;
     }
   }
+#endif
 
   if ((type == PROP_ENUM) && (RNA_property_flag(prop) & PROP_ENUM_FLAG)) {
     flag |= UI_ITEM_R_EXPAND;
   }
 
   const bool slider = (flag & UI_ITEM_R_SLIDER) != 0;
-  const bool toggle = (flag & UI_ITEM_R_TOGGLE) != 0;
   const bool expand = (flag & UI_ITEM_R_EXPAND) != 0;
   const bool no_bg = (flag & UI_ITEM_R_NO_BG) != 0;
   const bool compact = (flag & UI_ITEM_R_COMPACT) != 0;
@@ -2164,7 +2173,7 @@ void uiItemFullR(uiLayout *layout,
       }
     }
 
-    if (toggle && but->type == UI_BTYPE_CHECKBOX) {
+    if ((toggle == 1) && but->type == UI_BTYPE_CHECKBOX) {
       but->type = UI_BTYPE_TOGGLE;
     }
 
@@ -2183,6 +2192,18 @@ void uiItemFullR(uiLayout *layout,
     }
   }
 
+  /* The resulting button may have the icon set since boolean button drawing
+   * is being 'helpful' and adding an icon for us.
+   * In this case we want the ability not to have an icon.
+   *
+   * We could pass an argument not to set the icon to begin with however this is the one case
+   * the functionality is needed.  */
+  if (but && no_icon) {
+    if ((icon == ICON_NONE) && (but->icon != ICON_NONE)) {
+      ui_def_but_icon_clear(but);
+    }
+  }
+
   /* Mark non-embossed textfields inside a listbox. */
   if (but && (block->flag & UI_BLOCK_LIST_ITEM) && (but->type == UI_BTYPE_TEXT) &&
       (but->dt & UI_EMBOSS_NONE)) {
index 5a001f05b5376f1e110fe3956bc839b3995f06c6..d50f97e88cac35ec5de995296abec3d3f6fce194 100644 (file)
@@ -95,7 +95,7 @@ static void rna_uiItemR(uiLayout *layout,
                         int icon,
                         bool expand,
                         bool slider,
-                        bool toggle,
+                        int toggle,
                         bool icon_only,
                         bool event,
                         bool full_event,
@@ -121,7 +121,12 @@ static void rna_uiItemR(uiLayout *layout,
 
   flag |= (slider) ? UI_ITEM_R_SLIDER : 0;
   flag |= (expand) ? UI_ITEM_R_EXPAND : 0;
-  flag |= (toggle) ? UI_ITEM_R_TOGGLE : 0;
+  if (toggle == 1) {
+    flag |= UI_ITEM_R_TOGGLE;
+  }
+  else if (toggle == 0) {
+    flag |= UI_ITEM_R_ICON_NEVER;
+  }
   flag |= (icon_only) ? UI_ITEM_R_ICON_ONLY : 0;
   flag |= (event) ? UI_ITEM_R_EVENT : 0;
   flag |= (full_event) ? UI_ITEM_R_FULL_EVENT : 0;
@@ -773,7 +778,17 @@ void RNA_api_ui_layout(StructRNA *srna)
   api_ui_item_common(func);
   RNA_def_boolean(func, "expand", false, "", "Expand button to show more detail");
   RNA_def_boolean(func, "slider", false, "", "Use slider widget for numeric values");
-  RNA_def_boolean(func, "toggle", false, "", "Use toggle widget for boolean values");
+  RNA_def_int(func,
+              "toggle",
+              -1,
+              -1,
+              1,
+              "",
+              "Use toggle widget for boolean values, "
+              "or a checkbox when disabled "
+              "(the default is -1 which uses toggle only when an icon is displayed)",
+              -1,
+              1);
   RNA_def_boolean(func, "icon_only", false, "", "Draw only icons in buttons, no text");
   RNA_def_boolean(func, "event", false, "", "Use button to input key events");
   RNA_def_boolean(