diff --git a/dearpygui/_dearpygui.pyi b/dearpygui/_dearpygui.pyi index e0d70d2f1..3ffbd5356 100644 --- a/dearpygui/_dearpygui.pyi +++ b/dearpygui/_dearpygui.pyi @@ -343,6 +343,10 @@ def add_item_resize_handler(*, label: str ='', user_data: Any ='', use_internal_ """Adds a resize handler.""" ... +def add_item_scroll_handler(*, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', parent: Union[int, str] ='', callback: Callable ='', show: bool ='') -> Union[int, str]: + """Adds a scroll handler.""" + ... + def add_item_toggled_open_handler(*, label: str ='', user_data: Any ='', use_internal_label: bool ='', tag: Union[int, str] ='', parent: Union[int, str] ='', callback: Callable ='', show: bool ='') -> Union[int, str]: """Adds a togged open handler.""" ... @@ -1179,12 +1183,12 @@ def set_viewport_resize_callback(callback : Callable, *, user_data: Any ='') -> """Sets a callback to run on viewport resize.""" ... -def set_x_scroll(item : Union[int, str], value : float) -> None: - """Undocumented""" +def set_x_scroll(item : Union[int, str], value : float, *, when: int ='') -> None: + """Sets horizontal scroll position.""" ... -def set_y_scroll(item : Union[int, str], value : float) -> None: - """Undocumented""" +def set_y_scroll(item : Union[int, str], value : float, *, when: int ='') -> None: + """Sets vertical scroll position.""" ... def setup_dearpygui() -> None: @@ -1434,6 +1438,13 @@ mvEventType_Off=0 mvEventType_Enter=0 mvEventType_On=0 mvEventType_Leave=0 +mvSetScrollFlags_Now=0 +mvSetScrollFlags_Delayed=0 +mvSetScrollFlags_Both=0 +mvScrollDirection_XAxis=0 +mvScrollDirection_YAxis=0 +mvScrollDirection_Horizontal=0 +mvScrollDirection_Vertical=0 mvPlatform_Windows=0 mvPlatform_Apple=0 mvPlatform_Linux=0 @@ -1856,6 +1867,7 @@ mvDeactivatedAfterEditHandler=0 mvToggledOpenHandler=0 mvClickedHandler=0 mvDoubleClickedHandler=0 +mvScrollHandler=0 mvDragPayload=0 mvResizeHandler=0 mvFont=0 diff --git a/dearpygui/_dearpygui_RTD.py b/dearpygui/_dearpygui_RTD.py index 5130a5050..abb68f6a9 100644 --- a/dearpygui/_dearpygui_RTD.py +++ b/dearpygui/_dearpygui_RTD.py @@ -5001,6 +5001,24 @@ def add_item_resize_handler(**kwargs): return internal_dpg.add_item_resize_handler(**kwargs) +def add_item_scroll_handler(**kwargs): + """ Adds a scroll handler. + + Args: + label (str, optional): Overrides 'name' as label. + user_data (Any, optional): User data for callbacks + use_internal_label (bool, optional): Use generated internal label instead of user specified (appends ### uuid). + tag (Union[int, str], optional): Unique id used to programmatically refer to the item.If label is unused this will be the label. + parent (Union[int, str], optional): Parent to add this item to. (runtime adding) + callback (Callable, optional): Registers a callback. + show (bool, optional): Attempt to render widget. + id (Union[int, str], optional): (deprecated) + Returns: + Union[int, str] + """ + + return internal_dpg.add_item_scroll_handler(**kwargs) + def add_item_toggled_open_handler(**kwargs): """ Adds a togged open handler. @@ -8600,29 +8618,31 @@ def set_viewport_resize_callback(callback, **kwargs): return internal_dpg.set_viewport_resize_callback(callback, **kwargs) -def set_x_scroll(item, value): - """ Undocumented +def set_x_scroll(item, value, **kwargs): + """ Sets horizontal scroll position. Args: item (Union[int, str]): - value (float): + value (float): Scroll position + when (int, optional): Specifies whether the scroll position will be set in the nearest frame (mvSetScrollFlags_Now) or with a 1-frame delay (mvSetScrollFlags_Delayed). The former prevents flickering, the latter works better if contents change in the same frame as when set_x_scroll called. mvSetScrollFlags_Both can also be used to set the position twice. Returns: None """ - return internal_dpg.set_x_scroll(item, value) + return internal_dpg.set_x_scroll(item, value, **kwargs) -def set_y_scroll(item, value): - """ Undocumented +def set_y_scroll(item, value, **kwargs): + """ Sets vertical scroll position. Args: item (Union[int, str]): - value (float): + value (float): Scroll position + when (int, optional): Specifies whether the scroll position will be set in the nearest frame (mvSetScrollFlags_Now) or with a 1-frame delay (mvSetScrollFlags_Delayed). The former prevents flickering, the latter works better if contents change in the same frame as when set_x_scroll called. mvSetScrollFlags_Both can also be used to set the position twice. Returns: None """ - return internal_dpg.set_y_scroll(item, value) + return internal_dpg.set_y_scroll(item, value, **kwargs) def setup_dearpygui(): """ Sets up Dear PyGui @@ -8989,6 +9009,13 @@ def unstage(item): mvEventType_Enter=internal_dpg.mvEventType_Enter mvEventType_On=internal_dpg.mvEventType_On mvEventType_Leave=internal_dpg.mvEventType_Leave +mvSetScrollFlags_Now=internal_dpg.mvSetScrollFlags_Now +mvSetScrollFlags_Delayed=internal_dpg.mvSetScrollFlags_Delayed +mvSetScrollFlags_Both=internal_dpg.mvSetScrollFlags_Both +mvScrollDirection_XAxis=internal_dpg.mvScrollDirection_XAxis +mvScrollDirection_YAxis=internal_dpg.mvScrollDirection_YAxis +mvScrollDirection_Horizontal=internal_dpg.mvScrollDirection_Horizontal +mvScrollDirection_Vertical=internal_dpg.mvScrollDirection_Vertical mvPlatform_Windows=internal_dpg.mvPlatform_Windows mvPlatform_Apple=internal_dpg.mvPlatform_Apple mvPlatform_Linux=internal_dpg.mvPlatform_Linux @@ -9411,6 +9438,7 @@ def unstage(item): mvToggledOpenHandler=internal_dpg.mvToggledOpenHandler mvClickedHandler=internal_dpg.mvClickedHandler mvDoubleClickedHandler=internal_dpg.mvDoubleClickedHandler +mvScrollHandler=internal_dpg.mvScrollHandler mvDragPayload=internal_dpg.mvDragPayload mvResizeHandler=internal_dpg.mvResizeHandler mvFont=internal_dpg.mvFont diff --git a/dearpygui/dearpygui.py b/dearpygui/dearpygui.py index 6d0db5a34..aa4c2d846 100644 --- a/dearpygui/dearpygui.py +++ b/dearpygui/dearpygui.py @@ -5552,6 +5552,28 @@ def add_item_resize_handler(*, label: str =None, user_data: Any =None, use_inter return internal_dpg.add_item_resize_handler(label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, callback=callback, show=show, **kwargs) +def add_item_scroll_handler(*, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =0, callback: Callable =None, show: bool =True, **kwargs) -> Union[int, str]: + """ Adds a scroll handler. + + Args: + label (str, optional): Overrides 'name' as label. + user_data (Any, optional): User data for callbacks + use_internal_label (bool, optional): Use generated internal label instead of user specified (appends ### uuid). + tag (Union[int, str], optional): Unique id used to programmatically refer to the item.If label is unused this will be the label. + parent (Union[int, str], optional): Parent to add this item to. (runtime adding) + callback (Callable, optional): Registers a callback. + show (bool, optional): Attempt to render widget. + id (Union[int, str], optional): (deprecated) + Returns: + Union[int, str] + """ + + if 'id' in kwargs.keys(): + warnings.warn('id keyword renamed to tag', DeprecationWarning, 2) + tag=kwargs['id'] + + return internal_dpg.add_item_scroll_handler(label=label, user_data=user_data, use_internal_label=use_internal_label, tag=tag, parent=parent, callback=callback, show=show, **kwargs) + def add_item_toggled_open_handler(*, label: str =None, user_data: Any =None, use_internal_label: bool =True, tag: Union[int, str] =0, parent: Union[int, str] =0, callback: Callable =None, show: bool =True, **kwargs) -> Union[int, str]: """ Adds a togged open handler. @@ -9561,29 +9583,31 @@ def set_viewport_resize_callback(callback : Callable, *, user_data: Any =None, * return internal_dpg.set_viewport_resize_callback(callback, user_data=user_data, **kwargs) -def set_x_scroll(item : Union[int, str], value : float, **kwargs) -> None: - """ Undocumented +def set_x_scroll(item : Union[int, str], value : float, *, when: int =internal_dpg.mvSetScrollFlags_Delayed, **kwargs) -> None: + """ Sets horizontal scroll position. Args: item (Union[int, str]): - value (float): + value (float): Scroll position + when (int, optional): Specifies whether the scroll position will be set in the nearest frame (mvSetScrollFlags_Now) or with a 1-frame delay (mvSetScrollFlags_Delayed). The former prevents flickering, the latter works better if contents change in the same frame as when set_x_scroll called. mvSetScrollFlags_Both can also be used to set the position twice. Returns: None """ - return internal_dpg.set_x_scroll(item, value, **kwargs) + return internal_dpg.set_x_scroll(item, value, when=when, **kwargs) -def set_y_scroll(item : Union[int, str], value : float, **kwargs) -> None: - """ Undocumented +def set_y_scroll(item : Union[int, str], value : float, *, when: int =internal_dpg.mvSetScrollFlags_Delayed, **kwargs) -> None: + """ Sets vertical scroll position. Args: item (Union[int, str]): - value (float): + value (float): Scroll position + when (int, optional): Specifies whether the scroll position will be set in the nearest frame (mvSetScrollFlags_Now) or with a 1-frame delay (mvSetScrollFlags_Delayed). The former prevents flickering, the latter works better if contents change in the same frame as when set_x_scroll called. mvSetScrollFlags_Both can also be used to set the position twice. Returns: None """ - return internal_dpg.set_y_scroll(item, value, **kwargs) + return internal_dpg.set_y_scroll(item, value, when=when, **kwargs) def setup_dearpygui(**kwargs) -> None: """ Sets up Dear PyGui @@ -9968,6 +9992,13 @@ def unstage(item : Union[int, str], **kwargs) -> None: mvEventType_Enter=internal_dpg.mvEventType_Enter mvEventType_On=internal_dpg.mvEventType_On mvEventType_Leave=internal_dpg.mvEventType_Leave +mvSetScrollFlags_Now=internal_dpg.mvSetScrollFlags_Now +mvSetScrollFlags_Delayed=internal_dpg.mvSetScrollFlags_Delayed +mvSetScrollFlags_Both=internal_dpg.mvSetScrollFlags_Both +mvScrollDirection_XAxis=internal_dpg.mvScrollDirection_XAxis +mvScrollDirection_YAxis=internal_dpg.mvScrollDirection_YAxis +mvScrollDirection_Horizontal=internal_dpg.mvScrollDirection_Horizontal +mvScrollDirection_Vertical=internal_dpg.mvScrollDirection_Vertical mvPlatform_Windows=internal_dpg.mvPlatform_Windows mvPlatform_Apple=internal_dpg.mvPlatform_Apple mvPlatform_Linux=internal_dpg.mvPlatform_Linux @@ -10390,6 +10421,7 @@ def unstage(item : Union[int, str], **kwargs) -> None: mvToggledOpenHandler=internal_dpg.mvToggledOpenHandler mvClickedHandler=internal_dpg.mvClickedHandler mvDoubleClickedHandler=internal_dpg.mvDoubleClickedHandler +mvScrollHandler=internal_dpg.mvScrollHandler mvDragPayload=internal_dpg.mvDragPayload mvResizeHandler=internal_dpg.mvResizeHandler mvFont=internal_dpg.mvFont diff --git a/src/dearpygui.cpp b/src/dearpygui.cpp index 27145a651..d26509634 100644 --- a/src/dearpygui.cpp +++ b/src/dearpygui.cpp @@ -74,6 +74,16 @@ GetModuleConstants() ModuleConstants.push_back({"mvEventType_On", mvEventType_On }); ModuleConstants.push_back({"mvEventType_Leave", mvEventType_Leave }); + // We're not adding 'None' because it's useless in the API + ModuleConstants.push_back({"mvSetScrollFlags_Now", mvSetScrollFlags_Now }); + ModuleConstants.push_back({"mvSetScrollFlags_Delayed", mvSetScrollFlags_Delayed }); + ModuleConstants.push_back({"mvSetScrollFlags_Both", mvSetScrollFlags_Both }); + + ModuleConstants.push_back({"mvScrollDirection_XAxis", mvScrollDirection_XAxis }); + ModuleConstants.push_back({"mvScrollDirection_YAxis", mvScrollDirection_YAxis }); + ModuleConstants.push_back({"mvScrollDirection_Horizontal", mvScrollDirection_Horizontal }); + ModuleConstants.push_back({"mvScrollDirection_Vertical", mvScrollDirection_Vertical }); + ModuleConstants.push_back({"mvPlatform_Windows", 0L }); ModuleConstants.push_back({"mvPlatform_Apple", 1L }); ModuleConstants.push_back({"mvPlatform_Linux", 2L }); diff --git a/src/dearpygui_commands.h b/src/dearpygui_commands.h index 86bb62e94..e25355bc2 100644 --- a/src/dearpygui_commands.h +++ b/src/dearpygui_commands.h @@ -207,9 +207,10 @@ set_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw; float value; + int when = (int)mvSetScrollFlags_Delayed; if (!Parse((GetParsers())["set_x_scroll"], args, kwargs, __FUNCTION__, - &itemraw, &value)) + &itemraw, &value, &when)) return GetPyNone(); mvPySafeLockGuard lk(GContext->mutex); @@ -224,25 +225,10 @@ set_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) return GetPyNone(); } - if (window->type == mvAppItemType::mvWindowAppItem) + if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) { - - auto pWindow = static_cast(window); - - pWindow->configData.scrollX = value; - pWindow->configData._scrollXSet = true; - } - else if (window->type == mvAppItemType::mvChildWindow) - { - auto pChild = static_cast(window); - pChild->configData.scrollX = value; - pChild->configData._scrollXSet = true; - } - else if (window->type == mvAppItemType::mvTable) - { - auto pChild = static_cast(window); - pChild->_scrollX = value; - pChild->_scrollXSet = true; + window->config.scrollX = value; + window->config.scrollXFlags = (mvSetScrollFlags)when; } else { @@ -259,9 +245,10 @@ set_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) PyObject* itemraw; float value; + int when = (int)mvSetScrollFlags_Delayed; if (!Parse((GetParsers())["set_y_scroll"], args, kwargs, __FUNCTION__, - &itemraw, &value)) + &itemraw, &value, &when)) return GetPyNone(); mvPySafeLockGuard lk(GContext->mutex); @@ -276,25 +263,10 @@ set_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) return GetPyNone(); } - if (window->type == mvAppItemType::mvWindowAppItem) - { - - auto pWindow = static_cast(window); - - pWindow->configData.scrollY = value; - pWindow->configData._scrollYSet = true; - } - else if (window->type == mvAppItemType::mvChildWindow) - { - auto pChild = static_cast(window); - pChild->configData.scrollY = value; - pChild->configData._scrollYSet = true; - } - else if (window->type == mvAppItemType::mvTable) + if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) { - auto pChild = static_cast(window); - pChild->_scrollY = value; - pChild->_scrollYSet = true; + window->config.scrollY = value; + window->config.scrollYFlags = (mvSetScrollFlags)when; } else { @@ -327,24 +299,9 @@ get_x_scroll(PyObject* self, PyObject* args, PyObject* kwargs) return GetPyNone(); } - if (window->type == mvAppItemType::mvWindowAppItem) + if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) { - - auto pWindow = static_cast(window); - - return ToPyFloat(pWindow->configData.scrollX); - } - else if (window->type == mvAppItemType::mvChildWindow) - { - auto pChild = static_cast(window); - - return ToPyFloat(pChild->configData.scrollX); - } - else if (window->type == mvAppItemType::mvTable) - { - auto pTable = static_cast(window); - - return ToPyFloat(pTable->_scrollX); + return ToPyFloat(window->state.scrollPos.x); } else { @@ -377,24 +334,9 @@ get_y_scroll(PyObject* self, PyObject* args, PyObject* kwargs) return GetPyNone(); } - if (window->type == mvAppItemType::mvWindowAppItem) + if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) { - - auto pWindow = static_cast(window); - - return ToPyFloat(pWindow->configData.scrollY); - } - else if (window->type == mvAppItemType::mvChildWindow) - { - auto pChild = static_cast(window); - - return ToPyFloat(pChild->configData.scrollY); - } - else if (window->type == mvAppItemType::mvTable) - { - auto pTable = static_cast(window); - - return ToPyFloat(pTable->_scrollY); + return ToPyFloat(window->state.scrollPos.y); } else { @@ -427,24 +369,9 @@ get_x_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs) return GetPyNone(); } - if (window->type == mvAppItemType::mvWindowAppItem) - { - - auto pWindow = static_cast(window); - - return ToPyFloat(pWindow->configData.scrollMaxX); - } - else if (window->type == mvAppItemType::mvChildWindow) - { - auto pChild = static_cast(window); - - return ToPyFloat(pChild->configData.scrollMaxX); - } - else if (window->type == mvAppItemType::mvTable) + if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) { - auto pTable = static_cast(window); - - return ToPyFloat(pTable->_scrollMaxX); + return ToPyFloat(window->state.scrollMax.x); } else { @@ -477,28 +404,13 @@ get_y_scroll_max(PyObject* self, PyObject* args, PyObject* kwargs) return GetPyNone(); } - if (window->type == mvAppItemType::mvWindowAppItem) + if (DearPyGui::GetApplicableState(window->type) & MV_STATE_SCROLL) { - - auto pWindow = static_cast(window); - - return ToPyFloat(pWindow->configData.scrollMaxY); - } - else if (window->type == mvAppItemType::mvChildWindow) - { - auto pChild = static_cast(window); - - return ToPyFloat(pChild->configData.scrollMaxY); - } - else if (window->type == mvAppItemType::mvTable) - { - auto pTable = static_cast(window); - - return ToPyFloat(pTable->_scrollMaxY); + return ToPyFloat(window->state.scrollMax.y); } else { - mvThrowPythonError(mvErrorCode::mvIncompatibleType, "set_y_scroll_max", + mvThrowPythonError(mvErrorCode::mvIncompatibleType, "get_y_scroll_max", "Incompatible type. Expected types include: mvWindowAppItem, mvChildWindow, mvTable", window); } @@ -3730,7 +3642,8 @@ get_item_info(PyObject* self, PyObject* args, PyObject* kwargs) PyDict_SetItemString(pdict, "deactivatedae_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_DEACTIVATEDAE))); PyDict_SetItemString(pdict, "toggled_open_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_TOGGLED_OPEN))); PyDict_SetItemString(pdict, "resized_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_RECT_SIZE))); - + PyDict_SetItemString(pdict, "scroll_handler_applicable", mvPyObject(ToPyBool(applicableState & MV_STATE_SCROLL))); + } else diff --git a/src/dearpygui_parsers.h b/src/dearpygui_parsers.h index 0d6d4d745..8446225ed 100644 --- a/src/dearpygui_parsers.h +++ b/src/dearpygui_parsers.h @@ -1784,9 +1784,15 @@ InsertParser_Block4(std::map& parsers) { std::vector args; args.push_back({ mvPyDataType::UUID, "item" }); - args.push_back({ mvPyDataType::Float, "value" }); + args.push_back({ mvPyDataType::Float, "value", mvArgType::REQUIRED_ARG, "", "Scroll position"}); + args.push_back({ mvPyDataType::Integer, "when", mvArgType::KEYWORD_ARG, "internal_dpg.mvSetScrollFlags_Delayed", + "Specifies whether the scroll position will be set in the nearest frame (mvSetScrollFlags_Now) or with " + "a 1-frame delay (mvSetScrollFlags_Delayed). The former prevents flickering, the latter works better " + "if contents change in the same frame as when set_x_scroll called. mvSetScrollFlags_Both can also be used " + "to set the position twice." }); mvPythonParserSetup setup; + setup.about = "Sets horizontal scroll position."; mvPythonParser parser = FinalizeParser(setup, args); parsers.insert({ "set_x_scroll", parser }); @@ -1795,8 +1801,16 @@ InsertParser_Block4(std::map& parsers) { std::vector args; args.push_back({ mvPyDataType::UUID, "item" }); - args.push_back({ mvPyDataType::Float, "value" }); + args.push_back({ mvPyDataType::Float, "value", mvArgType::REQUIRED_ARG, "", "Scroll position"}); + args.push_back({ mvPyDataType::Integer, "when", mvArgType::KEYWORD_ARG, "internal_dpg.mvSetScrollFlags_Delayed", + "Specifies whether the scroll position will be set in the nearest frame (mvSetScrollFlags_Now) or with " + "a 1-frame delay (mvSetScrollFlags_Delayed). The former prevents flickering, the latter works better " + "if contents change in the same frame as when set_x_scroll called. mvSetScrollFlags_Both can also be used " + "to set the position twice." }); + mvPythonParserSetup setup; + setup.about = "Sets vertical scroll position."; + mvPythonParser parser = FinalizeParser(setup, args); parsers.insert({ "set_y_scroll", parser }); } diff --git a/src/mvAppItem.cpp b/src/mvAppItem.cpp index bae62efd6..4815f8797 100644 --- a/src/mvAppItem.cpp +++ b/src/mvAppItem.cpp @@ -258,6 +258,37 @@ mvAppItem::setDataSource(mvUUID value) config.source = value; } +void +mvAppItem::handleImmediateScroll() +{ + if (!((config.scrollXFlags | config.scrollYFlags) & mvSetScrollFlags_Now)) + return; + + ImVec2 scroll = { (config.scrollXFlags & mvSetScrollFlags_Now)? config.scrollX : -1, + (config.scrollYFlags & mvSetScrollFlags_Now)? config.scrollY : -1 }; + ImGui::SetNextWindowScroll(scroll); +} + +void +mvAppItem::handleDelayedScroll() +{ + if (config.scrollXFlags & mvSetScrollFlags_Delayed) + { + if (config.scrollX < 0.0f) + ImGui::SetScrollHereX(1.0f); + else + ImGui::SetScrollX(config.scrollX); + } + + if (config.scrollYFlags & mvSetScrollFlags_Delayed) + { + if (config.scrollY < 0.0f) + ImGui::SetScrollHereY(1.0f); + else + ImGui::SetScrollY(config.scrollY); + } +} + static bool CanItemTypeBeHovered(mvAppItemType type) { @@ -912,6 +943,19 @@ CanItemTypeHaveContAvail(mvAppItemType type) } +static bool +CanItemTypeBeScrolled(mvAppItemType type) +{ + switch (type) + { + case mvAppItemType::mvWindowAppItem: + case mvAppItemType::mvChildWindow: + case mvAppItemType::mvTable: return true; + default: return false; + } + +} + int DearPyGui::GetApplicableState(mvAppItemType type) { @@ -930,6 +974,7 @@ DearPyGui::GetApplicableState(mvAppItemType type) if(CanItemTypeHaveRectMax(type)) applicableState |= MV_STATE_RECT_MAX; if(CanItemTypeHaveRectSize(type)) applicableState |= MV_STATE_RECT_SIZE; if(CanItemTypeHaveContAvail(type)) applicableState |= MV_STATE_CONT_AVAIL; + if(CanItemTypeBeScrolled(type)) applicableState |= MV_STATE_SCROLL; return applicableState; } @@ -1012,6 +1057,7 @@ DearPyGui::GetEntityDesciptionFlags(mvAppItemType type) case mvAppItemType::mvHoverHandler: case mvAppItemType::mvResizeHandler: case mvAppItemType::mvToggledOpenHandler: + case mvAppItemType::mvScrollHandler: case mvAppItemType::mvVisibleHandler: return MV_ITEM_DESC_HANDLER; case mvAppItemType::mvTooltip: @@ -1227,6 +1273,7 @@ DearPyGui::GetAllowableParents(mvAppItemType type) case mvAppItemType::mvHoverHandler: case mvAppItemType::mvResizeHandler: case mvAppItemType::mvToggledOpenHandler: + case mvAppItemType::mvScrollHandler: case mvAppItemType::mvVisibleHandler: MV_START_PARENTS MV_ADD_PARENT(mvAppItemType::mvStage), @@ -1545,7 +1592,8 @@ DearPyGui::GetAllowableChildren(mvAppItemType type) MV_ADD_CHILD(mvAppItemType::mvHoverHandler), MV_ADD_CHILD(mvAppItemType::mvResizeHandler), MV_ADD_CHILD(mvAppItemType::mvToggledOpenHandler), - MV_ADD_CHILD(mvAppItemType::mvVisibleHandler) + MV_ADD_CHILD(mvAppItemType::mvVisibleHandler), + MV_ADD_CHILD(mvAppItemType::mvScrollHandler), MV_END_CHILDREN case mvAppItemType::mvValueRegistry: @@ -5027,6 +5075,19 @@ DearPyGui::GetEntityParser(mvAppItemType type) setup.category = { "Widgets", "Events" }; break; } + case mvAppItemType::mvScrollHandler: + { + AddCommonArgs(args, (CommonParserArgs)( + MV_PARSER_ARG_ID | + MV_PARSER_ARG_SHOW | + MV_PARSER_ARG_PARENT | + MV_PARSER_ARG_CALLBACK) + ); + + setup.about = "Adds a scroll handler."; + setup.category = { "Widgets", "Events" }; + break; + } case mvAppItemType::mvFont: { AddCommonArgs(args, (CommonParserArgs)( diff --git a/src/mvAppItem.h b/src/mvAppItem.h index a0e3a0f0e..aa125b789 100644 --- a/src/mvAppItem.h +++ b/src/mvAppItem.h @@ -109,6 +109,14 @@ struct mvAppItemInfo bool dirtyPos = false; }; +enum mvSetScrollFlags +{ + mvSetScrollFlags_None = 0, + mvSetScrollFlags_Now = 1 << 0, + mvSetScrollFlags_Delayed = 1 << 1, + mvSetScrollFlags_Both = mvSetScrollFlags_Now | mvSetScrollFlags_Delayed +}; + struct mvAppItemConfig { mvUUID source = 0; @@ -134,6 +142,10 @@ struct mvAppItemConfig // the callback. This is to pass user_data into mvAddCallback that comes from a // different source than the callback owner (required for the drag callback). std::shared_ptr user_data = std::make_shared(nullptr); + float scrollX = 0.0f; + float scrollY = 0.0f; + mvSetScrollFlags scrollXFlags = mvSetScrollFlags_None; + mvSetScrollFlags scrollYFlags = mvSetScrollFlags_None; }; struct mvAppItemDrawInfo @@ -244,7 +256,12 @@ class mvAppItem : public std::enable_shared_from_this // config setters //----------------------------------------------------------------------------- virtual void setDataSource(mvUUID value); - + + //----------------------------------------------------------------------------- + // scrolling support + //----------------------------------------------------------------------------- + void handleImmediateScroll(); + void handleDelayedScroll(); }; inline bool mvClipPoint(float clipViewport[6], mvVec4& point) @@ -406,6 +423,7 @@ GetEntityCommand(mvAppItemType type) case mvAppItemType::mvDoubleClickedHandler: return "add_item_double_clicked_handler"; case mvAppItemType::mvDragPayload: return "add_drag_payload"; case mvAppItemType::mvResizeHandler: return "add_item_resize_handler"; + case mvAppItemType::mvScrollHandler: return "add_item_scroll_handler"; case mvAppItemType::mvFont: return "add_font"; case mvAppItemType::mvFontRegistry: return "add_font_registry"; case mvAppItemType::mvTheme: return "add_theme"; diff --git a/src/mvAppItemState.cpp b/src/mvAppItemState.cpp index 33a0d5b0b..4c636a5ae 100644 --- a/src/mvAppItemState.cpp +++ b/src/mvAppItemState.cpp @@ -23,6 +23,8 @@ ResetAppItemState(mvAppItemState& state) state.deactivatedAfterEdit = false; state.toggledOpen = false; state.mvRectSizeResized = false; + state.scrolledX = state.scrolledY = false; + state.isScrollingX = state.isScrollingY = false; } void @@ -61,6 +63,17 @@ UpdateAppItemState(mvAppItemState& state) state.mvPrevRectSize = state.rectSize; } +void +UpdateAppItemScrollInfo(mvAppItemState& state) +{ + float scrollX = ImGui::GetScrollX(); + float scrollY = ImGui::GetScrollY(); + state.scrolledX = (scrollX != state.scrollPos.x); + state.scrolledY = (scrollY != state.scrollPos.y); + state.scrollPos = { scrollX, scrollY }; + state.scrollMax = { ImGui::GetScrollMaxX(), ImGui::GetScrollMaxY() }; +} + void FillAppItemState(PyObject* dict, mvAppItemState& state, i32 applicableState) { @@ -97,6 +110,14 @@ FillAppItemState(PyObject* dict, mvAppItemState& state, i32 applicableState) } if(applicableState & MV_STATE_CONT_AVAIL) PyDict_SetItemString(dict, "content_region_avail", mvPyObject(ToPyPairII((i32)state.contextRegionAvail.x, (i32)state.contextRegionAvail.y))); + if(applicableState & MV_STATE_SCROLL) + { + PyDict_SetItemString(dict, "scrolled", mvPyObject(ToPyTPair(valid && state.scrolledX, valid && state.scrolledY))); + PyDict_SetItemString(dict, "is_scrolling", mvPyObject(ToPyTPair(valid && state.isScrollingX, valid && state.isScrollingY))); + PyDict_SetItemString(dict, "scroll_pos", mvPyObject(ToPyPair(state.scrollPos.x, state.scrollPos.y))); + PyDict_SetItemString(dict, "scroll_max", mvPyObject(ToPyPair(state.scrollMax.x, state.scrollMax.y))); + } + } b8 diff --git a/src/mvAppItemState.h b/src/mvAppItemState.h index 691326387..d22d90c41 100644 --- a/src/mvAppItemState.h +++ b/src/mvAppItemState.h @@ -31,8 +31,9 @@ enum mvStateItems MV_STATE_RECT_MAX = 1 << 12, MV_STATE_RECT_SIZE = 1 << 13, MV_STATE_CONT_AVAIL = 1 << 14, + MV_STATE_SCROLL = 1 << 15, MV_STATE_ALL = MV_STATE_HOVER |MV_STATE_ACTIVE |MV_STATE_FOCUSED |MV_STATE_CLICKED |MV_STATE_VISIBLE |MV_STATE_EDITED |MV_STATE_ACTIVATED |MV_STATE_DEACTIVATED |MV_STATE_DEACTIVATEDAE | - MV_STATE_TOGGLED_OPEN | MV_STATE_RECT_MIN |MV_STATE_RECT_MAX |MV_STATE_RECT_SIZE |MV_STATE_CONT_AVAIL + MV_STATE_TOGGLED_OPEN | MV_STATE_RECT_MIN |MV_STATE_RECT_MAX |MV_STATE_RECT_SIZE |MV_STATE_CONT_AVAIL |MV_STATE_SCROLL }; //----------------------------------------------------------------------------- @@ -42,6 +43,10 @@ enum mvStateItems void FillAppItemState (PyObject* dict, mvAppItemState& state, i32 applicableState); // fills python dict with applicable state values void ResetAppItemState (mvAppItemState& state); // reset values to false void UpdateAppItemState(mvAppItemState& state); // standard imgui update +// Retrieves scroll info; only applicable to containers. Does NOT update state.lastFrameUpdate +// and therefore MUST be called in the same branch where that variable is updated +// by the caller (or where UpdateAppItemState() gets called). +void UpdateAppItemScrollInfo(mvAppItemState& state); // return actual value if frame is active b8 IsItemHovered (mvAppItemState& state, i32 frameDelay = 0); @@ -81,12 +86,20 @@ struct mvAppItemState b8 deactivatedAfterEdit = false; b8 toggledOpen = false; b8 mvRectSizeResized = false; + b8 scrolledX = false; + b8 scrolledY = false; + // isScrolling flags are not implemented yet - we need support on ImGui side. + // They are here just as a reserved placeholder for future implementation. + b8 isScrollingX = false; + b8 isScrollingY = false; mvVec2 rectMin = { 0.0f, 0.0f }; mvVec2 rectMax = { 0.0f, 0.0f }; mvVec2 rectSize = { 0.0f, 0.0f }; mvVec2 mvPrevRectSize = { 0.0f, 0.0f }; mvVec2 pos = { 0.0f, 0.0f }; mvVec2 contextRegionAvail = { 0.0f, 0.0f }; + mvVec2 scrollPos = { 0.0f, 0.0f }; + mvVec2 scrollMax = { 0.0f, 0.0f }; b8 ok = true; i32 lastFrameUpdate = 0; // last frame update occured mvAppItem* parent = nullptr; // hacky, but quick fix for widget handlers diff --git a/src/mvAppItemTypes.inc b/src/mvAppItemTypes.inc index 249db0b13..5ecffb450 100644 --- a/src/mvAppItemTypes.inc +++ b/src/mvAppItemTypes.inc @@ -130,6 +130,7 @@ X( mvToggledOpenHandler ) \ X( mvClickedHandler ) \ X( mvDoubleClickedHandler ) \ + X( mvScrollHandler ) \ X( mvDragPayload ) \ X( mvResizeHandler ) \ X( mvFont ) \ diff --git a/src/mvContainers.cpp b/src/mvContainers.cpp index 9b097909b..367c9f39c 100644 --- a/src/mvContainers.cpp +++ b/src/mvContainers.cpp @@ -927,6 +927,8 @@ DearPyGui::draw_child_window(ImDrawList* drawlist, mvAppItem& item, mvChildWindo { ScopedID id(item.uuid); + item.handleImmediateScroll(); + // TODO: Do we want to put an if statement to prevent further drawing if not shown? ImGui::BeginChild(item.info.internalLabel.c_str(), ImVec2(config.autosize_x ? 0 : (float)item.config.width, config.autosize_y ? 0 : (float)item.config.height), config.childFlags, config.windowflags); item.state.lastFrameUpdate = GContext->frame; @@ -951,23 +953,8 @@ DearPyGui::draw_child_window(ImDrawList* drawlist, mvAppItem& item, mvChildWindo } - if (config._scrollXSet) - { - if (config.scrollX < 0.0f) - ImGui::SetScrollHereX(1.0f); - else - ImGui::SetScrollX(config.scrollX); - config._scrollXSet = false; - } - - if (config._scrollYSet) - { - if (config.scrollY < 0.0f) - ImGui::SetScrollHereY(1.0f); - else - ImGui::SetScrollY(config.scrollY); - config._scrollYSet = false; - } + item.handleDelayedScroll(); + item.config.scrollXFlags = item.config.scrollYFlags = mvSetScrollFlags_None; // allows this item to have a render callback if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) @@ -984,10 +971,7 @@ DearPyGui::draw_child_window(ImDrawList* drawlist, mvAppItem& item, mvChildWindo } - config.scrollX = ImGui::GetScrollX(); - config.scrollY = ImGui::GetScrollY(); - config.scrollMaxX = ImGui::GetScrollMaxX(); - config.scrollMaxY = ImGui::GetScrollMaxY(); + UpdateAppItemScrollInfo(item.state); ImGui::EndChild(); } @@ -1502,6 +1486,8 @@ DearPyGui::draw_window(ImDrawList* drawlist, mvAppItem& item, mvWindowAppItemCon ImGui::SetNextWindowSizeConstraints(config.min_size, config.max_size); + item.handleImmediateScroll(); + if (config.modal) { if (item.info.shownLastFrame) @@ -1640,28 +1626,10 @@ DearPyGui::draw_window(ImDrawList* drawlist, mvAppItem& item, mvWindowAppItemCon // handle popping themes cleanup_local_theming(&item); - if (config._scrollXSet) - { - if (config.scrollX < 0.0f) - ImGui::SetScrollHereX(1.0f); - else - ImGui::SetScrollX(config.scrollX); - config._scrollXSet = false; - } - - if (config._scrollYSet) - { - if (config.scrollY < 0.0f) - ImGui::SetScrollHereY(1.0f); - else - ImGui::SetScrollY(config.scrollY); - config._scrollYSet = false; - } + item.handleDelayedScroll(); + item.config.scrollXFlags = item.config.scrollYFlags = mvSetScrollFlags_None; - config.scrollX = ImGui::GetScrollX(); - config.scrollY = ImGui::GetScrollY(); - config.scrollMaxX = ImGui::GetScrollMaxX(); - config.scrollMaxY = ImGui::GetScrollMaxY(); + UpdateAppItemScrollInfo(item.state); //----------------------------------------------------------------------------- // update state diff --git a/src/mvContainers.h b/src/mvContainers.h index 56ed8612c..a5390c7fc 100644 --- a/src/mvContainers.h +++ b/src/mvContainers.h @@ -101,12 +101,6 @@ struct mvChildWindowConfig bool autosize_x = false; bool autosize_y = false; ImGuiWindowFlags windowflags = ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_NavFlattened; - float scrollX = 0.0f; - float scrollY = 0.0f; - float scrollMaxX = 0.0f; - float scrollMaxY = 0.0f; - bool _scrollXSet = false; - bool _scrollYSet = false; }; struct mvTreeNodeConfig @@ -173,13 +167,7 @@ struct mvWindowAppItemConfig mvPyObject on_close = nullptr; mvVec2 min_size = { 100.0f, 100.0f }; mvVec2 max_size = { 30000.0f, 30000.0f }; - float scrollX = 0.0f; - float scrollY = 0.0f; - float scrollMaxX = 0.0f; - float scrollMaxY = 0.0f; bool _collapsedDirty = true; - bool _scrollXSet = false; - bool _scrollYSet = false; ImGuiWindowFlags _oldWindowflags = ImGuiWindowFlags_None; float _oldxpos = 200; float _oldypos = 200; diff --git a/src/mvItemHandlers.cpp b/src/mvItemHandlers.cpp index f8491a475..2c77ac5b7 100644 --- a/src/mvItemHandlers.cpp +++ b/src/mvItemHandlers.cpp @@ -119,6 +119,14 @@ void mvItemHandlerRegistry::onBind(mvAppItem* item) break; } + case mvAppItemType::mvScrollHandler: + { + if (!(applicableState & ~MV_STATE_SCROLL)) + mvThrowPythonError(mvErrorCode::mvNone, "bind_item_handler_registry", + "Item Handler Registry includes inapplicable handler: mvScrollHandler", item); + break; + } + default: break; } } @@ -347,3 +355,45 @@ void mvVisibleHandler::customAction(void* data) submitHandler(state->parent); } } + +PyObject* mvScrollHandler::makeAppData(mvUUID uuid, const std::string& alias, mvScrollDirection dir, bool isScrolling, float pos, float max) +{ + PyObject* app_data = PyTuple_New(5); + PyTuple_SetItem(app_data, 0, ToPyUUID(uuid, alias)); + PyTuple_SetItem(app_data, 1, ToPyInt(dir)); + PyTuple_SetItem(app_data, 2, ToPyFloat(pos)); + PyTuple_SetItem(app_data, 3, ToPyFloat(max)); + PyTuple_SetItem(app_data, 4, ToPyBool(isScrolling)); + return app_data; +} + +void mvScrollHandler::customAction(void* data) +{ + mvAppItemState* state = static_cast(data); + mvAppItem* parent = state->parent; + if (state->scrolledX) + { + submitCallbackEx([=, + uuid=parent->uuid, + alias=parent->config.alias, + isScrolling=state->isScrollingX, + pos=state->scrollPos.x, + max=state->scrollMax.x] () + { + return makeAppData(uuid, alias, mvScrollDirection_XAxis, isScrolling, pos, max); + }); + } + + if (state->scrolledY) + { + submitCallbackEx([=, + uuid=parent->uuid, + alias=parent->config.alias, + isScrolling=state->isScrollingY, + pos=state->scrollPos.y, + max=state->scrollMax.y] () + { + return makeAppData(uuid, alias, mvScrollDirection_YAxis, isScrolling, pos, max); + }); + } +} diff --git a/src/mvItemHandlers.h b/src/mvItemHandlers.h index d32a91d06..08f59bca2 100644 --- a/src/mvItemHandlers.h +++ b/src/mvItemHandlers.h @@ -159,3 +159,23 @@ class mvVisibleHandler : public mvItemHandler void draw(ImDrawList* drawlist, float x, float y) override {} void customAction(void* data = nullptr) override; }; + +enum mvScrollDirection +{ + // These constants can be used as a combination of flags + mvScrollDirection_XAxis = 1, + mvScrollDirection_Horizontal = 1, + mvScrollDirection_YAxis = 2, + mvScrollDirection_Vertical = 2, +}; + +class mvScrollHandler : public mvItemHandler +{ +public: + explicit mvScrollHandler(mvUUID uuid) : mvItemHandler(uuid) {} + void draw(ImDrawList* drawlist, float x, float y) override {} + void customAction(void* data = nullptr) override; + +private: + static PyObject* makeAppData(mvUUID uuid, const std::string& alias, mvScrollDirection dir, bool isScrolling, float pos, float max); +}; diff --git a/src/mvPyUtils.cpp b/src/mvPyUtils.cpp index 2ddd70a45..782bbe684 100644 --- a/src/mvPyUtils.cpp +++ b/src/mvPyUtils.cpp @@ -570,6 +570,17 @@ ToPyPair(const std::string& x, const std::string& y) return Py_BuildValue("[ss]", x.c_str(), y.c_str()); } +PyObject* +ToPyTPair(bool x, bool y) +{ + PyObject* result = PyTuple_New(2); + + PyTuple_SetItem(result, 0, ToPyBool(x)); + PyTuple_SetItem(result, 1, ToPyBool(y)); + + return result; +} + PyObject* ToPyList(const std::vector& value) { diff --git a/src/mvPyUtils.h b/src/mvPyUtils.h index 5e83cbd38..252f71f1d 100644 --- a/src/mvPyUtils.h +++ b/src/mvPyUtils.h @@ -151,6 +151,7 @@ PyObject* ToPyPair (float x, float y); PyObject* ToPyPair (double x, double y); PyObject* ToPyPairII(int x, int y); PyObject* ToPyPair (const std::string& x, const std::string& y); +PyObject* ToPyTPair (bool x, bool y); // tuple-based pair (unlike other pairs that are list-based) PyObject* ToPyList (const std::vector& value); PyObject* ToPyList (const std::vector& value); PyObject* ToPyList (const std::vector& value); diff --git a/src/mvTables.cpp b/src/mvTables.cpp index 4bcd43809..b56df39c0 100644 --- a/src/mvTables.cpp +++ b/src/mvTables.cpp @@ -5,6 +5,7 @@ #include "mvPyUtils.h" #include "mvFontItems.h" #include "mvThemes.h" +#include "mvItemHandlers.h" mvTableCell::mvTableCell(mvUUID uuid) : mvAppItem(uuid) @@ -225,6 +226,8 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y) } }; + handleImmediateScroll(); + if (ImGui::BeginTable(info.internalLabel.c_str(), _columns, _flags, ImVec2((float)config.width, (float)config.height), (float)_inner_width)) { @@ -374,27 +377,11 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y) columnnum++; } - if (_scrollXSet) - { - if (_scrollX < 0.0f) - ImGui::SetScrollHereX(1.0f); - else - ImGui::SetScrollX(_scrollX); - _scrollXSet = false; - } - if (_scrollYSet) - { - if (_scrollY < 0.0f) - ImGui::SetScrollHereY(1.0f); - else - ImGui::SetScrollY(_scrollY); - _scrollYSet = false; - } + // TODO: maybe it should actually go after EndTable + handleDelayedScroll(); + config.scrollXFlags = config.scrollYFlags = mvSetScrollFlags_None; - _scrollX = ImGui::GetScrollX(); - _scrollMaxX = ImGui::GetScrollMaxX(); - _scrollY = ImGui::GetScrollY(); - _scrollMaxY = ImGui::GetScrollMaxY(); + UpdateAppItemScrollInfo(state); ImGui::EndTable(); @@ -414,6 +401,8 @@ void mvTable::draw(ImDrawList* drawlist, float x, float y) // handle popping themes cleanup_local_theming(this); + if (handlerRegistry) + handlerRegistry->checkEvents(&state); } void mvTable::onChildAdd(std::shared_ptr item) diff --git a/src/mvTables.h b/src/mvTables.h index c2d7b19e6..6bf137060 100644 --- a/src/mvTables.h +++ b/src/mvTables.h @@ -76,13 +76,6 @@ class mvTable : public mvAppItem bool _tableHeader = true; bool _useClipper = false; - float _scrollX = 0.0f; - float _scrollY = 0.0f; - float _scrollMaxX = 0.0f; - float _scrollMaxY = 0.0f; - bool _scrollXSet = false; - bool _scrollYSet = false; - std::vector _columnColorsSet; std::vector _rowColorsSet; std::vector _rowSelectionColorsSet;