Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 28 additions & 18 deletions cmake/custom_target_helper.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -50,29 +50,39 @@ function(qt_add_windows_deploy target_name)
NO_UNSUPPORTED_PLATFORM_ERROR
)

message(STATUS "Deploy script for '${target_name}': ${_deploy_script}")

add_custom_target(deploy_${target_name}
COMMAND ${CMAKE_COMMAND}
--install "${CMAKE_BINARY_DIR}"
message(STATUS "Deploy script for '${target_name}': ${_deploy_script}")

set(_locked_deploy_script
"${CMAKE_BINARY_DIR}/.qt/locked_deploy_${target_name}_$<CONFIG>.cmake")
file(GENERATE
OUTPUT "${_locked_deploy_script}"
CONTENT
"file(LOCK \"${CMAKE_BINARY_DIR}/.qt/windeployqt.lock\" GUARD PROCESS TIMEOUT 300)
set(QT_DEPLOY_PREFIX \"$<TARGET_FILE_DIR:${target_name}>\")
set(QT_DEPLOY_BIN_DIR \".\")
set(QT_DEPLOY_LIB_DIR \".\")
set(QT_DEPLOY_PLUGINS_DIR \"plugins\")
set(QT_DEPLOY_QML_DIR \"qml\")
include(\"${_deploy_script}\")
"
)

add_custom_target(deploy_${target_name}
COMMAND ${CMAKE_COMMAND}
--install "${CMAKE_BINARY_DIR}"
--config $<CONFIG>
DEPENDS ${target_name}
COMMENT "Deploying Qt dependencies for '${target_name}'..."
VERBATIM
)

if(QT_DEPLOY_AUTO_DEPLOY)
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DQT_DEPLOY_PREFIX=$<TARGET_FILE_DIR:${target_name}>
-DQT_DEPLOY_BIN_DIR=.
-DQT_DEPLOY_LIB_DIR=.
-DQT_DEPLOY_PLUGINS_DIR=plugins
-DQT_DEPLOY_QML_DIR=qml
-P "${_deploy_script}"
COMMENT "Auto-deploying Qt dependencies for '${target_name}'..."
VERBATIM
)
if(QT_DEPLOY_AUTO_DEPLOY)
add_custom_command(TARGET ${target_name} POST_BUILD
COMMAND ${CMAKE_COMMAND}
-P "${_locked_deploy_script}"
COMMENT "Auto-deploying Qt dependencies for '${target_name}'..."
VERBATIM
)
message(STATUS "[${target_name}] AUTO_DEPLOY enabled: will run after every build")
endif()

Expand All @@ -81,4 +91,4 @@ function(qt_add_windows_deploy target_name)
message(STATUS "[${target_name}] INSTALL_DEPLOY enabled: will run during cmake --install")
endif()

endfunction()
endfunction()
9 changes: 9 additions & 0 deletions desktop/ui/CFDesktop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "cflog.h"
#include "components/PanelManager.h"

#include <QMoveEvent>
#include <QResizeEvent>

namespace cf::desktop {
Expand All @@ -39,6 +40,14 @@ void CFDesktop::resizeEvent(QResizeEvent* event) {
}
}

void CFDesktop::moveEvent(QMoveEvent* event) {
QWidget::moveEvent(event);
// A pure position change (drag) does not alter the local panel layout, so
// relayout/availableGeometryChanged will not fire — emit geometryChanged so
// screen-coordinate dependents (window placement) re-evaluate.
emit geometryChanged();
}

bool CFDesktop::component_available(const DesktopComponent d) const noexcept {
switch (d) {
case DesktopComponent::Common:
Expand Down
29 changes: 29 additions & 0 deletions desktop/ui/CFDesktop.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,35 @@ class CF_DESKTOP_EXPORT CFDesktop final : public QWidget {
protected:
void resizeEvent(QResizeEvent* event) override;

/**
* @brief Forwards desktop position changes to interested shell systems.
*
* Widget move events are not otherwise observed; window placement (and any
* screen-coordinate-relative shell logic) needs to re-evaluate when the
* desktop widget is dragged, so this emits geometryChanged().
*
* @param[in] event The move event descriptor.
*
* @throws None
* @since 0.20
* @ingroup desktop_ui
*/
void moveEvent(QMoveEvent* event) override;

signals:
/**
* @brief Emitted when the desktop widget moves (position changes).
*
* Resize is already covered by PanelManager::availableGeometryChanged
* (relayout); this signal covers the pure-position case so screen-coordinate
* dependents (e.g. window placement) can re-evaluate when the desktop is
* dragged without changing size.
*
* @since 0.20
* @ingroup desktop_ui
*/
void geometryChanged();

private:
aex::WeakPtrFactory<CFDesktop> weak_ptr_factory_;
};
Expand Down
98 changes: 96 additions & 2 deletions desktop/ui/CFDesktopEntity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "IDesktopDisplaySizeStrategy.h"
#include "IDesktopPropertyStrategy.h"
#include "aex/weak_ptr/weak_ptr.h"
#include "cfconfig.hpp"
#include "cflog.h"
#include "components/DisplayServerBackendFactory.h"
#include "components/IDisplayServerBackend.h"
Expand All @@ -13,6 +14,7 @@
#include "components/launcher/app_launcher.h"
#include "components/statusbar/status_bar.h"
#include "components/taskbar/centered_taskbar.h"
#include "components/window_placement/window_placement_policy.h"
#include "platform/DesktopPropertyStrategyFactory.h"
#include "platform/display_backend_helper.h"
#include "platform/shell_layer_helper.h"
Expand Down Expand Up @@ -104,12 +106,64 @@ CFDesktopEntity::RunsSetupResult CFDesktopEntity::run_init(RunsSetupMethod m) {
res.shell_layer_ = shell;
desktop_entity_->register_desktop_resources(res);

// Connect PanelManager geometry changes to ShellLayer
// Connect PanelManager geometry changes to ShellLayer. The wallpaper shell
// spans the FULL host geometry (not the panel-reduced central rect) so it
// renders continuously behind the top/bottom bars; each bar composites a
// frosted copy of the strip directly behind it. The launcher popup still
// uses PanelManager::availableGeometry() (the central-rect getter), which is
// independent and unaffected.
QObject::connect(panel_mgr, &PanelManager::availableGeometryChanged, desktop_entity_,
[shell](const QRect& r) { shell->onAvailableGeometryChanged(r); });
[shell, this](const QRect&) {
shell->onAvailableGeometryChanged(desktop_entity_->rect());
});

// Re-constrain every tracked external window into the current screen-space
// work area. The work area is PanelManager's local central rect translated
// to screen coordinates (window->geometry() is root-relative), so it stays
// correct when the desktop is MOVED, not only resized. Shared by the two
// triggers below: availableGeometryChanged (desktop resized / panels
// relaid out) and geometryChanged (desktop moved without resizing) — the
// back-propagation that was missing for the drag/move case.
auto reconstrain_windows = [panel_mgr, this]() {
if (!display_backend_) {
return;
}
auto backend = display_backend_->windowBackend();
if (!backend) {
return;
}
namespace cfg = cf::config;
const bool enabled = cfg::ConfigStore::instance()
.domain("window_management")
.query<bool>(cfg::KeyView{.group = "window_management",
.key = "constrain_to_workarea"},
true);
const QRect work = panel_mgr->availableGeometry().translated(desktop_entity_->pos());
const cf::desktop::placement::WindowPlacementPolicy policy;
int moved = 0;
for (const auto& wptr : backend->windows()) {
auto* w = wptr.Get();
if (w == nullptr) {
continue;
}
const QRect before = w->geometry();
policy.constrain(*w, work, enabled);
if (w->geometry() != before) {
++moved;
}
}
if (moved > 0) {
cf::log::infoftag("WindowPlacement",
"re-constrained {} window(s) into screen work area {}", moved, work);
}
};
QObject::connect(panel_mgr, &PanelManager::availableGeometryChanged, this,
[reconstrain_windows](const QRect&) { reconstrain_windows(); });
QObject::connect(desktop_entity_, &CFDesktop::geometryChanged, this, reconstrain_windows);

// ── Status bar: top-edge panel (clock + system icons) ──
auto* status_bar = new cf::desktop::desktop_component::StatusBar(desktop_entity_);
status_bar->setBackdropSource(shell);
panel_mgr->registerPanel(status_bar->GetWeak());
status_bar->show();

Expand All @@ -125,12 +179,52 @@ CFDesktopEntity::RunsSetupResult CFDesktopEntity::run_init(RunsSetupMethod m) {
}
}

// ── Window placement: constrain launched windows into the work area ──
// On each external window appearance, clamp it inside the central work area
// (between the status bar and the taskbar) so it neither overlaps a bar nor
// flies off-screen, mirroring a real desktop WM. The policy is stateless, so
// a temporary is fine here. Runtime toggle via config domain
// "window_management" / key "constrain_to_workarea" (default on), read per
// appearance so flipping the config needs no restart.
if (display_backend_) {
if (auto window_backend = display_backend_->windowBackend()) {
QObject::connect(
window_backend.Get(), &cf::desktop::IWindowBackend::window_came, this,
[panel_mgr, this](aex::WeakPtr<cf::desktop::IWindow> win) {
if (!win) {
cf::log::warningftag("WindowPlacement", "window_came with null window");
return;
}
auto* w = win.Get();
namespace cfg = cf::config;
const bool enabled =
cfg::ConfigStore::instance()
.domain("window_management")
.query<bool>(cfg::KeyView{.group = "window_management",
.key = "constrain_to_workarea"},
true);
const QRect work =
panel_mgr->availableGeometry().translated(desktop_entity_->pos());
const QRect cur = w->geometry();
const auto target =
cf::desktop::placement::WindowPlacementPolicy{}.computeConstrain(cur, work,
enabled);
if (target.has_value()) {
w->set_geometry(*target);
cf::log::infoftag("WindowPlacement", "constrained '{}' {} -> {}",
w->title().toStdString(), cur, *target);
}
});
}
}

// ── Taskbar: bottom-edge panel (centered app icons) ──
// apps is captured by the click handler to resolve app_id -> exec_command.
const QList<cf::desktop::desktop_component::AppEntry> apps =
cf::desktop::desktop_component::defaultApps();
auto* taskbar = new cf::desktop::desktop_component::CenteredTaskbar(desktop_entity_);
taskbar->setApps(apps);
taskbar->setBackdropSource(shell);
panel_mgr->registerPanel(taskbar->GetWeak());
// Shared launch path: resolve app_id -> exec, launch, capture PID. Used by
// both the taskbar tile click and the launcher popup so the running-state
Expand Down
1 change: 1 addition & 0 deletions desktop/ui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ PRIVATE
cf_desktop_ui_widget # Link widget submodule
cf_desktop_ui_platform # Link platform submodule
cf_desktop_render # Link render backend abstraction
cfconfig # ConfigStore: window_management.constrain_to_workarea toggle
QuarkWidgets::quarkwidgets # For qt_format.h (std::formatter<QString>)
)

3 changes: 3 additions & 0 deletions desktop/ui/components/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ log_info("Dekstop UI" "Start UI Components Configurations")

add_subdirectory(wallpaper)
add_subdirectory(shell_layer_impl)
add_subdirectory(frosted_backdrop)
add_subdirectory(window_placement)
add_subdirectory(statusbar)
add_subdirectory(taskbar)
add_subdirectory(launcher)
Expand Down Expand Up @@ -36,5 +38,6 @@ PRIVATE
cfdesktop_statusbar
cfdesktop_taskbar
cfdesktop_launcher
cfdesktop_window_placement
Qt6::Core Qt6::Gui Qt6::Widgets
)
12 changes: 12 additions & 0 deletions desktop/ui/components/IShellLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#pragma once

#include "IShellLayerStrategy.h"
#include <QImage>
#include <QRect>
#include <memory>

Expand Down Expand Up @@ -70,6 +71,17 @@ class IShellLayer {
* @param[in] available The new available geometry rectangle.
*/
virtual void onAvailableGeometryChanged(const QRect& available) = 0;

/**
* @brief Returns the current background image, if any.
*
* The default returns a null QImage. Implementations backed by a wallpaper
* strategy delegate to it. Shell panels use this to composite a frosted
* copy of the backdrop (e.g. the status bar and taskbar acrylic surfaces).
*
* @return Current background image, or a null QImage when none is available.
*/
virtual QImage currentBackgroundImage() const { return {}; }
};

} // namespace cf::desktop
16 changes: 9 additions & 7 deletions desktop/ui/components/app_entry.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace cf::desktop::desktop_component {
struct AppEntry {
QString app_id; ///< Stable unique identifier (e.g. "terminal").
QString display_name; ///< Human-readable label (initial is drawn on the tile).
QString icon_path; ///< Optional icon resource path (empty -> use initial).
QString icon_path; ///< Icon resource path; empty leaves the tile blank (no fallback).
QString exec_command; ///< Launch command consumed later by QProcess.
bool is_running{false}; ///< Whether the app currently has a live window.
};
Expand All @@ -49,13 +49,15 @@ struct AppEntry {
*/
inline QList<AppEntry> defaultApps() {
return {
{QStringLiteral("files"), QStringLiteral("Files"), QString{}, QStringLiteral("xdg-open ."),
{QStringLiteral("files"), QStringLiteral("Files"),
QStringLiteral(":/cfdesktop/taskbar/files.png"), QStringLiteral("xdg-open ."), false},
{QStringLiteral("terminal"), QStringLiteral("Terminal"),
QStringLiteral(":/cfdesktop/taskbar/terminal.png"), QStringLiteral("xterm"), false},
{QStringLiteral("settings"), QStringLiteral("Settings"),
QStringLiteral(":/cfdesktop/taskbar/settings.png"), QStringLiteral("cfdesktop-settings"),
false},
{QStringLiteral("terminal"), QStringLiteral("Terminal"), QString{}, QStringLiteral("xterm"),
false},
{QStringLiteral("settings"), QStringLiteral("Settings"), QString{},
QStringLiteral("cfdesktop-settings"), false},
{QStringLiteral("browser"), QStringLiteral("Browser"), QString{},
{QStringLiteral("browser"), QStringLiteral("Browser"),
QStringLiteral(":/cfdesktop/taskbar/browser.png"),
QStringLiteral("xdg-open https://example.com"), false},
};
}
Expand Down
13 changes: 13 additions & 0 deletions desktop/ui/components/frosted_backdrop/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Frosted-glass (acrylic) backdrop renderer for shell panels.
# Pure Qt Gui utility: crop -> box blur -> tint -> acrylic grain. Depends on
# nothing in desktop/ (three-layer clean) so it stays unit-testable in isolation.
add_library(cfdesktop_frosted_backdrop STATIC
frosted_backdrop.cpp
)

target_include_directories(cfdesktop_frosted_backdrop PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>
)

target_link_libraries(cfdesktop_frosted_backdrop PUBLIC Qt6::Gui)
Loading
Loading