Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 181 additions & 12 deletions src/main/java/MarkNote.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import config.AppConfig;
import config.ThemeManager;
import ui.BasePanel;
import ui.DetachedPanelTab;
import ui.DockingManager;
import ui.DockPosition;
import ui.DocumentTab;
import ui.FrontMatterPanel;
import ui.ImagePreviewTab;
import ui.DetachedPanelTab;
import ui.OptionsDialog;
import ui.PreviewPanel;
import ui.ProjectExplorerPanel;
Expand Down Expand Up @@ -73,6 +75,8 @@ public class MarkNote extends Application {
private SplitPane mainSplit;
private SplitPane editorSplit;
private SplitPane leftSplit;
private BorderPane root;
private DockingManager dockingManager;

private GitService gitService;

Expand Down Expand Up @@ -100,7 +104,7 @@ public void init() {
public void start(Stage stage) {
this.primaryStage = stage;

BorderPane root = new BorderPane();
root = new BorderPane();

// TabPane pour les documents
mainTabPane = new TabPane();
Expand Down Expand Up @@ -241,7 +245,13 @@ public void start(Stage stage) {
HBox.setHgrow(menuBar, Priority.NEVER);
root.setTop(topBar);

Scene scene = new Scene(root, 1200, 700);
// Configuration du DockingManager
setupDockingManager();

// StackPane pour permettre l'overlay de docking
javafx.scene.layout.StackPane sceneRoot = new javafx.scene.layout.StackPane(root, dockingManager.getOverlay());

Scene scene = new Scene(sceneRoot, 1200, 700);
applyTheme(scene);
if (projectExplorerPanel.getProjectDirectory() == null) {
stage.setTitle(messages.getString("app.title.editor"));
Expand Down Expand Up @@ -339,18 +349,20 @@ private MenuBar createMenuBar() {
showProjectPanel.setAccelerator(KeyCombination.keyCombination("Ctrl+E"));
showProjectPanel.setSelected(true);
showProjectPanel.selectedProperty().addListener((obs, wasSelected, isSelected) -> {
// The project explorer and tag cloud are in a vertical SplitPane (leftSplit).
// We need to find it — it's the parent of projectExplorerPanel.
javafx.scene.Parent leftPane = projectExplorerPanel.getParent();
if (leftPane == null)
leftPane = projectExplorerPanel; // fallback
if (isSelected) {
if (!mainSplit.getItems().contains(leftPane)) {
mainSplit.getItems().addFirst(leftPane);
mainSplit.setDividerPositions(0.2);
// S'assurer que leftSplit est dans mainSplit
if (!mainSplit.getItems().contains(leftSplit)) {
mainSplit.getItems().add(0, leftSplit);
}
if (!leftSplit.getItems().contains(projectExplorerPanel)) {
// Insérer en première position
leftSplit.getItems().add(0, projectExplorerPanel);
}
redistributeDividers(leftSplit);
redistributeDividers(mainSplit);
} else {
mainSplit.getItems().remove(leftPane);
leftSplit.getItems().remove(projectExplorerPanel);
cleanupEmptyContainers();
}
});

Expand Down Expand Up @@ -379,13 +391,20 @@ private MenuBar createMenuBar() {
showTagCloud.setSelected(true);
showTagCloud.selectedProperty().addListener((obs, wasSelected, isSelected) -> {
if (isSelected) {
// S'assurer que leftSplit est dans mainSplit
if (!mainSplit.getItems().contains(leftSplit)) {
mainSplit.getItems().add(0, leftSplit);
}
if (!leftSplit.getItems().contains(tagCloudPanel)) {
// Insérer après l'explorateur (index 1) ou en fin
int idx = leftSplit.getItems().indexOf(projectExplorerPanel);
leftSplit.getItems().add(idx + 1, tagCloudPanel);
}
redistributeDividers(leftSplit);
redistributeDividers(mainSplit);
} else {
leftSplit.getItems().remove(tagCloudPanel);
cleanupEmptyContainers();
}
});

Expand All @@ -397,11 +416,18 @@ private MenuBar createMenuBar() {
showNetworkDiagram.setSelected(true);
showNetworkDiagram.selectedProperty().addListener((obs, wasSelected, isSelected) -> {
if (isSelected) {
// S'assurer que leftSplit est dans mainSplit
if (!mainSplit.getItems().contains(leftSplit)) {
mainSplit.getItems().add(0, leftSplit);
}
if (!leftSplit.getItems().contains(visualLinkPanel)) {
leftSplit.getItems().add(visualLinkPanel);
}
redistributeDividers(leftSplit);
redistributeDividers(mainSplit);
} else {
leftSplit.getItems().remove(visualLinkPanel);
cleanupEmptyContainers();
}
});

Expand Down Expand Up @@ -513,6 +539,141 @@ private void showWelcomeTab() {
mainTabPane.getSelectionModel().select(welcomeTab);
}

/**
* Configure le gestionnaire de docking.
*/
private void setupDockingManager() {
dockingManager = new DockingManager(root);

// Enregistrer les conteneurs de docking
dockingManager.registerDockContainer(DockPosition.LEFT, leftSplit);

// Définir l'action de docking (vers un bord)
dockingManager.setOnDockAction(this::handleDockPanel);

// Définir l'action d'insertion (entre panels)
dockingManager.setOnInsertAction(this::handleInsertPanel);

// Associer le DockingManager à chaque panel
projectExplorerPanel.setDockingManager(dockingManager);
projectExplorerPanel.setDockPosition(DockPosition.LEFT);

tagCloudPanel.setDockingManager(dockingManager);
tagCloudPanel.setDockPosition(DockPosition.LEFT);

visualLinkPanel.setDockingManager(dockingManager);
visualLinkPanel.setDockPosition(DockPosition.LEFT);
}

/**
* Gère le docking d'un panel vers une nouvelle position.
*
* @param panel le panel à docker
* @param position la position cible
*/
private void handleDockPanel(BasePanel panel, DockPosition position) {
// Retirer le panel de son emplacement actuel
leftSplit.getItems().remove(panel);
mainSplit.getItems().remove(panel);

// Nettoyer les zones vides
cleanupEmptyContainers();

if (position == DockPosition.CENTER) {
// Détacher vers un onglet
detachPanel(panel);
} else if (position == DockPosition.LEFT) {
// Ajouter au leftSplit
if (!leftSplit.getItems().contains(panel)) {
// S'assurer que leftSplit est dans mainSplit
if (!mainSplit.getItems().contains(leftSplit)) {
mainSplit.getItems().add(0, leftSplit);
}
leftSplit.getItems().add(panel);
panel.setDockPosition(DockPosition.LEFT);
redistributeDividers(leftSplit);
redistributeDividers(mainSplit);
}
} else if (position == DockPosition.RIGHT) {
// Ajouter au mainSplit à droite
if (!mainSplit.getItems().contains(panel)) {
mainSplit.getItems().add(panel);
panel.setDockPosition(DockPosition.RIGHT);
redistributeDividers(mainSplit);
}
} else if (position == DockPosition.TOP || position == DockPosition.BOTTOM) {
// Fallback vers LEFT pour l'instant
if (!leftSplit.getItems().contains(panel)) {
if (!mainSplit.getItems().contains(leftSplit)) {
mainSplit.getItems().add(0, leftSplit);
}
leftSplit.getItems().add(panel);
panel.setDockPosition(DockPosition.LEFT);
redistributeDividers(leftSplit);
redistributeDividers(mainSplit);
}
}
}

/**
* Gère l'insertion d'un panel entre d'autres panels.
*
* @param panel le panel à insérer
* @param container le conteneur cible
* @param insertIndex l'index d'insertion
*/
private void handleInsertPanel(BasePanel panel, SplitPane container, int insertIndex) {
// Retirer le panel de son emplacement actuel
leftSplit.getItems().remove(panel);
mainSplit.getItems().remove(panel);

// Nettoyer les zones vides
cleanupEmptyContainers();

// S'assurer que le conteneur est visible
if (container == leftSplit && !mainSplit.getItems().contains(leftSplit)) {
mainSplit.getItems().add(0, leftSplit);
}

// Insérer à l'index demandé (ajuster si nécessaire)
int maxIndex = container.getItems().size();
int safeIndex = Math.min(insertIndex, maxIndex);

container.getItems().add(safeIndex, panel);

// Mettre à jour la position du panel
if (container == leftSplit) {
panel.setDockPosition(DockPosition.LEFT);
}

redistributeDividers(container);
redistributeDividers(mainSplit);
}

/**
* Nettoie les conteneurs vides.
*/
private void cleanupEmptyContainers() {
// Si leftSplit est vide, le retirer de mainSplit
if (leftSplit.getItems().isEmpty()) {
mainSplit.getItems().remove(leftSplit);
}
}

/**
* Redistribue les diviseurs d'un SplitPane de manière égale.
*/
private void redistributeDividers(SplitPane splitPane) {
int count = splitPane.getItems().size();
if (count <= 1) return;

double[] positions = new double[count - 1];
for (int i = 0; i < count - 1; i++) {
positions[i] = (double)(i + 1) / count;
}
splitPane.setDividerPositions(positions);
}

/**
* Détache un panel dans un onglet séparé.
*
Expand All @@ -529,14 +690,22 @@ private void detachPanel(BasePanel panel) {

// Masquer le panel dans le SplitPane
leftSplit.getItems().remove(panel);
mainSplit.getItems().remove(panel);
cleanupEmptyContainers();

// Créer l'onglet
DetachedPanelTab detachedTab = new DetachedPanelTab(panel);
detachedTab.setOnCloseAction(() -> {
// Réafficher le panel si l'option est activée
if (config.isReattachDiagramOnTabClose() || !leftSplit.getItems().contains(panel)) {
if (!leftSplit.getItems().contains(panel)) {
// S'assurer que leftSplit est dans mainSplit
if (!mainSplit.getItems().contains(leftSplit)) {
mainSplit.getItems().add(0, leftSplit);
}
leftSplit.getItems().add(panel);
redistributeDividers(leftSplit);
redistributeDividers(mainSplit);
}
}
});
Expand Down
80 changes: 78 additions & 2 deletions src/main/java/ui/BasePanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
Expand All @@ -16,9 +17,10 @@

/**
* Classe parente pour les panels avec un bandeau contenant un titre et un bouton de fermeture [x].
* Implémente l'interface Detachable pour permettre le détachement vers un onglet.
* Implémente les interfaces Detachable et Dockable pour permettre le détachement vers un onglet
* et le docking vers différentes zones de la fenêtre.
*/
public abstract class BasePanel extends BorderPane implements Detachable {
public abstract class BasePanel extends BorderPane implements Detachable, Dockable {

protected static ResourceBundle getMessages() {
return ResourceBundle.getBundle("i18n.messages", Locale.getDefault());
Expand All @@ -32,6 +34,13 @@ protected static ResourceBundle getMessages() {

private Runnable onCloseAction;
private Runnable onDetachAction;

// Docking support
private DockPosition dockPosition = DockPosition.LEFT;
private DockingManager dockingManager;
private double dragStartX, dragStartY;
private boolean dragging = false;
private static final double DRAG_THRESHOLD = 10.0;

/**
* Crée un panel avec un bandeau contenant un titre et un bouton de fermeture.
Expand Down Expand Up @@ -75,10 +84,58 @@ protected BasePanel(String titleKey, String closeTooltipKey) {
header.setAlignment(Pos.CENTER_LEFT);
header.setPadding(new Insets(4));
header.getStyleClass().add("panel-header");
header.setCursor(Cursor.MOVE);

// Support du drag pour le docking
setupDragHandlers();

setTop(header);
}

/**
* Configure les handlers de drag pour le docking.
*/
private void setupDragHandlers() {
header.setOnMousePressed(e -> {
if (dockingManager == null) return;
dragStartX = e.getScreenX();
dragStartY = e.getScreenY();
dragging = false;
});

header.setOnMouseDragged(e -> {
if (dockingManager == null) return;

double deltaX = Math.abs(e.getScreenX() - dragStartX);
double deltaY = Math.abs(e.getScreenY() - dragStartY);

if (!dragging && (deltaX > DRAG_THRESHOLD || deltaY > DRAG_THRESHOLD)) {
dragging = true;
dockingManager.startDrag(this, e.getScreenX(), e.getScreenY());
}

if (dragging) {
dockingManager.updateDrag(e.getScreenX(), e.getScreenY());
}
});

header.setOnMouseReleased(e -> {
if (dockingManager != null && dragging) {
dockingManager.endDrag();
}
dragging = false;
});
}

/**
* Définit le DockingManager pour ce panel.
*
* @param manager le gestionnaire de docking
*/
public void setDockingManager(DockingManager manager) {
this.dockingManager = manager;
}

/**
* Définit le contenu principal du panel.
*
Expand Down Expand Up @@ -180,4 +237,23 @@ public String getDetachTabTitle() {
public void onReattached(Node content) {
setCenter(content);
}

// ══════════════════════════════════════════════════════════════
// Implémentation de l'interface Dockable
// ══════════════════════════════════════════════════════════════

@Override
public DockPosition getDockPosition() {
return dockPosition;
}

@Override
public void setDockPosition(DockPosition position) {
this.dockPosition = position;
}

@Override
public String getDockTitle() {
return getMessages().getString(titleKey);
}
}
Loading