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
1 change: 1 addition & 0 deletions src/modules/fancyzones/FancyZonesLib/FancyZones.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa
else if (message == WM_PRIV_CUSTOM_LAYOUTS_FILE_UPDATE)
{
CustomLayouts::instance().LoadData();
RefreshLayouts();
}
else if (message == WM_PRIV_APPLIED_LAYOUTS_FILE_UPDATE)
{
Expand Down
18 changes: 17 additions & 1 deletion src/modules/fancyzones/FancyZonesLib/WorkArea.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "FancyZonesData/AppliedLayouts.h"
#include "FancyZonesData/AppZoneHistory.h"
#include "FancyZonesData/CustomLayouts.h"
#include "ZonesOverlay.h"
#include "Settings.h"
#include <FancyZonesLib/FancyZonesWindowProperties.h>
Expand Down Expand Up @@ -303,13 +304,28 @@ void WorkArea::InitSnappedWindows()

void WorkArea::CalculateZoneSet()
{
const auto appliedLayout = AppliedLayouts::instance().GetDeviceLayout(m_uniqueId);
auto appliedLayout = AppliedLayouts::instance().GetDeviceLayout(m_uniqueId);
if (!appliedLayout.has_value())
{
Logger::error(L"Layout wasn't applied. Can't init layout on work area {}x{}", m_workAreaRect.width(), m_workAreaRect.height());
return;
}

// For custom layouts the spacing, sensitivity radius and zone count live in the custom
// layout definition (custom-layouts.json), while the applied-layouts.json snapshot keeps a
// copy taken at apply time. Editing those properties only rewrites custom-layouts.json, so
// without this sync the snapshot stays stale and the edits don't take effect until the layout
// is re-applied (see GH #44058). Re-derive the scalar properties from the current custom
// layout using the same logic the apply path uses (CustomLayouts::GetLayout also derives the
// canvas zone count from the zone list, keeping it consistent with Layout::Init validation).
if (appliedLayout->type == FancyZonesDataTypes::ZoneSetLayoutType::Custom)
{
if (const auto refreshed = CustomLayouts::instance().GetLayout(appliedLayout->uuid))
{
appliedLayout = refreshed;
}
}

m_layout = std::make_unique<Layout>(appliedLayout.value());
m_layout->Init(m_workAreaRect, m_uniqueId.monitorId.monitor);
}
Expand Down
98 changes: 98 additions & 0 deletions src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#include "pch.h"

#include <algorithm>
#include <filesystem>
#include <vector>

#include <FancyZonesLib/WorkArea.h>
#include <FancyZonesLib/FancyZonesData/AppliedLayouts.h>
#include <FancyZonesLib/FancyZonesData/AppZoneHistory.h>
#include <FancyZonesLib/FancyZonesData/CustomLayouts.h>
#include <FancyZonesLib/FancyZonesData/DefaultLayouts.h>
#include <FancyZonesLib/FancyZonesWindowProperties.h>
#include <FancyZonesLib/LayoutAssignedWindows.h>
Expand Down Expand Up @@ -37,13 +40,15 @@ namespace FancyZonesUnitTests

AppZoneHistory::instance().LoadData();
AppliedLayouts::instance().LoadData();
CustomLayouts::instance().LoadData();
DefaultLayouts::instance().LoadData();
}

TEST_METHOD_CLEANUP(CleanUp) noexcept
{
std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName());
std::filesystem::remove(CustomLayouts::CustomLayoutsFileName());
std::filesystem::remove(DefaultLayouts::DefaultLayoutsFileName());
}

Expand Down Expand Up @@ -173,6 +178,99 @@ namespace FancyZonesUnitTests
Assert::AreEqual(static_cast<size_t>(4), actualLayout->Zones().size());
Assert::IsTrue(GUID_NULL == actualLayout->Id());
}

