1
0
mirror of https://github.com/psychopy/psychojs.git synced 2025-05-12 08:38:10 +00:00

visual: enforce consistent formatting

This commit is contained in:
Sotiri Bakagiannis 2021-07-09 14:07:04 +01:00
parent 61fb7744a1
commit c9cb3c8412
13 changed files with 917 additions and 1002 deletions

View File

@ -7,10 +7,8 @@
* @license Distributed under the terms of the MIT License * @license Distributed under the terms of the MIT License
*/ */
import { Mouse } from "../core/Mouse.js";
import {TextBox} from './TextBox.js'; import { TextBox } from "./TextBox.js";
import {Mouse} from '../core/Mouse.js';
/** /**
* <p>ButtonStim visual stimulus.</p> * <p>ButtonStim visual stimulus.</p>
@ -39,28 +37,71 @@ import {Mouse} from '../core/Mouse.js';
*/ */
export class ButtonStim extends TextBox export class ButtonStim extends TextBox
{ {
constructor({win, name, text, font, pos, size, padding, anchor = 'center', units, color, fillColor = 'darkgrey', borderColor, borderWidth = 0, opacity, letterHeight, bold = true, italic, autoDraw, autoLog} = {}) constructor(
{
win,
name,
text,
font,
pos,
size,
padding,
anchor = "center",
units,
color,
fillColor = "darkgrey",
borderColor,
borderWidth = 0,
opacity,
letterHeight,
bold = true,
italic,
autoDraw,
autoLog,
} = {},
)
{ {
super({win, name, text, font, pos, size, padding, anchor, units, color, fillColor, borderColor, borderWidth, opacity, letterHeight, bold, italic, alignment: 'center', autoDraw, autoLog}); super({
win,
name,
text,
font,
pos,
size,
padding,
anchor,
units,
color,
fillColor,
borderColor,
borderWidth,
opacity,
letterHeight,
bold,
italic,
alignment: "center",
autoDraw,
autoLog,
});
this.psychoJS.logger.debug('create a new Button with name: ', name); this.psychoJS.logger.debug("create a new Button with name: ", name);
this.listener = new Mouse({name, win, autoLog}); this.listener = new Mouse({ name, win, autoLog });
this._addAttribute( this._addAttribute(
'wasClicked', "wasClicked",
false false,
); );
// Arrays to store times of clicks on and off // Arrays to store times of clicks on and off
this._addAttribute( this._addAttribute(
'timesOn', "timesOn",
[] [],
); );
this._addAttribute( this._addAttribute(
'timesOff', "timesOff",
[] [],
); );
if (this._autoLog) if (this._autoLog)
@ -69,8 +110,6 @@ export class ButtonStim extends TextBox
} }
} }
/** /**
* How many times has this button been clicked on? * How many times has this button been clicked on?
* *
@ -82,8 +121,6 @@ export class ButtonStim extends TextBox
return this.timesOn.length; return this.timesOn.length;
} }
/** /**
* Is this button currently being clicked on? * Is this button currently being clicked on?
* *
@ -94,5 +131,4 @@ export class ButtonStim extends TextBox
{ {
return this.listener.isPressedIn(this, [1, 0, 0]); return this.listener.isPressedIn(this, [1, 0, 0]);
} }
} }

View File

@ -7,19 +7,16 @@
* @license Distributed under the terms of the MIT License * @license Distributed under the terms of the MIT License
*/ */
import * as PIXI from "pixi.js-legacy";
import * as PIXI from 'pixi.js-legacy'; import { TrialHandler } from "../data/TrialHandler.js";
import {Color} from '../util/Color.js'; import { Color } from "../util/Color.js";
import {ColorMixin} from '../util/ColorMixin.js'; import { ColorMixin } from "../util/ColorMixin.js";
import * as util from '../util/Util.js';
import {TrialHandler} from '../data/TrialHandler.js';
import {TextStim} from './TextStim.js';
import {TextBox} from './TextBox.js';
import {VisualStim} from './VisualStim.js';
import {Slider} from './Slider.js';
import { to_pixiPoint } from "../util/Pixi.js"; import { to_pixiPoint } from "../util/Pixi.js";
import * as util from "../util/Util.js";
import { Slider } from "./Slider.js";
import { TextBox } from "./TextBox.js";
import { TextStim } from "./TextStim.js";
import { VisualStim } from "./VisualStim.js";
/** /**
* Form stimulus. * Form stimulus.
@ -58,99 +55,127 @@ import { to_pixiPoint } from "../util/Pixi.js";
*/ */
export class Form extends util.mix(VisualStim).with(ColorMixin) export class Form extends util.mix(VisualStim).with(ColorMixin)
{ {
constructor({name, win, pos, size, units, borderColor, fillColor, itemColor, markerColor, responseColor, color, contrast, opacity, depth, items, randomize, itemPadding, font, fontFamily, bold, italic, fontSize, clipMask, autoDraw, autoLog} = {}) constructor(
{
name,
win,
pos,
size,
units,
borderColor,
fillColor,
itemColor,
markerColor,
responseColor,
color,
contrast,
opacity,
depth,
items,
randomize,
itemPadding,
font,
fontFamily,
bold,
italic,
fontSize,
clipMask,
autoDraw,
autoLog,
} = {},
)
{ {
super({name, win, units, opacity, depth, pos, size, clipMask, autoDraw, autoLog}); super({ name, win, units, opacity, depth, pos, size, clipMask, autoDraw, autoLog });
this._addAttribute( this._addAttribute(
'itemPadding', "itemPadding",
itemPadding, itemPadding,
util.to_unit([20, 0], 'pix', win, this._units)[0], util.to_unit([20, 0], "pix", win, this._units)[0],
this._onChange(true, false) this._onChange(true, false),
); );
// colors: // colors:
this._addAttribute( this._addAttribute(
'color', "color",
// Same as itemColor // Same as itemColor
color, color,
undefined, undefined,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'borderColor', "borderColor",
borderColor, borderColor,
fillColor, fillColor,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'fillColor', "fillColor",
fillColor, fillColor,
undefined, undefined,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'itemColor', "itemColor",
itemColor, itemColor,
undefined, undefined,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'markerColor', "markerColor",
markerColor, markerColor,
undefined, undefined,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'responseColor', "responseColor",
responseColor, responseColor,
undefined, undefined,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'contrast', "contrast",
contrast, contrast,
1.0, 1.0,
this._onChange(true, false) this._onChange(true, false),
); );
// fonts: // fonts:
this._addAttribute( this._addAttribute(
'font', "font",
font, font,
'Arial', "Arial",
this._onChange(true, true) this._onChange(true, true),
); );
// Not in use at present // Not in use at present
this._addAttribute( this._addAttribute(
'fontFamily', "fontFamily",
fontFamily, fontFamily,
'Helvetica', "Helvetica",
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'fontSize', "fontSize",
fontSize, fontSize,
(this._units === 'pix') ? 14 : 0.03, (this._units === "pix") ? 14 : 0.03,
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'bold', "bold",
bold, bold,
false, false,
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'italic', "italic",
italic, italic,
false, false,
this._onChange(true, true) this._onChange(true, true),
); );
// callback to deal with changes to items: // callback to deal with changes to items:
@ -166,16 +191,17 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
}; };
this._addAttribute( this._addAttribute(
'items', "items",
items, items,
[], [],
onItemChange); onItemChange,
);
this._addAttribute( this._addAttribute(
'randomize', "randomize",
randomize, randomize,
false, false,
onItemChange); onItemChange,
);
this._scrollbarWidth = 0.02; this._scrollbarWidth = 0.02;
this._responseTextHeightRatio = 0.8; this._responseTextHeightRatio = 0.8;
@ -192,8 +218,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
} }
} }
/** /**
* Force a refresh of the stimulus. * Force a refresh of the stimulus.
* *
@ -218,8 +242,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
} }
} }
/** /**
* Overridden draw that also calls the draw method of all form elements. * Overridden draw that also calls the draw method of all form elements.
* *
@ -260,8 +282,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
this._scrollbar.draw(); this._scrollbar.draw();
} }
/** /**
* Overridden hide that also calls the hide method of all form elements. * Overridden hide that also calls the hide method of all form elements.
* *
@ -276,7 +296,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
super.hide(); super.hide();
// hide the stimuli: // hide the stimuli:
if (typeof this._items !== 'undefined') if (typeof this._items !== "undefined")
{ {
for (let i = 0; i < this._items.length; ++i) for (let i = 0; i < this._items.length; ++i)
{ {
@ -298,8 +318,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
} }
} }
/** /**
* Reset the form. * Reset the form.
* *
@ -309,7 +327,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
*/ */
reset() reset()
{ {
this.psychoJS.logger.debug('reset Form: ', this._name); this.psychoJS.logger.debug("reset Form: ", this._name);
// reset the stimuli: // reset the stimuli:
for (let i = 0; i < this._items.length; ++i) for (let i = 0; i < this._items.length; ++i)
@ -327,8 +345,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
this._needUpdate = true; this._needUpdate = true;
} }
/** /**
* Collate the questions and responses into a single dataset. * Collate the questions and responses into a single dataset.
* *
@ -352,9 +368,9 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
item.response = responseStim.getRating(); item.response = responseStim.getRating();
item.rt = responseStim.getRT(); item.rt = responseStim.getRT();
if (typeof item.response === 'undefined') if (typeof item.response === "undefined")
{ {
++ nbIncompleteResponse; ++nbIncompleteResponse;
} }
} }
else if (item.type === Form.Types.FREE_TEXT) else if (item.type === Form.Types.FREE_TEXT)
@ -364,7 +380,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
if (item.response.length === 0) if (item.response.length === 0)
{ {
++ nbIncompleteResponse; ++nbIncompleteResponse;
} }
} }
} }
@ -372,9 +388,8 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
this._items._complete = (nbIncompleteResponse === 0); this._items._complete = (nbIncompleteResponse === 0);
// return a copy of this._items: // return a copy of this._items:
return this._items.map(item => Object.assign({}, item)); return this._items.map((item) => Object.assign({}, item));
} }
/** /**
* Check if the form is complete. * Check if the form is complete.
@ -386,7 +401,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
*/ */
formComplete() formComplete()
{ {
//same as complete but might be used by some experiments before 2020.2 // same as complete but might be used by some experiments before 2020.2
this.getData(); this.getData();
return this._items._complete; return this._items._complete;
} }
@ -399,15 +414,21 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
* @param {module:data.ExperimentHandler} experiment - the experiment into which to insert the form data * @param {module:data.ExperimentHandler} experiment - the experiment into which to insert the form data
* @param {string} [format= 'rows'] - whether to insert the data as rows or as columns * @param {string} [format= 'rows'] - whether to insert the data as rows or as columns
*/ */
addDataToExp(experiment, format = 'rows') addDataToExp(experiment, format = "rows")
{ {
const addAsColumns = ['cols', 'columns'].includes(format.toLowerCase()); const addAsColumns = ["cols", "columns"].includes(format.toLowerCase());
const data = this.getData(); const data = this.getData();
const _doNotSave = [ const _doNotSave = [
'itemCtrl', 'responseCtrl', "itemCtrl",
'itemColor', 'options', 'ticks', 'tickLabels', "responseCtrl",
'responseWidth', 'responseColor', 'layout' "itemColor",
"options",
"ticks",
"tickLabels",
"responseWidth",
"responseColor",
"layout",
]; ];
for (const item of this.getData()) for (const item of this.getData())
@ -420,7 +441,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
const columnName = (addAsColumns) ? `${this._name}[${index}]${field}` : `${this._name}${field}`; const columnName = (addAsColumns) ? `${this._name}[${index}]${field}` : `${this._name}${field}`;
experiment.addData(columnName, item[field]); experiment.addData(columnName, item[field]);
} }
++ index; ++index;
} }
if (!addAsColumns) if (!addAsColumns)
@ -435,8 +456,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
} }
} }
/** /**
* Import and process the form items from either a spreadsheet resource files (.csv, .xlsx, etc.) or from an array. * Import and process the form items from either a spreadsheet resource files (.csv, .xlsx, etc.) or from an array.
* *
@ -447,8 +466,8 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
_processItems() _processItems()
{ {
const response = { const response = {
origin: 'Form._processItems', origin: "Form._processItems",
context: 'when processing the form items' context: "when processing the form items",
}; };
try try
@ -456,7 +475,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
if (this._autoLog) if (this._autoLog)
{ {
// note: we use the same log message as PsychoPy even though we called this method differently // note: we use the same log message as PsychoPy even though we called this method differently
this._psychoJS.experimentLogger.exp('Importing items...'); this._psychoJS.experimentLogger.exp("Importing items...");
} }
// import the items: // import the items:
@ -474,12 +493,10 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
catch (error) catch (error)
{ {
// throw { ...response, error }; // throw { ...response, error };
throw Object.assign(response, {error}); throw Object.assign(response, { error });
} }
} }
/** /**
* Import the form items from either a spreadsheet resource files (.csv, .xlsx, etc.) or from an array. * Import the form items from either a spreadsheet resource files (.csv, .xlsx, etc.) or from an array.
* *
@ -490,8 +507,8 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
_importItems() _importItems()
{ {
const response = { const response = {
origin: 'Form._importItems', origin: "Form._importItems",
context: 'when importing the form items' context: "when importing the form items",
}; };
try try
@ -499,17 +516,15 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
const itemsType = typeof this._items; const itemsType = typeof this._items;
// we treat undefined items as a list with a single default entry: // we treat undefined items as a list with a single default entry:
if (itemsType === 'undefined') if (itemsType === "undefined")
{ {
this._items = [Form._defaultItems]; this._items = [Form._defaultItems];
} }
// if items is a string, we treat it as the name of a resource file and import it: // if items is a string, we treat it as the name of a resource file and import it:
else if (itemsType === 'string') else if (itemsType === "string")
{ {
this._items = TrialHandler.importConditions(this._psychoJS.serverManager, this._items); this._items = TrialHandler.importConditions(this._psychoJS.serverManager, this._items);
} }
// unknown items type: // unknown items type:
else else
{ {
@ -521,17 +536,14 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
{ {
this._items = [Form._defaultItems]; this._items = [Form._defaultItems];
} }
} }
catch (error) catch (error)
{ {
// throw { ...response, error }; // throw { ...response, error };
throw Object.assign(response, {error}); throw Object.assign(response, { error });
} }
} }
/** /**
* Sanitize the form items: check that the keys are valid, and fill in default values. * Sanitize the form items: check that the keys are valid, and fill in default values.
* *
@ -542,8 +554,8 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
_sanitizeItems() _sanitizeItems()
{ {
const response = { const response = {
origin: 'Form._sanitizeItems', origin: "Form._sanitizeItems",
context: 'when sanitizing the form items' context: "when sanitizing the form items",
}; };
try try
@ -552,7 +564,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
for (const item of this._items) for (const item of this._items)
{ {
// old style forms have questionText instead of itemText: // old style forms have questionText instead of itemText:
if (typeof item.questionText !== 'undefined') if (typeof item.questionText !== "undefined")
{ {
item.itemText = item.questionText; item.itemText = item.questionText;
delete item.questionText; delete item.questionText;
@ -561,12 +573,11 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
delete item.questionWidth; delete item.questionWidth;
// for items of type 'rating, the ticks are in 'options' instead of in 'ticks': // for items of type 'rating, the ticks are in 'options' instead of in 'ticks':
if (item.type === 'rating' || item.type === 'slider') if (item.type === "rating" || item.type === "slider")
{ {
item.ticks = item.options; item.ticks = item.options;
item.options = undefined; item.options = undefined;
} }
} }
} }
@ -584,9 +595,8 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
missingKeys.add(key); missingKeys.add(key);
item[key] = Form._defaultItems[key]; item[key] = Form._defaultItems[key];
} }
// undefined value: // undefined value:
else if (typeof item[key] === 'undefined') else if (typeof item[key] === "undefined")
{ {
// TODO: options = '' for FREE_TEXT // TODO: options = '' for FREE_TEXT
item[key] = Form._defaultItems[key]; item[key] = Form._defaultItems[key];
@ -596,16 +606,17 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
if (missingKeys.size > 0) if (missingKeys.size > 0)
{ {
this._psychoJS.logger.warn(`Missing headers: ${Array.from(missingKeys).join(', ')}\nNote, headers are case sensitive and must match: ${Array.from(defaultKeys).join(', ')}`); this._psychoJS.logger.warn(
`Missing headers: ${Array.from(missingKeys).join(", ")}\nNote, headers are case sensitive and must match: ${Array.from(defaultKeys).join(", ")}`,
);
} }
// check the types and options: // check the types and options:
const formTypes = Object.getOwnPropertyNames(Form.Types); const formTypes = Object.getOwnPropertyNames(Form.Types);
for (const item of this._items) for (const item of this._items)
{ {
// convert type to upper case, replace spaces by underscores // convert type to upper case, replace spaces by underscores
item.type = item.type.toUpperCase().replace(' ', '_'); item.type = item.type.toUpperCase().replace(" ", "_");
// check that the type is valid: // check that the type is valid:
if (!formTypes.includes(item.type)) if (!formTypes.includes(item.type))
@ -614,9 +625,9 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
} }
// Support the 'radio' type found on older versions of PsychoPy // Support the 'radio' type found on older versions of PsychoPy
if (item.type === 'RADIO') if (item.type === "RADIO")
{ {
item.type = 'CHOICE'; item.type = "CHOICE";
} }
// convert item type to symbol: // convert item type to symbol:
@ -625,18 +636,17 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
// turn the option into an array and check length, where applicable: // turn the option into an array and check length, where applicable:
if (item.type === Form.Types.CHOICE) if (item.type === Form.Types.CHOICE)
{ {
item.options = item.options.split(','); item.options = item.options.split(",");
if (item.options.length < 2) if (item.options.length < 2)
{ {
throw `at least two choices should be provided for choice item: ${item.itemText}`; throw `at least two choices should be provided for choice item: ${item.itemText}`;
} }
} }
// turn the ticks and tickLabels into arrays, where applicable: // turn the ticks and tickLabels into arrays, where applicable:
else if (item.type === Form.Types.RATING || item.type === Form.Types.SLIDER) else if (item.type === Form.Types.RATING || item.type === Form.Types.SLIDER)
{ {
item.ticks = item.ticks.split(',').map( (_,t) => parseInt(t) ); item.ticks = item.ticks.split(",").map((_, t) => parseInt(t));
item.tickLabels = (item.tickLabels.length > 0) ? item.tickLabels.split(',') : []; item.tickLabels = (item.tickLabels.length > 0) ? item.tickLabels.split(",") : [];
} }
// TODO // TODO
@ -645,7 +655,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
} }
// check the layout: // check the layout:
const formLayouts = ['HORIZ', 'VERT']; const formLayouts = ["HORIZ", "VERT"];
for (const item of this._items) for (const item of this._items)
{ {
// convert layout to upper case: // convert layout to upper case:
@ -658,18 +668,16 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
} }
// convert item layout to symbol: // convert item layout to symbol:
item.layout = (item.layout === 'HORIZ') ? Form.Layout.HORIZONTAL : Form.Layout.VERTICAL; item.layout = (item.layout === "HORIZ") ? Form.Layout.HORIZONTAL : Form.Layout.VERTICAL;
} }
} }
catch (error) catch (error)
{ {
// throw { ...response, error }; // throw { ...response, error };
throw Object.assign(response, {error}); throw Object.assign(response, { error });
} }
} }
/** /**
* Estimate the bounding box. * Estimate the bounding box.
* *
@ -685,12 +693,10 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
this._pos[0] - this._size[0] / 2.0, this._pos[0] - this._size[0] / 2.0,
this._pos[1] - this._size[1] / 2.0, this._pos[1] - this._size[1] / 2.0,
this._size[0], this._size[0],
this._size[1] this._size[1],
); );
} }
/** /**
* Setup the stimuli, and the scrollbar. * Setup the stimuli, and the scrollbar.
* *
@ -706,7 +712,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
} }
// clean up the previously setup stimuli: // clean up the previously setup stimuli:
if (typeof this._visual !== 'undefined') if (typeof this._visual !== "undefined")
{ {
for (const textStim of this._visual.textStims) for (const textStim of this._visual.textStims)
{ {
@ -724,31 +730,30 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
textStims: [], textStims: [],
responseStims: [], responseStims: [],
visibles: [], visibles: [],
stimuliTotalHeight: 0 stimuliTotalHeight: 0,
}; };
// instantiate the clip mask that will be used by all stimuli: // instantiate the clip mask that will be used by all stimuli:
this._stimuliClipMask = new PIXI.Graphics(); this._stimuliClipMask = new PIXI.Graphics();
// default stimulus options: // default stimulus options:
const textStimOption = { const textStimOption = {
win: this._win, win: this._win,
name: 'item text', name: "item text",
font: this.font, font: this.font,
units: this._units, units: this._units,
alignHoriz: 'left', alignHoriz: "left",
alignVert: 'top', alignVert: "top",
height: this._fontSize, height: this._fontSize,
color: this.itemColor, color: this.itemColor,
ori: 0, ori: 0,
opacity: 1, opacity: 1,
depth: this._depth + 1, depth: this._depth + 1,
clipMask: this._stimuliClipMask clipMask: this._stimuliClipMask,
}; };
const sliderOption = { const sliderOption = {
win: this._win, win: this._win,
name: 'choice response', name: "choice response",
units: this._units, units: this._units,
flip: false, flip: false,
// Not part of Slider options as things stand // Not part of Slider options as things stand
@ -763,13 +768,13 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
opacity: 1, opacity: 1,
depth: this._depth + 1, depth: this._depth + 1,
clipMask: this._stimuliClipMask, clipMask: this._stimuliClipMask,
granularity: 1 granularity: 1,
}; };
const textBoxOption = { const textBoxOption = {
win: this._win, win: this._win,
name: 'free text response', name: "free text response",
units: this._units, units: this._units,
anchor: 'left-top', anchor: "left-top",
flip: false, flip: false,
opacity: 1, opacity: 1,
depth: this._depth + 1, depth: this._depth + 1,
@ -777,7 +782,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
letterHeight: this._fontSize * this._responseTextHeightRatio, letterHeight: this._fontSize * this._responseTextHeightRatio,
bold: false, bold: false,
italic: false, italic: false,
alignment: 'left', alignment: "left",
color: this.responseColor, color: this.responseColor,
fillColor: this.fillColor, fillColor: this.fillColor,
contrast: 1.0, contrast: 1.0,
@ -785,17 +790,16 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
borderWidth: 0.002, borderWidth: 0.002,
padding: 0.01, padding: 0.01,
editable: true, editable: true,
clipMask: this._stimuliClipMask clipMask: this._stimuliClipMask,
}; };
// we use for the slider's tick size the height of a word: // we use for the slider's tick size the height of a word:
const textStim = new TextStim(Object.assign(textStimOption, { text: 'Ag', pos: [0, 0]})); const textStim = new TextStim(Object.assign(textStimOption, { text: "Ag", pos: [0, 0] }));
const textMetrics_px = textStim.getTextMetrics(); const textMetrics_px = textStim.getTextMetrics();
const sliderTickSize = this._getLengthUnits(textMetrics_px.height) / 2; const sliderTickSize = this._getLengthUnits(textMetrics_px.height) / 2;
textStim.release(false); textStim.release(false);
let stimulusOffset = -this._itemPadding;
let stimulusOffset = - this._itemPadding;
for (const item of this._items) for (const item of this._items)
{ {
// initially, all items are invisible: // initially, all items are invisible:
@ -806,8 +810,10 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
// - description: <padding> + <item> + <padding> + <scrollbar> = this._size[0] // - description: <padding> + <item> + <padding> + <scrollbar> = this._size[0]
// - choice with vert layout: <padding> + <item> + <padding> + <scrollbar> = this._size[0] // - choice with vert layout: <padding> + <item> + <padding> + <scrollbar> = this._size[0]
let rowWidth; let rowWidth;
if (item.type === Form.Types.HEADING || item.type === Form.Types.DESCRIPTION || if (
(item.type === Form.Types.CHOICE && item.layout === Form.Layout.VERTICAL)) item.type === Form.Types.HEADING || item.type === Form.Types.DESCRIPTION
|| (item.type === Form.Types.CHOICE && item.layout === Form.Layout.VERTICAL)
)
{ {
rowWidth = (this._size[0] - this._itemPadding * 2 - this._scrollbarWidth); rowWidth = (this._size[0] - this._itemPadding * 2 - this._scrollbarWidth);
} }
@ -818,12 +824,13 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
} }
// item text // item text
const itemWidth = rowWidth * item.itemWidth; const itemWidth = rowWidth * item.itemWidth;
const textStim = new TextStim( const textStim = new TextStim(
Object.assign(textStimOption, { Object.assign(textStimOption, {
text: item.itemText, text: item.itemText,
wrapWidth: itemWidth wrapWidth: itemWidth,
})); }),
);
textStim._relativePos = [this._itemPadding, stimulusOffset]; textStim._relativePos = [this._itemPadding, stimulusOffset];
const textHeight = textStim.boundingBox.height; const textHeight = textStim.boundingBox.height;
this._visual.textStims.push(textStim); this._visual.textStims.push(textStim);
@ -847,7 +854,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
} }
else else
{ {
sliderSize = [sliderTickSize, (sliderTickSize*1.5) * item.options.length]; sliderSize = [sliderTickSize, (sliderTickSize * 1.5) * item.options.length];
compact = false; compact = false;
flip = true; flip = true;
} }
@ -882,23 +889,23 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
labels, labels,
ticks, ticks,
compact, compact,
flip flip,
}) }),
); );
responseHeight = responseStim.boundingBox.height; responseHeight = responseStim.boundingBox.height;
if (item.layout === Form.Layout.HORIZONTAL) if (item.layout === Form.Layout.HORIZONTAL)
{ {
responseStim._relativePos = [ responseStim._relativePos = [
this._itemPadding * 2 + itemWidth + responseWidth / 2, this._itemPadding * 2 + itemWidth + responseWidth / 2,
stimulusOffset stimulusOffset,
//- Math.max(0, (textHeight - responseHeight) / 2) // (vertical centering) // - Math.max(0, (textHeight - responseHeight) / 2) // (vertical centering)
]; ];
} }
else else
{ {
responseStim._relativePos = [ responseStim._relativePos = [
this._itemPadding * 2 + itemWidth, //this._itemPadding + sliderTickSize, this._itemPadding * 2 + itemWidth, // this._itemPadding + sliderTickSize,
stimulusOffset - responseHeight / 2 - textHeight - this._itemPadding stimulusOffset - responseHeight / 2 - textHeight - this._itemPadding,
]; ];
// since rowHeight will be the max of itemHeight and responseHeight, we need to alter responseHeight // since rowHeight will be the max of itemHeight and responseHeight, we need to alter responseHeight
@ -906,20 +913,19 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
responseHeight += textHeight + this._itemPadding; responseHeight += textHeight + this._itemPadding;
} }
} }
// FREE TEXT // FREE TEXT
else if (item.type === Form.Types.FREE_TEXT) else if (item.type === Form.Types.FREE_TEXT)
{ {
responseStim = new TextBox( responseStim = new TextBox(
Object.assign(textBoxOption, { Object.assign(textBoxOption, {
text: item.options, text: item.options,
size: [responseWidth, -1] size: [responseWidth, -1],
}) }),
); );
responseHeight = responseStim.boundingBox.height; responseHeight = responseStim.boundingBox.height;
responseStim._relativePos = [ responseStim._relativePos = [
this._itemPadding * 2 + itemWidth, this._itemPadding * 2 + itemWidth,
stimulusOffset stimulusOffset,
]; ];
} }
@ -932,13 +938,12 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
} }
this._visual.stimuliTotalHeight = stimulusOffset; this._visual.stimuliTotalHeight = stimulusOffset;
// scrollbar // scrollbar
// note: we add this Form as a dependent stimulus such that the Form is redrawn whenever // note: we add this Form as a dependent stimulus such that the Form is redrawn whenever
// the slider is updated // the slider is updated
this._scrollbar = new Slider({ this._scrollbar = new Slider({
win: this._win, win: this._win,
name: 'scrollbar', name: "scrollbar",
units: this._units, units: this._units,
color: this.itemColor, color: this.itemColor,
depth: this._depth + 1, depth: this._depth + 1,
@ -946,24 +951,20 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
size: [this._scrollbarWidth, this._size[1]], size: [this._scrollbarWidth, this._size[1]],
style: [Slider.Style.SLIDER], style: [Slider.Style.SLIDER],
ticks: [0, -this._visual.stimuliTotalHeight / this._size[1]], ticks: [0, -this._visual.stimuliTotalHeight / this._size[1]],
dependentStims: [this] dependentStims: [this],
}); });
this._prevScrollbarMarkerPos = 0; this._prevScrollbarMarkerPos = 0;
this._scrollbar.setMarkerPos(this._prevScrollbarMarkerPos); this._scrollbar.setMarkerPos(this._prevScrollbarMarkerPos);
// estimate the bounding box: // estimate the bounding box:
this._estimateBoundingBox(); this._estimateBoundingBox();
if (this._autoLog) if (this._autoLog)
{ {
this._psychoJS.experimentLogger.exp(`Layout set for: ${this.name}`); this._psychoJS.experimentLogger.exp(`Layout set for: ${this.name}`);
} }
} }
/** /**
* Update the form visual representation, if necessary. * Update the form visual representation, if necessary.
* *
@ -991,17 +992,18 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
[this._leftEdge, this._topEdge], [this._leftEdge, this._topEdge],
this.units, this.units,
this.win, this.win,
true); true,
);
[this._rightEdge_px, this._bottomEdge_px] = util.to_px( [this._rightEdge_px, this._bottomEdge_px] = util.to_px(
[this._rightEdge, this._bottomEdge], [this._rightEdge, this._bottomEdge],
this.units, this.units,
this.win, this.win,
true); true,
);
this._itemPadding_px = this._getLengthPix(this._itemPadding); this._itemPadding_px = this._getLengthPix(this._itemPadding);
this._scrollbarWidth_px = this._getLengthPix(this._scrollbarWidth, true); this._scrollbarWidth_px = this._getLengthPix(this._scrollbarWidth, true);
this._size_px = util.to_px(this._size, this.units, this.win, true); this._size_px = util.to_px(this._size, this.units, this.win, true);
// update the stimuli clip mask // update the stimuli clip mask
// note: the clip mask is in screen coordinates // note: the clip mask is in screen coordinates
this._stimuliClipMask.clear(); this._stimuliClipMask.clear();
@ -1010,11 +1012,10 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
this._win._rootContainer.position.x + this._leftEdge_px + 2, this._win._rootContainer.position.x + this._leftEdge_px + 2,
this._win._rootContainer.position.y + this._bottomEdge_px + 2, this._win._rootContainer.position.y + this._bottomEdge_px + 2,
this._size_px[0] - 4, this._size_px[0] - 4,
this._size_px[1] - 6 this._size_px[1] - 6,
); );
this._stimuliClipMask.endFill(); this._stimuliClipMask.endFill();
// position the scrollbar and get the scrollbar offset, in form units: // position the scrollbar and get the scrollbar offset, in form units:
this._scrollbar.setPos([this._rightEdge - this._scrollbarWidth / 2, this._pos[1]], false); this._scrollbar.setPos([this._rightEdge - this._scrollbarWidth / 2, this._pos[1]], false);
this._scrollbar.setOpacity(0.5); this._scrollbar.setOpacity(0.5);
@ -1025,8 +1026,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
this._updateDecorations(); this._updateDecorations();
} }
/** /**
* Update the visible stimuli. * Update the visible stimuli.
* *
@ -1042,7 +1041,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
const textStim = this._visual.textStims[i]; const textStim = this._visual.textStims[i];
const textStimPos = [ const textStimPos = [
this._leftEdge + textStim._relativePos[0], this._leftEdge + textStim._relativePos[0],
this._topEdge + textStim._relativePos[1] - this._scrollbarOffset this._topEdge + textStim._relativePos[1] - this._scrollbarOffset,
]; ];
textStim.setPos(textStimPos); textStim.setPos(textStimPos);
@ -1052,7 +1051,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
{ {
const responseStimPos = [ const responseStimPos = [
this._leftEdge + responseStim._relativePos[0], this._leftEdge + responseStim._relativePos[0],
this._topEdge + responseStim._relativePos[1] - this._scrollbarOffset this._topEdge + responseStim._relativePos[1] - this._scrollbarOffset,
]; ];
responseStim.setPos(responseStimPos); responseStim.setPos(responseStimPos);
} }
@ -1078,11 +1077,8 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
this._visual.visibles[i] = false; this._visual.visibles[i] = false;
} }
} }
} }
/** /**
* Update the form decorations (bounding box, lines between items, etc.) * Update the form decorations (bounding box, lines between items, etc.)
* *
@ -1092,7 +1088,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
*/ */
_updateDecorations() _updateDecorations()
{ {
if (typeof this._pixi !== 'undefined') if (typeof this._pixi !== "undefined")
{ {
this._pixi.destroy(true); this._pixi.destroy(true);
} }
@ -1109,7 +1105,6 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
// apply the form clip mask (n.b., that is not the stimuli clip mask): // apply the form clip mask (n.b., that is not the stimuli clip mask):
this._pixi.mask = this._clipMask; this._pixi.mask = this._clipMask;
// form background: // form background:
this._pixi.lineStyle(1, new Color(this.borderColor).int, this._opacity, 0.5); this._pixi.lineStyle(1, new Color(this.borderColor).int, this._opacity, 0.5);
// this._decorations.beginFill(this._barFillColor.int, this._opacity); // this._decorations.beginFill(this._barFillColor.int, this._opacity);
@ -1122,7 +1117,7 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
this._decorations = new PIXI.Graphics(); this._decorations = new PIXI.Graphics();
this._pixi.addChild(this._decorations); this._pixi.addChild(this._decorations);
this._decorations.mask = this._stimuliClipMask; this._decorations.mask = this._stimuliClipMask;
this._decorations.lineStyle(1, new Color('gray').int, this._opacity, 0.5); this._decorations.lineStyle(1, new Color("gray").int, this._opacity, 0.5);
this._decorations.alpha = 0.5; this._decorations.alpha = 0.5;
for (let i = 0; i < this._items.length; ++i) for (let i = 0; i < this._items.length; ++i)
@ -1136,27 +1131,23 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
const textStim = this._visual.textStims[i]; const textStim = this._visual.textStims[i];
const textStimPos = [ const textStimPos = [
this._leftEdge + textStim._relativePos[0], this._leftEdge + textStim._relativePos[0],
this._topEdge + textStim._relativePos[1] - this._scrollbarOffset this._topEdge + textStim._relativePos[1] - this._scrollbarOffset,
]; ];
const textStimPos_px = util.to_px(textStimPos, this._units, this._win); const textStimPos_px = util.to_px(textStimPos, this._units, this._win);
this._decorations.beginFill(new Color('darkgray').int); this._decorations.beginFill(new Color("darkgray").int);
this._decorations.drawRect( this._decorations.drawRect(
textStimPos_px[0] - this._itemPadding_px / 2, textStimPos_px[0] - this._itemPadding_px / 2,
textStimPos_px[1] + this._itemPadding_px / 2, textStimPos_px[1] + this._itemPadding_px / 2,
this._size_px[0] - this._itemPadding_px - this._scrollbarWidth_px, this._size_px[0] - this._itemPadding_px - this._scrollbarWidth_px,
-this._getLengthPix(this._visual.rowHeights[i]) - this._itemPadding_px -this._getLengthPix(this._visual.rowHeights[i]) - this._itemPadding_px,
); );
this._decorations.endFill(); this._decorations.endFill();
} }
} }
} }
} }
} }
/** /**
* Form item types. * Form item types.
* *
@ -1165,17 +1156,15 @@ export class Form extends util.mix(VisualStim).with(ColorMixin)
* @public * @public
*/ */
Form.Types = { Form.Types = {
HEADING: Symbol.for('HEADING'), HEADING: Symbol.for("HEADING"),
DESCRIPTION: Symbol.for('DESCRIPTION'), DESCRIPTION: Symbol.for("DESCRIPTION"),
RATING: Symbol.for('RATING'), RATING: Symbol.for("RATING"),
SLIDER: Symbol.for('SLIDER'), SLIDER: Symbol.for("SLIDER"),
FREE_TEXT: Symbol.for('FREE_TEXT'), FREE_TEXT: Symbol.for("FREE_TEXT"),
CHOICE: Symbol.for('CHOICE'), CHOICE: Symbol.for("CHOICE"),
RADIO: Symbol.for('RADIO') RADIO: Symbol.for("RADIO"),
}; };
/** /**
* Form item layout. * Form item layout.
* *
@ -1184,12 +1173,10 @@ Form.Types = {
* @public * @public
*/ */
Form.Layout = { Form.Layout = {
HORIZONTAL: Symbol.for('HORIZONTAL'), HORIZONTAL: Symbol.for("HORIZONTAL"),
VERTICAL: Symbol.for('VERTICAL') VERTICAL: Symbol.for("VERTICAL"),
}; };
/** /**
* Default form item. * Default form item.
* *
@ -1198,18 +1185,16 @@ Form.Layout = {
* *
*/ */
Form._defaultItems = { Form._defaultItems = {
'itemText': 'Default question', "itemText": "Default question",
'type': 'rating', "type": "rating",
'options': 'Yes, No', "options": "Yes, No",
'tickLabels': '', "tickLabels": "",
'itemWidth': 0.7, "itemWidth": 0.7,
'itemColor': 'white', "itemColor": "white",
'responseWidth': 0.3, "responseWidth": 0.3,
'responseColor': 'white', "responseColor": "white",
'index': 0, "index": 0,
'layout': 'horiz' "layout": "horiz",
}; };

