mirror of
https://github.com/processing/processing4.git
synced 2026-06-16 04:26:26 +02:00
Merge branch 'master' of github.com:processing/processing
This commit is contained in:
@@ -653,6 +653,11 @@ public class PShape implements PConstants {
|
||||
}
|
||||
|
||||
fill = false;
|
||||
fillColor = 0x0;
|
||||
|
||||
if (!setAmbient) {
|
||||
ambientColor = fillColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -665,6 +670,10 @@ public class PShape implements PConstants {
|
||||
fill = true;
|
||||
colorCalc(rgb);
|
||||
fillColor = calcColor;
|
||||
|
||||
if (!setAmbient) {
|
||||
ambientColor = fillColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -677,6 +686,10 @@ public class PShape implements PConstants {
|
||||
fill = true;
|
||||
colorCalc(rgb, alpha);
|
||||
fillColor = calcColor;
|
||||
|
||||
if (!setAmbient) {
|
||||
ambientColor = fillColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -689,6 +702,10 @@ public class PShape implements PConstants {
|
||||
fill = true;
|
||||
colorCalc(gray);
|
||||
fillColor = calcColor;
|
||||
|
||||
if (!setAmbient) {
|
||||
ambientColor = fillColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -701,6 +718,15 @@ public class PShape implements PConstants {
|
||||
fill = true;
|
||||
colorCalc(gray, alpha);
|
||||
fillColor = calcColor;
|
||||
|
||||
if (!setAmbient) {
|
||||
ambient(fillColor);
|
||||
setAmbient = false;
|
||||
}
|
||||
|
||||
if (!setAmbient) {
|
||||
ambientColor = fillColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -713,6 +739,10 @@ public class PShape implements PConstants {
|
||||
fill = true;
|
||||
colorCalc(x, y, z);
|
||||
fillColor = calcColor;
|
||||
|
||||
if (!setAmbient) {
|
||||
ambientColor = fillColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -725,6 +755,10 @@ public class PShape implements PConstants {
|
||||
fill = true;
|
||||
colorCalc(x, y, z, a);
|
||||
fillColor = calcColor;
|
||||
|
||||
if (!setAmbient) {
|
||||
ambientColor = fillColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -61,8 +61,6 @@ import processing.event.MouseEvent;
|
||||
|
||||
import com.jogamp.newt.awt.NewtCanvasAWT;
|
||||
import com.jogamp.newt.event.InputEvent;
|
||||
import com.jogamp.newt.event.WindowEvent;
|
||||
import com.jogamp.newt.event.WindowUpdateEvent;
|
||||
import com.jogamp.newt.opengl.GLWindow;
|
||||
import com.jogamp.opengl.FBObject;
|
||||
import com.jogamp.opengl.util.AnimatorBase;
|
||||
@@ -630,6 +628,7 @@ public class PGL {
|
||||
canvasAWT = new GLCanvas(caps);
|
||||
canvasAWT.setBounds(0, 0, pg.width, pg.height);
|
||||
canvasAWT.setBackground(new Color(pg.backgroundColor, true));
|
||||
canvasAWT.setFocusable(true);
|
||||
|
||||
pg.parent.setLayout(new BorderLayout());
|
||||
pg.parent.add(canvasAWT, BorderLayout.CENTER);
|
||||
@@ -651,9 +650,7 @@ public class PGL {
|
||||
canvasNEWT = new NewtCanvasAWT(window);
|
||||
canvasNEWT.setBounds(0, 0, pg.width, pg.height);
|
||||
canvasNEWT.setBackground(new Color(pg.backgroundColor, true));
|
||||
|
||||
pg.parent.setLayout(new BorderLayout());
|
||||
pg.parent.add(canvasNEWT, BorderLayout.CENTER);
|
||||
canvasNEWT.setFocusable(true);
|
||||
|
||||
NEWTMouseListener mouseListener = new NEWTMouseListener();
|
||||
window.addMouseListener(mouseListener);
|
||||
@@ -663,6 +660,9 @@ public class PGL {
|
||||
window.addWindowListener(winListener);
|
||||
canvasNEWT.addFocusListener(pg.parent); // So focus detection work.
|
||||
|
||||
pg.parent.setLayout(new BorderLayout());
|
||||
pg.parent.add(canvasNEWT, BorderLayout.CENTER);
|
||||
|
||||
capabilities = window.getChosenGLCapabilities();
|
||||
canvas = canvasNEWT;
|
||||
canvasAWT = null;
|
||||
@@ -3237,6 +3237,7 @@ public class PGL {
|
||||
|
||||
protected void nativeMouseEvent(com.jogamp.newt.event.MouseEvent nativeEvent,
|
||||
int peAction) {
|
||||
// if (!hasFocus) return;
|
||||
int modifiers = nativeEvent.getModifiers();
|
||||
int peModifiers = modifiers &
|
||||
(InputEvent.SHIFT_MASK |
|
||||
@@ -3296,31 +3297,44 @@ public class PGL {
|
||||
pg.parent.postEvent(ke);
|
||||
}
|
||||
|
||||
boolean hasFocus = true;
|
||||
class NEWTWindowListener implements com.jogamp.newt.event.WindowListener {
|
||||
@Override
|
||||
public void windowGainedFocus(WindowEvent arg0) {
|
||||
public void windowGainedFocus(com.jogamp.newt.event.WindowEvent arg0) {
|
||||
PApplet.println("window gained focus");
|
||||
pg.parent.focusGained(null);
|
||||
hasFocus = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowLostFocus(WindowEvent arg0) {
|
||||
public void windowLostFocus(com.jogamp.newt.event.WindowEvent arg0) {
|
||||
PApplet.println("window lost focus");
|
||||
pg.parent.focusLost(null);
|
||||
hasFocus = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowDestroyNotify(WindowEvent arg0) { }
|
||||
public void windowDestroyNotify(com.jogamp.newt.event.WindowEvent arg0) {
|
||||
PApplet.println("destroy");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowDestroyed(WindowEvent arg0) { }
|
||||
public void windowDestroyed(com.jogamp.newt.event.WindowEvent arg0) {
|
||||
PApplet.println("destroyed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowMoved(WindowEvent arg0) { }
|
||||
public void windowMoved(com.jogamp.newt.event.WindowEvent arg0) {
|
||||
PApplet.println("moved");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowRepaint(WindowUpdateEvent arg0) { }
|
||||
public void windowRepaint(com.jogamp.newt.event.WindowUpdateEvent arg0) {
|
||||
PApplet.println("window repaint");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowResized(WindowEvent arg0) { }
|
||||
public void windowResized(com.jogamp.newt.event.WindowEvent arg0) { }
|
||||
}
|
||||
|
||||
// NEWT mouse listener
|
||||
|
||||
@@ -389,7 +389,7 @@ public class PGraphics2D extends PGraphicsOpenGL {
|
||||
shape = new PShapeOpenGL(parent, PShape.PRIMITIVE);
|
||||
shape.setKind(ELLIPSE);
|
||||
} else if (kind == ARC) {
|
||||
if (len != 6) {
|
||||
if (len != 6 && len != 7) {
|
||||
showWarning("Wrong number of parameters");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ public class PGraphics3D extends PGraphicsOpenGL {
|
||||
shape = new PShapeOpenGL(parent, PShape.PRIMITIVE);
|
||||
shape.setKind(ELLIPSE);
|
||||
} else if (kind == ARC) {
|
||||
if (len != 6) {
|
||||
if (len != 6 && len != 7) {
|
||||
showWarning("Wrong number of parameters");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -4971,8 +4971,8 @@ public class PGraphicsOpenGL extends PGraphics {
|
||||
// color buffer into it.
|
||||
@Override
|
||||
public void loadPixels() {
|
||||
if (sized) {
|
||||
// Something wrong going on with threading, sized can never be true if the
|
||||
if (primarySurface && sized) {
|
||||
// Something wrong going on with threading, sized can never be true if
|
||||
// all the steps in a resize happen inside the Animation thread.
|
||||
return;
|
||||
}
|
||||
@@ -5397,6 +5397,8 @@ public class PGraphicsOpenGL extends PGraphics {
|
||||
textureMode = NORMAL;
|
||||
boolean prevStroke = stroke;
|
||||
stroke = false;
|
||||
// int prevBlendMode = blendMode;
|
||||
// blendMode(REPLACE);
|
||||
PolyTexShader prevTexShader = polyTexShader;
|
||||
polyTexShader = (PolyTexShader) shader;
|
||||
beginShape(QUADS);
|
||||
@@ -5414,6 +5416,7 @@ public class PGraphicsOpenGL extends PGraphics {
|
||||
stroke = prevStroke;
|
||||
lights = prevLights;
|
||||
textureMode = prevTextureMode;
|
||||
// blendMode(prevBlendMode);
|
||||
|
||||
if (!hints[DISABLE_DEPTH_TEST]) {
|
||||
pgl.enable(PGL.DEPTH_TEST);
|
||||
|
||||
@@ -3416,7 +3416,7 @@ public class PShapeOpenGL extends PShape {
|
||||
d = params[3];
|
||||
}
|
||||
|
||||
ellipseMode = CORNER;
|
||||
// ellipseMode = CORNER;
|
||||
inGeo.setMaterial(fillColor, strokeColor, strokeWeight,
|
||||
ambientColor, specularColor, emissiveColor, shininess);
|
||||
inGeo.setNormal(normalX, normalY, normalZ);
|
||||
@@ -3428,20 +3428,24 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void tessellateArc() {
|
||||
float a = 0, b = 0, c = 0, d = 0;
|
||||
float start = 0, stop = 0;
|
||||
if (params.length == 6) {
|
||||
int mode = 0;
|
||||
if (params.length == 6 || params.length == 7) {
|
||||
a = params[0];
|
||||
b = params[1];
|
||||
c = params[2];
|
||||
d = params[3];
|
||||
start = params[4];
|
||||
stop = params[5];
|
||||
if (params.length == 7) {
|
||||
mode = (int)(params[6]);
|
||||
}
|
||||
}
|
||||
|
||||
ellipseMode = CORNER;
|
||||
// ellipseMode = CORNER;
|
||||
inGeo.setMaterial(fillColor, strokeColor, strokeWeight,
|
||||
ambientColor, specularColor, emissiveColor, shininess);
|
||||
inGeo.setNormal(normalX, normalY, normalZ);
|
||||
inGeo.addArc(a, b, c, d, start, stop, fill, stroke, ellipseMode);
|
||||
inGeo.addArc(a, b, c, d, start, stop, fill, stroke, mode);
|
||||
tessellator.tessellateTriangleFan();
|
||||
}
|
||||
|
||||
@@ -4432,8 +4436,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyPolyVertices(int offset, int size) {
|
||||
tessGeo.updatePolyVerticesBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyVertex);
|
||||
tessGeo.polyVerticesBuffer.position(4 * offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT,
|
||||
4 * size * PGL.SIZEOF_FLOAT, tessGeo.polyVerticesBuffer);
|
||||
tessGeo.polyVerticesBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -4441,8 +4447,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyPolyColors(int offset, int size) {
|
||||
tessGeo.updatePolyColorsBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyColor);
|
||||
tessGeo.polyColorsBuffer.position(offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT,
|
||||
size * PGL.SIZEOF_INT, tessGeo.polyColorsBuffer);
|
||||
tessGeo.polyColorsBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -4450,8 +4458,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyPolyNormals(int offset, int size) {
|
||||
tessGeo.updatePolyNormalsBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyNormal);
|
||||
tessGeo.polyNormalsBuffer.position(3 * offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, 3 * offset * PGL.SIZEOF_FLOAT,
|
||||
3 * size * PGL.SIZEOF_FLOAT, tessGeo.polyNormalsBuffer);
|
||||
tessGeo.polyNormalsBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -4459,8 +4469,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyPolyTexcoords(int offset, int size) {
|
||||
tessGeo.updatePolyTexcoordsBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyTexcoord);
|
||||
tessGeo.polyTexcoordsBuffer.position(2 * offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, 2 * offset * PGL.SIZEOF_FLOAT,
|
||||
2 * size * PGL.SIZEOF_FLOAT, tessGeo.polyTexcoordsBuffer);
|
||||
tessGeo.polyTexcoordsBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -4468,8 +4480,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyPolyAmbient(int offset, int size) {
|
||||
tessGeo.updatePolyAmbientBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyAmbient);
|
||||
tessGeo.polyAmbientBuffer.position(offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT,
|
||||
size * PGL.SIZEOF_INT, tessGeo.polyAmbientBuffer);
|
||||
tessGeo.polyAmbientBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -4477,8 +4491,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyPolySpecular(int offset, int size) {
|
||||
tessGeo.updatePolySpecularBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolySpecular);
|
||||
tessGeo.polySpecularBuffer.position(offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT,
|
||||
size * PGL.SIZEOF_INT, tessGeo.polySpecularBuffer);
|
||||
tessGeo.polySpecularBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -4486,8 +4502,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyPolyEmissive(int offset, int size) {
|
||||
tessGeo.updatePolyEmissiveBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyEmissive);
|
||||
tessGeo.polyEmissiveBuffer.position(offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT,
|
||||
size * PGL.SIZEOF_INT, tessGeo.polyEmissiveBuffer);
|
||||
tessGeo.polyEmissiveBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -4495,8 +4513,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyPolyShininess(int offset, int size) {
|
||||
tessGeo.updatePolyShininessBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyShininess);
|
||||
tessGeo.polyShininessBuffer.position(offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_FLOAT,
|
||||
size * PGL.SIZEOF_FLOAT, tessGeo.polyShininessBuffer);
|
||||
tessGeo.polyShininessBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -4504,8 +4524,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyLineVertices(int offset, int size) {
|
||||
tessGeo.updateLineVerticesBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineVertex);
|
||||
tessGeo.lineVerticesBuffer.position(4 * offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT,
|
||||
4 * size * PGL.SIZEOF_FLOAT, tessGeo.lineVerticesBuffer);
|
||||
tessGeo.lineVerticesBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -4513,8 +4535,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyLineColors(int offset, int size) {
|
||||
tessGeo.updateLineColorsBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineColor);
|
||||
tessGeo.lineColorsBuffer.position(offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT,
|
||||
size * PGL.SIZEOF_INT, tessGeo.lineColorsBuffer);
|
||||
tessGeo.lineColorsBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -4522,8 +4546,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyLineAttributes(int offset, int size) {
|
||||
tessGeo.updateLineAttribsBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineAttrib);
|
||||
tessGeo.lineAttribsBuffer.position(4 * offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT,
|
||||
4 * size * PGL.SIZEOF_FLOAT, tessGeo.lineAttribsBuffer);
|
||||
tessGeo.lineAttribsBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -4531,8 +4557,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyPointVertices(int offset, int size) {
|
||||
tessGeo.updatePointVerticesBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointVertex);
|
||||
tessGeo.pointVerticesBuffer.position(4 * offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT,
|
||||
4 * size * PGL.SIZEOF_FLOAT, tessGeo.pointVerticesBuffer);
|
||||
tessGeo.pointVerticesBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -4540,8 +4568,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyPointColors(int offset, int size) {
|
||||
tessGeo.updatePointColorsBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointColor);
|
||||
tessGeo.pointColorsBuffer.position(offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT,
|
||||
size * PGL.SIZEOF_INT,tessGeo.pointColorsBuffer);
|
||||
tessGeo.pointColorsBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
@@ -4549,8 +4579,10 @@ public class PShapeOpenGL extends PShape {
|
||||
protected void copyPointAttributes(int offset, int size) {
|
||||
tessGeo.updatePointAttribsBuffer(offset, size);
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointAttrib);
|
||||
tessGeo.pointAttribsBuffer.position(2 * offset);
|
||||
pgl.bufferSubData(PGL.ARRAY_BUFFER, 2 * offset * PGL.SIZEOF_FLOAT,
|
||||
2 * size * PGL.SIZEOF_FLOAT, tessGeo.pointAttribsBuffer);
|
||||
tessGeo.pointAttribsBuffer.rewind();
|
||||
pgl.bindBuffer(PGL.ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -328,7 +328,6 @@ public class Texture implements PConstants {
|
||||
|
||||
public void set(int[] pixels, int x, int y, int w, int h, int format) {
|
||||
if (pixels == null) {
|
||||
pixels = null;
|
||||
PGraphics.showWarning("The pixels array is null.");
|
||||
return;
|
||||
}
|
||||
@@ -1283,6 +1282,9 @@ public class Texture implements PConstants {
|
||||
// FBO copy:
|
||||
pg.pushFramebuffer();
|
||||
pg.setFramebuffer(tempFbo);
|
||||
// Clear the color buffer to make sure that the alpha of the
|
||||
pgl.clearColor(0, 0, 0, 0);
|
||||
pgl.clear(PGL.COLOR_BUFFER_BIT);
|
||||
if (scale) {
|
||||
// Rendering tex into "this", and scaling the source rectangle
|
||||
// to cover the entire destination region.
|
||||
|
||||
@@ -45,17 +45,15 @@ void setup() {
|
||||
// Make a primitive (Rectangle) PShape
|
||||
PShape rectangle = createShape(RECT,-10,-10,20,20);
|
||||
rectangle.setFill(false);
|
||||
rectangle.setStroke(255);
|
||||
rectangle.setStroke(color(255));
|
||||
|
||||
// Add them all to the group
|
||||
group.addChild(star);
|
||||
group.addChild(path);
|
||||
group.addChild(rectangle); // Rectangle is missing???
|
||||
|
||||
group.addChild(rectangle);
|
||||
}
|
||||
|
||||
void draw() {
|
||||
|
||||
// We can access them individually via the group PShape
|
||||
PShape rectangle = group.getChild(2);
|
||||
// Shapes can be rotated
|
||||
|
||||
Reference in New Issue
Block a user