Android ImageView with setAdjustViewBounds inside ConstraintLayout with inexplicable behavior - imageview

I have a Android ConstraintLayout inside a ScrollView.
I add a few buttons and an ImageView to the Layout. One of the buttons should constrain to the bottom of the ImageView.
Here arises the problem: As soon as I set the image to resize to the bounds of the view (setAdjustViewBounds), the image is resized exactly as I want, but the button that should follow the ImageView is drawn at the bottom of the image, not beneath it.
See the following output:
This is how it looks
When I add one more button, whose definition has to follow the ImageView-definition, then everything looks fine, even if its constraints have nothing to do with the image-constraints. If I move the definition of the additional button before the ImageView-definition I get the wrong output again, with the one additional button of course.
Here is the correct output:
And this is how it should look
But I don't want any additional declarations after the ImageView just to make the Layout work.
Does anyone have an idea, what goes wrong here?
Here is my code that produces the wrong position of button2 (Test 2):
public class MainActivity extends AppCompatActivity {
int id = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ScrollView sv = new ScrollView(this);
sv.setId(id++);
ConstraintLayout mainLayout = new ConstraintLayout(this);
mainLayout.setId(id++);
mainLayout.setLayoutParams(new ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
Button button = new Button(this);
button.setId(id++);
button.setText("Test");
mainLayout.addView(button);
Button button1 = new Button(this);
button1.setId(id++);
button1.setText("Test 1");
mainLayout.addView(button1);
ImageView image = new ImageView(this);
image.setId(id++);
image.setImageResource(R.drawable.img);
image.setBackgroundColor(Color.parseColor("#0000FF"));
image.setLayoutParams(new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_CONSTRAINT, ConstraintLayout.LayoutParams.WRAP_CONTENT));
image.setAdjustViewBounds(true);
mainLayout.addView(image);
Button button2 = new Button(this);
button2.setId(id++);
button2.setText("TEST 2");
mainLayout.addView(button2);
//getFragmentManager().beginTransaction().add(sv.getId(), BlankFragment.newInstance("Inhalt 1"), "someTag1").commit();
sv.addView(mainLayout);
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(mainLayout);
constraintSet.constrainWidth(button.getId(), constraintSet.MATCH_CONSTRAINT);
constraintSet.connect(button.getId(), ConstraintSet.TOP, mainLayout.getId(), ConstraintSet.TOP, 10);
constraintSet.connect(button.getId(), ConstraintSet.START, mainLayout.getId(), ConstraintSet.START, 50);
constraintSet.connect(button.getId(), ConstraintSet.END, mainLayout.getId(), ConstraintSet.END, 50);
constraintSet.constrainWidth(button1.getId(), constraintSet.MATCH_CONSTRAINT);
constraintSet.connect(button1.getId(), ConstraintSet.TOP, button.getId(), ConstraintSet.BOTTOM, 10);
constraintSet.connect(button1.getId(), ConstraintSet.START, button.getId(), ConstraintSet.START, 0);
constraintSet.connect(button1.getId(), ConstraintSet.END, button.getId(), ConstraintSet.END, 700);
constraintSet.constrainWidth(image.getId(), constraintSet.MATCH_CONSTRAINT);
constraintSet.connect(image.getId(), ConstraintSet.TOP, button1.getId(), ConstraintSet.BOTTOM, 10);
constraintSet.connect(image.getId(), ConstraintSet.START, button.getId(), ConstraintSet.START, 100);
constraintSet.connect(image.getId(), ConstraintSet.END, button.getId(), ConstraintSet.END, 100);
constraintSet.constrainWidth(button2.getId(), constraintSet.MATCH_CONSTRAINT);
constraintSet.connect(button2.getId(), ConstraintSet.TOP, image.getId(), ConstraintSet.BOTTOM, 10);
constraintSet.connect(button2.getId(), ConstraintSet.START, button.getId(), ConstraintSet.START, 0);
constraintSet.connect(button2.getId(), ConstraintSet.END, button.getId(), ConstraintSet.END, 0);
constraintSet.connect(button2.getId(), ConstraintSet.BOTTOM, mainLayout.getId(), ConstraintSet.BOTTOM, 10);
constraintSet.applyTo(mainLayout);
setContentView(sv);
}
}
And here is the code with the additional button, that moves button2 to the correct location. Nothing special, just the additional button.
public class MainActivity extends AppCompatActivity {
int id = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ScrollView sv = new ScrollView(this);
sv.setId(id++);
ConstraintLayout mainLayout = new ConstraintLayout(this);
mainLayout.setId(id++);
mainLayout.setLayoutParams(new ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
Button button = new Button(this);
button.setId(id++);
button.setText("Test");
mainLayout.addView(button);
Button button1 = new Button(this);
button1.setId(id++);
button1.setText("Test 1");
mainLayout.addView(button1);
ImageView image = new ImageView(this);
image.setId(id++);
image.setImageResource(R.drawable.img);
image.setBackgroundColor(Color.parseColor("#0000FF"));
image.setLayoutParams(new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_CONSTRAINT, ConstraintLayout.LayoutParams.WRAP_CONTENT));
image.setAdjustViewBounds(true);
mainLayout.addView(image);
//Even when button3 is left inside the example, but its declaration (next four lines) are moved above the imageview-initialization, the error occurs
Button button3 = new Button(this);
button3.setId(id++);
button3.setText("Test 3");
mainLayout.addView(button3);
Button button2 = new Button(this);
button2.setId(id++);
button2.setText("TEST 2");
mainLayout.addView(button2);
//getFragmentManager().beginTransaction().add(sv.getId(), BlankFragment.newInstance("Inhalt 1"), "someTag1").commit();
sv.addView(mainLayout);
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(mainLayout);
constraintSet.constrainWidth(button.getId(), constraintSet.MATCH_CONSTRAINT);
constraintSet.connect(button.getId(), ConstraintSet.TOP, mainLayout.getId(), ConstraintSet.TOP, 10);
constraintSet.connect(button.getId(), ConstraintSet.START, mainLayout.getId(), ConstraintSet.START, 50);
constraintSet.connect(button.getId(), ConstraintSet.END, mainLayout.getId(), ConstraintSet.END, 50);
constraintSet.constrainWidth(button1.getId(), constraintSet.MATCH_CONSTRAINT);
constraintSet.connect(button1.getId(), ConstraintSet.TOP, button.getId(), ConstraintSet.BOTTOM, 10);
constraintSet.connect(button1.getId(), ConstraintSet.START, button.getId(), ConstraintSet.START, 0);
constraintSet.connect(button1.getId(), ConstraintSet.END, button.getId(), ConstraintSet.END, 700);
constraintSet.constrainWidth(image.getId(), constraintSet.MATCH_CONSTRAINT);
constraintSet.connect(image.getId(), ConstraintSet.TOP, button1.getId(), ConstraintSet.BOTTOM, 10);
constraintSet.connect(image.getId(), ConstraintSet.START, button.getId(), ConstraintSet.START, 100);
constraintSet.connect(image.getId(), ConstraintSet.END, button.getId(), ConstraintSet.END, 100);
constraintSet.constrainWidth(button3.getId(), constraintSet.MATCH_CONSTRAINT);
constraintSet.connect(button3.getId(), ConstraintSet.TOP, button.getId(), ConstraintSet.BOTTOM, 10);
constraintSet.connect(button3.getId(), ConstraintSet.START, button.getId(), ConstraintSet.START, 700);
constraintSet.connect(button3.getId(), ConstraintSet.END, button.getId(), ConstraintSet.END, 0);
constraintSet.constrainWidth(button2.getId(), constraintSet.MATCH_CONSTRAINT);
constraintSet.connect(button2.getId(), ConstraintSet.TOP, image.getId(), ConstraintSet.BOTTOM, 10);
constraintSet.connect(button2.getId(), ConstraintSet.START, button.getId(), ConstraintSet.START, 0);
constraintSet.connect(button2.getId(), ConstraintSet.END, button.getId(), ConstraintSet.END, 0);
constraintSet.connect(button2.getId(), ConstraintSet.BOTTOM, mainLayout.getId(), ConstraintSet.BOTTOM, 10);
constraintSet.applyTo(mainLayout);
setContentView(sv);
}
}

