处理 - 将一起形成一个圆的多个图像按钮

Processing - Multiple Image-Buttons That will Shape a Circle Together

我有几个这样创建的按钮:

setup()

PImage[] imgs1 = {loadImage("AREA1_1.png"),loadImage("AREA1_2.png"),loadImage("AREA1_3.png")};
cp5.addButton("AREA_1")  // The button
.setValue(128)
.setPosition(-60,7)     // x and y relative to the group
.setImages(imgs1)
.updateSize()
.moveTo(AreaRingGroup);   // add it to the group 
; 

之后 draw()

void AREA_1(){
  println("AREA_1");
  if (port != null){ 
      port.write("a\n");
  }

现在,我必须从将被视为 1 个圆圈的图像中添加大约 24 个小按钮。但是我想我应该创建一个 class.

而不是像上面那样使用 cp5.addButton 添加它们

我想扩展现有的 CP5 按钮 class 因为我希望能够达到 cp5 参数;就像在按钮图像中使用 .setImages() 作为 3 个 mouseClick 条件。

我的问题是,我想不通

这是我愚蠢的尝试:

class myButton extends Button
 {
   myButton(ControlP5 cp5, String theName)
   {
     super(cp5, cp5.getTab("default"), theName, 0,0,0, autoWidth, autoHeight);
   }
   
  PVector pos = new PVector (0,0);
  PVector center = new PVector(500,500);
  PImage[] img = new PImage[3];
  String lable;
  int sizeX = 10, sizeY=10;
  color imgColor;
  Boolean pressed = false;
  Boolean clicked = false;
  
  //Constructor
  myButton(float a, String theName)
  {
    
    //Angle between the center I determined and the position of the 
    button. 
    a = degrees (PVector.angleBetween(center, pos));

    //Every button will have 3 images for the 3 mouseClick conditions.
    for (int i = 0; i < 2; ++i)
        (img[i] = loadImage(lable + i + ".png")).resize(sizeX, sizeY);

  }
}

Laancelot 的想法不错,但是,由于 controlP5 的 OOP 架构,扩展 controlP5 的按钮 class 以通过 controlP5 草图实现您的目标可能是不可能的,或者至少是微不足道的。

我假设这与您这次发布的其他 LED 环相关问题有关(例如 )。

这可能是错误的(我很想看到一个解决方案),但是如果没有 forking/modifying controlP5 库本身,继承链会变得有点棘手。

理想情况下,您只需要扩展 Button 并完成它,但是用于告诉状态的方法(如果按钮 over/pressed/etc。)是控制器 superclass的inside()方法。要使用您的图像,您需要覆盖此方法并交换逻辑,以便它考虑到您的 png 按钮皮肤的 alpha 透明度(例如,如果光标下的像素是不透明的,那么它在里面,否则它不是)

这是您被迫使用处理草图的粗略图示:

public abstract class CustomController< T > extends Controller< T> {
  
  public CustomController( ControlP5 theControlP5 , String theName ) {
    super( theControlP5 , theControlP5.getDefaultTab( ) , theName , 0 , 0 , autoWidth , autoHeight );
    theControlP5.register( theControlP5.papplet , theName , this );
  }
  
  protected CustomController( final ControlP5 theControlP5 , final ControllerGroup< ? > theParent , final String theName , final float theX , final float theY , final int theWidth , final int theHeight ) {
    super(theControlP5, theParent, theName, theX, theY, theWidth, theHeight);
  }
  
  boolean inside( ) {
    /* constrain the bounds of the controller to the dimensions of the cp5 area, required since PGraphics as render
     * area has been introduced. */
    //float x0 = PApplet.max( 0 , x( position ) + x( _myParent.getAbsolutePosition( ) ) );
    //float x1 = PApplet.min( cp5.pgw , x( position ) + x( _myParent.getAbsolutePosition( ) ) + getWidth( ) );
    //float y0 = PApplet.max( 0 , y( position ) + y( _myParent.getAbsolutePosition( ) ) );
    //float y1 = PApplet.min( cp5.pgh , y( position ) + y( _myParent.getAbsolutePosition( ) ) + getHeight( ) );
    //return ( _myControlWindow.mouseX > x0 && _myControlWindow.mouseX < x1 && _myControlWindow.mouseY > y0 && _myControlWindow.mouseY < y1 );
    // FIXME: add alpha pixel logic here
    return true;
  }
  
}

public class CustomButton<Button> extends CustomController< Button > {
  
  protected CustomButton( ControlP5 theControlP5 , ControllerGroup< ? > theParent , String theName , float theDefaultValue , int theX , int theY , int theWidth , int theHeight ) {
    super( theControlP5 , theParent , theName , theX , theY , theWidth , theHeight );
    _myValue = theDefaultValue;
    _myCaptionLabel.align( CENTER , CENTER );
  }
 
  // isn't this fun ?
  //public CustomButton setImage( PImage theImage ) {
  //  return super.setImage( theImage , DEFAULT );
  //}
  
}

我会建议一个更简单的解决方法:只需在环形布局中添加较小的按钮(使用 ):

import controlP5.*;

ControlP5 cp5;

void setup(){
  size(600, 600);
  background(0);
  
  cp5 = new ControlP5(this);
  
  int numLEDs = 24;
  float radius = 240;
  for(int i = 0; i < numLEDs; i++){
    float angle = (TWO_PI / numLEDs * i) - HALF_PI;
    cp5.addButton(nf(i, 2))
       .setPosition(width * 0.5 + cos(angle) * radius, height * 0.5 + sin(angle) * radius)
       .setSize(30, 30);
  } 
}

void draw(){}

当然,没有那么漂亮,但简单得多。 希望将绘制在后面的环形图像渲染为背景并更改按钮颜色可能足以说明这一点。

完全跳过 ControlP5 并为此类自定义行为制作您自己的按钮 class 可能更简单。

假设您希望在环形布局中有一个方形按钮,它也可以沿环旋转。这种轮换将使检查边界变得更棘手,但并非不可能。您可以使用 PMatrix2D) 跟踪按钮的 2D 变换矩阵来呈现按钮,但使用此变换矩阵的逆在 Processing 的全局坐标系与按钮的局部坐标系之间进行转换。这将使检查按钮是否再次悬停变得微不足道(因为转换为本地坐标的鼠标将在按钮的边界框内)。这是一个例子:

