Can anyone explain jpearmans code (buttons one)


#1

So @jpearman posted a buttons code a few months ago, and I’m trying to implement it into my program, but half of the things in there idk what they do.

Here’s the code:


#include "robot-config.h"

////robot-config.h//////
//vex::brain Brain;
////////////////////////

// collect data for on screen button
typedef struct _button {                               //Like this?
    int    xpos;
    int    ypos;
    int    width;
    int    height;
    bool   state;
    vex::color color;
} button;

// Button definitions
button buttons] = {                                      //what does this array do?
  {   30,  30, 60, 60,  false, 0x800000 },
  {  150,  30, 60, 60,  false, 0x008000 },
  {  270,  30, 60, 60,  false, 0x000080 },
  {  390,  30, 60, 60,  false, 0x808000 },
  {   30, 150, 60, 60,  false, 0x800080 },
  {  150, 150, 60, 60,  false, 0x804040 },
  {  270, 150, 60, 60,  false, 0x008080 },
  {  390, 150, 60, 60,  false, 0x808080 }
};

// forward ref
void displayButtonControls( int index, bool pressed );

/*-----------------------------------------------------------------------------*/
/** @brief      Check if touch is inside button                                */             //   ?
/*-----------------------------------------------------------------------------*/
int
findButton(  int16_t xpos, int16_t ypos ) {
    int nButtons = sizeof(buttons) / sizeof(button);

    for( int index=0;index < nButtons;index++) {
      button *pButton = &buttons index ];
      if( xpos < pButton->xpos || xpos > (pButton->xpos + pButton->width) )
        continue;

      if( ypos < pButton->ypos || ypos > (pButton->ypos + pButton->height) )
        continue;
      
      return(index);
    }
    return (-1);
}
/*-----------------------------------------------------------------------------*/
/** @brief      Init button states                                             */
/*-----------------------------------------------------------------------------*/
void
initButtons() {
    int nButtons = sizeof(buttons) / sizeof(button);

    for( int index=0;index < nButtons;index++) {
      buttons[index].state = false;
    }
}

/*-----------------------------------------------------------------------------*/
/** @brief      Screen has been touched                                        */
/*-----------------------------------------------------------------------------*/
void
userTouchCallbackPressed() {
    int index;
    int xpos = Brain.Screen.xPosition();
    int ypos = Brain.Screen.yPosition();
    
    if( (index = findButton( xpos, ypos )) >= 0 ) {
      displayButtonControls( index, true );
    }
    
}

/*-----------------------------------------------------------------------------*/
/** @brief      Screen has been (un)touched                                    */
/*-----------------------------------------------------------------------------*/
void
userTouchCallbackReleased() {
    int index;
    int xpos = Brain.Screen.xPosition();
    int ypos = Brain.Screen.yPosition();
    
    if( (index = findButton( xpos, ypos )) >= 0 ) {
      buttons[index].state = !buttons[index].state;
      displayButtonControls( index, false );
    }
}

/*-----------------------------------------------------------------------------*/
/** @brief      Draw all buttons                                               */
/*-----------------------------------------------------------------------------*/
void
displayButtonControls( int index, bool pressed ) {
    vex::color c;
    Brain.Screen.setPenColor( vex::color(0xe0e0e0) );

    for(int i=0;i<sizeof(buttons)/sizeof(button);i++) {
      
      if( buttons*.state )
        c = buttons*.color;
      else
        c = vex::color::black;

      Brain.Screen.setFillColor( c );

      // button fill
      if( i == index && pressed == true ) {
        c = c + 0x404040;
        Brain.Screen.drawRectangle( buttons*.xpos, buttons*.ypos, buttons*.width, buttons*.height, c );
      }
      else
        Brain.Screen.drawRectangle( buttons*.xpos, buttons*.ypos, buttons*.width, buttons*.height );
  
      // outline
      Brain.Screen.drawRectangle( buttons*.xpos, buttons*.ypos, buttons*.width, buttons*.height, vex::color::transparent );
    }
}