Cheticamp's hint to check for the version was the solution. After updating to the latest ConstraintLayout version, currently 1.1.0-beta4, everything looks good. Seemed to be a problem of my older version, which was 1.0.2.

Related

Delete the created progress bars by pushing button on the console window

I am trying to learn myself use Qt so still struggling a lot... How would I go about deleting my bouncing object by pressing the delete button? I am struggling to implement the delete function...
MainWindow::MainWindow(QWidget * parent): QMainWindow(parent) {
setFixedSize(800, 600);
int start;
timer = new QTimer(this);
connect(timer , SIGNAL(timeout()), this, SLOT(step()));
timer -> setInterval(5);
timer -> start();
QPushButton * sillyLabel = new QPushButton(this);
connect(sillyLabel , SIGNAL(clicked()), this, SLOT(doSomethingSilly()));
sillyLabel -> setText("Spawn Object");
sillyLabel -> setGeometry(400, 400, 200, 50);
QPushButton * dLabel = new QPushButton(this);
connect(dLabel , SIGNAL(clicked()), this, SLOT(deleteSomethingSilly()));
dLabel -> setText("Delete Object");
dLabel -> setGeometry(200, 200, 200, 50);
MainWindow::~MainWindow() {
}
void MainWindow::doSomethingSilly() {
QProgressBar * sillyobject = new QProgressBar(this);
sillyobject -> setGeometry(400, 300, 80, 50);
sillyobject -> show();
sillyobject -> setValue(100);
bouncyobject.push_back(sillyobject);
}
void MainWindow::deleteSomethingSilly() {
delete ?;
}
You have to declare sillyobject inside the class header as a private pointer (to have access inside all the class members), then you just have to delete the pointer with :
delete sillyobject;
In your header file :
(...)
private :
QProgressBar * sillyobject
(...)
then
void MainWindow::doSomethingSilly() {
sillyobject = new QProgressBar(this);
sillyobject -> setGeometry(400, 300, 80, 50);
sillyobject -> show();
sillyobject -> setValue(100);
bouncyobject.push_back(sillyobject);
}
and :
void MainWindow::deleteSomethingSilly() {
delete sillyobject;
}

Getting subclassed qgraphicsitem coords when casting by type

I have a subclass of a qgraphicsitem to create a group of items that goes in another subclass that goes to a subclassed scene.
When casting by subclass I can get the items in the good scene coords but item.pos() = (0,0) or the wrong position using item->setPos();
1) Here I get the item coords but the wrong position when adding.
2) Here I get the item coords = (0,0) but the items are in the good position.
void addExtChordseistris()
{
QPoint p = QCursor::pos();
for(QGraphicsView *view: views()){
QWidget *viewport = view->viewport();
QRect vr = viewport->rect();
QPoint vp = viewport->mapFromGlobal(p);
if(vr.contains(vp)){
QPointF sp = view->mapToScene(vp);
chord *itemdos = new chord();
addItem(itemdos);
itemdos->addchorddos(sp);
itemdos->setPos(sp); // -> using this or not
}
}
}
This is what gives me pos.() = (0,0).
void debugSceneItemscuatro()
{
QList<QGraphicsItem *> allitems = items();
foreach(auto item, allitems) {
if(item->type() == chord::Type){
qDebug() << item->pos();
}
}
}
This is how the item were added:
#include "chord.h"
#include "note.h"
chord::chord()
{
Pressed = false;
setFlag(ItemIsMovable);
// QList<QGraphicsItem> coso;
}
QRectF chord::boundingRect() const
{
// outer most edges
return QRectF(0,0,100,100);
}
void chord::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
QRectF rect = boundingRect();
if(Pressed)
{
QPen pen(Qt::red, 3);
painter->setPen(pen);
// painter->drawEllipse(rect);
}
else
{
QPen pen(Qt::black, 3);
painter->setPen(pen);
// painter->drawRect(rect);
}
}
//[1] Add chord
void chord::addchord(QPointF sp)
{
// scene()->addLine(sp.x(), sp.y(), sp.x()+10, sp.y()+10);
auto *line = scene()->addLine(sp.x(), sp.y(), sp.x() + 10, sp.y() + 10);
line->setParentItem(this);
QList<int> midics = {10, 30, 40};
for(int i = 0; i < midics.length(); i++)
{
QGraphicsSimpleTextItem *item = new QGraphicsSimpleTextItem("n", this);
item->setFont(QFont("omheads", 20));
item->setPos(sp.x(), sp.y()+midics[i]);
// scene()->addItem(item);
coso.append(item);
}
}
//[1] Add chord
void chord::addchorddos(QPointF sp)
{
// scene()->addLine(sp.x(), sp.y(), sp.x()+10, sp.y()+10);
auto *line = scene()->addLine(sp.x(), sp.y(), sp.x() + 10, sp.y() + 10);
line->setParentItem(this);
QList<int> midics = {0, 10, 30, 40};
for(int i = 0; i < midics.length(); i++)
{
note *item = new note();
item->setParentItem(this);
QPointF ssp = {sp.x(), sp.y()+midics[i]};
item->addnote(ssp);
// item->setPos(sp.x(), sp.y()+midics[i]);
// scene()->addItem(item);
// coso.append(item);
}
}
//void chord::getobjects(QGraphicsItem item)
//{
// return coso;
//}
QList<QGraphicsItem*> chord::retrievedata() const
{
return coso;
}
void chord::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
Pressed = true;
update();
QGraphicsItem::mousePressEvent(event);
}
void chord::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
Pressed = false;
update();
QGraphicsItem::mouseReleaseEvent(event);
}
And the other subclass,..
void note::addnote(QPointF ssp)
{
QGraphicsSimpleTextItem *item = new QGraphicsSimpleTextItem("n", this);
item->setFont(QFont("omheads", 20));
item->setPos(ssp.x(), ssp.y());
}
Thanks,...maybe I'm doing a wrong approach, thanks for any Idea, solution...! :-)

