diff --git a/CMakeLists.txt b/CMakeLists.txt index a88e571b..2361e373 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,24 +24,6 @@ option(FETCH_EFSW "Fetch efsw" ON) option(NUI_SFT_ENABLE_LANGUAGE_TOOLING "Enable building language tooling, which includes a command line tool for checking language files" OFF) option(TAR_ARCHIVE_HAS_XZ "Enable xz (liblzma) compression in the tar-archive library" ON) -if (OFFLINE_BUILD) - include("${CMAKE_CURRENT_LIST_DIR}/_cmake/offline_build.cmake") -endif() - -if (OMIT_FRONTEND_BUILD) - set(NUI_OMIT_FRONTEND ON CACHE BOOL "Do not build the frontend, sometimes useful for environments where emscripten is hard to use" FORCE) -endif() - -if (NOT EMSCRIPTEN) - set(BACKEND_BUILD_TYPE "${CMAKE_BUILD_TYPE}") -endif() -configure_file( - "${CMAKE_CURRENT_LIST_DIR}/_cmake/configured_files/build_environment.hpp.in" - "${CMAKE_BINARY_DIR}/include/build_environment.hpp" -) - -include (${CMAKE_CURRENT_LIST_DIR}/_cmake/common_options.cmake) - # Webkit from custom build if (NOT EMSCRIPTEN) if (WEBKIT_PATH) @@ -62,6 +44,37 @@ if (NOT EMSCRIPTEN) endif() endif() +# Nui and webview both look up WebKitGTK, with different preferences they linked GTK 3 and GTK 4 at once. +# One API for both: webkitgtk-6.0 (GTK 4) where it is installed, otherwise webkit2gtk-4.1 (GTK 3). +if (NOT EMSCRIPTEN AND UNIX AND NOT APPLE) + find_package(PkgConfig REQUIRED) + if (WEBKIT_PATH) + pkg_search_module(webkit REQUIRED IMPORTED_TARGET ${WEBKIT_PACKAGE_NAME}) + else() + pkg_search_module(webkit REQUIRED IMPORTED_TARGET webkitgtk-6.0 webkit2gtk-4.1 webkit2gtk-4.0) + endif() + set(WEBVIEW_WEBKITGTK_MODULE_NAME "${webkit_MODULE_NAME}") + message(STATUS "WebKitGTK API: ${webkit_MODULE_NAME}") +endif() + +if (OFFLINE_BUILD) + include("${CMAKE_CURRENT_LIST_DIR}/_cmake/offline_build.cmake") +endif() + +if (OMIT_FRONTEND_BUILD) + set(NUI_OMIT_FRONTEND ON CACHE BOOL "Do not build the frontend, sometimes useful for environments where emscripten is hard to use" FORCE) +endif() + +if (NOT EMSCRIPTEN) + set(BACKEND_BUILD_TYPE "${CMAKE_BUILD_TYPE}") +endif() +configure_file( + "${CMAKE_CURRENT_LIST_DIR}/_cmake/configured_files/build_environment.hpp.in" + "${CMAKE_BINARY_DIR}/include/build_environment.hpp" +) + +include (${CMAKE_CURRENT_LIST_DIR}/_cmake/common_options.cmake) + # Nui Dependency add_subdirectory("${CMAKE_SOURCE_DIR}/dependencies/Nui" EXCLUDE_FROM_ALL) diff --git a/_cmake/copy_styles_to_build_dir.cmake b/_cmake/copy_styles_to_build_dir.cmake index 90e1bf04..738835c0 100644 --- a/_cmake/copy_styles_to_build_dir.cmake +++ b/_cmake/copy_styles_to_build_dir.cmake @@ -2,6 +2,8 @@ if (NOT OMIT_FRONTEND_BUILD) add_custom_command( OUTPUT "${CMAKE_BINARY_DIR}/styles/nui-file-explorer/file_grid.css" + "${CMAKE_BINARY_DIR}/styles/nui-file-explorer/file_grid_background.png" + "${CMAKE_BINARY_DIR}/styles/nui-file-explorer/file_grid_background_light.png" "${CMAKE_BINARY_DIR}/styles/5cript-nui-components/button.css" "${CMAKE_BINARY_DIR}/styles/5cript-nui-components/carousel.css" "${CMAKE_BINARY_DIR}/styles/5cript-nui-components/color_picker.css" @@ -27,6 +29,8 @@ if (NOT OMIT_FRONTEND_BUILD) COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/styles/5cript-nui-components" COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/styles/5cript-nui-components/themes" COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/nui-file-explorer/styles/file_grid.css" "${CMAKE_BINARY_DIR}/styles/nui-file-explorer/file_grid.css" + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/nui-file-explorer/styles/file_grid_background.png" "${CMAKE_BINARY_DIR}/styles/nui-file-explorer/file_grid_background.png" + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/nui-file-explorer/styles/file_grid_background_light.png" "${CMAKE_BINARY_DIR}/styles/nui-file-explorer/file_grid_background_light.png" COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dependencies/5cript-nui-components/styles/button.css" "${CMAKE_BINARY_DIR}/styles/5cript-nui-components/button.css" COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dependencies/5cript-nui-components/styles/carousel.css" "${CMAKE_BINARY_DIR}/styles/5cript-nui-components/carousel.css" COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dependencies/5cript-nui-components/styles/color_picker.css" "${CMAKE_BINARY_DIR}/styles/5cript-nui-components/color_picker.css" @@ -50,6 +54,8 @@ if (NOT OMIT_FRONTEND_BUILD) COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dependencies/5cript-nui-components/styles/themes/light.css" "${CMAKE_BINARY_DIR}/styles/5cript-nui-components/themes/light.css" DEPENDS "${CMAKE_SOURCE_DIR}/nui-file-explorer/styles/file_grid.css" + "${CMAKE_SOURCE_DIR}/nui-file-explorer/styles/file_grid_background.png" + "${CMAKE_SOURCE_DIR}/nui-file-explorer/styles/file_grid_background_light.png" "${CMAKE_SOURCE_DIR}/dependencies/5cript-nui-components/styles/button.css" "${CMAKE_SOURCE_DIR}/dependencies/5cript-nui-components/styles/carousel.css" "${CMAKE_SOURCE_DIR}/dependencies/5cript-nui-components/styles/color_picker.css" @@ -78,6 +84,8 @@ if (NOT OMIT_FRONTEND_BUILD) ALL DEPENDS "${CMAKE_BINARY_DIR}/styles/nui-file-explorer/file_grid.css" + "${CMAKE_BINARY_DIR}/styles/nui-file-explorer/file_grid_background.png" + "${CMAKE_BINARY_DIR}/styles/nui-file-explorer/file_grid_background_light.png" "${CMAKE_BINARY_DIR}/styles/5cript-nui-components/button.css" "${CMAKE_BINARY_DIR}/styles/5cript-nui-components/carousel.css" "${CMAKE_BINARY_DIR}/styles/5cript-nui-components/color_picker.css" diff --git a/backend/source/backend/main.cpp b/backend/source/backend/main.cpp index f10ebffe..3707f3ed 100644 --- a/backend/source/backend/main.cpp +++ b/backend/source/backend/main.cpp @@ -8,6 +8,8 @@ #else # include # include + +# include #endif #include @@ -476,6 +478,10 @@ int main(int const argc, char const* const* argv) #ifdef __linux__ setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1", 0); + // GTK 4 does not take it from argv like GTK 3, it names the X11 WM_CLASS and the Wayland app id. + // In a flatpak WebKit has to use the app id, the portal refuses to spawn its sandboxed web process otherwise. + const char* const flatpakApplicationId = std::getenv("FLATPAK_ID"); + g_set_prgname(flatpakApplicationId != nullptr && *flatpakApplicationId != '\0' ? flatpakApplicationId : "nui-sftp"); #endif ssh_init(); diff --git a/frontend/source/frontend/CMakeLists.txt b/frontend/source/frontend/CMakeLists.txt index be3e9c10..2e1da979 100644 --- a/frontend/source/frontend/CMakeLists.txt +++ b/frontend/source/frontend/CMakeLists.txt @@ -247,6 +247,8 @@ nui_prepare_emscripten_target( OBSERVED_BUNDLE_FILES "${CMAKE_SOURCE_DIR}/static/index.html" "${CMAKE_SOURCE_DIR}/nui-file-explorer/styles/file_grid.css" + "${CMAKE_SOURCE_DIR}/nui-file-explorer/styles/file_grid_background.png" + "${CMAKE_SOURCE_DIR}/nui-file-explorer/styles/file_grid_background_light.png" "${CMAKE_SOURCE_DIR}/static/source/content_panels/file_explorer.ts" "${CMAKE_SOURCE_DIR}/static/source/content_panels/terminal.ts" "${CMAKE_SOURCE_DIR}/static/source/content_panel_manager.ts" diff --git a/nui-file-explorer/styles/file_grid.css b/nui-file-explorer/styles/file_grid.css index f178bb45..a9baba53 100644 --- a/nui-file-explorer/styles/file_grid.css +++ b/nui-file-explorer/styles/file_grid.css @@ -48,21 +48,13 @@ } .nui-file-grid-background { - /* Makes glass work */ - --background-image: - radial-gradient(at 56% 8%, hsla(323, 64%, 77%, 1) 0px, transparent 50%), - radial-gradient(at 88% 7%, hsla(186, 94%, 76%, 1) 0px, transparent 50%), - radial-gradient(at 67% 1%, hsla(321, 80%, 79%, 1) 0px, transparent 50%), - radial-gradient(at 60% 69%, hsla(39, 75%, 79%, 1) 0px, transparent 50%), - radial-gradient(at 22% 33%, hsla(314, 83%, 65%, 1) 0px, transparent 50%), - radial-gradient(at 7% 91%, hsla(200, 78%, 70%, 1) 0px, transparent 50%), - radial-gradient(at 19% 86%, hsla(137, 62%, 74%, 1) 0px, transparent 50%); - - background-image: var(--background-image); + /* Makes glass work. Radial gradients multiplied over the background color, pre-rendered by + scripts/render_file_grid_background.py because WebKitGTK paints live gradients slowly */ + background-image: var(--background-image, url("data-url:./file_grid_background.png")); + background-size: 100% 100%; /* Darken overlay to improve contrast (doesn't affect child elements) */ background-color: rgba(0, 0, 0, 0.55); - background-blend-mode: multiply; } .nui-file-grid-content:first-child { @@ -212,8 +204,8 @@ overflow: hidden; isolation: isolate; - box-shadow: 0 10px 25px rgba(0, 0, 0, 0.35); - transition: transform 120ms ease, box-shadow 120ms ease; + /* WebKitGTK paints without compositing: blurry shadows and hover transitions repaint the whole grid */ + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.35); &[data-type="Symbolic Link"] { color: var(--nui-file-grid-symbolic-link-color, rgb(0, 242, 255)); @@ -261,8 +253,6 @@ linear-gradient(145deg, rgba(255, 255, 255, 0.32), rgba(255, 255, 255, 0.05)); - - transition: background 120ms ease; } /* ── Specular highlight border ── */ @@ -277,7 +267,6 @@ inset 2px 2px 0px -1px rgba(255, 255, 255, 0.75), inset 0 0 4px 1px rgba(255, 255, 255, 0.45); pointer-events: none; - transition: box-shadow 120ms ease, border-color 120ms ease; } /* ── Content above pseudo layers ── */ @@ -288,7 +277,7 @@ .nui-file-grid-item-icons.is-muted { opacity: 0.55; - box-shadow: 0 6px 14px rgba(0, 0, 0, 0.45); + box-shadow: 0 3px 6px rgba(0, 0, 0, 0.45); filter: saturate(70%) brightness(85%); } @@ -343,7 +332,7 @@ .nui-file-grid-item-icons:hover { transform: translateY(-2px); - box-shadow: 0 16px 32px rgba(0, 0, 0, 0.45); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.45); } .nui-file-grid-item-icons:hover::before { diff --git a/nui-file-explorer/styles/file_grid_background.png b/nui-file-explorer/styles/file_grid_background.png new file mode 100644 index 00000000..b3de0f50 Binary files /dev/null and b/nui-file-explorer/styles/file_grid_background.png differ diff --git a/nui-file-explorer/styles/file_grid_background_light.png b/nui-file-explorer/styles/file_grid_background_light.png new file mode 100644 index 00000000..e616b36c Binary files /dev/null and b/nui-file-explorer/styles/file_grid_background_light.png differ diff --git a/package.json b/package.json index da61aefa..4b20e5df 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ "webpage:serve": "npm --prefix webpage run serve" }, "devDependencies": { + "@parcel/optimizer-data-url": "^2.13.0", + "@parcel/transformer-inline-string": "^2.13.0", "cross-env": "^7.0.3", "dotenv": "^16.4.5", "less": "^4.2.2", diff --git a/scripts/render_file_grid_background.py b/scripts/render_file_grid_background.py new file mode 100755 index 00000000..6a377dac --- /dev/null +++ b/scripts/render_file_grid_background.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# Renders the file grid background images into nui-file-explorer/styles. +# Seven radial gradients multiplied over the grid background color, following CSS Images 3 (farthest-corner +# ellipses, premultiplied interpolation) and Compositing 1 (separable blend, then source-over) in sRGB. +# The image holds only what the gradients add, drawn over that same background color it reproduces the blend. +# WebKitGTK paints the live gradients on the CPU on every repaint, a stretched image is far cheaper. +# Usage: render_file_grid_background.py [width] [height] +import colorsys +import sys +from pathlib import Path + +import numpy as np +from PIL import Image + +styles = Path(__file__).resolve().parent.parent / "nui-file-explorer/styles" +width = int(sys.argv[1]) if len(sys.argv) > 1 else 160 +height = int(sys.argv[2]) if len(sys.argv) > 2 else 128 + +# (x %, y %, hue, saturation %, lightness %), first entry is the top layer +spots = [ + (56, 8, 323, 64, 77), + (88, 7, 186, 94, 76), + (67, 1, 321, 80, 79), + (60, 69, 39, 75, 79), + (22, 33, 314, 83, 65), + (7, 91, 200, 78, 70), + (19, 86, 137, 62, 74), +] + +# file name -> background color (r, g, b, a) of .nui-file-grid-background in that theme +variants = { + "file_grid_background.png": (0, 0, 0, 0.55), + "file_grid_background_light.png": (224 / 255, 224 / 255, 224 / 255, 1), +} + + +def blendedGradients(backdropColor, backdropAlpha): + rows, columns = np.mgrid[0:height, 0:width].astype(np.float64) + 0.5 + premultiplied = np.broadcast_to(np.array(backdropColor) * backdropAlpha, (height, width, 3)).copy() + alpha = np.full((height, width), backdropAlpha) + for spotX, spotY, hue, saturation, lightness in reversed(spots): + source = np.array(colorsys.hls_to_rgb(hue / 360, lightness / 100, saturation / 100)) + centerX = spotX / 100 * width + centerY = spotY / 100 * height + radiusX = max(centerX, width - centerX) * np.sqrt(2) + radiusY = max(centerY, height - centerY) * np.sqrt(2) + distance = np.sqrt(((columns - centerX) / radiusX) ** 2 + ((rows - centerY) / radiusY) ** 2) + sourceAlpha = np.clip(1 - distance / 0.5, 0, 1)[..., None] + + layerAlpha = alpha[..., None] + backdrop = premultiplied / np.maximum(layerAlpha, 1e-9) + blended = (1 - layerAlpha) * source + layerAlpha * source * backdrop + premultiplied = sourceAlpha * blended + (1 - sourceAlpha) * premultiplied + alpha = sourceAlpha[..., 0] + alpha * (1 - sourceAlpha[..., 0]) + return premultiplied, alpha + + +for fileName, (red, green, blue, backdropAlpha) in variants.items(): + backdropColor = np.array([red, green, blue]) + premultiplied, alpha = blendedGradients(backdropColor, backdropAlpha) + if backdropAlpha >= 1: + imageAlpha = np.ones_like(alpha) + imagePremultiplied = premultiplied + else: + # Solve image over backdrop == blended result + imageAlpha = np.clip((alpha - backdropAlpha) / (1 - backdropAlpha), 0, 1) + imagePremultiplied = premultiplied - (1 - imageAlpha[..., None]) * backdropAlpha * backdropColor + color = np.clip(imagePremultiplied / np.maximum(imageAlpha[..., None], 1e-9), 0, 1) + pixels = np.round(np.dstack([color, imageAlpha]) * 255).astype(np.uint8) + Image.fromarray(pixels, "RGBA").save(styles / fileName, optimize=True) + print(f"{styles / fileName} {width}x{height}") diff --git a/static/styles/main.css b/static/styles/main.css index abd61686..acd519cc 100644 --- a/static/styles/main.css +++ b/static/styles/main.css @@ -224,6 +224,7 @@ & .nui-file-grid-background { background-color: var(--background-color); + background-image: var(--background-image, url("data-url:../../../styles/nui-file-explorer/file_grid_background_light.png")); } & .nui-file-grid-item-icons:is(.selected)::after { diff --git a/static/styles/main_page.css b/static/styles/main_page.css index 2511671e..f59a1dc3 100644 --- a/static/styles/main_page.css +++ b/static/styles/main_page.css @@ -12,4 +12,10 @@ .main-page-wrap { width: 100%; height: 100%; -} \ No newline at end of file +} +/* WebKitGTK paints without compositing and repaints whatever lies under an overlay repaint, skip the covered session area */ +.settings-page-background-blocker[style*="display: flex"] ~ .main-page .session-area, +.licenses-page-background-blocker[style*="display: flex"] ~ .main-page .session-area, +.notification-log-background-blocker[style*="display: flex"] ~ .main-page .session-area { + content-visibility: hidden; +} diff --git a/themes/Plain.css b/themes/Plain.css index e2320a2b..647ecf87 100644 --- a/themes/Plain.css +++ b/themes/Plain.css @@ -7,5 +7,5 @@ } .nui-file-grid-background { - --background-image: unset !important; + --background-image: none !important; } \ No newline at end of file