This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new b46a83ecb1 give left sidebar more modern look, fixes #6266 (#6267)
b46a83ecb1 is described below

commit b46a83ecb1f0417b66a32c5a141d4180f3ae6ae1
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Tue Dec 30 20:58:28 2025 +0100

    give left sidebar more modern look, fixes #6266 (#6267)
---
 .../org/apache/hop/ui/hopgui/dark-mode.css         |  34 +-
 .../org/apache/hop/ui/hopgui/light-mode.css        |  34 +-
 .../main/java/org/apache/hop/ui/core/PropsUi.java  |  66 ++--
 .../org/apache/hop/ui/core/gui/GuiResource.java    |  32 +-
 .../main/java/org/apache/hop/ui/hopgui/HopGui.java | 400 ++++++++++++++-------
 .../perspective/explorer/ExplorerPerspective.java  |   6 +-
 .../perspective/search/HopSearchPerspective.java   |   4 +
 7 files changed, 356 insertions(+), 220 deletions(-)

diff --git a/rap/src/main/resources/org/apache/hop/ui/hopgui/dark-mode.css 
b/rap/src/main/resources/org/apache/hop/ui/hopgui/dark-mode.css
index 6ca08a8427..a9656701e0 100644
--- a/rap/src/main/resources/org/apache/hop/ui/hopgui/dark-mode.css
+++ b/rap/src/main/resources/org/apache/hop/ui/hopgui/dark-mode.css
@@ -1360,6 +1360,10 @@ ToolItem[VERTICAL] {
     border: none;
     border-top: 1px solid transparent;
     border-bottom: 1px solid transparent;
+    padding: 6px;
+    width: 34px;
+    height: 34px;
+    border-radius: 6px;
 }
 
 ToolItem:first, ToolItem[RIGHT_TO_LEFT]:last {
@@ -1426,23 +1430,15 @@ ToolItem:selected {
 }
 
 ToolItem[VERTICAL]:hover {
-    background-image: gradient(
-            linear, left top, right top,
-            from(#d9d9d9),
-            color-stop(45%, #161616),
-            color-stop(55%, #161616),
-            to(#d9d9d9)
-    );
+    background-color: #3C3F41;
+    background-image: none;
+    border-radius: 6px;
 }
 
 ToolItem[VERTICAL]:pressed, ToolItem[VERTICAL]:selected {
-    background-image: gradient(
-            linear, left top, right top,
-            from(#161616),
-            color-stop(45%, #252525),
-            color-stop(55%, #252525),
-            to(#161616)
-    );
+    background-color: #4A9EFF;
+    background-image: none;
+    border-radius: 6px;
 }
 
 ToolItem:first:hover,
@@ -2509,6 +2505,16 @@ FileUpload-FocusIndicator {
     opacity: 1;
 }
 
+/* Sidebar button styling */
+
+Composite.sidebarButton {
+    border-radius: 6px;
+}
+
+Label.sidebarButton {
+    border-radius: 6px;
+}
+
 /* JFace specific theming */
 
 Shell.jface_contentProposalPopup, Shell.jface_infoPopupDialog {
diff --git a/rap/src/main/resources/org/apache/hop/ui/hopgui/light-mode.css 
b/rap/src/main/resources/org/apache/hop/ui/hopgui/light-mode.css
index 2c22d6b056..0d0179c456 100644
--- a/rap/src/main/resources/org/apache/hop/ui/hopgui/light-mode.css
+++ b/rap/src/main/resources/org/apache/hop/ui/hopgui/light-mode.css
@@ -1368,6 +1368,10 @@ ToolItem[VERTICAL] {
     border: none;
     border-top: 1px solid transparent;
     border-bottom: 1px solid transparent;
+    padding: 6px;
+    width: 34px;
+    height: 34px;
+    border-radius: 6px;
 }
 
 ToolItem:first, ToolItem[RIGHT_TO_LEFT]:last {
@@ -1434,23 +1438,15 @@ ToolItem:selected {
 }
 
 ToolItem[VERTICAL]:hover {
-    background-image: gradient(
-            linear, left top, right top,
-            from(#d9d9d9),
-            color-stop(45%, #e9e9e9),
-            color-stop(55%, #e9e9e9),
-            to(#d9d9d9)
-    );
+    background-color: #D0D0D0;
+    background-image: none;
+    border-radius: 6px;
 }
 
 ToolItem[VERTICAL]:pressed, ToolItem[VERTICAL]:selected {
-    background-image: gradient(
-            linear, left top, right top,
-            from(#e9e9e9),
-            color-stop(45%, #dadada),
-            color-stop(55%, #dadada),
-            to(#e9e9e9)
-    );
+    background-color: #4A9EFF;
+    background-image: none;
+    border-radius: 6px;
 }
 
 ToolItem:first:hover,
@@ -2520,6 +2516,16 @@ FileUpload-FocusIndicator {
     opacity: 1;
 }
 
+/* Sidebar button styling */
+
+Composite.sidebarButton {
+    border-radius: 6px;
+}
+
+Label.sidebarButton {
+    border-radius: 6px;
+}
+
 /* JFace specific theming */
 
 Shell.jface_contentProposalPopup, Shell.jface_infoPopupDialog {
diff --git a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java 
b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
index 1f79a0f156..2c292f7fcf 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/PropsUi.java
@@ -569,51 +569,38 @@ public class PropsUi extends Props {
   protected static void setLookOnWindows(final Widget widget, int style) {
     final GuiResource gui = GuiResource.getInstance();
     Font font = gui.getFontDefault();
-    Color background = null;
-    Color foreground = null;
+    Color background = GuiResource.getInstance().getWidgetBackGroundColor();
+    ;
+    Color foreground = gui.getColorBlack();
 
     if (widget instanceof Shell shell) {
-      background = gui.getColorWhite();
-      foreground = gui.getColorBlack();
       shell.setBackgroundMode(SWT.INHERIT_FORCE);
-      shell.setForeground(gui.getColorBlack());
-      shell.setBackground(gui.getColorWhite());
+      shell.setForeground(foreground);
+      shell.setBackground(background);
       return;
     }
 
     switch (style) {
       case WIDGET_STYLE_DEFAULT:
-        background = gui.getColorWhite();
-        foreground = gui.getColorBlack();
         break;
       case WIDGET_STYLE_FIXED:
         font = gui.getFontFixed();
-        background = gui.getColorWhite();
-        foreground = gui.getColorBlack();
         break;
       case WIDGET_STYLE_TABLE:
         if (PropsUi.getInstance().isDarkMode()) {
-          background = gui.getColorWhite();
-          foreground = gui.getColorBlack();
           Table table = (Table) widget;
-          table.setHeaderBackground(gui.getColorLightGray());
-          table.setHeaderForeground(gui.getColorDarkGray());
+          table.setHeaderBackground(background);
+          table.setHeaderForeground(foreground);
         }
         break;
       case WIDGET_STYLE_TREE:
         if (PropsUi.getInstance().isDarkMode()) {
-          background = gui.getColorWhite();
-          foreground = gui.getColorBlack();
           Tree tree = (Tree) widget;
-          tree.setHeaderBackground(gui.getColorLightGray());
-          tree.setHeaderForeground(gui.getColorDarkGray());
+          tree.setHeaderBackground(background);
+          tree.setHeaderForeground(foreground);
         }
         break;
       case WIDGET_STYLE_TOOLBAR:
-        if (PropsUi.getInstance().isDarkMode()) {
-          background = gui.getColorLightGray();
-          foreground = gui.getColorBlack();
-        }
         break;
       case WIDGET_STYLE_TAB:
         CTabFolder tabFolder = (CTabFolder) widget;
@@ -621,16 +608,16 @@ public class PropsUi extends Props {
         tabFolder.setTabHeight(28);
         ensureSafeRenderer(tabFolder);
         if (PropsUi.getInstance().isDarkMode()) {
-          tabFolder.setBackground(gui.getColorWhite());
-          tabFolder.setForeground(gui.getColorBlack());
-          tabFolder.setSelectionBackground(gui.getColorWhite());
-          tabFolder.setSelectionForeground(gui.getColorBlack());
+          tabFolder.setBackground(background);
+          tabFolder.setForeground(foreground);
+          tabFolder.setSelectionBackground(background);
+          tabFolder.setSelectionForeground(foreground);
         }
         break;
       case WIDGET_STYLE_PUSH_BUTTON:
         break;
       default:
-        background = gui.getColorGray();
+        background = GuiResource.getInstance().getWidgetBackGroundColor();
         font = null;
         break;
     }
@@ -655,22 +642,19 @@ public class PropsUi extends Props {
   protected static void setLookOnMac(final Widget widget, int style) {
     final GuiResource gui = GuiResource.getInstance();
     Font font = gui.getFontDefault();
-    Color background = null;
+    Color background = GuiResource.getInstance().getWidgetBackGroundColor();
 
     Display display = Display.getCurrent();
     if (display == null) {
       return;
     }
 
-    // Use system colors that automatically adapt to light/dark mode
-    // Only set backgrounds where needed - let macOS handle text colors 
natively
-    Color systemWidgetBackground = 
display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
     Color systemListBackground = 
display.getSystemColor(SWT.COLOR_LIST_BACKGROUND);
     Color systemListForeground = 
display.getSystemColor(SWT.COLOR_LIST_FOREGROUND);
 
     // Handle Shell windows with system background, but let macOS handle text 
color
     if (widget instanceof Shell shell) {
-      shell.setBackground(systemWidgetBackground);
+      shell.setBackground(background);
       shell.setBackgroundMode(SWT.INHERIT_FORCE);
       // Don't set foreground - let macOS handle it natively
       return;
@@ -680,10 +664,8 @@ public class PropsUi extends Props {
       case WIDGET_STYLE_DEFAULT:
         // Use system widget background for default composites
         // Don't set foreground - let macOS handle text colors
-        background = systemWidgetBackground;
         break;
       case WIDGET_STYLE_OSX_GROUP:
-        background = systemWidgetBackground;
         font = gui.getFontDefault();
         Group group = ((Group) widget);
         final Color groupBg = background;
@@ -698,25 +680,25 @@ public class PropsUi extends Props {
         break;
       case WIDGET_STYLE_FIXED:
         font = gui.getFontFixed();
-        background = systemWidgetBackground;
+        background = background;
         break;
       case WIDGET_STYLE_TABLE:
-        background = systemWidgetBackground;
+        background = background;
         Table table = (Table) widget;
-        table.setHeaderBackground(systemWidgetBackground);
+        table.setHeaderBackground(background);
         // Don't set foreground colors - let macOS handle them
         break;
       case WIDGET_STYLE_TREE:
-        background = systemWidgetBackground;
+        background = background;
         break;
       case WIDGET_STYLE_TOOLBAR:
-        background = systemWidgetBackground;
+        background = background;
         break;
       case WIDGET_STYLE_TAB:
         CTabFolder tabFolder = (CTabFolder) widget;
         tabFolder.setBorderVisible(true);
-        tabFolder.setBackground(systemWidgetBackground);
-        tabFolder.setSelectionBackground(systemWidgetBackground);
+        tabFolder.setBackground(background);
+        tabFolder.setSelectionBackground(background);
         // Don't set foreground colors - let macOS handle them
         ensureSafeRenderer(tabFolder);
         break;
@@ -761,7 +743,7 @@ public class PropsUi extends Props {
   protected static void setLookOnLinux(final Widget widget, int style) {
     final GuiResource gui = GuiResource.getInstance();
     Font font = gui.getFontDefault();
-    Color background = gui.getColorWhite();
+    Color background = GuiResource.getInstance().getWidgetBackGroundColor();
     Color foreground = gui.getColorBlack();
 
     switch (style) {
diff --git a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java 
b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
index 230c2569d4..c3c7804542 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/gui/GuiResource.java
@@ -1019,13 +1019,6 @@ public class GuiResource {
     return imageLogo.getAsBitmapForSize(display, ConstUi.SMALL_ICON_SIZE, 
ConstUi.SMALL_ICON_SIZE);
   }
 
-  /**
-   * @return Returns the imagesTransforms.
-   */
-  public Map<String, SwtUniversalImage> getImagesTransforms() {
-    return imagesTransforms;
-  }
-
   /**
    * Get an image of an action or a missing image if not found.
    *
@@ -1054,13 +1047,6 @@ public class GuiResource {
     return image;
   }
 
-  /**
-   * @return Returns the imagesActions.
-   */
-  public Map<String, SwtUniversalImage> getImagesActions() {
-    return imagesActions;
-  }
-
   /**
    * Return the image of the IValueMeta from plugin
    *
@@ -1485,14 +1471,6 @@ public class GuiResource {
     return color;
   }
 
-  /**
-   * @return The image map used to cache images loaded from certain location 
using getImage(String
-   *     location);
-   */
-  public Map<String, Image> getImageMap() {
-    return imageMap;
-  }
-
   /**
    * @return the imageTrue
    */
@@ -1625,4 +1603,14 @@ public class GuiResource {
   public SwtUniversalImage getSwtImageArrowCandidate() {
     return imageArrowCandidate;
   }
+
+  public Color getWidgetBackGroundColor() {
+    if (OsHelper.isWindows()) {
+      return colorWhite;
+    } else if (OsHelper.isMac()) {
+      return display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
+    } else {
+      return colorLightGray;
+    }
+  }
 }
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java 
b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
index 9a246820ea..5b0d965386 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
@@ -133,10 +133,17 @@ import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.StackLayout;
 import org.eclipse.swt.events.ShellAdapter;
 import org.eclipse.swt.events.ShellEvent;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
 import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.layout.FormAttachment;
 import org.eclipse.swt.layout.FormData;
 import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Canvas;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
@@ -259,7 +266,8 @@ public class HopGui
   private ToolBar statusToolbar;
   private GuiToolbarWidgets statusToolbarWidgets;
 
-  private ToolBar perspectivesToolbar;
+  private Composite perspectivesSidebar;
+  private java.util.List<SidebarButton> sidebarButtons = new 
java.util.ArrayList<>();
   private Composite mainPerspectivesComposite;
   private HopPerspectiveManager perspectiveManager;
   private IHopPerspective activePerspective;
@@ -593,78 +601,29 @@ public class HopGui
                 perspective.getId());
         ClassLoader classLoader = 
pluginRegistry.getClassLoader(perspectivePlugin);
 
-        ToolItem item;
-        if (EnvironmentUtils.getInstance().isWeb()) {
-          item =
-              addWebToolbarButton(
-                  perspectivePlugin.getIds()[0],
-                  this.perspectivesToolbar,
-                  perspectivePlugin.getImageFile(),
-                  tooltip,
-                  // TODO: check if there is unnecessary refresh
-                  event -> {
-                    // Special handling for ExplorerPerspective: if already 
active, toggle file
-                    // explorer panel instead of just activating
-                    if (perspective instanceof ExplorerPerspective
-                        && isActivePerspective(perspective)) {
-                      ((ExplorerPerspective) 
perspective).toggleFileExplorerPanel();
-                    } else {
-                      // Normal perspective activation
-                      setActivePerspective(perspective);
-                    }
-                  });
-        } else {
-          item = new ToolItem(this.perspectivesToolbar, SWT.RADIO);
-          item.setToolTipText(tooltip);
-          item.addListener(
-              SWT.Selection,
-              event -> {
-                // Event is sent first to the unselected tool item and then 
the selected item.
-                // To avoid unnecessary refresh, only process on the selected 
item.
-                if (item.getSelection()) {
-                  // Special handling for ExplorerPerspective: check if 
control is already on top
-                  // (truly active and visible) before toggling
-                  if (perspective instanceof ExplorerPerspective
-                      && mainPerspectivesComposite != null
-                      && !mainPerspectivesComposite.isDisposed()) {
-                    StackLayout layout = (StackLayout) 
mainPerspectivesComposite.getLayout();
-                    // Only toggle if the perspective control is already on 
top (truly active)
-                    if (layout.topControl == perspective.getControl()) {
-                      ((ExplorerPerspective) 
perspective).toggleFileExplorerPanel();
-                      return; // Don't call setActivePerspective
-                    }
-                  }
-                  // Normal perspective activation
-                  setActivePerspective(perspective);
-                }
-              });
-          Image image =
-              GuiResource.getInstance()
-                  .getImage(
-                      perspectivePlugin.getImageFile(),
-                      classLoader,
-                      ConstUi.SMALL_ICON_SIZE,
-                      ConstUi.SMALL_ICON_SIZE);
-          if (image != null) {
-            item.setImage(image);
-          }
-        }
-        item.setData(perspective);
-
-        // Store reference to ExplorerPerspective ToolItem for dynamic tooltip 
updates
-        if (perspective instanceof ExplorerPerspective) {
-          explorerPerspectiveToolItem = item;
-        }
-
+        int sidebarIconSize = 21;
+        Image image =
+            GuiResource.getInstance()
+                .getImage(
+                    perspectivePlugin.getImageFile(),
+                    classLoader,
+                    sidebarIconSize,
+                    sidebarIconSize);
         // See if there's a shortcut for the perspective, add it to tooltip...
         KeyboardShortcut shortcut =
             GuiRegistry.getInstance()
                 .findKeyboardShortcut(perspectiveClass.getName(), "activate", 
Const.isOSX());
+
         if (shortcut != null) {
-          item.setToolTipText(item.getToolTipText() + " (" + shortcut + ')');
+          tooltip += " (" + shortcut + ")";
         }
+
+        // Create styled sidebar button with hover, selection, and rounded 
corners
+        // This works for both desktop SWT and web/RAP modes
+        createStyledSidebarButton(perspectivesSidebar, image, tooltip, 
perspective);
       }
-      perspectivesToolbar.pack();
+
+      perspectivesSidebar.layout(true, true);
     } catch (Exception e) {
       new ErrorDialog(shell, "Error", "Error loading perspectives", e);
     }
@@ -1311,8 +1270,6 @@ public class HopGui
   }
 
   protected void addPerspectivesToolbar() {
-    // We can't mix horizontal and vertical toolbars so we need to add a 
composite.
-    //
     shell.setLayout(new FormLayout());
     mainHopGuiComposite = new Composite(shell, SWT.NO_BACKGROUND);
     mainHopGuiComposite.setLayout(new FormLayout());
@@ -1323,13 +1280,24 @@ public class HopGui
     formData.bottom = new FormAttachment(statusToolbar, 0);
     mainHopGuiComposite.setLayoutData(formData);
 
-    perspectivesToolbar = new ToolBar(mainHopGuiComposite, SWT.WRAP | 
SWT.RIGHT | SWT.VERTICAL);
-    PropsUi.setLook(perspectivesToolbar, Props.WIDGET_STYLE_TOOLBAR);
-    FormData fdToolBar = new FormData();
-    fdToolBar.left = new FormAttachment(0, 0);
-    fdToolBar.top = new FormAttachment(0, 0);
-    fdToolBar.bottom = new FormAttachment(100, 0);
-    perspectivesToolbar.setLayoutData(fdToolBar);
+    // Create custom sidebar composite instead of ToolBar for better control
+    perspectivesSidebar = new Composite(mainHopGuiComposite, SWT.NONE);
+    PropsUi.setLook(perspectivesSidebar);
+
+    // Use GridLayout for vertical stacking
+    org.eclipse.swt.layout.GridLayout sidebarLayout =
+        new org.eclipse.swt.layout.GridLayout(1, false);
+    sidebarLayout.marginWidth = 1;
+    sidebarLayout.marginHeight = 2;
+    sidebarLayout.verticalSpacing = 1; // Minimal spacing between buttons
+    perspectivesSidebar.setLayout(sidebarLayout);
+
+    FormData fdSidebar = new FormData();
+    fdSidebar.left = new FormAttachment(0, 0);
+    fdSidebar.top = new FormAttachment(0, 0);
+    fdSidebar.bottom = new FormAttachment(100, 0);
+    fdSidebar.width = (int) (40 * PropsUi.getNativeZoomFactor());
+    perspectivesSidebar.setLayoutData(fdSidebar);
   }
 
   /**
@@ -1341,7 +1309,7 @@ public class HopGui
     mainPerspectivesComposite.setLayout(new StackLayout());
     FormData fdMain = new FormData();
     fdMain.top = new FormAttachment(0, 0);
-    fdMain.left = new FormAttachment(perspectivesToolbar, 0);
+    fdMain.left = new FormAttachment(perspectivesSidebar, 0);
     fdMain.bottom = new FormAttachment(100, 0);
     fdMain.right = new FormAttachment(100, 0);
     mainPerspectivesComposite.setLayoutData(fdMain);
@@ -1532,51 +1500,10 @@ public class HopGui
     layout.topControl = perspective.getControl();
     mainPerspectivesComposite.layout();
 
-    // Select toolbar item and update tooltips
-    //
-    if (perspectivesToolbar != null && !perspectivesToolbar.isDisposed()) {
-      for (ToolItem item : perspectivesToolbar.getItems()) {
-        boolean shaded = perspective.equals(item.getData());
-        if (EnvironmentUtils.getInstance().isWeb()) {
-          SvgLabelFacade.shadeSvg((Label) item.getControl(), (String) 
item.getData("id"), shaded);
-        } else {
-          item.setSelection(shaded);
-        }
-      }
+    // Selection is handled by updateSidebarButtonSelection()
 
-      // Update ExplorerPerspective tooltip based on active state
-      if (explorerPerspectiveToolItem != null && 
!explorerPerspectiveToolItem.isDisposed()) {
-        boolean isExplorerActive = perspective instanceof ExplorerPerspective;
-        String baseName =
-            BaseMessages.getString(ExplorerPerspective.PKG, 
"ExplorerPerspective.Name");
-        String tooltipText;
-
-        // Get keyboard shortcut if it exists
-        KeyboardShortcut shortcut =
-            GuiRegistry.getInstance()
-                .findKeyboardShortcut(
-                    ExplorerPerspective.class.getName(), "activate", 
Const.isOSX());
-
-        if (isExplorerActive) {
-          // Format: "File Explorer (Ctrl+Shift+E). Click the folder to 
show/hide the project tree"
-          String additionalText =
-              BaseMessages.getString(ExplorerPerspective.PKG, 
"ExplorerPerspective.Tooltip.Active");
-          if (shortcut != null) {
-            tooltipText = baseName + " (" + shortcut + "). " + additionalText;
-          } else {
-            tooltipText = baseName + ". " + additionalText;
-          }
-        } else {
-          // Format: "File Explorer (Ctrl+Shift+E)"
-          if (shortcut != null) {
-            tooltipText = baseName + " (" + shortcut + ')';
-          } else {
-            tooltipText = baseName;
-          }
-        }
-        explorerPerspectiveToolItem.setToolTipText(tooltipText);
-      }
-    }
+    // Update sidebar button selection states
+    updateSidebarButtonSelection(perspective);
 
     // Notify the perspective that it has been activated.
     //
@@ -1586,14 +1513,237 @@ public class HopGui
   }
 
   public boolean isActivePerspective(IHopPerspective perspective) {
-    if (perspective != null) {
-      for (ToolItem item : perspectivesToolbar.getItems()) {
-        if (perspective.equals(item.getData())) {
-          return item.getSelection();
+    return activePerspective != null && activePerspective.equals(perspective);
+  }
+
+  /** Update the visual selection state of sidebar buttons when perspective 
changes. */
+  private void updateSidebarButtonSelection(IHopPerspective activePerspective) 
{
+    for (SidebarButton button : sidebarButtons) {
+      button.setSelected(button.perspective.equals(activePerspective));
+    }
+  }
+
+  /**
+   * Create a styled sidebar button with modern appearance. Features rounded 
corners, hover effects,
+   * and selection colors.
+   */
+  private void createStyledSidebarButton(
+      Composite parent, Image image, String tooltip, IHopPerspective 
perspective) {
+
+    SidebarButton button = new SidebarButton(parent, image, tooltip, 
perspective);
+    sidebarButtons.add(button);
+
+    org.eclipse.swt.layout.GridData gd = new org.eclipse.swt.layout.GridData();
+    gd.widthHint = (int) (34 * PropsUi.getNativeZoomFactor());
+    gd.heightHint = (int) (34 * PropsUi.getNativeZoomFactor());
+    button.composite.setLayoutData(gd);
+  }
+
+  /** Custom sidebar button class with hover, selection, and rounded corners */
+  private class SidebarButton {
+    Control composite; // Use Control to allow both Composite (RAP) and Canvas 
(desktop)
+    Image image;
+    IHopPerspective perspective;
+    boolean isHovered = false;
+    boolean isSelected = false;
+
+    Color selectionBg = GuiResource.getInstance().getColorLightBlue();
+    Color hoverBg = GuiResource.getInstance().getColorLightGray();
+    Color normalBg = GuiResource.getInstance().getWidgetBackGroundColor();
+
+    public SidebarButton(
+        Composite parent, Image image, String tooltip, IHopPerspective 
perspective) {
+      this.image = image;
+      this.perspective = perspective;
+
+      // Create a label inside the composite to display the image (only for 
RAP)
+      final Label imageLabel;
+      if (EnvironmentUtils.getInstance().isWeb()) {
+        // For RAP/web: use Composite with Label child
+        Composite comp = new Composite(parent, SWT.NONE);
+        composite = comp;
+        comp.setToolTipText(tooltip);
+        comp.setBackground(normalBg);
+
+        // Set custom variant for CSS styling in RAP
+        comp.setData("org.eclipse.rap.rwt.customVariant", "sidebarButton");
+
+        // Use GridLayout to center the image without stretching
+        GridLayout layout = new GridLayout(1, false);
+        layout.marginWidth = 0;
+        layout.marginHeight = 0;
+        layout.horizontalSpacing = 0;
+        layout.verticalSpacing = 0;
+        comp.setLayout(layout);
+
+        imageLabel = new Label(comp, SWT.NONE);
+        imageLabel.setImage(image);
+        imageLabel.setBackground(normalBg);
+        imageLabel.setToolTipText(tooltip);
+        imageLabel.setData("org.eclipse.rap.rwt.customVariant", 
"sidebarButton");
+
+        // Center the label in the composite
+        GridData gd = new GridData(SWT.CENTER, SWT.CENTER, true, true);
+        imageLabel.setLayoutData(gd);
+      } else {
+        Canvas canvas = new Canvas(parent, SWT.NONE);
+        composite = canvas;
+        canvas.setToolTipText(tooltip);
+        canvas.setBackground(normalBg);
+        imageLabel = null;
+      }
+
+      // Update background colors method for both RAP and desktop
+      final Runnable updateColors =
+          () -> {
+            if (EnvironmentUtils.getInstance().isWeb()) {
+              // For RAP, update composite background color (rounded corners 
applied via CSS)
+              Color bgColor;
+              if (isSelected) {
+                bgColor = selectionBg;
+              } else if (isHovered) {
+                bgColor = hoverBg;
+              } else {
+                bgColor = normalBg;
+              }
+              composite.setBackground(bgColor);
+              // Keep label background transparent/matching to show composite 
background
+              if (imageLabel != null) {
+                imageLabel.setBackground(bgColor);
+              }
+              // Force redraw in RAP
+              composite.redraw();
+              if (composite instanceof Composite) {
+                ((Composite) composite).layout();
+              }
+            } else {
+              // For desktop Canvas, trigger repaint
+              composite.redraw();
+            }
+          };
+
+      // Only add paint listener for desktop SWT (RAP doesn't support it)
+      if (!EnvironmentUtils.getInstance().isWeb()) {
+        composite.addPaintListener(
+            e -> {
+              GC gc = e.gc;
+              Point size = composite.getSize();
+
+              gc.setAntialias(SWT.ON);
+
+              // Choose background color
+              if (isSelected) {
+                gc.setBackground(selectionBg);
+              } else if (isHovered) {
+                gc.setBackground(hoverBg);
+              } else {
+                gc.setBackground(normalBg);
+              }
+
+              // Fill rounded rectangle
+              gc.fillRoundRectangle(4, 4, size.x - 8, size.y - 8, 8, 8);
+
+              // Draw image centered
+              if (image != null && !image.isDisposed()) {
+                Rectangle imgBounds = image.getBounds();
+                int x = (size.x - imgBounds.width) / 2;
+                int y = (size.y - imgBounds.height) / 2;
+                gc.drawImage(image, x, y);
+              }
+            });
+      }
+
+      // Mouse listeners
+      composite.addListener(
+          SWT.MouseEnter,
+          e -> {
+            isHovered = true;
+            updateColors.run();
+          });
+
+      composite.addListener(
+          SWT.MouseExit,
+          e -> {
+            isHovered = false;
+            updateColors.run();
+          });
+
+      composite.addListener(
+          SWT.MouseDown,
+          e -> {
+            // Deselect all other buttons
+            for (SidebarButton btn : sidebarButtons) {
+              btn.setSelected(false);
+            }
+
+            // Select this button
+            setSelected(true);
+
+            // Handle perspective activation
+            if (perspective instanceof ExplorerPerspective explorerPerspective
+                && mainPerspectivesComposite != null
+                && !mainPerspectivesComposite.isDisposed()) {
+              StackLayout layout = (StackLayout) 
mainPerspectivesComposite.getLayout();
+              if (layout.topControl == explorerPerspective.getControl()) {
+                explorerPerspective.toggleFileExplorerPanel();
+                return;
+              }
+            }
+            setActivePerspective(perspective);
+          });
+
+      // Also attach listeners to the image label for better hit detection 
(RAP only)
+      if (imageLabel != null) {
+        imageLabel.addListener(
+            SWT.MouseEnter,
+            e -> {
+              isHovered = true;
+              updateColors.run();
+            });
+        imageLabel.addListener(
+            SWT.MouseExit,
+            e -> {
+              isHovered = false;
+              updateColors.run();
+            });
+        imageLabel.addListener(
+            SWT.MouseDown,
+            e -> {
+              // Deselect all other buttons
+              for (SidebarButton btn : sidebarButtons) {
+                btn.setSelected(false);
+              }
+
+              // Select this button
+              setSelected(true);
+
+              // Handle perspective activation
+              if (perspective instanceof ExplorerPerspective 
explorerPerspective
+                  && mainPerspectivesComposite != null
+                  && !mainPerspectivesComposite.isDisposed()) {
+                StackLayout layout = (StackLayout) 
mainPerspectivesComposite.getLayout();
+                if (layout.topControl == explorerPerspective.getControl()) {
+                  explorerPerspective.toggleFileExplorerPanel();
+                  return;
+                }
+              }
+              setActivePerspective(perspective);
+            });
+      }
+
+      // Store the update method for later use
+      composite.setData("updateColors", updateColors);
+    }
+
+    public void setSelected(boolean selected) {
+      this.isSelected = selected;
+      if (!composite.isDisposed()) {
+        Runnable updateColors = (Runnable) composite.getData("updateColors");
+        if (updateColors != null) {
+          updateColors.run();
         }
       }
     }
-    return false;
   }
 
   @GuiKeyboardShortcut(key = SWT.F1)
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
index 130b0d8a1d..3990d52213 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
@@ -231,8 +231,8 @@ public class ExplorerPerspective implements 
IHopPerspective, TabClosable {
     return "explorer-perspective";
   }
 
-  @GuiKeyboardShortcut(control = true, shift = true, key = 'e')
-  @GuiOsxKeyboardShortcut(command = true, shift = true, key = 'e')
+  @GuiKeyboardShortcut(control = true, shift = true, key = 'd')
+  @GuiOsxKeyboardShortcut(command = true, shift = true, key = 'd')
   @Override
   public void activate() {
     hopGui.setActivePerspective(this);
@@ -269,7 +269,7 @@ public class ExplorerPerspective implements 
IHopPerspective, TabClosable {
     createTree(sash);
     createTabFolder(sash);
 
-    sash.setWeights(new int[] {20, 80});
+    sash.setWeights(20, 80);
 
     // Set initial file explorer panel visibility from configuration
     Boolean visibleByDefault =
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/search/HopSearchPerspective.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/search/HopSearchPerspective.java
index 13ec9fe713..22c43cae55 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/search/HopSearchPerspective.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/search/HopSearchPerspective.java
@@ -24,6 +24,8 @@ import java.util.List;
 import java.util.Map;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.gui.plugin.GuiPlugin;
+import org.apache.hop.core.gui.plugin.key.GuiKeyboardShortcut;
+import org.apache.hop.core.gui.plugin.key.GuiOsxKeyboardShortcut;
 import org.apache.hop.core.plugins.IPlugin;
 import org.apache.hop.core.plugins.PluginRegistry;
 import org.apache.hop.core.search.ISearchResult;
@@ -93,6 +95,8 @@ public class HopSearchPerspective implements IHopPerspective {
   }
 
   @Override
+  @GuiKeyboardShortcut(control = true, key = 'f')
+  @GuiOsxKeyboardShortcut(command = true, key = 'f')
   public void activate() {
     // Someone clicked on the search icon of used CTRL-F
     //

Reply via email to