package com.kartoflane.superluminal2.ui; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseWheelListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.Transform; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Slider; import com.kartoflane.superluminal2.components.interfaces.MouseInputListener; import com.kartoflane.superluminal2.core.Cache; import com.kartoflane.superluminal2.utils.UIUtils; public class ZoomWindow implements MouseInputListener, MouseWheelListener, PaintListener, Listener { private static final float scaleMin = 1.0f; private static final float scaleMax = 6.0f; private static final RGB canvasRGB = new RGB(164, 164, 164); private static ZoomWindow instance; private volatile boolean oddPaintEvent = false; private boolean canvasRedrawn = true; private Control copySource = null; private Point sourceSize = null; private Point copyOrigin = new Point(0, 0); private int bufferW = 0; private int bufferH = 0; private Image buffer; private Point dragClick = null; private Transform transform = null; private float scale = 1.0f; private long lastScrollEventTime = 0; private Shell shell; private Canvas canvas; private Label lblInfo; private Label lblZoom; private Slider slider; public ZoomWindow(Shell parent, Control source) { instance = this; shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.MIN | SWT.RESIZE); shell.setLayout(new GridLayout(3, false)); lblZoom = new Label(shell, SWT.NONE); lblZoom.setText("Zoom:"); slider = new Slider(shell, SWT.NONE); slider.setIncrement(10); slider.setPageIncrement(100); slider.setMaximum((int) scaleMax * 100 + 10); slider.setMinimum((int) scaleMin * 100); slider.setSelection(100); lblInfo = new Label(shell, SWT.NONE); lblInfo.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, false, 1, 1)); lblInfo.setImage(Cache.checkOutImage(this, "cpath:/assets/help.png")); String msg = "- Left-click and drag in this window to move the viewport\n" + "- Hold down mouse scroll button and drag in the main window to move the viewport\n" + "- Scroll with mouse scroll to zoom in or out"; UIUtils.addTooltip(lblInfo, msg); canvas = new Canvas(shell, SWT.BORDER | SWT.DOUBLE_BUFFERED); canvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1)); canvas.setBackground(Cache.checkOutColor(this, canvasRGB)); canvas.addMouseListener(this); canvas.addMouseMoveListener(this); canvas.addMouseWheelListener(this); transform = new Transform(shell.getDisplay()); transform.scale(scale, scale); shell.addListener(SWT.Close, new Listener() { public void handleEvent(Event e) { dispose(); } }); shell.addListener(SWT.Resize, new Listener() { public void handleEvent(Event e) { copyOrigin.x = (int) Math.min(sourceSize.x - bufferW / scale, copyOrigin.x); copyOrigin.y = (int) Math.min(sourceSize.y - bufferH / scale, copyOrigin.y); setBufferSize(shell.getClientArea().width, canvas.getClientArea().height); repaint(false); } }); slider.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { int sel = slider.getSelection(); float newScale = sel / 100f; setScale(newScale); } }); updateText(); setCopySource(source); shell.setSize(300, 300); setBufferSize(300, 300); } public static ZoomWindow getInstance() { return instance; } public void setCopySource(Control source) { if (copySource != null) { copySource.removePaintListener(this); copySource.removeMouseMoveListener(this); copySource.removeMouseListener(this); Display display = UIUtils.getDisplay(); display.removeFilter(SWT.MouseWheel, this); } copySource = source; if (copySource != null) { copySource.addPaintListener(this); copySource.addMouseMoveListener(this); copySource.addMouseListener(this); Display display = UIUtils.getDisplay(); display.addFilter(SWT.MouseWheel, this); sourceSize = copySource.getSize(); } } public void setCopyAreaLoc(int x, int y) { Point sourceSize = copySource.getSize(); copyOrigin.x = (int) Math.min(sourceSize.x - bufferW / scale, x); copyOrigin.y = (int) Math.min(sourceSize.y - bufferH / scale, y); copyOrigin.x = Math.max(0, x); copyOrigin.y = Math.max(0, y); } public void setBufferSize(int w, int h) { Point sourceSize = copySource.getSize(); bufferW = (int) Math.min(sourceSize.x, w); bufferH = (int) Math.min(sourceSize.y, h); if (buffer != null) { buffer.dispose(); buffer = null; } if (bufferW <= 0 || bufferH <= 0) return; buffer = new Image(shell.getDisplay(), bufferW, bufferH); } public void open() { shell.open(); shell.setMinimized(false); repaint(false); } public void repaint(boolean wipe) { if (copyOrigin == null || buffer == null || copySource == null) return; GC gc = new GC(copySource); gc.copyArea(buffer, copyOrigin.x, copyOrigin.y); gc.dispose(); gc = new GC(canvas); if (wipe) { gc.fillRectangle(canvas.getClientArea()); canvasRedrawn = true; } gc.setTransform(transform); gc.drawImage(buffer, 0, 0); gc.dispose(); } public void dispose() { Cache.checkInColor(this, canvasRGB); Cache.checkInImage(this, "cpath:/assets/help.png"); setCopySource(null); buffer.dispose(); buffer = null; transform.dispose(); transform = null; shell.dispose(); shell = null; instance = null; } private void updateText() { shell.setText(String.format("Zoom Window - %.2f%%", scale * 100)); } private void setScale(float newScale) { // Undo previous scaling transform.scale(1 / scale, 1 / scale); // Limit the scale newScale = Math.max(scaleMin, newScale); newScale = Math.min(scaleMax, newScale); // Sometimes when going from high magnification to lower, artifacts can appear Rectangle canvasArea = canvas.getClientArea(); if ((bufferW * scale > canvasArea.width && bufferW * newScale < canvasArea.width) || (bufferH * scale > canvasArea.height && bufferH * newScale < canvasArea.height)) canvasRedrawn = false; scale = newScale; // Apply the scaling Point sourceSize = copySource.getSize(); copyOrigin.x = (int) Math.min(sourceSize.x - bufferW / scale, copyOrigin.x); copyOrigin.y = (int) Math.min(sourceSize.y - bufferH / scale, copyOrigin.y); transform.scale(scale, scale); repaint(!canvasRedrawn); updateText(); } /* * ===================== * XXX: Listener methods * ===================== */ public void mouseMove(MouseEvent e) { if (dragClick != null) { copyOrigin.x += dragClick.x - e.x; copyOrigin.y += dragClick.y - e.y; dragClick.x = e.x; dragClick.y = e.y; Point sourceSize = copySource.getSize(); copyOrigin.x = (int) Math.min(sourceSize.x - bufferW / scale, copyOrigin.x); copyOrigin.y = (int) Math.min(sourceSize.y - bufferH / scale, copyOrigin.y); copyOrigin.x = Math.max(0, copyOrigin.x); copyOrigin.y = Math.max(0, copyOrigin.y); repaint(false); } } public void mouseDoubleClick(MouseEvent e) { } public void mouseDown(MouseEvent e) { Object source = e.getSource(); if ((source == canvas && e.button == 1) || e.button == 2) { dragClick = new Point(e.x, e.y); } } public void mouseUp(MouseEvent e) { dragClick = null; } public void mouseScrolled(MouseEvent e) { Object source = e.getSource(); /* * Since the zoom window is a display filter, all scroll events pass through it. * * Oftentimes, a single scroll of the mouse will trigger several scroll events, * one per each widget, and they all would trigger the scale change. * * We only want to change the scale once per mouse scroll, so we filter the events * based on time at which they've been issued (since the multiple events get issued * at the same time) */ if (lastScrollEventTime < e.time && (source == slider || source == canvas || (source instanceof Control && ((Control) source).getShell() == EditorWindow.getInstance().getShell()))) { float newScale = scale + e.count / 5f; slider.setSelection((int) (newScale * 100)); setScale(newScale); lastScrollEventTime = e.time; } } public void paintControl(PaintEvent e) { oddPaintEvent = !oddPaintEvent; // If the source widget is double buffered, then we need to redraw its area before we copy from it // This is because the first paint event is sent when the image is painted to buffer, // and the second event when the widget itself gets painted (ie. our copySource) if (!oddPaintEvent) { if ((copySource.getStyle() & SWT.DOUBLE_BUFFERED) != 0) { copySource.redraw(copyOrigin.x, copyOrigin.y, (int) (bufferW / scale), (int) (bufferH / scale), false); } else { // If it's not double buffered, then we want to redraw with every paint event... oddPaintEvent = true; } } if (oddPaintEvent) repaint(false); } @Override public void handleEvent(Event e) { if (e.type == SWT.MouseWheel) { mouseScrolled(new MouseEvent(e)); } } public void mouseEnter(MouseEvent e) { } public void mouseExit(MouseEvent e) { } public void mouseHover(MouseEvent e) { } }