Google Material Design buttons in Processing ( With the ripple effect)

I always loved the look and feel of Google’s Material Design components, especially the ripple effect on the buttons. And I realized it had been some time since I did something with processing so I decided to replicate it in processing and here is the result:

LFsdYQS2Ld

JB4Nah9t5y

They are not particularly perfect but they work and look pretty good! I made the code as simple as I could (but it’s still kinda sphagetti). I am also going to make a material design slider, text field and some other ui elements, I hope I will be able to release all of them in a library. Feedback would be appreciated!

Note: One thing I’m not really happy about is that you need to use text offset along with the text size correctly to properly align the text on the button. I will try doing that a bit simpler.

Button:

/*
  Material Design Button | by qewer3322 | 2020
 
 PUBLIC PROPERTIES
 - x (int | x pos of the button)
 - y (int | y pos of the button)
 - w (int | width of the button)
 - h (int | height of the button)
 - mainColor (color | color of the button)
 - textColor (color | color of the button text)
 - effectColor (color | color of hover and ripple effects)
 - icon (PImage | image icon of the button)
 - text (String | text of the button)
 - textFont (PFont | font of the button text)
 - textSize (int | size of the button text)
 - textOffsetX (int | offset of the text on the x axis)
 - textOffsetY (int | offset of the text on the y axis)
 - hoverFadeSpeed (int | controls hover fade speed, default value is 5)
 - ripple speed (int | controls the speed of the ripple effect, default value is 12)
 - rippleFadeSpeed (int | controls the fade speed of the ripple effect, default value is 5)
 
 PUBLIC FUNCTIONS
 - mouseOver() (boolean | returns true when mouse hovers the button, can be used with an if statement in mouseClicked() to do something when the button is clicked)
 - display() (displays the button, should be called in draw() in order for the button to work)
 - mousePress() (registers mouse press events, should be called in mousePressed() in order for the button to work )
 - setIcon(PImage icon) (sets the image icon of the button, optional)
 - setColor(color c) (sets the color of the button, optional)
 - setColor(int r, int g, int b) (sets the color of the button, optional)
 - setEffectColor(color c) (sets the color of hover and ripple effects, optional)
 - setEffectColor(int r, int g, int b) (sets the color of hover and ripple effects, optional)
 - setText(String text) (sets the button text)
 - setTextColor(color c) (sets the color of the button text)
 - setTextColor(int r, int g, int b) (sets the color of the button text)
 - setTextFont(PFont font) (sets the font of the button text)
 - setTextSize(int size) (sets the size of the button text)
 - setTextOffset(int offsetX, int offsetY) (sets the x and y offset of the button text)
 
 Please keep this top header if you share this code
 */


class QButton {

  //x pos, y pos, width, height and corner radius
  int x;
  int y;
  int w;
  int h;
  int r;

  //color of the button
  color mainColor = color(159, 72, 217);

  //button icon
  PImage icon;

  //button text
  String text = "";

  //PGraphics object (used for the ripple effect)
  private PGraphics pg;
  //PGraphics mask object (used for the ripple effect)
  private PGraphics maskPg;

  //constructor 1
  QButton(int x1, int y1, int w1, int h1, int r1) {
    x = x1;
    y = y1;
    w = w1;
    h = h1;
    r = r1;

    pg = createGraphics(w, h);
    maskPg = createGraphics(w, h);
  }
  //constructor2
  QButton(int x1, int y1, int w1, int h1, int r1, String text1) {
    x = x1;
    y = y1;
    w = w1;
    h = h1;
    r = r1;
    text = text1;

    pg = createGraphics(w, h);
    maskPg = createGraphics(w, h);
  }

  //text font
  PFont textFont;
  //font boolean
  private boolean fontB = false;
  //text color
  color textColor = color(255);
  //text size
  int textSize = 25;
  //text offset on x axis
  int textOffsetX = 20;
  //text offset on y axis
  int textOffsetY = 20;

  //icon boolean
  private boolean iconB = false;

  //color of the ripple and hover
  color effectColor = color(255);

  //hover fade speed
  int hoverFadeSpeed = 5;
  //hover opacity
  private int hOpacity = 0;

  //ripple diameter
  int rd = 0;
  //ripple x and y
  int rx = 0;
  int ry = 0;
  //ripplevspeed
  int rippleSpeed = 15;
  //ripple boolean
  private boolean ripple = false;
  //ripple opacity
  private int rOpacity = 0;
  //ripple fade speed
  int rippleFadeSpeed = 5;


