From d07cd476de61c7035fc9c7d241e59ffc4c8702da Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Sun, 17 May 2026 17:50:55 +0200 Subject: [PATCH] fixes ; kernel stability --- src/main/java/com/rdesk/Main.java | 1 + .../java/com/rdesk/kernel/AppLauncher.java | 238 ------------------ .../java/com/rdesk/kernel/AppManager.java | 7 +- .../com/rdesk/kernel/DesktopIconManager.java | 185 -------------- src/main/java/com/rdesk/kernel/Kernel.java | 15 -- .../java/com/rdesk/kernel/WindowManager.java | 21 +- .../java/com/rdesk/kernel/WindowSnapper.java | 221 ++++++++++++++++ .../rdesk/kernel/launcher/AppLauncher.java | 79 ++++++ .../rdesk/kernel/launcher/CategoryPanel.java | 90 +++++++ 9 files changed, 407 insertions(+), 450 deletions(-) delete mode 100644 src/main/java/com/rdesk/kernel/AppLauncher.java delete mode 100644 src/main/java/com/rdesk/kernel/DesktopIconManager.java create mode 100644 src/main/java/com/rdesk/kernel/WindowSnapper.java create mode 100644 src/main/java/com/rdesk/kernel/launcher/AppLauncher.java create mode 100644 src/main/java/com/rdesk/kernel/launcher/CategoryPanel.java diff --git a/src/main/java/com/rdesk/Main.java b/src/main/java/com/rdesk/Main.java index 831bc02..513f8ea 100644 --- a/src/main/java/com/rdesk/Main.java +++ b/src/main/java/com/rdesk/Main.java @@ -8,6 +8,7 @@ import com.rdesk.applications.productivity.Notes; import com.rdesk.applications.system.TaskManager; import com.rdesk.applications.system.filehaj.FileExplorerApp; import com.rdesk.kernel.*; +import com.rdesk.kernel.launcher.AppLauncher; import javax.swing.*; diff --git a/src/main/java/com/rdesk/kernel/AppLauncher.java b/src/main/java/com/rdesk/kernel/AppLauncher.java deleted file mode 100644 index a8367e9..0000000 --- a/src/main/java/com/rdesk/kernel/AppLauncher.java +++ /dev/null @@ -1,238 +0,0 @@ -package com.rdesk.kernel; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.*; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -/** - * A clean, functional sidebar that lists applications by category. - * Categories tile vertically, each taking full width. - */ -public class AppLauncher extends JPanel { - - private static final String DEFAULT_CATEGORY = "General"; - private static final int EXPANDED_WIDTH = 220; - private static final int COLLAPSED_WIDTH = 40; - - private final Kernel kernel; - private final Map categories = new LinkedHashMap<>(); - private final JPanel mainPanel; - private final JButton globalToggleButton; - private final JScrollPane scrollPane; - private boolean expanded = true; - - public AppLauncher(Kernel kernel) { - this.kernel = kernel; - setLayout(new BorderLayout()); - setPreferredSize(new Dimension(EXPANDED_WIDTH, 0)); - - globalToggleButton = new JButton("<<"); - globalToggleButton.setToolTipText("Collapse sidebar"); - globalToggleButton.setFocusPainted(false); - globalToggleButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - globalToggleButton.addActionListener(this::toggleSidebar); - add(globalToggleButton, BorderLayout.NORTH); - - mainPanel = new JPanel(); - mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); - scrollPane = new JScrollPane(mainPanel); - scrollPane.setBorder(null); - scrollPane.getVerticalScrollBar().setUnitIncrement(10); - scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - add(scrollPane, BorderLayout.CENTER); - } - - // Public API ----------------------------------------------------------------- - - public void addApp(DesktopApp app) { - addApp(app, DEFAULT_CATEGORY); - } - - public void addApp(DesktopApp app, String category) { - if (!kernel.apps().isRegistered(app.getId())) { - kernel.apps().register(app); - } - SwingUtilities.invokeLater(() -> getCategoryPanel(category).addApp(app)); - } - - public void loadAppsFromFolder(String folderPath) { - loadAppsFromFolder(folderPath, DEFAULT_CATEGORY); - } - - public void loadAppsFromFolder(String folderPath, String category) { - File folder = new File(folderPath); - if (!folder.exists() || !folder.isDirectory()) { - System.err.println("[AppLauncher] Folder not found: " + folderPath); - return; - } - File[] jars = folder.listFiles((dir, name) -> name.endsWith(".jar")); - if (jars == null) return; - for (File jar : jars) loadJarApp(jar, category); - } - - public void setCategoryExpanded(String category, boolean expand) { - CategoryPanel panel = categories.get(category); - if (panel != null) panel.setExpanded(expand); - } - - // Internal logic ------------------------------------------------------------ - - private void toggleSidebar(ActionEvent e) { - expanded = !expanded; - globalToggleButton.setText(expanded ? "<<" : ">>"); - globalToggleButton.setToolTipText(expanded ? "Collapse sidebar" : "Expand sidebar"); - - scrollPane.setVisible(expanded); - setPreferredSize(new Dimension(expanded ? EXPANDED_WIDTH : COLLAPSED_WIDTH, 0)); - revalidate(); - - Container parent = getParent(); - if (parent instanceof JSplitPane split) { - split.setDividerLocation(expanded ? EXPANDED_WIDTH : COLLAPSED_WIDTH); - } - } - - private CategoryPanel getCategoryPanel(String name) { - return categories.computeIfAbsent(name, n -> { - CategoryPanel panel = new CategoryPanel(n); - mainPanel.add(panel); - mainPanel.revalidate(); - return panel; - }); - } - - private void loadJarApp(File jarFile, String category) { - try (URLClassLoader loader = new URLClassLoader( - new URL[]{jarFile.toURI().toURL()}, - getClass().getClassLoader())) { - - String className = findDesktopAppClass(loader, jarFile); - if (className == null) { - System.err.println("[AppLauncher] No DesktopApp in " + jarFile.getName()); - return; - } - - Class clazz = Class.forName(className, true, loader); - DesktopApp app = (DesktopApp) clazz.getDeclaredConstructor().newInstance(); - addApp(app, category); - System.out.println("[AppLauncher] Loaded: " + app.getName()); - - } catch (Exception e) { - System.err.println("[AppLauncher] Failed to load " + jarFile.getName()); - e.printStackTrace(); - } - } - - private String findDesktopAppClass(URLClassLoader loader, File jarFile) { - try (JarFile jar = new JarFile(jarFile)) { - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - if (!entry.getName().endsWith(".class")) continue; - String className = entry.getName().replace("/", ".").replace(".class", ""); - try { - Class clazz = Class.forName(className, false, loader); - for (Class iface : clazz.getInterfaces()) { - if (iface == DesktopApp.class) return className; - } - } catch (Throwable ignored) {} - } - } catch (Exception e) { - JOptionPane.showMessageDialog( - null, - "Kernel Fault", - "The Kernel has encoutered a fault when trying to find DesktopAppClass" + e.getStackTrace(), - JOptionPane.WARNING_MESSAGE - ); - } - return null; - } - - // Inner classes ------------------------------------------------------------- - - private class CategoryPanel extends JPanel { - private final String categoryName; - private final JButton headerButton; - private final JPanel appsContainer; - private boolean expanded = true; - - CategoryPanel(String categoryName) { - this.categoryName = categoryName; - setLayout(new BorderLayout()); - setAlignmentX(LEFT_ALIGNMENT); - setBorder(BorderFactory.createEmptyBorder(0, 0, 4, 0)); - - headerButton = new JButton(); - headerButton.setHorizontalAlignment(SwingConstants.LEFT); - headerButton.setFocusPainted(false); - headerButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - headerButton.addActionListener(e -> setExpanded(!expanded)); - updateHeaderText(); - - appsContainer = new JPanel(); - appsContainer.setLayout(new BoxLayout(appsContainer, BoxLayout.Y_AXIS)); - appsContainer.setBorder(BorderFactory.createEmptyBorder(2, 12, 2, 2)); - appsContainer.setVisible(true); - appsContainer.setAlignmentX(LEFT_ALIGNMENT); - - add(headerButton, BorderLayout.NORTH); - add(appsContainer, BorderLayout.CENTER); - } - - // Override getMaximumSize to allow full width while respecting preferred height - @Override - public Dimension getMaximumSize() { - Dimension preferred = getPreferredSize(); - return new Dimension(Integer.MAX_VALUE, preferred.height); - } - - private void updateHeaderText() { - String prefix = expanded ? "[-] " : "[+] "; - headerButton.setText(prefix + categoryName); - } - - void addApp(DesktopApp app) { - JButton appButton = new JButton(app.getName()); - appButton.setHorizontalAlignment(SwingConstants.LEFT); - appButton.setFocusPainted(false); - appButton.setBorder(BorderFactory.createEmptyBorder(4, 8, 4, 8)); - appButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - appButton.addActionListener(e -> { - try { - kernel.apps().launch(app.getId()); - } catch (IOException ex) { - JOptionPane.showMessageDialog( - appsContainer, - "Kernel Fault", - "The Kernel encoutered a critical error when launching an Application", - JOptionPane.ERROR_MESSAGE - ); - } - }); - - // Make each app button fill the width of its container - appButton.setAlignmentX(LEFT_ALIGNMENT); - appButton.setMaximumSize(new Dimension(Integer.MAX_VALUE, appButton.getPreferredSize().height)); - - appsContainer.add(appButton); - appsContainer.revalidate(); - appsContainer.repaint(); - } - - void setExpanded(boolean expand) { - if (this.expanded == expand) return; - this.expanded = expand; - appsContainer.setVisible(expanded); - updateHeaderText(); - revalidate(); - repaint(); - } - } -} \ No newline at end of file diff --git a/src/main/java/com/rdesk/kernel/AppManager.java b/src/main/java/com/rdesk/kernel/AppManager.java index d89ab9e..47b007f 100644 --- a/src/main/java/com/rdesk/kernel/AppManager.java +++ b/src/main/java/com/rdesk/kernel/AppManager.java @@ -46,7 +46,12 @@ public class AppManager { app.onStop(kernel); System.out.println("[AppManager] Stopped: " + app.getName()); } catch (Exception e) { - e.printStackTrace(); + JOptionPane.showMessageDialog( + null, + "AppManager Fault", + "Fault when trying to stop all Applications in ApplicationManager", + JOptionPane.ERROR_MESSAGE + ); } } runningApps.clear(); diff --git a/src/main/java/com/rdesk/kernel/DesktopIconManager.java b/src/main/java/com/rdesk/kernel/DesktopIconManager.java deleted file mode 100644 index ace0b7a..0000000 --- a/src/main/java/com/rdesk/kernel/DesktopIconManager.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.rdesk.kernel; - -import javax.swing.*; -import java.awt.*; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.jar.*; - -public class DesktopIconManager { - - private final Kernel kernel; - private final JDesktopPane desktop; - - private final int cellWidth = 110; - private final int cellHeight = 110; - private final int iconWidth = 90; - private final int iconHeight = 70; - - /** Tracks how many icons have been placed so far. */ - private int index = 0; - - public DesktopIconManager(Kernel kernel, JDesktopPane desktop) { - this.kernel = kernel; - this.desktop = desktop; - this.desktop.setLayout(null); - } - - // ------------------------------------------------------------------ // - // Public API // - // ------------------------------------------------------------------ // - - /** - * Scan a folder for {@code .jar} files and create a desktop icon for - * every {@link DesktopApp} implementation found inside them. - */ - public void loadAppsFromFolder(String path) { - File folder = new File(path); - - if (!folder.exists() || !folder.isDirectory()) { - System.out.println("[IconManager] applications folder not found: " + path); - return; - } - - File[] jars = folder.listFiles((d, name) -> name.endsWith(".jar")); - if (jars == null) return; - - for (File jar : jars) { - loadJar(jar); - } - } - - /** - * Directly add a {@link DesktopApp} icon to the desktop. - * Use this for system / built-in apps registered from {@code main()}. - */ - public void addApp(DesktopApp app) { - SwingUtilities.invokeLater(() -> createIcon(app)); - } - - // ------------------------------------------------------------------ // - // JAR loading // - // ------------------------------------------------------------------ // - - private void loadJar(File jarFile) { - // URLClassLoader implements Closeable — always close it when done - try (URLClassLoader loader = new URLClassLoader( - new URL[]{jarFile.toURI().toURL()}, - getClass().getClassLoader())) { - - String className = findAppClass(loader, jarFile); - if (className == null) { - System.out.println("[IconManager] No DesktopApp found in " + jarFile.getName()); - return; - } - - Class clazz = Class.forName(className, true, loader); - DesktopApp app = (DesktopApp) clazz.getDeclaredConstructor().newInstance(); - - SwingUtilities.invokeLater(() -> createIcon(app)); - System.out.println("[IconManager] Loaded: " + app.getName()); - - } catch (Exception e) { - System.err.println("[IconManager] Failed to load " + jarFile.getName() + ": " + e.getMessage()); - e.printStackTrace(); - } - } - - private String findAppClass(URLClassLoader loader, File jarFile) { - try (JarFile jar = new JarFile(jarFile)) { - - var entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - - if (!entry.getName().endsWith(".class")) continue; - - String className = entry.getName() - .replace("/", ".") - .replace(".class", ""); - - try { - Class clazz = Class.forName(className, false, loader); - for (Class iface : clazz.getInterfaces()) { - if (iface == DesktopApp.class) { - return className; - } - } - } catch (Throwable ignored) {} - } - - } catch (Exception e) { - e.printStackTrace(); - } - - return null; - } - - // ------------------------------------------------------------------ // - // Icon creation & layout // - // ------------------------------------------------------------------ // - - /** - * Calculate the next grid position. - * - * Column count is derived lazily from the desktop's current width so it - * remains correct even when the frame has been packed / resized before - * icons are added. Falls back to 1 column if the pane has no width yet. - */ - private Point nextGridPosition() { - int desktopWidth = desktop.getWidth(); - int cols = (desktopWidth > 0) ? Math.max(1, desktopWidth / cellWidth) : 1; - - int row = index / cols; - int col = index % cols; - - int x = col * cellWidth + (cellWidth - iconWidth) / 2; - int y = row * cellHeight + (cellHeight - iconHeight) / 2; - - index++; - return new Point(x, y); - } - - private void createIcon(DesktopApp app) { - JButton button = new JButton("
" + app.getName() + "
"); - - // Style — a clean, dark desktop-icon look - button.setFont(new Font("Segoe UI", Font.PLAIN, 11)); - button.setForeground(Color.WHITE); - button.setBackground(new Color(40, 40, 60, 200)); - button.setBorderPainted(false); - button.setFocusPainted(false); - button.setOpaque(true); - button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - button.setToolTipText(app.getName()); - - button.addMouseListener(new java.awt.event.MouseAdapter() { - @Override public void mouseEntered(java.awt.event.MouseEvent e) { - button.setBackground(new Color(70, 70, 100, 220)); - } - @Override public void mouseExited(java.awt.event.MouseEvent e) { - button.setBackground(new Color(40, 40, 60, 200)); - } - }); - - Point p = nextGridPosition(); - button.setBounds(p.x, p.y, iconWidth, iconHeight); - - button.addActionListener(e -> { - // app may be pre registered, so dont do shit - if (!kernel.apps().isRegistered(app.getId())) { - kernel.apps().register(app); - } - try { - kernel.apps().launch(app.getId()); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - }); - - desktop.add(button); - desktop.repaint(); - } -} \ No newline at end of file diff --git a/src/main/java/com/rdesk/kernel/Kernel.java b/src/main/java/com/rdesk/kernel/Kernel.java index c6d2941..d83b7ed 100644 --- a/src/main/java/com/rdesk/kernel/Kernel.java +++ b/src/main/java/com/rdesk/kernel/Kernel.java @@ -11,7 +11,6 @@ public class Kernel { private final EventBus eventBus; private final ServiceRegistry services; - private DesktopIconManager iconManager; private JFrame desktopFrame; private boolean running = false; @@ -44,20 +43,6 @@ public class Kernel { this.desktopFrame = frame; } - public void attachIconManager(DesktopIconManager iconManager) { - this.iconManager = iconManager; - } - - public void registerSystemApp(DesktopApp app) { - if (iconManager == null) { - throw new IllegalStateException( - "[Kernel] attachIconManager() must be called before registerSystemApp()"); - } - appManager.register(app); - iconManager.addApp(app); - System.out.println("[Kernel] System app registered: " + app.getName()); - } - public void shutdown() { if (!running) return; diff --git a/src/main/java/com/rdesk/kernel/WindowManager.java b/src/main/java/com/rdesk/kernel/WindowManager.java index 6a25a86..13a0ccc 100644 --- a/src/main/java/com/rdesk/kernel/WindowManager.java +++ b/src/main/java/com/rdesk/kernel/WindowManager.java @@ -1,33 +1,31 @@ package com.rdesk.kernel; import javax.swing.*; -import java.util.ArrayList; -import java.util.List; public class WindowManager { - private final Kernel kernel; private JDesktopPane desktop; + private final WindowSnapper snapper; public WindowManager(Kernel kernel) { - this.kernel = kernel; + this.snapper = new WindowSnapper(this); } public void attachDesktop(JDesktopPane desktop) { this.desktop = desktop; + snapper.attachToDesktop(desktop); } public void open(JInternalFrame frame) { desktop.add(frame); frame.setVisible(true); + snapper.attach(frame); } public void closeAll() { if (desktop == null) return; for (JInternalFrame frame : desktop.getAllFrames()) { - try { - frame.setClosed(true); - } catch (Exception ignored) {} + try { frame.setClosed(true); } catch (Exception ignored) {} } } @@ -41,15 +39,16 @@ public class WindowManager { for (JInternalFrame frame : desktop.getAllFrames()) { Object prop = frame.getClientProperty("appId"); if (appId.equals(prop)) { - try { - frame.setClosed(true); - } catch (Exception ignored) {} + try { frame.setClosed(true); } catch (Exception ignored) {} } } } public JDesktopPane getDesktop() { - // allows to change stuff for the desktop pane, essential for our bgchanger class return desktop; } + + public WindowSnapper getSnapper() { + return snapper; + } } \ No newline at end of file diff --git a/src/main/java/com/rdesk/kernel/WindowSnapper.java b/src/main/java/com/rdesk/kernel/WindowSnapper.java new file mode 100644 index 0000000..4e4a8e2 --- /dev/null +++ b/src/main/java/com/rdesk/kernel/WindowSnapper.java @@ -0,0 +1,221 @@ +package com.rdesk.kernel; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +public class WindowSnapper { + + private static final int SNAP_THRESHOLD = 28; + + private final WindowManager windowManager; + private int sidebarWidth = 0; + + private JWindow snapOverlay; + + private SnapZone activeZone = SnapZone.NONE; + private JInternalFrame draggingFrame = null; + private boolean mouseDown = false; + + private AWTEventListener awtListener; + + public WindowSnapper(WindowManager windowManager) { + this.windowManager = windowManager; + } + + public void setSidebarWidth(int width) { + this.sidebarWidth = width; + } + + public void attachToDesktop(JDesktopPane desktop) { + if (awtListener != null) { + Toolkit.getDefaultToolkit().removeAWTEventListener(awtListener); + } + + awtListener = event -> { + if (!(event instanceof MouseEvent me)) return; + + if (!isInsideDesktop(me.getComponent(), desktop)) return; + + int id = me.getID(); + + switch (id) { + + case MouseEvent.MOUSE_PRESSED -> { + mouseDown = true; + draggingFrame = desktop.getSelectedFrame(); + } + + case MouseEvent.MOUSE_DRAGGED -> { + if (!mouseDown) return; + + JInternalFrame frame = draggingFrame != null + ? draggingFrame : desktop.getSelectedFrame(); + draggingFrame = frame; + + if (frame == null || isMaximised(frame)) { + if (activeZone != SnapZone.NONE) { + activeZone = SnapZone.NONE; + hideOverlay(); + } + return; + } + + Point screenCursor = me.getLocationOnScreen(); + Rectangle usable = usableAreaOnScreen(desktop); + SnapZone zone = detectZone(screenCursor, usable); + + if (zone != activeZone) { + activeZone = zone; + if (zone == SnapZone.NONE) { + hideOverlay(); + } else { + Rectangle overlayBounds = boundsForZone(zone, usable); + if (overlayBounds != null) showOverlay(overlayBounds); + } + } + } + + case MouseEvent.MOUSE_RELEASED -> { + if (!mouseDown) return; + mouseDown = false; + hideOverlay(); + commitSnap(desktop); + activeZone = SnapZone.NONE; + draggingFrame = null; + } + } + }; + + Toolkit.getDefaultToolkit().addAWTEventListener( + awtListener, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); + } + + public void attach(JInternalFrame frame) { + } + + + private void commitSnap(JDesktopPane desktop) { + if (draggingFrame == null || activeZone == SnapZone.NONE) return; + + Rectangle usable = usableAreaOnScreen(desktop); + Rectangle snapScreen = boundsForZone(activeZone, usable); + if (snapScreen == null) return; + + Point desktopOrigin = desktop.getLocationOnScreen(); + Rectangle local = new Rectangle( + snapScreen.x - desktopOrigin.x, + snapScreen.y - desktopOrigin.y, + snapScreen.width, + snapScreen.height + ); + + if (activeZone == SnapZone.TOP) { + try { draggingFrame.setMaximum(true); } + catch (Exception ignored) {} + } else { + draggingFrame.setBounds(local); + draggingFrame.revalidate(); + desktop.repaint(); + } + } + + private enum SnapZone { + LEFT, RIGHT, TOP, + TOP_LEFT, TOP_RIGHT, + BOTTOM_LEFT, BOTTOM_RIGHT, + NONE + } + + private Rectangle usableAreaOnScreen(JDesktopPane desktop) { + Point origin = desktop.getLocationOnScreen(); + int x = origin.x + sidebarWidth; + int y = origin.y; + int w = desktop.getWidth() - sidebarWidth; + int h = desktop.getHeight(); + return new Rectangle(x, y, w, h); + } + + private SnapZone detectZone(Point screenCursor, Rectangle usable) { + int rx = screenCursor.x - usable.x; // position within usable area + int ry = screenCursor.y - usable.y; + int w = usable.width; + int h = usable.height; + + boolean nearLeft = rx <= SNAP_THRESHOLD || screenCursor.x <= usable.x + SNAP_THRESHOLD; + boolean nearRight = rx >= w - SNAP_THRESHOLD; + boolean nearTop = ry <= SNAP_THRESHOLD; + boolean nearBottom = ry >= h - SNAP_THRESHOLD; + + if (nearTop && nearLeft) return SnapZone.TOP_LEFT; + if (nearTop && nearRight) return SnapZone.TOP_RIGHT; + if (nearBottom && nearLeft) return SnapZone.BOTTOM_LEFT; + if (nearBottom && nearRight) return SnapZone.BOTTOM_RIGHT; + if (nearTop) return SnapZone.TOP; + if (nearLeft) return SnapZone.LEFT; + if (nearRight) return SnapZone.RIGHT; + return SnapZone.NONE; + } + + private Rectangle boundsForZone(SnapZone zone, Rectangle u) { + int hw = u.width / 2; + int hh = u.height / 2; + + return switch (zone) { + case LEFT -> new Rectangle(u.x, u.y, hw, u.height); + case RIGHT -> new Rectangle(u.x + hw, u.y, hw, u.height); + case TOP -> new Rectangle(u.x, u.y, u.width, u.height); + case TOP_LEFT -> new Rectangle(u.x, u.y, hw, hh); + case TOP_RIGHT -> new Rectangle(u.x + hw, u.y, hw, hh); + case BOTTOM_LEFT -> new Rectangle(u.x, u.y + hh, hw, hh); + case BOTTOM_RIGHT -> new Rectangle(u.x + hw, u.y + hh, hw, hh); + default -> null; + }; + } + + private void showOverlay(Rectangle screenBounds) { + if (snapOverlay == null) { + snapOverlay = new JWindow(); + snapOverlay.setBackground(new Color(0, 0, 0, 0)); + + JPanel glass = new JPanel() { + @Override protected void paintComponent(Graphics g) { + Graphics2D g2 = (Graphics2D) g.create(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g2.setColor(new Color(0, 120, 215, 55)); + g2.fillRoundRect(3, 3, getWidth() - 6, getHeight() - 6, 10, 10); + g2.setColor(new Color(0, 120, 215, 180)); + g2.setStroke(new BasicStroke(2f)); + g2.drawRoundRect(3, 3, getWidth() - 6, getHeight() - 6, 10, 10); + g2.dispose(); + } + }; + glass.setOpaque(false); + snapOverlay.setContentPane(glass); + snapOverlay.setAlwaysOnTop(true); + } + + snapOverlay.setBounds(screenBounds); + if (!snapOverlay.isVisible()) snapOverlay.setVisible(true); + snapOverlay.repaint(); + } + + private void hideOverlay() { + if (snapOverlay != null && snapOverlay.isVisible()) + snapOverlay.setVisible(false); + } + + private boolean isMaximised(JInternalFrame f) { + try { return f.isMaximum(); } catch (Exception e) { return false; } + } + + private boolean isInsideDesktop(Component component, JDesktopPane desktop) { + Component c = component; + while (c != null) { + if (c == desktop) return true; + c = c.getParent(); + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/com/rdesk/kernel/launcher/AppLauncher.java b/src/main/java/com/rdesk/kernel/launcher/AppLauncher.java new file mode 100644 index 0000000..8b9c4f7 --- /dev/null +++ b/src/main/java/com/rdesk/kernel/launcher/AppLauncher.java @@ -0,0 +1,79 @@ +package com.rdesk.kernel.launcher; + +import com.rdesk.kernel.DesktopApp; +import com.rdesk.kernel.Kernel; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.util.*; + +/** + * A clean, functional sidebar that lists applications by category. + * Categories tile vertically, each taking full width. + */ +public class AppLauncher extends JPanel { + + private static final int EXPANDED_WIDTH = 220; + private static final int COLLAPSED_WIDTH = 40; + + private final Kernel kernel; + private final Map categories = new LinkedHashMap<>(); + private final JPanel mainPanel; + private final JButton globalToggleButton; + private final JScrollPane scrollPane; + private boolean expanded = true; + + public AppLauncher(Kernel kernel) { + this.kernel = kernel; + setLayout(new BorderLayout()); + setPreferredSize(new Dimension(EXPANDED_WIDTH, 0)); + + globalToggleButton = new JButton("<<"); + globalToggleButton.setToolTipText("Collapse sidebar"); + globalToggleButton.setFocusPainted(false); + globalToggleButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + globalToggleButton.addActionListener(this::toggleSidebar); + add(globalToggleButton, BorderLayout.NORTH); + + mainPanel = new JPanel(); + mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); + scrollPane = new JScrollPane(mainPanel); + scrollPane.setBorder(null); + scrollPane.getVerticalScrollBar().setUnitIncrement(10); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + add(scrollPane, BorderLayout.CENTER); + } + + public void addApp(DesktopApp app, String category) { + if (!kernel.apps().isRegistered(app.getId())) { + kernel.apps().register(app); + } + SwingUtilities.invokeLater(() -> getCategoryPanel(category).addApp(app)); + } + + private void toggleSidebar(ActionEvent e) { + expanded = !expanded; + globalToggleButton.setText(expanded ? "<<" : ">>"); + globalToggleButton.setToolTipText(expanded ? "Collapse sidebar" : "Expand sidebar"); + + scrollPane.setVisible(expanded); + setPreferredSize(new Dimension(expanded ? EXPANDED_WIDTH : COLLAPSED_WIDTH, 0)); + revalidate(); + + Container parent = getParent(); + if (parent instanceof JSplitPane split) { + split.setDividerLocation(expanded ? EXPANDED_WIDTH : COLLAPSED_WIDTH); + } + } + + private CategoryPanel getCategoryPanel(String name) { + return categories.computeIfAbsent(name, n -> { + CategoryPanel panel = new CategoryPanel(n, this.kernel); + mainPanel.add(panel); + mainPanel.revalidate(); + return panel; + }); + } + +} \ No newline at end of file diff --git a/src/main/java/com/rdesk/kernel/launcher/CategoryPanel.java b/src/main/java/com/rdesk/kernel/launcher/CategoryPanel.java new file mode 100644 index 0000000..d34c2ee --- /dev/null +++ b/src/main/java/com/rdesk/kernel/launcher/CategoryPanel.java @@ -0,0 +1,90 @@ +package com.rdesk.kernel.launcher; + +import com.rdesk.kernel.DesktopApp; +import com.rdesk.kernel.Kernel; +import com.sun.jna.platform.win32.WinReg; + +import javax.swing.*; +import java.awt.*; +import java.io.IOException; + +class CategoryPanel extends JPanel { + private final String categoryName; + private final JButton headerButton; + private final JPanel appsContainer; + private boolean expanded = true; + private final Kernel kernel; + + CategoryPanel(String categoryName, Kernel kernel) { + this.categoryName = categoryName; + this.kernel = kernel; + setLayout(new BorderLayout()); + setAlignmentX(LEFT_ALIGNMENT); + setBorder(BorderFactory.createEmptyBorder(0, 0, 4, 0)); + + headerButton = new JButton(); + headerButton.setHorizontalAlignment(SwingConstants.LEFT); + headerButton.setFocusPainted(false); + headerButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + headerButton.addActionListener(e -> setExpanded(!expanded)); + updateHeaderText(); + + appsContainer = new JPanel(); + appsContainer.setLayout(new BoxLayout(appsContainer, BoxLayout.Y_AXIS)); + appsContainer.setBorder(BorderFactory.createEmptyBorder(2, 12, 2, 2)); + appsContainer.setVisible(true); + appsContainer.setAlignmentX(LEFT_ALIGNMENT); + + add(headerButton, BorderLayout.NORTH); + add(appsContainer, BorderLayout.CENTER); + } + + // Override getMaximumSize to allow full width while respecting preferred height + @Override + public Dimension getMaximumSize() { + Dimension preferred = getPreferredSize(); + return new Dimension(Integer.MAX_VALUE, preferred.height); + } + + private void updateHeaderText() { + String prefix = expanded ? "[-] " : "[+] "; + headerButton.setText(prefix + categoryName); + } + + void addApp(DesktopApp app) { + JButton appButton = new JButton(app.getName()); + appButton.setHorizontalAlignment(SwingConstants.LEFT); + appButton.setFocusPainted(false); + appButton.setBorder(BorderFactory.createEmptyBorder(4, 8, 4, 8)); + appButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + appButton.addActionListener(e -> { + try { + kernel.apps().launch(app.getId()); + } catch (IOException ex) { + JOptionPane.showMessageDialog( + appsContainer, + "Kernel Fault", + "The Kernel encoutered a critical error when launching an Application", + JOptionPane.ERROR_MESSAGE + ); + } + }); + + // Make each app button fill the width of its container + appButton.setAlignmentX(LEFT_ALIGNMENT); + appButton.setMaximumSize(new Dimension(Integer.MAX_VALUE, appButton.getPreferredSize().height)); + + appsContainer.add(appButton); + appsContainer.revalidate(); + appsContainer.repaint(); + } + + void setExpanded(boolean expand) { + if (this.expanded == expand) return; + this.expanded = expand; + appsContainer.setVisible(expanded); + updateHeaderText(); + revalidate(); + repaint(); + } +}