Tutorial: Customizing text layout

Customizing text layout

Text layout in MessageLayer is calculated by combining multiple TextCustomizer.

Texts are laid out with the following procedure.

  1. Initiate TextCustomizers instances, with array of TextProperties passed
  2. Loop through all TextCustomizers instances
    1. Assign time elapsed to the timePassed properties
    2. Calls perform() on the instance
    3. Check if the display is ended by looking at the isEnded property
    4. If all text customizers are ended, stop
    5. Otherwise, continue the loop in the next animation frame

To customize the layout, you are suppose to create your own TextCustomizer subclass, then implement the perform() method.

In the method, you can change the textProperties as you like, but you cannot change the length of the text array. For example a customizer for moving texts around might modify text.rect.x and text.rect.y.

Things to aware:

  • Any time-aware changes should be based on the timePassed property, the message layer would set this property accordingly.
  • You should not modify texts outside of the perform() function, including delayed modification (e.g. via setTimeout).

This is a sample TextCustomizer that moves all texts 10px downward.

class MoveDown extends TextCustomizer {
  constructor(messageLayer, oldTexts, newTexts) {
    super(textLayer, oldTexts, newTexts);
    this.newTexts = newTexts;
  }
  perform() {
    this.newTexts.forEach(text => {
      text.rect.y += 10;
    });
  }
}

Adding your own TextCustomizer

To add a TextCustomizer, you need to first register the class to MessageLayer.textCustomizers

// plugin/movedown.js
MessageLayer.textCustomizers.moveDown10px = MoveDown;

Next, you may enable the text customiser by appending the name to a message layer's textCustomizers property.

layer.textCustomizers.push('moveDown10px');

Disabling is similar:

let index = layer.textCustomizers.indexOf('moveDown10px');
layer.textCustomizers.splice(index, 1);

Please be aware that it is the customiser's responsibility to make sure it is inserted into the correct index. For example you should not insert text customizer prior to the build-in baseTextCustomizer.

This is how it is looks like in a tag that toggle the text customizer.

// plugin/movedown.js
Tag.actions.toggle_move_down = new TagAction({
  rules: {
    layer  : {type: "MESSAGE_LAYER", required: true},
    page   : {type: /fore|back/, required: true}
  },
  action: (args)=> {
    const layer = args.layer[args.page];
    let index = layer.textCustomizers.indexOf('moveDown10px');
    if (index >= 0) {
      layer.textCustomizers.splice(index, 1);
    } else {
      layer.textCustomizers.push('moveDown10px');
    }
    return 0;
  }
});
// first.ks
[o2_loadplugin module="movedown.js"]
before
[toggle_move_down]
after

Animation

A text customiser by default redraws screen with minimal FPS, depends on the setting of the [delay] tag. If a text customizer needs to perform animation, it has to implement the following:

  • It should return true for the isAnimation property, message layer will then redraw every animation frame.
  • It should implement the isEnded property. Text presentation will end if all text customizers are ended. Therefore you should expect perform() being called even after isEnded is true.

A simple TextCustomizer that shows characters one by one with an interval would looks like this:

class OneByOne extends TextCustomizer {
  constructor(messageLayer, oldTexts, newTexts) {
    super(textLayer, oldTexts, newTexts);
    this.newTexts = newTexts;
  }
  perform() {
    this.newTexts.forEach((text, i)=> {
      if (this.timePassed / 1000 > i) {
        text.styles.visible = true;
      } else {
        text.styles.visible = false;
      }
    });
  }
  get isEnded() {
    return this.timePassed / 1000 >= this.newTexts.length;
  }
  get isAnimation() {
    return true;
  }
}