Build and add children in performLayout - flutter

I'm making a skill tree package and I'm wanting to have a robust api for users to alter the appearance of edges between nodes. For that, I want the signature of an edgeBuilder to contain some layout information of the from and to nodes it connects. For example, I could pass through to the builder the angle an imaginary straight line from one node to another makes. This angle could then be used to rotate the widgets I have at the ends of edges to properly look like they're pointing at the right things (in the image below, I have a triangle clipper that is just always pointed down).
I have this structure for laying out the children (both edges and nodes) inside my multi-child custom RenderObject.
#override
void performLayout() {
// ...
final skillNodeLayout = delegate.layoutNodes(
loosenedConstraints,
graph,
nodeChildren,
);
size = skillNodeLayout.size;
// ...
delegate.layoutEdges(
loosenedConstraints,
skillNodeLayout,
graph,
edgeChildren,
nodeChildren,
);
}
But now I'm curious to know whether Flutter advises against building and adding widgets in the layout phase. The ContainerRenderObjectMixin I'm mixing in specifically has methods to add to the list of children such as adoptChild(child) or addAll.
Can I avoid passing through the edge children to the RenderObject constructor and instead pass them through as a plain list of builder functions I call after the node layout and then add to the RenderObject? Something roughly like this:
#override
void performLayout() {
// ...
final skillNodeLayout = delegate.layoutNodes(
loosenedConstraints,
graph,
nodeChildren,
);
size = skillNodeLayout.size;
final edgeChildren = edgeChildrenBuilders.map((builder) {
return builder(skillNodeLayout);
}).toList();
// Found in the `ContainerRenderObjectMixin`
addAll(edgeChildren);
delegate.layoutEdges(
loosenedConstraints,
skillNodeLayout,
graph,
edgeChildren,
nodeChildren,
);
}
My worry is that it keeps adding children to the RenderObject every time a performLayout is requested or that this was never the intended way to work with renderObjects.

Related

Flutter: How to correctly apply ImageFilter to the current layer?

