imgui icon indicating copy to clipboard operation
imgui copied to clipboard

Scrolling gradient

Open Lukylix opened this issue 2 years ago • 2 comments

Version/Branch of Dear ImGui:

Version: 1.88 Branch: master

Back-end/Renderer/Compiler/OS

Back-ends: imgui_impl_glfw.cpp + imgui_impl_opengl3.cpp Operating System: Linux Debian based (Pop_OS)

My Issue/Question:

Firstly thanks for your amazing work.

I am trying to do an effect that adds a linear gradient when you can scroll to the direction. But the child window order of render is tricking me. My assumption was that child windows will be rendered like any other widget, but a child window is rendered on top of everything. Is there a way to have scroll behavior without child window? Or is there a way to change the child windows order of rendering? If you could give me a hint on where to start looking this would be great ...

Expected result: In all exemples text should be render under the gradient since he is draw just before EndChild();

I apologize for my bad English and for the confusing triple title.

Please dont #1586 me i try my best:

  • This will not be a cheat only a skin changer it doesn't affect other users expérience or give any other advantage
  • This is not a heavy modified version of imgui and it could profit to others ( the only changes that i have made are inside imgui_custom_widgets and ofc main.cpp)
  • I am not trying to hide the fact that i am using Imgui (This will be a opensource project)

Screenshots Minimal exemple image Full exemple image

Standalone, minimal, complete and verifiable example: main.cpp

#include "imgui_custom_widgets.h"

//...

ImGui::StyleColorsDark();
ImGuiStyle *style = &ImGui::GetStyle();
ImVec4 *colors = style->Colors;
colors[ImGuiCol_ChildBg] = ImVec4(0.149f, 0.156f, 0.180f, 1.00f);

//...

ImGui::Begin("A Window", __null, ImGuiWindowFlags_NoTitleBar);
{
  ImGui::Text("Some title or custom menu");
  ImGui::BeginScrollChild("Main content", ImVec2(0, 0), false, ImGuiWindowFlags_NoScrollbar);
  {
    ImGui::BeginScrollChild("Sub content", ImVec2(ImGui::GetContentRegionAvail().x - style->WindowPadding.x * 2, 100));
    ImGui::Text("Hello SubContent");
    ImGui::EndScrollChild();
  }
  ImGui::EndScrollChild();
}
ImGui::End();

//...

imgui_custom_widgets.cpp

#include "imgui.h"
#include "imgui_internal.h"

//...


// Max nested level of BeginScrollChild() is 2
static ImVec2 s_startChildPos[2];
static int s_startChildIndex = -1;

static ImVec4 CLighten(ImVec4 color, int inAmount)
{
  return ImVec4(
      ImMin(1.0f, color.x + 0.01f * inAmount),
      ImMin(1.0f, color.y + 0.01f * inAmount),
      ImMin(1.0f, color.z + 0.01f * inAmount),
      color.w);
}

static ImVec4 CDarken(ImVec4 color, int inAmount)
{
  return ImVec4(
      ImMax(0.0f, color.x - 0.01f * inAmount),
      ImMax(0.0f, color.y - 0.01f * inAmount),
      ImMax(0.0f, color.z - 0.01f * inAmount),
      color.w);
}

static ImVec4 CAlpha(ImVec4 color, float inAlpha)
{
  return ImVec4(color.x, color.y, color.z, inAlpha);
}

//...

namespace ImGui
{

  //...