// Saves a 1x2 custom grid layout to custom-layouts.json so it can be resolved by uuid.
void SaveCustomGridLayout(const GUID& uuid, bool showSpacing, int spacing, int sensitivityRadius)
{
json::JsonObject root{};
json::JsonArray layoutsArray{};

json::JsonObject gridLayoutJson{};
gridLayoutJson.SetNamedValue(NonLocalizable::CustomLayoutsIds::UuidID, json::value(FancyZonesUtils::GuidToString(uuid).value()));
gridLayoutJson.SetNamedValue(NonLocalizable::CustomLayoutsIds::NameID, json::value(L"Custom grid layout"));
gridLayoutJson.SetNamedValue(NonLocalizable::CustomLayoutsIds::TypeID, json::value(NonLocalizable::CustomLayoutsIds::GridID));

json::JsonArray rowsPercentage{};
rowsPercentage.Append(json::value(10000));

json::JsonArray columnsPercentage{};
columnsPercentage.Append(json::value(5000));
columnsPercentage.Append(json::value(5000));

json::JsonArray cells{};
{
json::JsonArray cellsRow{};
cellsRow.Append(json::value(0));
cellsRow.Append(json::value(1));
cells.Append(cellsRow);
}

json::JsonObject info{};
info.SetNamedValue(NonLocalizable::CustomLayoutsIds::RowsID, json::value(1));
info.SetNamedValue(NonLocalizable::CustomLayoutsIds::ColumnsID, json::value(2));
info.SetNamedValue(NonLocalizable::CustomLayoutsIds::RowsPercentageID, rowsPercentage);
info.SetNamedValue(NonLocalizable::CustomLayoutsIds::ColumnsPercentageID, columnsPercentage);
info.SetNamedValue(NonLocalizable::CustomLayoutsIds::CellChildMapID, cells);
info.SetNamedValue(NonLocalizable::CustomLayoutsIds::ShowSpacingID, json::value(showSpacing));
info.SetNamedValue(NonLocalizable::CustomLayoutsIds::SpacingID, json::value(spacing));
info.SetNamedValue(NonLocalizable::CustomLayoutsIds::SensitivityRadiusID, json::value(sensitivityRadius));

gridLayoutJson.SetNamedValue(NonLocalizable::CustomLayoutsIds::InfoID, info);
layoutsArray.Append(gridLayoutJson);
root.SetNamedValue(NonLocalizable::CustomLayoutsIds::CustomLayoutsArrayID, layoutsArray);

json::to_file(CustomLayouts::CustomLayoutsFileName(), root);
CustomLayouts::instance().LoadData();
}

// Regression test for GH #44058: editing a custom layout's spacing only rewrites
// custom-layouts.json, while applied-layouts.json keeps a snapshot taken at apply time.
// An already-created WorkArea must pick up the edited spacing when it is refreshed
// (WorkArea::InitLayout, which is what the custom-layouts file-update handler calls via
// RefreshLayouts). Without re-deriving the scalar properties from the current custom
// layout the stale snapshot is used and the edit has no effect until re-apply.
TEST_METHOD (EditedCustomLayoutSpacingRefreshesExistingWorkArea)
{
const auto uuid = FancyZonesUtils::GuidFromString(L"{5A9D6A0F-4C6E-4C0C-8B1B-9F3E7C2D1A11}").value();

auto zoneGap = [](const auto& zones) -> LONG {
std::vector<RECT> rects;
for (const auto& [id, zone] : zones)
{
rects.push_back(zone.GetZoneRect());
}
std::sort(rects.begin(), rects.end(), [](const RECT& a, const RECT& b) { return a.left < b.left; });
return rects[1].left - rects[0].right;
};

// The layout and its applied snapshot initially have no spacing.
SaveCustomGridLayout(uuid, /*showSpacing*/ false, /*spacing*/ 0, /*sensitivityRadius*/ 5);
LayoutData snapshot{
.uuid = uuid,
.type = FancyZonesDataTypes::ZoneSetLayoutType::Custom,
.showSpacing = false,
.spacing = 0,
.zoneCount = 2,
.sensitivityRadius = 5,
};
AppliedLayouts::instance().ApplyLayout(m_workAreaId, snapshot);

auto workArea = WorkArea::Create({}, m_workAreaId, m_emptyUniqueId, m_workAreaRect);
Assert::IsFalse(workArea == nullptr);
Assert::IsNotNull(workArea->GetLayout().get());
Assert::AreEqual(static_cast<size_t>(2), workArea->GetLayout()->Zones().size());
Assert::AreEqual(0L, zoneGap(workArea->GetLayout()->Zones()), L"Zones should be adjacent before the edit");

// User edits the layout to add spacing; only custom-layouts.json is rewritten, the
// applied-layouts snapshot stays stale.
SaveCustomGridLayout(uuid, /*showSpacing*/ true, /*spacing*/ 100, /*sensitivityRadius*/ 30);

// The custom-layouts file-update handler refreshes active work areas via InitLayout.
workArea->InitLayout();

Assert::AreEqual(static_cast<size_t>(2), workArea->GetLayout()->Zones().size());
Assert::IsTrue(zoneGap(workArea->GetLayout()->Zones()) > 20, L"Edited spacing was not applied to the existing work area");
}
};

TEST_CLASS (WorkAreaSnapUnitTests)
Expand Down