/* * @(#)GradientSliderUI.java * * $Date: 2014-06-06 20:04:49 +0200 (P, 06 jún. 2014) $ * * Copyright (c) 2011 by Jeremy Wood. * All rights reserved. * * The copyright of this software is owned by Jeremy Wood. * You may not use, copy or modify this software, except in * accordance with the license agreement you entered into with * Jeremy Wood. For details see accompanying license terms. * * This software is probably, but not necessarily, discussed here: * https://javagraphics.java.net/ * * That site should also contain the most recent official version * of this software. (See the SVN repository for more details.) */ package com.bric.plaf; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.TexturePaint; import java.awt.geom.AffineTransform; import java.awt.geom.GeneralPath; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import javax.swing.JComponent; import com.bric.swing.GradientSlider; import com.bric.swing.MultiThumbSlider; /** The UI for the GradientSlider class. * * There are a few properties you can use to customize the UI * of a GradientSlider. You can set these for each slider * by calling: * <BR><code>slider.putClientProperty(key,value);</code> * <P>Or you can set these globally by calling: * <BR><code>UIManager.put(key,value);</code> * <P>The available properties are: * <P><TABLE summary="GradientSliderUI Client Properties" BORDER="1" CELLPADDING=5> * <TR> * <TD>Property Name</TD><TD>Default Value</TD><TD>Description</td> * </TR> * <TR> * <TD>GradientSlider.useBevel</TD><TD>"false"</TD><TD>If this is <code>true</code>, then this slider will be painted in a rectangle with a bevel effect around the borders. If this is <code>false</code>, then this slider will be painted in a rounded rectangle.</td> * </TR> * <TR> * <TD>GradientSlider.showTranslucency</TD><TD>"true"</TD><TD>If this is <code>true</code>, then the slider will reflect the opacity of the colors in the gradient, and paint a checkered background underneath the colors to indicate opacity. If this is <code>false</code>, then this slider will always paint with completely opaque colors, although the actual colors may be translucent.</td> * </TR> * <TR> * <TD>GradientSlider.colorPickerIncludesOpacity</TD><TD>"true"</TD><TD>This is used when the user double-clicks a color and a ColorPicker dialog is invoked. (So this value may not have any meaning if you override <code>GradientSlider.doDoubleClick()</code>.) This controls whether the opacity/alpha controls are available in that dialog. This does <i>not</i> control whether translucent colors can be used in this slider: translucent colors are always allowed, if the user can enter them.</TD> * </TR> * <TR> * <TD>MultiThumbSlider.indicateComponent</TD><TD>"true"</TD><TD>If this is <code>true</code>, then the thumbs will only paint on this component when the mouse is inside this slider <i>or</i> when this slider as the keyboard focus.</td> * </TR> * <TR> * <TD>MultiThumbSlider.indicateThumb</TD><TD>"true"</TD><TD>If this is <code>true</code>, then the thumb the mouse is over will gently fade into a slightly different color.</td> * </TR> * </TABLE> * */ public class GradientSliderUI extends MultiThumbSliderUI { int TRIANGLE_SIZE = 8; /** The width of this image is the absolute widest the track will * ever become. */ BufferedImage img = new BufferedImage(1000,1,BufferedImage.TYPE_INT_ARGB); /** A temporary array used for the buffered image */ int[] array = new int[img.getWidth()]; public GradientSliderUI(GradientSlider slider) { super(slider); } @Override public int getClickLocationTolerance() { return TRIANGLE_SIZE; } protected void calculateImage() { float[] f = slider.getThumbPositions(); Color[] c = ((GradientSlider)slider).getColors(); /** make sure we DO have a value at 0 and 1: */ if(f[0]!=0) { float[] f2 = new float[f.length+1]; System.arraycopy(f,0,f2,1,f.length); Color[] c2 = new Color[c.length+1]; System.arraycopy(c,0,c2,1,f.length); f = f2; c = c2; f[0] = 0; c[0] = c[1]; } if(f[f.length-1]!=1) { float[] f2 = new float[f.length+1]; System.arraycopy(f,0,f2,0,f.length); Color[] c2 = new Color[c.length+1]; System.arraycopy(c,0,c2,0,f.length); f = f2; c = c2; f[f.length-1] = 1; c[c.length-1] = c[c.length-2]; } /** Now, finally paint */ int[] argb = new int[c.length]; for(int a = 0; a<argb.length; a++) { argb[a] = ((c[a].getAlpha() & 0xff) << 24) + ((c[a].getRed() & 0xff) << 16) + ((c[a].getGreen() & 0xff) << 8) + ((c[a].getBlue() & 0xff) << 0) ; } int max; if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) { max = trackRect.width; } else { max = trackRect.height; } if(max<=0) return; boolean alwaysOpaque = getProperty(slider,"GradientSlider.showTranslucency","true").equals("false"); float fraction; int i1 = 0; int i2 = 1; int a1 = (argb[0] >> 24) & 0xff; int r1 = (argb[0] & 0x00ff0000) >> 16; int g1 = (argb[0] & 0x0000ff00) >> 8; int b1 = (argb[0] & 0x000000ff) >> 0; int a2 = (argb[1] >> 24) & 0xff; int r2 = (argb[1] & 0x00ff0000) >> 16; int g2 = (argb[1] & 0x0000ff00) >> 8; int b2 = (argb[1] & 0x000000ff) >> 0; for(int z = 0; z<max; z++) { fraction = ((float)z)/((float)(max-1)); if(fraction<1 && fraction>=f[i2]) { while(fraction<1 && fraction>=f[i2]) { i1++; i2++; } a1 = (argb[i1] >> 24) & 0xff; r1 = (argb[i1] & 0x00ff0000) >> 16; g1 = (argb[i1] & 0x0000ff00) >> 8; b1 = (argb[i1] & 0x000000ff) >> 0; a2 = (argb[i2] >> 24) & 0xff; r2 = (argb[i2] & 0x00ff0000) >> 16; g2 = (argb[i2] & 0x0000ff00) >> 8; b2 = (argb[i2] & 0x000000ff) >> 0; } float colorFraction = (fraction-f[i1])/(f[i2]-f[i1]); if(colorFraction>1) colorFraction = 1; if(colorFraction<0) colorFraction = 0; if(alwaysOpaque) { a1 = 255; a2 = 255; } array[z] = (((int)(a1*(1-colorFraction)+a2*colorFraction)) << 24) + (((int)(r1*(1-colorFraction)+r2*colorFraction)) << 16) + (((int)(g1*(1-colorFraction)+g2*colorFraction)) << 8) + (((int)(b1*(1-colorFraction)+b2*colorFraction))); } img.getRaster().setDataElements(0,0,max,1,array); } @Override public Dimension getMinimumSize(JComponent s) { Dimension d = super.getMinimumSize(s); if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) { d.height+=2; } else { d.width+=2; } return d; } @Override public Dimension getPreferredSize(JComponent s) { Dimension d = super.getPreferredSize(s); if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) { d.height+=2; } else { d.width+=2; } return d; } @Override protected Rectangle calculateTrackRect() { int w = slider.getWidth(); int h = slider.getHeight(); Rectangle r = new Rectangle(); if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) { r.x = TRIANGLE_SIZE; r.y = 3; r.height = h-TRIANGLE_SIZE-r.y; r.width = w-2*TRIANGLE_SIZE; if(r.width>img.getWidth()) { r.width = img.getWidth(); r.x = (w-2*TRIANGLE_SIZE)/2-r.width/2; } if(r.height>2*DEPTH) { r.height = 2*DEPTH; r.y = (h-TRIANGLE_SIZE)/2-r.height/2; } } else { r.x = 3; r.y = TRIANGLE_SIZE; r.width = w-TRIANGLE_SIZE-r.x; r.height = h-2*TRIANGLE_SIZE; if(r.height>img.getWidth()) { r.height = img.getWidth(); r.y = (h-2*TRIANGLE_SIZE)/2-r.height/2; } if(r.width>2*DEPTH) { r.width = 2*DEPTH; r.x = (w-TRIANGLE_SIZE)/2-r.width/2; } } return r; } @Override protected void calculateGeometry() { super.calculateGeometry(); calculateImage(); } static TexturePaint checkerPaint; private static void createCheckerPaint() { int k = 4; BufferedImage bi = new BufferedImage(2*k,2*k,BufferedImage.TYPE_INT_RGB); Graphics2D g = bi.createGraphics(); g.setColor(Color.white); g.fillRect(0,0,2*k,2*k); g.setColor(Color.lightGray); g.fillRect(0,0,k,k); g.fillRect(k,k,k,k); checkerPaint = new TexturePaint(bi,new Rectangle(0,0,bi.getWidth(),bi.getHeight())); } /** The "frame" includes the trackRect and possible some extra padding. * For example, the frame might be the rounded rectangle enclosing the * track (if rounded rectangles are turned on) * @return */ private Shape getFrame() { if(getProperty(slider,"GradientSlider.useBevel","false").equals("true")) { return trackRect; } if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) { int curve = Math.min(TRIANGLE_SIZE-2,trackRect.height/2); return new RoundRectangle2D.Float( trackRect.x-curve, trackRect.y, trackRect.width+2*curve, trackRect.height, curve*2, curve*2 ); } else { int curve = Math.min(TRIANGLE_SIZE-2,trackRect.width/2); return new RoundRectangle2D.Float( trackRect.x, trackRect.y-curve, trackRect.width, trackRect.height+2*curve, curve*2, curve*2 ); } } @Override protected void paintTrack(Graphics2D g) { Composite oldComposite = g.getComposite(); float alpha = slider.isEnabled() ? 1 : .5f; g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); Shape frame = getFrame(); boolean alwaysOpaque = getProperty(slider,"GradientSlider.showTranslucency","true").equals("false"); if(alwaysOpaque==false) { if(checkerPaint==null) createCheckerPaint(); g.setPaint(checkerPaint); g.fill(frame); } TexturePaint tp = new TexturePaint(img,new Rectangle(trackRect.x,0,img.getWidth(),1)); g.setPaint(tp); AffineTransform oldTransform = null; oldTransform = g.getTransform(); AffineTransform transform = new AffineTransform(); if(slider.getOrientation()==MultiThumbSlider.VERTICAL) { if(slider.isInverted()) { transform.rotate(Math.PI/2, trackRect.x, trackRect.y); } else { transform.rotate(-Math.PI/2, trackRect.x, trackRect.y+trackRect.height); } } else { if(slider.isInverted()) { //flip horizontal: double x1 = trackRect.x; double x2 = trackRect.x+trackRect.width; //m00*x1+m02 = x2 //m00*x2+m02 = x1 double m00 = (x2-x1)/(x1-x2); double m02 = x1-m00*x2; transform.setTransform(m00, 0, 0, 1, m02, 0); } else { //no transform necessary } } g.transform(transform); try { g.fill( transform.createInverse().createTransformedShape(trackRect)); } catch(NoninvertibleTransformException e) { //this won't happen; unless a width/height //is zero somewhere, in which case we have nothing to paint anyway. } if(oldTransform!=null) { g.setTransform(oldTransform); } if(getProperty(slider,"GradientSlider.useBevel","false").equals("true")) { PlafPaintUtils.drawBevel(g, trackRect); } else { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Shape oldClip = g.getClip(); int first, last; Color[] colors = ((GradientSlider)slider).getColors(); float[] f = slider.getThumbPositions(); if( (slider.isInverted()==false && slider.getOrientation()==MultiThumbSlider.HORIZONTAL) || (slider.isInverted()==true && slider.getOrientation()==MultiThumbSlider.VERTICAL)) { first = 0; last = colors.length-1; while(f[first]<0) { first++; } while(f[last]>1) { last--; } } else { last = 0; first = colors.length-1; while(f[last]<0) { last++; } while(f[first]>1) { first--; } } if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) { g.clip(frame); g.setColor(colors[first]); g.fillRect(0, 0, trackRect.x, slider.getHeight()); g.setColor(colors[last]); g.fillRect(trackRect.x+trackRect.width, 0, slider.getWidth()-(trackRect.x+trackRect.width), slider.getHeight()); } else { g.clip(frame); g.setColor(colors[first]); g.fillRect(0, 0, slider.getWidth(), trackRect.y); g.setColor(colors[last]); g.fillRect(0, trackRect.y+trackRect.height, slider.getWidth(), slider.getHeight()-(trackRect.y+trackRect.height) ); } g.setStroke(new BasicStroke(1)); g.setClip(oldClip); g.setColor(new Color(0,0,0,130)); g.draw(frame); g.setColor(new Color(0,0,0,130)); } if(slider.isPaintTicks()) { paintTick(g,.25f,2); paintTick(g,.5f,2); paintTick(g,.75f,2); paintTick(g,0f,2); paintTick(g,1f,2); } g.setComposite(oldComposite); } protected void paintTick(Graphics2D g,float f,int d) { if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) { int x = (int)(trackRect.x+trackRect.width*f+.5f); int y = trackRect.y+trackRect.height; g.drawLine(x,y,x,y+d); y = trackRect.y; g.drawLine(x,y,x,y-d); } else { int y = (int)(trackRect.y+trackRect.height*f+.5f); int x = trackRect.x+trackRect.width; g.drawLine(x,y,x+d,y); x = trackRect.x; g.drawLine(x,y,x-d,y); } } @Override protected void paintFocus(Graphics2D g) { } static GeneralPath hTriangle = null; static GeneralPath vTriangle = null; @Override protected void paintThumbs(Graphics2D g) { if(slider.isEnabled()==false) return; if(hTriangle==null) { hTriangle = new GeneralPath(); hTriangle.moveTo(0,0); hTriangle.lineTo(TRIANGLE_SIZE,TRIANGLE_SIZE); hTriangle.lineTo(-TRIANGLE_SIZE,TRIANGLE_SIZE); hTriangle.lineTo(0,0); hTriangle.closePath(); vTriangle = new GeneralPath(); vTriangle.moveTo(0,0); vTriangle.lineTo(TRIANGLE_SIZE,TRIANGLE_SIZE); vTriangle.lineTo(TRIANGLE_SIZE,-TRIANGLE_SIZE); vTriangle.lineTo(0,0); vTriangle.closePath(); } AffineTransform t = new AffineTransform(); int dx = trackRect.x+trackRect.width; int dy = trackRect.y+trackRect.height; dy -= trackRect.height/6; dx -= trackRect.width/6; int selected = slider.getSelectedThumb(false); float[] f = slider.getThumbPositions(); int orientation = slider.getOrientation(); Shape shape; Composite oldComposite = g.getComposite(); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, indication)); for(int a = 0; a<thumbPositions.length; a++) { if(f[a]>=0 && f[a]<=1 && a!=selected) { if(orientation==MultiThumbSlider.HORIZONTAL) { dx = thumbPositions[a]; shape = hTriangle; } else { dy = thumbPositions[a]; shape = vTriangle; } t.setToTranslation(dx,dy); g.transform(t); float brightness = Math.max(0, thumbIndications[a]*.6f); g.setColor( new Color( (int)(255*brightness), (int)(255*brightness), (int)(255*brightness)) ); g.fill(shape); g.translate(-.5f,-.5f); g.setColor( new Color( 255, 255, 255) ); g.draw(shape); g.translate(.5f,.5f); t.setToTranslation(-dx,-dy); g.transform(t); } } g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, indication)); if(selected!=-1 && f[selected]>=0 && f[selected]<=1) { if(orientation==MultiThumbSlider.HORIZONTAL) { dx = thumbPositions[selected]; shape = hTriangle; } else { dy = thumbPositions[selected]; shape = vTriangle; } t.setToTranslation(dx,dy); g.transform(t); g.setColor( new Color( 255, 255, 255) ); g.fill(shape); g.translate(-.5f,-.5f); g.setColor( new Color( 0, 0, 0) ); g.draw(shape); g.translate(.5f,.5f); t.setToTranslation(-dx,-dy); g.transform(t); } g.setComposite(oldComposite); } }