NEED HELP: Menu Auto-Scroll Issue

So I tried making the QoL feature, Auto-Scrolling for UI pages like Options.

Problem is it only fires once instead of looping when the button is held.

Here’s how the set up looks:

Input is Received from Player Pawn.
MenuBase→PageTarget→BPF_PageNav_Handle.

    UFUNCTION()
    void BPF_PageNav_Handle(ECndWidget_HandleActions Handle_Action, bool ButtonState);


    UFUNCTION()
    void BPF_Page_NavigateItem(ECndWidget_HandleActions Handle_Action, bool ButtonState);
    
    UFUNCTION()
    void BPF_Page_NavigateValue(ECndWidget_HandleActions Handle_Action, bool ButtonState);

    void BPF_Page_NavigateAutoscroll(ECndWidget_HandleActions Handle_Action, bool ButtonState, float Speed);
   
    UFUNCTION()
    void BPF_PageNav_AutoScroll_Clear();



// Triggered From Menu Base
void UCndWgt_MenuPage_Master::BPF_PageNav_Handle(ECndWidget_HandleActions Handle_Action, bool ButtonState)
{

if (ButtonState)
{

    switch (Handle_Action)
    {

        // Page Item Selection

    case HA_ItemUp:
    case HA_ItemDown:
    case HA_ItemNext:
    case HA_ItemPrev:
    {

        switch (Window_Active.Type)
        {
        case WindowType_Vertical:
        {

            switch (Handle_Action)
            {

                // Page Item Selection

            case HA_ItemUp:
            case HA_ItemDown:
            {
                BPF_Page_NavigateItem(Handle_Action, ButtonState);

                break;

            }
            case HA_ItemNext:
            case HA_ItemPrev:
            {

                BPF_Page_NavigateValue(Handle_Action, ButtonState);

                break;
            }

            break;
        }

            break;
        }

        }



        break;
    }

}

void UCndWgt_MenuPage_Master::BPF_Page_NavigateItem(ECndWidget_HandleActions Handle_Action, bool ButtonState)
{

UCndWgt_MenuParts_ListItem_Master* Item_Desired = nullptr;

if (ButtonState)
{


}

// Check if we’re not scrolling item’s value right now.
if (!Data.IsAutoScrolling_Values)
{
if (CanAutoScroll_Items)
{
if (AutoScroll_Speed_Items > 0.09f)
{
if (!Data.IsAutoScrolling_Items)
{

            if (CND_DebugLog)
            {

                UE_LOG(LogTemp, Warning,
                    TEXT("%s | Autoscrolling Started - Items [%s] [%s] Speed: [%s]"),
                    *CND_DebugString,
                    *UEnum::GetValueAsString(Handle_Action),
                    ButtonState ? TEXT("True") : TEXT("False"),
                    *FString::SanitizeFloat(AutoScroll_Speed_Items)
                );

            }
           
            Data.TDEL_UI_AutoScroll.Unbind();
            Data.TDEL_UI_AutoScroll.BindUFunction(this, FName("BPF_Page_NavigateItem"), Handle_Action, ButtonState);

            Data.IsAutoScrolling_Items = true;

        }

        if (Data.IsAutoScrolling_Items)
        {

            BPF_Page_NavigateAutoscroll(Handle_Action, ButtonState, AutoScroll_Speed_Items);

        }
    }
}

}

}



void UCndWgt_MenuPage_Master::BPF_Page_NavigateAutoscroll(ECndWidget_HandleActions Handle_Action, bool ButtonState, float Speed)
{

    if (CND_DebugLog)
    {

        UE_LOG(LogTemp, Warning,
            TEXT("%s | Autoscrolling... [%s] [%s]"),
            *CND_DebugString,
            *UEnum::GetValueAsString(Handle_Action),
            ButtonState ? TEXT("True") : TEXT("False")
        );

    }

    // Delay: Set up a timer for Action
    GetWorld()->GetTimerManager().SetTimer(
        Data.TH_UI_AutoScroll,
        Data.TDEL_UI_AutoScroll,
        Speed,
        false
    );

}

Bump.

I don’t see Data.IsAutoScrolling_Items = true; turned false anywhere. I suspect this one. Since it’s true then the next check (if timing etc everything goes fine) it will go line if(!Data.IsAutoScrolling_Items) and fail there is no retry method, after if you can make an else try clearing and setting it once more timer, especially if not existing.

Also you don’t really need to make even a timer for this, you can do even with a repeating timer, however if this is a button held method you can do like below, if you press once and it should auto scroll (carousel like) then either loop timer or you need to match timings and make Data.IsAutoScrolling_Items = false; somewhere.

In your BPF_PageNav_Handle function, make a new function as CanAutoScroll

inside

bool UCndWgt_MenuPage_Master::CanAutoScroll(bool& Status)
{
CanAutoScroll = False;
if (!ScrollInProgress && !WhateverCondition)

{
CanAutoScroll = True;
}
Return CanAutoScroll;
}

This way you can always send events from the input to function BPF_PageNav_Handle and it will scrolll whenever it can, if


void UCndWgt_MenuPage_Master::BPF_Page_NavigateItem(ECndWidget_HandleActions Handle_Action, bool ButtonState)
{

// Check if function ready to handle.
if (!CanAutoScroll)
   {
      Return;
   }


UCndWgt_MenuParts_ListItem_Master* Item_Desired = nullptr;

if (ButtonState)
{
//Rest of the things



}

I forgot about showing these:

// Navigation Root - Triggered From Menu Base - ButtonState = Button Held
void UCndWgt_MenuPage_Master::BPF_PageNav_Handle(ECndWidget_HandleActions Handle_Action, bool ButtonState)
{

if (ButtonState)
else
{
if (Data.IsAutoScrolling_Items || Data.IsAutoScrolling_Values)
{

    UE_LOG(LogTemp, Warning,
        TEXT("%s | Autoscrolling Stopped [%s] [%s]"),
        *CND_DebugString,
        *UEnum::GetValueAsString(Handle_Action),
        ButtonState ? TEXT("True") : TEXT("False")
    );

    // Stop Auto-Scroll
    BPF_PageNav_AutoScroll_Clear();
    
}

}

}

void UCndWgt_MenuPage_Master::BPF_PageNav_AutoScroll_Clear()
{

    // Delay: Clear the timer for Action
    GetWorld()->GetTimerManager().ClearTimer(Data.TH_UI_AutoScroll);

    Data.IsAutoScrolling_Items = false;
    Data.IsAutoScrolling_Values = false;

    Data.TDEL_UI_AutoScroll.Unbind();



}

I think it didn’t change the fact that

  1. Player presses button
  2. NavigateItem runs
  3. Sets IsAutoScrolling_Items = true
  4. Timer scheduled
  5. Timer fired → calls NavigateItem() again
  6. Now guarded → nothing happens → time not scheduled so that loop stops even the fact you are holding the button (With the else changed to If(ButtonState) and Else now only we clear timer so it stops scrolling.

But it doesn’t changes the fact that when you press button 1st time Data = False turns True and you run timer which delegates to same function now its guarded again with Data = True and nothing happens. Timer won’t run again.

Can you print your log and full code it you are turning something false from somewhere else.

This pattern a bit fragile imo, what you can do simply you can loop the timer and you will see it that it will start working. Since its looping in the background, if its guarded during scroll moment it would be rejected, if pass will continue scroll anyway. This is the solution.

Also if you don’t want to loop timer you can do something like below to check if autoscrolling and if there is no active timer (since it will finish) you can schedule a new one and it will run.

UFUNCTION()
void BPF_Page_NavigateItem(ECndWidget_HandleActions Handle_Action, bool ButtonState);

UFUNCTION()
void BPF_Page_NavigateValue(ECndWidget_HandleActions Handle_Action, bool ButtonState);

void BPF_Page_NavigateAutoscroll(ECndWidget_HandleActions Handle_Action, bool ButtonState, float Speed);

UFUNCTION()
void BPF_PageNav_AutoScroll_Clear();


// Triggered From Menu Base
void UCndWgt_MenuPage_Master::BPF_PageNav_Handle(ECndWidget_HandleActions Handle_Action, bool ButtonState)
{
	if (ButtonState)
	{
		switch (Handle_Action)
		{
		// Page Item Selection

		case HA_ItemUp:
		case HA_ItemDown:
		case HA_ItemNext:
		case HA_ItemPrev:
			{
				switch (Window_Active.Type)
				{
				case WindowType_Vertical:
					{
						switch (Handle_Action)
						{
						// Page Item Selection

						case HA_ItemUp:
						case HA_ItemDown:
							{
								BPF_Page_NavigateItem(Handle_Action, ButtonState);

								break;
							}
						case HA_ItemNext:
						case HA_ItemPrev:
							{
								BPF_Page_NavigateValue(Handle_Action, ButtonState);

								break;
							}

							break;
						}

						break;
					}
				}


				break;
			}
		}
	}
	else
	{
		if (Data.IsAutoScrolling_Items || Data.IsAutoScrolling_Values)
		{
			UE_LOG(LogTemp, Warning,
			       TEXT("%s | Autoscrolling Stopped [%s] [%s]"),
			       *CND_DebugString,
			       *UEnum::GetValueAsString(Handle_Action),
			       ButtonState ? TEXT("True") : TEXT("False")
			);

			// Stop Auto-Scroll
			BPF_PageNav_AutoScroll_Clear();
		}
	}

	void UCndWgt_MenuPage_Master::BPF_Page_NavigateItem(ECndWidget_HandleActions Handle_Action, bool ButtonState)
	{
		UCndWgt_MenuParts_ListItem_Master* Item_Desired = nullptr;


		// Button true , 1st click, bind and set true. Now executes scrolling data.
		if (!Data.IsAutoScrolling_Items)
		{
			Data.TDEL_UI_AutoScroll.Unbind();
			Data.TDEL_UI_AutoScroll.BindUFunction(
				this,
				FName("BPF_Page_NavigateItem"),
				Handle_Action,
				ButtonState
			);

			Data.IsAutoScrolling_Items = true;
		}

		// Continuation since we set true it will run every time here. but will check if timer is active, if not then will schedule a new one.
		//
		if (Data.IsAutoScrolling_Items &&
			!GetWorld()->GetTimerManager().IsTimerActive(Data.TH_UI_AutoScroll))
		{
			BPF_Page_NavigateAutoscroll(
				Handle_Action,
				ButtonState,
				AutoScroll_Speed_Items
			);
		}
	}


	void UCndWgt_MenuPage_Master::BPF_Page_NavigateAutoscroll(ECndWidget_HandleActions Handle_Action, bool ButtonState,
	                                                          float Speed)
	{
		if (CND_DebugLog)
		{
			UE_LOG(LogTemp, Warning,
			       TEXT("%s | Autoscrolling... [%s] [%s]"),
			       *CND_DebugString,
			       *UEnum::GetValueAsString(Handle_Action),
			       ButtonState ? TEXT("True") : TEXT("False")
			);
		}

		// Delay: Set up a timer for Action
		GetWorld()->GetTimerManager().SetTimer(
			Data.TH_UI_AutoScroll,
			Data.TDEL_UI_AutoScroll,
			Speed,
			false
		);
	}


	void UCndWgt_MenuPage_Master::BPF_PageNav_AutoScroll_Clear()
	{
		// Delay: Clear the timer for Action
		GetWorld()->GetTimerManager().ClearTimer(Data.TH_UI_AutoScroll);

		Data.IsAutoScrolling_Items = false;
		Data.IsAutoScrolling_Values = false;

		Data.TDEL_UI_AutoScroll.Unbind();
	}

but without even that you set timer looping, Bind it once. When timer finishes if can scroll will do its job or you have to track when scrolling finishes and set IsAutoScrolling_Items = false at that moment.

So what I would recommend as pattern besides fixing it.

  1. Player presses button
  2. NavigateItem runs (This only acts as gateway branch switch on case)
  3. PerformScrollingItem () - New function this sets Sets IsAutoScrolling_Items = true and makes the navigation. At the beginning and end of this function, you run an event OnScrollingItem.Broadcast (bStatus) . So you inform system about the task start and end.
  4. You bind to these function somewhere, OnScrollingItem(False)->ShouldScroll?->CanScroll->Then ->NavigateItem(). You don’t even need bools since its event based.

In this your action of button press is a request, request will do something like TryNavigate. Then after it performs the navigation PerformNavigation()->OnFinishPerformNavigation callback should let system know that, we are ready for the next one. I would recommend this since its safer in many terms but ofcourse not super necessary.

If you drop the whole code block I can try making a slighly safer version.

Okay then, detailed code block and new version of it.

No need to make “safer” version or whatever.
Everything is set right. No issues with held input.


// Navigation Root - Triggered From Menu Base - ButtonState = Button Heldvoid UCndWgt_MenuPage_Master::BPF_PageNav_Handle(ECndWidget_HandleActions Handle_Action, bool ButtonState)

{

if (CND_DebugLog)
{

    if (!Data.IsAutoScrolling_Items || !Data.IsAutoScrolling_Values)
    {


        UE_LOG(LogTemp, Warning, TEXT("%s | Nav Triggered - Handle: [%s]. WindowType [%s]. State: [%s]."),
            *CND_DebugString,
            *UEnum::GetValueAsString(Handle_Action),
            *UEnum::GetValueAsString(Data.Active.Window),
            ButtonState ? TEXT("True") : TEXT("False")
        );
    }
}

if (ButtonState)
{

    switch (Handle_Action)
    {

        // Page Item Selection
        

    case HA_ItemUp:
    case HA_ItemDown:
    case HA_ItemNext:
    case HA_ItemPrev:
    {

        switch (Window_Active.Type)
        {
        case WindowType_Vertical:
        {

            switch (Handle_Action)
            {

                // Page Item Selection

            case HA_ItemUp:
            case HA_ItemDown:
            {
                BPF_Page_NavigateItem(Handle_Action, ButtonState);

                break;

            }
            case HA_ItemNext:
            case HA_ItemPrev:
            {

                BPF_Page_NavigateValue(Handle_Action, ButtonState);

                break;
            }

            break;
        }

            break;
        }

        }



        break;
    }

}

}

    else
    {


        if (Data.IsAutoScrolling_Items || Data.IsAutoScrolling_Values)
        {

            UE_LOG(LogTemp, Warning,
                TEXT("%s | Autoscrolling Stopped [%s] [%s]"),
                *CND_DebugString,
                *UEnum::GetValueAsString(Handle_Action),
                ButtonState ? TEXT("True") : TEXT("False")
            );

            // Stop Auto-Scroll
            BPF_PageNav_AutoScroll_Clear();
            
        }
    }

}

void UCndWgt_MenuPage_Master::BPF_Page_NavigateItem(ECndWidget_HandleActions Handle_Action, bool ButtonState)
{

    // Prevent Cross-Scrolling Values (Check if we're not scrolling values right now.)
    if (!Data.IsAutoScrolling_Values)
    {

        UCndWgt_MenuParts_ListItem_Master* Item_Desired = nullptr;

        if (ButtonState)
        {

            // If not valid.
            if (!Window_Active.Item[VAL_Current])
            {
                if (Window_Active.Items.Num() > 0)
                {
                    Window_Active.Item[VAL_Current] = Window_Active.Items[0];

                    Item_Desired = Window_Active.Item[VAL_Current];
                }
            }
            else
            {

                auto* Current = Window_Active.Item[VAL_Current];

                // Navigate Via Neighoubring Widget Positions
                Item_Desired = BPF_PageNav_FindNeighbour(Current, Handle_Action);

            }

            if (Item_Desired)
            {
                BPF_PageItem_Highlight(Item_Desired, true);
            }

            if (CanAutoScroll_Items)
            {

                if (!Data.IsAutoScrolling_Items)
                {
                    Data.HA_Last = Handle_Action;
                    Data.Delay = NAV_Delay_Items;

                    BPF_PageNav_AutoScroll_Start(AutoScroll_Speed_Items_Initial);
                }
                else
                {
                    if (AutoScroll_Speed_Items > 0.09f)
                    {

                        if (CND_DebugLog)
                        {

                            UE_LOG(LogTemp, Warning,
                                TEXT("%s | Autoscrolling Items - Detected"),
                                *CND_DebugString
                            );

                        }

                        BPF_PageNav_Autoscroll(AutoScroll_Speed_Items);
                    }
                }
            }
        }
    }

    if (Data.IsAutoScrolling_Items)
    {
        if (CND_DebugLog)
        {

            UE_LOG(LogTemp, Warning,
                TEXT("%s | Autoscrolling Items - Detected | State [%s] - Action [%s] - Speed [%s] - Can Scroll Values [%s]"),
                *CND_DebugString,
                ButtonState ? TEXT("True") : TEXT("False"),
                *UEnum::GetValueAsString(Handle_Action),
                *FString::SanitizeFloat(AutoScroll_Speed_Items),
                Data.IsAutoScrolling_Values ? TEXT("True") : TEXT("False")
            );

        }
    }
}

void UCndWgt_MenuPage_Master::BPF_Page_NavigateValue(ECndWidget_HandleActions Handle_Action, bool ButtonState)
{

    // Prevent Cross-Scrolling Items (Check if we're not scrolling items right now.)
    if (!Data.IsAutoScrolling_Items)
    {

        UCndWgt_MenuParts_ListItem_Master* Item_Desired = nullptr;

        if (ButtonState)
        {
            if (Window_Active.Item[VAL_Current])
            {
                Item_Desired = Window_Active.Item[VAL_Current];

                if (ButtonState) Item_Desired->ED_OnItem_Interacted.Broadcast(Handle_Action, ButtonState);

                if (CanAutoScroll_Values)
                {

                    switch (Item_Desired->ItemType)
                    {

                    case LI_SelectionList:
                    case LI_NumSlider:
                    {


                        if (!Data.IsAutoScrolling_Values)
                        {
                            Data.HA_Last = Handle_Action;
                            Data.Delay = NAV_Delay_Value;

                            BPF_PageNav_AutoScroll_Start(AutoScroll_Speed_Values_Initial);
                        }
                        else
                        {
                            if (AutoScroll_Speed_Values > 0.09f)
                            {

                                if (CND_DebugLog)
                                {

                                    UE_LOG(LogTemp, Warning,
                                        TEXT("%s | Autoscrolling Values - Detected"),
                                        *CND_DebugString
                                    );

                                }

                                BPF_PageNav_Autoscroll(AutoScroll_Speed_Values);
                            }
                        }

                        break;
                    }


                    }
                }
            }
        }
    }
    if (Data.IsAutoScrolling_Values)
    {
        if (CND_DebugLog)
        {

            UE_LOG(LogTemp, Warning,
                TEXT("%s | Autoscrolling Items - Detected | State [%s] - Action [%s] - Speed [%s] - Can Scroll Items [%s]"),
                *CND_DebugString,
                ButtonState ? TEXT("True") : TEXT("False"),
                *UEnum::GetValueAsString(Handle_Action),
                *FString::SanitizeFloat(AutoScroll_Speed_Values),
                Data.IsAutoScrolling_Items ? TEXT("True") : TEXT("False")
            );

        }
    }

}

void UCndWgt_MenuPage_Master::BPF_PageNav_AutoScroll_Start(float Speed)
{

    Data.TDEL_UI_AutoScroll_Start.Unbind();
    

    switch (Data.Delay)
    {

    case NAV_Delay_Items:
    {

        if (CND_DebugLog)
        {

            UE_LOG(LogTemp, Warning,
                TEXT("%s | Autoscrolling Started - Items [%s] Speed: [%s]."),
                *CND_DebugString,
                *UEnum::GetValueAsString(Data.HA_Last),
                *FString::SanitizeFloat(AutoScroll_Speed_Items_Initial)
            );

        }

        Data.IsAutoScrolling_Items = true;

        Data.TDEL_UI_AutoScroll_Start.BindUFunction(this, FName("BPF_Page_NavigateItem"), Data.HA_Last, true);
        Data.TDEL_UI_AutoScroll_Loop.BindUFunction(this, FName("BPF_Page_NavigateItem"), Data.HA_Last, true);

        break;
    }
    case NAV_Delay_Value:
    {

        if (CND_DebugLog)
        {

            UE_LOG(LogTemp, Warning,
                TEXT("%s | Autoscrolling Started - Value [%s] Speed: [%s]."),
                *CND_DebugString,
                *UEnum::GetValueAsString(Data.HA_Last),
                *FString::SanitizeFloat(AutoScroll_Speed_Values_Initial)
            );

        }

        Data.IsAutoScrolling_Values = true;

        Data.TDEL_UI_AutoScroll_Start.BindUFunction(this, FName("BPF_Page_NavigateValue"), Data.HA_Last, true);
        Data.TDEL_UI_AutoScroll_Loop.BindUFunction(this, FName("BPF_Page_NavigateValue"), Data.HA_Last, true);

        break;
    }

    }

    GetWorld()->GetTimerManager().SetTimer(
        Data.TH_UI_AutoScroll_Start,
        Data.TDEL_UI_AutoScroll_Start,
        Speed,
        false
    );

}

void UCndWgt_MenuPage_Master::BPF_PageNav_Autoscroll(float Speed)
{

    switch (Data.Delay)
    {

    case NAV_Delay_Items:
    {

        if (CND_DebugLog)
        {

            UE_LOG(LogTemp, Warning,
                TEXT("%s | Autoscrolling Items... Speed: [%s]"),
                *CND_DebugString,
                *FString::SanitizeFloat(AutoScroll_Speed_Items)
            );

        }

        //BPF_Page_NavigateItem(Data.HA_Last, true);

        break;
    }
    case NAV_Delay_Value:
    {

        if (CND_DebugLog)
        {

            UE_LOG(LogTemp, Warning,
                TEXT("%s | Autoscrolling Values... Speed: [%s]"),
                *CND_DebugString,
                *FString::SanitizeFloat(AutoScroll_Speed_Values)
            );

        }

        //BPF_Page_NavigateValue(Data.HA_Last, true);

        break;
    }

    }

    // Delay
    GetWorld()->GetTimerManager().SetTimer(
        Data.TH_UI_AutoScroll_Loop,
        Data.TDEL_UI_AutoScroll_Loop,
        Speed,
        false
    );

}

void UCndWgt_MenuPage_Master::BPF_PageNav_AutoScroll_Clear()
{

    // Delay: Clear the timer for Action
    GetWorld()->GetTimerManager().ClearTimer(Data.TH_UI_AutoScroll_Start);
    GetWorld()->GetTimerManager().ClearTimer(Data.TH_UI_AutoScroll_Loop);

    Data.IsAutoScrolling_Items = false;
    Data.IsAutoScrolling_Values = false;

    Data.TDEL_UI_AutoScroll_Start.Unbind();
    Data.TDEL_UI_AutoScroll_Loop.Unbind();

}

But logs show some sort of bool anomaly:

LogTemp: Warning: CND_DEBUG (Player Pawn) | BP_IA_MenuHandle - Handle: [HA_ItemDown]. State: [True].

LogTemp: Warning: CND_DEBUG (Widget) | Menu Base | BPF_MenuNav_Handle - [Page: CndPage_UI_Options] [Menu Type: CndMenuType_Shared] [Handle Action: HA_ItemDown] [State: True].

LogTemp: Warning: CND_DEBUG (Widget - [CndPage_UI_Options]) | Nav Triggered - Handle: [HA_ItemDown]. WindowType [WindowType_Vertical]. State: [True].

LogTemp: Warning: CND_DEBUG (Widget - [CndPage_UI_Options]) | Autoscrolling Started - Items [HA_ItemDown] Speed: [0.25].

LogTemp: Warning: CND_DEBUG (Widget - [CndPage_UI_Options]) | Autoscrolling Items - Detected | State [True] - Action [HA_ItemDown] - Speed [0.125] - Can Scroll Values [False]

LogTemp: Warning: CND_DEBUG (Widget - [CndPage_UI_Options]) | Autoscrolling Items - Detected | State [False] - Action [HA_ItemDown] - Speed [0.125] - Can Scroll Values [False]

LogTemp: Warning: CND_DEBUG: Windows Controller Type: PC

LogTemp: Warning: CND_DEBUG: Windows Controller Type: PC

LogTemp: Warning: CND_DEBUG: Windows Controller Type: PC

LogTemp: Warning: CND_DEBUG: Windows Controller Type: PC

LogTemp: Warning: CND_DEBUG: Windows Controller Type: PC

LogTemp: Warning: CND_DEBUG: Windows Controller Type: PC

LogTemp: Warning: CND_DEBUG: Windows Controller Type: PC

LogTemp: Warning: CND_DEBUG: Windows Controller Type: PC

LogTemp: Warning: CND_DEBUG (Player Pawn) | BP_IA_MenuHandle - Handle: [HA_ItemDown]. State: [False].

LogTemp: Warning: CND_DEBUG (Widget) | Menu Base | BPF_MenuNav_Handle - [Page: CndPage_UI_Options] [Menu Type: CndMenuType_Shared] [Handle Action: HA_ItemDown] [State: False].

LogTemp: Warning: CND_DEBUG (Widget - [CndPage_UI_Options]) | Nav Triggered - Handle: [HA_ItemDown]. WindowType [WindowType_Vertical]. State: [False].

LogTemp: Warning: CND_DEBUG (Widget - [CndPage_UI_Options]) | Autoscrolling Stopped [HA_ItemDown] [False]

You mean [State: False] ? If thats the anomaly, its first coming true, since it triggered from button but after that doesn’t check button state, timer triggers it.

That’s is something I mentioned previous post actually, event driven vs timer driven. Right now you press once and timer continues and triggers till button is up. But input doesn’t call function every tick to try run it. Instead when timer finished it auto runs. That is the distinction input driven gate vs timer gates.

However if you are looking for a fix minimal for that without changing structure and declerations. You can have an additional global variable as bButtonPressed or an enum if you like. Then when you first call BPF_PageNav_Handle you can do bButtonPressed = ButtonState and use global bool variable as debug print out. Since its global not run from timer it would turn true always untill the button is released. Ofcourse you have to also on release set global bButtonPressed = false;

This feels a bit complex for a menu scroller. Is there a specific reason for using timers here? Since this is mostly a QoL feature, it could probably be implemented in a simpler way.

You have the BPF_PageNav_Handle → PerformAction-> Callback Event → ShouldScrollNext()

ShouldScrollNext()
{
    if (ButtonStillHeld &&
        CurrentTime - LastActionTime >= RepeatDelay)
    {
        PerformAction();
    }
}