offset of HitArea after zooming and scrolling - math

What will be the offset of the getHitRect after zooming and scrolling. Offset of the hit area after zoom can be calculated - (event.getX() / ZoomLayout.mScaleFactor + ZoomLayout.mClipBound.left) But i have not still figured out the offset of hit area after zooming and scrolling.
public class ZoomLayout extends RelativeLayout implements OnDoubleTapListener, OnGestureListener{
//ScalingFactor i.e. Amount of Zoom
static float mScaleFactor = 1.0f;
// Maximum and Minimum Zoom
private static float MIN_ZOOM = 1.0f;
private static float MAX_ZOOM = 2.0f;
//Different Operation to be used
private final int NONE_OPERATION=0;
private final int DRAG_OPERATION=1;
private final int ZOOM_OPERATION=2;
private float mWidth= 1280;
private float mHeight=800;
// Mode to select the operation
private int mode;
//Track X and Y coordinate of the finger when it first touches the screen
private float mInitialX = 0f;
private float mInitialY = 0f;
// Track the Bound of the Image after zoom to calculate the offset
static Rect mClipBound;
// mDetector to detect the scaleGesture for the pinch Zoom
private ScaleGestureDetector mDetector;
// mDoubleTapDetector to detect the double tap
private GestureDetector mDoubleTapDetector;
//Pivot point for Scaling
static float gx=0,gy=0;
boolean mdrag=false,mZoom=false;
public ZoomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
mClipBound = new Rect();
// Intialize ScaleGestureDetector
mDetector = new ScaleGestureDetector(getContext(), new ZoomListener());
mDoubleTapDetector = new GestureDetector(context,this);
mDoubleTapDetector.setOnDoubleTapListener(this);
}
public ZoomLayout(Context context) {
super(context);
setWillNotDraw(false);
mClipBound = new Rect();
// Intialize ScaleGestureDetector
mDetector = new ScaleGestureDetector(getContext(), new ZoomListener());
mDoubleTapDetector = new GestureDetector(context,this);
mDoubleTapDetector.setOnDoubleTapListener(this);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// Handles all type of motion-events possible
switch(event.getAction() ) {
case MotionEvent.ACTION_DOWN:
// Event occurs when the first finger is pressed on the Screen
Log.d("ZoomPrint", "Event: Action_Down " );
mInitialX = event.getX();
mInitialY = event.getY();
break;
case MotionEvent.ACTION_POINTER_DOWN:
//Event occurs when the second finger is pressed down
Log.d("ZoomPrint", "Event: Action_Pointer_Down " );
// If second finger is pressed on the screen with the first set the Mode to Zoom operation
mode=ZOOM_OPERATION;
break;
case MotionEvent.ACTION_POINTER_UP:
Log.d("ZoomPrint", "Event: Action_Pointer_UP " );
mdrag=true;
case MotionEvent.ACTION_UP:
//Event occurs when all the finger are taken of the screen
Log.d("ZoomPrint", "Event: Action_UP " );
//If all the fingers are taken up there will be no operation
mode = NONE_OPERATION;
mdrag=false;
break;
}
// give the event to the mDetector to get the scaling Factor
mDetector.onTouchEvent(event);
// give the event to the mDoubleTapDetector for the doubleTap
mDoubleTapDetector.onTouchEvent(event);
if(!mdrag)
invalidate();
return true;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
onTouchEvent(ev);
return super.onInterceptTouchEvent(ev);
// return true;
}
#Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
return super.invalidateChildInParent(location, dirty);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
int count = getChildCount();
for(int i=0;i<count;i++){
View child = getChildAt(i);
if(child.getVisibility()!=GONE){
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)child.getLayoutParams();
child.layout(
(int)(params.leftMargin ),
(int)(params.topMargin ),
(int)((params.leftMargin + child.getMeasuredWidth()) ),
(int)((params.topMargin + child.getMeasuredHeight()))
);
}
}
}
#Override
protected void dispatchDraw(Canvas canvas) {
//Save the canvas to set the scaling factor returned from detector
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(mScaleFactor, mScaleFactor,gx,gy);
super.dispatchDraw(canvas);
mClipBound = canvas.getClipBounds();
canvas.restore();
}
private class ZoomListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
#Override
public boolean onScale(ScaleGestureDetector detector) {
// getting the scaleFactor from the detector
mScaleFactor *= detector.getScaleFactor(); // gives the scaling factor from the previous scaling to the current
// Log.d("ZoomPrint", "detector scaling Factor" + mScaleFactor);
gx = detector.getFocusX();
gy = detector.getFocusY();
// Limit the scale factor in the MIN and MAX bound
mScaleFactor= Math.max(Math.min(mScaleFactor, MAX_ZOOM),MIN_ZOOM);
// Log.d("ZoomPrint", "Bounded scaling Factor" + mScaleFactor);
/*//Force canvas to redraw itself only if the one event is to happen (say Zooming only ) else do not invalidate here for multi operations
As what we de for scrolling or panning will not reflect here. So we will add this in onDraw method
invalidate();*/
// Here we are only zooming so invalidate has to be done
// invalidate();
// requestLayout();
// we have handle the onScale
return true;
}
}
#Override
public boolean onDoubleTap(MotionEvent e) {
// Make the mScaleFactor to its normal value
if(mScaleFactor>1.0f)
{
mScaleFactor=1.0f;
}
// Force the canvas to redraw itself again as the changes has been occured.
invalidate();
requestLayout();
return false;
}
#Override
public boolean onDoubleTapEvent(MotionEvent e) {
// Log.d("ZoomPrint", "OnDoubleTapEvent");
return false;
}
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
// Log.d("ZoomPrint", "OnSingleTap");
return false;
}
#Override
public boolean onDown(MotionEvent e) {
// TODO Auto-generated method stub
return false;
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
return false;
}
#Override
public void onLongPress(MotionEvent e) {
}
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
int distX= (int) distanceX, distY =(int) distanceY;
//Log.d("Print"," X " + this.mClipBound.left +" Y " + this.mClipBound.right + " b "+ this.mClipBound.bottom + " g" + this.mClipBound.top) ;
Log.d("Print", "Scroll X " + distanceX + " Y " + distanceY);
if(this.mClipBound.left<=0)
this.scrollTo(-280, 0);
else if(this.mClipBound.top<=0)
this.scrollTo(0, -250);
else if (this.mClipBound.right>=1047)
this.scrollTo(280, 0);
else if (this.mClipBound.bottom>=800)
this.scrollTo(0, 250);
else
this.scrollBy((int)distanceX,(int)distanceY);
return true;
}
#Override
public void onShowPress(MotionEvent e) {
}
#Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}

