imgui icon indicating copy to clipboard operation
imgui copied to clipboard

Table/grid range select

Open tasiek30 opened this issue 2 years ago • 20 comments

Version/Branch of Dear ImGui:

Version 1.90.1, Branch: docking

Back-ends:

imgui_impl_win32.cpp + imgui_impl_opengl3.cpp

Compiler, OS:

Windows 11 + GCC 12.2.0

Full config/build information:

No response

Details:

I have question what is best method to implement range select in table.

I have to implement table with scalar input. In this table I need to be able to select an area and change the values ​​in all selected cells. Also I have to know which cells have been changed.

At this moment i pass to my function vectors that hold cell state.

Problems:

  • Current selection method need to hold each cell flag, and cell background is set in next loop iteration
  • Selection is not limited to only one table (video)
  • After i.e. change window size keyboard focus is lost

Maybe someone have some ideas how to improve my implementation?

Screenshots/Video:

https://github.com/ocornut/imgui/assets/34631947/21a44096-01c2-4284-942c-0020d9a3ee94

Minimal, Complete and Verifiable Example code:

static ImVec2 sel_start, sel_end;
static bool selActive;

static bool SelectionRect(ImVec2& start_pos, ImVec2& end_pos, ImGuiMouseButton mouse_button)
{
    //IM_ASSERT(start_pos != NULL);
    //IM_ASSERT(end_pos != NULL);
    bool selecting = false;
    if (ImGui::IsMouseClicked(mouse_button))
        start_pos = ImGui::GetMousePos();
    if (ImGui::IsMouseDown(mouse_button)) {
        selecting = true;
        end_pos = ImGui::GetMousePos();
        ImDrawList* draw_list = ImGui::GetForegroundDrawList(); //ImGui::GetWindowDrawList();
        draw_list->AddRect(start_pos, end_pos, ImGui::GetColorU32(IM_COL32(0, 130, 216, 255)));   // Border
        draw_list->AddRectFilled(start_pos, end_pos, ImGui::GetColorU32(IM_COL32(0, 130, 216, 50)));    // Background
    }
    //return ImGui::IsMouseReleased(mouse_button);
    return selecting;
}

static bool isSelected(ImVec2& sel_min, ImVec2& sel_max, ImVec2& item_min, ImVec2& item_max ) {
    ImVec2 tmp_min = sel_min;
    ImVec2 tmp_max = sel_max;

    if(tmp_min.x > tmp_max.x) {
        float x_min = tmp_min.x;
        float x_max = tmp_max.x;
        tmp_min.x = x_max;
        tmp_max.x = x_min;
    }

    if(tmp_min.y > tmp_max.y) {
        float y_min = tmp_min.y;
        float y_max = tmp_max.y;
        tmp_min.y = y_max;
        tmp_max.y = y_min;
    }

    if(    tmp_min.x < item_max.x
        && tmp_min.y < item_max.y
        && tmp_max.x > item_min.x
        && tmp_max.y > item_min.y) {
        return true;
    }
    else {

    }
    return false;
}
void * getter(void * base, int index, int stride) {
    return base+(index*stride);
}
bool GUI::wdgMapNew(const char* label,
        int sizeX,
        int sizeY,
        float* valX,
        float* valY,
        float* valV,
        bool* selected,
        bool* edited,
        int lenX,
        int lenY,
        int stride,
        const char* formatX,
        const char* formatY,
        const char* formatV) {

    bool isEdited = false;

    bool firstCellFlag = false;
    bool setSelectionFlag = false;
    float toSet = 0.0f;

    ImGuiTableFlags flags =
                    ImGuiTableFlags_Borders | ImGuiTableFlags_NoPadInnerX | ImGuiTableFlags_NoPadOuterX;
                    //ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY |
                    //ImGuiTableFlags_NoHostExtendX | ImGuiTableFlags_NoHostExtendY;
    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
    ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 0));

    if ( ImGui::BeginTable(label, sizeX+1, flags) ) {
        //bool selActive = SelectionRect(sel_start, sel_end);


        ImGui::PushID(label);
        ImGui::TableNextColumn(); // first empty, could be i.e. units? bar/rpm

        for(int x=0; x<sizeX; x++) {
            if(x >= lenX) {
                break;
            }
            ImGui::TableNextColumn();
            ImGui::PushID(x);
            ImGui::SetNextItemWidth(-FLT_MIN);
            ImGui::InputScalar("##AxisX", ImGuiDataType_Float, getter(valX, x, stride), NULL, NULL, formatX);
            ImGui::PopID();
        }

        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyleColorVec4(ImGuiCol_ChildBg));
        for(int y=0; y<sizeY; y++) {
            ImGui::TableNextRow();
            ImGui::TableNextColumn();

            if(y < lenY) {
                ImGui::PushID(y);
                ImGui::SetNextItemWidth(-FLT_MIN);
                ImGui::InputScalar("##AxisY", ImGuiDataType_Float, getter(valY, y, stride), NULL, NULL, formatY);
                ImGui::PopID();
            }

            for(int x=0; x<sizeX; x++) {
                int index = y*lenX + x;
                if(index >= lenX*lenY) {
                    break;
                }
                ImGui::TableNextColumn();
                ImGui::PushID(index);
                float prev = *(float*)getter(valV, index, stride);
                bool * pIsSelected = (bool*)getter(selected, index, stride);
                if(*pIsSelected) {
                    ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 130, 216, 50));

                    if(!firstCellFlag) {
                        firstCellFlag = true;
                        if(selActive) {
                            ImGui::SetKeyboardFocusHere();
                        }
                    }
                    if(setSelectionFlag) {
                        *(float*)getter(valV, index, stride) = toSet;
                    }
                }

                ImGui::SetNextItemWidth(-FLT_MIN);

                if (ImGui::InputScalar("##Data", ImGuiDataType_Float, getter(valV, index, stride), NULL, NULL, formatV, ImGuiInputTextFlags_EnterReturnsTrue) ) {
                    // on enter edit all other values
                    *(bool*)getter(edited, index, stride) = true;
                }
                else {
                    *(bool*)getter(edited, index, stride) = false;
                }

                // catch event when there is no change in value, but i want to set other cells
                bool enterPressed = ImGui::IsKeyPressed(ImGuiKey_Enter) | ImGui::IsKeyPressed(ImGuiKey_KeypadEnter);
                if( enterPressed && ImGui::IsItemDeactivated() ) {
                    setSelectionFlag = true;
                    toSet = *(float*)getter(valV, index, stride);
                    isEdited = true;
                }

                // check if item changed
                if(isEdited) {
                    if(prev != *(float*)getter(valV, index, stride)) {
                        *(bool*)getter(edited, index, stride) = true;
                    }
                    else {
                        *(bool*)getter(edited, index, stride) = false;
                    }
                }
                if(*pIsSelected) {
                    ImGui::PopStyleColor();
                }
                ImVec2 cell_start = ImGui::GetItemRectMin();
                ImVec2 cell_end = ImGui::GetItemRectMax();
                if(selActive) {
                    *pIsSelected = isSelected(sel_start, sel_end, cell_start, cell_end);
                }
                ImGui::PopID();
            }
        }
        ImGui::PopStyleColor();

        ImGui::PopID();

        ImGui::EndTable();

        bool isActive = ImGui::IsItemActive();
        bool isHovered = ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly);
        bool isFocused = ImGui::IsItemFocused();

        if(ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) {
            selActive = SelectionRect(sel_start, sel_end);
        }
        else {
           // selActive = false;
        }

        ImGui::Text("active: %d, hovered: %d, focused: %d, selActive: %d", isActive, isHovered, isFocused, selActive);
    }
    ImGui::PopStyleVar(2);

    return isEdited;
}
typedef struct {
    float val;
    bool isSelected;
    bool isEdited;
}TABLE_CELL;

class TABLE {
public:
    TABLE() {};
    TABLE(unsigned int sizeX, unsigned int sizeY) {
        resize(sizeX, sizeY);
    }
    ~TABLE() {};

    int cellSize(void) { return sizeof(TABLE_CELL); }

    void resize(unsigned int sizeX, unsigned int sizeY) {
        X.resize(sizeX);
        Y.resize(sizeY);
        V.resize(sizeX*sizeY);
    }

public:
    std::vector<TABLE_CELL> X;
    std::vector<TABLE_CELL> Y;
    std::vector<TABLE_CELL> V;
};

static bool wdgTable(const char* label, TABLE &t) {
    return GUI::wdgMapNew(label,
                        t.X.size(),
                        t.Y.size(),
                        &t.X[0].val,
                        &t.Y[0].val,
                        &t.V[0].val,
                        &t.V[0].isSelected,
                        &t.V[0].isEdited,
                        t.X.size(),
                        t.Y.size(),
                        t.cellSize(),
                        "%.0f", "%.0f");
}

tasiek30 avatar Mar 20 '24 07:03 tasiek30

Have you tried the range_select branch to see if it meets your needs?

I know Omar is looking for feedback on it, and in the near future it should be the canonical way to handle what you're doing.

It can be merged into docking with some minor conflict resolution.

PathogenDavid avatar Mar 20 '24 16:03 PathogenDavid

I need couple days to try it :)

tasiek30 avatar Mar 22 '24 07:03 tasiek30

This feature looks very promising. But actually i don't know if there is posibility to select input widgets i.e. InputScalar?. Also i have some strange behavior during rectangle selection (video)

https://github.com/ocornut/imgui/assets/34631947/4b36f793-99bd-4b6c-88f3-d2f1a0914185

    ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_ClearOnClickVoid | ImGuiMultiSelectFlags_BoxSelect2d);

    static ImGuiSelectionBasicStorage selection;
    selection.ApplyRequests(ms_io, 16*16);

    ImGui::Text("Selection: %d/%d", selection.Size, 16*16);
    if ( ImGui::BeginTable("MAP NEW", 16, flags) ) {
        ImGui::PushStyleColor(ImGuiCol_FrameBg, ImGui::GetStyleColorVec4(ImGuiCol_ChildBg));
        for(int y=0; y<16; y++) {
            ImGui::TableNextRow();
            for(int x=0; x<16; x++) {
                ImGui::TableNextColumn();
                ImGui::PushID(y+x*16);
                ImGui::SetNextItemWidth(50.0);

                bool IsSelected = selection.Contains((ImGuiID)(y+x*16));

                ImGui::SetNextItemSelectionUserData(y+x*16);

                if(IsSelected) {
                    ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 130, 216, 50));
                }

                char label[64];
                sprintf(label, "%d", y+x*16);
                ImGui::Selectable(label, IsSelected);
                //ImGui::Text(label);
                //ImGui::Checkbox(label, (bool*)&data[y][x]);
                /*if (ImGui::InputScalar("##Data", ImGuiDataType_Float, &data[y][x], NULL, NULL, "%.2f", ImGuiInputTextFlags_EnterReturnsTrue) ) {
                    dataToSet = data[y][x];
                }*/

                if(IsSelected) {
                    ImGui::PopStyleColor();
                }

                ImGui::PopID();
            }
        }
        ImGui::PopStyleColor();
        ImGui::EndTable();
    }

    ms_io = ImGui::EndMultiSelect();

    selection.ApplyRequests(ms_io, 16*16);

tasiek30 avatar Apr 18 '24 11:04 tasiek30

Thank you for your thoughtful and careful repro, I will investigate it.

An InputScalar() is not selectable but I'm not sure what it would mean to select it. Would the underlying intent to e.g. select many fields and type in all of them together ?

ocornut avatar Apr 18 '24 12:04 ocornut

Note how you are using "y + x * 16" everywhere, meaning your selectables are not submitted in the same sequential order as their value, and by default ImGuiSelectionBasicStorage assume that value passed to ImGui::SetNextItemSelectionUserData() are interpolable indices.

[A] If you instead use:

//int idx = y + x * 16; // Broken
int idx = x + y * 16; // OK

(and change all values to use idx)

Note the value order now goes left-to-right, top-to-bottom: image

Then it works, but note that shift+down/up assume a type of selection that's not necessarily what you want here (I think we may need a flag to make shift+select use 2d coordinates rather than sequential?). That's the case for all three alternatives.

[B] Alternatively, you change change the idx->stored selection id mapping:

selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx)
{
    int x = idx % 16;
    int y = idx / 16;
    return (ImGuiID)(y + x * 16);
};
int idx = x + y * 16; // Submission index
int id = y + x * 16; // ID (== selection.AdapterIndexToStorageId(idx))
//IM_ASSERT(selection.AdapterIndexToStorageId(&selection, idx) == id);
[...]
ImGui::PushID(idx); // <-- here it doesn't matter which you use as long as it is unique
[...]
ImGui::SetNextItemSelectionUserData(idx); // <-- here you pass index
[...]
bool IsSelected = selection.Contains((ImGuiID)(id)); // <-- Stored selection ID, == selection.AdapterIndexToStorageId(idx)

image

[C] A third alternative would to submit items in the same order as the id you want to use. aka fill entire column first, but it may be harder to perform clipping there.

( I also found an issue when using box-select in a window that is not a child window. The current logic prevents focusing, steals hovers and nav id. I pushed a mitigation (a304677) to allow clicking on title bar at least, and will need to revisit some of the logic for box-select. )

This is really useful feedback as I found two things to improve already. Thanks!

ocornut avatar Apr 18 '24 14:04 ocornut

(I also found an issue when using box-select in a window that is not a child window. The current logic prevents focusing, steals hovers and nav id. I pushed a mitigation to allow clicking on title bar at least, and will need to revisit some of the logic for box-select.)

Pushed a better fix d60299d for both ScopeWindow and ScopeRect cases.

ocornut avatar Apr 18 '24 14:04 ocornut

Thank you for your thoughtful and careful repro, I will investigate it.

An InputScalar() is not selectable but I'm not sure what it would mean to select it. Would the underlying intent to e.g. select many fields and type in all of them together ?

Yes exactly. I need to select cells and be able to change them to same value, increase all or even extrapolate (smooth)

tasiek30 avatar Apr 18 '24 15:04 tasiek30

Yes exactly. I need to select cells and be able to change them to same value, increase all or even extrapolate (smooth)

I think in this case it makes sense to display and focus a single InputText() widget, and when edited apply the value to all of selection. Aka you don't need (and you cannot have) multiple active InputText(), but one can represent the selection.

ocornut avatar Apr 19 '24 12:04 ocornut

Is this possible to draw this InputText Over Selectable?

tasiek30 avatar Apr 19 '24 12:04 tasiek30

