3

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

在里面 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 个小按钮。cp5.addButton但是我认为我应该创建一个类,而不是像上面那样添加它们。

我想扩展现有的 CP5 按钮类,因为我希望能够达到 cp5 参数;就像.setImages()在按钮图像中使用 3 个鼠标单击条件一样。

我的问题是,我想不通

  • 如何扩展 CP5 按钮类,以便我可以使用角度(因为我想以圆形方式放置按钮)而不是给出 x,y 位置。

  • 如何使用此类使用串行端口写入,如上面的 void (AREA_1) 示例。

这是我的愚蠢尝试:

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);

  }
}
4

1 回答 1

1

Laancelot 的想法不错,但是,由于 controlP5 的 OOP 架构,扩展 controlP5 的 Button 类以从 controlP5 草图中实现您的目标可能是不可能的,或者至少是微不足道的。

我假设这与您在这段时间发布的其他与 LED 环相关的问题(例如这个)有关。

这可能是错误的(我有兴趣看到一个解决方案),但是如果不分叉/修改 controlP5 库本身,继承链就会变得有点棘手。

理想情况下,您只需要扩展Button并完成它,但是用于告知状态的方法(如果按钮上方/按下/等)是 Controller 超类的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 并为这种自定义行为创建自己的按钮类可能会更简单。

假设您想在环形布局中有一个方形按钮,该按钮也沿环形旋转。这种旋转将使检查边界变得更加棘手,但并非不可能。您可以使用 ) 来跟踪按钮的 2D 变换矩阵PMatrix2D来渲染按钮,但使用此变换矩阵的逆矩阵在 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():可能需要由按钮处理?如果按钮在被调用时嵌套在一堆 pushMatrix()/popMatrix() 调用.draw()中怎么办?ETC...

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

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

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 环形图像上使用这些圆形按钮并且可能带有一点发光可能看起来非常好,并且在处理草图中实现比采用 ControlP5 路线更简单。不要误会我的意思,它是一个很棒的库,但对于自定义行为,有时使用您自己的自定义代码更有意义。

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

于 2021-11-09T00:20:26.317 回答