Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 126 additions & 2 deletions lib/cubit/canvas_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,116 @@ class CanvasCubit extends Cubit<CanvasState> {
}
}

// Toggle text shadow on/off
void toggleTextShadow(int index) {
if (index < 0 || index >= state.textItems.length) return;

final updatedItems = List<TextItem>.from(state.textItems);
updatedItems[index] = updatedItems[index].copyWith(
hasShadow: !updatedItems[index].hasShadow,
);
_updateState(textItems: updatedItems);
}

// Change shadow color
void changeShadowColor(int index, Color color) {
if (index < 0 || index >= state.textItems.length) return;

final updatedItems = List<TextItem>.from(state.textItems);
updatedItems[index] = updatedItems[index].copyWith(
hasShadow: true, // Automatically enable shadow when changing color
shadowColor: color,
);
_updateState(textItems: updatedItems);
}

// Change shadow blur radius
void changeShadowBlur(int index, double blurRadius) {
if (index < 0 || index >= state.textItems.length) return;

final updatedItems = List<TextItem>.from(state.textItems);
updatedItems[index] = updatedItems[index].copyWith(
hasShadow: true,
shadowBlurRadius: blurRadius.clamp(0.0, 50.0),
);
_updateState(textItems: updatedItems);
}

// Change shadow offset
void changeShadowOffset(int index, Offset offset) {
if (index < 0 || index >= state.textItems.length) return;

final updatedItems = List<TextItem>.from(state.textItems);
updatedItems[index] = updatedItems[index].copyWith(
hasShadow: true,
shadowOffset: offset,
);
_updateState(textItems: updatedItems);
}

// Apply preset shadow styles
void applyShadowPreset(int index, ShadowPreset preset) {
if (index < 0 || index >= state.textItems.length) return;

final updatedItems = List<TextItem>.from(state.textItems);

switch (preset) {
case ShadowPreset.soft:
updatedItems[index] = updatedItems[index].copyWith(
hasShadow: true,
shadowColor: Colors.black.withValues(alpha: 0.3),
shadowBlurRadius: 8.0,
shadowOffset: const Offset(2.0, 2.0),
);
break;
case ShadowPreset.medium:
updatedItems[index] = updatedItems[index].copyWith(
hasShadow: true,
shadowColor: Colors.black.withValues(alpha: 0.5),
shadowBlurRadius: 6.0,
shadowOffset: const Offset(3.0, 3.0),
);
break;
case ShadowPreset.hard:
updatedItems[index] = updatedItems[index].copyWith(
hasShadow: true,
shadowColor: Colors.black.withValues(alpha: 0.7),
shadowBlurRadius: 2.0,
shadowOffset: const Offset(4.0, 4.0),
);
break;
case ShadowPreset.glow:
updatedItems[index] = updatedItems[index].copyWith(
hasShadow: true,
shadowColor: Colors.white.withValues(alpha: 0.8),
shadowBlurRadius: 15.0,
shadowOffset: Offset.zero,
);
break;
case ShadowPreset.coloredGlow:
// Use the text color for the glow
final textColor = updatedItems[index].color;
updatedItems[index] = updatedItems[index].copyWith(
hasShadow: true,
shadowColor: textColor.withValues(alpha: 0.6),
shadowBlurRadius: 12.0,
shadowOffset: Offset.zero,
);
break;
case ShadowPreset.outline:
updatedItems[index] = updatedItems[index].copyWith(
hasShadow: true,
shadowColor: Colors.black,
shadowBlurRadius: 1.0,
shadowOffset: const Offset(1.0, 1.0),
);
break;
}

_updateState(textItems: updatedItems);
CustomSnackbar.showSuccess('${preset.name} shadow applied');
}

