Add a macro to create bitsets from enums

Adds a RIVE_MAKE_ENUM_BITFIELD() macro and accompanying EnumBitset<> class that allow us to treat strongly typed C++ enums as bitsets, including with an implicit bool conversion for expressions like "if (flags & Flags::myFlag)".

Diffs=
654d4488e Add a macro to create bitsets from enums (#5922)

Co-authored-by: Chris Dalton <99840794+csmartdalton@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head
index e430605..5706491 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-7bbf083ce32d92ba240dbf08b852d2702fac18ca
+654d4488e936ac8c63930253b83229ef1c82618b
diff --git a/include/rive/component_dirt.hpp b/include/rive/component_dirt.hpp
index a6730f8..73f465e 100644
--- a/include/rive/component_dirt.hpp
+++ b/include/rive/component_dirt.hpp
@@ -1,6 +1,7 @@
 #ifndef _RIVE_DIRTY_FLAGS_HPP_
 #define _RIVE_DIRTY_FLAGS_HPP_
-#include <type_traits>
+
+#include "rive/enum_bitset.hpp"
 
 namespace rive
 {
@@ -62,52 +63,6 @@
     /// All dirty. Every flag (apart from Collapsed) is set.
     Filthy = 0xFFFE
 };
-
-inline constexpr ComponentDirt operator&(ComponentDirt lhs, ComponentDirt rhs)
-{
-    return static_cast<ComponentDirt>(static_cast<std::underlying_type<ComponentDirt>::type>(lhs) &
-                                      static_cast<std::underlying_type<ComponentDirt>::type>(rhs));
-}
-
-inline constexpr ComponentDirt operator^(ComponentDirt lhs, ComponentDirt rhs)
-{
-    return static_cast<ComponentDirt>(static_cast<std::underlying_type<ComponentDirt>::type>(lhs) ^
-                                      static_cast<std::underlying_type<ComponentDirt>::type>(rhs));
-}
-
-inline constexpr ComponentDirt operator|(ComponentDirt lhs, ComponentDirt rhs)
-{
-    return static_cast<ComponentDirt>(static_cast<std::underlying_type<ComponentDirt>::type>(lhs) |
-                                      static_cast<std::underlying_type<ComponentDirt>::type>(rhs));
-}
-
-inline constexpr ComponentDirt operator~(ComponentDirt rhs)
-{
-    return static_cast<ComponentDirt>(~static_cast<std::underlying_type<ComponentDirt>::type>(rhs));
-}
-
-inline ComponentDirt& operator|=(ComponentDirt& lhs, ComponentDirt rhs)
-{
-    lhs = static_cast<ComponentDirt>(static_cast<std::underlying_type<ComponentDirt>::type>(lhs) |
-                                     static_cast<std::underlying_type<ComponentDirt>::type>(rhs));
-
-    return lhs;
-}
-
-inline ComponentDirt& operator&=(ComponentDirt& lhs, ComponentDirt rhs)
-{
-    lhs = static_cast<ComponentDirt>(static_cast<std::underlying_type<ComponentDirt>::type>(lhs) &
-                                     static_cast<std::underlying_type<ComponentDirt>::type>(rhs));
-
-    return lhs;
-}
-
-inline ComponentDirt& operator^=(ComponentDirt& lhs, ComponentDirt rhs)
-{
-    lhs = static_cast<ComponentDirt>(static_cast<std::underlying_type<ComponentDirt>::type>(lhs) ^
-                                     static_cast<std::underlying_type<ComponentDirt>::type>(rhs));
-
-    return lhs;
-}
+RIVE_MAKE_ENUM_BITSET(ComponentDirt)
 } // namespace rive
