5.1 窗口系统概述

Sciter窗口架构

Sciter提供了强大的窗口管理系统,支持创建和管理多种类型的窗口。每个Sciter窗口都是一个独立的渲染上下文,可以包含完整的HTML/CSS/TIScript内容。

窗口类型

  • 主窗口:应用程序的主要界面窗口
  • 对话框:模态或非模态的对话框窗口
  • 弹出窗口:临时显示的弹出式窗口
  • 工具窗口:辅助工具和面板窗口
  • 子窗口:嵌入在其他窗口中的子窗口

窗口特性

  • 独立渲染:每个窗口有独立的DOM和渲染上下文
  • 事件隔离:窗口间事件相互独立
  • 资源共享:可以共享样式表和脚本资源
  • 通信机制:支持窗口间消息传递
  • 生命周期管理:完整的窗口创建、显示、隐藏、销毁流程

5.2 窗口创建与管理

基本窗口操作

1. 创建窗口

// 窗口管理器类
class WindowManager {
    function this() {
        this.windows = new Map();
        this.windowCounter = 0;
    }
    
    // 创建新窗口
    function createWindow(options = {}) {
        var windowId = "window_" + (++this.windowCounter);
        
        var defaultOptions = {
            url: "about:blank",
            width: 800,
            height: 600,
            x: undefined,
            y: undefined,
            resizable: true,
            minimizable: true,
            maximizable: true,
            closable: true,
            modal: false,
            topmost: false,
            title: "Sciter Window",
            icon: undefined,
            parent: undefined
        };
        
        var config = Object.assign(defaultOptions, options);
        
        try {
            // 使用Sciter API创建窗口
            var window = view.window(config.url, {
                x: config.x,
                y: config.y,
                width: config.width,
                height: config.height,
                resizable: config.resizable,
                minimizable: config.minimizable,
                maximizable: config.maximizable,
                closable: config.closable,
                modal: config.modal,
                topmost: config.topmost,
                title: config.title,
                icon: config.icon,
                parent: config.parent
            });
            
            // 存储窗口引用
            this.windows.set(windowId, {
                id: windowId,
                window: window,
                config: config,
                created: Date.now(),
                state: "created"
            });
            
            // 设置窗口事件处理
            this.setupWindowEvents(windowId, window);
            
            stdout.println(`窗口创建成功: ${windowId}`);
            return windowId;
            
        } catch (error) {
            stderr.println(`窗口创建失败: ${error.message}`);
            return null;
        }
    }
    
    // 设置窗口事件
    function setupWindowEvents(windowId, window) {
        var self = this;
        
        // 窗口关闭事件
        window.on("close", function() {
            self.handleWindowClose(windowId);
        });
        
        // 窗口激活事件
        window.on("activate", function() {
            self.handleWindowActivate(windowId);
        });
        
        // 窗口大小改变事件
        window.on("size", function(width, height) {
            self.handleWindowResize(windowId, width, height);
        });
        
        // 窗口移动事件
        window.on("move", function(x, y) {
            self.handleWindowMove(windowId, x, y);
        });
    }
    
    // 显示窗口
    function showWindow(windowId, state = "normal") {
        var windowInfo = this.windows.get(windowId);
        if (!windowInfo) {
            stderr.println(`窗口不存在: ${windowId}`);
            return false;
        }
        
        try {
            switch (state) {
                case "normal":
                    windowInfo.window.show();
                    break;
                case "minimized":
                    windowInfo.window.minimize();
                    break;
                case "maximized":
                    windowInfo.window.maximize();
                    break;
                case "fullscreen":
                    windowInfo.window.fullscreen();
                    break;
            }
            
            windowInfo.state = state;
            stdout.println(`窗口显示: ${windowId} (${state})`);
            return true;
            
        } catch (error) {
            stderr.println(`窗口显示失败: ${error.message}`);
            return false;
        }
    }
    
    // 隐藏窗口
    function hideWindow(windowId) {
        var windowInfo = this.windows.get(windowId);
        if (!windowInfo) return false;
        
        try {
            windowInfo.window.hide();
            windowInfo.state = "hidden";
            stdout.println(`窗口隐藏: ${windowId}`);
            return true;
        } catch (error) {
            stderr.println(`窗口隐藏失败: ${error.message}`);
            return false;
        }
    }
    
