Timepicker: How can I animate the hands of this clock?

Hi!

I’m trying to animate the hands of this “timepicker” when clicking on any of the numbers in the outer or inner circle.

I’ve tried to use <Change/>, but that will only animate the hand to arrowRotation degrees, then back to the intial value. <Set/> doesn’t seem to have a Duration property so that’s a no go i guess.

Any ideas?

(In the code below i’m just setting <Rotation Degrees="{arrowRotation}" ux:Name="rot"/> to see that the calculations are correct)

Also, would it be possible to select an hour based on touch X and Y? (Kind of like drag’n drop)

<App Theme="Basic">
  <JavaScript>
    var Observable = require('FuseJS/Observable');
    var UserEvents = require('FuseJS/UserEvents');

    function calculatePosition(index, radius, offset) {
      var elementSize = 20;
      var degsPerHour = 360 / 12;
      var centerPointCircleX = 0;
      var centerPointCircleY = 0;
      var degs = degsPerHour  index;

      var x = Math.sin(degs  Math.PI / 180)  radius + centerPointCircleX - offset;
      var y = Math.cos(degs  Math.PI / 180) * radius + centerPointCircleY - offset;
      return {x: x, y: y, degs: degs};
    }

    function Hour(index, text, value) {
      var self = this;
      this.text = text;
      this.value = value;
      this.index = index;
      this.selected = Observable(false);
    }

    var outerCircleHoursData = [
      { text: "18", value: 18 },
      { text: "17", value: 17 },
      { text: "16", value: 16 },
      { text: "15", value: 15 },
      { text: "14", value: 14 },
      { text: "13", value: 13 },
      { text: "00", value: 0 },
      { text: "23", value: 23 },
      { text: "22", value: 22 },
      { text: "21", value: 21 },
      { text: "20", value: 20 },
      { text: "19", value: 19 },
    ];

    var innerCircleData = [
      { text: "6", value: 6 },
      { text: "5", value: 5 },
      { text: "4", value: 4 },
      { text: "3", value: 3 },
      { text: "2", value: 2 },
      { text: "1", value: 1 },
      { text: "12", value: 12 },
      { text: "11", value: 11 },
      { text: "10", value: 10 },
      { text: "9", value: 9 },
      { text: "8", value: 8 },
      { text: "7", value: 7 },
    ];

    var outerHours = Observable();
    var innerHours = Observable();
    var allHours = Observable();
    var arrowRotation = Observable(90.0);

    outerCircleHoursData.forEach(function(el, i) {
      var hour = new Hour(i, el.text, el.value);
      var position = calculatePosition(i, 135, 0);
      hour.x = position.x;
      hour.y = position.y;
      hour.degs = position.degs;
      allHours.add(hour);
      outerHours.add(hour);
    });

    innerCircleData.forEach(function(el, i) {
      var hour = new Hour(i, el.text, el.value);
      var position = calculatePosition(i, 100, 0);
      hour.x = position.x;
      hour.y = position.y;
      hour.degs = position.degs;
      allHours.add(hour);
      innerHours.add(hour);
    });

    function deselectOthers(selected) {
      allHours.forEach(function(hour) {
        hour.selected.value = false;
      });
    }

    function pickOuterHour(arg) {
      // Deselect all
      deselectOthers(arg);
      arg.data.selected.value = !arg.data.selected.value;
      setRotationAndLength(arg, true);
    }

    function pickInnerHour(arg) {
      // Deselect all
      deselectOthers(arg);
      arg.data.selected.value = !arg.data.selected.value;
      setRotationAndLength(arg, false);
    }

    function setRotationAndLength(arg, isOuter) {
      arrowRotation.value = parseFloat(270 - arg.data.degs);
    }

    module.exports = {
      outerHours: outerHours,
      innerHours: innerHours,
      pickOuterHour: pickOuterHour,
      pickInnerHour: pickInnerHour,
      arrowRotation: arrowRotation
    }
  </JavaScript>


  <Panel ux:Class="TimePicker" ClipToBounds="true">
    <Panel ux:Name="hourPicker">
      <Circle ClipToBounds="true" Width="320" Height="320">
        <SolidColor Color="#d4d5d7" />

        <!-- arrow -->
        <Panel Width="100%" Height="100%" ux:Name="arr">
          <Rectangle Height="1" Width="42%" TransformOrigin="Anchor" Anchor="100%,0%">
            <SolidColor Color="#fff" />
          </Rectangle>

          <Rotation Degrees="{arrowRotation}" ux:Name="rot"/>
        </Panel>

        <!-- outer hours -->
        <Panel Alignment="Center" ux:Name="outerHours">
          <Each Items="{outerHours}">
            <Circle Height="25" Width="25" X="{x}" Y="{y}" Clicked="{pickOuterHour}" Padding="0,4,0,0">
              <SolidColor ux:Name="outerCircleColor" Color="#d4d5d7"/> <!-- #d4d5d7 -->
              <Text Value="{text}" Width="25" Margin="0,-2, 0, 0" Padding="0,0,13,0" TextAlignment="Center" FontSize="9" TextColor="#848586" ClipToBounds="true" />
              <WhileTrue Value="{selected}">
                  <Change outerCircleColor.Color="#fff" Duration="0.3"/>
              </WhileTrue>
            </Circle>
          </Each>

        </Panel>

        <!-- inner hours -->
        <Panel Alignment="Center" ux:Name="innerHours">
          <Each Items="{innerHours}">
            <Circle Height="25" Width="25" X="{x}" Y="{y}" Clicked="{pickInnerHour}" Padding="0,4,0,0">
              <SolidColor ux:Name="innerCircleColor" Color="#d4d5d7"/> <!-- #d4d5d7 -->
              <Text Value="{text}" Width="25" Margin="0,-2, 0, 0" Padding="0,0,13,0" TextAlignment="Center" FontSize="9" TextColor="#848586" ClipToBounds="true" />
              <WhileTrue Value="{selected}">
                  <Change innerCircleColor.Color="#fff" Duration="0.3"/>
              </WhileTrue>
            </Circle>
          </Each>
        </Panel>

      </Circle>
    </Panel>
  </Panel>

  <Panel Background="#f9f9f9">
      <TimePicker />
  </Panel>