I am trying to apply a sequence of ImageFilters to the currently drawn Canvas layer.
canvas.drawCircle(...)
canvas.drawCircle(...)
// TODO Apply ImageFilter 1: Blur
// TODO Apply ImageFilter 2: Blur again
Is there an easy way to do that? I'm likely missing some simple method, but I could not find anything...
Things I have tried:
I know that I can specify the combined ImageFilter in Paint, but this does not work - I want to apply them in sequence after all shapes are drawn, rather to each shape separately.
saveLayer seems to be the closest to achieve what I need, but according to docs it only applies ColorFilter. They also say it is a bit inefficient.
saveLayer(null, Paint()..colorFilter = filter2)
saveLayer(null, Paint()..colorFilter = filter1)
..
canvas.draw
canvas.draw
...
restore()
restore()
Thank you!
For the future reference, I was able to achieve the desired effect by using the RenderBox (this can be used for example from LeafRenderObjectWidget).
The RenderBox has the paint method:
#override
void paint(PaintingContext context, Offset offset) {
context.pushLayer(
ColorFilterLayer(colorFilter: ColorFilter.matrix(...)),
(PaintingContext context2, Offset offset2) =>
context2.pushLayer(
ImageFilterLayer(imageFilter: ui.ImageFilter.blur(...)),
(PaintingContext context3, Offset offset3) =>
simulation.draw(context3.canvas),
offset2),
offset);
Not that the simulation.draw(context3.canvas) will draw in a canvas that is followed by the blur ImageFilter and then by the ColorFilter. Any number of Layers can be added, the syntax is a bit painful though. It can be simplified with some effort if needed:
/// Returns the painter with the given layer applied.
PaintingContextCallback withLayer(
ContainerLayer layer, PaintingContextCallback painter) {
return (PaintingContext context, Offset offset) =>
context.pushLayer(layer, painter, offset);
}
...
final List<ContainerLayer> layers = [...];
var painter = ...
for (final layer in layers) {
painter = withLayer(layer, painter);
}
painter(context, offset);

Efficient Canvas rendering in Flutter

I have been trying to develop a drawing app and all the example codes including the one's from The Boring Flutter Development Show don't translate well into real-world usage.
The main problem being that CustomPaint's paint operation is too expensive and re-draws every point, every frame. And as the points increase the flutter app's render time per frame increases significantly
From the time I spent finding a solution to this problem, I found these
RepaintBoundary : Rasterizes layers
Custom Widget using SingleChildRenderObjectWidget and RenderProxyBox : must implement a paint method and no way of passing a controller
I don't think any of the above solutions work well for my needs: smooth canvas drawing operations without re-paint. I even tried simplifying the points and that didn't work either because of the inherent mechanism of CustomPaint
If there was a way to pass a canvas as a widget and attaching a controller it'll be easy to store the captured points and use basic canvas operations like canvas.drawPath() or canvas.drawLine() much efficiently
Any suggestion would be helpful. Thank you!
This is the Controller class
class DrawingController extends ChangeNotifier {
List<Offset> _points = []
.... // Perform operations on data points
....
void add(Offset point) {
_points.add(point);
notifyListeners();
}
}
This is the CustomPaint class
class Painter extends CustomPainter {
DrawingController controller;
Painter(this.controller) : super(repaint: controller); //This is important
#override
void paint(Canvas canvas, Size size) {
//Paint function
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
And in the Gesture detector simply call controller.add(point) to add new points as you grab them
I am not sure this is the most efficient way to paint but it reduced the paint times down to 13ms on a 60hz screen and 9ms on a 120Hz screen. One noticeable drawback is that a single stroke (onTapdownEvent to an onDragEndEvent) renders quite slowly(up to 18ms) when the stroke has many points. This problem disappears as soon as you start a new stroke. I tried asking on several forums and this is the best I could come up with after digging through the flutter source code.
I profiled this functionality on a snapdragon 855 device so the processor is not a bottleneck. If anyone finds a better solution please post it.
A helpful tip is to add a Timer.run(setState()). This way it only updates when it can. Instead of loading update1 update2 update3...etc. It loads update1 then if it can it loads update2 but if update1 took to long it goes to update3. In general it just looks smoother but doesn't necessarily speed it up. Sample:
scaleStart(ScaleStartDetails d) => startPoint = d.focalPoint - _offset;
scaleUpdate(ScaleUpdateDetails d) {
_offset = d.focalPoint - startPoint;
scale = min(max(1, scale * (d.scale + 13) / 14), 128);
Timer.run(() => setState(() {}));
}

How to Drag and Drop Custom Widgets?

I have created my own custom widget and I want to support internal drag and drop for the widgets.
I have added 4 of my custom widgets in a vertical box layout. Now i want to drag and drop the custom widgets internally. To be more clear, If i drag the last widget and drop it in the first position, then the first widget has to move to the second positon and the last widget (which is dragged) has to move to first position. (same like drag and drop of the items in the List view). Can anyone suggest me a way to drag and drop of the custom widgets.
You need to reimplement mousePressEvent, mouseMoveEvent and mouseReleaseEvent methods of a widget you want to drag or install an event filter on them.
Store the cursor position in mousePressEvent and move the widget in mousePressEvent to the distance the cursor moved from the press point. Don't forget to clear the cursor position in the mouseReleaseEvent. The exact code depends of how you want the widget to look when is being dragged and how other widgets should behave when drag/drop the widget. In the simplest case it will look like this:
void mousePressEvent(QMouseEvent* event)
{
m_nMouseClick_X_Coordinate = event->globalX();
m_nMouseClick_Y_Coordinate = event->globalY();
};
void mouseMoveEvent(QMouseEvent* event)
{
if (m_nMouseClick_X_Coordinate < 0)
return;
const int distanceX = event->globalX() - m_nMouseClick_X_Coordinate;
const int distanceY = event->globalY() - m_nMouseClick_Y_Coordinate;
move(x() + distanceX, y() + distanceY());
};
void mouseReleaseEvent(QMouseEvent* event)
{
m_nMouseClick_X_Coordinate = -1;
}

eclipse preference - grid layout confusion

I try to build a part of an eclipse pref page which contains a table and add/remove-buttons. I have found some example code but I don't understand the following thing:
The method
protected void adjustForNumColumns(int numColumns) {
((GridData)top.getLayoutData()).horizontalSpan = numColumns;
}
sets the horizontal span for the parent (top) composite to the number of columns.
And the method
protected void doFillIntoGrid(Composite parent, int numColumns) {
top = parent;
// set layout
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan = numColumns;
top.setLayoutData(gd); ... }
sets the layout with the horizontal span set to the number of columns.
Which method is used when and why is the number of columns somehow applied twice to a grid data object? It might be a pretty stupid question but I just started with the whole thing... Can anybody explain to me how it works? (Or even refer to a nice webpage where I can find an explanation)
You might find this article on SWT layouts useful - http://www.eclipse.org/articles/article.php?file=Article-Understanding-Layouts/index.html

I need to know when a VerticalPanel changes size

I'm using gwt-dnd to implement drag-and-drop functionality in my GWT program. To get scrolling to work right, I need
<ScrollPanel>
<AbsolutePanel>
<VerticalPanel>
<!-- lots of draggable widgets -->
</VerticalPanel>
</AbsolutePanel>
</ScrollPanel>
I have to manually set the size of the AbsolutePanel to be large enough to contain the VerticalPanel. When I add widgets to the VerticalPanel, though, the size reported by VerticalPanel.getOffsetHeight() isn't immediately updated - I guess it has to be rendered by the browser first. So I can't immediately update the AbsolutePanel's size, and it ends up being too small. Argh!
My stop-gap solution is to set up a timer to resize the panel 500ms later. By then, getOffsetHeight will usually be returning the updated values. Is there any way to immediately preview the size change, or anything? Or, alternatively, can I force a render loop immediately so that I can get the new size without setting up a timer that's bound to be error-prone?
This is a common problem with DOM manipulations. The offsetHeight doesn't update until a short time after components are added. I like to handle this using a recursive timer until a pre-condition is violated. E.g. In your case let there be a function which adds components and will be defined as below:
public void addComponent(Widget w)
{
final int verticalPanelHeight = verticalPanel.getOffsetHeight();
verticalPanel.add(w);
final Timer t = new Timer(){
public void run()
{
if(verticalPanelHeight != verticalPanel.getOffsetHeight())
absolutePanel.setHeight(verticalPanel.getOffsetHeight() + 10 + "px");
else
this.schedule(100);
}
};
t.schedule(100);
}