Arduino TVout, Erratic behavior when splitting code into separate .cpp files

Hello,

I am attempting to write a program that puts a number of "Buttons" up on the screen. Each distinct button shape will have class with certain properties and a draw function to draw a certain shape at a particular location.

In this program I have a Button parent class that all button shapes will inherit, and I have a Rectangle class that inherits Button.

I managed to make this system work well inside of a single .ino file, but I wanted to break out each individual button class to it's own file for the practice, readability, and ease of access for changes. But that's where I ran into problems.

Here is my working code in a single file. (Please ignore the ugly math)

#include <TVout.h>
#include <font6x8.h>
#include <video_gen.h>

TVout TV;

class Button
{
  public:

  bool isSet;

  Button(bool set)
  {
    isSet = set;
  }

  Button()
  {
    
  }
};

class Circle:public Button
{
  public:
  
  char faceChar;
  int posX;
  int posY;

  Circle(char newFace, int newPosX, int newPosY, bool newIsSet):Button(newIsSet)
  {
    faceChar = newFace;
    posX = newPosX;
    posY = newPosY;
  }

  Circle()
  {
    
  }
  
  void draw(bool isInv)
  {
    int buttonPosX = (29*posX)+64;
    int buttonPosY = (29*posY)+49;

    int circleFill = 2-(3*isInv);

    TV.println(buttonPosX-2, buttonPosY-3, faceChar);
    TV.draw_circle(buttonPosX, buttonPosY, 10, 1, circleFill);
  };
};

class Rectangle:public Button
{
  public:

  Rectangle()
  {
    
  }

  Rectangle(bool newIsSet):Button(newIsSet)
  {
    
  }

  void draw(bool isInv)
  {
    //TV.draw_rect(58, 42, 12, 12, 1, !isInv);
    TV.draw_rect(53, 39, 22, 20, 1, !isInv);
  }
};

class Arrow:public Button
{
  public:

  int posX; //-1 for left, 1 for right, 0 for middle
  int posY; //-1 for above, 1 for below, 0 for middle

  Arrow(int newPosX, int newPosY, bool newIsSet): Button(newIsSet)
  {
    posX = newPosX;
    posY = newPosY;
  }

  Arrow()
  {

  }

