初始化

This commit is contained in:
SmallMain
2022-06-25 00:23:03 +08:00
commit ef0589e8e5
2264 changed files with 617829 additions and 0 deletions

View File

@@ -0,0 +1,715 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const AnimationAnimator = require('../../animation/animation-animator');
const AnimationClip = require('../../animation/animation-clip');
const EventTarget = require('../event/event-target');
const js = require('../platform/js');
let equalClips = CC_EDITOR ? function (clip1, clip2) {
return clip1 === clip2 || (clip1 && clip2 && (clip1.name === clip2.name || clip1._uuid === clip2._uuid));
} : function (clip1, clip2) {
return clip1 === clip2;
};
/**
* !#en The event type supported by Animation
* !#zh Animation 支持的事件类型
* @class Animation.EventType
* @static
* @namespace Animationd
*/
let EventType = cc.Enum({
/**
* !#en Emit when begin playing animation
* !#zh 开始播放时触发
* @property {String} PLAY
* @static
*/
PLAY: 'play',
/**
* !#en Emit when stop playing animation
* !#zh 停止播放时触发
* @property {String} STOP
* @static
*/
STOP: 'stop',
/**
* !#en Emit when pause animation
* !#zh 暂停播放时触发
* @property {String} PAUSE
* @static
*/
PAUSE: 'pause',
/**
* !#en Emit when resume animation
* !#zh 恢复播放时触发
* @property {String} RESUME
* @static
*/
RESUME: 'resume',
/**
* !#en If animation repeat count is larger than 1, emit when animation play to the last frame
* !#zh 假如动画循环次数大于 1当动画播放到最后一帧时触发
* @property {String} LASTFRAME
* @static
*/
LASTFRAME: 'lastframe',
/**
* !#en Emit when finish playing animation
* !#zh 动画播放完成时触发
* @property {String} FINISHED
* @static
*/
FINISHED: 'finished'
});
/**
* !#en The animation component is used to play back animations.
*
* Animation provide several events to register
* - play : Emit when begin playing animation
* - stop : Emit when stop playing animation
* - pause : Emit when pause animation
* - resume : Emit when resume animation
* - lastframe : If animation repeat count is larger than 1, emit when animation play to the last frame
* - finished : Emit when finish playing animation
*
* !#zh Animation 组件用于播放动画。
*
* Animation 提供了一系列可注册的事件:
* - play : 开始播放时
* - stop : 停止播放时
* - pause : 暂停播放时
* - resume : 恢复播放时
* - lastframe : 假如动画循环次数大于 1当动画播放到最后一帧时
* - finished : 动画播放完成时
*
* @class Animation
* @extends Component
* @uses EventTarget
*/
let Animation = cc.Class({
name: 'cc.Animation',
extends: require('./CCComponent'),
mixins: [EventTarget],
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.others/Animation',
help: 'i18n:COMPONENT.help_url.animation',
executeInEditMode: true,
},
statics: {
EventType
},
ctor: function () {
cc.EventTarget.call(this);
// The actual implement for Animation
this._animator = null;
this._nameToState = js.createMap(true);
this._didInit = false;
this._currentClip = null;
},
properties: {
_defaultClip: {
default: null,
type: AnimationClip,
},
/**
* !#en Animation will play the default clip when start game.
* !#zh 在勾选自动播放或调用 play() 时默认播放的动画剪辑。
* @property defaultClip
* @type {AnimationClip}
*/
defaultClip: {
type: AnimationClip,
get: function () {
return this._defaultClip;
},
set: function (value) {
if (!CC_EDITOR || (cc.engine && cc.engine.isPlaying)) {
return;
}
this._defaultClip = value;
if (!value) {
return;
}
const contain = this._clips.findIndex((clip) => equalClips(clip, value)) >= 0;
if (!contain) {
this.addClip(value);
}
},
tooltip: CC_DEV && 'i18n:COMPONENT.animation.default_clip'
},
/**
* !#en Current played clip.
* !#zh 当前播放的动画剪辑。
* @property currentClip
* @type {AnimationClip}
*/
currentClip: {
get: function () {
return this._currentClip;
},
set: function (value) {
this._currentClip = value;
},
type: AnimationClip,
visible: false
},
// This property is used to watch clip changes in editor.
// Don't use in your game, use addClip/removeClip instead.
_writableClips: {
get () {
return this._clips;
},
set (val) {
this._didInit = false;
this._clips = val;
this._init();
},
type: [AnimationClip],
},
/**
* !#en All the clips used in this animation.
* !#zh 通过脚本可以访问并播放的 AnimationClip 列表。
* @property _clips
* @type {AnimationClip[]}
* @private
*/
_clips: {
default: [],
type: [AnimationClip],
tooltip: CC_DEV && 'i18n:COMPONENT.animation.clips',
visible: true
},
/**
* !#en Whether the animation should auto play the default clip when start game.
* !#zh 是否在运行游戏后自动播放默认动画剪辑。
* @property playOnLoad
* @type {Boolean}
* @default true
*/
playOnLoad: {
default: false,
tooltip: CC_DEV && 'i18n:COMPONENT.animation.play_on_load'
}
},
start: function () {
if (!CC_EDITOR && this.playOnLoad && this._defaultClip) {
let isPlaying = this._animator && this._animator.isPlaying;
if (!isPlaying) {
let state = this.getAnimationState(this._defaultClip.name);
this._animator.playState(state);
}
}
},
onEnable: function () {
if (this._animator) {
this._animator.resume();
}
},
onDisable: function () {
if (this._animator) {
this._animator.pause();
}
},
onDestroy: function () {
this.stop();
},
///////////////////////////////////////////////////////////////////////////////
// Public Methods
///////////////////////////////////////////////////////////////////////////////
/**
* !#en Get all the clips used in this animation.
* !#zh 获取动画组件上的所有动画剪辑。
* @method getClips
* @return {AnimationClip[]}
*/
getClips: function () {
return this._clips;
},
/**
* !#en Plays an animation and stop other animations.
* !#zh 播放指定的动画,并且停止当前正在播放动画。如果没有指定动画,则播放默认动画。
* @method play
* @param {String} [name] - The name of animation to play. If no name is supplied then the default animation will be played.
* @param {Number} [startTime] - play an animation from startTime
* @return {AnimationState} - The AnimationState of playing animation. In cases where the animation can't be played (ie, there is no default animation or no animation with the specified name), the function will return null.
* @example
* var animCtrl = this.node.getComponent(cc.Animation);
* animCtrl.play("linear");
*/
play: function (name, startTime) {
let state = this.playAdditive(name, startTime);
this._animator.stopStatesExcept(state);
return state;
},
/**
* !#en
* Plays an additive animation, it will not stop other animations.
* If there are other animations playing, then will play several animations at the same time.
* !#zh 播放指定的动画(将不会停止当前播放的动画)。如果没有指定动画,则播放默认动画。
* @method playAdditive
* @param {String} [name] - The name of animation to play. If no name is supplied then the default animation will be played.
* @param {Number} [startTime] - play an animation from startTime
* @return {AnimationState} - The AnimationState of playing animation. In cases where the animation can't be played (ie, there is no default animation or no animation with the specified name), the function will return null.
* @example
* // linear_1 and linear_2 at the same time playing.
* var animCtrl = this.node.getComponent(cc.Animation);
* animCtrl.playAdditive("linear_1");
* animCtrl.playAdditive("linear_2");
*/
playAdditive: function (name, startTime) {
this._init();
let state = this.getAnimationState(name || (this._defaultClip && this._defaultClip.name));
if (state) {
this.enabled = true;
let animator = this._animator;
if (animator.isPlaying && state.isPlaying) {
if (state.isPaused) {
animator.resumeState(state);
}
else {
animator.stopState(state);
animator.playState(state, startTime);
}
}
else {
animator.playState(state, startTime);
}
// Animation cannot be played when the component is not enabledInHierarchy.
// That would cause an error for the animation lost the reference after destroying the node.
// If users play the animation when the component is not enabledInHierarchy,
// we pause the animator here so that it will automatically resume the animation when users enable the component.
if (!this.enabledInHierarchy) {
animator.pause();
}
this.currentClip = state.clip;
}
return state;
},
/**
* !#en Stops an animation named name. If no name is supplied then stops all playing animations that were started with this Animation. <br/>
* Stopping an animation also Rewinds it to the Start.
* !#zh 停止指定的动画。如果没有指定名字,则停止当前正在播放的动画。
* @method stop
* @param {String} [name] - The animation to stop, if not supplied then stops all playing animations.
*/
stop: function (name) {
if (!this._didInit) {
return;
}
if (name) {
let state = this._nameToState[name];
if (state) {
this._animator.stopState(state);
}
}
else {
this._animator.stop();
}
},
/**
* !#en Pauses an animation named name. If no name is supplied then pauses all playing animations that were started with this Animation.
* !#zh 暂停当前或者指定的动画。如果没有指定名字,则暂停当前正在播放的动画。
* @method pause
* @param {String} [name] - The animation to pauses, if not supplied then pauses all playing animations.
*/
pause: function (name) {
if (!this._didInit) {
return;
}
if (name) {
let state = this._nameToState[name];
if (state) {
this._animator.pauseState(state);
}
}
else {
this.enabled = false;
}
},
/**
* !#en Resumes an animation named name. If no name is supplied then resumes all paused animations that were started with this Animation.
* !#zh 重新播放指定的动画,如果没有指定名字,则重新播放当前正在播放的动画。
* @method resume
* @param {String} [name] - The animation to resumes, if not supplied then resumes all paused animations.
*/
resume: function (name) {
if (!this._didInit) {
return;
}
if (name) {
let state = this.getAnimationState(name);
if (state) {
this._animator.resumeState(state);
}
}
else {
this.enabled = true;
}
},
/**
* !#en Make an animation named name go to the specified time. If no name is supplied then make all animations go to the specified time.
* !#zh 设置指定动画的播放时间。如果没有指定名字,则设置当前播放动画的播放时间。
* @method setCurrentTime
* @param {Number} [time] - The time to go to
* @param {String} [name] - Specified animation name, if not supplied then make all animations go to the time.
*/
setCurrentTime: function (time, name) {
this._init();
if (name) {
let state = this.getAnimationState(name);
if (state) {
this._animator.setStateTime(state, time);
}
}
else {
this._animator.setStateTime(time);
}
},
/**
* !#en Returns the animation state named name. If no animation with the specified name, the function will return null.
* !#zh 获取当前或者指定的动画状态,如果未找到指定动画剪辑则返回 null。
* @method getAnimationState
* @param {String} name
* @return {AnimationState}
*/
getAnimationState: function (name) {
this._init();
let state = this._nameToState[name];
if (CC_EDITOR && (!state || !cc.js.array.contains(this._clips, state.clip))) {
this._didInit = false;
if (this._animator) {
this._animator.stop();
}
this._init();
state = this._nameToState[name];
}
if (state && !state.curveLoaded) {
this._animator._reloadClip(state);
}
return state || null;
},
/**
* !#en Check whether the animation State with the name already exists.
* !#zh 通过名称判断是否包含某动画状态。也可用来判断是否已经添加了同名 clip.
* @method hasAnimationState
* @param {String} name
* @return {boolean} - Whether the animation State with the name already exists.
*/
hasAnimationState: function (name) {
this._init();
return !!(this._nameToState[name]);
},
/**
* !#en Adds a clip to the animation with name newName. If a clip with that name already exists it will be replaced with the new clip.
* !#zh 添加动画剪辑,并且可以重新设置该动画剪辑的名称。
* @method addClip
* @param {AnimationClip} clip - the clip to add
* @param {String} [newName]
* @return {AnimationState} - The AnimationState which gives full control over the animation clip.
*/
addClip: function (clip, newName) {
if (!clip) {
cc.warnID(3900);
return;
}
this._init();
// add clip
if (!cc.js.array.contains(this._clips, clip)) {
this._clips.push(clip);
}
// replace same name clip
newName = newName || clip.name;
let oldState = this._nameToState[newName];
if (oldState) {
if (oldState.clip === clip) {
return oldState;
}
else {
var index = this._clips.indexOf(oldState.clip);
if (index !== -1) {
this._clips.splice(index, 1);
}
}
}
// replace state
let newState = new cc.AnimationState(clip, newName);
this._nameToState[newName] = newState;
return newState;
},
/**
* !#en
* Remove clip from the animation list. This will remove the clip and any animation states based on it.
* If there are animation states depand on the clip are playing or clip is defaultClip, it will not delete the clip.
* But if force is true, then will always remove the clip and any animation states based on it. If clip is defaultClip, defaultClip will be reset to null
* !#zh
* 从动画列表中移除指定的动画剪辑,<br/>
* 如果依赖于 clip 的 AnimationState 正在播放或者 clip 是 defaultClip 的话,默认是不会删除 clip 的。
* 但是如果 force 参数为 true则会强制停止该动画然后移除该动画剪辑和相关的动画。这时候如果 clip 是 defaultClipdefaultClip 将会被重置为 null。
* @method removeClip
* @param {AnimationClip} clip
* @param {Boolean} [force=false] - If force is true, then will always remove the clip and any animation states based on it.
*/
removeClip: function (clip, force) {
if (!clip) {
cc.warnID(3901);
return;
}
this._init();
let state;
for (let name in this._nameToState) {
state = this._nameToState[name];
if (equalClips(state.clip, clip)) {
break;
}
}
if (clip === this._defaultClip) {
if (force) this._defaultClip = null;
else {
if (!CC_TEST) cc.warnID(3902);
return;
}
}
if (state && state.isPlaying) {
if (force) this.stop(state.name);
else {
if (!CC_TEST) cc.warnID(3903);
return;
}
}
this._clips = this._clips.filter(function (item) {
return !equalClips(item, clip);
});
if (state) {
delete this._nameToState[state.name];
}
},
/**
* !#en
* Samples animations at the current state.<br/>
* This is useful when you explicitly want to set up some animation state, and sample it once.
* !#zh 对指定或当前动画进行采样。你可以手动将动画设置到某一个状态,然后采样一次。
* @method sample
* @param {String} name
*/
sample: function (name) {
this._init();
if (name) {
let state = this.getAnimationState(name);
if (state) {
state.sample();
}
}
else {
this._animator.sample();
}
},
/**
* !#en
* Register animation event callback.
* The event arguments will provide the AnimationState which emit the event.
* When play an animation, will auto register the event callback to the AnimationState, and unregister the event callback from the AnimationState when animation stopped.
* !#zh
* 注册动画事件回调。
* 回调的事件里将会附上发送事件的 AnimationState。
* 当播放一个动画时,会自动将事件注册到对应的 AnimationState 上,停止播放时会将事件从这个 AnimationState 上取消注册。
* @method on
* @param {String} type - A string representing the event type to listen for.
* @param {Function} callback - The callback that will be invoked when the event is dispatched.
* The callback is ignored if it is a duplicate (the callbacks are unique).
* @param {cc.AnimationState} state
* @param {Object} [target] - The target (this object) to invoke the callback, can be null
* @param {Boolean} [useCapture=false] - When set to true, the capture argument prevents callback
* from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE.
* When false, callback will NOT be invoked when event's eventPhase attribute value is CAPTURING_PHASE.
* Either way, callback will be invoked when event's eventPhase attribute value is AT_TARGET.
*
* @return {Function} - Just returns the incoming callback so you can save the anonymous function easier.
* @typescript
* on(type: string, callback: (event: Event.EventCustom) => void, target?: any, useCapture?: boolean): (event: Event.EventCustom) => void
* on<T>(type: string, callback: (event: T) => void, target?: any, useCapture?: boolean): (event: T) => void
* on(type: string, callback: (type: string, state: cc.AnimationState) => void, target?: any, useCapture?: boolean): (type: string, state: cc.AnimationState) => void
* @example
* onPlay: function (type, state) {
* // callback
* }
*
* // register event to all animation
* animation.on('play', this.onPlay, this);
*/
on: function (type, callback, target, useCapture) {
this._init();
let ret = this._EventTargetOn(type, callback, target, useCapture);
if (type === 'lastframe') {
let states = this._nameToState;
for (let name in states) {
states[name]._lastframeEventOn = true;
}
}
return ret;
},
/**
* !#en
* Unregister animation event callback.
* !#zh
* 取消注册动画事件回调。
* @method off
* @param {String} type - A string representing the event type being removed.
* @param {Function} [callback] - The callback to remove.
* @param {Object} [target] - The target (this object) to invoke the callback, if it's not given, only callback without target will be removed
* @param {Boolean} [useCapture=false] - Specifies whether the callback being removed was registered as a capturing callback or not.
* If not specified, useCapture defaults to false. If a callback was registered twice,
* one with capture and one without, each must be removed separately. Removal of a capturing callback
* does not affect a non-capturing version of the same listener, and vice versa.
*
* @example
* // unregister event to all animation
* animation.off('play', this.onPlay, this);
*/
off: function (type, callback, target, useCapture) {
this._init();
if (type === 'lastframe') {
let states = this._nameToState;
for (let name in states) {
states[name]._lastframeEventOn = false;
}
}
this._EventTargetOff(type, callback, target, useCapture);
},
///////////////////////////////////////////////////////////////////////////////
// Internal Methods
///////////////////////////////////////////////////////////////////////////////
// Dont forget to call _init before every actual process in public methods.
// Just invoking _init by onLoad is not enough because onLoad is called only if the entity is active.
_init: function () {
if (this._didInit) {
return;
}
this._didInit = true;
this._animator = new AnimationAnimator(this.node, this);
this._createStates();
},
_createStates: function() {
this._nameToState = js.createMap(true);
// create animation states
let state = null;
let defaultClipState = false;
for (let i = 0; i < this._clips.length; ++i) {
let clip = this._clips[i];
if (clip) {
state = new cc.AnimationState(clip);
if (CC_EDITOR) {
this._animator._reloadClip(state);
}
this._nameToState[state.name] = state;
if (equalClips(this._defaultClip, clip)) {
defaultClipState = state;
}
}
}
if (this._defaultClip && !defaultClipState) {
state = new cc.AnimationState(this._defaultClip);
if (CC_EDITOR) {
this._animator._reloadClip(state);
}
this._nameToState[state.name] = state;
}
}
});
Animation.prototype._EventTargetOn = EventTarget.prototype.on;
Animation.prototype._EventTargetOff = EventTarget.prototype.off;
cc.Animation = module.exports = Animation;

View File

@@ -0,0 +1,333 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const misc = require('../utils/misc');
const Component = require('./CCComponent');
const AudioClip = require('../assets/CCAudioClip');
/**
* !#en Audio Source.
* !#zh 音频源组件,能对音频剪辑。
* @class AudioSource
* @extends Component
*/
var AudioSource = cc.Class({
name: 'cc.AudioSource',
extends: Component,
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.others/AudioSource',
help: 'i18n:COMPONENT.help_url.audiosource',
},
ctor: function () {
// We can't require Audio here because jsb Audio is implemented outside the engine,
// it can only take effect rely on overwriting cc._Audio
this.audio = new cc._Audio();
},
properties: {
_clip: {
default: null,
type: AudioClip
},
_volume: 1,
_mute: false,
_loop: false,
_pausedFlag: {
default: false,
serializable: false
},
_firstlyEnabled: true,
/**
* !#en
* Is the audio source playing (Read Only). <br/>
* Note: isPlaying is not supported for Native platforms.
* !#zh
* 该音频剪辑是否正播放(只读)。<br/>
* 注意Native 平台暂时不支持 isPlaying。
* @property isPlaying
* @type {Boolean}
* @readOnly
* @default false
*/
isPlaying: {
get: function () {
var state = this.audio.getState();
return state === cc._Audio.State.PLAYING;
},
visible: false
},
/**
* !#en The clip of the audio source to play.
* !#zh 要播放的音频剪辑。
* @property clip
* @type {AudioClip}
* @default 1
*/
clip: {
get: function () {
return this._clip;
},
set: function (value) {
if (value === this._clip) {
return;
}
if (!(value instanceof AudioClip)) {
return cc.error('Wrong type of AudioClip.');
}
this._clip = value;
this.audio.stop();
this.audio.src = this._clip;
if (this.preload) {
this._clip._ensureLoaded();
}
},
type: AudioClip,
tooltip: CC_DEV && 'i18n:COMPONENT.audio.clip',
animatable: false
},
/**
* !#en The volume of the audio source.
* !#zh 音频源的音量0.0 ~ 1.0)。
* @property volume
* @type {Number}
* @default 1
*/
volume: {
get: function () {
return this._volume;
},
set: function (value) {
value = misc.clamp01(value);
this._volume = value;
if (!this._mute) {
this.audio.setVolume(value);
}
return value;
},
tooltip: CC_DEV && 'i18n:COMPONENT.audio.volume'
},
/**
* !#en Is the audio source mute?
* !#zh 是否静音音频源。Mute 是设置音量为 0取消静音是恢复原来的音量。
* @property mute
* @type {Boolean}
* @default false
*/
mute: {
get: function () {
return this._mute;
},
set: function (value) {
this._mute = value;
this.audio.setVolume(value ? 0 : this._volume);
return value;
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.audio.mute',
},
/**
* !#en Is the audio source looping?
* !#zh 音频源是否循环播放?
* @property loop
* @type {Boolean}
* @default false
*/
loop: {
get: function () {
return this._loop;
},
set: function (value) {
this._loop = value;
this.audio.setLoop(value);
return value;
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.audio.loop'
},
/**
* !#en If set to true, the audio source will automatically start playing on onEnable.
* !#zh 如果设置为 true音频源将在 onEnable 时自动播放。
* @property playOnLoad
* @type {Boolean}
* @default true
*/
playOnLoad: {
default: false,
tooltip: CC_DEV && 'i18n:COMPONENT.audio.play_on_load',
animatable: false
},
/**
* !#en If set to true and AudioClip is a deferred load resource, the component will preload AudioClip in the onLoad phase.
* !#zh 如果设置为 true 且 AudioClip 为延迟加载资源,组件将在 onLoad 阶段预加载 AudioClip。
* @property preload
* @type {Boolean}
* @default false
*/
preload: {
default: false,
animatable: false
}
},
_pausedCallback: function () {
var state = this.audio.getState();
if (state === cc._Audio.State.PLAYING) {
this.audio.pause();
this._pausedFlag = true;
}
},
_restoreCallback: function () {
if (this._pausedFlag) {
this.audio.resume();
}
this._pausedFlag = false;
},
onLoad: function () {
// this.audio.src is undefined, when the clip property is deserialized from the scene
if (!this.audio.src) {
this.audio.src = this._clip;
}
if (this.preload) {
this._clip._ensureLoaded();
}
},
onEnable: function () {
if (this.playOnLoad && this._firstlyEnabled) {
this._firstlyEnabled = false;
this.play();
}
cc.game.on(cc.game.EVENT_HIDE, this._pausedCallback, this);
cc.game.on(cc.game.EVENT_SHOW, this._restoreCallback, this);
},
onDisable: function () {
this.stop();
cc.game.off(cc.game.EVENT_HIDE, this._pausedCallback, this);
cc.game.off(cc.game.EVENT_SHOW, this._restoreCallback, this);
},
onDestroy: function () {
this.audio.destroy();
},
/**
* !#en Plays the clip.
* !#zh 播放音频剪辑。
* @method play
*/
play: function () {
if ( CC_EDITOR || !this._clip ) return;
var audio = this.audio;
audio.setVolume(this._mute ? 0 : this._volume);
audio.setLoop(this._loop);
audio.setCurrentTime(0);
audio.play();
},
/**
* !#en Stops the clip.
* !#zh 停止当前音频剪辑。
* @method stop
*/
stop: function () {
this.audio.stop();
},
/**
* !#en Pause the clip.
* !#zh 暂停当前音频剪辑。
* @method pause
*/
pause: function () {
this.audio.pause();
},
/**
* !#en Resume the clip.
* !#zh 恢复播放。
* @method resume
*/
resume: function () {
this.audio.resume();
},
/**
* !#en Rewind playing music.
* !#zh 从头开始播放。
* @method rewind
*/
rewind: function(){
this.audio.setCurrentTime(0);
},
/**
* !#en Get current time
* !#zh 获取当前的播放时间
* @method getCurrentTime
* @return {Number}
*/
getCurrentTime: function () {
return this.audio.getCurrentTime();
},
/**
* !#en Set current time
* !#zh 设置当前的播放时间
* @method setCurrentTime
* @param {Number} time
* @return {Number}
*/
setCurrentTime: function (time) {
this.audio.setCurrentTime(time);
return time;
},
/**
* !#en Get audio duration
* !#zh 获取当前音频的长度
* @method getDuration
* @return {Number}
*/
getDuration: function () {
return this.audio.getDuration();
}
});
cc.AudioSource = module.exports = AudioSource;

View File

@@ -0,0 +1,69 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const BlockEvents = ['touchstart', 'touchmove', 'touchend',
'mousedown', 'mousemove', 'mouseup',
'mouseenter', 'mouseleave', 'mousewheel'];
function stopPropagation (event) {
event.stopPropagation();
}
/**
* !#en
* This component will block all input events (mouse and touch) within the bounding box of the node, preventing the input from penetrating into the underlying node, typically for the background of the top UI.<br>
* This component does not have any API interface and can be added directly to the scene to take effect.
* !#zh
* 该组件将拦截所属节点 bounding box 内的所有输入事件(鼠标和触摸),防止输入穿透到下层节点,一般用于上层 UI 的背景。<br>
* 该组件没有任何 API 接口,直接添加到场景即可生效。
*
* @class BlockInputEvents
* @extends Component
*/
const BlockInputEvents = cc.Class({
name: 'cc.BlockInputEvents',
extends: require('./CCComponent'),
editor: {
menu: 'i18n:MAIN_MENU.component.ui/Block Input Events',
inspector: 'packages://inspector/inspectors/comps/block-input-events.js',
help: 'i18n:COMPONENT.help_url.block_input_events',
},
onEnable () {
for (var i = 0; i < BlockEvents.length; i++) {
// supply the 'this' parameter so that the callback could be added and removed correctly,
// even if the same component is added more than once to a Node.
this.node.on(BlockEvents[i], stopPropagation, this);
}
},
onDisable () {
for (var i = 0; i < BlockEvents.length; i++) {
this.node.off(BlockEvents[i], stopPropagation, this);
}
}
});
cc.BlockInputEvents = module.exports = BlockInputEvents;

View File

@@ -0,0 +1,886 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const Component = require('./CCComponent');
const GraySpriteState = require('../utils/gray-sprite-state');
/**
* !#en Enum for transition type.
* !#zh 过渡类型
* @enum Button.Transition
*/
let Transition = cc.Enum({
/**
* !#en The none type.
* !#zh 不做任何过渡
* @property {Number} NONE
*/
NONE: 0,
/**
* !#en The color type.
* !#zh 颜色过渡
* @property {Number} COLOR
*/
COLOR: 1,
/**
* !#en The sprite type.
* !#zh 精灵过渡
* @property {Number} SPRITE
*/
SPRITE: 2,
/**
* !#en The scale type
* !#zh 缩放过渡
* @property {Number} SCALE
*/
SCALE: 3
});
const State = cc.Enum({
NORMAL: 0,
HOVER: 1,
PRESSED: 2,
DISABLED: 3,
});
/**
* !#en
* Button component. Can be pressed or clicked. Button has 4 Transition types:
*
* - Button.Transition.NONE // Button will do nothing
* - Button.Transition.COLOR // Button will change target's color
* - Button.Transition.SPRITE // Button will change target Sprite's sprite
* - Button.Transition.SCALE // Button will change target node's scale
*
* The button can bind events (but you must be on the button's node to bind events).<br/>
* The following events can be triggered on all platforms.
*
* - cc.Node.EventType.TOUCH_START // Press
* - cc.Node.EventType.TOUCH_MOVE // After pressing and moving
* - cc.Node.EventType.TOUCH_END // After pressing and releasing
* - cc.Node.EventType.TOUCH_CANCEL // Press to cancel
*
* The following events are only triggered on the PC platform:
*
* - cc.Node.EventType.MOUSE_DOWN
* - cc.Node.EventType.MOUSE_MOVE
* - cc.Node.EventType.MOUSE_ENTER
* - cc.Node.EventType.MOUSE_LEAVE
* - cc.Node.EventType.MOUSE_UP
* - cc.Node.EventType.MOUSE_WHEEL
*
* User can get the current clicked node with 'event.target' from event object which is passed as parameter in the callback function of click event.
*
* !#zh
* 按钮组件。可以被按下,或者点击。
*
* 按钮可以通过修改 Transition 来设置按钮状态过渡的方式:
*
* - Button.Transition.NONE // 不做任何过渡
* - Button.Transition.COLOR // 进行颜色之间过渡
* - Button.Transition.SPRITE // 进行精灵之间过渡
* - Button.Transition.SCALE // 进行缩放过渡
*
* 按钮可以绑定事件(但是必须要在按钮的 Node 上才能绑定事件):<br/>
* 以下事件可以在全平台上都触发:
*
* - cc.Node.EventType.TOUCH_START // 按下时事件
* - cc.Node.EventType.TOUCH_MOVE // 按住移动后事件
* - cc.Node.EventType.TOUCH_END // 按下后松开后事件
* - cc.Node.EventType.TOUCH_CANCEL // 按下取消事件
*
* 以下事件只在 PC 平台上触发:
*
* - cc.Node.EventType.MOUSE_DOWN // 鼠标按下时事件
* - cc.Node.EventType.MOUSE_MOVE // 鼠标按住移动后事件
* - cc.Node.EventType.MOUSE_ENTER // 鼠标进入目标事件
* - cc.Node.EventType.MOUSE_LEAVE // 鼠标离开目标事件
* - cc.Node.EventType.MOUSE_UP // 鼠标松开事件
* - cc.Node.EventType.MOUSE_WHEEL // 鼠标滚轮事件
*
* 用户可以通过获取 __点击事件__ 回调函数的参数 event 的 target 属性获取当前点击对象。
* @class Button
* @extends Component
* @uses GraySpriteState
* @example
*
* // Add an event to the button.
* button.node.on(cc.Node.EventType.TOUCH_START, function (event) {
* cc.log("This is a callback after the trigger event");
* });
* // You could also add a click event
* //Note: In this way, you can't get the touch event info, so use it wisely.
* button.node.on('click', function (button) {
* //The event is a custom event, you could get the Button component via first argument
* })
*
*/
let Button = cc.Class({
name: 'cc.Button',
extends: Component,
mixins: [GraySpriteState],
ctor () {
this._pressed = false;
this._hovered = false;
this._fromColor = null;
this._toColor = null;
this._time = 0;
this._transitionFinished = true;
// init _originalScale in __preload()
this._fromScale = cc.Vec2.ZERO;
this._toScale = cc.Vec2.ZERO;
this._originalScale = null;
this._graySpriteMaterial = null;
this._spriteMaterial = null;
this._sprite = null;
},
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.ui/Button',
help: 'i18n:COMPONENT.help_url.button',
inspector: 'packages://inspector/inspectors/comps/button.js',
executeInEditMode: true
},
properties: {
/**
* !#en
* Whether the Button is disabled.
* If true, the Button will trigger event and do transition.
* !#zh
* 按钮事件是否被响应,如果为 false则按钮将被禁用。
* @property {Boolean} interactable
* @default true
*/
interactable: {
default: true,
tooltip: CC_DEV && 'i18n:COMPONENT.button.interactable',
notify () {
this._updateState();
if (!this.interactable) {
this._resetState();
}
},
animatable: false
},
_resizeToTarget: {
animatable: false,
set (value) {
if (value) {
this._resizeNodeToTargetNode();
}
}
},
/**
* !#en When this flag is true, Button target sprite will turn gray when interactable is false.
* !#zh 如果这个标记为 true当 button 的 interactable 属性为 false 的时候,会使用内置 shader 让 button 的 target 节点的 sprite 组件变灰
* @property {Boolean} enableAutoGrayEffect
*/
enableAutoGrayEffect: {
default: false,
tooltip: CC_DEV && 'i18n:COMPONENT.button.auto_gray_effect',
notify () {
this._updateDisabledState(true);
}
},
/**
* !#en Transition type
* !#zh 按钮状态改变时过渡方式。
* @property {Button.Transition} transition
* @default Button.Transition.Node
*/
transition: {
default: Transition.NONE,
tooltip: CC_DEV && 'i18n:COMPONENT.button.transition',
type: Transition,
animatable: false,
notify (oldValue) {
this._updateTransition(oldValue);
},
formerlySerializedAs: 'transition'
},
// color transition
/**
* !#en Normal state color.
* !#zh 普通状态下按钮所显示的颜色。
* @property {Color} normalColor
*/
normalColor: {
default: cc.Color.WHITE,
displayName: 'Normal',
tooltip: CC_DEV && 'i18n:COMPONENT.button.normal_color',
notify () {
if (this.transition === Transition.Color && this._getButtonState() === State.NORMAL) {
this._getTarget().opacity = this.normalColor.a;
}
this._updateState();
}
},
/**
* !#en Pressed state color
* !#zh 按下状态时按钮所显示的颜色。
* @property {Color} pressedColor
*/
pressedColor: {
default: cc.color(211, 211, 211),
displayName: 'Pressed',
tooltip: CC_DEV && 'i18n:COMPONENT.button.pressed_color',
notify () {
if (this.transition === Transition.Color && this._getButtonState() === State.PRESSED) {
this._getTarget().opacity = this.pressedColor.a;
}
this._updateState();
},
formerlySerializedAs: 'pressedColor'
},
/**
* !#en Hover state color
* !#zh 悬停状态下按钮所显示的颜色。
* @property {Color} hoverColor
*/
hoverColor: {
default: cc.Color.WHITE,
displayName: 'Hover',
tooltip: CC_DEV && 'i18n:COMPONENT.button.hover_color',
notify () {
if (this.transition === Transition.Color && this._getButtonState() === State.HOVER) {
this._getTarget().opacity = this.hoverColor.a;
}
this._updateState();
},
formerlySerializedAs: 'hoverColor'
},
/**
* !#en Disabled state color
* !#zh 禁用状态下按钮所显示的颜色。
* @property {Color} disabledColor
*/
disabledColor: {
default: cc.color(124, 124, 124),
displayName: 'Disabled',
tooltip: CC_DEV && 'i18n:COMPONENT.button.disabled_color',
notify () {
if (this.transition === Transition.Color && this._getButtonState() === State.DISABLED) {
this._getTarget().opacity = this.disabledColor.a;
}
this._updateState();
}
},
/**
* !#en Color and Scale transition duration
* !#zh 颜色过渡和缩放过渡时所需时间
* @property {Number} duration
*/
duration: {
default: 0.1,
range: [0, 10],
tooltip: CC_DEV && 'i18n:COMPONENT.button.duration',
},
/**
* !#en When user press the button, the button will zoom to a scale.
* The final scale of the button equals (button original scale * zoomScale)
* !#zh 当用户点击按钮后,按钮会缩放到一个值,这个值等于 Button 原始 scale * zoomScale
* @property {Number} zoomScale
*/
zoomScale: {
default: 1.2,
tooltip: CC_DEV && 'i18n:COMPONENT.button.zoom_scale'
},
// sprite transition
/**
* !#en Normal state sprite
* !#zh 普通状态下按钮所显示的 Sprite 。
* @property {SpriteFrame} normalSprite
*/
normalSprite: {
default: null,
type: cc.SpriteFrame,
displayName: 'Normal',
tooltip: CC_DEV && 'i18n:COMPONENT.button.normal_sprite',
notify () {
this._updateState();
}
},
/**
* !#en Pressed state sprite
* !#zh 按下状态时按钮所显示的 Sprite 。
* @property {SpriteFrame} pressedSprite
*/
pressedSprite: {
default: null,
type: cc.SpriteFrame,
displayName: 'Pressed',
tooltip: CC_DEV && 'i18n:COMPONENT.button.pressed_sprite',
formerlySerializedAs: 'pressedSprite',
notify () {
this._updateState();
}
},
/**
* !#en Hover state sprite
* !#zh 悬停状态下按钮所显示的 Sprite 。
* @property {SpriteFrame} hoverSprite
*/
hoverSprite: {
default: null,
type: cc.SpriteFrame,
displayName: 'Hover',
tooltip: CC_DEV && 'i18n:COMPONENT.button.hover_sprite',
formerlySerializedAs: 'hoverSprite',
notify () {
this._updateState();
}
},
/**
* !#en Disabled state sprite
* !#zh 禁用状态下按钮所显示的 Sprite 。
* @property {SpriteFrame} disabledSprite
*/
disabledSprite: {
default: null,
type: cc.SpriteFrame,
displayName: 'Disabled',
tooltip: CC_DEV && 'i18n:COMPONENT.button.disabled_sprite',
notify () {
this._updateState();
}
},
/**
* !#en
* Transition target.
* When Button state changed:
* If Transition type is Button.Transition.NONE, Button will do nothing
* If Transition type is Button.Transition.COLOR, Button will change target's color
* If Transition type is Button.Transition.SPRITE, Button will change target Sprite's sprite
* !#zh
* 需要过渡的目标。
* 当前按钮状态改变规则:
* -如果 Transition type 选择 Button.Transition.NONE按钮不做任何过渡。
* -如果 Transition type 选择 Button.Transition.COLOR按钮会对目标颜色进行颜色之间的过渡。
* -如果 Transition type 选择 Button.Transition.Sprite按钮会对目标 Sprite 进行 Sprite 之间的过渡。
* @property {Node} target
*/
target: {
default: null,
type: cc.Node,
tooltip: CC_DEV && "i18n:COMPONENT.button.target",
notify (oldValue) {
this._applyTarget();
if (oldValue && this.target !== oldValue) {
this._unregisterTargetEvent(oldValue);
}
}
},
/**
* !#en If Button is clicked, it will trigger event's handler
* !#zh 按钮的点击事件列表。
* @property {Component.EventHandler[]} clickEvents
*/
clickEvents: {
default: [],
type: cc.Component.EventHandler,
tooltip: CC_DEV && 'i18n:COMPONENT.button.click_events',
}
},
statics: {
Transition: Transition,
},
__preload () {
this._applyTarget();
this._resetState();
},
_resetState () {
this._pressed = false;
this._hovered = false;
// // Restore button status
let target = this._getTarget();
let transition = this.transition;
let originalScale = this._originalScale;
if (transition === Transition.COLOR && this.interactable) {
this._setTargetColor(this.normalColor);
}
else if (transition === Transition.SCALE && originalScale) {
target.setScale(originalScale.x, originalScale.y);
}
this._transitionFinished = true;
},
onEnable () {
// check sprite frames
if (this.normalSprite) {
this.normalSprite.ensureLoadTexture();
}
if (this.hoverSprite) {
this.hoverSprite.ensureLoadTexture();
}
if (this.pressedSprite) {
this.pressedSprite.ensureLoadTexture();
}
if (this.disabledSprite) {
this.disabledSprite.ensureLoadTexture();
}
if (!CC_EDITOR) {
this._registerNodeEvent();
}
this._updateState();
},
onDisable () {
this._resetState();
if (!CC_EDITOR) {
this._unregisterNodeEvent();
}
},
_getTarget () {
return this.target ? this.target : this.node;
},
_onTargetSpriteFrameChanged (comp) {
if (this.transition === Transition.SPRITE) {
this._setCurrentStateSprite(comp.spriteFrame);
}
},
_onTargetColorChanged (color) {
if (this.transition === Transition.COLOR) {
this._setCurrentStateColor(color);
}
},
_onTargetScaleChanged () {
let target = this._getTarget();
// update _originalScale if target scale changed
if (this._originalScale) {
if (this.transition !== Transition.SCALE || this._transitionFinished) {
this._originalScale.x = target.scaleX;
this._originalScale.y = target.scaleY;
}
}
},
_setTargetColor (color) {
let target = this._getTarget();
let cloneColor = color.clone();
target.opacity = cloneColor.a;
cloneColor.a = 255; // don't set node opacity via node.color.a
target.color = cloneColor;
},
_getStateColor (state) {
switch (state) {
case State.NORMAL:
return this.normalColor;
case State.HOVER:
return this.hoverColor;
case State.PRESSED:
return this.pressedColor;
case State.DISABLED:
return this.disabledColor;
}
},
_getStateSprite (state) {
switch (state) {
case State.NORMAL:
return this.normalSprite;
case State.HOVER:
return this.hoverSprite;
case State.PRESSED:
return this.pressedSprite;
case State.DISABLED:
return this.disabledSprite;
}
},
_setCurrentStateColor (color) {
switch ( this._getButtonState() ) {
case State.NORMAL:
this.normalColor = color;
break;
case State.HOVER:
this.hoverColor = color;
break;
case State.PRESSED:
this.pressedColor = color;
break;
case State.DISABLED:
this.disabledColor = color;
break;
}
},
_setCurrentStateSprite (spriteFrame) {
switch ( this._getButtonState() ) {
case State.NORMAL:
this.normalSprite = spriteFrame;
break;
case State.HOVER:
this.hoverSprite = spriteFrame;
break;
case State.PRESSED:
this.pressedSprite = spriteFrame;
break;
case State.DISABLED:
this.disabledSprite = spriteFrame;
break;
}
},
update (dt) {
let target = this._getTarget();
if (this._transitionFinished) return;
if (this.transition !== Transition.COLOR && this.transition !== Transition.SCALE) return;
this.time += dt;
let ratio = 1.0;
if (this.duration > 0) {
ratio = this.time / this.duration;
}
// clamp ratio
if (ratio >= 1) {
ratio = 1;
}
if (this.transition === Transition.COLOR) {
let color = this._fromColor.lerp(this._toColor, ratio);
this._setTargetColor(color);
}
// Skip if _originalScale is invalid
else if (this.transition === Transition.SCALE && this._originalScale) {
target.scale = this._fromScale.lerp(this._toScale, ratio);
}
if (ratio === 1) {
this._transitionFinished = true;
}
},
_registerNodeEvent () {
this.node.on(cc.Node.EventType.TOUCH_START, this._onTouchBegan, this);
this.node.on(cc.Node.EventType.TOUCH_MOVE, this._onTouchMove, this);
this.node.on(cc.Node.EventType.TOUCH_END, this._onTouchEnded, this);
this.node.on(cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancel, this);
this.node.on(cc.Node.EventType.MOUSE_ENTER, this._onMouseMoveIn, this);
this.node.on(cc.Node.EventType.MOUSE_LEAVE, this._onMouseMoveOut, this);
},
_unregisterNodeEvent () {
this.node.off(cc.Node.EventType.TOUCH_START, this._onTouchBegan, this);
this.node.off(cc.Node.EventType.TOUCH_MOVE, this._onTouchMove, this);
this.node.off(cc.Node.EventType.TOUCH_END, this._onTouchEnded, this);
this.node.off(cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancel, this);
this.node.off(cc.Node.EventType.MOUSE_ENTER, this._onMouseMoveIn, this);
this.node.off(cc.Node.EventType.MOUSE_LEAVE, this._onMouseMoveOut, this);
},
_registerTargetEvent (target) {
if (CC_EDITOR) {
target.on('spriteframe-changed', this._onTargetSpriteFrameChanged, this);
target.on(cc.Node.EventType.COLOR_CHANGED, this._onTargetColorChanged, this);
}
target.on(cc.Node.EventType.SCALE_CHANGED, this._onTargetScaleChanged, this);
},
_unregisterTargetEvent (target) {
if (CC_EDITOR) {
target.off('spriteframe-changed', this._onTargetSpriteFrameChanged, this);
target.off(cc.Node.EventType.COLOR_CHANGED, this._onTargetColorChanged, this);
}
target.off(cc.Node.EventType.SCALE_CHANGED, this._onTargetScaleChanged, this);
},
_getTargetSprite (target) {
let sprite = null;
if (target) {
sprite = target.getComponent(cc.Sprite);
}
return sprite;
},
_applyTarget () {
let target = this._getTarget();
this._sprite = this._getTargetSprite(target);
if (!this._originalScale) {
this._originalScale = cc.Vec2.ZERO;
}
this._originalScale.x = target.scaleX;
this._originalScale.y = target.scaleY;
this._registerTargetEvent(target);
},
// touch event handler
_onTouchBegan (event) {
if (!this.interactable || !this.enabledInHierarchy) return;
this._pressed = true;
this._updateState();
event.stopPropagation();
},
_onTouchMove (event) {
if (!this.interactable || !this.enabledInHierarchy || !this._pressed) return;
// mobile phone will not emit _onMouseMoveOut,
// so we have to do hit test when touch moving
let touch = event.touch;
let hit = this.node._hitTest(touch.getLocation());
let target = this._getTarget();
let originalScale = this._originalScale;
if (this.transition === Transition.SCALE && originalScale) {
if (hit) {
this._fromScale.x = originalScale.x;
this._fromScale.y = originalScale.y;
this._toScale.x = originalScale.x * this.zoomScale;
this._toScale.y = originalScale.y * this.zoomScale;
this._transitionFinished = false;
} else {
this.time = 0;
this._transitionFinished = true;
target.setScale(originalScale.x, originalScale.y);
}
} else {
let state;
if (hit) {
state = State.PRESSED;
} else {
state = State.NORMAL;
}
this._applyTransition(state);
}
event.stopPropagation();
},
_onTouchEnded (event) {
if (!this.interactable || !this.enabledInHierarchy) return;
if (this._pressed) {
cc.Component.EventHandler.emitEvents(this.clickEvents, event);
this.node.emit('click', this);
}
this._pressed = false;
this._updateState();
event.stopPropagation();
},
_onTouchCancel () {
if (!this.interactable || !this.enabledInHierarchy) return;
this._pressed = false;
this._updateState();
},
_onMouseMoveIn () {
if (this._pressed || !this.interactable || !this.enabledInHierarchy) return;
if (this.transition === Transition.SPRITE && !this.hoverSprite) return;
if (!this._hovered) {
this._hovered = true;
this._updateState();
}
},
_onMouseMoveOut () {
if (this._hovered) {
this._hovered = false;
this._updateState();
}
},
// state handler
_updateState () {
let state = this._getButtonState();
this._applyTransition(state);
this._updateDisabledState();
},
_getButtonState () {
let state;
if (!this.interactable) {
state = State.DISABLED;
}
else if (this._pressed) {
state = State.PRESSED;
}
else if (this._hovered) {
state = State.HOVER;
}
else {
state = State.NORMAL;
}
return state;
},
_updateColorTransitionImmediately (state) {
let color = this._getStateColor(state);
this._setTargetColor(color);
this._fromColor = color.clone();
this._toColor = color;
},
_updateColorTransition (state) {
if (CC_EDITOR || state === State.DISABLED) {
this._updateColorTransitionImmediately(state);
} else {
let target = this._getTarget();
let color = this._getStateColor(state);
this._fromColor = target.color.clone();
this._toColor = color;
this.time = 0;
this._transitionFinished = false;
}
},
_updateSpriteTransition (state) {
let sprite = this._getStateSprite(state);
if (this._sprite && sprite) {
this._sprite.spriteFrame = sprite;
}
},
_updateScaleTransition (state) {
if (state === State.PRESSED) {
this._zoomUp();
} else {
this._zoomBack();
}
},
_zoomUp () {
// skip before __preload()
if (!this._originalScale) {
return;
}
this._fromScale.x = this._originalScale.x;
this._fromScale.y = this._originalScale.y;
this._toScale.x = this._originalScale.x * this.zoomScale;
this._toScale.y = this._originalScale.y * this.zoomScale;
this.time = 0;
this._transitionFinished = false;
},
_zoomBack () {
// skip before __preload()
if (!this._originalScale) {
return;
}
let target = this._getTarget();
this._fromScale.x = target.scaleX;
this._fromScale.y = target.scaleY;
this._toScale.x = this._originalScale.x;
this._toScale.y = this._originalScale.y;
this.time = 0;
this._transitionFinished = false;
},
_updateTransition (oldTransition) {
// Reset to normal data when change transition.
if (oldTransition === Transition.COLOR) {
this._updateColorTransitionImmediately(State.NORMAL);
}
else if (oldTransition === Transition.SPRITE) {
this._updateSpriteTransition(State.NORMAL);
}
this._updateState();
},
_applyTransition (state) {
let transition = this.transition;
if (transition === Transition.COLOR) {
this._updateColorTransition(state);
} else if (transition === Transition.SPRITE) {
this._updateSpriteTransition(state);
} else if (transition === Transition.SCALE) {
this._updateScaleTransition(state);
}
},
_resizeNodeToTargetNode: CC_EDITOR && function () {
this.node.setContentSize(this._getTarget().getContentSize());
},
_updateDisabledState (force) {
if (!this._sprite) return;
if (this.enableAutoGrayEffect || force) {
let useGrayMaterial = false;
if (!(this.transition === Transition.SPRITE && this.disabledSprite)) {
useGrayMaterial = this.enableAutoGrayEffect && !this.interactable;
}
this._switchGrayMaterial(useGrayMaterial, this._sprite);
}
}
});
cc.Button = module.exports = Button;
/**
* !#en
* Note: This event is emitted from the node to which the component belongs.
* !#zh
* 注意:此事件是从该组件所属的 Node 上面派发出来的,需要用 node.on 来监听。
* @event click
* @param {Event.EventCustom} event
* @param {Button} button - The Button component.
*/

