interface/Slider.js

/*
 * Created by yvesb on 08/10/2016.
 */
/*
 * MathGraph32 Javascript : Software for animating online dynamic mathematics figures
 * https://www.mathgraph32.org/
 * @Author Yves Biton (yves.biton@sesamath.net)
 * @License: GNU AGPLv3 https://www.gnu.org/licenses/agpl-3.0.html
 */
import { cens, getStr, preventDefault } from '../kernel/kernel'
import { mousePosition, touchPosition } from '../kernel/kernelAdd'
import constantes from '../kernel/constantes'
import $ from 'jquery'

export default Slider

/**
 *
 * @param {MtgApp} app
 * @param width
 * @param height
 * @param min
 * @param max
 * @param startval
 * @param digits
 * @param step
 * @param tip
 * @param transform
 * @param {string} append
 * @param y
 * @param fonc
 * @constructor
 */
function Slider (app, width, height, min, max, startval, digits, step, tip, transform, append, y, fonc) {
  this.target = 'right' // Pour les tips
  const zf = app.zoomFactor
  width *= zf
  height *= zf
  this.app = app
  this.width = width
  this.height = height
  this.min = min
  this.max = max
  this.val = startval
  this.fonc = fonc
  this.step = step
  this.tip = getStr(tip)
  this.append = append // L'éventuelle chaîne à rajouter au bout de l'affichage
  this.y = y // Pour la ligne d'affichage du tip
  this.captured = false
  const gap = constantes.sliderGap * zf
  const g = cens('g', {
    transform
  })
  this.container = g
  // Un rectangle à gauche qui contiendra le curseur et réagira à la souris
  const widthc = width - digits * constantes.sliderDigitsFontSize * 0.6 * zf // Largeur du curseur
  // this.widthc = widthc;
  const rectg = cens('rect', {
    x: 0,
    y: 0,
    width: widthc,
    height,
    stroke: 'none',
    fill: constantes.buttonBackGroundColor
  })
  g.appendChild(rectg)
  const rectd = cens('rect', {
    x: widthc,
    y: 0,
    width: width - widthc,
    height,
    fill: constantes.buttonBackGroundColor
  })
  g.appendChild(rectd)
  const yl = (height - constantes.sliderThickness * zf) / 2
  // On crée le trait horizontal
  this.segmentWidth = widthc - 2 * constantes.sliderRadius * zf - 2 * gap
  this.xc = gap + constantes.sliderRadius * zf // Abscisse de début du segment
  const seg = cens('line', {
    stroke: constantes.sliderColor,
    'stroke-linecap': 'round',
    x1: this.xc,
    y1: yl,
    x2: gap + this.segmentWidth,
    y2: yl
  })
  g.appendChild(seg)
  /*
  seg.addEventListener("mousedown", function(evt) {
    self.onbuttonDeviceDown(evt);
  });
  */
  $(seg).css('pointer-events', 'none')
  // On crée le rond qui sera le curseur
  this.xCurseur = gap + this.abscisse(startval)
  this.circ = cens('ellipse', {
    cx: this.xCurseur,
    cy: yl,
    rx: constantes.sliderRadius * zf,
    ry: (constantes.sliderHeight / 2 - 2) * zf,
    stroke: 'black',
    fill: 'url(#sliderGrad)'
  })
  $(this.circ).css('pointer-events', 'none')
  g.appendChild(this.circ)

  // même si c'est les params par défaut, faut le préciser explicitement pour que chrome arrête de râler en console
  const activeOpts = { capture: false, passive: false }
  const upListener = this.ondeviceup.bind(this)
  rectg.addEventListener('mouseup', upListener, activeOpts)
  rectg.addEventListener('touchend', upListener, activeOpts)
  rectg.addEventListener('mousedown', this.onDeviceDown.bind(this, mousePosition), activeOpts)
  rectg.addEventListener('touchstart', this.onDeviceDown.bind(this, touchPosition), activeOpts)
  rectg.addEventListener('mousemove', this.onDeviceMove.bind(this, mousePosition), activeOpts)
  rectg.addEventListener('touchmove', this.onDeviceMove.bind(this, touchPosition), activeOpts)

  // On crée à droite un affichage de la valeur
  this.txt = cens('text', {
    x: String(width - 2),
    y: height - 4 * zf,
    'font-size': String(constantes.sliderDigitsFontSize * zf) + 'px',
    style: 'stroke:blue;font-size:' + String(constantes.sliderDigitsFontSize * zf) + 'px;' + 'font-family:serif;text-anchor:end;'
  })
  this.txt.appendChild(document.createTextNode(this.val + this.append))
  g.appendChild(this.txt)
}
Slider.prototype.abscisse = function (val) {
  return (val - this.min) / (this.max - this.min) * this.segmentWidth + constantes.sliderRadius * this.app.zoomFactor
}

