package org.openautonomousconnection.webclient.ui; import org.openautonomousconnection.oacswing.component.OACButton; import org.openautonomousconnection.oacswing.component.OACFrame; import org.openautonomousconnection.oacswing.component.OACPanel; import org.openautonomousconnection.oacswing.component.OACTextField; import org.openautonomousconnection.oacswing.component.design.DesignManager; import javax.swing.*; import java.awt.*; import java.util.LinkedHashMap; import java.util.Map; import java.util.Objects; /** * A simple multi-tab browser UI: * - Tab headers live in the custom title bar (OACTitleBar / OACTabbedPane). * - Tab content (JavaFX WebView) lives in the frame content area. * - Address bar controls the currently selected tab. */ public class BrowserUI extends OACFrame { private static final int TITLE_BAR_HEIGHT = 42; private final OACTextField addressField; private final OACButton goButton; private final OACButton newTabButton; private final OACButton closeTabButton; private final OACButton backButton; private final OACButton forwardButton; private final OACButton reloadButton; private final CardLayout cardLayout; private final OACPanel pageHost; private final Map tabsByKey = new LinkedHashMap<>(); private int tabCounter = 0; /** * Creates the browser UI and wires tab selection and address bar to tab content. */ public BrowserUI() { super("OAC Browser"); // Content pane must be offset because your title bar is an overlay in the layered pane. JComponent content = (JComponent) getContentPane(); content.setLayout(new BorderLayout()); content.setBorder(BorderFactory.createEmptyBorder(TITLE_BAR_HEIGHT, 0, 0, 0)); // Address bar (top of content area) OACPanel navBar = new OACPanel(new BorderLayout(8, 0)); navBar.setBorder(BorderFactory.createEmptyBorder(8, 10, 8, 10)); OACPanel leftControls = new OACPanel(new FlowLayout(FlowLayout.LEFT, 6, 0)); backButton = new OACButton("←"); forwardButton = new OACButton("→"); reloadButton = new OACButton("⟳"); leftControls.add(backButton); leftControls.add(forwardButton); leftControls.add(reloadButton); addressField = new OACTextField(); goButton = new OACButton("Go"); OACPanel rightControls = new OACPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0)); newTabButton = new OACButton("+"); closeTabButton = new OACButton("✕"); rightControls.add(newTabButton); rightControls.add(closeTabButton); navBar.add(leftControls, BorderLayout.WEST); navBar.add(addressField, BorderLayout.CENTER); navBar.add(goButton, BorderLayout.EAST); navBar.add(rightControls, BorderLayout.EAST); // Fix: BorderLayout only allows one EAST; wrap Go+RightControls in a single panel OACPanel east = new OACPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0)); east.add(goButton); east.add(newTabButton); east.add(closeTabButton); navBar.add(east, BorderLayout.EAST); content.add(navBar, BorderLayout.NORTH); // Page host cardLayout = new CardLayout(); pageHost = new OACPanel(cardLayout); content.add(pageHost, BorderLayout.CENTER); // Wire title bar tab selection to content host getTitleBar().getTabs().addChangeListener(e -> onHeaderTabChanged()); // Wire address bar actions addressField.addActionListener(e -> navigateCurrent(addressField.getText())); goButton.addActionListener(e -> navigateCurrent(addressField.getText())); // Wire navigation buttons backButton.addActionListener(e -> { BrowserTab tab = getCurrentTab(); if (tab != null) tab.goBack(); }); forwardButton.addActionListener(e -> { BrowserTab tab = getCurrentTab(); if (tab != null) tab.goForward(); }); reloadButton.addActionListener(e -> { BrowserTab tab = getCurrentTab(); if (tab != null) tab.reload(); }); newTabButton.addActionListener(e -> openNewTab("web://info.oac/")); closeTabButton.addActionListener(e -> closeCurrentTab()); // Create first tab DesignManager.apply(this); } /** * Opens a new tab and navigates to the given URL. * * @param url initial URL */ public void openNewTab(String url) { String key = nextTabKey(); BrowserTab tab = new BrowserTab(url, newLocation -> SwingUtilities.invokeLater(() -> { BrowserTab current = getCurrentTab(); if (current != null && Objects.equals(current.getKey(), key)) { addressField.setText(newLocation); } })); tabsByKey.put(key, tab); // Real page content in center host pageHost.add(tab.getView(), key); // Header tab in title bar: DO NOT place the real page here (title bar is only ~42px high). getTitleBar().addTab(key, new OACPanel()); // Select it int idx = getTitleBar().getTabs().getTabCount() - 1; getTitleBar().getTabs().setSelectedIndex(idx); // Navigate tab.loadUrl(url); // Show content cardLayout.show(pageHost, key); pageHost.revalidate(); pageHost.repaint(); // Update address field immediately addressField.setText(url); } /** * Navigates the current tab to the given input (URL or host). * * @param input user input */ public void navigateCurrent(String input) { BrowserTab tab = getCurrentTab(); if (tab == null) return; String url = normalizeUrl(input); addressField.setText(url); tab.loadUrl(url); } /** * Closes the currently selected tab. */ public void closeCurrentTab() { int idx = getTitleBar().getTabs().getSelectedIndex(); if (idx < 0) return; String key = getTitleBar().getTabs().getTitleAt(idx); BrowserTab removed = tabsByKey.remove(key); if (removed != null) { removed.dispose(); pageHost.remove(removed.getView()); } getTitleBar().getTabs().removeTabAt(idx); // If no tabs left, open a new one if (getTitleBar().getTabs().getTabCount() == 0) { openNewTab("web://info.oac/"); return; } // Show selected tab content onHeaderTabChanged(); } private void onHeaderTabChanged() { BrowserTab tab = getCurrentTab(); if (tab == null) return; cardLayout.show(pageHost, tab.getKey()); pageHost.revalidate(); pageHost.repaint(); // Sync address bar String loc = tab.getLocation(); if (loc != null && !loc.isBlank()) { addressField.setText(loc); } } public BrowserTab getCurrentTab() { int idx = getTitleBar().getTabs().getSelectedIndex(); if (idx < 0) return null; String key = getTitleBar().getTabs().getTitleAt(idx); return tabsByKey.get(key); } private String nextTabKey() { tabCounter++; return "Tab " + tabCounter; } private static String normalizeUrl(String input) { String s = input == null ? "" : input.trim(); if (s.isEmpty()) return "web://info.oac/"; if (s.startsWith("web://")) { // Ensure trailing slash for "host only" URLs String rest = s.substring("web://".length()); if (!rest.contains("/")) return s + "/"; return s; } return "web://" + s + (s.contains("/") ? "" : "/"); } }