View File

@@ -0,0 +1,234 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
var Camera = require('../camera/CCCamera');
var Component = require('./CCComponent');
// Screen adaptation strategy for Canvas + Widget
function resetWidgetComponent (canvas) {
let widget = canvas.node.getComponent(cc.Widget);
if (!widget) {
widget = canvas.node.addComponent(cc.Widget);
}
widget.isAlignTop = true;
widget.isAlignBottom = true;
widget.isAlignLeft = true;
widget.isAlignRight = true;
widget.top = 0;
widget.bottom = 0;
widget.left = 0;
widget.right = 0;
}
/**
* !#zh 作为 UI 根节点,为所有子节点提供视窗四边的位置信息以供对齐,另外提供屏幕适配策略接口,方便从编辑器设置。<br>
* 注:由于本节点的尺寸会跟随屏幕拉伸,所以 anchorPoint 只支持 (0.5, 0.5),否则适配不同屏幕时坐标会有偏差。
*
* @class Canvas
* @extends Component
*/
var Canvas = cc.Class({
name: 'cc.Canvas',
extends: Component,
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.ui/Canvas',
help: 'i18n:COMPONENT.help_url.canvas',
executeInEditMode: true,
disallowMultiple: true,
},
resetInEditor: CC_EDITOR && function () {
_Scene._applyCanvasPreferences(this);
resetWidgetComponent(this);
},
statics: {
/**
* !#en Current active canvas, the scene should only have one active canvas at the same time.
* !#zh 当前激活的画布组件,场景同一时间只能有一个激活的画布。
* @property {Canvas} instance
* @static
*/
instance: null
},
properties: {
/**
* !#en The desigin resolution for current scene.
* !#zh 当前场景设计分辨率。
* @property {Size} designResolution
* @default new cc.Size(960, 640)
*/
_designResolution: cc.size(960, 640),
designResolution: {
get: function () {
return cc.size(this._designResolution);
},
set: function (value) {
this._designResolution.width = value.width;
this._designResolution.height = value.height;
this.applySettings();
},
tooltip: CC_DEV && 'i18n:COMPONENT.canvas.design_resolution'
},
_fitWidth: false,
_fitHeight: true,
/**
* !#en TODO
* !#zh: 是否优先将设计分辨率高度撑满视图高度。
* @property {Boolean} fitHeight
* @default false
*/
fitHeight: {
get: function () {
return this._fitHeight;
},
set: function (value) {
if (this._fitHeight !== value) {
this._fitHeight = value;
this.applySettings();
}
},
tooltip: CC_DEV && 'i18n:COMPONENT.canvas.fit_height'
},
/**
* !#en TODO
* !#zh: 是否优先将设计分辨率宽度撑满视图宽度。
* @property {Boolean} fitWidth
* @default false
*/
fitWidth: {
get: function () {
return this._fitWidth;
},
set: function (value) {
if (this._fitWidth !== value) {
this._fitWidth = value;
this.applySettings();
}
},
tooltip: CC_DEV && 'i18n:COMPONENT.canvas.fit_width'
}
},
// fit canvas node to design resolution
_fitDesignResolution: CC_EDITOR && function () {
// TODO: support paddings of locked widget
var designSize = cc.engine.getDesignResolutionSize();
this.node.setPosition(designSize.width * 0.5, designSize.height * 0.5);
this.node.setContentSize(designSize);
},
__preload: function () {
if (CC_DEV) {
var Flags = cc.Object.Flags;
this._objFlags |= (Flags.IsPositionLocked | Flags.IsAnchorLocked | Flags.IsSizeLocked);
}
if (Canvas.instance) {
return cc.warnID(6700,
this.node.name, Canvas.instance.node.name);
}
Canvas.instance = this;
// Align node to fit the screen
this.applySettings();
// Stretch to matched size during the scene initialization
let widget = this.getComponent(cc.Widget);
if (widget) {
widget.updateAlignment();
}
else if (CC_EDITOR) {
this._fitDesignResolution();
}
// Constantly align canvas node in edit mode
if (CC_EDITOR) {
cc.director.on(cc.Director.EVENT_AFTER_UPDATE, this._fitDesignResolution, this);
cc.engine.on('design-resolution-changed', this._fitDesignResolution, this);
}
},
start () {
if (!Camera.main && cc.game.renderType !== cc.game.RENDER_TYPE_CANVAS) {
// Create default Main Camera
let cameraNode = new cc.Node('Main Camera');
cameraNode.parent = this.node;
cameraNode.setSiblingIndex(0);
let camera = cameraNode.addComponent(Camera);
let ClearFlags = Camera.ClearFlags;
camera.clearFlags = ClearFlags.COLOR | ClearFlags.DEPTH | ClearFlags.STENCIL;
camera.depth = -1;
}
},
onDestroy: function () {
if (CC_EDITOR) {
cc.director.off(cc.Director.EVENT_AFTER_UPDATE, this._fitDesignResolution, this);
cc.engine.off('design-resolution-changed', this._fitDesignResolution, this);
}
if (Canvas.instance === this) {
Canvas.instance = null;
}
},
applySettings: function () {
var ResolutionPolicy = cc.ResolutionPolicy;
var policy;
if (this.fitHeight && this.fitWidth) {
policy = ResolutionPolicy.SHOW_ALL;
}
else if (!this.fitHeight && !this.fitWidth) {
policy = ResolutionPolicy.NO_BORDER;
}
else if (this.fitWidth) {
policy = ResolutionPolicy.FIXED_WIDTH;
}
else { // fitHeight
policy = ResolutionPolicy.FIXED_HEIGHT;
}
var designRes = this._designResolution;
if (CC_EDITOR) {
cc.engine.setDesignResolutionSize(designRes.width, designRes.height);
}
else {
cc.view.setDesignResolutionSize(designRes.width, designRes.height, policy);
}
}
});
cc.Canvas = module.exports = Canvas;

View File

@@ -0,0 +1,723 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
var CCObject = require('../platform/CCObject');
var js = require('../platform/js');
var idGenerater = new (require('../platform/id-generater'))('Comp');
var IsOnEnableCalled = CCObject.Flags.IsOnEnableCalled;
var IsOnLoadCalled = CCObject.Flags.IsOnLoadCalled;
var ActionManagerExist = !!cc.ActionManager;
/**
* !#en
* Base class for everything attached to Node(Entity).<br/>
* <br/>
* NOTE: Not allowed to use construction parameters for Component's subclasses,
* because Component is created by the engine.
* !#zh
* 所有附加到节点的基类。<br/>
* <br/>
* 注意:不允许使用组件的子类构造参数,因为组件是由引擎创建的。
*
* @class Component
* @extends Object
*/
var Component = cc.Class({
name: 'cc.Component',
extends: CCObject,
ctor: CC_EDITOR ? function () {
if ((typeof _Scene !== "undefined") && _Scene.AssetsWatcher) {
_Scene.AssetsWatcher.initComponent(this);
}
this._id = Editor.Utils.UuidUtils.uuid();
/**
* !#en
* Register all related EventTargets,
* all event callbacks will be removed in `_onPreDestroy`.
* !#zh
* 注册所有相关的 EventTargets所有事件回调将在 `_onPreDestroy` 中删除。
* @property {Array} __eventTargets
* @private
*/
this.__eventTargets = [];
} : function () {
this._id = idGenerater.getNewId();
this.__eventTargets = [];
},
properties: {
/**
* !#en The node this component is attached to. A component is always attached to a node.
* !#zh 该组件被附加到的节点。组件总会附加到一个节点。
* @property node
* @type {Node}
* @example
* cc.log(comp.node);
*/
node: {
default: null,
visible: false
},
name: {
get () {
if (this._name) {
return this._name;
}
var className = cc.js.getClassName(this);
var trimLeft = className.lastIndexOf('.');
if (trimLeft >= 0) {
className = className.slice(trimLeft + 1);
}
return this.node.name + '<' + className + '>';
},
set (value) {
this._name = value;
},
visible: false
},
/**
* !#en The uuid for editor.
* !#zh 组件的 uuid用于编辑器。
* @property uuid
* @type {String}
* @readOnly
* @example
* cc.log(comp.uuid);
*/
uuid: {
get () {
return this._id;
},
visible: false
},
__scriptAsset: CC_EDITOR && {
get () {},
//set (value) {
// if (this.__scriptUuid !== value) {
// if (value && Editor.Utils.UuidUtils.isUuid(value._uuid)) {
// var classId = Editor.Utils.UuidUtils.compressUuid(value._uuid);
// var NewComp = cc.js._getClassById(classId);
// if (js.isChildClassOf(NewComp, cc.Component)) {
// cc.warn('Sorry, replacing component script is not yet implemented.');
// //Editor.Ipc.sendToWins('reload:window-scripts', Editor._Sandbox.compiled);
// }
// else {
// cc.error('Can not find a component in the script which uuid is "%s".', value._uuid);
// }
// }
// else {
// cc.error('Invalid Script');
// }
// }
//},
displayName: 'Script',
type: cc._Script,
tooltip: CC_DEV && 'i18n:INSPECTOR.component.script'
},
/**
* @property _enabled
* @type {Boolean}
* @private
*/
_enabled: true,
/**
* !#en indicates whether this component is enabled or not.
* !#zh 表示该组件自身是否启用。
* @property enabled
* @type {Boolean}
* @default true
* @example
* comp.enabled = true;
* cc.log(comp.enabled);
*/
enabled: {
get () {
return this._enabled;
},
set (value) {
if (this._enabled !== value) {
this._enabled = value;
if (this.node._activeInHierarchy) {
var compScheduler = cc.director._compScheduler;
if (value) {
compScheduler.enableComp(this);
}
else {
compScheduler.disableComp(this);
}
}
}
},
visible: false,
animatable: true
},
/**
* !#en indicates whether this component is enabled and its node is also active in the hierarchy.
* !#zh 表示该组件是否被启用并且所在的节点也处于激活状态。
* @property enabledInHierarchy
* @type {Boolean}
* @readOnly
* @example
* cc.log(comp.enabledInHierarchy);
*/
enabledInHierarchy: {
get () {
if (CC_EDITOR) {
// _activeInHierarchy will not be updated before node's onRestore
return this._enabled && this.node?._active && this.node?._parent?._activeInHierarchy;
}
return this._enabled && this.node && this.node._activeInHierarchy;
},
visible: false
},
/**
* !#en Returns a value which used to indicate the onLoad get called or not.
* !#zh 返回一个值用来判断 onLoad 是否被调用过,不等于 0 时调用过,等于 0 时未调用。
* @property _isOnLoadCalled
* @type {Number}
* @readOnly
* @example
* cc.log(this._isOnLoadCalled > 0);
*/
_isOnLoadCalled: {
get () {
return this._objFlags & IsOnLoadCalled;
}
},
},
// LIFECYCLE METHODS
// Fireball provides lifecycle methods that you can specify to hook into this process.
// We provide Pre methods, which are called right before something happens, and Post methods which are called right after something happens.
/**
* !#en Update is called every frame, if the Component is enabled.<br/>
* This is a lifecycle method. It may not be implemented in the super class. You can only call its super class method inside it. It should not be called manually elsewhere.
* !#zh 如果该组件启用,则每帧调用 update。<br/>
* 该方法为生命周期方法,父类未必会有实现。并且你只能在该方法内部调用父类的实现,不可在其它地方直接调用该方法。
* @method update
* @param {Number} dt - the delta time in seconds it took to complete the last frame
* @protected
*/
update: null,
/**
* !#en LateUpdate is called every frame, if the Component is enabled.<br/>
* This is a lifecycle method. It may not be implemented in the super class. You can only call its super class method inside it. It should not be called manually elsewhere.
* !#zh 如果该组件启用,则每帧调用 LateUpdate。<br/>
* 该方法为生命周期方法,父类未必会有实现。并且你只能在该方法内部调用父类的实现,不可在其它地方直接调用该方法。
* @method lateUpdate
* @param {Number} dt - the delta time in seconds it took to complete the last frame
* @protected
*/
lateUpdate: null,
/**
* `__preload` is called before every onLoad.
* It is used to initialize the builtin components internally,
* to avoid checking whether onLoad is called before every public method calls.
* This method should be removed if script priority is supported.
*
* @method __preload
* @private
*/
__preload: null,
/**
* !#en
* When attaching to an active node or its node first activated.
* onLoad is always called before any start functions, this allows you to order initialization of scripts.<br/>
* This is a lifecycle method. It may not be implemented in the super class. You can only call its super class method inside it. It should not be called manually elsewhere.
* !#zh
* 当附加到一个激活的节点上或者其节点第一次激活时候调用。onLoad 总是会在任何 start 方法调用前执行,这能用于安排脚本的初始化顺序。<br/>
* 该方法为生命周期方法,父类未必会有实现。并且你只能在该方法内部调用父类的实现,不可在其它地方直接调用该方法。
* @method onLoad
* @protected
*/
onLoad: null,
/**
* !#en
* Called before all scripts' update if the Component is enabled the first time.
* Usually used to initialize some logic which need to be called after all components' `onload` methods called.<br/>
* This is a lifecycle method. It may not be implemented in the super class. You can only call its super class method inside it. It should not be called manually elsewhere.
* !#zh
* 如果该组件第一次启用,则在所有组件的 update 之前调用。通常用于需要在所有组件的 onLoad 初始化完毕后执行的逻辑。<br/>
* 该方法为生命周期方法,父类未必会有实现。并且你只能在该方法内部调用父类的实现,不可在其它地方直接调用该方法。
* @method start
* @protected
*/
start: null,
/**
* !#en Called when this component becomes enabled and its node is active.<br/>
* This is a lifecycle method. It may not be implemented in the super class. You can only call its super class method inside it. It should not be called manually elsewhere.
* !#zh 当该组件被启用,并且它的节点也激活时。<br/>
* 该方法为生命周期方法,父类未必会有实现。并且你只能在该方法内部调用父类的实现,不可在其它地方直接调用该方法。
* @method onEnable
* @protected
*/
onEnable: null,
/**
* !#en Called when this component becomes disabled or its node becomes inactive.<br/>
* This is a lifecycle method. It may not be implemented in the super class. You can only call its super class method inside it. It should not be called manually elsewhere.
* !#zh 当该组件被禁用或节点变为无效时调用。<br/>
* 该方法为生命周期方法,父类未必会有实现。并且你只能在该方法内部调用父类的实现,不可在其它地方直接调用该方法。
* @method onDisable
* @protected
*/
onDisable: null,
/**
* !#en Called when this component will be destroyed.<br/>
* This is a lifecycle method. It may not be implemented in the super class. You can only call its super class method inside it. It should not be called manually elsewhere.
* !#zh 当该组件被销毁时调用<br/>
* 该方法为生命周期方法,父类未必会有实现。并且你只能在该方法内部调用父类的实现,不可在其它地方直接调用该方法。
* @method onDestroy
* @protected
*/
onDestroy: null,
/**
* @method onFocusInEditor
* @protected
*/
onFocusInEditor: null,
/**
* @method onLostFocusInEditor
* @protected
*/
onLostFocusInEditor: null,
/**
* !#en Called to initialize the component or nodes properties when adding the component the first time or when the Reset command is used. This function is only called in editor.
* !#zh 用来初始化组件或节点的一些属性,当该组件被第一次添加到节点上或用户点击了它的 Reset 菜单时调用。这个回调只会在编辑器下调用。
* @method resetInEditor
* @protected
*/
resetInEditor: null,
// PUBLIC
/**
* !#en Adds a component class to the node. You can also add component to node by passing in the name of the script.
* !#zh 向节点添加一个组件类,你还可以通过传入脚本的名称来添加组件。
*
* @method addComponent
* @param {Function|String} typeOrClassName - the constructor or the class name of the component to add
* @return {Component} - the newly added component
* @example
* var sprite = node.addComponent(cc.Sprite);
* var test = node.addComponent("Test");
* @typescript
* addComponent<T extends Component>(type: {new(): T}): T
* addComponent(className: string): any
*/
addComponent (typeOrClassName) {
return this.node.addComponent(typeOrClassName);
},
/**
* !#en
* Returns the component of supplied type if the node has one attached, null if it doesn't.<br/>
* You can also get component in the node by passing in the name of the script.
* !#zh
* 获取节点上指定类型的组件,如果节点有附加指定类型的组件,则返回,如果没有则为空。<br/>
* 传入参数也可以是脚本的名称。
*
* @method getComponent
* @param {Function|String} typeOrClassName
* @return {Component}
* @example
* // get sprite component.
* var sprite = node.getComponent(cc.Sprite);
* // get custom test calss.
* var test = node.getComponent("Test");
* @typescript
* getComponent<T extends Component>(type: {prototype: T}): T
* getComponent(className: string): any
*/
getComponent (typeOrClassName) {
return this.node.getComponent(typeOrClassName);
},
/**
* !#en Returns all components of supplied Type in the node.
* !#zh 返回节点上指定类型的所有组件。
*
* @method getComponents
* @param {Function|String} typeOrClassName
* @return {Component[]}
* @example
* var sprites = node.getComponents(cc.Sprite);
* var tests = node.getComponents("Test");
* @typescript
* getComponents<T extends Component>(type: {prototype: T}): T[]
* getComponents(className: string): any[]
*/
getComponents (typeOrClassName) {
return this.node.getComponents(typeOrClassName);
},
/**
* !#en Returns the component of supplied type in any of its children using depth first search.
* !#zh 递归查找所有子节点中第一个匹配指定类型的组件。
*
* @method getComponentInChildren
* @param {Function|String} typeOrClassName
* @returns {Component}
* @example
* var sprite = node.getComponentInChildren(cc.Sprite);
* var Test = node.getComponentInChildren("Test");
* @typescript
* getComponentInChildren<T extends Component>(type: {prototype: T}): T
* getComponentInChildren(className: string): any
*/
getComponentInChildren (typeOrClassName) {
return this.node.getComponentInChildren(typeOrClassName);
},
/**
* !#en Returns the components of supplied type in self or any of its children using depth first search.
* !#zh 递归查找自身或所有子节点中指定类型的组件
*
* @method getComponentsInChildren
* @param {Function|String} typeOrClassName
* @returns {Component[]}
* @example
* var sprites = node.getComponentsInChildren(cc.Sprite);
* var tests = node.getComponentsInChildren("Test");
* @typescript
* getComponentsInChildren<T extends Component>(type: {prototype: T}): T[]
* getComponentsInChildren(className: string): any[]
*/
getComponentsInChildren (typeOrClassName) {
return this.node.getComponentsInChildren(typeOrClassName);
},
// VIRTUAL
/**
* !#en
* If the component's bounding box is different from the node's, you can implement this method to supply
* a custom axis aligned bounding box (AABB), so the editor's scene view can perform hit test properly.
* !#zh
* 如果组件的包围盒与节点不同您可以实现该方法以提供自定义的轴向对齐的包围盒AABB
* 以便编辑器的场景视图可以正确地执行点选测试。
*
* @method _getLocalBounds
* @param {Rect} out_rect - the Rect to receive the bounding box
*/
_getLocalBounds: null,
/**
* !#en
* onRestore is called after the user clicks the Reset item in the Inspector's context menu or performs
* an undo operation on this component.<br/>
* <br/>
* If the component contains the "internal state", short for "temporary member variables which not included<br/>
* in its CCClass properties", then you may need to implement this function.<br/>
* <br/>
* The editor will call the getset accessors of your component to record/restore the component's state<br/>
* for undo/redo operation. However, in extreme cases, it may not works well. Then you should implement<br/>
* this function to manually synchronize your component's "internal states" with its public properties.<br/>
* Once you implement this function, all the getset accessors of your component will not be called when<br/>
* the user performs an undo/redo operation. Which means that only the properties with default value<br/>
* will be recorded or restored by editor.<br/>
* <br/>
* Similarly, the editor may failed to reset your component correctly in extreme cases. Then if you need<br/>
* to support the reset menu, you should manually synchronize your component's "internal states" with its<br/>
* properties in this function. Once you implement this function, all the getset accessors of your component<br/>
* will not be called during reset operation. Which means that only the properties with default value<br/>
* will be reset by editor.
*
* This function is only called in editor mode.
* !#zh
* onRestore 是用户在检查器菜单点击 Reset 时,对此组件执行撤消操作后调用的。<br/>
* <br/>
* 如果组件包含了“内部状态”(不在 CCClass 属性中定义的临时成员变量),那么你可能需要实现该方法。<br/>
* <br/>
* 编辑器执行撤销/重做操作时,将调用组件的 get set 来录制和还原组件的状态。然而,在极端的情况下,它可能无法良好运作。<br/>
* 那么你就应该实现这个方法,手动根据组件的属性同步“内部状态”。一旦你实现这个方法,当用户撤销或重做时,组件的所有 get set 都不会再被调用。这意味着仅仅指定了默认值的属性将被编辑器记录和还原。<br/>
* <br/>
* 同样的,编辑可能无法在极端情况下正确地重置您的组件。如果你需要支持组件重置菜单,则需要在该方法中手工同步组件属性到“内部状态”。一旦你实现这个方法,组件的所有 get set 都不会在重置操作时被调用。这意味着仅仅指定了默认值的属性将被编辑器重置。
* <br/>
* 此方法仅在编辑器下会被调用。
* @method onRestore
*/
onRestore: null,
// OVERRIDE
destroy () {
if (CC_EDITOR) {
var depend = this.node._getDependComponent(this);
if (depend) {
return cc.errorID(3626,
cc.js.getClassName(this), cc.js.getClassName(depend));
}
}
if (this._super()) {
if (this._enabled && this.node._activeInHierarchy) {
cc.director._compScheduler.disableComp(this);
}
}
},
_onPreDestroy () {
if (ActionManagerExist) {
cc.director.getActionManager().removeAllActionsFromTarget(this);
}
// Schedules
this.unscheduleAllCallbacks();
// Remove all listeners
var eventTargets = this.__eventTargets;
for (var i = eventTargets.length - 1; i >= 0; --i) {
var target = eventTargets[i];
target && target.targetOff(this);
}
eventTargets.length = 0;
//
if (CC_EDITOR && !CC_TEST) {
_Scene.AssetsWatcher.stop(this);
}
// onDestroy
cc.director._nodeActivator.destroyComp(this);
// do remove component
this.node._removeComponent(this);
},
_instantiate (cloned) {
if (!cloned) {
cloned = cc.instantiate._clone(this, this);
}
cloned.node = null;
return cloned;
},
// Scheduler
/**
* !#en
* Schedules a custom selector.<br/>
* If the selector is already scheduled, then the interval parameter will be updated without scheduling it again.
* !#zh
* 调度一个自定义的回调函数。<br/>
* 如果回调函数已调度,那么将不会重复调度它,只会更新时间间隔参数。
* @method schedule
* @param {function} callback The callback function
* @param {Number} [interval=0] Tick interval in seconds. 0 means tick every frame.
* @param {Number} [repeat=cc.macro.REPEAT_FOREVER] The selector will be executed (repeat + 1) times, you can use cc.macro.REPEAT_FOREVER for tick infinitely.
* @param {Number} [delay=0] The amount of time that the first tick will wait before execution. Unit: s
* @example
* var timeCallback = function (dt) {
* cc.log("time: " + dt);
* }
* this.schedule(timeCallback, 1);
*/
schedule (callback, interval, repeat, delay) {
cc.assertID(callback, 1619);
interval = interval || 0;
cc.assertID(interval >= 0, 1620);
repeat = isNaN(repeat) ? cc.macro.REPEAT_FOREVER : repeat;
delay = delay || 0;
var scheduler = cc.director.getScheduler();
// should not use enabledInHierarchy to judge whether paused,
// because enabledInHierarchy is assigned after onEnable.
// Actually, if not yet scheduled, resumeTarget/pauseTarget has no effect on component,
// therefore there is no way to guarantee the paused state other than isTargetPaused.
var paused = scheduler.isTargetPaused(this);
scheduler.schedule(callback, this, interval, repeat, delay, paused);
},
/**
* !#en Schedules a callback function that runs only once, with a delay of 0 or larger.
* !#zh 调度一个只运行一次的回调函数,可以指定 0 让回调函数在下一帧立即执行或者在一定的延时之后执行。
* @method scheduleOnce
* @see cc.Node#schedule
* @param {function} callback A function wrapped as a selector
* @param {Number} [delay=0] The amount of time that the first tick will wait before execution. Unit: s
* @example
* var timeCallback = function (dt) {
* cc.log("time: " + dt);
* }
* this.scheduleOnce(timeCallback, 2);
*/
scheduleOnce (callback, delay) {
this.schedule(callback, 0, 0, delay);
},
/**
* !#en Unschedules a custom callback function.
* !#zh 取消调度一个自定义的回调函数。
* @method unschedule
* @see cc.Node#schedule
* @param {function} callback_fn A function wrapped as a selector
* @example
* this.unschedule(_callback);
*/
unschedule (callback_fn) {
if (!callback_fn)
return;
cc.director.getScheduler().unschedule(callback_fn, this);
},
/**
* !#en
* unschedule all scheduled callback functions: custom callback functions, and the 'update' callback function.<br/>
* Actions are not affected by this method.
* !#zh 取消调度所有已调度的回调函数:定制的回调函数以及 `update` 回调函数。动作不受此方法影响。
* @method unscheduleAllCallbacks
* @example
* this.unscheduleAllCallbacks();
*/
unscheduleAllCallbacks () {
cc.director.getScheduler().unscheduleAllForTarget(this);
},
});
Component._requireComponent = null;
Component._executionOrder = 0;
if (CC_EDITOR && CC_PREVIEW) Component._disallowMultiple = null;
if (CC_EDITOR || CC_TEST) {
// INHERITABLE STATIC MEMBERS
Component._executeInEditMode = false;
Component._playOnFocus = false;
Component._help = '';
// NON-INHERITED STATIC MEMBERS
// (TypeScript 2.3 will still inherit them, so always check hasOwnProperty before using)
js.value(Component, '_inspector', '', true);
js.value(Component, '_icon', '', true);
// COMPONENT HELPERS
cc._componentMenuItems = [];
Component._addMenuItem = function (cls, path, priority) {
cc._componentMenuItems.push({
component: cls,
menuPath: path,
priority: priority
});
};
}
// We make this non-enumerable, to prevent inherited by sub classes.
js.value(Component, '_registerEditorProps', function (cls, props) {
var reqComp = props.requireComponent;
if (reqComp) {
cls._requireComponent = reqComp;
}
var order = props.executionOrder;
if (order && typeof order === 'number') {
cls._executionOrder = order;
}
if ((CC_EDITOR || CC_PREVIEW) && 'disallowMultiple' in props) {
cls._disallowMultiple = cls;
}
if (CC_EDITOR || CC_TEST) {
var name = cc.js.getClassName(cls);
for (var key in props) {
var val = props[key];
switch (key) {
case 'executeInEditMode':
cls._executeInEditMode = !!val;
break;
case 'playOnFocus':
if (val) {
var willExecuteInEditMode = ('executeInEditMode' in props) ? props.executeInEditMode : cls._executeInEditMode;
if (willExecuteInEditMode) {
cls._playOnFocus = true;
}
else {
cc.warnID(3601, name);
}
}
break;
case 'inspector':
js.value(cls, '_inspector', val, true);
break;
case 'icon':
js.value(cls, '_icon', val, true);
break;
case 'menu':
Component._addMenuItem(cls, val, props.menuPriority);
break;
case 'requireComponent':
case 'executionOrder':
case 'disallowMultiple':
// skip here
break;
case 'help':
cls._help = val;
break;
default:
cc.warnID(3602, key, name);
break;
}
}
}
});
Component.prototype.__scriptUuid = '';
cc.Component = module.exports = Component;

