Přetahování elemetů mezi dvěma seznamy patří většinou do příjemných rozšíření aplikací a přináší zpětnou vazbu a jednoduchost do jinak těžce realizovatelných funkcí.
Začněme definováním jednoduchého grafického seznamu elementů. Nejdříve nadefinuji třídu Container, která se bude starat o seznam elementů, zobrazí k nim pozadí a bude je udržovat zarovnané pod sebou.
class Container extends CustomNode {
var list: Group = Group {}
var nodes: Node[] on replace {
var t = 0;
for (node in nodes) {
node.translateY = t;
t += node.layoutBounds.height + 10;
}
list.content = nodes;
}
override function create() {
Group {
content: [
Rectangle {
x: bind list.boundsInParent.minX - 5
y: bind list.boundsInParent.minY - 5
width: bind list.boundsInParent.width + 10
height: bind list.boundsInParent.height + 10
fill: Color.YELLOW
},
list
]
}
}
}
Jako elementy pro jednoduchost použiju obyčejné obdélníky vyplněné náhodnými barvami, kterými následně vyplním Container.
class Draggable extends CustomNode {
var container : Container;
var rect = Rectangle {
width: 80
height: 30
fill: randomColor()
}
override function create() {
rect
}
function randomColor() : Color {
Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255))
}
}
var container1 = Container {
translateX: 10
translateY: 50
}
container1.nodes = [
Draggable { container: container1 },
Draggable { container: container1 },
Draggable { container: container1 },
Draggable { container: container1 }
];
Stejným způsobem pak vytvořím container2 a oba vložím spolu s jednoduchým nadpisem do scény.
Stage {
title: TITLE
scene: Scene {
width: 240
height: 320
content: [
header,
container1,
container2
]
}
}
Pro funkčnost drag&drop je třeba, aby každý z vytvořených obdélníků reagoval na onMouseDragged.
Rectangle {
width: 80
height: 30
fill: randomColor()
onMouseDragged: function(e) {
this.translateX = e.dragX;
this.translateY = e.dragY;
}
}
Tato úprava sice způsobí, že se element stane táhnuteným, bohužel ale rozbije všechno ostatní. Při táhnutí je potřeba obdélník ze seznamu vyjmout a vložit do scény samostatně. Nejdříve přidám proměnou pro aktuálně tažený elment a zajistím aby se zobrazil ve scéně.
// aktuálně tažený element
var dragged: Draggable;
Stage {
scene: Scene {
content: [
header,
container1,
container2,
Group {
// zobrazuje aktuálně tažený element
content: bind [dragged]
}
]
}
}
Následně vložím kód zpracovávající reakci na drag a release. Funkce přidávám přímo do třídy Draggable pro větší přehlednost:
class Draggable extends CustomNode {
override var onMouseDragged = function(e) {
if (not drag) {
drag = true;
delete this from container.nodes;
dragged = this;
// napozicovat element "pod myš"
rect.x = e.sceneX - e.x;
rect.y = e.sceneY - e.y;
}
// posunout element o relativní posun myši
translateX = e.dragX;
translateY = e.dragY;
}
override var onMouseReleased = function (e) {
if (drag) {
drag = false;
// všechno se vrátí do původního stavu
translateX = 0;
translateY = 0;
rect.x = 0;
rect.y = 0;
dragged = null;
insert this into container.nodes;
}
}
}
Teď už se táhnutelné elemety chovají tak jak mají – pohybují se spolu s myší a při upuštění se vrátí zpět do původního seznamu. Aby se element při upuštění zařadil do správného seznamu, musí každý Container reagovat na onMouseEntered a onMouseExited. Je tedy třeba říct táhnutému elementu, kam má upadnout. A dále je potřeba zajistit, aby se element upuštěný jenom tak v prostoru vrátil na původní místo.
class Draggable extends CustomNode {
var startingContainer: Container;
var container : Container on replace {
if (container == null and startingContainer != null) {
container = startingContainer;
}
};
}
class Container extends CustomNode {
override var onMouseEntered = function(e) {
// já jsem teď tvůj nový container
dragged.container = this;
}
override var onMouseExited = function(e) {
// zapomeň na mě, vrať se odkud si přišel
dragged.container = null;
}
}
Teď už mám plně funkční kód a zbývá jenom přidat trochu vizuální odezvy. Přidáním jedné řádky se táhnuté elementy stanou lehce průhlednými a aktivní seznamy se rozsvítí po najetí myši.
class Draggable extends CustomNode {
override var opacity = bind if (drag) 0.6 else 1
}
class Container extends CustomNode {
override function create() {
Group {
content: [
Rectangle {
fill: bind if (hover and dragged != null) Color.GREEN else Color.YELLOW
},
list
]
}
}
}
Jak to celé funguje si můžeš prohlédnout tu:
Zdrojové kódy jsou k dispozici na githubu: simple-drag-drop