Botão de Volume (com JSlider Circular?)

Olá, eu e minha equipe estamos fazendo um sintetizador para o trabalho da faculdade, no caso estou fazendo a interface gráfica e gostaria de fazer os seguintes botões:

botoes

Os botões de mixagem funcionariam como desse teclado online https://nicroto.github.io/viktor/

Editei porque agora acredito que deveria usar um JSlider Circular por baixo do botão, ou mesmo no lugar do botão, por causa de como faz girar os botões do sintetizador online que eu linkei (acredito que ficaria mais fácil para o usuário), porém, ainda não faço ideia de como fazer. Alguém sabe de algum material bom (serve até PDFs), de preferência em Português, para eu tentar aprender sobre Graphics 2D e o JSlider?

Teria que usar o Graphics2D, acho que seria mais ou menos:

class BotaoCircular extends JComponent {
  public BotaoCircular(int size) {
    setSize(size, size);
    setPreferredSize(size, size);
    ângulo = 0;
  }
  public void paint(Graphics g) {
    if (g != null) {
      Graphics2D g2d = (Graphics2d) g;
      g2d.setColor(BLACK);
      size = getWidth;

      g2d.fillRect(0, 0, size , size );
      g2d.setColor(WHITE);
      
      xc = size / 2.0; // centro x
      yc = size / 2.0; // centro y
      r = size / 2.0; // raio
      
      yl = sin(angulo) * r + yc; // acho q vai gira no sentido horário, se inverter o sinal do sin, vai no anti
      xl = cos(ângulo) * r + xc;

      g2d.drawLine(xc,yc,xl,yl);
      g2d.drawOval(0, 0, size, size)
      g2d.dispose();
    }
  }
}

Tentei dar uma arrumada no seu código, que tava dando erro, ficou assim:

Porém, ainda não ficou um botão no formato de circulo (ao redor do circulo fica um fundo preto, no formato de quadrado, e a circunferência do circulo não ficou legal…), segue imagem de como ficou (o de cima), e como seria um circulo mesmo:

BCs

Não sei como fazer o evento de click para ele girar (porque deveria atualizar o angulo, certo?), e na verdade eu acredito que deveria usar um JSlider por baixo do botão (ou mesmo ao invés do botão), para fazer o evento de girar, mas também não sei como faria isso. Tem algum pdf, algum material bom, de preferência em português para eu tentar aprender sobre o JSlider e sobre o Graphics 2D?

O que postei foi só uma idéia, eu estou sem java para testar.

Não conheço o JSlider.

Sobre Graphics, o melhor site que conheço é http://www.pontov.com.br/site/index.php/java/48-java2d

Acho que está no caminho certo, mas tem que ir com calma:

adicione a linha com g2d.setRenderingHint para o circulo ficar mais elegante:

        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

e altere o raio para:

  r = (size - 2) / 2.0; // raio

No construtor adicione seguinte linha para remover o fundo:

setOpaque(false);

e no método paint remova as linhas ou troque somente a cor pelo fundo desejado:

// g2d.setColor(BLACK);
// g2d.fillRect(0, 0, size , size );

Em outra classe onde é instanciado o botão, adicione o listener:

botaoCircular.addMouseListener(new MouseAdapter(){
  @Override 
  publiv void mouseClicked(MouseEvent e) {
    botaoCircular.setAngulo(botaoCircular.getAngulo() + Math.toRadians(30)); // rotacione 30 graus (o java usa radianos)
    botaoCircular.repaint(); // avisa o componente para atualizar
  }
});

Então, to tentando fazer o seguinte, ao clicar no componente, enquanto manter o botão do mouse pressionado e arrastar pra cima ou para direita, e o angulo for menor do que 315º (porque o botão de frequência vai ter 5 frequências, e eu tive que inicializar o angulo com 90º pra ficar na posição do 0 igual na imagem) , o angulo incrementaria em 45° (vai pra próxima posição), senão se, arrastar para baixo ou para esquerda, e o angulo for maior do que 90º, o ângulo decrementa em 45° (vai pra posição anterior).