View File

@ -7,14 +7,12 @@
* @license Distributed under the terms of the MIT License * @license Distributed under the terms of the MIT License
*/ */
import * as PIXI from "pixi.js-legacy";
import * as PIXI from 'pixi.js-legacy'; import { Color } from "../util/Color.js";
import {VisualStim} from './VisualStim.js'; import { ColorMixin } from "../util/ColorMixin.js";
import {Color} from '../util/Color.js';
import {ColorMixin} from '../util/ColorMixin.js';
import * as util from '../util/Util.js';
import { to_pixiPoint } from "../util/Pixi.js"; import { to_pixiPoint } from "../util/Pixi.js";
import * as util from "../util/Util.js";
import { VisualStim } from "./VisualStim.js";
/** /**
* Image Stimulus. * Image Stimulus.
@ -46,53 +44,53 @@ import { to_pixiPoint } from "../util/Pixi.js";
*/ */
export class ImageStim extends util.mix(VisualStim).with(ColorMixin) export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
{ {
constructor({name, win, image, mask, pos, units, ori, size, color, opacity, contrast, texRes, depth, interpolate, flipHoriz, flipVert, autoDraw, autoLog} = {}) constructor({ name, win, image, mask, pos, units, ori, size, color, opacity, contrast, texRes, depth, interpolate, flipHoriz, flipVert, autoDraw, autoLog } = {})
{ {
super({name, win, units, ori, opacity, depth, pos, size, autoDraw, autoLog}); super({ name, win, units, ori, opacity, depth, pos, size, autoDraw, autoLog });
this._addAttribute( this._addAttribute(
'image', "image",
image image,
); );
this._addAttribute( this._addAttribute(
'mask', "mask",
mask mask,
); );
this._addAttribute( this._addAttribute(
'color', "color",
color, color,
'white', "white",
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'contrast', "contrast",
contrast, contrast,
1.0, 1.0,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'texRes', "texRes",
texRes, texRes,
128, 128,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'interpolate', "interpolate",
interpolate, interpolate,
false, false,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'flipHoriz', "flipHoriz",
flipHoriz, flipHoriz,
false, false,
this._onChange(false, false) this._onChange(false, false),
); );
this._addAttribute( this._addAttribute(
'flipVert', "flipVert",
flipVert, flipVert,
false, false,
this._onChange(false, false) this._onChange(false, false),
); );
// estimate the bounding box: // estimate the bounding box:
@ -104,8 +102,6 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
} }
} }
/** /**
* Setter for the image attribute. * Setter for the image attribute.
* *
@ -117,22 +113,22 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
setImage(image, log = false) setImage(image, log = false)
{ {
const response = { const response = {
origin: 'ImageStim.setImage', origin: "ImageStim.setImage",
context: 'when setting the image of ImageStim: ' + this._name context: "when setting the image of ImageStim: " + this._name,
}; };
try try
{ {
// image is undefined: that's fine but we raise a warning in case this is a symptom of an actual problem // image is undefined: that's fine but we raise a warning in case this is a symptom of an actual problem
if (typeof image === 'undefined') if (typeof image === "undefined")
{ {
this.psychoJS.logger.warn('setting the image of ImageStim: ' + this._name + ' with argument: undefined.'); this.psychoJS.logger.warn("setting the image of ImageStim: " + this._name + " with argument: undefined.");
this.psychoJS.logger.debug('set the image of ImageStim: ' + this._name + ' as: undefined'); this.psychoJS.logger.debug("set the image of ImageStim: " + this._name + " as: undefined");
} }
else else
{ {
// image is a string: it should be the name of a resource, which we load // image is a string: it should be the name of a resource, which we load
if (typeof image === 'string') if (typeof image === "string")
{ {
image = this.psychoJS.serverManager.getResource(image); image = this.psychoJS.serverManager.getResource(image);
} }
@ -140,16 +136,16 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
// image should now be an actual HTMLImageElement: we raise an error if it is not // image should now be an actual HTMLImageElement: we raise an error if it is not
if (!(image instanceof HTMLImageElement)) if (!(image instanceof HTMLImageElement))
{ {
throw 'the argument: ' + image.toString() + ' is not an image" }'; throw "the argument: " + image.toString() + ' is not an image" }';
} }
this.psychoJS.logger.debug('set the image of ImageStim: ' + this._name + ' as: src= ' + image.src + ', size= ' + image.width + 'x' + image.height); this.psychoJS.logger.debug("set the image of ImageStim: " + this._name + " as: src= " + image.src + ", size= " + image.width + "x" + image.height);
} }
const existingImage = this.getImage(); const existingImage = this.getImage();
const hasChanged = existingImage ? existingImage.src !== image.src : true; const hasChanged = existingImage ? existingImage.src !== image.src : true;
this._setAttribute('image', image, log); this._setAttribute("image", image, log);
if (hasChanged) if (hasChanged)
{ {
@ -158,12 +154,10 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
} }
catch (error) catch (error)
{ {
throw Object.assign(response, {error}); throw Object.assign(response, { error });
} }
} }
/** /**
* Setter for the mask attribute. * Setter for the mask attribute.
* *
@ -175,22 +169,22 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
setMask(mask, log = false) setMask(mask, log = false)
{ {
const response = { const response = {
origin: 'ImageStim.setMask', origin: "ImageStim.setMask",
context: 'when setting the mask of ImageStim: ' + this._name context: "when setting the mask of ImageStim: " + this._name,
}; };
try try
{ {
// mask is undefined: that's fine but we raise a warning in case this is a sympton of an actual problem // mask is undefined: that's fine but we raise a warning in case this is a sympton of an actual problem
if (typeof mask === 'undefined') if (typeof mask === "undefined")
{ {
this.psychoJS.logger.warn('setting the mask of ImageStim: ' + this._name + ' with argument: undefined.'); this.psychoJS.logger.warn("setting the mask of ImageStim: " + this._name + " with argument: undefined.");
this.psychoJS.logger.debug('set the mask of ImageStim: ' + this._name + ' as: undefined'); this.psychoJS.logger.debug("set the mask of ImageStim: " + this._name + " as: undefined");
} }
else else
{ {
// mask is a string: it should be the name of a resource, which we load // mask is a string: it should be the name of a resource, which we load
if (typeof mask === 'string') if (typeof mask === "string")
{ {
mask = this.psychoJS.serverManager.getResource(mask); mask = this.psychoJS.serverManager.getResource(mask);
} }
@ -198,24 +192,22 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
// mask should now be an actual HTMLImageElement: we raise an error if it is not // mask should now be an actual HTMLImageElement: we raise an error if it is not
if (!(mask instanceof HTMLImageElement)) if (!(mask instanceof HTMLImageElement))
{ {
throw 'the argument: ' + mask.toString() + ' is not an image" }'; throw "the argument: " + mask.toString() + ' is not an image" }';
} }
this.psychoJS.logger.debug('set the mask of ImageStim: ' + this._name + ' as: src= ' + mask.src + ', size= ' + mask.width + 'x' + mask.height); this.psychoJS.logger.debug("set the mask of ImageStim: " + this._name + " as: src= " + mask.src + ", size= " + mask.width + "x" + mask.height);
} }
this._setAttribute('mask', mask, log); this._setAttribute("mask", mask, log);
this._onChange(true, false)(); this._onChange(true, false)();
} }
catch (error) catch (error)
{ {
throw Object.assign(response, {error}); throw Object.assign(response, { error });
} }
} }
/** /**
* Estimate the bounding box. * Estimate the bounding box.
* *
@ -227,21 +219,19 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
_estimateBoundingBox() _estimateBoundingBox()
{ {
const size = this._getDisplaySize(); const size = this._getDisplaySize();
if (typeof size !== 'undefined') if (typeof size !== "undefined")
{ {
this._boundingBox = new PIXI.Rectangle( this._boundingBox = new PIXI.Rectangle(
this._pos[0] - size[0] / 2, this._pos[0] - size[0] / 2,
this._pos[1] - size[1] / 2, this._pos[1] - size[1] / 2,
size[0], size[0],
size[1] size[1],
); );
} }
// TODO take the orientation into account // TODO take the orientation into account
} }
/** /**
* Update the stimulus, if necessary. * Update the stimulus, if necessary.
* *
@ -261,14 +251,14 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
{ {
this._needPixiUpdate = false; this._needPixiUpdate = false;
if (typeof this._pixi !== 'undefined') if (typeof this._pixi !== "undefined")
{ {
this._pixi.destroy(true); this._pixi.destroy(true);
} }
this._pixi = undefined; this._pixi = undefined;
// no image to draw: return immediately // no image to draw: return immediately
if (typeof this._image === 'undefined') if (typeof this._image === "undefined")
{ {
return; return;
} }
@ -279,7 +269,7 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
this._pixi = PIXI.Sprite.from(this._texture); this._pixi = PIXI.Sprite.from(this._texture);
// add a mask if need be: // add a mask if need be:
if (typeof this._mask !== 'undefined') if (typeof this._mask !== "undefined")
{ {
this._pixi.mask = PIXI.Sprite.from(this._mask); this._pixi.mask = PIXI.Sprite.from(this._mask);
@ -330,8 +320,6 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
this._estimateBoundingBox(); this._estimateBoundingBox();
} }
/** /**
* Get the size of the display image, which is either that of the ImageStim or that of the image * Get the size of the display image, which is either that of the ImageStim or that of the image
* it contains. * it contains.
@ -344,18 +332,16 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
{ {
let displaySize = this.size; let displaySize = this.size;
if (typeof displaySize === 'undefined') if (typeof displaySize === "undefined")
{ {
// use the size of the texture, if we have access to it: // use the size of the texture, if we have access to it:
if (typeof this._texture !== 'undefined' && this._texture.width > 0) if (typeof this._texture !== "undefined" && this._texture.width > 0)
{ {
const textureSize = [this._texture.width, this._texture.height]; const textureSize = [this._texture.width, this._texture.height];
displaySize = util.to_unit(textureSize, 'pix', this.win, this.units); displaySize = util.to_unit(textureSize, "pix", this.win, this.units);
} }
} }
return displaySize; return displaySize;
} }
} }

View File

@ -7,15 +7,13 @@
* @license Distributed under the terms of the MIT License * @license Distributed under the terms of the MIT License
*/ */
import * as PIXI from "pixi.js-legacy";
import * as PIXI from 'pixi.js-legacy'; import { PsychoJS } from "../core/PsychoJS.js";
import {VisualStim} from './VisualStim.js'; import { Color } from "../util/Color.js";
import {Color} from '../util/Color.js'; import { ColorMixin } from "../util/ColorMixin.js";
import {ColorMixin} from '../util/ColorMixin.js';
import * as util from '../util/Util.js';
import {PsychoJS} from "../core/PsychoJS.js";
import { to_pixiPoint } from "../util/Pixi.js"; import { to_pixiPoint } from "../util/Pixi.js";
import * as util from "../util/Util.js";
import { VisualStim } from "./VisualStim.js";
/** /**
* Movie Stimulus. * Movie Stimulus.
@ -49,82 +47,81 @@ import { to_pixiPoint } from "../util/Pixi.js";
*/ */
export class MovieStim extends VisualStim export class MovieStim extends VisualStim
{ {
constructor({name, win, movie, pos, units, ori, size, color, opacity, contrast, interpolate, flipHoriz, flipVert, loop, volume, noAudio, autoPlay, autoDraw, autoLog} = {}) constructor({ name, win, movie, pos, units, ori, size, color, opacity, contrast, interpolate, flipHoriz, flipVert, loop, volume, noAudio, autoPlay, autoDraw, autoLog } = {})
{ {
super({name, win, units, ori, opacity, pos, size, autoDraw, autoLog}); super({ name, win, units, ori, opacity, pos, size, autoDraw, autoLog });
this.psychoJS.logger.debug('create a new MovieStim with name: ', name); this.psychoJS.logger.debug("create a new MovieStim with name: ", name);
// movie and movie control: // movie and movie control:
this._addAttribute( this._addAttribute(
'movie', "movie",
movie movie,
); );
this._addAttribute( this._addAttribute(
'volume', "volume",
volume, volume,
1.0, 1.0,
this._onChange(false, false) this._onChange(false, false),
); );
this._addAttribute( this._addAttribute(
'noAudio', "noAudio",
noAudio, noAudio,
false, false,
this._onChange(false, false) this._onChange(false, false),
); );
this._addAttribute( this._addAttribute(
'autoPlay', "autoPlay",
autoPlay, autoPlay,
true, true,
this._onChange(false, false) this._onChange(false, false),
); );
this._addAttribute( this._addAttribute(
'flipHoriz', "flipHoriz",
flipHoriz, flipHoriz,
false, false,
this._onChange(false, false) this._onChange(false, false),
); );
this._addAttribute( this._addAttribute(
'flipVert', "flipVert",
flipVert, flipVert,
false, false,
this._onChange(false, false) this._onChange(false, false),
); );
this._addAttribute( this._addAttribute(
'interpolate', "interpolate",
interpolate, interpolate,
false, false,
this._onChange(true, false) this._onChange(true, false),
); );
// colors: // colors:
this._addAttribute( this._addAttribute(
'color', "color",
color, color,
'white', "white",
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'contrast', "contrast",
contrast, contrast,
1.0, 1.0,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'loop', "loop",
loop, loop,
false, false,
this._onChange(false, false) this._onChange(false, false),
); );
// estimate the bounding box: // estimate the bounding box:
this._estimateBoundingBox(); this._estimateBoundingBox();
// check whether the fastSeek method on HTMLVideoElement is implemented: // check whether the fastSeek method on HTMLVideoElement is implemented:
const videoElement = document.createElement('video'); const videoElement = document.createElement("video");
this._hasFastSeek = (typeof videoElement.fastSeek === 'function'); this._hasFastSeek = (typeof videoElement.fastSeek === "function");
if (this._autoLog) if (this._autoLog)
{ {
@ -132,8 +129,6 @@ export class MovieStim extends VisualStim
} }
} }
/** /**
* Setter for the movie attribute. * Setter for the movie attribute.
* *
@ -146,22 +141,22 @@ export class MovieStim extends VisualStim
setMovie(movie, log = false) setMovie(movie, log = false)
{ {
const response = { const response = {
origin: 'MovieStim.setMovie', origin: "MovieStim.setMovie",
context: 'when setting the movie of MovieStim: ' + this._name context: "when setting the movie of MovieStim: " + this._name,
}; };
try try
{ {
// movie is undefined: that's fine but we raise a warning in case this is a symptom of an actual problem // movie is undefined: that's fine but we raise a warning in case this is a symptom of an actual problem
if (typeof movie === 'undefined') if (typeof movie === "undefined")
{ {
this.psychoJS.logger.warn('setting the movie of MovieStim: ' + this._name + ' with argument: undefined.'); this.psychoJS.logger.warn("setting the movie of MovieStim: " + this._name + " with argument: undefined.");
this.psychoJS.logger.debug('set the movie of MovieStim: ' + this._name + ' as: undefined'); this.psychoJS.logger.debug("set the movie of MovieStim: " + this._name + " as: undefined");
} }
else else
{ {
// movie is a string: it should be the name of a resource, which we load // movie is a string: it should be the name of a resource, which we load
if (typeof movie === 'string') if (typeof movie === "string")
{ {
movie = this.psychoJS.serverManager.getResource(movie); movie = this.psychoJS.serverManager.getResource(movie);
} }
@ -169,7 +164,7 @@ export class MovieStim extends VisualStim
// movie should now be an actual HTMLVideoElement: we raise an error if it is not // movie should now be an actual HTMLVideoElement: we raise an error if it is not
if (!(movie instanceof HTMLVideoElement)) if (!(movie instanceof HTMLVideoElement))
{ {
throw 'the argument: ' + movie.toString() + ' is not a video" }'; throw "the argument: " + movie.toString() + ' is not a video" }';
} }
this.psychoJS.logger.debug(`set the movie of MovieStim: ${this._name} as: src= ${movie.src}, size= ${movie.videoWidth}x${movie.videoHeight}, duration= ${movie.duration}s`); this.psychoJS.logger.debug(`set the movie of MovieStim: ${this._name} as: src= ${movie.src}, size= ${movie.videoWidth}x${movie.videoHeight}, duration= ${movie.duration}s`);
@ -186,20 +181,16 @@ export class MovieStim extends VisualStim
} }
} }
this._setAttribute("movie", movie, log);
this._setAttribute('movie', movie, log);
this._needUpdate = true; this._needUpdate = true;
this._needPixiUpdate = true; this._needPixiUpdate = true;
} }
catch (error) catch (error)
{ {
throw Object.assign(response, {error}); throw Object.assign(response, { error });
} }
} }
/** /**
* Reset the stimulus. * Reset the stimulus.
* *
@ -212,8 +203,6 @@ export class MovieStim extends VisualStim
this.seek(0, log); this.seek(0, log);
} }
/** /**
* Start playing the movie. * Start playing the movie.
* *
@ -228,18 +217,17 @@ export class MovieStim extends VisualStim
if (playPromise !== undefined) if (playPromise !== undefined)
{ {
playPromise.catch((error) => { playPromise.catch((error) =>
{
throw { throw {
origin: 'MovieStim.play', origin: "MovieStim.play",
context: `when attempting to play MovieStim: ${this._name}`, context: `when attempting to play MovieStim: ${this._name}`,
error error,
}; };
}); });
} }
} }
/** /**
* Pause the movie. * Pause the movie.
* *
@ -251,8 +239,6 @@ export class MovieStim extends VisualStim
this._movie.pause(); this._movie.pause();
} }
/** /**
* Stop the movie and reset to 0s. * Stop the movie and reset to 0s.
* *
@ -265,8 +251,6 @@ export class MovieStim extends VisualStim
this.seek(0, log); this.seek(0, log);
} }
/** /**
* Jump to a specific timepoint * Jump to a specific timepoint
* *
@ -280,9 +264,9 @@ export class MovieStim extends VisualStim
if (timePoint < 0 || timePoint > this._movie.duration) if (timePoint < 0 || timePoint > this._movie.duration)
{ {
throw { throw {
origin: 'MovieStim.seek', origin: "MovieStim.seek",
context: `when seeking to timepoint: ${timePoint} of MovieStim: ${this._name}`, context: `when seeking to timepoint: ${timePoint} of MovieStim: ${this._name}`,
error: `the timepoint does not belong to [0, ${this._movie.duration}` error: `the timepoint does not belong to [0, ${this._movie.duration}`,
}; };
} }
@ -299,16 +283,14 @@ export class MovieStim extends VisualStim
catch (error) catch (error)
{ {
throw { throw {
origin: 'MovieStim.seek', origin: "MovieStim.seek",
context: `when seeking to timepoint: ${timePoint} of MovieStim: ${this._name}`, context: `when seeking to timepoint: ${timePoint} of MovieStim: ${this._name}`,
error error,
}; };
} }
} }
} }
/** /**
* Estimate the bounding box. * Estimate the bounding box.
* *
@ -320,21 +302,19 @@ export class MovieStim extends VisualStim
_estimateBoundingBox() _estimateBoundingBox()
{ {
const size = this._getDisplaySize(); const size = this._getDisplaySize();
if (typeof size !== 'undefined') if (typeof size !== "undefined")
{ {
this._boundingBox = new PIXI.Rectangle( this._boundingBox = new PIXI.Rectangle(
this._pos[0] - size[0] / 2, this._pos[0] - size[0] / 2,
this._pos[1] - size[1] / 2, this._pos[1] - size[1] / 2,
size[0], size[0],
size[1] size[1],
); );
} }
// TODO take the orientation into account // TODO take the orientation into account
} }
/** /**
* Update the stimulus, if necessary. * Update the stimulus, if necessary.
* *
@ -354,20 +334,20 @@ export class MovieStim extends VisualStim
{ {
this._needPixiUpdate = false; this._needPixiUpdate = false;
if (typeof this._pixi !== 'undefined') if (typeof this._pixi !== "undefined")
{ {
// Leave original video in place // Leave original video in place
// https://pixijs.download/dev/docs/PIXI.Sprite.html#destroy // https://pixijs.download/dev/docs/PIXI.Sprite.html#destroy
this._pixi.destroy({ this._pixi.destroy({
children: true, children: true,
texture: true, texture: true,
baseTexture: false baseTexture: false,
}); });
} }
this._pixi = undefined; this._pixi = undefined;
// no movie to draw: return immediately // no movie to draw: return immediately
if (typeof this._movie === 'undefined') if (typeof this._movie === "undefined")
{ {
return; return;
} }
@ -414,8 +394,6 @@ export class MovieStim extends VisualStim
this._estimateBoundingBox(); this._estimateBoundingBox();
} }
/** /**
* Get the size of the display image, which is either that of the ImageStim or that of the image * Get the size of the display image, which is either that of the ImageStim or that of the image
* it contains. * it contains.
@ -428,18 +406,16 @@ export class MovieStim extends VisualStim
{ {
let displaySize = this.size; let displaySize = this.size;
if (typeof displaySize === 'undefined') if (typeof displaySize === "undefined")
{ {
// use the size of the texture, if we have access to it: // use the size of the texture, if we have access to it:
if (typeof this._texture !== 'undefined' && this._texture.width > 0) if (typeof this._texture !== "undefined" && this._texture.width > 0)
{ {
const textureSize = [this._texture.width, this._texture.height]; const textureSize = [this._texture.width, this._texture.height];
displaySize = util.to_unit(textureSize, 'pix', this.win, this.units); displaySize = util.to_unit(textureSize, "pix", this.win, this.units);
} }
} }
return displaySize; return displaySize;
} }
} }

View File

@ -7,10 +7,8 @@
* @license Distributed under the terms of the MIT License * @license Distributed under the terms of the MIT License
*/ */
import { Color } from "../util/Color.js";
import {ShapeStim} from './ShapeStim.js'; import { ShapeStim } from "./ShapeStim.js";
import {Color} from '../util/Color.js';
/** /**
* <p>Polygonal visual stimulus.</p> * <p>Polygonal visual stimulus.</p>
@ -39,7 +37,7 @@ import {Color} from '../util/Color.js';
*/ */
export class Polygon extends ShapeStim export class Polygon extends ShapeStim
{ {
constructor({name, win, lineWidth, lineColor, fillColor, opacity, edges, radius, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog} = {}) constructor({ name, win, lineWidth, lineColor, fillColor, opacity, edges, radius, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog } = {})
{ {
super({ super({
name, name,
@ -56,20 +54,20 @@ export class Polygon extends ShapeStim
depth, depth,
interpolate, interpolate,
autoDraw, autoDraw,
autoLog autoLog,
}); });
this._psychoJS.logger.debug('create a new Polygon with name: ', name); this._psychoJS.logger.debug("create a new Polygon with name: ", name);
this._addAttribute( this._addAttribute(
'edges', "edges",
edges, edges,
3 3,
); );
this._addAttribute( this._addAttribute(
'radius', "radius",
radius, radius,
0.5 0.5,
); );
this._updateVertices(); this._updateVertices();
@ -80,8 +78,6 @@ export class Polygon extends ShapeStim
} }
} }
/** /**
* Setter for the radius attribute. * Setter for the radius attribute.
* *
@ -92,7 +88,7 @@ export class Polygon extends ShapeStim
*/ */
setRadius(radius, log = false) setRadius(radius, log = false)
{ {
const hasChanged = this._setAttribute('radius', radius, log); const hasChanged = this._setAttribute("radius", radius, log);
if (hasChanged) if (hasChanged)
{ {
@ -100,8 +96,6 @@ export class Polygon extends ShapeStim
} }
} }
/** /**
* Setter for the edges attribute. * Setter for the edges attribute.
* *
@ -112,7 +106,7 @@ export class Polygon extends ShapeStim
*/ */
setEdges(edges, log = false) setEdges(edges, log = false)
{ {
const hasChanged = this._setAttribute('edges', Math.round(edges), log); const hasChanged = this._setAttribute("edges", Math.round(edges), log);
if (hasChanged) if (hasChanged)
{ {
@ -120,8 +114,6 @@ export class Polygon extends ShapeStim
} }
} }
/** /**
* Update the vertices. * Update the vertices.
* *
@ -130,7 +122,7 @@ export class Polygon extends ShapeStim
*/ */
_updateVertices() _updateVertices()
{ {
this._psychoJS.logger.debug('update the vertices of Polygon: ', this.name); this._psychoJS.logger.debug("update the vertices of Polygon: ", this.name);
const angle = 2.0 * Math.PI / this._edges; const angle = 2.0 * Math.PI / this._edges;
const vertices = []; const vertices = [];
@ -141,5 +133,4 @@ export class Polygon extends ShapeStim
this.setVertices(vertices); this.setVertices(vertices);
} }
} }