Qt5 menubar's action with text "Exit" just disappear

I was trying to write a QT application inherit from QMainWindow class.
I use Qt's ui to write a menubar with three Action:Open Close Exit.
The configuration of each Action looks same, but the last one just disappear.
By the way, the generated ui_mainwindow.h is:
class Ui_MainWindow
{
public:
QAction *actionOpen;
QAction *actionClose;
QAction *actionExit;
QWidget *centralWidget;
QHBoxLayout *horizontalLayout;
QTextBrowser *textBrowser;
QMenuBar *menuBar;
QMenu *menuFile_2;
QToolBar *mainToolBar;
QStatusBar *statusBar;
void setupUi(QMainWindow *MainWindow)
{
if (MainWindow->objectName().isEmpty())
MainWindow->setObjectName(QStringLiteral("MainWindow"));
MainWindow->resize(400, 300);
actionOpen = new QAction(MainWindow);
actionOpen->setObjectName(QStringLiteral("actionOpen"));
actionClose = new QAction(MainWindow);
actionClose->setObjectName(QStringLiteral("actionClose"));
actionExit = new QAction(MainWindow);
actionExit->setObjectName(QStringLiteral("actionExit"));
centralWidget = new QWidget(MainWindow);
centralWidget->setObjectName(QStringLiteral("centralWidget"));
horizontalLayout = new QHBoxLayout(centralWidget);
horizontalLayout->setSpacing(6);
horizontalLayout->setContentsMargins(11, 11, 11, 11);
horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
textBrowser = new QTextBrowser(centralWidget);
textBrowser->setObjectName(QStringLiteral("textBrowser"));
horizontalLayout->addWidget(textBrowser);
MainWindow->setCentralWidget(centralWidget);
menuBar = new QMenuBar(MainWindow);
menuBar->setObjectName(QStringLiteral("menuBar"));
menuBar->setGeometry(QRect(0, 0, 400, 22));
menuFile_2 = new QMenu(menuBar);
menuFile_2->setObjectName(QStringLiteral("menuFile_2"));
MainWindow->setMenuBar(menuBar);
mainToolBar = new QToolBar(MainWindow);
mainToolBar->setObjectName(QStringLiteral("mainToolBar"));
MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar);
statusBar = new QStatusBar(MainWindow);
statusBar->setObjectName(QStringLiteral("statusBar"));
MainWindow->setStatusBar(statusBar);
menuBar->addAction(menuFile_2->menuAction());
menuFile_2->addAction(actionOpen);
menuFile_2->addAction(actionClose);
menuFile_2->addAction(actionExit);
retranslateUi(MainWindow);
QMetaObject::connectSlotsByName(MainWindow);
} // setupUi
void retranslateUi(QMainWindow *MainWindow)
{
MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", Q_NULLPTR));
actionOpen->setText(QApplication::translate("MainWindow", "Open", Q_NULLPTR));
actionOpen->setShortcut(QApplication::translate("MainWindow", "Alt+0", Q_NULLPTR));
actionClose->setText(QApplication::translate("MainWindow", "Close", Q_NULLPTR));
actionClose->setShortcut(QApplication::translate("MainWindow", "Alt+C", Q_NULLPTR));
actionExit->setText(QApplication::translate("MainWindow", "Exit", Q_NULLPTR));
menuFile_2->setTitle(QApplication::translate("MainWindow", "File", Q_NULLPTR));
} // retranslateUi
};

