Skip to content

Commit

Permalink
Recode shape painter, closes #476
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeDoctorDE committed Aug 3, 2023
1 parent d4f2a5f commit e3e74ae
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 160 deletions.
110 changes: 110 additions & 0 deletions app/lib/handlers/handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,113 @@ Set<int> _executeRayCast(_RayCastParams params) {
.map((e) => e.key)
.toSet();
}

abstract class PastingHandler<T> extends Handler<T> {
Offset? _firstPos;
Offset? _secondPos;
bool _aspectRatio = false, _center = false;
String _currentLayer = '';

PastingHandler(super.data);

@override
List<Renderer> createForegrounds(CurrentIndexCubit currentIndexCubit,
NoteData document, DocumentPage page, DocumentInfo info,
[Area? currentArea]) =>
[
if (_firstPos != null && _secondPos != null)
...getTransformed().map((e) => Renderer.fromInstance(e)).toList(),
];

List<PadElement> transformElements(Rect rect, String layer);

List<PadElement> getTransformed() {
final first = _firstPos;
final second = _secondPos;
if (first == null || second == null) return [];
var top = min(first.dy, second.dy);
var left = min(first.dx, second.dx);
var bottom = max(first.dy, second.dy);
var right = max(first.dx, second.dx);
var width = right - left;
var height = bottom - top;
if (_aspectRatio) {
final largest = max(width, height);
width = largest;
height = largest;
right = left + width;
bottom = top + height;
}
if (constraintedHeight != 0) {
height = constraintedHeight;
bottom = top + height;
}
if (constraintedWidth != 0) {
width = constraintedWidth;
right = left + width;
}
if (constraintedAspectRatio != 0 &&
(constraintedHeight == 0 || constraintedWidth == 0)) {
if (constraintedHeight != 0) {
height = constraintedHeight;
width = constraintedAspectRatio * height;
right = left + width;
} else if (constraintedWidth != 0) {
width = constraintedWidth;
height = width / constraintedAspectRatio;
bottom = top + height;
} else {
final largest = max(width, height);
width = constraintedAspectRatio * largest;
height = largest / constraintedAspectRatio;
right = left + width;
bottom = top + height;
}
}
if (_center) {
top -= height;
left -= width;
}
final rect = Rect.fromLTRB(left, top, right, bottom);
if (rect.isEmpty) return [];
return transformElements(rect, _currentLayer);
}

void _updateElement(PointerEvent event, EventContext context,
[bool first = false]) {
final transform = context.getCameraTransform();
if (first) _firstPos = transform.localToGlobal(event.localPosition);
_secondPos = transform.localToGlobal(event.localPosition);
_aspectRatio = context.isCtrlPressed;
_center = context.isShiftPressed;
_currentLayer = context.getState()?.currentLayer ?? '';

context.refresh();
}

@override
void onPointerDown(PointerDownEvent event, EventContext context) =>
_updateElement(event, context, true);
@override
void onPointerMove(PointerMoveEvent event, EventContext context) =>
_updateElement(event, context);

@override
void onPointerUp(PointerUpEvent event, EventContext context) {
final bloc = context.getDocumentBloc();
final state = bloc.state;
if (state is! DocumentLoadSuccess) return;
final elements = getTransformed();
if (elements.isEmpty) return;
final current = List<PadElement>.from(elements);
bloc.add(ElementsCreated(current));
bloc.bake();
_firstPos = null;
_secondPos = null;
context.refresh();
}

double get constraintedAspectRatio => 0;
double get constraintedWidth => 0;
double get constraintedHeight => 0;
}
180 changes: 20 additions & 160 deletions app/lib/handlers/shape.dart
Original file line number Diff line number Diff line change
@@ -1,167 +1,8 @@
part of 'handler.dart';