View File

@ -7,10 +7,8 @@
* @license Distributed under the terms of the MIT License * @license Distributed under the terms of the MIT License
*/ */
import { Color } from "../util/Color.js";
import {ShapeStim} from './ShapeStim.js'; import { ShapeStim } from "./ShapeStim.js";
import {Color} from '../util/Color.js';
/** /**
* <p>Rectangular visual stimulus.</p> * <p>Rectangular visual stimulus.</p>
@ -39,7 +37,7 @@ import {Color} from '../util/Color.js';
*/ */
export class Rect extends ShapeStim export class Rect extends ShapeStim
{ {
constructor({name, win, lineWidth, lineColor, fillColor, opacity, width, height, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog} = {}) constructor({ name, win, lineWidth, lineColor, fillColor, opacity, width, height, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog } = {})
{ {
super({ super({
name, name,
@ -56,20 +54,20 @@ export class Rect extends ShapeStim
depth, depth,
interpolate, interpolate,
autoDraw, autoDraw,
autoLog autoLog,
}); });
this._psychoJS.logger.debug('create a new Rect with name: ', name); this._psychoJS.logger.debug("create a new Rect with name: ", name);
this._addAttribute( this._addAttribute(
'width', "width",
width, width,
0.5 0.5,
); );
this._addAttribute( this._addAttribute(
'height', "height",
height, height,
0.5 0.5,
); );
this._updateVertices(); this._updateVertices();
@ -80,8 +78,6 @@ export class Rect extends ShapeStim
} }
} }
/** /**
* Setter for the width attribute. * Setter for the width attribute.
* *
@ -92,9 +88,9 @@ export class Rect extends ShapeStim
*/ */
setWidth(width, log = false) setWidth(width, log = false)
{ {
this._psychoJS.logger.debug('set the width of Rect: ', this.name, 'to: ', width); this._psychoJS.logger.debug("set the width of Rect: ", this.name, "to: ", width);
const hasChanged = this._setAttribute('width', width, log); const hasChanged = this._setAttribute("width", width, log);
if (hasChanged) if (hasChanged)
{ {
@ -102,8 +98,6 @@ export class Rect extends ShapeStim
} }
} }
/** /**
* Setter for the height attribute. * Setter for the height attribute.
* *
@ -114,9 +108,9 @@ export class Rect extends ShapeStim
*/ */
setHeight(height, log = false) setHeight(height, log = false)
{ {
this._psychoJS.logger.debug('set the height of Rect: ', this.name, 'to: ', height); this._psychoJS.logger.debug("set the height of Rect: ", this.name, "to: ", height);
const hasChanged = this._setAttribute('height', height, log); const hasChanged = this._setAttribute("height", height, log);
if (hasChanged) if (hasChanged)
{ {
@ -124,8 +118,6 @@ export class Rect extends ShapeStim
} }
} }
/** /**
* Update the vertices. * Update the vertices.
* *
@ -134,7 +126,7 @@ export class Rect extends ShapeStim
*/ */
_updateVertices() _updateVertices()
{ {
this._psychoJS.logger.debug('update the vertices of Rect: ', this.name); this._psychoJS.logger.debug("update the vertices of Rect: ", this.name);
const halfWidth = this._width / 2.0; const halfWidth = this._width / 2.0;
const halfHeight = this._height / 2.0; const halfHeight = this._height / 2.0;
@ -143,8 +135,7 @@ export class Rect extends ShapeStim
[-halfWidth, -halfHeight], [-halfWidth, -halfHeight],
[halfWidth, -halfHeight], [halfWidth, -halfHeight],
[halfWidth, halfHeight], [halfWidth, halfHeight],
[-halfWidth, halfHeight] [-halfWidth, halfHeight],
]); ]);
} }
} }