  void draw(bool isInv)
  {
    
    int rectX = 58+(20*posX);
    int rectY = 43+(20*posY);
    int deltX = 11+abs(posY)+abs(posX);
    int deltY = 11+abs(posX)+abs(posY);

    TV.draw_rect(rectX, rectY, deltX, deltY, 1, !isInv);

    if (isInv)
    {
      int lineOffAX0 =  ((6+(5*posX))*abs(posX))+(-7*abs(posY));
      int lineOffAY0 =  (-7*abs(posX))+((6+(5*posY))*abs(posY));
      int lineOffAX1 =  ((6+(5*posX))*abs(posX))+(19*abs(posY));
      int lineOffAY1 = (19*abs(posX))+((6+(5*posY))*abs(posY));

      int linePosAX0 = rectX + lineOffAX0;
      int linePosAY0 = rectY + lineOffAY0;
      int linePosAX1 = rectX + lineOffAX1;
      int linePosAY1 = rectY + lineOffAY1;

      int lineTipX = rectX +((6-posX)+(20*posX)-posX)*abs(posX)+(6*abs(posY));
      int lineTipY = rectY +(6+(18*posY));
      
      int linePosBX0 = rectX + (((1+posX)/2)*12)*posX;
      int linePosBY0 = rectY + (((1+posY)/2)*12)*posY;
      int linePosBX1 = rectX + (((1+posX)/2)*12)*posX + 13*abs(posY);
      int linePosBY1 = rectY + (((1+posY)/2)*12)*posY+12*abs(posX);

      int linePosCX0 = rectX + (((1+posX)/2)*12)*posX + abs(posY) - posX;
      int linePosCY0 = rectY + (((1+posY)/2)*12)*posY - posY + abs(posX);
      int linePosCX1 = rectX + (((1+posX)/2)*12)*posX + 13*abs(posY) - abs(posY) - posX;
      int linePosCY1 = rectY + (((1+posY)/2)*12)*posY+12*abs(posX) - posY - abs(posX);
      
      //TV.draw_line(51, 24, 78, 24, 2);
      TV.draw_line(linePosAX0, linePosAY0, linePosAX1, linePosAY1, 1);
      TV.draw_line(linePosAX0, linePosAY0, lineTipX, lineTipY, 1);
      TV.draw_line(linePosAX1, linePosAY1, lineTipX, lineTipY, 1);
      TV.draw_line(linePosBX0, linePosBY0, linePosBX1, linePosBY1, 0);
      TV.draw_line(linePosCX0, linePosCY0, linePosCX1, linePosCY1, 0);
      
    }else
    {
      
      int arrOffX0 = (-7*abs(posY))+((((1+posX)/2)*11)+((-1+posX)/2))*posX;
      int arrOffY0 = ((((1+posY)/2)*11)+((-1+posY)/2))*posY+(-7*abs(posX));
      int arrOffX1 = (20*abs(posY))+((((1+posX)/2)*11)+((-1+posX)/2))*posX;
      int arrOffY1 = ((((1+posY)/2)*11)+((-1+posY)/2))*posY+(19*abs(posX));

      int arrMultX0 = posX+1-abs(posX);
      int arrMultY0 = posY+1-abs(posY);
      int arrMultX1 = (-1*abs(posY))+posX;
      int arrMultY1 = posY+(-1*abs(posX));

      int arrPosX0 = rectX+arrOffX0;
      int arrPosY0 = rectY+arrOffY0;
      int arrPosX1 = rectX+arrOffX1;
      int arrPosY1 = rectY+arrOffY1;
 
      for(int i = 0; i < 14; i++)
      {
        TV.draw_line(arrPosX0+(i*arrMultX0), arrPosY0+(i*arrMultY0), arrPosX1+(i*arrMultX1), arrPosY1+(i*arrMultY1), 1);
      }
    }
  };
};


void setup() 
{
  // put your setup code here, to run once:
  TV.begin(_NTSC);
  TV.select_font(font6x8);

  Circle buttonA('A',-1,-1,0);
  Circle buttonB('B', 1, -1, 0);
  Circle buttonC('C',-1, 1, 0);
  Circle buttonD('D', 1, 1, 0);

  Arrow upArrow(0, -1, false);
  Arrow downArrow(0, 1, false);
  Arrow leftArrow(-1, 0, false);
  Arrow rightArrow(1, 0, false);

  Rectangle buttonMid(0);
  
  buttonA.draw(false);
  buttonB.draw(false);
  buttonC.draw(false);
  buttonD.draw(false);

  upArrow.draw(true);
  downArrow.draw(true);
  leftArrow.draw(true);
  rightArrow.draw(true);

  buttonMid.draw(false);

  //Reference lines for layout changes
  /*
  TV.draw_line(64, 0, 64, 96, 2);
  TV.draw_line(0, 49, 128, 49, 2);
  TV.draw_line(24, 49, 64, 9, 2);
  TV.draw_line(24, 49, 64, 89, 2);
  TV.draw_line(64, 89, 104, 49, 2);
  TV.draw_line(64, 9, 104, 49, 2);
  */
}
void loop() 
{
  
}

The trouble I am having is using the TVout library outside of the main .ino file.
I believe to use the TVout library I define a TVout object and use the member functions of the object.
The tricky part seems to be getting each implementation file to recognize the same defined TVout object and compile correctly with it.

After a lot of reading around and trying things I managed to make a simplified version of my program, with only one button class, compile. I did this by defining the TVout object as static in the parent class header. This version that compiles, however, seems to behave extremely erratically in the location of the drawing.

main.ino

#include "Button.h"
#include "Rectangle.h"

void setup()
{
  TV.begin(_NTSC);
  TV.select_font(font6x8);

  Rectangle midButton;

  midButton.draw(false);
  //TV.draw_rect(53, 39, 22, 20, 1, true);
  
}

void loop()
{

}

Button.h (parent class)

