diff --git a/src/main/java/controller/AiControllerOnnx.java b/src/main/java/controller/AiControllerOnnx.java index ad2a13b6..54bdf2a7 100644 --- a/src/main/java/controller/AiControllerOnnx.java +++ b/src/main/java/controller/AiControllerOnnx.java @@ -23,7 +23,6 @@ import org.opencv.core.Size; import org.opencv.imgproc.Imgproc; import org.opencv.videoio.VideoCapture; -import userinterface.PlottableImage; import java.nio.FloatBuffer; import java.util.ArrayList; @@ -43,13 +42,10 @@ public class AiControllerOnnx { @FXML private CheckBox enablePathMode; - @FXML - public ChoiceBox sourceChoiceBox; @FXML - private ImageView videoImagePlot; + private ImageView videoImagePlot = new ImageView(); private int cameraSourceIndex; - private VideoCapture capture; private YOLOv8Model yoloModel; private OrtEnvironment env; private Image placeholderImage = new Image("THU_Nami.jpg",640, 480, false, true); @@ -57,10 +53,10 @@ public class AiControllerOnnx { private Point detectionPoint; private List pathPoints = new ArrayList<>(); // List to hold the path points - private Mat frame; private double totalDistance; + public void initialize() { OpenCV.loadLocally(); clearAllPoints.setOnAction(new EventHandler() { @@ -73,19 +69,11 @@ public void handle(ActionEvent actionEvent) { instructionsLabel.setText("Insructions: Unknown direction"); } }); - sourceChoiceBox.setValue("Select Source"); videoImagePlot.setImage(placeholderImage); // Set up mouse click event on the videoImagePlot videoImagePlot.setOnMouseClicked(this::handleVideoImageClick); - // Set up a listener for the ChoiceBox to change camera source - sourceChoiceBox.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> { - handleCameraSourceChange(newValue.intValue()); // Pass the index as args - pathPoints.clear(); - }); - - // Initialize ONNX Runtime environment and YOLOv8 model try { env = OrtEnvironment.getEnvironment(); @@ -93,85 +81,65 @@ public void handle(ActionEvent actionEvent) { } catch (OrtException e) { e.printStackTrace(); } - } - - // Method to handle camera source changes - private void handleCameraSourceChange(int newSourceIndex) { - // Stop the current video stream if already running - if (capture != null && capture.isOpened()) { - stopCam(); - } - - // Update the camera source index based on the selection - cameraSourceIndex = newSourceIndex; - - // Try to open the selected camera - capture = new VideoCapture(cameraSourceIndex); - if (!capture.isOpened()) { - distanceLabel.setText("Distance: Invalid"); - navigationStatus.setText("Status: Could not open camera source!"); - // If the camera source is unavailable, set the placeholder image - System.out.println("Error: Could not open camera source."); - Platform.runLater(() -> videoImagePlot.setImage(placeholderImage)); - return; - } - distanceLabel.setText("Distance: 0.0"); - navigationStatus.setText("Status: navigating..."); - - // Start the video stream with the new camera source - startVideoStream(); - } - - // Method to start the video stream in a background thread - private void startVideoStream() { + // The method processFrame is called from videoController to update the videoimageplot of the current instance of this class + public void processFrame(Mat frame) { + // Task to handle AI processing and UI updates asynchronously Task videoTask = new Task() { @Override protected Void call() throws Exception { - frame = new Mat(); - while (!isCancelled()) { - if (capture.read(frame)) { - try { - Mat grayscaleFrame = new Mat(); - Imgproc.cvtColor(frame, grayscaleFrame, Imgproc.COLOR_BGR2GRAY); + try { + // Step 1: Convert the frame to grayscale if needed (optional) + Mat grayscaleFrame = new Mat(); + Imgproc.cvtColor(frame, grayscaleFrame, Imgproc.COLOR_BGR2GRAY); - // Convert grayscale frame back to BGR for colored overlays - Imgproc.cvtColor(grayscaleFrame, frame, Imgproc.COLOR_GRAY2BGR); + // Step 2: Keep the grayscale frame separate for the AI processing part + Mat processedFrame = new Mat(); + // Only convert back to BGR for overlay or AI processing if necessary + Imgproc.cvtColor(grayscaleFrame, processedFrame, Imgproc.COLOR_GRAY2BGR); - // Pre-process the frame for YOLOv8 - OnnxTensor inputTensor = preprocessFrame(frame, env); + // Step 3: Pre-process the frame for YOLOv8 model (use processed frame here) + OnnxTensor inputTensor = preprocessFrame(processedFrame, env); - // Run the YOLOv8 model on the frame - OrtSession.Result result = yoloModel.runOnnxModel(inputTensor); + // Step 4: Run YOLOv8 model on the frame, with a session check + if (yoloModel.isSessionOpen()) { // Check if the session is open before running the model + OrtSession.Result result = yoloModel.runOnnxModel(inputTensor); - // Post-process the output (apply NMS and IoU) - postProcess(result, frame); + // Step 5: Post-process the result (apply NMS, IoU, etc.) + postProcess(result, processedFrame); - // Convert the frame to a format the image view can use - Imgproc.cvtColor(frame, frame, Imgproc.COLOR_BGR2RGB); // Convert to RGB - Image imageToShow = matToImage(frame); + // Step 6: Convert the processed frame to RGB for JavaFX ImageView + Imgproc.cvtColor(processedFrame, processedFrame, Imgproc.COLOR_BGR2RGB); + // Step 7: Convert the processed Mat frame to Image for JavaFX + Image imageToShow = matToImage(processedFrame); - // Update the PlottableImage with the new frame - Platform.runLater(() -> videoImagePlot.setImage(imageToShow)); + // Step 8: Update the JavaFX ImageView on the JavaFX Application Thread + Platform.runLater(() -> videoImagePlot.setImage(imageToShow)); - inputTensor.close(); - result.close(); - } catch (OrtException e) { - e.printStackTrace(); - } + // Clean up resources + inputTensor.close(); + result.close(); } + } catch (Exception e) { + e.printStackTrace(); } return null; } }; + // Start the task in a background thread to avoid blocking the UI + new Thread(videoTask).start(); + } + - Thread videoThread = new Thread(videoTask); - videoThread.setDaemon(true); // This ensures the thread closes when the application closes - videoThread.start(); + // the method updateResolution is called from videoController to update the resolution of the videoImagePlot + public void updateResolution(double width, double height) { + videoImagePlot.setFitWidth(width); + videoImagePlot.setFitHeight(height); } + // Pre-process the frame for YOLOv8 (resize, normalize, etc.) private OnnxTensor preprocessFrame(Mat frame, OrtEnvironment env) throws OrtException { // Step 1: Resize the image to 640x640 @@ -318,13 +286,72 @@ private void postProcess(OrtSession.Result result, Mat frame) { private void handleVideoImageClick(javafx.scene.input.MouseEvent event) { - if (enablePathMode.isSelected() && capture != null) { + if (enablePathMode.isSelected()) { + // Get the image currently displayed in the ImageView + Image image = videoImagePlot.getImage(); + if (image == null) { + System.out.println("No image loaded."); + return; + } + + // Image dimensions (actual resolution) + double imageWidth = image.getWidth(); + double imageHeight = image.getHeight(); - // Create a new point and add to the path - Point newPoint = new Point(event.getX(), event.getY()); + // ImageView dimensions + double viewWidth = videoImagePlot.getBoundsInLocal().getWidth(); + double viewHeight = videoImagePlot.getBoundsInLocal().getHeight(); + + + // Calculate aspect ratios + double imageAspectRatio = imageWidth / imageHeight; + double viewAspectRatio = viewWidth / viewHeight; + + // Calculate displayed image dimensions within the ImageView + double displayedImageWidth, displayedImageHeight; + double offsetX = 0, offsetY = 0; + + if (viewAspectRatio > imageAspectRatio) { + // Image is constrained by height; black bars on the sides + displayedImageHeight = viewHeight; + displayedImageWidth = imageAspectRatio * displayedImageHeight; + offsetX = (viewWidth - displayedImageWidth) / 2; // Horizontal padding + } else { + // Image is constrained by width; black bars on the top/bottom + displayedImageWidth = viewWidth; + displayedImageHeight = displayedImageWidth / imageAspectRatio; + offsetY = (viewHeight - displayedImageHeight) / 2; // Vertical padding + } + + + // Map mouse click coordinates to the image coordinates + double clickX = event.getX(); + double clickY = event.getY(); + + // Check if click is within the displayed image area + if (clickX < offsetX || clickX > offsetX + displayedImageWidth || + clickY < offsetY || clickY > offsetY + displayedImageHeight) { + return; + } + + // Adjust click coordinates to image space + double adjustedX = (clickX - offsetX) * (imageWidth / displayedImageWidth); + double adjustedY = (clickY - offsetY) * (imageHeight / displayedImageHeight); + + + // Ensure adjusted coordinates are within the image bounds + if (adjustedX < 0 || adjustedX > imageWidth || adjustedY < 0 || adjustedY > imageHeight) { + return; + } + + // Add the adjusted point to the path + Point newPoint = new Point(adjustedX, adjustedY); pathPoints.add(newPoint); + } } + + // Method to draw the entire path (all points and lines) private void drawPath(Mat frame) { // Iterate through the list of points and draw them @@ -454,7 +481,6 @@ public String getDirection(Point pt1, Point pt2) { } - private void drawLine(Point start, Point end, Mat frame) { Imgproc.line(frame, start, end, new Scalar(255, 0, 0), 2); } @@ -470,34 +496,11 @@ private Image matToImage(Mat frame) { return null; } - public void stopCam() { - // Close the video capture if it's open - if (capture != null && capture.isOpened()) { - capture.release(); - } - } - // Stop the video stream when exiting - public void stopVideo() { - // Close the video capture if it's open - if (capture != null && capture.isOpened()) { - capture.release(); - } - - // Close the YOLO model if it is initialized and not already closed - if (yoloModel != null) { - try { - yoloModel.close(); - } catch (OrtException e) { - - e.printStackTrace(); - } - } - } public void setStatusLabel(Label status) { - } + public void close() { - stopVideo(); + } } diff --git a/src/main/java/controller/MainController.java b/src/main/java/controller/MainController.java index 5d3205c2..e592297e 100644 --- a/src/main/java/controller/MainController.java +++ b/src/main/java/controller/MainController.java @@ -14,6 +14,8 @@ import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.control.*; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; import javafx.stage.Modality; import javafx.stage.Stage; import javafx.scene.control.Alert; @@ -48,6 +50,75 @@ public class MainController implements Controller { private final VisualizationManager visualizationManager = new VisualizationManager(); private final Logger logger = Logger.getLogger(this.getClass().getName()); + private int selectedSource; + + @FXML + private Label igtLinkState; + + @FXML + private Circle igtLinkCircle; + + @FXML + private Label currentState; + + @FXML + private Circle videoStatusCircle; + + + private int statusIndex = 0; // Tracks the current state + + + + @FXML + public void handleChangeStatus(int sourceOrdinal, int statusIndex) { + // Determine the source type based on the sourceOrdinal + if (sourceOrdinal == 0) { // VideoController source + switch (statusIndex) { + case 0: // Not connected + currentState.setText("Video: Not Connected"); + updateCircleColors(videoStatusCircle, Color.rgb(255, 0, 0, 1.0)); // Red bright + break; + case 1: // Connected but not yet tracking + currentState.setText("Video: Connected (Not Yet Tracking)"); + updateCircleColors(videoStatusCircle, Color.rgb(255, 173, 51, 1.0)); // Yellow bright + break; + case 2: // Connected and tracking + currentState.setText("Video: Connected and Tracking"); + updateCircleColors(videoStatusCircle, Color.rgb(0, 255, 0, 1.0)); // Green bright + break; + default: + throw new IllegalArgumentException("Invalid status index for VideoController"); + } + } else if (sourceOrdinal == 1) { // OpenIGTLink source + switch (statusIndex) { + case 0: // Not connected + igtLinkState.setText("OpenIGTLink: Not Connected"); + updateCircleColors(igtLinkCircle, Color.rgb(255, 0, 0, 1.0)); // Red bright + break; + case 1: // Connected but not yet tracking + igtLinkState.setText("OpenIGTLink: Connected (Not Yet Tracking)"); + updateCircleColors(igtLinkCircle, Color.rgb(255, 173, 51, 1.0)); // Yellow bright + break; + case 2: // Connected and tracking + igtLinkState.setText("OpenIGTLink: Connected and Tracking"); + updateCircleColors(igtLinkCircle, Color.rgb(0, 255, 0, 1.0)); // Green bright + break; + default: + throw new IllegalArgumentException("Invalid status index for OpenIGTLink"); + } + } else { + throw new IllegalArgumentException("Invalid source ordinal"); + } + } + + // Helper method to update circle colors + private void updateCircleColors(Circle circle, Color color) { + if (circle != null) { + circle.setFill(color); + } + } + + @Override public void initialize(URL location, ResourceBundle resources) { registerController(); @@ -59,6 +130,8 @@ public void initialize(URL location, ResourceBundle resources) { visualizationController.injectTrackingDataController(trackingDataController); visualizationController.injectVisualizationManager(visualizationManager); visualizationManager.injectStatusLabel(status); + + videoController.setMainController(this); } @FXML @@ -132,6 +205,7 @@ private void openAIView(){ this.tabPane.getTabs().add(t); this.tabPane.getSelectionModel().select(t); + videoController.setAiController(this.AiController); t.setOnCloseRequest(e -> { this.AiController.close(); this.AiController = null; @@ -232,8 +306,6 @@ private void handleToggleTheme(ActionEvent event) { String lightModeUrl = Objects.requireNonNull(getClass().getResource("/css/customstyle.css")).toExternalForm(); String darkModeUrl = Objects.requireNonNull(getClass().getResource("/css/dark-mode.css")).toExternalForm(); - System.out.println("Light Mode URL: " + lightModeUrl); - System.out.println("Dark Mode URL: " + darkModeUrl); if (lightModeUrl == null || darkModeUrl == null) { throw new Exception("Theme CSS file(s) not found."); diff --git a/src/main/java/controller/VideoController.java b/src/main/java/controller/VideoController.java index 1500467f..9756e674 100644 --- a/src/main/java/controller/VideoController.java +++ b/src/main/java/controller/VideoController.java @@ -6,6 +6,7 @@ import java.util.logging.Logger; import algorithm.ImageDataManager; +import algorithm.ImageDataProcessor; import inputOutput.VideoSource; import javafx.animation.Animation; import javafx.animation.KeyFrame; @@ -13,10 +14,15 @@ import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.control.*; +import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.util.Duration; +import org.opencv.core.Mat; + +import static inputOutput.VideoSource.LIVESTREAM; +import static inputOutput.VideoSource.OPENIGTLINK; public class VideoController implements Controller { @@ -39,6 +45,21 @@ public class VideoController implements Controller { private final Logger logger = Logger.getLogger(this.getClass().getName()); private Label statusLabel; + private AiControllerOnnx aiController; + private int sourceTracker; + + public void setAiController(AiControllerOnnx aiController) { + this.aiController = aiController; + } + + private MainController mainController; + + public void setMainController(MainController mainController) { + this.mainController = mainController; + } + + + @Override public void initialize(URL location, ResourceBundle resources) { registerController(); @@ -69,10 +90,10 @@ public void connectToSource() { connectionIndicator.setVisible(true); switch(sourceChoiceBox.getValue()) { case "Video Source": - connectToSourceAsync(VideoSource.LIVESTREAM, 0); + connectToSourceAsync(LIVESTREAM, 0); break; case "OpenIGTLink": - connectToSourceAsync(VideoSource.OPENIGTLINK); + connectToSourceAsync(OPENIGTLINK); break; case "Video File": File file = this.loadFile(); @@ -93,8 +114,18 @@ private void connectToSourceAsync(VideoSource connectionId, int deviceId){ Platform.runLater(() -> { connectionIndicator.setVisible(false); if(success) { + if (connectionId == LIVESTREAM) { + // Video Source case + sourceTracker = 0; + mainController.handleChangeStatus(0, 1); // Not Connected (initial state) + } else if (connectionId == OPENIGTLINK) { + // OpenIGTLink case + sourceTracker = 1; + mainController.handleChangeStatus(1, 1); // Not Connected (initial state) + } startButton.setDisable(false); startButton.requestFocus(); + }else{ statusLabel.setText("Unable to establish connection."); logger.warning("Unable to esatblish connection for connection-id "+connectionId+", openConnection returned false."); @@ -110,6 +141,15 @@ private void connectToSourceAsync(VideoSource connectionId, int deviceId){ @FXML public void startVideo() { if(dataManager.getDataProcessor() != null && dataManager.getDataProcessor().isConnected()) { + switch (sourceTracker){ + case 0: + mainController.handleChangeStatus(0,2); + break; + case 1: + mainController.handleChangeStatus(1,2); + break; + } + this.setInitialImageSize(); timeline.setCycleCount(Animation.INDEFINITE); timeline.getKeyFrames().add( @@ -125,6 +165,14 @@ public void startVideo() { @FXML public void stopVideo() { + switch (sourceTracker){ + case 0: + mainController.handleChangeStatus(0,0); + break; + case 1: + mainController.handleChangeStatus(1,0); + break; + } dataManager.closeConnection(); timeline.stop(); // Need to reconnect first @@ -140,10 +188,36 @@ public void stopVideo() { public void setIvSize() { iv.setFitHeight(Double.parseDouble(ivHeight.getText())); iv.setFitWidth(Double.parseDouble(ivWidth.getText())); + + // Notify AiControllerOnnx of the new resolution + if (aiController != null) { + aiController.updateResolution(Double.parseDouble(ivHeight.getText()), Double.parseDouble(ivWidth.getText())); + } } - private void update() { - iv.setImage(dataManager.readImg()); + public void update() { + Mat matrix = dataManager.readMat(); + + // Create a copy of the matrix for the ImageView to prevent modifications in AI processing + Mat matrixCopy = matrix.clone(); + + // Convert the frame to Image for ImageView without any processing + Image frame = matToImage(matrixCopy); + iv.setImage(frame); + + // Send the original matrix for AI processing + if (aiController != null) { + aiController.processFrame(matrix); + } + } + + private Image matToImage(Mat frame) { + try { + return ImageDataProcessor.Mat2Image(frame, ".png"); + } catch (Exception e) { + e.printStackTrace(); + } + return null; } private File loadFile() { diff --git a/src/main/java/controller/YOLOv8Model.java b/src/main/java/controller/YOLOv8Model.java index 5627fd80..f4b60d36 100644 --- a/src/main/java/controller/YOLOv8Model.java +++ b/src/main/java/controller/YOLOv8Model.java @@ -6,24 +6,28 @@ public class YOLOv8Model { private OrtEnvironment env; private OrtSession session; + private boolean isSessionOpen; // Flag to track session state public YOLOv8Model(String modelPath) throws OrtException { env = OrtEnvironment.getEnvironment(); session = env.createSession(modelPath, new OrtSession.SessionOptions()); + isSessionOpen = true; // Initially the session is open } public OrtSession.Result runOnnxModel(OnnxTensor inputTensor) throws OrtException { + // Check if session is open before running the model + if (!isSessionOpen) { + throw new OrtException("Session is closed, cannot run the model."); + } // Run the model on the input tensor return session.run(Collections.singletonMap("images", inputTensor)); } - - - public void close() throws OrtException { // Check if session is open before closing if (session != null) { session.close(); + isSessionOpen = false; // Update the flag when session is closed } // Check if env is open before closing @@ -32,4 +36,8 @@ public void close() throws OrtException { } } + // Method to check if the session is open + public boolean isSessionOpen() { + return isSessionOpen; + } } diff --git a/src/main/resources/css/customstyle.css b/src/main/resources/css/customstyle.css index c42347b8..770fb581 100644 --- a/src/main/resources/css/customstyle.css +++ b/src/main/resources/css/customstyle.css @@ -78,15 +78,12 @@ }*/ -/* Styling for the Distance Label */ #distanceLabel { -fx-background-color: #d9d9d9; -fx-font-size: 13px; -fx-padding: 5px; -1 } -/* Styling for the Navigation Status Label */ #navigationStatus { -fx-background-color: #d9d9d9; -fx-font-size: 13px; diff --git a/src/main/resources/css/dark-mode.css b/src/main/resources/css/dark-mode.css index 6e602ba1..b5faeddb 100644 --- a/src/main/resources/css/dark-mode.css +++ b/src/main/resources/css/dark-mode.css @@ -48,7 +48,7 @@ -fx-border-color: red; } -/*Mohamed style changes*/ +/* Added dark mode style changes*/ .choice-box { -fx-background-color: #333333; @@ -57,42 +57,40 @@ .choice-box .label { -fx-text-fill: white; - /* Text color for the head (including hint text) */ + } .choice-box .list-view { - -fx-background-color: #333333; /* Darker background for drop-down */ - -fx-text-fill: black; /* Ensure the text is white */ + -fx-background-color: #333333; + -fx-text-fill: black; } - -/* Styling the title area */ .titled-pane > .title { -fx-border-color: white; -fx-border-width: 1px; - -fx-padding: 10px; /* Padding for inner content */ - -fx-background-color: #333333; /* Darker background for the title */ - -fx-text-fill: white; /* Title text color */ - -fx-padding: 5px; /* Padding for spacing */ - -fx-font-weight: bold; /* Bold text for title */ + -fx-padding: 10px; + -fx-background-color: #333333; + -fx-text-fill: white; + -fx-padding: 5px; + -fx-font-weight: bold; } .titled-pane > .title > .text { - -fx-fill: white; /* Changes the color of the title text */ - -fx-font-size: 14px; /* Adjust font size */ - -fx-font-weight: bold; /* Make it bold */ + -fx-fill: white; + -fx-font-size: 14px; + -fx-font-weight: bold; } -/* Styling the content area */ + .titled-pane > .content { -fx-border-width: 1px; -fx-text-fill: white; - -fx-background-color: #555555; /* Background for content area */ - -fx-padding: 10px; /* Padding for inner content */ + -fx-background-color: #555555; + -fx-padding: 10px; } -/* Styling for the Distance Label */ + #distanceLabel { -fx-background-color: #333333; -fx-font-size: 13px; @@ -100,7 +98,7 @@ -fx-padding: 5px; } -/* Styling for the Navigation Status Label */ + #navigationStatus { -fx-background-color: #333333; -fx-font-size: 13px; @@ -118,26 +116,126 @@ .toggle-button { -fx-background-color: #333333; -fx-text-fill: white; + -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.5), 5, 0, 0, 2); } -/* Hover state for ToggleButton */ + .toggle-button:hover { - -fx-background-color: #222222; /* Darker shade on hover */ + -fx-background-color: #222222; } -/* Selected (pressed) state for ToggleButton */ + .toggle-button:selected { - -fx-background-color: #0077cc; /* Blue color when selected */ - -fx-text-fill: white; /* White text on selected state */ + -fx-background-color: #0077cc; + -fx-text-fill: white; } -/* When ToggleButton is focused */ + .toggle-button:focused { - -fx-background-color: #555555; /* Slightly lighter background when focused */ + -fx-background-color: #555555; } .button:hover { - -fx-background-color: #222222; /* Darker background on hover */ - -fx-text-fill: white; /* Ensure text color remains white */ + -fx-background-color: #222222; + -fx-text-fill: white; +} + +.button-bar{ + -fx-background-color: #222222; + -fx-text-fill: white; +} + +.tool-bar { + -fx-background-color: #222222; + -fx-text-fill: white; +} + +.tree-view { + -fx-background-color: #222222; + -fx-text-fill: white; +} + +.tree-cell { + -fx-background-color: #555555; + -fx-text-fill: white; +} + +.tree-cell:selected { + -fx-background-color: #444444; +} + +.tree-cell:hover { + -fx-background-color: #444444; +} + + +.choice-box:hover { + -fx-background-color: #222222; +} + +.menu-bar { + -fx-background-color: #333; + -fx-text-fill: white; +} + + +/* MenuBar styling */ +.menu-bar { + -fx-background-color: #333; +} + +/* Menu text styling */ +.menu { + -fx-text-fill: white; +} + +.menu .label { + -fx-text-fill: white; +} + +/* MenuItem styling */ +.menu-item .label { + -fx-text-fill: white; +} + + +.context-menu { + -fx-background-color: #222222; + } +/* Style unselected tabs */ +.tab { + -fx-background-color: #2B2B2B; + +} + +/* Style the selected tab */ +.tab:selected { + -fx-text-fill: white; +} + + +.tab .tab-label { + -fx-text-fill: white; +} + +/* Hover effect on tabs */ +.tab:hover { + -fx-background-color: #444444; +} + + +/* status bar styling*/ + +.hbox-class .label { + -fx-text-fill: black; +} + +.hbox-class { + -fx-background-color: #bfbfbf; + -fx-padding: 5 10 5 10; +} + + + diff --git a/src/main/resources/view/AiView.fxml b/src/main/resources/view/AiView.fxml index 30e7771a..1fd212f4 100644 --- a/src/main/resources/view/AiView.fxml +++ b/src/main/resources/view/AiView.fxml @@ -1,16 +1,10 @@ - - - + - - - - @@ -18,26 +12,12 @@ - - - -