Is it possible to use animated icons with VirtualTreeView nodes? - delphi

I am using the awesome VirtualTreeView component with Delphi 2010. Currently, my nodes are setup with a standard image in a TImageList. What I'd like to do is to be able to show some type of spinning animated icon to show visually that a node is "busy".
Obviously the node won't be busy, but what the node represents to the user will actually be busy. Is there an easy way to do this?
The only way I can think of is to create add 8-10 images represent a spinning wheel to my current TImageList and then cycle through them for that node.
Does anyone have any suggestions?

Probably you can do the animation yourself. For example you could store the animation status in the node data. Then you could repeatedly (from a timer) iterate the whole tree (IterateSubTree) and from the callback check the node data and if the node should be animated call RepaintNode (to force repainting immediately) or InvalidateNode (to invalidate the node so it's painted in the next paint cycle). The actual painting could be done as usual from an OnGetImageIndex event handler, returning different image indexes for different stages of the animation cycle.

Related

What’s the most efficient method to drag/move objects simultaneously?

This is more of a general design question than a specific question about a piece of code. Let me do an outline:
Imagine a canvas with a poly line consisting of 3 or more points. Each of those points has a circle object as draggable handle. The user can move the drag handle, the action updates a react context containing only those 3 coordinates. The new context triggers a redraw of the canvas, updating the poly line and the drag handle with new coordinates, while performing things like “snap to axis” and “snap to closest other vector” checks while dragging occurs.
I only use functional components (React.FC) and state is shared where needed using hooks.
I use requestAnimationFrame() on the drag handler to debounce updates as close as possible to the maximum refresh rate of the client. The goal is to stay at 60fps.
Questions:
Does a re-render of a component attached to a stage in react-konva always trigger a redraw of everything on the actual canvas or does react-konva keep track?
Would it be advisable to use vanilla Konva instead of react-konva abstraction in this case, to avoid excessive redraw, while only using the react context to persist state once a drag action ended?
Is there a more efficient way to handle this use case I currently am not aware of?
When you re-render a component it will trigger an internal update inside react-konva.
react-konva will update props of Konva nodes. It is trying to work smart, so if you re-rendered a component, but no props changed - Konva nodes will be not updated (and no canvas draws).
Any change of props will trigger layer (or stage) redraw.
You can use vanilla Konva instead of react-konva if you want to have full control and if you want to skip some react reconciler work. But it may be hard on large components.
Also, it is better to avoid using React.Context for frequent updates. Updating the context may trigger a lot of re-renders of components. So it is better to use plain state (or even mobx) for faster updates.

How should I update the node in Virtual TreeView?

I am using Delphi XE3 and Virtual TreeView.
I want to use Virtual TreeView to implement a tree, when clicking "Start" button, the program will search all files and folders under a drive recursively, then add them one by one to the tree, just like Windows Explorer. Moreover, there should be a number indicating number of files and subfolders under a folder, using static text like this:
VirtualTreeView - different color of text in the same node
In my impelemention, I find sometimes the number is not updated correctly.
Therefore, I think the following way to refresh the node, whenever the number of files/subfolders are changed:
Call tvItems.Change(PNode) to update the node.
Call tvItems.InvalidateNode(PNode).
Call tvItems.RepaintNode(PNode).
Call tvItems.UpdateAction.
However, 1 is protected method that cannot be invoked. 2 and 3 are both OK but don't know which is better for updating. 4 is not documented and don't know how to call it.
The fundamental idea is that if something behind the scenes changes, the tree need to repainted. This means that the next time the tree draws itself, it will use the new underlying values.
If your tree is sitting there on screen:
You can simply call:
tvItems.Invalidate;
This tells Windows that the entire tree is now "invalid" and needs to be redrawn. I can represent this "invalid" region that will be updated the next time the tree paints itself:
And that's fine, correct, and will work perfectly adequately.
Performance improvements
A lot of times it's perfectly reasonable to force the entire tree to repaint all of itself.
But it's also possible to begin to optimize things. If you know that only a certain region of a Windows control is "invalid", then you could just invalidate that portion.
The Windows function to do that is InvalidateRect:
InvalidateRect(tvItems.Handle, Rect(13, 18, 30, 38), True);
That will invalidate a 30x38 square at 13,18:
In fact all TWinControl.Invalidate is doing is turning around and calling the Windows InvalidateRect function:
//true means to also trigger an erase of the background
InvalidateRect(Self.Handle, Self.BoundsRect, True);
There may not be much use for such a strange rectangle to be invalidated. But you can probably imagine other rectangles you'd like to be invalidated.
Invalidating nodes
Windows doesn't realize it, but your control represents a tree, and the tree and nodes. And there might be times when you want to invalidate the rectangle of a "node":
Rather than you having to figure out the coordinates and size of a node, the
TVirtualTree already provides you a handy method to invalidate a node:
function InvalidateNode(Node: PVirtualNode): TRect; virtual;
// Initiates repaint of the given node and returns the just invalidated rectangle.
so:
tvItems.InvalidateNode(someNode);
It also provides a method to invalidate a node and all its children:
procedure TBaseVirtualTree.InvalidateChildren(Node: PVirtualNode; Recursive: Boolean);
// Invalidates Node and its immediate children.
// If Recursive is True then all grandchildren are invalidated as well.
// The node itself is initialized if necessary and its child nodes are created (and initialized too if
// Recursive is True).
This is useful when your tree has children:
You can invalidate the parent node and all the children that now need to be updated with the new numbers:
tvItems.InvalidateChildren(someNode, True);
And other helper methods
The Virtual Tree has other helpful methods that:
get a certain interesting rectangle to invalidate
call Windows.InvalidateRect
That is:
InvalidateToBottom(Node: PVirtualNode);
Initiates repaint of client area starting at given node. If this node is not visible or not yet initialized then nothing happens.
TBaseVirtualTree.InvalidateColumn(Column: TColumnIndex); Invalidates the client area part of a column.
Invalidating vs Repaint
Your other question is in regards to the confusion over the difference between:
invalidating
repainting
When you invalidate a rectangle (e.g. an entire form, an entire control, or some smaller rectangle such as a node, node and its children, or a column) you are telling Windows that it needs to ask the control to paint self. That is:
the pixels on screen are now invalid and must be repainted
This will happen the next time the tree is asked to paint itself. Windows is message-based. And as your application runs, it processes messages, including a WM_PAINT message. When the VirtualTree gets a WM_PAINT message, it paints the portions it was asked to repaint.
This means that for any and all painting to happen, you have to be processing messages - i.e. you have to let your program go "idle".
If you sit there is a busy loop, never letting your code exit:
procedure TForm1.Button1Click(Sender: TObject);
begin
while (true) do
begin
tvItems.Invalidate;
Sleep(100);
end;
end;
The loop never ends, and the tree is never given a chance to actually paint itself.
Delphi's horrible Repaint hack
Delphi has a horrible hack that forces a control to be painted.
it pretends that it's Windows asking a control to paint itself
and then jumps directly to the control's paint routine
This means that the control will paint itself, even though it's not received a WM_PAINT message from Windows - it just does the scribbling.
procedure TForm1.Button1Click(Sender: TObject);
begin
while (true) do
begin
tvItems.Repaint; //force the control to repaint itself
Sleep(100);
end;
end;
It's an ugly hack because:
in the first case our code wasn't processing Windows messages
in the revised case we're still not doing the right thing, and trying to use a hammer to screw in a screw
The correct solution in these cases is to have a background thread. And have the background thread signal the main application that it needs to invalidate the tree. The main program will then receive a WM_PAINT message and draw itself as normal.

How to replicate the effect of a ccsprite being a child of another but using a batch node?

The game I'm designing uses multiple nested CCSprites, so that when the parent sprite moves, rotates, scales, etc. the child sprite does as well, automatically, and so do its grandchildren.
It works great but bogs down very quickly since I haven't implemented batch nodes, currently it's making over 4000 draw calls at once which is obviously not optimal.
However as far as I know the only way to use batch nodes is to make all the sprites a child of the batch node. I thought maybe I could add each parent to the batch node and then add the rest as children of that one but that doesn't work.
Any ideas? I'd like to avoid having to manually calculate the position, rotation, scale, etc of each child sprite every time its parent moves, which at the moment seems to be the only way I can think of to make this work.
Looks like you CAN in fact nest children inside a ccsprite and have them be a part of the batch node, as long as the parent is a child of the batch node. When I first tried this I'd simply forgotten to change "spriteWithFile:" to "spriteWithSpriteFrameName:" when I created the children of one of the sprites. Now it works perfetly, all in one batch node, with one draw call. Hooray!

Best Practice for Rendering Context of free hand drawing on iPad 3

I currently have a free hand drawing iPad app, that adds lines to a mutable path via quad curves in the touches methods then calls setNeedsDisplayInRect on the new area.
Problem is when the drawing (path) gets rather large, it takes longer to redraw, and begins to bog down. As well as whenever the user changes the brush size or color, it applies this to overlapping parts of the previously drawn path on redraw.
To counter this, I call renderInContext in a background thread in touchesEnded, and merge this with another UIImage in an imageview behind the draw view. Then clear the draw view.
This also helps so when the user hits save, the drawing is usually already rendered in a single UIImage - ready to go.
This works fine on other devices, but on he iPad 3 retina display, the performance is really awful and tends to crash whenever the user lifts his finger multiple times when drawing quickly.
I am seeking any type of advice for best practice in handling this type of situation? Aside from adding additional views to render off of in the background to prevent the main and background thread from accessing the same view at a time - which sounds rather hack-ish - I feel like I'm beating a dead horse?
In my current app, I made a working implementation that works fine on iPad 2 as well as 3, regardless of path length or number of paths. It seems that the graphics card is better at drawing lots of small paths then a few large paths, and either one is faster than rendering an image into a context. So, what I do is even if the user is continuously drawing, I break the path into many smaller paths and add those to an array. This approach gives me one advantage, and one disadvantage.
Advantage: The ability to zoom and redraw the image crisply
Disadvantage: Can't do pixel perfect erasing
As far as multiple colors, I made a subclass of UIBezierPath that includes a color property. Since colors are now serializable via NSCoding, they are easily saveable. In addition, I have a "stroke" object, which holds all of the paths the user created in one continuous stroke. This way I can handle undo / redo correctly.
Hope this info helps.

Menu from the Contre Jour app

I'm trying to do a menu like the one that "Contre Jour" game has, with 3 elements spinning in a circle when user drags left and right. I'm using CALayers with CATransforms to position them in a 3d spinning wheel (no problem so far).
I need a way (maybe with NSTimers?) to calculate in-between values, because CoreAnimation just interpolates values, but if you NSLog them, it's just gonna show the start and the end, or just the end. I need all the in-between values, I need to snap the wheel movement when I release the finger (touches ends)in one position (there are 3 elements, each one shoud be at 120 degrees.
My guess and am quite sure I'm correct is that they are using a game engine such as Unity3D or Cocos2D or any other of the many to manage their sprites, animations, textures, physics and basically everything. Trying to replicate it outside of game engine will most likely result in crummy performance and a lot of hair pulling. I would suggest looking into a dedicated game engine and give it a shot there.
I am not sure I understand exactly what Contre Jour does with the spinners, anyway, I think that a reasonable approach for your case is using a UIPanGestureRecognizers to update the status of your spinning wheels according to the panning.
Now, it is not clear what you do to animate the spinning wheel (if you could provide some code, this would help understanding exactly what you are trying to do), but the idea would be this: instead of specifying an animation ending point far away from the starting point (and letting Core Animation do all the handling for you, even when the dragging has stopped), you would only modify the status of the spinning wheel in small increments.
If your only issue is stopping the animation when the dragging stops, you could try calling removeAnimationForKey on your layer to halt a specific animation.
Look into CADisplayLink. This works very much like an NSTimer, except its refresh rate is tied to that of the display, so your animations will be smoother than if you were to use timers. This will allow you to calculate all the in-between values and update your control.
I'm not clear what you are asking, but I do have one insight for you: To get the in-between values of an in-flight animation, query the layer's presentationLayer property. the property that's being animated will have a value that's a close approximation of it's on-screen appearance at the moment you fetch the value.

Resources