1. 添加编辑器3D场景服务接口及实现 2. 实现平台IO工具类与数据库连接 3. 新增SVG图标资源 4. 优化编辑器操作Mixin架构 5. 修复Web端兼容性问题 6. 更新依赖配置与构建脚本 7. 改进主题自适应服务 8. 添加对齐辅助线组件 9. 完善迷你文字编辑栏 10. 优化页面过渡动画
252 lines
8.6 KiB
Dart
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,
|
|
),
|
|
};
|
|
}
|