View File

@@ -0,0 +1,188 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
/**
* !#en
* Component will register a event to target component's handler.
* And it will trigger the handler when a certain event occurs.
*
* !@zh
* “EventHandler” 类用来设置场景中的事件回调,
* 该类允许用户设置回调目标节点,目标组件名,组件方法名,
* 并可通过 emit 方法调用目标函数。
* @class Component.EventHandler
* @example
* // Let's say we have a MainMenu component on newTarget
* // file: MainMenu.js
* cc.Class({
* extends: cc.Component,
* // sender: the node MainMenu.js belongs to
* // eventType: CustomEventData
* onClick (sender, eventType) {
* cc.log('click');
* }
* })
* // Create new EventHandler
* var eventHandler = new cc.Component.EventHandler();
* eventHandler.target = newTarget;
* eventHandler.component = "MainMenu";
* eventHandler.handler = "onClick";
* eventHandler.customEventData = "my data";
*/
cc.Component.EventHandler = cc.Class({
name: 'cc.ClickEvent',
properties: {
/**
* !#en the node that contains target callback, such as the node example script belongs to
* !#zh 事件响应函数所在节点 ,比如例子中脚本归属的节点本身
* @property target
* @type {Node}
* @default null
*/
target: {
default: null,
type: cc.Node,
},
/**
* !#en name of the component(script) that contains target callback, such as the name 'MainMenu' of script in example
* !#zh 事件响应函数所在组件名(脚本名), 比如例子中的脚本名 'MainMenu'
* @property component
* @type {String}
* @default ''
*/
// only for deserializing old project component field
component: '',
_componentId: '',
_componentName: {
get () {
this._genCompIdIfNeeded();
return this._compId2Name(this._componentId);
},
set (value) {
this._componentId = this._compName2Id(value);
},
},
/**
* !#en Event handler, such as function's name 'onClick' in example
* !#zh 响应事件函数名,比如例子中的 'onClick'
* @property handler
* @type {String}
* @default ''
*/
handler: {
default: '',
},
/**
* !#en Custom Event Data, such as 'eventType' in example
* !#zh 自定义事件数据,比如例子中的 eventType
* @property customEventData
* @default ''
* @type {String}
*/
customEventData: {
default: ''
}
},
statics: {
/**
* @method emitEvents
* @param {Component.EventHandler[]} events
* @param {any} ...params
* @static
*/
emitEvents: function(events) {
'use strict';
let args;
if (arguments.length > 0) {
args = new Array(arguments.length - 1);
for (let i = 0, l = args.length; i < l; i++) {
args[i] = arguments[i+1];
}
}
for (let i = 0, l = events.length; i < l; i++) {
var event = events[i];
if (!(event instanceof cc.Component.EventHandler)) continue;
event.emit(args);
}
}
},
/**
* !#en Emit event with params
* !#zh 触发目标组件上的指定 handler 函数,该参数是回调函数的参数值(可不填)。
* @method emit
* @param {Array} params
* @example
* // Call Function
* var eventHandler = new cc.Component.EventHandler();
* eventHandler.target = newTarget;
* eventHandler.component = "MainMenu";
* eventHandler.handler = "OnClick"
* eventHandler.emit(["param1", "param2", ....]);
*/
emit: function(params) {
var target = this.target;
if (!cc.isValid(target)) return;
this._genCompIdIfNeeded();
var compType = cc.js._getClassById(this._componentId);
var comp = target.getComponent(compType);
if (!cc.isValid(comp)) return;
var handler = comp[this.handler];
if (typeof(handler) !== 'function') return;
if (this.customEventData != null && this.customEventData !== '') {
params = params.slice();
params.push(this.customEventData);
}
handler.apply(comp, params);
},
_compName2Id (compName) {
let comp = cc.js.getClassByName(compName);
return cc.js._getClassId(comp);
},
_compId2Name (compId) {
let comp = cc.js._getClassById(compId);
return cc.js.getClassName(comp);
},
// to be deprecated in the future
_genCompIdIfNeeded () {
if (!this._componentId) {
this._componentName = this.component;
this.component = '';
}
},
});

View File

