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

anchor implementation moved to visualstim to create common approach; Different approach to set anchor implemented in specific stimuli that require it;

This commit is contained in:
lgtst 2022-07-07 21:36:17 +03:00
parent fa731254bc
commit 98386ed83f
9 changed files with 143 additions and 120 deletions

View File

@ -721,7 +721,6 @@ export class GratingStim extends VisualStim
};
}
this._pixi = this._getPixiMeshFromPredefinedShaders(shaderName, Object.assign(shaderUniforms, currentUniforms));
this._pixi.pivot.set(this._pixi.width * 0.5, this._pixi.width * 0.5);
this._pixi.filters = [this._adjustmentFilter];
// add a mask if need be:
@ -765,16 +764,14 @@ export class GratingStim extends VisualStim
this._pixi.zIndex = -this._depth;
this.opacity = this._opacity;
this.anchor = this._anchor;
// set the scale:
const displaySize = this._getDisplaySize();
this._size_px = util.to_px(displaySize, this.units, this.win);
const scaleX = this._size_px[0] / this._pixi.width;
const scaleY = this._size_px[1] / this._pixi.height;
this._pixi.scale.x = this.flipHoriz ? -scaleX : scaleX;
this._pixi.scale.y = this.flipVert ? scaleY : -scaleY;
this._pixi.scale.x = 1;
this._pixi.scale.y = -1;
// set the position, rotation, and anchor (image centered on pos):
let pos = to_pixiPoint(this.pos, this.units, this.win);
this._pixi.position.set(pos.x, pos.y);
this._pixi.rotation = -this.ori * Math.PI / 180;

View File

@ -29,6 +29,7 @@ import {Camera} from "../hardware";
* @param {string | HTMLImageElement} options.mask - the name of the mask resource or HTMLImageElement corresponding to the mask
* @param {string} [options.units= "norm"] - the units of the stimulus (e.g. for size, position, vertices)
* @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the stimulus
* @param {string} [options.anchor = "center"] - sets the origin point of the stim
* @param {string} [options.units= 'norm'] - the units of the stimulus vertices, size and position
* @param {number} [options.ori= 0.0] - the orientation (in degrees)
* @param {number} [options.size] - the size of the rendered image (the size of the image will be used if size is not specified)
@ -45,9 +46,9 @@ import {Camera} from "../hardware";
*/
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, anchor, 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, anchor, size, autoDraw, autoLog });
this._addAttribute(
"image",
@ -358,14 +359,13 @@ export class ImageStim extends util.mix(VisualStim).with(ColorMixin)
const size_px = util.to_px(displaySize, this.units, this.win);
const scaleX = size_px[0] / this._texture.width;
const scaleY = size_px[1] / this._texture.height;
this.anchor = this._anchor;
this._pixi.scale.x = this.flipHoriz ? -scaleX : scaleX;
this._pixi.scale.y = this.flipVert ? scaleY : -scaleY;
// set the position, rotation, and anchor (image centered on pos):
this._pixi.position = to_pixiPoint(this.pos, this.units, this.win);
this._pixi.rotation = -this.ori * Math.PI / 180;
this._pixi.anchor.x = 0.5;
this._pixi.anchor.y = 0.5;
// re-estimate the bounding box, as the texture's width may now be available:
this._estimateBoundingBox();

View File

@ -30,6 +30,7 @@ import {Camera} from "../hardware/Camera.js";
* movie resource or of a HTMLVideoElement or of a Camera component
* @param {string} [options.units= "norm"] - the units of the stimulus (e.g. for size, position, vertices)
* @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the stimulus
* @param {string} [options.anchor = "center"] - sets the origin point of the stim
* @param {string} [options.units= 'norm'] - the units of the stimulus vertices, size and position
* @param {number} [options.ori= 0.0] - the orientation (in degrees)
* @param {number} [options.size] - the size of the rendered image (the size of the image will be used if size is not specified)
@ -50,9 +51,9 @@ import {Camera} from "../hardware/Camera.js";
*/
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, anchor, 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, anchor, size, autoDraw, autoLog });
this.psychoJS.logger.debug("create a new MovieStim with name: ", name);
@ -408,8 +409,7 @@ export class MovieStim extends VisualStim
// set the position, rotation, and anchor (movie centered on pos):
this._pixi.position = to_pixiPoint(this.pos, this.units, this.win);
this._pixi.rotation = -this.ori * Math.PI / 180;
this._pixi.anchor.x = 0.5;
this._pixi.anchor.y = 0.5;
this.anchor = this._anchor;
// re-estimate the bounding box, as the texture's width may now be available:
this._estimateBoundingBox();

