/* * * Copyright 2014 http://Bither.net * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * / */ package net.bither.viewsystem.components.borders; import net.bither.viewsystem.components.ImageDecorator; import javax.swing.border.AbstractBorder; import java.awt.*; import java.awt.geom.Area; import java.awt.geom.RoundRectangle2D; /** * <p>Border to provide the following to panels:</p> * <ul> * <li>Rounded, colored border with variable thickness</li> * </ul> * <p>Adapted from <a href="http://stackoverflow.com/a/16909994/396747">this Stack Overflow answer</a></p> * * @since 0.0.1 */ public class TextBubbleBorder extends AbstractBorder { /** * The border color */ private final Color color; /** * The border thickness */ private final int thickness; /** * The border radii */ private final int radii; /** * The "speech pointer" */ private final int pointerSize; /** * The positioning of the "speech pointer" */ private final boolean pointerLeft; private final Insets insets; private final BasicStroke stroke; private final int strokePad; private final RenderingHints hints; /** * <p>A default rounded panel with no pointer</p> * * @param color The color */ public TextBubbleBorder(Color color) { this(color, 1, 10, 0, true); } /** * <p>A customised text bubble with no pointer</p> * * @param color The color * @param thickness The thickness (in px) * @param radii The radius to use for each corner (default 10) */ public TextBubbleBorder(Color color, int thickness, int radii) { this(color, thickness, radii, 0, true); } /** * <p>A customised text bubble with pointer defined</p> * * @param color The color * @param thickness The thickness (in px) * @param radii The radius to use for each corner (default 10) * @param pointerSize The pointer size * @param pointerLeft True if the pointer is on the left */ public TextBubbleBorder(Color color, int thickness, int radii, int pointerSize, boolean pointerLeft) { this.pointerLeft = pointerLeft; this.thickness = thickness; this.radii = radii; this.pointerSize = pointerSize; this.color = color; this.stroke = new BasicStroke(thickness); this.strokePad = thickness / 2; this.hints = new RenderingHints(ImageDecorator.smoothRenderingHints()); // Top and bottom padding must adjust to allow for single line panels (e.g. alerts) // and also compromise for text fields and text areas. A good value is 6px. int topPad = 6; int bottomPad = Math.max(topPad, pointerSize + strokePad); insets = new Insets(topPad, 8, bottomPad, 8); } @Override public Insets getBorderInsets(Component c) { return insets; } @Override public Insets getBorderInsets(Component c, Insets insets) { return getBorderInsets(c); } @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { // Work out the lowest inside line of the bubble int bottomLineY = height - thickness - pointerSize - 1; // Draw the rounded bubble border with a few tweaks for text fields and areas RoundRectangle2D.Double bubble = new RoundRectangle2D.Double( strokePad + 2, strokePad + 2, width - thickness - strokePad - 3, bottomLineY - 2, radii, radii); Area area = new Area(bubble); // Should the "speech pointer" polygon be included? if (pointerSize > 0) { Polygon pointer = new Polygon(); int pointerPad = 4; // Place on left if (pointerLeft) { // Left point pointer.addPoint(strokePad + radii + pointerPad, bottomLineY); // Right point pointer.addPoint(strokePad + radii + pointerPad + pointerSize, bottomLineY); // Bottom point pointer.addPoint(strokePad + radii + pointerPad + (pointerSize / 2), height - strokePad); } else { // Left point pointer.addPoint(width - (strokePad + radii + pointerPad), bottomLineY); // Right point pointer.addPoint(width - (strokePad + radii + pointerPad + pointerSize), bottomLineY); // Bottom point pointer.addPoint(width - (strokePad + radii + pointerPad + (pointerSize / 2)), height - strokePad); } area.add(new Area(pointer)); } // Get the 2D graphics context Graphics2D g2 = (Graphics2D) g; g2.setRenderingHints(hints); // Paint the background color of the parent everywhere // outside the clip of the text bubble Component parent = c.getParent(); if (parent != null) { Color bg = parent.getBackground(); Rectangle rect = new Rectangle(0, 0, width, height); Area borderRegion = new Area(rect); borderRegion.subtract(area); g2.setClip(borderRegion); g2.setColor(bg); g2.fillRect(0, 0, width, height); g2.setClip(null); } // Set the border color g2.setColor(color); g2.setStroke(stroke); g2.draw(area); } }