View File

@ -8,15 +8,13 @@
* @license Distributed under the terms of the MIT License * @license Distributed under the terms of the MIT License
*/ */
import * as PIXI from "pixi.js-legacy";
import * as PIXI from 'pixi.js-legacy'; import { WindowMixin } from "../core/WindowMixin.js";
import {VisualStim} from './VisualStim.js'; import { Color } from "../util/Color.js";
import {Color} from '../util/Color.js'; import { ColorMixin } from "../util/ColorMixin.js";
import {ColorMixin} from '../util/ColorMixin.js';
import * as util from '../util/Util.js';
import { to_pixiPoint } from "../util/Pixi.js"; import { to_pixiPoint } from "../util/Pixi.js";
import {WindowMixin} from "../core/WindowMixin.js"; import * as util from "../util/Util.js";
import { VisualStim } from "./VisualStim.js";
/** /**
* <p>This class provides the basic functionality of shape stimuli.</p> * <p>This class provides the basic functionality of shape stimuli.</p>
@ -45,9 +43,9 @@ import {WindowMixin} from "../core/WindowMixin.js";
*/ */
export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin) export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
{ {
constructor({name, win, lineWidth, lineColor, fillColor, opacity, vertices, closeShape, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog} = {}) constructor({ name, win, lineWidth, lineColor, fillColor, opacity, vertices, closeShape, pos, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog } = {})
{ {
super({name, win, units, ori, opacity, pos, depth, size, autoDraw, autoLog}); super({ name, win, units, ori, opacity, pos, depth, size, autoDraw, autoLog });
// the PIXI polygon corresponding to the vertices, in pixel units: // the PIXI polygon corresponding to the vertices, in pixel units:
this._pixiPolygon_px = undefined; this._pixiPolygon_px = undefined;
@ -55,58 +53,56 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
this._vertices_px = undefined; this._vertices_px = undefined;
// shape: // shape:
if (typeof size === 'undefined' || size === null) if (typeof size === "undefined" || size === null)
{ {
this.size = [1.0, 1.0]; this.size = [1.0, 1.0];
} }
this._addAttribute( this._addAttribute(
'vertices', "vertices",
vertices, vertices,
[[-0.5, 0], [0, 0.5], [0.5, 0]] [[-0.5, 0], [0, 0.5], [0.5, 0]],
); );
this._addAttribute( this._addAttribute(
'closeShape', "closeShape",
closeShape, closeShape,
true, true,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'interpolate', "interpolate",
interpolate, interpolate,
true, true,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'lineWidth', "lineWidth",
lineWidth, lineWidth,
1.5, 1.5,
this._onChange(true, true) this._onChange(true, true),
); );
// colors: // colors:
this._addAttribute( this._addAttribute(
'lineColor', "lineColor",
lineColor, lineColor,
'white', "white",
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'fillColor', "fillColor",
fillColor, fillColor,
undefined, undefined,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'contrast', "contrast",
contrast, contrast,
1.0, 1.0,
this._onChange(true, false) this._onChange(true, false),
); );
} }
/** /**
* Setter for the vertices attribute. * Setter for the vertices attribute.
* *
@ -118,16 +114,16 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
setVertices(vertices, log = false) setVertices(vertices, log = false)
{ {
const response = { const response = {
origin: 'ShapeStim.setVertices', origin: "ShapeStim.setVertices",
context: 'when setting the vertices of ShapeStim: ' + this._name context: "when setting the vertices of ShapeStim: " + this._name,
}; };
this._psychoJS.logger.debug('set the vertices of ShapeStim:', this.name); this._psychoJS.logger.debug("set the vertices of ShapeStim:", this.name);
try try
{ {
// if vertices is a string, we check whether it is a known shape: // if vertices is a string, we check whether it is a known shape:
if (typeof vertices === 'string') if (typeof vertices === "string")
{ {
if (vertices in ShapeStim.KnownShapes) if (vertices in ShapeStim.KnownShapes)
{ {
@ -139,18 +135,16 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
} }
} }
this._setAttribute('vertices', vertices, log); this._setAttribute("vertices", vertices, log);
this._onChange(true, true)(); this._onChange(true, true)();
} }
catch (error) catch (error)
{ {
throw Object.assign(response, {error: error}); throw Object.assign(response, { error: error });
} }
} }
/** /**
* Determine whether an object is inside the bounding box of the ShapeStim. * Determine whether an object is inside the bounding box of the ShapeStim.
* *
@ -168,24 +162,22 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
// get the position of the object, in pixel coordinates: // get the position of the object, in pixel coordinates:
const objectPos_px = util.getPositionFromObject(object, units); const objectPos_px = util.getPositionFromObject(object, units);
if (typeof objectPos_px === 'undefined') if (typeof objectPos_px === "undefined")
{ {
throw { throw {
origin: 'VisualStim.contains', origin: "VisualStim.contains",
context: 'when determining whether VisualStim: ' + this._name + ' contains object: ' + util.toString(object), context: "when determining whether VisualStim: " + this._name + " contains object: " + util.toString(object),
error: 'unable to determine the position of the object' error: "unable to determine the position of the object",
}; };
} }
// test for inclusion: // test for inclusion:
const pos_px = util.to_px(this.pos, this.units, this.win); const pos_px = util.to_px(this.pos, this.units, this.win);
this._getVertices_px(); this._getVertices_px();
const polygon_px = this._vertices_px.map(v => [v[0] + pos_px[0], v[1] + pos_px[1]]); const polygon_px = this._vertices_px.map((v) => [v[0] + pos_px[0], v[1] + pos_px[1]]);
return util.IsPointInsidePolygon(objectPos_px, polygon_px); return util.IsPointInsidePolygon(objectPos_px, polygon_px);
} }
/** /**
* Estimate the bounding box. * Estimate the bounding box.
* *
@ -203,7 +195,7 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY,
Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY,
Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY,
Number.NEGATIVE_INFINITY Number.NEGATIVE_INFINITY,
]; ];
for (const vertex of this._vertices_px) for (const vertex of this._vertices_px)
{ {
@ -217,14 +209,12 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
this._pos[0] + this._getLengthUnits(limits_px[0]), this._pos[0] + this._getLengthUnits(limits_px[0]),
this._pos[1] + this._getLengthUnits(limits_px[1]), this._pos[1] + this._getLengthUnits(limits_px[1]),
this._getLengthUnits(limits_px[2] - limits_px[0]), this._getLengthUnits(limits_px[2] - limits_px[0]),
this._getLengthUnits(limits_px[3] - limits_px[1]) this._getLengthUnits(limits_px[3] - limits_px[1]),
); );
// TODO take the orientation into account // TODO take the orientation into account
} }
/** /**
* Update the stimulus, if necessary. * Update the stimulus, if necessary.
* *
@ -244,7 +234,7 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
{ {
this._needPixiUpdate = false; this._needPixiUpdate = false;
if (typeof this._pixi !== 'undefined') if (typeof this._pixi !== "undefined")
{ {
this._pixi.destroy(true); this._pixi.destroy(true);
} }
@ -256,13 +246,13 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
// prepare the polygon in the given color and opacity: // prepare the polygon in the given color and opacity:
this._pixi = new PIXI.Graphics(); this._pixi = new PIXI.Graphics();
this._pixi.lineStyle(this._lineWidth, this._lineColor.int, this._opacity, 0.5); this._pixi.lineStyle(this._lineWidth, this._lineColor.int, this._opacity, 0.5);
if (typeof this._fillColor !== 'undefined' && this._fillColor !== null) if (typeof this._fillColor !== "undefined" && this._fillColor !== null)
{ {
const contrastedColor = this.getContrastedColor(new Color(this._fillColor), this._contrast); const contrastedColor = this.getContrastedColor(new Color(this._fillColor), this._contrast);
this._pixi.beginFill(contrastedColor.int, this._opacity); this._pixi.beginFill(contrastedColor.int, this._opacity);
} }
this._pixi.drawPolygon(this._pixiPolygon_px); this._pixi.drawPolygon(this._pixiPolygon_px);
if (typeof this._fillColor !== 'undefined' && this._fillColor !== null) if (typeof this._fillColor !== "undefined" && this._fillColor !== null)
{ {
this._pixi.endFill(); this._pixi.endFill();
} }
@ -273,8 +263,6 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
this._pixi.rotation = this.ori * Math.PI / 180.0; this._pixi.rotation = this.ori * Math.PI / 180.0;
} }
/** /**
* Get the PIXI polygon (in pixel units) corresponding to the vertices. * Get the PIXI polygon (in pixel units) corresponding to the vertices.
* *
@ -312,8 +300,6 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
return this._pixiPolygon_px; return this._pixiPolygon_px;
} }
/** /**
* Get the vertices in pixel units. * Get the vertices in pixel units.
* *
@ -325,28 +311,28 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
{ {
// handle flipping: // handle flipping:
let flip = [1.0, 1.0]; let flip = [1.0, 1.0];
if ('_flipHoriz' in this && this._flipHoriz) if ("_flipHoriz" in this && this._flipHoriz)
{ {
flip[0] = -1.0; flip[0] = -1.0;
} }
if ('_flipVert' in this && this._flipVert) if ("_flipVert" in this && this._flipVert)
{ {
flip[1] = -1.0; flip[1] = -1.0;
} }
// handle size, flipping, and convert to pixel units: // handle size, flipping, and convert to pixel units:
this._vertices_px = this._vertices.map(v => util.to_px( this._vertices_px = this._vertices.map((v) =>
[v[0] * this._size[0] * flip[0], v[1] * this._size[1] * flip[1]], util.to_px(
this._units, [v[0] * this._size[0] * flip[0], v[1] * this._size[1] * flip[1]],
this._win) this._units,
this._win,
)
); );
return this._vertices_px; return this._vertices_px;
} }
} }
/** /**
* Known shapes. * Known shapes.
* *
@ -358,15 +344,15 @@ ShapeStim.KnownShapes = {
[-0.1, +0.5], // up [-0.1, +0.5], // up
[+0.1, +0.5], [+0.1, +0.5],
[+0.1, +0.1], [+0.1, +0.1],
[+0.5, +0.1], // right [+0.5, +0.1], // right
[+0.5, -0.1], [+0.5, -0.1],
[+0.1, -0.1], [+0.1, -0.1],
[+0.1, -0.5], // down [+0.1, -0.5], // down
[-0.1, -0.5], [-0.1, -0.5],
[-0.1, -0.1], [-0.1, -0.1],
[-0.5, -0.1], // left [-0.5, -0.1], // left
[-0.5, +0.1], [-0.5, +0.1],
[-0.1, +0.1] [-0.1, +0.1],
], ],
star7: [ star7: [
@ -383,7 +369,6 @@ ShapeStim.KnownShapes = {
[-0.49, -0.11], [-0.49, -0.11],
[-0.19, 0.04], [-0.19, 0.04],
[-0.39, 0.31], [-0.39, 0.31],
[-0.09, 0.18] [-0.09, 0.18],
] ],
}; };

View File

@ -7,17 +7,15 @@
* @license Distributed under the terms of the MIT License * @license Distributed under the terms of the MIT License
*/ */
import * as PIXI from "pixi.js-legacy";
import * as PIXI from 'pixi.js-legacy'; import { PsychoJS } from "../core/PsychoJS.js";
import {VisualStim} from './VisualStim.js'; import { WindowMixin } from "../core/WindowMixin.js";
import {Color} from '../util/Color.js'; import { Clock } from "../util/Clock.js";
import {ColorMixin} from '../util/ColorMixin.js'; import { Color } from "../util/Color.js";
import {WindowMixin} from '../core/WindowMixin.js'; import { ColorMixin } from "../util/ColorMixin.js";
import {Clock} from '../util/Clock.js';
import * as util from '../util/Util.js';
import {PsychoJS} from "../core/PsychoJS.js";
import { to_pixiPoint } from "../util/Pixi.js"; import { to_pixiPoint } from "../util/Pixi.js";
import * as util from "../util/Util.js";
import { VisualStim } from "./VisualStim.js";
/** /**
* Slider stimulus. * Slider stimulus.
@ -70,9 +68,38 @@ import { to_pixiPoint } from "../util/Pixi.js";
*/ */
export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin) export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
{ {
constructor({name, win, pos, size, ori, units, color, markerColor, lineColor, contrast, opacity, style, ticks, labels, granularity, flip, readOnly, font, bold, italic, fontSize, compact, clipMask, autoDraw, autoLog, dependentStims} = {}) constructor(
{
name,
win,
pos,
size,
ori,
units,
color,
markerColor,
lineColor,
contrast,
opacity,
style,
ticks,
labels,
granularity,
flip,
readOnly,
font,
bold,
italic,
fontSize,
compact,
clipMask,
autoDraw,
autoLog,
dependentStims,
} = {},
)
{ {
super({name, win, units, ori, opacity, pos, size, clipMask, autoDraw, autoLog}); super({ name, win, units, ori, opacity, pos, size, clipMask, autoDraw, autoLog });
this._needMarkerUpdate = false; this._needMarkerUpdate = false;
@ -96,119 +123,117 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
}; };
this._addAttribute( this._addAttribute(
'style', "style",
style, style,
[Slider.Style.RATING], [Slider.Style.RATING],
onChange(true, true, true) onChange(true, true, true),
); );
this._addAttribute( this._addAttribute(
'ticks', "ticks",
ticks, ticks,
[1, 2, 3, 4, 5], [1, 2, 3, 4, 5],
onChange(true, true, true) onChange(true, true, true),
); );
this._addAttribute( this._addAttribute(
'labels', "labels",
labels, labels,
[], [],
onChange(true, true, true) onChange(true, true, true),
); );
this._addAttribute( this._addAttribute(
'granularity', "granularity",
granularity, granularity,
0, 0,
this._onChange(false, false) this._onChange(false, false),
); );
this._addAttribute( this._addAttribute(
'readOnly', "readOnly",
readOnly, readOnly,
false false,
); );
this._addAttribute( this._addAttribute(
'compact', "compact",
compact, compact,
false, false,
this._onChange(true, true) this._onChange(true, true),
); );
// font: // font:
this._addAttribute( this._addAttribute(
'font', "font",
font, font,
'Arial', "Arial",
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'fontSize', "fontSize",
fontSize, fontSize,
(this._units === 'pix') ? 14 : 0.03, (this._units === "pix") ? 14 : 0.03,
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'bold', "bold",
bold, bold,
true, true,
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'italic', "italic",
italic, italic,
false, false,
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'flip', "flip",
flip, flip,
false, false,
this._onChange(true, true) this._onChange(true, true),
); );
// color: // color:
this._addAttribute( this._addAttribute(
'color', "color",
color, color,
'lightgray', "lightgray",
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'lineColor', "lineColor",
lineColor, lineColor,
'lightgray', "lightgray",
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'markerColor', "markerColor",
markerColor, markerColor,
'red', "red",
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'contrast', "contrast",
contrast, contrast,
1.0, 1.0,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'dependentStims', "dependentStims",
dependentStims, dependentStims,
[], [],
this._onChange(false, false) this._onChange(false, false),
); );
// slider rating (which might be different from the visible marker rating): // slider rating (which might be different from the visible marker rating):
this._addAttribute('rating', undefined); this._addAttribute("rating", undefined);
// visible marker rating (which might be different from the actual rating): // visible marker rating (which might be different from the actual rating):
this._addAttribute('markerPos', undefined); this._addAttribute("markerPos", undefined);
// full history of ratings and response times: // full history of ratings and response times:
this._addAttribute('history', []); this._addAttribute("history", []);
// various graphical components: // various graphical components:
this._addAttribute('lineAspectRatio', 0.01); this._addAttribute("lineAspectRatio", 0.01);
// check for attribute conflicts, missing values, etc.: // check for attribute conflicts, missing values, etc.:
this._sanitizeAttributes(); this._sanitizeAttributes();
@ -225,8 +250,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
} }
} }
/** /**
* Force a refresh of the stimulus. * Force a refresh of the stimulus.
* *
@ -240,8 +263,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._needMarkerUpdate = true; this._needMarkerUpdate = true;
} }
/** /**
* Reset the slider. * Reset the slider.
* *
@ -250,7 +271,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
*/ */
reset() reset()
{ {
this.psychoJS.logger.debug('reset Slider: ', this._name); this.psychoJS.logger.debug("reset Slider: ", this._name);
this._markerPos = undefined; this._markerPos = undefined;
this._history = []; this._history = [];
@ -262,14 +283,12 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._needUpdate = true; this._needUpdate = true;
// the marker should be invisible when markerPos is undefined: // the marker should be invisible when markerPos is undefined:
if (typeof this._marker !== 'undefined') if (typeof this._marker !== "undefined")
{ {
this._marker.alpha = 0; this._marker.alpha = 0;
} }
} }
/** /**
* Get the current value of the rating. * Get the current value of the rating.
* *
@ -290,8 +309,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
} }
} }
/** /**
* Get the response time of the most recent change to the rating. * Get the response time of the most recent change to the rating.
* *
@ -312,8 +329,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
} }
} }
/** /**
* Setter for the readOnly attribute. * Setter for the readOnly attribute.
* *
@ -327,7 +342,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
*/ */
setReadOnly(readOnly = true, log = false) setReadOnly(readOnly = true, log = false)
{ {
const hasChanged = this._setAttribute('readOnly', readOnly, log); const hasChanged = this._setAttribute("readOnly", readOnly, log);
if (hasChanged) if (hasChanged)
{ {
@ -345,8 +360,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
} }
} }
/** /**
* Setter for the markerPos attribute. * Setter for the markerPos attribute.
* *
@ -372,8 +385,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
} }
} }
/** /**
* Setter for the rating attribute. * Setter for the rating attribute.
* *
@ -393,60 +404,51 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
rating = this._labels[Math.round(rating)]; rating = this._labels[Math.round(rating)];
} }
this._setAttribute('rating', rating, log); this._setAttribute("rating", rating, log);
} }
/** Let `borderColor` alias `lineColor` to parallel PsychoPy */ /** Let `borderColor` alias `lineColor` to parallel PsychoPy */
set borderColor(color) { set borderColor(color)
{
this.lineColor = color; this.lineColor = color;
} }
setBorderColor(color)
{
setBorderColor(color) {
this.setLineColor(color); this.setLineColor(color);
} }
get borderColor()
{
get borderColor() {
return this.lineColor; return this.lineColor;
} }
getBorderColor()
{
getBorderColor() {
return this.getLineColor(); return this.getLineColor();
} }
/** Let `fillColor` alias `markerColor` to parallel PsychoPy */ /** Let `fillColor` alias `markerColor` to parallel PsychoPy */
set fillColor(color) { set fillColor(color)
{
this.markerColor = color; this.markerColor = color;
} }
setFillColor(color)
{
setFillColor(color) {
this.setMarkerColor(color); this.setMarkerColor(color);
} }
get fillColor()
{
get fillColor() {
return this.markerColor; return this.markerColor;
} }
getFillColor()
{
getFillColor() {
return this.getMarkerColor(); return this.getMarkerColor();
} }
/** /**
* Estimate the bounding box. * Estimate the bounding box.
* *
@ -462,18 +464,18 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
{ {
// setup the slider's style (taking into account the Window dimension, etc.): // setup the slider's style (taking into account the Window dimension, etc.):
this._setupStyle(); this._setupStyle();
// calculate various values in pixel units: // calculate various values in pixel units:
this._tickSize_px = util.to_px(this._tickSize, this._units, this._win); this._tickSize_px = util.to_px(this._tickSize, this._units, this._win);
this._fontSize_px = this._getLengthPix(this._fontSize); this._fontSize_px = this._getLengthPix(this._fontSize);
this._barSize_px = util.to_px(this._barSize, this._units, this._win, true).map(v => Math.max(1, v)); this._barSize_px = util.to_px(this._barSize, this._units, this._win, true).map((v) => Math.max(1, v));
this._markerSize_px = util.to_px(this._markerSize, this._units, this._win, true); this._markerSize_px = util.to_px(this._markerSize, this._units, this._win, true);
const pos_px = util.to_px(this._pos, this._units, this._win); const pos_px = util.to_px(this._pos, this._units, this._win);
const size_px = util.to_px(this._size, this._units, this._win); const size_px = util.to_px(this._size, this._units, this._win);
// calculate the position of the ticks: // calculate the position of the ticks:
const tickPositions = this._ratingToPos(this._ticks); const tickPositions = this._ratingToPos(this._ticks);
this._tickPositions_px = tickPositions.map(p => util.to_px(p, this._units, this._win)); this._tickPositions_px = tickPositions.map((p) => util.to_px(p, this._units, this._win));
// left, top, right, bottom limits: // left, top, right, bottom limits:
const limits_px = [0, 0, size_px[0], size_px[1]]; const limits_px = [0, 0, size_px[0], size_px[1]];
@ -490,7 +492,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
for (let l = 0; l < this._labels.length; ++l) for (let l = 0; l < this._labels.length; ++l)
{ {
const tickPositionIndex = Math.round( l / (this._labels.length - 1) * (this._ticks.length - 1) ); const tickPositionIndex = Math.round(l / (this._labels.length - 1) * (this._ticks.length - 1));
this._labelPositions_px[l] = this._tickPositions_px[tickPositionIndex]; this._labelPositions_px[l] = this._tickPositions_px[tickPositionIndex];
const labelBounds = PIXI.TextMetrics.measureText(this._labels[l].toString(), labelTextStyle); const labelBounds = PIXI.TextMetrics.measureText(this._labels[l].toString(), labelTextStyle);
@ -515,8 +517,10 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
} }
// ensure that that labels are not overlapping: // ensure that that labels are not overlapping:
if (prevLabelBounds && if (
(this._labelPositions_px[l - 1][0] + prevLabelBounds.width + tolerance >= this._labelPositions_px[l][0])) prevLabelBounds
&& (this._labelPositions_px[l - 1][0] + prevLabelBounds.width + tolerance >= this._labelPositions_px[l][0])
)
{ {
if (prevNonOverlapOffset === 0) if (prevNonOverlapOffset === 0)
{ {
@ -576,12 +580,10 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._getLengthUnits(position_px.x + limits_px[0]), this._getLengthUnits(position_px.x + limits_px[0]),
this._getLengthUnits(position_px.y + limits_px[1]), this._getLengthUnits(position_px.y + limits_px[1]),
this._getLengthUnits(limits_px[2] - limits_px[0]), this._getLengthUnits(limits_px[2] - limits_px[0]),
this._getLengthUnits(limits_px[3] - limits_px[1]) this._getLengthUnits(limits_px[3] - limits_px[1]),
); );
} }
/** /**
* Sanitize the slider attributes: check for attribute conflicts, missing values, etc. * Sanitize the slider attributes: check for attribute conflicts, missing values, etc.
* *
@ -592,9 +594,9 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
_sanitizeAttributes() _sanitizeAttributes()
{ {
// convert potential string styles into Symbols: // convert potential string styles into Symbols:
this._style.forEach( (style, index) => this._style.forEach((style, index) =>
{ {
if (typeof style === 'string') if (typeof style === "string")
{ {
this._style[index] = Symbol.for(style.toUpperCase()); this._style[index] = Symbol.for(style.toUpperCase());
} }
@ -606,14 +608,11 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._isCategorical = (this._ticks.length === 0); this._isCategorical = (this._ticks.length === 0);
if (this._isCategorical) if (this._isCategorical)
{ {
this._ticks = [...Array(this._labels.length)].map( (_, i) => i ); this._ticks = [...Array(this._labels.length)].map((_, i) => i);
this._granularity = 1.0; this._granularity = 1.0;
} }
} }
/** /**
* Set the current rating. * Set the current rating.
* *
@ -629,7 +628,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
recordRating(rating, responseTime = undefined, log = false) recordRating(rating, responseTime = undefined, log = false)
{ {
// get response time: // get response time:
if (typeof responseTime === 'undefined') if (typeof responseTime === "undefined")
{ {
responseTime = this._responseClock.getTime(); responseTime = this._responseClock.getTime();
} }
@ -640,15 +639,14 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this.setRating(rating, log); this.setRating(rating, log);
// add rating and response time to history: // add rating and response time to history:
this._history.push({rating: this._rating, responseTime}); this._history.push({ rating: this._rating, responseTime });
this.psychoJS.logger.debug('record a new rating: ', this._rating, 'with response time: ', responseTime, 'for Slider: ', this._name); this.psychoJS.logger.debug("record a new rating: ", this._rating, "with response time: ", responseTime, "for Slider: ", this._name);
// update slider: // update slider:
this._needMarkerUpdate = true; this._needMarkerUpdate = true;
this._needUpdate = true; this._needUpdate = true;
} }
/** /**
* Update the stimulus, if necessary. * Update the stimulus, if necessary.
* *
@ -682,7 +680,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
} }
} }
/** /**
* Estimate the position of the slider, taking the compactness into account. * Estimate the position of the slider, taking the compactness into account.
* *
@ -693,8 +690,10 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
_getPosition_px() _getPosition_px()
{ {
const position = to_pixiPoint(this.pos, this.units, this.win, true); const position = to_pixiPoint(this.pos, this.units, this.win, true);
if (this._compact && if (
(this._style.indexOf(Slider.Style.RADIO) > -1 || this._style.indexOf(Slider.Style.RATING) > -1)) this._compact
&& (this._style.indexOf(Slider.Style.RADIO) > -1 || this._style.indexOf(Slider.Style.RATING) > -1)
)
{ {
if (this._isHorizontal()) if (this._isHorizontal())
{ {
@ -709,8 +708,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
return position; return position;
} }
/** /**
* Update the position of the marker if necessary. * Update the position of the marker if necessary.
* *
@ -725,9 +722,9 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
} }
this._needMarkerUpdate = false; this._needMarkerUpdate = false;
if (typeof this._marker !== 'undefined') if (typeof this._marker !== "undefined")
{ {
if (typeof this._markerPos !== 'undefined') if (typeof this._markerPos !== "undefined")
{ {
const visibleMarkerPos = this._ratingToPos([this._markerPos]); const visibleMarkerPos = this._ratingToPos([this._markerPos]);
this._marker.position = to_pixiPoint(visibleMarkerPos[0], this.units, this.win, true); this._marker.position = to_pixiPoint(visibleMarkerPos[0], this.units, this.win, true);
@ -740,8 +737,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
} }
} }
/** /**
* Setup the PIXI components of the slider (bar, ticks, labels, marker, etc.). * Setup the PIXI components of the slider (bar, ticks, labels, marker, etc.).
* *
@ -759,17 +754,15 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._setupStyle(); this._setupStyle();
// calculate various values in pixel units: // calculate various values in pixel units:
this._tickSize_px = util.to_px(this._tickSize, this._units, this._win); this._tickSize_px = util.to_px(this._tickSize, this._units, this._win);
this._fontSize_px = this._getLengthPix(this._fontSize); this._fontSize_px = this._getLengthPix(this._fontSize);
this._barSize_px = util.to_px(this._barSize, this._units, this._win, true).map(v => Math.max(1, v)); this._barSize_px = util.to_px(this._barSize, this._units, this._win, true).map((v) => Math.max(1, v));
this._markerSize_px = util.to_px(this._markerSize, this._units, this._win, true); this._markerSize_px = util.to_px(this._markerSize, this._units, this._win, true);
const tickPositions = this._ratingToPos(this._ticks); const tickPositions = this._ratingToPos(this._ticks);
this._tickPositions_px = tickPositions.map(p => util.to_px(p, this._units, this._win)); this._tickPositions_px = tickPositions.map((p) => util.to_px(p, this._units, this._win));
if (typeof this._pixi !== "undefined")
if (typeof this._pixi !== 'undefined')
{ {
this._pixi.destroy(true); this._pixi.destroy(true);
} }
@ -782,7 +775,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._body.interactive = true; this._body.interactive = true;
this._pixi.addChild(this._body); this._pixi.addChild(this._body);
// ensure that pointer events will be captured along the slider body, even outside of // ensure that pointer events will be captured along the slider body, even outside of
// marker and labels: // marker and labels:
if (this._tickType === Slider.Shape.DISC) if (this._tickType === Slider.Shape.DISC)
@ -792,7 +784,8 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
-this._barSize_px[0] / 2 - maxTickSize_px, -this._barSize_px[0] / 2 - maxTickSize_px,
-this._barSize_px[1] / 2 - maxTickSize_px, -this._barSize_px[1] / 2 - maxTickSize_px,
this._barSize_px[0] + maxTickSize_px * 2, this._barSize_px[0] + maxTickSize_px * 2,
this._barSize_px[1] + maxTickSize_px * 2); this._barSize_px[1] + maxTickSize_px * 2,
);
} }
else else
{ {
@ -800,7 +793,8 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
-this._barSize_px[0] / 2 - this._tickSize_px[0] / 2, -this._barSize_px[0] / 2 - this._tickSize_px[0] / 2,
-this._barSize_px[1] / 2 - this._tickSize_px[1] / 2, -this._barSize_px[1] / 2 - this._tickSize_px[1] / 2,
this._barSize_px[0] + this._tickSize_px[0], this._barSize_px[0] + this._tickSize_px[0],
this._barSize_px[1] + this._tickSize_px[1]); this._barSize_px[1] + this._tickSize_px[1],
);
} }
// central bar: // central bar:
@ -816,8 +810,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._setupMarker(); this._setupMarker();
} }
/** /**
* Setup the central bar. * Setup the central bar.
* *
@ -830,7 +822,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
if (this._barLineWidth_px > 0) if (this._barLineWidth_px > 0)
{ {
this._body.lineStyle(this._barLineWidth_px, this._barLineColor.int, 1, 0.5); this._body.lineStyle(this._barLineWidth_px, this._barLineColor.int, 1, 0.5);
if (typeof this._barFillColor !== 'undefined') if (typeof this._barFillColor !== "undefined")
{ {
this._body.beginFill(this._barFillColor.int, 1); this._body.beginFill(this._barFillColor.int, 1);
} }
@ -838,17 +830,15 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
Math.round(-this._barSize_px[0] / 2), Math.round(-this._barSize_px[0] / 2),
Math.round(-this._barSize_px[1] / 2), Math.round(-this._barSize_px[1] / 2),
Math.round(this._barSize_px[0]), Math.round(this._barSize_px[0]),
Math.round(this._barSize_px[1]) Math.round(this._barSize_px[1]),
); );
if (typeof this._barFillColor !== 'undefined') if (typeof this._barFillColor !== "undefined")
{ {
this._body.endFill(); this._body.endFill();
} }
} }
} }
/** /**
* Setup the marker, and the associated mouse events. * Setup the marker, and the associated mouse events.
* *
@ -858,7 +848,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
*/ */
_setupMarker() _setupMarker()
{ {
/* this is now deprecated and replaced by _body.hitArea /* this is now deprecated and replaced by _body.hitArea
// transparent rectangle necessary to capture pointer events outside of marker and labels: // transparent rectangle necessary to capture pointer events outside of marker and labels:
const eventCaptureRectangle = new PIXI.Graphics(); const eventCaptureRectangle = new PIXI.Graphics();
eventCaptureRectangle.beginFill(0, 0); eventCaptureRectangle.beginFill(0, 0);
@ -870,7 +860,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
); );
eventCaptureRectangle.endFill(); eventCaptureRectangle.endFill();
this._pixi.addChild(eventCaptureRectangle); this._pixi.addChild(eventCaptureRectangle);
*/ */
// marker: // marker:
this._marker = new PIXI.Graphics(); this._marker = new PIXI.Graphics();
@ -927,7 +917,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
Math.round(-this._markerSize_px[0] / 2), Math.round(-this._markerSize_px[0] / 2),
Math.round(-this._markerSize_px[1] / 2), Math.round(-this._markerSize_px[1] / 2),
this._markerSize_px[0], this._markerSize_px[0],
this._markerSize_px[1] this._markerSize_px[1],
); );
this._marker.endFill(); this._marker.endFill();
@ -935,7 +925,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
// this._marker.drawCircle(0, 0, this._markerSize_px[0] / 3); // this._marker.drawCircle(0, 0, this._markerSize_px[0] / 3);
} }
// marker mouse events: // marker mouse events:
const self = this; const self = this;
self._markerDragging = false; self._markerDragging = false;
@ -1001,7 +990,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
} }
}; };
// (*) slider mouse events outside of marker // (*) slider mouse events outside of marker
// note: this only works thanks to eventCaptureRectangle // note: this only works thanks to eventCaptureRectangle
/* not quite right just yet (as of May 2020) /* not quite right just yet (as of May 2020)
@ -1035,8 +1023,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
}; };
} }
/** /**
* Setup the ticks. * Setup the ticks.
* *
@ -1072,8 +1058,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
} }
} }
/** /**
* Get the PIXI Text Style applied to the PIXI.Text labels. * Get the PIXI Text Style applied to the PIXI.Text labels.
* *
@ -1084,19 +1068,17 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
_getTextStyle() _getTextStyle()
{ {
this._fontSize_px = this._getLengthPix(this._fontSize); this._fontSize_px = this._getLengthPix(this._fontSize);
return new PIXI.TextStyle({ return new PIXI.TextStyle({
fontFamily: this._font, fontFamily: this._font,
fontSize: Math.round(this._fontSize_px), fontSize: Math.round(this._fontSize_px),
fontWeight: (this._bold) ? 'bold' : 'normal', fontWeight: (this._bold) ? "bold" : "normal",
fontStyle: (this._italic) ? 'italic' : 'normal', fontStyle: (this._italic) ? "italic" : "normal",
fill: this.getContrastedColor(this._labelColor, this._contrast).hex, fill: this.getContrastedColor(this._labelColor, this._contrast).hex,
align: 'center', align: "center",
}); });
} }
/** /**
* Setup the labels. * Setup the labels.
* *
@ -1121,8 +1103,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
} }
} }
/** /**
* Apply a particular style to the slider. * Apply a particular style to the slider.
* *
@ -1158,7 +1138,8 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._tickType = Slider.Shape.LINE; this._tickType = Slider.Shape.LINE;
this._tickColor = (!skin.TICK_COLOR) ? new Color(this._lineColor) : skin.TICK_COLOR; this._tickColor = (!skin.TICK_COLOR) ? new Color(this._lineColor) : skin.TICK_COLOR;
if (this.markerColor === undefined) { if (this.markerColor === undefined)
{
this.markerColor = skin.MARKER_COLOR; this.markerColor = skin.MARKER_COLOR;
} }
@ -1171,7 +1152,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._labelOri = 0; this._labelOri = 0;
// rating: // rating:
if (this._style.indexOf(Slider.Style.RATING) > -1) if (this._style.indexOf(Slider.Style.RATING) > -1)
{ {
@ -1184,8 +1164,8 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._markerType = Slider.Shape.TRIANGLE; this._markerType = Slider.Shape.TRIANGLE;
if (!this._skin.MARKER_SIZE) if (!this._skin.MARKER_SIZE)
{ {
this._markerSize = this._markerSize.map(s => s * 2); this._markerSize = this._markerSize.map((s) => s * 2);
} }
} }
// slider: // slider:
@ -1194,9 +1174,9 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._markerType = Slider.Shape.BOX; this._markerType = Slider.Shape.BOX;
if (!this._skin.MARKER_SIZE) if (!this._skin.MARKER_SIZE)
{ {
this._markerSize = (this._isHorizontal()) ? this._markerSize = (this._isHorizontal())
[this._size[0] / (this._ticks[this._ticks.length - 1] - this._ticks[0]), this._size[1]] : ? [this._size[0] / (this._ticks[this._ticks.length - 1] - this._ticks[0]), this._size[1]]
[this._size[0], this._size[1] / (this._ticks[this._ticks.length - 1] - this._ticks[0])]; : [this._size[0], this._size[1] / (this._ticks[this._ticks.length - 1] - this._ticks[0])];
} }
this._barSize = [this._size[0], this._size[1]]; this._barSize = [this._size[0], this._size[1]];
this._barFillColor = this.getContrastedColor(new Color(this.color), 0.5); this._barFillColor = this.getContrastedColor(new Color(this.color), 0.5);
@ -1235,12 +1215,10 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
if (!this._skin.MARKER_SIZE) if (!this._skin.MARKER_SIZE)
{ {
this._markerSize = this._markerSize.map(s => s * 0.7); this._markerSize = this._markerSize.map((s) => s * 0.7);
}
} }
} }
}
/** /**
* Convert an array of ratings into an array of [x,y] positions (in Slider units, with 0 at the center of the Slider) * Convert an array of ratings into an array of [x,y] positions (in Slider units, with 0 at the center of the Slider)
@ -1260,20 +1238,22 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
// in compact mode the circular markers of RADIO sliders must fit within the width: // in compact mode the circular markers of RADIO sliders must fit within the width:
if (this._compact && this._style.indexOf(Slider.Style.RADIO) > -1) if (this._compact && this._style.indexOf(Slider.Style.RADIO) > -1)
{ {
return ratings.map(v => [ return ratings.map((v) => [
((v - this._ticks[0]) / range) * (this._size[0] - this._tickSize[1]*2) - ((v - this._ticks[0]) / range) * (this._size[0] - this._tickSize[1] * 2)
(this._size[0] / 2) + this._tickSize[1], - (this._size[0] / 2) + this._tickSize[1],
0]); 0,
]);
} }
else if (this._style.indexOf(Slider.Style.SLIDER) > -1) else if (this._style.indexOf(Slider.Style.SLIDER) > -1)
{ {
return ratings.map(v => [ return ratings.map((v) => [
((v - this._ticks[0]) / range - 0.5) * (this._size[0] - this._markerSize[0]), ((v - this._ticks[0]) / range - 0.5) * (this._size[0] - this._markerSize[0]),
0]); 0,
]);
} }
else else
{ {
return ratings.map(v => [((v - this._ticks[0]) / range - 0.5) * this._size[0], 0]); return ratings.map((v) => [((v - this._ticks[0]) / range - 0.5) * this._size[0], 0]);
} }
} }
else else
@ -1281,25 +1261,26 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
// in compact mode the circular markers of RADIO sliders must fit within the height: // in compact mode the circular markers of RADIO sliders must fit within the height:
if (this._compact && this._style.indexOf(Slider.Style.RADIO) > -1) if (this._compact && this._style.indexOf(Slider.Style.RADIO) > -1)
{ {
return ratings.map(v => [0, return ratings.map((v) => [
((v - this._ticks[0]) / range) * (this._size[1] - this._tickSize[0]*2) - 0,
(this._size[1] / 2) + this._tickSize[0]]); ((v - this._ticks[0]) / range) * (this._size[1] - this._tickSize[0] * 2)
- (this._size[1] / 2) + this._tickSize[0],
]);
} }
else if (this._style.indexOf(Slider.Style.SLIDER) > -1) else if (this._style.indexOf(Slider.Style.SLIDER) > -1)
{ {
return ratings.map(v => [ return ratings.map((v) => [
0, 0,
((v - this._ticks[0]) / range - 0.5) * (this._size[1] - this._markerSize[1])]); ((v - this._ticks[0]) / range - 0.5) * (this._size[1] - this._markerSize[1]),
]);
} }
else else
{ {
return ratings.map(v => [0, (1.0 - (v - this._ticks[0]) / range - 0.5) * this._size[1]]); return ratings.map((v) => [0, (1.0 - (v - this._ticks[0]) / range - 0.5) * this._size[1]]);
} }
} }
} }
/** /**
* Convert a [x,y] position, in pixel units, relative to the slider, into a rating. * Convert a [x,y] position, in pixel units, relative to the slider, into a rating.
* *
@ -1339,8 +1320,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
} }
} }
/** /**
* Determine whether the slider is horizontal. * Determine whether the slider is horizontal.
* *
@ -1356,8 +1335,6 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
return (this._size[0] > this._size[1]); return (this._size[0] > this._size[1]);
} }
/** /**
* Calculate the rating once granularity has been taken into account. * Calculate the rating once granularity has been taken into account.
* *
@ -1369,7 +1346,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
*/ */
_granularise(rating) _granularise(rating)
{ {
if (typeof rating === 'undefined') if (typeof rating === "undefined")
{ {
return undefined; return undefined;
} }
@ -1382,10 +1359,8 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
return rating; return rating;
} }
} }
/** /**
* Shape of the marker and of the ticks. * Shape of the marker and of the ticks.
* *
@ -1395,13 +1370,12 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
* @public * @public
*/ */
Slider.Shape = { Slider.Shape = {
DISC: Symbol.for('DISC'), DISC: Symbol.for("DISC"),
TRIANGLE: Symbol.for('TRIANGLE'), TRIANGLE: Symbol.for("TRIANGLE"),
LINE: Symbol.for('LINE'), LINE: Symbol.for("LINE"),
BOX: Symbol.for('BOX') BOX: Symbol.for("BOX"),
}; };
/** /**
* Styles. * Styles.
* *
@ -1411,15 +1385,14 @@ Slider.Shape = {
* @public * @public
*/ */
Slider.Style = { Slider.Style = {
RATING: Symbol.for('RATING'), RATING: Symbol.for("RATING"),
TRIANGLE_MARKER: Symbol.for('TRIANGLE_MARKER'), TRIANGLE_MARKER: Symbol.for("TRIANGLE_MARKER"),
SLIDER: Symbol.for('SLIDER'), SLIDER: Symbol.for("SLIDER"),
WHITE_ON_BLACK: Symbol.for('WHITE_ON_BLACK'), WHITE_ON_BLACK: Symbol.for("WHITE_ON_BLACK"),
LABELS_45: Symbol.for('LABELS_45'), LABELS_45: Symbol.for("LABELS_45"),
RADIO: Symbol.for('RADIO') RADIO: Symbol.for("RADIO"),
}; };
/** /**
* Skin. * Skin.
* *
@ -1433,15 +1406,15 @@ Slider.Style = {
Slider.Skin = { Slider.Skin = {
MARKER_SIZE: null, MARKER_SIZE: null,
STANDARD: { STANDARD: {
MARKER_COLOR: new Color('red'), MARKER_COLOR: new Color("red"),
BAR_LINE_COLOR: null, BAR_LINE_COLOR: null,
TICK_COLOR: null, TICK_COLOR: null,
LABEL_COLOR: null LABEL_COLOR: null,
}, },
WHITE_ON_BLACK: { WHITE_ON_BLACK: {
MARKER_COLOR: new Color('white'), MARKER_COLOR: new Color("white"),
BAR_LINE_COLOR: new Color('black'), BAR_LINE_COLOR: new Color("black"),
TICK_COLOR: new Color('black'), TICK_COLOR: new Color("black"),
LABEL_COLOR: new Color('black') LABEL_COLOR: new Color("black"),
} },
}; };

View File

@ -7,14 +7,13 @@
* @license Distributed under the terms of the MIT License * @license Distributed under the terms of the MIT License
*/ */
import * as PIXI from "pixi.js-legacy";
import * as PIXI from 'pixi.js-legacy'; import { Color } from "../util/Color.js";
import {VisualStim} from './VisualStim.js'; import { ColorMixin } from "../util/ColorMixin.js";
import {Color} from '../util/Color.js'; import * as util from "../util/Util.js";
import {ColorMixin} from '../util/ColorMixin.js'; import { ButtonStim } from "./ButtonStim.js";
import {TextInput} from './TextInput.js'; import { TextInput } from "./TextInput.js";
import {ButtonStim} from './ButtonStim.js'; import { VisualStim } from "./VisualStim.js";
import * as util from '../util/Util.js';
// TODO finish documenting all options // TODO finish documenting all options
/** /**
@ -50,122 +49,154 @@ import * as util from '../util/Util.js';
*/ */
export class TextBox extends util.mix(VisualStim).with(ColorMixin) export class TextBox extends util.mix(VisualStim).with(ColorMixin)
{ {
constructor({name, win, pos, anchor, size, units, ori, opacity, depth, text, font, letterHeight, bold, italic, alignment, color, contrast, flipHoriz, flipVert, fillColor, borderColor, borderWidth, padding, editable, multiline, autofocus, clipMask, autoDraw, autoLog} = {}) constructor(
{
name,
win,
pos,
anchor,
size,
units,
ori,
opacity,
depth,
text,
font,
letterHeight,
bold,
italic,
alignment,
color,
contrast,
flipHoriz,
flipVert,
fillColor,
borderColor,
borderWidth,
padding,
editable,
multiline,
autofocus,
clipMask,
autoDraw,
autoLog,
} = {},
)
{ {
super({name, win, pos, size, units, ori, opacity, depth, clipMask, autoDraw, autoLog}); super({ name, win, pos, size, units, ori, opacity, depth, clipMask, autoDraw, autoLog });
this._addAttribute( this._addAttribute(
'text', "text",
text, text,
'', "",
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'placeholder', "placeholder",
text, text,
'', "",
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'anchor', "anchor",
anchor, anchor,
'center', "center",
this._onChange(false, true) this._onChange(false, true),
); );
this._addAttribute( this._addAttribute(
'flipHoriz', "flipHoriz",
flipHoriz, flipHoriz,
false, false,
this._onChange(false, false) this._onChange(false, false),
); );
this._addAttribute( this._addAttribute(
'flipVert', "flipVert",
flipVert, flipVert,
false, false,
this._onChange(false, false) this._onChange(false, false),
); );
// font: // font:
this._addAttribute( this._addAttribute(
'font', "font",
font, font,
'Arial', "Arial",
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'letterHeight', "letterHeight",
letterHeight, letterHeight,
this._getDefaultLetterHeight(), this._getDefaultLetterHeight(),
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'bold', "bold",
bold, bold,
false, false,
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'italic', "italic",
italic, italic,
false, false,
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'alignment', "alignment",
alignment, alignment,
'left', "left",
this._onChange(true, true) this._onChange(true, true),
); );
// colors: // colors:
this._addAttribute( this._addAttribute(
'color', "color",
color, color,
'white', "white",
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'fillColor', "fillColor",
fillColor, fillColor,
'lightgrey', "lightgrey",
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'borderColor', "borderColor",
borderColor, borderColor,
this.fillColor, this.fillColor,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'contrast', "contrast",
contrast, contrast,
1.0, 1.0,
this._onChange(true, false) this._onChange(true, false),
); );
// default border width: 1px // default border width: 1px
this._addAttribute( this._addAttribute(
'borderWidth', "borderWidth",
borderWidth, borderWidth,
util.to_unit([1, 0], 'pix', win, this._units)[0], util.to_unit([1, 0], "pix", win, this._units)[0],
this._onChange(true, true) this._onChange(true, true),
); );
// default padding: half of the letter height // default padding: half of the letter height
this._addAttribute( this._addAttribute(
'padding', "padding",
padding, padding,
this._letterHeight / 2.0, this._letterHeight / 2.0,
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute('multiline', multiline, false, this._onChange(true, true)); this._addAttribute("multiline", multiline, false, this._onChange(true, true));
this._addAttribute('editable', editable, false, this._onChange(true, true)); this._addAttribute("editable", editable, false, this._onChange(true, true));
this._addAttribute('autofocus', autofocus, true, this._onChange(true, false)); this._addAttribute("autofocus", autofocus, true, this._onChange(true, false));
// this._setAttribute({ // this._setAttribute({
// name: 'vertices', // name: 'vertices',
// value: vertices, // value: vertices,
// assert: v => (v != null) && (typeof v !== 'undefined') && Array.isArray(v) ) // assert: v => (v != null) && (typeof v !== 'undefined') && Array.isArray(v) )
// log); // log);
// estimate the bounding box: // estimate the bounding box:
this._estimateBoundingBox(); this._estimateBoundingBox();
@ -176,7 +207,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
} }
} }
/** /**
* Clears the current text value or sets it back to match the placeholder. * Clears the current text value or sets it back to match the placeholder.
* *
@ -185,13 +215,11 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
*/ */
reset() reset()
{ {
const text = this.editable ? '' : this.placeholder; const text = this.editable ? "" : this.placeholder;
this.setText(this.placeholder); this.setText(this.placeholder);
} }
/** /**
* Clears the current text value. * Clears the current text value.
* *
@ -203,8 +231,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
this.setText(); this.setText();
} }
/** /**
* For tweaking the underlying input value. * For tweaking the underlying input value.
* *
@ -212,9 +238,9 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
* @public * @public
* @param {string} text * @param {string} text
*/ */
setText(text = '') setText(text = "")
{ {
if (typeof this._pixi !== 'undefined') if (typeof this._pixi !== "undefined")
{ {
this._pixi.text = text; this._pixi.text = text;
} }
@ -222,7 +248,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
this._text = text; this._text = text;
} }
/** /**
* For accessing the underlying input value. * For accessing the underlying input value.
* *
@ -232,7 +257,7 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
*/ */
getText() getText()
{ {
if (typeof this._pixi !== 'undefined') if (typeof this._pixi !== "undefined")
{ {
return this._pixi.text; return this._pixi.text;
} }
@ -240,7 +265,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
return this._text; return this._text;
} }
/** /**
* Setter for the size attribute. * Setter for the size attribute.
* *
@ -253,25 +277,25 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
{ {
// test with the size is undefined, or [undefined, undefined]: // test with the size is undefined, or [undefined, undefined]:
let isSizeUndefined = ( let isSizeUndefined = (
(typeof size === 'undefined') || (size === null) || (typeof size === "undefined") || (size === null)
( Array.isArray(size) && size.every( v => typeof v === 'undefined' || v === null) ) || (Array.isArray(size) && size.every((v) => typeof v === "undefined" || v === null))
); );
if (isSizeUndefined) if (isSizeUndefined)
{ {
size = TextBox._defaultSizeMap.get(this._units); size = TextBox._defaultSizeMap.get(this._units);
if (typeof size === 'undefined') if (typeof size === "undefined")
{ {
throw { throw {
origin: 'TextBox.setSize', origin: "TextBox.setSize",
context: 'when setting the size of TextBox: ' + this._name, context: "when setting the size of TextBox: " + this._name,
error: 'no default size for unit: ' + this._units error: "no default size for unit: " + this._units,
}; };
} }
} }
const hasChanged = this._setAttribute('size', size, log); const hasChanged = this._setAttribute("size", size, log);
if (hasChanged) if (hasChanged)
{ {
@ -283,8 +307,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
} }
} }
/** /**
* Get the default letter height given the stimulus' units. * Get the default letter height given the stimulus' units.
* *
@ -296,20 +318,18 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
{ {
const height = TextBox._defaultLetterHeightMap.get(this._units); const height = TextBox._defaultLetterHeightMap.get(this._units);
if (typeof height === 'undefined') if (typeof height === "undefined")
{ {
throw { throw {
origin: 'TextBox._getDefaultLetterHeight', origin: "TextBox._getDefaultLetterHeight",
context: 'when getting the default height of TextBox: ' + this._name, context: "when getting the default height of TextBox: " + this._name,
error: 'no default letter height for unit: ' + this._units error: "no default letter height for unit: " + this._units,
}; };
} }
return height; return height;
} }
/** /**
* Get the TextInput options applied to the PIXI.TextInput. * Get the TextInput options applied to the PIXI.TextInput.
* *
@ -328,24 +348,24 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
return { return {
input: { input: {
fontFamily: this._font, fontFamily: this._font,
fontSize: letterHeight_px + 'px', fontSize: letterHeight_px + "px",
color: new Color(this._color).hex, color: new Color(this._color).hex,
fontWeight: (this._bold) ? 'bold' : 'normal', fontWeight: (this._bold) ? "bold" : "normal",
fontStyle: (this._italic) ? 'italic' : 'normal', fontStyle: (this._italic) ? "italic" : "normal",
padding: padding_px + 'px', padding: padding_px + "px",
multiline, multiline,
text: this._text, text: this._text,
height: multiline ? (height_px - 2 * padding_px) + 'px' : undefined, height: multiline ? (height_px - 2 * padding_px) + "px" : undefined,
width: (width_px - 2 * padding_px) + 'px' width: (width_px - 2 * padding_px) + "px",
}, },
box: { box: {
fill: new Color(this._fillColor).int, fill: new Color(this._fillColor).int,
rounded: 5, rounded: 5,
stroke: { stroke: {
color: new Color(this._borderColor).int, color: new Color(this._borderColor).int,
width: borderWidth_px width: borderWidth_px,
} },
/*default: { /*default: {
fill: new Color(this._fillColor).int, fill: new Color(this._fillColor).int,
rounded: 5, rounded: 5,
@ -370,12 +390,10 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
width: borderWidth_px width: borderWidth_px
} }
}*/ }*/
} },
}; };
} }
/** /**
* Estimate the bounding box. * Estimate the bounding box.
* *
@ -395,14 +413,12 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
this._pos[0] - anchor[0] * this._size[0], this._pos[0] - anchor[0] * this._size[0],
this._pos[1] - anchor[1] * boxHeight, this._pos[1] - anchor[1] * boxHeight,
this._size[0], this._size[0],
boxHeight boxHeight,
); );
// TODO take the orientation into account // TODO take the orientation into account
} }
/** /**
* Update the stimulus, if necessary. * Update the stimulus, if necessary.
* *
@ -424,13 +440,13 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
{ {
this._needPixiUpdate = false; this._needPixiUpdate = false;
if (typeof this._pixi !== 'undefined') if (typeof this._pixi !== "undefined")
{ {
this._pixi.destroy(true); this._pixi.destroy(true);
} }
// Get the currently entered text // Get the currently entered text
let enteredText = this._pixi !== undefined? this._pixi.text: ''; let enteredText = this._pixi !== undefined ? this._pixi.text : "";
// Create new TextInput // Create new TextInput
this._pixi = new TextInput(this._getTextInputOptions()); this._pixi = new TextInput(this._getTextInputOptions());
// listeners required for regular textboxes, but may cause problems with button stimuli // listeners required for regular textboxes, but may cause problems with button stimuli
@ -441,7 +457,7 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
// check if other TextBox instances are already in focus // check if other TextBox instances are already in focus
const { _drawList = [] } = this.psychoJS.window; const { _drawList = [] } = this.psychoJS.window;
const otherTextBoxWithFocus = _drawList.some(item => item instanceof TextBox && item._pixi && item._pixi._hasFocus()); const otherTextBoxWithFocus = _drawList.some((item) => item instanceof TextBox && item._pixi && item._pixi._hasFocus());
if (this._autofocus && !otherTextBoxWithFocus) if (this._autofocus && !otherTextBoxWithFocus)
{ {
this._pixi._onSurrogateFocus(); this._pixi._onSurrogateFocus();
@ -452,7 +468,7 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
} }
if (this._editable) if (this._editable)
{ {
this.text = enteredText; this.text = enteredText;
this._pixi.placeholder = this._placeholder; this._pixi.placeholder = this._placeholder;
} }
else else
@ -479,8 +495,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
this._pixi.mask = this._clipMask; this._pixi.mask = this._clipMask;
} }
/** /**
* Convert the anchor attribute into numerical values. * Convert the anchor attribute into numerical values.
* *
@ -493,30 +507,27 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
{ {
const anchor = [0.5, 0.5]; const anchor = [0.5, 0.5];
if (this._anchor.indexOf('left') > -1) if (this._anchor.indexOf("left") > -1)
{ {
anchor[0] = 0; anchor[0] = 0;
} }
else if (this._anchor.indexOf('right') > -1) else if (this._anchor.indexOf("right") > -1)
{ {
anchor[0] = 1; anchor[0] = 1;
} }
if (this._anchor.indexOf('top') > -1) if (this._anchor.indexOf("top") > -1)
{ {
anchor[1] = 0; anchor[1] = 0;
} }
else if (this._anchor.indexOf('bottom') > -1) else if (this._anchor.indexOf("bottom") > -1)
{ {
anchor[1] = 1; anchor[1] = 1;
} }
return anchor; return anchor;
} }
} }
/** /**
* <p>This map associates units to default letter height.</p> * <p>This map associates units to default letter height.</p>
* *
@ -525,18 +536,17 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
* @private * @private
*/ */
TextBox._defaultLetterHeightMap = new Map([ TextBox._defaultLetterHeightMap = new Map([
['cm', 1.0], ["cm", 1.0],
['deg', 1.0], ["deg", 1.0],
['degs', 1.0], ["degs", 1.0],
['degFlatPos', 1.0], ["degFlatPos", 1.0],
['degFlat', 1.0], ["degFlat", 1.0],
['norm', 0.1], ["norm", 0.1],
['height', 0.2], ["height", 0.2],
['pix', 20], ["pix", 20],
['pixels', 20] ["pixels", 20],
]); ]);
/** /**
* <p>This map associates units to default sizes.</p> * <p>This map associates units to default sizes.</p>
* *
@ -545,13 +555,13 @@ TextBox._defaultLetterHeightMap = new Map([
* @private * @private
*/ */
TextBox._defaultSizeMap = new Map([ TextBox._defaultSizeMap = new Map([
['cm', [15.0, -1]], ["cm", [15.0, -1]],
['deg', [15.0, -1]], ["deg", [15.0, -1]],
['degs', [15.0, -1]], ["degs", [15.0, -1]],
['degFlatPos', [15.0, -1]], ["degFlatPos", [15.0, -1]],
['degFlat', [15.0, -1]], ["degFlat", [15.0, -1]],
['norm', [1, -1]], ["norm", [1, -1]],
['height', [1, -1]], ["height", [1, -1]],
['pix', [500, -1]], ["pix", [500, -1]],
['pixels', [500, -1]] ["pixels", [500, -1]],
]); ]);