int main() {
    // register events
    Brain.Screen.pressed( userTouchCallbackPressed );
    Brain.Screen.released( userTouchCallbackReleased );

    // initial display
    displayButtonControls( 0, false );
    
    Brain.Screen.printAt(155, 125, "Touch the squares");
    while(1) {
      // Allow other tasks to run
      vex::this_thread::sleep_for(10);
    }
}

Thank you.**************
buttons.vex (7.5 KB)


#2

Can you link where he shared the program? The context will probably be helpful.


#3

So after giving the program a second read through I understand what it is doing. He is creating a set of buttons on the LCD screen and triggering some state variables when a button is pressed.

So he has an array of arbitrary length that contains the structure for each object. And he defines the data necessary for each button

This is the necessary information to draw a button on the LCD screen. Its position, size etc.

typedef struct _button {                               //Like this?
    int    xpos;
    int    ypos;
    int    width;
    int    height;
    bool   state;
    vex::color color;
} button;

Either

  1. start with his program and walk yourself through the code, testing it and seeing how changes affect the results
  2. write your own button code from scratch, which wouldnt be that difficult either

#4

@tabor473 I will probably go off of his code and make some changes. I’m trying to make it so when I press one of those buttons, it’ll choose the auton for the match, it’s hard for me to do the drawing stuff because I don’t have the v5 yet.

Thank you.


#5

ok, so @Lukyan_1 has asked me to explain this code some more. The idea behind the code is to store the information for a “button” and when a screen press or release is detected take some action if that occurs where the button is drawn on the screen. In the original code all I did was change the state of each button to on or off (true or false) this affected how the button was drawn on the screen, each button was acting as a toggle button.

With that in mind, lets look at some of the data structures and functions, going to jump about a bit here.

// collect data for on screen button
typedef struct _button {
    int    xpos;
    int    ypos;
    int    width;
    int    height;
    bool   state;
    vex::color color;
} button;

// Button definitions
button buttons] = {
  {   30,  30, 60, 60,  false, 0x800000 },
  {  150,  30, 60, 60,  false, 0x008000 },
  {  270,  30, 60, 60,  false, 0x000080 },
  {  390,  30, 60, 60,  false, 0x808000 },
  {   30, 150, 60, 60,  false, 0x800080 },
  {  150, 150, 60, 60,  false, 0x804040 },
  {  270, 150, 60, 60,  false, 0x008080 },
  {  390, 150, 60, 60,  false, 0x808080 }
};

We create a new structure, this is a composite datatype that can store all the information needed to draw each button on the screen. We have the coordinates of the top/left corner, the width and height, the “state” of the button, that is should it be displayed filled or not in this demo, and a color to draw the button with.

we create an array using this structure as the data type and initialize it, that is, the first button will be set like this if we did it using C code statements


button[0].xpos = 30;
button[0].ypos = 30;
button[0].width = 60;
button[0].height = 60;
button[0].state = false;
button[0].color = 0x800000;

using The C compiler to initialize the array for us saves lots of lines of code.


    // register events
    Brain.Screen.pressed( userTouchCallbackPressed );
    Brain.Screen.released( userTouchCallbackReleased );

this registers the two functions, userTouchCallbackPressed and userTouchCallbackReleased as functions that will be called when the screen is pressed (ie. touched) or released. Both functions are similar

void
userTouchCallbackPressed() {
    int index;
    int xpos = Brain.Screen.xPosition();
    int ypos = Brain.Screen.yPosition();
    
    if( (index = findButton( xpos, ypos )) >= 0 ) {
      displayButtonControls( index, true );
    }
}

we read the location (coordinates) of the press into two variables, xpos (the x position) and ypos. We pass these coordinates to a function called findButton.

int
findButton(  int16_t xpos, int16_t ypos ) {
    int nButtons = sizeof(buttons) / sizeof(button);

    for( int index=0;index < nButtons;index++) {
      button *pButton = &buttons index ];
      if( xpos < pButton->xpos || xpos > (pButton->xpos + pButton->width) )
        continue;

      if( ypos < pButton->ypos || ypos > (pButton->ypos + pButton->height) )
        continue;
      
      return(index);
    }
    return (-1);
}

