/* Copyright (c) 2012 Jesper Öqvist <jesper@llbit.se> * * This file is part of Chunky. * * Chunky is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Chunky is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with Chunky. If not, see <http://www.gnu.org/licenses/>. */ package se.llbit.chunky.world; import se.llbit.chunky.world.listeners.ChunkDeletionListener; import se.llbit.chunky.world.listeners.ChunkUpdateListener; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.Set; /** * Tracks chunk selections. * * @author Jesper Öqvist <jesper@llbit.se> */ public class ChunkSelectionTracker implements ChunkDeletionListener { private final Set<ChunkPosition> selected = new HashSet<>(); private final Collection<ChunkUpdateListener> chunkUpdateListeners = new LinkedList<>(); private final Collection<ChunkSelectionListener> selectionListeners = new LinkedList<>(); /** * Add a chunk update listener to listen for selection changes. */ public void addChunkUpdateListener(ChunkUpdateListener listener) { synchronized (chunkUpdateListeners) { chunkUpdateListeners.add(listener); } } /** * Add a chunk update listener to listen for selection changes. */ public void addSelectionListener(ChunkSelectionListener listener) { synchronized (chunkUpdateListeners) { selectionListeners.add(listener); } } /** * Remove a chunk update listener. */ public synchronized void removeSelectionListener(ChunkSelectionListener listener) { synchronized (chunkUpdateListeners) { selectionListeners.remove(listener); } } /** * Notify the chunk update listeners that chunks have been updated * * @param chunks the updated chunks */ private void notifyChunksUpdated(Collection<ChunkPosition> chunks) { for (ChunkPosition chunk : chunks) { notifyChunkUpdated(chunk); } } /** * Notify the chunk update listeners that a chunk has been updated. * * @param chunk the updated chunk */ private void notifyChunkUpdated(ChunkPosition chunk) { for (ChunkUpdateListener listener : chunkUpdateListeners) { listener.chunkUpdated(chunk); } } private void notifyChunkSelectionChange() { for (ChunkSelectionListener listener : selectionListeners) { listener.chunkSelectionChanged(); } } @Override public void chunkDeleted(ChunkPosition chunk) { selected.remove(chunk); notifyChunkUpdated(chunk); notifyChunkSelectionChange(); } /** * Toggle the selected status of the chunk at the given coordinates. * * @param cx chunk x-position * @param cz chunk z-position */ public synchronized void toggleChunk(World world, int cx, int cz) { ChunkPosition chunk = ChunkPosition.get(cx, cz); if (selected.contains(chunk)) { selected.remove(chunk); notifyChunkUpdated(chunk); notifyChunkSelectionChange(); } else if (!world.getChunk(chunk).isEmpty()) { selected.add(chunk); notifyChunkUpdated(chunk); notifyChunkSelectionChange(); } } /** * Adds a chunk to the selection. * * @param cx chunk x-position * @param cz chunk z-position */ public synchronized void selectChunk(World world, int cx, int cz) { ChunkPosition chunk = ChunkPosition.get(cx, cz); if (!selected.contains(chunk) && !world.getChunk(chunk).isEmpty()) { selected.add(chunk); notifyChunkUpdated(chunk); notifyChunkSelectionChange(); } } /** * Select the region containing the given chunk. * * @param cx chunk x-position * @param cz chunk z-position */ public synchronized void selectRegion(World world, int cx, int cz) { ChunkPosition chunk = ChunkPosition.get(cx, cz); int rx = cx >> 5; int rz = cz >> 5; if (selected.contains(chunk)) { deselectChunks(rx * 32, rz * 32, rx * 32 + 31, rz * 32 + 31); } else { selectChunks(world, rx * 32, rz * 32, rx * 32 + 31, rz * 32 + 31); } } /** * Select chunks within rectangle. */ public synchronized void selectChunks(World world, int cx0, int cz0, int cx1, int cz1) { boolean selectionChanged = false; for (int cx = cx0; cx <= cx1; ++cx) { for (int cz = cz0; cz <= cz1; ++cz) { ChunkPosition chunk = ChunkPosition.get(cx, cz); if (!selected.contains(chunk) && !world.getChunk(chunk).isEmpty()) { selected.add(chunk); selectionChanged = true; notifyChunkUpdated(chunk); } } } if (selectionChanged) { notifyChunkSelectionChange(); } } /** * Deselect chunks within rectangle. */ public synchronized void deselectChunks(int cx0, int cz0, int cx1, int cz1) { boolean selectionChanged = false; for (int cx = cx0; cx <= cx1; ++cx) { for (int cz = cz0; cz <= cz1; ++cz) { ChunkPosition chunk = ChunkPosition.get(cx, cz); if (selected.contains(chunk)) { selected.remove(chunk); selectionChanged = true; notifyChunkUpdated(chunk); } } } if (selectionChanged) { notifyChunkSelectionChange(); } } /** * Deselect all chunks. */ public void clearSelection() { if (!selected.isEmpty()) { Set<ChunkPosition> prev = new HashSet<>(selected); selected.clear(); notifyChunksUpdated(prev); notifyChunkSelectionChange(); } } /** * @return <code>true</code> if the given chunk position is selected */ public boolean isSelected(ChunkPosition chunk) { return selected.contains(chunk); } /** * @return The currently selected chunks */ public synchronized Collection<ChunkPosition> getSelection() { return new LinkedList<>(selected); } }