'use strict';
let Rac = require('../Rac');
let utils = require('../util/utils');
/**
* Information regarding the currently selected
* `[Control]{@link Rac.Control}`.
*
* Created and kept by `[Controller]{@link Rac.Controller}` when a control
* becomes selected.
*
* @alias Rac.Controller.Selection
*/
class ControlSelection{
/**
* Builds a new `Selection` with the given `control` and pointer located
* at `pointerCenter`.
*
* @param {Rac.Control} control - The selected control
* @param {Rac.Point} pointerCenter - The location of the pointer when
* the selection started
*/
constructor(control, pointerCenter) {
/**
* The selected control.
* @type {Rac.Control}
*/
this.control = control;
/**
* Anchor produced by
* `[control.affixAnchor]{@link Rac.Control#affixAnchor}` when the
* selection began.
*
* This anchor is persisted during user interaction as to allow the user
* to interact with the selected control in a fixed location, even if
* the control moves during the interaction.
*
* @type {object}
*/
this.fixedAnchor = control.affixAnchor();
/**
* `Segment` that represents the offset from the pointer position to the
* control knob center.
*
* Used to interact with the control knob at a constant offset position
* during user interaction.
*
* The pointer starting location is equal to `segment.startPoint()`,
* the control knob center starting position is equal to
* `segment.endPoint()`.
*
* @type {Rac.Segment}
*/
this.pointerToKnobOffset = pointerCenter.segmentToPoint(control.knob());
}
drawSelection(pointerCenter) {
this.control.drawSelection(pointerCenter, this.fixedAnchor, this.pointerToKnobOffset);
}
}
/**
* Manager of interactive `[Control]{@link Rac.Control}`s for an instance
* of `Rac`.
*
* Contains a list of all managed controls and coordinates drawing and user
* interaction between them.
*
* For controls to be functional the `pointerPressed`, `pointerReleased`,
* and `pointerDragged` methods have to be called as pointer interactions
* happen. The `drawControls` method handles the drawing of all controls
* and the currently selected control, it is usually called at the very end
* of drawing.
*
* Also contains settings shared between all controls and used for user
* interaction, like `pointerStyle` to draw the pointer, `controlStyle` as
* a default style for drawing controls, and `knobRadius` that defines the
* size of the interactive element of most controls.
*
* @alias Rac.Controller
*/
class Controller {
static Selection = ControlSelection;
/**
* Builds a new `Controller` with the given `Rac` instance.
*
* @param {Rac} rac - Instance to use for drawing and creating other objects
*/
constructor(rac) {
/**
* Instance of `Rac` used for drawing and passed along to any created
* object.
*
* @type {Rac}
*/
this.rac = rac;
/**
* Distance at which the pointer is considered to interact with a
* control knob. Also used by controls for drawing.
*
* @type {number}
*/
this.knobRadius = 22;
/**
* Collection of all controlls managed by the instance. Controls in this
* list are considered for pointer hit testing and for drawing.
*
* @type {Rac.Control[]}
* @default []
*/
this.controls = [];
/**
* Indicates controls to add themselves into `this.controls` when
* created.
*
* This property is a shared configuration. The behaviour is implemented
* independently by each control constructor.
*
* @type {boolean}
* @default true
*/
this.autoAddControls = true;
// TODO: separate lastControl from lastPointer
// Last `Point` of the position when the pointer was pressed, or last
// Control interacted with. Set to `null` when there has been no
// interaction yet and while there is a selected control.
this.lastPointer = null;
/**
* Style object used for the visual elements related to pointer
* interaction and control selection. When `null` no pointer or
* selection visuals are drawn.
*
* By default contains a style that uses the current stroke
* configuration with no-fill.
*
* @type {?Rac.Stroke|Rac.Fill|Rac.StyleContainer}
* @default {@link instance.Fill#none}
*/
this.pointerStyle = rac.Fill.none;
/**
* Default style to apply for all controls. When set it is applied
* before control drawing. The individual control style in
* `[control.style]{@link Rac.Control#style}` is applied afterwards.
*
* @type {?Rac.Stroke|Rac.Fill|Rac.StyleContainer}
* @default null
*/
this.controlStyle = null
/**
* Selection information for the currently selected control, or `null`
* when there is no selection.
*
* @type {?Rac.Controller.Selection}
*/
this.selection = null;
} // constructor
/**
* Pushes `control` into `this.controls`, allowing the instance to handle
* pointer interaction and drawing of `control`.
*
* @param {Rac.Control} control - A `Control` to add into `controls`
*/
add(control) {
this.controls.push(control);
}
/**
* Notifies the instance that the pointer has been pressed at the
* `pointerCenter` location. All controls are hit tested and the first
* control to be hit is marked as selected.
*
* This function must be called along pointer press interaction for all
* managed controls to properly work.
*
* @param {Rac.Point} pointerCenter - The location where the pointer was
* pressed
*/
pointerPressed(pointerCenter) {
this.lastPointer = null;
// Test pointer hit
const selected = this.controls.find( item => {
const controlKnob = item.knob();
if (controlKnob === null) { return false; }
if (controlKnob.distanceToPoint(pointerCenter) <= this.knobRadius) {
return true;
}
return false;
});
if (selected === undefined) {
return;
}
this.selection = new Controller.Selection(selected, pointerCenter);
}
/**
* Notifies the instance that the pointer has been dragged to the
* `pointerCenter` location. When there is a selected control, user
* interaction is performed and the control value is updated.
*
* This function must be called along pointer drag interaction for all
* managed controls to properly work.
*
* @param {Rac.Point} pointerCenter - The location where the pointer was
* dragged
*/
pointerDragged(pointerCenter){
if (this.selection === null) {
return;
}
let control = this.selection.control;
let fixedAnchor = this.selection.fixedAnchor;
// Offset center of dragged control knob from the pointer position
let pointerKnobCenter = this.selection.pointerToKnobOffset
.withStartPoint(pointerCenter)
.endPoint();
control.updateWithPointer(pointerKnobCenter, fixedAnchor);
}
/**
* Notifies the instance that the pointer has been released at the
* `pointerCenter` location. When there is a selected control, user
* interaction is finalized and the control selection is cleared.
*
* This function must be called along pointer drag interaction for all
* managed controls to properly work.
*
* @param {Rac.Point} pointerCenter - The location where the pointer was
* released
*/
pointerReleased(pointerCenter) {
if (this.selection === null) {
this.lastPointer = pointerCenter;
return;
}
this.lastPointer = this.selection.control;
this.selection = null;
}
/**
* Draws all controls contained in
* `[controls]{@link Rac.Controller#controls}` along the visual elements
* for pointer and control selection.
*
* Usually called at the end of drawing, as to draw controls on top of
* other graphics.
*/
drawControls() {
let pointerCenter = this.rac.Point.pointer();
this.drawPointer(pointerCenter);
// All controls in display
this.controls.forEach(item => item.draw());
if (this.selection !== null) {
this.selection.drawSelection(pointerCenter);
}
}
drawPointer(pointerCenter) {
let pointerStyle = this.pointerStyle;
if (pointerStyle === null) {
return;
}
// Last pointer or control
if (this.lastPointer instanceof Rac.Point) {
this.lastPointer.arc(12).draw(pointerStyle);
}
if (this.lastPointer instanceof Rac.Control) {
// TODO: implement last selected control state
}
// Pointer pressed
if (this.rac.drawer.p5.mouseIsPressed) {
if (this.selection === null) {
pointerCenter.arc(10).draw(pointerStyle);
} else {
pointerCenter.arc(5).draw(pointerStyle);
}
}
}
} // class Controller
module.exports = Controller;