    // 关闭窗口
    function closeWindow(windowId) {
        var windowInfo = this.windows.get(windowId);
        if (!windowInfo) return false;
        
        try {
            windowInfo.window.close();
            this.windows.delete(windowId);
            stdout.println(`窗口关闭: ${windowId}`);
            return true;
        } catch (error) {
            stderr.println(`窗口关闭失败: ${error.message}`);
            return false;
        }
    }
    
    // 获取窗口信息
    function getWindowInfo(windowId) {
        return this.windows.get(windowId);
    }
    
    // 获取所有窗口
    function getAllWindows() {
        return Array.from(this.windows.values());
    }
    
    // 查找窗口
    function findWindow(predicate) {
        for (var windowInfo of this.windows.values()) {
            if (predicate(windowInfo)) {
                return windowInfo;
            }
        }
        return null;
    }
    
    // 窗口事件处理器
    function handleWindowClose(windowId) {
        stdout.println(`窗口关闭事件: ${windowId}`);
        this.windows.delete(windowId);
        
        // 如果是最后一个窗口,退出应用
        if (this.windows.size === 0) {
            view.quit();
        }
    }
    
    function handleWindowActivate(windowId) {
        stdout.println(`窗口激活: ${windowId}`);
        var windowInfo = this.windows.get(windowId);
        if (windowInfo) {
            windowInfo.lastActivated = Date.now();
        }
    }
    
    function handleWindowResize(windowId, width, height) {
        stdout.println(`窗口大小改变: ${windowId} (${width}x${height})`);
        var windowInfo = this.windows.get(windowId);
        if (windowInfo) {
            windowInfo.config.width = width;
            windowInfo.config.height = height;
        }
    }
    
    function handleWindowMove(windowId, x, y) {
        stdout.println(`窗口移动: ${windowId} (${x}, ${y})`);
        var windowInfo = this.windows.get(windowId);
        if (windowInfo) {
            windowInfo.config.x = x;
            windowInfo.config.y = y;
        }
    }
}

// 创建全局窗口管理器
var windowManager = new WindowManager();

// 使用示例
function createMainWindow() {
    var mainWindowId = windowManager.createWindow({
        url: "main.html",
        title: "主窗口",
        width: 1200,
        height: 800,
        x: 100,
        y: 100
    });
    
    windowManager.showWindow(mainWindowId);
    return mainWindowId;
}

function createSettingsDialog() {
    var dialogId = windowManager.createWindow({
        url: "settings.html",
        title: "设置",
        width: 600,
        height: 400,
        modal: true,
        resizable: false,
        parent: windowManager.findWindow(w => w.config.title === "主窗口")
    });
    
    windowManager.showWindow(dialogId);
    return dialogId;
}

2. 窗口通信

// 窗口间通信管理器
class WindowCommunication {
    function this() {
        this.messageHandlers = new Map();
        this.setupGlobalMessageHandler();
    }
    
    // 设置全局消息处理
    function setupGlobalMessageHandler() {
        var self = this;
        
        // 监听窗口消息
        view.on("message", function(evt) {
            self.handleMessage(evt);
        });
    }
    
    // 发送消息到指定窗口
    function sendMessage(targetWindowId, messageType, data) {
        var targetWindow = windowManager.getWindowInfo(targetWindowId);
        if (!targetWindow) {
            stderr.println(`目标窗口不存在: ${targetWindowId}`);
            return false;
        }
        
        var message = {
            type: messageType,
            data: data,
            sender: view.windowId || "unknown",
            timestamp: Date.now()
        };
        
        try {
            targetWindow.window.postMessage(message);
            stdout.println(`消息发送成功: ${messageType} -> ${targetWindowId}`);
            return true;
        } catch (error) {
            stderr.println(`消息发送失败: ${error.message}`);
            return false;
        }
    }
    
    // 广播消息到所有窗口
    function broadcastMessage(messageType, data, excludeSelf = true) {
        var currentWindowId = view.windowId;
        var sentCount = 0;
        
        for (var windowInfo of windowManager.getAllWindows()) {
            if (excludeSelf && windowInfo.id === currentWindowId) {
                continue;
            }
            
            if (this.sendMessage(windowInfo.id, messageType, data)) {
                sentCount++;
            }
        }
        
        stdout.println(`广播消息: ${messageType} (发送到 ${sentCount} 个窗口)`);
        return sentCount;
    }
    