  //public fuctions
  //display function (should be called in draw() for the button to show)
  void display() {
    noStroke();

    //pgraphics (ripple)
    image(pg, x, y);

    //hover rect
    fill(effectColor, hOpacity);
    rect(x, y, w, h, r);

    //text
    if (fontB) {
      textFont(font);
    }
    textSize(textSize);
    fill(textColor);
    if (! iconB) {
      text(text, x + textOffsetX, y + textOffsetY);
    }
    if (iconB) {
      image(icon, x + int(h/4), y + int(h/4), h - int(h/2), h - int(h/2));
      text(text, x + h + textOffsetX, y + textOffsetY);
    }

    hover();
    betterRipple();
    //ripple();
  }

  //mouse press function (should be called in mousePressed() for the button to register mouse press events)
  void mousePress() {
    if (overRect(x, y, w, h)) {
      rd = 0;
      rx = mouseX - x;
      ry = mouseY - y;

      ripple = true;
      rOpacity = 80;
    }
  }

  //getters
  boolean mouseOver() {
    if (overRect(x, y, w, h)) {
      return true;
    } else return false;
  }

  //setters
  void setColor(color c) {
    mainColor = c;
  }

  void setColor(int r, int g, int b) {
    mainColor = color(r, g, b);
  }

  void setEffectColor(color c) {
    effectColor = c;
  }

  void setEffectColor(int r, int g, int b) {
    effectColor = color(r, g, b);
  }

  void setText(String str) {
    text = str;
  }

  void setTextColor(color c) {
    textColor = c;
  }

  void setTextColor(int r, int g, int b) {
    textColor = color(r, g, b);
  }

  void setTextFont(PFont font) {
    fontB = true;
    textFont = font;
  }

  void setTextSize(int s1) {
    textSize = s1;
  }

  void setTextOffset(int x1, int y1) {
    textOffsetX = x1;
    textOffsetY = y1;
  }

  void setIcon(PImage img) {
    icon = img;
    iconB = true;
  }

  //private fuctions
  //hover effect function
  private void hover() {
    if (overRect(x, y, w, h) && hOpacity < 50) {
      hOpacity += hoverFadeSpeed;
    } else if (hOpacity > 0) {
      hOpacity -= hoverFadeSpeed;
    }
  }

  //ripple test
  private void betterRipple() {
    if (ripple) {
      rd += rippleSpeed;
    }
    if (rx > w/2 && ry > h/2 && rd > dist(0,0,rx,ry) * 2) {
      ripple = false; 
    }
    if (rx >= w/2 && ry <= h/2 && rd > dist(0,h,rx,ry) * 2) {
      ripple = false; 
    }
    if (rx < w/2 && ry > h/2 && rd > dist(w,0,rx,ry) * 2) {
      ripple = false; 
    }
    if (rx <= w/2 && ry <= h/2 && rd > dist(w,h,rx,ry) * 2) {
      ripple = false; 
    }
    if (ripple == false) {
      rOpacity -= rippleFadeSpeed;
    }
    pg.beginDraw();
    pg.noStroke();
    pg.fill(mainColor);
    pg.rect(0, 0, w, h, r);
    pg.fill(effectColor, rOpacity);
    pg.ellipse(rx, ry, rd, rd);
    pg.endDraw();
    maskPg.beginDraw();
    maskPg.rect(0, 0, w, h, r);
    maskPg.endDraw();
    pg.mask(maskPg);
  }

  private boolean overRect(int x, int y, int width, int height) {
    if (mouseX >= x && mouseX <= x+width &&
      mouseY >= y && mouseY <= y+height) {
      return true;
    } else {
      return false;
    }
  }
}

Circle Button:

/*
  /*
  Material Design Circle Button | by qewer3322 | 2020
 
 PUBLIC PROPERTIES
 - mouseOver() (boolean | becomes true when mouse hovers the button, can be used with an if statement in mouseClicked() to do something when the button is clicked)
 - x (int | x pos of the button)
 - y (int | y pos of the button)
 - d (int | diameter of the button)
 - mainColor (color | color of the button)
 - effectColor (color | color of hover and ripple effects)
 - icon (PImage | image icon of the button)
 - hoverFadeSpeed (int | controls hover fade speed, default value is 5)
 - ripple speed (int | controls the speed of the ripple effect, default value is 8)
 - rippleFadeSpeed (int | controls the fade speed of the ripple effect, default value is 5)
 
 PUBLIC FUNCTIONS
 - display() (displays the button, should be called in draw() in order for the button to work)
 - mousePress() (registers mouse press events, should be called in mousePressed() in order for the button to work )
 - setIcon(PImage icon) (sets the image icon of the button, optional)
 - setColor(color c) (sets the color of the button, optional)
 - setColor(int r, int g, int b) (sets the color of the button, optional)
 - setEffectColor(color c) (sets the color of hover and ripple effects, optional)
 - setEffectColor(int r, int g, int b) (sets the color of hover and ripple effects, optional)
 
 Please keep this top header if you share this code
 */


