#include #include #include #include #include #include #include #include #include #include #include static HANDLE g_pluginHandle = nullptr; namespace Layout::Tiled { class CColumnsAlgorithm final : public ITiledAlgorithm { public: void newTarget(SP target) override { addTarget(std::move(target)); recalculate(); } void movedTarget(SP target, std::optional /*focalPoint*/ = std::nullopt) override { addTarget(std::move(target)); recalculate(); } void removeTarget(SP target) override { cleanup(); std::erase_if(m_targets, [&](const WP& weak) { const auto locked = weak.lock(); return !locked || locked == target; }); recalculate(); } void resizeTarget(const Vector2D& /*delta*/, SP /*target*/, eRectCorner /*corner*/ = CORNER_NONE) override { // Intentionally ignored: this layout always keeps equal-width columns. } void recalculate(eRecalculateReason /*reason*/ = RECALCULATE_REASON_UNKNOWN) override { cleanup(); const auto parent = m_parent.lock(); if (!parent) return; const auto space = parent->space(); if (!space) return; std::vector> tiledTargets; tiledTargets.reserve(m_targets.size()); for (auto const& weak : m_targets) { const auto target = weak.lock(); if (target) tiledTargets.push_back(target); } if (tiledTargets.empty()) return; const auto area = space->workArea(false); const auto count = tiledTargets.size(); for (size_t i = 0; i < count; ++i) { const double left = area.x + (area.w * static_cast(i) / static_cast(count)); const double right = area.x + (area.w * static_cast(i + 1) / static_cast(count)); const CBox box(left, area.y, right - left, area.h); tiledTargets[i]->setPositionGlobal(box); tiledTargets[i]->recalc(); } } SP getNextCandidate(SP old) override { cleanup(); auto targets = liveTargets(); if (targets.empty()) return nullptr; if (!old) return targets.front(); const auto it = std::ranges::find(targets, old); if (it == targets.end()) return targets.front(); const auto idx = static_cast(std::distance(targets.begin(), it)); return targets[(idx + 1) % targets.size()]; } Config::ErrorResult layoutMsg(const std::string_view& sv) override { if (sv == "togglesplit" || sv == "swapsplit") return {}; if (sv == "recalculate") { recalculate(); return {}; } return Config::configError(std::string{"columns: unsupported layout message: "} + std::string{sv}, Config::eConfigErrorLevel::ERROR, Config::eConfigErrorCode::INVALID_ARGUMENT); } std::optional predictSizeForNewTarget() override { const auto parent = m_parent.lock(); if (!parent) return std::nullopt; const auto space = parent->space(); if (!space) return std::nullopt; const auto area = space->workArea(false); const auto n = std::max(1, liveTargets().size() + 1); return Vector2D(area.w / static_cast(n), area.h); } void swapTargets(SP a, SP b) override { cleanup(); auto ia = indexOf(a); auto ib = indexOf(b); if (!ia.has_value() || !ib.has_value() || ia == ib) return; std::swap(m_targets[*ia], m_targets[*ib]); recalculate(); } void moveTargetInDirection(SP t, Math::eDirection dir, bool /*silent*/) override { cleanup(); const auto idx = indexOf(t); if (!idx.has_value()) return; if (dir == Math::DIRECTION_LEFT && *idx > 0) { std::swap(m_targets[*idx], m_targets[*idx - 1]); recalculate(); return; } if (dir == Math::DIRECTION_RIGHT && *idx + 1 < m_targets.size()) { std::swap(m_targets[*idx], m_targets[*idx + 1]); recalculate(); return; } } private: std::vector> m_targets; void addTarget(SP target) { cleanup(); const auto exists = std::ranges::any_of(m_targets, [&](const WP& weak) { const auto locked = weak.lock(); return locked && locked == target; }); if (!exists) m_targets.emplace_back(target); } void cleanup() { std::erase_if(m_targets, [](const WP& weak) { return weak.expired(); }); } std::vector> liveTargets() const { std::vector> out; out.reserve(m_targets.size()); for (auto const& weak : m_targets) { const auto locked = weak.lock(); if (locked) out.push_back(locked); } return out; } std::optional indexOf(SP target) const { for (size_t i = 0; i < m_targets.size(); ++i) { const auto locked = m_targets[i].lock(); if (locked && locked == target) return i; } return std::nullopt; } }; } APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; } APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { g_pluginHandle = handle; const bool ok = HyprlandAPI::addTiledAlgo(handle, "columns", &typeid(Layout::Tiled::CColumnsAlgorithm), []() -> UP { return makeUnique(); }); if (!ok) HyprlandAPI::addNotification(handle, "hyprcolumns: failed to register columns layout", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); else HyprlandAPI::addNotification(handle, "hyprcolumns: registered columns layout", CHyprColor{0.2, 1.0, 0.6, 1.0}, 3000); return {.name = "hyprcolumns", .description = "Equal-width tiled columns layout", .author = "OpenAI", .version = "0.1.0"}; } APICALL EXPORT void PLUGIN_EXIT() { if (g_pluginHandle) HyprlandAPI::removeAlgo(g_pluginHandle, "columns"); }