Esse é meu código (não tá funcionando):

    frequencia.addMouseListener(new MouseAdapter(){
    	public void mouseClicked(MouseEvent e) {
    		Point pi = new Point();
    		Point pf = new Point();
    		
    		double angulo;
    		
    		while(e.isAltDown()) { // Enquanto mouse esta pressionado
    			pi = MouseInfo.getPointerInfo().getLocation();
    			try {
					TimeUnit.MILLISECONDS.sleep(250);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
    			pf = MouseInfo.getPointerInfo().getLocation();
    			angulo = frequencia.getAngulo();
    			
    			if((pf.x > pi.x || pf.y > pi.y ) && (angulo < Math.toRadians(315))){ // Aumentar Frenquencia
    				frequencia.setAngulo(frequencia.getAngulo() + Math.toRadians(45)); // rotaciona 45 graus
            		frequencia.repaint(); // avisa o componente para atualizar
    			} else if((pf.x < pi.x || pf.y < pi.y) && (angulo > Math.toRadians(90))){ // Diminuir Frequencia
    				frequencia.setAngulo(frequencia.getAngulo() + Math.toRadians(-45)); // rotaciona 45 graus
            		frequencia.repaint(); // avisa o componente para atualizar
    			} else // Nao arastou o cursor
    				continue;
    		}
    	}
    });

O código da classe do botão:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JComponent;

public class BotaoCircular extends JComponent {
	private double angulo;
    private double xc;
    private double yc;
    private double r;
	private double xl;
	private double yl;
    private int diametro;
    
    public BotaoCircular(int size, int posx, int posy) {
    	diametro = size;
		angulo = 90;
		setBounds(posx, posy, size+1, size+1);
		setOpaque(false);
	}
	  
	public void paint(Graphics g) {
		if (g != null) {
			Graphics2D g2d = (Graphics2D) g;
			g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
			//g2d.setColor(Color.BLACK);
			//diametro = getWidth();

			//g2d.fillRect(0, 0, size , size );
			g2d.setColor(Color.WHITE);
	      
			xc = diametro / 2.0; // centro x
			yc = diametro / 2.0; // centro y
			r = (diametro - 2) / 2.0; // raio
	      
			yl = Math.sin(angulo) * r + yc;
			xl = Math.cos(angulo) * r + xc;

			g2d.drawLine((int)xc, (int)yc, (int)xl, (int)yl);
			g2d.drawOval(0, 0, diametro, diametro);
			g2d.dispose();
		}
	}

	public double getAngulo() {
		return this.angulo;
	}
	
	public void setAngulo(double d) {
		this.angulo = d;
	}
}

E como eu faço para colocar um texto no JFrame com o graphics 2D, porque no caso eu queria colocar o 0 a 5 nas exatas posições com os ângulos do circulo.

Uma coisa de cada vez:

Primeiro na classe do botão, adicione os atributos e métodos:

void setValor(int valor) {
  if (valor < 0) valor = 0;
  if (valor > valorMaximo) valor = valorMaximo;
  this.valor = valor;
  this.repaint();
}


void setValorMaximo(int valorMaximo);

void setAnguloMinimo(double);

void setAnguloMaximo(double);

no paint:

double totalDePosicoes = valorMaximo + 1; // considerando o zero
double deltaAngulo = anguloMaximo - anguloMinimo; // delta = diferença entre os angulos
double ângulo = ((valor * deltaAngulo) / totalDePosicoes) + anguloMinimo;

Faça teste:

frequência.setAnguloMinimo(315);
frequência.setAnguloMaximo(90);
frequência.setValorMaximo(4); // são 5 considerando o 0
frequência.setValor(0);

frequencia.addMouseListener(new MouseAdapter(){
  public void mouseClicked(MouseEvent e) {
    frequência.setValor((frequência.getValor()+1) % 5); // não é necessário o repaint pois já atualiza no método setValor

fazer o “botão do mouse pressionado e arrastar pra cima ou para direita” é mais complicado

Tem uma forma mais fácil de fazer sem usar o Graphics, mas primeiro é melhor arrumar o botão.

Depois de testado e supondo que esteja funcionando:

No construtor da classe do botão:

MouseAdapter listener = new MouseAdapter() {
  publiv void mousePressed(MouseEvent e) {
    apontarPara(e.getPoint()); repaint();
  }
  publiv void mouseReleased(MouseEvent e) {
    apontarPara(e.getPoint());
    apontar = null; // fixa
     repaint();
  }
  publiv void mouseDragged(MouseEvent e) {
    apontarPara(e.getPoint()); repaint();
  }
}
addMouseListener(listener); // usa o pressed e released
addMouseMotionListener(listener); // usa o dragged (arrastar)

Atributo apontar:

Point apontar = null;

no paint:

if (apontar != null) {
    xrm = apontar.x - xc; // x relativo do mouse
    yrm = apontar.y - yc;
    d = raiz(xrm * xrm + yrm * yrm); // distancia do mouse ao centro
    seno = yrm / d;
    cosseno = xrm / d;
    // falta verificar o mínimo e máximo aqui
} else {
    double totalDePosicoes = valorMaximo + 1; // considerando o zero
    double deltaAngulo = anguloMaximo - anguloMinimo; // delta = diferença entre os angulos
    double ângulo = ((valor * deltaAngulo) / totalDePosicoes) + anguloMinimo;
    seno = Math.sin(angulo);
    cosseno = Math.sin(angulo);
}
  yl = seno * r + yc; // acho q vai gira no sentido horário, se inverter o sinal do sin, vai no anti
  xl = cosseno * r + xc;

e

void apontarPara(apontar) {
  this.apontar = apontar;
 // aqui precisa atualozar e encontrar o valor, não sei como fazer isso ainda.  
}

Fiz isso, porém, os valores no caso vão de 0 a 5, e ai setando valor máximo para 5, ele por algum motivo roda no sentido anti-horário, e roda com um ângulo de inclinação bem pequeno, e testando com 4 mesmo, ele roda com um ângulo perto de 45º, mas diferente. Ai fiz alguns ajustes, porém, não tá rodando em 45° bonitinho, ou por algum motivo não tá iniciando na posição correta…

no número total de posições eu fiz:

double totalDePosicoes = valorMaximo;

Porque seria o número total de posições que iria variar, então 6 - 1, já que teoricamente ele iria variar em 45º e 45º, e não sei porque diabos tem que setar 90º como a posição inicial que eu quero (poque deveria ser 135°, já eu quero no meio do quarto quadrante)… Na real eu acho que ele não tá setando na posição certa, mas ta rodando certo.

Então, como é,

angulo = valor * ((deltaAngulo / totalDePosicoes)) + anguloMinimo;

ficaria:

angulo = [0 , 5] * (225 / 5) + a_min
angulo = [a_min, 45 + a_min, 90 + a_min, 135 + a_min, 180 + a_min, 225 + a_min]

era pra dar certo… E no caso eu ajustei:

frequencia.setAnguloMinimo(90);
frequencia.setAnguloMaximo(315);
frequencia.setValorMaximo(5);
frequencia.setValor(frequencia.getValor()+1); // Porque [1, 6] % 6 = [1, 2, 3, 4, 5, 0], ou [1, 6] % 5 = [1, 2, 3, 4, 0, 1]

Eu acho que 90 não é a posição inicial certinha, e também não é 135º, porque fica quase a 180º…

ruim

Eu vou acabar de fazer os outros rolês da interface, que tem que entregar parte amanhã, ai amanhã eu volto a tentar arrumar o botão… E meu, muito obrigado, cê tá me ajudando muito ^^

Ao atribuir o valor, ele tentar achar o ângulo, portanto é o ângulo que deve estar errado.

Verifique se o ângulo está em radiano, caso contrário:

double anguloRad = Math.toRadians(angulo);

O Java usa o eixo y invertido, faça um teste:

// yl = Math.sin(anguloRad) * r + yc;
yl = -Math.sin(anguloRad) * r + yc;