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
25 changes: 24 additions & 1 deletion lib/cubit/canvas_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -360,10 +360,33 @@ class CanvasCubit extends Cubit<CanvasState> {
_updateState(textItems: updatedItems);
}

// method to moveText and emit changes
// method to moveText and emit changes (without history)
void moveText(int index, double x, double y) {
final updatedItems = List<TextItem>.from(state.textItems);
updatedItems[index] = updatedItems[index].copyWith(x: x, y: y);
emit(state.copyWith(textItems: updatedItems));
}

// method to moveText and add to history (called when drag ends)
void moveTextWithHistory(int index, double x, double y) {
final updatedItems = List<TextItem>.from(state.textItems);
updatedItems[index] = updatedItems[index].copyWith(x: x, y: y);
_updateState(textItems: updatedItems);
}

// method to rotate text and emit changes (without history)
void rotateText(int index, double rotation) {
if (index < 0 || index >= state.textItems.length) return;
final updatedItems = List<TextItem>.from(state.textItems);
updatedItems[index] = updatedItems[index].copyWith(rotation: rotation);
emit(state.copyWith(textItems: updatedItems));
}

// method to rotate text and add to history (called when rotation ends)
void rotateTextWithHistory(int index, double rotation) {
if (index < 0 || index >= state.textItems.length) return;
final updatedItems = List<TextItem>.from(state.textItems);
updatedItems[index] = updatedItems[index].copyWith(rotation: rotation);
_updateState(textItems: updatedItems);
}

Expand Down
32 changes: 20 additions & 12 deletions lib/models/text_item_model.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import 'dart:ui';
import 'package:flutter/material.dart';

class TextItem {
Expand All @@ -14,13 +13,16 @@ class TextItem {
final bool isHighlighted;
final Color? highlightColor;
final TextAlign textAlign;

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

// Rotation in radians
final double rotation;

TextItem({
required this.text,
required this.x,
Expand All @@ -38,6 +40,7 @@ class TextItem {
this.shadowColor = Colors.black,
this.shadowBlurRadius = 4.0,
this.shadowOffset = const Offset(2.0, 2.0),
this.rotation = 0.0,
});

TextItem copyWith({
Expand All @@ -57,6 +60,7 @@ class TextItem {
Color? shadowColor,
double? shadowBlurRadius,
Offset? shadowOffset,
double? rotation,
}) {
return TextItem(
text: text ?? this.text,
Expand All @@ -75,6 +79,7 @@ class TextItem {
shadowColor: shadowColor ?? this.shadowColor,
shadowBlurRadius: shadowBlurRadius ?? this.shadowBlurRadius,
shadowOffset: shadowOffset ?? this.shadowOffset,
rotation: rotation ?? this.rotation,
);
}

Expand All @@ -87,7 +92,7 @@ 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)}; '
: '';
Expand Down Expand Up @@ -117,6 +122,7 @@ class TextItem {
'shadowBlurRadius': shadowBlurRadius,
'shadowOffsetDx': shadowOffset.dx,
'shadowOffsetDy': shadowOffset.dy,
'rotation': rotation,
};
}

Expand All @@ -134,17 +140,19 @@ class TextItem {
isUnderlined: json['isUnderlined'] ?? false,
textAlign: TextAlign.values[json['textAlign'] ?? 0],
isHighlighted: json['isHighlighted'] ?? false,
highlightColor: json['highlightColor'] != null
? Color(json['highlightColor'])
: null,
highlightColor:
json['highlightColor'] != null ? Color(json['highlightColor']) : null,
hasShadow: json['hasShadow'] ?? false,
shadowColor: json['shadowColor'] != null
? Color(json['shadowColor'])
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),
shadowOffset:
json['shadowOffsetDx'] != null && json['shadowOffsetDy'] != null
? Offset(json['shadowOffsetDx'].toDouble(),
json['shadowOffsetDy'].toDouble())
: const Offset(2.0, 2.0),
rotation: json['rotation']?.toDouble() ?? 0.0,
);
}
}
}
62 changes: 35 additions & 27 deletions lib/ui/screens/canvas_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:texterra/ui/screens/saved_pages.dart';
import '../../constants/color_constants.dart';
import '../../cubit/canvas_cubit.dart';
import '../../cubit/canvas_state.dart';
import '../widgets/editable_text_widget.dart';
import '../widgets/text_box_widget.dart';
import '../widgets/font_controls.dart';
import '../widgets/background_color_tray.dart';
import '../widgets/background_options_sheet.dart';
Expand Down Expand Up @@ -270,7 +270,7 @@ class CanvasScreen extends StatelessWidget {
top: state.textItems[index].y,
child: IgnorePointer(
ignoring: state.isDrawingMode,
child: _DraggableText(
child: _DraggableTextBox(
key: ValueKey('text_item_$index'),
index: index,
textItem: state.textItems[index],
Expand Down Expand Up @@ -529,66 +529,74 @@ class CanvasScreen extends StatelessWidget {
}
}

class _DraggableText extends StatefulWidget {
class _DraggableTextBox extends StatefulWidget {
final int index;
final TextItem textItem;
final bool isSelected;

const _DraggableText({
const _DraggableTextBox({
super.key,
required this.index,
required this.textItem,
required this.isSelected,
});

@override
State<_DraggableText> createState() => _DraggableTextState();
State<_DraggableTextBox> createState() => _DraggableTextBoxState();
}

class _DraggableTextState extends State<_DraggableText>
with AutomaticKeepAliveClientMixin {
class _DraggableTextBoxState extends State<_DraggableTextBox> {
Offset? _startPosition;
Offset? _dragStartPosition;

@override
bool get wantKeepAlive => true;
bool _isRotating = false;

@override
Widget build(BuildContext context) {
super.build(context);
return GestureDetector(
onTap: () {
// Select this text item when tapped
context.read<CanvasCubit>().selectText(widget.index);
},
onPanStart: (details) {
// Select this text item when starting to drag
context.read<CanvasCubit>().selectText(widget.index);
_startPosition = Offset(widget.textItem.x, widget.textItem.y);
_dragStartPosition = details.localPosition;
if (!_isRotating) {
context.read<CanvasCubit>().selectText(widget.index);
_startPosition = Offset(widget.textItem.x, widget.textItem.y);
_dragStartPosition = details.localPosition;
}
},
onPanUpdate: (details) {
if (_startPosition != null && _dragStartPosition != null) {
if (!_isRotating &&
_startPosition != null &&
_dragStartPosition != null) {
final delta = details.localPosition - _dragStartPosition!;
final newPosition = _startPosition! + delta;

// Update position in real-time during drag
context.read<CanvasCubit>().moveText(
widget.index,
newPosition.dx,
newPosition.dy,
);
}
},
onPanEnd: (_) {
_startPosition = null;
_dragStartPosition = null;
onPanEnd: (details) {
if (!_isRotating &&
_startPosition != null &&
_dragStartPosition != null) {
final delta = details.localPosition - _dragStartPosition!;
final newPosition = _startPosition! + delta;
context.read<CanvasCubit>().moveTextWithHistory(
widget.index,
newPosition.dx,
newPosition.dy,
);
_startPosition = null;
_dragStartPosition = null;
}
},
behavior: HitTestBehavior.opaque,
child: EditableTextWidget(
child: TextBoxWidget(
index: widget.index,
textItem: widget.textItem,
isSelected: widget.isSelected,
onRotationStateChanged: (isRotating) {
setState(() {
_isRotating = isRotating;
});
},
),
);
}
Expand Down
Loading