From 324df5fa9fc2db50c8e40225fb66c539f4c47fc1 Mon Sep 17 00:00:00 2001 From: Arthur Poiret Date: Sun, 26 Apr 2026 17:35:42 +0200 Subject: [PATCH 1/4] feat: add app home overview tab --- .../core/controllers/HomeController.java | 291 ++++++++++++++++++ .../core/controllers/MainController.java | 1 + .../plugin/controllers/PluginsController.java | 4 + .../repositories/FileStatRepository.java | 2 + .../plugin/repositories/PluginRepository.java | 6 +- .../repositories/PluginLookupRepository.java | 4 + .../project/services/ProjectService.java | 8 + .../src/main/resources/fxml/HomeView.fxml | 276 +++++++++++++++++ .../src/main/resources/fxml/MainView.fxml | 7 +- owlplug-client/src/main/resources/owlplug.css | 104 +++++++ 10 files changed, 698 insertions(+), 5 deletions(-) create mode 100644 owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java create mode 100644 owlplug-client/src/main/resources/fxml/HomeView.fxml diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java new file mode 100644 index 00000000..075534fb --- /dev/null +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java @@ -0,0 +1,291 @@ +/* OwlPlug + * Copyright (C) 2021 Arthur + * + * This file is part of OwlPlug. + * + * OwlPlug is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 + * as published by the Free Software Foundation. + * + * OwlPlug is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OwlPlug. If not, see . + */ + +package com.owlplug.core.controllers; + +import com.owlplug.core.components.ApplicationDefaults; +import com.owlplug.core.controllers.dialogs.ListDirectoryDialogController; +import com.owlplug.core.utils.FX; +import com.owlplug.plugin.controllers.PluginsController; +import com.owlplug.plugin.events.PluginUpdateEvent; +import com.owlplug.plugin.model.FileStat; +import com.owlplug.plugin.model.PluginFormat; +import com.owlplug.plugin.repositories.FileStatRepository; +import com.owlplug.plugin.repositories.PluginRepository; +import com.owlplug.plugin.services.PluginService; +import com.owlplug.project.model.LookupResult; +import com.owlplug.project.repositories.DawProjectRepository; +import com.owlplug.project.repositories.PluginLookupRepository; +import com.owlplug.project.services.ProjectService; +import java.util.List; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.chart.BarChart; +import javafx.scene.chart.CategoryAxis; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.XYChart; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.scene.shape.Rectangle; +import org.apache.commons.io.FileUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Controller; + +@Controller +public class HomeController extends BaseController { + + @Autowired + private PluginRepository pluginRepository; + @Autowired + private DawProjectRepository dawProjectRepository; + @Autowired + private PluginLookupRepository pluginLookupRepository; + @Autowired + private FileStatRepository fileStatRepository; + @Autowired + private PluginService pluginService; + @Autowired + private ProjectService projectService; + @Autowired + @Lazy + private MainController mainController; + @Autowired + @Lazy + private PluginsController pluginsController; + @Autowired + private ListDirectoryDialogController listDirectoryDialogController; + + @FXML + private Label pluginCountLabel; + @FXML + private Label vst2CountLabel; + @FXML + private Label vst3CountLabel; + @FXML + private Label auCountLabel; + @FXML + private Label lv2CountLabel; + @FXML + private Label projectCountLabel; + @FXML + private Label unresolvedPluginCountLabel; + @FXML + private Label disabledCountLabel; + @FXML + private Label pluginDirectoryCountLabel; + @FXML + private Label projectDirectoryCountLabel; + + @FXML + private VBox pluginTile; + @FXML + private VBox projectTile; + + @FXML + private VBox fileSizeChartContainer; + @FXML + private Label fileSizeEmptyLabel; + + @FXML + private VBox setupPane; + @FXML + private VBox noPluginDirectorySuggestion; + @FXML + private VBox noPluginSuggestion; + @FXML + private VBox noProjectSuggestion; + + @FXML + private TextField pluginSearchField; + + @FXML + private Button scanPluginsButton; + @FXML + private Button exploreButton; + @FXML + private Button syncProjectsButton; + @FXML + private Button settingsButton; + @FXML + private Button setupPluginDirButton; + @FXML + private Button scanSuggestionButton; + @FXML + private Button setupProjectDirButton; + + private BarChart fileSizeChart; + + /** + * FXML initialize method. + */ + @FXML + public void initialize() { + pluginTile.setOnMouseClicked(e -> mainController.navigateToMainTab(MainController.PLUGINS_TAB_INDEX)); + projectTile.setOnMouseClicked(e -> mainController.navigateToMainTab(MainController.PROJECTS_TAB_INDEX)); + + pluginSearchField.setOnAction(e -> { + mainController.navigateToMainTab(MainController.PLUGINS_TAB_INDEX); + pluginsController.setSearch(pluginSearchField.getText()); + }); + + scanPluginsButton.setOnAction(e -> pluginService.scanPlugins()); + exploreButton.setOnAction(e -> mainController.navigateToMainTab(MainController.EXPLORE_TAB_INDEX)); + syncProjectsButton.setOnAction(e -> projectService.syncProjects()); + settingsButton.setOnAction(e -> mainController.navigateToMainTab(MainController.OPTIONS_TAB_INDEX)); + + setupPluginDirButton.setOnAction(e -> mainController.navigateToMainTab(MainController.OPTIONS_TAB_INDEX)); + scanSuggestionButton.setOnAction(e -> pluginService.scanPlugins()); + setupProjectDirButton.setOnAction(e -> { + listDirectoryDialogController.configure(ApplicationDefaults.PROJECT_DIRECTORY_KEY); + listDirectoryDialogController.show(); + }); + + initFileSizeChart(); + refresh(); + } + + private void initFileSizeChart() { + NumberAxis sizeAxis = new NumberAxis(); + sizeAxis.setTickLabelsVisible(false); + sizeAxis.setTickMarkVisible(false); + sizeAxis.setMinorTickVisible(false); + + CategoryAxis nameAxis = new CategoryAxis(); + + fileSizeChart = new BarChart<>(sizeAxis, nameAxis); + fileSizeChart.setAnimated(true); + fileSizeChart.setLegendVisible(false); + fileSizeChart.setCategoryGap(12); + fileSizeChart.setBarGap(0); + fileSizeChart.setMaxWidth(Double.MAX_VALUE); + fileSizeChart.setMaxHeight(Double.MAX_VALUE); + fileSizeChart.getStyleClass().add("dashboard-bar-chart"); + VBox.setVgrow(fileSizeChart, javafx.scene.layout.Priority.ALWAYS); + + fileSizeChartContainer.getChildren().add(fileSizeChart); + } + + /** + * Refreshes all dashboard statistics and updates setup suggestion visibility. + */ + public void refresh() { + final long totalPlugins = pluginRepository.count(); + final long vst2Count = pluginRepository.countByFormat(PluginFormat.VST2); + final long vst3Count = pluginRepository.countByFormat(PluginFormat.VST3); + final long auCount = pluginRepository.countByFormat(PluginFormat.AU); + final long lv2Count = pluginRepository.countByFormat(PluginFormat.LV2); + final long projectCount = dawProjectRepository.count(); + final long unresolvedCount = pluginLookupRepository.countByResult(LookupResult.MISSING); + final long disabledCount = pluginRepository.countByDisabledTrue(); + final long pluginDirectoryCount = pluginService.getDirectoriesExplorationSet().size(); + final long projectDirectoryCount = projectService.getProjectDirectories().size(); + + + pluginCountLabel.setText(String.valueOf(totalPlugins)); + vst2CountLabel.setText(String.valueOf(vst2Count)); + vst3CountLabel.setText(String.valueOf(vst3Count)); + auCountLabel.setText(String.valueOf(auCount)); + lv2CountLabel.setText(String.valueOf(lv2Count)); + projectCountLabel.setText(String.valueOf(projectCount)); + unresolvedPluginCountLabel.setText(String.valueOf(unresolvedCount)); + disabledCountLabel.setText(String.valueOf(disabledCount)); + pluginDirectoryCountLabel.setText(String.valueOf(pluginDirectoryCount)); + projectDirectoryCountLabel.setText(String.valueOf(projectDirectoryCount)); + + final boolean hasPluginDirectories = !pluginService.getDirectoriesExplorationSet().isEmpty(); + final boolean hasProjectDirectories = !projectService.getProjectDirectories().isEmpty(); + final boolean hasPlugins = totalPlugins > 0; + + setNodeVisible(noPluginDirectorySuggestion, !hasPluginDirectories); + setNodeVisible(noPluginSuggestion, hasPluginDirectories && !hasPlugins); + setNodeVisible(noProjectSuggestion, !hasProjectDirectories); + setNodeVisible(setupPane, !hasPluginDirectories || !hasPlugins || !hasProjectDirectories); + + refreshFileSizeChart(); + } + + private void refreshFileSizeChart() { + final List topStats = fileStatRepository.findTop5ByParentIsNullOrderByLengthDesc(); + final boolean hasData = !topStats.isEmpty(); + + fileSizeEmptyLabel.setVisible(!hasData); + fileSizeEmptyLabel.setManaged(!hasData); + fileSizeChart.setVisible(hasData); + fileSizeChart.setManaged(hasData); + + if (!hasData) { + return; + } + + fileSizeChart.getData().clear(); + + XYChart.Series series = new XYChart.Series<>(); + for (FileStat stat : topStats) { + final double mb = stat.getLength() / (1024.0 * 1024.0); + series.getData().add(new XYChart.Data<>(mb, stat.getName())); + } + fileSizeChart.getData().add(series); + + // Defer label and tooltip attachment until bars are laid out in the scene graph + Platform.runLater(() -> series.getData().forEach(data -> + topStats.stream() + .filter(fs -> fs.getName().equals(data.getYValue())) + .findFirst() + .ifPresent(fs -> { + final String sizeText = FileUtils.byteCountToDisplaySize(fs.getLength()); + Tooltip.install(data.getNode(), new Tooltip(data.getYValue() + " — " + sizeText)); + if (data.getNode() instanceof StackPane bar) { + Label label = new Label(sizeText); + label.getStyleClass().add("dashboard-bar-label"); + label.setMouseTransparent(true); + StackPane.setAlignment(label, Pos.CENTER_RIGHT); + StackPane.setMargin(label, new Insets(0, 6, 0, 0)); + bar.getChildren().add(label); + + // Clip the bar to exactly 20px, centered in the allocated slot + Rectangle clip = new Rectangle(); + clip.setArcWidth(4); + clip.setArcHeight(4); + clip.setHeight(20); + clip.widthProperty().bind(bar.widthProperty()); + clip.yProperty().bind(bar.heightProperty().subtract(20).divide(2)); + bar.setClip(clip); + } + }) + )); + } + + private void setNodeVisible(VBox node, boolean visible) { + node.setVisible(visible); + node.setManaged(visible); + } + + @EventListener + private void handle(PluginUpdateEvent event) { + FX.run(this::refresh); + } + +} \ No newline at end of file diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/MainController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/MainController.java index 07d4dd3a..6bea6769 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/controllers/MainController.java +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/MainController.java @@ -110,6 +110,7 @@ public class MainController extends BaseController { @FXML private Button downloadUpdateButton; + public static int HOME_TAB_INDEX = 0; public static int PLUGINS_TAB_INDEX = 1; public static int EXPLORE_TAB_INDEX = 2; public static int PROJECTS_TAB_INDEX = 3; diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginsController.java b/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginsController.java index 45fcbb5b..ef5ab4c1 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginsController.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginsController.java @@ -230,6 +230,10 @@ public void refresh() { tableController.refresh(); } + public void setSearch(String query) { + searchTextField.setText(query); + } + public void setInfoPaneDisplay(boolean display) { pluginInfoPane.setManaged(display); pluginInfoPane.setVisible(display); diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/repositories/FileStatRepository.java b/owlplug-client/src/main/java/com/owlplug/plugin/repositories/FileStatRepository.java index 4f7b089f..d61f1191 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/repositories/FileStatRepository.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/repositories/FileStatRepository.java @@ -33,6 +33,8 @@ public interface FileStatRepository extends JpaRepository { List findByParentPathOrderByLengthDesc(String parentPath); + List findTop5ByParentIsNullOrderByLengthDesc(); + @Transactional @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("delete from FileStat f where f.path=:path") diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/repositories/PluginRepository.java b/owlplug-client/src/main/java/com/owlplug/plugin/repositories/PluginRepository.java index 70260c42..f6c3b8e3 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/repositories/PluginRepository.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/repositories/PluginRepository.java @@ -49,8 +49,12 @@ static Specification hasComponentName(String name) { Plugin findByPath(String path); - + List findBySyncComplete(boolean syncComplete); + + long countByFormat(PluginFormat format); + + long countByDisabledTrue(); @Transactional void deleteByPathContainingIgnoreCase(String path); diff --git a/owlplug-client/src/main/java/com/owlplug/project/repositories/PluginLookupRepository.java b/owlplug-client/src/main/java/com/owlplug/project/repositories/PluginLookupRepository.java index 069ebbbc..2a7dffec 100644 --- a/owlplug-client/src/main/java/com/owlplug/project/repositories/PluginLookupRepository.java +++ b/owlplug-client/src/main/java/com/owlplug/project/repositories/PluginLookupRepository.java @@ -19,7 +19,11 @@ package com.owlplug.project.repositories; import com.owlplug.project.model.DawPluginLookup; +import com.owlplug.project.model.LookupResult; import org.springframework.data.repository.CrudRepository; public interface PluginLookupRepository extends CrudRepository { + + long countByResult(LookupResult result); + } diff --git a/owlplug-client/src/main/java/com/owlplug/project/services/ProjectService.java b/owlplug-client/src/main/java/com/owlplug/project/services/ProjectService.java index c2eb168b..28cccc22 100644 --- a/owlplug-client/src/main/java/com/owlplug/project/services/ProjectService.java +++ b/owlplug-client/src/main/java/com/owlplug/project/services/ProjectService.java @@ -18,10 +18,14 @@ package com.owlplug.project.services; +import com.owlplug.core.components.ApplicationDefaults; import com.owlplug.core.services.BaseService; import com.owlplug.project.components.ProjectTaskFactory; import com.owlplug.project.model.DawProject; import com.owlplug.project.repositories.DawProjectRepository; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -41,4 +45,8 @@ public Iterable getAllProjects() { return dawProjectRepository.findAll(); } + public Set getProjectDirectories() { + return new HashSet<>(this.getPreferences().getList(ApplicationDefaults.PROJECT_DIRECTORY_KEY)); + } + } diff --git a/owlplug-client/src/main/resources/fxml/HomeView.fxml b/owlplug-client/src/main/resources/fxml/HomeView.fxml new file mode 100644 index 00000000..247600af --- /dev/null +++ b/owlplug-client/src/main/resources/fxml/HomeView.fxml @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/owlplug-client/src/main/resources/fxml/MainView.fxml b/owlplug-client/src/main/resources/fxml/MainView.fxml index 49db49c0..131cf7c2 100644 --- a/owlplug-client/src/main/resources/fxml/MainView.fxml +++ b/owlplug-client/src/main/resources/fxml/MainView.fxml @@ -26,7 +26,7 @@ - + @@ -69,9 +69,8 @@ - - + + diff --git a/owlplug-client/src/main/resources/owlplug.css b/owlplug-client/src/main/resources/owlplug.css index 7d6e13bd..034b5758 100644 --- a/owlplug-client/src/main/resources/owlplug.css +++ b/owlplug-client/src/main/resources/owlplug.css @@ -452,34 +452,58 @@ ProgressIndicator { .default-color0.chart-pie { -fx-pie-color: #116A7B; } +.default-color0.chart-bar { + -fx-bar-fill: #116A7B; +} .default-color1.chart-pie { -fx-pie-color: #85586F; } +.default-color1.chart-bar { + -fx-bar-fill: #85586F; +} .default-color2.chart-pie { -fx-pie-color: #7F8B52; } +.default-color2.chart-bar { + -fx-bar-fill: #7F8B52; +} .default-color3.chart-pie { -fx-pie-color: #FFDBA4; } +.default-color3.chart-bar { + -fx-bar-fill: #FFDBA4; +} .default-color4.chart-pie { -fx-pie-color: #47A992; } +.default-color4.chart-bar { + -fx-bar-fill: #47A992; +} .default-color5.chart-pie { -fx-pie-color: #C84B31; } +.default-color5.chart-bar { + -fx-bar-fill: #C84B31; +} .default-color6.chart-pie { -fx-pie-color: #BCCC9A; } +.default-color6.chart-bar { + -fx-bar-fill: #BCCC9A; +} .default-color7.chart-pie { -fx-pie-color: #968C83; } +.default-color7.chart-bar { + -fx-bar-fill: #968C83; +} .chart-pie { -fx-border-width: 0px; @@ -667,6 +691,86 @@ ProgressIndicator { -fx-background-color: transparent; } +/**************************************************************** + * + * Home Dashboard + * + * **************************************************************/ + +.dashboard-tile { + -fx-background-color: card-pane-color; + -fx-background-radius: 6px; + -fx-border-radius: 6px; + -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.55), 8, 0, 0, 2); + -fx-spacing: 4; + -fx-min-width: 90px; +} + +.dashboard-tile-clickable { + -fx-cursor: hand; +} + +.dashboard-tile-clickable:hover { + -fx-background-color: rgb(60, 60, 60); +} + +.dashboard-tile-value { + -fx-font-size: 34px; + -fx-font-weight: bold; + -fx-text-fill: text-color; +} + +.dashboard-tile-value-small { + -fx-font-size: 24px; + -fx-font-weight: bold; + -fx-text-fill: text-color; +} + +.dashboard-tile-label { + -fx-font-size: 12px; + -fx-text-fill: text-emphase-color; +} + +.dashboard-section-title { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: text-emphase-color; +} + +.dashboard-bar-chart { + -fx-background-color: transparent; + -fx-padding: 0; +} + +.dashboard-bar-chart .chart-plot-background { + -fx-background-color: transparent; +} + +.dashboard-bar-chart .chart-vertical-grid-lines { + -fx-stroke: transparent; +} + +.dashboard-bar-chart .chart-horizontal-grid-lines { + -fx-stroke: transparent; +} + +.dashboard-bar-chart .axis { + -fx-tick-label-fill: text-emphase-color; +} + +.dashboard-bar-chart .axis-label { + -fx-text-fill: text-emphase-color; +} + +.dashboard-bar-chart .axis-line { + visibility: hidden; +} + +.dashboard-bar-label { + -fx-font-size: 11px; + -fx-font-weight: bold; +} + .no-padding { -fx-padding: 0; } \ No newline at end of file From 6800635c32505cc93dec52f831a372b017b96d4a Mon Sep 17 00:00:00 2001 From: Arthur Poiret Date: Sat, 20 Jun 2026 16:34:04 +0200 Subject: [PATCH 2/4] chore: align home view display with theme and revamp the welcome dialog --- .../core/controllers/HomeController.java | 57 ++- .../dialogs/WelcomeDialogController.java | 140 ++++--- .../owlplug/core/services/OptionsService.java | 4 + .../plugin/components/PluginTaskFactory.java | 7 +- .../plugin/controllers/PluginsController.java | 4 +- ...ent.java => PluginScanCompletedEvent.java} | 2 +- .../plugin/events/PluginScanStartedEvent.java | 25 ++ .../src/main/resources/fxml/HomeView.fxml | 346 ++++++++---------- .../src/main/resources/fxml/MainView.fxml | 6 +- .../resources/fxml/dialogs/WelcomeView.fxml | 96 ++++- owlplug-client/src/main/resources/owlplug.css | 54 ++- 11 files changed, 428 insertions(+), 313 deletions(-) rename owlplug-client/src/main/java/com/owlplug/plugin/events/{PluginScanEvent.java => PluginScanCompletedEvent.java} (94%) create mode 100644 owlplug-client/src/main/java/com/owlplug/plugin/events/PluginScanStartedEvent.java diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java index 075534fb..05ecf638 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java @@ -18,10 +18,10 @@ package com.owlplug.core.controllers; -import com.owlplug.core.components.ApplicationDefaults; -import com.owlplug.core.controllers.dialogs.ListDirectoryDialogController; import com.owlplug.core.utils.FX; import com.owlplug.plugin.controllers.PluginsController; +import com.owlplug.plugin.events.PluginScanCompletedEvent; +import com.owlplug.plugin.events.PluginScanStartedEvent; import com.owlplug.plugin.events.PluginUpdateEvent; import com.owlplug.plugin.model.FileStat; import com.owlplug.plugin.model.PluginFormat; @@ -33,10 +33,12 @@ import com.owlplug.project.repositories.PluginLookupRepository; import com.owlplug.project.services.ProjectService; import java.util.List; +import javafx.animation.RotateTransition; import javafx.application.Platform; import javafx.fxml.FXML; import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.util.Duration; import javafx.scene.chart.BarChart; import javafx.scene.chart.CategoryAxis; import javafx.scene.chart.NumberAxis; @@ -44,11 +46,13 @@ import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.TextField; +import javafx.scene.layout.HBox; import javafx.scene.control.Tooltip; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.shape.Rectangle; import org.apache.commons.io.FileUtils; +import org.kordamp.ikonli.javafx.FontIcon; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; @@ -76,7 +80,8 @@ public class HomeController extends BaseController { @Lazy private PluginsController pluginsController; @Autowired - private ListDirectoryDialogController listDirectoryDialogController; + @Lazy + private OptionsController optionsController; @FXML private Label pluginCountLabel; @@ -99,10 +104,21 @@ public class HomeController extends BaseController { @FXML private Label projectDirectoryCountLabel; + @FXML + private HBox scanProgressPane; + @FXML + private FontIcon scanIcon; + + private RotateTransition scanRotation; + @FXML private VBox pluginTile; @FXML private VBox projectTile; + @FXML + private VBox pluginDirTile; + @FXML + private VBox projectDirTile; @FXML private VBox fileSizeChartContainer; @@ -145,6 +161,14 @@ public class HomeController extends BaseController { public void initialize() { pluginTile.setOnMouseClicked(e -> mainController.navigateToMainTab(MainController.PLUGINS_TAB_INDEX)); projectTile.setOnMouseClicked(e -> mainController.navigateToMainTab(MainController.PROJECTS_TAB_INDEX)); + pluginDirTile.setOnMouseClicked(e -> { + mainController.navigateToMainTab(MainController.OPTIONS_TAB_INDEX); + optionsController.navigateToSection(OptionsController.SCAN_SECTION_INDEX); + }); + projectDirTile.setOnMouseClicked(e -> { + mainController.navigateToMainTab(MainController.OPTIONS_TAB_INDEX); + optionsController.navigateToSection(OptionsController.PROJECTS_SECTION_INDEX); + }); pluginSearchField.setOnAction(e -> { mainController.navigateToMainTab(MainController.PLUGINS_TAB_INDEX); @@ -159,10 +183,14 @@ public void initialize() { setupPluginDirButton.setOnAction(e -> mainController.navigateToMainTab(MainController.OPTIONS_TAB_INDEX)); scanSuggestionButton.setOnAction(e -> pluginService.scanPlugins()); setupProjectDirButton.setOnAction(e -> { - listDirectoryDialogController.configure(ApplicationDefaults.PROJECT_DIRECTORY_KEY); - listDirectoryDialogController.show(); + mainController.navigateToMainTab(MainController.OPTIONS_TAB_INDEX); + optionsController.navigateToSection(OptionsController.PROJECTS_SECTION_INDEX); }); + scanRotation = new RotateTransition(Duration.millis(700), scanIcon); + scanRotation.setByAngle(360); + scanRotation.setCycleCount(RotateTransition.INDEFINITE); + initFileSizeChart(); refresh(); } @@ -283,6 +311,25 @@ private void setNodeVisible(VBox node, boolean visible) { node.setManaged(visible); } + @EventListener + private void handle(PluginScanStartedEvent event) { + FX.run(() -> { + scanProgressPane.setVisible(true); + scanProgressPane.setManaged(true); + scanRotation.play(); + }); + } + + @EventListener + private void handle(PluginScanCompletedEvent event) { + FX.run(() -> { + scanProgressPane.setVisible(false); + scanProgressPane.setManaged(false); + scanRotation.stop(); + refresh(); + }); + } + @EventListener private void handle(PluginUpdateEvent event) { FX.run(this::refresh); diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/WelcomeDialogController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/WelcomeDialogController.java index 77746b3a..6f3e3833 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/WelcomeDialogController.java +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/WelcomeDialogController.java @@ -15,26 +15,27 @@ * You should have received a copy of the GNU General Public License * along with OwlPlug. If not, see . */ - + package com.owlplug.core.controllers.dialogs; +import atlantafx.base.controls.ToggleSwitch; import com.owlplug.controls.DialogLayout; import com.owlplug.core.components.ApplicationDefaults; import com.owlplug.core.components.LazyViewRegistry; import com.owlplug.core.controllers.OptionsController; -import com.owlplug.core.controllers.fragments.PluginPathFragmentController; -import com.owlplug.core.events.PreferencesChangedEvent; import com.owlplug.core.model.OperatingSystem; -import com.owlplug.core.utils.FX; import com.owlplug.plugin.components.PluginTaskFactory; import com.owlplug.plugin.model.PluginFormat; +import com.owlplug.plugin.ui.PluginFormatBadgeView; import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; -import org.kordamp.ikonli.javafx.FontIcon; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.event.EventListener; import org.springframework.stereotype.Controller; @Controller @@ -46,108 +47,103 @@ public class WelcomeDialogController extends AbstractDialogController { private PluginTaskFactory taskFactory; @Autowired private OptionsController optionsController; - @Autowired - private ListDirectoryDialogController listDirectoryDialogController; @FXML - private VBox pluginPathContainer; + private VBox stepWelcome; @FXML - private Button okButton; + private VBox stepFormats; + @FXML + private VBox formatTogglesContainer; + @FXML + private Button startButton; @FXML private Button cancelButton; - - private PluginPathFragmentController vst2PluginPathFragment; - private PluginPathFragmentController vst3PluginPathFragment; - private PluginPathFragmentController auPluginPathFragment; - private PluginPathFragmentController lv2PluginPathFragment; + @FXML + private Button backButton; + @FXML + private Button skipButton; + @FXML + private Button okButton; WelcomeDialogController() { - super(700, 300); + super(640, 480); this.setOverlayClose(false); } /** * FXML initialize. */ + @FXML public void initialize() { - - vst2PluginPathFragment = new PluginPathFragmentController(PluginFormat.VST2, - ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY, ApplicationDefaults.VST_DIRECTORY_KEY, - ApplicationDefaults.VST2_EXTRA_DIRECTORY_KEY, - this.getPreferences(), this.getApplicationDefaults(), - this.listDirectoryDialogController); - vst3PluginPathFragment = new PluginPathFragmentController(PluginFormat.VST3, - ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY, - ApplicationDefaults.VST3_DIRECTORY_KEY, - ApplicationDefaults.VST3_EXTRA_DIRECTORY_KEY, - this.getPreferences(), this.getApplicationDefaults(), - this.listDirectoryDialogController); - auPluginPathFragment = new PluginPathFragmentController(PluginFormat.AU, - ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY, - ApplicationDefaults.AU_DIRECTORY_KEY, - ApplicationDefaults.AU_EXTRA_DIRECTORY_KEY, - this.getPreferences(), this.getApplicationDefaults(), - this.listDirectoryDialogController); - lv2PluginPathFragment = new PluginPathFragmentController(PluginFormat.LV2, - ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY, - ApplicationDefaults.LV2_DIRECTORY_KEY, - ApplicationDefaults.LV2_EXTRA_DIRECTORY_KEY, - this.getPreferences(), this.getApplicationDefaults(), - this.listDirectoryDialogController); - - pluginPathContainer.getChildren().add(vst2PluginPathFragment.getNode()); - pluginPathContainer.getChildren().add(vst3PluginPathFragment.getNode()); - pluginPathContainer.getChildren().add(auPluginPathFragment.getNode()); - pluginPathContainer.getChildren().add(lv2PluginPathFragment.getNode()); - + startButton.setOnAction(e -> showStep(2)); + cancelButton.setOnAction(e -> this.close()); + backButton.setOnAction(e -> showStep(1)); + skipButton.setOnAction(e -> this.close()); okButton.setOnAction(e -> { this.close(); optionsController.refreshView(); taskFactory.createPluginScanTask().schedule(); }); - cancelButton.setOnAction(e -> this.close()); - - refreshView(); - + buildFormatToggles(); } - public void refreshView() { - vst2PluginPathFragment.refresh(); - vst3PluginPathFragment.refresh(); - auPluginPathFragment.refresh(); - lv2PluginPathFragment.refresh(); + private void buildFormatToggles() { + formatTogglesContainer.getChildren().addAll( + buildFormatRow(PluginFormat.VST2, ApplicationDefaults.VST2_DISCOVERY_ENABLED_KEY, + "VST2", "Legacy format, broadly supported across DAWs"), + buildFormatRow(PluginFormat.VST3, ApplicationDefaults.VST3_DISCOVERY_ENABLED_KEY, + "VST3", "Modern Steinberg format — recommended"), + buildFormatRow(PluginFormat.AU, ApplicationDefaults.AU_DISCOVERY_ENABLED_KEY, + "AU", "Audio Units — macOS only"), + buildFormatRow(PluginFormat.LV2, ApplicationDefaults.LV2_DISCOVERY_ENABLED_KEY, + "LV2", "Open standard, primarily for Linux") + ); - // Disable AU options for non MAC users if (!this.getApplicationDefaults().getRuntimePlatform() .getOperatingSystem().equals(OperatingSystem.MAC)) { - auPluginPathFragment.disable(); + formatTogglesContainer.getChildren().get(2).setDisable(true); } - } - @EventListener - private void handle(PreferencesChangedEvent event) { - FX.run(this::refreshView); + private HBox buildFormatRow(PluginFormat format, String enableKey, String name, String desc) { + HBox row = new HBox(14); + row.setAlignment(Pos.CENTER_LEFT); + row.setPadding(new Insets(10, 4, 10, 4)); + + PluginFormatBadgeView badge = new PluginFormatBadgeView( + format, this.getApplicationDefaults(), PluginFormatBadgeView.DisplayMode.DEFAULT); + + VBox labels = new VBox(2); + Label nameLabel = new Label(name); + nameLabel.getStyleClass().add("label-emphase"); + Label descLabel = new Label(desc); + descLabel.getStyleClass().add("label-disabled"); + labels.getChildren().addAll(nameLabel, descLabel); + HBox.setHgrow(labels, Priority.ALWAYS); + + ToggleSwitch toggle = new ToggleSwitch(); + toggle.setSelected(this.getPreferences().getBoolean(enableKey, false)); + toggle.selectedProperty().addListener((obs, old, v) -> + this.getPreferences().putBoolean(enableKey, v)); + + row.getChildren().addAll(badge, labels, toggle); + return row; } - @Override - protected void onDialogShow() { - this.refreshView(); + private void showStep(int step) { + boolean onStep1 = step == 1; + stepWelcome.setVisible(onStep1); + stepWelcome.setManaged(onStep1); + stepFormats.setVisible(!onStep1); + stepFormats.setManaged(!onStep1); } @Override protected DialogLayout getLayout() { - Label title = new Label("Owlplug is almost ready !"); - title.getStyleClass().add("heading-3"); - FontIcon icon = new FontIcon("mdi2r-rocket-launch-outline"); - icon.setIconSize(20); - title.setGraphic(icon); DialogLayout layout = new DialogLayout(); - layout.setHeading(title); layout.setBody(lazyViewRegistry.get(LazyViewRegistry.WELCOME_VIEW)); - return layout; } -} +} \ No newline at end of file diff --git a/owlplug-client/src/main/java/com/owlplug/core/services/OptionsService.java b/owlplug-client/src/main/java/com/owlplug/core/services/OptionsService.java index 494989a4..1e975c85 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/services/OptionsService.java +++ b/owlplug-client/src/main/java/com/owlplug/core/services/OptionsService.java @@ -29,6 +29,7 @@ import com.owlplug.plugin.model.PluginFormat; import com.owlplug.plugin.repositories.FileStatRepository; import com.owlplug.plugin.repositories.PluginRepository; +import com.owlplug.project.repositories.DawProjectRepository; import jakarta.annotation.PostConstruct; import java.util.prefs.BackingStoreException; import org.slf4j.Logger; @@ -54,6 +55,8 @@ public class OptionsService extends BaseService { @Autowired private FileStatRepository fileStatRepository; @Autowired + private DawProjectRepository projectRepository; + @Autowired private ImageCache imageCache; @PostConstruct @@ -108,6 +111,7 @@ public void clearAllUserData() { packageRepository.deleteAll(); remoteSourceRepository.deleteAll(); fileStatRepository.deleteAll(); + projectRepository.deleteAll(); clearCache(); diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java b/owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java index 9c321a18..ee8393aa 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java @@ -23,7 +23,8 @@ import com.owlplug.core.components.BaseTaskFactory; import com.owlplug.core.tasks.TaskExecutionContext; import com.owlplug.core.utils.FileUtils; -import com.owlplug.plugin.events.PluginScanEvent; +import com.owlplug.plugin.events.PluginScanCompletedEvent; +import com.owlplug.plugin.events.PluginScanStartedEvent; import com.owlplug.plugin.model.Plugin; import com.owlplug.plugin.repositories.FileStatRepository; import com.owlplug.plugin.repositories.PluginFootprintRepository; @@ -120,8 +121,10 @@ public TaskExecutionContext createPluginScanTask(String directoryScope, boolean symlinkRepository, nativeHostService); + scanTask.setOnRunning(e -> publisher.publishEvent(new PluginScanStartedEvent())); + scanTask.setOnSucceeded(scanEvent -> { - publisher.publishEvent(new PluginScanEvent()); + publisher.publishEvent(new PluginScanCompletedEvent()); TaskExecutionContext lookupTask = projectTaskFactory.createLookupTask(); if (prefs.getBoolean(ApplicationDefaults.SYNC_FILE_STAT_KEY, true) diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginsController.java b/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginsController.java index ef5ab4c1..f24863e5 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginsController.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/controllers/PluginsController.java @@ -25,7 +25,7 @@ import com.owlplug.plugin.controllers.dialogs.ExportDialogController; import com.owlplug.plugin.controllers.dialogs.NewLinkController; import com.owlplug.plugin.events.PluginRefreshEvent; -import com.owlplug.plugin.events.PluginScanEvent; +import com.owlplug.plugin.events.PluginScanCompletedEvent; import com.owlplug.plugin.events.PluginUpdateEvent; import com.owlplug.plugin.repositories.PluginRepository; import com.owlplug.plugin.services.PluginService; @@ -245,7 +245,7 @@ public void toggleInfoPaneDisplay() { } @EventListener - private void handle(PluginScanEvent event) { + private void handle(PluginScanCompletedEvent event) { FX.run(this::displayPlugins); } diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/events/PluginScanEvent.java b/owlplug-client/src/main/java/com/owlplug/plugin/events/PluginScanCompletedEvent.java similarity index 94% rename from owlplug-client/src/main/java/com/owlplug/plugin/events/PluginScanEvent.java rename to owlplug-client/src/main/java/com/owlplug/plugin/events/PluginScanCompletedEvent.java index a0657c79..fea9bd61 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/events/PluginScanEvent.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/events/PluginScanCompletedEvent.java @@ -21,5 +21,5 @@ /** * Plugin scan task has completed successfully. */ -public record PluginScanEvent() { +public record PluginScanCompletedEvent() { } diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/events/PluginScanStartedEvent.java b/owlplug-client/src/main/java/com/owlplug/plugin/events/PluginScanStartedEvent.java new file mode 100644 index 00000000..511157c2 --- /dev/null +++ b/owlplug-client/src/main/java/com/owlplug/plugin/events/PluginScanStartedEvent.java @@ -0,0 +1,25 @@ +/* OwlPlug + * Copyright (C) 2021 Arthur + * + * This file is part of OwlPlug. + * + * OwlPlug is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 + * as published by the Free Software Foundation. + * + * OwlPlug is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OwlPlug. If not, see . + */ + +package com.owlplug.plugin.events; + +/** + * Plugin scan task has started running. + */ +public record PluginScanStartedEvent() { +} diff --git a/owlplug-client/src/main/resources/fxml/HomeView.fxml b/owlplug-client/src/main/resources/fxml/HomeView.fxml index 247600af..b015027c 100644 --- a/owlplug-client/src/main/resources/fxml/HomeView.fxml +++ b/owlplug-client/src/main/resources/fxml/HomeView.fxml @@ -9,268 +9,216 @@ + - + - - - + + + + + + + + + + + + + - - \ No newline at end of file diff --git a/owlplug-client/src/main/resources/fxml/MainView.fxml b/owlplug-client/src/main/resources/fxml/MainView.fxml index 131cf7c2..308c2b57 100644 --- a/owlplug-client/src/main/resources/fxml/MainView.fxml +++ b/owlplug-client/src/main/resources/fxml/MainView.fxml @@ -26,7 +26,11 @@ - + + + + + diff --git a/owlplug-client/src/main/resources/fxml/dialogs/WelcomeView.fxml b/owlplug-client/src/main/resources/fxml/dialogs/WelcomeView.fxml index 3452e0f9..f02aa781 100644 --- a/owlplug-client/src/main/resources/fxml/dialogs/WelcomeView.fxml +++ b/owlplug-client/src/main/resources/fxml/dialogs/WelcomeView.fxml @@ -1,21 +1,89 @@ - + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/owlplug-client/src/main/resources/owlplug.css b/owlplug-client/src/main/resources/owlplug.css index 034b5758..651c0bf2 100644 --- a/owlplug-client/src/main/resources/owlplug.css +++ b/owlplug-client/src/main/resources/owlplug.css @@ -388,7 +388,10 @@ ProgressIndicator { .pane-card { - -fx-padding: 10, 10, 10, 10; + -fx-padding: 14; + -fx-background-color: -color-bg-subtle; + -fx-background-radius: 8px; + -fx-border-radius: 8px; -fx-border-width: 1px; -fx-border-color: -color-border-default; } @@ -698,10 +701,11 @@ ProgressIndicator { * **************************************************************/ .dashboard-tile { - -fx-background-color: card-pane-color; - -fx-background-radius: 6px; - -fx-border-radius: 6px; - -fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.55), 8, 0, 0, 2); + -fx-background-color: -color-bg-subtle; + -fx-background-radius: 8px; + -fx-border-radius: 8px; + -fx-border-color: -color-border-default; + -fx-border-width: 1px; -fx-spacing: 4; -fx-min-width: 90px; } @@ -711,30 +715,31 @@ ProgressIndicator { } .dashboard-tile-clickable:hover { - -fx-background-color: rgb(60, 60, 60); + -fx-background-color: -color-bg-overlay; + -fx-border-color: -color-accent-fg; } .dashboard-tile-value { - -fx-font-size: 34px; + -fx-font-size: 32px; -fx-font-weight: bold; - -fx-text-fill: text-color; + -fx-text-fill: -color-fg-emphasis; } .dashboard-tile-value-small { - -fx-font-size: 24px; + -fx-font-size: 22px; -fx-font-weight: bold; - -fx-text-fill: text-color; + -fx-text-fill: -color-fg-default; } .dashboard-tile-label { - -fx-font-size: 12px; - -fx-text-fill: text-emphase-color; + -fx-font-size: 11px; + -fx-text-fill: -color-fg-muted; } .dashboard-section-title { - -fx-font-size: 14px; + -fx-font-size: 11px; -fx-font-weight: bold; - -fx-text-fill: text-emphase-color; + -fx-text-fill: -color-fg-subtle; } .dashboard-bar-chart { @@ -755,11 +760,11 @@ ProgressIndicator { } .dashboard-bar-chart .axis { - -fx-tick-label-fill: text-emphase-color; + -fx-tick-label-fill: -color-fg-muted; } .dashboard-bar-chart .axis-label { - -fx-text-fill: text-emphase-color; + -fx-text-fill: -color-fg-muted; } .dashboard-bar-chart .axis-line { @@ -769,8 +774,23 @@ ProgressIndicator { .dashboard-bar-label { -fx-font-size: 11px; -fx-font-weight: bold; + -fx-text-fill: -color-fg-emphasis; } .no-padding { -fx-padding: 0; -} \ No newline at end of file +} + +.dashboard-scan-indicator { + -fx-background-color: -color-accent-subtle; + -fx-background-radius: 6px; + -fx-padding: 6 12 6 12; +} + +.dashboard-scan-indicator .ikonli-font-icon { + -fx-icon-color: -color-accent-fg; +} + +.dashboard-scan-indicator Label { + -fx-text-fill: -color-accent-fg; +} From 29557cadd4b206fdee78e6a7d36b1d2d0555bfdc Mon Sep 17 00:00:00 2001 From: Arthur Poiret Date: Mon, 22 Jun 2026 22:39:53 +0200 Subject: [PATCH 3/4] chore: update task bar view display --- .../core/components/ApplicationDefaults.java | 1 + .../core/controllers/HomeController.java | 11 +++ .../core/controllers/TaskBarController.java | 98 +++++++------------ .../com/owlplug/core/utils/TimeUtils.java | 2 +- .../plugin/components/PluginTaskFactory.java | 1 + .../src/main/resources/fxml/HomeView.fxml | 11 ++- .../src/main/resources/fxml/TaskBarView.fxml | 62 ++++++------ owlplug-client/src/main/resources/owlplug.css | 28 +++++- 8 files changed, 113 insertions(+), 101 deletions(-) diff --git a/owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java b/owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java index 4047aa7f..2ee0761b 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java +++ b/owlplug-client/src/main/java/com/owlplug/core/components/ApplicationDefaults.java @@ -119,6 +119,7 @@ public class ApplicationDefaults { public static final String SYNC_FILE_STAT_KEY = "SYNC_FILE_STAT_KEY"; public static final String TELEMETRY_ENABLED_KEY = "TELEMETRY_ENABLED_KEY"; public static final String TELEMETRY_USER_ID_KEY = "TELEMETRY_USER_ID_KEY"; + public static final String LAST_PLUGIN_SCAN_DATE_KEY = "LAST_PLUGIN_SCAN_DATE_KEY"; /** * Creates a new ApplicationDefaults. diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java index 05ecf638..ac763460 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java @@ -18,7 +18,9 @@ package com.owlplug.core.controllers; +import com.owlplug.core.components.ApplicationDefaults; import com.owlplug.core.utils.FX; +import com.owlplug.core.utils.TimeUtils; import com.owlplug.plugin.controllers.PluginsController; import com.owlplug.plugin.events.PluginScanCompletedEvent; import com.owlplug.plugin.events.PluginScanStartedEvent; @@ -32,6 +34,7 @@ import com.owlplug.project.repositories.DawProjectRepository; import com.owlplug.project.repositories.PluginLookupRepository; import com.owlplug.project.services.ProjectService; +import java.util.Date; import java.util.List; import javafx.animation.RotateTransition; import javafx.application.Platform; @@ -134,6 +137,9 @@ public class HomeController extends BaseController { @FXML private VBox noProjectSuggestion; + @FXML + private Label lastScanLabel; + @FXML private TextField pluginSearchField; @@ -232,6 +238,10 @@ public void refresh() { final long projectDirectoryCount = projectService.getProjectDirectories().size(); + final long lastScanTimestamp = getPreferences().getLong(ApplicationDefaults.LAST_PLUGIN_SCAN_DATE_KEY, 0L); + lastScanLabel.setText(lastScanTimestamp == 0L ? "Never scanned" + : "Last scan: " + TimeUtils.getHumanReadableDurationFrom(new Date(lastScanTimestamp))); + pluginCountLabel.setText(String.valueOf(totalPlugins)); vst2CountLabel.setText(String.valueOf(vst2Count)); vst3CountLabel.setText(String.valueOf(vst3Count)); @@ -306,6 +316,7 @@ private void refreshFileSizeChart() { )); } + private void setNodeVisible(VBox node, boolean visible) { node.setVisible(visible); node.setManaged(visible); diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/TaskBarController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/TaskBarController.java index ffacc3d5..da740cd5 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/controllers/TaskBarController.java +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/TaskBarController.java @@ -15,10 +15,9 @@ * You should have received a copy of the GNU General Public License * along with OwlPlug. If not, see . */ - + package com.owlplug.core.controllers; -import com.owlplug.controls.Popup; import com.owlplug.core.components.TaskRunner; import com.owlplug.core.tasks.AbstractTask; import java.util.ArrayList; @@ -32,17 +31,15 @@ import javafx.beans.property.StringProperty; import javafx.concurrent.Worker.State; import javafx.fxml.FXML; +import javafx.geometry.Side; import javafx.scene.control.Button; +import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; +import javafx.scene.control.MenuItem; import javafx.scene.control.ProgressBar; import javafx.scene.control.TextArea; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.layout.Region; -import javafx.util.Callback; import javafx.util.Duration; +import org.kordamp.ikonli.javafx.FontIcon; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @@ -62,69 +59,54 @@ public class TaskBarController extends BaseController { private Button logsButton; private final DoubleProperty progressProperty = new SimpleDoubleProperty(); - private final StringProperty taskNameProperty = new SimpleStringProperty(); private Timeline progressTimeline; - /** * FXML initialize. */ public void initialize() { - taskHistoryButton.setOnAction(e -> openTaskHistory()); resetErrorLog(); - progressProperty.addListener((obs, oldVal, newVal) -> { - updateProgress(newVal.doubleValue()); - }); + progressProperty.addListener((obs, oldVal, newVal) -> updateProgress(newVal.doubleValue())); taskLabel.textProperty().bind(taskNameProperty); + taskNameProperty.set("Application is ready"); } private void openTaskHistory() { + ArrayList tasks = new ArrayList<>(taskRunner.getTaskHistory()); + tasks.addAll(taskRunner.getPendingTasks()); + if (tasks.isEmpty()) { + return; + } - if (!taskRunner.getTaskHistory().isEmpty()) { - ListView list = new ListView<>(); - list.setPrefSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE); - - ArrayList tasks = new ArrayList<>(taskRunner.getTaskHistory()); - tasks.addAll(taskRunner.getPendingTasks()); - list.getItems().addAll(tasks); - - list.setCellFactory(new Callback<>() { - @Override - public ListCell call(ListView param) { - return new ListCell<>() { - @Override - public void updateItem(AbstractTask item, boolean empty) { - super.updateItem(item, empty); - if (item != null && !empty) { - Image icon = getApplicationDefaults().taskPendingImage; - if (item.isRunning()) { - icon = getApplicationDefaults().taskRunningImage; - } else if (item.isDone()) { - icon = getApplicationDefaults().taskSuccessImage; - } - if (item.getState().equals(State.FAILED)) { - icon = getApplicationDefaults().taskFailImage; - setOnMouseClicked(e -> { - showErrorDialog(item.getException().getMessage(), item.getException().toString()); - }); - } - ImageView imageView = new ImageView(icon); - setGraphic(imageView); - setText(item.getName()); - } - } - }; - } - }); - - Popup popup = new Popup(list); - popup.show(taskHistoryButton, Popup.PopupVPosition.BOTTOM, Popup.PopupHPosition.RIGHT); + ContextMenu menu = new ContextMenu(); + for (AbstractTask task : tasks) { + MenuItem item = new MenuItem(task.getName(), buildTaskIcon(task)); + if (task.getState().equals(State.FAILED)) { + item.setOnAction(e -> showErrorDialog( + task.getException().getMessage(), task.getException().toString())); + } + menu.getItems().add(item); } + menu.show(taskHistoryButton, Side.TOP, 0, 0); + } + private FontIcon buildTaskIcon(AbstractTask task) { + FontIcon icon; + if (task.getState().equals(State.FAILED)) { + icon = new FontIcon("mdi2a-alert-circle-outline"); + } else if (task.isRunning()) { + icon = new FontIcon("mdi2s-sync"); + } else if (task.isDone()) { + icon = new FontIcon("mdi2c-check-circle-outline"); + } else { + icon = new FontIcon("mdi2c-clock-outline"); + } + icon.setIconSize(14); + return icon; } private void showErrorDialog(String title, String content) { @@ -134,7 +116,6 @@ private void showErrorDialog(String title, String content) { } public void setErrorLog(AbstractTask task, String title, String content, String summary) { - this.getTelemetryService().event("/Error/TaskExecution", p -> { p.put("taskName", task.getName()); p.put("error", title); @@ -143,9 +124,7 @@ public void setErrorLog(AbstractTask task, String title, String content, String taskProgressBar.getStyleClass().add("progress-bar-error"); logsButton.setVisible(true); logsButton.setManaged(true); - logsButton.setOnAction(e -> { - showErrorDialog(title, content); - }); + logsButton.setOnAction(e -> showErrorDialog(title, content)); } public void resetErrorLog() { @@ -154,7 +133,6 @@ public void resetErrorLog() { logsButton.setVisible(false); } - public StringProperty taskNameProperty() { return taskNameProperty; } @@ -206,6 +184,6 @@ private void updateProgress(double target) { progressTimeline = new Timeline(kf); progressTimeline.setOnFinished(ev -> progressTimeline = null); progressTimeline.play(); - } -} + +} \ No newline at end of file diff --git a/owlplug-client/src/main/java/com/owlplug/core/utils/TimeUtils.java b/owlplug-client/src/main/java/com/owlplug/core/utils/TimeUtils.java index bd42a682..a512834a 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/utils/TimeUtils.java +++ b/owlplug-client/src/main/java/com/owlplug/core/utils/TimeUtils.java @@ -52,7 +52,7 @@ private static String toDuration(long duration) { } } if ("".contentEquals(res)) { - return "0 seconds ago"; + return "Just now"; } else { return res.toString(); } diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java b/owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java index ee8393aa..941e63e3 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java @@ -124,6 +124,7 @@ public TaskExecutionContext createPluginScanTask(String directoryScope, boolean scanTask.setOnRunning(e -> publisher.publishEvent(new PluginScanStartedEvent())); scanTask.setOnSucceeded(scanEvent -> { + prefs.putLong(ApplicationDefaults.LAST_PLUGIN_SCAN_DATE_KEY, System.currentTimeMillis()); publisher.publishEvent(new PluginScanCompletedEvent()); TaskExecutionContext lookupTask = projectTaskFactory.createLookupTask(); diff --git a/owlplug-client/src/main/resources/fxml/HomeView.fxml b/owlplug-client/src/main/resources/fxml/HomeView.fxml index b015027c..c217f0e7 100644 --- a/owlplug-client/src/main/resources/fxml/HomeView.fxml +++ b/owlplug-client/src/main/resources/fxml/HomeView.fxml @@ -20,10 +20,13 @@ - - + + + + diff --git a/owlplug-client/src/main/resources/fxml/TaskBarView.fxml b/owlplug-client/src/main/resources/fxml/TaskBarView.fxml index 2dca2248..64d1ef3f 100644 --- a/owlplug-client/src/main/resources/fxml/TaskBarView.fxml +++ b/owlplug-client/src/main/resources/fxml/TaskBarView.fxml @@ -1,36 +1,34 @@ - - - - - + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/owlplug-client/src/main/resources/owlplug.css b/owlplug-client/src/main/resources/owlplug.css index 651c0bf2..4c2dd11b 100644 --- a/owlplug-client/src/main/resources/owlplug.css +++ b/owlplug-client/src/main/resources/owlplug.css @@ -327,10 +327,6 @@ Text { -fx-background-color: -color-bg-default; } -.progress-bar-error>.bar { - -fx-background-color: -color-danger-fg; -} - .progress-bar-link { dot_color: rgb(143, 143, 143); } @@ -384,6 +380,30 @@ ProgressIndicator { .task-bar-footer { -fx-background-color: -color-bg-inset; + -fx-border-color: -color-border-muted transparent transparent transparent; + -fx-border-width: 1px; +} + +.task-bar-progress { + -fx-padding: 0; + -fx-border-width: 0; + -fx-background-color: transparent; +} + +.task-bar-progress > .track { + -fx-background-color: transparent; + -fx-background-insets: 0; + -fx-background-radius: 0; +} + +.task-bar-progress > .bar { + -fx-background-color: -color-accent-fg; + -fx-background-insets: 0; + -fx-background-radius: 0; +} + +.task-bar-progress.progress-bar-error > .bar { + -fx-background-color: -color-danger-fg; } From a9429f05d6ba1b41cea2e165641d0fc997e85360 Mon Sep 17 00:00:00 2001 From: Arthur Poiret Date: Mon, 22 Jun 2026 22:56:20 +0200 Subject: [PATCH 4/4] chore: improve dashboard loading performance and updte events --- .../core/controllers/HomeController.java | 99 +++++++++++-------- .../dialogs/WelcomeDialogController.java | 1 + .../plugin/components/PluginTaskFactory.java | 3 + .../src/main/resources/fxml/TaskBarView.fxml | 9 +- owlplug-client/src/main/resources/owlplug.css | 2 +- 5 files changed, 72 insertions(+), 42 deletions(-) diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java index ac763460..2d0245db 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/HomeController.java @@ -19,6 +19,7 @@ package com.owlplug.core.controllers; import com.owlplug.core.components.ApplicationDefaults; +import com.owlplug.core.utils.Async; import com.owlplug.core.utils.FX; import com.owlplug.core.utils.TimeUtils; import com.owlplug.plugin.controllers.PluginsController; @@ -160,6 +161,8 @@ public class HomeController extends BaseController { private BarChart fileSizeChart; + private final Async.Sequence refreshSequence = new Async.Sequence(); + /** * FXML initialize method. */ @@ -224,49 +227,51 @@ private void initFileSizeChart() { /** * Refreshes all dashboard statistics and updates setup suggestion visibility. + * Data fetching runs on a virtual thread; UI updates are applied on the FX thread. */ public void refresh() { - final long totalPlugins = pluginRepository.count(); - final long vst2Count = pluginRepository.countByFormat(PluginFormat.VST2); - final long vst3Count = pluginRepository.countByFormat(PluginFormat.VST3); - final long auCount = pluginRepository.countByFormat(PluginFormat.AU); - final long lv2Count = pluginRepository.countByFormat(PluginFormat.LV2); - final long projectCount = dawProjectRepository.count(); - final long unresolvedCount = pluginLookupRepository.countByResult(LookupResult.MISSING); - final long disabledCount = pluginRepository.countByDisabledTrue(); - final long pluginDirectoryCount = pluginService.getDirectoriesExplorationSet().size(); - final long projectDirectoryCount = projectService.getProjectDirectories().size(); - - - final long lastScanTimestamp = getPreferences().getLong(ApplicationDefaults.LAST_PLUGIN_SCAN_DATE_KEY, 0L); - lastScanLabel.setText(lastScanTimestamp == 0L ? "Never scanned" - : "Last scan: " + TimeUtils.getHumanReadableDurationFrom(new Date(lastScanTimestamp))); - - pluginCountLabel.setText(String.valueOf(totalPlugins)); - vst2CountLabel.setText(String.valueOf(vst2Count)); - vst3CountLabel.setText(String.valueOf(vst3Count)); - auCountLabel.setText(String.valueOf(auCount)); - lv2CountLabel.setText(String.valueOf(lv2Count)); - projectCountLabel.setText(String.valueOf(projectCount)); - unresolvedPluginCountLabel.setText(String.valueOf(unresolvedCount)); - disabledCountLabel.setText(String.valueOf(disabledCount)); - pluginDirectoryCountLabel.setText(String.valueOf(pluginDirectoryCount)); - projectDirectoryCountLabel.setText(String.valueOf(projectDirectoryCount)); - - final boolean hasPluginDirectories = !pluginService.getDirectoriesExplorationSet().isEmpty(); - final boolean hasProjectDirectories = !projectService.getProjectDirectories().isEmpty(); - final boolean hasPlugins = totalPlugins > 0; - - setNodeVisible(noPluginDirectorySuggestion, !hasPluginDirectories); - setNodeVisible(noPluginSuggestion, hasPluginDirectories && !hasPlugins); - setNodeVisible(noProjectSuggestion, !hasProjectDirectories); - setNodeVisible(setupPane, !hasPluginDirectories || !hasPlugins || !hasProjectDirectories); - - refreshFileSizeChart(); + refreshSequence.supply(() -> new DashboardData( + pluginRepository.count(), + pluginRepository.countByFormat(PluginFormat.VST2), + pluginRepository.countByFormat(PluginFormat.VST3), + pluginRepository.countByFormat(PluginFormat.AU), + pluginRepository.countByFormat(PluginFormat.LV2), + dawProjectRepository.count(), + pluginLookupRepository.countByResult(LookupResult.MISSING), + pluginRepository.countByDisabledTrue(), + pluginService.getDirectoriesExplorationSet().size(), + projectService.getProjectDirectories().size(), + fileStatRepository.findTop5ByParentIsNullOrderByLengthDesc(), + getPreferences().getLong(ApplicationDefaults.LAST_PLUGIN_SCAN_DATE_KEY, 0L) + )).thenAccept(data -> FX.run(() -> { + lastScanLabel.setText(data.lastScanTimestamp() == 0L ? "Never scanned" + : "Last scan: " + TimeUtils.getHumanReadableDurationFrom(new Date(data.lastScanTimestamp()))); + + pluginCountLabel.setText(String.valueOf(data.totalPlugins())); + vst2CountLabel.setText(String.valueOf(data.vst2Count())); + vst3CountLabel.setText(String.valueOf(data.vst3Count())); + auCountLabel.setText(String.valueOf(data.auCount())); + lv2CountLabel.setText(String.valueOf(data.lv2Count())); + projectCountLabel.setText(String.valueOf(data.projectCount())); + unresolvedPluginCountLabel.setText(String.valueOf(data.unresolvedCount())); + disabledCountLabel.setText(String.valueOf(data.disabledCount())); + pluginDirectoryCountLabel.setText(String.valueOf(data.pluginDirectoryCount())); + projectDirectoryCountLabel.setText(String.valueOf(data.projectDirectoryCount())); + + final boolean hasPluginDirectories = data.pluginDirectoryCount() > 0; + final boolean hasProjectDirectories = data.projectDirectoryCount() > 0; + final boolean hasPlugins = data.totalPlugins() > 0; + + setNodeVisible(noPluginDirectorySuggestion, !hasPluginDirectories); + setNodeVisible(noPluginSuggestion, hasPluginDirectories && !hasPlugins); + setNodeVisible(noProjectSuggestion, !hasProjectDirectories); + setNodeVisible(setupPane, !hasPluginDirectories || !hasPlugins || !hasProjectDirectories); + + applyFileSizeChart(data.topStats()); + })); } - private void refreshFileSizeChart() { - final List topStats = fileStatRepository.findTop5ByParentIsNullOrderByLengthDesc(); + private void applyFileSizeChart(List topStats) { final boolean hasData = !topStats.isEmpty(); fileSizeEmptyLabel.setVisible(!hasData); @@ -316,6 +321,22 @@ private void refreshFileSizeChart() { )); } + private record DashboardData( + long totalPlugins, + long vst2Count, + long vst3Count, + long auCount, + long lv2Count, + long projectCount, + long unresolvedCount, + long disabledCount, + long pluginDirectoryCount, + long projectDirectoryCount, + List topStats, + long lastScanTimestamp + ) { + } + private void setNodeVisible(VBox node, boolean visible) { node.setVisible(visible); diff --git a/owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/WelcomeDialogController.java b/owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/WelcomeDialogController.java index 6f3e3833..37fd0527 100644 --- a/owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/WelcomeDialogController.java +++ b/owlplug-client/src/main/java/com/owlplug/core/controllers/dialogs/WelcomeDialogController.java @@ -100,6 +100,7 @@ private void buildFormatToggles() { "LV2", "Open standard, primarily for Linux") ); + // Disable AU options for non MAC users if (!this.getApplicationDefaults().getRuntimePlatform() .getOperatingSystem().equals(OperatingSystem.MAC)) { formatTogglesContainer.getChildren().get(2).setDisable(true); diff --git a/owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java b/owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java index 941e63e3..398cc93a 100644 --- a/owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java +++ b/owlplug-client/src/main/java/com/owlplug/plugin/components/PluginTaskFactory.java @@ -141,6 +141,9 @@ public TaskExecutionContext createPluginScanTask(String directoryScope, boolean lookupTask.scheduleNow(); }); + scanTask.setOnFailed(scanEvent -> publisher.publishEvent(new PluginScanCompletedEvent())); + scanTask.setOnCancelled(scanEvent -> publisher.publishEvent(new PluginScanCompletedEvent())); + return create(scanTask); } diff --git a/owlplug-client/src/main/resources/fxml/TaskBarView.fxml b/owlplug-client/src/main/resources/fxml/TaskBarView.fxml index 64d1ef3f..dc24a53d 100644 --- a/owlplug-client/src/main/resources/fxml/TaskBarView.fxml +++ b/owlplug-client/src/main/resources/fxml/TaskBarView.fxml @@ -4,6 +4,7 @@ + @@ -19,12 +20,16 @@ diff --git a/owlplug-client/src/main/resources/owlplug.css b/owlplug-client/src/main/resources/owlplug.css index 4c2dd11b..56a3f8c8 100644 --- a/owlplug-client/src/main/resources/owlplug.css +++ b/owlplug-client/src/main/resources/owlplug.css @@ -811,6 +811,6 @@ ProgressIndicator { -fx-icon-color: -color-accent-fg; } -.dashboard-scan-indicator Label { +.dashboard-scan-indicator .label { -fx-text-fill: -color-accent-fg; }