#ifndef BUTTON_H
#define BUTTON_H

#include "TVout.h"
#include "font6x8.h"
#include "video_gen.h"

static TVout TV;

class Button
{
  
  private:
  bool isSet;

  public:
  Button();
  Button(bool set);
};
#endif

Button.cpp

#include "Button.h"

//TVout TV;

Button::Button()
{
  isSet = false;
}

Button::Button(bool set)
{
  isSet = set;
}

Rectangle.h

#ifndef RECTANGLE_H
#define RECTANGLE_H

#include "Button.h"

class Rectangle:public Button
{
  public:
  Rectangle();
  Rectangle(bool set);

  void draw(bool isInv);
};
#endif

Rectangle.cpp

#include "Rectangle.h"
#include "TVout.h"

//TVout TV;


Rectangle::Rectangle():Button()
{

}

Rectangle::Rectangle(bool set):Button(set)
{
  
}

void Rectangle::draw(bool isInv)
{
  TV.draw_rect(53, 39, 22, 20, 1, !isInv);
}

When using TVout in the main.ino file, it works as normal, but when calling a function from a different implementation file, the output doesn't match the program.

In the main file I have two lines that should draw a white box in the center of the screen:

midbutton.draw(false);
and
TV.draw_rect(53, 39, 22, 20, 1, true);

Attached is a screenshot of the image generated with the TV.draw_rect function uncommented, and one with the image generated with the midButton.draw uncommented.

Theoretically these two functions should output the same image, since all midButton.draw does is call the exact same function as TV.draw_rect, except it's stored inside a class instead of being in the main.ino.

You can clearly see, however, that the two outputs are quite different.

I believe that I am doing something wrong regarding the definition of the TVout object, but this is mostly a guess.

What could be causing this fault, and what can I do to try to fix this code?

Thank you in advance for your help

I stopped reading at Button.h
That header file contains:

#include "TVout.h"
#include "font6x8.h"
#include "video_gen.h"

static TVout TV;

These should not be there. Your button.h does not make ant reference to TV/TVout, font, video-gen.
More, the static TVout TV will be included in every file that has

#include "Button.h"

Is this what you want?

More, the static TVout TV will be included in every file

I was trying to get that TVout TV into every file, so yes that was what I wanted. The idea was that every class that included Button.h would be using TVout, so I thought I might as well include the TVout liobrary in Button.h. It didn't behave like I expected it too.

I managed to find the correct way to get the same isntance of TVout TV into evey file, so I've found the solution to my question.

In my, now working, program, I define TVout TV in my main.ino.
In the individual button classes, I include TVout.h, and I have the line

extern TVout TV;

This causes the program to compile and behave correctly.

Here is the working example.

main.ino

#include "Button.h"
#include "Rectangle.h"


#include <TVout.h>
#include "font6x8.h"
#include "video_gen.h"

TVout TV;

void setup()
{
  TV.begin(_NTSC);

  Rectangle midButton;

  midButton.draw(false);
  //TV.draw_rect(53, 39, 22, 20, 1, true);

}

void loop()
{

}

Button.h

#ifndef BUTTON_H
#define BUTTON_H

class Button
{
  
  private:
  bool isSet;

  public:
  Button();
  Button(bool set);
};
#endif

Button.cpp

#include "Button.h"

Button::Button()
{
  isSet = false;
}

Button::Button(bool set)
{
  isSet = set;
}

Rectangle.h

#ifndef RECTANGLE_H
#define RECTANGLE_H

#include "Button.h"
#include "TVout.h"

extern TVout TV;

class Rectangle:public Button
{
  public:
  Rectangle();
  Rectangle(bool set);

  void draw(bool isInv);
};
#endif

Rectangle.cpp

#include "Rectangle.h"

Rectangle::Rectangle():Button()
{

}

Rectangle::Rectangle(bool set):Button(set)
{
  
}

void Rectangle::draw(bool isInv)
{
  TV.draw_rect(53, 39, 22, 20, 1, !isInv);
}

So then you understood the difference between

extern TVout TV;

and

TVout TV;

One declares, the other defines.