-#endif
\ No newline at end of file
+#endif
diff --git a/include/rive/enum_bitset.hpp b/include/rive/enum_bitset.hpp
new file mode 100644
index 0000000..6461bdd
--- /dev/null
+++ b/include/rive/enum_bitset.hpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2023 Rive
+ */
+
+#ifndef _RIVE_ENUM_BITSET_HPP_
+#define _RIVE_ENUM_BITSET_HPP_
+
+#include <type_traits>
+
+namespace rive
+{
+// Wraps an enum containing a bitfield so that we can do implicit bool conversions.
+template <typename T> class EnumBitset
+{
+public:
+    using UnderlyingType = typename std::underlying_type<T>::type;
+    constexpr EnumBitset() = default;
+    constexpr EnumBitset(T value) : m_bits(static_cast<UnderlyingType>(value)) {}
+    constexpr UnderlyingType bits() const { return m_bits; }
+    constexpr operator T() const { return static_cast<T>(m_bits); }
+    constexpr operator bool() const { return m_bits != 0; }
+
+private:
+    UnderlyingType m_bits = 0;
+};
+} // namespace rive
+
+#define RIVE_MAKE_ENUM_BITSET(ENUM)                                                                \
+    constexpr inline ::rive::EnumBitset<ENUM> operator&(::rive::EnumBitset<ENUM> lhs,              \
+                                                        ::rive::EnumBitset<ENUM> rhs)              \
+    {                                                                                              \
+        return static_cast<ENUM>(lhs.bits() & rhs.bits());                                         \
+    }                                                                                              \
+    constexpr inline ::rive::EnumBitset<ENUM> operator&(ENUM lhs, ::rive::EnumBitset<ENUM> rhs)    \
+    {                                                                                              \
+        return ::rive::EnumBitset<ENUM>(lhs) & rhs;                                                \
+    }                                                                                              \
+    constexpr inline ::rive::EnumBitset<ENUM> operator&(::rive::EnumBitset<ENUM> lhs, ENUM rhs)    \
+    {                                                                                              \
+        return lhs & ::rive::EnumBitset<ENUM>(rhs);                                                \
+    }                                                                                              \
+    constexpr inline ::rive::EnumBitset<ENUM> operator&(ENUM lhs, ENUM rhs)                        \
+    {                                                                                              \
+        return ::rive::EnumBitset<ENUM>(lhs) & ::rive::EnumBitset<ENUM>(rhs);                      \
+    }                                                                                              \
+                                                                                                   \
+    constexpr inline ::rive::EnumBitset<ENUM> operator|(::rive::EnumBitset<ENUM> lhs,              \
+                                                        ::rive::EnumBitset<ENUM> rhs)              \
+    {                                                                                              \
+        return static_cast<ENUM>(lhs.bits() | rhs.bits());                                         \
+    }                                                                                              \
+    constexpr inline ::rive::EnumBitset<ENUM> operator|(ENUM lhs, ::rive::EnumBitset<ENUM> rhs)    \
+    {                                                                                              \
+        return ::rive::EnumBitset<ENUM>(lhs) | rhs;                                                \
+    }                                                                                              \
+    constexpr inline ::rive::EnumBitset<ENUM> operator|(::rive::EnumBitset<ENUM> lhs, ENUM rhs)    \
+    {                                                                                              \
+        return lhs | ::rive::EnumBitset<ENUM>(rhs);                                                \
+    }                                                                                              \
+    constexpr inline ::rive::EnumBitset<ENUM> operator|(ENUM lhs, ENUM rhs)                        \
+    {                                                                                              \
+        return ::rive::EnumBitset<ENUM>(lhs) | ::rive::EnumBitset<ENUM>(rhs);                      \
+    }                                                                                              \
+                                                                                                   \
+    constexpr inline ::rive::EnumBitset<ENUM> operator~(::rive::EnumBitset<ENUM> rhs)              \
+    {                                                                                              \
+        return static_cast<ENUM>(~rhs.bits());                                                     \
+    }                                                                                              \
+    constexpr inline ::rive::EnumBitset<ENUM> operator~(ENUM rhs)                                  \
+    {                                                                                              \
+        return ~::rive::EnumBitset<ENUM>(rhs);                                                     \
+    }                                                                                              \
+                                                                                                   \
+    inline ENUM& operator&=(ENUM& lhs, ::rive::EnumBitset<ENUM> rhs) { return lhs = lhs & rhs; }   \
+    inline ENUM& operator&=(ENUM& lhs, ENUM rhs) { return lhs = lhs & rhs; }                       \
+                                                                                                   \
+    inline ENUM& operator|=(ENUM& lhs, ::rive::EnumBitset<ENUM> rhs) { return lhs = lhs | rhs; }   \
+    inline ENUM& operator|=(ENUM& lhs, ENUM rhs) { return lhs = lhs | rhs; }
+
+#endif
diff --git a/include/rive/joystick.hpp b/include/rive/joystick.hpp
index e75556e..b781890 100644
--- a/include/rive/joystick.hpp
+++ b/include/rive/joystick.hpp
@@ -20,7 +20,7 @@
 
     bool isJoystickFlagged(JoystickFlags flag) const
     {
-        return (((JoystickFlags)joystickFlags()) & flag) == flag;
+        return ((JoystickFlags)joystickFlags()) & flag;
     }
 
     bool canApplyBeforeUpdate() const { return m_handleSource == nullptr; }
