TL;DR - My understanding of LayoutRole
“Independent” is that the element should no longer take its layout from its parent. If I set the LayoutRole
of an element to “Independent”, where does it inherit its layout from?
I’ve whittled down my use case to the bare bones and will try to explain what I’m attempting by walking through the code piece by piece (full code all the way at the bottom). For a visual of what I’m trying to accomplish, picture your iOS home screen - draggable tiles that are attracted to their spot in the grid, but whose location changes based on certain events. When released, the tile seamlessly gravitates to its new spot.
Code Walkthrough
Let me set the stage. Say I have a “Tile”, which is just a rectangle that can be dragged and triggers a startDrag
and endDrag
at the appropriate times.
<Panel ux:Class="Tile">
<Rectangle Width="60" Height="60" ux:Name="rect" Color="#ddda" />
<Draggable />
<WhileDragging>
<Callback Handler="{startDrag}" />
</WhileDragging>
<Released>
<Callback Handler="{endDrag}" />
</Released>
</Panel>
Suppose that I always want that draggable Tile to snap to it’s “original” or “home” location, so I pair it with a PointAttractor
in its own physics world - TileWorld
. We’ll draw a circle at the center of this world for visual debugging and give it a LayoutChange
LayoutAnimation
for when it moves.
<Panel ux:Class="TileWorld" Physics.IsPhysicsWorld="true">
<Tile ux:Name="tile" />
<PointAttractor Exclusive="true" Radius="1000" Strength="1000" />
<Circle Color="#0005" Width="10" Height="10" />
<LayoutAnimation>
<Move RelativeTo="LayoutChange" X="1" Y="1" Duration="0.2" />
</LayoutAnimation>
</Panel>
Now, what I’d like to do with this Tile (in this contrived simplification of my actual use case) is have it move from one side of the screen to the other when some condition is met - for example, a button is pressed. So I set up a 2-column grid and specify which column it should occupy.
<Grid RowCount="1" ColumnCount="2">
<TileWorld Column="{col}" />
</Grid>
Add some test buttons and give it a go:
So far so good.
Now, let’s say I want to have that switch happen after the Tile is dragged for 2 seconds. I add a call to my change function in the WhileDragging
trigger:
<Callback Handler="{change}" Delay="2" />
But, no surprise that my whole TileWorld, including the Tile gets translated over.
I’d really like the Tile itself (i.e. the rectangle, but not the point attractor) to stay with my mouse, though, so I set its LayoutRole
of the Tile to Independent
while dragging.
<Panel ux:Class="TileWorld" ux:Name="self" Physics.IsPhysicsWorld="true">
<Tile ux:Name="tile" />
...
<WhileTrue Value="{dragging}">
<Change tile.LayoutRole="Independent" /> <!-- Tried `Set` here, too -->
</WhileTrue>
</Panel>
My expectation is that the element is “detached” from its parent layout. It’s still being dragged, so I’d expect its position stay with the mouse, but neither of those things happen. The Tile moves with its parent layout rather than staying with the mouse. It seems to behave just like it used to without setting the LayoutRole
.
Am I using LayoutRole
wrong?
Close, but no cigar
The closest I’ve gotten to solving this problem is by setting the LayoutRole
of the entire TileWorld to “Independent”. However, since it then doesn’t inherit from its parent layout, I have sort of force it to re-layout when I stop dragging and it has this effect as the LayoutAnimation
happens at the same time as the PointAttractor
physics.
Full Code
<App>
<JavaScript>
var Observable = require('FuseJS/Observable');
var col = Observable(0);
var dragging = Observable(false);
module.exports = {
col,
dragging,
change: () => { col.value = (col.value + 1) % 2; },
startDrag: () => { dragging.value = true; },
endDrag: () => { dragging.value = false; }
}
</JavaScript>
<Panel ux:Class="Tile">
<Rectangle Width="60" Height="60" ux:Name="rect" Color="#ddda" />
<Draggable />
<WhileDragging>
<Callback Handler="{startDrag}" />
<Callback Handler="{change}" Delay="2" />
</WhileDragging>
<Released>
<Callback Handler="{endDrag}" />
</Released>
</Panel>
<Panel ux:Class="TileWorld" ux:Name="self" Physics.IsPhysicsWorld="true">
<Tile ux:Name="tile" />
<PointAttractor Exclusive="true" Radius="1000" Strength="1000" />
<Circle Color="#0005" Width="10" Height="10" />
<LayoutAnimation>
<Move RelativeTo="LayoutChange" X="1" Y="1" Duration="0" />
</LayoutAnimation>
<!-- What if I only change the LayoutRole of the 'Tile'? -->
<!-- <WhileTrue Value="{dragging}">
<Change tile.LayoutRole="Independent" />
</WhileTrue> -->
</Panel>
<DockPanel Padding="10">
<StackPanel Alignment="Center" ItemSpacing="20" Dock="Bottom" Orientation="Horizontal">
<Text Value="Col: {col}" />
<Button Alignment="Center" Dock="Bottom" Text="Switch" Clicked="{change}" />
</StackPanel>
<Panel>
<Grid RowCount="1" ColumnCount="2">
<TileWorld Column="{col}" ux:Name="tw">
<!-- What if I only change the LayoutRole of the 'TileWorld'? -->
<WhileTrue Value="{dragging}">
<Change tw.LayoutRole="Independent" />
</WhileTrue>
<!-- Why doesn't the Column property change while the contents are being dragged? -->
<WhileFalse Value="{dragging}">
<Change tw.Column="{col}" />
</WhileFalse>
</TileWorld>
</Grid>
</Panel>
</DockPanel>
</App>