View File

@ -26,6 +26,7 @@ import { ShapeStim } from "./ShapeStim.js";
* @param {number} [options.width= 0.5] - the width of the rectangle
* @param {number} [options.height= 0.5] - the height of the rectangle
* @param {Array.<number>} [options.pos= [0, 0]] - the position
* @param {string} [options.anchor = "center"] - sets the origin point of the stim
* @param {number} [options.size= 1.0] - the size
* @param {number} [options.ori= 0.0] - the orientation (in degrees)
* @param {string} [options.units= "height"] - the units of the stimulus vertices, size and position
@ -37,7 +38,7 @@ import { ShapeStim } from "./ShapeStim.js";
*/
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, anchor, size, ori, units, contrast, depth, interpolate, autoDraw, autoLog } = {})
{
super({
name,
@ -47,6 +48,7 @@ export class Rect extends ShapeStim
fillColor,
opacity,
pos,
anchor,
ori,
size,
units,

View File

@ -32,6 +32,7 @@ import { VisualStim } from "./VisualStim.js";
* @param {Array.<Array.<number>>} [options.vertices= [[-0.5, 0], [0, 0.5], [0.5, 0]]] - the shape vertices
* @param {boolean} [options.closeShape= true] - whether or not the shape is closed
* @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the shape
* @param {string} [options.anchor = "center"] - sets the origin point of the stim
* @param {number} [options.size= 1.0] - the size
* @param {number} [options.ori= 0.0] - the orientation (in degrees)
* @param {string} options.units - the units of the stimulus vertices, size and position
@ -43,9 +44,9 @@ import { VisualStim } from "./VisualStim.js";
*/
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, anchor, 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, anchor, depth, size, autoDraw, autoLog });
// the PIXI polygon corresponding to the vertices, in pixel units:
this._pixiPolygon_px = undefined;
@ -178,6 +179,28 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
return util.IsPointInsidePolygon(objectPos_px, polygon_px);
}
/**
* Setter for the anchor attribute.
*
* @name module:visual.ShapeStim#setAnchor
* @public
* @param {string} anchor - anchor of the stim
* @param {boolean} [log= false] - whether or not to log
*/
setAnchor (anchor = "center", log = false)
{
this._setAttribute("anchor", anchor, log);
if (this._pixi !== undefined)
{
// since vertices are passed directly, usually assuming origin at [0, 0]
// and already being centered around it, subtracting 0.5 from anchorNum vals
// to get a desired effect.
const anchorNum = this._anchorTextToNum(this._anchor);
this._pixi.pivot.x = (anchorNum[0] - 0.5) * this._pixi.scale.x * this._pixi.width;
this._pixi.pivot.y = (anchorNum[1] - 0.5) * -this._pixi.scale.y * this._pixi.height;
}
}
/**
* Estimate the bounding box.
*
@ -258,6 +281,7 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
}
this._pixi.zIndex = -this._depth;
this.anchor = this._anchor;
}
// set polygon position and rotation:
@ -295,7 +319,6 @@ export class ShapeStim extends util.mix(VisualStim).with(ColorMixin, WindowMixin
coords_px.push(coords_px[1]);
}
}
// destroy the previous PIXI polygon and create a new one:
this._pixiPolygon_px = new PIXI.Polygon(coords_px);
this._pixiPolygon_px.closeStroke = this._closeShape;

View File

@ -454,6 +454,26 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
}
}
/**
* Setter for the anchor attribute.
*
* @name module:visual.Slider#setAnchor
* @public
* @param {string} anchor - anchor of the stim
* @param {boolean} [log= false] - whether or not to log
*/
setAnchor (anchor = "center", log = false)
{
this._setAttribute("anchor", anchor, log);
if (this._pixi !== undefined)
{
// container has origin at [0, 0], subtracting 0.5 from anchorNum vals to get a desired effect.
const anchorNum = this._anchorTextToNum(this._anchor);
this._pixi.pivot.x = (anchorNum[0] - 0.5) * this._pixi.scale.x * this._pixi.width;
this._pixi.pivot.y = (anchorNum[1] - 0.5) * this._pixi.scale.y * this._pixi.height;
}
}
/** Let `borderColor` alias `lineColor` to parallel PsychoPy */
set borderColor(color)
{
@ -782,6 +802,7 @@ export class Slider extends util.mix(VisualStim).with(ColorMixin, WindowMixin)
this._pixi.alpha = this._opacity;
this._pixi.zIndex = -this._depth;
this.anchor = this._anchor;
// make sure that the dependent Stimuli are also updated:
for (const dependentStim of this._dependentStims)

View File

@ -37,7 +37,7 @@ import { VisualStim } from "./VisualStim.js";
* @param {number} [options.height= 0.1] - the height of the text
* @param {boolean} [options.bold= false] - whether or not the text is bold
* @param {boolean} [options.italic= false] - whether or not the text is italic
* @param {string} [options.anchor = 'left'] - horizontal alignment
* @param {string} [options.anchor = "center"] - sets the origin point of the stim
*
* @param {boolean} [options.multiline= false] - whether or not a multiline element is used
* @param {boolean} [options.autofocus= true] - whether or not the first input should receive focus by default
@ -89,7 +89,7 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
} = {},
)
{
super({ name, win, pos, size, units, ori, opacity, depth, clipMask, autoDraw, autoLog });
super({ name, win, pos, anchor, size, units, ori, opacity, depth, clipMask, autoDraw, autoLog });
this._addAttribute(
"text",
@ -102,11 +102,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
"",
this._onChange(true, true),
);
this._addAttribute(
"anchor",
anchor,
"center"
);
this._addAttribute(
"flipHoriz",
flipHoriz,
@ -274,24 +269,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
}
}
/**
* Setter for the anchor attribute.
*
* @name module:visual.TextBox#setAnchor
* @public
* @param {boolean} anchor - anchor of the textbox
* @param {boolean} [log= false] - whether or not to log
*/
setAnchor (anchor = "center", log = false)
{
this._setAttribute("anchor", anchor, log);
if (this._pixi !== undefined) {
const anchorUnits = this._getAnchor();
this._pixi.anchor.x = anchorUnits[0];
this._pixi.anchor.y = anchorUnits[1];
}
}
/**
* For tweaking the underlying input value.
*
@ -596,7 +573,7 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
const boxHeight = this._letterHeight + 2 * this._padding + 2 * this._borderWidth;
// take the alignment into account:
const anchor = this._getAnchor();
const anchor = this._anchorTextToNum(this._anchor);
this._boundingBox = new PIXI.Rectangle(
this._pos[0] - anchor[0] * this._size[0],
this._pos[1] - anchor[1] * boxHeight,
@ -689,38 +666,6 @@ export class TextBox extends util.mix(VisualStim).with(ColorMixin)
// apply the clip mask:
this._pixi.mask = this._clipMask;
}
/**
* Convert the anchor attribute into numerical values.
*
* @name module:visual.TextBox#_getAnchor
* @function
* @protected
* @return {number[]} - the anchor, as an array of numbers in [0,1]
*/
_getAnchor()
{
const anchor = [0.5, 0.5];
if (this._anchor.indexOf("left") > -1)
{
anchor[0] = 0;
}
else if (this._anchor.indexOf("right") > -1)
{
anchor[0] = 1;
}
if (this._anchor.indexOf("top") > -1)
{
anchor[1] = 0;
}
else if (this._anchor.indexOf("bottom") > -1)
{
anchor[1] = 1;
}
return anchor;
}
}
/**

View File

@ -25,6 +25,7 @@ import { VisualStim } from "./VisualStim.js";
* @param {string} [options.text="Hello World"] - the text to be rendered
* @param {string} [options.font= "Arial"] - the font family
* @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the text
* @param {string} [options.anchor = "center"] - sets the origin point of the stim
* @param {Color} [options.color= 'white'] the background color
* @param {number} [options.opacity= 1.0] - the opacity
* @param {number} [options.depth= 0] - the depth (i.e. the z order)
@ -54,6 +55,7 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
text,
font,
pos,
anchor,
color,
opacity,
depth,
@ -74,7 +76,7 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
} = {},
)
{
super({ name, win, units, ori, opacity, depth, pos, clipMask, autoDraw, autoLog });
super({ name, win, units, ori, opacity, depth, pos, anchor, clipMask, autoDraw, autoLog });
// callback to deal with text metrics invalidation:
const onChange = (withPixi = false, withBoundingBox = false, withMetrics = false) =>
@ -164,6 +166,13 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
this._onChange(true, false)
);
// alignHoriz and alignVert should be deprecated and replaced by anchor, but leaving this here
// for compatibility for a while.
if (typeof alignVert === "string" && typeof alignHoriz === "string")
{
this.anchor = `${alignVert}-${alignHoriz}`;
}
// estimate the bounding box (using TextMetrics):
this._estimateBoundingBox();
@ -337,7 +346,7 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
); console.log(this._name, '>>', textSize);
// take the alignment into account:
const anchor = this._getAnchor();
const anchor = this._anchorTextToNum(this._anchor);
this._boundingBox = new PIXI.Rectangle(
this._pos[0] - anchor[0] * textSize[0],
this._pos[1] - textSize[1] + anchor[1] * textSize[1],
@ -419,7 +428,7 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
// this._pixi.updateText();
}
const anchor = this._getAnchor();
const anchor = this._anchorTextToNum(this._anchor);
[this._pixi.anchor.x, this._pixi.anchor.y] = anchor;
this._pixi.scale.x = this._flipHoriz ? -1 : 1;
@ -450,46 +459,6 @@ export class TextStim extends util.mix(VisualStim).with(ColorMixin)
this._size[1],
);
}
/**
* Convert the alignment attributes into an anchor.
*
* @name module:visual.TextStim#_getAnchor
* @function
* @private
* @return {number[]} - the anchor
*/
_getAnchor()
{
let anchor = [];
switch (this._alignHoriz)
{
case "left":
anchor.push(0);
break;
case "right":
anchor.push(1);
break;
default:
case "center":
anchor.push(0.5);
}
switch (this._alignVert)
{
case "top":
anchor.push(0);
break;
case "bottom":
anchor.push(1);
break;
default:
case "center":
anchor.push(0.5);
}
return anchor;
}
}
/**

View File

@ -27,6 +27,7 @@ import * as util from "../util/Util.js";
* @param {number} [options.opacity= 1.0] - the opacity
* @param {number} [options.depth= 0] - the depth (i.e. the z order)
* @param {Array.<number>} [options.pos= [0, 0]] - the position of the center of the stimulus
* @param {string} [options.anchor = "center"] - sets the origin point of the stim
* @param {number} [options.size= 1.0] - the size
* @param {PIXI.Graphics} [options.clipMask= null] - the clip mask
* @param {boolean} [options.autoDraw= false] - whether or not the stimulus should be automatically drawn on every frame flip
@ -34,7 +35,7 @@ import * as util from "../util/Util.js";
*/
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, anchor, size, clipMask, autoDraw, autoLog } = {})
{
super({ win, name, autoDraw, autoLog });
@ -49,6 +50,11 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
pos,
[0, 0],
);
this._addAttribute(
"anchor",
anchor,
"center",
);
this._addAttribute(
"size",
size,
@ -223,6 +229,66 @@ export class VisualStim extends util.mix(MinimalStim).with(WindowMixin)
return this._getBoundingBox_px().contains(objectPos_px[0], objectPos_px[1]);
}
/**
* Setter for the anchor attribute.
*
* @name module:visual.VisualStim#setAnchor
* @public
* @param {string} anchor - anchor of the stim
* @param {boolean} [log= false] - whether or not to log
*/
setAnchor (anchor = "center", log = false)
{
this._setAttribute("anchor", anchor, log);
if (this._pixi !== undefined)
{
const anchorNum = this._anchorTextToNum(this._anchor);
if (this._pixi.anchor !== undefined)
{
this._pixi.anchor.x = anchorNum[0];
this._pixi.anchor.y = anchorNum[1];
}
else
{
this._pixi.pivot.x = anchorNum[0] * this._pixi.scale.x * this._pixi.width;
this._pixi.pivot.y = anchorNum[1] * this._pixi.scale.y * this._pixi.height;
}
}
}
/**
* Convert the anchor attribute into numerical values.
*
* @name module:visual.VisualStim#_anchorTextToNum
* @function
* @protected
* @param {string} anchorText - text version of anchor value ["top-left", "top-right", "center", ...]
* @return {number[]} - the anchor, as an array of numbers in [0,1]
*/
_anchorTextToNum(anchorText = "")
{
const anchor = [0.5, 0.5];
if (anchorText.indexOf("left") > -1)
{
anchor[0] = 0.0;
}
else if (anchorText.indexOf("right") > -1)
{
anchor[0] = 1.0;
}
if (anchorText.indexOf("top") > -1)
{
anchor[1] = 0.0;
}
else if (anchorText.indexOf("bottom") > -1)
{
anchor[1] = 1.0;
}
return anchor;
}
/**
* Estimate the bounding box.
*