TButton btn;

void setup(){
  size(600, 600);
  btn = new TButton(0, 300, 300, 90, 90, radians(45));
}
void draw(){
  background(0);
  btn.update(mouseX, mouseY, mousePressed);
  btn.draw();
}
class TButton{
  
  PMatrix2D transform = new PMatrix2D();
  PMatrix2D inverseTransform;
  
  int index;
  int x, y, w, h;
  float angle;
  
  boolean isOver;
  boolean isPressed;
  
  PVector cursorGlobal = new PVector();
  PVector cursorLocal = new PVector();
  
  color fillOver = color(192);
  color fillOut  = color(255);
  color fillOn   = color(127);
  
  TButton(int index, int x, int y, int w, int h, float angle){
    this.index = index;
    this.x     = x;
    this.y     = y;
    this.w     = w;
    this.h     = h;
    this.angle = angle;
    
    transform.translate(x, y);
    transform.rotate(angle);
    inverseTransform = transform.get();
    inverseTransform.invert();
    
  }
  
  
  
  void update(int mx, int my, boolean mPressed){
    cursorGlobal.set(mx, my);
    inverseTransform.mult(cursorGlobal, cursorLocal);
    isOver = ((cursorLocal.x >= 0 && cursorLocal.x <= w) &&
              (cursorLocal.y >= 0 && cursorLocal.y <= h));
    isPressed = mPressed && isOver;
  }
  
  void draw(){
    pushMatrix();
    applyMatrix(transform);
    pushStyle();
    if(isOver) fill(fillOver);
    if(isPressed) fill(fillOn);
    rect(0, 0, w, h);
    popStyle();
    popMatrix();
  }
  
  
}

如果你能画一个按钮,你可以画很多按钮,环形布局:

int numLEDs = 24;
TButton[] ring = new TButton[numLEDs];

TButton selectedLED;

void setup(){
  size(600, 600);
  float radius = 240;
  float ledSize= 30;
  float offsetX = width * 0.5;
  float offsetY = height * 0.5;
  for(int i = 0 ; i < numLEDs; i++){
    float angle = (TWO_PI / numLEDs * i) - HALF_PI;
    float x = offsetX + (cos(angle) * radius);
    float y = offsetY + (sin(angle) * radius);
    ring[i] = new TButton(i, x, y, ledSize, ledSize, angle);
  }
  println("click to select");
  println("click and drag to change colour");
}

void draw(){
  background(0);
  for(int i = 0 ; i < numLEDs; i++){
    ring[i].update(mouseX, mouseY);
    ring[i].draw();
  }
}

void onButtonClicked(TButton button){
  selectedLED = button;
  println("selected", selectedLED); 
}

void mouseReleased(){
  for(int i = 0 ; i < numLEDs; i++){
    ring[i].mouseReleased();
  }
}

void mouseDragged(){
  if(selectedLED != null){
    float r = map(mouseX, 0, width, 0, 255);
    float g = map(mouseY, 0, height, 0, 255);
    float b = 255 - r;
    selectedLED.fillColor = color(r, g, b);
  }
}

class TButton{
  
  PMatrix2D transform = new PMatrix2D();
  PMatrix2D inverseTransform;
  
  int index;
  float x, y, w, h;
  float angle;
  
