I looked into drag and drop and found that the drop method of Container shuffles the containers components.
That brought me to ask myself: wouldn't it be nice if drop happened continously while keeping on dragging.
So I did just that - only it yields strange results. Apparently it gets into a state where the dragged component is not painted anymore and the dragFinished method of the dragged component is not called when releasing the pointer.
Please help me understanding what is going wrong there.
Here is the code:
public class FormContinuousDrop extends Form {
private class LabelDraggable extends Label {
int pressedX = 0;
public LabelDraggable(String aCaption) {
super(aCaption);
setDraggable(true);
}
#Override
public void pointerPressed(int x, int y) {
super.pointerPressed(x, y);
pressedX = x;
}
#Override
public void pointerDragged(int x, int y) {
Log.p("LabelDraggable.pointerDragged(" + x + ", " + y + ")");
super.pointerDragged(pressedX, y);
{ // Here is where the "list" is shuffled whilst remaining dragging
if (isDragActivated() && !getAnimationManager().isAnimating()) {
ContainerDropTarget containerDropTarget = (ContainerDropTarget) getParent();
containerDropTarget.drop(this, pressedX, getDraggedy());
}
}
}
#Override
protected void dragFinished(int x, int y) {
Log.p("LabelDraggable.dragFinished(" + x + ", " + y + ")");
super.dragFinished(x, y);
}
}
private class ContainerDropTarget extends Container {
public ContainerDropTarget() {
super(new BoxLayout(BoxLayout.Y_AXIS));
setDropTarget(true);
}
#Override
public Component getComponentAt(int x, int y) {
boolean edt = Display.getInstance().isEdt();
Log.p("ContainerDropTarget.getComponentAt(" + x + ", " + y + ") - EDT: " + String.valueOf(edt));
return super.getComponentAt(x, y);
}
#Override
public void drop(Component dragged, int x, int y) {
Log.p("ContainerDropTarget.drop(" + x + ", " + y + ")");
super.drop(dragged, x, y);
}
}
public FormContinuousDrop() {
setTitle("FormContinuousDrop");
setScrollable(false);
Container containerContent = getContentPane();
containerContent.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
containerContent.add(new SpanLabel("Simple Drag And Drop example where drop is done continously whilst dragging"));
ContainerDropTarget containerDropTarget = new ContainerDropTarget();
for (int tally = 0; tally < 20; tally++) {
containerDropTarget.add(new LabelDraggable("draggable " + (tally + 1)));
}
containerContent.add(containerDropTarget);
}
}
If you have some animation pending then the remove/add might create an animation and defer things like the removal/addition to prevent collision between multiple animations. You should always check the actual status of the component before adding/removing.
Ok, what I tried to do cannot be done for now and for those reasons:
The method com.codename1.ui.Container.drop(Component, int, int) indirectly causes the variable Form.dragged to be set to zero, which in turn causes the drag to be interrupted - see issue https://github.com/codenameone/CodenameOne/issues/1992
If the method com.codename1.ui.AnimationManager.isAnimating() returns false this doesn't mean no animation is in progress - see issue https://github.com/codenameone/CodenameOne/issues/1993
com.codename1.ui.Component.dragFinishedImpl(int, int) scrolls the drop target container using screen coordinates which often leads to strange results with scrollable containers - see issue https://github.com/codenameone/CodenameOne/issues/1994
It would be difficult to find workarounds for the mentioned things because often only private or package private access exists and Codename One does not support overshadowing of classes.
On the other hand, I do not feel familiar enough with Codename One to make the appropriate changes myself and post a pull request.
Related
I have a custom class in p5js that defines an "ImageButton".
This is being used in React through react-p5-wrapper but I don't think that is related to this issue.
I have several different instances of this ImageButton running, and I have a function inside the class to detect if the mouse is over the button. If it detects that it is over, it should change the image to a different one (basically different color icons), and it should also change the mouse cursor to a pointer.
I know the detection works correctly, because all instances change to the "hover" image.
The problem is the mouse cursor only changes to pointer on the first instance of the class, and remains as default everywhere else. Couldn't find any resources on this issue, so it's probably something wrong with my code, but haven't been able to figure it out, so here it is:
export default class ImageButton {
constructor(p5, x, y, img, hoverImg) {
this.p5 = p5;
this.x = x;
this.y = y;
this.img = img;
this.hoverImg = hoverImg;
this.rad = 16;
}
display() {
if (this.over()) {
this.p5.cursor('pointer');
this.p5.image(this.hoverImg, this.x, this.y, this.rad, this.rad);
} else {
this.p5.cursor('default');
this.p5.image(this.img, this.x, this.y, this.rad, this.rad);
}
}
over() {
if (
this.p5.mouseX > this.x &&
this.p5.mouseX < this.x + this.rad &&
this.p5.mouseY > this.y &&
this.p5.mouseY < this.y + this.rad
) {
return true;
} else {
return false;
}
}
}
And here is a small quick video showing the issue: video
Any ideas?
Searched the web for similar problems, couldn't find anything helpful.
I assume you call the 'display()' function for each instance of ImageButton class. In the 'display()' function you are using a simple if statement to check if the element is hovered, which sets the cursor to 'default' when it's not being hovered. It means that only last element that is being checked is able to set the cursor to 'pointer', because every element that is currently not hovered is setting the cursor back to 'default'.
The first solution that comes to my mind is to set the return type of the 'display()' function to boolean and then using it in the main loop function to handle the cursor changing. If any element returned 'true' then the cursor should be a pointer, otherwise set it to default.
You should also consider simplifying the 'over()' function:
over()
{
return
this.p5.mouseX > this.x &&
this.p5.mouseX < this.x + this.rad &&
this.p5.mouseY > this.y &&
this.p5.mouseY < this.y + this.rad;
}
Today I tried to investigate this issue: https://github.com/codenameone/CodenameOne/issues/2975
I'm writing here to ask how I can find exactly what goes wrong. This bug is frustrating.
Basically, on iOS only, I have this error, that happens after some random app usage:
java.lang.NullPointerException
at com_codename1_ui_Form.pointerReleased:3758
at net_informaticalibera_cn1_simpleapi_OuterForm.pointerReleased:360
at com_codename1_ui_Component.pointerReleased:4679
at com_codename1_ui_Display.handleEvent:2289
at com_codename1_ui_Display.edtLoopImpl:1214
at com_codename1_ui_Display.mainEDTLoop:1132
at com_codename1_ui_RunnableWrapper.run:120
at com_codename1_impl_CodenameOneThread.run:176
at java_lang_Thread.runImpl:153
I've overridden the pointerReleased method to see if x and y are acceptable values when the previous exception is thrown, it seems so:
#Override
public void pointerReleased(int x, int y) {
try {
super.pointerReleased(x, y);
} catch (Exception ex) {
Log.p("OuterForm.pointerReleased ERROR, x->" + x + ", y->" + y + ", https://github.com/codenameone/CodenameOne/issues/2975");
Log.e(ex);
SendLog.sendLogAsync();
}
}
Using that override, that is equivalent to the crash protection feature, after the first time that this exception happens the TextArea components are not more usable: the tap on them doesn't open the VKB.
In short, there is a NullPointerException inside the iOS port of Form.pointerReleased: how can I discover which line of that method throws the exception? I hope to find info that can help for the bug resolution.
The problem is that the code of the method public void pointerReleased(int x, int y) of the class Form is all inside a try... finally, that hides the actual cause of the exception.
To get the actual cause, I used the following override in the BaseForm class of my app, that extends Form and that I use as superclass for all other Forms:
#Override
public void pointerReleased(int x, int y) {
try {
Component cmp = instance.getResponderAt(x, y);
if (cmp != null) {
cmp.pointerReleased(x, y);
}
} catch (Exception ex) {
Log.p("BaseForm.pointerReleased ERROR, x->" + x + ", y->" + y + ", https://github.com/codenameone/CodenameOne/issues/2975");
Log.e(ex);
SendLog.sendLogAsync();
}
}
As expected, this gave me the actual cause of the bug, that was inside a lambda expression of a TextArea actionListener: more specifically, the issue was a revalidate on an Container reference that can be null in some circumstances (oddly, this happens only on iOS). After that, I removed the previous override (that broke some functionalities), I fixed my code preventing the revalidate on a null object (with an if condition) and the bug disappeared (I've done a test with a long usage of the app).
I noticed that sometimes the content pane changes its size after initialisation and was wondering why that would be.
Here's some code to demonstrate this in the simulator - run it in the simulator and watch the output - smaller skins have smaller differences:
public class FormScrollExtras extends Form {
private int entrytally = 0;
private Runnable runnableLog = null;
public FormScrollExtras() {
setTitle("FormScrollExtras");
setScrollable(false);
setTensileDragEnabled(false);
Container contentPane = getContentPane();
contentPane.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
contentPane.setScrollableY(true);
contentPane.setTensileDragEnabled(false);
TextArea textArea = new TextArea("This form logs the content panes height and layoutHeight - right after init and whenever an entry is created.");
textArea.setEditable(false);
contentPane.add(textArea);
FloatingActionButton floatingActionButton = FloatingActionButton.createFAB(FontImage2.MATERIAL_ADD);
floatingActionButton.bindFabToContainer(getContentPane(), Component.RIGHT, Component.BOTTOM);
floatingActionButton.addActionListener((e) -> newEntry(contentPane));
contentPane.getParent().layoutContainer(); // Doesn't make a difference
runnableLog = () -> {
Log.p("x/y, height/layoutHeight: " +
contentPane.getAbsoluteX() + "/" + contentPane.getAbsoluteY() + ", " +
contentPane.getHeight() + "/" + contentPane.getLayoutHeight());
};
runnableLog.run();
}
private void newEntry(Container contentPane) {
contentPane.add(new Label("" + ++entrytally));
runnableLog.run();
contentPane.repaint();
}
}
Without the numbers related to the height change it's hard to guess but you can verify this by looking if this only happens for iOS skins or only for Android skins.
I'm guessing it would be because of the status bar space that is reserved in iOS and impacts the toolbar height. Notice that during construction elements aren't laid out yet so there is no size guarantee at this stage. There are several callbacks that are more deterministic such as initComponent() or laidOut().
I want to be able to pinch two containers in a list of containers away from each other to insert a new empty container between them. Similar to how the iPhone app “Clear” inserts new tasks (see for example the very first picture on this page https://www.raywenderlich.com/22174/how-to-make-a-gesture-driven-to-do-list-app-part-33 - the small red container is inserted when the two sorounding containers are pinched away from each other). Any hints on how I can achieve this in Codename One?
Normally you would override the pinch method to implement pinch to zoom or similar calls. However, this won't work in this case as the pinch will exceed component boundaries and it wouldn't work.
The only way I can think of doing this is to override the pointerDragged(int[],int[]) method in Form and detect the pinch motion as growing to implement this. You can check out the code for pinch in Component.java as it should be a good base for this:
public void pointerDragged(int[] x, int[] y) {
if (x.length > 1) {
double currentDis = distance(x, y);
// prevent division by 0
if (pinchDistance <= 0) {
pinchDistance = currentDis;
}
double scale = currentDis / pinchDistance;
if (pinch((float)scale)) {
return;
}
}
pointerDragged(x[0], y[0]);
}
private double distance(int[] x, int[] y) {
int disx = x[0] - x[1];
int disy = y[0] - y[1];
return Math.sqrt(disx * disx + disy * disy);
}
Adding the entry is simple, just place a blank component in the place and grow its preferred size until it reaches the desired size.
I have a method that is gobbling up 25% of my cpu time. I call this method about 27,000 times per second. (Yup, lots of calls since it's updating frequently). I am wondering if anybody knows a faster way to detect if 2 polygons overlap. Basically, I have to check the moving objects on the screen against stationary objects on the screen. I am using PathGeometry and the two calls below are using up 25% of the cpu time used by my program. The PointCollection objects I am passing just contain 4 points representing 4 corners of a polygon. They may not create a rectangular area, but all the points are connected. I guess a trapazoid would be the shape.
These methods are short and were very easy to implement, but I think I might want to opt for a more complicated solution if I can have it run more quickly than the code below. Any ideas?
public static bool PointCollectionsOverlap(PointCollection area1, PointCollection area2)
{
PathGeometry pathGeometry1 = GetPathGeometry(area1);
PathGeometry pathGeometry2 = GetPathGeometry(area2);
return pathGeometry1.FillContainsWithDetail(pathGeometry2) != IntersectionDetail.Empty;
}
public static PathGeometry GetPathGeometry(PointCollection polygonCorners)
{
List<PathSegment> pathSegments = new List<PathSegment>
{ new PolyLineSegment(polygonCorners, true) };
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures.Add(new PathFigure(polygonCorners[0], pathSegments, true));
return pathGeometry;
}
Ok, after lots of research and finding many partial answers, but none that fully answered the question, I have found a faster way and it is actually about 4.6 times faster than the old way.
I created a special test app to test the speed this. You can find the test app here. If you download it, you can see a checkbox at the top of the app. Check and uncheck it to switch back and forth between the old way and the new way. The app generates a bunch of random polygons and the borders of the polygons change to white when they intersect another polygon. The numbers to the left of the 'Redraw' button are to allow you to enter the Number of Polygons, Max Length of a side, and Max offset from square (to make them less square and more odd shaped). Push 'Refresh' to clear and regenerate new polygons with the settings you've entered.
Anyway, here is the code for the two different implementations. You pass in a collection of the points that make up each polygon. The old way uses less code, but is 4.6 times slower than the new way.
Oh, one quick note. The new way has a couple calls to 'PointIsInsidePolygon'. These were necessary because without it, the method returned false when one polygon was entirely contained within a different polygon. But the PointIsInsidePolygon method fixes that problem.
Hope this all helps somebody else out with polygon intercepts and overlaps.
Old Way (4.6 times slower. YES REALLY 4.6 TIMES slower):
public static bool PointCollectionsOverlap_Slow(PointCollection area1, PointCollection area2)
{
PathGeometry pathGeometry1 = GetPathGeometry(area1);
PathGeometry pathGeometry2 = GetPathGeometry(area2);
bool result = pathGeometry1.FillContainsWithDetail(pathGeometry2) != IntersectionDetail.Empty;
return result;
}
public static PathGeometry GetPathGeometry(PointCollection polygonCorners)
{
List<PathSegment> pathSegments = new List<PathSegment> { new PolyLineSegment(polygonCorners, true) };
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures.Add(new PathFigure(polygonCorners[0], pathSegments, true));
return pathGeometry;
}
New Way (4.6 times faster. YES REALLY 4.6 TIMES faster):
public static bool PointCollectionsOverlap_Fast(PointCollection area1, PointCollection area2)
{
for (int i = 0; i < area1.Count; i++)
{
for (int j = 0; j < area2.Count; j++)
{
if (lineSegmentsIntersect(area1[i], area1[(i + 1) % area1.Count], area2[j], area2[(j + 1) % area2.Count]))
{
return true;
}
}
}
if (PointCollectionContainsPoint(area1, area2[0]) ||
PointCollectionContainsPoint(area2, area1[0]))
{
return true;
}
return false;
}
public static bool PointCollectionContainsPoint(PointCollection area, Point point)
{
Point start = new Point(-100, -100);
int intersections = 0;
for (int i = 0; i < area.Count; i++)
{
if (lineSegmentsIntersect(area[i], area[(i + 1) % area.Count], start, point))
{
intersections++;
}
}
return (intersections % 2) == 1;
}
private static double determinant(Vector vector1, Vector vector2)
{
return vector1.X * vector2.Y - vector1.Y * vector2.X;
}
private static bool lineSegmentsIntersect(Point _segment1_Start, Point _segment1_End, Point _segment2_Start, Point _segment2_End)
{
double det = determinant(_segment1_End - _segment1_Start, _segment2_Start - _segment2_End);
double t = determinant(_segment2_Start - _segment1_Start, _segment2_Start - _segment2_End) / det;
double u = determinant(_segment1_End - _segment1_Start, _segment2_Start - _segment1_Start) / det;
return (t >= 0) && (u >= 0) && (t <= 1) && (u <= 1);
}