// Add to your CanvasCubit class
void fillCanvas(Color fillColor) {
// Create a special "fill" path that covers the entire canvas
Expand Down Expand Up @@ -116,9 +226,13 @@ class CanvasCubit extends Cubit<CanvasState> {
fontWeight: FontWeight.normal,
fontFamily: 'Roboto',
isUnderlined: false,
isHighlighted: false, // Add this line
highlightColor: null, // Add this line
isHighlighted: false,
highlightColor: null,
color: ColorConstants.uiWhite,
hasShadow: false, // Reset shadow
shadowColor: Colors.black,
shadowBlurRadius: 4.0,
shadowOffset: const Offset(2.0, 2.0),
);
_updateState(textItems: updatedItems);
}
Expand Down Expand Up @@ -909,3 +1023,13 @@ class CanvasCubit extends Cubit<CanvasState> {
CustomSnackbar.showInfo('Last stroke undone');
}
}

// Enum for shadow presets
enum ShadowPreset {
soft,
medium,
hard,
glow,
coloredGlow,
outline,
}
85 changes: 79 additions & 6 deletions lib/models/text_item_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ class TextItem {
final Color color;
final bool isHighlighted;
final Color? highlightColor;
final TextAlign textAlign;
final TextAlign textAlign;

// Shadow properties
final bool hasShadow;
final Color shadowColor;
final double shadowBlurRadius;
final Offset shadowOffset;

TextItem({
required this.text,
Expand All @@ -27,7 +33,11 @@ class TextItem {
required this.color,
this.isHighlighted = false,
this.highlightColor,
this.textAlign = TextAlign.left,
this.textAlign = TextAlign.left,
this.hasShadow = false,
this.shadowColor = Colors.black,
this.shadowBlurRadius = 4.0,
this.shadowOffset = const Offset(2.0, 2.0),
});

TextItem copyWith({
Expand All @@ -42,7 +52,11 @@ class TextItem {
Color? color,
bool? isHighlighted,
Color? highlightColor,
TextAlign? textAlign,
TextAlign? textAlign,
bool? hasShadow,
Color? shadowColor,
double? shadowBlurRadius,
Offset? shadowOffset,
}) {
return TextItem(
text: text ?? this.text,
Expand All @@ -56,7 +70,11 @@ class TextItem {
color: color ?? this.color,
isHighlighted: isHighlighted ?? this.isHighlighted,
highlightColor: highlightColor ?? this.highlightColor,
textAlign: textAlign ?? this.textAlign,
textAlign: textAlign ?? this.textAlign,
hasShadow: hasShadow ?? this.hasShadow,
shadowColor: shadowColor ?? this.shadowColor,
shadowBlurRadius: shadowBlurRadius ?? this.shadowBlurRadius,
shadowOffset: shadowOffset ?? this.shadowOffset,
);
}

Expand All @@ -69,9 +87,64 @@ class TextItem {
final backgroundColor = isHighlighted && highlightColor != null
? 'background-color: #${highlightColor!.toARGB32().toRadixString(16).padLeft(8, '0').substring(2)}; '
: '';

final textShadow = hasShadow
? 'text-shadow: ${shadowOffset.dx}px ${shadowOffset.dy}px ${shadowBlurRadius}px #${shadowColor.toARGB32().toRadixString(16).padLeft(8, '0').substring(2)}; '
: '';

final style =
'font-size: ${fontSize}px; font-family: $fontFamily; color: $cssColor; font-weight: $cssFontWeight; font-style: $cssFontStyle; $textDecoration$backgroundColor';
'font-size: ${fontSize}px; font-family: $fontFamily; color: $cssColor; font-weight: $cssFontWeight; font-style: $cssFontStyle; $textDecoration$backgroundColor$textShadow';
return '<span style="$style">$text</span>';
}
}

// Convert to map for saving
Map<String, dynamic> toJson() {
return {
'text': text,
'x': x,
'y': y,
'fontSize': fontSize,
'fontWeight': fontWeight.index,
'fontStyle': fontStyle.index,
'color': color.toARGB32(),
'fontFamily': fontFamily,
'isUnderlined': isUnderlined,
'textAlign': textAlign.index,
'isHighlighted': isHighlighted,
'highlightColor': highlightColor?.toARGB32(),
'hasShadow': hasShadow,
'shadowColor': shadowColor.toARGB32(),
'shadowBlurRadius': shadowBlurRadius,
'shadowOffsetDx': shadowOffset.dx,
'shadowOffsetDy': shadowOffset.dy,
};
}

// Create from map when loading
factory TextItem.fromJson(Map<String, dynamic> json) {
return TextItem(
text: json['text'],
x: json['x'].toDouble(),
y: json['y'].toDouble(),
fontSize: json['fontSize'].toDouble(),
fontWeight: FontWeight.values[json['fontWeight']],
fontStyle: FontStyle.values[json['fontStyle']],
color: Color(json['color']),
fontFamily: json['fontFamily'],
isUnderlined: json['isUnderlined'] ?? false,
textAlign: TextAlign.values[json['textAlign'] ?? 0],
isHighlighted: json['isHighlighted'] ?? false,
highlightColor: json['highlightColor'] != null
? Color(json['highlightColor'])
: null,
hasShadow: json['hasShadow'] ?? false,
shadowColor: json['shadowColor'] != null
? Color(json['shadowColor'])
: Colors.black,
shadowBlurRadius: json['shadowBlurRadius']?.toDouble() ?? 4.0,
shadowOffset: json['shadowOffsetDx'] != null && json['shadowOffsetDy'] != null
? Offset(json['shadowOffsetDx'].toDouble(), json['shadowOffsetDy'].toDouble())
: const Offset(2.0, 2.0),
);
}
}
61 changes: 36 additions & 25 deletions lib/ui/widgets/editable_text_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,39 @@ class EditableTextWidget extends StatelessWidget {
context.read<CanvasCubit>().editText(index, result);
}
},
// 4. onDoubleTap handler is now gone
child:SizedBox(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: Text(
textItem.text,
textAlign: textItem.textAlign,
style: GoogleFonts.getFont(
textItem.fontFamily,
fontStyle: textItem.fontStyle,
fontWeight: textItem.fontWeight,
fontSize: textItem.fontSize,
decoration: textItem.isUnderlined
? TextDecoration.underline
: TextDecoration.none,
color: textItem.color,
backgroundColor:
textItem.isHighlighted && textItem.highlightColor != null
? textItem.highlightColor
: (isSelected
? ColorConstants.highlightYellow.withAlpha((0.3 * 255).toInt())
: null),
textItem.text,
textAlign: textItem.textAlign,
style: GoogleFonts.getFont(
textItem.fontFamily,
fontStyle: textItem.fontStyle,
fontWeight: textItem.fontWeight,
fontSize: textItem.fontSize,
decoration: textItem.isUnderlined
? TextDecoration.underline
: TextDecoration.none,
color: textItem.color,
backgroundColor:
textItem.isHighlighted && textItem.highlightColor != null
? textItem.highlightColor
: (isSelected
? ColorConstants.highlightYellow
.withValues(alpha: 0.3)
: null),
shadows: textItem.hasShadow
? [
Shadow(
color: textItem.shadowColor,
offset: textItem.shadowOffset,
blurRadius: textItem.shadowBlurRadius,
),
]
: null,
),
),
),)
),
);
}
}
Expand Down Expand Up @@ -108,8 +118,8 @@ class EditTextDialog extends StatelessWidget {
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide:
const BorderSide(color: ColorConstants.dialogPurple, width: 2),
borderSide: const BorderSide(
color: ColorConstants.dialogPurple, width: 2),
),
),
),
Expand All @@ -120,7 +130,7 @@ class EditTextDialog extends StatelessWidget {
children: [
TextButton(
onPressed: () => Navigator.pop(context, '_delete_'),
child: Text(
child: const Text(
'Remove',
style: TextStyle(color: ColorConstants.gray600),
),
Expand All @@ -137,7 +147,8 @@ class EditTextDialog extends StatelessWidget {
}
},
style: ElevatedButton.styleFrom(
backgroundColor: ColorConstants.dialogPurple.withAlpha((0.2 * 255).toInt()),
backgroundColor:
ColorConstants.dialogPurple.withValues(alpha: 0.2),
foregroundColor: ColorConstants.dialogPurple,
elevation: 0,
padding: const EdgeInsets.symmetric(
Expand All @@ -157,4 +168,4 @@ class EditTextDialog extends StatelessWidget {
),
);
}
}
}
Loading
Loading