You need to translate the canvas during the scroll functionality. I have done it in this way in my code:
canvas.translate(getScrollX(), getScrollY());
getScrollX and Y are the amount of scroll.

Related

Skaisharp Bind CroppedBitmap

I followed this tutorial to create an Image cropping page https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/bitmaps/cropping. However i now bound two views of the PhotoCropperCanvasView to an carousel view an wanted to Bind the CroppedBitMap so that i can access this property directl in the viewmodel. I just cannot figure ouit how i would achieve that. I f i just make this a Bindable Property the property does not change when i make a new rectangle. So i think i kind of have to exclude the Code of the property but i am very confused.
The whole ocde:
public class PhotoCropperCanvasView : SKCanvasView, INotifyPropertyChanged
{
const int CORNER = 50; // pixel length of cropper corner
const int RADIUS = 100; // pixel radius of touch hit-test
//SKBitmap bitmap;
//CroppingRectangle croppingRect;
SKMatrix inverseBitmapMatrix;
public SKBitmap testmap;
//public SKBitmap bitmap { get; set; }
public Image testImage { get; set; }
public static readonly BindableProperty mapProperty =
BindableProperty.Create(nameof(map), typeof(SKBitmap), typeof(PhotoCropperCanvasView), null);
public SKBitmap map
{
get
{
return (SKBitmap)GetValue(mapProperty);
}
set
{
SetValue(mapProperty, value);
}
}
public CroppingRectangle croppingRect { get; set; }
//public SKMatrix inverseBitmapMatrix { get; set; }
public static readonly BindableProperty bitmapProperty =
BindableProperty.Create(nameof(bitmap), typeof(SKBitmap), typeof(Image),null,propertyChanged: OnbitmapChanged);
static void OnbitmapChanged(BindableObject bindable, object oldValue, object newValue)
{
Console.WriteLine("test");
}
public SKBitmap bitmap
{
get
{
return (SKBitmap)GetValue(bitmapProperty);
}
set
{
SetValue(bitmapProperty, value);
}
}
public SKBitmap CroppedBitmap
{
get
{
SKRect cropRect = new SKRect(croppingRect.Rect.Left,croppingRect.Rect.Top,croppingRect.Rect.Right,croppingRect.Rect.Bottom);
SKBitmap croppedBitmap = new SKBitmap((int)cropRect.Width,
(int)cropRect.Height);
SKRect dest = new SKRect(0, 0, cropRect.Width, cropRect.Height);
SKRect source = new SKRect(cropRect.Left, cropRect.Top,
cropRect.Right, cropRect.Bottom);
using (SKCanvas canvas = new SKCanvas(croppedBitmap))
{
canvas.DrawBitmap(bitmap, source, dest);
}
return croppedBitmap;
}
}
// Touch tracking
TouchEffect touchEffect = new TouchEffect();
struct TouchPoint
{
public int CornerIndex { set; get; }
public SKPoint Offset { set; get; }
}
Dictionary<long, TouchPoint> touchPoints = new Dictionary<long, TouchPoint>();
// Drawing objects
SKPaint cornerStroke = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.White,
StrokeWidth = 10
};
SKPaint edgeStroke = new SKPaint
{
Style = SKPaintStyle.Stroke,
Color = SKColors.White,
StrokeWidth = 2
};
// this constructor for profile image
public PhotoCropperCanvasView(SKBitmap bitmap, float? aspectRatio = null)
{
this.bitmap = bitmap;
SKRect bitmapRect = new SKRect(0, 0, bitmap.Width, bitmap.Height);
croppingRect = new CroppingRectangle(bitmapRect, aspectRatio);
touchEffect.TouchAction += OnTouchEffectTouchAction;
}
// this constructor for post images
public PhotoCropperCanvasView()
{
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = nameof(bitmap))
{
base.OnPropertyChanged(propertyName);
if (bitmap != null)
{
SKRect bitmapRect = new SKRect(0, 0, bitmap.Width, bitmap.Width);
croppingRect = new CroppingRectangle(bitmapRect, 1);
touchEffect.TouchAction += OnTouchEffectTouchAction;
}
}
protected override void OnParentSet()
{
base.OnParentSet();
// Attach TouchEffect to parent view
Parent.Effects.Add(touchEffect);
}
protected override void OnPaintSurface(SKPaintSurfaceEventArgs args)
{
base.OnPaintSurface(args);
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear(SKColors.Gray);
// Calculate rectangle for displaying bitmap
float scale = Math.Min((float)info.Width / bitmap.Width, (float)info.Height / bitmap.Height);
float x = (info.Width - scale * bitmap.Width) / 2;
float y = (info.Height - scale * bitmap.Height) / 2;
SKRect bitmapRect = new SKRect(x, y, x + scale * bitmap.Width, y + scale * bitmap.Height);
canvas.DrawBitmap(bitmap, bitmapRect);
// Calculate a matrix transform for displaying the cropping rectangle
SKMatrix bitmapScaleMatrix = SKMatrix.MakeIdentity();
bitmapScaleMatrix.SetScaleTranslate(scale, scale, x, y);
// Display rectangle
SKRect scaledCropRect = bitmapScaleMatrix.MapRect(croppingRect.Rect);
canvas.DrawRect(scaledCropRect, edgeStroke);
// Display heavier corners
using (SKPath path = new SKPath())
{
path.MoveTo(scaledCropRect.Left, scaledCropRect.Top + CORNER);
path.LineTo(scaledCropRect.Left, scaledCropRect.Top);
path.LineTo(scaledCropRect.Left + CORNER, scaledCropRect.Top);
path.MoveTo(scaledCropRect.Right - CORNER, scaledCropRect.Top);
path.LineTo(scaledCropRect.Right, scaledCropRect.Top);
path.LineTo(scaledCropRect.Right, scaledCropRect.Top + CORNER);
path.MoveTo(scaledCropRect.Right, scaledCropRect.Bottom - CORNER);
path.LineTo(scaledCropRect.Right, scaledCropRect.Bottom);
path.LineTo(scaledCropRect.Right - CORNER, scaledCropRect.Bottom);
path.MoveTo(scaledCropRect.Left + CORNER, scaledCropRect.Bottom);
path.LineTo(scaledCropRect.Left, scaledCropRect.Bottom);
path.LineTo(scaledCropRect.Left, scaledCropRect.Bottom - CORNER);
canvas.DrawPath(path, cornerStroke);
}
// Invert the transform for touch tracking
bitmapScaleMatrix.TryInvert(out inverseBitmapMatrix);
}
void OnTouchEffectTouchAction(object sender, TouchActionEventArgs args)
{
int i = 0;
SKPoint pixelLocation = ConvertToPixel(args.Location);
SKPoint bitmapLocation = inverseBitmapMatrix.MapPoint(pixelLocation);
switch (args.Type)
{
case TouchActionType.Pressed:
// Convert radius to bitmap/cropping scale
float radius = inverseBitmapMatrix.ScaleX * RADIUS;
// Find corner that the finger is touching
int cornerIndex = croppingRect.HitTest(bitmapLocation, radius);
if (cornerIndex != -1 && !touchPoints.ContainsKey(args.Id))
{
TouchPoint touchPoint = new TouchPoint
{
CornerIndex = cornerIndex,
Offset = bitmapLocation - croppingRect.Corners[cornerIndex]
};
touchPoints.Add(args.Id, touchPoint);
}
break;
case TouchActionType.Moved:
if (touchPoints.ContainsKey(args.Id))
{
TouchPoint touchPoint = touchPoints[args.Id];
croppingRect.MoveCorner(touchPoint.CornerIndex,
bitmapLocation - touchPoint.Offset);
InvalidateSurface();
}
break;
case TouchActionType.Released:
case TouchActionType.Cancelled:
if (touchPoints.ContainsKey(args.Id))
{
touchPoints.Remove(args.Id);
//map = CroppedBitmap;
}
break;
}
}
SKPoint ConvertToPixel(Xamarin.Forms.Point pt)
{
return new SKPoint((float)(CanvasSize.Width * pt.X / Width),
(float)(CanvasSize.Height * pt.Y / Height));
}
}
}
Since the case TouchActionType.Cancelled gets only triggerd once everytime the rectangel was moved, i thought i would set thew bindable Proeprty map to the Cropped bitmap property so that i can get the Cropped Image from the view obver a Binding to the viewmodel. This part works, however, when i activate the line map = CroppedBitmap the cropping rectangle can only be moved by opposite corners. So if i start moving it with the bottom right corner i con only use the top left or bottom right. If i leave the line map = CroppedBitman(249) deactivated i can move the rectangle on all corners at every times. I do not understand this behaviour.
the view:
<CarouselView Grid.Row="0"
IsSwipeEnabled="False"
x:Name="carousel"
Margin="0,-40,0,0"
CurrentItem="{Binding CurrentCutImage, Mode=TwoWay}"
CurrentItemChanged="CarouselView_CurrentItemChanged"
HorizontalScrollBarVisibility="Always"
IsScrollAnimated="True"
ItemsSource="{Binding ImageObjects}"
VerticalScrollBarVisibility="Always"
>
<CarouselView.ItemTemplate>
<DataTemplate x:DataType="viewmodel:CutImages">
<Grid>
<bitmaps:PhotoCropperCanvasView bitmap="{Binding ImageSource }" map="{Binding MapSource, Mode=TwoWay}" >
</bitmaps:PhotoCropperCanvasView>
</Grid>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
and the VM:
public partial class CutImagesViewModel : ObservableObject
{
// == observable properties ==
[ObservableProperty]
public Collection<CutImages> imageObjects = new Collection<CutImages>();
[ObservableProperty]
CutImages currentCutImage;
[ObservableProperty]
public SKBitmap maps;
public CutImagesViewModel(Collection<SKBitmapImageSource> images)
{
foreach(var image in images)
{
ImageObjects.Add(new CutImages(image));
}
this.CurrentCutImage = this.ImageObjects.FirstOrDefault();
}
}
public partial class CutImages : ObservableObject
{
[ObservableProperty]
public SKBitmap imageSource;
[ObservableProperty]
public SKBitmap mapSource;
partial void OnMapSourceChanged(SKBitmap value)
{
if(!images.Contains(value))
{
images.Add(value);
}
}
[ObservableProperty]
Collection<SKBitmap> images = new Collection<SKBitmap>();
public CutImages(ImageSource imageSource)
{
SKBitmapImageSource sourceImage = (SKBitmapImageSource)imageSource;
SKBitmap image = sourceImage;
ImageSource = image;
}
}