@@ -0,0 +1,837 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const macro = require('../platform/CCMacro');
const RenderComponent = require('./CCRenderComponent');
const Material = require('../assets/material/CCMaterial');
const LabelFrame = require('../renderer/utils/label/label-frame');
const BlendFunc = require('../utils/blend-func');
const deleteFromDynamicAtlas = require('../renderer/utils/utils').deleteFromDynamicAtlas;
/**
* !#en Enum for text alignment.
* !#zh 文本横向对齐类型
* @enum Label.HorizontalAlign
*/
/**
* !#en Alignment left for text.
* !#zh 文本内容左对齐。
* @property {Number} LEFT
*/
/**
* !#en Alignment center for text.
* !#zh 文本内容居中对齐。
* @property {Number} CENTER
*/
/**
* !#en Alignment right for text.
* !#zh 文本内容右边对齐。
* @property {Number} RIGHT
*/
const HorizontalAlign = macro.TextAlignment;
/**
* !#en Enum for vertical text alignment.
* !#zh 文本垂直对齐类型
* @enum Label.VerticalAlign
*/
/**
* !#en Vertical alignment top for text.
* !#zh 文本顶部对齐。
* @property {Number} TOP
*/
/**
* !#en Vertical alignment center for text.
* !#zh 文本居中对齐。
* @property {Number} CENTER
*/
/**
* !#en Vertical alignment bottom for text.
* !#zh 文本底部对齐。
* @property {Number} BOTTOM
*/
const VerticalAlign = macro.VerticalTextAlignment;
/**
* !#en Enum for Overflow.
* !#zh Overflow 类型
* @enum Label.Overflow
*/
/**
* !#en NONE.
* !#zh 不做任何限制。
* @property {Number} NONE
*/
/**
* !#en In CLAMP mode, when label content goes out of the bounding box, it will be clipped.
* !#zh CLAMP 模式中,当文本内容超出边界框时,多余的会被截断。
* @property {Number} CLAMP
*/
/**
* !#en In SHRINK mode, the font size will change dynamically to adapt the content size. This mode may takes up more CPU resources when the label is refreshed.
* !#zh SHRINK 模式,字体大小会动态变化,以适应内容大小。这个模式在文本刷新的时候可能会占用较多 CPU 资源。
* @property {Number} SHRINK
*/
/**
* !#en In RESIZE_HEIGHT mode, you can only change the width of label and the height is changed automatically.
* !#zh 在 RESIZE_HEIGHT 模式下,只能更改文本的宽度,高度是自动改变的。
* @property {Number} RESIZE_HEIGHT
*/
const Overflow = cc.Enum({
NONE: 0,
CLAMP: 1,
SHRINK: 2,
RESIZE_HEIGHT: 3
});
/**
* !#en Enum for font type.
* !#zh Type 类型
* @enum Label.Type
*/
/**
* !#en The TTF font type.
* !#zh TTF字体
* @property {Number} TTF
*/
/**
* !#en The bitmap font type.
* !#zh 位图字体
* @property {Number} BMFont
*/
/**
* !#en The system font type.
* !#zh 系统字体
* @property {Number} SystemFont
*/
/**
* !#en Enum for cache mode.
* !#zh CacheMode 类型
* @enum Label.CacheMode
*/
/**
* !#en Do not do any caching.
* !#zh 不做任何缓存。
* @property {Number} NONE
*/
/**
* !#en In BITMAP mode, cache the label as a static image and add it to the dynamic atlas for batch rendering, and can batching with Sprites using broken images.
* !#zh BITMAP 模式,将 label 缓存成静态图像并加入到动态图集,以便进行批次合并,可与使用碎图的 Sprite 进行合批(注:动态图集在 Chrome 以及微信小游戏暂时关闭,该功能无效)。
* @property {Number} BITMAP
*/
/**
* !#en In CHAR mode, split text into characters and cache characters into a dynamic atlas which the size of 2048*2048.
* !#zh CHAR 模式,将文本拆分为字符,并将字符缓存到一张单独的大小为 2048*2048 的图集中进行重复使用,不再使用动态图集(注:当图集满时将不再进行缓存,暂时不支持 SHRINK 自适应文本尺寸(后续完善))。
* @property {Number} CHAR
*/
const CacheMode = cc.Enum({
NONE: 0,
BITMAP: 1,
CHAR: 2,
});
const BOLD_FLAG = 1 << 0;
const ITALIC_FLAG = 1 << 1;
const UNDERLINE_FLAG = 1 << 2;
/**
* !#en The Label Component.
* !#zh 文字标签组件
* @class Label
* @extends RenderComponent
*/
let Label = cc.Class({
name: 'cc.Label',
extends: RenderComponent,
mixins: [BlendFunc],
ctor () {
if (CC_EDITOR) {
this._userDefinedFont = null;
}
this._actualFontSize = 0;
this._assemblerData = null;
this._frame = null;
this._ttfTexture = null;
this._letterTexture = null;
if (cc.game.renderType === cc.game.RENDER_TYPE_CANVAS) {
this._updateMaterial = this._updateMaterialCanvas;
}
else {
this._updateMaterial = this._updateMaterialWebgl;
}
},
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.renderers/Label',
help: 'i18n:COMPONENT.help_url.label',
inspector: 'packages://inspector/inspectors/comps/label.js',
},
properties: {
/**
* !#en Content string of label.
* !#zh 标签显示的文本内容。
* @property {String} string
*/
_string: {
default: '',
formerlySerializedAs: '_N$string',
},
string: {
get () {
return this._string;
},
set (value) {
let oldValue = this._string;
this._string = '' + value;
if (this.string !== oldValue) {
this.setVertsDirty();
}
this._checkStringEmpty();
},
multiline: true,
tooltip: CC_DEV && 'i18n:COMPONENT.label.string'
},
/**
* !#en Horizontal Alignment of label.
* !#zh 文本内容的水平对齐方式。
* @property {Label.HorizontalAlign} horizontalAlign
*/
horizontalAlign: {
default: HorizontalAlign.LEFT,
type: HorizontalAlign,
tooltip: CC_DEV && 'i18n:COMPONENT.label.horizontal_align',
notify (oldValue) {
if (this.horizontalAlign === oldValue) return;
this.setVertsDirty();
},
animatable: false
},
/**
* !#en Vertical Alignment of label.
* !#zh 文本内容的垂直对齐方式。
* @property {Label.VerticalAlign} verticalAlign
*/
verticalAlign: {
default: VerticalAlign.TOP,
type: VerticalAlign,
tooltip: CC_DEV && 'i18n:COMPONENT.label.vertical_align',
notify (oldValue) {
if (this.verticalAlign === oldValue) return;
this.setVertsDirty();
},
animatable: false
},
/**
* !#en The actual rendering font size in shrink mode
* !#zh SHRINK 模式下面文本实际渲染的字体大小
* @property {Number} actualFontSize
*/
actualFontSize: {
displayName: 'Actual Font Size',
animatable: false,
readonly: true,
get () {
return this._actualFontSize;
},
tooltip: CC_DEV && 'i18n:COMPONENT.label.actualFontSize',
},
_fontSize: 40,
/**
* !#en Font size of label.
* !#zh 文本字体大小。
* @property {Number} fontSize
*/
fontSize: {
get () {
return this._fontSize;
},
set (value) {
if (this._fontSize === value) return;
this._fontSize = value;
this.setVertsDirty();
},
range: [0, 512],
tooltip: CC_DEV && 'i18n:COMPONENT.label.font_size',
},
/**
* !#en Font family of label, only take effect when useSystemFont property is true.
* !#zh 文本字体名称, 只在 useSystemFont 属性为 true 的时候生效。
* @property {String} fontFamily
*/
fontFamily: {
default: "Arial",
tooltip: CC_DEV && 'i18n:COMPONENT.label.font_family',
notify (oldValue) {
if (this.fontFamily === oldValue) return;
this.setVertsDirty();
},
animatable: false
},
_lineHeight: 40,
/**
* !#en Line Height of label.
* !#zh 文本行高。
* @property {Number} lineHeight
*/
lineHeight: {
get () {
return this._lineHeight;
},
set (value) {
if (this._lineHeight === value) return;
this._lineHeight = value;
this.setVertsDirty();
},
tooltip: CC_DEV && 'i18n:COMPONENT.label.line_height',
},
/**
* !#en Overflow of label.
* !#zh 文字显示超出范围时的处理方式。
* @property {Label.Overflow} overflow
*/
overflow: {
default: Overflow.NONE,
type: Overflow,
tooltip: CC_DEV && 'i18n:COMPONENT.label.overflow',
notify (oldValue) {
if (this.overflow === oldValue) return;
this.setVertsDirty();
},
animatable: false
},
_enableWrapText: true,
/**
* !#en Whether auto wrap label when string width is large than label width.
* !#zh 是否自动换行。
* @property {Boolean} enableWrapText
*/
enableWrapText: {
get () {
return this._enableWrapText;
},
set (value) {
if (this._enableWrapText === value) return;
this._enableWrapText = value;
this.setVertsDirty();
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.label.wrap',
},
// 这个保存了旧项目的 file 数据
_N$file: null,
/**
* !#en The font of label.
* !#zh 文本字体。
* @property {Font} font
*/
font: {
get () {
return this._N$file;
},
set (value) {
if (this.font === value) return;
//if delete the font, we should change isSystemFontUsed to true
if (!value) {
this._isSystemFontUsed = true;
}
if (CC_EDITOR && value) {
this._userDefinedFont = value;
}
this._N$file = value;
if (value && this._isSystemFontUsed)
this._isSystemFontUsed = false;
if (!this.enabledInHierarchy) return;
this._forceUpdateRenderData();
},
type: cc.Font,
tooltip: CC_DEV && 'i18n:COMPONENT.label.font',
animatable: false
},
_isSystemFontUsed: true,
/**
* !#en Whether use system font name or not.
* !#zh 是否使用系统字体。
* @property {Boolean} useSystemFont
*/
useSystemFont: {
get () {
return this._isSystemFontUsed;
},
set (value) {
if (this._isSystemFontUsed === value) return;
this._isSystemFontUsed = !!value;
if (CC_EDITOR) {
if (!value && this._userDefinedFont) {
this.font = this._userDefinedFont;
this.spacingX = this._spacingX;
return;
}
}
if (value) {
this.font = null;
if (!this.enabledInHierarchy) return;
this._forceUpdateRenderData();
}
this.markForValidate();
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.label.system_font',
},
_bmFontOriginalSize: {
displayName: 'BMFont Original Size',
get () {
if (this._N$file instanceof cc.BitmapFont) {
return this._N$file.fontSize;
}
else {
return -1;
}
},
visible: true,
animatable: false
},
_spacingX: 0,
/**
* !#en The spacing of the x axis between characters, only take Effect when using bitmap fonts.
* !#zh 文字之间 x 轴的间距,仅在使用位图字体时生效。
* @property {Number} spacingX
*/
spacingX: {
get () {
return this._spacingX;
},
set (value) {
this._spacingX = value;
this.setVertsDirty();
},
tooltip: CC_DEV && 'i18n:COMPONENT.label.spacingX',
},
//For compatibility with v2.0.x temporary reservation.
_batchAsBitmap: false,
/**
* !#en The cache mode of label. This mode only supports system fonts.
* !#zh 文本缓存模式, 该模式只支持系统字体。
* @property {Label.CacheMode} cacheMode
*/
cacheMode: {
default: CacheMode.NONE,
type: CacheMode,
tooltip: CC_DEV && 'i18n:COMPONENT.label.cacheMode',
notify (oldValue) {
if (this.cacheMode === oldValue) return;
if (oldValue === CacheMode.BITMAP && !(this.font instanceof cc.BitmapFont)) {
this._frame && this._frame._resetDynamicAtlasFrame();
}
if (oldValue === CacheMode.CHAR) {
this._ttfTexture = null;
}
if (!this.enabledInHierarchy) return;
this._forceUpdateRenderData();
},
animatable: false
},
_styleFlags: 0,
/**
* !#en Whether enable bold.
* !#zh 是否启用黑体。
* @property {Boolean} enableBold
*/
enableBold: {
get () {
return !!(this._styleFlags & BOLD_FLAG);
},
set (value) {
if (value) {
this._styleFlags |= BOLD_FLAG;
} else {
this._styleFlags &= ~BOLD_FLAG;
}
this.setVertsDirty();
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.label.bold'
},
/**
* !#en Whether enable italic.
* !#zh 是否启用斜体。
* @property {Boolean} enableItalic
*/
enableItalic: {
get () {
return !!(this._styleFlags & ITALIC_FLAG);
},
set (value) {
if (value) {
this._styleFlags |= ITALIC_FLAG;
} else {
this._styleFlags &= ~ITALIC_FLAG;
}
this.setVertsDirty();
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.label.italic'
},
/**
* !#en Whether enable underline.
* !#zh 是否启用下划线。
* @property {Boolean} enableUnderline
*/
enableUnderline: {
get () {
return !!(this._styleFlags & UNDERLINE_FLAG);
},
set (value) {
if (value) {
this._styleFlags |= UNDERLINE_FLAG;
} else {
this._styleFlags &= ~UNDERLINE_FLAG;
}
this.setVertsDirty();
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.label.underline'
},
_underlineHeight: 0,
/**
* !#en The height of underline.
* !#zh 下划线高度。
* @property {Number} underlineHeight
*/
underlineHeight: {
get () {
return this._underlineHeight;
},
set (value) {
if (this._underlineHeight === value) return;
this._underlineHeight = value;
this.setVertsDirty();
},
tooltip: CC_DEV && 'i18n:COMPONENT.label.underline_height',
},
},
statics: {
HorizontalAlign: HorizontalAlign,
VerticalAlign: VerticalAlign,
Overflow: Overflow,
CacheMode: CacheMode,
_shareAtlas: null,
/**
* !#zh 需要保证当前场景中没有使用CHAR缓存的Label才可以清除否则已渲染的文字没有重新绘制会不显示
* !#en It can be cleared that need to ensure there is not use the CHAR cache in the current scene. Otherwise, the rendered text will not be displayed without repainting.
* @method clearCharCache
* @static
*/
clearCharCache () {
if (Label._shareAtlas) {
Label._shareAtlas.clearAllCache();
}
}
},
onLoad () {
// For compatibility with v2.0.x temporary reservation.
if (this._batchAsBitmap && this.cacheMode === CacheMode.NONE) {
this.cacheMode = CacheMode.BITMAP;
this._batchAsBitmap = false;
}
if (cc.game.renderType === cc.game.RENDER_TYPE_CANVAS) {
// CacheMode is not supported in Canvas.
this.cacheMode = CacheMode.NONE;
}
},
onEnable () {
this._super();
// Keep track of Node size
this.node.on(cc.Node.EventType.SIZE_CHANGED, this._nodeSizeChanged, this);
this.node.on(cc.Node.EventType.ANCHOR_CHANGED, this.setVertsDirty, this);
this.node.on(cc.Node.EventType.COLOR_CHANGED, this._nodeColorChanged, this);
this._forceUpdateRenderData();
},
onDisable () {
this._super();
this.node.off(cc.Node.EventType.SIZE_CHANGED, this._nodeSizeChanged, this);
this.node.off(cc.Node.EventType.ANCHOR_CHANGED, this.setVertsDirty, this);
this.node.off(cc.Node.EventType.COLOR_CHANGED, this._nodeColorChanged, this);
},
onDestroy () {
this._assembler && this._assembler._resetAssemblerData && this._assembler._resetAssemblerData(this._assemblerData);
this._assemblerData = null;
this._letterTexture = null;
if (this._ttfTexture) {
this._ttfTexture.destroy();
this._ttfTexture = null;
}
this._super();
},
_nodeSizeChanged () {
// Because the content size is automatically updated when overflow is NONE.
// And this will conflict with the alignment of the CCWidget.
if (CC_EDITOR || this.overflow !== Overflow.NONE) {
this.setVertsDirty();
}
},
_nodeColorChanged () {
if (!(this.font instanceof cc.BitmapFont)) {
this.setVertsDirty();
}
},
setVertsDirty() {
if(CC_JSB && this._nativeTTF()) {
this._assembler && this._assembler.updateRenderData(this)
}
this._super();
},
_updateColor () {
if (!(this.font instanceof cc.BitmapFont)) {
if (!(this._srcBlendFactor === cc.macro.BlendFactor.SRC_ALPHA && this.node._renderFlag & cc.RenderFlow.FLAG_OPACITY)) {
this.setVertsDirty();
}
}
RenderComponent.prototype._updateColor.call(this);
},
_validateRender () {
if (!this.string) {
this.disableRender();
return;
}
if (this._materials[0]) {
let font = this.font;
if (font instanceof cc.BitmapFont) {
let spriteFrame = font.spriteFrame;
if (spriteFrame &&
spriteFrame.textureLoaded() &&
font._fntConfig) {
return;
}
}
else {
return;
}
}
this.disableRender();
},
_resetAssembler () {
this._resetFrame();
RenderComponent.prototype._resetAssembler.call(this);
},
_resetFrame () {
if (this._frame && !(this.font instanceof cc.BitmapFont)) {
deleteFromDynamicAtlas(this, this._frame);
this._frame = null;
}
},
_checkStringEmpty () {
this.markForRender(!!this.string);
},
_on3DNodeChanged () {
this._resetAssembler();
this._applyFontTexture();
},
_onBMFontTextureLoaded () {
this._frame._texture = this.font.spriteFrame._texture;
this.markForRender(true);
this._updateMaterial();
this._assembler && this._assembler.updateRenderData(this);
},
_onBlendChanged () {
if (!this.useSystemFont || !this.enabledInHierarchy) return;
this._forceUpdateRenderData();
},
_applyFontTexture () {
let font = this.font;
if (font instanceof cc.BitmapFont) {
let spriteFrame = font.spriteFrame;
this._frame = spriteFrame;
if (spriteFrame) {
spriteFrame.onTextureLoaded(this._onBMFontTextureLoaded, this);
}
}
else {
if(!this._nativeTTF()){
if (!this._frame) {
this._frame = new LabelFrame();
}
if (this.cacheMode === CacheMode.CHAR) {
this._letterTexture = this._assembler._getAssemblerData();
this._frame._refreshTexture(this._letterTexture);
} else if (!this._ttfTexture) {
this._ttfTexture = new cc.Texture2D();
this._assemblerData = this._assembler._getAssemblerData();
this._ttfTexture.initWithElement(this._assemblerData.canvas);
}
if (this.cacheMode !== CacheMode.CHAR) {
this._frame._resetDynamicAtlasFrame();
this._frame._refreshTexture(this._ttfTexture);
if (this._srcBlendFactor === cc.macro.BlendFactor.ONE && !CC_NATIVERENDERER) {
this._ttfTexture.setPremultiplyAlpha(true);
}
}
this._updateMaterial();
}
this._assembler && this._assembler.updateRenderData(this);
}
this.markForValidate();
},
_updateMaterialCanvas () {
if (!this._frame) return;
this._frame._texture._nativeUrl = this.uuid + '_texture';
},
_updateMaterialWebgl () {
let material = this.getMaterial(0);
if(this._nativeTTF()) {
if(material) this._assembler._updateTTFMaterial(this)
return;
}
if (!this._frame) return;
material && material.setProperty('texture', this._frame._texture);
BlendFunc.prototype._updateMaterial.call(this);
},
_forceUseCanvas: false,
_useNativeTTF() {
return cc.macro.ENABLE_NATIVE_TTF_RENDERER && !this._forceUseCanvas;
},
_nativeTTF() {
return this._useNativeTTF() && !!this._assembler && !!this._assembler._updateTTFMaterial;
},
_forceUpdateRenderData () {
this.setVertsDirty();
this._resetAssembler();
this._applyFontTexture();
},
/**
* @deprecated `label._enableBold` is deprecated, use `label.enableBold = true` instead please.
*/
_enableBold (enabled) {
if (CC_DEBUG) {
cc.warn('`label._enableBold` is deprecated, use `label.enableBold = true` instead please');
}
this.enableBold = !!enabled;
},
/**
* @deprecated `label._enableItalics` is deprecated, use `label.enableItalics = true` instead please.
*/
_enableItalics (enabled) {
if (CC_DEBUG) {
cc.warn('`label._enableItalics` is deprecated, use `label.enableItalics = true` instead please');
}
this.enableItalic = !!enabled;
},
/**
* @deprecated `label._enableUnderline` is deprecated, use `label.enableUnderline = true` instead please.
*/
_enableUnderline (enabled) {
if (CC_DEBUG) {
cc.warn('`label._enableUnderline` is deprecated, use `label.enableUnderline = true` instead please');
}
this.enableUnderline = !!enabled;
},
});
cc.Label = module.exports = Label;

View File

@@ -0,0 +1,115 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
/**
* !#en Outline effect used to change the display, only for system fonts or TTF fonts
* !#zh 描边效果组件,用于字体描边,只能用于系统字体
* @class LabelOutline
* @extends Component
* @example
* // Create a new node and add label components.
* var node = new cc.Node("New Label");
* var label = node.addComponent(cc.Label);
* label.string = "hello world";
* var outline = node.addComponent(cc.LabelOutline);
* node.parent = this.node;
*/
let LabelOutline = cc.Class({
name: 'cc.LabelOutline',
extends: require('./CCComponent'),
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.renderers/LabelOutline',
executeInEditMode: true,
requireComponent: cc.Label,
},
properties: {
_color: cc.Color.WHITE,
_width: 1,
/**
* !#en outline color
* !#zh 改变描边的颜色
* @property color
* @type {Color}
* @example
* outline.color = cc.Color.BLACK;
*/
color: {
tooltip: CC_DEV && 'i18n:COMPONENT.outline.color',
get: function () {
return this._color.clone();
},
set: function (value) {
if (!this._color.equals(value)) {
this._color.set(value);
}
this._updateRenderData();
}
},
/**
* !#en Change the outline width
* !#zh 改变描边的宽度
* @property width
* @type {Number}
* @example
* outline.width = 3;
*/
width: {
tooltip: CC_DEV && 'i18n:COMPONENT.outline.width',
get: function () {
return this._width;
},
set: function (value) {
if (this._width === value) return;
this._width = value;
this._updateRenderData();
},
range: [0, 512],
}
},
onEnable () {
this._updateRenderData();
},
onDisable () {
this._updateRenderData();
},
_updateRenderData () {
let label = this.node.getComponent(cc.Label);
if (label) {
label.setVertsDirty();
}
}
});
cc.LabelOutline = module.exports = LabelOutline;

View File

@@ -0,0 +1,133 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2019 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
/**
* !#en Shadow effect for Label component, only for system fonts or TTF fonts
* !#zh 用于给 Label 组件添加阴影效果,只能用于系统字体或 ttf 字体
* @class LabelShadow
* @extends Component
* @example
* // Create a new node and add label components.
* var node = new cc.Node("New Label");
* var label = node.addComponent(cc.Label);
* label.string = "hello world";
* var labelShadow = node.addComponent(cc.LabelShadow);
* node.parent = this.node;
*/
let LabelShadow = cc.Class({
name: 'cc.LabelShadow',
extends: require('./CCComponent'),
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.renderers/LabelShadow',
executeInEditMode: true,
requireComponent: cc.Label,
},
properties: {
_color: cc.Color.WHITE,
_offset: cc.v2(2, 2),
_blur: 2,
/**
* !#en The shadow color
* !#zh 阴影的颜色
* @property color
* @type {Color}
* @example
* labelShadow.color = cc.Color.YELLOW;
*/
color: {
tooltip: CC_DEV && 'i18n:COMPONENT.shadow.color',
get: function () {
return this._color.clone();
},
set: function (value) {
if (!this._color.equals(value)) {
this._color.set(value);
}
this._updateRenderData();
}
},
/**
* !#en Offset between font and shadow
* !#zh 字体与阴影的偏移
* @property offset
* @type {Vec2}
* @example
* labelShadow.offset = new cc.Vec2(2, 2);
*/
offset: {
tooltip: CC_DEV && 'i18n:COMPONENT.shadow.offset',
get: function () {
return this._offset;
},
set: function (value) {
this._offset = value;
this._updateRenderData();
}
},
/**
* !#en A non-negative float specifying the level of shadow blur
* !#zh 阴影的模糊程度
* @property blur
* @type {Number}
* @example
* labelShadow.blur = 2;
*/
blur: {
tooltip: CC_DEV && 'i18n:COMPONENT.shadow.blur',
get: function () {
return this._blur;
},
set: function (value) {
this._blur = value;
this._updateRenderData();
},
range: [0, 1024],
},
},
onEnable () {
this._updateRenderData();
},
onDisable () {
this._updateRenderData();
},
_updateRenderData () {
let label = this.node.getComponent(cc.Label);
if (label) {
label.setVertsDirty();
}
}
});
cc.LabelShadow = module.exports = LabelShadow;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,487 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
import gfx from '../../renderer/gfx';
const misc = require('../utils/misc');
const RenderComponent = require('./CCRenderComponent');
const RenderFlow = require('../renderer/render-flow');
const Graphics = require('../graphics/graphics');
import Mat4 from '../value-types/mat4';
import Vec2 from '../value-types/vec2';
import MaterialVariant from '../assets/material/material-variant';
let _vec2_temp = new Vec2();
let _mat4_temp = new Mat4();
let _circlepoints =[];
function _calculateCircle (center, radius, segements) {
_circlepoints.length = 0;
let anglePerStep = Math.PI * 2 / segements;
for (let step = 0; step < segements; ++step) {
_circlepoints.push(cc.v2(radius.x * Math.cos(anglePerStep * step) + center.x,
radius.y * Math.sin(anglePerStep * step) + center.y));
}
return _circlepoints;
}
/**
* !#en the type for mask.
* !#zh 遮罩组件类型
* @enum Mask.Type
*/
let MaskType = cc.Enum({
/**
* !#en Rect mask.
* !#zh 使用矩形作为遮罩
* @property {Number} RECT
*/
RECT: 0,
/**
* !#en Ellipse Mask.
* !#zh 使用椭圆作为遮罩
* @property {Number} ELLIPSE
*/
ELLIPSE: 1,
/**
* !#en Image Stencil Mask.
* !#zh 使用图像模版作为遮罩
* @property {Number} IMAGE_STENCIL
*/
IMAGE_STENCIL: 2,
});
const SEGEMENTS_MIN = 3;
const SEGEMENTS_MAX = 10000;
/**
* !#en The Mask Component
* !#zh 遮罩组件
* @class Mask
* @extends RenderComponent
*/
let Mask = cc.Class({
name: 'cc.Mask',
extends: RenderComponent,
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.renderers/Mask',
help: 'i18n:COMPONENT.help_url.mask',
inspector: 'packages://inspector/inspectors/comps/mask.js'
},
ctor () {
this._graphics = null;
this._enableMaterial = null;
this._exitMaterial = null;
this._clearMaterial = null;
},
properties: {
_spriteFrame: {
default: null,
type: cc.SpriteFrame
},
/**
* !#en The mask type.
* !#zh 遮罩类型
* @property type
* @type {Mask.Type}
* @example
* mask.type = cc.Mask.Type.RECT;
*/
_type: MaskType.RECT,
type: {
get: function () {
return this._type;
},
set: function (value) {
if (this._type !== value) {
this._resetAssembler();
}
this._type = value;
if (this._type !== MaskType.IMAGE_STENCIL) {
this.spriteFrame = null;
this.alphaThreshold = 0;
this._updateGraphics();
}
this._activateMaterial();
},
type: MaskType,
tooltip: CC_DEV && 'i18n:COMPONENT.mask.type',
},
/**
* !#en The mask image
* !#zh 遮罩所需要的贴图
* @property spriteFrame
* @type {SpriteFrame}
* @default null
* @example
* mask.spriteFrame = newSpriteFrame;
*/
spriteFrame: {
type: cc.SpriteFrame,
tooltip: CC_DEV && 'i18n:COMPONENT.mask.spriteFrame',
get: function () {
return this._spriteFrame;
},
set: function (value) {
let lastSprite = this._spriteFrame;
if (CC_EDITOR) {
if ((lastSprite && lastSprite._uuid) === (value && value._uuid)) {
return;
}
}
else {
if (lastSprite === value) {
return;
}
}
if (lastSprite) {
lastSprite.off('load', this.setVertsDirty, this);
}
this._spriteFrame = value;
this.setVertsDirty();
this._updateMaterial();
},
},
/**
* !#en
* The alpha threshold.(Not supported Canvas Mode) <br/>
* The content is drawn only where the stencil have pixel with alpha greater than the alphaThreshold. <br/>
* Should be a float between 0 and 1. <br/>
* This default to 0.1.
* When it's set to 1, the stencil will discard all pixels, nothing will be shown.
* !#zh
* Alpha 阈值(不支持 Canvas 模式)<br/>
* 只有当模板的像素的 alpha 大于等于 alphaThreshold 时,才会绘制内容。<br/>
* 该数值 0 ~ 1 之间的浮点数,默认值为 0.1
* 当被设置为 1 时,会丢弃所有蒙版像素,所以不会显示任何内容
* @property alphaThreshold
* @type {Number}
* @default 0.1
*/
alphaThreshold: {
default: 0.1,
type: cc.Float,
range: [0, 1, 0.1],
slide: true,
tooltip: CC_DEV && 'i18n:COMPONENT.mask.alphaThreshold',
notify: function () {
if (cc.game.renderType === cc.game.RENDER_TYPE_CANVAS) {
cc.warnID(4201);
return;
}
this._updateMaterial();
}
},
/**
* !#en Reverse mask (Not supported Canvas Mode)
* !#zh 反向遮罩(不支持 Canvas 模式)
* @property inverted
* @type {Boolean}
* @default false
*/
inverted: {
default: false,
type: cc.Boolean,
tooltip: CC_DEV && 'i18n:COMPONENT.mask.inverted',
notify: function () {
if (cc.game.renderType === cc.game.RENDER_TYPE_CANVAS) {
cc.warnID(4202);
}
}
},
/**
* TODO: remove segments, not supported by graphics
* !#en The segements for ellipse mask.
* !#zh 椭圆遮罩的曲线细分数
* @property segements
* @type {Number}
* @default 64
*/
_segments: 64,
segements: {
get: function () {
return this._segments;
},
set: function (value) {
this._segments = misc.clampf(value, SEGEMENTS_MIN, SEGEMENTS_MAX);
this._updateGraphics();
},
type: cc.Integer,
tooltip: CC_DEV && 'i18n:COMPONENT.mask.segements',
},
_resizeToTarget: {
animatable: false,
set: function (value) {
if(value) {
this._resizeNodeToTargetNode();
}
}
}
},
statics: {
Type: MaskType,
},
onRestore () {
this._activateMaterial();
},
onEnable () {
this._super();
if (this._type !== MaskType.IMAGE_STENCIL) {
this._updateGraphics();
}
else if (this._spriteFrame) {
this._spriteFrame.once('load', this.setVertsDirty, this);
}
this.node.on(cc.Node.EventType.POSITION_CHANGED, this._updateGraphics, this);
this.node.on(cc.Node.EventType.ROTATION_CHANGED, this._updateGraphics, this);
this.node.on(cc.Node.EventType.SCALE_CHANGED, this._updateGraphics, this);
this.node.on(cc.Node.EventType.SIZE_CHANGED, this._updateGraphics, this);
this.node.on(cc.Node.EventType.ANCHOR_CHANGED, this._updateGraphics, this);
},
onDisable () {
this._super();
this.node.off(cc.Node.EventType.POSITION_CHANGED, this._updateGraphics, this);
this.node.off(cc.Node.EventType.ROTATION_CHANGED, this._updateGraphics, this);
this.node.off(cc.Node.EventType.SCALE_CHANGED, this._updateGraphics, this);
this.node.off(cc.Node.EventType.SIZE_CHANGED, this._updateGraphics, this);
this.node.off(cc.Node.EventType.ANCHOR_CHANGED, this._updateGraphics, this);
this.node._renderFlag &= ~RenderFlow.FLAG_POST_RENDER;
},
onDestroy () {
this._super();
this._removeGraphics();
if (this._spriteFrame) {
this._spriteFrame.off('load', this.setVertsDirty, this);
}
},
_resizeNodeToTargetNode: CC_EDITOR && function () {
if(this.spriteFrame) {
let rect = this.spriteFrame.getRect();
this.node.setContentSize(rect.width, rect.height);
}
},
_validateRender () {
if (this._type !== MaskType.IMAGE_STENCIL) return;
let spriteFrame = this._spriteFrame;
if (spriteFrame &&
spriteFrame.textureLoaded()) {
return;
}
this.disableRender();
},
_activateMaterial () {
this._createGraphics();
// Init material
let material = this._materials[0];
if (!material) {
material = MaterialVariant.createWithBuiltin('2d-sprite', this);
}
else {
material = MaterialVariant.create(material, this);
}
material.define('USE_ALPHA_TEST', true);
// Reset material
if (this._type === MaskType.IMAGE_STENCIL) {
material.define('CC_USE_MODEL', false);
material.define('USE_TEXTURE', true);
}
else {
material.define('CC_USE_MODEL', true);
material.define('USE_TEXTURE', false);
}
if (!this._enableMaterial) {
this._enableMaterial = MaterialVariant.createWithBuiltin('2d-sprite', this);
}
if (!this._exitMaterial) {
this._exitMaterial = MaterialVariant.createWithBuiltin('2d-sprite', this);
this._exitMaterial.setStencilEnabled(gfx.STENCIL_DISABLE);
}
if (!this._clearMaterial) {
this._clearMaterial = MaterialVariant.createWithBuiltin('clear-stencil', this);
}
this.setMaterial(0, material);
this._graphics._materials[0] = material;
this._updateMaterial();
},
_updateMaterial () {
let material = this._materials[0];
if (!material) return;
if (this._type === MaskType.IMAGE_STENCIL && this.spriteFrame) {
let texture = this.spriteFrame.getTexture();
material.setProperty('texture', texture);
}
material.setProperty('alphaThreshold', this.alphaThreshold);
},
_createGraphics () {
if (!this._graphics) {
this._graphics = new Graphics();
cc.Assembler.init(this._graphics);
this._graphics.node = this.node;
this._graphics.lineWidth = 0;
this._graphics.strokeColor = cc.color(0, 0, 0, 0);
}
},
_updateGraphics () {
if (!this.enabledInHierarchy) return;
let node = this.node;
let graphics = this._graphics;
// Share render data with graphics content
graphics.clear(false);
let width = node._contentSize.width;
let height = node._contentSize.height;
let x = -width * node._anchorPoint.x;
let y = -height * node._anchorPoint.y;
if (this._type === MaskType.RECT) {
graphics.rect(x, y, width, height);
}
else if (this._type === MaskType.ELLIPSE) {
let center = cc.v2(x + width / 2, y + height / 2);
let radius = {
x: width / 2,
y: height / 2
};
let points = _calculateCircle(center, radius, this._segments);
for (let i = 0; i < points.length; ++i) {
let point = points[i];
if (i === 0) {
graphics.moveTo(point.x, point.y);
}
else {
graphics.lineTo(point.x, point.y);
}
}
graphics.close();
}
if (cc.game.renderType === cc.game.RENDER_TYPE_CANVAS) {
graphics.stroke();
}
else {
graphics.fill();
}
this.setVertsDirty();
},
_removeGraphics () {
if (this._graphics) {
this._graphics.destroy();
this._graphics._destroyImmediate(); // FIX: cocos-creator/2d-tasks#2511. TODO: cocos-creator/2d-tasks#2516
this._graphics = null;
}
},
_hitTest (cameraPt) {
let node = this.node;
let size = node.getContentSize(),
w = size.width,
h = size.height,
testPt = _vec2_temp;
node._updateWorldMatrix();
// If scale is 0, it can't be hit.
if (!Mat4.invert(_mat4_temp, node._worldMatrix)) {
return false;
}
Vec2.transformMat4(testPt, cameraPt, _mat4_temp);
testPt.x += node._anchorPoint.x * w;
testPt.y += node._anchorPoint.y * h;
let result = false;
if (this.type === MaskType.RECT || this.type === MaskType.IMAGE_STENCIL) {
result = testPt.x >= 0 && testPt.y >= 0 && testPt.x <= w && testPt.y <= h;
}
else if (this.type === MaskType.ELLIPSE) {
let rx = w / 2, ry = h / 2;
let px = testPt.x - 0.5 * w, py = testPt.y - 0.5 * h;
result = px * px / (rx * rx) + py * py / (ry * ry) < 1;
}
if (this.inverted) {
result = !result;
}
return result;
},
markForRender (enable) {
let flag = RenderFlow.FLAG_RENDER | RenderFlow.FLAG_UPDATE_RENDER_DATA | RenderFlow.FLAG_POST_RENDER;
if (enable) {
this.node._renderFlag |= flag;
this.markForValidate();
}
else if (!enable) {
this.node._renderFlag &= ~flag;
}
},
disableRender () {
this.node._renderFlag &= ~(RenderFlow.FLAG_RENDER | RenderFlow.FLAG_UPDATE_RENDER_DATA |
RenderFlow.FLAG_POST_RENDER);
},
});
cc.Mask = module.exports = Mask;

View File

@@ -0,0 +1,263 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const RenderComponent = require('../components/CCRenderComponent');
const BlendFunc = require('../../core/utils/blend-func');
/**
* !#en
* cc.MotionStreak manages a Ribbon based on it's motion in absolute space. <br/>
* You construct it with a fadeTime, minimum segment size, texture path, texture <br/>
* length and color. The fadeTime controls how long it takes each vertex in <br/>
* the streak to fade out, the minimum segment size it how many pixels the <br/>
* streak will move before adding a new ribbon segment, and the texture <br/>
* length is the how many pixels the texture is stretched across. The texture <br/>
* is vertically aligned along the streak segment.
* !#zh 运动轨迹,用于游戏对象的运动轨迹上实现拖尾渐隐效果。
* @class MotionStreak
* @extends Component
* @uses BlendFunc
*/
var MotionStreak = cc.Class({
name: 'cc.MotionStreak',
// To avoid conflict with other render component, we haven't use ComponentUnderSG,
// its implementation also requires some different approach:
// 1.Needed a parent node to make motion streak's position global related.
// 2.Need to update the position in each frame by itself because we don't know
// whether the global position have changed
extends: RenderComponent,
mixins: [BlendFunc],
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.others/MotionStreak',
help: 'i18n:COMPONENT.help_url.motionStreak',
playOnFocus: true,
executeInEditMode: true
},
ctor () {
this._points = [];
this._lastWPos = new cc.Vec2();
},
properties: {
/**
* !#en
* !#zh 在编辑器模式下预览拖尾效果。
* @property {Boolean} preview
* @default false
*/
preview: {
default: false,
editorOnly: true,
notify: CC_EDITOR && function () {
this.reset();
},
animatable: false
},
/**
* !#en The fade time to fade.
* !#zh 拖尾的渐隐时间,以秒为单位。
* @property fadeTime
* @type {Number}
* @example
* motionStreak.fadeTime = 3;
*/
_fadeTime: 1,
fadeTime: {
get () {
return this._fadeTime;
},
set (value) {
this._fadeTime = value;
this.reset();
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.motionStreak.fadeTime'
},
/**
* !#en The minimum segment size.
* !#zh 拖尾之间最小距离。
* @property minSeg
* @type {Number}
* @example
* motionStreak.minSeg = 3;
*/
_minSeg: 1,
minSeg: {
get () {
return this._minSeg;
},
set (value) {
this._minSeg = value;
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.motionStreak.minSeg'
},
/**
* !#en The stroke's width.
* !#zh 拖尾的宽度。
* @property stroke
* @type {Number}
* @example
* motionStreak.stroke = 64;
*/
_stroke: 64,
stroke: {
get () {
return this._stroke;
},
set (value) {
this._stroke = value;
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.motionStreak.stroke'
},
/**
* !#en The texture of the MotionStreak.
* !#zh 拖尾的贴图。
* @property texture
* @type {Texture2D}
* @example
* motionStreak.texture = newTexture;
*/
_texture: {
default: null,
type: cc.Texture2D
},
texture: {
get () {
return this._texture;
},
set (value) {
if (this._texture === value) return;
this._texture = value;
this._updateMaterial();
},
type: cc.Texture2D,
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.motionStreak.texture'
},
/**
* !#en The color of the MotionStreak.
* !#zh 拖尾的颜色
* @property color
* @type {Color}
* @default cc.Color.WHITE
* @example
* motionStreak.color = new cc.Color(255, 255, 255);
*/
_color: cc.Color.WHITE,
color: {
get () {
return this._color.clone();
},
set (value) {
if (!this._color.equals(value)) {
this._color.set(value);
}
},
type: cc.Color,
tooltip: CC_DEV && 'i18n:COMPONENT.motionStreak.color'
},
/**
* !#en The fast Mode.
* !#zh 是否启用了快速模式。当启用快速模式,新的点会被更快地添加,但精度较低。
* @property fastMode
* @type {Boolean}
* @default false
* @example
* motionStreak.fastMode = true;
*/
_fastMode: false,
fastMode: {
get () {
return this._fastMode;
},
set (value) {
this._fastMode = value;
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.motionStreak.fastMode'
}
},
onEnable () {
this._super();
this.reset();
},
_updateMaterial () {
let material = this.getMaterial(0);
material && material.setProperty('texture', this._texture);
BlendFunc.prototype._updateMaterial.call(this);
},
onFocusInEditor: CC_EDITOR && function () {
if (this.preview) {
this.reset();
}
},
onLostFocusInEditor: CC_EDITOR && function () {
if (this.preview) {
this.reset();
}
},
/**
* !#en Remove all living segments of the ribbon.
* !#zh 删除当前所有的拖尾片段。
* @method reset
* @example
* // Remove all living segments of the ribbon.
* myMotionStreak.reset();
*/
reset () {
this._points.length = 0;
this._assembler && this._assembler._renderData.clear();
this._lastWPos.x = 0;
this._lastWPos.y = 0;
if (CC_EDITOR) {
cc.engine.repaintInEditMode();
}
},
lateUpdate (dt) {
this._assembler && this._assembler.update(this, dt);
}
});
cc.MotionStreak = module.exports = MotionStreak;

View File

@@ -0,0 +1,627 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
/**
* !#en The Page View Size Mode
* !#zh 页面视图每个页面统一的大小类型
* @enum PageView.SizeMode
*/
var SizeMode = cc.Enum({
/**
* !#en Each page is unified in size
* !#zh 每个页面统一大小
* @property {Number} Unified
*/
Unified: 0,
/**
* !#en Each page is in free size
* !#zh 每个页面大小随意
* @property {Number} Free
*/
Free: 1
});
/**
* !#en The Page View Direction
* !#zh 页面视图滚动类型
* @enum PageView.Direction
*/
var Direction = cc.Enum({
/**
* !#en Horizontal scroll.
* !#zh 水平滚动
* @property {Number} Horizontal
*/
Horizontal: 0,
/**
* !#en Vertical scroll.
* !#zh 垂直滚动
* @property {Number} Vertical
*/
Vertical: 1
});
/**
* !#en Enum for ScrollView event type.
* !#zh 滚动视图事件类型
* @enum PageView.EventType
*/
var EventType = cc.Enum({
/**
* !#en The page turning event
* !#zh 翻页事件
* @property {Number} PAGE_TURNING
*/
PAGE_TURNING: 0
});
/**
* !#en The PageView control
* !#zh 页面视图组件
* @class PageView
* @extends ScrollView
*/
var PageView = cc.Class({
name: 'cc.PageView',
extends: cc.ScrollView,
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.ui/PageView',
help: 'i18n:COMPONENT.help_url.pageview',
inspector: 'packages://inspector/inspectors/comps/ccpageview.js',
executeInEditMode: false
},
ctor: function () {
this._curPageIdx = 0;
this._lastPageIdx = 0;
this._pages = [];
this._initContentPos = cc.v2();
this._scrollCenterOffsetX = []; // 每一个页面居中时需要的偏移量X
this._scrollCenterOffsetY = []; // 每一个页面居中时需要的偏移量Y
},
properties: {
/**
* !#en Specify the size type of each page in PageView.
* !#zh 页面视图中每个页面大小类型
* @property {PageView.SizeMode} sizeMode
*/
sizeMode: {
default: SizeMode.Unified,
type: SizeMode,
tooltip: CC_DEV && 'i18n:COMPONENT.pageview.sizeMode',
notify: function() {
this._syncSizeMode();
}
},
/**
* !#en The page view direction
* !#zh 页面视图滚动类型
* @property {PageView.Direction} direction
*/
direction: {
default: Direction.Horizontal,
type: Direction,
tooltip: CC_DEV && 'i18n:COMPONENT.pageview.direction',
notify: function() {
this._syncScrollDirection();
}
},
/**
* !#en
* The scroll threshold value, when drag exceeds this value,
* release the next page will automatically scroll, less than the restore
* !#zh 滚动临界值,默认单位百分比,当拖拽超出该数值时,松开会自动滚动下一页,小于时则还原。
* @property {Number} scrollThreshold
*/
scrollThreshold: {
default: 0.5,
type: cc.Float,
slide: true,
range: [0, 1, 0.01],
tooltip: CC_DEV && 'i18n:COMPONENT.pageview.scrollThreshold'
},
/**
* !#en
* Auto page turning velocity threshold. When users swipe the PageView quickly,
* it will calculate a velocity based on the scroll distance and time,
* if the calculated velocity is larger than the threshold, then it will trigger page turning.
* !#zh
* 快速滑动翻页临界值。
* 当用户快速滑动时,会根据滑动开始和结束的距离与时间计算出一个速度值,
* 该值与此临界值相比较,如果大于临界值,则进行自动翻页。
* @property {Number} autoPageTurningThreshold
*/
autoPageTurningThreshold: {
default: 100,
type: cc.Float,
tooltip: CC_DEV && 'i18n:COMPONENT.pageview.autoPageTurningThreshold'
},
/**
* !#en Change the PageTurning event timing of PageView.
* !#zh 设置 PageView PageTurning 事件的发送时机。
* @property {Number} pageTurningEventTiming
*/
pageTurningEventTiming: {
default: 0.1,
type: cc.Float,
range: [0, 1, 0.01],
tooltip: CC_DEV && 'i18n:COMPONENT.pageview.pageTurningEventTiming'
},
/**
* !#en The Page View Indicator
* !#zh 页面视图指示器组件
* @property {PageViewIndicator} indicator
*/
indicator: {
default: null,
type: cc.PageViewIndicator,
tooltip: CC_DEV && 'i18n:COMPONENT.pageview.indicator',
notify: function() {
if (this.indicator) {
this.indicator.setPageView(this);
}
}
},
/**
* !#en The time required to turn over a page. unit: second
* !#zh 每个页面翻页时所需时间。单位:秒
* @property {Number} pageTurningSpeed
*/
pageTurningSpeed: {
default: 0.3,
type: cc.Float,
tooltip: CC_DEV && 'i18n:COMPONENT.pageview.pageTurningSpeed'
},
/**
* !#en PageView events callback
* !#zh 滚动视图的事件回调函数
* @property {Component.EventHandler[]} pageEvents
*/
pageEvents: {
default: [],
type: cc.Component.EventHandler,
tooltip: CC_DEV && 'i18n:COMPONENT.pageview.pageEvents'
}
},
statics: {
SizeMode: SizeMode,
Direction: Direction,
EventType: EventType
},
onEnable: function () {
this._super();
this.node.on(cc.Node.EventType.SIZE_CHANGED, this._updateAllPagesSize, this);
if(!CC_EDITOR) {
this.node.on('scroll-ended-with-threshold', this._dispatchPageTurningEvent, this);
}
},
onDisable: function () {
this._super();
this.node.off(cc.Node.EventType.SIZE_CHANGED, this._updateAllPagesSize, this);
if(!CC_EDITOR) {
this.node.off('scroll-ended-with-threshold', this._dispatchPageTurningEvent, this);
}
},
onLoad: function () {
this._initPages();
if (this.indicator) {
this.indicator.setPageView(this);
}
},
/**
* !#en Returns current page index
* !#zh 返回当前页面索引
* @method getCurrentPageIndex
* @returns {Number}
*/
getCurrentPageIndex: function () {
return this._curPageIdx;
},
/**
* !#en Set current page index
* !#zh 设置当前页面索引
* @method setCurrentPageIndex
* @param {Number} index
*/
setCurrentPageIndex: function (index) {
this.scrollToPage(index, true);
},
/**
* !#en Returns all pages of pageview
* !#zh 返回视图中的所有页面
* @method getPages
* @returns {Node[]}
*/
getPages: function () {
return this._pages;
},
/**
* !#en At the end of the current page view to insert a new view
* !#zh 在当前页面视图的尾部插入一个新视图
* @method addPage
* @param {Node} page
*/
addPage: function (page) {
if (!page || this._pages.indexOf(page) !== -1 || !this.content)
return;
this.content.addChild(page);
this._pages.push(page);
this._updatePageView();
},
/**
* !#en Inserts a page in the specified location
* !#zh 将页面插入指定位置中
* @method insertPage
* @param {Node} page
* @param {Number} index
*/
insertPage: function (page, index) {
if (index < 0 || !page || this._pages.indexOf(page) !== -1 || !this.content)
return;
var pageCount = this._pages.length;
if (index >= pageCount)
this.addPage(page);
else {
this._pages.splice(index, 0, page);
this.content.addChild(page);
this._updatePageView();
}
},
/**
* !#en Removes a page from PageView.
* !#zh 移除指定页面
* @method removePage
* @param {Node} page
*/
removePage: function (page) {
if (!page || !this.content) return;
var index = this._pages.indexOf(page);
if (index === -1) {
cc.warnID(4300, page.name);
return;
}
this.removePageAtIndex(index);
},
/**
* !#en Removes a page at index of PageView.
* !#zh 移除指定下标的页面
* @method removePageAtIndex
* @param {Number} index
*/
removePageAtIndex: function (index) {
var pageList = this._pages;
if (index < 0 || index >= pageList.length) return;
var page = pageList[index];
if (!page) return;
this.content.removeChild(page);
pageList.splice(index, 1);
this._updatePageView();
},
/**
* !#en Removes all pages from PageView
* !#zh 移除所有页面
* @method removeAllPages
*/
removeAllPages: function () {
if (!this.content) { return; }
var locPages = this._pages;
for (var i = 0, len = locPages.length; i < len; i++)
this.content.removeChild(locPages[i]);
this._pages.length = 0;
this._updatePageView();
},
/**
* !#en Scroll PageView to index.
* !#zh 滚动到指定页面
* @method scrollToPage
* @param {Number} idx index of page.
* @param {Number} timeInSecond scrolling time
*/
scrollToPage: function (idx, timeInSecond) {
if (idx < 0 || idx >= this._pages.length)
return;
timeInSecond = timeInSecond !== undefined ? timeInSecond : 0.3;
this._curPageIdx = idx;
this.scrollToOffset(this._moveOffsetValue(idx), timeInSecond, true);
if (this.indicator) {
this.indicator._changedState();
}
},
//override the method of ScrollView
getScrollEndedEventTiming: function () {
return this.pageTurningEventTiming;
},
_syncScrollDirection: function () {
this.horizontal = this.direction === Direction.Horizontal;
this.vertical = this.direction === Direction.Vertical;
},
_syncSizeMode: function () {
if (!this.content) { return; }
var layout = this.content.getComponent(cc.Layout);
if (layout) {
if (this.sizeMode === SizeMode.Free && this._pages.length > 0) {
var lastPage = this._pages[this._pages.length - 1];
if (this.direction === Direction.Horizontal) {
layout.paddingLeft = (this._view.width - this._pages[0].width) / 2;
layout.paddingRight = (this._view.width - lastPage.width) / 2;
}
else if (this.direction === Direction.Vertical) {
layout.paddingTop = (this._view.height - this._pages[0].height) / 2;
layout.paddingBottom = (this._view.height - lastPage.height) / 2;
}
}
layout.updateLayout();
}
},
// 刷新页面视图
_updatePageView: function () {
// 当页面数组变化时修改 content 大小
var layout = this.content.getComponent(cc.Layout);
if (layout && layout.enabled) {
layout.updateLayout();
}
var pageCount = this._pages.length;
if (this._curPageIdx >= pageCount) {
this._curPageIdx = pageCount === 0 ? 0 : pageCount - 1;
this._lastPageIdx = this._curPageIdx;
}
// 进行排序
var contentPos = this._initContentPos;
for (var i = 0; i < pageCount; ++i) {
var page = this._pages[i];
page.setSiblingIndex(i);
if (this.direction === Direction.Horizontal) {
this._scrollCenterOffsetX[i] = Math.abs(contentPos.x + page.x);
}
else {
this._scrollCenterOffsetY[i] = Math.abs(contentPos.y + page.y);
}
}
// 刷新 indicator 信息与状态
if (this.indicator) {
this.indicator._refresh();
}
},
// 刷新所有页面的大小
_updateAllPagesSize: function () {
if (this.sizeMode !== SizeMode.Unified || !this._view) {
return;
}
var locPages = CC_EDITOR ? this.content.children : this._pages;
var selfSize = this._view.getContentSize();
for (var i = 0, len = locPages.length; i < len; i++) {
locPages[i].setContentSize(selfSize);
}
},
// 初始化页面
_initPages: function () {
if (!this.content) { return; }
this._initContentPos = this.content.position;
var children = this.content.children;
for (var i = 0; i < children.length; ++i) {
var page = children[i];
if (this._pages.indexOf(page) >= 0) { continue; }
this._pages.push(page);
}
this._syncScrollDirection();
this._syncSizeMode();
this._updatePageView();
},
_dispatchPageTurningEvent: function () {
if (this._lastPageIdx === this._curPageIdx) return;
this._lastPageIdx = this._curPageIdx;
cc.Component.EventHandler.emitEvents(this.pageEvents, this, EventType.PAGE_TURNING);
this.node.emit('page-turning', this);
},
// 是否超过自动滚动临界值
_isScrollable: function (offset, index, nextIndex) {
if (this.sizeMode === SizeMode.Free) {
var curPageCenter, nextPageCenter;
if (this.direction === Direction.Horizontal) {
curPageCenter = this._scrollCenterOffsetX[index];
nextPageCenter = this._scrollCenterOffsetX[nextIndex];
return Math.abs(offset.x) >= Math.abs(curPageCenter - nextPageCenter) * this.scrollThreshold;
}
else if (this.direction === Direction.Vertical) {
curPageCenter = this._scrollCenterOffsetY[index];
nextPageCenter = this._scrollCenterOffsetY[nextIndex];
return Math.abs(offset.y) >= Math.abs(curPageCenter - nextPageCenter) * this.scrollThreshold;
}
}
else {
if (this.direction === Direction.Horizontal) {
return Math.abs(offset.x) >= this._view.width * this.scrollThreshold;
}
else if (this.direction === Direction.Vertical) {
return Math.abs(offset.y) >= this._view.height * this.scrollThreshold;
}
}
},
// 快速滑动
_isQuicklyScrollable: function (touchMoveVelocity) {
if (this.direction === Direction.Horizontal) {
if (Math.abs(touchMoveVelocity.x) > this.autoPageTurningThreshold) {
return true;
}
}
else if (this.direction === Direction.Vertical) {
if (Math.abs(touchMoveVelocity.y) > this.autoPageTurningThreshold) {
return true;
}
}
return false;
},
// 通过 idx 获取偏移值数值
_moveOffsetValue: function (idx) {
var offset = cc.v2(0, 0);
if (this.sizeMode === SizeMode.Free) {
if (this.direction === Direction.Horizontal) {
offset.x = this._scrollCenterOffsetX[idx];
}
else if (this.direction === Direction.Vertical) {
offset.y = this._scrollCenterOffsetY[idx];
}
}
else {
if (this.direction === Direction.Horizontal) {
offset.x = idx * this._view.width;
}
else if (this.direction === Direction.Vertical) {
offset.y = idx * this._view.height;
}
}
return offset;
},
_getDragDirection: function (moveOffset) {
if (this.direction === Direction.Horizontal) {
if (moveOffset.x === 0) { return 0; }
return (moveOffset.x > 0 ? 1 : -1);
}
else if (this.direction === Direction.Vertical) {
// 由于滚动 Y 轴的原点在在右上角所以应该是小于 0
if (moveOffset.y === 0) { return 0; }
return (moveOffset.y < 0 ? 1 : -1);
}
},
_handleReleaseLogic: function(touch) {
this._autoScrollToPage();
if (this._scrolling) {
this._scrolling = false;
if (!this._autoScrolling) {
this._dispatchEvent('scroll-ended');
}
}
},
_autoScrollToPage: function () {
var bounceBackStarted = this._startBounceBackIfNeeded();
if (bounceBackStarted) {
let bounceBackAmount = this._getHowMuchOutOfBoundary();
bounceBackAmount = this._clampDelta(bounceBackAmount);
if (bounceBackAmount.x > 0 || bounceBackAmount.y < 0) {
this._curPageIdx = this._pages.length === 0 ? 0 : this._pages.length - 1;
}
if (bounceBackAmount.x < 0 || bounceBackAmount.y > 0) {
this._curPageIdx = 0;
}
if (this.indicator) {
this.indicator._changedState();
}
}
else {
var moveOffset = this._touchBeganPosition.sub(this._touchEndPosition);
var index = this._curPageIdx, nextIndex = index + this._getDragDirection(moveOffset);
var timeInSecond = this.pageTurningSpeed * Math.abs(index - nextIndex);
if (nextIndex < this._pages.length) {
if (this._isScrollable(moveOffset, index, nextIndex)) {
this.scrollToPage(nextIndex, timeInSecond);
return;
}
else {
var touchMoveVelocity = this._calculateTouchMoveVelocity();
if (this._isQuicklyScrollable(touchMoveVelocity)) {
this.scrollToPage(nextIndex, timeInSecond);
return;
}
}
}
this.scrollToPage(index, timeInSecond);
}
},
_onTouchBegan: function (event, captureListeners) {
this._touchBeganPosition = event.touch.getLocation();
this._super(event, captureListeners);
},
_onTouchMoved: function (event, captureListeners) {
this._super(event, captureListeners);
},
_onTouchEnded: function (event, captureListeners) {
this._touchEndPosition = event.touch.getLocation();
this._super(event, captureListeners);
},
_onTouchCancelled: function (event, captureListeners) {
this._touchEndPosition = event.touch.getLocation();
this._super(event, captureListeners);
},
_onMouseWheel: function () { }
});
cc.PageView = module.exports = PageView;
/**
* !#en
* Note: This event is emitted from the node to which the component belongs.
* !#zh
* 注意:此事件是从该组件所属的 Node 上面派发出来的,需要用 node.on 来监听。
* @event page-turning
* @param {Event.EventCustom} event
* @param {PageView} pageView - The PageView component.
*/

View File

@@ -0,0 +1,201 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
/**
* !#en Enum for PageView Indicator direction
* !#zh 页面视图指示器的摆放方向
* @enum PageViewIndicator.Direction
*/
var Direction = cc.Enum({
/**
* !#en The horizontal direction.
* !#zh 水平方向
* @property {Number} HORIZONTAL
*/
HORIZONTAL: 0,
/**
* !#en The vertical direction.
* !#zh 垂直方向
* @property {Number} VERTICAL
*/
VERTICAL: 1
});
/**
* !#en The Page View Indicator Component
* !#zh 页面视图每页标记组件
* @class PageViewIndicator
* @extends Component
*/
var PageViewIndicator = cc.Class({
name: 'cc.PageViewIndicator',
extends: require('./CCComponent'),
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.ui/PageViewIndicator',
help: 'i18n:COMPONENT.help_url.pageviewIndicator'
},
properties: {
_layout: null,
_pageView: null,
_indicators: [],
/**
* !#en The spriteFrame for each element.
* !#zh 每个页面标记显示的图片
* @property {SpriteFrame} spriteFrame
*/
spriteFrame: {
default: null,
type: cc.SpriteFrame,
tooltip: CC_DEV && 'i18n:COMPONENT.pageview_indicator.spriteFrame'
},
/**
* !#en The location direction of PageViewIndicator.
* !#zh 页面标记摆放方向
*@property {PageViewIndicator.Direction} direction
*/
direction: {
default: Direction.HORIZONTAL,
type: Direction,
tooltip: CC_DEV && 'i18n:COMPONENT.pageview_indicator.direction'
},
/**
* !#en The cellSize for each element.
* !#zh 每个页面标记的大小
* @property {Size} cellSize
*/
cellSize: {
default: cc.size(20, 20),
tooltip: CC_DEV && 'i18n:COMPONENT.pageview_indicator.cell_size'
},
/**
* !#en The distance between each element.
* !#zh 每个页面标记之间的边距
* @property {Number} spacing
*/
spacing: {
default: 0,
tooltip: CC_DEV && 'i18n:COMPONENT.pageview_indicator.spacing'
}
},
statics: {
Direction: Direction
},
onLoad: function () {
this._updateLayout();
},
/**
* !#en Set Page View
* !#zh 设置页面视图
* @method setPageView
* @param {PageView} target
*/
setPageView: function (target) {
this._pageView = target;
this._refresh();
},
_updateLayout: function () {
this._layout = this.getComponent(cc.Layout);
if (!this._layout) {
this._layout = this.addComponent(cc.Layout);
}
if (this.direction === Direction.HORIZONTAL) {
this._layout.type = cc.Layout.Type.HORIZONTAL;
this._layout.spacingX = this.spacing;
}
else if (this.direction === Direction.VERTICAL) {
this._layout.type = cc.Layout.Type.VERTICAL;
this._layout.spacingY = this.spacing;
}
this._layout.resizeMode = cc.Layout.ResizeMode.CONTAINER;
},
_createIndicator: function () {
var node = new cc.Node();
var sprite = node.addComponent(cc.Sprite);
sprite.spriteFrame = this.spriteFrame;
sprite.sizeMode = cc.Sprite.SizeMode.CUSTOM;
node.parent = this.node;
node.width = this.cellSize.width;
node.height = this.cellSize.height;
return node;
},
_changedState: function () {
var indicators = this._indicators;
if (indicators.length === 0) return;
var idx = this._pageView._curPageIdx;
if (idx >= indicators.length) return;
for (var i = 0; i < indicators.length; ++i) {
var node = indicators[i];
node.opacity = 255 / 2;
}
indicators[idx].opacity = 255;
},
_refresh: function () {
if (!this._pageView) { return; }
var indicators = this._indicators;
var pages = this._pageView.getPages();
if (pages.length === indicators.length) {
return;
}
var i = 0;
if (pages.length > indicators.length) {
for (i = 0; i < pages.length; ++i) {
if (!indicators[i]) {
indicators[i] = this._createIndicator();
}
}
}
else {
var count = indicators.length - pages.length;
for (i = count; i > 0; --i) {
var node = indicators[i - 1];
this.node.removeChild(node);
indicators.splice(i - 1, 1);
}
}
if(this._layout && this._layout.enabledInHierarchy) {
this._layout.updateLayout();
}
this._changedState();
}
});
cc.PageViewIndicator = module.exports = PageViewIndicator;

View File

@@ -0,0 +1,297 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const misc = require('../utils/misc');
const Component = require('./CCComponent');
/**
* !#en Enum for ProgressBar mode
* !#zh 进度条模式
* @enum ProgressBar.Mode
*/
var Mode = cc.Enum({
/**
* !#en TODO
* !#zh 水平方向模式
* @property {Number} HORIZONTAL
*/
HORIZONTAL: 0,
/**
* !#en TODO
* !#zh 垂直方向模式
* @property {Number} VERTICAL
*/
VERTICAL: 1,
/**
* !#en TODO
* !#zh 填充模式
* @property {Number} FILLED
*/
FILLED: 2,
});
/**
* !#en
* Visual indicator of progress in some operation.
* Displays a bar to the user representing how far the operation has progressed.
* !#zh
* 进度条组件,可用于显示加载资源时的进度。
* @class ProgressBar
* @extends Component
* @example
* // update progressBar
* update: function (dt) {
* var progress = progressBar.progress;
* if (progress > 0) {
* progress += dt;
* }
* else {
* progress = 1;
* }
* progressBar.progress = progress;
* }
*
*/
var ProgressBar = cc.Class({
name: 'cc.ProgressBar',
extends: Component,
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.ui/ProgressBar',
help: 'i18n:COMPONENT.help_url.progressbar',
},
_initBarSprite: function() {
if (this.barSprite) {
var entity = this.barSprite.node;
if (!entity) return;
var nodeSize = this.node.getContentSize();
var nodeAnchor = this.node.getAnchorPoint();
var entitySize = entity.getContentSize();
if(entity.parent === this.node){
this.node.setContentSize(entitySize);
}
if (this.barSprite.fillType === cc.Sprite.FillType.RADIAL) {
this.mode = Mode.FILLED;
}
var barSpriteSize = entity.getContentSize();
if (this.mode === Mode.HORIZONTAL) {
this.totalLength = barSpriteSize.width;
}
else if(this.mode === Mode.VERTICAL) {
this.totalLength = barSpriteSize.height;
}
else {
this.totalLength = this.barSprite.fillRange;
}
if(entity.parent === this.node){
var x = - nodeSize.width * nodeAnchor.x;
var y = 0;
entity.setPosition(cc.v2(x, y));
}
}
},
_updateBarStatus: function() {
if (this.barSprite) {
var entity = this.barSprite.node;
if (!entity) return;
var entityAnchorPoint = entity.getAnchorPoint();
var entitySize = entity.getContentSize();
var entityPosition = entity.getPosition();
var anchorPoint = cc.v2(0, 0.5);
var progress = misc.clamp01(this.progress);
var actualLenth = this.totalLength * progress;
var finalContentSize;
var totalWidth;
var totalHeight;
switch (this.mode) {
case Mode.HORIZONTAL:
if (this.reverse) {
anchorPoint = cc.v2(1, 0.5);
}
finalContentSize = cc.size(actualLenth, entitySize.height);
totalWidth = this.totalLength;
totalHeight = entitySize.height;
break;
case Mode.VERTICAL:
if (this.reverse) {
anchorPoint = cc.v2(0.5, 1);
} else {
anchorPoint = cc.v2(0.5, 0);
}
finalContentSize = cc.size(entitySize.width, actualLenth);
totalWidth = entitySize.width;
totalHeight = this.totalLength;
break;
}
//handling filled mode
if (this.mode === Mode.FILLED) {
if (this.barSprite.type !== cc.Sprite.Type.FILLED) {
cc.warn('ProgressBar FILLED mode only works when barSprite\'s Type is FILLED!');
} else {
if (this.reverse) {
actualLenth = actualLenth * -1;
}
this.barSprite.fillRange = actualLenth;
}
} else {
if (this.barSprite.type !== cc.Sprite.Type.FILLED) {
var anchorOffsetX = anchorPoint.x - entityAnchorPoint.x;
var anchorOffsetY = anchorPoint.y - entityAnchorPoint.y;
var finalPosition = cc.v2(totalWidth * anchorOffsetX, totalHeight * anchorOffsetY);
entity.setPosition(entityPosition.x + finalPosition.x, entityPosition.y + finalPosition.y);
entity.setAnchorPoint(anchorPoint);
entity.setContentSize(finalContentSize);
} else {
cc.warn('ProgressBar non-FILLED mode only works when barSprite\'s Type is non-FILLED!');
}
}
}
},
properties: {
/**
* !#en The targeted Sprite which will be changed progressively.
* !#zh 用来显示进度条比例的 Sprite 对象。
* @property {Sprite} barSprite
*/
barSprite: {
default: null,
type: cc.Sprite,
tooltip: CC_DEV && 'i18n:COMPONENT.progress.bar_sprite',
notify: function() {
this._initBarSprite();
},
animatable: false
},
/**
* !#en The progress mode, there are two modes supported now: horizontal and vertical.
* !#zh 进度条的模式
* @property {ProgressBar.Mode} mode
*/
mode: {
default: Mode.HORIZONTAL,
type: Mode,
tooltip: CC_DEV && 'i18n:COMPONENT.progress.mode',
notify: function() {
if (this.barSprite) {
var entity = this.barSprite.node;
if (!entity) return;
var entitySize = entity.getContentSize();
if (this.mode === Mode.HORIZONTAL) {
this.totalLength = entitySize.width;
} else if (this.mode === Mode.VERTICAL) {
this.totalLength = entitySize.height;
} else if (this.mode === Mode.FILLED) {
this.totalLength = this.barSprite.fillRange;
}
}
},
animatable: false
},
_N$totalLength: 1,
/**
* !#en The total width or height of the bar sprite.
* !#zh 进度条实际的总长度
* @property {Number} totalLength
*/
totalLength: {
range: [0, Number.MAX_VALUE],
tooltip: CC_DEV && 'i18n:COMPONENT.progress.total_length',
get: function () {
return this._N$totalLength;
},
set: function(value) {
if (this.mode === Mode.FILLED) {
value = misc.clamp01(value);
}
this._N$totalLength = value;
this._updateBarStatus();
}
},
/**
* !#en The current progress of the bar sprite. The valid value is between 0-1.
* !#zh 当前进度值,该数值的区间是 0-1 之间。
* @property {Number} progress
*/
progress: {
default: 1,
type: cc.Float,
range: [0, 1, 0.1],
slide: true,
tooltip: CC_DEV && 'i18n:COMPONENT.progress.progress',
notify: function() {
this._updateBarStatus();
}
},
/**
* !#en Whether reverse the progress direction of the bar sprite.
* !#zh 进度条是否进行反方向变化。
* @property {Boolean} reverse
*/
reverse: {
default: false,
tooltip: CC_DEV && 'i18n:COMPONENT.progress.reverse',
notify: function() {
if (this.barSprite) {
this.barSprite.fillStart = 1 - this.barSprite.fillStart;
}
this._updateBarStatus();
},
animatable: false
}
},
statics: {
Mode: Mode
}
});
cc.ProgressBar = module.exports = ProgressBar;

View File

@@ -0,0 +1,258 @@
/****************************************************************************
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
import Assembler from '../renderer/assembler';
import MaterialVariant from '../assets/material/material-variant';
import { Color } from '../value-types';
const Component = require('./CCComponent');
const RenderFlow = require('../renderer/render-flow');
const Material = require('../assets/material/CCMaterial');
let _temp_color = new Color();
/**
* !#en
* Base class for components which supports rendering features.
* !#zh
* 所有支持渲染的组件的基类
*
* @class RenderComponent
* @extends Component
*/
let RenderComponent = cc.Class({
name: 'RenderComponent',
extends: Component,
editor: CC_EDITOR && {
executeInEditMode: true,
disallowMultiple: true
},
properties: {
_materials: {
default: [],
type: Material,
},
/**
* !#en The materials used by this render component.
* !#zh 渲染组件使用的材质。
* @property {[Material]} sharedMaterials
*/
materials: {
get () {
return this._materials;
},
set (val) {
this._materials = val;
this._activateMaterial();
},
type: [Material],
displayName: 'Materials',
animatable: false
}
},
ctor () {
this._vertsDirty = true;
this._assembler = null;
},
_resetAssembler () {
Assembler.init(this);
this._updateColor();
this.setVertsDirty();
},
__preload () {
this._resetAssembler();
this._activateMaterial();
},
onEnable () {
if (this.node._renderComponent) {
this.node._renderComponent.enabled = false;
}
this.node._renderComponent = this;
this.node._renderFlag |= RenderFlow.FLAG_OPACITY_COLOR;
this.setVertsDirty();
},
onDisable () {
this.node._renderComponent = null;
this.disableRender();
},
onDestroy () {
let materials = this._materials;
for (let i = 0; i < materials.length; i++) {
cc.pool.material.put(materials[i]);
}
materials.length = 0;
cc.pool.assembler.put(this._assembler);
this.disableRender();
},
setVertsDirty () {
this._vertsDirty = true;
this.markForRender(true);
},
_on3DNodeChanged () {
this._resetAssembler();
},
_validateRender () {
},
markForValidate () {
cc.RenderFlow.registerValidate(this);
},
markForRender (enable) {
let flag = RenderFlow.FLAG_RENDER | RenderFlow.FLAG_UPDATE_RENDER_DATA;
if (enable) {
this.node._renderFlag |= flag;
this.markForValidate();
}
else {
this.node._renderFlag &= ~flag;
}
},
disableRender () {
this.node._renderFlag &= ~(RenderFlow.FLAG_RENDER | RenderFlow.FLAG_UPDATE_RENDER_DATA);
},
/**
* !#en Get the material by index.
* !#zh 根据指定索引获取材质
* @method getMaterial
* @param {Number} index
* @return {MaterialVariant}
*/
getMaterial (index) {
if (index < 0 || index >= this._materials.length) {
return null;
}
let material = this._materials[index];
if (!material) return null;
let instantiated = MaterialVariant.create(material, this);
if (instantiated !== material) {
this.setMaterial(index, instantiated);
}
return instantiated;
},
/**
* !#en Gets all the materials.
* !#zh 获取所有材质。
* @method getMaterials
* @return {[MaterialVariant]}
*/
getMaterials () {
let materials = this._materials;
for (let i = 0; i < materials.length; i++) {
materials[i] = MaterialVariant.create(materials[i], this);
}
return materials;
},
/**
* !#en Set the material by index.
* !#zh 根据指定索引设置材质
* @method setMaterial
* @param {Number} index
* @param {Material} material
* @return {Material}
*/
setMaterial (index, material) {
if (material !== this._materials[index]) {
material = MaterialVariant.create(material, this);
this._materials[index] = material;
}
this._updateMaterial();
this.markForRender(true);
return material;
},
_getDefaultMaterial () {
return Material.getBuiltinMaterial('2d-sprite');
},
/**
* Init material.
*/
_activateMaterial () {
let materials = this._materials;
if (!materials[0]) {
let material = this._getDefaultMaterial();
materials[0] = material;
}
for (let i = 0; i < materials.length; i++) {
materials[i] = MaterialVariant.create(materials[i], this);
}
this._updateMaterial();
},
/**
* Update material properties.
*/
_updateMaterial () {
},
_updateColor () {
if (this._assembler.updateColor) {
let premultiply = this.srcBlendFactor === cc.macro.BlendFactor.ONE;
premultiply && Color.premultiplyAlpha(_temp_color, this.node._color);
let color = premultiply ? _temp_color._val : null;
this._assembler.updateColor(this, color);
}
},
_checkBacth (renderer, cullingMask) {
let material = this._materials[0];
if ((material && material.getHash() !== renderer.material.getHash()) ||
renderer.cullingMask !== cullingMask) {
renderer._flush();
renderer.node = material.getDefine('CC_USE_MODEL') ? this.node : renderer._dummyNode;
renderer.material = material;
renderer.cullingMask = cullingMask;
}
}
});
cc.RenderComponent = module.exports = RenderComponent;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,111 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const Widget = require('./CCWidget');
const WidgetManager = require('../base-ui/CCWidgetManager');
/**
* !#en
* This component is used to adjust the layout of current node to respect the safe area of a notched mobile device such as the iPhone X.
* It is typically used for the top node of the UI interaction area. For specific usage, refer to the official [example-cases/02_ui/16_safeArea/SafeArea.fire](https://github.com/cocos-creator/example-cases).
*
* The concept of safe area is to give you a fixed inner rectangle in which you can safely display content that will be drawn on screen.
* You are strongly discouraged from providing controls outside of this area. But your screen background could embellish edges.
*
* This component internally uses the API `cc.sys.getSafeAreaRect();` to obtain the safe area of the current iOS or Android device,
* and implements the adaptation by using the Widget component and set anchor.
*
* !#zh
* 该组件会将所在节点的布局适配到 iPhone X 等异形屏手机的安全区域内,通常用于 UI 交互区域的顶层节点,具体用法可参考官方范例 [example-cases/02_ui/16_safeArea/SafeArea.fire](https://github.com/cocos-creator/example-cases)。
*
* 该组件内部通过 API `cc.sys.getSafeAreaRect();` 获取到当前 iOS 或 Android 设备的安全区域,并通过 Widget 组件实现适配。
*
* @class SafeArea
* @extends Component
*/
var SafeArea = cc.Class({
name: 'cc.SafeArea',
extends: require('./CCComponent'),
editor: CC_EDITOR && {
help: 'i18n:COMPONENT.help_url.safe_area',
menu: 'i18n:MAIN_MENU.component.ui/SafeArea',
inspector: 'packages://inspector/inspectors/comps/safe-area.js',
executeInEditMode: true,
requireComponent: Widget,
},
onEnable () {
this.updateArea();
cc.view.on('canvas-resize', this.updateArea, this);
},
onDisable () {
cc.view.off('canvas-resize', this.updateArea, this);
},
/**
* !#en Adapt to safe area
* !#zh 立即适配安全区域
* @method updateArea
* @example
* let safeArea = this.node.addComponent(cc.SafeArea);
* safeArea.updateArea();
*/
updateArea () {
// TODO Remove Widget dependencies in the future
let widget = this.node.getComponent(Widget);
if (!widget) {
return;
}
if (CC_EDITOR) {
widget.top = widget.bottom = widget.left = widget.right = 0;
widget.isAlignTop = widget.isAlignBottom = widget.isAlignLeft = widget.isAlignRight = true;
return;
}
// IMPORTANT: need to update alignment to get the latest position
widget.updateAlignment();
let lastPos = this.node.position;
let lastAnchorPoint = this.node.getAnchorPoint();
//
widget.isAlignTop = widget.isAlignBottom = widget.isAlignLeft = widget.isAlignRight = true;
let screenWidth = cc.winSize.width, screenHeight = cc.winSize.height;
let safeArea = cc.sys.getSafeAreaRect();
widget.top = screenHeight - safeArea.y - safeArea.height;
widget.bottom = safeArea.y;
widget.left = safeArea.x;
widget.right = screenWidth - safeArea.x - safeArea.width;
widget.updateAlignment();
// set anchor, keep the original position unchanged
let curPos = this.node.position;
let anchorX = lastAnchorPoint.x - (curPos.x - lastPos.x) / this.node.width;
let anchorY = lastAnchorPoint.y - (curPos.y - lastPos.y) / this.node.height;
this.node.setAnchorPoint(anchorX, anchorY);
// IMPORTANT: restore to lastPos even if widget is not ALWAYS
WidgetManager.add(widget);
}
});
cc.SafeArea = module.exports = SafeArea;

View File

@@ -0,0 +1,360 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const misc = require('../utils/misc');
const Component = require('./CCComponent');
var GETTINGSHORTERFACTOR = 20;
/**
* Enum for Scrollbar direction
* @enum Scrollbar.Direction
*/
var Direction = cc.Enum({
/**
* @property {Number} HORIZONTAL
*/
HORIZONTAL: 0,
/**
* @property {Number} VERTICAL
*/
VERTICAL: 1
});
/**
* !#en
* The Scrollbar control allows the user to scroll an image or other view that is too large to see completely
* !#zh 滚动条组件
* @class Scrollbar
* @extends Component
*/
var Scrollbar = cc.Class({
name: 'cc.Scrollbar',
extends: require('./CCComponent'),
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.ui/ScrollBar',
help: 'i18n:COMPONENT.help_url.scrollbar',
},
properties: {
_scrollView: null,
_touching: false,
_autoHideRemainingTime: {
default: 0,
serializable: false
},
_opacity: 255,
/**
* !#en The "handle" part of the scrollbar.
* !#zh 作为当前滚动区域位置显示的滑块 Sprite。
* @property {Sprite} handle
*/
handle: {
default: null,
type: cc.Sprite,
tooltip: CC_DEV && 'i18n:COMPONENT.scrollbar.handle',
notify: function() {
this._onScroll(cc.v2(0, 0));
},
animatable: false
},
/**
* !#en The direction of scrollbar.
* !#zh ScrollBar 的滚动方向。
* @property {Scrollbar.Direction} direction
*/
direction: {
default: Direction.HORIZONTAL,
type: Direction,
tooltip: CC_DEV && 'i18n:COMPONENT.scrollbar.direction',
notify: function() {
this._onScroll(cc.v2(0, 0));
},
animatable: false
},
/**
* !#en Whether enable auto hide or not.
* !#zh 是否在没有滚动动作时自动隐藏 ScrollBar。
* @property {Boolean} enableAutoHide
*/
enableAutoHide: {
default: true,
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.scrollbar.auto_hide',
},
/**
* !#en
* The time to hide scrollbar when scroll finished.
* Note: This value is only useful when enableAutoHide is true.
* !#zh
* 没有滚动动作后经过多久会自动隐藏。
* 注意:只要当 “enableAutoHide” 为 true 时,才有效。
* @property {Number} autoHideTime
*/
autoHideTime: {
default: 1.0,
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.scrollbar.auto_hide_time',
}
},
statics: {
Direction: Direction
},
setTargetScrollView: function(scrollView) {
this._scrollView = scrollView;
},
_convertToScrollViewSpace: function(content) {
let scrollViewNode = this._scrollView.node;
var worldSpacePos = content.convertToWorldSpaceAR(cc.v2(-content.anchorX * content.width, -content.anchorY * content.height));
var scrollViewSpacePos = scrollViewNode.convertToNodeSpaceAR(worldSpacePos);
scrollViewSpacePos.x += scrollViewNode.anchorX * scrollViewNode.width;
scrollViewSpacePos.y += scrollViewNode.anchorY * scrollViewNode.height;
return scrollViewSpacePos;
},
_setOpacity: function(opacity) {
if (this.handle) {
this.node.opacity = opacity;
this.handle.node.opacity = opacity;
}
},
_onScroll: function(outOfBoundary) {
if (this._scrollView) {
var content = this._scrollView.content;
if(content){
var contentSize = content.getContentSize();
var scrollViewSize = this._scrollView.node.getContentSize();
var handleNodeSize = this.node.getContentSize();
if(this._conditionalDisableScrollBar(contentSize, scrollViewSize)) {
return;
}
if (this.enableAutoHide) {
this._autoHideRemainingTime = this.autoHideTime;
this._setOpacity(this._opacity);
}
var contentMeasure = 0;
var scrollViewMeasure = 0;
var outOfBoundaryValue = 0;
var contentPosition = 0;
var handleNodeMeasure = 0;
if (this.direction === Direction.HORIZONTAL) {
contentMeasure = contentSize.width;
scrollViewMeasure = scrollViewSize.width;
handleNodeMeasure = handleNodeSize.width;
outOfBoundaryValue = outOfBoundary.x;
contentPosition = -this._convertToScrollViewSpace(content).x;
} else if (this.direction === Direction.VERTICAL) {
contentMeasure = contentSize.height;
scrollViewMeasure = scrollViewSize.height;
handleNodeMeasure = handleNodeSize.height;
outOfBoundaryValue = outOfBoundary.y;
contentPosition = -this._convertToScrollViewSpace(content).y;
}
var length = this._calculateLength(contentMeasure, scrollViewMeasure, handleNodeMeasure, outOfBoundaryValue);
var position = this._calculatePosition(contentMeasure, scrollViewMeasure, handleNodeMeasure, contentPosition, outOfBoundaryValue, length);
this._updateLength(length);
this._updateHanlderPosition(position);
}
}
},
_updateHanlderPosition: function(position) {
if (this.handle) {
var oldPosition = this._fixupHandlerPosition();
this.handle.node.setPosition(position.x + oldPosition.x, position.y + oldPosition.y);
}
},
_fixupHandlerPosition: function() {
var barSize = this.node.getContentSize();
var barAnchor = this.node.getAnchorPoint();
var handleSize = this.handle.node.getContentSize();
var handleParent = this.handle.node.parent;
var leftBottomWorldPosition = this.node.convertToWorldSpaceAR(cc.v2(-barSize.width * barAnchor.x, -barSize.height * barAnchor.y));
var fixupPosition = handleParent.convertToNodeSpaceAR(leftBottomWorldPosition);
if (this.direction === Direction.HORIZONTAL) {
fixupPosition = cc.v2(fixupPosition.x, fixupPosition.y + (barSize.height - handleSize.height) / 2);
} else if (this.direction === Direction.VERTICAL) {
fixupPosition = cc.v2(fixupPosition.x + (barSize.width - handleSize.width) / 2, fixupPosition.y);
}
this.handle.node.setPosition(fixupPosition);
return fixupPosition;
},
_onTouchBegan: function() {
if (!this.enableAutoHide) {
return;
}
this._touching = true;
},
_conditionalDisableScrollBar: function (contentSize, scrollViewSize) {
if(contentSize.width <= scrollViewSize.width
&& this.direction === Direction.HORIZONTAL){
return true;
}
if(contentSize.height <= scrollViewSize.height
&& this.direction === Direction.VERTICAL){
return true;
}
return false;
},
_onTouchEnded: function() {
if (!this.enableAutoHide) {
return;
}
this._touching = false;
if (this.autoHideTime <= 0) {
return;
}
if (this._scrollView) {
var content = this._scrollView.content;
if(content){
var contentSize = content.getContentSize();
var scrollViewSize = this._scrollView.node.getContentSize();
if(this._conditionalDisableScrollBar(contentSize, scrollViewSize)) {
return;
}
}
}
this._autoHideRemainingTime = this.autoHideTime;
},
_calculateLength: function(contentMeasure, scrollViewMeasure, handleNodeMeasure, outOfBoundary) {
var denominatorValue = contentMeasure;
if (outOfBoundary) {
denominatorValue += (outOfBoundary > 0 ? outOfBoundary : -outOfBoundary) * GETTINGSHORTERFACTOR;
}
var lengthRation = scrollViewMeasure / denominatorValue;
return handleNodeMeasure * lengthRation;
},
_calculatePosition: function(contentMeasure, scrollViewMeasure, handleNodeMeasure, contentPosition, outOfBoundary, actualLenth) {
var denominatorValue = contentMeasure - scrollViewMeasure;
if (outOfBoundary) {
denominatorValue += Math.abs(outOfBoundary);
}
var positionRatio = 0;
if (denominatorValue) {
positionRatio = contentPosition / denominatorValue;
positionRatio = misc.clamp01(positionRatio);
}
var position = (handleNodeMeasure - actualLenth) * positionRatio;
if (this.direction === Direction.VERTICAL) {
return cc.v2(0, position);
} else {
return cc.v2(position, 0);
}
},
_updateLength: function(length) {
if (this.handle) {
var handleNode = this.handle.node;
var handleNodeSize = handleNode.getContentSize();
handleNode.setAnchorPoint(cc.v2(0, 0));
if (this.direction === Direction.HORIZONTAL) {
handleNode.setContentSize(length, handleNodeSize.height);
} else {
handleNode.setContentSize(handleNodeSize.width, length);
}
}
},
_processAutoHide: function(deltaTime) {
if (!this.enableAutoHide || this._autoHideRemainingTime <= 0) {
return;
} else if (this._touching) {
return;
}
this._autoHideRemainingTime -= deltaTime;
if (this._autoHideRemainingTime <= this.autoHideTime) {
this._autoHideRemainingTime = Math.max(0, this._autoHideRemainingTime);
var opacity = this._opacity * (this._autoHideRemainingTime / this.autoHideTime);
this._setOpacity(opacity);
}
},
start: function() {
if (this.enableAutoHide) {
this._setOpacity(0);
}
},
hide: function() {
this._autoHideRemainingTime = 0;
this._setOpacity(0);
},
show: function() {
this._autoHideRemainingTime = this.autoHideTime;
this._setOpacity(this._opacity);
},
update: function(dt) {
this._processAutoHide(dt);
}
});
cc.Scrollbar = module.exports = Scrollbar;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,241 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const misc = require('../utils/misc');
const Component = require('./CCComponent');
/**
* !#en The Slider Direction
* !#zh 滑动器方向
* @enum Slider.Direction
*/
var Direction = cc.Enum({
/**
* !#en The horizontal direction.
* !#zh 水平方向
* @property {Number} Horizontal
*/
Horizontal: 0,
/**
* !#en The vertical direction.
* !#zh 垂直方向
* @property {Number} Vertical
*/
Vertical: 1
});
/**
* !#en The Slider Control
* !#zh 滑动器组件
* @class Slider
* @extends Component
*/
var Slider = cc.Class({
name: 'cc.Slider',
extends: Component,
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.ui/Slider',
help: 'i18n:COMPONENT.help_url.slider'
},
ctor: function () {
this._offset = cc.v2();
this._touchHandle = false;
this._dragging = false;
},
properties: {
/**
* !#en The "handle" part of the slider
* !#zh 滑动器滑块按钮部件
* @property {Button} handle
*/
handle: {
default: null,
type: cc.Button,
tooltip: CC_DEV && 'i18n:COMPONENT.slider.handle',
notify: function() {
if (CC_EDITOR && this.handle) {
this._updateHandlePosition();
}
}
},
/**
* !#en The slider direction
* !#zh 滑动器方向
* @property {Slider.Direction} direction
*/
direction: {
default: Direction.Horizontal,
type: Direction,
tooltip: CC_DEV && 'i18n:COMPONENT.slider.direction'
},
/**
* !#en The current progress of the slider. The valid value is between 0-1
* !#zh 当前进度值,该数值的区间是 0-1 之间
* @property {Number} progress
*/
progress: {
default: 0.5,
type: cc.Float,
range: [0, 1, 0.1],
slide: true,
tooltip: CC_DEV && 'i18n:COMPONENT.slider.progress',
notify: function() {
this._updateHandlePosition();
}
},
/**
* !#en The slider slide events' callback array
* !#zh 滑动器组件滑动事件回调函数数组
* @property {Component.EventHandler[]} slideEvents
*/
slideEvents: {
default: [],
type: cc.Component.EventHandler,
tooltip: CC_DEV && 'i18n:COMPONENT.slider.slideEvents'
}
},
statics: {
Direction: Direction
},
__preload: function () {
this._updateHandlePosition();
},
// 注册事件
onEnable: function () {
this.node.on(cc.Node.EventType.TOUCH_START, this._onTouchBegan, this);
this.node.on(cc.Node.EventType.TOUCH_MOVE, this._onTouchMoved, this);
this.node.on(cc.Node.EventType.TOUCH_END, this._onTouchEnded, this);
this.node.on(cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancelled, this);
if (this.handle && this.handle.isValid) {
this.handle.node.on(cc.Node.EventType.TOUCH_START, this._onHandleDragStart, this);
this.handle.node.on(cc.Node.EventType.TOUCH_MOVE, this._onTouchMoved, this);
this.handle.node.on(cc.Node.EventType.TOUCH_END, this._onTouchEnded, this);
}
},
onDisable: function() {
this.node.off(cc.Node.EventType.TOUCH_START, this._onTouchBegan, this);
this.node.off(cc.Node.EventType.TOUCH_MOVE, this._onTouchMoved, this);
this.node.off(cc.Node.EventType.TOUCH_END, this._onTouchEnded, this);
this.node.off(cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancelled, this);
if (this.handle && this.handle.isValid) {
this.handle.node.off(cc.Node.EventType.TOUCH_START, this._onHandleDragStart, this);
this.handle.node.off(cc.Node.EventType.TOUCH_MOVE, this._onTouchMoved, this);
this.handle.node.off(cc.Node.EventType.TOUCH_END, this._onTouchEnded, this);
}
},
_onHandleDragStart: function (event) {
this._dragging = true;
this._touchHandle = true;
this._offset = this.handle.node.convertToNodeSpaceAR(event.touch.getLocation());
event.stopPropagation();
},
_onTouchBegan: function (event) {
if (!this.handle) { return; }
this._dragging = true;
if (!this._touchHandle) {
this._handleSliderLogic(event.touch);
}
event.stopPropagation();
},
_onTouchMoved: function (event) {
if (!this._dragging) { return; }
this._handleSliderLogic(event.touch);
event.stopPropagation();
},
_onTouchEnded: function (event) {
this._dragging = false;
this._touchHandle = false;
this._offset = cc.v2();
event.stopPropagation();
},
_onTouchCancelled: function (event) {
this._dragging = false;
event.stopPropagation();
},
_handleSliderLogic: function (touch) {
this._updateProgress(touch);
this._emitSlideEvent();
},
_emitSlideEvent: function () {
cc.Component.EventHandler.emitEvents(this.slideEvents, this);
this.node.emit('slide', this);
},
_updateProgress: function (touch) {
if (!this.handle) { return; }
let node = this.node;
var localTouchPos = node.convertToNodeSpaceAR(touch.getLocation());
if (this.direction === Direction.Horizontal) {
this.progress = misc.clamp01((localTouchPos.x - this._offset.x + node.anchorX * node.width) / node.width);
}
else {
this.progress = misc.clamp01((localTouchPos.y - this._offset.y + node.anchorY * node.height) / node.height);
}
},
_updateHandlePosition: function () {
if (!this.handle) { return; }
var handlelocalPos;
if (this.direction === Direction.Horizontal) {
handlelocalPos = cc.v2(-this.node.width * this.node.anchorX + this.progress * this.node.width, 0);
}
else {
handlelocalPos = cc.v2(0, -this.node.height * this.node.anchorY + this.progress * this.node.height);
}
var worldSpacePos = this.node.convertToWorldSpaceAR(handlelocalPos);
this.handle.node.position = this.handle.node.parent.convertToNodeSpaceAR(worldSpacePos);
}
});
cc.Slider = module.exports = Slider;
/**
* !#en
* Note: This event is emitted from the node to which the component belongs.
* !#zh
* 注意:此事件是从该组件所属的 Node 上面派发出来的,需要用 node.on 来监听。
* @event slide
* @param {Event.EventCustom} event
* @param {Slider} slider - The slider component.
*/

