From b8c2a695b8e0a209f92ca56c8dfa659f3cceae99 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 7 Jun 2021 12:18:05 +0200 Subject: [PATCH] ui: allow drag scrolling anywhere. change some ui elements allow scrolling by dragging (almost) anywhere move physical devices before dreamcast ones hide insets when displaying full screen window reduce scroll bar size on android fix controller mapping column sizes --- core/deps/imgui/imgui.cpp | 1 + core/deps/imgui/imgui_internal.h | 2 + core/deps/imgui/imgui_widgets.cpp | 22 +++ core/rend/gles/imgui_impl_opengl3.cpp | 8 +- core/rend/gui.cpp | 264 +++++++++++++------------ core/rend/gui_util.cpp | 149 +++++++++++++- core/rend/gui_util.h | 16 +- core/rend/vulkan/imgui_impl_vulkan.cpp | 14 +- 8 files changed, 330 insertions(+), 146 deletions(-) diff --git a/core/deps/imgui/imgui.cpp b/core/deps/imgui/imgui.cpp index 34c0da3c1..ca18fbb19 100644 --- a/core/deps/imgui/imgui.cpp +++ b/core/deps/imgui/imgui.cpp @@ -2795,6 +2795,7 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) : DrawListInst DrawList = &DrawListInst; DrawList->_Data = &context->DrawListSharedData; DrawList->_OwnerName = Name; + DragScrolling = false; } ImGuiWindow::~ImGuiWindow() diff --git a/core/deps/imgui/imgui_internal.h b/core/deps/imgui/imgui_internal.h index f3ab30e76..27aaa0e91 100644 --- a/core/deps/imgui/imgui_internal.h +++ b/core/deps/imgui/imgui_internal.h @@ -1619,6 +1619,8 @@ struct IMGUI_API ImGuiWindow ImVec2 ScrollTargetCenterRatio; // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target position is centered ImVec2 ScrollTargetEdgeSnapDist; // 0.0f = no snapping, >0.0f snapping threshold ImVec2 ScrollbarSizes; // Size taken by each scrollbars on their smaller axis. Pay attention! ScrollbarSizes.x == width of the vertical scrollbar, ScrollbarSizes.y = height of the horizontal scrollbar. + ImVec2 ScrollSpeed; + bool DragScrolling; bool ScrollbarX, ScrollbarY; // Are scrollbars visible? bool Active; // Set to true on Begin(), unless Collapsed bool WasActive; diff --git a/core/deps/imgui/imgui_widgets.cpp b/core/deps/imgui/imgui_widgets.cpp index 08a741fbc..08fea337d 100644 --- a/core/deps/imgui/imgui_widgets.cpp +++ b/core/deps/imgui/imgui_widgets.cpp @@ -636,7 +636,24 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool ClearActiveID(); } if (!(flags & ImGuiButtonFlags_NoNavFocus)) + { g.NavDisableHighlight = true; + // Check if dragging (except for scrollbars) + if (held && !hovered && !pressed) + { + ImVec2 delta = GetMouseDragDelta(ImGuiMouseButton_Left); + if (delta.x != 0.f || delta.y != 0.f) + { + ClearActiveID(); + ImGuiWindow *scrollableWindow = window; + while ((scrollableWindow->Flags & ImGuiWindowFlags_ChildWindow) && scrollableWindow->ScrollMax.x == 0.0f && scrollableWindow->ScrollMax.y == 0.0f) + scrollableWindow = scrollableWindow->ParentWindow; + scrollableWindow->DragScrolling = true; + held = false; + pressed = false; + } + } + } } else if (g.ActiveIdSource == ImGuiInputSource_Nav) { @@ -6007,6 +6024,11 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl if (flags & ImGuiSelectableFlags_Disabled) selected = false; + if (window->DragScrolling) + { + selected = false; + button_flags |= ImGuiButtonFlags_Disabled; + } const bool was_selected = selected; bool hovered, held; diff --git a/core/rend/gles/imgui_impl_opengl3.cpp b/core/rend/gles/imgui_impl_opengl3.cpp index da0afec97..12ba3e62d 100644 --- a/core/rend/gles/imgui_impl_opengl3.cpp +++ b/core/rend/gles/imgui_impl_opengl3.cpp @@ -115,8 +115,6 @@ void ImGui_ImplOpenGL3_NewFrame() ImGui_ImplOpenGL3_DrawBackground(); } -extern int insetLeft, insetTop; // Android notches - // OpenGL3 Render function. // (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) // Note that this implementation is little overcomplicated because we are saving/setting up/restoring every OpenGL state explicitly, in order to be able to run within any OpenGL engine that doesn't do so. @@ -157,7 +155,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) // Setup viewport, orthographic projection matrix // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. - glViewport(insetLeft, insetTop, (GLsizei)fb_width, (GLsizei)fb_height); + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); float L = draw_data->DisplayPos.x; float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; float T = draw_data->DisplayPos.y; @@ -222,9 +220,9 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) { // Apply scissor/clipping rectangle if (clip_origin_lower_left) - glcache.Scissor(insetLeft + (int)clip_rect.x, (int)(fb_height - clip_rect.w), insetTop + (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y)); + glcache.Scissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y)); else - glcache.Scissor(insetLeft + (int)clip_rect.x, (int)clip_rect.y, insetTop + (int)clip_rect.z, (int)clip_rect.w); // Support for GL 4.5's glClipControl(GL_UPPER_LEFT) + glcache.Scissor((int)clip_rect.x, (int)clip_rect.y, (int)clip_rect.z, (int)clip_rect.w); // Support for GL 4.5's glClipControl(GL_UPPER_LEFT) // Bind texture, Draw glcache.BindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index cb2f45781..66b66a167 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -136,8 +136,6 @@ void gui_init() ImGui::GetStyle().ItemInnerSpacing = ImVec2(4, 6); // from 4,4 //ImGui::GetStyle().WindowRounding = 0; #ifdef __ANDROID__ - ImGui::GetStyle().GrabMinSize = 20.0f; // from 10 - ImGui::GetStyle().ScrollbarSize = 24.0f; // from 16 ImGui::GetStyle().TouchExtraPadding = ImVec2(1, 1); // from 0,0 #endif @@ -267,8 +265,8 @@ static void ImGui_Impl_NewFrame() { if (config::RendererType.isOpenGL()) ImGui_ImplOpenGL3_NewFrame(); - ImGui::GetIO().DisplaySize.x = screen_width - insetLeft - insetRight; - ImGui::GetIO().DisplaySize.y = screen_height - insetTop - insetBottom; + ImGui::GetIO().DisplaySize.x = screen_width; + ImGui::GetIO().DisplaySize.y = screen_height; ImGuiIO& io = ImGui::GetIO(); @@ -289,12 +287,12 @@ static void ImGui_Impl_NewFrame() if (mo_x_phy < 0 || mo_x_phy >= screen_width || mo_y_phy < 0 || mo_y_phy >= screen_height) io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); else - io.MousePos = ImVec2(mo_x_phy - insetLeft, mo_y_phy - insetTop); + io.MousePos = ImVec2(mo_x_phy, mo_y_phy); static bool delayTouch; #ifdef __ANDROID__ // Delay touch by one frame to allow widgets to be hovered before click // This is required for widgets using ImGuiButtonFlags_AllowItemOverlap such as TabItem's - if (!delayTouch && (mo_buttons[0] & (1 << 2)) == 0 && io.MouseDown[ImGuiMouseButton_Left] == 0) + if (!delayTouch && (mo_buttons[0] & (1 << 2)) == 0 && !io.MouseDown[ImGuiMouseButton_Left]) delayTouch = true; else delayTouch = false; @@ -346,6 +344,7 @@ static void ImGui_Impl_NewFrame() io.AddInputCharacter((0xc2 + (b > 0xbf)) | ((b & 0x3f) + 0x80) << 8); } } + ImGui::GetStyle().Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.94f); } void gui_set_insets(int left, int right, int top, int bottom) @@ -676,16 +675,14 @@ static void detect_input_popup(int index, bool analog) static void controller_mapping_popup(const std::shared_ptr& gamepad) { - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); + fullScreenWindow(true); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); - if (ImGui::BeginPopupModal("Controller Mapping", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) + if (ImGui::BeginPopupModal("Controller Mapping", NULL, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) { - const float width = ImGui::GetIO().DisplaySize.x / 2; - const float col_width = (width - - ImGui::GetStyle().GrabMinSize - - (0 + ImGui::GetStyle().ItemSpacing.x) - - (ImGui::CalcTextSize("Map").x + ImGui::GetStyle().FramePadding.x * 2.0f + ImGui::GetStyle().ItemSpacing.x)) / 2; + const ImGuiStyle& style = ImGui::GetStyle(); + const float width = (ImGui::GetIO().DisplaySize.x - insetLeft - insetRight - style.ItemSpacing.x) / 2 - style.WindowBorderSize - style.WindowPadding.x; + const float col_width = (width - style.GrabMinSize - style.ItemSpacing.x + - (ImGui::CalcTextSize("Map").x + style.FramePadding.x * 2.0f + style.ItemSpacing.x)) / 2; std::shared_ptr input_mapping = gamepad->get_input_mapping(); if (input_mapping == NULL || ImGui::Button("Done", ImVec2(100 * scaling, 30 * scaling))) @@ -715,7 +712,7 @@ static void controller_mapping_popup(const std::shared_ptr& gamep ImGui::PopItemWidth(); } ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("Arcade button names").x - - ImGui::GetStyle().FramePadding.x * 3.0f - ImGui::GetStyle().ItemSpacing.x); + - style.FramePadding.x * 3.0f - style.ItemSpacing.x); ImGui::Checkbox("Arcade button names", &arcade_button_mode); char key_id[32]; @@ -764,6 +761,10 @@ static void controller_mapping_popup(const std::shared_ptr& gamep ImGui::NextColumn(); ImGui::PopID(); } + ImGui::Columns(1, nullptr, false); + scrollWhenDraggingOnVoid(); + windowDragScroll(); + ImGui::EndChildFrame(); ImGui::EndGroup(); @@ -772,7 +773,7 @@ static void controller_mapping_popup(const std::shared_ptr& gamep ImGui::BeginGroup(); ImGui::Text(" Analog Axes "); ImGui::BeginChildFrame(ImGui::GetID("analog"), ImVec2(width, 0), ImGuiWindowFlags_None); - ImGui::Columns(3, "bindings", false); + ImGui::Columns(3, "anabindings", false); ImGui::SetColumnWidth(0, col_width); ImGui::SetColumnWidth(1, col_width); @@ -814,6 +815,9 @@ static void controller_mapping_popup(const std::shared_ptr& gamep ImGui::NextColumn(); ImGui::PopID(); } + ImGui::Columns(1, nullptr, false); + scrollWhenDraggingOnVoid(); + windowDragScroll(); ImGui::EndChildFrame(); ImGui::EndGroup(); ImGui::EndPopup(); @@ -897,14 +901,21 @@ static void contentpath_warning_popup() } } +inline static void header(const char *title) +{ + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.f, 0.5f)); // Left + ImGui::ButtonEx(title, ImVec2(-1, 0), ImGuiButtonFlags_Disabled); + ImGui::PopStyleVar(); +} + static void gui_display_settings() { static bool maple_devices_changed; RenderType pvr_rend = config::RendererType; bool vulkan = !config::RendererType.isOpenGL(); - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); + + fullScreenWindow(false); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); ImGui::Begin("Settings", NULL, /*ImGuiWindowFlags_AlwaysAutoResize |*/ ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); @@ -1024,6 +1035,7 @@ static void gui_display_settings() } }); ImGui::PopStyleVar(); + scrollWhenDraggingOnVoid(); ImGui::ListBoxFooter(); if (to_delete >= 0) @@ -1076,7 +1088,76 @@ static void gui_display_settings() if (ImGui::BeginTabItem("Controls")) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); - if (ImGui::CollapsingHeader("Dreamcast Devices", ImGuiTreeNodeFlags_DefaultOpen)) + header("Physical Devices"); + { + ImGui::Columns(4, "physicalDevices", false); + ImGui::Text("System"); + ImGui::SetColumnWidth(-1, ImGui::CalcTextSize("System").x + ImGui::GetStyle().FramePadding.x * 2.0f + ImGui::GetStyle().ItemSpacing.x); + ImGui::NextColumn(); + ImGui::Text("Name"); + ImGui::NextColumn(); + ImGui::Text("Port"); + ImGui::SetColumnWidth(-1, ImGui::CalcTextSize("None").x * 1.6f + ImGui::GetStyle().FramePadding.x * 2.0f + ImGui::GetFrameHeight() + + ImGui::GetStyle().ItemInnerSpacing.x + ImGui::GetStyle().ItemSpacing.x); + ImGui::NextColumn(); + ImGui::NextColumn(); + for (int i = 0; i < GamepadDevice::GetGamepadCount(); i++) + { + std::shared_ptr gamepad = GamepadDevice::GetGamepad(i); + if (!gamepad) + continue; + ImGui::Text("%s", gamepad->api_name().c_str()); + ImGui::NextColumn(); + ImGui::Text("%s", gamepad->name().c_str()); + ImGui::NextColumn(); + char port_name[32]; + sprintf(port_name, "##mapleport%d", i); + ImGui::PushID(port_name); + if (ImGui::BeginCombo(port_name, maple_ports[gamepad->maple_port() + 1])) + { + for (int j = -1; j < (int)ARRAY_SIZE(maple_ports) - 1; j++) + { + bool is_selected = gamepad->maple_port() == j; + if (ImGui::Selectable(maple_ports[j + 1], &is_selected)) + gamepad->set_maple_port(j); + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + + ImGui::EndCombo(); + } + ImGui::NextColumn(); + if (gamepad->remappable() && ImGui::Button("Map")) + { + gamepad_port = 0; + ImGui::OpenPopup("Controller Mapping"); + } + + controller_mapping_popup(gamepad); + +#ifdef __ANDROID__ + if (gamepad->is_virtual_gamepad()) + { + if (ImGui::Button("Edit")) + { + vjoy_start_editing(); + gui_state = GuiState::VJoyEdit; + } + ImGui::SameLine(); + OptionSlider("Haptic", config::VirtualGamepadVibration, 0, 60); + } +#endif + ImGui::NextColumn(); + ImGui::PopID(); + } + } + ImGui::Columns(1, NULL, false); + + ImGui::Spacing(); + OptionSlider("Mouse sensitivity", config::MouseSensitivity, 1, 500); + + ImGui::Spacing(); + header("Dreamcast Devices"); { for (int bus = 0; bus < MAPLE_PORTS; bus++) { @@ -1160,75 +1241,7 @@ static void gui_display_settings() } ImGui::PopItemWidth(); } - ImGui::Spacing(); } - if (ImGui::CollapsingHeader("Physical Devices", ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::Columns(4, "physicalDevices", false); - ImGui::Text("System"); - ImGui::SetColumnWidth(-1, ImGui::CalcTextSize("System").x + ImGui::GetStyle().FramePadding.x * 2.0f + ImGui::GetStyle().ItemSpacing.x); - ImGui::NextColumn(); - ImGui::Text("Name"); - ImGui::NextColumn(); - ImGui::Text("Port"); - ImGui::SetColumnWidth(-1, ImGui::CalcTextSize("None").x * 1.6f + ImGui::GetStyle().FramePadding.x * 2.0f + ImGui::GetFrameHeight() - + ImGui::GetStyle().ItemInnerSpacing.x + ImGui::GetStyle().ItemSpacing.x); - ImGui::NextColumn(); - ImGui::NextColumn(); - for (int i = 0; i < GamepadDevice::GetGamepadCount(); i++) - { - std::shared_ptr gamepad = GamepadDevice::GetGamepad(i); - if (!gamepad) - continue; - ImGui::Text("%s", gamepad->api_name().c_str()); - ImGui::NextColumn(); - ImGui::Text("%s", gamepad->name().c_str()); - ImGui::NextColumn(); - char port_name[32]; - sprintf(port_name, "##mapleport%d", i); - ImGui::PushID(port_name); - if (ImGui::BeginCombo(port_name, maple_ports[gamepad->maple_port() + 1])) - { - for (int j = -1; j < (int)ARRAY_SIZE(maple_ports) - 1; j++) - { - bool is_selected = gamepad->maple_port() == j; - if (ImGui::Selectable(maple_ports[j + 1], &is_selected)) - gamepad->set_maple_port(j); - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - - ImGui::EndCombo(); - } - ImGui::NextColumn(); - if (gamepad->remappable() && ImGui::Button("Map")) - { - gamepad_port = 0; - ImGui::OpenPopup("Controller Mapping"); - } - - controller_mapping_popup(gamepad); - -#ifdef __ANDROID__ - if (gamepad->is_virtual_gamepad()) - { - if (ImGui::Button("Edit")) - { - vjoy_start_editing(); - gui_state = GuiState::VJoyEdit; - } - ImGui::SameLine(); - OptionSlider("Haptic", config::VirtualGamepadVibration, 0, 60); - } -#endif - ImGui::NextColumn(); - ImGui::PopID(); - } - } - ImGui::Columns(1, NULL, false); - - ImGui::Spacing(); - OptionSlider("Mouse sensitivity", config::MouseSensitivity, 1, 500); ImGui::PopStyleVar(); ImGui::EndTabItem(); @@ -1247,7 +1260,7 @@ static void gui_display_settings() #else bool has_per_pixel = false; #endif - if (ImGui::CollapsingHeader("Transparent Sorting", ImGuiTreeNodeFlags_DefaultOpen)) + header("Transparent Sorting"); { int renderer = (pvr_rend == RenderType::OpenGL_OIT || pvr_rend == RenderType::Vulkan_OIT) ? 2 : config::PerStripSorting ? 1 : 0; ImGui::Columns(has_per_pixel ? 3 : 2, "renderers", false); @@ -1290,7 +1303,8 @@ static void gui_display_settings() break; } } - if (ImGui::CollapsingHeader("Rendering Options", ImGuiTreeNodeFlags_DefaultOpen)) + ImGui::Spacing(); + header("Rendering Options"); { ImGui::Text("Automatic Frame Skipping:"); ImGui::Columns(3, "autoskip", false); @@ -1352,9 +1366,9 @@ static void gui_display_settings() resLabels[i] += " (" + scalingsText[i] + ")"; } - ImGuiStyle& scaling_style = ImGui::GetStyle(); - float scaling_spacing = scaling_style.ItemInnerSpacing.x; - ImGui::PushItemWidth(ImGui::CalcItemWidth() - scaling_spacing * 2.0f - ImGui::GetFrameHeight() * 2.0f); + ImGuiStyle& style = ImGui::GetStyle(); + float innerSpacing = style.ItemInnerSpacing.x; + ImGui::PushItemWidth(ImGui::CalcItemWidth() - innerSpacing * 2.0f - ImGui::GetFrameHeight() * 2.0f); if (ImGui::BeginCombo("##Resolution", resLabels[selected].c_str(), ImGuiComboFlags_NoArrowButton)) { for (u32 i = 0; i < scalings.size(); i++) @@ -1368,20 +1382,20 @@ static void gui_display_settings() ImGui::EndCombo(); } ImGui::PopItemWidth(); - ImGui::SameLine(0, scaling_spacing); + ImGui::SameLine(0, innerSpacing); if (ImGui::ArrowButton("##Decrease Res", ImGuiDir_Left)) { if (selected > 0) config::RenderResolution = vres[selected - 1]; } - ImGui::SameLine(0, scaling_spacing); + ImGui::SameLine(0, innerSpacing); if (ImGui::ArrowButton("##Increase Res", ImGuiDir_Right)) { if (selected < vres.size() - 1) config::RenderResolution = vres[selected + 1]; } - ImGui::SameLine(0, scaling_style.ItemInnerSpacing.x); + ImGui::SameLine(0, style.ItemInnerSpacing.x); ImGui::Text("Internal Resolution"); ImGui::SameLine(); @@ -1389,22 +1403,24 @@ static void gui_display_settings() OptionSlider("Horizontal Stretching", config::ScreenStretching, 100, 150, "Stretch the screen horizontally"); - OptionSlider("Frame Skipping", config::SkipFrame, 0, 6, + OptionArrowButtons("Frame Skipping", config::SkipFrame, 0, 6, "Number of frames to skip between two actually rendered frames"); } - if (ImGui::CollapsingHeader("Render to Texture", ImGuiTreeNodeFlags_DefaultOpen)) + ImGui::Spacing(); + header("Render to Texture"); { OptionCheckbox("Copy to VRAM", config::RenderToTextureBuffer, "Copy rendered-to textures back to VRAM. Slower but accurate"); } - if (ImGui::CollapsingHeader("Texture Upscaling", ImGuiTreeNodeFlags_DefaultOpen)) + ImGui::Spacing(); + header("Texture Upscaling"); { #ifndef TARGET_NO_OPENMP - OptionSlider("Texture Upscaling", config::TextureUpscale, 1, 8, + OptionArrowButtons("Texture Upscaling", config::TextureUpscale, 1, 8, "Upscale textures with the xBRZ algorithm. Only on fast platforms and for certain 2D games"); - OptionSlider("Upscaled Texture Max Size", config::MaxFilteredTextureSize, 8, 1024, + OptionSlider("Texture Max Size", config::MaxFilteredTextureSize, 8, 1024, "Textures larger than this dimension squared will not be upscaled"); - OptionSlider("Max Threads", config::MaxThreads, 1, 8, + OptionArrowButtons("Max Threads", config::MaxThreads, 1, 8, "Maximum number of threads to use for texture upscaling. Recommended: number of physical cores minus one"); #endif OptionCheckbox("Load Custom Textures", config::CustomTextures, @@ -1525,7 +1541,7 @@ static void gui_display_settings() if (ImGui::BeginTabItem("Advanced")) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); - if (ImGui::CollapsingHeader("CPU Mode", ImGuiTreeNodeFlags_DefaultOpen)) + header("CPU Mode"); { ImGui::Columns(2, "cpu_modes", false); OptionRadioButton("Dynarec", config::DynarecEnabled, true, @@ -1535,13 +1551,16 @@ static void gui_display_settings() "Use the interpreter. Very slow but may help in case of a dynarec problem"); ImGui::Columns(1, NULL, false); } - if (config::DynarecEnabled && ImGui::CollapsingHeader("Dynarec Options", ImGuiTreeNodeFlags_DefaultOpen)) + if (config::DynarecEnabled) { + ImGui::Spacing(); + header("Dynarec Options"); OptionCheckbox("Safe Mode", config::DynarecSafeMode, "Do not optimize integer division. Not recommended"); OptionCheckbox("Idle Skip", config::DynarecIdleSkip, "Skip wait loops. Recommended"); } - if (ImGui::CollapsingHeader("Network", ImGuiTreeNodeFlags_DefaultOpen)) + ImGui::Spacing(); + header("Network"); { OptionCheckbox("Broadband Adapter Emulation", config::EmulateBBA, "Emulate the Ethernet Broadband Adapter (BBA) instead of the Modem"); @@ -1562,7 +1581,8 @@ static void gui_display_settings() } } } - if (ImGui::CollapsingHeader("Other", ImGuiTreeNodeFlags_DefaultOpen)) + ImGui::Spacing(); + header("Other"); { OptionCheckbox("HLE BIOS", config::UseReios, "Force high-level BIOS emulation"); OptionCheckbox("Force Windows CE", config::ForceWindowsCE, @@ -1592,13 +1612,14 @@ static void gui_display_settings() if (ImGui::BeginTabItem("About")) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); - if (ImGui::CollapsingHeader("Flycast", ImGuiTreeNodeFlags_DefaultOpen)) + header("Flycast"); { ImGui::Text("Version: %s", GIT_VERSION); ImGui::Text("Git Hash: %s", GIT_HASH); ImGui::Text("Build Date: %s", BUILD_DATE); } - if (ImGui::CollapsingHeader("Platform", ImGuiTreeNodeFlags_DefaultOpen)) + ImGui::Spacing(); + header("Platform"); { ImGui::Text("CPU: %s", #if HOST_CPU == CPU_X86 @@ -1635,24 +1656,21 @@ static void gui_display_settings() #endif ); } + ImGui::Spacing(); if (config::RendererType.isOpenGL()) { - if (ImGui::CollapsingHeader("Open GL", ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::Text("Renderer: %s", (const char *)glGetString(GL_RENDERER)); - ImGui::Text("Version: %s", (const char *)glGetString(GL_VERSION)); - } + header("Open GL"); + ImGui::Text("Renderer: %s", (const char *)glGetString(GL_RENDERER)); + ImGui::Text("Version: %s", (const char *)glGetString(GL_VERSION)); } #ifdef USE_VULKAN else { - if (ImGui::CollapsingHeader("Vulkan", ImGuiTreeNodeFlags_DefaultOpen)) - { - std::string name = VulkanContext::Instance()->GetDriverName(); - ImGui::Text("Driver Name: %s", name.c_str()); - std::string version = VulkanContext::Instance()->GetDriverVersion(); - ImGui::Text("Version: %s", version.c_str()); - } + header("Vulkan"); + std::string name = VulkanContext::Instance()->GetDriverName(); + ImGui::Text("Driver Name: %s", name.c_str()); + std::string version = VulkanContext::Instance()->GetDriverVersion(); + ImGui::Text("Version: %s", version.c_str()); } #endif @@ -1670,8 +1688,8 @@ static void gui_display_settings() } ImGui::PopStyleVar(); - ImVec2 mouse_delta = ImGui::GetIO().MouseDelta; - ScrollWhenDraggingOnVoid(ImVec2(0.0f, -mouse_delta.y), ImGuiMouseButton_Left); + scrollWhenDraggingOnVoid(); + windowDragScroll(); ImGui::End(); ImGui::PopStyleVar(); @@ -1703,8 +1721,7 @@ inline static void gui_display_demo() static void gui_display_content() { - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); + fullScreenWindow(false); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); @@ -1786,6 +1803,7 @@ static void gui_display_content() } ImGui::PopStyleVar(); } + windowDragScroll(); ImGui::EndChild(); ImGui::End(); ImGui::PopStyleVar(); diff --git a/core/rend/gui_util.cpp b/core/rend/gui_util.cpp index 48f5ede0f..46fb152db 100644 --- a/core/rend/gui_util.cpp +++ b/core/rend/gui_util.cpp @@ -27,6 +27,7 @@ #include "oslib/directory.h" #include "imgui/imgui.h" #include "imgui/imgui_internal.h" +#include "hw/maple/maple_devs.h" static std::string select_current_directory; static std::vector subfolders; @@ -41,6 +42,8 @@ static const std::string native_separator = "/"; #endif #define PSEUDO_ROOT ":" +extern int insetLeft, insetRight, insetTop, insetBottom; + void select_file_popup(const char *prompt, StringCallback callback, bool selectFile, const std::string& selectExtension) { @@ -77,8 +80,7 @@ void select_file_popup(const char *prompt, StringCallback callback, } } - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); + fullScreenWindow(true); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); if (ImGui::BeginPopupModal(prompt, NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize )) @@ -280,6 +282,9 @@ void select_file_popup(const char *prompt, StringCallback callback, } ImGui::PopStyleColor(); + scrollWhenDraggingOnVoid(); + windowDragScroll(); + ImGui::PopStyleVar(); ImGui::EndChild(); if (!selectFile) @@ -305,20 +310,24 @@ void select_file_popup(const char *prompt, StringCallback callback, } // See https://github.com/ocornut/imgui/issues/3379 -void ScrollWhenDraggingOnVoid(const ImVec2& delta, ImGuiMouseButton mouse_button) +void scrollWhenDraggingOnVoid(ImGuiMouseButton mouse_button) { ImGuiContext& g = *ImGui::GetCurrentContext(); ImGuiWindow* window = g.CurrentWindow; + while ((window->Flags & ImGuiWindowFlags_ChildWindow) && window->ScrollMax.x == 0.0f && window->ScrollMax.y == 0.0f) + window = window->ParentWindow; bool hovered = false; bool held = false; ImGuiButtonFlags button_flags = (mouse_button == ImGuiMouseButton_Left) ? ImGuiButtonFlags_MouseButtonLeft : (mouse_button == ImGuiMouseButton_Right) ? ImGuiButtonFlags_MouseButtonRight : ImGuiButtonFlags_MouseButtonMiddle; if (g.HoveredId == 0) // If nothing hovered so far in the frame (not same as IsAnyItemHovered()!) ImGui::ButtonBehavior(window->Rect(), window->GetID("##scrolldraggingoverlay"), &hovered, &held, button_flags); - if (held && delta.x != 0.0f) - ImGui::SetScrollX(window, window->Scroll.x + delta.x); - if (held && delta.y != 0.0f) - ImGui::SetScrollY(window, window->Scroll.y + delta.y); + const ImVec2& delta = ImGui::GetIO().MouseDelta; + if (held && delta != ImVec2()) + { + window->DragScrolling = true; + window->ScrollSpeed = delta; + } } static void UnpackAccumulativeOffsetsIntoRanges(int base_codepoint, const short* accumulative_offsets, int accumulative_offsets_count, ImWchar* out_ranges) @@ -635,6 +644,44 @@ bool OptionSlider(const char *name, config::Option& option, int min, int ma return valueChanged; } +bool OptionArrowButtons(const char *name, config::Option& option, int min, int max, const char *help) +{ + const float innerSpacing = ImGui::GetStyle().ItemInnerSpacing.x; + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.f, 0.5f)); // Left + ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_FrameBg]); + float width = ImGui::CalcItemWidth() - innerSpacing * 2.0f - ImGui::GetFrameHeight() * 2.0f; + ImGui::ButtonEx(std::to_string((int)option).c_str(), ImVec2(width, 0), ImGuiButtonFlags_Disabled); + ImGui::PopStyleColor(); + ImGui::PopStyleVar(); + + ImGui::SameLine(0.0f, innerSpacing); + ImGui::PushButtonRepeat(true); + bool valueChanged = false; + if (option.isReadOnly()) + { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); + } + std::string id = "##" + std::string(name); + if (ImGui::ArrowButton((id + "left").c_str(), ImGuiDir_Left)) { option.set(std::max(min, option - 1)); valueChanged = true; } + ImGui::SameLine(0.0f, innerSpacing); + if (ImGui::ArrowButton((id + "right").c_str(), ImGuiDir_Right)) { option.set(std::min(max, option + 1)); valueChanged = true; } + if (option.isReadOnly()) + { + ImGui::PopItemFlag(); + ImGui::PopStyleVar(); + } + ImGui::PopButtonRepeat(); + ImGui::SameLine(0.0f, innerSpacing); + ImGui::Text("%s", name); + if (help != nullptr) + { + ImGui::SameLine(); + ShowHelpMarker(help); + } + return valueChanged; +} + template bool OptionRadioButton(const char *name, config::Option& option, T value, const char *help) { @@ -693,3 +740,91 @@ void OptionComboBox(const char *name, config::Option& option, const char *v ShowHelpMarker(help); } } + +void fullScreenWindow(bool modal) +{ + if (!modal) + { + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); + + if (insetLeft > 0) + { + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(ImVec2(insetLeft, ImGui::GetIO().DisplaySize.y)); + ImGui::Begin("##insetLeft", NULL, ImGuiWindowFlags_NoDecoration); + ImGui::End(); + } + if (insetRight > 0) + { + ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - insetRight, 0)); + ImGui::SetNextWindowSize(ImVec2(insetRight, ImGui::GetIO().DisplaySize.y)); + ImGui::Begin("##insetRight", NULL, ImGuiWindowFlags_NoDecoration); + ImGui::End(); + } + if (insetTop > 0) + { + ImGui::SetNextWindowPos(ImVec2(0, 0)); + ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, insetTop)); + ImGui::Begin("##insetTop", NULL, ImGuiWindowFlags_NoDecoration); + ImGui::End(); + } + if (insetBottom > 0) + { + ImGui::SetNextWindowPos(ImVec2(0, ImGui::GetIO().DisplaySize.y - insetBottom)); + ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, insetBottom)); + ImGui::Begin("##insetBottom", NULL, ImGuiWindowFlags_NoDecoration); + ImGui::End(); + } + ImGui::PopStyleVar(2); + } + ImGui::SetNextWindowPos(ImVec2(insetLeft, insetTop)); + ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x - insetLeft - insetRight, ImGui::GetIO().DisplaySize.y - insetTop - insetBottom)); +} + +static void computeScrollSpeed(float &v) +{ + constexpr float friction = 3.f; + if (std::abs(v) > friction) + { + float sign = (v > 0.f) - (v < 0.f); + v -= friction * sign; + } + else + { + v = 0.f; + } +} + +void windowDragScroll() +{ + ImGuiWindow *window = ImGui::GetCurrentWindow(); + if (window->DragScrolling) + { + if (!ImGui::GetIO().MouseDown[ImGuiMouseButton_Left]) + { + computeScrollSpeed(window->ScrollSpeed.x); + computeScrollSpeed(window->ScrollSpeed.y); + if (window->ScrollSpeed == ImVec2()) + { + window->DragScrolling = false; + // FIXME we should really move the mouse off-screen after a touch up and this wouldn't be necessary + // the only problem is tool tips + mo_x_phy = -1; + mo_y_phy = -1; + } + } + else + { + ImVec2 delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left); + if (delta != ImVec2()) + ImGui::ResetMouseDragDelta(); + window->ScrollSpeed = delta; + } + if (window->DragScrolling) + { + ImGui::SetScrollX(window, window->Scroll.x - window->ScrollSpeed.x); + ImGui::SetScrollY(window, window->Scroll.y - window->ScrollSpeed.y); + } + } +} diff --git a/core/rend/gui_util.h b/core/rend/gui_util.h index e148c70dc..d186ee932 100644 --- a/core/rend/gui_util.h +++ b/core/rend/gui_util.h @@ -44,7 +44,7 @@ static inline void ImGui_impl_RenderDrawData(ImDrawData *draw_data) ImGui_ImplOpenGL3_RenderDrawData(draw_data); } -void ScrollWhenDraggingOnVoid(const ImVec2& delta, ImGuiMouseButton mouse_button); +void scrollWhenDraggingOnVoid(ImGuiMouseButton mouse_button = ImGuiMouseButton_Left); IMGUI_API const ImWchar* GetGlyphRangesChineseSimplifiedOfficial();// Default + Half-Width + Japanese Hiragana/Katakana + set of 7800 CJK Unified Ideographs from General Standard Chinese Characters IMGUI_API const ImWchar* GetGlyphRangesChineseTraditionalOfficial();// Default + Half-Width + Japanese Hiragana/Katakana + set of 4700 CJK Unified Ideographs from Hong Kong's List of Graphemes of Commonly-Used Chinese Characters @@ -58,9 +58,23 @@ template bool OptionRadioButton(const char *name, config::Option& option, T value, const char *help = nullptr); void OptionComboBox(const char *name, config::Option& option, const char *values[], int count, const char *help = nullptr); +bool OptionArrowButtons(const char *name, config::Option& option, int min, int max, const char *help = nullptr); static inline void centerNextWindow() { ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x / 2.f, ImGui::GetIO().DisplaySize.y / 2.f), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); } + +static inline bool operator==(const ImVec2& l, const ImVec2& r) +{ + return l.x == r.x && l.y == r.y; +} + +static inline bool operator!=(const ImVec2& l, const ImVec2& r) +{ + return !(l == r); +} + +void fullScreenWindow(bool modal); +void windowDragScroll(); diff --git a/core/rend/vulkan/imgui_impl_vulkan.cpp b/core/rend/vulkan/imgui_impl_vulkan.cpp index 2148d3139..83df6e62f 100644 --- a/core/rend/vulkan/imgui_impl_vulkan.cpp +++ b/core/rend/vulkan/imgui_impl_vulkan.cpp @@ -199,8 +199,6 @@ static void CreateOrResizeBuffer(VkBuffer& buffer, VkDeviceMemory& buffer_memory p_buffer_size = new_size; } -extern int insetLeft, insetTop; // Android notches - // Render function // (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer) @@ -267,8 +265,8 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm // Setup viewport: { VkViewport viewport; - viewport.x = insetLeft; - viewport.y = insetTop; + viewport.x = 0; + viewport.y = 0; viewport.width = draw_data->DisplaySize.x; viewport.height = draw_data->DisplaySize.y; viewport.minDepth = 0.0f; @@ -308,12 +306,8 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm // Apply scissor/clipping rectangle // FIXME: We could clamp width/height based on clamped min/max values. VkRect2D scissor; - scissor.offset.x = (int32_t)(pcmd->ClipRect.x - display_pos.x + insetLeft) > 0 - ? (int32_t)(pcmd->ClipRect.x - display_pos.x + insetLeft) - : 0; - scissor.offset.y = (int32_t)(pcmd->ClipRect.y - display_pos.y + insetTop) > 0 - ? (int32_t)(pcmd->ClipRect.y - display_pos.y + insetTop) - : 0; + scissor.offset.x = (int32_t)(pcmd->ClipRect.x - display_pos.x) > 0 ? (int32_t)(pcmd->ClipRect.x - display_pos.x) : 0; + scissor.offset.y = (int32_t)(pcmd->ClipRect.y - display_pos.y) > 0 ? (int32_t)(pcmd->ClipRect.y - display_pos.y) : 0; scissor.extent.width = (uint32_t)(pcmd->ClipRect.z - pcmd->ClipRect.x); scissor.extent.height = (uint32_t)(pcmd->ClipRect.w - pcmd->ClipRect.y + 1); // FIXME: Why +1 here? vkCmdSetScissor(command_buffer, 0, 1, &scissor);