WindowFormElement

This commit is contained in:
2025-09-16 14:32:57 +08:00
parent 08e08043d7
commit 122fba228a
8 changed files with 737 additions and 386 deletions

View File

@@ -0,0 +1,57 @@
import { EventBuilderImpl } from '@/core/events/impl/EventBuilderImpl.ts'
import type { IEventMap } from '@/core/events/IEventBuilder.ts'
import type { TWindowFormState } from '@/core/window/types/WindowFormTypes.ts'
/**
* 窗口的事件
*/
export interface WindowFormEvent extends IEventMap {
/**
* 窗口最小化
* @param id 窗口id
*/
windowFormMinimize: (id: string) => void;
/**
* 窗口最大化
* @param id 窗口id
*/
windowFormMaximize: (id: string) => void;
/**
* 窗口还原
* @param id 窗口id
*/
windowFormRestore: (id: string) => void;
/**
* 窗口关闭
* @param id 窗口id
*/
windowFormClose: (id: string) => void;
/**
* 窗口聚焦
* @param id 窗口id
*/
windowFormFocus: (id: string) => void;
/**
* 窗口数据更新
* @param data 窗口数据
*/
windowFormDataUpdate: (data: IWindowFormDataUpdateParams) => void;
}
interface IWindowFormDataUpdateParams {
/** 窗口id */
id: string;
/** 窗口状态 */
state: TWindowFormState,
/** 窗口宽度 */
width: number,
/** 窗口高度 */
height: number,
/** 窗口x坐标(左上角) */
x: number,
/** 窗口y坐标(左上角) */
y: number
}
/** 窗口事件管理器 */
export const wfem = new EventBuilderImpl<WindowFormEvent>()

View File

