Files
xianyan/lib/editor/models/layer_models.dart
Developer a5b997aecb feat: 新增编辑器功能与优化
1. 添加编辑器3D场景服务接口及实现
2. 实现平台IO工具类与数据库连接
3. 新增SVG图标资源
4. 优化编辑器操作Mixin架构
5. 修复Web端兼容性问题
6. 更新依赖配置与构建脚本
7. 改进主题自适应服务
8. 添加对齐辅助线组件
9. 完善迷你文字编辑栏
10. 优化页面过渡动画
2026-04-25 09:50:30 +08:00

252 lines
8.6 KiB
Dart

// ============================================================
// 闲言APP — 统一图层模型 (freezed sealed class)
// 创建时间: 2026-04-25
// 更新时间: 2026-04-25
// 作用: 统一 TextLayer/StickerLayer/GlassCardLayer/AnnotationLayer 为 LayerInfo
// 上次更新: 移除重复枚举定义,复用 editor_models.dart 中的类型
// ============================================================
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'editor_models.dart';
part 'layer_models.freezed.dart';
// ============================================================
// 图层类型枚举
// ============================================================
enum LayerType {
text('📝', '文字'),
sticker('🎭', '贴纸'),
glass('🧊', '玻璃'),
annotation('✏️', '标注');
const LayerType(this.emoji, this.label);
final String emoji;
final String label;
}
// ============================================================
// 统一图层模型 — freezed sealed class
// ============================================================
@freezed
sealed class LayerInfo with _$LayerInfo {
const LayerInfo._();
// ─── 文字层 ───
const factory LayerInfo.text({
required String id,
@Default('') String name,
required String text,
@Default(24.0) double fontSize,
@Default(FontWeight.normal) FontWeight fontWeight,
@Default('Inter') String fontFamily,
@Default(Color(0xFF1A1A2E)) Color color,
@Default(TextAlignMode.center) TextAlignMode textAlign,
@Default(0.0) double offsetX,
@Default(0.0) double offsetY,
@Default(0.0) double rotation,
@Default(1.0) double scale,
@Default(0.0) double letterSpacing,
@Default(1.5) double lineHeight,
@Default(false) bool italic,
@Default(false) bool underline,
@Default(false) bool strikethrough,
Color? strokeColor,
@Default(0.0) double strokeWidth,
Color? shadowColor,
@Default(0.0) double shadowBlur,
@Default(0.0) double shadowOffsetX,
@Default(0.0) double shadowOffsetY,
@Default(true) bool visible,
@Default(false) bool locked,
@Default(1.0) double opacity,
@Default(0) int zIndex,
}) = TextLayerInfo;
// ─── 贴纸层 ───
const factory LayerInfo.sticker({
required String id,
@Default('') String name,
@Default(StickerType.emoji) StickerType stickerType,
required String assetPath,
@Default(80.0) double size,
@Default(0.0) double offsetX,
@Default(0.0) double offsetY,
@Default(0.0) double rotation,
@Default(1.0) double scale,
@Default(1.0) double opacity,
@Default(true) bool visible,
@Default(false) bool locked,
@Default(0) int zIndex,
}) = StickerLayerInfo;
// ─── 玻璃层 ───
const factory LayerInfo.glass({
required String id,
@Default('') String name,
@Default(GlassStyle.crystal) GlassStyle style,
@Default(0.0) double offsetX,
@Default(0.0) double offsetY,
@Default(200.0) double width,
@Default(150.0) double height,
@Default(0.0) double rotation,
@Default(1.0) double scale,
@Default(20.0) double borderRadius,
@Default(25.0) double blurIntensity,
@Default(0.65) double opacity,
@Default(0.15) double borderOpacity,
@Default(0.4) double highlightStrength,
@Default(true) bool showHighlight,
@Default(true) bool showInnerShadow,
@Default(Colors.white) Color tintColor,
@Default(true) bool visible,
@Default(false) bool locked,
@Default(10) int zIndex,
@Default(<String>[]) List<String> containedTextIds,
@Default(<String>[]) List<String> containedStickerIds,
}) = GlassLayerInfo;
// ─── 标注层 ───
const factory LayerInfo.annotation({
required String id,
@Default('') String name,
required AnnotationType annotationType,
required List<AnnotationPoint> points,
@Default(3.0) double strokeWidth,
@Default(Colors.red) Color color,
@Default(1.0) double opacity,
@Default(true) bool visible,
@Default(false) bool locked,
@Default(0) int zIndex,
}) = AnnotationLayerInfo;
// ─── 公共接口 ───
String get layerId => switch (this) {
TextLayerInfo(:final id) => id,
StickerLayerInfo(:final id) => id,
GlassLayerInfo(:final id) => id,
AnnotationLayerInfo(:final id) => id,
};
String get layerName => switch (this) {
TextLayerInfo(:final name) => name.isNotEmpty ? name : '文字层',
StickerLayerInfo(:final name) => name.isNotEmpty ? name : '贴纸层',
GlassLayerInfo(:final name) => name.isNotEmpty ? name : '玻璃层',
AnnotationLayerInfo(:final name) => name.isNotEmpty ? name : '标注层',
};
LayerType get layerType => switch (this) {
TextLayerInfo() => LayerType.text,
StickerLayerInfo() => LayerType.sticker,
GlassLayerInfo() => LayerType.glass,
AnnotationLayerInfo() => LayerType.annotation,
};
bool get isVisible => switch (this) {
TextLayerInfo(:final visible) => visible,
StickerLayerInfo(:final visible) => visible,
GlassLayerInfo(:final visible) => visible,
AnnotationLayerInfo(:final visible) => visible,
};
bool get isLocked => switch (this) {
TextLayerInfo(:final locked) => locked,
StickerLayerInfo(:final locked) => locked,
GlassLayerInfo(:final locked) => locked,
AnnotationLayerInfo(:final locked) => locked,
};
double get layerOpacity => switch (this) {
TextLayerInfo(:final opacity) => opacity,
StickerLayerInfo(:final opacity) => opacity,
GlassLayerInfo(:final opacity) => opacity,
AnnotationLayerInfo(:final opacity) => opacity,
};
double get layerOffsetX => switch (this) {
TextLayerInfo(:final offsetX) => offsetX,
StickerLayerInfo(:final offsetX) => offsetX,
GlassLayerInfo(:final offsetX) => offsetX,
AnnotationLayerInfo() => 0.0,
};
double get layerOffsetY => switch (this) {
TextLayerInfo(:final offsetY) => offsetY,
StickerLayerInfo(:final offsetY) => offsetY,
GlassLayerInfo(:final offsetY) => offsetY,
AnnotationLayerInfo() => 0.0,
};
double get layerRotation => switch (this) {
TextLayerInfo(:final rotation) => rotation,
StickerLayerInfo(:final rotation) => rotation,
GlassLayerInfo(:final rotation) => rotation,
AnnotationLayerInfo() => 0.0,
};
double get layerScale => switch (this) {
TextLayerInfo(:final scale) => scale,
StickerLayerInfo(:final scale) => scale,
GlassLayerInfo(:final scale) => scale,
AnnotationLayerInfo() => 1.0,
};
int get layerZIndex => switch (this) {
TextLayerInfo(:final zIndex) => zIndex,
StickerLayerInfo(:final zIndex) => zIndex,
GlassLayerInfo(:final zIndex) => zIndex,
AnnotationLayerInfo(:final zIndex) => zIndex,
};
String get layerEmoji => layerType.emoji;
LayerInfo withVisibility(bool visible) => switch (this) {
TextLayerInfo() => (this as TextLayerInfo).copyWith(visible: visible),
StickerLayerInfo() => (this as StickerLayerInfo).copyWith(visible: visible),
GlassLayerInfo() => (this as GlassLayerInfo).copyWith(visible: visible),
AnnotationLayerInfo() => (this as AnnotationLayerInfo).copyWith(
visible: visible,
),
};
LayerInfo withLocked(bool locked) => switch (this) {
TextLayerInfo() => (this as TextLayerInfo).copyWith(locked: locked),
StickerLayerInfo() => (this as StickerLayerInfo).copyWith(locked: locked),
GlassLayerInfo() => (this as GlassLayerInfo).copyWith(locked: locked),
AnnotationLayerInfo() => (this as AnnotationLayerInfo).copyWith(
locked: locked,
),
};
LayerInfo withOpacity(double opacity) => switch (this) {
TextLayerInfo() => (this as TextLayerInfo).copyWith(opacity: opacity),
StickerLayerInfo() => (this as StickerLayerInfo).copyWith(opacity: opacity),
GlassLayerInfo() => (this as GlassLayerInfo).copyWith(opacity: opacity),
AnnotationLayerInfo() => (this as AnnotationLayerInfo).copyWith(
opacity: opacity,
),
};
LayerInfo withName(String name) => switch (this) {
TextLayerInfo() => (this as TextLayerInfo).copyWith(name: name),
StickerLayerInfo() => (this as StickerLayerInfo).copyWith(name: name),
GlassLayerInfo() => (this as GlassLayerInfo).copyWith(name: name),
AnnotationLayerInfo() => (this as AnnotationLayerInfo).copyWith(name: name),
};
LayerInfo withZIndex(int zIndex) => switch (this) {
TextLayerInfo() => (this as TextLayerInfo).copyWith(zIndex: zIndex),
StickerLayerInfo() => (this as StickerLayerInfo).copyWith(zIndex: zIndex),
GlassLayerInfo() => (this as GlassLayerInfo).copyWith(zIndex: zIndex),
AnnotationLayerInfo() => (this as AnnotationLayerInfo).copyWith(
zIndex: zIndex,
),
};
}