I followed this tutorial, in order to display some graphical element on a canvas:
Here is where I come:
Scroll listener
vBar.addListener(SWT.Selection, new Listener() {
#Override
public void handleEvent(Event event) {
System.out.println("Scroll event");
int vSelection = vBar.getSelection();
int destY = -vSelection - origin.y;
canvas.scroll(0, destY, 0, 0, canvas.getSize().x, canvas.getSize().y, false);
origin.y = -vSelection;
}
});
Resize listener
canvas.addListener(SWT.Resize, new Listener() {
#Override
public void handleEvent(Event e) {
System.out.println("Resize event");
Rectangle client = canvas.getClientArea();
vBar.setThumb(Math.min(theoreticalScreenHeight, client.height));
vBar.setPageIncrement(Math.min(theoreticalScreenHeight, client.height));
vBar.setIncrement(20);
int vPage = canvas.getSize().y - client.height;
int vSelection = vBar.getSelection();
if (vSelection >= vPage) {
if (vPage <= 0)
vSelection = 0;
origin.y = -vSelection;
}
}
});
Paint listener
canvas.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
System.out.println("Paint event");
// Costly drawing here
...
// rectangle
for (int productIndex = 0; productIndex < products.size(); productIndex++) {
Product p = products.get(productIndex);
...
e.gc.drawRoundRectangle(rectEdge.x, rectEdge.y, (int) productWidth, productHeight, 30, 30);
Point prevSubRectEdge = rectEdge; // Coordinate of each result rectangle
for (int resultIndex = 0; resultIndex < productResultsAmount; resultIndex++) {
Result result1 = p.getResult(resultIndex);
...
e.gc.drawLine(sepStart.x, sepStart.y, sepEnd.x, sepEnd.y);
...
}
rectEdge.y += 300;
}
drawLinks(e);
}
});
Since I display a lot of graphical element, it can take some time to be displayed. That is why I would like to display them once at the beginning, and because there is a lot, I would like to scroll vertically on the canvas, so I can see all of my elements.
My problem here is that the scroll event trigger the repaint event, which is redrawing the elements. But since it's a costly operation, it is not the thing that I want.
I would like to avoid this repainting behavior, and just paint the elements once in the beginning, and then simply scroll to see them.
I have added the code, because I thought somebody would like to see it, but I don't think it is really helpfull, it was just to show the difference between the tutorial and my code. I think it is more a matter of swt comprehension, about listener and repainting life cycle ?
I also have to say that I never call canvas.redraw().
Here is a screenshot of my canvas:
Related
im trying to create a simple space invaders game. This is my first ever game, and so im having some issues. The invaders is a rectangle object with an image of the oldschool invader. In each frame im drawing those boxes 15 pixels to the right, and then using root.getChildren().remove(), to remove the sprites in each frame. But it causes some weird behaviour. It kind of looks like it isnt being removed quickly enough, and so just when i launch the app it kind of explodes, and after that there seems to be a little bit of latency causing two images to be displayed constantly.
I guess it would probably help for you to see it yourself and so i will post all of my files here.
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Space invaders");
primaryStage.setResizable(false);
Group root = new Group();
Scene game = new Scene(root, Color.BLACK);
primaryStage.setFullScreen(true);
Images images = new Images();
// Make canvas and add it to primaryStage
Canvas canvas = new Canvas(1280,720);
root.getChildren().add(canvas);
GraphicsContext gc = canvas.getGraphicsContext2D();
new AnimationTimer() {
int move = 0;
private long lastUpdate = 0;
ArrayList<Sprite> enemies = new ArrayList<>();
#Override
public void handle(long now) {
if (now - lastUpdate >= 150_000_000) {
int spacing = 0;
try {
for (int i = 0; i < enemies.size(); i++) {
root.getChildren().remove(enemies.get(i));
enemies.remove(i);
}
} catch (IndexOutOfBoundsException ioobe) {
System.out.println(ioobe.toString());
}
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 6; j++) {
Sprite enemy = new Sprite(60 * i + spacing + move, 43 * j, 60, 43, "enemy", true, images.getEnemy1());
enemies.add(enemy);
root.getChildren().add(enemy);
}
spacing += 15;
}
move += 5;
lastUpdate = now;
}
}
}.start();
primaryStage.setScene(game);
primaryStage.show();
}
}
If you want to run the program yourself, her is the Sprite class:
public class Sprite extends Rectangle {
private String type;
private Boolean alive;
public Sprite(int x, int y, int w, int h, String type, boolean alive, Image image) {
this.setTranslateX(x);
this.setTranslateY(y);
this.setWidth(w);
this.setHeight(h);
this.type = type;
this.alive = alive;
this.setFill(new ImagePattern(image));
}
public String getType() {
return type;
}
public Boolean getAlive() {
return alive;
}
}
This is just not the way the scene graph is supposed to be used. Why do you constantly remove and re-add your sprites? Why don't you just update their positions via the setTranslateX/Y which you are already using anyway?
try {
for (int i = 0; i < enemies.size(); i++) {
root.getChildren().remove(enemies.get(i));
enemies.remove(i);
}
} catch (IndexOutOfBoundsException ioobe) {
System.out.println(ioobe.toString());
}
A correct implementation wouldn't require you to catch a IndexOutOfBoundsException. I don't know why this would throw such an exception though, since the check i < enemies.size() should ensure the index is valid.
Note however that this skips every other list element:
First you remove the first element of enemies from both lists (the one at index 0). This results in all elements remaining in the enemies list to be shifted to the left, i.e. the one that was at index 1 before the removal is now at index 0. Since you increment the index after each loop iteration, all elements at odd indices are skipped.
There's a simpler way of implementing this btw:
root.getChildren().removeAll(enemies);
enemies.clear();
Note
Recreating your sprites in every frame should be avoided. The memory requirement of Nodes doesn't make them objects you should throw away, if this can be avoided. Otherwise the garbage collector will be kept busy cleaning up the objects you "throw" away at best and an OutOfMemoryException happens at worst...
Reuse the sprites and modify the properties instead.
Also I recommend going with ImageView instead of Rectangles filled with ImagePattern. You can adjust the size using the fitWidth and fitHeight properties.
I'm trying to make a flip effect with java swing, but my flip method doesn't look like a real flip.In my method I change the width with the x var and the xspeed, making the image smaller and then check if the x is >= as half of my image. hope somebody could help me improve it thanks in advance.
Animation Class
public class Animation extends JPanel implements MouseListener {
private static final long serialVersionUID = 3264508834913061718L;
public Timer timer;
public int x = 0;
public int xspeed = 2;
public boolean turning = true;
public String pic = "/images/image.jpg";
public URL url = this.getClass().getResource(pic);
public ImageIcon im = new ImageIcon(url);
public String rev = "/images/image2.jpg";
public URL url2 = this.getClass().getResource(rev);
public ImageIcon reverse = new ImageIcon(url2);
public Animation(){
this.setPreferredSize(new Dimension(128,128));
this.setBackground(Color.BLACK);
this.setFocusable(true);
this.addMouseListener(this);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(im.getImage(), 0 , 0, im.getIconWidth() - x, im.getIconHeight(), null);
}
public void flipAnimation(){
ActionListener actionListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
//
if (turning) {
x = x + xspeed;
}
repaint();
// x in the middle paint new image & set turning to false to stop animation
if(x >= im.getIconWidth()/2){
turning = false;
x = 0;
im = reverse;
}
}
};
if (turning) {
if (timer != null)timer.stop();
timer = new Timer(30, actionListener);
timer.start();
}
}
#Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
e.getSource();
flipAnimation();
}
First, I’m guessing you want your image to “fold in” from both sides, so you’ll want to increase the image’s left edge along with its right edge:
g.drawImage(im.getImage(), x / 2 , 0, im.getIconWidth() - x, im.getIconHeight(), this);
Notice the first int argument has been changed from 0 to x / 2. Also, it is good practice to pass this as the ImageObserver argument when drawing images in a paintComponent method, since the component itself is the object which is interested in repainting itself when the image has finished loading in the background.
Second, change this:
if (turning) {
x = x + xspeed;
}
to this:
x = x + xspeed;
You don’t need a flag to control the animation. (The correct way to stop the animation is to call timer.stop(), which I’ll get to in a moment.)
Third, change this:
if(x >= im.getIconWidth()/2){
turning = false;
x = 0;
to this:
if(x >= im.getIconWidth()){
xspeed = -xspeed;
As I said, the turning flag is not needed.
The reason for comparing x to the image’s width, instead of half of the width, is that we want to change the image only when the first image has completely “folded in.”
The negation of xspeed reverses the animation.
Finally, at the end of your actionPerformed method, you’ll want to add this:
if (x <= 0) {
timer.stop();
}
This halts the animation when the reverse image reaches its full size, which is why the turning flag is not needed.
This is my second post on Mandelbrot fractal conversion from Java to C#.
As per my assignment, I need to Draw a mandelbrot fractal on a form, and once it is drawn, allow the user to Zoom in using the mouse, while also drawing a rectangle from the initial click point to the point where the click is released. This is the part of code which i believe is responsible for the rectangle.
private static void Swap<T>(ref T t1, ref T t2)
{
T temp = t1;
t1 = t2;
t2 = t1;
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g1 = e.Graphics;
g1.DrawImage(bitmap, 0, 0, x1, y1);
if (action)
{
//g.setColor(Color.White);
if (xe < xs)
{
Swap(ref xs, ref xe);
}
if (ye < ys)
{
Swap(ref ys, ref ye);
}
g1.DrawRectangle(Pens.White, xs, ys, (xe - xs), (ye - ys));
//g1.Dispose();
}
}
//load method here
private void Form1_Load(object sender, EventArgs e)
//while loading
{
init();
start();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (action)
{
xe = e.X;
ye = e.Y;
}
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
action = true;
// e.consume();
if (action)
{
xs = xe = e.X;
ys = ye = e.Y;
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
using (Graphics g = this.CreateGraphics())
{
Pen pen = new Pen(Color.White);
g.DrawRectangle(pen, xs, ys, Math.Abs(xs - xe), Math.Abs(ys - ye));
}
int z, w;
if (xs > xe)
{
z = xs;
xs = xe;
xe = z;
}
if (ys > ye)
{
z = ys;
ys = ye;
ye = z;
}
w = (xe - xs);
z = (ye - ys);
if ((w < 2) && (z < 2)) initvalues();
else
{
if (((float)w > (float)z * xy)) ye = (int)((float)ys + (float)w / xy);
else xe = (int)((float)xs + (float)z * xy);
xende = xstart + xzoom * (double)xe;
yende = ystart + yzoom * (double)ye;
xstart += xzoom * (double)xs;
ystart += yzoom * (double)ys;
}
xzoom = (xende - xstart) / (double)x1;
yzoom = (yende - ystart) / (double)y1;
mandelbrot();
this.Invalidate();
}
What the code does is, draw a rectangle AFTER the dragging is done, and then zoom in with the drawn rectangle still being displayed. What I needed is the rectangle to draw as the mouse is being dragged.
I referred to this question, and solution mentioned there did not help.
Java to C# conversion. How do i draw a rectangle on my bitmap?
Any help would be appreciated.
Drawing the Rectangle
First of all, it appears that the Graphics.DrawRectangle method is unable to draw a rectangle with negative widths or heights. You will therefore have to write a method that will take two points and produce a rectangle meeting the requirements (positive width and height).
private Rectangle CreateRectangle(Point pt1, Point pt2)
{
// we use this method to create the rectangle with positive width and height
int x1 = Math.Min(pt1.X, pt2.X);
int y1 = Math.Min(pt1.Y, pt2.Y);
return new Rectangle(x1, y1, Math.Abs(pt1.X - pt2.X), Math.Abs(pt1.Y - pt2.Y));
}
Second, in your event handler for the MouseDown event, record the position at which the mouse was held down.
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
this.startPoint = e.Location;// record the start position
}
Next, modify your mouse move method to update the variable that holds current location of the mouse. Additionally, make it invalidate the form so that the image is redrawn (along with the rectangle).
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
// record the current position as the end point if the left button is down
this.endPoint = e.Location;
// force a redraw
this.Invalidate();
}
}
In the form's Paint event handler, make your code call the CreateRectangle method with the start and end points of the rectangle in order to draw the rectangle on the form.
private void Form1_Paint(object sender, PaintEventArgs e)
{
// draw the cached Mandelbrot image
e.Graphics.DrawImage(mandelbrotCache, new Point(0, 0));
// draw the current rectangle
e.Graphics.DrawRectangle(rectPen, CreateRectangle(startPoint, endPoint));
}
Finally, in order to remove the rectangle when the mouse button is no longer pressed, set startPoint and endPoint to a value that gets drawn outside the image. This should be done in the MouseUp event handler.
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
// setting the point to -1,-1 makes them get drawn off the screen
startPoint = new Point(-1, -1);
endPoint = new Point(-1, -1);
// force an update so that the rectangle disappears
this.Invalidate();
}
}
Addressing the Flickering Issue
In order to stop the form from flickering while you're drawing to it, you will need to enable double buffering on the form. This is done by setting the DoubleBuffered property of the form to true. You can do this anywhere, but I prefer to do it right after the form is created, as below:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// this reduces the flickering
this.DoubleBuffered = true;
}
}
Complete Code:
Here is the complete code for all the steps I detailed above. You can plug in your methods in order to have a working solution.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Point startPoint;
private Point endPoint;
private Image mandelbrotCache;
private Pen rectPen;
public Form1()
{
InitializeComponent();
// this reduces the flickering
this.DoubleBuffered = true;
// initialize a dummy image. Cache a copy of your Mandelbrot fractal here
mandelbrotCache = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
using (var g = Graphics.FromImage(mandelbrotCache))
{
var imgRect = new Rectangle(0, 0,
mandelbrotCache.Width,
mandelbrotCache.Height);
g.FillRectangle(new HatchBrush(HatchStyle.Cross, Color.DarkBlue,
Color.LightBlue), imgRect);
}
// this is the pen to draw the rectangle with
rectPen = new Pen(Color.Red, 3);
}
private Rectangle CreateRectangle(Point pt1, Point pt2)
{
// we use this method to create a rectangle with positive width and height
int x1 = Math.Min(pt1.X, pt2.X);
int y1 = Math.Min(pt1.Y, pt2.Y);
return new Rectangle(x1, y1, Math.Abs(pt1.X - pt2.X), Math.Abs(pt1.Y - pt2.Y));
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
this.startPoint = e.Location;// record the start position
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
// record the current position as the end point if the left button is down
this.endPoint = e.Location;
// force a redraw
this.Invalidate();
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
// setting the point to -1,-1 makes them get drawn off the screen
startPoint = new Point(-1, -1);
endPoint = new Point(-1, -1);
// force an update so that the rectangle disappears
this.Invalidate();
}
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
// draw the cached Mandelbrot image
e.Graphics.DrawImage(mandelbrotCache, new Point(0, 0));
// draw the current rectangle
e.Graphics.DrawRectangle(rectPen, CreateRectangle(startPoint, endPoint));
}
}
}
Here is a screenshot of a rectangle being drawn.
Note: The left mouse button is still held down. The rectangle disappears immediately the button is released.
I would like to change the color of the underline of an active CTabItem. The line is black and I want another color, see picture below.
I agree with #greg-449 that normally you should not mess with CTabFolderRenderer but in some cases you have to do that. Luckily, you don't have to write again the entire renderer. This is the code in the original SWT renderer that draws the line:
// draw a Focus rectangle
if (parent.isFocusControl()) {
Display display = parent.getDisplay();
if (parent.simple || parent.single) {
gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
gc.drawFocus(xDraw-1, textY-1, extent.x+2, extent.y+2);
} else {
gc.setForeground(display.getSystemColor(BUTTON_BORDER));
gc.drawLine(xDraw, textY+extent.y+1, xDraw+extent.x+1, textY+extent.y+1);
}
}
The interesting part here is gc.drawLine(...). You can let the original renderer draw everything and then you can draw on top of it you own line with a different color.
I just recomputed the arguments. I did cut some corners, and this will not work when text uses ellipses, but it can be a good starting point.
Note: this code might break with the next version of SWT. You have to update it whenever you update SWT.
Here is a snippet where the items have different colors:
public static void main(String[] args) {
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new FillLayout());
final int tabFolderStyle = SWT.NONE;
final CTabFolder tabFolder = new CTabFolder(shell, SWT.NONE);
tabFolder.setSimple(false);
final CTabItem tabItem1 = new CTabItem(tabFolder, SWT.NONE);
tabItem1.setText("Tab1");
tabItem1.setData("color", display.getSystemColor(SWT.COLOR_CYAN));
final CTabItem tabItem2 = new CTabItem(tabFolder, SWT.NONE);
tabItem2.setText("Tab2");
tabItem2.setData("color", display.getSystemColor(SWT.COLOR_YELLOW));
tabFolder.setRenderer(new org.eclipse.swt.custom.CTabFolderRenderer(tabFolder){
protected void draw (int part, int state, Rectangle bounds, GC gc) {
super.draw(part, state, bounds, gc);
if (part >= 0 && part == tabFolder.getSelectionIndex()) {
int itemIndex = part;
CTabItem item = parent.getItem(itemIndex);
int x = bounds.x;
int y = bounds.y;
int height = bounds.height;
int width = bounds.width;
boolean onBottom = (tabFolderStyle & SWT.BOTTOM) != 0;
Point extent = gc.textExtent(item.getText(), SWT.DRAW_TRANSPARENT | SWT.DRAW_MNEMONIC);
int textY = y + (height - extent.y) / 2;
textY += onBottom ? -1 : 1;
Rectangle trim = computeTrim(itemIndex, SWT.NONE, 0, 0, 0, 0);
int xDraw = x - trim.x;
gc.setForeground((Color) item.getData("color"));
gc.drawLine(xDraw, textY+extent.y+1, xDraw+extent.x+1, textY+extent.y+1);
}
}
});
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
The standard tab folder renderer org.eclipse.swt.custom.CTabFolderRenderer does not support changing this color.
You can write your own renderer and install it using CTabFolder.setRenderer but this is quite hard work.
I think this line is only shown if the tab itself has focus, making a control in the tab have focus stops it being drawn.
I'm having some trouble with my selection listener, currently I have independently trialed selection of text from TOP to BOTTOM, and BOTTOM to TOP (Mouse movement), however these trials won't work together i.e. the one SelectionListener is bidirectional...
private void setupSelectionListener() {
this.contentValues.addSelectionListener(new SelectionListener() {
#Override
public void widgetSelected(SelectionEvent event) {
StyledText text = (StyledText)event.widget;
int x = event.x;
int y = event.y;
//Mouse Drag Listener here??? - Detects Right
//FOR TOP TO BOTTOM SELECTION
text.setSelection(event.x);
int beginPosition = event.x;
int beginByte = beginPosition / 3;
int endPosition = event.y;
int endByte = endPosition / 3;
setSelection(beginByte, endByte);
//Mouse Drag Listener here??? - Detects Left
//FOR BOTTOM TO TOP SELECTION
text.setSelection(event.y);
int beginPosition = event.y;
int beginByte = beginPosition / 3;
int endPosition = event.x;
int endByte = endPosition / 3;
setSelection(beginByte, endByte);
}
#Override
public void widgetDefaultSelected(SelectionEvent e) {
// Does nothing...
}
});
So either I need a Mouse Drag Listener like I have stated in the comments, or a conditional statement comparing the event.x and event.y
I have attempted at adding a drag detection listener inside the selection listener, but this disrupts the format of the event coordinate selection.
Any help would be greatly appreciated.
If the source widget on which event happend is Styled Text object then this selection will happen automatically without any listener correct, means if you drag with mouse holding, the contents will automatically selected.