How to change Background color of child in GTK3 (c language) with CSS

Like the Title says I'm developing an App and I need to set the Background of all 4 child, but I have no clue how to do it.
Here is an Example which explains how the App looks like:
I need to change the backgrounds like this:
One - should be Red
Two - should be yellow
Three - Should be green
Four - Should be Blue
Here is the Code:
#include <gtk/gtk.h>
int main(int argc, char *argv[]){
//---------- CSS -------------
GtkCssProvider *provider;
GdkDisplay *display;
GdkScreen *screen;
//---------------------------
GtkWidget *window;
GtkWidget *child, *child2, *child3, *child4;
GtkWidget *grid;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "Equalizer");
gtk_window_set_default_size(GTK_WINDOW(window), 400, 250);
gtk_container_set_border_width(GTK_CONTAINER(window), 5);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
// ---------------------------------------------------- CSS -----------------------------------------------------------
provider = gtk_css_provider_new ();
display = gdk_display_get_default ();
screen = gdk_display_get_default_screen (display);
gtk_style_context_add_provider_for_screen (screen, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
const gchar *myCssFile = "mystyle.css";
GError *error = 0;
gtk_css_provider_load_from_file(provider, g_file_new_for_path(myCssFile), &error);
g_object_unref (provider);
// --------------------------------------------------------------------------------------------------------------------
grid = gtk_grid_new ();
gtk_container_add (GTK_CONTAINER (window), grid);
child = gtk_label_new ("One");
gtk_grid_attach (GTK_GRID (grid), child, 0, 1, 1, 1);
child2 = gtk_label_new ("Two");
g_object_set (child, "margin", 55, NULL);
gtk_grid_attach_next_to (GTK_GRID (grid), child2, child, GTK_POS_RIGHT, 50, 50);
child3 = gtk_label_new ("Three");
g_object_set (child3, "margin", 55, NULL);
gtk_grid_attach_next_to (GTK_GRID (grid), child3, child2, GTK_POS_RIGHT, 50, 50);
child4 = gtk_label_new ("Four");
g_object_set (child4, "margin", 0, NULL);
gtk_grid_attach_next_to (GTK_GRID (grid), child4, child3, GTK_POS_RIGHT, 50, 50);
gtk_widget_show_all(window);
gtk_main();
}
and the CSS file:
* {
background-color: yellow;
}
GtkWindow {
background-color: green;
border-width: 3px;
border-color: blue;
}
How do I do it in GTK3 using CSS ?
You probably need to give each of your widgets a name in order to give them styles by ID, e.g. #child3.
The name you would need to set in this case would be child3.
void
gtk_widget_set_name (GtkWidget *widget,
const gchar *name);
https://developer.gnome.org/gtk3/stable/GtkWidget.html#gtk-widget-set-name

