mirror of
https://github.com/psychopy/psychojs.git
synced 2025-05-10 18:50:54 +00:00
various fixed to Slider, add intensity to MultiStairHandler, improved background download of resources, faster TextStim
This commit is contained in:
parent
860cf1f51d
commit
5dbac82d73
@ -258,7 +258,19 @@ export class EventManager
|
|||||||
self._mouseInfo.buttons.times[event.button] = self._psychoJS._monotonicClock.getTime() - self._mouseInfo.buttons.clocks[event.button].getLastResetTime();
|
self._mouseInfo.buttons.times[event.button] = self._psychoJS._monotonicClock.getTime() - self._mouseInfo.buttons.clocks[event.button].getLastResetTime();
|
||||||
self._mouseInfo.pos = [event.offsetX, event.offsetY];
|
self._mouseInfo.pos = [event.offsetX, event.offsetY];
|
||||||
|
|
||||||
this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button down, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")");
|
this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button up, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")");
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
renderer.view.addEventListener("pointerout", (event) =>
|
||||||
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// if the pointer leaves the canvas: cancel all buttons
|
||||||
|
self._mouseInfo.buttons.pressed = [0, 0, 0];
|
||||||
|
self._mouseInfo.buttons.times = [0.0, 0.0, 0.0];
|
||||||
|
self._mouseInfo.pos = [event.offsetX, event.offsetY];
|
||||||
|
|
||||||
|
this._psychoJS.experimentLogger.data("Mouse: out, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")");
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
renderer.view.addEventListener("touchend", (event) =>
|
renderer.view.addEventListener("touchend", (event) =>
|
||||||
@ -272,7 +284,7 @@ export class EventManager
|
|||||||
const touches = event.changedTouches;
|
const touches = event.changedTouches;
|
||||||
self._mouseInfo.pos = [touches[0].pageX, touches[0].pageY];
|
self._mouseInfo.pos = [touches[0].pageX, touches[0].pageY];
|
||||||
|
|
||||||
this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button down, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")");
|
this._psychoJS.experimentLogger.data("Mouse: " + event.button + " button up, pos=(" + self._mouseInfo.pos[0] + "," + self._mouseInfo.pos[1] + ")");
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
renderer.view.addEventListener("pointermove", (event) =>
|
renderer.view.addEventListener("pointermove", (event) =>
|
||||||
|
@ -190,7 +190,10 @@ export class GUI
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
htmlCode += '<p class="validateTips">Fields marked with an asterisk (*) are required.</p>';
|
if (this._requiredKeys.length > 0)
|
||||||
|
{
|
||||||
|
htmlCode += '<p class="validateTips">Fields marked with an asterisk (*) are required.</p>';
|
||||||
|
}
|
||||||
|
|
||||||
// add a progress bar:
|
// add a progress bar:
|
||||||
htmlCode += '<hr><div id="progressMsg" class="progress">' + self._progressMsg + "</div>";
|
htmlCode += '<hr><div id="progressMsg" class="progress">' + self._progressMsg + "</div>";
|
||||||
@ -322,16 +325,7 @@ export class GUI
|
|||||||
} = {})
|
} = {})
|
||||||
{
|
{
|
||||||
// close the previously opened dialog box, if there is one:
|
// close the previously opened dialog box, if there is one:
|
||||||
const expDialog = jQuery("#expDialog");
|
this.closeDialog();
|
||||||
if (expDialog.length)
|
|
||||||
{
|
|
||||||
expDialog.dialog("destroy").remove();
|
|
||||||
}
|
|
||||||
const msgDialog = jQuery("#msgDialog");
|
|
||||||
if (msgDialog.length)
|
|
||||||
{
|
|
||||||
msgDialog.dialog("destroy").remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
let htmlCode;
|
let htmlCode;
|
||||||
let titleColour;
|
let titleColour;
|
||||||
@ -448,6 +442,27 @@ export class GUI
|
|||||||
.prev(".ui-dialog-titlebar").css("background", titleColour);
|
.prev(".ui-dialog-titlebar").css("background", titleColour);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the previously opened dialog box, if there is one.
|
||||||
|
*
|
||||||
|
* @name module:core.GUI#closeDialog
|
||||||
|
* @function
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
closeDialog()
|
||||||
|
{
|
||||||
|
const expDialog = jQuery("#expDialog");
|
||||||
|
if (expDialog.length)
|
||||||
|
{
|
||||||
|
expDialog.dialog("destroy").remove();
|
||||||
|
}
|
||||||
|
const msgDialog = jQuery("#msgDialog");
|
||||||
|
if (msgDialog.length)
|
||||||
|
{
|
||||||
|
msgDialog.dialog("destroy").remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener for resource event from the [Server Manager]{@link ServerManager}.
|
* Listener for resource event from the [Server Manager]{@link ServerManager}.
|
||||||
*
|
*
|
||||||
|
@ -393,7 +393,7 @@ export class PsychoJS
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start the asynchronous download of resources:
|
// start the asynchronous download of resources:
|
||||||
await this._serverManager.prepareResources(resources);
|
this._serverManager.prepareResources(resources);
|
||||||
|
|
||||||
// start the experiment:
|
// start the experiment:
|
||||||
this.logger.info("[PsychoJS] Start Experiment.");
|
this.logger.info("[PsychoJS] Start Experiment.");
|
||||||
|
@ -12,6 +12,7 @@ import { ExperimentHandler } from "../data/ExperimentHandler.js";
|
|||||||
import { Clock, MonotonicClock } from "../util/Clock.js";
|
import { Clock, MonotonicClock } from "../util/Clock.js";
|
||||||
import { PsychObject } from "../util/PsychObject.js";
|
import { PsychObject } from "../util/PsychObject.js";
|
||||||
import * as util from "../util/Util.js";
|
import * as util from "../util/Util.js";
|
||||||
|
import { Scheduler } from "../util/Scheduler.js";
|
||||||
import { PsychoJS } from "./PsychoJS.js";
|
import { PsychoJS } from "./PsychoJS.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -519,7 +520,7 @@ export class ServerManager extends PsychObject
|
|||||||
}
|
}
|
||||||
catch (error)
|
catch (error)
|
||||||
{
|
{
|
||||||
console.log("error", error);
|
console.error("error", error);
|
||||||
throw Object.assign(response, { error });
|
throw Object.assign(response, { error });
|
||||||
// throw { ...response, error: error };
|
// throw { ...response, error: error };
|
||||||
}
|
}
|
||||||
@ -543,7 +544,7 @@ export class ServerManager extends PsychObject
|
|||||||
};
|
};
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
return () =>
|
return async () =>
|
||||||
{
|
{
|
||||||
const t = self._waitForDownloadComponent.clock.getTime();
|
const t = self._waitForDownloadComponent.clock.getTime();
|
||||||
|
|
||||||
@ -558,7 +559,7 @@ export class ServerManager extends PsychObject
|
|||||||
{
|
{
|
||||||
for (const [name, { status, path, data }] of this._resources)
|
for (const [name, { status, path, data }] of this._resources)
|
||||||
{
|
{
|
||||||
resources.append({ name, path });
|
resources.push({ name, path });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -592,6 +593,7 @@ export class ServerManager extends PsychObject
|
|||||||
resourcesToDownload.add(name);
|
resourcesToDownload.add(name);
|
||||||
self._psychoJS.logger.debug("registered resource:", name, path);
|
self._psychoJS.logger.debug("registered resource:", name, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the resource has been registered but is not downloaded yet:
|
// the resource has been registered but is not downloaded yet:
|
||||||
else if (typeof pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED)
|
else if (typeof pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED)
|
||||||
{ // else if (typeof pathStatusData.data === 'undefined')
|
{ // else if (typeof pathStatusData.data === 'undefined')
|
||||||
@ -599,25 +601,30 @@ export class ServerManager extends PsychObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self._waitForDownloadComponent.status = PsychoJS.Status.STARTED;
|
||||||
|
|
||||||
// start the download:
|
// start the download:
|
||||||
self._downloadResources(resourcesToDownload);
|
self._downloadResources(resourcesToDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check whether all resources have been downloaded:
|
if (self._waitForDownloadComponent.status === PsychoJS.Status.STARTED)
|
||||||
for (const name of self._waitForDownloadComponent.resources)
|
|
||||||
{
|
{
|
||||||
const pathStatusData = this._resources.get(name);
|
// check whether all resources have been downloaded:
|
||||||
|
for (const name of self._waitForDownloadComponent.resources)
|
||||||
|
{
|
||||||
|
const pathStatusData = this._resources.get(name);
|
||||||
|
|
||||||
// the resource has not been downloaded yet: loop this component
|
// the resource has not been downloaded yet: loop this component
|
||||||
if (typeof pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED)
|
if (pathStatusData.status !== ServerManager.ResourceStatus.DOWNLOADED)
|
||||||
{ // if (typeof pathStatusData.data === 'undefined')
|
{ // if (typeof pathStatusData.data === 'undefined')
|
||||||
return Scheduler.Event.FLIP_REPEAT;
|
return Scheduler.Event.FLIP_REPEAT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// all resources have been downloaded: move to the next component:
|
// all resources have been downloaded: move to the next component:
|
||||||
self._waitForDownloadComponent.status = PsychoJS.Status.FINISHED;
|
self._waitForDownloadComponent.status = PsychoJS.Status.FINISHED;
|
||||||
return Scheduler.Event.NEXT;
|
return Scheduler.Event.NEXT;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -757,13 +764,16 @@ export class ServerManager extends PsychObject
|
|||||||
* @name module:core.ServerManager#uploadAudioVideo
|
* @name module:core.ServerManager#uploadAudioVideo
|
||||||
* @function
|
* @function
|
||||||
* @public
|
* @public
|
||||||
* @param {Blob} mediaBlob - the audio or video blob to be uploaded
|
* @param @param {Object} options
|
||||||
* @param {string} tag - additional tag
|
* @param {Blob} options.mediaBlob - the audio or video blob to be uploaded
|
||||||
* @param {boolean} [waitForCompletion=false] - whether or not to wait for completion
|
* @param {string} options.tag - additional tag
|
||||||
|
* @param {boolean} [options.waitForCompletion=false] - whether or not to wait for completion
|
||||||
* before returning
|
* before returning
|
||||||
|
* @param {boolean} [options.showDialog=false] - whether or not to open a dialog box to inform the participant to wait for the data to be uploaded to the server
|
||||||
|
* @param {string} [options.dialogMsg="Please wait a few moments while the data is uploading to the server"] - default message informing the participant to wait for the data to be uploaded to the server
|
||||||
* @returns {Promise<ServerManager.UploadDataPromise>} the response
|
* @returns {Promise<ServerManager.UploadDataPromise>} the response
|
||||||
*/
|
*/
|
||||||
async uploadAudioVideo(mediaBlob, tag, waitForCompletion = false)
|
async uploadAudioVideo({mediaBlob, tag, waitForCompletion = false, showDialog = false, dialogMsg = "Please wait a few moments while the data is uploading to the server"})
|
||||||
{
|
{
|
||||||
const response = {
|
const response = {
|
||||||
origin: "ServerManager.uploadAudio",
|
origin: "ServerManager.uploadAudio",
|
||||||
@ -783,7 +793,13 @@ export class ServerManager extends PsychObject
|
|||||||
this.setStatus(ServerManager.Status.BUSY);
|
this.setStatus(ServerManager.Status.BUSY);
|
||||||
|
|
||||||
// open pop-up dialog:
|
// open pop-up dialog:
|
||||||
// TODO
|
if (showDialog)
|
||||||
|
{
|
||||||
|
this.psychoJS.gui.dialog({
|
||||||
|
warning: dialogMsg,
|
||||||
|
showOK: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// prepare the request:
|
// prepare the request:
|
||||||
const info = this.psychoJS.experiment.extraInfo;
|
const info = this.psychoJS.experiment.extraInfo;
|
||||||
@ -850,7 +866,7 @@ export class ServerManager extends PsychObject
|
|||||||
redirect: "follow",
|
redirect: "follow",
|
||||||
referrerPolicy: "no-referrer"
|
referrerPolicy: "no-referrer"
|
||||||
});
|
});
|
||||||
const checkSatusResponse = await response.json();
|
const checkStatusResponse = await response.json();
|
||||||
this._psychoJS.logger.debug(`check upload status response: ${JSON.stringify(checkStatusResponse)}`);
|
this._psychoJS.logger.debug(`check upload status response: ${JSON.stringify(checkStatusResponse)}`);
|
||||||
|
|
||||||
if (("status" in checkStatusResponse) && checkStatusResponse["status"] === "COMPLETED")
|
if (("status" in checkStatusResponse) && checkStatusResponse["status"] === "COMPLETED")
|
||||||
@ -860,6 +876,11 @@ export class ServerManager extends PsychObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showDialog)
|
||||||
|
{
|
||||||
|
this.psychoJS.gui.closeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
this.setStatus(ServerManager.Status.READY);
|
this.setStatus(ServerManager.Status.READY);
|
||||||
return postMediaResponse;
|
return postMediaResponse;
|
||||||
}
|
}
|
||||||
@ -1103,6 +1124,7 @@ export class ServerManager extends PsychObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start loading resources marked for howler.js:
|
// start loading resources marked for howler.js:
|
||||||
|
const self = this;
|
||||||
for (const name of soundResources)
|
for (const name of soundResources)
|
||||||
{
|
{
|
||||||
const pathStatusData = this._resources.get(name);
|
const pathStatusData = this._resources.get(name);
|
||||||
|
@ -76,14 +76,14 @@ export let WindowMixin = (superclass) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert the given length from pixel units to the stimulus units
|
* Convert the given length from pixel units to the stimulus units
|
||||||
*
|
*
|
||||||
* @name module:core.WindowMixin#_getLengthUnits
|
* @name module:core.WindowMixin#_getLengthUnits
|
||||||
* @function
|
* @function
|
||||||
* @protected
|
* @protected
|
||||||
* @param {number} length_px - the length in pixel units
|
* @param {number} length_px - the length in pixel units
|
||||||
* @return {number} - the length in stimulus units
|
* @return {number} - the length in stimulus units
|
||||||
*/
|
*/
|
||||||
_getLengthUnits(length_px)
|
_getLengthUnits(length_px)
|
||||||
{
|
{
|
||||||
let response = {
|
let response = {
|
||||||
|
@ -170,7 +170,7 @@ export class ExperimentHandler extends PsychObject
|
|||||||
* @name module:data.ExperimentHandler#nextEntry
|
* @name module:data.ExperimentHandler#nextEntry
|
||||||
* @function
|
* @function
|
||||||
* @public
|
* @public
|
||||||
* @param {Object[]} snapshots - array of loop snapshots
|
* @param {Object | Object[] | undefined} snapshots - array of loop snapshots
|
||||||
*/
|
*/
|
||||||
nextEntry(snapshots)
|
nextEntry(snapshots)
|
||||||
{
|
{
|
||||||
@ -239,16 +239,20 @@ export class ExperimentHandler extends PsychObject
|
|||||||
* @public
|
* @public
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
* @param {Array.<Object>} [options.attributes] - the attributes to be saved
|
* @param {Array.<Object>} [options.attributes] - the attributes to be saved
|
||||||
* @param {Array.<Object>} [options.sync] - whether or not to communicate with the server in a synchronous manner
|
* @param {boolean} [options.sync=false] - whether or not to communicate with the server in a synchronous manner
|
||||||
|
* @param {string} [options.tag=''] - an optional tag to add to the filename to which the data is saved (for CSV and XLSX saving options)
|
||||||
|
* @param {boolean} [options.clear=false] - whether or not to clear all experiment results immediately after they are saved (this is useful when saving data in separate chunks, throughout an experiment)
|
||||||
*/
|
*/
|
||||||
async save({
|
async save({
|
||||||
attributes = [],
|
attributes = [],
|
||||||
sync = false,
|
sync = false,
|
||||||
|
tag = "",
|
||||||
|
clear = false
|
||||||
} = {})
|
} = {})
|
||||||
{
|
{
|
||||||
this._psychoJS.logger.info("[PsychoJS] Save experiment results.");
|
this._psychoJS.logger.info("[PsychoJS] Save experiment results.");
|
||||||
|
|
||||||
// (*) get attributes:
|
// get attributes:
|
||||||
if (attributes.length === 0)
|
if (attributes.length === 0)
|
||||||
{
|
{
|
||||||
attributes = this._trialsKeys.slice();
|
attributes = this._trialsKeys.slice();
|
||||||
@ -274,7 +278,7 @@ export class ExperimentHandler extends PsychObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// (*) get various experiment info:
|
// get various experiment info:
|
||||||
const info = this.extraInfo;
|
const info = this.extraInfo;
|
||||||
const __experimentName = (typeof info.expName !== "undefined") ? info.expName : this.psychoJS.config.experiment.name;
|
const __experimentName = (typeof info.expName !== "undefined") ? info.expName : this.psychoJS.config.experiment.name;
|
||||||
const __participant = ((typeof info.participant === "string" && info.participant.length > 0) ? info.participant : "PARTICIPANT");
|
const __participant = ((typeof info.participant === "string" && info.participant.length > 0) ? info.participant : "PARTICIPANT");
|
||||||
@ -283,17 +287,26 @@ export class ExperimentHandler extends PsychObject
|
|||||||
const gitlabConfig = this._psychoJS.config.gitlab;
|
const gitlabConfig = this._psychoJS.config.gitlab;
|
||||||
const __projectId = (typeof gitlabConfig !== "undefined" && typeof gitlabConfig.projectId !== "undefined") ? gitlabConfig.projectId : undefined;
|
const __projectId = (typeof gitlabConfig !== "undefined" && typeof gitlabConfig.projectId !== "undefined") ? gitlabConfig.projectId : undefined;
|
||||||
|
|
||||||
// (*) save to a .csv file:
|
let data = this._trialsData;
|
||||||
|
// if the experiment data have to be cleared, we first make a copy of them:
|
||||||
|
if (clear)
|
||||||
|
{
|
||||||
|
data = this._trialsData.slice();
|
||||||
|
this._trialsData = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// save to a .csv file:
|
||||||
if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.CSV)
|
if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.CSV)
|
||||||
{
|
{
|
||||||
// note: we use the XLSX library as it automatically deals with header, takes care of quotes,
|
// note: we use the XLSX library as it automatically deals with header, takes care of quotes,
|
||||||
// newlines, etc.
|
// newlines, etc.
|
||||||
const worksheet = XLSX.utils.json_to_sheet(this._trialsData);
|
// TODO only save the given attributes
|
||||||
|
const worksheet = XLSX.utils.json_to_sheet(data);
|
||||||
// prepend BOM
|
// prepend BOM
|
||||||
const csv = "\ufeff" + XLSX.utils.sheet_to_csv(worksheet);
|
const csv = "\ufeff" + XLSX.utils.sheet_to_csv(worksheet);
|
||||||
|
|
||||||
// upload data to the pavlovia server or offer them for download:
|
// upload data to the pavlovia server or offer them for download:
|
||||||
const key = __participant + "_" + __experimentName + "_" + __datetime + ".csv";
|
const key = `${__participant}_${__experimentName}_${__datetime}${tag}.csv`;
|
||||||
if (
|
if (
|
||||||
this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER
|
this._psychoJS.getEnvironment() === ExperimentHandler.Environment.SERVER
|
||||||
&& this._psychoJS.config.experiment.status === "RUNNING"
|
&& this._psychoJS.config.experiment.status === "RUNNING"
|
||||||
@ -307,17 +320,17 @@ export class ExperimentHandler extends PsychObject
|
|||||||
util.offerDataForDownload(key, csv, "text/csv");
|
util.offerDataForDownload(key, csv, "text/csv");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// (*) save in the database on the remote server:
|
// save to the database on the pavlovia server:
|
||||||
else if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.DATABASE)
|
else if (this._psychoJS.config.experiment.saveFormat === ExperimentHandler.SaveFormat.DATABASE)
|
||||||
{
|
{
|
||||||
let documents = [];
|
let documents = [];
|
||||||
|
|
||||||
for (let r = 0; r < this._trialsData.length; r++)
|
for (let r = 0; r < data.length; r++)
|
||||||
{
|
{
|
||||||
let doc = { __projectId, __experimentName, __participant, __session, __datetime };
|
let doc = { __projectId, __experimentName, __participant, __session, __datetime };
|
||||||
for (let h = 0; h < attributes.length; h++)
|
for (let h = 0; h < attributes.length; h++)
|
||||||
{
|
{
|
||||||
doc[attributes[h]] = this._trialsData[r][attributes[h]];
|
doc[attributes[h]] = data[r][attributes[h]];
|
||||||
}
|
}
|
||||||
|
|
||||||
documents.push(doc);
|
documents.push(doc);
|
||||||
|
@ -95,10 +95,11 @@ export class MultiStairHandler extends TrialHandler
|
|||||||
* @function
|
* @function
|
||||||
* @public
|
* @public
|
||||||
* @param{number} response - the response to the trial, must be either 0 (incorrect or
|
* @param{number} response - the response to the trial, must be either 0 (incorrect or
|
||||||
* non-detected) or 1 (correct or detected).
|
* non-detected) or 1 (correct or detected)
|
||||||
|
* @param{number | undefined} [value] - optional intensity / contrast / threshold
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
addResponse(response)
|
addResponse(response, value)
|
||||||
{
|
{
|
||||||
// check that response is either 0 or 1:
|
// check that response is either 0 or 1:
|
||||||
if (response !== 0 && response !== 1)
|
if (response !== 0 && response !== 1)
|
||||||
@ -113,7 +114,7 @@ export class MultiStairHandler extends TrialHandler
|
|||||||
if (!this._finished)
|
if (!this._finished)
|
||||||
{
|
{
|
||||||
// update the current staircase:
|
// update the current staircase:
|
||||||
this._currentStaircase.addResponse(response);
|
this._currentStaircase.addResponse(response, value);
|
||||||
|
|
||||||
// move onto the next trial:
|
// move onto the next trial:
|
||||||
this._nextTrial();
|
this._nextTrial();
|
||||||
|
@ -95,10 +95,11 @@ export class QuestHandler extends TrialHandler
|
|||||||
* @function
|
* @function
|
||||||
* @public
|
* @public
|
||||||
* @param{number} response - the response to the trial, must be either 0 (incorrect or
|
* @param{number} response - the response to the trial, must be either 0 (incorrect or
|
||||||
* non-detected) or 1 (correct or detected).
|
* non-detected) or 1 (correct or detected)
|
||||||
|
* @param{number | undefined} [value] - optional intensity / contrast / threshold
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
addResponse(response)
|
addResponse(response, value)
|
||||||
{
|
{
|
||||||
// check that response is either 0 or 1:
|
// check that response is either 0 or 1:
|
||||||
if (response !== 0 && response !== 1)
|
if (response !== 0 && response !== 1)
|
||||||
@ -111,7 +112,14 @@ export class QuestHandler extends TrialHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update the QUEST pdf:
|
// update the QUEST pdf:
|
||||||
this._jsQuest = jsQUEST.QuestUpdate(this._jsQuest, this._questValue, response);
|
if (typeof value !== "undefined")
|
||||||
|
{
|
||||||
|
this._jsQuest = jsQUEST.QuestUpdate(this._jsQuest, value, response);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._jsQuest = jsQUEST.QuestUpdate(this._jsQuest, this._questValue, response);
|
||||||
|
}
|
||||||
|
|
||||||
if (!this._finished)
|
if (!this._finished)
|
||||||
{
|
{
|
||||||
|
@ -157,7 +157,10 @@ export class AudioClip extends PsychObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
// upload the data:
|
// upload the data:
|
||||||
return this._psychoJS.serverManager.uploadAudioVideo(this._data, filename);
|
return this._psychoJS.serverManager.uploadAudioVideo({
|
||||||
|
mediaBlob: this._data,
|
||||||
|
tag: filename
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -322,7 +322,10 @@ export class Microphone extends PsychObject
|
|||||||
|
|
||||||
// upload the blob:
|
// upload the blob:
|
||||||
const audioBlob = new Blob(this._audioBuffer);
|
const audioBlob = new Blob(this._audioBuffer);
|
||||||
return this._psychoJS.serverManager.uploadAudioVideo(audioBlob, tag);
|
return this._psychoJS.serverManager.uploadAudioVideo({
|
||||||
|
mediaBlob: audioBlob,
|
||||||
|
tag
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,8 +21,12 @@ import {ExperimentHandler} from "../data/ExperimentHandler.js";
|
|||||||
* @name module:visual.Camera
|
* @name module:visual.Camera
|
||||||
* @class
|
* @class
|
||||||
* @param {Object} options
|
* @param {Object} options
|
||||||
* @param @param {module:core.Window} options.win - the associated Window
|
* @param {module:core.Window} options.win - the associated Window
|
||||||
* @param {string} [options.format='video/webm;codecs=vp9'] the video format
|
* @param {string} [options.format='video/webm;codecs=vp9'] the video format
|
||||||
|
* @param {boolean} [options.showDialog=false] - whether or not to open a dialog box to inform the
|
||||||
|
* participant to wait for the camera to be initialised
|
||||||
|
* @param {string} [options.dialogMsg="Please wait a few moments while the camera initialises"] -
|
||||||
|
* default message informing the participant to wait for the camera to initialise
|
||||||
* @param {Clock} [options.clock= undefined] - an optional clock
|
* @param {Clock} [options.clock= undefined] - an optional clock
|
||||||
* @param {boolean} [options.autoLog= false] - whether or not to log
|
* @param {boolean} [options.autoLog= false] - whether or not to log
|
||||||
*
|
*
|
||||||
@ -34,7 +38,7 @@ export class Camera extends PsychObject
|
|||||||
* @constructor
|
* @constructor
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
constructor({win, name, format, clock, autoLog} = {})
|
constructor({win, name, format, showDialog, dialogMsg = "Please wait a few moments while the camera initialises", clock, autoLog} = {})
|
||||||
{
|
{
|
||||||
super(win._psychoJS);
|
super(win._psychoJS);
|
||||||
|
|
||||||
@ -45,8 +49,23 @@ export class Camera extends PsychObject
|
|||||||
this._addAttribute("autoLog", autoLog, false);
|
this._addAttribute("autoLog", autoLog, false);
|
||||||
this._addAttribute("status", PsychoJS.Status.NOT_STARTED);
|
this._addAttribute("status", PsychoJS.Status.NOT_STARTED);
|
||||||
|
|
||||||
|
// open pop-up dialog:
|
||||||
|
if (showDialog)
|
||||||
|
{
|
||||||
|
this.psychoJS.gui.dialog({
|
||||||
|
warning: dialogMsg,
|
||||||
|
showOK: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// prepare the recording:
|
// prepare the recording:
|
||||||
this._prepareRecording();
|
this._prepareRecording().then( () =>
|
||||||
|
{
|
||||||
|
if (showDialog)
|
||||||
|
{
|
||||||
|
this.psychoJS.gui.closeDialog();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (this._autoLog)
|
if (this._autoLog)
|
||||||
{
|
{
|
||||||
@ -54,6 +73,19 @@ export class Camera extends PsychObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query whether or not the camera is ready to record.
|
||||||
|
*
|
||||||
|
* @name module:visual.Camera#isReady
|
||||||
|
* @function
|
||||||
|
* @public
|
||||||
|
* @returns {boolean} whether or not the camera is ready to record
|
||||||
|
*/
|
||||||
|
isReady()
|
||||||
|
{
|
||||||
|
return (this._recorder !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the underlying video stream.
|
* Get the underlying video stream.
|
||||||
@ -373,8 +405,10 @@ export class Camera extends PsychObject
|
|||||||
* @param {string} options.tag an optional tag for the video file
|
* @param {string} options.tag an optional tag for the video file
|
||||||
* @param {boolean} [options.waitForCompletion= false] whether or not to wait for completion
|
* @param {boolean} [options.waitForCompletion= false] whether or not to wait for completion
|
||||||
* before returning
|
* before returning
|
||||||
|
* @param {boolean} [options.showDialog=false] - whether or not to open a dialog box to inform the participant to wait for the data to be uploaded to the server
|
||||||
|
* @param {string} [options.dialogMsg=""] - default message informing the participant to wait for the data to be uploaded to the server
|
||||||
*/
|
*/
|
||||||
async upload({tag, waitForCompletion = false} = {})
|
async upload({tag, waitForCompletion = false, showDialog = false, dialogMsg = ""} = {})
|
||||||
{
|
{
|
||||||
// default tag: the name of this Camera object
|
// default tag: the name of this Camera object
|
||||||
if (typeof tag === "undefined")
|
if (typeof tag === "undefined")
|
||||||
@ -397,7 +431,12 @@ export class Camera extends PsychObject
|
|||||||
|
|
||||||
// upload the blob:
|
// upload the blob:
|
||||||
const videoBlob = new Blob(this._videoBuffer);
|
const videoBlob = new Blob(this._videoBuffer);
|
||||||
return this._psychoJS.serverManager.uploadAudioVideo(videoBlob, tag, waitForCompletion);
|
return this._psychoJS.serverManager.uploadAudioVideo({
|
||||||
|
mediaBlob: videoBlob,
|
||||||
|
tag,
|
||||||
|
waitForCompletion,
|
||||||
|
showDialog,
|
||||||
|
dialogMsg});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,6 +65,19 @@ export class FaceDetector extends VisualStim
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query whether or not the face detector is ready to detect.
|
||||||
|
*
|
||||||
|
* @name module:visual.FaceDetector#isReady
|
||||||
|
* @function
|
||||||
|
* @public
|
||||||
|
* @returns {boolean} whether or not the face detector is ready to detect
|
||||||
|
*/
|
||||||
|
isReady()
|
||||||
|
{
|
||||||
|
return this._modelsLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setter for the video attribute.
|
* Setter for the video attribute.
|
||||||
@ -207,7 +220,8 @@ export class FaceDetector extends VisualStim
|
|||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
async _initFaceApi()
|
async _initFaceApi()
|
||||||
{/*
|
{
|
||||||
|
/*
|
||||||
// load the library:
|
// load the library:
|
||||||
await this._psychoJS.serverManager.prepareResources([
|
await this._psychoJS.serverManager.prepareResources([
|
||||||
{
|
{
|
||||||
@ -215,13 +229,16 @@ export class FaceDetector extends VisualStim
|
|||||||
"path": this.faceApiUrl,
|
"path": this.faceApiUrl,
|
||||||
"download": true
|
"download": true
|
||||||
}
|
}
|
||||||
]);*/
|
]);
|
||||||
|
*/
|
||||||
|
|
||||||
// load the models:
|
// load the models:
|
||||||
faceapi.nets.tinyFaceDetector.loadFromUri(this._modelDir);
|
this._modelsLoaded = false;
|
||||||
faceapi.nets.faceLandmark68Net.loadFromUri(this._modelDir);
|
await faceapi.nets.tinyFaceDetector.loadFromUri(this._modelDir);
|
||||||
faceapi.nets.faceRecognitionNet.loadFromUri(this._modelDir);
|
await faceapi.nets.faceLandmark68Net.loadFromUri(this._modelDir);
|
||||||
faceapi.nets.faceExpressionNet.loadFromUri(this._modelDir);
|
await faceapi.nets.faceRecognitionNet.loadFromUri(this._modelDir);
|
||||||
|
await faceapi.nets.faceExpressionNet.loadFromUri(this._modelDir);
|
||||||
|
this._modelsLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -289,6 +289,19 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query whether or not the marker is currently being dragged.
|
||||||
|
*
|
||||||
|
* @name module:visual.Slider#isMarkerDragging
|
||||||
|
* @function
|
||||||
|
* @public
|
||||||
|
* @returns {boolean} whether or not the marker is being dragged
|
||||||
|
*/
|
||||||
|
isMarkerDragging()
|
||||||
|
{
|
||||||
|
return this._markerDragging;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current value of the rating.
|
* Get the current value of the rating.
|
||||||
*
|
*
|
||||||
@ -593,6 +606,9 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
|
|||||||
*/
|
*/
|
||||||
_sanitizeAttributes()
|
_sanitizeAttributes()
|
||||||
{
|
{
|
||||||
|
this._isSliderStyle = false;
|
||||||
|
this._frozenMarker = false;
|
||||||
|
|
||||||
// convert potential string styles into Symbols:
|
// convert potential string styles into Symbols:
|
||||||
this._style.forEach((style, index) =>
|
this._style.forEach((style, index) =>
|
||||||
{
|
{
|
||||||
@ -602,7 +618,51 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: only two ticks for SLIDER type, non-empty ticks, that RADIO is also categorical, etc.
|
// TODO: non-empty ticks, RADIO is also categorical, etc.
|
||||||
|
|
||||||
|
// SLIDER style: two ticks, first one is zero, second one is > 1
|
||||||
|
if (this._style.indexOf(Slider.Style.SLIDER) > -1)
|
||||||
|
{
|
||||||
|
this._isSliderStyle = true;
|
||||||
|
|
||||||
|
// more than 2 ticks: cut to two
|
||||||
|
if (this._ticks.length > 2)
|
||||||
|
{
|
||||||
|
this.psychoJS.logger.warn(`Slider "${this._name}" has style: SLIDER and more than two ticks. We cut the ticks to 2.`);
|
||||||
|
this._ticks = this._ticks.slice(0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// less than 2 ticks: error
|
||||||
|
if (this._ticks.length < 2)
|
||||||
|
{
|
||||||
|
throw {
|
||||||
|
origin: "Slider._sanitizeAttributes",
|
||||||
|
context: "when sanitizing the attributes of Slider: " + this._name,
|
||||||
|
error: "less than 2 ticks were given for a slider of type: SLIDER"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// first tick different from zero: change it to zero
|
||||||
|
if (this._ticks[0] !== 0)
|
||||||
|
{
|
||||||
|
this.psychoJS.logger.warn(`Slider "${this._name}" has style: SLIDER but the first tick is not 0. We changed it to 0.`);
|
||||||
|
this._ticks[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// second tick smaller than 1: change it to 1
|
||||||
|
if (this._ticks[1] < 1)
|
||||||
|
{
|
||||||
|
this.psychoJS.logger.warn(`Slider "${this._name}" has style: SLIDER but the second tick is less than 1. We changed it to 1.`);
|
||||||
|
this._ticks[1] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// second tick is 1: the marker is frozen
|
||||||
|
if (this._ticks[1] === 1)
|
||||||
|
{
|
||||||
|
this._frozenMarker = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// deal with categorical sliders:
|
// deal with categorical sliders:
|
||||||
this._isCategorical = (this._ticks.length === 0);
|
this._isCategorical = (this._ticks.length === 0);
|
||||||
@ -911,7 +971,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
|
|||||||
}
|
}
|
||||||
else if (this._markerType === Slider.Shape.BOX)
|
else if (this._markerType === Slider.Shape.BOX)
|
||||||
{
|
{
|
||||||
this._marker.lineStyle(1, this.getContrastedColor(this._markerColor, 0.5).int, 1, 0.5);
|
this._marker.lineStyle(1, this.getContrastedColor(this._markerColor, 0.5).int, 1, 0);
|
||||||
this._marker.beginFill(this._markerColor.int, 1);
|
this._marker.beginFill(this._markerColor.int, 1);
|
||||||
this._marker.drawRect(
|
this._marker.drawRect(
|
||||||
Math.round(-this._markerSize_px[0] / 2),
|
Math.round(-this._markerSize_px[0] / 2),
|
||||||
@ -954,9 +1014,12 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
|
|||||||
{
|
{
|
||||||
self._markerDragging = false;
|
self._markerDragging = false;
|
||||||
|
|
||||||
const mouseLocalPos_px = event.data.getLocalPosition(self._pixi);
|
if (!this._frozenMarker)
|
||||||
const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]);
|
{
|
||||||
self.recordRating(rating);
|
const mouseLocalPos_px = event.data.getLocalPosition(self._pixi);
|
||||||
|
const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]);
|
||||||
|
self.recordRating(rating);
|
||||||
|
}
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
@ -967,12 +1030,15 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
|
|||||||
{
|
{
|
||||||
if (self._markerDragging)
|
if (self._markerDragging)
|
||||||
{
|
{
|
||||||
const mouseLocalPos_px = event.data.getLocalPosition(self._pixi);
|
|
||||||
const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]);
|
|
||||||
self.recordRating(rating);
|
|
||||||
|
|
||||||
self._markerDragging = false;
|
self._markerDragging = false;
|
||||||
|
|
||||||
|
if (!this._frozenMarker)
|
||||||
|
{
|
||||||
|
const mouseLocalPos_px = event.data.getLocalPosition(self._pixi);
|
||||||
|
const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]);
|
||||||
|
self.recordRating(rating);
|
||||||
|
}
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -982,9 +1048,12 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
|
|||||||
{
|
{
|
||||||
if (self._markerDragging)
|
if (self._markerDragging)
|
||||||
{
|
{
|
||||||
const mouseLocalPos_px = event.data.getLocalPosition(self._pixi);
|
if (!this._frozenMarker)
|
||||||
const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]);
|
{
|
||||||
self.setMarkerPos(rating);
|
const mouseLocalPos_px = event.data.getLocalPosition(self._pixi);
|
||||||
|
const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]);
|
||||||
|
self.setMarkerPos(rating);
|
||||||
|
}
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
@ -1015,12 +1084,37 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
|
|||||||
|
|
||||||
this._pixi.pointerup = this._pixi.mouseup = this._pixi.touchend = (event) =>
|
this._pixi.pointerup = this._pixi.mouseup = this._pixi.touchend = (event) =>
|
||||||
{
|
{
|
||||||
const mouseLocalPos_px = event.data.getLocalPosition(self._body);
|
if (!this._frozenMarker)
|
||||||
const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]);
|
{
|
||||||
self.recordRating(rating);
|
const mouseLocalPos_px = event.data.getLocalPosition(self._body);
|
||||||
|
const rating = self._posToRating([mouseLocalPos_px.x, mouseLocalPos_px.y]);
|
||||||
|
self.recordRating(rating);
|
||||||
|
}
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// mouse wheel over slider:
|
||||||
|
if (this._isSliderStyle)
|
||||||
|
{
|
||||||
|
self._pointerIsOver = false;
|
||||||
|
|
||||||
|
this._pixi.pointerover = this._pixi.mouseover = (event) =>
|
||||||
|
{
|
||||||
|
self._pointerIsOver = true;
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
this._pixi.pointerout = this._pixi.mouseout = (event) =>
|
||||||
|
{
|
||||||
|
self._pointerIsOver = false;
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*renderer.view.addEventListener("wheel", (event) =>
|
||||||
|
{
|
||||||
|
|
||||||
|
}*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1082,7 +1176,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
|
|||||||
/**
|
/**
|
||||||
* Setup the labels.
|
* Setup the labels.
|
||||||
*
|
*
|
||||||
* @name module:visual.Slider#_setupTicks
|
* @name module:visual.Slider#_setupLabels
|
||||||
* @function
|
* @function
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@ -1311,7 +1405,14 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
|
|||||||
{
|
{
|
||||||
if (this._style.indexOf(Slider.Style.SLIDER) > -1)
|
if (this._style.indexOf(Slider.Style.SLIDER) > -1)
|
||||||
{
|
{
|
||||||
return (pos_px[1] / (size_px[1] - markerSize_px[1]) + 0.5) * range + this._ticks[0];
|
if (size_px[1] === markerSize_px[1])
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (pos_px[1] / (size_px[1] - markerSize_px[1]) + 0.5) * range + this._ticks[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -151,19 +151,17 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
|
|||||||
false,
|
false,
|
||||||
onChange(true, true, true),
|
onChange(true, true, true),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 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):
|
||||||
@ -342,7 +340,7 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
|
|||||||
const anchor = this._getAnchor();
|
const anchor = this._getAnchor();
|
||||||
this._boundingBox = new PIXI.Rectangle(
|
this._boundingBox = new PIXI.Rectangle(
|
||||||
this._pos[0] - anchor[0] * textSize[0],
|
this._pos[0] - anchor[0] * textSize[0],
|
||||||
this._pos[1] - anchor[1] * textSize[1],
|
this._pos[1] - textSize[1] - anchor[1] * textSize[1],
|
||||||
textSize[0],
|
textSize[0],
|
||||||
textSize[1],
|
textSize[1],
|
||||||
);
|
);
|
||||||
@ -370,6 +368,28 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for the color attribute.
|
||||||
|
*
|
||||||
|
* @name module:visual.TextStim#setColor
|
||||||
|
* @public
|
||||||
|
* @param {undefined | null | number} color - the color
|
||||||
|
* @param {boolean} [log= false] - whether of not to log
|
||||||
|
*/
|
||||||
|
setColor(color, log = false)
|
||||||
|
{
|
||||||
|
const hasChanged = this._setAttribute("color", color, log);
|
||||||
|
|
||||||
|
if (hasChanged)
|
||||||
|
{
|
||||||
|
if (typeof this._pixi !== "undefined")
|
||||||
|
{
|
||||||
|
this._pixi.style = this._getTextStyle();
|
||||||
|
this._needUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the stimulus, if necessary.
|
* Update the stimulus, if necessary.
|
||||||
*
|
*
|
||||||
@ -395,7 +415,8 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
|
|||||||
this._pixi.destroy(true);
|
this._pixi.destroy(true);
|
||||||
}
|
}
|
||||||
this._pixi = new PIXI.Text(this._text, this._getTextStyle());
|
this._pixi = new PIXI.Text(this._text, this._getTextStyle());
|
||||||
this._pixi.updateText();
|
// TODO is updateText necessary?
|
||||||
|
// this._pixi.updateText();
|
||||||
}
|
}
|
||||||
|
|
||||||
const anchor = this._getAnchor();
|
const anchor = this._getAnchor();
|
||||||
@ -413,16 +434,18 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
|
|||||||
// apply the clip mask:
|
// apply the clip mask:
|
||||||
this._pixi.mask = this._clipMask;
|
this._pixi.mask = this._clipMask;
|
||||||
|
|
||||||
// update the size attributes:
|
// update the size attribute:
|
||||||
this._size = [
|
this._size = util.to_unit(
|
||||||
this._getLengthUnits(Math.abs(this._pixi.width)),
|
[Math.abs(this._pixi.width), Math.abs(this._pixi.height)],
|
||||||
this._getLengthUnits(Math.abs(this._pixi.height)),
|
"pix",
|
||||||
];
|
this._win,
|
||||||
|
this._units
|
||||||
|
);
|
||||||
|
|
||||||
// refine the estimate of the bounding box:
|
// refine the estimate of the bounding box:
|
||||||
this._boundingBox = new PIXI.Rectangle(
|
this._boundingBox = new PIXI.Rectangle(
|
||||||
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] - this._size[1] - anchor[1] * this._size[1],
|
||||||
this._size[0],
|
this._size[0],
|
||||||
this._size[1],
|
this._size[1],
|
||||||
);
|
);
|
||||||
|
@ -258,14 +258,17 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
*
|
*
|
||||||
* @name module:visual.VisualStim#_onChange
|
* @name module:visual.VisualStim#_onChange
|
||||||
* @function
|
* @function
|
||||||
* @param {boolean} [withPixi = false] - whether or not the PIXI representation must also be updated
|
|
||||||
* @param {boolean} [withBoundingBox = false] - whether or not to immediately estimate the bounding box
|
|
||||||
* @return {Function}
|
|
||||||
* @protected
|
* @protected
|
||||||
|
* @param {boolean} [withPixi = false] - whether or not the PIXI representation must
|
||||||
|
* also be updated
|
||||||
|
* @param {boolean} [withBoundingBox = false] - whether or not to immediately estimate
|
||||||
|
* the bounding box
|
||||||
|
* @return {Function}
|
||||||
*/
|
*/
|
||||||
_onChange(withPixi = false, withBoundingBox = false)
|
_onChange(withPixi = false, withBoundingBox = false)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user