├── .editorconfig ├── .meta └── imgui_toggle_example.gif ├── EXAMPLE.md ├── LICENSE ├── README.md ├── imgui_offset_rect.h ├── imgui_toggle.cpp ├── imgui_toggle.h ├── imgui_toggle_math.h ├── imgui_toggle_palette.cpp ├── imgui_toggle_palette.h ├── imgui_toggle_presets.cpp ├── imgui_toggle_presets.h ├── imgui_toggle_renderer.cpp └── imgui_toggle_renderer.h /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | end_of_line = crlf 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /.meta/imgui_toggle_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmdwtf/imgui_toggle/2b95aac1732058521d505d6e506f12aa1a331f90/.meta/imgui_toggle_example.gif -------------------------------------------------------------------------------- /EXAMPLE.md: -------------------------------------------------------------------------------- 1 | # 🔘 imgui_toggle Example 2 | 3 | This is a more elaborate example of how to use most of the features of `imgui_toggle`. 4 | 5 | ```cpp 6 | static void imgui_toggle_example(); 7 | static void imgui_toggle_simple(); 8 | static void imgui_toggle_custom(); 9 | static void imgui_toggle_state(const ImGuiToggleConfig& config, ImGuiToggleStateConfig& state); 10 | 11 | static void imgui_toggle_example() 12 | { 13 | // use some lovely gray backgrounds for "off" toggles 14 | // the default would otherwise use your theme's frame background colors. 15 | ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.45f, 0.45f, 0.45f, 1.0f)); 16 | ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.65f, 0.65f, 0.65f, 1.0f)); 17 | 18 | // a toggle that will allow the user to view the demo for simple toggles or a custom toggle 19 | static bool show_custom_toggle = true; 20 | ImGui::Toggle( 21 | show_custom_toggle ? "Showing Custom Toggle" : "Showing Simple Toggles" 22 | , &show_custom_toggle); 23 | 24 | ImGui::Separator(); 25 | 26 | if (show_custom_toggle) 27 | { 28 | imgui_toggle_custom(); 29 | } 30 | else 31 | { 32 | imgui_toggle_simple(); 33 | } 34 | 35 | // pop the color styles 36 | ImGui::PopStyleColor(2); 37 | } 38 | 39 | static void imgui_toggle_simple() 40 | { 41 | static bool values[] = { true, true, true, true, true, true, true, true }; 42 | size_t value_index = 0; 43 | 44 | const ImVec4 green(0.16f, 0.66f, 0.45f, 1.0f); 45 | const ImVec4 green_hover(0.0f, 1.0f, 0.57f, 1.0f); 46 | const ImVec4 salmon(1.0f, 0.43f, 0.35f, 1.0f); 47 | const ImVec4 green_shadow(0.0f, 1.0f, 0.0f, 0.4f); 48 | 49 | // a default and default animated toggle 50 | ImGui::Toggle("Default Toggle", &values[value_index++]); 51 | ImGui::Toggle("Animated Toggle", &values[value_index++], ImGuiToggleFlags_Animated); 52 | 53 | // this toggle draws a simple border around it's frame and knob 54 | ImGui::Toggle("Bordered Knob", &values[value_index++], ImGuiToggleFlags_Bordered, 1.0f); 55 | 56 | // this toggle draws a simple shadow around it's frame and knob 57 | ImGui::PushStyleColor(ImGuiCol_BorderShadow, green_shadow); 58 | ImGui::Toggle("Shadowed Knob", &values[value_index++], ImGuiToggleFlags_Shadowed, 1.0f); 59 | 60 | // this toggle draws the shadow & and the border around it's frame and knob. 61 | ImGui::Toggle("Bordered + Shadowed Knob", &values[value_index++], ImGuiToggleFlags_Bordered | ImGuiToggleFlags_Shadowed, 1.0f); 62 | ImGui::PopStyleColor(1); 63 | 64 | // this toggle uses stack-pushed style colors to change the way it displays 65 | ImGui::PushStyleColor(ImGuiCol_Button, green); 66 | ImGui::PushStyleColor(ImGuiCol_ButtonHovered, green_hover); 67 | ImGui::Toggle("Green Toggle", &values[value_index++]); 68 | ImGui::PopStyleColor(2); 69 | 70 | ImGui::Toggle("Toggle with A11y Labels", &values[value_index++], ImGuiToggleFlags_A11y); 71 | 72 | // this toggle shows no label 73 | ImGui::Toggle("##Toggle With Hidden Label", &values[value_index++]); 74 | } 75 | 76 | static void imgui_toggle_custom() 77 | { 78 | static ImGuiToggleConfig config; 79 | static bool toggle_custom = true; 80 | 81 | ImGui::NewLine(); 82 | 83 | ImGui::Toggle("Customized Toggle", &toggle_custom, config); 84 | 85 | ImGui::NewLine(); 86 | 87 | // these first settings are used no matter the toggle's state. 88 | ImGui::Text("Persistent Toggle Settings"); 89 | 90 | // animation duration controls how long the toggle animates, in seconds. if set to 0, animation is disabled. 91 | if (ImGui::SliderFloat("Animation Duration (seconds)", &config.AnimationDuration, ImGuiToggleConstants::AnimationDurationMinimum, 2.0f)) 92 | { 93 | // if the user adjusted the animation duration slider, go ahead and turn on the animation flags. 94 | config.Flags |= ImGuiToggleFlags_Animated; 95 | } 96 | 97 | // frame rounding sets how round the frame is when drawn, where 0 is a rectangle, and 1 is a circle. 98 | ImGui::SliderFloat("Frame Rounding (scale)", &config.FrameRounding, ImGuiToggleConstants::FrameRoundingMinimum, ImGuiToggleConstants::FrameRoundingMaximum); 99 | 100 | // knob rounding sets how round the knob is when drawn, where 0 is a rectangle, and 1 is a circle. 101 | ImGui::SliderFloat("Knob Rounding (scale)", &config.KnobRounding, ImGuiToggleConstants::KnobRoundingMinimum, ImGuiToggleConstants::KnobRoundingMaximum); 102 | 103 | // size controls the width and the height of the toggle frame 104 | ImGui::SliderFloat2("Size (px: w, h)", &config.Size.x, 0.0f, 200.0f, "%.0f"); 105 | 106 | // width ratio sets how wide the toggle is with relation to the frame height. if Size is non-zero, this is unused. 107 | ImGui::SliderFloat("Width Ratio (scale)", &config.WidthRatio, ImGuiToggleConstants::WidthRatioMinimum, ImGuiToggleConstants::WidthRatioMaximum); 108 | 109 | // a11y style sets the type of additional on/off indicator drawing 110 | if (ImGui::Combo("A11y Style", &config.A11yStyle, 111 | "Label\0" 112 | "Glyph\0" 113 | "Dot\0" 114 | "\0")) 115 | { 116 | // if the user adjusted the a11y style combo, go ahead and turn on the a11y flag. 117 | config.Flags |= ImGuiToggleFlags_A11y; 118 | } 119 | 120 | // some tabs to adjust the "state" settings of the toggle (configuration dependent on if the toggle is on or off.) 121 | if (ImGui::BeginTabBar("State")) 122 | { 123 | if (ImGui::BeginTabItem("\"Off State\" Settings")) 124 | { 125 | imgui_toggle_state(config, config.Off); 126 | ImGui::EndTabItem(); 127 | } 128 | 129 | if (ImGui::BeginTabItem("\"On State\"Settings")) 130 | { 131 | imgui_toggle_state(config, config.On); 132 | ImGui::EndTabItem(); 133 | } 134 | 135 | ImGui::EndTabBar(); 136 | } 137 | 138 | ImGui::Separator(); 139 | 140 | // flags for various toggle features 141 | ImGui::Text("Flags"); 142 | ImGui::Columns(2); 143 | ImGui::Text("Meta Flags"); 144 | ImGui::NextColumn(); 145 | ImGui::Text("Individual Flags"); 146 | ImGui::Separator(); 147 | ImGui::NextColumn(); 148 | 149 | // should the toggle have borders (sets all border flags) 150 | ImGui::CheckboxFlags("Bordered", &config.Flags, ImGuiToggleFlags_Bordered); 151 | 152 | // should the toggle have shadows (sets all shadow flags) 153 | ImGui::CheckboxFlags("Shadowed", &config.Flags, ImGuiToggleFlags_Shadowed); 154 | 155 | ImGui::NextColumn(); 156 | 157 | // should the toggle animate 158 | ImGui::CheckboxFlags("Animated", &config.Flags, ImGuiToggleFlags_Animated); 159 | 160 | // should the toggle have a bordered frame 161 | ImGui::CheckboxFlags("BorderedFrame", &config.Flags, ImGuiToggleFlags_BorderedFrame); 162 | 163 | // should the toggle have a bordered knob 164 | ImGui::CheckboxFlags("BorderedKnob", &config.Flags, ImGuiToggleFlags_BorderedKnob); 165 | 166 | // should the toggle have a shadowed frame 167 | ImGui::CheckboxFlags("ShadowedFrame", &config.Flags, ImGuiToggleFlags_ShadowedFrame); 168 | 169 | // should the toggle have a shadowed knob 170 | ImGui::CheckboxFlags("ShadowedKnob", &config.Flags, ImGuiToggleFlags_ShadowedKnob); 171 | 172 | // should the toggle draw a11y glyphs 173 | ImGui::CheckboxFlags("A11y", &config.Flags, ImGuiToggleFlags_A11y); 174 | ImGui::Columns(); 175 | 176 | ImGui::Separator(); 177 | 178 | // what follows are some configuration presets. check the source of those functions to see how they work. 179 | ImGui::Text("Configuration Style Presets"); 180 | 181 | if (ImGui::Button("Reset to Default")) 182 | { 183 | config = ImGuiTogglePresets::DefaultStyle(); 184 | } 185 | ImGui::SameLine(); 186 | 187 | if (ImGui::Button("Rectangle")) 188 | { 189 | config = ImGuiTogglePresets::RectangleStyle(); 190 | } 191 | ImGui::SameLine(); 192 | 193 | if (ImGui::Button("Glowing")) 194 | { 195 | config = ImGuiTogglePresets::GlowingStyle(); 196 | } 197 | ImGui::SameLine(); 198 | 199 | if (ImGui::Button("iOS")) 200 | { 201 | config = ImGuiTogglePresets::iOSStyle(); 202 | } 203 | ImGui::SameLine(); 204 | 205 | if (ImGui::Button("Material")) 206 | { 207 | config = ImGuiTogglePresets::MaterialStyle(); 208 | } 209 | ImGui::SameLine(); 210 | 211 | if (ImGui::Button("Minecraft")) 212 | { 213 | config = ImGuiTogglePresets::MinecraftStyle(); 214 | } 215 | } 216 | 217 | static void imgui_toggle_state(const ImGuiToggleConfig& config, ImGuiToggleStateConfig& state) 218 | { 219 | // some values to use for slider limits 220 | const float border_thickness_max_pixels = 50.0f; 221 | const float max_height = config.Size.y > 0 ? config.Size.y : ImGui::GetFrameHeight(); 222 | const float half_max_height = max_height * 0.5f; 223 | 224 | // knob offset controls how far into or out of the frame the knob should draw. 225 | ImGui::SliderFloat2("Knob Offset (px: x, y)", &state.KnobOffset.x, -half_max_height, half_max_height); 226 | 227 | // knob inset controls how many pixels the knob is set into the frame. negative values will cause it to grow outside the frame. 228 | // for circular knobs, we will just use a single value, while for we will use top/left/bottom/right offsets. 229 | const bool is_rounded = config.KnobRounding >= 1.0f; 230 | if (is_rounded) 231 | { 232 | float inset_average = state.KnobInset.GetAverage(); 233 | ImGui::SliderFloat("Knob Inset (px)", &inset_average, -half_max_height, half_max_height); 234 | state.KnobInset = inset_average; 235 | } 236 | else 237 | { 238 | ImGui::SliderFloat4("Knob Inset (px: t, l, b, r)", state.KnobInset.Offsets, -half_max_height, half_max_height); 239 | } 240 | 241 | // how thick should the frame border be (if enabled) 242 | ImGui::SliderFloat("Frame Border Thickness (px)", &state.FrameBorderThickness, 0.0f, border_thickness_max_pixels); 243 | 244 | // how thick should the knob border be (if enabled) 245 | ImGui::SliderFloat("Knob Border Thickness (px)", &state.KnobBorderThickness, 0.0f, border_thickness_max_pixels); 246 | } 247 | ``` 248 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2022 nitz — chris marc dailey https://cmd.wtf 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 7 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 8 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 9 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 10 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 11 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 12 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🔘 imgui_toggle 2 | 3 | A small Dear ImGui widget that implements a modern toggle style switch. Requires C++11. 4 | 5 | ## Overview 6 | 7 | Based on the discussion in [https://github.com/ocornut/imgui/issues/1537](https://github.com/ocornut/imgui/issues/1537), and on the implementation of `ImGui::Checkbox()`, 8 | this small widget collection implements a customizable "Toggle" style button for Dear ImGui. A toggle represents a boolean on/off much like a checkbox, but is better suited 9 | to some particular paradigms depending on the UI designer's goals. It can often more clearly indicate an enabled/disabled state. 10 | 11 | `imgui_toggle` also supports an optional small animation, similar to that seen in mobile OS and web applications, which can further aid in user feedback. 12 | 13 | Internally, `imgui_toggle` functions very similarly to `ImGui::Checkbox()`, with the exception that it activates on mouse down rather than the release. It supports drawing 14 | a label in the same way, and toggling the value by clicking that associated label. The label can be hidden [as on other controls](https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-how-can-i-have-widgets-with-an-empty-label). 15 | 16 | ## Preview 17 | 18 | ![`imgui_toggle` example animated gif](./.meta/imgui_toggle_example.gif) 19 | 20 | _An example of `imgui_toggle`, produced by the [example code](./EXAMPLE.md), as an animated gif._ 21 | 22 | ## Usage 23 | 24 | Add the `*.cpp` and `*.h` files to your project, and include `imgui_toggle.h` in the source file you wish to use toggles. 25 | 26 | See `imgui_toggle.h` for the API, or below for a brief example. As well, [EXAMPLE.md](./EXAMPLE.md) has a more thorough usage example. 27 | 28 | ```cpp 29 | 30 | const ImVec4 green(0.16f, 0.66f, 0.45f, 1.0f); 31 | const ImVec4 green_hover(0.0f, 1.0f, 0.57f, 1.0f); 32 | 33 | const ImVec4 gray_dim(0.45f, 0.45f, 0.45f, 1.0f); 34 | const ImVec4 gray(0.65f, 0.65f, 0.65f, 1.0f); 35 | 36 | // use some lovely gray backgrounds for "off" toggles 37 | // the default will use your theme's frame background colors. 38 | ImGui::PushStyleColor(ImGuiCol_FrameBg, gray_dim); 39 | ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, gray); 40 | 41 | static bool values[] = { true, true, true, true, true, true, true, true }; 42 | size_t value_index = 0; 43 | 44 | const ImVec4 green(0.16f, 0.66f, 0.45f, 1.0f); 45 | const ImVec4 green_hover(0.0f, 1.0f, 0.57f, 1.0f); 46 | const ImVec4 salmon(1.0f, 0.43f, 0.35f, 1.0f); 47 | const ImVec4 green_shadow(0.0f, 1.0f, 0.0f, 0.4f); 48 | 49 | // a default and default animated toggle 50 | ImGui::Toggle("Default Toggle", &values[value_index++]); 51 | ImGui::Toggle("Animated Toggle", &values[value_index++], ImGuiToggleFlags_Animated); 52 | 53 | // this toggle draws a simple border around it's frame and knob 54 | ImGui::Toggle("Bordered Knob", &values[value_index++], ImGuiToggleFlags_Bordered, 1.0f); 55 | 56 | // this toggle draws a simple shadow around it's frame and knob 57 | ImGui::PushStyleColor(ImGuiCol_BorderShadow, green_shadow); 58 | ImGui::Toggle("Shadowed Knob", &values[value_index++], ImGuiToggleFlags_Shadowed, 1.0f); 59 | 60 | // this toggle draws the shadow & and the border around it's frame and knob. 61 | ImGui::Toggle("Bordered + Shadowed Knob", &values[value_index++], ImGuiToggleFlags_Bordered | ImGuiToggleFlags_Shadowed, 1.0f); 62 | ImGui::PopStyleColor(1); 63 | 64 | // this toggle uses stack-pushed style colors to change the way it displays 65 | ImGui::PushStyleColor(ImGuiCol_Button, green); 66 | ImGui::PushStyleColor(ImGuiCol_ButtonHovered, green_hover); 67 | ImGui::Toggle("Green Toggle", &values[value_index++]); 68 | ImGui::PopStyleColor(2); 69 | 70 | ImGui::Toggle("Toggle with A11y Labels", &values[value_index++], ImGuiToggleFlags_A11y); 71 | 72 | // this toggle shows no label 73 | ImGui::Toggle("##Toggle With Hidden Label", &values[value_index++]); 74 | 75 | // pop the FrameBg/FrameBgHover color styles 76 | ImGui::PopStyleColor(2); 77 | ``` 78 | 79 | ## Styling 80 | 81 | While `imgui_toggle` maintains a simple API for quick and easy toggles, a more complex one exists to allow the user to better customize the widget. 82 | 83 | By using the overload that takes a `const ImGuiToggleConfig&`, a structure can be provided that provides a multitude of configuration parameters. 84 | 85 | Notably this also allows providing a pointer to a `ImGuiTogglePalette` structure, which allows changing all the colors used to draw the widget. However, this method of configuration is not strictly necessary, as `imgui_toggle` will follow your theme colors as defined below if no palette or color replacement is specified. 86 | 87 | ### Theme Colors 88 | 89 | Since `imgui_toggle` isn't part of Dear ImGui proper, it doesn't have any direct references in `ImGuiCol_` for styling. `imgui_toggle` in addition to the method described above, you can use `ImGui::PushStyleColor()` and `ImGui::PopStyleColor()` to adjust the following theme colors around your call to `ImGui::Toggle()`: 90 | 91 | - `ImGuiCol_Text`: Will be used as the color of the knob portion of the toggle, and the color of the accessibility glyph if enabled and "on". 92 | - `ImGuiCol_Button`: Will be used as the frame color of the toggle when it is in the "on" position, and the widget is not hovered. 93 | - `ImGuiCol_ButtonHovered`: Will be used as the frame color of the toggle when it is in the "on" position, and the widget is hovered over. 94 | - `ImGuiCol_FrameBg`: Will be used as the frame color of the toggle when it is in the "off" position, and the widget is not hovered. Also used for the color of the accessibility glyph if enabled and "off". 95 | - `ImGuiCol_FrameBgHovered`: Will be used as the frame color of the toggle when it is in the "off" position, and the widget is hovered over. 96 | - `ImGuiCol_Border`: Will be used as the color drawn as the border on the frame and knob if the related flags are passed. 97 | 98 | Unfortunately, the dark gray and light gray used while the toggle is in the "off" position are currently defined by the widget code itself and not by any theme color. 99 | 100 | ## Future Considerations 101 | 102 | As brought up by [ocornut](https://github.com/ocornut/imgui/issues/1537#issuecomment-355562097), if `imgui_toggle` were to be part of mainline Dear ImGui in the future, 103 | there are some questions that should likely be answered. Most notably for me are the following: 104 | 105 | - Should the toggle get it's own enums in the style system? 106 | - If so, should which colors should it define, and which would it be okay sharing? 107 | - If I were choosing, I feel the button and hovered styles as the "on" coloring are acceptable, and perhaps adding three more shared styles `ImGuiCol_Knob`, `ImGuiCol_FrameBgOff`, and `ImGuiCol_FrameBgOffHover` for use as the foreground knob color, and the "off" background states. (An `Active` may be needed too if switching to operate on input release instead of press.) 108 | - Is the rendering quality good enough? 109 | - Is the dependence on the `LastActiveIdTimer` acceptable for animation, and the user must accept that clicking quickly would skip previous animations? 110 | - Should the toggle behave *exactly* like `ImGui::Checkbox()`, toggling on release rather than press? 111 | 112 | ---- 113 | 114 | ## 📝 License 115 | 116 | `imgui_toggle` is [licensed](./LICENSE) under the Zero-Clause BSD License (SPDX-License-Identifier: 0BSD). If you're interested in `imgui_toggle` under other terms, please contact the author. 117 | 118 | Copyright © 2022 [Chris Marc Dailey](https://cmd.wtf) 119 | 120 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. 121 | 122 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 123 | 124 | ## Acknowledgements 125 | 126 | `imgui_toggle` drew inspiration from [ocornut's original share](https://github.com/ocornut/imgui/issues/1537#issuecomment-355562097), 127 | his [animated variant](https://github.com/ocornut/imgui/issues/1537#issuecomment-355569554), [nerdtronik's shift to theme colors](https://github.com/ocornut/imgui/issues/1537#issuecomment-780262461), 128 | and [ashifolfi's squircle](https://github.com/ocornut/imgui/issues/1537#issuecomment-1272612641) concept. Inspiration for border drawing came from [moebiussurfing](https://github.com/cmdwtf/imgui_toggle/issues/1#issue-1441329209). 129 | 130 | As well, inspiration was derived from [Dear ImGui's current `Checkbox` implementation](https://github.com/ocornut/imgui/blob/529cba19b09cf6db206de2b9eaa3152ecb2feff8/imgui_widgets.cpp#L1102), 131 | for the behavior, label drawing, and hopefully preparing to [handle mixed values/indeterminate states](https://github.com/ocornut/imgui/issues/2644) (albeit unsupported as of yet). 132 | -------------------------------------------------------------------------------- /imgui_offset_rect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | 5 | // Helper: ImOffsetRect A set of offsets to apply to an ImRect. 6 | struct IMGUI_API ImOffsetRect 7 | { 8 | union 9 | { 10 | float Offsets[4]; 11 | 12 | struct 13 | { 14 | float Top; 15 | float Left; 16 | float Bottom; 17 | float Right; 18 | }; 19 | }; 20 | 21 | constexpr ImOffsetRect() : Top(0.0f), Left(0.0f), Bottom(0.0f), Right(0.0f) {} 22 | constexpr ImOffsetRect(const ImVec2& topLeft, const ImVec2& bottomRight) : ImOffsetRect(topLeft.y, topLeft.x, bottomRight.y, bottomRight.x) {} 23 | constexpr ImOffsetRect(const ImVec4& v) : ImOffsetRect(v.x, v.y, v.z, v.w) {} 24 | constexpr ImOffsetRect(float top, float left, float bottom, float right) : Top(top), Left(left), Bottom(bottom), Right(right) {} 25 | constexpr ImOffsetRect(float all) : Top(all), Left(all), Bottom(all), Right(all) {} 26 | 27 | ImVec2 GetSize() const { return ImVec2(Left + Right, Top + Bottom); } 28 | float GetWidth() const { return Left + Right; } 29 | float GetHeight() const { return Top + Bottom; } 30 | float GetAverage() const { return (Top + Left + Bottom + Right) / 4.0f; } 31 | ImOffsetRect MirrorHorizontally() const { return ImOffsetRect(Top, Right, Bottom, Left); } 32 | ImOffsetRect MirrorVertically() const { return ImOffsetRect(Bottom, Left, Top, Right); } 33 | ImOffsetRect Mirror() const { return ImOffsetRect(Bottom, Right, Top, Left); } 34 | }; 35 | 36 | // Helpers: ImOffsetRect operators 37 | IM_MSVC_RUNTIME_CHECKS_OFF 38 | static inline ImOffsetRect operator+(const ImOffsetRect& lhs, const ImOffsetRect& rhs) { return ImOffsetRect(lhs.Top + rhs.Top, lhs.Left + rhs.Left, lhs.Bottom + rhs.Bottom, lhs.Right + rhs.Right); } 39 | static inline ImOffsetRect operator-(const ImOffsetRect& lhs, const ImOffsetRect& rhs) { return ImOffsetRect(lhs.Top - rhs.Top, lhs.Left - rhs.Left, lhs.Bottom - rhs.Bottom, lhs.Right - rhs.Right); } 40 | static inline ImOffsetRect operator*(const ImOffsetRect& lhs, const ImOffsetRect& rhs) { return ImOffsetRect(lhs.Top * rhs.Top, lhs.Left * rhs.Left, lhs.Bottom * rhs.Bottom, lhs.Right * rhs.Right); } 41 | IM_MSVC_RUNTIME_CHECKS_RESTORE 42 | -------------------------------------------------------------------------------- /imgui_toggle.cpp: -------------------------------------------------------------------------------- 1 | #ifndef IMGUI_DEFINE_MATH_OPERATORS 2 | #define IMGUI_DEFINE_MATH_OPERATORS 3 | #endif // IMGUI_DEFINE_MATH_OPERATORS 4 | 5 | #include "imgui_toggle.h" 6 | #include "imgui.h" 7 | 8 | #include "imgui_toggle_math.h" 9 | #include "imgui_toggle_palette.h" 10 | #include "imgui_toggle_renderer.h" 11 | 12 | 13 | using namespace ImGuiToggleConstants; 14 | using namespace ImGuiToggleMath; 15 | 16 | namespace 17 | { 18 | bool ToggleInternal(const char* label, bool* value, const ImGuiToggleConfig& config); 19 | 20 | // sets the given config structure's values to the 21 | // default ones used by the `Toggle()` overloads. 22 | inline void SetToAliasDefaults(ImGuiToggleConfig& config) 23 | { 24 | config.Flags = ImGuiToggleFlags_Default; 25 | config.AnimationDuration = AnimationDurationDisabled; 26 | config.FrameRounding = FrameRoundingDefault; 27 | config.KnobRounding = KnobRoundingDefault; 28 | } 29 | 30 | // thread-local data for the `Toggle()` functions to easily call `ToggleInternal()`. 31 | static thread_local ImGuiToggleConfig _internalConfig; 32 | } // namespace 33 | 34 | bool ImGui::Toggle(const char* label, bool* v, const ImVec2& size /*= ImVec2()*/) 35 | { 36 | ::SetToAliasDefaults(::_internalConfig); 37 | ::_internalConfig.Size = size; 38 | return ::ToggleInternal(label, v, ::_internalConfig); 39 | } 40 | 41 | bool ImGui::Toggle(const char* label, bool* v, ImGuiToggleFlags flags, const ImVec2& size /*= ImVec2()*/) 42 | { 43 | ::SetToAliasDefaults(::_internalConfig); 44 | ::_internalConfig.Flags = flags; 45 | ::_internalConfig.Size = size; 46 | 47 | // if the user is using any animation flags, 48 | // set the default animation duration. 49 | if ((flags & ImGuiToggleFlags_Animated) != 0) 50 | { 51 | _internalConfig.AnimationDuration = AnimationDurationDefault; 52 | } 53 | 54 | return ::ToggleInternal(label, v, ::_internalConfig); 55 | } 56 | 57 | bool ImGui::Toggle(const char* label, bool* v, ImGuiToggleFlags flags, float animation_duration, const ImVec2& size /*= ImVec2()*/) 58 | { 59 | // this overload implies the toggle should be animated. 60 | if (animation_duration > 0 && (flags & ImGuiToggleFlags_Animated) != 0) 61 | { 62 | // if the user didn't specify ImGuiToggleFlags_Animated, enable it. 63 | flags = flags | (ImGuiToggleFlags_Animated); 64 | } 65 | 66 | ::SetToAliasDefaults(::_internalConfig); 67 | ::_internalConfig.Flags = flags; 68 | ::_internalConfig.AnimationDuration = animation_duration; 69 | ::_internalConfig.Size = size; 70 | 71 | return ::ToggleInternal(label, v, ::_internalConfig); 72 | } 73 | 74 | bool ImGui::Toggle(const char* label, bool* v, ImGuiToggleFlags flags, float frame_rounding, float knob_rounding, const ImVec2& size /*= ImVec2()*/) 75 | { 76 | ::SetToAliasDefaults(::_internalConfig); 77 | ::_internalConfig.Flags = flags; 78 | ::_internalConfig.FrameRounding = frame_rounding; 79 | ::_internalConfig.KnobRounding = knob_rounding; 80 | ::_internalConfig.Size = size; 81 | 82 | return ::ToggleInternal(label, v, ::_internalConfig); 83 | } 84 | 85 | bool ImGui::Toggle(const char* label, bool* v, ImGuiToggleFlags flags, float animation_duration, float frame_rounding, float knob_rounding, const ImVec2& size /*= ImVec2()*/) 86 | { 87 | // this overload implies the toggle should be animated. 88 | if (animation_duration > 0 && (flags & ImGuiToggleFlags_Animated) != 0) 89 | { 90 | // if the user didn't specify ImGuiToggleFlags_Animated, enable it. 91 | flags = flags | (ImGuiToggleFlags_Animated); 92 | } 93 | 94 | ::_internalConfig.Flags = flags; 95 | ::_internalConfig.AnimationDuration = animation_duration; 96 | ::_internalConfig.FrameRounding = frame_rounding; 97 | ::_internalConfig.KnobRounding = knob_rounding; 98 | ::_internalConfig.Size = size; 99 | 100 | return ::ToggleInternal(label, v, ::_internalConfig); 101 | } 102 | 103 | bool ImGui::Toggle(const char* label, bool* v, const ImGuiToggleConfig& config) 104 | { 105 | return ::ToggleInternal(label, v, config); 106 | } 107 | 108 | namespace 109 | { 110 | bool ToggleInternal(const char* label, bool* v, const ImGuiToggleConfig& config) 111 | { 112 | static thread_local ImGuiToggleRenderer renderer; 113 | renderer.SetConfig(label, v, config); 114 | return renderer.Render(); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /imgui_toggle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | #include "imgui_offset_rect.h" 5 | 6 | // Type forward declarations, definitions below in this file. 7 | struct ImGuiToggleConfig; // Configuration data to fully customize a toggle. 8 | struct ImGuiToggleStateConfig; // The data describing how to draw a toggle in a given state. 9 | struct ImGuiTogglePalette; // Color data to adjust how a toggle is drawn. 10 | 11 | typedef int ImGuiToggleFlags; // -> enum ImGuiToggleFlags_ // Flags: for Toggle() modes 12 | typedef int ImGuiToggleA11yStyle; // -> enum ImGuiToggleA11yStyle_ // Describes how to draw A11y labels. 13 | 14 | namespace ImGui 15 | { 16 | // Widgets: Toggle Switches 17 | // - Toggles behave similarly to ImGui::Checkbox() 18 | // - Sometimes called a toggle switch, see also: https://en.wikipedia.org/wiki/Toggle_switch_(widget) 19 | // - They represent two mutually exclusive states, with an optional animation on the UI when toggled. 20 | // Optional parameters: 21 | // - flags: Values from the ImGuiToggleFlags_ enumeration to set toggle modes. 22 | // - animation_duration: Animation duration. Amount of time in seconds the toggle should animate. (0,...] default: 1.0f (Overloads with this parameter imply ImGuiToggleFlags_Animated) 23 | // - frame_rounding: A scalar that controls how rounded the toggle frame is. 0 is square, 1 is round. (0, 1) default 1.0f 24 | // - knob_rounding: A scalar that controls how rounded the toggle knob is. 0 is square, 1 is round. (0, 1) default 1.0f 25 | // - size: A width and height to draw the toggle at. Defaults to `ImGui::GetFrameHeight()` and that height * Phi for the width. 26 | // Config structure: 27 | // - The overload taking a reference to an ImGuiToggleConfig structure allows for more complete control over the widget. 28 | IMGUI_API bool Toggle(const char* label, bool* v, const ImVec2& size = ImVec2()); 29 | IMGUI_API bool Toggle(const char* label, bool* v, ImGuiToggleFlags flags, const ImVec2& size = ImVec2()); 30 | IMGUI_API bool Toggle(const char* label, bool* v, ImGuiToggleFlags flags, float animation_duration, const ImVec2& size = ImVec2()); 31 | IMGUI_API bool Toggle(const char* label, bool* v, ImGuiToggleFlags flags, float frame_rounding, float knob_rounding, const ImVec2& size = ImVec2()); 32 | IMGUI_API bool Toggle(const char* label, bool* v, ImGuiToggleFlags flags, float animation_duration, float frame_rounding, float knob_rounding, const ImVec2& size = ImVec2()); 33 | IMGUI_API bool Toggle(const char* label, bool* v, const ImGuiToggleConfig& config); 34 | 35 | } // namespace ImGui 36 | 37 | 38 | // ImGuiToggleFlags: A set of flags that adjust behavior and display for ImGui::Toggle(). 39 | enum ImGuiToggleFlags_ 40 | { 41 | ImGuiToggleFlags_None = 0, 42 | ImGuiToggleFlags_Animated = 1 << 0, // The toggle's knob should be animated. 43 | // Bits 1-2 reserved. 44 | ImGuiToggleFlags_BorderedFrame = 1 << 3, // The toggle should have a border drawn on the frame. 45 | ImGuiToggleFlags_BorderedKnob = 1 << 4, // The toggle should have a border drawn on the knob. 46 | ImGuiToggleFlags_ShadowedFrame = 1 << 5, // The toggle should have a shadow drawn under the frame. 47 | ImGuiToggleFlags_ShadowedKnob = 1 << 6, // The toggle should have a shadow drawn under the knob. 48 | // Bit 7 reserved. 49 | ImGuiToggleFlags_A11y = 1 << 8, // The toggle should draw on and off glyphs to help indicate its state. 50 | ImGuiToggleFlags_Bordered = ImGuiToggleFlags_BorderedFrame | ImGuiToggleFlags_BorderedKnob, // Shorthand for bordered frame and knob. 51 | ImGuiToggleFlags_Shadowed = ImGuiToggleFlags_ShadowedFrame | ImGuiToggleFlags_ShadowedKnob, // Shorthand for shadowed frame and knob. 52 | ImGuiToggleFlags_Default = ImGuiToggleFlags_None, // The default flags used when no ImGuiToggleFlags_ are specified. 53 | }; 54 | 55 | // ImGuiToggleA11yStyle: Styles to draw A11y labels. 56 | enum ImGuiToggleA11yStyle_ 57 | { 58 | ImGuiToggleA11yStyle_Label, // A11y glyphs draw as text labels. 59 | ImGuiToggleA11yStyle_Glyph, // A11y glyphs draw as power-icon style "I/O" glyphs. 60 | ImGuiToggleA11yStyle_Dot, // A11y glyphs draw as a small dot that can be colored separately from the frame. 61 | ImGuiToggleA11yStyle_Default = ImGuiToggleA11yStyle_Label, // Default: text labels. 62 | }; 63 | 64 | // ImGuiToggleConstants: A set of defaults and limits used by ImGuiToggleConfig 65 | namespace ImGuiToggleConstants 66 | { 67 | // The golden ratio. 68 | constexpr float Phi = 1.6180339887498948482045f; 69 | 70 | // d = 2r 71 | constexpr float DiameterToRadiusRatio = 0.5f; 72 | 73 | // Animation is disabled with a animation_duration of 0. 74 | constexpr float AnimationDurationDisabled = 0.0f; 75 | 76 | // The default animation duration, in seconds. (0.1f: 100 ms.) 77 | constexpr float AnimationDurationDefault = 0.1f; 78 | 79 | // The lowest allowable value for animation duration. (0.0f: Disabled animation.) 80 | constexpr float AnimationDurationMinimum = AnimationDurationDisabled; 81 | 82 | // The default frame rounding value. (1.0f: Full rounding.) 83 | constexpr float FrameRoundingDefault = 1.0f; 84 | 85 | // The minimum frame rounding value. (0.0f: Full rectangle.) 86 | constexpr float FrameRoundingMinimum = 0.0f; 87 | 88 | // The maximum frame rounding value. (1.0f: Full rounding.) 89 | constexpr float FrameRoundingMaximum = 1.0f; 90 | 91 | // The default knob rounding value. (1.0f: Full rounding.) 92 | constexpr float KnobRoundingDefault = 1.0f; 93 | 94 | // The minimum knob rounding value. (0.0f: Full rectangle.) 95 | constexpr float KnobRoundingMinimum = 0.0f; 96 | 97 | // The maximum knob rounding value. (1.0f: Full rounding.) 98 | constexpr float KnobRoundingMaximum = 1.0f; 99 | 100 | // The default height to width ratio. (Phi: The golden ratio.) 101 | constexpr float WidthRatioDefault = Phi; 102 | 103 | // The minimum allowable width ratio. (1.0f: Toggle width==height, not very useful but interesting.) 104 | constexpr float WidthRatioMinimum = 1.0f; 105 | 106 | // The maximum allowable width ratio. (10.0f: It starts to get silly quickly.) 107 | constexpr float WidthRatioMaximum = 10.0f; 108 | 109 | // The default amount of pixels the knob should be inset into the toggle frame. (1.5f in each direction: Pleasing to the eye.) 110 | constexpr ImOffsetRect KnobInsetDefault = 1.5f; 111 | 112 | // The minimum amount of pixels the knob should be negatively inset (outset) from the toggle frame. (-100.0f: Big overgrown toggle.) 113 | constexpr float KnobInsetMinimum = -100.0f; 114 | 115 | // The maximum amount of pixels the knob should be inset into the toggle frame. (100.0f: Toggle likely invisible!) 116 | constexpr float KnobInsetMaximum = 100.0f; 117 | 118 | // The default thickness for borders drawn on the toggle frame and knob. 119 | constexpr float BorderThicknessDefault = 1.0f; 120 | 121 | // The default thickness for shadows drawn under the toggle frame and knob. 122 | constexpr float ShadowThicknessDefault = 2.0f; 123 | 124 | // The default a11y string used when the toggle is on. 125 | const char* const LabelA11yOnDefault = "1"; 126 | 127 | // The default a11y string used when the toggle is off. 128 | const char* const LabelA11yOffDefault = "0"; 129 | } 130 | 131 | struct ImGuiToggleStateConfig 132 | { 133 | // The thickness the border should be drawn on the frame when ImGuiToggleFlags_BorderedFrame is specified in `Flags`. 134 | float FrameBorderThickness = ImGuiToggleConstants::BorderThicknessDefault; 135 | 136 | // The thickness the shadow should be drawn on the frame when ImGuiToggleFlags_ShadowedFrame is specified in `Flags`. 137 | float FrameShadowThickness = ImGuiToggleConstants::ShadowThicknessDefault; 138 | 139 | // The thickness the border should be drawn on the frame when ImGuiToggleFlags_BorderedKnob is specified in `Flags`. 140 | float KnobBorderThickness = ImGuiToggleConstants::BorderThicknessDefault; 141 | 142 | // The thickness the shadow should be drawn on the frame when ImGuiToggleFlags_ShadowedKnob is specified in `Flags`. 143 | float KnobShadowThickness = ImGuiToggleConstants::ShadowThicknessDefault; 144 | 145 | // The label drawn on the toggle to show the toggle is in the when ImGuiToggleFlags_A11yLabels is specified in `Flags`. 146 | // If left null, default strings will be used. 147 | const char* Label = nullptr; 148 | 149 | // The number of pixels the knob should be inset into the toggle frame. 150 | // With a round (circle) knob, an average of each offset will be used. 151 | // With a rectangular knob, each offset will be used in it's intended direction. 152 | ImOffsetRect KnobInset = ImGuiToggleConstants::KnobInsetDefault; 153 | 154 | // A custom amount of pixels to offset the knob by. Positive x values will move the knob towards the inside, negative the outside. 155 | ImVec2 KnobOffset = ImVec2(0.0f, 0.0f); 156 | 157 | // An optional custom palette to use for the colors to use when drawing the toggle. If left null, theme colors will be used. 158 | // If any of the values in the palette are zero, those specific colors will default to theme colors. 159 | const ImGuiTogglePalette* Palette = nullptr; 160 | }; 161 | 162 | // ImGuiToggleConfig: A collection of data used to customize the appearance and behavior of a toggle widget. 163 | struct ImGuiToggleConfig 164 | { 165 | // Flags that control the toggle's behavior and display. 166 | ImGuiToggleFlags Flags = ImGuiToggleFlags_Default; 167 | 168 | // What style of A11y glyph to draw on the widget. 169 | ImGuiToggleFlags A11yStyle = ImGuiToggleA11yStyle_Default; 170 | 171 | // The a duration in seconds that the toggle should animate for. If 0, animation will be disabled. 172 | float AnimationDuration = ImGuiToggleConstants::AnimationDurationDefault; 173 | 174 | // A scalar that controls how rounded the toggle frame is. 0 is square, 1 is round. (0, 1) default 1.0f 175 | float FrameRounding = ImGuiToggleConstants::FrameRoundingDefault; 176 | 177 | //A scalar that controls how rounded the toggle knob is. 0 is square, 1 is round. (0, 1) default 1.0f 178 | float KnobRounding = ImGuiToggleConstants::KnobRoundingDefault; 179 | 180 | // A ratio that controls how wide the toggle is compared to it's height. If `Size.x` is non-zero, this value is ignored. 181 | float WidthRatio = ImGuiToggleConstants::WidthRatioDefault; 182 | 183 | // A custom size to draw the toggle with. Defaults to (0, 0). If `Size.x` is zero, `WidthRatio` will control the toggle width. 184 | // If `Size.y` is zero, the toggle height will be set by `ImGui::GetFrameHeight()`. 185 | ImVec2 Size = ImVec2(0.0f, 0.0f); 186 | 187 | // Specific configuration data to use when the knob is in the on state. 188 | ImGuiToggleStateConfig On; 189 | 190 | // Specific configuration data to use when the knob is in the off state. 191 | ImGuiToggleStateConfig Off; 192 | }; 193 | -------------------------------------------------------------------------------- /imgui_toggle_math.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #ifndef IMGUI_DEFINE_MATH_OPERATORS 5 | #define IMGUI_DEFINE_MATH_OPERATORS 6 | #endif // IMGUI_DEFINE_MATH_OPERATORS 7 | #include "imgui.h" 8 | #include "imgui_internal.h" 9 | 10 | namespace ImGuiToggleMath 11 | { 12 | // lerp, but backwards! 13 | template constexpr inline T ImInvLerp(T a, T b, float value) { return (T)((value - a) / (b - a)); } 14 | 15 | // float comparison w/tolerance - can't constexper as ImFabs isn't constexpr. 16 | inline bool ImApproximately(float a, float b, float tolerance = 0.0001f) { return ImAbs(a - b) < tolerance; } 17 | 18 | // helpers for checking if an ImVec4 is zero or not. 19 | constexpr inline bool IsZero(const ImVec4& v) { return v.w == 0 && v.x == 0 && v.y == 0 && v.z == 0; } 20 | constexpr inline bool IsNonZero(const ImVec4& v) { return v.w != 0 || v.x != 0 || v.y != 0 || v.z != 0; } 21 | } // namespace 22 | -------------------------------------------------------------------------------- /imgui_toggle_palette.cpp: -------------------------------------------------------------------------------- 1 | #ifndef IMGUI_DEFINE_MATH_OPERATORS 2 | #define IMGUI_DEFINE_MATH_OPERATORS 3 | #endif // IMGUI_DEFINE_MATH_OPERATORS 4 | 5 | #include "imgui_toggle_palette.h" 6 | #include "imgui_toggle_math.h" 7 | 8 | #include "imgui.h" 9 | 10 | 11 | using namespace ImGuiToggleMath; 12 | 13 | void ImGui::UnionPalette(ImGuiTogglePalette* target, const ImGuiTogglePalette* candidate, const ImVec4 colors[], bool v) 14 | { 15 | 16 | target->Knob = colors[ImGuiCol_Text]; 17 | target->KnobHover = colors[ImGuiCol_Text]; 18 | target->Frame = colors[!v ? ImGuiCol_FrameBg : ImGuiCol_Button]; 19 | target->FrameHover = colors[!v ? ImGuiCol_FrameBgHovered : ImGuiCol_ButtonHovered]; 20 | target->FrameBorder = colors[ImGuiCol_Border]; 21 | target->FrameShadow = colors[ImGuiCol_BorderShadow]; 22 | target->KnobBorder = colors[ImGuiCol_Border]; 23 | target->KnobShadow = colors[ImGuiCol_BorderShadow]; 24 | target->A11yGlyph = colors[!v ? ImGuiCol_FrameBg : ImGuiCol_Text]; 25 | 26 | // if the user didn't provide a candidate, just provide the theme colored palette. 27 | if (candidate == nullptr) 28 | { 29 | return; 30 | } 31 | 32 | // if the user did provide a candidate, populate all non-zero colors 33 | #define GET_PALETTE_POPULATE_NONZERO(member) \ 34 | do { \ 35 | if (IsNonZero(candidate->member)) \ 36 | { \ 37 | target->member = candidate->member; \ 38 | } \ 39 | } while (0) 40 | 41 | GET_PALETTE_POPULATE_NONZERO(Knob); 42 | GET_PALETTE_POPULATE_NONZERO(KnobHover); 43 | GET_PALETTE_POPULATE_NONZERO(Frame); 44 | GET_PALETTE_POPULATE_NONZERO(FrameHover); 45 | GET_PALETTE_POPULATE_NONZERO(FrameBorder); 46 | GET_PALETTE_POPULATE_NONZERO(FrameShadow); 47 | GET_PALETTE_POPULATE_NONZERO(KnobBorder); 48 | GET_PALETTE_POPULATE_NONZERO(KnobShadow); 49 | GET_PALETTE_POPULATE_NONZERO(A11yGlyph); 50 | 51 | #undef GET_PALETTE_POPULATE_NONZERO 52 | } 53 | 54 | void ImGui::BlendPalettes(ImGuiTogglePalette* result, const ImGuiTogglePalette& a, const ImGuiTogglePalette& b, float blend_amount) 55 | { 56 | // a quick out for if we are at either end of the blend. 57 | if (ImApproximately(blend_amount, 0.0f)) 58 | { 59 | *result = a; 60 | return; 61 | } 62 | else if (ImApproximately(blend_amount, 1.0f)) 63 | { 64 | *result = b; 65 | return; 66 | } 67 | 68 | 69 | #define BLEND_PALETTES_LERP(member) \ 70 | do { \ 71 | result->member = ImLerp(a.member, b.member, blend_amount); \ 72 | } while (0) 73 | 74 | BLEND_PALETTES_LERP(Knob); 75 | BLEND_PALETTES_LERP(KnobHover); 76 | BLEND_PALETTES_LERP(Frame); 77 | BLEND_PALETTES_LERP(FrameHover); 78 | BLEND_PALETTES_LERP(FrameBorder); 79 | BLEND_PALETTES_LERP(FrameShadow); 80 | BLEND_PALETTES_LERP(KnobBorder); 81 | BLEND_PALETTES_LERP(KnobShadow); 82 | BLEND_PALETTES_LERP(A11yGlyph); 83 | 84 | #undef BLEND_PALETTES_LERP 85 | } 86 | -------------------------------------------------------------------------------- /imgui_toggle_palette.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | 5 | // ImGuiTogglePalette: A collection of colors used to customize the rendering of a toggle widget. 6 | // Leaving any ImVec4 as default (zero) will allow the theme color to be used for that member. 7 | struct ImGuiTogglePalette 8 | { 9 | // The default knob color. 10 | ImVec4 Knob; 11 | 12 | // The default knob color, used when when the knob is hovered. 13 | ImVec4 KnobHover; 14 | 15 | // The background color of the toggle frame. 16 | ImVec4 Frame; 17 | 18 | // The background color of the toggle frame when the toggle is hovered. 19 | ImVec4 FrameHover; 20 | 21 | // The background color of the toggle frame's border used when ImGuiToggleFlags_BorderedFrame is specified. 22 | ImVec4 FrameBorder; 23 | 24 | // The shadow color of the toggle frame. 25 | ImVec4 FrameShadow; 26 | 27 | // The background color of the toggle knob's border used when ImGuiToggleFlags_BorderedKnob is specified. 28 | ImVec4 KnobBorder; 29 | 30 | // The shadow color of the toggle knob. 31 | ImVec4 KnobShadow; 32 | 33 | // The color of the accessibility label or glyph. 34 | ImVec4 A11yGlyph; 35 | }; 36 | 37 | namespace ImGui 38 | { 39 | void UnionPalette(ImGuiTogglePalette* target, const ImGuiTogglePalette* candidate, const ImVec4 colors[], bool v); 40 | void BlendPalettes(ImGuiTogglePalette* result, const ImGuiTogglePalette& a, const ImGuiTogglePalette& b, float blend_amount); 41 | } 42 | -------------------------------------------------------------------------------- /imgui_toggle_presets.cpp: -------------------------------------------------------------------------------- 1 | #include "imgui_toggle_presets.h" 2 | #include "imgui_toggle_palette.h" 3 | 4 | namespace 5 | { 6 | // some color values shared between styles 7 | const ImVec4 White(1.0f, 1.0f, 1.0f, 1.0f); 8 | const ImVec4 Black(0.0f, 0.0f, 0.0f, 1.0f); 9 | const ImVec4 Green(0.24f, 0.52f, 0.15f, 1.0f); 10 | const ImVec4 GreenBorder(0.39f, 0.62f, 0.32f, 1.0f); 11 | const ImVec4 GreenHighlight(0.3f, 1.0f, 0.0f, 0.75f); 12 | const ImVec4 RedHighlight(1.0f, 0.3f, 0.0f, 0.75f); 13 | 14 | // DPI aware scale utility: the scale should proportional to the font size 15 | // font Size is typically 14.5 on normal DPI screens, and 29 on windows HighDPI 16 | float DpiFactor() 17 | { 18 | return ImGui::GetFontSize() / 14.5f; 19 | } 20 | } // namespace 21 | 22 | ImGuiToggleConfig ImGuiTogglePresets::DefaultStyle() 23 | { 24 | return ImGuiToggleConfig(); 25 | } 26 | 27 | ImGuiToggleConfig ImGuiTogglePresets::RectangleStyle() 28 | { 29 | ImGuiToggleConfig config; 30 | config.Flags |= ImGuiToggleFlags_Animated; 31 | config.FrameRounding = 0.1f; 32 | config.KnobRounding = 0.3f; 33 | config.AnimationDuration = 0.5f; 34 | 35 | return config; 36 | } 37 | 38 | ImGuiToggleConfig ImGuiTogglePresets::GlowingStyle() 39 | { 40 | static ImGuiTogglePalette palette_on; 41 | palette_on.FrameShadow = ::GreenHighlight; 42 | palette_on.KnobShadow = ::GreenHighlight; 43 | 44 | static ImGuiTogglePalette palette_off; 45 | palette_off.FrameShadow = ::RedHighlight; 46 | palette_off.KnobShadow = ::RedHighlight; 47 | 48 | ImGuiToggleConfig config; 49 | config.Flags |= ImGuiToggleFlags_Animated | ImGuiToggleFlags_Shadowed; 50 | config.On.Palette = &palette_on; 51 | config.Off.Palette = &palette_off; 52 | 53 | return config; 54 | } 55 | 56 | 57 | ImGuiToggleConfig ImGuiTogglePresets::iOSStyle(const float _size_scale /*= 1.0f*/, bool light_mode /*= false*/) 58 | { 59 | float size_scale = _size_scale * DpiFactor(); 60 | 61 | const ImVec4 frame_on(0.3f, 0.85f, 0.39f, 1.0f); 62 | const ImVec4 frame_on_hover(0.0f, 1.0f, 0.57f, 1.0f); 63 | const ImVec4 dark_mode_frame_off(0.22f, 0.22f, 0.24f, 1.0f); 64 | const ImVec4 light_mode_frame_off(0.91f, 0.91f, 0.92f, 1.0f); 65 | const ImVec4 dark_mode_frame_off_hover(0.4f, 0.4f, 0.4f, 1.0f); 66 | const ImVec4 light_mode_frame_off_hover(0.7f, 0.7f, 0.7f, 1.0f); 67 | const ImVec4 light_gray(0.89f, 0.89f, 0.89f, 1.0f); 68 | const ImVec4 a11y_glyph_on(1.0f, 1.0f, 1.0f, 1.0f); 69 | const ImVec4 a11y_glyph_off(0.4f, 0.4f, 0.4f, 1.0f); 70 | 71 | const float ios_width = 153 * size_scale; 72 | const float ios_height = 93 * size_scale; 73 | const float ios_frame_border_thickness = 0.0f * size_scale; 74 | const float ios_border_thickness = 0.0f * size_scale; 75 | const float ios_offset = 0.0f * size_scale; 76 | const float ios_inset = 6.0f * size_scale; 77 | 78 | // setup 'on' colors 79 | static ImGuiTogglePalette ios_palette_on; 80 | ios_palette_on.Knob = ::White; 81 | ios_palette_on.Frame = frame_on; 82 | ios_palette_on.FrameHover = frame_on_hover; 83 | ios_palette_on.KnobBorder = light_gray; 84 | ios_palette_on.FrameBorder = light_gray; 85 | ios_palette_on.A11yGlyph = a11y_glyph_on; 86 | 87 | // setup 'off' colors 88 | static ImGuiTogglePalette ios_palette_off; 89 | ios_palette_off.Knob = ::White; 90 | ios_palette_off.Frame = light_mode ? light_mode_frame_off : dark_mode_frame_off; 91 | ios_palette_off.FrameHover = light_mode ? light_mode_frame_off_hover : light_mode_frame_off_hover; 92 | ios_palette_off.KnobBorder = light_gray; 93 | ios_palette_off.FrameBorder = light_gray; 94 | ios_palette_off.A11yGlyph = a11y_glyph_off; 95 | 96 | // setup config 97 | ImGuiToggleConfig config; 98 | config.Size = ImVec2(ios_width, ios_height); 99 | config.Flags |= ImGuiToggleFlags_A11y 100 | | ImGuiToggleFlags_Animated 101 | | (light_mode ? ImGuiToggleFlags_Bordered : 0); 102 | config.A11yStyle = ImGuiToggleA11yStyle_Glyph; 103 | 104 | // setup 'on' config 105 | config.On.FrameBorderThickness = 0; 106 | config.On.KnobBorderThickness = ios_border_thickness; 107 | config.On.KnobOffset = ImVec2(ios_offset, 0); 108 | config.On.KnobInset = ios_inset; 109 | config.On.Palette = &ios_palette_on; 110 | 111 | // setup 'off' config 112 | config.Off.FrameBorderThickness = ios_frame_border_thickness; 113 | config.Off.KnobBorderThickness = ios_border_thickness; 114 | config.Off.KnobOffset = ImVec2(ios_offset, 0); 115 | config.Off.KnobInset = ios_inset; 116 | config.Off.Palette = &ios_palette_off; 117 | 118 | return config; 119 | } 120 | 121 | ImGuiToggleConfig ImGuiTogglePresets::MaterialStyle(float _size_scale /*= 1.0f*/) 122 | { 123 | float size_scale = DpiFactor() * _size_scale; 124 | 125 | const ImVec4 purple(0.4f, 0.08f, 0.97f, 1.0f); 126 | const ImVec4 purple_dim(0.78f, 0.65f, 0.99f, 1.0f); 127 | const ImVec4 purple_hover(0.53f, 0.08f, 1.0f, 1.0f); 128 | 129 | const ImVec2 material_size(37 * size_scale, 16 * size_scale); 130 | const float material_inset = -2.5f * size_scale; 131 | 132 | static ImGuiTogglePalette material_palette_on; 133 | material_palette_on.Frame = purple_dim; 134 | material_palette_on.FrameHover = purple_dim; 135 | material_palette_on.Knob = purple; 136 | material_palette_on.KnobHover = purple_hover; 137 | 138 | // setup config 139 | ImGuiToggleConfig config; 140 | config.Flags |= ImGuiToggleFlags_Animated; 141 | config.Size = material_size; 142 | config.On.KnobInset = config.Off.KnobInset = material_inset; 143 | config.On.KnobOffset = config.Off.KnobOffset = ImVec2(-material_inset, 0); 144 | config.On.Palette = &material_palette_on; 145 | 146 | return config; 147 | } 148 | 149 | ImGuiToggleConfig ImGuiTogglePresets::MinecraftStyle(float _size_scale /*= 1.0f*/) 150 | { 151 | float size_scale = DpiFactor() * _size_scale; 152 | 153 | const ImVec4 gray(0.35f, 0.35f, 0.35f, 1.0f); 154 | const ImVec4 dark_gray(0.12f, 0.12f, 0.12f, 1.0f); 155 | const ImVec4 frame_border_off(0.6f, 0.6f, 0.61f, 1.0f); 156 | const ImVec4 toggle_frame_off(0.55f, 0.55f, 0.56f, 1.0f); 157 | const ImVec4 gray_knob(0.82f, 0.82f, 0.83f, 1.0f); 158 | 159 | const ImVec2 minecraft_knob_size(56.0f * size_scale, 40.0f * size_scale); 160 | const ImVec2 minecraft_size(104.0f * size_scale, 40.0f * size_scale); // 112x48 161 | const float minecraft_borders = 4.0f * size_scale; 162 | const ImVec2 minecraft_offset(0.0f * size_scale, -minecraft_borders * 2.0f); 163 | const ImOffsetRect minecraft_inset( 164 | 0.0f * size_scale, // top 165 | -16.0f * size_scale, // left 166 | 0.0f * size_scale, // bottom 167 | 0.0f * size_scale // right 168 | ); 169 | const float minecraft_rounding = 0.0f; // disable rounding 170 | const float minecraft_shadows = 4.0f * size_scale; 171 | 172 | // set up the "on" palette. 173 | static ImGuiTogglePalette minecraft_palette_on; 174 | minecraft_palette_on.Frame = ::Green; 175 | minecraft_palette_on.FrameHover = ::Green; 176 | minecraft_palette_on.FrameBorder = ::GreenBorder; 177 | minecraft_palette_on.FrameShadow = ::Black; 178 | minecraft_palette_on.Knob = gray_knob; 179 | minecraft_palette_on.KnobHover = gray_knob; 180 | minecraft_palette_on.A11yGlyph = ::White; 181 | minecraft_palette_on.KnobBorder = ::White; 182 | minecraft_palette_on.KnobShadow = ::Black; 183 | 184 | // start the "off" palette as a copy of the on 185 | static ImGuiTogglePalette minecraft_palette_off; 186 | minecraft_palette_off = minecraft_palette_on; 187 | 188 | // make changes to the off palette 189 | minecraft_palette_off.Frame = toggle_frame_off; 190 | minecraft_palette_off.FrameHover = toggle_frame_off; 191 | minecraft_palette_off.FrameBorder = frame_border_off; 192 | 193 | // setup config 194 | ImGuiToggleConfig config; 195 | config.Flags |= ImGuiToggleFlags_A11y | ImGuiToggleFlags_Bordered | ImGuiToggleFlags_Shadowed; 196 | config.Size = minecraft_size; 197 | config.FrameRounding = minecraft_rounding; 198 | config.KnobRounding = minecraft_rounding; 199 | config.A11yStyle = ImGuiToggleA11yStyle_Glyph; 200 | 201 | // set up the "on" state configuration 202 | config.On.KnobInset = minecraft_inset; 203 | config.On.KnobOffset = minecraft_offset; 204 | config.On.FrameBorderThickness = minecraft_borders; 205 | config.On.FrameShadowThickness = minecraft_shadows; 206 | config.On.KnobBorderThickness = minecraft_borders; 207 | config.On.KnobShadowThickness = minecraft_shadows; 208 | config.On.Palette = &minecraft_palette_on; 209 | 210 | // duplicate the "on" config to the "off", then make changes. 211 | config.Off = config.On; 212 | config.Off.KnobInset = minecraft_inset.MirrorHorizontally(); 213 | config.Off.Palette = &minecraft_palette_off; 214 | 215 | return config; 216 | } 217 | 218 | 219 | -------------------------------------------------------------------------------- /imgui_toggle_presets.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui_toggle.h" 4 | 5 | // ImGuiTogglePresets: A few canned configurations for various presets OOTB. 6 | namespace ImGuiTogglePresets 7 | { 8 | // The default, unmodified style. 9 | ImGuiToggleConfig DefaultStyle(); 10 | 11 | // A style similar to default, but with rectangular knob and frame. 12 | ImGuiToggleConfig RectangleStyle(); 13 | 14 | // A style that uses a shadow to appear to glow while it's on. 15 | ImGuiToggleConfig GlowingStyle(); 16 | 17 | // A style that emulates what a toggle on iOS looks like. 18 | ImGuiToggleConfig iOSStyle(float size_scale = 1.0f, bool light_mode = false); 19 | 20 | // A style that emulates what a Material Design toggle looks like. 21 | ImGuiToggleConfig MaterialStyle(float size_scale = 1.0f); 22 | 23 | // A style that emulates what a toggle close to one from Minecraft. 24 | ImGuiToggleConfig MinecraftStyle(float size_scale = 1.0f); 25 | } 26 | -------------------------------------------------------------------------------- /imgui_toggle_renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "imgui_toggle_renderer.h" 2 | #include "imgui_toggle_palette.h" 3 | #include "imgui_toggle_math.h" 4 | 5 | using namespace ImGuiToggleConstants; 6 | using namespace ImGuiToggleMath; 7 | 8 | namespace 9 | { 10 | // a small helper to quickly check the mixed value flag. 11 | inline bool IsItemMixedValue() 12 | { 13 | #if IMGUI_VERSION_NUM >= 19135 14 | return (GImGui->LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0; 15 | #else 16 | return (GImGui->LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0; 17 | #endif 18 | } 19 | } // namespace 20 | 21 | ImGuiToggleRenderer::ImGuiToggleRenderer() 22 | { 23 | SetConfig(nullptr, nullptr, ImGuiToggleConfig()); 24 | } 25 | 26 | ImGuiToggleRenderer::ImGuiToggleRenderer(const char* label, bool* value, const ImGuiToggleConfig& user_config) : _style(nullptr), _label(label), _value(value) 27 | { 28 | SetConfig(label, value, user_config); 29 | } 30 | 31 | void ImGuiToggleRenderer::SetConfig(const char* label, bool* value, const ImGuiToggleConfig& user_config) 32 | { 33 | // store mandatory settings 34 | _label = label; 35 | _value = value; 36 | 37 | // copy our user's config and ensure it's valid. 38 | _config = user_config; 39 | ValidateConfig(); 40 | } 41 | 42 | bool ImGuiToggleRenderer::Render() 43 | { 44 | ImGuiWindow* window = ImGui::GetCurrentWindow(); 45 | 46 | IM_ASSERT(window); 47 | IM_ASSERT(_label != nullptr); 48 | IM_ASSERT(_value != nullptr); 49 | 50 | if (window->SkipItems) 51 | { 52 | return false; 53 | } 54 | 55 | // update igui context 56 | ImGuiContext& g = *GImGui; 57 | _id = window->GetID(_label); 58 | _drawList = ImGui::GetWindowDrawList(); 59 | _style = &ImGui::GetStyle(); 60 | 61 | // calculate the size of the toggle portion 62 | const float height = _config.Size.y > 0 63 | ? _config.Size.y 64 | : ImGui::GetFrameHeight(); 65 | const float width = _config.Size.x > 0 66 | ? _config.Size.x 67 | : height * _config.WidthRatio; 68 | 69 | // get the position of the widget and how large the label should be 70 | ImVec2 widget_position = window->DC.CursorPos; 71 | ImVec2 label_size = ImGui::CalcTextSize(_label, nullptr, true); 72 | 73 | // if the knob is offset horizontally outside of the frame in the on state, we want to bump our label over. 74 | const float label_x_offset = ImMax(0.0f, -_config.On.KnobOffset.x / 2.0f); 75 | 76 | // calculate bounding boxes for the toggle, and the whole widget including the label for interaction 77 | _boundingBox = ImRect(widget_position, widget_position + ImVec2(width, height)); 78 | ImRect total_bounding_box = ImRect(widget_position, 79 | widget_position 80 | + ImVec2( 81 | width + (label_size.x > 0.0f ? _style->ItemInnerSpacing.x + label_size.x : 0.0f) + label_x_offset, 82 | ImMax(height, label_size.y) + _style->FramePadding.y * 2.0f 83 | )); 84 | 85 | // handle the toggle input behavior 86 | bool pressed = ToggleBehavior(total_bounding_box); 87 | _isMixedValue = ::IsItemMixedValue(); 88 | 89 | // draw the toggle itself and the label 90 | DrawToggle(); 91 | DrawLabel(label_x_offset); 92 | 93 | IMGUI_TEST_ENGINE_ITEM_INFO(_id, _label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*_value ? ImGuiItemStatusFlags_Checked : 0)); 94 | return pressed; 95 | } 96 | 97 | 98 | void ImGuiToggleRenderer::ValidateConfig() 99 | { 100 | IM_ASSERT_USER_ERROR(_config.Size.x >= 0, "Size.x specified was negative."); 101 | IM_ASSERT_USER_ERROR(_config.Size.y >= 0, "Size.y specified was negative."); 102 | 103 | // if no flags were specified, use defaults. 104 | if (_config.Flags == ImGuiToggleFlags_None) 105 | { 106 | _config.Flags = ImGuiToggleFlags_Default; 107 | } 108 | 109 | // a zero or negative duration would prevent animation. 110 | _config.AnimationDuration = ImMax(_config.AnimationDuration, AnimationDurationMinimum); 111 | 112 | // keep our size/scale and rounding numbers sane. 113 | _config.FrameRounding = ImClamp(_config.FrameRounding, FrameRoundingMinimum, FrameRoundingMaximum); 114 | _config.KnobRounding = ImClamp(_config.KnobRounding, KnobRoundingMinimum, KnobRoundingMaximum); 115 | _config.WidthRatio = ImClamp(_config.WidthRatio, WidthRatioMinimum, WidthRatioMaximum); 116 | 117 | // Make sure our a11y labels have values. 118 | if (_config.On.Label == nullptr) 119 | { 120 | _config.On.Label = LabelA11yOnDefault; 121 | } 122 | 123 | if (_config.Off.Label == nullptr) 124 | { 125 | _config.Off.Label = LabelA11yOffDefault; 126 | } 127 | } 128 | 129 | bool ImGuiToggleRenderer::ToggleBehavior(const ImRect& interaction_bounding_box) 130 | { 131 | ImGui::ItemSize(interaction_bounding_box, _style->FramePadding.y); 132 | if (!ImGui::ItemAdd(interaction_bounding_box, _id)) 133 | { 134 | ImGuiContext& g = *GImGui; 135 | IMGUI_TEST_ENGINE_ITEM_INFO(_id, _label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*_value ? ImGuiItemStatusFlags_Checked : 0)); 136 | return false; 137 | } 138 | 139 | // the meat and potatoes: the actual toggle button 140 | const ImGuiButtonFlags button_flags = ImGuiButtonFlags_PressedOnClick; 141 | bool hovered, held; 142 | bool pressed = ImGui::ButtonBehavior(interaction_bounding_box, _id, &hovered, &held, button_flags); 143 | if (pressed) 144 | { 145 | *_value = !(*_value); 146 | ImGui::MarkItemEdited(_id); 147 | } 148 | 149 | return pressed; 150 | } 151 | 152 | void ImGuiToggleRenderer::DrawToggle() 153 | { 154 | const float height = GetHeight(); 155 | const float width = GetWidth(); 156 | 157 | ImGuiContext& g = *GImGui; 158 | // update imgui state 159 | _isHovered = g.HoveredId == _id; 160 | _isLastActive = g.LastActiveId == _id; 161 | _lastActiveTimer = g.LastActiveIdTimer; 162 | 163 | // radius is by default half the diameter 164 | const float knob_radius = height * DiameterToRadiusRatio; 165 | 166 | // update the toggle's animation timer, state, and palette. 167 | UpdateAnimationPercent(); 168 | UpdateStateConfig(); 169 | UpdatePalette(); 170 | 171 | // get colors modified by hover. 172 | const ImU32 color_frame = ImGui::GetColorU32(_isHovered ? _palette.FrameHover : _palette.Frame); 173 | const ImU32 color_knob = ImGui::GetColorU32(_isHovered ? _palette.KnobHover : _palette.Knob); 174 | 175 | // draw the background frame 176 | DrawFrame(color_frame); 177 | 178 | // draw accessibility labels, if enabled. 179 | if (HasA11yGlyphs()) 180 | { 181 | DrawA11yFrameOverlays(knob_radius); 182 | } 183 | 184 | // draw the knob 185 | if (HasCircleKnob()) 186 | { 187 | DrawCircleKnob(knob_radius, color_knob); 188 | } 189 | else if (HasRectangleKnob()) 190 | { 191 | DrawRectangleKnob(knob_radius, color_knob); 192 | } 193 | else 194 | { 195 | // user didn't specify a knob mode, they get no knob. 196 | IM_ASSERT_USER_ERROR(false, "No toggle knob type to draw."); 197 | } 198 | } 199 | 200 | void ImGuiToggleRenderer::DrawFrame(ImU32 color_frame) 201 | { 202 | const float height = GetHeight(); 203 | const float frame_rounding = _config.FrameRounding >= 0 204 | ? height * _config.FrameRounding 205 | : height * 0.5f; 206 | 207 | // draw frame shadow, if enabled 208 | if (HasShadowedFrame()) 209 | { 210 | const ImU32 color_frame_shadow = ImGui::GetColorU32(_palette.FrameShadow); 211 | DrawRectShadow(_boundingBox, color_frame_shadow, frame_rounding, _state.FrameShadowThickness); 212 | } 213 | 214 | // draw frame background 215 | _drawList->AddRectFilled(_boundingBox.Min, _boundingBox.Max, color_frame, frame_rounding); 216 | 217 | // draw frame border, if enabled 218 | if (HasBorderedFrame()) 219 | { 220 | const ImU32 color_frame_border = ImGui::GetColorU32(_palette.FrameBorder); 221 | DrawRectBorder(_boundingBox, color_frame_border, frame_rounding, _state.FrameBorderThickness); 222 | } 223 | } 224 | 225 | void ImGuiToggleRenderer::DrawA11yDot(const ImVec2& pos, ImU32 color) 226 | { 227 | ImGui::RenderBullet(_drawList, pos, color); 228 | } 229 | 230 | void ImGuiToggleRenderer::DrawA11yGlyph(ImVec2 pos, ImU32 color, bool state, float radius, float thickness) 231 | { 232 | if (state) 233 | { 234 | // draw the I bar 235 | const float half_thickness = thickness * 0.5f; 236 | const ImVec2 offset(half_thickness, radius); 237 | _drawList->AddRectFilled(pos - offset, pos + offset, color); 238 | } 239 | else 240 | { 241 | // draw the O ring 242 | const float o_adjustment = 1.0f; 243 | const float o_radius = radius - o_adjustment; 244 | const float o_thickness = thickness + o_adjustment; 245 | pos.x += o_adjustment; 246 | _drawList->AddCircle(pos, o_radius, color, 0, o_thickness); 247 | } 248 | } 249 | 250 | void ImGuiToggleRenderer::DrawA11yLabel(ImVec2 pos, ImU32 color, const char* label) 251 | { 252 | // subtract out half the sizes of the text to center them 253 | const ImVec2 text_size = ImGui::CalcTextSize(label); 254 | pos.x -= (text_size.x * 0.5f); 255 | pos.y -= (text_size.y * 0.5f); 256 | 257 | // draw the label. 258 | _drawList->AddText(pos, color, label); 259 | } 260 | 261 | void ImGuiToggleRenderer::DrawA11yFrameOverlay(float knob_radius, bool state) 262 | { 263 | const float AnimationPercentOff = 0.0f; 264 | const float AnimationPercentOn = 1.0f; 265 | 266 | // notice we swap the animation percents as compared to the labels/glyphs, as we want to draw the 267 | // a11y labels where the knob *isn't* when it's in a given state. 268 | ImVec2 pos = CalculateKnobCenter(knob_radius, state ? AnimationPercentOff : AnimationPercentOn); 269 | 270 | // next, we want to adjust the position to move to a more pleasing spot in the toggle. 271 | // this is just some tinkering that got to a nice looking area based on the sizes, 272 | // but this is subject to change. 273 | const float diameter = ImMax(1.0f, GetHeight() / 3.0f); 274 | const float radius = diameter * 0.5f; 275 | const float thickness = ImCeil(radius * 0.2f); 276 | const ImVec2 adjustment = ImVec2(radius - thickness, 0.0f) 277 | * (state ? -1.0f : 1.0f); // if state is true, we want to subtract rather than add. 278 | 279 | pos += adjustment; 280 | 281 | const ImU32 color = state 282 | ? ImGui::GetColorU32(_colorA11yGlyphOn) 283 | : ImGui::GetColorU32(_colorA11yGlyphOff); 284 | 285 | switch (_config.A11yStyle) 286 | { 287 | case ImGuiToggleA11yStyle_Label: 288 | DrawA11yLabel(pos, color, state ? _config.On.Label : _config.Off.Label); 289 | break; 290 | case ImGuiToggleA11yStyle_Glyph: 291 | DrawA11yGlyph(pos, color, state, radius, thickness); 292 | break; 293 | case ImGuiToggleA11yStyle_Dot: 294 | DrawA11yDot(pos, color); 295 | break; 296 | default: 297 | break; 298 | } 299 | } 300 | 301 | void ImGuiToggleRenderer::DrawA11yFrameOverlays(float knob_radius) 302 | { 303 | DrawA11yFrameOverlay(knob_radius, true); 304 | DrawA11yFrameOverlay(knob_radius, false); 305 | } 306 | 307 | void ImGuiToggleRenderer::DrawCircleKnob(float radius, ImU32 color_knob) 308 | { 309 | const float inset_size = ImMin(_state.KnobInset.GetAverage(), radius); 310 | IM_ASSERT_USER_ERROR(inset_size <= radius, "Inset size needs to be smaller or equal to the knob's radius for circular knobs."); 311 | 312 | const ImVec2 knob_center = CalculateKnobCenter(radius, _animationPercent, _state.KnobOffset); 313 | const float knob_radius = radius - inset_size; 314 | 315 | // draw knob shadow, if enabled 316 | if (HasShadowedKnob()) 317 | { 318 | const ImU32 color_knob_shadow = ImGui::GetColorU32(_palette.KnobShadow); 319 | DrawCircleShadow(knob_center, knob_radius, color_knob_shadow, _state.KnobShadowThickness); 320 | } 321 | 322 | // draw circle knob 323 | _drawList->AddCircleFilled(knob_center, knob_radius, color_knob); 324 | 325 | // draw knob border, if enabled 326 | if (HasBorderedKnob()) 327 | { 328 | const ImU32 color_knob_border = ImGui::GetColorU32(_palette.KnobBorder); 329 | DrawCircleBorder(knob_center, knob_radius, color_knob_border, _state.KnobBorderThickness); 330 | } 331 | } 332 | 333 | void ImGuiToggleRenderer::DrawRectangleKnob(float radius, ImU32 color_knob) 334 | { 335 | const ImRect bounds = CalculateKnobBounds(radius, _animationPercent, _state.KnobOffset); 336 | 337 | const float knob_diameter_total = bounds.GetHeight(); 338 | const float knob_rounded_radius = (knob_diameter_total * 0.5f) * _config.KnobRounding; 339 | 340 | // draw knob shadow, if enabled 341 | if (HasShadowedKnob()) 342 | { 343 | const ImU32 color_knob_shadow = ImGui::GetColorU32(_palette.KnobShadow); 344 | DrawRectShadow(bounds, color_knob_shadow, _config.KnobRounding, _state.KnobShadowThickness); 345 | } 346 | 347 | // draw rectangle/squircle knob 348 | _drawList->AddRectFilled(bounds.Min, bounds.Max, color_knob, knob_rounded_radius); 349 | 350 | // draw knob border, if enabled 351 | if (HasBorderedKnob()) 352 | { 353 | const ImU32 color_knob_border = ImGui::GetColorU32(_palette.KnobBorder); 354 | DrawRectBorder(bounds, color_knob_border, knob_rounded_radius, _state.KnobBorderThickness); 355 | } 356 | } 357 | 358 | void ImGuiToggleRenderer::DrawLabel(float x_offset) 359 | { 360 | const ImVec2 label_size = ImGui::CalcTextSize(_label, nullptr, true); 361 | 362 | const float half_height = GetHeight() * 0.5f; 363 | const float label_x = _boundingBox.Max.x + _style->ItemInnerSpacing.x + x_offset; 364 | const float label_y = _boundingBox.Min.y + half_height - (label_size.y * 0.5f); 365 | const ImVec2 label_pos = ImVec2(label_x, label_y); 366 | 367 | ImGuiContext& g = *GImGui; 368 | if (g.LogEnabled) 369 | { 370 | ImGui::LogRenderedText(&label_pos, _isMixedValue ? "[~]" : *_value ? "[x]" : "[ ]"); 371 | } 372 | 373 | if (label_size.x > 0.0f) 374 | { 375 | ImGui::RenderText(label_pos, _label); 376 | } 377 | } 378 | 379 | void ImGuiToggleRenderer::UpdateAnimationPercent() 380 | { 381 | // calculate the lerp percentage for animation, 382 | // but default to 1/0 for if we aren't animating at all, 383 | // or 0.5f if we have a mixed value. Also, trying to keep parity with 384 | // undocumented tristate/mixed/indeterminate checkbox (#2644) 385 | 386 | float t = _isMixedValue 387 | ? 0.5f 388 | : (*_value ? 1.0f : 0.0f); 389 | 390 | if (IsAnimated() && _isLastActive) 391 | { 392 | const float t_anim = ImSaturate(ImInvLerp(0.0f, _config.AnimationDuration, _lastActiveTimer)); 393 | t = *_value ? (t_anim) : (1.0f - t_anim); 394 | } 395 | 396 | _animationPercent = t; 397 | } 398 | 399 | void ImGuiToggleRenderer::UpdateStateConfig() 400 | { 401 | if (!IsAnimated()) 402 | { 403 | _state = *_value ? _config.On : _config.Off; 404 | return; 405 | } 406 | 407 | _state.FrameBorderThickness = ImLerp(_config.Off.FrameBorderThickness, _config.On.FrameBorderThickness, _animationPercent); 408 | _state.KnobBorderThickness = ImLerp(_config.Off.KnobBorderThickness, _config.On.KnobBorderThickness, _animationPercent); 409 | _state.KnobInset = ImLerp(_config.Off.KnobInset, _config.On.KnobInset, _animationPercent); 410 | _state.KnobOffset = ImLerp(_config.Off.KnobOffset, _config.On.KnobOffset, _animationPercent); 411 | } 412 | 413 | void ImGuiToggleRenderer::UpdatePalette() 414 | { 415 | const ImGuiTogglePalette* on_candidate = _config.On.Palette; 416 | const ImGuiTogglePalette* off_candidate = _config.Off.Palette; 417 | 418 | if (!IsAnimated()) 419 | { 420 | ImGui::UnionPalette( 421 | &_palette, 422 | *_value ? on_candidate : off_candidate, 423 | _style->Colors, 424 | *_value); 425 | 426 | // store specific colors that shouldn't blend. 427 | _colorA11yGlyphOff = _palette.A11yGlyph; 428 | _colorA11yGlyphOn = _palette.A11yGlyph; 429 | 430 | return; 431 | } 432 | 433 | ImGuiTogglePalette off_unioned; 434 | ImGuiTogglePalette on_unioned; 435 | ImGui::UnionPalette(&off_unioned, off_candidate, _style->Colors, false); 436 | ImGui::UnionPalette(&on_unioned, on_candidate, _style->Colors, true); 437 | 438 | // otherwise, lets lerp them! 439 | ImGui::BlendPalettes(&_palette, off_unioned, on_unioned, _animationPercent); 440 | 441 | // store specific colors that shouldn't blend. 442 | _colorA11yGlyphOff = off_unioned.A11yGlyph; 443 | _colorA11yGlyphOn = on_unioned.A11yGlyph; 444 | } 445 | 446 | ImVec2 ImGuiToggleRenderer::CalculateKnobCenter(float radius, float animation_percent, const ImVec2& offset /*= ImVec2()*/) const 447 | { 448 | const ImVec2 pos = GetPosition(); 449 | const float double_radius = radius * 2.0f; 450 | const float animation_percent_inverse = 1.0f - animation_percent; 451 | 452 | const float knob_x = (pos.x + radius) 453 | + animation_percent * (GetWidth() - double_radius - offset.x) 454 | + (animation_percent_inverse * offset.x); 455 | const float knob_y = pos.y + radius + offset.y; 456 | return ImVec2(knob_x, knob_y); 457 | } 458 | 459 | ImRect ImGuiToggleRenderer::CalculateKnobBounds(float radius, float animation_percent, const ImVec2& offset /*= ImVec2()*/) const 460 | { 461 | const ImVec2 position = GetPosition(); 462 | const float double_radius = radius * 2.0f; 463 | const float animation_percent_inverse = 1.0f - animation_percent; 464 | 465 | const float knob_left = (animation_percent * (GetWidth() - double_radius - offset.x)) 466 | + (animation_percent_inverse * offset.x) 467 | + _state.KnobInset.Left; 468 | const float knob_top = _state.KnobInset.Top + _state.KnobOffset.y; 469 | const float knob_bottom = GetHeight() - _state.KnobInset.Bottom + _state.KnobOffset.y; 470 | const float knob_right = (knob_left - _state.KnobInset.Left) + double_radius - _state.KnobInset.Right; 471 | 472 | // if our offsets in the x or y are close to 0, 473 | // we will just skip drawing the whole thing. 474 | if (ImApproximately(knob_left, knob_right) || 475 | ImApproximately(knob_top, knob_bottom)) 476 | { 477 | return ImRect(); 478 | } 479 | 480 | const ImVec2 knob_min = position + ImVec2(knob_left, knob_top); 481 | const ImVec2 knob_max = position + ImVec2(knob_right, knob_bottom); 482 | 483 | return ImRect(knob_min, knob_max); 484 | } 485 | 486 | void ImGuiToggleRenderer::DrawRectBorder(ImRect bounds, ImU32 color_border, float rounding, float thickness) 487 | { 488 | // the border should only grow "inside" the bounding box, 489 | // so we need to shrink the bounds used to prevent it from puffing out. 490 | const float half_thickness = thickness * 0.5f; 491 | bounds.Expand(-half_thickness); 492 | 493 | _drawList->AddRect(bounds.Min, bounds.Max, color_border, rounding, ImDrawFlags_None, thickness); 494 | } 495 | 496 | void ImGuiToggleRenderer::DrawCircleBorder(const ImVec2& center, float radius, ImU32 color_border, float thickness) 497 | { 498 | // the border should only grow "inside" the bounding box, 499 | // so we need to shrink the radius used to prevent it from puffing out. 500 | const float half_thickness = thickness * 0.5f; 501 | radius -= half_thickness; 502 | 503 | _drawList->AddCircle(center, radius, color_border, 0, thickness); 504 | } 505 | 506 | 507 | void ImGuiToggleRenderer::DrawRectShadow(ImRect bounds, ImU32 color_shadow, float rounding, float thickness) 508 | { 509 | // the shadow should only grow "outside" the bounding box, 510 | // so we need to expand the bounds used to puff it out. 511 | const float half_thickness = thickness * 0.5f; 512 | bounds.Expand(half_thickness); 513 | 514 | _drawList->AddRect(bounds.Min, bounds.Max, color_shadow, rounding, ImDrawFlags_None, thickness); 515 | } 516 | 517 | void ImGuiToggleRenderer::DrawCircleShadow(const ImVec2& center, float radius, ImU32 color_border, float thickness) 518 | { 519 | // the shadow should only grow "outside" the bounding box, 520 | // so we need to expand the radius used to puff it out. 521 | const float half_thickness = thickness * 0.5f; 522 | radius += half_thickness; 523 | 524 | _drawList->AddCircle(center, radius, color_border, 0, thickness); 525 | } 526 | -------------------------------------------------------------------------------- /imgui_toggle_renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef IMGUI_DEFINE_MATH_OPERATORS 4 | #define IMGUI_DEFINE_MATH_OPERATORS 5 | #endif // IMGUI_DEFINE_MATH_OPERATORS 6 | #include "imgui.h" 7 | #include "imgui_internal.h" 8 | 9 | #include "imgui_toggle.h" 10 | #include "imgui_toggle_palette.h" 11 | 12 | 13 | class ImGuiToggleRenderer 14 | { 15 | public: 16 | ImGuiToggleRenderer(); 17 | ImGuiToggleRenderer(const char* label, bool* value, const ImGuiToggleConfig& user_config); 18 | void SetConfig(const char* label, bool* value, const ImGuiToggleConfig& user_config); 19 | bool Render(); 20 | 21 | private: 22 | // toggle state & context 23 | ImGuiToggleConfig _config; 24 | ImGuiToggleStateConfig _state; 25 | ImGuiTogglePalette _palette; 26 | 27 | bool _isMixedValue; 28 | bool _isHovered; 29 | bool _isLastActive; 30 | float _lastActiveTimer; 31 | float _animationPercent; 32 | 33 | // imgui specific context 34 | const ImGuiStyle* _style; 35 | ImDrawList* _drawList; 36 | ImGuiID _id; 37 | 38 | // raw ui value & label 39 | const char* _label; 40 | bool* _value; 41 | 42 | // calculated values 43 | ImRect _boundingBox; 44 | ImVec4 _colorA11yGlyphOff; 45 | ImVec4 _colorA11yGlyphOn; 46 | 47 | // inline accessors 48 | inline float GetWidth() const { return _boundingBox.GetWidth(); } 49 | inline float GetHeight() const { return _boundingBox.GetHeight(); } 50 | inline ImVec2 GetPosition() const { return _boundingBox.Min; } 51 | inline ImVec2 GetToggleSize() const { return _boundingBox.GetSize(); } 52 | inline bool IsAnimated() const { return (_config.Flags & ImGuiToggleFlags_Animated) != 0 && _config.AnimationDuration > 0; } 53 | inline bool HasBorderedFrame() const { return (_config.Flags & ImGuiToggleFlags_BorderedFrame) != 0 && _state.FrameBorderThickness > 0; } 54 | inline bool HasShadowedFrame() const { return (_config.Flags & ImGuiToggleFlags_ShadowedFrame) != 0 && _state.FrameShadowThickness > 0; } 55 | inline bool HasBorderedKnob() const { return (_config.Flags & ImGuiToggleFlags_BorderedKnob) != 0 && _state.KnobBorderThickness > 0; } 56 | inline bool HasShadowedKnob() const { return (_config.Flags & ImGuiToggleFlags_ShadowedKnob) != 0 && _state.KnobShadowThickness > 0; } 57 | inline bool HasA11yGlyphs() const { return (_config.Flags & ImGuiToggleFlags_A11y) != 0; } 58 | inline bool HasCircleKnob() const { return _config.KnobRounding >= 1.0f; } 59 | inline bool HasRectangleKnob() const { return _config.KnobRounding < 1.0f; } 60 | 61 | // behavior 62 | void ValidateConfig(); 63 | bool ToggleBehavior(const ImRect& interaction_bounding_box); 64 | 65 | // drawing - general 66 | void DrawToggle(); 67 | 68 | // drawing - frame 69 | void DrawFrame(ImU32 color_frame); 70 | 71 | // drawing a11y 72 | void DrawA11yDot(const ImVec2& pos, ImU32 color); 73 | void DrawA11yGlyph(ImVec2 pos, ImU32 color, bool state, float radius, float thickness); 74 | void DrawA11yLabel(ImVec2 pos, ImU32 color, const char* label); 75 | void DrawA11yFrameOverlay(float knob_radius, bool state); 76 | void DrawA11yFrameOverlays(float knob_radius); 77 | 78 | // drawing - knob 79 | void DrawCircleKnob(float radius, ImU32 color_knob); 80 | void DrawRectangleKnob(float radius, ImU32 color_knob); 81 | 82 | // drawing - label 83 | void DrawLabel(float x_offset); 84 | 85 | // state updating 86 | void UpdateAnimationPercent(); 87 | void UpdateStateConfig(); 88 | void UpdatePalette(); 89 | 90 | // helpers 91 | ImVec2 CalculateKnobCenter(float radius, float animation_percent, const ImVec2& offset = ImVec2()) const; 92 | ImRect CalculateKnobBounds(float radius, float animation_percent, const ImVec2& offset = ImVec2()) const; 93 | void DrawRectBorder(ImRect bounds, ImU32 color_border, float rounding, float thickness); 94 | void DrawCircleBorder(const ImVec2& center, float radius, ImU32 color_border, float thickness); 95 | void DrawRectShadow(ImRect bounds, ImU32 color_shadow, float rounding, float thickness); 96 | void DrawCircleShadow(const ImVec2& center, float radius, ImU32 color_shadow, float thickness); 97 | }; 98 | --------------------------------------------------------------------------------