Move Codename One Components with the finger - codenameone

I need to implement a complex feature with Codename One that involves the moving of a tapped component (like a Button) on the screen: the users will have some buttons on the layered pane and they should be able to move them using the finger. Abstracting my problem to make it as more general as possible, these are the requirements:
there are several buttons in the layered pane;
each button has an initial position calculated by an algorithm that needs to know the screen size;
the position of each button can be inside the visible part of the screen or out of it (for example the x/y position can be negative or bigger than the screen size);
if the user only taps a button, its action listener should be invoked...
... instead, if the user taps a button and moves the finger while continuing to press the screen, my algorithm should receive the x/y axis movement of the finger in real time and it should be able to update the position of all buttons while the finger moves.
It's not a game. Here I abstracted the problem to adapt my requirements to several situation. How can I implement these things in Codename One?
A "simple" use case, to better understand what I mean, is for example the moving of buttons disposed in a circle: in this example, the user can tap a single button or he/she can rotate all the circle of buttons moving the finger while tapping a button.

You can use setDraggable(true) and make the drop containers into a droppable target using setDropTarget(true). Once you do that the default behavior of Container will allow you to visually rearrange/move components around between droppable Container instances. You can simply override the default drop method in Container with something smarter that implements the functionality you want:
public void drop(Component dragged, int x, int y) {
int i = getComponentIndex(dragged);
if(i > -1) {
Component dest = getComponentAt(x, y);
if(dest != dragged) {
int destIndex = getComponentIndex(dest);
if(destIndex > -1 && destIndex != i) {
setComponentIndex(dragged,destIndex);
}
}
animateLayout(400);
} else {
Container oldParent = dragged.getParent();
if(oldParent != null) {
oldParent.removeComponent(dragged);
}
Component pos = getComponentAt(x, y);
i = getComponentIndex(pos);
if(i > -1) {
addComponent(i, dragged);
} else {
addComponent(dragged);
}
getComponentForm().animateHierarchy(400);
}
}

Related

Make videos scrollable

I have a scrollabillity issue in a Form, layered out by BoxLayout.y().
The form contains many Tabs (with fixed size), each tab of the Tabs can contain an image or a video (the video is inside a BorderLayout to scale it at the tab size).
If an image is shown, the scrolling up and down works correctly.
If a video is shown, the y scrolling is not possibile, I can only swipe to change the tab.
I suppose that the cause of this issue is that videos are native component (I used the Codename One API to show the videos).
How can I solve or workaround this issue? This is crucial for the app design. Thanks for the tips.
The video.setEnabled(false) workaround (Make videos scrollable) doesn't work.
I workaround in a different way, inserting the MediaPlayer container in a LayeredLayout container, and then placing a Button over the MediaPlayer. A generic empty Label doesn't work, but an empty Button works. Of course I have to override calcPreferredSize to make the MediaPlayer and the Button of the same size (or use a different approach to make them of the same size).
This allows scrolling, but prevents the tapping of the play and pause Buttons (that are over the video). I solved also this issue.
In short, this is my solution, tested on Simulator, Android and iOS (in the following code, note that videoBtnsCnt is a Container over the video, in which I inserted play and pause Buttons):
MediaPlayer mediaPlayer = new MediaPlayer(video) {
#Override
public Dimension getPreferredSize() {
return new Dimension(size, size);
}
};
Container mediaPlayerCnt = new Container(new LayeredLayout(), "NoMarginNoPadding") {
#Override
public Dimension getPreferredSize() {
return new Dimension(size, size);
}
};
mediaPlayerCnt.add(mediaPlayer);
Button allowScrollingLabel = new Button() {
#Override
public Dimension getPreferredSize() {
return new Dimension(size, size);
}
};
allowScrollingLabel.setUIID("NoMarginNoPadding");
allowScrollingLabel.addActionListener(l -> {
Component responder = videoBtnsCnt.getResponderAt(l.getX(), l.getY());
if (responder instanceof Button) {
// it can be a play button or a pause button
((Button) responder).pressed();
((Button) responder).released();
}
});
mediaPlayerCnt.add(allowScrollingLabel);

