this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add renaming/moving/deleting of assets

+1003 -457
-454
src/main/java/app/pane/AssetsPanel.java
··· 1 - package app.pane; 2 - 3 - import java.awt.BorderLayout; 4 - import java.awt.Color; 5 - import java.awt.Cursor; 6 - import java.awt.Dimension; 7 - import java.awt.FlowLayout; 8 - import java.awt.Font; 9 - import java.awt.Graphics; 10 - import java.awt.Graphics2D; 11 - import java.awt.Image; 12 - import java.awt.Rectangle; 13 - import java.awt.RenderingHints; 14 - import java.awt.TexturePaint; 15 - import java.awt.image.BufferedImage; 16 - import java.awt.event.MouseAdapter; 17 - import java.awt.event.MouseEvent; 18 - import java.io.File; 19 - import java.io.IOException; 20 - import java.nio.file.FileSystems; 21 - import java.nio.file.StandardWatchEventKinds; 22 - import java.nio.file.WatchEvent; 23 - import java.nio.file.WatchKey; 24 - import java.nio.file.WatchService; 25 - import java.util.ArrayList; 26 - import java.util.List; 27 - 28 - import javax.swing.BorderFactory; 29 - import javax.swing.Icon; 30 - import javax.swing.ImageIcon; 31 - import javax.swing.JLabel; 32 - import javax.swing.JPanel; 33 - import javax.swing.JScrollPane; 34 - import javax.swing.SwingUtilities; 35 - import javax.swing.SwingWorker; 36 - import javax.swing.UIManager; 37 - 38 - import app.Environment; 39 - import assets.AssetHandle; 40 - import assets.AssetManager; 41 - import assets.AssetManager.DirectoryListing; 42 - import net.miginfocom.swing.MigLayout; 43 - import util.Logger; 44 - import util.ui.ThemedIcon; 45 - import util.ui.UniformGridLayout; 46 - 47 - public class AssetsPanel extends JPanel 48 - { 49 - private String currentPath = ""; 50 - private String selectedName; 51 - private JPanel selectedPanel; 52 - 53 - private JPanel breadcrumbBar; 54 - private JPanel resultsPanel; 55 - private JScrollPane scrollPane; 56 - 57 - private WatchService watchService; 58 - private Thread watchThread; 59 - private List<WatchKey> watchKeys = new ArrayList<>(); 60 - 61 - private static final int ITEM_SIZE = AssetHandle.THUMBNAIL_WIDTH + 4 * 2; 62 - 63 - public AssetsPanel() 64 - { 65 - setLayout(new MigLayout("ins 4, fill", "[grow]", "[pref!][grow]")); 66 - 67 - breadcrumbBar = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); 68 - add(breadcrumbBar, "growx, wrap"); 69 - 70 - resultsPanel = new JPanel(new UniformGridLayout(ITEM_SIZE, ITEM_SIZE, 0, 0)); 71 - resultsPanel.addMouseListener(new MouseAdapter() { 72 - @Override 73 - public void mouseClicked(MouseEvent e) 74 - { 75 - clearSelection(); 76 - } 77 - }); 78 - scrollPane = new JScrollPane(resultsPanel); 79 - scrollPane.setBorder(null); 80 - add(scrollPane, "grow, push"); 81 - 82 - try { 83 - watchService = FileSystems.getDefault().newWatchService(); 84 - startWatchThread(); 85 - } 86 - catch (IOException e) { 87 - Logger.logError("Failed to create file watch service: " + e.getMessage()); 88 - } 89 - 90 - navigateTo(""); 91 - } 92 - 93 - // --- Navigation --- 94 - 95 - private void navigateTo(String path) 96 - { 97 - currentPath = path; 98 - clearSelection(); 99 - rebuildBreadcrumb(); 100 - refresh(); 101 - registerWatchers(); 102 - } 103 - 104 - private void select(String name, JPanel panel) 105 - { 106 - if (selectedPanel != null) { 107 - selectedPanel.repaint(); 108 - } 109 - 110 - selectedName = name; 111 - selectedPanel = panel; 112 - panel.repaint(); 113 - rebuildBreadcrumb(); 114 - } 115 - 116 - private void clearSelection() 117 - { 118 - if (selectedPanel != null) { 119 - selectedPanel.repaint(); 120 - } 121 - selectedName = null; 122 - selectedPanel = null; 123 - rebuildBreadcrumb(); 124 - } 125 - 126 - // --- Breadcrumb --- 127 - 128 - private void rebuildBreadcrumb() 129 - { 130 - breadcrumbBar.removeAll(); 131 - 132 - // Project name as root 133 - String projectName = Environment.getProject().getManifest().getName(); 134 - JLabel rootLabel = createClickableLabel(projectName); 135 - rootLabel.addMouseListener(new MouseAdapter() { 136 - @Override 137 - public void mouseClicked(MouseEvent e) 138 - { 139 - navigateTo(""); 140 - } 141 - }); 142 - breadcrumbBar.add(rootLabel); 143 - 144 - // Path components 145 - if (!currentPath.isEmpty()) { 146 - String[] parts = currentPath.split("/"); 147 - StringBuilder pathSoFar = new StringBuilder(); 148 - for (String part : parts) { 149 - if (part.isEmpty()) 150 - continue; 151 - pathSoFar.append(part).append("/"); 152 - 153 - breadcrumbBar.add(createSeparatorLabel()); 154 - 155 - String targetPath = pathSoFar.toString(); 156 - JLabel partLabel = createClickableLabel(part); 157 - partLabel.addMouseListener(new MouseAdapter() { 158 - @Override 159 - public void mouseClicked(MouseEvent e) 160 - { 161 - navigateTo(targetPath); 162 - } 163 - }); 164 - breadcrumbBar.add(partLabel); 165 - } 166 - } 167 - 168 - // Selected item name 169 - if (selectedName != null) { 170 - breadcrumbBar.add(createSeparatorLabel()); 171 - 172 - JLabel fileLabel = new JLabel(selectedName); 173 - fileLabel.setFont(fileLabel.getFont().deriveFont(Font.BOLD)); 174 - breadcrumbBar.add(fileLabel); 175 - } 176 - 177 - breadcrumbBar.revalidate(); 178 - breadcrumbBar.repaint(); 179 - } 180 - 181 - private JLabel createClickableLabel(String text) 182 - { 183 - JLabel label = new JLabel(text); 184 - label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 185 - return label; 186 - } 187 - 188 - private JLabel createSeparatorLabel() 189 - { 190 - JLabel sep = new JLabel(" / "); 191 - sep.setForeground(UIManager.getColor("Label.disabledForeground")); 192 - return sep; 193 - } 194 - 195 - // --- Results --- 196 - 197 - private void refresh() 198 - { 199 - resultsPanel.removeAll(); 200 - 201 - DirectoryListing listing = AssetManager.listDirectory(currentPath); 202 - 203 - // Subdirectories first 204 - for (String subdirName : listing.subdirectories()) { 205 - resultsPanel.add(createSubdirItem(subdirName)); 206 - } 207 - 208 - // Then files 209 - for (AssetHandle asset : listing.files()) { 210 - resultsPanel.add(createAssetItem(asset)); 211 - } 212 - 213 - resultsPanel.revalidate(); 214 - resultsPanel.repaint(); 215 - } 216 - 217 - private JPanel createSubdirItem(String name) 218 - { 219 - return createItem(name, ThemedIcon.FOLDER_OPEN_24, false, () -> { 220 - navigateTo(currentPath + name + "/"); 221 - }); 222 - } 223 - 224 - private JPanel createAssetItem(AssetHandle asset) 225 - { 226 - JPanel panel = createItem(asset.getAssetName(), ThemedIcon.PACKAGE_24, asset.thumbnailHasCheckerboard(), () -> { 227 - openAsset(asset); 228 - }); 229 - 230 - JLabel icon = (JLabel) ((BorderLayout) panel.getLayout()).getLayoutComponent(BorderLayout.CENTER); 231 - 232 - // Load thumbnail asynchronously 233 - new SwingWorker<Image, Void>() { 234 - @Override 235 - protected Image doInBackground() 236 - { 237 - return asset.getThumbnail(); 238 - } 239 - 240 - @Override 241 - protected void done() 242 - { 243 - try { 244 - Image thumb = get(); 245 - if (thumb != null) { 246 - icon.setIcon(new ImageIcon(thumb)); 247 - panel.revalidate(); 248 - panel.repaint(); 249 - } 250 - } 251 - catch (Exception e) { 252 - // ignore — keep default icon 253 - } 254 - } 255 - }.execute(); 256 - 257 - String desc = asset.getAssetDescription(); 258 - if (desc != null && !desc.isEmpty()) { 259 - panel.setToolTipText(desc); 260 - } 261 - 262 - return panel; 263 - } 264 - 265 - private JPanel createItem(String name, Icon defaultIcon, boolean checkerboard, Runnable onDoubleClick) 266 - { 267 - JPanel panel = createItemPanel(); 268 - JLabel icon = checkerboard ? new JLabel(defaultIcon) { 269 - @Override 270 - protected void paintComponent(Graphics g) 271 - { 272 - if (getIcon() != null) 273 - paintCheckerboard((Graphics2D) g, this); 274 - super.paintComponent(g); 275 - } 276 - } : new JLabel(defaultIcon); 277 - icon.setHorizontalAlignment(JLabel.CENTER); 278 - icon.setVerticalAlignment(JLabel.CENTER); 279 - 280 - JLabel label = new JLabel(name); 281 - label.setHorizontalAlignment(JLabel.CENTER); 282 - 283 - panel.add(icon, BorderLayout.CENTER); 284 - panel.add(label, BorderLayout.SOUTH); 285 - 286 - panel.addMouseListener(new MouseAdapter() { 287 - @Override 288 - public void mouseEntered(MouseEvent e) 289 - { 290 - panel.putClientProperty("hovered", true); 291 - panel.repaint(); 292 - } 293 - 294 - @Override 295 - public void mouseExited(MouseEvent e) 296 - { 297 - panel.putClientProperty("hovered", false); 298 - panel.repaint(); 299 - } 300 - 301 - @Override 302 - public void mouseClicked(MouseEvent e) 303 - { 304 - if (e.getClickCount() == 1) { 305 - select(name, panel); 306 - } 307 - else if (e.getClickCount() == 2) { 308 - onDoubleClick.run(); 309 - } 310 - } 311 - }); 312 - 313 - return panel; 314 - } 315 - 316 - private JPanel createItemPanel() 317 - { 318 - JPanel panel = new JPanel(new BorderLayout()) { 319 - @Override 320 - protected void paintComponent(Graphics g) 321 - { 322 - if (this == selectedPanel) { 323 - Graphics2D g2 = (Graphics2D) g.create(); 324 - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 325 - 326 - Color bg = UIManager.getColor("Component.borderColor"); 327 - g2.setColor(bg); 328 - g2.fillRoundRect(0, 0, getWidth(), getHeight(), 8, 8); 329 - g2.dispose(); 330 - } 331 - 332 - super.paintComponent(g); 333 - } 334 - }; 335 - panel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); 336 - panel.setPreferredSize(new Dimension(ITEM_SIZE, ITEM_SIZE)); 337 - panel.setOpaque(false); 338 - return panel; 339 - } 340 - 341 - private static void paintCheckerboard(Graphics2D g2, JLabel label) 342 - { 343 - Icon icon = label.getIcon(); 344 - int iconW = icon.getIconWidth(); 345 - int iconH = icon.getIconHeight(); 346 - int x = (label.getWidth() - iconW) / 2; 347 - int y = (label.getHeight() - iconH) / 2; 348 - 349 - Color panelBg = UIManager.getColor("Panel.background"); 350 - boolean dark = panelBg != null && luminance(panelBg) < 0.5f; 351 - Color c1 = dark ? new Color(0x3C3C3C) : new Color(0xCCCCCC); 352 - Color c2 = dark ? new Color(0x2C2C2C) : new Color(0xFFFFFF); 353 - int cs = 4; 354 - var tile = new BufferedImage(cs * 2, cs * 2, BufferedImage.TYPE_INT_RGB); 355 - Graphics2D tg = tile.createGraphics(); 356 - tg.setColor(c1); 357 - tg.fillRect(0, 0, cs, cs); 358 - tg.fillRect(cs, cs, cs, cs); 359 - tg.setColor(c2); 360 - tg.fillRect(cs, 0, cs, cs); 361 - tg.fillRect(0, cs, cs, cs); 362 - tg.dispose(); 363 - g2.setPaint(new TexturePaint(tile, new Rectangle(x, y, cs * 2, cs * 2))); 364 - g2.fillRect(x, y, iconW, iconH); 365 - } 366 - 367 - private static float luminance(Color c) 368 - { 369 - return (0.299f * c.getRed() + 0.587f * c.getGreen() + 0.114f * c.getBlue()) / 255f; 370 - } 371 - 372 - private void openAsset(AssetHandle asset) 373 - { 374 - // TODO 375 - } 376 - 377 - // --- File watching --- 378 - 379 - private void registerWatchers() 380 - { 381 - if (watchService == null) 382 - return; 383 - 384 - // Cancel existing keys 385 - for (WatchKey key : watchKeys) { 386 - key.cancel(); 387 - } 388 - watchKeys.clear(); 389 - 390 - // Register all stack dirs for current path 391 - for (File dir : AssetManager.getStackDirsForPath(currentPath)) { 392 - try { 393 - WatchKey key = dir.toPath().register(watchService, 394 - StandardWatchEventKinds.ENTRY_CREATE, 395 - StandardWatchEventKinds.ENTRY_DELETE, 396 - StandardWatchEventKinds.ENTRY_MODIFY); 397 - watchKeys.add(key); 398 - } 399 - catch (IOException e) { 400 - Logger.logError("Failed to watch directory: " + dir); 401 - } 402 - } 403 - } 404 - 405 - private void startWatchThread() 406 - { 407 - watchThread = new Thread(() -> { 408 - while (!Thread.interrupted()) { 409 - try { 410 - WatchKey key = watchService.take(); 411 - 412 - // Drain events 413 - for (WatchEvent<?> event : key.pollEvents()) { 414 - // just need to know something changed 415 - } 416 - key.reset(); 417 - 418 - // Debounce: wait a bit for rapid changes to settle 419 - Thread.sleep(200); 420 - 421 - // Drain any additional events that arrived during debounce 422 - WatchKey extra; 423 - while ((extra = watchService.poll()) != null) { 424 - extra.pollEvents(); 425 - extra.reset(); 426 - } 427 - 428 - SwingUtilities.invokeLater(this::refresh); 429 - } 430 - catch (InterruptedException e) { 431 - Thread.currentThread().interrupt(); 432 - break; 433 - } 434 - } 435 - }, "AssetsPanel-FileWatcher"); 436 - watchThread.setDaemon(true); 437 - watchThread.start(); 438 - } 439 - 440 - public void dispose() 441 - { 442 - if (watchThread != null) { 443 - watchThread.interrupt(); 444 - } 445 - if (watchService != null) { 446 - try { 447 - watchService.close(); 448 - } 449 - catch (IOException e) { 450 - // ignore on shutdown 451 - } 452 - } 453 - } 454 - }
+4 -3
src/main/java/app/pane/Dock.java
··· 8 8 import javax.swing.JTabbedPane; 9 9 import javax.swing.JTextArea; 10 10 11 + import app.pane.explorer.Explorer; 11 12 import util.Logger; 12 13 import util.ui.ThemedIcon; 13 14 ··· 23 24 tabbedPane.putClientProperty("JTabbedPane.tabType", "card"); 24 25 tabbedPane.putClientProperty("JTabbedPane.hasFullBorder", true); 25 26 26 - // Add File Explorer tab with AssetsPanel 27 - AssetsPanel assetsPanel = new AssetsPanel(); 28 - tabbedPane.addTab(null, ThemedIcon.FOLDER_OPEN_16, assetsPanel); 27 + // Add File Explorer tab 28 + var explorer = new Explorer(); 29 + tabbedPane.addTab(null, ThemedIcon.FOLDER_OPEN_16, explorer); 29 30 30 31 // Add Logs tab with text area 31 32 logTextArea = createLogTextArea();
+72
src/main/java/app/pane/explorer/AssetItem.java
··· 1 + package app.pane.explorer; 2 + 3 + import java.awt.Image; 4 + import java.awt.datatransfer.Transferable; 5 + 6 + import javax.swing.ImageIcon; 7 + import javax.swing.SwingWorker; 8 + 9 + import assets.AssetHandle; 10 + import util.ui.ThemedIcon; 11 + 12 + class AssetItem extends Item 13 + { 14 + final AssetHandle asset; 15 + 16 + AssetItem(Explorer explorer, AssetHandle asset) 17 + { 18 + super(explorer, asset.getAssetName(), ThemedIcon.PACKAGE_24, false); 19 + this.asset = asset; 20 + 21 + new SwingWorker<Image, Void>() { 22 + @Override 23 + protected Image doInBackground() 24 + { 25 + return asset.getThumbnail(); 26 + } 27 + 28 + @Override 29 + protected void done() 30 + { 31 + try { 32 + Image thumb = get(); 33 + if (thumb != null) { 34 + setIcon(new ImageIcon(thumb)); 35 + checkerboard = asset.thumbnailHasCheckerboard(); 36 + } 37 + } 38 + catch (Exception e) { 39 + // ignore — keep default icon 40 + } 41 + } 42 + }.execute(); 43 + 44 + String desc = asset.getAssetDescription(); 45 + if (desc != null && !desc.isEmpty()) 46 + setToolTipText(desc); 47 + } 48 + 49 + @Override 50 + void onDoubleClick() 51 + { 52 + explorer.openAsset(asset); 53 + } 54 + 55 + @Override 56 + AssetHandle getAsset() 57 + { 58 + return asset; 59 + } 60 + 61 + @Override 62 + boolean isDraggable() 63 + { 64 + return true; 65 + } 66 + 67 + @Override 68 + Transferable createDragTransferable() 69 + { 70 + return new Explorer.AssetTransferable(asset); 71 + } 72 + }
+82
src/main/java/app/pane/explorer/DirectoryItem.java
··· 1 + package app.pane.explorer; 2 + 3 + import java.io.File; 4 + 5 + import java.awt.dnd.DnDConstants; 6 + import java.awt.dnd.DropTarget; 7 + import java.awt.dnd.DropTargetAdapter; 8 + import java.awt.dnd.DropTargetDragEvent; 9 + import java.awt.dnd.DropTargetDropEvent; 10 + import java.awt.dnd.DropTargetEvent; 11 + 12 + import app.SwingUtils; 13 + import assets.AssetHandle; 14 + import assets.AssetManager; 15 + import util.Logger; 16 + import util.ui.ThemedIcon; 17 + 18 + class DirectoryItem extends Item 19 + { 20 + private final String targetPath; 21 + 22 + DirectoryItem(Explorer explorer, String name, String targetPath) 23 + { 24 + super(explorer, name, ThemedIcon.FOLDER_OPEN_24, false); 25 + this.targetPath = targetPath; 26 + 27 + new DropTarget(this, DnDConstants.ACTION_MOVE, new DropTargetAdapter() { 28 + @Override 29 + public void dragEnter(DropTargetDragEvent dtde) 30 + { 31 + if (dtde.isDataFlavorSupported(AssetHandle.FLAVOUR)) { 32 + dtde.acceptDrag(DnDConstants.ACTION_MOVE); 33 + dropTarget = true; 34 + repaint(); 35 + } 36 + else { 37 + dtde.rejectDrag(); 38 + } 39 + } 40 + 41 + @Override 42 + public void dragExit(DropTargetEvent dte) 43 + { 44 + dropTarget = false; 45 + repaint(); 46 + } 47 + 48 + @Override 49 + public void drop(DropTargetDropEvent dtde) 50 + { 51 + dropTarget = false; 52 + repaint(); 53 + try { 54 + dtde.acceptDrop(DnDConstants.ACTION_MOVE); 55 + var asset = (AssetHandle) dtde.getTransferable().getTransferData(AssetHandle.FLAVOUR); 56 + 57 + File targetDir = new File(AssetManager.getTopLevelAssetDir(), targetPath); 58 + targetDir.mkdirs(); 59 + boolean ok = asset.moveAsset(targetDir); 60 + dtde.dropComplete(ok); 61 + 62 + if (!ok) { 63 + SwingUtils.getErrorDialog() 64 + .setTitle("Move Failed") 65 + .setMessage("Could not move " + asset.getName() + " to " + name + "/.") 66 + .show(); 67 + } 68 + } 69 + catch (Exception ex) { 70 + dtde.dropComplete(false); 71 + Logger.logError("Drop failed: " + ex.getMessage()); 72 + } 73 + } 74 + }); 75 + } 76 + 77 + @Override 78 + void onDoubleClick() 79 + { 80 + explorer.navigateTo(targetPath); 81 + } 82 + }
+357
src/main/java/app/pane/explorer/Explorer.java
··· 1 + package app.pane.explorer; 2 + 3 + import java.awt.Cursor; 4 + import java.awt.FlowLayout; 5 + import java.awt.Font; 6 + import java.awt.datatransfer.DataFlavor; 7 + import java.awt.datatransfer.Transferable; 8 + import java.awt.datatransfer.UnsupportedFlavorException; 9 + import java.awt.dnd.DnDConstants; 10 + import java.awt.dnd.DropTarget; 11 + import java.awt.dnd.DropTargetAdapter; 12 + import java.awt.dnd.DropTargetDragEvent; 13 + import java.awt.dnd.DropTargetDropEvent; 14 + import java.awt.dnd.DropTargetEvent; 15 + import java.awt.event.MouseAdapter; 16 + import java.awt.event.MouseEvent; 17 + import java.io.File; 18 + import java.io.IOException; 19 + import java.nio.file.FileSystems; 20 + import java.nio.file.StandardWatchEventKinds; 21 + import java.nio.file.WatchEvent; 22 + import java.nio.file.WatchKey; 23 + import java.nio.file.WatchService; 24 + import java.util.ArrayList; 25 + import java.util.List; 26 + 27 + import javax.swing.JLabel; 28 + import javax.swing.JPanel; 29 + import javax.swing.JScrollPane; 30 + import javax.swing.SwingUtilities; 31 + import javax.swing.UIManager; 32 + 33 + import app.Environment; 34 + import app.SwingUtils; 35 + import assets.AssetHandle; 36 + import assets.AssetManager; 37 + import assets.AssetManager.DirectoryListing; 38 + import net.miginfocom.swing.MigLayout; 39 + import util.Logger; 40 + import util.ui.UniformGridLayout; 41 + 42 + public class Explorer extends JPanel 43 + { 44 + private String currentPath = ""; 45 + private Item selectedItem; 46 + 47 + private JPanel breadcrumbBar; 48 + private JPanel resultsPanel; 49 + private JScrollPane scrollPane; 50 + 51 + private WatchService watchService; 52 + private Thread watchThread; 53 + private final List<WatchKey> watchKeys = new ArrayList<>(); 54 + 55 + public Explorer() 56 + { 57 + setLayout(new MigLayout("ins 4, fill", "[grow]", "[pref!][grow]")); 58 + 59 + breadcrumbBar = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); 60 + add(breadcrumbBar, "growx, wrap"); 61 + 62 + resultsPanel = new JPanel(new UniformGridLayout(Item.SIZE, Item.SIZE, 0, 0)); 63 + resultsPanel.addMouseListener(new MouseAdapter() { 64 + @Override 65 + public void mouseClicked(MouseEvent e) 66 + { 67 + clearSelection(); 68 + } 69 + }); 70 + scrollPane = new JScrollPane(resultsPanel); 71 + scrollPane.setBorder(null); 72 + add(scrollPane, "grow, push"); 73 + 74 + try { 75 + watchService = FileSystems.getDefault().newWatchService(); 76 + startWatchThread(); 77 + } 78 + catch (IOException e) { 79 + Logger.logError("Failed to create file watch service: " + e.getMessage()); 80 + } 81 + 82 + navigateTo(""); 83 + } 84 + 85 + // --- Navigation --- 86 + 87 + void navigateTo(String path) 88 + { 89 + currentPath = path; 90 + clearSelection(); 91 + rebuildBreadcrumb(); 92 + refresh(); 93 + registerWatchers(); 94 + } 95 + 96 + void select(Item item) 97 + { 98 + if (selectedItem != null) { 99 + selectedItem.cancelRename(); 100 + selectedItem.selected = false; 101 + selectedItem.repaint(); 102 + } 103 + 104 + selectedItem = item; 105 + item.selected = true; 106 + item.repaint(); 107 + rebuildBreadcrumb(); 108 + } 109 + 110 + private void clearSelection() 111 + { 112 + if (selectedItem != null) { 113 + selectedItem.cancelRename(); 114 + selectedItem.selected = false; 115 + selectedItem.repaint(); 116 + } 117 + selectedItem = null; 118 + rebuildBreadcrumb(); 119 + } 120 + 121 + void openAsset(AssetHandle asset) 122 + { 123 + // TODO 124 + } 125 + 126 + // --- Breadcrumb --- 127 + 128 + private void rebuildBreadcrumb() 129 + { 130 + breadcrumbBar.removeAll(); 131 + 132 + String projectName = Environment.getProject().getManifest().getName(); 133 + breadcrumbBar.add(createBreadcrumbLabel(projectName, "")); 134 + 135 + if (!currentPath.isEmpty()) { 136 + String[] parts = currentPath.split("/"); 137 + var pathSoFar = new StringBuilder(); 138 + for (String part : parts) { 139 + if (part.isEmpty()) 140 + continue; 141 + pathSoFar.append(part).append("/"); 142 + 143 + breadcrumbBar.add(createSeparatorLabel()); 144 + breadcrumbBar.add(createBreadcrumbLabel(part, pathSoFar.toString())); 145 + } 146 + } 147 + 148 + if (selectedItem != null) { 149 + breadcrumbBar.add(createSeparatorLabel()); 150 + 151 + var fileLabel = new JLabel(selectedItem.name); 152 + fileLabel.setFont(fileLabel.getFont().deriveFont(Font.BOLD)); 153 + breadcrumbBar.add(fileLabel); 154 + } 155 + 156 + breadcrumbBar.revalidate(); 157 + breadcrumbBar.repaint(); 158 + } 159 + 160 + private JLabel createBreadcrumbLabel(String text, String targetPath) 161 + { 162 + var label = new JLabel(text); 163 + label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 164 + final Font normalFont = label.getFont(); 165 + 166 + label.addMouseListener(new MouseAdapter() { 167 + @Override 168 + public void mouseClicked(MouseEvent e) 169 + { 170 + navigateTo(targetPath); 171 + } 172 + }); 173 + 174 + new DropTarget(label, DnDConstants.ACTION_MOVE, new DropTargetAdapter() { 175 + @Override 176 + public void dragEnter(DropTargetDragEvent dtde) 177 + { 178 + if (dtde.isDataFlavorSupported(AssetHandle.FLAVOUR)) { 179 + dtde.acceptDrag(DnDConstants.ACTION_MOVE); 180 + label.setFont(normalFont.deriveFont(Font.BOLD)); 181 + } 182 + else { 183 + dtde.rejectDrag(); 184 + } 185 + } 186 + 187 + @Override 188 + public void dragExit(DropTargetEvent dte) 189 + { 190 + label.setFont(normalFont); 191 + } 192 + 193 + @Override 194 + public void drop(DropTargetDropEvent dtde) 195 + { 196 + label.setFont(normalFont); 197 + try { 198 + dtde.acceptDrop(DnDConstants.ACTION_MOVE); 199 + var asset = (AssetHandle) dtde.getTransferable().getTransferData(AssetHandle.FLAVOUR); 200 + 201 + if (asset.assetPath.startsWith(targetPath) && !asset.assetPath.substring(targetPath.length()).contains("/")) { 202 + dtde.dropComplete(false); 203 + return; 204 + } 205 + 206 + File targetDir = new File(AssetManager.getTopLevelAssetDir(), targetPath); 207 + targetDir.mkdirs(); 208 + boolean ok = asset.moveAsset(targetDir); 209 + dtde.dropComplete(ok); 210 + 211 + if (!ok) { 212 + SwingUtils.getErrorDialog() 213 + .setTitle("Move Failed") 214 + .setMessage("Could not move " + asset.getName() + ".") 215 + .show(); 216 + } 217 + } 218 + catch (Exception ex) { 219 + dtde.dropComplete(false); 220 + Logger.logError("Drop failed: " + ex.getMessage()); 221 + } 222 + } 223 + }); 224 + 225 + return label; 226 + } 227 + 228 + private JLabel createSeparatorLabel() 229 + { 230 + var sep = new JLabel(" / "); 231 + sep.setForeground(UIManager.getColor("Label.disabledForeground")); 232 + return sep; 233 + } 234 + 235 + // --- Results --- 236 + 237 + private void refresh() 238 + { 239 + resultsPanel.removeAll(); 240 + 241 + DirectoryListing listing = AssetManager.listDirectory(currentPath); 242 + 243 + for (String subdirName : listing.subdirectories()) 244 + resultsPanel.add(new DirectoryItem(this, subdirName, currentPath + subdirName + "/")); 245 + 246 + for (AssetHandle asset : listing.files()) 247 + resultsPanel.add(new AssetItem(this, asset)); 248 + 249 + resultsPanel.revalidate(); 250 + resultsPanel.repaint(); 251 + } 252 + 253 + // --- File watching --- 254 + 255 + private void registerWatchers() 256 + { 257 + if (watchService == null) 258 + return; 259 + 260 + for (WatchKey key : watchKeys) 261 + key.cancel(); 262 + watchKeys.clear(); 263 + 264 + for (File dir : AssetManager.getStackDirsForPath(currentPath)) { 265 + try { 266 + WatchKey key = dir.toPath().register(watchService, 267 + StandardWatchEventKinds.ENTRY_CREATE, 268 + StandardWatchEventKinds.ENTRY_DELETE, 269 + StandardWatchEventKinds.ENTRY_MODIFY); 270 + watchKeys.add(key); 271 + } 272 + catch (IOException e) { 273 + Logger.logError("Failed to watch directory: " + dir); 274 + } 275 + } 276 + } 277 + 278 + private void startWatchThread() 279 + { 280 + watchThread = new Thread(() -> { 281 + while (!Thread.interrupted()) { 282 + try { 283 + WatchKey key = watchService.take(); 284 + 285 + for (WatchEvent<?> event : key.pollEvents()) { 286 + // just need to know something changed 287 + } 288 + key.reset(); 289 + 290 + SwingUtilities.invokeLater(this::refresh); 291 + 292 + // Debounce: coalesce further events within 200ms 293 + Thread.sleep(200); 294 + 295 + WatchKey extra; 296 + while ((extra = watchService.poll()) != null) { 297 + extra.pollEvents(); 298 + extra.reset(); 299 + SwingUtilities.invokeLater(this::refresh); 300 + } 301 + } 302 + catch (InterruptedException e) { 303 + Thread.currentThread().interrupt(); 304 + break; 305 + } 306 + } 307 + }, "Explorer-FileWatcher"); 308 + watchThread.setDaemon(true); 309 + watchThread.start(); 310 + } 311 + 312 + public void dispose() 313 + { 314 + if (watchThread != null) 315 + watchThread.interrupt(); 316 + if (watchService != null) { 317 + try { 318 + watchService.close(); 319 + } 320 + catch (IOException e) { 321 + // ignore on shutdown 322 + } 323 + } 324 + } 325 + 326 + // --- Drag and drop --- 327 + 328 + static class AssetTransferable implements Transferable 329 + { 330 + private final AssetHandle asset; 331 + 332 + AssetTransferable(AssetHandle asset) 333 + { 334 + this.asset = asset; 335 + } 336 + 337 + @Override 338 + public DataFlavor[] getTransferDataFlavors() 339 + { 340 + return new DataFlavor[] { AssetHandle.FLAVOUR }; 341 + } 342 + 343 + @Override 344 + public boolean isDataFlavorSupported(DataFlavor flavor) 345 + { 346 + return AssetHandle.FLAVOUR.equals(flavor); 347 + } 348 + 349 + @Override 350 + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException 351 + { 352 + if (!isDataFlavorSupported(flavor)) 353 + throw new UnsupportedFlavorException(flavor); 354 + return asset; 355 + } 356 + } 357 + }
+352
src/main/java/app/pane/explorer/Item.java
··· 1 + package app.pane.explorer; 2 + 3 + import java.awt.BasicStroke; 4 + import java.awt.Color; 5 + import java.awt.Dimension; 6 + import java.awt.FontMetrics; 7 + import java.awt.Graphics; 8 + import java.awt.Graphics2D; 9 + import java.awt.Insets; 10 + import java.awt.Rectangle; 11 + import java.awt.RenderingHints; 12 + import java.awt.TexturePaint; 13 + import java.awt.datatransfer.Transferable; 14 + import java.awt.dnd.DnDConstants; 15 + import java.awt.dnd.DragGestureEvent; 16 + import java.awt.dnd.DragSource; 17 + import java.awt.event.ActionEvent; 18 + import java.awt.event.FocusAdapter; 19 + import java.awt.event.FocusEvent; 20 + import java.awt.event.KeyEvent; 21 + import java.awt.event.MouseAdapter; 22 + import java.awt.event.MouseEvent; 23 + import java.awt.image.BufferedImage; 24 + 25 + import javax.swing.AbstractAction; 26 + import javax.swing.BorderFactory; 27 + import javax.swing.Icon; 28 + import javax.swing.JMenuItem; 29 + import javax.swing.JOptionPane; 30 + import javax.swing.JPanel; 31 + import javax.swing.JPopupMenu; 32 + import javax.swing.JTextField; 33 + import javax.swing.KeyStroke; 34 + import javax.swing.SwingUtilities; 35 + import javax.swing.UIManager; 36 + 37 + import app.SwingUtils; 38 + import assets.AssetHandle; 39 + 40 + abstract class Item extends JPanel 41 + { 42 + static final int PADDING = 2; 43 + static final int SIZE = AssetHandle.THUMBNAIL_WIDTH + (4 + PADDING) * 2; 44 + 45 + final Explorer explorer; 46 + final String name; 47 + 48 + private Icon icon; 49 + protected boolean checkerboard; 50 + boolean selected; 51 + boolean dropTarget; 52 + private JTextField renameField; 53 + 54 + private static final JPopupMenu contextMenu = buildContextMenu(); 55 + private static Item popupItem; 56 + 57 + Item(Explorer explorer, String name, Icon defaultIcon, boolean checkerboard) 58 + { 59 + this.explorer = explorer; 60 + this.name = name; 61 + this.icon = defaultIcon; 62 + this.checkerboard = checkerboard; 63 + 64 + setLayout(null); 65 + setPreferredSize(new Dimension(SIZE, SIZE)); 66 + setOpaque(false); 67 + setBorder(BorderFactory.createEmptyBorder(4 + PADDING, 4 + PADDING, 4 + PADDING, 4 + PADDING)); 68 + 69 + var adapter = new MouseAdapter() { 70 + @Override 71 + public void mousePressed(MouseEvent e) 72 + { 73 + if (e.isPopupTrigger()) 74 + showContextMenu(e); 75 + } 76 + 77 + @Override 78 + public void mouseReleased(MouseEvent e) 79 + { 80 + if (e.isPopupTrigger()) 81 + showContextMenu(e); 82 + } 83 + 84 + @Override 85 + public void mouseClicked(MouseEvent e) 86 + { 87 + if (SwingUtilities.isLeftMouseButton(e)) { 88 + if (e.getClickCount() == 1) 89 + explorer.select(Item.this); 90 + else if (e.getClickCount() == 2) 91 + onDoubleClick(); 92 + } 93 + } 94 + }; 95 + addMouseListener(adapter); 96 + 97 + if (isDraggable()) { 98 + var dragSource = new DragSource(); 99 + dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_MOVE, (DragGestureEvent dge) -> { 100 + Transferable transferable = createDragTransferable(); 101 + if (transferable != null) 102 + dge.startDrag(DragSource.DefaultMoveDrop, transferable); 103 + }); 104 + } 105 + } 106 + 107 + void setIcon(Icon icon) 108 + { 109 + this.icon = icon; 110 + repaint(); 111 + } 112 + 113 + abstract void onDoubleClick(); 114 + 115 + boolean isDraggable() 116 + { 117 + return false; 118 + } 119 + 120 + Transferable createDragTransferable() 121 + { 122 + return null; 123 + } 124 + 125 + /** Override to return the asset for context menu operations. */ 126 + AssetHandle getAsset() 127 + { 128 + return null; 129 + } 130 + 131 + private void showContextMenu(MouseEvent e) 132 + { 133 + AssetHandle asset = getAsset(); 134 + if (asset == null) 135 + return; 136 + 137 + explorer.select(this); 138 + popupItem = this; 139 + contextMenu.show(this, e.getX(), e.getY()); 140 + } 141 + 142 + private static JPopupMenu buildContextMenu() 143 + { 144 + var menu = new JPopupMenu(); 145 + 146 + var renameItem = new JMenuItem("Rename"); 147 + renameItem.addActionListener(e -> { 148 + if (popupItem != null) 149 + popupItem.onRename(); 150 + }); 151 + menu.add(renameItem); 152 + 153 + var deleteItem = new JMenuItem("Delete"); 154 + deleteItem.addActionListener(e -> { 155 + if (popupItem != null) 156 + popupItem.onDelete(); 157 + }); 158 + menu.add(deleteItem); 159 + 160 + return menu; 161 + } 162 + 163 + void cancelRename() 164 + { 165 + if (renameField == null) 166 + return; 167 + remove(renameField); 168 + renameField = null; 169 + repaint(); 170 + } 171 + 172 + private void onRename() 173 + { 174 + AssetHandle asset = getAsset(); 175 + if (asset == null || renameField != null) 176 + return; 177 + 178 + String currentName = asset.getAssetName(); 179 + 180 + Insets ins = getInsets(); 181 + int x = ins.left; 182 + int w = getWidth() - ins.left - ins.right; 183 + int h = getHeight() - ins.top - ins.bottom; 184 + int iconAreaH = h * 4 / 5; 185 + int labelY = ins.top + iconAreaH; 186 + int labelH = h - iconAreaH; 187 + 188 + renameField = new JTextField(currentName); 189 + renameField.setHorizontalAlignment(JTextField.CENTER); 190 + renameField.setBounds(x, labelY, w, labelH); 191 + renameField.selectAll(); 192 + 193 + Runnable commit = () -> { 194 + if (renameField == null) 195 + return; 196 + String newName = renameField.getText().trim(); 197 + cancelRename(); 198 + 199 + if (newName.isEmpty() || newName.equals(currentName) 200 + || newName.contains("/") || newName.contains("\\")) 201 + return; 202 + 203 + if (!asset.renameAsset(newName)) { 204 + SwingUtils.getErrorDialog() 205 + .setTitle("Rename Failed") 206 + .setMessage("Could not rename " + currentName + " to " + newName + ".") 207 + .show(); 208 + } 209 + }; 210 + 211 + renameField.addActionListener(e -> commit.run()); 212 + 213 + renameField.addFocusListener(new FocusAdapter() { 214 + @Override 215 + public void focusLost(FocusEvent e) 216 + { 217 + commit.run(); 218 + } 219 + }); 220 + 221 + renameField.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel"); 222 + renameField.getActionMap().put("cancel", new AbstractAction() { 223 + @Override 224 + public void actionPerformed(ActionEvent e) 225 + { 226 + cancelRename(); 227 + } 228 + }); 229 + 230 + add(renameField); 231 + revalidate(); 232 + renameField.requestFocusInWindow(); 233 + } 234 + 235 + private void onDelete() 236 + { 237 + AssetHandle asset = getAsset(); 238 + if (asset == null) 239 + return; 240 + 241 + String assetName = asset.getAssetName(); 242 + int result = SwingUtils.getConfirmDialog() 243 + .setTitle("Delete") 244 + .setMessage("Delete " + assetName + "?") 245 + .setOptionsType(JOptionPane.YES_NO_OPTION) 246 + .choose(); 247 + 248 + if (result != JOptionPane.YES_OPTION) 249 + return; 250 + 251 + if (!asset.deleteAsset()) { 252 + SwingUtils.getErrorDialog() 253 + .setTitle("Delete Failed") 254 + .setMessage("Could not delete " + assetName + ".") 255 + .show(); 256 + } 257 + } 258 + 259 + @Override 260 + protected void paintComponent(Graphics g) 261 + { 262 + var g2 = (Graphics2D) g.create(); 263 + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 264 + 265 + // Selected background 266 + if (selected) { 267 + g2.setColor(UIManager.getColor("Component.borderColor")); 268 + g2.fillRoundRect(0, 0, getWidth(), getHeight(), 8, 8); 269 + } 270 + 271 + Insets ins = getInsets(); 272 + int x = ins.left; 273 + int y = ins.top; 274 + int w = getWidth() - ins.left - ins.right; 275 + int h = getHeight() - ins.top - ins.bottom; 276 + 277 + int iconAreaH = h * 4 / 5; 278 + int labelAreaH = h - iconAreaH; 279 + 280 + // Icon 281 + if (icon != null) { 282 + int iconW = icon.getIconWidth(); 283 + int iconH = icon.getIconHeight(); 284 + int iconX = x + (w - iconW) / 2; 285 + int iconY = y + (iconAreaH - iconH) / 2; 286 + 287 + if (checkerboard) 288 + paintCheckerboard(g2, iconX, iconY, iconW, iconH); 289 + 290 + icon.paintIcon(this, g2, iconX, iconY); 291 + } 292 + 293 + // Name label with ellipsis (hidden during inline rename) 294 + if (renameField == null) { 295 + g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 296 + g2.setColor(UIManager.getColor("Label.foreground")); 297 + g2.setFont(getFont()); 298 + FontMetrics fm = g2.getFontMetrics(); 299 + 300 + String displayText = name; 301 + int textW = fm.stringWidth(displayText); 302 + if (textW > w) { 303 + String ellipsis = "..."; 304 + int ellipsisW = fm.stringWidth(ellipsis); 305 + int maxW = w - ellipsisW; 306 + int len = displayText.length(); 307 + while (len > 0 && fm.stringWidth(displayText.substring(0, len)) > maxW) 308 + len--; 309 + displayText = displayText.substring(0, len) + ellipsis; 310 + textW = fm.stringWidth(displayText); 311 + } 312 + 313 + int textX = x + (w - textW) / 2; 314 + int textY = y + iconAreaH + (labelAreaH + fm.getAscent() - fm.getDescent()) / 2; 315 + g2.drawString(displayText, textX, textY); 316 + } 317 + 318 + // Drop target highlight 319 + if (dropTarget) { 320 + g2.setColor(UIManager.getColor("Component.focusColor")); 321 + g2.setStroke(new BasicStroke(2)); 322 + g2.drawRoundRect(1, 1, getWidth() - 3, getHeight() - 3, 8, 8); 323 + } 324 + 325 + g2.dispose(); 326 + } 327 + 328 + private static void paintCheckerboard(Graphics2D g2, int x, int y, int w, int h) 329 + { 330 + Color panelBg = UIManager.getColor("Panel.background"); 331 + boolean dark = panelBg != null && luminance(panelBg) < 0.5f; 332 + Color c1 = dark ? new Color(0x3C3C3C) : new Color(0xCCCCCC); 333 + Color c2 = dark ? new Color(0x2C2C2C) : new Color(0xFFFFFF); 334 + int cs = 4; 335 + var tile = new BufferedImage(cs * 2, cs * 2, BufferedImage.TYPE_INT_RGB); 336 + Graphics2D tg = tile.createGraphics(); 337 + tg.setColor(c1); 338 + tg.fillRect(0, 0, cs, cs); 339 + tg.fillRect(cs, cs, cs, cs); 340 + tg.setColor(c2); 341 + tg.fillRect(cs, 0, cs, cs); 342 + tg.fillRect(0, cs, cs, cs); 343 + tg.dispose(); 344 + g2.setPaint(new TexturePaint(tile, new Rectangle(x, y, cs * 2, cs * 2))); 345 + g2.fillRect(x, y, w, h); 346 + } 347 + 348 + private static float luminance(Color c) 349 + { 350 + return (0.299f * c.getRed() + 0.587f * c.getGreen() + 0.114f * c.getBlue()) / 255f; 351 + } 352 + }
+58
src/main/java/assets/AssetHandle.java
··· 3 3 import java.awt.Graphics2D; 4 4 import java.awt.Image; 5 5 import java.awt.RenderingHints; 6 + import java.awt.datatransfer.DataFlavor; 6 7 import java.awt.image.BaseMultiResolutionImage; 7 8 import java.awt.image.BufferedImage; 8 9 import java.io.File; 10 + import java.io.IOException; 11 + import java.nio.file.Files; 12 + 13 + import org.apache.commons.io.FileUtils; 9 14 10 15 import assets.ui.BackgroundAsset; 11 16 import assets.ui.MapAsset; ··· 16 21 public static final int THUMBNAIL_WIDTH = 80; 17 22 public static final int THUMBNAIL_HEIGHT = 50; 18 23 24 + public static final DataFlavor FLAVOUR; 25 + static { 26 + try { 27 + FLAVOUR = new DataFlavor( 28 + DataFlavor.javaJVMLocalObjectMimeType + ";class=\"" + AssetHandle.class.getName() + "\""); 29 + } 30 + catch (ClassNotFoundException e) { 31 + throw new RuntimeException(e); 32 + } 33 + } 34 + 19 35 public final File assetDir; 20 36 public final String assetPath; // relative path from assetDir 21 37 ··· 45 61 public String getAssetDescription() 46 62 { 47 63 return null; 64 + } 65 + 66 + /** Deletes this asset from disk. */ 67 + public boolean deleteAsset() 68 + { 69 + return FileUtils.deleteQuietly(this); 70 + } 71 + 72 + /** Renames this asset within its current directory. */ 73 + public boolean renameAsset(String newAssetName) 74 + { 75 + // Preserve file extension 76 + String oldName = getName(); 77 + int dot = oldName.lastIndexOf('.'); 78 + if (dot > 0) 79 + newAssetName += oldName.substring(dot); 80 + 81 + File newFile = new File(getParentFile(), newAssetName); 82 + if (newFile.exists()) 83 + return false; 84 + try { 85 + Files.move(toPath(), newFile.toPath()); 86 + return true; 87 + } 88 + catch (IOException e) { 89 + return false; 90 + } 91 + } 92 + 93 + /** Moves this asset to a different directory. */ 94 + public boolean moveAsset(File targetDir) 95 + { 96 + File targetFile = new File(targetDir, getName()); 97 + if (targetFile.exists()) 98 + return false; 99 + try { 100 + Files.move(toPath(), targetFile.toPath()); 101 + return true; 102 + } 103 + catch (IOException e) { 104 + return false; 105 + } 48 106 } 49 107 50 108 /** Whether to paint a checkerboard behind the thumbnail for transparency. */
+1
src/main/java/assets/AssetManager.java
··· 70 70 } 71 71 } 72 72 73 + 73 74 public static AssetHandle getTextureArchive(String texName) 74 75 { 75 76 return get(AssetSubdir.MAP_TEX, texName + EXT_NEW_TEX);
+28
src/main/java/assets/ui/MapAsset.java
··· 7 7 import java.io.File; 8 8 import java.io.FileReader; 9 9 import java.io.IOException; 10 + import java.nio.file.Files; 10 11 import java.util.regex.Matcher; 11 12 import java.util.regex.Pattern; 12 13 ··· 81 82 @Override 82 83 public String getAssetDescription() { 83 84 return desc; 85 + } 86 + 87 + @Override 88 + public boolean deleteAsset() 89 + { 90 + File thumbFile = new File(PROJ_THUMBNAIL + assetPath + ".png"); 91 + if (thumbFile.exists()) 92 + thumbFile.delete(); 93 + return super.deleteAsset(); 94 + } 95 + 96 + @Override 97 + public boolean renameAsset(String newFileName) 98 + { 99 + File oldThumb = new File(PROJ_THUMBNAIL + assetPath + ".png"); 100 + if (oldThumb.exists()) { 101 + try { 102 + String newAssetPath = assetPath.substring(0, assetPath.lastIndexOf('/') + 1) + newFileName; 103 + File newThumb = new File(PROJ_THUMBNAIL + newAssetPath + ".png"); 104 + newThumb.getParentFile().mkdirs(); 105 + Files.move(oldThumb.toPath(), newThumb.toPath()); 106 + } 107 + catch (IOException e) { 108 + return false; 109 + } 110 + } 111 + return super.renameAsset(newFileName); 84 112 } 85 113 86 114 @Override
+49
src/main/java/assets/ui/TexturesAsset.java
··· 15 15 16 16 import javax.imageio.ImageIO; 17 17 18 + import org.apache.commons.io.FileUtils; 18 19 import org.apache.commons.io.FilenameUtils; 19 20 20 21 import assets.AssetHandle; ··· 63 64 catch (IOException e) { 64 65 Logger.logError("IOException while gathering previews from " + dirName); 65 66 } 67 + } 68 + 69 + private File getCompanionDir() 70 + { 71 + String dirName = FilenameUtils.getBaseName(getName()) + "/"; 72 + File dir = new File(assetDir, AssetSubdir.MAP_TEX + dirName); 73 + return dir.isDirectory() ? dir : null; 74 + } 75 + 76 + @Override 77 + public boolean deleteAsset() 78 + { 79 + File dir = getCompanionDir(); 80 + if (dir != null) 81 + FileUtils.deleteQuietly(dir); 82 + return super.deleteAsset(); 83 + } 84 + 85 + @Override 86 + public boolean renameAsset(String newFileName) 87 + { 88 + File dir = getCompanionDir(); 89 + if (dir != null) { 90 + try { 91 + String newDirName = FilenameUtils.getBaseName(newFileName); 92 + File newDir = new File(dir.getParentFile(), newDirName); 93 + Files.move(dir.toPath(), newDir.toPath()); 94 + } 95 + catch (IOException e) { 96 + return false; 97 + } 98 + } 99 + return super.renameAsset(newFileName); 100 + } 101 + 102 + @Override 103 + public boolean moveAsset(File targetDir) 104 + { 105 + File dir = getCompanionDir(); 106 + if (dir != null) { 107 + try { 108 + FileUtils.moveDirectory(dir, new File(targetDir, dir.getName())); 109 + } 110 + catch (IOException e) { 111 + return false; 112 + } 113 + } 114 + return super.moveAsset(targetDir); 66 115 } 67 116 68 117 @Override