JavaFx zooming to mouse as pivot

I have tried the this example given in another post to learn about zooming and panning relative to the mouse pointer. When everything is on the grid, zooming works as expected:
When zooming into the mouse pointer location on the top left image, it is zoomed into the exact location as seen in the top right image.
If something is dragged off the grid, e.g. the pivot starts to 'misbehave':
When zooming into the mouse pointer location on the bottom left image, it is zoomed into a location other than the one intended, seen in the bottom right image.
The bounds of the canvas inside the parent changes from 600x600 (without scale) to something like 600x700… Which affects the outcomes dx, dy of the following function.
double dx = (event.getSceneX() - (canvas.getBoundsInParent().getWidth()/2 + canvas.getBoundsInParent().getMinX()));
double dy = (event.getSceneY() - (canvas.getBoundsInParent().getHeight()/2 + canvas.getBoundsInParent().getMinY()));
When editing this function by changing .getWidth() to .getHeight() and then again move the rectangle out right… the zoom works correctly. However, if the rectangle is moved out vertically (to the bottom or top) and to the left the problem again is reproduced again.
Is the above function correct, what is it trying to do? Why does the zoom not work the same, as when everything was on the grid?
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
class PannableCanvas extends Pane {
DoubleProperty myScale = new SimpleDoubleProperty(1.0);
public PannableCanvas() {
setPrefSize(600, 600);
setStyle("-fx-background-color: lightgrey; -fx-border-color: blue;");
// add scale transform
scaleXProperty().bind(myScale);
scaleYProperty().bind(myScale);
}
/**
* Add a grid to the canvas, send it to back
*/
public void addGrid() {
double w = getBoundsInLocal().getWidth();
double h = getBoundsInLocal().getHeight();
// add grid
Canvas grid = new Canvas(w, h);
// don't catch mouse events
grid.setMouseTransparent(true);
GraphicsContext gc = grid.getGraphicsContext2D();
gc.setStroke(Color.GRAY);
gc.setLineWidth(1);
// draw grid lines
double offset = 50;
for( double i=offset; i < w; i+=offset) {
gc.strokeLine( i, 0, i, h);
gc.strokeLine( 0, i, w, i);
}
getChildren().add( grid);
grid.toBack();
}
public double getScale() {
return myScale.get();
}
public void setScale( double scale) {
myScale.set(scale);
}
public void setPivot( double x, double y) {
setTranslateX(getTranslateX()-x);
setTranslateY(getTranslateY()-y);
}
}
/**
* Mouse drag context used for scene and nodes.
*/
class DragContext {
double mouseAnchorX;
double mouseAnchorY;
double translateAnchorX;
double translateAnchorY;
}
/**
* Listeners for making the nodes draggable via left mouse button. Considers if parent is zoomed.
*/
class NodeGestures {
private DragContext nodeDragContext = new DragContext();
PannableCanvas canvas;
public NodeGestures( PannableCanvas canvas) {
this.canvas = canvas;
}
public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
return onMousePressedEventHandler;
}
public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
return onMouseDraggedEventHandler;
}
private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
// left mouse button => dragging
if( !event.isPrimaryButtonDown())
return;
nodeDragContext.mouseAnchorX = event.getSceneX();
nodeDragContext.mouseAnchorY = event.getSceneY();
Node node = (Node) event.getSource();
nodeDragContext.translateAnchorX = node.getTranslateX();
nodeDragContext.translateAnchorY = node.getTranslateY();
}
};
private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
// left mouse button => dragging
if( !event.isPrimaryButtonDown())
return;
double scale = canvas.getScale();
Node node = (Node) event.getSource();
node.setTranslateX(nodeDragContext.translateAnchorX + (( event.getSceneX() - nodeDragContext.mouseAnchorX) / scale));
node.setTranslateY(nodeDragContext.translateAnchorY + (( event.getSceneY() - nodeDragContext.mouseAnchorY) / scale));
event.consume();
}
};
}
/**
* Listeners for making the scene's canvas draggable and zoomable
*/
class SceneGestures {
private static final double MAX_SCALE = 10.0d;
private static final double MIN_SCALE = .1d;
private DragContext sceneDragContext = new DragContext();
PannableCanvas canvas;
public SceneGestures( PannableCanvas canvas) {
this.canvas = canvas;
}
public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
return onMousePressedEventHandler;
}
public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
return onMouseDraggedEventHandler;
}
public EventHandler<ScrollEvent> getOnScrollEventHandler() {
return onScrollEventHandler;
}
private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
// right mouse button => panning
if( !event.isSecondaryButtonDown())
return;
sceneDragContext.mouseAnchorX = event.getSceneX();
sceneDragContext.mouseAnchorY = event.getSceneY();
sceneDragContext.translateAnchorX = canvas.getTranslateX();
sceneDragContext.translateAnchorY = canvas.getTranslateY();
}
};
private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
// right mouse button => panning
if( !event.isSecondaryButtonDown())
return;
canvas.setTranslateX(sceneDragContext.translateAnchorX + event.getSceneX() - sceneDragContext.mouseAnchorX);
canvas.setTranslateY(sceneDragContext.translateAnchorY + event.getSceneY() - sceneDragContext.mouseAnchorY);
event.consume();
}
};
/**
* Mouse wheel handler: zoom to pivot point
*/
private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double delta = 1.2;
double scale = canvas.getScale(); // currently we only use Y, same value is used for X
double oldScale = scale;
if (event.getDeltaY() < 0)
scale /= delta;
else
scale *= delta;
scale = clamp( scale, MIN_SCALE, MAX_SCALE);
double f = (scale / oldScale)-1;
double dx = (event.getSceneX() - (canvas.getBoundsInParent().getWidth()/2 + canvas.getBoundsInParent().getMinX()));
double dy = (event.getSceneY() - (canvas.getBoundsInParent().getHeight()/2 + canvas.getBoundsInParent().getMinY()));
canvas.setScale( scale);
// note: pivot value must be untransformed, i. e. without scaling
canvas.setPivot(f*dx, f*dy);
event.consume();
}
};
public static double clamp( double value, double min, double max) {
if( Double.compare(value, min) < 0)
return min;
if( Double.compare(value, max) > 0)
return max;
return value;
}
}
/**
* An application with a zoomable and pannable canvas.
*/
public class ZoomAndScrollApplication extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Group group = new Group();
// create canvas
PannableCanvas canvas = new PannableCanvas();
// we don't want the canvas on the top/left in this example => just
// translate it a bit
canvas.setTranslateX(100);
canvas.setTranslateY(100);
// create sample nodes which can be dragged
NodeGestures nodeGestures = new NodeGestures( canvas);
Label label1 = new Label("Draggable node 1");
label1.setTranslateX(10);
label1.setTranslateY(10);
label1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
label1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());
Label label2 = new Label("Draggable node 2");
label2.setTranslateX(100);
label2.setTranslateY(100);
label2.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
label2.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());
Label label3 = new Label("Draggable node 3");
label3.setTranslateX(200);
label3.setTranslateY(200);
label3.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
label3.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());
Circle circle1 = new Circle( 300, 300, 50);
circle1.setStroke(Color.ORANGE);
circle1.setFill(Color.ORANGE.deriveColor(1, 1, 1, 0.5));
circle1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
circle1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());
Rectangle rect1 = new Rectangle(100,100);
rect1.setTranslateX(450);
rect1.setTranslateY(450);
rect1.setStroke(Color.BLUE);
rect1.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.5));
rect1.addEventFilter( MouseEvent.MOUSE_PRESSED, nodeGestures.getOnMousePressedEventHandler());
rect1.addEventFilter( MouseEvent.MOUSE_DRAGGED, nodeGestures.getOnMouseDraggedEventHandler());
canvas.getChildren().addAll(label1, label2, label3, circle1, rect1);
group.getChildren().add(canvas);
// create scene which can be dragged and zoomed
Scene scene = new Scene(group, 1024, 768);
SceneGestures sceneGestures = new SceneGestures(canvas);
scene.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
scene.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
scene.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());
stage.setScene(scene);
stage.show();
canvas.addGrid();
}
}
As nobody answered the question up until now and I stumbled over the same problem, I will post my solution, which adds a simple calculation of the left/up/lower and right overhang of nodes.
If you replace the part of the zooming-code with the part attached below, you should be good to got.
//maxX = right overhang, maxY = lower overhang
double maxX = canvas.getBoundsInParent().getMaxX() - canvas.localToParent(canvas.getPrefWidth(), canvas.getPrefHeight()).getX();
double maxY = canvas.getBoundsInParent().getMaxY() - canvas.localToParent(canvas.getPrefWidth(), canvas.getPrefHeight()).getY();
// minX = left overhang, minY = upper overhang
double minX = canvas.localToParent(0,0).getX() - canvas.getBoundsInParent().getMinX();
double minY = canvas.localToParent(0,0).getY() - canvas.getBoundsInParent().getMinY();
// adding the overhangs together, as we only consider the width of canvas itself
double subX = maxX + minX;
double subY = maxY + minY;
// subtracting the overall overhang from the width and only the left and upper overhang from the upper left point
double dx = (event.getSceneX() - ((canvas.getBoundsInParent().getWidth()-subX)/2 + (canvas.getBoundsInParent().getMinX()+minX)));
double dy = (event.getSceneY() - ((canvas.getBoundsInParent().getHeight()-subY)/2 + (canvas.getBoundsInParent().getMinY()+minY)));
WARNING: The left and up overhang will always be computed correctly, but I did not find any working way, to compute the right and lower overhang of nodes without the use of the preferred height and width attributes. So keep in mind, that you need these.
Also, you can improve the performance by only computing the canvas.getBoundsInParent() thing only once before as well as the the other calculations that are computed multiple times.
Hope it helps someone.

Resources