├── .gitignore ├── LICENSE ├── Readme.md ├── atlas.go ├── clipboard.go ├── color.go ├── context.go ├── cpp.go ├── cpp └── imgui_demo.cpp ├── debug.go ├── demo.go ├── disabled.go ├── dragdrop.go ├── drawing.go ├── drawlist.go ├── drawlist_splitter.go ├── enums.go ├── example ├── example.go ├── font.png ├── platforms │ └── glfw │ │ └── glfw.go └── renderers │ ├── opengl.go │ └── shader │ ├── main.frag │ └── main.vert ├── font.go ├── font_glyphs.go ├── fontatlas.go ├── frame.go ├── funcs.go ├── garbage.go ├── go.mod ├── go.sum ├── golang └── golang.go ├── helpers.go ├── id_stack.go ├── id_stack_test.go ├── imstb_textedit.go ├── internal.go ├── internal_funcs.go ├── keyboard.go ├── layout.go ├── listclipper.go ├── logging.go ├── math.go ├── media └── quickbrownfox.png ├── misc.utillities.go ├── mouse.go ├── navigation.go ├── popups.go ├── proggy.ttf ├── rendering.go ├── rendering.text.go ├── scrolling.go ├── settings.go ├── shading.go ├── spacing.go ├── stb ├── stb.go ├── stbrp │ └── stbrp.go └── stbtt │ └── stbtt.go ├── storage.go ├── style.go ├── styles.go ├── tables.columns.go ├── tables.internal.go ├── tables.public.go ├── tabs.go ├── text.go ├── textfilters.go ├── tooltips.go ├── types.go ├── viewports.go ├── widgets.behaviour.go ├── widgets.button.go ├── widgets.checkbox.go ├── widgets.colorpicker.go ├── widgets.combo.go ├── widgets.drag.go ├── widgets.go ├── widgets.helpers.go ├── widgets.image.go ├── widgets.input.go ├── widgets.input.scalar.go ├── widgets.interface.go ├── widgets.listbox.go ├── widgets.menu.go ├── widgets.plotting.go ├── widgets.progress.go ├── widgets.query.go ├── widgets.seperator.go ├── widgets.slider.go ├── widgets.tabbar.go ├── widgets.text.go ├── widgets.tree.go ├── windows.begin.go ├── windows.children.go ├── windows.focus.go ├── windows.go ├── windows.helpers.go ├── windows.mut.go ├── windows.queries.go ├── windows.render.go ├── windows.resize.go └── windows.scroll.go /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode/settings.json 2 | example/imgui.ini 3 | **/imgui.ini 4 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2022 Omar Cornut 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # GOLANG IMGUI PORTING EFFORTS 2 | 3 | License will be MIT 4 | 5 | A pure Go port of imgui as of github.com/ocornut/imgui commit 5ee40c8d34bea3009cf462ec963225bd22067e5e 6 | *(the remaining IMGUI code that needs to be ported from this commit are included in the repository)* 7 | 8 | It is important to me to have an Imgui package in pure Go, without any C code. 9 | Ideally I would like the entire functionality of Imgui ported, including all 10 | current widgets and features. 11 | 12 | The end goal is to have the complete IMGUI demo window running 13 | natively in Go (so far demo window is only partially implemented). 14 | 15 | ## Current Status 16 | 17 | The example directory contains a backend for glfw/opengl3 and a buildable 18 | program. 19 | 20 | `go get && go mod download && go build` with Go 1.16+ should build it just fine. 21 | 22 | Besides the demo window, the port is technically complete but I am sure to have made mistakes during the port which have 23 | introduced bugs here and there. 24 | 25 | ![The Quick Brown Fox Jumps Over The Lazy Dog](media/quickbrownfox.png) 26 | 27 | ## How to debug bugs 28 | 29 | 1. Clone imgui and checkout the commit hash specified at the top of this Readme, 30 | 2. Create an identical program in Go and C++ 31 | 3. Either use a debugger and/or insert print lines and figure out how, why and when values are diverging. 32 | 33 | ## API Differences 34 | 35 | I'm trying to stick as close to the C++ API as possible at the moment, at some stage, I think it would be nice to clean 36 | up the API to make it more idiomatic for Go 37 | 38 | ## Helpful tips for porting C++ to Go 39 | 40 | I have stumbled upon a number of 41 | roadblocks, and I have included a list of things to keep in mind when porting from C++ to Go. 42 | 43 | 1. Go has a stronger type system then C++, therefore any integer conversions need to be explicit 44 | 45 | var x uint16 46 | var y int32 47 | y = int32(x) //annoying but required for porting 48 | 49 | 2. A lot of the code I have seen, passes arrays by pointer, in Go 50 | this can generally be replaced by a slice. 51 | 52 | STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); 53 | //becomes 54 | func GetNumberOfFonts(data []byte) int { 55 | 56 | 3. Sometimes these C++ array pointers are incremented, I find this to be a very 57 | strange pattern but it can be replicated in Go by using a slice operation. 58 | Keep in mind that the solution below only works for pointer increments, pointer decrements require a different 59 | approach (passing an additional index offset). 60 | 61 | //C++ 62 | stbtt_uint8 *points; 63 | stbtt_uint8 flags = *points++; 64 | 65 | //Go 66 | var points []byte 67 | var flags byte = points[0] //get the first byte of the 68 | points = points[1:] //slice operation, moves the pointer forward by one byte 69 | slice 70 | 71 | 72 | 4. Go doesn't have struct/array constants, but you can just use a variable to 73 | hold the value. 74 | 75 | //C++ 76 | const ImVec2 zero = ImVec2(0,0); 77 | //Go 78 | var zero = ImVec2{0,0} 79 | 80 | 5. Go doesn't have `static`, so the porting of the imgui debug window is 81 | painful, perhaps it can be broken up into seperate functions? I don't know. 82 | 83 | 6. If a C++ function uses a *void pointer, this can be replaced with a Go 84 | interface{} type, any value can be assigned to it. 85 | 86 | //C++ 87 | void *userdata; 88 | //Go 89 | var userdata interface{} 90 | 91 | 7. Imgui appears to support callbacks of some kind, they are normally 92 | passed as a struct, I am not too sure how this works in C++, but Go 93 | has function types and closures that can be used instead. 94 | 95 | //C++ 96 | typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data) 97 | //Go 98 | type ImGuiSizeCallback func(data *ImGuiSizeCallbackData) 99 | 100 | 101 | 8. Go doesn't have ternary operator. 102 | 103 | //C++ 104 | int x = a ? b : c; 105 | //Go 106 | var x int32 107 | if a { 108 | x = b 109 | } else { 110 | x = c 111 | } 112 | 113 | 9. Go is garbage collected, so the ImGui/STB memory management can 114 | be removed. Can just use builtin `new` and `make` for allocations. 115 | 116 | 10. I found it useful to move cpp files into the different packages and go through them function-by-function and delete 117 | them as I go to track porting progress. It's nice to see the lines of code in the file you are working on slowly go 118 | down as you 119 | port them. 120 | -------------------------------------------------------------------------------- /clipboard.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // Clipboard Utilities 4 | // - Also see the LogToClipboard() function to capture GUI into clipboard, or easily output text data to the clipboard. 5 | 6 | func GetClipboardText() string { 7 | var g = GImGui 8 | if g.IO.GetClipboardTextFn != nil { 9 | return g.IO.GetClipboardTextFn(g.IO.ClipboardUserData) 10 | } 11 | return "" 12 | } 13 | 14 | func SetClipboardText(text string) { 15 | var g = GImGui 16 | if g.IO.SetClipboardTextFn != nil { 17 | g.IO.SetClipboardTextFn(g.IO.ClipboardUserData, text) 18 | } 19 | } 20 | 21 | // GetClipboardTextFn_DefaultImpl Local Dear ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers. 22 | func GetClipboardTextFn_DefaultImpl(any) string { 23 | var g = GImGui 24 | if len(g.ClipboardHandlerData) == 0 { 25 | return "" 26 | } 27 | return string(g.ClipboardHandlerData) 28 | } 29 | 30 | func SetClipboardTextFn_DefaultImpl(_ any, text string) { 31 | var g = GImGui 32 | g.ClipboardHandlerData = g.ClipboardHandlerData[:0] 33 | g.ClipboardHandlerData = []byte(text) 34 | } 35 | -------------------------------------------------------------------------------- /color.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | func ColorConvertFloat4ToU32(in ImVec4) ImU32 { 4 | var out ImU32 5 | out = ((ImU32)(IM_F32_TO_INT8_SAT(in.x))) << IM_COL32_R_SHIFT 6 | out |= ((ImU32)(IM_F32_TO_INT8_SAT(in.y))) << IM_COL32_G_SHIFT 7 | out |= ((ImU32)(IM_F32_TO_INT8_SAT(in.z))) << IM_COL32_B_SHIFT 8 | out |= ((ImU32)(IM_F32_TO_INT8_SAT(in.w))) << IM_COL32_A_SHIFT 9 | return out 10 | } 11 | 12 | func ImAlphaBlendColors(col_a, col_b ImU32) ImU32 { 13 | var t = float((col_b>>IM_COL32_A_SHIFT)&0xFF) / 255 14 | var r = int(ImLerp(float((int)(col_a>>IM_COL32_R_SHIFT)&0xFF), float((int)(col_b>>IM_COL32_R_SHIFT)&0xFF), t)) 15 | var g = int(ImLerp(float((int)(col_a>>IM_COL32_G_SHIFT)&0xFF), float((int)(col_b>>IM_COL32_G_SHIFT)&0xFF), t)) 16 | var b = int(ImLerp(float((int)(col_a>>IM_COL32_B_SHIFT)&0xFF), float((int)(col_b>>IM_COL32_B_SHIFT)&0xFF), t)) 17 | return IM_COL32(byte(r), byte(g), byte(b), 0xFF) 18 | } 19 | 20 | // ColorConvertU32ToFloat4 Color Utilities 21 | func ColorConvertU32ToFloat4(in ImU32) ImVec4 { 22 | var s float = 1.0 / 255.0 23 | return ImVec4{ 24 | float((in>>IM_COL32_R_SHIFT)&0xFF) * s, 25 | float((in>>IM_COL32_G_SHIFT)&0xFF) * s, 26 | float((in>>IM_COL32_B_SHIFT)&0xFF) * s, 27 | float((in>>IM_COL32_A_SHIFT)&0xFF) * s} 28 | } 29 | 30 | // ColorConvertRGBtoHSV Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592 31 | // Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv 32 | func ColorConvertRGBtoHSV(r float, g float, b float, out_h, out_s, out_v *float) { 33 | var K float = 0 34 | if g < b { 35 | g, b = b, g 36 | K = -1 37 | } 38 | if r < g { 39 | r, g = g, r 40 | K = float(-2)/float(6) - K 41 | } 42 | 43 | var chroma = r 44 | if g < b { 45 | chroma -= g 46 | } else { 47 | chroma -= b 48 | } 49 | *out_h = ImFabs(K + (g-b)/(6*chroma+1e-20)) 50 | *out_s = chroma / (r + 1e-20) 51 | *out_v = r 52 | } 53 | 54 | // ColorConvertHSVtoRGB Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593 55 | // also http://en.wikipedia.org/wiki/HSL_and_HSV 56 | func ColorConvertHSVtoRGB(h float, s float, v float, out_r, out_g, out_b *float) { 57 | if s == 0.0 { 58 | // gray 59 | *out_r = v 60 | *out_g = v 61 | *out_b = v 62 | return 63 | } 64 | 65 | h = ImFmod(h, 1.0) / (60.0 / 360.0) 66 | var i = (int)(h) 67 | var f = h - (float)(i) 68 | var p = v * (1.0 - s) 69 | var q = v * (1.0 - s*f) 70 | var t = v * (1.0 - s*(1.0-f)) 71 | 72 | switch i { 73 | case 0: 74 | *out_r = v 75 | *out_g = t 76 | *out_b = p 77 | case 1: 78 | *out_r = q 79 | *out_g = v 80 | *out_b = p 81 | case 2: 82 | *out_r = p 83 | *out_g = v 84 | *out_b = t 85 | case 3: 86 | *out_r = p 87 | *out_g = q 88 | *out_b = v 89 | case 4: 90 | *out_r = t 91 | *out_g = p 92 | *out_b = v 93 | case 5: 94 | fallthrough 95 | default: 96 | *out_r = v 97 | *out_g = p 98 | *out_b = q 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /cpp.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | type double = float64 4 | type int = int32 5 | type uint = uint32 6 | type float = float32 7 | type size_t = uintptr 8 | type char = byte 9 | 10 | /* 11 | func isfalse(x int) bool { 12 | return x == 0 13 | } 14 | */ 15 | 16 | func istrue(x int) bool { 17 | return x != 0 18 | } 19 | 20 | func bool2int(b bool) int { 21 | if b { 22 | return 1 23 | } 24 | return 0 25 | } 26 | 27 | const ( 28 | IM_S8_MIN = -128 29 | IM_S8_MAX = 127 30 | IM_U8_MIN = 0 31 | IM_U8_MAX = 255 32 | IM_S16_MIN = -32768 33 | IM_S16_MAX = 32767 34 | IM_U16_MIN = 0 35 | IM_U16_MAX = 65535 36 | IM_S32_MIN = -2147483648 37 | IM_S32_MAX = 2147483647 38 | IM_U32_MIN = 0 39 | IM_U32_MAX = 4294967295 40 | IM_S64_MIN = -9223372036854775808 41 | IM_S64_MAX = 9223372036854775807 42 | IM_U64_MIN = 0 43 | IM_U64_MAX = 18446744073709551615 44 | ) 45 | -------------------------------------------------------------------------------- /disabled.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // Disabling [BETA API] 4 | // - Disable all user interactions and dim items visuals (applying style.DisabledAlpha over current colors) 5 | // - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled) 6 | // - BeginDisabled(false) essentially does nothing useful but is provided to facilitate use of boolean expressions. If you can a calling BeginDisabled(False)/EndDisabled() best to a it. 7 | 8 | // BeginDisabled BeginDisabled()/EndDisabled() 9 | // - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled) 10 | // - Visually this is currently altering alpha, but it is expected that in a future styling system this would work differently. 11 | // - Feedback welcome at https://github.com/ocornut/imgui/issues/211 12 | // - BeginDisabled(false) essentially does nothing useful but is provided to facilitate use of boolean expressions. If you can avoid calling BeginDisabled(False)/EndDisabled() best to avoid it. 13 | // - Optimized shortcuts instead of PushStyleVar() + PushItemFlag() 14 | func BeginDisabled(disabled bool /*= true*/) { 15 | var g = GImGui 16 | var was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0 17 | if !was_disabled && disabled { 18 | g.DisabledAlphaBackup = g.Style.Alpha 19 | g.Style.Alpha *= g.Style.DisabledAlpha // PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * g.Style.DisabledAlpha); 20 | } 21 | if was_disabled || disabled { 22 | g.CurrentItemFlags |= ImGuiItemFlags_Disabled 23 | } 24 | g.ItemFlagsStack = append(g.ItemFlagsStack, g.CurrentItemFlags) 25 | } 26 | 27 | func EndDisabled() { 28 | var g = GImGui 29 | var was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0 30 | //PopItemFlag(); 31 | g.ItemFlagsStack = g.ItemFlagsStack[:len(g.ItemFlagsStack)-1] 32 | g.CurrentItemFlags = g.ItemFlagsStack[len(g.ItemFlagsStack)-1] 33 | if was_disabled && (g.CurrentItemFlags&ImGuiItemFlags_Disabled) == 0 { 34 | g.Style.Alpha = g.DisabledAlphaBackup //PopStyleVar(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /drawlist_splitter.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | //----------------------------------------------------------------------------- 4 | // [SECTION] ImDrawListSplitter 5 | //----------------------------------------------------------------------------- 6 | // FIXME: This may be a little confusing, trying to be a little too low-level/optimal instead of just doing vector swap.. 7 | //----------------------------------------------------------------------------- 8 | 9 | // Split/Merge functions are used to split the draw list into different layers which can be drawn into out of order. 10 | // This is used by the Columns/Tables API, so items of each column can be batched together in a same draw call. 11 | type ImDrawListSplitter struct { 12 | _Current int // Current channel number (0) 13 | _Count int // Number of active channels (1+) 14 | _Channels []ImDrawChannel // Draw channels (not resized down so _Count might be < Channels.Size) 15 | } 16 | 17 | func (this *ImDrawListSplitter) Clear() { 18 | this._Current = 0 19 | this._Count = 1 // Do not clear Channels[] so our allocations are reused next frame 20 | } 21 | 22 | func (this *ImDrawListSplitter) ClearFreeMemory() { 23 | for i := int(0); i < int(len(this._Channels)); i++ { 24 | if i == this._Current { 25 | this._Channels[i] = ImDrawChannel{} // Current channel is a copy of CmdBuffer/IdxBuffer, don't destruct again 26 | } 27 | this._Channels[i]._CmdBuffer = nil 28 | this._Channels[i]._IdxBuffer = nil 29 | } 30 | this._Current = 0 31 | this._Count = 1 32 | this._Channels = nil 33 | } 34 | 35 | func (this *ImDrawListSplitter) Split(draw_list *ImDrawList, channels_count int) { 36 | IM_ASSERT_USER_ERROR(this._Current == 0 && this._Count <= 1, "Nested channel splitting is not supported. Please use separate instances of ImDrawListSplitter.") 37 | var old_channels_count = int(len(this._Channels)) 38 | if old_channels_count < channels_count { 39 | this._Channels = append(this._Channels, make([]ImDrawChannel, channels_count-old_channels_count)...) 40 | } 41 | this._Count = channels_count 42 | 43 | // Channels[] (24/32 bytes each) hold storage that we'll swap with draw_list._CmdBuffer/_IdxBuffer 44 | // The content of Channels[0] at this point doesn't matter. We clear it to make state tidy in a debugger but we don't strictly need to. 45 | // When we switch to the next channel, we'll copy draw_list._CmdBuffer/_IdxBuffer into Channels[0] and then Channels[1] into draw_list.CmdBuffer/_IdxBuffer 46 | this._Channels[0] = ImDrawChannel{} 47 | for i := int(1); i < channels_count; i++ { 48 | if i >= old_channels_count { 49 | this._Channels[i] = ImDrawChannel{} 50 | } else { 51 | this._Channels[i]._CmdBuffer = this._Channels[i]._CmdBuffer[:0] 52 | this._Channels[i]._IdxBuffer = this._Channels[i]._IdxBuffer[:0] 53 | } 54 | } 55 | } 56 | 57 | func (this *ImDrawListSplitter) Merge(draw_list *ImDrawList) { 58 | // Note that we never use or rely on _Channels.Size because it is merely a buffer that we never shrink back to 0 to keep all sub-buffers ready for use. 59 | if this._Count <= 1 { 60 | return 61 | } 62 | 63 | this.SetCurrentChannel(draw_list, 0) 64 | draw_list._PopUnusedDrawCmd() 65 | 66 | // Calculate our final buffer sizes. Also fix the incorrect IdxOffset values in each command. 67 | var new_cmd_buffer_count int = 0 68 | var new_idx_buffer_count int = 0 69 | 70 | var last_cmd *ImDrawCmd 71 | if this._Count > 0 && len(draw_list.CmdBuffer) > 0 { 72 | last_cmd = &draw_list.CmdBuffer[len(draw_list.CmdBuffer)-1] 73 | } 74 | 75 | var idx_offset int 76 | if last_cmd != nil { 77 | idx_offset = int(last_cmd.IdxOffset + last_cmd.ElemCount) 78 | } 79 | 80 | for i := int(1); i < this._Count; i++ { 81 | var ch = &this._Channels[i] 82 | 83 | // Equivalent of PopUnusedDrawCmd() for this channel's cmdbuffer and except we don't need to test for UserCallback. 84 | if len(ch._CmdBuffer) > 0 && ch._CmdBuffer[len(ch._CmdBuffer)-1].ElemCount == 0 { 85 | ch._CmdBuffer = ch._CmdBuffer[:len(ch._CmdBuffer)-1] 86 | } 87 | 88 | if len(ch._CmdBuffer) > 0 && last_cmd != nil { 89 | var next_cmd = &ch._CmdBuffer[0] 90 | if last_cmd.HeaderEquals(next_cmd) && last_cmd.UserCallback == nil && next_cmd.UserCallback == nil { 91 | // Merge previous channel last draw command with current channel first draw command if matching. 92 | last_cmd.ElemCount += next_cmd.ElemCount 93 | idx_offset += int(next_cmd.ElemCount) 94 | copy(ch._CmdBuffer, ch._CmdBuffer[1:]) // FIXME-OPT: Improve for multiple merges. 95 | } 96 | } 97 | if len(ch._CmdBuffer) > 0 { 98 | last_cmd = &ch._CmdBuffer[len(ch._CmdBuffer)-1] 99 | } 100 | new_cmd_buffer_count += int(len(ch._CmdBuffer)) 101 | new_idx_buffer_count += int(len(ch._IdxBuffer)) 102 | for cmd_n := range ch._CmdBuffer { 103 | ch._CmdBuffer[cmd_n].IdxOffset = uint(idx_offset) 104 | idx_offset += int(ch._CmdBuffer[cmd_n].ElemCount) 105 | } 106 | } 107 | draw_list.CmdBuffer = append(draw_list.CmdBuffer, make([]ImDrawCmd, new_cmd_buffer_count)...) 108 | draw_list.IdxBuffer = append(draw_list.IdxBuffer, make([]ImDrawIdx, new_idx_buffer_count)...) 109 | 110 | // Write commands and indices in order (they are fairly small structures, we don't copy vertices only indices) 111 | var cmd_write = draw_list.CmdBuffer[int(len(draw_list.CmdBuffer))-new_cmd_buffer_count:] 112 | var idx_write = draw_list.IdxBuffer[int(len(draw_list.IdxBuffer))-new_idx_buffer_count:] 113 | for i := int(1); i < this._Count; i++ { 114 | var ch = this._Channels[i] 115 | if sz := len(ch._CmdBuffer); sz != 0 { 116 | copy(cmd_write, ch._CmdBuffer[sz:]) 117 | cmd_write = cmd_write[sz:] 118 | } 119 | if sz := len(ch._IdxBuffer); sz != 0 { 120 | copy(idx_write, ch._IdxBuffer[sz:]) 121 | idx_write = idx_write[sz:] 122 | } 123 | } 124 | draw_list._IdxWritePtr = int(len(draw_list.IdxBuffer) - len(idx_write)) 125 | 126 | // Ensure there's always a non-callback draw command trailing the command-buffer 127 | if len(draw_list.CmdBuffer) == 0 || draw_list.CmdBuffer[len(draw_list.CmdBuffer)-1].UserCallback != nil { 128 | draw_list.AddDrawCmd() 129 | } 130 | 131 | // If current command is used with different settings we need to add a new command 132 | var curr_cmd = &draw_list.CmdBuffer[len(draw_list.CmdBuffer)-1] 133 | if curr_cmd.ElemCount == 0 { 134 | curr_cmd.HeaderCopyFromHeader(draw_list._CmdHeader) 135 | } else if curr_cmd.HeaderEqualsHeader(&draw_list._CmdHeader) { 136 | draw_list.AddDrawCmd() 137 | } 138 | 139 | this._Count = 1 140 | } 141 | 142 | func (this *ImDrawListSplitter) SetCurrentChannel(draw_list *ImDrawList, idx int) { 143 | IM_ASSERT(idx >= 0 && idx < this._Count) 144 | if this._Current == idx { 145 | return 146 | } 147 | 148 | // Overwrite ImVector (12/16 bytes), four times. This is merely a silly optimization instead of doing .swap() 149 | copy(this._Channels[this._Current]._CmdBuffer, draw_list.CmdBuffer) 150 | copy(this._Channels[this._Current]._IdxBuffer, draw_list.IdxBuffer) 151 | this._Current = idx 152 | copy(draw_list.CmdBuffer, this._Channels[idx]._CmdBuffer) 153 | copy(draw_list.IdxBuffer, this._Channels[idx]._IdxBuffer) 154 | draw_list._IdxWritePtr = int(len(draw_list.IdxBuffer)) 155 | 156 | // If current command is used with different settings we need to add a new command 157 | var curr_cmd *ImDrawCmd 158 | if !(len(draw_list.CmdBuffer) == 0) { 159 | curr_cmd = &draw_list.CmdBuffer[len(draw_list.CmdBuffer)-1] 160 | } 161 | if curr_cmd == nil { 162 | draw_list.AddDrawCmd() 163 | } else if curr_cmd.ElemCount == 0 { 164 | curr_cmd.HeaderCopyFromHeader(draw_list._CmdHeader) // Copy ClipRect, TextureId, VtxOffset 165 | } else if curr_cmd.HeaderEqualsHeader(&draw_list._CmdHeader) { 166 | draw_list.AddDrawCmd() 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /example/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/Splizard/imgui" 8 | 9 | platforms "github.com/Splizard/imgui/example/platforms/glfw" 10 | "github.com/Splizard/imgui/example/renderers" 11 | ) 12 | 13 | // Renderer covers rendering imgui draw data. 14 | type Renderer interface { 15 | // PreRender causes the display buffer to be prepared for new output. 16 | PreRender(clearColor [3]float32) 17 | // Render draws the provided imgui draw data. 18 | Render(displaySize [2]float32, framebufferSize [2]float32, drawData imgui.ImDrawData) 19 | } 20 | 21 | func main() { 22 | imgui.CreateContext(nil) 23 | imgui.GetCurrentContext().IO.IniFilename = "" // disable imgui.ini 24 | 25 | io := imgui.GetIO() 26 | 27 | p, err := platforms.NewGLFW(io, platforms.GLFWClientAPIOpenGL3) 28 | if err != nil { 29 | _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) 30 | os.Exit(-1) 31 | } 32 | defer p.Dispose() 33 | // the swap interval is 1 by default, use 34 | // p.SwapInterval(n) to set it 35 | 36 | r, err := renderers.NewOpenGL3(io) 37 | if err != nil { 38 | _, _ = fmt.Fprintf(os.Stderr, "%v\n", err) 39 | os.Exit(-1) 40 | } 41 | defer r.Dispose() 42 | 43 | //imgui.CurrentIO().SetClipboard(clipboard{platform: p}) 44 | 45 | /* 46 | showDemoWindow := false 47 | showGoDemoWindow := false 48 | clearColor := [3]float32{0.0, 0.0, 0.0} 49 | f := float32(0) 50 | counter := 0 51 | showAnotherWindow := false 52 | */ 53 | clearColor := [3]float32{0.45, 0.55, 0.60} 54 | sliderValue := float32(0) 55 | inputText := []byte{} 56 | 57 | for !p.ShouldStop() { 58 | p.ProcessEvents() 59 | 60 | // Signal start of a new frame 61 | p.NewFrame() 62 | imgui.NewFrame() 63 | 64 | imgui.ShowMetricsWindow(nil) 65 | imgui.ShowDemoWindow(nil) 66 | 67 | // 1. Show a simple window. 68 | // Tip: if we don't call imgui.Begin()/imgui.End() the widgets automatically appears in a window called "Debug". 69 | //{ 70 | //imgui.Text("ภาษาไทย测试조선말") // To display these, you'll need to register a compatible font 71 | 72 | for i := 0; i < 5; i++ { 73 | imgui.Text("the quick brown fox jumped over the lazy dog") // Display some text 74 | imgui.Text("THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG") // Display some text (all caps, as if drawn manually) 75 | } 76 | 77 | imgui.SliderFloat( 78 | "Slider", 79 | &sliderValue, // value 80 | 0, // minimum value 81 | 10, // maximum value 82 | "%.3f", // float format string (round to 3 digits after the decimal point) 83 | imgui.ImGuiSliderFlags_None, 84 | ) 85 | 86 | // doesn't work yet 87 | imgui.InputText( 88 | "Text input", 89 | &inputText, 90 | imgui.ImGuiInputTextFlags_None, 91 | nil, 92 | nil, 93 | ) 94 | 95 | imgui.ColorEdit3( 96 | "Clear color", 97 | &clearColor, 98 | imgui.ImGuiColorEditFlags_DisplayRGB, 99 | ) 100 | 101 | // for some reason, tables will panic if they're in a CollapsingHeader 102 | if imgui.BeginTable("test table", 3, 0, imgui.ImVec2{}, 0) { 103 | imgui.TableNextColumn() 104 | imgui.Checkbox("checkbox!", nil) 105 | imgui.TableNextColumn() 106 | imgui.Checkbox("Another checkbox", nil) 107 | imgui.TableNextRow(0, 0) 108 | imgui.TableNextColumn() 109 | imgui.Text("text 1") 110 | imgui.TableNextColumn() 111 | imgui.Text("text 2") 112 | imgui.EndTable() 113 | } 114 | 115 | /* 116 | imgui.SliderFloat("float", &f, 0.0, 1.0) // Edit 1 float using a slider from 0.0f to 1.0f 117 | { 118 | imgui.ColorEdit3("clear color", &clearColor) // Edit 3 floats representing a color 119 | 120 | imgui.Checkbox("Demo Window", &showDemoWindow) // Edit bools storing our window open/close state 121 | imgui.Checkbox("Go Demo Window", &showGoDemoWindow) 122 | imgui.Checkbox("Another Window", &showAnotherWindow) 123 | 124 | if imgui.Button("Button") { // Buttons return true when clicked (most widgets return true when edited/activated) 125 | counter++ 126 | } 127 | imgui.SameLine() 128 | imgui.Text(fmt.Sprintf("counter = %d", counter)) 129 | 130 | imgui.Text(fmt.Sprintf("Application average %.3f ms/frame (%.1f FPS)", 131 | millisPerSecond/imgui.CurrentIO().Framerate(), imgui.CurrentIO().Framerate())) 132 | } 133 | 134 | // 2. Show another simple window. In most cases you will use an explicit Begin/End pair to name your windows. 135 | if showAnotherWindow { 136 | // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) 137 | imgui.BeginV("Another window", &showAnotherWindow, 0) 138 | imgui.Text("Hello from another window!") 139 | if imgui.Button("Close Me") { 140 | showAnotherWindow = false 141 | } 142 | imgui.End() 143 | } 144 | 145 | // 3. Show the ImGui demo window. Most of the sample code is in imgui.ShowDemoWindow(). 146 | // Read its code to learn more about Dear ImGui! 147 | if showDemoWindow { 148 | // Normally user code doesn't need/want to call this because positions are saved in .ini file anyway. 149 | // Here we just want to make the demo initial state a bit more friendly! 150 | const demoX = 650 151 | const demoY = 20 152 | imgui.SetNextWindowPosV(imgui.Vec2{X: demoX, Y: demoY}, imgui.ConditionFirstUseEver, imgui.Vec2{}) 153 | 154 | imgui.ShowDemoWindow(&showDemoWindow) 155 | } 156 | if showGoDemoWindow { 157 | demo.Show(&showGoDemoWindow) 158 | } 159 | */ 160 | 161 | // Rendering 162 | imgui.Render() // This call only creates the draw data list. Actual rendering to framebuffer is done below. 163 | 164 | r.PreRender(clearColor) 165 | // A this point, the application could perform its own rendering... 166 | // app.RenderScene() 167 | 168 | r.Render(p.DisplaySize(), p.FramebufferSize(), imgui.GetDrawData()) 169 | p.PostRender() 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /example/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splizard/imgui/c17e6223290dfa6a404aefd13a2280287cccfb64/example/font.png -------------------------------------------------------------------------------- /example/platforms/glfw/glfw.go: -------------------------------------------------------------------------------- 1 | package platforms 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Splizard/imgui" 6 | "github.com/go-gl/glfw/v3.2/glfw" 7 | "math" 8 | "runtime" 9 | ) 10 | 11 | const ( 12 | windowWidth = 1280 13 | windowHeight = 720 14 | 15 | mouseButtonPrimary = 0 16 | mouseButtonSecondary = 1 17 | mouseButtonTertiary = 2 18 | mouseButtonCount = 3 19 | ) 20 | 21 | // StringError describes a basic error with static information. 22 | type StringError string 23 | 24 | // Error returns the string itself. 25 | func (err StringError) Error() string { 26 | return string(err) 27 | } 28 | 29 | const ( 30 | // ErrUnsupportedClientAPI is used in case the API is not available by the platform. 31 | ErrUnsupportedClientAPI = StringError("unsupported ClientAPI") 32 | ) 33 | 34 | // GLFWClientAPI identifies the render system that shall be initialized. 35 | type GLFWClientAPI string 36 | 37 | // This is a list of GLFWClientAPI constants. 38 | const ( 39 | GLFWClientAPIOpenGL2 GLFWClientAPI = "OpenGL2" 40 | GLFWClientAPIOpenGL3 GLFWClientAPI = "OpenGL3" 41 | ) 42 | 43 | // GLFW implements a platform based on github.com/go-gl/glfw (v3.2). 44 | type GLFW struct { 45 | imguiIO *imgui.ImGuiIO 46 | 47 | window *glfw.Window 48 | 49 | time float64 50 | mouseJustPressed [3]bool 51 | } 52 | 53 | // NewGLFW attempts to initialize a GLFW context. 54 | func NewGLFW(io *imgui.ImGuiIO, clientAPI GLFWClientAPI) (*GLFW, error) { 55 | runtime.LockOSThread() 56 | 57 | err := glfw.Init() 58 | if err != nil { 59 | return nil, fmt.Errorf("failed to initialize glfw: %w", err) 60 | } 61 | 62 | switch clientAPI { 63 | case GLFWClientAPIOpenGL2: 64 | glfw.WindowHint(glfw.ContextVersionMajor, 2) 65 | glfw.WindowHint(glfw.ContextVersionMinor, 1) 66 | case GLFWClientAPIOpenGL3: 67 | glfw.WindowHint(glfw.ContextVersionMajor, 3) 68 | glfw.WindowHint(glfw.ContextVersionMinor, 2) 69 | glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) 70 | glfw.WindowHint(glfw.OpenGLForwardCompatible, 1) 71 | default: 72 | glfw.Terminate() 73 | return nil, ErrUnsupportedClientAPI 74 | } 75 | 76 | window, err := glfw.CreateWindow(windowWidth, windowHeight, "ImGui-Go GLFW+"+string(clientAPI)+" example", nil, nil) 77 | if err != nil { 78 | glfw.Terminate() 79 | return nil, fmt.Errorf("failed to create window: %w", err) 80 | } 81 | window.MakeContextCurrent() 82 | glfw.SwapInterval(1) 83 | 84 | platform := &GLFW{ 85 | imguiIO: io, 86 | window: window, 87 | } 88 | platform.setKeyMapping() 89 | platform.installCallbacks() 90 | 91 | return platform, nil 92 | } 93 | 94 | // Dispose cleans up the resources. 95 | func (platform *GLFW) Dispose() { 96 | platform.window.Destroy() 97 | glfw.Terminate() 98 | } 99 | 100 | // ShouldStop returns true if the window is to be closed. 101 | func (platform *GLFW) ShouldStop() bool { 102 | return platform.window.ShouldClose() 103 | } 104 | 105 | // ProcessEvents handles all pending window events. 106 | func (platform *GLFW) ProcessEvents() { 107 | glfw.PollEvents() 108 | } 109 | 110 | // DisplaySize returns the dimension of the display. 111 | func (platform *GLFW) DisplaySize() [2]float32 { 112 | w, h := platform.window.GetSize() 113 | return [2]float32{float32(w), float32(h)} 114 | } 115 | 116 | // FramebufferSize returns the dimension of the framebuffer. 117 | func (platform *GLFW) FramebufferSize() [2]float32 { 118 | w, h := platform.window.GetFramebufferSize() 119 | return [2]float32{float32(w), float32(h)} 120 | } 121 | 122 | // NewFrame marks the begin of a render pass. It forwards all current state to imgui IO. 123 | func (platform *GLFW) NewFrame() { 124 | // Setup display size (every frame to accommodate for window resizing) 125 | displaySize := platform.DisplaySize() 126 | platform.imguiIO.DisplaySize = *imgui.NewImVec2(displaySize[0], displaySize[1]) 127 | 128 | // Setup time step 129 | currentTime := glfw.GetTime() 130 | if platform.time > 0 { 131 | platform.imguiIO.DeltaTime = float32(currentTime - platform.time) 132 | } 133 | platform.time = currentTime 134 | 135 | // Setup inputs 136 | if platform.window.GetAttrib(glfw.Focused) != 0 { 137 | x, y := platform.window.GetCursorPos() 138 | platform.imguiIO.MousePos = *imgui.NewImVec2(float32(x), float32(y)) 139 | } else { 140 | platform.imguiIO.MousePos = *imgui.NewImVec2(-math.MaxFloat32, -math.MaxFloat32) 141 | } 142 | 143 | for i := 0; i < len(platform.mouseJustPressed); i++ { 144 | down := platform.mouseJustPressed[i] || (platform.window.GetMouseButton(glfwButtonIDByIndex[i]) == glfw.Press) 145 | platform.imguiIO.MouseDown[i] = down 146 | platform.mouseJustPressed[i] = false 147 | } 148 | } 149 | 150 | // PostRender performs a buffer swap. 151 | func (platform *GLFW) PostRender() { 152 | platform.window.SwapBuffers() 153 | } 154 | 155 | // SwapInterval sets the GLFW swap interval. 156 | func (platform *GLFW) SwapInterval(interval int) { 157 | glfw.SwapInterval(interval) 158 | } 159 | 160 | func (platform *GLFW) setKeyMapping() { 161 | // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. 162 | platform.imguiIO.KeyMap[imgui.ImGuiKey_Tab] = int32(glfw.KeyTab) 163 | platform.imguiIO.KeyMap[imgui.ImGuiKey_LeftArrow] = int32(glfw.KeyLeft) 164 | platform.imguiIO.KeyMap[imgui.ImGuiKey_RightArrow] = int32(glfw.KeyRight) 165 | platform.imguiIO.KeyMap[imgui.ImGuiKey_UpArrow] = int32(glfw.KeyUp) 166 | platform.imguiIO.KeyMap[imgui.ImGuiKey_DownArrow] = int32(glfw.KeyDown) 167 | platform.imguiIO.KeyMap[imgui.ImGuiKey_PageUp] = int32(glfw.KeyPageUp) 168 | platform.imguiIO.KeyMap[imgui.ImGuiKey_PageDown] = int32(glfw.KeyPageDown) 169 | platform.imguiIO.KeyMap[imgui.ImGuiKey_Home] = int32(glfw.KeyHome) 170 | platform.imguiIO.KeyMap[imgui.ImGuiKey_End] = int32(glfw.KeyEnd) 171 | platform.imguiIO.KeyMap[imgui.ImGuiKey_Insert] = int32(glfw.KeyInsert) 172 | platform.imguiIO.KeyMap[imgui.ImGuiKey_Delete] = int32(glfw.KeyDelete) 173 | platform.imguiIO.KeyMap[imgui.ImGuiKey_Backspace] = int32(glfw.KeyBackspace) 174 | platform.imguiIO.KeyMap[imgui.ImGuiKey_Space] = int32(glfw.KeySpace) 175 | platform.imguiIO.KeyMap[imgui.ImGuiKey_Enter] = int32(glfw.KeyEnter) 176 | platform.imguiIO.KeyMap[imgui.ImGuiKey_Escape] = int32(glfw.KeyEscape) 177 | platform.imguiIO.KeyMap[imgui.ImGuiKey_A] = int32(glfw.KeyA) 178 | platform.imguiIO.KeyMap[imgui.ImGuiKey_C] = int32(glfw.KeyC) 179 | platform.imguiIO.KeyMap[imgui.ImGuiKey_V] = int32(glfw.KeyV) 180 | platform.imguiIO.KeyMap[imgui.ImGuiKey_X] = int32(glfw.KeyX) 181 | platform.imguiIO.KeyMap[imgui.ImGuiKey_Y] = int32(glfw.KeyY) 182 | platform.imguiIO.KeyMap[imgui.ImGuiKey_Z] = int32(glfw.KeyZ) 183 | } 184 | 185 | func (platform *GLFW) installCallbacks() { 186 | platform.window.SetMouseButtonCallback(platform.mouseButtonChange) 187 | platform.window.SetScrollCallback(platform.mouseScrollChange) 188 | platform.window.SetKeyCallback(platform.keyChange) 189 | platform.window.SetCharCallback(platform.charChange) 190 | } 191 | 192 | var glfwButtonIndexByID = map[glfw.MouseButton]int{ 193 | glfw.MouseButton1: mouseButtonPrimary, 194 | glfw.MouseButton2: mouseButtonSecondary, 195 | glfw.MouseButton3: mouseButtonTertiary, 196 | } 197 | 198 | var glfwButtonIDByIndex = map[int]glfw.MouseButton{ 199 | mouseButtonPrimary: glfw.MouseButton1, 200 | mouseButtonSecondary: glfw.MouseButton2, 201 | mouseButtonTertiary: glfw.MouseButton3, 202 | } 203 | 204 | func (platform *GLFW) mouseButtonChange(window *glfw.Window, rawButton glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { 205 | buttonIndex, known := glfwButtonIndexByID[rawButton] 206 | 207 | if known && (action == glfw.Press) { 208 | platform.mouseJustPressed[buttonIndex] = true 209 | } 210 | } 211 | 212 | func (platform *GLFW) mouseScrollChange(window *glfw.Window, x, y float64) { 213 | platform.imguiIO.MouseWheelH += float32(x) 214 | platform.imguiIO.MouseWheel += float32(y) 215 | } 216 | 217 | func (platform *GLFW) keyChange(window *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 218 | if action == glfw.Press { 219 | platform.imguiIO.KeysDown[key] = true 220 | } 221 | if action == glfw.Release { 222 | platform.imguiIO.KeysDown[key] = false 223 | } 224 | 225 | platform.imguiIO.KeyCtrl = (mods & glfw.ModControl) != 0 226 | platform.imguiIO.KeyShift = (mods & glfw.ModShift) != 0 227 | platform.imguiIO.KeyAlt = (mods & glfw.ModAlt) != 0 228 | platform.imguiIO.KeySuper = (mods & glfw.ModSuper) != 0 229 | } 230 | 231 | func (platform *GLFW) charChange(window *glfw.Window, char rune) { 232 | platform.imguiIO.AddInputCharacter(char) 233 | } 234 | 235 | // ClipboardText returns the current clipboard text, if available. 236 | func (platform *GLFW) ClipboardText() (string, error) { 237 | return platform.window.GetClipboardString() 238 | } 239 | 240 | // SetClipboardText sets the text as the current clipboard text. 241 | func (platform *GLFW) SetClipboardText(text string) { 242 | platform.window.SetClipboardString(text) 243 | } 244 | -------------------------------------------------------------------------------- /example/renderers/shader/main.frag: -------------------------------------------------------------------------------- 1 | uniform sampler2D Texture; 2 | 3 | in vec2 Frag_UV; 4 | in vec4 Frag_Color; 5 | 6 | out vec4 Out_Color; 7 | 8 | void main() 9 | { 10 | Out_Color = vec4(Frag_Color.rgb, Frag_Color.a * texture(Texture, Frag_UV.st).r); 11 | //Out_Color = vec4(Frag_Color.rgb, Frag_Color.a); 12 | } -------------------------------------------------------------------------------- /example/renderers/shader/main.vert: -------------------------------------------------------------------------------- 1 | uniform mat4 ProjMtx; 2 | 3 | in vec2 Position; 4 | in vec2 UV; 5 | in vec4 Color; 6 | 7 | out vec2 Frag_UV; 8 | out vec4 Frag_Color; 9 | 10 | void main() 11 | { 12 | Frag_UV = UV; 13 | Frag_Color = Color; 14 | gl_Position = ProjMtx * vec4(Position.xy, 0, 1); 15 | } -------------------------------------------------------------------------------- /funcs.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // GetIO access the IO structure (mouse/keyboard/gamepad inputs, time, various configuration options/flags) 4 | func GetIO() *ImGuiIO { 5 | IM_ASSERT_USER_ERROR(GImGui != nil, "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?") 6 | return &GImGui.IO 7 | } 8 | 9 | // ShowAboutWindow Demo, Debug, Information 10 | func ShowAboutWindow(p_open *bool) { panic("not implemented") } // create About window. display Dear ImGui version, credits and build/system information. 11 | func ShowStyleEditor(ref *ImGuiStyle) { panic("not implemented") } // add style editor block (not a window). you can pass in a reference ImGuiStyle structure to compare to, revert to and save to (else it uses the default style) 12 | func ShowStyleSelector(label string) bool { panic("not implemented") } // add style selector block (not a window), essentially a combo listing the default styles. 13 | func ShowFontSelector(label string) { panic("not implemented") } // add font selector block (not a window), essentially a combo listing the loaded fonts. 14 | 15 | // GetVersion get the compiled version string e.g. "1.80 WIP" (essentially the value for IMGUI_VERSION from the compiled version of imgui.cpp) 16 | func GetVersion() string { 17 | return IMGUI_VERSION 18 | } 19 | 20 | // PushClipRect Clipping 21 | // - Mouse hovering is affected by ImGui::PushClipRect() calls, unlike direct calls to ImDrawList::PushClipRect() which are render only. 22 | // Push a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering. 23 | // - When using this function it is sane to ensure that float are perfectly rounded to integer values, 24 | // so that e.g. (int)(max.x-min.x) in user's render produce correct result. 25 | // - If the code here changes, may need to update code of functions like NextColumn() and PushColumnClipRect(): 26 | // some frequently called functions which to modify both channels and clipping simultaneously tend to use the 27 | // more specialized SetWindowClipRectBeforeSetChannel() to avoid extraneous updates of underlying ImDrawCmds. 28 | func PushClipRect(cr_min ImVec2, cr_max ImVec2, intersect_with_current_clip_rect bool) { 29 | var window = GetCurrentWindow() 30 | window.DrawList.PushClipRect(cr_min, cr_max, intersect_with_current_clip_rect) 31 | window.ClipRect = ImRectFromVec4(&window.DrawList._ClipRectStack[len(window.DrawList._ClipRectStack)-1]) 32 | } 33 | 34 | func PopClipRect() { 35 | var window = GetCurrentWindow() 36 | window.DrawList.PopClipRect() 37 | window.ClipRect = ImRectFromVec4(&window.DrawList._ClipRectStack[len(window.DrawList._ClipRectStack)-1]) 38 | } 39 | 40 | // GetMainViewport Viewports 41 | // - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows. 42 | // - In 'docking' branch with multi-viewport enabled, we extend this concept to have multiple active viewports. 43 | // - In the future we will extend this concept further to also represent Platform Monitor and support a "no main platform window" operation mode. 44 | func GetMainViewport() *ImGuiViewport { 45 | var g = GImGui 46 | return g.Viewports[0] 47 | } // return primary/default viewport. This can never be NULL. 48 | -------------------------------------------------------------------------------- /garbage.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // GcCompactTransientMiscBuffers Garbage collection 4 | func GcCompactTransientMiscBuffers() { 5 | var g = GImGui 6 | g.ItemFlagsStack = nil 7 | g.GroupStack = nil 8 | TableGcCompactSettings() 9 | } 10 | 11 | // GcCompactTransientWindowBuffers Free up/compact internal window buffers, we can use this when a window becomes unused. 12 | // Not freed: 13 | // - ImGuiWindow, ImGuiWindowSettings, Name, StateStorage, ColumnsStorage (may hold useful data) 14 | // This should have no noticeable visual effect. When the window reappear however, expect new allocation/buffer growth/copy cost. 15 | func GcCompactTransientWindowBuffers(window *ImGuiWindow) { 16 | window.MemoryCompacted = true 17 | window.MemoryDrawListIdxCapacity = int(cap(window.DrawList.IdxBuffer)) 18 | window.MemoryDrawListVtxCapacity = int(cap(window.DrawList.VtxBuffer)) 19 | window.IDStack = nil 20 | window.DrawList._ClearFreeMemory() 21 | window.DC.ChildWindows = nil 22 | window.DC.ItemWidthStack = nil 23 | window.DC.TextWrapPosStack = nil 24 | } 25 | 26 | func GcAwakeTransientWindowBuffers(window *ImGuiWindow) { 27 | // We stored capacity of the ImDrawList buffer to reduce growth-caused allocation/copy when awakening. 28 | // The other buffers tends to amortize much faster. 29 | window.MemoryCompacted = false 30 | 31 | //reserve 32 | window.DrawList.IdxBuffer = append(window.DrawList.IdxBuffer, make([]ImDrawIdx, window.MemoryDrawListIdxCapacity-int(len(window.DrawList.IdxBuffer)))...) 33 | window.DrawList.VtxBuffer = append(window.DrawList.VtxBuffer, make([]ImDrawVert, window.MemoryDrawListVtxCapacity-int(len(window.DrawList.VtxBuffer)))...) 34 | 35 | window.MemoryDrawListIdxCapacity = 0 36 | window.MemoryDrawListVtxCapacity = 0 37 | } 38 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Splizard/imgui 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 7 | github.com/go-gl/glfw v0.0.0-20231124074035-2de0cf0c80af 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f h1:s0O46d8fPwk9kU4k1jj76wBquMVETx7uveQD9MCIQoU= 2 | github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM= 3 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= 4 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= 5 | github.com/go-gl/glfw v0.0.0-20210727001814-0db043d8d5be h1:UVW91pfMB1GRQfVwC7//RGVbqX6Ea8jURmJhlANak1M= 6 | github.com/go-gl/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 7 | github.com/go-gl/glfw v0.0.0-20231124074035-2de0cf0c80af h1:V4DLCrN57QoLQYtwnlmGQvAIM6DMU5eMrR9VQhmxPPs= 8 | github.com/go-gl/glfw v0.0.0-20231124074035-2de0cf0c80af/go.mod h1:wyvWpaEu9B/VQiV1jsPs7Mha9I7yto/HqIBw197ZAzk= 9 | -------------------------------------------------------------------------------- /golang/golang.go: -------------------------------------------------------------------------------- 1 | package golang 2 | 3 | type Int = int 4 | -------------------------------------------------------------------------------- /helpers.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // reserveVec2Slice operates like C++ vector reserve. 4 | func reserveVec2Slice(slice []ImVec2, capacity int) []ImVec2 { 5 | if int(cap(slice)) < capacity { 6 | bigger := make([]ImVec2, len(slice), capacity) 7 | copy(bigger, slice) 8 | return bigger 9 | } 10 | return slice 11 | } 12 | 13 | /* 14 | func printf(format string, v ...any) { 15 | fmt.Printf(format, v...) 16 | } 17 | */ 18 | 19 | // GCrc32LookupTable CRC32 needs a 1KB lookup table (not cache friendly) 20 | // Although the code to generate the table is simple and shorter than the table itself, using a const table allows us to easily: 21 | // - avoid an unnecessary branch/memory tap, - keep the ImHashXXX functions usable by static constructors, - make it thread-safe. 22 | var GCrc32LookupTable = [256]ImU32{ 23 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 24 | 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 25 | 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 26 | 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 27 | 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 28 | 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 29 | 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 30 | 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 31 | 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 32 | 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 33 | 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 34 | 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 35 | 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 36 | 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 37 | 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 38 | 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D, 39 | } 40 | 41 | // ImHashStr Zero-terminated string hash, with support for ### to reset back to seed value 42 | // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed. 43 | // Because this syntax is rarely used we are optimizing for the common case. 44 | // - If we reach ### in the string we discard the hash so far and reset to the seed. 45 | // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller/faster (measured ~10% diff in Debug build) 46 | // FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements. 47 | func ImHashStr(data_p string, data_size size_t, seed ImU32) ImGuiID { 48 | seed = ^seed 49 | var crc = seed 50 | var data = data_p 51 | var crc32_lut = GCrc32LookupTable 52 | if data_size != 0 { 53 | for { 54 | data_size-- 55 | if data_size == 0 { 56 | break 57 | } 58 | data = data[1:] 59 | var c = data[0] 60 | if c == '#' && data_size >= 2 && data[0] == '#' && data[1] == '#' { 61 | crc = seed 62 | } 63 | crc = (crc >> 8) ^ crc32_lut[(crc&0xFF)^uint(c)] 64 | } 65 | } else { 66 | for i := 0; i < len(data_p); i++ { 67 | c := data_p[i] 68 | if c == '#' && data[0] == '#' && data[1] == '#' { 69 | crc = seed 70 | } 71 | crc = (crc >> 8) ^ crc32_lut[(crc&0xFF)^uint(c)] 72 | } 73 | } 74 | return ^crc 75 | } 76 | 77 | func ImStrSkipBlank(str string) string { 78 | for str[0] == ' ' || str[0] == '\t' { 79 | str = str[1:] 80 | } 81 | return str 82 | } 83 | -------------------------------------------------------------------------------- /id_stack.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // ID stack/scopes 4 | // Read the FAQ (docs/FAQ.md or http://dearimgui.org/faq) for more details about how ID are handled in dear imgui. 5 | // - Those questions are answered and impacted by understanding of the ID stack system: 6 | // - "Q: Why is my widget not reacting when I click on it?" 7 | // - "Q: How can I have widgets with an empty label?" 8 | // - "Q: How can I have multiple widgets with the same label?" 9 | // - Short version: ID are hashes of the entire ID stack. If you are creating widgets in a loop you most likely 10 | // want to push a unique identifier (e.g. object pointer, loop index) to uniquely differentiate them. 11 | // - You can also use the "Label##foobar" syntax within widget label to distinguish them from each others. 12 | // - In this header file we use the "label"/"name" terminology to denote a string that will be displayed + used as an ID, 13 | // whereas "str_id" denote a string that is only used as an ID and not normally displayed. 14 | 15 | // PushOverrideID Push given value as-is at the top of the ID stack (whereas PushID combines old and new hashes) 16 | func PushOverrideID(id ImGuiID) { 17 | var g = GImGui 18 | var window = g.CurrentWindow 19 | window.IDStack = append(window.IDStack, id) 20 | } 21 | 22 | // GetIDWithSeed Helper to avoid a common series of PushOverrideID . GetID() . PopID() call 23 | // (note that when using this pattern, TestEngine's "Stack Tool" will tend to not display the intermediate stack level. 24 | // 25 | // for that to work we would need to do PushOverrideID() . ItemAdd() . PopID() which would alter widget code a little more) 26 | func GetIDWithSeed(str string, seed ImGuiID) ImGuiID { 27 | var id = ImHashStr(str, uintptr(len(str)), seed) 28 | KeepAliveID(id) 29 | return id 30 | } 31 | 32 | func PushString(str_id string) { 33 | var g = GImGui 34 | var window = g.CurrentWindow 35 | var id = window.GetIDNoKeepAlive(str_id) 36 | window.IDStack = append(window.IDStack, id) 37 | } 38 | 39 | // PushInterface push pointer into the ID stack (will hash pointer). 40 | func PushInterface(ptr_id any) { 41 | var g = GImGui 42 | var window = g.CurrentWindow 43 | var id = window.GetIDNoKeepAliveInterface(ptr_id) 44 | window.IDStack = append(window.IDStack, id) 45 | } 46 | 47 | // PushID push integer into the ID stack (will hash integer). 48 | func PushID(int_id int) { 49 | var g = GImGui 50 | var window = g.CurrentWindow 51 | var id = window.GetIDNoKeepAliveInt(int_id) 52 | window.IDStack = append(window.IDStack, id) 53 | } 54 | 55 | func PopID() { 56 | var window = GImGui.CurrentWindow 57 | IM_ASSERT(len(window.IDStack) > 1) // Too many PopID(), or could be popping in a wrong/different window? 58 | window.IDStack = window.IDStack[:len(window.IDStack)-1] 59 | } // pop from the ID stack. 60 | 61 | func GetIDFromString(str_id string) ImGuiID { 62 | return GImGui.CurrentWindow.GetIDs(str_id) 63 | 64 | } // calculate unique ID (hash of whole ID stack + given parameter). e.g. if you want to query into ImGuiStorage yourself 65 | 66 | func GetIDs(str_id_begin string) ImGuiID { 67 | return GImGui.CurrentWindow.GetIDs(str_id_begin) 68 | } 69 | 70 | func GetIDFromInterface(ptr_id any) ImGuiID { 71 | var window = GImGui.CurrentWindow 72 | return window.GetIDInterface(ptr_id) 73 | } 74 | -------------------------------------------------------------------------------- /id_stack_test.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "testing" 7 | ) 8 | 9 | func TestIDs(t *testing.T) { 10 | fmt.Println(ImHashStr("Hello World", 0, 0)) 11 | fmt.Println(ImHashStr("Beep Boop", 0, 0)) 12 | } 13 | 14 | func BenchmarkImguiIDs(b *testing.B) { 15 | for i := 0; i < b.N; i++ { 16 | ImHashStr("Hello World", 0, 0) 17 | } 18 | } 19 | 20 | var pc [1]uintptr 21 | 22 | func BenchmarkProgramCounterIDs(b *testing.B) { 23 | for i := 0; i < b.N; i++ { 24 | runtime.Callers(1, pc[:]) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /keyboard.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // Inputs Utilities: Keyboard 4 | // - For 'user_key_index int' you can use your own indices/enums according to how your backend/engine stored them in io.KeysDown[]. 5 | // - We don't know the meaning of those value. You can use GetKeyIndex() to map a ImGuiKey_ value into the user index. 6 | 7 | // == tab stop enable. Allow focusing using TAB/Shift-TAB, enabled by default but you can disable it for certain widgets 8 | func PushAllowKeyboardFocus(allow_keyboard_focus bool) { 9 | PushItemFlag(ImGuiItemFlags_NoTabStop, !allow_keyboard_focus) 10 | } 11 | func PopAllowKeyboardFocus() { 12 | PopItemFlag() 13 | } 14 | 15 | // map ImGuiKey_* values into user's key index. == io.KeyMap[key] 16 | func GetKeyIndex(imgui_key ImGuiKey) int { 17 | IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT) 18 | var g = GImGui 19 | return g.IO.KeyMap[imgui_key] 20 | } 21 | 22 | // Note that dear imgui doesn't know the semantic of each entry of io.KeysDown[]! 23 | // Use your own indices/enums according to how your backend/engine stored them into io.KeysDown[]! 24 | // is key being held. == io.KeysDown[user_key_index]. 25 | func IsKeyDown(user_key_index int) bool { 26 | if user_key_index < 0 { 27 | return false 28 | } 29 | var g = GImGui 30 | IM_ASSERT(user_key_index >= 0 && user_key_index < int(len(g.IO.KeysDown))) 31 | return g.IO.KeysDown[user_key_index] 32 | } 33 | 34 | // was key released (went from Down to !Down)? 35 | func IsKeyReleased(user_key_index int) bool { 36 | var g = GImGui 37 | if user_key_index < 0 { 38 | return false 39 | } 40 | IM_ASSERT(user_key_index >= 0 && user_key_index < int(len(g.IO.KeysDown))) 41 | return g.IO.KeysDownDurationPrev[user_key_index] >= 0.0 && !g.IO.KeysDown[user_key_index] 42 | } 43 | 44 | // uses provided repeat rate/delay. return a count, most often 0 or 1 but might be >1 if RepeatRate is small enough that DeltaTime > RepeatRate 45 | func GetKeyPressedAmount(key_index int, repeat_delay float, repeat_rate float) int { 46 | var g = GImGui 47 | if key_index < 0 { 48 | return 0 49 | } 50 | IM_ASSERT(key_index >= 0 && key_index < int(len(g.IO.KeysDown))) 51 | var t = g.IO.KeysDownDuration[key_index] 52 | return CalcTypematicRepeatAmount(t-g.IO.DeltaTime, t, repeat_delay, repeat_rate) 53 | } 54 | 55 | // attention: misleading name! manually override io.WantCaptureKeyboard flag next frame (said flag is entirely left for your application to handle). e.g. force capture keyboard when your widget is being hovered. This is equivalent to setting "io.WantCaptureKeyboard = want_capture_keyboard_value" {panic("not implemented")} after the next NewFrame() call. 56 | func CaptureKeyboardFromApp(want_capture_keyboard_value bool /*= true*/) { 57 | if want_capture_keyboard_value { 58 | GImGui.WantCaptureKeyboardNextFrame = 1 59 | } else { 60 | GImGui.WantCaptureKeyboardNextFrame = 0 61 | } 62 | } 63 | 64 | // Pass in translated ASCII characters for text input. 65 | // - with glfw you can get those from the callback set in glfwSetCharCallback() 66 | // - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message 67 | func (io *ImGuiIO) AddInputCharacter(c rune) { 68 | if c != 0 { 69 | if c <= IM_UNICODE_CODEPOINT_MAX { 70 | io.InputQueueCharacters = append(io.InputQueueCharacters, c) 71 | } else { 72 | io.AddInputCharacter(IM_UNICODE_CODEPOINT_INVALID) 73 | } 74 | } 75 | } 76 | 77 | func (io *ImGuiIO) AddInputCharacters(chars string) { 78 | for _, c := range chars { 79 | io.AddInputCharacter(c) 80 | } 81 | } 82 | 83 | // Clear the text input buffer manually 84 | func (io *ImGuiIO) ClearInputCharacters() { 85 | io.InputQueueCharacters = io.InputQueueCharacters[:0] 86 | } 87 | 88 | func GetMergedKeyModFlags() ImGuiKeyModFlags { 89 | var g = GImGui 90 | var key_mod_flags = ImGuiKeyModFlags_None 91 | if g.IO.KeyCtrl { 92 | key_mod_flags |= ImGuiKeyModFlags_Ctrl 93 | } 94 | if g.IO.KeyShift { 95 | key_mod_flags |= ImGuiKeyModFlags_Shift 96 | } 97 | if g.IO.KeyAlt { 98 | key_mod_flags |= ImGuiKeyModFlags_Alt 99 | } 100 | if g.IO.KeySuper { 101 | key_mod_flags |= ImGuiKeyModFlags_Super 102 | } 103 | return key_mod_flags 104 | } 105 | 106 | func IsKeyPressed(user_key_index int, repeat bool /*= true*/) bool { 107 | var g = GImGui 108 | if user_key_index < 0 { 109 | return false 110 | } 111 | IM_ASSERT(user_key_index >= 0 && user_key_index < int(len(g.IO.KeysDown))) 112 | var t = g.IO.KeysDownDuration[user_key_index] 113 | if t == 0.0 { 114 | return true 115 | } 116 | if repeat && t > g.IO.KeyRepeatDelay { 117 | return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0 118 | } 119 | return false 120 | } // was key pressed (went from !Down to Down)? if repeat=true, uses io.KeyRepeatDelay / KeyRepeatRate 121 | -------------------------------------------------------------------------------- /listclipper.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // Helper: Manually clip large list of items. 4 | // If you are submitting lots of evenly spaced items and you have a random access to the list, you can perform coarse 5 | // clipping based on visibility to save yourself from processing those items at all. 6 | // The clipper calculates the range of visible items and advance the cursor to compensate for the non-visible items we have skipped. 7 | // (Dear ImGui already clip items based on their bounds but it needs to measure text size to do so, whereas manual coarse clipping before submission makes this cost and your own data fetching/submission cost almost null) 8 | // Usage: 9 | // 10 | // clipper ImGuiListClipper 11 | // 12 | // clipper.Begin(1000) // // We have 1000 elements, evenly spaced. 13 | // 14 | // while (clipper.Step()) 15 | // for (int i clipper.DisplayStart = i clipper.DisplayEnd < i++) 16 | // ImGui::Text("line number i) %d", 17 | // 18 | // Generally what happens is: 19 | // - Clipper lets you process the first element (DisplayStart DisplayEnd = 1) regardless of it being visible or not. 20 | // - User code submit one element. 21 | // - Clipper can measure the height of the first element 22 | // - Clipper calculate the actual range of elements to display based on the current clipping rectangle, position the cursor before the first visible element. 23 | // - User code submit visible elements. 24 | type ImGuiListClipper struct { 25 | DisplayStart int 26 | DisplayEnd int 27 | 28 | // [Internal] 29 | ItemsCount int 30 | StepNo int 31 | ItemsFrozen int 32 | ItemsHeight float 33 | StartPosY float 34 | } 35 | 36 | func NewImGuiListClipper() ImGuiListClipper { 37 | return ImGuiListClipper{ 38 | ItemsCount: -1, 39 | } 40 | } 41 | 42 | // Use case A: Begin() called from constructor with items_height<0, then called again from Step() in StepNo 1 43 | // Use case B: Begin() called from constructor with items_height>0 44 | // FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style. 45 | // items_count: Use INT_MAX if you don't know how many items you have (in which case the cursor won't be advanced in the final step) 46 | // items_height: Use -1.0f to be calculated automatically on first step. Otherwise pass in the distance between your items, typically GetTextLineHeightWithSpacing() or GetFrameHeightWithSpacing(). 47 | func (this ImGuiListClipper) Begin(items_count int, items_height float /*= -1.0f*/) { 48 | var g = GImGui 49 | var window = g.CurrentWindow 50 | 51 | if table := g.CurrentTable; table != nil { 52 | if table.IsInsideRow { 53 | TableEndRow(table) 54 | } 55 | } 56 | 57 | this.StartPosY = window.DC.CursorPos.y 58 | this.ItemsHeight = items_height 59 | this.ItemsCount = items_count 60 | this.ItemsFrozen = 0 61 | this.StepNo = 0 62 | this.DisplayStart = -1 63 | this.DisplayEnd = 0 64 | } 65 | 66 | // Automatically called on the last call of Step() that returns false. 67 | func (this ImGuiListClipper) End() { 68 | if this.ItemsCount < 0 { // Already ended 69 | return 70 | } 71 | 72 | // In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user. 73 | if this.ItemsCount < INT_MAX && this.DisplayStart >= 0 { 74 | SetCursorPosYAndSetupForPrevLine(this.StartPosY+float(this.ItemsCount-this.ItemsFrozen)*this.ItemsHeight, this.ItemsHeight) 75 | } 76 | this.ItemsCount = -1 77 | this.StepNo = 3 78 | } 79 | 80 | func (this ImGuiListClipper) Step() bool { 81 | var g = GImGui 82 | var window = g.CurrentWindow 83 | 84 | var table = g.CurrentTable 85 | if table != nil && table.IsInsideRow { 86 | TableEndRow(table) 87 | } 88 | 89 | // No items 90 | if this.ItemsCount == 0 || GetSkipItemForListClipping() { 91 | this.End() 92 | return false 93 | } 94 | 95 | // Step 0: Let you process the first element (regardless of it being visible or not, so we can measure the element height) 96 | if this.StepNo == 0 { 97 | // While we are in frozen row state, keep displaying items one by one, unclipped 98 | // FIXME: Could be stored as a table-agnostic state. 99 | if table != nil && !table.IsUnfrozenRows { 100 | this.DisplayStart = this.ItemsFrozen 101 | this.DisplayEnd = this.ItemsFrozen + 1 102 | this.ItemsFrozen++ 103 | return true 104 | } 105 | 106 | this.StartPosY = window.DC.CursorPos.y 107 | if this.ItemsHeight <= 0.0 { 108 | // Submit the first item so we can measure its height (generally it is 0..1) 109 | this.DisplayStart = this.ItemsFrozen 110 | this.DisplayEnd = this.ItemsFrozen + 1 111 | this.StepNo = 1 112 | return true 113 | } 114 | 115 | // Already has item height (given by user in Begin): skip to calculating step 116 | this.DisplayStart = this.DisplayEnd 117 | this.StepNo = 2 118 | } 119 | 120 | // Step 1: the clipper infer height from first element 121 | if this.StepNo == 1 { 122 | IM_ASSERT(this.ItemsHeight <= 0.0) 123 | if table != nil { 124 | var pos_y1 = table.RowPosY1 // Using this instead of StartPosY to handle clipper straddling the frozen row 125 | var pos_y2 = table.RowPosY2 // Using this instead of CursorPos.y to take account of tallest cell. 126 | this.ItemsHeight = pos_y2 - pos_y1 127 | window.DC.CursorPos.y = pos_y2 128 | } else { 129 | this.ItemsHeight = window.DC.CursorPos.y - this.StartPosY 130 | } 131 | IM_ASSERT_USER_ERROR(this.ItemsHeight > 0.0, "Unable to calculate item height! First item hasn't moved the cursor vertically!") 132 | this.StepNo = 2 133 | } 134 | 135 | // Reached end of list 136 | if this.DisplayEnd >= this.ItemsCount { 137 | this.End() 138 | return false 139 | } 140 | 141 | // Step 2: calculate the actual range of elements to display, and position the cursor before the first element 142 | if this.StepNo == 2 { 143 | IM_ASSERT(this.ItemsHeight > 0.0) 144 | 145 | var already_submitted = this.DisplayEnd 146 | CalcListClipping(this.ItemsCount-already_submitted, this.ItemsHeight, &this.DisplayStart, &this.DisplayEnd) 147 | this.DisplayStart += already_submitted 148 | this.DisplayEnd += already_submitted 149 | 150 | // Seek cursor 151 | if this.DisplayStart > already_submitted { 152 | SetCursorPosYAndSetupForPrevLine(this.StartPosY+float(this.DisplayStart-this.ItemsFrozen)*this.ItemsHeight, this.ItemsHeight) 153 | } 154 | 155 | this.StepNo = 3 156 | return true 157 | } 158 | 159 | // Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), 160 | // Advance the cursor to the end of the list and then returns 'false' to end the loop. 161 | if this.StepNo == 3 { 162 | // Seek cursor 163 | if this.ItemsCount < INT_MAX { 164 | SetCursorPosYAndSetupForPrevLine(this.StartPosY+float(this.ItemsCount-this.ItemsFrozen)*this.ItemsHeight, this.ItemsHeight) // advance cursor 165 | } 166 | this.ItemsCount = -1 167 | return false 168 | } 169 | 170 | IM_ASSERT(false) 171 | return false 172 | } 173 | 174 | // FIXME-TABLE: This prevents us from using ImGuiListClipper _inside_ a table cell. 175 | // The problem we have is that without a Begin/End scheme for rows using the clipper is ambiguous. 176 | func GetSkipItemForListClipping() bool { 177 | var g = GImGui 178 | if g.CurrentTable != nil { 179 | return g.CurrentTable.HostSkipItems 180 | } 181 | return g.CurrentWindow.SkipItems 182 | } 183 | 184 | // Helper to calculate coarse clipping of large list of evenly sized items. 185 | // NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern. 186 | // NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX 187 | func CalcListClipping(items_count int, items_height float, out_items_display_start *int, out_items_display_end *int) { 188 | var g = GImGui 189 | var window = g.CurrentWindow 190 | if g.LogEnabled { 191 | // If logging is active, do not perform any clipping 192 | *out_items_display_start = 0 193 | *out_items_display_end = items_count 194 | return 195 | } 196 | if GetSkipItemForListClipping() { 197 | *out_items_display_start = 0 198 | *out_items_display_end = 0 199 | return 200 | } 201 | 202 | // We create the union of the ClipRect and the scoring rect which at worst should be 1 page away from ClipRect 203 | var unclipped_rect = window.ClipRect 204 | if g.NavMoveScoringItems { 205 | unclipped_rect.AddRect(g.NavScoringRect) 206 | } 207 | if g.NavJustMovedToId != 0 && window.NavLastIds[0] == g.NavJustMovedToId { 208 | // Could store and use NavJustMovedToRectRe 209 | unclipped_rect.AddRect(ImRect{window.Pos.Add(window.NavRectRel[0].Min), window.Pos.Add(window.NavRectRel[0].Max)}) 210 | } 211 | 212 | var pos = window.DC.CursorPos 213 | var start = (int)((unclipped_rect.Min.y - pos.y) / items_height) 214 | var end = (int)((unclipped_rect.Max.y - pos.y) / items_height) 215 | 216 | // When performing a navigation request, ensure we have one item extra in the direction we are moving to 217 | if g.NavMoveScoringItems && g.NavMoveClipDir == ImGuiDir_Up { 218 | start-- 219 | } 220 | if g.NavMoveScoringItems && g.NavMoveClipDir == ImGuiDir_Down { 221 | end++ 222 | } 223 | 224 | start = ImClampInt(start, 0, items_count) 225 | end = ImClampInt(end+1, start, items_count) 226 | *out_items_display_start = start 227 | *out_items_display_end = end 228 | } 229 | 230 | func SetCursorPosYAndSetupForPrevLine(pos_y, line_height float) { 231 | // Set cursor position and a few other things so that SetScrollHereY() and Columns() can work when seeking cursor. 232 | // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. 233 | // The clipper should probably have a 4th step to display the last item in a regular manner. 234 | var g = GImGui 235 | var window = g.CurrentWindow 236 | var off_y = pos_y - window.DC.CursorPos.y 237 | window.DC.CursorPos.y = pos_y 238 | window.DC.CursorMaxPos.y = ImMax(window.DC.CursorMaxPos.y, pos_y) 239 | window.DC.CursorPosPrevLine.y = window.DC.CursorPos.y - line_height // Setting those fields so that SetScrollHereY() can properly function after the end of our clipper usage. 240 | window.DC.PrevLineSize.y = (line_height - g.Style.ItemSpacing.y) // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list. 241 | if columns := window.DC.CurrentColumns; columns != nil { 242 | columns.LineMinY = window.DC.CursorPos.y // Setting this so that cell Y position are set properly 243 | } 244 | if table := g.CurrentTable; table != nil { 245 | if table.IsInsideRow { 246 | TableEndRow(table) 247 | } 248 | table.RowPosY2 = window.DC.CursorPos.y 249 | var row_increase = (int)((off_y / line_height) + 0.5) 250 | //table.CurrentRow += row_increase; // Can't do without fixing TableEndRow() 251 | table.RowBgColorCounter += row_increase 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /logging.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | // LogBegin Logging/Capture 9 | // . BeginCapture() when we design v2 api, for now stay under the radar by using the old name. 10 | func LogBegin(ltype ImGuiLogType, auto_open_depth int) { 11 | var g = GImGui 12 | var window = g.CurrentWindow 13 | IM_ASSERT(!g.LogEnabled) 14 | IM_ASSERT(g.LogFile == nil) 15 | IM_ASSERT(g.LogBuffer.Len() == 0) 16 | g.LogEnabled = true 17 | g.LogType = ltype 18 | g.LogNextPrefix = "" 19 | g.LogNextSuffix = "" 20 | g.LogDepthRef = window.DC.TreeDepth 21 | g.LogDepthToExpand = g.LogDepthToExpandDefault 22 | if auto_open_depth >= 0 { 23 | g.LogDepthToExpand = auto_open_depth 24 | } 25 | g.LogLinePosY = FLT_MAX 26 | g.LogLineFirstItem = true 27 | } 28 | 29 | // LogToBuffer Start logging/capturing to internal buffer 30 | func LogToBuffer(auto_open_depth int /*= -1*/) { 31 | var g = GImGui 32 | if g.LogEnabled { 33 | return 34 | } 35 | LogBegin(ImGuiLogType_Buffer, auto_open_depth) 36 | } 37 | 38 | // LogRenderedText Internal version that takes a position to decide on newline placement and pad items according to their depth. 39 | // We split text into individual lines to add current tree level padding 40 | // FIXME: This code is a little complicated perhaps, considering simplifying the whole system. 41 | func LogRenderedText(ref_pos *ImVec2, text string) { 42 | LogText(text) 43 | } 44 | 45 | // LogSetNextTextDecoration Important: doesn't copy underlying data, use carefully (prefix/suffix must be in scope at the time of the next LogRenderedText) 46 | func LogSetNextTextDecoration(prefix string, suffix string) { 47 | var g = GImGui 48 | g.LogNextPrefix = prefix 49 | g.LogNextSuffix = suffix 50 | } 51 | 52 | // Logging/Capture 53 | // - All text output from the interface can be captured into tty/file/clipboard. By default, tree nodes are automatically opened during logging. 54 | 55 | // LogToTTY start logging to tty (stdout) 56 | func LogToTTY(auto_open_depth int /*= -1*/) { 57 | var g = GImGui 58 | if g.LogEnabled { 59 | return 60 | } 61 | LogBegin(ImGuiLogType_TTY, auto_open_depth) 62 | g.LogFile = os.Stdout 63 | } 64 | 65 | // LogToFile Start logging/capturing text output to given file 66 | func LogToFile(auto_open_depth int /*= 1*/, filename string) { 67 | var g = GImGui 68 | if g.LogEnabled { 69 | return 70 | } 71 | 72 | // FIXME: We could probably open the file in text mode "at", however note that clipboard/buffer logging will still 73 | // be subject to outputting OS-incompatible carriage return if within strings the user doesn't use IM_NEWLINE. 74 | // By opening the file in binary mode "ab" we have consistent output everywhere. 75 | if filename == "" { 76 | filename = g.IO.LogFilename 77 | } 78 | if filename == "" || filename[0] == 0 { 79 | return 80 | } 81 | var f = ImFileOpen(filename, "ab") 82 | if f == nil { 83 | IM_ASSERT(false) 84 | return 85 | } 86 | 87 | LogBegin(ImGuiLogType_File, auto_open_depth) 88 | g.LogFile = f 89 | } 90 | 91 | // LogToClipboard start logging to OS clipboard 92 | func LogToClipboard(auto_open_depth int /*= -1*/) { 93 | var g = GImGui 94 | if g.LogEnabled { 95 | return 96 | } 97 | LogBegin(ImGuiLogType_Clipboard, auto_open_depth) 98 | } 99 | 100 | func LogFinish() { 101 | var g = GImGui 102 | if !g.LogEnabled { 103 | return 104 | } 105 | 106 | LogText(IM_NEWLINE) 107 | switch g.LogType { 108 | case ImGuiLogType_TTY: 109 | //g.LogFile 110 | case ImGuiLogType_File: 111 | ImFileClose(g.LogFile) 112 | case ImGuiLogType_Buffer: 113 | case ImGuiLogType_Clipboard: 114 | if g.LogBuffer.Len() > 0 { 115 | SetClipboardText(g.LogBuffer.String()) 116 | } 117 | case ImGuiLogType_None: 118 | IM_ASSERT(false) 119 | } 120 | 121 | g.LogEnabled = false 122 | g.LogType = ImGuiLogType_None 123 | g.LogFile = nil 124 | g.LogBuffer.Reset() 125 | } // stop logging (close file, etc.) 126 | 127 | // LogButtons helper to display buttons for logging to tty/file/clipboard 128 | func LogButtons() { 129 | var g = GImGui 130 | 131 | PushString("LogButtons") 132 | var log_to_tty = Button("Log To TTY") 133 | SameLine(0, 0) 134 | var log_to_file = Button("Log To File") 135 | SameLine(0, 0) 136 | var log_to_clipboard = Button("Log To Clipboard") 137 | SameLine(0, 0) 138 | PushAllowKeyboardFocus(false) 139 | SetNextItemWidth(80.0) 140 | SliderInt("Default Depth", &g.LogDepthToExpandDefault, 0, 9, "", 0) 141 | PopAllowKeyboardFocus() 142 | PopID() 143 | 144 | // Start logging at the end of the function so that the buttons don't appear in the log 145 | if log_to_tty { 146 | LogToTTY(-1) 147 | } 148 | if log_to_file { 149 | LogToFile(-1, "") 150 | } 151 | if log_to_clipboard { 152 | LogToClipboard(-1) 153 | } 154 | } 155 | 156 | // LogText pass text data straight to log (without being displayed) 157 | func LogText(format string, args ...any) { 158 | var g = GImGui 159 | if !g.LogEnabled { 160 | return 161 | } 162 | 163 | if g.LogFile != nil { 164 | fmt.Fprintf(g.LogFile, format, args...) 165 | } else { 166 | fmt.Fprintf(&g.LogBuffer, format, args...) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /media/quickbrownfox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splizard/imgui/c17e6223290dfa6a404aefd13a2280287cccfb64/media/quickbrownfox.png -------------------------------------------------------------------------------- /misc.utillities.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // Miscellaneous Utilities 4 | 5 | func GetViewportDrawList(viewport *ImGuiViewportP, drawlist_no size_t, drawlist_name string) *ImDrawList { 6 | // Create the draw list on demand, because they are not frequently used for all viewports 7 | var g = GImGui 8 | IM_ASSERT(drawlist_no < size_t(len(viewport.DrawLists))) 9 | var draw_list = viewport.DrawLists[drawlist_no] 10 | if draw_list == nil { 11 | l := NewImDrawList(&g.DrawListSharedData) 12 | draw_list = &l 13 | draw_list._OwnerName = drawlist_name 14 | viewport.DrawLists[drawlist_no] = draw_list 15 | } 16 | 17 | // Our ImDrawList system requires that there is always a command 18 | if viewport.DrawListsLastFrame[drawlist_no] != g.FrameCount { 19 | draw_list._ResetForNewFrame() 20 | draw_list.PushTextureID(g.IO.Fonts.TexID) 21 | draw_list.PushClipRect(viewport.Pos, viewport.Pos.Add(viewport.Size), false) 22 | viewport.DrawListsLastFrame[drawlist_no] = g.FrameCount 23 | } 24 | return draw_list 25 | } 26 | 27 | // test if rectangle (of given size, starting from cursor position) is visible / not clipped. 28 | func IsRectVisible(size ImVec2) bool { 29 | var window = GImGui.CurrentWindow 30 | return window.ClipRect.Overlaps(ImRect{window.DC.CursorPos, window.DC.CursorPos.Add(size)}) 31 | } 32 | 33 | // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side. 34 | func IsRectVisibleMinMax(rect_min, rect_max ImVec2) bool { 35 | var window = GImGui.CurrentWindow 36 | return window.ClipRect.Overlaps(ImRect{rect_min, rect_max}) 37 | } 38 | 39 | func GetTime() double { return GImGui.Time } // get global imgui time. incremented by io.DeltaTime every frame. 40 | func GetFrameCount() int { return GImGui.FrameCount } // get global imgui frame count. incremented by 1 every frame. 41 | 42 | // this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents. 43 | func GetBackgroundDrawList(viewport *ImGuiViewport) *ImDrawList { 44 | var g = GImGui 45 | if viewport == nil { 46 | viewport = g.Viewports[0] 47 | } 48 | return GetViewportDrawList(viewport, 0, "##Background") 49 | } 50 | 51 | // this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents. 52 | func GetForegroundDrawList(viewport *ImGuiViewport) *ImDrawList { 53 | var g = GImGui 54 | if viewport == nil { 55 | viewport = g.Viewports[0] 56 | } 57 | return GetViewportDrawList(viewport, 1, "##Foreground") 58 | } 59 | 60 | // you may use this when creating your own ImDrawList instances. 61 | func GetDrawListSharedData() *ImDrawListSharedData { 62 | return &GImGui.DrawListSharedData 63 | } 64 | 65 | // replace current window storage with our own (if you want to manipulate it yourself, typically clear subsection of it) 66 | func SetStateStorage(storage *ImGuiStorage) { 67 | var window = GImGui.CurrentWindow 68 | if storage != nil { 69 | window.DC.StateStorage = *storage 70 | } else { 71 | window.DC.StateStorage = window.StateStorage 72 | } 73 | } 74 | 75 | func GetStateStorage() ImGuiStorage { 76 | var window = GImGui.CurrentWindow 77 | return window.DC.StateStorage 78 | } 79 | -------------------------------------------------------------------------------- /proggy.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Splizard/imgui/c17e6223290dfa6a404aefd13a2280287cccfb64/proggy.ttf -------------------------------------------------------------------------------- /rendering.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | func RenderFrameBorder(p_min ImVec2, p_max ImVec2, rounding float) { 4 | var g = GImGui 5 | var window = g.CurrentWindow 6 | var border_size = g.Style.FrameBorderSize 7 | if border_size > 0.0 { 8 | window.DrawList.AddRect(p_min.Add(ImVec2{1, 1}), p_max.Add(ImVec2{1, 1}), GetColorU32FromID(ImGuiCol_BorderShadow, 1), rounding, 0, border_size) 9 | window.DrawList.AddRect(p_min, p_max, GetColorU32FromID(ImGuiCol_Border, 1), rounding, 0, border_size) 10 | } 11 | } 12 | 13 | func RenderBullet(draw_list *ImDrawList, pos ImVec2, col ImU32) { 14 | draw_list.AddCircleFilled(pos, draw_list._Data.FontSize*0.20, col, 8) 15 | } 16 | 17 | // RenderArrow Render an arrow aimed to be aligned with text (p_min is a position in the same space text would be positioned). To e.g. denote expanded/collapsed state 18 | func RenderArrow(draw_list *ImDrawList, pos ImVec2, col ImU32, dir ImGuiDir, scale float /*= 1.0f*/) { 19 | var h = draw_list._Data.FontSize * 1.00 20 | var r = h * 0.40 * scale 21 | var center = pos.Add(ImVec2{h * 0.50, h * 0.50 * scale}) 22 | 23 | var a, b, c ImVec2 24 | switch dir { 25 | case ImGuiDir_Up: 26 | fallthrough 27 | case ImGuiDir_Down: 28 | if dir == ImGuiDir_Up { 29 | r = -r 30 | } 31 | a = ImVec2{+0.000, +0.750}.Scale(r) 32 | b = ImVec2{-0.866, -0.750}.Scale(r) 33 | c = ImVec2{+0.866, -0.750}.Scale(r) 34 | case ImGuiDir_Left: 35 | fallthrough 36 | case ImGuiDir_Right: 37 | if dir == ImGuiDir_Left { 38 | r = -r 39 | } 40 | a = ImVec2{+0.750, +0.000}.Scale(r) 41 | b = ImVec2{-0.750, +0.866}.Scale(r) 42 | c = ImVec2{-0.750, -0.866}.Scale(r) 43 | case ImGuiDir_None: 44 | fallthrough 45 | case ImGuiDir_COUNT: 46 | IM_ASSERT(false) 47 | } 48 | 49 | p1, p2 := center.Add(a), center.Add(b) 50 | draw_list.AddTriangleFilled(&p1, &p2, center.Add(c), col) 51 | } 52 | 53 | // Render ends the Dear ImGui frame, finalize the draw data. You can then get call GetDrawData() 54 | // Prepare the data for rendering so you can call GetDrawData() 55 | // (As with anything within the ImGui:: namspace this doesn't touch your GPU or graphics API at all: 56 | // it is the role of the ImGui_ImplXXXX_RenderDrawData() function provided by the renderer backend). 57 | func Render() { 58 | var g = GImGui 59 | IM_ASSERT(g.Initialized) 60 | 61 | if g.FrameCountEnded != g.FrameCount { 62 | EndFrame() 63 | } 64 | g.FrameCountRendered = g.FrameCount 65 | g.IO.MetricsRenderWindows = 0 66 | 67 | CallContextHooks(g, ImGuiContextHookType_RenderPre) 68 | 69 | // Add background ImDrawList (for each active viewport) 70 | for n := range g.Viewports { 71 | var viewport = g.Viewports[n] 72 | viewport.DrawDataBuilder.Clear() 73 | if viewport.DrawLists[0] != nil { 74 | AddDrawListToDrawData(&viewport.DrawDataBuilder[0], getBackgroundDrawList(viewport)) 75 | } 76 | } 77 | 78 | // Add ImDrawList to render 79 | var windows_to_render_top_most [2]*ImGuiWindow 80 | if g.NavWindowingTarget != nil && g.NavWindowingTarget.Flags&ImGuiWindowFlags_NoBringToFrontOnFocus == 0 { 81 | windows_to_render_top_most[0] = g.NavWindowingTarget.RootWindow 82 | } 83 | if g.NavWindowingTarget != nil { 84 | windows_to_render_top_most[1] = g.NavWindowingListWindow 85 | } 86 | 87 | for n := range g.Windows { 88 | var window = g.Windows[n] 89 | if IsWindowActiveAndVisible(window) && (window.Flags&ImGuiWindowFlags_ChildWindow) == 0 && window != windows_to_render_top_most[0] && window != windows_to_render_top_most[1] { 90 | AddRootWindowToDrawData(window) 91 | } 92 | } 93 | for n := range windows_to_render_top_most { 94 | if windows_to_render_top_most[n] != nil && IsWindowActiveAndVisible(windows_to_render_top_most[n]) { // NavWindowingTarget is always temporarily displayed as the top-most window 95 | AddRootWindowToDrawData(windows_to_render_top_most[n]) 96 | } 97 | } 98 | 99 | // Setup ImDrawData structures for end-user 100 | g.IO.MetricsRenderVertices = 0 101 | g.IO.MetricsRenderIndices = 0 102 | for n := range g.Viewports { 103 | var viewport = g.Viewports[n] 104 | viewport.DrawDataBuilder.FlattenIntoSingleLayer() 105 | 106 | // Draw software mouse cursor if requested by io.MouseDrawCursor flag 107 | if g.IO.MouseDrawCursor { 108 | RenderMouseCursor(GetForegroundDrawListViewport(viewport), g.IO.MousePos, g.Style.MouseCursorScale, g.MouseCursor, IM_COL32_WHITE, IM_COL32_BLACK, IM_COL32(0, 0, 0, 48)) 109 | } 110 | 111 | // Add foreground ImDrawList (for each active viewport) 112 | if viewport.DrawLists[1] != nil { 113 | AddDrawListToDrawData(&viewport.DrawDataBuilder[0], GetForegroundDrawListViewport(viewport)) 114 | } 115 | 116 | SetupViewportDrawData(viewport, &viewport.DrawDataBuilder[0]) 117 | var draw_data = &viewport.DrawDataP 118 | g.IO.MetricsRenderVertices += draw_data.TotalVtxCount 119 | g.IO.MetricsRenderIndices += draw_data.TotalIdxCount 120 | } 121 | 122 | CallContextHooks(g, ImGuiContextHookType_RenderPost) 123 | } 124 | -------------------------------------------------------------------------------- /scrolling.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // Return scrollbar rectangle, must only be called for corresponding axis if window.ScrollbarX/Y is set. 4 | func GetWindowScrollbarRect(window *ImGuiWindow, axis ImGuiAxis) ImRect { 5 | var outer_rect = window.Rect() 6 | var inner_rect = window.InnerRect 7 | var border_size = window.WindowBorderSize 8 | var scrollbar_size float 9 | 10 | // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar) 11 | //TODO/FIXME is this correct? 12 | switch axis { 13 | case ImGuiAxis_X: 14 | scrollbar_size = window.ScrollbarSizes.y 15 | case ImGuiAxis_Y: 16 | scrollbar_size = window.ScrollbarSizes.x 17 | } 18 | 19 | IM_ASSERT(scrollbar_size > 0.0) 20 | if axis == ImGuiAxis_X { 21 | return ImRect{ImVec2{inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y-border_size-scrollbar_size)}, ImVec2{inner_rect.Max.x, outer_rect.Max.y}} 22 | } else { 23 | return ImRect{ImVec2{ImMax(outer_rect.Min.x, outer_rect.Max.x-border_size-scrollbar_size), inner_rect.Min.y}, ImVec2{outer_rect.Max.x, inner_rect.Max.y}} 24 | } 25 | } 26 | 27 | func GetWindowScrollbarID(window *ImGuiWindow, axis ImGuiAxis) ImGuiID { 28 | switch axis { 29 | case ImGuiAxis_X: 30 | return window.GetIDNoKeepAlive("#SCROLLX") 31 | case ImGuiAxis_Y: 32 | return window.GetIDNoKeepAlive("#SCROLLY") 33 | } 34 | return 0 35 | } 36 | 37 | func Scrollbar(axis ImGuiAxis) { 38 | var g = GImGui 39 | var window = g.CurrentWindow 40 | 41 | var id = GetWindowScrollbarID(window, axis) 42 | KeepAliveID(id) 43 | 44 | // Calculate scrollbar bounding box 45 | var bb = GetWindowScrollbarRect(window, axis) 46 | var rounding_corners = ImDrawFlags_RoundCornersNone 47 | if axis == ImGuiAxis_X { 48 | rounding_corners |= ImDrawFlags_RoundCornersBottomLeft 49 | if !window.ScrollbarY { 50 | rounding_corners |= ImDrawFlags_RoundCornersBottomRight 51 | } 52 | } else { 53 | if (window.Flags&ImGuiWindowFlags_NoTitleBar != 0) && window.Flags&ImGuiWindowFlags_MenuBar == 0 { 54 | rounding_corners |= ImDrawFlags_RoundCornersTopRight 55 | } 56 | if !window.ScrollbarX { 57 | rounding_corners |= ImDrawFlags_RoundCornersBottomRight 58 | } 59 | } 60 | var size_avail float 61 | var size_contents float 62 | var amount float 63 | switch axis { 64 | case ImGuiAxis_X: 65 | size_avail = window.InnerRect.Max.x - window.InnerRect.Min.x 66 | size_contents = window.ContentSize.x + window.WindowPadding.x*2.0 67 | amount = window.Scroll.x 68 | case ImGuiAxis_Y: 69 | size_avail = window.InnerRect.Max.y - window.InnerRect.Min.y 70 | size_contents = window.ContentSize.y + window.WindowPadding.y*2.0 71 | amount = window.Scroll.y 72 | } 73 | 74 | ScrollbarEx(&bb, id, axis, &amount, size_avail, size_contents, rounding_corners) 75 | 76 | switch axis { 77 | case ImGuiAxis_X: 78 | window.Scroll.x = amount 79 | case ImGuiAxis_Y: 80 | window.Scroll.y = amount 81 | } 82 | } 83 | 84 | // Vertical/Horizontal scrollbar 85 | // The entire piece of code below is rather confusing because: 86 | // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) 87 | // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar 88 | // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. 89 | // Still, the code should probably be made simpler.. 90 | func ScrollbarEx(bb_frame *ImRect, id ImGuiID, axis ImGuiAxis, p_scroll_v *float, size_avail_v float, size_contents_v float, flags ImDrawFlags) bool { 91 | var g = GImGui 92 | var window = g.CurrentWindow 93 | if window.SkipItems { 94 | return false 95 | } 96 | 97 | var bb_frame_width = bb_frame.GetWidth() 98 | var bb_frame_height = bb_frame.GetHeight() 99 | if bb_frame_width <= 0.0 || bb_frame_height <= 0.0 { 100 | return false 101 | } 102 | 103 | // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the window resize grab) 104 | var alpha float = 1.0 105 | if (axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize+g.Style.FramePadding.y*2.0 { 106 | alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0)) 107 | } 108 | if alpha <= 0.0 { 109 | return false 110 | } 111 | 112 | var style = &g.Style 113 | var allow_interaction = alpha >= 1.0 114 | 115 | var bb = *bb_frame 116 | bb.ExpandVec(ImVec2{-ImClamp(IM_FLOOR((bb_frame_width-2.0)*0.5), 0.0, 3.0), -ImClamp(IM_FLOOR((bb_frame_height-2.0)*0.5), 0.0, 3.0)}) 117 | 118 | // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar) 119 | var scrollbar_size_v float 120 | 121 | if axis == ImGuiAxis_X { 122 | scrollbar_size_v = bb.GetWidth() 123 | } else { 124 | scrollbar_size_v = bb.GetHeight() 125 | } 126 | 127 | // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount) 128 | // But we maintain a minimum size in pixel to allow for the user to still aim inside. 129 | IM_ASSERT(ImMax(size_contents_v, size_avail_v) > 0.0) // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers. 130 | var win_size_v = ImMax(ImMax(size_contents_v, size_avail_v), 1.0) 131 | var grab_h_pixels = ImClamp(scrollbar_size_v*(size_avail_v/win_size_v), style.GrabMinSize, scrollbar_size_v) 132 | var grab_h_norm = grab_h_pixels / scrollbar_size_v 133 | 134 | // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar(). 135 | var held = false 136 | var hovered = false 137 | ButtonBehavior(&bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus) 138 | 139 | var scroll_max = ImMax(1.0, size_contents_v-size_avail_v) 140 | var scroll_ratio = ImSaturate(*p_scroll_v / scroll_max) 141 | var grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v // Grab position in normalized space 142 | if held && allow_interaction && grab_h_norm < 1.0 { 143 | var scrollbar_pos_v float 144 | var mouse_pos_v float 145 | switch axis { 146 | case ImGuiAxis_X: 147 | scrollbar_pos_v = bb.Min.x 148 | mouse_pos_v = g.IO.MousePos.x 149 | case ImGuiAxis_Y: 150 | scrollbar_pos_v = bb.Min.y 151 | mouse_pos_v = g.IO.MousePos.y 152 | } 153 | 154 | // Click position in scrollbar normalized space (0.0f.1.0f) 155 | var clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v) 156 | SetHoveredID(id) 157 | 158 | var seek_absolute = false 159 | if g.ActiveIdIsJustActivated { 160 | // On initial click calculate the distance between mouse and the center of the grab 161 | seek_absolute = clicked_v_norm < grab_v_norm || clicked_v_norm > grab_v_norm+grab_h_norm 162 | if seek_absolute { 163 | g.ScrollbarClickDeltaToGrabCenter = 0.0 164 | } else { 165 | g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm*0.5 166 | } 167 | } 168 | 169 | // Apply scroll (p_scroll_v will generally point on one member of window.Scroll) 170 | // It is ok to modify Scroll here because we are being called in Begin() after the calculation of ContentSize and before setting up our starting position 171 | var scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm*0.5) / (1.0 - grab_h_norm)) 172 | *p_scroll_v = IM_ROUND(scroll_v_norm * scroll_max) //(win_size_contents_v - win_size_v)); 173 | 174 | // Update values for rendering 175 | scroll_ratio = ImSaturate(*p_scroll_v / scroll_max) 176 | grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v 177 | 178 | // Update distance to grab now that we have seeked and saturated 179 | if seek_absolute { 180 | g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm*0.5 181 | } 182 | } 183 | 184 | // Render 185 | var bg_col = GetColorU32FromID(ImGuiCol_ScrollbarBg, 1) 186 | var grab_col = GetColorU32FromID(ImGuiCol_ScrollbarGrab, alpha) 187 | 188 | if held { 189 | grab_col = GetColorU32FromID(ImGuiCol_ScrollbarGrabActive, 1) 190 | } else if hovered { 191 | grab_col = GetColorU32FromID(ImGuiCol_ScrollbarGrabHovered, 1) 192 | } 193 | 194 | window.DrawList.AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window.WindowRounding, flags) 195 | var grab_rect ImRect 196 | if axis == ImGuiAxis_X { 197 | grab_rect = ImRect{ImVec2{ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y}, ImVec2{ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y}} 198 | } else { 199 | grab_rect = ImRect{ImVec2{bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm)}, ImVec2{bb.Max.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels}} 200 | } 201 | 202 | window.DrawList.AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding, 0) 203 | 204 | return held 205 | } 206 | -------------------------------------------------------------------------------- /settings.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | // LoadIniSettingsFromDisk Settings/.Ini Utilities 12 | // - The disk functions are automatically called if io.IniFilename != NULL (default is "imgui.ini"). 13 | // - Set io.IniFilename to NULL to load/save manually. Read io.WantSaveIniSettings description about handling .ini saving manually. 14 | // - Important: default value "imgui.ini" is relative to current working dir! Most apps will want to lock this to an absolute path (e.g. same path as executables). 15 | func LoadIniSettingsFromDisk(ini_filename string) { 16 | var file_data_size uintptr = 0 17 | var file_data = ImFileLoadToMemory(ini_filename, "rb", &file_data_size, 0) 18 | if file_data == nil { 19 | return 20 | } 21 | LoadIniSettingsFromMemory(file_data, (size_t)(file_data_size)) 22 | } // call after CreateContext() and before the first call to NewFrame(). NewFrame() automatically calls LoadIniSettingsFromDisk(io.IniFilename). 23 | 24 | func LoadIniSettingsFromMemory(buf []byte, ini_size uintptr) { 25 | var g = GImGui 26 | IM_ASSERT(g.Initialized) 27 | //IM_ASSERT(!g.WithinFrameScope && "Cannot be called between NewFrame() and EndFrame()"); 28 | //IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0); 29 | 30 | // Call pre-read handlers 31 | // Some types will clear their data (e.g. dock information) some types will allow merge/override (window) 32 | for handler_n := range g.SettingsHandlers { 33 | if g.SettingsHandlers[handler_n].ReadInitFn != nil { 34 | g.SettingsHandlers[handler_n].ReadInitFn(g, &g.SettingsHandlers[handler_n]) 35 | } 36 | } 37 | 38 | var reader = bufio.NewReader(bytes.NewReader(buf)) 39 | var entry_handler *ImGuiSettingsHandler 40 | var entry_data any = nil 41 | 42 | for { 43 | line, err := reader.ReadString('\n') 44 | if err != nil { 45 | break 46 | } 47 | line = line[:len(line)-1] 48 | 49 | if line == "" { 50 | continue 51 | } 52 | 53 | if line[0] == '[' && line[len(line)-1] == ']' { 54 | splits := strings.SplitN(line[1:len(line)-1], "][", 2) 55 | if len(splits) == 2 { 56 | var settings_type = splits[0] 57 | var settings_id = splits[1] 58 | 59 | entry_handler = FindSettingsHandler(settings_type) 60 | if entry_handler != nil { 61 | entry_data = entry_handler.ReadOpenFn(g, entry_handler, settings_id) 62 | } 63 | } 64 | } else if entry_handler != nil { 65 | entry_handler.ReadLineFn(g, entry_handler, entry_data, line) 66 | } 67 | } 68 | 69 | g.SettingsLoaded = true 70 | 71 | // Call post-read handlers 72 | for handler_n := range g.SettingsHandlers { 73 | if g.SettingsHandlers[handler_n].ApplyAllFn != nil { 74 | g.SettingsHandlers[handler_n].ApplyAllFn(g, &g.SettingsHandlers[handler_n]) 75 | } 76 | } 77 | } // call after CreateContext() and before the first call to NewFrame() to provide .ini data from your own data source. 78 | 79 | func SaveIniSettingsToDisk(ini_filename string) { 80 | var g = GImGui 81 | g.SettingsDirtyTimer = 0.0 82 | if ini_filename == "" { 83 | return 84 | } 85 | 86 | var ini_data_size size_t = 0 87 | var ini_data = SaveIniSettingsToMemory(&ini_data_size) 88 | 89 | os.WriteFile(ini_filename, ini_data, 0666) 90 | } // this is automatically called (if io.IniFilename is not empty) a few seconds after any modification that should be reflected in the .ini file (and also by DestroyContext). 91 | 92 | func SaveIniSettingsToMemory(out_size *uintptr) []byte { 93 | var g = GImGui 94 | g.SettingsDirtyTimer = 0.0 95 | g.SettingsIniData = g.SettingsIniData[:0] 96 | for handler_n := range g.SettingsHandlers { 97 | var handler = &g.SettingsHandlers[handler_n] 98 | handler.WriteAllFn(g, handler, &g.SettingsIniData) 99 | } 100 | if out_size != nil { 101 | *out_size = (size_t)(len(g.SettingsIniData)) 102 | } 103 | return g.SettingsIniData 104 | } // return a zero-terminated string with the .ini data which you can save by your own mean. call when io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings. 105 | 106 | // MarkIniSettingsDirty Settings 107 | func MarkIniSettingsDirty() { 108 | var g = GImGui 109 | if g.SettingsDirtyTimer <= 0.0 { 110 | g.SettingsDirtyTimer = g.IO.IniSavingRate 111 | } 112 | } 113 | 114 | func MarkIniSettingsDirtyWindow(window *ImGuiWindow) { 115 | var g = GImGui 116 | if window.Flags&ImGuiWindowFlags_NoSavedSettings == 0 { 117 | if g.SettingsDirtyTimer <= 0.0 { 118 | g.SettingsDirtyTimer = g.IO.IniSavingRate 119 | } 120 | } 121 | } 122 | 123 | func ClearIniSettings() { 124 | var g = GImGui 125 | g.SettingsIniData = g.SettingsIniData[:0] 126 | for handler_n := range g.SettingsHandlers { 127 | if g.SettingsHandlers[handler_n].ClearAllFn != nil { 128 | g.SettingsHandlers[handler_n].ClearAllFn(g, &g.SettingsHandlers[handler_n]) 129 | } 130 | } 131 | } 132 | 133 | func CreateNewWindowSettings(name string) *ImGuiWindowSettings { 134 | var g = GImGui 135 | 136 | if index := strings.Index(name, "###"); index != -1 { 137 | name = name[index:] 138 | } 139 | 140 | var settings ImGuiWindowSettings 141 | 142 | settings.ID = ImHashStr(name, 0, 0) 143 | settings.name = name 144 | 145 | g.SettingsWindows = append(g.SettingsWindows, settings) 146 | 147 | return &g.SettingsWindows[len(g.SettingsWindows)-1] 148 | } 149 | 150 | func FindWindowSettings(id ImGuiID) *ImGuiWindowSettings { 151 | var g = GImGui 152 | for i := range g.SettingsWindows { 153 | settings := &g.SettingsWindows[i] 154 | if settings.ID == id { 155 | return settings 156 | } 157 | } 158 | return nil 159 | } 160 | 161 | func FindOrCreateWindowSettings(name string) *ImGuiWindowSettings { 162 | if settings := FindWindowSettings(ImHashStr(name, 0, 0)); settings != nil { 163 | return settings 164 | } 165 | return CreateNewWindowSettings(name) 166 | } 167 | 168 | func FindSettingsHandler(name string) *ImGuiSettingsHandler { 169 | var g = GImGui 170 | var type_hash = ImHashStr(name, 0, 0) 171 | for handler_n := range g.SettingsHandlers { 172 | if g.SettingsHandlers[handler_n].TypeHash == type_hash { 173 | return &g.SettingsHandlers[handler_n] 174 | } 175 | } 176 | return nil 177 | } 178 | 179 | // UpdateSettings Called by NewFrame() 180 | func UpdateSettings() { 181 | // Load settings on first frame (if not explicitly loaded manually before) 182 | var g = GImGui 183 | if !g.SettingsLoaded { 184 | IM_ASSERT(len(g.SettingsWindows) == 0) 185 | if g.IO.IniFilename != "" { 186 | LoadIniSettingsFromDisk(g.IO.IniFilename) 187 | } 188 | g.SettingsLoaded = true 189 | } 190 | 191 | // Save settings (with a delay after the last modification, so we don't spam disk too much) 192 | if g.SettingsDirtyTimer > 0.0 { 193 | g.SettingsDirtyTimer -= g.IO.DeltaTime 194 | if g.SettingsDirtyTimer <= 0.0 { 195 | if g.IO.IniFilename != "" { 196 | SaveIniSettingsToDisk(g.IO.IniFilename) 197 | } else { 198 | g.IO.WantSaveIniSettings = true // Let user know they can call SaveIniSettingsToMemory(). user will need to clear io.WantSaveIniSettings themselves. 199 | } 200 | g.SettingsDirtyTimer = 0 201 | } 202 | } 203 | } 204 | 205 | // WindowSettingsHandler_ApplyAll Apply to existing windows (if any) 206 | func WindowSettingsHandler_ApplyAll(ctx *ImGuiContext, _ *ImGuiSettingsHandler) { 207 | var g = ctx 208 | for i := range g.SettingsWindows { 209 | settings := &g.SettingsWindows[i] 210 | if settings.WantApply { 211 | if window := FindWindowByID(settings.ID); window != nil { 212 | ApplyWindowSettings(window, settings) 213 | } 214 | settings.WantApply = false 215 | } 216 | } 217 | } 218 | 219 | func ApplyWindowSettings(window *ImGuiWindow, settings *ImGuiWindowSettings) { 220 | window.Pos = *ImFloorVec(&ImVec2{float(settings.Pos.x), float(settings.Pos.y)}) 221 | if settings.Size.x > 0 && settings.Size.y > 0 { 222 | window.Size = *ImFloorVec(&ImVec2{float(settings.Size.x), float(settings.Size.y)}) 223 | window.SizeFull = *ImFloorVec(&ImVec2{float(settings.Size.x), float(settings.Size.y)}) 224 | } 225 | window.Collapsed = settings.Collapsed 226 | } 227 | 228 | func WindowSettingsHandler_ClearAll(ctx *ImGuiContext, _ *ImGuiSettingsHandler) { 229 | var g = ctx 230 | for i := range g.Windows { 231 | g.Windows[i].SettingsOffset = -1 232 | } 233 | g.SettingsWindows = g.SettingsWindows[:0] 234 | } 235 | 236 | func WindowSettingsHandler_ReadOpen(_ *ImGuiContext, _ *ImGuiSettingsHandler, name string) any { 237 | var settings = FindOrCreateWindowSettings(name) 238 | var id = settings.ID 239 | *settings = ImGuiWindowSettings{} // Clear existing if recycling previous entry 240 | settings.ID = id 241 | settings.name = name 242 | settings.WantApply = true 243 | return settings 244 | } 245 | 246 | func WindowSettingsHandler_ReadLine(_ *ImGuiContext, _ *ImGuiSettingsHandler, entry any, line string) { 247 | var settings = entry.(*ImGuiWindowSettings) 248 | var x, y int 249 | var i bool 250 | 251 | if n, _ := fmt.Sscanf(line, "Pos=%v,%v", &x, &y); n == 2 { 252 | settings.Pos = ImVec2ih{(short)(x), (short)(y)} 253 | } else if n, _ := fmt.Sscanf(line, "Size=%v,%v", &x, &y); n == 2 { 254 | settings.Size = ImVec2ih{(short)(x), (short)(y)} 255 | } else if n, _ := fmt.Sscanf(line, "Collapsed=%v", &i); n == 1 { 256 | settings.Collapsed = i 257 | } 258 | } 259 | 260 | func WindowSettingsHandler_WriteAll(ctx *ImGuiContext, handler *ImGuiSettingsHandler, buf *ImGuiTextBuffer) { 261 | // Gather data from windows that were active during this session 262 | // (if a window wasn't opened in this session we preserve its settings) 263 | var g = ctx 264 | for i := range g.Windows { 265 | var window = g.Windows[i] 266 | if window.Flags&ImGuiWindowFlags_NoSavedSettings != 0 { 267 | continue 268 | } 269 | 270 | var settings *ImGuiWindowSettings 271 | if window.SettingsOffset != -1 { 272 | settings = &g.SettingsWindows[window.SettingsOffset] 273 | } else { 274 | settings = FindOrCreateWindowSettings(window.Name) 275 | } 276 | 277 | if settings == nil { 278 | settings = CreateNewWindowSettings(window.Name) 279 | 280 | window.SettingsOffset = -1 281 | for i := range g.SettingsWindows { 282 | if settings == &g.SettingsWindows[i] { 283 | window.SettingsOffset = int(i) 284 | break 285 | } 286 | } 287 | } 288 | IM_ASSERT(settings.ID == window.ID) 289 | settings.Pos = ImVec2ih{short(window.Pos.x), short(window.Pos.y)} 290 | settings.Size = ImVec2ih{short(window.Size.x), short(window.Size.y)} 291 | 292 | settings.Collapsed = window.Collapsed 293 | } 294 | 295 | // Write to text buffer 296 | for i := range g.SettingsWindows { 297 | settings := &g.SettingsWindows[i] 298 | var settings_name = settings.GetName() 299 | *buf = append(*buf, []byte(fmt.Sprintf("[%s][%s]\n", handler.TypeName, settings_name))...) 300 | *buf = append(*buf, []byte(fmt.Sprintf("Pos=%d,%d\n", settings.Pos.x, settings.Pos.y))...) 301 | *buf = append(*buf, []byte(fmt.Sprintf("Size=%d,%d\n", settings.Size.x, settings.Size.y))...) 302 | *buf = append(*buf, []byte(fmt.Sprintf("Collapsed=%v\n", settings.Collapsed))...) 303 | *buf = append(*buf, []byte("\n")...) 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /shading.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // Shade functions (write over already created vertices) 4 | 5 | // ShadeVertsLinearColorGradientKeepAlpha Generic linear color gradient, write to RGB fields, leave A untouched. 6 | func ShadeVertsLinearColorGradientKeepAlpha(draw_list *ImDrawList, vert_start_idx int, vert_end_idx int, gradient_p0 ImVec2, gradient_p1 ImVec2, col0 ImU32, col1 ImU32) { 7 | var gradient_extent = gradient_p1.Sub(gradient_p0) 8 | var gradient_inv_length2 = 1.0 / ImLengthSqrVec2(gradient_extent) 9 | var col0_r = (int)(col0>>IM_COL32_R_SHIFT) & 0xFF 10 | var col0_g = (int)(col0>>IM_COL32_G_SHIFT) & 0xFF 11 | var col0_b = (int)(col0>>IM_COL32_B_SHIFT) & 0xFF 12 | var col_delta_r = ((int)(col1>>IM_COL32_R_SHIFT) & 0xFF) - col0_r 13 | var col_delta_g = ((int)(col1>>IM_COL32_G_SHIFT) & 0xFF) - col0_g 14 | var col_delta_b = ((int)(col1>>IM_COL32_B_SHIFT) & 0xFF) - col0_b 15 | for vert_idx := vert_start_idx; vert_idx < vert_end_idx; vert_idx++ { 16 | vert := draw_list.VtxBuffer[vert_idx] 17 | var diff = vert.Pos.Sub(gradient_p0) 18 | var d = ImDot(&diff, &gradient_extent) 19 | var t = ImClamp(d*gradient_inv_length2, 0.0, 1.0) 20 | var r = (int)(float(col0_r) + float(col_delta_r)*t) 21 | var g = (int)(float(col0_g) + float(col_delta_g)*t) 22 | var b = (int)(float(col0_b) + float(col_delta_b)*t) 23 | vert.Col = (uint(r) << IM_COL32_R_SHIFT) | (uint(g) << IM_COL32_G_SHIFT) | (uint(b) << IM_COL32_B_SHIFT) | (vert.Col & IM_COL32_A_MASK) 24 | } 25 | } 26 | 27 | // ShadeVertsLinearUV Distribute UV over (a, b) rectangle 28 | func ShadeVertsLinearUV(t *ImDrawList, vert_start_idx int, vert_end_idx int, a *ImVec2, b *ImVec2, uv_a *ImVec2, uv_b *ImVec2, clamp bool) { 29 | var size = b.Sub(*a) 30 | var uv_size = uv_b.Sub(*uv_a) 31 | 32 | var scale ImVec2 33 | if size.x != 0.0 { 34 | scale.x = uv_size.x / size.x 35 | } 36 | if size.y != 0.0 { 37 | scale.y = uv_size.y / size.y 38 | } 39 | 40 | if clamp { 41 | var min = ImMinVec2(uv_a, uv_b) 42 | var max = ImMinVec2(uv_a, uv_b) 43 | for vertex_idx := vert_start_idx; vertex_idx < vert_end_idx; vertex_idx++ { 44 | vertex := &t.VtxBuffer[vertex_idx] 45 | d := ImVec2{vertex.Pos.x, vertex.Pos.y}.Sub(*a) 46 | v := uv_a.Add(*ImMul(&d, &scale)) 47 | vertex.Uv = ImClampVec2(&v, &min, max) 48 | } 49 | } else { 50 | for vertex_idx := vert_start_idx; vertex_idx < vert_end_idx; vertex_idx++ { 51 | vertex := &t.VtxBuffer[vertex_idx] 52 | v := ImVec2{vertex.Pos.x, vertex.Pos.y}.Sub(*a) 53 | vertex.Uv = uv_a.Add(*ImMul(&v, &scale)) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spacing.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // move content position toward the right, by indent_w, or style.IndentSpacing if indent_w <= 0 4 | func Indent(indent_w float) { 5 | var g = GImGui 6 | var window = GetCurrentWindow() 7 | 8 | if indent_w == 0 { 9 | indent_w = g.Style.IndentSpacing 10 | } 11 | 12 | window.DC.Indent.x += indent_w 13 | window.DC.CursorPos.x = window.Pos.x + window.DC.Indent.x + window.DC.ColumnsOffset.x 14 | } 15 | 16 | // move content position back to the left, by indent_w, or style.IndentSpacing if indent_w <= 0 17 | func Unindent(indent_w float) { 18 | var g = GImGui 19 | var window = GetCurrentWindow() 20 | 21 | if indent_w == 0 { 22 | indent_w = g.Style.IndentSpacing 23 | } 24 | 25 | window.DC.Indent.x -= indent_w 26 | window.DC.CursorPos.x = window.Pos.x + window.DC.Indent.x + window.DC.ColumnsOffset.x 27 | } 28 | -------------------------------------------------------------------------------- /stb/stb.go: -------------------------------------------------------------------------------- 1 | package stb 2 | -------------------------------------------------------------------------------- /storage.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | /* 8 | The storage functions have been modified to use an ordinary Go map, 9 | this is probably inefficient, but convinient for now - Quentin. 10 | */ 11 | 12 | // ImGuiStoragePair [Internal] 13 | type ImGuiStoragePair struct { 14 | key ImGuiID 15 | val int 16 | } 17 | 18 | func (this *ImGuiStoragePair) SetInt(key ImGuiID, val int) { 19 | this.key = key 20 | this.val = val 21 | } 22 | 23 | func (this *ImGuiStoragePair) SetFloat(key ImGuiID, val float) { 24 | this.key = key 25 | *(*float)(unsafe.Pointer(&this.val)) = val 26 | } 27 | 28 | // ImGuiStorage Helper: Key->Value storage 29 | // Typically you don't have to worry about this since a storage is held within each Window. 30 | // We use it to e.g. store collapse state for a tree (Int 0/1) 31 | // This is optimized for efficient lookup (dichotomy into a contiguous buffer) and rare insertion (typically tied to user interactions aka max once a frame) 32 | // You can use it as custom user storage for temporary values. Declare your own storage if, for example: 33 | // - You want to manipulate the open/close state of a particular sub-tree in your interface (tree node uses Int 0/1 to store their state). 34 | // - You want to store custom debug data easily without adding or editing structures in your code (probably not efficient, but convenient) 35 | // Types are NOT stored, so it is up to you to make sure your Key don't collide with different types. 36 | type ImGuiStorage struct { 37 | Data map[ImGuiID]int 38 | Pointers map[ImGuiID]any 39 | } 40 | 41 | func (this *ImGuiStorage) Clear() { 42 | this.Data = map[ImGuiID]int{} 43 | this.Pointers = map[ImGuiID]any{} 44 | } 45 | 46 | func (this *ImGuiStorage) GetInt(key ImGuiID, default_val int) int { 47 | val, ok := this.Data[key] 48 | if !ok { 49 | return default_val 50 | } 51 | return val 52 | } 53 | 54 | func (this *ImGuiStorage) SetInt(key ImGuiID, val int) { 55 | if this.Data == nil { 56 | this.Data = map[ImGuiID]int{} 57 | } 58 | this.Data[key] = val 59 | } 60 | 61 | func (this *ImGuiStorage) GetBool(key ImGuiID, default_val bool) bool { 62 | var def int 63 | if default_val { 64 | def = 1 65 | } 66 | return this.GetInt(key, def) != 0 67 | } 68 | 69 | func (this *ImGuiStorage) SetBool(key ImGuiID, val bool) { 70 | if val { 71 | this.SetInt(key, 1) 72 | } else { 73 | this.SetInt(key, 0) 74 | } 75 | } 76 | 77 | func (this *ImGuiStorage) GetFloat(key ImGuiID, default_val float) float { 78 | val := this.GetInt(key, *(*int)(unsafe.Pointer(&default_val))) 79 | return *(*float)(unsafe.Pointer(&val)) 80 | } 81 | 82 | func (this *ImGuiStorage) SetFloat(key ImGuiID, val float) { 83 | this.SetInt(key, *(*int)(unsafe.Pointer(&val))) 84 | } 85 | 86 | func (this *ImGuiStorage) GetInterface(key ImGuiID) any { 87 | return this.Pointers[key] 88 | } 89 | 90 | func (this *ImGuiStorage) SetInterface(key ImGuiID, val any) { 91 | if this.Pointers == nil { 92 | this.Pointers = make(map[ImGuiID]any) 93 | } 94 | this.Pointers[key] = val 95 | } 96 | 97 | // SetAllInt Use on your own storage if you know only integer are being stored (open/close all tree nodes) 98 | func (this *ImGuiStorage) SetAllInt(val int) { 99 | for key := range this.Data { 100 | this.Data[key] = val 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /tabs.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | func UpdateTabFocus() { 4 | var g = GImGui 5 | 6 | // Pressing TAB activate widget focus 7 | g.TabFocusPressed = g.NavWindow != nil && g.NavWindow.Active && (g.NavWindow.Flags&ImGuiWindowFlags_NoNavInputs == 0) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab, true) 8 | if g.ActiveId == 0 && g.TabFocusPressed { 9 | // - This path is only taken when no widget are active/tabbed-into yet. 10 | // Subsequent tabbing will be processed by FocusableItemRegister() 11 | // - Note that SetKeyboardFocusHere() sets the Next fields mid-frame. To be consistent we also 12 | // manipulate the Next fields here even though they will be turned into Curr fields below. 13 | g.TabFocusRequestNextWindow = g.NavWindow 14 | g.TabFocusRequestNextCounterRegular = INT_MAX 15 | 16 | var shift int 17 | if g.IO.KeyShift { 18 | shift = -1 19 | } 20 | 21 | if g.NavId != 0 && g.NavIdTabCounter != INT_MAX { 22 | g.TabFocusRequestNextCounterTabStop = g.NavIdTabCounter + shift 23 | } else { 24 | g.TabFocusRequestNextCounterTabStop = shift 25 | } 26 | } 27 | 28 | // Turn queued focus request into current one 29 | g.TabFocusRequestCurrWindow = nil 30 | g.TabFocusRequestCurrCounterRegular = INT_MAX 31 | g.TabFocusRequestCurrCounterTabStop = INT_MAX 32 | if g.TabFocusRequestNextWindow != nil { 33 | var window = g.TabFocusRequestNextWindow 34 | g.TabFocusRequestCurrWindow = window 35 | if g.TabFocusRequestNextCounterRegular != INT_MAX && window.DC.FocusCounterRegular != -1 { 36 | g.TabFocusRequestCurrCounterRegular = ImModPositive(g.TabFocusRequestNextCounterRegular, window.DC.FocusCounterRegular+1) 37 | } 38 | if g.TabFocusRequestNextCounterTabStop != INT_MAX && window.DC.FocusCounterTabStop != -1 { 39 | g.TabFocusRequestCurrCounterTabStop = ImModPositive(g.TabFocusRequestNextCounterTabStop, window.DC.FocusCounterTabStop+1) 40 | } 41 | g.TabFocusRequestNextWindow = nil 42 | g.TabFocusRequestNextCounterRegular = INT_MAX 43 | g.TabFocusRequestNextCounterTabStop = INT_MAX 44 | } 45 | 46 | g.NavIdTabCounter = INT_MAX 47 | } 48 | -------------------------------------------------------------------------------- /text.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // push word-wrapping position for Text*() commands. < 0.0: no wrapping; 0.0: wrap to end of window (or column) {panic("not implemented")} > 0.0: wrap at 'wrap_pos_x' position in window local space 4 | func PushTextWrapPos(wrap_local_pos_x float) { 5 | var window = GetCurrentWindow() 6 | window.DC.TextWrapPosStack = append(window.DC.TextWrapPosStack, window.DC.TextWrapPos) 7 | window.DC.TextWrapPos = wrap_local_pos_x 8 | } 9 | 10 | func PopTextWrapPos() { 11 | var window = GetCurrentWindow() 12 | window.DC.TextWrapPos = window.DC.TextWrapPosStack[len(window.DC.TextWrapPosStack)-1] 13 | window.DC.TextWrapPosStack = window.DC.TextWrapPosStack[:len(window.DC.TextWrapPosStack)-1] 14 | } 15 | -------------------------------------------------------------------------------- /textfilters.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Helper: Growable text buffer for logging/accumulating text 8 | // (this could be called 'ImGuiTextBuilder' / 'ImGuiStringBuilder') 9 | type ImGuiTextBuffer []byte 10 | 11 | // Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" 12 | type ImGuiTextFilter struct { 13 | InputBuf []byte 14 | Filters []string 15 | CountGrep int 16 | } 17 | 18 | func NewImGuiTextFilter(default_filter string) ImGuiTextFilter { 19 | if default_filter != "" { 20 | filter := ImGuiTextFilter{InputBuf: []byte(default_filter)} 21 | filter.Build() 22 | return filter 23 | } 24 | return ImGuiTextFilter{} 25 | } 26 | 27 | func (this *ImGuiTextFilter) Draw(label string /*= "Filter (inc,-exc)"*/, width float) bool { 28 | if width != 0.0 { 29 | SetNextItemWidth(width) 30 | } 31 | var value_changed = InputText(label, &this.InputBuf, 0, nil, nil) 32 | if value_changed { 33 | this.Build() 34 | } 35 | return value_changed 36 | } 37 | 38 | // Helper calling InputText+Build 39 | func (this *ImGuiTextFilter) PassFilter(text string) bool { 40 | if len(this.Filters) == 0 { 41 | return true 42 | } 43 | 44 | for _, f := range this.Filters { 45 | if len(f) == 0 { 46 | continue 47 | } 48 | if f[0] == '-' { 49 | if strings.Contains(text, f[1:]) { 50 | return false 51 | } 52 | } else { 53 | // Grep 54 | if strings.Contains(text, f) { 55 | return false 56 | } 57 | } 58 | } 59 | 60 | // Implicit * grep 61 | if this.CountGrep == 0 { 62 | return true 63 | } 64 | 65 | return false 66 | } 67 | 68 | func (this *ImGuiTextFilter) Build() { 69 | this.Filters = strings.Split(string(this.InputBuf), ",") 70 | 71 | this.CountGrep = 0 72 | for i := range this.Filters { 73 | this.Filters[i] = strings.TrimSpace(this.Filters[i]) 74 | if len(this.Filters[i]) == 0 { 75 | continue 76 | } 77 | if this.Filters[i][0] != '-' { 78 | this.CountGrep += 1 79 | } 80 | } 81 | } 82 | 83 | func (this *ImGuiTextFilter) Clear() { 84 | this.InputBuf[0] = 0 85 | this.Build() 86 | } 87 | 88 | func (this *ImGuiTextFilter) IsActive() bool { return len(this.Filters) > 0 } 89 | -------------------------------------------------------------------------------- /tooltips.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | import "fmt" 4 | 5 | // BeginTooltip Tooltips 6 | // - Tooltip are windows following the mouse. They do not take focus away. 7 | // begin/append a tooltip window. to create full-featured tooltip (with any kind of items). 8 | func BeginTooltip() { 9 | BeginTooltipEx(ImGuiWindowFlags_None, ImGuiTooltipFlags_None) 10 | } 11 | 12 | func EndTooltip() { 13 | IM_ASSERT(GetCurrentWindowRead().Flags&ImGuiWindowFlags_Tooltip != 0) // Mismatched BeginTooltip()/EndTooltip() calls 14 | End() 15 | } 16 | 17 | // SetTooltip set a text-only tooltip, typically use with ImGui::IsItemHovered(). override any previous call to SetTooltip(). 18 | func SetTooltip(format string, args ...any) { 19 | BeginTooltipEx(0, ImGuiTooltipFlags_OverridePreviousTooltip) 20 | Text(format, args...) 21 | EndTooltip() 22 | } 23 | 24 | func BeginTooltipEx(extra_flags ImGuiWindowFlags, tooltip_flags ImGuiTooltipFlags) { 25 | var g = GImGui 26 | 27 | if g.DragDropWithinSource || g.DragDropWithinTarget { 28 | // The default tooltip position is a little offset to give space to see the context menu (it's also clamped within the current viewport/monitor) 29 | // In the context of a dragging tooltip we try to reduce that offset and we enforce following the cursor. 30 | // Whatever we do we want to call SetNextWindowPos() to enforce a tooltip position and disable clipping the tooltip without our display area, like regular tooltip do. 31 | //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding; 32 | var tooltip_pos = g.IO.MousePos.Add(ImVec2{16 * g.Style.MouseCursorScale, 8 * g.Style.MouseCursorScale}) 33 | SetNextWindowPos(&tooltip_pos, 0, ImVec2{}) 34 | SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60) 35 | //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :( 36 | tooltip_flags |= ImGuiTooltipFlags_OverridePreviousTooltip 37 | } 38 | 39 | var window_name = fmt.Sprintf("##Tooltip_%02d", g.TooltipOverrideCount) 40 | if tooltip_flags&ImGuiTooltipFlags_OverridePreviousTooltip != 0 { 41 | if window := FindWindowByName(window_name); window != nil { 42 | if window.Active { 43 | // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one. 44 | window.Hidden = true 45 | window.HiddenFramesCanSkipItems = 1 // FIXME: This may not be necessary? 46 | g.TooltipOverrideCount++ 47 | window_name = fmt.Sprintf("##Tooltip_%02d", g.TooltipOverrideCount) 48 | } 49 | } 50 | } 51 | var flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize 52 | Begin(window_name, nil, flags|extra_flags) 53 | } 54 | -------------------------------------------------------------------------------- /viewports.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // Update viewports and monitor infos 4 | func UpdateViewportsNewFrame() { 5 | var g = GImGui 6 | IM_ASSERT(len(g.Viewports) == 1) 7 | 8 | // Update main viewport with current platform position. 9 | // FIXME-VIEWPORT: Size is driven by backend/user code for backward-compatibility but we should aim to make this more consistent. 10 | var main_viewport = g.Viewports[0] 11 | main_viewport.Flags = ImGuiViewportFlags_IsPlatformWindow | ImGuiViewportFlags_OwnedByApp 12 | main_viewport.Pos = ImVec2{} 13 | main_viewport.Size = g.IO.DisplaySize 14 | 15 | for n := range g.Viewports { 16 | var viewport = g.Viewports[n] 17 | 18 | // Lock down space taken by menu bars and status bars, reset the offset for fucntions like BeginMainMenuBar() to alter them again. 19 | viewport.WorkOffsetMin = viewport.BuildWorkOffsetMin 20 | viewport.WorkOffsetMax = viewport.BuildWorkOffsetMax 21 | viewport.BuildWorkOffsetMin = ImVec2{} 22 | viewport.BuildWorkOffsetMax = ImVec2{} 23 | viewport.UpdateWorkRect() 24 | } 25 | } 26 | 27 | func SetupViewportDrawData(viewport *ImGuiViewportP, draw_lists *[]*ImDrawList) { 28 | var io = GetIO() 29 | var draw_data = &viewport.DrawDataP 30 | draw_data.Valid = true 31 | if len(*draw_lists) > 0 { 32 | draw_data.CmdLists = *draw_lists 33 | } 34 | draw_data.CmdListsCount = int(len(*draw_lists)) 35 | draw_data.TotalVtxCount = 0 36 | draw_data.TotalIdxCount = 0 37 | draw_data.DisplayPos = viewport.Pos 38 | draw_data.DisplaySize = viewport.Size 39 | draw_data.FramebufferScale = io.DisplayFramebufferScale 40 | 41 | for _, v := range *draw_lists { 42 | 43 | draw_data.TotalVtxCount += int(len(v.VtxBuffer)) 44 | draw_data.TotalIdxCount += int(len(v.IdxBuffer)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /widgets.behaviour.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | func SplitterBehavior(bb *ImRect, id ImGuiID, axis ImGuiAxis, size1 *float, size2 *float, min_size1 float, min_size2 float, hover_extend float, hover_visibility_delay float) bool { 4 | var g = GImGui 5 | var window = g.CurrentWindow 6 | 7 | var item_flags_backup = g.CurrentItemFlags 8 | g.CurrentItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus 9 | var item_add = ItemAdd(bb, id, nil, 0) 10 | g.CurrentItemFlags = item_flags_backup 11 | if !item_add { 12 | return false 13 | } 14 | 15 | var hovered, held bool 16 | var bb_interact = *bb 17 | if axis == ImGuiAxis_Y { 18 | bb_interact.ExpandVec(ImVec2{0.0, hover_extend}) 19 | } else { 20 | bb_interact.ExpandVec(ImVec2{hover_extend, 0.0}) 21 | } 22 | ButtonBehavior(&bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren|ImGuiButtonFlags_AllowItemOverlap) 23 | if hovered { 24 | g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect // for IsItemHovered(), because bb_interact is larger than bb 25 | } 26 | if g.ActiveId != id { 27 | SetItemAllowOverlap() 28 | } 29 | 30 | if held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay) { 31 | if axis == ImGuiAxis_Y { 32 | SetMouseCursor(ImGuiMouseCursor_ResizeNS) 33 | } else { 34 | SetMouseCursor(ImGuiMouseCursor_ResizeEW) 35 | } 36 | } 37 | 38 | var bb_render = *bb 39 | if held { 40 | var mouse_delta_2d = g.IO.MousePos.Sub(g.ActiveIdClickOffset).Sub(bb_interact.Min) 41 | var mouse_delta = mouse_delta_2d.x 42 | if axis == ImGuiAxis_Y { 43 | mouse_delta = mouse_delta_2d.y 44 | } 45 | 46 | // Minimum pane size 47 | var size_1_maximum_delta = ImMax(0.0, *size1-min_size1) 48 | var size_2_maximum_delta = ImMax(0.0, *size2-min_size2) 49 | if mouse_delta < -size_1_maximum_delta { 50 | mouse_delta = -size_1_maximum_delta 51 | } 52 | if mouse_delta > size_2_maximum_delta { 53 | mouse_delta = size_2_maximum_delta 54 | } 55 | 56 | // Apply resize 57 | if mouse_delta != 0.0 { 58 | if mouse_delta < 0.0 { 59 | IM_ASSERT(*size1+mouse_delta >= min_size1) 60 | } 61 | if mouse_delta > 0.0 { 62 | IM_ASSERT(*size2-mouse_delta >= min_size2) 63 | } 64 | *size1 += mouse_delta 65 | *size2 -= mouse_delta 66 | if axis == ImGuiAxis_X { 67 | bb_render.Translate(ImVec2{mouse_delta, 0.0}) 68 | } else { 69 | bb_render.Translate(ImVec2{0.0, mouse_delta}) 70 | } 71 | MarkItemEdited(id) 72 | } 73 | } 74 | 75 | var c = ImGuiCol_Separator 76 | if held { 77 | c = ImGuiCol_SeparatorActive 78 | } else if hovered && g.HoveredIdTimer >= hover_visibility_delay { 79 | c = ImGuiCol_SeparatorHovered 80 | } 81 | 82 | // Render 83 | var col = GetColorU32FromID(c, 1) 84 | window.DrawList.AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0, 0) 85 | 86 | return held 87 | } 88 | 89 | // FIXME: Move more of the code into SliderBehavior() 90 | func sliderBehaviour(bb *ImRect, id ImGuiID, v *float, v_min float, v_max float, format string, flags ImGuiSliderFlags, out_grab_bb *ImRect) bool { 91 | var g = GImGui 92 | var style = g.Style 93 | 94 | var axis = ImGuiAxis_X 95 | if (flags & ImGuiSliderFlags_Vertical) != 0 { 96 | axis = ImGuiAxis_Y 97 | } 98 | 99 | var is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0 100 | var is_floating_point = true 101 | 102 | var grab_padding float = 2.0 103 | var slider_sz = (bb.Max.Axis(axis) - bb.Min.Axis(axis)) - grab_padding*2.0 104 | var grab_sz = style.GrabMinSize 105 | var v_range = v_min - v_max 106 | if v_min < v_max { 107 | v_range = v_max - v_min 108 | } 109 | if !is_floating_point && v_range >= 0 { // v_range < 0 may happen on integer overflows 110 | grab_sz = ImMax((float)(slider_sz/(v_range+1)), style.GrabMinSize) // For integer sliders: if possible have the grab size represent 1 unit 111 | } 112 | grab_sz = ImMin(grab_sz, slider_sz) 113 | var slider_usable_sz = slider_sz - grab_sz 114 | var slider_usable_pos_min = bb.Min.Axis(axis) + grab_padding + grab_sz*0.5 115 | var slider_usable_pos_max = bb.Max.Axis(axis) - grab_padding - grab_sz*0.5 116 | 117 | var logarithmic_zero_epsilon float = 0.0 // Only valid when is_logarithmic is true 118 | var zero_deadzone_halfsize float = 0.0 // Only valid when is_logarithmic is true 119 | if is_logarithmic { 120 | // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. 121 | var decimal_precision int = 1 122 | if is_floating_point { 123 | decimal_precision = 3 124 | } 125 | logarithmic_zero_epsilon = ImPow(0.1, (float)(decimal_precision)) 126 | zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5) / ImMax(slider_usable_sz, 1.0) 127 | } 128 | 129 | // Process interacting with the slider 130 | var value_changed = false 131 | if g.ActiveId == id { 132 | var set_new_value = false 133 | var clicked_t float = 0.0 134 | if g.ActiveIdSource == ImGuiInputSource_Mouse { 135 | if !g.IO.MouseDown[0] { 136 | ClearActiveID() 137 | } else { 138 | var mouse_abs_pos = g.IO.MousePos.Axis(axis) 139 | clicked_t = 0.0 140 | if slider_usable_sz > 0.0 { 141 | clicked_t = ImClamp((mouse_abs_pos-slider_usable_pos_min)/slider_usable_sz, 0.0, 1.0) 142 | } 143 | if axis == ImGuiAxis_Y { 144 | clicked_t = 1.0 - clicked_t 145 | } 146 | set_new_value = true 147 | } 148 | } else if g.ActiveIdSource == ImGuiInputSource_Nav { 149 | if g.ActiveIdIsJustActivated { 150 | g.SliderCurrentAccum = 0.0 // Reset any stored nav delta upon activation 151 | g.SliderCurrentAccumDirty = false 152 | } 153 | 154 | var input_delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard|ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0, 0.0) 155 | var input_delta = -input_delta2.y 156 | if axis == ImGuiAxis_X { 157 | input_delta = input_delta2.x 158 | } 159 | if input_delta != 0.0 { 160 | var decimal_precision int 161 | if is_floating_point { 162 | decimal_precision = 3 163 | } 164 | if decimal_precision > 0 { 165 | input_delta /= 100.0 // Gamepad/keyboard tweak speeds in % of slider bounds 166 | if IsNavInputDown(ImGuiNavInput_TweakSlow) { 167 | input_delta /= 10.0 168 | } 169 | } else { 170 | if (v_range >= -100.0 && v_range <= 100.0) || IsNavInputDown(ImGuiNavInput_TweakSlow) { 171 | // Gamepad/keyboard tweak speeds in integer steps 172 | if input_delta < 0.0 { 173 | input_delta = -1.0 / v_range 174 | } else { 175 | input_delta = 1.0 / v_range 176 | } 177 | } else { 178 | input_delta /= 100.0 179 | } 180 | } 181 | if IsNavInputDown(ImGuiNavInput_TweakFast) { 182 | input_delta *= 10.0 183 | } 184 | 185 | g.SliderCurrentAccum += input_delta 186 | g.SliderCurrentAccumDirty = true 187 | } 188 | 189 | var delta = g.SliderCurrentAccum 190 | if g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated { 191 | ClearActiveID() 192 | } else if g.SliderCurrentAccumDirty { 193 | clicked_t = ScaleRatioFromValueT(*v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize) 194 | 195 | if (clicked_t >= 1.0 && delta > 0.0) || (clicked_t <= 0.0 && delta < 0.0) { // This is to avoid applying the saturation when already past the limits 196 | set_new_value = false 197 | g.SliderCurrentAccum = 0.0 // If pushing up against the limits, don't continue to accumulate 198 | } else { 199 | set_new_value = true 200 | var old_clicked_t = clicked_t 201 | clicked_t = ImSaturate(clicked_t + delta) 202 | 203 | // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator 204 | var v_new = ScaleValueFromRatioT(clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize) 205 | if (flags & ImGuiSliderFlags_NoRoundToFormat) == 0 { 206 | v_new = RoundScalarWithFormatT(format, v_new) 207 | } 208 | var new_clicked_t = ScaleRatioFromValueT(v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize) 209 | 210 | if delta > 0 { 211 | g.SliderCurrentAccum -= ImMin(new_clicked_t-old_clicked_t, delta) 212 | } else { 213 | g.SliderCurrentAccum -= ImMax(new_clicked_t-old_clicked_t, delta) 214 | } 215 | } 216 | 217 | g.SliderCurrentAccumDirty = false 218 | } 219 | } 220 | 221 | if set_new_value { 222 | var v_new = ScaleValueFromRatioT(clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize) 223 | 224 | // Round to user desired precision based on format string 225 | if (flags & ImGuiSliderFlags_NoRoundToFormat) == 0 { 226 | v_new = RoundScalarWithFormatT(format, v_new) 227 | } 228 | 229 | // Apply result 230 | if *v != v_new { 231 | *v = v_new 232 | value_changed = true 233 | } 234 | } 235 | } 236 | 237 | if slider_sz < 1.0 { 238 | *out_grab_bb = ImRect{bb.Min, bb.Min} 239 | } else { 240 | // Output grab position so it can be displayed by the caller 241 | var grab_t = ScaleRatioFromValueT(*v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize) 242 | if axis == ImGuiAxis_Y { 243 | grab_t = 1.0 - grab_t 244 | } 245 | var grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t) 246 | if axis == ImGuiAxis_X { 247 | *out_grab_bb = ImRect{ImVec2{grab_pos - grab_sz*0.5, bb.Min.y + grab_padding}, ImVec2{grab_pos + grab_sz*0.5, bb.Max.y - grab_padding}} 248 | } else { 249 | *out_grab_bb = ImRect{ImVec2{bb.Min.x + grab_padding, grab_pos - grab_sz*0.5}, ImVec2{bb.Max.x - grab_padding, grab_pos + grab_sz*0.5}} 250 | } 251 | } 252 | 253 | return value_changed 254 | } 255 | 256 | // For 32-bit and larger types, slider bounds are limited to half the natural type range. 257 | // So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok. 258 | // It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders. 259 | func SliderBehavior(bb *ImRect, id ImGuiID, data_type ImGuiDataType, p_v any, p_min any, p_max any, format string, flags ImGuiSliderFlags, out_grab_bb *ImRect) bool { 260 | // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert. 261 | IM_ASSERT_USER_ERROR((flags == 1 || (flags&ImGuiSliderFlags_InvalidMask_) == 0), "Invalid ImGuiSliderFlags flag! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.") 262 | 263 | var g = GImGui 264 | if (g.LastItemData.InFlags&ImGuiItemFlags_ReadOnly != 0) || (flags&ImGuiSliderFlags_ReadOnly != 0) { 265 | return false 266 | } 267 | 268 | switch data_type { 269 | case ImGuiDataType_Float: 270 | IM_ASSERT(*p_min.(*float) >= -FLT_MAX/2.0 && *p_max.(*float) <= FLT_MAX/2.0) 271 | return sliderBehaviour(bb, id, p_v.(*float), *p_min.(*float), *p_max.(*float), format, flags, out_grab_bb) 272 | } 273 | 274 | // FIXME: support other slider types 275 | // https://github.com/ocornut/imgui/blob/5ee40c8d34bea3009cf462ec963225bd22067e5e/imgui_widgets.cpp#L2959 276 | IM_ASSERT(false) 277 | return false 278 | } 279 | -------------------------------------------------------------------------------- /widgets.checkbox.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | func CheckboxFlagsInt(label string, flags *int, flags_value int) bool { 4 | var all_on = (*flags & flags_value) == flags_value 5 | var any_on = (*flags & flags_value) != 0 6 | var pressed bool 7 | if !all_on && any_on { 8 | var g = GImGui 9 | var backup_item_flags = g.CurrentItemFlags 10 | g.CurrentItemFlags |= ImGuiItemFlags_MixedValue 11 | pressed = Checkbox(label, &all_on) 12 | g.CurrentItemFlags = backup_item_flags 13 | } else { 14 | pressed = Checkbox(label, &all_on) 15 | 16 | } 17 | if pressed { 18 | if all_on { 19 | *flags |= flags_value 20 | } else { 21 | *flags &= ^flags_value 22 | } 23 | } 24 | return pressed 25 | } 26 | 27 | func CheckboxFlagsUint(label string, flags *uint, flags_value uint) bool { 28 | var all_on = (*flags & flags_value) == flags_value 29 | var any_on = (*flags & flags_value) != 0 30 | var pressed bool 31 | if !all_on && any_on { 32 | var g = GImGui 33 | var backup_item_flags = g.CurrentItemFlags 34 | g.CurrentItemFlags |= ImGuiItemFlags_MixedValue 35 | pressed = Checkbox(label, &all_on) 36 | g.CurrentItemFlags = backup_item_flags 37 | } else { 38 | pressed = Checkbox(label, &all_on) 39 | 40 | } 41 | if pressed { 42 | if all_on { 43 | *flags |= flags_value 44 | } else { 45 | *flags &= ^flags_value 46 | } 47 | } 48 | return pressed 49 | } 50 | 51 | func Checkbox(label string, v *bool) bool { 52 | var window = GetCurrentWindow() 53 | if window.SkipItems { 54 | return false 55 | } 56 | 57 | var g = GImGui 58 | var style = g.Style 59 | var id = window.GetIDs(label) 60 | var label_size = CalcTextSize(label, true, -1) 61 | 62 | var square_sz = GetFrameHeight() 63 | var pos = window.DC.CursorPos 64 | 65 | var x float 66 | if label_size.x > 0 { 67 | x = style.ItemInnerSpacing.x + label_size.x 68 | } 69 | 70 | var total_bb = ImRect{pos, pos.Add(ImVec2{square_sz + x, label_size.y + style.FramePadding.y*2.0})} 71 | ItemSizeRect(&total_bb, style.FramePadding.y) 72 | if !ItemAdd(&total_bb, id, nil, 0) { 73 | return false 74 | } 75 | 76 | var hovered, held bool 77 | var pressed = ButtonBehavior(&total_bb, id, &hovered, &held, 0) 78 | if pressed { 79 | if v != nil { 80 | *v = !(*v) 81 | } 82 | MarkItemEdited(id) 83 | } 84 | 85 | c := ImGuiCol_FrameBg 86 | if held && hovered { 87 | c = ImGuiCol_FrameBgActive 88 | } else if hovered { 89 | c = ImGuiCol_FrameBgHovered 90 | } 91 | 92 | var check_bb = ImRect{pos, pos.Add(ImVec2{square_sz, square_sz})} 93 | RenderNavHighlight(&total_bb, id, 0) 94 | RenderFrame(check_bb.Min, check_bb.Max, GetColorU32FromID(c, 1), true, style.FrameRounding) 95 | var check_col = GetColorU32FromID(ImGuiCol_CheckMark, 1) 96 | var mixed_value = (g.LastItemData.InFlags & ImGuiItemFlags_MixedValue) != 0 97 | if mixed_value { 98 | // Undocumented tristate/mixed/indeterminate checkbox (#2644) 99 | // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox) 100 | var pad = ImVec2{ImMax(1.0, IM_FLOOR(square_sz/3.6)), ImMax(1.0, IM_FLOOR(square_sz/3.6))} 101 | window.DrawList.AddRectFilled(check_bb.Min.Add(pad), check_bb.Max.Sub(pad), check_col, style.FrameRounding, 0) 102 | } else if v != nil && *v { 103 | var pad = ImMax(1.0, IM_FLOOR(square_sz/6.0)) 104 | RenderCheckMark(window.DrawList, check_bb.Min.Add(ImVec2{pad, pad}), check_col, square_sz-pad*2.0) 105 | } 106 | 107 | var label_pos = ImVec2{check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y} 108 | if g.LogEnabled { 109 | s := "[ ]" 110 | if mixed_value { 111 | s = "[~]" 112 | } else if *v { 113 | s = "[x]" 114 | } 115 | LogRenderedText(&label_pos, s) 116 | } 117 | if label_size.x > 0.0 { 118 | RenderText(label_pos, label, true) 119 | } 120 | 121 | return pressed 122 | } 123 | 124 | func RenderCheckMark(draw_list *ImDrawList, pos ImVec2, col ImU32, sz float) { 125 | var thickness = ImMax(sz/5.0, 1.0) 126 | sz -= thickness * 0.5 127 | pos = pos.Add(ImVec2{thickness * 0.25, thickness * 0.25}) 128 | 129 | var third = sz / 3.0 130 | var bx = pos.x + third 131 | var by = pos.y + sz - third*0.5 132 | draw_list.PathLineTo(ImVec2{bx - third, by - third}) 133 | draw_list.PathLineTo(ImVec2{bx, by}) 134 | draw_list.PathLineTo(ImVec2{bx + third*2.0, by - third*2.0}) 135 | draw_list.PathStroke(col, 0, thickness) 136 | } 137 | -------------------------------------------------------------------------------- /widgets.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | const DRAGDROP_HOLD_TO_OPEN_TIMER float = 0.70 // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior. 4 | const DRAG_MOUSE_THRESHOLD_FACTOR float = 0.50 // Multiplier for the default value of io.MouseDragThreshold to make DragFloat/DragInt react faster to mouse drags. 5 | 6 | func IsClippedEx(bb *ImRect, id ImGuiID, clip_even_when_logged bool) bool { 7 | var g = GImGui 8 | var window = g.CurrentWindow 9 | if !bb.Overlaps(window.ClipRect) { 10 | if id == 0 || (id != g.ActiveId && id != g.NavId) { 11 | if clip_even_when_logged || !g.LogEnabled { 12 | return true 13 | } 14 | } 15 | } 16 | return false 17 | } 18 | 19 | // is the last item active? (e.g. button being held, text field being edited. This will continuously return true while holding mouse button on an item. Items that don't interact will always return false) 20 | func IsItemActive() bool { 21 | var g = GImGui 22 | if g.ActiveId != 0 { 23 | return g.ActiveId == g.LastItemData.ID 24 | } 25 | return false 26 | } 27 | 28 | // Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered(). 29 | func ItemHoverable(bb *ImRect, id ImGuiID) bool { 30 | var g = GImGui 31 | if g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap { 32 | return false 33 | } 34 | 35 | var window = g.CurrentWindow 36 | if g.HoveredWindow != window { 37 | return false 38 | } 39 | if g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap { 40 | return false 41 | } 42 | if !IsMouseHoveringRect(bb.Min, bb.Max, true) { 43 | return false 44 | } 45 | if g.NavDisableMouseHover { 46 | return false 47 | } 48 | if !IsWindowContentHoverable(window, ImGuiHoveredFlags_None) { 49 | g.HoveredIdDisabled = true 50 | return false 51 | } 52 | 53 | // We exceptionally allow this function to be called with id==0 to allow using it for easy high-level 54 | // hover test in widgets code. We could also decide to split this function is two. 55 | if id != 0 { 56 | SetHoveredID(id) 57 | } 58 | 59 | // When disabled we'll return false but still set HoveredId 60 | var item_flags = g.CurrentItemFlags 61 | if g.LastItemData.ID == id { 62 | item_flags = g.LastItemData.InFlags 63 | } 64 | if item_flags&ImGuiItemFlags_Disabled != 0 { 65 | // Release active id if turning disabled 66 | if g.ActiveId == id { 67 | ClearActiveID() 68 | } 69 | g.HoveredIdDisabled = true 70 | return false 71 | } 72 | 73 | /*if id != 0 { 74 | // [DEBUG] Item Picker tool! 75 | // We perform the check here because SetHoveredID() is not frequently called (1~ time a frame), making 76 | // the cost of this tool near-zero. We can get slightly better call-stack and support picking non-hovered 77 | // items if we perform the test in ItemAdd(), but that would incur a small runtime cost. 78 | // #define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX in imconfig.h if you want this check to also be performed in ItemAdd(). 79 | if g.DebugItemPickerActive && g.HoveredIdPreviousFrame == id { 80 | GetForegroundDrawList().AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255)) 81 | } 82 | if g.DebugItemPickerBreakId == id { 83 | IM_DEBUG_BREAK() 84 | } 85 | }*/ 86 | 87 | return true 88 | } 89 | 90 | // [Internal] Calculate full item size given user provided 'size' parameter and default width/height. Default width is often == CalcItemWidth(). 91 | // Those two functions CalcItemWidth vs CalcItemSize are awkwardly named because they are not fully symmetrical. 92 | // Note that only CalcItemWidth() is publicly exposed. 93 | // The 4.0f here may be changed to match CalcItemWidth() and/or BeginChild() (right now we have a mismatch which is harmless but undesirable) 94 | func CalcItemSize(size ImVec2, default_w float, default_h float) ImVec2 { 95 | var window = GImGui.CurrentWindow 96 | 97 | var region_max ImVec2 98 | if size.x < 0.0 || size.y < 0.0 { 99 | region_max = GetContentRegionMaxAbs() 100 | } 101 | 102 | if size.x == 0.0 { 103 | size.x = default_w 104 | } else if size.x < 0.0 { 105 | size.x = ImMax(4.0, region_max.x-window.DC.CursorPos.x+size.x) 106 | } 107 | 108 | if size.y == 0.0 { 109 | size.y = default_h 110 | } else if size.y < 0.0 { 111 | size.y = ImMax(4.0, region_max.y-window.DC.CursorPos.y+size.y) 112 | } 113 | 114 | return size 115 | } 116 | 117 | // Declare item bounding box for clipping and interaction. 118 | // Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface 119 | // declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction. 120 | func ItemAdd(bb *ImRect, id ImGuiID, nav_bb_arg *ImRect, extra_flags ImGuiItemFlags) bool { 121 | var g = GImGui 122 | var window = g.CurrentWindow 123 | 124 | // Set item data 125 | // (DisplayRect is left untouched, made valid when ImGuiItemStatusFlags_HasDisplayRect is set) 126 | g.LastItemData.ID = id 127 | g.LastItemData.Rect = *bb 128 | if nav_bb_arg != nil { 129 | g.LastItemData.NavRect = *nav_bb_arg 130 | } else { 131 | g.LastItemData.NavRect = *bb 132 | } 133 | g.LastItemData.InFlags = g.CurrentItemFlags | extra_flags 134 | g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None 135 | 136 | // Directional navigation processing 137 | if id != 0 { 138 | // Runs prior to clipping early-out 139 | // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget 140 | // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests 141 | // unfortunately, but it is still limited to one window. It may not scale very well for windows with ten of 142 | // thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame. 143 | // We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able 144 | // to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick). 145 | // We intentionally don't check if g.NavWindow != nil because g.NavAnyRequest should only be set when it is non nil. 146 | // If we crash on a nil g.NavWindow we need to fix the bug elsewhere. 147 | window.DC.NavLayersActiveMaskNext |= (1 << window.DC.NavLayerCurrent) 148 | if g.NavId == id || g.NavAnyRequest { 149 | if g.NavWindow.RootWindowForNav == window.RootWindowForNav { 150 | if window == g.NavWindow || ((window.Flags|g.NavWindow.Flags)&ImGuiWindowFlags_NavFlattened != 0) { 151 | NavProcessItem() 152 | } 153 | } 154 | } 155 | } 156 | // Clipping test 157 | var is_clipped = IsClippedEx(bb, id, false) 158 | if is_clipped { 159 | return false 160 | } 161 | //if (g.IO.KeyAlt) window.DrawList.AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] 162 | 163 | // [WIP] Tab stop handling (previously was using internal FocusableItemRegister() api) 164 | // FIXME-NAV: We would now want to move this before the clipping test, but this would require being able to scroll and currently this would mean an extra frame. (#4079, #343) 165 | if extra_flags&ImGuiItemFlags_Inputable != 0 { 166 | ItemInputable(window, id) 167 | } 168 | 169 | // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) 170 | if IsMouseHoveringRect(bb.Min, bb.Max, true) { 171 | g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect 172 | } 173 | return true 174 | } 175 | 176 | // Advance cursor given item size for layout. 177 | // Register minimum needed size so it can extend the bounding box used for auto-fit calculation. 178 | // See comments in ItemAdd() about how/why the size provided to ItemSize() vs ItemAdd() may often different. 179 | func ItemSizeVec(size *ImVec2, text_baseline_y float) { 180 | var g = GImGui 181 | var window = g.CurrentWindow 182 | if window.SkipItems { 183 | return 184 | } 185 | 186 | // We increase the height in this function to accommodate for baseline offset. 187 | // In theory we should be offsetting the starting position (window.DC.CursorPos), that will be the topic of a larger refactor, 188 | // but since ItemSize() is not yet an API that moves the cursor (to handle e.g. wrapping) enlarging the height has the same effect. 189 | var offset_to_match_baseline_y float 190 | if text_baseline_y >= 0 { 191 | offset_to_match_baseline_y = ImMax(0.0, window.DC.CurrLineTextBaseOffset-text_baseline_y) 192 | } 193 | var line_height = ImMax(window.DC.CurrLineSize.y, size.y+offset_to_match_baseline_y) 194 | 195 | // Always align ourselves on pixel boundaries 196 | //if (g.IO.KeyAlt) window.DrawList.AddRect(window.DC.CursorPos, window.DC.CursorPos + ImVec2(size.x, line_height), IM_COL32(255,0,0,200)); // [DEBUG] 197 | window.DC.CursorPosPrevLine.x = window.DC.CursorPos.x + size.x 198 | window.DC.CursorPosPrevLine.y = window.DC.CursorPos.y 199 | window.DC.CursorPos.x = IM_FLOOR(window.Pos.x + window.DC.Indent.x + window.DC.ColumnsOffset.x) // Next line 200 | window.DC.CursorPos.y = IM_FLOOR(window.DC.CursorPos.y + line_height + g.Style.ItemSpacing.y) // Next line 201 | window.DC.CursorMaxPos.x = ImMax(window.DC.CursorMaxPos.x, window.DC.CursorPosPrevLine.x) 202 | window.DC.CursorMaxPos.y = ImMax(window.DC.CursorMaxPos.y, window.DC.CursorPos.y-g.Style.ItemSpacing.y) 203 | //if (g.IO.KeyAlt) window.DrawList.AddCircle(window.DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG] 204 | 205 | window.DC.PrevLineSize.y = line_height 206 | window.DC.CurrLineSize.y = 0.0 207 | window.DC.PrevLineTextBaseOffset = ImMax(window.DC.CurrLineTextBaseOffset, text_baseline_y) 208 | window.DC.CurrLineTextBaseOffset = 0.0 209 | 210 | // Horizontal layout mode 211 | if window.DC.LayoutType == ImGuiLayoutType_Horizontal { 212 | SameLine(0, -1) 213 | } 214 | } 215 | 216 | // Gets back to previous line and continue with horizontal layout 217 | // 218 | // offset_from_start_x == 0 : follow right after previous item 219 | // offset_from_start_x != 0 : align to specified x position (relative to window/group left) 220 | // spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0 221 | // spacing_w >= 0 : enforce spacing amount 222 | func SameLine(offset_from_start_x, spacing_w float) { 223 | var window = GetCurrentWindow() 224 | if window.SkipItems { 225 | return 226 | } 227 | 228 | var g = GImGui 229 | if offset_from_start_x != 0.0 { 230 | if spacing_w < 0.0 { 231 | spacing_w = 0.0 232 | } 233 | window.DC.CursorPos.x = window.Pos.x - window.Scroll.x + offset_from_start_x + spacing_w + window.DC.GroupOffset.x + window.DC.ColumnsOffset.x 234 | window.DC.CursorPos.y = window.DC.CursorPosPrevLine.y 235 | } else { 236 | if spacing_w < 0.0 { 237 | spacing_w = g.Style.ItemSpacing.x 238 | } 239 | window.DC.CursorPos.x = window.DC.CursorPosPrevLine.x + spacing_w 240 | window.DC.CursorPos.y = window.DC.CursorPosPrevLine.y 241 | } 242 | window.DC.CurrLineSize = window.DC.PrevLineSize 243 | window.DC.CurrLineTextBaseOffset = window.DC.PrevLineTextBaseOffset 244 | } 245 | 246 | func ItemSizeRect(bb *ImRect, text_baseline_y float) { 247 | size := bb.GetSize() 248 | ItemSizeVec(&size, text_baseline_y) 249 | } 250 | -------------------------------------------------------------------------------- /widgets.helpers.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/Splizard/imgui/golang" 7 | ) 8 | 9 | // Remotely activate a button, checkbox, tree node etc. given its unique ID. activation is queued and processed on the next frame when the item is encountered again. 10 | func ActivateItem(id ImGuiID) { 11 | var g = GImGui 12 | g.NavNextActivateId = id 13 | } 14 | 15 | // Called by ItemAdd() 16 | // Process TAB/Shift+TAB. Be mindful that this function may _clear_ the ActiveID when tabbing out. 17 | // [WIP] This will eventually be refactored and moved into NavProcessItem() 18 | func ItemInputable(window *ImGuiWindow, id ImGuiID) { 19 | var g = GImGui 20 | IM_ASSERT(id != 0 && id == g.LastItemData.ID) 21 | 22 | // Increment counters 23 | // FIXME: ImGuiItemFlags_Disabled should disable more. 24 | var is_tab_stop = (g.LastItemData.InFlags & (ImGuiItemFlags_NoTabStop | ImGuiItemFlags_Disabled)) == 0 25 | window.DC.FocusCounterRegular++ 26 | if is_tab_stop { 27 | window.DC.FocusCounterTabStop++ 28 | if g.NavId == id { 29 | g.NavIdTabCounter = window.DC.FocusCounterTabStop 30 | } 31 | } 32 | 33 | // Process TAB/Shift-TAB to tab *OUT* of the currently focused item. 34 | // (Note that we can always TAB out of a widget that doesn't allow tabbing in) 35 | if g.ActiveId == id && g.TabFocusPressed && !IsActiveIdUsingKey(ImGuiKey_Tab) && g.TabFocusRequestNextWindow == nil { 36 | g.TabFocusRequestNextWindow = window 37 | 38 | var add int 39 | if g.IO.KeyShift { 40 | if is_tab_stop { 41 | add = -1 42 | } 43 | } else { 44 | add = +1 45 | } 46 | 47 | g.TabFocusRequestNextCounterTabStop = window.DC.FocusCounterTabStop + add // Modulo on index will be applied at the end of frame once we've got the total counter of items. 48 | } 49 | 50 | // Handle focus requests 51 | if g.TabFocusRequestCurrWindow == window { 52 | if window.DC.FocusCounterRegular == g.TabFocusRequestCurrCounterRegular { 53 | g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_FocusedByCode 54 | return 55 | } 56 | if is_tab_stop && window.DC.FocusCounterTabStop == g.TabFocusRequestCurrCounterTabStop { 57 | g.NavJustTabbedId = id 58 | g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_FocusedByTabbing 59 | return 60 | } 61 | 62 | // If another item is about to be focused, we clear our own active id 63 | if g.ActiveId == id { 64 | ClearActiveID() 65 | } 66 | } 67 | } 68 | 69 | func CalcWrapWidthForPos(pos *ImVec2, wrap_pos_x float) float { 70 | if wrap_pos_x < 0.0 { 71 | return 0.0 72 | } 73 | 74 | var g = GImGui 75 | var window = g.CurrentWindow 76 | if wrap_pos_x == 0.0 { 77 | // We could decide to setup a default wrapping max point for auto-resizing windows, 78 | // or have auto-wrap (with unspecified wrapping pos) behave as a ContentSize extending function? 79 | //if (window.Hidden && (window.Flags & ImGuiWindowFlags_AlwaysAutoResize)) 80 | // wrap_pos_x = ImMax(window.WorkRect.Min.x + g.FontSize * 10.0f, window.WorkRect.Max.x); 81 | //else 82 | wrap_pos_x = window.WorkRect.Max.x 83 | } else if wrap_pos_x > 0.0 { 84 | wrap_pos_x += window.Pos.x - window.Scroll.x // wrap_pos_x is provided is window local space 85 | } 86 | 87 | return ImMax(wrap_pos_x-pos.x, 1.0) 88 | } 89 | 90 | // Was the last item selection toggled? (after Selectable(), TreeNode() etc. We only returns toggle _event_ in order to handle clipping correctly) 91 | func IsItemToggledSelection() bool { 92 | var g = GImGui 93 | return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledSelection) != 0 94 | } 95 | 96 | // Shrink excess width from a set of item, by removing width from the larger items first. 97 | // Set items Width to -1.0f to disable shrinking this item. 98 | func ShrinkWidths(items []ImGuiShrinkWidthItem, count int, width_excess float) { 99 | if count == 1 { 100 | if items[0].Width >= 0.0 { 101 | items[0].Width = ImMax(items[0].Width-width_excess, 1.0) 102 | } 103 | return 104 | } 105 | sort.Slice(items, func(i, j golang.Int) bool { 106 | var a = items[i] 107 | var b = items[j] 108 | if d := (int)(b.Width - a.Width); d != 0 { 109 | return true 110 | } 111 | return (b.Index - a.Index) != 0 112 | }) 113 | var count_same_width int = 1 114 | for width_excess > 0.0 && count_same_width < count { 115 | for count_same_width < count && items[0].Width <= items[count_same_width].Width { 116 | count_same_width++ 117 | } 118 | var max_width_to_remove_per_item = (items[0].Width - 1.0) 119 | if count_same_width < count && items[count_same_width].Width >= 0.0 { 120 | max_width_to_remove_per_item = (items[0].Width - items[count_same_width].Width) 121 | } 122 | if max_width_to_remove_per_item <= 0.0 { 123 | break 124 | } 125 | var width_to_remove_per_item = ImMin(width_excess/float(count_same_width), max_width_to_remove_per_item) 126 | for item_n := int(0); item_n < count_same_width; item_n++ { 127 | items[item_n].Width -= width_to_remove_per_item 128 | } 129 | width_excess -= width_to_remove_per_item * float(count_same_width) 130 | } 131 | 132 | // Round width and redistribute remainder left-to-right (could make it an option of the function?) 133 | // Ensure that e.g. the right-most tab of a shrunk tab-bar always reaches exactly at the same distance from the right-most edge of the tab bar separator. 134 | width_excess = 0.0 135 | for n := int(0); n < count; n++ { 136 | var width_rounded = ImFloor(items[n].Width) 137 | width_excess += items[n].Width - width_rounded 138 | items[n].Width = width_rounded 139 | } 140 | if width_excess > 0.0 { 141 | for n := int(0); n < count; n++ { 142 | if items[n].Index < (int)(width_excess+0.01) { 143 | items[n].Width += 1.0 144 | } 145 | } 146 | } 147 | } 148 | 149 | // Inputs 150 | // FIXME: Eventually we should aim to move e.g. IsActiveIdUsingKey() into IsKeyXXX functions. 151 | func SetItemUsingMouseWheel() { 152 | var g = GImGui 153 | var id = g.LastItemData.ID 154 | if g.HoveredId == id { 155 | g.HoveredIdUsingMouseWheel = true 156 | } 157 | if g.ActiveId == id { 158 | g.ActiveIdUsingMouseWheel = true 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /widgets.image.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | func Image(user_texture_id ImTextureID, size ImVec2, uv0 ImVec2, uv1 ImVec2, tint_col ImVec4, border_col ImVec4) { 4 | var window = GetCurrentWindow() 5 | if window.SkipItems { 6 | return 7 | } 8 | 9 | var bb = ImRect{window.DC.CursorPos, window.DC.CursorPos.Add(size)} 10 | if border_col.w > 0.0 { 11 | bb.Max = bb.Max.Add(ImVec2{2, 2}) 12 | } 13 | ItemSizeRect(&bb, 0) 14 | if !ItemAdd(&bb, 0, nil, 0) { 15 | return 16 | } 17 | 18 | if border_col.w > 0.0 { 19 | window.DrawList.AddRect(bb.Min, bb.Max, GetColorU32FromVec(border_col), 0.0, 0, 1) 20 | window.DrawList.AddImage(user_texture_id, bb.Min.Add(ImVec2{1, 1}), bb.Max.Sub(ImVec2{1, 1}), &uv0, &uv1, GetColorU32FromVec(tint_col)) 21 | } else { 22 | window.DrawList.AddImage(user_texture_id, bb.Min, bb.Max, &uv0, &uv1, GetColorU32FromVec(tint_col)) 23 | } 24 | } 25 | 26 | // ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390) 27 | // We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API. 28 | func ImageButtonEx(id ImGuiID, texture_id ImTextureID, size *ImVec2, uv0 *ImVec2, uv1 *ImVec2, padding *ImVec2, bg_col *ImVec4, tint_col *ImVec4) bool { 29 | var g = GImGui 30 | var window = GetCurrentWindow() 31 | if window.SkipItems { 32 | return false 33 | } 34 | 35 | var bb = ImRect{window.DC.CursorPos, window.DC.CursorPos.Add(*size).Add(padding.Scale(2))} 36 | ItemSizeRect(&bb, 0) 37 | if !ItemAdd(&bb, id, nil, 0) { 38 | return false 39 | } 40 | 41 | var hovered, held bool 42 | var pressed = ButtonBehavior(&bb, id, &hovered, &held, 0) 43 | 44 | // Render 45 | var c = ImGuiCol_Button 46 | if held && hovered { 47 | c = ImGuiCol_ButtonActive 48 | } else if hovered { 49 | c = ImGuiCol_ButtonHovered 50 | } 51 | var col = GetColorU32FromID(c, 1) 52 | RenderNavHighlight(&bb, id, 0) 53 | RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)(ImMin(padding.x, padding.y)), 0.0, g.Style.FrameRounding)) 54 | if bg_col.w > 0.0 { 55 | window.DrawList.AddRectFilled(bb.Min.Add(*padding), bb.Max.Sub(*padding), GetColorU32FromVec(*bg_col), 0, 0) 56 | } 57 | window.DrawList.AddImage(texture_id, bb.Min.Add(*padding), bb.Max.Sub(*padding), uv0, uv1, GetColorU32FromVec(*tint_col)) 58 | 59 | return pressed 60 | } 61 | 62 | // frame_padding < 0: uses FramePadding from style (default) 63 | // frame_padding = 0: no framing 64 | // frame_padding > 0: set framing size 65 | func ImageButton(user_texture_id ImTextureID, size ImVec2, uv0 ImVec2, uv1 ImVec2, frame_padding int /*/*= /*/, bg_col ImVec4, tint_col ImVec4) bool { 66 | var g = GImGui 67 | var window = g.CurrentWindow 68 | if window.SkipItems { 69 | return false 70 | } 71 | 72 | // Default to using texture ID as ID. User can still push string/integer prefixes. 73 | PushID(int(user_texture_id)) 74 | var id = window.GetIDs("#image") 75 | PopID() 76 | 77 | var padding = g.Style.FramePadding 78 | if frame_padding >= 0 { 79 | padding = ImVec2{(float)(frame_padding), (float)(frame_padding)} 80 | } 81 | return ImageButtonEx(id, user_texture_id, &size, &uv0, &uv1, &padding, &bg_col, &tint_col) 82 | } 83 | 84 | // Image primitives 85 | // - Read FAQ to understand what ImTextureID is. 86 | // - "p_min" and "p_max" represent the upper-left and lower-right corners of the rectangle. 87 | // - "uv_min" and "uv_max" represent the normalized texture coordinates to use for those corners. Using (0,0)->(1,1) texture coordinates will generally display the entire texture. 88 | -------------------------------------------------------------------------------- /widgets.listbox.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // Widgets: List Boxes 4 | // - This is essentially a thin wrapper to using BeginChild/EndChild with some stylistic changes. 5 | // - The BeginListBox()/EndListBox() api allows you to manage your contents and selection state however you want it, by creating e.g. Selectable() or any items. 6 | // - The simplified/old ListBox() api are helpers over BeginListBox()/EndListBox() which are kept available for convenience purpose. This is analoguous to how Combos are created. 7 | // - Choose frame width: size.x > 0.0: custom / size.x < 0.0 or -FLT_MIN: right-align / size.x = 0.0 (default): use current ItemWidth 8 | // - Choose frame height: size.y > 0.0: custom / size.y < 0.0 or -FLT_MIN: bottom-align / size.y = 0.0 (default): arbitrary default height which can fit ~7 items 9 | 10 | // open a framed scrolling region 11 | // Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty" 12 | // Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.25 * item_height). 13 | func BeginListBox(label string, size_arg ImVec2) bool { 14 | var g = GImGui 15 | var window = GetCurrentWindow() 16 | if window.SkipItems { 17 | return false 18 | } 19 | 20 | var style = g.Style 21 | var id = GetIDs(label) 22 | var label_size = CalcTextSize(label, true, -1) 23 | 24 | // Size default to hold ~7.25 items. 25 | // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar. 26 | var size_not_floored = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing()*7.25+style.FramePadding.y*2.0) 27 | var size = ImFloorVec(&size_not_floored) 28 | var frame_size = ImVec2{size.x, ImMax(size.y, label_size.y)} 29 | var frame_bb = ImRect{window.DC.CursorPos, window.DC.CursorPos.Add(frame_size)} 30 | 31 | var padding float = 0 32 | if label_size.x > 0.0 { 33 | padding = style.ItemInnerSpacing.x + label_size.x 34 | } 35 | 36 | var bb = ImRect{frame_bb.Min, frame_bb.Max.Add(ImVec2{padding, 0.0})} 37 | g.NextItemData.ClearFlags() 38 | 39 | if !IsRectVisibleMinMax(bb.Min, bb.Max) { 40 | v := bb.GetSize() 41 | ItemSizeVec(&v, style.FramePadding.y) 42 | ItemAdd(&bb, 0, &frame_bb, 0) 43 | return false 44 | } 45 | 46 | // FIXME-OPT: We could omit the BeginGroup() if label_size.x but would need to omit the EndGroup() as well. 47 | BeginGroup() 48 | if label_size.x > 0.0 { 49 | var label_pos = ImVec2{frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y} 50 | RenderText(label_pos, label, true) 51 | b := label_pos.Add(label_size) 52 | window.DC.CursorMaxPos = ImMaxVec2(&window.DC.CursorMaxPos, &b) 53 | } 54 | 55 | BeginChildFrame(id, frame_bb.GetSize(), 0) 56 | return true 57 | } 58 | 59 | // only call EndListBox() if BeginListBox() returned true! 60 | func EndListBox() { 61 | var g = GImGui 62 | var window = g.CurrentWindow 63 | IM_ASSERT_USER_ERROR((window.Flags&ImGuiWindowFlags_ChildWindow) != 0, "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?") 64 | 65 | EndChildFrame() 66 | EndGroup() // This is only required to be able to do IsItemXXX query on the whole ListBox including label 67 | } 68 | 69 | func ListBox(label string, current_item *int, items []string, items_count int, height_in_items int /*= -1*/) bool { 70 | var value_changed = ListBoxFunc(label, current_item, func(data any, idx int, out_text *string) bool { 71 | var items = data.([]string) 72 | if out_text != nil { 73 | *out_text = items[idx] 74 | } 75 | return true 76 | }, items, items_count, height_in_items) 77 | return value_changed 78 | } 79 | 80 | // This is merely a helper around BeginListBox(), EndListBox(). 81 | // Considering using those directly to submit custom data or store selection differently. 82 | func ListBoxFunc(label string, current_item *int, items_getter func(data any, idx int, out_text *string) bool, data any, items_count int, height_in_items int /*= -1*/) bool { 83 | var g = GImGui 84 | 85 | // Calculate size from "height_in_items" 86 | if height_in_items < 0 { 87 | height_in_items = ImMinInt(items_count, 7) 88 | } 89 | var height_in_items_f = float(height_in_items) + 0.25 90 | var size = ImVec2{0.0, ImFloor(GetTextLineHeightWithSpacing()*height_in_items_f + g.Style.FramePadding.y*2.0)} 91 | 92 | if !BeginListBox(label, size) { 93 | return false 94 | } 95 | 96 | // Assume all items have even height (= 1 line of text). If you need items of different height, 97 | // you can create a custom version of ListBox() in your code without using the clipper. 98 | var value_changed = false 99 | var clipper ImGuiListClipper 100 | clipper.Begin(items_count, GetTextLineHeightWithSpacing()) // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. 101 | for clipper.Step() { 102 | for i := clipper.DisplayStart; i < clipper.DisplayEnd; i++ { 103 | var item_text string 104 | if !items_getter(data, i, &item_text) { 105 | item_text = "*Unknown item*" 106 | } 107 | 108 | PushID(i) 109 | var item_selected = (i == *current_item) 110 | if (Selectable(item_text, item_selected, 0, ImVec2{})) { 111 | *current_item = i 112 | value_changed = true 113 | } 114 | if item_selected { 115 | SetItemDefaultFocus() 116 | } 117 | PopID() 118 | } 119 | } 120 | EndListBox() 121 | 122 | if value_changed { 123 | MarkItemEdited(g.LastItemData.ID) 124 | } 125 | 126 | return value_changed 127 | } 128 | -------------------------------------------------------------------------------- /widgets.plotting.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | //------------------------------------------------------------------------- 4 | // [SECTION] Widgets: PlotLines, PlotHistogram 5 | //------------------------------------------------------------------------- 6 | // - PlotEx() [Internal] 7 | // - PlotLines() 8 | // - PlotHistogram() 9 | //------------------------------------------------------------------------- 10 | // Plot/Graph widgets are not very good. 11 | // Consider writing your own, or using a third-party one, see: 12 | // - ImPlot https://github.com/epezent/implot 13 | // - others https://github.com/ocornut/imgui/wiki/Useful-Extensions 14 | //------------------------------------------------------------------------- 15 | 16 | type ImGuiPlotArrayGetterData struct { 17 | Values []float 18 | Stride int 19 | } 20 | 21 | func Plot_ArrayGetter(data any, idx int) float { 22 | var plot_data = (data).(*ImGuiPlotArrayGetterData) 23 | return plot_data.Values[idx*plot_data.Stride] 24 | } 25 | 26 | // Widgets: Data Plotting 27 | // - Consider using ImPlot (https://github.com/epezent/implot) which is much better! 28 | func PlotLines(label string, values []float, values_count int, values_offset int /*= 0*/, overlay_text string /*= L*/, scale_min float /*= X*/, scale_max float /*= X*/, graph_size ImVec2 /*= 0*/, stride int /*= sizeof(float)*/) { 29 | var data = ImGuiPlotArrayGetterData{values, stride} 30 | PlotEx(ImGuiPlotType_Lines, label, Plot_ArrayGetter, &data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size) 31 | } 32 | 33 | func PlotLinesFunc(label string, values_getter func(data any, idx int) float, data any, values_count int, values_offset int /*= 0*/, overlay_text string /*= L*/, scale_min float /*= X*/, scale_max float /*= X*/, graph_size ImVec2 /*= 0*/) { 34 | PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size) 35 | } 36 | 37 | func PlotHistogram(label string, values []float, values_count int, values_offset int /*= 0*/, overlay_text string /*= L*/, scale_min float /*= X*/, scale_max float /*= X*/, graph_size ImVec2 /*= 0*/, stride int /* = sizeof(float)*/) { 38 | var data = ImGuiPlotArrayGetterData{values, stride} 39 | PlotEx(ImGuiPlotType_Histogram, label, Plot_ArrayGetter, &data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size) 40 | } 41 | 42 | func PlotHistogramFunc(label string, values_getter func(data any, idx int) float, data any, values_count int, values_offset int /*= 0*/, overlay_text string /*= L*/, scale_min float /*= X*/, scale_max float /*= X*/, graph_size ImVec2 /*= 0*/) { 43 | PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size) 44 | } 45 | 46 | func PlotEx(plot_type ImGuiPlotType, label string, values_getter func(data any, idx int) float, data any, values_count int, values_offset int, overlay_text string, scale_min float, scale_max float, frame_size ImVec2) int { 47 | var g = GImGui 48 | var window = GetCurrentWindow() 49 | if window.SkipItems { 50 | return -1 51 | } 52 | 53 | var style = g.Style 54 | var id = window.GetIDs(label) 55 | 56 | var label_size = CalcTextSize(label, true, -1) 57 | if frame_size.x == 0.0 { 58 | frame_size.x = CalcItemWidth() 59 | } 60 | if frame_size.y == 0.0 { 61 | frame_size.y = label_size.y + (style.FramePadding.y * 2) 62 | } 63 | 64 | var padding float = 0 65 | if label_size.x > 0 { 66 | padding = style.ItemInnerSpacing.x + label_size.x 67 | } 68 | 69 | var frame_bb = ImRect{window.DC.CursorPos, window.DC.CursorPos.Add(frame_size)} 70 | var inner_bb = ImRect{frame_bb.Min.Add(style.FramePadding), frame_bb.Max.Sub(style.FramePadding)} 71 | var total_bb = ImRect{frame_bb.Min, frame_bb.Max.Add(ImVec2{padding, 0})} 72 | ItemSizeRect(&total_bb, style.FramePadding.y) 73 | if !ItemAdd(&total_bb, 0, &frame_bb, 0) { 74 | return -1 75 | } 76 | var hovered = ItemHoverable(&frame_bb, id) 77 | 78 | // Determine scale from values if not specified 79 | if scale_min == FLT_MAX || scale_max == FLT_MAX { 80 | var v_min float = FLT_MAX 81 | var v_max float = -FLT_MAX 82 | for i := int(0); i < values_count; i++ { 83 | var v = values_getter(data, i) 84 | if v != v { // Ignore NaN values 85 | continue 86 | } 87 | v_min = ImMin(v_min, v) 88 | v_max = ImMax(v_max, v) 89 | } 90 | if scale_min == FLT_MAX { 91 | scale_min = v_min 92 | } 93 | if scale_max == FLT_MAX { 94 | scale_max = v_max 95 | } 96 | } 97 | 98 | RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32FromID(ImGuiCol_FrameBg, 1), true, style.FrameRounding) 99 | 100 | var values_count_min int = 1 101 | if plot_type == ImGuiPlotType_Lines { 102 | values_count_min = 2 103 | } 104 | var idx_hovered int = -1 105 | if values_count >= values_count_min { 106 | var b int = 0 107 | if plot_type == ImGuiPlotType_Lines { 108 | b = -1 109 | } 110 | var res_w = ImMinInt((int)(frame_size.x), values_count) + b 111 | var item_count = values_count + b 112 | 113 | // Tooltip on hover 114 | if hovered && inner_bb.ContainsVec(g.IO.MousePos) { 115 | var t = ImClamp((g.IO.MousePos.x-inner_bb.Min.x)/(inner_bb.Max.x-inner_bb.Min.x), 0.0, 0.9999) 116 | var v_idx = (int)(t * float(item_count)) 117 | IM_ASSERT(v_idx >= 0 && v_idx < values_count) 118 | 119 | var v0 = values_getter(data, (v_idx+values_offset)%values_count) 120 | var v1 = values_getter(data, (v_idx+1+values_offset)%values_count) 121 | if plot_type == ImGuiPlotType_Lines { 122 | SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1) 123 | } else if plot_type == ImGuiPlotType_Histogram { 124 | SetTooltip("%d: %8.4g", v_idx, v0) 125 | } 126 | idx_hovered = v_idx 127 | } 128 | 129 | var t_step = 1.0 / (float)(res_w) 130 | var inv_scale = (1.0 / (scale_max - scale_min)) 131 | if scale_min == scale_max { 132 | inv_scale = 0 133 | } 134 | 135 | var v0 = values_getter(data, (0+values_offset)%values_count) 136 | var t0 float = 0.0 137 | var tp0 = ImVec2{t0, 1.0 - ImSaturate((v0-scale_min)*inv_scale)} // Point in the normalized space of our target rectangle 138 | var histogram_zero_line_t float // Where does the zero line stands 139 | if scale_min*scale_max < 0.0 { 140 | histogram_zero_line_t = (1 + scale_min*inv_scale) 141 | } else { 142 | if scale_min < 0 { 143 | histogram_zero_line_t = 0 144 | } else { 145 | histogram_zero_line_t = 1 146 | } 147 | } 148 | 149 | c := ImGuiCol_PlotHistogram 150 | if plot_type == ImGuiPlotType_Lines { 151 | c = ImGuiCol_PlotLines 152 | } 153 | 154 | var col_base = GetColorU32FromID(c, 1) 155 | 156 | c = ImGuiCol_PlotHistogramHovered 157 | if plot_type == ImGuiPlotType_Lines { 158 | c = ImGuiCol_PlotLinesHovered 159 | } 160 | 161 | var col_hovered = GetColorU32FromID(c, 1) 162 | 163 | for n := int(0); n < res_w; n++ { 164 | var t1 = t0 + t_step 165 | var v1_idx = (int)(t0*float(item_count) + 0.5) 166 | IM_ASSERT(v1_idx >= 0 && v1_idx < values_count) 167 | var v1 = values_getter(data, (v1_idx+values_offset+1)%values_count) 168 | var tp1 = ImVec2{t1, 1.0 - ImSaturate((v1-scale_min)*inv_scale)} 169 | 170 | // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU. 171 | var pos0 = ImLerpVec2WithVec2(&inner_bb.Min, &inner_bb.Max, tp0) 172 | 173 | var t = ImVec2{tp1.x, histogram_zero_line_t} 174 | if plot_type == ImGuiPlotType_Lines { 175 | t = tp1 176 | } 177 | 178 | var pos1 = ImLerpVec2WithVec2(&inner_bb.Min, &inner_bb.Max, t) 179 | if plot_type == ImGuiPlotType_Lines { 180 | c := col_base 181 | if idx_hovered == v1_idx { 182 | c = col_hovered 183 | } 184 | window.DrawList.AddLine(&pos0, &pos1, c, 1) 185 | } else if plot_type == ImGuiPlotType_Histogram { 186 | if pos1.x >= pos0.x+2.0 { 187 | pos1.x -= 1.0 188 | } 189 | c := col_base 190 | if idx_hovered == v1_idx { 191 | c = col_hovered 192 | } 193 | window.DrawList.AddRectFilled(pos0, pos1, c, 0, 0) 194 | } 195 | 196 | t0 = t1 197 | tp0 = tp1 198 | } 199 | } 200 | 201 | // Text overlay 202 | if overlay_text != "" { 203 | RenderTextClipped(&ImVec2{frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y}, &frame_bb.Max, overlay_text, nil, &ImVec2{0.5, 0.0}, nil) 204 | } 205 | if label_size.x > 0.0 { 206 | RenderText(ImVec2{frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y}, label, true) 207 | } 208 | 209 | // Return hovered index or -1 if none are hovered. 210 | // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx(). 211 | return idx_hovered 212 | } 213 | -------------------------------------------------------------------------------- /widgets.progress.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | import "fmt" 4 | 5 | // size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size 6 | func ProgressBar(fraction float, size_arg ImVec2 /*= ImVec2(-FLT_MIN, 0)*/, overlay string) { 7 | var window = GetCurrentWindow() 8 | if window.SkipItems { 9 | return 10 | } 11 | 12 | var g = GImGui 13 | var style = g.Style 14 | 15 | var pos = window.DC.CursorPos 16 | var size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize+style.FramePadding.y*2.0) 17 | var bb = ImRect{pos, pos.Add(size)} 18 | ItemSizeVec(&size, style.FramePadding.y) 19 | if !ItemAdd(&bb, 0, nil, 0) { 20 | return 21 | } 22 | 23 | // Render 24 | fraction = ImSaturate(fraction) 25 | RenderFrame(bb.Min, bb.Max, GetColorU32FromID(ImGuiCol_FrameBg, 1), true, style.FrameRounding) 26 | bb.ExpandVec(ImVec2{-style.FrameBorderSize, -style.FrameBorderSize}) 27 | var fill_br = ImVec2{ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y} 28 | RenderRectFilledRangeH(window.DrawList, &bb, GetColorU32FromID(ImGuiCol_PlotHistogram, 1), 0.0, fraction, style.FrameRounding) 29 | 30 | // Default displaying the fraction as percentage string, but user can override it 31 | if overlay == "" { 32 | overlay = fmt.Sprintf("%.0f%%", fraction*100+0.01) 33 | } 34 | 35 | var overlay_size = CalcTextSize(overlay, true, -1) 36 | if overlay_size.x > 0.0 { 37 | RenderTextClipped(&ImVec2{ImClamp(fill_br.x+style.ItemSpacing.x, bb.Min.x, bb.Max.x-overlay_size.x-style.ItemInnerSpacing.x), bb.Min.y}, &bb.Max, overlay, &overlay_size, &ImVec2{0.0, 0.5}, &bb) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /widgets.query.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // Item/Widgets Utilities and Query Functions 4 | // - Most of the functions are referring to the previous Item that has been submitted. 5 | // - See Demo Window under "Widgets->Querying Status" for an interactive visualization of most of those functions. 6 | 7 | // IsItemHovered is the last item hovered? (and usable, aka not blocked by a popup, etc.). See ImGuiHoveredFlags for more options. 8 | // This is roughly matching the behavior of internal-facing ItemHoverable() 9 | // - we allow hovering to be true when ActiveId==window.MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered() 10 | // - this should work even for non-interactive items that have no ID, so we cannot use LastItemId 11 | func IsItemHovered(flags ImGuiHoveredFlags) bool { 12 | var g = GImGui 13 | var window = g.CurrentWindow 14 | if g.NavDisableMouseHover && !g.NavDisableHighlight { 15 | if (g.LastItemData.InFlags&ImGuiItemFlags_Disabled != 0) && (flags&ImGuiHoveredFlags_AllowWhenDisabled == 0) { 16 | return false 17 | } 18 | return IsItemFocused() 19 | } 20 | 21 | // Test for bounding box overlap, as updated as ItemAdd() 22 | var status_flags = g.LastItemData.StatusFlags 23 | if status_flags&ImGuiItemStatusFlags_HoveredRect == 0 { 24 | return false 25 | } 26 | IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0) // Flags not supported by this function 27 | 28 | // Test if we are hovering the right window (our window could be behind another window) 29 | // [2021/03/02] Reworked / reverted the revert, finally. Note we want e.g. BeginGroup/ItemAdd/EndGroup to work as well. (#3851) 30 | // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable 31 | // to use IsItemHovered() after EndChild() itself. Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was 32 | // the test that has been running for a long while. 33 | if g.HoveredWindow != window && (status_flags&ImGuiItemStatusFlags_HoveredWindow) == 0 { 34 | if (flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0 { 35 | return false 36 | } 37 | } 38 | 39 | // Test if another item is active (e.g. being dragged) 40 | if (flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0 { 41 | if g.ActiveId != 0 && g.ActiveId != g.LastItemData.ID && !g.ActiveIdAllowOverlap && g.ActiveId != window.MoveId { 42 | return false 43 | } 44 | } 45 | 46 | // Test if interactions on this window are blocked by an active popup or modal. 47 | // The ImGuiHoveredFlags_AllowWhenBlockedByPopup flag will be tested here. 48 | if !IsWindowContentHoverable(window, flags) { 49 | return false 50 | } 51 | 52 | // Test if the item is disabled 53 | if (g.LastItemData.InFlags&ImGuiItemFlags_Disabled != 0) && (flags&ImGuiHoveredFlags_AllowWhenDisabled == 0) { 54 | return false 55 | } 56 | 57 | // Special handling for calling after Begin() which represent the title bar or tab. 58 | // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case. 59 | return g.LastItemData.ID == window.MoveId && window.WriteAccessed 60 | } 61 | 62 | // IsItemFocused is the last item focused for keyboard/gamepad navigation? 63 | // == GetItemID() == GetFocusID() 64 | func IsItemFocused() bool { 65 | var g = GImGui 66 | return !(g.NavId != g.LastItemData.ID || g.NavId == 0) 67 | } 68 | 69 | // IsItemClicked is the last item hovered and mouse clicked on? (**) == IsMouseClicked(mouse_button) && IsItemHovered()Important. (**) this it NOT equivalent to the behavior of e.g. Button(). Read comments in function definition. 70 | // Important: this can be useful but it is NOT equivalent to the behavior of e.g.Button()! 71 | // Most widgets have specific reactions based on mouse-up/down state, mouse position etc. 72 | func IsItemClicked(mouse_button ImGuiMouseButton) bool { 73 | return IsMouseClicked(mouse_button, false) && IsItemHovered(ImGuiHoveredFlags_None) 74 | } 75 | 76 | // IsItemVisible is the last item visible? (items may be out of sight because of clipping/scrolling) 77 | func IsItemVisible() bool { 78 | var g = GImGui 79 | return g.CurrentWindow.ClipRect.Overlaps(g.LastItemData.Rect) 80 | } 81 | 82 | // IsItemEdited did the last item modify its underlying value this frame? or was pressed? This is generally the same as the "bool" return value of many widgets. 83 | func IsItemEdited() bool { 84 | var g = GImGui 85 | return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Edited) != 0 86 | } 87 | 88 | // IsItemActivated was the last item just made active (item was previously inactive). 89 | func IsItemActivated() bool { 90 | var g = GImGui 91 | if g.ActiveId != 0 { 92 | return g.ActiveId == g.LastItemData.ID && g.ActiveIdPreviousFrame != g.LastItemData.ID 93 | } 94 | return false 95 | } 96 | 97 | // IsItemDeactivated was the last item just made inactive (item was previously active). Useful for Undo/Redo patterns with widgets that requires continuous editing. 98 | func IsItemDeactivated() bool { 99 | var g = GImGui 100 | if g.LastItemData.StatusFlags&ImGuiItemStatusFlags_HasDeactivated != 0 { 101 | return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0 102 | } 103 | return g.ActiveIdPreviousFrame == g.LastItemData.ID && g.ActiveIdPreviousFrame != 0 && g.ActiveId != g.LastItemData.ID 104 | } 105 | 106 | // IsItemDeactivatedAfterEdit was the last item just made inactive and made a value change when it was active? (e.g. Slider/Drag moved). Useful for Undo/Redo patterns with widgets that requires continuous editing. Note that you may get false positives (some widgets such as Combo()/ListBox()/Selectable() will return true even when clicking an already selected item). 107 | func IsItemDeactivatedAfterEdit() bool { 108 | var g = GImGui 109 | return IsItemDeactivated() && (g.ActiveIdPreviousFrameHasBeenEditedBefore || (g.ActiveId == 0 && g.ActiveIdHasBeenEditedBefore)) 110 | } 111 | 112 | // IsItemToggledOpen was the last item open state toggled? set by TreeNode(). 113 | func IsItemToggledOpen() bool { 114 | var g = GImGui 115 | return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledOpen) != 0 116 | } 117 | 118 | // IsAnyItemHovered is any item hovered? 119 | func IsAnyItemHovered() bool { 120 | var g = GImGui 121 | return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0 122 | } 123 | 124 | // IsAnyItemActive is any item active? 125 | func IsAnyItemActive() bool { 126 | var g = GImGui 127 | return g.ActiveId != 0 128 | } 129 | 130 | // IsAnyItemFocused is any item focused? 131 | func IsAnyItemFocused() bool { 132 | var g = GImGui 133 | return g.NavId != 0 && !g.NavDisableHighlight 134 | } 135 | 136 | // GetItemRectMin get upper-left bounding rectangle of the last item (screen space) 137 | func GetItemRectMin() ImVec2 { 138 | var g = GImGui 139 | return g.LastItemData.Rect.Min 140 | } 141 | 142 | // GetItemRectMax get lower-right bounding rectangle of the last item (screen space) 143 | func GetItemRectMax() ImVec2 { 144 | var g = GImGui 145 | return g.LastItemData.Rect.Max 146 | } 147 | 148 | // GetItemRectSize get size of last item 149 | func GetItemRectSize() ImVec2 { 150 | var g = GImGui 151 | return g.LastItemData.Rect.GetSize() 152 | } 153 | 154 | // SetItemAllowOverlap allow last item to be overlapped by a subsequent item. sometimes useful with invisible buttons, selectables, etc. to catch unused area. 155 | // Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority. 156 | // FIXME: Although this is exposed, its interaction and ideal idiom with using ImGuiButtonFlags_AllowItemOverlap flag are extremely confusing, need rework. 157 | func SetItemAllowOverlap() { 158 | var g = GImGui 159 | var id = g.LastItemData.ID 160 | if g.HoveredId == id { 161 | g.HoveredIdAllowOverlap = true 162 | } 163 | if g.ActiveId == id { 164 | g.ActiveIdAllowOverlap = true 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /widgets.seperator.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // separator, generally horizontal. inside a menu bar or in horizontal layout mode, this becomes a vertical separator. 4 | func Separator() { 5 | var g = GImGui 6 | var window = g.CurrentWindow 7 | if window.SkipItems { 8 | return 9 | } 10 | 11 | // Those flags should eventually be overridable by the user 12 | var flags ImGuiSeparatorFlags 13 | if window.DC.LayoutType == ImGuiLayoutType_Horizontal { 14 | flags = ImGuiSeparatorFlags_Vertical 15 | } else { 16 | flags = ImGuiSeparatorFlags_Horizontal 17 | } 18 | 19 | flags |= ImGuiSeparatorFlags_SpanAllColumns 20 | SeparatorEx(flags) 21 | } 22 | 23 | // Horizontal/vertical separating line 24 | func SeparatorEx(flags ImGuiSeparatorFlags) { 25 | var window = GetCurrentWindow() 26 | if window.SkipItems { 27 | return 28 | } 29 | 30 | var g = GImGui 31 | IM_ASSERT(ImIsPowerOfTwoInt(int(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))) // Check that only 1 option is selected 32 | 33 | var thickness_draw float = 1.0 34 | var thickness_layout float = 0.0 35 | if flags&ImGuiSeparatorFlags_Vertical != 0 { 36 | // Vertical separator, for menu bars (use current line height). Not exposed because it is misleading and it doesn't have an effect on regular layout. 37 | var y1 = window.DC.CursorPos.y 38 | var y2 = window.DC.CursorPos.y + window.DC.CurrLineSize.y 39 | var bb = ImRect{ImVec2{window.DC.CursorPos.x, y1}, ImVec2{window.DC.CursorPos.x + thickness_draw, y2}} 40 | ItemSizeVec(&ImVec2{thickness_layout, 0.0}, 0) 41 | if !ItemAdd(&bb, 0, nil, 0) { 42 | return 43 | } 44 | 45 | // Draw 46 | window.DrawList.AddLine(&ImVec2{bb.Min.x, bb.Min.y}, &ImVec2{bb.Min.x, bb.Max.y}, GetColorU32FromID(ImGuiCol_Separator, 1), 1) 47 | if g.LogEnabled { 48 | LogText(" |") 49 | } 50 | } else if flags&ImGuiSeparatorFlags_Horizontal != 0 { 51 | // Horizontal Separator 52 | var x1 = window.Pos.x 53 | var x2 = window.Pos.x + window.Size.x 54 | 55 | // FIXME-WORKRECT: old hack (#205) until we decide of consistent behavior with WorkRect/Indent and Separator 56 | if len(g.GroupStack) > 0 && g.GroupStack[len(g.GroupStack)-1].WindowID == window.ID { 57 | x1 += window.DC.Indent.x 58 | } 59 | 60 | var columns *ImGuiOldColumns 61 | if flags&ImGuiSeparatorFlags_SpanAllColumns != 0 { 62 | columns = window.DC.CurrentColumns 63 | } 64 | if columns != nil { 65 | PushColumnsBackground() 66 | } 67 | 68 | // We don't provide our width to the layout so that it doesn't get feed back into AutoFit 69 | var bb = ImRect{ImVec2{x1, window.DC.CursorPos.y}, ImVec2{x2, window.DC.CursorPos.y + thickness_draw}} 70 | ItemSizeVec(&ImVec2{0.0, thickness_layout}, 0) 71 | var item_visible = ItemAdd(&bb, 0, nil, 0) 72 | if item_visible { 73 | // Draw 74 | window.DrawList.AddLine(&bb.Min, &ImVec2{bb.Max.x, bb.Min.y}, GetColorU32FromID(ImGuiCol_Separator, 1), 1) 75 | if g.LogEnabled { 76 | LogRenderedText(&bb.Min, "--------------------------------\n") 77 | } 78 | } 79 | if columns != nil { 80 | PopColumnsBackground() 81 | columns.LineMinY = window.DC.CursorPos.y 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /widgets.slider.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // Widgets: Regular Sliders 9 | // - CTRL+Click on any slider to turn them into an input box. Manually input values aren't clamped and can go off-bounds. 10 | // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2 secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. 11 | // - Format string may also be set to NULL or use the default format ("%f" or "%d"). 12 | // - Legacy: Pre-1.78 there are SliderXXX() function signatures that takes a final `power float=1.0' argument instead of the `ImGuiSliderFlags flags=0' argument. 13 | // If you get a warning converting a to float ImGuiSliderFlags, read https://github.com/ocornut/imgui/issues/3361 14 | 15 | func SliderFloat(label string, v *float, v_min float, v_max float, format string /*= "%.3f"*/, flags ImGuiSliderFlags) bool { 16 | return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags) 17 | } 18 | 19 | // adjust format to decorate the value with a prefix or a suffix for in-slider labels or unit display. 20 | 21 | func SliderFloat2(label string, v *[2]float, v_min float, v_max float, format string /*= "%.3f"*/, flags ImGuiSliderFlags) bool { 22 | return SliderScalarN(label, ImGuiDataType_Float, v[:], v_min, v_max, format, flags) 23 | } 24 | func SliderFloat3(label string, v *[3]float, v_min float, v_max float, format string /*= "%.3f"*/, flags ImGuiSliderFlags) bool { 25 | return SliderScalarN(label, ImGuiDataType_Float, v[:], v_min, v_max, format, flags) 26 | } 27 | func SliderFloat4(label string, v *[4]float, v_min float, v_max float, format string /*= "%.3f"*/, flags ImGuiSliderFlags) bool { 28 | return SliderScalarN(label, ImGuiDataType_Float, v[:], v_min, v_max, format, flags) 29 | } 30 | 31 | func SliderAngle(label string, v_rad *float, v_degrees_min float /*= 0*/, v_degrees_max float /*= 0*/, format string /* = "%.0f deg"*/, flags ImGuiSliderFlags) bool { 32 | if format == "" { 33 | format = "%.0f deg" 34 | } 35 | var v_deg = (*v_rad) * 360.0 / (2 * IM_PI) 36 | var value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags) 37 | *v_rad = v_deg * (2 * IM_PI) / 360.0 38 | return value_changed 39 | } 40 | 41 | func SliderInt(label string, v *int, v_min int, v_max int, format string /*= "%d"*/, flags ImGuiSliderFlags) bool { 42 | return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags) 43 | } 44 | func SliderInt2(label string, v [2]int, v_min int, v_max int, format string /*= "%d"*/, flags ImGuiSliderFlags) bool { 45 | panic("not implemented") 46 | } 47 | func SliderInt3(label string, v [3]int, v_min int, v_max int, format string /*= "%d"*/, flags ImGuiSliderFlags) bool { 48 | panic("not implemented") 49 | } 50 | func SliderInt4(label string, v [4]int, v_min int, v_max int, format string /*= "%d"*/, flags ImGuiSliderFlags) bool { 51 | panic("not implemented") 52 | } 53 | 54 | // Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required. 55 | // Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo.Widgets.Data Types' to understand how to use this function directly. 56 | func SliderScalar(label string, data_type ImGuiDataType, p_data any, p_min any, p_max any, format string, flags ImGuiSliderFlags) bool { 57 | var window = GetCurrentWindow() 58 | if window.SkipItems { 59 | return false 60 | } 61 | 62 | var g = GImGui 63 | var style = g.Style 64 | var id = window.GetIDs(label) 65 | var w = CalcItemWidth() 66 | 67 | var label_size = CalcTextSize(label, true, -1) 68 | var frame_bb = ImRect{window.DC.CursorPos, window.DC.CursorPos.Add(ImVec2{w, label_size.y + style.FramePadding.y*2.0})} 69 | 70 | var padding float 71 | if label_size.x > 0.0 { 72 | padding = style.ItemInnerSpacing.x + label_size.x 73 | } 74 | 75 | var total_bb = ImRect{frame_bb.Min, frame_bb.Max.Add(ImVec2{padding, 0.0})} 76 | 77 | var temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0 78 | ItemSizeRect(&total_bb, style.FramePadding.y) 79 | 80 | var inputable ImGuiItemFlags 81 | if temp_input_allowed { 82 | inputable = ImGuiItemFlags_Inputable 83 | } 84 | 85 | if !ItemAdd(&total_bb, id, &frame_bb, inputable) { 86 | return false 87 | } 88 | 89 | // Default format string when passing nil 90 | if format == "" { 91 | format = DataTypeGetInfo(data_type).PrintFmt 92 | } 93 | 94 | // Tabbing or CTRL-clicking on Slider turns it into an input box 95 | var hovered = ItemHoverable(&frame_bb, id) 96 | var temp_input_is_active = temp_input_allowed && TempInputIsActive(id) 97 | if !temp_input_is_active { 98 | var focus_requested = temp_input_allowed && (g.LastItemData.StatusFlags&ImGuiItemStatusFlags_Focused) != 0 99 | var clicked = (hovered && g.IO.MouseClicked[0]) 100 | if focus_requested || clicked || g.NavActivateId == id || g.NavInputId == id { 101 | SetActiveID(id, window) 102 | SetFocusID(id, window) 103 | FocusWindow(window) 104 | g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) 105 | if temp_input_allowed && (focus_requested || (clicked && g.IO.KeyCtrl) || g.NavInputId == id) { 106 | temp_input_is_active = true 107 | } 108 | } 109 | } 110 | 111 | if temp_input_is_active { 112 | // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set 113 | var is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0 114 | 115 | var min, max any 116 | if is_clamp_input { 117 | min = p_min 118 | max = p_max 119 | } 120 | 121 | return TempInputScalar(&frame_bb, id, label, data_type, p_data, format, min, max) 122 | } 123 | 124 | // Draw frame 125 | var c = ImGuiCol_FrameBg 126 | if g.ActiveId == id { 127 | c = ImGuiCol_FrameBgActive 128 | } else if hovered { 129 | c = ImGuiCol_FrameBgHovered 130 | } 131 | 132 | var frame_col = GetColorU32FromID(c, 1) 133 | RenderNavHighlight(&frame_bb, id, 0) 134 | RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding) 135 | 136 | // Slider behavior 137 | var grab_bb ImRect 138 | var value_changed = SliderBehavior(&frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb) 139 | if value_changed { 140 | MarkItemEdited(id) 141 | } 142 | 143 | // Render grab 144 | if grab_bb.Max.x > grab_bb.Min.x { 145 | var c = ImGuiCol_SliderGrab 146 | if g.ActiveId == id { 147 | c = ImGuiCol_SliderGrabActive 148 | } 149 | 150 | window.DrawList.AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32FromID(c, 1), style.GrabRounding, 0) 151 | } 152 | 153 | // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. 154 | p_data_val := reflect.ValueOf(p_data).Elem() 155 | var value_buf = fmt.Sprintf(format, p_data_val) 156 | if g.LogEnabled { 157 | LogSetNextTextDecoration("{", "}") 158 | } 159 | RenderTextClipped(&frame_bb.Min, &frame_bb.Max, value_buf, nil, &ImVec2{0.5, 0.5}, nil) 160 | 161 | if label_size.x > 0.0 { 162 | RenderText(ImVec2{frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y}, label, true) 163 | } 164 | 165 | return value_changed 166 | } 167 | 168 | // Add multiple sliders on 1 line for compact edition of multiple components 169 | func SliderScalarN(label string, data_type ImGuiDataType, p_data []float, p_min float, p_max float, format string, flags ImGuiSliderFlags) bool { 170 | var window = GetCurrentWindow() 171 | if window.SkipItems { 172 | return false 173 | } 174 | 175 | var g = GImGui 176 | var value_changed = false 177 | BeginGroup() 178 | PushString(label) 179 | PushMultiItemsWidths(1, CalcItemWidth()) 180 | for i := 0; i < len(p_data); i++ { 181 | PushID(int(i)) 182 | if i > 0 { 183 | SameLine(0, g.Style.ItemInnerSpacing.x) 184 | } 185 | value_changed = SliderScalar("", data_type, &p_data[i], p_min, p_max, format, flags) || value_changed 186 | PopID() 187 | PopItemWidth() 188 | } 189 | PopID() 190 | 191 | SameLine(0, g.Style.ItemInnerSpacing.x) 192 | TextEx(label, 0) 193 | 194 | EndGroup() 195 | return value_changed 196 | } 197 | 198 | func VSliderFloat(label string, size ImVec2, v *float, v_min float, v_max float, format string /*= "%.3f"*/, flags ImGuiSliderFlags) bool { 199 | return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags) 200 | } 201 | 202 | func VSliderInt(label string, size ImVec2, v *int, v_min int, v_max int, format string /*= "%d"*/, flags ImGuiSliderFlags) bool { 203 | return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags) 204 | } 205 | 206 | func VSliderScalar(label string, size ImVec2, data_type ImGuiDataType, p_data any, p_min any, p_max any, format string, flags ImGuiSliderFlags) bool { 207 | var window = GetCurrentWindow() 208 | if window.SkipItems { 209 | return false 210 | } 211 | 212 | var g = GImGui 213 | var style = g.Style 214 | var id = window.GetIDs(label) 215 | 216 | var label_size = CalcTextSize(label, true, -1) 217 | var frame_bb = ImRect{window.DC.CursorPos, window.DC.CursorPos.Add(size)} 218 | 219 | var padding float 220 | if label_size.x > 0.0 { 221 | padding = style.ItemInnerSpacing.x + label_size.x 222 | } 223 | 224 | var bb = ImRect{frame_bb.Min, frame_bb.Max.Add(ImVec2{padding, 0.0})} 225 | 226 | ItemSizeRect(&bb, style.FramePadding.y) 227 | if !ItemAdd(&frame_bb, id, nil, 0) { 228 | return false 229 | } 230 | 231 | // Default format string when passing nil 232 | if format == "" { 233 | format = DataTypeGetInfo(data_type).PrintFmt 234 | } 235 | 236 | var hovered = ItemHoverable(&frame_bb, id) 237 | if (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id { 238 | SetActiveID(id, window) 239 | SetFocusID(id, window) 240 | FocusWindow(window) 241 | g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down) 242 | } 243 | 244 | // Draw frame 245 | var c = ImGuiCol_FrameBg 246 | if g.ActiveId == id { 247 | c = ImGuiCol_FrameBgActive 248 | } else if hovered { 249 | c = ImGuiCol_FrameBgHovered 250 | } 251 | 252 | var frame_col = GetColorU32FromID(c, 1) 253 | RenderNavHighlight(&frame_bb, id, 0) 254 | RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding) 255 | 256 | // Slider behavior 257 | var grab_bb ImRect 258 | var value_changed = SliderBehavior(&frame_bb, id, data_type, p_data, p_min, p_max, format, flags|ImGuiSliderFlags_Vertical, &grab_bb) 259 | if value_changed { 260 | MarkItemEdited(id) 261 | } 262 | 263 | // Render grab 264 | if grab_bb.Max.y > grab_bb.Min.y { 265 | var c = ImGuiCol_SliderGrab 266 | if g.ActiveId == id { 267 | c = ImGuiCol_SliderGrabActive 268 | } 269 | window.DrawList.AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32FromID(c, 1), style.GrabRounding, 0) 270 | } 271 | 272 | // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. 273 | // For the vertical slider we allow centered text to overlap the frame padding 274 | var value_buf = fmt.Sprintf(format, p_data) 275 | RenderTextClipped(&ImVec2{frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y}, &frame_bb.Max, value_buf, nil, &ImVec2{0.5, 0.0}, nil) 276 | if label_size.x > 0.0 { 277 | RenderText(ImVec2{frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y}, label, true) 278 | } 279 | 280 | return value_changed 281 | } 282 | -------------------------------------------------------------------------------- /widgets.text.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | import "fmt" 4 | 5 | // Widgets: Text 6 | // raw text without formatting. Roughly equivalent to Text("%s", text) but: A) doesn't require null terminated string if 'text_end' is specified, B) it's faster, no memory copy is done, no buffer size limits, recommended for long chunks of text. 7 | func TextUnformatted(text string) { 8 | TextEx(text, ImGuiTextFlags_NoWidthForLargeClippedText) 9 | } 10 | 11 | // shortcut for PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]); Text(fmt, ...); PopStyleColor() {panic("not implemented")} 12 | func TextDisabled(format string, args ...any) { 13 | var g = GImGui 14 | PushStyleColorVec(ImGuiCol_Text, &g.Style.Colors[ImGuiCol_TextDisabled]) 15 | if format[0] == '%' && format[1] == 's' && format[2] == 0 { 16 | TextEx(fmt.Sprintf(format, args...), ImGuiTextFlags_NoWidthForLargeClippedText) // Skip formatting 17 | } else { 18 | Text(format, args...) 19 | } 20 | PopStyleColor(1) 21 | } 22 | 23 | // shortcut for PushTextWrapPos(0.0); Text(fmt, ...); PopTextWrapPos() {panic("not implemented")}. Note that this won't work on an auto-resizing window if there's no other widgets to extend the window width, yoy may need to set a size using SetNextWindowSize(). 24 | func TextWrapped(format string, args ...any) { 25 | var g = GImGui 26 | var need_backup = (g.CurrentWindow.DC.TextWrapPos < 0.0) // Keep existing wrap position if one is already set 27 | if need_backup { 28 | PushTextWrapPos(0.0) 29 | } 30 | if format[0] == '%' && format[1] == 's' && format[2] == 0 { 31 | TextEx(fmt.Sprintf(format, args...), ImGuiTextFlags_NoWidthForLargeClippedText) // Skip formatting 32 | } else { 33 | Text(format, args...) 34 | } 35 | if need_backup { 36 | PopTextWrapPos() 37 | } 38 | } 39 | 40 | // display text+label aligned the same way as value+label widgets 41 | func LabelText(label string, format string, args ...any) { 42 | var window = GetCurrentWindow() 43 | if window.SkipItems { 44 | return 45 | } 46 | 47 | var g = GImGui 48 | var style = g.Style 49 | var w = CalcItemWidth() 50 | 51 | var value = fmt.Sprintf(format, args...) 52 | var value_size = CalcTextSize(value, false, -1) 53 | var label_size = CalcTextSize(label, true, -1) 54 | 55 | var pos = window.DC.CursorPos 56 | var value_bb = ImRect{pos, pos.Add(ImVec2{w, value_size.y + style.FramePadding.y*2})} 57 | 58 | var padding float 59 | if label_size.x > 0.0 { 60 | padding = style.ItemInnerSpacing.x + label_size.x 61 | } 62 | 63 | var total_bb = ImRect{pos, pos.Add(ImVec2{w + padding, ImMax(value_size.y, label_size.y) + style.FramePadding.y*2})} 64 | ItemSizeRect(&total_bb, style.FramePadding.y) 65 | if !ItemAdd(&total_bb, 0, nil, 0) { 66 | return 67 | } 68 | 69 | // Render 70 | min := value_bb.Min.Add(style.FramePadding) 71 | RenderTextClipped(&min, &value_bb.Max, value, &value_size, &ImVec2{}, nil) 72 | if label_size.x > 0.0 { 73 | RenderText(ImVec2{value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y}, label, true) 74 | } 75 | } 76 | 77 | // Text with a little bullet aligned to the typical tree node. 78 | // shortcut for Bullet()+Text() 79 | func BulletText(format string, args ...any) { 80 | var window = GetCurrentWindow() 81 | if window.SkipItems { 82 | return 83 | } 84 | 85 | var g = GImGui 86 | var style = g.Style 87 | 88 | var text = fmt.Sprintf(format, args...) 89 | var label_size = CalcTextSize(text, false, -1) 90 | 91 | var padding float 92 | if label_size.x > 0.0 { // Empty text doesn't add padding 93 | padding = (label_size.x + style.FramePadding.x*2) 94 | } 95 | 96 | var total_size = ImVec2{g.FontSize + padding, label_size.y} 97 | var pos = window.DC.CursorPos 98 | pos.y += window.DC.CurrLineTextBaseOffset 99 | ItemSizeVec(&total_size, 0.0) 100 | var bb = ImRect{pos, pos.Add(total_size)} 101 | if !ItemAdd(&bb, 0, nil, 0) { 102 | return 103 | } 104 | 105 | // Render 106 | var text_col = GetColorU32FromID(ImGuiCol_Text, 1) 107 | RenderBullet(window.DrawList, bb.Min.Add(ImVec2{style.FramePadding.x + g.FontSize*0.5, g.FontSize * 0.5}), text_col) 108 | RenderText(bb.Min.Add(ImVec2{g.FontSize + style.FramePadding.x*2, 0.0}), text, false) 109 | } 110 | 111 | // draw a small circle + keep the cursor on the same line. advance cursor x position by GetTreeNodeToLabelSpacing(), same distance that TreeNode() uses 112 | func Bullet() { 113 | var window = GetCurrentWindow() 114 | if window.SkipItems { 115 | return 116 | } 117 | 118 | var g = GImGui 119 | var style = g.Style 120 | var line_height = ImMax(ImMin(window.DC.CurrLineSize.y, g.FontSize+g.Style.FramePadding.y*2), g.FontSize) 121 | var bb = ImRect{window.DC.CursorPos, window.DC.CursorPos.Add(ImVec2{g.FontSize, line_height})} 122 | ItemSizeRect(&bb, 0) 123 | if !ItemAdd(&bb, 0, nil, 0) { 124 | SameLine(0, style.FramePadding.x*2) 125 | return 126 | } 127 | 128 | // Render and stay on same line 129 | var text_col = GetColorU32FromID(ImGuiCol_Text, 1) 130 | RenderBullet(window.DrawList, bb.Min.Add(ImVec2{style.FramePadding.x + g.FontSize*0.5, line_height * 0.5}), text_col) 131 | SameLine(0, style.FramePadding.x*2.0) 132 | } 133 | -------------------------------------------------------------------------------- /windows.children.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | import "fmt" 4 | 5 | // Popups, Modals, Tooltips 6 | func BeginChildEx(name string, id ImGuiID, size_arg *ImVec2, border bool, flags ImGuiWindowFlags) bool { 7 | var g = GImGui 8 | var parent_window = g.CurrentWindow 9 | 10 | flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_ChildWindow 11 | flags |= (parent_window.Flags & ImGuiWindowFlags_NoMove) // Inherit the NoMove flag 12 | 13 | // Size 14 | var content_avail = GetContentRegionAvail() 15 | var size = *ImFloorVec(size_arg) 16 | 17 | var auto_fit_axises int 18 | if size.x == 0.0 { 19 | auto_fit_axises = (1 << ImGuiAxis_X) 20 | } 21 | if size.y == 0.0 { 22 | auto_fit_axises |= (1 << ImGuiAxis_Y) 23 | } 24 | 25 | if size.x <= 0.0 { 26 | size.x = ImMax(content_avail.x+size.x, 4.0) // Arbitrary minimum child size (0.0f causing too much issues) 27 | } 28 | if size.y <= 0.0 { 29 | size.y = ImMax(content_avail.y+size.y, 4.0) 30 | } 31 | SetNextWindowSize(&size, 0) 32 | 33 | // Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value. 34 | if name != "" { 35 | g.TempBuffer = fmt.Sprintf("%s/%s_%08X", parent_window.Name, name, id) 36 | } else { 37 | g.TempBuffer = fmt.Sprintf("%s/%08X", parent_window.Name, id) 38 | } 39 | 40 | var backup_border_size = g.Style.ChildBorderSize 41 | if !border { 42 | g.Style.ChildBorderSize = 0.0 43 | } 44 | var ret = Begin(string(g.TempBuffer[:]), nil, flags) 45 | g.Style.ChildBorderSize = backup_border_size 46 | 47 | var child_window = g.CurrentWindow 48 | child_window.ChildId = id 49 | child_window.AutoFitChildAxises = (ImS8)(auto_fit_axises) 50 | 51 | // Set the cursor to handle case where the user called SetNextWindowPos()+BeginChild() manually. 52 | // While this is not really documented/defined, it seems that the expected thing to do. 53 | if child_window.BeginCount == 1 { 54 | parent_window.DC.CursorPos = child_window.Pos 55 | } 56 | 57 | // Process navigation-in immediately so NavInit can run on first frame 58 | if g.NavActivateId == id && (flags&ImGuiWindowFlags_NavFlattened == 0) && (child_window.DC.NavLayersActiveMask != 0 || child_window.DC.NavHasScroll) { 59 | FocusWindow(child_window) 60 | NavInitWindow(child_window, false) 61 | SetActiveID(id+1, child_window) // Steal ActiveId with another arbitrary id so that key-press won't activate child item 62 | g.ActiveIdSource = ImGuiInputSource_Nav 63 | } 64 | return ret 65 | } 66 | 67 | // Child Windows 68 | // - Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window. Child windows can embed their own child. 69 | // - For each independent axis of 'size': ==0.0: use remaining host window size / >0.0: fixed size / <0.0: use remaining window size minus abs(size) / Each axis can use a different mode, e.g. ImVec2(0,400). 70 | // - BeginChild() returns false to indicate the window is collapsed or fully clipped, so you may early out and omit submitting anything to the window. 71 | // Always call a matching EndChild() for each BeginChild() call, regardless of its return value. 72 | // [Important: due to legacy reason, this is inconsistent with most other functions such as BeginMenu/EndMenu, 73 | // BeginPopup/EndPopup, etc. where the EndXXX call should only be called if the corresponding BeginXXX function 74 | // returned true. Begin and BeginChild are the only odd ones out. Will be fixed in a future update.] 75 | func BeginChild(str_id string, size ImVec2, border bool, flags ImGuiWindowFlags) bool { 76 | var window = GetCurrentWindow() 77 | return BeginChildEx(str_id, window.GetIDs(str_id), &size, border, flags) 78 | } 79 | 80 | func BeginChildID(id ImGuiID, size ImVec2, border bool, flags ImGuiWindowFlags) bool { 81 | IM_ASSERT(id != 0) 82 | return BeginChildEx("", id, &size, border, flags) 83 | } 84 | 85 | func EndChild() { 86 | var g = GImGui 87 | var window = g.CurrentWindow 88 | 89 | IM_ASSERT(!g.WithinEndChild) 90 | IM_ASSERT(window.Flags&ImGuiWindowFlags_ChildWindow != 0) // Mismatched BeginChild()/EndChild() calls 91 | 92 | g.WithinEndChild = true 93 | if window.BeginCount > 1 { 94 | End() 95 | } else { 96 | var sz = window.Size 97 | if window.AutoFitChildAxises&(1<= 0; i-- { 62 | var candidate_window = g.Windows[i] 63 | if candidate_window == potential_above { 64 | return true 65 | } 66 | if candidate_window == potential_below { 67 | return false 68 | } 69 | } 70 | return false 71 | } 72 | 73 | func SetWindowHitTestHole(window *ImGuiWindow, pos *ImVec2, size *ImVec2) { 74 | IM_ASSERT(window.HitTestHoleSize.x == 0) // We don't support multiple holes/hit test filters 75 | window.HitTestHoleSize = ImVec2ih{x: int16(size.x), y: int16(size.y)} 76 | diff := pos.Sub(window.Pos) 77 | window.HitTestHoleOffset = ImVec2ih{int16(diff.x), int16(diff.y)} 78 | } 79 | 80 | func BringWindowToDisplayBack(window *ImGuiWindow) { 81 | var g = GImGui 82 | if g.Windows[0] == window { 83 | return 84 | } 85 | for i := range g.Windows { 86 | if g.Windows[i] == window { 87 | *g.Windows[1] = *g.Windows[0] 88 | g.Windows[0] = window 89 | break 90 | } 91 | } 92 | } 93 | 94 | // 0..3: corners (Lower-right, Lower-left, Unused, Unused) 95 | func GetWindowResizeCornerID(window *ImGuiWindow, n int) ImGuiID { 96 | IM_ASSERT(n >= 0 && n < 4) 97 | var id = window.ID 98 | id = ImHashStr("#RESIZE", 0, id) 99 | id = ImHashData(unsafe.Pointer(&n), unsafe.Sizeof(n), id) 100 | return id 101 | } 102 | 103 | // Borders (Left, Right, Up, Down) 104 | func GetWindowResizeBorderID(window *ImGuiWindow, dir ImGuiDir) ImGuiID { 105 | IM_ASSERT(dir >= 0 && dir < 4) 106 | var n = (int)(dir) + 4 107 | var id = window.ID 108 | id = ImHashStr("#RESIZE", 0, id) 109 | id = ImHashData(unsafe.Pointer(&n), unsafe.Sizeof(n), id) 110 | return id 111 | } 112 | -------------------------------------------------------------------------------- /windows.mut.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // Window manipulation 4 | // - Prefer using SetNextXXX functions (before Begin) rather that SetXXX functions (after Begin). 5 | 6 | // set next window position. call before Begin(). use pivot=(0.5,0.5) to center on given point, etc. 7 | func SetNextWindowPos(pos *ImVec2, cond ImGuiCond, pivot ImVec2) { 8 | var g = GImGui 9 | IM_ASSERT(cond == 0 || ImIsPowerOfTwoInt(int(cond))) // Make sure the user doesn't attempt to combine multiple condition flags. 10 | g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasPos 11 | g.NextWindowData.PosVal = *pos 12 | g.NextWindowData.PosPivotVal = pivot 13 | if cond != 0 { 14 | g.NextWindowData.PosCond = cond 15 | } else { 16 | g.NextWindowData.PosCond = ImGuiCond_Always 17 | } 18 | } 19 | 20 | // set next window size limits. use -1,-1 on either X/Y axis to preserve the current size. Sizes will be rounded down. Use callback to apply non-trivial programmatic constraints. 21 | func SetNextWindowSizeConstraints(size_min ImVec2, size_max ImVec2, custom_callback ImGuiSizeCallback, custom_callback_data any) { 22 | var g = GImGui 23 | g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSizeConstraint 24 | g.NextWindowData.SizeConstraintRect = ImRect{size_min, size_max} 25 | g.NextWindowData.SizeCallback = custom_callback 26 | g.NextWindowData.SizeCallbackUserData = custom_callback_data 27 | } 28 | 29 | // Content size = inner scrollable rectangle, padded with WindowPadding. 30 | // SetNextWindowContentSize(ImVec2(100,100) + ImGuiWindowFlags_AlwaysAutoResize will always allow submitting a 100x100 item. 31 | // set next window content size (~ scrollable client area, which enforce the range of scrollbars). Not including window decorations (title bar, menu bar, etc.) nor WindowPadding. set an axis to 0.0 to leave it automatic. call before Begin() 32 | func SetNextWindowContentSize(size ImVec2) { 33 | var g = GImGui 34 | g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasContentSize 35 | g.NextWindowData.ContentSizeVal = *ImFloorVec(&size) 36 | } 37 | 38 | // set next window collapsed state. call before Begin() 39 | func SetNextWindowCollapsed(collapsed bool, cond ImGuiCond) { 40 | var g = GImGui 41 | IM_ASSERT(cond == 0 || ImIsPowerOfTwoInt(int(cond))) // Make sure the user doesn't attempt to combine multiple condition flags. 42 | g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasCollapsed 43 | g.NextWindowData.CollapsedVal = collapsed 44 | if cond != 0 { 45 | g.NextWindowData.CollapsedCond = cond 46 | } else { 47 | g.NextWindowData.CollapsedCond = ImGuiCond_Always 48 | } 49 | } 50 | 51 | // set next window to be focused / top-most. call before Begin() 52 | func SetNextWindowFocus() { 53 | var g = GImGui 54 | g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasFocus 55 | } 56 | 57 | // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects. 58 | func SetWindowPos(pos ImVec2, cond ImGuiCond) { 59 | var window = GetCurrentWindowRead() 60 | setWindowPos(window, &pos, cond) 61 | } 62 | 63 | // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0, 0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects. 64 | func SetWindowSize(size ImVec2, cond ImGuiCond) { 65 | setWindowSize(GImGui.CurrentWindow, &size, cond) 66 | } 67 | 68 | func setWindowCollapsed(window *ImGuiWindow, collapsed bool, cond ImGuiCond) { 69 | // Test condition (NB: bit 0 is always true) and clear flags for next time 70 | if cond != 0 && (window.SetWindowCollapsedAllowFlags&cond) == 0 { 71 | return 72 | } 73 | window.SetWindowCollapsedAllowFlags &= ^(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing) 74 | 75 | // Set 76 | window.Collapsed = collapsed 77 | } 78 | 79 | // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed(). 80 | func SetWindowCollapsed(collapsed bool, cond ImGuiCond) { 81 | setWindowCollapsed(GImGui.CurrentWindow, collapsed, cond) 82 | } 83 | 84 | // (not recommended) set current window to be focused / top-most. prefer using SetNextWindowFocus(). 85 | func SetWindowFocus() { 86 | FocusWindow(GImGui.CurrentWindow) 87 | } 88 | 89 | // [OBSOLETE] set font scale. Adjust IO.FontGlobalScale if you want to scale all windows. This is an old API! For correct scaling, prefer to reload font + rebuild ImFontAtlas + call style.ScaleAllSizes(). 90 | func SetWindowFontScale(scale float) { 91 | IM_ASSERT(scale > 0.0) 92 | var g = GImGui 93 | var window = GetCurrentWindow() 94 | window.FontWindowScale = scale 95 | calculated := window.CalcFontSize() 96 | g.FontSize = calculated 97 | g.DrawListSharedData.FontSize = calculated 98 | } 99 | 100 | // set named window position. 101 | func SetNamedWindowPos(name string, pos ImVec2, cond ImGuiCond) { 102 | if window := FindWindowByName(name); window != nil { 103 | setWindowPos(window, &pos, cond) 104 | } 105 | } 106 | 107 | // set named window size. set axis to 0.0 to force an auto-fit on this axis. 108 | func SetNamedWindowSize(name string, size ImVec2, cond ImGuiCond) { 109 | if window := FindWindowByName(name); window != nil { 110 | setWindowSize(window, &size, cond) 111 | } 112 | } 113 | 114 | // set named window collapsed state 115 | func SetNamedWindowCollapsed(name string, collapsed bool, cond ImGuiCond) { 116 | if window := FindWindowByName(name); window != nil { 117 | setWindowCollapsed(window, collapsed, cond) 118 | } 119 | } 120 | 121 | // set named window to be focused / top-most. use NULL to remove focus. 122 | func SetNamedWindowFocus(name string) { 123 | if name != "" { 124 | if window := FindWindowByName(name); window != nil { 125 | FocusWindow(window) 126 | } 127 | } else { 128 | FocusWindow(nil) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /windows.queries.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // Windows Utilities 4 | // - 'current window' = the window we are appending into while inside a Begin()/End() block. 'next window' = next window we will Begin() into. 5 | 6 | func IsWindowAppearing() bool { 7 | var window = GetCurrentWindowRead() 8 | return window.Appearing 9 | } 10 | 11 | func IsWindowCollapsed() bool { 12 | var window = GetCurrentWindowRead() 13 | return window.Collapsed 14 | } 15 | 16 | // Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext) 17 | // Note that NoNavFocus makes the window not reachable with CTRL+TAB but it can still be focused with mouse or programmatically. 18 | // If you want a window to never be focused, you may use the e.g. NoInputs flag. 19 | func IsWindowNavFocusable(window *ImGuiWindow) bool { 20 | return window.WasActive && window == window.RootWindow && (window.Flags&ImGuiWindowFlags_NoNavFocus == 0) 21 | } 22 | 23 | // is current window focused? or its root/child, depending on flags. see flags for options. 24 | func IsWindowFocused(flags ImGuiFocusedFlags) bool { 25 | var g = GImGui 26 | 27 | if flags&ImGuiFocusedFlags_AnyWindow != 0 { 28 | return g.NavWindow != nil 29 | } 30 | 31 | IM_ASSERT(g.CurrentWindow != nil) // Not inside a Begin()/End() 32 | switch flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows) { 33 | case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows: 34 | return g.NavWindow != nil && g.NavWindow.RootWindow == g.CurrentWindow.RootWindow 35 | case ImGuiFocusedFlags_RootWindow: 36 | return g.NavWindow == g.CurrentWindow.RootWindow 37 | case ImGuiFocusedFlags_ChildWindows: 38 | return g.NavWindow != nil && IsWindowChildOf(g.NavWindow, g.CurrentWindow) 39 | default: 40 | return g.NavWindow == g.CurrentWindow 41 | } 42 | } 43 | 44 | // is current window hovered (and typically: not blocked by a popup/modal)? see flags for options. NB: If you are trying to check whether your mouse should be dispatched to imgui or to your app, you should use the 'io.WantCaptureMouse' boolean for that! Please read the FAQ! 45 | func IsWindowHovered(flags ImGuiHoveredFlags) bool { 46 | IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0) // Flags not supported by this function 47 | var g = GImGui 48 | if g.HoveredWindow == nil { 49 | return false 50 | } 51 | 52 | if (flags & ImGuiHoveredFlags_AnyWindow) == 0 { 53 | var window = g.CurrentWindow 54 | switch flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows) { 55 | case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows: 56 | if g.HoveredWindow.RootWindow != window.RootWindow { 57 | return false 58 | } 59 | case ImGuiHoveredFlags_RootWindow: 60 | if g.HoveredWindow != window.RootWindow { 61 | return false 62 | } 63 | case ImGuiHoveredFlags_ChildWindows: 64 | if !IsWindowChildOf(g.HoveredWindow, window) { 65 | return false 66 | } 67 | default: 68 | if g.HoveredWindow != window { 69 | return false 70 | } 71 | } 72 | } 73 | 74 | if !IsWindowContentHoverable(g.HoveredWindow, flags) { 75 | return false 76 | } 77 | if flags&ImGuiHoveredFlags_AllowWhenBlockedByActiveItem == 0 { 78 | if g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow.MoveId { 79 | return false 80 | } 81 | } 82 | return true 83 | } 84 | 85 | // get draw list associated to the current window, to append your own drawing primitives 86 | func GetWindowDrawList() *ImDrawList { 87 | var window = GetCurrentWindow() 88 | return window.DrawList 89 | } 90 | 91 | // get current window position in screen space (useful if you want to do your own drawing via the DrawList API) 92 | func GetWindowPos() ImVec2 { 93 | var g = GImGui 94 | var window = g.CurrentWindow 95 | return window.Pos 96 | } 97 | 98 | // get current window size 99 | func GetWindowSize() ImVec2 { 100 | var window = GetCurrentWindowRead() 101 | return window.Size 102 | } 103 | 104 | // get current window width (shortcut for GetWindowSize().x) 105 | func GetWindowWidth() float { 106 | var window = GImGui.CurrentWindow 107 | return window.Size.x 108 | } 109 | 110 | // get current window height (shortcut for GetWindowSize().y) 111 | func GetWindowHeight() float { 112 | var window = GImGui.CurrentWindow 113 | return window.Size.y 114 | } 115 | -------------------------------------------------------------------------------- /windows.scroll.go: -------------------------------------------------------------------------------- 1 | package imgui 2 | 3 | // Scrolling 4 | // Windows Scrolling 5 | 6 | // GetScrollX get scrolling amount [0 .. GetScrollMaxX()] 7 | func GetScrollX() float { 8 | var window = GImGui.CurrentWindow 9 | return window.Scroll.x 10 | } 11 | 12 | // GetScrollY get scrolling amount [0 .. GetScrollMaxY()] 13 | func GetScrollY() float { 14 | var window = GImGui.CurrentWindow 15 | return window.Scroll.y 16 | } 17 | 18 | // SetScrollX set scrolling amount [0 .. GetScrollMaxX()] 19 | func SetScrollX(scroll_x float) { 20 | var g = GImGui 21 | setScrollX(g.CurrentWindow, scroll_x) 22 | } 23 | 24 | // SetScrollY set scrolling amount [0 .. GetScrollMaxY()] 25 | func SetScrollY(scroll_y float) { 26 | var g = GImGui 27 | setScrollY(g.CurrentWindow, scroll_y) 28 | } 29 | 30 | // GetScrollMaxX get maximum scrolling amount ~~ ContentSize.x - WindowSize.x - DecorationsSize.x 31 | func GetScrollMaxX() float { 32 | var window = GImGui.CurrentWindow 33 | return window.ScrollMax.x 34 | } 35 | 36 | // GetScrollMaxY get maximum scrolling amount ~~ ContentSize.y - WindowSize.y - DecorationsSize.y 37 | func GetScrollMaxY() float { 38 | var window = GImGui.CurrentWindow 39 | return window.ScrollMax.y 40 | } 41 | 42 | // SetScrollHereX adjust scrolling amount to make current cursor position visible. center_x_ratio=0.0: left, 0.5: center, 1.0: right. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. 43 | // center_x_ratio: 0.0f left of last item, 0.5f horizontal center of last item, 1.0f right of last item. 44 | func SetScrollHereX(center_x_ratio float /*= 0.5*/) { 45 | var g = GImGui 46 | var window = g.CurrentWindow 47 | var spacing_x = ImMax(window.WindowPadding.x, g.Style.ItemSpacing.x) 48 | var target_pos_x = ImLerp(g.LastItemData.Rect.Min.x-spacing_x, g.LastItemData.Rect.Max.x+spacing_x, center_x_ratio) 49 | setScrollFromPosX(window, target_pos_x-window.Pos.x, center_x_ratio) // Convert from absolute to local pos 50 | 51 | // Tweak: snap on edges when aiming at an item very close to the edge 52 | window.ScrollTargetEdgeSnapDist.x = ImMax(0.0, window.WindowPadding.x-spacing_x) 53 | } 54 | 55 | // SetScrollHereY adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. 56 | // center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item. 57 | func SetScrollHereY(center_y_ratio float /*= 0.5*/) { 58 | var g = GImGui 59 | var window = g.CurrentWindow 60 | var spacing_y = ImMax(window.WindowPadding.y, g.Style.ItemSpacing.y) 61 | var target_pos_y = ImLerp(window.DC.CursorPosPrevLine.y-spacing_y, window.DC.CursorPosPrevLine.y+window.DC.PrevLineSize.y+spacing_y, center_y_ratio) 62 | setScrollFromPosY(window, target_pos_y-window.Pos.y, center_y_ratio) // Convert from absolute to local pos 63 | 64 | // Tweak: snap on edges when aiming at an item very close to the edge 65 | window.ScrollTargetEdgeSnapDist.y = ImMax(0.0, window.WindowPadding.y-spacing_y) 66 | } 67 | 68 | // SetScrollFromPosX adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position 69 | func SetScrollFromPosX(local_x, center_x_ratio float /*= 0.5*/) { 70 | var g = GImGui 71 | setScrollFromPosX(g.CurrentWindow, local_x, center_x_ratio) 72 | } 73 | 74 | // SetScrollFromPosY adjust scrolling amount to make given position visible. Generally GetCursorStartPos() + offset to compute a valid position. 75 | func SetScrollFromPosY(local_y, center_y_ratio float /*= 0.5*/) { 76 | var g = GImGui 77 | setScrollFromPosY(g.CurrentWindow, local_y, center_y_ratio) 78 | } 79 | 80 | // SetNextWindowScroll Use -1.0f on one axis to leave as-is 81 | func SetNextWindowScroll(scroll *ImVec2) { 82 | var g = GImGui 83 | g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasScroll 84 | g.NextWindowData.ScrollVal = *scroll 85 | } 86 | 87 | func setScrollX(window *ImGuiWindow, scroll_x float) { 88 | window.ScrollTarget.x = scroll_x 89 | window.ScrollTargetCenterRatio.x = 0.0 90 | window.ScrollTargetEdgeSnapDist.x = 0.0 91 | } 92 | 93 | func setScrollY(window *ImGuiWindow, scroll_y float) { 94 | window.ScrollTarget.y = scroll_y 95 | window.ScrollTargetCenterRatio.y = 0.0 96 | window.ScrollTargetEdgeSnapDist.y = 0.0 97 | } 98 | 99 | // Note that a local position will vary depending on initial scroll value, 100 | // This is a little bit confusing so bear with us: 101 | // - local_pos = (absolution_pos - window.Pos) 102 | // - So local_x/local_y are 0.0f for a position at the upper-left corner of a window, 103 | // and generally local_x/local_y are >(padding+decoration) && <(size-padding-decoration) when in the visible area. 104 | // - They mostly exists because of legacy API. 105 | // 106 | // Following the rules above, when trying to work with scrolling code, consider that: 107 | // - SetScrollFromPosY(0.0f) == SetScrollY(0.0f + scroll.y) == has no effect! 108 | // - SetScrollFromPosY(-scroll.y) == SetScrollY(-scroll.y + scroll.y) == SetScrollY(0.0f) == reset scroll. Of course writing SetScrollY(0.0f) directly then makes more sense 109 | // 110 | // We store a target position so centering and clamping can occur on the next frame when we are guaranteed to have a known window size 111 | func setScrollFromPosX(window *ImGuiWindow, local_x float, center_x_ratio float) { 112 | IM_ASSERT(center_x_ratio >= 0.0 && center_x_ratio <= 1.0) 113 | window.ScrollTarget.x = IM_FLOOR(local_x + window.Scroll.x) // Convert local position to scroll offset 114 | window.ScrollTargetCenterRatio.x = center_x_ratio 115 | window.ScrollTargetEdgeSnapDist.x = 0.0 116 | } 117 | 118 | func setScrollFromPosY(window *ImGuiWindow, local_y float, center_y_ratio float) { 119 | IM_ASSERT(center_y_ratio >= 0.0 && center_y_ratio <= 1.0) 120 | var decoration_up_height = window.TitleBarHeight() + window.MenuBarHeight() // FIXME: Would be nice to have a more standardized access to our scrollable/client rect; 121 | local_y -= decoration_up_height 122 | window.ScrollTarget.y = IM_FLOOR(local_y + window.Scroll.y) // Convert local position to scroll offset 123 | window.ScrollTargetCenterRatio.y = center_y_ratio 124 | window.ScrollTargetEdgeSnapDist.y = 0.0 125 | } 126 | 127 | func ScrollToBringRectIntoView(window *ImGuiWindow, item_rect *ImRect) ImVec2 { 128 | var g = GImGui 129 | var window_rect = ImRect{window.InnerRect.Min.Sub(ImVec2{1, 1}), window.InnerRect.Max.Add(ImVec2{1, 1})} 130 | //GetForegroundDrawList(window).AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG] 131 | 132 | var delta_scroll ImVec2 133 | if !window_rect.ContainsRect(*item_rect) { 134 | if window.ScrollbarX && item_rect.Min.x < window_rect.Min.x { 135 | setScrollFromPosX(window, item_rect.Min.x-window.Pos.x-g.Style.ItemSpacing.x, 0.0) 136 | } else if window.ScrollbarX && item_rect.Max.x >= window_rect.Max.x { 137 | setScrollFromPosX(window, item_rect.Max.x-window.Pos.x+g.Style.ItemSpacing.x, 1.0) 138 | } 139 | if item_rect.Min.y < window_rect.Min.y { 140 | setScrollFromPosY(window, item_rect.Min.y-window.Pos.y-g.Style.ItemSpacing.y, 0.0) 141 | } else if item_rect.Max.y >= window_rect.Max.y { 142 | setScrollFromPosY(window, item_rect.Max.y-window.Pos.y+g.Style.ItemSpacing.y, 1.0) 143 | } 144 | 145 | var next_scroll = CalcNextScrollFromScrollTargetAndClamp(window) 146 | delta_scroll = next_scroll.Sub(window.Scroll) 147 | } 148 | 149 | // Also scroll parent window to keep us into view if necessary 150 | if window.Flags&ImGuiWindowFlags_ChildWindow != 0 { 151 | delta_scroll = delta_scroll.Add(ScrollToBringRectIntoView(window.ParentWindow, 152 | &ImRect{item_rect.Min.Sub(delta_scroll), item_rect.Max.Sub(delta_scroll)})) 153 | } 154 | 155 | return delta_scroll 156 | } 157 | 158 | // CalcScrollEdgeSnap Helper to snap on edges when aiming at an item very close to the edge, 159 | // So the difference between WindowPadding and ItemSpacing will be in the visible area after scrolling. 160 | // When we refactor the scrolling API this may be configurable with a flag? 161 | // Note that the effect for this won't be visible on X axis with default Style settings as WindowPadding.x == ItemSpacing.x by default. 162 | func CalcScrollEdgeSnap(target, snap_min, snap_max, snap_threshold, center_ratio float) float { 163 | if target <= snap_min+snap_threshold { 164 | return ImLerp(snap_min, target, center_ratio) 165 | } 166 | if target >= snap_max-snap_threshold { 167 | return ImLerp(target, snap_max, center_ratio) 168 | } 169 | return target 170 | } 171 | 172 | func CalcNextScrollFromScrollTargetAndClamp(window *ImGuiWindow) ImVec2 { 173 | var scroll = window.Scroll 174 | if window.ScrollTarget.x < FLT_MAX { 175 | var decoration_total_width = window.ScrollbarSizes.x 176 | var center_x_ratio = window.ScrollTargetCenterRatio.x 177 | var scroll_target_x = window.ScrollTarget.x 178 | if window.ScrollTargetEdgeSnapDist.x > 0.0 { 179 | var snap_x_min float = 0.0 180 | var snap_x_max = window.ScrollMax.x + window.SizeFull.x - decoration_total_width 181 | scroll_target_x = CalcScrollEdgeSnap(scroll_target_x, snap_x_min, snap_x_max, window.ScrollTargetEdgeSnapDist.x, center_x_ratio) 182 | } 183 | scroll.x = scroll_target_x - center_x_ratio*(window.SizeFull.x-decoration_total_width) 184 | } 185 | if window.ScrollTarget.y < FLT_MAX { 186 | var decoration_total_height = window.TitleBarHeight() + window.MenuBarHeight() + window.ScrollbarSizes.y 187 | var center_y_ratio = window.ScrollTargetCenterRatio.y 188 | var scroll_target_y = window.ScrollTarget.y 189 | if window.ScrollTargetEdgeSnapDist.y > 0.0 { 190 | var snap_y_min float = 0.0 191 | var snap_y_max = window.ScrollMax.y + window.SizeFull.y - decoration_total_height 192 | scroll_target_y = CalcScrollEdgeSnap(scroll_target_y, snap_y_min, snap_y_max, window.ScrollTargetEdgeSnapDist.y, center_y_ratio) 193 | } 194 | scroll.y = scroll_target_y - center_y_ratio*(window.SizeFull.y-decoration_total_height) 195 | } 196 | scroll.x = IM_FLOOR(ImMax(scroll.x, 0.0)) 197 | scroll.y = IM_FLOOR(ImMax(scroll.y, 0.0)) 198 | if !window.Collapsed && !window.SkipItems { 199 | scroll.x = ImMin(scroll.x, window.ScrollMax.x) 200 | scroll.y = ImMin(scroll.y, window.ScrollMax.y) 201 | } 202 | return scroll 203 | } 204 | --------------------------------------------------------------------------------