fix(shell): align the search bar with the gallery below it
HeaderBar's centre region defaults to centring on the whole bar's own width, regardless of how wide the leading/trailing regions actually are — search floated visibly off from NavigationSidebar's own right edge as a result. CENTER_LEFT switches it to fill the entire leftover span instead, left-aligned within it; a dynamic left margin then places it exactly at NavigationSidebar.TOTAL_WIDTH. Margin is computed from the brand's actual bounds, not from pholio.css's own header padding: on macOS, HeaderBar also reserves leading space of its own for the native traffic-light buttons, which that padding alone doesn't account for and which disappears in fullscreen.
This commit is contained in:
@@ -13,9 +13,9 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.HeaderBar;
|
||||
import javafx.scene.layout.HeaderDragType;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.util.Duration;
|
||||
import org.icroco.pholio.infra.i18n.I18nService;
|
||||
import org.icroco.pholio.infra.library.LibraryCatalog;
|
||||
import org.icroco.pholio.infra.library.LibraryService;
|
||||
import org.icroco.pholio.infra.preferences.AppPreferences;
|
||||
@@ -25,7 +25,6 @@ import org.icroco.pholio.ui.common.FxThread;
|
||||
import org.icroco.pholio.ui.common.SubscriptionScope;
|
||||
import org.icroco.pholio.ui.common.UiComponent;
|
||||
import org.icroco.pholio.ui.event.NavigateToDestinationEvent;
|
||||
import org.icroco.pholio.infra.i18n.I18nService;
|
||||
import org.icroco.pholio.ui.theme.EColorMode;
|
||||
import org.icroco.pholio.ui.theme.ThemeManager;
|
||||
import org.kordamp.ikonli.feather.Feather;
|
||||
@@ -63,7 +62,7 @@ public class AppHeaderBar extends HeaderBar implements Disposable {
|
||||
private static final String AVAILABLE_KEY = "available";
|
||||
|
||||
/**
|
||||
* Caps the selector so a long database name cannot push the search bar off centre.
|
||||
* Caps the selector so a long database name cannot push the search bar out of the header entirely.
|
||||
*/
|
||||
private static final double SELECTOR_MAX_WIDTH = 180;
|
||||
|
||||
@@ -99,8 +98,32 @@ public class AppHeaderBar extends HeaderBar implements Disposable {
|
||||
this.gallerySearchState = gallerySearchState;
|
||||
|
||||
getStyleClass().add("app-header-bar");
|
||||
setLeft(buildBrand());
|
||||
setCenter(buildCenter());
|
||||
HBox brand = buildBrand();
|
||||
TextField search = buildSearch();
|
||||
|
||||
setLeft(brand);
|
||||
setCenter(search);
|
||||
// HeaderBar's own default centre alignment (null, or any *_CENTER Pos) centres a child on the
|
||||
// bar's whole width, independently of how wide the leading/trailing regions actually are — which
|
||||
// is exactly what kept the search field from lining up with NavigationSidebar's own right edge
|
||||
// (and so with the gallery grid starting below it): a search bar floating a few dozen pixels off
|
||||
// from the content it searches read as a misalignment bug on sight. CENTER_LEFT is what switches
|
||||
// HeaderBar over to its other layout branch instead — the centre child fills the *entire* leftover
|
||||
// span between the leading and trailing regions (not just centres within it), left-aligned inside
|
||||
// that span; the left margin set below is what then places it exactly at NavigationSidebar's own
|
||||
// edge rather than flush against the brand.
|
||||
HeaderBar.setAlignment(search, Pos.CENTER_LEFT);
|
||||
// brand's actual right edge (its bounds, not just its width) + this margin == NavigationSidebar's
|
||||
// own TOTAL_WIDTH — measured from where brand really ends, not computed from pholio.css's own
|
||||
// padding, because that padding is not the only offset at play here: on macOS, HeaderBar reserves
|
||||
// additional leading space of its own for the native traffic-light buttons, before its content
|
||||
// ever starts, and how much varies by platform (and disappears in fullscreen). boundsInParent
|
||||
// already reflects all of that, whatever it happens to be, plus app.name's own translated width.
|
||||
Runnable alignSearchWithGallery = () -> HeaderBar.setMargin(search, new Insets(0, 12, 0,
|
||||
Math.max(0, NavigationSidebar.TOTAL_WIDTH - brand.getBoundsInParent().getMaxX())));
|
||||
alignSearchWithGallery.run();
|
||||
brand.boundsInParentProperty().addListener((obs, oldBounds, newBounds) -> alignSearchWithGallery.run());
|
||||
|
||||
setRight(buildActions());
|
||||
}
|
||||
|
||||
@@ -293,13 +316,15 @@ public class AppHeaderBar extends HeaderBar implements Disposable {
|
||||
|
||||
/**
|
||||
* Just the search bar now — the module selector moved out entirely (its destinations live in
|
||||
* {@link NavigationSidebar} instead), so this is free to take up most of the header's width.
|
||||
* {@link NavigationSidebar} instead), so this is free to take up most of the header's width. Set as
|
||||
* {@code HeaderBar}'s own {@code center} node directly (see the constructor for why, and for the
|
||||
* alignment/margin that actually positions it) — no wrapping {@code HBox} needed, since a
|
||||
* {@code CENTER_LEFT}-aligned centre child is resized to fill its whole span on its own.
|
||||
*/
|
||||
private HBox buildCenter() {
|
||||
private TextField buildSearch() {
|
||||
search.promptTextProperty().bind(i18n.binding("action.search.prompt"));
|
||||
HBox.setHgrow(search, Priority.ALWAYS);
|
||||
// Unstyled, a TextField's own padding makes it taller than the header bar comfortably fits.
|
||||
search.getStyleClass().add(Styles.SMALL);
|
||||
search.getStyleClass().addAll(Styles.TEXT_NORMAL, Styles.ROUNDED);
|
||||
HeaderBar.setDragType(search, HeaderDragType.NONE);
|
||||
Tooltip searchTooltip = new Tooltip();
|
||||
searchTooltip.textProperty().bind(i18n.binding("action.search.tooltip"));
|
||||
@@ -327,13 +352,7 @@ public class AppHeaderBar extends HeaderBar implements Disposable {
|
||||
publisher.publishEvent(new NavigateToDestinationEvent(ENavigationDestination.PHOTOS));
|
||||
});
|
||||
|
||||
HBox centre = new HBox(search);
|
||||
centre.setAlignment(Pos.CENTER);
|
||||
centre.setPadding(new Insets(0, 12, 0, 12));
|
||||
// Blank space in the centre block still moves the window; only the controls inside opt out.
|
||||
HeaderBar.setDragType(centre, HeaderDragType.DRAGGABLE_SUBTREE);
|
||||
|
||||
return centre;
|
||||
return search;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,6 +69,12 @@ public class NavigationSidebar extends VBox implements Disposable {
|
||||
*/
|
||||
private static final double VIEWPORT_GAP = 8;
|
||||
|
||||
/**
|
||||
* The sidebar's total on-screen width, gap included — where the main viewport (and so the gallery)
|
||||
* actually starts. {@code AppHeaderBar} reads this to line its own search field up with it.
|
||||
*/
|
||||
public static final double TOTAL_WIDTH = WIDTH + VIEWPORT_GAP;
|
||||
|
||||
/**
|
||||
* Every entry's fixed leading column — an invisible spacer or a chevron, see the class javadoc.
|
||||
*/
|
||||
@@ -95,9 +101,9 @@ public class NavigationSidebar extends VBox implements Disposable {
|
||||
this.searchState = searchState;
|
||||
|
||||
getStyleClass().add("navigation-sidebar");
|
||||
setMinWidth(WIDTH + VIEWPORT_GAP);
|
||||
setPrefWidth(WIDTH + VIEWPORT_GAP);
|
||||
setMaxWidth(WIDTH + VIEWPORT_GAP);
|
||||
setMinWidth(TOTAL_WIDTH);
|
||||
setPrefWidth(TOTAL_WIDTH);
|
||||
setMaxWidth(TOTAL_WIDTH);
|
||||
setPadding(new Insets(0, VIEWPORT_GAP, 0, 0));
|
||||
|
||||
Entry photos = expandableEntry(ENavigationDestination.PHOTOS, libraryFolderTree,
|
||||
|
||||
Reference in New Issue
Block a user