How to draw arrow JavaFX? (pane)

I need to make directed graph from undirected. I can draw line-Edge, but I don't know how to make arrow:
public class Edge extends Group {
protected Cell source;
protected Cell target;
Line line;
public Edge(Cell source, Cell target) {
this.source = source;
this.target = target;
source.addCellChild(target);
target.addCellParent(source);
line = new Line();
line.startXProperty().bind(source.layoutXProperty().add(source.getBoundsInParent().getWidth() / 2.0));
line.startYProperty().bind(source.layoutYProperty().add(source.getBoundsInParent().getHeight() / 2.0));
line.endXProperty().bind(target.layoutXProperty().add( target.getBoundsInParent().getWidth() / 2.0));
line.endYProperty().bind(target.layoutYProperty().add( target.getBoundsInParent().getHeight() / 2.0));
getChildren().addAll(line);
}
You need to add 2 more lines to make an arrow head (or a Polygon with the same points for a filled arrow head).
Note that the direction of the arrow can be determined based on the difference between start and end of the line ends of the "main" connection. One end of each of the lines that make up the arrow head need to be at the same coordinates as the end of the main line. The other end can be calculated by combining a part in direction of the main line and a part ortogonal to the main line:
public class Arrow extends Group {
private final Line line;
public Arrow() {
this(new Line(), new Line(), new Line());
}
private static final double arrowLength = 20;
private static final double arrowWidth = 7;
private Arrow(Line line, Line arrow1, Line arrow2) {
super(line, arrow1, arrow2);
this.line = line;
InvalidationListener updater = o -> {
double ex = getEndX();
double ey = getEndY();
double sx = getStartX();
double sy = getStartY();
arrow1.setEndX(ex);
arrow1.setEndY(ey);
arrow2.setEndX(ex);
arrow2.setEndY(ey);
if (ex == sx && ey == sy) {
// arrow parts of length 0
arrow1.setStartX(ex);
arrow1.setStartY(ey);
arrow2.setStartX(ex);
arrow2.setStartY(ey);
} else {
double factor = arrowLength / Math.hypot(sx-ex, sy-ey);
double factorO = arrowWidth / Math.hypot(sx-ex, sy-ey);
// part in direction of main line
double dx = (sx - ex) * factor;
double dy = (sy - ey) * factor;
// part ortogonal to main line
double ox = (sx - ex) * factorO;
double oy = (sy - ey) * factorO;
arrow1.setStartX(ex + dx - oy);
arrow1.setStartY(ey + dy + ox);
arrow2.setStartX(ex + dx + oy);
arrow2.setStartY(ey + dy - ox);
}
};
// add updater to properties
startXProperty().addListener(updater);
startYProperty().addListener(updater);
endXProperty().addListener(updater);
endYProperty().addListener(updater);
updater.invalidated(null);
}
// start/end properties
public final void setStartX(double value) {
line.setStartX(value);
}
public final double getStartX() {
return line.getStartX();
}
public final DoubleProperty startXProperty() {
return line.startXProperty();
}
public final void setStartY(double value) {
line.setStartY(value);
}
public final double getStartY() {
return line.getStartY();
}
public final DoubleProperty startYProperty() {
return line.startYProperty();
}
public final void setEndX(double value) {
line.setEndX(value);
}
public final double getEndX() {
return line.getEndX();
}
public final DoubleProperty endXProperty() {
return line.endXProperty();
}
public final void setEndY(double value) {
line.setEndY(value);
}
public final double getEndY() {
return line.getEndY();
}
public final DoubleProperty endYProperty() {
return line.endYProperty();
}
}
Use
#Override
public void start(Stage primaryStage) {
Pane root = new Pane();
Arrow arrow = new Arrow();
root.getChildren().add(arrow);
root.setOnMouseClicked(evt -> {
switch (evt.getButton()) {
case PRIMARY:
// set pos of end with arrow head
arrow.setEndX(evt.getX());
arrow.setEndY(evt.getY());
break;
case SECONDARY:
// set pos of end without arrow head
arrow.setStartX(evt.getX());
arrow.setStartY(evt.getY());
break;
}
});
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}

Drag BorderPane body

I use this JavaFX code to drag BorderPane into FlowPane:
private Node dragPanel(Node bp)
{
bp.setOnDragDetected(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
Dragboard db = bp.startDragAndDrop(TransferMode.MOVE);
ClipboardContent clipboard = new ClipboardContent();
final int nodeIndex = bp.getParent().getChildrenUnmodifiable()
.indexOf(bp);
clipboard.putString(Integer.toString(nodeIndex));
db.setContent(clipboard);
Image img = bp.snapshot(null, null);
db.setDragView(img, 7, 7);
event.consume();
}
});
bp.setOnDragOver(new EventHandler<DragEvent>()
{
#Override
public void handle(DragEvent event)
{
boolean accept = true;
final Dragboard dragboard = event.getDragboard();
if (dragboard.hasString())
{
try
{
int incomingIndex = Integer.parseInt(dragboard.getString());
int myIndex = bp.getParent().getChildrenUnmodifiable()
.indexOf(bp);
if (incomingIndex == myIndex)
{
accept = false;
}
}
catch (java.lang.NumberFormatException e)
{
// handle null or not number string in clipboard
accept = false;
}
}
else
{
accept = false;
}
if (accept)
{
event.acceptTransferModes(TransferMode.MOVE);
}
}
});
bp.setOnDragDropped(new EventHandler<DragEvent>()
{
#Override
public void handle(DragEvent event)
{
boolean success = false;
final Dragboard dragboard = event.getDragboard();
if (dragboard.hasString())
{
try
{
int incomingIndex = Integer.parseInt(dragboard.getString());
final Pane parent = (Pane) bp.getParent();
final ObservableList<Node> children = parent.getChildren();
int myIndex = children.indexOf(bp);
final int laterIndex = Math.max(incomingIndex, myIndex);
Node removedLater = children.remove(laterIndex);
final int earlierIndex = Math.min(incomingIndex, myIndex);
Node removedEarlier = children.remove(earlierIndex);
children.add(earlierIndex, removedLater);
children.add(laterIndex, removedEarlier);
success = true;
}
catch (java.lang.NumberFormatException e)
{
//TO DO... handle null or not number string in clipboard
}
}
event.setDropCompleted(success);
}
});
// bp.setMinSize(50, 50);
return bp;
}
I enable this drag event using this code:
BorderPane panel = new BorderPane();
dragPanel(panel),
I also have resize code which is also activated. I need some way to apply the drag code only of I click and drag the panel. I want to disable the drag listener when I drag the panel borders. Is there a way to limit this?
I'm guessing by "borders" you just mean the edges of the border panes. You can just check the coordinates of the mouse event and only initiate dragging if you're away from the borders. To do this, you need to know the width and height of the border pane. The methods to get those are defined in Region, so you need to narrow the type of the parameter from Node to Region. This will still work if you call dragPanel(panel) but you won't be able to pass in a Node that is not a Region instance.
final int borderSize = 5 ;
// ...
private Node dragPane(Region bp) {
bp.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
double x = event.getX();
double y = event.getY();
double width = bp.getWidth();
double height = bp.getHeight();
if (x > borderSize && x < width - borderSize
&& y > borderSize && y < height - borderSize) {
Dragboard db = bp.startDragAndDrop(TransferMode.MOVE);
ClipboardContent clipboard = new ClipboardContent();
final int nodeIndex = bp.getParent().getChildrenUnmodifiable()
.indexOf(bp);
clipboard.putString(Integer.toString(nodeIndex));
db.setContent(clipboard);
Image img = bp.snapshot(null, null);
db.setDragView(img, 7, 7);
event.consume();
}
}
});
// ...
}