View File

@@ -0,0 +1,580 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const misc = require('../utils/misc');
const NodeEvent = require('../CCNode').EventType;
const RenderComponent = require('./CCRenderComponent');
const BlendFunc = require('../utils/blend-func');
/**
* !#en Enum for sprite type.
* !#zh Sprite 类型
* @enum Sprite.Type
*/
var SpriteType = cc.Enum({
/**
* !#en The simple type.
* !#zh 普通类型
* @property {Number} SIMPLE
*/
SIMPLE: 0,
/**
* !#en The sliced type.
* !#zh 切片(九宫格)类型
* @property {Number} SLICED
*/
SLICED: 1,
/**
* !#en The tiled type.
* !#zh 平铺类型
* @property {Number} TILED
*/
TILED: 2,
/**
* !#en The filled type.
* !#zh 填充类型
* @property {Number} FILLED
*/
FILLED: 3,
/**
* !#en The mesh type.
* !#zh 以 Mesh 三角形组成的类型
* @property {Number} MESH
*/
MESH: 4
});
/**
* !#en Enum for fill type.
* !#zh 填充类型
* @enum Sprite.FillType
*/
var FillType = cc.Enum({
/**
* !#en The horizontal fill.
* !#zh 水平方向填充
* @property {Number} HORIZONTAL
*/
HORIZONTAL: 0,
/**
* !#en The vertical fill.
* !#zh 垂直方向填充
* @property {Number} VERTICAL
*/
VERTICAL: 1,
/**
* !#en The radial fill.
* !#zh 径向填充
* @property {Number} RADIAL
*/
RADIAL:2,
});
/**
* !#en Sprite Size can track trimmed size, raw size or none.
* !#zh 精灵尺寸调整模式
* @enum Sprite.SizeMode
*/
var SizeMode = cc.Enum({
/**
* !#en Use the customized node size.
* !#zh 使用节点预设的尺寸
* @property {Number} CUSTOM
*/
CUSTOM: 0,
/**
* !#en Match the trimmed size of the sprite frame automatically.
* !#zh 自动适配为精灵裁剪后的尺寸
* @property {Number} TRIMMED
*/
TRIMMED: 1,
/**
* !#en Match the raw size of the sprite frame automatically.
* !#zh 自动适配为精灵原图尺寸
* @property {Number} RAW
*/
RAW: 2
});
/**
* !#en Sprite state can choice the normal or grayscale.
* !#zh 精灵颜色通道模式。
* @enum Sprite.State
* @deprecated
*/
var State = cc.Enum({
/**
* !#en The normal state
* !#zh 正常状态
* @property {Number} NORMAL
*/
NORMAL: 0,
/**
* !#en The gray state, all color will be modified to grayscale value.
* !#zh 灰色状态,所有颜色会被转换成灰度值
* @property {Number} GRAY
*/
GRAY: 1
});
/**
* !#en Renders a sprite in the scene.
* !#zh 该组件用于在场景中渲染精灵。
* @class Sprite
* @extends RenderComponent
* @uses BlendFunc
* @example
* // Create a new node and add sprite components.
* var node = new cc.Node("New Sprite");
* var sprite = node.addComponent(cc.Sprite);
* node.parent = this.node;
*/
var Sprite = cc.Class({
name: 'cc.Sprite',
extends: RenderComponent,
mixins: [BlendFunc],
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.renderers/Sprite',
help: 'i18n:COMPONENT.help_url.sprite',
inspector: 'packages://inspector/inspectors/comps/sprite.js',
},
properties: {
_spriteFrame: {
default: null,
type: cc.SpriteFrame
},
_type: SpriteType.SIMPLE,
_sizeMode: SizeMode.TRIMMED,
_fillType: 0,
_fillCenter: cc.v2(0,0),
_fillStart: 0,
_fillRange: 0,
_isTrimmedMode: true,
_atlas: {
default: null,
type: cc.SpriteAtlas,
tooltip: CC_DEV && 'i18n:COMPONENT.sprite.atlas',
editorOnly: true,
visible: true,
animatable: false
},
/**
* !#en The sprite frame of the sprite.
* !#zh 精灵的精灵帧
* @property spriteFrame
* @type {SpriteFrame}
* @example
* sprite.spriteFrame = newSpriteFrame;
*/
spriteFrame: {
get () {
return this._spriteFrame;
},
set (value, force) {
var lastSprite = this._spriteFrame;
if (CC_EDITOR) {
if (!force && ((lastSprite && lastSprite._uuid) === (value && value._uuid))) {
return;
}
}
else {
if (lastSprite === value) {
return;
}
}
this._spriteFrame = value;
this._applySpriteFrame(lastSprite);
if (CC_EDITOR) {
this.node.emit('spriteframe-changed', this);
}
},
type: cc.SpriteFrame,
},
/**
* !#en The sprite render type.
* !#zh 精灵渲染类型
* @property type
* @type {Sprite.Type}
* @example
* sprite.type = cc.Sprite.Type.SIMPLE;
*/
type: {
get () {
return this._type;
},
set (value) {
if (this._type !== value) {
this._type = value;
this.setVertsDirty();
this._resetAssembler();
}
},
type: SpriteType,
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.sprite.type',
},
/**
* !#en
* The fill type, This will only have any effect if the "type" is set to “cc.Sprite.Type.FILLED”.
* !#zh
* 精灵填充类型,仅渲染类型设置为 cc.Sprite.Type.FILLED 时有效。
* @property fillType
* @type {Sprite.FillType}
* @example
* sprite.fillType = cc.Sprite.FillType.HORIZONTAL;
*/
fillType : {
get () {
return this._fillType;
},
set (value) {
if (value !== this._fillType) {
this._fillType = value;
this.setVertsDirty();
this._resetAssembler();
}
},
type: FillType,
tooltip: CC_DEV && 'i18n:COMPONENT.sprite.fill_type'
},
/**
* !#en
* The fill Center, This will only have any effect if the "type" is set to “cc.Sprite.Type.FILLED”.
* !#zh
* 填充中心点,仅渲染类型设置为 cc.Sprite.Type.FILLED 时有效。
* @property fillCenter
* @type {Vec2}
* @example
* sprite.fillCenter = new cc.Vec2(0, 0);
*/
fillCenter: {
get () {
return this._fillCenter;
},
set (value) {
this._fillCenter.x = value.x;
this._fillCenter.y = value.y;
if (this._type === SpriteType.FILLED) {
this.setVertsDirty();
}
},
tooltip: CC_DEV && 'i18n:COMPONENT.sprite.fill_center',
},
/**
* !#en
* The fill Start, This will only have any effect if the "type" is set to “cc.Sprite.Type.FILLED”.
* !#zh
* 填充起始点,仅渲染类型设置为 cc.Sprite.Type.FILLED 时有效。
* @property fillStart
* @type {Number}
* @example
* // -1 To 1 between the numbers
* sprite.fillStart = 0.5;
*/
fillStart: {
get () {
return this._fillStart;
},
set (value) {
this._fillStart = misc.clampf(value, -1, 1);
if (this._type === SpriteType.FILLED) {
this.setVertsDirty();
}
},
tooltip: CC_DEV && 'i18n:COMPONENT.sprite.fill_start'
},
/**
* !#en
* The fill Range, This will only have any effect if the "type" is set to “cc.Sprite.Type.FILLED”.
* !#zh
* 填充范围,仅渲染类型设置为 cc.Sprite.Type.FILLED 时有效。
* @property fillRange
* @type {Number}
* @example
* // -1 To 1 between the numbers
* sprite.fillRange = 1;
*/
fillRange: {
get () {
return this._fillRange;
},
set (value) {
this._fillRange = misc.clampf(value, -1, 1);
if (this._type === SpriteType.FILLED) {
this.setVertsDirty();
}
},
tooltip: CC_DEV && 'i18n:COMPONENT.sprite.fill_range'
},
/**
* !#en specify the frame is trimmed or not.
* !#zh 是否使用裁剪模式
* @property trim
* @type {Boolean}
* @example
* sprite.trim = true;
*/
trim: {
get () {
return this._isTrimmedMode;
},
set (value) {
if (this._isTrimmedMode !== value) {
this._isTrimmedMode = value;
if (this._type === SpriteType.SIMPLE || this._type === SpriteType.MESH) {
this.setVertsDirty();
}
if (CC_EDITOR) {
this.node.emit('trim-changed', this);
}
}
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.sprite.trim'
},
/**
* !#en specify the size tracing mode.
* !#zh 精灵尺寸调整模式
* @property sizeMode
* @type {Sprite.SizeMode}
* @example
* sprite.sizeMode = cc.Sprite.SizeMode.CUSTOM;
*/
sizeMode: {
get () {
return this._sizeMode;
},
set (value) {
this._sizeMode = value;
if (value !== SizeMode.CUSTOM) {
this._applySpriteSize();
}
},
animatable: false,
type: SizeMode,
tooltip: CC_DEV && 'i18n:COMPONENT.sprite.size_mode'
}
},
statics: {
FillType: FillType,
Type: SpriteType,
SizeMode: SizeMode,
State: State,
},
setVisible (visible) {
this.enabled = visible;
},
/**
* Change the state of sprite.
* @method setState
* @see `Sprite.State`
* @param state {Sprite.State} NORMAL or GRAY State.
* @deprecated
*/
setState () {},
/**
* Gets the current state.
* @method getState
* @see `Sprite.State`
* @return {Sprite.State}
* @deprecated
*/
getState () {},
__preload () {
this._super();
CC_EDITOR && this.node.on(NodeEvent.SIZE_CHANGED, this._resizedInEditor, this);
this._applySpriteFrame();
},
onEnable () {
this._super();
this._spriteFrame && this._spriteFrame.isValid && this._spriteFrame.ensureLoadTexture();
this.node.on(cc.Node.EventType.SIZE_CHANGED, this.setVertsDirty, this);
this.node.on(cc.Node.EventType.ANCHOR_CHANGED, this.setVertsDirty, this);
},
onDisable () {
this._super();
this.node.off(cc.Node.EventType.SIZE_CHANGED, this.setVertsDirty, this);
this.node.off(cc.Node.EventType.ANCHOR_CHANGED, this.setVertsDirty, this);
},
onRestore: CC_EDITOR && function () {
// Because undo/redo will not call onEnable/onDisable,
// we need call onEnable/onDisable manually to active/disactive children nodes.
if (this.enabledInHierarchy) {
this.node._renderComponent = null;
this.onEnable();
}
else {
this.onDisable();
}
},
_updateMaterial () {
let texture = null;
if (this._spriteFrame) {
texture = this._spriteFrame.getTexture();
}
// make sure material is belong to self.
let material = this.getMaterial(0);
if (material) {
let oldDefine = material.getDefine('USE_TEXTURE');
if (oldDefine !== undefined && !oldDefine) {
material.define('USE_TEXTURE', true);
}
let textureImpl = texture && texture.getImpl();
if (material.getProperty('texture') !== textureImpl) {
material.setProperty('texture', texture);
}
}
BlendFunc.prototype._updateMaterial.call(this);
},
_applyAtlas: CC_EDITOR && function (spriteFrame) {
// Set atlas
if (spriteFrame && spriteFrame.isValid && spriteFrame._atlasUuid) {
var self = this;
cc.assetManager.loadAny(spriteFrame._atlasUuid, function (err, asset) {
self._atlas = asset;
});
} else {
this._atlas = null;
}
},
_validateRender () {
let spriteFrame = this._spriteFrame;
if (this._materials[0] &&
spriteFrame &&
spriteFrame.textureLoaded()) {
return;
}
this.disableRender();
},
_applySpriteSize () {
if (!this.isValid || !this._spriteFrame || !this._spriteFrame.isValid) {
return;
}
if (SizeMode.RAW === this._sizeMode) {
var size = this._spriteFrame._originalSize;
this.node.setContentSize(size);
} else if (SizeMode.TRIMMED === this._sizeMode) {
var rect = this._spriteFrame._rect;
this.node.setContentSize(rect.width, rect.height);
}
this.setVertsDirty();
},
_applySpriteFrame (oldFrame) {
if (!this.isValid) return;
let oldTexture = oldFrame && oldFrame.isValid && oldFrame.getTexture();
if (oldTexture && !oldTexture.loaded) {
oldFrame.off('load', this._applySpriteSize, this);
}
let spriteFrame = this._spriteFrame;
const frameValid = spriteFrame && spriteFrame.isValid;
let newTexture = frameValid && spriteFrame.getTexture();
if (oldTexture !== newTexture) {
this._updateMaterial();
}
if (newTexture && newTexture.loaded) {
this._applySpriteSize();
}
else {
this.disableRender();
if (frameValid) {
spriteFrame.once('load', this._applySpriteSize, this);
}
}
if (CC_EDITOR) {
// Set atlas
this._applyAtlas(spriteFrame);
}
},
});
if (CC_EDITOR) {
Sprite.prototype._resizedInEditor = function () {
if (this._spriteFrame && this._spriteFrame.isValid) {
var actualSize = this.node.getContentSize();
var expectedW = actualSize.width;
var expectedH = actualSize.height;
if (this._sizeMode === SizeMode.RAW) {
var size = this._spriteFrame.getOriginalSize();
expectedW = size.width;
expectedH = size.height;
} else if (this._sizeMode === SizeMode.TRIMMED) {
var rect = this._spriteFrame.getRect();
expectedW = rect.width;
expectedH = rect.height;
}
if (expectedW !== actualSize.width || expectedH !== actualSize.height) {
this._sizeMode = SizeMode.CUSTOM;
}
}
};
// override onDestroy
Sprite.prototype.__superOnDestroy = RenderComponent.prototype.onDestroy;
Sprite.prototype.onDestroy = function () {
if (this.__superOnDestroy) this.__superOnDestroy();
this.node.off(NodeEvent.SIZE_CHANGED, this._resizedInEditor, this);
};
}
cc.Sprite = module.exports = Sprite;

