this repo has no description
1
fork

Configure Feed

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

add directories to AssetsPanel

+458 -98
+392
src/main/java/app/pane/AssetsPanel.java
··· 1 + package app.pane; 2 + 3 + import java.awt.Color; 4 + import java.awt.Cursor; 5 + import java.awt.Dimension; 6 + import java.awt.FlowLayout; 7 + import java.awt.Font; 8 + import java.awt.Graphics; 9 + import java.awt.Graphics2D; 10 + import java.awt.RenderingHints; 11 + import java.awt.event.MouseAdapter; 12 + import java.awt.event.MouseEvent; 13 + import java.io.File; 14 + import java.io.IOException; 15 + import java.nio.file.FileSystems; 16 + import java.nio.file.Path; 17 + import java.nio.file.StandardWatchEventKinds; 18 + import java.nio.file.WatchEvent; 19 + import java.nio.file.WatchKey; 20 + import java.nio.file.WatchService; 21 + import java.util.ArrayList; 22 + import java.util.List; 23 + 24 + import javax.swing.JLabel; 25 + import javax.swing.JPanel; 26 + import javax.swing.JScrollPane; 27 + import javax.swing.SwingUtilities; 28 + import javax.swing.UIManager; 29 + 30 + import app.Environment; 31 + import assets.AssetHandle; 32 + import assets.AssetManager; 33 + import assets.AssetManager.DirectoryListing; 34 + import net.miginfocom.swing.MigLayout; 35 + import util.Logger; 36 + import util.ui.ThemedIcon; 37 + import util.ui.WrapLayout; 38 + 39 + public class AssetsPanel extends JPanel 40 + { 41 + private String currentPath = ""; 42 + private AssetHandle selectedAsset; 43 + private JPanel selectedPanel; 44 + 45 + private JPanel breadcrumbBar; 46 + private JPanel resultsPanel; 47 + private JScrollPane scrollPane; 48 + 49 + private WatchService watchService; 50 + private Thread watchThread; 51 + private List<WatchKey> watchKeys = new ArrayList<>(); 52 + 53 + public AssetsPanel() 54 + { 55 + setLayout(new MigLayout("ins 4, fill", "[grow]", "[pref!][grow]")); 56 + 57 + breadcrumbBar = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); 58 + add(breadcrumbBar, "growx, wrap"); 59 + 60 + resultsPanel = new JPanel(new WrapLayout(FlowLayout.LEFT, 0, 0)); 61 + scrollPane = new JScrollPane(resultsPanel); 62 + scrollPane.setBorder(null); 63 + add(scrollPane, "grow, push"); 64 + 65 + try { 66 + watchService = FileSystems.getDefault().newWatchService(); 67 + startWatchThread(); 68 + } 69 + catch (IOException e) { 70 + Logger.logError("Failed to create file watch service: " + e.getMessage()); 71 + } 72 + 73 + navigateTo(""); 74 + } 75 + 76 + // --- Navigation --- 77 + 78 + private void navigateTo(String path) 79 + { 80 + currentPath = path; 81 + selectedAsset = null; 82 + selectedPanel = null; 83 + rebuildBreadcrumb(); 84 + refresh(); 85 + registerWatchers(); 86 + } 87 + 88 + private void selectAsset(AssetHandle asset, JPanel panel) 89 + { 90 + // Deselect previous 91 + if (selectedPanel != null) { 92 + selectedPanel.repaint(); 93 + } 94 + 95 + selectedAsset = asset; 96 + selectedPanel = panel; 97 + panel.repaint(); 98 + rebuildBreadcrumb(); 99 + } 100 + 101 + // --- Breadcrumb --- 102 + 103 + private void rebuildBreadcrumb() 104 + { 105 + breadcrumbBar.removeAll(); 106 + 107 + // Project name as root 108 + String projectName = Environment.getProject().getManifest().getName(); 109 + JLabel rootLabel = createClickableLabel(projectName); 110 + rootLabel.addMouseListener(new MouseAdapter() { 111 + @Override 112 + public void mouseClicked(MouseEvent e) 113 + { 114 + navigateTo(""); 115 + } 116 + }); 117 + breadcrumbBar.add(rootLabel); 118 + 119 + // Path components 120 + if (!currentPath.isEmpty()) { 121 + String[] parts = currentPath.split("/"); 122 + StringBuilder pathSoFar = new StringBuilder(); 123 + for (String part : parts) { 124 + if (part.isEmpty()) 125 + continue; 126 + pathSoFar.append(part).append("/"); 127 + 128 + breadcrumbBar.add(createSeparatorLabel()); 129 + 130 + String targetPath = pathSoFar.toString(); 131 + JLabel partLabel = createClickableLabel(part); 132 + partLabel.addMouseListener(new MouseAdapter() { 133 + @Override 134 + public void mouseClicked(MouseEvent e) 135 + { 136 + navigateTo(targetPath); 137 + } 138 + }); 139 + breadcrumbBar.add(partLabel); 140 + } 141 + } 142 + 143 + // Selected asset filename 144 + if (selectedAsset != null) { 145 + breadcrumbBar.add(createSeparatorLabel()); 146 + 147 + JLabel fileLabel = new JLabel(selectedAsset.getAssetName()); 148 + fileLabel.setFont(fileLabel.getFont().deriveFont(Font.BOLD)); 149 + breadcrumbBar.add(fileLabel); 150 + } 151 + 152 + breadcrumbBar.revalidate(); 153 + breadcrumbBar.repaint(); 154 + } 155 + 156 + private JLabel createClickableLabel(String text) 157 + { 158 + JLabel label = new JLabel(text); 159 + label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 160 + return label; 161 + } 162 + 163 + private JLabel createSeparatorLabel() 164 + { 165 + JLabel sep = new JLabel(" / "); 166 + sep.setForeground(UIManager.getColor("Label.disabledForeground")); 167 + return sep; 168 + } 169 + 170 + // --- Results --- 171 + 172 + private void refresh() 173 + { 174 + resultsPanel.removeAll(); 175 + 176 + DirectoryListing listing = AssetManager.listDirectory(currentPath); 177 + 178 + // Subdirectories first 179 + for (String subdirName : listing.subdirectories()) { 180 + resultsPanel.add(createSubdirItem(subdirName)); 181 + } 182 + 183 + // Then files 184 + for (AssetHandle asset : listing.files()) { 185 + resultsPanel.add(createAssetItem(asset)); 186 + } 187 + 188 + resultsPanel.revalidate(); 189 + resultsPanel.repaint(); 190 + } 191 + 192 + private JPanel createSubdirItem(String name) 193 + { 194 + JPanel panel = createItemPanel(); 195 + 196 + JLabel icon = new JLabel(ThemedIcon.FOLDER_OPEN_24); 197 + JLabel label = new JLabel(name); 198 + label.setPreferredSize(new Dimension(80, 20)); 199 + 200 + panel.add(icon, "wrap, align center"); 201 + panel.add(label, "align center, wmax 80"); 202 + 203 + panel.addMouseListener(new MouseAdapter() { 204 + @Override 205 + public void mouseEntered(MouseEvent e) 206 + { 207 + panel.putClientProperty("hovered", true); 208 + panel.repaint(); 209 + } 210 + 211 + @Override 212 + public void mouseExited(MouseEvent e) 213 + { 214 + panel.putClientProperty("hovered", false); 215 + panel.repaint(); 216 + } 217 + 218 + @Override 219 + public void mouseClicked(MouseEvent e) 220 + { 221 + if (e.getClickCount() == 2) { 222 + navigateTo(currentPath + name + "/"); 223 + } 224 + } 225 + }); 226 + 227 + return panel; 228 + } 229 + 230 + private JPanel createAssetItem(AssetHandle asset) 231 + { 232 + JPanel panel = createItemPanel(); 233 + 234 + JLabel icon = new JLabel(ThemedIcon.PACKAGE_24); 235 + 236 + JLabel name = new JLabel(asset.getAssetName()); 237 + name.setPreferredSize(new Dimension(80, 20)); 238 + 239 + panel.add(icon, "wrap, align center"); 240 + panel.add(name, "align center, wmax 80"); 241 + 242 + String desc = asset.getAssetDescription(); 243 + if (desc != null && !desc.isEmpty()) { 244 + panel.setToolTipText(desc); 245 + } 246 + 247 + panel.addMouseListener(new MouseAdapter() { 248 + @Override 249 + public void mouseEntered(MouseEvent e) 250 + { 251 + panel.putClientProperty("hovered", true); 252 + panel.repaint(); 253 + } 254 + 255 + @Override 256 + public void mouseExited(MouseEvent e) 257 + { 258 + panel.putClientProperty("hovered", false); 259 + panel.repaint(); 260 + } 261 + 262 + @Override 263 + public void mouseClicked(MouseEvent e) 264 + { 265 + if (e.getClickCount() == 1) { 266 + selectAsset(asset, panel); 267 + } 268 + else if (e.getClickCount() == 2) { 269 + openAsset(asset); 270 + } 271 + } 272 + }); 273 + 274 + return panel; 275 + } 276 + 277 + private JPanel createItemPanel() 278 + { 279 + JPanel panel = new JPanel(new MigLayout("ins 4, fill")) { 280 + @Override 281 + protected void paintComponent(Graphics g) 282 + { 283 + boolean hovered = Boolean.TRUE.equals(getClientProperty("hovered")); 284 + boolean selected = (this == selectedPanel); 285 + 286 + if (selected || hovered) { 287 + Graphics2D g2 = (Graphics2D) g.create(); 288 + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 289 + 290 + Color bg = UIManager.getColor("Table.selectionBackground"); 291 + if (selected) { 292 + g2.setColor(bg); 293 + } 294 + else { 295 + // Lighter for hover 296 + g2.setColor(new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 80)); 297 + } 298 + g2.fillRoundRect(0, 0, getWidth(), getHeight(), 8, 8); 299 + g2.dispose(); 300 + } 301 + 302 + super.paintComponent(g); 303 + } 304 + }; 305 + panel.setPreferredSize(new Dimension(80, 80)); 306 + panel.setOpaque(false); 307 + return panel; 308 + } 309 + 310 + private void openAsset(AssetHandle asset) 311 + { 312 + // TODO 313 + } 314 + 315 + // --- File watching --- 316 + 317 + private void registerWatchers() 318 + { 319 + if (watchService == null) 320 + return; 321 + 322 + // Cancel existing keys 323 + for (WatchKey key : watchKeys) { 324 + key.cancel(); 325 + } 326 + watchKeys.clear(); 327 + 328 + // Register all stack dirs for current path 329 + for (File dir : AssetManager.getStackDirsForPath(currentPath)) { 330 + try { 331 + WatchKey key = dir.toPath().register(watchService, 332 + StandardWatchEventKinds.ENTRY_CREATE, 333 + StandardWatchEventKinds.ENTRY_DELETE, 334 + StandardWatchEventKinds.ENTRY_MODIFY); 335 + watchKeys.add(key); 336 + } 337 + catch (IOException e) { 338 + Logger.logError("Failed to watch directory: " + dir); 339 + } 340 + } 341 + } 342 + 343 + private void startWatchThread() 344 + { 345 + watchThread = new Thread(() -> { 346 + while (!Thread.interrupted()) { 347 + try { 348 + WatchKey key = watchService.take(); 349 + 350 + // Drain events 351 + for (WatchEvent<?> event : key.pollEvents()) { 352 + // just need to know something changed 353 + } 354 + key.reset(); 355 + 356 + // Debounce: wait a bit for rapid changes to settle 357 + Thread.sleep(200); 358 + 359 + // Drain any additional events that arrived during debounce 360 + WatchKey extra; 361 + while ((extra = watchService.poll()) != null) { 362 + extra.pollEvents(); 363 + extra.reset(); 364 + } 365 + 366 + SwingUtilities.invokeLater(this::refresh); 367 + } 368 + catch (InterruptedException e) { 369 + Thread.currentThread().interrupt(); 370 + break; 371 + } 372 + } 373 + }, "AssetsPanel-FileWatcher"); 374 + watchThread.setDaemon(true); 375 + watchThread.start(); 376 + } 377 + 378 + public void dispose() 379 + { 380 + if (watchThread != null) { 381 + watchThread.interrupt(); 382 + } 383 + if (watchService != null) { 384 + try { 385 + watchService.close(); 386 + } 387 + catch (IOException e) { 388 + // ignore on shutdown 389 + } 390 + } 391 + } 392 + }
-1
src/main/java/app/pane/Dock.java
··· 9 9 import javax.swing.JTextArea; 10 10 11 11 import util.Logger; 12 - import util.ui.AssetsPanel; 13 12 import util.ui.ThemedIcon; 14 13 15 14 public class Dock extends JPanel
+66
src/main/java/assets/AssetManager.java
··· 7 7 import java.nio.file.DirectoryStream; 8 8 import java.nio.file.Files; 9 9 import java.nio.file.Path; 10 + import java.util.ArrayList; 10 11 import java.util.Collection; 11 12 import java.util.HashMap; 13 + import java.util.List; 12 14 import java.util.Map; 13 15 import java.util.TreeMap; 16 + import java.util.TreeSet; 14 17 import java.util.function.Predicate; 15 18 import java.util.stream.Collectors; 16 19 ··· 237 240 } 238 241 239 242 return assetMap; 243 + } 244 + 245 + // --- Generic directory listing --- 246 + 247 + public record DirectoryListing(List<AssetHandle> files, List<String> subdirectories) {} 248 + 249 + /** 250 + * Lists all files and subdirectories at a relative path across the asset stack. 251 + * Files are returned as AssetHandles (first occurrence in the stack wins). 252 + * Subdirectories are returned as names (union across all stack levels). 253 + * @param relativePath Relative path from asset root, e.g. "" for root, "mapfs/", "mapfs/geom/" 254 + */ 255 + public static DirectoryListing listDirectory(String relativePath) 256 + { 257 + Map<String, AssetHandle> fileMap = new HashMap<>(); 258 + TreeSet<String> subdirSet = new TreeSet<>(); 259 + 260 + for (File stackDir : Environment.assetDirectories) { 261 + Path dir = stackDir.toPath().resolve(relativePath); 262 + 263 + if (!Files.exists(dir) || !Files.isDirectory(dir)) 264 + continue; 265 + 266 + try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) { 267 + for (Path entry : stream) { 268 + String name = entry.getFileName().toString(); 269 + 270 + if (Files.isDirectory(entry)) { 271 + subdirSet.add(name); 272 + } 273 + else { 274 + String relPath = relativePath + name; 275 + AssetHandle ah = new AssetHandle(stackDir, relPath); 276 + fileMap.putIfAbsent(name, ah); 277 + } 278 + } 279 + } 280 + catch (IOException e) { 281 + Logger.logError("Failed to read directory: " + dir); 282 + } 283 + } 284 + 285 + List<AssetHandle> files = fileMap.entrySet().stream() 286 + .sorted(Map.Entry.comparingByKey()) 287 + .map(Map.Entry::getValue) 288 + .collect(Collectors.toList()); 289 + 290 + return new DirectoryListing(files, new ArrayList<>(subdirSet)); 291 + } 292 + 293 + /** 294 + * Returns the real directories across the asset stack that correspond to a relative path. 295 + * Only directories that actually exist are returned. 296 + */ 297 + public static List<File> getStackDirsForPath(String relativePath) 298 + { 299 + List<File> dirs = new ArrayList<>(); 300 + for (File stackDir : Environment.assetDirectories) { 301 + File dir = new File(stackDir, relativePath); 302 + if (dir.isDirectory()) 303 + dirs.add(dir); 304 + } 305 + return dirs; 240 306 } 241 307 242 308 public static Collection<AssetHandle> getIcons() throws IOException
-97
src/main/java/util/ui/AssetsPanel.java
··· 1 - package util.ui; 2 - 3 - import java.awt.Dimension; 4 - import java.awt.FlowLayout; 5 - import java.awt.Graphics; 6 - import java.awt.Graphics2D; 7 - import java.awt.event.MouseAdapter; 8 - import java.awt.event.MouseEvent; 9 - import java.util.ArrayList; 10 - import java.util.Collection; 11 - 12 - import javax.swing.JLabel; 13 - import javax.swing.JPanel; 14 - import javax.swing.JScrollPane; 15 - import javax.swing.UIManager; 16 - 17 - import assets.AssetManager; 18 - import assets.ui.MapAsset; 19 - import assets.AssetHandle; 20 - import net.miginfocom.swing.MigLayout; 21 - import util.Logger; 22 - 23 - public class AssetsPanel extends JPanel { 24 - private Collection<? extends AssetHandle> results; 25 - 26 - private JPanel resultsPanel; 27 - 28 - public AssetsPanel() { 29 - super(); 30 - 31 - try { 32 - results = AssetManager.getMapSources().stream().map(asset -> new MapAsset(asset)).toList(); 33 - } catch (Exception e) { 34 - Logger.logError("Failed to load assets: " + e.getMessage()); 35 - results = new ArrayList<>(); 36 - } 37 - 38 - setLayout(new MigLayout("ins 4")); 39 - 40 - resultsPanel = new JPanel(new WrapLayout(FlowLayout.LEFT, 0, 0)); 41 - for (AssetHandle asset : results) { 42 - //resultsPanel.add(createAssetItem(asset)); 43 - } 44 - 45 - JScrollPane scrollPane = new JScrollPane(resultsPanel); 46 - scrollPane.setBorder(null); 47 - 48 - add(scrollPane, "grow, push"); 49 - } 50 - 51 - private JPanel createAssetItem(AssetHandle asset) { 52 - JPanel panel = new JPanel(new MigLayout("ins 0, fill")); 53 - 54 - panel.setPreferredSize(new Dimension(80, 80)); 55 - panel.setOpaque(true); 56 - 57 - JLabel icon = new JLabel(ThemedIcon.PACKAGE_24); 58 - // TODO: worker to load thumbnail with an asset method (e.g. MapAsset can return the thumbnail made by action_captureThumbnails) 59 - 60 - JLabel name = new JLabel(asset.getAssetName()); 61 - name.setPreferredSize(new Dimension(80, 20)); 62 - 63 - panel.add(icon, "wrap"); 64 - panel.add(name, "align center, wmax 80"); 65 - 66 - String desc = asset.getAssetDescription(); 67 - if (desc != null && !desc.isEmpty()) { 68 - panel.setToolTipText(desc); 69 - } 70 - 71 - panel.addMouseListener(new MouseAdapter() { 72 - @Override 73 - public void mouseEntered(MouseEvent e) { 74 - panel.setBackground(UIManager.getColor("Table.selectionBackground")); 75 - } 76 - 77 - @Override 78 - public void mouseExited(MouseEvent e) { 79 - panel.setBackground(null); 80 - } 81 - 82 - @Override 83 - public void mouseClicked(MouseEvent e) { 84 - if (e.getClickCount() == 2) { 85 - // Double-click to open 86 - openAsset(asset); 87 - } 88 - } 89 - }); 90 - 91 - return panel; 92 - } 93 - 94 - private void openAsset(AssetHandle asset) { 95 - // TODO 96 - } 97 - }