# 自定义对话框

创建自定义对话框和面板的指南。

## 对话框类型

| 类型 | 特点 | 适用场景 | 推荐基类 |
|------|------|----------|----------|
| **模态对话框** | 弹出后图形界面不能操作 | 复杂设置、数据输入、需要拾取点/实体 | `ModalDialogBase` |
| **非模态面板** | 图形界面仍可操作 | 工具面板、查找替换、实时预览 | `ModelessPanelBase` |

---

## ModalDialogBase - 模态对话框基类

内置深色主题样式，支持 `suspend()/resume()` 暂停恢复功能。

### 基本用法

```typescript
import { ModalDialogBase, html } from 'vjcad';

class SettingsDialog extends ModalDialogBase<{ color: number; width: number }> {
    static dialogTitle = "设置";

    static properties = {
        ...ModalDialogBase.properties,
        color: { type: Number },
        width: { type: Number },
    };

    color = 1;
    width = 1;

    // 必须实现：渲染对话框内容
    renderContent() {
        return html`
            <div style="min-width: 300px;">
                <div class="row">
                    <span class="label" style="width: 80px;">颜色:</span>
                    <input type="number" class="input" style="flex: 1;"
                        .value=${String(this.color)}
                        @input=${(e) => this.color = parseInt(e.target.value)}>
                </div>
                <div class="row" style="margin-top: 12px;">
                    <span class="label" style="width: 80px;">线宽:</span>
                    <input type="number" class="input" style="flex: 1;"
                        .value=${String(this.width)}
                        @input=${(e) => this.width = parseInt(e.target.value)}>
                </div>
            </div>
        `;
    }

    // 覆盖 confirm() 设置返回结果
    confirm() {
        this.result = { color: this.color, width: this.width };
        this.close();
    }
}

customElements.define('settings-dialog', SettingsDialog);

// 使用
const dialog = new SettingsDialog();
const result = await dialog.startDialog();
if (result) {
    console.log('设置:', result.color, result.width);
}
```

### 使用 suspend/resume 拾取点

> **重要**：`suspend()/resume()` **必须在命令上下文中使用**！

```typescript
import { 
    ModalDialogBase, html, Engine, CircleEnt,
    PointInputOptions, InputStatusEnum, Point2D,
    CommandDefinition, CommandRegistry, CommandOptions
} from 'vjcad';

class DrawCircleDialog extends ModalDialogBase<{ center: any, radius: number }> {
    static dialogTitle = "画圆";

    static properties = {
        ...ModalDialogBase.properties,
        centerPoint: { type: Object },
        radius: { type: Number },
    };

    centerPoint = null;
    radius = 20;

    // 拾取圆心
    async pickCenter() {
        this.suspend();  // 暂停对话框
        
        const options = new PointInputOptions("指定圆心:");
        const result = await Engine.editor.getPoint(options);
        
        this.resume();   // 恢复对话框
        
        if (result.status === InputStatusEnum.OK) {
            this.centerPoint = result.value;
        }
    }

    // 拾取半径（带橡皮线）
    async pickRadius() {
        if (!this.centerPoint) return;
        
        this.suspend();
        
        const options = new PointInputOptions("指定半径:");
        options.useBasePoint = true;
        options.basePoint = new Point2D(this.centerPoint.x, this.centerPoint.y);
        
        const result = await Engine.editor.getPoint(options);
        
        this.resume();
        
        if (result.status === InputStatusEnum.OK) {
            const dx = result.value.x - this.centerPoint.x;
            const dy = result.value.y - this.centerPoint.y;
            this.radius = Math.sqrt(dx * dx + dy * dy);
        }
    }

    renderContent() {
        return html`
            <div style="min-width: 320px;">
                <div class="section-title">圆参数</div>
                <div class="row">
                    <span class="label" style="width: 60px;">圆心:</span>
                    <span class="input" style="flex: 1; background: #0d1117;">
                        ${this.centerPoint ? `(${this.centerPoint.x.toFixed(2)}, ${this.centerPoint.y.toFixed(2)})` : '未指定'}
                    </span>
                    <button class="btn" @click=${this.pickCenter}>拾取</button>
                </div>
                <div class="row" style="margin-top: 12px;">
                    <span class="label" style="width: 60px;">半径:</span>
                    <input type="number" class="input" style="width: 80px;"
                        .value=${String(this.radius.toFixed(2))}
                        @input=${(e) => this.radius = parseFloat(e.target.value)}>
                    <button class="btn" @click=${this.pickRadius} ?disabled=${!this.centerPoint}>从图拾取</button>
                </div>
            </div>
        `;
    }

    confirm() {
        if (!this.centerPoint) return;
        this.result = { center: this.centerPoint, radius: this.radius };
        this.close();
    }
}

customElements.define('draw-circle-dialog', DrawCircleDialog);

// 命令类
class DrawCircleCommand {
    async main() {
        const dialog = new DrawCircleDialog();
        const result = await dialog.startDialog();
        
        if (result) {
            const circle = new CircleEnt([result.center.x, result.center.y], result.radius);
            circle.setDefaults();
            Engine.addEntities(circle);
            Engine.zoomExtents();
        }
    }
}

// 注册并执行命令
CommandRegistry.regist(new CommandDefinition('DRAWCIRCLE', '画圆', DrawCircleCommand, new CommandOptions()));
await Engine.editor.executerWithOp('DRAWCIRCLE');
```

