Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rotated Text Example #6923

Closed
dgm3333 opened this issue Oct 14, 2023 · 1 comment
Closed

Rotated Text Example #6923

dgm3333 opened this issue Oct 14, 2023 · 1 comment

Comments

@dgm3333
Copy link

dgm3333 commented Oct 14, 2023

In case its of any use to anyone
NB there is a bug in this code:
Imgui clips the text before it is rotated. This means text at the edges can have parts missing
(as you can see in this image with the text at the right: epezent/implot#523).

2023-10-14.10-46-41.mp4

To run the example just call from the main imgui render loop
drawRotatedText()

//Initial code for this comes from: https://gist.github.com/carasuca/e72aacadcf6cf8139de46f97158f790f
//  It doesn't seem to have a license statement, so I'm assuming it's public domain

#include "imgui_internal.h"
//internal bounding boxes
// BB = outermost 'true' bounding box (including bounding box for handles)
// DR = bounding box for drawing
// IN = inner (ie inside bufferzone for boundaries handles
// CE = centron (prob central 10%)
// TL...B = bb for handles for grabbing the various edges/corners
enum bbEnum { BB_BB, BB_DR, BB_IN, BB_CE, BB_TL, BB_TR, BB_BL, BB_BR, BB_L, BB_R, BB_T, BB_B };

int rotation_start_index;
void ImRotateStart()
{
    rotation_start_index = ImGui::GetWindowDrawList()->VtxBuffer.Size;
}

ImVec2 ImRotationCenter()
{
    ImVec2 l(FLT_MAX, FLT_MAX), u(-FLT_MAX, -FLT_MAX); // bounds

    const auto& buf = ImGui::GetWindowDrawList()->VtxBuffer;
    for (int i = rotation_start_index; i < buf.Size; i++)
        l = ImMin(l, buf[i].pos), u = ImMax(u, buf[i].pos);

    return ImVec2((l.x + u.x) / 2, (l.y + u.y) / 2); // or use _ClipRectStack?
}

void ImRotateEnd(float rad, ImVec2 center = ImRotationCenter())
{
    // Adjust the angle to match the standard horizontal orientation
    rad -= IM_PI / 2.0f; // Subtract 90 degrees (PI/2 radians)

    float s = sin(rad), c = cos(rad);
    center = ImRotate(center, s, c) - center;

    auto& buf = ImGui::GetWindowDrawList()->VtxBuffer;
    for (int i = rotation_start_index; i < buf.Size; i++)
        buf[i].pos = ImRotate(buf[i].pos, s, c) - center;
}

void drawRotatedText(std::string textToRotate, float angleToRotate = 0, bbEnum rotationCentre = BB_CE, bool isRads = true)
{
    // Calculate rotation angle in radians
    float rad = angleToRotate;
    if (!isRads)
		rad = rad * IM_PI / 180.0f;

    ImVec2 textStartPosition = ImGui::GetWindowPos() + ImGui::GetCursorPos();           // ImRotateEnd uses GetWindowDrawList which is based on position of the screen

    // Calculate text size to determine bounding box
    ImVec2 textSize = ImGui::CalcTextSize(textToRotate.c_str());

    ImVec2 rotationCenter;

    // Calculate rotation center based on bbEnum value
    switch (rotationCentre)
    {
    case BB_CE:
        rotationCenter = ImVec2(textStartPosition.x + textSize.x * 0.5f, textStartPosition.y + textSize.y * 0.5f);
        break;
    case BB_TL:
        rotationCenter = textStartPosition;
        break;
    case BB_TR:
        rotationCenter = ImVec2(textStartPosition.x + textSize.x, textStartPosition.y);
        break;
    case BB_BL:
        rotationCenter = ImVec2(textStartPosition.x, textStartPosition.y + textSize.y);
        break;
    case BB_BR:
        rotationCenter = ImVec2(textStartPosition.x + textSize.x, textStartPosition.y + textSize.y);
        break;
    case BB_L:
        rotationCenter = ImVec2(textStartPosition.x, textStartPosition.y + textSize.y / 2);
        break;
    case BB_R:
        rotationCenter = ImVec2(textStartPosition.x + textSize.x, textStartPosition.y + textSize.y / 2);
        break;
    case BB_T:
        rotationCenter = ImVec2(textStartPosition.x + textSize.x / 2, textStartPosition.y);
        break;
    case BB_B:
        rotationCenter = ImVec2(textStartPosition.x + textSize.x / 2, textStartPosition.y + textSize.y);
        break;
    default:
        std::cout << "ERROR: invalid rotationCentre in drawRotatedText\n";
        return;
    }

    // Start rotation
    ImRotateStart();

    // Render the text
    ImGui::Text(textToRotate.c_str());

    // Apply the rotation
    ImRotateEnd(rad, rotationCenter);
}

void drawBbEnumExamples(float rotationAngle)
{
    // Create a child window
    ImGui::BeginChild("BbEnumExamplesChild", ImVec2(500, 500), true);


    // Base coordinates for the middle point of our layout area
    const float midX = 250.0f;
    const float midY = 250.0f;

    const float xOffset = 110.0f;  // Horizontal distance from center
    const float yOffset = 110.0f;  // Vertical distance from center

    // Calculate starting positions for each enum based on their logical position
    ImVec2 positions[12];
    positions[BB_TL] = ImVec2(midX - xOffset, midY - yOffset);
    positions[BB_T] = ImVec2(midX, midY - yOffset);
    positions[BB_TR] = ImVec2(midX + xOffset, midY - yOffset);
    positions[BB_L] = ImVec2(midX - xOffset, midY);
    positions[BB_CE] = ImVec2(midX, midY);
    positions[BB_R] = ImVec2(midX + xOffset, midY);
    positions[BB_BL] = ImVec2(midX - xOffset, midY + yOffset);
    positions[BB_B] = ImVec2(midX, midY + yOffset);
    positions[BB_BR] = ImVec2(midX + xOffset, midY + yOffset);

    // Iterate over each bbEnum and display the text and its rotated version
    for (int i = 0; i < 12; i++)
    {
        // Determine the cursor position based on the current index
        ImGui::SetCursorPos(positions[i]);

        // Get the enum name as a string
        std::string enumName = "";
        switch ((bbEnum)i)
        {
        case BB_CE: enumName = "BB_CE"; break;
        case BB_TL: enumName = "BB_TL"; break;
        case BB_TR: enumName = "BB_TR"; break;
        case BB_BL: enumName = "BB_BL"; break;
        case BB_BR: enumName = "BB_BR"; break;
        case BB_L:  enumName = "BB_L";  break;
        case BB_R:  enumName = "BB_R";  break;
        case BB_T:  enumName = "BB_T";  break;
        case BB_B:  enumName = "BB_B";  break;
        default: continue;
        }


        // Adjust cursor position for the rotated text based on text size
        ImVec2 textSize = ImGui::CalcTextSize(enumName.c_str());
        ImVec2 halftextSize = textSize * 0.5f;

        ImVec2 cursorPos = positions[i] - halftextSize;

        ImGui::SetCursorPos(cursorPos);

        // Draw the unrotated text
        ImGui::Text(enumName.c_str());

        ImGui::SetCursorPos(cursorPos);

        // Draw the rotated version
        drawRotatedText(enumName, rotationAngle, (bbEnum)i);
    }

    ImGui::EndChild();
}

void drawRotatedText() {

    ImGui::Begin("Rotated Text Example");
    // Create a child window
    ImGui::BeginChild("RotatedTextChild", ImVec2(-1, -1));

    drawBbEnumExamples(0.0005f * ::GetTickCount());



    /*
    ImGui::SetCursorPos({ 100, 100 });

    ImRotateStart();
    ImGui::Text(__FUNCTION__);
    ImRotateEnd(0.0005f * ::GetTickCount());

    ImRotateStart();
    ImGui::SameLine();
    ImGui::Text("Freeze On Hover");
    static float rot = 0;
    if (!ImGui::IsItemHovered()) {
        rot = 0.0009f * ::GetTickCount();
    }
    ImRotateEnd(rot);

    ImVec2 winPos = ImGui::GetWindowPos();
    ImVec2 mPos = ImGui::GetMousePos();
    float halfWidth = ImGui::CalcTextSize("mouse Rotation Test").x / 2;
    ImVec2 cPos = mPos - winPos;
    cPos.x -= halfWidth;
    //cPos.y += halfWidth;
    ImGui::SetCursorPos(cPos);
    ImRotateStart();
    ImGui::Text("mouse Rotation Test");
    ImRotateEnd(0.002f * ::GetTickCount());
    */


    ImGui::EndChild();
    ImGui::End();
}

@ocornut
Copy link
Owner

ocornut commented Oct 15, 2023

Hello,

This seems a little complicated and could be reduced to a single call.
Pivot could be expressed as a normalized ImVec2 (0.5,05 for center, etc.).

I very recently added a helper ImGui::ShadeVertsTransformPos() as I needed it for #6917
The function is:

void ImGui::ShadeVertsTransformPos(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, const ImVec2& pivot_in, float cos_a, float sin_a, const ImVec2& pivot_out)
{
    ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx;
    ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx;
    for (ImDrawVert* vertex = vert_start; vertex < vert_end; ++vertex)
        vertex->pos = ImRotate(vertex->pos- pivot_in, cos_a, sin_a) + pivot_out;
}

It's used in TableAngledHeadersRowEx():

int vtx_idx_begin = draw_list->_VtxCurrentIdx;
RenderText......
//if (g.IO.KeyShift) { draw_list->AddRect(label_r.Min, label_r.Max, IM_COL32(0, 255, 0, 255), 0.0f, 0, 2.0f); }
int vtx_idx_end = draw_list->_VtxCurrentIdx;

// Rotate and offset label
ImVec2 pivot_in = label_r.GetBL();
ImVec2 pivot_out = ImVec2(column->WorkMinX, row_r.Max.y) + (flip_label ? (unit_right * clip_width) : ImVec2(header_height, 0.0f));
ShadeVertsTransformPos(draw_list, vtx_idx_begin, vtx_idx_end, pivot_in, label_cos_a, label_sin_a, pivot_out); // Rotate and offset

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants