diff --git a/dearpygui/type_info.py b/dearpygui/type_info.py index 76e91ec74..acd5a85bd 100644 --- a/dearpygui/type_info.py +++ b/dearpygui/type_info.py @@ -1273,7 +1273,7 @@ def get_applicable_states(**kwargs) -> dict: "mvAppItemType::mvCollapsingHeader": ("ok", "pos", "hovered", "active", "focused", "clicked", "left_clicked", "right_clicked", "middle_clicked", "visible", "activated", "deactivated", "toggled_open", "rect_min", "rect_max", "rect_size", "resized", "content_region_avail", ), "mvAppItemType::mvSeparator": ("ok", "pos", ), "mvAppItemType::mvCheckbox": ("ok", "pos", "hovered", "active", "focused", "clicked", "left_clicked", "right_clicked", "middle_clicked", "visible", "activated", "deactivated", "deactivated_after_edit", "rect_min", "rect_max", "rect_size", "resized", "content_region_avail", ), - "mvAppItemType::mvListbox": ("ok", "pos", "hovered", "active", "focused", "clicked", "left_clicked", "right_clicked", "middle_clicked", "visible", "edited", "activated", "deactivated", "deactivated_after_edit", "rect_min", "rect_max", "rect_size", "resized", "content_region_avail", ), + "mvAppItemType::mvListbox": ("ok", "pos", "hovered", "active", "focused", "clicked", "left_clicked", "right_clicked", "middle_clicked", "visible", "edited", "activated", "deactivated", "deactivated_after_edit", "rect_min", "rect_max", "rect_size", "resized", "content_region_avail", "scrolled", "is_scrolling", "scroll_pos", "scroll_max", ), "mvAppItemType::mvText": ("ok", "pos", "hovered", "clicked", "left_clicked", "right_clicked", "middle_clicked", "visible", "rect_min", "rect_max", "rect_size", "resized", "content_region_avail", ), "mvAppItemType::mvCombo": ("ok", "pos", "hovered", "active", "focused", "clicked", "left_clicked", "right_clicked", "middle_clicked", "visible", "edited", "activated", "deactivated", "deactivated_after_edit", "rect_min", "rect_max", "rect_size", "resized", "content_region_avail", ), "mvAppItemType::mvPlot": ("ok", "pos", "hovered", "active", "focused", "clicked", "left_clicked", "right_clicked", "middle_clicked", "visible", "activated", "deactivated", "rect_min", "rect_max", "rect_size", "resized", "content_region_avail", ), diff --git a/src/dearpygui_commands.h b/src/dearpygui_commands.h index 4a8b5cbe1..90bc677ed 100644 --- a/src/dearpygui_commands.h +++ b/src/dearpygui_commands.h @@ -233,7 +233,7 @@ set_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) else { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "set_x_scroll", - "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); + "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable, mvListbox", window); return nullptr; } @@ -272,7 +272,7 @@ set_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) else { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "set_y_scroll", - "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); + "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable, mvListbox", window); return nullptr; } @@ -308,7 +308,7 @@ get_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) else { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_x_scroll", - "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); + "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable, mvListbox", window); return nullptr; } @@ -344,7 +344,7 @@ get_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) else { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_y_scroll", - "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); + "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable, mvListbox", window); return nullptr; } @@ -380,7 +380,7 @@ get_x_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs) else { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_x_scroll_max", - "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); + "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable, mvListbox", window); return nullptr; } @@ -416,7 +416,7 @@ get_y_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs) else { mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_y_scroll_max", - "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); + "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable, mvListbox", window); return nullptr; } diff --git a/src/mvAppItem.cpp b/src/mvAppItem.cpp index 10fd1b73d..0445af410 100644 --- a/src/mvAppItem.cpp +++ b/src/mvAppItem.cpp @@ -954,7 +954,8 @@ CanItemTypeBeScrolled(mvAppItemType type) { case mvAppItemType::mvWindowAppItem: case mvAppItemType::mvChildWindow: - case mvAppItemType::mvTable: return true; + case mvAppItemType::mvTable: + case mvAppItemType::mvListbox: return true; default: return false; } diff --git a/src/mvBasicWidgets.cpp b/src/mvBasicWidgets.cpp index 6111f2f6c..8b0e3ba9e 100644 --- a/src/mvBasicWidgets.cpp +++ b/src/mvBasicWidgets.cpp @@ -3976,15 +3976,67 @@ DearPyGui::draw_listbox(ImDrawList *drawlist, mvAppItem &item, mvListboxConfig & config.disabledindex = config.index; } + item.handleImmediateScroll(); + // remap Header to FrameBgActive ImGuiStyle* style = &ImGui::GetStyle(); ImGui::PushStyleColor(ImGuiCol_Header, style->Colors[ImGuiCol_FrameBgActive]); - if (ImGui::ListBox(item.info.internalLabel.c_str(), item.config.enabled ? &config.index : &config.disabledindex, config.charNames.data(), (int)config.names.size(), config.itemsHeight)) + // We cannot use `ImGui::ListBox()` directly because to apply scrolling, + // we need access to the child window and `ListBox()` does not expose it. + // That's why we have to reimplement it using BeginListBox/EndListBox + // (as ImGui recommends it anyway). Ideally, the code below needs to be + // kept in sync, more or less, with `ImGui::ListBox()`. + int items_count = (int)config.names.size(); + int* current_item = item.config.enabled ? &config.index : &config.disabledindex; + + int height_in_items = config.itemsHeight; + if (height_in_items < 0) + height_in_items = ImMin(items_count, 7); + float height_in_items_f = (float)height_in_items + 0.25f; + ImVec2 size(0.0f, ImTrunc(ImGui::GetTextLineHeightWithSpacing() * height_in_items_f + ImGui::GetStyle().FramePadding.y * 2.0f)); + + if (ImGui::BeginListBox(item.info.internalLabel.c_str(), size)) { - *config.value = config.names[config.index]; - config.disabled_value = config.names[config.index]; - item.submitCallback(*config.value); + bool value_changed = false; + ImGuiListClipper clipper; + clipper.Begin(items_count, ImGui::GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to. + clipper.IncludeItemByIndex(*current_item); + while (clipper.Step()) + { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + ImGui::PushID(i); + const bool item_selected = (i == *current_item); + if (ImGui::Selectable(config.charNames[i], item_selected)) + { + *current_item = i; + value_changed = true; + } + if (item_selected) + ImGui::SetItemDefaultFocus(); + ImGui::PopID(); + } + } + + item.handleDelayedScroll(); + item.config.scrollXFlags = item.config.scrollYFlags = mvSetScrollFlags_None; + UpdateAppItemScrollInfo(item.state); + + ImGui::EndListBox(); + + if (value_changed) + { + ImGuiContext& g = *GImGui; + ImGui::MarkItemEdited(g.LastItemData.ID); + + *config.value = config.names[config.index]; + // TODO: we need to review how listbox behaves in the disabled state. It does + // not look good that it uses `index` rather than `disabledindex` here. Neither + // does `config.value` in `submitCallback` below. + config.disabled_value = config.names[config.index]; + item.submitCallback(*config.value); + } } ImGui::PopStyleColor();