/* * Copyright 2014 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.rendering.nui.layouts; import com.google.common.collect.Lists; import org.terasology.input.Keyboard; import org.terasology.math.geom.Rect2i; import org.terasology.math.TeraMath; import org.terasology.math.geom.Vector2i; import org.terasology.math.geom.Vector2f; import org.terasology.rendering.nui.BaseInteractionListener; import org.terasology.rendering.nui.Canvas; import org.terasology.rendering.nui.CoreLayout; import org.terasology.rendering.nui.InteractionListener; import org.terasology.rendering.nui.LayoutHint; import org.terasology.rendering.nui.UIWidget; import org.terasology.rendering.nui.events.NUIMouseClickEvent; import org.terasology.rendering.nui.events.NUIMouseDragEvent; import org.terasology.rendering.nui.events.NUIMouseOverEvent; import org.terasology.rendering.nui.events.NUIMouseWheelEvent; import java.util.Iterator; import java.util.List; /** * A layout that allows positioning to a virtual coordinate system, which is mapped to screen coordinates using a * viewport. * */ public class ZoomableLayout extends CoreLayout { private List<PositionalWidget> widgets = Lists.newArrayList(); private Vector2f pixelSize; private Vector2i screenSize; private Vector2f windowPosition = new Vector2f(); private Vector2f windowSize = new Vector2f(50, 50); private Vector2i last; private InteractionListener dragListener = new BaseInteractionListener() { @Override public void onMouseOver(NUIMouseOverEvent event) { last = new Vector2i(event.getRelativeMousePosition()); } @Override public boolean onMouseClick(NUIMouseClickEvent event) { return true; } @Override public void onMouseDrag(NUIMouseDragEvent event) { Vector2f p = screenToWorld(last); p.sub(screenToWorld(event.getRelativeMousePosition())); p.add(windowPosition); setWindowPosition(p); } @Override public boolean onMouseWheel(NUIMouseWheelEvent event) { if (event.getKeyboard().isKeyDown(Keyboard.Key.LEFT_SHIFT.getId())) { float scale = 1 + event.getWheelTurns() * 0.05f; zoom(scale, scale, event.getRelativeMousePosition()); } return false; } }; public ZoomableLayout() { } public ZoomableLayout(String id) { super(id); } @Override public void addWidget(UIWidget element, LayoutHint hint) { if (element instanceof PositionalWidget) { PositionalWidget positionalWidget = (PositionalWidget) element; addWidget(positionalWidget); } } public void addWidget(PositionalWidget widget) { if (widget != null) { widgets.add(widget); widget.onAdded(this); } } @Override public void removeWidget(UIWidget element) { if (element instanceof PositionalWidget) { PositionalWidget positionalWidget = (PositionalWidget) element; removeWidget(positionalWidget); } } public void removeWidget(PositionalWidget widget) { if (widget != null) { widget.onRemoved(this); widgets.remove(widget); } } public void removeAll() { for (PositionalWidget widget : widgets) { widget.onRemoved(this); } widgets.clear(); } @Override public void onDraw(Canvas canvas) { setScreenSize(canvas.size()); calculateSizes(); canvas.addInteractionRegion(dragListener); for (PositionalWidget widget : widgets) { if (!widget.isVisible()) { continue; } Vector2i screenStart = worldToScreen(widget.getPosition()); Vector2f worldEnd = new Vector2f(widget.getPosition()); worldEnd.add(widget.getSize()); Vector2i screenEnd = worldToScreen(worldEnd); canvas.drawWidget(widget, Rect2i.createFromMinAndMax(screenStart, screenEnd)); } } @Override public Vector2i getPreferredContentSize(Canvas canvas, Vector2i sizeHint) { return Vector2i.zero(); } @Override public Vector2i getMaxContentSize(Canvas canvas) { return new Vector2i(Integer.MAX_VALUE, Integer.MAX_VALUE); } @Override public void update(float delta) { for (PositionalWidget widget : widgets) { widget.update(delta); } } public List<PositionalWidget> getWidgets() { return widgets; } @Override public Iterator<UIWidget> iterator() { return new Iterator<UIWidget>() { private Iterator<PositionalWidget> delegate = widgets.iterator(); @Override public boolean hasNext() { return delegate.hasNext(); } @Override public UIWidget next() { return delegate.next(); } @Override public void remove() { delegate.remove(); } }; } public Vector2f screenToWorld(Vector2i screenPos) { Vector2f world = new Vector2f(screenPos.x / pixelSize.x, screenPos.y / pixelSize.y); world.add(windowPosition); return world; } public Vector2i worldToScreen(Vector2f world) { return new Vector2i(TeraMath.ceilToInt((world.x - windowPosition.x) * pixelSize.x), TeraMath.ceilToInt((world.y - windowPosition.y) * pixelSize.y)); } public void setWindowPosition(Vector2f pos) { windowPosition = pos; } public void setWindowSize(Vector2f size) { windowSize = size; } public void setScreenSize(Vector2i size) { screenSize = size; } public Vector2f getPixelSize() { return pixelSize; } public Vector2i getScreenSize() { return screenSize; } public Vector2f getWindowPosition() { return windowPosition; } public Vector2f getWindowSize() { return windowSize; } public void calculateSizes() { if (windowSize.x > windowSize.y) { windowSize.x = windowSize.y; } if (windowSize.x < windowSize.y) { windowSize.y = windowSize.x; } if ((screenSize.x != 0) && (screenSize.y != 0)) { if (screenSize.x > screenSize.y) { windowSize.x *= (float) screenSize.x / screenSize.y; } else { windowSize.y *= (float) screenSize.y / screenSize.x; } } if ((windowSize.x > 0) && (windowSize.y > 0)) { pixelSize = new Vector2f(screenSize.x / windowSize.x, screenSize.y / windowSize.y); } else { pixelSize = new Vector2f(); } } public void zoom(float zoomX, float zoomY, Vector2i mousePos) { Vector2f mouseBefore = screenToWorld(mousePos); windowSize.x *= zoomX; windowSize.y *= zoomY; calculateSizes(); Vector2f mouseAfter = screenToWorld(mousePos); windowPosition.x -= mouseAfter.x - mouseBefore.x; windowPosition.y -= mouseAfter.y - mouseBefore.y; } public interface PositionalWidget<L extends ZoomableLayout> extends UIWidget { Vector2f getPosition(); Vector2f getSize(); void onAdded(L layout); void onRemoved(L layout); } }