class QCircleButton {

  // x pos, y pos and diameter
  int x;
  int y;
  int d;

  //color of the button
  color mainColor = color(159, 72, 217);

  //button icon
  PImage icon;

  //constructor
  QCircleButton(int x1, int y1, int d1) {
    x = x1;
    y = y1;
    d = d1;
  }

  //icon boolean
  private boolean iconB = false;

  //color of the ripple and hover
  color effectColor = color(255);

  //hover fade speed
  int hoverFadeSpeed = 5;
  //hover opacity
  private int hOpacity = 0;

  //ripple speed
  int rippleSpeed = 8;
  //ripple boolean
  private boolean ripple = false;
  //ripple fade speed
  int rippleFadeSpeed = 5;
  //ripple circle diameter
  private int rd = 0;
  //ripple opacity (used for fade)
  private int rOpacity = 80;


  //display function (should be called in draw() for the button to show)
  void display() {
    //button
    noStroke();
    fill(mainColor);
    ellipse(x, y, d, d);

    //hover circle
    noStroke();
    fill(effectColor, hOpacity);
    ellipse(x, y, d, d);

    //ripple circle
    noStroke();
    fill(effectColor, rOpacity);
    ellipse(x, y, rd, rd);
    if (rd > d) {
      rd = d;
    }

    //icon
    if (iconB) {
      image(icon, x - (d/1.5)/2, y - (d/1.5)/2, d/1.5, d/1.5);
    }

    hover();
    ripple();
  }

  //mouse press function (should be called in mousePressed() for the button to register mouse press events)
  void mousePress() {
    if (overCircle(x, y, d)) {
      ripple = true;
      rd = 0;
      rOpacity = 80;
    }
  }

  void setColor(color c) {
    mainColor = c;
  }

  void setColor(int r, int g, int b) {
    mainColor = color(r, g, b);
  }

  void setEffectColor(color c) {
    effectColor = c;
  }

  void setEffectColor(int r, int g, int b) {
    effectColor = color(r, g, b);
  }

  void setIcon(PImage img) {
    icon = img;
    iconB = true;
  }

  boolean mouseOver() {
    if (overCircle(x, y, d)) {
      return true;
    } else return false;
  }

  //hover effect function
  private void hover() {
    if (overCircle(x, y, d) && hOpacity < 50) {
      hOpacity += hoverFadeSpeed;
    } else if (hOpacity > 0) {
      hOpacity -= hoverFadeSpeed;
    }
  }

  //ripple effect function
  private void ripple() {
    if (ripple == true && rd < d) {
      rd += rippleSpeed;
    }
    if (rd == d) {
      ripple = false;
    }
    if (ripple == false) {
      rOpacity -= rippleFadeSpeed;
    }
  }

  private boolean overCircle(int x, int y, int diameter) {
    float disX = x - mouseX;
    float disY = y - mouseY;
    if (sqrt(sq(disX) + sq(disY)) < diameter/2 ) {
      return true;
    } else {
      return false;
    }
  }
}

Demo:

PImage plus;
QCircleButton b1 = new QCircleButton(320, 320, 80);

PFont font;
PImage star;
QButton b2;

void setup() {
  size(640, 640);
  plus = loadImage("plus.png");
  star = loadImage("star.png");
  font = createFont("Roboto-Medium.ttf", 48);

  new QButton(210, 170, 220, 60, 15, "BUTTON");
}

void draw() {
  background(get(0, 0));

  b1.setIcon(plus);
  b1.display();

  b2.setTextSize(35);
  b2.setTextFont(font);
  b2.setTextOffset(2, 44);
  b2.setIcon(star);
  b2.display();
}

void mousePressed() {
  b1.mousePress();
  b2.mousePress();
}

Assets for the demo:

Edit: Updated the button ripple effect.

2 Likes

Nice. I thought about doing this recently but you’ve beat me to it :grinning:. I look forward to more UI elements – it’d be nice if a material UI library like this could replace CP5 since it’s pretty ugly (not to mention the API…)

1 Like

Also, looks like your on-click ‘ripple effect’ is horizontal only. For a more mature design, consider porting a ripple effect from one of the many javascript ripple libraries. It would probably boil down to overlaying a circle of growing radius which is bounded by a mask of the button.

1 Like

Yeah it is horizontal only, I was at first going to try making it like normal ripple but I didn’t really know how to approach it so I went with 2 rectangles growing right and left. Thanks for the tip! I will look at how they did it in other libraries. If I could figure it out, I will also try making it so the ripple on the circle button starts from mouse pos rather than the center.

Done! Used PGraphics and PGraphics.mask()
LFsdYQS2Ld
Looks beautiful, thanks for the advice!

1 Like