  boolean isOver;
  
  PVector cursorGlobal = new PVector();
  PVector cursorLocal = new PVector();
  
  color fillColor = color(255);
  
  TButton(int index, float x, float y, float w, float h, float angle){
    this.index = index;
    this.x     = x;
    this.y     = y;
    this.w     = w;
    this.h     = h;
    this.angle = angle;
    
    transform.translate(x, y);
    transform.rotate(angle);
    inverseTransform = transform.get();
    inverseTransform.invert();
    
  }
  
  
  
  void update(int mx, int my){
    cursorGlobal.set(mx, my);
    inverseTransform.mult(cursorGlobal, cursorLocal);
    isOver = ((cursorLocal.x >= 0 && cursorLocal.x <= w) &&
              (cursorLocal.y >= 0 && cursorLocal.y <= h));
  }
  
  void mouseReleased(){
    if(isOver) onButtonClicked(this);
  }
  
  void draw(){
    pushMatrix();
    applyMatrix(transform);
    pushStyle();
    if(isOver) {
      fill(red(fillColor) * .5, green(fillColor) * .5, blue(fillColor) * .5);
    }else{
      fill(fillColor);
    }
    rect(0, 0, w, h);
    popStyle();
    popMatrix();
  }
  
  String toString(){
    return "[TButton index=" + index + "]";
  }
  
}

这还不错,但很容易变得复杂,对吧? 您在 draw() 中应用什么全局转换:可能需要由按钮处理?如果在调用 .draw() 时按钮嵌套在一堆 pushMatrix()/popMatrix() 调用中怎么办?等...

这能更简单吗?当然,LED 在环形布局上显示为旋转的正方形,但点亮的重要部分是圆形的。无论角度如何,旋转的圆看起来都一样。检查鼠标是否悬停是微不足道的,正如您在 Processing RollOver example 中看到的那样:只需检查圆的位置和光标之间的距离是否小于圆的半径。

这是一个使用圆形按钮的示例(无需处理坐标 space 转换):

int numLEDs = 24;
CButton[] ring = new CButton[numLEDs];

CButton selectedLED;

void setup(){
  size(600, 600);
  float radius = 240;
  float ledSize= 30;
  float offsetX = width * 0.5;
  float offsetY = height * 0.5;
  for(int i = 0 ; i < numLEDs; i++){
    float angle = (TWO_PI / numLEDs * i) - HALF_PI;
    float x = offsetX + (cos(angle) * radius);
    float y = offsetY + (sin(angle) * radius);
    ring[i] = new CButton(i, x, y, ledSize);
  }
  println("click to select");
  println("click and drag to change colour");
}

void draw(){
  background(0);
  for(int i = 0 ; i < numLEDs; i++){
    ring[i].update(mouseX, mouseY);
    ring[i].draw();
  }
}

void onButtonClicked(CButton button){
  selectedLED = button;
  println("selected", selectedLED); 
}

void mouseReleased(){
  for(int i = 0 ; i < numLEDs; i++){
    ring[i].mouseReleased();
  }
}

void mouseDragged(){
  if(selectedLED != null){
    float r = map(mouseX, 0, width, 0, 255);
    float g = map(mouseY, 0, height, 0, 255);
    float b = 255 - r;
    selectedLED.fillColor = color(r, g, b);
  }
}
class CButton{
  
  int index;
  float x, y, radius, diameter;
  
  boolean isOver;
  
  color fillColor = color(255);
  
  CButton(int index, float x, float y, float radius){
    this.index = index;
    this.x     = x;
    this.y     = y;
    this.radius= radius;
    
    diameter = radius * 2;
  }
  
  void update(int mx, int my){
    isOver = dist(x, y, mx, my) < radius;
  }
  
  void mouseReleased(){
    if(isOver) onButtonClicked(this);
  }
  
  void draw(){
    pushMatrix();
    pushStyle();
    if(isOver) {
      fill(red(fillColor) * .5, green(fillColor) * .5, blue(fillColor) * .5);
    }else{
      fill(fillColor);
    }
    ellipse(x, y, diameter, diameter);
    popStyle();
    popMatrix();
  }
  
  String toString(){
    return "[CButton index=" + index + "]";
  }
  
}

在你的 NeoPixel 环形图像上使用这些圆形按钮,也许还有一点发光可能看起来非常好,并且在 Processing 草图中比采用 ControlP5 路线更容易实现。不要误会我的意思,这是一个很棒的库,但对于自定义行为,有时使用您自己的自定义代码更有意义。

要考虑的另一个方面是整体交互。 如果此接口用于设置 LED 环的颜色一次,那么它可能没问题,尽管需要 numLEDs * 3 次交互才能将所有 LED 设置为自定义颜色。如果这是用于自定义逐帧 LED 动画工具,那么交互会在几帧后变得疲惫不堪。