How-to logically attach crank to menu?

I have some odd menus i'm playing with - i'd like to use the crank to navigate within the menu. is this possible? It would also be useful if i could programmatically control the cursor a bit, for example I might want to open a menu with the cursor at the most recently used position rather than at the top. Perhaps i'd like to move it elsewhere too.

I think all of the above could be accomplished if i could emit button events. for the crank i would simply decide the angular delta i'd like to use as the equivalent to an "up" or "down" button press; and for default menu cursor location i could record an offset for the most recently used menu item and simply emit the corresponding movements (perhaps in the player.draw handler so they happen after the menu is created? or can i drop code inside the menu itself but not within the menu options?)

Have a look at the crank event and the event.ra variable. The event will be called every frame if the crank has been moved, and event.ra will be the amount of degrees it has moved in that frame. You can keep track of the total in a variable, and then use that to select your menu options.

1 Like

Thanks, but what I'm missing here is how to select the menu options.

Assume for a moment that I define 30 degrees as the trigger to move to the next menu option. I listen to the crank using the event handler as you described, I see event.ra equals 30 or more... so I want to bother move the menu cursor. but how?

Let's say you have a variable menuSelected which is the number of the current option selected in your menu. To change this every time the crank is turned 30 degrees, you could use the following code:

on crank do
  // Keep track of the total degrees
  crankDegrees += event.ra

  // Change the selected option
  if crankDegrees>=30 then
    crankDegrees = 0
    menuSelected++
  elseif crankDegrees<=-30 then
    crankDegrees = 0
    menuSelected--
  end

  // Lets say you have 5 menu options
  if menuSelected>5 then
    // Loop back to the beginning
    menuSelected = 0
  elseif menuSelected<0 then
    // Loop to the end
    menuSelected = 5
  end
end

Thanks for replying again, but I think I've still failed to articulate the question properly.

We have a native menu widget which we call on using [menu] followed by a number of options. When it is displayed there is a cursor which is drawn by default at the first option. During dpad interaction a single tap up or down moves that cursor. I'd like to move that cursor using the crank.

I understand how to read the crank and how to convert from relative angle changes to my own variables tracking which option I'd like to see the cursor point to, but I don't know how to move the cursor itself

You might have to make your own menu. As it's out of the box you might not have the hooks you need to control it.

1 Like

thanks, yeah i'm thinking the same. though it feels like something worth adding to the native menu widget.

feeling through the implementation, it seems that a call to menu does a few things (just an educated guess, but it aligns well with observation):

  1. defines a menu structure (the list of options)
  2. registers that structure to be drawn by a system task
  3. registers 1 callback function for each option
  4. returns immediately - that is, your script runs to completion before the menu is drawn

this isn't what i expected initially, but in hindsight it makes sense. i had originally scripted re-entrant menus in a while loop with the exit condition set inside the menu options - that didn't work as it looked like an infinite loop to the runtime engine (it looped hundreds of times without drawing a menu onscreen, then the runtime reported an error flag). This means the user script that requests a menu isn't itself active while the menu is on-screen... i would need some asynchronous method to acquire crank updates and emit messages to the running menu code.

i think the idea i shared in the original post would be a reasonable way to implement this - very little "new code" and no real change to the current architecture (as i understand it anyway). I originally thought i would submit this as a feature request, but then it felt so obvious to me that folks would want to use the cranks to scroll through menus that i must be missing some existing solution.

i'll submit as a feature request later today. thanks to you both for chiming in all the same :slight_smile:
-bit

1 Like

I heartily encourage crank use for menus. Keep playdate weird!

Though I have no idea on how to go about it. Crank events are a mystery so far for me. The doc coverage of it is pretty lightweight for a ... lightweight like myself.

1 Like

The docs explain it pretty well actually! You just have to read it carefully. It's called on the player. So make sure to do the following in the player script.

on crank do
 log "cranking!"
end

If you want to hook into that from another script just make sure to emit a command from the player script that can be called from another script. Like so:
Player Script:

on crank do
 emit "isCranking!"
end