What is the expected behaviour of a shrinking scrollable Container?

With Codename One components can change their size and this even can be animated, which is nice.
But what is the expected behaviour with shrinking Container instances, specifically if they are scrollable?
Having a Container, scrollable in the Y-axis and content that is smaller than the container the content sticks to the top. The container can be dragged and the content scrolls back to the top of the container. However, when the content shrinks and becomes smaller than the container the behaviour is - somewhat strange.
I have made an example - when the coloured components are tapped they expand. If tapped again they shrink. One can tap the green Label and it expands. Tapping it again it shrinks and everything is as of before.
However, if one taps a coloured label and scrolls any amount down before tapping it again to shrink it this is the resulting view:
Only if one then drags the scrolling container ever so lightly then the contant scrolls back to the top.
I assume this is a bug. Though I wonder - what should be the behaviour when the content shrinks beyond the scrolling containers size? Since the user might expect the component just tapped to remain where it was - can the scrolling behaviour be controlled somehow?
This is just an example but I want to build expandable "drawer" components.
Here is the code:
public class FormContentSmallerScrollableContainer extends Form {
private class LabelExpandable extends Label {
boolean expanded = false;
LabelExpandable(String aTitle, int aColor) {
super(aTitle);
getAllStyles().setBgPainter((aGraphics, aRectangle) -> {
aGraphics.setColor(aColor);
aGraphics.fillRoundRect(getX(), getY(), aRectangle.getWidth() - 1, aRectangle.getHeight() - 1, 20, 20);
});
setOpaque(true);
}
#Override
public void pointerReleased(int x, int y) {
super.pointerReleased(x, y);
expanded = !expanded;
setShouldCalcPreferredSize(true);
getParent().animateLayout(400);
}
#Override
protected Dimension calcPreferredSize() {
if (!expanded) {
return super.calcPreferredSize();
}
Dimension dimension = super.calcPreferredSize();
dimension.setHeight(Display.getInstance().getDisplayHeight());
return dimension;
}
}
public FormContentSmallerScrollableContainer() {
super("FormContentSmallerScrollableContainer");
setScrollable(false);
setLayout(new BorderLayout());
add(BorderLayout.NORTH, new Label("The container below is scrollable in the y-axis"));
Container containerScrollable = BoxLayout.encloseY(
new LabelExpandable("LabelExpandable green", 0x00ff00),
new LabelExpandable("LabelExpandable blue", 0x00c0ff));
containerScrollable.setScrollableY(true);
add(BorderLayout.CENTER, containerScrollable);
add(BorderLayout.SOUTH, new Label("The container above is scrollable in the y axis"));
}
}
This is a long standing behavior when we animate a scrollable area to what's effectively a non-scrollable area. We tried working around this in the past but couldn't find the right approach. You can file the issue on this but I'm not optimistic as it's really hard to even find a workaround for this.

How to handle pointer events in both a Component and its Container?

Suppose I have a custom slider component, many of those, in a BoxLayout.Y-scrollable-Y Container. The custom slider components cover all of the container.
What I want is that the slider components handle pointerDragged in the X-Axis while still having the Container beeing draggable in order to scroll the "list" just as if there where no slider components at all.
The only way I can think of to achieve this seems to be a hack. I'd do that by overriding some methods in the slider components in a way that it would forward pointer event to its Container like this:
#Override
protected int getDragRegionStatus(int x, int y) {
return Component.DRAG_REGION_LIKELY_DRAG_XY;
}
#Override
public void pointerDragged(int x, int y) {
super.pointerDragged(x, y);
{
// Handle X-axis dragging here
}
boolean focusable = getParent().isFocusable();
try { // handle Y-axis dragging there
getParent().setFocusable(true);
getParent().pointerDragged(x, y);
} finally {
getParent().setFocusable(focusable);
}
}
I doubt that this is the way this should be done. But just as a wrote above this is the only way I can think of how to make it work with Codename One.
But how would You get this working?
Why is it a hack?
You're trying to achieve a special case, you can do it by overriding the form pointer events or binding a listener to the form itself.