</App>

Hi!

An important thing to understand in Fuse is that JavaScript (for performance reasons) is not running on the UI thread. This means that contolling animation directly from JS won’t work. JS should only be concerned with the business logic of your application, not micro-managing UI effects.

The main way of working with animation in Fuse is in pure UX markup. UX markup represents objects that live and breathe on the UI thread. Most animations can be defined in UX markup already. We are constanlty working to design new general purpose UX markup components, triggers and behaviors that will make even more things possible in pure markup.

If you need to do something very complex or “new”, and you are unable to find the combinations of triggers/animators to do the job, the next step is to build a custom Fuse.Behavior class in Uno code that does the appropriate gesture interpretation and response animation, and instantiate that class in UX markup.

As this is a use case that we are still quite thin on documentation and examples, we would be happy to assist you in implementing a custom behavior for your time picker use case. If you describe what you want to acheive in detail, we can provide an .uno file with suggested implementation. :slight_smile:

Anders

Hi!

Got it! I’ll try to explain what we’re trying to do. Please see attached gif below for referance.

  • Every hour is a <Circle/> that is clickable.
  • If a user click on a inner or outer hour, the hand/arrow rotation and length will animate to the selected inner/outer hour.
  • If a user drags the hand/arrow, it will snap to the closest hour and set it as selected.

Timepicker

Thank you. I’m starting work on the components that are needed to build this type of control. I’m not exactly sure what form it’ll take yet, how generic or specific. But I’ll get it working.

@edA-qa mort-ora-y: Thanks!