    // 注册消息处理器
    function registerHandler(messageType, handler) {
        if (!this.messageHandlers.has(messageType)) {
            this.messageHandlers.set(messageType, []);
        }
        
        this.messageHandlers.get(messageType).push(handler);
        stdout.println(`注册消息处理器: ${messageType}`);
    }
    
    // 取消注册消息处理器
    function unregisterHandler(messageType, handler) {
        if (!this.messageHandlers.has(messageType)) return;
        
        var handlers = this.messageHandlers.get(messageType);
        var index = handlers.indexOf(handler);
        if (index >= 0) {
            handlers.splice(index, 1);
            stdout.println(`取消注册消息处理器: ${messageType}`);
        }
    }
    
    // 处理接收到的消息
    function handleMessage(evt) {
        var message = evt.data;
        if (!message || !message.type) return;
        
        stdout.println(`接收消息: ${message.type} from ${message.sender}`);
        
        var handlers = this.messageHandlers.get(message.type);
        if (handlers) {
            for (var handler of handlers) {
                try {
                    handler(message.data, message);
                } catch (error) {
                    stderr.println(`消息处理器错误: ${error.message}`);
                }
            }
        }
    }
    
    // 请求-响应模式
    function sendRequest(targetWindowId, requestType, data, timeout = 5000) {
        return new Promise(function(resolve, reject) {
            var requestId = "req_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
            
            // 注册响应处理器
            var responseHandler = function(responseData, message) {
                if (message.requestId === requestId) {
                    windowComm.unregisterHandler(requestType + "_response", responseHandler);
                    resolve(responseData);
                }
            };
            
            windowComm.registerHandler(requestType + "_response", responseHandler);
            
            // 设置超时
            setTimeout(function() {
                windowComm.unregisterHandler(requestType + "_response", responseHandler);
                reject(new Error("请求超时"));
            }, timeout);
            
            // 发送请求
            var requestData = {
                requestId: requestId,
                data: data
            };
            
            if (!windowComm.sendMessage(targetWindowId, requestType, requestData)) {
                reject(new Error("发送请求失败"));
            }
        });
    }
    
    // 响应请求
    function sendResponse(targetWindowId, requestType, requestId, responseData) {
        var response = {
            requestId: requestId,
            data: responseData
        };
        
        return this.sendMessage(targetWindowId, requestType + "_response", response);
    }
}

// 创建全局窗口通信管理器
var windowComm = new WindowCommunication();

// 使用示例
function setupWindowCommunication() {
    // 注册消息处理器
    windowComm.registerHandler("user-data-update", function(data) {
        stdout.println("用户数据更新: " + JSON.stringify(data));
        updateUserInterface(data);
    });
    
    windowComm.registerHandler("settings-changed", function(data) {
        stdout.println("设置改变: " + JSON.stringify(data));
        applySettings(data);
    });
    
    windowComm.registerHandler("get-user-info", function(data, message) {
        var userInfo = getCurrentUserInfo();
        windowComm.sendResponse(message.sender, "get-user-info", message.requestId, userInfo);
    });
}

// 发送消息示例
function notifyUserDataUpdate(userData) {
    windowComm.broadcastMessage("user-data-update", userData);
}

function requestUserInfo(targetWindowId) {
    windowComm.sendRequest(targetWindowId, "get-user-info", {})
        .then(function(userInfo) {
            stdout.println("获取用户信息成功: " + JSON.stringify(userInfo));
        })
        .catch(function(error) {
            stderr.println("获取用户信息失败: " + error.message);
        });
}

5.3 布局系统

CSS布局增强

1. Flow布局

/* Flow布局是Sciter特有的布局方式 */
.flow-container {
    flow: horizontal; /* 水平流式布局 */
    /* flow: vertical; 垂直流式布局 */
    /* flow: horizontal-wrap; 水平换行布局 */
    /* flow: vertical-wrap; 垂直换行布局 */
}

/* 水平布局示例 */
.horizontal-layout {
    flow: horizontal;
    width: 100%;
    height: 60px;
    border: 1px solid #ccc;
}

.horizontal-layout > .item {
    width: 100px;
    height: 50px;
    margin: 5px;
    background: #e0e0e0;
    text-align: center;
    line-height: 50px;
}

