Files
xianyan/lib/editor/models/canvas_style_model.dart
Developer f91be94e9c refactor: 完成项目架构重构,统一模块导入路径
- 清理大量废弃的 barrel 导出文件,移除冗余的中间导出层
- 修复所有相对路径导入错误,统一调整为扁平化模块引用
- 更新多平台 pubspec 版本号与依赖库版本
- 补充后端功能问题管理后台与脚本工具
- 调整部分页面的快捷方式文案适配新功能
- 更新部分翻译覆盖率与API文档
2026-06-12 08:53:57 +08:00

298 lines
10 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// 闲言APP — 画布样式模型
// 创建时间: 2026-05-05
// 更新时间: 2026-06-12
// 作用: 画布边缘样式数据模型 — 圆角+边框+阴影+叠层+外边距+内阴影
// 上次更新: 新增四角独立圆角/内阴影/常用叠层位置
// ============================================================
import 'dart:convert';
import 'package:flutter/material.dart';
// ============================================================
// 边框样式枚举
// ============================================================
enum CanvasBorderStyle {
none('无边框'),
solid('实线'),
dashed('虚线'),
dotted('点线');
final String label;
const CanvasBorderStyle(this.label);
}
// ============================================================
// 叠层位置枚举
// ============================================================
enum CanvasStackPosition {
topLeft('左上', AlignmentDirectional.topStart),
topCenter('上中', AlignmentDirectional.topCenter),
topRight('右上', AlignmentDirectional.topEnd),
centerLeft('左中', AlignmentDirectional.centerStart),
center('居中', AlignmentDirectional.center),
centerRight('右中', AlignmentDirectional.centerEnd),
bottomLeft('左下', AlignmentDirectional.bottomStart),
bottomCenter('下中', AlignmentDirectional.bottomCenter),
bottomRight('右下', AlignmentDirectional.bottomEnd),
left('', AlignmentDirectional.centerStart),
right('', AlignmentDirectional.centerEnd),
top('', AlignmentDirectional.topCenter),
bottom('', AlignmentDirectional.bottomCenter);
final String label;
final AlignmentDirectional alignment;
const CanvasStackPosition(this.label, this.alignment);
/// 常用叠层位置 — 面板UI展示的精简列表6个最常用位置
static const List<CanvasStackPosition> commonPositions = [
bottomRight,
bottomLeft,
topRight,
topLeft,
center,
right,
];
}
// ============================================================
// 画布样式模型
// ============================================================
class CanvasStyleModel {
// ─── 圆角 ───
final double borderRadius;
final bool useIndependentRadius;
final double borderRadiusTL;
final double borderRadiusTR;
final double borderRadiusBL;
final double borderRadiusBR;
// ─── 边框 ───
final CanvasBorderStyle borderStyle;
final double borderWidth;
final Color borderColor;
// ─── 外阴影 ───
final double shadowBlur;
final double shadowOpacity;
final Color shadowColor;
final double shadowSpread;
final double shadowOffsetX;
final double shadowOffsetY;
// ─── 内阴影 ───
final double innerShadowBlur;
final double innerShadowOpacity;
final double innerShadowOffsetX;
final double innerShadowOffsetY;
// ─── 叠层 ───
final int stackLayers;
final double stackDistance;
final CanvasStackPosition stackPosition;
// ─── 外边距 ───
final double outerMargin;
// ─── 计算属性 ───
bool get hasShadow => shadowBlur > 0 && shadowOpacity > 0;
bool get hasInnerShadow => innerShadowBlur > 0 && innerShadowOpacity > 0;
bool get hasStack => stackLayers > 1;
bool get hasOuterMargin => outerMargin > 0;
bool get hasBorder => borderStyle != CanvasBorderStyle.none;
/// 获取有效圆角 — 独立模式返回TL值统一模式返回borderRadius
double get effectiveBorderRadius =>
useIndependentRadius ? borderRadiusTL : borderRadius;
static const CanvasStyleModel defaults = CanvasStyleModel._();
const CanvasStyleModel._({
this.borderRadius = 0,
this.useIndependentRadius = false,
this.borderRadiusTL = 0,
this.borderRadiusTR = 0,
this.borderRadiusBL = 0,
this.borderRadiusBR = 0,
this.borderStyle = CanvasBorderStyle.none,
this.borderWidth = 1.0,
this.borderColor = const Color(0xFFFFFFFF),
this.shadowBlur = 0,
this.shadowOpacity = 0,
this.shadowColor = const Color(0xFF000000),
this.shadowSpread = 0,
this.shadowOffsetX = 0,
this.shadowOffsetY = 4,
this.innerShadowBlur = 0,
this.innerShadowOpacity = 0,
this.innerShadowOffsetX = 0,
this.innerShadowOffsetY = 2,
this.stackLayers = 1,
this.stackDistance = 8,
this.stackPosition = CanvasStackPosition.bottomRight,
this.outerMargin = 0,
});
factory CanvasStyleModel.fromJson(Map<String, dynamic> json) {
return CanvasStyleModel._(
borderRadius: (json['borderRadius'] as num?)?.toDouble() ?? 0,
useIndependentRadius:
(json['useIndependentRadius'] as bool?) ?? false,
borderRadiusTL:
(json['borderRadiusTL'] as num?)?.toDouble() ??
(json['borderRadius'] as num?)?.toDouble() ??
0,
borderRadiusTR:
(json['borderRadiusTR'] as num?)?.toDouble() ??
(json['borderRadius'] as num?)?.toDouble() ??
0,
borderRadiusBL:
(json['borderRadiusBL'] as num?)?.toDouble() ??
(json['borderRadius'] as num?)?.toDouble() ??
0,
borderRadiusBR:
(json['borderRadiusBR'] as num?)?.toDouble() ??
(json['borderRadius'] as num?)?.toDouble() ??
0,
borderStyle: CanvasBorderStyle.values.firstWhere(
(e) => e.name == json['borderStyle'],
orElse: () => CanvasBorderStyle.none,
),
borderWidth: (json['borderWidth'] as num?)?.toDouble() ?? 1.0,
borderColor: _colorFromJson(json['borderColor']),
shadowBlur: (json['shadowBlur'] as num?)?.toDouble() ?? 0,
shadowOpacity: (json['shadowOpacity'] as num?)?.toDouble() ?? 0,
shadowColor: json.containsKey('shadowColor')
? _colorFromJson(json['shadowColor'])
: const Color(0xFF000000),
shadowSpread: (json['shadowSpread'] as num?)?.toDouble() ?? 0,
shadowOffsetX: (json['shadowOffsetX'] as num?)?.toDouble() ?? 0,
shadowOffsetY: (json['shadowOffsetY'] as num?)?.toDouble() ?? 4,
innerShadowBlur:
(json['innerShadowBlur'] as num?)?.toDouble() ?? 0,
innerShadowOpacity:
(json['innerShadowOpacity'] as num?)?.toDouble() ?? 0,
innerShadowOffsetX:
(json['innerShadowOffsetX'] as num?)?.toDouble() ?? 0,
innerShadowOffsetY:
(json['innerShadowOffsetY'] as num?)?.toDouble() ?? 2,
stackLayers: (json['stackLayers'] as num?)?.toInt() ?? 1,
stackDistance: (json['stackDistance'] as num?)?.toDouble() ?? 8,
stackPosition: CanvasStackPosition.values.firstWhere(
(e) => e.name == json['stackPosition'],
orElse: () => CanvasStackPosition.bottomRight,
),
outerMargin: (json['outerMargin'] as num?)?.toDouble() ?? 0,
);
}
Map<String, dynamic> toJson() => {
'borderRadius': borderRadius,
'useIndependentRadius': useIndependentRadius,
'borderRadiusTL': borderRadiusTL,
'borderRadiusTR': borderRadiusTR,
'borderRadiusBL': borderRadiusBL,
'borderRadiusBR': borderRadiusBR,
'borderStyle': borderStyle.name,
'borderWidth': borderWidth,
'borderColor': borderColor.toARGB32(),
'shadowBlur': shadowBlur,
'shadowOpacity': shadowOpacity,
'shadowColor': shadowColor.toARGB32(),
'shadowSpread': shadowSpread,
'shadowOffsetX': shadowOffsetX,
'shadowOffsetY': shadowOffsetY,
'innerShadowBlur': innerShadowBlur,
'innerShadowOpacity': innerShadowOpacity,
'innerShadowOffsetX': innerShadowOffsetX,
'innerShadowOffsetY': innerShadowOffsetY,
'stackLayers': stackLayers,
'stackDistance': stackDistance,
'stackPosition': stackPosition.name,
'outerMargin': outerMargin,
};
CanvasStyleModel copyWith({
double? borderRadius,
bool? useIndependentRadius,
double? borderRadiusTL,
double? borderRadiusTR,
double? borderRadiusBL,
double? borderRadiusBR,
CanvasBorderStyle? borderStyle,
double? borderWidth,
Color? borderColor,
double? shadowBlur,
double? shadowOpacity,
Color? shadowColor,
double? shadowSpread,
double? shadowOffsetX,
double? shadowOffsetY,
double? innerShadowBlur,
double? innerShadowOpacity,
double? innerShadowOffsetX,
double? innerShadowOffsetY,
int? stackLayers,
double? stackDistance,
CanvasStackPosition? stackPosition,
double? outerMargin,
}) {
return CanvasStyleModel._(
borderRadius: borderRadius ?? this.borderRadius,
useIndependentRadius:
useIndependentRadius ?? this.useIndependentRadius,
borderRadiusTL: borderRadiusTL ?? this.borderRadiusTL,
borderRadiusTR: borderRadiusTR ?? this.borderRadiusTR,
borderRadiusBL: borderRadiusBL ?? this.borderRadiusBL,
borderRadiusBR: borderRadiusBR ?? this.borderRadiusBR,
borderStyle: borderStyle ?? this.borderStyle,
borderWidth: borderWidth ?? this.borderWidth,
borderColor: borderColor ?? this.borderColor,
shadowBlur: shadowBlur ?? this.shadowBlur,
shadowOpacity: shadowOpacity ?? this.shadowOpacity,
shadowColor: shadowColor ?? this.shadowColor,
shadowSpread: shadowSpread ?? this.shadowSpread,
shadowOffsetX: shadowOffsetX ?? this.shadowOffsetX,
shadowOffsetY: shadowOffsetY ?? this.shadowOffsetY,
innerShadowBlur: innerShadowBlur ?? this.innerShadowBlur,
innerShadowOpacity: innerShadowOpacity ?? this.innerShadowOpacity,
innerShadowOffsetX: innerShadowOffsetX ?? this.innerShadowOffsetX,
innerShadowOffsetY: innerShadowOffsetY ?? this.innerShadowOffsetY,
stackLayers: stackLayers ?? this.stackLayers,
stackDistance: stackDistance ?? this.stackDistance,
stackPosition: stackPosition ?? this.stackPosition,
outerMargin: outerMargin ?? this.outerMargin,
);
}
static Color _colorFromJson(dynamic value) {
if (value is int) return Color(value);
if (value is String) {
final hex = value.replaceFirst('#', '');
return Color(int.parse('FF$hex', radix: 16));
}
return const Color(0xFFFFFFFF);
}
/// 导出为JSON字符串 — 用于样式分享
String exportToJsonString() => const JsonEncoder.withIndent(' ').convert(toJson());
/// 从JSON字符串导入样式
static CanvasStyleModel? tryFromJsonString(String jsonStr) {
try {
final map = jsonDecode(jsonStr) as Map<String, dynamic>;
return CanvasStyleModel.fromJson(map);
} catch (_) {
return null;
}
}
@override
String toString() => 'CanvasStyleModel(${jsonEncode(toJson())})';
}