Let's say I have a container with a transform applied to it. Because of the transform it's drawn to a slightly shifted position on screen (while its layout position remain the original one, without the transform).
Eg:
Here I have two containers in a stack. Blue one is without transform. And red one is with transform.
https://dartpad.dev/?id=5fc0a808ea606a97216bb06df0824b77&null_safety=true
How do I determine actual position of red one on screen? Since transform is applied at the paint time (not layout time), the usual ways of getting renderobject and using it to obtain position relative to ancestor doesn't work. It just gives same rect for both.
Blue Rect Rect.fromLTRB(0.0, 0.0, 400.0, 400.0)
Red Rect Rect.fromLTRB(0.0, 0.0, 400.0, 400.0)
Is there any way to get the actual position of red/transformed widget relative to ancestor/screen?
Finally found a way. You just need to find a child renderobject which is actually positioning things.
// function to get relative rect from ancestor
Rect getRenderBoxRect(RenderBox rb, RenderObject ancestor) {
final size = rb.size;
final topLeft = rb.localToGlobal(Offset.zero, ancestor: ancestor);
final bottomRight =
rb.localToGlobal(Offset(size.width, size.height), ancestor: ancestor);
return Rect.fromPoints(topLeft, bottomRight);
}
// function to get specific type of child renderobject
RenderObject? findRenderObjectByType(RenderObject parent, Type type) {
RenderObject? found;
parent.visitChildren((child) {
if (found != null) {
return;
}
if (child.runtimeType == type) {
found = child;
return;
}
found ??= findRenderObjectByType(child, type);
});
return found;
}
// then calculate rect in post frame callback using global key
Widget build(BuildContext context) {
WidgetsBinding.instance?.addPostFrameCallback((duration) {
final ancestor = stackKey.currentContext!.findRenderObject()!;
final redRo = redKey.currentContext!.findRenderObject()!;
final redRoT = findRenderObjectByType(redRo, RenderPositionedBox);
if (redRoT != null) {
print("RECT ${getRenderBoxRect(redRoT as RenderBox, ancestor)}");
}
}
}
Prints:
RECT Rect.fromLTRB(200.0, 0.0, 683.0, 500.0)
This gives you the rect for real position (black border in the following image):
Related
From this, I would expect the following RevoluteJoint bodyB, to be higher up than the blue box on the y-axe - setting
revoluteJointDef.localAnchorA?
flame_forge2d: ^0.11.0
class Box extends BaseBox { // just a BodyComponent for same shape
BaseBox? boxB;
Box(position, paint, [this.boxB]) : super(position, paint);
#override
Body createBody() {
super.createBody();
if (this.boxB != null) {
paint.color = Colors.blue;
final revoluteJointDef = RevoluteJointDef();
revoluteJointDef.bodyA = body;
revoluteJointDef.bodyB = this.boxB!.body;
revoluteJointDef.localAnchorA.setFrom(Vector2(1, 1));
RevoluteJoint joint = RevoluteJoint(revoluteJointDef);
world.createJoint(joint);
}
return body;
}
}
I would expect it to be like this:
In flame_forge2d 0.11.0 the coordinate system was flipped on the y-axis to be the same coordinate system as Flame, so larger y-values will result in further down, instead of further up like it is in normal forge2d.
Just negate the y-value of the anchor and you should get your intended result:
revoluteJointDef.localAnchorA.setFrom(Vector2(1, -1));
From Here: https://api.flutter.dev/flutter/widgets/GestureDetector/onPanEnd.html
there is velocity concept.
I want to remove after drag by some velocity.
How to remove following moving or make velocity to 0?
I want to very not flexible interactive viewer.
EDIT:
STICKY TRANSFORMCONTROLLER CODE:
class CustomSecondTransformationController extends TransformationController {
CustomSecondTransformationController([Matrix4? value])
: super(value ?? Matrix4.identity());
set before(Offset value) {
_before = value;
}
Offset toScene(Offset viewportPoint) {
Offset vp = viewportPoint;
final Matrix4 inverseMatrix = Matrix4.inverted(value);
final Vector3 untransformed = inverseMatrix.transform3(Vector3(
vp.dx,
vp.dy,
0,
));
return Offset(untransformed.x / 3, untransformed.y / 3);
}
I am trying to draw lines with flutter using a Custom painter. i.e https://medium.com/flutter-community/drawing-in-flutter-using-custompainter-307a9f1c21f8
How can I make the lines drawling by user smooth?
To draw smooth line path you can use .cubicTo() method of Path. It applies cubic Bezier interpolation. Here is code example of smooth line painter:
class SmoothLinePainter extends CustomPainter {
final Color lineColor;
final List<double> values;
SmoothLinePainter(this.lineColor, this.values);
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = lineColor
..style = PaintingStyle.stroke
..strokeWidth = 1;
final path = Path();
final yMin = values.reduce(min);
final yMax = values.reduce(max);
final yHeight = yMax - yMin;
final xAxisStep = size.width / values.length;
var xValue = 0.0;
for (var i = 0; i < values.length; i++) {
final value = values[i];
final yValue = yHeight == 0
? (0.5 * size.height)
: ((yMax - value) / yHeight) * size.height;
if (xValue == 0) {
path.moveTo(xValue, yValue);
} else {
final previousValue = values[i - 1];
final xPrevious = xValue - xAxisStep;
final yPrevious = yHeight == 0
? (0.5 * size.height)
: ((yMax - previousValue) / yHeight) * size.height;
final controlPointX = xPrevious + (xValue - xPrevious) / 2;
// HERE is the main line of code making your line smooth
path.cubicTo(
controlPointX, yPrevious, controlPointX, yValue, xValue, yValue);
}
xValue += xAxisStep;
}
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(_LineChartPainter old) =>
old.lineColor != lineColor && old.values != values;
}
And here is two images showing difference between using .lineTo() and .cubicTo() methods:
path.lineTo(...)
path.cubicTo(...)
I made a blog on how to make this feature, I hope it helps you.
It describes how to get rid of the weird-looking lines when drawing and also how to implement erase and undo feature as well.
Blog:
https://ivanstajcer.medium.com/flutter-drawing-erasing-and-undo-with-custompainter-6d00fec2bbc2
If you are not interested in all of that I will tell you quick summary of two solutions, one I found online and one I made:
Use the list of offsets and draw a line or a curve between them and also draw a circle on every offset. But this is not performance good and it will give you a bad UX in the long run.
You can read about this in the blog, divide all lines into objects and store a Path for each line, as well as a list of Offsets. Then draw the path by calling canvas.drawPath() (this is better than going through all offsets and drawing in between them) and also draw a circle in all of the offsets.
I hope this solves your problem.
This pub package provides a quick way to draw a smooth graph going through specific points - https://pub.dev/packages/smoothie
When drawing lines using the drawPath method on Canvas, strokeJoin might be another property worth checking out from the Paint class.
Since it defaults to StrokeJoin.miter, changing it StrokeJoin.round did the trick for my use case.
class SmoothPainter extends CustomPainter {
const SmoothPainter(this.path);
final Path path;
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.round
..strokeWidth = 25
..color = const Color(0xFFAABBCC);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(SmoothPainter oldDelegate) => path != oldDelegate.path;
}
From docs -
The kind of finish to place on the joins between segments.
This applies to paths drawn when style is set to PaintingStyle.stroke, It does not apply to points drawn as lines with Canvas.drawPoints.
Defaults to StrokeJoin.miter, i.e. sharp corners.
Note: The doc link also contains visuals to each StrokeJoin enum.
After some tinkering and Googling all over the place I became super frustrated with the API / lack of documentation for ScrollPhysics.
On Android you can use what's called a SnapHelper inside your RecyclerView (analogous to a ListView in Flutter) that will automatically snap to a certain position.
The SnapHelper does this on a position based API.
You can ask which View is currently in your chosen ViewPort and get its position and ask the RecyclerView to animate to that position.
Flutter on the other hand wants us to work with logical pixels, which makes this super trivial, common pattern difficult to implement.
All the solutions I found was to use items inside the list that have a fixed width/height and don't account for flinging gestures.
tl;dr How to implement this πin Flutter so it works for any item in a ListView?
I'm including the poor mans version we are currently using.
Which works, but not like we are used to on Android.
Especially passing the itemWidth is an eyesore
class SnappingListScrollPhysics extends ScrollPhysics {
final double itemWidth;
const SnappingListScrollPhysics({
#required this.itemWidth,
ScrollPhysics parent,
}) : super(parent: parent);
#override
SnappingListScrollPhysics applyTo(ScrollPhysics ancestor) => SnappingListScrollPhysics(
parent: buildParent(ancestor),
itemWidth: itemWidth,
);
double _getItem(ScrollPosition position) => (position.pixels) / itemWidth;
double _getPixels(ScrollPosition position, double item) => min(item * itemWidth, position.maxScrollExtent);
double _getTargetPixels(ScrollPosition position, Tolerance tolerance, double velocity) {
double item = _getItem(position);
if (velocity < -tolerance.velocity) {
item -= 0.5;
} else if (velocity > tolerance.velocity) {
item += 0.5;
}
return _getPixels(position, item.roundToDouble());
}
#override
Simulation createBallisticSimulation(ScrollMetrics position, double velocity) {
// If we're out of range and not headed back in range, defer to the parent
// ballistics, which should put us back in range at a page boundary.
if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
(velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) {
return super.createBallisticSimulation(position, velocity);
}
final Tolerance tolerance = this.tolerance;
final double target = _getTargetPixels(position, tolerance, velocity);
if (target != position.pixels) {
return ScrollSpringSimulation(spring, position.pixels, target, velocity, tolerance: tolerance);
}
return null;
}
#override
bool get allowImplicitScrolling => false;
}
Clutter 1.12
Cogl 1.10
Vala or C or Python.
I might have a fundamental misunderstanding here β
I think of "Actors" as 3D polygon things. I think of their colours as either vertex colors or as texture-mapping. In this light, I have been trying to draw a custom Actor and fill it with stuff drawn via Cairo. I'm not getting anywhere.
Code is included below (in Vala). Can anyone set me right about Clutter's basics (the docs just don't cut it) or, if I'm close, help me get that code working.
I expect to see a rounded rectangle with a smiley face within. What I'm seeing instead is the Cogl path fill covering* the face. I think the paint() is being done after the drawme()
*If you set the Clutter.Color to "#0001" in method paint(), you'll see this.
/*
Clutter Actor with custom paint() and filled with a Cairo-drawn texture.
(NOT yet working.)
Compile with:
valac \
--pkg clutter-1.0 \
--pkg cogl-1.0 \
somename.vala
*/
public class SmileyRect : Clutter.Actor {
//private vars
private Clutter.Canvas _canvas;
private bool _flip = false;
private int _w = 300;
private int _h = 300;
//Constructor
construct {
_canvas = new Clutter.Canvas();
_canvas.set_size( this._w, this._h );
this.set_size( this._w, this._h );
this.set_content( _canvas );
//Connect to the draw signal - this is as-per Clutter docs.
_canvas.draw.connect( drawme );
//Make it reactive and connect to the button-press-event
this.set_reactive( true );
this.button_press_event.connect( button_press );
}
/*
Button press signal handler.
Changes the colour of what will be painted on the canvas.
*/
private bool button_press ( Clutter.ButtonEvent evt ) {
this._flip = !this._flip; //Jiggle a value.
this.redraw(); // Forces re-run of the drawme() method.
return true; //all done with this signal.
}
//Common function to draw Cogl stuff - used in paint and pick.
private void draw_rr( Clutter.Color? color ) {
if (color != null ) { Cogl.set_source_color4ub(color.red,color.green,color.blue,color.alpha); }
Cogl.Path.round_rectangle( 0, 0, this._w, this._h, 15, 0.3f );
Cogl.Path.close();
// When from paint():
// Is there some way to fill this Cogl path with the contents
// of this._canvas? Or some other paradigm?
if (color != null ) { Cogl.Path.fill(); }
}
/* Some kind of freaky, this magic paint() thing.
Took me ages to get it running.
I want to draw a rounded rectangle as the basic shape
of this actor.
*/
public override void paint() {
stdout.printf("paint runs.\n");
// I did try a transparent color #0000 - just to see. No go.
// #000f - draws a black rounded rect *OVER* the Cairo canvas. It covers
// the smiley face.
this.draw_rr( Clutter.Color.from_string("#0000") );
}
/* I followed paint() example, but the argument was tricky.
I eventually found it from some PyClutter source code.
*/
public override void pick(Clutter.Color color) {
stdout.printf("pick runs.\n");
this.draw_rr( color );
}
/*
Draws the Cairo art to the canvas.
I want this art to be the bitmap/pixmap that *fills* the
basic rounded rectangle shape of this actor.
i.e I want the smile face cairo rectangle to be *within* the
polygon that is draw via Cogl in paint() method.
Does this even make sense?
*/
private bool drawme( Cairo.Context ctx, int width, int height) {
//Draw a rectangle
double[] col;
if (this._flip) {
col = {1f,0f,0f};
}
else {
col = {0f,1f,0f};
}
ctx.set_source_rgb( col[0], col[1], col[2] );
ctx.rectangle( 0, 0, width, height );
ctx.fill();
//Draw a smiley face.
// Aside: Been trying to avoid all the weird casting of the floats/doubles used below
// (see the generated c source.) Not at all sure what's going on there.
// Also not sure about that 'f' on the numbers. Where/when do I have to use it?
double pi = 3.14f;
double w = (double) width;
double h = (double) height;
ctx.set_line_width( 6f );
ctx.set_source_rgb( 0f, 0f, 0.8f );
//eyes
ctx.move_to( w/3f, h/3f );
ctx.rel_line_to( 0f, h/6f );
ctx.move_to( 2*(w/3f), h/3f );
ctx.rel_line_to( 0f, h/6f );
ctx.stroke();
ctx.set_source_rgb( 1f, 1f, 0f );
double rad = (w > h) ? h : w;
//face
ctx.arc( w/2f, h/2f, (rad/2f) - 20f,0f,2f * pi );
ctx.stroke();
//smile
ctx.arc( w/2f, h/2f, (rad/3f) -10f, pi/3f, 2f * (pi/3f) );
ctx.stroke();
return true;
}
//Redraw - forces invalidate which trips the draw event
public void redraw() {
this._canvas.invalidate();
}
} //end SmileyRect class
void main( string[] args ) {
Clutter.init(ref args);
var stage = new Clutter.Stage();
stage.set_size(400,400);
var rs = new SmileyRect();
stage.add_child(rs);
rs.redraw();
stage.destroy.connect(Clutter.main_quit);
stage.show();
Clutter.main();
}