View File

@@ -0,0 +1,305 @@
/****************************************************************************
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
var ComponentType = cc.Enum({
NONE : 0,
CHECKBOX : 1,
TEXT_ATLAS : 2,
SLIDER_BAR : 3,
LIST_VIEW : 4,
PAGE_VIEW : 5
});
var ListDirection = cc.Enum({
VERTICAL : 0,
HORIZONTAL: 1
});
var VerticalAlign = cc.Enum({
TOP : 0,
CENTER: 1,
BOTTOM: 2
});
var HorizontalAlign = cc.Enum({
LEFT : 0,
CENTER: 1,
RIGHT: 2
});
var StudioComponent = cc.Class({
name: 'cc.StudioComponent',
extends: cc.Component,
editor: CC_EDITOR && {
inspector: 'unpack://engine-dev/extensions/cocostudio/editor/studio-component.js'
},
properties: CC_EDITOR && {
_type : ComponentType.NONE,
type : {
get: function () {
return this._type;
},
set: function (value) {
this._type = value;
},
readonly: true,
type: ComponentType
},
// props for checkbox
_checkNormalBackFrame: null,
checkNormalBackFrame: {
get: function () {
return this._checkNormalBackFrame;
},
set: function (value) {
this._checkNormalBackFrame = value;
},
readonly: true,
type: cc.SpriteFrame
},
_checkPressedBackFrame: null,
checkPressedBackFrame: {
get: function () {
return this._checkPressedBackFrame;
},
set: function (value) {
this._checkPressedBackFrame = value;
},
readonly: true,
type: cc.SpriteFrame
},
_checkDisableBackFrame: null,
checkDisableBackFrame: {
get: function () {
return this._checkDisableBackFrame;
},
set: function (value) {
this._checkDisableBackFrame = value;
},
readonly: true,
type: cc.SpriteFrame
},
_checkNormalFrame: null,
checkNormalFrame: {
get: function () {
return this._checkNormalFrame;
},
set: function (value) {
this._checkNormalFrame = value;
},
readonly: true,
type: cc.SpriteFrame
},
_checkDisableFrame: null,
checkDisableFrame: {
get: function () {
return this._checkDisableFrame;
},
set: function (value) {
this._checkDisableFrame = value;
},
readonly: true,
type: cc.SpriteFrame
},
checkInteractable : {
readonly: true,
default: true
},
isChecked : {
readonly: true,
default: true
},
// props for TextAtlas
_atlasFrame: null,
atlasFrame: {
get: function () {
return this._atlasFrame;
},
set: function (value) {
this._atlasFrame = value;
},
readonly: true,
type: cc.SpriteFrame
},
firstChar: {
readonly: true,
default: '.'
},
charWidth: {
readonly: true,
default: 0
},
charHeight: {
readonly: true,
default: 0
},
string: {
readonly: true,
default: ''
},
// props for SliderBar
_sliderBackFrame: null,
sliderBackFrame : {
get: function () {
return this._sliderBackFrame;
},
set: function (value) {
this._sliderBackFrame = value;
},
readonly: true,
type: cc.SpriteFrame
},
_sliderBarFrame: null,
sliderBarFrame : {
get: function () {
return this._sliderBarFrame;
},
set: function (value) {
this._sliderBarFrame = value;
},
readonly: true,
type: cc.SpriteFrame
},
_sliderBtnNormalFrame: null,
sliderBtnNormalFrame : {
get: function () {
return this._sliderBtnNormalFrame;
},
set: function (value) {
this._sliderBtnNormalFrame = value;
},
readonly: true,
type: cc.SpriteFrame
},
_sliderBtnPressedFrame: null,
sliderBtnPressedFrame : {
get: function () {
return this._sliderBtnPressedFrame;
},
set: function (value) {
this._sliderBtnPressedFrame = value;
},
readonly: true,
type: cc.SpriteFrame
},
_sliderBtnDisabledFrame: null,
sliderBtnDisabledFrame : {
get: function () {
return this._sliderBtnDisabledFrame;
},
set: function (value) {
this._sliderBtnDisabledFrame = value;
},
readonly: true,
type: cc.SpriteFrame
},
sliderInteractable : {
readonly: true,
default: true
},
sliderProgress: {
default: 0.5,
readonly: true,
type: cc.Float,
range: [0, 1, 0.1]
},
// props for ListView
listInertia: {
readonly: true,
default: true,
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.scrollview.inertia',
},
listDirection: {
readonly: true,
default: ListDirection.VERTICAL,
type: ListDirection
},
listHorizontalAlign: {
readonly: true,
default: HorizontalAlign.LEFT,
type: HorizontalAlign
},
listVerticalAlign: {
readonly: true,
default: VerticalAlign.TOP,
type: VerticalAlign
},
listPadding: {
readonly: true,
default: 0
}
},
statics : {
ComponentType : ComponentType,
ListDirection : ListDirection,
VerticalAlign : VerticalAlign,
HorizontalAlign : HorizontalAlign
},
});
cc.StudioComponent = module.exports = StudioComponent;
var StudioWidget = cc.Class({
name: 'cc.StudioWidget',
extends: cc.Widget,
editor: CC_EDITOR && {
inspector: 'packages://inspector/inspectors/comps/ccwidget.js',
},
_validateTargetInDEV () {}
});
cc.StudioWidget = module.exports = StudioWidget;

View File

@@ -0,0 +1,246 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const GraySpriteState = require('../utils/gray-sprite-state');
/**
* !#en The toggle component is a CheckBox, when it used together with a ToggleGroup, it
* could be treated as a RadioButton.
* !#zh Toggle 是一个 CheckBox当它和 ToggleGroup 一起使用的时候,可以变成 RadioButton。
* @class Toggle
* @extends Button
* @uses GraySpriteState
*/
let Toggle = cc.Class({
name: 'cc.Toggle',
extends: require('./CCButton'),
mixins: [GraySpriteState],
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.ui/Toggle',
help: 'i18n:COMPONENT.help_url.toggle',
inspector: 'packages://inspector/inspectors/comps/toggle.js',
},
properties: {
/**
* !#en When this value is true, the check mark component will be enabled, otherwise
* the check mark component will be disabled.
* !#zh 如果这个设置为 true则 check mark 组件会处于 enabled 状态,否则处于 disabled 状态。
* @property {Boolean} isChecked
*/
_N$isChecked: true,
isChecked: {
get: function () {
return this._N$isChecked;
},
set: function (value) {
if (value === this._N$isChecked) {
return;
}
var group = this.toggleGroup || this._toggleContainer;
if (group && group.enabled && this._N$isChecked) {
if (!group.allowSwitchOff) {
return;
}
}
this._N$isChecked = value;
this._updateCheckMark();
if (group && group.enabled) {
group.updateToggles(this);
}
if (cc.Toggle._triggerEventInScript_isChecked) {
this._emitToggleEvents();
}
},
tooltip: CC_DEV && 'i18n:COMPONENT.toggle.isChecked',
},
/**
* !#en The toggle group which the toggle belongs to, when it is null, the toggle is a CheckBox.
* Otherwise, the toggle is a RadioButton.
* !#zh Toggle 所属的 ToggleGroup这个属性是可选的。如果这个属性为 null则 Toggle 是一个 CheckBox
* 否则Toggle 是一个 RadioButton。
* @property {ToggleGroup} toggleGroup
*/
toggleGroup: {
default: null,
tooltip: CC_DEV && 'i18n:COMPONENT.toggle.toggleGroup',
type: require('./CCToggleGroup')
},
/**
* !#en The image used for the checkmark.
* !#zh Toggle 处于选中状态时显示的图片
* @property {Sprite} checkMark
*/
checkMark: {
default: null,
type: cc.Sprite,
tooltip: CC_DEV && 'i18n:COMPONENT.toggle.checkMark'
},
/**
* !#en If Toggle is clicked, it will trigger event's handler
* !#zh Toggle 按钮的点击事件列表。
* @property {Component.EventHandler[]} checkEvents
*/
checkEvents: {
default: [],
type: cc.Component.EventHandler
},
_resizeToTarget: {
animatable: false,
set: function (value) {
if (value) {
this._resizeNodeToTargetNode();
}
}
},
},
statics: {
_triggerEventInScript_check: false,
_triggerEventInScript_isChecked: false,
},
onEnable: function () {
this._super();
if (!CC_EDITOR) {
this._registerToggleEvent();
}
if (this.toggleGroup && this.toggleGroup.enabledInHierarchy) {
this.toggleGroup.addToggle(this);
}
},
onDisable: function () {
this._super();
if (!CC_EDITOR) {
this._unregisterToggleEvent();
}
if (this.toggleGroup && this.toggleGroup.enabledInHierarchy) {
this.toggleGroup.removeToggle(this);
}
},
_hideCheckMark () {
this._N$isChecked = false;
this._updateCheckMark();
},
toggle: function (event) {
this.isChecked = !this.isChecked;
if (!cc.Toggle._triggerEventInScript_isChecked && (cc.Toggle._triggerEventInScript_check || event)) {
this._emitToggleEvents();
}
},
/**
* !#en Make the toggle button checked.
* !#zh 使 toggle 按钮处于选中状态
* @method check
*/
check: function () {
this.isChecked = true;
if (!cc.Toggle._triggerEventInScript_isChecked && cc.Toggle._triggerEventInScript_check) {
this._emitToggleEvents();
}
},
/**
* !#en Make the toggle button unchecked.
* !#zh 使 toggle 按钮处于未选中状态
* @method uncheck
*/
uncheck: function () {
this.isChecked = false;
if (!cc.Toggle._triggerEventInScript_isChecked && cc.Toggle._triggerEventInScript_check) {
this._emitToggleEvents();
}
},
_updateCheckMark: function () {
if (this.checkMark) {
this.checkMark.node.active = !!this.isChecked;
}
},
_updateDisabledState: function () {
this._super();
if (this.enableAutoGrayEffect && this.checkMark) {
let useGrayMaterial = !this.interactable;
this._switchGrayMaterial(useGrayMaterial, this.checkMark);
}
},
_registerToggleEvent: function () {
this.node.on('click', this.toggle, this);
},
_unregisterToggleEvent: function () {
this.node.off('click', this.toggle, this);
},
_emitToggleEvents: function () {
this.node.emit('toggle', this);
if (this.checkEvents) {
cc.Component.EventHandler.emitEvents(this.checkEvents, this);
}
}
});
cc.Toggle = module.exports = Toggle;
const js = require('../platform/js');
js.get(Toggle.prototype, '_toggleContainer',
function () {
let parent = this.node.parent;
if (cc.Node.isNode(parent)) {
return parent.getComponent(cc.ToggleContainer);
}
return null;
}
);
/**
* !#en
* Note: This event is emitted from the node to which the component belongs.
* !#zh
* 注意:此事件是从该组件所属的 Node 上面派发出来的,需要用 node.on 来监听。
* @event toggle
* @param {Event.EventCustom} event
* @param {Toggle} toggle - The Toggle component.
*/

View File

@@ -0,0 +1,137 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
/**
* !#en ToggleContainer is not a visiable UI component but a way to modify the behavior of a set of Toggles. <br/>
* Toggles that belong to the same group could only have one of them to be switched on at a time.<br/>
* Note: All the first layer child node containing the toggle component will auto be added to the container
* !#zh ToggleContainer 不是一个可见的 UI 组件,它可以用来修改一组 Toggle 组件的行为。<br/>
* 当一组 Toggle 属于同一个 ToggleContainer 的时候,任何时候只能有一个 Toggle 处于选中状态。<br/>
* 注意:所有包含 Toggle 组件的一级子节点都会自动被添加到该容器中
* @class ToggleContainer
* @extends Component
*/
var ToggleContainer = cc.Class({
name: 'cc.ToggleContainer',
extends: cc.Component,
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.ui/ToggleContainer',
help: 'i18n:COMPONENT.help_url.toggleContainer',
executeInEditMode: true
},
properties: {
/**
* !#en If this setting is true, a toggle could be switched off and on when pressed.
* If it is false, it will make sure there is always only one toggle could be switched on
* and the already switched on toggle can't be switched off.
* !#zh 如果这个设置为 true 那么 toggle 按钮在被点击的时候可以反复地被选中和未选中。
* @property {Boolean} allowSwitchOff
*/
allowSwitchOff: {
tooltip: CC_DEV && 'i18n:COMPONENT.toggle_group.allowSwitchOff',
default: false
},
/**
* !#en If Toggle is clicked, it will trigger event's handler
* !#zh Toggle 按钮的点击事件列表。
* @property {Component.EventHandler[]} checkEvents
*/
checkEvents: {
default: [],
type: cc.Component.EventHandler
},
},
updateToggles: function (toggle) {
if(!this.enabledInHierarchy) return;
if (toggle.isChecked) {
this.toggleItems.forEach(function (item) {
if (item !== toggle && item.isChecked && item.enabled) {
item._hideCheckMark();
}
});
if (this.checkEvents) {
cc.Component.EventHandler.emitEvents(this.checkEvents, toggle);
}
}
},
_allowOnlyOneToggleChecked: function () {
var isChecked = false;
this.toggleItems.forEach(function (item) {
if (isChecked) {
item._hideCheckMark();
}
else if (item.isChecked) {
isChecked = true;
}
});
return isChecked;
},
_makeAtLeastOneToggleChecked: function () {
var isChecked = this._allowOnlyOneToggleChecked();
if (!isChecked && !this.allowSwitchOff) {
var toggleItems = this.toggleItems;
if (toggleItems.length > 0) {
toggleItems[0].check();
}
}
},
onEnable: function () {
this._makeAtLeastOneToggleChecked();
this.node.on('child-added', this._allowOnlyOneToggleChecked, this);
this.node.on('child-removed', this._makeAtLeastOneToggleChecked, this);
},
onDisable: function () {
this.node.off('child-added', this._allowOnlyOneToggleChecked, this);
this.node.off('child-removed', this._makeAtLeastOneToggleChecked, this);
},
});
/**
* !#en Read only property, return the toggle items array reference managed by ToggleContainer.
* !#zh 只读属性,返回 ToggleContainer 管理的 toggle 数组引用
* @property {Toggle[]} toggleItems
*/
var js = require('../platform/js');
js.get(ToggleContainer.prototype, 'toggleItems',
function () {
return this.node._children.map(function (item) {
return item.getComponent(cc.Toggle);
}).filter(Boolean);
}
);
cc.ToggleContainer = module.exports = ToggleContainer;

View File

@@ -0,0 +1,139 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
/**
* !#en ToggleGroup is not a visiable UI component but a way to modify the behavior of a set of Toggles.
* Toggles that belong to the same group could only have one of them to be switched on at a time.
* !#zh ToggleGroup 不是一个可见的 UI 组件,它可以用来修改一组 Toggle 组件的行为。当一组 Toggle 属于同一个 ToggleGroup 的时候,
* 任何时候只能有一个 Toggle 处于选中状态。
* @class ToggleGroup
* @extends Component
*/
var ToggleGroup = cc.Class({
name: 'cc.ToggleGroup',
extends: cc.Component,
ctor: function () {
this._toggleItems = [];
},
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.ui/ToggleGroup (Legacy)',
help: 'i18n:COMPONENT.help_url.toggleGroup'
},
properties: {
/**
* !#en If this setting is true, a toggle could be switched off and on when pressed.
* If it is false, it will make sure there is always only one toggle could be switched on
* and the already switched on toggle can't be switched off.
* !#zh 如果这个设置为 true 那么 toggle 按钮在被点击的时候可以反复地被选中和未选中。
* @property {Boolean} allowSwitchOff
*/
allowSwitchOff: {
tooltip: CC_DEV && 'i18n:COMPONENT.toggle_group.allowSwitchOff',
default: false
},
/**
* !#en Read only property, return the toggle items array reference managed by toggleGroup.
* !#zh 只读属性,返回 toggleGroup 管理的 toggle 数组引用
* @property {Array} toggleItems
*/
toggleItems: {
get: function () {
return this._toggleItems;
}
}
},
updateToggles: function (toggle) {
if(!this.enabledInHierarchy) return;
this._toggleItems.forEach(function (item){
if(toggle.isChecked) {
if (item !== toggle && item.isChecked && item.enabled) {
item._hideCheckMark();
}
}
});
},
addToggle: function (toggle) {
var index = this._toggleItems.indexOf(toggle);
if (index === -1) {
this._toggleItems.push(toggle);
}
this._allowOnlyOneToggleChecked();
},
removeToggle: function (toggle) {
var index = this._toggleItems.indexOf(toggle);
if(index > -1) {
this._toggleItems.splice(index, 1);
}
this._makeAtLeastOneToggleChecked();
},
_allowOnlyOneToggleChecked: function () {
var isChecked = false;
this._toggleItems.forEach(function (item) {
if(isChecked && item.enabled) {
item._hideCheckMark();
}
if (item.isChecked && item.enabled) {
isChecked = true;
}
});
return isChecked;
},
_makeAtLeastOneToggleChecked: function () {
var isChecked = this._allowOnlyOneToggleChecked();
if(!isChecked && !this.allowSwitchOff) {
if(this._toggleItems.length > 0) {
this._toggleItems[0].isChecked = true;
}
}
},
start: function () {
this._makeAtLeastOneToggleChecked();
}
});
var js = require('../platform/js');
var showed = false;
js.get(cc, 'ToggleGroup', function () {
if (!showed) {
cc.errorID(1405, 'cc.ToggleGroup', 'cc.ToggleContainer');
showed = true;
}
return ToggleGroup;
});
module.exports = ToggleGroup;

View File

@@ -0,0 +1,49 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
/**
* !#en
* Handling touch events in a ViewGroup takes special care,
* because it's common for a ViewGroup to have children that are targets for different touch events than the ViewGroup itself.
* To make sure that each view correctly receives the touch events intended for it,
* ViewGroup should register capture phase event and handle the event propagation properly.
* Please refer to Scrollview for more information.
*
* !#zh
* ViewGroup的事件处理比较特殊因为 ViewGroup 里面的子节点关心的事件跟 ViewGroup 本身可能不一样。
* 为了让子节点能够正确地处理事件ViewGroup 需要注册 capture 阶段的事件,并且合理地处理 ViewGroup 之间的事件传递。
* 请参考 ScrollView 的实现来获取更多信息。
* @class ViewGroup
* @extends Component
*/
var ViewGroup = cc.Class({
name: 'cc.ViewGroup',
extends: require('./CCComponent')
});
cc.ViewGroup = module.exports = ViewGroup;

View File

