Возможный способ:
- нарисовать полный синий круг без обрезки
- нарисовать полный красный круг, обрезанный вычисленным (и возможно повернутым) прямоугольником, который будет перезаписанчасть синего круга
- нарисуйте контур круга
- нарисуйте разделительную линию, используя соответствующий сегмент предыдущего прямоугольника отсечения, и обрежьте контур круга
IЯ создал класс, который реализует это решение, и небольшую программу для его тестирования.
Для пункта 2 я обнаружил, что использование Tranform
для выполнения поворота очень проблематично, поскольку оно преобразуетвесь дисплей, и я не смог найти способ ограничить преобразования в ограниченной области. Вместо этого я использовал Eclipse GEF , чтобы создать Прямоугольник , повернуть его и преобразовать в PathData
, который можно использовать для отсечения.
Для пункта 4,Я повторно использовал PathData
из точки 2, чтобы нарисовать нижний сегмент прямоугольника отсечения, который эквивалентен разделительной линии между двумя цветами. Чтобы не рисовать часть сегмента за пределами круга, я обрезал его контуром круга.
Это результат:
Это тестовая программа, используйте клавиши со стрелками для перемещения / поворота разделительной линии:
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import static org.eclipse.swt.events.KeyListener.keyPressedAdapter;
public class SeparatedCircleTest {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setSize(600, 600);
shell.setLayout(new FillLayout());
// double buffering to avoid flickering while redrawing the circle
final SeparatedCircle separatedCircle = new SeparatedCircle(shell, SWT.DOUBLE_BUFFERED, 300, 300, 200, 0, 0.f);
// to move/rotate the separation
separatedCircle.addKeyListener(keyPressedAdapter(e -> {
if(e.keyCode == SWT.ARROW_UP) {
separatedCircle.setySeparationDelta(separatedCircle.getySeparationDelta() - 5);
} else if(e.keyCode == SWT.ARROW_DOWN) {
separatedCircle.setySeparationDelta(separatedCircle.getySeparationDelta() + 5);
} else if(e.keyCode == SWT.ARROW_LEFT) {
separatedCircle.setSeparationAngle(separatedCircle.getSeparationAngle() + 5.f);
} else if(e.keyCode == SWT.ARROW_RIGHT) {
separatedCircle.setSeparationAngle(separatedCircle.getSeparationAngle() - 5.f);
}
if(separatedCircle.needRedraw()) {
separatedCircle.redraw();
}
}));
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}
display.dispose();
}
}
И это класс реализации:
import org.eclipse.gef.geometry.convert.swt.Geometry2SWT;
import org.eclipse.gef.geometry.euclidean.Angle;
import org.eclipse.gef.geometry.planar.Polygon;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.graphics.PathData;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
public class SeparatedCircle extends Canvas {
private int xCoord;
private int yCoord;
private int diameter;
private int ySeparationDelta;
private float separationAngle;
private boolean needRedraw;
private Rectangle circleBounds;
private PathData clippingData;
public SeparatedCircle(Composite parent, int style, int x, int y, int diameter, int ySeparationDelta, float separationAngle) {
super(parent, style);
xCoord = x;
yCoord = y;
this.diameter = diameter;
this.ySeparationDelta = ySeparationDelta;
this.separationAngle = separationAngle;
needRedraw = true;
addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
paint(e);
}
});
}
private void paint(PaintEvent event) {
// if some variable changed, we recalculate the bounds
if(needRedraw) {
calculateBounds();
needRedraw = false;
}
GC gc = event.gc;
// enable high quality drawing
gc.setAntialias(SWT.ON);
gc.setInterpolation(SWT.HIGH);
// draw the first circle, no clipping
gc.setBackground( event.display.getSystemColor( SWT.COLOR_BLUE ) );
gc.fillOval(circleBounds.x, circleBounds.y, circleBounds.width, circleBounds.height);
// clipping for the second circle
Path clipping = new Path(gc.getDevice(), clippingData);
gc.setClipping(clipping);
clipping.dispose();
// draw the second circle
gc.setBackground( event.display.getSystemColor( SWT.COLOR_RED ) );
gc.fillOval(circleBounds.x, circleBounds.y, circleBounds.width, circleBounds.height);
// remove the clipping
gc.setClipping((Rectangle) null);
// draw the circle outline
gc.setForeground(event.display.getSystemColor( SWT.COLOR_BLACK ));
gc.setLineWidth(4);
gc.drawOval(circleBounds.x, circleBounds.y, circleBounds.width, circleBounds.height);
// clipping for the separation line
Path circlePath = new Path(gc.getDevice());
circlePath.addArc(circleBounds.x, circleBounds.y, circleBounds.width, circleBounds.height, 0.f, 360.f);
gc.setClipping(circlePath);
circlePath.dispose();
// draw the separation line
// we want to draw the bottom segment of the clipping rectangle (the third segment), so we use its third and fourth point
gc.drawLine(
(int) clippingData.points[4], // third point x
(int) clippingData.points[5], // third point y
(int) clippingData.points[6], // fourth point x
(int) clippingData.points[7] // fourth point y
);
}
private void calculateBounds() {
circleBounds = calculateCircleBounds();
clippingData = calculateClipping();
}
private Rectangle calculateCircleBounds() {
return new Rectangle(calculateLeft(), calculateTop(), diameter, diameter);
}
private int calculateLeft() {
return xCoord - ( diameter / 2 );
}
private int calculateTop() {
return yCoord - ( diameter / 2 );
}
private PathData calculateClipping() {
// create the clipping rectangle
org.eclipse.gef.geometry.planar.Rectangle rectangle = new org.eclipse.gef.geometry.planar.Rectangle(
circleBounds.x, circleBounds.y, circleBounds.width, calculateClippingRectangleHeight());
// rotate it, using the center of our circle as its point of rotation
Polygon rotatedRectangle = rectangle.getRotatedCCW(Angle.fromDeg(separationAngle), xCoord, yCoord);
// convert the rotated rectangle to PathData
return Geometry2SWT.toSWTPathData(rotatedRectangle.toPath());
}
private int calculateClippingRectangleHeight() {
return circleBounds.height / 2 + ySeparationDelta;
}
public int getxCoord() {
return xCoord;
}
public void setxCoord(int xCoord) {
this.xCoord = xCoord;
needRedraw = true;
}
public int getyCoord() {
return yCoord;
}
public void setyCoord(int yCoord) {
this.yCoord = yCoord;
needRedraw = true;
}
public int getDiameter() {
return diameter;
}
public void setDiameter(int diameter) {
this.diameter = diameter;
needRedraw = true;
}
public int getySeparationDelta() {
return ySeparationDelta;
}
public void setySeparationDelta(int ySeparationDelta) {
this.ySeparationDelta = ySeparationDelta;
needRedraw = true;
}
public float getSeparationAngle() {
return separationAngle;
}
public void setSeparationAngle(float separationAngle) {
this.separationAngle = separationAngle;
needRedraw = true;
}
public boolean needRedraw() {
return needRedraw;
}
}
Для использованияGEF:
Чтобы использовать GEF, вам просто нужно включить следующие файлы:
org.eclipse.gef.geometry.convert.swt.Geometry2SWT<version>.jar
org.eclipse.gef.geometry<version>.jar
Вы можете получить их в папке "plugin" из сборок здесь: https://www.eclipse.org/gef/downloads/index.php.
Выберите последнюю версию и нажмите на ссылку сайта обновления, чтобы загрузить полный zip.