diff --git a/README.md b/README.md index b2f2d92..14a3627 100644 --- a/README.md +++ b/README.md @@ -201,25 +201,39 @@ To enable liquid glass in Lniri, add this window rule to `~/.config/niri/config. ```kdl window-rule { + match app-id="io.github.soyeb_jim285.Bubble" + match app-id="bubble" match app-id="Bubble" + + open-floating true + default-column-width { proportion 0.72; } + default-window-height { proportion 0.78; } + draw-border-with-background false + geometry-corner-radius 14 + clip-to-geometry true + background-effect { blur true xray true liquid-glass { - liquidity 0.2 - refraction-strength 5.0 - power-factor 3.5 - refraction-power 1 - glow-weight 0.1 - edge-lighting 0.2 - saturation 1.1 - vibrancy 0.6 - adaptive-dim 0.1 - adaptive-boost 0.0 - physical-refraction 0.1 - lens-distortion 0.1 - fringing 0.6 + mode "kwin-glass" + liquidity 0.6 + refraction-strength 4.5 + power-factor 3.2 + refraction-bevel-intensity 10.0 + refraction-offset-strength 8.0 + edge-thickness 0.18 + fringing 0.45 + glow-weight 0.015 + edge-lighting 0.20 + oklab-saturation 1.0 + saturation 1.20 + vibrancy 0.45 + adaptive-dim 0.0 + adaptive-boost 0.20 + physical-refraction 1.0 + lens-distortion 0.20 } } } diff --git a/config.md b/config.md new file mode 100644 index 0000000..5d429e3 --- /dev/null +++ b/config.md @@ -0,0 +1,369 @@ +# Bubble Configuration & Ecosystem Guide + +Comprehensive guide for configuring **Bubble**, customizing themes, and integrating with Wayland compositors (such as **Lniri / Niri**, **Hyprland**, and **KDE Plasma**) for an optical liquid-glass aesthetic. + +--- + +## Table of Contents + +1. [Configuration Overview](#configuration-overview) +2. [File Locations](#file-locations) +3. [Configuration Reference (`config.toml`)](#configuration-reference-configtoml) + - [General (`[general]`)](#general-general) + - [Sidebar (`[sidebar]`)](#sidebar-sidebar) + - [Appearance (`[appearance]`)](#appearance-appearance) + - [Animations (`[animations]`)](#animations-animations) + - [Window Controls (`[window]`)](#window-controls-window) + - [Detailed View Columns (`[list_view]`)](#detailed-view-columns-list_view) + - [Miller Columns (`[miller_view]`)](#miller-columns-miller_view) + - [Bookmarks (`[bookmarks]`)](#bookmarks-bookmarks) + - [Custom Context Menu Actions (`[context_menu]`)](#custom-context-menu-actions-context_menu) + - [Keyboard Shortcuts (`[shortcuts]`)](#keyboard-shortcuts-shortcuts) +4. [Liquid Glass Ecosystem & Compositor Integration](#liquid-glass-ecosystem--compositor-integration) + - [Lniri / Niri (with kwin-glass)](#lniri--niri-with-kwin-glass) + - [Hyprland](#hyprland) + - [KDE Plasma / KWin](#kde-plasma--kwin) +5. [The Liquid Themes: `liquid-light` & `liquid-dark`](#the-liquid-themes-liquid-light--liquid-dark) +6. [Creating Custom Themes](#creating-custom-themes) + +--- + +## Configuration Overview + +Bubble is designed to be instantly configurable both via its graphical **Settings Panel** (`Ctrl+,` or the gear icon in the top-right toolbar) and directly via human-readable TOML files. + +When Bubble is running, modifications saved in `config.toml` are automatically detected and reloaded live without needing to restart the application. + +--- + +## File Locations + +| Path | Purpose | +| :--- | :--- | +| `~/.config/bubble/config.toml` | User configuration file | +| `~/.config/bubble/config.toml.sample` | Auto-generated reference template containing all default keys and comments | +| `~/.config/bubble/themes/` | User-defined custom TOML themes | +| `/usr/share/bubble/themes/` | System-wide built-in themes | +| `~/.config/bubble/folder_sort.json` | Per-folder sort memory state | + +> [!NOTE] +> When settings are updated from within Bubble's graphical UI, `config.toml` is written cleanly. The adjacent `config.toml.sample` is automatically updated on every launch to retain a complete, documented reference template. + +--- + +## Configuration Reference (`config.toml`) + +Below is a complete reference of all available tables and keys in `config.toml`. + +### General (`[general]`) + +```toml +[general] +# Active colour theme (theme name matching a TOML file without .toml). +# If omitted or empty, Bubble follows system light/dark preference. +theme = "liquid-light" + +# Themes used when toggling Dark Mode in Settings or via shortcut. +light_theme = "liquid-light" +dark_theme = "liquid-dark" + +# Icon theme for files and folders (directory name under /usr/share/icons or ~/.icons). +icon_theme = "Adwaita" + +# UI font family. Empty string uses the system default UI font. +font_family = "" + +# Default view for newly opened tabs: "grid" | "detailed" | "miller" +default_view = "grid" + +# Show hidden (dot) files by default: true | false +show_hidden = false + +# Right-clicking the breadcrumb address bar switches to text edit mode (Ctrl+L behavior) +right_click_to_edit_path = true + +# Primary sort order: "name" | "size" | "modified" | "type" +sort_by = "name" +sort_ascending = true + +# Store and remember individual sort preferences per folder +remember_sort_per_folder = true + +# Check for required CLI helpers (gvfs, ffmpeg, bat, pdftoppm...) on startup +dependency_startup_check = true +``` + +### Sidebar (`[sidebar]`) + +```toml +[sidebar] +# Sidebar placement: "left" | "right" +position = "left" + +# Sidebar width in pixels +width = 200 + +# Initial sidebar visibility: true | false +visible = true + +# Built-in quick access items to hide. +# Possible entries: "Home", "Recents", "Trash", "Network", "Pictures", "Downloads" +hidden_quick_access = [] +``` + +### Appearance (`[appearance]`) + +```toml +[appearance] +# Corner radii in pixels for components and cards +radius_small = 4 +radius_medium = 8 +radius_large = 14 + +# Master transparency toggle and level (0.0 = fully transparent, 1.0 = fully opaque) +transparency_enabled = true +transparency_level = 1.0 +``` + +### Animations (`[animations]`) + +```toml +[animations] +enabled = true + +# Transition durations in milliseconds +anim_duration_fast = 100 +anim_duration = 200 +anim_duration_slow = 350 + +# Easing curves (Qt easing curve names): +# "Linear" | "InCubic" | "OutCubic" | "InOutCubic" | "OutBack" | "InOutQuad" | "OutQuad" | "OutExpo" | "InOutExpo" | "Bezier" +anim_curve_enter = "OutCubic" +anim_curve_exit = "InCubic" +anim_curve_transition = "Bezier" +``` + +### Window Controls (`[window]`) + +```toml +[window] +# Render client-side minimize, maximize, and close buttons. +# When omitted, Bubble automatically detects if the compositor provides server-side decorations. +# show_controls = false + +# Titlebar button ordering (":" separates left side from right side) +button_layout = ":minimize,maximize,close" +``` + +### Detailed View Columns (`[list_view]`) + +```toml +[list_view] +# Column keys in display order ("name" is always present) +# Available: "name", "size", "modified", "type", "permissions", "owner", "group", "created", "accessed", "extension", "mime", "git", "symlink" +columns = ["name", "size", "modified", "type"] + +# Column widths in pixels +column_widths = { size = 110, modified = 140, type = 80 } +``` + +### Miller Columns (`[miller_view]`) + +```toml +[miller_view] +# Relative widths of parent and current columns (floating fraction between 0.12 and 0.70) +parent_fraction = 0.2 +current_fraction = 0.5 +``` + +### Bookmarks (`[bookmarks]`) + +```toml +[bookmarks] +# Custom sidebar bookmark directory paths (defaults to standard XDG user folders) +paths = ["~/Documents", "~/Downloads", "~/Pictures", "~/Projects"] + +# Custom bookmark display aliases +names = { "~/Projects" = "Work" } +``` + +### Custom Context Menu Actions (`[context_menu]`) + +Add custom actions to the right-click menu for selected files and directories. + +```toml +[[context_menu.actions]] +name = "Open in VS Code" +command = "code %f" +types = ["dir", "text/*"] + +[[context_menu.actions]] +name = "Optimize PNG" +command = "oxipng -o 4 %f" +types = ["png"] +``` + +- `%f` or `%u`: Path or URL of the selected item. +- `types`: `"*"` (all files), `"dir"` (directories only), an extension (e.g. `"png"`), or a MIME type pattern (e.g. `"image/*"`). + +### Keyboard Shortcuts (`[shortcuts]`) + +Override any default key sequence using Qt shortcut syntax: + +```toml +[shortcuts] +open = "Return" +back = "Alt+Left" +forward = "Alt+Right" +parent = "Alt+Up" +home = "Alt+Home" +refresh = "F5" +new_tab = "Ctrl+T" +new_window = "Ctrl+Alt+N" +close_tab = "Ctrl+W" +next_tab = "Ctrl+Tab" +previous_tab = "Ctrl+Shift+Tab" +reopen_tab = "Ctrl+Shift+T" +open_in_new_tab = "Ctrl+Return" +open_in_split = "Ctrl+Shift+Return" +copy = "Ctrl+C" +cut = "Ctrl+X" +paste = "Ctrl+V" +rename = "F2" +new_folder = "Ctrl+Shift+N" +new_file = "Ctrl+N" +trash = "Delete" +permanent_delete = "Shift+Delete" +toggle_hidden = "Ctrl+H" +toggle_transparency = "Ctrl+Shift+B" +quick_preview = "Space" +search = "Ctrl+F" +context_menu = "Shift+F10" +open_terminal = "Ctrl+Alt+T" +properties = "Alt+Return" +path_bar = "Ctrl+L" +vault_lock = "Ctrl+Shift+L" +toggle_sidebar = "F9" +split_view = "F3" +focus_next_pane = "F6" +focus_previous_pane = "Shift+F6" +focus_left_pane = "Ctrl+Alt+Left" +focus_right_pane = "Ctrl+Alt+Right" +grid_view = "Ctrl+1" +miller_view = "Ctrl+2" +detailed_view = "Ctrl+3" +select_all = "Ctrl+A" +undo = "Ctrl+Z" +redo = "Ctrl+Shift+Z" +settings = "Ctrl+," +edit_config = "Ctrl+Shift+," +keyboard_shortcuts = "Ctrl+?" +``` + +--- + +## Liquid Glass Ecosystem & Compositor Integration + +Bubble features a dedicated **Liquid Glass Layout Engine** calibrated to match the optical aesthetics of modern frosted glass desktop environments (such as KDE Dolphin under Lniri's `kwin-glass` shader stack). + +### Architectural Behavior: +- **Zero-Opacity Framing**: The sidebar, toolbar, and status bar have 0% background opacity (`opacity = 0.0`), eliminating harsh cutouts, divider lines, and inverse corner artifacts. +- **Floating Elevated Card Container**: The active file view sits in an elevated floating card with `14px` border radius, a `1px` subtle ambient glass rim border, and `30%` view opacity. +- **Dual-Pane Split View (`F3`)**: In split-view mode, Bubble renders twin floating frosted cards side-by-side with independent focus borders and clear visual separation. + +### Lniri / Niri (with kwin-glass) + +In `~/.config/niri/config.kdl`, add window rules for Bubble to activate hardware blur and rounded window clipping: + +```kdl +window-rule { + match app-id="bubble" + match app-id="Bubble" + match title="Bubble" + match title="Bubble Settings" + + clip-to-geometry true + geometry-corner-radius 14 + blur true +} +``` + +Reload Lniri config dynamically: +```bash +niri msg action load-config-file +``` + +### Hyprland + +In `~/.config/hypr/hyprland.conf`: + +```ini +windowrulev2 = blur, class:^(bubble)$ +windowrulev2 = rounding 14, class:^(bubble)$ +windowrulev2 = noshadow, class:^(bubble)$ +``` + +### KDE Plasma / KWin + +In KDE Plasma, ensure **Background Contrast** and **Blur** desktop effects are enabled in *System Settings -> Workspace Behavior -> Desktop Effects*. Window rules can be added via *System Settings -> Window Management -> Window Rules* targeting `bubble` to enforce background blur. + +--- + +## The Liquid Themes: `liquid-light` & `liquid-dark` + +Bubble provides two official liquid themes pre-calibrated for optical glass environments: + +1. **`liquid-light`**: High-key translucent glass theme with subtle specular borders and clean typography designed for light wallpapers. +2. **`liquid-dark`**: Deep ambient obsidian glass theme with luminous selections and soft rim highlights designed for dark and colorful wallpapers. + +### Exact Optical Parameters: + +```toml +[glass] +sidebar_opacity = 0.0 +toolbar_opacity = 0.0 +content_opacity = 0.30 +noise_enabled = false +noise_opacity = 0.0 +border_width = 1 +``` + +Switch between them dynamically in `~/.config/bubble/config.toml` or using the Dark Mode switch in the Settings dialog (`Ctrl+,`). + +--- + +## Creating Custom Themes + +To create a custom theme, place a `.toml` file inside `~/.config/bubble/themes/.toml`: + +```toml +[theme] +name = "my-custom-glass" +author = "You" +description = "Custom Frosted Glass Theme" + +[colors] +base = "#181825" +text = "#cdd6f4" +subtext0 = "#a6adc8" +surface0 = "#313244" +surface1 = "#45475a" +blue = "#89b4fa" +red = "#f38ba8" +green = "#a6e3a1" +yellow = "#f9e2af" +lavender = "#b4befe" + +[glass] +# Set sidebar and toolbar to 0.0 to activate the floating card glass layout +sidebar_opacity = 0.0 +toolbar_opacity = 0.0 +content_opacity = 0.35 +noise_enabled = false +noise_opacity = 0.0 +border_color = "#ffffff" +border_opacity = 0.12 +border_width = 1 +``` + +When `sidebar_opacity` and `toolbar_opacity` are set to `0.0`, Bubble's QML engine automatically activates floating elevated card mode. If set to standard opacity (e.g. `0.9` or `1.0`), Bubble smoothly falls back to the classic unified panel layout. diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 3c57f9f..d9fa9b5 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -531,6 +531,11 @@ ApplicationWindow { } function scheduleActivePaneFocus() { + if (root.shouldFocusActivePane()) { + var immediateView = root.activeSubView() + if (immediateView) + immediateView.forceActiveFocus() + } if (paneFocusScheduled) return @@ -3892,7 +3897,7 @@ ApplicationWindow { Layout.fillHeight: true Rectangle { - visible: root.sidebarVisible + visible: root.sidebarVisible && !Theme.isFloatingGlassLayout x: config.sidebarPosition === "right" ? parent.width - 1 : -1 y: 0 width: 2 @@ -3979,12 +3984,23 @@ ApplicationWindow { id: contentArea Layout.fillWidth: true Layout.fillHeight: true - color: Theme.contentBackground + Layout.topMargin: Theme.isFloatingGlassLayout ? 4 : 0 + Layout.bottomMargin: Theme.isFloatingGlassLayout ? 6 : 0 + Layout.leftMargin: Theme.isFloatingGlassLayout ? (config.sidebarPosition === "right" ? 10 : (root.sidebarVisible ? 6 : 10)) : 0 + Layout.rightMargin: Theme.isFloatingGlassLayout ? (config.sidebarPosition === "right" ? (root.sidebarVisible ? 6 : 10) : 10) : 0 + radius: Theme.isFloatingGlassLayout ? (root.splitViewPresented ? 0 : Theme.radiusLarge) : 0 + clip: Theme.isFloatingGlassLayout ? !root.splitViewPresented : false + color: Theme.isFloatingGlassLayout + ? (root.splitViewPresented ? "transparent" : Theme.contentBackground) + : Theme.contentBackground + border.width: Theme.isFloatingGlassLayout && !root.splitViewPresented ? 1 : 0 + border.color: Qt.rgba(Theme.glowColor.r, Theme.glowColor.g, Theme.glowColor.b, 0.22) // ── Glass Effects (content area) ───────────────────────── Rectangle { - visible: Theme.hasEffects && Theme.gradientEnabled + visible: Theme.hasEffects && Theme.gradientEnabled && (!Theme.isFloatingGlassLayout || !root.splitViewPresented) anchors.fill: parent + radius: parent.radius z: 0 gradient: Gradient { orientation: Gradient.Vertical @@ -3996,6 +4012,7 @@ ApplicationWindow { Rectangle { visible: Theme.hasEffects && Theme.noiseEnabled && Theme.noiseOpacity > 0 anchors.fill: parent + radius: parent.radius z: 0 color: "transparent" opacity: Theme.noiseOpacity * 0.5 @@ -4018,8 +4035,9 @@ ApplicationWindow { } } - // Curved mantle fills for inverse rounded corners + // Curved mantle fills for inverse rounded corners (only for flush classic layout) Shape { + visible: !Theme.isFloatingGlassLayout && !Theme.isZeroOpacityToolbar z: 1; width: Theme.radiusMedium; height: Theme.radiusMedium anchors.top: parent.top; anchors.left: parent.left ShapePath { @@ -4035,6 +4053,7 @@ ApplicationWindow { } } Shape { + visible: !Theme.isFloatingGlassLayout && !Theme.isZeroOpacityToolbar z: 1; width: Theme.radiusMedium; height: Theme.radiusMedium anchors.top: parent.top; anchors.right: parent.right ShapePath { @@ -4053,10 +4072,10 @@ ApplicationWindow { RowLayout { id: paneRow anchors.fill: parent - anchors.margins: 8 * root.splitTransitionProgress + anchors.margins: root.splitViewPresented ? (Theme.isFloatingGlassLayout ? 0 : 8 * root.splitTransitionProgress) : 0 spacing: 8 * root.splitTransitionProgress - readonly property real dividerWidth: root.splitTransitionProgress + readonly property real dividerWidth: Theme.isFloatingGlassLayout ? 0 : root.splitTransitionProgress readonly property real secondaryPreferredWidth: Math.max( 0, (width - spacing - dividerWidth) * 0.5 * root.splitTransitionProgress @@ -4066,12 +4085,16 @@ ApplicationWindow { id: primaryPaneFrame Layout.fillWidth: true Layout.fillHeight: true - radius: Theme.radiusMedium * root.splitTransitionProgress + radius: Theme.isFloatingGlassLayout + ? (root.splitViewPresented ? Theme.radiusLarge : 0) + : Theme.radiusMedium * root.splitTransitionProgress clip: true // Fill stays opaque the entire time so the pane never // flashes transparent at progress ≈ 0 during close. // Only the split-specific border tint fades. - color: Theme.containerColor(Theme.crust, 0.14) + color: root.splitViewPresented + ? (Theme.isFloatingGlassLayout ? Theme.contentBackground : Theme.containerColor(Theme.crust, 0.14)) + : (Theme.isFloatingGlassLayout ? "transparent" : Theme.containerColor(Theme.crust, 0.14)) ColumnLayout { anchors.fill: parent @@ -4228,17 +4251,21 @@ ApplicationWindow { z: 10 color: "transparent" radius: primaryPaneFrame.radius - border.width: root.splitTransitionProgress + border.width: Theme.isFloatingGlassLayout + ? (root.splitViewPresented ? 1 : 0) + : root.splitTransitionProgress border.color: root.activePane === "primary" - ? Qt.rgba(Theme.accent.r, Theme.accent.g, Theme.accent.b, 0.45 * root.splitTransitionProgress) - : Qt.rgba(Theme.text.r, Theme.text.g, Theme.text.b, 0.08 * root.splitTransitionProgress) + ? Qt.rgba(Theme.accent.r, Theme.accent.g, Theme.accent.b, 0.45 * (Theme.isFloatingGlassLayout ? 1.0 : root.splitTransitionProgress)) + : (Theme.isFloatingGlassLayout + ? Qt.rgba(Theme.glowColor.r, Theme.glowColor.g, Theme.glowColor.b, 0.22) + : Qt.rgba(Theme.text.r, Theme.text.g, Theme.text.b, 0.08 * root.splitTransitionProgress)) Behavior on border.color { ColorAnimation { duration: Theme.animDuration } } } } Loader { id: dividerLoader - active: root.splitViewPresented + active: root.splitViewPresented && !Theme.isFloatingGlassLayout visible: active Layout.preferredWidth: paneRow.dividerWidth Layout.fillHeight: true @@ -4265,9 +4292,9 @@ ApplicationWindow { opacity: root.splitTransitionProgress scale: 0.96 + (0.04 * root.splitTransitionProgress) transformOrigin: Item.Right - radius: Theme.radiusMedium + radius: Theme.isFloatingGlassLayout ? Theme.radiusLarge : Theme.radiusMedium clip: true - color: Theme.containerColor(Theme.crust, 0.14) + color: Theme.isFloatingGlassLayout ? Theme.contentBackground : Theme.containerColor(Theme.crust, 0.14) ColumnLayout { anchors.fill: parent @@ -4313,7 +4340,9 @@ ApplicationWindow { border.width: 1 border.color: root.activePane === "secondary" ? Qt.rgba(Theme.accent.r, Theme.accent.g, Theme.accent.b, 0.45) - : Qt.rgba(Theme.text.r, Theme.text.g, Theme.text.b, 0.08) + : (Theme.isFloatingGlassLayout + ? Qt.rgba(Theme.glowColor.r, Theme.glowColor.g, Theme.glowColor.b, 0.22) + : Qt.rgba(Theme.text.r, Theme.text.g, Theme.text.b, 0.08)) Behavior on border.color { ColorAnimation { duration: Theme.animDuration } } } } diff --git a/src/qml/components/SettingsPanel.qml b/src/qml/components/SettingsPanel.qml index a30c2c5..bc61c0f 100644 --- a/src/qml/components/SettingsPanel.qml +++ b/src/qml/components/SettingsPanel.qml @@ -308,17 +308,35 @@ Window { try { draftTheme = config.theme draftDarkMode = isDarkTheme(draftTheme) - themeOptions = buildOptions(availableThemeValues, draftTheme, "catppuccin-mocha") + if (themeOptions.length === 0) { + themeOptions = buildOptions(availableThemeValues, draftTheme, "catppuccin-mocha") + lightThemeOptions = buildOptions(availableThemeValues, draftLightTheme, "catppuccin-latte") + darkThemeOptions = buildOptions(availableThemeValues, draftDarkTheme, "catppuccin-mocha") + } else if (draftTheme !== "" && themeOptions.indexOf(draftTheme) === -1) { + var topts = themeOptions.slice() + topts.unshift(draftTheme) + themeOptions = topts + } draftLightTheme = config.lightTheme draftDarkTheme = config.darkTheme - lightThemeOptions = buildOptions(availableThemeValues, draftLightTheme, "catppuccin-latte") - darkThemeOptions = buildOptions(availableThemeValues, draftDarkTheme, "catppuccin-mocha") draftFontFamily = config.fontFamily - fontOptions = buildFontOptions() + if (fontOptions.length <= 1) { + fontOptions = buildFontOptions() + } else if (draftFontFamily !== "" && fontOptions.indexOf(draftFontFamily) === -1) { + var fopts = fontOptions.slice() + fopts.push(draftFontFamily) + fontOptions = fopts + } draftIconTheme = config.iconTheme - iconThemeOptions = buildOptions(availableIconThemeValues, draftIconTheme, "Adwaita") + if (iconThemeOptions.length === 0) { + iconThemeOptions = buildOptions(availableIconThemeValues, draftIconTheme, "Adwaita") + } else if (draftIconTheme !== "" && iconThemeOptions.indexOf(draftIconTheme) === -1) { + var iopts = iconThemeOptions.slice() + iopts.unshift(draftIconTheme) + iconThemeOptions = iopts + } draftShowHidden = currentShowHidden draftRightClickToEditPath = config.rightClickToEditPath @@ -352,8 +370,6 @@ Window { } function openPanel() { - syncFromCurrentState() - showSection(0) // Center over the parent window if (transientParent) { root.x = transientParent.x + Math.round((transientParent.width - root.width) / 2) @@ -362,6 +378,8 @@ Window { root.show() root.raise() root.requestActivate() + syncFromCurrentState() + showSection(0) root.syncHyprlandRounding() } diff --git a/src/qml/components/Sidebar.qml b/src/qml/components/Sidebar.qml index 85afcb5..27860fe 100644 --- a/src/qml/components/Sidebar.qml +++ b/src/qml/components/Sidebar.qml @@ -43,8 +43,9 @@ Rectangle { Component { id: iconFolder; IconFolder { size: 18; color: Theme.subtext } } Component { id: iconMonitor; IconMonitor { size: 18; color: Theme.subtext } } - // Inverse rounded corner — top right + // Inverse rounded corner — top right (only for flush classic layout) Shape { + visible: !Theme.isFloatingGlassLayout && !Theme.isZeroOpacitySidebar z: 1; width: Theme.radiusMedium; height: Theme.radiusMedium anchors.top: parent.top; anchors.left: parent.right ShapePath { @@ -60,8 +61,9 @@ Rectangle { } } - // Inverse rounded corner — bottom right + // Inverse rounded corner — bottom right (only for flush classic layout) Shape { + visible: !Theme.isFloatingGlassLayout && !Theme.isZeroOpacitySidebar z: 1; width: Theme.radiusMedium; height: Theme.radiusMedium anchors.bottom: parent.bottom; anchors.left: parent.right ShapePath { @@ -81,7 +83,7 @@ Rectangle { // Gradient tint overlay Rectangle { - visible: Theme.hasEffects && Theme.gradientEnabled + visible: Theme.hasEffects && Theme.gradientEnabled && !Theme.isZeroOpacitySidebar anchors.fill: parent z: 0 gradient: Gradient { @@ -93,7 +95,7 @@ Rectangle { // Noise texture overlay (frosted grain) Rectangle { - visible: Theme.hasEffects && Theme.noiseEnabled && Theme.noiseOpacity > 0 + visible: Theme.hasEffects && Theme.noiseEnabled && Theme.noiseOpacity > 0 && !Theme.isZeroOpacitySidebar anchors.fill: parent z: 0 color: "transparent" @@ -119,7 +121,7 @@ Rectangle { // Border glow effect Rectangle { - visible: Theme.hasEffects && Theme.glowEnabled && Theme.glowOpacity > 0 + visible: Theme.hasEffects && Theme.glowEnabled && Theme.glowOpacity > 0 && !Theme.isZeroOpacitySidebar anchors.fill: parent z: 0 color: "transparent" diff --git a/src/qml/components/StatusBar.qml b/src/qml/components/StatusBar.qml index 9fef199..04944e3 100644 --- a/src/qml/components/StatusBar.qml +++ b/src/qml/components/StatusBar.qml @@ -53,11 +53,12 @@ Rectangle { } height: 28 - color: Theme.chromeBackground + color: Theme.isFloatingGlassLayout ? "transparent" : Theme.chromeBackground clip: false - // Inverse rounded corner — top left + // Inverse rounded corner — top left (only for flush classic layout) Shape { + visible: !Theme.isFloatingGlassLayout && !Theme.isZeroOpacityToolbar z: 1; width: Theme.radiusMedium; height: Theme.radiusMedium anchors.bottom: parent.top; anchors.left: parent.left ShapePath { @@ -73,8 +74,9 @@ Rectangle { } } - // Inverse rounded corner — top right + // Inverse rounded corner — top right (only for flush classic layout) Shape { + visible: !Theme.isFloatingGlassLayout && !Theme.isZeroOpacityToolbar z: 1; width: Theme.radiusMedium; height: Theme.radiusMedium anchors.bottom: parent.top; anchors.right: parent.right ShapePath { diff --git a/src/qml/components/Toolbar.qml b/src/qml/components/Toolbar.qml index 2f16911..793db42 100644 --- a/src/qml/components/Toolbar.qml +++ b/src/qml/components/Toolbar.qml @@ -98,7 +98,7 @@ Rectangle { // ── Glass Effects (toolbar) ────────────────────────────────────── Rectangle { - visible: Theme.hasEffects && Theme.gradientEnabled + visible: Theme.hasEffects && Theme.gradientEnabled && !Theme.isZeroOpacityToolbar anchors.fill: parent z: 0 gradient: Gradient { @@ -109,7 +109,7 @@ Rectangle { } Rectangle { - visible: Theme.hasEffects && Theme.glowEnabled && Theme.glowOpacity > 0 + visible: Theme.hasEffects && Theme.glowEnabled && Theme.glowOpacity > 0 && !Theme.isZeroOpacityToolbar anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom diff --git a/src/qml/theme/Theme.qml b/src/qml/theme/Theme.qml index b1d93de..294f149 100644 --- a/src/qml/theme/Theme.qml +++ b/src/qml/theme/Theme.qml @@ -213,4 +213,7 @@ QtObject { readonly property color sidebarBackground: hasEffects ? glassColor(crust, sidebarOpacity, 0.80) : containerColor(crust, 0.80) + readonly property bool isFloatingGlassLayout: hasEffects && transparencyEnabled + readonly property bool isZeroOpacitySidebar: hasEffects && transparencyEnabled && sidebarOpacity <= 0.01 + readonly property bool isZeroOpacityToolbar: hasEffects && transparencyEnabled && toolbarOpacity <= 0.01 } diff --git a/src/services/configmanager.cpp b/src/services/configmanager.cpp index 822984e..1e6d36a 100644 --- a/src/services/configmanager.cpp +++ b/src/services/configmanager.cpp @@ -225,6 +225,9 @@ ConfigManager::ConfigManager(const QString &configPath, QObject *parent, const Q QStringList ConfigManager::availableThemes() const { + if (!m_cachedThemes.isEmpty()) + return m_cachedThemes; + QStringList themes; for (const QString &themesDir : m_themesDirs) { if (themesDir.isEmpty()) @@ -239,19 +242,27 @@ QStringList ConfigManager::availableThemes() const } } themes.sort(Qt::CaseInsensitive); - return themes; + m_cachedThemes = themes; + return m_cachedThemes; } QStringList ConfigManager::availableFonts() const { + if (!m_cachedFonts.isEmpty()) + return m_cachedFonts; + QStringList fonts = QFontDatabase().families(); fonts.removeDuplicates(); fonts.sort(Qt::CaseInsensitive); - return fonts; + m_cachedFonts = fonts; + return m_cachedFonts; } QStringList ConfigManager::availableIconThemes() const { + if (!m_cachedIconThemes.isEmpty()) + return m_cachedIconThemes; + QStringList themes; for (const QString &baseDir : iconSearchDirs()) { QDir dir(baseDir); @@ -267,7 +278,8 @@ QStringList ConfigManager::availableIconThemes() const } themes.sort(Qt::CaseInsensitive); - return themes; + m_cachedIconThemes = themes; + return m_cachedIconThemes; } void ConfigManager::setDefaults() @@ -318,6 +330,7 @@ void ConfigManager::setDefaults() void ConfigManager::reload() { + m_cachedThemes.clear(); loadConfig(); emit configChanged(); } diff --git a/src/services/configmanager.h b/src/services/configmanager.h index b0e9452..245762b 100644 --- a/src/services/configmanager.h +++ b/src/services/configmanager.h @@ -219,4 +219,7 @@ class ConfigManager : public QObject QString m_configError; QMap m_shortcuts; static QMap s_defaultShortcuts; + mutable QStringList m_cachedFonts; + mutable QStringList m_cachedIconThemes; + mutable QStringList m_cachedThemes; }; diff --git a/src/services/fileoperations.cpp b/src/services/fileoperations.cpp index 6486dda..44f5a8b 100644 --- a/src/services/fileoperations.cpp +++ b/src/services/fileoperations.cpp @@ -2913,6 +2913,9 @@ void FileOperations::setWallpaper(const QString &path) void FileOperations::setHyprlandRounding(const QString &windowTitle, int radius) { + if (qEnvironmentVariableIsEmpty("HYPRLAND_INSTANCE_SIGNATURE")) + return; + auto *proc = new QProcess(this); connect(proc, qOverload(&QProcess::finished), this, [proc](int, QProcess::ExitStatus) { proc->deleteLater(); }); @@ -2925,6 +2928,9 @@ void FileOperations::setHyprlandRounding(const QString &windowTitle, int radius) void FileOperations::setHyprlandBorder(const QString &windowTitle, int size) { + if (qEnvironmentVariableIsEmpty("HYPRLAND_INSTANCE_SIGNATURE")) + return; + auto *proc = new QProcess(this); connect(proc, qOverload(&QProcess::finished), this, [proc](int, QProcess::ExitStatus) { proc->deleteLater(); }); diff --git a/tests/tst_themeloader.cpp b/tests/tst_themeloader.cpp index f22a66c..6d86ad6 100644 --- a/tests/tst_themeloader.cpp +++ b/tests/tst_themeloader.cpp @@ -233,21 +233,21 @@ private slots: QCOMPARE(loader.color("mantle"), QColor("#0d1f3c")); QCOMPARE(loader.color("accent"), QColor("#4da6ff")); QVERIFY(loader.hasEffects()); - QCOMPARE(loader.sidebarOpacity(), 0.72); - QCOMPARE(loader.contentOpacity(), 0.58); - QCOMPARE(loader.toolbarOpacity(), 0.70); + QCOMPARE(loader.sidebarOpacity(), 0.0); + QCOMPARE(loader.contentOpacity(), 0.30); + QCOMPARE(loader.toolbarOpacity(), 0.0); QVERIFY(loader.gradientEnabled()); QCOMPARE(loader.gradientColor(), QColor("#0a1e3d")); QCOMPARE(loader.gradientDirection(), QStringLiteral("top_to_bottom")); QVERIFY(loader.glowEnabled()); QCOMPARE(loader.glowColor(), QColor("#3388cc")); - QCOMPARE(loader.glowRadius(), 16.0); - QCOMPARE(loader.glowOpacity(), 0.35); + QCOMPARE(loader.glowRadius(), 14.0); + QCOMPARE(loader.glowOpacity(), 0.20); QVERIFY(loader.blurEnabled()); - QCOMPARE(loader.blurRadius(), 32.0); - QVERIFY(loader.noiseEnabled()); - QCOMPARE(loader.noiseOpacity(), 0.04); - QCOMPARE(loader.saturation(), 1.15); + QCOMPARE(loader.blurRadius(), 28.0); + QVERIFY(!loader.noiseEnabled()); + QCOMPARE(loader.noiseOpacity(), 0.0); + QCOMPARE(loader.saturation(), 1.20); QVERIFY(loader.refractionEnabled()); QCOMPARE(loader.refractionStrength(), 0.02); } @@ -256,11 +256,11 @@ private slots: { ThemeLoader loader; loader.loadTheme("liquid-light", QStringList{QString(THEMES_DIR)}); - QCOMPARE(loader.color("base"), QColor("#eaf2fb")); + QCOMPARE(loader.color("base"), QColor("#ffffff")); QCOMPARE(loader.color("accent"), QColor("#2080e0")); QVERIFY(loader.hasEffects()); - QCOMPARE(loader.sidebarOpacity(), 0.75); - QCOMPARE(loader.contentOpacity(), 0.62); + QCOMPARE(loader.sidebarOpacity(), 0.0); + QCOMPARE(loader.contentOpacity(), 0.30); QVERIFY(loader.blurEnabled()); } diff --git a/themes/liquid-dark.toml b/themes/liquid-dark.toml index ce2d5c7..11478e5 100644 --- a/themes/liquid-dark.toml +++ b/themes/liquid-dark.toml @@ -13,9 +13,9 @@ warning = "#ffc04d" error = "#ff6b8a" [effects] -sidebar_opacity = 0.72 -content_opacity = 0.58 -toolbar_opacity = 0.70 +sidebar_opacity = 0.0 +content_opacity = 0.30 +toolbar_opacity = 0.0 gradient_enabled = true gradient_color = "#0a1e3d" @@ -23,16 +23,16 @@ gradient_direction = "top_to_bottom" glow_enabled = true glow_color = "#3388cc" -glow_radius = 16.0 -glow_opacity = 0.35 +glow_radius = 14.0 +glow_opacity = 0.20 blur_enabled = true -blur_radius = 32.0 +blur_radius = 28.0 -noise_enabled = true -noise_opacity = 0.04 +noise_enabled = false +noise_opacity = 0.0 -saturation = 1.15 +saturation = 1.20 refraction_enabled = true refraction_strength = 0.02 diff --git a/themes/liquid-light.toml b/themes/liquid-light.toml index 1d627f3..a3c7f1c 100644 --- a/themes/liquid-light.toml +++ b/themes/liquid-light.toml @@ -1,8 +1,8 @@ [colors] -base = "#eaf2fb" +base = "#ffffff" mantle = "#dce8f5" crust = "#d0dff0" -surface = "#c4d6ec" +surface = "#e8f1fa" overlay = "#b0c8e0" text = "#1a2a40" subtext = "#2a3d5a" @@ -13,26 +13,26 @@ warning = "#d89020" error = "#d04060" [effects] -sidebar_opacity = 0.75 -content_opacity = 0.62 -toolbar_opacity = 0.72 +sidebar_opacity = 0.0 +content_opacity = 0.30 +toolbar_opacity = 0.0 gradient_enabled = true -gradient_color = "#c8ddf5" +gradient_color = "#ffffff" gradient_direction = "top_to_bottom" glow_enabled = true -glow_color = "#80b8e8" +glow_color = "#ffffff" glow_radius = 14.0 -glow_opacity = 0.28 +glow_opacity = 0.20 blur_enabled = true blur_radius = 28.0 -noise_enabled = true -noise_opacity = 0.03 +noise_enabled = false +noise_opacity = 0.0 -saturation = 1.10 +saturation = 1.20 refraction_enabled = true -refraction_strength = 0.015 +refraction_strength = 0.02