  void BeginScrollChild(const char *str_id, const ImVec2 &size_arg, bool border, ImGuiWindowFlags extra_flags)
  {
    ImGui::BeginChild(str_id, size_arg, border, extra_flags);
    s_startChildIndex++;
    s_startChildPos[s_startChildIndex] = ImGui::GetCursorScreenPos();
  }
  void EndScrollChild()
  {
    ImGuiContext &g = *GImGui;
    const ImGuiStyle &style = g.Style;
    const ImVec4 *colors = style.Colors;
    const float scrollY = ImGui::GetScrollY();
    const float gradiantSize = 15.0f;
    int columnsCount = ImGui::GetColumnsCount();
    if (columnsCount == 0)
      columnsCount = 1;
    if (scrollY > 0)
    {
      const ImVec2 minGradiant = ImVec2(s_startChildPos[s_startChildIndex].x, s_startChildPos[s_startChildIndex].y + scrollY);
      const ImVec2 maxGradiant = ImVec2(s_startChildPos[s_startChildIndex].x + ImGui::GetWindowWidth(), s_startChildPos[s_startChildIndex].y + scrollY + gradiantSize);
      for (size_t i = 0; i < columnsCount; i++)
      {
        ImGui::GetWindowDrawList()->AddRectFilledMultiColor(minGradiant, maxGradiant, ImColor(colors[ImGuiCol_WindowBg]), ImColor(colors[ImGuiCol_WindowBg]), ImColor(CAlpha(colors[ImGuiCol_WindowBg], 0)), ImColor(CAlpha(colors[ImGuiCol_WindowBg], 0)));
        ImGui::GetWindowDrawList()->AddRect(minGradiant, maxGradiant, ImColor(255, 0, 0));
        ImGui::NextColumn();
      }
    }
    if (scrollY < ImGui::GetScrollMaxY())
    {
      const float childHeight = ImGui::GetWindowHeight();
      const ImVec2 minGradiant = ImVec2(s_startChildPos[s_startChildIndex].x, s_startChildPos[s_startChildIndex].y + childHeight + scrollY - gradiantSize);
      const ImVec2 maxGradiant = ImVec2(s_startChildPos[s_startChildIndex].x + ImGui::GetWindowWidth(), s_startChildPos[s_startChildIndex].y + scrollY + childHeight);
      for (size_t i = 0; i < columnsCount; i++)
      {

        ImGui::GetWindowDrawList()->AddRectFilledMultiColor(minGradiant, maxGradiant, ImColor(CAlpha(colors[ImGuiCol_WindowBg], 0)), ImColor(CAlpha(colors[ImGuiCol_WindowBg], 0)), ImColor(colors[ImGuiCol_WindowBg]), ImColor(colors[ImGuiCol_WindowBg]));
        ImGui::GetWindowDrawList()->AddRect(minGradiant, maxGradiant, ImColor(255, 0, 0));
        ImGui::NextColumn();
      }
    }

    ImGui::EndChild();
    s_startChildIndex--;
  }
}

imgui_custom_widgets.h

#pragma once
#include "imgui.h"
namespace ImGui
{
  //...
  void BeginScrollChild(const char *str_id, const ImVec2 &size_arg = ImVec2(0, 0), bool border = false, ImGuiWindowFlags extra_flags = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollbar);
  void EndScrollChild();
}

Lukylix avatar Jul 10 '22 17:07 Lukylix

https://github.com/ocornut/imgui/issues/1586

rxvan avatar Jul 11 '22 00:07 rxvan

I am not sure what your problem is.

I am trying to do an effect that adds a linear gradient when you can scroll to the direction. But the child window order of render is tricking me. My assumption was that child windows will be rendered like any other widget, but a child window is rendered on top of everything.

A child window is rendered over its parent window.

Is there a way to have scroll behavior without child window?

No.

Or is there a way to change the child windows order of rendering? If you could give me a hint on where to start looking this would be great ...

I don't think you need to change that. Your logic of rendering before EndChild() is sound and should work. Your code appears to be complicated so I don't know what the issue is (you probably have a bug) but the generic logic should work as long as it is rendered before the last call of EndChild() in the case where a same child is appended twice.

I tried it and it worked:

void RenderScrollingGradients(const ImRect& rect, float thickness)
{
    ImGuiContext& g = *GImGui;
    ImGuiWindow* window = g.CurrentWindow;
    ImVec2 rect_center = rect.GetCenter();
    ImRect rect_top(rect.Min.x, rect.Min.y, rect.Max.x, ImMin(rect.Min.y + thickness, rect_center.y));
    ImRect rect_bot(rect.Min.x, ImMax(rect.Max.y - thickness, rect_center.y), rect.Max.x, rect.Max.y);
    if (window->Scroll.y > 0.0f)
        window->DrawList->AddRectFilledMultiColor(rect_top.Min, rect_top.Max, IM_COL32_BLACK, IM_COL32_BLACK, IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS);
    if (window->Scroll.y < window->ScrollMax.y)
        window->DrawList->AddRectFilledMultiColor(rect_bot.Min, rect_bot.Max, IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK);
}

Usage

RenderScrollingGradients(window->InnerRect, g.FontSize * 2.0f);
ImGui::EndChild();

image

ocornut avatar Jul 12 '22 10:07 ocornut