/* 垂直布局示例 */
.vertical-layout {
    flow: vertical;
    width: 200px;
    height: 100%;
    border: 1px solid #ccc;
}

.vertical-layout > .item {
    width: 180px;
    height: 40px;
    margin: 5px 10px;
    background: #f0f0f0;
    text-align: center;
    line-height: 40px;
}

/* 网格布局示例 */
.grid-layout {
    flow: horizontal-wrap;
    width: 100%;
    padding: 10px;
}

.grid-layout > .grid-item {
    width: 150px;
    height: 100px;
    margin: 5px;
    background: #ddd;
    border-radius: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* 弹性布局 */
.flex-container {
    display: flex;
    flex-direction: row; /* row, column, row-reverse, column-reverse */
    justify-content: space-between; /* flex-start, flex-end, center, space-between, space-around */
    align-items: center; /* flex-start, flex-end, center, stretch, baseline */
    flex-wrap: wrap; /* nowrap, wrap, wrap-reverse */
    gap: 10px;
}

.flex-item {
    flex: 1; /* flex-grow flex-shrink flex-basis */
    /* flex: 0 1 auto; 默认值 */
    /* flex: 1 1 0; 等分空间 */
    /* flex: 0 0 200px; 固定宽度 */
    min-width: 100px;
    padding: 10px;
    background: #f5f5f5;
    border-radius: 4px;
}

/* 响应式布局 */
@media screen and (max-width: 768px) {
    .responsive-container {
        flow: vertical;
    }
    
    .responsive-container > .item {
        width: 100%;
        margin: 5px 0;
    }
}

@media screen and (min-width: 769px) {
    .responsive-container {
        flow: horizontal;
    }
    
    .responsive-container > .item {
        flex: 1;
        margin: 0 5px;
    }
}

2. 高级布局组件

// 布局管理器
class LayoutManager {
    function this() {
        this.layouts = new Map();
        this.observers = new Map();
    }
    
    // 注册布局
    function registerLayout(name, layoutConfig) {
        this.layouts.set(name, layoutConfig);
        stdout.println(`注册布局: ${name}`);
    }
    
    // 应用布局
    function applyLayout(element, layoutName, options = {}) {
        var layout = this.layouts.get(layoutName);
        if (!layout) {
            stderr.println(`布局不存在: ${layoutName}`);
            return false;
        }
        
        try {
            layout.apply(element, options);
            
            // 设置响应式观察器
            if (layout.responsive) {
                this.setupResponsiveObserver(element, layout, options);
            }
            
            stdout.println(`应用布局成功: ${layoutName}`);
            return true;
        } catch (error) {
            stderr.println(`应用布局失败: ${error.message}`);
            return false;
        }
    }
    
    // 设置响应式观察器
    function setupResponsiveObserver(element, layout, options) {
        var self = this;
        var observer = new ResizeObserver(function(entries) {
            for (var entry of entries) {
                self.handleElementResize(entry.target, layout, options);
            }
        });
        
        observer.observe(element);
        this.observers.set(element, observer);
    }
    
    // 处理元素大小改变
    function handleElementResize(element, layout, options) {
        if (layout.onResize) {
            layout.onResize(element, options);
        }
    }
    
    // 移除布局观察器
    function removeObserver(element) {
        var observer = this.observers.get(element);
        if (observer) {
            observer.disconnect();
            this.observers.delete(element);
        }
    }
}

// 创建布局管理器
var layoutManager = new LayoutManager();

// 注册常用布局
function registerCommonLayouts() {
    // 卡片网格布局
    layoutManager.registerLayout("card-grid", {
        apply: function(element, options) {
            var cardWidth = options.cardWidth || 200;
            var gap = options.gap || 10;
            
            element.style.display = "grid";
            element.style.gridTemplateColumns = `repeat(auto-fill, minmax(${cardWidth}px, 1fr))`;
            element.style.gap = gap + "px";
            element.style.padding = gap + "px";
        },
        responsive: true,
        onResize: function(element, options) {
            // 响应式调整
            var width = element.clientWidth;
            var cardWidth = options.cardWidth || 200;
            var gap = options.gap || 10;
            
            if (width < 600) {
                element.style.gridTemplateColumns = "1fr";
            } else if (width < 900) {
                element.style.gridTemplateColumns = "repeat(2, 1fr)";
            } else {
                element.style.gridTemplateColumns = `repeat(auto-fill, minmax(${cardWidth}px, 1fr))`;
            }
        }
    });
    
    // 侧边栏布局
    layoutManager.registerLayout("sidebar", {
        apply: function(element, options) {
            var sidebarWidth = options.sidebarWidth || 250;
            var position = options.position || "left";
            
            element.style.display = "flex";
            element.style.height = "100%";
            
            var sidebar = element.$("[data-role='sidebar']");
            var content = element.$("[data-role='content']");
            
            if (sidebar && content) {
                sidebar.style.width = sidebarWidth + "px";
                sidebar.style.flexShrink = "0";
                content.style.flex = "1";
                content.style.overflow = "auto";
                
                if (position === "right") {
                    element.style.flexDirection = "row-reverse";
                } else {
                    element.style.flexDirection = "row";
                }
            }
        },
        responsive: true,
        onResize: function(element, options) {
            var width = element.clientWidth;
            var sidebar = element.$("[data-role='sidebar']");
            
            if (width < 768 && sidebar) {
                // 移动端隐藏侧边栏
                sidebar.style.display = "none";
            } else if (sidebar) {
                sidebar.style.display = "block";
            }
        }
    });
    
    // 头部-内容-底部布局
    layoutManager.registerLayout("header-content-footer", {
        apply: function(element, options) {
            var headerHeight = options.headerHeight || 60;
            var footerHeight = options.footerHeight || 40;
            
            element.style.display = "flex";
            element.style.flexDirection = "column";
            element.style.height = "100%";
            
            var header = element.$("[data-role='header']");
            var content = element.$("[data-role='content']");
            var footer = element.$("[data-role='footer']");
            
            if (header) {
                header.style.height = headerHeight + "px";
                header.style.flexShrink = "0";
            }
            
            if (content) {
                content.style.flex = "1";
                content.style.overflow = "auto";
            }
            
            if (footer) {
                footer.style.height = footerHeight + "px";
                footer.style.flexShrink = "0";
            }
        }
    });
    
    // 瀑布流布局
    layoutManager.registerLayout("masonry", {
        apply: function(element, options) {
            var columns = options.columns || 3;
            var gap = options.gap || 10;
            
            element.style.columnCount = columns;
            element.style.columnGap = gap + "px";
            element.style.columnFill = "balance";
            
            // 为子元素设置样式
            var items = element.$$("> *");
            items.forEach(function(item) {
                item.style.breakInside = "avoid";
                item.style.marginBottom = gap + "px";
            });
        },
        responsive: true,
        onResize: function(element, options) {
            var width = element.clientWidth;
            var columns;
            
            if (width < 600) {
                columns = 1;
            } else if (width < 900) {
                columns = 2;
            } else if (width < 1200) {
                columns = 3;
            } else {
                columns = options.columns || 4;
            }
            
            element.style.columnCount = columns;
        }
    });
}

// 注册布局
registerCommonLayouts();

// 使用示例
function setupPageLayout() {
    // 应用主布局
    var mainContainer = $("#main-container");
    layoutManager.applyLayout(mainContainer, "header-content-footer", {
        headerHeight: 80,
        footerHeight: 50
    });
    
    // 应用侧边栏布局
    var contentArea = $("#content-area");
    layoutManager.applyLayout(contentArea, "sidebar", {
        sidebarWidth: 300,
        position: "left"
    });
    
    // 应用卡片网格布局
    var cardContainer = $("#card-container");
    layoutManager.applyLayout(cardContainer, "card-grid", {
        cardWidth: 250,
        gap: 15
    });
}

动态布局

1. 可调整大小的面板

// 可调整大小的分割面板
class ResizablePanel {
    function this(container, options = {}) {
        this.container = container;
        this.options = Object.assign({
            direction: "horizontal", // horizontal, vertical
            minSize: 100,
            maxSize: null,
            initialSize: null,
            resizable: true,
            collapsible: false
        }, options);
        
        this.isResizing = false;
        this.startPos = 0;
        this.startSize = 0;
        
        this.init();
    }
    
    function init() {
        this.container.addClass("resizable-panel");
        this.container.addClass(this.options.direction);
        
        if (this.options.resizable) {
            this.createResizeHandle();
        }
        
        if (this.options.collapsible) {
            this.createCollapseButton();
        }
        
        this.setupEvents();
        
        // 设置初始大小
        if (this.options.initialSize) {
            this.setSize(this.options.initialSize);
        }
    }
    
    function createResizeHandle() {
        this.resizeHandle = Element.create("div", {
            class: "resize-handle " + this.options.direction
        });
        
        this.container.append(this.resizeHandle);
    }
    
    function createCollapseButton() {
        this.collapseButton = Element.create("button", {
            class: "collapse-button",
            html: "◀"
        });
        
        this.container.append(this.collapseButton);
    }
    
    function setupEvents() {
        var self = this;
        
        if (this.resizeHandle) {
            this.resizeHandle.on("mousedown", function(evt) {
                self.startResize(evt);
            });
        }
        
        if (this.collapseButton) {
            this.collapseButton.on("click", function(evt) {
                self.toggleCollapse();
            });
        }
        
        document.on("mousemove", function(evt) {
            if (self.isResizing) {
                self.handleResize(evt);
            }
        });
        
        document.on("mouseup", function(evt) {
            if (self.isResizing) {
                self.endResize(evt);
            }
        });
    }
    
    function startResize(evt) {
        this.isResizing = true;
        this.startPos = this.options.direction === "horizontal" ? evt.clientX : evt.clientY;
        this.startSize = this.getCurrentSize();
        
        document.body.addClass("resizing");
        this.container.addClass("resizing");
        
        evt.preventDefault();
    }
    
    function handleResize(evt) {
        var currentPos = this.options.direction === "horizontal" ? evt.clientX : evt.clientY;
        var delta = currentPos - this.startPos;
        var newSize = this.startSize + delta;
        
        // 应用大小限制
        if (this.options.minSize && newSize < this.options.minSize) {
            newSize = this.options.minSize;
        }
        
        if (this.options.maxSize && newSize > this.options.maxSize) {
            newSize = this.options.maxSize;
        }
        
        this.setSize(newSize);
    }
    
    function endResize(evt) {
        this.isResizing = false;
        
        document.body.removeClass("resizing");
        this.container.removeClass("resizing");
        
        // 触发调整完成事件
        this.container.dispatchEvent(new CustomEvent("resize-end", {
            detail: { size: this.getCurrentSize() }
        }));
    }
    
    function setSize(size) {
        if (this.options.direction === "horizontal") {
            this.container.style.width = size + "px";
        } else {
            this.container.style.height = size + "px";
        }
        
        // 触发调整事件
        this.container.dispatchEvent(new CustomEvent("resize", {
            detail: { size: size }
        }));
    }
    
    function getCurrentSize() {
        if (this.options.direction === "horizontal") {
            return this.container.clientWidth;
        } else {
            return this.container.clientHeight;
        }
    }
    
    function toggleCollapse() {
        if (this.container.hasClass("collapsed")) {
            this.expand();
        } else {
            this.collapse();
        }
    }
    
    function collapse() {
        this.savedSize = this.getCurrentSize();
        this.container.addClass("collapsed");
        this.setSize(0);
        
        if (this.collapseButton) {
            this.collapseButton.innerHTML = this.options.direction === "horizontal" ? "▶" : "▼";
        }
    }
    
    function expand() {
        this.container.removeClass("collapsed");
        this.setSize(this.savedSize || this.options.initialSize || 200);
        
        if (this.collapseButton) {
            this.collapseButton.innerHTML = this.options.direction === "horizontal" ? "◀" : "▲";
        }
    }
}

// 使用示例
function setupResizablePanels() {
    // 创建可调整大小的侧边栏
    var sidebar = $("#sidebar");
    var sidebarPanel = new ResizablePanel(sidebar, {
        direction: "horizontal",
        minSize: 200,
        maxSize: 400,
        initialSize: 300,
        collapsible: true
    });
    
    // 监听调整事件
    sidebar.on("resize", function(evt) {
        stdout.println(`侧边栏大小调整: ${evt.detail.size}px`);
        
        // 调整其他元素
        var content = $("#main-content");
        if (content) {
            content.style.marginLeft = evt.detail.size + "px";
        }
    });
    
    // 创建可调整大小的底部面板
    var bottomPanel = $("#bottom-panel");
    var bottomResizable = new ResizablePanel(bottomPanel, {
        direction: "vertical",
        minSize: 100,
        maxSize: 300,
        initialSize: 150
    });
}

2. 拖拽布局

// 拖拽布局管理器
class DragLayout {
    function this(container, options = {}) {
        this.container = container;
        this.options = Object.assign({
            itemSelector: ".layout-item",
            handleSelector: ".drag-handle",
            placeholder: true,
            animation: true,
            grid: false,
            snapToGrid: false,
            gridSize: 10
        }, options);
        
        this.draggedItem = null;
        this.placeholder = null;
        this.dragOffset = { x: 0, y: 0 };
        
        this.init();
    }
    
    function init() {
        this.container.addClass("drag-layout");
        this.setupEvents();
        
        if (this.options.placeholder) {
            this.createPlaceholder();
        }
    }
    
    function createPlaceholder() {
        this.placeholder = Element.create("div", {
            class: "layout-placeholder"
        });
    }
    
    function setupEvents() {
        var self = this;
        
        // 使用事件委托
        this.container.on("mousedown", this.options.handleSelector || this.options.itemSelector, function(evt) {
            if (evt.button === 0) { // 左键
                self.startDrag(this, evt);
            }
        });
        
        document.on("mousemove", function(evt) {
            if (self.draggedItem) {
                self.handleDrag(evt);
            }
        });
        
        document.on("mouseup", function(evt) {
            if (self.draggedItem) {
                self.endDrag(evt);
            }
        });
    }
    
    function startDrag(handle, evt) {
        // 找到要拖拽的项目
        this.draggedItem = handle.closest(this.options.itemSelector);
        if (!this.draggedItem) return;
        
        // 计算拖拽偏移
        var rect = this.draggedItem.getBoundingClientRect();
        this.dragOffset.x = evt.clientX - rect.left;
        this.dragOffset.y = evt.clientY - rect.top;
        
        // 添加拖拽样式
        this.draggedItem.addClass("dragging");
        this.container.addClass("drag-active");
        
        // 显示占位符
        if (this.placeholder) {
            this.placeholder.style.width = rect.width + "px";
            this.placeholder.style.height = rect.height + "px";
            this.draggedItem.parentNode.insertBefore(this.placeholder, this.draggedItem.nextSibling);
        }
        
        // 设置拖拽项目为绝对定位
        this.draggedItem.style.position = "absolute";
        this.draggedItem.style.zIndex = "1000";
        this.updateDragPosition(evt);
        
        evt.preventDefault();
    }
    
    function handleDrag(evt) {
        this.updateDragPosition(evt);
        
        // 查找插入位置
        var insertPosition = this.findInsertPosition(evt);
        if (insertPosition && this.placeholder) {
            this.updatePlaceholderPosition(insertPosition);
        }
    }
    
    function updateDragPosition(evt) {
        var x = evt.clientX - this.dragOffset.x;
        var y = evt.clientY - this.dragOffset.y;
        
        // 网格对齐
        if (this.options.snapToGrid) {
            x = Math.round(x / this.options.gridSize) * this.options.gridSize;
            y = Math.round(y / this.options.gridSize) * this.options.gridSize;
        }
        
        this.draggedItem.style.left = x + "px";
        this.draggedItem.style.top = y + "px";
    }
    
    function findInsertPosition(evt) {
        var items = this.container.$$(this.options.itemSelector);
        var draggedRect = this.draggedItem.getBoundingClientRect();
        
        for (var item of items) {
            if (item === this.draggedItem) continue;
            
            var itemRect = item.getBoundingClientRect();
            
            // 检查是否在项目上方或左侧
            if (this.isAbove(draggedRect, itemRect) || this.isLeftOf(draggedRect, itemRect)) {
                return { before: item };
            }
        }
        
        return { append: true };
    }
    
    function isAbove(draggedRect, itemRect) {
        return draggedRect.top + draggedRect.height / 2 < itemRect.top + itemRect.height / 2;
    }
    
    function isLeftOf(draggedRect, itemRect) {
        return draggedRect.left + draggedRect.width / 2 < itemRect.left + itemRect.width / 2;
    }
    
    function updatePlaceholderPosition(position) {
        if (position.before) {
            position.before.parentNode.insertBefore(this.placeholder, position.before);
        } else if (position.append) {
            this.container.append(this.placeholder);
        }
    }
    
    function endDrag(evt) {
        // 恢复项目样式
        this.draggedItem.removeClass("dragging");
        this.draggedItem.style.position = "";
        this.draggedItem.style.left = "";
        this.draggedItem.style.top = "";
        this.draggedItem.style.zIndex = "";
        
        this.container.removeClass("drag-active");
        
        // 插入到新位置
        if (this.placeholder && this.placeholder.parentNode) {
            this.placeholder.parentNode.insertBefore(this.draggedItem, this.placeholder);
            this.placeholder.remove();
        }
        
        // 触发布局改变事件
        this.container.dispatchEvent(new CustomEvent("layout-change", {
            detail: {
                item: this.draggedItem,
                newIndex: this.getItemIndex(this.draggedItem)
            }
        }));
        
        this.draggedItem = null;
    }
    
    function getItemIndex(item) {
        var items = this.container.$$(this.options.itemSelector);
        return Array.from(items).indexOf(item);
    }
    
    // 获取当前布局
    function getLayout() {
        var items = this.container.$$(this.options.itemSelector);
        return Array.from(items).map(function(item, index) {
            return {
                id: item.id || index,
                element: item,
                index: index
            };
        });
    }
    
    // 设置布局
    function setLayout(layout) {
        var fragment = document.createDocumentFragment();
        
        for (var item of layout) {
            if (item.element) {
                fragment.append(item.element);
            }
        }
        
        this.container.innerHTML = "";
        this.container.append(fragment);
    }
}

// 使用示例
function setupDragLayout() {
    var layoutContainer = $("#layout-container");
    var dragLayout = new DragLayout(layoutContainer, {
        itemSelector: ".widget",
        handleSelector: ".widget-header",
        snapToGrid: true,
        gridSize: 20
    });
    
    // 监听布局改变
    layoutContainer.on("layout-change", function(evt) {
        stdout.println(`项目移动: ${evt.detail.item.id} -> 位置 ${evt.detail.newIndex}`);
        
        // 保存布局
        saveLayoutToStorage(dragLayout.getLayout());
    });
    
    // 加载保存的布局
    var savedLayout = loadLayoutFromStorage();
    if (savedLayout) {
        dragLayout.setLayout(savedLayout);
    }
}

function saveLayoutToStorage(layout) {
    var layoutData = layout.map(function(item) {
        return {
            id: item.id,
            index: item.index
        };
    });
    
    localStorage.setItem("layout", JSON.stringify(layoutData));
}

function loadLayoutFromStorage() {
    var layoutData = localStorage.getItem("layout");
    if (!layoutData) return null;
    
    try {
        return JSON.parse(layoutData);
    } catch (error) {
        stderr.println("加载布局失败: " + error.message);
        return null;
    }
}

5.4 本章总结

在本章中,我们学习了Sciter的窗口管理与布局系统:

窗口管理

  • 窗口的创建、显示、隐藏和关闭
  • 窗口间通信机制
  • 窗口事件处理
  • 多窗口应用架构

布局系统

  • CSS布局增强(Flow布局)
  • 响应式布局设计
  • 动态布局组件
  • 可调整大小的面板

高级功能

  • 拖拽布局实现
  • 布局状态管理
  • 性能优化技巧

实践应用

  • 复杂界面布局设计
  • 用户体验优化
  • 跨平台适配

5.5 练习题

基础练习

  1. 创建一个多窗口的文本编辑器应用
  2. 实现一个响应式的仪表板布局
  3. 制作一个可调整大小的分割面板组件
  4. 设计一个拖拽排序的任务列表

进阶练习

  1. 实现一个复杂的IDE界面布局
  2. 创建一个可视化的布局编辑器
  3. 设计一个多标签页的窗口管理系统
  4. 实现一个自适应的网格布局系统

挑战练习

  1. 创建一个完整的桌面环境界面
  2. 实现一个支持嵌套的可拖拽布局系统
  3. 设计一个跨窗口的拖拽功能
  4. 制作一个布局模板系统

下一章预告: 在第6章中,我们将学习Sciter的数据绑定与状态管理,包括数据绑定机制、状态管理模式,以及如何构建响应式的数据驱动应用。