Open ocornut opened 8 years ago
Great job :-)
Looking really good! Will you be adding it to the lib or will it just be a snippet?
Eventually it should be added but it's not complete nor good enough yet. In order to support submenus the API would likely need to be changed?
What would submenues within a pie-popup look like?
I'm not sure. Open a second pie-popup over the center of the previous button? That would suggest that merely holding mouse would open the sub-menu (like normal menu), which in turn ask the question of how we can get back to the parent menu.
I haven't spend time to think about it in details, just implemented something as fast as I can for someone, which is essentially why this is a proof of concept rather than a feature.
Another idea would perhaps be to expand the pie outwards, presenting a top-down menu. The amount of choices could be increased by increasing the size of the extended pie-slice upwards/downwards?
It does not work when trying to display in without a window ? I try to display it on a simple OpenGL window ! After a drag & drop... I drop inside a 3D view... and nothing appear !
It always bothered me, that the border between the items increased from the inner ring to the outer, so here is a fix for that (only listing the inner rendering for-loop):
for (int item_n = 0; item_n < items_count; item_n++)
{
const char* item_label = items[item_n];
const float inner_spacing = style.ItemInnerSpacing.x / RADIUS_MIN / 2;
const float item_inner_ang_min = item_arc_span * (item_n - 0.5f + inner_spacing);
const float item_inner_ang_max = item_arc_span * (item_n + 0.5f - inner_spacing);
const float item_outer_ang_min = item_arc_span * (item_n - 0.5f + inner_spacing * (RADIUS_MIN / RADIUS_MAX));
const float item_outer_ang_max = item_arc_span * (item_n + 0.5f - inner_spacing * (RADIUS_MIN / RADIUS_MAX));
bool hovered = false;
if (drag_dist2 >= RADIUS_INTERACT_MIN*RADIUS_INTERACT_MIN)
{
if (drag_angle >= item_inner_ang_min && drag_angle < item_inner_ang_max)
hovered = true;
}
bool selected = p_selected && (*p_selected == item_n);
int arc_segments = (int)(32 * item_arc_span / (2*IM_PI)) + 1;
draw_list->PathArcTo(center, RADIUS_MAX - style.ItemInnerSpacing.x, item_outer_ang_min, item_outer_ang_max, arc_segments);
draw_list->PathArcTo(center, RADIUS_MIN + style.ItemInnerSpacing.x, item_inner_ang_max, item_inner_ang_min, arc_segments);
//draw_list->PathFill(window->Color(hovered ? ImGuiCol_HeaderHovered : ImGuiCol_FrameBg));
draw_list->PathFill(hovered ? ImColor(100,100,150) : selected ? ImColor(120,120,140) : ImColor(70,70,70));
ImVec2 text_size = ImGui::GetWindowFont()->CalcTextSizeA(ImGui::GetWindowFontSize(), FLT_MAX, 0.0f, item_label);
ImVec2 text_pos = ImVec2(
center.x + cosf((item_inner_ang_min + item_inner_ang_max) * 0.5f) * (RADIUS_MIN + RADIUS_MAX) * 0.5f - text_size.x * 0.5f,
center.y + sinf((item_inner_ang_min + item_inner_ang_max) * 0.5f) * (RADIUS_MIN + RADIUS_MAX) * 0.5f - text_size.y * 0.5f);
draw_list->AddText(text_pos, ImColor(255,255,255), item_label);
if (hovered)
item_hovered = item_n;
}
Furthermore here are inspirational videos from other pie menu implementations:
https://www.youtube.com/watch?v=nXh1Tm24kTE&t=50s the submenu implementation from this one is probably not advisable, but I like how it adds a second ring if there are more then six items.
https://www.youtube.com/watch?v=Job4Rg-sbDo this is the one in OneNote; it has some kind of submenu implementation, but from the video, I am unable to understand how it works.
I really like the simple Ubuntu "look", just a simple set of "alpha blended" pies over the screen :-P
Thanks @Horrowind, really useful!
You should be able to, the pie-menu is its own window. Krys please provide more detailed information and standalone repro code with your questions when possible! Lots of your questions are really unclear, if you can't clarify the question then please provide the smallest set of code that demonstrate the issue you have. Thanks!
It is impossible to gives a small reproduction case, because the application is huge !
What he means is, start a new, tiny application that just reproduces a single problem.
On Thu, Dec 17, 2015 at 12:25 PM, krys-spectralpixel < notifications@github.com> wrote:
I agree but...
It is impossible to gives a small reproduction case, because the application is huge !
The effect is that nothing appear !
Here is my code, but it is approx. a copy/paste of your code:
const char* items[] = { "Color", "Specular", "Bump", "Opacity" }; int items_count = sizeof(items) / sizeof(*items); static int selected = -1; ImGui::OpenPopup("##piepopup"); // ImVec2 pie_menu_center = ImGui::GetIO().MouseClickedPos[0]; ImVec2 pie_menu_center = ImGui::GetMousePos(); int n = PiePopupSelectMenu(pie_menu_center, "##piepopup", items, items_count, &selected);
— Reply to this email directly or view it on GitHub https://github.com/ocornut/imgui/issues/434#issuecomment-165410961.
It is your job to provide a repro to ensure that your request is well formed and thought up. It also in many cases helps you understand the issue better. Your code above is not a repro I can paste.
Have you noticed that my pie code above close automatically if mouse is released?
This works without a window for me:
const char* items[] = { "Color", "Specular", "Bump", "Opacity" };
int items_count = sizeof(items) / sizeof(*items);
static int selected = -1;
if (!ImGui::IsMouseHoveringAnyWindow() && ImGui::IsMouseClicked(0))
ImGui::OpenPopup("##piepopup");
int n = PiePopupSelectMenu(ImGui::GetIO().MouseClickedPos[0], "##piepopup", items, items_count, &selected);
I propose to replace:
if (drag_dist2 >= RADIUS_INTERACT_MIN*RADIUS_INTERACT_MIN && drag_dist2 < RADIUS_MAX*RADIUS_MAX)
with
if (&& drag_dist2 >= RADIUS_INTERACT_MIN*RADIUS_INTERACT_MIN && drag_dist2 < RADIUS_MAX*RADIUS_MAX)
In order to avoid selection when the mouse is out of the pie menu !
I did that intentionally to avoid false negatives and also considering that pie menus have a "gesture" feel to them. e.g. click and drag left, release, by not worrying about distance travelled you can accomplish faster actions. Maybe it isn't as important with mouse controls. Both ways have legit uses, it could be a global setting of the pie menus potentially.
Right. Sure there can be plenty of options for this control :-D
Hi,
I have noticed that when I use only 4 "items" in the pie menu I have a line going over the items (See the red arrow on the attached picture please).
Have you ever noticed it, can you reproduce it ?
The PathFill function in theory only handle convex fill, so I suppose that's an artefact of this limitation. Proper support for concave fill would be quite some work. The cheap workaround would be to split the arc in smaller section (say, divided by 2) and draw it as two parts. It would however show visible artefacts if your fill alpha is < 1.0f
Great explanation !
Thanks for it. You helped me a lot.
I compiled pie menu with DX9, after run exe ,I found a black area at the centre of the circle, do not know how to cancel this area?
@zhouxs1023 this is caused by the popup background, I don't know why it dosen't follow the style pushed but I "fixed" it by adding this code just before if (ImGui::BeginPopup(popup_id))
ImGui::SetNextWindowPos({-100, -100});
Thank you very much!
A modified version with support of sub menu, it's not finished or optimized and need a new struct for storing values and items label. @ocornut, is there any chance to have a storage of string someday for avoiding this custom struct? ;) Or add a way (with macro?) to add custom variables in ImGuiContext.
Can by used like this
if( ImGui::IsWindowHovered() && ImGui::IsMouseClicked( 1 ) )
{
ImGui::OpenPopup( "PieMenu" );
}
if( BeginPiePopup( "PieMenu", 1 ) )
{
if( PieMenuItem( "Test1" ) ) { /*TODO*/ }
if( PieMenuItem( "Test2" ) ) { /*TODO*/ }
if( PieMenuItem( "Test3", false ) ) { /*TODO*/ }
if( BeginPieMenu( "Sub" ) )
{
if( BeginPieMenu( "Sub sub\nmenu" ) )
{
if( PieMenuItem( "SubSub" ) ) { /*TODO*/ }
if( PieMenuItem( "SubSub2" ) ) { /*TODO*/ }
EndPieMenu();
}
if( PieMenuItem( "TestSub" ) ) { /*TODO*/ }
if( PieMenuItem( "TestSub2" ) ) { /*TODO*/ }
EndPieMenu();
}
EndPiePopup();
}
Full source here https://gist.github.com/thennequin/64b4b996ec990c6ddc13a48c6a0ba68c
@thennequin
is there any chance to have a storage of string someday for avoiding this custom struct? ;)
There's a helper ImGuiTextBuffer which is close to what you are doing.
Maybe a stripped down version of https://github.com/ocornut/Str would generally be useful, but if you can use a single buffer like ImGuiTextBuffer it helps.
Or add a way (with macro?) to add custom variables in ImGuiContext.
I've been considering it, perhaps a way for a external subsystem to register a slot instead an array of pointers stored in ImGuiContext, so you can store one PieMenuContext per ImGuiContext. Is that were you were thinking of?
There's a helper ImGuiTextBuffer which is close to what you are doing.
I use an ImVector< char > for storing strings. Sorry for the misunderstanding of the first question, I just wanted a way to add strings (like bool/int/float/ptr) in the ImGuiStorage for avoiding the use of a new external struct because I store bool/float/int and string.
I've been considering it, perhaps a way for a external subsystem to register a slot instead an array of pointers stored in ImGuiContext, so you can store one PieMenuContext per ImGuiContext. Is that were you were thinking of?
Yes, I want to store one PieMenuContext per ImGuiContext.
@thennequin You probably haven't updated this in a while nor remember how you wrote it, but it's worth the shot. I tried your implementation, and I've seen only 1 issue, and a weird quirk I wanted to see if I can change. The issue is that there is no background color, and yes, I've tried to change the color, didn't budge, and the quirk is that if it goes a little offscreen, it pushes itself away from the border, if I wanted to remove this quirk, how would I go about doing that?
I updated the gist to fix the "quirk" (if you talk about the black rectangle) and I fixed the background (wrong UV).
does this work with controller navigation?
Well no this is old code but nowadays you can use GetKeyData(ImGuiKey_GamepadLStickX)->AnalogValue and ditto for Y to construct a 2D vector and use that in your pie menu code.
For those of you that want to implement controller support:
1) There is no ImGuiKey_GamepadLStickX, but there is ImGuiKey_GamepadLStickUp, down, left, right, etc, so you need to subtract.
2) Implement hovered memory so that releasing the joystick keeps the highlight and subsequent return when you close the menu.
3) Mind the inner_spacing gaps. For controllers it creates awkward locations inbetween elements that results in no selection. Either remove it or create separate floats for the actual selectable and visual representation of segments if you want a clean look without usability sacrifice.
4) Implement the controller equivalent to the minimum drag distance (deadzone) so that the kickback from releasing a thumbstick doesn't inadvertently select a different element. I've found |x| or |y| greater than 0.6 works well.
This could maybe better live within ImPlot? https://github.com/epezent/implot
Hi! just dropping by to say thanks for the initial idea. I've managed to make sth cool with it
Hi! just dropping by to say thanks for the initial idea. I've managed to make sth cool with it
Nice! I used it to make a combat art + prosthetic wheel in Sekiro. Yours seems much more polished though. I was considering doing a rewrite that would fix the bugs, crashes, and terrible spaghetti code. Mind if I yoink parts of that with credit? (if I even get to it lol)
@tmsrise It's MIT licensed so take anything you'd like :)
Posting a minor update to the 2015 version (some code simplification) note however that it is functionally the same.
I reckon Thibault's version may be better suited: https://github.com/ocornut/imgui/issues/434#issuecomment-351743369
#include "imgui_internal.h"
// Return >= 0 on mouse release
// Optional int* p_selected display and update a currently selected item
int PiePopupSelectMenu(const ImVec2& center, const char* popup_id, const char** items, int items_count, int* p_selected)
{
int ret = -1;
if (ImGui::BeginPopup(popup_id, ImGuiWindowFlags_NoDecoration))
{
const ImVec2 drag_delta = ImVec2(ImGui::GetIO().MousePos.x - center.x, ImGui::GetIO().MousePos.y - center.y);
const float drag_dist2 = drag_delta.x * drag_delta.x + drag_delta.y * drag_delta.y;
const ImGuiStyle& style = ImGui::GetStyle();
const float RADIUS_MIN = 30.0f;
const float RADIUS_MAX = 120.0f;
const float RADIUS_INTERACT_MIN = 20.0f; // Handle hit testing slightly below RADIUS_MIN
const int ITEMS_MIN = 6; // If they are less than 6 items, we still make each item fill a 1/6 slice.
// Draw background
ImDrawList* draw_list = ImGui::GetWindowDrawList();
draw_list->PushClipRectFullScreen();
draw_list->PathArcTo(center, (RADIUS_MIN + RADIUS_MAX) * 0.5f, 0.0f, IM_PI * 2.0f);
draw_list->PathStroke(IM_COL32(0, 0, 0, 255), ImDrawFlags_Closed, RADIUS_MAX - RADIUS_MIN);
const float item_arc_span = 2 * IM_PI / ImMax(ITEMS_MIN, items_count);
float drag_angle = ImAtan2(drag_delta.y, drag_delta.x);
if (drag_angle < -0.5f * item_arc_span)
drag_angle += 2.0f * IM_PI;
//ImGui::Text("%f", drag_angle); // [Debug]
// Draw items
int item_hovered = -1;
for (int item_n = 0; item_n < items_count; item_n++)
{
const char* item_label = items[item_n];
const float item_ang_min = item_arc_span * (item_n + 0.02f) - item_arc_span * 0.5f; // FIXME: Could calculate padding angle based on how many pixels they'll take
const float item_ang_max = item_arc_span * (item_n + 0.98f) - item_arc_span * 0.5f;
bool hovered = false;
if (drag_dist2 >= RADIUS_INTERACT_MIN * RADIUS_INTERACT_MIN)
if (drag_angle >= item_ang_min && drag_angle < item_ang_max)
hovered = true;
bool selected = p_selected && (*p_selected == item_n);
draw_list->PathArcTo(center, RADIUS_MAX - style.ItemInnerSpacing.x, item_ang_min, item_ang_max);
draw_list->PathArcTo(center, RADIUS_MIN + style.ItemInnerSpacing.x, item_ang_max, item_ang_min);
draw_list->PathFillConvex(ImGui::GetColorU32(hovered ? ImGuiCol_HeaderHovered : selected ? ImGuiCol_HeaderActive : ImGuiCol_Header));
ImVec2 text_size = ImGui::CalcTextSize(item_label);
ImVec2 text_pos = ImVec2(
center.x + cosf((item_ang_min + item_ang_max) * 0.5f) * (RADIUS_MIN + RADIUS_MAX) * 0.5f - text_size.x * 0.5f,
center.y + sinf((item_ang_min + item_ang_max) * 0.5f) * (RADIUS_MIN + RADIUS_MAX) * 0.5f - text_size.y * 0.5f);
draw_list->AddText(text_pos, ImGui::GetColorU32(ImGuiCol_Text), item_label);
if (hovered)
item_hovered = item_n;
}
draw_list->PopClipRect();
if (ImGui::IsMouseReleased(0))
{
ImGui::CloseCurrentPopup();
ret = item_hovered;
if (p_selected)
*p_selected = item_hovered;
}
ImGui::EndPopup();
}
return ret;
}
Usage:
static const char* test_data = "Menu";
const char* items[] = { "Orange", "Blue", "Purple", "Gray", "Yellow", "Las Vegas" };
int items_count = sizeof(items) / sizeof(*items);
static int selected = -1;
ImGui::Button(selected >= 0 ? items[selected] : "Menu", ImVec2(50, 50));
if (ImGui::IsItemActive()) // Don't wait for button release to activate the pie menu
ImGui::OpenPopup("##piepopup");
ImVec2 pie_menu_center = ImGui::GetIO().MouseClickedPos[0];
int n = PiePopupSelectMenu(pie_menu_center, "##piepopup", items, items_count, &selected);
if (n >= 0)
printf("returned %d\n", n);
}
This is more a proof of concept that a finished api.