LibGDX TextButton do not respond to mouse clicks

The text button draws normally (no errors or warnings) but it doesn't respond to mouse clicks. The situation is the same on Desktop and Android build. I've read and followed every topic about it with no results. What am I doing wrong?
Maybe I need to set something in stage object?
Here's my MenuScreen class code:
public class MenuScreen implements Screen {
private OrthographicCamera camera;
private BitmapFont font;
private TextureAtlas menuGraphics;
Stage stage;
TextButton button;
TextButtonStyle buttonStyle;
Skin skin;
public MenuScreen(Game game)
{
//create satage object
stage = new Stage();
//create font object
font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"),false);
font.setColor(Color.RED);
//load buttons texture atlas and create Skin object
menuGraphics = new TextureAtlas( Gdx.files.internal( "buttons/buttons.pack" ) );
skin = new Skin();
skin.addRegions(menuGraphics);
// Store the default libgdx font under the name "defaultFont".
skin.add("defaultFont",font);
//Set button style
buttonStyle = new TextButtonStyle();
buttonStyle.up = skin.newDrawable("yellow");
buttonStyle.down = skin.newDrawable("orange");
buttonStyle.checked = skin.newDrawable("orange");
buttonStyle.over = skin.newDrawable("orange");
buttonStyle.font = skin.getFont("defaultFont");
skin.add("default", buttonStyle);
//assign button style
button=new TextButton("PLAY",buttonStyle);
button.setPosition(200, 200);
button.setSize(200,200);
//add button to stage
stage.addActor(button);
//add click listener to the button
button.addListener(new ClickListener() {
public void clicked(InputEvent event, float x, float y) {
button.setText("Starting new game");
Gdx.app.log("MenuScreen", "clicked button");
}
});
Gdx.app.log("MenuScreen", "create");
}
#Override
public void resize(int width, int height )
{
float aspectRatio = (float) width / (float) height;
camera = new OrthographicCamera(640, 360);
camera.translate(320,180);
camera.update();
stage.setViewport(new FillViewport(640, 360, camera));
stage.getViewport().setCamera(camera);
Gdx.app.log( "MenuScreen", "Resizing screen to: " + width + " x " + height );
}
#Override
public void show() {
Gdx.input.setInputProcessor(stage);
float w = Gdx.graphics.getWidth();
float h = Gdx.graphics.getHeight();
resize((int)w,(int)h);
Gdx.app.log( "MenuScreen", "Show screen code" );
}
#Override
public void render(float delta)
{
Gdx.gl.glClearColor( 0f, 1f, 0f, 1f );
Gdx.gl.glClear( GL20.GL_COLOR_BUFFER_BIT );
stage.act( delta );
// draw the actors
stage.draw();
}
#Override
public void dispose()
{
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void hide() {
}
}
I Finally solved it!
Additional line in resize function did the trick:
stage.getViewport().update(width, height);
I realized that the problem was caused by setting the Camera and Viewport in the resize function. When I removed that code (an adjusted the button position to be visible with standard stage viewport) the button started working.
But I wanted to use camera and viewport. Reading about stage resizing in LibGDX documantation (https://github.com/libgdx/libgdx/wiki/Scene2d) I found out that after setting camera, the viewport needs to be updated, otherwise the actor boundries are not recalculated. So I added the viewport update line in resize function and it started working!
The new resize function looks like that:
#Override
public void resize(int width, int height )
{
float aspectRatio = (float) width / (float) height;
camera = new OrthographicCamera(640, 360);
camera.translate(320,180);
camera.update();
stage.setViewport(new FillViewport(640, 360, camera));
stage.getViewport().setCamera(camera);
stage.getViewport().update(width, height);
Gdx.app.log( "MenuScreen", "Resizing screen to: " + width + " x " + height );
}
add implements ApplicationListener to your class
public MenuScreen implements ApplicationListener
hope this helps.

Android - How can add horizontal swipe gesture in vertical scroll view like pulse app has screen view

I am newbie to android please help me, is their any possibility to add swipe action in vertical scroll view of activity screen.I am trying hard, but not getting...
I just converted vertical scroll view to Listview, Its works like a charm... Thanks to omid nazifi and wwyt, for more u can see this link Gesture in listview android
public class MainActivity extends ListActivity {
private OnTouchListener gestureListener;
private GestureDetector gestureDetector;
private int REL_SWIPE_MIN_DISTANCE;
private int REL_SWIPE_MAX_OFF_PATH;
private int REL_SWIPE_THRESHOLD_VELOCITY;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// As paiego pointed out, it's better to use density-aware measurements.
DisplayMetrics dm = getResources().getDisplayMetrics();
REL_SWIPE_MIN_DISTANCE = (int)(1.0f * dm.densityDpi / 160.0f + 0.5);
REL_SWIPE_MAX_OFF_PATH = (int)(250.0f * dm.densityDpi / 160.0f + 0.5);
REL_SWIPE_THRESHOLD_VELOCITY = (int)(200.0f * dm.densityDpi / 160.0f + 0.5);
ListView lv = getListView();
lv.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
m_Starbucks));
final GestureDetector gestureDetector = new GestureDetector(new MyGestureDetector());
View.OnTouchListener gestureListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}};
lv.setOnTouchListener(gestureListener);
// Long-click still works in the usual way.
lv.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
String str = MessageFormat.format("Item long clicked = {0,number}", position);
Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
return true;
}
});
/*lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
String str = MessageFormat.format("Item #extra clicked = {0,number}", position);
Toast.makeText(MainActivity.this, str, Toast.LENGTH_SHORT).show();
}
});*/
}
// Do not use LitView.setOnItemClickListener(). Instead, I override
// SimpleOnGestureListener.onSingleTapUp() method, and it will call to this method when
// it detects a tap-up event.
private void myOnItemClick(int position, View v) {
String str = MessageFormat.format("Item clicked = {0,number}", position);
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}
private void onLTRFling() {
Toast.makeText(this, "Left-to-right fling", Toast.LENGTH_SHORT).show();
}
private void onRTLFling() {
Toast.makeText(this, "Right-to-left fling", Toast.LENGTH_SHORT).show();
}
class MyGestureDetector extends SimpleOnGestureListener{
// Detect a single-click and call my own handler.
#Override
public boolean onSingleTapUp(MotionEvent e) {
View lv = (View)getListView();
int pos = ((AbsListView) lv).pointToPosition((int)e.getX(), (int)e.getY());
myOnItemClick(pos,lv);
return false;
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
if (Math.abs(e1.getY() - e2.getY()) > REL_SWIPE_MAX_OFF_PATH)
return false;
if(e1.getX() - e2.getX() > REL_SWIPE_MIN_DISTANCE &&
Math.abs(velocityX) > REL_SWIPE_THRESHOLD_VELOCITY) {
onRTLFling();
} else if (e2.getX() - e1.getX() > REL_SWIPE_MIN_DISTANCE &&
Math.abs(velocityX) > REL_SWIPE_THRESHOLD_VELOCITY) {
onLTRFling();
}
return false;
}
}
private static final String[] m_Starbucks = {
"Latte", "Cappuccino", "Caramel Macchiato", "Americano", "Mocha", "White Mocha",
"Mocha Valencia", "Cinnamon Spice Mocha", "Toffee Nut Latte", "Espresso",
"Espresso Macchiato", "Espresso Con Panna"
};
}

Resources