@@ -1,7 +1,7 @@
import type { IProcessInfo } from '@/core/process/IProcessInfo.ts'
import type { IWindowForm } from '@/core/window/IWindowForm.ts'
import type { IEventBuilder } from '@/core/events/IEventBuilder.ts'
import type { IProcessEvent } from '@/core/process/types/ProcessEvent.ts'
import type { IProcessEvent } from '@/core/process/types/ProcessEventTypes.ts'
/**
* 进程接口

View File

@@ -6,7 +6,7 @@ import type { IWindowForm } from '@/core/window/IWindowForm.ts'
import { processManager } from '@/core/process/ProcessManager.ts'
import { EventBuilderImpl } from '@/core/events/impl/EventBuilderImpl.ts'
import type { IEventBuilder } from '@/core/events/IEventBuilder.ts'
import type { IProcessEvent } from '@/core/process/types/ProcessEvent.ts'
import type { IProcessEvent } from '@/core/process/types/ProcessEventTypes.ts'
/**
* 进程

View File

@@ -180,7 +180,7 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
}
/** 订阅整个状态变化 */
subscribe(fn: TObservableListener<T>, options: { immediate?: boolean } = {}): () => void {
public subscribe(fn: TObservableListener<T>, options: { immediate?: boolean } = {}): () => void {
this.listeners.add(fn)
if (options.immediate) fn(this.state)
return () => {
@@ -189,7 +189,7 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
}
/** 订阅指定字段变化 */
subscribeKey<K extends keyof T>(
public subscribeKey<K extends keyof T>(
keys: K | K[],
fn: TObservableKeyListener<T, K>,
options: { immediate?: boolean } = {}
@@ -214,7 +214,7 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
}
/** 批量更新状态 */
patch(values: Partial<T>): void {
public patch(values: Partial<T>): void {
for (const key in values) {
if (Object.prototype.hasOwnProperty.call(values, key)) {
const typedKey = key as keyof T
@@ -226,7 +226,7 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
}
/** 销毁 Observable 实例 */
dispose(): void {
public dispose(): void {
this.disposed = true
this.listeners.clear()
this.keyListeners.clear()
@@ -234,7 +234,7 @@ export class ObservableImpl<T extends TNonFunctionProperties<T>> implements IObs
}
/** 语法糖:返回一个可解构赋值的 Proxy */
toRefsProxy(): { [K in keyof T]: T[K] } {
public toRefsProxy(): { [K in keyof T]: T[K] } {
const self = this
return new Proxy({} as T, {
get(_, prop: string | symbol) {

View File

@@ -9,91 +9,125 @@ import { DraggableResizableWindow } from '@/core/utils/DraggableResizableWindow.
import '../css/window-form.scss'
import { serviceManager } from '@/core/service/kernel/ServiceManager.ts'
import '../ui/WindowFormElement.ts'
import { wfem } from '@/core/events/WindowFormEventManager.ts'
import type { IObservable } from '@/core/state/IObservable.ts'
import { ObservableImpl } from '@/core/state/impl/ObservableImpl.ts'
export default class WindowFormImpl implements IWindowForm {
private readonly _id: string = uuidV4();
private readonly _procId: string;
private dom: HTMLElement;
private drw: DraggableResizableWindow;
private pos: WindowFormPos = { x: 0, y: 0 };
private width: number;
private height: number;
private readonly _id: string = uuidV4()
private readonly _procId: string
private dom: HTMLElement
private drw: DraggableResizableWindow
private pos: WindowFormPos = { x: 0, y: 0 }
private width: number
private height: number
private _state: IObservable<{ x: number, y: number }> = new ObservableImpl({
x: 0,
y: 0,
})
public get id() {
return this._id;
return this._id
}
public get proc() {
return processManager.findProcessById(this._procId)
}
private get desktopRootDom() {
return XSystem.instance.desktopRootDom;
return XSystem.instance.desktopRootDom
}
private get sm() {
return serviceManager.getService('WindowForm')
}
public get windowFormEle() {
return this.dom;
return this.dom
}
public get windowFormState() {
return this.drw.windowFormState
}
constructor(proc: IProcess, config: IWindowFormConfig) {
this._procId = proc.id;
this._procId = proc.id
console.log('WindowForm')
this.pos = {
x: config.left ?? 0,
y: config.top ?? 0
y: config.top ?? 0,
}
this.width = config.width ?? 200;
this.height = config.height ?? 100;
this.width = config.width ?? 200
this.height = config.height ?? 100
this.createWindowFrom();
this.createWindowFrom()
this.initEvent()
}
private initEvent() {
wfem.addEventListener('windowFormClose', this.windowFormCloseFun)
}
private windowFormCloseFun = (id: string) => {
if (id === this.id) {
this.closeWindowForm()
}
}
private createWindowFrom() {
this.dom = this.createWindowFormEle();
this.dom.style.position = 'absolute';
this.dom.style.width = `${this.width}px`;
this.dom.style.height = `${this.height}px`;
this.dom.style.zIndex = '10';
const header = this.dom.querySelector('.title-bar') as HTMLElement;
const content = this.dom.querySelector('.window-content') as HTMLElement;
this.drw = new DraggableResizableWindow({
target: this.dom,
handle: header,
snapAnimation: true,
snapThreshold: 20,
boundaryElement: document.body,
taskbarElementId: '#taskbar',
onWindowStateChange: (state) => {
if (state === 'maximized') {
this.dom.style.borderRadius = '0px';
} else {
this.dom.style.borderRadius = '5px';
}
},
})
// this.dom = this.createWindowFormEle();
// this.dom.style.position = 'absolute';
// this.dom.style.width = `${this.width}px`;
// this.dom.style.height = `${this.height}px`;
// this.dom.style.zIndex = '10';
//
// const header = this.dom.querySelector('.title-bar') as HTMLElement;
// const content = this.dom.querySelector('.window-content') as HTMLElement;
// this.drw = new DraggableResizableWindow({
// target: this.dom,
// handle: header,
// snapAnimation: true,
// snapThreshold: 20,
// boundaryElement: document.body,
// taskbarElementId: '#taskbar',
// onWindowStateChange: (state) => {
// if (state === 'maximized') {
// this.dom.style.borderRadius = '0px';
// } else {
// this.dom.style.borderRadius = '5px';
// }
// },
// })
// this.desktopRootDom.appendChild(this.dom);
const wf = document.createElement('window-form-element')
wf.pos = this._state
wf.wid = this.id
wf.dragContainer = document.body
wf.snapDistance = 20
wf.addEventListener('windowForm:dragStart', (e) => {
console.log('dragstart', e)
wf.taskbarElementId = '#taskbar'
wf.addManagedEventListener('windowForm:stateChange:minimize', (e) => {
console.log('windowForm:stateChange:minimize', e)
})
this.desktopRootDom.appendChild(wf)
wf.addManagedEventListener('windowForm:stateChange:maximize', (e) => {
console.log('windowForm:stateChange:maximize', e)
})
wf.addManagedEventListener('windowForm:stateChange:restore', (e) => {
console.log('windowForm:stateChange:restore', e)
})
wf.addManagedEventListener('windowForm:close', () => {
this.closeWindowForm()
})
this.dom = wf
this.desktopRootDom.appendChild(this.dom)
wfem.notifyEvent('windowFormFocus', this.id)
}
public closeWindowForm() {
this.drw.destroy();
this.desktopRootDom.removeChild(this.dom);
// this.drw.destroy();
this.desktopRootDom.removeChild(this.dom)
this.proc?.event.notifyEvent('onProcessWindowFormExit', this.id)
wfem.removeEventListener('windowFormClose', this.windowFormCloseFun)
// wfem.notifyEvent('windowFormClose', this.id)
}
private createWindowFormEle() {
const template = document.createElement('template');
const template = document.createElement('template')
template.innerHTML = `
<div class="window">
<div class="title-bar">
@@ -107,23 +141,24 @@ export default class WindowFormImpl implements IWindowForm {
<div class="window-content"></div>
</div>
`
const fragment = template.content.cloneNode(true) as DocumentFragment;
const fragment = template.content.cloneNode(true) as DocumentFragment
const windowElement = fragment.firstElementChild as HTMLElement
windowElement.querySelector('.btn.minimize')
?.addEventListener('click', () => this.drw.minimize());
windowElement
.querySelector('.btn.minimize')
?.addEventListener('click', () => this.drw.minimize())
windowElement.querySelector('.btn.maximize')
?.addEventListener('click', () => {
if (this.drw.windowFormState === 'maximized') {
this.drw.restore()
} else {
this.drw.maximize()
}
});
windowElement.querySelector('.btn.maximize')?.addEventListener('click', () => {
if (this.drw.windowFormState === 'maximized') {
this.drw.restore()
} else {
this.drw.maximize()
}
})
windowElement.querySelector('.btn.close')
?.addEventListener('click', () => this.closeWindowForm());
windowElement
.querySelector('.btn.close')
?.addEventListener('click', () => this.closeWindowForm())
return windowElement
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,21 @@
--shadow: 0 10px 30px rgba(0,0,0,0.25);
font-family: system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial;
}
:host([focused]) {
z-index: 11;
.window {
border-color: #8338ec;
}
}
:host([windowFormState='maximized']) {
.window {
border-radius: 0;
box-shadow: none;
}
}
.window {
position: absolute;
box-shadow: var(--shadow, 0 10px 30px rgba(0,0,0,0.25));
@@ -20,7 +35,12 @@
flex-direction: column;
width: 100%;
height: 100%;
&.focus {
border-color: #3a86ff;
}
}
.titlebar {
height: var(--titlebar-height, 32px);
display: flex;
@@ -30,6 +50,7 @@
background: linear-gradient(#f2f2f2, #e9e9e9);
border-bottom: 1px solid rgba(0,0,0,0.06);
}
.title {
font-size: 13px;
font-weight: 600;
@@ -39,7 +60,9 @@
text-overflow: ellipsis;
color: #111;
}
.controls { display: flex; gap: 6px; }
button.ctrl {
width: 34px;
height: 24px;
@@ -50,7 +73,9 @@ button.ctrl {
font-weight: 600;
font-size: 12px;
}
button.ctrl:hover { background: rgba(0,0,0,0.06); }
.content { flex: 1; overflow: auto; padding: 12px; background: transparent; }
.resizer { position: absolute; z-index: 20; }