Other scripts for instance a level script:

on isCranking do
 log "Im cranking!"
end

And then you could check for the degree it is turned and use that to do other things with.

on isCranking do
  crankDegrees += event.ra
  log "{crankDegrees}"
end

I must have asked this poorly because you've answered in the same way several others did, but without answering the question I meant to ask.

Getting crank input is easy, I agree. Attaching that input to a menu is not. I'm not convinced it's currently possible to do.

Ah like that. Ok, so to answer your question, the native menu currently does not support input from the crank no. That however does not mean it is not possible. I have for instance created Pokedex where you can scroll through the items with the crank. A simplified version can be found in the code below.
It combines all of the scripts we have above.

Player:

on crank do
	emit "isCranking"
end

on draw do
	hide
	emit "updateInterface"
end

Room:

on enter do
	menuSelected = 0
end

on updateInterface do
	currentIndex = menuSelected
	currentIndex++
	
	loopIndex = 0
	dexPosition = 1
	while loopIndex<6 do
		currentItemName = "???"
		call "item_{currentIndex}"
		
		if currentItemName!="" then
			label "{currentIndex}: {currentItemName}" at 1,dexPosition
		else
			label "{currentIndex}: ???" at 1,dexPosition
		end
		currentIndex++
		loopIndex++
		dexPosition += 3
	end
end

on item_1 do
	currentItemName = "The first item"
end

on item_2 do
	currentItemName = "The second item"
end

on item_3 do
	currentItemName = "The third item"
end

on item_4 do
	currentItemName = "The aaand another item"
end

on item_5 do
	currentItemName = "Last one ok? for sure!"
end

on isCranking do
	// Keep track of the total degrees
	crankDegrees += event.ra
	
	// Change the selected option
	if crankDegrees>=30 then
		crankDegrees = 0
		menuSelected++
	elseif crankDegrees<=-30 then
		crankDegrees = 0
		menuSelected--
	end
	
	totalMonsterCount = 50
	
	// Lets say you have 5 menu options
	if menuSelected>totalMonsterCount then
		menuSelected = totalMonsterCount
	elseif menuSelected<0 then
		menuSelected = 0
	end
end

And also, here is a fully working example so people won't have try to get it working themselves :slight_smile: Hope this actually solves your issue.
CranktTest.json.zip (3.2 KB)

1 Like

I'll mark this as the solution because it's as close as we can get. I still think a better approach for devs here would be to enable us to emit events equivalent to d-pad button presses.

something like:

on load do
  posIncr=30
  negIncr=-30
  menuOnScreen=0
end

on drawMenu do
  // draw the menu in question
  menu at x,y,len,items then
    option "optionOne" then
        // do one thing
    end
    option "optionTwo" then
        // do another
    end
  end

  // set a flag indicating that a menu is drawn, therefore accept crank input as up/down equivalent
  menuOnScreen=1
end

on crank do
  if menuOnScreen==1 then
    accumCrank += event.ra
    if accumCrank >= posIncr then
      emit "dpad_up"
      accumCrank=0
    end
    if accumCrank <= negIncr then
      emit "dpad_down"
      accumCrank=0
    end
  else
    // use the crank for something other than replicating dpad up/down behavior to navigate a menu
  end

end

for that to work we'd need to know what event to fire for the dpad movements... something i'm guessing is actually really easy and might be nearly implemented already (somehow or another dpad events make there way into the runtime... and we already have events fired for A, B, and Crank... so dpad is just one more button source to connect with the same system).

You could always create a new thread and add the feature request tag to it to request that :slight_smile: would be nice to have!

1 Like

Such little code does a lot. I need to remember that when reading the docs.

1 Like

I just noticed in the new SDK simulator, the crank does move the menu items in the native Settings menu on the device.

It's slick.

I can see how you'd want it in native game-menus too :smiley:

1 Like

Or at least as a config option to turn it on.

1 Like

Apologies @bitflung I will aim to divert general discussion off this thread.

In the latest update to Pulp released this morning, the crank cycles through menu options.

6 Likes