LayoutRole=“Independent” Question

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:

file

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.

file

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.

file


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>

Hi!

I don’t think LayoutRole can be used to acheive what you want, nor layout in general.

Physics in fuse works on the result of layout, so if you change the layout in the middle of some physics simulation, everything will be shifted/offset.

If you throw away your assumptions about how to acheive this, and give us a plain explaination of the effect you want to achieve (it was a little hard to extract from your wall of text), then I’m happy to try to help find a way to achieve it :slight_smile:

Anders,

Thanks for the quick response!

I can see how post was really obscure. I was trying to distill the specific issue I was having rather than the larger effect I wanted to achieve. Within that wall of text, however, was buried my ultimate goal - to emulate the iOS home screen.

I’m making good head-way as you can see below. But that sort of spring effect when releasing the icon (same as I’ve described in the “Close, but no cigar” section of my original post) is what I’m trying to work around with the LayoutRole tweaking. If there’s a better way to achieve this, I’m all ears.

file

Hi,

This use case is to my knowledge not straight forward possible in UX markup, as of the 0.32 feature set.

This general problem (irrespective of animation and layout) is called list reordering, and this is a use case we plan to solve by introducing a new set of UX behaviors (implemented in Uno). I can’t give you a timeframe, though. It is on our todolist, but has also been on that list for some time. So far other features and improvements have taken priority.

However, it is probably possible to achevie this already today, by using a combination of physics and JavaScript micro-management. The strategy would be to track occupied cells in JS and move PointAttractors around (or: turn them on/off) based on available slots to force elements to snap. Not straight forward, but should be doable.

The alternative would be to dig deeper and attempt to implement this yourself in Uno code :slight_smile:

I’ve linked this forum thread to our internal ticket for the reorderable feature so this thread will be notified when the offical feature is released.

Anders,

One of the challenges I faced while working on this was, indeed, list reordering. I got past that, however, by simply maintaining the original order of the list, but attaching observables for the Row and Column that the list item needed to occupy within a grid. That’s how I’ve achieved the effect seen above. Still, the list reordering feature will simplify the code a bit.

I still don’t think that will solve the real problem I’m faced with, which is: changing the layout of a draggable item while it’s being dragged. Seems like the drag behavior should take precedence over any layout change that might be occurring at the same time. (Perhaps you’re considering this in the list reordering implementation.)

I spent a bit of time today looking into the Uno code for LayoutAnimation, Draggable, and generally getting a better sense of PlacedHandlers to try to implement my own version of Draggable that keeps the element at the pointer position regardless of layout changes. Let me know if you think this is a reasonable approach.

I also played more with your suggestion of moving a single PointAttractor around to the “free” slot and actually got pretty darn close:

file

Though that weird re-positioning happens whenever the LayoutRole is changed back to Standard. Still trying to work through that, but if I can’t work that out I’ll try the Uno approach.

Again, thanks for the help!

-Atish