Dragging a SOUTH container is incorrectly propagated to an underlying element in the CENTER container

I have a BorderLayout contentPane. In CENTER there is a list of elements which are all SwipeableContainer, in SOUTH, there is another SwipeableContainer. The SOUTH container is of course 'on top of' the CENTER list, but even then, when I swipe the SOUTH Swipeable, the underlying (half hidden) Swipeable is also swiped. So, it seems that the drag events are propagated to the below list even though the shouldn't.
The below example illustrates it: run the code and drag the "SOUTH CONTAINER" left, and the "ListElement" container below it will also swipe left.
To be sure to see the problem, swipe at the top of the SOUTH CONTAINER (so the pointer is also above the lowest visible ListElement), otherwise it could be the completely hidden ListElement that is swiped.
I guess this is a bug (I don't think the drag should affect an element which is partially 'hidden' below the visually dragged element), but if it's not, I'd appreciate input on how to avoid it since it is visually very disturbing.
//TEST: SWIPEABLE CONTAINER ALSO SWIPES UNDERLYING CONTAINER AS WELL
Form hi = new Form("Welcome", new BorderLayout());
Container list = new Container(BoxLayout.y());
for (int i = 0; i < 20; i++) {
SwipeableContainer swip = new SwipeableContainer(null, new Label("SWIPE"), new SpanLabel("ListElement " + i + " + a lot of fill text to make the element span over several lines so the dragging of the underlying Swipeable is normally noticeable"));
list.add(swip);
}
list.setScrollableY(true);
Container cont = hi.getContentPane();
cont.add(BorderLayout.CENTER, list);
SwipeableContainer swip = new SwipeableContainer(null, new Label("SOUTHSWIPE"), new Label("SOUTH CONTAINER"));
cont.add(BorderLayout.SOUTH, swip);
hi.show();
It's a bug but looking at the code I'm hard pressed on how to fix that as the code didn't foresee this sort of layout. We'll probably need to create a special case for dealing with this type of layout. I suggest filing an issue in the issue tracker with the test case code.

Changing scroll bar color programmatically

I have a requirement to change the color of the scroll bar based on some user selection.
In a typical form, I have switch case where is I am setting the scroll bar color using:
#Override
protected void beforeTopic(final Form f) {
int scrollColor=0x000000;
switch(userSelectedTopic)
{
case 1:
scrollColor=0x59be8a;
break;
case 2:
scrollColor = 0xff3333;
break;
.
.
.
}
// setting color to scroll thumb
Style s = UIManager.getInstance().getComponentStyle("ScrollThumb");
System.out.println(scrollColor);
s.setFgColor(scrollColor);
s.setBgColor(scrollColor);
s.setBgTransparency(255);
UIManager.getInstance().setComponentStyle("ScrollThumb", s);
s = UIManager.getInstance().getComponentStyle("ScrollThumb");
System.out.println("-->>"+s.getFgColor());
}
What happens is that the color code is picked properly for the first time.
When this form is invoked again, with a different user selection, the color code value changes as per the switch statement. The style attributes also change.
However, the initial color applied to the thumb prevails!
What could be the issue?
I tried f.refreshTheme(); but this does not seem to work. It just sustains the first applied color
That won't work. getComponentStyle will always return a copy which is the way it should be. Otherwise when you do something like getUnselectedStyle().setFgColor() you will change the color of all the components.
One way to do that is to update the theme, you can define another theme that just sets the color of the scroll then apply it using addThemeProps of the UIManager and then invoke refreshTheme() on the form.
The alternative is to derive the scrollable component and paint the scroll yourself or hide the scroll entirely and paint it yourself using the glasspane or something like that.

Resources