View File

@ -24,7 +24,7 @@ export class TextInput extends PIXI.Container
outline: "none", outline: "none",
text: "", text: "",
transformOrigin: "0 0", transformOrigin: "0 0",
lineHeight: "1" lineHeight: "1",
}, },
styles.input, styles.input,
); );
@ -58,7 +58,7 @@ export class TextInput extends PIXI.Container
this._restrict_value = ""; this._restrict_value = "";
this._createDOMInput(); this._createDOMInput();
this.substituteText = !this._multiline; this.substituteText = !this._multiline;
this._setState('DEFAULT'); this._setState("DEFAULT");
} }
// GETTERS & SETTERS // GETTERS & SETTERS
@ -638,7 +638,7 @@ export class TextInput extends PIXI.Container
} }
else if (components.length == 4) else if (components.length == 4)
{ {
let padding = components.map(component => let padding = components.map((component) =>
{ {
return parseFloat(component); return parseFloat(component);
}); });

View File

@ -7,14 +7,12 @@
* @license Distributed under the terms of the MIT License * @license Distributed under the terms of the MIT License
*/ */
import * as PIXI from "pixi.js-legacy";
import * as PIXI from 'pixi.js-legacy'; import { Color } from "../util/Color.js";
import {VisualStim} from './VisualStim.js'; import { ColorMixin } from "../util/ColorMixin.js";
import {Color} from '../util/Color.js';
import {ColorMixin} from '../util/ColorMixin.js';
import * as util from '../util/Util.js';
import { to_pixiPoint } from "../util/Pixi.js"; import { to_pixiPoint } from "../util/Pixi.js";
import * as util from "../util/Util.js";
import { VisualStim } from "./VisualStim.js";
/** /**
* @name module:visual.TextStim * @name module:visual.TextStim
@ -49,9 +47,34 @@ import { to_pixiPoint } from "../util/Pixi.js";
*/ */
export class TextStim extends util.mix(VisualStim).with(ColorMixin) export class TextStim extends util.mix(VisualStim).with(ColorMixin)
{ {
constructor({name, win, text, font, pos, color, opacity, depth, contrast, units, ori, height, bold, italic, alignHoriz, alignVert, wrapWidth, flipHoriz, flipVert, clipMask, autoDraw, autoLog} = {}) constructor(
{
name,
win,
text,
font,
pos,
color,
opacity,
depth,
contrast,
units,
ori,
height,
bold,
italic,
alignHoriz,
alignVert,
wrapWidth,
flipHoriz,
flipVert,
clipMask,
autoDraw,
autoLog,
} = {},
)
{ {
super({name, win, units, ori, opacity, depth, pos, clipMask, autoDraw, autoLog}); super({ name, win, units, ori, opacity, depth, pos, clipMask, autoDraw, autoLog });
// callback to deal with text metrics invalidation: // callback to deal with text metrics invalidation:
const onChange = (withPixi = false, withBoundingBox = false, withMetrics = false) => const onChange = (withPixi = false, withBoundingBox = false, withMetrics = false) =>
@ -69,81 +92,80 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
// text and font: // text and font:
this._addAttribute( this._addAttribute(
'text', "text",
text, text,
'Hello World', "Hello World",
onChange(true, true, true) onChange(true, true, true),
); );
this._addAttribute( this._addAttribute(
'alignHoriz', "alignHoriz",
alignHoriz, alignHoriz,
'center', "center",
onChange(true, true, true) onChange(true, true, true),
); );
this._addAttribute( this._addAttribute(
'alignVert', "alignVert",
alignVert, alignVert,
'center', "center",
onChange(true, true, true) onChange(true, true, true),
); );
this._addAttribute( this._addAttribute(
'flipHoriz', "flipHoriz",
flipHoriz, flipHoriz,
false, false,
onChange(true, true, true) onChange(true, true, true),
); );
this._addAttribute( this._addAttribute(
'flipVert', "flipVert",
flipVert, flipVert,
false, false,
onChange(true, true, true) onChange(true, true, true),
); );
this._addAttribute( this._addAttribute(
'font', "font",
font, font,
'Arial', "Arial",
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'height', "height",
height, height,
this._getDefaultLetterHeight(), this._getDefaultLetterHeight(),
onChange(true, true, true) onChange(true, true, true),
); );
this._addAttribute( this._addAttribute(
'wrapWidth', "wrapWidth",
wrapWidth, wrapWidth,
this._getDefaultWrapWidth(), this._getDefaultWrapWidth(),
onChange(true, true, true) onChange(true, true, true),
); );
this._addAttribute( this._addAttribute(
'bold', "bold",
bold, bold,
false, false,
onChange(true, true, true) onChange(true, true, true),
); );
this._addAttribute( this._addAttribute(
'italic', "italic",
italic, italic,
false, false,
onChange(true, true, true) onChange(true, true, true),
); );
// color: // color:
this._addAttribute( this._addAttribute(
'color', "color",
color, color,
'white', "white",
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'contrast', "contrast",
contrast, contrast,
1.0, 1.0,
this._onChange(true, false) this._onChange(true, false),
); );
// estimate the bounding box (using TextMetrics): // estimate the bounding box (using TextMetrics):
this._estimateBoundingBox(); this._estimateBoundingBox();
@ -153,8 +175,6 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
} }
} }
/** /**
* Get the metrics estimated for the text and style. * Get the metrics estimated for the text and style.
* *
@ -166,7 +186,7 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
*/ */
getTextMetrics() getTextMetrics()
{ {
if (typeof this._textMetrics === 'undefined') if (typeof this._textMetrics === "undefined")
{ {
this._textMetrics = PIXI.TextMetrics.measureText(this._text, this._getTextStyle()); this._textMetrics = PIXI.TextMetrics.measureText(this._text, this._getTextStyle());
} }
@ -174,8 +194,6 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
return this._textMetrics; return this._textMetrics;
} }
/** /**
* Get the default letter height given the stimulus' units. * Get the default letter height given the stimulus' units.
* *
@ -187,20 +205,18 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
{ {
const height = TextStim._defaultLetterHeightMap.get(this._units); const height = TextStim._defaultLetterHeightMap.get(this._units);
if (typeof height === 'undefined') if (typeof height === "undefined")
{ {
throw { throw {
origin: 'TextStim._getDefaultLetterHeight', origin: "TextStim._getDefaultLetterHeight",
context: 'when getting the default height of TextStim: ' + this._name, context: "when getting the default height of TextStim: " + this._name,
error: 'no default letter height for unit: ' + this._units error: "no default letter height for unit: " + this._units,
}; };
} }
return height; return height;
} }
/** /**
* Get the default wrap width given the stimulus' units. * Get the default wrap width given the stimulus' units.
* *
@ -212,20 +228,18 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
{ {
const wrapWidth = TextStim._defaultWrapWidthMap.get(this._units); const wrapWidth = TextStim._defaultWrapWidthMap.get(this._units);
if (typeof wrapWidth === 'undefined') if (typeof wrapWidth === "undefined")
{ {
throw { throw {
origin: 'TextStim._getDefaultWrapWidth', origin: "TextStim._getDefaultWrapWidth",
context: 'when getting the default wrap width of TextStim: ' + this._name, context: "when getting the default wrap width of TextStim: " + this._name,
error: 'no default wrap width for unit: ' + this._units error: "no default wrap width for unit: " + this._units,
}; };
} }
return wrapWidth; return wrapWidth;
} }
/** /**
* Estimate the bounding box. * Estimate the bounding box.
* *
@ -238,11 +252,11 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
{ {
// size of the text, irrespective of the orientation: // size of the text, irrespective of the orientation:
const textMetrics = this.getTextMetrics(); const textMetrics = this.getTextMetrics();
const textSize = util.to_unit( const textSize = util.to_unit(
[textMetrics.width, textMetrics.height], [textMetrics.width, textMetrics.height],
'pix', "pix",
this._win, this._win,
this._units this._units,
); );
// take the alignment into account: // take the alignment into account:
@ -251,14 +265,12 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
this._pos[0] - anchor[0] * textSize[0], this._pos[0] - anchor[0] * textSize[0],
this._pos[1] - anchor[1] * textSize[1], this._pos[1] - anchor[1] * textSize[1],
textSize[0], textSize[0],
textSize[1] textSize[1],
); );
// TODO take the orientation into account // TODO take the orientation into account
} }
/** /**
* Get the PIXI Text Style applied to the PIXI.Text * Get the PIXI Text Style applied to the PIXI.Text
* *
@ -270,17 +282,15 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
return new PIXI.TextStyle({ return new PIXI.TextStyle({
fontFamily: this._font, fontFamily: this._font,
fontSize: Math.round(this._getLengthPix(this._height)), fontSize: Math.round(this._getLengthPix(this._height)),
fontWeight: (this._bold) ? 'bold' : 'normal', fontWeight: (this._bold) ? "bold" : "normal",
fontStyle: (this._italic) ? 'italic' : 'normal', fontStyle: (this._italic) ? "italic" : "normal",
fill: this.getContrastedColor(new Color(this._color), this._contrast).hex, fill: this.getContrastedColor(new Color(this._color), this._contrast).hex,
align: this._alignHoriz, align: this._alignHoriz,
wordWrap: (typeof this._wrapWidth !== 'undefined'), wordWrap: (typeof this._wrapWidth !== "undefined"),
wordWrapWidth: (typeof this._wrapWidth !== 'undefined') ? this._getHorLengthPix(this._wrapWidth) : 0 wordWrapWidth: (typeof this._wrapWidth !== "undefined") ? this._getHorLengthPix(this._wrapWidth) : 0,
}); });
} }
/** /**
* Update the stimulus, if necessary. * Update the stimulus, if necessary.
* *
@ -301,7 +311,7 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
{ {
this._needPixiUpdate = false; this._needPixiUpdate = false;
if (typeof this._pixi !== 'undefined') if (typeof this._pixi !== "undefined")
{ {
this._pixi.destroy(true); this._pixi.destroy(true);
} }
@ -326,7 +336,7 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
// update the size attributes: // update the size attributes:
this._size = [ this._size = [
this._getLengthUnits(Math.abs(this._pixi.width)), this._getLengthUnits(Math.abs(this._pixi.width)),
this._getLengthUnits(Math.abs(this._pixi.height)) this._getLengthUnits(Math.abs(this._pixi.height)),
]; ];
// refine the estimate of the bounding box: // refine the estimate of the bounding box:
@ -334,12 +344,10 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
this._pos[0] - anchor[0] * this._size[0], this._pos[0] - anchor[0] * this._size[0],
this._pos[1] - anchor[1] * this._size[1], this._pos[1] - anchor[1] * this._size[1],
this._size[0], this._size[0],
this._size[1] this._size[1],
); );
} }
/** /**
* Convert the alignment attributes into an anchor. * Convert the alignment attributes into an anchor.
* *
@ -354,36 +362,33 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
switch (this._alignHoriz) switch (this._alignHoriz)
{ {
case 'left': case "left":
anchor.push(0); anchor.push(0);
break; break;
case 'right': case "right":
anchor.push(1); anchor.push(1);
break; break;
default: default:
case 'center': case "center":
anchor.push(0.5); anchor.push(0.5);
} }
switch (this._alignVert) switch (this._alignVert)
{ {
case 'top': case "top":
anchor.push(0); anchor.push(0);
break; break;
case 'bottom': case "bottom":
anchor.push(1); anchor.push(1);
break; break;
default: default:
case 'center': case "center":
anchor.push(0.5); anchor.push(0.5);
} }
return anchor; return anchor;
} }
} }
/** /**
* <p>This map associates units to default letter height.</p> * <p>This map associates units to default letter height.</p>
* *
@ -392,19 +397,17 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
* @private * @private
*/ */
TextStim._defaultLetterHeightMap = new Map([ TextStim._defaultLetterHeightMap = new Map([
['cm', 1.0], ["cm", 1.0],
['deg', 1.0], ["deg", 1.0],
['degs', 1.0], ["degs", 1.0],
['degFlatPos', 1.0], ["degFlatPos", 1.0],
['degFlat', 1.0], ["degFlat", 1.0],
['norm', 0.1], ["norm", 0.1],
['height', 0.2], ["height", 0.2],
['pix', 20], ["pix", 20],
['pixels', 20] ["pixels", 20],
]); ]);
/** /**
* <p>This map associates units to default wrap width.</p> * <p>This map associates units to default wrap width.</p>
* *
@ -413,13 +416,13 @@ TextStim._defaultLetterHeightMap = new Map([
* @private * @private
*/ */
TextStim._defaultWrapWidthMap = new Map([ TextStim._defaultWrapWidthMap = new Map([
['cm', 15.0], ["cm", 15.0],
['deg', 15.0], ["deg", 15.0],
['degs', 15.0], ["degs", 15.0],
['degFlatPos', 15.0], ["degFlatPos", 15.0],
['degFlat', 15.0], ["degFlat", 15.0],
['norm', 1], ["norm", 1],
['height', 1], ["height", 1],
['pix', 500], ["pix", 500],
['pixels', 500] ["pixels", 500],
]); ]);

View File

@ -7,12 +7,10 @@
* @license Distributed under the terms of the MIT License * @license Distributed under the terms of the MIT License
*/ */
import * as PIXI from "pixi.js-legacy";
import * as PIXI from 'pixi.js-legacy'; import { MinimalStim } from "../core/MinimalStim.js";
import {MinimalStim} from '../core/MinimalStim.js'; import { WindowMixin } from "../core/WindowMixin.js";
import {WindowMixin} from '../core/WindowMixin.js'; import * as util from "../util/Util.js";
import * as util from '../util/Util.js';
/** /**
* Base class for all visual stimuli. * Base class for all visual stimuli.
@ -36,55 +34,54 @@ import * as util from '../util/Util.js';
*/ */
export class VisualStim extends util.mix(MinimalStim).with(WindowMixin) export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
{ {
constructor({name, win, units, ori, opacity, depth, pos, size, clipMask, autoDraw, autoLog} = {}) constructor({ name, win, units, ori, opacity, depth, pos, size, clipMask, autoDraw, autoLog } = {})
{ {
super({win, name, autoDraw, autoLog}); super({ win, name, autoDraw, autoLog });
this._addAttribute( this._addAttribute(
'units', "units",
units, units,
(typeof win !== 'undefined' && win !== null) ? win.units : 'height', (typeof win !== "undefined" && win !== null) ? win.units : "height",
this._onChange(true, true) this._onChange(true, true),
); );
this._addAttribute( this._addAttribute(
'pos', "pos",
pos, pos,
[0, 0] [0, 0],
); );
this._addAttribute( this._addAttribute(
'size', "size",
size, size,
undefined undefined,
); );
this._addAttribute( this._addAttribute(
'ori', "ori",
ori, ori,
0.0 0.0,
); );
this._addAttribute( this._addAttribute(
'opacity', "opacity",
opacity, opacity,
1.0, 1.0,
this._onChange(true, false) this._onChange(true, false),
); );
this._addAttribute( this._addAttribute(
'depth', "depth",
depth, depth,
0, 0,
this._onChange(false, false) this._onChange(false, false),
); );
this._addAttribute( this._addAttribute(
'clipMask', "clipMask",
clipMask, clipMask,
null, null,
this._onChange(false, false) this._onChange(false, false),
); );
// bounding box of the stimulus, in stimulus units // bounding box of the stimulus, in stimulus units
// note: boundingBox does not take the orientation into account // note: boundingBox does not take the orientation into account
this._addAttribute('boundingBox', PIXI.Rectangle.EMPTY); this._addAttribute("boundingBox", PIXI.Rectangle.EMPTY);
// the stimulus need to be updated: // the stimulus need to be updated:
this._needUpdate = true; this._needUpdate = true;
@ -92,8 +89,6 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
this._needPixiUpdate = true; this._needPixiUpdate = true;
} }
/** /**
* Force a refresh of the stimulus. * Force a refresh of the stimulus.
* *
@ -107,8 +102,6 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
this._onChange(true, true)(); this._onChange(true, true)();
} }
/** /**
* Setter for the size attribute. * Setter for the size attribute.
* *
@ -120,7 +113,7 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
setSize(size, log = false) setSize(size, log = false)
{ {
// size is either undefined, null, or a tuple of numbers: // size is either undefined, null, or a tuple of numbers:
if (typeof size !== 'undefined' && size !== null) if (typeof size !== "undefined" && size !== null)
{ {
size = util.toNumerical(size); size = util.toNumerical(size);
if (!Array.isArray(size)) if (!Array.isArray(size))
@ -129,7 +122,7 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
} }
} }
const hasChanged = this._setAttribute('size', size, log); const hasChanged = this._setAttribute("size", size, log);
if (hasChanged) if (hasChanged)
{ {
@ -137,8 +130,6 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
} }
} }
/** /**
* Setter for the orientation attribute. * Setter for the orientation attribute.
* *
@ -149,20 +140,17 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
*/ */
setOri(ori, log = false) setOri(ori, log = false)
{ {
const hasChanged = this._setAttribute('ori', ori, log); const hasChanged = this._setAttribute("ori", ori, log);
if (hasChanged) if (hasChanged)
{ {
let radians = -ori * 0.017453292519943295; let radians = -ori * 0.017453292519943295;
this._rotationMatrix = [[Math.cos(radians), -Math.sin(radians)], this._rotationMatrix = [[Math.cos(radians), -Math.sin(radians)], [Math.sin(radians), Math.cos(radians)]];
[Math.sin(radians), Math.cos(radians)]];
this._onChange(true, true)(); this._onChange(true, true)();
} }
} }
/** /**
* Setter for the position attribute. * Setter for the position attribute.
* *
@ -174,20 +162,18 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
setPos(pos, log = false) setPos(pos, log = false)
{ {
const prevPos = this._pos; const prevPos = this._pos;
const hasChanged = this._setAttribute('pos', util.toNumerical(pos), log); const hasChanged = this._setAttribute("pos", util.toNumerical(pos), log);
if (hasChanged) if (hasChanged)
{ {
this._needUpdate = true; this._needUpdate = true;
// update the bounding box, without calling _estimateBoundingBox: // update the bounding box, without calling _estimateBoundingBox:
this._boundingBox.x += this._pos[0] - prevPos[0]; this._boundingBox.x += this._pos[0] - prevPos[0];
this._boundingBox.y += this._pos[1] - prevPos[1]; this._boundingBox.y += this._pos[1] - prevPos[1];
} }
} }
/** /**
* Determine whether an object is inside the bounding box of the stimulus. * Determine whether an object is inside the bounding box of the stimulus.
* *
@ -202,12 +188,12 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
// get the position of the object, in pixel coordinates: // get the position of the object, in pixel coordinates:
const objectPos_px = util.getPositionFromObject(object, units); const objectPos_px = util.getPositionFromObject(object, units);
if (typeof objectPos_px === 'undefined') if (typeof objectPos_px === "undefined")
{ {
throw { throw {
origin: 'VisualStim.contains', origin: "VisualStim.contains",
context: 'when determining whether VisualStim: ' + this._name + ' contains object: ' + util.toString(object), context: "when determining whether VisualStim: " + this._name + " contains object: " + util.toString(object),
error: 'unable to determine the position of the object' error: "unable to determine the position of the object",
}; };
} }
@ -215,8 +201,6 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
return this._getBoundingBox_px().contains(objectPos_px[0], objectPos_px[1]); return this._getBoundingBox_px().contains(objectPos_px[0], objectPos_px[1]);
} }
/** /**
* Estimate the bounding box. * Estimate the bounding box.
* *
@ -227,14 +211,12 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
_estimateBoundingBox() _estimateBoundingBox()
{ {
throw { throw {
origin: 'VisualStim._estimateBoundingBox', origin: "VisualStim._estimateBoundingBox",
context: `when estimating the bounding box of visual stimulus: ${this._name}`, context: `when estimating the bounding box of visual stimulus: ${this._name}`,
error: 'this method is abstract and should not be called.' error: "this method is abstract and should not be called.",
}; };
} }
/** /**
* Get the bounding box in pixel coordinates * Get the bounding box in pixel coordinates
* *
@ -245,37 +227,35 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
*/ */
_getBoundingBox_px() _getBoundingBox_px()
{ {
if (this._units === 'pix') if (this._units === "pix")
{ {
return this._boundingBox.clone(); return this._boundingBox.clone();
} }
else if (this._units === 'norm') else if (this._units === "norm")
{ {
return new PIXI.Rectangle( return new PIXI.Rectangle(
this._boundingBox.x * this._win.size[0] / 2, this._boundingBox.x * this._win.size[0] / 2,
this._boundingBox.y * this._win.size[1] / 2, this._boundingBox.y * this._win.size[1] / 2,
this._boundingBox.width * this._win.size[0] / 2, this._boundingBox.width * this._win.size[0] / 2,
this._boundingBox.height * this._win.size[1] / 2 this._boundingBox.height * this._win.size[1] / 2,
); );
} }
else if (this._units === 'height') else if (this._units === "height")
{ {
const minSize = Math.min(this._win.size[0], this._win.size[1]); const minSize = Math.min(this._win.size[0], this._win.size[1]);
return new PIXI.Rectangle( return new PIXI.Rectangle(
this._boundingBox.x * minSize, this._boundingBox.x * minSize,
this._boundingBox.y * minSize, this._boundingBox.y * minSize,
this._boundingBox.width * minSize, this._boundingBox.width * minSize,
this._boundingBox.height * minSize this._boundingBox.height * minSize,
); );
} }
else else
{ {
throw Object.assign(response, {error: `unknown units: ${this._units}`}); throw Object.assign(response, { error: `unknown units: ${this._units}` });
} }
} }
/** /**
* Generate a callback that prepares updates to the stimulus. * Generate a callback that prepares updates to the stimulus.
* This is typically called in the constructor of a stimulus, when attributes are added with _addAttribute. * This is typically called in the constructor of a stimulus, when attributes are added with _addAttribute.
@ -302,5 +282,4 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
} }
}; };
} }
} }

View File

@ -1,12 +1,12 @@
export * from './ButtonStim.js'; export * from "./ButtonStim.js";
export * from './Form.js'; export * from "./Form.js";
export * from './ImageStim.js'; export * from "./ImageStim.js";
export * from './MovieStim.js'; export * from "./MovieStim.js";
export * from './Polygon.js'; export * from "./Polygon.js";
export * from './Rect.js'; export * from "./Rect.js";
export * from './ShapeStim.js'; export * from "./ShapeStim.js";
export * from './Slider.js'; export * from "./Slider.js";
export * from './TextBox.js'; export * from "./TextBox.js";
export * from './TextInput.js'; export * from "./TextInput.js";
export * from './TextStim.js'; export * from "./TextStim.js";
export * from './VisualStim.js'; export * from "./VisualStim.js";