Initial commit

This commit is contained in:
2026-01-14 15:36:52 +06:00
commit f17c1b42a2
21 changed files with 1101 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.vscode/

87
README.md Normal file
View File

@@ -0,0 +1,87 @@
# Java Browser
Полнофункциональный веб-браузер на Java с JavaFX.
## Возможности
- 🎨 Красивая стартовая страница с градиентным дизайном
- 🔍 Выбор поисковой системы (Google, Yandex, DuckDuckGo, Bing)
- ⭐ Система закладок с сохранением
- 📜 История посещений (до 100 записей)
- 📑 Множественные вкладки
- ⚡ Оптимизированная загрузка страниц
- 💾 Автосохранение всех данных
## Требования
- Java 17 или выше
- Maven 3.6+
## Запуск
### Через Maven (рекомендуется):
```bash
mvn clean javafx:run
```
### Через JAR:
```bash
mvn clean package
java -jar target/java-browser-1.0-SNAPSHOT.jar
```
## Управление
### Навигация
- **◀ ▶** — Навигация назад/вперёд
- **⟳** — Обновить страницу
- **🏠** — Вернуться на стартовую страницу
- **★** — Добавить текущую страницу в закладки
- **☰** — Открыть/закрыть панель с закладками и историей
- **+** — Создать новую вкладку
### Управление окном
- **□** — Развернуть/восстановить окно на весь экран
- Окно можно свободно изменять в размере, перетаскивая края
- Стандартные кнопки Windows для сворачивания и закрытия работают как обычно
### Закрытие браузера
При закрытии окна появится диалог с тремя вариантами:
- **Свернуть** — минимизировать окно в панель задач
- **Выйти** — полностью закрыть браузер
- **Отмена** — продолжить работу
## Оптимизации
- Кэширование WebView для быстрой загрузки
- Асинхронная обработка навигации
- Оптимизированный User-Agent
- Фильтрация служебных URL в истории
- Ограничение истории до 100 записей
## Структура проекта
```
src/
├── main/
│ ├── java/com/browser/
│ │ ├── BrowserApp.java # Главный класс приложения
│ │ ├── BrowserController.java # Контроллер UI и логики
│ │ ├── DataManager.java # Управление данными
│ │ └── Launcher.java # Launcher для JAR
│ └── resources/
│ └── styles/
│ └── browser.css # Стили интерфейса
└── pom.xml # Maven конфигурация
```
## Хранение данных
Все данные сохраняются в:
- Windows: `C:\Users\[username]\.javabrowser\`
- Linux/Mac: `~/.javabrowser/`
Файлы:
- `bookmarks.json` — закладки
- `history.json` — история
- `settings.json` — настройки (поисковая система)

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.browser</groupId>
<artifactId>java-browser</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>com.browser.BrowserApp</mainClass>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer>
<mainClass>com.browser.Launcher</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>21</javafx.version>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
</properties>
</project>

69
pom.xml Normal file
View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.browser</groupId>
<artifactId>java-browser</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javafx.version>21</javafx.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>com.browser.BrowserApp</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.browser.Launcher</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,26 @@
package com.browser;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class BrowserApp extends Application {
@Override
public void start(Stage primaryStage) {
BrowserController controller = new BrowserController(primaryStage);
Scene scene = new Scene(controller.getRoot(), 1280, 800);
scene.getStylesheets().add(getClass().getResource("/styles/browser.css").toExternalForm());
primaryStage.setTitle("NeveTime Browser");
primaryStage.setScene(scene);
primaryStage.setResizable(true);
primaryStage.setMinWidth(600);
primaryStage.setMinHeight(600);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

View File

@@ -0,0 +1,403 @@
package com.browser;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Worker;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.net.URLDecoder;
public class BrowserController {
private BorderPane root;
private TabPane tabPane;
private TextField urlField;
private ComboBox<String> searchEngineCombo;
private VBox sidePanel;
private ListView<String> bookmarksList;
private ListView<String> historyList;
private DataManager dataManager;
private ObservableList<String> bookmarks;
private ObservableList<String> history;
private Stage stage;
private static final String[] SEARCH_ENGINES = {"Google", "DuckDuckGo"};
public BrowserController(Stage stage) {
this.stage = stage;
dataManager = new DataManager();
bookmarks = FXCollections.observableArrayList(dataManager.loadBookmarks());
history = FXCollections.observableArrayList(dataManager.loadHistory());
initUI();
}
private void initUI() {
root = new BorderPane();
root.getStyleClass().add("browser-root");
root.setTop(createToolbar());
tabPane = new TabPane();
tabPane.getStyleClass().add("browser-tabs");
sidePanel = createSidePanel();
sidePanel.setVisible(false);
sidePanel.setManaged(false);
HBox center = new HBox(tabPane, sidePanel);
HBox.setHgrow(tabPane, Priority.ALWAYS);
root.setCenter(center);
createNewTab();
}
private HBox createToolbar() {
HBox toolbar = new HBox(8);
toolbar.setPadding(new Insets(10));
toolbar.setAlignment(Pos.CENTER_LEFT);
toolbar.getStyleClass().add("toolbar");
Button backBtn = createNavButton("", "Назад");
backBtn.setOnAction(e -> goBack());
Button forwardBtn = createNavButton("", "Вперёд");
forwardBtn.setOnAction(e -> goForward());
Button refreshBtn = createNavButton("", "Обновить");
refreshBtn.setOnAction(e -> refresh());
Button homeBtn = createNavButton("🏠", "Домой");
homeBtn.setOnAction(e -> showStartPage());
// Левый спейсер для центрирования поиска
Region leftSpacer = new Region();
HBox.setHgrow(leftSpacer, Priority.ALWAYS);
urlField = new TextField();
urlField.setPromptText("Введите URL или поисковый запрос...");
urlField.getStyleClass().add("url-field");
urlField.setPrefWidth(600);
urlField.setMaxWidth(600);
urlField.setOnAction(e -> navigate(urlField.getText()));
searchEngineCombo = new ComboBox<>();
searchEngineCombo.getItems().addAll(SEARCH_ENGINES);
searchEngineCombo.setValue(dataManager.loadSearchEngine());
searchEngineCombo.getStyleClass().add("search-combo");
searchEngineCombo.setOnAction(e -> dataManager.saveSearchEngine(searchEngineCombo.getValue()));
// Правый спейсер для центрирования поиска
Region rightSpacer = new Region();
HBox.setHgrow(rightSpacer, Priority.ALWAYS);
Button addBookmarkBtn = createNavButton("", "Добавить закладку");
addBookmarkBtn.setOnAction(e -> addBookmark());
Button panelBtn = createNavButton("", "Панель");
panelBtn.setOnAction(e -> toggleSidePanel());
Button newTabBtn = createNavButton("+", "Новая вкладка");
newTabBtn.setOnAction(e -> createNewTab());
Button maximizeBtn = createNavButton("", "Развернуть/Восстановить");
maximizeBtn.setOnAction(e -> stage.setMaximized(!stage.isMaximized()));
toolbar.getChildren().addAll(backBtn, forwardBtn, refreshBtn, homeBtn,
leftSpacer, urlField, searchEngineCombo, rightSpacer,
addBookmarkBtn, panelBtn, newTabBtn, maximizeBtn);
return toolbar;
}
private Button createNavButton(String text, String tooltip) {
Button btn = new Button(text);
btn.getStyleClass().add("nav-button");
btn.setTooltip(new Tooltip(tooltip));
return btn;
}
private VBox createSidePanel() {
VBox panel = new VBox(10);
panel.setPadding(new Insets(10));
panel.setPrefWidth(280);
panel.getStyleClass().add("side-panel");
Label bookmarksLabel = new Label("📑 Закладки");
bookmarksLabel.getStyleClass().add("panel-label");
bookmarksList = new ListView<>(bookmarks);
bookmarksList.setPrefHeight(200);
bookmarksList.getStyleClass().add("panel-list");
bookmarksList.setOnMouseClicked(e -> {
if (e.getClickCount() == 2) {
String selected = bookmarksList.getSelectionModel().getSelectedItem();
if (selected != null) navigate(selected);
}
});
Button deleteBookmarkBtn = new Button("Удалить закладку");
deleteBookmarkBtn.getStyleClass().add("panel-button");
deleteBookmarkBtn.setOnAction(e -> {
String selected = bookmarksList.getSelectionModel().getSelectedItem();
if (selected != null) {
bookmarks.remove(selected);
dataManager.saveBookmarks(bookmarks);
}
});
Label historyLabel = new Label("📜 История");
historyLabel.getStyleClass().add("panel-label");
historyList = new ListView<>(history);
historyList.setPrefHeight(250);
historyList.getStyleClass().add("panel-list");
historyList.setOnMouseClicked(e -> {
if (e.getClickCount() == 2) {
String selected = historyList.getSelectionModel().getSelectedItem();
if (selected != null) {
String url = selected.split(" - ")[0];
navigate(url);
}
}
});
Button clearHistoryBtn = new Button("Очистить историю");
clearHistoryBtn.getStyleClass().add("panel-button");
clearHistoryBtn.setOnAction(e -> {
history.clear();
dataManager.saveHistory(history);
});
panel.getChildren().addAll(bookmarksLabel, bookmarksList, deleteBookmarkBtn,
new Separator(), historyLabel, historyList, clearHistoryBtn);
return panel;
}
private void createNewTab() {
Tab tab = new Tab("Новая вкладка");
tab.setClosable(tabPane.getTabs().size() > 0);
WebView webView = new WebView();
webView.setCache(true);
webView.setContextMenuEnabled(true);
WebEngine engine = webView.getEngine();
engine.setJavaScriptEnabled(true);
engine.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36");
engine.titleProperty().addListener((obs, oldTitle, newTitle) -> {
if (newTitle != null && !newTitle.isEmpty()) {
tab.setText(newTitle.length() > 20 ? newTitle.substring(0, 20) + "..." : newTitle);
}
});
engine.locationProperty().addListener((obs, oldLoc, newLoc) -> {
if (newLoc != null && tabPane.getSelectionModel().getSelectedItem() == tab) {
// Перехват поиска со стартовой страницы
if (newLoc.startsWith("search:")) {
try {
String query = URLDecoder.decode(newLoc.substring(7), "UTF-8");
Platform.runLater(() -> navigate(query));
} catch (Exception e) {
e.printStackTrace();
}
} else if (!newLoc.startsWith("data:")) {
urlField.setText(newLoc);
}
}
});
// Индикатор загрузки
engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
if (newState == Worker.State.RUNNING) {
tab.setText("Загрузка...");
}
});
tab.setContent(webView);
tab.setUserData(webView);
tabPane.getTabs().add(tab);
tabPane.getSelectionModel().select(tab);
showStartPage();
}
private void showStartPage() {
WebView webView = getCurrentWebView();
if (webView != null) {
webView.getEngine().loadContent(getStartPageHTML());
urlField.setText("");
}
}
private String getStartPageHTML() {
String engine = searchEngineCombo.getValue();
return "<!DOCTYPE html><html><head><meta charset='UTF-8'><style>" +
"* { margin: 0; padding: 0; box-sizing: border-box; }" +
"body { font-family: 'Segoe UI', Arial, sans-serif; " +
"background: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 100%); " +
"min-height: 100vh; display: flex; flex-direction: column; " +
"align-items: center; justify-content: center; color: white; }" +
".container { text-align: center; padding: 40px; }" +
".logo { font-size: 3.2em; font-weight: 700; margin-bottom: 8px; color: #8b5cf6; }" +
".subtitle { color: #888; margin-bottom: 40px; font-size: 1.1em; }" +
".engine-badge { display: inline-block; padding: 6px 14px; " +
"background: #252542; border: 1px solid #3a3a5a; border-radius: 16px; " +
"color: #aaa; font-size: 0.9em; margin-bottom: 35px; }" +
".search-box { display: flex; max-width: 550px; margin: 0 auto 50px; " +
"background: #1e1e32; border-radius: 12px; border: 1px solid #3a3a5a; }" +
".search-box:focus-within { border-color: #8b5cf6; }" +
".search-input { flex: 1; padding: 16px 22px; font-size: 16px; border: none; " +
"background: transparent; color: white; outline: none; }" +
".search-input::placeholder { color: #666; }" +
".search-btn { padding: 16px 28px; font-size: 14px; font-weight: 600; border: none; " +
"border-radius: 0 10px 10px 0; background: #8b5cf6; color: white; cursor: pointer; }" +
".search-btn:hover { background: #7c3aed; }" +
".quick-links { display: flex; gap: 14px; flex-wrap: wrap; justify-content: center; }" +
".quick-link { display: flex; align-items: center; gap: 10px; padding: 14px 24px; background: #252542; border-radius: 10px; " +
"color: #ddd; text-decoration: none; font-size: 14px; border: 1px solid #3a3a5a; }" +
".quick-link:hover { background: #8b5cf6; border-color: #8b5cf6; color: white; }" +
".quick-link:hover svg { fill: white; }" +
".quick-link svg { width: 18px; height: 18px; fill: #aaa; }" +
".time { position: fixed; top: 30px; right: 40px; font-size: 2.5em; " +
"color: rgba(255,255,255,0.15); font-weight: 300; }" +
"</style></head><body>" +
"<div class='time' id='clock'></div>" +
"<div class='container'>" +
"<h1 class='logo'>NeveTime Browser</h1>" +
"<p class='subtitle'>Быстрый и современный браузер</p>" +
"<div class='engine-badge'>Поиск в " + engine + "</div>" +
"<form class='search-box' onsubmit='search(event)'>" +
"<input type='text' class='search-input' id='searchInput' placeholder='Поиск в интернете...' autofocus>" +
"<button type='submit' class='search-btn'>Найти</button></form>" +
"<div class='quick-links'>" +
"<a href='https://github.com' class='quick-link'><svg viewBox='0 0 24 24'><path d='M12 0C5.37 0 0 5.37 0 12c0 5.31 3.44 9.8 8.21 11.39.6.11.82-.26.82-.57v-2.23c-3.34.73-4.04-1.61-4.04-1.61-.55-1.39-1.33-1.76-1.33-1.76-1.09-.74.08-.73.08-.73 1.2.08 1.84 1.24 1.84 1.24 1.07 1.83 2.8 1.3 3.49 1 .11-.78.42-1.3.76-1.6-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.12-.3-.54-1.52.12-3.18 0 0 1-.32 3.3 1.23a11.5 11.5 0 016 0c2.28-1.55 3.29-1.23 3.29-1.23.66 1.66.24 2.88.12 3.18.77.84 1.24 1.91 1.24 3.22 0 4.61-2.81 5.63-5.48 5.92.43.37.81 1.1.81 2.22v3.29c0 .31.22.68.83.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z'/></svg>GitHub</a>" +
"<a href='https://youtube.com' class='quick-link'><svg viewBox='0 0 24 24'><path d='M23.5 6.2a3 3 0 00-2.1-2.1C19.5 3.5 12 3.5 12 3.5s-7.5 0-9.4.5a3 3 0 00-2.1 2.1C0 8.1 0 12 0 12s0 3.9.5 5.8a3 3 0 002.1 2.1c1.9.5 9.4.5 9.4.5s7.5 0 9.4-.5a3 3 0 002.1-2.1c.5-1.9.5-5.8.5-5.8s0-3.9-.5-5.8zM9.5 15.6V8.4l6.3 3.6-6.3 3.6z'/></svg>YouTube</a>" +
"<a href='https://wikipedia.org' class='quick-link'><svg viewBox='0 0 24 24'><path d='M12.1 13.1c-.9 1.9-2.2 4.5-2.9 5.7-.6 1.1-1.1.9-1.5 0-1.4-3.3-4.3-9.1-5.7-12.4-.3-.6-.4-1-.6-1.1-.2-.2-.6-.2-1.1-.3-.2 0-.3-.1-.3-.2v-.5h5.4v.5c0 .1-.1.2-.2.2l-.6.1c-.5 0-.7.2-.7.4 0 .1.1.3.2.6l4.8 10.5 2.4-4.8-.5-1.1-1.7-3.3c-.1-.2-.3-.6-.4-.9-.7-1.4-.7-1.5-1.4-1.6-.2 0-.3-.1-.3-.2v-.5h4.3v.5c0 .1-.1.2-.2.2l-.3.1c-.8.1-.7.4-.1 1.4l1.6 3.3 1.8-3.5c.3-.6.2-.8.1-.9-.1-.1-.3-.2-.7-.2h-.3c-.1 0-.2-.1-.2-.2v-.5h4.4v.5c0 .1-.1.2-.2.2-.7 0-1.3.1-1.7.7l-.4.6-2.5 4.9 3.3 6.6c.6-1.2 2.5-4.9 3.2-6.4.3-.6.3-.9.3-1.1 0-.5-.5-.5-1.1-.6-.2 0-.2-.1-.2-.2v-.5h4v.5c0 .1-.1.2-.2.2-1.5.1-1.6.5-2.4 2.2l-3.7 7.4c-.3.5-.7.5-1 0l-3.5-6.9z'/></svg>Wikipedia</a>" +
"<a href='https://stackoverflow.com' class='quick-link'><svg viewBox='0 0 24 24'><path d='M15 22v-4h3v6H3v-6h3v4h9zm.5-10.2l.9 2.1-9.7 4.1-.9-2.1 9.7-4.1zm2.5-3.5l-1.4 1.6-8.2-6.9 1.4-1.6 8.2 6.9zM21 0l-1.7 1.3-6.4 8.6 1.7 1.3L21 0zM6 18v-2h9v2H6z'/></svg>Stack Overflow</a>" +
"</div></div>" +
"<script>" +
"function updateClock() { var now = new Date(); " +
"document.getElementById('clock').textContent = now.toLocaleTimeString('ru-RU', {hour: '2-digit', minute: '2-digit'}); }" +
"updateClock(); setInterval(updateClock, 1000);" +
"function search(e) { e.preventDefault(); var query = document.getElementById('searchInput').value; " +
"if (query) window.location.href = 'search:' + encodeURIComponent(query); }" +
"</script></body></html>";
}
private void navigate(String input) {
if (input == null || input.trim().isEmpty()) return;
input = input.trim();
WebView webView = getCurrentWebView();
if (webView == null) return;
String url;
if (input.startsWith("search:")) {
try {
String query = URLDecoder.decode(input.substring(7), "UTF-8");
url = getSearchUrl(query);
} catch (Exception e) {
url = getSearchUrl(input.substring(7));
}
} else if (input.matches("^https?://.*")) {
url = input;
} else if (input.contains(".") && !input.contains(" ")) {
url = "https://" + input;
} else {
url = getSearchUrl(input);
}
final String finalUrl = url;
Platform.runLater(() -> {
webView.getEngine().load(finalUrl);
addToHistory(finalUrl);
});
}
private String getSearchUrl(String query) {
String engine = searchEngineCombo.getValue();
try {
query = java.net.URLEncoder.encode(query, "UTF-8");
} catch (Exception e) {}
return switch (engine) {
case "Yandex" -> "https://yandex.ru/search/?text=" + query;
case "DuckDuckGo" -> "https://duckduckgo.com/?q=" + query;
case "Bing" -> "https://www.bing.com/search?q=" + query;
default -> "https://www.google.com/search?q=" + query;
};
}
private void addToHistory(String url) {
if (url == null || url.isEmpty() || url.startsWith("data:") || url.startsWith("about:")) return;
Platform.runLater(() -> {
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm dd.MM"));
String entry = url + " - " + timestamp;
history.add(0, entry);
if (history.size() > 100) history.remove(history.size() - 1);
dataManager.saveHistory(history);
});
}
private void addBookmark() {
WebView webView = getCurrentWebView();
if (webView != null) {
String url = webView.getEngine().getLocation();
if (url != null && !url.isEmpty() && !url.startsWith("data:") && !bookmarks.contains(url)) {
bookmarks.add(url);
dataManager.saveBookmarks(bookmarks);
showAlert("Закладка добавлена", url);
}
}
}
private void showAlert(String title, String message) {
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(message);
alert.showAndWait();
});
}
private void goBack() {
WebView webView = getCurrentWebView();
if (webView != null && webView.getEngine().getHistory().getCurrentIndex() > 0) {
webView.getEngine().getHistory().go(-1);
}
}
private void goForward() {
WebView webView = getCurrentWebView();
if (webView != null) {
var hist = webView.getEngine().getHistory();
if (hist.getCurrentIndex() < hist.getEntries().size() - 1) {
hist.go(1);
}
}
}
private void refresh() {
WebView webView = getCurrentWebView();
if (webView != null) webView.getEngine().reload();
}
private void toggleSidePanel() {
sidePanel.setVisible(!sidePanel.isVisible());
sidePanel.setManaged(sidePanel.isVisible());
}
private WebView getCurrentWebView() {
Tab tab = tabPane.getSelectionModel().getSelectedItem();
return tab != null ? (WebView) tab.getUserData() : null;
}
public BorderPane getRoot() { return root; }
}

View File

@@ -0,0 +1,82 @@
package com.browser;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.*;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
public class DataManager {
private static final String DATA_DIR = System.getProperty("user.home") + "/.javabrowser";
private static final String BOOKMARKS_FILE = DATA_DIR + "/bookmarks.json";
private static final String HISTORY_FILE = DATA_DIR + "/history.json";
private static final String SETTINGS_FILE = DATA_DIR + "/settings.json";
private final Gson gson = new Gson();
public DataManager() {
try {
Files.createDirectories(Paths.get(DATA_DIR));
} catch (IOException e) {
e.printStackTrace();
}
}
public List<String> loadBookmarks() {
return loadList(BOOKMARKS_FILE);
}
public void saveBookmarks(List<String> bookmarks) {
saveList(BOOKMARKS_FILE, bookmarks);
}
public List<String> loadHistory() {
return loadList(HISTORY_FILE);
}
public void saveHistory(List<String> history) {
saveList(HISTORY_FILE, history);
}
public String loadSearchEngine() {
try {
String content = Files.readString(Paths.get(SETTINGS_FILE));
Settings settings = gson.fromJson(content, Settings.class);
return settings != null && settings.searchEngine != null ? settings.searchEngine : "Google";
} catch (IOException e) {
return "Google";
}
}
public void saveSearchEngine(String engine) {
try {
Settings settings = new Settings();
settings.searchEngine = engine;
Files.writeString(Paths.get(SETTINGS_FILE), gson.toJson(settings));
} catch (IOException e) {
e.printStackTrace();
}
}
private List<String> loadList(String file) {
try {
String content = Files.readString(Paths.get(file));
List<String> list = gson.fromJson(content, new TypeToken<List<String>>(){}.getType());
return list != null ? new ArrayList<>(list) : new ArrayList<>();
} catch (IOException e) {
return new ArrayList<>();
}
}
private void saveList(String file, List<String> list) {
try {
Files.writeString(Paths.get(file), gson.toJson(list));
} catch (IOException e) {
e.printStackTrace();
}
}
private static class Settings {
String searchEngine;
}
}

View File

@@ -0,0 +1,8 @@
package com.browser;
// Launcher class needed for JAR execution with JavaFX
public class Launcher {
public static void main(String[] args) {
BrowserApp.main(args);
}
}

View File

@@ -0,0 +1,184 @@
/* Lightweight Modern Theme */
.browser-root {
-fx-background-color: #0f0f1a;
}
.toolbar {
-fx-background-color: #1a1a2e;
-fx-padding: 10px 16px;
-fx-border-color: #2a2a4a;
-fx-border-width: 0 0 1px 0;
}
.nav-button {
-fx-background-color: #252542;
-fx-text-fill: #e0e0e0;
-fx-font-size: 14px;
-fx-padding: 8px 14px;
-fx-background-radius: 8px;
-fx-border-radius: 8px;
-fx-border-color: #3a3a5a;
-fx-border-width: 1px;
-fx-cursor: hand;
}
.nav-button:hover {
-fx-background-color: #8b5cf6;
-fx-border-color: #8b5cf6;
}
.nav-button:pressed {
-fx-background-color: #7c3aed;
}
.url-field {
-fx-background-color: #1e1e32;
-fx-text-fill: white;
-fx-prompt-text-fill: #666;
-fx-font-size: 14px;
-fx-padding: 10px 20px;
-fx-background-radius: 12px;
-fx-border-radius: 12px;
-fx-border-color: #3a3a5a;
-fx-border-width: 1px;
}
.url-field:focused {
-fx-border-color: #8b5cf6;
}
.search-combo {
-fx-background-color: #252542;
-fx-border-color: #3a3a5a;
-fx-border-radius: 8px;
-fx-background-radius: 8px;
-fx-padding: 6px 12px;
}
.search-combo .list-cell {
-fx-text-fill: white;
-fx-background-color: transparent;
}
.search-combo .combo-box-popup .list-view {
-fx-background-color: #1a1a2e;
}
.search-combo .combo-box-popup .list-cell {
-fx-background-color: #1a1a2e;
-fx-text-fill: white;
-fx-padding: 10px 14px;
}
.search-combo .combo-box-popup .list-cell:hover {
-fx-background-color: #8b5cf6;
}
.browser-tabs {
-fx-background-color: #0f0f1a;
}
.browser-tabs .tab-header-area {
-fx-background-color: #12121f;
-fx-padding: 6px 6px 0 6px;
}
.browser-tabs .tab-header-background {
-fx-background-color: #12121f;
}
.browser-tabs .tab {
-fx-background-color: #1a1a2e;
-fx-background-radius: 8px 8px 0 0;
-fx-padding: 8px 16px;
}
.browser-tabs .tab:selected {
-fx-background-color: #252542;
}
.browser-tabs .tab .tab-label {
-fx-text-fill: #aaa;
-fx-font-size: 13px;
}
.browser-tabs .tab:selected .tab-label {
-fx-text-fill: white;
}
.side-panel {
-fx-background-color: #1a1a2e;
-fx-border-color: #2a2a4a;
-fx-border-width: 0 0 0 1px;
}
.panel-label {
-fx-text-fill: white;
-fx-font-size: 15px;
-fx-font-weight: bold;
-fx-padding: 6px 0;
}
.panel-list {
-fx-background-color: #1e1e32;
-fx-background-radius: 8px;
}
.panel-list .list-cell {
-fx-background-color: transparent;
-fx-text-fill: #ccc;
-fx-padding: 10px 12px;
-fx-font-size: 12px;
}
.panel-list .list-cell:hover {
-fx-background-color: #2a2a4a;
}
.panel-list .list-cell:selected {
-fx-background-color: #8b5cf6;
-fx-text-fill: white;
}
.panel-button {
-fx-background-color: #8b5cf6;
-fx-text-fill: white;
-fx-padding: 10px 16px;
-fx-background-radius: 8px;
-fx-cursor: hand;
-fx-font-size: 12px;
}
.panel-button:hover {
-fx-background-color: #7c3aed;
}
.separator .line {
-fx-border-color: #2a2a4a;
}
.scroll-bar {
-fx-background-color: transparent;
}
.scroll-bar .thumb {
-fx-background-color: #3a3a5a;
-fx-background-radius: 4px;
}
.scroll-bar .thumb:hover {
-fx-background-color: #8b5cf6;
}
.scroll-bar .increment-button, .scroll-bar .decrement-button {
-fx-background-color: transparent;
-fx-padding: 0;
}
.tooltip {
-fx-background-color: #252542;
-fx-text-fill: white;
-fx-font-size: 12px;
-fx-padding: 8px 12px;
-fx-background-radius: 6px;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,184 @@
/* Lightweight Modern Theme */
.browser-root {
-fx-background-color: #0f0f1a;
}
.toolbar {
-fx-background-color: #1a1a2e;
-fx-padding: 10px 16px;
-fx-border-color: #2a2a4a;
-fx-border-width: 0 0 1px 0;
}
.nav-button {
-fx-background-color: #252542;
-fx-text-fill: #e0e0e0;
-fx-font-size: 14px;
-fx-padding: 8px 14px;
-fx-background-radius: 8px;
-fx-border-radius: 8px;
-fx-border-color: #3a3a5a;
-fx-border-width: 1px;
-fx-cursor: hand;
}
.nav-button:hover {
-fx-background-color: #8b5cf6;
-fx-border-color: #8b5cf6;
}
.nav-button:pressed {
-fx-background-color: #7c3aed;
}
.url-field {
-fx-background-color: #1e1e32;
-fx-text-fill: white;
-fx-prompt-text-fill: #666;
-fx-font-size: 14px;
-fx-padding: 10px 20px;
-fx-background-radius: 12px;
-fx-border-radius: 12px;
-fx-border-color: #3a3a5a;
-fx-border-width: 1px;
}
.url-field:focused {
-fx-border-color: #8b5cf6;
}
.search-combo {
-fx-background-color: #252542;
-fx-border-color: #3a3a5a;
-fx-border-radius: 8px;
-fx-background-radius: 8px;
-fx-padding: 6px 12px;
}
.search-combo .list-cell {
-fx-text-fill: white;
-fx-background-color: transparent;
}
.search-combo .combo-box-popup .list-view {
-fx-background-color: #1a1a2e;
}
.search-combo .combo-box-popup .list-cell {
-fx-background-color: #1a1a2e;
-fx-text-fill: white;
-fx-padding: 10px 14px;
}
.search-combo .combo-box-popup .list-cell:hover {
-fx-background-color: #8b5cf6;
}
.browser-tabs {
-fx-background-color: #0f0f1a;
}
.browser-tabs .tab-header-area {
-fx-background-color: #12121f;
-fx-padding: 6px 6px 0 6px;
}
.browser-tabs .tab-header-background {
-fx-background-color: #12121f;
}
.browser-tabs .tab {
-fx-background-color: #1a1a2e;
-fx-background-radius: 8px 8px 0 0;
-fx-padding: 8px 16px;
}
.browser-tabs .tab:selected {
-fx-background-color: #252542;
}
.browser-tabs .tab .tab-label {
-fx-text-fill: #aaa;
-fx-font-size: 13px;
}
.browser-tabs .tab:selected .tab-label {
-fx-text-fill: white;
}
.side-panel {
-fx-background-color: #1a1a2e;
-fx-border-color: #2a2a4a;
-fx-border-width: 0 0 0 1px;
}
.panel-label {
-fx-text-fill: white;
-fx-font-size: 15px;
-fx-font-weight: bold;
-fx-padding: 6px 0;
}
.panel-list {
-fx-background-color: #1e1e32;
-fx-background-radius: 8px;
}
.panel-list .list-cell {
-fx-background-color: transparent;
-fx-text-fill: #ccc;
-fx-padding: 10px 12px;
-fx-font-size: 12px;
}
.panel-list .list-cell:hover {
-fx-background-color: #2a2a4a;
}
.panel-list .list-cell:selected {
-fx-background-color: #8b5cf6;
-fx-text-fill: white;
}
.panel-button {
-fx-background-color: #8b5cf6;
-fx-text-fill: white;
-fx-padding: 10px 16px;
-fx-background-radius: 8px;
-fx-cursor: hand;
-fx-font-size: 12px;
}
.panel-button:hover {
-fx-background-color: #7c3aed;
}
.separator .line {
-fx-border-color: #2a2a4a;
}
.scroll-bar {
-fx-background-color: transparent;
}
.scroll-bar .thumb {
-fx-background-color: #3a3a5a;
-fx-background-radius: 4px;
}
.scroll-bar .thumb:hover {
-fx-background-color: #8b5cf6;
}
.scroll-bar .increment-button, .scroll-bar .decrement-button {
-fx-background-color: transparent;
-fx-padding: 0;
}
.tooltip {
-fx-background-color: #252542;
-fx-text-fill: white;
-fx-font-size: 12px;
-fx-padding: 8px 12px;
-fx-background-radius: 6px;
}

Binary file not shown.

View File

@@ -0,0 +1,3 @@
artifactId=java-browser
groupId=com.browser
version=1.0-SNAPSHOT

View File

@@ -0,0 +1,6 @@
com\browser\Launcher.class
com\browser\BrowserApp.class
com\browser\DataManager$Settings.class
com\browser\BrowserController.class
com\browser\DataManager$1.class
com\browser\DataManager.class

View File

@@ -0,0 +1,4 @@
d:\Desktop\adadad\src\main\java\com\browser\BrowserApp.java
d:\Desktop\adadad\src\main\java\com\browser\BrowserController.java
d:\Desktop\adadad\src\main\java\com\browser\DataManager.java
d:\Desktop\adadad\src\main\java\com\browser\Launcher.java

Binary file not shown.