### 禁用 Shadow DOM

需要使用第三方库（如 x-spreadsheet）时：

```typescript
import { ModalDialogBase, createNoShadowStyles, html } from 'vjcad';

class ThirdPartyDialog extends ModalDialogBase {
    static dialogTitle = "使用第三方库";
    
    useShadowDOM = false;  // 禁用 Shadow DOM

    renderContent() {
        return html`<div id="spreadsheet-container"></div>`;
    }

    // 需要手动注入样式
    render() {
        return html`
            <style>${createNoShadowStyles('third-party-dialog')}</style>
            <base-dialog>
                <div class="dialog-body">${this.renderContent()}</div>
                <div class="dialog-footer">${this.renderFooter()}</div>
            </base-dialog>
        `;
    }
}
```

### API 参考

| 属性/方法 | 说明 |
|-----------|------|
| `static dialogTitle` | 对话框标题 |
| `result` | 对话框返回结果 |
| `useShadowDOM` | 是否使用 Shadow DOM，默认 `true` |
| `startDialog(options?)` | 显示对话框并等待结果 |
| `renderContent()` | **必须实现** - 渲染对话框内容 |
| `renderFooter()` | 渲染底部按钮，默认为确定/取消 |
| `confirm()` | 确定按钮回调，需设置 `this.result` 后调用 `this.close()` |
| `cancel()` | 取消按钮回调 |
| `close()` | 关闭对话框 |
| `suspend()` | 暂停对话框（用于拾取点/选择实体） |
| `resume()` | 恢复对话框 |
| `isSuspended` | 是否处于暂停状态 |

---

## ModelessPanelBase - 非模态面板基类

内置深色主题样式和拖拽功能，不阻止用户操作 CAD 界面。

### 基本用法

```typescript
import { ModelessPanelBase, html, Engine, LineEnt, CircleEnt } from 'vjcad';

class DrawToolsPanel extends ModelessPanelBase {
    static panelTitle = "绘图工具";
    static panelWidth = "200px";
    static initialPosition = { top: '100px', right: '20px' };

    drawLine() {
        const line = new LineEnt(
            [Math.random() * 100, Math.random() * 100],
            [Math.random() * 100, Math.random() * 100]
        );
        line.setDefaults();
        line.color = Math.floor(Math.random() * 7) + 1;
        Engine.addEntities(line);
        Engine.zoomExtents();
    }

    drawCircle() {
        const circle = new CircleEnt(
            [Math.random() * 100, Math.random() * 100],
            Math.random() * 20 + 10
        );
        circle.setDefaults();
        Engine.addEntities(circle);
        Engine.zoomExtents();
    }

    // 必须实现：渲染面板内容
    renderContent() {
        return html`
            <div style="display: flex; flex-direction: column; gap: 8px;">
                <button class="btn btn-primary" @click=${this.drawLine}>画直线</button>
                <button class="btn btn-primary" @click=${this.drawCircle}>画圆</button>
                <div style="border-top: 1px solid #3d4a5c; margin: 8px 0;"></div>
                <button class="btn" @click=${() => Engine.zoomExtents()}>缩放全图</button>
            </div>
        `;
    }
}

customElements.define('draw-tools-panel', DrawToolsPanel);

// 使用
const panel = new DrawToolsPanel();
document.body.appendChild(panel);
panel.show();
```

### 带状态的面板

```typescript
class PropertiesPanel extends ModelessPanelBase {
    static panelTitle = "属性设置";
    static panelWidth = "280px";
    static initialPosition = { top: '100px', left: '20px' };

    static properties = {
        ...ModelessPanelBase.properties,
        currentColor: { type: Number },
        lineWeight: { type: Number },
    };

    constructor() {
        super();
        this.currentColor = 7;
        this.lineWeight = 1;
    }

    renderContent() {
        return html`
            <div>
                <div class="section-title">常规属性</div>
                <div class="row">
                    <span class="label" style="width: 60px;">颜色:</span>
                    <input type="number" class="input" style="flex: 1;"
                        .value=${String(this.currentColor)}
                        @input=${(e) => this.currentColor = parseInt(e.target.value)}>
                </div>
                <div class="row" style="margin-top: 8px;">
                    <span class="label" style="width: 60px;">线宽:</span>
                    <input type="number" class="input" style="flex: 1;"
                        .value=${String(this.lineWeight)}
                        @input=${(e) => this.lineWeight = parseInt(e.target.value)}>
                </div>
                <div style="margin-top: 16px;">
                    <button class="btn btn-primary" style="width: 100%;" @click=${this.applySettings}>
                        应用设置
                    </button>
                </div>
            </div>
        `;
    }

    applySettings() {
        console.log('应用设置:', this.currentColor, this.lineWeight);
    }
}
```