@@ -37,4 +37,4 @@
 };
 } // namespace rive
 
-#endif
\ No newline at end of file
+#endif
diff --git a/include/rive/joystick_flags.hpp b/include/rive/joystick_flags.hpp
index d3e73cd..f4b2534 100644
--- a/include/rive/joystick_flags.hpp
+++ b/include/rive/joystick_flags.hpp
@@ -1,5 +1,8 @@
 #ifndef _RIVE_JOYSTICK_FLAGS_HPP_
 #define _RIVE_JOYSTICK_FLAGS_HPP_
+
+#include "rive/enum_bitset.hpp"
+
 namespace rive
 {
 enum class JoystickFlags : unsigned char
@@ -13,52 +16,6 @@
     /// Whether this Joystick works in world space.
     worldSpace = 1 << 2
 };
-
-inline constexpr JoystickFlags operator&(JoystickFlags lhs, JoystickFlags rhs)
-{
-    return static_cast<JoystickFlags>(static_cast<std::underlying_type<JoystickFlags>::type>(lhs) &
-                                      static_cast<std::underlying_type<JoystickFlags>::type>(rhs));
-}
-
-inline constexpr JoystickFlags operator^(JoystickFlags lhs, JoystickFlags rhs)
-{
-    return static_cast<JoystickFlags>(static_cast<std::underlying_type<JoystickFlags>::type>(lhs) ^
-                                      static_cast<std::underlying_type<JoystickFlags>::type>(rhs));
-}
-
-inline constexpr JoystickFlags operator|(JoystickFlags lhs, JoystickFlags rhs)
-{
-    return static_cast<JoystickFlags>(static_cast<std::underlying_type<JoystickFlags>::type>(lhs) |
-                                      static_cast<std::underlying_type<JoystickFlags>::type>(rhs));
-}
-
-inline constexpr JoystickFlags operator~(JoystickFlags rhs)
-{
-    return static_cast<JoystickFlags>(~static_cast<std::underlying_type<JoystickFlags>::type>(rhs));
-}
-
-inline JoystickFlags& operator|=(JoystickFlags& lhs, JoystickFlags rhs)
-{
-    lhs = static_cast<JoystickFlags>(static_cast<std::underlying_type<JoystickFlags>::type>(lhs) |
-                                     static_cast<std::underlying_type<JoystickFlags>::type>(rhs));
-
-    return lhs;
-}
-
-inline JoystickFlags& operator&=(JoystickFlags& lhs, JoystickFlags rhs)
-{
-    lhs = static_cast<JoystickFlags>(static_cast<std::underlying_type<JoystickFlags>::type>(lhs) &
-                                     static_cast<std::underlying_type<JoystickFlags>::type>(rhs));
-
-    return lhs;
-}
-
-inline JoystickFlags& operator^=(JoystickFlags& lhs, JoystickFlags rhs)
-{
-    lhs = static_cast<JoystickFlags>(static_cast<std::underlying_type<JoystickFlags>::type>(lhs) ^
-                                     static_cast<std::underlying_type<JoystickFlags>::type>(rhs));
-
-    return lhs;
-}
+RIVE_MAKE_ENUM_BITSET(JoystickFlags)
 } // namespace rive