Yes, you need to pass ImGuiSelectableFlags_AllowOverlap to the `Selectable().

See Demo->Layout->Overlap Mode or Demo->Widgets->Selectables->Rendering more items on the same line

ocornut avatar Apr 19 '24 12:04 ocornut

I am going to try working on a simple demo to demonstrate a grid with text editable items that allows multi-write like this.

ocornut avatar Apr 19 '24 14:04 ocornut

I wrote a draft of it but it doesn't allow to multi-edit as currently multi-select system has a bias toward unselecting others when e.g. pressing enter on an item, so may need an improvement of multi-select.

void DemoSelectableEditableGrid()
{
    ImGui::Begin("Selection #7424");
    {
        const int COUNT_X = 10;
        const int COUNT_Y = 16;
        const int COUNT = COUNT_X * COUNT_Y;
        static ImGuiSelectionBasicStorage selection;
        static float data[COUNT];
        static int editing_n = -1;
        static int focus_n_next = -1;

        // FIXME: don't clear selection when clicking selected item
        ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect2d);
        selection.ApplyRequests(ms_io, COUNT);
        ImGui::Text("Selection: %d/%d", selection.Size, COUNT);

        const int focus_n_curr = focus_n_next;
        focus_n_next = -1;
        if (ImGui::BeginTable("Array", COUNT_X, ImGuiTableFlags_Borders))
        {
            for (int n = 0; n < COUNT; n++)
            {
                ImGui::TableNextColumn(); // Next cell w/ auto-wrap
                ImGui::PushID(n);

                const bool is_selected = selection.Contains((ImGuiID)n);
                ImGui::SetNextItemSelectionUserData(n);

                ImVec2 p = ImGui::GetCursorScreenPos();
                if (focus_n_curr == n)
                    ImGui::SetKeyboardFocusHere(0);

                if (editing_n != n)
                {
                    char label[64];
                    sprintf(label, "%g###sel", data[n]);
                    ImGui::Selectable(label, is_selected);
                    if (ImGui::IsItemClicked() && ImGui::IsMouseDoubleClicked(0))
                        editing_n = focus_n_next = n;
                    if (ImGui::IsItemFocused() && ImGui::IsKeyPressed(ImGuiKey_Enter))
                        editing_n = focus_n_next = n;
                }
                if (editing_n == n)
                {
                    // May be easier if we had a public-facing version of TempInputXXX functions
                    ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ImGui::GetColorU32(ImGuiCol_Header));
                    ImGui::SetCursorScreenPos(p);
                    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
                    ImGui::SetNextItemWidth(-FLT_MIN);
                    // FIXME: May want to use InputText() and convert empty string to 0.0f (vs InputFloat preserve value)
                    ImGui::InputFloat("###edit", &data[n], 0.0f, 0.0f, "%g");
                    if (ImGui::IsItemDeactivated())
                    {
                        editing_n = -1;
                        if (ImGui::IsItemFocused() && !ImGui::IsMouseClicked(0))
                            focus_n_next = n;
                    }
                    ImGui::PopStyleVar();
                }
                ImGui::PopID();
            }
            ImGui::EndTable();
        }
        ms_io = ImGui::EndMultiSelect();
        selection.ApplyRequests(ms_io, COUNT);
    }
    ImGui::End();
}

selectable_editable_grid

Honestly this is the kind of thing where there are lots of subtleties which are not trivial to get right/perfect with dear imgui, so it'll require more work. It'll be an interesting demo.

ocornut avatar Apr 19 '24 16:04 ocornut

Hello

Thank @ocornut for Your reply. I think it is almost done.... One bad thing is that i have changed a little ImGui source code. This should be done in more sophisticated way.

https://github.com/ocornut/imgui/assets/34631947/036b4db9-f20b-4114-a134-2d5aa4457f29

void DemoSelectableEditableGrid()
{
    ImGui::Begin("Selection #7424");
    {
        const int COUNT_X = 10;
        const int COUNT_Y = 16;
        const int COUNT = COUNT_X * COUNT_Y;
        static ImGuiSelectionBasicStorage selection;
        static float data[COUNT];
        static int editing_n = -1;
        static int focus_n_next = -1;
        static bool set_flag = false;
        static bool change_flag = false;
        float change = 0.0f;

        // FIXME: don't clear selection when clicking selected item
        ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(
                ImGuiMultiSelectFlags_ClearOnEscape
                | ImGuiMultiSelectFlags_BoxSelect2d
                | ImGuiMultiSelectFlags_ClearOnClickVoid
                );
        selection.ApplyRequests(ms_io, COUNT);
        ImGui::Text("Selection: %d/%d", selection.Size, COUNT);

        const int focus_n_curr = focus_n_next;
        focus_n_next = -1;
        if (ImGui::BeginTable("Array", COUNT_X, ImGuiTableFlags_Borders))
        {
            if(ImGui::IsKeyPressed(ImGuiKey_KeypadAdd)) {
                change_flag = true;
                change = 1.0f;
            }
            else if(ImGui::IsKeyPressed(ImGuiKey_KeypadSubtract)) {
                change_flag = true;
                change = -1.0f;
            }

            for (int n = 0; n < COUNT; n++)
            {
                ImGui::TableNextColumn(); // Next cell w/ auto-wrap
                ImGui::PushID(n);

                const bool is_selected = selection.Contains((ImGuiID)n);
                ImGui::SetNextItemSelectionUserData(n);

                ImVec2 p = ImGui::GetCursorScreenPos();
                if (focus_n_curr == n)
                    ImGui::SetKeyboardFocusHere(0);

                char label[64];
                sprintf(label, "%g###sel", data[n]);
                ImGui::SetNextItemAllowOverlap();
                ImGui::Selectable(label, is_selected);

                if (ImGui::IsItemClicked() && ImGui::IsMouseDoubleClicked(0)) {
                    editing_n = focus_n_next = n;
                }
                if (ImGui::IsItemFocused() && ImGui::IsKeyPressed(ImGuiKey_Enter)) {
                    editing_n = focus_n_next = n;
                }

                if (editing_n == n)
                {
                    // May be easier if we had a public-facing version of TempInputXXX functions
                    ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ImGui::GetColorU32(ImGuiCol_Header));
                    ImGui::SetCursorScreenPos(p);
                    ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
                    ImGui::SetNextItemWidth(-FLT_MIN);
                    // FIXME: May want to use InputText() and convert empty string to 0.0f (vs InputFloat preserve value)
                    ImGui::SetKeyboardFocusHere(0);

                    ImGui::InputFloat("###edit", &data[n], 0.0f, 0.0f, "%g");
                    if (ImGui::IsItemDeactivated())
                    {
                        set_flag = true;
                        change = data[n];
                        editing_n = -1;
                        if (ImGui::IsItemFocused() && !ImGui::IsMouseClicked(0))
                            focus_n_next = n;
                    }
                    ImGui::PopStyleVar();
                }
                ImGui::PopID();
            }
            ImGui::EndTable();
        }
        ms_io = ImGui::EndMultiSelect();
        selection.ApplyRequests(ms_io, COUNT);

        if(set_flag) {
            set_flag = false;
            for(int n = 0; n < COUNT; n++) {
                if(selection.Contains((ImGuiID)n) ) {
                    data[n] = change;
                }
            }
        }

        if(change_flag) {
            change_flag = false;
            for(int n = 0; n < COUNT; n++) {
                if(selection.Contains((ImGuiID)n) ) {
                    data[n] += change;
                }
            }
        }

    }
    ImGui::End();
}

In Source code i just commented out clear request when focus is lost. ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags) in imgui_widgets.cpp

    else if (g.NavJustMovedFromFocusScopeId == ms->FocusScopeId)
    {
        // Also clear on leaving scope (may be optional?)
        if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0) {
            //request_clear = true;
        }
    }

tasiek30 avatar Apr 25 '24 11:04 tasiek30

In Source code i just commented out clear request when focus is lost. ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags) in imgui_widgets.cpp

This is not specifically when focus is lost but when LEAVING the current scope. Can you clarify why you want/need to disable it? (then I can see if it's worth adding an option for it)

ocornut avatar Apr 25 '24 12:04 ocornut

In Source code i just commented out clear request when focus is lost. ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags) in imgui_widgets.cpp

This is not specifically when focus is lost but when LEAVING the current scope. Can you clarify why you want/need to disable it? (then I can see if it's worth adding an option for it)

It was the easiest way to keep selection after enter pressed and switch focus to InputFloat

tasiek30 avatar Apr 25 '24 12:04 tasiek30

It was the easiest way to keep selection after enter pressed and switch focus to InputFloat

OK so that's a workaround but I will find a way to design a solution for it. Thanks for clarifying!

ocornut avatar Apr 25 '24 13:04 ocornut

   else if (g.NavJustMovedFromFocusScopeId == ms->FocusScopeId)
    {
        // Also clear on leaving scope (may be optional?)
        if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0) {
            //request_clear = true;
        }
    }

It's interesting because the logic in my code triggers the NavJustMovedToFocusScopeId block, while the logic in yours triggers the NavJustMovedFromFocusScopeId block. Will need further research before I can design a proper flag.

ocornut avatar May 08 '24 13:05 ocornut

@tasiek30 I think the right fix is currently to do this:

Everytime you call SetKeyboardFocusHere(), add this line:

ImGui::SetKeyboardFocusHere(0);
ImGui::GetCurrentContext()->NavMoveFlags |= ImGuiNavMoveFlags_NoSelect;

Can you try it?

I am going to refactor and expose new functions such as ActivateItem() which will have those flags exposed in some way.

Note that API for BeginMultiSelect() changed since your example, you have to pass selection.Size and COUNT to BeginMultiSelect().

ocornut avatar Jun 13 '24 15:06 ocornut

FYI

  • multi-select has now been merged into master.
  • ~~i have added a ImGuiMultiSelectFlags_NoAutoClearOnReselect flag which essentially has the effect you wanted (so you don't need to use the ImGuiNavMoveFlags_NoSelect hack)~~ apparently it is still needed

I am keeping this open because I would like to finish this "2d spreadsheet demo", however simple. I think one missing this for this is a flag to make RangeSelect a "2D" operation based on item geometry, so Shift+Down would select a single item in the next row instead of selecting in a 1D manner like e.g. explorer does. For some reason this is not trivial to implement so I'll do that later.

ocornut avatar Jul 18 '24 16:07 ocornut

Hi. Thank You for support! Currently a have a lot of other work so i will check everything later.

Currently my table looks like below

obraz

tasiek30 avatar Aug 13 '24 11:08 tasiek30