This functions looks at the position of every button stored in our array and decides if the screen coordinates passed to it are inside or outside its bounding rectangle. When it finds a match it returns the array index for that button, if nothing is found it returns (-1).

The userTouchCallbackReleased function differs from the press function in that if a release is detected inside a particular button, we change the state from true to false or vice verse.

For both the userTouchCallbackPressed and userTouchCallbackReleased actions we redraw all the buttons by calling displayButtonControls with two parameters, the index of the button that was just touched and whether we are touching the screen or not. Based on these parameters we can redraw everything on the screen.

So that’s the essence of the code, to make this work for an autonomous selector you will need to change it slightly, I will post an example shortly.


#6

To make the code work for auton selection, we will change how the buttons react and make them more like a radio control group, that is, only one can be selected at a time. We create a global variable to save our selection.

// storage for our auton selection
int   autonomousSelection = -1;

The userTouchCallbackReleased function now looks like this.

/*-----------------------------------------------------------------------------*/
/** @brief      Screen has been (un)touched                                    */
/*-----------------------------------------------------------------------------*/
void
userTouchCallbackReleased() {
    int index;
    int xpos = Brain.Screen.xPosition();
    int ypos = Brain.Screen.yPosition();
    
    if( (index = findButton( xpos, ypos )) >= 0 ) {
      // clear all buttons to false, ie. unselected
      initButtons();

      // now set this one as true
      buttons[index].state = true;
      
      // save as auton selection
      autonomousSelection = index;
      
      displayButtonControls( index, false );
    }
}

instead of toggling the state of the button, we set all buttons to false by calling initButtons and then set the button that was touched to true. We also save the index of the selected button in autonomousSelection.

In our autonomous function we can test the state of autonomousSelection and make decisions as to what code to run, a simple test may look like this.

void autonomous( void ) {
  int count = 0;
  while (true) {
    if( autonomousSelection < 0 )
      Brain.Screen.printAt( 60,  125, "Auton not selected  " );
    else
      Brain.Screen.printAt( 60,  125, "Auton %d Running %5d", autonomousSelection, count++ );
    task::sleep(100); 
  }
}

so attached is an updated version that looks like this

new_buttons.png
auton_select.vex (9.5 KB)


#7

You are making it a little too easy for them :slight_smile:

I am having my students extend the example to demonstrate they understand how it works a little bit better - for middle schoolers it is a good start :slight_smile:

Thanks for all you do!

I hope teams will remember to credit this code and include in their design notebook how they adapt it to their needs.


#8

So I’m new to coding and I somewhat understand what is going on here and this is really awesome for me since I’m learning and so now I have some example code to go off of. Anyways, I was wondering where exactly you would put the autonomous code, for instance, If I labeled one of the buttons to be “Red Auton, Flags+Caps” and I assigned that button a specific set of tasks that commands the robot to do those things, how would I link them together. Where would I put those set of commands so that the robot executes them when the assigned button is press. I suppose you would use the button index and if that button is pressed the said autonomous will be run based on the index if it’s true or not. Idk, I have an idea but no one on my team can code and it would be very helpful if you could further explain this.


#9

You’re thinking of it a little off. You would be pressing these buttons during the pre-autonomous period. You would want variations to happen during the autonomous period. So generally speaking you want to store the choices for later use. You can do things like set a Boolean robotIsBlue = true (if you’re on the blue alliance) or false (if you’re on the red alliance). Then during autonomous it chooses the appropriate routine based on what it was told during pre-autonomous. That doesn’t mean you’d never want to call functions directly in pre-autonomous. For example, maybe you call a function that starts up the sonar to check how far you are from the wall, printing it out on the screen. But as far as calling functions to flip caps and hit flags, that need to happen during autonomous.


#10

In the example that James Pearman posted, you can only select one button at a time. So at most 8 options that can picked one at a time.

You can configure the button label you wish.

What you might do is something like this:

if( autonomousSelection < 0 )
  Brain.Screen.printAt( 60,  125, "Auton not selected  " );