-#endif
\ No newline at end of file
+#endif
diff --git a/test/enum_bitset_test.cpp b/test/enum_bitset_test.cpp
new file mode 100644
index 0000000..b1fbd9f
--- /dev/null
+++ b/test/enum_bitset_test.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2023 Rive
+ */
+
+#include <catch.hpp>
+
+#include "rive/enum_bitset.hpp"
+
+namespace rive
+{
+enum class Flags : uint8_t
+{
+    zero = 0,
+    one = 1 << 0,
+    two = 1 << 1,
+    four = 1 << 2,
+    eight = 1 << 3,
+};
+RIVE_MAKE_ENUM_BITSET(Flags);
+
+// Check rive::EnumBitset<> operators
+TEST_CASE("enum-operators", "[enum_bitset]")
+{
+    Flags flags;
+
+    flags = Flags::one | Flags::two;
+    CHECK(flags == (Flags)3);
+
+    flags &= ~Flags::two;
+    CHECK(flags == Flags::one);
+
+    flags = Flags::two | (Flags::four | Flags::eight);
+    CHECK(flags == (Flags)14);
+
+    flags = (Flags::two | Flags::four) & ~(Flags::one | Flags::two);
+    CHECK(flags == Flags::four);
+
+    flags = (Flags::two | Flags::four) & (Flags::one | Flags::two);
+    CHECK(flags == Flags::two);
+    CHECK(flags & Flags::two);
+    CHECK(!(flags & Flags::one));
+    CHECK(flags & (Flags::two | Flags::eight));
+    CHECK(!(flags & (Flags::four | Flags::eight)));
+
+    // All & overloads.
+    CHECK(!(Flags::one & Flags::two));
+    CHECK(Flags::four & Flags::four);
+    CHECK((Flags::four & Flags::four) == Flags::four);
+    CHECK((Flags::one & (Flags::one | Flags::two)) == Flags::one);
+    CHECK(!(~Flags::one & Flags::one));
+
+    // All | overloads.
+    CHECK(!(Flags::zero | Flags::zero));
+    CHECK(Flags::zero | Flags::one);
+    CHECK((Flags::one | Flags::two) == (Flags)3);
+    CHECK((Flags::one | (Flags::two | Flags::four)) == (Flags)7);
+    CHECK(((Flags::one | Flags::two) | Flags::four) == (Flags)7);
+    CHECK(((Flags::one | Flags::two) | (Flags::four | Flags::eight)) == (Flags)15);
+
+    // All ~ overloads.
+    CHECK(~Flags::two == (Flags)(255 ^ 2));                      // Flags is a uint8_t
+    CHECK(~(Flags::two | Flags::eight) == (Flags)(255 ^ 2 ^ 8)); // Flags is a uint8_t
+
+    // All &= overloads.
+    flags = Flags::eight | Flags::four | Flags::two | Flags::one;
+    CHECK(flags == (Flags)15);
+    Flags inverseEight = ~Flags::eight;
+    flags &= inverseEight;
+    CHECK(flags == (Flags)7);
+    flags &= ~(Flags::four | Flags::one);
+    CHECK(flags == Flags::two);
+    flags &= Flags::two;
+    CHECK(flags == Flags::two);
+    flags &= Flags::one;
+    CHECK(flags == Flags::zero);
+
+    // All |= overloads.
+    flags = Flags::zero;
+    CHECK(flags == Flags::zero);
+    flags |= Flags::eight;
+    CHECK(flags == Flags::eight);
+    flags |= ~Flags::eight;
+    CHECK(flags == (Flags)255); // Flags is a uint8_t
+}
+} // namespace rive