class ShapeHandler extends Handler {
final Map<int, ShapeElement> elements = {};
final List<ShapeElement> submittedElements = [];

class ShapeHandler extends PastingHandler<ShapePainter> {
ShapeHandler(super.data);

@override
List<Renderer> createForegrounds(CurrentIndexCubit currentIndexCubit,
NoteData document, DocumentPage page, DocumentInfo info,
[Area? currentArea]) =>
elements.values.map((e) => ShapeRenderer(e)).toList()
..addAll(submittedElements.map((e) => ShapeRenderer(e)));

@override
void resetInput(DocumentBloc bloc) {
elements.clear();
submittedElements.clear();
}

@override
void onPointerUp(PointerUpEvent event, EventContext context) {
addShape(context.buildContext, event.pointer, event.localPosition,
event.pressure, event.kind,
refresh: false);
submitElement(context.viewportSize, context.buildContext, event.pointer);
}

void _setRect(Offset nextPosition, int index) {
final element = elements[index];
if (element == null) return;
final currentRect = Rect.fromPoints(
element.firstPosition.toOffset(), element.secondPosition.toOffset());
double width = 0, height = 0;
final nextWidth = nextPosition.dx - currentRect.left;
final nextHeight = nextPosition.dy - currentRect.top;
if (data.constrainedHeight != 0 && data.constrainedWidth != 0) {
width = data.constrainedWidth;
height = data.constrainedHeight;
}
if (data.constrainedAspectRatio != 0) {
if (data.constrainedHeight != 0) {
height = data.constrainedHeight;
width = data.constrainedAspectRatio * height;
} else if (data.constrainedWidth != 0) {
width = data.constrainedWidth;
height = width / data.constrainedAspectRatio;
} else {
final largest = nextHeight > nextWidth ? nextWidth : nextHeight;
width = data.constrainedAspectRatio * largest;
height = largest / data.constrainedAspectRatio;
}
} else {
if (data.constrainedHeight != 0) {
height = data.constrainedHeight;
width = nextWidth;
} else if (data.constrainedWidth != 0) {
width = data.constrainedWidth;
height = nextHeight;
} else {
width = nextWidth;
height = nextHeight;
}
}
final secondPosition =
Offset(currentRect.left + width, currentRect.top + height);

elements[index] = element.copyWith(
firstPosition: element.firstPosition,
secondPosition: secondPosition.toPoint());
}

ShapeElement _normalizeElement(ShapeElement element) {
if (element.property.shape is LineShape) return element;
var firstX = element.firstPosition.x;
var firstY = element.firstPosition.y;
var secondX = element.secondPosition.x;
var secondY = element.secondPosition.y;
if (firstX > secondX) {
final temp = firstX;
firstX = secondX;
secondX = temp;
}
if (firstY > secondY) {
final temp = firstY;
firstY = secondY;
secondY = temp;
}
return element.copyWith(
firstPosition: Point(firstX, firstY),
secondPosition: Point(secondX, secondY),
);
}

void submitElement(Size viewportSize, BuildContext context, int index) {
final bloc = context.read<DocumentBloc>();
var element = elements.remove(index);
if (element == null) return;
submittedElements.add(_normalizeElement(element));
if (elements.isEmpty) {
final current = List<PadElement>.from(submittedElements);
bloc.add(ElementsCreated(current));
bloc.bake();
submittedElements.clear();
}
bloc.refresh();
}

void addShape(BuildContext context, int pointer, Offset localPosition,
double pressure, PointerDeviceKind kind,
{bool refresh = true, bool shouldCreate = false}) {
final bloc = context.read<DocumentBloc>();
final transform = context.read<TransformCubit>().state;
final state = bloc.state as DocumentLoadSuccess;
final currentIndexCubit = context.read<CurrentIndexCubit>();
final viewport = currentIndexCubit.state.cameraViewport;
localPosition = viewport.tool.getGridPosition(
localPosition, state.page, state.info, currentIndexCubit);
final globalPosition = transform.localToGlobal(localPosition);
final settings = context.read<SettingsCubit>().state;
final penOnlyInput = settings.penOnlyInput;
if (penOnlyInput && kind != PointerDeviceKind.stylus) {
return;
}
double zoom = data.zoomDependent ? transform.size : 1;

final createNew = !elements.containsKey(pointer);

if (createNew && !shouldCreate) return;
if (createNew) {
elements[pointer] = ShapeElement(
layer: state.currentLayer,
firstPosition: globalPosition.toPoint(),
secondPosition: globalPosition.toPoint(),
property: data.property.copyWith(
strokeWidth: data.property.strokeWidth / zoom,
),
);
}
_setRect(globalPosition, pointer);
if (refresh) bloc.refresh();
}

@override
void onPointerDown(PointerDownEvent event, EventContext context) {
if (context.getCurrentIndex().moveEnabled) {
elements.clear();
context.refresh();
return;
}
addShape(context.buildContext, event.pointer, event.localPosition,
event.pressure, event.kind,
shouldCreate: true);
}

@override
void onPointerMove(PointerMoveEvent event, EventContext context) => addShape(
context.buildContext,
event.pointer,
event.localPosition,
event.pressure,
event.kind);

@override
PreferredSizeWidget getToolbar(DocumentBloc bloc) => ColorToolbarView(
color: data.property.color,
Expand All @@ -173,4 +14,23 @@ class ShapeHandler extends Handler {
}));
},
);

@override
List<PadElement> transformElements(Rect rect, String layer) {
return [
ShapeElement(
firstPosition: rect.topLeft.toPoint(),
secondPosition: rect.bottomRight.toPoint(),
property: data.property,
layer: layer,
),
];
}

@override
double get constraintedAspectRatio => data.constrainedAspectRatio;
@override
double get constraintedHeight => data.constrainedHeight;
@override
double get constraintedWidth => data.constrainedWidth;
}
2 changes: 2 additions & 0 deletions fastlane/metadata/android/en-US/changelogs/70.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* Save sort state ([#469](https://github.com/LinwoodDev/Butterfly/issues/469))
* Save deleted images in cache
* Fix pasting images not working ([#465](https://github.com/LinwoodDev/Butterfly/issues/465))
* Recode shape painter
* Add center, 1:1 features ([#476](https://github.com/LinwoodDev/Butterfly/issues/476))
* Improve save state icons
* Improve import painter position
* Improve hit calculation on single point pen elements
Expand Down

0 comments on commit e3e74ae

Please sign in to comment.