else
{
  Brain.Screen.printAt( 60,  125, "Auton %d Running", autonomousSelection );
switch(autonomousSelection){
    case 0:
        allianceColor=0;    // RED
        startTile=0;        // NEAR
        doPark=1;           // PARK
        shootPreload=1;     // SHOOT PRELOAD
        break;
    case 1:
        allianceColor=0;    // RED
        startTile=0;        // NEAR
        doPark=0;           // NO PARK
        shootPreload=1;     // SHOOT PRELOAD
        break;
    case 2:
        break;
    case 3:
        break;
    case 4:
        break;
    case 5:
        break;
    case 6:
        break;
    case 7:
        break;
}

/*
Shoot ball
*/
if(shootBall){

 }

Not optimal, so you might want to change the behavior of the buttons in button released procedure so that the state of any button is maintained and not set to false… This way you get more than eight possibilities.


#11

It does take a little thinking about when you press buttons - best to try it with a competition switch and see how the demo code works. Pretty neat stuff!!!

Thanks James!


#12

I’ll get back to this thread once I test the modified code. I highly doubt it will work but if it does, I wanna give a huge thank you to James and everyone else that assisted me. I will also post an example of what I did if it works.


#13

Pretty sure it does work - we have been banging away with it for the past two weeks and pretty happy with the direction we took it.

next generation interface will be gestures - but have to figure out if multitouch works :slight_smile:


#14

@lacsap I’ve been tinkering with the code you provided and it has been working well as aesthetics but, I was wondering if you could describe where/how to add our autonomous in each button


#15

I had this issue and here is how I tackled it. The easiest way to do it is to go down into autonomous and add else if statements to the if else statement there, then in the else if statement you call back the index of autonomousSelection buttons and set it = to the corresponding button. So it would look something like:

else if (autonomousSelection == 1){
insert autonomous
}

the autonomous selection would be = to a number corresponding to which it is indexed. You have to look first at the button definitions and count down to which one you want to assign that auton for. So lets say you want B1 to be an autonomous of your creation, you would set the "else if(autonomousSelection == 5) because that is the number in which it is indexed. Not sure if that made any sense, I’m not familiar with c++ vocabulary but that is the simple way of approaching autonomous with these buttons. It has so far worked with me.


#16

actually, it is easier than that. We have one autonomous, and based on the selection the capabilities are enabled, so it looks like this:

        /*
        Shoot preload */
        if(shootPreload){
            /* insert code for shooting preload here */
            Brain.Screen.printAt( 60,  125, "Shoot Preload         " );
            // WAIT
            // rotate to proper orientation
        }       
        driveSetVelocity(80);    // set the drive velocity for all four motors

        // Drive routines... for autonomous before parking
        
        /* Parking routines for start tile farthest from flag/net*/
        if(doPark){
            Brain.Screen.printAt( 60,  125, "Do park         " );
            driveSetVelocity( 50 );   
            if(!allianceBlue)              /* red tile is false and blue is true */
                driveTurnDegrees(90.0);
            else
                driveTurnDegrees(90.0*-1.0); /* rotate in opposite direction */
            driveSetVelocity( 80 );          
            driveRotateFor(2.5);     
        }              


#17

@mvas8037 When i put that example as an else if function underneath my autonomous void it came up with errors even though your logic behind it is crystal clear


#18

Here’s a snippet of text from our code to get you guys started:

void autonomous( void ) {
int count = 0;
while (true) {
if( autonomousSelection < 0 )
Brain.Screen.printAt( 60, 125, "Auton not selected " );

else if(autonomousSelection == 0){
Brain.Screen.printAt( 60,  125, "Red Autonomous, Flags + Caps Selected" );

Intake.startRotateFor(-5000,rotationUnits::deg,100,velocityUnits::pct);


#19

@mvas8037 Thanks for everything


#20

@mvas8037 As you can tell I’m pretty new but i was wondering how i decide to run more than one if i used

void autonomous( void ) {
int count = 0;
while (true) {
if( autonomousSelection < 0 )
Brain.Screen.printAt( 60, 125, "Auton not selected " );

else if(autonomousSelection == 1){
Brain.Screen.printAt( 60, 125, “Red Autonomous, Flags + Caps Selected” );
Intake.startRotateFor(-5000,rotationUnits::deg,100,velocityUnits::pct);

Would that default to the first box