Slider.prototype.ondeviceup = function (evt) {
  this.captured = false
  preventDefault(evt)
  // evt.stopPropagation(evt);
}

// fonc est imposé par le bind, ev filé par l'EventListener
Slider.prototype.onDeviceDown = function (fonc, ev) {
  let newval
  const zf = this.app.zoomFactor
  // this.captured = false;
  const svg = this.app.svgFigure
  // Ligne suivante modifié version 6.5.1
  const point = fonc(svg, ev, this.app.zoomFactor) // Renvoie les coordonnées de la souris par rapport au svgFigure
  const x = point.x - parseInt(svg.getAttribute('width'))
  if (Math.abs(x - this.xCurseur) < 2 * constantes.sliderRadius * zf) this.captured = true
  else {
    if (x > this.xCurseur) {
      newval = this.val + this.step
      if (newval > this.max) newval = this.val
      this.updateValue(newval)
      this.updatePosition()
    } else {
      if (x < this.xCurseur) {
        newval = this.val - this.step
        if (newval < this.min) newval = this.val
        this.updateValue(newval)
        this.updatePosition()
      }
    }
  }
  preventDefault(ev)
  // ev.stopPropagation();
}

/**
 * Listener du move, actif (il faut préciser passive: false au addEventListener)
 * @param {function} fonc la fonction imposée par le .bind()
 * @param {MouseEvent|TouchEvent} ev l'événement transmis par l'EventListener
 */
Slider.prototype.onDeviceMove = function (fonc, ev) {
  const self = this
  if (this.captured) {
    const svg = this.app.svgFigure
    // Ligne suivante modifiée version 6.5.1
    const point = fonc(svg, ev, this.app.zoomFactor) // Renvoie les coordonnées de la souris par rapport au svgFigure
    let x = point.x - parseInt(svg.getAttribute('width'))
    const x1 = this.xc + this.segmentWidth
    if (x < this.xc) x = this.xc
    else if (x > x1) x = x1
    const abs = (x - this.xc) / this.segmentWidth
    // Modifié version 7.3 pour optimisation
    // const newval = Math.floor(this.min + abs * (this.max - this.min) + 0.5)
    const newval = Math.round(this.min + abs * (this.max - this.min))
    this.updateValue(newval)
    this.updatePosition()
    preventDefault(ev)
    // ev.stopPropagation();
  } else {
    if (!self.tipDisplayed) {
      self.app.cacheTip() // Sans argument pour effacer l'ancien tip quel qu'il soit
      self.tipDisplayed = false // Sera modifié par app
      self.app.setTip(self)
      setTimeout(function () {
        self.app.cacheTip(self)
      }, 2500)
    }
  }
}
Slider.prototype.updatePosition = function () {
  this.xCurseur = this.xc + this.segmentWidth * (this.val - this.min) / (this.max - this.min)
  this.circ.setAttribute('cx', this.xCurseur)
  this.updateDisplay()
}

Slider.prototype.updateDisplay = function () {
  this.txt.removeChild(this.txt.firstChild)
  this.txt.appendChild(document.createTextNode(this.val + this.append))
}

Slider.prototype.updateValue = function (newval) {
  this.val = newval
  this.fonc(newval) // Pour appeler la fonction affectant cette valeur
}

Slider.prototype.update = function (newVal) {
  this.updateValue(newVal)
  this.updatePosition()
}