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);