/*
* Copyright 2014-2015 Cel Skeggs.
*
* This file is part of the CCRE, the Common Chicken Runtime Engine.
*
* The CCRE is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the CCRE. If not, see <http://www.gnu.org/licenses/>.
*/
package ccre.supercanvas.components.channels;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.io.Serializable;
import ccre.channel.BooleanInput;
import ccre.channel.BooleanOutput;
import ccre.channel.CancelOutput;
import ccre.rconf.RConf.Entry;
import ccre.supercanvas.BaseChannelComponent;
import ccre.supercanvas.Rendering;
import ccre.supercanvas.SuperCanvasPanel;
/**
* A component allowing interaction with booleans.
*
* @author skeggsc
*/
public class BooleanControlComponent extends BaseChannelComponent<BooleanControlComponent.View> {
private static final long serialVersionUID = -5140494090957643875L;
static enum View {
RED_GREEN_SWITCH, LINEAR_ON_OFF, TEXTUAL
}
private boolean lastSentValue;
private final BooleanInput alternateSource;
private final BooleanOutput rawOut;
private transient CancelOutput unsubscribe;
/**
* Create a new BooleanControlComponent with a BooleanOutput to control.
*
* @param cx the X coordinate.
* @param cy the Y coordinate.
* @param name the name of the output.
* @param out the BooleanOutput to control.
*/
public BooleanControlComponent(int cx, int cy, String name, BooleanOutput out) {
this(cx, cy, name, null, out);
}
/**
* Create a new BooleanControlComponent.
*
* @param cx the X coordinate.
* @param cy the Y coordinate.
* @param name the name of the output.
*/
public BooleanControlComponent(int cx, int cy, String name) {
this(cx, cy, name, BooleanOutput.ignored);
}
/**
* Create a new BooleanControlComponent, with an input channel to represent
* the actual value as returned by the remote.
*
* @param cx the X coordinate.
* @param cy the Y coordinate.
* @param name the name of the output.
* @param inp the BooleanInput to control.
* @param out the BooleanOutput to control.
*/
public BooleanControlComponent(int cx, int cy, String name, BooleanInput inp, BooleanOutput out) {
super(cx, cy, name);
rawOut = out;
alternateSource = inp;
}
@Override
protected boolean containsForInteract(int x, int y) {
switch (activeView) {
case RED_GREEN_SWITCH:
return x >= centerX - 40 && x <= centerX + 30 && y >= centerY - 20 && y <= centerY + 30;
case LINEAR_ON_OFF:
return x >= centerX - halfWidth + 5 && x <= centerX + halfWidth - 5 && y >= centerY - 15 && y <= centerY + 15;
case TEXTUAL:
return x >= centerX - 50 && x <= centerX + 50 && y >= centerY - 10 && y <= centerY + 20;
default:
return false;
}
}
private boolean getDele() {
return this.alternateSource != null ? this.alternateSource.get() : lastSentValue;
}
@Override
public void channelRender(Graphics2D g, int screenWidth, int screenHeight, FontMetrics fontMetrics, int mouseX, int mouseY) {
boolean isPressed = getDele();
switch (activeView) {
case RED_GREEN_SWITCH:
AffineTransform origO = g.getTransform(); {
g.setColor(isPressed ? Color.GREEN.darker() : Color.RED.darker());
AffineTransform orig = g.getTransform();
g.rotate(isPressed ? 10 : -10, centerX + (isPressed ? 3 : -3), centerY + 10);
g.fillRect(centerX - 5, centerY + 5, 10, 45);
g.setTransform(orig);
g.setColor(Color.GRAY.darker().darker());
g.fillRect(centerX - 20, centerY + 10, 40, 20);
}
g.translate(-5, 2); {
g.setColor(isPressed ? Color.GREEN : Color.RED);
AffineTransform orig = g.getTransform();
g.rotate(isPressed ? 10 : -10, centerX + (isPressed ? 3 : -3), centerY + 10);
g.fillRect(centerX - 5, centerY + 5, 10, 45);
g.setTransform(orig);
g.setColor(Color.GRAY.darker());
g.fillRect(centerX - 20, centerY + 10, 40, 20);
}
g.setTransform(origO);
break;
case LINEAR_ON_OFF:
g.setColor(Color.LIGHT_GRAY);
g.fillRoundRect(centerX - halfWidth + 10, centerY - 20, halfWidth * 2 - 20, 40, 20, 20);
g.setColor(Color.BLACK);
g.drawRoundRect(centerX - halfWidth + 10, centerY - 20, halfWidth * 2 - 20, 40, 20, 20);
g.setColor(isPressed ? Color.GREEN : Color.RED);
g.fillRoundRect(centerX - halfWidth + (isPressed ? halfWidth + 5 : 15), centerY - 15, halfWidth - 20, 30, 20, 20);
g.setColor(Color.BLACK);
g.drawRoundRect(centerX - halfWidth + (isPressed ? halfWidth + 5 : 15), centerY - 15, halfWidth - 20, 30, 20, 20);
Stroke oldStroke = g.getStroke();
g.setStroke(new BasicStroke(3, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND));
g.setColor(isPressed ? Color.BLACK : Color.GRAY);
g.drawLine(centerX + halfWidth / 2 - 5, centerY - 5, centerX + halfWidth / 2 - 5, centerY + 5);
g.setColor(!isPressed ? Color.BLACK : Color.GRAY);
g.drawOval(centerX - halfWidth / 2, centerY - 5, 10, 10);
g.setStroke(oldStroke);
break;
case TEXTUAL:
g.setFont(Rendering.labels);
if (getPanel().editmode) {
g.setColor(Color.BLACK);
} else {
g.setColor(isPressed ? Color.GREEN : Color.RED);
}
String text = isPressed ? "TRUE" : "FALSE";
g.drawString(text, centerX - g.getFontMetrics().stringWidth(text) / 2, centerY + g.getFontMetrics().getAscent() / 2);
break;
}
}
@Override
public boolean onInteract(int x, int y) {
if (!containsForInteract(x, y)) {
return false;
}
switch (activeView) {
case RED_GREEN_SWITCH:
case TEXTUAL:
lastSentValue = !getDele();
break;
case LINEAR_ON_OFF:
if (x < centerX - 5) {
lastSentValue = false;
} else if (x > centerX + 5) {
lastSentValue = true;
} else {
return true;
}
break;
default:
return false;
}
if (rawOut != null) {
rawOut.safeSet(lastSentValue);
}
return true;
}
@Override
protected void setDefaultView() {
activeView = View.RED_GREEN_SWITCH;
}
private final BooleanOutput fakeOut = new FakeBooleanOutput();
private boolean isFakeSubscribed = false;
@Override
protected void onChangePanel(SuperCanvasPanel panel) {
boolean hasPanel = panel != null;
if (alternateSource != null && hasPanel != isFakeSubscribed) {
if (unsubscribe != null) {
unsubscribe.cancel();
unsubscribe = null;
}
if (hasPanel) {
unsubscribe = alternateSource.send(fakeOut);
}
isFakeSubscribed = hasPanel;
}
}
private static final class FakeBooleanOutput implements BooleanOutput, Serializable {
private static final long serialVersionUID = -5025143910878910655L;
@Override
public void set(boolean b) {
// Do nothing. This is just so that we can make the remote end send
// us data by subscribing.
}
}
@Override
public Entry[] queryRConf() throws InterruptedException {
return rconfBase();
}
@Override
public boolean signalRConf(int field, byte[] data) throws InterruptedException {
return rconfBase(field, data) == BASE_VALID;
}
}