### 动态标题

```typescript
class EntityInfoPanel extends ModelessPanelBase {
    static panelTitle = "实体信息";
    
    entityCount = 0;

    // 覆盖 onShow 在显示时刷新数据
    onShow() {
        super.onShow();
        this.entityCount = Engine.getEntities(e => e.isAlive).length;   // getEntities 含已删除实体
    }

    // 覆盖 getPanelTitle 实现动态标题
    getPanelTitle() {
        return `实体信息 (${this.entityCount})`;
    }

    renderContent() {
        return html`
            <div>
                <div class="status">共 <strong style="color: #58a6ff;">${this.entityCount}</strong> 个实体</div>
                <button class="btn" style="width: 100%; margin-top: 12px;" @click=${() => { this.entityCount = Engine.getEntities(e => e.isAlive).length; this.requestUpdate(); }}>
                    刷新
                </button>
            </div>
        `;
    }
}
```

### 使用 createPanel 工厂函数

```typescript
import { createPanel } from 'vjcad';

const panelManager = createPanel(PropertiesPanel, 'properties-panel');
panelManager.show();
panelManager.hide();
panelManager.toggle();
panelManager.destroy();

if (panelManager.isVisible) {
    console.log('面板可见');
}
```

### API 参考

| 属性/方法 | 说明 |
|-----------|------|
| `static panelTitle` | 面板标题 |
| `static panelWidth` | 面板宽度，如 `"360px"` |
| `static initialPosition` | 初始位置，如 `{ top: '100px', right: '20px' }` |
| `static maxHeight` | 最大高度，如 `"80vh"` |
| `renderContent()` | **必须实现** - 渲染面板内容 |
| `getPanelTitle()` | 获取标题（可覆盖实现动态标题） |
| `show()` | 显示面板 |
| `hide()` | 隐藏面板 |
| `toggle()` | 切换显示状态 |
| `destroy()` | 销毁面板 |
| `isVisible` | 是否可见 |
| `onShow()` | 面板显示时回调 |
| `onHide()` | 面板隐藏时回调 |
| `onDestroy()` | 面板销毁时回调 |

---

## 内置 CSS 类

基类提供深色主题样式类，可直接在 `renderContent()` 中使用：

| CSS 类 | 说明 |
|--------|------|
| `.row` | 行容器，flex 布局，`align-items: center` |
| `.label` | 标签文字 |
| `.input` | 输入框样式（深色背景） |
| `.select` | 下拉选择框样式 |
| `.btn` | 普通按钮 |
| `.btn-primary` | 主按钮（蓝色） |
| `.section-title` | 分组标题 |
| `.hint` | 提示文字（灰色） |
| `.status` | 状态文字 |

---

## 对比总结

| 特性 | ModalDialogBase | ModelessPanelBase |
|------|-----------------|-------------------|
| 挂载位置 | 自动挂载到 `Engine.dialog` | `document.body.appendChild()` |
| CAD 交互 | 需要 `suspend()/resume()` | 始终可交互 |
| 拖拽支持 | 内置 | 内置 |
| 显示控制 | `startDialog()` 返回 Promise | `show()/hide()/toggle()` |
| 返回值 | `this.result` | 无 |
| 适用场景 | 复杂表单、需要拾取 | 工具面板、实时操作 |

---

## 底层实现（高级）

如果基类不满足需求，可直接使用底层 `base-dialog` 组件或手动实现面板。

### base-dialog CSS 变量

```css
base-dialog {
    --dialog-header-bg: #252d3a;       /* 标题栏背景 */
    --dialog-header-color: #e8eaed;    /* 标题栏文字 */
    --dialog-header-border: #3d4a5c;   /* 标题栏边框 */
    --dialog-contents-color: #1e2530;  /* 内容区背景 */
}
```

### 手动创建模态对话框

```typescript
import { html, LitElement, Engine } from 'vjcad';

class ManualDialog extends LitElement {
    private baseDialog: any;
    
    createRenderRoot() { return this; }  // 禁用 Shadow DOM
    
    async firstUpdated() {
        this.baseDialog = this.querySelector('base-dialog');
    }
    
    async startDialog() {
        Engine.dialog!.appendChild(this);
        await this.updateComplete;
        
        await this.baseDialog?._startBaseDialog({
            title: "手动对话框",
            renderTarget: this.renderRoot
        });
        
        this.remove();
    }
    
    render() {
        return html`
            <style>
                manual-dialog, manual-dialog base-dialog {
                    --dialog-header-bg: #252d3a;
                    --dialog-header-color: #e8eaed;
                }
            </style>
            <base-dialog>
                <div style="padding: 16px; background: #1e2530; color: #e8eaed;">
                    内容
                </div>
            </base-dialog>
        `;
    }
}
```

### 关键方法

| 方法 | 说明 |
|------|------|
| `baseDialog._startBaseDialog({ title, renderTarget })` | 启动对话框 |
| `baseDialog.close()` | 关闭对话框 |
| `baseDialog.suspend()` | 暂停对话框 |
| `baseDialog.resume()` | 恢复对话框 |