@@ -0,0 +1,697 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
var WidgetManager = require('../base-ui/CCWidgetManager');
/**
* !#en Enum for Widget's alignment mode, indicating when the widget should refresh.
* !#zh Widget 的对齐模式,表示 Widget 应该何时刷新。
* @enum Widget.AlignMode
*/
/**
* !#en
* Only align once when the Widget is enabled for the first time.
* This will allow the script or animation to continue controlling the current node.
* It will only be aligned once before the end of frame when onEnable is called,
* then immediately disables the Widget.
* !#zh
* 仅在 Widget 第一次激活时对齐一次,便于脚本或动画继续控制当前节点。
* 开启后会在 onEnable 时所在的那一帧结束前对齐一次,然后立刻禁用该 Widget。
* @property {Number} ONCE
*/
/**
* !#en Align first from the beginning as ONCE, and then realign it every time the window is resized.
* !#zh 一开始会像 ONCE 一样对齐一次,之后每当窗口大小改变时还会重新对齐。
* @property {Number} ON_WINDOW_RESIZE
*/
/**
* !#en Keep aligning all the way.
* !#zh 始终保持对齐。
* @property {Number} ALWAYS
*/
var AlignMode = WidgetManager.AlignMode;
var AlignFlags = WidgetManager._AlignFlags;
var TOP = AlignFlags.TOP;
var MID = AlignFlags.MID;
var BOT = AlignFlags.BOT;
var LEFT = AlignFlags.LEFT;
var CENTER = AlignFlags.CENTER;
var RIGHT = AlignFlags.RIGHT;
var TOP_BOT = TOP | BOT;
var LEFT_RIGHT = LEFT | RIGHT;
/**
* !#en
* Stores and manipulate the anchoring based on its parent.
* Widget are used for GUI but can also be used for other things.
* Widget will adjust current node's position and size automatically, but the results after adjustment can not be obtained until the next frame unless you call {{#crossLink "Widget/updateAlignment:method"}}{{/crossLink}} manually.
* !#zh
* Widget 组件用于设置和适配其相对于父节点的边距Widget 通常被用于 UI 界面,也可以用于其他地方。
* Widget 会自动调整当前节点的坐标和宽高,不过目前调整后的结果要到下一帧才能在脚本里获取到,除非你先手动调用 {{#crossLink "Widget/updateAlignment:method"}}{{/crossLink}}。
*
* @class Widget
* @extends Component
*/
var Widget = cc.Class({
name: 'cc.Widget', extends: require('./CCComponent'),
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.ui/Widget',
help: 'i18n:COMPONENT.help_url.widget',
inspector: 'packages://inspector/inspectors/comps/ccwidget.js',
executeInEditMode: true,
disallowMultiple: true,
},
properties: {
/**
* !#en Specifies an alignment target that can only be one of the parent nodes of the current node.
* The default value is null, and when null, indicates the current parent.
* !#zh 指定一个对齐目标,只能是当前节点的其中一个父节点,默认为空,为空时表示当前父节点。
* @property {Node} target
* @default null
*/
target: {
get: function () {
return this._target;
},
set: function (value) {
this._target = value;
if (CC_EDITOR && !cc.engine._isPlaying && this.node._parent) {
// adjust the offsets to keep the size and position unchanged after target chagned
WidgetManager.updateOffsetsToStayPut(this);
}
},
type: cc.Node,
tooltip: CC_DEV && 'i18n:COMPONENT.widget.target',
},
// ENABLE ALIGN ?
/**
* !#en Whether to align the top.
* !#zh 是否对齐上边。
* @property isAlignTop
* @type {Boolean}
* @default false
*/
isAlignTop: {
get: function () {
return (this._alignFlags & TOP) > 0;
},
set: function (value) {
this._setAlign(TOP, value);
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.widget.align_top',
},
/**
* !#en
* Vertically aligns the midpoint, This will open the other vertical alignment options cancel.
* !#zh
* 是否垂直方向对齐中点,开启此项会将垂直方向其他对齐选项取消。
* @property isAlignVerticalCenter
* @type {Boolean}
* @default false
*/
isAlignVerticalCenter: {
get: function () {
return (this._alignFlags & MID) > 0;
},
set: function (value) {
if (value) {
this.isAlignTop = false;
this.isAlignBottom = false;
this._alignFlags |= MID;
}
else {
this._alignFlags &= ~MID;
}
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.widget.align_v_center',
},
/**
* !#en Whether to align the bottom.
* !#zh 是否对齐下边。
* @property isAlignBottom
* @type {Boolean}
* @default false
*/
isAlignBottom: {
get: function () {
return (this._alignFlags & BOT) > 0;
},
set: function (value) {
this._setAlign(BOT, value);
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.widget.align_bottom',
},
/**
* !#en Whether to align the left.
* !#zh 是否对齐左边
* @property isAlignLeft
* @type {Boolean}
* @default false
*/
isAlignLeft: {
get: function () {
return (this._alignFlags & LEFT) > 0;
},
set: function (value) {
this._setAlign(LEFT, value);
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.widget.align_left',
},
/**
* !#en
* Horizontal aligns the midpoint. This will open the other horizontal alignment options canceled.
* !#zh
* 是否水平方向对齐中点,开启此选项会将水平方向其他对齐选项取消。
* @property isAlignHorizontalCenter
* @type {Boolean}
* @default false
*/
isAlignHorizontalCenter: {
get: function () {
return (this._alignFlags & CENTER) > 0;
},
set: function (value) {
if (value) {
this.isAlignLeft = false;
this.isAlignRight = false;
this._alignFlags |= CENTER;
}
else {
this._alignFlags &= ~CENTER;
}
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.widget.align_h_center',
},
/**
* !#en Whether to align the right.
* !#zh 是否对齐右边。
* @property isAlignRight
* @type {Boolean}
* @default false
*/
isAlignRight: {
get: function () {
return (this._alignFlags & RIGHT) > 0;
},
set: function (value) {
this._setAlign(RIGHT, value);
},
animatable: false,
tooltip: CC_DEV && 'i18n:COMPONENT.widget.align_right',
},
/**
* !#en
* Whether the stretched horizontally, when enable the left and right alignment will be stretched horizontally,
* the width setting is invalid (read only).
* !#zh
* 当前是否水平拉伸。当同时启用左右对齐时,节点将会被水平拉伸,此时节点的宽度只读。
* @property isStretchWidth
* @type {Boolean}
* @default false
* @readOnly
*/
isStretchWidth: {
get: function () {
return (this._alignFlags & LEFT_RIGHT) === LEFT_RIGHT;
},
visible: false
},
/**
* !#en
* Whether the stretched vertically, when enable the left and right alignment will be stretched vertically,
* then height setting is invalid (read only)
* !#zh
* 当前是否垂直拉伸。当同时启用上下对齐时,节点将会被垂直拉伸,此时节点的高度只读。
* @property isStretchHeight
* @type {Boolean}
* @default false
* @readOnly
*/
isStretchHeight: {
get: function () {
return (this._alignFlags & TOP_BOT) === TOP_BOT;
},
visible: false
},
// ALIGN MARGINS
/**
* !#en
* The margins between the top of this node and the top of parent node,
* the value can be negative, Only available in 'isAlignTop' open.
* !#zh
* 本节点顶边和父节点顶边的距离,可填写负值,只有在 isAlignTop 开启时才有作用。
* @property top
* @type {Number}
* @default 0
*/
top: {
get: function () {
return this._top;
},
set: function (value) {
this._top = value;
},
tooltip: CC_DEV && 'i18n:COMPONENT.widget.top',
},
/**
* !#en
* The margins between the bottom of this node and the bottom of parent node,
* the value can be negative, Only available in 'isAlignBottom' open.
* !#zh
* 本节点底边和父节点底边的距离,可填写负值,只有在 isAlignBottom 开启时才有作用。
* @property bottom
* @type {Number}
* @default 0
*/
bottom: {
get: function () {
return this._bottom;
},
set: function (value) {
this._bottom = value;
},
tooltip: CC_DEV && 'i18n:COMPONENT.widget.bottom',
},
/**
* !#en
* The margins between the left of this node and the left of parent node,
* the value can be negative, Only available in 'isAlignLeft' open.
* !#zh
* 本节点左边和父节点左边的距离,可填写负值,只有在 isAlignLeft 开启时才有作用。
* @property left
* @type {Number}
* @default 0
*/
left: {
get: function () {
return this._left;
},
set: function (value) {
this._left = value;
},
tooltip: CC_DEV && 'i18n:COMPONENT.widget.left',
},
/**
* !#en
* The margins between the right of this node and the right of parent node,
* the value can be negative, Only available in 'isAlignRight' open.
* !#zh
* 本节点右边和父节点右边的距离,可填写负值,只有在 isAlignRight 开启时才有作用。
* @property right
* @type {Number}
* @default 0
*/
right: {
get: function () {
return this._right;
},
set: function (value) {
this._right = value;
},
tooltip: CC_DEV && 'i18n:COMPONENT.widget.right',
},
/**
* !#en
* Horizontal aligns the midpoint offset value,
* the value can be negative, Only available in 'isAlignHorizontalCenter' open.
* !#zh 水平居中的偏移值,可填写负值,只有在 isAlignHorizontalCenter 开启时才有作用。
* @property horizontalCenter
* @type {Number}
* @default 0
*/
horizontalCenter: {
get: function () {
return this._horizontalCenter;
},
set: function (value) {
this._horizontalCenter = value;
},
tooltip: CC_DEV && 'i18n:COMPONENT.widget.horizontal_center',
},
/**
* !#en
* Vertical aligns the midpoint offset value,
* the value can be negative, Only available in 'isAlignVerticalCenter' open.
* !#zh 垂直居中的偏移值,可填写负值,只有在 isAlignVerticalCenter 开启时才有作用。
* @property verticalCenter
* @type {Number}
* @default 0
*/
verticalCenter: {
get: function () {
return this._verticalCenter;
},
set: function (value) {
this._verticalCenter = value;
},
tooltip: CC_DEV && 'i18n:COMPONENT.widget.vertical_center',
},
// PARCENTAGE OR ABSOLUTE
/**
* !#en If true, horizontalCenter is pixel margin, otherwise is percentage (0 - 1) margin.
* !#zh 如果为 true"horizontalCenter" 将会以像素作为偏移值反之为百分比0 到 1
* @property isAbsoluteHorizontalCenter
* @type {Boolean}
* @default true
*/
isAbsoluteHorizontalCenter: {
get: function () {
return this._isAbsHorizontalCenter;
},
set: function (value) {
this._isAbsHorizontalCenter = value;
},
animatable: false
},
/**
* !#en If true, verticalCenter is pixel margin, otherwise is percentage (0 - 1) margin.
* !#zh 如果为 true"verticalCenter" 将会以像素作为偏移值反之为百分比0 到 1
* @property isAbsoluteVerticalCenter
* @type {Boolean}
* @default true
*/
isAbsoluteVerticalCenter: {
get: function () {
return this._isAbsVerticalCenter;
},
set: function (value) {
this._isAbsVerticalCenter = value;
},
animatable: false
},
/**
* !#en
* If true, top is pixel margin, otherwise is percentage (0 - 1) margin relative to the parent's height.
* !#zh
* 如果为 true"top" 将会以像素作为边距否则将会以相对父物体高度的百分比0 到 1作为边距。
* @property isAbsoluteTop
* @type {Boolean}
* @default true
*/
isAbsoluteTop: {
get: function () {
return this._isAbsTop;
},
set: function (value) {
this._isAbsTop = value;
},
animatable: false
},
/**
* !#en
* If true, bottom is pixel margin, otherwise is percentage (0 - 1) margin relative to the parent's height.
* !#zh
* 如果为 true"bottom" 将会以像素作为边距否则将会以相对父物体高度的百分比0 到 1作为边距。
* @property isAbsoluteBottom
* @type {Boolean}
* @default true
*/
isAbsoluteBottom: {
get: function () {
return this._isAbsBottom;
},
set: function (value) {
this._isAbsBottom = value;
},
animatable: false
},
/**
* !#en
* If true, left is pixel margin, otherwise is percentage (0 - 1) margin relative to the parent's width.
* !#zh
* 如果为 true"left" 将会以像素作为边距否则将会以相对父物体宽度的百分比0 到 1作为边距。
* @property isAbsoluteLeft
* @type {Boolean}
* @default true
*/
isAbsoluteLeft: {
get: function () {
return this._isAbsLeft;
},
set: function (value) {
this._isAbsLeft = value;
},
animatable: false
},
/**
* !#en
* If true, right is pixel margin, otherwise is percentage (0 - 1) margin relative to the parent's width.
* !#zh
* 如果为 true"right" 将会以像素作为边距否则将会以相对父物体宽度的百分比0 到 1作为边距。
* @property isAbsoluteRight
* @type {Boolean}
* @default true
*/
isAbsoluteRight: {
get: function () {
return this._isAbsRight;
},
set: function (value) {
this._isAbsRight = value;
},
animatable: false
},
/**
* !#en Specifies the alignment mode of the Widget, which determines when the widget should refresh.
* !#zh 指定 Widget 的对齐模式,用于决定 Widget 应该何时刷新。
* @property {Widget.AlignMode} alignMode
* @example
* widget.alignMode = cc.Widget.AlignMode.ON_WINDOW_RESIZE;
*/
alignMode: {
default: AlignMode.ON_WINDOW_RESIZE,
type: AlignMode,
tooltip: CC_DEV && 'i18n:COMPONENT.widget.align_mode',
},
//
_wasAlignOnce: {
default: undefined,
formerlySerializedAs: 'isAlignOnce',
},
_target: null,
/**
* !#zh: 对齐开关,由 AlignFlags 组成
*
* @property _alignFlags
* @type {Number}
* @default 0
* @private
*/
_alignFlags: 0,
_left: 0,
_right: 0,
_top: 0,
_bottom: 0,
_verticalCenter: 0,
_horizontalCenter: 0,
_isAbsLeft: true,
_isAbsRight: true,
_isAbsTop: true,
_isAbsBottom: true,
_isAbsHorizontalCenter: true,
_isAbsVerticalCenter: true,
// original size before align
_originalWidth: 0,
_originalHeight: 0
},
statics: {
AlignMode: AlignMode,
},
onLoad: function () {
if (this._wasAlignOnce !== undefined) {
// migrate for old version
this.alignMode = this._wasAlignOnce ? AlignMode.ONCE : AlignMode.ALWAYS;
this._wasAlignOnce = undefined;
}
},
onEnable: function () {
WidgetManager.add(this);
},
onDisable: function () {
WidgetManager.remove(this);
},
_validateTargetInDEV: CC_DEV && function () {
var target = this._target;
if (target) {
var isParent = this.node !== target && this.node.isChildOf(target);
if (!isParent) {
cc.errorID(6500);
this._target = null;
}
}
},
_setAlign: function (flag, isAlign) {
var current = (this._alignFlags & flag) > 0;
if (isAlign === current) {
return;
}
var isHorizontal = (flag & LEFT_RIGHT) > 0;
if (isAlign) {
this._alignFlags |= flag;
if (isHorizontal) {
this.isAlignHorizontalCenter = false;
if (this.isStretchWidth) {
// become stretch
this._originalWidth = this.node.width;
// test check conflict
if (CC_EDITOR && !cc.engine.isPlaying) {
_Scene.DetectConflict.checkConflict_Widget(this);
}
}
}
else {
this.isAlignVerticalCenter = false;
if (this.isStretchHeight) {
// become stretch
this._originalHeight = this.node.height;
// test check conflict
if (CC_EDITOR && !cc.engine.isPlaying) {
_Scene.DetectConflict.checkConflict_Widget(this);
}
}
}
if (CC_EDITOR && !cc.engine._isPlaying && this.node._parent) {
// adjust the offsets to keep the size and position unchanged after alignment chagned
WidgetManager.updateOffsetsToStayPut(this, flag);
}
}
else {
if (isHorizontal) {
if (this.isStretchWidth) {
// will cancel stretch
this.node.width = this._originalWidth;
}
}
else {
if (this.isStretchHeight) {
// will cancel stretch
this.node.height = this._originalHeight;
}
}
this._alignFlags &= ~flag;
}
},
/**
* !#en
* Immediately perform the widget alignment. You need to manually call this method only if
* you need to get the latest results after the alignment before the end of current frame.
* !#zh
* 立刻执行 widget 对齐操作。这个接口一般不需要手工调用。
* 只有当你需要在当前帧结束前获得 widget 对齐后的最新结果时才需要手动调用这个方法。
*
* @method updateAlignment
*
* @example
* widget.top = 10; // change top margin
* cc.log(widget.node.y); // not yet changed
* widget.updateAlignment();
* cc.log(widget.node.y); // changed
*/
updateAlignment: function () {
WidgetManager.updateAlignment(this.node);
},
});
/**
* !#en
* When turned on, it will only be aligned once at the end of the onEnable frame,
* then immediately disables the current component.
* This will allow the script or animation to continue controlling the current node.
* Note: It will still be aligned at the frame when onEnable is called.
* !#zh
* 开启后仅会在 onEnable 的当帧结束时对齐一次,然后立刻禁用当前组件。
* 这样便于脚本或动画继续控制当前节点。
* 注意onEnable 时所在的那一帧仍然会进行对齐。
* @property {Boolean} isAlignOnce
* @default false
* @deprecated
*/
Object.defineProperty(Widget.prototype, 'isAlignOnce', {
get () {
if (CC_DEBUG) {
cc.warn('`widget.isAlignOnce` is deprecated, use `widget.alignMode === cc.Widget.AlignMode.ONCE` instead please.');
}
return this.alignMode === AlignMode.ONCE;
},
set (value) {
if (CC_DEBUG) {
cc.warn('`widget.isAlignOnce` is deprecated, use `widget.alignMode = cc.Widget.AlignMode.*` instead please.');
}
this.alignMode = value ? AlignMode.ONCE : AlignMode.ALWAYS;
}
});
cc.Widget = module.exports = Widget;

View File

@@ -0,0 +1,258 @@
/****************************************************************************
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const Component = require('./CCComponent');
/**
* !#en SubContextView is a view component which controls open data context viewport in minigame platform.<br/>
* The component's node size decide the viewport of the sub context content in main context,
* the entire sub context texture will be scaled to the node's bounding box area.<br/>
* This component provides multiple important features:<br/>
* 1. Sub context could use its own resolution size and policy.<br/>
* 2. Sub context could be minized to smallest size it needed.<br/>
* 3. Resolution of sub context content could be increased.<br/>
* 4. User touch input is transformed to the correct viewport.<br/>
* 5. Texture update is handled by this component. User don't need to worry.<br/>
* One important thing to be noted, whenever the node's bounding box change,
* !#zh SubContextView 可以用来控制小游戏平台开放数据域在主域中的视窗的位置。<br/>
* 这个组件的节点尺寸决定了开放数据域内容在主域中的尺寸,整个开放数据域会被缩放到节点的包围盒范围内。<br/>
* 在这个组件的控制下,用户可以更自由得控制开放数据域:<br/>
* 1. 子域中可以使用独立的设计分辨率和适配模式<br/>
* 2. 子域区域尺寸可以缩小到只容纳内容即可<br/>
* 3. 子域的分辨率也可以被放大,以便获得更清晰的显示效果<br/>
* 4. 用户输入坐标会被自动转换到正确的子域视窗中<br/>
* 5. 子域内容贴图的更新由组件负责,用户不需要处理<br/>
* @class SubContextView
* @extends Component
*/
let SubContextView = cc.Class({
name: 'cc.SubContextView',
extends: Component,
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.others/SubContextView',
help: 'i18n:COMPONENT.help_url.subcontext_view',
},
properties: {
_firstlyEnabled: true,
_fps: 60,
fps: {
get () {
return this._fps;
},
set (value) {
if (this._fps === value) {
return;
}
this._fps = value;
this._updateInterval = 1 / value;
this._updateSubContextFrameRate();
},
tooltip: CC_DEV && 'i18n:COMPONENT.subcontext_view.fps'
}
},
ctor () {
this._sprite = null;
this._tex = new cc.Texture2D();
this._tex._packable = false;
this._context = null;
this._updatedTime = performance.now();
this._updateInterval = 0;
},
onLoad () {
// Setup subcontext canvas size
if (window.__globalAdapter && __globalAdapter.getOpenDataContext) {
this._updateInterval = 1000 / this._fps;
this._context = __globalAdapter.getOpenDataContext();
this.reset();
let sharedCanvas = this._context.canvas;
this._tex.setPremultiplyAlpha(true);
this._tex.initWithElement(sharedCanvas);
this._tex._packable = false;
this._sprite = this.node.getComponent(cc.Sprite);
if (!this._sprite) {
this._sprite = this.node.addComponent(cc.Sprite);
this._sprite.srcBlendFactor = cc.macro.BlendFactor.ONE;
}
this._sprite.spriteFrame = new cc.SpriteFrame(this._tex);
}
else {
this.enabled = false;
}
},
/**
* !#en Reset open data context size and viewport
* !#zh 重置开放数据域的尺寸和视窗
* @method reset
*/
reset () {
if (this._context) {
this.updateSubContextViewport();
let sharedCanvas = this._context.canvas;
if (sharedCanvas) {
sharedCanvas.width = this.node.width;
sharedCanvas.height = this.node.height;
}
}
},
onEnable () {
if (this._firstlyEnabled && this._context) {
this._context.postMessage({
fromEngine: true,
event: 'boot',
});
this._firstlyEnabled = false;
}
else {
this._runSubContextMainLoop();
}
this._registerNodeEvent();
this._updateSubContextFrameRate();
this.updateSubContextViewport();
},
onDisable () {
this._unregisterNodeEvent();
this._stopSubContextMainLoop();
},
update (dt) {
let calledUpdateMannually = (dt === undefined);
if (calledUpdateMannually) {
this._context && this._context.postMessage({
fromEngine: true,
event: 'step',
});
this._updateSubContextTexture();
return;
}
let now = performance.now();
let deltaTime = (now - this._updatedTime);
if (deltaTime >= this._updateInterval) {
this._updatedTime += this._updateInterval;
this._updateSubContextTexture();
}
},
_updateSubContextTexture () {
if (!this._tex || !this._context) {
return;
}
this._tex.initWithElement(this._context.canvas);
this._tex._packable = false;
this._sprite._activateMaterial();
},
/**
* !#en Update the sub context viewport manually, it should be called whenever the node's bounding box changes.
* !#zh 更新开放数据域相对于主域的 viewport这个函数应该在节点包围盒改变时手动调用。
* @method updateSubContextViewport
*/
updateSubContextViewport () {
if (this._context) {
let box = this.node.getBoundingBoxToWorld();
let sx = cc.view._scaleX;
let sy = cc.view._scaleY;
this._context.postMessage({
fromEngine: true,
event: 'viewport',
x: box.x * sx + cc.view._viewportRect.x,
y: box.y * sy + cc.view._viewportRect.y,
width: box.width * sx,
height: box.height * sy
});
}
},
_registerNodeEvent () {
this.node.on('position-changed', this.updateSubContextViewport, this);
this.node.on('scale-changed', this.updateSubContextViewport, this);
this.node.on('size-changed', this.updateSubContextViewport, this);
},
_unregisterNodeEvent () {
this.node.off('position-changed', this.updateSubContextViewport, this);
this.node.off('scale-changed', this.updateSubContextViewport, this);
this.node.off('size-changed', this.updateSubContextViewport, this);
},
_runSubContextMainLoop () {
if (this._context) {
this._context.postMessage({
fromEngine: true,
event: 'mainLoop',
value: true,
});
}
},
_stopSubContextMainLoop () {
if (this._context) {
this._context.postMessage({
fromEngine: true,
event: 'mainLoop',
value: false,
});
}
},
_updateSubContextFrameRate () {
if (this._context) {
this._context.postMessage({
fromEngine: true,
event: 'frameRate',
value: this._fps,
});
}
},
});
cc.SubContextView = module.exports = SubContextView;
/**
* !#en WXSubContextView is deprecated since v2.4.1, please use SubContextView instead.
* !#zh 自 v2.4.1 起WXSubContextView 已经废弃,请使用 SubContextView
* @class WXSubContextView
* @extends Component
* @deprecated since v2.4.1
*/
cc.WXSubContextView = SubContextView;
/**
* !#en SwanSubContextView is deprecated since v2.4.1, please use SubContextView instead.
* !#zh 自 v2.4.1 起SwanSubContextView 已经废弃,请使用 SubContextView
* @class SwanSubContextView
* @extends Component
* @deprecated since v2.4.1
*/
cc.SwanSubContextView = SubContextView;

View File

@@ -0,0 +1,905 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
const macro = require('../../platform/CCMacro');
const EditBoxImplBase = require('../editbox/EditBoxImplBase');
const Label = require('../CCLabel');
const Types = require('./types');
const InputMode = Types.InputMode;
const InputFlag = Types.InputFlag;
const KeyboardReturnType = Types.KeyboardReturnType;
function capitalize (string) {
return string.replace(/(?:^|\s)\S/g, function(a) { return a.toUpperCase(); });
}
function capitalizeFirstLetter (string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
/**
* !#en cc.EditBox is a component for inputing text, you can use it to gather small amounts of text from users.
* !#zh EditBox 组件,用于获取用户的输入文本。
* @class EditBox
* @extends Component
*/
let EditBox = cc.Class({
name: 'cc.EditBox',
extends: cc.Component,
editor: CC_EDITOR && {
menu: 'i18n:MAIN_MENU.component.ui/EditBox',
inspector: 'packages://inspector/inspectors/comps/cceditbox.js',
help: 'i18n:COMPONENT.help_url.editbox',
executeInEditMode: true,
},
properties: {
_string: '',
/**
* !#en Input string of EditBox.
* !#zh 输入框的初始输入内容,如果为空则会显示占位符的文本。
* @property {String} string
*/
string: {
tooltip: CC_DEV && 'i18n:COMPONENT.editbox.string',
get () {
return this._string;
},
set(value) {
value = '' + value;
if (this.maxLength >= 0 && value.length >= this.maxLength) {
value = value.slice(0, this.maxLength);
}
this._string = value;
this._updateString(value);
}
},
/**
* !#en The Label component attached to the node for EditBox's input text label
* !#zh 输入框输入文本节点上挂载的 Label 组件对象
* @property {Label} textLabel
*/
textLabel: {
tooltip: CC_DEV && 'i18n:COMPONENT.editbox.textLabel',
default: null,
type: Label,
notify (oldValue) {
if (this.textLabel && this.textLabel !== oldValue) {
this._updateTextLabel();
this._updateLabels();
}
},
},
/**
* !#en The Label component attached to the node for EditBox's placeholder text label
* !#zh 输入框占位符节点上挂载的 Label 组件对象
* @property {Label} placeholderLabel
*/
placeholderLabel: {
tooltip: CC_DEV && 'i18n:COMPONENT.editbox.placeholderLabel',
default: null,
type: Label,
notify (oldValue) {
if (this.placeholderLabel && this.placeholderLabel !== oldValue) {
this._updatePlaceholderLabel();
this._updateLabels();
}
},
},
/**
* !#en The Sprite component attached to the node for EditBox's background
* !#zh 输入框背景节点上挂载的 Sprite 组件对象
* @property {Sprite} background
*/
background: {
tooltip: CC_DEV && 'i18n:COMPONENT.editbox.background',
default: null,
type: cc.Sprite,
notify (oldValue) {
if (this.background && this.background !== oldValue) {
this._updateBackgroundSprite();
}
},
},
// To be removed in the future
_N$backgroundImage: {
default: undefined,
type: cc.SpriteFrame,
},
/**
* !#en The background image of EditBox. This property will be removed in the future, use editBox.background instead please.
* !#zh 输入框的背景图片。 该属性会在将来的版本中移除,请用 editBox.background
* @property {SpriteFrame} backgroundImage
* @deprecated since v2.1
*/
backgroundImage: {
get () {
// if (!CC_EDITOR) cc.warnID(1400, 'editBox.backgroundImage', 'editBox.background');
if (!this.background) {
return null;
}
return this.background.spriteFrame;
},
set (value) {
// if (!CC_EDITOR) cc.warnID(1400, 'editBox.backgroundImage', 'editBox.background');
if (this.background) {
this.background.spriteFrame = value;
}
},
},
/**
* !#en
* The return key type of EditBox.
* Note: it is meaningless for web platforms and desktop platforms.
* !#zh
* 指定移动设备上面回车按钮的样式。
* 注意:这个选项对 web 平台与 desktop 平台无效。
* @property {EditBox.KeyboardReturnType} returnType
* @default KeyboardReturnType.DEFAULT
*/
returnType: {
default: KeyboardReturnType.DEFAULT,
tooltip: CC_DEV && 'i18n:COMPONENT.editbox.returnType',
displayName: 'KeyboardReturnType',
type: KeyboardReturnType,
},
// To be removed in the future
_N$returnType: {
default: undefined,
type: cc.Float,
},
/**
* !#en Set the input flags that are to be applied to the EditBox.
* !#zh 指定输入标志位,可以指定输入方式为密码或者单词首字母大写。
* @property {EditBox.InputFlag} inputFlag
* @default InputFlag.DEFAULT
*/
inputFlag: {
tooltip: CC_DEV && 'i18n:COMPONENT.editbox.input_flag',
default: InputFlag.DEFAULT,
type: InputFlag,
notify () {
this._updateString(this._string);
}
},
/**
* !#en
* Set the input mode of the edit box.
* If you pass ANY, it will create a multiline EditBox.
* !#zh
* 指定输入模式: ANY表示多行输入其它都是单行输入移动平台上还可以指定键盘样式。
* @property {EditBox.InputMode} inputMode
* @default InputMode.ANY
*/
inputMode: {
tooltip: CC_DEV && 'i18n:COMPONENT.editbox.input_mode',
default: InputMode.ANY,
type: InputMode,
notify (oldValue) {
if (this.inputMode !== oldValue) {
this._updateTextLabel();
this._updatePlaceholderLabel();
}
}
},
/**
* !#en Font size of the input text. This property will be removed in the future, use editBox.textLabel.fontSize instead please.
* !#zh 输入框文本的字体大小。 该属性会在将来的版本中移除,请使用 editBox.textLabel.fontSize。
* @property {Number} fontSize
* @deprecated since v2.1
*/
fontSize: {
get () {
// if (!CC_EDITOR) cc.warnID(1400, 'editBox.fontSize', 'editBox.textLabel.fontSize');
if (!this.textLabel) {
return 0;
}
return this.textLabel.fontSize;
},
set (value) {
// if (!CC_EDITOR) cc.warnID(1400, 'editBox.fontSize', 'editBox.textLabel.fontSize');
if (this.textLabel) {
this.textLabel.fontSize = value;
}
},
},
// To be removed in the future
_N$fontSize: {
default: undefined,
type: cc.Float,
},
/**
* !#en Change the lineHeight of displayed text. This property will be removed in the future, use editBox.textLabel.lineHeight instead.
* !#zh 输入框文本的行高。该属性会在将来的版本中移除,请使用 editBox.textLabel.lineHeight
* @property {Number} lineHeight
* @deprecated since v2.1
*/
lineHeight: {
get () {
// if (!CC_EDITOR) cc.warnID(1400, 'editBox.lineHeight', 'editBox.textLabel.lineHeight');
if (!this.textLabel) {
return 0;
}
return this.textLabel.lineHeight;
},
set (value) {
// if (!CC_EDITOR) cc.warnID(1400, 'editBox.lineHeight', 'editBox.textLabel.lineHeight');
if (this.textLabel) {
this.textLabel.lineHeight = value;
}
},
},
// To be removed in the future
_N$lineHeight: {
default: undefined,
type: cc.Float,
},
/**
* !#en Font color of the input text. This property will be removed in the future, use editBox.textLabel.node.color instead.
* !#zh 输入框文本的颜色。该属性会在将来的版本中移除,请使用 editBox.textLabel.node.color
* @property {Color} fontColor
* @deprecated since v2.1
*/
fontColor: {
get () {
// if (!CC_EDITOR) cc.warnID(1400, 'editBox.fontColor', 'editBox.textLabel.node.color');
if (!this.textLabel) {
return cc.Color.BLACK;
}
return this.textLabel.node.color;
},
set (value) {
// if (!CC_EDITOR) cc.warnID(1400, 'editBox.fontColor', 'editBox.textLabel.node.color');
if (this.textLabel) {
this.textLabel.node.color = value;
this.textLabel.node.opacity = value.a;
}
},
},
// To be removed in the future
_N$fontColor: undefined,
/**
* !#en The display text of placeholder.
* !#zh 输入框占位符的文本内容。
* @property {String} placeholder
*/
placeholder: {
tooltip: CC_DEV && 'i18n:COMPONENT.editbox.placeholder',
get () {
if (!this.placeholderLabel) {
return '';
}
return this.placeholderLabel.string;
},
set (value) {
if (this.placeholderLabel) {
this.placeholderLabel.string = value;
}
}
},
// To be removed in the future
_N$placeholder: {
default: undefined,
type: cc.String,
},
/**
* !#en The font size of placeholder. This property will be removed in the future, use editBox.placeholderLabel.fontSize instead.
* !#zh 输入框占位符的字体大小。该属性会在将来的版本中移除,请使用 editBox.placeholderLabel.fontSize
* @property {Number} placeholderFontSize
* @deprecated since v2.1
*/
placeholderFontSize: {
get () {
// if (!CC_EDITOR) cc.warnID(1400, 'editBox.placeholderFontSize', 'editBox.placeholderLabel.fontSize');
if (!this.placeholderLabel) {
return 0;
}
return this.placeholderLabel.fontSize;
},
set (value) {
// if (!CC_EDITOR) cc.warnID(1400, 'editBox.placeholderFontSize', 'editBox.placeholderLabel.fontSize');
if (this.placeholderLabel) {
this.placeholderLabel.fontSize = value;
}
},
},
// To be removed in the future
_N$placeholderFontSize: {
default: undefined,
type: cc.Float,
},
/**
* !#en The font color of placeholder. This property will be removed in the future, use editBox.placeholderLabel.node.color instead.
* !#zh 输入框占位符的字体颜色。该属性会在将来的版本中移除,请使用 editBox.placeholderLabel.node.color
* @property {Color} placeholderFontColor
* @deprecated since v2.1
*/
placeholderFontColor: {
get () {
// if (!CC_EDITOR) cc.warnID(1400, 'editBox.placeholderFontColor', 'editBox.placeholderLabel.node.color');
if (!this.placeholderLabel) {
return cc.Color.BLACK;
}
return this.placeholderLabel.node.color;
},
set (value) {
// if (!CC_EDITOR) cc.warnID(1400, 'editBox.placeholderFontColor', 'editBox.placeholderLabel.node.color');
if (this.placeholderLabel) {
this.placeholderLabel.node.color = value;
this.placeholderLabel.node.opacity = value.a;
}
},
},
// To be removed in the future
_N$placeholderFontColor: undefined,
/**
* !#en The maximize input length of EditBox.
* - If pass a value less than 0, it won't limit the input number of characters.
* - If pass 0, it doesn't allow input any characters.
* !#zh 输入框最大允许输入的字符个数。
* - 如果值为小于 0 的值,则不会限制输入字符个数。
* - 如果值为 0则不允许用户进行任何输入。
* @property {Number} maxLength
*/
maxLength: {
tooltip: CC_DEV && 'i18n:COMPONENT.editbox.max_length',
default: 20,
},
// To be removed in the future
_N$maxLength: {
default: undefined,
type: cc.Float,
},
/**
* !#en The input is always visible and be on top of the game view (only useful on Web), this property will be removed on v2.1
* !zh 输入框总是可见,并且永远在游戏视图的上面(这个属性只有在 Web 上面修改有意义),该属性会在 v2.1 中移除
* Note: only available on Web at the moment.
* @property {Boolean} stayOnTop
* @deprecated since 2.0.8
*/
stayOnTop: {
default: false,
notify () {
cc.warn('editBox.stayOnTop is removed since v2.1.');
}
},
_tabIndex: 0,
/**
* !#en Set the tabIndex of the DOM input element (only useful on Web).
* !#zh 修改 DOM 输入元素的 tabIndex这个属性只有在 Web 上面修改有意义)。
* @property {Number} tabIndex
*/
tabIndex: {
tooltip: CC_DEV && 'i18n:COMPONENT.editbox.tab_index',
get () {
return this._tabIndex;
},
set (value) {
if (this._tabIndex !== value) {
this._tabIndex = value;
if (this._impl) {
this._impl.setTabIndex(value);
}
}
}
},
/**
* !#en The event handler to be called when EditBox began to edit text.
* !#zh 开始编辑文本输入框触发的事件回调。
* @property {Component.EventHandler[]} editingDidBegan
*/
editingDidBegan: {
default: [],
type: cc.Component.EventHandler,
},
/**
* !#en The event handler to be called when EditBox text changes.
* !#zh 编辑文本输入框时触发的事件回调。
* @property {Component.EventHandler[]} textChanged
*/
textChanged: {
default: [],
type: cc.Component.EventHandler,
},
/**
* !#en The event handler to be called when EditBox edit ends.
* !#zh 结束编辑文本输入框时触发的事件回调。
* @property {Component.EventHandler[]} editingDidEnded
*/
editingDidEnded: {
default: [],
type: cc.Component.EventHandler,
},
/**
* !#en The event handler to be called when return key is pressed. Windows is not supported.
* !#zh 当用户按下回车按键时的事件回调,目前不支持 windows 平台
* @property {Component.EventHandler[]} editingReturn
*/
editingReturn: {
default: [],
type: cc.Component.EventHandler
}
},
statics: {
_ImplClass: EditBoxImplBase, // implemented on different platform adapter
KeyboardReturnType: KeyboardReturnType,
InputFlag: InputFlag,
InputMode: InputMode
},
_init () {
this._upgradeComp();
this._isLabelVisible = true;
this.node.on(cc.Node.EventType.SIZE_CHANGED, this._syncSize, this);
let impl = this._impl = new EditBox._ImplClass();
impl.init(this);
this._updateString(this._string);
this._syncSize();
},
_updateBackgroundSprite () {
let background = this.background;
// If background doesn't exist, create one.
if (!background) {
let node = this.node.getChildByName('BACKGROUND_SPRITE');
if (!node) {
node = new cc.Node('BACKGROUND_SPRITE');
}
background = node.getComponent(cc.Sprite);
if (!background) {
background = node.addComponent(cc.Sprite);
}
node.parent = this.node;
this.background = background;
}
// update
background.type = cc.Sprite.Type.SLICED;
// handle old data
if (this._N$backgroundImage !== undefined) {
background.spriteFrame = this._N$backgroundImage;
this._N$backgroundImage = undefined;
}
},
_updateTextLabel () {
let textLabel = this.textLabel;
// If textLabel doesn't exist, create one.
if (!textLabel) {
let node = this.node.getChildByName('TEXT_LABEL');
if (!node) {
node = new cc.Node('TEXT_LABEL');
}
textLabel = node.getComponent(Label);
if (!textLabel) {
textLabel = node.addComponent(Label);
}
node.parent = this.node;
this.textLabel = textLabel;
}
// update
textLabel.node.setAnchorPoint(0, 1);
textLabel.overflow = Label.Overflow.CLAMP;
if (this.inputMode === InputMode.ANY) {
textLabel.verticalAlign = macro.VerticalTextAlignment.TOP;
textLabel.enableWrapText = true;
}
else {
textLabel.verticalAlign = macro.VerticalTextAlignment.CENTER;
textLabel.enableWrapText = false;
}
textLabel.string = this._updateLabelStringStyle(this._string);
// handle old data
if (this._N$fontColor !== undefined) {
textLabel.node.color = this._N$fontColor;
textLabel.node.opacity = this._N$fontColor.a;
this._N$fontColor = undefined;
}
if (this._N$fontSize !== undefined) {
textLabel.fontSize = this._N$fontSize;
this._N$fontSize = undefined;
}
if (this._N$lineHeight !== undefined) {
textLabel.lineHeight = this._N$lineHeight;
this._N$lineHeight = undefined;
}
},
_updatePlaceholderLabel () {
let placeholderLabel = this.placeholderLabel;
// If placeholderLabel doesn't exist, create one.
if (!placeholderLabel) {
let node = this.node.getChildByName('PLACEHOLDER_LABEL');
if (!node) {
node = new cc.Node('PLACEHOLDER_LABEL');
}
placeholderLabel = node.getComponent(Label);
if (!placeholderLabel) {
placeholderLabel = node.addComponent(Label);
}
node.parent = this.node;
this.placeholderLabel = placeholderLabel;
}
// update
placeholderLabel.node.setAnchorPoint(0, 1);
placeholderLabel.overflow = Label.Overflow.CLAMP;
if (this.inputMode === InputMode.ANY) {
placeholderLabel.verticalAlign = macro.VerticalTextAlignment.TOP;
placeholderLabel.enableWrapText = true;
}
else {
placeholderLabel.verticalAlign = macro.VerticalTextAlignment.CENTER;
placeholderLabel.enableWrapText = false;
}
placeholderLabel.string = this.placeholder;
// handle old data
if (this._N$placeholderFontColor !== undefined) {
placeholderLabel.node.color = this._N$placeholderFontColor;
placeholderLabel.node.opacity = this._N$placeholderFontColor.a;
this._N$placeholderFontColor = undefined;
}
if (this._N$placeholderFontSize !== undefined) {
placeholderLabel.fontSize = this._N$placeholderFontSize;
this._N$placeholderFontSize = undefined;
}
},
_upgradeComp () {
if (this._N$returnType !== undefined) {
this.returnType = this._N$returnType;
this._N$returnType = undefined;
}
if (this._N$maxLength !== undefined) {
this.maxLength = this._N$maxLength;
this._N$maxLength = undefined;
}
if (this._N$backgroundImage !== undefined) {
this._updateBackgroundSprite();
}
if (this._N$fontColor !== undefined || this._N$fontSize !== undefined || this._N$lineHeight !== undefined) {
this._updateTextLabel();
}
if (this._N$placeholderFontColor !== undefined || this._N$placeholderFontSize !== undefined) {
this._updatePlaceholderLabel();
}
if (this._N$placeholder !== undefined) {
this.placeholder = this._N$placeholder;
this._N$placeholder = undefined;
}
},
_syncSize () {
if (this._impl) {
let size = this.node.getContentSize();
this._impl.setSize(size.width, size.height);
}
},
_showLabels () {
this._isLabelVisible = true;
this._updateLabels();
},
_hideLabels () {
this._isLabelVisible = false;
if (this.textLabel) {
this.textLabel.node.active = false;
}
if (this.placeholderLabel) {
this.placeholderLabel.node.active = false;
}
},
_updateLabels () {
if (this._isLabelVisible) {
let content = this._string;
if (this.textLabel) {
this.textLabel.node.active = (content !== '');
}
if (this.placeholderLabel) {
this.placeholderLabel.node.active = (content === '');
}
}
},
_updateString (text) {
let textLabel = this.textLabel;
// Not inited yet
if (!textLabel) {
return;
}
let displayText = text;
if (displayText) {
displayText = this._updateLabelStringStyle(displayText);
}
textLabel.string = displayText;
this._updateLabels();
},
_updateLabelStringStyle (text, ignorePassword) {
let inputFlag = this.inputFlag;
if (!ignorePassword && inputFlag === InputFlag.PASSWORD) {
let passwordString = '';
let len = text.length;
for (let i = 0; i < len; ++i) {
passwordString += '\u25CF';
}
text = passwordString;
}
else if (inputFlag === InputFlag.INITIAL_CAPS_ALL_CHARACTERS) {
text = text.toUpperCase();
}
else if (inputFlag === InputFlag.INITIAL_CAPS_WORD) {
text = capitalize(text);
}
else if (inputFlag === InputFlag.INITIAL_CAPS_SENTENCE) {
text = capitalizeFirstLetter(text);
}
return text;
},
editBoxEditingDidBegan () {
cc.Component.EventHandler.emitEvents(this.editingDidBegan, this);
this.node.emit('editing-did-began', this);
},
editBoxEditingDidEnded () {
cc.Component.EventHandler.emitEvents(this.editingDidEnded, this);
this.node.emit('editing-did-ended', this);
},
editBoxTextChanged (text) {
text = this._updateLabelStringStyle(text, true);
this.string = text;
cc.Component.EventHandler.emitEvents(this.textChanged, text, this);
this.node.emit('text-changed', this);
},
editBoxEditingReturn() {
cc.Component.EventHandler.emitEvents(this.editingReturn, this);
this.node.emit('editing-return', this);
},
onEnable () {
if (!CC_EDITOR) {
this._registerEvent();
}
if (this._impl) {
this._impl.enable();
}
},
onDisable () {
if (!CC_EDITOR) {
this._unregisterEvent();
}
if (this._impl) {
this._impl.disable();
}
},
onDestroy () {
if (this._impl) {
this._impl.clear();
}
},
__preload () {
this._init();
},
_registerEvent () {
this.node.on(cc.Node.EventType.TOUCH_START, this._onTouchBegan, this);
this.node.on(cc.Node.EventType.TOUCH_END, this._onTouchEnded, this);
},
_unregisterEvent () {
this.node.off(cc.Node.EventType.TOUCH_START, this._onTouchBegan, this);
this.node.off(cc.Node.EventType.TOUCH_END, this._onTouchEnded, this);
},
_onTouchBegan (event) {
event.stopPropagation();
},
_onTouchCancel (event) {
event.stopPropagation();
},
_onTouchEnded (event) {
if (this._impl) {
this._impl.beginEditing();
}
event.stopPropagation();
},
/**
* !#en Let the EditBox get focus, this method will be removed on v2.1
* !#zh 让当前 EditBox 获得焦点, 这个方法会在 v2.1 中移除
* @method setFocus
* @deprecated since 2.0.8
*/
setFocus () {
cc.errorID(1400, 'setFocus()', 'focus()');
if (this._impl) {
this._impl.setFocus(true);
}
},
/**
* !#en Let the EditBox get focus
* !#zh 让当前 EditBox 获得焦点
* @method focus
*/
focus () {
if (this._impl) {
this._impl.setFocus(true);
}
},
/**
* !#en Let the EditBox lose focus
* !#zh 让当前 EditBox 失去焦点
* @method blur
*/
blur () {
if (this._impl) {
this._impl.setFocus(false);
}
},
/**
* !#en Determine whether EditBox is getting focus or not.
* !#zh 判断 EditBox 是否获得了焦点
* @method isFocused
*/
isFocused () {
if (this._impl) {
return this._impl.isFocused();
}
else {
return false;
}
},
update () {
if (this._impl) {
this._impl.update();
}
}
});
cc.EditBox = module.exports = EditBox;
if (cc.sys.isBrowser) {
require('./WebEditBoxImpl');
}
/**
* !#en
* Note: This event is emitted from the node to which the component belongs.
* !#zh
* 注意:此事件是从该组件所属的 Node 上面派发出来的,需要用 node.on 来监听。
* @event editing-did-began
* @param {Event.EventCustom} event
* @param {EditBox} editbox - The EditBox component.
*/
/**
* !#en
* Note: This event is emitted from the node to which the component belongs.
* !#zh
* 注意:此事件是从该组件所属的 Node 上面派发出来的,需要用 node.on 来监听。
* @event editing-did-ended
* @param {Event.EventCustom} event
* @param {EditBox} editbox - The EditBox component.
*/
/**
* !#en
* Note: This event is emitted from the node to which the component belongs.
* !#zh
* 注意:此事件是从该组件所属的 Node 上面派发出来的,需要用 node.on 来监听。
* @event text-changed
* @param {Event.EventCustom} event
* @param {EditBox} editbox - The EditBox component.
*/
/**
* !#en
* Note: This event is emitted from the node to which the component belongs.
* !#zh
* 注意:此事件是从该组件所属的 Node 上面派发出来的,需要用 node.on 来监听。
* @event editing-return
* @param {Event.EventCustom} event
* @param {EditBox} editbox - The EditBox component.
*/
/**
* !#en if you don't need the EditBox and it isn't in any running Scene, you should
* call the destroy method on this component or the associated node explicitly.
* Otherwise, the created DOM element won't be removed from web page.
* !#zh
* 如果你不再使用 EditBox并且组件未添加到场景中那么你必须手动对组件或所在节点调用 destroy。
* 这样才能移除网页上的 DOM 节点,避免 Web 平台内存泄露。
* @example
* editbox.node.parent = null; // or editbox.node.removeFromParent(false);
* // when you don't need editbox anymore
* editbox.node.destroy();
* @method destroy
* @return {Boolean} whether it is the first time the destroy being called
*/

View File

@@ -0,0 +1,87 @@
/****************************************************************************
Copyright (c) 2011-2012 cocos2d-x.org
Copyright (c) 2012 James Chen
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
let EditBoxImplBase = cc.Class({
ctor () {
this._delegate = null;
this._editing = false;
},
init (delegate) {
},
enable () {
},
disable () {
if (this._editing) {
this.endEditing();
}
},
clear () {
},
update () {
},
setTabIndex (index) {
},
setSize (width, height) {
},
setFocus (value) {
if (value) {
this.beginEditing();
}
else {
this.endEditing();
}
},
isFocused () {
return this._editing;
},
beginEditing () {
},
endEditing () {
},
});
module.exports = EditBoxImplBase;

View File

@@ -0,0 +1,723 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
import Mat4 from '../../value-types/mat4';
const utils = require('../../platform/utils');
const macro = require('../../platform/CCMacro');
const Types = require('./types');
const Label = require('../CCLabel');
const tabIndexUtil = require('./tabIndexUtil');
const EditBox = cc.EditBox;
const js = cc.js;
const InputMode = Types.InputMode;
const InputFlag = Types.InputFlag;
const KeyboardReturnType = Types.KeyboardReturnType;
// polyfill
let polyfill = {
zoomInvalid: false
};
if (cc.sys.OS_ANDROID === cc.sys.os &&
(cc.sys.browserType === cc.sys.BROWSER_TYPE_SOUGOU ||
cc.sys.browserType === cc.sys.BROWSER_TYPE_360)) {
polyfill.zoomInvalid = true;
}
// https://segmentfault.com/q/1010000002914610
const DELAY_TIME = 800;
const SCROLLY = 100;
const LEFT_PADDING = 2;
// private static property
let _domCount = 0;
let _vec3 = cc.v3();
let _currentEditBoxImpl = null;
// on mobile
let _fullscreen = false;
let _autoResize = false;
const BaseClass = EditBox._ImplClass;
// This is an adapter for EditBoxImpl on web platform.
// For more adapters on other platforms, please inherit from EditBoxImplBase and implement the interface.
function WebEditBoxImpl () {
BaseClass.call(this);
this._domId = `EditBoxId_${++_domCount}`;
this._placeholderStyleSheet = null;
this._elem = null;
this._isTextArea = false;
// matrix
this._worldMat = new Mat4();
this._cameraMat = new Mat4();
// matrix cache
this._m00 = 0;
this._m01 = 0;
this._m04 = 0;
this._m05 = 0;
this._m12 = 0;
this._m13 = 0;
this._w = 0;
this._h = 0;
// viewport cache
this._cacheViewportRect = cc.rect(0, 0, 0, 0);
// inputType cache
this._inputMode = null;
this._inputFlag = null;
this._returnType = null;
// event listeners
this._eventListeners = {};
// update style sheet cache
this._textLabelFont = null;
this._textLabelFontSize = null;
this._textLabelFontColor = null;
this._textLabelAlign = null;
this._placeholderLabelFont = null;
this._placeholderLabelFontSize = null;
this._placeholderLabelFontColor = null;
this._placeholderLabelAlign = null;
this._placeholderLineHeight = null;
}
js.extend(WebEditBoxImpl, BaseClass);
EditBox._ImplClass = WebEditBoxImpl;
Object.assign(WebEditBoxImpl.prototype, {
// =================================
// implement EditBoxImplBase interface
init (delegate) {
if (!delegate) {
return;
}
this._delegate = delegate;
if (delegate.inputMode === InputMode.ANY) {
this._createTextArea();
}
else {
this._createInput();
}
tabIndexUtil.add(this);
this.setTabIndex(delegate.tabIndex);
this._initStyleSheet();
this._registerEventListeners();
this._addDomToGameContainer();
_fullscreen = cc.view.isAutoFullScreenEnabled();
_autoResize = cc.view._resizeWithBrowserSize;
},
clear () {
this._removeEventListeners();
this._removeDomFromGameContainer();
tabIndexUtil.remove(this);
// clear while editing
if (_currentEditBoxImpl === this) {
_currentEditBoxImpl = null;
}
},
update () {
this._updateMatrix();
},
setTabIndex (index) {
this._elem.tabIndex = index;
tabIndexUtil.resort();
},
setSize (width, height) {
let elem = this._elem;
elem.style.width = width + 'px';
elem.style.height = height + 'px';
},
beginEditing () {
if (_currentEditBoxImpl && _currentEditBoxImpl !== this) {
_currentEditBoxImpl.setFocus(false);
}
this._editing = true;
_currentEditBoxImpl = this;
this._delegate.editBoxEditingDidBegan();
this._showDom();
this._elem.focus(); // set focus
},
endEditing () {
if (this._elem) {
this._elem.blur();
}
},
// ==========================================================================
// implement dom input
_createInput () {
this._isTextArea = false;
this._elem = document.createElement('input');
},
_createTextArea () {
this._isTextArea = true;
this._elem = document.createElement('textarea');
},
_addDomToGameContainer () {
cc.game.container.appendChild(this._elem);
document.head.appendChild(this._placeholderStyleSheet);
},
_removeDomFromGameContainer () {
let hasElem = utils.contains(cc.game.container, this._elem);
if (hasElem) {
cc.game.container.removeChild(this._elem);
}
let hasStyleSheet = utils.contains(document.head, this._placeholderStyleSheet);
if (hasStyleSheet) {
document.head.removeChild(this._placeholderStyleSheet);
}
delete this._elem;
delete this._placeholderStyleSheet;
},
_showDom () {
this._updateMaxLength();
this._updateInputType();
this._updateStyleSheet();
this._elem.style.display = '';
this._delegate._hideLabels();
if (cc.sys.isMobile) {
this._showDomOnMobile();
}
},
_hideDom () {
let elem = this._elem;
elem.style.display = 'none';
this._delegate._showLabels();
if (cc.sys.isMobile) {
this._hideDomOnMobile();
}
},
_showDomOnMobile () {
if (cc.sys.os !== cc.sys.OS_ANDROID) {
return;
}
if (_fullscreen) {
cc.view.enableAutoFullScreen(false);
cc.screen.exitFullScreen();
}
if (_autoResize) {
cc.view.resizeWithBrowserSize(false);
}
this._adjustWindowScroll();
},
_hideDomOnMobile () {
if (cc.sys.os === cc.sys.OS_ANDROID) {
if (_autoResize) {
cc.view.resizeWithBrowserSize(true);
}
// In case enter full screen when soft keyboard still showing
setTimeout(function () {
if (!_currentEditBoxImpl) {
if (_fullscreen) {
cc.view.enableAutoFullScreen(true);
}
}
}, DELAY_TIME);
}
// This is an outdated strategy that causes other problems on newer systems, consider removing
// this._scrollBackWindow();
},
// adjust view to editBox
_adjustWindowScroll () {
let self = this;
setTimeout(function() {
if (window.scrollY < SCROLLY) {
self._elem.scrollIntoView({block: "start", inline: "nearest", behavior: "smooth"});
}
}, DELAY_TIME);
},
_scrollBackWindow () {
setTimeout(function () {
// FIX: wechat browser bug on iOS
// If gameContainer is included in iframe,
// Need to scroll the top window, not the one in the iframe
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/Window/top
let sys = cc.sys;
if (sys.browserType === sys.BROWSER_TYPE_WECHAT && sys.os === sys.OS_IOS) {
window.top && window.top.scrollTo(0, 0);
return;
}
window.scrollTo(0, 0);
}, DELAY_TIME);
},
_updateCameraMatrix () {
let node = this._delegate.node;
node.getWorldMatrix(this._worldMat);
let worldMat = this._worldMat;
let nodeContentSize = node._contentSize,
nodeAnchorPoint = node._anchorPoint;
_vec3.x = -nodeAnchorPoint.x * nodeContentSize.width;
_vec3.y = -nodeAnchorPoint.y * nodeContentSize.height;
Mat4.transform(worldMat, worldMat, _vec3);
// can't find node camera in editor
if (CC_EDITOR) {
this._cameraMat = worldMat;
}
else {
let camera = cc.Camera.findCamera(node);
if (!camera) {
return false;
}
camera.getWorldToScreenMatrix2D(this._cameraMat);
Mat4.mul(this._cameraMat, this._cameraMat, worldMat);
}
return true;
},
_updateMatrix () {
if (CC_EDITOR || !this._updateCameraMatrix()) {
return;
}
let cameraMatm = this._cameraMat.m;
let node = this._delegate.node;
let localView = cc.view;
// check whether need to update
if (this._m00 === cameraMatm[0] && this._m01 === cameraMatm[1] &&
this._m04 === cameraMatm[4] && this._m05 === cameraMatm[5] &&
this._m12 === cameraMatm[12] && this._m13 === cameraMatm[13] &&
this._w === node._contentSize.width && this._h === node._contentSize.height &&
this._cacheViewportRect.equals(localView._viewportRect)) {
return;
}
// update matrix cache
this._m00 = cameraMatm[0];
this._m01 = cameraMatm[1];
this._m04 = cameraMatm[4];
this._m05 = cameraMatm[5];
this._m12 = cameraMatm[12];
this._m13 = cameraMatm[13];
this._w = node._contentSize.width;
this._h = node._contentSize.height;
// update viewport cache
this._cacheViewportRect.set(localView._viewportRect);
let scaleX = localView._scaleX, scaleY = localView._scaleY,
viewport = localView._viewportRect,
dpr = localView._devicePixelRatio;
scaleX /= dpr;
scaleY /= dpr;
let container = cc.game.container;
let a = cameraMatm[0] * scaleX, b = cameraMatm[1], c = cameraMatm[4], d = cameraMatm[5] * scaleY;
let offsetX = container && container.style.paddingLeft && parseInt(container.style.paddingLeft);
offsetX += viewport.x / dpr;
let offsetY = container && container.style.paddingBottom && parseInt(container.style.paddingBottom);
offsetY += viewport.y / dpr;
let tx = cameraMatm[12] * scaleX + offsetX, ty = cameraMatm[13] * scaleY + offsetY;
if (polyfill.zoomInvalid) {
this.setSize(node.width * a, node.height * d);
a = 1;
d = 1;
}
let elem = this._elem;
let matrix = "matrix(" + a + "," + -b + "," + -c + "," + d + "," + tx + "," + -ty + ")";
elem.style['transform'] = matrix;
elem.style['-webkit-transform'] = matrix;
elem.style['transform-origin'] = '0px 100% 0px';
elem.style['-webkit-transform-origin'] = '0px 100% 0px';
},
// ===========================================
// input type and max length
_updateInputType () {
let delegate = this._delegate,
inputMode = delegate.inputMode,
inputFlag = delegate.inputFlag,
returnType = delegate.returnType,
elem = this._elem;
// whether need to update
if (this._inputMode === inputMode &&
this._inputFlag === inputFlag &&
this._returnType === returnType) {
return;
}
// update cache
this._inputMode = inputMode;
this._inputFlag = inputFlag;
this._returnType = returnType;
// FIX ME: TextArea actually dose not support password type.
if (this._isTextArea) {
// input flag
let textTransform = 'none';
if (inputFlag === InputFlag.INITIAL_CAPS_ALL_CHARACTERS) {
textTransform = 'uppercase';
}
else if (inputFlag === InputFlag.INITIAL_CAPS_WORD) {
textTransform = 'capitalize';
}
elem.style.textTransform = textTransform;
return;
}
// begin to updateInputType
if (inputFlag === InputFlag.PASSWORD) {
elem.type = 'password';
elem.style.textTransform = 'none';
return;
}
// input mode
let type = elem.type;
if (inputMode === InputMode.EMAIL_ADDR) {
type = 'email';
} else if(inputMode === InputMode.NUMERIC || inputMode === InputMode.DECIMAL) {
type = 'number';
} else if(inputMode === InputMode.PHONE_NUMBER) {
type = 'number';
elem.pattern = '[0-9]*';
elem.onmousewheel = function () { return false; };
} else if(inputMode === InputMode.URL) {
type = 'url';
} else {
type = 'text';
if (returnType === KeyboardReturnType.SEARCH) {
type = 'search';
}
}
elem.type = type;
// input flag
let textTransform = 'none';
if (inputFlag === InputFlag.INITIAL_CAPS_ALL_CHARACTERS) {
textTransform = 'uppercase';
}
else if (inputFlag === InputFlag.INITIAL_CAPS_WORD) {
textTransform = 'capitalize';
}
elem.style.textTransform = textTransform;
},
_updateMaxLength () {
let maxLength = this._delegate.maxLength;
if(maxLength < 0) {
//we can't set Number.MAX_VALUE to input's maxLength property
//so we use a magic number here, it should works at most use cases.
maxLength = 65535;
}
this._elem.maxLength = maxLength;
},
// ===========================================
// style sheet
_initStyleSheet () {
let elem = this._elem;
elem.style.display = 'none';
elem.style.border = 0;
elem.style.background = 'transparent';
elem.style.width = '100%';
elem.style.height = '100%';
elem.style.active = 0;
elem.style.outline = 'medium';
elem.style.padding = '0';
elem.style.textTransform = 'none';
elem.style.position = "absolute";
elem.style.bottom = "0px";
elem.style.left = LEFT_PADDING + "px";
elem.className = "cocosEditBox";
elem.id = this._domId;
if (!this._isTextArea) {
elem.type = 'text';
elem.style['-moz-appearance'] = 'textfield';
}
else {
elem.style.resize = 'none';
elem.style.overflow_y = 'scroll';
}
this._placeholderStyleSheet = document.createElement('style');
},
_updateStyleSheet () {
let delegate = this._delegate,
elem = this._elem;
elem.value = delegate.string;
elem.placeholder = delegate.placeholder;
this._updateTextLabel(delegate.textLabel);
this._updatePlaceholderLabel(delegate.placeholderLabel);
},
_updateTextLabel (textLabel) {
if (!textLabel) {
return;
}
// get font
let font = textLabel.font;
if (font && !(font instanceof cc.BitmapFont)) {
font = font._fontFamily;
}
else {
font = textLabel.fontFamily;
}
// get font size
let fontSize = textLabel.fontSize * textLabel.node.scaleY;
// whether need to update
if (this._textLabelFont === font
&& this._textLabelFontSize === fontSize
&& this._textLabelFontColor === textLabel.fontColor
&& this._textLabelAlign === textLabel.horizontalAlign) {
return;
}
// update cache
this._textLabelFont = font;
this._textLabelFontSize = fontSize;
this._textLabelFontColor = textLabel.fontColor;
this._textLabelAlign = textLabel.horizontalAlign;
let elem = this._elem;
// font size
elem.style.fontSize = `${fontSize}px`;
// font color
elem.style.color = textLabel.node.color.toCSS();
// font family
elem.style.fontFamily = font;
// text-align
switch(textLabel.horizontalAlign) {
case Label.HorizontalAlign.LEFT:
elem.style.textAlign = 'left';
break;
case Label.HorizontalAlign.CENTER:
elem.style.textAlign = 'center';
break;
case Label.HorizontalAlign.RIGHT:
elem.style.textAlign = 'right';
break;
}
// lineHeight
// Can't sync lineHeight property, because lineHeight would change the touch area of input
},
_updatePlaceholderLabel (placeholderLabel) {
if (!placeholderLabel) {
return;
}
// get font
let font = placeholderLabel.font;
if (font && !(font instanceof cc.BitmapFont)) {
font = placeholderLabel.font._fontFamily;
}
else {
font = placeholderLabel.fontFamily;
}
// get font size
let fontSize = placeholderLabel.fontSize * placeholderLabel.node.scaleY;
// whether need to update
if (this._placeholderLabelFont === font
&& this._placeholderLabelFontSize === fontSize
&& this._placeholderLabelFontColor === placeholderLabel.fontColor
&& this._placeholderLabelAlign === placeholderLabel.horizontalAlign
&& this._placeholderLineHeight === placeholderLabel.fontSize) {
return;
}
// update cache
this._placeholderLabelFont = font;
this._placeholderLabelFontSize = fontSize;
this._placeholderLabelFontColor = placeholderLabel.fontColor;
this._placeholderLabelAlign = placeholderLabel.horizontalAlign;
this._placeholderLineHeight = placeholderLabel.fontSize;
let styleEl = this._placeholderStyleSheet;
// font color
let fontColor = placeholderLabel.node.color.toCSS();
// line height
let lineHeight = placeholderLabel.fontSize; // top vertical align by default
// horizontal align
let horizontalAlign;
switch (placeholderLabel.horizontalAlign) {
case Label.HorizontalAlign.LEFT:
horizontalAlign = 'left';
break;
case Label.HorizontalAlign.CENTER:
horizontalAlign = 'center';
break;
case Label.HorizontalAlign.RIGHT:
horizontalAlign = 'right';
break;
}
styleEl.innerHTML = `#${this._domId}::-webkit-input-placeholder,#${this._domId}::-moz-placeholder,#${this._domId}:-ms-input-placeholder` +
`{text-transform: initial; font-family: ${font}; font-size: ${fontSize}px; color: ${fontColor}; line-height: ${lineHeight}px; text-align: ${horizontalAlign};}`;
// EDGE_BUG_FIX: hide clear button, because clearing input box in Edge does not emit input event
// issue refference: https://github.com/angular/angular/issues/26307
if (cc.sys.browserType === cc.sys.BROWSER_TYPE_EDGE) {
styleEl.innerHTML += `#${this._domId}::-ms-clear{display: none;}`;
}
},
// ===========================================
// handle event listeners
_registerEventListeners () {
let impl = this,
elem = this._elem,
inputLock = false,
cbs = this._eventListeners;
cbs.compositionStart = function () {
inputLock = true;
};
cbs.compositionEnd = function () {
inputLock = false;
impl._delegate.editBoxTextChanged(elem.value);
};
cbs.onInput = function () {
if (inputLock) {
return;
}
// input of number type doesn't support maxLength attribute
let maxLength = impl._delegate.maxLength;
if (maxLength >= 0) {
elem.value = elem.value.slice(0, maxLength);
}
impl._delegate.editBoxTextChanged(elem.value);
};
// There are 2 ways to focus on the input element:
// Click the input element, or call input.focus().
// Both need to adjust window scroll.
cbs.onClick = function (e) {
// In case operation sequence: click input, hide keyboard, then click again.
if (impl._editing) {
if (cc.sys.isMobile) {
impl._adjustWindowScroll();
}
}
};
cbs.onKeydown = function (e) {
if (e.keyCode === macro.KEY.enter) {
e.stopPropagation();
impl._delegate.editBoxEditingReturn();
if (!impl._isTextArea) {
elem.blur();
}
}
else if (e.keyCode === macro.KEY.tab) {
e.stopPropagation();
e.preventDefault();
tabIndexUtil.next(impl);
}
};
cbs.onBlur = function () {
// on mobile, sometimes input element doesn't fire compositionend event
if (cc.sys.isMobile && inputLock) {
cbs.compositionEnd();
}
impl._editing = false;
_currentEditBoxImpl = null;
impl._hideDom();
impl._delegate.editBoxEditingDidEnded();
};
elem.addEventListener('compositionstart', cbs.compositionStart);
elem.addEventListener('compositionend', cbs.compositionEnd);
elem.addEventListener('input', cbs.onInput);
elem.addEventListener('keydown', cbs.onKeydown);
elem.addEventListener('blur', cbs.onBlur);
elem.addEventListener('touchstart', cbs.onClick);
},
_removeEventListeners () {
let elem = this._elem,
cbs = this._eventListeners;
elem.removeEventListener('compositionstart', cbs.compositionStart);
elem.removeEventListener('compositionend', cbs.compositionEnd);
elem.removeEventListener('input', cbs.onInput);
elem.removeEventListener('keydown', cbs.onKeydown);
elem.removeEventListener('blur', cbs.onBlur);
elem.removeEventListener('touchstart', cbs.onClick);
cbs.compositionStart = null;
cbs.compositionEnd = null;
cbs.onInput = null;
cbs.onKeydown = null;
cbs.onBlur = null;
cbs.onClick = null;
},
});

View File

@@ -0,0 +1,39 @@
const tabIndexUtil = {
_tabIndexList: [],
add (editBoxImpl) {
let list = this._tabIndexList;
let index = list.indexOf(editBoxImpl);
if (index === -1){
list.push(editBoxImpl);
}
},
remove (editBoxImpl) {
let list = this._tabIndexList;
let index = list.indexOf(editBoxImpl);
if (index !== -1) {
list.splice(index, 1);
}
},
resort () {
this._tabIndexList.sort(function(a, b) {
return a._delegate._tabIndex - b._delegate._tabIndex;
});
},
next (editBoxImpl) {
let list = this._tabIndexList;
let index = list.indexOf(editBoxImpl);
editBoxImpl.setFocus(false);
if (index !== -1) {
let nextImpl = list[index+1];
if (nextImpl && nextImpl._delegate._tabIndex >= 0) {
nextImpl.setFocus(true);
}
}
},
}
module.exports = tabIndexUtil;

View File

@@ -0,0 +1,187 @@
/****************************************************************************
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
/**
* !#en Enum for keyboard return types
* !#zh 键盘的返回键类型
* @readonly
* @enum EditBox.KeyboardReturnType
*/
let KeyboardReturnType = cc.Enum({
/**
* !#en TODO
* !#zh 默认
* @property {Number} DEFAULT
*/
DEFAULT: 0,
/**
* !#en TODO
* !#zh 完成类型
* @property {Number} DONE
*/
DONE: 1,
/**
* !#en TODO
* !#zh 发送类型
* @property {Number} SEND
*/
SEND: 2,
/**
* !#en TODO
* !#zh 搜索类型
* @property {Number} SEARCH
*/
SEARCH: 3,
/**
* !#en TODO
* !#zh 跳转类型
* @property {Number} GO
*/
GO: 4,
/**
* !#en TODO
* !#zh 下一个类型
* @property {Number} NEXT
*/
NEXT: 5
});
/**
* !#en The EditBox's InputMode defines the type of text that the user is allowed to enter.
* !#zh 输入模式
* @readonly
* @enum EditBox.InputMode
*/
let InputMode = cc.Enum({
/**
* !#en TODO
* !#zh 用户可以输入任何文本,包括换行符。
* @property {Number} ANY
*/
ANY: 0,
/**
* !#en The user is allowed to enter an e-mail address.
* !#zh 允许用户输入一个电子邮件地址。
* @property {Number} EMAIL_ADDR
*/
EMAIL_ADDR: 1,
/**
* !#en The user is allowed to enter an integer value.
* !#zh 允许用户输入一个整数值。
* @property {Number} NUMERIC
*/
NUMERIC: 2,
/**
* !#en The user is allowed to enter a phone number.
* !#zh 允许用户输入一个电话号码。
* @property {Number} PHONE_NUMBER
*/
PHONE_NUMBER: 3,
/**
* !#en The user is allowed to enter a URL.
* !#zh 允许用户输入一个 URL。
* @property {Number} URL
*/
URL: 4,
/**
* !#en
* The user is allowed to enter a real number value.
* This extends kEditBoxInputModeNumeric by allowing a decimal point.
* !#zh
* 允许用户输入一个实数。
* @property {Number} DECIMAL
*/
DECIMAL: 5,
/**
* !#en The user is allowed to enter any text, except for line breaks.
* !#zh 除了换行符以外,用户可以输入任何文本。
* @property {Number} SINGLE_LINE
*/
SINGLE_LINE: 6
});
/**
* !#en Enum for the EditBox's input flags
* !#zh 定义了一些用于设置文本显示和文本格式化的标志位。
* @readonly
* @enum EditBox.InputFlag
*/
let InputFlag = cc.Enum({
/**
* !#en
* Indicates that the text entered is confidential data that should be
* obscured whenever possible. This implies EDIT_BOX_INPUT_FLAG_SENSITIVE.
* !#zh
* 表明输入的文本是保密的数据,任何时候都应该隐藏起来,它隐含了 EDIT_BOX_INPUT_FLAG_SENSITIVE。
* @property {Number} PASSWORD
*/
PASSWORD: 0,
/**
* !#en
* Indicates that the text entered is sensitive data that the
* implementation must never store into a dictionary or table for use
* in predictive, auto-completing, or other accelerated input schemes.
* A credit card number is an example of sensitive data.
* !#zh
* 表明输入的文本是敏感数据,它禁止存储到字典或表里面,也不能用来自动补全和提示用户输入。
* 一个信用卡号码就是一个敏感数据的例子。
* @property {Number} SENSITIVE
*/
SENSITIVE: 1,
/**
* !#en
* This flag is a hint to the implementation that during text editing,
* the initial letter of each word should be capitalized.
* !#zh
* 这个标志用来指定在文本编辑的时候,是否把每一个单词的首字母大写。
* @property {Number} INITIAL_CAPS_WORD
*/
INITIAL_CAPS_WORD: 2,
/**
* !#en
* This flag is a hint to the implementation that during text editing,
* the initial letter of each sentence should be capitalized.
* !#zh
* 这个标志用来指定在文本编辑是否每个句子的首字母大写。
* @property {Number} INITIAL_CAPS_SENTENCE
*/
INITIAL_CAPS_SENTENCE: 3,
/**
* !#en Capitalize all characters automatically.
* !#zh 自动把输入的所有字符大写。
* @property {Number} INITIAL_CAPS_ALL_CHARACTERS
*/
INITIAL_CAPS_ALL_CHARACTERS: 4,
/**
* Don't do anything with the input text.
* @property {Number} DEFAULT
*/
DEFAULT: 5
});
module.exports = {
KeyboardReturnType: KeyboardReturnType,
InputMode: InputMode,
InputFlag: InputFlag
};

View File

@@ -0,0 +1,70 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
require('./CCComponent');
require('./CCComponentEventHandler');
require('./missing-script');
// In case subContextView modules are excluded
let SubContextView = require('./SubContextView');
if (!SubContextView) {
SubContextView = cc.Class({
name: 'cc.SubContextView',
extends: cc.Component,
});
cc.SubContextView = cc.WXSubContextView = cc.SwanSubContextView = SubContextView;
}
var components = [
require('./CCSprite'),
require('./CCWidget'),
require('./CCCanvas'),
require('./CCAudioSource'),
require('./CCAnimation'),
require('./CCButton'),
require('./CCLabel'),
require('./CCProgressBar'),
require('./CCMask'),
require('./CCScrollBar'),
require('./CCScrollView'),
require('./CCPageViewIndicator'),
require('./CCPageView'),
require('./CCSlider'),
require('./CCLayout'),
require('./editbox/CCEditBox'),
require('./CCLabelOutline'),
require('./CCLabelShadow'),
require('./CCRichText'),
require('./CCToggleContainer'),
require('./CCToggleGroup'),
require('./CCToggle'),
require('./CCBlockInputEvents'),
require('./CCMotionStreak'),
require('./CCSafeArea'),
SubContextView,
];
module.exports = components;

View File

@@ -0,0 +1,101 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
var js = cc.js;
/*
* A temp fallback to contain the original serialized data which can not be loaded.
* Deserialized as a component by default.
*/
var MissingScript = cc.Class({
name: 'cc.MissingScript',
extends: cc.Component,
editor: {
inspector: 'packages://inspector/inspectors/comps/missing-script.js',
},
properties: {
//_scriptUuid: {
// get: function () {
// var id = this._$erialized.__type__;
// if (Editor.Utils.UuidUtils.isUuid(id)) {
// return Editor.Utils.UuidUtils.decompressUuid(id);
// }
// return '';
// },
// set: function (value) {
// if ( !sandbox.compiled ) {
// cc.error('Scripts not yet compiled, please fix script errors and compile first.');
// return;
// }
// if (value && Editor.Utils.UuidUtils.isUuid(value._uuid)) {
// var classId = Editor.Utils.UuidUtils.compressUuid(value);
// if (cc.js._getClassById(classId)) {
// this._$erialized.__type__ = classId;
// Editor.Ipc.sendToWins('reload:window-scripts', sandbox.compiled);
// }
// else {
// cc.error('Can not find a component in the script which uuid is "%s".', value);
// }
// }
// else {
// cc.error('invalid script');
// }
// }
//},
compiled: {
default: false,
serializable: false
},
// the serialized data for original script object
_$erialized: {
default: null,
visible: false,
editorOnly: true
}
},
ctor: CC_EDITOR && function () {
this.compiled = _Scene.Sandbox.compiled;
},
statics: {
/*
* @param {string} id
* @return {function} constructor
*/
safeFindClass: function (id) {
var cls = js._getClassById(id);
if (cls) {
return cls;
}
cc.deserialize.reportMissingClass(id);
return MissingScript;
},
},
onLoad: function () {
cc.warnID(4600, this.node.name);
}
});
cc._MissingScript = module.exports = MissingScript;