plasma-virtual-desktop-swit.../contents/ui/DesktopLogic.js

258 lines
8.6 KiB
JavaScript
Raw Permalink Normal View History

// DesktopLogic.js - Pure business logic (testable with Node.js)
// All functions are pure - no side effects, same input = same output
.pragma library
// ============================================================================
// COMMAND BUILDERS - Generate shell commands for KWin D-Bus operations
// ============================================================================
/**
* Build command to remove a desktop
* @param {string} desktopId - The desktop UUID
* @returns {string|null} - Command string or null if invalid
*/
function buildRemoveCommand(desktopId) {
if (!desktopId || typeof desktopId !== 'string' || desktopId.trim() === '') {
return null
}
return "qdbus org.kde.KWin /VirtualDesktopManager removeDesktop '" + desktopId + "'"
}
/**
* Build command to rename a desktop
* @param {string} desktopId - The desktop UUID
* @param {string} newName - The new name
* @returns {string|null} - Command string or null if invalid
*/
function buildRenameCommand(desktopId, newName) {
if (!desktopId || typeof desktopId !== 'string' || desktopId.trim() === '') {
return null
}
if (!newName || typeof newName !== 'string' || newName.trim() === '') {
return null
}
var escaped = escapeShell(newName.trim())
return "qdbus org.kde.KWin /VirtualDesktopManager setDesktopName '" + desktopId + "' '" + escaped + "'"
}
/**
* Build command to create a new desktop
* @param {number} position - Position index for new desktop
* @param {string} name - Name for new desktop
* @returns {string} - Command string
*/
function buildCreateCommand(position, name) {
var pos = Math.max(0, Math.floor(position))
var escaped = escapeShell(name ? name.trim() : 'Desktop')
return "qdbus org.kde.KWin /VirtualDesktopManager createDesktop " + pos + " '" + escaped + "'"
}
/**
* Build bash command to swap windows between two desktops using wmctrl
* @param {number} indexA - First desktop index (0-based)
* @param {number} indexB - Second desktop index (0-based)
* @returns {string} - Bash command string
*/
function buildSwapWindowsCommand(indexA, indexB) {
var a = Math.floor(indexA)
var b = Math.floor(indexB)
return "bash -c 'wins_a=$(wmctrl -l | awk \"\\$2==" + a + " {print \\$1}\"); " +
"wins_b=$(wmctrl -l | awk \"\\$2==" + b + " {print \\$1}\"); " +
"for w in $wins_a; do wmctrl -i -r $w -t " + b + "; done; " +
"for w in $wins_b; do wmctrl -i -r $w -t " + a + "; done'"
}
// ============================================================================
// STRING UTILITIES
// ============================================================================
/**
* Escape string for safe use in shell single quotes
* @param {string} str - Input string
* @returns {string} - Escaped string
*/
function escapeShell(str) {
if (!str || typeof str !== 'string') return ''
return str.replace(/'/g, "'\\''")
}
// ============================================================================
// GRID CALCULATIONS - Pure math functions for layout
// ============================================================================
/**
* Calculate optimal grid dimensions for N items
* Uses square root to create balanced grid
* @param {number} count - Number of items
* @returns {{cols: number, rows: number}} - Grid dimensions
*/
function calculateGrid(count) {
var n = Math.max(1, Math.floor(count))
var cols = Math.max(1, Math.ceil(Math.sqrt(n)))
var rows = Math.max(1, Math.ceil(n / cols))
return { cols: cols, rows: rows }
}
/**
* Calculate preview height maintaining aspect ratio
* @param {number} width - Preview width
* @param {number} screenWidth - Screen width
* @param {number} screenHeight - Screen height
* @returns {number} - Calculated height
*/
function calculatePreviewHeight(width, screenWidth, screenHeight) {
if (!screenWidth || screenWidth <= 0 || !screenHeight || screenHeight <= 0) {
return width * 9 / 16 // Default 16:9
}
return width * screenHeight / screenWidth
}
/**
* Calculate scale factors for positioning windows in preview
* @param {number} previewWidth - Preview width
* @param {number} previewHeight - Preview height
* @param {number} screenWidth - Screen width
* @param {number} screenHeight - Screen height
* @returns {{x: number, y: number}} - Scale factors
*/
function calculateScale(previewWidth, previewHeight, screenWidth, screenHeight) {
return {
x: previewWidth / Math.max(1, screenWidth),
y: previewHeight / Math.max(1, screenHeight)
}
}
// ============================================================================
// NAVIGATION - Desktop switching logic
// ============================================================================
/**
* Calculate next desktop index with wrap-around
* @param {number} current - Current desktop index
* @param {number} count - Total number of desktops
* @param {number} direction - Direction: positive = forward, negative = backward
* @returns {number} - Next desktop index
*/
function nextDesktop(current, count, direction) {
if (count <= 0) return 0
var c = Math.max(0, Math.floor(current))
var n = Math.max(1, Math.floor(count))
if (direction > 0) {
return (c + 1) % n
} else {
return (c - 1 + n) % n
}
}
// ============================================================================
// DRAG AND DROP - Validation and detection
// ============================================================================
/**
* Check if swap operation is valid
* @param {number} indexA - Source index
* @param {number} indexB - Target index
* @param {number} count - Total count
* @returns {boolean} - True if swap is valid
*/
function canSwap(indexA, indexB, count) {
if (typeof indexA !== 'number' || typeof indexB !== 'number' || typeof count !== 'number') {
return false
}
return indexA >= 0 && indexB >= 0 &&
indexA < count && indexB < count &&
indexA !== indexB
}
/**
* Find drop target index from mouse position
* This function needs QML objects, returns -1 for pure JS testing
* @param {object} repeater - QML Repeater
* @param {object} grid - QML Grid
* @param {number} mouseX - Mouse X in grid coordinates
* @param {number} mouseY - Mouse Y in grid coordinates
* @returns {number} - Target index or -1
*/
function findDropTarget(repeater, grid, mouseX, mouseY) {
if (!repeater || !grid || typeof repeater.count !== 'number') {
return -1
}
for (var i = 0; i < repeater.count; i++) {
var item = repeater.itemAt(i)
if (!item) continue
var itemPos = grid.mapFromItem(item, 0, 0)
if (mouseX >= itemPos.x && mouseX < itemPos.x + item.width &&
mouseY >= itemPos.y && mouseY < itemPos.y + item.height) {
return i
}
}
return -1
}
/**
* Calculate distance between two points
* @param {number} x1 - First point X
* @param {number} y1 - First point Y
* @param {number} x2 - Second point X
* @param {number} y2 - Second point Y
* @returns {number} - Distance
*/
function distance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
}
/**
* Check if drag threshold is exceeded
* @param {number} startX - Start X
* @param {number} startY - Start Y
* @param {number} currentX - Current X
* @param {number} currentY - Current Y
* @param {number} threshold - Drag threshold (default 10)
* @returns {boolean} - True if threshold exceeded
*/
function isDragStarted(startX, startY, currentX, currentY, threshold) {
var t = threshold || 10
return distance(startX, startY, currentX, currentY) > t
}
// ============================================================================
// VALIDATION - Input validation helpers
// ============================================================================
/**
* Validate desktop index
* @param {number} index - Index to validate
* @param {number} count - Total count
* @returns {boolean} - True if valid
*/
function isValidIndex(index, count) {
return typeof index === 'number' &&
typeof count === 'number' &&
index >= 0 &&
index < count &&
Number.isInteger(index)
}
/**
* Validate desktop name
* @param {string} name - Name to validate
* @returns {boolean} - True if valid
*/
function isValidName(name) {
return typeof name === 'string' && name.trim().length > 0
}
/**
* Clamp value between min and max
* @param {number} value - Value to clamp
* @param {number} min - Minimum
* @param {number} max - Maximum
* @returns {number} - Clamped value
*/
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value))
}